4 (lacks a fancy name, but DZF2 stands for dir/zlib/files/version2)
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)
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)
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
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";
30 #-- db interface backend
31 class ewiki_database_dzf2 {
33 var $dir = EWIKI_DBFILES_DIRECTORY;
34 var $gz = EWIKI_DBFILES_GZLEVEL;
38 function GET($id, $version=false) {
39 if (!$version && !($version = $this->LASTVER($id))) {
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));
50 flock($lock, LOCK_UN);
52 if ($r && DZF2_HIT_COUNTING) {
53 if ($f = @fopen($this->FN("$id.hits"), "rb")) {
54 $r["hits"] = trim(fread($f, 10));
62 function WRITE($hash, $overwrite=0) {
64 $version = $hash["version"];
65 $dbfile = $this->FN("$id.$version");
66 if (!$overwrite && file_exists($dbfile)) {
70 if (file_exists($dbfile)) {
71 $lock = fopen($dbfile, "rb");
72 flock($lock, LOCK_EX);
74 #-- open file for writing, secondary lock
75 if ($f = gzopen($dbfile, "wb".$this->gz)) {
79 $r = gzwrite($f, serialize($hash));
81 $this->SETVER($id, $version);
82 $this->CACHE_ADD($id, $version);
87 flock($lock, LOCK_UN);
94 function FIND($list) {
96 foreach ($list as $id) {
98 if (file_exists($fn)) {
100 if (EWIKI_DBFF_ACCURATE && (strpos($id, ":") || strpos($id, "."))) {
101 $uu = $this->GET($fn);
103 $r[$id] = $uu["meta"];
104 $r[$id]["flags"] = $uu["flags"];
106 $r[$id] = $uu["flags"];
115 function GETALL($fields, $mask=0, $filter=0) {
116 $r = new ewiki_dbquery_result($fields);
117 $r->entries = $this->ALL();
122 function SEARCH($field, $content, $ci="i", $regex=0, $mask=0, $filter=0) {
123 if ($ci && !$regex) {
124 $content = strtolower($content);
126 $r = new ewiki_dbquery_result($args);
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) )
135 $this->entries[] = $id;
139 #-- must load all files from disk
141 foreach ($this->ALL() as $id) {
142 $row = ewiki_database_dzf2::GET($id);
143 if ($mask && ($filter != ($row["flags"] & $mask))) {
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) )
158 function DELETE($id, $version) {
159 $fn = $this->FN($id);
160 unlink("$fn.$version");
161 if (!$this->LASTVER($id)) {
164 $this->ALL("_PURGE");
170 if (!is_writeable($this->dir) || !is_dir($this->dir)) {
172 or die("\nERROR in ewiki/db/dzf2: 'database' directory '$this->dir' is not world-writable (do the chmod 777 thing)!\n");
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."/@");
181 #---------------------------------------------------------- internal ---
184 $id = ewiki_lowercase($id);
186 if (($c0>="a") && ($c0<="z") || ($c0>="0") && ($c0<="9")) {
192 return( $this->dir . "/$letter/" . rawurlencode($id) );
195 function LASTVER($id, $count_through=0) {
197 $fn = $this->FN($id);
198 if (file_exists($fn) && ($f = fopen($fn, "rb"))) {
199 $ver = 0 + trim(fgets($f, 10));
205 function SETVER($id, $version) {
206 $fn = $this->FN($id);
207 if ($f = fopen($fn, "wb")) {
208 fwrite($f, "$version", 10);
212 echo "\nERROR in ewiki/db/dzf2: could not write version cache file for '$id'\n";
216 // reads page list cache file
217 function ALL($rewrite=0) {
218 $CACHE = $this->dir."/CACHE";
221 if (!file_exists($CACHE) || $rewrite) {
222 $r = $this->ALL_WALK();
223 if ($f = fopen($fn, "wb")) {
225 fwrite($f, "00000027_ewiki_DZF2_database_CACHE_FILE (do not edit!)\n"
226 . implode("\n", $r) . "\n");
231 elseif ($f = fopen($CACHE, "r")) {
233 $r = explode("\n", fread($f, 1<<21));
235 unset($r[0]); // header
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"))) {
248 fwrite($f, ewiki_lowercase($id) . "\n");
254 // scans through all dirs to detect existing pages
255 function ALL_WALK() {
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;
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);
274 function HIT($id, $add=+1)
276 if (!DZF2_HIT_COUNTING) {
279 $dbfile = $this->FN($id) . ".hits";
282 if ($fr = @fopen($dbfile, "r")) {
284 $r = trim(fgets($fr, 10));
295 $fw = fopen($dbfile, "w");
299 #-- close, return value