syncevolution/src/syncevo/TrackingSyncSource.h
Patrick Ohly b985da7011 TrackingSyncSource: avoid .ini write when nothing changes
During syncs where nothing changes, reseting the database revision and
setting it again at the end of the sync causes undesirable disk
writes. To avoid that, reset the database revision the first time an
item change is done.
2014-09-10 12:06:53 +02:00

288 lines
11 KiB
C++

/*
* Copyright (C) 2008-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
*/
#ifndef INCL_TRACKINGSYNCSOURCE
#define INCL_TRACKINGSYNCSOURCE
#include <syncevo/SyncSource.h>
#include <syncevo/ConfigNode.h>
#include <boost/shared_ptr.hpp>
#include <string>
#include <map>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
/**
* This class implements change tracking. Data sources which want to use
* this functionality have to provide the following functionality
* by implementing the pure virtual functions below:
* - open() the data
* - enumerate all existing items
* - provide LUID and "revision string":
* The LUID must remain *constant* when the user edits an item (it
* may change when SyncEvolution changes an item), whereas the
* revision string must *change* each time the item is changed
* by anyone.
* Both can be arbitrary strings, but keeping them simple (printable
* ASCII, no white spaces, no equal sign) makes debugging simpler
* because they can be stored as they are as key/value pairs in the
* sync source's change tracking config node (the .other.ini files when
* using file-based configuration). More complex strings use escape
* sequences introduced with an exclamation mark for unsafe characters.
* - import/export/update single items
* - persistently store all changes in flush()
* - clean up in close()
*
* A derived class may (but doesn't have to) override additional
* functions to modify or replace the default implementations, e.g.:
* - dumping the complete database (export())
*
* Potential implementations of the revision string are:
* - a modification time stamp
* - a hash value of a textual representation of the item
* (beware, such a hash might change as the textual representation
* changes even though the item is unchanged)
*/
class TrackingSyncSource : public TestingSyncSource,
virtual public SyncSourceRevisions,
virtual public SyncSourceBlob,
virtual public SyncSourceAdmin
{
public:
/**
* Creates a new tracking sync source.
*
* @param granularity sync sources whose revision string
* is based on time should specify the number
* of seconds which has to pass before changes
* are detected reliably (see SyncSourceRevisions
* for details), otherwise pass 0
*/
TrackingSyncSource(const SyncSourceParams &params,
int granularitySeconds = 1);
~TrackingSyncSource() {}
/**
* ConfigNode used for change tracking in SyncSourceRevisions.
* Derived classes might need that when implementing operations
* which have side effects on other items (for example,
* EvolutionCalendarSource::removeItem()).
*/
ConfigNode &getTrackingNode() { return *m_trackingNode; }
/**
* returns a list of all know sources for the kind of items
* supported by this sync source
*/
virtual Databases getDatabases() = 0;
/**
* Actually opens the data source specified in the constructor,
* will throw the normal exceptions if that fails. Should
* not modify the state of the sync source.
*
* The expectation is that this call is fairly light-weight, but
* does enough checking to determine whether the source is
* usable. More expensive operations (like determining changes)
* should be done in the beginSync() callback.
*
* In clients, it will be called for all sources before
* the sync starts. In servers, it is called for each source once
* the client asks for it, but not sooner.
*/
virtual void open() = 0;
/**
* A quick check whether the source currently has data. Currently
* used as part of the "allow slow sync" checking after open() and
* before beginSync(). Returning false is acceptable when it is
* uncertain and too expensive to check.
*/
virtual bool isEmpty() = 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" (the default implementation)
* - id not empty and id_1 == id_2 implies "nothing has changed";
* the inverse is not true (ids may be different although nothing has changed)
*/
virtual std::string databaseRevision() { return ""; }
/**
* fills the complete mapping from LUID to revision string of all
* currently existing items
*
* Usually both LUID and revision string must be non-empty. The
* only exception is a refresh-from-client: in that case the
* revision string may be empty. The implementor of this call
* cannot know whether empty strings are allowed, therefore it
* should not throw errors when it cannot create a non-empty
* string. The caller of this method will detect situations where
* a non-empty string is necessary and none was provided.
*/
virtual void listAllItems(SyncSourceRevisions::RevisionMap_t &revisions) = 0;
/**
* Called at the start of the sync session to tell
* the derived class about the cached information if (and only
* if) listAllItems() and updateAllItems() were not called. The derived class
* might not need this information, so the default implementation
* simply ignores.
*
* A more complex API could have been defined to only prepare the
* information when needed, but that seemed unnecessarily complex.
*/
virtual void setAllItems(const RevisionMap_t &revisions) {}
/**
* updates the revision map to reflect the current state
*
* May be called instead of listAllItems() if the caller has
* a valid list to start from. If the implementor
* cannot update the list, it must start from scratch by
* reseting the list and calling listAllItems(). The default
* implementation of this method does that.
*/
virtual void updateAllItems(SyncSourceRevisions::RevisionMap_t &revisions) {
revisions.clear();
listAllItems(revisions);
}
/**
* Create or modify an item.
*
* The sync source should be flexible: if the LUID is non-empty, it
* shall modify the item referenced by the LUID. If the LUID is
* empty, the normal operation is to add it. But if the item
* already exists (e.g., a calendar event which was imported
* by the user manually), then the existing item should be
* updated also in the second case.
*
* Passing a LUID of an item which does not exist is an error.
* This error should be reported instead of covering it up by
* (re)creating the item.
*
* Errors are signaled by throwing an exception. Returning empty
* strings in the result is an error which triggers an "item could
* not be stored" error.
*
* @param luid identifies the item to be modified, empty for creating
* @param item contains the new content of the item
* @param raw item has internal format instead of engine format;
* testing and backup/restore might use such an internal format
* which may be different (more complete!) than the
* format when talking to the sync engine
* @return the result of inserting the item
*/
virtual InsertItemResult insertItem(const std::string &luid, const std::string &item, bool raw) = 0;
/**
* Return item data in engine format.
*
* Must throw a STATUS_NOT_FOUND (= 404) StatusException when the
* item does not exist.
*
* @param luid identifies the item
* @param raw return item in internal format instead of engine format
* @retval item item data
*/
virtual void readItem(const std::string &luid, std::string &item, bool raw) = 0;
/**
* delete the item (renamed so that it can be wrapped by deleteItem())
*
* Must throw a STATUS_NOT_FOUND (= 404) StatusException when the
* item does not exist.
*/
virtual void removeItem(const string &luid) = 0;
/**
* optional: write all changes, throw error if that fails
*
* This is called while the sync is still active whereas
* close() is called afterwards. Reporting problems
* as early as possible may be useful at some point,
* but currently doesn't make a relevant difference.
*/
virtual void flush() {}
/**
* closes the data source so that it can be reopened
*
* Just as open() it should not affect the state of
* the database unless some previous action requires
* it.
*/
virtual void close() = 0;
/**
* Returns the preferred mime type of the items handled by the sync source.
* Example: "text/x-vcard"
*/
virtual std::string getMimeType() const = 0;
/**
* Returns the version of the mime type used by client.
* Example: "2.1"
*/
virtual std::string getMimeVersion() const = 0;
using SyncSource::getName;
private:
void checkStatus(SyncSourceReport &changes);
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;
protected:
/* implementations of SyncSource callbacks */
virtual void beginSync(const std::string &lastToken, const std::string &resumeToken);
virtual std::string endSync(bool success);
virtual void deleteItem(const string &luid);
virtual InsertItemResult insertItem(const std::string &luid, const std::string &item);
virtual void readItem(const std::string &luid, std::string &item);
virtual InsertItemResult insertItemRaw(const std::string &luid, const std::string &item);
virtual void readItemRaw(const std::string &luid, std::string &item);
virtual void enableServerMode();
virtual bool serverModeEnabled() const;
virtual std::string getPeerMimeType() const;
private:
InsertItemResult doInsertItem(const std::string &luid, const std::string &item, bool raw);
InsertItemResult continueInsertItem(const boost::function<InsertItemResult ()> &check, const std::string &luid);
void resetDatabaseRevision();
};
SE_END_CXX
#endif // INCL_TRACKINGSYNCSOURCE