22 commits - plugins/libkolab

Jeroen van Meeuwen vanmeeuwen at kolabsys.com
Thu Oct 17 16:48:16 CEST 2013


 plugins/libkolab/SQL/mysql.initial.sql                     |  158 +++
 plugins/libkolab/SQL/mysql/2013100400.sql                  |  174 ++++
 plugins/libkolab/bin/modcache.sh                           |   49 -
 plugins/libkolab/config.inc.php.dist                       |    6 
 plugins/libkolab/lib/kolab_storage.php                     |    4 
 plugins/libkolab/lib/kolab_storage_cache.php               |  438 ++++++----
 plugins/libkolab/lib/kolab_storage_cache_configuration.php |   40 
 plugins/libkolab/lib/kolab_storage_cache_contact.php       |   45 +
 plugins/libkolab/lib/kolab_storage_cache_event.php         |   49 +
 plugins/libkolab/lib/kolab_storage_cache_file.php          |   44 +
 plugins/libkolab/lib/kolab_storage_cache_freebusy.php      |   27 
 plugins/libkolab/lib/kolab_storage_cache_journal.php       |   28 
 plugins/libkolab/lib/kolab_storage_cache_mongodb.php       |  561 +++++++++++++
 plugins/libkolab/lib/kolab_storage_cache_note.php          |   27 
 plugins/libkolab/lib/kolab_storage_cache_task.php          |   44 +
 plugins/libkolab/lib/kolab_storage_folder.php              |   98 +-
 16 files changed, 1567 insertions(+), 225 deletions(-)

New commits:
commit 4065542e07cf2a355cee3e0d59454516676993f2
Merge: 8039a2f 38d113d
Author: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen at kolabsys.com>
Date:   Thu Oct 17 16:48:08 2013 +0200

    Merge branch 'dev/kolab-cache-refactoring'
    
    Conflicts:
    	plugins/libkolab/lib/kolab_storage_cache.php



commit 38d113d413704b0b1d19e43a759052e53cbacbd5
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Tue Oct 15 14:11:30 2013 +0200

    Escape % in resource prefix used for LIKE queries

diff --git a/plugins/libkolab/bin/modcache.sh b/plugins/libkolab/bin/modcache.sh
index 9285ec7..da6e4f8 100755
--- a/plugins/libkolab/bin/modcache.sh
+++ b/plugins/libkolab/bin/modcache.sh
@@ -157,7 +157,7 @@ default:
  */
 function resource_prefix($opts)
 {
-    return 'imap://' . urlencode($opts['username']) . '@' . $opts['host'] . '/';
+    return 'imap://' . str_replace('%', '\\%', urlencode($opts['username'])) . '@' . $opts['host'] . '/';
 }
 
 


commit bccb7c14177b8efb244d2a828080d239d19af044
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Tue Oct 15 10:05:32 2013 +0200

    Adapt the modcache script to the new kolab caching architecture

diff --git a/plugins/libkolab/bin/modcache.sh b/plugins/libkolab/bin/modcache.sh
index 5ac9a21..9285ec7 100755
--- a/plugins/libkolab/bin/modcache.sh
+++ b/plugins/libkolab/bin/modcache.sh
@@ -4,7 +4,7 @@
 /**
  * Kolab storage cache modification script
  *
- * @version 3.0
+ * @version 3.1
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  *
  * Copyright (C) 2012, Kolab Systems AG <contact at kolabsys.com>
@@ -56,7 +56,14 @@ $opts = get_opt(array(
 $opts['username'] = !empty($opts[1]) ? $opts[1] : $opts['user'];
 $action = $opts[0];
 
-$rcmail = rcube::get_instance();
+$rcmail = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS);
+
+
+// connect to database
+$db = $rcmail->get_dbh();
+$db->db_connect('w');
+if (!$db->is_connected() || $db->is_error())
+    die("No DB connection\n");
 
 
 /*
@@ -68,32 +75,42 @@ switch (strtolower($action)) {
  * Clear/expunge all cache records
  */
 case 'expunge':
+    $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','configuration','event','file','journal','note','task');
+    $folder_types_db = array_map(array($db, 'quote'), $folder_types);
     $expire = strtotime(!empty($opts[2]) ? $opts[2] : 'now - 10 days');
-    $sql_add = " AND created <= '" . date('Y-m-d 00:00:00', $expire) . "'";
+    $sql_where = "type IN (" . join(',', $folder_types_db) . ")";
+
+    if ($opts['username']) {
+        $sql_where .= ' AND resource LIKE ?';
+    }
+
+    $sql_query = "DELETE FROM %s WHERE folder_id IN (SELECT folder_id FROM kolab_folders WHERE $sql_where) AND created <= " . $db->quote(date('Y-m-d 00:00:00', $expire));
     if ($opts['limit']) {
-        $sql_add .= ' LIMIT ' . intval($opts['limit']);
+        $sql_query = ' LIMIT ' . intval($opts['limit']);
+    }
+    foreach ($folder_types as $type) {
+        $table_name = 'kolab_cache_' . $type;
+        $db->query(sprintf($sql_query, $table_name), resource_prefix($opts).'%');
+        echo $db->affected_rows() . " records deleted from '$table_name'\n";
     }
 
-case 'clear':
-    // connect to database
-    $db = $rcmail->get_dbh();
-    $db->db_connect('w');
-    if (!$db->is_connected() || $db->is_error())
-        die("No DB connection\n");
+    $db->query("UPDATE kolab_folders SET ctag='' WHERE $sql_where", resource_prefix($opts).'%');
+    break;
 
-    $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','distribution-list','event','task','configuration','file');
+case 'clear':
+    $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','configuration','event','file','journal','note','task');
     $folder_types_db = array_map(array($db, 'quote'), $folder_types);
 
     if ($opts['all']) {
-        $sql_query = "DELETE FROM kolab_cache WHERE type IN (" . join(',', $folder_types_db) . ")";
+        $sql_query = "DELETE FROM kolab_folders WHERE 1";
     }
     else if ($opts['username']) {
-        $sql_query = "DELETE FROM kolab_cache WHERE type IN (" . join(',', $folder_types_db) . ") AND resource LIKE ?";
+        $sql_query = "DELETE FROM kolab_folders WHERE type IN (" . join(',', $folder_types_db) . ") AND resource LIKE ?";
     }
 
     if ($sql_query) {
         $db->query($sql_query . $sql_add, resource_prefix($opts).'%');
-        echo $db->affected_rows() . " records deleted from 'kolab_cache'\n";
+        echo $db->affected_rows() . " records deleted from 'kolab_folders'\n";
     }
     break;
 
