source: subversion/branches/devel-mcache/roundcubemail/program/include/rcube_imap_cache.php @ 5047

Last change on this file since 5047 was 5047, checked in by alec, 22 months ago
  • Fixed SELECT from cache_thread table
  • Property svn:keywords set to Id Date Author
File size: 26.5 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/rcube_imap_cache.php                                  |
6 |                                                                       |
7 | This file is part of the Roundcube Webmail client                     |
8 | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
9 | Licensed under the GNU GPL                                            |
10 |                                                                       |
11 | PURPOSE:                                                              |
12 |   Caching of IMAP folder contents (messages and index)                |
13 |                                                                       |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16 | Author: Aleksander Machniak <alec@alec.pl>                            |
17 +-----------------------------------------------------------------------+
18
19 $Id$
20
21*/
22
23
24/**
25 * Interface class for accessing Roundcube messages cache
26 *
27 * @package    Cache
28 * @author     Thomas Bruederli <roundcube@gmail.com>
29 * @author     Aleksander Machniak <alec@alec.pl>
30 * @version    1.0
31 */
32class rcube_imap_cache
33{
34    /**
35     * Instance of rcube_imap
36     *
37     * @var rcube_imap
38     */
39    private $imap;
40
41    /**
42     * Instance of rcube_mdb2
43     *
44     * @var rcube_mdb2
45     */
46    private $db;
47
48    /**
49     * User ID
50     *
51     * @var int
52     */
53    private $userid;
54
55    /**
56     * Internal (in-memory) cache
57     *
58     * @var array
59     */
60    private $icache = array();
61
62    private $skip_deleted = false;
63
64    public $flag_fields = array('seen', 'deleted', 'answered', 'forwarded', 'flagged', 'mdnsent');
65
66
67    /**
68     * Object constructor.
69     */
70    function __construct($db, $imap, $userid, $skip_deleted)
71    {
72        $this->db           = $db;
73        $this->imap         = $imap;
74        $this->userid       = (int)$userid;
75        $this->skip_deleted = $skip_deleted;
76    }
77
78
79    public function close()
80    {
81    }
82
83
84    /**
85     * Return (sorted) messages index.
86     * If index doesn't exist or is invalid, will be updated.
87     *
88     * @param string  $mailbox     Folder name
89     * @param string  $sort_field  Sorting column
90     * @param string  $sort_order  Sorting order (ASC|DESC)
91     *
92     * @return array Messages index
93     */
94    function get_index($mailbox, $sort_field = null, $sort_order = null)
95    {
96        if (empty($this->icache[$mailbox]))
97            $this->icache[$mailbox] = array();
98
99        $sort_order = strtoupper($sort_order) == 'ASC' ? 'ASC' : 'DESC';
100
101        // Seek in internal cache
102        if (array_key_exists('index', $this->icache[$mailbox])
103            && ($sort_field == 'ANY' || $this->icache[$mailbox]['index']['sort_field'] == $sort_field)
104        ) {
105            if ($this->icache[$mailbox]['index']['sort_order'] == $sort_order)
106                return $this->icache[$mailbox]['index']['result'];
107            else
108                return array_reverse($this->icache[$mailbox]['index']['result'], true);
109        }
110
111        // Get index from DB
112        $index = $this->get_index_row($mailbox, $sort_field);
113        $data  = null;
114
115        // @TODO: Think about skipping validation checks.
116        // If we could check only every 10 minutes, we would be able to skip
117        // expensive checks, mailbox selection or even IMAP connection, this would require
118        // additional logic to force cache invalidation in some cases
119        // and many rcube_imap changes to connect when needed
120
121        // Entry exist, check cache status
122        if (!empty($index)) {
123            if ($sort_field == 'ANY') {
124                $sort_field = $index['sort_field'];
125            }
126
127            $exists = true;
128            $is_valid = $this->validate($mailbox, $index, $exists);
129
130            if ($is_valid) {
131                // build index, assign sequence IDs to unique IDs
132                $data = array_combine($index['seq'], $index['uid']);
133                // revert the order if needed
134                if ($index['sort_order'] != $sort_order)
135                    $data = array_reverse($data, true);
136            }
137        }
138        else if ($sort_field == 'ANY') {
139            $sort_field = '';
140        }
141
142        // Index not found or not valid, get index from IMAP server
143        if ($data === null) {
144            // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
145            $mbox_data = $this->imap->mailbox_data($mailbox);
146            $data      = array();
147
148            // Prevent infinite loop.
149            // It happens when rcube_imap::message_index_direct() is called.
150            // There id2uid() is called which will again call get_index() and so on.
151            if (!$sort_field && !$this->skip_deleted)
152                $this->icache['pending_index_update'] = true;
153
154            if ($mbox_data['EXISTS']) {
155                // fetch sorted sequence numbers
156                $data_seq = $this->imap->message_index_direct($mailbox, $sort_field, $sort_order);
157                // fetch UIDs
158                if (!empty($data_seq)) {
159                    // Seek in internal cache
160                    if (array_key_exists('index', (array)$this->icache[$mailbox]))
161                        $data_uid = $this->icache[$mailbox]['index']['result'];
162                    else
163                        $data_uid = $this->imap->conn->fetchUIDs($mailbox, $data_seq);
164
165                    // build index
166                    if (!empty($data_uid)) {
167                        foreach ($data_seq as $seq)
168                            if ($uid = $data_uid[$seq])
169                                $data[$seq] = $uid;
170                    }
171                }
172            }
173
174            $this->icache['pending_index_update'] = false;
175
176            // insert/update
177            $this->add_index_row($mailbox, $sort_field, $sort_order, $data, $mbox_data, $exists);
178        }
179
180        $this->icache[$mailbox]['index'] = array(
181            'result'     => $data,
182            'sort_field' => $sort_field,
183            'sort_order' => $sort_order,
184        );
185
186        return $data;
187    }
188
189
190    /**
191     * Return messages thread.
192     * If threaded index doesn't exist or is invalid, will be updated.
193     *
194     * @param string  $mailbox     Folder name
195     * @param string  $sort_field  Sorting column
196     * @param string  $sort_order  Sorting order (ASC|DESC)
197     *
198     * @return array Messages threaded index
199     */
200    function get_thread($mailbox)
201    {
202        if (empty($this->icache[$mailbox]))
203            $this->icache[$mailbox] = array();
204
205        // Seek in internal cache
206        if (array_key_exists('thread', $this->icache[$mailbox])) {
207            return array(
208                $this->icache[$mailbox]['thread']['tree'],
209                $this->icache[$mailbox]['thread']['depth'],
210                $this->icache[$mailbox]['thread']['children'],
211            );
212        }
213
214        // Get index from DB
215        $index = $this->get_thread_row($mailbox);
216        $data  = null;
217
218        // Entry exist, check cache status
219        if (!empty($index)) {
220            $exists   = true;
221            $is_valid = $this->validate($mailbox, $index, $exists);
222
223            if (!$is_valid) {
224                $index = null;
225            }
226        }
227
228        // Index not found or not valid, get index from IMAP server
229        if ($index === null) {
230            // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
231            $mbox_data = $this->imap->mailbox_data($mailbox);
232
233            if ($mbox_data['EXISTS']) {
234                // get all threads (default sort order)
235                list ($thread_tree, $msg_depth, $has_children) = $this->imap->fetch_threads($mailbox, true);
236            }
237
238            $index = array(
239                'tree'     => !empty($thread_tree) ? $thread_tree : array(),
240                'depth'    => !empty($msg_depth) ? $msg_depth : array(),
241                'children' => !empty($has_children) ? $has_children : array(),
242            );
243
244            // insert/update
245            $this->add_thread_row($mailbox, $index, $mbox_data, $exists);
246        }
247
248        $this->icache[$mailbox]['thread'] = $index;
249
250        return array($index['tree'], $index['depth'], $index['children']);
251    }
252
253
254    /**
255     * Returns list of messages (headers). See rcube_imap::fetch_headers().
256     *
257     * @param string $mailbox  Folder name
258     * @param array  $msgs     Message sequence numbers
259     * @param bool   $is_uid   True if $msgs contains message UIDs
260     *
261     * @return array The list of messages (rcube_mail_header) indexed by UID
262     */
263    function get_messages($mailbox, $msgs = array(), $is_uid = true)
264    {
265        if (empty($msgs)) {
266            return array();
267        }
268
269        // Convert IDs to UIDs
270        // @TODO: it would be nice if we could work with UIDs only
271        // then, e.g. when fetching search result, index would be not needed
272        if (!$is_uid) {
273            $index = $this->get_index($mailbox, 'ANY');
274            foreach ($msgs as $idx => $msgid)
275                if ($uid = $index[$msgid])
276                    $msgs[$idx] = $uid;
277        }
278
279        $flag_fields = implode(', ', array_map(array($this->db, 'quoteIdentifier'), $this->flag_fields));
280
281        // Fetch messages from cache
282        $sql_result = $this->db->query(
283            "SELECT uid, data, ".$flag_fields
284            ." FROM ".get_table_name('cache_messages')
285            ." WHERE user_id = ?"
286                ." AND mailbox = ?"
287                ." AND uid IN (".$this->db->array2list($msgs, 'integer').")",
288            $this->userid, $mailbox);
289
290        $msgs   = array_flip($msgs);
291        $result = array();
292
293        while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
294            $uid          = intval($sql_arr['uid']);
295            $result[$uid] = $this->build_message($sql_arr);
296//@TODO: update message ID according to index data?
297
298            if (!empty($result[$uid])) {
299                unset($msgs[$uid]);
300            }
301        }
302
303        // Fetch not found messages from IMAP server
304        if (!empty($msgs)) {
305            $messages = $this->imap->fetch_headers($mailbox, array_keys($msgs), true, true);
306
307            // Insert to DB and add to result list
308            if (!empty($messages)) {
309                foreach ($messages as $msg) {
310                    $this->add_message($mailbox, $msg, !array_key_exists($msg->uid, $result));
311                    $result[$msg->uid] = $msg;
312                }
313            }
314        }
315
316        return $result;
317    }
318
319
320    /**
321     * Returns message data.
322     *
323     * @param string $mailbox  Folder name
324     * @param int    $uid      Message UID
325     *
326     * @return rcube_mail_header Message data
327     */
328    function get_message($mailbox, $uid)
329    {
330        $flag_fields = implode(', ', array_map(array($this->db, 'quoteIdentifier'), $this->flag_fields));
331
332        $sql_result = $this->db->query(
333            "SELECT data, ".$flag_fields
334            ." FROM ".get_table_name('cache_messages')
335            ." WHERE user_id = ?"
336                ." AND mailbox = ?"
337                ." AND uid = ?",
338                $this->userid, $mailbox, (int)$uid);
339
340        if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
341            $message = $this->build_message($sql_arr);
342            $found   = true;
343
344//@TODO: update message ID according to index data?
345        }
346
347        // Get the message from IMAP server
348        if (empty($message)) {
349            $message = $this->imap->get_headers($uid, $mailbox, true);
350            // update cache
351            $this->add_message($mailbox, $message, !$found);
352        }
353
354        return $message;
355    }
356
357
358    /**
359     * Saves the message in cache.
360     *
361     * @param string            $mailbox  Folder name
362     * @param rcube_mail_header $message  Message data
363     * @param bool              $force    Skips message in-cache existance check
364     */
365    function add_message($mailbox, $message, $force = false)
366    {
367        if (!is_object($message) || empty($message->uid))
368            return;
369
370        $msg = serialize($this->db->encode(clone $message));
371
372        $flag_fields = array_map(array($this->db, 'quoteIdentifier'), $this->flag_fields);
373        $flag_values = array();
374
375        foreach ($this->flag_fields as $flag)
376            $flag_values[] = (int) $message->$flag;
377
378        // update cache record (even if it exists, the update
379        // here will work as select, assume row exist if affected_rows=0)
380        if (!$force) {
381            foreach ($flag_fields as $key => $val)
382                $flag_data[] = $val . " = " . $flag_values[$key];
383
384            $res = $this->db->query(
385                "UPDATE ".get_table_name('cache_messages')
386                ." SET data = ?, changed = ".$this->db->now()
387                .", " . implode(', ', $flag_data)
388                ." WHERE user_id = ?"
389                    ." AND mailbox = ?"
390                    ." AND uid = ?",
391                $msg, $this->userid, $mailbox, (int) $message->uid);
392
393            if ($this->db->affected_rows())
394                return;
395        }
396
397        // insert new record
398        $this->db->query(
399            "INSERT INTO ".get_table_name('cache_messages')
400            ." (user_id, mailbox, uid, changed, data, " . implode(', ', $flag_fields) . ")"
401            ." VALUES (?, ?, ?, ".$this->db->now().", ?, " . implode(', ', $flag_values) . ")",
402            $this->userid, $mailbox, (int) $message->uid, $msg);
403    }
404
405
406    /**
407     * Sets the flag for specified message.
408     *
409     * @param string  $mailbox  Folder name
410     * @param array   $uids     Message UIDs or -1 to change flag
411     *                          of all messages in a folder
412     * @param string  $flag     The name of the flag
413     * @param bool    $enabled  Flag state
414     */
415    function change_flag($mailbox, $uids, $flag, $enabled = false)
416    {
417        $flag = strtolower($flag);
418
419        if (in_array($flag, $this->flag_fields)) {
420            $this->db->query(
421                "UPDATE ".get_table_name('cache_messages')
422                ." SET changed = ".$this->db->now()
423                .", " .$this->db->quoteIdentifier($flag) . " = " . intval($enabled)
424                ." WHERE user_id = ?"
425                    ." AND mailbox = ?"
426                    .($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : ""),
427                $this->userid, $mailbox);
428        }
429        else {
430            // @TODO: SELECT+UPDATE?
431            $this->remove_message($mailbox, $uids);
432        }
433    }
434
435
436    /**
437     * Removes message(s) from cache.
438     *
439     * @param string $mailbox  Folder name
440     * @param array  $uids     Message UIDs, NULL removes all messages
441     */
442    function remove_message($mailbox = null, $uids = null)
443    {
444        if (!strlen($mailbox)) {
445            $this->db->query(
446                "DELETE FROM ".get_table_name('cache_messages')
447                ." WHERE user_id = ?",
448                $this->userid);
449        }
450        else {
451            $this->db->query(
452                "DELETE FROM ".get_table_name('cache_messages')
453                ." WHERE user_id = ?"
454                    ." AND mailbox = ".$this->db->quote($mailbox)
455                    .($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : ""),
456                $this->userid);
457        }
458
459    }
460
461
462    /**
463     * Clears index cache.
464     *
465     * @param string  $mailbox     Folder name
466     * @param string  $sort_field  Sorting column
467     */
468    function remove_index($mailbox = null, $sort_field = null)
469    {
470        $this->db->query(
471            "DELETE FROM ".get_table_name('cache_index')
472            ." WHERE user_id = ".intval($this->userid)
473                .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "")
474                .($sort_field !== null ? " AND sort_field = ".$this->db->quote($sort_field) : "")
475        );
476
477        if (strlen($mailbox))
478            unset($this->icache[$mailbox]['index']);
479        else
480            $this->icache = array();
481    }
482
483
484    /**
485     * Clears thread cache.
486     *
487     * @param string  $mailbox     Folder name
488     */
489    function remove_thread($mailbox = null)
490    {
491        $this->db->query(
492            "DELETE FROM ".get_table_name('cache_thread')
493            ." WHERE user_id = ".intval($this->userid)
494                .(strlen($mailbox) ? " AND mailbox = ".$this->db->quote($mailbox) : "")
495        );
496
497        if (strlen($mailbox))
498            unset($this->icache[$mailbox]['thread']);
499        else
500            $this->icache = array();
501    }
502
503
504    /**
505     * Clears the cache.
506     *
507     * @param string $mailbox  Folder name
508     * @param array  $uids     Message UIDs, NULL removes all messages in a folder
509     */
510    function clear($mailbox = null, $uids = null)
511    {
512        $this->remove_index($mailbox);
513        $this->remove_thread($mailbox);
514        $this->remove_message($mailbox, $uids);
515    }
516
517
518    /**
519     * @param string $mailbox Folder name
520     * @param int    $id      Message (sequence) ID
521     *
522     * @return int Message UID
523     */
524    function id2uid($mailbox, $id)
525    {
526        if (!empty($this->icache['pending_index_update']))
527            return null;
528
529        $index = $this->get_index($mailbox, 'ANY');
530
531        return $index[$id];
532    }
533
534
535    /**
536     * @param string $mailbox Folder name
537     * @param int    $uid     Message UID
538     *
539     * @return int Message (sequence) ID
540     */
541    function uid2id($mailbox, $uid)
542    {
543        if (!empty($this->icache['pending_index_update']))
544            return null;
545
546        $index = $this->get_index($mailbox, 'ANY');
547
548        return array_search($uid, (array)$index);
549    }
550
551
552    /**
553     * Fetches index data from database
554     */
555    private function get_index_row($mailbox, $sort_field = 'ANY')
556    {
557        // Get index from DB
558        // There's a special case when we want most recent index
559        if ($sort_field == 'ANY')
560            $sql_result = $this->db->limitquery(
561                "SELECT data, sort_field"
562                ." FROM ".get_table_name('cache_index')
563                ." WHERE user_id = ?"
564                    ." AND mailbox = ?"
565                // sort by 'changed' datetime to get most fresh index
566                // sort by 'sort_field' to get index with specified order,
567                // in some cases this saves one query
568                ." ORDER BY changed DESC, sort_field DESC",
569                0, 1, $this->userid, $mailbox);
570        else
571            $sql_result = $this->db->query(
572                "SELECT data, sort_field"
573                ." FROM ".get_table_name('cache_index')
574                ." WHERE user_id = ?"
575                    ." AND mailbox = ?"
576                    ." AND sort_field = ?",
577                $this->userid, $mailbox, (string)$sort_field);
578
579        if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
580            $data = explode('@', $sql_arr['data']);
581
582            return array(
583                'seq'        => explode(',', $data[0]),
584                'uid'        => explode(',', $data[1]),
585                'sort_field' => $sql_arr['sort_field'],
586                'sort_order' => $data[2],
587                'deleted'    => $data[3],
588                'validity'   => $data[4],
589                'uidnext'    => $data[5],
590            );
591        }
592
593        return null;
594    }
595
596
597    /**
598     * Fetches thread data from database
599     */
600    private function get_thread_row($mailbox)
601    {
602        // Get thread from DB
603        $sql_result = $this->db->query(
604            "SELECT data"
605            ." FROM ".get_table_name('cache_thread')
606            ." WHERE user_id = ?"
607                ." AND mailbox = ?",
608            $this->userid, $mailbox);
609
610        if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
611            $data = explode('@', $sql_arr['data']);
612
613            $data[0] = unserialize($data[0]);
614            // build 'depth' and 'children' arrays
615            $depth = $children = array();
616            $this->build_thread_data($data[0], $depth, $children);
617
618            return array(
619                'tree'     => $data[0],
620                'depth'    => $depth,
621                'children' => $children,
622                'deleted'  => $data[1],
623                'validity' => $data[2],
624                'uidnext'  => $data[3],
625            );
626        }
627
628        return null;
629    }
630
631
632    /**
633     * Saves index data into database
634     */
635    private function add_index_row($mailbox, $sort_field, $sort_order,
636        $data = array(), $mbox_data = array(), $exists = false)
637    {
638        $data = array(
639            implode(',', array_keys($data)),
640            implode(',', array_values($data)),
641            $sort_order,
642            (int) $this->skip_deleted,
643            (int) $mbox_data['UIDVALIDITY'],
644            (int) $mbox_data['UIDNEXT'],
645        );
646        $data = implode('@', $data);
647
648        if ($exists)
649            $sql_result = $this->db->query(
650                "UPDATE ".get_table_name('cache_index')
651                ." SET data = ?, changed = ".$this->db->now()
652                ." WHERE user_id = ?"
653                    ." AND mailbox = ?"
654                    ." AND sort_field = ?",
655                $data, $this->userid, $mailbox, (string)$sort_field);
656        else
657            $sql_result = $this->db->query(
658                "INSERT INTO ".get_table_name('cache_index')
659                ." (user_id, mailbox, sort_field, data, changed)"
660                ." VALUES (?, ?, ?, ?, ".$this->db->now().")",
661                $this->userid, $mailbox, (string)$sort_field, $data);
662    }
663
664
665    /**
666     * Saves thread data into database
667     */
668    private function add_thread_row($mailbox, $data = array(), $mbox_data = array(), $exists = false)
669    {
670        $data = array(
671            serialize($data['tree']),
672            (int) $this->skip_deleted,
673            (int) $mbox_data['UIDVALIDITY'],
674            (int) $mbox_data['UIDNEXT'],
675        );
676        $data = implode('@', $data);
677
678        if ($exists)
679            $sql_result = $this->db->query(
680                "UPDATE ".get_table_name('cache_thread')
681                ." SET data = ?, changed = ".$this->db->now()
682                ." WHERE user_id = ?"
683                    ." AND mailbox = ?",
684                $data, $this->userid, $mailbox);
685        else
686            $sql_result = $this->db->query(
687                "INSERT INTO ".get_table_name('cache_thread')
688                ." (user_id, mailbox, data, changed)"
689                ." VALUES (?, ?, ?, ".$this->db->now().")",
690                $this->userid, $mailbox, $data);
691    }
692
693
694    /**
695     * Checks index/thread validity
696     */
697    private function validate($mailbox, $index, &$exists = true)
698    {
699        $is_thread = isset($index['tree']);
700
701        // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
702        $mbox_data = $this->imap->mailbox_data($mailbox);
703
704        // @TODO: Think about skipping validation checks.
705        // If we could check only every 10 minutes, we would be able to skip
706        // expensive checks, mailbox selection or even IMAP connection, this would require
707        // additional logic to force cache invalidation in some cases
708        // and many rcube_imap changes to connect when needed
709
710        // Check UIDVALIDITY
711        // @TODO: while we're storing message sequence numbers in thread
712        //        index, should UIDVALIDITY invalidate the thread data?
713        if ($index['validity'] != $mbox_data['UIDVALIDITY']) {
714            // the whole cache (all folders) is invalid
715            $this->clear();
716            $exists = false;
717            return false;
718        }
719
720        // Folder is empty but cache isn't
721        if (empty($mbox_data['EXISTS']) && (!empty($index['seq']) || !empty($index['tree']))) {
722            $this->clear($mailbox);
723            $exists = false;
724            return false;
725        }
726
727        // Check UIDNEXT
728        if ($index['uidnext'] != $mbox_data['UIDNEXT']) {
729            unset($this->icache[$mailbox][$is_thread ? 'thread' : 'index']);
730            return false;
731        }
732
733        // Index was created with different skip_deleted setting
734        if ($this->skip_deleted != $index['deleted']) {
735            return false;
736        }
737
738        // @TODO: find better validity check for threaded index
739        if ($is_thread) {
740            // check messages number...
741            if ($mbox_data['EXISTS'] != max(array_keys($index['depth']))) {
742                return false;
743            }
744            return true;
745        }
746
747        // The rest of checks, more expensive
748        if (!empty($this->skip_deleted)) {
749            // compare counts if available
750            if ($mbox_data['COUNT_UNDELETED'] != null
751                && $mbox_data['COUNT_UNDELETED'] != count($index['uid'])) {
752                return false;
753            }
754            // compare UID sets
755            if ($mbox_data['ALL_UNDELETED'] != null) {
756                $uids_new = rcube_imap_generic::uncompressMessageSet($mbox_data['ALL_UNDELETED']);
757                $uids_old = $index['uid'];
758
759                if (count($uids_new) != count($uids_old)) {
760                    return false;
761                }
762
763                sort($uids_new, SORT_NUMERIC);
764                sort($uids_old, SORT_NUMERIC);
765
766                if ($uids_old != $uids_new)
767                    return false;
768            }
769            else {
770                // get all undeleted messages excluding cached UIDs
771                $ids = $this->imap->search_once($mailbox, 'ALL UNDELETED NOT UID '.
772                    rcube_imap_generic::compressMessageSet($index['uid']));
773
774                if (!empty($ids)) {
775                    $index = null; // cache invalid
776                }
777            }
778        }
779        else {
780            // check messages number...
781            if ($mbox_data['EXISTS'] != max($index['seq'])
782                // ... and max UID
783                || max($index['uid']) != $this->imap->id2uid($mbox_data['EXISTS'], $mailbox, true)
784            ) {
785                return false;
786            }
787        }
788
789        return true;
790    }
791
792
793    /**
794     * Converts cache row into message object.
795     *
796     * @param array $sql_arr Message row data
797     *
798     * @return rcube_mail_header Message object
799     */
800    private function build_message($sql_arr)
801    {
802        $message = $this->db->decode(unserialize($sql_arr['data']));
803
804        if ($message) {
805            foreach ($this->flag_fields as $field)
806                $message->$field = (bool) $sql_arr[$field];
807        }
808
809        return $message;
810    }
811
812
813    /**
814     * Creates 'depth' and 'children' arrays from stored thread 'tree' data.
815     */
816    private function build_thread_data($data, &$depth, &$children, $level = 0)
817    {
818        foreach ((array)$data as $key => $val) {
819            $children[$key] = !empty($val);
820            $depth[$key] = $level;
821            if (!empty($val))
822                $this->build_thread_data($val, $depth, $children, $level + 1);
823        }
824    }
825}
Note: See TracBrowser for help on using the repository browser.