First implementation of a caching layer for kolab_storage;
- Caching is disabled by default (until fully functional and tested) - Attention: database initialization required for cache) Silently ignore old Kolab2 objects if no Horde classes found to parse them.
This commit is contained in:
parent
63015a802d
commit
18d8fec133
7 changed files with 603 additions and 82 deletions
20
plugins/libkolab/SQL/mysql.sql
Normal file
20
plugins/libkolab/SQL/mysql.sql
Normal file
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* libkolab database schema
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Thomas Bruederli
|
||||
* @licence GNU AGPL
|
||||
**/
|
||||
|
||||
CREATE TABLE `kolab_cache` (
|
||||
`resource` VARCHAR(255) CHARACTER SET ascii NOT NULL,
|
||||
`type` VARCHAR(32) CHARACTER SET ascii NOT NULL,
|
||||
`msguid` BIGINT UNSIGNED NOT NULL,
|
||||
`uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
|
||||
`data` TEXT NOT NULL,
|
||||
`xml` TEXT NOT NULL,
|
||||
`dtstart` DATETIME,
|
||||
`dtend` DATETIME,
|
||||
`tags` VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY(`resource`,`type`,`msguid`)
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
|
@ -31,11 +31,17 @@ abstract class kolab_format
|
|||
|
||||
protected $obj;
|
||||
protected $data;
|
||||
protected $xmldata;
|
||||
protected $loaded = false;
|
||||
|
||||
/**
|
||||
* Factory method to instantiate a kolab_format object of the given type
|
||||
*
|
||||
* @param string Object type to instantiate
|
||||
* @param string Cached xml data to initialize with
|
||||
* @return object kolab_format
|
||||
*/
|
||||
public static function factory($type)
|
||||
public static function factory($type, $xmldata = null)
|
||||
{
|
||||
if (!isset(self::$timezone))
|
||||
self::$timezone = new DateTimeZone('UTC');
|
||||
|
@ -43,7 +49,7 @@ abstract class kolab_format
|
|||
$suffix = preg_replace('/[^a-z]+/', '', $type);
|
||||
$classname = 'kolab_format_' . $suffix;
|
||||
if (class_exists($classname))
|
||||
return new $classname();
|
||||
return new $classname($xmldata);
|
||||
|
||||
return PEAR::raiseError(sprintf("Failed to load Kolab Format wrapper for type %s", $type));
|
||||
}
|
||||
|
@ -165,10 +171,24 @@ abstract class kolab_format
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize libkolabxml object with cached xml data
|
||||
*/
|
||||
protected function init()
|
||||
{
|
||||
if (!$this->loaded) {
|
||||
if ($this->xmldata) {
|
||||
$this->load($this->xmldata);
|
||||
$this->xmldata = null;
|
||||
}
|
||||
$this->loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct getter for object properties
|
||||
*/
|
||||
function __get($var)
|
||||
public function __get($var)
|
||||
{
|
||||
return $this->data[$var];
|
||||
}
|
||||
|
|
|
@ -101,9 +101,10 @@ class kolab_format_contact extends kolab_format
|
|||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
function __construct()
|
||||
function __construct($xmldata = null)
|
||||
{
|
||||
$this->obj = new Contact;
|
||||
$this->xmldata = $xmldata;
|
||||
|
||||
// complete phone types
|
||||
$this->phonetypes['homefax'] |= Telephone::Home;
|
||||
|
@ -118,6 +119,7 @@ class kolab_format_contact extends kolab_format
|
|||
public function load($xml)
|
||||
{
|
||||
$this->obj = kolabformat::readContact($xml, false);
|
||||
$this->loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,9 +129,14 @@ class kolab_format_contact extends kolab_format
|
|||
*/
|
||||
public function write()
|
||||
{
|
||||
$xml = kolabformat::writeContact($this->obj);
|
||||
parent::update_uid();
|
||||
return $xml;
|
||||
$this->init();
|
||||
|
||||
if ($this->obj->isValid()) {
|
||||
$this->xmldata = kolabformat::writeContact($this->obj);
|
||||
parent::update_uid();
|
||||
}
|
||||
|
||||
return $this->xmldata;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,6 +146,8 @@ class kolab_format_contact extends kolab_format
|
|||
*/
|
||||
public function set(&$object)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
// set some automatic values if missing
|
||||
if (false && !$this->obj->created()) {
|
||||
if (!empty($object['created']))
|
||||
|
@ -255,9 +264,10 @@ class kolab_format_contact extends kolab_format
|
|||
if ($type = rc_image_content_type($object['photo']))
|
||||
$this->obj->setPhoto($object['photo'], $type);
|
||||
}
|
||||
else if (isset($object['photo'])) {
|
||||
else if (isset($object['photo']))
|
||||
$this->obj->setPhoto('','');
|
||||
}
|
||||
else if ($this->obj->photoMimetype()) // load saved photo for caching
|
||||
$object['photo'] = $this->obj->photo();
|
||||
|
||||
// spouse and children are relateds
|
||||
$rels = new vectorrelated;
|
||||
|
@ -299,8 +309,8 @@ class kolab_format_contact extends kolab_format
|
|||
|
||||
|
||||
// cache this data
|
||||
unset($object['_formatobj']);
|
||||
$this->data = $object;
|
||||
unset($this->data['_formatobj']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,7 +318,7 @@ class kolab_format_contact extends kolab_format
|
|||
*/
|
||||
public function is_valid()
|
||||
{
|
||||
return $this->data || (is_object($this->obj) && true /*$this->obj->isValid()*/);
|
||||
return $this->data || (is_object($this->obj) && $this->obj->uid() /*$this->obj->isValid()*/);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -322,6 +332,8 @@ class kolab_format_contact extends kolab_format
|
|||
if (!empty($this->data))
|
||||
return $this->data;
|
||||
|
||||
$this->init();
|
||||
|
||||
// read object properties into local data object
|
||||
$object = array(
|
||||
'uid' => $this->obj->uid(),
|
||||
|
|
|
@ -26,9 +26,10 @@ class kolab_format_distributionlist extends kolab_format
|
|||
{
|
||||
public $CTYPE = 'application/vcard+xml';
|
||||
|
||||
function __construct()
|
||||
function __construct($xmldata = null)
|
||||
{
|
||||
$this->obj = new DistList;
|
||||
$this->xmldata = $xmldata;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,6 +40,7 @@ class kolab_format_distributionlist extends kolab_format
|
|||
public function load($xml)
|
||||
{
|
||||
$this->obj = kolabformat::readDistlist($xml, false);
|
||||
$this->loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,13 +50,20 @@ class kolab_format_distributionlist extends kolab_format
|
|||
*/
|
||||
public function write()
|
||||
{
|
||||
$xml = kolabformat::writeDistlist($this->obj);
|
||||
parent::update_uid();
|
||||
return $xml;
|
||||
$this->init();
|
||||
|
||||
if ($this->obj->isValid()) {
|
||||
$this->xmldata = kolabformat::writeDistlist($this->obj);
|
||||
parent::update_uid();
|
||||
}
|
||||
|
||||
return $this->xmldata;
|
||||
}
|
||||
|
||||
public function set(&$object)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
// set some automatic values if missing
|
||||
if (!empty($object['uid']))
|
||||
$this->obj->setUid($object['uid']);
|
||||
|
@ -79,8 +88,8 @@ class kolab_format_distributionlist extends kolab_format
|
|||
$this->obj->setMembers($members);
|
||||
|
||||
// cache this data
|
||||
unset($object['_formatobj']);
|
||||
$this->data = $object;
|
||||
unset($this->data['_formatobj']);
|
||||
}
|
||||
|
||||
public function is_valid()
|
||||
|
@ -122,6 +131,8 @@ class kolab_format_distributionlist extends kolab_format
|
|||
if (!empty($this->data))
|
||||
return $this->data;
|
||||
|
||||
$this->init();
|
||||
|
||||
// read object properties
|
||||
$object = array(
|
||||
'uid' => $this->obj->uid(),
|
||||
|
|
|
@ -91,9 +91,10 @@ class kolab_format_event extends kolab_format
|
|||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
function __construct()
|
||||
function __construct($xmldata = null)
|
||||
{
|
||||
$this->obj = new Event;
|
||||
$this->xmldata = $xmldata;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,6 +105,7 @@ class kolab_format_event extends kolab_format
|
|||
public function load($xml)
|
||||
{
|
||||
$this->obj = kolabformat::readEvent($xml, false);
|
||||
$this->loaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -113,9 +115,14 @@ class kolab_format_event extends kolab_format
|
|||
*/
|
||||
public function write()
|
||||
{
|
||||
$xml = kolabformat::writeEvent($this->obj);
|
||||
parent::update_uid();
|
||||
return $xml;
|
||||
$this->init();
|
||||
|
||||
if ($this->obj->isValid()) {
|
||||
$this->xmldata = kolabformat::writeEvent($this->obj);
|
||||
parent::update_uid();
|
||||
}
|
||||
|
||||
return $this->xmldata;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,6 +132,8 @@ class kolab_format_event extends kolab_format
|
|||
*/
|
||||
public function set(&$object)
|
||||
{
|
||||
$this->init();
|
||||
|
||||
// set some automatic values if missing
|
||||
if (!$this->obj->created()) {
|
||||
if (!empty($object['created']))
|
||||
|
@ -293,8 +302,8 @@ class kolab_format_event extends kolab_format
|
|||
$this->obj->setAttachments($vattach);
|
||||
|
||||
// cache this data
|
||||
unset($object['_formatobj']);
|
||||
$this->data = $object;
|
||||
unset($this->data['_formatobj']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -302,7 +311,7 @@ class kolab_format_event extends kolab_format
|
|||
*/
|
||||
public function is_valid()
|
||||
{
|
||||
return $this->data || (is_object($this->obj) && $this->obj->isValid());
|
||||
return $this->data || (is_object($this->obj) && $this->obj->isValid() && $this->obj->uid());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -316,6 +325,8 @@ class kolab_format_event extends kolab_format
|
|||
if (!empty($this->data))
|
||||
return $this->data;
|
||||
|
||||
$this->init();
|
||||
|
||||
$sensitivity_map = array_flip($this->sensitivity_map);
|
||||
|
||||
// read object properties
|
||||
|
|
443
plugins/libkolab/lib/kolab_storage_cache.php
Normal file
443
plugins/libkolab/lib/kolab_storage_cache.php
Normal file
|
@ -0,0 +1,443 @@
|
|||
<?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
|
||||
{
|
||||
private $db;
|
||||
private $imap;
|
||||
private $folder;
|
||||
private $uid2msg;
|
||||
private $objects;
|
||||
private $resource_uri;
|
||||
private $enabled = true;
|
||||
private $synched = false;
|
||||
private $ready = false;
|
||||
|
||||
private $binary_cols = array('photo','pgppublickey','pkcs7publickey');
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public function __construct(kolab_storage_folder $storage_folder = null)
|
||||
{
|
||||
$rcmail = rcube::get_instance();
|
||||
$this->db = $rcmail->get_dbh();
|
||||
$this->imap = $rcmail->get_storage();
|
||||
$this->enabled = $rcmail->config->get('kolab_cache', false);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// strip namespace prefix from folder name
|
||||
$ns = $this->folder->get_namespace();
|
||||
$nsdata = $this->imap->get_namespace($ns);
|
||||
if (is_array($nsdata[0]) && strpos($this->folder->name, $nsdata[0][0]) === 0) {
|
||||
$subpath = substr($this->folder->name, strlen($nsdata[0][0]));
|
||||
if ($ns == 'other') {
|
||||
list($user, $suffix) = explode($nsdata[0][1], $subpath);
|
||||
$subpath = $suffix;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$subpath = $this->folder->name;
|
||||
}
|
||||
|
||||
// compose fully qualified ressource uri for this instance
|
||||
$this->resource_uri = 'imap://' . urlencode($this->folder->get_owner()) . '@' . $this->imap->options['host'] . '/' . $subpath;
|
||||
$this->ready = $this->enabled;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Synchronize local cache data with remote
|
||||
*/
|
||||
public function synchronize()
|
||||
{
|
||||
// only sync once per request cycle
|
||||
if ($this->synched)
|
||||
return;
|
||||
|
||||
// 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
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT msguid, uid FROM kolab_cache WHERE resource=?",
|
||||
$this->resource_uri
|
||||
);
|
||||
|
||||
$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
|
||||
$fetch_index = array_diff($this->index, $old_index);
|
||||
foreach ($this->_fetch($fetch_index, '*') as $object) {
|
||||
$msguid = $object['_msguid'];
|
||||
$this->set($msguid, $object);
|
||||
}
|
||||
|
||||
// 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->synched = time();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a single entry from cache or
|
||||
*/
|
||||
public function get($msguid, $type = null, $folder = null)
|
||||
{
|
||||
// load object if not in memory
|
||||
if (!isset($this->objects[$msguid])) {
|
||||
if ($this->ready) {
|
||||
// TODO: handle $folder != $this->folder->name situations
|
||||
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT * FROM kolab_cache ".
|
||||
"WHERE resource=? AND msguid=?",
|
||||
$this->resource_uri,
|
||||
$msguid
|
||||
);
|
||||
|
||||
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
||||
$this->objects[$msguid] = $this->_unserialize($sql_arr);
|
||||
}
|
||||
}
|
||||
|
||||
// fetch from IMAP if not present in cache
|
||||
if (empty($this->objects[$msguid])) {
|
||||
$result = $this->_fetch(array($msguid), $type, $folder);
|
||||
$this->objects[$msguid] = $result[0];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->objects[$msguid];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function set($msguid, $object, $folder = null)
|
||||
{
|
||||
// write to cache
|
||||
if ($this->ready) {
|
||||
// TODO: handle $folder != $this->folder->name situations
|
||||
|
||||
// remove old entry
|
||||
$this->db->query("DELETE FROM kolab_cache WHERE resource=? AND msguid=?",
|
||||
$this->resource_uri, $msguid);
|
||||
|
||||
// write new object data if not false (wich means deleted)
|
||||
if ($object) {
|
||||
$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, data, xml, dtstart, dtend)".
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
$this->resource_uri,
|
||||
$objtype,
|
||||
$msguid,
|
||||
$object['uid'],
|
||||
$sql_data['data'],
|
||||
$sql_data['xml'],
|
||||
$sql_data['dtstart'],
|
||||
$sql_data['dtend']
|
||||
);
|
||||
|
||||
if (!$this->db->affected_rows($result)) {
|
||||
rcmail::raise_error(array(
|
||||
'code' => 900, 'type' => 'php',
|
||||
'message' => "Failed to write to kolab cache"
|
||||
), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// keep a copy in memory for fast access
|
||||
$this->objects[$msguid] = $object;
|
||||
|
||||
if ($object)
|
||||
$this->uid2msg[$object['uid']] = $msguid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove all objects from local cache
|
||||
*/
|
||||
public function purge($type = null)
|
||||
{
|
||||
$result = $this->db->query(
|
||||
"DELETE FROM kolab_cache WHERE resource=?".
|
||||
($type ? ' AND type=?' : ''),
|
||||
$this->resource_uri,
|
||||
$type
|
||||
);
|
||||
return $this->db->affected_rows($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT * FROM kolab_cache ".
|
||||
"WHERE resource=? " . $this->_sql_where($query),
|
||||
$this->resource_uri
|
||||
);
|
||||
|
||||
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
||||
if ($object = $this->_unserialize($sql_arr))
|
||||
$result[] = $object;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// extract object type from query parameter
|
||||
$filter = $this->_query2assoc($query);
|
||||
$result = $this->_fetch($this->index, $filter['type']);
|
||||
|
||||
// TODO: post-filter result according to query
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get number of objects mathing the given query
|
||||
*
|
||||
* @param string $type Object type (e.g. contact, event, todo, journal, note, configuration)
|
||||
* @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) {
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT COUNT(*) AS NUMROWS FROM kolab_cache ".
|
||||
"WHERE resource=? " . $this->_sql_where($query),
|
||||
$this->resource_uri
|
||||
);
|
||||
|
||||
$sql_arr = $this->db->fetch_assoc($sql_result);
|
||||
$count = intval($sql_arr['NUMROWS']);
|
||||
}
|
||||
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 compose a valid SQL query from pseudo filter triplets
|
||||
*/
|
||||
private function _sql_where($query)
|
||||
{
|
||||
$sql_where = '';
|
||||
foreach ($query as $param) {
|
||||
$sql_where .= sprintf(' AND %s%s%s',
|
||||
$this->db->quote_identifier($param[0]),
|
||||
$param[1],
|
||||
$this->db->quote($param[2])
|
||||
);
|
||||
}
|
||||
return $sql_where;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->uid2msg[$object['uid']] = $msguid;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper method to convert the given Kolab object into a dataset to be written to cache
|
||||
*/
|
||||
private function _serialize($object)
|
||||
{
|
||||
$bincols = array_flip($this->binary_cols);
|
||||
$sql_data = array('dtstart' => null, 'dtend' => null, 'xml' => '');
|
||||
|
||||
// set type specific values
|
||||
if ($this->folder->type == 'event') {
|
||||
// database runs in server's timetone so using date() is safe
|
||||
$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']);
|
||||
}
|
||||
|
||||
if ($object['_formatobj'])
|
||||
$sql_data['xml'] = (string)$object['_formatobj']->write();
|
||||
|
||||
// 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 ($key[0] != '_') {
|
||||
$data[$key] = $val;
|
||||
}
|
||||
else if ($key == '_attachments') {
|
||||
foreach ($val as $k => $att) {
|
||||
unset($att['content']);
|
||||
if ($att['id'])
|
||||
$data[$key][$k] = $att;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sql_data['data'] = serialize($data);
|
||||
return $sql_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to turn stored cache data into a valid storage object
|
||||
*/
|
||||
private function _unserialize($sql_arr)
|
||||
{
|
||||
$object = unserialize($sql_arr['data']);
|
||||
|
||||
// decode binary properties
|
||||
foreach ($this->binary_cols as $key) {
|
||||
if (!empty($object[$key]))
|
||||
$object[$key] = base64_decode($object[$key]);
|
||||
}
|
||||
|
||||
// add meta data
|
||||
$object['_type'] = $sql_arr['type'];
|
||||
$object['_msguid'] = $sql_arr['msguid'];
|
||||
$object['_mailbox'] = $this->folder->name;
|
||||
$object['_formatobj'] = kolab_format::factory($sql_arr['type'], $sql_arr['xml']);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
}
|
||||
|
||||
}
|
|
@ -40,11 +40,10 @@ class kolab_storage_folder
|
|||
public $type;
|
||||
|
||||
private $type_annotation;
|
||||
private $subpath;
|
||||
private $imap;
|
||||
private $info;
|
||||
private $owner;
|
||||
private $objcache = array();
|
||||
private $cache;
|
||||
private $uid2msg = array();
|
||||
|
||||
|
||||
|
@ -54,7 +53,8 @@ class kolab_storage_folder
|
|||
function __construct($name, $imap = null)
|
||||
{
|
||||
$this->imap = is_object($imap) ? $imap : rcube::get_instance()->get_storage();
|
||||
$this->imap->set_options(array('skip_deleted' => false));
|
||||
$this->imap->set_options(array('skip_deleted' => true));
|
||||
$this->cache = new kolab_storage_cache($this);
|
||||
$this->set_folder($name);
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,8 @@ class kolab_storage_folder
|
|||
$metadata = $this->imap->get_metadata($this->name, array(kolab_storage::CTYPE_KEY));
|
||||
$this->type_annotation = $metadata[$this->name][kolab_storage::CTYPE_KEY];
|
||||
$this->type = reset(explode('.', $this->type_annotation));
|
||||
|
||||
$this->cache->set_folder($this);
|
||||
}
|
||||
|
||||
|
||||
|
@ -232,13 +234,12 @@ class kolab_storage_folder
|
|||
{
|
||||
if (!$type) $type = $this->type;
|
||||
|
||||
// search by object type
|
||||
$ctype = self::KTYPE_PREFIX . $type;
|
||||
$index = $this->imap->search_once($this->name, 'UNDELETED HEADER X-Kolab-Type ' . $ctype);
|
||||
// TODO: synchronize cache first?
|
||||
|
||||
return $index->count();
|
||||
return $this->cache->count(array(array('type','=',$type)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* List all Kolab objects of the given type
|
||||
*
|
||||
|
@ -249,8 +250,15 @@ class kolab_storage_folder
|
|||
{
|
||||
if (!$type) $type = $this->type;
|
||||
|
||||
$ctype = self::KTYPE_PREFIX . $type;
|
||||
// synchronize caches
|
||||
$this->cache->synchronize();
|
||||
|
||||
// fetch objects from cache
|
||||
return $this->cache->select(array(array('type','=',$type)));
|
||||
|
||||
/*
|
||||
$results = array();
|
||||
$ctype = self::KTYPE_PREFIX . $type;
|
||||
|
||||
// use 'list' for folder's default objects
|
||||
if ($type == $this->type) {
|
||||
|
@ -272,6 +280,7 @@ class kolab_storage_folder
|
|||
// TODO: write $this->uid2msg to cache
|
||||
|
||||
return $results;
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
@ -283,8 +292,11 @@ class kolab_storage_folder
|
|||
*/
|
||||
public function get_object($uid)
|
||||
{
|
||||
$msguid = $this->uid2msguid($uid);
|
||||
if ($msguid && ($object = $this->read_object($msguid)))
|
||||
// synchronize caches
|
||||
$this->cache->synchronize();
|
||||
|
||||
$msguid = $this->cache->uid2msguid($uid);
|
||||
if ($msguid && ($object = $this->cache->get($msguid)))
|
||||
return $object;
|
||||
|
||||
return false;
|
||||
|
@ -303,10 +315,8 @@ class kolab_storage_folder
|
|||
*/
|
||||
public function get_attachment($uid, $part, $mailbox = null)
|
||||
{
|
||||
if ($msguid = ($mailbox ? $uid : $this->uid2msguid($uid))) {
|
||||
if ($mailbox)
|
||||
$this->imap->set_folder($mailbox);
|
||||
|
||||
if ($msguid = ($mailbox ? $uid : $this->cache->uid2msguid($uid))) {
|
||||
$this->imap->set_folder($mailbox ? $mailbox : $this->name);
|
||||
return $this->imap->get_message_part($msguid, $part);
|
||||
}
|
||||
|
||||
|
@ -319,25 +329,23 @@ class kolab_storage_folder
|
|||
* the Kolab groupware object from it
|
||||
*
|
||||
* @param string The IMAP message UID to fetch
|
||||
* @param string The object type expected
|
||||
* @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
|
||||
*/
|
||||
private function read_object($msguid, $type = null, $folder = null)
|
||||
public function read_object($msguid, $type = null, $folder = null)
|
||||
{
|
||||
if (!$type) $type = $this->type;
|
||||
if (!$folder) $folder = $this->name;
|
||||
$ctype = self::KTYPE_PREFIX . $type;
|
||||
|
||||
// requested message in local cache
|
||||
if ($this->objcache[$msguid])
|
||||
return $this->objcache[$msguid];
|
||||
|
||||
$this->imap->set_folder($folder);
|
||||
|
||||
// check ctype header and abort on mismatch
|
||||
$headers = $this->imap->get_message_headers($msguid);
|
||||
if ($headers->others['x-kolab-type'] != $ctype)
|
||||
$object_type = substr($headers->others['x-kolab-type'], strlen(self::KTYPE_PREFIX));
|
||||
$content_type = self::KTYPE_PREFIX . $object_type;
|
||||
|
||||
// check object type header and abort on mismatch
|
||||
if ($type != '*' && $object_type != $type)
|
||||
return false;
|
||||
|
||||
$message = new rcube_message($msguid);
|
||||
|
@ -345,7 +353,7 @@ class kolab_storage_folder
|
|||
|
||||
// get XML part
|
||||
foreach ((array)$message->attachments as $part) {
|
||||
if (!$xml && ($part->mimetype == $ctype || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype))) {
|
||||
if (!$xml && ($part->mimetype == $content_type || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype))) {
|
||||
$xml = $part->body ? $part->body : $message->get_part_content($part->mime_id);
|
||||
}
|
||||
else if ($part->filename) {
|
||||
|
@ -368,14 +376,17 @@ class kolab_storage_folder
|
|||
return false;
|
||||
}
|
||||
|
||||
$format = kolab_format::factory($type);
|
||||
$format = kolab_format::factory($object_type);
|
||||
|
||||
if (is_a($format, 'PEAR_Error'))
|
||||
return false;
|
||||
|
||||
// check kolab format version
|
||||
if (strpos($xml, '<' . $type) !== false) {
|
||||
if (strpos($xml, '<' . $object_type) !== false) {
|
||||
// old Kolab 2.0 format detected
|
||||
$handler = Horde_Kolab_Format::factory('XML', $type);
|
||||
if (is_object($handler) && is_a($handler, 'PEAR_Error')) {
|
||||
continue;
|
||||
$handler = class_exists('Horde_Kolab_Format') ? Horde_Kolab_Format::factory('XML', $object_type) : null;
|
||||
if (!is_object($handler) || is_a($handler, 'PEAR_Error')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// XML-to-array
|
||||
|
@ -389,12 +400,12 @@ class kolab_storage_folder
|
|||
|
||||
if ($format->is_valid()) {
|
||||
$object = $format->to_array();
|
||||
$object['_type'] = $object_type;
|
||||
$object['_msguid'] = $msguid;
|
||||
$object['_mailbox'] = $this->name;
|
||||
$object['_attachments'] = array_merge((array)$object['_attachments'], $attachments);
|
||||
$object['_formatobj'] = $format;
|
||||
|
||||
$this->objcache[$msguid] = $object;
|
||||
return $object;
|
||||
}
|
||||
|
||||
|
@ -416,8 +427,8 @@ class kolab_storage_folder
|
|||
$type = $this->type;
|
||||
|
||||
// copy attachments from old message
|
||||
if (!empty($object['_msguid']) && ($old = $this->read_object($object['_msguid'], $type, $object['_mailbox']))) {
|
||||
foreach ($old['_attachments'] as $name => $att) {
|
||||
if (!empty($object['_msguid']) && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox']))) {
|
||||
foreach ((array)$old['_attachments'] as $name => $att) {
|
||||
if (!isset($object['_attachments'][$name])) {
|
||||
$object['_attachments'][$name] = $old['_attachments'][$name];
|
||||
}
|
||||
|
@ -435,13 +446,18 @@ class kolab_storage_folder
|
|||
// delete old message
|
||||
if ($result && !empty($object['_msguid']) && !empty($object['_mailbox'])) {
|
||||
$this->imap->delete_message($object['_msguid'], $object['_mailbox']);
|
||||
$this->cache->set($object['_msguid'], false, $object['_mailbox']);
|
||||
}
|
||||
else if ($result && $uid && ($msguid = $this->uid2msguid($uid))) {
|
||||
else if ($result && $uid && ($msguid = $this->cache->uid2msguid($uid))) {
|
||||
$this->imap->delete_message($msguid, $this->name);
|
||||
$this->cache->set($object['_msguid'], false);
|
||||
}
|
||||
|
||||
// TODO: update cache with new UID
|
||||
$this->uid2msg[$object['uid']] = $result;
|
||||
// update cache with new UID
|
||||
if ($result) {
|
||||
$object['_msguid'] = $result;
|
||||
$this->cache->set($result, $object);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
@ -459,16 +475,21 @@ class kolab_storage_folder
|
|||
*/
|
||||
public function delete($object, $expunge = true, $trigger = true)
|
||||
{
|
||||
$msguid = is_array($object) ? $object['_msguid'] : $this->uid2msguid($object);
|
||||
$msguid = is_array($object) ? $object['_msguid'] : $this->cache->uid2msguid($object);
|
||||
$success = false;
|
||||
|
||||
if ($msguid && $expunge) {
|
||||
return $this->imap->delete_message($msguid, $this->name);
|
||||
$success = $this->imap->delete_message($msguid, $this->name);
|
||||
}
|
||||
else if ($msguid) {
|
||||
return $this->imap->set_flag($msguid, 'DELETED', $this->name);
|
||||
$success = $this->imap->set_flag($msguid, 'DELETED', $this->name);
|
||||
}
|
||||
|
||||
return false;
|
||||
if ($success) {
|
||||
$this->cache->set($result, false);
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
|
||||
|
@ -477,6 +498,7 @@ class kolab_storage_folder
|
|||
*/
|
||||
public function delete_all()
|
||||
{
|
||||
$this->cache->purge();
|
||||
return $this->imap->clear_folder($this->name);
|
||||
}
|
||||
|
||||
|
@ -489,7 +511,7 @@ class kolab_storage_folder
|
|||
*/
|
||||
public function undelete($uid)
|
||||
{
|
||||
if ($msguid = $this->uid2msguid($uid, true)) {
|
||||
if ($msguid = $this->cache->uid2msguid($uid, true)) {
|
||||
if ($this->imap->set_flag($msguid, 'UNDELETED', $this->name)) {
|
||||
return $msguid;
|
||||
}
|
||||
|
@ -508,7 +530,7 @@ class kolab_storage_folder
|
|||
*/
|
||||
public function move($uid, $target_folder)
|
||||
{
|
||||
if ($msguid = $this->uid2msguid($uid)) {
|
||||
if ($msguid = $this->cache->uid2msguid($uid)) {
|
||||
if ($success = $this->imap->move_message($msguid, $target_folder, $this->name)) {
|
||||
// TODO: update cache
|
||||
return true;
|
||||
|
@ -526,24 +548,6 @@ class kolab_storage_folder
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolve an object UID into an IMAP message UID
|
||||
*/
|
||||
private function uid2msguid($uid, $deleted = false)
|
||||
{
|
||||
if (!isset($this->uid2msg[$uid])) {
|
||||
// use IMAP SEARCH to get the right message
|
||||
$index = $this->imap->search_once($this->name, ($deleted ? '' : 'UNDELETED ') . 'HEADER SUBJECT ' . $uid);
|
||||
$results = $index->get();
|
||||
$this->uid2msg[$uid] = $results[0];
|
||||
|
||||
// TODO: cache this lookup
|
||||
}
|
||||
|
||||
return $this->uid2msg[$uid];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates source of the configuration object message
|
||||
*/
|
||||
|
@ -552,7 +556,7 @@ class kolab_storage_folder
|
|||
// load old object to preserve data we don't understand/process
|
||||
if (is_object($object['_formatobj']))
|
||||
$format = $object['_formatobj'];
|
||||
else if ($object['_msguid'] && ($old = $this->read_object($object['_msguid'], $type, $object['_mailbox'])))
|
||||
else if ($object['_msguid'] && ($old = $this->cache->get($object['_msguid'], $type, $object['_mailbox'])))
|
||||
$format = $old['_formatobj'];
|
||||
|
||||
// create new kolab_format instance
|
||||
|
|
Loading…
Add table
Reference in a new issue