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 global $wgDBtype;
00441 if ( !isset( $this->db ) ) {
00442
00443
00444
00445
00446 if ( $wgDBtype == 'sqlite' ) {
00447 $this->db = wfGetDB( DB_MASTER );
00448 } else {
00449 $this->lb = wfGetLBFactory()->newMainLB();
00450 $this->db = $this->lb->getConnection( DB_MASTER );
00451 $this->db->clearFlag( DBO_TRX );
00452 }
00453 }
00454 return $this->db;
00455 }
00456 function _begin() {
00457 $this->_getDB()->begin();
00458 }
00459 function _commit() {
00460 $this->_getDB()->commit();
00461 }
00462 function _doquery($sql) {
00463 return $this->_getDB()->query( $sql, __METHOD__ );
00464 }
00465 function _doinsert($t, $v) {
00466 return $this->_getDB()->insert($t, $v, __METHOD__, array( 'IGNORE' ) );
00467 }
00468 function _fetchobject($result) {
00469 return $this->_getDB()->fetchObject($result);
00470 }
00471 function _freeresult($result) {
00472 return $this->_getDB()->freeResult($result);
00473 }
00474 function _dberror($result) {
00475 return $this->_getDB()->lastError();
00476 }
00477 function _maxdatetime() {
00478 if ( time() > 0x7fffffff ) {
00479 return $this->_fromunixtime( 1<<62 );
00480 } else {
00481 return $this->_fromunixtime( 0x7fffffff );
00482 }
00483 }
00484 function _fromunixtime($ts) {
00485 return $this->_getDB()->timestamp($ts);
00486 }
00487
00488
00489
00490
00491
00492
00493
00494
00495
00496
00497
00498 function _readonly(){
00499 return false;
00500 }
00501 function _strencode($s) {
00502 return $this->_getDB()->strencode($s);
00503 }
00504 function _blobencode($s) {
00505 return $this->_getDB()->encodeBlob($s);
00506 }
00507 function _blobdecode($s) {
00508 return $this->_getDB()->decodeBlob($s);
00509 }
00510 function getTableName() {
00511 if ( !$this->tableInitialised ) {
00512 $dbw = $this->_getDB();
00513
00514
00515 if (!$dbw)
00516 throw new MWException("Could not connect to database");
00517 $this->table = $dbw->tableName( $this->table );
00518 $this->tableInitialised = true;
00519 }
00520 return $this->table;
00521 }
00522 }
00523
00539 class TurckBagOStuff extends BagOStuff {
00540 function get($key) {
00541 $val = mmcache_get( $key );
00542 if ( is_string( $val ) ) {
00543 $val = unserialize( $val );
00544 }
00545 return $val;
00546 }
00547
00548 function set($key, $value, $exptime=0) {
00549 mmcache_put( $key, serialize( $value ), $exptime );
00550 return true;
00551 }
00552
00553 function delete($key, $time=0) {
00554 mmcache_rm( $key );
00555 return true;
00556 }
00557
00558 function lock($key, $waitTimeout = 0 ) {
00559 mmcache_lock( $key );
00560 return true;
00561 }
00562
00563 function unlock($key) {
00564 mmcache_unlock( $key );
00565 return true;
00566 }
00567 }
00568
00574 class APCBagOStuff extends BagOStuff {
00575 function get($key) {
00576 $val = apc_fetch($key);
00577 if ( is_string( $val ) ) {
00578 $val = unserialize( $val );
00579 }
00580 return $val;
00581 }
00582
00583 function set($key, $value, $exptime=0) {
00584 apc_store($key, serialize($value), $exptime);
00585 return true;
00586 }
00587
00588 function delete($key, $time=0) {
00589 apc_delete($key);
00590 return true;
00591 }
00592 }
00593
00594
00603 class eAccelBagOStuff extends BagOStuff {
00604 function get($key) {
00605 $val = eaccelerator_get( $key );
00606 if ( is_string( $val ) ) {
00607 $val = unserialize( $val );
00608 }
00609 return $val;
00610 }
00611
00612 function set($key, $value, $exptime=0) {
00613 eaccelerator_put( $key, serialize( $value ), $exptime );
00614 return true;
00615 }
00616
00617 function delete($key, $time=0) {
00618 eaccelerator_rm( $key );
00619 return true;
00620 }
00621
00622 function lock($key, $waitTimeout = 0 ) {
00623 eaccelerator_lock( $key );
00624 return true;
00625 }
00626
00627 function unlock($key) {
00628 eaccelerator_unlock( $key );
00629 return true;
00630 }
00631 }
00632
00639 class XCacheBagOStuff extends BagOStuff {
00640
00647 public function get( $key ) {
00648 $val = xcache_get( $key );
00649 if( is_string( $val ) )
00650 $val = unserialize( $val );
00651 return $val;
00652 }
00653
00662 public function set( $key, $value, $expire = 0 ) {
00663 xcache_set( $key, serialize( $value ), $expire );
00664 return true;
00665 }
00666
00674 public function delete( $key, $time = 0 ) {
00675 xcache_unset( $key );
00676 return true;
00677 }
00678
00679 }
00680
00685 class DBABagOStuff extends BagOStuff {
00686 var $mHandler, $mFile, $mReader, $mWriter, $mDisabled;
00687
00688 function __construct( $handler = 'db3', $dir = false ) {
00689 if ( $dir === false ) {
00690 global $wgTmpDirectory;
00691 $dir = $wgTmpDirectory;
00692 }
00693 $this->mFile = "$dir/mw-cache-" . wfWikiID();
00694 $this->mFile .= '.db';
00695 wfDebug( __CLASS__.": using cache file {$this->mFile}\n" );
00696 $this->mHandler = $handler;
00697 }
00698
00702 function encode( $value, $expiry ) {
00703 # Convert to absolute time
00704 $expiry = BagOStuff::convertExpiry( $expiry );
00705 return sprintf( '%010u', intval( $expiry ) ) . ' ' . serialize( $value );
00706 }
00707
00711 function decode( $blob ) {
00712 if ( !is_string( $blob ) ) {
00713 return array( null, 0 );
00714 } else {
00715 return array(
00716 unserialize( substr( $blob, 11 ) ),
00717 intval( substr( $blob, 0, 10 ) )
00718 );
00719 }
00720 }
00721
00722 function getReader() {
00723 if ( file_exists( $this->mFile ) ) {
00724 $handle = dba_open( $this->mFile, 'rl', $this->mHandler );
00725 } else {
00726 $handle = $this->getWriter();
00727 }
00728 if ( !$handle ) {
00729 wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
00730 }
00731 return $handle;
00732 }
00733
00734 function getWriter() {
00735 $handle = dba_open( $this->mFile, 'cl', $this->mHandler );
00736 if ( !$handle ) {
00737 wfDebug( "Unable to open DBA cache file {$this->mFile}\n" );
00738 }
00739 return $handle;
00740 }
00741
00742 function get( $key ) {
00743 wfProfileIn( __METHOD__ );
00744 wfDebug( __METHOD__."($key)\n" );
00745 $handle = $this->getReader();
00746 if ( !$handle ) {
00747 return null;
00748 }
00749 $val = dba_fetch( $key, $handle );
00750 list( $val, $expiry ) = $this->decode( $val );
00751 # Must close ASAP because locks are held
00752 dba_close( $handle );
00753
00754 if ( !is_null( $val ) && $expiry && $expiry < time() ) {
00755 # Key is expired, delete it
00756 $handle = $this->getWriter();
00757 dba_delete( $key, $handle );
00758 dba_close( $handle );
00759 wfDebug( __METHOD__.": $key expired\n" );
00760 $val = null;
00761 }
00762 wfProfileOut( __METHOD__ );
00763 return $val;
00764 }
00765
00766 function set( $key, $value, $exptime=0 ) {
00767 wfProfileIn( __METHOD__ );
00768 wfDebug( __METHOD__."($key)\n" );
00769 $blob = $this->encode( $value, $exptime );
00770 $handle = $this->getWriter();
00771 if ( !$handle ) {
00772 return false;
00773 }
00774 $ret = dba_replace( $key, $blob, $handle );
00775 dba_close( $handle );
00776 wfProfileOut( __METHOD__ );
00777 return $ret;
00778 }
00779
00780 function delete( $key, $time = 0 ) {
00781 wfProfileIn( __METHOD__ );
00782 wfDebug( __METHOD__."($key)\n" );
00783 $handle = $this->getWriter();
00784 if ( !$handle ) {
00785 return false;
00786 }
00787 $ret = dba_delete( $key, $handle );
00788 dba_close( $handle );
00789 wfProfileOut( __METHOD__ );
00790 return $ret;
00791 }
00792
00793 function add( $key, $value, $exptime = 0 ) {
00794 wfProfileIn( __METHOD__ );
00795 $blob = $this->encode( $value, $exptime );
00796 $handle = $this->getWriter();
00797 if ( !$handle ) {
00798 return false;
00799 }
00800 $ret = dba_insert( $key, $blob, $handle );
00801 # Insert failed, check to see if it failed due to an expired key
00802 if ( !$ret ) {
00803 list( $value, $expiry ) = $this->decode( dba_fetch( $key, $handle ) );
00804 if ( $expiry < time() ) {
00805 # Yes expired, delete and try again
00806 dba_delete( $key, $handle );
00807 $ret = dba_insert( $key, $blob, $handle );
00808 # This time if it failed then it will be handled by the caller like any other race
00809 }
00810 }
00811
00812 dba_close( $handle );
00813 wfProfileOut( __METHOD__ );
00814 return $ret;
00815 }
00816
00817 function keys() {
00818 $reader = $this->getReader();
00819 $k1 = dba_firstkey( $reader );
00820 if( !$k1 ) {
00821 return array();
00822 }
00823 $result[] = $k1;
00824 while( $key = dba_nextkey( $reader ) ) {
00825 $result[] = $key;
00826 }
00827 return $result;
00828 }
00829 }