1 | <?php |
---|
2 | /*********************************************** |
---|
3 | * File : devicemanager.php |
---|
4 | * Project : Z-Push |
---|
5 | * Descr : Manages device relevant data, provisioning, |
---|
6 | * loop detection and device states. |
---|
7 | * The DeviceManager uses a IStateMachine |
---|
8 | * implementation with IStateMachine::DEVICEDATA |
---|
9 | * to save device relevant data. |
---|
10 | * |
---|
11 | * Created : 11.04.2011 |
---|
12 | * |
---|
13 | * Copyright 2007 - 2012 Zarafa Deutschland GmbH |
---|
14 | * |
---|
15 | * This program is free software: you can redistribute it and/or modify |
---|
16 | * it under the terms of the GNU Affero General Public License, version 3, |
---|
17 | * as published by the Free Software Foundation with the following additional |
---|
18 | * term according to sec. 7: |
---|
19 | * |
---|
20 | * According to sec. 7 of the GNU Affero General Public License, version 3, |
---|
21 | * the terms of the AGPL are supplemented with the following terms: |
---|
22 | * |
---|
23 | * "Zarafa" is a registered trademark of Zarafa B.V. |
---|
24 | * "Z-Push" is a registered trademark of Zarafa Deutschland GmbH |
---|
25 | * The licensing of the Program under the AGPL does not imply a trademark license. |
---|
26 | * Therefore any rights, title and interest in our trademarks remain entirely with us. |
---|
27 | * |
---|
28 | * However, if you propagate an unmodified version of the Program you are |
---|
29 | * allowed to use the term "Z-Push" to indicate that you distribute the Program. |
---|
30 | * Furthermore you may use our trademarks where it is necessary to indicate |
---|
31 | * the intended purpose of a product or service provided you use it in accordance |
---|
32 | * with honest practices in industrial or commercial matters. |
---|
33 | * If you want to propagate modified versions of the Program under the name "Z-Push", |
---|
34 | * you may only do so if you have a written permission by Zarafa Deutschland GmbH |
---|
35 | * (to acquire a permission please contact Zarafa at trademark@zarafa.com). |
---|
36 | * |
---|
37 | * This program is distributed in the hope that it will be useful, |
---|
38 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
39 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
40 | * GNU Affero General Public License for more details. |
---|
41 | * |
---|
42 | * You should have received a copy of the GNU Affero General Public License |
---|
43 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
---|
44 | * |
---|
45 | * Consult LICENSE file for details |
---|
46 | ************************************************/ |
---|
47 | |
---|
48 | class DeviceManager { |
---|
49 | // broken message indicators |
---|
50 | const MSG_BROKEN_UNKNOWN = 1; |
---|
51 | const MSG_BROKEN_CAUSINGLOOP = 2; |
---|
52 | const MSG_BROKEN_SEMANTICERR = 4; |
---|
53 | |
---|
54 | private $device; |
---|
55 | private $deviceHash; |
---|
56 | private $statemachine; |
---|
57 | private $stateManager; |
---|
58 | private $incomingData = 0; |
---|
59 | private $outgoingData = 0; |
---|
60 | |
---|
61 | private $windowSize; |
---|
62 | private $latestFolder; |
---|
63 | |
---|
64 | private $loopdetection; |
---|
65 | private $hierarchySyncRequired; |
---|
66 | |
---|
67 | /** |
---|
68 | * Constructor |
---|
69 | * |
---|
70 | * @access public |
---|
71 | */ |
---|
72 | public function DeviceManager() { |
---|
73 | $this->statemachine = ZPush::GetStateMachine(); |
---|
74 | $this->deviceHash = false; |
---|
75 | $this->devid = Request::GetDeviceID(); |
---|
76 | $this->windowSize = array(); |
---|
77 | $this->latestFolder = false; |
---|
78 | $this->hierarchySyncRequired = false; |
---|
79 | |
---|
80 | // only continue if deviceid is set |
---|
81 | if ($this->devid) { |
---|
82 | $this->device = new ASDevice($this->devid, Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent()); |
---|
83 | $this->loadDeviceData(); |
---|
84 | |
---|
85 | ZPush::GetTopCollector()->SetUserAgent($this->device->GetDeviceUserAgent()); |
---|
86 | } |
---|
87 | else |
---|
88 | throw new FatalNotImplementedException("Can not proceed without a device id."); |
---|
89 | |
---|
90 | $this->loopdetection = new LoopDetection(); |
---|
91 | $this->loopdetection->ProcessLoopDetectionInit(); |
---|
92 | $this->loopdetection->ProcessLoopDetectionPreviousConnectionFailed(); |
---|
93 | |
---|
94 | $this->stateManager = new StateManager(); |
---|
95 | $this->stateManager->SetDevice($this->device); |
---|
96 | } |
---|
97 | |
---|
98 | /** |
---|
99 | * Returns the StateManager for the current device |
---|
100 | * |
---|
101 | * @access public |
---|
102 | * @return StateManager |
---|
103 | */ |
---|
104 | public function GetStateManager() { |
---|
105 | return $this->stateManager; |
---|
106 | } |
---|
107 | |
---|
108 | /**---------------------------------------------------------------------------------------------------------- |
---|
109 | * Device operations |
---|
110 | */ |
---|
111 | |
---|
112 | /** |
---|
113 | * Announces amount of transmitted data to the DeviceManager |
---|
114 | * |
---|
115 | * @param int $datacounter |
---|
116 | * |
---|
117 | * @access public |
---|
118 | * @return boolean |
---|
119 | */ |
---|
120 | public function SentData($datacounter) { |
---|
121 | // TODO save this somewhere |
---|
122 | $this->incomingData = Request::GetContentLength(); |
---|
123 | $this->outgoingData = $datacounter; |
---|
124 | } |
---|
125 | |
---|
126 | /** |
---|
127 | * Called at the end of the request |
---|
128 | * Statistics about received/sent data is saved here |
---|
129 | * |
---|
130 | * @access public |
---|
131 | * @return boolean |
---|
132 | */ |
---|
133 | public function Save() { |
---|
134 | // TODO save other stuff |
---|
135 | |
---|
136 | // check if previousily ignored messages were synchronized for the current folder |
---|
137 | // on multifolder operations of AS14 this is done by setLatestFolder() |
---|
138 | if ($this->latestFolder !== false) |
---|
139 | $this->checkBrokenMessages($this->latestFolder); |
---|
140 | |
---|
141 | // update the user agent and AS version on the device |
---|
142 | $this->device->SetUserAgent(Request::GetUserAgent()); |
---|
143 | $this->device->SetASVersion(Request::GetProtocolVersion()); |
---|
144 | |
---|
145 | // data to be saved |
---|
146 | $data = $this->device->GetData(); |
---|
147 | if ($data && Request::IsValidDeviceID()) { |
---|
148 | ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data changed"); |
---|
149 | |
---|
150 | try { |
---|
151 | // check if this is the first time the device data is saved and it is authenticated. If so, link the user to the device id |
---|
152 | if ($this->device->IsNewDevice() && RequestProcessor::isUserAuthenticated()) { |
---|
153 | ZLog::Write(LOGLEVEL_INFO, sprintf("Linking device ID '%s' to user '%s'", $this->devid, $this->device->GetDeviceUser())); |
---|
154 | $this->statemachine->LinkUserDevice($this->device->GetDeviceUser(), $this->devid); |
---|
155 | } |
---|
156 | |
---|
157 | if (RequestProcessor::isUserAuthenticated() || $this->device->GetForceSave() ) { |
---|
158 | $this->statemachine->SetState($data, $this->devid, IStateMachine::DEVICEDATA); |
---|
159 | ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data saved"); |
---|
160 | } |
---|
161 | } |
---|
162 | catch (StateNotFoundException $snfex) { |
---|
163 | ZLog::Write(LOGLEVEL_ERROR, "DeviceManager->Save(): Exception: ". $snfex->getMessage()); |
---|
164 | } |
---|
165 | } |
---|
166 | |
---|
167 | // remove old search data |
---|
168 | $oldpid = $this->loopdetection->ProcessLoopDetectionGetOutdatedSearchPID(); |
---|
169 | if ($oldpid) { |
---|
170 | ZPush::GetBackend()->GetSearchProvider()->TerminateSearch($oldpid); |
---|
171 | } |
---|
172 | |
---|
173 | // we terminated this process |
---|
174 | if ($this->loopdetection) |
---|
175 | $this->loopdetection->ProcessLoopDetectionTerminate(); |
---|
176 | |
---|
177 | return true; |
---|
178 | } |
---|
179 | |
---|
180 | /** |
---|
181 | * Newer mobiles send extensive device informations with the Settings command |
---|
182 | * These informations are saved in the ASDevice |
---|
183 | * |
---|
184 | * @param SyncDeviceInformation $deviceinformation |
---|
185 | * |
---|
186 | * @access public |
---|
187 | * @return boolean |
---|
188 | */ |
---|
189 | public function SaveDeviceInformation($deviceinformation) { |
---|
190 | ZLog::Write(LOGLEVEL_DEBUG, "Saving submitted device information"); |
---|
191 | |
---|
192 | // set the user agent |
---|
193 | if (isset($deviceinformation->useragent)) |
---|
194 | $this->device->SetUserAgent($deviceinformation->useragent); |
---|
195 | |
---|
196 | // save other informations |
---|
197 | foreach (array("model", "imei", "friendlyname", "os", "oslanguage", "phonenumber", "mobileoperator", "enableoutboundsms") as $info) { |
---|
198 | if (isset($deviceinformation->$info) && $deviceinformation->$info != "") { |
---|
199 | $this->device->__set("device".$info, $deviceinformation->$info); |
---|
200 | } |
---|
201 | } |
---|
202 | return true; |
---|
203 | } |
---|
204 | |
---|
205 | /**---------------------------------------------------------------------------------------------------------- |
---|
206 | * Provisioning operations |
---|
207 | */ |
---|
208 | |
---|
209 | /** |
---|
210 | * Checks if the sent policykey matches the latest policykey |
---|
211 | * saved for the device |
---|
212 | * |
---|
213 | * @param string $policykey |
---|
214 | * @param boolean $noDebug (opt) by default, debug message is shown |
---|
215 | * |
---|
216 | * @access public |
---|
217 | * @return boolean |
---|
218 | */ |
---|
219 | public function ProvisioningRequired($policykey, $noDebug = false) { |
---|
220 | $this->loadDeviceData(); |
---|
221 | |
---|
222 | // check if a remote wipe is required |
---|
223 | if ($this->device->GetWipeStatus() > SYNC_PROVISION_RWSTATUS_OK) { |
---|
224 | ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ProvisioningRequired('%s'): YES, remote wipe requested", $policykey)); |
---|
225 | return true; |
---|
226 | } |
---|
227 | |
---|
228 | $p = ( ($this->device->GetWipeStatus() != SYNC_PROVISION_RWSTATUS_NA && $policykey != $this->device->GetPolicyKey()) || |
---|
229 | Request::WasPolicyKeySent() && $this->device->GetPolicyKey() == ASDevice::UNDEFINED ); |
---|
230 | if (!$noDebug || $p) |
---|
231 | ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ProvisioningRequired('%s') saved device key '%s': %s", $policykey, $this->device->GetPolicyKey(), Utils::PrintAsString($p))); |
---|
232 | return $p; |
---|
233 | } |
---|
234 | |
---|
235 | /** |
---|
236 | * Generates a new Policykey |
---|
237 | * |
---|
238 | * @access public |
---|
239 | * @return int |
---|
240 | */ |
---|
241 | public function GenerateProvisioningPolicyKey() { |
---|
242 | return mt_rand(100000000, 999999999); |
---|
243 | } |
---|
244 | |
---|
245 | /** |
---|
246 | * Attributes a provisioned policykey to a device |
---|
247 | * |
---|
248 | * @param int $policykey |
---|
249 | * |
---|
250 | * @access public |
---|
251 | * @return boolean status |
---|
252 | */ |
---|
253 | public function SetProvisioningPolicyKey($policykey) { |
---|
254 | ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->SetPolicyKey('%s')", $policykey)); |
---|
255 | return $this->device->SetPolicyKey($policykey); |
---|
256 | } |
---|
257 | |
---|
258 | /** |
---|
259 | * Builds a Provisioning SyncObject with policies |
---|
260 | * |
---|
261 | * @access public |
---|
262 | * @return SyncProvisioning |
---|
263 | */ |
---|
264 | public function GetProvisioningObject() { |
---|
265 | $p = new SyncProvisioning(); |
---|
266 | // TODO load systemwide Policies |
---|
267 | $p->Load($this->device->GetPolicies()); |
---|
268 | return $p; |
---|
269 | } |
---|
270 | |
---|
271 | /** |
---|
272 | * Returns the status of the remote wipe policy |
---|
273 | * |
---|
274 | * @access public |
---|
275 | * @return int returns the current status of the device - SYNC_PROVISION_RWSTATUS_* |
---|
276 | */ |
---|
277 | public function GetProvisioningWipeStatus() { |
---|
278 | return $this->device->GetWipeStatus(); |
---|
279 | } |
---|
280 | |
---|
281 | /** |
---|
282 | * Updates the status of the remote wipe |
---|
283 | * |
---|
284 | * @param int $status - SYNC_PROVISION_RWSTATUS_* |
---|
285 | * |
---|
286 | * @access public |
---|
287 | * @return boolean could fail if trying to update status to a wipe status which was not requested before |
---|
288 | */ |
---|
289 | public function SetProvisioningWipeStatus($status) { |
---|
290 | ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->SetProvisioningWipeStatus() change from '%d' to '%d'",$this->device->GetWipeStatus(), $status)); |
---|
291 | |
---|
292 | if ($status > SYNC_PROVISION_RWSTATUS_OK && !($this->device->GetWipeStatus() > SYNC_PROVISION_RWSTATUS_OK)) { |
---|
293 | ZLog::Write(LOGLEVEL_ERROR, "Not permitted to update remote wipe status to a higher value as remote wipe was not initiated!"); |
---|
294 | return false; |
---|
295 | } |
---|
296 | $this->device->SetWipeStatus($status); |
---|
297 | return true; |
---|
298 | } |
---|
299 | |
---|
300 | |
---|
301 | /**---------------------------------------------------------------------------------------------------------- |
---|
302 | * LEGACY AS 1.0 and WRAPPER operations |
---|
303 | */ |
---|
304 | |
---|
305 | /** |
---|
306 | * Returns a wrapped Importer & Exporter to use the |
---|
307 | * HierarchyChache |
---|
308 | * |
---|
309 | * @see ChangesMemoryWrapper |
---|
310 | * @access public |
---|
311 | * @return object HierarchyCache |
---|
312 | */ |
---|
313 | public function GetHierarchyChangesWrapper() { |
---|
314 | return $this->device->GetHierarchyCache(); |
---|
315 | } |
---|
316 | |
---|
317 | /** |
---|
318 | * Initializes the HierarchyCache for legacy syncs |
---|
319 | * this is for AS 1.0 compatibility: |
---|
320 | * save folder information synched with GetHierarchy() |
---|
321 | * |
---|
322 | * @param string $folders Array with folder information |
---|
323 | * |
---|
324 | * @access public |
---|
325 | * @return boolean |
---|
326 | */ |
---|
327 | public function InitializeFolderCache($folders) { |
---|
328 | $this->stateManager->SetDevice($this->device); |
---|
329 | return $this->stateManager->InitializeFolderCache($folders); |
---|
330 | } |
---|
331 | |
---|
332 | /** |
---|
333 | * Returns a FolderID of default classes |
---|
334 | * this is for AS 1.0 compatibility: |
---|
335 | * this information was made available during GetHierarchy() |
---|
336 | * |
---|
337 | * @param string $class The class requested |
---|
338 | * |
---|
339 | * @access public |
---|
340 | * @return string |
---|
341 | * @throws NoHierarchyCacheAvailableException |
---|
342 | */ |
---|
343 | public function GetFolderIdFromCacheByClass($class) { |
---|
344 | $folderidforClass = false; |
---|
345 | // look at the default foldertype for this class |
---|
346 | $type = ZPush::getDefaultFolderTypeFromFolderClass($class); |
---|
347 | |
---|
348 | if ($type && $type > SYNC_FOLDER_TYPE_OTHER && $type < SYNC_FOLDER_TYPE_USER_MAIL) { |
---|
349 | $folderids = $this->device->GetAllFolderIds(); |
---|
350 | foreach ($folderids as $folderid) { |
---|
351 | if ($type == $this->device->GetFolderType($folderid)) { |
---|
352 | $folderidforClass = $folderid; |
---|
353 | break; |
---|
354 | } |
---|
355 | } |
---|
356 | |
---|
357 | // Old Palm Treos always do initial sync for calendar and contacts, even if they are not made available by the backend. |
---|
358 | // We need to fake these folderids, allowing a fake sync/ping, even if they are not supported by the backend |
---|
359 | // if the folderid would be available, they would already be returned in the above statement |
---|
360 | if ($folderidforClass == false && ($type == SYNC_FOLDER_TYPE_APPOINTMENT || $type == SYNC_FOLDER_TYPE_CONTACT)) |
---|
361 | $folderidforClass = SYNC_FOLDER_TYPE_DUMMY; |
---|
362 | } |
---|
363 | |
---|
364 | ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetFolderIdFromCacheByClass('%s'): '%s' => '%s'", $class, $type, $folderidforClass)); |
---|
365 | return $folderidforClass; |
---|
366 | } |
---|
367 | |
---|
368 | /** |
---|
369 | * Returns a FolderClass for a FolderID which is known to the mobile |
---|
370 | * |
---|
371 | * @param string $folderid |
---|
372 | * |
---|
373 | * @access public |
---|
374 | * @return int |
---|
375 | * @throws NoHierarchyCacheAvailableException, NotImplementedException |
---|
376 | */ |
---|
377 | public function GetFolderClassFromCacheByID($folderid) { |
---|
378 | //TODO check if the parent folder exists and is also beeing synchronized |
---|
379 | $typeFromCache = $this->device->GetFolderType($folderid); |
---|
380 | if ($typeFromCache === false) |
---|
381 | throw new NoHierarchyCacheAvailableException(sprintf("Folderid '%s' is not fully synchronized on the device", $folderid)); |
---|
382 | |
---|
383 | $class = ZPush::GetFolderClassFromFolderType($typeFromCache); |
---|
384 | if ($class === false) |
---|
385 | throw new NotImplementedException(sprintf("Folderid '%s' is saved to be of type '%d' but this type is not implemented", $folderid, $typeFromCache)); |
---|
386 | |
---|
387 | return $class; |
---|
388 | } |
---|
389 | |
---|
390 | /** |
---|
391 | * Checks if the message should be streamed to a mobile |
---|
392 | * Should always be called before a message is sent to the mobile |
---|
393 | * Returns true if there is something wrong and the content could break the |
---|
394 | * synchronization |
---|
395 | * |
---|
396 | * @param string $id message id |
---|
397 | * @param SyncObject &$message the method could edit the message to change the flags |
---|
398 | * |
---|
399 | * @access public |
---|
400 | * @return boolean returns true if the message should NOT be send! |
---|
401 | */ |
---|
402 | public function DoNotStreamMessage($id, &$message) { |
---|
403 | $folderid = $this->getLatestFolder(); |
---|
404 | |
---|
405 | if (isset($message->parentid)) |
---|
406 | $folder = $message->parentid; |
---|
407 | |
---|
408 | // message was identified to be causing a loop |
---|
409 | if ($this->loopdetection->IgnoreNextMessage(true, $id, $folderid)) { |
---|
410 | $this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_CAUSINGLOOP); |
---|
411 | return true; |
---|
412 | } |
---|
413 | |
---|
414 | // message is semantically incorrect |
---|
415 | if (!$message->Check(true)) { |
---|
416 | $this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_SEMANTICERR); |
---|
417 | return true; |
---|
418 | } |
---|
419 | |
---|
420 | // check if this message is broken |
---|
421 | if ($this->device->HasIgnoredMessage($folderid, $id)) { |
---|
422 | // reset the flags so the message is always streamed with <Add> |
---|
423 | $message->flags = false; |
---|
424 | |
---|
425 | // track the broken message in the loop detection |
---|
426 | $this->loopdetection->SetBrokenMessage($folderid, $id); |
---|
427 | } |
---|
428 | return false; |
---|
429 | } |
---|
430 | |
---|
431 | /** |
---|
432 | * Removes device information about a broken message as it is been removed from the mobile. |
---|
433 | * |
---|
434 | * @param string $id message id |
---|
435 | * |
---|
436 | * @access public |
---|
437 | * @return boolean |
---|
438 | */ |
---|
439 | public function RemoveBrokenMessage($id) { |
---|
440 | $folderid = $this->getLatestFolder(); |
---|
441 | if ($this->device->RemoveIgnoredMessage($folderid, $id)) { |
---|
442 | ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->RemoveBrokenMessage('%s', '%s'): cleared data about previously ignored message", $folderid, $id)); |
---|
443 | return true; |
---|
444 | } |
---|
445 | return false; |
---|
446 | } |
---|
447 | |
---|
448 | /** |
---|
449 | * Amount of items to me synchronized |
---|
450 | * |
---|
451 | * @param string $folderid |
---|
452 | * @param string $type |
---|
453 | * @param int $queuedmessages; |
---|
454 | * @access public |
---|
455 | * @return int |
---|
456 | */ |
---|
457 | public function GetWindowSize($folderid, $type, $uuid, $statecounter, $queuedmessages) { |
---|
458 | if (isset($this->windowSize[$folderid])) |
---|
459 | $items = $this->windowSize[$folderid]; |
---|
460 | else |
---|
461 | $items = (defined("SYNC_MAX_ITEMS")) ? SYNC_MAX_ITEMS : 100; |
---|
462 | |
---|
463 | if (defined("SYNC_MAX_ITEMS") && SYNC_MAX_ITEMS < $items) { |
---|
464 | if ($queuedmessages > SYNC_MAX_ITEMS) |
---|
465 | ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetWindowSize() overwriting max itmes requested of %d by %d forced in configuration.", $items, SYNC_MAX_ITEMS)); |
---|
466 | $items = SYNC_MAX_ITEMS; |
---|
467 | } |
---|
468 | |
---|
469 | $this->setLatestFolder($folderid); |
---|
470 | |
---|
471 | // detect if this is a loop condition |
---|
472 | if ($this->loopdetection->Detect($folderid, $type, $uuid, $statecounter, $items, $queuedmessages)) |
---|
473 | $items = ($items == 0) ? 0: 1+($this->loopdetection->IgnoreNextMessage(false)?1:0) ; |
---|
474 | |
---|
475 | if ($items >= 0 && $items <= 2) |
---|
476 | ZLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Messages sent to the mobile will be restricted to %d items in order to identify the conflict", $items)); |
---|
477 | |
---|
478 | return $items; |
---|
479 | } |
---|
480 | |
---|
481 | /** |
---|
482 | * Sets the amount of items the device is requesting |
---|
483 | * |
---|
484 | * @param string $folderid |
---|
485 | * @param int $maxItems |
---|
486 | * |
---|
487 | * @access public |
---|
488 | * @return boolean |
---|
489 | */ |
---|
490 | public function SetWindowSize($folderid, $maxItems) { |
---|
491 | $this->windowSize[$folderid] = $maxItems; |
---|
492 | |
---|
493 | return true; |
---|
494 | } |
---|
495 | |
---|
496 | /** |
---|
497 | * Sets the supported fields transmitted by the device for a certain folder |
---|
498 | * |
---|
499 | * @param string $folderid |
---|
500 | * @param array $fieldlist supported fields |
---|
501 | * |
---|
502 | * @access public |
---|
503 | * @return boolean |
---|
504 | */ |
---|
505 | public function SetSupportedFields($folderid, $fieldlist) { |
---|
506 | return $this->device->SetSupportedFields($folderid, $fieldlist); |
---|
507 | } |
---|
508 | |
---|
509 | /** |
---|
510 | * Gets the supported fields transmitted previousely by the device |
---|
511 | * for a certain folder |
---|
512 | * |
---|
513 | * @param string $folderid |
---|
514 | * |
---|
515 | * @access public |
---|
516 | * @return array/boolean |
---|
517 | */ |
---|
518 | public function GetSupportedFields($folderid) { |
---|
519 | return $this->device->GetSupportedFields($folderid); |
---|
520 | } |
---|
521 | |
---|
522 | /** |
---|
523 | * Removes all linked states of a specific folder. |
---|
524 | * During next request the folder is resynchronized. |
---|
525 | * |
---|
526 | * @param string $folderid |
---|
527 | * |
---|
528 | * @access public |
---|
529 | * @return boolean |
---|
530 | */ |
---|
531 | public function ForceFolderResync($folderid) { |
---|
532 | ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ForceFolderResync('%s'): folder resync", $folderid)); |
---|
533 | |
---|
534 | // delete folder states |
---|
535 | StateManager::UnLinkState($this->device, $folderid); |
---|
536 | |
---|
537 | return true; |
---|
538 | } |
---|
539 | |
---|
540 | /** |
---|
541 | * Removes all linked states from a device. |
---|
542 | * During next requests a full resync is triggered. |
---|
543 | * |
---|
544 | * @access public |
---|
545 | * @return boolean |
---|
546 | */ |
---|
547 | public function ForceFullResync() { |
---|
548 | ZLog::Write(LOGLEVEL_INFO, "Full device resync requested"); |
---|
549 | |
---|
550 | // delete hierarchy states |
---|
551 | StateManager::UnLinkState($this->device, false); |
---|
552 | |
---|
553 | // delete all other uuids |
---|
554 | foreach ($this->device->GetAllFolderIds() as $folderid) |
---|
555 | $uuid = StateManager::UnLinkState($this->device, $folderid); |
---|
556 | |
---|
557 | return true; |
---|
558 | } |
---|
559 | |
---|
560 | /** |
---|
561 | * Indicates if the hierarchy should be resynchronized |
---|
562 | * e.g. during PING |
---|
563 | * |
---|
564 | * @access public |
---|
565 | * @return boolean |
---|
566 | */ |
---|
567 | public function IsHierarchySyncRequired() { |
---|
568 | // check if a hierarchy sync might be necessary |
---|
569 | if ($this->device->GetFolderUUID(false) === false) |
---|
570 | $this->hierarchySyncRequired = true; |
---|
571 | |
---|
572 | return $this->hierarchySyncRequired; |
---|
573 | } |
---|
574 | |
---|
575 | /** |
---|
576 | * Indicates if a full hierarchy resync should be triggered due to loops |
---|
577 | * |
---|
578 | * @access public |
---|
579 | * @return boolean |
---|
580 | */ |
---|
581 | public function IsHierarchyFullResyncRequired() { |
---|
582 | // check for potential process loops like described in ZP-5 |
---|
583 | return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired(); |
---|
584 | } |
---|
585 | |
---|
586 | /** |
---|
587 | * Adds an Exceptions to the process tracking |
---|
588 | * |
---|
589 | * @param Exception $exception |
---|
590 | * |
---|
591 | * @access public |
---|
592 | * @return boolean |
---|
593 | */ |
---|
594 | public function AnnounceProcessException($exception) { |
---|
595 | return $this->loopdetection->ProcessLoopDetectionAddException($exception); |
---|
596 | } |
---|
597 | |
---|
598 | /** |
---|
599 | * Adds a non-ok status for a folderid to the process tracking. |
---|
600 | * On 'false' a hierarchy status is assumed |
---|
601 | * |
---|
602 | * @access public |
---|
603 | * @return boolean |
---|
604 | */ |
---|
605 | public function AnnounceProcessStatus($folderid, $status) { |
---|
606 | return $this->loopdetection->ProcessLoopDetectionAddStatus($folderid, $status); |
---|
607 | } |
---|
608 | |
---|
609 | /** |
---|
610 | * Checks if the given counter for a certain uuid+folderid was already exported or modified. |
---|
611 | * This is called when a heartbeat request found changes to make sure that the same |
---|
612 | * changes are not exported twice, as during the heartbeat there could have been a normal |
---|
613 | * sync request. |
---|
614 | * |
---|
615 | * @param string $folderid folder id |
---|
616 | * @param string $uuid synkkey |
---|
617 | * @param string $counter synckey counter |
---|
618 | * |
---|
619 | * @access public |
---|
620 | * @return boolean indicating if an uuid+counter were exported (with changes) before |
---|
621 | */ |
---|
622 | public function CheckHearbeatStateIntegrity($folderid, $uuid, $counter) { |
---|
623 | return $this->loopdetection->IsSyncStateObsolete($folderid, $uuid, $counter); |
---|
624 | } |
---|
625 | |
---|
626 | /** |
---|
627 | * Marks a syncstate as obsolete for Heartbeat, as e.g. an import was started using it. |
---|
628 | * |
---|
629 | * @param string $folderid folder id |
---|
630 | * @param string $uuid synkkey |
---|
631 | * @param string $counter synckey counter |
---|
632 | * |
---|
633 | * @access public |
---|
634 | * @return |
---|
635 | */ |
---|
636 | public function SetHeartbeatStateIntegrity($folderid, $uuid, $counter) { |
---|
637 | return $this->loopdetection->SetSyncStateUsage($folderid, $uuid, $counter); |
---|
638 | } |
---|
639 | |
---|
640 | /** |
---|
641 | * Indicates if the device needs an AS version update |
---|
642 | * |
---|
643 | * @access public |
---|
644 | * @return boolean |
---|
645 | */ |
---|
646 | public function AnnounceASVersion() { |
---|
647 | $latest = ZPush::GetSupportedASVersion(); |
---|
648 | $announced = $this->device->GetAnnouncedASversion(); |
---|
649 | $this->device->SetAnnouncedASversion($latest); |
---|
650 | |
---|
651 | return ($announced != $latest); |
---|
652 | } |
---|
653 | |
---|
654 | /**---------------------------------------------------------------------------------------------------------- |
---|
655 | * private DeviceManager methods |
---|
656 | */ |
---|
657 | |
---|
658 | /** |
---|
659 | * Loads devicedata from the StateMachine and loads it into the device |
---|
660 | * |
---|
661 | * @access public |
---|
662 | * @return boolean |
---|
663 | */ |
---|
664 | private function loadDeviceData() { |
---|
665 | if (!Request::IsValidDeviceID()) |
---|
666 | return false; |
---|
667 | try { |
---|
668 | $deviceHash = $this->statemachine->GetStateHash($this->devid, IStateMachine::DEVICEDATA); |
---|
669 | if ($deviceHash != $this->deviceHash) { |
---|
670 | if ($this->deviceHash) |
---|
671 | ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->loadDeviceData(): Device data was changed, reloading"); |
---|
672 | $this->device->SetData($this->statemachine->GetState($this->devid, IStateMachine::DEVICEDATA)); |
---|
673 | $this->deviceHash = $deviceHash; |
---|
674 | } |
---|
675 | } |
---|
676 | catch (StateNotFoundException $snfex) { |
---|
677 | $this->hierarchySyncRequired = true; |
---|
678 | } |
---|
679 | return true; |
---|
680 | } |
---|
681 | |
---|
682 | /** |
---|
683 | * Called when a SyncObject is not being streamed to the mobile. |
---|
684 | * The user can be informed so he knows about this issue |
---|
685 | * |
---|
686 | * @param string $folderid id of the parent folder (may be false if unknown) |
---|
687 | * @param string $id message id |
---|
688 | * @param SyncObject $message the broken message |
---|
689 | * @param string $reason (self::MSG_BROKEN_UNKNOWN, self::MSG_BROKEN_CAUSINGLOOP, self::MSG_BROKEN_SEMANTICERR) |
---|
690 | * |
---|
691 | * @access public |
---|
692 | * @return boolean |
---|
693 | */ |
---|
694 | public function AnnounceIgnoredMessage($folderid, $id, SyncObject $message, $reason = self::MSG_BROKEN_UNKNOWN) { |
---|
695 | if ($folderid === false) |
---|
696 | $folderid = $this->getLatestFolder(); |
---|
697 | |
---|
698 | $class = get_class($message); |
---|
699 | |
---|
700 | $brokenMessage = new StateObject(); |
---|
701 | $brokenMessage->id = $id; |
---|
702 | $brokenMessage->folderid = $folderid; |
---|
703 | $brokenMessage->ASClass = $class; |
---|
704 | $brokenMessage->folderid = $folderid; |
---|
705 | $brokenMessage->reasonCode = $reason; |
---|
706 | $brokenMessage->reasonString = 'unknown cause'; |
---|
707 | $brokenMessage->timestamp = time(); |
---|
708 | $brokenMessage->asobject = $message; |
---|
709 | $brokenMessage->reasonString = ZLog::GetLastMessage(LOGLEVEL_WARN); |
---|
710 | |
---|
711 | $this->device->AddIgnoredMessage($brokenMessage); |
---|
712 | |
---|
713 | ZLog::Write(LOGLEVEL_ERROR, sprintf("Ignored broken message (%s). Reason: '%s' Folderid: '%s' message id '%s'", $class, $reason, $folderid, $id)); |
---|
714 | return true; |
---|
715 | } |
---|
716 | |
---|
717 | /** |
---|
718 | * Called when a SyncObject was streamed to the mobile. |
---|
719 | * If the message could not be sent before this data is obsolete |
---|
720 | * |
---|
721 | * @param string $folderid id of the parent folder |
---|
722 | * @param string $id message id |
---|
723 | * |
---|
724 | * @access public |
---|
725 | * @return boolean returns true if the message was ignored before |
---|
726 | */ |
---|
727 | private function announceAcceptedMessage($folderid, $id) { |
---|
728 | if ($this->device->RemoveIgnoredMessage($folderid, $id)) { |
---|
729 | ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->announceAcceptedMessage('%s', '%s'): cleared previously ignored message as message is sucessfully streamed",$folderid, $id)); |
---|
730 | return true; |
---|
731 | } |
---|
732 | return false; |
---|
733 | } |
---|
734 | |
---|
735 | /** |
---|
736 | * Checks if there were broken messages streamed to the mobile. |
---|
737 | * If the sync completes/continues without further erros they are marked as accepted |
---|
738 | * |
---|
739 | * @param string $folderid folderid which is to be checked |
---|
740 | * |
---|
741 | * @access private |
---|
742 | * @return boolean |
---|
743 | */ |
---|
744 | private function checkBrokenMessages($folderid) { |
---|
745 | // check for correctly synchronized messages of the folder |
---|
746 | foreach($this->loopdetection->GetSyncedButBeforeIgnoredMessages($folderid) as $okID) { |
---|
747 | $this->announceAcceptedMessage($folderid, $okID); |
---|
748 | } |
---|
749 | return true; |
---|
750 | } |
---|
751 | |
---|
752 | /** |
---|
753 | * Setter for the latest folder id |
---|
754 | * on multi-folder operations of AS 14 this is used to set the new current folder id |
---|
755 | * |
---|
756 | * @param string $folderid the current folder |
---|
757 | * |
---|
758 | * @access private |
---|
759 | * @return boolean |
---|
760 | */ |
---|
761 | private function setLatestFolder($folderid) { |
---|
762 | // this is a multi folder operation |
---|
763 | // check on ignoredmessages before discaring the folderid |
---|
764 | if ($this->latestFolder !== false) |
---|
765 | $this->checkBrokenMessages($this->latestFolder); |
---|
766 | |
---|
767 | $this->latestFolder = $folderid; |
---|
768 | |
---|
769 | return true; |
---|
770 | } |
---|
771 | |
---|
772 | /** |
---|
773 | * Getter for the latest folder id |
---|
774 | * |
---|
775 | * @access private |
---|
776 | * @return string $folderid the current folder |
---|
777 | */ |
---|
778 | private function getLatestFolder() { |
---|
779 | return $this->latestFolder; |
---|
780 | } |
---|
781 | } |
---|
782 | |
---|
783 | ?> |
---|