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

Aleksander Machniak machniak at kolabsys.com
Tue Oct 9 14:06:45 CEST 2012


 lib/ext/Syncroton/Command/FolderCreate.php |   10 -
 lib/ext/Syncroton/Command/FolderDelete.php |   67 ++++------
 lib/ext/Syncroton/Command/FolderSync.php   |   61 +++++----
 lib/ext/Syncroton/Command/FolderUpdate.php |   28 ++--
 lib/ext/Syncroton/Command/Ping.php         |   58 +++++---
 lib/ext/Syncroton/Command/Provision.php    |   53 +++++--
 lib/ext/Syncroton/Command/Search.php       |    3 
 lib/ext/Syncroton/Command/SendMail.php     |    2 
 lib/ext/Syncroton/Command/Settings.php     |   27 ++--
 lib/ext/Syncroton/Command/Sync.php         |  104 ++++++++-------
 lib/ext/Syncroton/Command/Wbxml.php        |    4 
 lib/ext/Syncroton/Data/IDataSearch.php     |   10 -
 lib/ext/Syncroton/Server.php               |  194 +++++++++++++++--------------
 lib/kolab_sync_backend.php                 |   25 ++-
 lib/kolab_sync_data.php                    |   64 +++++++--
 lib/kolab_sync_data_email.php              |   45 ++++++
 16 files changed, 452 insertions(+), 303 deletions(-)

