Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F1974678
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
34 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/plugins/libkolab/SQL/postgres.initial.sql b/plugins/libkolab/SQL/postgres.initial.sql
new file mode 100644
index 00000000..df396883
--- /dev/null
+++ b/plugins/libkolab/SQL/postgres.initial.sql
@@ -0,0 +1,207 @@
+CREATE SEQUENCE kolab_folders_seq
+ INCREMENT BY 1
+ NO MAXVALUE
+ NO MINVALUE
+ CACHE 1;
+
+CREATE TABLE kolab_folders (
+ folder_id integer DEFAULT nextval('kolab_folders_seq'::text) PRIMARY KEY,
+ resource varchar(255) NOT NULL,
+ "type" varchar(32) NOT NULL,
+ synclock integer NOT NULL DEFAULT 0,
+ ctag varchar(40) DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ objectcount integer DEFAULT NULL
+);
+
+CREATE INDEX kolab_folders_resource_type_idx ON kolab_folders(resource, "type");
+
+CREATE TABLE kolab_cache_contact (
+ folder_id integer NOT NULL
+ REFERENCES kolab_folders (folder_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ msguid integer NOT NULL,
+ uid varchar(512) NOT NULL,
+ created timestamp with time zone DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ data text NOT NULL,
+ tags text NOT NULL,
+ words text NOT NULL,
+ "type" varchar(32) NOT NULL,
+ name varchar(255) NOT NULL,
+ firstname varchar(255) NOT NULL,
+ surname varchar(255) NOT NULL,
+ email varchar(255) NOT NULL,
+ PRIMARY KEY(folder_id, msguid)
+);
+
+CREATE INDEX kolab_cache_contact_type_idx ON kolab_cache_contact(folder_id, "type");
+CREATE INDEX kolab_cache_contact_uid2msguid_idx ON kolab_cache_contact(folder_id, uid, msguid);
+
+CREATE TABLE kolab_cache_event (
+ folder_id integer NOT NULL
+ REFERENCES kolab_folders (folder_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ msguid integer NOT NULL,
+ uid varchar(512) NOT NULL,
+ created timestamp with time zone DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ data text NOT NULL,
+ tags text NOT NULL,
+ words text NOT NULL,
+ dtstart timestamp with time zone,
+ dtend timestamp with time zone,
+ PRIMARY KEY(folder_id, msguid)
+);
+
+CREATE INDEX kolab_cache_event_uid2msguid_idx ON kolab_cache_event(folder_id, uid, msguid);
+
+CREATE TABLE kolab_cache_task (
+ folder_id integer NOT NULL
+ REFERENCES kolab_folders (folder_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ msguid integer NOT NULL,
+ uid varchar(512) NOT NULL,
+ created timestamp with time zone DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ data text NOT NULL,
+ tags text NOT NULL,
+ words text NOT NULL,
+ dtstart timestamp with time zone,
+ dtend timestamp with time zone,
+ PRIMARY KEY(folder_id, msguid)
+);
+
+CREATE INDEX kolab_cache_task_uid2msguid_idx ON kolab_cache_task(folder_id, uid, msguid);
+
+CREATE TABLE kolab_cache_journal (
+ folder_id integer NOT NULL
+ REFERENCES kolab_folders (folder_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ msguid integer NOT NULL,
+ uid varchar(512) NOT NULL,
+ created timestamp with time zone DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ data text NOT NULL,
+ tags text NOT NULL,
+ words text NOT NULL,
+ dtstart timestamp with time zone,
+ dtend timestamp with time zone,
+ PRIMARY KEY(folder_id, msguid)
+);
+
+CREATE INDEX kolab_cache_journal_uid2msguid_idx ON kolab_cache_journal(folder_id, uid, msguid);
+
+CREATE TABLE kolab_cache_note (
+ folder_id integer NOT NULL
+ REFERENCES kolab_folders (folder_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ msguid integer NOT NULL,
+ uid varchar(512) NOT NULL,
+ created timestamp with time zone DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ data text NOT NULL,
+ tags text NOT NULL,
+ words text NOT NULL,
+ PRIMARY KEY(folder_id, msguid)
+);
+
+CREATE INDEX kolab_cache_note_uid2msguid_idx ON kolab_cache_note(folder_id, uid, msguid);
+
+CREATE TABLE kolab_cache_file (
+ folder_id integer NOT NULL
+ REFERENCES kolab_folders (folder_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ msguid integer NOT NULL,
+ uid varchar(512) NOT NULL,
+ created timestamp with time zone DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ data text NOT NULL,
+ tags text NOT NULL,
+ words text NOT NULL,
+ filename varchar(255) DEFAULT NULL,
+ PRIMARY KEY(folder_id, msguid)
+);
+
+CREATE INDEX kolab_cache_file_filename_idx ON kolab_cache_file(folder_id, filename);
+CREATE INDEX kolab_cache_file_uid2msguid_idx ON kolab_cache_file(folder_id, uid, msguid);
+
+CREATE TABLE kolab_cache_configuration (
+ folder_id integer NOT NULL
+ REFERENCES kolab_folders (folder_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ msguid integer NOT NULL,
+ uid varchar(512) NOT NULL,
+ created timestamp with time zone DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ data text NOT NULL,
+ tags text NOT NULL,
+ words text NOT NULL,
+ "type" varchar(32) NOT NULL,
+ PRIMARY KEY(folder_id, msguid)
+);
+
+CREATE INDEX kolab_cache_configuration_type_idx ON kolab_cache_configuration(folder_id, "type");
+CREATE INDEX kolab_cache_configuration_uid2msguid_idx ON kolab_cache_configuration(folder_id, uid, msguid);
+
+CREATE TABLE kolab_cache_freebusy (
+ folder_id integer NOT NULL
+ REFERENCES kolab_folders (folder_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ msguid integer NOT NULL,
+ uid varchar(512) NOT NULL,
+ created timestamp with time zone DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ data text NOT NULL,
+ tags text NOT NULL,
+ words text NOT NULL,
+ dtstart timestamp with time zone,
+ dtend timestamp with time zone,
+ PRIMARY KEY(folder_id, msguid)
+);
+
+CREATE INDEX kolab_cache_freebusy_uid2msguid_idx ON kolab_cache_freebusy(folder_id, uid, msguid);
+
+CREATE TABLE kolab_cache_dav_contact (
+ folder_id integer NOT NULL
+ REFERENCES kolab_folders (folder_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ uid varchar(512) NOT NULL,
+ etag varchar(128) NOT NULL,
+ created timestamp with time zone DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ data text NOT NULL,
+ tags text NOT NULL,
+ words text NOT NULL,
+ "type" varchar(32) NOT NULL,
+ name varchar(255) NOT NULL,
+ firstname varchar(255) NOT NULL,
+ surname varchar(255) NOT NULL,
+ email varchar(255) NOT NULL,
+ PRIMARY KEY(folder_id, uid)
+);
+
+CREATE INDEX kolab_cache_dav_contact_type_idx ON kolab_cache_dav_contact(folder_id, "type");
+
+CREATE TABLE kolab_cache_dav_event (
+ folder_id integer NOT NULL
+ REFERENCES kolab_folders (folder_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ uid varchar(512) NOT NULL,
+ etag varchar(128) NOT NULL,
+ created timestamp with time zone DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ data text NOT NULL,
+ tags text NOT NULL,
+ words text NOT NULL,
+ dtstart timestamp with time zone,
+ dtend timestamp with time zone,
+ PRIMARY KEY(folder_id, uid)
+);
+
+CREATE TABLE kolab_cache_dav_task (
+ folder_id integer NOT NULL
+ REFERENCES kolab_folders (folder_id) ON DELETE CASCADE ON UPDATE CASCADE,
+ uid varchar(512) NOT NULL,
+ etag varchar(128) NOT NULL,
+ created timestamp with time zone DEFAULT NULL,
+ changed timestamp with time zone DEFAULT NULL,
+ data text NOT NULL,
+ tags text NOT NULL,
+ words text NOT NULL,
+ dtstart timestamp with time zone,
+ dtend timestamp with time zone,
+ PRIMARY KEY(folder_id, uid)
+);
+
+INSERT INTO "system" (name, "value") VALUES ('libkolab-version', '2022122800');
diff --git a/plugins/libkolab/lib/kolab_storage_dav_cache.php b/plugins/libkolab/lib/kolab_storage_dav_cache.php
index 68a97708..2bc4b317 100644
--- a/plugins/libkolab/lib/kolab_storage_dav_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_dav_cache.php
@@ -1,745 +1,747 @@
<?php
/**
* Kolab storage cache class providing a local caching layer for Kolab groupware objects.
*
* @author Aleksander Machniak <machniak@apheleia-it.ch>
*
* Copyright (C) 2012-2022, Apheleia IT AG <contact@apheleia-it.ch>
*
* 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_dav_cache extends kolab_storage_cache
{
/**
* Factory constructor
*/
public static function factory(kolab_storage_folder $storage_folder)
{
$subclass = 'kolab_storage_dav_cache_' . $storage_folder->type;
if (class_exists($subclass)) {
return new $subclass($storage_folder);
}
rcube::raise_error(
['code' => 900, 'message' => "No {$subclass} class found for folder '{$storage_folder->name}'"],
true
);
return new kolab_storage_dav_cache($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 (!$this->folder->valid) {
$this->ready = false;
return;
}
// 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_dav_' . $this->folder->type);
$this->ready = true;
}
/**
* Synchronize local cache data with remote
*/
public function synchronize()
{
// only sync once per request cycle
if ($this->synched) {
return;
}
$this->sync_start = time();
// read cached folder metadata
$this->_read_folder_data();
$ctag = $this->folder->get_ctag();
// check cache status ($this->metadata is set in _read_folder_data())
if (
empty($this->metadata['ctag'])
|| empty($this->metadata['changed'])
|| $this->metadata['ctag'] !== $ctag
) {
// lock synchronization for this folder and wait if already locked
$this->_sync_lock();
$result = $this->synchronize_worker();
// update ctag value (will be written to database in _sync_unlock())
if ($result) {
$this->metadata['ctag'] = $ctag;
$this->metadata['changed'] = date(self::DB_DATE_FORMAT, time());
}
// remove lock
$this->_sync_unlock();
}
$this->synched = time();
}
/**
* Perform cache synchronization
*/
protected function synchronize_worker()
{
// get effective time limit we have for synchronization (~70% of the execution time)
$time_limit = $this->_max_sync_lock_time() * 0.7;
if (time() - $this->sync_start > $time_limit) {
return false;
}
// TODO: Implement synchronization with use of WebDAV-Sync (RFC 6578)
// Get the objects from the DAV server
$dav_index = $this->folder->dav->getIndex($this->folder->href, $this->folder->get_dav_type());
if (!is_array($dav_index)) {
rcube::raise_error([
'code' => 900,
'message' => "Failed to sync the kolab cache for {$this->folder->href}"
], true);
return false;
}
// WARNING: For now we assume object's href is <calendar-href>/<uid>.ics,
// which would mean there are no duplicates (objects with the same uid).
// With DAV protocol we can't get UID without fetching the whole object.
// Also the folder_id + uid is a unique index in the database.
// In the future we maybe should store the href in database.
// Determine objects to fetch or delete
$new_index = [];
$update_index = [];
$old_index = $this->folder_index(); // uid -> etag
$chunk_size = 20; // max numer of objects per DAV request
foreach ($dav_index as $object) {
$uid = $object['uid'];
if (isset($old_index[$uid])) {
$old_etag = $old_index[$uid];
$old_index[$uid] = null;
if ($old_etag === $object['etag']) {
// the object didn't change
continue;
}
$update_index[$uid] = $object['href'];
}
else {
$new_index[$uid] = $object['href'];
}
}
$i = 0;
// Fetch new objects and store in DB
if (!empty($new_index)) {
foreach (array_chunk($new_index, $chunk_size, true) as $chunk) {
$objects = $this->folder->dav->getData($this->folder->href, $this->folder->get_dav_type(), $chunk);
if (!is_array($objects)) {
rcube::raise_error([
'code' => 900,
'message' => "Failed to sync the kolab cache for {$this->folder->href}"
], true);
return false;
}
foreach ($objects as $dav_object) {
if ($object = $this->folder->from_dav($dav_object)) {
$object['_raw'] = $dav_object['data'];
$this->_extended_insert(false, $object);
unset($object['_raw']);
}
}
$this->_extended_insert(true, null);
// check time limit and abort sync if running too long
if (++$i % 25 == 0 && time() - $this->sync_start > $time_limit) {
return false;
}
}
}
// Fetch updated objects and store in DB
if (!empty($update_index)) {
foreach (array_chunk($update_index, $chunk_size, true) as $chunk) {
$objects = $this->folder->dav->getData($this->folder->href, $this->folder->get_dav_type(), $chunk);
if (!is_array($objects)) {
rcube::raise_error([
'code' => 900,
'message' => "Failed to sync the kolab cache for {$this->folder->href}"
], true);
return false;
}
foreach ($objects as $dav_object) {
if ($object = $this->folder->from_dav($dav_object)) {
$object['_raw'] = $dav_object['data'];
$this->save($object, $object['uid']);
unset($object['_raw']);
}
}
// check time limit and abort sync if running too long
if (++$i % 25 == 0 && time() - $this->sync_start > $time_limit) {
return false;
}
}
}
// Remove deleted objects
$old_index = array_filter($old_index);
if (!empty($old_index)) {
$quoted_uids = join(',', array_map(array($this->db, 'quote'), $old_index));
$this->db->query(
"DELETE FROM `{$this->cache_table}` WHERE `folder_id` = ? AND `uid` IN ($quoted_uids)",
$this->folder_id
);
}
return true;
}
/**
* Return current folder index (uid -> etag)
*/
public function folder_index()
{
$this->_read_folder_data();
// read cache index
$sql_result = $this->db->query(
"SELECT `uid`, `etag` FROM `{$this->cache_table}` WHERE `folder_id` = ?",
$this->folder_id
);
$index = [];
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
$index[$sql_arr['uid']] = $sql_arr['etag'];
}
return $index;
}
/**
* Read a single entry from cache or from server directly
*
* @param string Object UID
* @param string Object type to read
* @param string Unused (kept for compat. with the parent class)
*
* @return null|array An array of objects, NULL if not found
*/
public function get($uid, $type = null, $unused = null)
{
if ($this->ready) {
$this->_read_folder_data();
$sql_result = $this->db->query(
"SELECT * FROM `{$this->cache_table}` WHERE `folder_id` = ? AND `uid` = ?",
$this->folder_id,
$uid
);
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
$object = $this->_unserialize($sql_arr);
}
}
// fetch from DAV if not present in cache
if (empty($object)) {
if ($object = $this->folder->read_object($uid, $type ?: '*')) {
$this->save($object);
}
}
return $object ?: null;
}
/**
* Read multiple entries from the server directly
*
* @param array Object UIDs
*
* @return false|array An array of objects, False on error
*/
public function multiget($uids)
{
return $this->folder->read_objects($uids);
}
/**
* Insert/Update a cache entry
*
* @param string Object UID
* @param array|false Hash array with object properties to save or false to delete the cache entry
* @param string Unused (kept for compat. with the parent class)
*/
public function set($uid, $object, $unused = null)
{
// remove old entry
if ($this->ready) {
$this->_read_folder_data();
$this->db->query(
"DELETE FROM `{$this->cache_table}` WHERE `folder_id` = ? AND `uid` = ?",
$this->folder_id,
$uid
);
}
if ($object) {
$this->save($object);
}
}
/**
* Insert (or update) a cache entry
*
* @param mixed Hash array with object properties to save or false to delete the cache entry
* @param string Optional old message UID (for update)
* @param string Unused (kept for compat. with the parent class)
*/
public function save($object, $olduid = null, $unused = null)
{
// write to cache
if ($this->ready) {
$this->_read_folder_data();
$sql_data = $this->_serialize($object);
$sql_data['folder_id'] = $this->folder_id;
$sql_data['uid'] = rcube_charset::clean($object['uid']);
$sql_data['etag'] = rcube_charset::clean($object['etag']);
$args = [];
$cols = ['folder_id', 'uid', 'etag', 'changed', 'data', 'tags', 'words'];
$cols = array_merge($cols, $this->extra_cols);
foreach ($cols as $idx => $col) {
$cols[$idx] = $this->db->quote_identifier($col);
$args[] = $sql_data[$col];
}
if ($olduid) {
foreach ($cols as $idx => $col) {
$cols[$idx] = "$col = ?";
}
$query = "UPDATE `{$this->cache_table}` SET " . implode(', ', $cols)
. " WHERE `folder_id` = ? AND `uid` = ?";
$args[] = $this->folder_id;
$args[] = $olduid;
}
else {
$query = "INSERT INTO `{$this->cache_table}` (`created`, " . implode(', ', $cols)
. ") VALUES (" . $this->db->now() . str_repeat(', ?', count($cols)) . ")";
}
$result = $this->db->query($query, $args);
if (!$this->db->affected_rows($result)) {
rcube::raise_error([
'code' => 900,
'message' => "Failed to write to kolab cache"
], true);
}
}
}
/**
* Move an existing cache entry to a new resource
*
* @param string Entry's UID
* @param kolab_storage_folder Target storage folder instance
* @param string Unused (kept for compat. with the parent class)
* @param string Unused (kept for compat. with the parent class)
*/
public function move($uid, $target, $unused1 = null, $unused2 = null)
{
// TODO
}
/**
* Update resource URI for existing folder
*
* @param string Target DAV folder to move it to
*/
public function rename($new_folder)
{
// TODO
}
/**
* Select Kolab objects filtered by the given query
*
* @param array Pseudo-SQL query as list of filter parameter triplets
* triplet: ['<colname>', '<comparator>', '<value>']
* @param bool Set true to only return UIDs instead of complete objects
* @param bool Use fast mode to fetch only minimal set of information
* (no xml fetching and parsing, etc.)
*
* @return array|null|kolab_storage_dataset List of Kolab data objects (each represented as hash array) or UIDs
*/
public function select($query = [], $uids = false, $fast = false)
{
$result = $uids ? [] : new kolab_storage_dataset($this);
$count = null;
$this->_read_folder_data();
// fetch full object data on one query if a small result set is expected
$fetchall = !$uids && ($this->limit ? $this->limit[0] : ($count = $this->count($query))) < self::MAX_RECORDS;
// skip SELECT if we know it will return nothing
if ($count === 0) {
return $result;
}
$sql_query = "SELECT " . ($fetchall ? '*' : "`uid`")
. " FROM `{$this->cache_table}` WHERE `folder_id` = ?"
. $this->_sql_where($query)
. (!empty($this->order_by) ? " ORDER BY " . $this->order_by : '');
$sql_result = $this->limit ?
$this->db->limitquery($sql_query, $this->limit[1], $this->limit[0], $this->folder_id) :
$this->db->query($sql_query, $this->folder_id);
if ($this->db->is_error($sql_result)) {
if ($uids) {
return null;
}
$result->set_error(true);
return $result;
}
while ($sql_arr = $this->db->fetch_assoc($sql_result)) {
if ($uids) {
$result[] = $sql_arr['uid'];
}
else if (!$fetchall) {
$result[] = $sql_arr;
}
else if (($object = $this->_unserialize($sql_arr, true, $fast))) {
$result[] = $object;
}
else {
$result[] = $sql_arr['uid'];
}
}
return $result;
}
/**
* Get number of objects mathing the given query
*
* @param array $query Pseudo-SQL query as list of filter parameter triplets
*
* @return int The number of objects of the given type
*/
public function count($query = [])
{
// read from local cache DB (assume it to be synchronized)
$this->_read_folder_data();
$sql_result = $this->db->query(
"SELECT COUNT(*) AS `numrows` FROM `{$this->cache_table}` ".
"WHERE `folder_id` = ?" . $this->_sql_where($query),
$this->folder_id
);
if ($this->db->is_error($sql_result)) {
return null;
}
$sql_arr = $this->db->fetch_assoc($sql_result);
$count = intval($sql_arr['numrows']);
return $count;
}
/**
* Getter for a single Kolab object identified by its UID
*
* @param string $uid Object UID
*
* @return array|null The Kolab object represented as hash array
*/
public function get_by_uid($uid)
{
$old_limit = $this->limit;
// set limit to skip count query
$this->limit = [1, 0];
$list = $this->select([['uid', '=', $uid]]);
// set the limit back to defined value
$this->limit = $old_limit;
if (!empty($list) && !empty($list[0])) {
return $list[0];
}
}
/**
* Write records into cache using extended inserts to reduce the number of queries to be executed
*
* @param bool Set to false to commit buffered insert, true to force an insert
* @param array Kolab object to cache
*/
protected function _extended_insert($force, $object)
{
static $buffer = '';
$line = '';
$cols = ['folder_id', 'uid', 'etag', 'created', 'changed', 'data', 'tags', 'words'];
if ($this->extra_cols) {
$cols = array_merge($cols, $this->extra_cols);
}
if ($object) {
$sql_data = $this->_serialize($object);
- // Skip multi-folder insert for all databases but MySQL
- // In Oracle we can't put long data inline, others we don't support yet
- if (strpos($this->db->db_provider, 'mysql') !== 0) {
+ // Skip multi-folder insert for all databases but MySQL and Postgres
+ if (!preg_match('/^(mysql|postgres)/', $this->db->db_provider)) {
$extra_args = [];
$params = [
$this->folder_id,
rcube_charset::clean($object['uid']),
rcube_charset::clean($object['etag']),
$sql_data['changed'],
$sql_data['data'],
$sql_data['tags'],
$sql_data['words']
];
foreach ($this->extra_cols as $col) {
$params[] = $sql_data[$col];
$extra_args[] = '?';
}
$cols = implode(', ', array_map(function($n) { return "`{$n}`"; }, $cols));
$extra_args = count($extra_args) ? ', ' . implode(', ', $extra_args) : '';
$result = $this->db->query(
"INSERT INTO `{$this->cache_table}` ($cols)"
. " VALUES (?, ?, ?, " . $this->db->now() . ", ?, ?, ?, ?$extra_args)",
$params
);
if (!$this->db->affected_rows($result)) {
rcube::raise_error(array(
'code' => 900, 'message' => "Failed to write to kolab cache"
), true);
}
return;
}
$values = array(
$this->db->quote($this->folder_id),
$this->db->quote(rcube_charset::clean($object['uid'])),
$this->db->quote(rcube_charset::clean($object['etag'])),
!empty($sql_data['created']) ? $this->db->quote($sql_data['created']) : $this->db->now(),
$this->db->quote($sql_data['changed']),
$this->db->quote($sql_data['data']),
$this->db->quote($sql_data['tags']),
$this->db->quote($sql_data['words']),
);
foreach ($this->extra_cols as $col) {
$values[] = $this->db->quote($sql_data[$col]);
}
$line = '(' . join(',', $values) . ')';
}
if ($buffer && ($force || (strlen($buffer) + strlen($line) > $this->max_sql_packet()))) {
$columns = implode(', ', array_map(function($n) { return "`{$n}`"; }, $cols));
- $update = implode(', ', array_map(function($i) { return "`{$i}` = VALUES(`{$i}`)"; }, array_slice($cols, 2)));
- $result = $this->db->query(
- "INSERT INTO `{$this->cache_table}` ($columns) VALUES $buffer"
- . " ON DUPLICATE KEY UPDATE $update"
- );
+ if ($this->db->db_provider == 'postgres') {
+ $update = "ON CONFLICT (folder_id, uid) DO UPDATE SET "
+ . implode(', ', array_map(function($i) { return "`{$i}` = EXCLUDED.`{$i}`"; }, array_slice($cols, 2)));
+ }
+ else {
+ $update = "ON DUPLICATE KEY UPDATE "
+ . implode(', ', array_map(function($i) { return "`{$i}` = VALUES(`{$i}`)"; }, array_slice($cols, 2)));
+ }
+
+ $result = $this->db->query("INSERT INTO `{$this->cache_table}` ($columns) VALUES $buffer $update");
if (!$this->db->affected_rows($result)) {
- rcube::raise_error(array(
- 'code' => 900, 'message' => "Failed to write to kolab cache"
- ), true);
+ rcube::raise_error(['code' => 900, 'message' => "Failed to write to kolab cache"], true);
}
$buffer = '';
}
$buffer .= ($buffer ? ',' : '') . $line;
}
/**
* Helper method to convert the given Kolab object into a dataset to be written to cache
*/
protected function _serialize($object)
{
static $threshold;
if ($threshold === null) {
$rcube = rcube::get_instance();
$threshold = parse_bytes(rcube::get_instance()->config->get('dav_cache_threshold', 0));
}
$data = [];
$sql_data = ['created' => date(self::DB_DATE_FORMAT), 'changed' => null, 'tags' => '', 'words' => ''];
if (!empty($object['changed'])) {
$sql_data['changed'] = self::_convert_datetime($object['changed']);
}
if (!empty($object['created'])) {
$sql_data['created'] = self::_convert_datetime($object['created']);
}
// Store only minimal set of object properties
foreach ($this->data_props as $prop) {
if (isset($object[$prop])) {
$data[$prop] = $object[$prop];
if ($data[$prop] instanceof DateTimeInterface) {
$data[$prop] = array(
'cl' => 'DateTime',
'dt' => $data[$prop]->format('Y-m-d H:i:s'),
'tz' => $data[$prop]->getTimezone()->getName(),
);
}
}
}
if (!empty($object['_raw']) && $threshold > 0 && strlen($object['_raw']) <= $threshold) {
$data['_raw'] = $object['_raw'];
}
$sql_data['data'] = json_encode(rcube_charset::clean($data));
return $sql_data;
}
/**
* Helper method to turn stored cache data into a valid storage object
*/
protected function _unserialize($sql_arr, $noread = false, $fast_mode = false)
{
$init = function(&$object) use ($sql_arr) {
if ($sql_arr['created'] && empty($object['created'])) {
$object['created'] = new DateTime($sql_arr['created'], $this->server_timezone);
}
if ($sql_arr['changed'] && empty($object['changed'])) {
$object['changed'] = new DateTime($sql_arr['changed'], $this->server_timezone);
}
$object['_type'] = !empty($sql_arr['type']) ? $sql_arr['type'] : $this->folder->type;
$object['uid'] = $sql_arr['uid'];
$object['etag'] = $sql_arr['etag'];
};
if (!empty($sql_arr['data']) && ($object = json_decode($sql_arr['data'], true))) {
foreach ($this->data_props as $prop) {
if (isset($object[$prop]) && is_array($object[$prop])
&& isset($object[$prop]['cl']) && $object[$prop]['cl'] == 'DateTime'
) {
$object[$prop] = new DateTime($object[$prop]['dt'], new DateTimeZone($object[$prop]['tz']));
}
else if (!isset($object[$prop]) && isset($sql_arr[$prop])) {
$object[$prop] = $sql_arr[$prop];
}
}
$init($object);
}
if (!empty($fast_mode) && !empty($object)) {
unset($object['_raw']);
}
else if ($noread) {
// We have the raw content already, parse it
if (!empty($object['_raw'])) {
$object['data'] = $object['_raw'];
if ($object = $this->folder->from_dav($object)) {
$init($object);
return $object;
}
}
return null;
}
else {
// Fetch a complete object from the server
$object = $this->folder->read_object($sql_arr['uid'], '*');
}
return $object;
}
/**
* Read this folder's ID and cache metadata
*/
protected function _read_folder_data()
{
// already done
if (!empty($this->folder_id) || !$this->ready) {
return;
}
// Different than in Kolab XML-based storage, in *DAV folders can
// contain different types of data, e.g. Calendar can store events and tasks.
// Therefore we both `resource` and `type` in WHERE.
$sql_arr = $this->db->fetch_assoc($this->db->query(
"SELECT `folder_id`, `synclock`, `ctag`, `changed` FROM `{$this->folders_table}`"
. " WHERE `resource` = ? AND `type` = ?",
$this->resource_uri,
$this->folder->type
));
if ($sql_arr) {
$this->folder_id = $sql_arr['folder_id'];
$this->metadata = $sql_arr;
}
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 = [];
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Sep 14, 1:39 PM (15 h, 53 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
281247
Default Alt Text
(34 KB)
Attached To
Mode
R14 roundcubemail-plugins-kolab
Attached
Detach File
Event Timeline
Log In to Comment