2 commits - lib/ext lib/kolab_sync_backend.php lib/kolab_sync_data_email.php lib/kolab_sync_data_gal.php

Aleksander Machniak machniak at kolabsys.com
Wed Sep 12 15:24:32 CEST 2012


 lib/ext/Syncroton/Command/FolderSync.php             |    2 
 lib/ext/Syncroton/Command/ItemOperations.php         |   56 +++++++--
 lib/ext/Syncroton/Command/Provision.php              |    2 
 lib/ext/Syncroton/Command/Search.php                 |   48 ++------
 lib/ext/Syncroton/Command/SendMail.php               |   10 +
 lib/ext/Syncroton/Command/SmartForward.php           |    2 
 lib/ext/Syncroton/Command/SmartReply.php             |    2 
 lib/ext/Syncroton/Command/Sync.php                   |  104 +++++++++++++----
 lib/ext/Syncroton/Data/Contacts.php                  |   71 +++++++++++-
 lib/ext/Syncroton/Data/IDataSearch.php               |    7 -
 lib/ext/Syncroton/Model/AEntry.php                   |   70 +++++++----
 lib/ext/Syncroton/Model/Contact.php                  |    4 
 lib/ext/Syncroton/Model/Device.php                   |    3 
 lib/ext/Syncroton/Model/Email.php                    |   24 ++--
 lib/ext/Syncroton/Model/EmailAttachment.php          |    4 
 lib/ext/Syncroton/Model/EmailBody.php                |    2 
 lib/ext/Syncroton/Model/IDevice.php                  |   13 ++
 lib/ext/Syncroton/Model/IEntry.php                   |   21 +++
 lib/ext/Syncroton/Model/StoreRequest.php             |    2 
 lib/ext/Syncroton/Model/StoreResponse.php            |   37 +++---
 lib/ext/Syncroton/Registry.php                       |    2 
 lib/ext/Syncroton/Server.php                         |    3 
 lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage1.php |  112 +++++++++----------
 lib/ext/Syncroton/Wbxml/Encoder.php                  |    6 -
 lib/kolab_sync_backend.php                           |    1 
 lib/kolab_sync_data_email.php                        |  102 +++++++++++------
 lib/kolab_sync_data_gal.php                          |   73 +++++++-----
 27 files changed, 504 insertions(+), 279 deletions(-)

New commits:
commit b9a488314de4423bb5525828409f8cde271151ea
Author: Aleksander Machniak <alec at alec.pl>
Date:   Wed Sep 12 15:23:47 2012 +0200

    Update Syncroton, update code for API changes

diff --git a/lib/ext/Syncroton/Command/FolderSync.php b/lib/ext/Syncroton/Command/FolderSync.php
index 9017798..99be44c 100644
--- a/lib/ext/Syncroton/Command/FolderSync.php
+++ b/lib/ext/Syncroton/Command/FolderSync.php
@@ -185,7 +185,7 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
         foreach($adds as $folder) {
             $add = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Add'));
             
-            $folder->appendXML($add);
+            $folder->appendXML($add, $this->_device);
 
             // store folder in backend
             if (empty($folder->id)) {
diff --git a/lib/ext/Syncroton/Command/ItemOperations.php b/lib/ext/Syncroton/Command/ItemOperations.php
index 7692970..a89bd5f 100644
--- a/lib/ext/Syncroton/Command/ItemOperations.php
+++ b/lib/ext/Syncroton/Command/ItemOperations.php
@@ -44,7 +44,8 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
         if (isset($xml->Fetch)) {
             foreach ($xml->Fetch as $fetch) {
                 $fetchArray = array(
-                    'store' => (string)$fetch->Store
+                    'store' => (string)$fetch->Store,
+                    'options' => array()
                 );
                 
                 // try to fetch element from namespace AirSync
@@ -55,6 +56,13 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
                     $fetchArray['serverId']     = (string)$airSync->ServerId;
                 }
                 
+                // try to fetch element from namespace Search
+                $search = $fetch->children('uri:Search');
+                
+                if (isset($search->LongId)) {
+                    $fetchArray['longId'] = (string)$search->LongId;
+                }
+                
                 // try to fetch element from namespace AirSyncBase
                 $airSyncBase = $fetch->children('uri:AirSyncBase');
                 
@@ -66,15 +74,26 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
                     // try to fetch element from namespace AirSyncBase
                     $airSyncBase = $fetch->Options->children('uri:AirSyncBase');
                     
-                    if (isset($airSyncBase->BodyPreference)) {
-                        // required
-                        $fetchArray['bodyPreferenceType'] = (int) $airSyncBase->BodyPreference->Type;
-                        
-                        // optional
-                        if (isset($airSyncBase->BodyPreference->TruncationSize)) {
-                            $fetchArray['truncationSize'] = (int) $airSyncBase->BodyPreference->TruncationSize;
-                        }
-                    }
+                    if (isset($airSyncBase->BodyPreference)) {
+                        foreach ($airSyncBase->BodyPreference as $bodyPreference) {
+                            $type = (int) $bodyPreference->Type;
+                            $fetchArray['options']['bodyPreferences'][$type] = array(
+                                'type' => $type
+                            );
+                    
+                            // optional
+                            if (isset($bodyPreference->TruncationSize)) {
+                                $fetchArray['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
+                            }
+                            
+                            // optional
+                            if (isset($bodyPreference->AllOrNone)) {
+                                $fetchArray['options']['bodyPreferences'][$type]['allOrNone'] = (int) $bodyPreference->AllOrNone;
+                            }
+                        }
+                    }
+                    
+                    
                 }
                 $this->_fetches[] = $fetchArray;
             }
@@ -92,6 +111,7 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
         // add aditional namespaces
         $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSyncBase' , 'uri:AirSyncBase');
         $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSync'     , 'uri:AirSync');
+        $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Search'      , 'uri:Search');
         
         $itemOperations = $this->_outputDom->documentElement;
         
@@ -112,8 +132,18 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
                     
                     $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
                     $dataController
-                        ->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['collectionId'])), $fetch['serverId'])
-                        ->appendXML($properties);
+                        ->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['collectionId'], 'options' => $fetch['options'])), $fetch['serverId'])
+                        ->appendXML($properties, $this->_device);
+                    $fetchTag->appendChild($properties);
+                    
+                } elseif (isset($fetch['longId'])) {
+                    $fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS));
+                    $fetchTag->appendChild($this->_outputDom->createElementNS('uri:Search', 'LongId', $fetch['longId']));
+                    
+                    $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
+                    $dataController
+                        ->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['longId'], 'options' => $fetch['options'])), $fetch['longId'])
+                        ->appendXML($properties, $this->_device);
                     $fetchTag->appendChild($properties);
                     
                 } elseif (isset($fetch['fileReference'])) {
@@ -123,7 +153,7 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
                     $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
                     $dataController
                         ->getFileReference($fetch['fileReference'])
-                        ->appendXML($properties);
+                        ->appendXML($properties, $this->_device);
                     $fetchTag->appendChild($properties);
                 }
             } catch (Syncroton_Exception_NotFound $e) {
diff --git a/lib/ext/Syncroton/Command/Provision.php b/lib/ext/Syncroton/Command/Provision.php
index c4e582b..417267c 100644
--- a/lib/ext/Syncroton/Command/Provision.php
+++ b/lib/ext/Syncroton/Command/Provision.php
@@ -126,7 +126,7 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
             $easProvisionDoc = $data->appendChild($this->_outputDom->createElementNS('uri:Provision', 'EASProvisionDoc'));
             $this->_policyBackend
                 ->get($this->_device->policyId)
-                ->appendXML($easProvisionDoc);
+                ->appendXML($easProvisionDoc, $this->_device);
         }
         
         $this->_deviceBackend->update($this->_device);
