1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10: 11: 12: 13: 14:
15: class Horde_ActiveSync_Request_Ping extends Horde_ActiveSync_Request_Base
16: {
17: const STATUS_NOCHANGES = 1;
18: const STATUS_NEEDSYNC = 2;
19: const STATUS_MISSING = 3;
20: const STATUS_PROTERROR = 4;
21: const STATUS_HBOUTOFBOUNDS = 5;
22:
23:
24: const STATUS_MAXFOLDERS = 6;
25:
26:
27: const STATUS_FOLDERSYNCREQD = 7;
28: const STATUS_SERVERERROR = 8;
29:
30:
31: const PING = 'Ping:Ping';
32: const STATUS = 'Ping:Status';
33: const HEARTBEATINTERVAL = 'Ping:HeartbeatInterval';
34: const FOLDERS = 'Ping:Folders';
35: const FOLDER = 'Ping:Folder';
36: const SERVERENTRYID = 'Ping:ServerEntryId';
37: const FOLDERTYPE = 'Ping:FolderType';
38:
39: protected $_ping_settings;
40:
41: protected function _checkHeartbeat($lifetime)
42: {
43: if (!empty($this->_ping_settings['forcedheartbeat'])) {
44: return $this->_ping_settings['forcedheartbeat'];
45: }
46: if ($lifetime !== 0 && $lifetime < $this->_ping_settings['heartbeatmin']) {
47: $this->_statusCode = self::STATUS_HBOUTOFBOUNDS;
48: $lifetime = $this->_ping_settings['heartbeatmin'];
49: $this->_state->setHeartbeatInterval($lifetime);
50: } elseif ($lifetime > $this->_ping_settings['heartbeatmax']) {
51: $this->_statusCode = self::STATUS_HBOUTOFBOUNDS;
52: $lifetime = $this->_ping_settings['heartbeatmax'];
53: $this->_state->setHeartbeatInterval($lifetime);
54: }
55:
56: return $lifetime;
57: }
58:
59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69:
70: public function handle()
71: {
72: $now = time();
73: parent::handle();
74:
75: $this->_logger->info('[' . $this->_device->id . '] PING received at timestamp: ' . $now . '.');
76:
77:
78: $this->_ping_settings = $this->_driver->getHeartbeatConfig();
79: $timeout = $this->_ping_settings['waitinterval'];
80:
81:
82: $this->_statusCode = self::STATUS_NOCHANGES;
83:
84:
85: $this->_state = &$this->_driver->getStateObject();
86: $this->_state->loadDeviceInfo($this->_device->id, $this->_driver->getUser());
87:
88:
89:
90:
91: $collections = array_values($this->_state->initPingState($this->_device));
92: $lifetime = $this->_checkHeartbeat($this->_state->getHeartbeatInterval());
93:
94:
95: if ($this->_decoder->getElementStartTag(self::PING)) {
96: if ($this->_decoder->getElementStartTag(self::HEARTBEATINTERVAL)) {
97: $lifetime = $this->_checkHeartbeat($this->_decoder->getElementContent());
98: $this->_decoder->getElementEndTag();
99: }
100: if ($lifetime == 0) {
101: $lifetime = $this->_ping_settings['heartbeatdefault'];
102: }
103: $this->_state->setHeartbeatInterval($lifetime);
104:
105: if ($this->_decoder->getElementStartTag(self::FOLDERS)) {
106: $collections = array();
107: while ($this->_decoder->getElementStartTag(self::FOLDER)) {
108: $collection = array();
109: if ($this->_decoder->getElementStartTag(self::SERVERENTRYID)) {
110: $collection['id'] = $this->_decoder->getElementContent();
111: $this->_decoder->getElementEndTag();
112: }
113: if ($this->_decoder->getElementStartTag(self::FOLDERTYPE)) {
114: $collection['class'] = $this->_decoder->getElementContent();
115: $this->_decoder->getElementEndTag();
116: }
117:
118: $this->_decoder->getElementEndTag();
119:
120: $collections = array_merge($collections, array($collection['id'] => $collection));
121: }
122: $collections = array_values($collections);
123:
124: if (!$this->_decoder->getElementEndTag()) {
125: $this->_statusCode = self::STATUS_PROTERROR;
126: return false;
127: }
128: }
129:
130: if (!$this->_decoder->getElementEndTag()) {
131: $this->_statusCode = self::STATUS_PROTERROR;
132: return false;
133: }
134:
135: $this->_state->addPingCollections($collections);
136: } else {
137: $this->_logger->debug(sprintf('Reusing PING state: %s', print_r($collections, true)));
138: }
139:
140: $changes = array();
141: $dataavailable = false;
142:
143:
144: if ($this->_statusCode == self::STATUS_NOCHANGES) {
145: $this->_logger->info(
146: sprintf('[%s] Waiting for changes (heartbeat interval: %d)',
147: $this->_device->id,
148: $lifetime));
149: $expire = $now + $lifetime;
150: while (time() <= $expire) {
151:
152:
153: if ($this->_provisioning === true) {
154: $rwstatus = $this->_state->getDeviceRWStatus($this->_device->id);
155: if ($rwstatus == Horde_ActiveSync::RWSTATUS_PENDING || $rwstatus == Horde_ActiveSync::RWSTATUS_WIPED) {
156: $this->_statusCode = self::STATUS_FOLDERSYNCREQD;
157: break;
158: }
159: }
160:
161: if (count($collections) == 0) {
162: $this->_logger->err('0 collections');
163: $this->_statusCode = self::STATUS_MISSING;
164: break;
165: }
166:
167: for ($i = 0; $i < count($collections); $i++) {
168: $collection = $collections[$i];
169: $collection['synckey'] = $this->_device->id;
170: $sync = $this->_driver->getSyncObject();
171: try {
172: $this->_state->loadPingCollectionState($collection);
173: } catch (Horde_ActiveSync_Exception_InvalidRequest $e) {
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184: $this->_logger->err('PING terminating: ' . $e->getMessage());
185: $expire = time();
186: continue;
187: } catch (Horde_ActiveSync_Exception_StateGone $e) {
188: $this->_logger->err('PING terminating, forcing a SYNC: ' . $e->getMessage());
189: $this->_statusCode = self::STATUS_NEEDSYNC;
190: $dataavailable = true;
191: $changes[$collection['id']] = 1;
192: continue;
193: } catch (Horde_ActiveSync_Exception $e) {
194: $this->_logger->err('PING terminating: ' . $e->getMessage());
195: $this->_statusCode = self::STATUS_SERVERERROR;
196: break;
197: }
198: try {
199: $sync->init($this->_state, null, $collection);
200: } catch (Horde_ActiveSync_Exception $e) {
201:
202: $this->_logger->err('Ping error: Exporter can not be configured. ' . $e->getMessage() . ' Waiting 30 seconds before ping is retried.');
203: sleep(30);
204: break;
205: }
206:
207: $changecount = $sync->GetChangeCount();
208: if ($changecount > 0) {
209: $dataavailable = true;
210: $changes[$collection['id']] = $changecount;
211: $this->_statusCode = self::STATUS_NEEDSYNC;
212: }
213:
214:
215:
216: while (is_array($sync->syncronize(Horde_ActiveSync::BACKEND_DISCARD_DATA)));
217: }
218:
219: if ($dataavailable) {
220: $this->_logger->info('[' . $this->_device->id . '] Changes available');
221: break;
222: }
223:
224: sleep($timeout);
225: }
226: }
227:
228:
229: $this->_logger->info('[' . $this->_device->id . '] Sending response for PING.');
230: $this->_encoder->StartWBXML();
231:
232: $this->_encoder->startTag(self::PING);
233:
234: $this->_encoder->startTag(self::STATUS);
235: $this->_encoder->content($this->_statusCode);
236: $this->_encoder->endTag();
237:
238: if ($this->_statusCode == self::STATUS_HBOUTOFBOUNDS) {
239: $this->_encoder->startTag(self::HEARTBEATINTERVAL);
240: $this->_encoder->content($lifetime);
241: $this->_encoder->endTag();
242: } else {
243: $this->_encoder->startTag(self::FOLDERS);
244: foreach ($collections as $collection) {
245: if (isset($changes[$collection['id']])) {
246: $this->_encoder->startTag(self::FOLDER);
247: $this->_encoder->content($collection['id']);
248: $this->_encoder->endTag();
249: }
250: }
251: $this->_encoder->endTag();
252: }
253: $this->_encoder->endTag();
254:
255: $this->_state->savePingState();
256:
257: return true;
258: }
259:
260: }
261: