From 5bfdd6e328a930abf426628998f82f8fdca2cee8 Mon Sep 17 00:00:00 2001 From: Tim Almdal Date: Sun, 28 Jun 2009 14:24:23 +0800 Subject: Implemented a Database driver for the Kohana Cache library. Rather then writing our own caching algorithm, we can leverage the Kohana library. This has the added advantage of allowing the administrator to replace the default caching with a 3rd party caching algorithm. Signed-off-by: Tim Almdal --- modules/gallery/models/cache.php | 20 ++++ modules/gallery/tests/Cache_Test.php | 178 +++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 modules/gallery/models/cache.php create mode 100644 modules/gallery/tests/Cache_Test.php diff --git a/modules/gallery/models/cache.php b/modules/gallery/models/cache.php new file mode 100644 index 00000000..99e19a6e --- /dev/null +++ b/modules/gallery/models/cache.php @@ -0,0 +1,20 @@ +from("caches")->where(1)->delete(); + $this->_driver = new Cache_Database_Driver(); + } + + public function cache_exists_test() { + $db = Database::instance(); + + $this->assert_false($this->_driver->exists("test_key"), "test_key should not be defined"); + + $id = md5(rand()); + $db->insert("caches", array("id" => $id, "tags" => ", ", + "expiration" => 84600 + time(), + "cache" => serialize("some test data"))); + + $this->assert_true($this->_driver->exists($id), "test_key should be defined"); + } + + public function cache_get_test() { + $db = Database::instance(); + + $id = md5(rand()); + $db->insert("caches", array("id" => $id, "tags" => ", ", + "expiration" => 84600 + time(), + "cache" => serialize("some test data"))); + + $data = $this->_driver->get($id); + $this->assert_equal("some test data", $data, "cached data should match"); + + $data = $this->_driver->get(""); + $this->assert_equal(null, $data, "cached data should not be found"); + } + + public function cache_set_test() { + $db = Database::instance(); + + $id = md5(rand()); + $original_data = array("field1" => "value1", "field2" => "value2"); + $this->_driver->set($id, $original_data, array("tag1", "tag2"), 84600); + + $data = $this->_driver->get($id); + $this->assert_equal($original_data, $data, "cached data should match"); + } + + public function cache_find_test() { + $db = Database::instance(); + + $id1 = md5(rand()); + $value1 = array("field1" => "value1", "field2" => "value2"); + $this->_driver->set($id1, $value1, array("tag1", "tag2"), 84600); + + $id2 = md5(rand()); + $value2 = array("field3" => "value3", "field4" => "value4"); + $this->_driver->set($id2, $value2, array("tag2", "tag3"), 84600); + + $id3 = md5(rand()); + $value3 = array("field5" => "value5", "field6" => "value6"); + $this->_driver->set($id3, $value3, array("tag3", "tag4"), 84600); + + $data = $this->_driver->find("tag2"); + + $expected = array($id1 => $value1, $id2 => $value2); + ksort($expected); + $this->assert_equal($expected, $data, "Expected id1 & id2"); + + $data = $this->_driver->find("tag4"); + $this->assert_equal(array($id3 => $value3), $data, "Expected id3"); + } + + public function cache_delete_expired_test() { + $db = Database::instance(); + + $id1 = md5(rand()); + $value1 = array("field1" => "value1", "field2" => "value2"); + $this->_driver->set($id1, $value1, array("tag1", "tag2"), -84600); + + $id2 = md5(rand()); + $value2 = array("field3" => "value3", "field4" => "value4"); + $this->_driver->set($id2, $value2, array("tag2", "tag3"), -846000); + + $id3 = md5(rand()); + $value3 = array("field5" => "value5", "field6" => "value6"); + $this->_driver->set($id3, $value3, array("tag3", "tag4"), -84600); + + $data = $this->_driver->delete_expired(); + + $this->assert_false($this->_driver->exists($id1), "$id1 should have been deleted"); + $this->assert_false($this->_driver->exists($id2), "$id2 should have been deleted"); + $this->assert_false($this->_driver->exists($id3), "$id3 should have been deleted"); + } + + public function cache_delete_id_test() { + $db = Database::instance(); + + $id1 = md5(rand()); + $value1 = array("field1" => "value1", "field2" => "value2"); + $this->_driver->set($id1, $value1, array("tag1", "tag2"), 84600); + + $id2 = md5(rand()); + $value2 = array("field3" => "value3", "field4" => "value4"); + $this->_driver->set($id2, $value2, array("tag2", "tag3"), 846000); + + $id3 = md5(rand()); + $value3 = array("field5" => "value5", "field6" => "value6"); + $this->_driver->set($id3, $value3, array("tag3", "tag4"), 84600); + + $this->_driver->delete($id1); + + $this->assert_false($this->_driver->exists($id1), "$id1 should have been deleted"); + $this->assert_true($this->_driver->exists($id2), "$id2 should not have been deleted"); + $this->assert_true($this->_driver->exists($id3), "$id3 should not have been deleted"); + } + + public function cache_delete_tag_test() { + $db = Database::instance(); + + $id1 = md5(rand()); + $value1 = array("field1" => "value1", "field2" => "value2"); + $this->_driver->set($id1, $value1, array("tag1", "tag2"), 84600); + + $id2 = md5(rand()); + $value2 = array("field3" => "value3", "field4" => "value4"); + $this->_driver->set($id2, $value2, array("tag2", "tag3"), 846000); + + $id3 = md5(rand()); + $value3 = array("field5" => "value5", "field6" => "value6"); + $this->_driver->set($id3, $value3, array("tag3", "tag4"), 84600); + + $data = $this->_driver->delete("tag3", true); + + $this->assert_true($this->_driver->exists($id1), "$id1 should not have been deleted"); + $this->assert_false($this->_driver->exists($id2), "$id2 should have been deleted"); + $this->assert_false($this->_driver->exists($id3), "$id3 should have been deleted"); + } + + public function cache_delete_all_test() { + $db = Database::instance(); + + $id1 = md5(rand()); + $value1 = array("field1" => "value1", "field2" => "value2"); + $this->_driver->set($id1, $value1, array("tag1", "tag2"), 84600); + + $id2 = md5(rand()); + $value2 = array("field3" => "value3", "field4" => "value4"); + $this->_driver->set($id2, $value2, array("tag2", "tag3"), 846000); + + $id3 = md5(rand()); + $value3 = array("field5" => "value5", "field6" => "value6"); + $this->_driver->set($id3, $value3, array("tag3", "tag4"), 84600); + + $data = $this->_driver->delete(true); + + $this->assert_false($this->_driver->exists($id1), "$id1 should have been deleted"); + $this->assert_false($this->_driver->exists($id2), "$id2 should have been deleted"); + $this->assert_false($this->_driver->exists($id3), "$id3 should have been deleted"); + } +} \ No newline at end of file -- cgit v1.2.3 From aa6bc97c7cc7e8cd1108ffad17c1087f9b3a26da Mon Sep 17 00:00:00 2001 From: Tim Almdal Date: Sun, 28 Jun 2009 14:30:02 +0800 Subject: Update the version number and upgrade method for gallery to reflect the addition of the cache table. Signed-off-by: Tim Almdal --- modules/gallery/helpers/gallery_installer.php | 25 ++++++++++++++++++++++++- modules/gallery/module.info | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/modules/gallery/helpers/gallery_installer.php b/modules/gallery/helpers/gallery_installer.php index df555d52..92fc662d 100644 --- a/modules/gallery/helpers/gallery_installer.php +++ b/modules/gallery/helpers/gallery_installer.php @@ -181,6 +181,15 @@ class gallery_installer { UNIQUE KEY(`module_name`, `name`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + $db->query("CREATE TABLE {caches} ( + `id` varchar(255) NOT NULL, + `tags` varchar(255), + `expiration` int(9) NOT NULL, + `cache` text, + PRIMARY KEY (`id`), + KEY (`tags`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + foreach (array("albums", "logs", "modules", "resizes", "thumbs", "tmp", "uploads") as $dir) { @mkdir(VARPATH . $dir); } @@ -249,10 +258,11 @@ class gallery_installer { module::set_var("gallery", "show_credits", 1); // @todo this string needs to be picked up by l10n_scanner module::set_var("gallery", "credits", "Powered by Gallery %version"); - module::set_version("gallery", 3); + module::set_version("gallery", 4); } static function upgrade($version) { + $db = Database::instance(); if ($version == 1) { module::set_var("gallery", "date_format", "Y-M-d"); module::set_var("gallery", "date_time_format", "Y-M-d H:i:s"); @@ -265,6 +275,18 @@ class gallery_installer { module::set_var("gallery", "show_credits", 1); module::set_version("gallery", $version = 3); } + + if ($version == 3) { + $db->query("CREATE TABLE {caches} ( + `id` varchar(255) NOT NULL, + `tags` varchar(255), + `expiration` int(9) NOT NULL, + `cache` text, + PRIMARY KEY (`id`), + KEY (`tags`)) + ENGINE=InnoDB DEFAULT CHARSET=utf8;"); + module::set_version("gallery", $version = 4); + } } static function uninstall() { @@ -282,6 +304,7 @@ class gallery_installer { $db->query("DROP TABLE IF EXISTS {tasks}"); $db->query("DROP TABLE IF EXISTS {themes}"); $db->query("DROP TABLE IF EXISTS {vars}"); + $db->query("DROP TABLE IF EXISTS {caches}"); foreach (array("albums", "resizes", "thumbs", "uploads", "modules", "logs", "database.php") as $entry) { system("/bin/rm -rf " . VARPATH . $entry); diff --git a/modules/gallery/module.info b/modules/gallery/module.info index e5b1f809..1a44ce51 100644 --- a/modules/gallery/module.info +++ b/modules/gallery/module.info @@ -1,3 +1,3 @@ name = Gallery 3 description = Gallery core application -version = 3 +version = 4 -- cgit v1.2.3 From 067e9f8ef7c7ed5763a5290b0e8d2f4a48123a86 Mon Sep 17 00:00:00 2001 From: Tim Almdal Date: Sun, 28 Jun 2009 14:34:07 +0800 Subject: The rest of the caching driver implementation that i somehow forgot. Signed-off-by: Tim Almdal --- modules/gallery/config/cache.php | 32 ++++ .../gallery/libraries/drivers/Cache/Database.php | 181 +++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 modules/gallery/config/cache.php create mode 100644 modules/gallery/libraries/drivers/Cache/Database.php diff --git a/modules/gallery/config/cache.php b/modules/gallery/config/cache.php new file mode 100644 index 00000000..5f2cd6de --- /dev/null +++ b/modules/gallery/config/cache.php @@ -0,0 +1,32 @@ + File cache is fast and reliable, but requires many filesystem lookups. + * > Database cache can be used to cache items remotely, but is slower. + * > Memcache is very high performance, but prevents cache tags from being used. + * + * params - Driver parameters, specific to each driver. + * + * lifetime - Default lifetime of caches in seconds. By default caches are stored for + * thirty minutes. Specific lifetime can also be set when creating a new cache. + * Setting this to 0 will never automatically delete caches. + * + * requests - Average number of cache requests that will processed before all expired + * caches are deleted. This is commonly referred to as "garbage collection". + * Setting this to 0 or a negative number will disable automatic garbage collection. + */ +$config['default'] = array +( + 'driver' => 'database', + 'params' => null, + 'lifetime' => 84600, + 'requests' => 1000 +); diff --git a/modules/gallery/libraries/drivers/Cache/Database.php b/modules/gallery/libraries/drivers/Cache/Database.php new file mode 100644 index 00000000..e008f473 --- /dev/null +++ b/modules/gallery/libraries/drivers/Cache/Database.php @@ -0,0 +1,181 @@ +db = Database::instance(); + + if (!$this->db->table_exists("caches")) { + throw new Kohana_Exception('cache.driver_error', "Cache table is not defined"); + } + Kohana::log('debug', 'Cache Database Driver Initialized'); + } + + /** + * Checks if a cache id is already set. + * + * @param string cache id + * @return boolean + */ + public function exists($id) { + $count = $this->db->count_records("caches", array('id' => $id, "expiration >=" => time())); + return $count > 0; + } + + /** + * Sets a cache item to the given data, tags, and lifetime. + * + * @param string cache id to set + * @param string data in the cache + * @param array cache tags + * @param integer lifetime + * @return bool + */ + public function set($id, $data, array $tags = NULL, $lifetime) { + if (!empty($tags)) { + // Escape the tags, adding brackets so the tag can be explicitly matched + $tags = '<' . implode('>,<', $tags) . '>'; + } + + // Cache Database driver expects unix timestamp + if ($lifetime !== 0) { + $lifetime += time(); + } + + $data = serialize($data); + if ($this->exists($id)) { + $status = $this->db->update("caches", + array("tags" => $tags, "expiration" => $lifetime, "cache" => $data), array("id" => $id)); + } else { + $status = $this->db->insert("caches", + array("id" => $id, "tags" => $tags, "expiration" => $lifetime, "cache" => $data)); + } + + return count($status) > 0; + } + + /** + * Finds an array of ids for a given tag. + * + * @param string tag name + * @return array of ids that match the tag + */ + public function find($tag) { + $db_result = $this->db->from("caches") + ->like("tags", "<$tag>") + ->get() + ->result(true); + + // An array will always be returned + $result = array(); + + if ($db_result->count() > 0) { + // Disable notices for unserializing + $ER = error_reporting(~E_NOTICE); + + foreach ($db_result as $row) { + // Add each cache to the array + $result[$row->id] = unserialize($row->cache); + } + + // Turn notices back on + error_reporting($ER); + } + + return $result; + } + + /** + * Fetches a cache item. This will delete the item if it is expired or if + * the hash does not match the stored hash. + * + * @param string cache id + * @return mixed|NULL + */ + public function get($id) { + $data = null; + $result = $this->db->getwhere("caches", array('id' => $id)); + + if (count($result) > 0) { + $cache = $result->current(); + // Make sure the expiration is valid and that the hash matches + if ($cache->expiration != 0 && $cache->expiration <= time()) { + // Cache is not valid, delete it now + $this->delete($cache->id); + } else { + // Disable notices for unserializing + $ER = error_reporting(~E_NOTICE); + + // Return the valid cache data + $data = unserialize($cache->cache); + + // Turn notices back on + error_reporting($ER); + } + } + + return $data; + } + + /** + * Deletes a cache item by id or tag + * + * @param string cache id or tag, or true for "all items" + * @param bool delete a tag + * @return bool + */ + public function delete($id, $tag = false) { + $this->db->from("caches"); + if ($id === true) { + $this->db->where(1); + // Delete all caches + } else if ($tag === true) { + $this->db->like("tags", "<$id>"); + } else { + $this->db->where("id", $id); + } + + $status = $this->db->delete(); + + return count($status) > 0; + } + + /** + * Deletes all cache files that are older than the current time. + */ + public function delete_expired() { + // Delete all expired caches + $status = $this->db->from("caches") + ->where(array("expiration !=" => 0, "expiration <=" => time())) + ->delete(); + + return count($status) > 0; + } + +} // End Cache Database Driver \ No newline at end of file -- cgit v1.2.3