@@ -106,7 +123,7 @@ case 'prewarm':
     $rcmail->plugins->load_plugin('libkolab');
 
     if (authenticate($opts)) {
-        $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','event','task','configuration','file');
+        $folder_types = $opts['type'] ? explode(',', $opts['type']) : array('contact','configuration','event','file','task');
         foreach ($folder_types as $type) {
             // sync every folder of the given type
             foreach (kolab_storage::get_folders($type) as $folder) {


commit 053a640b87ea078e6c31a1929aa797ef1966176a
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Mon Oct 14 15:04:00 2013 +0200

    Fix PHP warning when _sql_where() is callend with NULL as an argument

diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index 8e4e19c..9a16799 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -494,7 +494,7 @@ class kolab_storage_cache
     protected function _sql_where($query)
     {
         $sql_where = '';
-        foreach ($query as $param) {
+        foreach ((array) $query as $param) {
             if (is_array($param[0])) {
                 $subq = array();
                 foreach ($param[0] as $q) {


commit ee30f6fb8368ba8ee59fcdcd805d41eaef126f33
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Wed Oct 9 10:01:42 2013 +0200

    Fix typo in last commit

diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index 2c42103..0a895b6 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -433,7 +433,7 @@ class kolab_storage_folder
         // string equals type query
         // FIXME: should not be called this way!
         if (is_string($query)) {
-            return $this->cache->has_type_col() && !empty($type) ? array(array('type','=',$query)) : array();
+            return $this->cache->has_type_col() && !empty($query) ? array(array('type','=',$query)) : array();
         }
 
         foreach ((array)$query as $i => $param) {


commit e8f2cd98a32c97e5e4a358dacd7494f427531847
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Wed Oct 9 09:58:19 2013 +0200

    Fix get_objects() when called with no argument, folder type is used

diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index 34d119e..2c42103 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -384,7 +384,7 @@ class kolab_storage_folder
         $this->cache->synchronize();
 
         // fetch objects from cache
-        return $this->cache->select(array());
+        return $this->cache->select($this->_prepare_query($type));
     }
 
 
@@ -433,7 +433,7 @@ class kolab_storage_folder
         // string equals type query
         // FIXME: should not be called this way!
         if (is_string($query)) {
-            return $this->cache->has_type_col() ? array(array('type','=',$query)) : array();
+            return $this->cache->has_type_col() && !empty($type) ? array(array('type','=',$query)) : array();
         }
 
         foreach ((array)$query as $i => $param) {


commit d67a7f0d76ae432f6c05d3c071210ae7b457a1c7
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Tue Oct 8 13:37:49 2013 +0200

    Fix reference to kolab_cache table in count() method

diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index 97b6463..8e4e19c 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -468,7 +468,7 @@ class kolab_storage_cache
             $this->_read_folder_data();
 
             $sql_result = $this->db->query(
-                "SELECT COUNT(*) AS numrows FROM kolab_cache ".
+                "SELECT COUNT(*) AS numrows FROM $this->cache_table ".
                 "WHERE folder_id=? " . $this->_sql_where($query),
                 $this->folder_id
             );


commit cb8249d414a65540a3d594d078cb810095a6f410
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Oct 7 20:06:04 2013 +0200

    Rename kolab_folders.ID to kolab_folders.folder_id to be in sync with other Roundcube database conventions

diff --git a/plugins/libkolab/SQL/mysql.initial.sql b/plugins/libkolab/SQL/mysql.initial.sql
index c6bcb89..4f23a52 100644
--- a/plugins/libkolab/SQL/mysql.initial.sql
+++ b/plugins/libkolab/SQL/mysql.initial.sql
@@ -10,12 +10,12 @@
 DROP TABLE IF EXISTS `kolab_folders`;
 
 CREATE TABLE `kolab_folders` (
-  `ID` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+  `folder_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
   `resource` VARCHAR(255) NOT NULL,
   `type` VARCHAR(32) NOT NULL,
   `synclock` INT(10) NOT NULL DEFAULT '0',
   `ctag` VARCHAR(40) DEFAULT NULL,
-  PRIMARY KEY(`ID`),
+  PRIMARY KEY(`folder_id`),
   INDEX `resource_type` (`resource`, `type`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
@@ -35,7 +35,7 @@ CREATE TABLE `kolab_cache_contact` (
   `words` TEXT NOT NULL,
   `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
   CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`),
   INDEX `contact_type` (`folder_id`,`type`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
@@ -55,7 +55,7 @@ CREATE TABLE `kolab_cache_event` (
   `dtstart` DATETIME,
   `dtend` DATETIME,
   CONSTRAINT `fk_kolab_cache_event_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
@@ -74,7 +74,7 @@ CREATE TABLE `kolab_cache_task` (
   `dtstart` DATETIME,
   `dtend` DATETIME,
   CONSTRAINT `fk_kolab_cache_task_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
@@ -93,7 +93,7 @@ CREATE TABLE `kolab_cache_journal` (
   `dtstart` DATETIME,
   `dtend` DATETIME,
   CONSTRAINT `fk_kolab_cache_journal_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
@@ -110,7 +110,7 @@ CREATE TABLE `kolab_cache_note` (
   `tags` VARCHAR(255) NOT NULL,
   `words` TEXT NOT NULL,
   CONSTRAINT `fk_kolab_cache_note_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
@@ -128,7 +128,7 @@ CREATE TABLE `kolab_cache_file` (
   `words` TEXT NOT NULL,
   `filename` varchar(255) DEFAULT NULL,
   CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`),
   INDEX `folder_filename` (`folder_id`, `filename`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
@@ -147,7 +147,7 @@ CREATE TABLE `kolab_cache_configuration` (
   `words` TEXT NOT NULL,
   `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
   CONSTRAINT `fk_kolab_cache_configuration_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`),
   INDEX `configuration_type` (`folder_id`,`type`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
@@ -167,7 +167,7 @@ CREATE TABLE `kolab_cache_freebusy` (
   `dtstart` DATETIME,
   `dtend` DATETIME,
   CONSTRAINT `fk_kolab_cache_freebusy_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
diff --git a/plugins/libkolab/SQL/mysql/2013100400.sql b/plugins/libkolab/SQL/mysql/2013100400.sql
index efd4962..d41d0e1 100644
--- a/plugins/libkolab/SQL/mysql/2013100400.sql
+++ b/plugins/libkolab/SQL/mysql/2013100400.sql
@@ -1,10 +1,10 @@
 CREATE TABLE `kolab_folders` (
-  `ID` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+  `folder_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
   `resource` VARCHAR(255)  NOT NULL,
   `type` VARCHAR(32) NOT NULL,
   `synclock` INT(10) NOT NULL DEFAULT '0',
   `ctag` VARCHAR(40) DEFAULT NULL,
-  PRIMARY KEY(`ID`),
+  PRIMARY KEY(`folder_id`),
   INDEX `resource_type` (`resource`, `type`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
@@ -20,7 +20,7 @@ CREATE TABLE `kolab_cache_contact` (
   `words` TEXT NOT NULL,
   `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
   CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`),
   INDEX `contact_type` (`folder_id`,`type`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
@@ -38,7 +38,7 @@ CREATE TABLE `kolab_cache_event` (
   `dtstart` DATETIME,
   `dtend` DATETIME,
   CONSTRAINT `fk_kolab_cache_event_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
@@ -55,7 +55,7 @@ CREATE TABLE `kolab_cache_task` (
   `dtstart` DATETIME,
   `dtend` DATETIME,
   CONSTRAINT `fk_kolab_cache_task_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
@@ -72,7 +72,7 @@ CREATE TABLE `kolab_cache_journal` (
   `dtstart` DATETIME,
   `dtend` DATETIME,
   CONSTRAINT `fk_kolab_cache_journal_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
@@ -87,7 +87,7 @@ CREATE TABLE `kolab_cache_note` (
   `tags` VARCHAR(255) NOT NULL,
   `words` TEXT NOT NULL,
   CONSTRAINT `fk_kolab_cache_note_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
@@ -103,7 +103,7 @@ CREATE TABLE `kolab_cache_file` (
   `words` TEXT NOT NULL,
   `filename` varchar(255) DEFAULT NULL,
   CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`),
   INDEX `folder_filename` (`folder_id`, `filename`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
@@ -120,7 +120,7 @@ CREATE TABLE `kolab_cache_configuration` (
   `words` TEXT NOT NULL,
   `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
   CONSTRAINT `fk_kolab_cache_configuration_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`),
   INDEX `configuration_type` (`folder_id`,`type`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
@@ -138,7 +138,7 @@ CREATE TABLE `kolab_cache_freebusy` (
   `dtstart` DATETIME,
   `dtend` DATETIME,
   CONSTRAINT `fk_kolab_cache_freebusy_folder` FOREIGN KEY (`folder_id`)
-    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+    REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
@@ -150,24 +150,24 @@ INSERT INTO kolab_folders (resource, type)
   FROM  kolab_cache WHERE type IN ('event','contact','task','file');
 
 INSERT INTO kolab_cache_event (folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend)
-  SELECT kolab_folders.ID, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend
+  SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend
   FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource)
-  WHERE kolab_cache.type = 'event' AND kolab_folders.ID IS NOT NULL;
+  WHERE kolab_cache.type = 'event' AND kolab_folders.folder_id IS NOT NULL;
 
 INSERT INTO kolab_cache_task (folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend)
-  SELECT kolab_folders.ID, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend
+  SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend
   FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource)
-  WHERE kolab_cache.type = 'task' AND kolab_folders.ID IS NOT NULL;
+  WHERE kolab_cache.type = 'task' AND kolab_folders.folder_id IS NOT NULL;
 
 INSERT INTO kolab_cache_contact (folder_id, msguid, uid, created, changed, data, xml, tags, words, type)
-  SELECT kolab_folders.ID, msguid, uid, created, changed, data, xml, tags, words, kolab_cache.type
+  SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, kolab_cache.type
   FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource)
-  WHERE kolab_cache.type IN ('contact','distribution-list') AND kolab_folders.ID IS NOT NULL;
+  WHERE kolab_cache.type IN ('contact','distribution-list') AND kolab_folders.folder_id IS NOT NULL;
 
 INSERT INTO kolab_cache_file (folder_id, msguid, uid, created, changed, data, xml, tags, words, filename)
-  SELECT kolab_folders.ID, msguid, uid, created, changed, data, xml, tags, words, filename
+  SELECT kolab_folders.folder_id, msguid, uid, created, changed, data, xml, tags, words, filename
   FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource)
-  WHERE kolab_cache.type = 'file' AND kolab_folders.ID IS NOT NULL;
+  WHERE kolab_cache.type = 'file' AND kolab_folders.folder_id IS NOT NULL;
 
 
 DROP TABLE IF EXISTS `kolab_cache`;
diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index 3600e74..97b6463 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -746,10 +746,10 @@ class kolab_storage_cache
         if (!empty($this->folder_id))
             return;
 
-        $sql_arr = $this->db->fetch_assoc($this->db->query("SELECT ID, synclock, ctag FROM $this->folders_table WHERE resource=?", $this->resource_uri));
+        $sql_arr = $this->db->fetch_assoc($this->db->query("SELECT folder_id, synclock, ctag FROM $this->folders_table WHERE resource=?", $this->resource_uri));
         if ($sql_arr) {
             $this->metadata = $sql_arr;
-            $this->folder_id = $sql_arr['ID'];
+            $this->folder_id = $sql_arr['folder_id'];
         }
         else {
             $this->db->query("INSERT INTO $this->folders_table (resource, type) VALUES (?, ?)", $this->resource_uri, $this->folder->type);
@@ -767,7 +767,7 @@ class kolab_storage_cache
             return;
 
         $this->_read_folder_data();
-        $sql_query = "SELECT synclock, ctag FROM $this->folders_table WHERE ID=?";
+        $sql_query = "SELECT synclock, ctag FROM $this->folders_table WHERE folder_id=?";
 
         // abort if database is not set-up
         if ($this->db->is_error()) {
@@ -784,7 +784,7 @@ class kolab_storage_cache
         }
 
         // set lock
-        $this->db->query("UPDATE $this->folders_table SET synclock = ? WHERE ID = ?", time(), $this->folder_id);
+        $this->db->query("UPDATE $this->folders_table SET synclock = ? WHERE folder_id = ?", time(), $this->folder_id);
     }
 
     /**
@@ -796,7 +796,7 @@ class kolab_storage_cache
             return;
 
         $this->db->query(
-            "UPDATE $this->folders_table SET synclock = 0, ctag = ? WHERE ID = ?",
+            "UPDATE $this->folders_table SET synclock = 0, ctag = ? WHERE folder_id = ?",
             $this->metadata['ctag'],
             $this->folder_id
         );


commit 6fb8c71095b0963f8396c995126dcfb02a86e25c
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Oct 7 19:20:45 2013 +0200

    Fix queries to kolab_storage that include (obsolete) 'type'

diff --git a/plugins/libkolab/SQL/mysql.initial.sql b/plugins/libkolab/SQL/mysql.initial.sql
index 97218b6..c6bcb89 100644
--- a/plugins/libkolab/SQL/mysql.initial.sql
+++ b/plugins/libkolab/SQL/mysql.initial.sql
@@ -126,7 +126,6 @@ CREATE TABLE `kolab_cache_file` (
   `xml` TEXT NOT NULL,
   `tags` VARCHAR(255) NOT NULL,
   `words` TEXT NOT NULL,
-  `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
   `filename` varchar(255) DEFAULT NULL,
   CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`)
     REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
diff --git a/plugins/libkolab/SQL/mysql/2013100400.sql b/plugins/libkolab/SQL/mysql/2013100400.sql
index aed3557..efd4962 100644
--- a/plugins/libkolab/SQL/mysql/2013100400.sql
+++ b/plugins/libkolab/SQL/mysql/2013100400.sql
@@ -101,7 +101,6 @@ CREATE TABLE `kolab_cache_file` (
   `xml` TEXT NOT NULL,
   `tags` VARCHAR(255) NOT NULL,
   `words` TEXT NOT NULL,
-  `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
   `filename` varchar(255) DEFAULT NULL,
   CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`)
     REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index ecb41d9..34d119e 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -431,17 +431,16 @@ class kolab_storage_folder
     private function _prepare_query($query)
     {
         // string equals type query
+        // FIXME: should not be called this way!
         if (is_string($query)) {
-            if ($this->cache->has_type_col()) {
-                $query = array(array('type','=',$query));
-            }
-            else {
-                return array();
-            }
+            return $this->cache->has_type_col() ? array(array('type','=',$query)) : array();
         }
 
         foreach ((array)$query as $i => $param) {
-            if (($param[0] == 'dtstart' || $param[0] == 'dtend' || $param[0] == 'changed')) {
+            if ($param[0] == 'type' && !$this->cache->has_type_col()) {
+                unset($query[$i]);
+            }
+            else if (($param[0] == 'dtstart' || $param[0] == 'dtend' || $param[0] == 'changed')) {
                 if (is_object($param[2]) && is_a($param[2], 'DateTime'))
                     $param[2] = $param[2]->format('U');
                 if (is_numeric($param[2]))


commit 6b89e36c4e00c134efa6c552428936e1787beacd
Merge: 66e33b9 7161951
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Oct 7 17:34:17 2013 +0200

    Merge remote-tracking branch 'origin/libkolab-cache-bypass' into dev/kolab-cache-refactoring

diff --cc plugins/libkolab/lib/kolab_storage_cache.php
index 6a3e0d6,1165631..3600e74
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@@ -128,61 -96,57 +128,66 @@@ class kolab_storage_cach
          // increase time limit
          @set_time_limit($this->max_sync_lock_time);
  
 -        // lock synchronization for this folder or wait if locked
 -        $this->_sync_lock();
 +        // read cached folder metadata
 +        $this->_read_folder_data();
  
 -        // disable messages cache if configured to do so
 -        $this->bypass(true);
 +        // check cache status hash first ($this->metadata is set in _read_folder_data())
 +        if ($this->metadata['ctag'] != $this->folder->get_ctag()) {
  
 -        // synchronize IMAP mailbox cache
 -        $this->imap->folder_sync($this->folder->name);
 +            // lock synchronization for this folder or wait if locked
 +            $this->_sync_lock();
  
 -        // compare IMAP index with object cache index
 -        $imap_index = $this->imap->index($this->folder->name);
 -        $this->index = $imap_index->get();
++            // disable messages cache if configured to do so
++            $this->bypass(true);
+ 
 -        // determine objects to fetch or to invalidate
 -        if ($this->ready) {
 -            // read cache index
 -            $sql_result = $this->db->query(
 -                "SELECT msguid, uid FROM kolab_cache WHERE resource=? AND type<>?",
 -                $this->resource_uri,
 -                'lock'
 -            );
 +            // synchronize IMAP mailbox cache
 +            $this->imap->folder_sync($this->folder->name);
  
 -            $old_index = array();
 -            while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
 -                $old_index[] = $sql_arr['msguid'];
 -                $this->uid2msg[$sql_arr['uid']] = $sql_arr['msguid'];
 -            }
 +            // compare IMAP index with object cache index
 +            $imap_index = $this->imap->index($this->folder->name);
 +            $this->index = $imap_index->get();
  
 -            // fetch new objects from imap
 -            foreach (array_diff($this->index, $old_index) as $msguid) {
 -                if ($object = $this->folder->read_object($msguid, '*')) {
 -                    $this->_extended_insert($msguid, $object);
 -                }
 -            }
 -            $this->_extended_insert(0, null);
 -
 -            // delete invalid entries from local DB
 -            $del_index = array_diff($old_index, $this->index);
 -            if (!empty($del_index)) {
 -                $quoted_ids = join(',', array_map(array($this->db, 'quote'), $del_index));
 -                $this->db->query(
 -                    "DELETE FROM kolab_cache WHERE resource=? AND msguid IN ($quoted_ids)",
 -                    $this->resource_uri
 +            // determine objects to fetch or to invalidate
 +            if ($this->ready) {
 +                // read cache index
 +                $sql_result = $this->db->query(
 +                    "SELECT msguid, uid FROM $this->cache_table WHERE folder_id=?",
 +                    $this->folder_id
                  );
 +
 +                $old_index = array();
 +                while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
 +                    $old_index[] = $sql_arr['msguid'];
 +                    $this->uid2msg[$sql_arr['uid']] = $sql_arr['msguid'];
 +                }
 +
 +                // fetch new objects from imap
 +                foreach (array_diff($this->index, $old_index) as $msguid) {
 +                    if ($object = $this->folder->read_object($msguid, '*')) {
 +                        $this->_extended_insert($msguid, $object);
 +                    }
 +                }
 +                $this->_extended_insert(0, null);
 +
 +                // delete invalid entries from local DB
 +                $del_index = array_diff($old_index, $this->index);
 +                if (!empty($del_index)) {
 +                    $quoted_ids = join(',', array_map(array($this->db, 'quote'), $del_index));
 +                    $this->db->query(
 +                        "DELETE FROM $this->cache_table WHERE folder_id=? AND msguid IN ($quoted_ids)",
 +                        $this->folder_id
 +                    );
 +                }
 +
 +                // update ctag value (will be written to database in _sync_unlock())
 +                $this->metadata['ctag'] = $this->folder->get_ctag();
              }
 -        }
  
 -        $this->bypass(false);
++            $this->bypass(false);
+ 
 -        // remove lock
 -        $this->_sync_unlock();
 +            // remove lock
 +            $this->_sync_unlock();
 +        }
  
          $this->synched = time();
      }
@@@ -814,15 -794,58 +825,71 @@@
      }
  
      /**
 +     * Getter for protected member variables
 +     */
 +    public function __get($name)
 +    {
 +        if ($name == 'folder_id') {
 +            $this->_read_folder_data();
 +        }
 +
 +        return $this->$name;
 +    }
 +
++    /**
+      * Bypass Roundcube messages cache.
+      * Roundcube cache duplicates information already stored in kolab_cache.
+      *
+      * @param bool $disable True disables, False enables messages cache
+      */
+     public function bypass($disable = false)
+     {
+         // if kolab cache is disabled do nothing
+         if (!$this->enabled) {
+             return;
+         }
+ 
+         static $messages_cache, $cache_bypass;
+ 
+         if ($messages_cache === null) {
+             $rcmail = rcube::get_instance();
+             $messages_cache = (bool) $rcmail->config->get('messages_cache');
+             $cache_bypass   = (int) $rcmail->config->get('kolab_messages_cache_bypass');
+         }
+ 
+         if ($messages_cache) {
+             // handle recurrent (multilevel) bypass() calls
+             if ($disable) {
+                 $this->cache_bypassed += 1;
+                 if ($this->cache_bypassed > 1) {
+                     return;
+                 }
+             }
+             else {
+                 $this->cache_bypassed -= 1;
+                 if ($this->cache_bypassed > 0) {
+                     return;
+                 }
+             }
+ 
+             switch ($cache_bypass) {
+                 case 2:
+                     // Disable messages cache completely
+                     $this->imap->set_messages_caching(!$disable);
+                     break;
+ 
+                 case 1:
+                     // We'll disable messages cache, but keep index cache.
+                     // Default mode is both (MODE_INDEX | MODE_MESSAGE)
+                     $mode = rcube_imap_cache::MODE_INDEX;
+ 
+                     if (!$disable) {
+                         $mode |= rcube_imap_cache::MODE_MESSAGE;
+                     }
+ 
+                     $this->imap->set_messages_caching(true, $mode);
+             }
+         }
+     }
++
  }


commit 71619510c49633527c66de8ca2d98c06a71c4c35
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Mon Oct 7 15:57:55 2013 +0200

    Improve bypass() method so it works "recursively"

diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index 0e30dfa..1165631 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -99,16 +99,16 @@ class kolab_storage_cache
         // lock synchronization for this folder or wait if locked
         $this->_sync_lock();
 
-        // synchronize IMAP mailbox cache
+        // disable messages cache if configured to do so
         $this->bypass(true);
+
+        // synchronize IMAP mailbox cache
         $this->imap->folder_sync($this->folder->name);
 
         // compare IMAP index with object cache index
         $imap_index = $this->imap->index($this->folder->name);
         $this->index = $imap_index->get();
 
-        $this->bypass(false);
-
         // determine objects to fetch or to invalidate
         if ($this->ready) {
             // read cache index
@@ -143,6 +143,8 @@ class kolab_storage_cache
             }
         }
 
+        $this->bypass(false);
+
         // remove lock
         $this->_sync_unlock();
 
@@ -813,11 +815,25 @@ class kolab_storage_cache
         }
 
         if ($messages_cache) {
+            // handle recurrent (multilevel) bypass() calls
+            if ($disable) {
+                $this->cache_bypassed += 1;
+                if ($this->cache_bypassed > 1) {
+                    return;
+                }
+            }
+            else {
+                $this->cache_bypassed -= 1;
+                if ($this->cache_bypassed > 0) {
+                    return;
+                }
+            }
+
             switch ($cache_bypass) {
                 case 2:
                     // Disable messages cache completely
                     $this->imap->set_messages_caching(!$disable);
-                    return;
+                    break;
 
                 case 1:
                     // We'll disable messages cache, but keep index cache.


commit 66e33b946c3d486e09c6c15a1bb3cb1f2d46064a
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Oct 7 15:26:49 2013 +0200

    Better charset settings for kolab_folders table; add statements to migrate existing cache data into the new table structure

diff --git a/plugins/libkolab/SQL/mysql.initial.sql b/plugins/libkolab/SQL/mysql.initial.sql
index 8e9c8dc..97218b6 100644
--- a/plugins/libkolab/SQL/mysql.initial.sql
+++ b/plugins/libkolab/SQL/mysql.initial.sql
@@ -11,10 +11,10 @@ DROP TABLE IF EXISTS `kolab_folders`;
 
 CREATE TABLE `kolab_folders` (
   `ID` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
-  `resource` VARCHAR(255) CHARACTER SET ascii NOT NULL,
-  `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
+  `resource` VARCHAR(255) NOT NULL,
+  `type` VARCHAR(32) NOT NULL,
   `synclock` INT(10) NOT NULL DEFAULT '0',
-  `ctag` VARCHAR(32) DEFAULT NULL,
+  `ctag` VARCHAR(40) DEFAULT NULL,
   PRIMARY KEY(`ID`),
   INDEX `resource_type` (`resource`, `type`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
diff --git a/plugins/libkolab/SQL/mysql/2013100400.sql b/plugins/libkolab/SQL/mysql/2013100400.sql
index cdc1df6..aed3557 100644
--- a/plugins/libkolab/SQL/mysql/2013100400.sql
+++ b/plugins/libkolab/SQL/mysql/2013100400.sql
@@ -1,11 +1,9 @@
-DROP TABLE IF EXISTS `kolab_cache`;
-
 CREATE TABLE `kolab_folders` (
   `ID` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
-  `resource` VARCHAR(255) CHARACTER SET ascii NOT NULL,
-  `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
+  `resource` VARCHAR(255)  NOT NULL,
+  `type` VARCHAR(32) NOT NULL,
   `synclock` INT(10) NOT NULL DEFAULT '0',
-  `ctag` VARCHAR(32) DEFAULT NULL,
+  `ctag` VARCHAR(40) DEFAULT NULL,
   PRIMARY KEY(`ID`),
   INDEX `resource_type` (`resource`, `type`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
@@ -145,3 +143,33 @@ CREATE TABLE `kolab_cache_freebusy` (
   PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
+
+-- Migrate data from old kolab_cache table
+
+INSERT INTO kolab_folders (resource, type)
+  SELECT DISTINCT resource, type
+  FROM  kolab_cache WHERE type IN ('event','contact','task','file');
+
+INSERT INTO kolab_cache_event (folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend)
+  SELECT kolab_folders.ID, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend
+  FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource)
+  WHERE kolab_cache.type = 'event' AND kolab_folders.ID IS NOT NULL;
+
+INSERT INTO kolab_cache_task (folder_id, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend)
+  SELECT kolab_folders.ID, msguid, uid, created, changed, data, xml, tags, words, dtstart, dtend
+  FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource)
+  WHERE kolab_cache.type = 'task' AND kolab_folders.ID IS NOT NULL;
+
+INSERT INTO kolab_cache_contact (folder_id, msguid, uid, created, changed, data, xml, tags, words, type)
+  SELECT kolab_folders.ID, msguid, uid, created, changed, data, xml, tags, words, kolab_cache.type
+  FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource)
+  WHERE kolab_cache.type IN ('contact','distribution-list') AND kolab_folders.ID IS NOT NULL;
+
+INSERT INTO kolab_cache_file (folder_id, msguid, uid, created, changed, data, xml, tags, words, filename)
+  SELECT kolab_folders.ID, msguid, uid, created, changed, data, xml, tags, words, filename
+  FROM kolab_cache LEFT JOIN kolab_folders ON (kolab_folders.resource = kolab_cache.resource)
+  WHERE kolab_cache.type = 'file' AND kolab_folders.ID IS NOT NULL;
+
+
+DROP TABLE IF EXISTS `kolab_cache`;
+


commit 9d174daf9f98e8e60cddf77cf1416e42470ba77b
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Mon Oct 7 15:05:34 2013 +0200

    Add option kolab_messages_cache_bypass

diff --git a/plugins/libkolab/config.inc.php.dist b/plugins/libkolab/config.inc.php.dist
index 6260f52..0c612a3 100644
--- a/plugins/libkolab/config.inc.php.dist
+++ b/plugins/libkolab/config.inc.php.dist
@@ -24,3 +24,9 @@ $rcmail_config['kolab_custom_display_names'] = false;
 // See http://pear.php.net/manual/en/package.http.http-request2.config.php
 // for list of supported configuration options (array keys)
 $rcmail_config['kolab_http_request'] = array();
+
+// When kolab_cache is enabled Roundcube's messages cache will be redundant
+// when working on kolab folders. Here we can:
+// 2 - bypass messages/indexes cache completely
+// 1 - bypass only messages, but use index cache
+$rcmail_config['kolab_messages_cache_bypass'] = 0;
diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index 3b1d857..0e30dfa 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -804,21 +804,32 @@ class kolab_storage_cache
             return;
         }
 
-        if ($this->messages_cache === null) {
+        static $messages_cache, $cache_bypass;
+
+        if ($messages_cache === null) {
             $rcmail = rcube::get_instance();
-            $this->messages_cache = (bool) $rcmail->config->get('messages_cache');
+            $messages_cache = (bool) $rcmail->config->get('messages_cache');
+            $cache_bypass   = (int) $rcmail->config->get('kolab_messages_cache_bypass');
         }
 
-        if ($this->messages_cache) {
-            // we'll disable messages cache, but keep index cache
-            // default mode is both (MODE_INDEX | MODE_MESSAGE)
-            $mode = rcube_imap_cache::MODE_INDEX;
+        if ($messages_cache) {
+            switch ($cache_bypass) {
+                case 2:
+                    // Disable messages cache completely
+                    $this->imap->set_messages_caching(!$disable);
+                    return;
 
-            if (!$disable) {
-                $mode |= rcube_imap_cache::MODE_MESSAGE;
-            }
+                case 1:
+                    // We'll disable messages cache, but keep index cache.
+                    // Default mode is both (MODE_INDEX | MODE_MESSAGE)
+                    $mode = rcube_imap_cache::MODE_INDEX;
 
-            $this->imap->set_messages_caching(true, $mode);
+                    if (!$disable) {
+                        $mode |= rcube_imap_cache::MODE_MESSAGE;
+                    }
+
+                    $this->imap->set_messages_caching(true, $mode);
+            }
         }
     }
 }


commit 4f69556b7165fcf09d49c7b77a0f97ab6f1a0cc6
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Oct 7 14:31:48 2013 +0200

    Only set sync lock if folder hash changed

diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index 589b809..6a3e0d6 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -128,12 +128,15 @@ class kolab_storage_cache
         // increase time limit
         @set_time_limit($this->max_sync_lock_time);
 
-        // lock synchronization for this folder or wait if locked
-        $this->_sync_lock();
+        // read cached folder metadata
+        $this->_read_folder_data();
 
         // check cache status hash first ($this->metadata is set in _read_folder_data())
         if ($this->metadata['ctag'] != $this->folder->get_ctag()) {
 
+            // lock synchronization for this folder or wait if locked
+            $this->_sync_lock();
+
             // synchronize IMAP mailbox cache
             $this->imap->folder_sync($this->folder->name);
 
@@ -176,10 +179,10 @@ class kolab_storage_cache
                 // update ctag value (will be written to database in _sync_unlock())
                 $this->metadata['ctag'] = $this->folder->get_ctag();
             }
-        }
 
-        // remove lock
-        $this->_sync_unlock();
+            // remove lock
+            $this->_sync_unlock();
+        }
 
         $this->synched = time();
     }


commit 20d6aa265ca871b5043e3abd21abd413d1ef3b31
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Oct 7 12:14:44 2013 +0200

    Fix copy&paste typos

diff --git a/plugins/libkolab/lib/kolab_storage_cache_contact.php b/plugins/libkolab/lib/kolab_storage_cache_contact.php
index 763e9f7..e17923d 100644
--- a/plugins/libkolab/lib/kolab_storage_cache_contact.php
+++ b/plugins/libkolab/lib/kolab_storage_cache_contact.php
@@ -26,8 +26,8 @@ class kolab_storage_cache_contact extends kolab_storage_cache
     protected $extra_cols = array('type');
     protected $binary_items = array(
         'photo'          => '|<photo><uri>[^;]+;base64,([^<]+)</uri></photo>|i',
-        'pgppublickey'   => '|<key><uri>date:application/pgp-keys;base64,([^<]+)</uri></photo>|i',
-        'pkcs7publickey' => '|<key><uri>date:application/pkcs7-mime;base64,([^<]+)</uri></photo>|i',
+        'pgppublickey'   => '|<key><uri>date:application/pgp-keys;base64,([^<]+)</uri></key>|i',
+        'pkcs7publickey' => '|<key><uri>date:application/pkcs7-mime;base64,([^<]+)</uri></key>|i',
     );
 
     /**


commit 944b42afec882cf184f981b9103e84f99aecc11f
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Oct 7 12:13:05 2013 +0200

    Improve database schema and provide schema update queries

diff --git a/plugins/libkolab/SQL/mysql.initial.sql b/plugins/libkolab/SQL/mysql.initial.sql
index 2ce664b..8e9c8dc 100644
--- a/plugins/libkolab/SQL/mysql.initial.sql
+++ b/plugins/libkolab/SQL/mysql.initial.sql
@@ -21,6 +21,8 @@ CREATE TABLE `kolab_folders` (
 
 DROP TABLE IF EXISTS `kolab_cache`;
 
+DROP TABLE IF EXISTS `kolab_cache_contact`;
+
 CREATE TABLE `kolab_cache_contact` (
   `folder_id` BIGINT UNSIGNED NOT NULL,
   `msguid` BIGINT UNSIGNED NOT NULL,
@@ -35,10 +37,11 @@ CREATE TABLE `kolab_cache_contact` (
   CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`)
     REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`),
-  INDEX `contact_uid` (`uid`),
   INDEX `contact_type` (`folder_id`,`type`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
+DROP TABLE IF EXISTS `kolab_cache_event`;
+
 CREATE TABLE `kolab_cache_event` (
   `folder_id` BIGINT UNSIGNED NOT NULL,
   `msguid` BIGINT UNSIGNED NOT NULL,
@@ -47,16 +50,17 @@ CREATE TABLE `kolab_cache_event` (
   `changed` DATETIME DEFAULT NULL,
   `data` TEXT NOT NULL,
   `xml` TEXT NOT NULL,
-  `dtstart` DATETIME,
-  `dtend` DATETIME,
   `tags` VARCHAR(255) NOT NULL,
   `words` TEXT NOT NULL,
+  `dtstart` DATETIME,
+  `dtend` DATETIME,
   CONSTRAINT `fk_kolab_cache_event_folder` FOREIGN KEY (`folder_id`)
     REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
-  PRIMARY KEY(`folder_id`,`msguid`),
-  INDEX `event_uid` (`uid`)
+  PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
+DROP TABLE IF EXISTS `kolab_cache_task`;
+
 CREATE TABLE `kolab_cache_task` (
   `folder_id` BIGINT UNSIGNED NOT NULL,
   `msguid` BIGINT UNSIGNED NOT NULL,
@@ -65,16 +69,17 @@ CREATE TABLE `kolab_cache_task` (
   `changed` DATETIME DEFAULT NULL,
   `data` TEXT NOT NULL,
   `xml` TEXT NOT NULL,
-  `dtstart` DATETIME,
-  `dtend` DATETIME,
   `tags` VARCHAR(255) NOT NULL,
   `words` TEXT NOT NULL,
+  `dtstart` DATETIME,
+  `dtend` DATETIME,
   CONSTRAINT `fk_kolab_cache_task_folder` FOREIGN KEY (`folder_id`)
     REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
-  PRIMARY KEY(`folder_id`,`msguid`),
-  INDEX `task_uid` (`uid`)
+  PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
+DROP TABLE IF EXISTS `kolab_cache_journal`;
+
 CREATE TABLE `kolab_cache_journal` (
   `folder_id` BIGINT UNSIGNED NOT NULL,
   `msguid` BIGINT UNSIGNED NOT NULL,
@@ -83,16 +88,17 @@ CREATE TABLE `kolab_cache_journal` (
   `changed` DATETIME DEFAULT NULL,
   `data` TEXT NOT NULL,
   `xml` TEXT NOT NULL,
-  `dtstart` DATETIME,
-  `dtend` DATETIME,
   `tags` VARCHAR(255) NOT NULL,
   `words` TEXT NOT NULL,
+  `dtstart` DATETIME,
+  `dtend` DATETIME,
   CONSTRAINT `fk_kolab_cache_journal_folder` FOREIGN KEY (`folder_id`)
     REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
-  PRIMARY KEY(`folder_id`,`msguid`),
-  INDEX `journal` (`uid`)
+  PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
+DROP TABLE IF EXISTS `kolab_cache_note`;
+
 CREATE TABLE `kolab_cache_note` (
   `folder_id` BIGINT UNSIGNED NOT NULL,
   `msguid` BIGINT UNSIGNED NOT NULL,
@@ -105,10 +111,11 @@ CREATE TABLE `kolab_cache_note` (
   `words` TEXT NOT NULL,
   CONSTRAINT `fk_kolab_cache_note_folder` FOREIGN KEY (`folder_id`)
     REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
-  PRIMARY KEY(`folder_id`,`msguid`),
-  INDEX `note_uid` (`uid`)
+  PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
+DROP TABLE IF EXISTS `kolab_cache_file`;
+
 CREATE TABLE `kolab_cache_file` (
   `folder_id` BIGINT UNSIGNED NOT NULL,
   `msguid` BIGINT UNSIGNED NOT NULL,
@@ -127,6 +134,8 @@ CREATE TABLE `kolab_cache_file` (
   INDEX `folder_filename` (`folder_id`, `filename`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
+DROP TABLE IF EXISTS `kolab_cache_configuration`;
+
 CREATE TABLE `kolab_cache_configuration` (
   `folder_id` BIGINT UNSIGNED NOT NULL,
   `msguid` BIGINT UNSIGNED NOT NULL,
@@ -141,10 +150,11 @@ CREATE TABLE `kolab_cache_configuration` (
   CONSTRAINT `fk_kolab_cache_configuration_folder` FOREIGN KEY (`folder_id`)
     REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
   PRIMARY KEY(`folder_id`,`msguid`),
-  INDEX `configuration_uid` (`uid`),
   INDEX `configuration_type` (`folder_id`,`type`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
+DROP TABLE IF EXISTS `kolab_cache_freebusy`;
+
 CREATE TABLE `kolab_cache_freebusy` (
   `folder_id` BIGINT UNSIGNED NOT NULL,
   `msguid` BIGINT UNSIGNED NOT NULL,
@@ -153,14 +163,13 @@ CREATE TABLE `kolab_cache_freebusy` (
   `changed` DATETIME DEFAULT NULL,
   `data` TEXT NOT NULL,
   `xml` TEXT NOT NULL,
-  `dtstart` DATETIME,
-  `dtend` DATETIME,
   `tags` VARCHAR(255) NOT NULL,
   `words` TEXT NOT NULL,
-  CONSTRAINT `fk_kolab_cache_cfreebusy_folder` FOREIGN KEY (`folder_id`)
+  `dtstart` DATETIME,
+  `dtend` DATETIME,
+  CONSTRAINT `fk_kolab_cache_freebusy_folder` FOREIGN KEY (`folder_id`)
     REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
-  PRIMARY KEY(`folder_id`,`msguid`),
-  INDEX `freebusy_uid` (`uid`)
+  PRIMARY KEY(`folder_id`,`msguid`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
 
diff --git a/plugins/libkolab/SQL/mysql/2013100400.sql b/plugins/libkolab/SQL/mysql/2013100400.sql
new file mode 100644
index 0000000..cdc1df6
--- /dev/null
+++ b/plugins/libkolab/SQL/mysql/2013100400.sql
@@ -0,0 +1,147 @@
+DROP TABLE IF EXISTS `kolab_cache`;
+
+CREATE TABLE `kolab_folders` (
+  `ID` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+  `resource` VARCHAR(255) CHARACTER SET ascii NOT NULL,
+  `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
+  `synclock` INT(10) NOT NULL DEFAULT '0',
+  `ctag` VARCHAR(32) DEFAULT NULL,
+  PRIMARY KEY(`ID`),
+  INDEX `resource_type` (`resource`, `type`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_contact` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
+  CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`),
+  INDEX `contact_type` (`folder_id`,`type`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_event` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  `dtstart` DATETIME,
+  `dtend` DATETIME,
+  CONSTRAINT `fk_kolab_cache_event_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_task` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  `dtstart` DATETIME,
+  `dtend` DATETIME,
+  CONSTRAINT `fk_kolab_cache_task_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_journal` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  `dtstart` DATETIME,
+  `dtend` DATETIME,
+  CONSTRAINT `fk_kolab_cache_journal_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_note` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  CONSTRAINT `fk_kolab_cache_note_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_file` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
+  `filename` varchar(255) DEFAULT NULL,
+  CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`),
+  INDEX `folder_filename` (`folder_id`, `filename`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_configuration` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
+  CONSTRAINT `fk_kolab_cache_configuration_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`),
+  INDEX `configuration_type` (`folder_id`,`type`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_freebusy` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  `dtstart` DATETIME,
+  `dtend` DATETIME,
+  CONSTRAINT `fk_kolab_cache_freebusy_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+


commit 20a731c54057c766644cda1681381b5178e9731e
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Oct 7 12:12:23 2013 +0200

    Minor code improvements

diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index 48a0285..589b809 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -41,11 +41,7 @@ class kolab_storage_cache
     protected $folders_table;
     protected $max_sql_packet;
     protected $max_sync_lock_time = 600;
-    protected $binary_items = array(
-        'photo'          => '|<photo><uri>[^;]+;base64,([^<]+)</uri></photo>|i',
-        'pgppublickey'   => '|<key><uri>date:application/pgp-keys;base64,([^<]+)</uri></photo>|i',
-        'pkcs7publickey' => '|<key><uri>date:application/pkcs7-mime;base64,([^<]+)</uri></photo>|i',
-    );
+    protected $binary_items = array();
     protected $extra_cols = array();
 
 
@@ -600,7 +596,7 @@ class kolab_storage_cache
      */
     protected function _serialize($object)
     {
-        $sql_data = array('changed' => null, 'dtstart' => null, 'dtend' => null, 'xml' => '', 'tags' => '', 'words' => '');
+        $sql_data = array('changed' => null, 'xml' => '', 'tags' => '', 'words' => '');
 
         if ($object['changed']) {
             $sql_data['changed'] = date('Y-m-d H:i:s', is_object($object['changed']) ? $object['changed']->format('U') : $object['changed']);
diff --git a/plugins/libkolab/lib/kolab_storage_cache_contact.php b/plugins/libkolab/lib/kolab_storage_cache_contact.php
index 3d1ecf0..763e9f7 100644
--- a/plugins/libkolab/lib/kolab_storage_cache_contact.php
+++ b/plugins/libkolab/lib/kolab_storage_cache_contact.php
@@ -24,6 +24,11 @@
 class kolab_storage_cache_contact extends kolab_storage_cache
 {
     protected $extra_cols = array('type');
+    protected $binary_items = array(
+        'photo'          => '|<photo><uri>[^;]+;base64,([^<]+)</uri></photo>|i',
+        'pgppublickey'   => '|<key><uri>date:application/pgp-keys;base64,([^<]+)</uri></photo>|i',
+        'pkcs7publickey' => '|<key><uri>date:application/pkcs7-mime;base64,([^<]+)</uri></photo>|i',
+    );
 
     /**
      * Helper method to convert the given Kolab object into a dataset to be written to cache
diff --git a/plugins/libkolab/lib/kolab_storage_cache_task.php b/plugins/libkolab/lib/kolab_storage_cache_task.php
index 03facb4..a1953f6 100644
--- a/plugins/libkolab/lib/kolab_storage_cache_task.php
+++ b/plugins/libkolab/lib/kolab_storage_cache_task.php
@@ -32,7 +32,7 @@ class kolab_storage_cache_task extends kolab_storage_cache
      */
     protected function _serialize($object)
     {
-        $sql_data = parent::_serialize($object);
+        $sql_data = parent::_serialize($object) + array('dtstart' => null, 'dtend' => null);
 
         if ($object['start'])
             $sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']);


commit b72709a5cf0bf80eb4ca34d1eb5daea9765e7e80
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Oct 7 11:59:00 2013 +0200

    Lazy read kolab_folder record from DB; fix error when mass-inserting cache records

diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index eeae046..48a0285 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -38,6 +38,7 @@ class kolab_storage_cache
     protected $synclock = false;
     protected $ready = false;
     protected $cache_table;
+    protected $folders_table;
     protected $max_sql_packet;
     protected $max_sync_lock_time = 600;
     protected $binary_items = array(
@@ -78,6 +79,8 @@ class kolab_storage_cache
         $this->enabled = $rcmail->config->get('kolab_cache', false);
 
         if ($this->enabled) {
+            // always read folder cache and lock state from DB master
+            $this->db->set_table_dsn('kolab_folders', 'w');
             // remove sync-lock on script termination
             $rcmail->add_shutdown_function(array($this, '_sync_unlock'));
         }
@@ -103,21 +106,10 @@ class kolab_storage_cache
 
         // compose fully qualified ressource uri for this instance
         $this->resource_uri = $this->folder->get_resource_uri();
+        $this->folders_table = $this->db->table_name('kolab_folders');
         $this->cache_table = $this->db->table_name('kolab_cache_' . $this->folder->type);
         $this->ready = $this->enabled;
-
-        if ($this->enabled) {
-            $sql_arr = $this->db->fetch_assoc($this->db->query("SELECT ID, synclock, ctag FROM kolab_folders WHERE resource=?", $this->resource_uri));
-            if ($sql_arr) {
-                $this->metadata = $sql_arr;
-                $this->folder_id = $sql_arr['ID'];
-            }
-            else {
-                $this->db->query("INSERT INTO kolab_folders (resource, type) VALUES (?, ?)", $this->resource_uri, $this->folder->type);
-                $this->folder_id = $this->db->insert_id('kolab_folders');
-                $this->metadata = array();
-            }
-        }
+        $this->folder_id = null;
     }
 
     /**
@@ -143,7 +135,7 @@ class kolab_storage_cache
         // lock synchronization for this folder or wait if locked
         $this->_sync_lock();
 
-        // check cache status hash first ($this->metadata is set in _sync_lock())
+        // check cache status hash first ($this->metadata is set in _read_folder_data())
         if ($this->metadata['ctag'] != $this->folder->get_ctag()) {
 
             // synchronize IMAP mailbox cache
@@ -215,6 +207,8 @@ class kolab_storage_cache
         // load object if not in memory
         if (!isset($this->objects[$msguid])) {
             if ($this->ready) {
+                $this->_read_folder_data();
+
                 $sql_result = $this->db->query(
                     "SELECT * FROM $this->cache_table ".
                     "WHERE folder_id=? AND msguid=?",
@@ -259,6 +253,7 @@ class kolab_storage_cache
 
         // remove old entry
         if ($this->ready) {
+            $this->_read_folder_data();
             $this->db->query("DELETE FROM $this->cache_table WHERE folder_id=? AND msguid=?",
                 $this->folder_id, $msguid);
         }
@@ -284,6 +279,8 @@ class kolab_storage_cache
     {
         // write to cache
         if ($this->ready) {
+            $this->_read_folder_data();
+
             $sql_data = $this->_serialize($object);
 
             $extra_cols   = $this->extra_cols ? ', ' . join(', ', $this->extra_cols) : '';
@@ -307,7 +304,7 @@ class kolab_storage_cache
                 $args[] = $sql_data[$col];
             }
 
-            $result = call_user_func_array(array($this->db, 'call'), $args);
+            $result = call_user_func_array(array($this->db, 'query'), $args);
 
             if (!$this->db->affected_rows($result)) {
                 rcube::raise_error(array(
@@ -336,6 +333,8 @@ class kolab_storage_cache
 
         // resolve new message UID in target folder
         if ($new_msguid = $target->cache->uid2msguid($uid)) {
+            $this->_read_folder_data();
+
             $this->db->query(
                 "UPDATE $this->cache_table SET folder_id=?, msguid=? ".
                 "WHERE folder_id=? AND msguid=?",
@@ -359,6 +358,8 @@ class kolab_storage_cache
      */
     public function purge($type = null)
     {
+        $this->_read_folder_data();
+
         $result = $this->db->query(
             "DELETE FROM $this->cache_table WHERE folder_id=?".
             $this->folder_id
@@ -377,10 +378,10 @@ class kolab_storage_cache
 
         // resolve new message UID in target folder
         $this->db->query(
-            "UPDATE kolab_folders SET resource=? ".
-            "WHERE folder_id=?",
+            "UPDATE $this->folders_table SET resource=? ".
+            "WHERE resource=?",
             $target->get_resource_uri(),
-            $this->folder_id
+            $this->resource_uri
         );
     }
 
@@ -398,6 +399,8 @@ class kolab_storage_cache
 
         // read from local cache DB (assume it to be synchronized)
         if ($this->ready) {
+            $this->_read_folder_data();
+
             $sql_result = $this->db->query(
                 "SELECT " . ($uids ? 'msguid, uid' : '*') . " FROM $this->cache_table ".
                 "WHERE folder_id=? " . $this->_sql_where($query),
@@ -458,6 +461,8 @@ class kolab_storage_cache
 
         // cache is in sync, we can count records in local DB
         if ($this->synched) {
+            $this->_read_folder_data();
+
             $sql_result = $this->db->query(
                 "SELECT COUNT(*) AS numrows FROM kolab_cache ".
                 "WHERE folder_id=? " . $this->_sql_where($query),
@@ -723,6 +728,27 @@ class kolab_storage_cache
     }
 
     /**
+     * Read this folder's ID and cache metadata
+     */
+    protected function _read_folder_data()
+    {
+        // already done
+        if (!empty($this->folder_id))
+            return;
+
+        $sql_arr = $this->db->fetch_assoc($this->db->query("SELECT ID, synclock, ctag FROM $this->folders_table WHERE resource=?", $this->resource_uri));
+        if ($sql_arr) {
+            $this->metadata = $sql_arr;
+            $this->folder_id = $sql_arr['ID'];
+        }
+        else {
+            $this->db->query("INSERT INTO $this->folders_table (resource, type) VALUES (?, ?)", $this->resource_uri, $this->folder->type);
+            $this->folder_id = $this->db->insert_id('kolab_folders');
+            $this->metadata = array();
+        }
+    }
+
+    /**
      * Check lock record for this folder and wait if locked or set lock
      */
     protected function _sync_lock()
@@ -730,8 +756,8 @@ class kolab_storage_cache
         if (!$this->ready)
             return;
 
-        $sql_query = "SELECT synclock, ctag FROM kolab_folders WHERE ID=?";
-        $this->metadata = $this->db->fetch_assoc($this->db->query($sql_query, $this->folder_id));
+        $this->_read_folder_data();
+        $sql_query = "SELECT synclock, ctag FROM $this->folders_table WHERE ID=?";
 
         // abort if database is not set-up
         if ($this->db->is_error()) {
@@ -748,7 +774,7 @@ class kolab_storage_cache
         }
 
         // set lock
-        $this->db->query("UPDATE kolab_folders SET synclock = ? WHERE ID = ?", time(), $this->folder_id);
+        $this->db->query("UPDATE $this->folders_table SET synclock = ? WHERE ID = ?", time(), $this->folder_id);
     }
 
     /**
@@ -760,7 +786,7 @@ class kolab_storage_cache
             return;
 
         $this->db->query(
-            "UPDATE kolab_folders SET synclock = 0, ctag = ? WHERE ID = ?",
+            "UPDATE $this->folders_table SET synclock = 0, ctag = ? WHERE ID = ?",
             $this->metadata['ctag'],
             $this->folder_id
         );
@@ -788,4 +814,16 @@ class kolab_storage_cache
         return $this->uid2msg[$uid];
     }
 
+    /**
+     * Getter for protected member variables
+     */
+    public function __get($name)
+    {
+        if ($name == 'folder_id') {
+            $this->_read_folder_data();
+        }
+
+        return $this->$name;
+    }
+
 }


commit 16d9509a5d3795dce8aa2b04b22b69ec1e03879b
Author: Aleksander Machniak <machniak at kolabsys.com>
Date:   Mon Oct 7 09:56:06 2013 +0200

    Improved performance of kolab cache by bypassing Roundcube messages cache (Request #1740)

diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index a23fbaa..3b1d857 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -100,12 +100,15 @@ class kolab_storage_cache
         $this->_sync_lock();
 
         // synchronize IMAP mailbox cache
+        $this->bypass(true);
         $this->imap->folder_sync($this->folder->name);
 
         // compare IMAP index with object cache index
         $imap_index = $this->imap->index($this->folder->name);
         $this->index = $imap_index->get();
 
+        $this->bypass(false);
+
         // determine objects to fetch or to invalidate
         if ($this->ready) {
             // read cache index
@@ -523,8 +526,14 @@ class kolab_storage_cache
         if (!$type)
             $type = $this->folder->type;
 
+        $this->bypass(true);
+
         $results = array();
-        foreach ((array)$this->imap->fetch_headers($this->folder->name, $index, false) as $msguid => $headers) {
+        $headers = $this->imap->fetch_headers($this->folder->name, $index, false);
+
+        $this->bypass(false);
+
+        foreach ((array)$headers as $msguid => $headers) {
             $object_type = kolab_format::mime2object_type($headers->others['x-kolab-type']);
 
             // check object type header and abort on mismatch
@@ -782,4 +791,34 @@ class kolab_storage_cache
         return $this->uid2msg[$uid];
     }
 
+    /**
+     * Bypass Roundcube messages cache.
+     * Roundcube cache duplicates information already stored in kolab_cache.
+     *
+     * @param bool $disable True disables, False enables messages cache
+     */
+    public function bypass($disable = false)
+    {
+        // if kolab cache is disabled do nothing
+        if (!$this->enabled) {
+            return;
+        }
+
+        if ($this->messages_cache === null) {
+            $rcmail = rcube::get_instance();
+            $this->messages_cache = (bool) $rcmail->config->get('messages_cache');
+        }
+
+        if ($this->messages_cache) {
+            // we'll disable messages cache, but keep index cache
+            // default mode is both (MODE_INDEX | MODE_MESSAGE)
+            $mode = rcube_imap_cache::MODE_INDEX;
+
+            if (!$disable) {
+                $mode |= rcube_imap_cache::MODE_MESSAGE;
+            }
+
+            $this->imap->set_messages_caching(true, $mode);
+        }
+    }
 }
diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index e81153d..5be0f5f 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -340,7 +340,6 @@ class kolab_storage_folder
         return $subscribed ? kolab_storage::folder_subscribe($this->name) : kolab_storage::folder_unsubscribe($this->name);
     }
 
-
     /**
      * Get number of objects stored in this folder
      *
@@ -502,6 +501,7 @@ class kolab_storage_folder
      * @param string The IMAP message UID to fetch
      * @param string The object type expected (use wildcard '*' to accept all types)
      * @param string The folder name where the message is stored
+     *
      * @return mixed Hash array representing the Kolab object, a kolab_format instance or false if not found
      */
     public function read_object($msguid, $type = null, $folder = null)
@@ -511,31 +511,31 @@ class kolab_storage_folder
 
         $this->imap->set_folder($folder);
 
-        $headers = $this->imap->get_message_headers($msguid);
-        $message = null;
+        $this->cache->bypass(true);
+        $message = new rcube_message($msguid);
+        $this->cache->bypass(false);
 
         // Message doesn't exist?
-        if (empty($headers)) {
+        if (empty($message->headers)) {
             return false;
         }
 
         // extract the X-Kolab-Type header from the XML attachment part if missing
-        if (empty($headers->others['x-kolab-type'])) {
-            $message = new rcube_message($msguid);
+        if (empty($message->headers->others['x-kolab-type'])) {
             foreach ((array)$message->attachments as $part) {
                 if (strpos($part->mimetype, kolab_format::KTYPE_PREFIX) === 0) {
-                    $headers->others['x-kolab-type'] = $part->mimetype;
+                    $message->headers->others['x-kolab-type'] = $part->mimetype;
                     break;
                 }
             }
         }
         // fix buggy messages stating the X-Kolab-Type header twice
-        else if (is_array($headers->others['x-kolab-type'])) {
-            $headers->others['x-kolab-type'] = reset($headers->others['x-kolab-type']);
+        else if (is_array($message->headers->others['x-kolab-type'])) {
+            $message->headers->others['x-kolab-type'] = reset($message->headers->others['x-kolab-type']);
         }
 
         // no object type header found: abort
-        if (empty($headers->others['x-kolab-type'])) {
+        if (empty($message->headers->others['x-kolab-type'])) {
             rcube::raise_error(array(
                 'code' => 600,
                 'type' => 'php',
@@ -546,14 +546,13 @@ class kolab_storage_folder
             return false;
         }
 
-        $object_type = kolab_format::mime2object_type($headers->others['x-kolab-type']);
-        $content_type  = kolab_format::KTYPE_PREFIX . $object_type;
+        $object_type  = kolab_format::mime2object_type($message->headers->others['x-kolab-type']);
+        $content_type = kolab_format::KTYPE_PREFIX . $object_type;
 
         // check object type header and abort on mismatch
         if ($type != '*' && $object_type != $type)
             return false;
 
-        if (!$message) $message = new rcube_message($msguid);
         $attachments = array();
 
         // get XML part
@@ -595,7 +594,7 @@ class kolab_storage_folder
         }
 
         // check kolab format version
-        $format_version = $headers->others['x-kolab-mime-version'];
+        $format_version = $message->headers->others['x-kolab-mime-version'];
         if (empty($format_version)) {
             list($xmltype, $subtype) = explode('.', $object_type);
             $xmlhead = substr($xml, 0, 512);
@@ -749,7 +748,9 @@ class kolab_storage_folder
 
             // delete old message
             if ($result && !empty($object['_msguid']) && !empty($object['_mailbox'])) {
+                $this->cache->bypass(true);
                 $this->imap->delete_message($object['_msguid'], $object['_mailbox']);
+                $this->cache->bypass(false);
                 $this->cache->set($object['_msguid'], false, $object['_mailbox']);
             }
 
@@ -844,6 +845,8 @@ class kolab_storage_folder
         $msguid = is_array($object) ? $object['_msguid'] : $this->cache->uid2msguid($object);
         $success = false;
 
+        $this->cache->bypass(true);
+
         if ($msguid && $expunge) {
             $success = $this->imap->delete_message($msguid, $this->name);
         }
@@ -851,6 +854,8 @@ class kolab_storage_folder
             $success = $this->imap->set_flag($msguid, 'DELETED', $this->name);
         }
 
+        $this->cache->bypass(false);
+
         if ($success) {
             $this->cache->set($msguid, false);
         }
@@ -865,7 +870,11 @@ class kolab_storage_folder
     public function delete_all()
     {
         $this->cache->purge();
-        return $this->imap->clear_folder($this->name);
+        $this->cache->bypass(true);
+        $result = $this->imap->clear_folder($this->name);
+        $this->cache->bypass(false);
+
+        return $result;
     }
 
 
@@ -878,7 +887,11 @@ class kolab_storage_folder
     public function undelete($uid)
     {
         if ($msguid = $this->cache->uid2msguid($uid, true)) {
-            if ($this->imap->set_flag($msguid, 'UNDELETED', $this->name)) {
+            $this->cache->bypass(true);
+            $result = $this->imap->set_flag($msguid, 'UNDELETED', $this->name);
+            $this->cache->bypass(false);
+
+            if ($result) {
                 return $msguid;
             }
         }
@@ -897,7 +910,11 @@ class kolab_storage_folder
     public function move($uid, $target_folder)
     {
         if ($msguid = $this->cache->uid2msguid($uid)) {
-            if ($this->imap->move_message($msguid, $target_folder, $this->name)) {
+            $this->cache->bypass(true);
+            $result = $this->imap->move_message($msguid, $target_folder, $this->name);
+            $this->cache->bypass(false);
+
+            if ($result) {
                 $this->cache->move($msguid, $uid, $target_folder);
                 return true;
             }


commit af5115d0c3f24aa60272156c6ce1c7724d2945b3
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Fri Oct 4 19:38:44 2013 +0200

    Specify type field

diff --git a/plugins/libkolab/lib/kolab_storage_cache_configuration.php b/plugins/libkolab/lib/kolab_storage_cache_configuration.php
index 5de4c1e..8380aa8 100644
--- a/plugins/libkolab/lib/kolab_storage_cache_configuration.php
+++ b/plugins/libkolab/lib/kolab_storage_cache_configuration.php
@@ -24,5 +24,17 @@
 class kolab_storage_cache_configuration extends kolab_storage_cache
 {
     protected $extra_cols = array('type');
-    
+
+    /**
+     * Helper method to convert the given Kolab object into a dataset to be written to cache
+     *
+     * @override
+     */
+    protected function _serialize($object)
+    {
+        $sql_data = parent::_serialize($object);
+        $sql_data['type'] = $object['type'];
+
+        return $sql_data;
+    }
 }
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_cache_contact.php b/plugins/libkolab/lib/kolab_storage_cache_contact.php
index 5fc764f..3d1ecf0 100644
--- a/plugins/libkolab/lib/kolab_storage_cache_contact.php
+++ b/plugins/libkolab/lib/kolab_storage_cache_contact.php
@@ -25,4 +25,16 @@ class kolab_storage_cache_contact extends kolab_storage_cache
 {
     protected $extra_cols = array('type');
 
+    /**
+     * Helper method to convert the given Kolab object into a dataset to be written to cache
+     *
+     * @override
+     */
+    protected function _serialize($object)
+    {
+        $sql_data = parent::_serialize($object);
+        $sql_data['type'] = $object['_type'];
+
+        return $sql_data;
+    }
 }
\ No newline at end of file


commit b7d4731d9766a6f13d893b39f80512c266fc513c
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Fri Oct 4 19:32:21 2013 +0200

    Let the derrived kolab_storage_cache classes provide the contents of their custom cache record fields

diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index 0081d01..eeae046 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -596,32 +596,6 @@ class kolab_storage_cache
     protected function _serialize($object)
     {
         $sql_data = array('changed' => null, 'dtstart' => null, 'dtend' => null, 'xml' => '', 'tags' => '', 'words' => '');
-        $objtype  = $object['_type'] ? $object['_type'] : $this->folder->type;
-
-        // set type specific values
-        if ($objtype == 'event') {
-            // database runs in server's timezone so using date() is what we want
-            $sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']);
-            $sql_data['dtend']   = date('Y-m-d H:i:s', is_object($object['end'])   ? $object['end']->format('U')   : $object['end']);
-
-            // extend date range for recurring events
-            if ($object['recurrence'] && $object['_formatobj']) {
-                $recurrence = new kolab_date_recurrence($object['_formatobj']);
-                $sql_data['dtend'] = date('Y-m-d 23:59:59', $recurrence->end() ?: strtotime('now +1 year'));
-            }
-        }
-        else if ($objtype == 'task') {
-            if ($object['start'])
-                $sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']);
-            if ($object['due'])
-                $sql_data['dtend']   = date('Y-m-d H:i:s', is_object($object['due'])   ? $object['due']->format('U')   : $object['due']);
-        }
-        else if ($objtype == 'file') {
-            if (!empty($object['_attachments'])) {
-                reset($object['_attachments']);
-                $sql_data['filename'] = $object['_attachments'][key($object['_attachments'])]['name'];
-            }
-        }
 
         if ($object['changed']) {
             $sql_data['changed'] = date('Y-m-d H:i:s', is_object($object['changed']) ? $object['changed']->format('U') : $object['changed']);
diff --git a/plugins/libkolab/lib/kolab_storage_cache_event.php b/plugins/libkolab/lib/kolab_storage_cache_event.php
index a8e7176..69134e7 100644
--- a/plugins/libkolab/lib/kolab_storage_cache_event.php
+++ b/plugins/libkolab/lib/kolab_storage_cache_event.php
@@ -24,5 +24,26 @@
 class kolab_storage_cache_event extends kolab_storage_cache
 {
     protected $extra_cols = array('dtstart','dtend');
-    
+
+    /**
+     * Helper method to convert the given Kolab object into a dataset to be written to cache
+     *
+     * @override
+     */
+    protected function _serialize($object)
+    {
+        $sql_data = parent::_serialize($object);
+
+        // database runs in server's timezone so using date() is what we want
+        $sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']);
+        $sql_data['dtend']   = date('Y-m-d H:i:s', is_object($object['end'])   ? $object['end']->format('U')   : $object['end']);
+
+        // extend date range for recurring events
+        if ($object['recurrence'] && $object['_formatobj']) {
+            $recurrence = new kolab_date_recurrence($object['_formatobj']);
+            $sql_data['dtend'] = date('Y-m-d 23:59:59', $recurrence->end() ?: strtotime('now +1 year'));
+        }
+
+        return $sql_data;
+    }
 }
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_cache_file.php b/plugins/libkolab/lib/kolab_storage_cache_file.php
index 3c98f63..ea1823d 100644
--- a/plugins/libkolab/lib/kolab_storage_cache_file.php
+++ b/plugins/libkolab/lib/kolab_storage_cache_file.php
@@ -24,5 +24,21 @@
 class kolab_storage_cache_file extends kolab_storage_cache
 {
     protected $extra_cols = array('filename');
-    
+
+    /**
+     * Helper method to convert the given Kolab object into a dataset to be written to cache
+     *
+     * @override
+     */
+    protected function _serialize($object)
+    {
+        $sql_data = parent::_serialize($object);
+
+        if (!empty($object['_attachments'])) {
+            reset($object['_attachments']);
+            $sql_data['filename'] = $object['_attachments'][key($object['_attachments'])]['name'];
+        }
+
+        return $sql_data;
+    }
 }
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_cache_task.php b/plugins/libkolab/lib/kolab_storage_cache_task.php
index aaf75e6..03facb4 100644
--- a/plugins/libkolab/lib/kolab_storage_cache_task.php
+++ b/plugins/libkolab/lib/kolab_storage_cache_task.php
@@ -24,5 +24,21 @@
 class kolab_storage_cache_task extends kolab_storage_cache
 {
     protected $extra_cols = array('dtstart','dtend');
-    
+
+    /**
+     * Helper method to convert the given Kolab object into a dataset to be written to cache
+     *
+     * @override
+     */
+    protected function _serialize($object)
+    {
+        $sql_data = parent::_serialize($object);
+
+        if ($object['start'])
+            $sql_data['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']);
+        if ($object['due'])
+            $sql_data['dtend']   = date('Y-m-d H:i:s', is_object($object['due'])   ? $object['due']->format('U')   : $object['due']);
+
+        return $sql_data;
+    }
 }
\ No newline at end of file


commit 87335f387f9308d24667fd6a62ae6f2dffc2c389
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Fri Oct 4 17:14:34 2013 +0200

    Split kolab_cache table into folder-type specific tables and specialized kolab_storage_cache_* classes; the object type is now implicit

diff --git a/plugins/libkolab/SQL/mysql.initial.sql b/plugins/libkolab/SQL/mysql.initial.sql
index 764da2a..2ce664b 100644
--- a/plugins/libkolab/SQL/mysql.initial.sql
+++ b/plugins/libkolab/SQL/mysql.initial.sql
@@ -1,16 +1,46 @@
 /**
  * libkolab database schema
  *
- * @version @package_version@
+ * @version 1.0
  * @author Thomas Bruederli
  * @licence GNU AGPL
  **/
 
-DROP TABLE IF EXISTS `kolab_cache`;
 
-CREATE TABLE `kolab_cache` (
+DROP TABLE IF EXISTS `kolab_folders`;
+
+CREATE TABLE `kolab_folders` (
+  `ID` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
   `resource` VARCHAR(255) CHARACTER SET ascii NOT NULL,
   `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
+  `synclock` INT(10) NOT NULL DEFAULT '0',
+  `ctag` VARCHAR(32) DEFAULT NULL,
+  PRIMARY KEY(`ID`),
+  INDEX `resource_type` (`resource`, `type`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+DROP TABLE IF EXISTS `kolab_cache`;
+
+CREATE TABLE `kolab_cache_contact` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
+  CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`),
+  INDEX `contact_uid` (`uid`),
+  INDEX `contact_type` (`folder_id`,`type`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_event` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
   `msguid` BIGINT UNSIGNED NOT NULL,
   `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
   `created` DATETIME DEFAULT NULL,
@@ -21,9 +51,117 @@ CREATE TABLE `kolab_cache` (
   `dtend` DATETIME,
   `tags` VARCHAR(255) NOT NULL,
   `words` TEXT NOT NULL,
+  CONSTRAINT `fk_kolab_cache_event_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`),
+  INDEX `event_uid` (`uid`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_task` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `dtstart` DATETIME,
+  `dtend` DATETIME,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  CONSTRAINT `fk_kolab_cache_task_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`),
+  INDEX `task_uid` (`uid`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_journal` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `dtstart` DATETIME,
+  `dtend` DATETIME,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  CONSTRAINT `fk_kolab_cache_journal_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`),
+  INDEX `journal` (`uid`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_note` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  CONSTRAINT `fk_kolab_cache_note_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`),
+  INDEX `note_uid` (`uid`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_file` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
   `filename` varchar(255) DEFAULT NULL,
-  PRIMARY KEY(`resource`,`type`,`msguid`),
-  INDEX `resource_filename` (`resource`, `filename`)
+  CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`),
+  INDEX `folder_filename` (`folder_id`, `filename`)
 ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
 
-INSERT INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2013041900');
+CREATE TABLE `kolab_cache_configuration` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  `type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
+  CONSTRAINT `fk_kolab_cache_configuration_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`),
+  INDEX `configuration_uid` (`uid`),
+  INDEX `configuration_type` (`folder_id`,`type`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE `kolab_cache_freebusy` (
+  `folder_id` BIGINT UNSIGNED NOT NULL,
+  `msguid` BIGINT UNSIGNED NOT NULL,
+  `uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
+  `created` DATETIME DEFAULT NULL,
+  `changed` DATETIME DEFAULT NULL,
+  `data` TEXT NOT NULL,
+  `xml` TEXT NOT NULL,
+  `dtstart` DATETIME,
+  `dtend` DATETIME,
+  `tags` VARCHAR(255) NOT NULL,
+  `words` TEXT NOT NULL,
+  CONSTRAINT `fk_kolab_cache_cfreebusy_folder` FOREIGN KEY (`folder_id`)
+    REFERENCES `kolab_folders`(`ID`) ON DELETE CASCADE ON UPDATE CASCADE,
+  PRIMARY KEY(`folder_id`,`msguid`),
+  INDEX `freebusy_uid` (`uid`)
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+
+INSERT INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2013100400');
diff --git a/plugins/libkolab/lib/kolab_storage.php b/plugins/libkolab/lib/kolab_storage.php
index 533fab1..466c10c 100644
--- a/plugins/libkolab/lib/kolab_storage.php
+++ b/plugins/libkolab/lib/kolab_storage.php
@@ -157,7 +157,7 @@ class kolab_storage
      * This will search all folders storing objects of the given type.
      *
      * @param string Object UID
-     * @param string Object type (contact,distribution-list,event,task,note)
+     * @param string Object type (contact,event,task,journal,file,note,configuration)
      * @return array The Kolab object represented as hash array or false if not found
      */
     public static function get_object($uid, $type)
@@ -586,7 +586,7 @@ class kolab_storage
      *
      * @param string  Optional root folder
      * @param string  Optional name pattern
-     * @param string  Data type to list folders for (contact,distribution-list,event,task,note,mail)
+     * @param string  Data type to list folders for (contact,event,task,journal,file,note,mail,configuration)
      * @param boolean Enable to return subscribed folders only (null to use configured subscription mode)
      * @param array   Will be filled with folder-types data
      *
diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index a23fbaa..0081d01 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -6,7 +6,7 @@
  * @version @package_version@
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  *
- * Copyright (C) 2012, Kolab Systems AG <contact at kolabsys.com>
+ * Copyright (C) 2012-2013, Kolab Systems AG <contact at kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -24,24 +24,47 @@
 
 class kolab_storage_cache
 {
-    private $db;
-    private $imap;
-    private $folder;
-    private $uid2msg;
-    private $objects;
-    private $index = array();
-    private $resource_uri;
-    private $enabled = true;
-    private $synched = false;
-    private $synclock = false;
-    private $ready = false;
-    private $max_sql_packet;
-    private $max_sync_lock_time = 600;
-    private $binary_items = array(
+    protected $db;
+    protected $imap;
+    protected $folder;
+    protected $uid2msg;
+    protected $objects;
+    protected $index = array();
+    protected $metadata = array();
+    protected $folder_id;
+    protected $resource_uri;
+    protected $enabled = true;
+    protected $synched = false;
+    protected $synclock = false;
+    protected $ready = false;
+    protected $cache_table;
+    protected $max_sql_packet;
+    protected $max_sync_lock_time = 600;
+    protected $binary_items = array(
         'photo'          => '|<photo><uri>[^;]+;base64,([^<]+)</uri></photo>|i',
         'pgppublickey'   => '|<key><uri>date:application/pgp-keys;base64,([^<]+)</uri></photo>|i',
         'pkcs7publickey' => '|<key><uri>date:application/pkcs7-mime;base64,([^<]+)</uri></photo>|i',
     );
+    protected $extra_cols = array();
+
+
+    /**
+     * Factory constructor
+     */
+    public static function factory(kolab_storage_folder $storage_folder)
+    {
+        $subclass = 'kolab_storage_cache_' . $storage_folder->type;
+        if (class_exists($subclass)) {
+            return new $subclass($storage_folder);
+        }
+        else {
+            rcube::raise_error(array(
+                'code' => 900,
+                'type' => 'php',
+                'message' => "No kolab_storage_cache class found for folder of type " . $storage_folder->type
+            ), true);
+        }
+    }
 
 
     /**
@@ -80,9 +103,30 @@ class kolab_storage_cache
 
         // compose fully qualified ressource uri for this instance
         $this->resource_uri = $this->folder->get_resource_uri();
+        $this->cache_table = $this->db->table_name('kolab_cache_' . $this->folder->type);
         $this->ready = $this->enabled;
+
+        if ($this->enabled) {
+            $sql_arr = $this->db->fetch_assoc($this->db->query("SELECT ID, synclock, ctag FROM kolab_folders WHERE resource=?", $this->resource_uri));
+            if ($sql_arr) {
+                $this->metadata = $sql_arr;
+                $this->folder_id = $sql_arr['ID'];
+            }
+            else {
+                $this->db->query("INSERT INTO kolab_folders (resource, type) VALUES (?, ?)", $this->resource_uri, $this->folder->type);
+                $this->folder_id = $this->db->insert_id('kolab_folders');
+                $this->metadata = array();
+            }
+        }
     }
 
+    /**
+     * Returns true if this cache supports query by type
+     */
+    public function has_type_col()
+    {
+        return in_array('type', $this->extra_cols);
+    }
 
     /**
      * Synchronize local cache data with remote
@@ -99,44 +143,50 @@ class kolab_storage_cache
         // lock synchronization for this folder or wait if locked
         $this->_sync_lock();
 
-        // synchronize IMAP mailbox cache
-        $this->imap->folder_sync($this->folder->name);
+        // check cache status hash first ($this->metadata is set in _sync_lock())
+        if ($this->metadata['ctag'] != $this->folder->get_ctag()) {
 
-        // compare IMAP index with object cache index
-        $imap_index = $this->imap->index($this->folder->name);
-        $this->index = $imap_index->get();
+            // synchronize IMAP mailbox cache
+            $this->imap->folder_sync($this->folder->name);
 
-        // determine objects to fetch or to invalidate
-        if ($this->ready) {
-            // read cache index
-            $sql_result = $this->db->query(
-                "SELECT msguid, uid FROM kolab_cache WHERE resource=? AND type<>?",
-                $this->resource_uri,
-                'lock'
-            );
+            // compare IMAP index with object cache index
+            $imap_index = $this->imap->index($this->folder->name);
+            $this->index = $imap_index->get();
 
-            $old_index = array();
-            while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
-                $old_index[] = $sql_arr['msguid'];
-                $this->uid2msg[$sql_arr['uid']] = $sql_arr['msguid'];
-            }
+            // determine objects to fetch or to invalidate
+            if ($this->ready) {
+                // read cache index
+                $sql_result = $this->db->query(
+                    "SELECT msguid, uid FROM $this->cache_table WHERE folder_id=?",
+                    $this->folder_id
+                );
 
-            // fetch new objects from imap
-            foreach (array_diff($this->index, $old_index) as $msguid) {
-                if ($object = $this->folder->read_object($msguid, '*')) {
-                    $this->_extended_insert($msguid, $object);
+                $old_index = array();
+                while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
+                    $old_index[] = $sql_arr['msguid'];
+                    $this->uid2msg[$sql_arr['uid']] = $sql_arr['msguid'];
                 }
-            }
-            $this->_extended_insert(0, null);
-
-            // delete invalid entries from local DB
-            $del_index = array_diff($old_index, $this->index);
-            if (!empty($del_index)) {
-                $quoted_ids = join(',', array_map(array($this->db, 'quote'), $del_index));
-                $this->db->query(
-                    "DELETE FROM kolab_cache WHERE resource=? AND msguid IN ($quoted_ids)",
-                    $this->resource_uri
-                );
+
+                // fetch new objects from imap
+                foreach (array_diff($this->index, $old_index) as $msguid) {
+                    if ($object = $this->folder->read_object($msguid, '*')) {
+                        $this->_extended_insert($msguid, $object);
+                    }
+                }
+                $this->_extended_insert(0, null);
+
+                // delete invalid entries from local DB
+                $del_index = array_diff($old_index, $this->index);
+                if (!empty($del_index)) {
+                    $quoted_ids = join(',', array_map(array($this->db, 'quote'), $del_index));
+                    $this->db->query(
+                        "DELETE FROM $this->cache_table WHERE folder_id=? AND msguid IN ($quoted_ids)",
+                        $this->folder_id
+                    );
+                }
+
+                // update ctag value (will be written to database in _sync_unlock())
+                $this->metadata['ctag'] = $this->folder->get_ctag();
             }
         }
 
@@ -166,10 +216,9 @@ class kolab_storage_cache
         if (!isset($this->objects[$msguid])) {
             if ($this->ready) {
                 $sql_result = $this->db->query(
-                    "SELECT * FROM kolab_cache ".
-                    "WHERE resource=? AND type=? AND msguid=?",
-                    $this->resource_uri,
-                    $type ?: $this->folder->type,
+                    "SELECT * FROM $this->cache_table ".
+                    "WHERE folder_id=? AND msguid=?",
+                    $this->folder_id,
                     $msguid
                 );
 
@@ -210,8 +259,8 @@ class kolab_storage_cache
 
         // remove old entry
         if ($this->ready) {
-            $this->db->query("DELETE FROM kolab_cache WHERE resource=? AND msguid=? AND type<>?",
-                $this->resource_uri, $msguid, 'lock');
+            $this->db->query("DELETE FROM $this->cache_table WHERE folder_id=? AND msguid=?",
+                $this->folder_id, $msguid);
         }
 
         if ($object) {
@@ -236,26 +285,30 @@ class kolab_storage_cache
         // write to cache
         if ($this->ready) {
             $sql_data = $this->_serialize($object);
-            $objtype = $object['_type'] ? $object['_type'] : $this->folder->type;
 
-            $result = $this->db->query(
-                "INSERT INTO kolab_cache ".
-                " (resource, type, msguid, uid, created, changed, data, xml, dtstart, dtend, tags, words, filename)".
-                " VALUES (?, ?, ?, ?, " . $this->db->now() . ", ?, ?, ?, ?, ?, ?, ?, ?)",
-                $this->resource_uri,
-                $objtype,
+            $extra_cols   = $this->extra_cols ? ', ' . join(', ', $this->extra_cols) : '';
+            $extra_fields = $this->extra_cols ? str_repeat(', ?', count($this->extra_cols)) : '';
+
+            $args = array(
+                "INSERT INTO $this->cache_table ".
+                " (folder_id, msguid, uid, created, changed, data, xml, tags, words $extra_cols)".
+                " VALUES (?, ?, ?, " . $this->db->now() . ", ?, ?, ?, ?, ? $extra_fields)",
+                $this->folder_id,
                 $msguid,
                 $object['uid'],
                 $sql_data['changed'],
                 $sql_data['data'],
                 $sql_data['xml'],
-                $sql_data['dtstart'],
-                $sql_data['dtend'],
                 $sql_data['tags'],
                 $sql_data['words'],
-                $sql_data['filename']
             );
 
+            foreach ($this->extra_cols as $col) {
+                $args[] = $sql_data[$col];
+            }
+
+            $result = call_user_func_array(array($this->db, 'call'), $args);
+
             if (!$this->db->affected_rows($result)) {
                 rcube::raise_error(array(
                     'code' => 900, 'type' => 'php',
@@ -284,13 +337,12 @@ class kolab_storage_cache
         // resolve new message UID in target folder
         if ($new_msguid = $target->cache->uid2msguid($uid)) {
             $this->db->query(
-                "UPDATE kolab_cache SET resource=?, msguid=? ".
-                "WHERE resource=? AND msguid=? AND type<>?",
-                $target->get_resource_uri(),
+                "UPDATE $this->cache_table SET folder_id=?, msguid=? ".
+                "WHERE folder_id=? AND msguid=?",
+                $target->folder_id,
                 $new_msguid,
-                $this->resource_uri,
-                $msguid,
-                'lock'
+                $this->folder_id,
+                $msguid
             );
         }
         else {
@@ -308,10 +360,8 @@ class kolab_storage_cache
     public function purge($type = null)
     {
         $result = $this->db->query(
-            "DELETE FROM kolab_cache WHERE resource=?".
-            ($type ? ' AND type=?' : ''),
-            $this->resource_uri,
-            $type
+            "DELETE FROM $this->cache_table WHERE folder_id=?".
+            $this->folder_id
         );
         return $this->db->affected_rows($result);
     }
@@ -327,10 +377,10 @@ class kolab_storage_cache
 
         // resolve new message UID in target folder
         $this->db->query(
-            "UPDATE kolab_cache SET resource=? ".
-            "WHERE resource=?",
+            "UPDATE kolab_folders SET resource=? ".
+            "WHERE folder_id=?",
             $target->get_resource_uri(),
-            $this->resource_uri
+            $this->folder_id
         );
     }
 
@@ -349,9 +399,9 @@ class kolab_storage_cache
         // read from local cache DB (assume it to be synchronized)
         if ($this->ready) {
             $sql_result = $this->db->query(
-                "SELECT " . ($uids ? 'msguid, uid' : '*') . " FROM kolab_cache ".
-                "WHERE resource=? " . $this->_sql_where($query),
-                $this->resource_uri
+                "SELECT " . ($uids ? 'msguid, uid' : '*') . " FROM $this->cache_table ".
+                "WHERE folder_id=? " . $this->_sql_where($query),
+                $this->folder_id
             );
 
             while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
@@ -410,8 +460,8 @@ class kolab_storage_cache
         if ($this->synched) {
             $sql_result = $this->db->query(
                 "SELECT COUNT(*) AS numrows FROM kolab_cache ".
-                "WHERE resource=? " . $this->_sql_where($query),
-                $this->resource_uri
+                "WHERE folder_id=? " . $this->_sql_where($query),
+                $this->folder_id
             );
 
             $sql_arr = $this->db->fetch_assoc($sql_result);
@@ -432,7 +482,7 @@ class kolab_storage_cache
     /**
      * Helper method to compose a valid SQL query from pseudo filter triplets
      */
-    private function _sql_where($query)
+    protected function _sql_where($query)
     {
         $sql_where = '';
         foreach ($query as $param) {
@@ -477,7 +527,7 @@ class kolab_storage_cache
      * Helper method to convert the given pseudo-query triplets into
      * an associative filter array with 'equals' values only
      */
-    private function _query2assoc($query)
+    protected function _query2assoc($query)
     {
         // extract object type from query parameter
         $filter = array();
@@ -496,7 +546,7 @@ class kolab_storage_cache
      * @param string IMAP folder to read from
      * @return array List of parsed Kolab objects
      */
-    private function _fetch($index, $type = null, $folder = null)
+    protected function _fetch($index, $type = null, $folder = null)
     {
         $results = array();
         foreach ((array)$index as $msguid) {
@@ -518,7 +568,7 @@ class kolab_storage_cache
      * @param string IMAP folder to read from
      * @return array List of parsed Kolab objects
      */
-    private function _fetch_uids($index, $type = null)
+    protected function _fetch_uids($index, $type = null)
     {
         if (!$type)
             $type = $this->folder->type;
@@ -543,7 +593,7 @@ class kolab_storage_cache
     /**
      * Helper method to convert the given Kolab object into a dataset to be written to cache
      */
-    private function _serialize($object)
+    protected function _serialize($object)
     {
         $sql_data = array('changed' => null, 'dtstart' => null, 'dtend' => null, 'xml' => '', 'tags' => '', 'words' => '');
         $objtype  = $object['_type'] ? $object['_type'] : $this->folder->type;
@@ -613,7 +663,7 @@ class kolab_storage_cache
     /**
      * Helper method to turn stored cache data into a valid storage object
      */
-    private function _unserialize($sql_arr)
+    protected function _unserialize($sql_arr)
     {
         $object = unserialize($sql_arr['data']);
 
@@ -625,11 +675,11 @@ class kolab_storage_cache
         }
 
         // add meta data
-        $object['_type'] = $sql_arr['type'];
+        $object['_type'] = $sql_arr['type'] ?: $this->folder->type;
         $object['_msguid'] = $sql_arr['msguid'];
         $object['_mailbox'] = $this->folder->name;
         $object['_size'] = strlen($sql_arr['xml']);
-        $object['_formatobj'] = kolab_format::factory($sql_arr['type'], 3.0, $sql_arr['xml']);
+        $object['_formatobj'] = kolab_format::factory($object['_type'], 3.0, $sql_arr['xml']);
 
         return $object;
     }
@@ -640,37 +690,35 @@ class kolab_storage_cache
      * @param int  Message UID. Set 0 to commit buffered inserts
      * @param array Kolab object to cache
      */
-    private function _extended_insert($msguid, $object)
+    protected function _extended_insert($msguid, $object)
     {
         static $buffer = '';
 
         $line = '';
         if ($object) {
             $sql_data = $this->_serialize($object);
-            $objtype = $object['_type'] ? $object['_type'] : $this->folder->type;
-
             $values = array(
-                $this->db->quote($this->resource_uri),
-                $this->db->quote($objtype),
+                $this->db->quote($this->folder_id),
                 $this->db->quote($msguid),
                 $this->db->quote($object['uid']),
                 $this->db->now(),
                 $this->db->quote($sql_data['changed']),
                 $this->db->quote($sql_data['data']),
                 $this->db->quote($sql_data['xml']),
-                $this->db->quote($sql_data['dtstart']),
-                $this->db->quote($sql_data['dtend']),
                 $this->db->quote($sql_data['tags']),
                 $this->db->quote($sql_data['words']),
-                $this->db->quote($sql_data['filename']),
             );
+            foreach ($this->extra_cols as $col) {
+                $values[] = $this->db->quote($sql_data[$col]);
+            }
             $line = '(' . join(',', $values) . ')';
         }
 
         if ($buffer && (!$msguid || (strlen($buffer) + strlen($line) > $this->max_sql_packet()))) {
+            $extra_cols = $this->extra_cols ? ', ' . join(', ', $this->extra_cols) : '';
             $result = $this->db->query(
-                "INSERT INTO kolab_cache ".
-                " (resource, type, msguid, uid, created, changed, data, xml, dtstart, dtend, tags, words, filename)".
+                "INSERT INTO $this->cache_table ".
+                " (folder_id, msguid, uid, created, changed, data, xml, tags, words $extra_cols)".
                 " VALUES $buffer"
             );
             if (!$this->db->affected_rows($result)) {
@@ -689,7 +737,7 @@ class kolab_storage_cache
     /**
      * Returns max_allowed_packet from mysql config
      */
-    private function max_sql_packet()
+    protected function max_sql_packet()
     {
         if (!$this->max_sql_packet) {
             // mysql limit or max 4 MB
@@ -703,14 +751,13 @@ class kolab_storage_cache
     /**
      * Check lock record for this folder and wait if locked or set lock
      */
-    private function _sync_lock()
+    protected function _sync_lock()
     {
         if (!$this->ready)
             return;
 
-        $sql_query = "SELECT msguid AS locked, ".$this->db->unixtimestamp('created')." AS created FROM kolab_cache ".
-            "WHERE resource=? AND type=?";
-        $sql_arr = $this->db->fetch_assoc($this->db->query($sql_query, $this->resource_uri, 'lock'));
+        $sql_query = "SELECT synclock, ctag FROM kolab_folders WHERE ID=?";
+        $this->metadata = $this->db->fetch_assoc($this->db->query($sql_query, $this->folder_id));
 
         // abort if database is not set-up
         if ($this->db->is_error()) {
@@ -721,28 +768,13 @@ class kolab_storage_cache
         $this->synclock = true;
 
         // wait if locked (expire locks after 10 minutes)
-        while ($sql_arr && intval($sql_arr['locked']) > 0 && $sql_arr['created'] + $this->max_sync_lock_time > time()) {
+        while ($this->metadata && intval($this->metadata['synclock']) > 0 && $this->metadata['synclock'] + $this->max_sync_lock_time > time()) {
             usleep(500000);
-            $sql_arr = $this->db->fetch_assoc($this->db->query($sql_query, $this->resource_uri, 'lock'));
+            $this->metadata = $this->db->fetch_assoc($this->db->query($sql_query, $this->folder_id));
         }
 
-        // create lock record if not exists
-        if (!$sql_arr) {
-            $this->db->query(
-                "INSERT INTO kolab_cache (resource, type, msguid, created, uid, data, xml, tags, words)".
-                " VALUES (?, ?, 1, " . $this->db->now() . ", '', '', '', '', '')",
-                $this->resource_uri,
-                'lock'
-            );
-        }
-        else {
-            $this->db->query(
-                "UPDATE kolab_cache SET msguid = 1, created = " . $this->db->now() .
-                " WHERE resource = ? AND type = ?",
-                $this->resource_uri,
-                'lock'
-            );
-        }
+        // set lock
+        $this->db->query("UPDATE kolab_folders SET synclock = ? WHERE ID = ?", time(), $this->folder_id);
     }
 
     /**
@@ -754,9 +786,9 @@ class kolab_storage_cache
             return;
 
         $this->db->query(
-            "UPDATE kolab_cache SET msguid = 0 WHERE resource = ? AND type = ?",
-            $this->resource_uri,
-            'lock'
+            "UPDATE kolab_folders SET synclock = 0, ctag = ? WHERE ID = ?",
+            $this->metadata['ctag'],
+            $this->folder_id
         );
 
         $this->synclock = false;
diff --git a/plugins/libkolab/lib/kolab_storage_cache_configuration.php b/plugins/libkolab/lib/kolab_storage_cache_configuration.php
new file mode 100644
index 0000000..5de4c1e
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_cache_configuration.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Kolab storage cache class for configuration objects
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class kolab_storage_cache_configuration extends kolab_storage_cache
+{
+    protected $extra_cols = array('type');
+    
+}
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_cache_contact.php b/plugins/libkolab/lib/kolab_storage_cache_contact.php
new file mode 100644
index 0000000..5fc764f
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_cache_contact.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Kolab storage cache class for contact objects
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class kolab_storage_cache_contact extends kolab_storage_cache
+{
+    protected $extra_cols = array('type');
+
+}
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_cache_event.php b/plugins/libkolab/lib/kolab_storage_cache_event.php
new file mode 100644
index 0000000..a8e7176
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_cache_event.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Kolab storage cache class for calendar event objects
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class kolab_storage_cache_event extends kolab_storage_cache
+{
+    protected $extra_cols = array('dtstart','dtend');
+    
+}
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_cache_file.php b/plugins/libkolab/lib/kolab_storage_cache_file.php
new file mode 100644
index 0000000..3c98f63
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_cache_file.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Kolab storage cache class for file objects
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class kolab_storage_cache_file extends kolab_storage_cache
+{
+    protected $extra_cols = array('filename');
+    
+}
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_cache_freebusy.php b/plugins/libkolab/lib/kolab_storage_cache_freebusy.php
new file mode 100644
index 0000000..d8ab554
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_cache_freebusy.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Kolab storage cache class for freebusy objects
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class kolab_storage_cache_freebusy extends kolab_storage_cache
+{
+        protected $extra_cols = array('dtstart','dtend');
+}
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_cache_journal.php b/plugins/libkolab/lib/kolab_storage_cache_journal.php
new file mode 100644
index 0000000..a63577b
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_cache_journal.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Kolab storage cache class for journal objects
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class kolab_storage_cache_journal extends kolab_storage_cache
+{
+    protected $extra_cols = array('dtstart','dtend');
+    
+}
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_cache_mongodb.php b/plugins/libkolab/lib/kolab_storage_cache_mongodb.php
new file mode 100644
index 0000000..8ae95e4
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_cache_mongodb.php
@@ -0,0 +1,561 @@
+<?php
+
+/**
+ * Kolab storage cache class providing a local caching layer for Kolab groupware objects.
+ *
+ * @version @package_version@
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2012, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class kolab_storage_cache_mongodb
+{
+    private $db;
+    private $imap;
+    private $folder;
+    private $uid2msg;
+    private $objects;
+    private $index = array();
+    private $resource_uri;
+    private $enabled = true;
+    private $synched = false;
+    private $synclock = false;
+    private $ready = false;
+    private $max_sql_packet = 1046576;  // 1 MB - 2000 bytes
+    private $binary_cols = array('photo','pgppublickey','pkcs7publickey');
+
+
+    /**
+     * Default constructor
+     */
+    public function __construct(kolab_storage_folder $storage_folder = null)
+    {
+        $rcmail = rcube::get_instance();
+        $mongo = new Mongo();
+        $this->db = $mongo->kolab_cache;
+        $this->imap = $rcmail->get_storage();
+        $this->enabled = $rcmail->config->get('kolab_cache', false);
+
+        if ($this->enabled) {
+            // remove sync-lock on script termination
+            $rcmail->add_shutdown_function(array($this, '_sync_unlock'));
+        }
+
+        if ($storage_folder)
+            $this->set_folder($storage_folder);
+    }
+
+
+    /**
+     * Connect cache with a storage folder
+     *
+     * @param kolab_storage_folder The storage folder instance to connect with
+     */
+    public function set_folder(kolab_storage_folder $storage_folder)
+    {
+        $this->folder = $storage_folder;
+
+        if (empty($this->folder->name)) {
+            $this->ready = false;
+            return;
+        }
+
+        // compose fully qualified ressource uri for this instance
+        $this->resource_uri = $this->folder->get_resource_uri();
+        $this->ready = $this->enabled;
+    }
+
+
+    /**
+     * Synchronize local cache data with remote
+     */
+    public function synchronize()
+    {
+        // only sync once per request cycle
+        if ($this->synched)
+            return;
+
+        // increase time limit
+        @set_time_limit(500);
+
+        // lock synchronization for this folder or wait if locked
+        $this->_sync_lock();
+
+        // synchronize IMAP mailbox cache
+        $this->imap->folder_sync($this->folder->name);
+
+        // compare IMAP index with object cache index
+        $imap_index = $this->imap->index($this->folder->name);
+        $this->index = $imap_index->get();
+
+        // determine objects to fetch or to invalidate
+        if ($this->ready) {
+            // read cache index
+            $old_index = array();
+            $cursor = $this->db->cache->find(array('resource' => $this->resource_uri), array('msguid' => 1, 'uid' => 1));
+            foreach ($cursor as $doc) {
+                $old_index[] = $doc['msguid'];
+                $this->uid2msg[$doc['uid']] = $doc['msguid'];
+            }
+
+            // fetch new objects from imap
+            foreach (array_diff($this->index, $old_index) as $msguid) {
+                if ($object = $this->folder->read_object($msguid, '*')) {
+                    try {
+                        $this->db->cache->insert($this->_serialize($object, $msguid));
+                    }
+                    catch (Exception $e) {
+                        rcmail::raise_error(array(
+                            'code' => 900, 'type' => 'php',
+                            'message' => "Failed to write to mongodb cache: " . $e->getMessage(),
+                        ), true);
+                    }
+                }
+            }
+
+            // delete invalid entries from local DB
+            $del_index = array_diff($old_index, $this->index);
+            if (!empty($del_index)) {
+                $this->db->cache->remove(array('resource' => $this->resource_uri, 'msguid' => array('$in' => $del_index)));
+            }
+        }
+
+        // remove lock
+        $this->_sync_unlock();
+
+        $this->synched = time();
+    }
+
+
+    /**
+     * Read a single entry from cache or from IMAP directly
+     *
+     * @param string Related IMAP message UID
+     * @param string Object type to read
+     * @param string IMAP folder name the entry relates to
+     * @param array  Hash array with object properties or null if not found
+     */
+    public function get($msguid, $type = null, $foldername = null)
+    {
+        // delegate to another cache instance
+        if ($foldername && $foldername != $this->folder->name) {
+            return kolab_storage::get_folder($foldername)->cache->get($msguid, $object);
+        }
+
+        // load object if not in memory
+        if (!isset($this->objects[$msguid])) {
+            if ($this->ready && ($doc = $this->db->cache->findOne(array('resource' => $this->resource_uri, 'msguid' => $msguid))))
+                $this->objects[$msguid] = $this->_unserialize($doc);
+
+            // fetch from IMAP if not present in cache
+            if (empty($this->objects[$msguid])) {
+                $result = $this->_fetch(array($msguid), $type, $foldername);
+                $this->objects[$msguid] = $result[0];
+            }
+        }
+
+        return $this->objects[$msguid];
+    }
+
+
+    /**
+     * Insert/Update a cache entry
+     *
+     * @param string Related IMAP message UID
+     * @param mixed  Hash array with object properties to save or false to delete the cache entry
+     * @param string IMAP folder name the entry relates to
+     */
+    public function set($msguid, $object, $foldername = null)
+    {
+        // delegate to another cache instance
+        if ($foldername && $foldername != $this->folder->name) {
+            kolab_storage::get_folder($foldername)->cache->set($msguid, $object);
+            return;
+        }
+
+        // write to cache
+        if ($this->ready) {
+            // remove old entry
+            $this->db->cache->remove(array('resource' => $this->resource_uri, 'msguid' => $msguid));
+
+            // write new object data if not false (wich means deleted)
+            if ($object) {
+                try {
+                    $this->db->cache->insert($this->_serialize($object, $msguid));
+                }
+                catch (Exception $e) {
+                    rcmail::raise_error(array(
+                        'code' => 900, 'type' => 'php',
+                        'message' => "Failed to write to mongodb cache: " . $e->getMessage(),
+                    ), true);
+                }
+            }
+        }
+
+        // keep a copy in memory for fast access
+        $this->objects[$msguid] = $object;
+
+        if ($object)
+            $this->uid2msg[$object['uid']] = $msguid;
+    }
+
+    /**
+     * Move an existing cache entry to a new resource
+     *
+     * @param string Entry's IMAP message UID
+     * @param string Entry's Object UID
+     * @param string Target IMAP folder to move it to
+     */
+    public function move($msguid, $objuid, $target_folder)
+    {
+        $target = kolab_storage::get_folder($target_folder);
+
+        // resolve new message UID in target folder
+        if ($new_msguid = $target->cache->uid2msguid($objuid)) {
+/*
+            $this->db->query(
+                "UPDATE kolab_cache SET resource=?, msguid=? ".
+                "WHERE resource=? AND msguid=? AND type<>?",
+                $target->get_resource_uri(),
+                $new_msguid,
+                $this->resource_uri,
+                $msguid,
+                'lock'
+            );
+*/
+        }
+        else {
+            // just clear cache entry
+            $this->set($msguid, false);
+        }
+
+        unset($this->uid2msg[$uid]);
+    }
+
+
+    /**
+     * Remove all objects from local cache
+     */
+    public function purge($type = null)
+    {
+        return $this->db->cache->remove(array(), array('safe' => true));
+    }
+
+
+    /**
+     * Select Kolab objects filtered by the given query
+     *
+     * @param array Pseudo-SQL query as list of filter parameter triplets
+     *   triplet: array('<colname>', '<comparator>', '<value>')
+     * @return array List of Kolab data objects (each represented as hash array)
+     */
+    public function select($query = array())
+    {
+        $result = array();
+
+        // read from local cache DB (assume it to be synchronized)
+        if ($this->ready) {
+            $cursor = $this->db->cache->find(array('resource' => $this->resource_uri) + $this->_mongo_filter($query));
+            foreach ($cursor as $doc) {
+                if ($object = $this->_unserialize($doc))
+                    $result[] = $object;
+            }
+        }
+        else {
+            // extract object type from query parameter
+            $filter = $this->_query2assoc($query);
+
+            // use 'list' for folder's default objects
+            if ($filter['type'] == $this->type) {
+                $index = $this->index;
+            }
+            else {  // search by object type
+                $search = 'UNDELETED HEADER X-Kolab-Type ' . kolab_storage_folder::KTYPE_PREFIX . $filter['type'];
+                $index = $this->imap->search_once($this->folder->name, $search)->get();
+            }
+
+            // fetch all messages in $index from IMAP
+            $result = $this->_fetch($index, $filter['type']);
+
+            // TODO: post-filter result according to query
+        }
+
+        return $result;
+    }
+
+
+    /**
+     * Get number of objects mathing the given query
+     *
+     * @param array  $query Pseudo-SQL query as list of filter parameter triplets
+     * @return integer The number of objects of the given type
+     */
+    public function count($query = array())
+    {
+        $count = 0;
+
+        // cache is in sync, we can count records in local DB
+        if ($this->synched) {
+            $cursor = $this->db->cache->find(array('resource' => $this->resource_uri) + $this->_mongo_filter($query));
+            $count = $cursor->valid() ? $cursor->count() : 0;
+        }
+        else {
+            // search IMAP by object type
+            $filter = $this->_query2assoc($query);
+            $ctype  = kolab_storage_folder::KTYPE_PREFIX . $filter['type'];
+            $index = $this->imap->search_once($this->folder->name, 'UNDELETED HEADER X-Kolab-Type ' . $ctype);
+            $count = $index->count();
+        }
+
+        return $count;
+    }
+
+    /**
+     * Helper method to convert the pseudo-SQL query into a valid mongodb filter
+     */
+    private function _mongo_filter($query)
+    {
+        $filters = array();
+        foreach ($query as $param) {
+            $filter = array();
+            if ($param[1] == '=' && is_array($param[2])) {
+                $filter[$param[0]] = array('$in' => $param[2]);
+                $filters[] = $filter;
+            }
+            else if ($param[1] == '=') {
+                $filters[] = array($param[0] => $param[2]);
+            }
+            else if ($param[1] == 'LIKE' || $param[1] == '~') {
+                $filter[$param[0]] = array('$regex' => preg_quote($param[2]), '$options' => 'i');
+                $filters[] = $filter;
+            }
+            else if ($param[1] == '!~' || $param[1] == '!LIKE') {
+                $filter[$param[0]] = array('$not' => '/' . preg_quote($param[2]) . '/i');
+                $filters[] = $filter;
+            }
+            else {
+                $op = '';
+                switch ($param[1]) {
+                    case '>':  $op = '$gt';  break;
+                    case '>=': $op = '$gte'; break;
+                    case '<':  $op = '$lt';  break;
+                    case '<=': $op = '$lte'; break;
+                    case '!=':
+                    case '<>': $op = '$gte'; break;
+                }
+                if ($op) {
+                    $filter[$param[0]] = array($op => $param[2]);
+                    $filters[] = $filter;
+                }
+            }
+        }
+
+        return array('$and' => $filters);
+    }
+
+    /**
+     * Helper method to convert the given pseudo-query triplets into
+     * an associative filter array with 'equals' values only
+     */
+    private function _query2assoc($query)
+    {
+        // extract object type from query parameter
+        $filter = array();
+        foreach ($query as $param) {
+            if ($param[1] == '=')
+                $filter[$param[0]] = $param[2];
+        }
+        return $filter;
+    }
+
+    /**
+     * Fetch messages from IMAP
+     *
+     * @param array List of message UIDs to fetch
+     * @return array List of parsed Kolab objects
+     */
+    private function _fetch($index, $type = null, $folder = null)
+    {
+        $results = array();
+        foreach ((array)$index as $msguid) {
+            if ($object = $this->folder->read_object($msguid, $type, $folder)) {
+                $results[] = $object;
+                $this->set($msguid, $object);
+            }
+        }
+
+        return $results;
+    }
+
+
+    /**
+     * Helper method to convert the given Kolab object into a dataset to be written to cache
+     */
+    private function _serialize($object, $msguid)
+    {
+        $bincols = array_flip($this->binary_cols);
+        $doc = array(
+            'resource' => $this->resource_uri,
+            'type'     => $object['_type'] ? $object['_type'] : $this->folder->type,
+            'msguid'   => $msguid,
+            'uid'      => $object['uid'],
+            'xml'      => '',
+            'tags'     => array(),
+            'words'    => array(),
+            'objcols'  => array(),
+        );
+
+        // set type specific values
+        if ($this->folder->type == 'event') {
+            // database runs in server's timezone so using date() is what we want
+            $doc['dtstart'] = date('Y-m-d H:i:s', is_object($object['start']) ? $object['start']->format('U') : $object['start']);
+            $doc['dtend']   = date('Y-m-d H:i:s', is_object($object['end'])   ? $object['end']->format('U')   : $object['end']);
+
+            // extend date range for recurring events
+            if ($object['recurrence']) {
+                $doc['dtend'] = date('Y-m-d H:i:s', $object['recurrence']['UNTIL'] ?: strtotime('now + 2 years'));
+            }
+        }
+
+        if ($object['_formatobj']) {
+            $doc['xml'] = preg_replace('!(</?[a-z0-9:-]+>)[\n\r\t\s]+!ms', '$1', (string)$object['_formatobj']->write());
+            $doc['tags'] = $object['_formatobj']->get_tags();
+            $doc['words'] = $object['_formatobj']->get_words();
+        }
+
+        // extract object data
+        $data = array();
+        foreach ($object as $key => $val) {
+            if ($val === "" || $val === null) {
+                // skip empty properties
+                continue;
+            }
+            if (isset($bincols[$key])) {
+                $data[$key] = base64_encode($val);
+            }
+            else if (is_object($val)) {
+                if (is_a($val, 'DateTime')) {
+                    $data[$key] = array('_class' => 'DateTime', 'date' => $val->format('Y-m-d H:i:s'), 'timezone' => $val->getTimezone()->getName());
+                    $doc['objcols'][] = $key;
+                }
+            }
+            else if ($key[0] != '_') {
+                $data[$key] = $val;
+            }
+            else if ($key == '_attachments') {
+                foreach ($val as $k => $att) {
+                    unset($att['content'], $att['path']);
+                    if ($att['id'])
+                        $data[$key][$k] = $att;
+                }
+            }
+        }
+
+        $doc['data'] = $data;
+        return $doc;
+    }
+
+    /**
+     * Helper method to turn stored cache data into a valid storage object
+     */
+    private function _unserialize($doc)
+    {
+        $object = $doc['data'];
+
+        // decode binary properties
+        foreach ($this->binary_cols as $key) {
+            if (!empty($object[$key]))
+                $object[$key] = base64_decode($object[$key]);
+        }
+
+        // restore serialized objects
+        foreach ((array)$doc['objcols'] as $key) {
+            switch ($object[$key]['_class']) {
+                case 'DateTime':
+                    $val = new DateTime($object[$key]['date'], new DateTimeZone($object[$key]['timezone']));
+                    $object[$key] = $val;
+                    break;
+            }
+        }
+
+        // add meta data
+        $object['_type'] = $doc['type'];
+        $object['_msguid'] = $doc['msguid'];
+        $object['_mailbox'] = $this->folder->name;
+        $object['_formatobj'] = kolab_format::factory($doc['type'], $doc['xml']);
+
+        return $object;
+    }
+
+    /**
+     * Check lock record for this folder and wait if locked or set lock
+     */
+    private function _sync_lock()
+    {
+        if (!$this->ready)
+            return;
+
+        $this->synclock = true;
+        $lock = $this->db->locks->findOne(array('resource' => $this->resource_uri));
+
+        // create lock record if not exists
+        if (!$lock) {
+            $this->db->locks->insert(array('resource' => $this->resource_uri, 'created' => time()));
+        }
+        // wait if locked (expire locks after 10 minutes)
+        else if ((time() - $lock['created']) < 600) {
+            usleep(500000);
+            return $this->_sync_lock();
+        }
+        // set lock
+        else {
+            $lock['created'] = time();
+            $this->db->locks->update(array('_id' => $lock['_id']), $lock, array('safe' => true));
+        }
+    }
+
+    /**
+     * Remove lock for this folder
+     */
+    public function _sync_unlock()
+    {
+        if (!$this->ready || !$this->synclock)
+            return;
+
+        $this->db->locks->remove(array('resource' => $this->resource_uri));
+    }
+
+    /**
+     * Resolve an object UID into an IMAP message UID
+     *
+     * @param string  Kolab object UID
+     * @param boolean Include deleted objects
+     * @return int The resolved IMAP message UID
+     */
+    public function uid2msguid($uid, $deleted = false)
+    {
+        if (!isset($this->uid2msg[$uid])) {
+            // use IMAP SEARCH to get the right message
+            $index = $this->imap->search_once($this->folder->name, ($deleted ? '' : 'UNDELETED ') . 'HEADER SUBJECT ' . $uid);
+            $results = $index->get();
+            $this->uid2msg[$uid] = $results[0];
+        }
+
+        return $this->uid2msg[$uid];
+    }
+
+}
diff --git a/plugins/libkolab/lib/kolab_storage_cache_note.php b/plugins/libkolab/lib/kolab_storage_cache_note.php
new file mode 100644
index 0000000..8546927
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_cache_note.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Kolab storage cache class for note objects
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class kolab_storage_cache_note extends kolab_storage_cache
+{
+    
+}
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_cache_task.php b/plugins/libkolab/lib/kolab_storage_cache_task.php
new file mode 100644
index 0000000..aaf75e6
--- /dev/null
+++ b/plugins/libkolab/lib/kolab_storage_cache_task.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Kolab storage cache class for task objects
+ *
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class kolab_storage_cache_task extends kolab_storage_cache
+{
+    protected $extra_cols = array('dtstart','dtend');
+    
+}
\ No newline at end of file
diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index e81153d..81f5f57 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -7,7 +7,7 @@
  * @author Thomas Bruederli <bruederli at kolabsys.com>
  * @author Aleksander Machniak <machniak at kolabsys.com>
  *
- * Copyright (C) 2012, Kolab Systems AG <contact at kolabsys.com>
+ * Copyright (C) 2012-2013, Kolab Systems AG <contact at kolabsys.com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -64,7 +64,6 @@ class kolab_storage_folder
     {
         $this->imap = rcube::get_instance()->get_storage();
         $this->imap->set_options(array('skip_deleted' => true));
-        $this->cache = new kolab_storage_cache($this);
         $this->set_folder($name, $type);
     }
 
@@ -79,11 +78,16 @@ class kolab_storage_folder
     {
         $this->type_annotation = $ftype ? $ftype : kolab_storage::folder_type($name);
 
+        $oldtype = $this->type;
         list($this->type, $suffix) = explode('.', $this->type_annotation);
         $this->default      = $suffix == 'default';
         $this->name         = $name;
         $this->resource_uri = null;
 
+        // get a new cache instance of folder type changed
+        if (!$this->cache || $type != $oldtype)
+            $this->cache = kolab_storage_cache::factory($this);
+
         $this->imap->set_folder($this->name);
         $this->cache->set_folder($this);
     }
@@ -297,6 +301,15 @@ class kolab_storage_folder
     }
 
     /**
+     * Compose a folder Etag identifier
+     */
+    public function get_ctag()
+    {
+        $fdata = $this->get_imap_data();
+        return sprintf('%d-%d-%d', $fdata['UIDVALIDITY'], $fdata['HIGHESTMODSEQ'], $fdata['UIDNEXT']);
+    }
+
+    /**
      * Check activation status of this folder
      *
      * @return boolean True if enabled, false if not
@@ -349,19 +362,12 @@ class kolab_storage_folder
      * @return integer The number of objects of the given type
      * @see self::select()
      */
-    public function count($type_or_query = null)
+    public function count($query = null)
     {
-        if (!$type_or_query)
-            $query = array(array('type','=',$this->type));
-        else if (is_string($type_or_query))
-            $query = array(array('type','=',$type_or_query));
-        else
-            $query = $this->_prepare_query((array)$type_or_query);
-
         // synchronize cache first
         $this->cache->synchronize();
 
-        return $this->cache->count($query);
+        return $this->cache->count($this->_prepare_query($query));
     }
 
 
@@ -379,7 +385,7 @@ class kolab_storage_folder
         $this->cache->synchronize();
 
         // fetch objects from cache
-        return $this->cache->select(array(array('type','=',$type)));
+        return $this->cache->select(array());
     }
 
 
@@ -425,12 +431,18 @@ class kolab_storage_folder
      */
     private function _prepare_query($query)
     {
-        $type = null;
-        foreach ($query as $i => $param) {
-            if ($param[0] == 'type') {
-                $type = $param[2];
+        // string equals type query
+        if (is_string($query)) {
+            if ($this->cache->has_type_col()) {
+                $query = array(array('type','=',$query));
             }
-            else if (($param[0] == 'dtstart' || $param[0] == 'dtend' || $param[0] == 'changed')) {
+            else {
+                return array();
+            }
+        }
+
+        foreach ((array)$query as $i => $param) {
+            if (($param[0] == 'dtstart' || $param[0] == 'dtend' || $param[0] == 'changed')) {
                 if (is_object($param[2]) && is_a($param[2], 'DateTime'))
                     $param[2] = $param[2]->format('U');
                 if (is_numeric($param[2]))
@@ -438,10 +450,6 @@ class kolab_storage_folder
             }
         }
 
-        // add type selector if not in $query
-        if (!$type)
-            $query[] = array('type','=',$this->type);
-
         return $query;
     }
 




More information about the commits mailing list