diff --git a/lib/ext/Syncroton/Command/Search.php b/lib/ext/Syncroton/Command/Search.php
index ccef6ab..cf9eddd 100644
--- a/lib/ext/Syncroton/Command/Search.php
+++ b/lib/ext/Syncroton/Command/Search.php
@@ -56,46 +56,22 @@ class Syncroton_Command_Search extends Syncroton_Command_Wbxml
             throw new RuntimeException('class must be instanceof Syncroton_Data_IDataSearch');
         }
         
-        $storeResponse = new Syncroton_Model_StoreResponse(array(
-           'status' => self::STATUS_SUCCESS,
-        ));
-        
         try {
             $options = $this->_store->options;
             
             // Search
-            $results = $dataController->search($this->_store->query, $options);
-
-            // Calculate requested range
-            $start = $options['range'][0];
-            $limit = $options['range'][1] + 1;
-            $total = count($results);
-            $storeResponse->total = $total;
-
-            if ($total) {
-                if ($start > $total) {
-                    $start = $total;
-                }
-                if ($limit > $total) {
-                    $limit = max($start+1, $total);
-                }
-
-                if ($start > 0 || $limit < $total) {
-                    $results = array_slice($results, $start, $limit-$start);
-                }
-
-                $storeResponse->range = array($start, $start + count($results) - 1);
-            }
-
-            foreach ($results as $result) {
-                if (empty($result->properties)) {
-                    $result->properties = $dataController->getSearchEntry($result->longId, $this->_store->options);
-                }
-
-                $storeResponse->result[] = $result;
-            }
+            $storeResponse = $dataController->search($this->_store);
+            $storeResponse->status = self::STATUS_SUCCESS;
+            
         } catch (Exception $e) {
-            $storeResponse->status = self::STATUS_SERVER_ERROR;
+            if ($this->_logger instanceof Zend_Log)
+                $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " search exception: " . $e->getMessage());
+            if ($this->_logger instanceof Zend_Log)
+                $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " saerch exception trace : " . $e->getTraceAsString());
+            
+            $storeResponse = new Syncroton_Model_StoreResponse(array(
+               'status' => self::STATUS_SERVER_ERROR
+            ));
         }
 
         $search = $this->_outputDom->documentElement;
@@ -105,7 +81,7 @@ class Syncroton_Command_Search extends Syncroton_Command_Wbxml
         $response = $search->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Response'));
         $store    = $response->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Store'));
 
-        $storeResponse->appendXML($store);
+        $storeResponse->appendXML($store, $this->_device);
 
         return $this->_outputDom;
     }
diff --git a/lib/ext/Syncroton/Command/SendMail.php b/lib/ext/Syncroton/Command/SendMail.php
index d1875a2..603a61f 100644
--- a/lib/ext/Syncroton/Command/SendMail.php
+++ b/lib/ext/Syncroton/Command/SendMail.php
@@ -34,9 +34,13 @@ class Syncroton_Command_SendMail extends Syncroton_Command_Wbxml
         if ($this->_requestParameters['contentType'] == 'message/rfc822') {
             $this->_mime          = $this->_requestBody;
             $this->_saveInSent    = $this->_requestParameters['saveInSent'] == 'T';
+            $this->_replaceMime   = false;
             
-            $this->_collectionId  = $this->_requestParameters['collectionId'];
-            $this->_itemId        = $this->_requestParameters['itemId'];
+            $this->_source = array(
+                    'collectionId' => $this->_requestParameters['collectionId'],
+                    'itemId'       => $this->_requestParameters['itemId'],
+                    'instanceId'   => null
+            );
             
         } else {
             $xml = simplexml_import_dom($this->_requestBody);
@@ -81,7 +85,7 @@ class Syncroton_Command_SendMail extends Syncroton_Command_Wbxml
                 'status' => $ses->getCode(),
             ));
 
-            $response->appendXML($this->_outputDom->documentElement);
+            $response->appendXML($this->_outputDom->documentElement, $this->_device);
 
             return $this->_outputDom;
         }
diff --git a/lib/ext/Syncroton/Command/SmartForward.php b/lib/ext/Syncroton/Command/SmartForward.php
index aeedff4..4a9d841 100644
--- a/lib/ext/Syncroton/Command/SmartForward.php
+++ b/lib/ext/Syncroton/Command/SmartForward.php
@@ -39,7 +39,7 @@ class Syncroton_Command_SmartForward extends Syncroton_Command_SendMail
                 'status' => $ses->getCode(),
             ));
 
-            $response->appendXML($this->_outputDom->documentElement);
+            $response->appendXML($this->_outputDom->documentElement, $this->_device);
 
             return $this->_outputDom;
         }
diff --git a/lib/ext/Syncroton/Command/SmartReply.php b/lib/ext/Syncroton/Command/SmartReply.php
index 5778581..59149ee 100644
--- a/lib/ext/Syncroton/Command/SmartReply.php
+++ b/lib/ext/Syncroton/Command/SmartReply.php
@@ -39,7 +39,7 @@ class Syncroton_Command_SmartReply extends Syncroton_Command_SendMail
                 'status' => $ses->getCode(),
             ));
 
-            $response->appendXML($this->_outputDom->documentElement);
+            $response->appendXML($this->_outputDom->documentElement, $this->_device);
 
             return $this->_outputDom;
         }
diff --git a/lib/ext/Syncroton/Command/Sync.php b/lib/ext/Syncroton/Command/Sync.php
index 74ad41d..ea28966 100644
--- a/lib/ext/Syncroton/Command/Sync.php
+++ b/lib/ext/Syncroton/Command/Sync.php
@@ -105,6 +105,8 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
     
     protected $_maxWindowSize = 100;
     
+    protected $_heartbeatInterval = null;
+    
     /**
      * process the XML file and add, change, delete or fetches data 
      */
@@ -113,6 +115,12 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
         // input xml
         $xml = simplexml_import_dom($this->_requestBody);
         
