summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorThomas Bruederli <bruederli@kolabsys.com>2013-10-04 15:14:34 (GMT)
committerThomas Bruederli <bruederli@kolabsys.com>2013-10-04 15:14:34 (GMT)
commit87335f387f9308d24667fd6a62ae6f2dffc2c389 (patch)
tree57e6d6c1b542ac9242a185940dbdcaf9357ad2b7 /plugins
parenta3ef9150a4f807c4051137ffe225fedc34fdbc13 (diff)
downloadroundcubemail-plugins-kolab-87335f387f9308d24667fd6a62ae6f2dffc2c389.tar.gz
Split kolab_cache table into folder-type specific tables and specialized kolab_storage_cache_* classes; the object type is now implicit
Diffstat (limited to 'plugins')
-rw-r--r--plugins/libkolab/SQL/mysql.initial.sql150
-rw-r--r--plugins/libkolab/lib/kolab_storage.php4
-rw-r--r--plugins/libkolab/lib/kolab_storage_cache.php286
-rw-r--r--plugins/libkolab/lib/kolab_storage_cache_configuration.php28
-rw-r--r--plugins/libkolab/lib/kolab_storage_cache_contact.php28
-rw-r--r--plugins/libkolab/lib/kolab_storage_cache_event.php28
-rw-r--r--plugins/libkolab/lib/kolab_storage_cache_file.php28
-rw-r--r--plugins/libkolab/lib/kolab_storage_cache_freebusy.php27
-rw-r--r--plugins/libkolab/lib/kolab_storage_cache_journal.php28
-rw-r--r--plugins/libkolab/lib/kolab_storage_cache_mongodb.php561
-rw-r--r--plugins/libkolab/lib/kolab_storage_cache_note.php27
-rw-r--r--plugins/libkolab/lib/kolab_storage_cache_task.php28
-rw-r--r--plugins/libkolab/lib/kolab_storage_folder.php50
13 files changed, 1117 insertions, 156 deletions
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@kolabsys.com>
*
- * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
+ * Copyright (C) 2012-2013, Kolab Systems AG <contact@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@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@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@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@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@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@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@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@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@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@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@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@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@kolabsys.com>
+ *
+ * Copyright (C) 2012, Kolab Systems AG <contact@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@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@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@kolabsys.com>
+ *
+ * Copyright (C) 2013, Kolab Systems AG <contact@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@kolabsys.com>
* @author Aleksander Machniak <machniak@kolabsys.com>
*
- * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
+ * Copyright (C) 2012-2013, Kolab Systems AG <contact@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;
}