syncevolution/src/backends/activesync/ActiveSyncSource.cpp

601 lines
20 KiB
C++

/*
* Copyright (C) 2007-2009 Patrick Ohly <patrick.ohly@gmx.de>
* Copyright (C) 2009 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef ENABLE_ACTIVESYNC
#include "ActiveSyncSource.h"
#include <syncevo/IdentityProvider.h>
#include <eas-errors.h>
#include <stdlib.h>
#include <errno.h>
#include <boost/algorithm/string.hpp>
#include <boost/range/adaptors.hpp>
SE_GOBJECT_TYPE(EasSyncHandler)
/* #include <eas-connection-errors.h> */
#include <syncevo/declarations.h>
SE_BEGIN_CXX
void EASItemUnref(EasItemInfo *info) { g_object_unref(&info->parent_instance); }
void GStringUnref(char *str) { g_free(str); }
void EASFolderUnref(EasFolder *f) { g_object_unref(&f->parent_instance); }
void ActiveSyncSource::enableServerMode()
{
SyncSourceAdmin::init(m_operations, this);
SyncSourceBlob::init(m_operations, getCacheDir());
}
bool ActiveSyncSource::serverModeEnabled() const
{
return m_operations.m_loadAdminData;
}
/* Recursively work out full path name */
std::string ActiveSyncSource::Collection::fullPath() {
if (!pathFound) {
if (parentId == "0") {
pathName = name;
} else {
pathName = source->m_collections[parentId].fullPath() + "/" + name;
}
pathFound = true;
}
return pathName;
}
void ActiveSyncSource::findCollections(const std::string &account, const bool force_update)
{
GErrorCXX gerror;
EasSyncHandlerCXX handler;
EASFoldersCXX folders;
if (!m_collections.empty()) {
if (!force_update) return;
m_collections.clear();
m_folderPaths.clear();
}
/* Fetch the folders */
handler = EasSyncHandlerCXX::steal(eas_sync_handler_new(account.c_str()));
if (!handler) throwError(SE_HERE, "findCollections cannot allocate sync handler");
if (!eas_sync_handler_get_folder_list (handler,
force_update,
folders,
NULL,
gerror)) {
gerror.throwError(SE_HERE, "fetching folder list");
}
/* Save the Collections */
BOOST_FOREACH(EasFolder *folder, folders) {
m_collections[folder->folder_id].collectionId = folder->folder_id;
m_collections[folder->folder_id].name = folder->display_name;
m_collections[folder->folder_id].parentId = folder->parent_id;
m_collections[folder->folder_id].type = folder->type;
m_collections[folder->folder_id].source = this;
}
/* Save the full paths */
BOOST_FOREACH(std::string id, m_collections | boost::adaptors::map_keys) {
m_folderPaths[m_collections[id].fullPath()] = id;
}
}
int ActiveSyncSource::Collection::getFolderType () {
switch (type) {
case EAS_FOLDER_TYPE_DEFAULT_INBOX:
case EAS_FOLDER_TYPE_DEFAULT_DRAFTS:
case EAS_FOLDER_TYPE_DEFAULT_DELETED_ITEMS:
case EAS_FOLDER_TYPE_DEFAULT_SENT_ITEMS:
case EAS_FOLDER_TYPE_DEFAULT_OUTBOX:
case EAS_FOLDER_TYPE_USER_CREATED_MAIL:
return EAS_ITEM_MAIL;
case EAS_FOLDER_TYPE_DEFAULT_TASKS:
case EAS_FOLDER_TYPE_USER_CREATED_TASKS:
return EAS_ITEM_TODO;
case EAS_FOLDER_TYPE_DEFAULT_CALENDAR:
case EAS_FOLDER_TYPE_USER_CREATED_CALENDAR:
return EAS_ITEM_CALENDAR;
case EAS_FOLDER_TYPE_DEFAULT_CONTACTS:
case EAS_FOLDER_TYPE_USER_CREATED_CONTACTS:
return EAS_ITEM_CONTACT;
case EAS_FOLDER_TYPE_DEFAULT_NOTES:
case EAS_FOLDER_TYPE_USER_CREATED_NOTES:
//TODO: implement memos
case EAS_FOLDER_TYPE_DEFAULT_JOURNAL:
case EAS_FOLDER_TYPE_USER_CREATED_JOURNAL:
case EAS_FOLDER_TYPE_UNKNOWN:
case EAS_FOLDER_TYPE_RECIPIENT_CACHE:
default:
return -1;
}
}
bool ActiveSyncSource::Collection::collectionIsDefault () {
return type == EAS_FOLDER_TYPE_DEFAULT_INBOX ||
type == EAS_FOLDER_TYPE_DEFAULT_DRAFTS ||
type == EAS_FOLDER_TYPE_DEFAULT_DELETED_ITEMS ||
type == EAS_FOLDER_TYPE_DEFAULT_SENT_ITEMS ||
type == EAS_FOLDER_TYPE_DEFAULT_OUTBOX ||
type == EAS_FOLDER_TYPE_DEFAULT_TASKS ||
type == EAS_FOLDER_TYPE_DEFAULT_CALENDAR ||
type == EAS_FOLDER_TYPE_DEFAULT_CONTACTS ||
type == EAS_FOLDER_TYPE_DEFAULT_NOTES ||
type == EAS_FOLDER_TYPE_DEFAULT_JOURNAL;
}
ActiveSyncSource::Databases ActiveSyncSource::getDatabases()
{
Databases result;
// do a scan if username is set
UserIdentity identity = m_context->getSyncUser();
if (identity.m_provider != USER_IDENTITY_PLAIN_TEXT) {
throwError(SE_HERE, StringPrintf("%s: only the 'user:<account ID in gconf>' format is supported by ActiveSync", identity.toString().c_str()));
}
const std::string &account = identity.m_identity;
if (!account.empty()) {
findCollections(account, true);
BOOST_FOREACH(Collection coll, m_collections | boost::adaptors::map_values) {
if (coll.getFolderType() == getEasType()) {
result.push_back(Database(coll.pathName, coll.collectionId, coll.collectionIsDefault()));
}
}
} else {
result.push_back(Database("to scan, specify --print-databases username=<account> backend=\""+getSourceType().m_backend+"\"",
""));
}
return result;
}
std::string ActiveSyncSource::lookupFolder(const std::string &folder) {
// If folder matches a collectionId, use that
if (m_collections.find(folder) != m_collections.end()) return folder;
// If folder begins with /, drop it
std::string key;
if (!folder.empty() && folder[0] == '/') {
key = folder.substr(1);
} else {
key = folder;
}
// Lookup folder name
FolderPaths::const_iterator entry = m_folderPaths.find(key);
if (entry != m_folderPaths.end()) {
return entry->second;
}
// Not found
return "";
}
void ActiveSyncSource::open()
{
// extract account ID and throw error if missing
UserIdentity identity = m_context->getSyncUser();
if (identity.m_provider != USER_IDENTITY_PLAIN_TEXT) {
throwError(SE_HERE, StringPrintf("%s: only the 'user:<account ID in gconf>' format is supported by ActiveSync", identity.toString().c_str()));
}
const std::string &username = identity.m_identity;
std::string folder = getDatabaseID();
SE_LOG_DEBUG(NULL,
"using eas sync account %s from config %s with folder %s",
username.c_str(),
m_context->getConfigName().c_str(),
folder.c_str());
if (folder.empty()) { // Most common case is empty string
m_folder = folder;
} else { // Lookup folder name
// Try using cached folder list
findCollections(username, false);
m_folder = lookupFolder(folder);
if (m_folder.empty()) {
// Fetch latest folder list and try again
findCollections(username, true);
m_folder = lookupFolder(folder);
}
if (m_folder.empty()) {
throwError(SE_HERE, "could not find folder: "+folder);
}
}
m_account = username;
// create handler
m_handler.set(eas_sync_handler_new(m_account.c_str()), "EAS handler");
}
void ActiveSyncSource::close()
{
// free handler if not done already
m_handler.set(NULL);
}
void ActiveSyncSource::beginSync(const std::string &lastToken, const std::string &resumeToken)
{
// erase content which might have been set in a previous call
reset();
// claim item node for ids, if not done yet
if (m_itemNode && !m_ids) {
m_ids.swap(m_itemNode);
}
// incremental sync (non-empty token) or start from scratch
m_startSyncKey = lastToken;
if (lastToken.empty()) {
// slow sync: wipe out cached list of IDs, will be filled anew below
SE_LOG_DEBUG(getDisplayName(), "sync key empty, starting slow sync");
m_ids->clear();
} else {
SE_LOG_DEBUG(getDisplayName(), "sync key %s for account '%s' folder '%s', starting incremental sync",
lastToken.c_str(),
m_account.c_str(),
m_folder.c_str());
}
gboolean moreAvailable = TRUE;
m_currentSyncKey = m_startSyncKey;
// same logic as in ActiveSyncCalendarSource::beginSync()
bool slowSync = false;
for (bool firstIteration = true;
moreAvailable;
firstIteration = false) {
gchar *buffer = NULL;
GErrorCXX gerror;
EASItemsCXX created, updated;
EASIdsCXX deleted;
bool wasSlowSync = m_currentSyncKey.empty();
if (!eas_sync_handler_get_items(m_handler,
m_currentSyncKey.c_str(),
&buffer,
getEasType(),
m_folder.c_str(),
created, updated, deleted,
&moreAvailable,
gerror)) {
if (gerror.m_gerror &&
/*
gerror.m_gerror->domain == EAS_TYPE_CONNECTION_ERROR &&
gerror.m_gerror->code == EAS_CONNECTION_SYNC_ERROR_INVALIDSYNCKEY && */
gerror.m_gerror->message &&
strstr(gerror.m_gerror->message, "Sync error: Invalid synchronization key") &&
firstIteration) {
// fall back to slow sync
slowSync = true;
m_currentSyncKey = "";
m_ids->clear();
continue;
}
gerror.throwError(SE_HERE, "reading ActiveSync changes");
}
GStringPtr bufferOwner(buffer, "reading changes: empty sync key returned");
// TODO: Test that we really get an empty token here for an unexpected slow
// sync. If not, we'll start an incremental sync here and later the engine
// will ask us for older, unmodified item content which we won't have.
// populate ID lists and content cache
BOOST_FOREACH(EasItemInfo *item, created) {
if (!item->server_id) {
throwError(SE_HERE, "no server ID for new eas item");
}
string luid(item->server_id);
if (luid.empty()) {
throwError(SE_HERE, "empty server ID for new eas item");
}
SE_LOG_DEBUG(getDisplayName(), "new item %s", luid.c_str());
addItem(luid, NEW);
m_ids->setProperty(luid, "1");
if (!item->data) {
throwError(SE_HERE, StringPrintf("no body returned for new eas item %s", luid.c_str()));
}
m_items[luid] = item->data;
}
BOOST_FOREACH(EasItemInfo *item, updated) {
if (!item->server_id) {
throwError(SE_HERE, "no server ID for updated eas item");
}
string luid(item->server_id);
if (luid.empty()) {
throwError(SE_HERE, "empty server ID for updated eas item");
}
SE_LOG_DEBUG(getDisplayName(), "updated item %s", luid.c_str());
addItem(luid, UPDATED);
// m_ids.setProperty(luid, "1"); not necessary, should already exist (TODO: check?!)
if (!item->data) {
throwError(SE_HERE, StringPrintf("no body returned for updated eas item %s", luid.c_str()));
}
m_items[luid] = item->data;
}
BOOST_FOREACH(const char *serverID, deleted) {
if (!serverID) {
throwError(SE_HERE, "no server ID for deleted eas item");
}
string luid(serverID);
if (luid.empty()) {
throwError(SE_HERE, "empty server ID for deleted eas item");
}
SE_LOG_DEBUG(getDisplayName(), "deleted item %s", luid.c_str());
addItem(luid, DELETED);
m_ids->removeProperty(luid);
}
// update key
m_currentSyncKey = buffer;
// Google hack: if we started with an empty sync key (= slow sync)
// and got no results (= existing items), then try one more time,
// because Google only seems to report results when asked with
// a valid sync key. As an additional sanity check make sure that
// we have a valid sync key now.
if (wasSlowSync &&
created.empty() &&
!m_currentSyncKey.empty()) {
moreAvailable = true;
}
}
// now also generate full list of all current items:
// old items + new (added to m_ids above) - deleted (removed above)
ConfigProps props;
m_ids->readProperties(props);
BOOST_FOREACH(const StringPair &entry, props) {
const std::string &luid = entry.first;
SE_LOG_DEBUG(getDisplayName(), "existing item %s", luid.c_str());
addItem(luid, ANY);
}
if (slowSync) {
// tell engine that we need a slow sync, if it didn't know already
SE_THROW_EXCEPTION_STATUS(StatusException,
"ActiveSync error: Invalid synchronization key",
STATUS_SLOW_SYNC_508);
}
}
std::string ActiveSyncSource::endSync(bool success)
{
// store current set of items
if (!success) {
m_ids->clear();
}
m_ids->flush();
// let engine do incremental sync next time or start from scratch
// in case of failure
std::string newSyncKey = success ? m_currentSyncKey : "";
SE_LOG_DEBUG(getDisplayName(), "next sync key %s", newSyncKey.empty() ? "empty" : newSyncKey.c_str());
return newSyncKey;
}
void ActiveSyncSource::deleteItem(const string &luid)
{
// asking to delete a non-existent item via ActiveSync does not
// trigger an error; this is expected by the caller, so detect
// the problem by looking up the item in our list (and keep the
// list up-to-date elsewhere)
if (m_ids && m_ids->readProperty(luid).empty()) {
throwError(SE_HERE, STATUS_NOT_FOUND, "item not found: " + luid);
}
// send delete request
// TODO (?): batch delete requests
GListCXX<char, GSList> items;
items.push_back((char *)luid.c_str());
GErrorCXX gerror;
char *buffer;
if (!eas_sync_handler_delete_items(m_handler,
m_currentSyncKey.c_str(),
&buffer,
getEasType(),
m_folder.c_str(),
items,
gerror)) {
gerror.throwError(SE_HERE, "deleting eas item");
}
GStringPtr bufferOwner(buffer, "delete items: empty sync key returned");
// remove from item list
if (m_ids) {
m_items.erase(luid);
m_ids->removeProperty(luid);
}
// update key
m_currentSyncKey = buffer;
}
SyncSourceSerialize::InsertItemResult ActiveSyncSource::insertItem(const std::string &luid, const std::string &data)
{
SyncSourceSerialize::InsertItemResult res;
EASItemPtr tmp(eas_item_info_new(), "EasItem");
EasItemInfo *item = tmp.get();
if (!luid.empty()) {
// update
item->server_id = g_strdup(luid.c_str());
} else {
// add
// TODO: is a local id needed? We don't have one.
}
item->data = g_strdup(data.c_str());
EASItemsCXX items;
items.push_front(tmp.release());
GErrorCXX gerror;
char *buffer;
// distinguish between update (existing luid)
// or creation (empty luid)
if (luid.empty()) {
// send item to server
if (!eas_sync_handler_add_items(m_handler,
m_currentSyncKey.c_str(),
&buffer,
getEasType(),
m_folder.c_str(),
items,
gerror)) {
gerror.throwError(SE_HERE, "adding eas item");
}
if (!item->server_id) {
throwError(SE_HERE, "no server ID for new eas item");
}
// get new ID from updated item
res.m_luid = item->server_id;
if (res.m_luid.empty()) {
throwError(SE_HERE, "empty server ID for new eas item");
}
// TODO: if someone else has inserted a new calendar item
// with the same UID as the one we are trying to insert here,
// what will happen? Does the ActiveSync server prevent
// adding our own version of the item or does it merge?
// res.m_merged = ???
} else {
// update item on server
if (!eas_sync_handler_update_items(m_handler,
m_currentSyncKey.c_str(),
&buffer,
getEasType(),
m_folder.c_str(),
items,
gerror)) {
gerror.throwError(SE_HERE, "updating eas item");
}
res.m_luid = luid;
}
GStringPtr bufferOwner(buffer, "insert item: empty sync key returned");
// add/update in cache
if (m_ids) {
m_items[res.m_luid] = data;
m_ids->setProperty(res.m_luid, "1");
}
// update key
m_currentSyncKey = buffer;
return res;
}
void ActiveSyncSource::readItem(const std::string &luid, std::string &item)
{
// return straight from cache?
std::map<std::string, std::string>::iterator it = m_items.find(luid);
if (it == m_items.end()) {
// no, must fetch
EASItemPtr tmp(eas_item_info_new(), "EasItem");
GErrorCXX gerror;
if (!eas_sync_handler_fetch_item(m_handler,
m_folder.c_str(),
luid.c_str(),
tmp,
getEasType(),
gerror)) {
if (gerror.m_gerror->message &&
strstr(gerror.m_gerror->message, "ObjectNotFound")
/* gerror.matches(EAS_CONNECTION_ERROR, EAS_CONNECTION_ITEMOPERATIONS_ERROR_OBJECTNOTFOUND)
(gdb) p *m_gerror
$7 = {domain = 156, code = 36,
message = 0xda2940 "GDBus.Error:org.meego.activesyncd.ItemOperationsError.ObjectNotFound: Document library - The object was not found or access denied."}
*/) {
throwError(SE_HERE, STATUS_NOT_FOUND, "item not found: " + luid);
} else {
gerror.throwError(SE_HERE, StringPrintf("reading eas item %s", luid.c_str()));
}
}
if (!tmp->data) {
throwError(SE_HERE, StringPrintf("no body returned for eas item %s", luid.c_str()));
}
item = tmp->data;
} else {
item = it->second;
}
}
void ActiveSyncSource::getSynthesisInfo(SynthesisInfo &info,
XMLConfigFragments &fragments)
{
TestingSyncSource::getSynthesisInfo(info, fragments);
/**
* Disable reading of existing item by engine before updating
* it by pretending to do the merging ourselves. This works
* as long as the local side is able to store all data that
* activesyncd gives to us and updates on the ActiveSync
* server.
*
* Probably some Exchange-specific extensions currently get
* lost because activesyncd does not know how to represent
* them as vCard and does not tell the ActiveSync server that
* it cannot handle them.
*/
boost::replace_first(info.m_datastoreOptions,
"<updateallfields>true</updateallfields>",
"");
/**
* no ActiveSync specific rules yet, use condensed format as
* if we were storing locally, with all extensions enabled
*/
info.m_backendRule = "LOCALSTORAGE";
/**
* access to data must be done early so that a slow sync can be
* enforced when the ActiveSync sync key turns out to be
* invalid
*/
info.m_earlyStartDataRead = true;
}
SE_END_CXX
#endif /* ENABLE_ACTIVESYNC */
#ifdef ENABLE_MODULES
# include "ActiveSyncSourceRegister.cpp"
#endif