1 <?php
2 /**
3  * pfccontainer.class.php
4  *
5  * Copyright � 2006 Stephane Gully <stephane.gully@gmail.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, 51 Franklin St, Fifth Floor,
20  * Boston, MA  02110-1301  USA
21  */
22 
23  require_once dirname(__FILE__)."/pfccontainerinterface.class.php";
24  require_once dirname(__FILE__)."/pfcurlprocessing.php";
25 
26 /**
27  * pfcContainer is an abstract class which define interface
28  * to be implemented by concrete container (example: File)
29  *
30  * @author Stephane Gully <stephane.gully@gmail.com>
31  * @abstract
32  */
33 class pfcContainer extends pfcContainerInterface
34 {
35   var $_container = null; // contains the concrete container instance
36   var $_usememorycache  = true;
37 
38 
39   function &Instance($type = 'File', $usememorycache = true)
40   {
41     static $i;
42     if (!isset($i))
43       $i = new pfcContainer($type, $usememorycache);
44     return $i;
45   }
46 
47   function pfcContainer($type = 'File', $usememorycache = true)
48   {
49     pfcContainerInterface::pfcContainerInterface();
50 
51     $this->_usememorycache = $usememorycache;
52     $type = strtolower($type);
53 
54     // create the concrete container instance
55     require_once dirname(__FILE__)."/containers/".$type.".class.php";
56     $container_classname = "pfcContainer_".$type;
57     $this->_container = new $container_classname();
58   }
59   function getDefaultConfig()
60   {
61     if ($this->_container)
62       return $this->_container->getDefaultConfig();
63     else
64       return array();
65   }
66   function init(&$c)
67   {
68     if ($this->_container)
69       return $this->_container->init($c);
70   }
71 
72   /**
73    * Create (connect/join) the nickname into the server or the channel locations
74    * Notice: the caller must take care to update all channels the users joined (use stored channel list into metadata)
75    * @param $chan if NULL then create the user on the server (connect), otherwise create the user on the given channel (join)
76    * @param $nick the nickname to create
77    * @param $nickid is the corresponding nickname id (taken from session)
78    */
79   /*
80   function createNick($chan, $nick, $nickid)
81   {
82     $c =& pfcGlobalConfig::Instance();
83 
84     if ($nick == '')
85       user_error('pfcContainer::createNick nick is empty', E_USER_ERROR);
86     if ($nickid == '')
87       user_error('pfcContainer::createNick nickid is empty', E_USER_ERROR);
88 
89     if ($chan == NULL) $chan = 'SERVER';
90 
91     $this->setMeta("nickid-to-metadata",  $nickid, 'nick', $nick);
92     $this->setMeta("metadata-to-nickid",  'nick', $this->encode($nick), $nickid);
93 
94     $this->setMeta("nickid-to-channelid", $nickid, $this->encode($chan));
95     $this->setMeta("channelid-to-nickid", $this->encode($chan), $nickid);
96 
97     // update the SERVER channel
98     if ($chan != 'SERVER') $this->updateNick($nickid);
99 
100     return true;
101   }
102   */
103 
104   function createNick($nickid, $nick)
105   {
106     $c =& pfcGlobalConfig::Instance();
107 
108     if ($nick == '')
109       user_error('pfcContainer::createNick nick is empty', E_USER_ERROR);
110     if ($nickid == '')
111       user_error('pfcContainer::createNick nickid is empty', E_USER_ERROR);
112 
113     $this->setMeta("nickid-to-metadata",  $nickid, 'nick', $nick);
114     $this->setMeta("metadata-to-nickid",  'nick', $this->encode($nick), $nickid);
115 
116     return true;
117   }
118 
119 
120   function joinChan($nickid, $chan)
121   {
122     $c =& pfcGlobalConfig::Instance();
123 
124     if ($nickid == '')
125       user_error('pfcContainer::joinChan nickid is empty', E_USER_ERROR);
126 
127     if ($chan == NULL) $chan = 'SERVER';
128 
129     $this->setMeta("nickid-to-channelid", $nickid, $this->encode($chan));
130     $this->setMeta("channelid-to-nickid", $this->encode($chan), $nickid);
131 
132     // update the SERVER channel
133     if ($chan == 'SERVER') $this->updateNick($nickid);
134 
135     return true;
136   }
137 
138 
139 
140   /**
141    * Remove (disconnect/quit) the nickname from the server or from a channel
142    * Notice: when a user quit, the caller must take care removeNick from each channels ('SERVER' included)
143    * This function takes care to remove all users metadata when he his disconnected from all channels
144    * @param $chan if NULL then remove the user from the 'SERVER' channel, otherwise just remove the user from the given channel (quit)
145    * @param $nickid the nickname id to remove
146    * @return array which contains removed user infos ('nickid', 'nick', 'timestamp')
147    */
148   function removeNick($chan, $nickid)
149   {
150     $c =& pfcGlobalConfig::Instance();
151 
152     if ($chan == NULL) $chan = 'SERVER';
153 
154     $deleted_user = array();
155     $deleted_user["nick"]      = array();
156     $deleted_user["nickid"]    = array();
157     $deleted_user["timestamp"] = array();
158 
159     if (!$nickid) return $deleted_user;
160 
161     $timestamp = $this->getMeta("channelid-to-nickid", $this->encode('SERVER'), $nickid);
162     if (count($timestamp["timestamp"]) == 0) return $deleted_user;
163     $timestamp = $timestamp["timestamp"][0];
164 
165     $deleted_user["nick"][]      = $this->getNickname($nickid);
166     $deleted_user["nickid"][]    = $nickid;
167     $deleted_user["timestamp"][] = $timestamp;
168 
169     // remove the nickid from the channel list
170     $this->rmMeta('channelid-to-nickid', $this->encode($chan), $nickid);
171     $this->rmMeta('nickid-to-channelid', $nickid, $this->encode($chan));
172 
173     // if the user is the last one to quit this room,
174     // and this room is not a default room,
175     // then clean the room history
176     $channels = array();
177     foreach($c->channels as $cc)
178       $channels[] = 'ch_'.$cc; // @todo clean this piece of code when the chan and chanid will be refactored
179     if (!in_array($chan, $channels))
180     {
181       $ret = $this->getOnlineNick($chan);
182       if (count($ret['nickid']) == 0)
183       {
184         $this->rmMeta('channelid-to-msg',   $this->encode($chan));
185         $this->rmMeta('channelid-to-msgid', $this->encode($chan));
186       }
187     }
188 
189     // get the current user's channels list
190     $channels = $this->getMeta("nickid-to-channelid",$nickid);
191     $channels = $channels["value"];
192     // no more joined channel, just remove the user's metadata
193     if (count($channels) == 0)
194     {
195       // remove the nickname to nickid correspondance
196       $this->rmMeta('metadata-to-nickid', 'nick', $this->encode($this->getNickname($nickid)));
197       // remove disconnected nickname metadata
198       $this->rmMeta('nickid-to-metadata', $nickid);
199       // remove users commands in queue
200       $this->rmMeta("nickid-to-cmdtoplay", $nickid);
201       $this->rmMeta("nickid-to-cmdtoplayid", $nickid);
202     }
203 
204     return $deleted_user;
205   }
206 
207   /**
208    * Store/update the alive user status on the 'SERVER' channel
209    * The default File container will just touch (update the date) of the nickname file in the 'SERVER' channel.
210    * @param $nickid the nickname id to keep alive
211    */
212   function updateNick($nickid)
213   {
214     $c =& pfcGlobalConfig::Instance();
215 
216     $chan = 'SERVER';
217 
218     $this->setMeta("nickid-to-channelid", $nickid, $this->encode($chan));
219     $this->setMeta("channelid-to-nickid", $this->encode($chan), $nickid);
220     return true;
221   }
222 
223   /**
224    * Change the user's nickname
225    * As nickname value are stored in user's metadata, this function just update the 'nick' metadata
226    * @param $newnick
227    * @param $oldnick
228    * @return true on success, false on failure (if the oldnick doesn't exists)
229    */
230   function changeNick($newnick, $oldnick)
231   {
232     $c =& pfcGlobalConfig::Instance();
233 
234     $oldnickid = $this->getNickId($oldnick);
235     $newnickid = $this->getNickId($newnick);
236     if ($oldnickid == "") return false; // the oldnick must be connected
237     if ($newnickid != "") return false; // the newnick must not be inuse
238 
239     // remove the oldnick to oldnickid correspondance
240     $this->rmMeta("metadata-to-nickid", 'nick', $this->encode($oldnick));
241 
242     // update the nickname
243     $this->setMeta("nickid-to-metadata", $oldnickid, 'nick', $newnick);
244     $this->setMeta("metadata-to-nickid", 'nick', $this->encode($newnick), $oldnickid);
245     return true;
246   }
247 
248   /**
249    * Returns the nickid corresponding to the given nickname
250    * The nickid is a unique id used to identify a user (generated from the browser sessionid)
251    * The nickid is stored in the container when createNick is called.
252    * @param $nick
253    * @return string the nick id
254    */
255   function getNickId($nick)
256   {
257     $nickid = $this->getMeta("metadata-to-nickid", 'nick', $this->encode($nick), true);
258     $nickid = isset($nickid["value"][0]) ? $nickid["value"][0] : "";
259     return $nickid;
260   }
261 
262   /**
263    * Returns the nickname corresponding the the given nickid
264    * @param $nickid
265    * @return string the corresponding nickname
266    */
267   function getNickname($nickid)
268   {
269     $nick = $this->getMeta("nickid-to-metadata", $nickid, 'nick', true);
270     $nick = isset($nick["value"][0]) ? $this->decode($nick["value"][0]) : "";
271     return $nick;
272   }
273 
274   /**
275    * Remove (disconnect/quit) the timeouted nicknames
276    * Notice: this function will remove all nicknames which are not uptodate from all his joined channels
277    * @param $timeout
278    * @return array("nickid"=>array("nickid1", ...),"timestamp"=>array(timestamp1, ...)) contains all disconnected nickids and there timestamp
279    */
280   function removeObsoleteNick($timeout)
281   {
282     $c =& pfcGlobalConfig::Instance();
283 
284     $deleted_user = array('nick'=>array(),
285                           'nickid'=>array(),
286                           'timestamp'=>array(),
287                           'channels'=>array());
288     $ret = $this->getMeta("channelid-to-nickid", $this->encode('SERVER'));
289     for($i = 0; $i<count($ret['timestamp']); $i++)
290     {
291       $timestamp = $ret['timestamp'][$i];
292       $nickid    = $ret['value'][$i];
293       if (time() > ($timestamp+$timeout/1000) && $nickid) // user will be disconnected after 'timeout' secondes of inactivity
294       {
295         // get the current user's channels list
296         $channels = array();
297         $ret2 = $this->getMeta("nickid-to-channelid",$nickid);
298         foreach($ret2["value"] as $userchan)
299         {
300           $userchan = $this->decode($userchan);
301           if ($userchan != 'SERVER')
302           {
303             // disconnect the user from each joined channels
304             $this->removeNick($userchan, $nickid);
305             $channels[] = $userchan;
306           }
307         }
308         // now disconnect the user from the server
309         // (order is important because the SERVER channel has timestamp informations)
310         $du = $this->removeNick('SERVER', $nickid);
311         $channels[] = 'SERVER';
312 
313         $deleted_user["nick"]      = array_merge($deleted_user["nick"],      $du["nick"]);
314         $deleted_user["nickid"]    = array_merge($deleted_user["nickid"],    $du["nickid"]);
315         $deleted_user["timestamp"] = array_merge($deleted_user["timestamp"], $du["timestamp"]);
316         $deleted_user["channels"]  = array_merge($deleted_user["channels"],  array($channels));
317       }
318     }
319 
320     return $deleted_user;
321   }
322 
323   /**
324    * Returns the nickname list on the given channel or on the whole server
325    * @param $chan if NULL then returns all connected user, otherwise just returns the channel nicknames
326    * @return array("nickid"=>array("nickid1", ...),"timestamp"=>array(timestamp1, ...)) contains the nickid list with the associated timestamp (laste update time)
327    */
328   function getOnlineNick($chan)
329   {
330     $c =& pfcGlobalConfig::Instance();
331 
332     if ($chan == NULL) $chan = 'SERVER';
333 
334     $online_user = array('nick'=>array(),'nickid'=>array(),'timestamp'=>array());
335     $ret = $this->getMeta("channelid-to-nickid", $this->encode($chan));
336     for($i = 0; $i<count($ret['timestamp']); $i++)
337     {
338       $nickid = $ret['value'][$i];
339 
340       // get timestamp from the SERVER channel
341       $timestamp = $this->getMeta("channelid-to-nickid", $this->encode('SERVER'), $nickid);
342       if (count($timestamp['timestamp']) == 0) continue;
343       $timestamp = $timestamp['timestamp'][0];
344 
345       $online_user["nick"][]      = $this->getNickname($nickid);
346       $online_user["nickid"][]    = $nickid;
347       $online_user["timestamp"][] = $timestamp;
348     }
349     return $online_user;
350   }
351 
352   /**
353    * Returns returns a positive number if the nick is online in the given channel
354    * @param $chan if NULL then check if the user is online on the server, otherwise check if the user has joined the channel
355    * @return false if the user is off line, true if the user is online
356    */
357   function isNickOnline($chan, $nickid)
358   {
359     if (!$nickid) return false;
360     if ($chan == NULL) $chan = 'SERVER';
361 
362     $ret = $this->getMeta("channelid-to-nickid",
363                           $this->encode($chan),
364                           $nickid);
365 
366     return (count($ret['timestamp']) > 0);
367   }
368 
369   /**
370    * Write a command to the given channel or to the server
371    * Notice: a message is very generic, it can be a misc command (notice, me, ...)
372    * @param $chan if NULL then write the message on the server, otherwise just write the message on the channel message pool
373    * @param $nick is the sender nickname
374    * @param $cmd is the command name (ex: "send", "nick", "kick" ...)
375    * @param $param is the command' parameters (ex: param of the "send" command is the message)
376    * @return $msg_id the created message identifier
377    */
378   function write($chan, $nick, $cmd, $param)
379   {
380     $c =& pfcGlobalConfig::Instance();
381     if ($chan == NULL) $chan = 'SERVER';
382 
383     $msgid = $this->_requestMsgId($chan);
384 
385     // format message
386     $data = "\n";
387     $data .= $msgid."\t";
388     $data .= time()."\t";
389     $data .= $nick."\t";
390     $data .= $cmd."\t";
391     $data .= $param;
392 
393     // write message
394     $this->setMeta("channelid-to-msg", $this->encode($chan), $msgid, $data);
395 
396     // delete the obsolete message
397     $old_msgid = $msgid - $c->max_msg - 20;
398     if ($old_msgid > 0)
399       $this->rmMeta("channelid-to-msg", $this->encode($chan), $old_msgid);
400 
401     return $msgid;
402   }
403 
404   /**
405    * Read the last posted commands from a channel or from the server
406    * Notice: the returned array is ordered by id
407    * @param $chan if NULL then read from the server, otherwise read from the given channel
408    * @param $from_id read all message with a greater id
409    * @return array() contains the formated command list
410    */
411   function read($chan, $from_id)
412   {
413     $c =& pfcGlobalConfig::Instance();
414     if ($chan == NULL) $chan = 'SERVER';
415 
416     // read new messages content + parse content
417     $new_from_id = $this->getLastId($chan);
418     $datalist = array();
419     for ( $mid = $from_id; $mid <= $new_from_id; $mid++ )
420     {
421       $line = $this->getMeta("channelid-to-msg", $this->encode($chan), $mid, true);
422       $line = $line["value"][0];
423       if ($line != "" && $line != "\n")
424       {
425         $formated_line = explode( "\t", $line );
426         $data = array();
427         $data["id"]        = trim($formated_line[0]);
428         $data["timestamp"] = $formated_line[1];
429         $data["sender"]    = $formated_line[2];
430         $data["cmd"]       = $formated_line[3];
431         // convert URLs to html
432         $data["param"]     = $formated_line[4];
433         $datalist[$data["id"]] = $data;
434       }
435     }
436     return array("data" => $datalist,
437                  "new_from_id" => $new_from_id+1 );
438   }
439 
440   /**
441    * Returns the last message id
442    * Notice: the default file container just returns the messages.index file content
443    * @param $chan if NULL then read if from the server, otherwise read if from the given channel
444    * @return int is the last posted message id
445    */
446   function getLastId($chan)
447   {
448     if ($chan == NULL) $chan = 'SERVER';
449 
450     $lastmsgid = $this->getMeta("channelid-to-msgid", $this->encode($chan), 'lastmsgid', true);
451     if (count($lastmsgid["value"]) == 0)
452       $lastmsgid = 0;
453     else
454       $lastmsgid = $lastmsgid["value"][0];
455     return $lastmsgid;
456   }
457 
458 
459   /**
460    * Return a unique id. Each time this function is called, the last id is incremented.
461    * used internaly
462    * @private
463    */
464   function _requestMsgId($chan)
465   {
466     if ($chan == NULL) $chan = 'SERVER';
467 
468     $lastmsgid = $this->incMeta("channelid-to-msgid", $this->encode($chan), 'lastmsgid');
469 
470     if (count($lastmsgid["value"]) == 0)
471       $lastmsgid = 0;
472     else
473       $lastmsgid = $lastmsgid["value"][0];
474     return $lastmsgid;
475   }
476 
477   /**
478    * Remove all created data for this server (identified by serverid)
479    * Notice: for the default File container, it's just a recursive directory remove
480    */
481   function clear()
482   {
483     $this->rmMeta(NULL);
484   }
485 
486   function getAllUserMeta($nickid)
487   {
488     $result = array();
489     $ret = $this->getMeta("nickid-to-metadata", $nickid);
490     foreach($ret["value"] as $k)
491       $result[$k] = $this->getUserMeta($nickid, $k);
492     //    $result['chanid'] = $this->getMeta("nickid-to-channelid", $nickid);
493     //    $result['chanid'] = $result['chanid']['value'];
494     return $result;
495   }
496 
497   function getUserMeta($nickid, $key = NULL)
498   {
499     $ret = $this->getMeta("nickid-to-metadata", $nickid, $key, true);
500     return isset($ret['value'][0]) ? $ret['value'][0] : NULL;
501   }
502 
503   function setUserMeta($nickid, $key, $value)
504   {
505     $ret = $this->setMeta("nickid-to-metadata", $nickid, $key, $value);
506     return $ret;
507   }
508 
509   function getCmdMeta($nickid, $key = NULL)
510   {
511     $ret = $this->getMeta("nickid-to-cmdtoplay", $nickid, $key, true);
512     return $ret['value'];
513   }
514 
515   function setCmdMeta($nickid, $key, $value)
516   {
517     $ret = $this->setMeta("nickid-to-cmdtoplay", $nickid, $key, $value);
518     return $ret;
519   }
520 
521   function getAllChanMeta($chan)
522   {
523     $result = array();
524     $ret = $this->getMeta("channelid-to-metadata", $this->encode($chan));
525     foreach($ret["value"] as $k)
526       $result[$k] = $this->getChanMeta($chan, $k);
527     return $result;
528   }
529 
530   function getChanMeta($chan, $key = NULL)
531   {
532     $ret = $this->getMeta("channelid-to-metadata", $this->encode($chan), $key, true);
533     return isset($ret['value'][0]) ? $ret['value'][0] : NULL;
534   }
535 
536   function setChanMeta($chan, $key, $value)
537   {
538     $ret = $this->setMeta("channelid-to-metadata", $this->encode($chan), $key, $value);
539     return $ret;
540   }
541 
542   var $_cache = array();
543 
544   /**
545    * Write a meta data value identified by a group / subgroup / leaf [with a value]
546    * As an example in the default file container this  arborescent structure is modelised by simple directories
547    * group1/subgroup1/leaf1
548    *                 /leaf2
549    *       /subgroup2/...
550    * Each leaf can contain or not a value.
551    * However each leaf and each group/subgroup must store the lastmodified time (timestamp).
552    * @param $group root arborescent element
553    * @param $subgroup is the root first child which contains leafs
554    * @param $leaf is the only element which can contain values
555    * @param $leafvalue NULL means the leaf will not contain any values
556    * @return 1 if the old leaf has been overwritten, 0 if a new leaf has been created
557    */
558   function setMeta($group, $subgroup, $leaf, $leafvalue = NULL)
559   {
560     $ret = $this->_container->setMeta($group, $subgroup, $leaf, $leafvalue);
561 
562     if ($this->_usememorycache)
563     {
564       // store the modifications in the cache
565       if (isset($this->_cache[$group]['value']) &&
566           !in_array($subgroup, $this->_cache[$group]['value']))
567       {
568         $this->_cache[$group]['value'][]     = $subgroup;
569         $this->_cache[$group]['timestamp'][] = time();
570       }
571       if (isset($this->_cache[$group]['childs'][$subgroup]['value']) &&
572           !in_array($leaf, $this->_cache[$group]['childs'][$subgroup]['value']))
573       {
574         $this->_cache[$group]['childs'][$subgroup]['value'][]     = $leaf;
575         $this->_cache[$group]['childs'][$subgroup]['timestamp'][] = time();
576       }
577       $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['value'] = array($leafvalue);
578       $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['timestamp'] = array(time());
579     }
580 
581     return $ret;
582   }
583 
584 
585   /**
586    * Read meta data identified by a group [/ subgroup [/ leaf]]
587    * @param $group is mandatory, it's the arborescence's root
588    * @param $subgroup if null then the subgroup list names are returned
589    * @param $leaf if null then the leaf names are returned
590    * @param $withleafvalue if set to true the leaf value will be returned
591    * @return array which contains two subarray 'timestamp' and 'value'
592    */
593   function getMeta($group, $subgroup = null, $leaf = null, $withleafvalue = false)
594   {
595     $ret = array('timestamp' => array(),
596                  'value'     => array());
597 
598     if ($this->_usememorycache)
599     {
600       // check if the data exists in the cache
601       $incache = false;
602       if ($subgroup == null &&
603           isset($this->_cache[$group]['value']))
604       {
605         $incache = true;
606         $ret = $this->_cache[$group];
607       }
608       else if ($leaf == null &&
609                isset($this->_cache[$group]['childs'][$subgroup]['value']))
610       {
611         $incache = true;
612         $ret = $this->_cache[$group]['childs'][$subgroup];
613       }
614       else
615       {
616         if ($withleafvalue)
617         {
618           if (isset($this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['value']))
619           {
620             $incache = true;
621             $ret = $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf];
622           }
623         }
624         else
625         {
626           if (isset($this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['timestamp']))
627           {
628             $incache = true;
629             $ret = $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf];
630           }
631         }
632       }
633 
634       if ($incache)
635       {
636         $ret2 = array();
637         if (isset($ret['timestamp'])) $ret2['timestamp'] = $ret['timestamp'];
638         if (isset($ret['value']))     $ret2['value'] = $ret['value'];
639         return $ret2;
640       }
641     }
642 
643     // get the fresh data
644     $ret = $this->_container->getMeta($group, $subgroup, $leaf, $withleafvalue);
645 
646     if ($this->_usememorycache)
647     {
648       // store in the cache
649       if ($subgroup == null)
650       {
651         $this->_cache[$group]['value']     = $ret['value'];
652         $this->_cache[$group]['timestamp'] = $ret['timestamp'];
653       }
654       else if ($leaf == null)
655       {
656         $this->_cache[$group]['childs'][$subgroup]['value']     = $ret['value'];
657         $this->_cache[$group]['childs'][$subgroup]['timestamp'] = $ret['timestamp'];
658       }
659       else
660       {
661         if ($withleafvalue)
662           $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['value'] = $ret['value'];
663         else
664           unset($this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['value']);
665         $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['timestamp'] = $ret['timestamp'];
666       }
667     }
668 
669     return $ret;
670   }
671 
672   /**
673    * Increment a counter identified by the following path : group / subgroup / leaf
674    * Notice: this step must be atomic in order to avoid multithread problem (don't forget to use locking features)
675    * @param $group is mandatory
676    * @param $subgroup is mandatory
677    * @param $leaf is mandatory, it's the counter name
678    * @return array which contains two subarray 'timestamp' and 'value' (value contains the incremented numeric value)
679    */
680   function incMeta($group, $subgroup, $leaf)
681   {
682     $ret = $this->_container->incMeta($group, $subgroup, $leaf);
683 
684     if ($this->_usememorycache)
685     {
686       // store the modifications in the cache
687       if (isset($this->_cache[$group]['value']) &&
688           !in_array($subgroup, $this->_cache[$group]['value']))
689       {
690         $this->_cache[$group]['value'][]     = $subgroup;
691         $this->_cache[$group]['timestamp'][] = time();
692       }
693       if (isset($this->_cache[$group]['childs'][$subgroup]['value']) &&
694           !in_array($leaf, $this->_cache[$group]['childs'][$subgroup]['value']))
695       {
696         $this->_cache[$group]['childs'][$subgroup]['value'][]     = $leaf;
697         $this->_cache[$group]['childs'][$subgroup]['timestamp'][] = time();
698       }
699       $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['value']     = $ret['value'];
700       $this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]['timestamp'] = array(time());
701     }
702 
703     return $ret;
704   }
705 
706   /**
707    * Remove a meta data or a group of metadata
708    * @param $group if null then it will remove all the possible groups (all the created metadata)
709    * @param $subgroup if null then it will remove the $group's childs (all the subgroup contained by $group)
710    * @param $leaf if null then it will remove all the $subgroup's childs (all the leafs contained by $subgroup)
711    * @return true on success, false on error
712    */
713   function rmMeta($group, $subgroup = null, $leaf = null)
714   {
715     if ($this->_usememorycache)
716     {
717       // remove from the cache
718       if ($group == null)
719         $this->_cache = array();
720       else if ($subgroup == null)
721         unset($this->_cache[$group]);
722       else if ($leaf == null)
723       {
724         if (isset($this->_cache[$group]['value']))
725         {
726           $i = array_search($subgroup,$this->_cache[$group]['value']);
727           if ($i !== FALSE)
728           {
729             unset($this->_cache[$group]['value'][$i]);
730             unset($this->_cache[$group]['timestamp'][$i]);
731           }
732         }
733         unset($this->_cache[$group]['childs'][$subgroup]);
734       }
735       else
736       {
737         if (isset($this->_cache[$group]['childs'][$subgroup]['value']))
738         {
739           $i = array_search($leaf,$this->_cache[$group]['childs'][$subgroup]['value']);
740           if ($i !== FALSE)
741           {
742             unset($this->_cache[$group]['childs'][$subgroup]['value'][$i]);
743             unset($this->_cache[$group]['childs'][$subgroup]['timestamp'][$i]);
744           }
745         }
746         unset($this->_cache[$group]['childs'][$subgroup]['childs'][$leaf]);
747       }
748     }
749 
750     return $this->_container->rmMeta($group, $subgroup, $leaf);
751   }
752 
753   /**
754    * In the default File container: used to encode UTF8 strings to ASCII filenames
755    * This method can be overridden by the concrete container
756    */
757   function encode($str)
758   {
759     return $this->_container->encode($str);
760   }
761 
762   /**
763    * In the default File container: used to decode ASCII filenames to UTF8 strings
764    * This method can be overridden by the concrete container
765    */
766   function decode($str)
767   {
768     return $this->_container->decode($str);
769   }
770 }
771 
772 ?>
773