+        if (isset($xml->HeartbeatInterval)) {
+            $this->_heartbeatInterval = (int)$xml->HeartbeatInterval;
+        } elseif (isset($xml->Wait)) {
+            $this->_heartbeatInterval = (int)$xml->Wait * 60;
+        }
+        
         $this->_globalWindowSize = isset($xml->WindowSize) ? (int)$xml->WindowSize : 100;
         
         if ($this->_globalWindowSize > $this->_maxWindowSize) {
@@ -368,6 +376,38 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
 
         $totalChanges = 0;
         
+        // continue only if there are changes or no time is left
+        if ($this->_heartbeatInterval > 0) {
+            $intervalStart = time();
+            
+            do {
+                foreach($this->_collections as $collectionData) {
+                    // countinue immediately if folder does not exist 
+                    if (! ($collectionData->folder instanceof Syncroton_Model_IFolder)) {
+                        break 2;
+                        
+                    // countinue immediately if syncstate is invalid
+                    } elseif (! ($collectionData->syncState instanceof Syncroton_Model_ISyncState)) {
+                        break 2;
+                        
+                    } else {
+                        $dataController = Syncroton_Data_Factory::factory($collectionData->folder->class , $this->_device, $this->_syncTimeStamp);
+                        
+                        $estimate = $dataController->getCountOfChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState);
+                        
+                        // countinue immediately if there are changes available
+                        if ($estimate > 0) {
+                            break 2;
+                        }
+                    }
+                }
+                
+                // wait some PING_TIMEOUT seconds until neext loop
+                sleep(Syncroton_Command_Ping::PING_TIMEOUT);
+                
+            } while (time() - $intervalStart < $this->_heartbeatInterval);
+        }
+        
         foreach($this->_collections as $collectionData) {
             $collectionChanges = 0;
             
@@ -472,19 +512,15 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                         $this->_logger->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverModifications['added']) . '/' . count($serverModifications['changed']) . '/' . count($serverModifications['deleted'])  . ' entries for sync from server to client');
                 }
                 
-                
-                if (!empty($clientModifications['added']) || !empty($clientModifications['changed']) || !empty($clientModifications['deleted']) ||
-                    !empty($serverModifications['added']) || !empty($serverModifications['changed']) || !empty($serverModifications['deleted'])) {
-                    $collectionData->syncState->counter++;
-                }
-                
 
                 // collection header
                 $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
                 if (!empty($collectionData->folder->class)) {
                     $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData->folder->class));
                 }
-                $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', $collectionData->syncState->counter));
+                
+                $syncKeyNode = $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));
                 
@@ -525,7 +561,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                             
                             $dataController
                                 ->getEntry($collectionData, $serverId)
-                                ->appendXML($applicationData);
+                                ->appendXML($applicationData, $this->_device);
                             
                             $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
                             
@@ -552,7 +588,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                 $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;
                     }
                     
@@ -584,7 +620,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
 
                         $dataController
                             ->getEntry($collectionData, $serverId)
-                            ->appendXML($applicationData);
+                            ->appendXML($applicationData, $this->_device);
                         
                         $commands->appendChild($add);
                         
@@ -600,7 +636,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                         'folder_id'        => $collectionData->folder,
                         'contentid'        => $serverId,
                         'creation_time'    => $this->_syncTimeStamp,
-                        'creation_synckey' => $collectionData->syncState->counter
+                        'creation_synckey' => $collectionData->syncState->counter + 1
                     ));
                     unset($serverModifications['added'][$id]);    
                 }
@@ -609,7 +645,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                  * 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;
                     }
                     
@@ -621,7 +657,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                         
                         $dataController
                             ->getEntry($collectionData, $serverId)
-                            ->appendXML($applicationData);
+                            ->appendXML($applicationData, $this->_device);
                         
 
                         $commands->appendChild($change);
@@ -641,7 +677,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                 $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;
                     }
                     
@@ -665,18 +701,38 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                     unset($serverModifications['deleted'][$id]);
                 }
                 
-                if ($commands->hasChildNodes() === true) {
+                $countOfPendingChanges = (count($serverModifications['added']) + count($serverModifications['changed']) + count($serverModifications['deleted'])); 
+                if ($countOfPendingChanges > 0) {
+                    $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'MoreAvailable'));
+                } else {
+                    $serverModifications = null;
+                }
                     
-                    $countOfPendingChanges = (count($serverModifications['added']) + count($serverModifications['changed']) + count($serverModifications['deleted'])); 
-                    if ($countOfPendingChanges > 0) {
-                        $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'MoreAvailable'));
-                    }
-                
+                if ($commands->hasChildNodes() === true) {
                     $collection->appendChild($commands);
                 }
                 
                 $totalChanges += $collectionChanges;
                 
+                // increase SyncKey if needed
+                if ((
+                        // sent the clients updates?
+                        !empty($clientModifications['added']) ||
+                        !empty($clientModifications['changed']) ||
+                        !empty($clientModifications['deleted'])
+                    ) || (
+                        // sends the server updates to the client?
+                        $commands->hasChildNodes() === true
+                    ) || (
+                        // changed the pending data?
+                        $collectionData->syncState->pendingdata != $serverModifications
+                    )
+                ) {
+                    // then increase SyncKey
+                    $collectionData->syncState->counter++;
+                }
+                $syncKeyNode->appendChild($this->_outputDom->createTextNode($collectionData->syncState->counter));
+                
                 if ($this->_logger instanceof Zend_Log) 
                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " new synckey is ". $collectionData->syncState->counter);
             }
@@ -684,9 +740,6 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
             if (isset($collectionData->syncState) && $collectionData->syncState instanceof Syncroton_Model_ISyncState && 
                 $collectionData->syncState->counter != $collectionData->syncKey) {
                         
-                // increment sync timestamp by 1 second
-                $this->_syncTimeStamp->modify('+1 sec');
-                
                 // store pending data in sync state when needed
                 if(isset($countOfPendingChanges) && $countOfPendingChanges > 0) {
                     $collectionData->syncState->pendingdata = array(
@@ -707,7 +760,9 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                     $keepPreviousSyncKey = true;
                 }
                 
-                $collectionData->syncState->lastsync = $this->_syncTimeStamp;
+                $collectionData->syncState->lastsync = clone $this->_syncTimeStamp;
+                // increment sync timestamp by 1 second
+                $collectionData->syncState->lastsync->modify('+1 sec');
                 
                 try {
                     $transactionId = Syncroton_Registry::getTransactionManager()->startTransaction(Syncroton_Registry::getDatabase());
@@ -718,6 +773,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                     // 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);
                         }
                     }
diff --git a/lib/ext/Syncroton/Data/Contacts.php b/lib/ext/Syncroton/Data/Contacts.php
index 1b8718a..1f191c2 100644
--- a/lib/ext/Syncroton/Data/Contacts.php
+++ b/lib/ext/Syncroton/Data/Contacts.php
@@ -37,24 +37,40 @@ class Syncroton_Data_Contacts extends Syncroton_Data_AData implements Syncroton_
      * (non-PHPdoc)
      * @see Syncroton_Data_IDataSearch::search()
      */
-    public function search($query, $options)
+    public function search(Syncroton_Model_StoreRequest $store)
     {
-        $found = array();
+        $storeResponse = new Syncroton_Model_StoreResponse();
         
         $serverIds = $this->getServerEntries('addressbookFolderId', Syncroton_Command_Sync::FILTER_NOTHING);
         
+        $total = 0;
+        $found = array();
+        
         foreach ($serverIds as $serverId) {
             $contact = $this->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => 'addressbookFolderId')), $serverId);
             
-            if ($contact->firstName == $query) {
+            if ($contact->firstName == $store->query) {
+                $total++;
+                
+                if (count($found) == $store->options['range'][1]+1) {
+                    continue;
+                }
                 $found[] = new Syncroton_Model_StoreResponseResult(array(
                     'longId' => 'addressbookFolderId-' .  $serverId,
-                    'properties' => $this->getSearchEntry('addressbookFolderId-' .  $serverId, $options)
+                    'properties' => $this->getSearchEntry('addressbookFolderId-' .  $serverId, $store->options)
                 ));
             }
         }
         