New commits:
commit 5fd57443fcefac69be20016d618e1a9df0ef8f1e
Author: Aleksander Machniak <alec at alec.pl>
Date:   Tue Oct 9 11:43:08 2012 +0200

    Improve backend errors handling (Bug #1088)

diff --git a/lib/kolab_sync_backend.php b/lib/kolab_sync_backend.php
index 659fe6e..1804614 100644
--- a/lib/kolab_sync_backend.php
+++ b/lib/kolab_sync_backend.php
@@ -142,18 +142,22 @@ class kolab_sync_backend
      * @param string $deviceid  Device identifier
      * @param string $type      Folder type
      *
-     * @return array List of mailbox folders
+     * @return array|bool List of mailbox folders, False on backend failure
      */
     public function folders_list($deviceid, $type)
     {
-        $folders_list = array();
-
         // get all folders of specified type
         $folders = (array) kolab_storage::list_folders('', '*', $type, false, $typedata);
 
         // get folders activesync config
         $folderdata = $this->folder_meta();
 
+        if (!is_array($folders) || !is_array($folderdata)) {
+            return false;
+        }
+
+        $folders_list = array();
+
         // check if folders are "subscribed" for activesync
         foreach ($folderdata as $folder => $meta) {
             if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
@@ -178,7 +182,7 @@ class kolab_sync_backend
     /**
      * Getter for folder metadata
      *
-     * @return array Hash array with meta data for each folder
+     * @return array|bool Hash array with meta data for each folder, False on backend failure
      */
     public function folder_meta()
     {
@@ -187,8 +191,8 @@ class kolab_sync_backend
             // get folders activesync config
             $folderdata = $this->storage->get_metadata("*", self::ASYNC_KEY);
 
-            if (empty($folderdata)) {
-                return $this->folder_meta;
+            if (!is_array($folderdata)) {
+                return false;
             }
 
             foreach ($folderdata as $folder => $meta) {
@@ -292,6 +296,11 @@ class kolab_sync_backend
 
         // get folders activesync config
         $metadata = $this->folder_meta();
+
+        if (!is_array($metadata)) {
+            return false;
+        }
+
         $metadata = $metadata[$name];
 
         if ($flag)  {
@@ -676,6 +685,10 @@ class kolab_sync_backend
         // get all folders of specified type
         $folderdata = $this->folder_meta();
 
+        if (!is_array($folderdata)) {
+            return null;
+        }
+
         // check if folders are "subscribed" for activesync
         foreach ($folderdata as $folder => $meta) {
             if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php
index c238a91..2f9fc36 100644
--- a/lib/kolab_sync_data.php
+++ b/lib/kolab_sync_data.php
@@ -194,6 +194,12 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
             $list = array($default['serverId'] => $default);
         }
 
+        // getAllFolders() is called only in FolderSync
+        // throw Syncroton_Exception_Status_FolderSync exception
+        if (!is_array($list)) {
+            throw new Syncroton_Exception_Status_FolderSync(Syncroton_Exception_Status_FolderSync::FOLDER_SERVER_ERROR);
+        }
+
         foreach ($list as $idx => $folder) {
             $list[$idx] = new Syncroton_Model_Folder($folder);
         }
@@ -210,7 +216,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
         $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
 
         if (empty($folders)) {
-            return null;
+            return $folders;
         }
 
         foreach ($folders as $folder) {
@@ -367,7 +373,10 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
     {
         $entry = $this->toKolab($entry, $folderId);
         $entry = $this->createObject($folderId, $entry);
-        // @TODO: throw exception on error
+
+        if (empty($entry)) {
+            throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
+        }
 
         return $entry['uid'];
     }
@@ -391,7 +400,10 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
 
         $entry = $this->toKolab($entry, $folderId, $oldEntry);
         $entry = $this->updateObject($folderId, $serverId,  $entry);
-        // @TODO: throw exception on error
+
+        if (empty($entry)) {
+            throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
+        }
 
         return $entry['uid'];
     }
@@ -405,7 +417,11 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
      */
     public function deleteEntry($folderId, $serverId, $collectionData)
     {
-        $this->deleteObject($folderId, $serverId);
+        $deleted = $this->deleteObject($folderId, $serverId);
+
+        if (!$deleted) {
+            throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
+        }
     }
 
 
@@ -429,6 +445,11 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
     {
         if ($folderid == $this->defaultRootFolder) {
             $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+
+            if (!is_array($folders)) {
+                throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR);
+            }
+
             $folders = array_keys($folders);
         }
         else {
@@ -500,9 +521,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
      */
     public function getChangedEntries($folderId, DateTime $start, DateTime $end = null)
     {
-        $filter = array();
-
-        $filter[] = array('changed', '>', $start);
+        $filter = array(array('changed', '>', $start));
 
         if ($endTimeStamp) {
             $filter[] = array('changed', '<=', $end);
@@ -524,9 +543,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
      */
     public function getChangedEntriesCount($folderId, DateTime $start, DateTime $end = null)
     {
-        $filter = array();
-
-        $filter[] = array('changed', '>', $start);
+        $filter = array(array('changed', '>', $start));
 
         if ($endTimeStamp) {
             $filter[] = array('changed', '<=', $end);
@@ -605,6 +622,11 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
 
         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 {
@@ -620,7 +642,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
 
             $folder = $this->getFolderObject($foldername);
 
-            if ($object = $folder->get_object($entryid)) {
+            if ($folder && ($object = $folder->get_object($entryid))) {
                 $object['_folderid'] = $folderid;
 
                 return $object;
@@ -635,13 +657,18 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
     {
         if ($folderid == $this->defaultRootFolder) {
             $default  = $this->getDefaultFolder();
+
+            if (!is_array($default)) {
+                return null;
+            }
+
             $folderid = isset($default['realid']) ? $default['realid'] : $default['serverId'];
         }
 
         $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
         $folder     = $this->getFolderObject($foldername);
 
-        if ($folder->save($data)) {
+        if ($folder && $folder->save($data)) {
             return $data;
         }
     }
@@ -656,7 +683,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
         if ($object) {
             $folder = $this->getFolderObject($object['_mailbox']);
 
-            if ($folder->save($data)) {
+            if ($folder && $folder->save($data)) {
                 return $data;
             }
         }
@@ -671,7 +698,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
 
         if ($object) {
             $folder = $this->getFolderObject($object['_mailbox']);
-            return $folder->delete($entryid);
+            return $folder && $folder->delete($entryid);
         }
     }
 
@@ -684,6 +711,10 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
      */
     protected function getFolderObject($name)
     {
+        if ($name === null) {
+            return null;
+        }
+
         if (!isset($this->folders[$name])) {
             $this->folders[$name] = kolab_storage::get_folder($name);
         }
@@ -701,6 +732,11 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
     protected function getFolderConfig($name)
     {
         $metadata = $this->backend->folder_meta();
+
+        if (!is_array($metadata)) {
+            return array();
+        }
+
         $deviceid = $this->device->deviceid;
         $config   = $metadata[$name]['FOLDER'][$deviceid];
 
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
index a041d7f..e197b23 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -409,6 +409,10 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
     {
         $list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
 
+        if (!is_array($list)) {
+            throw new Syncroton_Exception_Status_FolderSync(Syncroton_Esception_Status_FolderSync::FOLDER_SERVER_ERROR);
+        }
+
         // device doesn't support multiple folders
         if (!in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) {
             // We'll return max. one folder of supported type
@@ -448,6 +452,10 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
         $list   = $this->backend->folders_list($this->device->deviceid, $this->modelName);
         $result = array();
 
+        if (!is_array($list)) {
+            throw new Syncroton_Exception_NotFound('Folder not found');
+        }
+
         // device supports multiple folders?
         if (in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) {
             if ($list[$folder_id]) {
@@ -527,8 +535,13 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
      */
     public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry)
     {
-        $msg        = $this->parseMessageId($serverId);
-        $message    = $this->getObject($serverId);
+        $msg     = $this->parseMessageId($serverId);
+        $message = $this->getObject($serverId);
+
+        if (empty($message)) {
+            throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
+        }
+
         $is_flagged = !empty($message->headers->flags['FLAGGED']);
 
         // Read status change
@@ -563,6 +576,10 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
         $trash = kolab_sync::get_instance()->config->get('trash_mbox');
         $msg   = $this->parseMessageId($serverId);
 
+        if (empty($msg)) {
+            throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
+        }
+
         // move message to trash folder
         if ($collection->deletesAsMoves
             && strlen($trash)
@@ -632,6 +649,10 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
         $msg     = $this->parseMessageId($itemId);
         $message = $this->getObject($itemId);
 
+        if (!$message) {
+            throw new Syncroton_Exception_Status(Syncroton_Exception_Status::ITEM_NOT_FOUND);
+        }
+
         // Parse message
         list($headers, $body) = $this->backend->parse_mime($body, true);
 
@@ -677,6 +698,10 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
         $msg     = $this->parseMessageId($itemId);
         $message = $this->getObject($itemId);
 
+        if (!$message) {
+            throw new Syncroton_Exception_Status(Syncroton_Exception_Status::ITEM_NOT_FOUND);
+        }
+
         // Parse message
         // @TODO: messages with attachments
         list($headers, $body) = $this->backend->parse_mime($body, true);
@@ -799,6 +824,10 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
             throw new Exception('Empty/invalid search request');
         }
 
+        if (!is_array($folders)) {
+            throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR);
+        }
+
         $result = array();
         // no sorting for best performance
         $sort_by = null;
@@ -928,7 +957,10 @@ 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 = array_keys($folders);
+
+            if (is_array($folders)) {
+                $folders = array_keys($folders);
+            }
         }
 
         return array($folders, $search_str);
@@ -962,7 +994,12 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
     {
         list($folderid, $uid, $part_id) = explode('::', $fileReference);
 
-        $message      = $this->getObject($fileReference);
+        $message = $this->getObject($fileReference);
+
+        if (!$message) {
+            throw new Syncroton_Exception_NotFound('Message not found');
+        }
+
         $part         = $message->mime_parts[$part_id];
         $body         = $message->get_part_content($part_id);
         $content_type = $part->mimetype;


commit 607f8b195acf08c17a22885ca0dd75b6e17de981
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Oct 8 12:14:04 2012 +0200

    Update Syncroton with some small improvements and fixes

diff --git a/lib/ext/Syncroton/Command/FolderCreate.php b/lib/ext/Syncroton/Command/FolderCreate.php
index 1932848..d87cd9d 100644
--- a/lib/ext/Syncroton/Command/FolderCreate.php
+++ b/lib/ext/Syncroton/Command/FolderCreate.php
@@ -20,13 +20,6 @@ class Syncroton_Command_FolderCreate extends Syncroton_Command_Wbxml
     protected $_defaultNameSpace    = 'uri:FolderHierarchy';
     protected $_documentElement     = 'FolderCreate';
     
-    protected $_classes             = array(
-        Syncroton_Data_Factory::CLASS_CALENDAR,
-        Syncroton_Data_Factory::CLASS_CONTACTS,
-        Syncroton_Data_Factory::CLASS_EMAIL,
-        Syncroton_Data_Factory::CLASS_TASKS
-    );
-    
     /**
      * 
      * @var Syncroton_Model_Folder
@@ -47,9 +40,6 @@ class Syncroton_Command_FolderCreate extends Syncroton_Command_Wbxml
             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $syncKey");
         
         if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
-        
-            $this->_syncStateBackend->resetState($this->_device, 'FolderSync');
-        
             return;
         }
         
diff --git a/lib/ext/Syncroton/Command/FolderDelete.php b/lib/ext/Syncroton/Command/FolderDelete.php
index 4259ea8..048372b 100644
--- a/lib/ext/Syncroton/Command/FolderDelete.php
+++ b/lib/ext/Syncroton/Command/FolderDelete.php
@@ -20,14 +20,10 @@ class Syncroton_Command_FolderDelete extends Syncroton_Command_Wbxml
     protected $_defaultNameSpace    = 'uri:FolderHierarchy';
     protected $_documentElement     = 'FolderDelete';
     
-    protected $_classes             = array(
-        Syncroton_Data_Factory::CLASS_CALENDAR,
-        Syncroton_Data_Factory::CLASS_CONTACTS,
-        Syncroton_Data_Factory::CLASS_EMAIL,
-        Syncroton_Data_Factory::CLASS_TASKS
-    );
-    
-    protected $_serverId;
+    /**
+     * @var Syncroton_Model_SyncState
+     */
+    protected $_folder;
     
     /**
      * @var Syncroton_Model_ISyncState
@@ -36,38 +32,41 @@ class Syncroton_Command_FolderDelete extends Syncroton_Command_Wbxml
     
     /**
      * parse FolderDelete request
-     *
      */
     public function handle()
     {
         $xml = simplexml_import_dom($this->_requestBody);
         
-        $syncKey = (int)$xml->SyncKey;
-        
+        // parse xml request
+        $syncKey  = (int)$xml->SyncKey;
+        $serverId = (string)$xml->ServerId;
+        
         if ($this->_logger instanceof Zend_Log) 
             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $syncKey");
         
         if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
-        
-            $this->_syncStateBackend->resetState($this->_device, 'FolderSync');
-        
             return;
         }
         
-        $serverId = (string)$xml->ServerId;
-        
         try {
-            $this->_folder = $this->_folderBackend->getFolder($this->_device, $serverId);
+            $folder = $this->_folderBackend->getFolder($this->_device, $serverId);
             
-            $dataController = Syncroton_Data_Factory::factory($this->_folder->class, $this->_device, $this->_syncTimeStamp);
+            $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
             
-            $dataController->deleteFolder($this->_folder);
-            $this->_folderBackend->delete($this->_folder);
+            // delete folder in data backend
+            $dataController->deleteFolder($folder);
             
         } catch (Syncroton_Exception_NotFound $senf) {
             if ($this->_logger instanceof Zend_Log)
                 $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
+            
+            return;
         }
+        
+        // delete folder in syncState backend
+        $this->_folderBackend->delete($folder);
+        
+        $this->_folder = $folder;
     }
     
     /**
@@ -84,23 +83,19 @@ class Syncroton_Command_FolderDelete extends Syncroton_Command_Wbxml
                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " invalid synckey provided. FolderSync 0 needed.");
             $folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_INVALID_SYNC_KEY));
             
+        } elseif (!$this->_folder instanceof Syncroton_Model_IFolder) {
+            $folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_FOLDER_NOT_FOUND));
+            
         } else {
-            if ($this->_folder instanceof Syncroton_Model_IFolder) {
-                $this->_syncState->counter++;
-                $this->_syncState->lastsync = $this->_syncTimeStamp;
-                
-                // store folder in state backend
-                $this->_syncStateBackend->update($this->_syncState);
-                
-                // create xml output
-                $folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_SUCCESS));
-                $folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
-
-            } else {
-                // create xml output
-                $folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_FOLDER_NOT_FOUND));
-                $folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
-            }
+            $this->_syncState->counter++;
+            $this->_syncState->lastsync = $this->_syncTimeStamp;
+            
+            // store folder in state backend
+            $this->_syncStateBackend->update($this->_syncState);
+            
+            // create xml output
+            $folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_SUCCESS));
+            $folderDelete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
         }
         
         return $this->_outputDom;
diff --git a/lib/ext/Syncroton/Command/FolderSync.php b/lib/ext/Syncroton/Command/FolderSync.php
index 99be44c..8c4917d 100644
--- a/lib/ext/Syncroton/Command/FolderSync.php
+++ b/lib/ext/Syncroton/Command/FolderSync.php
@@ -31,7 +31,6 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
 
     /**
      * some usefull constants for working with the xml files
-     *
      */
     const FOLDERTYPE_GENERIC_USER_CREATED   = 1;
     const FOLDERTYPE_INBOX                  = 2;
@@ -115,12 +114,10 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
 
             return $this->_outputDom;
         }
-        
-        $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_SUCCESS));
-        
+
         $adds = array();
         $deletes = array();
-        
+
         foreach($this->_classes as $class) {
             try {
                 $dataController = Syncroton_Data_Factory::factory($class, $this->_device, $this->_syncTimeStamp);
@@ -130,25 +127,45 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " no data backend defined for class: " . $class);
                 continue;
             }
-            
-            // retrieve all folders available in data backend
-            $serverFolders = $dataController->getAllFolders();
-            
-            // retrieve all folders sent to client
-            $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
-            
+
+            try {
+                // retrieve all folders available in data backend
+                $serverFolders = $dataController->getAllFolders();
+
+                // retrieve all folders sent to client
+                $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
+                
+            } catch (Exception $e) {
+                if ($this->_logger instanceof Zend_Log)
+                    $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Syncing folder hierarchy failed: " . $e->getMessage());
+                if ($this->_logger instanceof Zend_Log)
+                    $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Syncing folder hierarchy failed: " . $e->getTraceAsString());
+
+                // The Status element is global for all collections. If one collection fails,
+                // a failure status MUST be returned for all collections.
+                if ($e instanceof Syncroton_Exception_Status) {
+                    $status = $e->getCode();
+                } else {
+                    $status = Syncroton_Exception_Status_FolderSync::UNKNOWN_ERROR;
+                }
+
+                $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', $status));
+
+                return $this->_outputDom;
+            }
+
             $serverFoldersIds = array_keys($serverFolders);
-            
+
             // is this the first sync?
-            if($this->_syncState->counter == 0) {
+            if ($this->_syncState->counter == 0) {
                 $clientFoldersIds = array();
             } else {
                 $clientFoldersIds = array_keys($clientFolders);
-            } 
-            
+            }
+
             // calculate added entries
             $serverDiff = array_diff($serverFoldersIds, $clientFoldersIds);
-            foreach($serverDiff as $serverFolderId) {
+            foreach ($serverDiff as $serverFolderId) {
                 // have we created a folderObject in syncroton_folder before?
                 if (isset($clientFolders[$serverFolderId])) {
                     $add = $clientFolders[$serverFolderId];
@@ -159,17 +176,19 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
                     unset($add->id);
                 }
                 $add->class = $class;
-                
+
                 $adds[] = $add;
             }
-            
+
             // calculate deleted entries
             $serverDiff = array_diff($clientFoldersIds, $serverFoldersIds);
-            foreach($serverDiff as $serverFolderId) {
+            foreach ($serverDiff as $serverFolderId) {
                 $deletes[] = $clientFolders[$serverFolderId];
             }
         }
-        
+
+        $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_SUCCESS));
+
         $count = count($adds) + /*count($changes) + */count($deletes);
         if($count > 0) {
             $this->_syncState->counter++;
diff --git a/lib/ext/Syncroton/Command/FolderUpdate.php b/lib/ext/Syncroton/Command/FolderUpdate.php
index d219ede..55d1e54 100644
--- a/lib/ext/Syncroton/Command/FolderUpdate.php
+++ b/lib/ext/Syncroton/Command/FolderUpdate.php
@@ -21,6 +21,11 @@ class Syncroton_Command_FolderUpdate extends Syncroton_Command_Wbxml
     protected $_documentElement     = 'FolderUpdate';
     
     /**
+     * @var Syncroton_Model_SyncState
+     */
+    protected $_folder;
+    
+    /**
      * 
      * @var Syncroton_Model_Folder
      */
@@ -40,34 +45,34 @@ class Syncroton_Command_FolderUpdate extends Syncroton_Command_Wbxml
             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $syncKey");
 
         if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
-            
-            $this->_syncStateBackend->resetState($this->_device, 'FolderSync');
-            
             return;
         }
         
-        $folderUpdate = new Syncroton_Model_Folder($xml);
+        $updatedFolder = new Syncroton_Model_Folder($xml);
         
         if ($this->_logger instanceof Zend_Log)
-            $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " parentId: {$folderUpdate->parentId} displayName: {$folderUpdate->displayName}");
+            $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " parentId: {$updatedFolder->parentId} displayName: {$updatedFolder->displayName}");
         
         try {
-            $folder = $this->_folderBackend->getFolder($this->_device, $folderUpdate->serverId);
+            $folder = $this->_folderBackend->getFolder($this->_device, $updatedFolder->serverId);
             
-            $folder->displayName = $folderUpdate->displayName;
-            $folder->parentId    = $folderUpdate->parentId;
+            $folder->displayName = $updatedFolder->displayName;
+            $folder->parentId    = $updatedFolder->parentId;
             
             $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
             
             // update folder in data backend
             $dataController->updateFolder($folder);
-            // update folder status in Syncroton backend
-            $this->_folderBackend->update($folder);
             
         } catch (Syncroton_Exception_NotFound $senf) {
             if ($this->_logger instanceof Zend_Log)
                 $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
+            
+            return;
         }
+        
+        // update folder status in Syncroton backend
+        $this->_folder = $this->_folderBackend->update($folder);
     }
     
     /**
@@ -84,6 +89,9 @@ class Syncroton_Command_FolderUpdate extends Syncroton_Command_Wbxml
                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " invalid synckey provided. FolderSync 0 needed.");
             $folderUpdate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status',  Syncroton_Command_FolderSync::STATUS_INVALID_SYNC_KEY));
             
+        } elseif (!$this->_folder instanceof Syncroton_Model_IFolder) {
+            $folderUpdate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_FOLDER_NOT_FOUND));
+            
         } else {
             $this->_syncState->counter++;
             $this->_syncState->lastsync = $this->_syncTimeStamp;
diff --git a/lib/ext/Syncroton/Command/Ping.php b/lib/ext/Syncroton/Command/Ping.php
index e0c101e..52a2140 100644
--- a/lib/ext/Syncroton/Command/Ping.php
+++ b/lib/ext/Syncroton/Command/Ping.php
@@ -30,17 +30,25 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
     
     protected $_changesDetected = false;
     
-    const PING_TIMEOUT = 60;
+    /**
+     * 
+     * @var int
+     */
+    public static $pingTimeout = 60;
+    
+    /**
+     * 
+     * @var int
+     */
+    public static $quietTime = 180;
     
     /**
-     * Enter description here...
-     *
      * @var Syncroton_Backend_StandAlone_Abstract
      */
     protected $_dataBackend;
 
     protected $_defaultNameSpace = 'uri:Ping';
-    protected $_documentElement = 'Ping';
+    protected $_documentElement  = 'Ping';
     
     protected $_foldersWithChanges = array();
     
@@ -72,7 +80,7 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
                         // does the folder exist?
                         $folder = $this->_folderBackend->getFolder($this->_device, (string)$folderXml->Id);
                         
-                        $folders[] = $folder;                
+                        $folders[$folder->id] = $folder;
                     } catch (Syncroton_Exception_NotFound $senf) {
                         if ($this->_logger instanceof Zend_Log) 
                             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
@@ -80,7 +88,7 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
                         break;
                     }
                 }
-                $this->_device->pingfolder = serialize($folders);
+                $this->_device->pingfolder = serialize(array_keys($folders));
             }
             $this->_device = $this->_deviceBackend->update($this->_device);
         }
@@ -95,17 +103,33 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
         if ($this->_logger instanceof Zend_Log) 
             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folders to monitor($lifeTime / $intervalStart / $intervalEnd / $status): " . print_r($folders, true));
         
-        if($status === self::STATUS_NO_CHANGES_FOUND) {
+        if ($status === self::STATUS_NO_CHANGES_FOUND) {
 
             $folderWithChanges = array();
             
             do {
-                foreach((array) $folders as $folder) {
+                sleep(self::$pingTimeout);
+                
+                $now = new DateTime('now', new DateTimeZone('utc'));
+                
+                foreach ((array) $folders as $folderId) {
+                    $folder = $this->_folderBackend->get($folderId);
+                    
                     $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
                     
                     try {
                         $syncState = $this->_syncStateBackend->getSyncState($this->_device, $folder);
-
+                        
+                        // another process synchronized data of this folder already. let's skip it
+                        if ($syncState->lastsync > $this->_syncTimeStamp) {
+                            continue;
+                        }
+                        
+                        // safe battery time by skipping folders which got synchronied less than self::$quietTime seconds ago
+                        if (($now->getTimestamp() - $syncState->lastsync->getTimestamp()) < self::$quietTime) {
+                            continue;
+                        }
+                        
                         $foundChanges = !!$dataController->getCountOfChanges($this->_contentStateBackend, $folder, $syncState);
                         
                     } catch (Syncroton_Exception_NotFound $e) {
@@ -118,28 +142,22 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
                         $foundChanges = true;
                     }
                     
-                    if($foundChanges == true) {
+                    if ($foundChanges == true) {
                         $this->_foldersWithChanges[] = $folder;
                         $status = self::STATUS_CHANGES_FOUND;
                     }
                 }
                 
-                if($status === self::STATUS_CHANGES_FOUND) {
-                    break;
-                }
-                
-                // another process synchronized data already
-                if(isset($syncState) && $syncState->lastsync > $this->_syncTimeStamp) {
-                    if ($this->_logger instanceof Zend_Log) 
-                        $this->_logger->info(__METHOD__ . '::' . __LINE__ . " terminate ping process. Some other process updated data already.");
+                if ($status === self::STATUS_CHANGES_FOUND) {
                     break;
                 }
                 
-                sleep(self::PING_TIMEOUT);
                 $secondsLeft = $intervalEnd - time();
+                
                 if ($this->_logger instanceof Zend_Log)
                     $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " seconds left: " . $secondsLeft);
-            } while($secondsLeft > 0);
+                
+            } while ($secondsLeft > 0);
         }
         
         if ($this->_logger instanceof Zend_Log) 
diff --git a/lib/ext/Syncroton/Command/Provision.php b/lib/ext/Syncroton/Command/Provision.php
index 417267c..a3e7328 100644
--- a/lib/ext/Syncroton/Command/Provision.php
+++ b/lib/ext/Syncroton/Command/Provision.php
@@ -20,7 +20,6 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
     protected $_defaultNameSpace = 'uri:Provision';
     protected $_documentElement  = 'Provision';
     
-    const POLICYTYPE_XML   = 'MS-WAP-Provisioning-XML';
     const POLICYTYPE_WBXML = 'MS-EAS-Provisioning-WBXML';
     
     const STATUS_SUCCESS                   = 1;
@@ -28,6 +27,12 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
     const STATUS_GENERAL_SERVER_ERROR      = 3;
     const STATUS_DEVICE_MANAGED_EXTERNALLY = 4;
     
+    const STATUS_POLICY_SUCCESS        = 1;
+    const STATUS_POLICY_NOPOLICY       = 2;
+    const STATUS_POLICY_UNKNOWNTYPE    = 3;
+    const STATUS_POLICY_CORRUPTED      = 4;
+    const STATUS_POLICY_WRONGPOLICYKEY = 5;
+    
     const REMOTEWIPE_REQUESTED = 1;
     const REMOTEWIPE_CONFIRMED = 2;
     
@@ -35,7 +40,11 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
     
     protected $_policyType;
     protected $_sendPolicyKey;
-    protected $_deviceInformationSet = false;
+    
+    /**
+     * @var Syncroton_Model_DeviceInformation
+     */
+    protected $_deviceInformation;
     
     /**
      * process the XML file and add, change, delete or fetches data 
@@ -57,8 +66,17 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
         // try to fetch element from Settings namespace
         $settings = $xml->children('uri:Settings');
         if (isset($settings->DeviceInformation) && isset($settings->DeviceInformation->Set)) {
-            // @todo update device informations
-            $this->_deviceInformationSet = true;
+            $this->_deviceInformation = new Syncroton_Model_DeviceInformation($settings->DeviceInformation->Set);
+            
+            $this->_device->model           = $this->_deviceInformation->model;
+            $this->_device->imei            = $this->_deviceInformation->iMEI;
+            $this->_device->friendlyname    = $this->_deviceInformation->friendlyName;
+            $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);
+            
         }
     }
     
@@ -105,31 +123,38 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
         if ($this->_logger instanceof Zend_Log) 
             $this->_logger->info(__METHOD__ . '::' . __LINE__ . ' send policy to device');
         
-        $this->_device->policykey = $this->generatePolicyKey();
-                
         $provision = $sync = $this->_outputDom->documentElement;
-        if ($this->_deviceInformationSet === true) {
+        $provision->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Status', 1));
+
+        // settings
+        if ($this->_deviceInformation instanceof Syncroton_Model_DeviceInformation) {
             $deviceInformation = $provision->appendChild($this->_outputDom->createElementNS('uri:Settings', 'DeviceInformation'));
             $deviceInformation->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Status', 1));
         }
-        $provision->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Status', 1));
         
+        // policies
         $policies = $provision->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Policies'));
         $policy = $policies->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Policy'));
         $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'PolicyType', $this->_policyType));
-        $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Status', 1));
-        $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'PolicyKey', $this->_device->policykey));
-        if ($this->_policyType == self::POLICYTYPE_XML) {
-            $data = $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Data'));
+        
+        if ($this->_policyType != self::POLICYTYPE_WBXML) {
+            $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Status', self::STATUS_POLICY_UNKNOWNTYPE));
+        } elseif (empty($this->_device->policyId)) {
+            $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Status', self::STATUS_POLICY_NOPOLICY));
         } else {
+            $this->_device->policykey = $this->generatePolicyKey();
+            
+            $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Status', self::STATUS_POLICY_SUCCESS));
+            $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'PolicyKey', $this->_device->policykey));
+            
             $data = $policy->appendChild($this->_outputDom->createElementNS('uri:Provision', 'Data'));
             $easProvisionDoc = $data->appendChild($this->_outputDom->createElementNS('uri:Provision', 'EASProvisionDoc'));
             $this->_policyBackend
                 ->get($this->_device->policyId)
                 ->appendXML($easProvisionDoc, $this->_device);
+            
+            $this->_deviceBackend->update($this->_device);
         }
-        
-        $this->_deviceBackend->update($this->_device);
     }
     
     /**
diff --git a/lib/ext/Syncroton/Command/Search.php b/lib/ext/Syncroton/Command/Search.php
index cf9eddd..4913785 100644
--- a/lib/ext/Syncroton/Command/Search.php
+++ b/lib/ext/Syncroton/Command/Search.php
@@ -57,12 +57,9 @@ class Syncroton_Command_Search extends Syncroton_Command_Wbxml
         }
         
         try {
-            $options = $this->_store->options;
-            
             // Search
             $storeResponse = $dataController->search($this->_store);
             $storeResponse->status = self::STATUS_SUCCESS;
-            
         } catch (Exception $e) {
             if ($this->_logger instanceof Zend_Log)
                 $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " search exception: " . $e->getMessage());
diff --git a/lib/ext/Syncroton/Command/SendMail.php b/lib/ext/Syncroton/Command/SendMail.php
index 603a61f..c25f813 100644
--- a/lib/ext/Syncroton/Command/SendMail.php
+++ b/lib/ext/Syncroton/Command/SendMail.php
@@ -33,7 +33,7 @@ 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->_saveInSent    = $this->_requestParameters['saveInSent'];
             $this->_replaceMime   = false;
             
             $this->_source = array(
diff --git a/lib/ext/Syncroton/Command/Settings.php b/lib/ext/Syncroton/Command/Settings.php
index 6d779e3..e322355 100644
--- a/lib/ext/Syncroton/Command/Settings.php
+++ b/lib/ext/Syncroton/Command/Settings.php
@@ -30,8 +30,11 @@ class Syncroton_Command_Settings extends Syncroton_Command_Wbxml
     protected $_defaultNameSpace = 'uri:Settings';
     protected $_documentElement  = 'Settings';
     
-    protected $_deviceInformationSet     = false;
-    protected $_userInformationRequested = false;
+    /**
+     * @var Syncroton_Model_DeviceInformation
+     */
+    protected $_deviceInformation;
+    protected $_userInformationRequested = false;
     
     
     /**
@@ -43,15 +46,15 @@ class Syncroton_Command_Settings extends Syncroton_Command_Wbxml
         $xml = simplexml_import_dom($this->_requestBody);
         
         if(isset($xml->DeviceInformation->Set)) {
-            $this->_deviceInformationSet = true;
-            
-            $this->_device->model           = (string)$xml->DeviceInformation->Set->Model;
-            $this->_device->imei            = (string)$xml->DeviceInformation->Set->IMEI;
-            $this->_device->friendlyname    = (string)$xml->DeviceInformation->Set->FriendlyName;
-            $this->_device->os              = (string)$xml->DeviceInformation->Set->OS;
-            $this->_device->oslanguage      = (string)$xml->DeviceInformation->Set->OSLanguage;
-            $this->_device->phonenumber     = (string)$xml->DeviceInformation->Set->PhoneNumber;
-            
+            $this->_deviceInformation = new Syncroton_Model_DeviceInformation($xml->DeviceInformation->Set);
+            
+            $this->_device->model           = $this->_deviceInformation->model;
+            $this->_device->imei            = $this->_deviceInformation->iMEI;
+            $this->_device->friendlyname    = $this->_deviceInformation->friendlyName;
+            $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);
         }
         
@@ -71,7 +74,7 @@ class Syncroton_Command_Settings extends Syncroton_Command_Wbxml
         
         $settings->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Status', self::STATUS_SUCCESS));
         
-        if($this->_deviceInformationSet === true) {
+        if ($this->_deviceInformation instanceof Syncroton_Model_DeviceInformation) {
             $deviceInformation = $settings->appendChild($this->_outputDom->createElementNS('uri:Settings', 'DeviceInformation'));
             $set = $deviceInformation->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Set'));
             $set->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Status', self::STATUS_SUCCESS));
diff --git a/lib/ext/Syncroton/Command/Sync.php b/lib/ext/Syncroton/Command/Sync.php
index ea28966..3d6ddd6 100644
--- a/lib/ext/Syncroton/Command/Sync.php
+++ b/lib/ext/Syncroton/Command/Sync.php
@@ -460,58 +460,72 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
                         $serverModifications = $collectionData->syncState->pendingdata;
                         
                     } else {
-                        // fetch entries added since last sync
-                        $allClientEntries = $this->_contentStateBackend->getFolderState($this->_device, $collectionData->folder);
-                        $allServerEntries = $dataController->getServerEntries($collectionData->collectionId, $collectionData->options['filterType']);
-                        
-                        // add entries
-                        $serverDiff = array_diff($allServerEntries, $allClientEntries);
-                        // add entries which produced problems during delete from client
-                        $serverModifications['added'] = $clientModifications['forceAdd'];
-                        // add entries not yet sent to client
-                        $serverModifications['added'] = array_unique(array_merge($serverModifications['added'], $serverDiff));
-            
-                        # @todo still needed?
-                        foreach($serverModifications['added'] as $id => $serverId) {
-                            // skip entries added by client during this sync session
-                            if(isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) {
-                                if ($this->_logger instanceof Zend_Log)
-                                    $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId);
-                                unset($serverModifications['added'][$id]);
-                            }
-                        }
-            
-                        // entries to be deleted
-                        $serverModifications['deleted'] = array_diff($allClientEntries, $allServerEntries);
-            
-                        // fetch entries changed since last sync
-                        $serverModifications['changed'] = $dataController->getChangedEntries($collectionData->collectionId, $collectionData->syncState->lastsync, $this->_syncTimeStamp);
-                        $serverModifications['changed'] = array_merge($serverModifications['changed'], $clientModifications['forceChange']);
-            
-                        foreach($serverModifications['changed'] as $id => $serverId) {
-                            // skip entry, if it got changed by client during current sync
-                            if(isset($clientModifications['changed'][$serverId]) && !isset($clientModifications['forceChange'][$serverId])) {
-                                if ($this->_logger instanceof Zend_Log)
-                                    $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId);
-                                unset($serverModifications['changed'][$id]);
+                        try {
+                            // fetch entries added since last sync
+                            $allClientEntries = $this->_contentStateBackend->getFolderState($this->_device, $collectionData->folder);
+                            $allServerEntries = $dataController->getServerEntries($collectionData->collectionId, $collectionData->options['filterType']);
+
+                            // add entries
+                            $serverDiff = array_diff($allServerEntries, $allClientEntries);
+                            // add entries which produced problems during delete from client
+                            $serverModifications['added'] = $clientModifications['forceAdd'];
+                            // add entries not yet sent to client
+                            $serverModifications['added'] = array_unique(array_merge($serverModifications['added'], $serverDiff));
+
+                            // @todo still needed?
+                            foreach($serverModifications['added'] as $id => $serverId) {
+                                // skip entries added by client during this sync session
+                                if(isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) {
+                                    if ($this->_logger instanceof Zend_Log)
+                                        $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId);
+                                    unset($serverModifications['added'][$id]);
+                                }
                             }
-                            // skip entry, make sure we don't sent entries already added by client in this request
-                            else if (isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) {
-                                if ($this->_logger instanceof Zend_Log)
-                                    $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId);
-                                unset($serverModifications['changed'][$id]);
+
+                            // entries to be deleted
+                            $serverModifications['deleted'] = array_diff($allClientEntries, $allServerEntries);
+
+                            // fetch entries changed since last sync
+                            $serverModifications['changed'] = $dataController->getChangedEntries($collectionData->collectionId, $collectionData->syncState->lastsync, $this->_syncTimeStamp);
+                            $serverModifications['changed'] = array_merge($serverModifications['changed'], $clientModifications['forceChange']);
+
+                            foreach($serverModifications['changed'] as $id => $serverId) {
+                                // skip entry, if it got changed by client during current sync
+                                if(isset($clientModifications['changed'][$serverId]) && !isset($clientModifications['forceChange'][$serverId])) {
+                                    if ($this->_logger instanceof Zend_Log)
+                                        $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId);
+                                    unset($serverModifications['changed'][$id]);
+                                }
+                                // skip entry, make sure we don't sent entries already added by client in this request
+                                else if (isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) {
+                                    if ($this->_logger instanceof Zend_Log)
+                                        $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId);
+                                    unset($serverModifications['changed'][$id]);
+                                }
                             }
+
+                            // entries comeing in scope are already in $serverModifications['added'] and do not need to
+                            // be send with $serverCanges
+                            $serverModifications['changed'] = array_diff($serverModifications['changed'], $serverModifications['added']);
+                        } catch (Exception $e) {
+                            if ($this->_logger instanceof Zend_Log)
+                                $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getMessage());
+                            if ($this->_logger instanceof Zend_Log)
+                                $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getTraceAsString());
+                            
+                            // Prevent from removing client entries when getServerEntries() fails
+                            // @todo: should we set Status and break the loop here?
+                            $serverModifications = array(
+                                'added'   => array(),
+                                'changed' => array(),
+                                'deleted' => array(),
+                            );
                         }
-            
-                        // entries comeing in scope are already in $serverModifications['added'] and do not need to
-                        // be send with $serverCanges
-                        $serverModifications['changed'] = array_diff($serverModifications['changed'], $serverModifications['added']);
                     }
-                
+
                     if ($this->_logger instanceof Zend_Log)
                         $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');
                 }
-                
 
                 // collection header
                 $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
diff --git a/lib/ext/Syncroton/Command/Wbxml.php b/lib/ext/Syncroton/Command/Wbxml.php
index f87a71e..cab5cc9 100644
--- a/lib/ext/Syncroton/Command/Wbxml.php
+++ b/lib/ext/Syncroton/Command/Wbxml.php
@@ -161,10 +161,6 @@ abstract class Syncroton_Command_Wbxml implements Syncroton_Command_ICommand
         
         if ($this->_skipValidatePolicyKey != true) {
             if (!empty($this->_device->policyId)) {
-                if ($this->_policyKey === null) {
-                    throw new Syncroton_Exception_PolicyKeyMissing();
-                } 
-                
                 $policy = $this->_policyBackend->get($this->_device->policyId);
                 
                 if($policy->policyKey != $this->_policyKey) {
diff --git a/lib/ext/Syncroton/Data/IDataSearch.php b/lib/ext/Syncroton/Data/IDataSearch.php
index d499efe..5546f8d 100644
--- a/lib/ext/Syncroton/Data/IDataSearch.php
+++ b/lib/ext/Syncroton/Data/IDataSearch.php
@@ -19,16 +19,6 @@
 interface Syncroton_Data_IDataSearch
 {
     /**
-     * Returns properties of search entry
-     *
-     * @param string $longId  Entry identifier
-     * @param array  $options Search options
-     *
-     * @return Syncroton_Model_IEntry
-     */
-    public function getSearchEntry($longId, $options);
-
-    /**
      * Search command handler
      *
      * @param Syncroton_Model_StoreRequest $store   Search query parameters
diff --git a/lib/ext/Syncroton/Server.php b/lib/ext/Syncroton/Server.php
index 7cd61bb..a9bc110 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -102,7 +102,7 @@ class Syncroton_Server
         // get user device
         $device = $this->_getUserDevice($this->_userId, $requestParameters);
         
-        if ($requestParameters['contentType'] == 'application/vnd.ms-sync.wbxml') {
+        if ($requestParameters['contentType'] == 'application/vnd.ms-sync.wbxml' || $requestParameters['contentType'] == 'application/vnd.ms-sync') {
             // decode wbxml request
             try {
                 $decoder = new Syncroton_Wbxml_Decoder($this->_body);
@@ -129,12 +129,6 @@ class Syncroton_Server
         
             $response = $command->getResponse();
             
-        } catch (Syncroton_Exception_PolicyKeyMissing $sepkm) {
-            if ($this->_logger instanceof Zend_Log) 
-                $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " X-MS-POLICYKEY missing (" . $_command. ')');
-            
-            header("HTTP/1.1 400 header X-MS-POLICYKEY not found");
-            
         } catch (Syncroton_Exception_ProvisioningNeeded $sepn) {
             if ($this->_logger instanceof Zend_Log) 
                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " provisioning needed");
@@ -191,103 +185,117 @@ class Syncroton_Server
      */
     protected function _getRequestParameters(Zend_Controller_Request_Http $request)
     {
-        if(count($_GET) == 1) {
-            $arrayKeys = array_keys($_GET);
-            
-            $base64Decoded = base64_decode($arrayKeys[0]);
+        if (!isset($_GET['DeviceId'])) {
+            $commands = array(
+                0  => 'Sync',
+                1  => 'SendMail',
+                2  => 'SmartForward',
+                3  => 'SmartReply',
+                4  => 'GetAttachment',
+                9  => 'FolderSync',
+                10 => 'FolderCreate',
+                11 => 'FolderDelete',
+                12 => 'FolderUpdate',
+                13 => 'MoveItems',
+                14 => 'GetItemEstimate',
+                15 => 'MeetingResponse',
+                16 => 'Search',
+                17 => 'Settings',
+                18 => 'Ping',
+                19 => 'ItemOperations',
+                20 => 'Provision',
+                21 => 'ResolveRecipients',
+                22 => 'ValidateCert'
+            );
+            
+            // find the first $_GET parameter which has a key but no value
+            // the key are the request parameters
+            foreach ($_GET as $key => $value) {
+                if (empty($value)) {
+                    $requestParameters = $key;
+                    break;
+                }
+            }
             
             $stream = fopen("php://temp", 'r+');
-            fwrite($stream, base64_decode($arrayKeys[0]));
+            fwrite($stream, base64_decode($requestParameters));
             rewind($stream);
 
-            #fpassthru($stream);rewind($stream);
+            // unpack the first 4 bytes
+            $unpacked = unpack('CprotocolVersion/Ccommand/vlocale', fread($stream, 4));
             
-            $protocolVersion = ord(fread($stream, 1));
-            switch (ord(fread($stream, 1))) {
-                case 0:
-                    $command = 'Sync';
-                    break;
-                case 1:
-                    $command = 'SendMail';
-                    break;
-                case 2:
-                    $command = 'SmartForward';
-                    break;
-                case 3:
-                    $command = 'SmartReply';
-                    break;
-                case 4:
-                    $command = 'GetAttachment';
-                    break;
-                case 9:
-                    $command = 'FolderSync';
-                    break;
-                case 10:
-                    $command = 'FolderCreate';
-                    break;
-                case 11:
-                    $command = 'FolderDelete';
-                    break;
-                case 12:
-                    $command = 'FolderUpdate';
-                    break;
-                case 13:
-                    $command = 'MoveItems';
-                    break;
-                case 14:
-                    $command = 'GetItemEstimate';
-                    break;
-                case 15:
-                    $command = 'MeetingResponse';
-                    break;
-                case 16:
-                    $command = 'Search';
-                    break;
-                case 17:
-                    $command = 'Settings';
-                    break;
-                case 18:
-                    $command = 'Ping';
-                    break;
-                case 19:
-                    $command = 'ItemOperations';
-                    break;
-                case 20:
-                    $command = 'Provision';
-                    break;
-                case 21:
-                    $command = 'ResolveRecipients';
-                    break;
-                case 22:
-                    $command = 'ValidateCert';
-                    break;
-            }
+            $protocolVersion = $unpacked['protocolVersion'];
+            $command = $commands[$unpacked['command']];
+            $locale = $unpacked['locale'];
             
-            $locale = fread($stream, 2);
+            // unpack deviceId
+            $length = ord(fread($stream, 1));
+            if ($length > 0) {
+                $unpacked = unpack('H' . $length*2 . 'string', fread($stream, $length));
+                $deviceId = $unpacked['string'];
+            }
             
-            $deviceIdLength = ord(fread($stream, 1));
-            if ($deviceIdLength > 0) {
-                $deviceId = fread($stream, $deviceIdLength);
-            } 
+            // unpack policyKey
+            $length = ord(fread($stream, 1));
+            if ($length > 0) {
+                $unpacked  = unpack('Vstring', fread($stream, $length));
+                $policyKey = $unpacked['string'];
+            }
+
+            // unpack device type
+            $length = ord(fread($stream, 1));
+            if ($length > 0) {
+                $unpacked   = unpack('A' . $length . 'string', fread($stream, $length));
+                $deviceType = $unpacked['string'];
+            }
             
-            $policyKeyLength = ord(fread($stream, 1));
-            if ($policyKeyLength > 0) {
-                $policyKey = fread($stream, 4);
+            while (! feof($stream)) {
+                $tag    = ord(fread($stream, 1));
+                $length = ord(fread($stream, 1));
+                
+                switch ($tag) {
+                    case 0:
+                        $unpacked = unpack('A' . $length . 'string', fread($stream, $length));
+                        
+                        $attachmentName = $unpacked['string'];
+                        break;
+                        
+                    case 1:
+                        $unpacked = unpack('A' . $length . 'string', fread($stream, $length));
+                        
+                        $collectionId = $unpacked['string'];
+                        break;
+                        
+                    case 3:
+                        $unpacked = unpack('A' . $length . 'string', fread($stream, $length));
+                        
+                        $itemId = $unpacked['string'];
+                        break;
+                        
+                    case 7:
+                        $options = ord(fread($stream, 1));
+                        
+                        $saveInSent      = !!($options & 0x01); 
+                        $acceptMultiPart = !!($options & 0x02);
+                        break;
+                        
+                    default:
+                        if ($this->_logger instanceof Zend_Log)
+                            $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " found unhandled command parameters");
+                        
+                }
             }
-            
-            $deviceTypeLength = ord(fread($stream, 1));
-            $deviceType = fread($stream, $deviceTypeLength);
-            
-            // @todo parse command parameters 
+             
             $result = array(
                 'protocolVersion' => $protocolVersion,
                 'command'         => $command,
                 'deviceId'        => $deviceId,
-                'deviceType'      => $deviceType,
-                'saveInSent'      => null,
-                'collectionId'    => null,
-                'itemId'          => null,
-                'attachmentName'  => null
+                'deviceType'      => isset($deviceType)     ? $deviceType     : null,
+                'policyKey'       => isset($policyKey)      ? $policyKey      : null,
+                'saveInSent'      => isset($saveInSent)     ? $saveInSent     : false,
+                'collectionId'    => isset($collectionId)   ? $collectionId   : null,
+                'itemId'          => isset($itemId)         ? $itemId         : null,
+                'attachmentName'  => isset($attachmentName) ? $attachmentName : null
             );
         } else {
             $result = array(
@@ -295,7 +303,8 @@ class Syncroton_Server
                 'command'         => $request->getQuery('Cmd'),
                 'deviceId'        => $request->getQuery('DeviceId'),
                 'deviceType'      => $request->getQuery('DeviceType'),
-                'saveInSent'      => $request->getQuery('SaveInSent'),
+                'policyKey'       => $request->getServer('HTTP_X_MS_POLICYKEY'),
+                'saveInSent'      => $request->getQuery('SaveInSent') == 'T',
                 'collectionId'    => $request->getQuery('CollectionId'),
                 'itemId'          => $request->getQuery('ItemId'),
                 'attachmentName'  => $request->getQuery('AttachmentName'),
@@ -303,7 +312,6 @@ class Syncroton_Server
         }
         
         $result['userAgent']   = $request->getServer('HTTP_USER_AGENT', $result['deviceType']);
-        $result['policyKey']   = $request->getServer('HTTP_X_MS_POLICYKEY');
         $result['contentType'] = $request->getServer('CONTENT_TYPE');
         
         return $result;





More information about the commits mailing list