summaryrefslogtreecommitdiff
path: root/lib/kolab_sync_data.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kolab_sync_data.php')
-rw-r--r--lib/kolab_sync_data.php198
1 files changed, 138 insertions, 60 deletions
diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php
index 09956c2..9589b73 100644
--- a/lib/kolab_sync_data.php
+++ b/lib/kolab_sync_data.php
@@ -536,12 +536,15 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
if (empty($filter)) {
$filter = array();
}
+ else {
+ $changed_objects = $this->getChangesByRelations($folderid, $filter);
+ }
$result = $result_type == self::RESULT_COUNT ? 0 : array();
$found = 0;
- foreach ($folders as $folderid) {
- $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
+ foreach ($folders as $folder_id) {
+ $foldername = $this->backend->folder_id2name($folder_id, $this->device->deviceid);
if ($foldername === null || !($folder = $this->getFolderObject($foldername))) {
continue;
@@ -577,108 +580,183 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
if ($error) {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR);
}
+
+ // handle tag modifications
+ if (!empty($changed_objects)) {
+ // build new filter
+ // search objects mathing current filter,
+ // relations may contain members of many types, we need to
+ // search them by UID in all requested folders to get
+ // only these with requested type (and that really exist
+ // in specified folders)
+ $tag_filter = array(array('uid', '=', $changed_objects));
+ foreach ($filter as $f) {
+ if ($f[0] != 'changed') {
+ $tag_filter[] = $f;
+ }
+ }
+
+ switch ($result_type) {
+ case self::RESULT_COUNT:
+ // Note: this way we're potentally counting the same objects twice
+ // I'm not sure if this is a problem, we most likely do not
+ // need a precise result here
+ $count = $folder->count($tag_filter);
+ if ($count !== null && $count !== false) {
+ $result += (int) $count;
+ }
+
+ break;
+
+ case self::RESULT_UID:
+ $uids = $folder->get_uids($tag_filter);
+ if (is_array($uids)) {
+ $result = array_unique(array_merge($result, $uids));
+ }
+
+ break;
+ }
+ }
}
if (!$found) {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR);
}
- // consider Tag object modifications if needed
- if ($this->tag_categories) {
- $this->searchEntriesByTagChanges($filter, $folders, $result_type, $result);
- }
-
return $result;
}
/**
- * Checks if any Tags have been modified in specified time
- * and returns UIDs (or count) of objects assigned to them
+ * Detect changes of relation (tag) objects data and assigned objects
+ * Returns relation member identifiers
*/
- protected function searchEntriesByTagChanges($filter, $folders, $result_type, &$result)
+ protected function getChangesByRelations($folderid, $filter)
{
- $tag_filter = array();
- $obj_filter = array();
+ if (!$this->tag_categories) {
+ return;
+ }
// get period filter, create new objects filter
foreach ($filter as $f) {
- if ($f[0] == 'changed') {
- $tag_filter[] = $f;
- }
- else {
- $obj_filter[] = $f;
+ if ($f[0] == 'changed' && $f[1] == '>') {
+ $since = $f[2];
}
}
// this is not search for changes, do nothing
- if (empty($tag_filter)) {
+ if (empty($since)) {
return;
}
- // we're detecting changes here, let's check if any tags have been modified
- // we're covering here cases when tag name or tag assignment has been changed
+ // get relations state from the last sync
+ $last_state = (array) $this->backend->relations_state_get($this->device->id, $folderid, $since);
+ // get current relations state
$config = kolab_storage_config::get_instance();
- $members = array();
$default = true;
$filter = array(
array('type', '=', 'relation'),
array('category', '=', 'tag')
);
- $filter = array_merge($filter, $tag_filter);
- $tags = $config->get_objects($filter, $default, 100);
+ $relations = $config->get_objects($filter, $default, 100);
- // get UIDs of tag members
- foreach ($tags as $tag) {
- foreach ((array)$tag['members'] as $url) {
- if (strpos($url, 'urn:uuid:') === 0) {
- $members[] = substr($url, 9);
- }
+ $result = array();
+ $changed = false;
+
+ // compare states, get members of changed relations
+ foreach ($relations as $idx => $relation) {
+ $rel_id = $relation['uid'];
+
+ if ($relation['changed']) {
+ $relation['changed']->setTimezone(new DateTimeZone('UTC'));
}
- }
- $members = array_unique($members);
+ // last state unknown...
+ if (empty($last_state[$rel_id])) {
+ // ...get all members
+ if (!empty($relation['members'])) {
+ $changed = true;
+ $result = array_merge($result, $relation['members']);
+ }
+ }
+ // last state known, changed tag name...
+ else if ($last_state[$rel_id]['name'] != $relation['name']) {
+ // ...get all (old and new) members
+ $members_old = explode("\n", $last_state[$rel_id]['members']);
+ $changed = true;
+ $members = array_unique(array_merge($relation['members'], $members_old));
+ $result = array_merge($result, $members);
+ }
+ // last state known, any other change change...
+ else if ($last_state[$rel_id]['changed'] < $relation['changed']->format('U')) {
+ // ...find new and removed members
+ $members_old = explode("\n", $last_state[$rel_id]['members']);
+ $new = array_diff($relation['members'], $members_old);
+ $removed = array_diff($members_old, $relation['members']);
+
+ if (!empty($new) || !empty($removed)) {
+ $changed = true;
+ $result = array_merge($result, $new, $removed);
+ }
+ }
- // if we have UIDs in result already we can remove them here
- // FIXME: if the result is an int we can end up
- // with wrong result (some objects might be counted twice)
- if ($result_type == self::RESULT_UID) {
- $members = array_diff($members, $result);
+ unset($last_state[$rel_id]);
}
- if (empty($members)) {
- return;
+ // get members of deleted relations
+ if (!empty($last_state)) {
+ $changed = true;
+ foreach ($last_state as $relation) {
+ $members = explode("\n", $relation['members']);
+ $result = array_merge($result, $members);
+ }
}
- // search objects mathing current filter,
- // tags/relations may contain members of many types, we need to
- // search them by UID in all requested folders to get
- // only these with requested type (and that really exist)
- $obj_filter[] = array('uid', '=', $members);
-
- foreach ($folders as $folderid) {
- $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
-
- if ($foldername === null || !($folder = $this->getFolderObject($foldername))) {
- continue;
+ // save current state
+ if ($changed) {
+ $data = array();
+ foreach ($relations as $relation) {
+ $data[$relation['uid']] = array(
+ 'name' => $relation['name'],
+ 'changed' => $relation['changed']->format('U'),
+ 'members' => implode("\n", $relation['members']),
+ );
}
- switch ($result_type) {
- case self::RESULT_COUNT:
- $count = $folder->count($obj_filter);
- $result += (int) $count;
- break;
+ $now = new DateTime('now', new DateTimeZone('UTC'));
- case self::RESULT_UID:
- $uids = $folder->get_uids($obj_filter);
+ $this->backend->relations_state_set($this->device->id, $folderid, $now, $data);
+ }
- if (is_array($uids)) {
- $result = array_merge($result, $uids);
+ // in mail mode return only message URIs
+ if ($this->modelName == 'mail') {
+ // lambda function to skip email members
+ $filter_func = function($value) {
+ return strpos($value, 'imap://') === 0;
+ };
+
+ $result = array_filter(array_unique($result), $filter_func);
+ }
+ // otherwise return only object UIDs
+ else {
+ // lambda function to skip email members
+ $filter_func = function($value) {
+ return strpos($value, 'urn:uuid:') === 0;
+ };
+
+ // lambda function to parse member URI
+ $member_func = function($value) {
+ if (strpos($value, 'urn:uuid:') === 0) {
+ $value = substr($value, 9);
}
- break;
- }
+ return $value;
+ };
+
+ $result = array_map($member_func, array_filter(array_unique($result), $filter_func));
}
+
+ return $result;
}
/**