-        return $found;
+        if (count($found) > 0) {
+            $storeResponse->result = $found;
+            $storeResponse->range = array(0, count($found) - 1);
+            $storeResponse->total = $total;
+        } else {
+            $storeResponse->total = $total;
+        }
+        
+        return $storeResponse;
     }
     
     protected function _initData()
@@ -129,12 +145,57 @@ class Syncroton_Data_Contacts extends Syncroton_Data_AData implements Syncroton_
                         '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/IDataSearch.php b/lib/ext/Syncroton/Data/IDataSearch.php
index 025a9f8..d499efe 100644
--- a/lib/ext/Syncroton/Data/IDataSearch.php
+++ b/lib/ext/Syncroton/Data/IDataSearch.php
@@ -31,10 +31,9 @@ interface Syncroton_Data_IDataSearch
     /**
      * Search command handler
      *
-     * @param array $query   Search query parameters
-     * @param array $options Search options
+     * @param Syncroton_Model_StoreRequest $store   Search query parameters
      *
-     * @return array List of Syncroton_Model_StoreResponseResult
+     * @return Syncroton_Model_StoreResponse
      */
-    public function search($query, $options);
+    public function search(Syncroton_Model_StoreRequest $store);
 }
diff --git a/lib/ext/Syncroton/Model/AEntry.php b/lib/ext/Syncroton/Model/AEntry.php
index 21e2e3c..ed8c496 100644
--- a/lib/ext/Syncroton/Model/AEntry.php
+++ b/lib/ext/Syncroton/Model/AEntry.php
@@ -26,6 +26,10 @@ abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, Iterato
     
     protected $_dateTimeFormat = "Y-m-d\TH:i:s.000\Z";
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Model_IEntry::__construct()
+     */
     public function __construct($properties = null)
     {
         if ($properties instanceof SimpleXMLElement) {
@@ -39,9 +43,9 @@ abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, Iterato
      * (non-PHPdoc)
      * @see Syncroton_Model_IEntry::appendXML()
      */
-    public function appendXML(DOMElement $_domParrent)
+    public function appendXML(DOMElement $domParrent, Syncroton_Model_IDevice $device)
     {
-        $this->_addXMLNamespaces($_domParrent);
+        $this->_addXMLNamespaces($domParrent);
         
         foreach($this->_elements as $elementName => $value) {
             // skip empty values
@@ -55,25 +59,31 @@ abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, Iterato
                 continue;
             }
             
+            $elementVersion = isset($elementProperties['supportedSince']) ? $elementProperties['supportedSince'] : '12.0';
+            
+            if (version_compare($device->acsversion, $elementVersion, '<')) {
+                continue;
+            }
+            
             $nameSpace = 'uri:' . $nameSpace;
             
             if (isset($elementProperties['childElement'])) {
-                $element = $_domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
+                $element = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
                 foreach($value as $subValue) {
-                    $subElement = $_domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementProperties['childElement']));
+                    $subElement = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementProperties['childElement']));
                     
-                    $this->_appendXMLElement($subElement, $elementProperties, $subValue);
+                    $this->_appendXMLElement($device, $subElement, $elementProperties, $subValue);
                     
                     $element->appendChild($subElement);
                     
                 }
-                $_domParrent->appendChild($element);
+                $domParrent->appendChild($element);
             } else {
-                $element = $_domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
+                $element = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
                 
-                $this->_appendXMLElement($element, $elementProperties, $value);
+                $this->_appendXMLElement($device, $element, $elementProperties, $value);
                 
-                $_domParrent->appendChild($element);
+                $domParrent->appendChild($element);
             }
             
         }
@@ -113,6 +123,10 @@ abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, Iterato
         
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Model_IEntry::setFromArray()
+     */
     public function setFromArray(array $properties)
     {
         $this->_elements = array();
@@ -155,45 +169,46 @@ abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, Iterato
     /**
      * add needed xml namespaces to DomDocument
      * 
-     * @param unknown_type $_domParrent
+     * @param unknown_type $domParrent
      */
-    protected function _addXMLNamespaces(DOMElement $_domParrent)
+    protected function _addXMLNamespaces(DOMElement $domParrent)
     {
         foreach($this->_properties as $namespace => $namespaceProperties) {
             // don't add default namespace again
-            if($_domParrent->ownerDocument->documentElement->namespaceURI != 'uri:'.$namespace) {
-                $_domParrent->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:'.$namespace, 'uri:'.$namespace);
+            if($domParrent->ownerDocument->documentElement->namespaceURI != 'uri:'.$namespace) {
+                $domParrent->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:'.$namespace, 'uri:'.$namespace);
             }
         }
     }
     
-    protected function _appendXMLElement(DOMElement $element, $elementProperties, $value)
+    protected function _appendXMLElement(Syncroton_Model_IDevice $device, DOMElement $element, $elementProperties, $value)
     {
         if ($value instanceof Syncroton_Model_IEntry) {
-            $value->appendXML($element);
+            $value->appendXML($element, $device);
         } else {
             if ($value instanceof DateTime) {
                 $value = $value->format($this->_dateTimeFormat);
                 
             } elseif (isset($elementProperties['encoding']) && $elementProperties['encoding'] == 'base64') {
                 if (is_resource($value)) {
-                    stream_filter_append($value, 'convert.base64-encode');
+                    rewind($value);
                     $value = stream_get_contents($value);
-                } else {
-                    $value = base64_encode($value);
                 }
+                $value = base64_encode($value);
             }
             
-            // strip off any non printable control characters
-            if (!ctype_print($value)) {
-                $value = $this->_removeControlChars($value);
-            }
-            
             if ($elementProperties['type'] == 'byteArray') {
-                $element->setAttributeNS('uri:Syncroton', 'Syncroton:encoding', 'oqaque');
-            }
-            
-            $element->appendChild($element->ownerDocument->createTextNode($value));
+                $element->setAttributeNS('uri:Syncroton', 'Syncroton:encoding', 'opaque');
+                // encode to base64; the wbxml encoder will base64_decode it again
+                // this way we can also transport data, which would break the xmlparser otherwise
+                $element->appendChild($element->ownerDocument->createCDATASection(base64_encode($value)));
+            } else {
+                // strip off any non printable control characters
+                if (!ctype_print($value)) {
+                    $value = $this->_removeControlChars($value);
+                }
+                $element->appendChild($element->ownerDocument->createTextNode($value));
+            }
         }
     }
     
@@ -208,7 +223,6 @@ abstract class Syncroton_Model_AEntry implements Syncroton_Model_IEntry, Iterato
         return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', null, $dirty);
     }
     
