2 commits - docs/SQL lib/ext lib/kolab_sync_backend.php lib/kolab_sync_data_email.php lib/kolab_sync_data.php

Aleksander Machniak machniak at kolabsys.com
Sun Apr 7 14:08:23 CEST 2013


 docs/SQL/mysql.initial.sql                            |   45 --
 docs/SQL/mysql/2013040700.sql                         |    9 
 lib/ext/Syncroton/Backend/ABackend.php                |   39 ++
 lib/ext/Syncroton/Backend/Policy.php                  |   46 ++
 lib/ext/Syncroton/Command/FolderSync.php              |   31 +
 lib/ext/Syncroton/Command/GetItemEstimate.php         |    2 
 lib/ext/Syncroton/Command/ItemOperations.php          |  167 ++++++---
 lib/ext/Syncroton/Command/Ping.php                    |    7 
 lib/ext/Syncroton/Command/Provision.php               |   10 
 lib/ext/Syncroton/Command/Settings.php                |    6 
 lib/ext/Syncroton/Command/Sync.php                    |  322 +++++++++++++-----
 lib/ext/Syncroton/Command/Wbxml.php                   |    2 
 lib/ext/Syncroton/Data/AData.php                      |  133 ++++++-
 lib/ext/Syncroton/Data/IData.php                      |   23 +
 lib/ext/Syncroton/Exception/MemoryExhausted.php       |   20 +
 lib/ext/Syncroton/Model/AXMLEntry.php                 |   31 +
 lib/ext/Syncroton/Model/Content.php                   |   13 
 lib/ext/Syncroton/Model/Event.php                     |   90 ++---
 lib/ext/Syncroton/Model/EventException.php            |    6 
 lib/ext/Syncroton/Model/IContent.php                  |    1 
 lib/ext/Syncroton/Model/ISyncState.php                |    1 
 lib/ext/Syncroton/Model/SyncCollection.php            |    5 
 lib/ext/Syncroton/Model/SyncState.php                 |   13 
 lib/ext/Syncroton/Server.php                          |   20 -
 lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php |   31 +
 lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php  |    6 
 lib/kolab_sync_backend.php                            |   15 
 lib/kolab_sync_data.php                               |  145 +++++++-
 lib/kolab_sync_data_email.php                         |    6 
 29 files changed, 937 insertions(+), 308 deletions(-)

New commits:
commit f045cc4a793d1b08fb849f9f8923ad03bddd394b
Author: Aleksander Machniak <alec at alec.pl>
Date:   Sun Apr 7 14:01:39 2013 +0200

    Fix redundant "validate" device on device registration (Bug #1109)

diff --git a/lib/kolab_sync_backend.php b/lib/kolab_sync_backend.php
index 0498759..99a1257 100644
--- a/lib/kolab_sync_backend.php
+++ b/lib/kolab_sync_backend.php
@@ -371,6 +371,12 @@ class kolab_sync_backend
         // Fill local cache
         $this->devices_list();
 
+        // Some devices create dummy devices with name "validate" (#1109)
+        // This device entry is used in two initial requests, but later
+        // the device registers a real name. We can remove this dummy entry
+        // on new device creation
+        $this->device_delete('validate');
+
         // Old Kolab_ZPush device parameters
         // MODE:  -1 | 0 | 1  (not set | flatmode | foldermode)
         // TYPE:  device type string
@@ -423,7 +429,14 @@ class kolab_sync_backend
         return $result;
     }
 
