1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/z-push_ynh.git synced 2024-09-03 18:05:58 +02:00
z-push_ynh/sources/lib/core/devicemanager.php

903 lines
32 KiB
PHP

<?php
/***********************************************
* File : devicemanager.php
* Project : Z-Push
* Descr : Manages device relevant data, provisioning,
* loop detection and device states.
* The DeviceManager uses a IStateMachine
* implementation with IStateMachine::DEVICEDATA
* to save device relevant data.
*
* Created : 11.04.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class DeviceManager {
// broken message indicators
const MSG_BROKEN_UNKNOWN = 1;
const MSG_BROKEN_CAUSINGLOOP = 2;
const MSG_BROKEN_SEMANTICERR = 4;
const FLD_SYNC_INITIALIZED = 1;
const FLD_SYNC_INPROGRESS = 2;
const FLD_SYNC_COMPLETED = 4;
private $device;
private $deviceHash;
private $statemachine;
private $stateManager;
private $incomingData = 0;
private $outgoingData = 0;
private $windowSize;
private $latestFolder;
private $loopdetection;
private $hierarchySyncRequired;
/**
* Constructor
*
* @access public
*/
public function DeviceManager() {
$this->statemachine = ZPush::GetStateMachine();
$this->deviceHash = false;
$this->devid = Request::GetDeviceID();
$this->windowSize = array();
$this->latestFolder = false;
$this->hierarchySyncRequired = false;
// only continue if deviceid is set
if ($this->devid) {
$this->device = new ASDevice($this->devid, Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent());
$this->loadDeviceData();
ZPush::GetTopCollector()->SetUserAgent($this->device->GetDeviceUserAgent());
}
else
throw new FatalNotImplementedException("Can not proceed without a device id.");
$this->loopdetection = ZPush::GetLoopDetection();
$this->loopdetection->ProcessLoopDetectionInit();
$this->loopdetection->ProcessLoopDetectionPreviousConnectionFailed();
$this->stateManager = new StateManager();
$this->stateManager->SetDevice($this->device);
}
/**
* Returns the StateManager for the current device
*
* @access public
* @return StateManager
*/
public function GetStateManager() {
return $this->stateManager;
}
/**----------------------------------------------------------------------------------------------------------
* Device operations
*/
/**
* Announces amount of transmitted data to the DeviceManager
*
* @param int $datacounter
*
* @access public
* @return boolean
*/
public function SentData($datacounter) {
// TODO save this somewhere
$this->incomingData = Request::GetContentLength();
$this->outgoingData = $datacounter;
}
/**
* Called at the end of the request
* Statistics about received/sent data is saved here
*
* @access public
* @return boolean
*/
public function Save() {
// TODO save other stuff
// check if previousily ignored messages were synchronized for the current folder
// on multifolder operations of AS14 this is done by setLatestFolder()
if ($this->latestFolder !== false)
$this->checkBrokenMessages($this->latestFolder);
// update the user agent and AS version on the device
$this->device->SetUserAgent(Request::GetUserAgent());
$this->device->SetASVersion(Request::GetProtocolVersion());
// data to be saved
$data = $this->device->GetData();
if ($data && Request::IsValidDeviceID()) {
ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data changed");
try {
// 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
if ($this->device->IsNewDevice() && RequestProcessor::isUserAuthenticated()) {
ZLog::Write(LOGLEVEL_INFO, sprintf("Linking device ID '%s' to user '%s'", $this->devid, $this->device->GetDeviceUser()));
$this->statemachine->LinkUserDevice($this->device->GetDeviceUser(), $this->devid);
}
if (RequestProcessor::isUserAuthenticated() || $this->device->GetForceSave() ) {
$this->statemachine->SetState($data, $this->devid, IStateMachine::DEVICEDATA);
ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data saved");
}
}
catch (StateNotFoundException $snfex) {
ZLog::Write(LOGLEVEL_ERROR, "DeviceManager->Save(): Exception: ". $snfex->getMessage());
}
}
// remove old search data
$oldpid = $this->loopdetection->ProcessLoopDetectionGetOutdatedSearchPID();
if ($oldpid) {
ZPush::GetBackend()->GetSearchProvider()->TerminateSearch($oldpid);
}
// we terminated this process
if ($this->loopdetection)
$this->loopdetection->ProcessLoopDetectionTerminate();
return true;
}
/**
* Newer mobiles send extensive device informations with the Settings command
* These informations are saved in the ASDevice
*
* @param SyncDeviceInformation $deviceinformation
*
* @access public
* @return boolean
*/
public function SaveDeviceInformation($deviceinformation) {
ZLog::Write(LOGLEVEL_DEBUG, "Saving submitted device information");
// set the user agent
if (isset($deviceinformation->useragent))
$this->device->SetUserAgent($deviceinformation->useragent);
// save other informations
foreach (array("model", "imei", "friendlyname", "os", "oslanguage", "phonenumber", "mobileoperator", "enableoutboundsms") as $info) {
if (isset($deviceinformation->$info) && $deviceinformation->$info != "") {
$this->device->__set("device".$info, $deviceinformation->$info);
}
}
return true;
}
/**----------------------------------------------------------------------------------------------------------
* Provisioning operations
*/
/**
* Checks if the sent policykey matches the latest policykey
* saved for the device
*
* @param string $policykey
* @param boolean $noDebug (opt) by default, debug message is shown
*
* @access public
* @return boolean
*/
public function ProvisioningRequired($policykey, $noDebug = false) {
$this->loadDeviceData();
// check if a remote wipe is required
if ($this->device->GetWipeStatus() > SYNC_PROVISION_RWSTATUS_OK) {
ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ProvisioningRequired('%s'): YES, remote wipe requested", $policykey));
return true;
}
$p = ( ($this->device->GetWipeStatus() != SYNC_PROVISION_RWSTATUS_NA && $policykey != $this->device->GetPolicyKey()) ||
Request::WasPolicyKeySent() && $this->device->GetPolicyKey() == ASDevice::UNDEFINED );
if (!$noDebug || $p)
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ProvisioningRequired('%s') saved device key '%s': %s", $policykey, $this->device->GetPolicyKey(), Utils::PrintAsString($p)));
return $p;
}
/**
* Generates a new Policykey
*
* @access public
* @return int
*/
public function GenerateProvisioningPolicyKey() {
return mt_rand(100000000, 999999999);
}
/**
* Attributes a provisioned policykey to a device
*
* @param int $policykey
*
* @access public
* @return boolean status
*/
public function SetProvisioningPolicyKey($policykey) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->SetPolicyKey('%s')", $policykey));
return $this->device->SetPolicyKey($policykey);
}
/**
* Builds a Provisioning SyncObject with policies
*
* @access public
* @return SyncProvisioning
*/
public function GetProvisioningObject() {
$p = new SyncProvisioning();
// TODO load systemwide Policies
$p->Load($this->device->GetPolicies());
return $p;
}
/**
* Returns the status of the remote wipe policy
*
* @access public
* @return int returns the current status of the device - SYNC_PROVISION_RWSTATUS_*
*/
public function GetProvisioningWipeStatus() {
return $this->device->GetWipeStatus();
}
/**
* Updates the status of the remote wipe
*
* @param int $status - SYNC_PROVISION_RWSTATUS_*
*
* @access public
* @return boolean could fail if trying to update status to a wipe status which was not requested before
*/
public function SetProvisioningWipeStatus($status) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->SetProvisioningWipeStatus() change from '%d' to '%d'",$this->device->GetWipeStatus(), $status));
if ($status > SYNC_PROVISION_RWSTATUS_OK && !($this->device->GetWipeStatus() > SYNC_PROVISION_RWSTATUS_OK)) {
ZLog::Write(LOGLEVEL_ERROR, "Not permitted to update remote wipe status to a higher value as remote wipe was not initiated!");
return false;
}
$this->device->SetWipeStatus($status);
return true;
}
/**----------------------------------------------------------------------------------------------------------
* LEGACY AS 1.0 and WRAPPER operations
*/
/**
* Returns a wrapped Importer & Exporter to use the
* HierarchyChache
*
* @see ChangesMemoryWrapper
* @access public
* @return object HierarchyCache
*/
public function GetHierarchyChangesWrapper() {
return $this->device->GetHierarchyCache();
}
/**
* Initializes the HierarchyCache for legacy syncs
* this is for AS 1.0 compatibility:
* save folder information synched with GetHierarchy()
*
* @param string $folders Array with folder information
*
* @access public
* @return boolean
*/
public function InitializeFolderCache($folders) {
$this->stateManager->SetDevice($this->device);
return $this->stateManager->InitializeFolderCache($folders);
}
/**
* Returns the ActiveSync folder type for a FolderID
*
* @param string $folderid
*
* @access public
* @return int/boolean boolean if no type is found
*/
public function GetFolderTypeFromCacheById($folderid) {
return $this->device->GetFolderType($folderid);
}
/**
* Returns a FolderID of default classes
* this is for AS 1.0 compatibility:
* this information was made available during GetHierarchy()
*
* @param string $class The class requested
*
* @access public
* @return string
* @throws NoHierarchyCacheAvailableException
*/
public function GetFolderIdFromCacheByClass($class) {
$folderidforClass = false;
// look at the default foldertype for this class
$type = ZPush::getDefaultFolderTypeFromFolderClass($class);
if ($type && $type > SYNC_FOLDER_TYPE_OTHER && $type < SYNC_FOLDER_TYPE_USER_MAIL) {
$folderids = $this->device->GetAllFolderIds();
foreach ($folderids as $folderid) {
if ($type == $this->device->GetFolderType($folderid)) {
$folderidforClass = $folderid;
break;
}
}
// Old Palm Treos always do initial sync for calendar and contacts, even if they are not made available by the backend.
// We need to fake these folderids, allowing a fake sync/ping, even if they are not supported by the backend
// if the folderid would be available, they would already be returned in the above statement
if ($folderidforClass == false && ($type == SYNC_FOLDER_TYPE_APPOINTMENT || $type == SYNC_FOLDER_TYPE_CONTACT))
$folderidforClass = SYNC_FOLDER_TYPE_DUMMY;
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetFolderIdFromCacheByClass('%s'): '%s' => '%s'", $class, $type, $folderidforClass));
return $folderidforClass;
}
/**
* Returns a FolderClass for a FolderID which is known to the mobile
*
* @param string $folderid
*
* @access public
* @return int
* @throws NoHierarchyCacheAvailableException, NotImplementedException
*/
public function GetFolderClassFromCacheByID($folderid) {
//TODO check if the parent folder exists and is also beeing synchronized
$typeFromCache = $this->device->GetFolderType($folderid);
if ($typeFromCache === false)
throw new NoHierarchyCacheAvailableException(sprintf("Folderid '%s' is not fully synchronized on the device", $folderid));
$class = ZPush::GetFolderClassFromFolderType($typeFromCache);
if ($class === false)
throw new NotImplementedException(sprintf("Folderid '%s' is saved to be of type '%d' but this type is not implemented", $folderid, $typeFromCache));
return $class;
}
/**
* Checks if the message should be streamed to a mobile
* Should always be called before a message is sent to the mobile
* Returns true if there is something wrong and the content could break the
* synchronization
*
* @param string $id message id
* @param SyncObject &$message the method could edit the message to change the flags
*
* @access public
* @return boolean returns true if the message should NOT be send!
*/
public function DoNotStreamMessage($id, &$message) {
$folderid = $this->getLatestFolder();
if (isset($message->parentid))
$folder = $message->parentid;
// message was identified to be causing a loop
if ($this->loopdetection->IgnoreNextMessage(true, $id, $folderid)) {
$this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_CAUSINGLOOP);
return true;
}
if (!is_object($message))
throw new Exception("DeviceManager->DoNotStreamMessage(): message isn't an object");
// message is semantically incorrect
if (!$message->Check(true)) {
$this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_SEMANTICERR);
return true;
}
// check if this message is broken
if ($this->device->HasIgnoredMessage($folderid, $id)) {
// reset the flags so the message is always streamed with <Add>
$message->flags = false;
// track the broken message in the loop detection
$this->loopdetection->SetBrokenMessage($folderid, $id);
}
return false;
}
/**
* Removes device information about a broken message as it is been removed from the mobile.
*
* @param string $id message id
*
* @access public
* @return boolean
*/
public function RemoveBrokenMessage($id) {
$folderid = $this->getLatestFolder();
if ($this->device->RemoveIgnoredMessage($folderid, $id)) {
ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->RemoveBrokenMessage('%s', '%s'): cleared data about previously ignored message", $folderid, $id));
return true;
}
return false;
}
/**
* Amount of items to me synchronized
*
* @param string $folderid
* @param string $type
* @param int $queuedmessages;
* @access public
* @return int
*/
public function GetWindowSize($folderid, $type, $uuid, $statecounter, $queuedmessages) {
if (isset($this->windowSize[$folderid]))
$items = $this->windowSize[$folderid];
else
$items = (defined("SYNC_MAX_ITEMS")) ? SYNC_MAX_ITEMS : 100;
if (defined("SYNC_MAX_ITEMS") && SYNC_MAX_ITEMS < $items) {
if ($queuedmessages > SYNC_MAX_ITEMS)
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetWindowSize() overwriting max items requested of %d by %d forced in configuration.", $items, SYNC_MAX_ITEMS));
$items = SYNC_MAX_ITEMS;
}
$this->setLatestFolder($folderid);
// detect if this is a loop condition
if ($this->loopdetection->Detect($folderid, $type, $uuid, $statecounter, $items, $queuedmessages))
$items = ($items == 0) ? 0: 1+($this->loopdetection->IgnoreNextMessage(false)?1:0) ;
if ($items >= 0 && $items <= 2)
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));
return $items;
}
/**
* Sets the amount of items the device is requesting
*
* @param string $folderid
* @param int $maxItems
*
* @access public
* @return boolean
*/
public function SetWindowSize($folderid, $maxItems) {
$this->windowSize[$folderid] = $maxItems;
return true;
}
/**
* Sets the supported fields transmitted by the device for a certain folder
*
* @param string $folderid
* @param array $fieldlist supported fields
*
* @access public
* @return boolean
*/
public function SetSupportedFields($folderid, $fieldlist) {
return $this->device->SetSupportedFields($folderid, $fieldlist);
}
/**
* Gets the supported fields transmitted previousely by the device
* for a certain folder
*
* @param string $folderid
*
* @access public
* @return array/boolean
*/
public function GetSupportedFields($folderid) {
return $this->device->GetSupportedFields($folderid);
}
/**
* Removes all linked states of a specific folder.
* During next request the folder is resynchronized.
*
* @param string $folderid
*
* @access public
* @return boolean
*/
public function ForceFolderResync($folderid) {
ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ForceFolderResync('%s'): folder resync", $folderid));
// delete folder states
StateManager::UnLinkState($this->device, $folderid);
return true;
}
/**
* Removes all linked states from a device.
* During next requests a full resync is triggered.
*
* @access public
* @return boolean
*/
public function ForceFullResync() {
ZLog::Write(LOGLEVEL_INFO, "Full device resync requested");
// delete hierarchy states
StateManager::UnLinkState($this->device, false);
// delete all other uuids
foreach ($this->device->GetAllFolderIds() as $folderid)
$uuid = StateManager::UnLinkState($this->device, $folderid);
return true;
}
/**
* Indicates if the hierarchy should be resynchronized
* e.g. during PING
*
* @access public
* @return boolean
*/
public function IsHierarchySyncRequired() {
// check if a hierarchy sync might be necessary
if ($this->device->GetFolderUUID(false) === false)
$this->hierarchySyncRequired = true;
return $this->hierarchySyncRequired;
}
/**
* Indicates if a full hierarchy resync should be triggered due to loops
*
* @access public
* @return boolean
*/
public function IsHierarchyFullResyncRequired() {
// do not check for loop detection, if the foldersync is not yet complete
if ($this->GetFolderSyncComplete() === false) {
ZLog::Write(LOGLEVEL_INFO, "DeviceManager->IsHierarchyFullResyncRequired(): aborted, as exporting of folders has not yet completed");
return false;
}
// check for potential process loops like described in ZP-5
return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired();
}
/**
* Adds an Exceptions to the process tracking
*
* @param Exception $exception
*
* @access public
* @return boolean
*/
public function AnnounceProcessException($exception) {
return $this->loopdetection->ProcessLoopDetectionAddException($exception);
}
/**
* Adds a non-ok status for a folderid to the process tracking.
* On 'false' a hierarchy status is assumed
*
* @access public
* @return boolean
*/
public function AnnounceProcessStatus($folderid, $status) {
return $this->loopdetection->ProcessLoopDetectionAddStatus($folderid, $status);
}
/**
* Announces that the current process is a push connection to the process loop
* detection and to the Top collector
*
* @access public
* @return boolean
*/
public function AnnounceProcessAsPush() {
ZLog::Write(LOGLEVEL_DEBUG, "Announce process as PUSH connection");
return $this->loopdetection->ProcessLoopDetectionSetAsPush() && ZPush::GetTopCollector()->SetAsPushConnection();
}
/**
* Checks if the given counter for a certain uuid+folderid was already exported or modified.
* This is called when a heartbeat request found changes to make sure that the same
* changes are not exported twice, as during the heartbeat there could have been a normal
* sync request.
*
* @param string $folderid folder id
* @param string $uuid synkkey
* @param string $counter synckey counter
*
* @access public
* @return boolean indicating if an uuid+counter were exported (with changes) before
*/
public function CheckHearbeatStateIntegrity($folderid, $uuid, $counter) {
return $this->loopdetection->IsSyncStateObsolete($folderid, $uuid, $counter);
}
/**
* Marks a syncstate as obsolete for Heartbeat, as e.g. an import was started using it.
*
* @param string $folderid folder id
* @param string $uuid synkkey
* @param string $counter synckey counter
*
* @access public
* @return
*/
public function SetHeartbeatStateIntegrity($folderid, $uuid, $counter) {
return $this->loopdetection->SetSyncStateUsage($folderid, $uuid, $counter);
}
/**
* Sets the current status of the folder
*
* @param string $folderid folder id
* @param int $statusflag current status: DeviceManager::FLD_SYNC_INITIALIZED, DeviceManager::FLD_SYNC_INPROGRESS, DeviceManager::FLD_SYNC_COMPLETED
*
* @access public
* @return
*/
public function SetFolderSyncStatus($folderid, $statusflag) {
$currentStatus = $this->device->GetFolderSyncStatus($folderid);
// status available or just initialized
if (isset($currentStatus[ASDevice::FOLDERSYNCSTATUS]) || $statusflag == self::FLD_SYNC_INITIALIZED) {
// only update if there is a change
if ($statusflag !== $currentStatus[ASDevice::FOLDERSYNCSTATUS] && $statusflag != self::FLD_SYNC_COMPLETED) {
$this->device->SetFolderSyncStatus($folderid, array(ASDevice::FOLDERSYNCSTATUS => $statusflag));
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): set %s for %s", $statusflag, $folderid));
}
// if completed, remove the status
else if ($statusflag == self::FLD_SYNC_COMPLETED) {
$this->device->SetFolderSyncStatus($folderid, false);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): completed for %s", $folderid));
}
}
return true;
}
/**
* Returns the indicator if the FolderSync was completed successfully (all folders synchronized)
*
* @access public
* @return boolean
*/
public function GetFolderSyncComplete() {
return $this->device->GetFolderSyncComplete();
}
/**
* Sets if the FolderSync was completed successfully (all folders synchronized)
*
* @param boolean $complete indicating if all folders were sent
*
* @access public
* @return boolean
*/
public function SetFolderSyncComplete($complete, $user = false, $devid = false) {
$this->device->SetFolderSyncComplete($complete);
}
/**
* Removes the Loop detection data for a user & device
*
* @param string $user
* @param string $devid
*
* @access public
* @return boolean
*/
public function ClearLoopDetectionData($user, $devid) {
if ($user == false || $devid == false) {
return false;
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ClearLoopDetectionData(): clearing data for user '%s' and device '%s'", $user, $devid));
return $this->loopdetection->ClearData($user, $devid);
}
/**
* Indicates if the device needs an AS version update
*
* @access public
* @return boolean
*/
public function AnnounceASVersion() {
$latest = ZPush::GetSupportedASVersion();
$announced = $this->device->GetAnnouncedASversion();
$this->device->SetAnnouncedASversion($latest);
return ($announced != $latest);
}
/**----------------------------------------------------------------------------------------------------------
* DeviceManager User-Device pre-authorization
*/
/**
* Return if the User-Device has permission to sync against this Z-Push.
*
* @param string $user Username
* @param string $devid DeviceId
*
* @access public
* @return integer
*/
public function GetUserDevicePermission($user, $devid) {
return $this->statemachine->GetUserDevicePermission($user, $devid);
}
/**----------------------------------------------------------------------------------------------------------
* private DeviceManager methods
*/
/**
* Loads devicedata from the StateMachine and loads it into the device
*
* @access public
* @return boolean
*/
private function loadDeviceData() {
if (!Request::IsValidDeviceID())
return false;
try {
$deviceHash = $this->statemachine->GetStateHash($this->devid, IStateMachine::DEVICEDATA);
if ($deviceHash != $this->deviceHash) {
if ($this->deviceHash)
ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->loadDeviceData(): Device data was changed, reloading");
$this->device->SetData($this->statemachine->GetState($this->devid, IStateMachine::DEVICEDATA));
$this->deviceHash = $deviceHash;
}
}
catch (StateNotFoundException $snfex) {
$this->hierarchySyncRequired = true;
}
return true;
}
/**
* Called when a SyncObject is not being streamed to the mobile.
* The user can be informed so he knows about this issue
*
* @param string $folderid id of the parent folder (may be false if unknown)
* @param string $id message id
* @param SyncObject $message the broken message
* @param string $reason (self::MSG_BROKEN_UNKNOWN, self::MSG_BROKEN_CAUSINGLOOP, self::MSG_BROKEN_SEMANTICERR)
*
* @access public
* @return boolean
*/
public function AnnounceIgnoredMessage($folderid, $id, SyncObject $message, $reason = self::MSG_BROKEN_UNKNOWN) {
if ($folderid === false)
$folderid = $this->getLatestFolder();
$class = get_class($message);
$brokenMessage = new StateObject();
$brokenMessage->id = $id;
$brokenMessage->folderid = $folderid;
$brokenMessage->ASClass = $class;
$brokenMessage->folderid = $folderid;
$brokenMessage->reasonCode = $reason;
$brokenMessage->reasonString = 'unknown cause';
$brokenMessage->timestamp = time();
$brokenMessage->asobject = $message;
$brokenMessage->reasonString = ZLog::GetLastMessage(LOGLEVEL_WARN);
$this->device->AddIgnoredMessage($brokenMessage);
ZLog::Write(LOGLEVEL_ERROR, sprintf("Ignored broken message (%s). Reason: '%s' Folderid: '%s' message id '%s'", $class, $reason, $folderid, $id));
return true;
}
/**
* Called when a SyncObject was streamed to the mobile.
* If the message could not be sent before this data is obsolete
*
* @param string $folderid id of the parent folder
* @param string $id message id
*
* @access public
* @return boolean returns true if the message was ignored before
*/
private function announceAcceptedMessage($folderid, $id) {
if ($this->device->RemoveIgnoredMessage($folderid, $id)) {
ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->announceAcceptedMessage('%s', '%s'): cleared previously ignored message as message is sucessfully streamed",$folderid, $id));
return true;
}
return false;
}
/**
* Checks if there were broken messages streamed to the mobile.
* If the sync completes/continues without further erros they are marked as accepted
*
* @param string $folderid folderid which is to be checked
*
* @access private
* @return boolean
*/
private function checkBrokenMessages($folderid) {
// check for correctly synchronized messages of the folder
foreach($this->loopdetection->GetSyncedButBeforeIgnoredMessages($folderid) as $okID) {
$this->announceAcceptedMessage($folderid, $okID);
}
return true;
}
/**
* Setter for the latest folder id
* on multi-folder operations of AS 14 this is used to set the new current folder id
*
* @param string $folderid the current folder
*
* @access private
* @return boolean
*/
private function setLatestFolder($folderid) {
// this is a multi folder operation
// check on ignoredmessages before discaring the folderid
if ($this->latestFolder !== false)
$this->checkBrokenMessages($this->latestFolder);
$this->latestFolder = $folderid;
return true;
}
/**
* Getter for the latest folder id
*
* @access private
* @return string $folderid the current folder
*/
private function getLatestFolder() {
return $this->latestFolder;
}
}