/* * Copyright (C) 2007-2009 Patrick Ohly * Copyright (C) 2011 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 */ #ifndef INCL_ACTIVESYNCSOURCE #define INCL_ACTIVESYNCSOURCE #include #include #ifdef ENABLE_ACTIVESYNC #include #include #include #include #include #include #include #include "libeassync.h" #include #include #include SE_BEGIN_CXX /** * Synchronizes contacts, events, tasks and journals with an * ActiveSync server. Sub-classes provide the necessary specialization * for different data formats. * * Data is exchanged between ActiveSyncSource, ActiveSync library, and * Synthesis engine as vCard 3.0 and iCalendar 2.0 format. The * standard contact and calendar profile is used, with ACTIVESYNC set * as sub-rule. This influences how the Synthesis engine converts to * and from its internal format. See KDE in * src/syncevo/profiles/datatypes/01vcard-profile.xml for an extensive * example how that works. * * Each ActiveSync calendar item is a VCALENDAR which contains all * VEVENTs with the same UID. The SyncEvolution/Synthesis engine works * with individual VEVENTs per item. This implies that someone has to * map between the two concepts. This is done in the derived * ActiveSyncCalendarSource, similar to the existing MapSyncSource. * * Reusing that class was considered, but discarded because * MapSyncSource assumes that the class that it wraps uses id/revision * string pairs for change tracking, which is not the case for * ActiveSync. * * A sync session is done like this: * - The Synthesis sync anchor directly maps to the * ActiveSync sync key. * - In SyncSourceSession::beginSync() (inherited from TestingSyncSource), * the ActiveSync library is asked for changes (for a non-empty key) * or all items (empty key). * - The returned item IDs are stored in SyncSourceChanges. * Only the server's IDs are used. They map 1:1 with the "luid" in * the SyncSource API and the Synthesis engine. * Because a full list of all existing items is expected, * ActiveSyncSource maintains a list of all known items * in the params.m_nodes.getTrackingNode() that it gets * for that purpose. * - The returned item content is cached in a local content cache * and returns items from that when asked to via * SyncSourceSerialize. * - As items are added/remove/updated, the content cache, the list * of IDs, and the sync key are updated. The expectation is * that any changes made by other ActiveSync server clients * will be reported when asking for changes based on that updated * key without including changes made by our own client, even * when these changes happen concurrently. This is true for * Exchange 2010 + ActiveSync 12.1. * TODO: write a test program to verify that assumption. * Google + ActiveSync 12.0 do not support this. * TODO: deal with server-side changes reported to us at the * time when we make changes ourselves. * - When multiple events are in one item, it can happen that * the event series has to be retrieved individually from the * server. Example: * - nothing changed on server => nothing sent at start of sync, * cache empty * - ActiveSyncCalendarSource must delete a detached recurrence * inside a series * - retrieve series * - remove recurrence * - sent back the updated series * - At the end of the sync, the updated ID list is stored and * the updated sync key is returned to the Synthesis engine. * - If anything goes wrong, a fatal error is returned to the * Synthesis engine, which then invalidates the sync key and * thus forces a slow sync in the next session. * TODO: investigate more intelligent ways of recovering. * The problem will be that trying again with the original * sync key will return changes made by the client itself * as part of the incomplete sync session. * * The command line item manipulation operations * (--import/export/update/print-items/delete-items) always start a * session without a sync key and thus (with the current API) have to * download all items before doing anything. * TODO: optimize that */ class ActiveSyncSource : public TestingSyncSource, // == SyncSourceSession, SyncSourceChanges, SyncSourceDelete, SyncSourceSerialize // TODO: implement SyncSourceLogging to get nicer debug and command line output // virtual public SyncSourceLogging, virtual public SyncSourceAdmin, virtual public SyncSourceBlob { public: ActiveSyncSource(const SyncSourceParams ¶ms) : TestingSyncSource(params), // Ensure that arbitrary keys can be stored (SafeConfigNode) and // that we use a common prefix, so that we can use the key/value store // also for other keys if the need ever arises). m_itemNode(new PrefixConfigNode("item-", boost::shared_ptr(new SafeConfigNode(params.m_nodes.getTrackingNode())))), m_context(params.m_context) { if (!m_context) { m_context.reset(new SyncConfig()); } } /** sync config used by this instance, never NULL */ SyncConfig &getSyncConfig() { return *m_context; } /* partial implementation of SyncSource */ virtual void enableServerMode(); virtual bool serverModeEnabled() const; virtual Databases getDatabases(); virtual void open(); virtual void close(); virtual std::string getPeerMimeType() const { return getMimeType(); } /* implementation of SyncSourceSession */ virtual void beginSync(const std::string &lastToken, const std::string &resumeToken); virtual std::string endSync(bool success); /* implementation of SyncSourceDelete */ virtual void deleteItem(const string &luid); /* partial implementation of SyncSourceSerialize */ virtual std::string getMimeType() const = 0; virtual std::string getMimeVersion() const = 0; virtual InsertItemResult insertItem(const std::string &luid, const std::string &item); virtual void readItem(const std::string &luid, std::string &item); /** to be provided by derived class */ virtual EasItemType getEasType() const = 0; protected: virtual void getSynthesisInfo(SynthesisInfo &info, XMLConfigFragments &fragments); EasSyncHandler *getHandler() { return m_handler.get(); } std::string getFolder() { return m_folder; } std::string getStartSyncKey() { return m_startSyncKey; } void setStartSyncKey(const std::string &startSyncKey) { m_startSyncKey = startSyncKey; } std::string getCurrentSyncKey() { return m_currentSyncKey; } void setCurrentSyncKey(const std::string ¤tSyncKey) { m_currentSyncKey = currentSyncKey; } void findCollections(const std::string &account, bool force_update); std::string lookupFolder(const std::string &folder); boost::shared_ptr m_itemNode; private: /** "target-config@" instance which holds our username == ActiveSync account ID */ boost::shared_ptr m_context; /** account ID for libeas, must be set in "username" config property */ std::string m_account; /** folder ID for libeas, optionally set in "database" config property */ std::string m_folder; /** smart pointer holding reference to EasSyncHandler during session */ eptr m_handler; /** original sync key, set when session starts */ std::string m_startSyncKey; /** current sync key, set when session starts and updated as changes are made */ std::string m_currentSyncKey; /** * server-side IDs of all items, updated as changes are reported and/or are made; * NULL if not using change tracking */ boost::shared_ptr m_ids; /** * cache of all items, filled at begin of session and updated as * changes are made (if doing change tracking) */ std::map m_items; /** * list of folders */ typedef struct Collection { std::string collectionId; std::string name; std::string parentId; std::string pathName; unsigned type; bool pathFound; ActiveSyncSource *source; Collection() : type(0), pathFound(false), source(NULL) {} int getFolderType(); bool collectionIsDefault(); std::string fullPath(); } collection; std::map m_collections; // Indexed by collectionID typedef std::map FolderPaths; FolderPaths m_folderPaths; // Maps pathName to collectionId }; class ActiveSyncContactSource : public ActiveSyncSource { public: ActiveSyncContactSource(const SyncSourceParams ¶ms) : ActiveSyncSource(params) {} protected: /* partial implementation of SyncSourceSerialize */ virtual std::string getMimeType() const { return "text/vcard"; } virtual std::string getMimeVersion() const { return "3.0"; } EasItemType getEasType() const { return EAS_ITEM_CONTACT; } }; /** * used for all iCalendar 2.0 items (events, todos, journals) */ class ActiveSyncCalFormatSource : public ActiveSyncSource { EasItemType m_type; public: ActiveSyncCalFormatSource(const SyncSourceParams ¶ms, EasItemType type) : ActiveSyncSource(params), m_type(type) {} protected: /* partial implementation of SyncSourceSerialize */ virtual std::string getMimeType() const { return "text/calendar"; } virtual std::string getMimeVersion() const { return "2.0"; } EasItemType getEasType() const { return m_type; } }; void EASItemUnref(EasItemInfo *info); /** non-copyable list of EasItemInfo pointers, owned by list */ typedef GListCXX EASItemsCXX; void GStringUnref(char *str); /** non-copyable list of strings, owned by list */ typedef GListCXX EASIdsCXX; /** non-copyable smart pointer to an EasItemInfo, unrefs when going out of scope */ typedef eptr EASItemPtr; void EASFolderUnref(EasFolder *f); /** non-copyable list of EasFolder pointers, owned by list */ typedef GListCXX EASFoldersCXX; SE_END_CXX #endif // ENABLE_ACTIVESYNC #endif // INCL_ACTIVESYNCSOURCE