-
+    /**
+     * Device update.
+     *
+     * @param array  $device  Device data
+     * @param string $id      Device ID
+     *
+     * @return bool True on success, False on failure
+     */
     public function device_update($device, $id)
     {
         $devices_list = $this->devices_list();


commit ac10637a95996bf6dbd717218a4b01b8a84f9e89
Author: Aleksander Machniak <alec at alec.pl>
Date:   Sun Apr 7 13:28:34 2013 +0200

    Update Syncroton

diff --git a/docs/SQL/mysql.initial.sql b/docs/SQL/mysql.initial.sql
index b918f12..4f6ebcf 100644
--- a/docs/SQL/mysql.initial.sql
+++ b/docs/SQL/mysql.initial.sql
@@ -3,50 +3,9 @@ CREATE TABLE IF NOT EXISTS `syncroton_policy` (
     `name` varchar(255) NOT NULL,
     `description` varchar(255) DEFAULT NULL,
     `policy_key` varchar(64) NOT NULL,
-    `allow_bluetooth` int(11) DEFAULT NULL,
-    `allow_browser` int(11) DEFAULT NULL,
-    `allow_camera` int(11) DEFAULT NULL,
-    `allow_consumer_email` int(11) DEFAULT NULL,
-    `allow_desktop_sync` int(11) DEFAULT NULL,
-    `allow_h_t_m_l_email` int(11) DEFAULT NULL,
-    `allow_internet_sharing` int(11) DEFAULT NULL,
-    `allow_ir_d_a` int(11) DEFAULT NULL,
-    `allow_p_o_p_i_m_a_p_email` int(11) DEFAULT NULL,
-    `allow_remote_desktop` int(11) DEFAULT NULL,
-    `allow_simple_device_password` int(11) DEFAULT NULL,
-    `allow_s_m_i_m_e_encryption_algorithm_negotiation` int(11) DEFAULT NULL,
-    `allow_s_m_i_m_e_soft_certs` int(11) DEFAULT NULL,
-    `allow_storage_card` int(11) DEFAULT NULL,
-    `allow_text_messaging` int(11) DEFAULT NULL,
-    `allow_unsigned_applications` int(11) DEFAULT NULL,
-    `allow_unsigned_installation_packages` int(11) DEFAULT NULL,
-    `allow_wifi` int(11) DEFAULT NULL,
-    `alphanumeric_device_password_required` int(11) DEFAULT NULL,
-    `approved_application_list` varchar(255) DEFAULT NULL,
-    `attachments_enabled` int(11) DEFAULT NULL,
-    `device_password_enabled` int(11) DEFAULT NULL,
-    `device_password_expiration` int(11) DEFAULT NULL,
-    `device_password_history` int(11) DEFAULT NULL,
-    `max_attachment_size` int(11) DEFAULT NULL,
-    `max_calendar_age_filter` int(11) DEFAULT NULL,
-    `max_device_password_failed_attempts` int(11) DEFAULT NULL,
-    `max_email_age_filter` int(11) DEFAULT NULL,
-    `max_email_body_truncation_size` int(11) DEFAULT NULL,
-    `max_email_h_t_m_l_body_truncation_size` int(11) DEFAULT NULL,
-    `max_inactivity_time_device_lock` int(11) DEFAULT NULL,
-    `min_device_password_complex_characters` int(11) DEFAULT NULL,
-    `min_device_password_length` int(11) DEFAULT NULL,
-    `password_recovery_enabled` int(11) DEFAULT NULL,
-    `require_device_encryption` int(11) DEFAULT NULL,
-    `require_encrypted_s_m_i_m_e_messages` int(11) DEFAULT NULL,
-    `require_encryption_s_m_i_m_e_algorithm` int(11) DEFAULT NULL,
-    `require_manual_sync_when_roaming` int(11) DEFAULT NULL,
-    `require_signed_s_m_i_m_e_algorithm` int(11) DEFAULT NULL,
-    `require_signed_s_m_i_m_e_messages` int(11) DEFAULT NULL,
-    `require_storage_card_encryption` int(11) DEFAULT NULL,
-    `unapproved_in_r_o_m_application_list` varchar(255) DEFAULT NULL,
+    `json_policy` blob NOT NULL,
     PRIMARY KEY (`id`)
-) ENGINE=InnoDB;
+);
 
 CREATE TABLE IF NOT EXISTS `syncroton_device` (
     `id` varchar(40) NOT NULL,
diff --git a/docs/SQL/mysql/2013040700.sql b/docs/SQL/mysql/2013040700.sql
new file mode 100644
index 0000000..501d3d9
--- /dev/null
+++ b/docs/SQL/mysql/2013040700.sql
@@ -0,0 +1,9 @@
+DROP TABLE IF EXISTS `syncroton_policy`;
+CREATE TABLE IF NOT EXISTS `syncroton_policy` (
+    `id` varchar(40) NOT NULL,
+    `name` varchar(255) NOT NULL,
+    `description` varchar(255) DEFAULT NULL,
+    `policy_key` varchar(64) NOT NULL,
+    `json_policy` blob NOT NULL,
+    PRIMARY KEY (`id`)
+);
diff --git a/lib/ext/Syncroton/Backend/ABackend.php b/lib/ext/Syncroton/Backend/ABackend.php
index d345d6d..6c50e1c 100644
--- a/lib/ext/Syncroton/Backend/ABackend.php
+++ b/lib/ext/Syncroton/Backend/ABackend.php
@@ -13,7 +13,6 @@
  *
  * @package     Backend
  */
- 
 abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
 {
     /**
@@ -31,6 +30,12 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
     
     protected $_modelInterfaceName;
     
+    /**
+     * the constructor
+     * 
+     * @param  Zend_Db_Adapter_Abstract  $_db
+     * @param  string                    $_tablePrefix
+     */
     public function __construct(Zend_Db_Adapter_Abstract $_db, $_tablePrefix = 'Syncroton_')
     {
         $this->_db          = $_db;
@@ -58,6 +63,12 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
         return $this->get($data['id']);
     }
     
+    /**
+     * convert iteratable object to array
+     * 
+     * @param  unknown   $model
+     * @return array
+     */
     protected function _convertModelToArray($model)
     {
         $data = array();
@@ -99,6 +110,12 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
         return $this->_getObject($data);
     }
     
+    /**
+     * convert array to object
+     * 
+     * @param  array  $data
+     * @return object
+     */
     protected function _getObject($data)
     {
         foreach ($data as $key => $value) {
@@ -114,6 +131,10 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
         return new $this->_modelClassName($data);
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Backend_IBackend::delete()
+     */
     public function delete($id)
     {
         $id = $id instanceof $this->_modelInterfaceName ? $id->id : $id;
@@ -123,6 +144,10 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
         return (bool) $result;
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Backend_IBackend::update()
+     */
     public function update($model)
     {
         if (! $model instanceof $this->_modelInterfaceName) {
@@ -138,6 +163,11 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
         return $this->get($model->id);
     }
 
+    /**
+     * convert from camelCase to camel_case
+     * @param  string  $string
+     * @return string
+     */
     protected function _fromCamelCase($string)
     {
         $string = lcfirst($string);
@@ -145,6 +175,13 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
         return preg_replace_callback('/([A-Z])/', function ($string) {return '_' . strtolower($string[0]);}, $string);
     }
     
+    /**
+     * convert from camel_case to camelCase
+     * 
+     * @param  string $string
+     * @param  bool   $ucFirst
+     * @return string
+     */
     protected function _toCamelCase($string, $ucFirst = true)
     {
         if ($ucFirst === true) {
diff --git a/lib/ext/Syncroton/Backend/Policy.php b/lib/ext/Syncroton/Backend/Policy.php
index 5163359..bb62e6d 100644
--- a/lib/ext/Syncroton/Backend/Policy.php
+++ b/lib/ext/Syncroton/Backend/Policy.php
@@ -21,4 +21,50 @@ class Syncroton_Backend_Policy extends Syncroton_Backend_ABackend #implements Sy
     protected $_modelClassName = 'Syncroton_Model_Policy';
     
     protected $_modelInterfaceName = 'Syncroton_Model_IPolicy';
+    
+    /**
+     * convert iteratable object to array
+     * 
+     * @param  unknown   $model
+     * @return array
+     */
+    protected function _convertModelToArray($model)
+    {
+        $policyValues = $model->getProperties('Provision');
+        
+        $policy = array();
+        
+        foreach ($policyValues as $policyName) {
+            if ($model->$policyName !== NULL) { 
+                $policy[$policyName] = $model->$policyName;
+            }
+            
+            unset($model->$policyName);
+        }
+
+        $data = parent::_convertModelToArray($model);
+        
+        $data['json_policy'] = Zend_Json::encode($policy);
+        
+        return $data;
+    }
+    
+    /**
+     * convert array to object
+     * 
+     * @param  array  $data
+     * @return object
+     */
+    protected function _getObject($data)
+    {
+        $policy = Zend_Json::decode($data['json_policy']);
+        
+        foreach ($policy as $policyKey => $policyValue) {
+            $data[$policyKey] = $policyValue;
+        }
+        
+        unset($data['json_policy']);
+        
+        return parent::_getObject($data);
+    }
 }
diff --git a/lib/ext/Syncroton/Command/FolderSync.php b/lib/ext/Syncroton/Command/FolderSync.php
index 84e761e..87d9d3f 100644
--- a/lib/ext/Syncroton/Command/FolderSync.php
+++ b/lib/ext/Syncroton/Command/FolderSync.php
@@ -121,7 +121,8 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
             $this->_headers = array_merge($this->_headers, $optionsCommand->getHeaders());
         }
         
-        $adds = array();
+        $adds    = array();
+        $updates = array();
         $deletes = array();
 
         foreach($this->_classes as $class) {
@@ -138,6 +139,13 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
                 // retrieve all folders available in data backend
                 $serverFolders = $dataController->getAllFolders();
 
+                if ($this->_syncState->counter > 0) {
+                    // retrieve all folders changed since last sync
+                    $changedFolders = $dataController->getChangedFolders($this->_syncState->lastsync, $this->_syncTimeStamp);
+                } else {
+                    $changedFolders = array();
+                }
+                
                 // retrieve all folders sent to client
                 $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
                 
@@ -186,6 +194,17 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
                 $adds[] = $add;
             }
 
+            // calculate changed entries
+            foreach ($changedFolders as $changedFolder) {
+                $change = $clientFolders[$changedFolder->serverId];
+                
+                $change->displayName = $changedFolder->displayName;
+                $change->parentId    = $changedFolder->parentId;
+                $change->type        = $changedFolder->type;
+                
+                $updates[] = $change;
+            }
+            
             // calculate deleted entries
             $serverDiff = array_diff($clientFoldersIds, $serverFoldersIds);
             foreach ($serverDiff as $serverFolderId) {
@@ -195,7 +214,7 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
 
         $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_SUCCESS));
 
-        $count = count($adds) + /*count($changes) + */count($deletes);
+        $count = count($adds) + count($updates) + count($deletes);
         if($count > 0) {
             $this->_syncState->counter++;
             $this->_syncState->lastsync = $this->_syncTimeStamp;
@@ -218,6 +237,14 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
             }
         }
         
+        foreach($updates as $folder) {
+            $update = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Update'));
+            
+            $folder->appendXML($update, $this->_device);
+            
+            $this->_folderBackend->update($folder);
+        }
+        
         foreach($deletes as $folder) {
             $delete = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Delete'));
             $delete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder->serverId));
diff --git a/lib/ext/Syncroton/Command/GetItemEstimate.php b/lib/ext/Syncroton/Command/GetItemEstimate.php
index 5eb0f4c..c1ff330 100644
--- a/lib/ext/Syncroton/Command/GetItemEstimate.php
+++ b/lib/ext/Syncroton/Command/GetItemEstimate.php
@@ -137,7 +137,7 @@ class Syncroton_Command_GetItemEstimate extends Syncroton_Command_Wbxml
             }
             
             // folderState can be NULL in case of not existing folder
-            if (isset($collectionData['folder'])) {
+            if (isset($collectionData['folder']) && $collectionData['folder']->isDirty()) {
                 $this->_folderBackend->update($collectionData['folder']);
             }
         }
diff --git a/lib/ext/Syncroton/Command/ItemOperations.php b/lib/ext/Syncroton/Command/ItemOperations.php
index c3ee287..93aa722 100644
--- a/lib/ext/Syncroton/Command/ItemOperations.php
+++ b/lib/ext/Syncroton/Command/ItemOperations.php
@@ -34,6 +34,13 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
     protected $_fetches = array();
     
     /**
+     * list of folder to empty
+     * 
+     * @var array
+     */
+    protected $_emptyFolderContents = array();
+    
+    /**
      * parse MoveItems request
      *
      */
@@ -43,59 +50,13 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
         
         if (isset($xml->Fetch)) {
             foreach ($xml->Fetch as $fetch) {
-                $fetchArray = array(
-                    'store' => (string)$fetch->Store,
-                    'options' => array()
-                );
-                
-                // try to fetch element from namespace AirSync
-                $airSync = $fetch->children('uri:AirSync');
-                
-                if (isset($airSync->CollectionId)) {
-                    $fetchArray['collectionId'] = (string)$airSync->CollectionId;
-                    $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');
-                
-                if (isset($airSyncBase->FileReference)) {
-                    $fetchArray['fileReference'] = (string)$airSyncBase->FileReference;
-                }
-                
-                if (isset($fetch->Options)) {
-                    // try to fetch element from namespace AirSyncBase
-                    $airSyncBase = $fetch->Options->children('uri:AirSyncBase');
-                    
-                    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;
+                $this->_fetches[] = $this->_handleFetch($fetch);
+            }
+        }
+        
+        if (isset($xml->EmptyFolderContents)) {
+            foreach ($xml->EmptyFolderContents as $emptyFolderContents) {
+                $this->_emptyFolderContents[] = $this->_handleEmptyFolderContents($emptyFolderContents);
             }
         }
         
@@ -187,6 +148,106 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
             }
         }
         
+        foreach ($this->_emptyFolderContents as $emptyFolderContents) {
+            
+            try {
+                $folder = $this->_folderBackend->getFolder($this->_device, $emptyFolderContents['collectionId']);
+
+                $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
+                $dataController->emptyFolderContents($emptyFolderContents['collectionId'], $emptyFolderContents['options']);
+
+                $status = Syncroton_Command_ItemOperations::STATUS_SUCCESS;
+            }
+            catch (Syncroton_Exception_Status_ItemOperations $e) {
+                $status = $e->getCode();
+            }
+            catch (Exception $e) {
+                $status = Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR;
+            }
+
+            $emptyFolderContentsTag = $this->_outputDom->createElementNS('uri:ItemOperations', 'EmptyFolderContents');
+            
+            $emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', $status));
+            $emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $emptyFolderContents['collectionId']));
+            
+            $response->appendChild($emptyFolderContentsTag);
+        }
+        
         return $this->_outputDom;
     }
