source: subversion/trunk/roundcubemail/program/include/rcube_cache.php @ 4783

Last change on this file since 4783 was 4783, checked in by alec, 2 years ago
  • Added general rcube_cache class with memcache support
  • Improved caching performance by skipping writes of unchanged data
  • Option enable_caching replaced by imap_cache and messages_cache options
  • Property svn:keywords set to Id Date Author
File size: 9.4 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/rcube_cache.php                                       |
6 |                                                                       |
7 | This file is part of the Roundcube Webmail client                     |
8 | Copyright (C) 2011, The Roundcube Dev Team                            |
9 | Copyright (C) 2011, Kolab Systems AG                                  |
10 | Licensed under the GNU GPL                                            |
11 |                                                                       |
12 | PURPOSE:                                                              |
13 |   Caching engine                                                      |
14 |                                                                       |
15 +-----------------------------------------------------------------------+
16 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
17 | Author: Aleksander Machniak <alec@alec.pl>                            |
18 +-----------------------------------------------------------------------+
19
20 $Id$
21
22*/
23
24
25/**
26 * Interface class for accessing Roundcube cache
27 *
28 * @package    Cache
29 * @author     Thomas Bruederli <roundcube@gmail.com>
30 * @author     Aleksander Machniak <alec@alec.pl>
31 * @version    1.0
32 */
33class rcube_cache
34{
35    /**
36     * Instance of rcube_mdb2 or Memcache class
37     *
38     * @var rcube_mdb2/Memcache
39     */
40    private $db;
41    private $type;
42    private $userid;
43    private $prefix;
44    private $cache         = array();
45    private $cache_keys    = array();
46    private $cache_changes = array();
47    private $cache_sums    = array();
48
49
50
51    /**
52     * Object constructor.
53     *
54     * @param string $type   Engine type ('db' or 'memcache')
55     * @param int    $userid User identifier
56     * @param string $prefix Key name prefix
57     */
58    function __construct($type, $userid, $prefix='')
59    {
60        $rcmail = rcmail::get_instance();
61   
62        if (strtolower($type) == 'memcache') {
63            $this->type = 'memcache';
64            $this->db   = $rcmail->get_memcache();
65        }
66        else {
67            $this->type = 'db';
68            $this->db   = $rcmail->get_dbh();
69        }
70
71        $this->userid = (int) $userid;
72        $this->prefix = $prefix;
73    }
74
75
76    /**
77     * Returns cached value.
78     *
79     * @param string $key Cache key
80     *
81     * @return mixed Cached value
82     */
83    function get($key)
84    {
85        $key = $this->prefix.$key;
86   
87        if ($this->type == 'memcache') {
88            return $this->read_cache_record($key);
89        }
90
91        // read cache (if it was not read before)
92        if (!count($this->cache)) {
93            $do_read = true;
94        }
95        else if (isset($this->cache[$key])) {
96            $do_read = false;
97        }
98        // Find cache prefix, we'll load data for all keys
99        // with specified (class) prefix into internal cache (memory)
100        else if ($pos = strpos($key, '.')) {
101            $prefix = substr($key, 0, $pos);
102            $regexp = '/^' . preg_quote($prefix, '/') . '/';
103            if (!count(preg_grep($regexp, array_keys($this->cache_keys)))) {
104                $do_read = true;
105            }
106        }
107
108        if ($do_read) {
109            return $this->read_cache_record($key);
110        }
111
112        return $this->cache[$key];
113    }
114
115
116    /**
117     * Sets (add/update) value in cache.
118     *
119     * @param string $key  Cache key
120     * @param mixed  $data Data
121     */
122    function set($key, $data)
123    {
124        $key = $this->prefix.$key;
125
126        $this->cache[$key]         = $data;
127        $this->cache_changed       = true;
128        $this->cache_changes[$key] = true;
129    }
130
131
132    /**
133     * Clears the cache.
134     *
135     * @param string  $key          Cache key name or pattern
136     * @param boolean $pattern_mode Enable it to clear all keys with name
137     *                              matching PREG pattern in $key
138     */
139    function remove($key=null, $pattern_mode=false)
140    {
141        if ($key === null) {
142            foreach (array_keys($this->cache) as $key)
143                $this->clear_cache_record($key);
144
145            $this->cache         = array();
146            $this->cache_changed = false;
147            $this->cache_changes = array();
148        }
149        else if ($pattern_mode) {
150            $key = $this->prefix.$key;
151
152            foreach (array_keys($this->cache) as $k) {
153                if (preg_match($key, $k)) {
154                    $this->clear_cache_record($k);
155                    $this->cache_changes[$k] = false;
156                    unset($this->cache[$key]);
157                }
158            }
159            if (!count($this->cache)) {
160                $this->cache_changed = false;
161            }
162        }
163        else {
164            $key = $this->prefix.$key;
165
166            $this->clear_cache_record($key);
167            $this->cache_changes[$key] = false;
168            unset($this->cache[$key]);
169        }
170    }
171
172
173    /**
174     * Writes the cache back to the DB.
175     */
176    function close()
177    {
178        if (!$this->cache_changed) {
179            return;
180        }
181
182        foreach ($this->cache as $key => $data) {
183            // The key has been used
184            if ($this->cache_changes[$key]) {
185                // Make sure we're not going to write unchanged data
186                // by comparing current md5 sum with the sum calculated on DB read
187                $data = serialize($data);
188                if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) {
189                    $this->write_cache_record($key, $data);
190                }
191            }
192        }
193    }
194
195
196    /**
197     * Returns cached entry.
198     *
199     * @param string $key Cache key
200     *
201     * @return mixed Cached value
202     * @access private
203     */
204    private function read_cache_record($key)
205    {
206        if (!$this->db) {
207            return null;
208        }
209
210        if ($this->type == 'memcache') {
211            $data = $this->db->get($key);
212               
213            if ($data) {
214                $this->cache_sums[$key] = md5($data);
215                $data = unserialize($data);
216            }
217            return $this->cache[$key] = $data;
218        }
219
220        // Find cache prefix, we'll load data for all keys
221        // with specified (class) prefix into internal cache (memory)
222        if ($pos = strpos($key, '.')) {
223            $prefix = substr($key, 0, $pos);
224            $where = " AND cache_key LIKE '$prefix%'";
225        }
226        else {
227            $where = " AND cache_key = ".$this->db->quote($key);
228        }
229
230        // get cached data from DB
231        $sql_result = $this->db->query(
232            "SELECT cache_id, data, cache_key".
233            " FROM ".get_table_name('cache').
234            " WHERE user_id = ?".$where,
235            $this->userid);
236
237        while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
238            $sql_key = $sql_arr['cache_key'];
239            $this->cache_keys[$sql_key] = $sql_arr['cache_id'];
240                if (!isset($this->cache[$sql_key])) {
241                $md5sum = $sql_arr['data'] ? md5($sql_arr['data']) : null;
242                $data   = $sql_arr['data'] ? unserialize($sql_arr['data']) : false;
243                    $this->cache[$sql_key]      = $data;
244                    $this->cache_sums[$sql_key] = $md5sum;
245            }
246        }
247
248        return $this->cache[$key];
249    }
250
251
252    /**
253     * Writes single cache record.
254     *
255     * @param string $key  Cache key
256     * @param mxied  $data Cache value
257     * @access private
258     */
259    private function write_cache_record($key, $data)
260    {
261        if (!$this->db) {
262            return false;
263        }
264
265        if ($this->type == 'memcache') {
266            $result = $this->db->replace($key, $data, MEMCACHE_COMPRESSED);
267            if (!$result)
268                $result = $this->db->set($key, $data, MEMCACHE_COMPRESSED);
269            return $result;
270        }
271
272        // update existing cache record
273        if ($this->cache_keys[$key]) {
274            $this->db->query(
275                "UPDATE ".get_table_name('cache').
276                " SET created = ". $this->db->now().", data = ?".
277                " WHERE user_id = ?".
278                " AND cache_key = ?",
279                $data, $this->userid, $key);
280        }
281        // add new cache record
282        else {
283            $this->db->query(
284                "INSERT INTO ".get_table_name('cache').
285                " (created, user_id, cache_key, data)".
286                " VALUES (".$this->db->now().", ?, ?, ?)",
287                $this->userid, $key, $data);
288
289            // get cache entry ID for this key
290            $sql_result = $this->db->query(
291                "SELECT cache_id".
292                " FROM ".get_table_name('cache').
293                " WHERE user_id = ?".
294                " AND cache_key = ?",
295                $this->userid, $key);
296
297            if ($sql_arr = $this->db->fetch_assoc($sql_result))
298                $this->cache_keys[$key] = $sql_arr['cache_id'];
299        }
300    }
301
302
303    /**
304     * Clears cache for single record.
305     *
306     * @param string $key Cache key
307     * @access private
308     */
309    private function clear_cache_record($key)
310    {
311        if (!$this->db) {
312            return false;
313        }
314
315        if ($this->type == 'memcache') {
316            return $this->db->delete($key);
317        }
318
319        $this->db->query(
320            "DELETE FROM ".get_table_name('cache').
321            " WHERE user_id = ?".
322            " AND cache_key = ?",
323            $this->userid, $key);
324
325        unset($this->cache_keys[$key]);
326    }
327
328}
Note: See TracBrowser for help on using the repository browser.