-    
     /**
      * 
      * @param unknown_type $element
diff --git a/lib/ext/Syncroton/Model/Contact.php b/lib/ext/Syncroton/Model/Contact.php
index 047628f..16485c2 100644
--- a/lib/ext/Syncroton/Model/Contact.php
+++ b/lib/ext/Syncroton/Model/Contact.php
@@ -33,7 +33,7 @@ class Syncroton_Model_Contact extends Syncroton_Model_AEntry
             'body'                   => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody')
         ),
         'Contacts' => array(
-            'alias'                  => array('type' => 'string'),
+            'alias'                  => array('type' => 'string', 'supportedSince' => '14.0'),
             'anniversary'            => array('type' => 'datetime'),
             'assistantName'          => array('type' => 'string'),
             'assistantPhoneNumber'   => array('type' => 'string'),
@@ -82,7 +82,7 @@ class Syncroton_Model_Contact extends Syncroton_Model_AEntry
             'suffix'                 => array('type' => 'string'),
             'title'                  => array('type' => 'string'),
             'webPage'                => array('type' => 'string'),
-            'weightedRank'           => array('type' => 'string'),
+            'weightedRank'           => array('type' => 'string', 'supportedSince' => '14.0'),
             'yomiCompanyName'        => array('type' => 'string'),
             'yomiFirstName'          => array('type' => 'string'),
             'yomiLastName'           => array('type' => 'string'),
diff --git a/lib/ext/Syncroton/Model/Device.php b/lib/ext/Syncroton/Model/Device.php
index 5e382c6..3a0fdd6 100644
--- a/lib/ext/Syncroton/Model/Device.php
+++ b/lib/ext/Syncroton/Model/Device.php
@@ -14,13 +14,12 @@
  *
  * @package     Model
  */
-
 class Syncroton_Model_Device implements Syncroton_Model_IDevice
 {
     const TYPE_IPHONE          = 'iphone';
     const TYPE_WEBOS           = 'webos';
     const TYPE_ANDROID         = 'android';
-    const TYPE_ANDROID_40       = 'android40';
+    const TYPE_ANDROID_40      = 'android40';
     const TYPE_SMASUNGGALAXYS2 = 'samsunggti9100'; // Samsung Galaxy S-3
     
     public function __construct(array $_data = array())
diff --git a/lib/ext/Syncroton/Model/Email.php b/lib/ext/Syncroton/Model/Email.php
index 1e74a70..65cb674 100644
--- a/lib/ext/Syncroton/Model/Email.php
+++ b/lib/ext/Syncroton/Model/Email.php
@@ -33,7 +33,7 @@ class Syncroton_Model_Email extends Syncroton_Model_AEntry
         ),
         'Email' => array(
             'busyStatus'              => array('type' => 'number'),
-            'categories'              => array('type' => 'container', 'childElement' => 'category'),
+            'categories'              => array('type' => 'container', 'childElement' => 'category', 'supportedSince' => '14.0'),
             'cc'                      => array('type' => 'string'),
             'completeTime'            => array('type' => 'datetime'),
             'contentClass'            => array('type' => 'string'),
@@ -42,7 +42,7 @@ class Syncroton_Model_Email extends Syncroton_Model_AEntry
             'displayTo'               => array('type' => 'string'),
             'dTStamp'                 => array('type' => 'datetime'),
             'endTime'                 => array('type' => 'datetime'),
-            'flag'                    => array('type' => 'container'),
+            'flag'                    => array('type' => 'container', 'class' => 'Syncroton_Model_EmailFlag'),
             'from'                    => array('type' => 'string'),
             'globalObjId'             => array('type' => 'string'),
             'importance'              => array('type' => 'number'),
@@ -66,16 +66,16 @@ class Syncroton_Model_Email extends Syncroton_Model_AEntry
             'to'                      => array('type' => 'string'),
         ),
         'Email2' => array(
-            'accountId'             => array('type' => 'string'),
-            'conversationId'        => array('type' => 'byteArray'), // @todo handle this
-            'conversationIndex'     => array('type' => 'byteArray'), // @todo handle this
-            'lastVerbExecuted'      => array('type' => 'number'),
-            'lastVerbExecutionTime' => array('type' => 'datetime'),
-            'meetingMessageType'    => array('type' => 'number'),
-            'receivedAsBcc'         => array('type' => 'number'),
-            'sender'                => array('type' => 'string'),
-            'umCallerID'            => array('type' => 'string'),
-            'umUserNotes'           => array('type' => 'string'),
+            'accountId'             => array('type' => 'string', 'supportedSince' => '14.1'),
+            'conversationId'        => array('type' => 'byteArray', 'supportedSince' => '14.0'),
+            'conversationIndex'     => array('type' => 'byteArray', 'supportedSince' => '14.0'),
+            'lastVerbExecuted'      => array('type' => 'number', 'supportedSince' => '14.0'),
+            'lastVerbExecutionTime' => array('type' => 'datetime', 'supportedSince' => '14.0'),
+            'meetingMessageType'    => array('type' => 'number', 'supportedSince' => '14.1'),
+            'receivedAsBcc'         => array('type' => 'number', 'supportedSince' => '14.0'),
+            'sender'                => array('type' => 'string', 'supportedSince' => '14.0'),
+            'umCallerID'            => array('type' => 'string', 'supportedSince' => '14.0'),
+            'umUserNotes'           => array('type' => 'string', 'supportedSince' => '14.0'),
         ),
     );
 }
diff --git a/lib/ext/Syncroton/Model/EmailAttachment.php b/lib/ext/Syncroton/Model/EmailAttachment.php
index 59f0360..e415ad4 100644
--- a/lib/ext/Syncroton/Model/EmailAttachment.php
+++ b/lib/ext/Syncroton/Model/EmailAttachment.php
@@ -34,8 +34,8 @@ class Syncroton_Model_EmailAttachment extends Syncroton_Model_AEntry
             'method'                  => array('type' => 'string'),
         ),
         'Email2' => array(
-            'umAttDuration'         => array('type' => 'number'),
-            'umAttOrder'            => array('type' => 'number'),
+            'umAttDuration'         => array('type' => 'number', 'supportedSince' => '14.0'),
+            'umAttOrder'            => array('type' => 'number', 'supportedSince' => '14.0'),
         ),
     );
 }
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EmailBody.php b/lib/ext/Syncroton/Model/EmailBody.php
index 92a886b..3b0f16b 100644
--- a/lib/ext/Syncroton/Model/EmailBody.php
+++ b/lib/ext/Syncroton/Model/EmailBody.php
@@ -36,7 +36,7 @@ class Syncroton_Model_EmailBody extends Syncroton_Model_AEntry
             'data'              => array('type' => 'string'),
             'truncated'         => array('type' => 'number'),
             'part'              => array('type' => 'number'),
-            'preview'           => array('type' => 'string'),
+            'preview'           => array('type' => 'string', 'supportedSince' => '14.0'),
         ),
     );
 }
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/IDevice.php b/lib/ext/Syncroton/Model/IDevice.php
index e2c347e..b1177cb 100644
--- a/lib/ext/Syncroton/Model/IDevice.php
+++ b/lib/ext/Syncroton/Model/IDevice.php
@@ -24,8 +24,19 @@
  * @property    string   pinglifetime
  * @property    string   remotewipe
  * @property    string   useragent
+ * @property    string   imei
+ * @property    string   model
+ * @property    string   friendlyname
+ * @property    string   os
+ * @property    string   oslanguage
+ * @property    string   phonenumber
+ * @property    string   pinglifetime
+ * @property    string   pingfolder
+ * @property    string   contactsfilter_id
+ * @property    string   calendarfilter_id
+ * @property    string   tasksfilter_id
+ * @property    string   emailfilter_id
  */
