Merge branch 'master' of http://git.tine20.org/git/Syncope
[tine20] / tine20 / library / Syncope / lib / Syncope / Backend / SyncState.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Syncope
6  * @subpackage  Backend
7  * @license     http://www.tine20.org/licenses/lgpl.html LGPL Version 3
8  * @author      Lars Kneschke <l.kneschke@metaways.de>
9  * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  */
12
13 /**
14  * sql backend class for the folder state
15  *
16  * @package     Syncope
17  * @subpackage  Backend
18  */
19 class Syncope_Backend_SyncState implements Syncope_Backend_ISyncState
20 {
21     /**
22      * the database adapter
23      *
24      * @var Zend_Db_Adapter_Abstract
25      */
26     protected $_db;
27     
28     protected $_tablePrefix;
29     
30     public function __construct(Zend_Db_Adapter_Abstract $_db, $_tablePrefix = 'syncope_')
31     {
32         $this->_db          = $_db;
33         $this->_tablePrefix = $_tablePrefix;
34     }
35     
36     /**
37      * create new sync state
38      *
39      * @param Syncope_Model_ISyncState $_syncState
40      * @return Syncope_Model_SyncState
41      */
42     public function create(Syncope_Model_ISyncState $_syncState, $_keepPreviousSyncState = true)
43     {
44         $id = sha1(mt_rand(). microtime());
45         $deviceId = $_syncState->device_id instanceof Syncope_Model_IDevice ? $_syncState->device_id->id : $_syncState->device_id;
46     
47         $this->_db->insert($this->_tablePrefix . 'synckey', array(
48             'id'          => $id, 
49             'device_id'   => $deviceId,
50             'type'        => $_syncState->type instanceof Syncope_Model_IFolder ? $_syncState->type->id : $_syncState->type,
51             'counter'     => $_syncState->counter,
52             'lastsync'    => $_syncState->lastsync->format('Y-m-d H:i:s'),
53             'pendingdata' => isset($_syncState->pendingdata) && is_array($_syncState->pendingdata) ? Zend_Json::encode($_syncState->pendingdata) : null
54         ));
55         
56         $state = $this->get($id);
57         
58         if ($_keepPreviousSyncState !== true) {
59             // remove all other synckeys
60             $this->_deleteOtherStates($state);
61         }
62         
63         return $state;
64     }
65     
66     protected function _deleteOtherStates(Syncope_Model_ISyncState $_state)
67     {
68         // remove all other synckeys
69         $where = array(
70             'device_id = ?' => $_state->device_id,
71             'type = ?'      => $_state->type,
72             'counter != ?'  => $_state->counter
73         );
74     
75         $this->_db->delete($this->_tablePrefix . 'synckey', $where);
76     
77         return true;
78     
79     }
80     
81     /**
82      * @param string  $_id
83      * @throws Syncope_Exception_NotFound
84      * @return Syncope_Model_SyncState
85      */
86     public function get($_id)
87     {
88         $select = $this->_db->select()
89             ->from($this->_tablePrefix . 'synckey')
90             ->where('id = ?', $_id);
91     
92         $stmt = $this->_db->query($select);
93         $state = $stmt->fetchObject('Syncope_Model_SyncState');
94         $stmt = null; # see https://bugs.php.net/bug.php?id=44081
95         
96         if (! $state instanceof Syncope_Model_ISyncState) {
97             throw new Syncope_Exception_NotFound('id not found');
98         }
99         
100         $this->_convertFields($state);
101         
102         return $state;
103     }
104     
105     protected function _convertFields(Syncope_Model_SyncState $state)
106     {
107         if (!empty($state->lastsync)) {
108             $state->lastsync = new DateTime($state->lastsync, new DateTimeZone('utc'));
109         }
110         if ($state->pendingdata !== NULL) {
111             $state->pendingdata = Zend_Json::decode($state->pendingdata);
112         }
113     }
114     
115     /**
116      * always returns the latest syncstate
117      * 
118      * @param  Syncope_Model_IDevice|string  $_deviceId
119      * @param  Syncope_Model_IFolder|string  $_folderId
120      * @return Syncope_Model_SyncState
121      */
122     public function getSyncState($_deviceId, $_folderId)
123     {
124         $deviceId = $_deviceId instanceof Syncope_Model_IDevice ? $_deviceId->id : $_deviceId;
125         $folderId = $_folderId instanceof Syncope_Model_IFolder ? $_folderId->id : $_folderId;
126     
127         $select = $this->_db->select()
128             ->from($this->_tablePrefix . 'synckey')
129             ->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
130             ->where($this->_db->quoteIdentifier('type')      . ' = ?', $folderId)
131             ->order('counter DESC')
132             ->limit(1);
133         
134         $stmt = $this->_db->query($select);
135         $state = $stmt->fetchObject('Syncope_Model_SyncState');
136         $stmt = null; # see https://bugs.php.net/bug.php?id=44081
137         
138         if (! $state instanceof Syncope_Model_ISyncState) {
139             throw new Syncope_Exception_NotFound('id not found');
140         }
141         
142         $this->_convertFields($state);
143         
144         return $state;
145     }
146     
147     /**
148      * delete all stored synckeys for given type
149      *
150      * @param  Syncope_Model_IDevice|string  $_deviceId
151      * @param  Syncope_Model_IFolder|string  $_folderId
152      */
153     public function resetState($_deviceId, $_folderId)
154     {
155         $deviceId = $_deviceId instanceof Syncope_Model_IDevice ? $_deviceId->id : $_deviceId;
156         $folderId = $_folderId instanceof Syncope_Model_IFolder ? $_folderId->id : $_folderId;
157          
158         $where = array(
159             $this->_db->quoteInto($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId),
160             $this->_db->quoteInto($this->_db->quoteIdentifier('type') . ' = ?',      $folderId)
161         );
162     
163         $this->_db->delete($this->_tablePrefix . 'synckey', $where);
164     }
165     
166     public function update(Syncope_Model_ISyncState $_syncState)
167     {
168         $deviceId = $_syncState->device_id instanceof Syncope_Model_IDevice ? $_syncState->device_id->id : $_syncState->device_id;
169         
170         $this->_db->update($this->_tablePrefix . 'synckey', array(
171             'counter'     => $_syncState->counter,
172             'lastsync'    => $_syncState->lastsync->format('Y-m-d H:i:s'),
173             'pendingdata' => isset($_syncState->pendingdata) && is_array($_syncState->pendingdata) ? Zend_Json::encode($_syncState->pendingdata) : null
174         ), array(
175             'id = ?' => $_syncState->id
176         ));
177         
178         return $this->get($_syncState->id);
179     }
180     
181     /**
182      * get array of ids which got send to the client for a given class
183      *
184      * @param  Syncope_Model_IDevice|string  $_deviceId
185      * @param  Syncope_Model_IFolder|string  $_folderId
186      * @return Syncope_Model_SyncState
187      */
188     public function validate($_deviceId, $_folderId, $_syncKey)
189     {
190         $deviceId = $_deviceId instanceof Syncope_Model_IDevice ? $_deviceId->id : $_deviceId;
191         $folderId = $_folderId instanceof Syncope_Model_IFolder ? $_folderId->id : $_folderId;
192         
193         $select = $this->_db->select()
194             ->from($this->_tablePrefix . 'synckey')
195             ->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
196             ->where($this->_db->quoteIdentifier('counter')   . ' = ?', $_syncKey)
197             ->where($this->_db->quoteIdentifier('type')      . ' = ?', $folderId);
198         
199         $stmt = $this->_db->query($select);
200         $state = $stmt->fetchObject('Syncope_Model_SyncState');
201         $stmt = null; # see https://bugs.php.net/bug.php?id=44081
202         
203         if (! $state instanceof Syncope_Model_ISyncState) {
204             return false;
205         }
206
207         $this->_convertFields($state);
208         
209         // check if this was the latest syncKey
210         $select = $this->_db->select()
211             ->from($this->_tablePrefix . 'synckey')
212             ->where($this->_db->quoteIdentifier('device_id') . ' = ?', $deviceId)
213             ->where($this->_db->quoteIdentifier('counter') . ' = ?', $_syncKey + 1)
214             ->where($this->_db->quoteIdentifier('type') . ' = ?', $folderId);
215         
216         $stmt = $this->_db->query($select);
217         $moreRecentState = $stmt->fetchObject('Syncope_Model_SyncState');
218         $stmt = null; # see https://bugs.php.net/bug.php?id=44081
219         
220         // found more recent synckey => the last sync repsone got not received by the client
221         if ($moreRecentState instanceof Syncope_Model_ISyncState) {
222             // undelete entries marked as deleted in syncope_content table
223             $this->_db->update($this->_tablePrefix . 'content', array(
224                 'is_deleted'  => 0,
225             ), array(
226                 'device_id = ?'        => $deviceId,
227                 'folder_id = ?'        => $folderId,
228                 'creation_synckey = ?' => $state->counter,
229                 'is_deleted = ?'       => 1
230             ));
231             
232             // remove entries added during latest sync in syncope_content table
233             $this->_db->delete($this->_tablePrefix . 'content', array(
234                 'device_id = ?'        => $deviceId,
235                 'folder_id = ?'        => $folderId,
236                 'creation_synckey > ?' => $state->counter,
237             ));
238             
239         } else {
240             // finaly delete all entries marked for removal in syncope_content table    
241             $this->_db->delete($this->_tablePrefix . 'content', array(
242                 'device_id = ?'  => $deviceId,
243                 'folder_id = ?'  => $folderId,
244                 'is_deleted = ?' => 1
245             ));
246             
247         }
248         
249         // remove all other synckeys
250         $this->_deleteOtherStates($state);
251         
252         return $state;
253     }
254 }