1<?php
2/**
3 * file.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
23require_once dirname(__FILE__)."/../pfccontainerinterface.class.php";
24
25/**
26 * pfcContainer_File is a concret container which stock data into files
27 *
28 * @author Stephane Gully <stephane.gully@gmail.com>
29 */
30class pfcContainer_File extends pfcContainerInterface
31{
32  var $_users = array("nickid"    => array(),
33                      "timestamp" => array());
34  var $_meta = array();
35
36  function pfcContainer_File()
37  {
38    pfcContainerInterface::pfcContainerInterface();
39  }
40
41  function loadPaths(&$c)
42  {
43    if (!isset($c->container_cfg_chat_dir) || $c->container_cfg_chat_dir == '')
44      $c->container_cfg_chat_dir   = $c->data_private_path."/chat";
45    if (!isset($c->container_cfg_server_dir) || $c->container_cfg_server_dir == '')
46      $c->container_cfg_server_dir = $c->container_cfg_chat_dir."/s_".$c->serverid;
47  }
48
49  function getDefaultConfig()
50  {
51    $cfg = pfcContainerInterface::getDefaultConfig();
52    $cfg["chat_dir"]   = ''; // will be generated from the other parameters into the init step
53    $cfg["server_dir"] = ''; // will be generated from the other parameters into the init step
54    return $cfg;
55  }
56
57  function init(&$c)
58  {
59    $errors = pfcContainerInterface::init($c);
60
61    // generate the container parameters from other config parameters
62    $this->loadPaths($c);
63
64    $errors = array_merge($errors, @test_writable_dir($c->container_cfg_chat_dir,   "container_cfg_chat_dir"));
65    $errors = array_merge($errors, @test_writable_dir($c->container_cfg_server_dir, "container_cfg_chat_dir/serverid"));
66
67    // test the filemtime php function because it doesn't work on special filesystem
68    // example : NFS, VZFS
69    $filename   = $c->data_private_path.'/filemtime.test';
70    $timetowait = 2;
71    if (is_writable(dirname($filename)))
72    {
73      file_put_contents($filename,'some-data1-'.time());
74      clearstatcache();
75      $time1 = filemtime($filename);
76      sleep($timetowait);
77      file_put_contents($filename,'some-data2-'.time());
78      clearstatcache();
79      $time2 = filemtime($filename);
80      unlink($filename);
81      if ($time2-$time1 != $timetowait)
82        $errors[] = "filemtime php fuction is not usable on your filesystem. Please do not use the 'file' container (try the 'mysql' container) or swith to another filesystem type.";
83    }
84
85    // test the LOCK_EX feature because it doesn't work on special filsystem like NFS
86    $filename = $c->data_private_path.'/filemtime.test';
87    if (is_writable(dirname($filename)))
88    {
89      $data1 = time();
90      file_put_contents($filename, $data1, LOCK_EX);
91      $data2 = file_get_contents($filename);
92      if ($data1 != $data2)
93        $errors[] = "LOCK_EX feature is not usable on your filesystem. Please do not use the 'file' container (try the 'mysql' container) or swith to another filesystem type.";
94    }
95
96    return $errors;
97  }
98
99  function setMeta($group, $subgroup, $leaf, $leafvalue = NULL)
100  {
101    $c =& pfcGlobalConfig::Instance();
102
103    // create directories
104    $dir_base = $c->container_cfg_server_dir;
105    $dir = $dir_base.'/'.$group.'/'.$subgroup;
106    if (!is_dir($dir)) mkdir_r($dir);
107
108    // create or replace metadata file
109    $leaffilename = $dir."/".$leaf;
110    $leafexists = file_exists($leaffilename);
111    if ($leafvalue == NULL)
112    {
113      @unlink($leaffilename);
114      touch($leaffilename);
115    }
116    else
117    {
118      file_put_contents($leaffilename, $leafvalue, LOCK_EX);
119    }
120
121    // store the value in the memory cache
122    //@todo
123    //    $this->_meta[$enc_type][$enc_subtype][$enc_key] = $value;
124
125    if ($leafexists)
126      return 1; // value overwritten
127    else
128      return 0; // value created
129  }
130
131  function getMeta($group, $subgroup = null, $leaf = null, $withleafvalue = false)
132  {
133    $c =& pfcGlobalConfig::Instance();
134
135    // read data from metadata file
136    $ret = array();
137    $ret["timestamp"] = array();
138    $ret["value"]     = array();
139    $dir_base = $c->container_cfg_server_dir;
140
141    $dir = $dir_base.'/'.$group;
142    if ($subgroup == NULL)
143    {
144      if (is_dir($dir))
145      {
146        $dh = opendir($dir);
147        while (false !== ($file = readdir($dh)))
148        {
149          if ($file == "." || $file == "..") continue; // skip . and .. generic files
150          $ret["timestamp"][] = filemtime($dir.'/'.$file);
151          $ret["value"][]     = $file;
152        }
153        closedir($dh);
154      }
155      return $ret;
156    }
157
158    $dir .= '/'.$subgroup;
159
160    if ($leaf == NULL)
161    {
162      if (is_dir($dir))
163      {
164        $dh = opendir($dir);
165        while (false !== ($file = readdir($dh)))
166        {
167          if ($file == "." || $file == "..") continue; // skip . and .. generic files
168          $ret["timestamp"][] = filemtime($dir.'/'.$file);
169          $ret["value"][]     = $file;
170        }
171        closedir($dh);
172      }
173
174      return $ret;
175    }
176
177    $leaffilename = $dir."/".$leaf;
178
179    if (!file_exists($leaffilename)) return $ret;
180    if ($withleafvalue)
181      $ret["value"][] = file_get_contents_flock($leaffilename);
182    else
183      $ret["value"][] = NULL;
184    $ret["timestamp"][] = filemtime($leaffilename);
185
186    return $ret;
187  }
188
189  function incMeta($group, $subgroup, $leaf)
190  {
191    $c =& pfcGlobalConfig::Instance();
192
193    // create directories
194    $dir_base = $c->container_cfg_server_dir;
195    $dir = $dir_base.'/'.$group.'/'.$subgroup;
196    if (!is_dir($dir)) mkdir_r($dir);
197
198    // create or replace metadata file
199    $leaffilename = $dir."/".$leaf;
200
201    // create return array
202    $ret = array();
203    $ret["timestamp"] = array();
204    $ret["value"]     = array();
205
206    // read and increment data from metadata file
207    clearstatcache();
208    if (file_exists($leaffilename))
209    {
210      $fh = fopen($leaffilename, 'r+');
211      for($i = 0; $i < 10; $i++)  // Try 10 times until an exclusive lock can be obtained
212      {
213        if (flock($fh, LOCK_EX))
214        {
215          $leafvalue = chop(fread($fh, filesize($leaffilename)));
216          $leafvalue++;
217          rewind($fh);
218          fwrite($fh, $leafvalue);
219          fflush($fh);
220          ftruncate($fh, ftell($fh));
221          flock($fh, LOCK_UN);
222          break;
223        }
224        // If flock is working properly, this will never be reached
225        $delay = rand(0, pow(2, ($i+1)) - 1) * 5000;  // Exponential backoff
226        usleep($delay);
227      }
228      fclose($fh);
229    }
230    else
231    {
232      $leafvalue="1";
233      file_put_contents($leaffilename, $leafvalue, LOCK_EX);
234    }
235
236    $ret["value"][] = $leafvalue;
237    $ret["timestamp"][] = filemtime($leaffilename);
238
239    return $ret;
240  }
241
242  function rmMeta($group, $subgroup = null, $leaf = null)
243  {
244    $c =& pfcGlobalConfig::Instance();
245
246    $dir = $c->container_cfg_server_dir;
247
248    if ($group == NULL)
249    {
250      rm_r($dir);
251      return true;
252    }
253
254    $dir .= '/'.$group;
255
256    if ($subgroup == NULL)
257    {
258      rm_r($dir);
259      return true;
260    }
261
262    $dir .= '/'.$subgroup;
263
264    if ($leaf == NULL)
265    {
266      rm_r($dir);
267      return true;
268    }
269
270    $leaffilename = $dir.'/'.$leaf;
271    if (!file_exists($leaffilename)) return false;
272    unlink($leaffilename);
273
274    // check that the directory is empty or not
275    // remove it if it doesn't contains anything
276    $dh = opendir($dir);
277    readdir($dh); readdir($dh); // skip . and .. directories
278    $isnotempty = readdir($dh);
279    closedir($dh);
280    if ($isnotempty === false) rmdir($dir);
281
282    return true;
283  }
284
285  /**
286   * Used to encode UTF8 strings to ASCII filenames
287   */
288  function encode($str)
289  {
290    return urlencode($str);
291  }
292
293  /**
294   * Used to decode ASCII filenames to UTF8 strings
295   */
296  function decode($str)
297  {
298    return urldecode($str);
299  }
300}
301?>
302