1 | <?php
|
---|
2 | /**
|
---|
3 | * Generic class to handle caching
|
---|
4 | *
|
---|
5 | * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
|
---|
6 | * @author Chris Smith <[email protected]>
|
---|
7 | */
|
---|
8 |
|
---|
9 | if(!defined('DOKU_INC')) die('meh.');
|
---|
10 |
|
---|
11 | class cache {
|
---|
12 | var $key = ''; // primary identifier for this item
|
---|
13 | var $ext = ''; // file ext for cache data, secondary identifier for this item
|
---|
14 | var $cache = ''; // cache file name
|
---|
15 | var $depends = array(); // array containing cache dependency information,
|
---|
16 | // used by _useCache to determine cache validity
|
---|
17 |
|
---|
18 | var $_event = ''; // event to be triggered during useCache
|
---|
19 |
|
---|
20 | function cache($key,$ext) {
|
---|
21 | $this->key = $key;
|
---|
22 | $this->ext = $ext;
|
---|
23 | $this->cache = getCacheName($key,$ext);
|
---|
24 | }
|
---|
25 |
|
---|
26 | /**
|
---|
27 | * public method to determine whether the cache can be used
|
---|
28 | *
|
---|
29 | * to assist in cetralisation of event triggering and calculation of cache statistics,
|
---|
30 | * don't override this function override _useCache()
|
---|
31 | *
|
---|
32 | * @param array $depends array of cache dependencies, support dependecies:
|
---|
33 | * 'age' => max age of the cache in seconds
|
---|
34 | * 'files' => cache must be younger than mtime of each file
|
---|
35 | * (nb. dependency passes if file doesn't exist)
|
---|
36 | *
|
---|
37 | * @return bool true if cache can be used, false otherwise
|
---|
38 | */
|
---|
39 | function useCache($depends=array()) {
|
---|
40 | $this->depends = $depends;
|
---|
41 | $this->_addDependencies();
|
---|
42 |
|
---|
43 | if ($this->_event) {
|
---|
44 | return $this->_stats(trigger_event($this->_event,$this,array($this,'_useCache')));
|
---|
45 | } else {
|
---|
46 | return $this->_stats($this->_useCache());
|
---|
47 | }
|
---|
48 | }
|
---|
49 |
|
---|
50 | /**
|
---|
51 | * private method containing cache use decision logic
|
---|
52 | *
|
---|
53 | * this function processes the following keys in the depends array
|
---|
54 | * purge - force a purge on any non empty value
|
---|
55 | * age - expire cache if older than age (seconds)
|
---|
56 | * files - expire cache if any file in this array was updated more recently than the cache
|
---|
57 | *
|
---|
58 | * can be overridden
|
---|
59 | *
|
---|
60 | * @return bool see useCache()
|
---|
61 | */
|
---|
62 | function _useCache() {
|
---|
63 |
|
---|
64 | if (!empty($this->depends['purge'])) return false; // purge requested?
|
---|
65 | if (!($this->_time = @filemtime($this->cache))) return false; // cache exists?
|
---|
66 |
|
---|
67 | // cache too old?
|
---|
68 | if (!empty($this->depends['age']) && ((time() - $this->_time) > $this->depends['age'])) return false;
|
---|
69 |
|
---|
70 | if (!empty($this->depends['files'])) {
|
---|
71 | foreach ($this->depends['files'] as $file) {
|
---|
72 | if ($this->_time < @filemtime($file)) return false; // cache older than files it depends on?
|
---|
73 | }
|
---|
74 | }
|
---|
75 |
|
---|
76 | return true;
|
---|
77 | }
|
---|
78 |
|
---|
79 | /**
|
---|
80 | * add dependencies to the depends array
|
---|
81 | *
|
---|
82 | * this method should only add dependencies,
|
---|
83 | * it should not remove any existing dependencies and
|
---|
84 | * it should only overwrite a dependency when the new value is more stringent than the old
|
---|
85 | */
|
---|
86 | function _addDependencies() {
|
---|
87 | if (isset($_REQUEST['purge'])) $this->depends['purge'] = true; // purge requested
|
---|
88 | }
|
---|
89 |
|
---|
90 | /**
|
---|
91 | * retrieve the cached data
|
---|
92 | *
|
---|
93 | * @param bool $clean true to clean line endings, false to leave line endings alone
|
---|
94 | * @return string cache contents
|
---|
95 | */
|
---|
96 | function retrieveCache($clean=true) {
|
---|
97 | return io_readFile($this->cache, $clean);
|
---|
98 | }
|
---|
99 |
|
---|
100 | /**
|
---|
101 | * cache $data
|
---|
102 | *
|
---|
103 | * @param string $data the data to be cached
|
---|
104 | * @return bool true on success, false otherwise
|
---|
105 | */
|
---|
106 | function storeCache($data) {
|
---|
107 | return io_savefile($this->cache, $data);
|
---|
108 | }
|
---|
109 |
|
---|
110 | /**
|
---|
111 | * remove any cached data associated with this cache instance
|
---|
112 | */
|
---|
113 | function removeCache() {
|
---|
114 | @unlink($this->cache);
|
---|
115 | }
|
---|
116 |
|
---|
117 | /**
|
---|
118 | * Record cache hits statistics.
|
---|
119 | * (Only when debugging allowed, to reduce overhead.)
|
---|
120 | *
|
---|
121 | * @param bool $success result of this cache use attempt
|
---|
122 | * @return bool pass-thru $success value
|
---|
123 | */
|
---|
124 | function _stats($success) {
|
---|
125 | global $conf;
|
---|
126 | static $stats = null;
|
---|
127 | static $file;
|
---|
128 |
|
---|
129 | if (!$conf['allowdebug']) { return $success; }
|
---|
130 |
|
---|
131 | if (is_null($stats)) {
|
---|
132 | $file = $conf['cachedir'].'/cache_stats.txt';
|
---|
133 | $lines = explode("\n",io_readFile($file));
|
---|
134 |
|
---|
135 | foreach ($lines as $line) {
|
---|
136 | $i = strpos($line,',');
|
---|
137 | $stats[substr($line,0,$i)] = $line;
|
---|
138 | }
|
---|
139 | }
|
---|
140 |
|
---|
141 | if (isset($stats[$this->ext])) {
|
---|
142 | list($ext,$count,$hits) = explode(',',$stats[$this->ext]);
|
---|
143 | } else {
|
---|
144 | $ext = $this->ext;
|
---|
145 | $count = 0;
|
---|
146 | $hits = 0;
|
---|
147 | }
|
---|
148 |
|
---|
149 | $count++;
|
---|
150 | if ($success) $hits++;
|
---|
151 | $stats[$this->ext] = "$ext,$count,$hits";
|
---|
152 |
|
---|
153 | io_saveFile($file,join("\n",$stats));
|
---|
154 |
|
---|
155 | return $success;
|
---|
156 | }
|
---|
157 | }
|
---|
158 |
|
---|
159 | class cache_parser extends cache {
|
---|
160 |
|
---|
161 | var $file = ''; // source file for cache
|
---|
162 | var $mode = ''; // input mode (represents the processing the input file will undergo)
|
---|
163 |
|
---|
164 | var $_event = 'PARSER_CACHE_USE';
|
---|
165 |
|
---|
166 | function cache_parser($id, $file, $mode) {
|
---|
167 | if ($id) $this->page = $id;
|
---|
168 | $this->file = $file;
|
---|
169 | $this->mode = $mode;
|
---|
170 |
|
---|
171 | parent::cache($file.$_SERVER['HTTP_HOST'].$_SERVER['SERVER_PORT'],'.'.$mode);
|
---|
172 | }
|
---|
173 |
|
---|
174 | function _useCache() {
|
---|
175 |
|
---|
176 | if (!@file_exists($this->file)) return false; // source exists?
|
---|
177 | return parent::_useCache();
|
---|
178 | }
|
---|
179 |
|
---|
180 | function _addDependencies() {
|
---|
181 | global $conf, $config_cascade;
|
---|
182 |
|
---|
183 | $this->depends['age'] = isset($this->depends['age']) ?
|
---|
184 | min($this->depends['age'],$conf['cachetime']) : $conf['cachetime'];
|
---|
185 |
|
---|
186 | // parser cache file dependencies ...
|
---|
187 | $files = array($this->file, // ... source
|
---|
188 | DOKU_INC.'inc/parser/parser.php', // ... parser
|
---|
189 | DOKU_INC.'inc/parser/handler.php', // ... handler
|
---|
190 | );
|
---|
191 | $files = array_merge($files, getConfigFiles('main')); // ... wiki settings
|
---|
192 |
|
---|
193 | $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
|
---|
194 | parent::_addDependencies();
|
---|
195 | }
|
---|
196 |
|
---|
197 | }
|
---|
198 |
|
---|
199 | class cache_renderer extends cache_parser {
|
---|
200 | function _useCache() {
|
---|
201 | global $conf;
|
---|
202 |
|
---|
203 | if (!parent::_useCache()) return false;
|
---|
204 |
|
---|
205 | if (!isset($this->page)) {
|
---|
206 | return true;
|
---|
207 | }
|
---|
208 |
|
---|
209 | // check current link existence is consistent with cache version
|
---|
210 | // first check the purgefile
|
---|
211 | // - if the cache is more recent than the purgefile we know no links can have been updated
|
---|
212 | if ($this->_time >= @filemtime($conf['cachedir'].'/purgefile')) {
|
---|
213 | return true;
|
---|
214 | }
|
---|
215 |
|
---|
216 | // for wiki pages, check metadata dependencies
|
---|
217 | $metadata = p_get_metadata($this->page);
|
---|
218 |
|
---|
219 | if (!isset($metadata['relation']['references']) ||
|
---|
220 | empty($metadata['relation']['references'])) {
|
---|
221 | return true;
|
---|
222 | }
|
---|
223 |
|
---|
224 | foreach ($metadata['relation']['references'] as $id => $exists) {
|
---|
225 | if ($exists != page_exists($id,'',false)) return false;
|
---|
226 | }
|
---|
227 |
|
---|
228 | return true;
|
---|
229 | }
|
---|
230 |
|
---|
231 | function _addDependencies() {
|
---|
232 |
|
---|
233 | // renderer cache file dependencies ...
|
---|
234 | $files = array(
|
---|
235 | DOKU_INC.'inc/parser/'.$this->mode.'.php', // ... the renderer
|
---|
236 | );
|
---|
237 |
|
---|
238 | // page implies metadata and possibly some other dependencies
|
---|
239 | if (isset($this->page)) {
|
---|
240 |
|
---|
241 | $metafile = metaFN($this->page,'.meta');
|
---|
242 | $files[] = $metafile; // ... the page's own metadata
|
---|
243 |
|
---|
244 | $valid = p_get_metadata($this->page, 'date valid'); // for xhtml this will render the metadata if needed
|
---|
245 | if (!empty($valid['age'])) {
|
---|
246 | $this->depends['age'] = isset($this->depends['age']) ?
|
---|
247 | min($this->depends['age'],$valid['age']) : $valid['age'];
|
---|
248 | }
|
---|
249 | }
|
---|
250 |
|
---|
251 | $this->depends['files'] = !empty($this->depends['files']) ? array_merge($files, $this->depends['files']) : $files;
|
---|
252 | parent::_addDependencies();
|
---|
253 | }
|
---|
254 | }
|
---|
255 |
|
---|
256 | class cache_instructions extends cache_parser {
|
---|
257 |
|
---|
258 | function cache_instructions($id, $file) {
|
---|
259 | parent::cache_parser($id, $file, 'i');
|
---|
260 | }
|
---|
261 |
|
---|
262 | function retrieveCache($clean=true) {
|
---|
263 | $contents = io_readFile($this->cache, false);
|
---|
264 | return !empty($contents) ? unserialize($contents) : array();
|
---|
265 | }
|
---|
266 |
|
---|
267 | function storeCache($instructions) {
|
---|
268 | return io_savefile($this->cache,serialize($instructions));
|
---|
269 | }
|
---|
270 | }
|
---|