source: contrib/z-push/backend/diffbackend.php @ 3637

Revision 3637, 22.2 KB checked in by emersonfaria, 13 years ago (diff)

Ticket #1476 - Commit inicial dos arquivos do Projeto Z-Push customizados para o Expresso

  • Property svn:executable set to *
Line 
1<?php
2/***********************************************
3* File      :   diffbackend.php
4* Project   :   Z-Push
5* Descr     :   We do a standard differential
6*               change detection by sorting both
7*               lists of items by their unique id,
8*               and then traversing both arrays
9*               of items at once. Changes can be
10*               detected by comparing items at
11*               the same position in both arrays.
12*
13* Created   :   01.10.2007
14*
15* ᅵ Zarafa Deutschland GmbH, www.zarafaserver.de
16* This file is distributed under GPL v2.
17* Consult LICENSE file for details
18************************************************/
19
20include_once('proto.php');
21include_once('backend.php');
22
23
24
25function GetDiff($old, $new) {
26    $changes = array();
27
28    // Sort both arrays in the same way by ID
29    usort($old, "RowCmp");
30    usort($new, "RowCmp");
31
32    $inew = 0;
33    $iold = 0;
34
35    // Get changes by comparing our list of messages with
36    // our previous state
37    while(1) {
38        $change = array();
39
40        if($iold >= count($old) || $inew >= count($new))
41            break;
42
43        if($old[$iold]["id"] == $new[$inew]["id"]) {
44            // Both messages are still available, compare flags and mod
45            if(isset($old[$iold]["flags"]) && isset($new[$inew]["flags"]) && $old[$iold]["flags"] != $new[$inew]["flags"]) {
46                // Flags changed
47                $change["type"] = "flags";
48                $change["id"] = $new[$inew]["id"];
49                $change["flags"] = $new[$inew]["flags"];
50                $changes[] = $change;
51            }
52
53            if($old[$iold]["mod"] != $new[$inew]["mod"]) {
54                $change["type"] = "change";
55                $change["id"] = $new[$inew]["id"];
56                $changes[] = $change;
57            }
58
59            $inew++;
60            $iold++;
61        } else {
62            if($old[$iold]["id"] > $new[$inew]["id"]) {
63                // Message in state seems to have disappeared (delete)
64                $change["type"] = "delete";
65                $change["id"] = $old[$iold]["id"];
66                $changes[] = $change;
67                $iold++;
68            } else {
69                // Message in new seems to be new (add)
70                $change["type"] = "change";
71                $change["flags"] = SYNC_NEWMESSAGE;
72                $change["id"] = $new[$inew]["id"];
73                $changes[] = $change;
74                $inew++;
75            }
76        }
77    }
78
79    while($iold < count($old)) {
80        // All data left in _syncstate have been deleted
81        $change["type"] = "delete";
82        $change["id"] = $old[$iold]["id"];
83        $changes[] = $change;
84        $iold++;
85    }
86
87    while($inew < count($new)) {
88        // All data left in new have been added
89        $change["type"] = "change";
90        $change["flags"] = SYNC_NEWMESSAGE;
91        $change["id"] = $new[$inew]["id"];
92        $changes[] = $change;
93        $inew++;
94    }
95
96    return $changes;
97}
98
99function RowCmp($a, $b) {
100    return $a["id"] < $b["id"] ? 1 : -1;
101}
102
103class DiffState {
104    var $_syncstate;
105
106    // Update the state to reflect changes
107    function updateState($type, $change) {
108        // Change can be a change or an add
109        if($type == "change") {
110            for($i=0; $i < count($this->_syncstate); $i++) {
111                if($this->_syncstate[$i]["id"] == $change["id"]) {
112                    $this->_syncstate[$i] = $change;
113                    return;
114                }
115            }
116            // Not found, add as new
117            $this->_syncstate[] = $change;
118        } else {
119            for($i=0; $i < count($this->_syncstate); $i++) {
120                // Search for the entry for this item
121                if($this->_syncstate[$i]["id"] == $change["id"]) {
122                    if($type == "flags") {
123                        // Update flags
124                        $this->_syncstate[$i]["flags"] = $change["flags"];
125                    } else if($type == "delete") {
126                        // Delete item
127                        array_splice($this->_syncstate, $i, 1);
128                    }
129                    return;
130                }
131            }
132        }
133    }
134
135    // Returns TRUE if the given ID conflicts with the given operation. This is only true in the following situations:
136    //
137    // - Changed here and changed there
138    // - Changed here and deleted there
139    // - Deleted here and changed there
140    //
141    // Any other combination of operations can be done (e.g. change flags & move or move & delete)
142    function isConflict($type, $folderid, $id) {
143        $stat = $this->_backend->StatMessage($folderid, $id);
144
145        if(!$stat) {
146            // Message is gone
147            if($type == "change")
148                return true; // deleted here, but changed there
149            else
150                return false; // all other remote changes still result in a delete (no conflict)
151        }
152
153        foreach($this->_syncstate as $state) {
154            if($state["id"] == $id) {
155                $oldstat = $state;
156                break;
157            }
158        }
159
160        if(!isset($oldstat)) {
161            // New message, can never conflict
162            return false;
163        }
164
165        if($state["mod"] != $oldstat["mod"]) {
166            // Changed here
167            if($type == "delete" || $type == "change")
168                return true; // changed here, but deleted there -> conflict, or changed here and changed there -> conflict
169            else
170                return false; // changed here, and other remote changes (move or flags)
171        }
172    }
173
174    function GetState() {
175        return serialize($this->_syncstate);
176    }
177
178}
179
180class ImportContentsChangesDiff extends DiffState {
181    var $_user;
182    var $_folderid;
183
184    function ImportContentsChangesDiff($backend, $folderid) {
185        $this->_backend = $backend;
186        $this->_folderid = $folderid;
187    }
188
189    function Config($state, $flags = 0) {
190        $this->_syncstate = unserialize($state);
191        $this->_flags = $flags;
192    }
193
194    function ImportMessageChange($id, $message) {
195        //do nothing if it is in a dummy folder
196        if ($this->_folderid == SYNC_FOLDER_TYPE_DUMMY)
197            return false;
198
199        if($id) {
200            // See if there's a conflict
201            $conflict = $this->isConflict("change", $this->_folderid, $id);
202
203            // Update client state if this is an update
204            $change = array();
205            $change["id"] = $id;
206            $change["mod"] = 0; // dummy, will be updated later if the change succeeds
207            $change["parent"] = $this->_folderid;
208            $change["flags"] = (isset($message->read)) ? $message->read : 0;
209            $this->updateState("change", $change);
210
211            if($conflict && $this->_flags == SYNC_CONFLICT_OVERWRITE_PIM)
212                return true;
213        }
214
215        $stat = $this->_backend->ChangeMessage($this->_folderid, $id, $message);
216
217        if(!is_array($stat))
218            return $stat;
219
220        // Record the state of the message
221        $this->updateState("change", $stat);
222
223        return $stat["id"];
224    }
225
226    // Import a deletion. This may conflict if the local object has been modified.
227    function ImportMessageDeletion($id) {
228        //do nothing if it is in a dummy folder
229        if ($this->_folderid == SYNC_FOLDER_TYPE_DUMMY)
230            return true;
231
232        // See if there's a conflict
233        $conflict = $this->isConflict("delete", $this->_folderid, $id);
234
235        // Update client state
236        $change = array();
237        $change["id"] = $id;
238        $this->updateState("delete", $change);
239
240        // If there is a conflict, and the server 'wins', then return OK without performing the change
241        // this will cause the exporter to 'see' the overriding item as a change, and send it back to the PIM
242        if($conflict && $this->_flags == SYNC_CONFLICT_OVERWRITE_PIM)
243            return true;
244
245        $this->_backend->DeleteMessage($this->_folderid, $id);
246
247        return true;
248    }
249
250    // Import a change in 'read' flags .. This can never conflict
251    function ImportMessageReadFlag($id, $flags) {
252        //do nothing if it is a dummy folder
253        if ($this->_folderid == SYNC_FOLDER_TYPE_DUMMY)
254            return true;
255
256        // Update client state
257        $change = array();
258        $change["id"] = $id;
259        $change["flags"] = $flags;
260        $this->updateState("flags", $change);
261
262        $this->_backend->SetReadFlag($this->_folderid, $id, $flags);
263
264        return true;
265    }
266
267    function ImportMessageMove($id, $newfolder) {
268          $this->_backend->MoveMessage($this->_folderid, $id, $newfolder);
269        return true;
270    }
271};
272
273class ImportHierarchyChangesDiff extends DiffState {
274    var $_user;
275
276    function ImportHierarchyChangesDiff($backend) {
277        $this->_backend = $backend;
278    }
279
280    function Config($state) {
281        $this->_syncstate = unserialize($state);
282    }
283
284    function ImportFolderChange($id, $parent, $displayname, $type) {
285        //do nothing if it is a dummy folder
286        if ($parent == SYNC_FOLDER_TYPE_DUMMY)
287            return false;
288
289        if($id) {
290            $change = array();
291            $change["id"] = $id;
292            $change["mod"] = $displayname;
293            $change["parent"] = $parent;
294            $change["flags"] = 0;
295            $this->updateState("change", $change);
296        }
297
298        $stat = $this->_backend->ChangeFolder($parent, $id, $displayname, $type);
299
300        if($stat)
301            $this->updateState("change", $stat);
302
303        return $stat["id"];
304    }
305
306    function ImportFolderDeletion($id, $parent) {
307        //do nothing if it is a dummy folder
308        if ($parent == SYNC_FOLDER_TYPE_DUMMY)
309            return false;
310
311        $change = array();
312        $change["id"] = $id;
313
314        $this->updateState("delete", $change);
315
316        $this->_backend->DeleteFolder($parent, $id);
317
318        return true;
319    }
320};
321
322class ExportChangesDiff extends DiffState {
323    var $_importer;
324    var $_folderid;
325    var $_restrict;
326    var $_flags;
327    var $_user;
328
329    function ExportChangesDiff($backend, $folderid) {
330        $this->_backend = $backend;
331        $this->_folderid = $folderid;
332    }
333
334    function Config(&$importer, $folderid, $restrict, $syncstate, $flags, $truncation) {
335        $this->_importer = &$importer;
336        $this->_restrict = $restrict;
337        $this->_syncstate = unserialize($syncstate);
338        $this->_flags = $flags;
339        $this->_truncation = $truncation;
340
341        $this->_changes = array();
342        $this->_step = 0;
343
344        $cutoffdate = $this->getCutOffDate($restrict);
345
346        if($this->_folderid) {
347            // Get the changes since the last sync
348            debugLog("Initializing message diff engine");
349
350            if(!isset($this->_syncstate) || !$this->_syncstate)
351                $this->_syncstate = array();
352
353            debugLog(count($this->_syncstate) . " messages in state");
354
355            //do nothing if it is a dummy folder
356            if ($this->_folderid != SYNC_FOLDER_TYPE_DUMMY) {
357
358                // on ping: check if backend supports alternative PING mechanism & use it
359                if ($folderid === false && $this->_flags == BACKEND_DISCARD_DATA && $this->_backend->AlterPing()) {
360                    $this->_changes = $this->_backend->AlterPingChanges($this->_folderid, $this->_syncstate);
361                }
362                else {
363                    // Get our lists - syncstate (old)  and msglist (new)
364                    $msglist = $this->_backend->GetMessageList($this->_folderid, $cutoffdate);
365                    if($msglist === false)
366                        return false;
367
368                    $this->_changes = GetDiff($this->_syncstate, $msglist);
369                }
370            }
371
372            debugLog("Found " . count($this->_changes) . " message changes");
373        } else {
374            debugLog("Initializing folder diff engine");
375
376            $folderlist = $this->_backend->GetFolderList();
377            if($folderlist === false)
378                return false;
379
380            if(!isset($this->_syncstate) || !$this->_syncstate)
381                $this->_syncstate = array();
382
383            $this->_changes = GetDiff($this->_syncstate, $folderlist);
384
385            debugLog("Found " . count($this->_changes) . " folder changes");
386        }
387    }
388
389    function GetChangeCount() {
390        return count($this->_changes);
391    }
392
393    function Synchronize() {
394        $progress = array();
395
396        // Get one of our stored changes and send it to the importer, store the new state if
397        // it succeeds
398        if($this->_folderid == false) {
399            if($this->_step < count($this->_changes)) {
400                $change = $this->_changes[$this->_step];
401
402                switch($change["type"]) {
403                    case "change":
404                        $folder = $this->_backend->GetFolder($change["id"]);
405                        $stat = $this->_backend->StatFolder($change["id"]);
406
407                        if(!$folder)
408                            return;
409
410                        if($this->_flags & BACKEND_DISCARD_DATA || $this->_importer->ImportFolderChange($folder))
411                            $this->updateState("change", $stat);
412                        break;
413                    case "delete":
414                        if($this->_flags & BACKEND_DISCARD_DATA || $this->_importer->ImportFolderDeletion($change["id"]))
415                            $this->updateState("delete", $change);
416                        break;
417                }
418
419                $this->_step++;
420
421                $progress = array();
422                $progress["steps"] = count($this->_changes);
423                $progress["progress"] = $this->_step;
424
425                return $progress;
426            } else {
427                return false;
428            }
429        }
430        else {
431            if($this->_step < count($this->_changes)) {
432                $change = $this->_changes[$this->_step];
433
434                switch($change["type"]) {
435                    case "change":
436                        $truncsize = $this->getTruncSize($this->_truncation);
437
438                        // Note: because 'parseMessage' and 'statMessage' are two seperate
439                        // calls, we have a chance that the message has changed between both
440                        // calls. This may cause our algorithm to 'double see' changes.
441
442                        $stat = $this->_backend->StatMessage($this->_folderid, $change["id"]);
443                        $message = $this->_backend->GetMessage($this->_folderid, $change["id"], $truncsize);
444
445                        // copy the flag to the message
446                        $message->flags = (isset($change["flags"])) ? $change["flags"] : 0;
447
448                        if($stat && $message) {
449                            if($this->_flags & BACKEND_DISCARD_DATA || $this->_importer->ImportMessageChange($change["id"], $message) == true)
450                                $this->updateState("change", $stat);
451                        }
452                        break;
453                    case "delete":
454                        if($this->_flags & BACKEND_DISCARD_DATA || $this->_importer->ImportMessageDeletion($change["id"]) == true)
455                            $this->updateState("delete", $change);
456                        break;
457                    case "flags":
458                        if($this->_flags & BACKEND_DISCARD_DATA || $this->_importer->ImportMessageReadFlag($change["id"], $change["flags"]) == true)
459                            $this->updateState("flags", $change);
460                        break;
461                    case "move":
462                        if($this->_flags & BACKEND_DISCARD_DATA || $this->_importer->ImportMessageMove($change["id"], $change["parent"]) == true)
463                            $this->updateState("move", $change);
464                        break;
465                }
466
467                $this->_step++;
468
469                $progress = array();
470                $progress["steps"] = count($this->_changes);
471                $progress["progress"] = $this->_step;
472
473                return $progress;
474            } else {
475                return false;
476            }
477        }
478    }
479
480    // -----------------------------------------------------------------------
481
482    function getCutOffDate($restrict) {
483        switch($restrict) {
484            case SYNC_FILTERTYPE_1DAY:
485                $back = 60 * 60 * 24;
486                break;
487            case SYNC_FILTERTYPE_3DAYS:
488                $back = 60 * 60 * 24 * 3;
489                break;
490            case SYNC_FILTERTYPE_1WEEK:
491                $back = 60 * 60 * 24 * 7;
492                break;
493            case SYNC_FILTERTYPE_2WEEKS:
494                $back = 60 * 60 * 24 * 14;
495                break;
496            case SYNC_FILTERTYPE_1MONTH:
497                $back = 60 * 60 * 24 * 31;
498                break;
499            case SYNC_FILTERTYPE_3MONTHS:
500                $back = 60 * 60 * 24 * 31 * 3;
501                break;
502            case SYNC_FILTERTYPE_6MONTHS:
503                $back = 60 * 60 * 24 * 31 * 6;
504                break;
505            default:
506                break;
507        }
508
509        if(isset($back)) {
510            $date = time() - $back;
511            return $date;
512        } else
513            return 0; // unlimited
514    }
515
516    function getTruncSize($truncation) {
517        switch($truncation) {
518            case SYNC_TRUNCATION_HEADERS:
519                return 0;
520            case SYNC_TRUNCATION_512B:
521                return 512;
522            case SYNC_TRUNCATION_1K:
523                return 1024;
524            case SYNC_TRUNCATION_5K:
525                return 5*1024;
526            case SYNC_TRUNCATION_SEVEN:
527            case SYNC_TRUNCATION_ALL:
528                return 1024*1024; // We'll limit to 1MB anyway
529            default:
530                return 1024; // Default to 1Kb
531        }
532    }
533
534};
535
536class BackendDiff {
537    var $_user;
538    var $_devid;
539    var $_protocolversion;
540
541    function Logon($username, $domain, $password) {
542        return true;
543    }
544
545        // completing protocol
546    function Logoff() {
547        return true;
548    }
549
550    function Setup($user, $devid, $protocolversion) {
551        $this->_user = $user;
552        $this->_devid = $devid;
553        $this->_protocolversion = $protocolversion;
554
555        return true;
556    }
557
558    function GetHierarchyImporter() {
559        return new ImportHierarchyChangesDiff($this);
560    }
561
562    function GetContentsImporter($folderid) {
563        return new ImportContentsChangesDiff($this, $folderid);
564    }
565
566    function GetExporter($folderid = false) {
567        return new ExportChangesDiff($this, $folderid);
568    }
569
570    function GetHierarchy() {
571        $folders = array();
572
573        $fl = $this->getFolderList();
574        foreach($fl as $f){
575            $folders[] = $this->GetFolder($f['id']);
576        }
577
578        return $folders;
579    }
580
581    function Fetch($folderid, $id, $mimesupport = 0) {
582        return $this->GetMessage($folderid, $id, 1024*1024, $mimesupport); // Forces entire message (up to 1Mb)
583    }
584
585    function GetAttachmentData($attname) {
586        return false;
587    }
588
589    function SendMail($rfc822, $forward = false, $reply = false, $parent = false) {
590        return true;
591    }
592
593    function GetWasteBasket() {
594        return false;
595    }
596
597    function GetMessageList($folderid, $cutoffdate) {
598        return array();
599    }
600
601    function StatMessage($folderid, $id) {
602        return false;
603    }
604
605    function GetMessage($folderid, $id, $truncsize, $mimesupport = 0) {
606        return false;
607    }
608
609    function DeleteMessage($folderid, $id) {
610        return false;
611    }
612
613    function SetReadFlag($folderid, $id, $flags) {
614        return false;
615    }
616
617    function ChangeMessage($folderid, $id, $message) {
618        return false;
619    }
620
621    function MoveMessage($folderid, $id, $newfolderid) {
622        return false;
623    }
624
625    function MeetingResponse($requestid, $folderid, $error, &$calendarid) {
626        return false;
627    }
628
629    function getTruncSize($truncation) {
630        switch($truncation) {
631            case SYNC_TRUNCATION_HEADERS:
632                return 0;
633            case SYNC_TRUNCATION_512B:
634                return 512;
635            case SYNC_TRUNCATION_1K:
636                return 1024;
637            case SYNC_TRUNCATION_5K:
638                return 5*1024;
639            case SYNC_TRUNCATION_ALL:
640                return 1024*1024; // We'll limit to 1MB anyway
641            default:
642                return 1024; // Default to 1Kb
643        }
644    }
645
646    /**
647     * Returns array of items which contain contact information
648     *
649     * @param string $searchquery
650     *
651     * @return array
652     */
653    function getSearchResults($searchquery) {
654        return false;
655    }
656
657    /**
658     * Checks if the sent policykey matches the latest policykey on the server
659     *
660     * @param string $policykey
661     * @param string $devid
662     *
663     * @return status flag
664     */
665    function CheckPolicy($policykey, $devid) {
666        global $user, $auth_pw;
667
668        $status = SYNC_PROVISION_STATUS_SUCCESS;
669
670        $user_policykey = $this->getPolicyKey($user, $auth_pw, $devid);
671
672        if ($user_policykey != $policykey) {
673            $status = SYNC_PROVISION_STATUS_POLKEYMISM;
674        }
675
676        if (!$policykey) $policykey = $user_policykey;
677        return $status;
678    }
679
680    /**
681     * Return a policy key for given user with a given device id.
682     * If there is no combination user-deviceid available, a new key
683     * should be generated.
684     *
685     * @param string $user
686     * @param string $pass
687     * @param string $devid
688     *
689     * @return unknown
690     */
691    function getPolicyKey($user, $pass, $devid) {
692        return false;
693    }
694
695    /**
696     * Generate a random policy key. Right now it's a 10-digit number.
697     *
698     * @return unknown
699     */
700    function generatePolicyKey(){
701        return mt_rand(1000000000, 9999999999);
702    }
703
704    /**
705     * Set a new policy key for the given device id.
706     *
707     * @param string $policykey
708     * @param string $devid
709     * @return unknown
710     */
711    function setPolicyKey($policykey, $devid) {
712        return false;
713    }
714
715    /**
716     * Return a device wipe status
717     *
718     * @param string $user
719     * @param string $pass
720     * @param string $devid
721     * @return int
722     */
723    function getDeviceRWStatus($user, $pass, $devid) {
724        return false;
725    }
726
727    /**
728     * Set a new rw status for the device
729     *
730     * @param string $user
731     * @param string $pass
732     * @param string $devid
733     * @param string $status
734     *
735     * @return boolean
736     */
737    function setDeviceRWStatus($user, $pass, $devid, $status) {
738        return false;
739    }
740
741    function AlterPing() {
742        return false;
743    }
744
745    function AlterPingChanges($folderid, &$syncstate) {
746        return array();
747    }
748}
749?>
Note: See TracBrowser for help on using the repository browser.