1: <?php
  2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16: 
 17: class Horde_Cache_Storage_File extends Horde_Cache_Storage_Base
 18: {
 19:     
 20:     const GC_FILE = 'horde_cache_gc';
 21: 
 22:      23:  24:  25:  26: 
 27:     protected $_dir;
 28: 
 29:      30:  31:  32:  33: 
 34:     protected $_file = array();
 35: 
 36:      37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49: 
 50:     public function __construct(array $params = array())
 51:     {
 52:         $params = array_merge(array(
 53:             'prefix' => 'cache_',
 54:             'sub' => 0
 55:         ), $params);
 56: 
 57:         $this->_dir = (isset($params['dir']) && @is_dir($params['dir']))
 58:             ? $params['dir']
 59:             : Horde_Util::getTempDir();
 60: 
 61:         parent::__construct($params);
 62:     }
 63: 
 64:      65:  66: 
 67:     public function __destruct()
 68:     {
 69:         
 70:         if (rand(0, 999) != 0) {
 71:             return;
 72:         }
 73: 
 74:         $filename = $this->_dir . '/' . self::GC_FILE;
 75:         $excepts = array();
 76: 
 77:         if (is_readable($filename)) {
 78:             $gc_file = file($filename, FILE_IGNORE_NEW_LINES);
 79:             reset($gc_file);
 80:             next($gc_file);
 81:             while (list(,$data) = each($gc_file)) {
 82:                 $parts = explode("\t", $data, 2);
 83:                 $excepts[$parts[0]] = $parts[1];
 84:             }
 85:         }
 86: 
 87:         $c_time = time();
 88: 
 89:         foreach ($this->_getCacheFiles() as $fname => $pname) {
 90:             $d_time = isset($excepts[$fname])
 91:                 ? $excepts[$fname]
 92:                 : $this->_params['lifetime'];
 93: 
 94:             if (!empty($d_time) &&
 95:                 (($c_time - $d_time) > filemtime($pname))) {
 96:                 @unlink($pname);
 97:                 unset($excepts[$fname]);
 98:             }
 99:         }
100: 
101:         if ($fp = @fopen($filename, 'w')) {
102:             foreach ($excepts as $key => $val) {
103:                 fwrite($fp, $key . "\t" . $val . "\n");
104:             }
105:             fclose($fp);
106:         }
107:     }
108: 
109:     110: 
111:     public function get($key, $lifetime = 0)
112:     {
113:         if (!$this->exists($key, $lifetime)) {
114:             
115:             return false;
116:         }
117: 
118:         $filename = $this->_keyToFile($key);
119:         $size = filesize($filename);
120: 
121:         return $size
122:             ? @file_get_contents($filename)
123:             : '';
124:     }
125: 
126:     127: 
128:     public function set($key, $data, $lifetime = 0)
129:     {
130:         $filename = $this->_keyToFile($key, true);
131:         $tmp_file = Horde_Util::getTempFile('HordeCache', true, $this->_dir);
132:         if (isset($this->_params['umask'])) {
133:             chmod($tmp_file, 0666 & ~$this->_params['umask']);
134:         }
135: 
136:         if (file_put_contents($tmp_file, $data) === false) {
137:             throw new Horde_Cache_Exception('Cannot write to cache directory ' . $this->_dir);
138:         }
139: 
140:         @rename($tmp_file, $filename);
141: 
142:         if (($lifetime != $this->_params['lifetime']) &&
143:             ($fp = @fopen($this->_dir . '/' . self::GC_FILE, 'a'))) {
144:             
145:             
146:             
147:             fwrite($fp, $filename . "\t" . (empty($lifetime) ? 0 : time() + $lifetime) . "\n");
148:             fclose($fp);
149:         }
150:     }
151: 
152:     153: 
154:     public function exists($key, $lifetime = 0)
155:     {
156:         $filename = $this->_keyToFile($key);
157: 
158:         
159:         if (file_exists($filename)) {
160:             161: 162: 
163:             if (($lifetime == 0) ||
164:                 (time() - $lifetime <= filemtime($filename))) {
165:                 return true;
166:             }
167: 
168:             @unlink($filename);
169:         }
170: 
171:         return false;
172:     }
173: 
174:     175: 
176:     public function expire($key)
177:     {
178:         $filename = $this->_keyToFile($key);
179:         return @unlink($filename);
180:     }
181: 
182:     183: 
184:     public function clear()
185:     {
186:         foreach ($this->_getCacheFiles() as $val) {
187:             @unlink($val);
188:         }
189:         @unlink($this->_dir . '/' . self::GC_FILE);
190:     }
191: 
192:     193: 194: 195: 196: 
197:     protected function _getCacheFiles()
198:     {
199:         $paths = array();
200: 
201:         try {
202:             $it = empty($this->_params['sub'])
203:                 ? new DirectoryIterator($this->_dir)
204:                 : new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->_dir), RecursiveIteratorIterator::CHILD_FIRST);
205:         } catch (UnexpectedValueException $e) {
206:             return $paths;
207:         }
208: 
209:         foreach ($it as $val) {
210:             if (!$val->isDir() &&
211:                 ($fname = $val->getFilename()) &&
212:                 (strpos($fname, $this->_params['prefix']) === 0)) {
213:                 $paths[$fname] = $val->getPathname();
214:             }
215:         }
216: 
217:         return $paths;
218:     }
219: 
220:     221: 222: 223: 224: 225: 226: 227: 
228:     protected function _keyToFile($key, $create = false)
229:     {
230:         if ($create || !isset($this->_file[$key])) {
231:             $dir = $this->_dir . '/';
232:             $md5 = hash('md5', $key);
233:             $sub = '';
234: 
235:             if (!empty($this->_params['sub'])) {
236:                 $max = min($this->_params['sub'], strlen($md5));
237:                 for ($i = 0; $i < $max; $i++) {
238:                     $sub .= $md5[$i];
239:                     if ($create && !is_dir($dir . $sub)) {
240:                         if (!mkdir($dir . $sub)) {
241:                             $sub = '';
242:                             break;
243:                         }
244:                     }
245:                     $sub .= '/';
246:                 }
247:             }
248: 
249:             $this->_file[$key] = $dir . $sub . $this->_params['prefix'] . $md5;
250:         }
251: 
252:         return $this->_file[$key];
253:     }
254: 
255: }
256: