- adapted to new SyncClient API: configuration tree handling is now part

of the library
- do not reset anchors after failure: allows to proceed with two-way sync


git-svn-id: https://zeitsenke.de/svn/SyncEvolution/trunk@196 15ad00c4-1369-45f4-8270-35d70d36bdcd
This commit is contained in:
Patrick Ohly 2006-08-06 07:56:41 +00:00
parent 90e5393a6d
commit 8199a7b38c
7 changed files with 177 additions and 148 deletions

34
NEWS
View File

@ -1,20 +1,32 @@
SyncEvolution xxx, ????
-----------------------
* C++ client library revision "funambol30ga": the user
agent string can now be modified in the spds/syncml/config.txt,
but it is recommended to not set it explicitly. Then
SyncEvolution will automatically insert its current
version.
* patches on top of "funambol30ga" to make the client
library compatible with additional servers
* fix for library #305795: for tasks the "text/x-todo" type
from the configuration was sent to servers instead of the
correct "text/calendar" provided by SyncEvolution itself
* C++ client library revision "funambol30ga" plus the patches
stored in its ".patches" directory:
- the user agent string can now be modified in the
spds/syncml/config.txt, but it is recommended to not set
it explicitly. Then SyncEvolution will automatically insert
its current version.
- now compatible with additional servers (fixed some SyncML
protocol issues, added support for sending device
information)
- revised API of the client library
- #305795: for tasks the "text/x-todo" type from the configuration
was sent to servers instead of the correct "text/calendar"
provided by SyncEvolution itself
* updated default syncml/config.txt: firstTimeSyncMode has never
been implemented in the library, removed its documentation
* changed vCard 2.1 parser to make it compatible with servers
* changed vCard parser to make it compatible with servers
which send a verbatim semicolon as part of properties where
the semicolon has no special meaning
* If minor errors occur like not being able to insert an
item at the client or server side, then it is reported in the
log and output, but the next synchronization will be a normal
synchronization, not a forced slow one as in previous versions.
The old approach ensured that the problem was noticed and fixed,
but required user assistance. With the new approach synchronization
continues to work, although without fixing the root cause of
the problem.
SyncEvolution 0.3, 2006-06-27

23
README
View File

@ -123,17 +123,18 @@ encountered and print a summary of how the databases were modified.
This is done with the "synccompare" utility script described
in the "Exchanging Data" section.
In case of an error the synchronization run is aborted prematurely and
SyncEvolution will return a non-zero value. Recovery from failed
synchronization is done by forcing a full synchronization during the
next run, i.e. by sending all items and letting the SyncML server
compare against the ones it already knows. If the error is detected
by SyncEvolution itself, then it attempts to cause the full
synchronization only for the failed database, but if the SyncML
client library itself runs into a problem it might cause a full
synchronization of all databases even if only one of them failed.
Therefore it might be less risky to invoke SyncEvolution for each
data source separately.
In case of a severe error the synchronization run is aborted
prematurely and SyncEvolution will return a non-zero value. Recovery
from failed synchronization is done by forcing a full synchronization
during the next run, i.e. by sending all items and letting the SyncML
server compare against the ones it already knows. This is avoided
whenever possible because matching items during a slow synchronization
can lead to duplicate entries.
Due to a limitation in the client library implementation it might
force a slow synchronization of all databases even if only one of them
failed. Therefore it might be less risky to invoke SyncEvolution for
each data source separately.
After a successful synchronization the server's configuration file is
updated so that the next run can be done incrementally. If the

View File

