00001 <?php
00002 #
00003 # Copyright (C) 2003-2004 Brion Vibber <brion@pobox.com>
00004 # http://www.mediawiki.org/
00005 #
00006 # This program is free software; you can redistribute it and/or modify
00007 # it under the terms of the GNU General Public License as published by
00008 # the Free Software Foundation; either version 2 of the License, or
00009 # (at your option) any later version.
00010 #
00011 # This program is distributed in the hope that it will be useful,
00012 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00013 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014 # GNU General Public License for more details.
00015 #
00016 # You should have received a copy of the GNU General Public License along
00017 # with this program; if not, write to the Free Software Foundation, Inc.,
00018 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019 # http://www.gnu.org/copyleft/gpl.html
00020
00040 class BagOStuff {
00041 var $debugmode;
00042
00043 function __construct() {
00044 $this->set_debug( false );
00045 }
00046
00047 function set_debug($bool) {
00048 $this->debugmode = $bool;
00049 }
00050
00051
00052
00053
00054 function get($key) {
00055
00056 return false;
00057 }
00058
00059 function set($key, $value, $exptime=0) {
00060
00061 return false;
00062 }
00063
00064 function delete($key, $time=0) {
00065
00066 return false;
00067 }
00068
00069 function lock($key, $timeout = 0) {
00070
00071 return true;
00072 }
00073
00074 function unlock($key) {
00075
00076 return true;
00077 }
00078
00079 function keys() {
00080
00081 return array();
00082 }
00083
00084
00085
00086 function get_multi($keys) {
00087 $out = array();
00088 foreach($keys as $key)
00089 $out[$key] = $this->get($key);
00090 return $out;
00091 }
00092
00093 function set_multi($hash, $exptime=0) {
00094 foreach($hash as $key => $value)
00095 $this->set($key, $value, $exptime);
00096 }
00097
00098 function add($key, $value, $exptime=0) {
00099 if( $this->get($key) == false ) {
00100 $this->set($key, $value, $exptime);
00101 return true;
00102 }
00103 }
00104
00105 function add_multi($hash, $exptime=0) {
00106 foreach($hash as $key => $value)
00107 $this->add($key, $value, $exptime);
00108 }
00109
00110 function delete_multi($keys, $time=0) {
00111 foreach($keys as $key)
00112 $this->delete($key, $time);
00113 }
00114
00115 function replace($key, $value, $exptime=0) {
00116 if( $this->get($key) !== false )
00117 $this->set($key, $value, $exptime);
00118 }
00119
00120 function incr($key, $value=1) {
00121 if ( !$this->lock($key) ) {
00122 return false;
00123 }
00124 $value = intval($value);
00125 if($value < 0) $value = 0;
00126
00127 $n = false;
00128 if( ($n = $this->get($key)) !== false ) {
00129 $n += $value;
00130 $this->set($key, $n);
00131 }
00132 $this->unlock($key);
00133 return $n;
00134 }
00135
00136 function decr($key, $value=1) {
00137 if ( !$this->lock($key) ) {
00138 return false;
00139 }
00140 $value = intval($value);
00141 if($value < 0) $value = 0;
00142
00143 $m = false;
00144 if( ($n = $this->get($key)) !== false ) {
00145 $m = $n - $value;
00146 if($m < 0) $m = 0;
00147 $this->set($key, $m);
00148 }
00149 $this->unlock($key);
00150 return $m;
00151 }
00152
00153 function _debug($text) {
00154 if($this->debugmode)
00155 wfDebug("BagOStuff debug: $text\n");
00156 }
00157
00161 static function convertExpiry( $exptime ) {
00162 if(($exptime != 0) && ($exptime < 3600*24*30)) {
00163 return time() + $exptime;
00164 } else {
00165 return $exptime;
00166 }
00167 }
00168 }
00169
00170
00178 class HashBagOStuff extends BagOStuff {
00179 var $bag;
00180
00181 function __construct() {
00182 $this->bag = array();
00183 }
00184
00185 function _expire($key) {
00186 $et = $this->bag[$key][1];
00187 if(($et == 0) || ($et > time()))
00188 return false;
00189 $this->delete($key);
00190 return true;
00191 }
00192
00193 function get($key) {
00194 if(!$this->bag[$key])
00195 return false;
00196 if($this->_expire($key))
00197 return false;
00198 return $this->bag[$key][0];
00199 }
00200
00201 function set($key,$value,$exptime=0) {
00202 $this->bag[$key] = array( $value, BagOStuff::convertExpiry( $exptime ) );
00203 }
00204
00205 function delete($key,$time=0) {
00206 if(!$this->bag[$key])
00207 return false;
00208 unset($this->bag[$key]);
00209 return true;
00210 }
00211
00212 function keys() {
00213 return array_keys( $this->bag );
00214 }
00215 }
00216
00222 abstract class SqlBagOStuff extends BagOStuff {
00223 var $table;
00224 var $lastexpireall = 0;
00225
00231 function __construct($tablename = 'objectcache') {
00232 $this->table = $tablename;
00233 }
00234
00235 function get($key) {
00236
00237 $this->garbageCollect();
00238
00239 $res = $this->_query(
00240 "SELECT value,exptime FROM $0 WHERE keyname='$1'", $key);
00241 if(!$res) {
00242 $this->_debug("get: ** error: " . $this->_dberror($res) . " **");
00243 return false;
00244 }
00245 if($row=$this->_fetchobject($res)) {
00246 $this->_debug("get: retrieved data; exp time is " . $row->exptime);
00247 if ( $row->exptime != $this->_maxdatetime() &&
00248 wfTimestamp( TS_UNIX, $row->exptime ) < time() )
00249 {
00250 $this->_debug("get: key has expired, deleting");
00251 $this->delete($key);
00252 return false;
00253 }
00254 return $this->_unserialize($this->_blobdecode($row->value));
00255 } else {
00256 $this->_debug('get: no matching rows');
00257 }
00258 return false;
00259 }
00260
00261 function set($key,$value,$exptime=0) {
00262 if ( $this->_readonly() ) {
00263 return false;
00264 }
00265 $exptime = intval($exptime);
00266 if($exptime < 0) $exptime = 0;
00267 if($exptime == 0) {
00268 $exp = $this->_maxdatetime();
00269 } else {
00270 if($exptime < 3.16e8) # ~10 years
00271 $exptime += time();
00272 $exp = $this->_fromunixtime($exptime);
00273 }
00274 $this->_begin();
00275 $this->_query(
00276 "DELETE FROM $0 WHERE keyname='$1'", $key );
00277 $this->_doinsert($this->getTableName(), array(
00278 'keyname' => $key,
00279 'value' => $this->_blobencode($this->_serialize($value)),
00280 'exptime' => $exp
00281 ));
00282 $this->_commit();
00283 return true;
00284 }
00285
00286 function delete($key,$time=0) {
00287 if ( $this->_readonly() ) {
00288 return false;
00289 }
00290 $this->_begin();
00291 $this->_query(
00292 "DELETE FROM $0 WHERE keyname='$1'", $key );
00293 $this->_commit();
00294 return true;
00295 }
00296
00297 function keys() {
00298 $res = $this->_query( "SELECT keyname FROM $0" );
00299 if(!$res) {
00300 $this->_debug("keys: ** error: " . $this->_dberror($res) . " **");
00301 return array();
00302 }
00303 $result = array();
00304 while( $row = $this->_fetchobject($res) ) {
00305 $result[] = $row->keyname;
00306 }
00307 return $result;
00308 }
00309
00310 function getTableName() {
00311 return $this->table;
00312 }
00313
00314 function _query($sql) {
00315 $reps = func_get_args();
00316 $reps[0] = $this->getTableName();
00317
00318 for($i=0;$i<count($reps);$i++) {
00319 $sql = str_replace(
00320 '$' . $i,
00321 $i > 0 ? $this->_strencode($reps[$i]) : $reps[$i],
00322 $sql);
00323 }
00324 $res = $this->_doquery($sql);
00325 if($res == false) {
00326 $this->_debug('query failed: ' . $this->_dberror($res));
00327 }
00328 return $res;
00329 }
00330
00331 function _strencode($str) {
00332
00333 return str_replace( "'", "''", $str );
00334 }
00335 function _blobencode($str) {
00336 return $str;
00337 }
00338 function _blobdecode($str) {
00339 return $str;
00340 }
00341
00342 abstract function _doinsert($table, $vals);
00343 abstract function _doquery($sql);
00344
00345 abstract function _readonly();
00346
00347 function _begin() {}
00348 function _commit() {}
00349
00350 function _freeresult($result) {
00351
00352 return false;
00353 }
00354
00355 function _dberror($result) {
00356
00357 return 'unknown error';
00358 }
00359
00360 abstract function _maxdatetime();
00361 abstract function _fromunixtime($ts);
00362
00363 function garbageCollect() {
00364
00365 if ( !mt_rand( 0, 100 ) ) {
00366 $nowtime = time();
00367
00368 if ( $nowtime > ($this->lastexpireall + 1) ) {
00369 $this->lastexpireall = $nowtime;
00370 $this->expireall();
00371 }
00372 }
00373 }
00374
00375 function expireall() {
00376
00377 if ( $this->_readonly() ) {
00378 return false;
00379 }
00380 $now = $this->_fromunixtime( time() );
00381 $this->_begin();
00382 $this->_query( "DELETE FROM $0 WHERE exptime < '$now'" );
00383 $this->_commit();
00384 }
00385
00386 function deleteall() {
00387
00388 if ( $this->_readonly() ) {
00389 return false;
00390 }
00391 $this->_begin();
00392 $this->_query( "DELETE FROM $0" );
00393 $this->_commit();
00394 }
00395
00404 function _serialize( &$data ) {
00405 $serial = serialize( $data );
00406 if( function_exists( 'gzdeflate' ) ) {
00407 return gzdeflate( $serial );
00408 } else {
00409 return $serial;
00410 }
00411 }
00412
00418 function _unserialize( $serial ) {
00419 if( function_exists( 'gzinflate' ) ) {
00420 $decomp = @gzinflate( $serial );
00421 if( false !== $decomp ) {
00422 $serial = $decomp;
00423 }
00424 }
00425 $ret = unserialize( $serial );
00426 return $ret;
00427 }
00428 }
00429
00435 class MediaWikiBagOStuff extends SqlBagOStuff {
00436 var $tableInitialised = false;
00437 var $lb, $db;
00438
00439 function _getDB(){
00440 if ( !isset( $this->lb ) ) {
00441 $this->lb = wfGetLBFactory()->newMainLB();
00442 $this->db = $this->lb->getConnection( DB_MASTER );
00443 $this->db->clearFlag( DBO_TRX );
00444 }
00445 return $this->db;
00446 }
00447 function _begin() {
00448 $this->_getDB()->begin();
00449 }
00450 function _commit() {
00451 $this->_getDB()->commit();
00452 }
00453 function _doquery($sql) {
00454 return $this->_getDB()->query( $sql, __METHOD__ );
00455 }
00456 function _doinsert($t, $v) {
00457 return $this->_getDB()->insert($t, $v, __METHOD__, array( 'IGNORE' ) );
00458 }
00459 function _fetchobject($result) {
00460 return $this->_getDB()->fetchObject($result);
00461 }
00462 function _freeresult($result) {
00463 return $this->_getDB()->freeResult($result);
00464 }
00465 function _dberror($result) {
00466 return $this->_getDB()->lastError();
00467 }
00468 function _maxdatetime() {
00469 if ( time() > 0x7fffffff ) {
00470 return $this->_fromunixtime( 1<<62 );
00471 } else {
00472 return $this->_fromunixtime( 0x7fffffff );
00473 }
00474 }
00475 function _fromunixtime($ts) {
00476 return $this->_getDB()->timestamp($ts);
00477 }
00478
00479
00480
00481
00482
00483
00484
00485
00486
00487
00488
00489 function _readonly(){
00490 return false;
00491 }
00492 function _strencode($s) {
00493 return $this->_getDB()->strencode($s);
00494 }
00495 function _blobencode($s) {
00496 return $this->_getDB()->encodeBlob($s);
00497 }
00498 function _blobdecode($s) {
00499 return $this->_getDB()->decodeBlob($s);
00500 }
00501 function getTableName() {
00502 if ( !$this->tableInitialised ) {
00503 $dbw = $this->_getDB();
00504
00505
00506 if (!$dbw)
00507 throw new MWException("Could not connect to database");
00508 $this->table = $dbw->tableName( $this->table );
00509 $this->tableInitialised = true;
00510 }
00511 return $this->table;
00512 }
00513 }
00514
00530 class TurckBagOStuff extends BagOStuff {
00531 function get($key) {
00532 $val = mmcache_get( $key );
00533 if ( is_string( $val ) ) {
00534 $val = unserialize( $val );
00535 }
00536 return $val;
00537 }
00538
00539 function set($key, $value, $exptime=0) {
00540 mmcache_put( $key, serialize( $value ), $exptime );
00541 return true;
00542 }
00543
00544 function delete($key, $time=0) {
00545 mmcache_rm( $key );
00546 return true;
00547 }
00548
00549 function lock($key, $waitTimeout = 0 ) {
00550 mmcache_lock( $key );
00551 return true;
00552 }
00553
00554 function unlock($key) {
00555 mmcache_unlock( $key );
00556 return true;
00557 }
00558 }
00559
00565 class APCBagOStuff extends BagOStuff {
00566 function get($key) {
00567 $val = apc_fetch($key);
00568 if ( is_string( $val ) ) {
00569 $val = unserialize( $val );
00570 }
00571 return $val;
00572 }
00573
00574 function set($key, $value, $exptime=0) {
00575 apc_store($key, serialize($value), $exptime);
00576 return true;
00577 }
00578
00579 function delete($key, $time=0) {
00580 apc_delete($key);
00581 return true;
00582 }
00583 }
00584
00585
00594 class eAccelBagOStuff extends BagOStuff {
00595 function get($key) {
00596 $val = eaccelerator_get( $key );
00597 if ( is_string( $val ) ) {
00598 $val = unserialize( $val );
00599 }
00600 return $val;
00601 }
00602
00603 function set($key, $value, $exptime=0) {
00604 eaccelerator_put( $key, serialize( $value ), $exptime );
00605 return true;
00606 }
00607
00608 function delete($key, $time=0) {
00609 eaccelerator_rm( $key );
00610 return true;
00611 }
00612
00613 function lock($key, $waitTimeout = 0 ) {
00614 eaccelerator_lock( $key );
00615 return true;
00616 }
00617
00618 function unlock($key) {
00619 eaccelerator_unlock( $key );
00620 return true;
00621 }
00622 }
00623
00630 class XCacheBagOStuff extends BagOStuff {
00631
00638 public function get( $key ) {
00639 $val = xcache_get( $key );
00640 if( is_string( $val ) )
00641 $val = unserialize( $val );
00642 return $val;
00643 }
00644
00653 public function set( $key, $value, $expire = 0 ) {
00654 xcache_set( $key, serialize( $value ), $expire );
00655 return true;
00656 }
00657
00665 public function delete( $key, $time = 0 ) {
00666 xcache_unset( $key );
00667 return true;
00668 }
00669
00670 }
00671
00676 class DBABagOStuff extends BagOStuff {
00677 var $mHandler, $mFile, $mReader, $mWriter, $mDisabled;
00678
00679 function __construct( $handler = 'db3', $dir = false ) {
00680 if ( $dir === false ) {
00681 global $wgTmpDirectory;
00682 $dir = $wgTmpDirectory;
00683 }
00684 $this->mFile = "$dir/mw-cache-" . wfWikiID();
00685 $this->mFile .= '.db';
00686 wfDebug( __CLASS__.": using cache file {$this->mFile}\n" );
00687 $this->mHandler = $handler;
00688 }
00689
00693 function encode( $value, $expiry ) {
00694 # Convert to absolute time
00695 $expiry = BagOStuff::convertExpiry( $expiry );
00696 return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value );
00697 }
00698
00702 function decode( $blob ) {
00703 if ( !is_string( $blob ) ) {
00704 return array( null, 0 );
00705 } else {
00706 return array(
00707 unserialize( substr( $blob, 11 ) ),
00708 intval( substr( $blob, 0, 10 ) )
00709 );
00710 }
00711 }
00712
00713 function getReader() {
00714 if ( file_exists( $this->mFile ) ) {
00715 $handle = dba_open( $this->mFile, 'rl', $this->mHandler );
00716 } else {
00717 $handle = $this->getWriter();
00718 }
00719 if ( !$handle ) {
00720 wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
00721 }
00722 return $handle;
00723 }
00724
00725 function getWriter() {
00726 $handle = dba_open( $this->mFile, 'cl', $this->mHandler );
00727 if ( !$handle ) {
00728 wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
00729 }
00730 return $handle;
00731 }
00732
00733 function get( $key ) {
00734 wfProfileIn( __METHOD__ );
00735 wfDebug( __METHOD__."($key)\n" );
00736 $handle = $this->getReader();
00737 if ( !$handle ) {
00738 return null;
00739 }
00740 $val = dba_fetch( $key, $handle );
00741 list( $val, $expiry ) = $this->decode( $val );
00742 # Must close ASAP because locks are held
00743 dba_close( $handle );
00744
00745 if ( !is_null( $val ) && $expiry && $expiry < time() ) {
00746 # Key is expired, delete it
00747 $handle = $this->getWriter();
00748 dba_delete( $key, $handle );
00749 dba_close( $handle );
00750 wfDebug( __METHOD__.": $key expired\n" );
00751 $val = null;
00752 }
00753 wfProfileOut( __METHOD__ );
00754 return $val;
00755 }
00756
00757 function set( $key, $value, $exptime=0 ) {
00758 wfProfileIn( __METHOD__ );
00759 wfDebug( __METHOD__."($key)\n" );
00760 $blob = $this->encode( $value, $exptime );
00761 $handle = $this->getWriter();
00762 if ( !$handle ) {
00763 return false;
00764 }
00765 $ret = dba_replace( $key, $blob, $handle );
00766 dba_close( $handle );
00767 wfProfileOut( __METHOD__ );
00768 return $ret;
00769 }
00770
00771 function delete( $key, $time = 0 ) {
00772 wfProfileIn( __METHOD__ );
00773 wfDebug( __METHOD__."($key)\n" );
00774 $handle = $this->getWriter();
00775 if ( !$handle ) {
00776 return false;
00777 }
00778 $ret = dba_delete( $key, $handle );
00779 dba_close( $handle );
00780 wfProfileOut( __METHOD__ );
00781 return $ret;
00782 }
00783
00784 function add( $key, $value, $exptime = 0 ) {
00785 wfProfileIn( __METHOD__ );
00786 $blob = $this->encode( $value, $exptime );
00787 $handle = $this->getWriter();
00788 if ( !$handle ) {
00789 return false;
00790 }
00791 $ret = dba_insert( $key, $blob, $handle );
00792 # Insert failed, check to see if it failed due to an expired key
00793 if ( !$ret ) {
00794 list( $value, $expiry ) = $this->decode( dba_fetch( $key, $handle ) );
00795 if ( $expiry < time() ) {
00796 # Yes expired, delete and try again
00797 dba_delete( $key, $handle );
00798 $ret = dba_insert( $key, $blob, $handle );
00799 # This time if it failed then it will be handled by the caller like any other race
00800 }
00801 }
00802
00803 dba_close( $handle );
00804 wfProfileOut( __METHOD__ );
00805 return $ret;
00806 }
00807
00808 function keys() {
00809 $reader = $this->getReader();
00810 $k1 = dba_firstkey( $reader );
00811 if( !$k1 ) {
00812 return array();
00813 }
00814 $result[] = $k1;
00815 while( $key = dba_nextkey( $reader ) ) {
00816 $result[] = $key;
00817 }
00818 return $result;
00819 }
00820 }