1 <?php
  2 
  3   4   5   6   7   8   9  10 
 11 
 12 namespace ICanBoogie;
 13 
 14 class FileCache
 15 {
 16     const T_COMPRESS = 'compress';
 17     const T_REPOSITORY = 'repository';
 18     const T_REPOSITORY_DELETE_RATIO = 'repository_delete_ratio';
 19     const T_REPOSITORY_SIZE = 'repository_size';
 20     const T_SERIALIZE = 'serialize';
 21     const T_MODIFIED_TIME = 'modified_time';
 22 
 23     public $compress = false;
 24     public $repository;
 25     public $repository_delete_ratio = .25;
 26     public $repository_size = 512;
 27     public $serialize = false;
 28     public $modified_time;
 29 
 30     protected $root;
 31 
 32     public function __construct(array $tags)
 33     {
 34         if (empty($tags[self::T_REPOSITORY]))
 35         {
 36             throw new Exception('The %tag tag is required', [ '%tag' => 'T_REPOSITORY' ]);
 37         }
 38 
 39         foreach ($tags as $tag => $value)
 40         {
 41             $this->$tag = $value;
 42         }
 43 
 44         if (strpos($this->repository, DOCUMENT_ROOT) === 0)
 45         {
 46             $this->repository = substr($this->repository, strlen(DOCUMENT_ROOT) - 1);
 47         }
 48 
 49         $this->root = realpath(\ICanBoogie\DOCUMENT_ROOT . $this->repository);
 50     }
 51 
 52      53  54  55  56  57  58  59  60  61  62  63  64  65  66  67 
 68 
 69     public function get($file, $constructor, $userdata=null)
 70     {
 71         if (!is_dir($this->root))
 72         {
 73             throw new Exception('The repository %repository does not exists.', [ '%repository' => $this->repository ], 404);
 74         }
 75 
 76         $location = getcwd();
 77 
 78         chdir($this->root);
 79 
 80         if (!is_file($file) || ($this->modified_time && $this->modified_time > filemtime($file)))
 81         {
 82             $file = call_user_func($constructor, $this, $file, $userdata);
 83         }
 84 
 85         chdir($location);
 86 
 87         return $file ? $this->repository . '/' . $file : $file;
 88     }
 89 
 90     public function exists($key)
 91     {
 92         $location = getcwd();
 93 
 94         chdir($this->root);
 95 
 96         $rc = file_exists($key);
 97 
 98         chdir($location);
 99 
100         return $rc;
101     }
102 
103     104 105 106 107 108 109 110 111 112 113 114 115 116 
117     public function load($key, $constructor, $userdata=null)
118     {
119         
120         
121         
122         
123 
124         if (!is_dir($this->root))
125         {
126             throw new Exception('The repository %repository does not exists.', [ '%repository' => $this->repository ], 404);
127 
128             return call_user_func($contructor, $userdata, $this, $key);
129         }
130 
131         
132         
133         
134 
135         $location = getcwd();
136 
137         chdir($this->root);
138 
139         $contents = null;
140 
141         if (is_readable($key))
142         {
143             $contents = file_get_contents($key);
144 
145             if ($this->compress)
146             {
147                 $contents = gzinflate($contents);
148             }
149 
150             if ($this->serialize)
151             {
152                 $contents = unserialize($contents);
153             }
154         }
155 
156         if ($contents === null)
157         {
158             $contents = call_user_func($constructor, $userdata, $this, $key);
159 
160             $this->save($key, $contents);
161         }
162 
163         chdir($location);
164 
165         return $contents;
166     }
167 
168     169 170 171 172 173 174 175 
176     protected function save($file, $contents)
177     {
178         if (!is_writable($this->root))
179         {
180             throw new Exception('The repository %repository is not writable.', [ '%repository' => $this->repository ]);
181         }
182 
183         $location = getcwd();
184 
185         chdir($this->root);
186 
187         if ($this->serialize)
188         {
189             $contents = serialize($contents);
190         }
191 
192         if ($this->compress)
193         {
194             $contents = gzdeflate($contents);
195         }
196 
197         $rc = file_put_contents($file, $contents, LOCK_EX);
198 
199         chdir($location);
200 
201         return $rc;
202     }
203 
204     public function store($key, $data)
205     {
206         return $this->save($key, $data);
207     }
208 
209     public function retrieve($key)
210     {
211         $location = getcwd();
212 
213         chdir($this->root);
214 
215         $rc = file_get_contents($key);
216 
217         chdir($location);
218 
219         return $rc;
220     }
221 
222     public function delete($file)
223     {
224         return $this->unlink([ $file => true ]);
225     }
226 
227     228 229 230 231 232 233 234 
235 
236     protected function read()
237     {
238         $root = $this->root;
239 
240         if (!is_dir($root))
241         {
242             
243 
244             return false;
245         }
246 
247         try
248         {
249             $dir = new \DirectoryIterator($root);
250         }
251         catch (\UnexpectedValueException $e)
252         {
253             throw new Exception('Unable to open directory %root', [ '%root' => $root ]);
254         }
255 
256         
257         
258         
259         
260 
261         $files = [];
262 
263         foreach ($dir as $file)
264         {
265             if (!$file->isDot())
266             {
267                 $files[$file->getFilename()] = [ $file->getCTime(), $file->getSize() ];
268             }
269         }
270 
271         return $files;
272     }
273 
274     protected function unlink($files)
275     {
276         if (!$files)
277         {
278             return;
279         }
280 
281         
282         
283         
284 
285         $location = getcwd();
286 
287         chdir($this->root);
288 
289         
290         
291         
292 
293         $lh = fopen('.lock', 'w+');
294 
295         if (!$lh)
296         {
297             Debug::trigger('Unable to lock %repository', [ '%repository' => $this->repository ]);
298 
299             chdir($location);
300 
301             return;
302         }
303 
304         
305         
306         
307 
308         $n = 10;
309 
310         while (!flock($lh, LOCK_EX | LOCK_NB))
311         {
312             
313             
314             
315             
316             
317 
318             usleep(round(rand(0, 100) * 1000));
319 
320             if (!--$n)
321             {
322                 
323                 
324                 
325                 
326 
327                 chdir($location);
328 
329                 return;
330             }
331         }
332 
333         
334         
335         
336 
337         foreach ($files as $file => $dummy)
338         {
339             
340             
341             
342             
343 
344             if (!file_exists($file))
345             {
346                 continue;
347             }
348 
349             unlink($file);
350         }
351 
352         chdir($location);
353 
354         
355         
356         
357 
358         fclose($lh);
359     }
360 
361     362 363 364 365 
366 
367     public function clear()
368     {
369         $files = $this->read();
370 
371         return $this->unlink($files);
372     }
373 
374     375 376 377 378 
379 
380     public function clean()
381     {
382         $files = $this->read();
383 
384         if (!$files)
385         {
386             return;
387         }
388 
389         $totalsize = 0;
390 
391         foreach ($files as $stat)
392         {
393             $totalsize += $stat[1];
394         }
395 
396         $repository_size = $this->repository_size * 1024;
397 
398         if ($totalsize < $repository_size)
399         {
400             
401             
402             
403 
404             return;
405         }
406 
407         
408         
409         
410         
411         
412 
413         asort($files);
414 
415         $deletesize = $repository_size * $this->repository_delete_ratio;
416 
417         $i = 0;
418 
419         foreach ($files as $file => $stat)
420         {
421             $i++;
422 
423             $deletesize -= $stat[1];
424 
425             if ($deletesize < 0)
426             {
427                 break;
428             }
429         }
430 
431         $files = array_slice($files, 0, $i);
432 
433         return $this->unlink($files);
434     }
435 }