aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/memcached.doc105
-rw-r--r--docs/php-memcached/ChangeLog36
-rw-r--r--docs/php-memcached/Documentation257
-rw-r--r--includes/DefaultSettings.php7
-rw-r--r--includes/LinkCache.php25
-rw-r--r--includes/MemCachedClient.inc.php1104
-rw-r--r--includes/Setup.php19
-rw-r--r--includes/User.php51
-rw-r--r--includes/UserTalkUpdate.php6
9 files changed, 1576 insertions, 34 deletions
diff --git a/docs/memcached.doc b/docs/memcached.doc
new file mode 100644
index 000000000000..c79865d6bb05
--- /dev/null
+++ b/docs/memcached.doc
@@ -0,0 +1,105 @@
+memcached support for MediaWiki:
+
+From ca August 2003, MediaWiki has optional support for memcached, a
+"high-performance, distributed memory object caching system".
+For general information on it, see: http://www.danga.com/memcached/
+
+Memcached is likely more trouble than a small site will need, but
+for a larger site with heavy load, like Wikipedia, it should help
+lighten the load on the database servers by caching data and objects
+in memory.
+
+== Requirements ==
+
+* PHP must be compiled with --enable-sockets
+
+* libevent: http://www.monkey.org/~provos/libevent/
+ (as of 2003-08-11, 0.7a is current)
+
+* optionally, epoll-rt patch for Linux kernel:
+ http://www.xmailserver.org/linux-patches/nio-improve.html
+
+* memcached: http://www.danga.com/memcached/download.bml
+ (as of this writing, 1.1.9 is current)
+
+Memcached and libevent are under BSD-style licenses.
+
+The server should run on Linux and other Unix-like systems... you
+can run multiple servers on one machine or on multiple machines on
+a network; storage can be distributed across multiple servers, and
+multiple web servers can use the same cache cluster.
+
+
+********************* W A R N I N G ! ! ! ! ! ***********************
+Memcached has no security or authentication. Please ensure that your
+server is appropriately firewalled, and that the port(s) used for
+memcached servers are not publicly accessible. Otherwise, anyone on
+the internet can put data into and read data from your cache.
+
+An attacker familiar with MediaWiki internals could use this to give
+themselves developer access and delete all data from the wiki's
+database, as well as getting all users' password hashes and e-mail
+addresses.
+********************* W A R N I N G ! ! ! ! ! ***********************
+
+== Setup ==
+
+If you want to start small, just run one memcached on your web
+server:
+
+ memcached -d -l 127.0.0.1 -p 11000 -m 64
+
+(to run in daemon mode, accessible only via loopback interface,
+on port 11000, using up to 64MB of memory)
+
+In your LocalSettings.php file, set:
+
+ $wgUseMemCached = true;
+ $wgMemCachedServers = array( "127.0.0.1:11000" );
+
+The wiki should then use memcached to cache various data. To use
+multiple servers (physically separate boxes or multiple caches
+on one machine on a large-memory x86 box), just add more items
+to the array. To increase the weight of a server (say, because
+it has twice the memory of the others and you want to spread
+usage evenly), make its entry a subarray:
+
+ $wgMemCachedServers = array(
+ "127.0.0.1:11000", # one gig on this box
+ array("192.168.0.1:11000", 2) # two gigs on the other box
+ );
+
+
+== PHP client for memcached ==
+
+As of this writing, MediaWiki includes version 1.0.8 of the PHP
+memcached client by Ryan Gilfether <hotrodder@rocketmail.com>.
+You'll find some documentation for it in the 'php-memcached'
+subdirectory under the present one.
+
+We intend to track updates, but if you want to check for the lastest
+released version, see http://www.danga.com/memcached/apis.bml
+
+If you don't set $wgUseMemCached, we still create a MemCacheClient,
+but requests to it are no-ops and we always fall through to the
+database. If the cache daemon can't be contacted, it should also
+disable itself fairly smoothly.
+
+== Keys used ==
+
+User:
+ key: $wgDBname:user:user_id:$sId
+ ex: wikidb:user:user_id:51
+ stores: instance of class User
+ set in: User::loadFromSession()
+ cleared by: User::saveSettings()
+
+LinkCache:
+ key: $wgDBname:linkcache:title:$title
+ ex: wikidb:linkcache:title:Wikipedia:Welcome,_Newcomers!
+ stores: cur_id of page, or 0 if page does not exist
+ set in: LinkCache::addLink()
+ cleared by: LinkCache::clearBadLink()
+ should be cleared on page deletion and rename
+
+... more to come ...
diff --git a/docs/php-memcached/ChangeLog b/docs/php-memcached/ChangeLog
new file mode 100644
index 000000000000..bfabb494f38a
--- /dev/null
+++ b/docs/php-memcached/ChangeLog
@@ -0,0 +1,36 @@
+Release 1.0.8
+-------------
+* whitespace/punctuation/wording cleanups
+
+Release 1.0.7
+-------------
+* added 3 functions which handle error reporting
+ error() - returns error number of last error generated, else returns 0
+ error_string() - returns a string description of error number retuned
+ error_clear() - clears the last error number and error string
+* removed call to preg_match() in _loaditems()
+* only non-scalar values are serialize() before being
+ sent to the server
+* added the optional timestamp argument for delete()
+ read Documentation file for details
+* PHPDocs/PEAR style comments added
+* abstract debugging (Brion Vibber <brion@pobox.com>)
+
+Release 1.0.6
+-------------
+* removed all array_push() calls
+* applied patch provided by Stuart Herbert<stuart@gentoo.org>
+ corrects possible endless loop. Available at
+ http://bugs.gentoo.org/show_bug.cgi?id=25385
+* fixed problem with storing large binary files
+* added more error checking, specifically on all socket functions
+* added support for the INCR and DECR commands
+ which increment or decrement a value stored in MemCached
+* Documentation removed from source and is now available
+ in the file Documentation
+
+Release 1.0.4
+-------------
+* initial release, version numbers kept
+ in sync with MemCached version
+* capable of storing any datatype in MemCached
diff --git a/docs/php-memcached/Documentation b/docs/php-memcached/Documentation
new file mode 100644
index 000000000000..0d09d178604d
--- /dev/null
+++ b/docs/php-memcached/Documentation
@@ -0,0 +1,257 @@
+Ryan Gilfether <hotrodder@rocketmail.com>
+http://www.gilfether.com
+This module is Copyright (c) 2003 Ryan Gilfether.
+All rights reserved.
+
+You may distribute under the terms of the GNU General Public License
+This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
+
+See the memcached website: http://www.danga.com/memcached/
+
+
+// Takes one parameter, a array of options. The most important key is
+// options["servers"], but that can also be set later with the set_servers()
+// method. The servers must be an array of hosts, each of which is
+// either a scalar of the form <10.0.0.10:11211> or an array of the
+// former and an integer weight value. (the default weight if
+// unspecified is 1.) It's recommended that weight values be kept as low
+// as possible, as this module currently allocates memory for bucket
+// distribution proportional to the total host weights.
+// $options["debug"] turns the debugging on if set to true
+MemCachedClient::MemCachedClient($options);
+
+// sets up the list of servers and the ports to connect to
+// takes an array of servers in the same format as in the constructor
+MemCachedClient::set_servers($servers);
+
+// Retrieves a key from the memcache. Returns the value (automatically
+// unserialized, if necessary) or FALSE if it fails.
+// The $key can optionally be an array, with the first element being the
+// hash value, if you want to avoid making this module calculate a hash
+// value. You may prefer, for example, to keep all of a given user's
+// objects on the same memcache server, so you could use the user's
+// unique id as the hash value.
+// Possible errors set are:
+// MC_ERR_GET
+MemCachedClient::get($key);
+
+// just like get(), but takes an array of keys, returns FALSE on error
+// Possible errors set are:
+// MC_ERR_NOT_ACTIVE
+MemCachedClient::get_multi($keys)
+
+// Unconditionally sets a key to a given value in the memcache. Returns true
+// if it was stored successfully.
+// The $key can optionally be an arrayref, with the first element being the
+// hash value, as described above.
+// returns TRUE on success else FALSE
+// Possible errors set are:
+// MC_ERR_NOT_ACTIVE
+// MC_ERR_GET_SOCK
+// MC_ERR_SOCKET_WRITE
+// MC_ERR_SOCKET_READ
+// MC_ERR_SET
+MemCachedClient::set($key, $value, $exptime);
+
+// Like set(), but only stores in memcache if the key doesn't already exist.
+// returns TRUE on success else FALSE
+// Possible errors set are:
+// MC_ERR_NOT_ACTIVE
+// MC_ERR_GET_SOCK
+// MC_ERR_SOCKET_WRITE
+// MC_ERR_SOCKET_READ
+// MC_ERR_SET
+MemCachedClient::add($key, $value, $exptime);
+
+// Like set(), but only stores in memcache if the key already exists.
+// returns TRUE on success else FALSE
+// Possible errors set are:
+// MC_ERR_NOT_ACTIVE
+// MC_ERR_GET_SOCK
+// MC_ERR_SOCKET_WRITE
+// MC_ERR_SOCKET_READ
+// MC_ERR_SET
+MemCachedClient::replace($key, $value, $exptime);
+
+// removes the key from the MemCache
+// $time is the amount of time in seconds (or Unix time) until which
+// the client wishes the server to refuse "add" and "replace" commands
+// with this key. For this amount of item, the item is put into a
+// delete queue, which means that it won't possible to retrieve it by
+// the "get" command, but "add" and "replace" command with this key
+// will also fail (the "set" command will succeed, however). After the
+// time passes, the item is finally deleted from server memory.
+// The parameter $time is optional, and, if absent, defaults to 0
+// (which means that the item will be deleted immediately and further
+// storage commands with this key will succeed).
+// returns TRUE on success else returns FALSE
+// Possible errors set are:
+// MC_ERR_NOT_ACTIVE
+// MC_ERR_GET_SOCK
+// MC_ERR_SOCKET_WRITE
+// MC_ERR_SOCKET_READ
+// MC_ERR_DELETE
+MemCachedClient::delete($key, $time = 0);
+
+// Sends a command to the server to atomically increment the value for
+// $key by $value, or by 1 if $value is undefined. Returns FALSE if $key
+// doesn't exist on server, otherwise it returns the new value after
+// incrementing. Value should be zero or greater. Overflow on server
+// is not checked. Be aware of values approaching 2**32. See decr.
+// Possible errors set are:
+// MC_ERR_NOT_ACTIVE
+// MC_ERR_GET_SOCK
+// MC_ERR_SOCKET_WRITE
+// MC_ERR_SOCKET_READ
+// returns new value on success, else returns FALSE
+// ONLY WORKS WITH NUMERIC VALUES
+MemCachedClient::incr($key[, $value]);
+
+// Like incr, but decrements. Unlike incr, underflow is checked and new
+// values are capped at 0. If server value is 1, a decrement of 2
+// returns 0, not -1.
+// Possible errors set are:
+// MC_ERR_NOT_ACTIVE
+// MC_ERR_GET_SOCK
+// MC_ERR_SOCKET_WRITE
+// MC_ERR_SOCKET_READ
+// returns new value on success, else returns FALSE
+// ONLY WORKS WITH NUMERIC VALUES
+MemCachedClient::decr($key[, $value]);
+
+// disconnects from all servers
+MemCachedClient::disconnect_all();
+
+// if $do_debug is set to true, will print out
+// debugging info, else debug is turned off
+MemCachedClient::set_debug($do_debug);
+
+// remove all cached hosts that are no longer good
+MemCachedClient::forget_dead_hosts();
+
+// When a function returns FALSE, an error code is set.
+// This funtion will return the error code.
+// See error_string()
+// returns last error code set
+MemCachedClient::error()
+
+// Returns a string describing the error set in error()
+// See error()
+// returns a string describing the error code given
+MemCachedClient::error_string()
+
+// Resets the error number and error string
+MemCachedClient::error_clear()
+
+Error codes are as follows:
+MC_ERR_NOT_ACTIVE // no active servers
+MC_ERR_SOCKET_WRITE // socket_write() failed
+MC_ERR_SOCKET_READ // socket_read() failed
+MC_ERR_SOCKET_CONNECT // failed to connect to host
+MC_ERR_DELETE // delete() did not recieve DELETED command
+MC_ERR_HOST_FORMAT // sock_to_host() invalid host format
+MC_ERR_HOST_DEAD // sock_to_host() host is dead
+MC_ERR_GET_SOCK // get_sock() failed to find a valid socket
+MC_ERR_SET // _set() failed to receive the STORED response
+MC_ERR_LOADITEM_HEADER // _load_items failed to receive valid data header
+MC_ERR_LOADITEM_END // _load_items failed to receive END response
+MC_ERR_LOADITEM_BYTES // _load_items bytes read larger than bytes available
+MC_ERR_GET // failed to get value associated with key
+
+
+
+EXAMPLE:
+<?php
+require("MemCachedClient.inc.php");
+
+// set the servers, with the last one having an interger weight value of 3
+$options["servers"] = array("10.0.0.15:11000","10.0.0.16:11001",array("10.0.0.17:11002", 3));
+$options["debug"] = false;
+
+$memc = new MemCachedClient($options);
+
+
+/***********************
+ * STORE AN ARRAY
+ ***********************/
+$myarr = array("one","two", 3);
+$memc->set("key_one", $myarr);
+$val = $memc->get("key_one");
+print $val[0]."\n"; // prints 'one'
+print $val[1]."\n"; // prints 'two'
+print $val[2]."\n"; // prints 3
+
+
+print "\n";
+
+
+/***********************
+ * STORE A CLASS
+ ***********************/
+class tester
+{
+ var $one;
+ var $two;
+ var $three;
+}
+
+$t = new tester;
+$t->one = "one";
+$t->two = "two";
+$t->three = 3;
+$memc->set("key_two", $t);
+$val = $memc->get("key_two");
+print $val->one."\n";
+print $val->two."\n";
+print $val->three."\n";
+
+
+print "\n";
+
+
+/***********************
+ * STORE A STRING
+ ***********************/
+$memc->set("key_three", "my string");
+$val = $memc->get("key_three");
+print $val; // prints 'my string'
+
+$memc->delete("key_one");
+$memc->delete("key_two");
+$memc->delete("key_three");
+
+$memc->disconnect_all();
+
+
+
+print "\n";
+
+
+/***********************
+ * STORE A BINARY FILE
+ ***********************/
+
+ // first read the file and save it in memcache
+$fp = fopen( "./image.jpg", "rb" ) ;
+if ( !$fp )
+{
+ print "Could not open ./file.dat!\n" ;
+ exit ;
+}
+$data = fread( $fp, filesize( "./image.jpg" ) ) ;
+fclose( $fp ) ;
+print "Data length is " . strlen( $data ) . "\n" ;
+$memc->set( "key", $data ) ;
+
+// now open a file for writing and write the data
+// retrieved from memcache
+$fp = fopen("./test.jpg","wb");
+$data = $memc->get( "key" ) ;
+print "Data length is " . strlen( $data ) . "\n" ;
+fwrite($fp,$data,strlen( $data ));
+fclose($fp);
+
+
+?>
+
+
diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php
index 98fcbde19461..031c7e5b784c 100644
--- a/includes/DefaultSettings.php
+++ b/includes/DefaultSettings.php
@@ -38,6 +38,13 @@ $wgDBminWordLen = 4;
$wgDBtransactions = false; # Set to true if using InnoDB tables
$wgDBmysql4 = false; # Set to true to use enhanced fulltext search
+# memcached settings
+# See docs/memcached.doc
+#
+$wgUseMemCached = false;
+$wgMemCachedServers = array( "127.0.0.1:11000" );
+$wgMemCachedDebug = false;
+
# Language settings
#
$wgLanguageCode = "en";
diff --git a/includes/LinkCache.php b/includes/LinkCache.php
index 0955edda3a8c..62bf9dedb089 100644
--- a/includes/LinkCache.php
+++ b/includes/LinkCache.php
@@ -62,6 +62,8 @@ class LinkCache {
if ( isset( $index ) ) {
unset( $this->mBadLinks[$index] );
}
+ global $wgMemc, $wgDBname;
+ $wgMemc->delete( "$wgDBname:linkcache:title:$title" );
}
function suspend() { $this->mActive = false; }
@@ -76,6 +78,7 @@ class LinkCache {
$id = $this->getGoodLinkID( $title );
if ( 0 != $id ) { return $id; }
+ global $wgMemc, $wgDBname;
wfProfileIn( "LinkCache::addLink-checkdatabase" );
$nt = Title::newFromDBkey( $title );
@@ -83,15 +86,20 @@ class LinkCache {
$t = $nt->getDBkey();
if ( "" == $t ) { return 0; }
- $sql = "SELECT HIGH_PRIORITY cur_id FROM cur WHERE cur_namespace=" .
- "{$ns} AND cur_title='" . wfStrencode( $t ) . "'";
- $res = wfQuery( $sql, "LinkCache::addLink" );
- if ( 0 == wfNumRows( $res ) ) {
- $id = 0;
- } else {
- $s = wfFetchObject( $res );
- $id = $s->cur_id;
+ $id = $wgMemc->get( $key = "$wgDBname:linkcache:title:$title" );
+ if( $id === FALSE ) {
+ $sql = "SELECT HIGH_PRIORITY cur_id FROM cur WHERE cur_namespace=" .
+ "{$ns} AND cur_title='" . wfStrencode( $t ) . "'";
+ $res = wfQuery( $sql, "LinkCache::addLink" );
+
+ if ( 0 == wfNumRows( $res ) ) {
+ $id = 0;
+ } else {
+ $s = wfFetchObject( $res );
+ $id = $s->cur_id;
+ }
+ $wgMemc->add( $key, $id, time()+3600 );
}
if ( 0 == $id ) { $this->addBadLink( $title ); }
else { $this->addGoodLink( $id, $title ); }
@@ -200,5 +208,6 @@ class LinkCache {
$this->mBadLinks = array();
$this->mImageLinks = array();
}
+
}
?>
diff --git a/includes/MemCachedClient.inc.php b/includes/MemCachedClient.inc.php
new file mode 100644
index 000000000000..b1a35a230b87
--- /dev/null
+++ b/includes/MemCachedClient.inc.php
@@ -0,0 +1,1104 @@
+<?php
+/*
+ * MemCached PHP client
+ * Copyright (c) 2003
+ * Ryan Gilfether <hotrodder@rocketmail.com>
+ * http://www.gilfether.com
+ *
+ * Originally translated from Brad Fitzpatrick's <brad@danga.com> MemCached Perl client
+ * See the memcached website:
+ * http://www.danga.com/memcached/
+ *
+ * This module is Copyright (c) 2003 Ryan Gilfether.
+ * All rights reserved.
+ * You may distribute under the terms of the GNU General Public License
+ * This is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
+ *
+ */
+
+/**
+ * version string
+ */
+define("MC_VERSION", "1.0.8");
+/**
+ * int, buffer size used for sending and receiving
+ * data from sockets
+ */
+define("MC_BUFFER_SZ", 1024);
+/**
+ * MemCached error numbers
+ */
+define("MC_ERR_NOT_ACTIVE", 1001); // no active servers
+define("MC_ERR_SOCKET_WRITE", 1002); // socket_write() failed
+define("MC_ERR_SOCKET_READ", 1003); // socket_read() failed
+define("MC_ERR_SOCKET_CONNECT", 1004); // failed to connect to host
+define("MC_ERR_DELETE", 1005); // delete() did not recieve DELETED command
+define("MC_ERR_HOST_FORMAT", 1006); // sock_to_host() invalid host format
+define("MC_ERR_HOST_DEAD", 1007); // sock_to_host() host is dead
+define("MC_ERR_GET_SOCK", 1008); // get_sock() failed to find a valid socket
+define("MC_ERR_SET", 1009); // _set() failed to receive the STORED response
+define("MC_ERR_GET_KEY", 1010); // _load_items no values returned for key(s)
+define("MC_ERR_LOADITEM_END", 1011); // _load_items failed to receive END response
+define("MC_ERR_LOADITEM_BYTES", 1012); // _load_items bytes read larger than bytes available
+
+
+/**
+ * MemCached PHP client Class.
+ *
+ * Communicates with the MemCached server, and executes the MemCached protocol
+ * MemCached available at http://www.danga.com/memcached
+ *
+ * @author Ryan Gilfether <ryan@gilfether.com>
+ * @package MemCachedClient
+ * @access public
+ * @version 1.0.7
+ */
+class MemCachedClient
+{
+ /**
+ * array of servers no long available
+ * @var array
+ */
+ var $host_dead;
+ /**
+ * array of open sockets
+ * @var array
+ */
+ var $cache_sock;
+ /**
+ * determine if debugging is either on or off
+ * @var bool
+ */
+ var $debug;
+ /**
+ * array of servers to attempt to use, "host:port" string format
+ * @var array
+ */
+ var $servers;
+ /**
+ * count of currently active connections to servers
+ * @var int
+ */
+ var $active;
+ /**
+ * error code if one is set
+ * @var int
+ */
+ var $errno;
+ /**
+ * string describing error
+ * @var string
+ */
+ var $errstr;
+
+
+ /**
+ * Constructor
+ *
+ * Creates a new MemCachedClient object
+ * Takes one parameter, a array of options. The most important key is
+ * $options["servers"], but that can also be set later with the set_servers()
+ * method. The servers must be an array of hosts, each of which is
+ * either a scalar of the form <10.0.0.10:11211> or an array of the
+ * former and an integer weight value. (the default weight if
+ * unspecified is 1.) It's recommended that weight values be kept as low
+ * as possible, as this module currently allocates memory for bucket
+ * distribution proportional to the total host weights.
+ * $options["debug"] turns the debugging on if set to true
+ *
+ * @access public
+ * @param array $option an array of servers and debug status
+ * @return object MemCachedClient the new MemCachedClient object
+ */
+ function MemCachedClient($options = 0)
+ {
+ if(is_array($options))
+ {
+ $this->set_servers($options["servers"]);
+ $this->debug = $options["debug"];
+ $this->cache_sock = array();
+ }
+
+ $this->errno = 0;
+ $this->errstr = "";
+ }
+
+
+ /**
+ * sets up the list of servers and the ports to connect to
+ * takes an array of servers in the same format as in the constructor
+ *
+ * @access public
+ * @param array $servers array of servers in the format described in the constructor
+ */
+ function set_servers($servers)
+ {
+ $this->servers = $servers;
+ $this->active = count($this->servers);
+ }
+
+
+ /**
+ * if $do_debug is set to true, will print out
+ * debugging info, else debug is turned off
+ *
+ * @access public
+ * @param bool $do_debug set to true to turn debugging on, false to turn off
+ */
+ function set_debug($do_debug)
+ {
+ $this->debug = $do_debug;
+ }
+
+
+ /**
+ * remove all cached hosts that are no longer good
+ *
+ * @access public
+ */
+ function forget_dead_hosts()
+ {
+ unset($this->host_dead);
+ }
+
+
+ /**
+ * disconnects from all servers
+ *
+ * @access public
+ */
+ function disconnect_all()
+ {
+ foreach($this->cache_sock as $sock)
+ socket_close($sock);
+
+ unset($this->cache_sock);
+ $this->active = 0;
+ }
+
+
+ /**
+ * removes the key from the MemCache
+ * $time is the amount of time in seconds (or Unix time) until which
+ * the client wishes the server to refuse "add" and "replace" commands
+ * with this key. For this amount of item, the item is put into a
+ * delete queue, which means that it won't possible to retrieve it by
+ * the "get" command, but "add" and "replace" command with this key
+ * will also fail (the "set" command will succeed, however). After the
+ * time passes, the item is finally deleted from server memory.
+ * The parameter $time is optional, and, if absent, defaults to 0
+ * (which means that the item will be deleted immediately and further
+ * storage commands with this key will succeed).
+ * Possible errors set are:
+ * MC_ERR_NOT_ACTIVE
+ * MC_ERR_GET_SOCK
+ * MC_ERR_SOCKET_WRITE
+ * MC_ERR_SOCKET_READ
+ * MC_ERR_DELETE
+ *
+ * @access public
+ * @param string $key the key to delete
+ * @param timestamp $time optional, the amount of time server will refuse commands on key
+ * @return bool TRUE on success, FALSE if key does not exist
+ */
+ function delete($key, $time = 0)
+ {
+ if(!$this->active)
+ {
+ $this->errno = MC_ERR_NOT_ACTIVE;
+ $this->errstr = "No active servers are available";
+
+ if($this->debug)
+ $this->_debug("delete(): There are no active servers available.");
+
+ return FALSE;
+ }
+
+ $sock = $this->get_sock($key);
+
+ if(!is_resource($sock))
+ {
+ $this->errno = MC_ERR_GET_SOCK;
+ $this->errstr = "Unable to retrieve a valid socket.";
+
+ if($this->debug)
+ $this->_debug("delete(): get_sock() returned an invalid socket.");
+
+ return FALSE;
+ }
+
+ if(is_array($key))
+ $key = $key[1];
+
+ $cmd = "delete $key $time\r\n";
+ $cmd_len = strlen($cmd);
+ $offset = 0;
+
+ // now send the command
+ while($offset < $cmd_len)
+ {
+ $result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ), MC_BUFFER_SZ);
+
+ if($result !== FALSE)
+ $offset += $result;
+ else if($offset < $cmd_len)
+ {
+ $this->errno = MC_ERR_SOCKET_WRITE;
+ $this->errstr = "Failed to write to socket.";
+
+ if($this->debug)
+ {
+ $sockerr = socket_last_error($sock);
+ $this->_debug("delete(): socket_write() returned FALSE. Socket Error $sockerr: ".socket_strerror($sockerr));
+ }
+
+ return FALSE;
+ }
+ }
+
+ // now read the server's response
+ if(($retval = socket_read($sock, MC_BUFFER_SZ, PHP_NORMAL_READ)) === FALSE)
+ {
+ $this->errno = MC_ERR_SOCKET_READ;
+ $this->errstr = "Failed to read from socket.";
+
+ if($this->debug)
+ {
+ $sockerr = socket_last_error($sock);
+ $this->_debug("delete(): socket_read() returned FALSE. Socket Error $sockerr: ".socket_strerror($sockerr));
+ }
+
+ return FALSE;
+ }
+
+ // remove the \r\n from the end
+ $retval = rtrim($retval);
+
+ // now read the server's response
+ if($retval == "DELETED")
+ return TRUE;
+ else
+ {
+ // something went wrong, create the error
+ $this->errno = MC_ERR_DELETE;
+ $this->errstr = "Failed to receive DELETED response from server.";
+
+ if($this->debug)
+ $this->_debug("delete(): Failed to receive DELETED response from server. Received $retval instead.");
+
+ return FALSE;
+ }
+ }
+
+
+ /**
+ * Like set(), but only stores in memcache if the key doesn't already exist.
+ * Possible errors set are:
+ * MC_ERR_NOT_ACTIVE
+ * MC_ERR_GET_SOCK
+ * MC_ERR_SOCKET_WRITE
+ * MC_ERR_SOCKET_READ
+ * MC_ERR_SET
+ *
+ * @access public
+ * @param string $key the key to set
+ * @param mixed $val the value of the key
+ * @param timestamp $exptime optional, the to to live of the key
+ * @return bool TRUE on success, else FALSE
+ */
+ function add($key, $val, $exptime = 0)
+ {
+ return $this->_set("add", $key, $val, $exptime);
+ }
+
+
+ /**
+ * Like set(), but only stores in memcache if the key already exists.
+ * returns TRUE on success else FALSE
+ * Possible errors set are:
+ * MC_ERR_NOT_ACTIVE
+ * MC_ERR_GET_SOCK
+ * MC_ERR_SOCKET_WRITE
+ * MC_ERR_SOCKET_READ
+ * MC_ERR_SET
+ *
+ * @access public
+ * @param string $key the key to set
+ * @param mixed $val the value of the key
+ * @param timestamp $exptime optional, the to to live of the key
+ * @return bool TRUE on success, else FALSE
+ */
+ function replace($key, $val, $exptime = 0)
+ {
+ return $this->_set("replace", $key, $val, $exptime);
+ }
+
+
+ /**
+ * Unconditionally sets a key to a given value in the memcache. Returns true
+ * if it was stored successfully.
+ * The $key can optionally be an arrayref, with the first element being the
+ * hash value, as described above.
+ * Possible errors set are:
+ * MC_ERR_NOT_ACTIVE
+ * MC_ERR_GET_SOCK
+ * MC_ERR_SOCKET_WRITE
+ * MC_ERR_SOCKET_READ
+ * MC_ERR_SET
+ *
+ * @access public
+ * @param string $key the key to set
+ * @param mixed $val the value of the key
+ * @param timestamp $exptime optional, the to to live of the key
+ * @return bool TRUE on success, else FALSE
+ */
+ function set($key, $val, $exptime = 0)
+ {
+ return $this->_set("set", $key, $val, $exptime);
+ }
+
+
+ /**
+ * Retrieves a key from the memcache. Returns the value (automatically
+ * unserialized, if necessary) or FALSE if it fails.
+ * The $key can optionally be an array, with the first element being the
+ * hash value, if you want to avoid making this module calculate a hash
+ * value. You may prefer, for example, to keep all of a given user's
+ * objects on the same memcache server, so you could use the user's
+ * unique id as the hash value.
+ * Possible errors set are:
+ * MC_ERR_GET_KEY
+ *
+ * @access public
+ * @param string $key the key to retrieve
+ * @return mixed the value of the key, FALSE on error
+ */
+ function get($key)
+ {
+ $val =& $this->get_multi($key);
+
+ if(!$val)
+ {
+ $this->errno = MC_ERR_GET_KEY;
+ $this->errstr = "No value found for key $key";
+
+ if($this->debug)
+ $this->_debug("get(): No value found for key $key");
+
+ return FALSE;
+ }
+
+ return $val[$key];
+ }
+
+
+ /**
+ * just like get(), but takes an array of keys, returns FALSE on error
+ * Possible errors set are:
+ * MC_ERR_NOT_ACTIVE
+ *
+ * @access public
+ * @param array $keys the keys to retrieve
+ * @return array the value of each key, FALSE on error
+ */
+ function get_multi($keys)
+ {
+ $sock_keys = array();
+ $socks = array();
+ $val = 0;
+
+ if(!$this->active)
+ {
+ $this->errno = MC_ERR_NOT_ACTIVE;
+ $this->errstr = "No active servers are available";
+
+ if($this->debug)
+ $this->_debug("get_multi(): There are no active servers available.");
+
+ return FALSE;
+ }
+
+ if(!is_array($keys))
+ {
+ $arr[] = $keys;
+ $keys = $arr;
+ }
+
+ foreach($keys as $k)
+ {
+ $sock = $this->get_sock($k);
+
+ if($sock)
+ {
+ $k = is_array($k) ? $k[1] : $k;
+
+ if(@!is_array($sock_keys[$sock]))
+ $sock_keys[$sock] = array();
+
+ // if $sock_keys[$sock] doesn't exist, create it
+ if(!$sock_keys[$sock])
+ $socks[] = $sock;
+
+ $sock_keys[$sock][] = $k;
+ }
+ }
+
+ if(!is_array($socks))
+ {
+ $arr[] = $socks;
+ $socks = $arr;
+ }
+
+ foreach($socks as $s)
+ {
+ $this->_load_items($s, $val, $sock_keys[$sock]);
+ }
+
+ if($this->debug)
+ {
+ while(list($k, $v) = @each($val))
+ $this->_debug("MemCache: got $k = $v\n");
+ }
+
+ return $val;
+ }
+
+
+ /**
+ * Sends a command to the server to atomically increment the value for
+ * $key by $value, or by 1 if $value is undefined. Returns FALSE if $key
+ * doesn't exist on server, otherwise it returns the new value after
+ * incrementing. Value should be zero or greater. Overflow on server
+ * is not checked. Be aware of values approaching 2**32. See decr.
+ * ONLY WORKS WITH NUMERIC VALUES
+ * Possible errors set are:
+ * MC_ERR_NOT_ACTIVE
+ * MC_ERR_GET_SOCK
+ * MC_ERR_SOCKET_WRITE
+ * MC_ERR_SOCKET_READ
+ *
+ * @access public
+ * @param string $key the keys to increment
+ * @param int $value the amount to increment the key bye
+ * @return int the new value of the key, else FALSE
+ */
+ function incr($key, $value = 1)
+ {
+ return $this->_incrdecr("incr", $key, $value);
+ }
+
+
+ /**
+ * Like incr, but decrements. Unlike incr, underflow is checked and new
+ * values are capped at 0. If server value is 1, a decrement of 2
+ * returns 0, not -1.
+ * ONLY WORKS WITH NUMERIC VALUES
+ * Possible errors set are:
+ * MC_ERR_NOT_ACTIVE
+ * MC_ERR_GET_SOCK
+ * MC_ERR_SOCKET_WRITE
+ * MC_ERR_SOCKET_READ
+ *
+ * @access public
+ * @param string $key the keys to increment
+ * @param int $value the amount to increment the key bye
+ * @return int the new value of the key, else FALSE
+ */
+ function decr($key, $value = 1)
+ {
+ return $this->_incrdecr("decr", $key, $value);
+ }
+
+
+ /**
+ * When a function returns FALSE, an error code is set.
+ * This funtion will return the error code.
+ * See error_string()
+ *
+ * @access public
+ * @return int the value of the last error code
+ */
+ function error()
+ {
+ return $this->errno;
+ }
+
+
+ /**
+ * Returns a string describing the error set in error()
+ * See error()
+ *
+ * @access public
+ * @return int a string describing the error code given
+ */
+ function error_string()
+ {
+ return $this->errstr;
+ }
+
+
+ /**
+ * Resets the error number and error string
+ *
+ * @access public
+ */
+ function error_clear()
+ {
+ // reset to no error
+ $this->errno = 0;
+ $this->errstr = "";
+ }
+
+
+ /*
+ * PRIVATE FUNCTIONS
+ */
+
+
+ /**
+ * connects to a server
+ * The $host may either a string int the form of host:port or an array of the
+ * former and an integer weight value. (the default weight if
+ * unspecified is 1.) See the constructor for details
+ * Possible errors set are:
+ * MC_ERR_HOST_FORMAT
+ * MC_ERR_HOST_DEAD
+ * MC_ERR_SOCKET_CONNECT
+ *
+ * @access private
+ * @param mixed $host either an array or a string
+ * @return resource the socket of the new connection, else FALSE
+ */
+ function sock_to_host($host)
+ {
+ if(is_array($host))
+ $host = array_shift($host);
+
+ $now = time();
+
+ // seperate the ip from the port, index 0 = ip, index 1 = port
+ $conn = explode(":", $host);
+ if(count($conn) != 2)
+ {
+ $this->errno = MC_ERR_HOST_FORMAT;
+ $this->errstr = "Host address was not in the format of host:port";
+
+ if($this->debug)
+ $this->_debug("sock_to_host(): Host address was not in the format of host:port");
+
+ return FALSE;
+ }
+
+ if(@($this->host_dead[$host] && $this->host_dead[$host] > $now) ||
+ @($this->host_dead[$conn[0]] && $this->host_dead[$conn[0]] > $now))
+ {
+ $this->errno = MC_ERR_HOST_DEAD;
+ $this->errstr = "Host $host is not available.";
+
+ if($this->debug)
+ $this->_debug("sock_to_host(): Host $host is not available.");
+
+ return FALSE;
+ }
+
+ // connect to the server, if it fails, add it to the host_dead below
+ $sock = socket_create (AF_INET, SOCK_STREAM, getprotobyname("TCP"));
+
+ // we need surpress the error message if a connection fails
+ if(!@socket_connect($sock, $conn[0], $conn[1]))
+ {
+ $this->host_dead[$host]=$this->host_dead[$conn[0]]=$now+60+intval(rand(0, 10));
+
+ $this->errno = MC_ERR_SOCKET_CONNECT;
+ $this->errstr = "Failed to connect to ".$conn[0].":".$conn[1];
+
+ if($this->debug)
+ $this->_debug("sock_to_host(): Failed to connect to ".$conn[0].":".$conn[1]);
+
+ return FALSE;
+ }
+
+ // success, add to the list of sockets
+ $cache_sock[$host] = $sock;
+
+ return $sock;
+ }
+
+
+ /**
+ * retrieves the socket associated with a key
+ * Possible errors set are:
+ * MC_ERR_NOT_ACTIVE
+ * MC_ERR_GET_SOCK
+ *
+ * @access private
+ * @param string $key the key to retrieve the socket from
+ * @return resource the socket of the connection, else FALSE
+ */
+ function get_sock($key)
+ {
+ $buckets = 0;
+
+ if(!$this->active)
+ {
+ $this->errno = MC_ERR_NOT_ACTIVE;
+ $this->errstr = "No active servers are available";
+
+ if($this->debug)
+ $this->_debug("get_sock(): There are no active servers available.");
+
+ return FALSE;
+ }
+
+ $hv = is_array($key) ? intval($key[0]) : $this->_hashfunc($key);
+
+ if(!$buckets)
+ {
+ $bu = $buckets = array();
+
+ foreach($this->servers as $v)
+ {
+ if(is_array($v))
+ {
+ for($i = 1; $i <= $v[1]; ++$i)
+ $bu[] = $v[0];
+ }
+ else
+ $bu[] = $v;
+ }
+
+ $buckets = $bu;
+ }
+
+ $real_key = is_array($key) ? $key[1] : $key;
+ $tries = 0;
+ while($tries < 20)
+ {
+ $host = @$buckets[$hv % count($buckets)];
+ $sock = $this->sock_to_host($host);
+
+ if(is_resource($sock))
+ return $sock;
+
+ $hv += $this->_hashfunc($tries.$real_key);
+ ++$tries;
+ }
+
+ $this->errno = MC_ERR_GET_SOCK;
+ $this->errstr = "Unable to retrieve a valid socket.";
+
+ if($this->debug)
+ $this->_debug("get_sock(): Unable to retrieve a valid socket.");
+
+ return FALSE;
+ }
+
+
+ /**
+ * increments or decrements a numerical value in memcached. this function is
+ * called from incr() and decr()
+ * ONLY WORKS WITH NUMERIC VALUES
+ * Possible errors set are:
+ * MC_ERR_NOT_ACTIVE
+ * MC_ERR_GET_SOCK
+ * MC_ERR_SOCKET_WRITE
+ * MC_ERR_SOCKET_READ
+ *
+ * @access private
+ * @param string $cmdname the command to send, either incr or decr
+ * @param string $key the key to perform the command on
+ * @param mixed $value the value to incr or decr the key value by
+ * @return int the new value of the key, FALSE if something went wrong
+ */
+ function _incrdecr($cmdname, $key, $value)
+ {
+ if(!$this->active)
+ {
+ $this->errno = MC_ERR_NOT_ACTIVE;
+ $this->errstr = "No active servers are available";
+
+ if($this->debug)
+ $this->_debug("_incrdecr(): There are no active servers available.");
+
+ return FALSE;
+ }
+
+ $sock = $this->get_sock($key);
+ if(!is_resource($sock))
+ {
+ $this->errno = MC_ERR_GET_SOCK;
+ $this->errstr = "Unable to retrieve a valid socket.";
+
+ if($this->debug)
+ $this->_debug("_incrdecr(): Invalid socket returned by get_sock().");
+
+ return FALSE;
+ }
+
+ if($value == "")
+ $value = 1;
+
+ $cmd = "$cmdname $key $value\r\n";
+ $cmd_len = strlen($cmd);
+ $offset = 0;
+
+ // write the command to the server
+ while($offset < $cmd_len)
+ {
+ $result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ), MC_BUFFER_SZ);
+
+ if($result !== FALSE)
+ $offset += $result;
+ else if($offset < $cmd_len)
+ {
+ $this->errno = MC_ERR_SOCKET_WRITE;
+ $this->errstr = "Failed to write to socket.";
+
+ if($this->debug)
+ {
+ $sockerr = socket_last_error($sock);
+ $this->_debug("_incrdecr(): socket_write() returned FALSE. Error $errno: ".socket_strerror($sockerr));
+ }
+
+ return FALSE;
+ }
+ }
+
+ // now read the server's response
+ if(($retval = socket_read($sock, MC_BUFFER_SZ, PHP_NORMAL_READ)) === FALSE)
+ {
+ $this->errno = MC_ERR_SOCKET_READ;
+ $this->errstr = "Failed to read from socket.";
+
+ if($this->debug)
+ {
+ $sockerr = socket_last_error($sock);
+ $this->_debug("_incrdecr(): socket_read() returned FALSE. Socket Error $errno: ".socket_strerror($sockerr));
+ }
+
+ return FALSE;
+ }
+
+ // strip the /r/n from the end and return value
+ return trim($retval);
+ }
+
+
+ /**
+ * sends the command to the server
+ * Possible errors set are:
+ * MC_ERR_NOT_ACTIVE
+ * MC_ERR_GET_SOCK
+ * MC_ERR_SOCKET_WRITE
+ * MC_ERR_SOCKET_READ
+ * MC_ERR_SET
+ *
+ * @access private
+ * @param string $cmdname the command to send, either incr or decr
+ * @param string $key the key to perform the command on
+ * @param mixed $value the value to set the key to
+ * @param timestamp $exptime expiration time of the key
+ * @return bool TRUE on success, else FALSE
+ */
+ function _set($cmdname, $key, $val, $exptime = 0)
+ {
+ if(!$this->active)
+ {
+ $this->errno = MC_ERR_NOT_ACTIVE;
+ $this->errstr = "No active servers are available";
+
+ if($this->debug)
+ $this->_debug("_set(): No active servers are available.");
+
+ return FALSE;
+ }
+
+ $sock = $this->get_sock($key);
+ if(!is_resource($sock))
+ {
+ $this->errno = MC_ERR_GET_SOCK;
+ $this->errstr = "Unable to retrieve a valid socket.";
+
+ if($this->debug)
+ $this->_debug("_set(): Invalid socket returned by get_sock().");
+
+ return FALSE;
+ }
+
+ $flags = 0;
+ $key = is_array($key) ? $key[1] : $key;
+
+ $raw_val = $val;
+
+ // if the value is not scalar, we need to serialize it
+ if(!is_scalar($val))
+ {
+ $val = serialize($val);
+ $flags |= 1;
+ }
+
+ $len = strlen($val);
+ if (!is_int($exptime))
+ $exptime = 0;
+
+ // send off the request
+ $cmd = "$cmdname $key $flags $exptime $len\r\n$val\r\n";
+ $cmd_len = strlen($cmd);
+ $offset = 0;
+
+ // write the command to the server
+ while($offset < $cmd_len)
+ {
+ $result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ), MC_BUFFER_SZ);
+
+ if($result !== FALSE)
+ $offset += $result;
+ else if($offset < $cmd_len)
+ {
+ $this->errno = MC_ERR_SOCKET_WRITE;
+ $this->errstr = "Failed to write to socket.";
+
+ if($this->debug)
+ {
+ $errno = socket_last_error($sock);
+ $this->_debug("_set(): socket_write() returned FALSE. Error $errno: ".socket_strerror($errno));
+ }
+
+ return FALSE;
+ }
+ }
+
+ // now read the server's response
+ if(($l_szResponse = socket_read($sock, 6, PHP_NORMAL_READ)) === FALSE)
+ {
+ $this->errno = MC_ERR_SOCKET_READ;
+ $this->errstr = "Failed to read from socket.";
+
+ if($this->debug)
+ {
+ $errno = socket_last_error($sock);
+ $this->_debug("_set(): socket_read() returned FALSE. Error $errno: ".socket_strerror($errno));
+ }
+
+ return FALSE;
+ }
+
+ if($l_szResponse == "STORED")
+ {
+ if($this->debug)
+ $this->_debug("MemCache: $cmdname $key = $raw_val");
+
+ return TRUE;
+ }
+
+ $this->errno = MC_ERR_SET;
+ $this->errstr = "Failed to receive the STORED response from the server.";
+
+ if($this->debug)
+ $this->_debug("_set(): Did not receive STORED as the server response! Received $l_szResponse instead.");
+
+ return FALSE;
+ }
+
+
+ /**
+ * retrieves the value, and returns it unserialized
+ * Possible errors set are:
+ * MC_ERR_SOCKET_WRITE
+ * MC_ERR_SOCKET_READ
+ * MC_ERR_GET_KEY
+ * MC_ERR_LOADITEM_END
+ * MC_ERR_LOADITEM_BYTES
+ *
+ * @access private
+ * @param resource $sock the socket to connection we are retriving from
+ * @param array $val reference to the values retrieved
+ * @param mixed $sock_keys either a string or an array of keys to retrieve
+ * @return array TRUE on success, else FALSE
+ */
+ function _load_items($sock, &$val, $sock_keys)
+ {
+ $val = array();
+ $cmd = "get ";
+
+ if(!is_array($sock_keys))
+ {
+ $arr[] = $sock_keys;
+ $sock_keys = $arr;
+ }
+
+ foreach($sock_keys as $sk)
+ $cmd .= $sk." ";
+
+ $cmd .="\r\n";
+ $cmd_len = strlen($cmd);
+ $offset = 0;
+
+ // write the command to the server
+ while($offset < $cmd_len)
+ {
+ $result = socket_write($sock, substr($cmd, $offset, MC_BUFFER_SZ), MC_BUFFER_SZ);
+
+ if($result !== FALSE)
+ $offset += $result;
+ else if($offset < $cmd_len)
+ {
+ $this->errno = MC_ERR_SOCKET_WRITE;
+ $this->errstr = "Failed to write to socket.";
+
+ if($this->debug)
+ {
+ $errno = socket_last_error($sock);
+ $this->_debug("_load_items(): socket_write() returned FALSE. Error $errno: ".socket_strerror($errno));
+ }
+
+ return FALSE;
+ }
+ }
+
+ $len = 0;
+ $buf = "";
+ $flags_array = array();
+
+ // now read the response from the server
+ while($line = socket_read($sock, MC_BUFFER_SZ, PHP_BINARY_READ))
+ {
+ // check for a socket_read error
+ if($line === FALSE)
+ {
+ $this->errno = MC_ERR_SOCKET_READ;
+ $this->errstr = "Failed to read from socket.";
+
+ if($this->debug)
+ {
+ $errno = socket_last_error($sock);
+ $this->_debug("_load_items(): socket_read() returned FALSE. Error $errno: ".socket_strerror($errno));
+ }
+
+ return FALSE;
+ }
+
+ if($len == 0)
+ {
+ $header = substr($line, 0, strpos($line, "\r\n"));
+ $matches = explode(" ", $header);
+
+ if(is_string($matches[1]) && is_numeric($matches[2]) && is_numeric($matches[3]))
+ {
+ $rk = $matches[1];
+ $flags = $matches[2];
+ $len = $matches[3];
+
+ if($flags)
+ $flags_array[$rk] = $flags;
+
+ $len_array[$rk] = $len;
+ $bytes_read = 0;
+
+ // get the left over data after the header is read
+ $line = substr($line, strpos($line, "\r\n")+1, strlen($line));
+ }
+ else
+ {
+ $this->errno = MC_ERR_GET_KEY;
+ $this->errstr = "Requested key(s) returned no values.";
+
+ // something went wrong, we never recieved the header
+ if($this->debug)
+ $this->_debug("_load_items(): Requested key(s) returned no values.");
+
+ return FALSE;
+ }
+ }
+
+ // skip over the extra return or newline
+ if($line == "\r" || $line == "\n")
+ continue;
+
+ $bytes_read += strlen($line);
+ $buf .= $line;
+
+ // we read the all of the data, take in account
+ // for the /r/nEND/r/n
+ if($bytes_read == ($len + 7))
+ {
+ $end = substr($buf, $len+2, 3);
+ if($end == "END")
+ {
+ $val[$rk] = substr($buf, 0, $len);
+
+ foreach($sock_keys as $sk)
+ {
+ if(!isset($val[$sk]))
+ continue;
+
+ if(strlen($val[$sk]) != $len_array[$sk])
+ continue;
+
+ if(@$flags_array[$sk] & 1)
+ $val[$sk] = unserialize($val[$sk]);
+ }
+
+ return TRUE;
+ }
+ else
+ {
+ $this->errno = MC_ERR_LOADITEM_END;
+ $this->errstr = "Failed to receive END response from server.";
+
+ if($this->debug)
+ $this->_debug("_load_items(): Failed to receive END. Received $end instead.");
+
+ return FALSE;
+ }
+ }
+
+ // take in consideration for the "\r\nEND\r\n"
+ if($bytes_read > ($len + 7))
+ {
+ $this->errno = MC_ERR_LOADITEM_BYTES;
+ $this->errstr = "Bytes read from server greater than size of data.";
+
+ if($this->debug)
+ $this->_debug("_load_items(): Bytes read is greater than requested data size.");
+
+ return FALSE;
+ }
+
+ }
+ }
+
+
+ /**
+ * creates our hash
+ *
+ * @access private
+ * @param int $num
+ * @return hash
+ */
+ function _hashfunc($num)
+ {
+ $hash = 0;
+
+ foreach(preg_split('//', $num, -1, PREG_SPLIT_NO_EMPTY) as $v)
+ {
+ $hash = $hash * 33 + ord($v);
+ }
+
+ return $hash;
+ }
+
+ /**
+ * function that can be overridden to handle debug output
+ * by default debug info is print to the screen
+ *
+ * @access private
+ * @param $text string to output debug info
+ */
+ function _debug($text)
+ {
+ print $text . "\r\n";
+ }
+}
+
+?> \ No newline at end of file
diff --git a/includes/Setup.php b/includes/Setup.php
index 7b0c64670cb2..fd2a0f85c79a 100644
--- a/includes/Setup.php
+++ b/includes/Setup.php
@@ -17,9 +17,25 @@ include_once( "$IP/User.php" );
include_once( "$IP/LinkCache.php" );
include_once( "$IP/Title.php" );
include_once( "$IP/Article.php" );
+require( "$IP/MemCachedClient.inc.php" );
+
+wfDebug( "\n\n" );
global $wgUser, $wgLang, $wgOut, $wgTitle;
global $wgArticle, $wgDeferredUpdateList, $wgLinkCache;
+global $wgMemc;
+
+class MemCachedClientforWiki extends MemCachedClient {
+ function _debug( $text ) {
+ wfDebug( "memcached: $text\n" );
+ }
+}
+
+$wgMemc = new MemCachedClientforWiki();
+if( $wgUseMemCached ) {
+ $wgMemc->set_servers( $wgMemCachedServers );
+ $wgMemc->set_debug( $wgMemcachedDebug );
+}
$wgOut = new OutputPage();
$wgLangClass = "Language" . ucfirst( $wgLanguageCode );
@@ -29,8 +45,7 @@ if( ! class_exists( $wgLangClass ) ) {
}
$wgLang = new $wgLangClass();
-$wgUser = new User();
-$wgUser->loadFromSession();
+$wgUser = User::loadFromSession();
$wgDeferredUpdateList = array();
$wgLinkCache = new LinkCache();
diff --git a/includes/User.php b/includes/User.php
index 8306392a7199..ffef5355727b 100644
--- a/includes/User.php
+++ b/includes/User.php
@@ -129,23 +129,22 @@ class User {
return $this->mBlockreason;
}
- function loadFromSession()
+ /* static */ function loadFromSession()
{
global $HTTP_COOKIE_VARS, $wsUserID, $wsUserName, $wsUserPassword;
+ global $wgMemc, $wgDBname;
if ( isset( $wsUserID ) ) {
if ( 0 != $wsUserID ) {
$sId = $wsUserID;
} else {
- $this->mId = 0;
- return;
+ return new User();
}
} else if ( isset( $HTTP_COOKIE_VARS["wcUserID"] ) ) {
$sId = $HTTP_COOKIE_VARS["wcUserID"];
$wsUserID = $sId;
} else {
- $this->mId = 0;
- return;
+ return new User();
}
if ( isset( $wsUserName ) ) {
$sName = $wsUserName;
@@ -153,30 +152,40 @@ class User {
$sName = $HTTP_COOKIE_VARS["wcUserName"];
$wsUserName = $sName;
} else {
- $this->mId = 0;
- return;
+ return new User();
}
$passwordCorrect = FALSE;
- $this->mId = $sId;
- $this->loadFromDatabase();
+ $user = $wgMemc->get( $key = "$wgDBname:user:user_id:$sId" );
+ if($makenew = !$user) {
+ wfDebug( "User::loadFromSession() unable to load from memcached\n" );
+ $user = new User();
+ $user->mId = $sId;
+ $user->loadFromDatabase();
+ } else {
+ wfDebug( "User::loadFromSession() got from cache!\n" );
+ }
if ( isset( $wsUserPassword ) ) {
- $passwordCorrect = $wsUserPassword == $this->mPassword;
+ $passwordCorrect = $wsUserPassword == $user->mPassword;
} else if ( isset( $HTTP_COOKIE_VARS["wcUserPassword"] ) ) {
- $this->mCookiePassword = $HTTP_COOKIE_VARS["wcUserPassword"];
- $wsUserPassword = $this->addSalt( $this->mCookiePassword );
- $passwordCorrect = $wsUserPassword == $this->mPassword;
+ $user->mCookiePassword = $HTTP_COOKIE_VARS["wcUserPassword"];
+ $wsUserPassword = $user->addSalt( $user->mCookiePassword );
+ $passwordCorrect = $wsUserPassword == $user->mPassword;
} else {
- $this->mId = 0;
- $this->loadDefaults(); # Can't log in from session
- return;
+ return new User(); # Can't log in from session
}
- if ( ( $sName == $this->mName ) && $passwordCorrect ) {
- return;
+ if ( ( $sName == $user->mName ) && $passwordCorrect ) {
+ if($makenew) {
+ if($wgMemc->set( $key, $user ))
+ wfDebug( "User::loadFromSession() successfully saved user\n" );
+ else
+ wfDebug( "User::loadFromSession() unable to save to memcached\n" );
+ }
+ return $user;
}
- $this->loadDefaults(); # Can't log in from session
+ return new User(); # Can't log in from session
}
function loadFromDatabase()
@@ -491,7 +500,7 @@ class User {
function saveSettings()
{
- global $wgUser;
+ global $wgMemc, $wgDBname;
if ( ! $this->mNewtalk ) {
if( $this->mId ) {
@@ -515,6 +524,8 @@ class User {
"user_touched= '" . wfStrencode( $this->mTouched ) .
"' WHERE user_id={$this->mId}";
wfQuery( $sql, "User::saveSettings" );
+ #$wgMemc->replace( "$wgDBname:user:user_id:$this->mId", $this );
+ $wgMemc->delete( "$wgDBname:user:user_id:$this->mId" );
}
# Checks if a user with the given name exists
diff --git a/includes/UserTalkUpdate.php b/includes/UserTalkUpdate.php
index 011a7ba71118..777f5f4c81d2 100644
--- a/includes/UserTalkUpdate.php
+++ b/includes/UserTalkUpdate.php
@@ -15,7 +15,7 @@ class UserTalkUpdate {
function doUpdate()
{
- global $wgUser, $wgLang;
+ global $wgUser, $wgLang, $wgMemc, $wgDBname;
$fname = "UserTalkUpdate::doUpdate";
# If namespace isn't User_talk:, do nothing.
@@ -27,7 +27,6 @@ class UserTalkUpdate {
# If the user talk page is our own, clear the flag
# whether we are reading it or writing it.
if ( 0 == strcmp( $this->mTitle, $wgUser->getName() ) ) {
-
$wgUser->setNewtalk( 0 );
$wgUser->saveSettings();
@@ -39,13 +38,12 @@ class UserTalkUpdate {
$user->setID(User::idFromName($this->mTitle));
if ($id=$user->getID()) {
$sql = "INSERT INTO user_newtalk (user_id) values ({$id})";
-
+ $wgMemc->delete( "$wgDBname:user:user_id:$id" );
} else { #anon
if(preg_match("/^\d{1,3}\.\d{1,3}.\d{1,3}\.\d{1,3}$/",$this->mTitle)) { #real anon (user:xxx.xxx.xxx.xxx)
$sql = "INSERT INTO user_newtalk (user_id,user_ip) values (0,\"{$this->mTitle}\")";
-
}
}