2 * Copyright (C) 2007-2009 Patrick Ohly <patrick.ohly@gmx.de>
3 * Copyright (C) 2009 Intel Corporation
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.
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.
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
25 #ifdef ENABLE_ACTIVESYNC
27 #include "ActiveSyncSource.h"
29 #include <eas-errors.h>
34 #include <boost/algorithm/string.hpp>
35 #include <boost/range/adaptors.hpp>
37 SE_GOBJECT_TYPE(EasSyncHandler)
39 /* #include <eas-connection-errors.h> */
40 #include <syncevo/declarations.h>
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); }
47 void ActiveSyncSource::enableServerMode()
49 SyncSourceAdmin::init(m_operations, this);
50 SyncSourceBlob::init(m_operations, getCacheDir());
52 bool ActiveSyncSource::serverModeEnabled() const
54 return m_operations.m_loadAdminData;
57 /* Recursively work out full path name */
58 std::string ActiveSyncSource::Collection::fullPath() {
60 if (parentId == "0") {
63 pathName = source->m_collections[parentId].fullPath() + "/" + name;
71 void ActiveSyncSource::findCollections(const std::string account, const bool force_update)
74 EasSyncHandlerCXX handler;
75 EASFoldersCXX folders;
77 if (!m_collections.empty()) {
78 if (!force_update) return;
79 m_collections.clear();
80 m_folderPaths.clear();
83 /* Fetch the folders */
84 handler = EasSyncHandlerCXX::steal(eas_sync_handler_new(account.c_str()));
85 if (!handler) throwError("findCollections cannot allocate sync handler");
87 if (!eas_sync_handler_get_folder_list (handler,
92 gerror.throwError("fetching folder list");
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;
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;
110 int ActiveSyncSource::Collection::getFolderType () {
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:
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;
153 ActiveSyncSource::Databases ActiveSyncSource::getDatabases()
156 // do a scan if username is set
157 std::string account = m_context->getSyncUsername();
159 if (!account.empty()) {
161 findCollections(account, true);
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()));
170 result.push_back(Database("to scan, specify --print-databases username=<account> backend=\""+getSourceType().m_backend+"\"",
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;
181 // If folder begins with /, drop it
182 if (folder[0] == '/') folder.erase(0,1);
184 // Lookup folder name
185 if (m_folderPaths.find(folder) != m_folderPaths.end()) return m_folderPaths[folder];
191 void ActiveSyncSource::open()
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",
199 m_context->getConfigName().c_str(),
202 if (folder.empty()) { // Most common case is empty string
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);
213 if (m_folder.empty()) {
214 throwError("could not find folder: "+folder);
218 m_account = username;
221 m_handler.set(eas_sync_handler_new(m_account.c_str()), "EAS handler");
224 void ActiveSyncSource::close()
226 // free handler if not done already
230 void ActiveSyncSource::beginSync(const std::string &lastToken, const std::string &resumeToken)
232 // erase content which might have been set in a previous call
235 // claim item node for ids, if not done yet
236 if (m_itemNode && !m_ids) {
237 m_ids.swap(m_itemNode);
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");
247 SE_LOG_DEBUG(this, NULL, "sync key %s for account '%s' folder '%s', starting incremental sync",
253 gboolean moreAvailable = TRUE;
255 m_currentSyncKey = m_startSyncKey;
257 // same logic as in ActiveSyncCalendarSource::beginSync()
259 bool slowSync = false;
260 for (bool firstIteration = true;
262 firstIteration = false) {
263 gchar *buffer = NULL;
265 EASItemsCXX created, updated;
267 bool wasSlowSync = m_currentSyncKey.empty();
269 if (!eas_sync_handler_get_items(m_handler,
270 m_currentSyncKey.c_str(),
274 created, updated, deleted,
277 if (gerror.m_gerror &&
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") &&
284 // fall back to slow sync
286 m_currentSyncKey = "";
291 gerror.throwError("reading ActiveSync changes");
293 GStringPtr bufferOwner(buffer, "reading changes: empty sync key returned");
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.
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");
304 string luid(item->server_id);
306 throwError("empty server ID for new eas item");
308 SE_LOG_DEBUG(this, NULL, "new item %s", luid.c_str());
310 m_ids->setProperty(luid, "1");
312 throwError(StringPrintf("no body returned for new eas item %s", luid.c_str()));
314 m_items[luid] = item->data;
316 BOOST_FOREACH(EasItemInfo *item, updated) {
317 if (!item->server_id) {
318 throwError("no server ID for updated eas item");
320 string luid(item->server_id);
322 throwError("empty server ID for updated eas item");
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?!)
328 throwError(StringPrintf("no body returned for updated eas item %s", luid.c_str()));
330 m_items[luid] = item->data;
332 BOOST_FOREACH(const char *serverID, deleted) {
334 throwError("no server ID for deleted eas item");
336 string luid(serverID);
338 throwError("empty server ID for deleted eas item");
340 SE_LOG_DEBUG(this, NULL, "deleted item %s", luid.c_str());
341 addItem(luid, DELETED);
342 m_ids->removeProperty(luid);
346 m_currentSyncKey = buffer;
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.
355 !m_currentSyncKey.empty()) {
356 moreAvailable = true;
360 // now also generate full list of all current items:
361 // old items + new (added to m_ids above) - deleted (removed above)
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());
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);
378 std::string ActiveSyncSource::endSync(bool success)
380 // store current set of items
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());
393 void ActiveSyncSource::deleteItem(const string &luid)
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);
403 // send delete request
404 // TODO (?): batch delete requests
405 GListCXX<char, GSList> items;
406 items.push_back((char *)luid.c_str());
410 if (!eas_sync_handler_delete_items(m_handler,
411 m_currentSyncKey.c_str(),
417 gerror.throwError("deleting eas item");
419 GStringPtr bufferOwner(buffer, "delete items: empty sync key returned");
421 // remove from item list
424 m_ids->removeProperty(luid);
428 m_currentSyncKey = buffer;
431 SyncSourceSerialize::InsertItemResult ActiveSyncSource::insertItem(const std::string &luid, const std::string &data)
433 SyncSourceSerialize::InsertItemResult res;
435 EASItemPtr tmp(eas_item_info_new(), "EasItem");
436 EasItemInfo *item = tmp.get();
439 item->server_id = g_strdup(luid.c_str());
442 // TODO: is a local id needed? We don't have one.
444 item->data = g_strdup(data.c_str());
446 items.push_front(tmp.release());
451 // distinguish between update (existing luid)
452 // or creation (empty luid)
454 // send item to server
455 if (!eas_sync_handler_add_items(m_handler,
456 m_currentSyncKey.c_str(),
462 gerror.throwError("adding eas item");
464 if (!item->server_id) {
465 throwError("no server ID for new eas item");
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");
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 = ???
479 // update item on server
480 if (!eas_sync_handler_update_items(m_handler,
481 m_currentSyncKey.c_str(),
487 gerror.throwError("updating eas item");
491 GStringPtr bufferOwner(buffer, "insert item: empty sync key returned");
493 // add/update in cache
495 m_items[res.m_luid] = data;
496 m_ids->setProperty(res.m_luid, "1");
500 m_currentSyncKey = buffer;
505 void ActiveSyncSource::readItem(const std::string &luid, std::string &item)
507 // return straight from cache?
508 std::map<std::string, std::string>::iterator it = m_items.find(luid);
509 if (it == m_items.end()) {
511 EASItemPtr tmp(eas_item_info_new(), "EasItem");
513 if (!eas_sync_handler_fetch_item(m_handler,
519 if (gerror.m_gerror->message &&
520 strstr(gerror.m_gerror->message, "ObjectNotFound")
521 /* gerror.matches(EAS_CONNECTION_ERROR, EAS_CONNECTION_ITEMOPERATIONS_ERROR_OBJECTNOTFOUND)
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."}
527 throwError(STATUS_NOT_FOUND, "item not found: " + luid);
529 gerror.throwError(StringPrintf("reading eas item %s", luid.c_str()));
533 throwError(StringPrintf("no body returned for eas item %s", luid.c_str()));
541 void ActiveSyncSource::getSynthesisInfo(SynthesisInfo &info,
542 XMLConfigFragments &fragments)
544 TestingSyncSource::getSynthesisInfo(info, fragments);
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
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.
558 boost::replace_first(info.m_datastoreOptions,
559 "<updateallfields>true</updateallfields>",
563 * no ActiveSync specific rules yet, use condensed format as
564 * if we were storing locally, with all extensions enabled
566 info.m_backendRule = "LOCALSTORAGE";
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
573 info.m_earlyStartDataRead = true;
578 #endif /* ENABLE_ACTIVESYNC */
580 #ifdef ENABLE_MODULES
581 # include "ActiveSyncSourceRegister.cpp"