Merge branch 'master' of http://git.syncroton.org/Syncroton into SyncrotonMerge
[tine20] / tine20 / library / Syncroton / lib / Syncroton / Command / Ping.php
1 <?php
2 /**
3  * Syncroton
4  *
5  * @package     Syncroton
6  * @subpackage  Command
7  * @license     http://www.tine20.org/licenses/lgpl.html LGPL Version 3
8  * @copyright   Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  */
11
12 /**
13  * class to handle ActiveSync Ping command
14  *
15  * @package     Syncroton
16  * @subpackage  Command
17  */
18 class Syncroton_Command_Ping extends Syncroton_Command_Wbxml 
19 {
20     const STATUS_NO_CHANGES_FOUND           = 1;
21     const STATUS_CHANGES_FOUND              = 2;
22     const STATUS_MISSING_PARAMETERS         = 3;
23     const STATUS_REQUEST_FORMAT_ERROR       = 4;
24     const STATUS_INTERVAL_TO_GREAT_OR_SMALL = 5;
25     const STATUS_TO_MUCH_FOLDERS            = 6;
26     const STATUS_FOLDER_NOT_FOUND           = 7;
27     const STATUS_GENERAL_ERROR              = 8;
28     
29     protected $_skipValidatePolicyKey = true;
30     
31     protected $_changesDetected = false;
32     
33     /**
34      * @var Syncroton_Backend_StandAlone_Abstract
35      */
36     protected $_dataBackend;
37
38     protected $_defaultNameSpace = 'uri:Ping';
39     protected $_documentElement  = 'Ping';
40     
41     protected $_foldersWithChanges = array();
42     
43     /**
44      * process the XML file and add, change, delete or fetches data 
45      *
46      * @todo can we get rid of LIBXML_NOWARNING
47      * @todo we need to stored the initial data for folders and lifetime as the phone is sending them only when they change
48      * @return resource
49      */
50     public function handle()
51     {
52         $intervalStart = time();
53         $status = self::STATUS_NO_CHANGES_FOUND;
54         
55         // the client does not send a wbxml document, if the Ping parameters did not change compared with the last request
56         if($this->_requestBody instanceof DOMDocument) {
57             $xml = simplexml_import_dom($this->_requestBody);
58             $xml->registerXPathNamespace('Ping', 'Ping');    
59
60             if(isset($xml->HeartBeatInterval)) {
61                 $this->_device->pinglifetime = (int)$xml->HeartBeatInterval;
62             }
63             
64             if(isset($xml->Folders->Folder)) {
65                 $folders = array();
66                 foreach ($xml->Folders->Folder as $folderXml) {
67                     try {
68                         // does the folder exist?
69                         $folder = $this->_folderBackend->getFolder($this->_device, (string)$folderXml->Id);
70                         
71                         $folders[$folder->id] = $folder;
72                     } catch (Syncroton_Exception_NotFound $senf) {
73                         if ($this->_logger instanceof Zend_Log) 
74                             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
75                         $status = self::STATUS_FOLDER_NOT_FOUND;
76                         break;
77                     }
78                 }
79                 $this->_device->pingfolder = serialize(array_keys($folders));
80             }
81             $this->_device = $this->_deviceBackend->update($this->_device);
82         }
83         
84         $lifeTime = $this->_device->pinglifetime;
85         #Tinebase_Core::setExecutionLifeTime($lifeTime);
86         
87         $intervalEnd = $intervalStart + $lifeTime;
88         $secondsLeft = $intervalEnd;
89         $folders = unserialize($this->_device->pingfolder);
90         
91         if ($this->_logger instanceof Zend_Log) 
92             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folders to monitor($lifeTime / $intervalStart / $intervalEnd / $status): " . print_r($folders, true));
93         
94         if ($status === self::STATUS_NO_CHANGES_FOUND) {
95
96             $folderWithChanges = array();
97             
98             do {
99                 // take a break to save battery lifetime
100                 sleep(Syncroton_Registry::getPingTimeout());
101                 
102                 $now = new DateTime('now', new DateTimeZone('utc'));
103                 
104                 foreach ((array) $folders as $folderId) {
105                     try {
106                         $folder         = $this->_folderBackend->get($folderId);
107                         $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
108                     } catch (Syncroton_Exception_NotFound $e) {
109                         if ($this->_logger instanceof Zend_Log)
110                             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
111                         $status = self::STATUS_FOLDER_NOT_FOUND;
112                         break;
113                     } catch (Exception $e) {
114                         if ($this->_logger instanceof Zend_Log)
115                             $this->_logger->err(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
116                         // do nothing, maybe temporal issue, should we stop?
117                         continue;
118                     }
119
120                     try {
121                         $syncState = $this->_syncStateBackend->getSyncState($this->_device, $folder);
122                         \r
123                         // another process synchronized data of this folder already. let's skip it\r
124                         if ($syncState->lastsync > $this->_syncTimeStamp) {\r
125                             continue;
126                         }
127                         \r
128                         // safe battery time by skipping folders which got synchronied less than Syncroton_Registry::getQuietTime() seconds ago\r
129                         if (($now->getTimestamp() - $syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {\r
130                             continue;\r
131                         }
132                         
133                         $foundChanges = !!$dataController->getCountOfChanges($this->_contentStateBackend, $folder, $syncState);
134                         
135                     } catch (Syncroton_Exception_NotFound $e) {
136                         // folder got never synchronized to client
137                         if ($this->_logger instanceof Zend_Log) 
138                             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
139                         if ($this->_logger instanceof Zend_Log) 
140                             $this->_logger->info(__METHOD__ . '::' . __LINE__ . ' syncstate not found. enforce sync for folder: ' . $folder->serverId);
141                         
142                         $foundChanges = true;
143                     }
144                     
145                     if ($foundChanges == true) {
146                         $this->_foldersWithChanges[] = $folder;
147                         $status = self::STATUS_CHANGES_FOUND;
148                     }
149                 }
150                 
151                 if ($status != self::STATUS_NO_CHANGES_FOUND) {
152                     break;
153                 }
154                 
155                 $secondsLeft = $intervalEnd - time();
156                 
157                 if ($this->_logger instanceof Zend_Log)
158                     $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " seconds left: " . $secondsLeft);
159                 
160             } while ($secondsLeft > 0);
161         }
162         
163         if ($this->_logger instanceof Zend_Log) 
164             $this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " Lifetime: $lifeTime SecondsLeft: $secondsLeft Status: $status)");
165         
166         $ping = $this->_outputDom->documentElement;
167         $ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Status', $status));
168         if($status === self::STATUS_CHANGES_FOUND) {
169             $folders = $ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folders'));
170             
171             foreach($this->_foldersWithChanges as $changedFolder) {
172                 $folder = $folders->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folder', $changedFolder->serverId));
173                 if ($this->_logger instanceof Zend_Log) 
174                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " changes in folder: " . $changedFolder->serverId);
175             }
176         }
177     }
178         
179     /**
180      * generate ping command response
181      *
182      */
183     public function getResponse()
184     {
185         return $this->_outputDom;
186     }
187 }