syncevolution/src/syncevo/SyncContext.h
Patrick Ohly 5e7188c6e8 Logdir: getServer() might not include context
SyncContext::getServer() = m_server returns the string as passed in
by the user. This may be without the right context, if the peer name
alone is unique enough to select a config (scheduleworld2 instead
of scheduleworld2@other-context).

What we need is the normalized name as used by SyncConfig. Added
SyncConfig::getConfigName() for that and renamed getServer() to
getPeer(), with suitable documentation.
2010-01-21 11:43:21 +01:00

694 lines
22 KiB
C++

/*
* Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
* Copyright (C) 2009 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef INCL_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;
class SyncSource;
struct SuspendFlags
{
/** 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;
/**
* 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;
bool m_doLogging;
bool m_quiet;
bool m_dryrun;
bool m_serverMode;
std::string m_sessionID;
SharedBuffer m_initialMessage;
string m_initialMessageType;
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;
* set by sync() and/or SwapContext
*/
static SyncContext *m_activeContext;
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:
/**
* 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; }
/** only for server: device ID of peer */
void setSyncDeviceID(const std::string &deviceID) { m_syncDeviceID = deviceID; }
std::string getSyncDeviceID() const { return m_syncDeviceID; }
/** 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);
/** 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
* 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
*/
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; }
/**
* 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.
*/
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
*/
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,
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) {}
/**
* 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
*/
virtual bool checkForAbort() {
printSignals();
return (s_flags.state == SuspendFlags::CLIENT_ABORT);
}
/**
* 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.
*/
virtual bool checkForSuspend() {
printSignals();
return (s_flags.state == SuspendFlags::CLIENT_SUSPEND);
}
private:
/** initialize members as part of constructors */
void init();
/**
* 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"; }
/**
* 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();
/**
* 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