source: trunk/zpush/lib/core/devicemanager.php @ 7589

Revision 7589, 27.7 KB checked in by douglas, 11 years ago (diff)

Ticket #3209 - Integrar módulo de sincronização Z-push ao Expresso

Line 
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
48class 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?>
Note: See TracBrowser for help on using the repository browser.