X-Git-Url: http://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2FBagOStuff.php;h=7f400013331c2f5bd532c224e5cbade38beafd8f;hb=44158e12e6981525c67c879875c78a7fcb471725;hp=7e48fb04a7615bbfbd1bbc0e9d8cbdfe446f0351;hpb=b817c0c15fc8e7cf483c21089834f65d716d9786;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/BagOStuff.php b/includes/BagOStuff.php index 7e48fb04a7..7f40001333 100644 --- a/includes/BagOStuff.php +++ b/includes/BagOStuff.php @@ -1,347 +1,450 @@ -# http://www.mediawiki.org/ -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program; if not, write to the Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -# http://www.gnu.org/copyleft/gpl.html /** + * Classes to cache objects in PHP accelerators, SQL database or DBA files * - * @package MediaWiki + * Copyright © 2003-2004 Brion Vibber + * http://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Cache + */ + +/** + * @defgroup Cache Cache */ /** - * Simple generic object store - * * interface is intended to be more or less compatible with * the PHP memcached client. * * backends for local hash array and SQL table included: - * $bag = new HashBagOStuff(); - * $bag = new MysqlBagOStuff($tablename); # connect to db first + * + * $bag = new HashBagOStuff(); + * $bag = new SqlBagOStuff(); # connect to db first + * * - * @package MediaWiki - * @abstract + * @ingroup Cache */ -class BagOStuff { - var $debugmode; - - function BagOStuff() { - $this->set_debug( false ); - } +abstract class BagOStuff { + var $debugMode = false; - function set_debug($bool) { - $this->debugmode = $bool; + public function set_debug( $bool ) { + $this->debugMode = $bool; } /* *** THE GUTS OF THE OPERATION *** */ /* Override these with functional things in subclasses */ - function get($key) { - /* stub */ - return false; - } + /** + * Get an item with the given key. Returns false if it does not exist. + * @param $key string + */ + abstract public function get( $key ); - function set($key, $value, $exptime=0) { - /* stub */ - return false; - } + /** + * Set an item. + * @param $key string + * @param $value mixed + * @param $exptime int Either an interval in seconds or a unix timestamp for expiry + */ + abstract public function set( $key, $value, $exptime = 0 ); + + /* + * Delete an item. + * @param $key string + * @param $time int Amount of time to delay the operation (mostly memcached-specific) + */ + abstract public function delete( $key, $time = 0 ); - function delete($key, $time=0) { + public function lock( $key, $timeout = 0 ) { /* stub */ - return false; + return true; } - function lock($key, $timeout = 0) { + public function unlock( $key ) { /* stub */ return true; } - function unlock($key) { + public function keys() { /* stub */ - return true; + return array(); } /* *** Emulated functions *** */ /* Better performance can likely be got with custom written versions */ - function get_multi($keys) { + public function get_multi( $keys ) { $out = array(); - foreach($keys as $key) - $out[$key] = $this->get($key); + + foreach ( $keys as $key ) { + $out[$key] = $this->get( $key ); + } + return $out; } - function set_multi($hash, $exptime=0) { - foreach($hash as $key => $value) - $this->set($key, $value, $exptime); + public function set_multi( $hash, $exptime = 0 ) { + foreach ( $hash as $key => $value ) { + $this->set( $key, $value, $exptime ); + } } - function add($key, $value, $exptime=0) { - if( $this->get($key) == false ) { - $this->set($key, $value, $exptime); + public function add( $key, $value, $exptime = 0 ) { + if ( !$this->get( $key ) ) { + $this->set( $key, $value, $exptime ); + return true; } } - function add_multi($hash, $exptime=0) { - foreach($hash as $key => $value) - $this->add($key, $value, $exptime); + public function add_multi( $hash, $exptime = 0 ) { + foreach ( $hash as $key => $value ) { + $this->add( $key, $value, $exptime ); + } } - function delete_multi($keys, $time=0) { - foreach($keys as $key) - $this->delete($key, $time); + public function delete_multi( $keys, $time = 0 ) { + foreach ( $keys as $key ) { + $this->delete( $key, $time ); + } } - function replace($key, $value, $exptime=0) { - if( $this->get($key) !== false ) - $this->set($key, $value, $exptime); + public function replace( $key, $value, $exptime = 0 ) { + if ( $this->get( $key ) !== false ) { + $this->set( $key, $value, $exptime ); + } } - function incr($key, $value=1) { - if ( !$this->lock($key) ) { - return false; + /** + * @param $key String: Key to increase + * @param $value Integer: Value to add to $key (Default 1) + * @return null if lock is not possible else $key value increased by $value + */ + public function incr( $key, $value = 1 ) { + if ( !$this->lock( $key ) ) { + return null; } - $value = intval($value); - if($value < 0) $value = 0; - $n = false; - if( ($n = $this->get($key)) !== false ) { + $value = intval( $value ); + + if ( ( $n = $this->get( $key ) ) !== false ) { $n += $value; - $this->set($key, $n); // exptime? + $this->set( $key, $n ); // exptime? } - $this->unlock($key); + $this->unlock( $key ); + return $n; } - function decr($key, $value=1) { - if ( !$this->lock($key) ) { - return false; - } - $value = intval($value); - if($value < 0) $value = 0; + public function decr( $key, $value = 1 ) { + return $this->incr( $key, - $value ); + } - $m = false; - if( ($n = $this->get($key)) !== false ) { - $m = $n - $value; - if($m < 0) $m = 0; - $this->set($key, $m); // exptime? + public function debug( $text ) { + if ( $this->debugMode ) { + wfDebug( "BagOStuff debug: $text\n" ); } - $this->unlock($key); - return $m; } - function _debug($text) { - if($this->debugmode) - wfDebug("BagOStuff debug: $text\n"); + /** + * Convert an optionally relative time to an absolute time + */ + protected function convertExpiry( $exptime ) { + if ( ( $exptime != 0 ) && ( $exptime < 86400 * 3650 /* 10 years */ ) ) { + return time() + $exptime; + } else { + return $exptime; + } } } - /** * Functional versions! - * @todo document - * @package MediaWiki + * This is a test of the interface, mainly. It stores things in an associative + * array, which is not going to persist between program runs. + * + * @ingroup Cache */ class HashBagOStuff extends BagOStuff { - /* - This is a test of the interface, mainly. It stores - things in an associative array, which is not going to - persist between program runs. - */ var $bag; - function HashBagOStuff() { + function __construct() { $this->bag = array(); } - function _expire($key) { + protected function expire( $key ) { $et = $this->bag[$key][1]; - if(($et == 0) || ($et > time())) + + if ( ( $et == 0 ) || ( $et > time() ) ) { return false; - $this->delete($key); + } + + $this->delete( $key ); + return true; } - function get($key) { - if(!$this->bag[$key]) + function get( $key ) { + if ( !isset( $this->bag[$key] ) ) { return false; - if($this->_expire($key)) + } + + if ( $this->expire( $key ) ) { return false; + } + return $this->bag[$key][0]; } - function set($key,$value,$exptime=0) { - if(($exptime != 0) && ($exptime < 3600*24*30)) - $exptime = time() + $exptime; - $this->bag[$key] = array( $value, $exptime ); + function set( $key, $value, $exptime = 0 ) { + $this->bag[$key] = array( $value, $this->convertExpiry( $exptime ) ); } - function delete($key,$time=0) { - if(!$this->bag[$key]) + function delete( $key, $time = 0 ) { + if ( !isset( $this->bag[$key] ) ) { return false; - unset($this->bag[$key]); + } + + unset( $this->bag[$key] ); + return true; } -} -/* -CREATE TABLE objectcache ( - keyname char(255) binary not null default '', - value mediumblob, - exptime datetime, - unique key (keyname), - key (exptime) -); -*/ + function keys() { + return array_keys( $this->bag ); + } +} /** - * @todo document - * @abstract - * @package MediaWiki + * Class to store objects in the database + * + * @ingroup Cache */ class SqlBagOStuff extends BagOStuff { - var $table; - var $lastexpireall = 0; + var $lb, $db; + var $lastExpireAll = 0; + + protected function getDB() { + if ( !isset( $this->db ) ) { + /* We must keep a separate connection to MySQL in order to avoid deadlocks + * However, SQLite has an opposite behaviour. + * @todo Investigate behaviour for other databases + */ + if ( wfGetDB( DB_MASTER )->getType() == 'sqlite' ) { + $this->db = wfGetDB( DB_MASTER ); + } else { + $this->lb = wfGetLBFactory()->newMainLB(); + $this->db = $this->lb->getConnection( DB_MASTER ); + $this->db->clearFlag( DBO_TRX ); + } + } - function SqlBagOStuff($tablename = 'objectcache') { - $this->table = $tablename; + return $this->db; } - function get($key) { - /* expire old entries if any */ + public function get( $key ) { + # expire old entries if any $this->garbageCollect(); + $db = $this->getDB(); + $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ), + array( 'keyname' => $key ), __METHOD__ ); - $res = $this->_query( - "SELECT value,exptime FROM $0 WHERE keyname='$1'", $key); - if(!$res) { - $this->_debug("get: ** error: " . $this->_dberror($res) . " **"); + if ( !$row ) { + $this->debug( 'get: no matching rows' ); return false; } - if($row=$this->_fetchobject($res)) { - $this->_debug("get: retrieved data; exp time is " . $row->exptime); - return $this->_unserialize($row->value); - } else { - $this->_debug('get: no matching rows'); + + $this->debug( "get: retrieved data; expiry time is " . $row->exptime ); + + if ( $this->isExpired( $row->exptime ) ) { + $this->debug( "get: key has expired, deleting" ); + try { + $db->begin(); + # Put the expiry time in the WHERE condition to avoid deleting a + # newly-inserted value + $db->delete( 'objectcache', + array( + 'keyname' => $key, + 'exptime' => $row->exptime + ), __METHOD__ ); + $db->commit(); + } catch ( DBQueryError $e ) { + $this->handleWriteError( $e ); + } + + return false; } - return false; + + return $this->unserialize( $db->decodeBlob( $row->value ) ); } - function set($key,$value,$exptime=0) { - $exptime = intval($exptime); - if($exptime < 0) $exptime = 0; - if($exptime == 0) { - $exp = $this->_maxdatetime(); + public function set( $key, $value, $exptime = 0 ) { + $db = $this->getDB(); + $exptime = intval( $exptime ); + + if ( $exptime < 0 ) { + $exptime = 0; + } + + if ( $exptime == 0 ) { + $encExpiry = $this->getMaxDateTime(); } else { - if($exptime < 3600*24*30) + if ( $exptime < 3.16e8 ) { # ~10 years $exptime += time(); - $exp = $this->_fromunixtime($exptime); + } + + $encExpiry = $db->timestamp( $exptime ); } - $this->delete( $key ); - $this->_doinsert($this->getTableName(), array( + try { + $db->begin(); + // (bug 24425) use a replace if the db supports it instead of + // delete/insert to avoid clashes with conflicting keynames + $db->replace( 'objectcache', array( 'keyname' ), + array( 'keyname' => $key, - 'value' => $this->_blobencode($this->_serialize($value)), - 'exptime' => $exp - )); - return true; /* ? */ - } + 'value' => $db->encodeBlob( $this->serialize( $value ) ), + 'exptime' => $encExpiry + ), __METHOD__ ); + $db->commit(); + } catch ( DBQueryError $e ) { + $this->handleWriteError( $e ); - function delete($key,$time=0) { - $this->_query( - "DELETE FROM $0 WHERE keyname='$1'", $key ); - return true; /* ? */ - } + return false; + } - function getTableName() { - return $this->table; + return true; } - function _query($sql) { - $reps = func_get_args(); - $reps[0] = $this->getTableName(); - // ewwww - for($i=0;$i 0 ? $this->_strencode($reps[$i]) : $reps[$i], - $sql); - } - $res = $this->_doquery($sql); - if($res == false) { - $this->_debug('query failed: ' . $this->_dberror($res)); + public function delete( $key, $time = 0 ) { + $db = $this->getDB(); + + try { + $db->begin(); + $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ ); + $db->commit(); + } catch ( DBQueryError $e ) { + $this->handleWriteError( $e ); + + return false; } - return $res; - } - function _strencode($str) { - /* Protect strings in SQL */ - return str_replace( "'", "''", $str ); - } - function _blobencode($str) { - return $str; - } - function _doinsert($table, $vals) { - die( 'abstract function SqlBagOStuff::_doinsert() must be defined' ); - } - function _doquery($sql) { - die( 'abstract function SqlBagOStuff::_doquery() must be defined' ); + return true; } - function _fetchrow($res) { - die( 'abstract function SqlBagOStuff::_fetchrow() must be defined' ); - } + public function incr( $key, $step = 1 ) { + $db = $this->getDB(); + $step = intval( $step ); - function _freeresult($result) { - /* stub */ - return false; + try { + $db->begin(); + $row = $db->selectRow( 'objectcache', array( 'value', 'exptime' ), + array( 'keyname' => $key ), __METHOD__, array( 'FOR UPDATE' ) ); + if ( $row === false ) { + // Missing + $db->commit(); + + return null; + } + $db->delete( 'objectcache', array( 'keyname' => $key ), __METHOD__ ); + if ( $this->isExpired( $row->exptime ) ) { + // Expired, do not reinsert + $db->commit(); + + return null; + } + + $oldValue = intval( $this->unserialize( $db->decodeBlob( $row->value ) ) ); + $newValue = $oldValue + $step; + $db->insert( 'objectcache', + array( + 'keyname' => $key, + 'value' => $db->encodeBlob( $this->serialize( $newValue ) ), + 'exptime' => $row->exptime + ), __METHOD__ ); + $db->commit(); + } catch ( DBQueryError $e ) { + $this->handleWriteError( $e ); + + return null; + } + + return $newValue; } - function _dberror($result) { - /* stub */ - return 'unknown error'; + public function keys() { + $db = $this->getDB(); + $res = $db->select( 'objectcache', array( 'keyname' ), false, __METHOD__ ); + $result = array(); + + foreach ( $res as $row ) { + $result[] = $row->keyname; + } + + return $result; } - function _maxdatetime() { - die( 'abstract function SqlBagOStuff::_maxdatetime() must be defined' ); + protected function isExpired( $exptime ) { + return $exptime != $this->getMaxDateTime() && wfTimestamp( TS_UNIX, $exptime ) < time(); } - function _fromunixtime() { - die( 'abstract function SqlBagOStuff::_fromunixtime() must be defined' ); + protected function getMaxDateTime() { + if ( time() > 0x7fffffff ) { + return $this->getDB()->timestamp( 1 << 62 ); + } else { + return $this->getDB()->timestamp( 0x7fffffff ); + } } - function garbageCollect() { + protected function garbageCollect() { /* Ignore 99% of requests */ if ( !mt_rand( 0, 100 ) ) { - $nowtime = time(); + $now = time(); /* Avoid repeating the delete within a few seconds */ - if ( $nowtime > ($this->lastexpireall + 1) ) { - $this->lastexpireall = $nowtime; - $this->expireall(); + if ( $now > ( $this->lastExpireAll + 1 ) ) { + $this->lastExpireAll = $now; + $this->expireAll(); } } } - function expireall() { - /* Remove any items that have expired */ - $now = $this->_fromunixtime( time() ); - $this->_query( "DELETE FROM $0 WHERE exptime < '$now'" ); + public function expireAll() { + $db = $this->getDB(); + $now = $db->timestamp(); + + try { + $db->begin(); + $db->delete( 'objectcache', array( 'exptime < ' . $db->addQuotes( $now ) ), __METHOD__ ); + $db->commit(); + } catch ( DBQueryError $e ) { + $this->handleWriteError( $e ); + } } - function deleteall() { - /* Clear *all* items from cache table */ - $this->_query( "DELETE FROM $0" ); + public function deleteAll() { + $db = $this->getDB(); + + try { + $db->begin(); + $db->delete( 'objectcache', '*', __METHOD__ ); + $db->commit(); + } catch ( DBQueryError $e ) { + $this->handleWriteError( $e ); + } } /** @@ -349,12 +452,13 @@ class SqlBagOStuff extends BagOStuff { * On typical message and page data, this can provide a 3X decrease * in storage requirements. * - * @param mixed $data + * @param $data mixed * @return string */ - function _serialize( &$data ) { + protected function serialize( &$data ) { $serial = serialize( $data ); - if( function_exists( 'gzdeflate' ) ) { + + if ( function_exists( 'gzdeflate' ) ) { return gzdeflate( $serial ); } else { return $serial; @@ -363,159 +467,435 @@ class SqlBagOStuff extends BagOStuff { /** * Unserialize and, if necessary, decompress an object. - * @param string $serial + * @param $serial string * @return mixed */ - function _unserialize( $serial ) { - if( function_exists( 'gzinflate' ) ) { + protected function unserialize( $serial ) { + if ( function_exists( 'gzinflate' ) ) { $decomp = @gzinflate( $serial ); - if( false !== $decomp ) { + + if ( false !== $decomp ) { $serial = $decomp; } } - wfdebug("serial: [$serial]\n"); + $ret = unserialize( $serial ); + return $ret; } -} -/** - * @todo document - * @package MediaWiki - */ -class MediaWikiBagOStuff extends SqlBagOStuff { - var $tableInitialised = false; + /** + * Handle a DBQueryError which occurred during a write operation. + * Ignore errors which are due to a read-only database, rethrow others. + */ + protected function handleWriteError( $exception ) { + $db = $this->getDB(); - function _doquery($sql) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->query($sql, 'MediaWikiBagOStuff::_doquery'); - } - function _doinsert($t, $v) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->insert($t, $v, 'MediaWikiBagOStuff::_doinsert'); - } - function _fetchobject($result) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->fetchObject($result); - } - function _freeresult($result) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->freeResult($result); - } - function _dberror($result) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->lastError(); - } - function _maxdatetime() { - $dbw =& wfGetDB(DB_MASTER); - return $dbw->timestamp('9999-12-31 12:59:59'); - } - function _fromunixtime($ts) { - $dbw =& wfGetDB(DB_MASTER); - return $dbw->timestamp($ts); - } - function _strencode($s) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->strencode($s); - } - function _blobencode($s) { - $dbw =& wfGetDB( DB_MASTER ); - return $dbw->encodeBlob($s); - } - function getTableName() { - if ( !$this->tableInitialised ) { - $dbw =& wfGetDB( DB_MASTER ); - /* This is actually a hack, we should be able - to use Language classes here... or not */ - if (!$dbw) - die("Could not connect to database"); - $this->table = $dbw->tableName( $this->table ); - $this->tableInitialised = true; + if ( !$db->wasReadOnlyError() ) { + throw $exception; } - return $this->table; + + try { + $db->rollback(); + } catch ( DBQueryError $e ) { + } + + wfDebug( __METHOD__ . ": ignoring query error\n" ); + $db->ignoreErrors( false ); } } /** - * This is a wrapper for Turck MMCache's shared memory functions. - * - * You can store objects with mmcache_put() and mmcache_get(), but Turck seems - * to use a weird custom serializer that randomly segfaults. So we wrap calls - * with serialize()/unserialize(). - * - * The thing I noticed about the Turck serialized data was that unlike ordinary - * serialize(), it contained the names of methods, and judging by the amount of - * binary data, perhaps even the bytecode of the methods themselves. It may be - * that Turck's serializer is faster, so a possible future extension would be - * to use it for arrays but not for objects. + * Backwards compatibility alias + */ +class MediaWikiBagOStuff extends SqlBagOStuff { } + +/** + * This is a wrapper for APC's shared memory functions * - * @package MediaWiki + * @ingroup Cache */ -class TurckBagOStuff extends BagOStuff { - function get($key) { - $val = mmcache_get( $key ); +class APCBagOStuff extends BagOStuff { + public function get( $key ) { + $val = apc_fetch( $key ); + if ( is_string( $val ) ) { $val = unserialize( $val ); } + return $val; } - function set($key, $value, $exptime=0) { - mmcache_put( $key, serialize( $value ), $exptime ); - return true; - } + public function set( $key, $value, $exptime = 0 ) { + apc_store( $key, serialize( $value ), $exptime ); - function delete($key, $time=0) { - mmcache_rm( $key ); return true; } - function lock($key, $waitTimeout = 0 ) { - mmcache_lock( $key ); + public function delete( $key, $time = 0 ) { + apc_delete( $key ); + return true; } - function unlock($key) { - mmcache_unlock( $key ); - return true; + public function keys() { + $info = apc_cache_info( 'user' ); + $list = $info['cache_list']; + $keys = array(); + + foreach ( $list as $entry ) { + $keys[] = $entry['info']; + } + + return $keys; } } /** * This is a wrapper for eAccelerator's shared memory functions. * - * This is basically identical to the Turck MMCache version, + * This is basically identical to the deceased Turck MMCache version, * mostly because eAccelerator is based on Turck MMCache. * - * @package MediaWiki + * @ingroup Cache */ class eAccelBagOStuff extends BagOStuff { - function get($key) { + public function get( $key ) { $val = eaccelerator_get( $key ); + if ( is_string( $val ) ) { $val = unserialize( $val ); } + return $val; } - function set($key, $value, $exptime=0) { + public function set( $key, $value, $exptime = 0 ) { eaccelerator_put( $key, serialize( $value ), $exptime ); + return true; } - function delete($key, $time=0) { + public function delete( $key, $time = 0 ) { eaccelerator_rm( $key ); + return true; } - function lock($key, $waitTimeout = 0 ) { + public function lock( $key, $waitTimeout = 0 ) { eaccelerator_lock( $key ); + return true; } - function unlock($key) { + public function unlock( $key ) { eaccelerator_unlock( $key ); + return true; } } -?> + +/** + * Wrapper for XCache object caching functions; identical interface + * to the APC wrapper + * + * @ingroup Cache + */ +class XCacheBagOStuff extends BagOStuff { + /** + * Get a value from the XCache object cache + * + * @param $key String: cache key + * @return mixed + */ + public function get( $key ) { + $val = xcache_get( $key ); + + if ( is_string( $val ) ) { + $val = unserialize( $val ); + } + + return $val; + } + + /** + * Store a value in the XCache object cache + * + * @param $key String: cache key + * @param $value Mixed: object to store + * @param $expire Int: expiration time + * @return bool + */ + public function set( $key, $value, $expire = 0 ) { + xcache_set( $key, serialize( $value ), $expire ); + + return true; + } + + /** + * Remove a value from the XCache object cache + * + * @param $key String: cache key + * @param $time Int: not used in this implementation + * @return bool + */ + public function delete( $key, $time = 0 ) { + xcache_unset( $key ); + + return true; + } +} + +/** + * Cache that uses DBA as a backend. + * Slow due to the need to constantly open and close the file to avoid holding + * writer locks. Intended for development use only, as a memcached workalike + * for systems that don't have it. + * + * @ingroup Cache + */ +class DBABagOStuff extends BagOStuff { + var $mHandler, $mFile, $mReader, $mWriter, $mDisabled; + + public function __construct( $dir = false ) { + global $wgDBAhandler; + + if ( $dir === false ) { + global $wgTmpDirectory; + $dir = $wgTmpDirectory; + } + + $this->mFile = "$dir/mw-cache-" . wfWikiID(); + $this->mFile .= '.db'; + wfDebug( __CLASS__ . ": using cache file {$this->mFile}\n" ); + $this->mHandler = $wgDBAhandler; + } + + /** + * Encode value and expiry for storage + */ + function encode( $value, $expiry ) { + # Convert to absolute time + $expiry = $this->convertExpiry( $expiry ); + + return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value ); + } + + /** + * @return list containing value first and expiry second + */ + function decode( $blob ) { + if ( !is_string( $blob ) ) { + return array( null, 0 ); + } else { + return array( + unserialize( substr( $blob, 11 ) ), + intval( substr( $blob, 0, 10 ) ) + ); + } + } + + function getReader() { + if ( file_exists( $this->mFile ) ) { + $handle = dba_open( $this->mFile, 'rl', $this->mHandler ); + } else { + $handle = $this->getWriter(); + } + + if ( !$handle ) { + wfDebug( "Unable to open DBA cache file {$this->mFile}\n" ); + } + + return $handle; + } + + function getWriter() { + $handle = dba_open( $this->mFile, 'cl', $this->mHandler ); + + if ( !$handle ) { + wfDebug( "Unable to open DBA cache file {$this->mFile}\n" ); + } + + return $handle; + } + + function get( $key ) { + wfProfileIn( __METHOD__ ); + wfDebug( __METHOD__ . "($key)\n" ); + + $handle = $this->getReader(); + if ( !$handle ) { + return null; + } + + $val = dba_fetch( $key, $handle ); + list( $val, $expiry ) = $this->decode( $val ); + + # Must close ASAP because locks are held + dba_close( $handle ); + + if ( !is_null( $val ) && $expiry && $expiry < time() ) { + # Key is expired, delete it + $handle = $this->getWriter(); + dba_delete( $key, $handle ); + dba_close( $handle ); + wfDebug( __METHOD__ . ": $key expired\n" ); + $val = null; + } + + wfProfileOut( __METHOD__ ); + return $val; + } + + function set( $key, $value, $exptime = 0 ) { + wfProfileIn( __METHOD__ ); + wfDebug( __METHOD__ . "($key)\n" ); + + $blob = $this->encode( $value, $exptime ); + + $handle = $this->getWriter(); + if ( !$handle ) { + return false; + } + + $ret = dba_replace( $key, $blob, $handle ); + dba_close( $handle ); + + wfProfileOut( __METHOD__ ); + return $ret; + } + + function delete( $key, $time = 0 ) { + wfProfileIn( __METHOD__ ); + wfDebug( __METHOD__ . "($key)\n" ); + + $handle = $this->getWriter(); + if ( !$handle ) { + return false; + } + + $ret = dba_delete( $key, $handle ); + dba_close( $handle ); + + wfProfileOut( __METHOD__ ); + return $ret; + } + + function add( $key, $value, $exptime = 0 ) { + wfProfileIn( __METHOD__ ); + + $blob = $this->encode( $value, $exptime ); + + $handle = $this->getWriter(); + + if ( !$handle ) { + return false; + } + + $ret = dba_insert( $key, $blob, $handle ); + + # Insert failed, check to see if it failed due to an expired key + if ( !$ret ) { + list( $value, $expiry ) = $this->decode( dba_fetch( $key, $handle ) ); + + if ( $expiry < time() ) { + # Yes expired, delete and try again + dba_delete( $key, $handle ); + $ret = dba_insert( $key, $blob, $handle ); + # This time if it failed then it will be handled by the caller like any other race + } + } + + dba_close( $handle ); + + wfProfileOut( __METHOD__ ); + return $ret; + } + + function keys() { + $reader = $this->getReader(); + $k1 = dba_firstkey( $reader ); + + if ( !$k1 ) { + return array(); + } + + $result[] = $k1; + + while ( $key = dba_nextkey( $reader ) ) { + $result[] = $key; + } + + return $result; + } +} + +/** + * Wrapper for WinCache object caching functions; identical interface + * to the APC wrapper + * + * @ingroup Cache + */ +class WinCacheBagOStuff extends BagOStuff { + + /** + * Get a value from the WinCache object cache + * + * @param $key String: cache key + * @return mixed + */ + public function get( $key ) { + $val = wincache_ucache_get( $key ); + + if ( is_string( $val ) ) { + $val = unserialize( $val ); + } + + return $val; + } + + /** + * Store a value in the WinCache object cache + * + * @param $key String: cache key + * @param $value Mixed: object to store + * @param $expire Int: expiration time + * @return bool + */ + public function set( $key, $value, $expire = 0 ) { + $result = wincache_ucache_set( $key, serialize( $value ), $expire ); + + /* wincache_ucache_set returns an empty array on success if $value + was an array, bool otherwise */ + return ( is_array( $result ) && $result === array() ) || $result; + } + + /** + * Remove a value from the WinCache object cache + * + * @param $key String: cache key + * @param $time Int: not used in this implementation + * @return bool + */ + public function delete( $key, $time = 0 ) { + wincache_ucache_delete( $key ); + + return true; + } + + public function keys() { + $info = wincache_ucache_info(); + $list = $info['ucache_entries']; + $keys = array(); + + if ( is_null( $list ) ) { + return array(); + } + + foreach ( $list as $entry ) { + $keys[] = $entry['key_name']; + } + + return $keys; + } +}