+    
+    protected function _handleFetch(SimpleXMLElement $fetch)
+    {
+        $fetchArray = array(
+            'store' => (string)$fetch->Store,
+            'options' => array()
+        );
+        
+        // try to fetch element from namespace AirSync
+        $airSync = $fetch->children('uri:AirSync');
+        
+        if (isset($airSync->CollectionId)) {
+            $fetchArray['collectionId'] = (string)$airSync->CollectionId;
+            $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');
+        
+        if (isset($airSyncBase->FileReference)) {
+            $fetchArray['fileReference'] = (string)$airSyncBase->FileReference;
+        }
+        
+        if (isset($fetch->Options)) {
+            // try to fetch element from namespace AirSyncBase
+            $airSyncBase = $fetch->Options->children('uri:AirSyncBase');
+            
+            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;
+                    }
+                }
+            }
+        }
+        
+        return $fetchArray;
+    }
+    
+    protected function _handleEmptyFolderContents(SimpleXMLElement $emptyFolderContent)
+    {
+        $folderArray = array(
+            'collectiondId' => null,
+            'options'       => array('deleteSubFolders' => FALSE)
+        );
+        
+        // try to fetch element from namespace AirSync
+        $airSync = $emptyFolderContent->children('uri:AirSync');
+        
+        $folderArray['collectionId'] = (string)$airSync->CollectionId;
+        
+        if (isset($emptyFolderContent->Options)) {
+            $folderArray['options']['deleteSubFolders'] = isset($emptyFolderContent->Options->DeleteSubFolders); 
+        }
+        
+        return $folderArray;
+    }
 }
diff --git a/lib/ext/Syncroton/Command/Ping.php b/lib/ext/Syncroton/Command/Ping.php
index 9ac1b35..dc06215 100644
--- a/lib/ext/Syncroton/Command/Ping.php
+++ b/lib/ext/Syncroton/Command/Ping.php
@@ -89,8 +89,13 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
         
         $intervalEnd = $intervalStart + $lifeTime;
         $secondsLeft = $intervalEnd;
+        
         $folders = unserialize($this->_device->pingfolder);
         
+        if ($status === self::STATUS_NO_CHANGES_FOUND && (!is_array($folders) || count($folders) == 0)) {
+            $status = self::STATUS_MISSING_PARAMETERS;
+        }
+        
         if ($this->_logger instanceof Zend_Log) 
             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folders to monitor($lifeTime / $intervalStart / $intervalEnd / $status): " . print_r($folders, true));
         
@@ -104,7 +109,7 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
                 
                 $now = new DateTime('now', new DateTimeZone('utc'));
                 
-                foreach ((array) $folders as $folderId) {
+                foreach ($folders as $folderId) {
                     try {
                         $folder         = $this->_folderBackend->get($folderId);
                         $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
diff --git a/lib/ext/Syncroton/Command/Provision.php b/lib/ext/Syncroton/Command/Provision.php
index a3e7328..3163d5f 100644
--- a/lib/ext/Syncroton/Command/Provision.php
+++ b/lib/ext/Syncroton/Command/Provision.php
@@ -60,7 +60,6 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
         
         if ($this->_device->remotewipe == self::REMOTEWIPE_REQUESTED && isset($xml->RemoteWipe->Status) && (int)$xml->RemoteWipe->Status == self::STATUS_SUCCESS) {
             $this->_device->remotewipe = self::REMOTEWIPE_CONFIRMED;
-            $this->_device = $this->_deviceBackend->update($this->_device);
         }
         
         // try to fetch element from Settings namespace
@@ -74,9 +73,10 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
             $this->_device->os              = $this->_deviceInformation->oS;
             $this->_device->oslanguage      = $this->_deviceInformation->oSLanguage;
             $this->_device->phonenumber     = $this->_deviceInformation->phoneNumber;
-            
-            $this->_device = $this->_deviceBackend->update($this->_device);
-            
+        }
+
+        if ($this->_device->isDirty()) {
+            $this->_device = $this->_deviceBackend->update($this->_device);
         }
     }
     
@@ -194,6 +194,6 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
      */
     public static function generatePolicyKey()
     {
-        return sha1(mt_rand(). microtime());
+        return mt_rand(1, 2147483647);
     }
 }
diff --git a/lib/ext/Syncroton/Command/Settings.php b/lib/ext/Syncroton/Command/Settings.php
index e322355..11a36a3 100644
--- a/lib/ext/Syncroton/Command/Settings.php
+++ b/lib/ext/Syncroton/Command/Settings.php
@@ -54,8 +54,10 @@ class Syncroton_Command_Settings extends Syncroton_Command_Wbxml
             $this->_device->os              = $this->_deviceInformation->oS;
             $this->_device->oslanguage      = $this->_deviceInformation->oSLanguage;
             $this->_device->phonenumber     = $this->_deviceInformation->phoneNumber;
-                        
-            $this->_device = $this->_deviceBackend->update($this->_device);
+
+            if ($this->_device->isDirty()) {
+                $this->_device = $this->_deviceBackend->update($this->_device);
+            }
         }
         
         if(isset($xml->UserInformation->Get)) {
diff --git a/lib/ext/Syncroton/Command/Sync.php b/lib/ext/Syncroton/Command/Sync.php
index 3308bed..40ff6df 100644
--- a/lib/ext/Syncroton/Command/Sync.php
+++ b/lib/ext/Syncroton/Command/Sync.php
@@ -113,60 +113,60 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
     public function handle()
     {
         // input xml
-        $xml = simplexml_import_dom($this->_requestBody);
+        $requestXML = simplexml_import_dom($this->_mergeSyncRequest($this->_requestBody, $this->_device));
         
-        if (isset($xml->HeartbeatInterval)) {
-            $this->_heartbeatInterval = (int)$xml->HeartbeatInterval;
-        } elseif (isset($xml->Wait)) {
-            $this->_heartbeatInterval = (int)$xml->Wait * 60;
+        if (! isset($requestXML->Collections)) {
+            $this->_outputDom->documentElement->appendChild(
+                $this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_RESEND_FULL_XML)
+            );
+            
+            return $this->_outputDom;
         }
         
-        $this->_globalWindowSize = isset($xml->WindowSize) ? (int)$xml->WindowSize : 100;
         
+        if (isset($requestXML->HeartbeatInterval)) {
+            $this->_heartbeatInterval = (int)$requestXML->HeartbeatInterval;
+        } elseif (isset($requestXML->Wait)) {
+            $this->_heartbeatInterval = (int)$requestXML->Wait * 60;
+        }
+        
+        $this->_globalWindowSize = isset($requestXML->WindowSize) ? (int)$requestXML->WindowSize : 100;
         
         if ($this->_globalWindowSize > $this->_maxWindowSize) {
             $this->_globalWindowSize = $this->_maxWindowSize;
         }
+
+        // load options from lastsynccollection
+        $lastSyncCollection = array('options' => array());
+        if (!empty($this->_device->lastsynccollection)) {
+            $lastSyncCollection = Zend_Json::decode($this->_device->lastsynccollection);
+            if (!array_key_exists('options', $lastSyncCollection) || !is_array($lastSyncCollection['options'])) {
+                $lastSyncCollection['options'] = array();
+            }
+        }
         
         $collections = array();
-        $isPartialRequest = isset($xml->Partial);
         
-        // try to restore collections from previous request
-        if ($isPartialRequest) {
-            $decodedCollections = Zend_Json::decode($this->_device->lastsynccollection);
+        foreach ($requestXML->Collections->Collection as $xmlCollection) {
+            $collectionId = (string)$xmlCollection->CollectionId;
             
-            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);
-                }
-            }
+            $collections[$collectionId] = new Syncroton_Model_SyncCollection($xmlCollection);
             
-            // store current value of $collections for next Sync command request
-            $collectionsToSave = array();
-        
-            foreach ($collections as $collection) {
-                $collectionsToSave[$collection->collectionId] = $collection->toArray();
+            // do we have to reuse the options from the previous request?
+            if (!isset($xmlCollection->Options) && array_key_exists($collectionId, $lastSyncCollection['options'])) {
+                $collections[$collectionId]->options = $lastSyncCollection['options'][$collectionId];
+                if ($this->_logger instanceof Zend_Log)
+                    $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " restored options to " . print_r($collections[$collectionId]->options, TRUE));
             }
+            
+            // store current options for next Sync command request (sticky options)
+            $lastSyncCollection['options'][$collectionId] = $collections[$collectionId]->options;
+        }
         
