From 0677ca5a1e745643a9142081c4cbb7ea13e5a2e5 Mon Sep 17 00:00:00 2001 From: thomascube <thomas@roundcube.net> Date: Mon, 23 Jan 2006 18:12:23 -0500 Subject: [PATCH] Add created date to message cache --- program/include/rcube_imap.inc | 1130 +++++++++++++++++++++++++++++++++++++++++++++-------------- 1 files changed, 858 insertions(+), 272 deletions(-) diff --git a/program/include/rcube_imap.inc b/program/include/rcube_imap.inc index 8253442..7f91d3a 100644 --- a/program/include/rcube_imap.inc +++ b/program/include/rcube_imap.inc @@ -21,53 +21,91 @@ */ +/** + * Obtain classes from the Iloha IMAP library + */ require_once('lib/imap.inc'); require_once('lib/mime.inc'); require_once('lib/utf7.inc'); +/** + * Interface class for accessing an IMAP server + * + * This is a wrapper that implements the Iloha IMAP Library (IIL) + * + * @package RoundCube Webmail + * @author Thomas Bruederli <roundcube@gmail.com> + * @version 1.22 + * @link http://ilohamail.org + */ class rcube_imap { + var $db; var $conn; var $root_ns = ''; var $root_dir = ''; var $mailbox = 'INBOX'; var $list_page = 1; var $page_size = 10; + var $sort_field = 'date'; + var $sort_order = 'DESC'; var $delimiter = NULL; var $caching_enabled = FALSE; var $default_folders = array('inbox', 'drafts', 'sent', 'junk', 'trash'); var $cache = array(); + var $cache_keys = array(); var $cache_changes = array(); var $uid_id_map = array(); var $msg_headers = array(); + var $capabilities = array(); + var $skip_deleted = FALSE; + var $debug_level = 1; - // PHP 5 constructor - function __construct() + /** + * Object constructor + * + * @param object Database connection + */ + function __construct($db_conn) { - - } - - // PHP 4 compatibility - function rcube_imap() - { - $this->__construct(); + $this->db = $db_conn; } + /** + * PHP 4 object constructor + * + * @see rcube_imap::__construct + */ + function rcube_imap($db_conn) + { + $this->__construct($db_conn); + } + + + /** + * Connect to an IMAP server + * + * @param string Host to connect + * @param string Username for IMAP account + * @param string Password for IMAP account + * @param number Port to connect to + * @param boolean Use SSL connection + * @return boolean TRUE on success, FALSE on failure + * @access public + */ function connect($host, $user, $pass, $port=143, $use_ssl=FALSE) { - global $ICL_SSL, $ICL_PORT, $CONFIG; + global $ICL_SSL, $ICL_PORT; // check for Open-SSL support in PHP build if ($use_ssl && in_array('openssl', get_loaded_extensions())) $ICL_SSL = TRUE; else if ($use_ssl) { - raise_error(array('code' => 403, - 'type' => 'imap', - 'file' => __FILE__, + raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__, 'message' => 'Open SSL not available;'), TRUE, FALSE); $port = 143; } @@ -81,7 +119,7 @@ $this->ssl = $use_ssl; // print trace mesages - if ($this->conn && ($CONFIG['debug_level'] & 8)) + if ($this->conn && ($this->debug_level & 8)) console($this->conn->message); // write error log @@ -95,6 +133,7 @@ // get account namespace if ($this->conn) { + $this->_parse_capability($this->conn->capability); iil_C_NameSpace($this->conn); if (!empty($this->conn->delimiter)) @@ -110,6 +149,12 @@ } + /** + * Close IMAP connection + * Usually done on script shutdown + * + * @access public + */ function close() { if ($this->conn) @@ -117,6 +162,12 @@ } + /** + * Close IMAP connection and re-connect + * This is used to avoid some strange socket errors when talking to Courier IMAP + * + * @access public + */ function reconnect() { $this->close(); @@ -124,6 +175,15 @@ } + /** + * Set a root folder for the IMAP connection. + * + * Only folders within this root folder will be displayed + * and all folder paths will be translated using this folder name + * + * @param string Root folder + * @access public + */ function set_rootdir($root) { if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/') @@ -136,6 +196,12 @@ } + /** + * This list of folders will be listed above all other folders + * + * @param array Indexed list of folder names + * @access public + */ function set_default_mailboxes($arr) { if (is_array($arr)) @@ -153,6 +219,14 @@ } + /** + * Set internal mailbox reference. + * + * All operations will be perfomed on this mailbox/folder + * + * @param string Mailbox/Folder name + * @access public + */ function set_mailbox($mbox) { $mailbox = $this->_mod_mailbox($mbox); @@ -167,24 +241,62 @@ } + /** + * Set internal list page + * + * @param number Page number to list + * @access public + */ function set_page($page) { $this->list_page = (int)$page; } + /** + * Set internal page size + * + * @param number Number of messages to display on one page + * @access public + */ function set_pagesize($size) { $this->page_size = (int)$size; } + /** + * Returns the currently used mailbox name + * + * @return string Name of the mailbox/folder + * @access public + */ function get_mailbox_name() { return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : ''; } + /** + * Returns the IMAP server's capability + * + * @param string Capability name + * @return mixed Capability value or TRUE if supported, FALSE if not + * @access public + */ + function get_capability($cap) + { + $cap = strtoupper($cap); + return $this->capabilities[$cap]; + } + + + /** + * Returns the delimiter that is used by the IMAP server for folder separation + * + * @return string Delimiter string + * @access public + */ function get_hierarchy_delimiter() { if ($this->conn && empty($this->delimiter)) @@ -196,8 +308,17 @@ return $this->delimiter; } - // public method for mailbox listing - // convert mailbox name with root dir first + + /** + * Public method for mailbox listing. + * + * Converts mailbox name with root dir first + * + * @param string Optional root folder + * @param string Optional filter for mailbox listing + * @return array List of mailboxes/folders + * @access public + */ function list_mailboxes($root='', $filter='*') { $a_out = array(); @@ -216,7 +337,14 @@ return $a_out; } - // private method for mailbox listing + + /** + * Private method for mailbox listing + * + * @return array List of mailboxes/folders + * @access private + * @see rcube_imap::list_mailboxes + */ function _list_mailboxes($root='', $filter='*') { $a_defaults = $a_out = array(); @@ -248,39 +376,79 @@ } - // get message count for a specific mailbox; acceptes modes are: ALL, UNSEEN + /** + * Get message count for a specific mailbox + * + * @param string Mailbox/folder name + * @param string Mode for count [ALL|UNSEEN|RECENT] + * @param boolean Force reading from server and update cache + * @return number Number of messages + * @access public + */ function messagecount($mbox='', $mode='ALL', $force=FALSE) { $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox; return $this->_messagecount($mailbox, $mode, $force); } - // private method for getting nr of mesages + + /** + * Private method for getting nr of messages + * + * @access private + * @see rcube_imap::messagecount + */ function _messagecount($mailbox='', $mode='ALL', $force=FALSE) { $a_mailbox_cache = FALSE; $mode = strtoupper($mode); - if (!$mailbox) + if (empty($mailbox)) $mailbox = $this->mailbox; $a_mailbox_cache = $this->get_cache('messagecount'); // return cached value if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode])) - return $a_mailbox_cache[$mailbox][$mode]; + return $a_mailbox_cache[$mailbox][$mode]; - // get message count and store in cache - if ($mode == 'UNSEEN') - $count = iil_C_CountUnseen($this->conn, $mailbox); + // RECENT count is fetched abit different + if ($mode == 'RECENT') + $count = iil_C_CheckForRecent($this->conn, $mailbox); + + // use SEARCH for message counting + else if ($this->skip_deleted) + { + $search_str = "ALL UNDELETED"; + + // get message count and store in cache + if ($mode == 'UNSEEN') + $search_str .= " UNSEEN"; + + // get message count using SEARCH + // not very performant but more precise (using UNDELETED) + $count = 0; + $index = $this->_search_index($mailbox, $search_str); + if (is_array($index)) + { + $str = implode(",", $index); + if (!empty($str)) + $count = count($index); + } + } else - $count = iil_C_CountMessages($this->conn, $mailbox); + { + if ($mode == 'UNSEEN') + $count = iil_C_CountUnseen($this->conn, $mailbox); + else + $count = iil_C_CountMessages($this->conn, $mailbox); + } if (is_array($a_mailbox_cache[$mailbox])) $a_mailbox_cache[$mailbox] = array(); $a_mailbox_cache[$mailbox][$mode] = (int)$count; - + // write back to cache $this->update_cache('messagecount', $a_mailbox_cache); @@ -288,252 +456,327 @@ } - // public method for listing headers - // convert mailbox name with root dir first - function list_headers($mbox='', $page=NULL, $sort_field='date', $sort_order='DESC') + /** + * Public method for listing headers + * convert mailbox name with root dir first + * + * @param string Mailbox/folder name + * @param number Current page to list + * @param string Header field to sort by + * @param string Sort order [ASC|DESC] + * @return array Indexed array with message header objects + * @access public + */ + function list_headers($mbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL) { $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox; return $this->_list_headers($mailbox, $page, $sort_field, $sort_order); } - // private method for listing message header - // by DrSlump <drslump@drslump.biz> - function __list_headers($mailbox='', $page=NULL, $sort_field='date', $sort_order='DESC') + /** + * Private method for listing message header + * + * @access private + * @see rcube_imap::list_headers + */ + function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE) { - $a_out = array(); - $cached_count = 0; - - if (!strlen($mailbox)) - return $a_out; - - $mbox_count = $this->_messagecount($mailbox /*, 'ALL', TRUE*/); - - $revalidate = false; - if ($mbox_count) - { - // get cached headers - $a_out = $this->get_cache($mailbox.'.msg'); - $a_out = is_array($a_out) ? $a_out : array(); // make sure we get an array - - $cached_count = count($a_out); - $a_new = array(); - $revalidate = true; // revalidate by default - - // if the cache count is greater then there have been changes for sure - if ($cached_count <= $mbox_count) - { - $from = $cached_count?$cached_count:1; - - //get new headers (at least one is returned) - $a_temp = iil_C_FetchHeaders($this->conn, $mailbox, $from . ':' . $mbox_count); - $duplicated = $cached_count?true:false; - - foreach ($a_temp as $hdr) - { - //skip the first one if duplicated - if ($duplicated) - { - //check for changes using the UID - $lastCacheHdr = end($a_out); - if ($hdr->uid === $lastCacheHdr->uid) - $revalidate = false; - - $duplicated = false; - continue; - } - - //skip deleted ones - if (! $hdr->deleted) - $a_new[ $hdr->uid ] = $hdr; - } - } - - //revalidate cache if needed - $to = $mbox_count - count($a_new); - if ($revalidate && $to !== 0) //we'll need to reindex the array so we have to make a copy - { - $a_dirty = $a_out; - $a_out = array(); - $a_buffers = array(); - - //fetch chunks of 20 headers - $step = 20; - $found = false; - - //fetch headers in blocks starting from new to old - do { - $from = $to-$step; - if ($from < 1) $from = 1; - - //store the block in a temporal buffer - $a_buffers[$from] = iil_C_FetchHeaders($this->conn, $mailbox, $from . ':' . $to); - - //compare the fetched headers with the ones in the cache - $idx = 0; - foreach ($a_buffers[$from] as $k=>$hdr) - { - //if it's different the comparison ends - if (!isset($a_dirty[$hdr->uid]) || $a_dirty[$hdr->uid]->id !== $hdr->id) - break; - - //if we arrive here then we know that the older messages in cache are ok - $found = $hdr->id; - $idx++; - } - - //remove from the buffer the headers which are already cached - if ($found) - $a_buffers[$from] = array_splice($a_buffers[$from], 0, $idx ); - - $to = $from-1; - } - while ($found===false && $from > 1); - - //just keep the headers we are certain that didn't change in the cache - if ($found !== false) - { - foreach ($a_dirty as $hdr) - { - if ($hdr->id > $found) break; - $a_out[$hdr->uid] = $hdr; - } - } - - //we builded the block buffers from new to older, we process them in reverse order - ksort($a_buffers, SORT_NUMERIC); - foreach ($a_buffers as $a_buff) - { - foreach ($a_buff as $hdr) - { - if (! $hdr->deleted) - $a_out[$hdr->uid] = $hdr; - } - } - } - - //array_merge() would reindex the keys, so we use this 'hack' - $a_out += $a_new; - } - - //write headers list to cache if needed - if ($revalidate || count($a_out)!=$cached_count) { - $this->update_cache($mailbox.'.msg', $a_out); - } - - //sort headers by a specific col - $a_out = iil_SortHeaders( $a_out, $sort_field, $sort_order ); - - // return complete list of messages - if (strtolower($page)=='all') - return $a_out; - - $start_msg = ($this->list_page-1) * $this->page_size; - return array_slice($a_out, $start_msg, $this->page_size); - } - - - // original function; replaced 2005/10/18 - // private method for listing message header - function _list_headers($mailbox='', $page=NULL, $sort_field='date', $sort_order='DESC') - { - $max = $this->_messagecount($mailbox); - if (!strlen($mailbox)) return array(); + + if ($sort_field!=NULL) + $this->sort_field = $sort_field; + if ($sort_order!=NULL) + $this->sort_order = strtoupper($sort_order); - // get cached headers - $a_msg_headers = $this->get_cache($mailbox.'.msg'); - - // retrieve headers from IMAP - if (!is_array($a_msg_headers) || sizeof($a_msg_headers) != $max) + $max = $this->_messagecount($mailbox); + $start_msg = ($this->list_page-1) * $this->page_size; + + if ($page=='all') { - $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, "1:$max"); - $a_msg_headers = array(); - - if (!empty($a_header_index)) - foreach ($a_header_index as $i => $headers) - if (!$headers->deleted) - $a_msg_headers[$headers->uid] = $headers; + $begin = 0; + $end = $max; + } + else if ($this->sort_order=='DESC') + { + $begin = $max - $this->page_size - $start_msg; + $end = $max - $start_msg; } else - $headers_cached = TRUE; + { + $begin = $start_msg; + $end = $start_msg + $this->page_size; + } - if (!is_array($a_msg_headers)) + if ($begin < 0) $begin = 0; + if ($end < 0) $end = $max; + if ($end > $max) $end = $max; + +//console("fetch headers $start_msg to ".($start_msg+$this->page_size)." (msg $begin to $end)"); + + $headers_sorted = FALSE; + $cache_key = $mailbox.'.msg'; + $cache_status = $this->check_cache_status($mailbox, $cache_key); + +//console("Cache status = $cache_status"); + + // cache is OK, we can get all messages from local cache + if ($cache_status>0) + { + $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order); + $headers_sorted = TRUE; + } + else + { + // retrieve headers from IMAP + if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : ''))) + { +//console("$mailbox: ".join(',', $msg_index)); + + $msgs = $msg_index[$begin]; + for ($i=$begin+1; $i < $end; $i++) + { + //if ($this->sort_order == 'DESC') + // $msgs = $msg_index[$i].','.$msgs; + //else + $msgs = $msgs.','.$msg_index[$i]; + } + + $sorted = TRUE; + } + else + { + $msgs = sprintf("%d:%d", $begin+1, $end); + $sorted = FALSE; + } + + + // cache is dirty, sync it + if ($this->caching_enabled && $cache_status==-1 && !$recursive) + { + $this->sync_header_index($mailbox); + return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE); + } + + + // fetch reuested headers from server + $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs); + $a_msg_headers = array(); + $deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key); + + // delete cached messages with a higher index than $max + $this->clear_message_cache($cache_key, $max); + + + // kick child process to sync cache + // ... + + } + + + // return empty array if no messages found + if (!is_array($a_msg_headers) || empty($a_msg_headers)) return array(); - - // sort headers by a specific col - $a_headers = iil_SortHeaders($a_msg_headers, $sort_field, $sort_order); - // free memory - unset($a_msg_headers); - - // write headers list to cache - if (!$headers_cached) - $this->update_cache($mailbox.'.msg', $a_headers); - if (empty($a_headers)) - return array(); - - // return complete list of messages - if (strtolower($page)=='all') - return $a_headers; + // if not already sorted + if (!$headers_sorted) + $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order); - $start_msg = ($this->list_page-1) * $this->page_size; - return array_slice($a_headers, $start_msg, $this->page_size); + return array_values($a_msg_headers); } - + + + /** + * Fetches message headers + * Used for loop + * + * @param string Mailbox name + * @param string Message indey to fetch + * @param array Reference to message headers array + * @param array Array with cache index + * @return number Number of deleted messages + * @access private + */ + function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key) + { + // cache is incomplete + $cache_index = $this->get_message_cache_index($cache_key); + + // fetch reuested headers from server + $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs); + $deleted_count = 0; + + if (!empty($a_header_index)) + { + foreach ($a_header_index as $i => $headers) + { + if ($headers->deleted && $this->skip_deleted) + { + // delete from cache + if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid) + $this->remove_message_cache($cache_key, $headers->id); + + $deleted_count++; + continue; + } + + // add message to cache + if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid) + $this->add_message_cache($cache_key, $headers->id, $headers); + + $a_msg_headers[$headers->uid] = $headers; + } + } + + return $deleted_count; + } + + // return sorted array of message UIDs - function message_index($mbox='', $sort_field='date', $sort_order='DESC') + function message_index($mbox='', $sort_field=NULL, $sort_order=NULL) { + if ($sort_field!=NULL) + $this->sort_field = $sort_field; + if ($sort_order!=NULL) + $this->sort_order = strtoupper($sort_order); + $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox; - $a_out = array(); + $key = "$mbox:".$this->sort_field.":".$this->sort_order.".msgi"; - // get array of message headers - $a_headers = $this->_list_headers($mailbox, 'all', $sort_field, $sort_order); + // have stored it in RAM + if (isset($this->cache[$key])) + return $this->cache[$key]; - if (is_array($a_headers)) - foreach ($a_headers as $header) - $a_out[] = $header->uid; + // check local cache + $cache_key = $mailbox.'.msg'; + $cache_status = $this->check_cache_status($mailbox, $cache_key); - return $a_out; + // cache is OK + if ($cache_status>0) + { + $a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order); + return array_values($a_index); + } + + + // fetch complete message index + $msg_count = $this->_messagecount($mailbox); + if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field))) + { + $a_uids = iil_C_FetchUIDs($this->conn, $mailbox); + + if ($this->sort_order == 'DESC') + $a_index = array_reverse($a_index); + + $i = 0; + $this->cache[$key] = array(); + foreach ($a_index as $index => $value) + $this->cache[$key][$i++] = $a_uids[$value]; + } + else + { + $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field); + $a_uids = iil_C_FetchUIDs($this->conn, $mailbox); + + if ($this->sort_order=="ASC") + asort($a_index); + else if ($this->sort_order=="DESC") + arsort($a_index); + + $i = 0; + $this->cache[$key] = array(); + foreach ($a_index as $index => $value) + $this->cache[$key][$i++] = $a_uids[$index]; + } + + return $this->cache[$key]; } - function sync_header_index($mbox=NULL) + function sync_header_index($mailbox) { - + $cache_key = $mailbox.'.msg'; + $cache_index = $this->get_message_cache_index($cache_key); + $msg_count = $this->_messagecount($mailbox); + + // fetch complete message index + $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID'); + + foreach ($a_message_index as $id => $uid) + { + // message in cache at correct position + if ($cache_index[$id] == $uid) + { +// console("$id / $uid: OK"); + unset($cache_index[$id]); + continue; + } + + // message in cache but in wrong position + if (in_array((string)$uid, $cache_index, TRUE)) + { +// console("$id / $uid: Moved"); + unset($cache_index[$id]); + } + + // other message at this position + if (isset($cache_index[$id])) + { +// console("$id / $uid: Delete"); + $this->remove_message_cache($cache_key, $id); + unset($cache_index[$id]); + } + + +// console("$id / $uid: Add"); + + // fetch complete headers and add to cache + $headers = iil_C_FetchHeader($this->conn, $mailbox, $id); + $this->add_message_cache($cache_key, $headers->id, $headers); + } + + // those ids that are still in cache_index have been deleted + if (!empty($cache_index)) + { + foreach ($cache_index as $id => $uid) + $this->remove_message_cache($cache_key, $id); + } } function search($mbox='', $criteria='ALL') { $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox; + return $this->_search_index($mailbox, $criteria); + } + + + function _search_index($mailbox, $criteria='ALL') + { $a_messages = iil_C_Search($this->conn, $mailbox, $criteria); return $a_messages; } - function get_headers($uid, $mbox=NULL) + function get_headers($id, $mbox=NULL, $is_uid=TRUE) { $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox; - - // get cached headers - $a_msg_headers = $this->get_cache($mailbox.'.msg'); - - // return cached header - if ($a_msg_headers[$uid]) - return $a_msg_headers[$uid]; - $msg_id = $this->_uid2id($uid); - $header = iil_C_FetchHeader($this->conn, $mailbox, $msg_id); + // get cached headers + if ($headers = $this->get_cached_message($mailbox.'.msg', $uid)) + return $headers; + + $msg_id = $is_uid ? $this->_uid2id($id) : $id; + $headers = iil_C_FetchHeader($this->conn, $mailbox, $msg_id); // write headers cache - $a_msg_headers[$uid] = $header; - $this->update_cache($mailbox.'.msg', $a_msg_headers); + if ($headers) + $this->add_message_cache($mailbox.'.msg', $msg_id, $headers); - return $header; + return $headers; } @@ -577,32 +820,33 @@ $uids = array($uids); foreach ($uids as $uid) - $msg_ids[] = $this->_uid2id($uid); + $msg_ids[$uid] = $this->_uid2id($uid); if ($flag=='UNSEEN') - $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', $msg_ids)); + $result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids))); else - $result = iil_C_Flag($this->conn, $this->mailbox, join(',', $msg_ids), $flag); + $result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag); // reload message headers if cached $cache_key = $this->mailbox.'.msg'; - if ($this->caching_enabled && $result && ($a_cached_headers = $this->get_cache($cache_key))) + if ($this->caching_enabled) { - // close and re-open connection - $this->reconnect(); - - foreach ($uids as $uid) + foreach ($msg_ids as $uid => $id) { - if (isset($a_cached_headers[$uid])) + if ($cached_headers = $this->get_cached_message($cache_key, $uid)) { - unset($this->cache[$cache_key][$uid]); - $this->get_headers($uid); + $this->remove_message_cache($cache_key, $id); + //$this->get_headers($uid); } } + + // close and re-open connection + // this prevents connection problems with Courier + $this->reconnect(); } // set nr of messages that were flaged - $count = sizeof($msg_ids); + $count = count($msg_ids); // clear message count cache if ($result && $flag=='SEEN') @@ -624,7 +868,7 @@ // make shure mailbox exists if (in_array($mailbox, $this->_list_mailboxes())) $saved = iil_C_Append($this->conn, $mailbox, $message); - + if ($saved) { // increase messagecount of the target mailbox @@ -663,20 +907,24 @@ // really deleted from the source mailbox if ($moved) { - $this->expunge($from_mbox, FALSE); - $this->clear_cache($to_mbox.'.msg'); + $this->_expunge($from_mbox, FALSE); $this->_clear_messagecount($from_mbox); $this->_clear_messagecount($to_mbox); } // update cached message headers $cache_key = $from_mbox.'.msg'; - if ($moved && ($a_cached_headers = $this->get_cache($cache_key))) + if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key))) { + $start_index = 100000; foreach ($a_uids as $uid) - unset($a_cached_headers[$uid]); + { + $index = array_search($uid, $a_cache_index); + $start_index = min($index, $start_index); + } - $this->update_cache($cache_key, $a_cached_headers); + // clear cache from the lowest index on + $this->clear_message_cache($cache_key, $start_index); } return $moved; @@ -707,17 +955,23 @@ // really deleted from the mailbox if ($deleted) { - $this->expunge($mailbox, FALSE); + $this->_expunge($mailbox, FALSE); $this->_clear_messagecount($mailbox); } // remove deleted messages from cache - if ($deleted && ($a_cached_headers = $this->get_cache($mailbox.'.msg'))) + $cache_key = $mailbox.'.msg'; + if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key))) { + $start_index = 100000; foreach ($a_uids as $uid) - unset($a_cached_headers[$uid]); + { + $index = array_search($uid, $a_cache_index); + $start_index = min($index, $start_index); + } - $this->update_cache($mailbox.'.msg', $a_cached_headers); + // clear cache from the lowest index on + $this->clear_message_cache($cache_key, $start_index); } return $deleted; @@ -725,13 +979,16 @@ // clear all messages in a specific mailbox - function clear_mailbox($mbox) + function clear_mailbox($mbox=NULL) { - $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox; + $mailbox = !empty($mbox) ? $this->_mod_mailbox($mbox) : $this->mailbox; $msg_count = $this->_messagecount($mailbox, 'ALL'); if ($msg_count>0) + { + $this->clear_message_cache($mailbox.'.msg'); return iil_C_ClearFolder($this->conn, $mailbox); + } else return 0; } @@ -741,18 +998,23 @@ function expunge($mbox='', $clear_cache=TRUE) { $mailbox = $mbox ? $this->_mod_mailbox($mbox) : $this->mailbox; - + return $this->_expunge($mailbox, $clear_cache); + } + + + // send IMAP expunge command and clear cache + function _expunge($mailbox, $clear_cache=TRUE) + { $result = iil_C_Expunge($this->conn, $mailbox); if ($result>=0 && $clear_cache) { - $this->clear_cache($mailbox.'.msg'); + //$this->clear_message_cache($mailbox.'.msg'); $this->_clear_messagecount($mailbox); } return $result; } - /* -------------------------------- @@ -815,13 +1077,18 @@ function create_mailbox($name, $subscribe=FALSE) { $result = FALSE; + + // replace backslashes + $name = preg_replace('/[\\\]+/', '-', $name); + $name_enc = UTF7EncodeString($name); + + // reduce mailbox name to 100 chars + $name_enc = substr($name_enc, 0, 100); + $abs_name = $this->_mod_mailbox($name_enc); $a_mailbox_cache = $this->get_cache('mailboxes'); - - //if (strlen($this->root_ns)) - // $abs_name = $this->root_ns.$abs_name; - + if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache))) $result = iil_C_CreateFolder($this->conn, $abs_name); @@ -866,37 +1133,41 @@ // clear mailboxlist cache if ($deleted) + { + $this->clear_message_cache($mailbox.'.msg'); $this->clear_cache('mailboxes'); + } - return $updated; + return $deleted; } /* -------------------------------- - * internal caching functions + * internal caching methods * --------------------------------*/ function set_caching($set) { - if ($set && function_exists('rcube_read_cache')) + if ($set && is_object($this->db)) $this->caching_enabled = TRUE; else $this->caching_enabled = FALSE; } + function get_cache($key) { // read cache if (!isset($this->cache[$key]) && $this->caching_enabled) { - $cache_data = rcube_read_cache('IMAP.'.$key); + $cache_data = $this->_read_cache_record('IMAP.'.$key); $this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE; } - return $this->cache[$key]; + return $this->cache[$key]; } @@ -915,7 +1186,7 @@ foreach ($this->cache as $key => $data) { if ($this->cache_changes[$key]) - rcube_write_cache('IMAP.'.$key, serialize($data)); + $this->_write_cache_record('IMAP.'.$key, serialize($data)); } } } @@ -926,7 +1197,7 @@ if ($key===NULL) { foreach ($this->cache as $key => $data) - rcube_clear_cache('IMAP.'.$key); + $this->_clear_cache_record('IMAP.'.$key); $this->cache = array(); $this->cache_changed = FALSE; @@ -934,7 +1205,7 @@ } else { - rcube_clear_cache('IMAP.'.$key); + $this->_clear_cache_record('IMAP.'.$key); $this->cache_changes[$key] = FALSE; unset($this->cache[$key]); } @@ -942,8 +1213,279 @@ + function _read_cache_record($key) + { + $cache_data = FALSE; + + if ($this->db) + { + // get cached data from DB + $sql_result = $this->db->query( + "SELECT cache_id, data + FROM ".get_table_name('cache')." + WHERE user_id=? + AND cache_key=?", + $_SESSION['user_id'], + $key); + + if ($sql_arr = $this->db->fetch_assoc($sql_result)) + { + $cache_data = $sql_arr['data']; + $this->cache_keys[$key] = $sql_arr['cache_id']; + } + } + + return $cache_data; + } + + + function _write_cache_record($key, $data) + { + if (!$this->db) + return FALSE; + + // check if we already have a cache entry for this key + if (!isset($this->cache_keys[$key])) + { + $sql_result = $this->db->query( + "SELECT cache_id + FROM ".get_table_name('cache')." + WHERE user_id=? + AND cache_key=?", + $_SESSION['user_id'], + $key); + + if ($sql_arr = $this->db->fetch_assoc($sql_result)) + $this->cache_keys[$key] = $sql_arr['cache_id']; + else + $this->cache_keys[$key] = FALSE; + } + + // update existing cache record + if ($this->cache_keys[$key]) + { + $this->db->query( + "UPDATE ".get_table_name('cache')." + SET created=now(), + data=? + WHERE user_id=? + AND cache_key=?", + $data, + $_SESSION['user_id'], + $key); + } + // add new cache record + else + { + $this->db->query( + "INSERT INTO ".get_table_name('cache')." + (created, user_id, cache_key, data) + VALUES (now(), ?, ?, ?)", + $_SESSION['user_id'], + $key, + $data); + } + } + + + function _clear_cache_record($key) + { + $this->db->query( + "DELETE FROM ".get_table_name('cache')." + WHERE user_id=? + AND cache_key=?", + $_SESSION['user_id'], + $key); + } + + + /* -------------------------------- - * encoding/decoding functions + * message caching methods + * --------------------------------*/ + + + // checks if the cache is up-to-date + // return: -3 = off, -2 = incomplete, -1 = dirty + function check_cache_status($mailbox, $cache_key) + { + if (!$this->caching_enabled) + return -3; + + $cache_index = $this->get_message_cache_index($cache_key, TRUE); + $msg_count = $this->_messagecount($mailbox); + $cache_count = count($cache_index); + + // console("Cache check: $msg_count !== ".count($cache_index)); + + if ($cache_count==$msg_count) + { + // get highest index + $header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count"); + $cache_uid = array_pop($cache_index); + + // uids of highes message matches -> cache seems OK + if ($cache_uid == $header->uid) + return 1; + + // cache is dirty + return -1; + } + // if cache count differs less that 10% report as dirty + else if (abs($msg_count - $cache_count) < $msg_count/10) + return -1; + else + return -2; + } + + + + function get_message_cache($key, $from, $to, $sort_field, $sort_order) + { + $cache_key = "$key:$from:$to:$sort_field:$sort_order"; + $db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size'); + + if (!in_array($sort_field, $db_header_fields)) + $sort_field = 'idx'; + + if ($this->caching_enabled && !isset($this->cache[$cache_key])) + { + $this->cache[$cache_key] = array(); + $sql_result = $this->db->limitquery( + "SELECT idx, uid, headers + FROM ".get_table_name('messages')." + WHERE user_id=? + AND cache_key=? + ORDER BY ".$this->db->quoteIdentifier($sort_field)." ". + strtoupper($sort_order), + $from, + $to-$from, + $_SESSION['user_id'], + $key); + + while ($sql_arr = $this->db->fetch_assoc($sql_result)) + { + $uid = $sql_arr['uid']; + $this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']); + } + } + + return $this->cache[$cache_key]; + } + + + function get_cached_message($key, $uid, $body=FALSE) + { + if (!$this->caching_enabled) + return FALSE; + + $internal_key = '__single_msg'; + if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) || $body)) + { + $sql_select = "idx, uid, headers"; + if ($body) + $sql_select .= ", body"; + + $sql_result = $this->db->query( + "SELECT $sql_select + FROM ".get_table_name('messages')." + WHERE user_id=? + AND cache_key=? + AND uid=?", + $_SESSION['user_id'], + $key, + $uid); + + if ($sql_arr = $this->db->fetch_assoc($sql_result)) + { + $headers = unserialize($sql_arr['headers']); + if (is_object($headers) && !empty($sql_arr['body'])) + $headers->body = $sql_arr['body']; + + $this->cache[$internal_key][$uid] = $headers; + } + } + + return $this->cache[$internal_key][$uid]; + } + + + function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC') + { + static $sa_message_index = array(); + + if (!empty($sa_message_index[$key]) && !$force) + return $sa_message_index[$key]; + + $sa_message_index[$key] = array(); + $sql_result = $this->db->query( + "SELECT idx, uid + FROM ".get_table_name('messages')." + WHERE user_id=? + AND cache_key=? + ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order, + $_SESSION['user_id'], + $key); + + while ($sql_arr = $this->db->fetch_assoc($sql_result)) + $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid']; + + return $sa_message_index[$key]; + } + + + function add_message_cache($key, $index, $headers) + { + if (!is_object($headers) || empty($headers->uid)) + return; + + $this->db->query( + "INSERT INTO ".get_table_name('messages')." + (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers) + VALUES (?, 0, ?, now(), ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?)", + $_SESSION['user_id'], + $key, + $index, + $headers->uid, + substr($this->decode_header((string)$headers->subject, TRUE), 0, 128), + substr($this->decode_header((string)$headers->from, TRUE), 0, 128), + substr($this->decode_header((string)$headers->to, TRUE), 0, 128), + substr($this->decode_header((string)$headers->cc, TRUE), 0, 128), + (int)$headers->size, + serialize($headers)); + } + + + function remove_message_cache($key, $index) + { + $this->db->query( + "DELETE FROM ".get_table_name('messages')." + WHERE user_id=? + AND cache_key=? + AND idx=?", + $_SESSION['user_id'], + $key, + $index); + } + + + function clear_message_cache($key, $start_index=1) + { + $this->db->query( + "DELETE FROM ".get_table_name('messages')." + WHERE user_id=? + AND cache_key=? + AND idx>=?", + $_SESSION['user_id'], + $key, + $start_index); + } + + + + + /* -------------------------------- + * encoding/decoding methods * --------------------------------*/ @@ -977,7 +1519,19 @@ } - function decode_header($input) + function decode_header($input, $remove_quotes=FALSE) + { + $str = $this->decode_mime_string((string)$input); + if ($str{0}=='"' && $remove_quotes) + { + $str = str_replace('"', '', $str); + } + + return $str; + } + + + function decode_mime_string($input) { $out = ''; @@ -993,8 +1547,8 @@ $encstr = substr($input, $pos+2, ($end_pos-$pos-2)); $rest = substr($input, $end_pos+2); - $out .= $this->decode_mime_string($encstr); - $out .= $this->decode_header($rest); + $out .= rcube_imap::_decode_mime_string_part($encstr); + $out .= rcube_imap::decode_mime_string($rest); return $out; } @@ -1003,7 +1557,7 @@ } - function decode_mime_string($str) + function _decode_mime_string_part($str) { $a = explode('?', $str); $count = count($a); @@ -1022,10 +1576,10 @@ $rest = quoted_printable_decode($rest); } - return decode_specialchars($rest, $a[0]); + return rcube_charset_convert($rest, $a[0]); } else - return $str; //we dont' know what to do with this + return $str; // we dont' know what to do with this } @@ -1073,10 +1627,11 @@ function charset_decode($body, $ctype_param) { if (is_array($ctype_param) && !empty($ctype_param['charset'])) - return decode_specialchars($body, $ctype_param['charset']); + return rcube_charset_convert($body, $ctype_param['charset']); return $body; } + /* -------------------------------- @@ -1131,6 +1686,33 @@ $this->uid_id_map[$mbox][$uid] = iil_C_UID2ID($this->conn, $mbox, $uid); return $this->uid_id_map[$mbox][$uid]; + } + + + // parse string or array of server capabilities and put them in internal array + function _parse_capability($caps) + { + if (!is_array($caps)) + $cap_arr = explode(' ', $caps); + else + $cap_arr = $caps; + + foreach ($cap_arr as $cap) + { + if ($cap=='CAPABILITY') + continue; + + if (strpos($cap, '=')>0) + { + list($key, $value) = explode('=', $cap); + if (!is_array($this->capabilities[$key])) + $this->capabilities[$key] = array(); + + $this->capabilities[$key][] = $value; + } + else + $this->capabilities[$cap] = TRUE; + } } @@ -1189,6 +1771,10 @@ // add incremental value to messagecount $a_mailbox_cache[$mailbox][$mode] += $increment; + + // there's something wrong, delete from cache + if ($a_mailbox_cache[$mailbox][$mode] < 0) + unset($a_mailbox_cache[$mailbox][$mode]); // write back to cache $this->update_cache('messagecount', $a_mailbox_cache); -- Gitblit v1.9.1