3092 lines
118 KiB
C++
3092 lines
118 KiB
C++
/*
|
|
* Copyright (C) 2005-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_SYNCSOURCE
|
|
#define INCL_SYNCSOURCE
|
|
|
|
#include <syncevo/SyncConfig.h>
|
|
#include <syncevo/SyncML.h>
|
|
#include <syncevo/Timespec.h>
|
|
|
|
#include <synthesis/sync_declarations.h>
|
|
#include <synthesis/syerror.h>
|
|
#include <synthesis/blobs.h>
|
|
|
|
#include <boost/function.hpp>
|
|
#include <boost/signals2.hpp>
|
|
#include <boost/variant.hpp>
|
|
|
|
#include <syncevo/declarations.h>
|
|
SE_BEGIN_CXX
|
|
|
|
class SyncSource;
|
|
struct SDKInterface;
|
|
|
|
/**
|
|
* This set of parameters always has to be passed when constructing
|
|
* SyncSource instances.
|
|
*/
|
|
struct SyncSourceParams {
|
|
/**
|
|
* @param name the name needed by SyncSource
|
|
* @param nodes a set of config nodes to be used by this source
|
|
* @param context Additional non-source config settings.
|
|
* When running as part of a normal sync, these are the
|
|
* settings for the peer. When running in a local sync,
|
|
* these settings come from the "target-config" peer
|
|
* config inside the config context of the source.
|
|
* Testing uses "target-config@client-test". On the
|
|
* command line, this is the config chosen by the
|
|
* user, which may or may not have peer-specific settings!
|
|
* @param contextName optional name of context in which the source is defined,
|
|
* needed to disambiguates "name" when sources from
|
|
* different contexts are active in a sync
|
|
*/
|
|
SyncSourceParams(const string &name,
|
|
const SyncSourceNodes &nodes,
|
|
const boost::shared_ptr<SyncConfig> &context,
|
|
const string &contextName = "") :
|
|
m_name(name),
|
|
m_nodes(nodes),
|
|
m_context(context),
|
|
m_contextName(contextName)
|
|
{}
|
|
|
|
std::string getDisplayName() const { return m_contextName.empty() ? m_name : m_contextName + "/" + m_name; }
|
|
|
|
string m_name;
|
|
SyncSourceNodes m_nodes;
|
|
boost::shared_ptr<SyncConfig> m_context;
|
|
string m_contextName;
|
|
};
|
|
|
|
/**
|
|
* The SyncEvolution core has no knowledge of existing SyncSource
|
|
* implementations. Implementations have to register themselves
|
|
* by instantiating this class exactly once with information
|
|
* about themselves.
|
|
*
|
|
* It is also possible to add configuration options. For that define a
|
|
* derived class. In its constructor use
|
|
* SyncSourceConfig::getRegistry() resp. SyncConfig::getRegistry() to
|
|
* define new configuration properties. The advantage of registering
|
|
* them is that the user interface will automatically handle them like
|
|
* the predefined ones. The namespace of these configuration options
|
|
* is shared by all sources and the core.
|
|
*
|
|
* For properties with arbitrary names use the
|
|
* SyncSourceNodes::m_trackingNode.
|
|
*/
|
|
class RegisterSyncSource
|
|
{
|
|
public:
|
|
/**
|
|
* Users select a SyncSource and its data format via the "type"
|
|
* config property. Backends have to add this kind of function to
|
|
* the SourceRegistry_t in order to be considered by the
|
|
* SyncSource creation mechanism.
|
|
*
|
|
* The function will be called to check whether the backend was
|
|
* meant by the user. It should return a new instance which will
|
|
* be freed by the caller or NULL if it does not support the
|
|
* selected type.
|
|
*
|
|
* Inactive sources should return the special InactiveSource
|
|
* instance (created with InactiveSource() below) if they
|
|
* recognize without a doubt that the user wanted to instantiate
|
|
* them: for example, an inactive EvolutionContactSource will
|
|
* return NULL for "addressbook" but InactiveSource for
|
|
* "evolution-contacts".
|
|
*/
|
|
typedef SyncSource *(*Create_t)(const SyncSourceParams ¶ms);
|
|
|
|
/** create special result of Create_t: a source which just throws errors when used */
|
|
static SyncSource *InactiveSource(const SyncSourceParams ¶ms);
|
|
|
|
/**
|
|
* @param shortDescr a few words identifying the data to be synchronized,
|
|
* e.g. "Evolution Calendar"
|
|
* @param enabled true if the sync source can be instantiated,
|
|
* false if it was not enabled during compilation or is
|
|
* otherwise not functional
|
|
* @param create factory function for sync sources of this type
|
|
* @param typeDescr multiple lines separated by \n which get appended to
|
|
* the the description of the type property, e.g.
|
|
* "Evolution Memos = memo = evolution-memo\n"
|
|
* " plain text in UTF-8 (default) = text/plain\n"
|
|
* " iCalendar 2.0 = text/calendar\n"
|
|
* " The later format is not tested because none of the\n"
|
|
* " supported SyncML servers accepts it.\n"
|
|
* @param typeValues the config accepts multiple names for the same internal
|
|
* type string; this list here is added to that list of
|
|
* aliases. It should contain at least one unique string
|
|
* the can be used to pick this sync source among all
|
|
* SyncEvolution sync sources (testing, listing backends, ...).
|
|
* Example: Values() + (Aliases("Evolution Memos") + "evolution-memo")
|
|
*/
|
|
RegisterSyncSource(const string &shortDescr,
|
|
bool enabled,
|
|
Create_t create,
|
|
const string &typeDescr,
|
|
const Values &typeValues);
|
|
public:
|
|
const string m_shortDescr;
|
|
const bool m_enabled;
|
|
const Create_t m_create;
|
|
const string m_typeDescr;
|
|
const Values m_typeValues;
|
|
};
|
|
|
|
typedef list<const RegisterSyncSource *> SourceRegistry;
|
|
class ClientTest;
|
|
class TestingSyncSource;
|
|
|
|
/**
|
|
* Information about a data source. For the sake of simplicity all
|
|
* items pointed to are owned by the ClientTest and must
|
|
* remain valid throughout a test session. Not setting a pointer
|
|
* is okay, but it will disable all tests that need the
|
|
* information.
|
|
*/
|
|
struct ClientTestConfig {
|
|
/**
|
|
* The name is used in test names and has to be set.
|
|
*/
|
|
std::string m_sourceName;
|
|
|
|
/**
|
|
* A default URI to be used when creating a client config.
|
|
*/
|
|
std::string m_uri;
|
|
|
|
/**
|
|
* A corresponding source name in the default server template,
|
|
* this is used to copy corresponding uri set in the server template
|
|
* instead of the uri field above (which is the same for all servers).
|
|
*/
|
|
std::string m_sourceNameServerTemplate;
|
|
|
|
/**
|
|
* A member function of a subclass which is called to create a
|
|
* sync source referencing the data. This is used in tests of
|
|
* the SyncSource API itself as well as in tests which need to
|
|
* modify or check the data sources used during synchronization.
|
|
*
|
|
* The test framework will call beginSync() and then some of
|
|
* the functions it wants to test. After a successful test it
|
|
* will call endSync() which is then expected to store all
|
|
* changes persistently. Creating a sync source again
|
|
* with the same call should not report any
|
|
* new/updated/deleted items until such changes are made via
|
|
* another sync source.
|
|
*
|
|
* The instance will be deleted by the caller. Because this
|
|
* may be in the error case or in an exception handler,
|
|
* the sync source's desctructor should not thow exceptions.
|
|
*
|
|
* @param client the same instance to which this config belongs
|
|
* @param clientID the unique ID of the client, "1" resp. "2" in practice (can also be obtained as
|
|
* client->getClientID(), but not all implementers have (or want) access to the
|
|
* class definition)
|
|
* @param source index of the data source (from 0 to ClientTest::getNumSources() - 1)
|
|
* @param isSourceA true if the requested SyncSource is the first one accessing that
|
|
* data, otherwise the second
|
|
*/
|
|
typedef boost::function<TestingSyncSource *(ClientTest &, const std::string &, int, bool)> createsource_t;
|
|
|
|
/**
|
|
* Creates a sync source which references the primary database;
|
|
* it may report the same changes as the sync source used during
|
|
* sync tests.
|
|
*/
|
|
createsource_t m_createSourceA;
|
|
|
|
/**
|
|
* A second sync source also referencing the primary data
|
|
* source, but configured so that it tracks changes
|
|
* independently from the the primary sync source.
|
|
*
|
|
* In local tests the usage is like this:
|
|
* - add item via first SyncSource
|
|
* - iterate over new items in second SyncSource
|
|
* - check that it lists the added item
|
|
*
|
|
* In tests with a server the usage is:
|
|
* - do a synchronization with the server
|
|
* - iterate over items in second SyncSource
|
|
* - check that the total number and number of
|
|
* added/updated/deleted items is as expected
|
|
*/
|
|
createsource_t m_createSourceB;
|
|
|
|
/**
|
|
* The framework can generate vCard and vCalendar/iCalendar items
|
|
* automatically by copying a template item and modifying certain
|
|
* properties.
|
|
*
|
|
* This is the template for these automatically generated items.
|
|
* It must contain the string <<REVISION>> which will be replaced
|
|
* with the revision parameter of the createItem() method.
|
|
*/
|
|
std::string m_templateItem;
|
|
|
|
/**
|
|
* This is a colon (:) separated list of properties which need
|
|
* to be modified in templateItem.
|
|
*/
|
|
std::string m_uniqueProperties;
|
|
|
|
/**
|
|
* This is a single property in templateItem which can be extended
|
|
* to increase the size of generated items.
|
|
*/
|
|
std::string m_sizeProperty;
|
|
|
|
/**
|
|
* Type to be set when importing any of the items into the
|
|
* corresponding sync sources. Use "" if sync source doesn't
|
|
* need this information.
|
|
*
|
|
* Not currently used! All items are assumed to be in the raw,
|
|
* internal format (see SyncSourceRaw and SyncSourceSerialize).
|
|
*/
|
|
std::string m_itemType;
|
|
|
|
/**
|
|
* callback which is invoked with a specific item as paramter
|
|
* to do data type specific conversions before actually
|
|
* using the test item; default is a NOP function
|
|
*
|
|
* @param update modify item content so that it can be
|
|
* used as an update of the old data
|
|
* @param uniqueUIDSuffix beyond not reusing UIDs between tests, also embed this suffix
|
|
* in test items inside the running test to avoid reuse
|
|
* if necessary (Google CalDAV + UID/SEQUENCE => 409 error)
|
|
*/
|
|
boost::function<std::string (const std::string &data, bool update, const std::string &uniqueUIDSuffix)> m_mangleItem;
|
|
|
|
/**
|
|
* Items have a UID which really has to be unique among all items
|
|
* in the database. True for iCalendar 2.0, false for vCard 3.0
|
|
* (there is a UID, but its uniqueness is not enforced).
|
|
*/
|
|
bool m_uniqueID;
|
|
|
|
/**
|
|
* A very simple item that is inserted during basic tests. Ideally
|
|
* it only contains properties supported by all servers.
|
|
*/
|
|
std::string m_insertItem;
|
|
|
|
/**
|
|
* A slightly modified version of insertItem. If the source has UIDs
|
|
* embedded into the item data, then both must have the same UID.
|
|
* Again all servers should better support these modified properties.
|
|
*/
|
|
std::string m_updateItem;
|
|
|
|
/**
|
|
* A more heavily modified version of insertItem. Same UID if necessary,
|
|
* but can test changes to items only supported by more advanced
|
|
* servers.
|
|
*/
|
|
std::string m_complexUpdateItem;
|
|
|
|
/**
|
|
* To test merge conflicts two different updates of insertItem are
|
|
* needed. This is the first such update.
|
|
*/
|
|
std::string m_mergeItem1;
|
|
|
|
/**
|
|
* The second merge update item. To avoid true conflicts it should
|
|
* update different properties than mergeItem1, but even then servers
|
|
* usually have problems perfectly merging items. Therefore the
|
|
* test is run without expecting a certain merge result.
|
|
*/
|
|
std::string m_mergeItem2;
|
|
|
|
/**
|
|
* The items in the inner vector are related: the first one the is
|
|
* main one, the other(s) is/are a subordinate ones. The semantic
|
|
* is that the main item is complete on it its own, while the
|
|
* other normally should only be used in combination with the main
|
|
* one.
|
|
*
|
|
* Because SyncML cannot express such dependencies between items,
|
|
* a SyncSource has to be able to insert, updated and remove
|
|
* both items independently. However, operations which violate
|
|
* the semantic of the related items (like deleting the parent, but
|
|
* not the child) may have unspecified results (like also deleting
|
|
* the child). See linkedItemsRelaxedSemantic and sourceKnowsItemSemantic.
|
|
*
|
|
* One example for main and subordinate items are a recurring
|
|
* iCalendar 2.0 event and a detached recurrence.
|
|
*/
|
|
typedef class LinkedItems : public std::vector<std::string> {
|
|
public:
|
|
std::string m_name; /**< used as Client::Source::LinkedItems<m_name> */
|
|
StringMap m_options; /**< used to pass additional parameters to the test */
|
|
/** for testLinkedItemsSubset: create the additional VEVENT that is added when talking to Exchange;
|
|
parameters are start, skip, index and total number of items in that test */
|
|
boost::function<std::string (int, int, int, int)> m_testLinkedItemsSubsetAdditional;
|
|
} LinkedItems_t;
|
|
|
|
/**
|
|
* The linked items may exist in different variations (outer vector).
|
|
*/
|
|
typedef std::vector<LinkedItems_t> MultipleLinkedItems_t;
|
|
|
|
MultipleLinkedItems_t m_linkedItems;
|
|
|
|
/**
|
|
* Another set of linked items for the LinkedItems*::testItemsAll/Second/Third/... tests.
|
|
*/
|
|
MultipleLinkedItems_t m_linkedItemsSubset;
|
|
|
|
/**
|
|
* Backends atomic modification tests
|
|
*/
|
|
Bool m_atomicModification;
|
|
|
|
/**
|
|
* set to false to disable tests which slightly violate the
|
|
* semantic of linked items by inserting children
|
|
* before/without their parent
|
|
*/
|
|
Bool m_linkedItemsRelaxedSemantic;
|
|
|
|
/**
|
|
* setting this to false disables tests which depend
|
|
* on the source's support for linked item semantic
|
|
* (testLinkedItemsInsertParentTwice, testLinkedItemsInsertChildTwice)
|
|
*
|
|
* Matches SynthesisInfo::m_globalIDs.
|
|
*/
|
|
Bool m_sourceKnowsItemSemantic;
|
|
|
|
/**
|
|
* Set this to true if the backend does not have IDs which are the
|
|
* same for all clients and across slow syncs. For example, when
|
|
* testing the ActiveSync backend this field needs to be true,
|
|
* because items are renumbered as 1:x with x = 1, 2, ... for each
|
|
* clients when a sync anchor is assigned to it.
|
|
*/
|
|
Bool m_sourceLUIDsAreVolatile;
|
|
|
|
/**
|
|
* Set this to true if the backend supports
|
|
* X-SYNCEVOLUTION-EXDATE-DETACHED, see CalDAVSource.cpp
|
|
* CalDAVSource::readSubItem().
|
|
*/
|
|
Bool m_supportsReccurenceEXDates;
|
|
|
|
/**
|
|
* called to dump all items into a file, required by tests which need
|
|
* to compare items
|
|
*
|
|
* ClientTest::dump can be used: it will simply dump all items of the source
|
|
* with a blank line as separator.
|
|
*
|
|
* @param source sync source A already created and with beginSync() called
|
|
* @param file a file name
|
|
* @return error code, 0 for success
|
|
*/
|
|
boost::function<int (ClientTest &, TestingSyncSource &, const std::string &)> m_dump;
|
|
|
|
/**
|
|
* import test items: which these are is determined entirely by
|
|
* the implementor, but tests work best if several complex items are
|
|
* imported
|
|
*
|
|
* ClientTest::import can be used if the file contains items separated by
|
|
* empty lines.
|
|
*
|
|
* @param source sync source A already created and with beginSync() called
|
|
* @param file the name of the file to import
|
|
* @retval realfile the name of the file that was really imported;
|
|
* this may depend on the current server that is being tested
|
|
* @param luids optional; if empty, then fill with luids (empty string for failed items);
|
|
* if not empty, then update instead of adding the items
|
|
* @return error string, empty for success
|
|
*/
|
|
boost::function<std::string (ClientTest &, TestingSyncSource &, const ClientTestConfig &,
|
|
const std::string &, std::string &, std::list<std::string> *)> m_import;
|
|
|
|
/**
|
|
* a function which compares two files with items in the format used by "dump"
|
|
*
|
|
* @param fileA first file name
|
|
* @param fileB second file name
|
|
* @return true if the content of the files is considered equal
|
|
*/
|
|
boost::function<bool (ClientTest &, const std::string &, const std::string &)> m_compare;
|
|
|
|
/**
|
|
* A file with test cases in the format expected by import and compare.
|
|
* The file should contain data as supported by the local storage.
|
|
*
|
|
* It is used in "Source::*::testImport" test, which verifies that
|
|
* the backend can import and export that data.
|
|
*
|
|
* It is also used in "Sync::*::testItems", which verifies that
|
|
* the peer can store and export it. Often local extensions are
|
|
* not supported by peers. This can be handled in different ways:
|
|
* - Patch synccompare to ignore such changes on a per-peer basis.
|
|
* - Create a <testcases>.<peer>.tem file in the src/testcases
|
|
* build directory where <testcases> is the string here ("eds_event.ics"),
|
|
* and <peer> the value of CLIENT_TEST_SERVER ("funambol").
|
|
* That file then will be used in testItems instead of the base
|
|
* version. See the src/Makefile.am for rules that maintain such files.
|
|
*/
|
|
std::string m_testcases;
|
|
|
|
/**
|
|
* the item type normally used by the source (not used by the tests
|
|
* themselves; client-test.cpp uses it to initialize source configs)
|
|
*/
|
|
std::string m_type;
|
|
|
|
/**
|
|
* a list of sub configs separated via , if this is a super datastore
|
|
*/
|
|
std::string m_subConfigs;
|
|
|
|
/**
|
|
* TRUE if the source supports recovery from an interrupted
|
|
* synchronization. Enables the Client::Sync::*::Retry group
|
|
* of tests.
|
|
*/
|
|
Bool m_retrySync;
|
|
Bool m_suspendSync;
|
|
Bool m_resendSync;
|
|
|
|
/**
|
|
* Set this to a list of properties which must *not* be removed
|
|
* from the test items. Leave empty to disable the testRemoveProperties
|
|
* test. Test items must be in vCard 3.0/iCalendar 2.0 format.
|
|
*/
|
|
std::set<std::string> m_essentialProperties;
|
|
|
|
/**
|
|
* Set this to test if the source supports preserving local data extensions.
|
|
* Uses the "testcases" data. See Sync::*::testExtensions.
|
|
*
|
|
* The function must modify a single item such that re-importing
|
|
* it locally will be seen as updating it. It is empty by default
|
|
* because not all backends necessarily pass this test.
|
|
*
|
|
* genericUpdate works for vCard and iCalendar by updating FN, N, resp. SUMMARY
|
|
* and can be used as implementation of update.
|
|
*/
|
|
boost::function<void (std::string &)> m_update;
|
|
boost::function<void (std::string &)> m_genericUpdate;
|
|
|
|
/**
|
|
* A list of m_sourceName values of other ClientTestConfigs
|
|
* which share the same database. Normally, sources are tested in
|
|
* isolation, but for such linked sources we also need to test
|
|
* interdependencies, in particular regarding change tracking and
|
|
* item listing.
|
|
*/
|
|
std::list<std::string> m_linkedSources;
|
|
};
|
|
|
|
/**
|
|
* In addition to registering the sync source itself by creating an
|
|
* instance of RegisterSyncSource, configurations for testing it can
|
|
* also be registered. A sync source which supports more than one data
|
|
* exchange format can register one configuration for each format, but
|
|
* not registering any configuration is also okay.
|
|
*
|
|
* *Using* the registered tests depends on the CPPUnit test framework.
|
|
* *Registering* does not. Therefore backends should always register *
|
|
* *themselves for testing and leave it to the test runner
|
|
* "client-test" whether tests are really executed.
|
|
*
|
|
* Unit tests are different. They create hard dependencies on CPPUnit
|
|
* inside the code that contains them, and thus should be encapsulated
|
|
* inside #ifdef ENABLE_UNIT_TESTS checks.
|
|
*
|
|
* Sync sources have to work stand-alone without a full SyncClient
|
|
* configuration for all local tests. The minimal configuration prepared
|
|
* for the source includes:
|
|
* - a tracking node (as used f.i. by TrackingSyncSource) which
|
|
* points towards "~/.config/syncevolution/client-test-changes"
|
|
* - a unique change ID (as used f.i. by EvolutionContactSource)
|
|
* - a valid "evolutionsource" property in the config node, starting
|
|
* with the CLIENT_TEST_EVOLUTION_PREFIX env variable or (if that
|
|
* wasn't set) the "SyncEvolution_Test_" prefix
|
|
* - "evolutionuser/password" if CLIENT_TEST_EVOLUTION_USER/PASSWORD
|
|
* are set
|
|
*
|
|
* No other properties are set, which implies that currently sync sources
|
|
* which require further parameters cannot be tested.
|
|
*
|
|
* @warning There is a potential problem with the registration
|
|
* mechanism. Both the sync source tests as well as the CPPUnit tests
|
|
* derived from them are registrered when global class instances are
|
|
* initialized. If the RegisterTestEvolution instance in
|
|
* client-test-app.cpp is initialized *before* the sync source tests,
|
|
* then those won't show up in the test list. Currently the right
|
|
* order seems to be used, so everything works as expected.
|
|
*/
|
|
class RegisterSyncSourceTest
|
|
{
|
|
public:
|
|
/**
|
|
* Invoked after all global constructors are run.
|
|
* May add further RegisterSyncSourceTests to the
|
|
* global registry.
|
|
*/
|
|
virtual void init() const {}
|
|
|
|
/**
|
|
* This call is invoked after setting up the config with default
|
|
* values for the test cases selected via the constructor's
|
|
* testCaseName parameter (one of eds_contact, eds_contact, eds_event, eds_task;
|
|
* see ClientTest in the Funambol client library for the current
|
|
* list).
|
|
*
|
|
* This call can then override any of the values or (if there
|
|
* are no predefined test cases) add them.
|
|
*
|
|
* The "type" property must select your sync source and the
|
|
* data format for the test.
|
|
*
|
|
* @retval config change any field whose default is not suitable
|
|
*/
|
|
virtual void updateConfig(ClientTestConfig &config) const = 0;
|
|
|
|
/**
|
|
* @param configName a unique string: the predefined names known by
|
|
* ClientTest::getTestData() are already used for the initial
|
|
* set of Evolution sync sources, for new sync sources
|
|
* build a string by combining them with the sync source name
|
|
* (e.g., "sqlite_eds_contact")
|
|
* @param testCaseName a string recognized by ClientTest::getTestData() or an
|
|
* empty string if there are no predefined test cases
|
|
*/
|
|
RegisterSyncSourceTest(const string &configName,
|
|
const string &testCaseName);
|
|
virtual ~RegisterSyncSourceTest() {}
|
|
|
|
const string m_configName;
|
|
const string m_testCaseName;
|
|
|
|
/**
|
|
* A list of m_configName values of other RegisterSyncSourceTest
|
|
* which share the same database. Normally, sources are tested in
|
|
* isolation, but for such linked sources we also need to test
|
|
* interdependencies, in particular regarding change tracking and
|
|
* item listing.
|
|
*/
|
|
std::list<std::string> m_linkedSources;
|
|
};
|
|
|
|
class TestRegistry : public vector<const RegisterSyncSourceTest *>
|
|
{
|
|
public:
|
|
// TODO: using const RegisterSyncSourceTest * operator [] (int);
|
|
const RegisterSyncSourceTest * operator [] (const string &configName) const {
|
|
BOOST_FOREACH(const RegisterSyncSourceTest *test, *this) {
|
|
if (test->m_configName == configName) {
|
|
return test;
|
|
}
|
|
}
|
|
throw out_of_range(string("test config registry: ") + configName);
|
|
return NULL;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* a container for Synthesis XML config fragments
|
|
*
|
|
* Backends can define their own field lists, profiles, datatypes and
|
|
* remote rules. The name of each of these entities have to be unique:
|
|
* either prefix each name with the name of the backend or coordinate
|
|
* with other developers (e.g. regarding shared field lists).
|
|
*
|
|
* To add new items, add them to the respective hash in your backend's
|
|
* getDatastoreXML() or getSynthesisInfo() implementation. Both
|
|
* methods have default implementations: getSynthesisInfo() is called
|
|
* by the default getDatastoreXML() to provide some details and
|
|
* provides them based on the "type" configuration option.
|
|
*
|
|
* The default config XML contains several predefined items:
|
|
* - field lists: contacts, calendar, Note, bookmarks
|
|
* - profiles: vCard, vCalendar, Note, vBookmark
|
|
* - datatypes: vCard21, vCard30, vCalendar10, iCalendar20,
|
|
* note10/11 (no difference except the versioning!),
|
|
* vBookmark10
|
|
* - remote rule: EVOLUTION
|
|
*
|
|
* These items do not appear in the hashes, so avoid picking the same
|
|
* names. The entries of each hash has to be a well-formed XML
|
|
* element, their keys the name encoded in each XML element.
|
|
*/
|
|
struct XMLConfigFragments {
|
|
class mapping : public std::map<std::string, std::string> {
|
|
public:
|
|
string join() {
|
|
string res;
|
|
size_t len = 0;
|
|
BOOST_FOREACH(const value_type &entry, *this) {
|
|
len += entry.second.size() + 1;
|
|
}
|
|
res.reserve(len);
|
|
BOOST_FOREACH(const value_type &entry, *this) {
|
|
res += entry.second;
|
|
res += "\n";
|
|
}
|
|
return res;
|
|
}
|
|
} m_fieldlists,
|
|
m_profiles,
|
|
m_datatypes,
|
|
m_remoterules;
|
|
};
|
|
|
|
/**
|
|
* used in SyncSource::Operations post-operation signal
|
|
*/
|
|
enum OperationExecution {
|
|
OPERATION_SKIPPED, /**< operation was skipped because pre-operation slot threw an exception */
|
|
OPERATION_EXCEPTION, /**< operation itself failed with an exception (may also return error code) */
|
|
OPERATION_FINISHED, /**< operation finished normally (but might have returned an error code) */
|
|
OPERATION_EMPTY /**< operation not implemented */
|
|
};
|
|
|
|
/**
|
|
* Helper class for looking up a pending operation by a Synthesis parameter.
|
|
* KeyH (add, replace) and item ID (delete) are supported.
|
|
*/
|
|
template<class A1> struct KeyConverter;
|
|
|
|
/**
|
|
* For KeyH we make the assumption that the key exists as long
|
|
* as the pending operation, and thus its address can be used as
|
|
* unique identifier for the operation.
|
|
*/
|
|
template<> struct KeyConverter<sysync::KeyH>
|
|
{
|
|
typedef void * key_type;
|
|
static key_type toKey(sysync::KeyH key) { return static_cast<void *>(key); }
|
|
};
|
|
|
|
/**
|
|
* For cItemID we just use the item ID as string.
|
|
*/
|
|
template<> struct KeyConverter<sysync::cItemID>
|
|
{
|
|
typedef std::string key_type;
|
|
static key_type toKey(sysync::cItemID id) { return id->item; }
|
|
};
|
|
|
|
/**
|
|
* To be returned by a function wrapped by OperationWrapper
|
|
* when the function is not done yet and wants to be called again
|
|
* for the same item.
|
|
*/
|
|
template <class F> class ContinueOperation : public boost::function<F>
|
|
{
|
|
public:
|
|
ContinueOperation()
|
|
{}
|
|
|
|
ContinueOperation(const boost::function<F> &callback) :
|
|
boost::function<F>(callback)
|
|
{}
|
|
};
|
|
|
|
/**
|
|
* Interface expected by SyncSourceBase helper class.
|
|
* Needed to break a cyclic dependency.
|
|
*/
|
|
class SyncSourceName
|
|
{
|
|
public:
|
|
/**
|
|
* the name of the sync source (for example, "addressbook"),
|
|
* unique in the context of its own configuration
|
|
**/
|
|
virtual std::string getName() const { return "uninitialized SyncSourceBase"; }
|
|
|
|
/**
|
|
* the name of the sync source as it should be displayed to users
|
|
* in debug messages; typically the same as getName(), but may
|
|
* also include a context ("@foobar/addressbook") to disambiguate
|
|
* the name when "addressbook" is used multiple times in a sync (as
|
|
* with local sync)
|
|
*/
|
|
virtual std::string getDisplayName() const { return "uninitialized SyncSourceBase"; }
|
|
};
|
|
|
|
/**
|
|
* Implements the "call all slots, error if any failed" semantic of
|
|
* the pre- and post-signals described below.
|
|
*/
|
|
class OperationSlotInvoker {
|
|
SyncSourceName &m_source;
|
|
|
|
public:
|
|
OperationSlotInvoker(SyncSourceName &source) :
|
|
m_source(source)
|
|
{}
|
|
|
|
typedef sysync::TSyError result_type;
|
|
template<typename InputIterator>
|
|
result_type operator() (InputIterator first, InputIterator last) const
|
|
{
|
|
result_type res = sysync::LOCERR_OK;
|
|
while (first != last) {
|
|
SyncMLStatus status;
|
|
try {
|
|
status = *first;
|
|
} catch (...) {
|
|
status = Exception::handle(m_source.getDisplayName());
|
|
}
|
|
if (res == sysync::LOCERR_OK) {
|
|
res = static_cast<result_type>(status);
|
|
}
|
|
++first;
|
|
}
|
|
return res;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Helper class, needs to be specialized based on number of parameters
|
|
* and return type.
|
|
*/
|
|
template<class F, int arity, class R> class OperationWrapperSwitch;
|
|
|
|
/** one parameter, sysync::TSyError type */
|
|
template<class F> class OperationWrapperSwitch<F, 0, sysync::TSyError>
|
|
{
|
|
public:
|
|
typedef sysync::TSyError result_type;
|
|
typedef boost::function<F> OperationType;
|
|
|
|
/**
|
|
* The pre-signal is invoked with the same parameters as
|
|
* the operations, plus a reference to the sync source as
|
|
* initial parameter. Slots may return something other than
|
|
* STATUS_OK or throw exceptions, which
|
|
* will skip the actual implementation. However, all slots
|
|
* will be invoked exactly once even if one of them throws
|
|
* an exception. The result of the operation then is the
|
|
* error code extracted from the first exception (see
|
|
* OperationSlotInvoker).
|
|
*/
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &),
|
|
OperationSlotInvoker> PreSignal;
|
|
|
|
/**
|
|
* The post-signal is invoked exactly once, regardless
|
|
* whether the implementation was skipped, executed or
|
|
* doesn't exist at all. This information is passed as the
|
|
* second parameter, followed by the result of the
|
|
* operation or the pre-signals, followed by the
|
|
* parameters of the operation.
|
|
*
|
|
* As with the pre-signal, any slot may return something other
|
|
* than STATUS_OK or throw an exception
|
|
* to override the final result, but this won't interrupt
|
|
* calling the rest of the slots.
|
|
*/
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, OperationExecution, sysync::TSyError),
|
|
OperationSlotInvoker> PostSignal;
|
|
|
|
/**
|
|
* invokes signals and implementation of operation,
|
|
* combines all return codes into one
|
|
*/
|
|
sysync::TSyError operator () () const throw ()
|
|
{
|
|
sysync::TSyError res;
|
|
OperationExecution exec;
|
|
res = m_pre(dynamic_cast<SyncSource &>(m_source));
|
|
if (res != sysync::LOCERR_OK) {
|
|
exec = OPERATION_SKIPPED;
|
|
} else {
|
|
if (m_operation) {
|
|
try {
|
|
res = m_operation();
|
|
exec = OPERATION_FINISHED;
|
|
} catch (...) {
|
|
res = Exception::handle(m_source.getDisplayName());
|
|
exec = OPERATION_EXCEPTION;
|
|
}
|
|
} else {
|
|
res = sysync::LOCERR_NOTIMP;
|
|
exec = OPERATION_EMPTY;
|
|
}
|
|
}
|
|
sysync::TSyError newres = m_post(dynamic_cast<SyncSource &>(m_source), exec, res);
|
|
if (newres != sysync::LOCERR_OK) {
|
|
res = newres;
|
|
}
|
|
return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res;
|
|
}
|
|
|
|
/**
|
|
* Anyone may connect code to the signals via
|
|
* getOperations().getPre/PostSignal(), although strictly
|
|
* speaking this modifies the behavior of the
|
|
* implementation.
|
|
*/
|
|
PreSignal &getPreSignal() const { return const_cast<PreSignal &>(m_pre); }
|
|
PostSignal &getPostSignal() const { return const_cast<PostSignal &>(m_post); }
|
|
|
|
OperationWrapperSwitch(SyncSourceName &source) :
|
|
m_source(source),
|
|
m_pre(OperationSlotInvoker(source)),
|
|
m_post(OperationSlotInvoker(source))
|
|
{
|
|
}
|
|
|
|
protected:
|
|
OperationType m_operation;
|
|
|
|
private:
|
|
SyncSourceName &m_source;
|
|
PreSignal m_pre;
|
|
PostSignal m_post;
|
|
};
|
|
|
|
template<class F> class OperationWrapperSwitch<F, 1, sysync::TSyError>
|
|
{
|
|
public:
|
|
typedef sysync::TSyError result_type;
|
|
typedef boost::function<F> OperationType;
|
|
typedef typename boost::function<F>::arg1_type arg1_type;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, arg1_type a1),
|
|
OperationSlotInvoker> PreSignal;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, OperationExecution, sysync::TSyError,
|
|
arg1_type a1),
|
|
OperationSlotInvoker> PostSignal;
|
|
|
|
sysync::TSyError operator () (arg1_type a1) const throw ()
|
|
{
|
|
sysync::TSyError res;
|
|
OperationExecution exec;
|
|
res = m_pre(dynamic_cast<SyncSource &>(m_source), a1);
|
|
if (res != sysync::LOCERR_OK) {
|
|
exec = OPERATION_SKIPPED;
|
|
} else {
|
|
if (m_operation) {
|
|
try {
|
|
res = m_operation(a1);
|
|
exec = OPERATION_FINISHED;
|
|
} catch (...) {
|
|
res = Exception::handle(m_source.getDisplayName());
|
|
exec = OPERATION_EXCEPTION;
|
|
}
|
|
} else {
|
|
res = sysync::LOCERR_NOTIMP;
|
|
exec = OPERATION_EMPTY;
|
|
}
|
|
}
|
|
sysync::TSyError newres = m_post(dynamic_cast<SyncSource &>(m_source), exec, res, a1);
|
|
if (newres != sysync::LOCERR_OK) {
|
|
res = newres;
|
|
}
|
|
return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res;
|
|
}
|
|
|
|
PreSignal &getPreSignal() const { return const_cast<PreSignal &>(m_pre); }
|
|
PostSignal &getPostSignal() const { return const_cast<PostSignal &>(m_post); }
|
|
|
|
OperationWrapperSwitch(SyncSourceName &source) :
|
|
m_source(source),
|
|
m_pre(OperationSlotInvoker(source)),
|
|
m_post(OperationSlotInvoker(source))
|
|
{
|
|
}
|
|
|
|
protected:
|
|
OperationType m_operation;
|
|
|
|
private:
|
|
SyncSourceName &m_source;
|
|
PreSignal m_pre;
|
|
PostSignal m_post;
|
|
};
|
|
|
|
template<class F, class V> class OperationWrapperSwitch<F, 1, V>
|
|
{
|
|
public:
|
|
typedef sysync::TSyError result_type;
|
|
typedef boost::function<F> OperationType;
|
|
typedef typename boost::function<F>::arg1_type arg1_type;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, arg1_type a1),
|
|
OperationSlotInvoker> PreSignal;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, OperationExecution, sysync::TSyError,
|
|
arg1_type a1),
|
|
OperationSlotInvoker> PostSignal;
|
|
typedef KeyConverter<arg1_type> Converter;
|
|
typedef ContinueOperation<sysync::TSyError (arg1_type)> Continue;
|
|
typedef std::map<typename Converter::key_type, Continue> Pending;
|
|
|
|
sysync::TSyError operator () (arg1_type a1) const throw ()
|
|
{
|
|
sysync::TSyError res;
|
|
OperationExecution exec;
|
|
|
|
// Marking m_pending "volatile" didn't work, find() not defined for that.
|
|
typename Pending::iterator it = const_cast<Pending &>(m_pending).find(Converter::toKey(a1));
|
|
bool continuing = it != m_pending.end();
|
|
|
|
res = continuing ? sysync::LOCERR_OK : m_pre(dynamic_cast<SyncSource &>(m_source), a1);
|
|
if (res != sysync::LOCERR_OK) {
|
|
exec = OPERATION_SKIPPED;
|
|
} else {
|
|
if (continuing) {
|
|
res = it->second(a1);
|
|
if (res != sysync::LOCERR_AGAIN) {
|
|
const_cast<Pending &>(m_pending).erase(it);
|
|
}
|
|
} else if (m_operation) {
|
|
try {
|
|
V newres = m_operation(a1);
|
|
sysync::TSyError *completed = boost::get<sysync::TSyError>(&newres);
|
|
if (completed) {
|
|
res = *completed;
|
|
} else {
|
|
res = sysync::LOCERR_AGAIN;
|
|
Continue cont = boost::get<Continue>(newres);
|
|
const_cast<Pending &>(m_pending).insert(std::make_pair(Converter::toKey(a1), cont));
|
|
}
|
|
exec = OPERATION_FINISHED;
|
|
} catch (...) {
|
|
res = Exception::handle(m_source.getDisplayName());
|
|
exec = OPERATION_EXCEPTION;
|
|
}
|
|
} else {
|
|
res = sysync::LOCERR_NOTIMP;
|
|
exec = OPERATION_EMPTY;
|
|
}
|
|
}
|
|
if (res != sysync::LOCERR_AGAIN) {
|
|
sysync::TSyError newres = m_post(dynamic_cast<SyncSource &>(m_source), exec, res, a1);
|
|
if (newres != sysync::LOCERR_OK) {
|
|
res = newres;
|
|
}
|
|
}
|
|
return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res;
|
|
}
|
|
|
|
PreSignal &getPreSignal() const { return const_cast<PreSignal &>(m_pre); }
|
|
PostSignal &getPostSignal() const { return const_cast<PostSignal &>(m_post); }
|
|
|
|
OperationWrapperSwitch(SyncSourceName &source) :
|
|
m_source(source),
|
|
m_pre(OperationSlotInvoker(source)),
|
|
m_post(OperationSlotInvoker(source))
|
|
{
|
|
}
|
|
|
|
protected:
|
|
OperationType m_operation;
|
|
|
|
private:
|
|
SyncSourceName &m_source;
|
|
PreSignal m_pre;
|
|
PostSignal m_post;
|
|
Pending m_pending;
|
|
};
|
|
|
|
template<class F> class OperationWrapperSwitch<F, 2, sysync::TSyError>
|
|
{
|
|
public:
|
|
typedef sysync::TSyError result_type;
|
|
typedef boost::function<F> OperationType;
|
|
typedef typename boost::function<F>::arg1_type arg1_type;
|
|
typedef typename boost::function<F>::arg2_type arg2_type;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, arg1_type a1, arg2_type a2),
|
|
OperationSlotInvoker> PreSignal;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, OperationExecution, sysync::TSyError,
|
|
arg1_type a1, arg2_type a2),
|
|
OperationSlotInvoker> PostSignal;
|
|
|
|
sysync::TSyError operator () (arg1_type a1, arg2_type a2) const throw ()
|
|
{
|
|
sysync::TSyError res;
|
|
OperationExecution exec;
|
|
res = m_pre(dynamic_cast<SyncSource &>(m_source), a1, a2);
|
|
if (res != sysync::LOCERR_OK) {
|
|
exec = OPERATION_SKIPPED;
|
|
} else {
|
|
if (m_operation) {
|
|
try {
|
|
res = m_operation(a1, a2);
|
|
exec = OPERATION_FINISHED;
|
|
} catch (...) {
|
|
res = Exception::handle(m_source.getDisplayName());
|
|
exec = OPERATION_EXCEPTION;
|
|
}
|
|
} else {
|
|
res = sysync::LOCERR_NOTIMP;
|
|
exec = OPERATION_EMPTY;
|
|
}
|
|
}
|
|
sysync::TSyError newres = m_post(dynamic_cast<SyncSource &>(m_source), exec, res, a1, a2);
|
|
if (newres != sysync::LOCERR_OK) {
|
|
res = newres;
|
|
}
|
|
return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res;
|
|
}
|
|
|
|
PreSignal &getPreSignal() const { return const_cast<PreSignal &>(m_pre); }
|
|
PostSignal &getPostSignal() const { return const_cast<PostSignal &>(m_post); }
|
|
|
|
OperationWrapperSwitch(SyncSourceName &source) :
|
|
m_source(source),
|
|
m_pre(OperationSlotInvoker(source)),
|
|
m_post(OperationSlotInvoker(source))
|
|
{
|
|
}
|
|
|
|
protected:
|
|
OperationType m_operation;
|
|
|
|
private:
|
|
SyncSourceName &m_source;
|
|
PreSignal m_pre;
|
|
PostSignal m_post;
|
|
};
|
|
|
|
template<class F, class V> class OperationWrapperSwitch<F, 2, V>
|
|
{
|
|
public:
|
|
typedef sysync::TSyError result_type;
|
|
typedef boost::function<F> OperationType;
|
|
typedef typename boost::function<F>::arg1_type arg1_type;
|
|
typedef typename boost::function<F>::arg2_type arg2_type;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, arg1_type a1, arg2_type a2),
|
|
OperationSlotInvoker> PreSignal;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, OperationExecution, sysync::TSyError,
|
|
arg1_type a1, arg2_type a2),
|
|
OperationSlotInvoker> PostSignal;
|
|
typedef KeyConverter<arg1_type> Converter;
|
|
typedef ContinueOperation<sysync::TSyError (arg1_type, arg2_type)> Continue;
|
|
typedef std::map<typename Converter::key_type, Continue> Pending;
|
|
|
|
sysync::TSyError operator () (arg1_type a1, arg2_type a2) const throw ()
|
|
{
|
|
sysync::TSyError res;
|
|
OperationExecution exec;
|
|
|
|
// Marking m_pending "volatile" didn't work, find() not defined for that.
|
|
typename Pending::iterator it = const_cast<Pending &>(m_pending).find(Converter::toKey(a1));
|
|
bool continuing = it != m_pending.end();
|
|
|
|
res = continuing ? sysync::LOCERR_OK : m_pre(dynamic_cast<SyncSource &>(m_source), a1, a2);
|
|
if (res != sysync::LOCERR_OK) {
|
|
exec = OPERATION_SKIPPED;
|
|
} else {
|
|
if (continuing) {
|
|
res = it->second(a1, a2);
|
|
if (res != sysync::LOCERR_AGAIN) {
|
|
const_cast<Pending &>(m_pending).erase(it);
|
|
}
|
|
} else if (m_operation) {
|
|
try {
|
|
V newres = m_operation(a1, a2);
|
|
sysync::TSyError *completed = boost::get<sysync::TSyError>(&newres);
|
|
if (completed) {
|
|
res = *completed;
|
|
} else {
|
|
res = sysync::LOCERR_AGAIN;
|
|
Continue cont = boost::get<Continue>(newres);
|
|
const_cast<Pending &>(m_pending).insert(std::make_pair(Converter::toKey(a1), cont));
|
|
}
|
|
exec = OPERATION_FINISHED;
|
|
} catch (...) {
|
|
res = Exception::handle(m_source.getDisplayName());
|
|
exec = OPERATION_EXCEPTION;
|
|
}
|
|
} else {
|
|
res = sysync::LOCERR_NOTIMP;
|
|
exec = OPERATION_EMPTY;
|
|
}
|
|
}
|
|
if (res != sysync::LOCERR_AGAIN) {
|
|
sysync::TSyError newres = m_post(dynamic_cast<SyncSource &>(m_source), exec, res, a1, a2);
|
|
if (newres != sysync::LOCERR_OK) {
|
|
res = newres;
|
|
}
|
|
}
|
|
return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res;
|
|
}
|
|
|
|
PreSignal &getPreSignal() const { return const_cast<PreSignal &>(m_pre); }
|
|
PostSignal &getPostSignal() const { return const_cast<PostSignal &>(m_post); }
|
|
|
|
OperationWrapperSwitch(SyncSourceName &source) :
|
|
m_source(source),
|
|
m_pre(OperationSlotInvoker(source)),
|
|
m_post(OperationSlotInvoker(source))
|
|
{
|
|
}
|
|
|
|
protected:
|
|
OperationType m_operation;
|
|
|
|
private:
|
|
SyncSourceName &m_source;
|
|
PreSignal m_pre;
|
|
PostSignal m_post;
|
|
Pending m_pending;
|
|
};
|
|
|
|
template<class F> class OperationWrapperSwitch<F, 3, sysync::TSyError>
|
|
{
|
|
public:
|
|
typedef sysync::TSyError result_type;
|
|
typedef boost::function<F> OperationType;
|
|
typedef typename boost::function<F>::arg1_type arg1_type;
|
|
typedef typename boost::function<F>::arg2_type arg2_type;
|
|
typedef typename boost::function<F>::arg3_type arg3_type;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, arg1_type a1, arg2_type a2, arg3_type a3),
|
|
OperationSlotInvoker> PreSignal;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, OperationExecution, sysync::TSyError,
|
|
arg1_type a1, arg2_type a2, arg3_type a3),
|
|
OperationSlotInvoker> PostSignal;
|
|
|
|
sysync::TSyError operator () (arg1_type a1, arg2_type a2, arg3_type a3) const throw ()
|
|
{
|
|
sysync::TSyError res;
|
|
OperationExecution exec;
|
|
res = m_pre(dynamic_cast<SyncSource &>(m_source), a1, a2, a3);
|
|
if (res != sysync::LOCERR_OK) {
|
|
exec = OPERATION_SKIPPED;
|
|
} else {
|
|
if (m_operation) {
|
|
try {
|
|
res = m_operation(a1, a2, a3);
|
|
exec = OPERATION_FINISHED;
|
|
} catch (...) {
|
|
res = Exception::handle(m_source.getDisplayName());
|
|
exec = OPERATION_EXCEPTION;
|
|
}
|
|
} else {
|
|
res = sysync::LOCERR_NOTIMP;
|
|
exec = OPERATION_EMPTY;
|
|
}
|
|
}
|
|
sysync::TSyError newres = m_post(dynamic_cast<SyncSource &>(m_source), exec, res, a1, a2, a3);
|
|
if (newres != sysync::LOCERR_OK) {
|
|
res = newres;
|
|
}
|
|
return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res;
|
|
}
|
|
|
|
PreSignal &getPreSignal() const { return const_cast<PreSignal &>(m_pre); }
|
|
PostSignal &getPostSignal() const { return const_cast<PostSignal &>(m_post); }
|
|
|
|
OperationWrapperSwitch(SyncSourceName &source) :
|
|
m_source(source),
|
|
m_pre(OperationSlotInvoker(source)),
|
|
m_post(OperationSlotInvoker(source))
|
|
{
|
|
}
|
|
|
|
protected:
|
|
OperationType m_operation;
|
|
|
|
private:
|
|
SyncSourceName &m_source;
|
|
PreSignal m_pre;
|
|
PostSignal m_post;
|
|
|
|
};
|
|
|
|
template<class F, class V> class OperationWrapperSwitch<F, 3, V>
|
|
{
|
|
public:
|
|
typedef sysync::TSyError result_type;
|
|
typedef boost::function<F> OperationType;
|
|
typedef typename boost::function<F>::arg1_type arg1_type;
|
|
typedef typename boost::function<F>::arg2_type arg2_type;
|
|
typedef typename boost::function<F>::arg3_type arg3_type;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, arg1_type a1, arg2_type a2, arg3_type a3),
|
|
OperationSlotInvoker> PreSignal;
|
|
typedef boost::signals2::signal<SyncMLStatus (SyncSource &, OperationExecution, sysync::TSyError,
|
|
arg1_type a1, arg2_type a2, arg3_type a3),
|
|
OperationSlotInvoker> PostSignal;
|
|
typedef KeyConverter<arg1_type> Converter;
|
|
typedef ContinueOperation<sysync::TSyError (arg1_type, arg2_type, arg3_type)> Continue;
|
|
typedef std::map<typename Converter::key_type, Continue> Pending;
|
|
|
|
sysync::TSyError operator () (arg1_type a1, arg2_type a2, arg3_type a3) const throw ()
|
|
{
|
|
sysync::TSyError res;
|
|
OperationExecution exec;
|
|
|
|
// Marking m_pending "volatile" didn't work, find() not defined for that.
|
|
typename Pending::iterator it = const_cast<Pending &>(m_pending).find(Converter::toKey(a1));
|
|
bool continuing = it != m_pending.end();
|
|
|
|
res = continuing ? sysync::LOCERR_OK : m_pre(dynamic_cast<SyncSource &>(m_source), a1, a2, a3);
|
|
if (res != sysync::LOCERR_OK) {
|
|
exec = OPERATION_SKIPPED;
|
|
} else {
|
|
if (continuing) {
|
|
res = it->second(a1, a2, a3);
|
|
if (res != sysync::LOCERR_AGAIN) {
|
|
const_cast<Pending &>(m_pending).erase(it);
|
|
}
|
|
} else if (m_operation) {
|
|
try {
|
|
V newres = m_operation(a1, a2, a3);
|
|
sysync::TSyError *completed = boost::get<sysync::TSyError>(&newres);
|
|
if (completed) {
|
|
res = *completed;
|
|
} else {
|
|
res = sysync::LOCERR_AGAIN;
|
|
Continue cont = boost::get<Continue>(newres);
|
|
const_cast<Pending &>(m_pending).insert(std::make_pair(Converter::toKey(a1), cont));
|
|
}
|
|
exec = OPERATION_FINISHED;
|
|
} catch (...) {
|
|
res = Exception::handle(m_source.getDisplayName());
|
|
exec = OPERATION_EXCEPTION;
|
|
}
|
|
} else {
|
|
res = sysync::LOCERR_NOTIMP;
|
|
exec = OPERATION_EMPTY;
|
|
}
|
|
}
|
|
if (res != sysync::LOCERR_AGAIN) {
|
|
sysync::TSyError newres = m_post(dynamic_cast<SyncSource &>(m_source), exec, res, a1, a2, a3);
|
|
if (newres != sysync::LOCERR_OK) {
|
|
res = newres;
|
|
}
|
|
}
|
|
return res == STATUS_FATAL ? STATUS_DATASTORE_FAILURE : res;
|
|
}
|
|
|
|
PreSignal &getPreSignal() const { return const_cast<PreSignal &>(m_pre); }
|
|
PostSignal &getPostSignal() const { return const_cast<PostSignal &>(m_post); }
|
|
|
|
OperationWrapperSwitch(SyncSourceName &source) :
|
|
m_source(source),
|
|
m_pre(OperationSlotInvoker(source)),
|
|
m_post(OperationSlotInvoker(source))
|
|
{
|
|
}
|
|
|
|
protected:
|
|
OperationType m_operation;
|
|
|
|
private:
|
|
SyncSourceName &m_source;
|
|
PreSignal m_pre;
|
|
PostSignal m_post;
|
|
Pending m_pending;
|
|
};
|
|
|
|
/**
|
|
* This mimics a boost::function with the same signature. The function
|
|
* signature F must have a sysync::TSyError error return code, as in most
|
|
* of the Synthesis DB API, or a boost::variant of sysync::TSyError and
|
|
* ContinueOperation<F>.
|
|
*
|
|
* Specializations of this class for operations with different number
|
|
* of parameters provide a call operator which invokes a pre- and
|
|
* post-signal around the actual implementation. See
|
|
* OperationWrapperSwitch<F, 0> for details.
|
|
*
|
|
* If the function returns a variant with a ContinueOperation<F> inside,
|
|
* the OperationWrapper will store that callback for the current
|
|
* set of parameters (currently using only the first one as key),
|
|
* then when called again, skip the pre-signal and invoke the callback
|
|
* instead of the original operation. That callback may return LOCERR_AGAIN
|
|
* to request being called again the same way. The post-signal is
|
|
* called when the operation finally completes.
|
|
*
|
|
* Additional operations could be wrapped by providing more
|
|
* specializations (different return code, more parameters). The
|
|
* number or parameters in the operation cannot exceed six, because
|
|
* adding three more parameters in the post-signal would push the
|
|
* total number of parameters in that signal beyond the limit of nine
|
|
* supported arguments in boost::signals2/boost::function.
|
|
*/
|
|
template<class F> class OperationWrapper :
|
|
public OperationWrapperSwitch<F, boost::function<F>::arity, typename boost::function<F>::result_type>
|
|
{
|
|
typedef OperationWrapperSwitch<F, boost::function<F>::arity, typename boost::function<F>::result_type> inherited;
|
|
public:
|
|
OperationWrapper(SyncSourceName &source): inherited(source) {}
|
|
|
|
/** operation implemented? */
|
|
operator bool () const { return inherited::m_operation; }
|
|
|
|
/**
|
|
* Only usable by derived classes via read/write m_operations:
|
|
* sets the actual implementation of the operation.
|
|
*/
|
|
void operator = (const boost::function<F> &operation)
|
|
{
|
|
inherited::m_operation = operation;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* abstract base class for SyncSource with some common functionality
|
|
* and no data
|
|
*
|
|
* Used to implement and call that functionality in multiple derived
|
|
* classes, including situations where a derived class is derived from
|
|
* this base via different intermediate classes, therefore the
|
|
* need to keep it abstract.
|
|
*/
|
|
class SyncSourceBase : public SyncSourceName {
|
|
public:
|
|
virtual ~SyncSourceBase() {}
|
|
|
|
/**
|
|
* Convenience function, to be called inside a catch() block of
|
|
* (or for) the sync source.
|
|
*
|
|
* Rethrows the exception to determine what it is, then logs it
|
|
* as an error and returns a suitable error code (usually a general
|
|
* STATUS_DATASTORE_FAILURE).
|
|
*
|
|
* @param flags influence behavior of the method
|
|
*/
|
|
SyncMLStatus handleException(HandleExceptionFlags flags = HANDLE_EXCEPTION_FLAGS_NONE);
|
|
|
|
/**
|
|
* throw an exception after an operation failed
|
|
*
|
|
* output format: <source name>: <action>: <error string>
|
|
*
|
|
* @param action a string describing the operation or object involved
|
|
* @param error the errno error code for the failure
|
|
*/
|
|
void throwError(const SourceLocation &where, const string &action, int error) SE_NORETURN;
|
|
|
|
/**
|
|
* throw an exception after an operation failed and
|
|
* remember that this instance has failed
|
|
*
|
|
* output format: <source name>: <failure>
|
|
*
|
|
* @param action a string describing what was attempted *and* how it failed
|
|
*/
|
|
void throwError(const SourceLocation &where, const string &failure) SE_NORETURN;
|
|
|
|
/**
|
|
* throw an exception with a specific status code after an operation failed and
|
|
* remember that this instance has failed
|
|
*
|
|
* output format: <source name>: <failure>
|
|
*
|
|
* @param status a more specific status code; other throwError() variants use STATUS_FATAL
|
|
* @param action a string describing what was attempted *and* how it failed
|
|
*/
|
|
void throwError(const SourceLocation &where, SyncMLStatus status, const string &failure) SE_NORETURN;
|
|
|
|
/**
|
|
* The Synthesis engine only counts items which are deleted by the
|
|
* peer. Items deleted locally at the start of a
|
|
* refresh-from-server sync are not counted (and cannot be counted
|
|
* in all cases).
|
|
*
|
|
* Sync sources which want to have those items included in the
|
|
* sync statistics should count *all* deleted items using these
|
|
* methods. SyncContext will use this number for
|
|
* refresh-from-server syncs.
|
|
*/
|
|
/**@{*/
|
|
virtual long getNumDeleted() const = 0;
|
|
virtual void setNumDeleted(long num) = 0;
|
|
virtual void incrementNumDeleted() = 0;
|
|
/**@}*/
|
|
|
|
/**
|
|
* Return Synthesis <datastore> XML fragment for this sync source.
|
|
* Must *not* include the <datastore> element; it is created by
|
|
* the caller.
|
|
*
|
|
* The default implementation returns a configuration for the
|
|
* SynthesisDBPlugin, which invokes SyncSource::Operations. Items
|
|
* are exchanged with the SyncsSource in the format defined by
|
|
* getSynthesisInfo(). The format used with the SyncML side is
|
|
* negotiated via the peer's capabilities, with the type defined
|
|
* in the configuration being the preferred one of the data store.
|
|
*
|
|
* See SyncContext::getConfigXML() for details about
|
|
* predefined <datatype> entries that can be referenced here.
|
|
*
|
|
* @retval xml put content of <datastore>...</datastore> here
|
|
* @retval fragments the necessary definitions for the datastore have to be added here
|
|
*/
|
|
virtual void getDatastoreXML(string &xml, XMLConfigFragments &fragments);
|
|
|
|
/**
|
|
* Synthesis <datatype> name which matches the format used
|
|
* for importing and exporting items (exportData()).
|
|
* This is not necessarily the same format that is given
|
|
* to the Synthesis engine. If this internal format doesn't
|
|
* have a <datatype> in the engine, then an empty string is
|
|
* returned.
|
|
*/
|
|
virtual string getNativeDatatypeName();
|
|
|
|
/**
|
|
* return Synthesis API pointer, if one currently is available
|
|
* (between SyncEvolution_Module_CreateContext() and
|
|
* SyncEvolution_Module_DeleteContext())
|
|
*/
|
|
virtual SDKInterface *getSynthesisAPI() const = 0;
|
|
|
|
/**
|
|
* Prepare the sync source for usage inside a SyncML server. To
|
|
* be called directly after creating the source, if at all.
|
|
*/
|
|
virtual void enableServerMode() = 0;
|
|
virtual bool serverModeEnabled() const = 0;
|
|
|
|
/**
|
|
* The optional operations.
|
|
*
|
|
* All of them are guaranteed to happen between open() and
|
|
* close().
|
|
*
|
|
* They are all allowed to throw exceptions: the operations called
|
|
* by SyncEvolution then abort whatever SyncEvolution was doing
|
|
* and end in the normal exception handling. For the Synthesis
|
|
* operations, the bridge code in SynthesisDBPlugin code catches
|
|
* exceptions, logs them and translates them into Synthesis error
|
|
* codes, which are returned to the Synthesis engine.
|
|
*
|
|
* Monitoring of most DB operations is possible via the pre- and
|
|
* post-signals managed by OperationWrapper.
|
|
*/
|
|
struct Operations {
|
|
Operations(SyncSourceName &source);
|
|
|
|
/**
|
|
* The caller determines where item data is stored (m_dirname)
|
|
* and where meta information about them (m_node). The callee
|
|
* then can use both arbitrarily. As an additional hint,
|
|
* m_mode specifies why and when the backup is made, which
|
|
* is useful to determine whether information can be reused.
|
|
*/
|
|
struct BackupInfo {
|
|
enum Mode {
|
|
BACKUP_BEFORE, /**< directly at start of sync */
|
|
BACKUP_AFTER, /**< directly after sync */
|
|
BACKUP_OTHER
|
|
} m_mode;
|
|
string m_dirname;
|
|
boost::shared_ptr<ConfigNode> m_node;
|
|
BackupInfo() {}
|
|
BackupInfo(Mode mode,
|
|
const string &dirname,
|
|
const boost::shared_ptr<ConfigNode> &node) :
|
|
m_mode(mode),
|
|
m_dirname(dirname),
|
|
m_node(node)
|
|
{}
|
|
};
|
|
struct ConstBackupInfo {
|
|
BackupInfo::Mode m_mode;
|
|
string m_dirname;
|
|
boost::shared_ptr<const ConfigNode> m_node;
|
|
ConstBackupInfo() {}
|
|
ConstBackupInfo(BackupInfo::Mode mode,
|
|
const string &dirname,
|
|
const boost::shared_ptr<const ConfigNode> &node) :
|
|
m_mode(mode),
|
|
m_dirname(dirname),
|
|
m_node(node)
|
|
{}
|
|
};
|
|
|
|
/**
|
|
* Dump all data from source unmodified into the given backup location.
|
|
* Information about the created backup is added to the
|
|
* report.
|
|
*
|
|
* Required for the backup/restore functionality in
|
|
* SyncEvolution, not for syncing itself. But typically it is
|
|
* called before syncing (can be turned off by users), so
|
|
* implementations can reuse the information gathered while
|
|
* making a backup in later operations.
|
|
*
|
|
* @param previous the most recent backup, empty m_dirname if none
|
|
* @param next the backup which is to be created, directory and node are empty
|
|
* @param report to be filled with information about backup (number of items, etc.)
|
|
*/
|
|
typedef void (BackupData_t)(const ConstBackupInfo &oldBackup,
|
|
const BackupInfo &newBackup,
|
|
BackupReport &report);
|
|
boost::function<BackupData_t> m_backupData;
|
|
|
|
/**
|
|
* Restore database from data stored in backupData().
|
|
* If possible don't touch items which are the same as in the
|
|
* backup, to mimimize impact on future incremental syncs.
|
|
*
|
|
* @param oldBackup the backup which is to be restored
|
|
* @param dryrun pretend to restore and fill in report, without
|
|
* actually touching backend data
|
|
* @param report to be filled with information about restore
|
|
* (number of total items and changes)
|
|
*/
|
|
typedef void (RestoreData_t)(const ConstBackupInfo &oldBackup,
|
|
bool dryrun,
|
|
SyncSourceReport &report);
|
|
boost::function<RestoreData_t> m_restoreData;
|
|
|
|
/**
|
|
* initialize information about local changes and items
|
|
* as in beginSync() with all parameters set to true,
|
|
* but without changing the state of the underlying database
|
|
*
|
|
* This method will be called to check for local changes without
|
|
* actually running a sync, so there is no matching end call.
|
|
*
|
|
* There might be sources which don't support non-destructive
|
|
* change tracking (in other words, checking changes permanently
|
|
* modifies the state of the source and cannot be repeated).
|
|
* Such sources should leave the functor empty.
|
|
*/
|
|
typedef void (CheckStatus_t)(SyncSourceReport &local);
|
|
boost::function<CheckStatus_t> m_checkStatus;
|
|
|
|
/**
|
|
* A quick check whether the source currently has data.
|
|
*
|
|
* If this cannot be determined easily, don't provide the
|
|
* operation. The information is currently only used to
|
|
* determine whether a slow sync should be allowed. If
|
|
* the operation is not provided, the assumption is that
|
|
* there is local data, which disables the "allow slow
|
|
* sync for empty databases" heuristic and forces the user
|
|
* to choose.
|
|
*/
|
|
typedef bool (IsEmpty_t)();
|
|
boost::function<IsEmpty_t> m_isEmpty;
|
|
|
|
/**
|
|
* Synthesis DB API callbacks. For documentation see the
|
|
* Synthesis API specification (PDF and/or sync_dbapi.h).
|
|
*
|
|
* Implementing this is necessary for SyncSources which want
|
|
* to be part of a sync session.
|
|
*/
|
|
/**@{*/
|
|
typedef OperationWrapper<sysync::TSyError (const char *, const char *)> StartDataRead_t;
|
|
StartDataRead_t m_startDataRead;
|
|
|
|
typedef OperationWrapper<sysync::TSyError ()> EndDataRead_t;
|
|
EndDataRead_t m_endDataRead;
|
|
|
|
typedef OperationWrapper<sysync::TSyError ()> StartDataWrite_t;
|
|
StartDataWrite_t m_startDataWrite;
|
|
|
|
typedef OperationWrapper<sysync::TSyError (sysync::cItemID aID, sysync::ItemID updID)> FinalizeLocalID_t;
|
|
FinalizeLocalID_t m_finalizeLocalID;
|
|
|
|
typedef OperationWrapper<sysync::TSyError (bool success, char **newToken)> EndDataWrite_t;
|
|
EndDataWrite_t m_endDataWrite;
|
|
|
|
/** the SynthesisDBPlugin is configured so that this operation
|
|
doesn't have to (and cannot) return the item data */
|
|
typedef OperationWrapper<sysync::TSyError (sysync::ItemID aID,
|
|
sysync::sInt32 *aStatus, bool aFirst)> ReadNextItem_t;
|
|
ReadNextItem_t m_readNextItem;
|
|
|
|
typedef OperationWrapper<sysync::TSyError (sysync::cItemID aID, sysync::KeyH aItemKey)> ReadItemAsKey_t;
|
|
ReadItemAsKey_t m_readItemAsKey;
|
|
|
|
typedef ContinueOperation<sysync::TSyError (sysync::KeyH aItemKey, sysync::ItemID newID)> InsertItemAsKeyContinue_t;
|
|
typedef boost::variant<sysync::TSyError, InsertItemAsKeyContinue_t> InsertItemAsKeyResult_t;
|
|
typedef OperationWrapper<InsertItemAsKeyResult_t (sysync::KeyH aItemKey, sysync::ItemID newID)> InsertItemAsKey_t;
|
|
InsertItemAsKey_t m_insertItemAsKey;
|
|
|
|
typedef ContinueOperation<sysync::TSyError (sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID updID)> UpdateItemAsKeyContinue_t;
|
|
typedef boost::variant<sysync::TSyError, UpdateItemAsKeyContinue_t> UpdateItemAsKeyResult_t;
|
|
typedef OperationWrapper<UpdateItemAsKeyResult_t (sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID updID)> UpdateItemAsKey_t;
|
|
UpdateItemAsKey_t m_updateItemAsKey;
|
|
|
|
typedef ContinueOperation<sysync::TSyError (sysync::cItemID aID)> DeleteItemContinue_t;
|
|
typedef boost::variant<sysync::TSyError, DeleteItemContinue_t> DeleteItemResult_t;
|
|
typedef OperationWrapper<DeleteItemResult_t (sysync::cItemID aID)> DeleteItem_t;
|
|
DeleteItem_t m_deleteItem;
|
|
/**@}*/
|
|
|
|
|
|
/**
|
|
* Synthesis administration callbacks. For documentation see the
|
|
* Synthesis API specification (PDF and/or sync_dbapi.h).
|
|
*
|
|
* Implementing this is *optional* in clients. In the Synthesis client
|
|
* engine, the "binfiles" module provides these calls without SyncEvolution
|
|
* doing anything.
|
|
*
|
|
* In the Synthesis server engine, the
|
|
* SyncSource::enableServerMode() call must install an
|
|
* implementation, like the one from SyncSourceAdmin.
|
|
*/
|
|
/**@{*/
|
|
typedef OperationWrapper<sysync::TSyError (const char *aLocDB,
|
|
const char *aRemDB,
|
|
char **adminData)> LoadAdminData_t;
|
|
LoadAdminData_t m_loadAdminData;
|
|
|
|
typedef OperationWrapper<sysync::TSyError (const char *adminData)> SaveAdminData_t;
|
|
SaveAdminData_t m_saveAdminData;
|
|
|
|
// not currently wrapped because it has a different return type;
|
|
// templates could be adapted to handle that
|
|
typedef bool (ReadNextMapItem_t)(sysync::MapID mID, bool aFirst);
|
|
boost::function<ReadNextMapItem_t> m_readNextMapItem;
|
|
|
|
typedef OperationWrapper<sysync::TSyError (sysync::cMapID mID)> InsertMapItem_t;
|
|
InsertMapItem_t m_insertMapItem;
|
|
|
|
typedef OperationWrapper<sysync::TSyError (sysync::cMapID mID)> UpdateMapItem_t;
|
|
UpdateMapItem_t m_updateMapItem;
|
|
|
|
typedef OperationWrapper<sysync::TSyError (sysync::cMapID mID)> DeleteMapItem_t;
|
|
DeleteMapItem_t m_deleteMapItem;
|
|
|
|
// not wrapped, too many parameters
|
|
typedef boost::function<sysync::TSyError (sysync::cItemID aID, const char *aBlobID,
|
|
void **aBlkPtr, size_t *aBlkSize,
|
|
size_t *aTotSize,
|
|
bool aFirst, bool *aLast)> ReadBlob_t;
|
|
ReadBlob_t m_readBlob;
|
|
|
|
typedef boost::function<sysync::TSyError (sysync::cItemID aID, const char *aBlobID,
|
|
void *aBlkPtr, size_t aBlkSize,
|
|
size_t aTotSize,
|
|
bool aFirst, bool aLast)> WriteBlob_t;
|
|
WriteBlob_t m_writeBlob;
|
|
|
|
typedef OperationWrapper<sysync::TSyError (sysync::cItemID aID, const char *aBlobID)> DeleteBlob_t;
|
|
DeleteBlob_t m_deleteBlob;
|
|
/**@}*/
|
|
};
|
|
|
|
/**
|
|
* Read-only access to operations.
|
|
*/
|
|
virtual const Operations &getOperations() const = 0;
|
|
|
|
/**
|
|
* Start flushing item modifications which were not executed right
|
|
* away. Item modifications (add/update/delete) can be delayed by
|
|
* returning LOCERR_AGAIN or, when using for example
|
|
* SyncSourceSerialize aka TrackingSyncSource, by returning a "check"
|
|
* function instead of the final result.
|
|
*
|
|
* The sync engine calls this method after processing each incoming
|
|
* SyncML message.
|
|
*/
|
|
virtual void flushItemChanges() {}
|
|
|
|
/**
|
|
* Called after flush() to ensure that all pending modifications
|
|
* have completed. Called when the engine needs the results.
|
|
*
|
|
* Called by the sync engine when the SyncML peer ran out of new
|
|
* item changes. At that time we would start sending back and forth
|
|
* empty messages, unless we can provide results.
|
|
*/
|
|
virtual void finishItemChanges() {}
|
|
|
|
/**
|
|
* In some usage scenarios, change tracking is not necessary.
|
|
* This includes local caching (where local data is not expected
|
|
* to changed outside of sync) or item manipulation.
|
|
*
|
|
* The user of a source must explicitly disable change tracking,
|
|
* see SyncSource.
|
|
*/
|
|
virtual bool needChanges() { return true; }
|
|
|
|
enum ReadAheadOrder {
|
|
READ_ALL_ITEMS, /** all items as reported by the ReadNextItem operation */
|
|
READ_CHANGED_ITEMS, /** read updated or added items as reported by the ReadNextItem operation */
|
|
READ_SELECTED_ITEMS, /** read items as given in an explicit list */
|
|
READ_NONE /** remove previous hint */
|
|
};
|
|
|
|
typedef std::vector<std::string> ReadAheadItems;
|
|
/**
|
|
* Provides a hint to the source which items are going to be read
|
|
* next. A source may use this to implement read-ahead. This
|
|
* is just a hint, the source must also work if reads turn out to
|
|
* ask for other items.
|
|
*/
|
|
virtual void setReadAheadOrder(ReadAheadOrder order,
|
|
const ReadAheadItems &luids = ReadAheadItems())
|
|
{ }
|
|
virtual void getReadAheadOrder(ReadAheadOrder &order,
|
|
ReadAheadItems &luids)
|
|
{
|
|
order = READ_NONE;
|
|
luids.clear();
|
|
}
|
|
|
|
protected:
|
|
struct SynthesisInfo {
|
|
/**
|
|
* name to use for MAKE/PARSETEXTWITHPROFILE,
|
|
* leave empty when acessing the field list directly
|
|
*/
|
|
std::string m_profile;
|
|
|
|
/**
|
|
* the second parameter for MAKE/PARSETEXTWITHPROFILE
|
|
* which specifies a remote rule to be applied when
|
|
* converting to and from the backend
|
|
*/
|
|
std::string m_backendRule;
|
|
|
|
/** list of supported datatypes in "<use .../>" format */
|
|
std::string m_datatypes;
|
|
|
|
/** native datatype (see getNativeDatatypeName()) */
|
|
std::string m_native;
|
|
|
|
/** name of the field list used by the datatypes */
|
|
std::string m_fieldlist;
|
|
|
|
/**
|
|
* One or more Synthesis script statements, separated
|
|
* and terminated with a semicolon. Can be left empty.
|
|
*
|
|
* If not empty, then these statements are executed directly
|
|
* before converting the current item fields into
|
|
* a single string with MAKETEXTWITHPROFILE() in the sync source's
|
|
* <beforewritescript> (see SyncSourceBase::getDatastoreXML()).
|
|
*
|
|
* This value is currently only used by sync sources which
|
|
* set m_profile.
|
|
*/
|
|
std::string m_beforeWriteScript;
|
|
|
|
/**
|
|
* Same as m_beforeWriteScript, but used directly after
|
|
* converting a string into fields with PARSETEXTWITHPROFILE()
|
|
* in <afterreadscript>.
|
|
*/
|
|
std::string m_afterReadScript;
|
|
|
|
/**
|
|
* Arbitrary configuration options, can override the ones above
|
|
* because they are added to the <datastore></datastore>
|
|
* XML configuration directly before the closing element.
|
|
*
|
|
* One example is adding <updateallfields>: this is necessary
|
|
* in backends which depend on getting complete items (= for example,
|
|
* vCard 3.0 strings) from the engine. Note that any source
|
|
* derived from SyncSourceSerialize (= the majority of the backends)
|
|
* have this set by default.
|
|
*/
|
|
std::string m_datastoreOptions;
|
|
|
|
/**
|
|
* If true, then the StartDataRead call (aka SyncSourceSession::beginSync)
|
|
* is invoked before the first message exchange with the peer. Otherwise
|
|
* it is invoked only if the peer could be reached and accepts the credentials.
|
|
*
|
|
* See SyncSourceSession::beginSync for further comments.
|
|
*/
|
|
Bool m_earlyStartDataRead;
|
|
|
|
/**
|
|
* If true, then the storage is considered read-only by the
|
|
* engine. All write requests by the peer will be silently
|
|
* discarded. This is necessary for slow syncs, where the peer
|
|
* might send back modified items.
|
|
*/
|
|
Bool m_readOnly;
|
|
|
|
/**
|
|
* If true, then the storage preserves and supports UID and
|
|
* (in iCalendar 2.0) RECURRENCE-ID with the "globally unique"
|
|
* semantic from iCalendar 2.0 (id assigned once when item is
|
|
* created). If both sides in a sync support this, then the
|
|
* engine can rely on these properties to find matching items
|
|
* during a slow sync.
|
|
*
|
|
* Matches ClientTestConfig::m_sourceKnowsItemSemantic.
|
|
*/
|
|
Bool m_globalIDs;
|
|
};
|
|
|
|
/**
|
|
* helper function for getDatastoreXML(): fill in information
|
|
* as necessary
|
|
*
|
|
* @retval fragments the necessary definitions for the other
|
|
* return values have to be added here
|
|
*/
|
|
virtual void getSynthesisInfo(SynthesisInfo &info,
|
|
XMLConfigFragments &fragments) = 0;
|
|
|
|
/**
|
|
* utility code: creates Synthesis <use datatype=...>
|
|
* statements, using the predefined vCard21/vCard30/vcalendar10/icalendar20
|
|
* types. Throws an error if no suitable result can be returned (empty or invalid type)
|
|
*
|
|
* @param type the format specifier as used in SyncEvolution configs, with and without version
|
|
* (text/x-vcard:2.1, text/x-vcard, text/x-vcalendar, text/calendar, text/plain, ...);
|
|
* see SourceType::m_format
|
|
* @param forceFormat if true, then don't allow alternative formats (like vCard 3.0 in addition to 2.1);
|
|
* see SourceType::m_force
|
|
* @return generated XML fragment
|
|
*/
|
|
std::string getDataTypeSupport(const std::string &type,
|
|
bool forceFormat);
|
|
};
|
|
|
|
/**
|
|
* SyncEvolution accesses all sources through this interface.
|
|
*
|
|
* Certain functionality is optional or can be implemented in
|
|
* different ways. These methods are accessed through functors
|
|
* (function objects) which may be unset. The expected usage is that
|
|
* derived classes fill in the pieces that they provide by binding the
|
|
* functors to normal methods. For example, TrackingSyncSource
|
|
* provides a normal base class with pure virtual functions which have
|
|
* to be provided by users of that class.
|
|
*
|
|
* Error reporting is done via the Log class.
|
|
*/
|
|
class SyncSource : virtual public SyncSourceBase, public SyncSourceConfig, public SyncSourceReport
|
|
{
|
|
public:
|
|
SyncSource(const SyncSourceParams ¶ms);
|
|
virtual ~SyncSource() {}
|
|
|
|
/** true in sources which are not meant to be used, see RegisterSyncSource::InactiveSource() */
|
|
virtual bool isInactive() const { return false; }
|
|
|
|
/**
|
|
* SyncSource implementations must register themselves here via
|
|
* RegisterSyncSource
|
|
*/
|
|
static SourceRegistry &getSourceRegistry();
|
|
|
|
/**
|
|
* SyncSource tests are registered here by the constructor of
|
|
* RegisterSyncSourceTest
|
|
*/
|
|
static TestRegistry &getTestRegistry();
|
|
|
|
struct Database {
|
|
Database(const string &name, const string &uri, bool isDefault = false, bool isReadOnly = false) :
|
|
m_name( name ), m_uri( uri ), m_isDefault(isDefault), m_isReadOnly(isReadOnly) {}
|
|
string m_name;
|
|
string m_uri;
|
|
bool m_isDefault;
|
|
bool m_isReadOnly;
|
|
};
|
|
typedef vector<Database> Databases;
|
|
|
|
/**
|
|
* returns a list of all know data sources for the kind of items
|
|
* supported by this sync source
|
|
*/
|
|
virtual Databases getDatabases() = 0;
|
|
|
|
/**
|
|
* Creates a new database.
|
|
* The default implementation just throws an error.
|
|
*
|
|
* @param database At least the name should be set. Some backends
|
|
* may also be able to create the database with
|
|
* a specific URI.
|
|
* @return description of the new database
|
|
*/
|
|
virtual Database createDatabase(const Database &database) { throwError(SE_HERE, "creating databases is not supported by backend " + getBackend()); return Database("", ""); }
|
|
|
|
/**
|
|
* Removing a database primarily removes the meta data about the
|
|
* database. The data itself may still exist in a trash folder.
|
|
* The enum tells the deleteDatabase() call what the intention of
|
|
* the caller is.
|
|
*/
|
|
enum RemoveData {
|
|
REMOVE_DATA_DEFAULT, /**< do whatever makes most sense for the backend */
|
|
REMOVE_DATA_FORCE, /**< force immediate purging of the data, fail if not possible */
|
|
REMOVE_DATA_KEEP /**< keep data, only remove access to it */
|
|
};
|
|
|
|
/**
|
|
* Removes a database. To map a "database" property to a uri,
|
|
* instantiate the source with the desired config, open() it and
|
|
* then call getDatabase().
|
|
*
|
|
* @param uri unique identifier for the database
|
|
* @param removeData describes what to do about the database content
|
|
*/
|
|
virtual void deleteDatabase(const std::string &uri, RemoveData removeData) { throwError(SE_HERE, "deleting databases is not supported by backend " + getBackend()); }
|
|
|
|
/**
|
|
* 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 m_startDataRead callback (bound to
|
|
* beginSync() in some of the utility classes).
|
|
*
|
|
* 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;
|
|
|
|
/**
|
|
* Checks whether the source as currently configured can access
|
|
* data. open() must have been called first.
|
|
*
|
|
* The default implementation calls Operations::m_isEmpty; this
|
|
* assumes that m_isEmpty really does something with the
|
|
* database. Derived classes which neither check their config in
|
|
* open() nor during m_isEmpty() should provide their own
|
|
* implementation of isUsable(). It might also be possible to
|
|
* check usability more efficiently.
|
|
*
|
|
* It is also possible to suppress the usability check by
|
|
* providing an implementation which always returns true, for
|
|
* example when checking would be too expensive or lead to
|
|
* unexpected operations (like accessing a remote server).
|
|
*
|
|
* If unusable, the implementation should print an INFO message
|
|
* explaining why the source is unusable. Whether that is an
|
|
* error will be determined by the caller.
|
|
*
|
|
* @return false if the source is definitely unusable, true if it
|
|
* is usable or might be
|
|
*/
|
|
virtual bool isUsable();
|
|
|
|
/**
|
|
* Returns the actual database that is in use. open() must
|
|
* have been called first.
|
|
*
|
|
* Useful because the "database" property might be empty or
|
|
* be interpreted in different ways by different backends.
|
|
*
|
|
* Needed for deleting databases. Not implemented in all
|
|
* backends. The default implementation returns an empty
|
|
* structure.
|
|
*
|
|
* @return Database structure with at least m_uri set if
|
|
* the actual database is known.
|
|
*/
|
|
Database getDatabase() const { return m_database; }
|
|
|
|
/**
|
|
* To be called by derived implementation of open().
|
|
*/
|
|
void setDatabase(const Database &database) { m_database = database; }
|
|
|
|
/**
|
|
* Read-only access to operations. Derived classes can modify
|
|
* them via m_operations.
|
|
*/
|
|
virtual const Operations &getOperations() const { return m_operations; }
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* A hint to the source that syncing will stop processing data
|
|
* for a while (freeze = true) or resume processing (freeze =
|
|
* false).
|
|
*
|
|
* If the source needs to change its own state to accomodate
|
|
* the new freeze state of the sync and that change fails, then
|
|
* the source must throw an error in setFreeze(). The caller will
|
|
* not interact with the source while frozen and thus would not
|
|
* notice the failure if no error was thrown.
|
|
*/
|
|
virtual void setFreeze(bool freeze) {}
|
|
|
|
/**
|
|
* Number of InsertItem operations, regardless whether the
|
|
* operation succeeded or failed. Operations which get suspended
|
|
* are counted again each time they are resumed.
|
|
*/
|
|
int32_t getAdded() const { return m_added; }
|
|
int32_t getUpdated() const { return m_updated; }
|
|
int32_t getDeleted() const { return m_deleted; }
|
|
|
|
/**
|
|
* return Synthesis API pointer, if one currently is available
|
|
* (between SyncEvolution_Module_CreateContext() and
|
|
* SyncEvolution_Module_DeleteContext())
|
|
*/
|
|
virtual SDKInterface *getSynthesisAPI() const;
|
|
|
|
/**
|
|
* change the Synthesis API that is used by the source
|
|
*/
|
|
void pushSynthesisAPI(sysync::SDK_InterfaceType *synthesisAPI);
|
|
|
|
/**
|
|
* remove latest Synthesis API and return to previous one (if any)
|
|
*/
|
|
void popSynthesisAPI();
|
|
|
|
/**
|
|
* If called while a sync session runs (i.e. after m_startDataRead
|
|
* (aka beginSync()) and before m_endDataWrite (aka endSync())),
|
|
* the engine will finish the session and then immediately try to
|
|
* run another session where any source in which requestAnotherSync()
|
|
* was called is active again. There is no guarantee that this
|
|
* will be possible.
|
|
*
|
|
* The source must be prepared to correctly handle another sync
|
|
* session. m_endDataWrite will be called and then the sequence
|
|
* of calls starts again at m_startDataRead.
|
|
*
|
|
* The sync mode will switch to an incremental sync in the same
|
|
* direction as the initial sync (one-way to client or server,
|
|
* two-way).
|
|
*
|
|
* Does nothing when called at the wrong time. There's no
|
|
* guarantee either that restarting is possible.
|
|
*
|
|
* Currently only supported when a single source is active in
|
|
* the initial sync.
|
|
*/
|
|
void requestAnotherSync();
|
|
|
|
/**
|
|
* factory function for a SyncSource that provides the
|
|
* source type specified in the params.m_nodes.m_configNode
|
|
*
|
|
* @param error throw a runtime error describing what the problem is if no matching source is found
|
|
* @param config optional, needed for intantiating virtual sources
|
|
* @return valid instance, NULL if no source can handle the given type (only when error==false)
|
|
*/
|
|
static SyncSource *createSource(const SyncSourceParams ¶ms,
|
|
bool error = true,
|
|
SyncConfig *config = NULL);
|
|
|
|
/**
|
|
* Factory function for a SyncSource with the given name
|
|
* and handling the kind of data specified by "type" (e.g.
|
|
* "Evolution Contacts:text/x-vcard").
|
|
*
|
|
* The source is instantiated with dummy configuration nodes under
|
|
* the pseudo server name "testing". This function is used for
|
|
* testing sync sources, not for real syncs. If the prefix is set,
|
|
* then <prefix>_<name>_1 is used as database, just as in the
|
|
* Client::Sync and Client::Source tests. Otherwise the default
|
|
* database is used.
|
|
*
|
|
* @param error throw a runtime error describing what the problem is if no matching source is found
|
|
* @return NULL if no source can handle the given type
|
|
*/
|
|
static SyncSource *createTestingSource(const string &name, const string &type, bool error,
|
|
const char *prefix = getenv("CLIENT_TEST_EVOLUTION_PREFIX"));
|
|
|
|
/**
|
|
* Initialize and/or load backends. No longer
|
|
* done automatically to give libsyncevolution
|
|
* better control over when backends get loaded.
|
|
*/
|
|
static void backendsInit();
|
|
|
|
/**
|
|
* Some information about available backends.
|
|
* Multiple lines, formatted for users of the
|
|
* command line.
|
|
*/
|
|
static string backendsInfo();
|
|
/**
|
|
* Debug information about backends.
|
|
*/
|
|
static string backendsDebug();
|
|
|
|
/**
|
|
* Mime type a backend communicates with the remote peer by default,
|
|
* this is used to alert the remote peer in SAN during server alerted sync.
|
|
*/
|
|
virtual std::string getPeerMimeType() const =0;
|
|
|
|
/* implementation of SyncSourceBase */
|
|
virtual std::string getName() const { return SyncSourceConfig::getName(); }
|
|
virtual std::string getDisplayName() const { return m_name; }
|
|
virtual void setDisplayName(const std::string &name) { m_name = name; }
|
|
virtual long getNumDeleted() const { return m_numDeleted; }
|
|
virtual void setNumDeleted(long num) { m_numDeleted = num; }
|
|
virtual void incrementNumDeleted() { m_numDeleted++; }
|
|
virtual bool needChanges() { return m_needChanges; }
|
|
void setNeedChanges(bool needChanges) { m_needChanges = needChanges; }
|
|
|
|
|
|
/**
|
|
* Set to true in SyncContext::initSAN() when a SyncML server has
|
|
* to force a client into slow sync mode. This is necessary because
|
|
* the server cannot request that mode (missing in the standard).
|
|
* Forcing the slow sync mode is done via a FORCESLOWSYNC() macro
|
|
* call in an <alertscript>.
|
|
*/
|
|
void setForceSlowSync(bool forceSlowSync) { m_forceSlowSync = forceSlowSync; }
|
|
bool getForceSlowSync() const { return m_forceSlowSync; }
|
|
|
|
protected:
|
|
Operations m_operations;
|
|
|
|
private:
|
|
/**
|
|
* Counter for items deleted in the source. Has to be incremented
|
|
* by RemoveAllItems() and DeleteItem(). This counter is used to
|
|
* update the Synthesis engine counter in those cases where the
|
|
* engine does not (refresh from server) or cannot
|
|
* (RemoveAllItems()) count the removals itself.
|
|
*/
|
|
long m_numDeleted;
|
|
|
|
/**
|
|
* Counter for InsertItem operations. Updated by hooking into the operation.
|
|
*/
|
|
int32_t m_added, m_updated, m_deleted;
|
|
|
|
bool m_forceSlowSync;
|
|
|
|
/**
|
|
* Interface pointer for this sync source, allocated for us by the
|
|
* Synthesis engine and registered here by
|
|
* SyncEvolution_Module_CreateContext(). Only valid until
|
|
* SyncEvolution_Module_DeleteContext(), in other words, while
|
|
* the engine is running.
|
|
*/
|
|
std::vector<sysync::SDK_InterfaceType *> m_synthesisAPI;
|
|
|
|
/** database in use after open(), to be set via setDatabase() by derived class */
|
|
Database m_database;
|
|
|
|
/** actual name of the source */
|
|
std::string m_name;
|
|
|
|
/** change tracking enabled? */
|
|
bool m_needChanges;
|
|
};
|
|
|
|
/**
|
|
* A SyncSource with no pure virtual functions.
|
|
*/
|
|
class DummySyncSource : public SyncSource
|
|
{
|
|
public:
|
|
DummySyncSource(const SyncSourceParams ¶ms) :
|
|
SyncSource(params) {}
|
|
|
|
DummySyncSource(const std::string &name, const std::string &contextName) :
|
|
SyncSource(SyncSourceParams(name, SyncSourceNodes(), boost::shared_ptr<SyncConfig>(), contextName)) {}
|
|
|
|
virtual Databases getDatabases() { return Databases(); }
|
|
virtual void open() {}
|
|
virtual void close() {}
|
|
virtual void getSynthesisInfo(SynthesisInfo &info,
|
|
XMLConfigFragments &fragments) {}
|
|
virtual void enableServerMode() {}
|
|
virtual bool serverModeEnabled() const { return false; }
|
|
virtual std::string getPeerMimeType() const {return "";}
|
|
};
|
|
|
|
/**
|
|
* A special source which combines one or more real sources.
|
|
* Most of the special handling for that is in SyncContext.cpp.
|
|
*
|
|
* This class can be instantiated, opened and closed if and only if
|
|
* the underlying sources also support that.
|
|
*/
|
|
class VirtualSyncSource : public DummySyncSource
|
|
{
|
|
std::vector< boost::shared_ptr<SyncSource> > m_sources;
|
|
bool isEmpty();
|
|
|
|
public:
|
|
/**
|
|
* @param config optional: when given, the constructor will instantiate the
|
|
* referenced underlying sources and check them in open()
|
|
*/
|
|
VirtualSyncSource(const SyncSourceParams ¶ms, SyncConfig *config = NULL);
|
|
|
|
/** opens underlying sources and checks config by calling getDataTypeSupport() */
|
|
virtual void open();
|
|
virtual void close();
|
|
|
|
/**
|
|
* returns array with source names that are referenced by this
|
|
* virtual source
|
|
*/
|
|
std::vector<std::string> getMappedSources();
|
|
|
|
/**
|
|
* returns <use datatype=...> statements for XML config,
|
|
* throws error if not configured correctly
|
|
*/
|
|
std::string getDataTypeSupport();
|
|
using SyncSourceBase::getDataTypeSupport;
|
|
|
|
|
|
/*
|
|
* If any of the sub datasource has no databases associated, return an empty
|
|
* database list to indicate a possibly error condition; otherwise return a
|
|
* dummy database to identify "calendar+todo" combined datasource.
|
|
**/
|
|
virtual Databases getDatabases();
|
|
};
|
|
|
|
/**
|
|
* Hooks up the Synthesis DB Interface start sync (BeginDataRead) and
|
|
* end sync (EndDataWrite) calls with virtual methods. Ensures that
|
|
* sleepSinceModification() is called.
|
|
*
|
|
* Inherit from this class in your sync source and call the init()
|
|
* method to use it.
|
|
*/
|
|
class SyncSourceSession : virtual public SyncSourceBase {
|
|
public:
|
|
/**
|
|
* called before Synthesis engine starts to ask for changes and item data
|
|
*
|
|
* If SynthesisInfo::m_earlyStartDataRead is true, then this call is
|
|
* invoked before the first message exchange with a peer and it
|
|
* may throw a STATUS_SLOW_SYNC_508 StatusException if an
|
|
* incremental sync is not possible. In that case, preparations
|
|
* for a slow sync must have completed successfully inside the
|
|
* beginSync() call. It is not going to get called again.
|
|
*
|
|
* If SynthesisInfo::m_earlyStartDataRead is false (the default),
|
|
* then this is called only if the peer was reachable and accepted
|
|
* the credentials. This mode of operation is preferred if a fallback
|
|
* to slow sync is not needed, because it allows deferring expensive
|
|
* operations until really needed. For example, the engine does
|
|
* database dumps at the time when StartDataRead is called.
|
|
*
|
|
* See StartDataRead for details.
|
|
*
|
|
* @param lastToken identifies the last completed sync
|
|
* @param resumeToken identifies a more recent sync which needs to be resumed;
|
|
* if not empty, then report changes made after that sync
|
|
* instead of the last completed sync
|
|
*/
|
|
virtual void beginSync(const std::string &lastToken, const std::string &resumeToken) = 0;
|
|
|
|
/**
|
|
* called after completing or suspending the current sync
|
|
*
|
|
* See EndDataWrite for details.
|
|
*
|
|
* @return a token identifying this sync session for a future beginSync()
|
|
*/
|
|
virtual std::string endSync(bool success) = 0;
|
|
|
|
/** set Synthesis DB Interface operations */
|
|
void init(SyncSource::Operations &ops);
|
|
private:
|
|
sysync::TSyError startDataRead(const char *lastToken, const char *resumeToken);
|
|
sysync::TSyError endDataWrite(bool success, char **newToken);
|
|
};
|
|
|
|
|
|
/**
|
|
* Implements the Synthesis DB Interface for reporting item changes
|
|
* (ReadNextItemAsKey) *without* actually delivering the item data.
|
|
*/
|
|
class SyncSourceChanges : virtual public SyncSourceBase {
|
|
public:
|
|
SyncSourceChanges();
|
|
|
|
enum State {
|
|
ANY,
|
|
NEW,
|
|
UPDATED,
|
|
DELETED,
|
|
MAX
|
|
};
|
|
|
|
/**
|
|
* Add the LUID of a NEW/UPDATED/DELETED item.
|
|
* If unspecified, the luid is added to the list of
|
|
* all items. This must be done *in addition* to adding
|
|
* the luid with a specific state.
|
|
*
|
|
* For example, the luid of an updated item should be added with
|
|
* addItem(luid [, ANY]) and again with addItem(luid, DELETED).
|
|
*
|
|
* The Synthesis engine does not need the list of deleted items
|
|
* and does not distinguish between added and updated items, so
|
|
* for syncing, adding DELETED items is optional and all items
|
|
* which are different from the last sync can be added as
|
|
* UPDATED. The client-test program expects that the informationb
|
|
* is provided precisely.
|
|
*
|
|
* @return true if the luid was already listed
|
|
*/
|
|
bool addItem(const string &luid, State state = ANY);
|
|
|
|
/**
|
|
* Wipe out all added items, returning true if any were found.
|
|
*/
|
|
bool reset();
|
|
|
|
typedef std::set<std::string> Items_t;
|
|
const Items_t &getItems(State state) { return m_items[state]; }
|
|
const Items_t &getAllItems() const { return m_items[ANY]; }
|
|
const Items_t &getNewItems() const { return m_items[NEW]; }
|
|
const Items_t &getUpdatedItems() const { return m_items[UPDATED]; }
|
|
const Items_t &getDeletedItems() const { return m_items[DELETED]; }
|
|
|
|
/** set Synthesis DB Interface operations */
|
|
void init(SyncSource::Operations &ops);
|
|
|
|
private:
|
|
Items_t m_items[MAX];
|
|
bool m_first;
|
|
Items_t::const_iterator m_it;
|
|
|
|
sysync::TSyError iterate(sysync::ItemID aID,
|
|
sysync::sInt32 *aStatus,
|
|
bool aFirst);
|
|
};
|
|
|
|
/**
|
|
* Implements the Synthesis DB Interface for deleting an item
|
|
* (DeleteItem). Increments number of deleted items in
|
|
* SyncSourceBase.
|
|
*/
|
|
class SyncSourceDelete : virtual public SyncSourceBase {
|
|
public:
|
|
virtual void deleteItem(const string &luid) = 0;
|
|
|
|
/** set Synthesis DB Interface operations */
|
|
void init(SyncSource::Operations &ops);
|
|
|
|
private:
|
|
sysync::TSyError deleteItemSynthesis(sysync::cItemID aID);
|
|
};
|
|
|
|
enum InsertItemResultState {
|
|
/**
|
|
* Operation not complete, invoke callback in ItemResult to check
|
|
* for progress.
|
|
*/
|
|
ITEM_AGAIN,
|
|
|
|
/**
|
|
* item added or updated as requested
|
|
*/
|
|
ITEM_OKAY,
|
|
|
|
/**
|
|
* When a backend is asked to add an item and recognizes
|
|
* that the item matches an already existing item, it may
|
|
* replace that item instead of creating a duplicate. In this
|
|
* case it must return ITEM_REPLACED and set the luid/revision
|
|
* of that updated item.
|
|
*
|
|
* This can happen when such an item was added concurrently to
|
|
* the running sync or, more likely, was reported as new by
|
|
* the backend and the engine failed to find the match because
|
|
* it doesn't know about some special semantic, like iCalendar
|
|
* 2.0 UID).
|
|
*
|
|
* Note that depending on the age of the items, the older data
|
|
* will replace the more recent one when always using item
|
|
* replacement.
|
|
*/
|
|
ITEM_REPLACED,
|
|
|
|
/**
|
|
* Same as ITEM_REPLACED, except that the backend did some
|
|
* modifications to the data that was sent to it before
|
|
* storing it, like merging it with the existing item. The
|
|
* engine will treat the updated item as modified and send
|
|
* back the update to the peer as soon as possible. In server
|
|
* mode that will be in the same sync session, in a client in
|
|
* the next session (client cannot send changes after having
|
|
* received data from the server).
|
|
*/
|
|
ITEM_MERGED,
|
|
|
|
/**
|
|
* As before, a match against an existing item was detected.
|
|
* By returning this state and the luid of the matched item
|
|
* (revision not needed) the engine is instructed to do the
|
|
* necessary data comparison and merging itself. Useful when a
|
|
* backend can't do the necessary merging itself.
|
|
*/
|
|
ITEM_NEEDS_MERGE
|
|
};
|
|
|
|
/**
|
|
* an interface for reading and writing items in the internal
|
|
* format; see SyncSourceSerialize for an explanation
|
|
*/
|
|
class SyncSourceRaw : virtual public SyncSourceBase {
|
|
public:
|
|
class InsertItemResult {
|
|
public:
|
|
InsertItemResult() :
|
|
m_state(ITEM_OKAY)
|
|
{}
|
|
|
|
/**
|
|
* @param luid the LUID after the operation; during an update the LUID must
|
|
* not be changed, so return the original one here
|
|
* @param revision the revision string after the operation; leave empty if not used
|
|
* @param state report about what was done with the data
|
|
*/
|
|
InsertItemResult(const string &luid,
|
|
const string &revision,
|
|
InsertItemResultState state) :
|
|
m_luid(luid),
|
|
m_revision(revision),
|
|
m_state(state)
|
|
{}
|
|
|
|
/**
|
|
* Constructor for the case where the final result is not available yet.
|
|
*
|
|
* @param check will be called again later to poll for completion
|
|
*/
|
|
InsertItemResult(const boost::function<InsertItemResult ()> &check) :
|
|
m_state(ITEM_AGAIN),
|
|
m_continue(check)
|
|
{}
|
|
|
|
string m_luid;
|
|
string m_revision;
|
|
InsertItemResultState m_state;
|
|
typedef ContinueOperation<InsertItemResult ()> Continue_t;
|
|
Continue_t m_continue;
|
|
};
|
|
|
|
/** same as SyncSourceSerialize::insertItem(), but with internal format */
|
|
virtual InsertItemResult insertItemRaw(const std::string &luid, const std::string &item) = 0;
|
|
|
|
/** same as SyncSourceSerialize::readItem(), but with internal format */
|
|
virtual void readItemRaw(const std::string &luid, std::string &item) = 0;
|
|
};
|
|
|
|
/**
|
|
* Implements the Synthesis DB Interface for importing/exporting item
|
|
* data (ReadItemAsKey, InsertItemAsKey, UpdateItemAsKey) in such a
|
|
* way that the sync source only has to deal with a text
|
|
* representation of an item.
|
|
*
|
|
* There may be two such representations:
|
|
* - "engine format" is the one exchanged with the Synthesis engine
|
|
* - "internal or raw format" is a format that might better capture
|
|
* the internal representation and can be used for backup/restore
|
|
* and testing
|
|
*
|
|
* To give an example, the EvolutionMemoSource uses plain text as
|
|
* engine format and iCalendar 2.0 as raw format.
|
|
*
|
|
* The BackupData_t and RestoreData_t operations are implemented by
|
|
* this class using the internal format.
|
|
*
|
|
* The engine format must be something that the Synthesis engine can
|
|
* parse and generate, in other words, there must be a corresponding
|
|
* profile in the XML configuration. This class uses information
|
|
* provided by the sync source (mime type and version) and from the
|
|
* configuration (format selected by user) to generate the required
|
|
* XML configuration parts for common configurations (vCard,
|
|
* vCalendar, iCalendar, text). Special representations can be added
|
|
* to the global XML configuration by overriding default
|
|
* implementations provided in this class.
|
|
*
|
|
* InsertItemAsKey and UpdateItemAsKey are mapped to the same
|
|
* insertItem() call because in practice it can happen that a request
|
|
* to add an item must be turned into an update. For example, a
|
|
* meeting was imported both into the server and the client. A request
|
|
* to add the item again should be treated as an update, based on the
|
|
* unique iCalendar 2.0 LUID.
|
|
*/
|
|
class SyncSourceSerialize : virtual public SyncSourceBase, virtual public SyncSourceRaw {
|
|
public:
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* returns the backend selection and configuration
|
|
*/
|
|
virtual InitState<SourceType> getSourceType() const = 0;
|
|
|
|
/**
|
|
* 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, using the engine format
|
|
* @return the result of inserting the item
|
|
*/
|
|
virtual InsertItemResult insertItem(const std::string &luid, const std::string &item) = 0;
|
|
|
|
/**
|
|
* Return item data in engine format.
|
|
*
|
|
* @param luid identifies the item
|
|
* @retval item item data
|
|
*/
|
|
virtual void readItem(const std::string &luid, std::string &item) = 0;
|
|
|
|
/* implement SyncSourceRaw under the assumption that the internal and engine format are identical */
|
|
virtual InsertItemResult insertItemRaw(const std::string &luid, const std::string &item);
|
|
virtual void readItemRaw(const std::string &luid, std::string &item);
|
|
|
|
/** set Synthesis DB Interface operations */
|
|
void init(SyncSource::Operations &ops);
|
|
|
|
protected:
|
|
/**
|
|
* used getMimeType(), getMimeVersion() and getSourceType()
|
|
* to provide the information necessary for automatic
|
|
* conversion to the sync source's internal item representation
|
|
*/
|
|
virtual void getSynthesisInfo(SynthesisInfo &info,
|
|
XMLConfigFragments &fragments);
|
|
private:
|
|
sysync::TSyError readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey);
|
|
SyncSource::Operations::InsertItemAsKeyResult_t insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID);
|
|
SyncSource::Operations::UpdateItemAsKeyResult_t updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID);
|
|
sysync::TSyError insertContinue(sysync::ItemID newID, const InsertItemResult::Continue_t &cont);
|
|
};
|
|
|
|
/**
|
|
* Mapping from Hash() value to file.
|
|
* Used by SyncSourceRevisions, but may be of use for
|
|
* other backup implementations.
|
|
*/
|
|
class ItemCache
|
|
{
|
|
public:
|
|
#ifdef USE_SHA256
|
|
typedef std::string Hash_t;
|
|
Hash_t hashFunc(const std::string &data) { return SHA_256(data); }
|
|
#else
|
|
typedef unsigned long Hash_t;
|
|
Hash_t hashFunc(const std::string &data) { return Hash(data); }
|
|
#endif
|
|
typedef unsigned long Counter_t;
|
|
|
|
/** mark the algorithm used for the hash via different suffices */
|
|
static const char *m_hashSuffix;
|
|
|
|
/**
|
|
* Collect information about stored hashes. Provides
|
|
* access to file name via hash.
|
|
*
|
|
* If no hashes were written (as in an old SyncEvoltion
|
|
* version), we could read the files to recreate the
|
|
* hashes. This is not done because it won't occur
|
|
* often enough.
|
|
*
|
|
* Hashes are also not verified. Users should better
|
|
* not edit them or file contents...
|
|
*
|
|
* @param oldBackup existing backup to read; may be empty
|
|
* @param newBackup new backup to be created
|
|
* @param legacy legacy mode includes a bug
|
|
* which cannot be fixed without breaking on-disk format
|
|
*/
|
|
void init(const SyncSource::Operations::ConstBackupInfo &oldBackup,
|
|
const SyncSource::Operations::BackupInfo &newBackup,
|
|
bool legacy);
|
|
|
|
/**
|
|
* create file name for a specific hash, empty if no such hash
|
|
*/
|
|
string getFilename(Hash_t hash);
|
|
|
|
/**
|
|
* add a new item, reusing old one if possible
|
|
*
|
|
* @param item new item data
|
|
* @param uid its unique ID
|
|
* @param rev revision string
|
|
*/
|
|
void backupItem(const std::string &item,
|
|
const std::string &uid,
|
|
const std::string &rev);
|
|
|
|
/** to be called after init() and all backupItem() calls */
|
|
void finalize(BackupReport &report);
|
|
|
|
/** can be used to restart creating the backup after an intermediate failure */
|
|
void reset();
|
|
|
|
private:
|
|
typedef std::map<Hash_t, Counter_t> Map_t;
|
|
Map_t m_hash2counter;
|
|
string m_dirname;
|
|
SyncSource::Operations::BackupInfo m_backup;
|
|
bool m_legacy;
|
|
unsigned long m_counter;
|
|
};
|
|
|
|
/**
|
|
* Implements change tracking based on a "revision" string, a string
|
|
* which is guaranteed to change automatically each time an item is
|
|
* modified. Backup/restore is optionally implemented by this class if
|
|
* pointers to SyncSourceRaw and SyncSourceDelete interfaces are
|
|
* passed to init(). For backup only the former is needed, for restore
|
|
* both.
|
|
*
|
|
* 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)
|
|
*
|
|
* Sync sources which want to use this functionality have to provide
|
|
* the following functionality by implementing the pure virtual
|
|
* functions below:
|
|
* - enumerate all existing items
|
|
* - provide LUID and the revision string
|
|
* The LUID must remain *constant* when making changes to 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.
|
|
*
|
|
* Most of the functionality of this class must be activated
|
|
* explicitly as part of the life cycle of the sync source instance by
|
|
* calling detectChanges(), updateRevision() and deleteRevision().
|
|
*
|
|
* If the required interfaces are provided to init(), then backup/restore
|
|
* operations are set. init() also hooks into the session life cycle
|
|
* with an end callback that ensures that enough time passes at the end
|
|
* of the sync. This is important for sync sources which use time stamps
|
|
* as revision string. "enough time" is defined by a parameter to the
|
|
* init call.
|
|
*/
|
|
class SyncSourceRevisions : virtual public SyncSourceChanges, virtual public SyncSourceBase {
|
|
public:
|
|
typedef map<string, string> RevisionMap_t;
|
|
|
|
/**
|
|
* Fills the complete mapping from UID to revision string of all
|
|
* currently existing items.
|
|
*
|
|
* Usually both UID 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.
|
|
*
|
|
* An source must set the revision string for all items or
|
|
* none at all.
|
|
*
|
|
* This call is typically only invoked only once during the
|
|
* lifetime of a source, at the time when detectChanges() needs
|
|
* the information. The result returned in that invocation is
|
|
* used throught the session.
|
|
*
|
|
* When detectChanges() is called with CHANGES_NONE, listAllItems()
|
|
* is avoided. Instead the cached information is used. Sources
|
|
* may need to know that information, so in this case setAllItems()
|
|
* is called as part of detectChanges().
|
|
*/
|
|
virtual void listAllItems(RevisionMap_t &revisions) = 0;
|
|
|
|
/**
|
|
* Called by SyncSourceRevisions::detectChanges() 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);
|
|
}
|
|
|
|
/**
|
|
* Tells detectChanges() how to do its job.
|
|
*/
|
|
enum ChangeMode {
|
|
/**
|
|
* Call listAllItems() and use the list of previous items
|
|
* to calculate changes.
|
|
*/
|
|
CHANGES_FULL,
|
|
|
|
/**
|
|
* Don't rely on previous information. Will call
|
|
* listAllItems() and generate a full list of items based on
|
|
* the result.
|
|
*/
|
|
CHANGES_SLOW,
|
|
|
|
/**
|
|
* Caller has already determined that a) no items have changed
|
|
* and that b) the list of previous items is valid. For example,
|
|
* some backends have a way of getting a revision string for
|
|
* the whole database and can compare that against the value
|
|
* from the end of the previous sync.
|
|
*
|
|
* In this mode, listAllItems() doesn't have to be called.
|
|
* A list of all items will be created, with no items marked
|
|
* as added/updated/deleted.
|
|
*/
|
|
CHANGES_NONE
|
|
};
|
|
|
|
/**
|
|
* calculate changes, call when sync source is ready for
|
|
* listAllItems() and before changes are needed
|
|
*
|
|
* The trackingNode must be provided by the caller. It will
|
|
* be updated by each of the calls and must be stored by
|
|
* the caller.
|
|
*
|
|
* @param trackingNode a config node for exclusive use by this class
|
|
* @param mode determines how changes are detected; if unsure,
|
|
* use CHANGES_FULL, which will always produce
|
|
* the required information, albeit more slowly
|
|
* than the other modes
|
|
* @return true if change detection could only provide a list of currently
|
|
* existing items, but not the list of added/updated/deleted items
|
|
*/
|
|
bool detectChanges(ConfigNode &trackingNode, ChangeMode mode);
|
|
|
|
/**
|
|
* record that an item was added or updated
|
|
*
|
|
* @param old_luid empty for add, old LUID otherwise
|
|
* @param new_luid normally LUIDs must not change, but this call allows it
|
|
* @param revision revision string after change
|
|
*/
|
|
void updateRevision(ConfigNode &trackingNode,
|
|
const std::string &old_luid,
|
|
const std::string &new_luid,
|
|
const std::string &revision);
|
|
|
|
/**
|
|
* record that we deleted an item
|
|
*
|
|
* @param luid the obsolete LUID
|
|
*/
|
|
void deleteRevision(ConfigNode &trackingNode,
|
|
const std::string &luid);
|
|
|
|
/**
|
|
* set Synthesis DB Interface and backup/restore operations
|
|
* @param raw needed for backups; if NULL, no backups are made
|
|
* @param del needed for restores; if NULL, only backups are possible
|
|
* @param granularity time that has to pass between making a modification
|
|
* and checking for changes; this class ensures that
|
|
* at least this amount of time has passed before letting
|
|
* the session terminate. Delays in different source do
|
|
* not add up.
|
|
*/
|
|
void init(SyncSourceRaw *raw, SyncSourceDelete *del,
|
|
int granularity,
|
|
SyncSource::Operations &ops);
|
|
|
|
private:
|
|
SyncSourceRaw *m_raw;
|
|
SyncSourceDelete *m_del;
|
|
int m_revisionAccuracySeconds;
|
|
|
|
/** buffers the result of the initial listAllItems() call */
|
|
RevisionMap_t m_revisions;
|
|
bool m_revisionsSet;
|
|
bool m_firstCycle;
|
|
void initRevisions();
|
|
|
|
/**
|
|
* Dump all data from source unmodified into the given directory.
|
|
* The ConfigNode can be used to store meta information needed for
|
|
* restoring that state. Both directory and node are empty.
|
|
*/
|
|
void backupData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
|
|
const SyncSource::Operations::BackupInfo &newBackup,
|
|
BackupReport &report);
|
|
|
|
/**
|
|
* Restore database from data stored in backupData(). Will be
|
|
* called inside open()/close() pair. beginSync() is *not* called.
|
|
*/
|
|
void restoreData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
|
|
bool dryrun,
|
|
SyncSourceReport &report);
|
|
|
|
/**
|
|
* Increments the time stamp of the latest database modification,
|
|
* called automatically whenever revisions change.
|
|
*/
|
|
void databaseModified();
|
|
|
|
/** time stamp of latest database modification, for sleepSinceModification() */
|
|
Timespec m_modTimeStamp;
|
|
SyncMLStatus sleepSinceModification();
|
|
};
|
|
|
|
|
|
/**
|
|
* Common logging for sync sources.
|
|
*
|
|
* This class wraps the Synthesis DB functors that were set before
|
|
* calling its init() method. The wrappers then log a single line
|
|
* describing what is happening (adding/updating/removing)
|
|
* to which item (with a short item specific description extracted
|
|
* from the incoming item data or the backend).
|
|
*/
|
|
class SyncSourceLogging : public virtual SyncSourceBase
|
|
{
|
|
public:
|
|
/**
|
|
* wrap Synthesis DB Interface operations
|
|
*
|
|
* @param fields list of fields to read in getDescription()
|
|
* @param sep separator between non-empty fields
|
|
*/
|
|
void init(const std::list<std::string> &fields,
|
|
const std::string &sep,
|
|
SyncSource::Operations &ops);
|
|
|
|
/**
|
|
* Extract short description from Synthesis item data.
|
|
* The default implementation reads a list of fields
|
|
* as strings and concatenates the non-empty ones
|
|
* with a separator.
|
|
*
|
|
* @param aItemKey key for reading fields
|
|
* @return description, empty string will cause the ID of the item to be printed
|
|
*/
|
|
virtual std::string getDescription(sysync::KeyH aItemKey);
|
|
|
|
/**
|
|
* Extract short description from backend.
|
|
* Necessary for deleted items. The default implementation
|
|
* returns an empty string, so that implementing this
|
|
* is optional.
|
|
*
|
|
* @param luid LUID of the item to be deleted in the backend
|
|
* @return description, empty string will cause the ID of the item to be printed
|
|
*/
|
|
virtual std::string getDescription(const string &luid);
|
|
|
|
private:
|
|
std::list<std::string> m_fields;
|
|
std::string m_sep;
|
|
|
|
SyncMLStatus insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID);
|
|
SyncMLStatus updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID);
|
|
SyncMLStatus deleteItem(sysync::cItemID aID);
|
|
};
|
|
|
|
/**
|
|
* Implements Load/SaveAdminData and MapItem handling in a SyncML
|
|
* server. Uses a single property for the admin data in the "internal"
|
|
* node and a complete node for the map items.
|
|
*/
|
|
class SyncSourceAdmin : public virtual SyncSourceBase
|
|
{
|
|
boost::shared_ptr<ConfigNode> m_configNode;
|
|
std::string m_adminPropertyName;
|
|
boost::shared_ptr<ConfigNode> m_mappingNode;
|
|
bool m_mappingLoaded;
|
|
|
|
ConfigProps m_mapping;
|
|
ConfigProps::const_iterator m_mappingIterator;
|
|
|
|
sysync::TSyError loadAdminData(const char *aLocDB,
|
|
const char *aRemDB,
|
|
char **adminData);
|
|
sysync::TSyError saveAdminData(const char *adminData);
|
|
bool readNextMapItem(sysync::MapID mID, bool aFirst);
|
|
sysync::TSyError insertMapItem(sysync::cMapID mID);
|
|
sysync::TSyError updateMapItem(sysync::cMapID mID);
|
|
sysync::TSyError deleteMapItem(sysync::cMapID mID);
|
|
SyncMLStatus flush();
|
|
|
|
void resetMap();
|
|
void mapid2entry(sysync::cMapID mID, string &key, string &value);
|
|
void entry2mapid(const string &key, const string &value, sysync::MapID mID);
|
|
|
|
public:
|
|
/** flexible initialization */
|
|
void init(SyncSource::Operations &ops,
|
|
const boost::shared_ptr<ConfigNode> &config,
|
|
const std::string &adminPropertyName,
|
|
const boost::shared_ptr<ConfigNode> &mapping);
|
|
|
|
/**
|
|
* simpler initialization, using the default placement of data
|
|
* inside the SyncSourceConfig base class
|
|
*/
|
|
void init(SyncSource::Operations &ops, SyncSource *source);
|
|
};
|
|
|
|
/**
|
|
* Implements Read/Write/DeleteBlob. Blobs are stored inside a
|
|
* configurable directory, which has to be unique for the current
|
|
* peer.
|
|
*/
|
|
class SyncSourceBlob : public virtual SyncSourceBase
|
|
{
|
|
/**
|
|
* Only one blob is active at a time.
|
|
* This utility class provides the actual implementation.
|
|
*/
|
|
sysync::TBlob m_blob;
|
|
|
|
sysync::TSyError readBlob(sysync::cItemID aID, const char *aBlobID,
|
|
void **aBlkPtr, size_t *aBlkSize,
|
|
size_t *aTotSize,
|
|
bool aFirst, bool *aLast) {
|
|
// Translate between sysync::memSize and size_t, which
|
|
// is different on s390 (or at least the compiler complains...).
|
|
sysync::memSize blksize = aBlkSize ? static_cast<sysync::memSize>(*aBlkSize) : 0,
|
|
totsize = aTotSize ? static_cast<sysync::memSize>(*aTotSize) : 0;
|
|
sysync::TSyError err = m_blob.ReadBlob(aID, aBlobID, aBlkPtr,
|
|
&blksize,
|
|
&totsize,
|
|
aFirst, aLast);
|
|
if (aBlkSize) {
|
|
*aBlkSize = blksize;
|
|
}
|
|
if (aTotSize) {
|
|
*aTotSize = totsize;
|
|
}
|
|
return err;
|
|
}
|
|
sysync::TSyError writeBlob(sysync::cItemID aID, const char *aBlobID,
|
|
void *aBlkPtr, size_t aBlkSize,
|
|
size_t aTotSize,
|
|
bool aFirst, bool aLast) {
|
|
mkdir_p(m_blob.getBlobPath());
|
|
return m_blob.WriteBlob(aID, aBlobID, aBlkPtr, aBlkSize, aTotSize, aFirst, aLast);
|
|
}
|
|
sysync::TSyError deleteBlob(sysync::cItemID aID, const char *aBlobID) {
|
|
return m_blob.DeleteBlob(aID, aBlobID);
|
|
}
|
|
|
|
sysync::TSyError loadAdminData(sysync::cItemID aID, const char *aBlobID,
|
|
void **aBlkPtr, size_t *aBlkSize, size_t *aTotSize,
|
|
bool aFirst, bool *aLast);
|
|
|
|
public:
|
|
void init(SyncSource::Operations &ops,
|
|
const std::string &dir);
|
|
};
|
|
|
|
|
|
/**
|
|
* This is an interface definition that is expected by the client-test
|
|
* program. Part of the reason for this requirement is that the test
|
|
* program was originally written for the Funambol SyncSource API.
|
|
* The other reason is that the testing is based on importing/exporting
|
|
* items in the internal format of the sync source, which has to be
|
|
* text based or even MIMEDIR based (for tests involving synccompare).
|
|
*/
|
|
class TestingSyncSource : public SyncSource,
|
|
virtual public SyncSourceSession,
|
|
virtual public SyncSourceChanges,
|
|
virtual public SyncSourceDelete,
|
|
virtual public SyncSourceSerialize {
|
|
public:
|
|
TestingSyncSource(const SyncSourceParams ¶ms) :
|
|
SyncSource(params)
|
|
{
|
|
SyncSourceSession::init(m_operations);
|
|
SyncSourceChanges::init(m_operations);
|
|
SyncSourceDelete::init(m_operations);
|
|
SyncSourceSerialize::init(m_operations);
|
|
}
|
|
~TestingSyncSource() {}
|
|
|
|
virtual InitState<SourceType> getSourceType() const { return SyncSourceConfig::getSourceType(); }
|
|
|
|
virtual void removeAllItems();
|
|
};
|
|
|
|
|
|
SE_END_CXX
|
|
#endif // INCL_SYNCSOURCE
|