]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/pear/Cache/Container/file.php
extra_empty_lines
[SourceForge/phpwiki.git] / lib / pear / Cache / Container / file.php
1 <?php
2 // +----------------------------------------------------------------------+
3 // | PEAR :: Cache                                                        |
4 // +----------------------------------------------------------------------+
5 // | Copyright (c) 1997-2003 The PHP Group                                |
6 // +----------------------------------------------------------------------+
7 // | This source file is subject to version 2.0 of the PHP license,       |
8 // | that is bundled with this package in the file LICENSE, and is        |
9 // | available at through the world-wide-web at                           |
10 // | http://www.php.net/license/2_02.txt.                                 |
11 // | If you did not receive a copy of the PHP license and are unable to   |
12 // | obtain it through the world-wide-web, please send a note to          |
13 // | license@php.net so we can mail you a copy immediately.               |
14 // +----------------------------------------------------------------------+
15 // | Authors: Ulf Wendel <ulf.wendel@phpdoc.de>                           |
16 // |          Sebastian Bergmann <sb@sebastian-bergmann.de>               |
17 // +----------------------------------------------------------------------+
18 //
19 // $Id$
20
21 require_once 'Cache/Container.php';
22
23 /**
24 * Stores cache contents in a file.
25 *
26 * @author   Ulf Wendel  <ulf.wendel@phpdoc.de>
27 * @version  $Id$
28 */
29 class Cache_Container_file extends Cache_Container {
30
31     /**
32     * File locking
33     *
34     * With file container, it's possible, that you get corrupted
35     * data-entries under bad circumstances. The file locking must
36     * improve this problem but it's experimental stuff. So the
37     * default value is false. But it seems to give good results
38     *
39     * @var boolean
40     */
41     var $fileLocking = false;
42
43     /**
44     * Directory where to put the cache files.
45     *
46     * @var  string  Make sure to add a trailing slash
47     */
48     var $cache_dir = '';
49
50     /**
51     * Filename prefix for cache files.
52     *
53     * You can use the filename prefix to implement a "domain" based cache or just
54     * to give the files a more descriptive name. The word "domain" is borroed from
55     * a user authentification system. One user id (cached dataset with the ID x)
56     * may exists in different domains (different filename prefix). You might want
57     * to use this to have different cache values for a production, development and
58     * quality assurance system. If you want the production cache not to be influenced
59     * by the quality assurance activities, use different filename prefixes for them.
60     *
61     * I personally don't think that you'll never need this, but 640kb happend to be
62     * not enough, so... you know what I mean. If you find a useful application of the
63     * feature please update this inline doc.
64     *
65     * @var  string
66     */
67     var $filename_prefix = '';
68
69     /**
70     * List of cache entries, used within a gc run
71     *
72     * @var array
73     */
74     var $entries;
75
76     /**
77     * Total number of bytes required by all cache entries, used within a gc run.
78     *
79     * @var  int
80     */
81     var $total_size = 0;
82
83     /**
84     * Max Line Length of userdata
85     *
86     * If set to 0, it will take the default
87     * ( 1024 in php 4.2, unlimited in php 4.3)
88     * see http://ch.php.net/manual/en/function.fgets.php
89     * for details
90     *
91     * @var int
92     */
93     var $max_userdata_linelength = 257;
94
95     /**
96     * Creates the cache directory if neccessary
97     *
98     * @param    array   Config options: ["cache_dir" => ..., "filename_prefix" => ...]
99     */
100      function Cache_Container_file($options = '') {
101         if (is_array($options))
102             $this->setOptions($options, array_merge($this->allowed_options, array('cache_dir', 'filename_prefix', 'max_userdata_linelength')));
103
104         clearstatcache();
105         if ($this->cache_dir)
106         {
107             // make relative paths absolute for use in deconstructor.
108             // it looks like the deconstructor has problems with relative paths
109             if (OS_UNIX && '/' != $this->cache_dir{0}  )
110                 $this->cache_dir = realpath( getcwd() . '/' . $this->cache_dir) . '/';
111
112             // check if a trailing slash is in cache_dir
113             if ($this->cache_dir{strlen($this->cache_dir)-1} != DIRECTORY_SEPARATOR)
114                  $this->cache_dir .= '/';
115
116             if  (!file_exists($this->cache_dir) || !is_dir($this->cache_dir))
117                 mkdir($this->cache_dir, 0755);
118         }
119         $this->entries = array();
120         $this->group_dirs = array();
121
122     } // end func contructor
123
124     function fetch($id, $group) {
125         $file = $this->getFilename($id, $group);
126         if (!file_exists($file))
127             return array(NULL, NULL, NULL);
128
129         // retrive the content
130         if (!($fh = @fopen($file, 'rb')))
131             return new Cache_Error("Can't access cache file '$file'. Check access rights and path.", __FILE__, __LINE__);
132
133         // File locking (shared lock)
134         if ($this->fileLocking)
135             flock($fh, LOCK_SH);
136
137         // file format:
138         // 1st line: expiration date
139         // 2nd line: user data
140         // 3rd+ lines: cache data
141         $expire = trim(fgets($fh, 12));
142         if ($this->max_userdata_linelength == 0 ) {
143             $userdata = trim(fgets($fh));
144         } else {
145             $userdata = trim(fgets($fh, $this->max_userdata_linelength));
146         }
147         $cachedata = $this->decode(fread($fh, filesize($file)));
148
149         // Unlocking
150         if ($this->fileLocking)
151             flock($fh, LOCK_UN);
152
153         fclose($fh);
154
155         // last usage date used by the gc - maxlifetime
156         // touch without second param produced stupid entries...
157         touch($file,time());
158         clearstatcache();
159
160         return array($expire, $cachedata, $userdata);
161     } // end func fetch
162
163     /**
164     * Stores a dataset.
165     *
166     * WARNING: If you supply userdata it must not contain any linebreaks,
167     * otherwise it will break the filestructure.
168     */
169     function save($id, $cachedata, $expires, $group, $userdata) {
170         $this->flushPreload($id, $group);
171
172         $file = $this->getFilename($id, $group);
173         if (!($fh = @fopen($file, 'wb')))
174             return new Cache_Error("Can't access '$file' to store cache data. Check access rights and path.", __FILE__, __LINE__);
175
176         // File locking (exclusive lock)
177         if ($this->fileLocking)
178             flock($fh, LOCK_EX);
179
180         // file format:
181         // 1st line: expiration date
182         // 2nd line: user data
183         // 3rd+ lines: cache data
184         $expires = $this->getExpiresAbsolute($expires);
185         fwrite($fh, $expires . "\n");
186         fwrite($fh, $userdata . "\n");
187         fwrite($fh, $this->encode($cachedata));
188
189         // File unlocking
190         if ($this->fileLocking)
191             flock($fh, LOCK_UN);
192
193         fclose($fh);
194
195         // I'm not sure if we need this
196     // i don't think we need this (chregu)
197         // touch($file);
198
199         return true;
200     } // end func save
201
202     function remove($id, $group) {
203         $this->flushPreload($id, $group);
204
205         $file = $this->getFilename($id, $group);
206         if (file_exists($file)) {
207
208             $ok = unlink($file);
209             clearstatcache();
210
211             return $ok;
212         }
213
214         return false;
215     } // end func remove
216
217     function flush($group) {
218         $this->flushPreload();
219         $dir = ($group) ? $this->cache_dir . $group . '/' : $this->cache_dir;
220
221         $num_removed = $this->deleteDir($dir);
222         unset($this->group_dirs[$group]);
223         clearstatcache();
224
225         return $num_removed;
226     } // end func flush
227
228     function idExists($id, $group) {
229
230         return file_exists($this->getFilename($id, $group));
231     } // end func idExists
232
233     /**
234     * Deletes all expired files.
235     *
236     * Garbage collection for files is a rather "expensive", "long time"
237     * operation. All files in the cache directory have to be examined which
238     * means that they must be opened for reading, the expiration date has to be
239     * read from them and if neccessary they have to be unlinked (removed).
240     * If you have a user comment for a good default gc probability please add it to
241     * to the inline docs.
242     *
243     * @param    integer Maximum lifetime in seconds of an no longer used/touched entry
244     * @throws   Cache_Error
245     */
246     function garbageCollection($maxlifetime) {
247
248         $this->flushPreload();
249         clearstatcache();
250
251         $ok = $this->doGarbageCollection($maxlifetime, $this->cache_dir);
252
253         // check the space used by the cache entries
254         if ($this->total_size > $this->highwater) {
255
256             krsort($this->entries);
257             reset($this->entries);
258
259             while ($this->total_size > $this->lowwater && list($lastmod, $entry) = each($this->entries)) {
260                 if (@unlink($entry['file']))
261                     $this->total_size -= $entry['size'];
262                 else
263                     new CacheError("Can't delete {$entry["file"]}. Check the permissions.");
264             }
265
266         }
267
268         $this->entries = array();
269         $this->total_size = 0;
270
271         return $ok;
272     } // end func garbageCollection
273
274     /**
275     * Does the recursive gc procedure, protected.
276     *
277     * @param    integer Maximum lifetime in seconds of an no longer used/touched entry
278     * @param    string  directory to examine - don't sets this parameter, it's used for a
279     *                   recursive function call!
280     * @throws   Cache_Error
281     */
282     function doGarbageCollection($maxlifetime, $dir) {
283
284         if (!($dh = opendir($dir)))
285             return new Cache_Error("Can't access cache directory '$dir'. Check permissions and path.", __FILE__, __LINE__);
286
287         while ($file = readdir($dh)) {
288             if ('.' == $file || '..' == $file)
289                 continue;
290
291             $file = $dir . $file;
292             if (is_dir($file)) {
293                 $this->doGarbageCollection($maxlifetime,$file . '/');
294                 continue;
295             }
296
297             // skip trouble makers but inform the user
298             if (!($fh = @fopen($file, 'rb'))) {
299                 new Cache_Error("Can't access cache file '$file', skipping it. Check permissions and path.", __FILE__, __LINE__);
300                 continue;
301             }
302
303             $expire = fgets($fh, 11);
304             fclose($fh);
305             $lastused = filemtime($file);
306
307             $this->entries[$lastused] = array('file' => $file, 'size' => filesize($file));
308             $this->total_size += filesize($file);
309
310             // remove if expired
311             if (( ($expire && $expire <= time()) || ($lastused <= (time() - $maxlifetime)) ) && !unlink($file))
312                 new Cache_Error("Can't unlink cache file '$file', skipping. Check permissions and path.", __FILE__, __LINE__);
313         }
314
315         closedir($dh);
316
317         // flush the disk state cache
318         clearstatcache();
319
320     } // end func doGarbageCollection
321
322     /**
323     * Returns the filename for the specified id.
324     *
325     * @param    string  dataset ID
326     * @param    string  cache group
327     * @return   string  full filename with the path
328     * @access   public
329     */
330     function getFilename($id, $group) {
331
332         if (isset($this->group_dirs[$group]))
333             return $this->group_dirs[$group] . $this->filename_prefix . $id;
334
335         $dir = $this->cache_dir . $group . '/';
336         if (!file_exists($dir)) {
337             mkdir($dir, 0755);
338             clearstatcache();
339         }
340
341         $this->group_dirs[$group] = $dir;
342
343         return $dir . $this->filename_prefix . $id;
344     } // end func getFilename
345
346     /**
347     * Deletes a directory and all files in it.
348     *
349     * @param    string  directory
350     * @return   integer number of removed files
351     * @throws   Cache_Error
352     */
353     function deleteDir($dir) {
354         if (!($dh = opendir($dir)))
355             return new Cache_Error("Can't remove directory '$dir'. Check permissions and path.", __FILE__, __LINE__);
356
357         $num_removed = 0;
358
359         while (false !== $file = readdir($dh)) {
360             if ('.' == $file || '..' == $file)
361                 continue;
362
363             $file = $dir . $file;
364             if (is_dir($file)) {
365                 $file .= '/';
366                 $num = $this->deleteDir($file . '/');
367                 if (is_int($num))
368                     $num_removed += $num;
369             } else {
370                 if (unlink($file))
371                     $num_removed++;
372             }
373         }
374         // according to php-manual the following is needed for windows installations.
375         closedir($dh);
376         unset( $dh);
377         if ($dir != $this->cache_dir) {  //delete the sub-dir entries  itself also, but not the cache-dir.
378             rmDir($dir);
379             $num_removed++;
380         }
381
382         return $num_removed;
383     } // end func deleteDir
384
385 } // end class file
386 ?>