@ -38,17 +38,20 @@ using namespace std;
#include <dirent.h>
#include <errno.h>
EvolutionSyncClient::EvolutionSyncClient(const string &server, const set<string> &sources) :
EvolutionSyncClient::EvolutionSyncClient(const string &server, SyncMode syncMode,
bool doLogging, const set<string> &sources) :
m_server(server),
m_sources(sources),
m_configPath(string("evolution/") + server)
m_syncMode(syncMode),
m_doLogging(doLogging),
m_configPath(string("evolution/") + server),
m_sourceList(NULL)
{
setDMConfig(m_configPath.c_str());
}
EvolutionSyncClient::~EvolutionSyncClient()
{
Sync4jClient::dispose();
}
/// remove all files in the given directory and the directory itself
@ -368,48 +371,83 @@ public:
}
};
void EvolutionSyncClient::sync(SyncMode syncMode, bool doLogging)
int EvolutionSyncClient::sync()
{
SourceList sources(m_server, doLogging);
// this will trigger the prepareSync(), createSyncSource(), beginSource() callbacks
int res = SyncClient::sync();
DMTree config(m_configPath.c_str());
// find server URL (part of change id)
string serverPath = m_configPath + "/spds/syncml";
auto_ptr<ManagementNode> serverNode(config.getManagementNode(serverPath.c_str()));
string url = EvolutionSyncSource::getPropertyValue(*serverNode, "syncURL");
// redirect logging as soon as possible
sources.setLogdir(serverNode->getPropertyValue("logdir"),
atoi(serverNode->getPropertyValue("maxlogdirs")));
// find sources
string sourcesPath = m_configPath + "/spds/sources";
auto_ptr<ManagementNode> sourcesNode(config.getManagementNode(sourcesPath.c_str()));
unsigned int index, numSources = sourcesNode->getChildrenMaxCount();
char **sourceNamesPtr = sourcesNode->getChildrenNames();
// copy source names into format that will be
// freed in case of exception
vector<string> sourceNames;
for ( index = 0; index < numSources; index++ ) {
sourceNames.push_back(sourceNamesPtr[index]);
delete [] sourceNamesPtr[index];
#if 0
// Change of policy: even if a sync source failed allow
// the next sync to proceed normally. The rationale is
// that novice users then do not have to care about deleting
// "bad" items because the next two-way sync will not stumble
// over them.
//
// Force slow sync in case of failed Evolution source
// by overwriting the last sync time stamp;
// don't do it if only the general result is a failure
// because in that case it is not obvious which source
// failed.
for ( index = 0; index < m_sourceList->size(); index++ ) {
EvolutionSyncSource *source = (*m_sourceList)[index];
if (source->hasFailed()) {
string sourcePath(sourcesPath + "/" + sourceArray[index]->getName());
auto_ptr<ManagementNode> sourceNode(config.getManagementNode(sourcePath.c_str()));
sourceNode->setPropertyValue("last", "0");
}
}
delete [] sourceNamesPtr;
// iterate over sources
for ( index = 0; index < numSources; index++ ) {
#endif
if (res) {
if (lastErrorCode && lastErrorMsg[0]) {
throw runtime_error(lastErrorMsg);
}
// no error code/description?!
throw runtime_error("sync failed without an error description, check log");
}
// all went well: print final report before cleaning up
m_sourceList->syncDone(true);
delete m_sourceList;
m_sourceList = NULL;
}
int EvolutionSyncClient::prepareSync(const AccessConfig &config,
ManagementNode &node)
{
try {
// remember for use by sync sources
m_url = config.getSyncURL() ? config.getSyncURL() : "";
// redirect logging as soon as possible
m_sourceList = new SourceList(m_server, m_doLogging);
m_sourceList->setLogdir(node.getPropertyValue("logdir"),
atoi(node.getPropertyValue("maxlogdirs")));
} catch(...) {
EvolutionSyncSource::handleException();
return ERR_UNSPECIFIED;
}
return ERR_NONE;
}
int EvolutionSyncClient::createSyncSource(const char *name,
const SyncSourceConfig &config,
ManagementNode &node,
SyncSource **source)
{
try {
// by default no source for this name
*source = NULL;
// is the source enabled?
string sourcePath(sourcesPath + "/" + sourceNames[index]);
auto_ptr<ManagementNode> sourceNode(config.getManagementNode(sourcePath.c_str()));
string sync = EvolutionSyncSource::getPropertyValue(*sourceNode, "sync");
string sync = config.getSync() ? config.getSync() : "";
bool enabled = sync != "none";
SyncMode overrideMode = SYNC_NONE;
// override state?
if (m_sources.size()) {
if (m_sources.find(sourceNames[index]) != m_sources.end()) {
if (m_sources.find(name) != m_sources.end()) {
if (!enabled) {
overrideMode = SYNC_TWO_WAY;
enabled = true;
@ -421,79 +459,54 @@ void EvolutionSyncClient::sync(SyncMode syncMode, bool doLogging)
if (enabled) {
// create it
string type = EvolutionSyncSource::getPropertyValue(*sourceNode, "type");
string type = config.getType() ? config.getType() : "";
EvolutionSyncSource *syncSource =
EvolutionSyncSource::createSource(
sourceNames[index],
string("sync4jevolution:") + url + "/" + EvolutionSyncSource::getPropertyValue(*sourceNode, "name"),
EvolutionSyncSource::getPropertyValue(*sourceNode, "evolutionsource"),
name,
string("sync4jevolution:") + m_url + "/" + name,
EvolutionSyncSource::getPropertyValue(node, "evolutionsource"),
type
);
if (!syncSource) {
throw runtime_error(sourceNames[index] + ": type " +
throw runtime_error(string(name) + ": type " +
( type.size() ? string("not configured") :
string("'") + type + "' empty or unknown" ));
}
m_sourceList->push_back(syncSource);
if (syncMode != SYNC_NONE) {
// configure it
syncSource->setConfig(config);
if (m_syncMode != SYNC_NONE) {
// caller overrides mode
syncSource->setFixedSyncMode(syncMode);
syncSource->setPreferredSyncMode(m_syncMode);
} else if (overrideMode != SYNC_NONE) {
// disabled source selected via source name
syncSource->setFixedSyncMode(overrideMode);
syncSource->setPreferredSyncMode(overrideMode);
}
sources.push_back(syncSource);
// also open it; failing now is still safe
syncSource->open();
// success!
*source = syncSource;
}
} catch(...) {
EvolutionSyncSource::handleException();
return ERR_UNSPECIFIED;
}
if (!sources.size()) {
throw runtime_error("no sources configured");
}
// ready to go: dump initial databases and prepare for final report
sources.syncPrepare();
// build array as sync wants it, then sync
// (no exceptions allowed here)
SyncSource **sourceArray = new SyncSource *[sources.size() + 1];
index = 0;
for ( list<EvolutionSyncSource *>::iterator it = sources.begin();
it != sources.end();
++it ) {
sourceArray[index] = *it;
++index;
}
sourceArray[index] = NULL;
int res = SyncClient::sync( sourceArray );
// force slow sync in case of failed Evolution source
// by overwriting the last sync time stamp;
// don't do it if only the general result is a failure
// because in that case it is not obvious which source
// failed
for ( index = 0; index < sources.size(); index++ ) {
if (sourceArray[index] &&
((EvolutionSyncSource *)sourceArray[index])->hasFailed()) {
string sourcePath(sourcesPath + "/" + sourceArray[index]->getName());
auto_ptr<ManagementNode> sourceNode(config.getManagementNode(sourcePath.c_str()));
sourceNode->setPropertyValue("last", "0");
}
}
delete [] sourceArray;
if (res) {
if (lastErrorCode && lastErrorMsg[0]) {
throw runtime_error(lastErrorMsg);
}
// no error code/description?!
throw runtime_error("sync() failed without setting an error description");
}
// all went well: print final report before cleaning up
sources.syncDone(true);
return ERR_NONE;
}
int EvolutionSyncClient::beginSync()
{
try {
// ready to go: dump initial databases and prepare for final report
m_sourceList->syncPrepare();
} catch(...) {
EvolutionSyncSource::handleException();
return ERR_UNSPECIFIED;
}
return ERR_NONE;
}

View File

@ -26,34 +26,45 @@
#include <set>
using namespace std;
class SourceList;
/*
* This is the main class inside sync4jevolution which
* looks at the configuration, activates all enabled
* sources and executes the synchronization.
*
* Despite the name it is not a Sync4jClient, but rather
* uses one.
*/
class EvolutionSyncClient : private SyncClient {
const string m_server;
const set<string> m_sources;
const SyncMode m_syncMode;
const bool m_doLogging;
const string m_configPath;
string m_url;
/*
* variable shared by sync() and prepareSync():
* stores active sync sources and handles reporting
*/
SourceList *m_sourceList;
public:
/**
* @param server identifies the server config to be used
* @param syncMode setting this overrides the sync mode from the config
* @param doLogging write additional log and datatbase files about the sync
*/
EvolutionSyncClient(const string &server, const set<string> &sources = set<string>());
EvolutionSyncClient(const string &server,
SyncMode syncMode = SYNC_NONE,
bool doLogging = false,
const set<string> &sources = set<string>());
~EvolutionSyncClient();
/**
* Executes the sync, throws an exception in case of failure.
* Handles automatic backups and report generation.
*
* @param syncMode setting this overrides the sync mode from the config
* @param doLogging write additional log and datatbase files about the sync
*/
void sync(SyncMode syncMode = SYNC_NONE, bool doLogging = false);
int sync();
/**************************************************/
/************ override SyncClient interface *******/
@ -64,6 +75,16 @@ class EvolutionSyncClient : private SyncClient {
const char *getManufacturer() const { return "Patrick Ohly"; }
const char *getClientType() const { return "workstation"; }
int isUTC() const { return true; }
protected:
/* Sync4jClient callbacks */
int prepareSync(const AccessConfig &config,
ManagementNode &node);
int createSyncSource(const char *name,
const SyncSourceConfig &config,
ManagementNode &node,
SyncSource **source);
int beginSync();
};
#endif // INCL_EVOLUTIONSYNCCLIENT

View File

@ -73,8 +73,7 @@ class EvolutionSyncSource : public SyncSource
m_updatedItems( *this, "updated", SYNC_STATE_UPDATED ),
m_deletedItems( *this, "deleted", SYNC_STATE_DELETED ),
m_hasFailed( false ),
m_isModified( false ),
m_fixedSyncMode( SYNC_NONE )
m_isModified( false )
{}
virtual ~EvolutionSyncSource() {}
@ -189,18 +188,6 @@ class EvolutionSyncSource : public SyncSource
virtual SyncItem* getFirstItemKey() { return getFirstItem(); }
virtual SyncItem* getNextItemKey() { return getNextItem(); }
// if the SyncSource was fixed to a certain mode, then override
// the configuration in prepareSync()
virtual int prepareSync() {
if (m_fixedSyncMode != SYNC_NONE) {
setPreferredSyncMode(m_fixedSyncMode);
}
return 0;
}
void setFixedSyncMode(SyncMode sourceSyncMode) {
m_fixedSyncMode = sourceSyncMode;
}
virtual int beginSync();
virtual int endSync();
virtual void setItemStatus(const char *key, int status);
@ -330,9 +317,6 @@ class EvolutionSyncSource : public SyncSource
*/
bool m_isModified;
/** if not SYNC_NONE then override preferred sync mode in prepareSync() */
SyncMode m_fixedSyncMode;
private:
/**
* private wrapper function for add/delete/updateItemThrow()

View File

@ -1040,16 +1040,14 @@ template<class T> void TestEvolution<T>::doSync(const string &logfilesuffix, int
remove( logfile.c_str() );
setLogFile( logfile.c_str(), TRUE );
LOG.setLevel(LOG_LEVEL_INFO);
{
try {
set<string> sources;
sources.insert(m_source[config]);
EvolutionSyncClient client(m_syncConfigs[config], sources);
try {
client.sync(syncMode);
} catch(...) {
EvolutionSyncSource::handleException();
res = 1;
}
EvolutionSyncClient client(m_syncConfigs[config], syncMode, false, sources);
client.sync();
} catch(...) {
EvolutionSyncSource::handleException();
res = 1;
}
string oldlogfile = getCurrentTest() + ".log";
simplifyFilename(oldlogfile);

View File

@ -95,8 +95,8 @@ int main( int argc, char **argv )
sources.insert(argv[source]);
}
EvolutionSyncClient client(argv[1], sources);
client.sync(SYNC_NONE, true);
EvolutionSyncClient client(argv[1], SYNC_NONE, true, sources);
client.sync();
}
return 0;
} catch ( const std::exception &ex ) {