b30daf02f0edcbdcdf8e33cf010f9d82a1bbd134
[atutor.git] / mods / wiki / plugins / db / dzf2.php
1 <?php
2
3 /*
4    (lacks a fancy name, but DZF2 stands for dir/zlib/files/version2)
5    
6    This plugin implements a more advanced flat-file database backend,
7    which is designed to be faster than the older db/flat_files and to
8    use the compressed/serialized format per default. Additionally it
9    works case-insensitive (ONLY!) even on Unix filesystems and the
10    filename encoding is engaged per default.
11    (summary: this database backend is optimized but inconfigureable)
12    
13    things you may do:
14    - the CACHE file can be deleted, it is auto-recreated when needed
15      (but this should not be necessary, except after a server crash)
16 */
17
18
19 #-- configuration settings
20 // define("EWIKI_DBFILES_DIRECTORY", "pages/");  # like with plugins/db/flat_files
21 define("EWIKI_DBFF_ACCURATE", 1);   # makes FIND call return image sizes
22 define("DZF2_HIT_COUNTING", 1);     # enables hit-counting
23
24
25 #-- hard-coded settings (don't try to change this)
26 define("EWIKI_CASE_INSENSITIVE", true);   # this is hard-coded in here, so don't disable or everything would break
27 $ewiki_plugins["database"][0] = "ewiki_database_dzf2";
28
29
30 #-- db interface backend
31 class ewiki_database_dzf2 {
32
33    var $dir = EWIKI_DBFILES_DIRECTORY;
34    var $gz = EWIKI_DBFILES_GZLEVEL;
35
36
37
38    function GET($id, $version=false) {
39       if (!$version && !($version = $this->LASTVER($id))) {
40          return;
41       }
42       #-- read file      
43       $dbfile = $this->FN("$id.$version");
44       $lock = fopen($dbfile, "rb");
45       flock($lock, LOCK_SH);
46       if ($f = @gzopen($dbfile, "rb")) {
47          $r = unserialize(gzread($f, 1<<21));
48          gzclose($f);
49       }
50       flock($lock, LOCK_UN);
51       fclose($lock);
52       if ($r && DZF2_HIT_COUNTING) {
53          if ($f = @fopen($this->FN("$id.hits"), "rb")) {
54             $r["hits"] = trim(fread($f, 10));
55             fclose($f);
56          }
57       }
58       return($r);
59    }
60
61
62    function WRITE($hash, $overwrite=0) {
63       $id = $hash["id"];
64       $version = $hash["version"];
65       $dbfile = $this->FN("$id.$version");
66       if (!$overwrite && file_exists($dbfile)) {
67          return;
68       }
69       #-- read-lock
70       if (file_exists($dbfile)) {
71          $lock = fopen($dbfile, "rb");
72          flock($lock, LOCK_EX);
73       }
74       #-- open file for writing, secondary lock
75       if ($f = gzopen($dbfile, "wb".$this->gz)) {
76          if (!lock) {
77             flock($f, LOCK_EX);
78          }
79          $r = gzwrite($f, serialize($hash));
80          gzclose($f);
81          $this->SETVER($id, $version);
82          $this->CACHE_ADD($id, $version);
83          return(1);
84       }
85       #-- dispose lock
86       if ($lock) {
87          flock($lock, LOCK_UN);
88          fclose($lock);
89       }
90       return(0);
91    }
92
93
94    function FIND($list) {
95       $r = array();
96       foreach ($list as $id) {
97          $fn = $this->FN($id);
98          if (file_exists($fn)) {
99             $r[$id] = 1;
100             if (EWIKI_DBFF_ACCURATE && (strpos($id, ":") || strpos($id, "."))) {
101                $uu = $this->GET($fn);
102                if ($uu["meta"]) {
103                   $r[$id] = $uu["meta"];
104                   $r[$id]["flags"] = $uu["flags"];
105                } else {
106                   $r[$id] = $uu["flags"];
107                }
108             }
109          }
110       }
111       return($r);
112    }
113
114
115    function GETALL($fields, $mask=0, $filter=0) {
116       $r = new ewiki_dbquery_result($fields);
117       $r->entries = $this->ALL();
118       return($r);
119    }
120
121
122    function SEARCH($field, $content, $ci="i", $regex=0, $mask=0, $filter=0) {
123       if ($ci && !$regex) {
124          $content = strtolower($content);
125       }
126       $r = new ewiki_dbquery_result($args);
127       
128       #-- fast title search
129       if (!$mask && ($field == "id")) {
130          foreach ($this->ALL() as $id) {
131             if ($regex && preg_match("\007$content\007$ci", $id)
132             or $ci && strpos(strtolower($id), $content)
133             or !$ci && strpos($id, $content) )
134             {
135                $this->entries[] = $id;
136             }
137          }
138       }
139       #-- must load all files from disk
140       else {
141          foreach ($this->ALL() as $id) {
142             $row = ewiki_database_dzf2::GET($id);
143             if ($mask && ($filter != ($row["flags"] & $mask))) {
144                continue;
145             }
146             if ($regex && preg_match("\007$content\007$ci", $row[$field])
147             or $ci && strpos(strtolower($row[$field]), $content)
148             or !$ci && strpos($row[$field], $content) )
149             {
150                $r->add($row);
151             }
152          }
153       }
154       return($r);
155    }
156
157
158    function DELETE($id, $version) {
159       $fn = $this->FN($id);
160       unlink("$fn.$version");
161       if (!$this->LASTVER($id)) {
162          @unlink("$fn");
163          @unlink("$fn.hits");
164          $this->ALL("_PURGE");
165       }
166    }
167
168
169    function INIT() {
170       if (!is_writeable($this->dir) || !is_dir($this->dir)) {
171          mkdir($this->dir)
172          or die("\nERROR in ewiki/db/dzf2: 'database' directory '$this->dir' is not world-writable (do the chmod 777 thing)!\n");
173       }
174       for ($c=97; $c<=122; $c++) { @mkdir($this->dir.'/'.chr($c)); }
175       for ($c=48; $c<=57; $c++) { @mkdir($this->dir.'/'.chr($c)); }
176       @mkdir($this->dir."/@");
177    }
178
179
180
181    #---------------------------------------------------------- internal ---
182
183    function FN($id) {
184       $id = ewiki_lowercase($id);
185       $c0 = $id[0];
186       if (($c0>="a") && ($c0<="z") || ($c0>="0") && ($c0<="9")) {
187          $letter = $c0;
188       }
189       else {
190          $letter = "@";
191       }
192       return(  $this->dir . "/$letter/" . rawurlencode($id)  );
193    }
194
195    function LASTVER($id, $count_through=0) {
196       $ver = NULL;
197       $fn = $this->FN($id);
198       if (file_exists($fn) && ($f = fopen($fn, "rb"))) {
199          $ver = 0 + trim(fgets($f, 10));
200          fclose($f);
201       }
202       return($ver);
203    }
204
205    function SETVER($id, $version) {
206       $fn = $this->FN($id);
207       if ($f = fopen($fn, "wb")) {
208          fwrite($f, "$version", 10);
209          fclose($f);
210       }
211       else {
212          echo "\nERROR in ewiki/db/dzf2: could not write version cache file for '$id'\n";
213       }
214    }
215
216    // reads page list cache file
217    function ALL($rewrite=0) {
218       $CACHE = $this->dir."/CACHE";
219
220       #-- generate cache
221       if (!file_exists($CACHE) || $rewrite) {
222          $r = $this->ALL_WALK();
223          if ($f = fopen($fn, "wb")) {
224             flock($f, LOCK_EX);
225             fwrite($f, "00000027_ewiki_DZF2_database_CACHE_FILE (do not edit!)\n"
226                   . implode("\n", $r) . "\n");
227             flock($f, LOCK_UN);
228          }
229       }
230       #-- read
231       elseif ($f = fopen($CACHE, "r")) {
232          flock($f, LOCK_SH);
233          $r = explode("\n", fread($f, 1<<21));
234          flock($f, LOCK_UN);
235          unset($r[0]);   // header
236          array_pop($r);
237       }
238       $f and fclose($f);
239
240       return($r);
241    }
242
243    // adds one entry to the cache file
244    function CACHE_ADD($id, $version) {
245       $CACHE = $this->dir."/CACHE";
246       if (($version >= 1) && ($f = fopen($CACHE, "ab"))) {
247          flock($f, LOCK_EX);
248          fwrite($f, ewiki_lowercase($id) . "\n");
249          flock($f, LOCK_UN);
250          fclose($f);
251       }
252    }
253
254    // scans through all dirs to detect existing pages
255    function ALL_WALK() {
256        $r = array();
257        $main = opendir($this->dir);
258        while ($sub = readdir($main)) {
259           if ((strlen($sub)==1) && ($sub[0]!=".") && is_dir($this->dir."/$sub")) {
260              $sub = $this->dir . "/" . $sub;
261              $dh = opendir($sub);
262              while ($fn = readdir($dh)) {
263                 if (($fn[0] != ".") && (strpos($fn, ".hits") != strlen($fn)-5)) {
264                    $fs = filesize($sub ."/". $fn);
265                    if ($fs && ($fs < 10)) {
266                       $r[] = rawurldecode($fn);
267              }  }  }
268           }
269        }
270        return($r);
271     }
272
273
274     function HIT($id, $add=+1)
275     {
276        if (!DZF2_HIT_COUNTING) {
277           return;
278        }
279        $dbfile = $this->FN($id) . ".hits";
280        
281        #-- open, read
282        if ($fr = @fopen($dbfile, "r")) {
283           flock($fr, LOCK_SH);
284           $r = trim(fgets($fr, 10));
285        }
286        else {
287           $r = 0;
288        }
289        #-- update
290        if ($add) {
291           if ($fr) {
292              flock($fr, LOCK_EX);
293           }
294           $r += $add;
295           $fw = fopen($dbfile, "w");
296           fwrite($fw, "$r");
297           fclose($fw);
298        }
299        #-- close, return value
300        if ($fr) {
301           flock($fr, LOCK_UN);
302           fclose($fr);
303        }
304        return($r);
305     }
306
307
308 }  // end of class
309
310
311 ?>