Nov 19, 2011

Android :: wrong image / data in ListView rows

Android's ListView reuses list items when they aren't need anymore. For this reason, you need to make sure all views that should change, will actually get changed. Your problem is that if you don't find a drawable for the current list item, you don't empty nor hide the ImageView. You should do thumb.setImageDrawable(null) in that case, or thumb.setVisibility(View.GONE).
- From stackoverflow

Example code:

     public View getView(int pos, View v, ViewGroup parent) {
      TimetableViewHolder timetableHolder;
         if (v == null) {
             LayoutInflater vi = ((Activity)context).getLayoutInflater();
             v = vi.inflate(R.layout.timetable_item, parent, false);
             timetableHolder = new TimetableViewHolder();
             timetableHolder.rel = (TextView) v.findViewById(;
             timetableHolder.time = (TextView) v.findViewById(;
             timetableHolder.icon = (ImageView) v.findViewById(;
         } else {
          timetableHolder = (TimetableViewHolder) v.getTag(); 

         TimetableItem item = items.get(pos);

         if(item != null) {
          if(item.url.equals("ERROR")) {
          } else if(item.url.equals("PRESTOP")) {
          } else { // THIS IS IMPORTANT

         return v;

Android search dialog doesn't appear

Have a problem?
The search dialog doesn't show up?

I just thought you should know, I found this interesting comment on stackoverflow:

oh my god. this is f*ing stupid. i had to use android:label="@string/xyz" instead of android:label="lol" and no single word about this in the documentation. unbelievable

 This is my manifest:

<activity android:name=".SearchableActivity" >
 <intent-filter >
  <action android:name="android.intent.action.SEARCH" />

  android:resource="@xml/searchable" />
 android:label="Preferred stations"
 android:theme="@android:style/Theme.NoTitleBar" >
  android:value=".SearchableActivity" />
And searchable
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android=""
    android:hint="@string/search_hint" >

Android :: google maps (MapView) hacks, tricks, workarounds

MapView isn't very developer friendly now is it?

Here are some hacks I've had to work with:

1) When tapping on the overlay, the app crashes if you're trying to display the dialog.
Unable to add window -- token null is not for an application
Yes, this is due to context. Especially if you're trying to do this in a seperate thread or AsyncTask.
Turns out the context you need to pass to your dialog is mapView.getContext();
AlertDialog.Builder dialog = new AlertDialog.Builder(mapView.getContext());

2) Displaying only the overlays which are within map bounds
Oh yeah, several problems here. You've probably come across at least one of these:

  • Wrong map bounds in onCreate (0, 360000000),
    Yeah, in onCreate those haven't been calculated yet. onStart doesn't help either. Try this:
Runnable waitForMapTimeTask = new Runnable() {
  public void run() {
    if(mapView.getLatitudeSpan()==0||mapView.getLongitudeSpan()== 360000000) {
      mapView.postDelayed(this, 100);
    } else {
      redrawMarkers(); // draw here
mapView.postDelayed(waitForMapTimeTask, 100);

You create a new thread and wait until you get the right bounds. Recursively call it again.
  • Map bounds ???
    Don't worry, it's simple.
public Rect getMapBounds() {
return new Rect(
mapView.getMapCenter().getLongitudeE6() - mapView.getLongitudeSpan()/2,
mapView.getMapCenter().getLatitudeE6() - mapView.getLatitudeSpan()/2,
mapView.getMapCenter().getLongitudeE6() + mapView.getLongitudeSpan()/2,
mapView.getMapCenter().getLatitudeE6() + mapView.getLatitudeSpan()/2

if(!s.drawn && rect.contains(point.getLongitudeE6(), point.getLatitudeE6())) {

  • Yeah, okay, but what about panning / zooming?
    Well, there are no methods, like onPan or onZoom, but some people found their way around this problem. There is no perfect solution, you'll see.
    Check these links out:
  • Zoom in on double tap?
    Click here:
  • Overlays don't get drawn immediately!
    Try this:
    or this:

    But keep in mind, that invalidate() needs to be called from an UI! If you're trying to get it working from a thread, use postInvalidate()!
  • MapView java.util.ConcurrentModificationException when adding new overlays
    Not sure if I solved this one, but it seems to work now. I read somewhere that this could happen if you add overlays in a non UI thread. I moved the 
    Nope, sorry, this one was my bad. I was doing some crazy async sorting and all hell broke loose.

From doInBackground to  onPostExecute in  AsyncTask.

You could also run something in a UI thread like this:

runOnUiThread(new Runnable() {
       public void run() {
           //do stuff here

Nov 18, 2011

Android :: google maps on double tap zoom in

How hard is it to make a simple zoom in call on double tap in MapView in Android?

Not very.

How hard is it to get the information on how to do it?


Here's what you probably didn't know:
You need to extend the MapView and use this extended class in the Android XML layout file.
In the extended class you instantiate the gesture detector and set on double tap listener.
In the Map Activity you implement OnGestureListener and OnDoubleTapListener.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android=""
    android:gravity="center_horizontal" >

package net.kafol.vlaki;

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.MotionEvent;
import android.view.GestureDetector.OnGestureListener;

public class ExtMapView extends MapView {
 private Context context;
 private GestureDetector gestureDetector;

 public ExtMapView(Context c, AttributeSet attrs) {
  super(c, attrs);
  context = c;

  gestureDetector = new GestureDetector((OnGestureListener) context);
  gestureDetector.setOnDoubleTapListener((OnDoubleTapListener) context);

 public boolean onTouchEvent(MotionEvent ev) {
  if (this.gestureDetector.onTouchEvent(ev))
   return true;
   return super.onTouchEvent(ev);
public class Map extends MapActivity implements OnGestureListener, OnDoubleTapListener {
 public boolean onDoubleTap(MotionEvent e) {
     int x = (int)e.getX(), y = (int)e.getY();;
     Projection p = mapView.getProjection();
     mapView.getController().animateTo(p.fromPixels(x, y)); // zoom in to a point you tapped 
  return true;

Android : Checkbox ListView - (un) check all

This recycling of views in android is pretty insane. Pain to work with.

So the problem is how to check all check boxes in a list view, if a list view only contains the visible items.

Iterating through the adapter or array of holders was pretty unreliable, some checkboxes weren't affected.

What I did was actually add an attribute to the data object and iterate through this object array (the same way it get's added in the adapter)-

Here are some functions:
 public void toggleCheck(Boolean val) {
  for(Station s : stations.list) {
   s.checked = val;
   editor.putBoolean("PF_"+s.getID(), val);
  for(int i=0 ; i < lv.getChildCount() ; i++) {
   CheckBox cb = (CheckBox) lv.getChildAt(i).findViewById(;

    private class StationCBViewHolder {
        public CheckBox cb;
        public Station s;
 private class StationListAdapter extends ArrayAdapter {
     private ArrayList items;
     private Context context;
     public StationListAdapter(Context context, int tvResId, ArrayList items) {
         super(context, tvResId, items);
         this.items = items;
         this.context = context;

     public View getView(int pos, View v, ViewGroup parent) {
      final StationCBViewHolder holder;
      final Station item = items.get(pos);
         if (v == null) {
             LayoutInflater vi = ((Activity)context).getLayoutInflater();
             v = vi.inflate(R.layout.stationcheckboxitem, parent, false);
             holder = new StationCBViewHolder();
             holder.cb = (CheckBox) v.findViewById(;
             holder.s = item;
             item.checked = prefs.getBoolean("PF_"+item.getID(),true);
             holder.cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
     public void onCheckedChanged(CompoundButton v, boolean isChecked) {
      Station s = (Station) v.getTag();
      editor.putBoolean("PF_"+s.getID(), isChecked);
         } else {
          holder = (StationCBViewHolder) v.getTag(); 


         return v;


Full code here:

The code still has some bugs related to SharedPreferences and SharedPreferences.Editor, but at least it checks and unchecks all checkboxes.

Sep 14, 2011

URL shortening - make your own URL shortener

If you ever thought about URL shortening, you've probably already figured out, how it works.

Those random letters and numbers which serve as a key (in hashtable data structures) are basically encoded integers, which are AUTO_INCREMENT values in the database.

Here are the encoding and decoding functions:
const ALLOWED_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

function id2short($integer, $base = self::ALLOWED_CHARS) {
	$out = '';
	$length = strlen($base);
	while($integer > $length-1) {
		$out = $base[fmod($integer, $length)].$out;
		$integer = floor( $integer / $length);
	return $base[$integer].$out;

function short2id($string, $base = self::ALLOWED_CHARS) {
	$length = strlen($base);
	$size = strlen($string)-1;
	$string = str_split($string);
	$out = strpos($base, array_pop($string));
	foreach($string as $i=>$char) {
		$out += strpos($base, $char) * pow($length, $size - $i);
	return $out;

Full code here.

Sep 3, 2011

MySQL error 1236: Client requested master to start replication from impossible position

[1236] Got fatal error 1236 from master when reading data from binary log: 'Client requested master to start replication from impossible position'

110902 16:47:08 [ERROR] Error reading packet from server: Client requested master to start replication from impossible position ( server_errno=1236)
110902 16:47:08 [ERROR] Slave I/O: Got fatal error 1236 from master when reading data from binary log: 'Client requested master to start replication from impossible position', Error_code: 1236
110902 16:47:08 [Note] Slave I/O thread exiting, read up to log 'mysql-bin.000033', position 4621679

on the master
[email protected]:/var/lib/mysql# ls -la mysql-bin.000033
-rw-rw---- 1 mysql mysql 4620018 2011-09-01 13:45 mysql-bin.000033

4620018 is less than 4621679, therefore it's an invalid position.

Master server has crashed and the binlog cache has not been flushed to disk. Slave has recieved a new position, did not recieve data, and data gets lost in a crash (however it might have been written to table, but not in binlog).

Use this CHANGE MASTER statement on the slave.

in my case that would be


I don't know why the master log position needs to be 4 for the new file.

What happens:
When the master server restarts it logs binary changes to a new binlog file, so that we minimize data loss by skipping to the next file (everything from the previous file was written already).

Add this line to my.cnf:

sync_binlog = 1

 With this setting the master server flushes cache in the binlog after every write, so that in case of a crash you can lose one statement at most.

Jul 28, 2011

Facebook php-sdk 3.0 changes getLoginURL()

Somehow these frequent changes are still poorly documented. Documentation is scattered across forums, changelogs, code comments and docs pages. Never on the single place. This is horrible. I'm sorry Facebook people, but this makes you look incompetent.

The most obvious change is the abandonment of the getSession method. I have also found out that getLoginURL has been changed.
'req_perms' has been changed to 'scope'
'next' has been changed to 'redirect_uri'
'cancel_url' option has been removed. You'll have to find another way. If the user denies access it will be redirected to 'redirect_uri'. The user will also be redirected to 'redirect_uri' if he clicks allow.
You can know if the user denied access by looking at these GET parameters:
[error_reason] => user_denied 
[error] => access_denied 
[error_description] => The user denied your request.
Also worth knowing is that redirect_uri will not work every time on a single page load (without refreshing the page).
The first time the users sees the oauth dialog there will be two options: Allow and Deny. Both buttons will redirect to 'redirect_uri'.
The second time the user sees the oauth dialog, Deny option will be renamed to 'Leave app'. 'redirect_uri' will still work.
The third time and so on, the button 'Leave app' will redirect to

While this may not be entirely precise, it is true that eventually the Deny/Leave app button will not follow the redirect_uri parameter.

Jul 24, 2011

SiOL TV, dve mrežni kartici, VLC player, Windows 7 x64-bit

V primeru da vam IPTv ne deluje, če imate dve mrežni kartici (eno za internet in drugo za TV), poskusite pri mrežnem adapterju za TV nastaviti metriko na 2.

Ne pozabite na ostale nastavitve za vzpostavitev TV na VLC playerju:
-odprite VLC in pritisnite CTRL+P (Preferences)
-spodaj levo obkljukate Show settings "All"-s tem se omogočijo napredne nastavitve
-v meniju gremo na Input/Codecs - na desni pri UDP portu vnesemo pravi port, ki je za t2 "5000", pri MTU pa imam "1492", potem pa še obkljukamo "Force IPv4"
-gremo v meni "Video" in nato kliknemo "output modules" - pri "Video output modules" izberemo možnost iz seznama "OpenGL video output"
-nato v levem meniju izberemo meni "Stream output" in nato "Access output"
-V Access output sem v okence kjer piše "Multicast output interface" vpisal IP mrežne kartice, ki jo uporabljam za internet, v okence "IPv4 multicast output interface adress" pa sem vpisal IP mrežne kartice, ki jo uporabljam zgolj za IPTV
-zadevo shranite in ponovno zaženite program.

M3U file editor

M3U are playlist files. I have developed this simple editor for a friend, that needed to edit IPTv playlist files.

The editor includes mass (bulk) edits as well as item editor. You can upload and export the same filetype.

You can check out the editor here:

M3U files are easy to parse:
class m3u {
 public $data = array();
 public $name = '';
 public function __construct($data = null) {
  if(!is_null($data)) {
 public function load($data) {
  if(!is_array($data)) explode("\n",$data);
  foreach($data as $i=>$line) {
   if(preg_match('/^#EXTNAME:(.+)/is',$line,$m)) {
    $this->name = clean($m[1]);
   if(preg_match('/^#EXTINF:(\d+),(.+)/is',$line,$m)) {
    $item = new m3uitem();
    $item->length = intval($m[1]);
    if(preg_match('/^#EXTTV:(.+)/is',$data[$i+1],$m)) {
    $item->file = trim($data[$i+2]);
    $this->data[] = $item;
 public function sort() {
  usort($this->data, array(__CLASS__,'cmp'));
 public static function cmp($a,$b) {
  if($a->sort == $b->sort) {
   return 0;
  return ($a->sort > $b->sort) ? +1 : -1;
 public function export() {
  $r = "#EXTM3U\n#EXTNAME:{$this->name}\n\n";
  foreach($this->data as $d) {
   $r .= "#EXTINF:{$d->length},{$d->getName()}\n";
   if($d->getCategories() != '') {
    $r .= "#EXTTV:{$d->getCategories()}\n";
   $r .= "{$d->file}\n\n";
  return $r;

class m3uitem {
 public $sort = 0;
 public $length = 0;
 public $file = '';
 private $name = '';
 private $categories = array();
 public function __construct() {}
 public function getName() { 
  return $this->name; 
 public function setName($name) {
  $this->name = clean($name);
 public function getCategories() {
  return implode(';',$this->categories);
 public function setCategories($data) {
  $this->categories = is_array($data) ? $data : explode(';',clean($data));
  foreach($this->categories as $i=>$cat) {
   if(empty($cat)) {
   } else {
    $this->categories[$i] = clean($this->categories[$i]);

Jul 19, 2011

Automatically download subtitles in VLC player

You can write VideoLAN VLC player's extensions in LUA programming language.
Extensions are in Videolan\VLC directory lua\extensions directory.

This is a modified extension to automatically download subtitles.

Download extension
Save extension (the code/text file) as AutoSubtitles.lua in the extensions directory mentioned above.

Will add more languages upon request.
Currently this extension is intended for personal use.


PHP url shortener

Here's an implementation of Google's url shortener:
class googl {
 const api = '';
 private $key = null;
 public function __construct($key = null) {
  if(defined('GOOGLE_API_KEY')) {
  if(!is_null($key)) {
 public function setKey($key) {
  $this->key = $key;
 public function s($url) {
  $data = $this->shorten($url);
  return isset($data->id) ? $data->id : $url;
 public function shorten($url) {
  $key = '';
  $data = array();
  $data['longUrl'] = $url;
  if(!is_null($this->key)) {
   $key = '?key='.$this->key;
  return $this->fetch(self::api.$key,$data);
 public function expand($url) {
  $key = is_null($this->key) ? '' : "&key={$this->key}";
  return $this->fetch(self::api.'?shortUrl='.urlencode($url)."$key&projection=FULL");
 private function fetch($url, $data = array()) {
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json'));
  if(!empty($data)) {
   curl_setopt($ch, CURLOPT_POST, 1);
   curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
  $r = curl_exec($ch);
  return json_decode($r);

May 25, 2011

Disable Ubuntu Unity Launcher

I guess I'm the only one that hates launchers in desktop environment systems. They're too dumbed down and I can't tell which apps are running and which are just shortcuts.

The easiest way to disable the Unity Netbook Launcher in Ubuntu is to log out from the session, click on the user name and before you enter the password, click on the bottom on the screen and switch to Ubuntu classic

Apr 27, 2011

Custom page numbering in MS Word

I'm sure most of you came across this problem before - how to add custom page numbering.

I don't really like making seperate documents for the pages I don't want numbered and options in MS Word are preety limited. You can set up the numbering to start with a different number. You can't, however, set it up to start with a negative number.

Example: You have the first three pages you don't want numbered (front page, page index, thanks). Everything after those pages is numbered and starts with 1.

- Go to the header/footer - wherever you want to set up page numbering.
- Add page numbering, set it up to start with zero and check the option to have a different header/footer on the first page. This way the front page will have the page number zero, but you can delete numbering for that page.
- Now you need to "extend" the first page, so that the second (or third) page will also be unnumbered. You can do this with page breaks, go to Insert -> page break. There are a couple of different page breaks, choose the one that fits your needs.

There's an other way to do a completely custom page numbering, but it involves a tiny amount of "programming" and it won't work the same if you add page index, but here goes:

- Go to the header/footer
- Retype this code for the example above:
{ IF { PAGE } < 4 "" "{ = { PAGE } - 3 }" }

The important thing here is that you need to press CTRL+F9 to get the curly braces. If you retype them, it won't work.
- After you've written the conditions for page numbering, right-click on it and choose "Update field".

{ IF { PAGE } < 4 "" "{ = { PAGE } - 3 }" }
This is a simple conditional statement, if the current page number is less than four, the first argument (The empty "" string) will be used, otherwise it will calculate the current page number minus three.

Here are some other examples:
{ IF { PAGE } < 3 "" "{ = { PAGE } - 2 }" }
Skip the first two pages

{ IF { PAGE } < 3 "{ PAGE }" "{ = { PAGE } + 5 }" }
Number the first two pages normally, then add 5 to the current page number.

{ IF { PAGE } < 3 "{ PAGE }" "" }
Add a page number only to the first two pages.


Apr 25, 2011

PHP: in_range()

function in_range($num,$low,$high,$step=1) {
    return in_array($num,range($low,$high,$step));

Apr 16, 2011

jQuery cycle flash - swf file reloads

Flash object gets reloaded if you use jQuery functions .hide() and .show() or to be exact, setting the css property display from 'block' to 'none' resets the swf (flash) objects.

This topic has some info on how to hide a flash object without resetting it.

jQuery Cycle plugin hides slides with .hide(), so I changed all .hide(), .show() functions and .display properties to use the 'visibility' property. The topic mentioned above says that this is not a cross-browser solution, so it needs some testing.

Here's the fixed code

Apr 14, 2011

Exporting wireless network passwords in windows

Open command prompt as an administrator

netsh wlan export profile folder=C:\

To import, type:

netsh wlan add profile filename="c:\Wireless Network Connection-whatever.xml" user=all

Mar 18, 2011

Configuring repcached service on Debian/Ubuntu

Repcached is a slightly modified version of memcached, that supports replication of data between two repcached nodes.

Let's say you've read all about these two and you know the benefits of replication and why this article could be useful to you.

One example of why replicated memcache could be useful are replicated PHP sessions between servers.

If you want to configure that PHP sessions are stored in memcache's memory, you need to edit these settings in /etc/php5/apache2/php.ini:
session.save_handler = memcache
session.save_path = "tcp://IP_OF_REPCACHE_1:11311, tcp://IP_OF_REPCACHE_2:11311"
and these optional settings in /etc/php5/apache2/conf.d /memcache.ini:
memcache.allow_failover setting is used if one of the servers becomes unreachable, so there is an automatic failover.
Read more about configuring PHP sessions in memcached here.

Now, let's set up repcached to start on boot and System V init scripts, so you can easly start and stop the daemon with the service command.

Steps described here imitate memcached's default configuration in great detail, so you shoud set up memcached before repcached.
sudo apt-get install memcached

Obtain, configure, compile and install repcached. There is a dependancy with libevent-dev for repcached.
sudo apt-get install libevent-dev
tar xvf memcached-1.2.8-repcached-2.2.tar 
cd memcached-1.2.8-repcached-2.2/
./configure --enable-replication
make install

(Read this if it won't compile)

At this point you have two installations of memcached. Default memcached that came from apt packages, which is installed in /usr/bin/memcached and repcached, that installed itself in /usr/local/bin/memcached, leaving the original memcached intact.

Now that we have both versions installed, we can copy memcached's default settings and init script and modify them to use repcached. This way you can quickly switch between versions. I would even recommend using default ports (just remember to firewall them!) Arguments are saved in /etc/memcached.conf, so we will create /etc/repcached.conf

See example here.

Note that the only differences with memcached.conf is the name (repcached) and two extra arguments: -x for the server IP and -X for replication port.

Memcached has an enable/disable config in /etc/default so you can quickly switch between daemons or disable them. We will copy this as well.
cp /etc/default/memcached /etc/default/repcached
vi /etc/default/repcached
Change the line to: ENABLE_REPCACHED=yes, and then edit /etc/default/memcached
vi /etc/default/memcached
and disable it, by changing the line to ENABLE_MEMCACHED=no.

Now let's move on to init scripts.
cd /etc/init.d
cp memcached repcached
Edit the file /etc/init.d/repcached.

Here is my example.

Again, we didn't change much, mostly changed from memcached to repcached, but note that the actual start-up of the service happens in this file: /usr/share/memcached/scripts/start-repcached which doesn't exist yet, so we will copy and edit it.
cp /usr/share/memcached/scripts/start-memcached /usr/share/memcached/scripts/start-repcached
File contents or /usr/share/memcached/scripts/start-repcached

Setting up repcached to start at boot

We need to be sure that /etc/init.d/repcached is executable. If you copied it from memcached, everything should be OK, but if init's not recognising the repcached service, you need to chmod +x /etc/init.d/repcached

After you've run update-rc.d command in the terminal it will create shortcuts in rc?.d files which are read at boot.
update-rc.d repcached defaults
For more information on update-rc.d, click here.

You have successfully configured repcached as a service and to start on boot.

To start/stop repcached use
service repcached start
service repcached stop
Try to run repcached by hand at first with the configuration you provided in /etc/repcached.conf.
In my example it's this:
/usr/local/bin/memcached -m 64 -p 11211 -u memcache -X 11212 -x

After installing repcached on another machine I've found out that the default user for memcached is nobody, not memcache, so please always check the differences from the default memcache config with the repcached config you've modified or copied from here.

Feb 5, 2011

PHP:: socket_select(), socket_write() and socket_recv()

As it was already said, some clients need \0 character to end transmission, for example Flash's XMLSocket.

You should also be prepared to read less data than you have requested.

Here is an example of a socket buffer - it's an array which has socket resources for keys and an array of a timestamp and recieved data as values.

I find that the best practice for sending data is trailing it with a new line and zero character (\n\0), because you will probably have different types of clients which behave differently for reading data from sockets. Some need a \n to fire an event, some need \0.

For recieving data, sometimes you will get splitted data - this can hapen because the buffer is full (in my example 8192 bytes) or it just gets broken during transmission in lower levels.

Sometimes you can read two messages at once, but they have a zero character in between, so you can just use preg_split() to split the messages. The second message may not be complete, so you add it to your buffer.

 const message_delimiter = "\n\0";

  * Clear socket buffers older than 1 hour
 function clear_buffer() {
  foreach($this->buffer as $key=>$val) {
   if(time() - $val['ts'] > 3600) {

  * Add data to a buffer
 function buffer_add($sock,$data) {
  if(!isset($this->buffer[$sock])) {
   $this->buffer[$sock]['data'] = '';

  $this->buffer[$sock]['data'] .= $data;
  $this->buffer[$sock]['ts'] = time();

 function buffer_get($sock) {
  // split buffer by the end of string
  $lines = preg_split('/\0/',$this->buffer[$sock]['data']);

  // reset buffer to the last line of input
  // if the buffer was sent completely, the last line of input should be
  // an empty string
  $this->buffer[$sock]['data'] = trim($lines[count($lines)-1]);

  if(!empty($this->buffer[$sock]['data'])) {
   debug("buffer is not empty for $sock, len: ".strlen($this->buffer[$sock]['data']));

  // remove the last line of input (incomplete data)
  // parse any complete data

  // return only the fully sent data
  return $lines;

 function read(&$sock,$len=8192,$flag=MSG_DONTWAIT) {
  $lines = array();


  $bytes_read = @socket_recv($sock,$read_data,$len,$flag);

  if ($bytes_read === false || $bytes_read == 0) {
   return false;
  } else {
   debug("recv: $read_data");
   return $this->buffer_get($sock);

  * Write to a socket
  * add a newline and null character at the end
  * some clients don't read until new line is recieved
  * try to send the rest of the data if it gets truncated
 function write(&$sock,$msg) {
  $msg = $msg.self::message_delimiter;
  $length = strlen($msg);
  while(true) {
   $sent = @socket_write($sock,$msg,$length);
   if($sent <= 0) {
    return false;
   if($sent < $length) {
    $msg = substr($msg, $sent);
    $length -= $sent;
    debug("Message truncated: Resending: $msg");
   } else {
    return true;
  return false;