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 */
33class 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