-
 interface Syncroton_Model_IDevice
 {
     /**
diff --git a/lib/ext/Syncroton/Model/IEntry.php b/lib/ext/Syncroton/Model/IEntry.php
index 7d9293d..34f6d53 100644
--- a/lib/ext/Syncroton/Model/IEntry.php
+++ b/lib/ext/Syncroton/Model/IEntry.php
@@ -22,9 +22,22 @@
 
 interface Syncroton_Model_IEntry
 {
+    /**
+     * 
+     * @param unknown_type $properties
+     */
     public function __construct($properties = null);
     
-    public function appendXML(DOMElement $_domParrent);
+    /**
+     * 
+     * @param DOMElement $_domParrent
+     */
+    /**
+     * 
+     * @param DOMElement $_domParrent
+     * @param Syncroton_Model_IDevice $device
+     */
+    public function appendXML(DOMElement $_domParrent, Syncroton_Model_IDevice $device);
     
     /**
      * return array of valid properties
@@ -32,7 +45,11 @@ interface Syncroton_Model_IEntry
      * @return array
      */
     public function getProperties();
-    
+    
+    /**
+     * 
+     * @param array $properties
+     */
     public function setFromArray(array $properties);
     
     /**
diff --git a/lib/ext/Syncroton/Model/StoreRequest.php b/lib/ext/Syncroton/Model/StoreRequest.php
index 170c3cb..f013b33 100644
--- a/lib/ext/Syncroton/Model/StoreRequest.php
+++ b/lib/ext/Syncroton/Model/StoreRequest.php
@@ -83,7 +83,7 @@ class Syncroton_Model_StoreRequest
         } elseif (isset($xmlStore->Query)) {
             if (isset($xmlStore->Query->And)) {
                 if (isset($xmlStore->Query->And->FreeText)) {
-                    $this->_store['query']['and']['freetext'] = (string) $xmlStore->Query->And->FreeText;
+                    $this->_store['query']['and']['freeText'] = (string) $xmlStore->Query->And->FreeText;
                 }
                 if (isset($xmlStore->Query->And->ConversationId)) {
                     $this->_store['query']['and']['conversationId'] = (string) $xmlStore->Query->And->ConversationId;
diff --git a/lib/ext/Syncroton/Model/StoreResponse.php b/lib/ext/Syncroton/Model/StoreResponse.php
index 2708f0f..84fe036 100644
--- a/lib/ext/Syncroton/Model/StoreResponse.php
+++ b/lib/ext/Syncroton/Model/StoreResponse.php
@@ -2,7 +2,8 @@
 /**
  * Syncroton
  *
- * @package     Model
+ * @package     Syncroton
+ * @subpackage  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>
@@ -13,24 +14,28 @@
  *
  * @package    Syncroton
  * @subpackage Model
+ * @property  string  status
+ * @property  array   result
+ * @property  array   range
+ * @property  int     total
  */
 class Syncroton_Model_StoreResponse extends Syncroton_Model_AEntry
 {
     /**
      * status constants
      */
-    const STATUS_SUCCESS          = 1;
-    const STATUS_INVALIDREQUEST   = 2;
-    const STATUS_SERVERERROR      = 3;
-    const STATUS_BADLINK          = 4;
-    const STATUS_ACCESSDENIED     = 5;
-    const STATUS_NOTFOUND         = 6;
-    const STATUS_CONNECTIONFAILED = 7;
-    const STATUS_TOOCOMPLEX       = 8;
-    const STATUS_TIMEDOUT         = 10;
+    const STATUS_SUCCESS            = 1;
+    const STATUS_INVALIDREQUEST     = 2;
+    const STATUS_SERVERERROR        = 3;
+    const STATUS_BADLINK            = 4;
+    const STATUS_ACCESSDENIED       = 5;
+    const STATUS_NOTFOUND           = 6;
+    const STATUS_CONNECTIONFAILED   = 7;
+    const STATUS_TOOCOMPLEX         = 8;
+    const STATUS_TIMEDOUT           = 10;
     const STATUS_FOLDERSYNCREQUIRED = 11;
-    const STATUS_ENDOFRANGE       = 12;
-    const STATUS_ACCESSBLOCKED    = 13;
+    const STATUS_ENDOFRANGE         = 12;
+    const STATUS_ACCESSBLOCKED      = 13;
     const STATUS_CREDENTIALSREQUIRED = 14;
 
     protected $_xmlBaseElement = 'Store';
@@ -44,7 +49,11 @@ class Syncroton_Model_StoreResponse extends Syncroton_Model_AEntry
         )
     );
 
-    public function appendXML(DOMElement $_domParrent)
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Model_AEntry::appendXML()
+     */
+    public function appendXML(DOMElement $_domParrent, Syncroton_Model_IDevice $device)
     {
         $this->_addXMLNamespaces($_domParrent);
 
@@ -62,7 +71,7 @@ class Syncroton_Model_StoreResponse extends Syncroton_Model_AEntry
                 case 'result':
                     foreach ($value as $result) {
                         $element = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Result');
-                        $result->appendXML($element);
+                        $result->appendXML($element, $device);
                         $_domParrent->appendChild($element);
                     }
                     break;
diff --git a/lib/ext/Syncroton/Registry.php b/lib/ext/Syncroton/Registry.php
index c971456..6903c5d 100644
--- a/lib/ext/Syncroton/Registry.php
+++ b/lib/ext/Syncroton/Registry.php
@@ -35,6 +35,8 @@ class Syncroton_Registry extends ArrayObject
     const TASKS_DATA_CLASS    = 'tasks_data_class';
     const GAL_DATA_CLASS      = 'gal_data_class';
     
+    const DEFAULT_POLICY      = 'default_policy';
+    
     const DATABASE            = 'database';
     const TRANSACTIONMANAGER  = 'transactionmanager';
     
diff --git a/lib/ext/Syncroton/Server.php b/lib/ext/Syncroton/Server.php
index 2a9555a..7cd61bb 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -335,7 +335,8 @@ class Syncroton_Server
                 'deviceid'   => $requestParameters['deviceId'],
                 'devicetype' => $requestParameters['deviceType'],
                 'useragent'  => $requestParameters['userAgent'],
-                'acsversion' => $requestParameters['protocolVersion']
+                'acsversion' => $requestParameters['protocolVersion'],
+                'policyId'   => Syncroton_Registry::isRegistered(Syncroton_Registry::DEFAULT_POLICY) ? Syncroton_Registry::get(Syncroton_Registry::DEFAULT_POLICY) : null
             )));
         }
         
