d48e119f5c1d9643663d7171b80f09d39056e199
[platform/upstream/syncevolution.git] / src / backends / activesync / ActiveSyncSource.cpp
1 /*
2  * Copyright (C) 2007-2009 Patrick Ohly <patrick.ohly@gmx.de>
3  * Copyright (C) 2009 Intel Corporation
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) version 3.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301  USA
19  */
20
21 #ifdef HAVE_CONFIG_H
22 # include "config.h"
23 #endif
24
25 #ifdef ENABLE_ACTIVESYNC
26
27 #include "ActiveSyncSource.h"
28
29 #include <eas-errors.h>
30
31 #include <stdlib.h>
32 #include <errno.h>
33
34 #include <boost/algorithm/string.hpp>
35 #include <boost/range/adaptors.hpp>
36
37 SE_GOBJECT_TYPE(EasSyncHandler)
38
39 /* #include <eas-connection-errors.h> */
40 #include <syncevo/declarations.h>
41 SE_BEGIN_CXX
42
43 void EASItemUnref(EasItemInfo *info) { g_object_unref(&info->parent_instance); }
44 void GStringUnref(char *str) { g_free(str); }
45 void EASFolderUnref(EasFolder *f) { g_object_unref(&f->parent_instance); }
46
47 void ActiveSyncSource::enableServerMode()
48 {
49     SyncSourceAdmin::init(m_operations, this);
50     SyncSourceBlob::init(m_operations, getCacheDir());
51 }
52 bool ActiveSyncSource::serverModeEnabled() const
53 {
54     return m_operations.m_loadAdminData;
55 }
56
57 /* Recursively work out full path name */
58 std::string ActiveSyncSource::Collection::fullPath() {
59     if (!pathFound) {
60         if (parentId == "0") {
61             pathName = name;
62         } else {
63             pathName = source->m_collections[parentId].fullPath() + "/" + name;
64         }
65         pathFound = true;
66     }
67
68     return pathName;
69 }
70
71 void ActiveSyncSource::findCollections(const std::string account, const bool force_update)
72 {
73     GErrorCXX gerror;
74     EasSyncHandlerCXX handler;
75     EASFoldersCXX folders;
76     
77     if (!m_collections.empty()) {
78         if (!force_update) return;
79         m_collections.clear();
80         m_folderPaths.clear();
81     }
82     
83     /* Fetch the folders */
84     handler = EasSyncHandlerCXX::steal(eas_sync_handler_new(account.c_str()));
85     if (!handler) throwError("findCollections cannot allocate sync handler");
86     
87     if (!eas_sync_handler_get_folder_list (handler,
88                                            force_update,
89                                            folders,
90                                            NULL,
91                                            gerror)) {
92         gerror.throwError("fetching folder list");
93     }
94     
95     /* Save the Collections */
96     BOOST_FOREACH(EasFolder *folder, folders) {
97         m_collections[folder->folder_id].collectionId = folder->folder_id;
98         m_collections[folder->folder_id].name = folder->display_name;
99         m_collections[folder->folder_id].parentId = folder->parent_id;
100         m_collections[folder->folder_id].type = folder->type;
101         m_collections[folder->folder_id].source = this;
102     }
103     
104     /* Save the full paths */
105     BOOST_FOREACH(std::string id, m_collections | boost::adaptors::map_keys) {
106         m_folderPaths[m_collections[id].fullPath()] = id;
107     }
108 }
109
110 int ActiveSyncSource::Collection::getFolderType () {
111     switch (type) {
112     case EAS_FOLDER_TYPE_DEFAULT_INBOX:
113     case EAS_FOLDER_TYPE_DEFAULT_DRAFTS:
114     case EAS_FOLDER_TYPE_DEFAULT_DELETED_ITEMS:
115     case EAS_FOLDER_TYPE_DEFAULT_SENT_ITEMS:
116     case EAS_FOLDER_TYPE_DEFAULT_OUTBOX:
117     case EAS_FOLDER_TYPE_USER_CREATED_MAIL:
118         return EAS_ITEM_MAIL;
119     case EAS_FOLDER_TYPE_DEFAULT_TASKS:
120     case EAS_FOLDER_TYPE_USER_CREATED_TASKS:
121         return EAS_ITEM_TODO;
122     case EAS_FOLDER_TYPE_DEFAULT_CALENDAR:
123     case EAS_FOLDER_TYPE_USER_CREATED_CALENDAR:
124         return EAS_ITEM_CALENDAR;
125     case EAS_FOLDER_TYPE_DEFAULT_CONTACTS:
126     case EAS_FOLDER_TYPE_USER_CREATED_CONTACTS:
127         return EAS_ITEM_CONTACT;
128     case EAS_FOLDER_TYPE_DEFAULT_NOTES:
129     case EAS_FOLDER_TYPE_USER_CREATED_NOTES:
130         //TODO: implement memos
131     case EAS_FOLDER_TYPE_DEFAULT_JOURNAL:
132     case EAS_FOLDER_TYPE_USER_CREATED_JOURNAL:
133     case EAS_FOLDER_TYPE_UNKNOWN:
134     case EAS_FOLDER_TYPE_RECIPIENT_CACHE:
135     default:
136         return -1;
137     }
138 }
139
140 bool ActiveSyncSource::Collection::collectionIsDefault () {
141     return type == EAS_FOLDER_TYPE_DEFAULT_INBOX ||
142         type == EAS_FOLDER_TYPE_DEFAULT_DRAFTS ||
143         type == EAS_FOLDER_TYPE_DEFAULT_DELETED_ITEMS ||
144         type == EAS_FOLDER_TYPE_DEFAULT_SENT_ITEMS ||
145         type == EAS_FOLDER_TYPE_DEFAULT_OUTBOX ||
146         type == EAS_FOLDER_TYPE_DEFAULT_TASKS ||
147         type == EAS_FOLDER_TYPE_DEFAULT_CALENDAR ||
148         type == EAS_FOLDER_TYPE_DEFAULT_CONTACTS ||
149         type == EAS_FOLDER_TYPE_DEFAULT_NOTES ||
150         type == EAS_FOLDER_TYPE_DEFAULT_JOURNAL;
151 }
152
153 ActiveSyncSource::Databases ActiveSyncSource::getDatabases()
154 {
155     Databases result;
156     // do a scan if username is set
157     std::string account = m_context->getSyncUsername();
158
159     if (!account.empty()) {
160
161         findCollections(account, true);
162
163         BOOST_FOREACH(Collection coll, m_collections | boost::adaptors::map_values) {
164             if (coll.getFolderType() == getEasType()) {
165                 result.push_back(Database(coll.pathName, coll.collectionId, coll.collectionIsDefault()));
166             }
167         }
168
169     } else {
170         result.push_back(Database("to scan, specify --print-databases username=<account> backend=\""+getSourceType().m_backend+"\"",
171                                   ""));
172     }
173
174     return result;
175 }
176
177 std::string ActiveSyncSource::lookupFolder(std::string folder) {
178     // If folder matches a collectionId, use that
179     if (m_collections.find(folder) != m_collections.end()) return folder;
180
181     // If folder begins with /, drop it
182     if (folder[0] == '/') folder.erase(0,1);
183
184     // Lookup folder name
185     if (m_folderPaths.find(folder) != m_folderPaths.end()) return m_folderPaths[folder];
186
187     // Not found
188     return "";
189 }
190
191 void ActiveSyncSource::open()
192 {
193     // extract account ID and throw error if missing
194     std::string username = m_context->getSyncUsername();
195     std::string folder = getDatabaseID();
196     SE_LOG_DEBUG(NULL, NULL,
197                  "using eas sync account %s from config %s with folder %s",
198                  username.c_str(),
199                  m_context->getConfigName().c_str(),
200                  folder.c_str());
201
202     if (folder.empty()) { // Most common case is empty string
203         m_folder = folder;
204     } else { // Lookup folder name
205         // Try using cached folder list
206         findCollections(username, false);
207         m_folder = lookupFolder(folder);
208         if (m_folder.empty()) {
209             // Fetch latest folder list and try again
210             findCollections(username, true);
211             m_folder = lookupFolder(folder);
212         }
213         if (m_folder.empty()) {
214             throwError("could not find folder: "+folder);
215         }
216     }
217
218     m_account = username;
219
220     // create handler
221     m_handler.set(eas_sync_handler_new(m_account.c_str()), "EAS handler");
222 }
223
224 void ActiveSyncSource::close()
225 {
226     // free handler if not done already
227     m_handler.set(NULL);
228 }
229
230 void ActiveSyncSource::beginSync(const std::string &lastToken, const std::string &resumeToken)
231 {
232     // erase content which might have been set in a previous call
233     reset();
234
235     // claim item node for ids, if not done yet
236     if (m_itemNode && !m_ids) {
237         m_ids.swap(m_itemNode);
238     }
239
240     // incremental sync (non-empty token) or start from scratch
241     m_startSyncKey = lastToken;
242     if (lastToken.empty()) {
243         // slow sync: wipe out cached list of IDs, will be filled anew below
244         SE_LOG_DEBUG(this, NULL, "sync key empty, starting slow sync");
245         m_ids->clear();
246     } else {
247         SE_LOG_DEBUG(this, NULL, "sync key %s for account '%s' folder '%s', starting incremental sync",
248                      lastToken.c_str(),
249                      m_account.c_str(),
250                      m_folder.c_str());
251     }
252
253     gboolean moreAvailable = TRUE;
254
255     m_currentSyncKey = m_startSyncKey;
256
257     // same logic as in ActiveSyncCalendarSource::beginSync()
258
259     bool slowSync = false;
260     for (bool firstIteration = true;
261          moreAvailable;
262          firstIteration = false) {
263         gchar *buffer = NULL;
264         GErrorCXX gerror;
265         EASItemsCXX created, updated;
266         EASIdsCXX deleted;
267         bool wasSlowSync = m_currentSyncKey.empty();
268
269         if (!eas_sync_handler_get_items(m_handler,
270                                         m_currentSyncKey.c_str(),
271                                         &buffer,
272                                         getEasType(),
273                                         m_folder.c_str(),
274                                         created, updated, deleted,
275                                         &moreAvailable,
276                                         gerror)) {
277             if (gerror.m_gerror &&
278                 /*
279                 gerror.m_gerror->domain == EAS_TYPE_CONNECTION_ERROR &&
280                 gerror.m_gerror->code == EAS_CONNECTION_SYNC_ERROR_INVALIDSYNCKEY && */
281                 gerror.m_gerror->message &&
282                 strstr(gerror.m_gerror->message, "Sync error: Invalid synchronization key") &&
283                 firstIteration) {
284                 // fall back to slow sync
285                 slowSync = true;
286                 m_currentSyncKey = "";
287                 m_ids->clear();
288                 continue;
289             }
290
291             gerror.throwError("reading ActiveSync changes");
292         }
293         GStringPtr bufferOwner(buffer, "reading changes: empty sync key returned");
294
295         // TODO: Test that we really get an empty token here for an unexpected slow
296         // sync. If not, we'll start an incremental sync here and later the engine
297         // will ask us for older, unmodified item content which we won't have.
298
299         // populate ID lists and content cache
300         BOOST_FOREACH(EasItemInfo *item, created) {
301             if (!item->server_id) {
302                 throwError("no server ID for new eas item");
303             }
304             string luid(item->server_id);
305             if (luid.empty()) {
306                 throwError("empty server ID for new eas item");
307             }
308             SE_LOG_DEBUG(this, NULL, "new item %s", luid.c_str());
309             addItem(luid, NEW);
310             m_ids->setProperty(luid, "1");
311             if (!item->data) {
312                 throwError(StringPrintf("no body returned for new eas item %s", luid.c_str()));
313             }
314             m_items[luid] = item->data;
315         }
316         BOOST_FOREACH(EasItemInfo *item, updated) {
317             if (!item->server_id) {
318                 throwError("no server ID for updated eas item");
319             }
320             string luid(item->server_id);
321             if (luid.empty()) {
322                 throwError("empty server ID for updated eas item");
323             }
324             SE_LOG_DEBUG(this, NULL, "updated item %s", luid.c_str());
325             addItem(luid, UPDATED);
326             // m_ids.setProperty(luid, "1"); not necessary, should already exist (TODO: check?!)
327             if (!item->data) {
328                 throwError(StringPrintf("no body returned for updated eas item %s", luid.c_str()));
329             }
330             m_items[luid] = item->data;
331         }
332         BOOST_FOREACH(const char *serverID, deleted) {
333             if (!serverID) {
334                 throwError("no server ID for deleted eas item");
335             }
336             string luid(serverID);
337             if (luid.empty()) {
338                 throwError("empty server ID for deleted eas item");
339             }
340             SE_LOG_DEBUG(this, NULL, "deleted item %s", luid.c_str());
341             addItem(luid, DELETED);
342             m_ids->removeProperty(luid);
343         }
344
345         // update key
346         m_currentSyncKey = buffer;
347
348         // Google  hack: if we started with an empty sync key (= slow sync)
349         // and got no results (= existing items), then try one more time,
350         // because Google only seems to report results when asked with
351         // a valid sync key. As an additional sanity check make sure that
352         // we have a valid sync key now.
353         if (wasSlowSync &&
354             created.empty() &&
355             !m_currentSyncKey.empty()) {
356             moreAvailable = true;
357         }
358     }
359
360     // now also generate full list of all current items:
361     // old items + new (added to m_ids above) - deleted (removed above)
362     ConfigProps props;
363     m_ids->readProperties(props);
364     BOOST_FOREACH(const StringPair &entry, props) {
365         const std::string &luid = entry.first;
366         SE_LOG_DEBUG(this, NULL, "existing item %s", luid.c_str());
367         addItem(luid, ANY);
368     }
369
370     if (slowSync) {
371         // tell engine that we need a slow sync, if it didn't know already
372         SE_THROW_EXCEPTION_STATUS(StatusException,
373                                   "ActiveSync error: Invalid synchronization key",
374                                   STATUS_SLOW_SYNC_508);
375     }
376 }
377
378 std::string ActiveSyncSource::endSync(bool success)
379 {
380     // store current set of items
381     if (!success) {
382         m_ids->clear();
383     }
384     m_ids->flush();
385
386     // let engine do incremental sync next time or start from scratch
387     // in case of failure
388     std::string newSyncKey = success ? m_currentSyncKey : "";
389     SE_LOG_DEBUG(this, NULL, "next sync key %s", newSyncKey.empty() ? "empty" : newSyncKey.c_str());
390     return newSyncKey;
391 }
392
393 void ActiveSyncSource::deleteItem(const string &luid)
394 {
395     // asking to delete a non-existent item via ActiveSync does not
396     // trigger an error; this is expected by the caller, so detect
397     // the problem by looking up the item in our list (and keep the
398     // list up-to-date elsewhere)
399     if (m_ids && m_ids->readProperty(luid).empty()) {
400         throwError(STATUS_NOT_FOUND, "item not found: " + luid);
401     }
402
403     // send delete request
404     // TODO (?): batch delete requests
405     GListCXX<char, GSList> items;
406     items.push_back((char *)luid.c_str());
407
408     GErrorCXX gerror;
409     char *buffer;
410     if (!eas_sync_handler_delete_items(m_handler,
411                                        m_currentSyncKey.c_str(),
412                                        &buffer,
413                                        getEasType(),
414                                        m_folder.c_str(),
415                                        items,
416                                        gerror)) {
417         gerror.throwError("deleting eas item");
418     }
419     GStringPtr bufferOwner(buffer, "delete items: empty sync key returned");
420
421     // remove from item list
422     if (m_ids) {
423         m_items.erase(luid);
424         m_ids->removeProperty(luid);
425     }
426
427     // update key
428     m_currentSyncKey = buffer;
429 }
430
431 SyncSourceSerialize::InsertItemResult ActiveSyncSource::insertItem(const std::string &luid, const std::string &data)
432 {
433     SyncSourceSerialize::InsertItemResult res;
434
435     EASItemPtr tmp(eas_item_info_new(), "EasItem");
436     EasItemInfo *item = tmp.get();
437     if (!luid.empty()) {
438         // update
439         item->server_id = g_strdup(luid.c_str());
440     } else {
441         // add
442         // TODO: is a local id needed? We don't have one.
443     }
444     item->data = g_strdup(data.c_str());
445     EASItemsCXX items;
446     items.push_front(tmp.release());
447
448     GErrorCXX gerror;
449     char *buffer;
450
451     // distinguish between update (existing luid)
452     // or creation (empty luid)
453     if (luid.empty()) {
454         // send item to server
455         if (!eas_sync_handler_add_items(m_handler,
456                                         m_currentSyncKey.c_str(),
457                                         &buffer,
458                                         getEasType(),
459                                         m_folder.c_str(),
460                                         items,
461                                         gerror)) {
462             gerror.throwError("adding eas item");
463         }
464         if (!item->server_id) {
465             throwError("no server ID for new eas item");
466         }
467         // get new ID from updated item
468         res.m_luid = item->server_id;
469         if (res.m_luid.empty()) {
470             throwError("empty server ID for new eas item");
471         }
472
473         // TODO: if someone else has inserted a new calendar item
474         // with the same UID as the one we are trying to insert here,
475         // what will happen? Does the ActiveSync server prevent
476         // adding our own version of the item or does it merge?
477         // res.m_merged = ???
478     } else {
479         // update item on server
480         if (!eas_sync_handler_update_items(m_handler,
481                                            m_currentSyncKey.c_str(),
482                                            &buffer,
483                                            getEasType(),
484                                            m_folder.c_str(),
485                                            items,
486                                            gerror)) {
487             gerror.throwError("updating eas item");
488         }
489         res.m_luid = luid;
490     }
491     GStringPtr bufferOwner(buffer, "insert item: empty sync key returned");
492
493     // add/update in cache
494     if (m_ids) {
495         m_items[res.m_luid] = data;
496         m_ids->setProperty(res.m_luid, "1");
497     }
498
499     // update key
500     m_currentSyncKey = buffer;
501
502     return res;
503 }
504
505 void ActiveSyncSource::readItem(const std::string &luid, std::string &item)
506 {
507     // return straight from cache?
508     std::map<std::string, std::string>::iterator it = m_items.find(luid);
509     if (it == m_items.end()) {
510         // no, must fetch
511         EASItemPtr tmp(eas_item_info_new(), "EasItem");
512         GErrorCXX gerror;
513         if (!eas_sync_handler_fetch_item(m_handler,
514                                          m_folder.c_str(),
515                                          luid.c_str(),
516                                          tmp,
517                                          getEasType(),
518                                          gerror)) {
519             if (gerror.m_gerror->message &&
520                 strstr(gerror.m_gerror->message, "ObjectNotFound")
521                 /* gerror.matches(EAS_CONNECTION_ERROR, EAS_CONNECTION_ITEMOPERATIONS_ERROR_OBJECTNOTFOUND)
522                    (gdb) p *m_gerror
523                    $7 = {domain = 156, code = 36, 
524                    message = 0xda2940 "GDBus.Error:org.meego.activesyncd.ItemOperationsError.ObjectNotFound: Document library - The object was not found or access denied."}
525
526                 */) {
527                 throwError(STATUS_NOT_FOUND, "item not found: " + luid);
528             } else {
529                 gerror.throwError(StringPrintf("reading eas item %s", luid.c_str()));
530             }
531         }
532         if (!tmp->data) {
533             throwError(StringPrintf("no body returned for eas item %s", luid.c_str()));
534         }
535         item = tmp->data;
536     } else {
537         item = it->second;
538     }
539 }
540
541 void ActiveSyncSource::getSynthesisInfo(SynthesisInfo &info,
542                                         XMLConfigFragments &fragments)
543 {
544     TestingSyncSource::getSynthesisInfo(info, fragments);
545
546     /**
547      * Disable reading of existing item by engine before updating
548      * it by pretending to do the merging ourselves. This works
549      * as long as the local side is able to store all data that
550      * activesyncd gives to us and updates on the ActiveSync
551      * server.
552      *
553      * Probably some Exchange-specific extensions currently get
554      * lost because activesyncd does not know how to represent
555      * them as vCard and does not tell the ActiveSync server that
556      * it cannot handle them.
557      */
558     boost::replace_first(info.m_datastoreOptions,
559                          "<updateallfields>true</updateallfields>",
560                          "");
561
562     /**
563      * no ActiveSync specific rules yet, use condensed format as
564      * if we were storing locally, with all extensions enabled
565      */
566     info.m_backendRule = "LOCALSTORAGE";
567
568     /**
569      * access to data must be done early so that a slow sync can be
570      * enforced when the ActiveSync sync key turns out to be
571      * invalid
572      */
573     info.m_earlyStartDataRead = true;
574 }
575
576 SE_END_CXX
577
578 #endif /* ENABLE_ACTIVESYNC */
579
580 #ifdef ENABLE_MODULES
581 # include "ActiveSyncSourceRegister.cpp"
582 #endif