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:
parent
d68488c0be
commit
1ccbd695ee
|
@ -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) {
|
||||
|
|
|
@ -62,6 +62,8 @@ SYNCEBOOK_SOURCES = \
|
|||
EvolutionContactSource.cpp
|
||||
|
||||
SYNCSQLITE_SOURCES = \
|
||||
TrackingSyncSource.h \
|
||||
TrackingSyncSource.cpp \
|
||||
SQLiteUtil.h \
|
||||
SQLiteUtil.cpp \
|
||||
SQLiteContactSource.h \
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
|
@ -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
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue