syncevolution/src/syncevo/SyncContext.h

695 lines
22 KiB
C
Raw Normal View History

/*
* 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_EVOLUTIONSYNCCLIENT
#define INCL_EVOLUTIONSYNCCLIENT
#include <syncevo/SmartPtr.h>
#include <syncevo/SyncConfig.h>
#include <syncevo/SyncML.h>
#include <syncevo/SynthesisEngine.h>
#include <string>
#include <set>
#include <map>
#include <stdint.h>
using namespace std;
#include <boost/smart_ptr.hpp>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
class TransportAgent;
class SourceList;
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
class SyncSource;
struct SuspendFlags
{
syncevo-dbus-server + syncevolution: fixed signal handling and D-Bus suspend/abort/shutdown (MB#7555) This patch fixes signal handling and shutdown of syncevo-dbus-server when signals were received. This problems were found in combination with automated tests via test-dbus.py. Signals handled by the signal handlers in the core had no effect on syncevo-dbus-server, which called its own event loop once the suspended or aborted sync returned. Signals received while outside of a sync and outside of an event loop also had no effect. Now signals are always caught by the niam() function in syncevo-dbus-server. It uses the new SyncContext::handleSignal() API to tell the core engine about it and in addition, remembers that syncevo-dbus-server is meant to shut down (suspendRequested). This is then checked by the syncevo-dbus-server event loop (DBusServer::run()) before blocking again. The implementation of Session.Abort() and Session.Suspend() didn't work. isSuspend() and isAbort() mixed up the state. They also did not wake up DBusTransportAgent, so the syncevo-dbus-server might get stuck although an abort was already requested. Partially fixed by adding g_main_loop_quit(). It's not entirely sure whether g_main_loop_quit() is reentrant (see below). There is other code which uses it in signal handlers, but that doesn't mean that this is correct. The right long term solution would be: - avoid entering and leaving the event loop - feed signals into the event loop as normal events SoupTransportAgent solves this by polling for a signal once per second. This also should be improved. Because such code was used in several places for a connection, that common code was moved into Connection::wakeupSession(). Things which went in unnoticed when the original signal handling was merged: - SyncContext::getSuspendFlags() should not allow the caller to modify the SyncContext state. Made the returned SuspendFlags const. - A signal handler may only call re-entrant functions (Stevens, "Advanced Programming in a Unix Environment", 10.6). All printing therefore has to be done outside of the signal handlers. Now the signal handlers just set a message and the regular code checking for abort/suspend prints it. There's a slight race condition (a message might get overwritten before it is printed), but printing just the last message might actually make more sense. There might be a slight delay between receiving the signal and printing now.
2009-10-30 07:25:53 +01:00
/** SIGINT twice within this amount of seconds aborts the sync */
static const time_t ABORT_INTERVAL = 2;
enum CLIENT_STATE
{
CLIENT_NORMAL,
CLIENT_SUSPEND,
CLIENT_ABORT,
CLIENT_ILLEGAL
}state;
time_t last_suspend;
syncevo-dbus-server + syncevolution: fixed signal handling and D-Bus suspend/abort/shutdown (MB#7555) This patch fixes signal handling and shutdown of syncevo-dbus-server when signals were received. This problems were found in combination with automated tests via test-dbus.py. Signals handled by the signal handlers in the core had no effect on syncevo-dbus-server, which called its own event loop once the suspended or aborted sync returned. Signals received while outside of a sync and outside of an event loop also had no effect. Now signals are always caught by the niam() function in syncevo-dbus-server. It uses the new SyncContext::handleSignal() API to tell the core engine about it and in addition, remembers that syncevo-dbus-server is meant to shut down (suspendRequested). This is then checked by the syncevo-dbus-server event loop (DBusServer::run()) before blocking again. The implementation of Session.Abort() and Session.Suspend() didn't work. isSuspend() and isAbort() mixed up the state. They also did not wake up DBusTransportAgent, so the syncevo-dbus-server might get stuck although an abort was already requested. Partially fixed by adding g_main_loop_quit(). It's not entirely sure whether g_main_loop_quit() is reentrant (see below). There is other code which uses it in signal handlers, but that doesn't mean that this is correct. The right long term solution would be: - avoid entering and leaving the event loop - feed signals into the event loop as normal events SoupTransportAgent solves this by polling for a signal once per second. This also should be improved. Because such code was used in several places for a connection, that common code was moved into Connection::wakeupSession(). Things which went in unnoticed when the original signal handling was merged: - SyncContext::getSuspendFlags() should not allow the caller to modify the SyncContext state. Made the returned SuspendFlags const. - A signal handler may only call re-entrant functions (Stevens, "Advanced Programming in a Unix Environment", 10.6). All printing therefore has to be done outside of the signal handlers. Now the signal handlers just set a message and the regular code checking for abort/suspend prints it. There's a slight race condition (a message might get overwritten before it is printed), but printing just the last message might actually make more sense. There might be a slight delay between receiving the signal and printing now.
2009-10-30 07:25:53 +01:00
/**
* Simple string to print in SyncContext::printSignals().
* Set by SyncContext::handleSignal() when updating the
* global state. There's a slight race condition: if
* messages are set more quickly than they are printed,
* only the last message is printed.
*/
const char *message;
SuspendFlags():state(CLIENT_NORMAL),last_suspend(0),message(NULL)
{}
};
/**
* This is the main class inside SyncEvolution which
* looks at the configuration, activates all enabled
* sources and executes the synchronization.
*
* All interaction with the user (reporting progress, asking for
* passwords, ...) is done via virtual methods. The default
* implementation of those uses stdin/out.
*
*/
class SyncContext : public SyncConfig, public ConfigUserInterface {
/**
* the string used to request a config,
* *not* the normalized config name itself;
* for that use SyncConfig::getConfigName()
*/
const string m_server;
config: share properties between peers, configuration view without peer This patch makes the configuration layout with per-source and per-peer properties the default for new configurations. Migrating old configurations is not implemented. The command line has not been updated at all (MB #8048). The D-Bus API is fairly complete, only listing sessions independently of a peer is missing (MB #8049). The key concept of this patch is that a pseudo-node implemented by MultiplexConfigNode provides a view on all user-visible or hidden properties. Based on the property name, it looks up the property definition, picks one of the underlying nodes based on the property visibility and sharing attributes, then reads and writes the property via that node. Clearing properties is not needed and not implemented, because of the uncertain semantic (really remove shared properties?!). The "sync" property must be available both in the per-source config (to pick a backend independently of a specific peer) and in the per-peer configuration (to select a specific data format). This is solved by making the property special (SHARED_AND_UNSHARED flag) and then writing it into two nodes. Reading is done from the more specific per-peer node, with the other node acting as fallback. The MultiplexConfigNode has to implement the FilterConfigNode API because it is used as one by the code which sets passwords in the filter. For this to work, the base FilterConfigNode implementation must use virtual method calls. The TestDBusSessionConfig.testUpdateConfigError checks that the error generated for an incorrect "sync" property contains the path of the config.ini file. The meaning of the error message in this case is that the wrong value is *for* that file, not that the property is already wrong *in* the file, but that's okay. The MultiplexConfigNode::getName() can only return a fixed name. To satisfy the test and because it is the right choice at the moment for all properties which might trigger such an error, it now is configured so that it returns the most specific path of the non-shared properties. "syncevolution --print-config" shows errors that are in files. Wrong command line parameters are rejected with a message that refers to the command line parameter ("--source-property sync=foo"). A future enhancement would be to make the name depend on the property (MB#8037). Because an empty string is now a valid configuration name (referencing the source properties without the per-peer properties) several checks for such empty strings were removed. The corresponding tests were updated resp. removed. Instead of talking about "server not found", the more neutral name "configuration" is used. The new TestMultipleConfigs.testSharing() covers the semantic of sharing properties between multiple configs. Access to non-existant nodes is routed into the new DevNullConfigNode. It always returns an empty string when reading and throws an error when trying to write into it. Unintentionally writing into a config.ini file therefore became harder, compared with the previous instantiation of SyncContext() with empty config name. The parsing of incoming messages uses a SyncContext which is bound to a VolatileConfigNode. This allows reading and writing of properties without any risk of touching files on disk. The patch which introduced the new config nodes was not complete yet with regards to the new layout. Removing nodes and trees used the wrong root path: getRootPath() refers to the most specific peer config, m_root to the part without the peer path. SyncConfig must distinguish between a view with peer-specific properties and one without, which is done by setting the m_peerPath only if a peer was selected. Copying properties must know whether writing per-specific properties ("unshared") is wanted, because trying to do it for a view without those properties would trigger the DevNullConfigNode exception. SyncConfig::removeSyncSource() removes source properties both in the shared part of the config and in *all* peers. This is used by Session.SetConfig() for the case that the caller is a) setting instead of updating the config and b) not providing any properties for the source. This is clearly a risky operation which should not be done when there are other peers which still use the source. We might have a problem in our D-Bus API definition for "removing a peer configuration" (MB #8059) because it always has an effect on other peers. The property registries were initialized implicitly before. With the recent changes it happened that SyncContext was initialized to analyze a SyncML message without initializing the registry, which caused getRemoteDevID() to use a property where the hidden flag had not been set yet. Moving all of these additional flags into the property constructors is awkward (which is why they are in the getRegistry() methods), so this was fixed by initializing the properties in the SyncConfig constructors by asking for the registries. Because there is no way to access them except via the registry and SyncConfig instances (*), this should ensure that the properties are valid when used. (*) Exception are some properties which are declared publicly to have access to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
bool m_doLogging;
bool m_quiet;
bool m_dryrun;
bool m_serverMode;
std::string m_sessionID;
SharedBuffer m_initialMessage;
string m_initialMessageType;
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
string m_syncDeviceID;
boost::shared_ptr<TransportAgent> m_agent;
/**
* flags for suspend and abort
*/
static SuspendFlags s_flags;
/**
* a pointer to the active SourceList instance if one exists;
* used for error handling in throwError() on the iPhone
*/
static SourceList *m_sourceListPtr;
/**
* a pointer to the active SyncContext instance if one exists;
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
* set by sync() and/or SwapContext
*/
static SyncContext *m_activeContext;
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
class SwapContext {
SyncContext *m_oldContext;
public:
SwapContext(SyncContext *newContext) :
m_oldContext(SyncContext::m_activeContext) {
SyncContext::m_activeContext = newContext;
}
~SwapContext() {
SyncContext::m_activeContext = m_oldContext;
}
};
/**
* Connection to the Synthesis engine. Always valid in a
* constructed SyncContext. Use getEngine() to reference
* it.
*/
SharedEngine m_engine;
/**
* Synthesis session handle. Only valid while sync is running.
*/
SharedSession m_session;
/**
* installs session in SyncContext and removes it again
* when going out of scope
*/
class SessionSentinel {
SyncContext &m_client;
public:
SessionSentinel(SyncContext &client, SharedSession &session) :
m_client(client) {
m_client.m_session = session;
}
~SessionSentinel() {
m_client.m_session.reset();
}
};
public:
config: share properties between peers, configuration view without peer This patch makes the configuration layout with per-source and per-peer properties the default for new configurations. Migrating old configurations is not implemented. The command line has not been updated at all (MB #8048). The D-Bus API is fairly complete, only listing sessions independently of a peer is missing (MB #8049). The key concept of this patch is that a pseudo-node implemented by MultiplexConfigNode provides a view on all user-visible or hidden properties. Based on the property name, it looks up the property definition, picks one of the underlying nodes based on the property visibility and sharing attributes, then reads and writes the property via that node. Clearing properties is not needed and not implemented, because of the uncertain semantic (really remove shared properties?!). The "sync" property must be available both in the per-source config (to pick a backend independently of a specific peer) and in the per-peer configuration (to select a specific data format). This is solved by making the property special (SHARED_AND_UNSHARED flag) and then writing it into two nodes. Reading is done from the more specific per-peer node, with the other node acting as fallback. The MultiplexConfigNode has to implement the FilterConfigNode API because it is used as one by the code which sets passwords in the filter. For this to work, the base FilterConfigNode implementation must use virtual method calls. The TestDBusSessionConfig.testUpdateConfigError checks that the error generated for an incorrect "sync" property contains the path of the config.ini file. The meaning of the error message in this case is that the wrong value is *for* that file, not that the property is already wrong *in* the file, but that's okay. The MultiplexConfigNode::getName() can only return a fixed name. To satisfy the test and because it is the right choice at the moment for all properties which might trigger such an error, it now is configured so that it returns the most specific path of the non-shared properties. "syncevolution --print-config" shows errors that are in files. Wrong command line parameters are rejected with a message that refers to the command line parameter ("--source-property sync=foo"). A future enhancement would be to make the name depend on the property (MB#8037). Because an empty string is now a valid configuration name (referencing the source properties without the per-peer properties) several checks for such empty strings were removed. The corresponding tests were updated resp. removed. Instead of talking about "server not found", the more neutral name "configuration" is used. The new TestMultipleConfigs.testSharing() covers the semantic of sharing properties between multiple configs. Access to non-existant nodes is routed into the new DevNullConfigNode. It always returns an empty string when reading and throws an error when trying to write into it. Unintentionally writing into a config.ini file therefore became harder, compared with the previous instantiation of SyncContext() with empty config name. The parsing of incoming messages uses a SyncContext which is bound to a VolatileConfigNode. This allows reading and writing of properties without any risk of touching files on disk. The patch which introduced the new config nodes was not complete yet with regards to the new layout. Removing nodes and trees used the wrong root path: getRootPath() refers to the most specific peer config, m_root to the part without the peer path. SyncConfig must distinguish between a view with peer-specific properties and one without, which is done by setting the m_peerPath only if a peer was selected. Copying properties must know whether writing per-specific properties ("unshared") is wanted, because trying to do it for a view without those properties would trigger the DevNullConfigNode exception. SyncConfig::removeSyncSource() removes source properties both in the shared part of the config and in *all* peers. This is used by Session.SetConfig() for the case that the caller is a) setting instead of updating the config and b) not providing any properties for the source. This is clearly a risky operation which should not be done when there are other peers which still use the source. We might have a problem in our D-Bus API definition for "removing a peer configuration" (MB #8059) because it always has an effect on other peers. The property registries were initialized implicitly before. With the recent changes it happened that SyncContext was initialized to analyze a SyncML message without initializing the registry, which caused getRemoteDevID() to use a property where the hidden flag had not been set yet. Moving all of these additional flags into the property constructors is awkward (which is why they are in the getRegistry() methods), so this was fixed by initializing the properties in the SyncConfig constructors by asking for the registries. Because there is no way to access them except via the registry and SyncConfig instances (*), this should ensure that the properties are valid when used. (*) Exception are some properties which are declared publicly to have access to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
/**
* SyncContext using a volatile config
* and no logging.
*/
SyncContext();
/**
* @param server identifies the server config to be used
* @param doLogging write additional log and datatbase files about the sync
*/
SyncContext(const string &server,
bool doLogging = false);
~SyncContext();
bool getQuiet() { return m_quiet; }
void setQuiet(bool quiet) { m_quiet = quiet; }
bool getDryRun() { return m_dryrun; }
void setDryRun(bool dryrun) { m_dryrun = dryrun; }
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
/** only for server: device ID of peer */
void setSyncDeviceID(const std::string &deviceID) { m_syncDeviceID = deviceID; }
std::string getSyncDeviceID() const { return m_syncDeviceID; }
syncevo-dbus-server + syncevolution: fixed signal handling and D-Bus suspend/abort/shutdown (MB#7555) This patch fixes signal handling and shutdown of syncevo-dbus-server when signals were received. This problems were found in combination with automated tests via test-dbus.py. Signals handled by the signal handlers in the core had no effect on syncevo-dbus-server, which called its own event loop once the suspended or aborted sync returned. Signals received while outside of a sync and outside of an event loop also had no effect. Now signals are always caught by the niam() function in syncevo-dbus-server. It uses the new SyncContext::handleSignal() API to tell the core engine about it and in addition, remembers that syncevo-dbus-server is meant to shut down (suspendRequested). This is then checked by the syncevo-dbus-server event loop (DBusServer::run()) before blocking again. The implementation of Session.Abort() and Session.Suspend() didn't work. isSuspend() and isAbort() mixed up the state. They also did not wake up DBusTransportAgent, so the syncevo-dbus-server might get stuck although an abort was already requested. Partially fixed by adding g_main_loop_quit(). It's not entirely sure whether g_main_loop_quit() is reentrant (see below). There is other code which uses it in signal handlers, but that doesn't mean that this is correct. The right long term solution would be: - avoid entering and leaving the event loop - feed signals into the event loop as normal events SoupTransportAgent solves this by polling for a signal once per second. This also should be improved. Because such code was used in several places for a connection, that common code was moved into Connection::wakeupSession(). Things which went in unnoticed when the original signal handling was merged: - SyncContext::getSuspendFlags() should not allow the caller to modify the SyncContext state. Made the returned SuspendFlags const. - A signal handler may only call re-entrant functions (Stevens, "Advanced Programming in a Unix Environment", 10.6). All printing therefore has to be done outside of the signal handlers. Now the signal handlers just set a message and the regular code checking for abort/suspend prints it. There's a slight race condition (a message might get overwritten before it is printed), but printing just the last message might actually make more sense. There might be a slight delay between receiving the signal and printing now.
2009-10-30 07:25:53 +01:00
/** read-only access to suspend and abort state */
static const SuspendFlags &getSuspendFlags() { return s_flags; }
/*
* Use initSAN as the first step is sync() if this is a server alerted sync.
* Prepare the san package and send the SAN request to the peer.
* Returns false if failed to get a valid client sync request
* otherwise put the client sync request into m_initialMessage which will
* be used to initalze the server via initServer(), then continue sync() to
* start the real sync serssion.
*/
bool initSAN();
/**
* Initializes the session so that it runs as SyncML server once
* sync() is called. For this to work the first client message
* must be available already.
*
* @param sessionID session ID to be used by server
* @param data content of initial message sent by the client
* @param messageType content type set by the client
*/
void initServer(const std::string &sessionID,
SharedBuffer data,
const std::string &messageType);
/**
* Executes the sync, throws an exception in case of failure.
* Handles automatic backups and report generation.
*
* @retval complete sync report, skipped if NULL
* @return overall sync status, for individual sources see report
*/
SyncMLStatus sync(SyncReport *report = NULL);
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
/** result of analyzeSyncMLMessage() */
struct SyncMLMessageInfo {
std::string m_deviceID;
/** a string representation of the whole structure for debugging */
std::string toString() { return std::string("deviceID ") + m_deviceID; }
};
/**
* Instead or executing a sync, analyze the initial message
* without changing any local data. Returns once the LocURI =
* device ID of the client is known.
*
* @return device ID, empty if not in data
*/
static SyncMLMessageInfo
analyzeSyncMLMessage(const char *data, size_t len,
const std::string &messageType);
/**
* Convenience function, to be called inside a catch() block of
* (or for) the sync.
*
* 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).
*/
SyncMLStatus handleException();
/**
* Determines the log directory of the previous sync (either in
* temp or logdir) and shows changes since then.
*/
void status();
enum RestoreDatabase {
DATABASE_BEFORE_SYNC,
DATABASE_AFTER_SYNC
};
/**
* Restore data of selected sources from before or after the given
* sync session, identified by absolute path to the log dir.
*/
void restore(const string &dirname, RestoreDatabase database);
/**
* Sleep the sync session in interval seconds, blocks until the interval
* is expired or a user suspend/abort request is signaled (CTRL+C)
* returns time left to sleep if the method is interrupted by CTRL+C.
*/
virtual int sleep (int interval);
/**
* fills vector with absolute path to information about previous
* sync sessions, oldest one first
*/
void getSessions(vector<string> &dirs);
/**
* fills report with information about previous session
* @return the peer name from the dir.
*/
string readSessionInfo(const string &dir, SyncReport &report);
/**
* fills report with information about local changes
*
* Only sync sources selected in the SyncContext
* constructor are checked. The local item changes will be set in
* the SyncReport's ITEM_LOCAL ITEM_ADDED/UPDATED/REMOVED.
*
* Some sync sources might not be able to report this
* information outside of a regular sync, in which case
* these fields are set to -1.
*
* Start and end times of the check are also reported.
*/
void checkStatus(SyncReport &report);
/**
* throws a runtime_error with the given string
* or (on the iPhone, where exception handling is not
* supported by the toolchain) prints an error directly
* and aborts
*
* output format: <error>
*
* @param error a string describing the error
*/
static void throwError(const string &error);
/**
* throw an exception after an operation failed and
* remember that this instance has failed
*
* output format: <action>: <error string>
*
* @Param action a string describing the operation or object involved
* @param error the errno error code for the failure
*/
static void throwError(const string &action, int error);
/**
* An error handler which prints the error message and then
* stops the program. Never returns.
*
* The API was chosen so that it can be used as libebook/libecal
* "backend-dies" signal handler.
*/
static void fatalError(void *object, const char *error);
/**
* When using Evolution this function starts a background thread
* which drives the default event loop. Without that loop
* "backend-died" signals are not delivered. The problem with
* the thread is that it seems to interfere with gconf startup
* when added to the main() function of syncevolution. Therefore
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
* it is started by SyncSource::beginSync() (for unit
* testing of sync sources) and SyncContext::sync() (for
* normal operation).
*/
static void startLoopThread();
/**
* Finds activated sync source by name. May return NULL
* if no such sync source was defined or is not currently
* instantiated. Pointer remains valid throughout the sync
* session. Called by Synthesis DB plugin to find active
* sources.
*
* @TODO: roll SourceList into SyncContext and
* make this non-static
*/
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
static SyncSource *findSource(const char *name);
/**
* Find the active sync context for the given session.
*
* @param sessionName chosen by SyncEvolution and passed to
* Synthesis engine, which calls us back
* with it in SyncEvolution_Session_CreateContext()
* @return context or NULL if not found
*/
static SyncContext *findContext(const char *sessionName);
SharedEngine getEngine() { return m_engine; }
const SharedEngine getEngine() const { return m_engine; }
bool getDoLogging() { return m_doLogging; }
/**
* Returns the string used to select the peer config
* used by this instance.
*
* Note that this is not the same as a valid configuration
* name. For example "foo" might be matched against a
* "foo@bar" config by SyncConfig. Use SyncConfig::getConfigName()
* to get the underlying config.
*/
std::string getPeer() { return m_server; }
/**
* Handle for active session, may be NULL.
*/
SharedSession getSession() { return m_session; }
syncevo-dbus-server + syncevolution: fixed signal handling and D-Bus suspend/abort/shutdown (MB#7555) This patch fixes signal handling and shutdown of syncevo-dbus-server when signals were received. This problems were found in combination with automated tests via test-dbus.py. Signals handled by the signal handlers in the core had no effect on syncevo-dbus-server, which called its own event loop once the suspended or aborted sync returned. Signals received while outside of a sync and outside of an event loop also had no effect. Now signals are always caught by the niam() function in syncevo-dbus-server. It uses the new SyncContext::handleSignal() API to tell the core engine about it and in addition, remembers that syncevo-dbus-server is meant to shut down (suspendRequested). This is then checked by the syncevo-dbus-server event loop (DBusServer::run()) before blocking again. The implementation of Session.Abort() and Session.Suspend() didn't work. isSuspend() and isAbort() mixed up the state. They also did not wake up DBusTransportAgent, so the syncevo-dbus-server might get stuck although an abort was already requested. Partially fixed by adding g_main_loop_quit(). It's not entirely sure whether g_main_loop_quit() is reentrant (see below). There is other code which uses it in signal handlers, but that doesn't mean that this is correct. The right long term solution would be: - avoid entering and leaving the event loop - feed signals into the event loop as normal events SoupTransportAgent solves this by polling for a signal once per second. This also should be improved. Because such code was used in several places for a connection, that common code was moved into Connection::wakeupSession(). Things which went in unnoticed when the original signal handling was merged: - SyncContext::getSuspendFlags() should not allow the caller to modify the SyncContext state. Made the returned SuspendFlags const. - A signal handler may only call re-entrant functions (Stevens, "Advanced Programming in a Unix Environment", 10.6). All printing therefore has to be done outside of the signal handlers. Now the signal handlers just set a message and the regular code checking for abort/suspend prints it. There's a slight race condition (a message might get overwritten before it is printed), but printing just the last message might actually make more sense. There might be a slight delay between receiving the signal and printing now.
2009-10-30 07:25:53 +01:00
/**
* sync() installs signal handlers for SIGINT and SIGTERM if no
* handler was installed already. SIGINT will try to suspend.
* Sending the signal again quickly (typically done by pressing
* CTRL-C twice) will abort. SIGTERM will abort the running sync
* immediately.
*
* If a handler was installed already, the caller is responsible
* for calling this function if this kind of SIGINT/SIGTERM
* handling is desired.
*/
static void handleSignal(int signal);
/**
* Once a signal was received, all future calls to sync() will
* react to it unless this function is called first.
*/
static void resetSignals() { s_flags = SuspendFlags(); }
protected:
/** exchange active Synthesis engine */
SharedEngine swapEngine(SharedEngine newengine) {
SharedEngine oldengine = m_engine;
m_engine = newengine;
return oldengine;
}
/** sentinel class which creates, installs and removes a new
Synthesis engine for the duration of its own life time */
class SwapEngine {
SyncContext &m_client;
SharedEngine m_oldengine;
public:
SwapEngine(SyncContext &client) :
m_client(client) {
SharedEngine syncengine(m_client.createEngine());
m_oldengine = m_client.swapEngine(syncengine);
}
~SwapEngine() {
m_client.swapEngine(m_oldengine);
}
};
/**
* Create a Synthesis engine for the currently active
* sources (might be empty!) and settings.
*/
SharedEngine createEngine();
/**
* Maps from source name to sync mode with one default
* for all sources which don't have a specific entry
* in the hash.
*/
class SyncModes : public std::map<string, SyncMode> {
SyncMode m_syncMode;
public:
SyncModes(SyncMode syncMode = SYNC_NONE) :
m_syncMode(syncMode)
{}
SyncMode getDefaultSyncMode() { return m_syncMode; }
void setDefaultMode(SyncMode syncMode) { m_syncMode = syncMode; }
SyncMode getSyncMode(const string &sourceName) const {
const_iterator it = find(sourceName);
if (it == end()) {
return m_syncMode;
} else {
return it->second;
}
}
void setSyncMode(const string &sourceName, SyncMode syncMode) {
(*this)[sourceName] = syncMode;
}
};
/**
* An utility function which can be used as part of
* prepare() below to reconfigure the sync mode that
* is going to be used for the active sync session.
* SYNC_NONE as mode means that the sync mode of the
* source is not modified and the default from the
* configuration is used.
*/
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
void setSyncModes(const std::vector<SyncSource *> &sources,
const SyncModes &modes);
/**
* Return skeleton Synthesis client XML configuration.
*
* If it contains a <datastore/> element, then that element will
* be replaced by the configurations of all active sync
* sources. Otherwise the configuration is used as-is.
*
* The default implementation of this function takes the configuration from
* (in this order):
* - ./syncevolution.xml
* - <server config dir>/syncevolution.xml
* - built-in default
*
* @retval xml is filled with Synthesis client config which may hav <datastore/>
* @retval configname a string describing where the config came from
*/
virtual void getConfigTemplateXML(string &xml, string &configname);
/**
* Return complete Synthesis XML configuration.
*
* Calls getConfigTemplateXML(), then fills in
* sync source XML fragments if necessary.
*
* @retval xml is filled with complete Synthesis client config
* @retval configname a string describing where the config came from
*/
virtual void getConfigXML(string &xml, string &configname);
/**
* A helper function which interactively asks the user for
* a certain password. May throw errors.
*
* The default implementation uses stdin/stdout to communicate
* with the user.
*
* @param passwordName the name of the password in the config file
* @param descr A simple string explaining what the password is needed for,
* e.g. "SyncML server". Has to be unique and understandable
* by the user.
* @param key the key used to retrieve password
* @return entered password
*/
virtual string askPassword(const string &passwordName, const string &descr, const ConfigPasswordKey &key);
/**
* A helper function which is used for user interface to save
* a certain password with a specific mechanism.
* Currently possibly syncml server. May throw errors.
* The default implementation do nothing.
*/
virtual bool savePassword(const string &passwordName, const string &password, const ConfigPasswordKey &key) {
return false;
}
/**
* Callback for derived classes: called after initializing the
* client, but before doing anything with its configuration.
* Can be used to override the client configuration.
*/
virtual void prepare() {}
/**
* Callback for derived classes: called after setting up the client's
* and sources' configuration. Can be used to reconfigure sources before
* actually starting the synchronization.
*
* @param sources a NULL terminated array of all active sources
*/
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
virtual void prepare(const std::vector<SyncSource *> &sources) {}
/**
* instantiate transport agent
*
* Called by engine when it needs to do HTTP POST requests. The
* transport agent will be used throughout the sync session and
* unref'ed when no longer needed. At most one agent will be
* requested at a time. The transport agent is intentionally
* returned as a Boost shared pointer so that a pointer to a
* class with a different life cycle is possible, either by
* keeping a reference or by returning a shared_ptr where the
* destructor doesn't do anything.
*
* The default implementation instantiates one of the builtin
* transport agents, depending on how it was compiled.
*
* @return transport agent
*/
virtual boost::shared_ptr<TransportAgent> createTransportAgent();
/**
* display a text message from the server
*
* Not really used by SyncML servers. Could be displayed in a
* modal dialog.
*
* @param message string with local encoding, possibly with line breaks
*/
virtual void displayServerMessage(const string &message);
/**
* display general sync session progress
*
* @param type PEV_*, see <synthesis/engine_defs.h>
* @param extra1 extra information depending on type
* @param extra2 extra information depending on type
* @param extra3 extra information depending on type
*/
virtual void displaySyncProgress(sysync::TProgressEventEnum type,
int32_t extra1, int32_t extra2, int32_t extra3);
/**
* display sync source specific progress
*
* @param type PEV_*, see <synthesis/engine_defs.h>
* @param source source which is the target of the event
* @param extra1 extra information depending on type
* @param extra2 extra information depending on type
* @param extra3 extra information depending on type
*/
virtual void displaySourceProgress(sysync::TProgressEventEnum type,
redesigned SyncSource base class + API The main motivation for this change is that it allows the implementor of a backend to choose the implementations for the different aspects of a datasource (change tracking, item import/export, logging, ...) independently of each other. For example, change tracking via revision strings can now be combined with exchanging data with the Synthesis engine via a single string (the traditional method in SyncEvolution) and with direct access to the Synthesis field list (now possible for the first time). The new backend API is based on the concept of providing implementations for certain functionality via function objects instead of implementing certain virtual methods. The advantage is that implementors can define their own, custom interfaces and mix and match implementations of the different groups of functionality. Logging (see SyncSourceLogging in a later commit) can be done by wrapping some arbitrary other item import/export function objects (decorator design pattern). The class hierarchy is now this: - SyncSourceBase: interface for common utility code, all other classes are derived from it and thus can use that code - SyncSource: base class which implements SyncSourceBase and hooks a datasource into the SyncEvolution core; its "struct Operations" holds the function objects which can be implemented in different ways - TestingSyncSource: combines some of the following classes into an interface that is expected by the client-test program; backends only have to derive from (and implement this) if they want to use the automated testing - TrackingSyncSource: provides the same functionality as before (change tracking via revision strings, item import/export as string) in a single interface; the description of the pure virtual methods are duplicated so that developers can go through this class and find everything they need to know to implement it The following classes contain the code that was previously found in the EvolutionSyncSource base class. Implementors can derive from them and call the init() methods to inherit and activate the functionality: - SyncSourceSession: binds Synthesis session callbacks to virtual methods beginSync(), endSync() - SyncSourceChanges: implements Synthesis item tracking callbacks with set of LUIDs that the user of the class has to fill - SyncSourceDelete: binds Synthesis delete callback to virtual method - SyncSourceRaw: read and write items in the backends format, used for testing and backup/restore - SyncSourceSerialize: exchanges items with Synthesis engine using a string representation of the data; this is how EvolutionSyncSource has traditionally worked, so much of the same virtual methods are now in this class - SyncSourceRevisions: utility class which does change tracking via some kind of "revision" string which changes each time an item is modified; this code was previously in the TrackingSyncSource
2009-08-25 09:27:46 +02:00
SyncSource &source,
int32_t extra1, int32_t extra2, int32_t extra3);
/**
* report step command info
*
* Will be called after each step in step loop in SyncContext::doSync().
* This reports step command info.
* @param stepCmd step command enum value
*/
virtual void reportStepCmd(sysync::uInt16 stepCmd) {}
2009-03-11 12:59:25 +01:00
/**
* Called to find out whether user wants to abort sync.
*
* Will be called regularly. Once it has flagged an abort, all
* following calls should return the same value. When the engine
* aborts, the sync is shut down as soon as possible. The next
* sync most likely has to be done in slow mode, so don't do this
* unless absolutely necessary.
*
* @return true if user wants to abort
*/
syncevo-dbus-server + syncevolution: fixed signal handling and D-Bus suspend/abort/shutdown (MB#7555) This patch fixes signal handling and shutdown of syncevo-dbus-server when signals were received. This problems were found in combination with automated tests via test-dbus.py. Signals handled by the signal handlers in the core had no effect on syncevo-dbus-server, which called its own event loop once the suspended or aborted sync returned. Signals received while outside of a sync and outside of an event loop also had no effect. Now signals are always caught by the niam() function in syncevo-dbus-server. It uses the new SyncContext::handleSignal() API to tell the core engine about it and in addition, remembers that syncevo-dbus-server is meant to shut down (suspendRequested). This is then checked by the syncevo-dbus-server event loop (DBusServer::run()) before blocking again. The implementation of Session.Abort() and Session.Suspend() didn't work. isSuspend() and isAbort() mixed up the state. They also did not wake up DBusTransportAgent, so the syncevo-dbus-server might get stuck although an abort was already requested. Partially fixed by adding g_main_loop_quit(). It's not entirely sure whether g_main_loop_quit() is reentrant (see below). There is other code which uses it in signal handlers, but that doesn't mean that this is correct. The right long term solution would be: - avoid entering and leaving the event loop - feed signals into the event loop as normal events SoupTransportAgent solves this by polling for a signal once per second. This also should be improved. Because such code was used in several places for a connection, that common code was moved into Connection::wakeupSession(). Things which went in unnoticed when the original signal handling was merged: - SyncContext::getSuspendFlags() should not allow the caller to modify the SyncContext state. Made the returned SuspendFlags const. - A signal handler may only call re-entrant functions (Stevens, "Advanced Programming in a Unix Environment", 10.6). All printing therefore has to be done outside of the signal handlers. Now the signal handlers just set a message and the regular code checking for abort/suspend prints it. There's a slight race condition (a message might get overwritten before it is printed), but printing just the last message might actually make more sense. There might be a slight delay between receiving the signal and printing now.
2009-10-30 07:25:53 +01:00
virtual bool checkForAbort() {
printSignals();
return (s_flags.state == SuspendFlags::CLIENT_ABORT);
}
2009-03-11 12:59:25 +01:00
/**
* Called to find out whether user wants to suspend sync.
*
* Same as checkForAbort(), but the session is finished
* gracefully so that it can be resumed.
*/
syncevo-dbus-server + syncevolution: fixed signal handling and D-Bus suspend/abort/shutdown (MB#7555) This patch fixes signal handling and shutdown of syncevo-dbus-server when signals were received. This problems were found in combination with automated tests via test-dbus.py. Signals handled by the signal handlers in the core had no effect on syncevo-dbus-server, which called its own event loop once the suspended or aborted sync returned. Signals received while outside of a sync and outside of an event loop also had no effect. Now signals are always caught by the niam() function in syncevo-dbus-server. It uses the new SyncContext::handleSignal() API to tell the core engine about it and in addition, remembers that syncevo-dbus-server is meant to shut down (suspendRequested). This is then checked by the syncevo-dbus-server event loop (DBusServer::run()) before blocking again. The implementation of Session.Abort() and Session.Suspend() didn't work. isSuspend() and isAbort() mixed up the state. They also did not wake up DBusTransportAgent, so the syncevo-dbus-server might get stuck although an abort was already requested. Partially fixed by adding g_main_loop_quit(). It's not entirely sure whether g_main_loop_quit() is reentrant (see below). There is other code which uses it in signal handlers, but that doesn't mean that this is correct. The right long term solution would be: - avoid entering and leaving the event loop - feed signals into the event loop as normal events SoupTransportAgent solves this by polling for a signal once per second. This also should be improved. Because such code was used in several places for a connection, that common code was moved into Connection::wakeupSession(). Things which went in unnoticed when the original signal handling was merged: - SyncContext::getSuspendFlags() should not allow the caller to modify the SyncContext state. Made the returned SuspendFlags const. - A signal handler may only call re-entrant functions (Stevens, "Advanced Programming in a Unix Environment", 10.6). All printing therefore has to be done outside of the signal handlers. Now the signal handlers just set a message and the regular code checking for abort/suspend prints it. There's a slight race condition (a message might get overwritten before it is printed), but printing just the last message might actually make more sense. There might be a slight delay between receiving the signal and printing now.
2009-10-30 07:25:53 +01:00
virtual bool checkForSuspend() {
printSignals();
return (s_flags.state == SuspendFlags::CLIENT_SUSPEND);
}
2009-03-11 12:59:25 +01:00
private:
config: share properties between peers, configuration view without peer This patch makes the configuration layout with per-source and per-peer properties the default for new configurations. Migrating old configurations is not implemented. The command line has not been updated at all (MB #8048). The D-Bus API is fairly complete, only listing sessions independently of a peer is missing (MB #8049). The key concept of this patch is that a pseudo-node implemented by MultiplexConfigNode provides a view on all user-visible or hidden properties. Based on the property name, it looks up the property definition, picks one of the underlying nodes based on the property visibility and sharing attributes, then reads and writes the property via that node. Clearing properties is not needed and not implemented, because of the uncertain semantic (really remove shared properties?!). The "sync" property must be available both in the per-source config (to pick a backend independently of a specific peer) and in the per-peer configuration (to select a specific data format). This is solved by making the property special (SHARED_AND_UNSHARED flag) and then writing it into two nodes. Reading is done from the more specific per-peer node, with the other node acting as fallback. The MultiplexConfigNode has to implement the FilterConfigNode API because it is used as one by the code which sets passwords in the filter. For this to work, the base FilterConfigNode implementation must use virtual method calls. The TestDBusSessionConfig.testUpdateConfigError checks that the error generated for an incorrect "sync" property contains the path of the config.ini file. The meaning of the error message in this case is that the wrong value is *for* that file, not that the property is already wrong *in* the file, but that's okay. The MultiplexConfigNode::getName() can only return a fixed name. To satisfy the test and because it is the right choice at the moment for all properties which might trigger such an error, it now is configured so that it returns the most specific path of the non-shared properties. "syncevolution --print-config" shows errors that are in files. Wrong command line parameters are rejected with a message that refers to the command line parameter ("--source-property sync=foo"). A future enhancement would be to make the name depend on the property (MB#8037). Because an empty string is now a valid configuration name (referencing the source properties without the per-peer properties) several checks for such empty strings were removed. The corresponding tests were updated resp. removed. Instead of talking about "server not found", the more neutral name "configuration" is used. The new TestMultipleConfigs.testSharing() covers the semantic of sharing properties between multiple configs. Access to non-existant nodes is routed into the new DevNullConfigNode. It always returns an empty string when reading and throws an error when trying to write into it. Unintentionally writing into a config.ini file therefore became harder, compared with the previous instantiation of SyncContext() with empty config name. The parsing of incoming messages uses a SyncContext which is bound to a VolatileConfigNode. This allows reading and writing of properties without any risk of touching files on disk. The patch which introduced the new config nodes was not complete yet with regards to the new layout. Removing nodes and trees used the wrong root path: getRootPath() refers to the most specific peer config, m_root to the part without the peer path. SyncConfig must distinguish between a view with peer-specific properties and one without, which is done by setting the m_peerPath only if a peer was selected. Copying properties must know whether writing per-specific properties ("unshared") is wanted, because trying to do it for a view without those properties would trigger the DevNullConfigNode exception. SyncConfig::removeSyncSource() removes source properties both in the shared part of the config and in *all* peers. This is used by Session.SetConfig() for the case that the caller is a) setting instead of updating the config and b) not providing any properties for the source. This is clearly a risky operation which should not be done when there are other peers which still use the source. We might have a problem in our D-Bus API definition for "removing a peer configuration" (MB #8059) because it always has an effect on other peers. The property registries were initialized implicitly before. With the recent changes it happened that SyncContext was initialized to analyze a SyncML message without initializing the registry, which caused getRemoteDevID() to use a property where the hidden flag had not been set yet. Moving all of these additional flags into the property constructors is awkward (which is why they are in the getRegistry() methods), so this was fixed by initializing the properties in the SyncConfig constructors by asking for the registries. Because there is no way to access them except via the registry and SyncConfig instances (*), this should ensure that the properties are valid when used. (*) Exception are some properties which are declared publicly to have access to their name. Nobody's perfect...
2009-11-13 20:02:44 +01:00
/** initialize members as part of constructors */
void init();
SyncML server: find configuration for client automatically (MB#7710) When the syncevo-dbus-server receives a SyncML message as initial data from a transport stub, it peeks into the data with the help of the Synthesis server engine and our SyncEvolution_Session_CheckDevice() and extracts the LocURI = device ID of the client. This value is guaranteed to be in the initial SyncML message. Then it searches for a peer configuration (still called "server configuration" in the current code) which has the same ID in its remoteDeviceID property and uses that configuration. Instantiating SyncContext and Synthesis engine multiple times is a bit tricky. The context for the SynthesisDBPlugin currently has to be passed via a global variable, SyncContext::m_activeContext. SyncContext::analyzeSyncMLMessage() temporarily replaces that with the help of a new sentinal class SwapContext, which ensures that the previous context is restored when leaving the function. Some common code was moved around for this (SyncContext::initEngine()). The "default config" parameter in syncevo-http-server.py was removed because it is no longer needed. The possibility to select some kind of config context via the path below the sync URL still exists. This is passed to syncevo-dbus-server via the "config" peer attribute. It has no effect there at the moment. TestConnection.testStartSync() in test-dbus.py covers this kind of config selection. To run it, a peer configuration with remoteDeviceID "sc-api-nat" must exist.
2009-11-06 11:43:55 +01:00
/**
* generate XML configuration and (re)initialize engine with it
*/
void initEngine(bool logXML);
/**
* the code common to init() and status():
* populate source list with active sources and open
*/
void initSources(SourceList &sourceList);
/**
* utility function for status() and getChanges():
* iterate over sources, check for changes and copy result
*/
void checkSourceChanges(SourceList &sourceList, SyncReport &changes);
/**
* sets up Synthesis session and executes it
*/
SyncMLStatus doSync();
/**
* directory for Synthesis client binfiles or
* Synthesis server textdb files, unique for each
* peer
*/
string getSynthesisDatadir() { return getRootPath() + "/.synthesis"; }
syncevo-dbus-server + syncevolution: fixed signal handling and D-Bus suspend/abort/shutdown (MB#7555) This patch fixes signal handling and shutdown of syncevo-dbus-server when signals were received. This problems were found in combination with automated tests via test-dbus.py. Signals handled by the signal handlers in the core had no effect on syncevo-dbus-server, which called its own event loop once the suspended or aborted sync returned. Signals received while outside of a sync and outside of an event loop also had no effect. Now signals are always caught by the niam() function in syncevo-dbus-server. It uses the new SyncContext::handleSignal() API to tell the core engine about it and in addition, remembers that syncevo-dbus-server is meant to shut down (suspendRequested). This is then checked by the syncevo-dbus-server event loop (DBusServer::run()) before blocking again. The implementation of Session.Abort() and Session.Suspend() didn't work. isSuspend() and isAbort() mixed up the state. They also did not wake up DBusTransportAgent, so the syncevo-dbus-server might get stuck although an abort was already requested. Partially fixed by adding g_main_loop_quit(). It's not entirely sure whether g_main_loop_quit() is reentrant (see below). There is other code which uses it in signal handlers, but that doesn't mean that this is correct. The right long term solution would be: - avoid entering and leaving the event loop - feed signals into the event loop as normal events SoupTransportAgent solves this by polling for a signal once per second. This also should be improved. Because such code was used in several places for a connection, that common code was moved into Connection::wakeupSession(). Things which went in unnoticed when the original signal handling was merged: - SyncContext::getSuspendFlags() should not allow the caller to modify the SyncContext state. Made the returned SuspendFlags const. - A signal handler may only call re-entrant functions (Stevens, "Advanced Programming in a Unix Environment", 10.6). All printing therefore has to be done outside of the signal handlers. Now the signal handlers just set a message and the regular code checking for abort/suspend prints it. There's a slight race condition (a message might get overwritten before it is printed), but printing just the last message might actually make more sense. There might be a slight delay between receiving the signal and printing now.
2009-10-30 07:25:53 +01:00
/**
* handleSignals() is called in a signal handler,
* which can only call reentrant functions. Our
* logging code is not reentrant and thus has
* to be called outside of the signal handler.
*/
static void printSignals();
sync client: detect unexpected slow sync and abort (MB #2416) This solution depends on ABORTDATASTORE() in an <alertscript>, which is supported by libsynthesis only with an additional patch. ABORTDATASTORE() is done for each source where the sync mode requested by the server does not match the expected one. The check is only added when a slow sync would not be acceptable. Explicit "slow" and "resfresh-from-server" (which is done under the hood as a slow sync?!) don't need the check. Distinguishing unexpected "slow" from "refresh-from-client" has to be done via ALERTCODE(), SLOWSYNC() returns true in both cases. Note that currently, "no local data" and "first time sync" are ignored when checking for acceptable slow syncs. This might have to be added. In addition, the code in SyncContext::doSync() is notified via the "delayedabort" session variable that it should abort after processing the current message and before sending the reply. This is necessary to get the <alertscript> executed for all sources. Calling ABORTSESSION() inside the script was tried first but aborted right away, so that an unexpected slow sync could only be detected for one source. Abort handling didn't work for aborting before sending a reply: - SessionStep would return STEPCMD_SENDDATA - we would start sending - abort was checked later A good point to abort a sync is after the engine has processed a message and before sending out the reply. This patch adds that check for STEPCMD_SENDDATA and checkForAbort() directly after the SessionStep() call. Because the whole suspend/abort handling is somewhat tricky, this patch adds debug logging around those state changes. The code which detects the request to abort also analyzes the sitution and prints some guidance for the user how to recover from the problem: [ERROR] Aborting because of unexpected slow sync for source(s): calendar [INFO] Doing a slow synchronization may lead to duplicated items or lost data when the server merges items incorrectly. Choosing a different synchronization mode may be the better alternative. Restart synchronization of affected source(s) with one of the following sync modes to recover from this problem: slow, refresh-from-server, refresh-from-client Analyzing the current state: syncevolution --status syncevolution_client calendar Running with one of the three modes: syncevolution --sync [slow|refresh-from-server|refresh-from-client] syncevolution_client calendar On the server, doing this check in <alertscript> is not sufficient because the server does the anchor check after executing the <alertscript> and thus switches to an unexpected slow sync later on. Need a different solution for server initiated syncs with phones.
2009-12-15 18:19:14 +01:00
/**
* return true if "delayedabort" session variable is true
*/
bool checkForScriptAbort(SharedSession session);
// total retry duration
int m_retryDuration;
// message resend interval
int m_retryInterval;
// Current retry count
int m_retries;
public:
static bool transport_cb (void *data);
void setTransportCallback(int seconds);
};
SE_END_CXX
#endif // INCL_EVOLUTIONSYNCCLIENT