2 commits - config/main.inc.php.dist docs/SQL docs/syncroton.sql lib/ext lib/kolab_sync_data_calendar.php lib/kolab_sync_data_gal.php lib/kolab_sync.php
Aleksander Machniak
machniak at kolabsys.com
Sat Jan 12 16:38:10 CET 2013
config/main.inc.php.dist | 8 +
docs/SQL/mysql/2013011200.sql | 11 +
docs/syncroton.sql | 13 +
lib/ext/Syncroton/Command/FolderCreate.php | 11 -
lib/ext/Syncroton/Command/FolderSync.php | 6
lib/ext/Syncroton/Command/GetItemEstimate.php | 4
lib/ext/Syncroton/Command/ICommand.php | 7
lib/ext/Syncroton/Command/ItemOperations.php | 29 +++
lib/ext/Syncroton/Command/MeetingResponse.php | 87 +++++++++++
lib/ext/Syncroton/Command/Options.php | 10 -
lib/ext/Syncroton/Command/Ping.php | 28 +--
lib/ext/Syncroton/Command/SendMail.php | 6
lib/ext/Syncroton/Command/Sync.php | 177 ++++++++++++++++++-----
lib/ext/Syncroton/Command/Wbxml.php | 35 ++++
lib/ext/Syncroton/Data/AData.php | 109 +++++++++-----
lib/ext/Syncroton/Data/Calendar.php | 37 +---
lib/ext/Syncroton/Data/Contacts.php | 137 +-----------------
lib/ext/Syncroton/Data/Email.php | 76 +---------
lib/ext/Syncroton/Data/IDataCalendar.php | 26 +++
lib/ext/Syncroton/Data/IDataEmail.php | 6
lib/ext/Syncroton/Data/Tasks.php | 27 ---
lib/ext/Syncroton/Model/AEntry.php | 4
lib/ext/Syncroton/Model/Email.php | 19 +-
lib/ext/Syncroton/Model/FileReference.php | 1
lib/ext/Syncroton/Model/Folder.php | 1
lib/ext/Syncroton/Model/IDevice.php | 1
lib/ext/Syncroton/Model/IFolder.php | 1
lib/ext/Syncroton/Model/MeetingResponse.php | 46 ++++++
lib/ext/Syncroton/Model/SyncCollection.php | 194 +++++++++++++++-----------
lib/ext/Syncroton/Registry.php | 34 ++++
lib/ext/Syncroton/Server.php | 131 +++++++++++------
lib/ext/Syncroton/Wbxml/Abstract.php | 6
lib/kolab_sync.php | 8 -
lib/kolab_sync_data_calendar.php | 15 +-
lib/kolab_sync_data_gal.php | 4
35 files changed, 825 insertions(+), 490 deletions(-)
New commits:
commit 0dffe2b42b0fe77a756ad96b3bccc4a4b2953d5e
Author: Aleksander Machniak <alec at alec.pl>
Date: Sat Jan 12 16:37:25 2013 +0100
Update Syncroton, added activesync_ping_timeout and activesync_quiet_time options
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index 77f04ac..56f5808 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -42,3 +42,11 @@ $rcmail_config['activesync_gal_fieldmap'] = null;
// List of Roundcube plugins
// WARNING: Not all plugins used in Roundcube can be listed here
$rcmail_config['activesync_plugins'] = array();
+
+// Defines for how many seconds we'll sleep between every
+// action for detecting changes in folders. Default: 60
+$rcmail_config['activesync_ping_timeout'] = 60;
+
+// We start detecting changes n seconds since the last sync of a folder
+// Default: 180
+$rcmail_config['activesync_quiet_time'] = 180;
diff --git a/docs/SQL/mysql/2013011200.sql b/docs/SQL/mysql/2013011200.sql
new file mode 100644
index 0000000..bcf8453
--- /dev/null
+++ b/docs/SQL/mysql/2013011200.sql
@@ -0,0 +1,11 @@
+ALTER TABLE `syncroton_device` ADD `lastsynccollection` longblob DEFAULT NULL;
+ALTER TABLE `syncroton_folder` ADD `supportedfields` longblob DEFAULT NULL;
+ALTER TABLE `syncroton_data` CHANGE `type` `class` varchar(40) NOT NULL;
+CREATE TABLE `syncroton_data_folder` (
+ `id` varchar(40) NOT NULL,
+ `type` int(11) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `owner_id` varchar(40) NOT NULL,
+ `parent_id` varchar(40) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB;
diff --git a/docs/syncroton.sql b/docs/syncroton.sql
index 5e140d9..b35a6b4 100644
--- a/docs/syncroton.sql
+++ b/docs/syncroton.sql
@@ -66,6 +66,7 @@ CREATE TABLE IF NOT EXISTS `syncroton_device` (
`pinglifetime` int(11) DEFAULT NULL,
`remotewipe` int(11) DEFAULT '0',
`pingfolder` longblob,
+ `lastsynccollection` longblob DEFAULT NULL,
`contactsfilter_id` varchar(40) DEFAULT NULL,
`calendarfilter_id` varchar(40) DEFAULT NULL,
`tasksfilter_id` varchar(40) DEFAULT NULL,
@@ -84,6 +85,7 @@ CREATE TABLE IF NOT EXISTS `syncroton_folder` (
`type` int(11) NOT NULL,
`creation_time` datetime NOT NULL,
`lastfiltertype` int(11) DEFAULT NULL,
+ `supportedfields` longblob DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `device_id--class--folderid` (`device_id`(40),`class`(40),`folderid`(40)),
KEY `folderstates::device_id--devices::id` (`device_id`),
@@ -118,8 +120,17 @@ CREATE TABLE IF NOT EXISTS `syncroton_content` (
CREATE TABLE IF NOT EXISTS `syncroton_data` (
`id` varchar(40) NOT NULL,
- `type` varchar(40) NOT NULL,
+ `class` varchar(40) NOT NULL,
`folder_id` varchar(40) NOT NULL,
`data` longblob,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
+
+CREATE TABLE IF NOT EXISTS `syncroton_data_folder` (
+ `id` varchar(40) NOT NULL,
+ `type` int(11) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `owner_id` varchar(40) NOT NULL,
+ `parent_id` varchar(40) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB;
diff --git a/lib/ext/Syncroton/Command/FolderCreate.php b/lib/ext/Syncroton/Command/FolderCreate.php
index d87cd9d..e7eda47 100644
--- a/lib/ext/Syncroton/Command/FolderCreate.php
+++ b/lib/ext/Syncroton/Command/FolderCreate.php
@@ -70,12 +70,13 @@ class Syncroton_Command_FolderCreate extends Syncroton_Command_Wbxml
break;
}
- $folder->deviceId = $this->_device;
- $folder->creationTime = $this->_syncTimeStamp;
-
- $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
- $this->_folder = $dataController->createFolder($folder);
+ $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
+
+ $this->_folder = $dataController->createFolder($folder);
+ $this->_folder->class = $folder->class;
+ $this->_folder->deviceId = $this->_device;
+ $this->_folder->creationTime = $this->_syncTimeStamp;
$this->_folderBackend->create($this->_folder);
}
diff --git a/lib/ext/Syncroton/Command/FolderSync.php b/lib/ext/Syncroton/Command/FolderSync.php
index 8c4917d..84e761e 100644
--- a/lib/ext/Syncroton/Command/FolderSync.php
+++ b/lib/ext/Syncroton/Command/FolderSync.php
@@ -115,6 +115,12 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
return $this->_outputDom;
}
+ // send headers from options command also when FolderSync SyncKey is 0
+ if ($this->_syncState->counter == 0) {
+ $optionsCommand = new Syncroton_Command_Options();
+ $this->_headers = array_merge($this->_headers, $optionsCommand->getHeaders());
+ }
+
$adds = array();
$deletes = array();
diff --git a/lib/ext/Syncroton/Command/GetItemEstimate.php b/lib/ext/Syncroton/Command/GetItemEstimate.php
index 79ed73d..5eb0f4c 100644
--- a/lib/ext/Syncroton/Command/GetItemEstimate.php
+++ b/lib/ext/Syncroton/Command/GetItemEstimate.php
@@ -100,7 +100,6 @@ class Syncroton_Command_GetItemEstimate extends Syncroton_Command_Wbxml
$response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_INVALID_COLLECTION));
$collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
- $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
$collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));
$collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 0));
@@ -114,14 +113,12 @@ class Syncroton_Command_GetItemEstimate extends Syncroton_Command_Wbxml
*
$response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_INVALID_SYNC_KEY));
$collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
- $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
$collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));
$collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 0));
*/
$response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_SUCCESS));
$collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
- $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
$collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));
$collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 1));
} else {
@@ -129,7 +126,6 @@ class Syncroton_Command_GetItemEstimate extends Syncroton_Command_Wbxml
$response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_SUCCESS));
$collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
- $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
$collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));
if($collectionData['syncState']->counter === 0) {
// this is the first sync. in most cases there are data on the server.
diff --git a/lib/ext/Syncroton/Command/ICommand.php b/lib/ext/Syncroton/Command/ICommand.php
index fd286d4..d6edb23 100644
--- a/lib/ext/Syncroton/Command/ICommand.php
+++ b/lib/ext/Syncroton/Command/ICommand.php
@@ -35,4 +35,11 @@ interface Syncroton_Command_ICommand
* create the response
*/
public function getResponse();
+
+ /**
+ * return headers of command
+ *
+ * @return array list of headers
+ */
+ public function getHeaders();
}
diff --git a/lib/ext/Syncroton/Command/ItemOperations.php b/lib/ext/Syncroton/Command/ItemOperations.php
index a89bd5f..c3ee287 100644
--- a/lib/ext/Syncroton/Command/ItemOperations.php
+++ b/lib/ext/Syncroton/Command/ItemOperations.php
@@ -105,6 +105,8 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
/**
* generate ItemOperations response
+ *
+ * @todo add multipart support to all types of fetches
*/
public function getResponse()
{
@@ -151,9 +153,30 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSyncBase', 'FileReference', $fetch['fileReference']));
$properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
- $dataController
- ->getFileReference($fetch['fileReference'])
- ->appendXML($properties, $this->_device);
+
+ $fileReference = $dataController->getFileReference($fetch['fileReference']);
+
+ // unset data field and move content to stream
+ if ($this->_requestParameters['acceptMultipart'] == true) {
+ $this->_headers['Content-Type'] = 'application/vnd.ms-sync.multipart';
+
+ $partStream = fopen("php://temp", 'r+');
+
+ if (is_resource($fileReference->data)) {
+ stream_copy_to_stream($fileReference->data, $partStream);
+ } else {
+ fwrite($partStream, $fileReference->data);
+ }
+
+ unset($fileReference->data);
+
+ $this->_parts[] = $partStream;
+
+ $fileReference->part = count($this->_parts);
+ }
+
+ $fileReference->appendXML($properties, $this->_device);
+
$fetchTag->appendChild($properties);
}
} catch (Syncroton_Exception_NotFound $e) {
diff --git a/lib/ext/Syncroton/Command/MeetingResponse.php b/lib/ext/Syncroton/Command/MeetingResponse.php
new file mode 100644
index 0000000..47eb12c
--- /dev/null
+++ b/lib/ext/Syncroton/Command/MeetingResponse.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package Syncroton
+ * @subpackage Command
+ * @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author Lars Kneschke <l.kneschke at metaways.de>
+ */
+
+/**
+ * class to handle ActiveSync MeetingResponse command
+ *
+ * @package Syncroton
+ * @subpackage Command
+ */
+class Syncroton_Command_MeetingResponse extends Syncroton_Command_Wbxml
+{
+ protected $_results = array();
+
+ protected $_defaultNameSpace = 'uri:MeetingResponse';
+ protected $_documentElement = 'MeetingResponse';
+
+ /**
+ * parse MeetingResponse request
+ */
+ public function handle()
+ {
+ $dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_CALENDAR, $this->_device, $this->_syncTimeStamp);
+
+ $xml = simplexml_import_dom($this->_requestBody);
+
+ foreach ($xml as $meetingResponse) {
+ $request = new Syncroton_Model_MeetingResponse($meetingResponse);
+
+ try {
+ $calendarId = $dataController->setAttendeeStatus($request);
+
+ $this->_results[] = array(
+ 'calendarId' => $calendarId,
+ 'request' => $request,
+ 'status' => 1
+ );
+
+ } catch (Syncroton_Exception_Status_MeetingResponse $sesmr) {
+ $this->_results[] = array(
+ 'request' => $request,
+ 'status' => $sesmr->getCode()
+ );
+ }
+ }
+
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " results: " . print_r($this->_results, true));
+ }
+
+ /**
+ * generate MeetingResponse response
+ */
+ public function getResponse()
+ {
+ $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Search', 'uri:Search');
+
+ $meetingResponse = $this->_outputDom->documentElement;
+
+ foreach ($this->_results as $result) {
+ $resultElement = $this->_outputDom->createElementNS('uri:MeetingResponse', 'Result');
+
+ if (isset($result['request']->requestId)) {
+ $resultElement->appendChild($this->_outputDom->createElementNS('uri:MeetingResponse', 'RequestId', $result['request']->requestId));
+ } elseif (isset($result['request']->longId)) {
+ $resultElement->appendChild($this->_outputDom->createElementNS('uri:Search', 'LongId', $result['request']->longId));
+ }
+
+ $resultElement->appendChild($this->_outputDom->createElementNS('uri:MeetingResponse', 'Status', $result['status']));
+
+ if (isset($result['calendarId'])) {
+ $resultElement->appendChild($this->_outputDom->createElementNS('uri:MeetingResponse', 'CalendarId', $result['calendarId']));
+ }
+
+ $meetingResponse->appendChild($resultElement);
+ }
+
+ return $this->_outputDom;
+ }
+}
diff --git a/lib/ext/Syncroton/Command/Options.php b/lib/ext/Syncroton/Command/Options.php
index 252899c..f126cc5 100644
--- a/lib/ext/Syncroton/Command/Options.php
+++ b/lib/ext/Syncroton/Command/Options.php
@@ -22,11 +22,13 @@ class Syncroton_Command_Options
*
* @return void
*/
- public function getResponse()
+ public function getHeaders()
{
// same header like Exchange 2xxx???
- header('MS-Server-ActiveSync: 14.00.0536.000');
- header("MS-ASProtocolVersions: 2.5,12.0,12.1,14.0,14.1");
- header("MS-ASProtocolCommands: CreateCollection,DeleteCollection,FolderCreate,FolderDelete,FolderSync,FolderUpdate,GetAttachment,GetHierarchy,GetItemEstimate,ItemOperations,MeetingResponse,MoveCollection,MoveItems,Provision,ResolveRecipients,Ping,SendMail,Search,Settings,SmartForward,SmartReply,Sync,ValidateCert");
+ return array(
+ 'MS-Server-ActiveSync' => '14.00.0536.000',
+ 'MS-ASProtocolVersions' => '2.5,12.0,12.1,14.0,14.1',
+ 'MS-ASProtocolCommands' => 'CreateCollection,DeleteCollection,FolderCreate,FolderDelete,FolderSync,FolderUpdate,GetAttachment,GetHierarchy,GetItemEstimate,ItemOperations,MeetingResponse,MoveCollection,MoveItems,Provision,ResolveRecipients,Ping,SendMail,Search,Settings,SmartForward,SmartReply,Sync,ValidateCert'
+ );
}
}
diff --git a/lib/ext/Syncroton/Command/Ping.php b/lib/ext/Syncroton/Command/Ping.php
index 3270d53..1bb7da8 100644
--- a/lib/ext/Syncroton/Command/Ping.php
+++ b/lib/ext/Syncroton/Command/Ping.php
@@ -31,18 +31,6 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
protected $_changesDetected = false;
/**
- *
- * @var int
- */
- public static $pingTimeout = 60;
-
- /**
- *
- * @var int
- */
- public static $quietTime = 180;
-
- /**
* @var Syncroton_Backend_StandAlone_Abstract
*/
protected $_dataBackend;
@@ -108,7 +96,8 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
$folderWithChanges = array();
do {
- sleep(self::$pingTimeout);
+ // take a break to save battery lifetime
+ sleep(Syncroton_Registry::getPingTimeout());
$now = new DateTime('now', new DateTimeZone('utc'));
@@ -136,8 +125,8 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
continue;
}
- // safe battery time by skipping folders which got synchronied less than self::$quietTime seconds ago
- if (($now->getTimestamp() - $syncState->lastsync->getTimestamp()) < self::$quietTime) {
+ // safe battery time by skipping folders which got synchronied less than Syncroton_Registry::getQuietTime() seconds ago
+ if (($now->getTimestamp() - $syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
continue;
}
@@ -167,8 +156,13 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " seconds left: " . $secondsLeft);
-
- } while ($secondsLeft > 0);
+
+ // See: http://www.tine20.org/forum/viewtopic.php?f=12&t=12146
+ //
+ // break if there are less than PingTimeout + 10 seconds left for the next loop
+ // otherwise the response will be returned after the client has finished his Ping
+ // request already maybe
+ } while ($secondsLeft > (Syncroton_Registry::getPingTimeout() + 10));
}
if ($this->_logger instanceof Zend_Log)
diff --git a/lib/ext/Syncroton/Command/SendMail.php b/lib/ext/Syncroton/Command/SendMail.php
index c25f813..7fff25e 100644
--- a/lib/ext/Syncroton/Command/SendMail.php
+++ b/lib/ext/Syncroton/Command/SendMail.php
@@ -37,9 +37,9 @@ class Syncroton_Command_SendMail extends Syncroton_Command_Wbxml
$this->_replaceMime = false;
$this->_source = array(
- 'collectionId' => $this->_requestParameters['collectionId'],
- 'itemId' => $this->_requestParameters['itemId'],
- 'instanceId' => null
+ 'collectionId' => $this->_requestParameters['collectionId'],
+ 'itemId' => $this->_requestParameters['itemId'],
+ 'instanceId' => null
);
} else {
diff --git a/lib/ext/Syncroton/Command/Sync.php b/lib/ext/Syncroton/Command/Sync.php
index 204f7ab..46e38c2 100644
--- a/lib/ext/Syncroton/Command/Sync.php
+++ b/lib/ext/Syncroton/Command/Sync.php
@@ -123,14 +123,52 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$this->_globalWindowSize = isset($xml->WindowSize) ? (int)$xml->WindowSize : 100;
+
if ($this->_globalWindowSize > $this->_maxWindowSize) {
$this->_globalWindowSize = $this->_maxWindowSize;
}
- foreach ($xml->Collections->Collection as $xmlCollection) {
- $collectionData = new Syncroton_Model_SyncCollection($xmlCollection);
+ $collections = array();
+ $isPartialRequest = isset($xml->Partial);
+
+ // try to restore collections from previous request
+ if ($isPartialRequest) {
+ $decodedCollections = Zend_Json::decode($this->_device->lastsynccollection);
+
+ if (is_array($decodedCollections)) {
+ foreach ($decodedCollections as $collection) {
+ $collections[$collection['collectionId']] = new Syncroton_Model_SyncCollection($collection);
+ }
+ }
+ }
+
+ // Collections element is optional when Partial element is sent
+ if (isset($xml->Collections)) {
+ foreach ($xml->Collections->Collection as $xmlCollection) {
+ $collectionId = (string)$xmlCollection->CollectionId;
+
+ // do we have to update a collection sent in previous sync request?
+ if ($isPartialRequest && isset($collections[$collectionId])) {
+ $collections[$collectionId]->setFromSimpleXMLElement($xmlCollection);
+ } else {
+ $collections[$collectionId] = new Syncroton_Model_SyncCollection($xmlCollection);
+ }
+ }
- // got the folder synchronized to the device already
+ // store current value of $collections for next Sync command request
+ $collectionsToSave = array();
+
+ foreach ($collections as $collection) {
+ $collectionsToSave[$collection->collectionId] = $collection->toArray();
+ }
+
+ $this->_device->lastsynccollection = Zend_Json::encode($collectionsToSave);
+
+ Syncroton_Registry::getDeviceBackend()->update($this->_device);
+ }
+
+ foreach ($collections as $collectionData) {
+ // has the folder been synchronised to the device already
try {
$collectionData->folder = $this->_folderBackend->getFolder($this->_device, $collectionData->collectionId);
@@ -143,11 +181,11 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
if ($collectionData->syncKey > 0) {
$collectionData->folder = new Syncroton_Model_Folder(array(
'deviceId' => $this->_device,
- 'serverId' => $collectionData->collectionId
+ 'serverId' => $collectionData->collectionId
));
}
- $this->_collections[] = $collectionData;
+ $this->_collections[$collectionData->collectionId] = $collectionData;
continue;
}
@@ -171,7 +209,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
'lastsync' => $this->_syncTimeStamp
));
- $this->_collections[] = $collectionData;
+ $this->_collections[$collectionData->collectionId] = $collectionData;
continue;
}
@@ -185,7 +223,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$this->_syncStateBackend->resetState($this->_device, $collectionData->folder);
$this->_contentStateBackend->resetState($this->_device, $collectionData->folder);
- $this->_collections[] = $collectionData;
+ $this->_collections[$collectionData->collectionId] = $collectionData;
continue;
}
@@ -359,7 +397,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$collectionData->toBeFetched = $toBeFetched;
}
- $this->_collections[] = $collectionData;
+ $this->_collections[$collectionData->collectionId] = $collectionData;
$this->_modifications[$collectionData->collectionId] = $clientModifications;
}
}
@@ -372,6 +410,12 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
{
$sync = $this->_outputDom->documentElement;
+ if (count($this->_collections) == 0) {
+ $sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_RESEND_FULL_XML));
+
+ return $this->_outputDom;
+ }
+
$collections = $sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collections'));
$totalChanges = 0;
@@ -381,6 +425,11 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$intervalStart = time();
do {
+ // take a break to save battery lifetime
+ sleep(Syncroton_Registry::getPingTimeout());
+
+ $now = new DateTime(null, new DateTimeZone('utc'));
+
foreach($this->_collections as $collectionData) {
// countinue immediately if folder does not exist
if (! ($collectionData->folder instanceof Syncroton_Model_IFolder)) {
@@ -391,6 +440,56 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
break 2;
} else {
+ if ($collectionData->getChanges !== true) {
+ continue;
+ }
+
+ try {
+ // just check if the folder still exists
+ $this->_folderBackend->get($collectionData->folder);
+ } catch (Syncroton_Exception_NotFound $senf) {
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " collection does not exist anymore: " . $collectionData->collectionId);
+
+ $collectionData->getChanges = false;
+
+ // make sure this is the last while loop
+ // no break 2 here, as we like to check the other folders too
+ $intervalStart -= $this->_heartbeatInterval;
+ }
+
+ // check that the syncstate still exists and is still valid
+ try {
+ $syncState = $this->_syncStateBackend->getSyncState($this->_device, $collectionData->folder);
+
+ // another process synchronized data of this folder already. let's skip it
+ if ($syncState->id !== $collectionData->syncState->id) {
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " syncstate changed during heartbeat interval for collection: " . $collectionData->folder->serverId);
+
+ $collectionData->getChanges = false;
+
+ // make sure this is the last while loop
+ // no break 2 here, as we like to check the other folders too
+ $intervalStart -= $this->_heartbeatInterval;
+ }
+ } catch (Syncroton_Exception_NotFound $senf) {
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " no syncstate found anymore for collection: " . $collectionData->folder->serverId);
+
+ $collectionData->syncState = null;
+
+ // make sure this is the last while loop
+ // no break 2 here, as we like to check the other folders too
+ $intervalStart -= $this->_heartbeatInterval;
+ }
+
+
+ // safe battery time by skipping folders which got synchronied less than Syncroton_Command_Ping::$quietTime seconds ago
+ if (($now->getTimestamp() - $collectionData->syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
+ continue;
+ }
+
$dataController = Syncroton_Data_Factory::factory($collectionData->folder->class , $this->_device, $this->_syncTimeStamp);
$estimate = $dataController->getCountOfChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState);
@@ -402,15 +501,29 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
}
}
- // wait some PING_TIMEOUT seconds until neext loop
- sleep(Syncroton_Command_Ping::$pingTimeout);
+ $this->_syncTimeStamp = clone $now;
- } while (time() - $intervalStart < $this->_heartbeatInterval);
+ // See: http://www.tine20.org/forum/viewtopic.php?f=12&t=12146
+ //
+ // break if there are less than PingTimeout + 10 seconds left for the next loop
+ // otherwise the response will be returned after the client has finished his Ping
+ // request already maybe
+ } while (time() - $intervalStart < $this->_heartbeatInterval - (Syncroton_Registry::getPingTimeout() + 10));
}
foreach($this->_collections as $collectionData) {
$collectionChanges = 0;
+ /**
+ * keep track of entries added on server side
+ */
+ $newContentStates = array();
+
+ /**
+ * keep track of entries deleted on server side
+ */
+ $deletedContentStates = array();
+
// invalid collectionid provided
if (! ($collectionData->folder instanceof Syncroton_Model_IFolder)) {
$collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
@@ -484,7 +597,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
// entries to be deleted
$serverModifications['deleted'] = array_diff($allClientEntries, $allServerEntries);
-
+
// fetch entries changed since last sync
$serverModifications['changed'] = $dataController->getChangedEntries($collectionData->collectionId, $collectionData->syncState->lastsync, $this->_syncTimeStamp);
$serverModifications['changed'] = array_merge($serverModifications['changed'], $clientModifications['forceChange']);
@@ -533,7 +646,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData->folder->class));
}
- $syncKeyNode = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey'));
+ $syncKeyElement = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey'));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData->collectionId));
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
@@ -596,13 +709,8 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$commands = $this->_outputDom->createElementNS('uri:AirSync', 'Commands');
- /**
- * process entries added on server side
- */
- $newContentStates = array();
-
foreach($serverModifications['added'] as $id => $serverId) {
- if($collectionChanges === $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
+ if($collectionChanges == $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
break;
}
@@ -652,14 +760,14 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
'creation_time' => $this->_syncTimeStamp,
'creation_synckey' => $collectionData->syncState->counter + 1
));
- unset($serverModifications['added'][$id]);
+ unset($serverModifications['added'][$id]);
}
/**
* process entries changed on server side
*/
foreach($serverModifications['changed'] as $id => $serverId) {
- if($collectionChanges === $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
+ if($collectionChanges == $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
break;
}
@@ -685,13 +793,8 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
unset($serverModifications['changed'][$id]);
}
- /**
- * process entries deleted on server side
- */
- $deletedContentStates = array();
-
foreach($serverModifications['deleted'] as $id => $serverId) {
- if($collectionChanges === $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
+ if($collectionChanges == $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
break;
}
@@ -745,7 +848,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
// then increase SyncKey
$collectionData->syncState->counter++;
}
- $syncKeyNode->appendChild($this->_outputDom->createTextNode($collectionData->syncState->counter));
+ $syncKeyElement->appendChild($this->_outputDom->createTextNode($collectionData->syncState->counter));
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " new synckey is ". $collectionData->syncState->counter);
@@ -753,7 +856,10 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
if (isset($collectionData->syncState) && $collectionData->syncState instanceof Syncroton_Model_ISyncState &&
$collectionData->syncState->counter != $collectionData->syncKey) {
-
+
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " update syncState for collection: " . $collectionData->collectionId);
+
// store pending data in sync state when needed
if(isset($countOfPendingChanges) && $countOfPendingChanges > 0) {
$collectionData->syncState->pendingdata = array(
@@ -785,18 +891,13 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$this->_syncStateBackend->create($collectionData->syncState, $keepPreviousSyncKey);
// store contentstates for new entries added to client
- if (isset($newContentStates)) {
- foreach($newContentStates as $state) {
- #$state->creation_synckey = $collectionData->syncState->counter;
- $this->_contentStateBackend->create($state);
- }
+ foreach($newContentStates as $state) {
+ $this->_contentStateBackend->create($state);
}
// remove contentstates for entries to be deleted on client
- if (isset($deletedContentStates)) {
- foreach($deletedContentStates as $state) {
- $this->_contentStateBackend->delete($state);
- }
+ foreach($deletedContentStates as $state) {
+ $this->_contentStateBackend->delete($state);
}
Syncroton_Registry::getTransactionManager()->commitTransaction($transactionId);
diff --git a/lib/ext/Syncroton/Command/Wbxml.php b/lib/ext/Syncroton/Command/Wbxml.php
index cab5cc9..e927cc2 100644
--- a/lib/ext/Syncroton/Command/Wbxml.php
+++ b/lib/ext/Syncroton/Command/Wbxml.php
@@ -118,6 +118,20 @@ abstract class Syncroton_Command_Wbxml implements Syncroton_Command_ICommand
protected $_logger;
/**
+ * list of part streams
+ *
+ * @var array
+ */
+ protected $_parts = array();
+
+ /**
+ * list of headers
+ *
+ * @var array
+ */
+ protected $_headers = array();
+
+ /**
* the constructor
*
* @param mixed $requestBody
@@ -142,6 +156,9 @@ abstract class Syncroton_Command_Wbxml implements Syncroton_Command_ICommand
$this->_syncTimeStamp = new DateTime(null, new DateTimeZone('UTC'));
+ // set default content type
+ $this->_headers['Content-Type'] = 'application/vnd.ms-sync.wbxml';
+
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " sync timestamp: " . $this->_syncTimeStamp->format('Y-m-d H:i:s'));
@@ -184,4 +201,22 @@ abstract class Syncroton_Command_Wbxml implements Syncroton_Command_ICommand
}
}
}
+
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Command_ICommand::getHeaders()
+ */
+ public function getHeaders()
+ {
+ return $this->_headers;
+ }
+ /**
+ * return array of part streams
+ *
+ * @return array
+ */
+ public function getParts()
+ {
+ return $this->_parts;
+ }
}
diff --git a/lib/ext/Syncroton/Data/AData.php b/lib/ext/Syncroton/Data/AData.php
index 4e02ef5..1382f68 100644
--- a/lib/ext/Syncroton/Data/AData.php
+++ b/lib/ext/Syncroton/Data/AData.php
@@ -16,53 +16,71 @@
abstract class Syncroton_Data_AData implements Syncroton_Data_IData
{
- /**
- * used by unit tests only to simulated added folders
- */
- public static $folders = array();
+ const LONGID_DELIMITER = "\xe2\x87\x94"; # UTF8 â
/**
* used by unit tests only to simulated added folders
*/
- public static $entries = array();
-
- /**
- * used by unit tests only to simulated added folders
- */
public static $changedEntries = array();
public function __construct(Syncroton_Model_IDevice $_device, DateTime $_timeStamp)
{
- $this->_device = $_device;
- $this->_timestamp = $_timeStamp;
-
+ $this->_device = $_device;
+ $this->_timestamp = $_timeStamp;
$this->_db = Syncroton_Registry::getDatabase();
$this->_tablePrefix = 'Syncroton_';
+ $this->_ownerId = '1234';
+ }
+
+ public function getFolder($id)
+ {
+ $select = $this->_db->select()
+ ->from($this->_tablePrefix . 'data_folder')
+ ->where('owner_id = ?', $this->_ownerId)
+ ->where('id = ?', $id);
- $this->_initData();
+ $stmt = $this->_db->query($select);
+ $folder = $stmt->fetch();
+ $stmt = null; # see https://bugs.php.net/bug.php?id=44081
+
+ if ($folder === false) {
+ throw new Syncroton_Exception_NotFound("folder $id not found");
+ }
+
+ return new Syncroton_Model_Folder(array(
+ 'serverId' => $folder['id'],
+ 'displayName' => $folder['name'],
+ 'type' => $folder['type'],
+ 'parentId' => !empty($folder['parent_id']) ? $folder['parent_id'] : null
+ ));
}
public function createFolder(Syncroton_Model_IFolder $folder)
{
- $folder->id = sha1(mt_rand(). microtime());
+ if (!in_array($folder->type, $this->_supportedFolderTypes)) {
+ throw new Syncroton_Exception_UnexpectedValue();
+ }
- // normaly generated on server backend
- $folder->serverId = sha1(mt_rand(). microtime());
-
- Syncroton_Data_AData::$folders[get_class($this)][$folder->serverId] = $folder;
-
- return Syncroton_Data_AData::$folders[get_class($this)][$folder->serverId];
+ $id = !empty($folder->serverId) ? $folder->serverId : sha1(mt_rand(). microtime());
+
+ $this->_db->insert($this->_tablePrefix . 'data_folder', array(
+ 'id' => $id,
+ 'type' => $folder->type,
+ 'name' => $folder->displayName,
+ 'owner_id' => $this->_ownerId,
+ 'parent_id' => $folder->parentId
+ ));
+
+ return $this->getFolder($id);
}
public function createEntry($_folderId, Syncroton_Model_IEntry $_entry)
{
$id = sha1(mt_rand(). microtime());
- #Syncroton_Data_AData::$entries[get_class($this)][$_folderId][$id] = $_entry;
-
$this->_db->insert($this->_tablePrefix . 'data', array(
'id' => $id,
- 'type' => get_class($this),
+ 'class' => get_class($_entry),
'folder_id' => $_folderId,
'data' => serialize($_entry)
));
@@ -72,26 +90,46 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
public function deleteEntry($_folderId, $_serverId, $_collectionData)
{
- #$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
+ $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
$result = $this->_db->delete($this->_tablePrefix . 'data', array('id = ?' => $_serverId));
return (bool) $result;
-
- #unset(Syncroton_Data_AData::$entries[get_class($this)][$folderId][$_serverId]);
}
public function deleteFolder($_folderId)
{
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
-
- unset(Syncroton_Data_AData::$folders[get_class($this)][$folderId]);
- unset(Syncroton_Data_AData::$entries[get_class($this)][$folderId]);
+
+ $result = $this->_db->delete($this->_tablePrefix . 'data', array('folder_id = ?' => $folderId));
+ $result = $this->_db->delete($this->_tablePrefix . 'data_folder', array('id = ?' => $folderId));
+
+ return (bool) $result;
}
public function getAllFolders()
{
- return Syncroton_Data_AData::$folders[get_class($this)];
+ $select = $this->_db->select()
+ ->from($this->_tablePrefix . 'data_folder')
+ ->where('type IN (?)', $this->_supportedFolderTypes)
+ ->where('owner_id = ?', $this->_ownerId);
+
+ $stmt = $this->_db->query($select);
+ $folders = $stmt->fetchAll();
+ $stmt = null; # see https://bugs.php.net/bug.php?id=44081
+
+ $result = array();
+
+ foreach ((array) $folders as $folder) {
+ $result[$folder['id']] = new Syncroton_Model_Folder(array(
+ 'serverId' => $folder['id'],
+ 'displayName' => $folder['name'],
+ 'type' => $folder['type'],
+ 'parentId' => $folder['parent_id']
+ ));
+ }
+
+ return $result;
}
public function getChangedEntries($_folderId, DateTime $_startTimeStamp, DateTime $_endTimeStamp = NULL)
@@ -177,7 +215,6 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
public function updateEntry($_folderId, $_serverId, Syncroton_Model_IEntry $_entry)
{
$this->_db->update($this->_tablePrefix . 'data', array(
- 'type' => get_class($this),
'folder_id' => $_folderId,
'data' => serialize($_entry)
), array(
@@ -187,10 +224,12 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
public function updateFolder(Syncroton_Model_IFolder $folder)
{
- Syncroton_Data_AData::$folders[get_class($this)][$folder->serverId] = $folder;
+ $this->_db->update($this->_tablePrefix . 'data_folder', array(
+ 'name' => $folder->displayName,
+ 'parent_id' => $folder->parentId
+ ), array(
+ 'id = ?' => $folder->serverId
+ ));
}
-
-
- abstract protected function _initData();
}
diff --git a/lib/ext/Syncroton/Data/Calendar.php b/lib/ext/Syncroton/Data/Calendar.php
index 9f24590..66c8d25 100644
--- a/lib/ext/Syncroton/Data/Calendar.php
+++ b/lib/ext/Syncroton/Data/Calendar.php
@@ -13,31 +13,22 @@
*
* @package Model
*/
-
-class Syncroton_Data_Calendar extends Syncroton_Data_AData
+class Syncroton_Data_Calendar extends Syncroton_Data_AData implements Syncroton_Data_IDataCalendar
{
- protected function _initData()
+ protected $_supportedFolderTypes = array(
+ Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR,
+ Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR_USER_CREATED
+ );
+
+ /**
+ * set attendee status for meeting
+ *
+ * @param Syncroton_Model_MeetingResponse $request the meeting response
+ * @return string id of new calendar entry
+ */
+ public function setAttendeeStatus(Syncroton_Model_MeetingResponse $reponse)
{
- /**
- * used by unit tests only to simulated added folders
- */
- Syncroton_Data_AData::$folders[get_class($this)] = array(
- 'calendarFolderId' => new Syncroton_Model_Folder(array(
- 'id' => sha1(mt_rand(). microtime()),
- 'serverId' => 'calendarFolderId',
- 'parentId' => 0,
- 'displayName' => 'Default Contacts Folder',
- 'type' => Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR
- ))
- );
-
- #/**
- # * used by unit tests only to simulated added folders
- # */
- #Syncroton_Data_AData::$entries[get_class($this)] = array(
- # 'calendarFolderId' => array(
- # )
- #);
+ return $reponse->requestId;
}
}
diff --git a/lib/ext/Syncroton/Data/Contacts.php b/lib/ext/Syncroton/Data/Contacts.php
index 1f191c2..a9b8d58 100644
--- a/lib/ext/Syncroton/Data/Contacts.php
+++ b/lib/ext/Syncroton/Data/Contacts.php
@@ -16,13 +16,18 @@
class Syncroton_Data_Contacts extends Syncroton_Data_AData implements Syncroton_Data_IDataSearch
{
+ protected $_supportedFolderTypes = array(
+ Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT,
+ Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED
+ );
+
/**
* (non-PHPdoc)
* @see Syncroton_Data_IDataSearch::getSearchEntry()
*/
public function getSearchEntry($longId, $options)
{
- list($collectionId, $serverId) = explode('-', $longId, 2);
+ list($collectionId, $serverId) = explode(Syncroton_Data_AData::LONGID_DELIMITER, $longId, 2);
$contact = $this->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $collectionId)), $serverId);
@@ -56,8 +61,8 @@ class Syncroton_Data_Contacts extends Syncroton_Data_AData implements Syncroton_
continue;
}
$found[] = new Syncroton_Model_StoreResponseResult(array(
- 'longId' => 'addressbookFolderId-' . $serverId,
- 'properties' => $this->getSearchEntry('addressbookFolderId-' . $serverId, $store->options)
+ 'longId' => 'addressbookFolderId' . Syncroton_Data_AData::LONGID_DELIMITER . $serverId,
+ 'properties' => $this->getSearchEntry('addressbookFolderId' . Syncroton_Data_AData::LONGID_DELIMITER . $serverId, $store->options)
));
}
}
@@ -72,131 +77,5 @@ class Syncroton_Data_Contacts extends Syncroton_Data_AData implements Syncroton_
return $storeResponse;
}
-
- protected function _initData()
- {
- /**
- * used by unit tests only to simulated added folders
- */
- if (!isset(Syncroton_Data_AData::$folders[get_class($this)])) {
- Syncroton_Data_AData::$folders[get_class($this)] = array(
- 'addressbookFolderId' => new Syncroton_Model_Folder(array(
- 'id' => sha1(mt_rand(). microtime()),
- 'serverId' => 'addressbookFolderId',
- 'parentId' => 0,
- 'displayName' => 'Default Contacts Folder',
- 'type' => Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT
- )),
- 'anotherAddressbookFolderId' => new Syncroton_Model_Folder(array(
- 'id' => sha1(mt_rand(). microtime()),
- 'serverId' => 'anotherAddressbookFolderId',
- 'parentId' => 0,
- 'displayName' => 'Another Contacts Folder',
- 'type' => Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED
- ))
- );
- }
-
- /**
- * used by unit tests only to simulated added folders
- */
- $entries = $this->getServerEntries('addressbookFolderId', 1);
-
- if (count($entries) == 0) {
- $testData = array(
- 'addressbookFolderId' => array(
- 'contact1' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Lars',
- 'lastName' => 'Kneschke'
- )),
- 'contact2' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Cornelius',
- 'lastName' => 'WeiÃ'
- )),
- 'contact3' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Lars',
- 'lastName' => 'Kneschke'
- )),
- 'contact4' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Cornelius',
- 'lastName' => 'WeiÃ'
- )),
- 'contact5' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Lars',
- 'lastName' => 'Kneschke'
- )),
- 'contact6' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Cornelius',
- 'lastName' => 'WeiÃ'
- )),
- 'contact7' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Lars',
- 'lastName' => 'Kneschke'
- )),
- 'contact8' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Cornelius',
- 'lastName' => 'WeiÃ'
- )),
- 'contact9' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Lars',
- 'lastName' => 'Kneschke'
- )),
- 'contact10' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Cornelius',
- 'lastName' => 'WeiÃ'
- ))
- ),
- 'anotherAddressbookFolderId' => array(
- 'contact1' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Lars',
- 'lastName' => 'Kneschke'
- )),
- 'contact2' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Cornelius',
- 'lastName' => 'WeiÃ'
- )),
- 'contact3' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Lars',
- 'lastName' => 'Kneschke'
- )),
- 'contact4' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Cornelius',
- 'lastName' => 'WeiÃ'
- )),
- 'contact5' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Lars',
- 'lastName' => 'Kneschke'
- )),
- 'contact6' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Cornelius',
- 'lastName' => 'WeiÃ'
- )),
- 'contact7' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Lars',
- 'lastName' => 'Kneschke'
- )),
- 'contact8' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Cornelius',
- 'lastName' => 'WeiÃ'
- )),
- 'contact9' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Lars',
- 'lastName' => 'Kneschke'
- )),
- 'contact10' => new Syncroton_Model_Contact(array(
- 'firstName' => 'Cornelius',
- 'lastName' => 'WeiÃ'
- ))
- )
- );
-
- foreach ($testData['addressbookFolderId'] as $data) {
- $this->createEntry('addressbookFolderId', $data);
- }
- foreach ($testData['anotherAddressbookFolderId'] as $data) {
- $this->createEntry('anotherAddressbookFolderId', $data);
- }
- }
- }
}
diff --git a/lib/ext/Syncroton/Data/Email.php b/lib/ext/Syncroton/Data/Email.php
index 9c84aae..5005c97 100644
--- a/lib/ext/Syncroton/Data/Email.php
+++ b/lib/ext/Syncroton/Data/Email.php
@@ -16,10 +16,14 @@
*/
class Syncroton_Data_Email extends Syncroton_Data_AData implements Syncroton_Data_IDataEmail
{
- /**
- * used by unit tests only to simulated added folders
- */
- public static $entries = array();
+ protected $_supportedFolderTypes = array(
+ Syncroton_Command_FolderSync::FOLDERTYPE_DELETEDITEMS,
+ Syncroton_Command_FolderSync::FOLDERTYPE_DRAFTS,
+ Syncroton_Command_FolderSync::FOLDERTYPE_INBOX,
+ Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED,
+ Syncroton_Command_FolderSync::FOLDERTYPE_OUTBOX,
+ Syncroton_Command_FolderSync::FOLDERTYPE_SENTMAIL
+ );
/**
* (non-PHPdoc)
@@ -40,7 +44,7 @@ class Syncroton_Data_Email extends Syncroton_Data_AData implements Syncroton_Dat
*/
public function getFileReference($fileReference)
{
- list($messageId, $partId) = explode('-', $fileReference, 2);
+ list($messageId, $partId) = explode(Syncroton_Data_AData::LONGID_DELIMITER, $fileReference, 2);
// example code
return new Syncroton_Model_FileReference(array(
@@ -78,67 +82,5 @@ class Syncroton_Data_Email extends Syncroton_Data_AData implements Syncroton_Dat
}
// send email
}
-
- protected function _initData()
- {
- /**
- * used by unit tests only to simulated added folders
- */
- Syncroton_Data_AData::$folders[get_class($this)] = array(
- 'emailInboxFolderId' => new Syncroton_Model_Folder(array(
- 'id' => sha1(mt_rand(). microtime()),
- 'serverId' => 'emailInboxFolderId',
- 'parentId' => 0,
- 'displayName' => 'Inbox',
- 'type' => Syncroton_Command_FolderSync::FOLDERTYPE_INBOX
- )),
- 'emailSentFolderId' => new Syncroton_Model_Folder(array(
- 'id' => sha1(mt_rand(). microtime()),
- 'serverId' => 'emailSentFolderId',
- 'parentId' => 0,
- 'displayName' => 'Sent',
- 'type' => Syncroton_Command_FolderSync::FOLDERTYPE_SENTMAIL
- ))
- );
-
- /**
- * used by unit tests only to simulated added folders
- */
-
- $entries = $this->getServerEntries('emailInboxFolderId', 1);
-
- if (count($entries) == 0) {
- $testData = array(
- 'emailInboxFolderId' => array(
- 'email1' => new Syncroton_Model_Email(array(
- 'accountId' => 'FooBar',
- 'attachments' => array(
- new Syncroton_Model_EmailAttachment(array(
- 'fileReference' => '12345abcd',
- 'umAttOrder' => 1
- ))
- ),
- 'categories' => array('123', '456'),
- 'cc' => 'l.kneschke at metaways.de',
- 'dateReceived' => new DateTime('2012-03-21 14:00:00', new DateTimeZone('UTC')),
- 'from' => 'k.kneschke at metaways.de',
- 'subject' => 'Test Subject',
- 'to' => 'j.kneschke at metaways.de',
- 'read' => 1,
- 'body' => new Syncroton_Model_EmailBody(array(
- 'type' => Syncroton_Model_EmailBody::TYPE_PLAINTEXT,
- 'data' => 'Hello!',
- 'truncated' => true,
- 'estimatedDataSize' => 600
- ))
- )),
- )
- );
-
- foreach ($testData['emailInboxFolderId'] as $data) {
- $this->createEntry('emailInboxFolderId', $data);
- }
- }
- }
}
diff --git a/lib/ext/Syncroton/Data/IDataCalendar.php b/lib/ext/Syncroton/Data/IDataCalendar.php
new file mode 100644
index 0000000..2ea6831
--- /dev/null
+++ b/lib/ext/Syncroton/Data/IDataCalendar.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package Model
+ * @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author Lars Kneschke <l.kneschke at metaways.de>
+ */
+
+/**
+ * interface for extended calendar backend
+ *
+ * @package Model
+ */
+interface Syncroton_Data_IDataCalendar
+{
+ /**
+ * set attendee status for meeting
+ *
+ * @param Syncroton_Model_MeetingResponse $request the meeting response
+ * @return string id of new calendar entry
+ */
+ public function setAttendeeStatus(Syncroton_Model_MeetingResponse $request);
+}
+
diff --git a/lib/ext/Syncroton/Data/IDataEmail.php b/lib/ext/Syncroton/Data/IDataEmail.php
index 7ecbb37..bca70b9 100644
--- a/lib/ext/Syncroton/Data/IDataEmail.php
+++ b/lib/ext/Syncroton/Data/IDataEmail.php
@@ -9,7 +9,7 @@
*/
/**
- * intace for email backend
+ * interface for extended email backend
*
* @package Model
*/
@@ -26,7 +26,7 @@ interface Syncroton_Data_IDataEmail
/**
* forward an email
*
- * @param string|array $source is either a string(LongId) or an arrey with following properties collectionId, itemId and instanceId
+ * @param string|array $source is either a string(LongId) or an array with following properties collectionId, itemId and instanceId
* @param string $inputStream
* @param string $saveInSent
*/
@@ -35,7 +35,7 @@ interface Syncroton_Data_IDataEmail
/**
* reply to an email
*
- * @param string|array $source is either a string(LongId) or an arrey with following properties collectionId, itemId and instanceId
+ * @param string|array $source is either a string(LongId) or an array with following properties collectionId, itemId and instanceId
* @param string $inputStream
* @param string $saveInSent
*/
diff --git a/lib/ext/Syncroton/Data/Tasks.php b/lib/ext/Syncroton/Data/Tasks.php
index 30a80d2..a0c4756 100644
--- a/lib/ext/Syncroton/Data/Tasks.php
+++ b/lib/ext/Syncroton/Data/Tasks.php
@@ -16,28 +16,9 @@
class Syncroton_Data_Tasks extends Syncroton_Data_AData
{
- protected function _initData()
- {
- /**
- * used by unit tests only to simulated added folders
- */
- Syncroton_Data_AData::$folders[get_class($this)] = array(
- 'tasksFolderId' => new Syncroton_Model_Folder(array(
- 'id' => sha1(mt_rand(). microtime()),
- 'serverId' => 'tasksFolderId',
- 'parentId' => 0,
- 'displayName' => 'Default Tasks Folder',
- 'type' => Syncroton_Command_FolderSync::FOLDERTYPE_TASK
- ))
- );
-
- /**
- * used by unit tests only to simulated added folders
- */
- #Syncroton_Data_AData::$entries[get_class($this)] = array(
- # 'tasksFolderId' => array(
- # )
- #);
- }
+ protected $_supportedFolderTypes = array(
+ Syncroton_Command_FolderSync::FOLDERTYPE_TASK,
+ Syncroton_Command_FolderSync::FOLDERTYPE_TASK_USER_CREATED
+ );
}
diff --git a/lib/ext/Syncroton/Model/AEntry.php b/lib/ext/Syncroton/Model/AEntry.php
index ed8c496..1a58827 100644
--- a/lib/ext/Syncroton/Model/AEntry.php
+++ b/lib/ext/Syncroton/Model/AEntry.php
@@ -129,8 +129,6 @@ abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, Iterato
*/
public function setFromArray(array $properties)
{
- $this->_elements = array();
-
foreach($properties as $key => $value) {
try {
$this->$key = $value; //echo __LINE__ . PHP_EOL;
@@ -153,8 +151,6 @@ abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, Iterato
throw new InvalidArgumentException('Unexpected element name: ' . $properties->getName());
}
- $this->_elements = array();
-
foreach (array_keys($this->_properties) as $namespace) {
if ($namespace == 'Internal') {
continue;
diff --git a/lib/ext/Syncroton/Model/Email.php b/lib/ext/Syncroton/Model/Email.php
index 65cb674..35f2661 100644
--- a/lib/ext/Syncroton/Model/Email.php
+++ b/lib/ext/Syncroton/Model/Email.php
@@ -12,16 +12,23 @@
* class to handle ActiveSync email
*
* @package Model
- * @property array attachments
- * @property string contentType
- * @property array flag
+ * @property array attachments
+ * @property string contentType
+ * @property array flag
* @property Syncroton_Model_EmailBody body
- * @property array cc
- * @property array to
- * @property int read
+ * @property array cc
+ * @property array to
+ * @property int lastVerbExecuted
+ * @property DateTime lastVerbExecutionTime
+ * @property int read
*/
class Syncroton_Model_Email extends Syncroton_Model_AEntry
{
+ const LASTVERB_UNKNOWN = 0;
+ const LASTVERB_REPLYTOSENDER = 1;
+ const LASTVERB_REPLYTOALL = 2;
+ const LASTVERB_FORWARD = 3;
+
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
diff --git a/lib/ext/Syncroton/Model/FileReference.php b/lib/ext/Syncroton/Model/FileReference.php
index 3aa00cb..9f0bbad 100644
--- a/lib/ext/Syncroton/Model/FileReference.php
+++ b/lib/ext/Syncroton/Model/FileReference.php
@@ -26,6 +26,7 @@ class Syncroton_Model_FileReference extends Syncroton_Model_AEntry
),
'ItemOperations' => array(
'data' => array('type' => 'string', 'encoding' => 'base64'),
+ 'part' => array('type' => 'number')
)
);
diff --git a/lib/ext/Syncroton/Model/Folder.php b/lib/ext/Syncroton/Model/Folder.php
index 33412cf..dfabc34 100644
--- a/lib/ext/Syncroton/Model/Folder.php
+++ b/lib/ext/Syncroton/Model/Folder.php
@@ -29,6 +29,7 @@ class Syncroton_Model_Folder extends Syncroton_Model_AEntry implements Syncroton
'Internal' => array(
'id' => array('type' => 'string'),
'deviceId' => array('type' => 'string'),
+ 'ownerId' => array('type' => 'string'),
'class' => array('type' => 'string'),
'creationTime' => array('type' => 'datetime'),
'lastfiltertype' => array('type' => 'number')
diff --git a/lib/ext/Syncroton/Model/IDevice.php b/lib/ext/Syncroton/Model/IDevice.php
index b1177cb..e0c59ab 100644
--- a/lib/ext/Syncroton/Model/IDevice.php
+++ b/lib/ext/Syncroton/Model/IDevice.php
@@ -36,6 +36,7 @@
* @property string calendarfilter_id
* @property string tasksfilter_id
* @property string emailfilter_id
+ * @property string lastsynccollection
*/
interface Syncroton_Model_IDevice
{
diff --git a/lib/ext/Syncroton/Model/IFolder.php b/lib/ext/Syncroton/Model/IFolder.php
index 4ec3700..f27f523 100644
--- a/lib/ext/Syncroton/Model/IFolder.php
+++ b/lib/ext/Syncroton/Model/IFolder.php
@@ -21,6 +21,7 @@
* @property string displayName
* @property string creationTime
* @property string lastfiltertype
+ * @property string type
*/
interface Syncroton_Model_IFolder
diff --git a/lib/ext/Syncroton/Model/MeetingResponse.php b/lib/ext/Syncroton/Model/MeetingResponse.php
new file mode 100644
index 0000000..4d1e171
--- /dev/null
+++ b/lib/ext/Syncroton/Model/MeetingResponse.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package Model
+ * @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author Lars Kneschke <l.kneschke at metaways.de>
+ */
+
+/**
+ * class to handle MeetingResponse request
+ *
+ * @package Model
+ * @property int userResponse
+ * @property string collectionId
+ * @property string calendarId
+ * @property string requestId
+ * @property string instanceId
+ * @property string longId
+ */
+
+class Syncroton_Model_MeetingResponse extends Syncroton_Model_AEntry
+{
+ protected $_xmlBaseElement = 'Request';
+
+ /**
+ * attendee status
+ */
+ const RESPONSE_ACCEPTED = 1;
+ const RESPONSE_TENTATIVE = 2;
+ const RESPONSE_DECLINED = 3;
+
+ protected $_properties = array(
+ 'MeetingResponse' => array(
+ 'userResponse' => array('type' => 'number'),
+ 'collectionId' => array('type' => 'string'),
+ 'calendarId' => array('type' => 'string'),
+ 'requestId' => array('type' => 'string'),
+ 'instanceId' => array('type' => 'datetime'),
+ ),
+ 'Search' => array(
+ 'longId' => array('type' => 'string')
+ )
+ );
+}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/SyncCollection.php b/lib/ext/Syncroton/Model/SyncCollection.php
index 736c582..220226b 100644
--- a/lib/ext/Syncroton/Model/SyncCollection.php
+++ b/lib/ext/Syncroton/Model/SyncCollection.php
@@ -20,19 +20,40 @@
* @property int windowSize
*/
-class Syncroton_Model_SyncCollection
+class Syncroton_Model_SyncCollection extends Syncroton_Model_AEntry
{
- protected $_collection = array();
+ protected $_elements = array(
+ 'syncState' => null,
+ 'folder' => null
+ );
protected $_xmlCollection;
+ protected $_xmlBaseElement = 'Collection';
+
public function __construct($properties = null)
{
if ($properties instanceof SimpleXMLElement) {
$this->setFromSimpleXMLElement($properties);
} elseif (is_array($properties)) {
$this->setFromArray($properties);
- }
+ }
+
+ if (!isset($this->_elements['options'])) {
+ $this->_elements['options'] = array();
+ }
+ if (!isset($this->_elements['options']['filterType'])) {
+ $this->_elements['options']['filterType'] = Syncroton_Command_Sync::FILTER_NOTHING;
+ }
+ if (!isset($this->_elements['options']['mimeSupport'])) {
+ $this->_elements['options']['mimeSupport'] = Syncroton_Command_Sync::MIMESUPPORT_DONT_SEND_MIME;
+ }
+ if (!isset($this->_elements['options']['mimeTruncation'])) {
+ $this->_elements['options']['mimeTruncation'] = Syncroton_Command_Sync::TRUNCATE_NOTHING;
+ }
+ if (!isset($this->_elements['options']['bodyPreferences'])) {
+ $this->_elements['options']['bodyPreferences'] = array();
+ }
}
/**
@@ -100,7 +121,7 @@ class Syncroton_Model_SyncCollection
public function hasClientAdds()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
- throw new InvalidArgumentException('no collection xml element set');
+ return false;
}
return isset($this->_xmlCollection->Commands->Add);
@@ -115,7 +136,7 @@ class Syncroton_Model_SyncCollection
public function hasClientChanges()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
- throw new InvalidArgumentException('no collection xml element set');
+ return false;
}
return isset($this->_xmlCollection->Commands->Change);
@@ -130,7 +151,7 @@ class Syncroton_Model_SyncCollection
public function hasClientDeletes()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
- throw new InvalidArgumentException('no collection xml element set');
+ return false;
}
return isset($this->_xmlCollection->Commands->Delete);
@@ -145,97 +166,113 @@ class Syncroton_Model_SyncCollection
public function hasClientFetches()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
- throw new InvalidArgumentException('no collection xml element set');
+ return false;
}
return isset($this->_xmlCollection->Commands->Fetch);
}
- public function setFromArray(array $properties)
- {
- $this->_collection = array('options' => array(
- 'filterType' => Syncroton_Command_Sync::FILTER_NOTHING,
- 'mimeSupport' => Syncroton_Command_Sync::MIMESUPPORT_DONT_SEND_MIME,
- 'mimeTruncation' => Syncroton_Command_Sync::TRUNCATE_NOTHING,
- 'bodyPreferences' => array()
- ));
-
- foreach($properties as $key => $value) {
- try {
- $this->$key = $value; //echo __LINE__ . PHP_EOL;
- } catch (InvalidArgumentException $iae) {
- //ignore invalid properties
- //echo __LINE__ . PHP_EOL;
- }
- }
- }
-
/**
+ * this functions does not only set from SimpleXMLElement but also does merge from SimpleXMLElement
+ * to support partial sync requests
*
- * @param SimpleXMLElement $xmlCollection
+ * @param SimpleXMLElement $properties
* @throws InvalidArgumentException
*/
- public function setFromSimpleXMLElement(SimpleXMLElement $xmlCollection)
+ public function setFromSimpleXMLElement(SimpleXMLElement $properties)
{
- if ($xmlCollection->getName() !== 'Collection') {
- throw new InvalidArgumentException('Unexpected element name: ' . $xmlCollection->getName());
+ if (!in_array($properties->getName(), (array) $this->_xmlBaseElement)) {
+ throw new InvalidArgumentException('Unexpected element name: ' . $properties->getName());
}
- $this->_xmlCollection = $xmlCollection;
+ $this->_xmlCollection = $properties;
- $this->_collection = array(
- 'syncKey' => (int)$xmlCollection->SyncKey,
- 'collectionId' => (string)$xmlCollection->CollectionId,
- 'deletesAsMoves' => isset($xmlCollection->DeletesAsMoves) && (string)$xmlCollection->DeletesAsMoves === '0' ? false : true,
- 'conversationMode' => isset($xmlCollection->ConversationMode) && (string)$xmlCollection->ConversationMode === '0' ? false : true,
- 'getChanges' => isset($xmlCollection->GetChanges) && (string) $xmlCollection->GetChanges === '0' ? false : true,
- 'windowSize' => isset($xmlCollection->WindowSize) ? (int)$xmlCollection->WindowSize : 100,
- 'class' => isset($xmlCollection->Class) ? (string)$xmlCollection->Class : null,
- 'options' => array(
- 'filterType' => Syncroton_Command_Sync::FILTER_NOTHING,
- 'mimeSupport' => Syncroton_Command_Sync::MIMESUPPORT_DONT_SEND_MIME,
- 'mimeTruncation' => Syncroton_Command_Sync::TRUNCATE_NOTHING,
- 'bodyPreferences' => array()
- ),
-
- 'syncState' => null,
- 'folder' => null
- );
+ if (isset($properties->CollectionId)) {
+ $this->_elements['collectionId'] = (string)$properties->CollectionId;
+ }
+
+ if (isset($properties->SyncKey)) {
+ $this->_elements['syncKey'] = (int)$properties->SyncKey;
+ }
+
+ if (isset($properties->Class)) {
+ $this->_elements['class'] = (string)$properties->Class;
+ } elseif (!array_key_exists('class', $this->_elements)) {
+ $this->_elements['class'] = null;
+ }
- if (isset($xmlCollection->Supported)) {
- // @todo collected supported elements
+ if (isset($properties->WindowSize)) {
+ $this->_elements['windowSize'] = (string)$properties->WindowSize;
+ } elseif (!array_key_exists('windowSize', $this->_elements)) {
+ $this->_elements['windowSize'] = 100;
+ }
+
+ if (isset($properties->DeletesAsMoves)) {
+ if ((string)$properties->DeletesAsMoves === '0') {
+ $this->_elements['deletesAsMoves'] = false;
+ } else {
+ $this->_elements['deletesAsMoves'] = true;
+ }
+ } elseif (!array_key_exists('deletesAsMoves', $this->_elements)) {
+ $this->_elements['deletesAsMoves'] = true;
+ }
+
+ if (isset($properties->ConversationMode)) {
+ if ((string)$properties->ConversationMode === '0') {
+ $this->_elements['conversationMode'] = false;
+ } else {
+ $this->_elements['conversationMode'] = true;
+ }
+ } elseif (!array_key_exists('conversationMode', $this->_elements)) {
+ $this->_elements['conversationMode'] = true;
+ }
+
+ if (isset($properties->GetChanges)) {
+ if ((string)$properties->GetChanges === '0') {
+ $this->_elements['getChanges'] = false;
+ } else {
+ $this->_elements['getChanges'] = true;
+ }
+ } elseif (!array_key_exists('getChanges', $this->_elements)) {
+ $this->_elements['getChanges'] = true;
+ }
+
+ if (isset($properties->Supported)) {
+ // @todo collect supported elements
}
// process options
- if (isset($xmlCollection->Options)) {
+ if (isset($properties->Options)) {
+ $this->_elements['options'] = array();
+
// optional parameters
- if (isset($xmlCollection->Options->FilterType)) {
- $this->_collection['options']['filterType'] = (int)$xmlCollection->Options->FilterType;
+ if (isset($properties->Options->FilterType)) {
+ $this->_elements['options']['filterType'] = (int)$properties->Options->FilterType;
}
- if (isset($xmlCollection->Options->MIMESupport)) {
- $this->_collection['options']['mimeSupport'] = (int)$xmlCollection->Options->MIMESupport;
+ if (isset($properties->Options->MIMESupport)) {
+ $this->_elements['options']['mimeSupport'] = (int)$properties->Options->MIMESupport;
}
- if (isset($xmlCollection->Options->MIMETruncation)) {
- $this->_collection['options']['mimeTruncation'] = (int)$xmlCollection->Options->MIMETruncation;
+ if (isset($properties->Options->MIMETruncation)) {
+ $this->_elements['options']['mimeTruncation'] = (int)$properties->Options->MIMETruncation;
}
- if (isset($xmlCollection->Options->Class)) {
- $this->_collection['options']['class'] = (string)$xmlCollection->Options->Class;
+ if (isset($properties->Options->Class)) {
+ $this->_elements['options']['class'] = (string)$properties->Options->Class;
}
// try to fetch element from AirSyncBase:BodyPreference
- $airSyncBase = $xmlCollection->Options->children('uri:AirSyncBase');
+ $airSyncBase = $properties->Options->children('uri:AirSyncBase');
if (isset($airSyncBase->BodyPreference)) {
foreach ($airSyncBase->BodyPreference as $bodyPreference) {
$type = (int) $bodyPreference->Type;
- $this->_collection['options']['bodyPreferences'][$type] = array(
+ $this->_elements['options']['bodyPreferences'][$type] = array(
'type' => $type
);
// optional
if (isset($bodyPreference->TruncationSize)) {
- $this->_collection['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
+ $this->_elements['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
}
}
}
@@ -246,27 +283,30 @@ class Syncroton_Model_SyncCollection
}
}
+ public function toArray()
+ {
+ $result = array();
+
+ foreach (array('syncKey', 'collectionId', 'deletesAsMoves', 'conversationMode', 'getChanges', 'windowSize', 'class', 'options') as $key) {
+ if (isset($this->$key)) {
+ $result[$key] = $this->$key;
+ }
+ }
+
+ return $result;
+ }
+
public function &__get($name)
{
- if (array_key_exists($name, $this->_collection)) {
- return $this->_collection[$name];
+ if (array_key_exists($name, $this->_elements)) {
+ return $this->_elements[$name];
}
- //echo $name . PHP_EOL;
+ echo $name . PHP_EOL;
return null;
}
public function __set($name, $value)
{
- $this->_collection[$name] = $value;
- }
-
- public function __isset($name)
- {
- return isset($this->_collection[$name]);
- }
-
- public function __unset($name)
- {
- unset($this->_collection[$name]);
+ $this->_elements[$name] = $value;
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Registry.php b/lib/ext/Syncroton/Registry.php
index 6903c5d..1495f8f 100644
--- a/lib/ext/Syncroton/Registry.php
+++ b/lib/ext/Syncroton/Registry.php
@@ -36,6 +36,8 @@ class Syncroton_Registry extends ArrayObject
const GAL_DATA_CLASS = 'gal_data_class';
const DEFAULT_POLICY = 'default_policy';
+ const PING_TIMEOUT = 'ping_timeout';
+ const QUIET_TIME = 'quiet_time';
const DATABASE = 'database';
const TRANSACTIONMANAGER = 'transactionmanager';
@@ -241,6 +243,22 @@ class Syncroton_Registry extends ArrayObject
}
/**
+ * return ping timeout
+ *
+ * sleep "ping timeout" seconds between folder checks in Ping and Sync command
+ *
+ * @return int
+ */
+ public static function getPingTimeout()
+ {
+ if (!self::isRegistered(self::PING_TIMEOUT)) {
+ return 60;
+ }
+
+ return self::get(self::PING_TIMEOUT);
+ }
+
+ /**
* returns policy backend
*
* creates Syncroton_Backend_Policy on the fly if not set before via
@@ -258,6 +276,22 @@ class Syncroton_Registry extends ArrayObject
}
/**
+ * return quiet time
+ *
+ * don't check folders if last sync was "quiet time" seconds ago
+ *
+ * @return int
+ */
+ public static function getQuietTime()
+ {
+ if (!self::isRegistered(self::QUIET_TIME)) {
+ return 180;
+ }
+
+ return self::get(self::QUIET_TIME);
+ }
+
+ /**
* returns syncstate backend
*
* creates Syncroton_Backend_SyncState on the fly if not before via
diff --git a/lib/ext/Syncroton/Server.php b/lib/ext/Syncroton/Server.php
index a9bc110..dc1064e 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -15,6 +15,11 @@
*/
class Syncroton_Server
{
+ const PARAMETER_ATTACHMENTNAME = 0;
+ const PARAMETER_COLLECTIONID = 1;
+ const PARAMETER_ITEMID = 3;
+ const PARAMETER_OPTIONS = 7;
+
protected $_body;
/**
@@ -71,16 +76,25 @@ class Syncroton_Server
}
/**
- * handle options request
- *
- */
+ * handle options request
+ */
protected function _handleOptions()
{
$command = new Syncroton_Command_Options();
- $command->getResponse();
+ $this->_sendHeaders($command->getHeaders());
}
+ protected function _sendHeaders(array $headers)
+ {
+ foreach ($headers as $name => $value) {
+ header($name . ': ' . $value);
+ }
+ }
+
+ /**
+ * handle post request
+ */
protected function _handlePost()
{
$requestParameters = $this->_getRequestParameters($this->_request);
@@ -118,9 +132,7 @@ class Syncroton_Server
$requestBody = $this->_body;
}
- if (PHP_SAPI !== 'cli') {
- header("MS-Server-ActiveSync: 14.00.0536.000");
- }
+ header("MS-Server-ActiveSync: 14.00.0536.000");
try {
$command = new $className($requestBody, $device, $requestParameters);
@@ -142,8 +154,6 @@ class Syncroton_Server
return;
}
-
-
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " unexpected exception occured: " . get_class($e));
@@ -161,22 +171,61 @@ class Syncroton_Server
if ($this->_logger instanceof Zend_Log) {
$response->formatOutput = true;
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " xml response:\n" . $response->saveXML());
+ $response->formatOutput = false;
}
-
+
+ if (isset($command) && $command instanceof Syncroton_Command_ICommand) {
+ $this->_sendHeaders($command->getHeaders());
+ }
+
$outputStream = fopen("php://temp", 'r+');
-
+
$encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3);
$encoder->encode($response);
-
- // avoid sending headers while running on cli (phpunit)
- if (PHP_SAPI !== 'cli') {
- header("Content-Type: application/vnd.ms-sync.wbxml");
+
+ if ($requestParameters['acceptMultipart'] == true) {
+ $parts = $command->getParts();
+
+ // output multipartheader
+ $bodyPartCount = 1 + count($parts);
+
+ // number of parts (4 bytes)
+ $header = pack('i', $bodyPartCount);
+
+ $partOffset = 4 + (($bodyPartCount * 2) * 4);
+
+ // wbxml body start and length
+ $streamStat = fstat($outputStream);
+ $header .= pack('ii', $partOffset, $streamStat['size']);
+
+ $partOffset += $streamStat['size'];
+
+ // calculate start and length of parts
+ foreach ($parts as $partId => $partStream) {
+ rewind($partStream);
+ $streamStat = fstat($partStream);
+
+ // part start and length
+ $header .= pack('ii', $partOffset, $streamStat['size']);
+ $partOffset += $streamStat['size'];
+ }
+
+ echo $header;
}
-
+
+ // output body
rewind($outputStream);
fpassthru($outputStream);
+
+ // output multiparts
+ if (isset($parts)) {
+ foreach ($parts as $partStream) {
+ rewind($partStream);
+ fpassthru($partStream);
+ }
+ }
}
- }
+ }
/**
* return request params
@@ -185,7 +234,7 @@ class Syncroton_Server
*/
protected function _getRequestParameters(Zend_Controller_Request_Http $request)
{
- if (!isset($_GET['DeviceId'])) {
+ if (strpos($request->getRequestUri(), '&') === false) {
$commands = array(
0 => 'Sync',
1 => 'SendMail',
@@ -208,15 +257,8 @@ class Syncroton_Server
22 => 'ValidateCert'
);
- // find the first $_GET parameter which has a key but no value
- // the key are the request parameters
- foreach ($_GET as $key => $value) {
- if (empty($value)) {
- $requestParameters = $key;
- break;
- }
- }
-
+ $requestParameters = substr($request->getRequestUri(), strpos($request->getRequestUri(), '?'));
+
$stream = fopen("php://temp", 'r+');
fwrite($stream, base64_decode($requestParameters));
rewind($stream);
@@ -224,14 +266,17 @@ class Syncroton_Server
// unpack the first 4 bytes
$unpacked = unpack('CprotocolVersion/Ccommand/vlocale', fread($stream, 4));
- $protocolVersion = $unpacked['protocolVersion'];
+ // 140 => 14.0
+ $protocolVersion = substr($unpacked['protocolVersion'], 0, -1) . '.' . substr($unpacked['protocolVersion'], -1);
$command = $commands[$unpacked['command']];
$locale = $unpacked['locale'];
// unpack deviceId
$length = ord(fread($stream, 1));
if ($length > 0) {
- $unpacked = unpack('H' . $length*2 . 'string', fread($stream, $length));
+ $toUnpack = fread($stream, $length);
+
+ $unpacked = unpack("H" . ($length * 2) . "string", $toUnpack);
$deviceId = $unpacked['string'];
}
@@ -241,7 +286,7 @@ class Syncroton_Server
$unpacked = unpack('Vstring', fread($stream, $length));
$policyKey = $unpacked['string'];
}
-
+
// unpack device type
$length = ord(fread($stream, 1));
if ($length > 0) {
@@ -252,27 +297,27 @@ class Syncroton_Server
while (! feof($stream)) {
$tag = ord(fread($stream, 1));
$length = ord(fread($stream, 1));
-
+
switch ($tag) {
- case 0:
+ case self::PARAMETER_ATTACHMENTNAME:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$attachmentName = $unpacked['string'];
break;
- case 1:
+ case self::PARAMETER_COLLECTIONID:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$collectionId = $unpacked['string'];
break;
- case 3:
+ case self::PARAMETER_ITEMID:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$itemId = $unpacked['string'];
break;
- case 7:
+ case self::PARAMETER_OPTIONS:
$options = ord(fread($stream, 1));
$saveInSent = !!($options & 0x01);
@@ -290,12 +335,13 @@ class Syncroton_Server
'protocolVersion' => $protocolVersion,
'command' => $command,
'deviceId' => $deviceId,
- 'deviceType' => isset($deviceType) ? $deviceType : null,
- 'policyKey' => isset($policyKey) ? $policyKey : null,
- 'saveInSent' => isset($saveInSent) ? $saveInSent : false,
- 'collectionId' => isset($collectionId) ? $collectionId : null,
- 'itemId' => isset($itemId) ? $itemId : null,
- 'attachmentName' => isset($attachmentName) ? $attachmentName : null
+ 'deviceType' => isset($deviceType) ? $deviceType : null,
+ 'policyKey' => isset($policyKey) ? $policyKey : null,
+ 'saveInSent' => isset($saveInSent) ? $saveInSent : false,
+ 'collectionId' => isset($collectionId) ? $collectionId : null,
+ 'itemId' => isset($itemId) ? $itemId : null,
+ 'attachmentName' => isset($attachmentName) ? $attachmentName : null,
+ 'acceptMultipart' => isset($acceptMultiPart) ? $acceptMultiPart : false
);
} else {
$result = array(
@@ -307,7 +353,8 @@ class Syncroton_Server
'saveInSent' => $request->getQuery('SaveInSent') == 'T',
'collectionId' => $request->getQuery('CollectionId'),
'itemId' => $request->getQuery('ItemId'),
- 'attachmentName' => $request->getQuery('AttachmentName'),
+ 'attachmentName' => $request->getQuery('AttachmentName'),
+ 'acceptMultipart' => $request->getServer('HTTP_MS_ASACCEPTMULTIPART') == 'T'
);
}
diff --git a/lib/ext/Syncroton/Wbxml/Abstract.php b/lib/ext/Syncroton/Wbxml/Abstract.php
index 6c86806..7fdd653 100644
--- a/lib/ext/Syncroton/Wbxml/Abstract.php
+++ b/lib/ext/Syncroton/Wbxml/Abstract.php
@@ -111,10 +111,12 @@ abstract class Syncroton_Wbxml_Abstract
*/
public function getDPI($_uInt = 0)
{
- if(($dpi = @constant('Syncroton_Wbxml_Abstract::DPI_' . $_uInt)) === NULL) {
+ if(!defined('Syncroton_Wbxml_Abstract::DPI_' . $_uInt)) {
throw new Syncroton_Wbxml_Exception('unknown wellknown identifier: ' . $_uInt);
}
-
+
+ $dpi = constant('Syncroton_Wbxml_Abstract::DPI_' . $_uInt);
+
return $dpi;
}
diff --git a/lib/kolab_sync.php b/lib/kolab_sync.php
index 03001b7..9316b7a 100644
--- a/lib/kolab_sync.php
+++ b/lib/kolab_sync.php
@@ -43,7 +43,7 @@ class kolab_sync extends rcube
public $user;
const CHARSET = 'UTF-8';
- const VERSION = "2.0";
+ const VERSION = "2.1";
/**
@@ -140,7 +140,7 @@ class kolab_sync extends rcube
Syncroton_Registry::set(Syncroton_Registry::FOLDERBACKEND, new kolab_sync_backend_folder);
Syncroton_Registry::set(Syncroton_Registry::SYNCSTATEBACKEND, new kolab_sync_backend_state);
Syncroton_Registry::set(Syncroton_Registry::CONTENTSTATEBACKEND, new kolab_sync_backend_content);
- Syncroton_Registry::set(Syncroton_Registry::POLICYBACKEND, new kolab_sync_backend_policy);
+ Syncroton_Registry::set(Syncroton_Registry::POLICYBACKEND, new kolab_sync_backend_policy);
Syncroton_Registry::setContactsDataClass('kolab_sync_data_contacts');
Syncroton_Registry::setCalendarDataClass('kolab_sync_data_calendar');
@@ -148,6 +148,10 @@ class kolab_sync extends rcube
Syncroton_Registry::setTasksDataClass('kolab_sync_data_tasks');
Syncroton_Registry::setGALDataClass('kolab_sync_data_gal');
+ // Configuration
+ Syncroton_Registry::set(Syncroton_Registry::PING_TIMEOUT, $this->config->get('activesync_ping_timeout', 60));
+ Syncroton_Registry::set(Syncroton_Registry::QUIET_TIME, $this->config->get('activesync_quiet_time', 180));
+
// Run Syncroton
$syncroton = new Syncroton_Server($userid);
$syncroton->handle();
diff --git a/lib/kolab_sync_data_calendar.php b/lib/kolab_sync_data_calendar.php
index 7cf2ae2..a0c828b 100644
--- a/lib/kolab_sync_data_calendar.php
+++ b/lib/kolab_sync_data_calendar.php
@@ -26,7 +26,7 @@
/**
* Calendar (Events) data class for Syncroton
*/
-class kolab_sync_data_calendar extends kolab_sync_data
+class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data_IDataCalendar
{
/**
* Mapping from ActiveSync Calendar namespace fields
@@ -472,6 +472,18 @@ class kolab_sync_data_calendar extends kolab_sync_data
return $event;
}
+ /**
+ * Set attendee status for meeting
+ *
+ * @param Syncroton_Model_MeetingResponse $request The meeting response
+ *
+ * @return string ID of new calendar entry
+ */
+ public function setAttendeeStatus(Syncroton_Model_MeetingResponse $request)
+ {
+ // @TODO: not implemented
+ throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingMeeting::MEETING_ERROR);
+ }
/**
* Returns filter query array according to specified ActiveSync FilterType
@@ -508,7 +520,6 @@ class kolab_sync_data_calendar extends kolab_sync_data
return $filter;
}
-
/**
* Converts libkolab alarms string into number of minutes
*/
commit 59e4d286e2f7cd05774e5bc2e46090eabdffdea1
Author: Aleksander Machniak <alec at alec.pl>
Date: Sat Jan 12 16:34:10 2013 +0100
Fix GAL search for records with 'email:*', but not 'email' fields
diff --git a/lib/kolab_sync_data_gal.php b/lib/kolab_sync_data_gal.php
index ed47c46..95e3e17 100644
--- a/lib/kolab_sync_data_gal.php
+++ b/lib/kolab_sync_data_gal.php
@@ -222,6 +222,10 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat
while ($row = $result->next()) {
$row['sourceid'] = $idx;
+
+ // make sure 'email' item is there, convert all email:* into one
+ $row['email'] = $book->get_col_values('email', $row, true);
+
$key = $this->contact_key($row);
unset($row['_raw_attrib']); // save some memory, @TODO: do this in rcube_ldap
$records[$key] = $row;
More information about the commits
mailing list