syncevolution/src/syncevo/MapSyncSource.h

322 lines
13 KiB
C++

/*
* Copyright (C) 2010 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_MAPSYNCSOURCE
#define INCL_MAPSYNCSOURCE
#include <syncevo/TrackingSyncSource.h>
#include <syncevo/util.h>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
class MapSyncSource;
/**
* rev + uid + list of subid; mainid is part of the context
*/
struct SubRevisionEntry {
std::string m_revision;
std::string m_uid;
set<string> m_subids;
};
/**
* mainid to rev + uid + list of subid
*
* List must contain an empty entry for the main item, if and only
* if one exists.
*/
typedef map<string, SubRevisionEntry> SubRevisionMap_t;
/**
* This is the API that must be implemented in addition to
* TrackingSyncSource and SyncSourceLogging by a source to
* be wrapped by MapSyncSource.
*
* The original interface will only be used in "raw" mode, which
* should bypass any kind of cache used by the implementation.
* They are guaranteed to be passed merged items.
*
* The new methods with mainid and subid are using during a sync
* and should use the cache. They work on single items but modify
* merged items. Thus the revision string of all sub items in
* the same merged item will get modified when manipulating
* one of its sub items.
*/
class SubSyncSource : virtual public SyncSourceBase
{
public:
class SubItemResult {
public:
SubItemResult() :
m_state(ITEM_OKAY)
{}
/**
* @param mainid the ID used to access a set of items; may be different
* from a (iCalendar 2.0) UID; during an update the mainid must
* not be changed, so return the original one here
* @param subid optional subid, same rules as for mainid
* @param revision the revision string of the merged item after the operation; leave empty if not used
* @param uid an arbitrary string, stored, but not used by MapSyncSource;
* used in the CalDAV backend to associate mainid (= resource path)
* with UID (= part of the item content, but with special semantic)
* @param state report about what was done with the data
*/
SubItemResult(const string &mainid,
const string &subid,
const string &revision,
const string &uid,
InsertItemResultState state) :
m_mainid(mainid),
m_subid(subid),
m_revision(revision),
m_uid(uid),
m_state(state)
{}
string m_mainid;
string m_subid;
string m_revision;
string m_uid;
InsertItemResultState m_state;
};
SubSyncSource() : m_parent(NULL) {}
/**
* tells SubSyncSource about MapSyncSource which wraps it,
* for getSynthesisAPI()
*/
void setParent(MapSyncSource *parent) { m_parent = parent; }
MapSyncSource *getParent() const { return m_parent; }
virtual SDKInterface *getSynthesisAPI() const;
/** called after open() and before any of the following methods */
virtual void begin() = 0;
/** called after a sync */
virtual void endSubSync(bool success) = 0;
/**
* A unique identifier for the current state of the complete database.
* The semantic is the following:
* - empty string implies "state unknown" or "identifier not supported"
* - id not empty and ID1 == ID2 implies "nothing has changed";
* the inverse is not true (ids may be different although nothing has changed)
*
* Matches TrackingSyncSource::databaseRevision().
*/
virtual std::string subDatabaseRevision() { return ""; }
/**
* Either listAllSubItems(), setAllSubItems(), or updateAllSubitems()
* will be called after begin().
*
* In the first case, the sub source is expected to provide a full list
* of its items. In the second case, the caller was able to determine
* that its cached copy of that list is still correct and provides it
* the the source. In the third case, some revision information is know,
* but it may be obsolete (revision string and/or subids changed or removed)
* or incomplete (new items missing). The callee then must update the
* information, possibly by falling back to listAllSubItems().
*/
virtual void listAllSubItems(SubRevisionMap_t &revisions) = 0;
/**
* Called instead of listAllSubItems().
*/
virtual void updateAllSubItems(SubRevisionMap_t &revisions) {
revisions.clear();
listAllSubItems(revisions);
}
/**
* Called instead of listAllSubItems().
*/
virtual void setAllSubItems(const SubRevisionMap_t &revisions) = 0;
virtual SubItemResult insertSubItem(const std::string &mainid, const std::string &subid,
const std::string &item) = 0;
virtual void readSubItem(const std::string &mainid, const std::string &subid, std::string &item) = 0;
/**
* Ensure that the sub-item does not exist. It is not an error to be called
* for a non-existent sub-item or item.
*
* @return empty string if item is empty after removal, otherwise new revision string
*/
virtual std::string removeSubItem(const string &mainid, const std::string &subid) = 0;
/**
* Remove all sub-items belonging to mainid.
*/
virtual void removeMergedItem(const std::string &mainid) = 0;
/**
* Called whenever this class thinks that the item may no longer be
* needed. Might be wrong...
*/
virtual void flushItem(const string &mainid) = 0;
/**
* Describe sub-item. Might be called for item which does not exist and
* must not throw an error in that case. Providing a description is optional
* and should only be done when it is reasonably cheap.
*/
virtual std::string getSubDescription(const string &mainid, const string &subid) = 0;
/**
* Called after MapSyncSource already populated the info structure.
*/
virtual void updateSynthesisInfo(SynthesisInfo &info,
XMLConfigFragments &fragments) {}
private:
MapSyncSource *m_parent;
};
/**
* This class wraps an underlying SyncSource and maps each of the
* underlying items into one or more items in this class. The main use case
* are CalDAV and Exchange Web Services, where each item is a set
* of all events with the same UID, whereas SyncEvolution and SyncML
* treat each individual event as one item.
*
* Terminology:
* - single item = item as presented by this class (VEVENT)
* - merged item = combination of all items sharing the same luid/uid (VCALENDAR)
* - luid = SyncEvolution locally unique ID (VEVENT), mapped to mainid+subid
* - mainid = ID for accessing the set of items (WebDAV resource path)
* - subid = unique ID (RECURRENCE-ID) for sub-items (VEVENT) inside underlying item (VCALENDAR)
* - uid = another unique ID shared by underlying items (iCalendar 2.0 UID),
* not used by this class
*
* "luid" is composed from "mainid" and "subid" by backslash-escaping a
* slash (/), then concatenating the results with a slash as
* separator. If the subid is empty, no slash is added. The main
* advantage of this scheme is that it works well with sub sources
* which use URI escaping.
*
* This class expects an extended TrackingSyncSource (=
* SubSyncSource) merely because that interface is the most convenient
* to work with. If necessary, the logic may be refactored later on.
*
* This class uses much of the same infrastructure as the TrackingSyncSource,
* except for change detection. This used to be done with TrackingSyncSource
* as base class, but there are some key differences which made this very
* awkward, primarily the fact that all merged items share the same revision
* string. Now the tracking node is used to store one entry per merged item, in
* the format "ref-<mainid> = <revision>/<uid>/<subid1>/<subid2>/..."
*
* The following rules apply:
* - A single item is added if its luid is new, updated if it exists and
* the merged item's revision string is different, deleted if the luid is
* gone (same logic as in normal TrackingSyncSource).
* - A mainid is assigned to a new merged item by creating the merged item.
* - Changes for an existing merged item may be applied to a cache,
* which is explicitly flushed by this class. This implies that
* such local changes must keep the mainid stable and have control
* over the subid.
* - Item logging is offered by this class (LoggingSyncSource), but
* entirely depends on the sub source to implement the functionality.
*/
class MapSyncSource :
public TestingSyncSource, // == SyncSourceSession, SyncSourceChanges, SyncSourceDelete, SyncSourceSerialize
virtual public SyncSourceAdmin,
virtual public SyncSourceBlob,
virtual public SyncSourceLogging
{
public:
/**
* @param sub must also implement TrackingSyncSource and SyncSourceLogging interfaces!
*/
MapSyncSource(const SyncSourceParams &params,
const boost::shared_ptr<SubSyncSource> &sub);
~MapSyncSource() {}
/** compose luid from mainid and subid */
static std::string createLUID(const std::string &mainid, const std::string &subid);
/** split luid into mainid (first) and subid (second) */
static StringPair splitLUID(const std::string &luid);
virtual void enableServerMode();
virtual bool serverModeEnabled() const;
virtual std::string getPeerMimeType() const { return getMimeType(); }
virtual Databases getDatabases() { return dynamic_cast<SyncSource &>(*m_sub).getDatabases(); }
virtual void open() { dynamic_cast<SyncSource &>(*m_sub).open(); }
virtual void beginSync(const std::string &lastToken, const std::string &resumeToken);
virtual std::string endSync(bool success);
virtual bool isEmpty() { return dynamic_cast<SyncSource &>(*m_sub).getOperations().m_isEmpty(); }
virtual InsertItemResult insertItem(const std::string &luid, const std::string &item);
virtual void readItem(const std::string &luid, std::string &item);
virtual void deleteItem(const string &luid);
virtual void close() { dynamic_cast<SyncSource &>(*m_sub).close(); }
virtual std::string getMimeType() const { return dynamic_cast<SyncSourceSerialize &>(*m_sub).getMimeType(); }
virtual std::string getMimeVersion() const { return dynamic_cast<SyncSourceSerialize &>(*m_sub).getMimeVersion(); }
virtual std::string getDescription(sysync::KeyH aItemKey) { return dynamic_cast<SyncSourceLogging &>(*m_sub).getDescription(aItemKey); }
virtual std::string getDescription(const string &luid);
/* TestingSyncSource */
virtual void removeAllItems();
protected:
virtual void getSynthesisInfo(SynthesisInfo &info,
XMLConfigFragments &fragments) {
TestingSyncSource::getSynthesisInfo(info, fragments);
m_sub->updateSynthesisInfo(info, fragments);
}
private:
boost::shared_ptr<SubSyncSource> m_sub;
/** escape / in uid with %2F, so that splitMainIDValue() and splitLUID() can use / as separator */
static StringEscape m_escape;
std::string m_oldLUID;
/**
* information about the current set of items:
* initialized as part of beginSync(),
* updated as items are modified,
* stored in endSync()
*/
SubRevisionMap_t m_revisions;
/** on-disk representation of m_revisions */
boost::shared_ptr<ConfigNode> m_trackingNode;
/**
* Stores meta information besides the item list:
* - "databaseRevision" = result of databaseRevision() at end of last sync
*
* Shares the same key/value store as m_trackingNode,
* which uses the "item-" prefix in its keys to
* avoid name clashes.
*/
boost::shared_ptr<ConfigNode> m_metaNode;
/** mirrors SyncSourceRevisions::detectChanges() */
void detectChanges(SyncSourceRevisions::ChangeMode mode);
};
SE_END_CXX
#endif // INCL_MAPSYNCSOURCE