Page MenuHomePhorge

D4686.1774863840.diff
No OneTemporary

Authored By
Unknown
Size
51 KB
Referenced Files
None
Subscribers
None

D4686.1774863840.diff

diff --git a/docs/SQL/mysql.initial.sql b/docs/SQL/mysql.initial.sql
--- a/docs/SQL/mysql.initial.sql
+++ b/docs/SQL/mysql.initial.sql
@@ -107,12 +107,22 @@
CONSTRAINT `syncroton_relations_state::device_id--syncroton_device::id` FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+CREATE TABLE IF NOT EXISTS `syncroton_subscriptions` (
+ `device_id` varchar(40) NOT NULL,
+ `type` varchar(16) NOT NULL,
+ `data` longblob NOT NULL,
+ PRIMARY KEY (`device_id`, `type`),
+ KEY `syncroton_subscriptions::device_id` (`device_id`),
+ CONSTRAINT `syncroton_subscriptions::device_id--syncroton_device::id`
+ FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
-- Roundcube core table should exist if we're using the same database
CREATE TABLE IF NOT EXISTS `system` (
- `name` varchar(64) NOT NULL,
- `value` mediumtext,
- PRIMARY KEY(`name`)
+ `name` varchar(64) NOT NULL,
+ `value` mediumtext,
+ PRIMARY KEY (`name`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2024031100');
+INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2024040800');
diff --git a/docs/SQL/mysql/2024040800.sql b/docs/SQL/mysql/2024040800.sql
new file mode 100644
--- /dev/null
+++ b/docs/SQL/mysql/2024040800.sql
@@ -0,0 +1,9 @@
+CREATE TABLE IF NOT EXISTS `syncroton_subscriptions` (
+ `device_id` varchar(40) NOT NULL,
+ `type` varchar(16) NOT NULL,
+ `data` longblob NOT NULL,
+ PRIMARY KEY (`device_id`, `type`),
+ KEY `syncroton_subscriptions::device_id` (`device_id`),
+ CONSTRAINT `syncroton_subscriptions::device_id--syncroton_device::id`
+ FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
diff --git a/lib/kolab_sync_backend_device.php b/lib/kolab_sync_backend_device.php
--- a/lib/kolab_sync_backend_device.php
+++ b/lib/kolab_sync_backend_device.php
@@ -31,23 +31,6 @@
protected $table_name = 'syncroton_device';
protected $interface_name = 'Syncroton_Model_IDevice';
- /**
- * Kolab Sync storage backend
- *
- * @var kolab_sync_storage
- */
- protected $backend;
-
-
- /**
- * Constructor
- */
- public function __construct()
- {
- parent::__construct();
- $this->backend = kolab_sync::storage();
- }
-
/**
* Create (register) a new device
*
@@ -59,33 +42,23 @@
{
$device = parent::create($device);
- // Create device entry in kolab backend
- $created = $this->backend->device_create([
- 'ID' => $device->id,
- 'TYPE' => $device->devicetype,
- 'ALIAS' => $device->friendlyname,
- ], $device->deviceid);
-
- if (!$created) {
- throw new Syncroton_Exception_NotFound('Device creation failed');
+ $sync = kolab_sync::get_instance();
+
+ // Some devices create dummy devices with name "validate" (#1109)
+ // This device entry is used in two initial requests, but later
+ // the device registers a real name. We can remove this dummy entry
+ // on new device creation
+ if ($device->deviceid != 'validate') {
+ $this->db->query(
+ 'DELETE FROM `' . $this->table_name . '` WHERE `deviceid` = ? AND `owner_id` = ?',
+ ['validate', $sync->user->ID]
+ );
}
- return $device;
- }
-
- /**
- * Delete a device
- *
- * @param string|Syncroton_Model_IDevice $device Device object
- *
- * @return bool True on success, False on failure
- */
- public function delete($device)
- {
- // Update IMAP annotation
- $this->backend->device_delete($device->deviceid);
+ // Auto-subscribe a default set of folders
+ $sync->storage()->device_init($device->deviceid);
- return parent::delete($device);
+ return $device;
}
/**
@@ -109,18 +82,7 @@
throw new Syncroton_Exception_NotFound('Device not found');
}
- $device = $this->get_object($device);
-
- // Make sure device exists (could be deleted by the user)
- $dev = $this->backend->device_get($deviceid);
- if (empty($dev)) {
- // Remove the device (and related cached data) from database
- $this->delete($device);
-
- throw new Syncroton_Exception_NotFound('Device not found');
- }
-
- return $device;
+ return $this->get_object($device);
}
/**
diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php
--- a/lib/kolab_sync_data.php
+++ b/lib/kolab_sync_data.php
@@ -334,7 +334,13 @@
*/
public function updateFolder(Syncroton_Model_IFolder $folder)
{
- $result = $this->backend->folder_rename($folder->serverId, $this->device->deviceid, $folder->displayName, $folder->parentId);
+ $result = $this->backend->folder_rename(
+ $folder->serverId,
+ $this->device->deviceid,
+ $this->modelName,
+ $folder->displayName,
+ $folder->parentId
+ );
if ($result) {
return $folder;
@@ -353,7 +359,7 @@
}
// @TODO: throw exception
- return $this->backend->folder_delete($folder, $this->device->deviceid);
+ return $this->backend->folder_delete($folder, $this->device->deviceid, $this->modelName);
}
/**
@@ -371,7 +377,7 @@
// TODO: Respond with MailboxQuotaExceeded status. Where exactly?
foreach ($this->extractFolders($folderid) as $folderid) {
- if (!$this->backend->folder_empty($folderid, $this->device->deviceid, !empty($options['deleteSubFolders']))) {
+ if (!$this->backend->folder_empty($folderid, $this->device->deviceid, $this->modelName, !empty($options['deleteSubFolders']))) {
throw new Syncroton_Exception_Status_ItemOperations(Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR);
}
}
@@ -587,10 +593,16 @@
return $this->searchEntries($folderId, $filter, self::RESULT_COUNT, $syncState->extraData);
}
-
+ /**
+ * Get additional metadata for a specified folder
+ *
+ * @param Syncroton_Model_IFolder $folder Folder object
+ *
+ * @return string|null JSON-encoded string
+ */
public function getExtraData(Syncroton_Model_IFolder $folder)
{
- return $this->backend->getExtraData($folder->serverId, $this->device->deviceid);
+ return $this->backend->getExtraData($folder->serverId, $this->device->deviceid, $this->modelName);
}
/**
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -972,7 +972,7 @@
// @TODO: caching with Options->RebuildResults support
foreach ($folders as $folderid) {
- $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
+ $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid, $this->modelName);
if ($foldername === null) {
continue;
diff --git a/lib/kolab_sync_storage.php b/lib/kolab_sync_storage.php
--- a/lib/kolab_sync_storage.php
+++ b/lib/kolab_sync_storage.php
@@ -51,12 +51,11 @@
public $syncTimeStamp;
protected $storage;
- protected $folder_meta;
protected $folder_uids;
protected $folders = [];
- protected $root_meta;
protected $relations = [];
protected $relationSupport = true;
+ protected $subscriptions;
protected $tag_rts = [];
private $modseq = [];
@@ -116,6 +115,8 @@
// Disable paging
$this->storage->set_pagesize(999999);
+
+ $this->subscriptions = new kolab_subscriptions();
}
/**
@@ -126,29 +127,6 @@
$this->folders = [];
}
- /**
- * List known devices
- *
- * @return array Device list as hash array
- */
- public function devices_list()
- {
- if ($this->root_meta === null) {
- // @TODO: consider server annotation instead of INBOX
- if ($meta = $this->storage->get_metadata(self::ROOT_MAILBOX, self::ASYNC_KEY)) {
- $this->root_meta = $this->unserialize_metadata($meta[self::ROOT_MAILBOX][self::ASYNC_KEY]);
- } else {
- $this->root_meta = [];
- }
- }
-
- if (!empty($this->root_meta['DEVICE']) && is_array($this->root_meta['DEVICE'])) {
- return $this->root_meta['DEVICE'];
- }
-
- return [];
- }
-
/**
* Get list of folders available for sync
*
@@ -160,33 +138,15 @@
*/
public function folders_list($deviceid, $type, $flat_mode = false)
{
- // get all folders of specified type
- $folders = kolab_storage::list_folders('', '*', $type, false, $typedata);
-
- // get folders activesync config
- $folderdata = $this->folder_meta();
-
- if (!is_array($folders) || !is_array($folderdata)) {
- return false;
- }
+ $typedata = kolab_storage::folders_typedata();
$folders_list = [];
// check if folders are "subscribed" for activesync
- foreach ($folderdata as $folder => $meta) {
- if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
- || empty($meta['FOLDER'][$deviceid]['S'])
- ) {
- continue;
- }
-
+ foreach ($this->subscriptions->list_subscriptions($deviceid, $type) as $folder => $sub) {
// force numeric folder name to be a string (T1283)
$folder = (string) $folder;
- if (!empty($type) && !in_array($folder, $folders)) {
- continue;
- }
-
// Activesync folder identifier (serverId)
$folder_type = !empty($typedata[$folder]) ? $typedata[$folder] : 'mail';
$folder_id = $this->folder_id($folder, $folder_type);
@@ -256,35 +216,6 @@
return $folders;
}
- /**
- * Getter for folder metadata
- *
- * @return array|bool Hash array with meta data for each folder, False on backend failure
- */
- protected function folder_meta()
- {
- if (!isset($this->folder_meta)) {
- // get folders activesync config
- $folderdata = $this->storage->get_metadata("*", self::ASYNC_KEY);
-
- if (!is_array($folderdata)) {
- return $this->folder_meta = false;
- }
-
- $this->folder_meta = [];
-
- foreach ($folderdata as $folder => $meta) {
- if (isset($meta[self::ASYNC_KEY])) {
- if ($metadata = $this->unserialize_metadata($meta[self::ASYNC_KEY])) {
- $this->folder_meta[$folder] = $metadata;
- }
- }
- }
- }
-
- return $this->folder_meta;
- }
-
/**
* Creates folder and subscribes to the device
*
@@ -299,9 +230,10 @@
{
$parent = null;
$name = rcube_charset::convert($name, kolab_sync::CHARSET, 'UTF7-IMAP');
+ $type = self::type_activesync2kolab($type);
if ($parentid) {
- $parent = $this->folder_id2name($parentid, $deviceid);
+ $parent = $this->folder_id2name($parentid, $deviceid, $type);
if ($parent === null) {
throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::PARENT_NOT_FOUND);
@@ -317,12 +249,10 @@
throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::FOLDER_EXISTS);
}
- $type = self::type_activesync2kolab($type);
$created = kolab_storage::folder_create($name, $type, true);
if ($created) {
- // Set ActiveSync subscription flag
- $this->folder_set($name, $deviceid, 1);
+ $this->subscriptions->folder_subscribe($deviceid, $name, 1, $type);
return $this->folder_id($name, $type);
}
@@ -341,17 +271,18 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
* @param string $new_name New folder name (UTF8)
* @param ?string $parentid Folder parent identifier
*
* @return bool True on success, False on failure
*/
- public function folder_rename($folderid, $deviceid, $new_name, $parentid)
+ public function folder_rename($folderid, $deviceid, $type, $new_name, $parentid)
{
- $old_name = $this->folder_id2name($folderid, $deviceid);
+ $old_name = $this->folder_id2name($folderid, $deviceid, $type);
if ($parentid) {
- $parent = $this->folder_id2name($parentid, $deviceid);
+ $parent = $this->folder_id2name($parentid, $deviceid, $type);
}
$name = rcube_charset::convert($new_name, kolab_sync::CHARSET, 'UTF7-IMAP');
@@ -366,18 +297,22 @@
return true;
}
- $this->folder_meta = null;
-
// TODO: folder type change?
- $type = kolab_storage::folder_type($old_name);
-
// don't use kolab_storage for moving mail folders
- if (preg_match('/^mail/', $type)) {
- return $this->storage->rename_folder($old_name, $name);
+ if ($type == self::MODEL_EMAIL) {
+ $result = $this->storage->rename_folder($old_name, $name);
} else {
- return kolab_storage::folder_rename($old_name, $name);
+ $result = kolab_storage::folder_rename($old_name, $name);
}
+
+ if ($result) {
+ // Set ActiveSync subscription flag
+ // TODO: Use old subscription flag value
+ $this->subscriptions->folder_subscribe($deviceid, $name, 1, $type);
+ }
+
+ return $result;
}
/**
@@ -385,18 +320,16 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
*
* @return bool True on success, False otherwise
*/
- public function folder_delete($folderid, $deviceid)
+ public function folder_delete($folderid, $deviceid, $type)
{
- $name = $this->folder_id2name($folderid, $deviceid);
- $type = kolab_storage::folder_type($name);
-
- unset($this->folder_meta[$name]);
+ $name = $this->folder_id2name($folderid, $deviceid, $type);
// don't use kolab_storage for deleting mail folders
- if (preg_match('/^mail/', $type)) {
+ if ($type == self::MODEL_EMAIL) {
return $this->storage->delete_folder($name);
}
@@ -408,30 +341,27 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
* @param bool $recursive Apply to the folder and its subfolders
*
* @return bool True on success, False otherwise
*/
- public function folder_empty($folderid, $deviceid, $recursive = false)
+ public function folder_empty($folderid, $deviceid, $type, $recursive = false)
{
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
// Remove all entries
if (!$this->storage->clear_folder($foldername)) {
return false;
}
- // Remove subfolders
+ // Empty subfolders
if ($recursive) {
$delim = $this->storage->get_hierarchy_delimiter();
- $folderdata = $this->folder_meta();
+ $folders = $this->subscriptions->list_subscriptions($deviceid, $type);
- if (!is_array($folderdata)) {
- return false;
- }
-
- foreach ($folderdata as $subfolder => $meta) {
- if (!empty($meta['FOLDER'][$deviceid]['S']) && strpos((string) $subfolder, $foldername . $delim)) {
+ foreach (array_keys($folders) as $subfolder) {
+ if (strpos((string) $subfolder, $foldername . $delim)) {
if (!$this->storage->clear_folder((string) $subfolder)) {
return false;
}
@@ -442,233 +372,6 @@
return true;
}
- /**
- * Sets ActiveSync subscription flag on a folder
- *
- * @param string $name Folder name (UTF7-IMAP)
- * @param string $deviceid Device identifier
- * @param int $flag Flag value (0|1|2)
- *
- * @return bool True on success, False on failure
- */
- protected function folder_set($name, $deviceid, $flag)
- {
- if (empty($deviceid)) {
- return false;
- }
-
- // get folders activesync config
- $metadata = $this->folder_meta();
-
- if (!is_array($metadata)) {
- return false;
- }
-
- $metadata = $metadata[$name] ?? [];
-
- if ($flag) {
- if (empty($metadata)) {
- $metadata = [];
- }
-
- if (empty($metadata['FOLDER'])) {
- $metadata['FOLDER'] = [];
- }
-
- if (empty($metadata['FOLDER'][$deviceid])) {
- $metadata['FOLDER'][$deviceid] = [];
- }
-
- // Z-Push uses:
- // 1 - synchronize, no alarms
- // 2 - synchronize with alarms
- $metadata['FOLDER'][$deviceid]['S'] = $flag;
- } else {
- unset($metadata['FOLDER'][$deviceid]['S']);
-
- if (empty($metadata['FOLDER'][$deviceid])) {
- unset($metadata['FOLDER'][$deviceid]);
- }
-
- if (empty($metadata['FOLDER'])) {
- unset($metadata['FOLDER']);
- }
-
- if (empty($metadata)) {
- $metadata = null;
- }
- }
-
- // Return if nothing's been changed
- if (!self::data_array_diff($this->folder_meta[$name] ?? null, $metadata)) {
- return true;
- }
-
- $this->folder_meta[$name] = $metadata;
-
- return $this->storage->set_metadata($name, [self::ASYNC_KEY => $this->serialize_metadata($metadata)]);
- }
-
- /**
- * Returns device metadata
- *
- * @param string $id Device ID
- *
- * @return array|null Device metadata
- */
- public function device_get($id)
- {
- $devices_list = $this->devices_list();
- return $devices_list[$id] ?? null;
- }
-
- /**
- * Registers new device on server
- *
- * @param array $device Device data
- * @param string $id Device ID
- *
- * @return bool True on success, False on failure
- */
- public function device_create($device, $id)
- {
- // Fill local cache
- $this->devices_list();
-
- // Some devices create dummy devices with name "validate" (#1109)
- // This device entry is used in two initial requests, but later
- // the device registers a real name. We can remove this dummy entry
- // on new device creation
- $this->device_delete('validate');
-
- // Old Kolab_ZPush device parameters
- // MODE: -1 | 0 | 1 (not set | flatmode | foldermode)
- // TYPE: device type string
- // ALIAS: user-friendly device name
-
- // Syncroton (kolab_sync_backend_device) uses
- // ID: internal identifier in syncroton database
- // TYPE: device type string
- // ALIAS: user-friendly device name
-
- $metadata = $this->root_meta;
- $metadata['DEVICE'][$id] = $device;
- $metadata = [self::ASYNC_KEY => $this->serialize_metadata($metadata)];
-
- $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
-
- if ($result) {
- // Update local cache
- $this->root_meta['DEVICE'][$id] = $device;
-
- // subscribe default set of folders
- $this->device_init_subscriptions($id);
- }
-
- return $result;
- }
-
- /**
- * Device update.
- *
- * @param array $device Device data
- * @param string $id Device ID
- *
- * @return bool True on success, False on failure
- */
- public function device_update($device, $id)
- {
- $devices_list = $this->devices_list();
- $old_device = $devices_list[$id];
-
- if (!$old_device) {
- return false;
- }
-
- // Do nothing if nothing is changed
- if (!self::data_array_diff($old_device, $device)) {
- return true;
- }
-
- $device = array_merge($old_device, $device);
-
- $metadata = $this->root_meta;
- $metadata['DEVICE'][$id] = $device;
- $metadata = [self::ASYNC_KEY => $this->serialize_metadata($metadata)];
-
- $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
-
- if ($result) {
- // Update local cache
- $this->root_meta['DEVICE'][$id] = $device;
- }
-
- return $result;
- }
-
- /**
- * Device delete.
- *
- * @param string $id Device ID
- *
- * @return bool True on success, False on failure
- */
- public function device_delete($id)
- {
- $device = $this->device_get($id);
-
- if (!$device) {
- return false;
- }
-
- unset($this->root_meta['DEVICE'][$id], $this->root_meta['FOLDER'][$id]);
-
- if (empty($this->root_meta['DEVICE'])) {
- unset($this->root_meta['DEVICE']);
- }
- if (empty($this->root_meta['FOLDER'])) {
- unset($this->root_meta['FOLDER']);
- }
-
- $metadata = $this->serialize_metadata($this->root_meta);
- $metadata = [self::ASYNC_KEY => $metadata];
-
- // update meta data
- $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
-
- if ($result) {
- // remove device annotation for every folder
- foreach ($this->folder_meta() as $folder => $meta) {
- // skip root folder (already handled above)
- if ($folder == self::ROOT_MAILBOX) {
- continue;
- }
-
- if (!empty($meta['FOLDER']) && isset($meta['FOLDER'][$id])) {
- unset($meta['FOLDER'][$id]);
-
- if (empty($meta['FOLDER'])) {
- unset($this->folder_meta[$folder]['FOLDER']);
- unset($meta['FOLDER']);
- }
- if (empty($meta)) {
- unset($this->folder_meta[$folder]);
- $meta = null;
- }
-
- $metadata = [self::ASYNC_KEY => $this->serialize_metadata($meta)];
- $res = $this->storage->set_metadata($folder, $metadata);
-
- if ($res && $meta) {
- $this->folder_meta[$folder] = $meta;
- }
- }
- }
- }
-
- return $result;
- }
-
/**
* Creates an item in a folder.
*
@@ -683,7 +386,7 @@
public function createItem($folderid, $deviceid, $type, $data, $params = [])
{
if ($type == self::MODEL_EMAIL) {
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
$uid = $this->storage->save_message($foldername, $data, '', false, $params['flags'] ?? []);
@@ -733,7 +436,7 @@
public function deleteItem($folderid, $deviceid, $type, $uid, $moveToTrash = false)
{
if ($type == self::MODEL_EMAIL) {
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
$trash = kolab_sync::get_instance()->config->get('trash_mbox');
// move message to the Trash folder
@@ -786,7 +489,7 @@
public function updateItem($folderid, $deviceid, $type, $uid, $data, $params = [])
{
if ($type == self::MODEL_EMAIL) {
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
// Note: We do not support a message body update, as it's not needed
@@ -905,7 +608,7 @@
return $this->folders[$unique_key];
}
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
return $this->folders[$unique_key] = kolab_storage::get_folder($foldername, $type);
}
@@ -921,17 +624,12 @@
*/
public function getFolderConfig($folderid, $deviceid, $type)
{
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
- $metadata = $this->folder_meta();
- $config = [];
-
- if (!empty($metadata[$foldername]['FOLDER'][$deviceid])) {
- $config = $metadata[$foldername]['FOLDER'][$deviceid];
- }
+ $subs = $this->subscriptions->folder_subscriptions($foldername, $type);
return [
- 'ALARMS' => ($config['S'] ?? 0) == 2,
+ 'ALARMS' => ($subs[$deviceid] ?? 0) == 2,
];
}
@@ -948,7 +646,7 @@
public function getItem($folderid, $deviceid, $type, $uid)
{
if ($type == self::MODEL_EMAIL) {
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
$message = new rcube_message($uid, $foldername);
if (!empty($message->headers)) {
@@ -1025,8 +723,8 @@
public function moveItem($srcFolderId, $deviceid, $type, $uid, $dstFolderId)
{
if ($type === self::MODEL_EMAIL) {
- $src_name = $this->folder_id2name($srcFolderId, $deviceid);
- $dst_name = $this->folder_id2name($dstFolderId, $deviceid);
+ $src_name = $this->folder_id2name($srcFolderId, $deviceid, $type);
+ $dst_name = $this->folder_id2name($dstFolderId, $deviceid, $type);
if ($dst_name === null) {
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION);
@@ -1170,7 +868,7 @@
$result = $result_type == kolab_sync_data::RESULT_COUNT ? 0 : [];
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
if ($foldername === null) {
return $result;
@@ -1271,10 +969,11 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
*
* @return string|null Extra data (JSON-encoded)
*/
- public function getExtraData($folderid, $deviceid)
+ public function getExtraData($folderid, $deviceid, $type)
{
//We explicitly return a cached value that was used during the search.
//Otherwise we'd risk storing a higher modseq value and missing an update.
@@ -1283,7 +982,7 @@
}
//If we didn't fetch modseq in the first place we have to fetch it now.
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
if ($foldername !== null) {
$folder_data = $this->storage->folder_data($foldername);
if (!empty($folder_data['HIGHESTMODSEQ'])) {
@@ -1598,12 +1297,19 @@
}
/**
- * Subscribe default set of folders on device registration
+ * Subscribes to a default set of folder on a new device registration
+ *
+ * @param string $deviceid Device ID
*/
- protected function device_init_subscriptions($deviceid)
+ public function device_init($deviceid)
{
- // INBOX always exists
- $this->folder_set('INBOX', $deviceid, 1);
+ $subscribed = [
+ 'mail' => ['INBOX' => 1], // INBOX always exists
+ 'event' => [],
+ 'contact' => [],
+ 'task' => [],
+ 'note' => [],
+ ];
$supported_types = [
'mail.drafts',
@@ -1640,99 +1346,76 @@
// only personal folders
if ($this->storage->folder_namespace($folder) == 'personal') {
$flag = preg_match('/^(event|task)/', $type) ? 2 : 1;
- $this->folder_set($folder, $deviceid, $flag);
+ [$type, ] = explode('.', $type);
+ $subscribed[$type][$folder] = $flag;
$folders[] = $folder;
}
}
}
- // we're in default mode, exit
- if (!$mode) {
- return;
- }
-
- // below we support additionally all mail folders
- $supported_types[] = 'mail';
- $supported_types[] = 'mail.junkemail';
+ if ($mode) {
+ // below we support additionally all mail folders
+ $supported_types[] = 'mail';
+ $supported_types[] = 'mail.junkemail';
- // get configured special folders
- $special_folders = [];
- $map = [
- 'drafts' => 'mail.drafts',
- 'junk' => 'mail.junkemail',
- 'sent' => 'mail.sentitems',
- 'trash' => 'mail.wastebasket',
- ];
+ // get configured special folders
+ $special_folders = [];
+ $map = [
+ 'drafts' => 'mail.drafts',
+ 'junk' => 'mail.junkemail',
+ 'sent' => 'mail.sentitems',
+ 'trash' => 'mail.wastebasket',
+ ];
- foreach ($map as $folder => $type) {
- if ($folder = $config->get($folder . '_mbox')) {
- $special_folders[$folder] = $type;
+ foreach ($map as $folder => $type) {
+ if ($folder = $config->get($folder . '_mbox')) {
+ $special_folders[$folder] = $type;
+ }
}
- }
- // get folders list(s)
- if (($mode & self::INIT_ALL_PERSONAL) || ($mode & self::INIT_ALL_OTHER) || ($mode & self::INIT_ALL_SHARED)) {
- $all_folders = $this->storage->list_folders();
- if (($mode & self::INIT_SUB_PERSONAL) || ($mode & self::INIT_SUB_OTHER) || ($mode & self::INIT_SUB_SHARED)) {
- $subscribed_folders = $this->storage->list_folders_subscribed();
+ // get folders list(s)
+ if (($mode & self::INIT_ALL_PERSONAL) || ($mode & self::INIT_ALL_OTHER) || ($mode & self::INIT_ALL_SHARED)) {
+ $all_folders = $this->storage->list_folders();
+ if (($mode & self::INIT_SUB_PERSONAL) || ($mode & self::INIT_SUB_OTHER) || ($mode & self::INIT_SUB_SHARED)) {
+ $subscribed_folders = $this->storage->list_folders_subscribed();
+ }
+ } else {
+ $all_folders = $this->storage->list_folders_subscribed();
}
- } else {
- $all_folders = $this->storage->list_folders_subscribed();
- }
- foreach ($all_folders as $folder) {
- // folder already subscribed
- if (in_array($folder, $folders)) {
- continue;
- }
+ foreach ($all_folders as $folder) {
+ // folder already subscribed
+ if (in_array($folder, $folders)) {
+ continue;
+ }
- $type = ($foldertypes[$folder] ?? null) ?: 'mail';
- if ($type == 'mail' && isset($special_folders[$folder])) {
- $type = $special_folders[$folder];
- }
+ $type = ($foldertypes[$folder] ?? null) ?: 'mail';
+ if ($type == 'mail' && isset($special_folders[$folder])) {
+ $type = $special_folders[$folder];
+ }
- if (!in_array($type, $supported_types)) {
- continue;
- }
+ if (!in_array($type, $supported_types)) {
+ continue;
+ }
- $ns = strtoupper($this->storage->folder_namespace($folder));
+ $ns = strtoupper($this->storage->folder_namespace($folder));
- // subscribe the folder according to configured mode
- // and folder namespace/subscription status
- if (($mode & constant("self::INIT_ALL_{$ns}"))
- || (($mode & constant("self::INIT_SUB_{$ns}"))
- && (!isset($subscribed_folders) || in_array($folder, $subscribed_folders)))
- ) {
- $flag = preg_match('/^(event|task)/', $type) ? 2 : 1;
- $this->folder_set($folder, $deviceid, $flag);
+ // subscribe the folder according to configured mode
+ // and folder namespace/subscription status
+ if (($mode & constant("self::INIT_ALL_{$ns}"))
+ || (($mode & constant("self::INIT_SUB_{$ns}"))
+ && (!isset($subscribed_folders) || in_array($folder, $subscribed_folders)))
+ ) {
+ $flag = preg_match('/^(event|task)/', $type) ? 2 : 1;
+ [$type, ] = explode('.', $type);
+ $subscribed[$type][$folder] = $flag;
+ }
}
}
- }
-
- /**
- * Helper method to decode saved IMAP metadata
- */
- protected function unserialize_metadata($str)
- {
- if (!empty($str)) {
- $data = json_decode($str, true);
- return $data;
- }
-
- return null;
- }
- /**
- * Helper method to encode IMAP metadata for saving
- */
- protected function serialize_metadata($data)
- {
- if (!empty($data) && is_array($data)) {
- $data = json_encode($data);
- return $data;
+ foreach ($subscribed as $type => $list) {
+ $this->subscriptions->set_subscriptions($deviceid, $type, $list);
}
-
- return null;
}
/**
@@ -1817,19 +1500,6 @@
return $this->folder_uids[$name];
}
- /*
- @TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves.
- There's one inconvenience of this solution: folder name/type change
- would be handled in ActiveSync as delete + create.
-
- // get folders unique identifier
- $folderdata = $this->storage->get_metadata($name, self::UID_KEY);
-
- if ($folderdata && !empty($folderdata[$name])) {
- $uid = $folderdata[$name][self::UID_KEY];
- return $this->folder_uids[$name] = $uid;
- }
- */
if (strcasecmp($name, 'INBOX') === 0) {
// INBOX is always inbox, prevent from issues related with a change of
// folder type annotation (it can be initially unset).
@@ -1854,57 +1524,32 @@
/**
* Returns IMAP folder name
*
- * @param string $id Folder identifier
- * @param string $deviceid Device dentifier
+ * @param string $id Folder identifier
+ * @param string $deviceid Device dentifier
+ * @param string $type Folder type
*
* @return string|null Folder name (UTF7-IMAP)
*/
- public function folder_id2name($id, $deviceid)
+ public function folder_id2name($id, $deviceid, $type)
{
+ // TODO: This method should become protected
+
// check in cache first
if (!empty($this->folder_uids)) {
if (($name = array_search($id, $this->folder_uids)) !== false) {
return $name;
}
}
- /*
- @TODO: see folder_id()
-
- // get folders unique identifier
- $folderdata = $this->storage->get_metadata('*', self::UID_KEY);
-
- foreach ((array)$folderdata as $folder => $data) {
- if (!empty($data[self::UID_KEY])) {
- $uid = $data[self::UID_KEY];
- $this->folder_uids[$folder] = $uid;
- if ($uid == $id) {
- $name = $folder;
- }
- }
- }
- */
- // get all folders of specified type
- $folderdata = $this->folder_meta();
-
- if (!is_array($folderdata) || empty($id)) {
- return null;
- }
$name = null;
- // check if folders are "subscribed" for activesync
- foreach ($folderdata as $folder => $meta) {
- if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
- || empty($meta['FOLDER'][$deviceid]['S'])
- ) {
- continue;
- }
-
- if ($uid = $this->folder_id($folder)) {
- $this->folder_uids[$folder] = $uid;
- }
+ if (strpos($type, '.')) {
+ [$type, ] = explode('.', $type);
+ }
- if ($uid === $id) {
+ // Get the uids of all folders subscribed for activesync
+ foreach ($this->subscriptions->list_subscriptions($deviceid, $type) as $folder => $props) {
+ if ($this->folder_id($folder) === $id) {
$name = $folder;
}
}
@@ -1990,34 +1635,4 @@
{
return kolab_storage::$last_error;
}
-
- /**
- * Compares two arrays
- *
- * @param array $array1
- * @param array $array2
- *
- * @return bool True if arrays differs, False otherwise
- */
- protected static function data_array_diff($array1, $array2)
- {
- if (!is_array($array1) || !is_array($array2)) {
- return $array1 != $array2;
- }
-
- if (count($array1) != count($array2)) {
- return true;
- }
-
- foreach ($array1 as $key => $val) {
- if (!array_key_exists($key, $array2)) {
- return true;
- }
- if ($val !== $array2[$key]) {
- return true;
- }
- }
-
- return false;
- }
}
diff --git a/lib/kolab_sync_storage_kolab4.php b/lib/kolab_sync_storage_kolab4.php
--- a/lib/kolab_sync_storage_kolab4.php
+++ b/lib/kolab_sync_storage_kolab4.php
@@ -77,6 +77,9 @@
// Disable paging
$this->storage->set_pagesize(999999);
+
+ // Folders subscriptions engine
+ $this->subscriptions = new kolab_subscriptions($url);
}
/**
@@ -94,12 +97,6 @@
// get mail folders subscribed for sync
if ($type === self::MODEL_EMAIL) {
- $folderdata = $this->folder_meta();
-
- if (!is_array($folderdata)) {
- return false;
- }
-
$special_folders = $this->storage->get_special_folders(true);
$type_map = [
'drafts' => 3,
@@ -108,13 +105,7 @@
];
// Get the folders "subscribed" for activesync
- foreach ($folderdata as $folder => $meta) {
- if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
- || empty($meta['FOLDER'][$deviceid]['S'])
- ) {
- continue;
- }
-
+ foreach ($this->subscriptions->list_subscriptions($deviceid, self::MODEL_EMAIL) as $folder => $meta) {
// Force numeric folder name to be a string (T1283)
$folder = (string) $folder;
@@ -138,10 +129,10 @@
}
}
- // TODO: For now all DAV folders are subscribed
-
if (empty($list)) {
- foreach ($this->davStorage->get_folders($type) as $folder) {
+ foreach ($this->subscriptions->list_subscriptions($deviceid, $type) as $folder) {
+ /** @var kolab_storage_dav_folder $folder */
+ $folder = $folder[2];
$folder_data = $this->folder_data($folder, $type);
$list[$folder_data['serverId']] = $folder_data;
@@ -179,7 +170,7 @@
$name = rcube_charset::convert($name, kolab_sync::CHARSET, 'UTF7-IMAP');
if ($parentid) {
- $parent = $this->folder_id2name($parentid, $deviceid);
+ $parent = $this->folder_id2name($parentid, $deviceid, self::MODEL_EMAIL);
if ($parent === null) {
throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::PARENT_NOT_FOUND);
@@ -201,7 +192,7 @@
if ($created) {
// Set ActiveSync subscription flag
- $this->folder_set($name, $deviceid, 1);
+ $this->subscriptions->folder_subscribe($deviceid, $name, 1, self::MODEL_EMAIL);
return $this->folder_id($name, 'mail');
}
@@ -229,6 +220,10 @@
$props = ['name' => $name, 'type' => $type];
if ($id = $this->davStorage->folder_update($props)) {
+ // Set ActiveSync subscription flag
+ $this->subscriptions->folder_subscribe($deviceid, $this->davStorage->new_location, 1, $type);
+ $this->folders = [];
+
return "DAV:{$type}:{$id}";
}
@@ -243,12 +238,13 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
* @param string $new_name New folder name (UTF8)
* @param ?string $parentid Folder parent identifier
*
* @return bool True on success, False on failure
*/
- public function folder_rename($folderid, $deviceid, $new_name, $parentid)
+ public function folder_rename($folderid, $deviceid, $type, $new_name, $parentid)
{
// DAV folder
if (strpos($folderid, 'DAV:') === 0) {
@@ -265,10 +261,10 @@
}
// Mail folder
- $old_name = $this->folder_id2name($folderid, $deviceid);
+ $old_name = $this->folder_id2name($folderid, $deviceid, $type);
if ($parentid) {
- $parent = $this->folder_id2name($parentid, $deviceid);
+ $parent = $this->folder_id2name($parentid, $deviceid, $type);
}
$name = rcube_charset::convert($new_name, kolab_sync::CHARSET, 'UTF7-IMAP');
@@ -282,9 +278,14 @@
return true;
}
- $this->folder_meta = null;
+ $result = $this->storage->rename_folder($old_name, $name);
+
+ if ($result) {
+ // Set ActiveSync subscription flag
+ $this->subscriptions->folder_subscribe($deviceid, $name, 1, self::MODEL_EMAIL);
+ }
- return $this->storage->rename_folder($old_name, $name);
+ return $result;
}
/**
@@ -292,10 +293,11 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
*
* @return bool True on success, False otherwise
*/
- public function folder_delete($folderid, $deviceid)
+ public function folder_delete($folderid, $deviceid, $type)
{
// DAV folder
if (strpos($folderid, 'DAV:') === 0) {
@@ -305,9 +307,7 @@
}
// Mail folder
- $name = $this->folder_id2name($folderid, $deviceid);
-
- unset($this->folder_meta[$name]);
+ $name = $this->folder_id2name($folderid, $deviceid, $type);
return $this->storage->delete_folder($name);
}
@@ -317,11 +317,12 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
* @param bool $recursive Apply to the folder and its subfolders
*
* @return bool True on success, False otherwise
*/
- public function folder_empty($folderid, $deviceid, $recursive = false)
+ public function folder_empty($folderid, $deviceid, $type, $recursive = false)
{
// DAV folder
if (strpos($folderid, 'DAV:') === 0) {
@@ -337,7 +338,7 @@
}
// Mail folder
- return parent::folder_empty($folderid, $deviceid, $recursive);
+ return parent::folder_empty($folderid, $deviceid, $type, $recursive);
}
/**
@@ -388,20 +389,7 @@
if (isset($this->folder_uids[$name])) {
return $this->folder_uids[$name];
}
- /*
- @TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves.
- There's one inconvenience of this solution: folder name/type change
- would be handled in ActiveSync as delete + create.
- @TODO: Consider using MAILBOXID (RFC8474) that Cyrus v3 supports
- // get folders unique identifier
- $folderdata = $this->storage->get_metadata($name, self::UID_KEY);
-
- if ($folderdata && !empty($folderdata[$name])) {
- $uid = $folderdata[$name][self::UID_KEY];
- return $this->folder_uids[$name] = $uid;
- }
- */
if (strcasecmp($name, 'INBOX') === 0) {
// INBOX is always inbox, prevent from issues related with a change of
// folder type annotation (it can be initially unset).
@@ -418,50 +406,19 @@
/**
* Returns IMAP folder name
*
- * @param string $id Folder identifier
- * @param string $deviceid Device dentifier
+ * @param string $id Folder identifier
+ * @param string $deviceid Device dentifier
+ * @param string $type Folder type
*
* @return null|string Folder name (UTF7-IMAP)
*/
- public function folder_id2name($id, $deviceid)
+ public function folder_id2name($id, $deviceid, $type)
{
- // TODO: This method should become protected and be used for mail folders only
if (strpos($id, 'DAV:') === 0) {
throw new Exception("Unsupported folder_id2name() call on a DAV folder");
}
- // check in cache first
- if (!empty($this->folder_uids)) {
- if (($name = array_search($id, $this->folder_uids)) !== false) {
- return $name;
- }
- }
-
- // get all folders of specified type
- $folderdata = $this->folder_meta();
-
- if (!is_array($folderdata) || empty($id)) {
- return null;
- }
-
- // check if folders are "subscribed" for activesync
- foreach ($folderdata as $folder => $meta) {
- if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
- || empty($meta['FOLDER'][$deviceid]['S'])
- ) {
- continue;
- }
-
- if ($uid = $this->folder_id($folder, 'mail')) {
- $this->folder_uids[$folder] = $uid;
- }
-
- if ($uid === $id) {
- $name = $folder;
- }
- }
-
- return $name ?? null;
+ return parent::folder_id2name($id, $deviceid, $type);
}
/**
@@ -471,7 +428,7 @@
* @param string $deviceid Device identifier
* @param string $type Activesync model name (folder type)
*
- * @return ?kolab_storage_folder
+ * @return ?kolab_storage_dav_folder
*/
public function getFolder($folderid, $deviceid, $type)
{
@@ -501,10 +458,15 @@
*/
public function getFolderConfig($folderid, $deviceid, $type)
{
- // TODO: Get "alarms" from the DAV folder props, or implement
- // a storage for folder properties
+ $alarms = 0;
+
+ if ($folder = $this->getFolder($folderid, $deviceid, $type)) {
+ $subs = $this->subscriptions->folder_subscriptions($folder->href, $type);
+ $alarms = $subs[$deviceid] ?? 0;
+ }
+
return [
- 'ALARMS' => true,
+ 'ALARMS' => $alarms == 2,
];
}
@@ -518,9 +480,11 @@
}
/**
- * Subscribe default set of folders on device registration
+ * Subscribes to a default set of folder on a new device registration
+ *
+ * @param string $deviceid Device ID
*/
- protected function device_init_subscriptions($deviceid)
+ public function device_init($deviceid)
{
$config = rcube::get_instance()->config;
$mode = (int) $config->get('activesync_init_subscriptions');
@@ -544,6 +508,13 @@
$all_folders = $this->storage->list_folders_subscribed();
}
+ $subscribe = [
+ 'mail' => ['INBOX' => 1],
+ 'contact' => [],
+ 'event' => [],
+ 'task' => [],
+ ];
+
foreach ($all_folders as $folder) {
$ns = strtoupper($this->storage->folder_namespace($folder));
@@ -553,19 +524,28 @@
|| ($mode & constant("self::INIT_ALL_{$ns}"))
|| (($mode & constant("self::INIT_SUB_{$ns}")) && ($subscribed_folders === null || in_array($folder, $subscribed_folders)))
) {
- $this->folder_set($folder, $deviceid, 1);
+ $subscribe['mail'][$folder] = 1;
}
}
- // TODO: Subscribe personal DAV folders, for now we assume all are subscribed
- // TODO: Subscribe shared DAV folders
+ foreach ($subscribe as $type => $list) {
+ if ($type != 'mail') {
+ foreach ($this->subscriptions->list_folders($type) as $folder) {
+ // TODO: Subscribe personal DAV folders, for now we assume all are subscribed
+ $list[$folder[0]] = ($type == 'event' || $type == 'task') ? 2 : 1;
+ }
+ }
+
+ $this->subscriptions->set_subscriptions($deviceid, $type, $list);
+ }
}
- public function getExtraData($folderid, $deviceid)
+ public function getExtraData($folderid, $deviceid, $type)
{
if (strpos($folderid, 'DAV:') === 0) {
return null;
}
- return parent::getExtraData($folderid, $deviceid);
+
+ return parent::getExtraData($folderid, $deviceid, $type);
}
}
diff --git a/tests/SyncTestCase.php b/tests/SyncTestCase.php
--- a/tests/SyncTestCase.php
+++ b/tests/SyncTestCase.php
@@ -46,11 +46,8 @@
self::$deviceId = 'test' . time();
$db->query('DELETE FROM syncroton_device');
- $db->query('DELETE FROM syncroton_synckey');
- $db->query('DELETE FROM syncroton_folder');
$db->query('DELETE FROM syncroton_data');
$db->query('DELETE FROM syncroton_data_folder');
- $db->query('DELETE FROM syncroton_content');
self::$client = new \GuzzleHttp\Client([
'http_errors' => false,
@@ -75,18 +72,16 @@
{
if (self::$deviceId) {
$sync = \kolab_sync::get_instance();
-
+ /*
if (self::$authenticated || $sync->authenticate(self::$username, self::$password)) {
$sync->password = self::$password;
-
- $storage = $sync->storage();
- $storage->device_delete(self::$deviceId);
}
+ */
$db = $sync->get_dbh();
$db->query('DELETE FROM syncroton_device');
- $db->query('DELETE FROM syncroton_synckey');
- $db->query('DELETE FROM syncroton_folder');
+ $db->query('DELETE FROM syncroton_data');
+ $db->query('DELETE FROM syncroton_data_folder');
}
}

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 30, 9:44 AM (4 d, 9 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18769854
Default Alt Text
D4686.1774863840.diff (51 KB)

Event Timeline