1<?php
2/**
3 * DokuWiki Plugin cleanup (Helper Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Andreas Gohr <gohr@cosmocode.de>
7 */
8
9// must be run within Dokuwiki
10if(!defined('DOKU_INC')) die();
11
12class helper_plugin_cleanup extends DokuWiki_Plugin {
13    /** @var int log file pointer */
14    private $log = 0;
15
16    /** @var bool do no actually delete */
17    private $dryrun = true;
18
19    /** @var array list of files */
20    public $list = array();
21
22    /** @var int sum of deleted files */
23    public $size = 0;
24
25
26
27    /**
28     * Runs all the checks
29     */
30    public function run($run=false) {
31        global $conf;
32        $data = array();
33
34        $this->dryrun = !$run;
35
36        @set_time_limit(0);
37
38        search(
39            $data,
40            $conf['cachedir'],
41            array($this, 'cb_check_cache'),
42            array(
43                 'maxage' => $this->getConf('cacheage'),
44                 'useatime' => $this->supportsatime()
45            )
46        );
47
48        search(
49            $data,
50            $conf['olddir'],
51            array($this, 'cb_check_attic'),
52            array(
53                 'maxage' => $this->getConf('atticage'),
54                 'nonexonly' => $this->getConf('atticnoexonly')
55            )
56        );
57
58        search(
59            $data,
60            $conf['mediaolddir'],
61            array($this, 'cb_check_mediaattic'),
62            array(
63                 'maxage' => $this->getConf('mediaatticage'),
64                 'nonexonly' => $this->getConf('mediaatticnoexonly')
65            )
66        );
67
68        search(
69            $data,
70            $conf['metadir'],
71            array($this, 'cb_check_meta'),
72            array(
73                 'maxage' => $this->getConf('metaage'),
74            )
75        );
76
77        search(
78            $data,
79            $conf['lockdir'],
80            array($this, 'cb_check_locks'),
81            array(
82                 'maxage' => $this->getConf('lockage'),
83            )
84        );
85    }
86
87    /**
88     * Deletes the given file if $this->dryrun isn't set
89     *
90     * @param string $file file to delete
91     * @param string $type type of file to delete
92     */
93    public function delete($file, $type) {
94        global $conf;
95
96        $size = filesize($file);
97        $time = time();
98
99        // delete the file
100        if(!$this->dryrun){
101            if(@unlink($file)){
102                // log to file
103                if(!$this->log) $this->log = fopen($conf['cachedir'] . '/cleanup.log', 'a');
104                if($this->log) {
105                    fwrite($this->log, "$time\t$size\t$type\t$file\n");
106                }
107
108                $this->size += $size;
109                $this->list[] = $file;
110            }
111        }else{
112            $this->size += $size;
113            $this->list[] = $file;
114        }
115    }
116
117    /**
118     * Checks if the filesystem supports atimes
119     *
120     * @return bool
121     */
122    protected function supportsatime(){
123        global $conf;
124
125        $testfile = $conf['cachedir'].'/atime';
126        io_saveFile($testfile, 'x');
127        $mtime = filemtime($testfile);
128        sleep(1);
129        io_readFile($testfile);
130        clearstatcache(false, $testfile);
131        $atime = @fileatime($testfile);
132        @unlink($testfile);
133
134        return ($mtime != $atime);
135    }
136
137    /**
138     * Callback for checking the cache directories
139     */
140    public function cb_check_cache(&$data, $base, $file, $type, $lvl, $opts) {
141        if($type == 'd') {
142            // we only recurse into our known cache key directories
143            if($lvl == 1 && !preg_match('/^\/[a-f0-9]$/', $file)) return false;
144            return true;
145        }
146        if($lvl == 1) return false; // ignore all files in top directory
147
148        $time = $opts['useatime'] ? fileatime($base . $file) : filemtime($base . $file);
149
150        if(time() - $time > $opts['maxage']) {
151            $this->delete($base.$file, 'cache');
152        }
153        return true;
154    }
155
156    /**
157     * Callback for checking the page attic directories
158     */
159    public function cb_check_attic(&$data, $base, $file, $type, $lvl, $opts) {
160        if($type == 'd') {
161            return true;
162        }
163
164        $time = filemtime($base . $file);
165        if(time() - $time > $opts['maxage']) {
166            // skip existing?
167            if($opts['nonexonly']) {
168                $path = preg_replace('/\.\d+\.txt(\.gz)?$/', '', $file);
169                $id = pathID($path, true);
170                if(page_exists($id)) return false;
171            }
172
173            $this->delete($base.$file, 'attic');
174        }
175        return true;
176    }
177
178    /**
179     * Callback for checking the media attic directories
180     */
181    public function cb_check_mediaattic(&$data, $base, $file, $type, $lvl, $opts) {
182        if($type == 'd') {
183            return true;
184        }
185
186        $time = filemtime($base . $file);
187        if(time() - $time > $opts['maxage']) {
188            // skip existing?
189            if($opts['nonexonly']) {
190                list($ext) = mimetype($file);
191                $ext = preg_quote($ext, '/');
192                $path = preg_replace('/\.\d+\.' . $ext . '?$/', ".$ext", $file);
193                $id = pathID($path, true);
194
195                if(file_exists(mediaFN($id))) return false;
196            }
197
198            $this->delete($base.$file, 'mediattic');
199        }
200        return true;
201    }
202
203    /**
204     * Callback for checking the page meta directories
205     */
206    public function cb_check_meta(&$data, $base, $file, $type, $lvl, $opts) {
207        if($type == 'd') {
208            return true;
209        }
210
211        // only handle known extensions
212        if(!preg_match('/\.(meta|changes|indexed)$/', $file, $m)) return false;
213        $type = $m[1];
214
215        $time = filemtime($base . $file);
216        if(time() - $time > $opts['maxage']) {
217            $path = substr($file, 0, -1 * (strlen($type) + 1));
218            $id = pathID($path);
219            if(page_exists($id)) return false;
220
221            $this->delete($base.$file, 'meta');
222        }
223        return true;
224    }
225
226    /**
227     * Callback for checking the media meta directories
228     */
229    public function cb_check_mediameta(&$data, $base, $file, $type, $lvl, $opts) {
230        if($type == 'd') {
231            return true;
232        }
233
234        // only handle known extensions
235        if(!preg_match('/\.(changes)$/', $file, $m)) return false;
236        $type = $m[1];
237
238        $time = filemtime($base . $file);
239        if(time() - $time > $opts['maxage']) {
240            $path = substr($file, 0, -1 * (strlen($type) + 1));
241            $id = pathID($path);
242            if(file_exists(mediaFN($id))) return false;
243
244            $this->delete($base.$file, 'mediameta');
245        }
246        return true;
247    }
248
249    /**
250     * Callback for checking the locks directories
251     */
252    public function cb_check_locks(&$data, $base, $file, $type, $lvl, $opts) {
253        if($type == 'd') {
254            return true;
255        }
256
257        // only handle known extensions
258        if(!preg_match('/\.(lock)$/', $file, $m)) return false;
259        $type = $m[1];
260
261        $time = filemtime($base . $file);
262        if(time() - $time > $opts['maxage']) {
263            $this->delete($base.$file, 'lock');
264        }
265        return true;
266    }
267
268}
269
270// vim:ts=4:sw=4:et:
271