2 rcs_id('$Id: file.php,v 1.2 2003-01-04 03:30:34 wainstead Exp $');
5 * Backend for handling file storage.
7 * Author: Jochen Kalmbach, Jochen@kalmbachnet.de
12 * - Implement "optimize" / "sync" / "check" / "rebuild"
13 * - Optimize "get_previous_version"
14 * - Optimize "get_links" (reversed = true)
15 * - Optimize "get_all_revisions"
16 * - Optimize "most_popular" (separate file for "hitcount",
17 * which contains all pages)
18 * - Optimize "most_recent"
19 * - What should be done in "lock"/"unlock"/"close" ?
20 * - "WikiDB_backend_file_iter": Do I need to return 'version' and 'versiondata' ?
24 require_once('lib/WikiDB/backend.php');
25 require_once('lib/ErrorManager.php');
29 class WikiDB_backend_file
30 extends WikiDB_backend
35 var $_page_data; // temorarily stores the pagedata (via _loadPageData)
36 var $_page_version_data; // temorarily stores the versiondata (via _loadVersionData)
37 var $_latest_versions; // temorarily stores the latest version-numbers (for every pagename) (via _loadLatestVersions)
40 function WikiDB_backend_file( $dbparam )
42 $this->data_dir = $dbparam['directory'];
43 if (is_dir($this->data_dir) == false) {
44 mkdir($this->data_dir, 0755);
48 = array('ver_data' => $this->data_dir.'/'.'ver_data',
49 'page_data' => $this->data_dir.'/'.'page_data',
50 'latest_ver' => $this->data_dir.'/'.'latest_ver',
51 'links' => $this->data_dir.'/'.'links' );
53 foreach ($this->_dir_names as $key => $val) {
54 if (is_dir($val) == false)
58 $this->_page_data = NULL;
59 $this->_page_version_data = NULL;
60 $this->_latest_versions = NULL;
65 // *********************************************************************
66 // common file load / save functions:
67 function _pagename2filename($type, $pagename, $version) {
69 return $this->_dir_names[$type].'/'.urlencode($pagename);
71 return $this->_dir_names[$type].'/'.urlencode($pagename).'--'.$version;
74 function _loadPage($type, $pagename, $version, $set_pagename = true) {
75 $filename = $this->_pagename2filename($type, $pagename, $version);
76 if ($fd = @fopen($filename, "rb")) {
77 $locked = flock($fd, 1); # Read lock
79 ExitWiki("Timeout while obtaining lock. Please try again");
81 if ($data = fread($fd, filesize($filename))) {
82 $pd = unserialize($data);
83 if ($set_pagename == true)
84 $pd['pagename'] = $pagename;
86 $pd['version'] = $version;
88 ExitWiki(sprintf(gettext("'%s': corrupt file"),
89 htmlspecialchars($filename)));
98 function _savePage($type, $pagename, $version, $data) {
99 $filename = $this->_pagename2filename($type, $pagename, $version);
100 if($fd = fopen($filename, 'a+b')) {
101 $locked = flock($fd,2); #Exclusive blocking lock
103 ExitWiki("Timeout while obtaining lock. Please try again");
109 $pagedata = serialize($data);
110 fwrite($fd, $pagedata);
113 ExitWiki("Error while writing page '$pagename'");
117 function _removePage($type, $pagename, $version) {
118 $filename = $this->_pagename2filename($type, $pagename, $version);
119 $f = @unlink($filename);
121 trigger_error("delete file failed: ".$filename." ver: ".$version, E_USER_WARNING);
124 // *********************************************************************
127 // *********************************************************************
128 // Load/Save Version-Data
129 function _loadVersionData($pagename, $version) {
130 if ($this->_page_version_data != NULL) {
131 if ( ($this->_page_version_data['pagename'] == $pagename) &&
132 ($this->_page_version_data['version'] == $version) ) {
133 return $this->_page_version_data;
136 $vd = $this->_loadPage('ver_data', $pagename, $version);
138 $this->_page_version_data = $vd;
139 if ( ($this->_page_version_data['pagename'] == $pagename) &&
140 ($this->_page_version_data['version'] == $version) ) {
141 return $this->_page_version_data;
147 function _saveVersionData($pagename, $version, $data) {
148 $this->_savePage('ver_data', $pagename, $version, $data);
150 // check if this is a newer version:
151 if ($this->_getLatestVersion($pagename) < $version) {
152 // write new latest-version-info
153 $this->_setLatestVersion($pagename, $version);
158 // *********************************************************************
159 // Load/Save Page-Data
160 function _loadPageData($pagename) {
161 if ($this->_page_data != NULL) {
162 if ($this->_page_data['pagename'] == $pagename) {
163 return $this->_page_data;
166 $pd = $this->_loadPage('page_data', $pagename, 0);
168 $this->_page_data = $pd;
169 if ($this->_page_data != NULL) {
170 if ($this->_page_data['pagename'] == $pagename) {
171 return $this->_page_data;
174 return array(); // no values found
177 function _savePageData($pagename, $data) {
178 $this->_savePage('page_data', $pagename, 0, $data);
181 // *********************************************************************
182 // Load/Save Latest-Version
183 function _saveLatestVersions() {
184 $data = $this->_latest_versions;
187 $this->_savePage('latest_ver', 'latest_versions', 0, $data);
190 function _setLatestVersion($pagename, $version) {
191 // make sure the page version list is loaded:
192 $this->_getLatestVersion($pagename);
194 $this->_getLatestVersion($pagename);
195 $this->_latest_versions[$pagename] = $version;
198 // Remove this page from the Latest-Version-List:
199 unset($this->_latest_versions[$pagename]);
201 $this->_saveLatestVersions();
204 function _loadLatestVersions() {
205 if ($this->_latest_versions != NULL)
208 $pd = $this->_loadPage('latest_ver', 'latest_versions', 0, false);
210 $this->_latest_versions = $pd;
212 $this->_latest_versions = array(); // empty array
215 function _getLatestVersion($pagename) {
216 $this->_loadLatestVersions();
217 if (array_key_exists($pagename, $this->_latest_versions) == false)
218 return 0; // do version exists
219 return $this->_latest_versions[$pagename];
223 // *********************************************************************
224 // Load/Save Page-Links
225 function _loadPageLinks($pagename) {
226 $pd = $this->_loadPage('links', $pagename, 0, false);
229 return array(); // no values found
232 function _savePageLinks($pagename, $links) {
233 $this->_savePage('links', $pagename, 0, $links);
239 * Get page meta-data from database.
241 * @param $pagename string Page name.
243 * Returns a hash containing the page meta-data.
244 * Returns an empty array if there is no meta-data for the requested page.
245 * Keys which might be present in the hash are:
247 * <dt> locked <dd> If the page is locked.
248 * <dt> hits <dd> The page hit count.
249 * <dt> created <dd> Unix time of page creation. (FIXME: Deprecated: I
250 * don't think we need this...)
253 function get_pagedata($pagename) {
254 return $this->_loadPageData($pagename);
258 * Update the page meta-data.
260 * Set page meta-data.
262 * Only meta-data whose keys are preset in $newdata is affected.
266 * $backend->update_pagedata($pagename, array('locked' => 1));
268 * will set the value of 'locked' to 1 for the specified page, but it
269 * will not affect the value of 'hits' (or whatever other meta-data
270 * may have been stored for the page.)
272 * To delete a particular piece of meta-data, set it's value to false.
274 * $backend->update_pagedata($pagename, array('locked' => false));
277 * @param $pagename string Page name.
278 * @param $newdata hash New meta-data.
281 * This will create a new page if page being requested does not
284 function update_pagedata($pagename, $newdata) {
285 $data = $this->get_pagedata($pagename);
286 if (count($data) == 0) {
287 $this->_savePageData($pagename, $newdata); // create a new pagedata-file
291 foreach ($newdata as $key => $val) {
297 $this->_savePageData($pagename, $data); // write new pagedata-file
302 * Get the current version number for a page.
304 * @param $pagename string Page name.
305 * @return int The latest version number for the page. Returns zero if
306 * no versions of a page exist.
308 function get_latest_version($pagename) {
309 return $this->_getLatestVersion($pagename);
313 * Get preceding version number.
315 * @param $pagename string Page name.
316 * @param $version int Find version before this one.
317 * @return int The version number of the version in the database which
318 * immediately preceeds $version.
320 function get_previous_version($pagename, $version) {
321 return ($version > 0 ? $version - 1 : 0);
325 * Get revision meta-data and content.
327 * @param $pagename string Page name.
328 * @param $version integer Which version to get.
329 * @param $want_content boolean
330 * Indicates the caller really wants the page content. If this
331 * flag is not set, the backend is free to skip fetching of the
332 * page content (as that may be expensive). If the backend omits
333 * the content, the backend might still want to set the value of
334 * '%content' to the empty string if it knows there's no content.
336 * @return hash The version data, or false if specified version does not
339 * Some keys which might be present in the $versiondata hash are:
342 * <dd> This is a pseudo-meta-data element (since it's actually
343 * the page data, get it?) containing the page content.
344 * If the content was not fetched, this key may not be present.
346 * For description of other version meta-data see WikiDB_PageRevision::get().
347 * @see WikiDB_PageRevision::get
349 function get_versiondata($pagename, $version, $want_content = false) {
350 $vd = $this->_loadVersionData($pagename, $version);
357 * Delete page from the database.
359 * Delete page (and all it's revisions) from the database.
361 * @param $pagename string Page name.
363 function delete_page($pagename) {
364 $ver = $this->get_latest_version($pagename);
366 $this->_removePage('ver_data', $pagename, $ver);
367 $ver = $this->get_previous_version($pagename, $ver);
369 $this->_removePage('page_data', $pagename, 0);
370 $this->_removePage('links', $pagename, 0);
371 // remove page from latest_version...
372 $this->_setLatestVersion($pagename, 0);
376 * Delete an old revision of a page.
378 * Note that one is never allowed to delete the most recent version,
379 * but that this requirement is enforced by WikiDB not by the backend.
381 * In fact, to be safe, backends should probably allow the deletion of
382 * the most recent version.
384 * @param $pagename string Page name.
385 * @param $version integer Version to delete.
387 function delete_versiondata($pagename, $version) {
388 if ($this->get_latest_version($pagename) == $version) {
389 // try to delete the latest version!
390 // so check if an older version exist:
391 if ($this->get_versiondata($pagename, $this->get_previous_version($pagename, $version), false) == false) {
392 // there is no older version....
393 // so the completely page will be removed:
394 $this->delete_page($pagename);
398 $this->_removePage('ver_data', $pagename, $version);
402 * Create a new page revision.
404 * If the given ($pagename,$version) is already in the database,
405 * this method completely overwrites any stored data for that version.
407 * @param $pagename string Page name.
408 * @param $version int New revisions content.
409 * @param $data hash New revision metadata.
411 * @see get_versiondata
413 function set_versiondata($pagename, $version, $data) {
414 $this->_saveVersionData($pagename, $version, $data);
418 * Update page version meta-data.
420 * If the given ($pagename,$version) is already in the database,
421 * this method only changes those meta-data values whose keys are
422 * explicity listed in $newdata.
424 * @param $pagename string Page name.
425 * @param $version int New revisions content.
426 * @param $newdata hash New revision metadata.
427 * @see set_versiondata, get_versiondata
429 function update_versiondata($pagename, $version, $newdata) {
430 $data = $this->get_versiondata($pagename, $version, true);
435 foreach ($newdata as $key => $val) {
441 $this->set_versiondata($pagename, $version, $data);
445 * Set links for page.
447 * @param $pagename string Page name.
449 * @param $links array List of page(names) which page links to.
451 function set_links($pagename, $links) {
452 $this->_savePageLinks($pagename, $links);
456 * Find pages which link to or are linked from a page.
458 * @param $pagename string Page name.
459 * @param $reversed boolean True to get backlinks.
461 * FIXME: array or iterator?
462 * @return object A WikiDB_backend_iterator.
464 function get_links($pagename, $reversed) {
465 if ($reversed == false)
466 return new WikiDB_backend_file_iter($this, $this->_loadPageLinks($pagename));
468 $this->_loadLatestVersions();
469 $pagenames = $this->_latest_versions; // now we have an array with the key is the pagename of all pages
471 $out = array(); // create empty out array
473 foreach ($pagenames as $key => $val) {
474 $links = $this->_loadPageLinks($key);
475 foreach ($links as $key2 => $val2) {
476 if ($val2 == $pagename)
477 array_push($out, $key);
480 return new WikiDB_backend_file_iter($this, $out);
484 * Get all revisions of a page.
486 * @param $pagename string The page name.
487 * @return object A WikiDB_backend_iterator.
489 function get_all_revisions($pagename) {
490 include_once('lib/WikiDB/backend/dumb/AllRevisionsIter.php');
491 return new WikiDB_backend_dumb_AllRevisionsIter($this, $pagename);
495 * Get all pages in the database.
497 * Pages should be returned in alphabetical order if that is
502 * @param $include_defaulted boolean
503 * If set, even pages with no content will be returned
504 * --- but still only if they have at least one revision (not
505 * counting the default revision 0) entered in the database.
507 * Normally pages whose current revision has empty content
508 * are not returned as these pages are considered to be
511 * @return object A WikiDB_backend_iterator.
513 function get_all_pages($include_deleted) {
514 $this->_loadLatestVersions();
515 $a = array_keys($this->_latest_versions);
517 return new WikiDB_backend_file_iter($this, $a);
521 * Title or full text search.
523 * Pages should be returned in alphabetical order if that is
528 * @param $search object A TextSearchQuery object describing what pages
529 * are to be searched for.
531 * @param $fullsearch boolean If true, a full text search is performed,
532 * otherwise a title search is performed.
534 * @return object A WikiDB_backend_iterator.
536 * @see WikiDB::titleSearch
538 function text_search($search = '', $fullsearch = false) {
539 // This is method implements a simple linear search
540 // through all the pages in the database.
542 // It is expected that most backends will overload
543 // method with something more efficient.
544 include_once('lib/WikiDB/backend/dumb/TextSearchIter.php');
545 $pages = $this->get_all_pages(false);
546 return new WikiDB_backend_dumb_TextSearchIter($this, $pages, $search, $fullsearch);
550 * Find pages with highest hit counts.
552 * Find the pages with the highest hit counts. The pages should
553 * be returned in reverse order by hit count.
556 * @param $limit integer No more than this many pages
557 * @return object A WikiDB_backend_iterator.
559 function most_popular($limit) {
560 // This is method fetches all pages, then
561 // sorts them by hit count.
562 // (Not very efficient.)
564 // It is expected that most backends will overload
565 // method with something more efficient.
566 include_once('lib/WikiDB/backend/dumb/MostPopularIter.php');
567 $pages = $this->get_all_pages(false);
569 return new WikiDB_backend_dumb_MostPopularIter($this, $pages, $limit);
573 * Find recent changes.
576 * @param $params hash See WikiDB::mostRecent for a description
577 * of parameters which can be included in this hash.
578 * @return object A WikiDB_backend_iterator.
579 * @see WikiDB::mostRecent
581 function most_recent($params) {
582 // This method is very inefficient and searches through
583 // all pages for the most recent changes.
585 // It is expected that most backends will overload
586 // method with something more efficient.
587 include_once('lib/WikiDB/backend/dumb/MostRecentIter.php');
588 $pages = $this->get_all_pages(true);
589 return new WikiDB_backend_dumb_MostRecentIter($this, $pages, $params);
593 * Lock backend database.
595 * Calls may be nested.
597 * @param $write_lock boolean Unless this is set to false, a write lock
598 * is acquired, otherwise a read lock. If the backend doesn't support
599 * read locking, then it should make a write lock no matter which type
600 * of lock was requested.
602 * All backends <em>should</em> support write locking.
604 function lock($write_lock = true) {
605 //trigger_error("lock: Not Implemented", E_USER_WARNING);
609 * Unlock backend database.
611 * @param $force boolean Normally, the database is not unlocked until
612 * unlock() is called as many times as lock() has been. If $force is
613 * set to true, the the database is unconditionally unlocked.
615 function unlock($force = false) {
616 //trigger_error("unlock: Not Implemented", E_USER_WARNING);
624 //trigger_error("close: Not Implemented", E_USER_WARNING);
628 * Synchronize with filesystem.
630 * This should flush all unwritten data to the filesystem.
633 //trigger_error("sync: Not Implemented", E_USER_WARNING);
637 * Optimize the database.
639 function optimize() {
640 //trigger_error("optimize: Not Implemented", E_USER_WARNING);
644 * Check database integrity.
646 * This should check the validity of the internal structure of the database.
647 * Errors should be reported via:
649 * trigger_error("Message goes here.", E_USER_WARNING);
652 * @return boolean True iff database is in a consistent state.
655 //trigger_error("check: Not Implemented", E_USER_WARNING);
659 * Put the database into a consistent state.
661 * This should put the database into a consistent state.
662 * (I.e. rebuild indexes, etc...)
664 * @return boolean True iff successful.
667 //trigger_error("rebuild: Not Implemented", E_USER_WARNING);
670 function _parse_searchwords($search) {
671 $search = strtolower(trim($search));
673 return array(array(),array());
675 $words = preg_split('/\s+/', $search);
677 foreach ($words as $key => $word) {
678 if ($word[0] == '-' && $word != '-') {
679 $word = substr($word, 1);
680 $exclude[] = preg_quote($word);
684 return array($words, $exclude);
689 class WikiDB_backend_file_iter extends WikiDB_backend_iterator
691 function WikiDB_backend_file_iter(&$backend, &$query_result) {
692 $this->_backend = &$backend;
693 $this->_result = $query_result;
695 if (count($this->_result) > 0)
696 reset($this->_result);
700 $backend = &$this->_backend;
705 if (count($this->_result) <= 0)
708 $e = each($this->_result);
712 $pn = urldecode($e[1]);
714 $pagedata = $backend->get_pagedata($pn);
715 $rec = array('pagename' => $pn,
716 'pagedata' => $pagedata);
718 //$rec['version'] = $backend->get_latest_version($pn);
719 //$rec['versiondata'] = $backend->get_versiondata($pn, $rec['version'], true);
728 // $Log: not supported by cvs2svn $
735 // c-hanging-comment-ender-p: nil
736 // indent-tabs-mode: nil