diff --git a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage1.php b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage1.php
index 9badae6..699638e 100644
--- a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage1.php
+++ b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage1.php
@@ -23,63 +23,63 @@ class Syncroton_Wbxml_Dtd_ActiveSync_CodePage1 extends Syncroton_Wbxml_Dtd_Activ
     protected $_codePageName    = 'Contacts';
         
     protected $_tags = array(     
-        'Anniversary'           => 0x05,
-        'AssistantName'         => 0x06,
-        'AssistnamePhoneNumber' => 0x07,
-        'Birthday'              => 0x08,
-        'Body'                  => 0x09,
-        'BodySize'              => 0x0a,
-        'BodyTruncated'         => 0x0b,
-        'Business2PhoneNumber'  => 0x0c,
-        'BusinessAddressCity'   => 0x0d,
-        'BusinessAddressCountry' => 0x0e,
+        'Anniversary'               => 0x05,
+        'AssistantName'             => 0x06,
+        'AssistantPhoneNumber'      => 0x07,
+        'Birthday'                  => 0x08,
+        'Body'                      => 0x09,
+        'BodySize'                  => 0x0a,
+        'BodyTruncated'             => 0x0b,
+        'Business2PhoneNumber'      => 0x0c,
+        'BusinessAddressCity'       => 0x0d,
+        'BusinessAddressCountry'    => 0x0e,
         'BusinessAddressPostalCode' => 0x0f,
-        'BusinessAddressState'  => 0x10,
-        'BusinessAddressStreet' => 0x11,
-        'BusinessFaxNumber'     => 0x12,
-        'BusinessPhoneNumber'   => 0x13,
-        'CarPhoneNumber'        => 0x14,
-        'Categories'            => 0x15,
-        'Category'              => 0x16,
-        'Children'              => 0x17,
-        'Child'                 => 0x18,
-        'CompanyName'           => 0x19,
-        'Department'            => 0x1a,
-        'Email1Address'         => 0x1b,
-        'Email2Address'         => 0x1c,
-        'Email3Address'         => 0x1d,
-        'FileAs'                => 0x1e,
-        'FirstName'             => 0x1f,
-        'Home2PhoneNumber'      => 0x20,
-        'HomeAddressCity'       => 0x21,
-        'HomeAddressCountry'    => 0x22,
-        'HomeAddressPostalCode' => 0x23,
-        'HomeAddressState'      => 0x24,
-        'HomeAddressStreet'     => 0x25,
-        'HomeFaxNumber'         => 0x26,
-        'HomePhoneNumber'       => 0x27,
-        'JobTitle'              => 0x28,
-        'LastName'              => 0x29,
-        'MiddleName'            => 0x2a,
-        'MobilePhoneNumber'     => 0x2b,
-        'OfficeLocation'        => 0x2c,
-        'OtherAddressCity'      => 0x2d,
-        'OtherAddressCountry'   => 0x2e,
+        'BusinessAddressState'   => 0x10,
+        'BusinessAddressStreet'  => 0x11,
+        'BusinessFaxNumber'      => 0x12,
+        'BusinessPhoneNumber'    => 0x13,
+        'CarPhoneNumber'         => 0x14,
+        'Categories'             => 0x15,
+        'Category'               => 0x16,
+        'Children'               => 0x17,
+        'Child'                  => 0x18,
+        'CompanyName'            => 0x19,
+        'Department'             => 0x1a,
+        'Email1Address'          => 0x1b,
+        'Email2Address'          => 0x1c,
+        'Email3Address'          => 0x1d,
+        'FileAs'                 => 0x1e,
+        'FirstName'              => 0x1f,
+        'Home2PhoneNumber'       => 0x20,
+        'HomeAddressCity'        => 0x21,
+        'HomeAddressCountry'     => 0x22,
+        'HomeAddressPostalCode'  => 0x23,
+        'HomeAddressState'       => 0x24,
+        'HomeAddressStreet'      => 0x25,
+        'HomeFaxNumber'          => 0x26,
+        'HomePhoneNumber'        => 0x27,
+        'JobTitle'               => 0x28,
+        'LastName'               => 0x29,
+        'MiddleName'             => 0x2a,
+        'MobilePhoneNumber'      => 0x2b,
+        'OfficeLocation'         => 0x2c,
+        'OtherAddressCity'       => 0x2d,
+        'OtherAddressCountry'    => 0x2e,
         'OtherAddressPostalCode' => 0x2f,
-        'OtherAddressState'     => 0x30,
-        'OtherAddressStreet'    => 0x31,
-        'PagerNumber'           => 0x32,
-        'RadioPhoneNumber'      => 0x33,
-        'Spouse'                => 0x34,
-        'Suffix'                => 0x35,
-        'Title'                 => 0x36,
-        'WebPage'               => 0x37,
-        'YomiCompanyName'       => 0x38,
-        'YomiFirstName'         => 0x39,
-        'YomiLastName'          => 0x3a,
-        'Rtf'                   => 0x3b,
-        'Picture'               => 0x3c,
-        'Alias'                 => 0x3d,
-        'WeightedRank'          => 0x3e
+        'OtherAddressState'      => 0x30,
+        'OtherAddressStreet'     => 0x31,
+        'PagerNumber'            => 0x32,
+        'RadioPhoneNumber'       => 0x33,
+        'Spouse'                 => 0x34,
+        'Suffix'                 => 0x35,
+        'Title'                  => 0x36,
+        'WebPage'                => 0x37,
+        'YomiCompanyName'        => 0x38,
+        'YomiFirstName'          => 0x39,
+        'YomiLastName'           => 0x3a,
+        'Rtf'                    => 0x3b,
+        'Picture'                => 0x3c,
+        'Alias'                  => 0x3d,
+        'WeightedRank'           => 0x3e
     );
 }
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Wbxml/Encoder.php b/lib/ext/Syncroton/Wbxml/Encoder.php
index 667cfec..746ea67 100644
--- a/lib/ext/Syncroton/Wbxml/Encoder.php
+++ b/lib/ext/Syncroton/Wbxml/Encoder.php
@@ -292,9 +292,9 @@ class Syncroton_Wbxml_Encoder extends Syncroton_Wbxml_Abstract
         // handle the tag
         $identity = $this->_codePage->getIdentity($_tag);
         
-        if (is_array($_attributes) && isset($_attributes['Syncroton:encoding'])) {
+        if (is_array($_attributes) && isset($_attributes['uri:Syncroton;encoding'])) {
             $encoding = 'opaque';
-            unset($_attributes['Syncroton:encoding']);
+            unset($_attributes['uri:Syncroton;encoding']);
         } else {
             $encoding = 'termstring';
         }
@@ -312,7 +312,7 @@ class Syncroton_Wbxml_Encoder extends Syncroton_Wbxml_Abstract
         // handle the data
         if($_data !== NULL) {
             if ($encoding == 'opaque') {
-                $this->_writeOpaqueString($_data);
+                $this->_writeOpaqueString(base64_decode($_data));
             } else {
                 $this->_writeTerminatedString($_data);
             }
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
index cebfdfb..a041d7f 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -336,7 +336,7 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
     /**
      * Returns properties of a message for Search response
      *
-     * @param string $longId Message identifier
+     * @param string $longId  Message identifier
      * @param array  $options Search options
      *
      * @return Syncroton_Model_Email Email object
@@ -787,17 +787,16 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
     /**
      * ActiveSync Search handler
      *
-     * @param array $query   Search query parameters
-     * @param array $options Search options
+     * @param Syncroton_Model_StoreRequest $store Search query
      *
-     * @return array List of Syncroton_Model_StoreResponseResult objects
+     * @return Syncroton_Model_StoreResponse Complete Search response
      */
-    public function search($query, $options)
+    public function search(Syncroton_Model_StoreRequest $store)
     {
-        list ($folders, $search_str) = $this->parse_search_query($query, $options);
+        list($folders, $search_str) = $this->parse_search_query($store);
 
         if (empty($search_str)) {
-            return array();
+            throw new Exception('Empty/invalid search request');
         }
 
         $result = array();
@@ -838,48 +837,76 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
             }
         }
 
-        return array_values($result);
+        $result   = array_values($result);
+        $response = new Syncroton_Model_StoreResponse();
+
+        // Calculate requested range
+        $start = (int) $store->options['range'][0];
+        $limit = (int) $store->options['range'][1] + 1;
+        $total = count($result);
+        $response->total = $total;
+
+        // Get requested chunk of data set
+        if ($total) {
+            if ($start > $total) {
+                $start = $total;
+            }
+            if ($limit > $total) {
+                $limit = max($start+1, $total);
+            }
+            if ($start > 0 || $limit < $total) {
+                $result = array_slice($result, $start, $limit-$start);
+            }
+
+            $response->range = array($start, $start + count($result) - 1);
+        }
+
+        // Build result array, convert to ActiveSync format
+        foreach ($result as $idx => $rec) {
+            $rec->properties    = $this->getSearchEntry($rec->longId, $store->options);
+            $response->result[] = $rec;
+            unset($result[$idx]);
+        }
+
+        return $response;
     }
 
     /**
      * Converts ActiveSync search parameters into IMAP search string
      */
