/gforge/plugins/wiki/www/lib/pear/Cache/Container/imgfile.php

https://github.com/neymanna/fusionforge · PHP · 382 lines · 264 code · 32 blank · 86 comment · 32 complexity · d2694fc398427ad600ba44966c9509d0 MD5 · raw file

  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PHP Version 4 |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 1997, 1998, 1999, 2000, 2001 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: imgfile.php,v 1.3 2004/04/26 20:44:36 rurban Exp $
  20. require_once('Cache/Container.php');
  21. /**
  22. * Stores cache contents in a file.
  23. *
  24. * @author Ulf Wendel <ulf.wendel@phpdoc.de>
  25. * @version $Id: imgfile.php,v 1.3 2004/04/26 20:44:36 rurban Exp $
  26. */
  27. class Cache_Container_file extends Cache_Container {
  28. /**
  29. * Directory where to put the cache files.
  30. *
  31. * @var string Make sure to add a trailing slash
  32. */
  33. var $cache_dir = '';
  34. /**
  35. * Filename prefix for cache files.
  36. *
  37. * You can use the filename prefix to implement a "domain" based cache or just
  38. * to give the files a more descriptive name. The word "domain" is borroed from
  39. * a user authentication system. One user id (cached dataset with the ID x)
  40. * may exists in different domains (different filename prefix). You might want
  41. * to use this to have different cache values for a production, development and
  42. * quality assurance system. If you want the production cache not to be influenced
  43. * by the quality assurance activities, use different filename prefixes for them.
  44. *
  45. * I personally don't think that you'll never need this, but 640kb happend to be
  46. * not enough, so... you know what I mean. If you find a useful application of the
  47. * feature please update this inline doc.
  48. *
  49. * @var string
  50. */
  51. var $filename_prefix = '';
  52. /**
  53. * List of cache entries, used within a gc run
  54. *
  55. * @var array
  56. */
  57. var $entries;
  58. /**
  59. * Total number of bytes required by all cache entries, used within a gc run.
  60. *
  61. * @var int
  62. */
  63. var $total_size = 0;
  64. /**
  65. * Creates the cache directory if neccessary
  66. *
  67. * @param array Config options: ["cache_dir" => ..., "filename_prefix" => ...]
  68. */
  69. function Cache_Container_file($options = '') {
  70. if (is_array($options))
  71. $this->setOptions($options, array_merge($this->allowed_options, array('cache_dir', 'filename_prefix')));
  72. clearstatcache();
  73. if ($this->cache_dir)
  74. {
  75. // make relative paths absolute for use in deconstructor.
  76. // it looks like the deconstructor has problems with relative paths
  77. if (OS_UNIX && '/' != $this->cache_dir{0} )
  78. $this->cache_dir = realpath( getcwd() . '/' . $this->cache_dir) . '/';
  79. // check if a trailing slash is in cache_dir
  80. if (!substr($this->cache_dir,-1) )
  81. $this->cache_dir .= '/';
  82. if (!file_exists($this->cache_dir) || !is_dir($this->cache_dir))
  83. mkdir($this->cache_dir, 0755);
  84. }
  85. $this->entries = array();
  86. $this->group_dirs = array();
  87. } // end func contructor
  88. function fetch($id, $group) {
  89. $file = $this->getFilename($id, $group);
  90. if (!file_exists($file))
  91. return array(NULL, NULL, NULL);
  92. // retrive the content
  93. if (!($fh = @fopen($file, 'rb')))
  94. return new Cache_Error("Can't access cache file '$file'. Check access rights and path.", __FILE__, __LINE__);
  95. // file format:
  96. // 1st line: expiration date
  97. // 2nd line: user data
  98. // 3rd+ lines: cache data
  99. $expire = trim(fgets($fh, 12));
  100. $userdata = trim(fgets($fh, 257));
  101. $cachedata = $this->decode(fread($fh, filesize($file)));
  102. fclose($fh);
  103. //JOHANNES START
  104. if (is_array($cachedata))
  105. if (file_exists($file.'.img')) {
  106. $fh = @fopen($file.'.img',"rb");
  107. $cachedata['image'] = fread($fh,filesize($file.'.img'));
  108. fclose($fh);
  109. }
  110. //JOHANNES END
  111. // last usage date used by the gc - maxlifetime
  112. // touch without second param produced stupid entries...
  113. touch($file,time());
  114. clearstatcache();
  115. return array($expire, $cachedata, $userdata);
  116. } // end func fetch
  117. /**
  118. * Stores a dataset.
  119. *
  120. * WARNING: If you supply userdata it must not contain any linebreaks,
  121. * otherwise it will break the filestructure.
  122. */
  123. function save($id, $cachedata, $expires, $group, $userdata) {
  124. $this->flushPreload($id, $group);
  125. $file = $this->getFilename($id, $group);
  126. if (!($fh = @fopen($file, 'wb')))
  127. return new Cache_Error("Can't access '$file' to store cache data. Check access rights and path.", __FILE__, __LINE__);
  128. //JOHANNES
  129. if (is_array($cachedata)&&isset($cachedata['image'])) {
  130. $image = $cachedata['image'];
  131. unset($cachedata['image']);
  132. }
  133. //JOHANNES
  134. // file format:
  135. // 1st line: expiration date
  136. // 2nd line: user data
  137. // 3rd+ lines: cache data
  138. $expires = $this->getExpiresAbsolute($expires);
  139. fwrite($fh, $expires . "\n");
  140. fwrite($fh, $userdata . "\n");
  141. fwrite($fh, $this->encode($cachedata));
  142. fclose($fh);
  143. // I'm not sure if we need this
  144. // i don't think we need this (chregu)
  145. // touch($file);
  146. //JOHANNES START
  147. if ($image) {
  148. $file = $this->getFilename($id, $group).'.img';
  149. if (!($fh = @fopen($file, 'wb')))
  150. return new Cache_Error("Can't access '$file' to store cache data. Check access rights and path.", __FILE__, __LINE__);
  151. fwrite($fh, $image);
  152. fclose($fh);
  153. }
  154. //JOHANNES END
  155. return true;
  156. } // end func save
  157. function remove($id, $group) {
  158. $this->flushPreload($id, $group);
  159. $file = $this->getFilename($id, $group);
  160. if (file_exists($file)) {
  161. $ok = unlink($file);
  162. clearstatcache();
  163. return $ok;
  164. }
  165. return false;
  166. } // end func remove
  167. function flush($group) {
  168. $this->flushPreload();
  169. $dir = ($group) ? $this->cache_dir . $group . '/' : $this->cache_dir;
  170. $num_removed = $this->deleteDir($dir);
  171. unset($this->group_dirs[$group]);
  172. clearstatcache();
  173. return $num_removed;
  174. } // end func flush
  175. function idExists($id, $group) {
  176. return file_exists($this->getFilename($id, $group));
  177. } // end func idExists
  178. /**
  179. * Deletes all expired files.
  180. *
  181. * Garbage collection for files is a rather "expensive", "long time"
  182. * operation. All files in the cache directory have to be examined which
  183. * means that they must be opened for reading, the expiration date has to be
  184. * read from them and if neccessary they have to be unlinked (removed).
  185. * If you have a user comment for a good default gc probability please add it to
  186. * to the inline docs.
  187. *
  188. * @param integer Maximum lifetime in seconds of an no longer used/touched entry
  189. * @throws Cache_Error
  190. */
  191. function garbageCollection($maxlifetime) {
  192. $this->flushPreload();
  193. clearstatcache();
  194. $ok = $this->doGarbageCollection($maxlifetime, $this->cache_dir);
  195. // check the space used by the cache entries
  196. if ($this->total_size > $this->highwater) {
  197. krsort($this->entries);
  198. reset($this->entries);
  199. while ($this->total_size > $this->lowwater && list($lastmod, $entry) = each($this->entries)) {
  200. if (@unlink($entry['file']))
  201. $this->total_size -= $entry['size'];
  202. else
  203. new CacheError("Can't delete {$entry["file"]}. Check the permissions.");
  204. }
  205. }
  206. $this->entries = array();
  207. $this->total_size = 0;
  208. return $ok;
  209. } // end func garbageCollection
  210. /**
  211. * Does the recursive gc procedure, protected.
  212. *
  213. * @param integer Maximum lifetime in seconds of an no longer used/touched entry
  214. * @param string directory to examine - don't sets this parameter, it's used for a
  215. * recursive function call!
  216. * @throws Cache_Error
  217. */
  218. function doGarbageCollection($maxlifetime, $dir) {
  219. if (!($dh = opendir($dir)))
  220. return new Cache_Error("Can't access cache directory '$dir'. Check permissions and path.", __FILE__, __LINE__);
  221. while ($file = readdir($dh)) {
  222. if ('.' == $file || '..' == $file)
  223. continue;
  224. //JOHANNES START
  225. if ('.img' == substr($file,-4))
  226. continue;
  227. //JOHANNES END
  228. $file = $dir . $file;
  229. if (is_dir($file)) {
  230. $this->doGarbageCollection($maxlifetime,$file . '/');
  231. continue;
  232. }
  233. // skip trouble makers but inform the user
  234. if (!($fh = @fopen($file, 'rb'))) {
  235. new Cache_Error("Can't access cache file '$file', skipping it. Check permissions and path.", __FILE__, __LINE__);
  236. continue;
  237. }
  238. $expire = fgets($fh, 12);
  239. fclose($fh);
  240. $lastused = filemtime($file);
  241. //JOHANNES START
  242. $x = 0;
  243. if (file_exists($file.'.img'))
  244. $x = filesize($file.'.img');
  245. $this->entries[$lastused] = array('file' => $file, 'size' => filesize($file)+$x);
  246. $this->total_size += filesize($file)+$x;
  247. // remove if expired
  248. if ( ($expire && $expire <= time()) || ($lastused <= (time() - $maxlifetime)) ) {
  249. $ok = unlink($file);
  250. if ( file_exists($file.'.img') )
  251. $ok = $ok && unlink($file.'.img');
  252. if (!$ok)
  253. new Cache_Error("Can't unlink cache file '$file', skipping. Check permissions and path.", __FILE__, __LINE__);
  254. }
  255. //JOHANNES END
  256. }
  257. closedir($dh);
  258. // flush the disk state cache
  259. clearstatcache();
  260. } // end func doGarbageCollection
  261. /**
  262. * Returns the filename for the specified id.
  263. *
  264. * @param string dataset ID
  265. * @param string cache group
  266. * @return string full filename with the path
  267. * @access public
  268. */
  269. function getFilename($id, $group) {
  270. if (isset($this->group_dirs[$group]))
  271. return $this->group_dirs[$group] . $this->filename_prefix . $id;
  272. $dir = $this->cache_dir . $group . '/';
  273. if (!file_exists($dir)) {
  274. mkdir($dir, 0755);
  275. clearstatcache();
  276. }
  277. $this->group_dirs[$group] = $dir;
  278. return $dir . $this->filename_prefix . $id;
  279. } // end func getFilename
  280. /**
  281. * Deletes a directory and all files in it.
  282. *
  283. * @param string directory
  284. * @return integer number of removed files
  285. * @throws Cache_Error
  286. */
  287. function deleteDir($dir) {
  288. if (!($dh = opendir($dir)))
  289. return new Cache_Error("Can't remove directory '$dir'. Check permissions and path.", __FILE__, __LINE__);
  290. $num_removed = 0;
  291. while ($file = readdir($dh)) {
  292. if ('.' == $file || '..' == $file)
  293. continue;
  294. $file = $dir . $file;
  295. if (is_dir($file)) {
  296. $file .= '/';
  297. $num = $this->deleteDir($file . '/');
  298. if (is_int($num))
  299. $num_removed += $num;
  300. } else {
  301. if (unlink($file))
  302. $num_removed++;
  303. }
  304. }
  305. // according to php-manual the following is needed for windows installations.
  306. closedir($dh);
  307. unset( $dh);
  308. if ($dir != $this->cache_dir) { //delete the sub-dir entries itself also, but not the cache-dir.
  309. rmDir($dir);
  310. $num_removed++;
  311. }
  312. return $num_removed;
  313. } // end func deleteDir
  314. } // end class file
  315. ?>