-            $this->_device->lastsynccollection = Zend_Json::encode($collectionsToSave);
+        $this->_device->lastsynccollection = Zend_Json::encode($lastSyncCollection);
         
-            if ($this->_device->isDirty()) {
-                Syncroton_Registry::getDeviceBackend()->update($this->_device);
-            }
+        if ($this->_device->isDirty()) {
+            Syncroton_Registry::getDeviceBackend()->update($this->_device);
         }
         
         foreach ($collections as $collectionData) {
@@ -412,13 +412,7 @@ 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'));
+        $collections = $this->_outputDom->createElementNS('uri:AirSync', 'Collections');
 
         $totalChanges = 0;
         
@@ -433,7 +427,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                 $now = new DateTime(null, new DateTimeZone('utc'));
 
                 foreach($this->_collections as $collectionData) {
-                    // countinue immediately if folder does not exist 
+                    // continue immediately if folder does not exist 
                     if (! ($collectionData->folder instanceof Syncroton_Model_IFolder)) {
                         break 2;
                         
@@ -488,23 +482,20 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                         
                         
                         // 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()) {
+                        if ( ! $collectionData->syncState instanceof Syncroton_Model_SyncState ||
+                             ($now->getTimestamp() - $collectionData->syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
                             continue;
                         }
                         
                         $dataController = Syncroton_Data_Factory::factory($collectionData->folder->class , $this->_device, $this->_syncTimeStamp);
                         
-                        $hasChanges = $dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState);
-                        
                         // countinue immediately if there are any changes available
-                        if ($hasChanges) {
+                        if($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
                             break 2;
                         }
                     }
                 }
                 
-                $this->_syncTimeStamp = clone $now;
-                
             // 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
@@ -574,11 +565,21 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                         
                         $serverModifications = $collectionData->syncState->pendingdata;
                         
-                    } else {
+                    } elseif ($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
+                        
+                        // update _syncTimeStamp as $dataController->hasChanges might have spent some time
+                        $this->_syncTimeStamp = new DateTime(null, new DateTimeZone('utc'));
+                        
                         try {
                             // fetch entries added since last sync
-                            $allClientEntries = $this->_contentStateBackend->getFolderState($this->_device, $collectionData->folder);
-                            $allServerEntries = $dataController->getServerEntries($collectionData->collectionId, $collectionData->options['filterType']);
+                            $allClientEntries = $this->_contentStateBackend->getFolderState(
+                                $this->_device, 
+                                $collectionData->folder
+                            );
+                            $allServerEntries = $dataController->getServerEntries(
+                                $collectionData->collectionId, 
+                                $collectionData->options['filterType']
+                            );
 
                             // add entries
                             $serverDiff = array_diff($allServerEntries, $allClientEntries);
@@ -601,7 +602,12 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                             $serverModifications['deleted'] = array_diff($allClientEntries, $allServerEntries);
                             
                             // fetch entries changed since last sync
-                            $serverModifications['changed'] = $dataController->getChangedEntries($collectionData->collectionId, $collectionData->syncState->lastsync, $this->_syncTimeStamp, $collectionData->options['filterType']);
+                            $serverModifications['changed'] = $dataController->getChangedEntries(
+                                $collectionData->collectionId, 
+                                $collectionData->syncState->lastsync, 
+                                $this->_syncTimeStamp, 
+                                $collectionData->options['filterType']
+                            );
                             $serverModifications['changed'] = array_merge($serverModifications['changed'], $clientModifications['forceChange']);
 
                             foreach($serverModifications['changed'] as $id => $serverId) {
@@ -643,7 +649,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                 }
 
                 // collection header
-                $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
+                $collection = $this->_outputDom->createElementNS('uri:AirSync', 'Collection');
                 if (!empty($collectionData->folder->class)) {
                     $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData->folder->class));
                 }
@@ -681,6 +687,17 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                 
                 // send response for to be fetched entries
                 if(!empty($collectionData->toBeFetched)) {
+                    // unset all truncation settings as entries are not allowed to be truncated during fetch
+                    $fetchCollectionData = clone $collectionData;
+                    
+                    // unset truncationSize
+                    if (isset($fetchCollectionData->options['bodyPreferences']) && is_array($fetchCollectionData->options['bodyPreferences'])) {
+                        foreach($fetchCollectionData->options['bodyPreferences'] as $key => $bodyPreference) {
+                            unset($fetchCollectionData->options['bodyPreferences'][$key]['truncationSize']);
+                        }
+                    }
+                    $fetchCollectionData->options['mimeTruncation'] = Syncroton_Command_Sync::TRUNCATE_NOTHING;
+                    
                     foreach($collectionData->toBeFetched as $serverId) {
                         $fetch = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Fetch'));
                         $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
@@ -689,7 +706,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                             $applicationData = $this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData');
                             
                             $dataController
-                                ->getEntry($collectionData, $serverId)
+                                ->getEntry($fetchCollectionData, $serverId)
                                 ->appendXML($applicationData, $this->_device);
                             
                             $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
@@ -741,7 +758,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                         $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
                         
                         $applicationData = $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData'));
-
+                        
                         $dataController
                             ->getEntry($collectionData, $serverId)
                             ->appendXML($applicationData, $this->_device);
@@ -749,12 +766,20 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                         $commands->appendChild($add);
                         
                         $collectionChanges++;
+                    } catch (Syncroton_Exception_MemoryExhausted $seme) {
+                        // continue to next entry, as there is not enough memory left for the current entry
+                        // this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command
+                        if ($this->_logger instanceof Zend_Log)
+                            $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId);
+                        
+                        continue;
+                        
                     } catch (Exception $e) {
-                        if ($this->_logger instanceof Zend_Log) 
+                        if ($this->_logger instanceof Zend_Log)
                             $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                     }
                     
-                    // mark as send to the client, even the conversion to xml might have failed 
+                    // mark as sent to the client, even the conversion to xml might have failed 
                     $newContentStates[] = new Syncroton_Model_Content(array(
                         'device_id'        => $this->_device,
                         'folder_id'        => $collectionData->folder,
@@ -787,6 +812,14 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                         $commands->appendChild($change);
                         
                         $collectionChanges++;
+                    } catch (Syncroton_Exception_MemoryExhausted $seme) {
+                        // continue to next entry, as there is not enough memory left for the current entry
+                        // this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command
+                        if ($this->_logger instanceof Zend_Log)
+                            $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId);
+                        
+                        continue;
+                        
                     } catch (Exception $e) {
                         if ($this->_logger instanceof Zend_Log) 
                             $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
@@ -835,29 +868,35 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                 
                 // increase SyncKey if needed
                 if ((
-                        // sent the clients updates?
+                        // sent the clients updates... ?
                         !empty($clientModifications['added']) ||
                         !empty($clientModifications['changed']) ||
                         !empty($clientModifications['deleted'])
                     ) || (
-                        // sends the server updates to the client?
+                        // is the server sending updates to the client... ?
                         $commands->hasChildNodes() === true
                     ) || (
-                        // changed the pending data?
+                        // changed the pending data... ?
                         $collectionData->syncState->pendingdata != $serverModifications
                     )
                 ) {
-                    // then increase SyncKey
+                    // ...then increase SyncKey
                     $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);
+                if ($this->_logger instanceof Zend_Log)
+                    $this->_logger->info(__METHOD__ . '::' . __LINE__ . " current synckey is ". $collectionData->syncState->counter);
+                
+                if ($collection->childNodes->length > 4 || $collectionData->syncState->counter != $collectionData->syncKey) {
+                     $collections->appendChild($collection);
+                }
             }
             
-            if (isset($collectionData->syncState) && $collectionData->syncState instanceof Syncroton_Model_ISyncState && 
-                $collectionData->syncState->counter != $collectionData->syncKey) {
+            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);
@@ -918,21 +957,144 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                     
                     throw $zdse;
                 }
-                    
-                
-                // store current filter type
-                try {
-                    $folderState = $this->_folderBackend->getFolder($this->_device, $collectionData->collectionId);
-                    $folderState->lastfiltertype = $collectionData->options['filterType'];
+            }
+            
+            // store current filter type
+            try {
+                $folderState = $this->_folderBackend->get($collectionData->folder);
+                $folderState->lastfiltertype = $collectionData->options['filterType'];
+                if ($folderState->isDirty()) {
                     $this->_folderBackend->update($folderState);
-                } catch (Syncroton_Exception_NotFound $senf) {
-                    // failed to get folderstate => should not happen but is also no problem in this state
-                    if ($this->_logger instanceof Zend_Log) 
-                        $this->_logger->crit(__METHOD__ . '::' . __LINE__ . ' failed to get content state for: ' . $collectionData->collectionId);
                 }
+            } catch (Syncroton_Exception_NotFound $senf) {
+                // failed to get folderstate => should not happen but is also no problem in this state
+                if ($this->_logger instanceof Zend_Log) 
+                    $this->_logger->warn(__METHOD__ . '::' . __LINE__ . ' failed to get folder state for: ' . $collectionData->collectionId);
+            }
+        }
+        
+        if ($collections->hasChildNodes() === true) {
+            $sync->appendChild($collections);
+        }
+        
+        if ($sync->hasChildNodes()) {
+            return $this->_outputDom;
+        }
+        
+        return null;
+    }
+    
+    /**
+     * remove Commands and Supported from collections XML tree
+     * 
+     * @param  DOMDocument $document
+     * @return DOMDocument
+     */
+    protected function _cleanUpXML(DOMDocument $document)
+    {
+        $cleanedDocument = clone $document;
+        
+        $xpath = new DomXPath($cleanedDocument);
+        $xpath->registerNamespace('AirSync', 'uri:AirSync');
+        
+        $collections = $xpath->query("//AirSync:Sync/AirSync:Collections/AirSync:Collection");
+        
+        // remove Commands and Supported elements
+        foreach ($collections as $collection) {
+            foreach (array('Commands', 'Supported') as $element) {
+                $childrenToRemove = $collection->getElementsByTagName($element);
+                
+                foreach ($childrenToRemove as $childToRemove) {
+                    $collection->removeChild($childToRemove);
+                }
+            }
+        }
+        
+        return $cleanedDocument;
+    }
+    
+    /**
+     * merge a partial XML document with the XML document from the previous request
+     * 
+     * @param  DOMDocument|null  $requestBody
+     * @return SimpleXMLElement
+     */
+    protected function _mergeSyncRequest($requestBody, Syncroton_Model_Device $device)
+    {
+        $lastSyncCollection = array();
+        
+        if (!empty($device->lastsynccollection)) {
+            $lastSyncCollection = Zend_Json::decode($device->lastsynccollection);
+            if (!empty($lastSyncCollection['lastXML'])) {
+                $lastXML = new DOMDocument();
+                $lastXML->loadXML($lastSyncCollection['lastXML']);
             }
         }
         
-        return $this->_outputDom;
+        if (! $requestBody instanceof DOMDocument && isset($lastXML) && $lastXML instanceof DOMDocument) {
+            $requestBody = $lastXML;
+        } elseif (! $requestBody instanceof DOMDocument) {
+            throw new Syncroton_Exception_UnexpectedValue('no xml body found');
+        }
+        
+        if ($requestBody->getElementsByTagName('Partial')->length > 0) {
+            $partialBody = clone $requestBody;
+            $requestBody = $lastXML;
+            
+            $xpath = new DomXPath($requestBody);
+            $xpath->registerNamespace('AirSync', 'uri:AirSync');
+            
+            foreach ($partialBody->documentElement->childNodes as $child) {
+                if (! $child instanceof DOMElement) {
+                    continue;
+                }
+                
+                if ($child->tagName == 'Partial') {
+                    continue;
+                }
+                
+                if ($child->tagName == 'Collections') {
+                    foreach ($child->getElementsByTagName('Collection') as $updatedCollection) {
+                        $collectionId = $updatedCollection->getElementsByTagName('CollectionId')->item(0)->nodeValue;
+                        
+                        $existingCollections = $xpath->query("//AirSync:Sync/AirSync:Collections/AirSync:Collection[AirSync:CollectionId='$collectionId']");
+                        
+                        if ($existingCollections->length > 0) {
+                            $existingCollection = $existingCollections->item(0);
+                            foreach ($updatedCollection->childNodes as $updatedCollectionChild) {
+                                if (! $updatedCollectionChild instanceof DOMElement) {
+                                    continue;
+                                }
+                                
+                                $duplicateChild = $existingCollection->getElementsByTagName($updatedCollectionChild->tagName);
+                                
+                                if ($duplicateChild->length > 0) {
+                                    $existingCollection->replaceChild($requestBody->importNode($updatedCollectionChild, TRUE), $duplicateChild->item(0));
+                                } else {
+                                    $existingCollection->appendChild($requestBody->importNode($updatedCollectionChild, TRUE));
+                                }
+                            }
+                        } else {
+                            $importedCollection = $requestBody->importNode($updatedCollection, TRUE);
+                        }
+                    }
+                    
+                } else {
+                    $duplicateChild = $xpath->query("//AirSync:Sync/AirSync:{$child->tagName}");
+                    
+                    if ($duplicateChild->length > 0) {
+                        $requestBody->documentElement->replaceChild($requestBody->importNode($child, TRUE), $duplicateChild->item(0));
+                    } else {
+                        $requestBody->documentElement->appendChild($requestBody->importNode($child, TRUE));
+                    }
+                }
+            }
+        }
+        
+        $lastSyncCollection['lastXML'] = $this->_cleanUpXML($requestBody)->saveXML();
+        
+        $device->lastsynccollection = Zend_Json::encode($lastSyncCollection);
+        
+        return $requestBody;
     }
 }
diff --git a/lib/ext/Syncroton/Command/Wbxml.php b/lib/ext/Syncroton/Command/Wbxml.php
index e927cc2..a252d25 100644
--- a/lib/ext/Syncroton/Command/Wbxml.php
+++ b/lib/ext/Syncroton/Command/Wbxml.php
@@ -180,7 +180,7 @@ abstract class Syncroton_Command_Wbxml implements Syncroton_Command_ICommand
             if (!empty($this->_device->policyId)) {
                 $policy = $this->_policyBackend->get($this->_device->policyId);
                 
-                if($policy->policyKey != $this->_policyKey) {
+                if((int)$policy->policyKey != (int)$this->_policyKey) {
                     $this->_outputDom->documentElement->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Status', 142));
                     
                     $sepn = new Syncroton_Exception_ProvisioningNeeded();
diff --git a/lib/ext/Syncroton/Data/AData.php b/lib/ext/Syncroton/Data/AData.php
index e99e0b9..1a76021 100644
--- a/lib/ext/Syncroton/Data/AData.php
+++ b/lib/ext/Syncroton/Data/AData.php
@@ -23,6 +23,17 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
      */
     public static $changedEntries = array();
     
+    /**
+     * used by unit tests only to simulated exhausted memory
+     */
+    public static $exhaustedEntries = array();
+    
+    /**
+     * the constructor
+     * 
+     * @param Syncroton_Model_IDevice $_device
+     * @param DateTime $_timeStamp
+     */
     public function __construct(Syncroton_Model_IDevice $_device, DateTime $_timeStamp)
     {
         $this->_device      = $_device;
@@ -32,6 +43,13 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         $this->_ownerId     = '1234';
     }
     
+    /**
+     * return one folder identified by id
+     * 
+     * @param  string  $id
+     * @throws Syncroton_Exception_NotFound
+     * @return Syncroton_Model_Folder
+     */
     public function getFolder($id)
     {
         $select = $this->_db->select()
@@ -55,6 +73,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         ));
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::createFolder()
+     */
     public function createFolder(Syncroton_Model_IFolder $folder)
     {
         if (!in_array($folder->type, $this->_supportedFolderTypes)) {
@@ -64,16 +86,21 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         $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
+            'id'            => $id,
+            'type'          => $folder->type,
+            'name'          => $folder->displayName,
+            'owner_id'      => $this->_ownerId,
+            'parent_id'     => $folder->parentId,
+            'creation_time' => $this->_timestamp->format('Y-m-d H:i:s')
         ));
         
         return $this->getFolder($id);
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::createEntry()
+     */
     public function createEntry($_folderId, Syncroton_Model_IEntry $_entry)
     {
         $id = sha1(mt_rand(). microtime());
@@ -88,6 +115,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         return $id;
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::deleteEntry()
+     */
     public function deleteEntry($_folderId, $_serverId, $_collectionData)
     {
         $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
@@ -97,6 +128,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         return (bool) $result;
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::deleteFolder()
+     */
     public function deleteFolder($_folderId)
     {
         $folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
@@ -107,6 +142,19 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         return (bool) $result;
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::emptyFolderContents()
+     */
+    public function emptyFolderContents($folderId, $options)
+    {
+        return true;
+    }
+    
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::getAllFolders()
+     */
     public function getAllFolders()
     {
         $select = $this->_db->select()
@@ -132,6 +180,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         return $result;
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::getChangedEntries()
+     */
     public function getChangedEntries($_folderId, DateTime $_startTimeStamp, DateTime $_endTimeStamp = NULL, $filterType = NULL)
     {
         if (!isset(Syncroton_Data_AData::$changedEntries[get_class($this)])) {
@@ -142,6 +194,40 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
     }
     
     /**
+     * retrieve folders which were modified since last sync
+     * 
+     * @param  DateTime  $startTimeStamp
+     * @param  DateTime  $endTimeStamp
+     * @return array list of Syncroton_Model_Folder
+     */
+    public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp)
+    {
+        $select = $this->_db->select()
+            ->from($this->_tablePrefix . 'data_folder')
+            ->where('type IN (?)', $this->_supportedFolderTypes)
+            ->where('owner_id = ?', $this->_ownerId)
+            ->where('last_modified_time > ?', $startTimeStamp->format('Y-m-d H:i:s'))
+            ->where('last_modified_time <= ?', $endTimeStamp->format('Y-m-d H:i:s'));
+        
+        $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;
+    }
+    
+    /**
      * @param  Syncroton_Model_IFolder|string  $_folderId
      * @param  string                        $_filter
      * @return array
@@ -164,6 +250,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         return $ids;
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::getCountOfChanges()
+     */
     public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
     {
         $allClientEntries = $contentBackend->getFolderState($this->_device, $folder);
@@ -176,6 +266,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         return count($addedEntries) + count($deletedEntries) + count($changedEntries);
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::getFileReference()
+     */
     public function getFileReference($fileReference)
     {
         throw new Syncroton_Exception_NotFound('filereference not found');
@@ -186,7 +280,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
      * @see Syncroton_Data_IData::getEntry()
      */
     public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
-    {
+    {
+        if (isset(self::$exhaustedEntries[get_class($this)]) && is_array(self::$exhaustedEntries[get_class($this)]) && in_array($serverId, self::$exhaustedEntries[get_class($this)])) {
+            throw new Syncroton_Exception_MemoryExhausted('memory exchausted for ' . $serverId);
+        } 
         $select = $this->_db->select()
             ->from($this->_tablePrefix . 'data', array('data'))
             ->where('id = ?', $serverId);
@@ -210,6 +307,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         return !!$this->getCountOfChanges($contentBackend, $folder, $syncState);
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::moveItem()
+     */
     public function moveItem($_srcFolderId, $_serverId, $_dstFolderId)
     {
         $this->_db->update($this->_tablePrefix . 'data', array(
@@ -221,6 +322,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
         return $_serverId;
     }
     
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::updateEntry()
+     */
     public function updateEntry($_folderId, $_serverId, Syncroton_Model_IEntry $_entry)
     {
         $this->_db->update($this->_tablePrefix . 'data', array(
@@ -230,15 +335,23 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
             'id = ?' => $_serverId
         ));
     }
-    
+    
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Data_IData::updateFolder()
+     */
     public function updateFolder(Syncroton_Model_IFolder $folder)
     {
         $this->_db->update($this->_tablePrefix . 'data_folder', array(
-            'name'      => $folder->displayName,
-            'parent_id' => $folder->parentId
+            'name'               => $folder->displayName,
+            'parent_id'          => $folder->parentId,
+            'last_modified_time' => $this->_timestamp->format('Y-m-d H:i:s')
         ), array(
-            'id = ?' => $folder->serverId
+            'id = ?'       => $folder->serverId,
+            'owner_id = ?' => $this->_ownerId
         ));
+        
+        return $this->getFolder($folder->serverId);
     }
 }
 
diff --git a/lib/ext/Syncroton/Data/IData.php b/lib/ext/Syncroton/Data/IData.php
index a21dfd5..2d26335 100644
--- a/lib/ext/Syncroton/Data/IData.php
+++ b/lib/ext/Syncroton/Data/IData.php
@@ -43,7 +43,20 @@ interface Syncroton_Data_IData
      */
     public function deleteEntry($_folderId, $_serverId, $_collectionData);
     
-    public function deleteFolder($_folderId);
+    /**
+     * delete folder
+     * 
+     * @param string $folderId
+     */
+    public function deleteFolder($folderId);
+    
+    /**
+     * empty folder
+     *
+     * @param string $folderId
+     * @param array  $options
+     */
+    public function emptyFolderContents($folderId, $options);
     
     /**
      * return list off all folders
@@ -53,6 +66,14 @@ interface Syncroton_Data_IData
     
     public function getChangedEntries($folderId, DateTime $startTimeStamp, DateTime $endTimeStamp = NULL, $filterType = NULL);
     
+    /**
+     * retrieve folders which were modified since last sync
+     * 
+     * @param DateTime $startTimeStamp
+     * @param DateTime $endTimeStamp
+     */
+    public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp);
+    
     public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState);
     
     /**
diff --git a/lib/ext/Syncroton/Exception/MemoryExhausted.php b/lib/ext/Syncroton/Exception/MemoryExhausted.php
new file mode 100644
index 0000000..ee346cc
--- /dev/null
+++ b/lib/ext/Syncroton/Exception/MemoryExhausted.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package     Syncroton
+ * @subpackage  Exception
+ * @license     http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright   Copyright (c) 2013-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Lars Kneschke <l.kneschke at metaways.de>
+ */
+
+/**
+ * exception for memory exhausted
+ *
+ * @package     Syncroton
+ * @subpackage  Exception
+ */
+class Syncroton_Exception_MemoryExhausted extends Syncroton_Exception
+{
+}
diff --git a/lib/ext/Syncroton/Model/AXMLEntry.php b/lib/ext/Syncroton/Model/AXMLEntry.php
index 6dc1e9f..faecc88 100644
--- a/lib/ext/Syncroton/Model/AXMLEntry.php
+++ b/lib/ext/Syncroton/Model/AXMLEntry.php
@@ -93,11 +93,14 @@ abstract class Syncroton_Model_AXMLEntry extends Syncroton_Model_AEntry implemen
      * (non-PHPdoc)
      * @see Syncroton_Model_IEntry::getProperties()
      */
-    public function getProperties()
+    public function getProperties($selectedNamespace = null)
     {
         $properties = array();
         
         foreach($this->_properties as $namespace => $namespaceProperties) {
+            if ($selectedNamespace !== null && $namespace != $selectedNamespace) {
+                continue;
+            }
             $properties = array_merge($properties, array_keys($namespaceProperties));
         }
         
@@ -169,7 +172,8 @@ abstract class Syncroton_Model_AXMLEntry extends Syncroton_Model_AEntry implemen
                 if (!ctype_print($value)) {
                     $value = $this->_removeControlChars($value);
                 }
-                $element->appendChild($element->ownerDocument->createTextNode($value));
+                
+                $element->appendChild($element->ownerDocument->createTextNode($this->_enforeUTF8($value)));
             }
         }
     }
@@ -186,6 +190,29 @@ abstract class Syncroton_Model_AXMLEntry extends Syncroton_Model_AEntry implemen
     }
     
     /**
+     * enforce >valid< utf-8 encoding
+     * 
+     * @param  string  $dirty  the string with maybe invalid utf-8 data
+     * @return string  string with valid utf-8
+     */
+    protected function _enforeUTF8($dirty)
+    {
+        if (function_exists('iconv')) {
+            if (($clean = @iconv('UTF-8', 'UTF-8//IGNORE', $dirty)) !== false) {
+                return $clean;
+            }
+        }
+        
+        if (function_exists('mb_convert_encoding')) {
+            if (($clean = mb_convert_encoding($dirty, 'UTF-8', 'UTF-8')) !== false) {
+                return $clean;
+            }
+        }
+    
+        return $dirty;
+    }
+    
+    /**
      * 
      * @param unknown_type $element
      * @throws InvalidArgumentException
diff --git a/lib/ext/Syncroton/Model/Content.php b/lib/ext/Syncroton/Model/Content.php
index 27075f2..5df9826 100644
--- a/lib/ext/Syncroton/Model/Content.php
+++ b/lib/ext/Syncroton/Model/Content.php
@@ -15,18 +15,7 @@
  * @package     Syncroton
  * @subpackage  Model
  */
-class Syncroton_Model_Content implements Syncroton_Model_IContent
+class Syncroton_Model_Content extends Syncroton_Model_AEntry implements Syncroton_Model_IContent
 {
-    public function __construct(array $_data = array())
-    {
-        $this->setFromArray($_data);
-    }
-    
-    public function setFromArray(array $_data)
-    {
-        foreach($_data as $key => $value) {
-            $this->$key = $value;
-        }
-    }
 }
 
diff --git a/lib/ext/Syncroton/Model/Event.php b/lib/ext/Syncroton/Model/Event.php
index 08dc9c5..0fccd2a 100644
--- a/lib/ext/Syncroton/Model/Event.php
+++ b/lib/ext/Syncroton/Model/Event.php
@@ -65,32 +65,45 @@ class Syncroton_Model_Event extends Syncroton_Model_AXMLEntry
         )
     );
     
-    public function setFromArray(array $properties)
-    {
-        parent::setFromArray($properties);
-        
-        $this->_copyFieldsFromParent();
-    }
-    
-    /**
-     * set properties from SimpleXMLElement object
-     *
-     * @param SimpleXMLElement $xmlCollection
-     * @throws InvalidArgumentException
-     */
-    public function setFromSimpleXMLElement(SimpleXMLElement $properties)
+    /**
+     * (non-PHPdoc)
+     * @see Syncroton_Model_IEntry::appendXML()
+     * @todo handle Attendees element
+     */
+    public function appendXML(DOMElement $domParrent, Syncroton_Model_IDevice $device)
     {
-        parent::setFromSimpleXMLElement($properties);
-        
-        $this->_copyFieldsFromParent();
+        parent::appendXML($domParrent, $device);
+
+        $exceptionElements = $domParrent->getElementsByTagName('Exception');
+        $parentFields      = array('AllDayEvent'/*, 'Attendees'*/, 'Body', 'BusyStatus'/*, 'Categories'*/, 'DtStamp', 'EndTime', 'Location', 'MeetingStatus', 'Reminder', 'ResponseType', 'Sensitivity', 'StartTime', 'Subject');
+
+        if ($exceptionElements->length > 0) {
+            $mainEventElement = $exceptionElements->item(0)->parentNode->parentNode;
+
+            foreach ($mainEventElement->childNodes as $childNode) {
+                if (in_array($childNode->localName, $parentFields)) {
+                    foreach ($exceptionElements as $exception) {
+                        $elementsToLeftOut = $exception->getElementsByTagName($childNode->localName);
+
+                        foreach ($elementsToLeftOut as $elementToLeftOut) {
+                            if ($elementToLeftOut->nodeValue == $childNode->nodeValue) {
+                                $exception->removeChild($elementToLeftOut);
+                            }
+                        }
+                    }
+                }
+            }
+        }
     }
     
     /**
-     * copy some fileds of the main event to the exception if they are missing
-     * these fields can be left out, if they have the same value in the main event
-     * and the exception 
+     * some elements of an exception can be left out, if they have the same value 
+     * like the main event
+     * 
+     * this function copies these elements to the exception for backends which need
+     * this elements in the exceptions too. Tine 2.0 needs this for example.
      */
-    protected function _copyFieldsFromParent()
+    public function copyFieldsFromParent()
     {
         if (isset($this->_elements['exceptions']) && is_array($this->_elements['exceptions'])) {
             foreach ($this->_elements['exceptions'] as $exception) {
@@ -98,30 +111,15 @@ class Syncroton_Model_Event extends Syncroton_Model_AXMLEntry
                 if ($exception->deleted == 1) {
                     continue;
                 }
-
-                $parentFields = array(
-                    'allDayEvent',
-                    'attendees',
-                    'busyStatus',
-                    'meetingStatus',
-                    'sensitivity',
-                    'subject',
-                    'body',
-                    'location',
-                    'reminder'
-                );
-
-                foreach ($parentFields as $field) {
-                    if (isset($this->_elements[$field])) {
-                        if (!isset($exception->$field)) {
-                            $exception->$field = $this->_elements[$field];
-                        }
-                        else if ($exception->$field == $this->_elements[$field]) {
-                            unset($exception->$field);
-                        }
-                    }
-                }
-            }
-        }
+        
+                $parentFields = array('allDayEvent', 'attendees', 'body', 'busyStatus', 'categories', 'dtStamp', 'endTime', 'location', 'meetingStatus', 'reminder', 'responseType', 'sensitivity', 'startTime', 'subject');
+        
+                foreach ($parentFields as $field) {
+                    if (!isset($exception->$field) && isset($this->_elements[$field])) {
+                        $exception->$field = $this->_elements[$field];
+                    }
+                }
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EventException.php b/lib/ext/Syncroton/Model/EventException.php
index 4c6361c..63ae15e 100644
--- a/lib/ext/Syncroton/Model/EventException.php
+++ b/lib/ext/Syncroton/Model/EventException.php
@@ -27,9 +27,9 @@ class Syncroton_Model_EventException extends Syncroton_Model_AXMLEntry
     protected $_dateTimeFormat = "Ymd\THis\Z";
     
     protected $_properties = array(
-        'AirSyncBase' => array(
-            'body'                    => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody')
-        ),
+        'AirSyncBase' => array(
+            'body'                    => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody')
+        ),
         'Calendar' => array(
             'allDayEvent'             => array('type' => 'number'),
             'appointmentReplyTime'    => array('type' => 'datetime'),
diff --git a/lib/ext/Syncroton/Model/IContent.php b/lib/ext/Syncroton/Model/IContent.php
index 0ec4e2a..2ce315a 100644
--- a/lib/ext/Syncroton/Model/IContent.php
+++ b/lib/ext/Syncroton/Model/IContent.php
@@ -24,6 +24,5 @@
 
 interface Syncroton_Model_IContent
 {
-    
 }
 
diff --git a/lib/ext/Syncroton/Model/ISyncState.php b/lib/ext/Syncroton/Model/ISyncState.php
index 8e73069..cd1fc2a 100644
--- a/lib/ext/Syncroton/Model/ISyncState.php
+++ b/lib/ext/Syncroton/Model/ISyncState.php
@@ -21,6 +21,5 @@
 
 interface Syncroton_Model_ISyncState
 {
-    
 }
 
diff --git a/lib/ext/Syncroton/Model/SyncCollection.php b/lib/ext/Syncroton/Model/SyncCollection.php
index b6370dc..5976b02 100644
--- a/lib/ext/Syncroton/Model/SyncCollection.php
+++ b/lib/ext/Syncroton/Model/SyncCollection.php
@@ -273,6 +273,11 @@ class Syncroton_Model_SyncCollection extends Syncroton_Model_AXMLEntry
                     // optional
                     if (isset($bodyPreference->TruncationSize)) {
                         $this->_elements['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
+                    }
+                    
+                    // optional
+                    if (isset($bodyPreference->Preview)) {
+                        $this->_elements['options']['bodyPreferences'][$type]['preview'] = (int) $bodyPreference->Preview;
                     }
                 }
             }
diff --git a/lib/ext/Syncroton/Model/SyncState.php b/lib/ext/Syncroton/Model/SyncState.php
index b44b4c5..398f3a7 100644
--- a/lib/ext/Syncroton/Model/SyncState.php
+++ b/lib/ext/Syncroton/Model/SyncState.php
@@ -15,18 +15,7 @@
  * @package     Model
  */
 
-class Syncroton_Model_SyncState implements Syncroton_Model_ISyncState
+class Syncroton_Model_SyncState extends Syncroton_Model_AEntry implements Syncroton_Model_ISyncState
 {
-    public function __construct(array $_data = array())
-    {
-        $this->setFromArray($_data);
-    }
-    
-    public function setFromArray(array $_data)
-    {
-        foreach($_data as $key => $value) {
-            $this->$key = $value;
-        }
-    }
 }
 
diff --git a/lib/ext/Syncroton/Server.php b/lib/ext/Syncroton/Server.php
index 8c3ccea..0e57ddd 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -81,7 +81,7 @@ class Syncroton_Server
     protected function _handleOptions()
     {
         $command = new Syncroton_Command_Options();
-
+    
         $this->_sendHeaders($command->getHeaders());
     }
     
@@ -145,12 +145,12 @@ class Syncroton_Server
             if ($this->_logger instanceof Zend_Log) 
                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " provisioning needed");
             
+            header("HTTP/1.1 449 Retry after sending a PROVISION command");
+            
             if (version_compare($device->acsversion, '14.0', '>=')) {
                 $response = $sepn->domDocument;
             } else {
                 // pre 14.0 method
-                header("HTTP/1.1 449 Retry after sending a PROVISION command");
-                   
                 return;
             }
             
@@ -181,7 +181,19 @@ class Syncroton_Server
             $outputStream = fopen("php://temp", 'r+');
             
             $encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3);
-            $encoder->encode($response);
+            
+            try {
+                 $encoder->encode($response);
+            } catch (Syncroton_Wbxml_Exception $swe) {
+                if ($this->_logger instanceof Zend_Log) {
+                    $this->_logger->err(__METHOD__ . '::' . __LINE__ . " Could not encode output: " . $swe);
+                    $this->_logger->err(__METHOD__ . '::' . __LINE__ . " xml response:\n" . $response->saveXML());
+                }
+                
+                header("HTTP/1.1 500 Internal server error");
+                
+                return;
+            }
             
             if ($requestParameters['acceptMultipart'] == true) {
                 $parts = $command->getParts();
diff --git a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php
index ee5635e..f34442b 100644
--- a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php
+++ b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php
@@ -49,5 +49,34 @@ class Syncroton_Wbxml_Dtd_ActiveSync_CodePage14 extends Syncroton_Wbxml_Dtd_Acti
         'AllowStorageCard'                   => 0x1b,
         'AllowCamera'                        => 0x1c,
         'RequireDeviceEncryption'            => 0x1d,
+        'AllowUnsignedApplications'          => 0x1e,
+        'AllowUnsignedInstallationPackages'  => 0x1f,
+        'MinDevicePasswordComplexCharacters' => 0x20,
+        'AllowWiFi'                          => 0x21,
+        'AllowTextMessaging'                 => 0x22,
+        'AllowPOPIMAPEmail'                  => 0x23,
+        'AllowBluetooth'                     => 0x24,
+        'AllowIrDA'                          => 0x25,
+        'RequireManualSyncWhenRoaming'       => 0x26,
+        'AllowDesktopSync'                   => 0x27,
+        'MaxCalendarAgeFilter'               => 0x28,
+        'AllowHTMLEmail'                     => 0x29,
+        'MaxEmailAgeFilter'                  => 0x2a,
+        'MaxEmailBodyTruncationSize'         => 0x2b,
+        'MaxEmailHTMLBodyTruncationSize'     => 0x2c,
+        'RequireSignedSMIMEMessages'         => 0x2d,
+        'RequireEncryptedSMIMEMessages'      => 0x2e,
+        'RequireSignedSMIMEAlgorithm'        => 0x2F,
+        'RequireEncryptionSMIMEAlgorithm'    => 0x30,
+        'AllowSMIMEEncryptionAlgorithmNegotiation' => 0x31,
+        'AllowSMIMESoftCerts'                => 0x32,
+        'AllowBrowser'                       => 0x33,
+        'AllowConsumerEmail'                 => 0x34,
+        'AllowRemoteDesktop'                 => 0x35,
+        'AllowInternetSharing'               => 0x36,
+        'UnapprovedInROMApplicationList'     => 0x37,
+        'ApplicationName'                    => 0x38,
+        'ApprovedApplicationList'            => 0x39,
+        'Hash'                               => 0x3a,
     );
-}
\ No newline at end of file
+}
diff --git a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php
index 033f156..7d33bc6 100644
--- a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php
+++ b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php
@@ -54,8 +54,8 @@ class Syncroton_Wbxml_Dtd_ActiveSync_CodePage9 extends Syncroton_Wbxml_Dtd_Activ
         'Rtf'                     => 0x21,
         'OrdinalDate'             => 0x22,
         'SubOrdinalDate'          => 0x23,
-        'CalendarType'            => 0x23,
-        'IsLeapMonth'             => 0x23,
-        'FirstDayOfWeek'          => 0x23
+        'CalendarType'            => 0x24,
+        'IsLeapMonth'             => 0x25,
+        'FirstDayOfWeek'          => 0x26,
     );
 }
