Back to index

d-push  2.0
loopdetection.php
Go to the documentation of this file.
00001 <?php
00002 /***********************************************
00003 * File      :   loopdetection.php
00004 * Project   :   Z-Push
00005 * Descr     :   detects an outgoing loop by looking
00006 *               if subsequent requests do try to get changes
00007 *               for the same sync key. If more than once a synckey
00008 *               is requested, the amount of items to be sent to the mobile
00009 *               is reduced to one. If then (again) the same synckey is
00010 *               requested, we have most probably found the 'broken' item.
00011 *
00012 * Created   :   20.10.2011
00013 *
00014 * Copyright 2007 - 2011 Zarafa Deutschland GmbH
00015 *
00016 * This program is free software: you can redistribute it and/or modify
00017 * it under the terms of the GNU Affero General Public License, version 3,
00018 * as published by the Free Software Foundation with the following additional
00019 * term according to sec. 7:
00020 *
00021 * According to sec. 7 of the GNU Affero General Public License, version 3,
00022 * the terms of the AGPL are supplemented with the following terms:
00023 *
00024 * "Zarafa" is a registered trademark of Zarafa B.V.
00025 * "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
00026 * The licensing of the Program under the AGPL does not imply a trademark license.
00027 * Therefore any rights, title and interest in our trademarks remain entirely with us.
00028 *
00029 * However, if you propagate an unmodified version of the Program you are
00030 * allowed to use the term "Z-Push" to indicate that you distribute the Program.
00031 * Furthermore you may use our trademarks where it is necessary to indicate
00032 * the intended purpose of a product or service provided you use it in accordance
00033 * with honest practices in industrial or commercial matters.
00034 * If you want to propagate modified versions of the Program under the name "Z-Push",
00035 * you may only do so if you have a written permission by Zarafa Deutschland GmbH
00036 * (to acquire a permission please contact Zarafa at trademark@zarafa.com).
00037 *
00038 * This program is distributed in the hope that it will be useful,
00039 * but WITHOUT ANY WARRANTY; without even the implied warranty of
00040 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00041 * GNU Affero General Public License for more details.
00042 *
00043 * You should have received a copy of the GNU Affero General Public License
00044 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00045 *
00046 * Consult LICENSE file for details
00047 ************************************************/
00048 
00049 
00050 class LoopDetection extends InterProcessData {
00051     const INTERPROCESSLD = "ipldkey";
00052     const BROKENMSGS = "bromsgs";
00053     static private $processident;
00054     static private $processentry;
00055     private $ignore_messageid;
00056     private $broken_message_uuid;
00057     private $broken_message_counter;
00058 
00059 
00065     public function LoopDetection() {
00066         // initialize super parameters
00067         $this->allocate = 204800; // 200 KB
00068         $this->type = 1337;
00069         parent::__construct();
00070 
00071         $this->ignore_messageid = false;
00072     }
00073 
00084     public function ProcessLoopDetectionInit() {
00085         return $this->updateProcessStack();
00086     }
00087 
00094     public function ProcessLoopDetectionTerminate() {
00095         // just to be sure that the entry is there
00096         self::GetProcessEntry();
00097 
00098         self::$processentry['end'] = time();
00099         ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionTerminate()");
00100         return $this->updateProcessStack();
00101     }
00102 
00109     public static function GetProcessIdentifier() {
00110         if (!isset(self::$processident))
00111             self::$processident = sprintf('%04x%04', mt_rand(0, 0xffff), mt_rand(0, 0xffff));
00112 
00113         return self::$processident;
00114     }
00115 
00122     public static function GetProcessEntry() {
00123         if (!isset(self::$processentry)) {
00124             self::$processentry = array();
00125             self::$processentry['id'] = self::GetProcessIdentifier();
00126             self::$processentry['pid'] = self::$pid;
00127             self::$processentry['time'] = self::$start;
00128             self::$processentry['cc'] = Request::GetCommandCode();
00129         }
00130 
00131         return self::$processentry;
00132     }
00133 
00142     public function ProcessLoopDetectionAddException($exception) {
00143         // generate entry if not already there
00144         self::GetProcessEntry();
00145 
00146         if (!isset(self::$processentry['stat']))
00147             self::$processentry['stat'] = array();
00148 
00149         self::$processentry['stat'][get_class($exception)] = $exception->getCode();
00150 
00151         $this->updateProcessStack();
00152         return true;
00153     }
00154 
00164     public function ProcessLoopDetectionAddStatus($folderid, $status) {
00165         // generate entry if not already there
00166         self::GetProcessEntry();
00167 
00168         if ($folderid === false)
00169             $folderid = "hierarchy";
00170 
00171         if (!isset(self::$processentry['stat']))
00172             self::$processentry['stat'] = array();
00173 
00174         self::$processentry['stat'][$folderid] = $status;
00175 
00176         $this->updateProcessStack();
00177 
00178         return true;
00179     }
00180 
00199     public function ProcessLoopDetectionIsHierarchyResyncRequired() {
00200         $seenFailed = array();
00201         $seenFolderSync = false;
00202 
00203         $lookback = self::$start - 600; // look at the last 5 min
00204         foreach ($this->getProcessStack() as $se) {
00205             if ($se['time'] > $lookback && $se['time'] < (self::$start-1)) {
00206                 // look for sync command
00207                 if (isset($se['stat']) && ($se['cc'] == ZPush::COMMAND_SYNC || $se['cc'] == ZPush::COMMAND_PING)) {
00208                     foreach($se['stat'] as $key => $value) {
00209                         if (!isset($seenFailed[$key]))
00210                             $seenFailed[$key] = 0;
00211                         $seenFailed[$key]++;
00212                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): seen command with Exception or folderid '%s' and code '%s'", $key, $value ));
00213                     }
00214                 }
00215                 // look for FolderSync command with previous failed commands
00216                 if ($se['cc'] == ZPush::COMMAND_FOLDERSYNC && !empty($seenFailed) && $se['id'] != self::GetProcessIdentifier()) {
00217                     // a full folderresync was already triggered
00218                     if (isset($se['stat']) && isset($se['stat']['hierarchy']) && $se['stat']['hierarchy'] == SYNC_FSSTATUS_SYNCKEYERROR) {
00219                         ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): a full FolderReSync was already requested. Resetting fail counter.");
00220                         $seenFailed = array();
00221                     }
00222                     else {
00223                         $seenFolderSync = true;
00224                         if (!empty($seenFailed))
00225                             ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): seen FolderSync after other failing command");
00226                     }
00227                 }
00228             }
00229         }
00230 
00231         $filtered = array();
00232         foreach ($seenFailed as $k => $count) {
00233             if ($count>1)
00234                 $filtered[] = $k;
00235         }
00236 
00237         if ($seenFolderSync && !empty($filtered)) {
00238             ZLog::Write(LOGLEVEL_INFO, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): Potential loop detected. Full hierarchysync indicated.");
00239             return true;
00240         }
00241 
00242         return false;
00243     }
00244 
00254     public function ProcessLoopDetectionPreviousConnectionFailed() {
00255         $stack = $this->getProcessStack();
00256         if (count($stack) > 1) {
00257             $se = $stack[0];
00258             if (!isset($se['end']) && $se['cc'] != ZPush::COMMAND_PING) {
00259                 // there is no end time
00260                 ZLog::Write(LOGLEVEL_ERROR, sprintf("LoopDetection->ProcessLoopDetectionPreviousConnectionFailed(): Command '%s' at %s with pid '%d' terminated unexpectedly or is still running.", Utils::GetCommandFromCode($se['cc']), Utils::GetFormattedTime($se['time']), $se['pid']));
00261                 ZLog::Write(LOGLEVEL_ERROR, "Please check your logs for this PID and errors like PHP-Fatals or Apache segmentation faults and report your results to the Z-Push dev team.");
00262             }
00263         }
00264     }
00265 
00275     public function ProcessLoopDetectionGetOutdatedSearchPID() {
00276         $stack = $this->getProcessStack();
00277         if (count($stack) > 1) {
00278             $se = $stack[0];
00279             if ($se['cc'] == ZPush::COMMAND_SEARCH) {
00280                 return $se['pid'];
00281             }
00282         }
00283         return false;
00284     }
00285 
00292     private function updateProcessStack() {
00293         // initialize params
00294         $this->InitializeParams();
00295         if ($this->blockMutex()) {
00296             $loopdata = ($this->hasData()) ? $this->getData() : array();
00297 
00298             // check and initialize the array structure
00299             $this->checkArrayStructure($loopdata, self::INTERPROCESSLD);
00300 
00301             $stack = $loopdata[self::$devid][self::$user][self::INTERPROCESSLD];
00302 
00303             // insert/update current process entry
00304             $nstack = array();
00305             $updateentry = self::GetProcessEntry();
00306             $found = false;
00307 
00308             foreach ($stack as $entry) {
00309                 if ($entry['id'] != $updateentry['id']) {
00310                     $nstack[] = $entry;
00311                 }
00312                 else {
00313                     $nstack[] = $updateentry;
00314                     $found = true;
00315                 }
00316             }
00317 
00318             if (!$found)
00319                 $nstack[] = $updateentry;
00320 
00321             if (count($nstack) > 10)
00322                 $nstack = array_slice($nstack, -10, 10);
00323 
00324             // update loop data
00325             $loopdata[self::$devid][self::$user][self::INTERPROCESSLD] = $nstack;
00326             $ok = $this->setData($loopdata);
00327 
00328             $this->releaseMutex();
00329         }
00330         // end exclusive block
00331 
00332         return true;
00333     }
00334 
00341     private function getProcessStack() {
00342         // initialize params
00343         $this->InitializeParams();
00344         $stack = array();
00345 
00346         if ($this->blockMutex()) {
00347             $loopdata = ($this->hasData()) ? $this->getData() : array();
00348 
00349             // check and initialize the array structure
00350             $this->checkArrayStructure($loopdata, self::INTERPROCESSLD);
00351 
00352             $stack = $loopdata[self::$devid][self::$user][self::INTERPROCESSLD];
00353 
00354             $this->releaseMutex();
00355         }
00356         // end exclusive block
00357 
00358         return $stack;
00359     }
00360 
00382     public function SetBrokenMessage($folderid, $id) {
00383         if ($folderid == false || !isset($this->broken_message_uuid) || !isset($this->broken_message_counter) || $this->broken_message_uuid == false || $this->broken_message_counter == false)
00384             return false;
00385 
00386         $ok = false;
00387         $brokenkey = self::BROKENMSGS ."-". $folderid;
00388 
00389         // initialize params
00390         $this->InitializeParams();
00391         if ($this->blockMutex()) {
00392             $loopdata = ($this->hasData()) ? $this->getData() : array();
00393 
00394             // check and initialize the array structure
00395             $this->checkArrayStructure($loopdata, $brokenkey);
00396 
00397             $brokenmsgs = $loopdata[self::$devid][self::$user][$brokenkey];
00398 
00399             $brokenmsgs[$id] = array('uuid' => $this->broken_message_uuid, 'counter' => $this->broken_message_counter);
00400             ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->SetBrokenMessage('%s', '%s'): tracking broken message", $folderid, $id));
00401 
00402             // update data
00403             $loopdata[self::$devid][self::$user][$brokenkey] = $brokenmsgs;
00404             $ok = $this->setData($loopdata);
00405 
00406             $this->releaseMutex();
00407         }
00408         // end exclusive block
00409 
00410         return $ok;
00411     }
00412 
00423     public function GetSyncedButBeforeIgnoredMessages($folderid) {
00424         if ($folderid == false || !isset($this->broken_message_uuid) || !isset($this->broken_message_counter) || $this->broken_message_uuid == false || $this->broken_message_counter == false)
00425             return array();
00426 
00427         $brokenkey = self::BROKENMSGS ."-". $folderid;
00428         $removeIds = array();
00429         $okIds = array();
00430 
00431         // initialize params
00432         $this->InitializeParams();
00433         if ($this->blockMutex()) {
00434             $loopdata = ($this->hasData()) ? $this->getData() : array();
00435 
00436             // check and initialize the array structure
00437             $this->checkArrayStructure($loopdata, $brokenkey);
00438 
00439             $brokenmsgs = $loopdata[self::$devid][self::$user][$brokenkey];
00440 
00441             if (!empty($brokenmsgs)) {
00442                 foreach ($brokenmsgs as $id => $data) {
00443                     // previously broken message was sucessfully synced!
00444                     if ($data['uuid'] == $this->broken_message_uuid && $data['counter'] < $this->broken_message_counter) {
00445                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): message '%s' was successfully synchronized", $folderid, $id));
00446                         $okIds[] = $id;
00447                     }
00448 
00449                     // if the uuid has changed this is old data which should also be removed
00450                     if ($data['uuid'] != $this->broken_message_uuid) {
00451                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): stored message id '%s' for uuid '%s' is obsolete", $folderid, $id, $data['uuid']));
00452                         $removeIds[] = $id;
00453                     }
00454                 }
00455 
00456                 // remove data
00457                 foreach (array_merge($okIds,$removeIds) as $id) {
00458                     unset($brokenmsgs[$id]);
00459                 }
00460 
00461                 if (empty($brokenmsgs) && isset($loopdata[self::$devid][self::$user][$brokenkey])) {
00462                     unset($loopdata[self::$devid][self::$user][$brokenkey]);
00463                     ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): removed folder from tracking of ignored messages", $folderid));
00464                 }
00465                 else {
00466                     // update data
00467                     $loopdata[self::$devid][self::$user][$brokenkey] = $brokenmsgs;
00468                 }
00469                 $ok = $this->setData($loopdata);
00470             }
00471 
00472             $this->releaseMutex();
00473         }
00474         // end exclusive block
00475 
00476         return $okIds;
00477     }
00478 
00491     public function IsSyncStateObsolete($folderid, $uuid, $counter) {
00492         // initialize params
00493         $this->InitializeParams();
00494 
00495         $obsolete = false;
00496 
00497         // exclusive block
00498         if ($this->blockMutex()) {
00499             $loopdata = ($this->hasData()) ? $this->getData() : array();
00500             $this->releaseMutex();
00501             // end exclusive block
00502 
00503             // check and initialize the array structure
00504             $this->checkArrayStructure($loopdata, $folderid);
00505 
00506             $current = $loopdata[self::$devid][self::$user][$folderid];
00507 
00508             if (!empty($current)) {
00509                 if ($current["uuid"] != $uuid) {
00510                     ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, uuid changed");
00511                     $obsolete = true;
00512                 }
00513                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IsSyncStateObsolete(): check uuid counter: %d - last known counter: %d with %d queued objects", $counter, $current["count"], $current["queued"]));
00514 
00515                 if ($current["uuid"] == $uuid && ($current["count"] > $counter || ($current["count"] == $counter && $current["queued"] > 0))) {
00516                     ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, counter already processed");
00517                     $obsolete = true;
00518                 }
00519             }
00520 
00521         }
00522 
00523         return $obsolete;
00524     }
00525 
00557     public function Detect($folderid, $type, $uuid, $counter, $maxItems, $queuedMessages) {
00558         $this->broken_message_uuid = $uuid;
00559         $this->broken_message_counter = $counter;
00560 
00561         // if an incoming loop is already detected, do nothing
00562         if ($maxItems === 0 && $queuedMessages > 0) {
00563             ZPush::GetTopCollector()->AnnounceInformation("Incoming loop!", true);
00564             return true;
00565         }
00566 
00567         // initialize params
00568         $this->InitializeParams();
00569 
00570         $loop = false;
00571 
00572         // exclusive block
00573         if ($this->blockMutex()) {
00574             $loopdata = ($this->hasData()) ? $this->getData() : array();
00575 
00576             // check and initialize the array structure
00577             $this->checkArrayStructure($loopdata, $folderid);
00578 
00579             $current = $loopdata[self::$devid][self::$user][$folderid];
00580 
00581             // completely new/unknown UUID
00582             if (empty($current))
00583                 $current = array("type" => $type, "uuid" => $uuid, "count" => $counter-1, "queued" => $queuedMessages);
00584 
00585             // old UUID in cache - the device requested a new state!!
00586             else if (isset($current['type']) && $current['type'] == $type && isset($current['uuid']) && $current['uuid'] != $uuid ) {
00587                 ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed for folder");
00588 
00589                 // some devices (iPhones) may request new UUIDs after broken items were sent several times
00590                 if (isset($current['queued']) && $current['queued'] > 0 &&
00591                     (isset($current['maxCount']) && $current['count']+1 < $current['maxCount'] || $counter == 1)) {
00592 
00593                     ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed and while items where sent to device - forcing loop mode");
00594                     $loop = true; // force loop mode
00595                     $current['queued'] = $queuedMessages;
00596                 }
00597                 else {
00598                     $current['queued'] = 0;
00599                 }
00600 
00601                 // set new data, unset old loop information
00602                 $current["uuid"] = $uuid;
00603                 $current['count'] = $counter;
00604                 unset($current['loopcount']);
00605                 unset($current['ignored']);
00606                 unset($current['maxCount']);
00607                 unset($current['potential']);
00608             }
00609 
00610             // see if there are values
00611             if (isset($current['uuid']) && $current['uuid'] == $uuid &&
00612                 isset($current['type']) && $current['type'] == $type &&
00613                 isset($current['count'])) {
00614 
00615                 // case 1 - standard, during loop-resolving & resolving
00616                 if ($current['count'] < $counter) {
00617 
00618                     // case 1.1
00619                     $current['count'] = $counter;
00620                     $current['queued'] = $queuedMessages;
00621 
00622                     // case 1.2
00623                     if (isset($current['maxCount'])) {
00624                         ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2 detected");
00625 
00626                         // case 1.2.1
00627                         // broken item not identified yet
00628                         if (!isset($current['ignored']) && $counter < $current['maxCount']) {
00629                             $loop = true; // continue in loop-resolving
00630                             ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.1 detected");
00631                         }
00632                         // case 1.2.2 - if there were any broken items they should be gone, return to normal
00633                         else {
00634                             ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.2 detected");
00635                             unset($current['loopcount']);
00636                             unset($current['ignored']);
00637                             unset($current['maxCount']);
00638                             unset($current['potential']);
00639                         }
00640                     }
00641                 }
00642 
00643                 // case 2 - same counter, but there were no changes before and are there now
00644                 else if ($current['count'] == $counter && $current['queued'] == 0 && $queuedMessages > 0) {
00645                     $current['queued'] = $queuedMessages;
00646                 }
00647 
00648                 // case 3 - same counter, changes sent before, hanging loop and ignoring
00649                 else if ($current['count'] == $counter && $current['queued'] > 0) {
00650 
00651                     if (!isset($current['loopcount'])) {
00652                         // case 3.1) we have just encountered a loop!
00653                         ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.1 detected - loop detected, init loop mode");
00654                         $current['loopcount'] = 1;
00655                         // the MaxCount is the max number of messages exported before
00656                         $current['maxCount'] = $counter + (($maxItems < $queuedMessages)? $maxItems: $queuedMessages);
00657                         $loop = true;   // loop mode!!
00658                     }
00659                     else if ($queuedMessages == 0) {
00660                         // case 3.2) there was a loop before but now the changes are GONE
00661                         ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.2 detected - changes gone - clearing loop data");
00662                         $current['queued'] = 0;
00663                         unset($current['loopcount']);
00664                         unset($current['ignored']);
00665                         unset($current['maxCount']);
00666                         unset($current['potential']);
00667                     }
00668                     else {
00669                         // case 3.3) still looping the same message! Increase counter
00670                         ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.3 detected - in loop mode, increase loop counter");
00671                         $current['loopcount']++;
00672 
00673                         // case 3.3.1 - we got our broken item!
00674                         if ($current['loopcount'] >= 3 && isset($current['potential'])) {
00675                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): case 3.3.1 detected - broken item should be next, attempt to ignore it - id '%s'", $current['potential']));
00676                             $this->ignore_messageid = $current['potential'];
00677                         }
00678                         $current['maxCount'] = $counter + $queuedMessages;
00679                         $loop = true;   // loop mode!!
00680                     }
00681                 }
00682 
00683             }
00684             if (isset($current['loopcount']))
00685                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): loop data: loopcount(%d), maxCount(%d), queued(%d), ignored(%s)", $current['loopcount'], $current['maxCount'], $current['queued'], (isset($current['ignored'])?$current['ignored']:'false')));
00686 
00687             // update loop data
00688             $loopdata[self::$devid][self::$user][$folderid] = $current;
00689             $ok = $this->setData($loopdata);
00690 
00691             $this->releaseMutex();
00692         }
00693         // end exclusive block
00694 
00695         if ($loop == true && $this->ignore_messageid == false) {
00696             ZPush::GetTopCollector()->AnnounceInformation("Loop detection", true);
00697         }
00698 
00699         return $loop;
00700     }
00701 
00712     public function IgnoreNextMessage($markAsIgnored = true, $messageid = false, $folderid = false) {
00713         // as the next message id is not available at all point this method is called, we use different indicators.
00714         // potentialbroken indicates that we know that the broken message should be exported next,
00715         // alltho we do not know for sure as it's export message orders can change
00716         // if the $messageid is available and matches then we are sure and only then really ignore it
00717 
00718         $potentialBroken = false;
00719         $realBroken = false;
00720         if (Request::GetCommandCode() == ZPush::COMMAND_SYNC && $this->ignore_messageid !== false)
00721             $potentialBroken = true;
00722 
00723         if ($messageid !== false && $this->ignore_messageid == $messageid)
00724             $realBroken = true;
00725 
00726         // this call is just to know what should be happening
00727         // no further actions necessary
00728         if ($markAsIgnored === false) {
00729             return $potentialBroken;
00730         }
00731 
00732         // we should really do something here
00733 
00734         // first we check if we are in the loop mode, if so,
00735         // we update the potential broken id message so we loop count the same message
00736 
00737         $changedData = false;
00738         // exclusive block
00739         if ($this->blockMutex()) {
00740             $loopdata = ($this->hasData()) ? $this->getData() : array();
00741 
00742             // check and initialize the array structure
00743             $this->checkArrayStructure($loopdata, $folderid);
00744 
00745             $current = $loopdata[self::$devid][self::$user][$folderid];
00746 
00747             // we found our broken message!
00748             if ($realBroken) {
00749                 $this->ignore_messageid = false;
00750                 $current['ignored'] = $messageid;
00751                 $changedData = true;
00752 
00753                 // check if this message was broken before - here we know that it still is and remove it from the tracking
00754                 $brokenkey = self::BROKENMSGS ."-". $folderid;
00755                 if (isset($loopdata[self::$devid][self::$user][$brokenkey]) && isset($loopdata[self::$devid][self::$user][$brokenkey][$messageid])) {
00756                     ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IgnoreNextMessage(): previously broken message '%s' is still broken and will not be tracked anymore", $messageid));
00757                     unset($loopdata[self::$devid][self::$user][$brokenkey][$messageid]);
00758                 }
00759             }
00760             // not the broken message yet
00761             else {
00762                 // update potential id if looping on an item
00763                 if (isset($current['loopcount'])) {
00764                     $current['potential'] = $messageid;
00765 
00766                     // this message should be the broken one, but is not!!
00767                     // we should reset the loop count because this is certainly not the broken one
00768                     if ($potentialBroken) {
00769                         $current['loopcount'] = 1;
00770                         ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IgnoreNextMessage(): this should be the broken one, but is not! Resetting loop count.");
00771                     }
00772 
00773                     ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IgnoreNextMessage(): Loop mode, potential broken message id '%s'", $current['potential']));
00774 
00775                     $changedData = true;
00776                 }
00777             }
00778 
00779             // update loop data
00780             if ($changedData == true) {
00781                 $loopdata[self::$devid][self::$user][$folderid] = $current;
00782                 $ok = $this->setData($loopdata);
00783             }
00784 
00785             $this->releaseMutex();
00786         }
00787         // end exclusive block
00788 
00789         if ($realBroken)
00790             ZPush::GetTopCollector()->AnnounceInformation("Broken message ignored", true);
00791 
00792         return $realBroken;
00793     }
00794 
00804     public function ClearData($user = false, $devid = false) {
00805         $stat = true;
00806         $ok = false;
00807 
00808         // exclusive block
00809         if ($this->blockMutex()) {
00810             $loopdata = ($this->hasData()) ? $this->getData() : array();
00811 
00812             if ($user == false && $devid == false)
00813                 $loopdata = array();
00814             elseif ($user == false && $devid != false)
00815                 $loopdata[$devid] = array();
00816             elseif ($user != false && $devid != false)
00817                 $loopdata[$devid][$user] = array();
00818             elseif ($user != false && $devid == false) {
00819                 ZLog::Write(LOGLEVEL_WARN, sprintf("Not possible to reset loop detection data for user '%s' without a specifying a device id", $user));
00820                 $stat = false;
00821             }
00822 
00823             if ($stat)
00824                 $ok = $this->setData($loopdata);
00825 
00826             $this->releaseMutex();
00827         }
00828         // end exclusive block
00829 
00830         return $stat && $ok;
00831     }
00832 
00842     public function GetCachedData($user, $devid) {
00843         // exclusive block
00844         if ($this->blockMutex()) {
00845             $loopdata = ($this->hasData()) ? $this->getData() : array();
00846             $this->releaseMutex();
00847         }
00848         // end exclusive block
00849         if (isset($loopdata) && isset($loopdata[$devid]) && isset($loopdata[$devid][$user]))
00850             return $loopdata[$devid][$user];
00851 
00852         return false;
00853     }
00854 
00863     private function checkArrayStructure(&$loopdata, $folderid) {
00864         if (!isset($loopdata) || !is_array($loopdata))
00865             $loopdata = array();
00866 
00867         if (!isset($loopdata[self::$devid]))
00868             $loopdata[self::$devid] = array();
00869 
00870         if (!isset($loopdata[self::$devid][self::$user]))
00871             $loopdata[self::$devid][self::$user] = array();
00872 
00873         if (!isset($loopdata[self::$devid][self::$user][$folderid]))
00874             $loopdata[self::$devid][self::$user][$folderid] = array();
00875     }
00876 }
00877 
00878 ?>