-    protected function parse_search_query($query, $options)
+    protected function parse_search_query($store)
     {
-        if (empty($query)) {
-            return array();
-        }
-
+        $options    = $store->options;
+        $query      = $store->query;
         $search_str = '';
         $folders    = array();
 
-        if (!is_array($query)) {
-            $search = $query;
+        if (empty($query) || !is_array($query)) {
+            return array();
         }
-        else {
-            if (isset($query['and']['freeText']) && strlen($query['and']['freeText'])) {
-                $search = $query['and']['freeText'];
-            }
 
-            if (!empty($query['and']['collections'])) {
-                foreach ($query['and']['collections'] as $collection) {
-                    $folders = array_merge($folders, $this->extractFolders($collection));
-                }
-            }
+        if (isset($query['and']['freeText']) && strlen($query['and']['freeText'])) {
+            $search = $query['and']['freeText'];
+        }
 
-            if (!empty($query['and']['greaterThan'])
-                && !empty($query['and']['greaterThan']['dateReceived'])
-                && !empty($query['and']['greaterThan']['value'])
-            ) {
-                $search_str .= ' SINCE ' . $query['and']['greaterThan']['value']->format('d-M-Y');
+        if (!empty($query['and']['collections'])) {
+            foreach ($query['and']['collections'] as $collection) {
+                $folders = array_merge($folders, $this->extractFolders($collection));
             }
+        }
 
-            if (!empty($query['and']['lessThan'])
-                && !empty($query['and']['lessThan']['dateReceived'])
-                && !empty($query['and']['lessThan']['value'])
-            ) {
-                $search_str .= ' BEFORE ' . $query['and']['lessThan']['value']->format('d-M-Y');
-            }
+        if (!empty($query['and']['greaterThan'])
+            && !empty($query['and']['greaterThan']['dateReceived'])
+            && !empty($query['and']['greaterThan']['value'])
+        ) {
+            $search_str .= ' SINCE ' . $query['and']['greaterThan']['value']->format('d-M-Y');
+        }
+
+        if (!empty($query['and']['lessThan'])
+            && !empty($query['and']['lessThan']['dateReceived'])
+            && !empty($query['and']['lessThan']['value'])
+        ) {
+            $search_str .= ' BEFORE ' . $query['and']['lessThan']['value']->format('d-M-Y');
         }
 
         if ($search !== null) {
diff --git a/lib/kolab_sync_data_gal.php b/lib/kolab_sync_data_gal.php
index d7bb2e8..ed47c46 100644
--- a/lib/kolab_sync_data_gal.php
+++ b/lib/kolab_sync_data_gal.php
@@ -124,16 +124,15 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat
     }
 
     /**
-     * Returns properties of a message for Search response
+     * Returns properties of a contact for Search response
      *
-     * @param string $longId Message identifier
-     * @param array  $options Search options
+     * @param array $data    Contact data
+     * @param array $options Search options
      *
-     * @return Syncroton_Model_Email Email object
+     * @return Syncroton_Model_GAL Contact (GAL) object
      */
-    public function getSearchEntry($longId, $options)
+    public function getSearchEntry($data, $options)
     {
-        $data   = $this->getObject($longId);
         $result = array();
 
         // Contacts namespace fields
@@ -175,15 +174,18 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat
     /**
      * ActiveSync Search handler
      *
-     * @param string|array $query   Search query parameters
-     * @param array        $options Search options
+     * @param Syncroton_Model_StoreRequest $store Search query parameters
      *
-     * @return array List of Syncroton_Model_StoreResponseResult objects
+     * @return Syncroton_Model_StoreResponse Complete Search response
+     * @throws Exception
      */
-    public function search($query, $options)
+    public function search(Syncroton_Model_StoreRequest $store)
     {
+        $options  = $store->options;
+        $query    = $store->query;
+
         if (empty($query) || !is_string($query)) {
-            return array();
+            throw new Exception('Empty/invalid search request');
         }
 
         $records = array();
@@ -234,30 +236,41 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat
         // sort the records
         ksort($records, SORT_LOCALE_STRING);
 
-        $result = array();
+        $records  = array_values($records);
+        $response = new Syncroton_Model_StoreResponse();
+
+        // Calculate requested range
+        $start = (int) $options['range'][0];
+        $limit = (int) $options['range'][1] + 1;
+        $total = count($records);
+        $response->total = $total;
+
+        // Get requested chunk of data set
+        if ($total) {
+            if ($start > $total) {
+                $start = $total;
+            }
+            if ($limit > $total) {
+                $limit = max($start+1, $total);
+            }
+
+            if ($start > 0 || $limit < $total) {
+                $records = array_slice($records, $start, $limit-$start);
+            }
+
+            $response->range = array($start, $start + count($records) - 1);
+        }
+
+        // Build result array, convert to ActiveSync format
         foreach ($records as $idx => $rec) {
-            $longId = $rec['ID'];
-            $result[] = new Syncroton_Model_StoreResponseResult(array(
-                'longId'       => $longId,
+            $response->result[] = new Syncroton_Model_StoreResponseResult(array(
+                'longId'     => $rec['ID'],
+                'properties' => $this->getSearchEntry($rec, $options),
             ));
-
-            $this->result[$longId] = $rec;
             unset($records[$idx]);
         }
 
-        return $result;
-    }
-
-    /**
-     * Return contact object from LDAP
-     *
-     * @param string $id Contact identifier
-     *
-     * @return array Object data
-     */
-    protected function getObject($id)
-    {
-        return $this->result[$id];
+        return $response;
     }
 
     /**


commit 9c1bfe24c4de02ded9141a222414e91d4b1049aa
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Sep 4 12:55:22 2012 +0200

    Add References header setting on mail reply

diff --git a/lib/kolab_sync_backend.php b/lib/kolab_sync_backend.php
index 29235a1..440f982 100644
--- a/lib/kolab_sync_backend.php
+++ b/lib/kolab_sync_backend.php
@@ -890,6 +890,7 @@ class kolab_sync_backend
             'cc'      => 'Cc',
             'bcc'     => 'Bcc',
             'message-id'   => 'Message-ID',
+            'references'   => 'References',
             'content-type' => 'Content-Type',
             'content-transfer-encoding' => 'Content-Transfer-Encoding',
         );
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
index 22207a0..cebfdfb 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -694,6 +694,11 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
             $body = rtrim($body) . "\n" . ltrim($message_body);
         }
 
+        // Add References header
+        if (empty($headers['References'])) {
+            $headers['References'] = trim($message->headers->references . ' ' . $message->headers->messageID);
+        }
+
         // Create complete message source
         $body = $this->backend->build_mime($headers, $body);
 





More information about the commits mailing list