]> CyberLeo.Net >> Repos - SourceForge/phpwiki.git/blob - lib/WikiDB/backend/dbaBase.php
more rename_page backend methods: only tested for PearDB! please help
[SourceForge/phpwiki.git] / lib / WikiDB / backend / dbaBase.php
1 <?php rcs_id('$Id: dbaBase.php,v 1.7 2004-02-12 14:11:36 rurban Exp $');
2
3 require_once('lib/WikiDB/backend.php');
4
5 // FIXME:padding of data?  Is it needed?  dba_optimize() seems to do a good
6 // job at packing 'gdbm' (and 'db2') databases.
7
8 /*
9  * Tables:
10  *
11  *  page:
12  *   Index: pagename
13  *  Values: latestversion . ':' . flags . ':' serialized hash of page meta data
14  *           Currently flags = 1 if latest version has empty content.
15  *
16  *  version
17  *   Index: version:pagename
18  *   Value: serialized hash of revision meta data, including:
19  *          + quasi-meta-data %content
20  *
21  *  links
22  *   index: 'o' . pagename
23  *   value: serialized list of pages (names) which pagename links to.
24  *   index: 'i' . pagename
25  *   value: serialized list of pages which link to pagename
26  *
27  *  TODO:
28  *
29  *  Don't keep tables locked the whole time?
30  *
31  *  index table with:
32  *   list of pagenames for get_all_pages
33  *   mostpopular list?
34  *   RecentChanges support: 
35  *     lists of most recent edits (major, minor, either).
36  *   
37  *
38  *  Separate hit table, so we don't have to update the whole page entry
39  *  each time we get a hit.  (Maybe not so important though...).
40  */     
41
42 require_once('lib/DbaPartition.php');
43
44 class WikiDB_backend_dbaBase
45 extends WikiDB_backend
46 {
47     function WikiDB_backend_dbaBase (&$dba) {
48         $this->_db = &$dba;
49         // FIXME: page and version tables should be in their own files, probably.
50         // We'll pack them all in one for now (testing).
51         $this->_pagedb = new DbaPartition($dba, 'p');
52         $this->_versiondb = new DbaPartition($dba, 'v');
53         $linkdbpart = new DbaPartition($dba, 'l');
54         $this->_linkdb = new WikiDB_backend_dbaBase_linktable($linkdbpart);
55         $this->_dbdb = new DbaPartition($dba, 'd');
56     }
57
58     function close() {
59         $this->_db->close();
60     }
61
62     function optimize() {
63         $this->_db->optimize();
64     }
65
66     function sync() {
67         $this->_db->sync();
68     }
69
70     function rebuild() {
71         $this->_linkdb->rebuild();
72         $this->optimize();
73     }
74     
75     function check() {
76         return $this->_linkdb->check();
77     }
78
79     function get_pagedata($pagename) {
80         $result = $this->_pagedb->get($pagename);
81         if (!$result)
82             return false;
83         list(,,$packed) = explode(':', $result, 3);
84         $data = unserialize($packed);
85         return $data;
86     }
87
88             
89     function update_pagedata($pagename, $newdata) {
90         $result = $this->_pagedb->get($pagename);
91         if ($result) {
92             list($latestversion,$flags,$data) = explode(':', $result, 3);
93             $data = unserialize($data);
94         }
95         else {
96             $latestversion = $flags = 0;
97             $data = array();
98         }
99         
100         foreach ($newdata as $key => $val) {
101             if (empty($val))
102                 unset($data[$key]);
103             else
104                 $data[$key] = $val;
105         }
106         $this->_pagedb->set($pagename,
107                             (int)$latestversion . ':'
108                             . (int)$flags . ':'
109                             . serialize($data));
110     }
111
112     function get_latest_version($pagename) {
113         return (int) $this->_pagedb->get($pagename);
114     }
115
116     function get_previous_version($pagename, $version) {
117         $versdb = &$this->_versiondb;
118
119         while (--$version > 0) {
120             if ($versdb->exists($version . ":$pagename"))
121                 return $version;
122         }
123         return false;
124     }
125         
126     function get_versiondata($pagename, $version, $want_content = false) {
127         $data = $this->_versiondb->get((int)$version . ":$pagename");
128         return $data ? unserialize($data) : false;
129     }
130         
131     /**
132      * Delete page from the database.
133      */
134     function delete_page($pagename) {
135         $pagedb = &$this->_pagedb;
136         $versdb = &$this->_versiondb;
137
138         $version = $this->get_latest_version($pagename);
139         while ($version > 0) {
140             $versdb->set($version-- . ":$pagename", false);
141         }
142         $pagedb->set($pagename, false);
143
144         $this->set_links($pagename, false);
145     }
146
147     function rename_page($pagename, $to) {
148         $data = get_pagedata($pagename);
149         if (isset($data['pagename']))
150           $data['pagename'] = $to;
151         //$vdata = get_versiondata($pagename, $version, 1);
152         //$this->delete_page($pagename);
153         $this->update_pagedata($to, $data);
154         return true;
155     }
156             
157     /**
158      * Delete an old revision of a page.
159      */
160     function delete_versiondata($pagename, $version) {
161         $versdb = &$this->_versiondb;
162
163         $latest = $this->get_latest_version($pagename);
164
165         assert($version > 0);
166         assert($version <= $latest);
167         
168         $versdb->set((int)$version . ":$pagename", false);
169
170         if ($version == $latest) {
171             $previous = $this->get_previous_version($version);
172             if ($previous> 0) {
173                 $pvdata = $this->get_versiondata($pagename, $previous);
174                 $is_empty = empty($pvdata['%content']);
175             }
176             else
177                 $is_empty = true;
178             $this->_update_latest_version($pagename, $previous, $is_empty);
179         }
180     }
181
182     /**
183      * Create a new revision of a page.
184      */
185     function set_versiondata($pagename, $version, $data) {
186         $versdb = &$this->_versiondb;
187
188         $versdb->set((int)$version . ":$pagename", serialize($data));
189         if ($version > $this->get_latest_version($pagename))
190             $this->_update_latest_version($pagename, $version, empty($data['%content']));
191     }
192
193     function _update_latest_version($pagename, $latest, $flags) {
194         $pagedb = &$this->_pagedb;
195
196         $pdata = $pagedb->get($pagename);
197         if ($pdata)
198             list(,,$pagedata) = explode(':',$pdata,3);
199         else
200             $pagedata = serialize(array());
201         
202         $pagedb->set($pagename, (int)$latest . ':' . (int)$flags . ":$pagedata");
203     }
204
205     function get_all_pages($include_deleted = false, $orderby='pagename') {
206         $pagedb = &$this->_pagedb;
207
208         $pages = array();
209         for ($page = $pagedb->firstkey(); $page!== false; $page = $pagedb->nextkey()) {
210             if (!$page) {
211                 assert(!empty($page));
212                 continue;
213             }
214             
215             if (!$include_deleted) {
216                 list($latestversion,$flags,) = explode(':', $pagedb->get($page), 3);
217                 if ($latestversion == 0 || $flags != 0)
218                     continue;   // current content is empty 
219             }
220             $pages[] = $page;
221         }
222         usort($pages, 'WikiDB_backend_dbaBase_sortbypagename');
223         return new WikiDB_backend_dbaBase_pageiter($this, $pages);
224     }
225
226     function set_links($pagename, $links) {
227         $this->_linkdb->set_links($pagename, $links);
228     }
229     
230
231     function get_links($pagename, $reversed = true) {
232         /*
233         if ($reversed) {
234             include_once('lib/WikiDB/backend/dumb/BackLinkIter.php');
235             $pages = $this->get_all_pages();
236             return new WikiDB_backend_dumb_BackLinkIter($this, $pages, $pagename);
237         }
238         */
239         $links = $this->_linkdb->get_links($pagename, $reversed);
240         return new WikiDB_backend_dbaBase_pageiter($this, $links);
241     }
242 };
243
244 function WikiDB_backend_dbaBase_sortbypagename ($a, $b) {
245     $aname = $a['pagename'];
246     $bname = $b['pagename'];
247     return strcasecmp($aname, $bname);
248 }
249
250
251 class WikiDB_backend_dbaBase_pageiter
252 extends WikiDB_backend_iterator
253 {
254     function WikiDB_backend_dbaBase_pageiter(&$backend, &$pages) {
255         $this->_backend = $backend;
256         $this->_pages = $pages ? $pages : array();
257     }
258
259     function next() {
260         if ( ! ($next = array_shift($this->_pages)) )
261             return false;
262         return array('pagename' => $next);
263     }
264             
265     function free() {
266         $this->_pages = array();
267     }
268 };
269
270 class WikiDB_backend_dbaBase_linktable 
271 {
272     function WikiDB_backend_dbaBase_linktable(&$dba) {
273         $this->_db = &$dba;
274     }
275
276     //FIXME: try stroring link lists as hashes rather than arrays.
277     // (backlink deletion would be faster.)
278     
279     function get_links($page, $reversed = true) {
280         return $this->_get_links($reversed ? 'i' : 'o', $page);
281     }
282     
283     function set_links($page, $newlinks) {
284
285         $oldlinks = $this->_get_links('o', $page);
286
287         if (!is_array($newlinks)) {
288             assert(empty($newlinks));
289             $newlinks = array();
290         }
291         else {
292             $newlinks = array_unique($newlinks);
293         }
294         sort($newlinks);
295         $this->_set_links('o', $page, $newlinks);
296
297         reset($newlinks);
298         reset($oldlinks);
299         $new = current($newlinks);
300         $old = current($oldlinks);
301         while ($new !== false || $old !== false) {
302             if ($old === false || ($new !== false && $new < $old)) {
303                 // $new is a new link (not in $oldlinks).
304                 $this->_add_backlink($new, $page);
305                 $new = next($newlinks);
306             }
307             elseif ($new === false || $old < $new) {
308                 // $old is a obsolete link (not in $newlinks).
309                 $this->_delete_backlink($old, $page);
310                 $old = next($oldlinks);
311             }
312             else {
313                 // Unchanged link (in both $newlist and $oldlinks).
314                 assert($new == $old);
315                 $new = next($newlinks);
316                 $old = next($oldlinks);
317             }
318         }
319     }
320
321     /**
322      * Rebuild the back-link index.
323      *
324      * This should never be needed, but if the database gets hosed for some reason,
325      * this should put it back into a consistent state.
326      *
327      * We assume the forward links in the our table are correct, and recalculate
328      * all the backlinks appropriately.
329      */
330     function rebuild () {
331         $db = &$this->_db;
332
333         // Delete the backlink tables, make a list of page names.
334         $okeys = array();
335         $ikeys = array();
336         for ($key = $db->firstkey(); $key; $key = $db->nextkey()) {
337             if ($key[0] == 'i')
338                 $ikeys[] = $key;
339             elseif ($key[0] == 'o')
340                 $okeys[] = $key;
341             else {
342                 trigger_error("Bad key in linktable: '$key'", E_USER_WARNING);
343                 $ikeys[] = $key;
344             }
345         }
346         foreach ($ikeys as $key) {
347             $db->delete($key);
348         }
349         foreach ($okeys as $key) {
350             $page = substr($key,1);
351             $links = $this->_get_links('o', $page);
352             $db->delete($key);
353             $this->set_links($page, $links);
354         }
355     }
356
357     function check() {
358         $db = &$this->_db;
359
360         // FIXME: check for sortedness and uniqueness in links lists.
361
362         for ($key = $db->firstkey(); $key; $key = $db->nextkey()) {
363             if (strlen($key) < 1 || ($key[0] != 'i' && $key[0] != 'o')) {
364                 $errs[] = "Bad key '$key' in table";
365                 continue;
366             }
367             $page = substr($key, 1);
368             if ($key[0] == 'o') {
369                 // Forward links.
370                 foreach($this->_get_links('o', $page) as $link) {
371                     if (!$this->_has_link('i', $link, $page))
372                         $errs[] = "backlink entry missing for link '$page'->'$link'";
373                 }
374             }
375             else {
376                 assert($key[0] == 'i');
377                 // Backlinks.
378                 foreach($this->_get_links('i', $page) as $link) {
379                     if (!$this->_has_link('o', $link, $page))
380                         $errs[] = "link entry missing for backlink '$page'<-'$link'";
381                 }
382             }
383         }
384
385         return isset($errs) ? $errs : false;
386     }
387     
388         
389     function _add_backlink($page, $linkedfrom) {
390         $backlinks = $this->_get_links('i', $page);
391         $backlinks[] = $linkedfrom;
392         sort($backlinks);
393         $this->_set_links('i', $page, $backlinks);
394     }
395     
396     function _delete_backlink($page, $linkedfrom) {
397         $backlinks = $this->_get_links('i', $page);
398         foreach ($backlinks as $key => $backlink) {
399             if ($backlink == $linkedfrom)
400                 unset($backlinks[$key]);
401         }
402         $this->_set_links('i', $page, $backlinks);
403     }
404     
405     function _has_link($which, $page, $link) {
406         $links = $this->_get_links($which, $page);
407         foreach($links as $l) {
408             if ($l == $link)
409                 return true;
410         }
411         return false;
412     }
413     
414     function _get_links($which, $page) {
415         $data = $this->_db->get($which . $page);
416         return $data ? unserialize($data) : array();
417     }
418
419     function _set_links($which, $page, &$links) {
420         $key = $which . $page;
421         if ($links)
422             $this->_db->set($key, serialize($links));
423         else
424             $this->_db->set($key, false);
425     }
426 }
427
428 // (c-file-style: "gnu")
429 // Local Variables:
430 // mode: php
431 // tab-width: 8
432 // c-basic-offset: 4
433 // c-hanging-comment-ender-p: nil
434 // indent-tabs-mode: nil
435 // End:   
436 ?>