\ No newline at end of file
diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php
index 0c6d0ef..bdfcbb7 100644
--- a/lib/kolab_sync_data.php
+++ b/lib/kolab_sync_data.php
@@ -85,6 +85,13 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
     protected $folders = array();
 
     /**
+     * Internal cache for IMAP folders list
+     *
+     * @var array
+     */
+    protected $imap_folders = array();
+
+    /**
      * Timezone
      *
      * @var string
@@ -101,6 +108,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
         'ipad',
         'thundertine',
         'windowsphone',
+        'playbook',
     );
 
     const RESULT_OBJECT = 0;
@@ -200,7 +208,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
         // device supports multiple folders ?
         if (in_array(strtolower($this->device->devicetype), $this->ext_devices)) {
             // get the folders the user has access to
-            $list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+            $list = $this->listFolders();
         }
         else if ($default = $this->getDefaultFolder()) {
             $list = array($default['serverId'] => $default);
@@ -220,12 +228,25 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
     }
 
     /**
+     * Retrieve folders which were modified since last sync
+     *
+     * @param DateTime $startTimeStamp
+     * @param DateTime $endTimeStamp
+     *
+     * @return array List of folders
+     */
+    public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp)
+    {
+        return array();
+    }
+
+    /**
      * Returns default folder for current class type.
      */
     protected function getDefaultFolder()
     {
         // Check if there's any folder configured for sync
-        $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+        $folders = $this->listFolders();
 
         if (empty($folders)) {
             return $folders;
@@ -343,6 +364,47 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
     }
 
     /**
+     * Empty folder (remove all entries and optionally subfolders)
+     *
+     * @param string $folderId Folder identifier
+     * @param array  $options  Options
+     */
+    public function emptyFolderContents($folderid, $options)
+    {
+        $folders = $this->extractFolders($folderid);
+
+        foreach ($folders as $folderid) {
+            $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
+
+            if ($foldername === null) {
+                continue;
+            }
+
+            $folder = $this->getFolderObject($foldername);
+
+            // Remove all entries
+            $folder->delete_all();
+
+            // Remove subfolders
+            if (!empty($options['deleteSubFolders'])) {
+                $list = $this->listFolders($folderid);
+                foreach ($list as $folderid => $folder) {
+                    $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
+
+                    if ($foldername === null) {
+                        continue;
+                    }
+
+                    $folder = $this->getFolderObject($foldername);
+
+                    // Remove all entries
+                    $folder->delete_all();
+                }
+            }
+        }
+    }
+
+    /**
      * Moves object into another location (folder)
      *
      * @param string $srcFolderId Source folder identifier
@@ -456,7 +518,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
     protected function searchEntries($folderid, $filter = array(), $result_type = self::RESULT_UID)
     {
         if ($folderid == $this->defaultRootFolder) {
-            $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+            $folders = $this->listFolders();
 
             if (!is_array($folders)) {
                 throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR);
@@ -656,22 +718,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
      */
     protected function getObject($folderid, $entryid, &$folder = null)
     {
-        if ($folderid instanceof Syncroton_Model_IFolder) {
-            $folderid = $folderid->serverId;
-        }
-
-        if ($folderid == $this->defaultRootFolder) {
-            $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
-
-            if (!is_array($folders)) {
-                return null;
-            }
-
-            $folders = array_keys($folders);
-        }
-        else {
-            $folders = array($folderid);
-        }
+        $folders = $this->extractFolders($folderid);
 
         foreach ($folders as $folderid) {
             $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
@@ -746,6 +793,66 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
     }
 
     /**
+     * Returns internal folder IDs
+     *
+     * @param string $folderid Folder identifier
+     *
+     * @return array List of folder identifiers
+     */
+    protected function extractFolders($folderid)
+    {
+        if ($folderid instanceof Syncroton_Model_IFolder) {
+            $folderid = $folderid->serverId;
+        }
+
+        if ($folderid == $this->defaultRootFolder) {
+            $folders = $this->listFolders();
+
+            if (!is_array($folders)) {
+                return null;
+            }
+
+            $folders = array_keys($folders);
+        }
+        else {
+            $folders = array($folderid);
+        }
+
+        return $folders;
+    }
+
+    /**
+     * List of all IMAP folders (or subtree)
+     *
+     * @param string $parentid Parent folder identifier
+     *
+     * @return array List of folder identifiers
+     */
+    protected function listFolders($parentid = null)
+    {
+        if (empty($this->imap_folders)) {
+            $this->imap_folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+        }
+
+        if ($parentid === null) {
+            return $this->imap_folders;
+        }
+
+        $folders = array();
+        $parents = array($parentid);
+
+        foreach ($this->imap_folders as $folder_id => $folder) {
+            if ($folder['parentId'] && in_array($folder['parentId'], $parents)) {
+                $folders[$folder_id] = $folder;
+                $parents[] = $folder_id;
+            }
+        }
+
+
+        return $folders;
+    }
+
+    /**
      * Returns Folder object (uses internal cache)
      *
      * @param string $name  Folder name (UTF7-IMAP)
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
index 6ad2ed8..6822f54 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -416,7 +416,7 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
      */
     public function getAllFolders()
     {
-        $list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+        $list = $this->listFolders();
 
         if (!is_array($list)) {
             throw new Syncroton_Exception_Status_FolderSync(Syncroton_Exception_Status_FolderSync::FOLDER_SERVER_ERROR);
@@ -457,7 +457,7 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
      */
     protected function extractFolders($folder_id)
     {
-        $list   = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+        $list   = $this->listFolders();
         $result = array();
 
         if (!is_array($list)) {
@@ -1002,7 +1002,7 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
 
         // @TODO: DeepTraversal
         if (empty($folders)) {
-            $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+            $folders = $this->listFolders();
 
             if (is_array($folders)) {
                 $folders = array_keys($folders);






More information about the commits mailing list