implemented full change tracking in demo SQLiteContactSource with new TrackingSyncSource base class

git-svn-id: https://zeitsenke.de/svn/SyncEvolution/trunk@486 15ad00c4-1369-45f4-8270-35d70d36bdcd
This commit is contained in:
Patrick Ohly 2008-01-14 21:25:03 +00:00
parent d68488c0be
commit 1ccbd695ee
9 changed files with 413 additions and 143 deletions

View File

@ -263,7 +263,12 @@ EvolutionSyncSource *EvolutionSyncSource::createSource(
#endif
} else if (mimeType == "sqlite") {
#ifdef ENABLE_SQLITE
return new SQLiteContactSource(name, sc, strippedChangeId, id);
arrayptr<char> configNodeName(node ? node->createFullName() : wstrdup(""));
string trackingNodeName = configNodeName.get();
trackingNodeName += "/changes";
eptr<spdm::DeviceManagementNode> trackingNode(new spdm::DeviceManagementNode(trackingNodeName.c_str()), "tracking node");
return new SQLiteContactSource(name, sc, strippedChangeId, id, trackingNode);
#else
if (error) {
EvolutionSyncClient::throwError(name + ": access to sqlite not compiled into this binary, " + mimeType + " not supported");
@ -271,7 +276,6 @@ EvolutionSyncSource *EvolutionSyncSource::createSource(
#endif
} else if (mimeType == "addressbook") {
#ifdef ENABLE_ADDRESSBOOK
arrayptr<char> configNodeName(node ? node->createFullName() : wstrdup(""));
return new AddressBookSource(name, sc, strippedChangeId, id, string(configNodeName));
#else
if (error) {

View File

@ -62,6 +62,8 @@ SYNCEBOOK_SOURCES = \
EvolutionContactSource.cpp
SYNCSQLITE_SOURCES = \
TrackingSyncSource.h \
TrackingSyncSource.cpp \
SQLiteUtil.h \
SQLiteUtil.cpp \
SQLiteContactSource.h \

View File

@ -107,70 +107,13 @@ void SQLiteContactSource::close()
m_sqlite.close();
}
void SQLiteContactSource::beginSyncThrow(bool needAll,
bool needPartial,
bool deleteLocal)
void SQLiteContactSource::listAllItems(RevisionMap_t &revisions)
{
SQLiteUtil::syncml_time_t lastSyncTime = anchorToTimestamp(getLastAnchor());
eptr<sqlite3_stmt> all(m_sqlite.prepareSQL("SELECT ROWID, CreationDate, ModificationDate FROM ABPerson;"));
while (m_sqlite.checkSQL(sqlite3_step(all)) == SQLITE_ROW) {
string uid = m_sqlite.toString(SQLITE3_COLUMN_KEY(all, 0));
m_allItems.addItem(uid);
// find new and updated items by comparing their creation resp. modification time stamp
// against the end of the last sync
if (needPartial) {
SQLiteUtil::syncml_time_t creationTime = m_sqlite.getTimeColumn(all, 1);
SQLiteUtil::syncml_time_t modTime = m_sqlite.getTimeColumn(all, 2);
if (creationTime > lastSyncTime) {
m_newItems.addItem(uid);
} else if (modTime > lastSyncTime) {
m_updatedItems.addItem(uid);
}
}
}
// TODO: find deleted items
// all modifications from now on will be rolled back on an error:
// if the server does the same, theoretically client and server
// could restart with a two-way sync
//
// TODO: currently syncevolution resets the last anchor in case of
// a failure and thus forces a slow sync - avoid that for SQLite
// database sources
eptr<sqlite3_stmt> start(m_sqlite.prepareSQL("BEGIN TRANSACTION;"));
m_sqlite.checkSQL(sqlite3_step(start));
if (deleteLocal) {
for (itemList::const_iterator it = m_allItems.begin();
it != m_allItems.end();
it++) {
deleteItemThrow(*it);
}
m_allItems.clear();
}
}
void SQLiteContactSource::endSyncThrow()
{
// complete the transaction started in beginSyncThrow()
eptr<sqlite3_stmt> end(m_sqlite.prepareSQL("COMMIT;"));
m_sqlite.checkSQL(sqlite3_step(end));
}
void SQLiteContactSource::exportData(ostream &out)
{
eptr<sqlite3_stmt> all(m_sqlite.prepareSQL("SELECT ROWID FROM ABPerson;"));
while (m_sqlite.checkSQL(sqlite3_step(all)) == SQLITE_ROW) {
string uid = m_sqlite.toString(SQLITE3_COLUMN_KEY(all, 1));
auto_ptr<SyncItem> item(createItem(uid));
out << item->getData();
out << "\n";
string modTime = m_sqlite.time2str(m_sqlite.getTimeColumn(all, 2));
revisions.insert(RevisionMap_t::value_type(uid, modTime));
}
}
@ -222,27 +165,12 @@ SyncItem *SQLiteContactSource::createItem(const string &uid)
return item.release();
}
int SQLiteContactSource::addItemThrow(SyncItem& item)
string SQLiteContactSource::insertItem(string &uid, const SyncItem &item)
{
return insertItemThrow(item, NULL, "");
}
int SQLiteContactSource::updateItemThrow(SyncItem& item)
{
// Make sure that there is no contact with this uid,
// then insert the new data. If there was no such uid,
// then this behaves like an add.
string creationTime = m_sqlite.findColumn("ABPerson", "ROWID", item.getKey(), "CreationDate", "");
deleteItemThrow(item.getKey());
return insertItemThrow(item, item.getKey(), creationTime);
}
int SQLiteContactSource::insertItemThrow(SyncItem &item, const char *uid, const string &creationTime)
{
string data(getData(item));
std::auto_ptr<VObject> vobj(VConverter::parse((char *)data.c_str()));
string creationTime;
std::auto_ptr<VObject> vobj(VConverter::parse((char *)((SyncItem &)item).getData()));
if (vobj.get() == 0) {
throwError(string("parsing contact ") + item.getKey());
throwError(string("parsing contact ") + ((SyncItem &)item).getKey());
}
vobj->toNativeEncoding();
@ -288,17 +216,27 @@ int SQLiteContactSource::insertItemThrow(SyncItem &item, const char *uid, const
transform(lastsort.begin(), lastsort.end(), lastsort.begin(), ::toupper);
// optional fixed UID, potentially fixed creation time
if (uid) {
if (uid.size()) {
creationTime = m_sqlite.findColumn("ABPerson", "ROWID", uid.c_str(), "CreationDate", "");
cols << ", ROWID";
values << ", ?";
}
cols << ", CreationDate, ModificationDate";
values << ", ?, ?";
// delete complete row so that we can recreate it
if (uid.size()) {
eptr<sqlite3_stmt> remove(m_sqlite.prepareSQL("DELETE FROM ABPerson WHERE ROWID == ?;"));
m_sqlite.checkSQL(sqlite3_bind_text(remove, 1, uid.c_str(), -1, SQLITE_TRANSIENT));
m_sqlite.checkSQL(sqlite3_step(remove));
}
string cols_str = cols.str();
string values_str = values.str();
eptr<sqlite3_stmt> insert(m_sqlite.prepareSQL("INSERT INTO ABPerson( %s ) "
"VALUES( %s );",
cols.str().c_str(),
values.str().c_str()));
"VALUES( %s );",
cols_str.c_str(),
values_str.c_str()));
// now bind parameter values in the same order as the columns specification above
int param = 1;
@ -307,30 +245,27 @@ int SQLiteContactSource::insertItemThrow(SyncItem &item, const char *uid, const
m_sqlite.checkSQL(sqlite3_bind_text(insert, param++, last.c_str(), -1, SQLITE_TRANSIENT));
m_sqlite.checkSQL(sqlite3_bind_text(insert, param++, lastsort.c_str(), -1, SQLITE_TRANSIENT));
m_sqlite.checkSQL(sqlite3_bind_text(insert, param++, firstsort.c_str(), -1, SQLITE_TRANSIENT));
if (uid) {
m_sqlite.checkSQL(sqlite3_bind_text(insert, param++, uid, -1, SQLITE_TRANSIENT));
if (uid.size()) {
m_sqlite.checkSQL(sqlite3_bind_text(insert, param++, uid.c_str(), -1, SQLITE_TRANSIENT));
m_sqlite.checkSQL(sqlite3_bind_text(insert, param++, creationTime.c_str(), -1, SQLITE_TRANSIENT));
} else {
m_sqlite.checkSQL(sqlite3_bind_int64(insert, param++, (long long)time(NULL)));
}
m_sqlite.checkSQL(sqlite3_bind_int64(insert, param++, (long long)time(NULL)));
SQLiteUtil::syncml_time_t modificationTime = time(NULL);
m_sqlite.checkSQL(sqlite3_bind_int64(insert, param++, modificationTime));
m_sqlite.checkSQL(sqlite3_step(insert));
if (!uid) {
if (!uid.size()) {
// figure out which UID was assigned to the new contact
string newuid = m_sqlite.findColumn("SQLITE_SEQUENCE", "NAME", "ABPerson", "SEQ", "");
item.setKey(newuid.c_str());
uid = m_sqlite.findColumn("SQLITE_SEQUENCE", "NAME", "ABPerson", "SEQ", "");
}
return STC_OK;
return m_sqlite.time2str(modificationTime);
}
int SQLiteContactSource::deleteItemThrow(const string &uid)
void SQLiteContactSource::deleteItem(const string &uid)
{
int status = STC_OK;
// delete address field members of contact
eptr<sqlite3_stmt> del(m_sqlite.prepareSQL("DELETE FROM ABMultiValueEntry "
"WHERE ABMultiValueEntry.parent_id IN "
@ -350,13 +285,16 @@ int SQLiteContactSource::deleteItemThrow(const string &uid)
"ABPerson.ROWID = ?;"));
m_sqlite.checkSQL(sqlite3_bind_text(del, 1, uid.c_str(), -1, SQLITE_TRANSIENT));
m_sqlite.checkSQL(sqlite3_step(del));
return status;
}
int SQLiteContactSource::deleteItemThrow(SyncItem& item)
void SQLiteContactSource::flush()
{
return deleteItemThrow(item.getKey());
// Our change tracking is time based.
// Don't let caller proceed without waiting for
// one second to prevent being called again before
// the modification time stamp is larger than it
// is now.
sleep(1);
}
void SQLiteContactSource::logItem(const string &uid, const string &info, bool debug)

View File

@ -20,7 +20,7 @@
#ifndef INCL_SQLITECONTACTSOURCE
#define INCL_SQLITECONTACTSOURCE
#include "EvolutionSyncSource.h"
#include "TrackingSyncSource.h"
#include "SQLiteUtil.h"
#ifdef ENABLE_SQLITE
@ -32,11 +32,11 @@
* supported by SQLiteUtil, so only the properties which
* have a 1:1 mapping are currently stored.
*/
class SQLiteContactSource : public EvolutionSyncSource
class SQLiteContactSource : public TrackingSyncSource
{
public:
SQLiteContactSource( const string name, SyncSourceConfig *sc, const string &changeId, const string &id ) :
EvolutionSyncSource(name, sc, changeId, id)
SQLiteContactSource( const string name, SyncSourceConfig *sc, const string &changeId, const string &id, eptr<spdm::DeviceManagementNode> trackingNode) :
TrackingSyncSource(name, sc, changeId, id, trackingNode)
{}
protected:
@ -45,24 +45,19 @@ class SQLiteContactSource : public EvolutionSyncSource
virtual void close();
virtual sources getSyncBackends() { return sources(); }
virtual SyncItem *createItem(const string &uid);
virtual void exportData(ostream &out);
virtual string fileSuffix() { return "vcf"; }
virtual const char *getMimeType() { return "text/x-vcard:2.1"; }
virtual const char *getMimeVersion() { return "2.1"; }
virtual const char *getSupportedTypes() { return "text/vcard:3.0,text/x-vcard:2.1"; }
virtual ArrayElement* clone() { new SQLiteContactSource(getName(), &getConfig(), m_changeId, m_id); }
virtual void beginSyncThrow(bool needAll,
bool needPartial,
bool deleteLocal);
virtual void endSyncThrow();
virtual int addItemThrow(SyncItem& item);
virtual int updateItemThrow(SyncItem& item);
virtual int deleteItemThrow(SyncItem& item);
virtual void logItem(const string &uid, const string &info, bool debug = false);
virtual void logItem(SyncItem &item, const string &info, bool debug = false);
/* implementation of TrackingSyncSource interface */
virtual void listAllItems(RevisionMap_t &revisions);
virtual string insertItem(string &uid, const SyncItem &item);
virtual void deleteItem(const string &uid);
virtual void flush();
private:
/** encapsulates access to database */
SQLiteUtil m_sqlite;
@ -77,16 +72,6 @@ class SQLiteContactSource : public EvolutionSyncSource
m_typeMobile,
m_typeHome,
m_typeWork;
/** same as deleteItemThrow() but works with the uid directly */
virtual int deleteItemThrow(const string &uid);
/**
* inserts the contact under a specific UID (if given) or
* adds under a new UID
*/
virtual int insertItemThrow(SyncItem &item, const char *uid, const string &creationTime);
};
#endif // ENABLE_SQLITE

View File

@ -99,6 +99,13 @@ SQLiteUtil::syncml_time_t SQLiteUtil::getTimeColumn(sqlite3_stmt *stmt, int col)
return sqlite3_column_int64(stmt, col);
}
string SQLiteUtil::time2str(SQLiteUtil::syncml_time_t t)
{
char buffer[128];
sprintf(buffer, "%lld", t);
return buffer;
}
void SQLiteUtil::rowToVObject(sqlite3_stmt *stmt, vocl::VObject &vobj)
{
const unsigned char *text;

View File

@ -118,6 +118,9 @@ class SQLiteUtil
/** transform column to same time base as used by SyncML libary (typically time()) */
syncml_time_t getTimeColumn(sqlite3_stmt *stmt, int col);
/** convert time to string */
static string time2str(syncml_time_t t);
/** copies all columns which directly map to a property into the vobj */
void rowToVObject(sqlite3_stmt *stmt, vocl::VObject &vobj);

146
src/TrackingSyncSource.cpp Normal file
View File

@ -0,0 +1,146 @@
/*
* Copyright (C) 2008 Patrick Ohly
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "TrackingSyncSource.h"
TrackingSyncSource::TrackingSyncSource(const string &name, SyncSourceConfig *sc, const string &changeId, const string &id,
eptr<spdm::DeviceManagementNode> trackingNode) :
EvolutionSyncSource(name, sc, changeId, id),
m_trackingNode(trackingNode)
{
m_trackingNode->setAutosave(false);
}
void TrackingSyncSource::beginSyncThrow(bool needAll,
bool needPartial,
bool deleteLocal)
{
RevisionMap_t revisions;
listAllItems(revisions);
for (RevisionMap_t::const_iterator it = revisions.begin();
it != revisions.end();
it++) {
const string &uid = it->first;
const string &revision = it->second;
if (deleteLocal) {
deleteItem(uid);
m_trackingNode->removeProperty(uid.c_str());
} else {
// always remember the item, need full list to find deleted items
m_allItems.addItem(uid);
if (needPartial) {
arrayptr<char> serverRevision(m_trackingNode->readPropertyValue(uid.c_str()));
if (!serverRevision || !serverRevision[0]) {
m_newItems.addItem(uid);
m_trackingNode->setPropertyValue(uid.c_str(), revision.c_str());
} else {
if (revision != serverRevision.get()) {
m_updatedItems.addItem(uid);
m_trackingNode->setPropertyValue(uid.c_str(), revision.c_str());
}
}
}
}
}
if (needPartial) {
ArrayList uids;
ArrayList modTimes;
m_trackingNode->readProperties(&uids, &modTimes);
for (int i = 0; i < uids.size(); i++ ) {
const StringBuffer *uid = (StringBuffer *)uids[i];
if (m_allItems.find(uid->c_str()) == m_allItems.end()) {
m_deletedItems.addItem(uid->c_str());
m_trackingNode->removeProperty(uid->c_str());
}
}
}
if (!needAll) {
// did not need full list after all...
m_allItems.clear();
}
}
void TrackingSyncSource::endSyncThrow()
{
// store changes persistently
flush();
if (!hasFailed()) {
m_trackingNode->update(false);
} else {
// SyncEvolution's error handling for failed sources
// forces a slow sync the next time. Therefore the
// content of the tracking node is irrelevant in
// case of a failure.
}
}
void TrackingSyncSource::exportData(ostream &out)
{
RevisionMap_t revisions;
listAllItems(revisions);
for (RevisionMap_t::const_iterator it = revisions.begin();
it != revisions.end();
it++) {
const string &uid = it->first;
eptr<SyncItem> item(createItem(uid), "sync item");
out << (char *)item->getData() << "\n";
}
}
int TrackingSyncSource::addItemThrow(SyncItem& item)
{
string uid;
string revision = insertItem(uid, item);
item.setKey(uid.c_str());
m_trackingNode->setPropertyValue(uid.c_str(), revision.c_str());
return STC_OK;
}
int TrackingSyncSource::updateItemThrow(SyncItem& item)
{
const string olduid = item.getKey();
string newuid = olduid;
string revision = insertItem(newuid, item);
if (olduid != newuid) {
m_trackingNode->removeProperty(olduid.c_str());
}
item.setKey(newuid.c_str());
m_trackingNode->setPropertyValue(newuid.c_str(), revision.c_str());
return STC_OK;
}
int TrackingSyncSource::deleteItemThrow(SyncItem& item)
{
const string uid = item.getKey();
deleteItem(uid);
m_trackingNode->removeProperty(uid.c_str());
return STC_OK;
}
void TrackingSyncSource::setItemStatusThrow(const char *uid, int status)
{
}

190
src/TrackingSyncSource.h Normal file
View File

@ -0,0 +1,190 @@
/*
* Copyright (C) 2008 Patrick Ohly
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef INCL_TRACKINGSYNCSOURCE
#define INCL_TRACKINGSYNCSOURCE
#include "EvolutionSyncSource.h"
#include "DeviceManagementNode.h"
#include <string>
#include <map>
using namespace std;
/**
* This class implements change tracking. Data sources which want to use
* this functionality have to provide the following functionality
* by implementing the pure virtual functions below:
* - open() the data
* - enumerate all existing items
* - provide UID and "revision string"; both have to be simple
* strings (printable ASCII, no white spaces, no equal sign);
* the UID must remain *constant* when the user edits an item (it
* may change when SyncEvolution changes an item), whereas the
* revision string must *change* each time the item is changed
* by anyone
* - import/export/update single items
* - persistently store all changes in flush()
* - clean up in close()
*
* A derived class may (but doesn't have to) override additional
* functions to modify or replace the default implementations, e.g.:
* - dumping the complete database (export())
*
* Potential implementations of the revision string are:
* - a modification time stamp
* - a hash value of a textual representation of the item
* (beware, such a hash might change as the textual representation
* changes even though the item is unchanged)
*/
class TrackingSyncSource : public EvolutionSyncSource
{
public:
/**
* Creates a new tracking sync source.
*
* @param name the named needed by SyncSource
* @param sc obligatory config for this source, must remain valid throughout the lifetime of the source;
* may be NULL for unit testing
* @param changeId is used to track changes in the Evolution backend
* @param id identifies the backend; not specifying it makes this instance
* unusable for anything but listing backend databases
* @param trackingNode the management node which this instance shall use to store its state:
* ownership over the pointer has to be transferred to the source
*/
TrackingSyncSource(const string &name, SyncSourceConfig *sc, const string &changeId, const string &id,
eptr<spdm::DeviceManagementNode> trackingNode);
/**
* returns a list of all know sources for the kind of items
* supported by this sync source
*/
virtual sources getSyncBackends() = 0;
/**
* Actually opens the data source specified in the constructor,
* will throw the normal exceptions if that fails. Should
* not modify the state of the sync source: that can be deferred
* until the server is also ready and beginSync() is called.
*/
virtual void open() = 0;
/**
* exports all items one after the other, separated by blank line;
* if that format is not suitable, then the derived class must
* override this call
*/
virtual void exportData(ostream &out);
typedef map<string, string> RevisionMap_t;
/**
* fills the complete mapping from UID to revision string of all
* currently existing items
*/
virtual void listAllItems(RevisionMap_t &revisions) = 0;
/**
* Create or modify an item.
*
* The sync source should be flexible: if the UID is non-empty,
* it should try to modify the item. If the item is not found,
* a new one should be created. The UID may be changed both when
* creating as well as when modifying an item.
*
* Errors are signalled by throwing an exception.
*
* @param uid in: identifies the item to be modified, empty for creating;
* out: UID after the operation
* @param item contains the new content of the item and its MIME type
* @return the new revision string
*/
virtual string insertItem(string &uid, const SyncItem &item) = 0;
/**
* Extract information for the item identified by UID
* and store it in a new SyncItem. The caller must
* free that item. May throw exceptions.
*
* @param uid identifies the item
*/
virtual SyncItem *createItem(const string &uid) = 0;
/**
* removes and item
*/
virtual void deleteItem(const string &uid) = 0;
/**
* write all changes, throw error if that fails
*/
virtual void flush() = 0;
/**
* 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;
/**
* file suffix for database files
*/
virtual string fileSuffix() = 0;
/**
* the actual type used by the source for items
*/
virtual const char *getMimeType() = 0;
/**
* the actual version of the mime specification
*/
virtual const char *getMimeVersion() = 0;
/**
* supported data types for send and receive,
* in the format "type1:version1,type2:version2,..."
*/
virtual const char *getSupportedTypes() = 0;
protected:
/** log a one-line info about an item */
virtual void logItem(const string &uid, const string &info, bool debug = false) = 0;
virtual void logItem(SyncItem &item, const string &info, bool debug = false) = 0;
private:
/* implementations of EvolutionSyncSource callbacks */
virtual void beginSyncThrow(bool needAll,
bool needPartial,
bool deleteLocal);
virtual void endSyncThrow();
virtual void setItemStatusThrow(const char *key, int status);
virtual int addItemThrow(SyncItem& item);
virtual int updateItemThrow(SyncItem& item);
virtual int deleteItemThrow(SyncItem& item);
/** cannot be cloned because clones would have to coordinate access to change tracking */
ArrayElement *clone() { return NULL; }
eptr<spdm::DeviceManagementNode> m_trackingNode;
};
#endif // INCL_TRACKINGSYNCSOURCE

View File

@ -135,6 +135,8 @@ public:
T("dummy", NULL, changeID, database) {}
TestEvolutionSyncSource(string changeID, string database, string configPath) :
T("dummy", NULL, changeID, database, configPath) {}
TestEvolutionSyncSource(string changeID, string database, eptr<spdm::DeviceManagementNode> trackingNode) :
T("dummy", NULL, changeID, database, trackingNode) {}
virtual int beginSync() {
CPPUNIT_ASSERT_NO_THROW(T::open());
@ -577,22 +579,15 @@ private:
break;
case TEST_SQLITE_CONTACT_SOURCE:
#ifdef ENABLE_SQLITE
ss = new TestEvolutionSyncSource<SQLiteContactSource>(changeID, database);
// this is a hack: it guesses the last sync time stamp by remembering
// the last time the sync source was created
static time_t lastts[TEST_MAX_SOURCE];
char anchor[DIM_ANCHOR];
time_t nextts;
timestampToAnchor(lastts[type], anchor);
ss->setLastAnchor(anchor);
nextts = time(NULL);
while (lastts[type] == nextts) {
sleep(1);
nextts = time(NULL);
}
lastts[type] = nextts;
{
string trackingNodePath = string("client-test-changes/") +
((TestEvolution &)client).getSourceName(type) +
"_" +
(isSourceA ? "1" : "2");
eptr<spdm::DeviceManagementNode> trackingNode(new spdm::DeviceManagementNode(trackingNodePath.c_str()), "tracking node");
ss = new TestEvolutionSyncSource<SQLiteContactSource>(changeID, database, trackingNode);
}
#endif
break;
case TEST_ADDRESS_BOOK_SOURCE: