* (bug 6243) Fix email for usernames containing dots when using PEAR::Mail
[lhc/web/wiklou.git] / includes / BagOStuff.php
1 <?php
2 #
3 # Copyright (C) 2003-2004 Brion Vibber <brion@pobox.com>
4 # http://www.mediawiki.org/
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 # http://www.gnu.org/copyleft/gpl.html
20 /**
21 *
22 * @package MediaWiki
23 */
24
25 /**
26 * Simple generic object store
27 *
28 * interface is intended to be more or less compatible with
29 * the PHP memcached client.
30 *
31 * backends for local hash array and SQL table included:
32 * $bag = new HashBagOStuff();
33 * $bag = new MysqlBagOStuff($tablename); # connect to db first
34 *
35 * @package MediaWiki
36 */
37 class BagOStuff {
38 var $debugmode;
39
40 function BagOStuff() {
41 $this->set_debug( false );
42 }
43
44 function set_debug($bool) {
45 $this->debugmode = $bool;
46 }
47
48 /* *** THE GUTS OF THE OPERATION *** */
49 /* Override these with functional things in subclasses */
50
51 function get($key) {
52 /* stub */
53 return false;
54 }
55
56 function set($key, $value, $exptime=0) {
57 /* stub */
58 return false;
59 }
60
61 function delete($key, $time=0) {
62 /* stub */
63 return false;
64 }
65
66 function lock($key, $timeout = 0) {
67 /* stub */
68 return true;
69 }
70
71 function unlock($key) {
72 /* stub */
73 return true;
74 }
75
76 /* *** Emulated functions *** */
77 /* Better performance can likely be got with custom written versions */
78 function get_multi($keys) {
79 $out = array();
80 foreach($keys as $key)
81 $out[$key] = $this->get($key);
82 return $out;
83 }
84
85 function set_multi($hash, $exptime=0) {
86 foreach($hash as $key => $value)
87 $this->set($key, $value, $exptime);
88 }
89
90 function add($key, $value, $exptime=0) {
91 if( $this->get($key) == false ) {
92 $this->set($key, $value, $exptime);
93 return true;
94 }
95 }
96
97 function add_multi($hash, $exptime=0) {
98 foreach($hash as $key => $value)
99 $this->add($key, $value, $exptime);
100 }
101
102 function delete_multi($keys, $time=0) {
103 foreach($keys as $key)
104 $this->delete($key, $time);
105 }
106
107 function replace($key, $value, $exptime=0) {
108 if( $this->get($key) !== false )
109 $this->set($key, $value, $exptime);
110 }
111
112 function incr($key, $value=1) {
113 if ( !$this->lock($key) ) {
114 return false;
115 }
116 $value = intval($value);
117 if($value < 0) $value = 0;
118
119 $n = false;
120 if( ($n = $this->get($key)) !== false ) {
121 $n += $value;
122 $this->set($key, $n); // exptime?
123 }
124 $this->unlock($key);
125 return $n;
126 }
127
128 function decr($key, $value=1) {
129 if ( !$this->lock($key) ) {
130 return false;
131 }
132 $value = intval($value);
133 if($value < 0) $value = 0;
134
135 $m = false;
136 if( ($n = $this->get($key)) !== false ) {
137 $m = $n - $value;
138 if($m < 0) $m = 0;
139 $this->set($key, $m); // exptime?
140 }
141 $this->unlock($key);
142 return $m;
143 }
144
145 function _debug($text) {
146 if($this->debugmode)
147 wfDebug("BagOStuff debug: $text\n");
148 }
149 }
150
151
152 /**
153 * Functional versions!
154 * @todo document
155 * @package MediaWiki
156 */
157 class HashBagOStuff extends BagOStuff {
158 /*
159 This is a test of the interface, mainly. It stores
160 things in an associative array, which is not going to
161 persist between program runs.
162 */
163 var $bag;
164
165 function HashBagOStuff() {
166 $this->bag = array();
167 }
168
169 function _expire($key) {
170 $et = $this->bag[$key][1];
171 if(($et == 0) || ($et > time()))
172 return false;
173 $this->delete($key);
174 return true;
175 }
176
177 function get($key) {
178 if(!$this->bag[$key])
179 return false;
180 if($this->_expire($key))
181 return false;
182 return $this->bag[$key][0];
183 }
184
185 function set($key,$value,$exptime=0) {
186 if(($exptime != 0) && ($exptime < 3600*24*30))
187 $exptime = time() + $exptime;
188 $this->bag[$key] = array( $value, $exptime );
189 }
190
191 function delete($key,$time=0) {
192 if(!$this->bag[$key])
193 return false;
194 unset($this->bag[$key]);
195 return true;
196 }
197 }
198
199 /*
200 CREATE TABLE objectcache (
201 keyname char(255) binary not null default '',
202 value mediumblob,
203 exptime datetime,
204 unique key (keyname),
205 key (exptime)
206 );
207 */
208
209 /**
210 * @todo document
211 * @abstract
212 * @package MediaWiki
213 */
214 abstract class SqlBagOStuff extends BagOStuff {
215 var $table;
216 var $lastexpireall = 0;
217
218 function SqlBagOStuff($tablename = 'objectcache') {
219 $this->table = $tablename;
220 }
221
222 function get($key) {
223 /* expire old entries if any */
224 $this->garbageCollect();
225
226 $res = $this->_query(
227 "SELECT value,exptime FROM $0 WHERE keyname='$1'", $key);
228 if(!$res) {
229 $this->_debug("get: ** error: " . $this->_dberror($res) . " **");
230 return false;
231 }
232 if($row=$this->_fetchobject($res)) {
233 $this->_debug("get: retrieved data; exp time is " . $row->exptime);
234 return $this->_unserialize($row->value);
235 } else {
236 $this->_debug('get: no matching rows');
237 }
238 return false;
239 }
240
241 function set($key,$value,$exptime=0) {
242 $exptime = intval($exptime);
243 if($exptime < 0) $exptime = 0;
244 if($exptime == 0) {
245 $exp = $this->_maxdatetime();
246 } else {
247 if($exptime < 3600*24*30)
248 $exptime += time();
249 $exp = $this->_fromunixtime($exptime);
250 }
251 $this->delete( $key );
252 $this->_doinsert($this->getTableName(), array(
253 'keyname' => $key,
254 'value' => $this->_blobencode($this->_serialize($value)),
255 'exptime' => $exp
256 ));
257 return true; /* ? */
258 }
259
260 function delete($key,$time=0) {
261 $this->_query(
262 "DELETE FROM $0 WHERE keyname='$1'", $key );
263 return true; /* ? */
264 }
265
266 function getTableName() {
267 return $this->table;
268 }
269
270 function _query($sql) {
271 $reps = func_get_args();
272 $reps[0] = $this->getTableName();
273 // ewwww
274 for($i=0;$i<count($reps);$i++) {
275 $sql = str_replace(
276 '$' . $i,
277 $i > 0 ? $this->_strencode($reps[$i]) : $reps[$i],
278 $sql);
279 }
280 $res = $this->_doquery($sql);
281 if($res == false) {
282 $this->_debug('query failed: ' . $this->_dberror($res));
283 }
284 return $res;
285 }
286
287 function _strencode($str) {
288 /* Protect strings in SQL */
289 return str_replace( "'", "''", $str );
290 }
291 function _blobencode($str) {
292 return $str;
293 }
294
295 abstract function _doinsert($table, $vals);
296 abstract function _doquery($sql);
297
298 function _freeresult($result) {
299 /* stub */
300 return false;
301 }
302
303 function _dberror($result) {
304 /* stub */
305 return 'unknown error';
306 }
307
308 abstract function _maxdatetime();
309 abstract function _fromunixtime($ts);
310
311 function garbageCollect() {
312 /* Ignore 99% of requests */
313 if ( !mt_rand( 0, 100 ) ) {
314 $nowtime = time();
315 /* Avoid repeating the delete within a few seconds */
316 if ( $nowtime > ($this->lastexpireall + 1) ) {
317 $this->lastexpireall = $nowtime;
318 $this->expireall();
319 }
320 }
321 }
322
323 function expireall() {
324 /* Remove any items that have expired */
325 $now = $this->_fromunixtime( time() );
326 $this->_query( "DELETE FROM $0 WHERE exptime < '$now'" );
327 }
328
329 function deleteall() {
330 /* Clear *all* items from cache table */
331 $this->_query( "DELETE FROM $0" );
332 }
333
334 /**
335 * Serialize an object and, if possible, compress the representation.
336 * On typical message and page data, this can provide a 3X decrease
337 * in storage requirements.
338 *
339 * @param mixed $data
340 * @return string
341 */
342 function _serialize( &$data ) {
343 $serial = serialize( $data );
344 if( function_exists( 'gzdeflate' ) ) {
345 return gzdeflate( $serial );
346 } else {
347 return $serial;
348 }
349 }
350
351 /**
352 * Unserialize and, if necessary, decompress an object.
353 * @param string $serial
354 * @return mixed
355 */
356 function _unserialize( $serial ) {
357 if( function_exists( 'gzinflate' ) ) {
358 $decomp = @gzinflate( $serial );
359 if( false !== $decomp ) {
360 $serial = $decomp;
361 }
362 }
363 $ret = unserialize( $serial );
364 return $ret;
365 }
366 }
367
368 /**
369 * @todo document
370 * @package MediaWiki
371 */
372 class MediaWikiBagOStuff extends SqlBagOStuff {
373 var $tableInitialised = false;
374
375 function _doquery($sql) {
376 $dbw =& wfGetDB( DB_MASTER );
377 return $dbw->query($sql, 'MediaWikiBagOStuff::_doquery');
378 }
379 function _doinsert($t, $v) {
380 $dbw =& wfGetDB( DB_MASTER );
381 return $dbw->insert($t, $v, 'MediaWikiBagOStuff::_doinsert');
382 }
383 function _fetchobject($result) {
384 $dbw =& wfGetDB( DB_MASTER );
385 return $dbw->fetchObject($result);
386 }
387 function _freeresult($result) {
388 $dbw =& wfGetDB( DB_MASTER );
389 return $dbw->freeResult($result);
390 }
391 function _dberror($result) {
392 $dbw =& wfGetDB( DB_MASTER );
393 return $dbw->lastError();
394 }
395 function _maxdatetime() {
396 $dbw =& wfGetDB(DB_MASTER);
397 return $dbw->timestamp('9999-12-31 12:59:59');
398 }
399 function _fromunixtime($ts) {
400 $dbw =& wfGetDB(DB_MASTER);
401 return $dbw->timestamp($ts);
402 }
403 function _strencode($s) {
404 $dbw =& wfGetDB( DB_MASTER );
405 return $dbw->strencode($s);
406 }
407 function _blobencode($s) {
408 $dbw =& wfGetDB( DB_MASTER );
409 return $dbw->encodeBlob($s);
410 }
411 function getTableName() {
412 if ( !$this->tableInitialised ) {
413 $dbw =& wfGetDB( DB_MASTER );
414 /* This is actually a hack, we should be able
415 to use Language classes here... or not */
416 if (!$dbw)
417 throw new MWException("Could not connect to database");
418 $this->table = $dbw->tableName( $this->table );
419 $this->tableInitialised = true;
420 }
421 return $this->table;
422 }
423 }
424
425 /**
426 * This is a wrapper for Turck MMCache's shared memory functions.
427 *
428 * You can store objects with mmcache_put() and mmcache_get(), but Turck seems
429 * to use a weird custom serializer that randomly segfaults. So we wrap calls
430 * with serialize()/unserialize().
431 *
432 * The thing I noticed about the Turck serialized data was that unlike ordinary
433 * serialize(), it contained the names of methods, and judging by the amount of
434 * binary data, perhaps even the bytecode of the methods themselves. It may be
435 * that Turck's serializer is faster, so a possible future extension would be
436 * to use it for arrays but not for objects.
437 *
438 * @package MediaWiki
439 */
440 class TurckBagOStuff extends BagOStuff {
441 function get($key) {
442 $val = mmcache_get( $key );
443 if ( is_string( $val ) ) {
444 $val = unserialize( $val );
445 }
446 return $val;
447 }
448
449 function set($key, $value, $exptime=0) {
450 mmcache_put( $key, serialize( $value ), $exptime );
451 return true;
452 }
453
454 function delete($key, $time=0) {
455 mmcache_rm( $key );
456 return true;
457 }
458
459 function lock($key, $waitTimeout = 0 ) {
460 mmcache_lock( $key );
461 return true;
462 }
463
464 function unlock($key) {
465 mmcache_unlock( $key );
466 return true;
467 }
468 }
469
470 /**
471 * This is a wrapper for APC's shared memory functions
472 *
473 * @package MediaWiki
474 */
475
476 class APCBagOStuff extends BagOStuff {
477 function get($key) {
478 $val = apc_fetch($key);
479 return $val;
480 }
481
482 function set($key, $value, $exptime=0) {
483 apc_store($key, $value, $exptime);
484 return true;
485 }
486
487 function delete($key) {
488 apc_delete($key);
489 return true;
490 }
491 }
492
493
494 /**
495 * This is a wrapper for eAccelerator's shared memory functions.
496 *
497 * This is basically identical to the Turck MMCache version,
498 * mostly because eAccelerator is based on Turck MMCache.
499 *
500 * @package MediaWiki
501 */
502 class eAccelBagOStuff extends BagOStuff {
503 function get($key) {
504 $val = eaccelerator_get( $key );
505 if ( is_string( $val ) ) {
506 $val = unserialize( $val );
507 }
508 return $val;
509 }
510
511 function set($key, $value, $exptime=0) {
512 eaccelerator_put( $key, serialize( $value ), $exptime );
513 return true;
514 }
515
516 function delete($key, $time=0) {
517 eaccelerator_rm( $key );
518 return true;
519 }
520
521 function lock($key, $waitTimeout = 0 ) {
522 eaccelerator_lock( $key );
523 return true;
524 }
525
526 function unlock($key) {
527 eaccelerator_unlock( $key );
528 return true;
529 }
530 }
531 ?>