From e48f8945b32ab5b67f1cdeb53a37d3d196e31e4d Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 20 May 2016 05:19:01 -0400
Subject: [PATCH] Fix bug where message list columns could be in wrong order after column drag-n-drop and list sorting
---
program/lib/Roundcube/rcube_cache.php | 199 ++++++++++++++++++++++++++++++++++---------------
1 files changed, 139 insertions(+), 60 deletions(-)
diff --git a/program/lib/Roundcube/rcube_cache.php b/program/lib/Roundcube/rcube_cache.php
index 3e1ce4f..8de5f37 100644
--- a/program/lib/Roundcube/rcube_cache.php
+++ b/program/lib/Roundcube/rcube_cache.php
@@ -2,8 +2,6 @@
/*
+-----------------------------------------------------------------------+
- | program/include/rcube_cache.php |
- | |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2011, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
@@ -14,7 +12,6 @@
| |
| PURPOSE: |
| Caching engine |
- | |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
@@ -41,12 +38,14 @@
private $type;
private $userid;
private $prefix;
+ private $table;
private $ttl;
private $packed;
private $index;
private $cache = array();
private $cache_changes = array();
private $cache_sums = array();
+ private $max_packet = -1;
/**
@@ -74,8 +73,9 @@
$this->db = function_exists('apc_exists'); // APC 3.1.4 required
}
else {
- $this->type = 'db';
- $this->db = $rcube->get_dbh();
+ $this->type = 'db';
+ $this->db = $rcube->get_dbh();
+ $this->table = $this->db->table_name('cache', true);
}
// convert ttl string to seconds
@@ -148,7 +148,7 @@
*/
function write($key, $data)
{
- return $this->write_record($key, $this->packed ? serialize($data) : $data);
+ return $this->write_record($key, $this->serialize($data));
}
@@ -164,7 +164,7 @@
// Remove all keys
if ($key === null) {
$this->cache = array();
- $this->cache_changed = false;
+ $this->cache_changed = true;
$this->cache_changes = array();
$this->cache_sums = array();
}
@@ -195,16 +195,27 @@
*/
function expunge()
{
- if ($this->type == 'db' && $this->db) {
+ if ($this->type == 'db' && $this->db && $this->ttl) {
$this->db->query(
- "DELETE FROM ".$this->db->table_name('cache').
- " WHERE user_id = ?".
- " AND cache_key LIKE ?".
- " AND " . $this->db->unixtimestamp('created')." < ?",
+ "DELETE FROM {$this->table}".
+ " WHERE `user_id` = ?".
+ " AND `cache_key` LIKE ?".
+ " AND `expires` < " . $this->db->now(),
$this->userid,
- $this->prefix.'.%',
- time() - $this->ttl);
+ $this->prefix.'.%');
}
+ }
+
+
+ /**
+ * Remove expired records of all caches
+ */
+ static function gc()
+ {
+ $rcube = rcube::get_instance();
+ $db = $rcube->get_dbh();
+
+ $db->query("DELETE FROM " . $db->table_name('cache', true) . " WHERE `expires` < " . $db->now());
}
@@ -222,7 +233,7 @@
if ($this->cache_changes[$key]) {
// Make sure we're not going to write unchanged data
// by comparing current md5 sum with the sum calculated on DB read
- $data = $this->packed ? serialize($data) : $data;
+ $data = $this->serialize($data);
if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) {
$this->write_record($key, $data);
@@ -231,6 +242,9 @@
}
$this->write_index();
+
+ // reset internal cache index, thanks to this we can force index reload
+ $this->index = null;
}
@@ -249,7 +263,15 @@
}
if ($this->type != 'db') {
- if ($this->type == 'memcache') {
+ $this->load_index();
+
+ // Consistency check (#1490390)
+ if (!in_array($key, $this->index)) {
+ // we always check if the key exist in the index
+ // to have data in consistent state. Keeping the index consistent
+ // is needed for keys delete operation when we delete all keys or by prefix.
+ }
+ else if ($this->type == 'memcache') {
$data = $this->db->get($this->ckey($key));
}
else if ($this->type == 'apc') {
@@ -258,7 +280,7 @@
if ($data) {
$md5sum = md5($data);
- $data = $this->packed ? unserialize($data) : $data;
+ $data = $this->unserialize($data);
if ($nostore) {
return $data;
@@ -273,20 +295,19 @@
}
else {
$sql_result = $this->db->limitquery(
- "SELECT data, cache_key".
- " FROM ".$this->db->table_name('cache').
- " WHERE user_id = ?".
- " AND cache_key = ?".
+ "SELECT `data`, `cache_key`".
+ " FROM {$this->table}".
+ " WHERE `user_id` = ? AND `cache_key` = ?".
// for better performance we allow more records for one key
// get the newer one
- " ORDER BY created DESC",
+ " ORDER BY `created` DESC",
0, 1, $this->userid, $this->prefix.'.'.$key);
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
$key = substr($sql_arr['cache_key'], strlen($this->prefix)+1);
$md5sum = $sql_arr['data'] ? md5($sql_arr['data']) : null;
if ($sql_arr['data']) {
- $data = $this->packed ? unserialize($sql_arr['data']) : $sql_arr['data'];
+ $data = $this->unserialize($sql_arr['data']);
}
if ($nostore) {
@@ -309,7 +330,7 @@
* Writes single cache record into DB.
*
* @param string $key Cache key name
- * @param mxied $data Serialized cache data
+ * @param mixed $data Serialized cache data
*
* @param boolean True on success, False on failure
*/
@@ -319,8 +340,22 @@
return false;
}
+ // don't attempt to write too big data sets
+ if (strlen($data) > $this->max_packet_size()) {
+ trigger_error("rcube_cache: max_packet_size ($this->max_packet) exceeded for key $key. Tried to write " . strlen($data) . " bytes", E_USER_WARNING);
+ return false;
+ }
+
if ($this->type == 'memcache' || $this->type == 'apc') {
- return $this->add_record($this->ckey($key), $data);
+ $result = $this->add_record($this->ckey($key), $data);
+
+ // make sure index will be updated
+ if ($result && !array_key_exists($key, $this->cache_sums)) {
+ $this->cache_changed = true;
+ $this->cache_sums[$key] = true;
+ }
+
+ return $result;
}
$key_exists = array_key_exists($key, $this->cache_sums);
@@ -329,9 +364,8 @@
// Remove NULL rows (here we don't need to check if the record exist)
if ($data == 'N;') {
$this->db->query(
- "DELETE FROM ".$this->db->table_name('cache').
- " WHERE user_id = ?".
- " AND cache_key = ?",
+ "DELETE FROM {$this->table}".
+ " WHERE `user_id` = ? AND `cache_key` = ?",
$this->userid, $key);
return true;
@@ -340,10 +374,12 @@
// update existing cache record
if ($key_exists) {
$result = $this->db->query(
- "UPDATE ".$this->db->table_name('cache').
- " SET created = ". $this->db->now().", data = ?".
- " WHERE user_id = ?".
- " AND cache_key = ?",
+ "UPDATE {$this->table}".
+ " SET `created` = " . $this->db->now().
+ ", `expires` = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL').
+ ", `data` = ?".
+ " WHERE `user_id` = ?".
+ " AND `cache_key` = ?",
$data, $this->userid, $key);
}
// add new cache record
@@ -351,9 +387,9 @@
// for better performance we allow more records for one key
// so, no need to check if record exist (see rcube_cache::read_record())
$result = $this->db->query(
- "INSERT INTO ".$this->db->table_name('cache').
- " (created, user_id, cache_key, data)".
- " VALUES (".$this->db->now().", ?, ?, ?)",
+ "INSERT INTO {$this->table}".
+ " (`created`, `expires`, `user_id`, `cache_key`, `data`)".
+ " VALUES (" . $this->db->now() . ", " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?, ?, ?)",
$this->userid, $key, $data);
}
@@ -367,7 +403,6 @@
* @param string $key Cache key name or pattern
* @param boolean $prefix_mode Enable it to clear all keys starting
* with prefix specified in $key
- *
*/
private function remove_record($key=null, $prefix_mode=false)
{
@@ -403,20 +438,19 @@
// Remove all keys (in specified cache)
if ($key === null) {
- $where = " AND cache_key LIKE " . $this->db->quote($this->prefix.'.%');
+ $where = " AND `cache_key` LIKE " . $this->db->quote($this->prefix.'.%');
}
// Remove keys by name prefix
else if ($prefix_mode) {
- $where = " AND cache_key LIKE " . $this->db->quote($this->prefix.'.'.$key.'%');
+ $where = " AND `cache_key` LIKE " . $this->db->quote($this->prefix.'.'.$key.'%');
}
// Remove one key by name
else {
- $where = " AND cache_key = " . $this->db->quote($this->prefix.'.'.$key);
+ $where = " AND `cache_key` = " . $this->db->quote($this->prefix.'.'.$key);
}
$this->db->query(
- "DELETE FROM ".$this->db->table_name('cache').
- " WHERE user_id = ?" . $where,
+ "DELETE FROM {$this->table} WHERE `user_id` = ?" . $where,
$this->userid);
}
@@ -424,13 +458,12 @@
/**
* Adds entry into memcache/apc DB.
*
- * @param string $key Cache key name
- * @param mxied $data Serialized cache data
- * @param bollean $index Enables immediate index update
+ * @param string $key Cache key name
+ * @param mixed $data Serialized cache data
*
* @param boolean True on success, False on failure
*/
- private function add_record($key, $data, $index=false)
+ private function add_record($key, $data)
{
if ($this->type == 'memcache') {
$result = $this->db->replace($key, $data, MEMCACHE_COMPRESSED, $this->ttl);
@@ -441,17 +474,6 @@
if (apc_exists($key))
apc_delete($key);
$result = apc_store($key, $data, $this->ttl);
- }
-
- // Update index
- if ($index && $result) {
- $this->load_index();
-
- if (array_search($key, $this->index) === false) {
- $this->index[] = $key;
- $data = serialize($this->index);
- $this->add_record($this->ikey(), $data);
- }
}
return $result;
@@ -496,10 +518,15 @@
// Make sure index contains new keys
foreach ($this->cache as $key => $value) {
- if ($value !== null) {
- if (array_search($key, $this->index) === false) {
- $this->index[] = $key;
- }
+ if ($value !== null && !in_array($key, $this->index)) {
+ $this->index[] = $key;
+ }
+ }
+
+ // new keys added using self::write()
+ foreach ($this->cache_sums as $key => $value) {
+ if ($value === true && !in_array($key, $this->index)) {
+ $this->index[] = $key;
}
}
@@ -556,4 +583,56 @@
// This way each cache will have its own index
return sprintf('%d:%s%s', $this->userid, $this->prefix, 'INDEX');
}
+
+ /**
+ * Serializes data for storing
+ */
+ private function serialize($data)
+ {
+ if ($this->type == 'db') {
+ return $this->db->encode($data, $this->packed);
+ }
+
+ return $this->packed ? serialize($data) : $data;
+ }
+
+ /**
+ * Unserializes serialized data
+ */
+ private function unserialize($data)
+ {
+ if ($this->type == 'db') {
+ return $this->db->decode($data, $this->packed);
+ }
+
+ return $this->packed ? @unserialize($data) : $data;
+ }
+
+ /**
+ * Determine the maximum size for cache data to be written
+ */
+ private function max_packet_size()
+ {
+ if ($this->max_packet < 0) {
+ $this->max_packet = 2097152; // default/max is 2 MB
+
+ if ($this->type == 'db') {
+ if ($value = $this->db->get_variable('max_allowed_packet', $this->max_packet)) {
+ $this->max_packet = $value;
+ }
+ $this->max_packet -= 2000;
+ }
+ else if ($this->type == 'memcache') {
+ $stats = $this->db->getStats();
+ $remaining = $stats['limit_maxbytes'] - $stats['bytes'];
+ $this->max_packet = min($remaining / 5, $this->max_packet);
+ }
+ else if ($this->type == 'apc' && function_exists('apc_sma_info')) {
+ $stats = apc_sma_info();
+ $this->max_packet = min($stats['avail_mem'] / 5, $this->max_packet);
+ }
+ }
+
+ return $this->max_packet;
+ }
}
--
Gitblit v1.9.1