syncevolution/src/dbus/server/session.h

569 lines
21 KiB
C++

/*
* Copyright (C) 2011 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 SESSION_H
#define SESSION_H
#include <syncevo/SynthesisEngine.h>
#include <boost/weak_ptr.hpp>
#include <boost/utility.hpp>
#include <gdbus-cxx-bridge.h>
#include <syncevo/SuspendFlags.h>
#include <syncevo/timeout.h>
#include "session-common.h"
#include "read-operations.h"
#include "progress-data.h"
#include "source-progress.h"
#include "source-status.h"
#include "timer.h"
#include "resource.h"
#include "dbus-callbacks.h"
SE_BEGIN_CXX
class Connection;
class Server;
class ForkExecParent;
class SessionProxy;
class InfoReq;
/**
* Represents and implements the Session interface. Use
* boost::shared_ptr to track it and ensure that there are references
* to it as long as the connection is needed.
*
* The actual implementation is split into two parts:
* - state as exposed via D-Bus is handled entirely in this class
* - syncing and command line execution run inside
* the forked syncevo-dbus-helper
*
* This allows creating and tracking a Session locally in
* syncevo-dbus-server and minimizes asynchronous calls into the
* helper. The helper is started on demand (which might be never,
* for simple sessions).
*/
class Session : public GDBusCXX::DBusObjectHelper,
public Logger,
public Resource,
private ReadOperations,
private boost::noncopyable
{
public:
/**
* the sync status for session
*/
enum SyncStatus {
SYNC_QUEUEING, ///< waiting to become ready for use
SYNC_IDLE, ///< ready, session is initiated but sync not started
SYNC_RUNNING, ///< sync is running
SYNC_ABORT, ///< sync is aborting
SYNC_SUSPEND, ///< sync is suspending
SYNC_DONE, ///< sync is done
SYNC_ILLEGAL
};
/** Part of D-Bus API. */
typedef std::map<std::string, SourceStatus> SourceStatuses_t;
/** Part of D-Bus API. */
typedef std::map<std::string, APISourceProgress> APISourceProgresses_t;
/** Internal, with automatic conversion to APISourceProgresses_t when needed. */
struct SourceProgresses_t : public std::map<std::string, SourceProgress> {
operator APISourceProgresses_t () const {
APISourceProgresses_t to;
to.insert(begin(), end());
return to;
}
};
private:
std::vector<std::string> m_flags;
const std::string m_sessionID;
std::string m_peerDeviceID;
/** Starts the helper, on demand (see useHelperAsync()). */
boost::shared_ptr<ForkExecParent> m_forkExecParent;
/** The D-Bus proxy for the helper. */
boost::shared_ptr<SessionProxy> m_helper;
/**
* Ensures that helper is running and that its D-Bus API is
* available via m_helper, then invokes the success
* callback. Startup errors are reported back via the error
* callback. It is the responsibility of that error callback to
* turn the session into the right failure state, usually via
* Session::failed(). Likewise, any unexpected failures or helper
* shutdowns need to be monitored by the caller of
* useHelperAsync(). useHelperAsync() merely logs these events.
*
* useHelperAsync() and its helper function, useHelper2(), are the
* ones called directly from the main event loop. They ensure that
* any exceptions thrown inside them, including exceptions thrown
* by the result.done(), are logged and turned into
* result.failed() calls.
*
* In practice, the helper is started at most once per session, to
* run the operation (see runOperation()). When it terminates, the
* session is either considered "done" or "failed", depending on
* whether the operation has completed already.
* @param env additional env variables to be set in the helper process
*/
void useHelperAsync(const SimpleResult &result, const StringMap &env = StringMap());
/**
* Finish the work started by useHelperAsync once helper has
* connected. The operation might still fail at this point.
*/
void useHelper2(const SimpleResult &result, const boost::signals2::connection &c);
/** set up m_helper */
void onConnect(const GDBusCXX::DBusConnectionPtr &conn) throw ();
/** unset m_helper but not m_forkExecParent (still processing signals) */
void onQuit(int result) throw ();
/** set after abort() and suspend(), to turn "child died' into the LOCERR_USERABORT status code */
void expectChildTerm(int result) throw ();
/** log failure */
void onFailure(SyncMLStatus status, const std::string &explanation) throw ();
/** log error output from helper */
void onOutput(const char *buffer, size_t length);
bool m_serverMode;
bool m_serverAlerted;
SharedBuffer m_initialMessage;
string m_initialMessageType;
boost::weak_ptr<Connection> m_connection;
std::string m_connectionError;
bool m_useConnection;
/** temporary config changes */
FilterConfigNode::ConfigFilter m_syncFilter;
FilterConfigNode::ConfigFilter m_sourceFilter;
SessionCommon::SourceFilters_t m_sourceFilters;
/** whether dbus clients set temporary configs */
bool m_tempConfig;
/**
* whether the dbus clients updated, removed or cleared configs,
* ignoring temporary configuration changes
*/
bool m_setConfig;
/** Session life cycle */
enum SessionStatus {
SESSION_IDLE, /**< not active yet, only Detach() allowed */
SESSION_ACTIVE, /**< active, config changes and Sync()/Execute() allowed */
SESSION_RUNNING, /**< one-time operation (Sync() or Execute()) in progress */
SESSION_DONE /**< operation completed, only Detach() still allowed */
};
SessionStatus m_status;
/**
* set when operation was aborted, enables special handling of "child quit" in onQuit().
*/
bool m_wasAborted;
/**
* True iff we initiated the sync.
*/
bool m_remoteInitiated;
SyncStatus m_syncStatus;
/** maps to names as used in D-Bus API */
inline std::string static syncStatusToString(SyncStatus state)
{
static const char * const strings[SYNC_ILLEGAL] = {
"queueing",
"idle",
"running",
"aborting",
"suspending",
"done"
};
return state >= SYNC_QUEUEING && state < SYNC_ILLEGAL ?
strings[state] :
"";
}
/** step info: whether engine is waiting for something */
bool m_stepIsWaiting;
/**
* Priority which determines position in queue.
* Lower is more important. PRI_DEFAULT is zero.
*/
int m_priority;
/** progress data, holding progress calculation related info */
ProgressData m_progData;
SourceStatuses_t m_sourceStatus;
uint32_t m_error;
SourceProgresses_t m_sourceProgress;
Timespec m_lastProgressTimestamp;
SourceProgresses_t m_lastProgress;
bool m_freeze;
// syncProgress() and sourceProgress() turn raw data from helper
// into usable information on D-Bus server side
void syncProgress(sysync::TProgressEventEnum type,
int32_t extra1, int32_t extra2, int32_t extra3);
void sourceProgress(sysync::TProgressEventEnum type,
const std::string &sourceName,
SyncMode sourceSyncMode,
int32_t extra1, int32_t extra2, int32_t extra3);
/** timer for fire status/progress usages */
Timer m_statusTimer;
Timer m_progressTimer;
/** the total number of sources to be restored */
int m_restoreSrcTotal;
/** the number of sources that have been restored */
int m_restoreSrcEnd;
/**
* Wrapper around useHelperAsync() which sets up the session
* to execute a specific operation (sync, command line, ...).
*/
void runOperationAsync(SessionCommon::RunOperation op,
const SuccessCb_t &helperReady,
const StringMap &env = StringMap());
/**
* A Session can be used for exactly one of the operations. This
* is the one. This gets set by the D-Bus method implementation
* which triggers the operation. All other D-Bus method
* implementations need to check it before allowing an operation
* or method call which would conflict or be illegal.
*/
SessionCommon::RunOperation m_runOperation;
/**
* If m_runOperation == OP_CMDLINE, then we need further information
* from the helper about the actual operation. We get that information
* via a sync progress signal with event == PEV_CUSTOM_START.
*/
SessionCommon::RunOperation m_cmdlineOp;
// parameters from syncExtended()
std::string m_syncMode;
StringMap m_syncEnv;
typedef std::map<std::string, SyncSourceReport> SyncSourceReports;
static void StoreSyncSourceReport(SyncSourceReports &reports, const std::string &name, const SyncSourceReport &report) { reports[name] = report; }
/** Recorded during a sync for getSyncSourceReport. */
SyncSourceReports m_syncSourceReports;
public:
SessionCommon::RunOperation getOperation() const { return m_runOperation; }
std::string getSyncMode() const { return m_syncMode; }
StringMap getSyncEnv() const { return m_syncEnv; }
/** Session.Attach() */
void attach(const GDBusCXX::Caller_t &caller);
/** Session.Detach() */
void detach(const GDBusCXX::Caller_t &caller);
/** Session.GetStatus() */
void getStatus(std::string &status,
uint32_t &error,
SourceStatuses_t &sources);
/** Session.GetProgress() */
void getAPIProgress(int32_t &progress,
APISourceProgresses_t &sources);
/** Internal, with more information. */
void getProgress(int32_t &progress,
SourceProgresses_t &sources);
/** Timestamp is set each time that m_progressSignal is triggered. */
Timespec getLastProgressTimestamp() const { return m_lastProgressTimestamp; }
/** SourceProgresses_t is set each time that m_progressSignal is triggered. */
void getLastProgress(Session::SourceProgresses_t &progress) const { progress = m_lastProgress; }
/** Session.Restore() */
void restore(const string &dir, bool before, const std::vector<std::string> &sources);
void restore2(const string &dir, bool before, const std::vector<std::string> &sources);
/** Session.checkPresence() */
void checkPresence (string &status);
/** Session.Execute() */
void execute(const vector<string> &args, const map<string, string> &vars);
void execute2(const vector<string> &args, const map<string, string> &vars);
private:
/**
* Must be called each time that properties changing the
* overall status are changed (m_syncStatus, m_error, m_sourceStatus).
* Ensures that the corresponding D-Bus signal is sent.
*
* Doesn't always send the signal immediately, because often it is
* likely that more status changes will follow shortly. To ensure
* that the "final" status is sent, call with flush=true.
*
* @param flush force sending the current status
*/
void fireStatus(bool flush = false);
/** like fireStatus() for progress information */
void fireProgress(bool flush = false);
/** Session.StatusChanged */
GDBusCXX::EmitSignal3<const std::string &,
uint32_t,
const SourceStatuses_t &> emitStatus;
/** Session.ProgressChanged */
GDBusCXX::EmitSignal2<int32_t,
const APISourceProgresses_t &> emitProgress;
public:
boost::signals2::signal<void (const std::string &,
uint32_t,
const SourceStatuses_t &)> m_statusSignal;
boost::signals2::signal<void (int32_t,
const SourceProgresses_t &)> m_progressSignal;
/**
* Sets minimum period of time in milliseconds between progress
* signals. Events coming to short after the previous one get
* skipped, to ensure that if an event gets emitted, it gets
* emitted at the time of the change.
*
* There is no guarantee that progress can be emitted very
* frequently (say, every second). In practice it gets updated at
* the end of processing each SyncML message.
*/
void setProgressTimeout(unsigned long ms) { m_progressTimer.setTimeout(ms); }
/**
* Sessions must always be held in a shared pointer
* because some operations depend on that. This
* constructor function here ensures that and
* also adds a weak pointer to the instance itself,
* so that it can create more shared pointers as
* needed.
*/
static boost::shared_ptr<Session> createSession(Server &server,
const std::string &peerDeviceID,
const std::string &config_name,
const std::string &session,
const std::vector<std::string> &flags = std::vector<std::string>());
/**
* automatically marks the session as completed before deleting it
*/
~Session();
/**
* explicitly mark an idle session as completed, even if it doesn't
* get deleted yet (exceptions not expected by caller)
*/
void done(bool success) throw () { doneCb(success); }
private:
Session(Server &server,
const std::string &peerDeviceID,
const std::string &config_name,
const std::string &session,
const std::vector<std::string> &flags = std::vector<std::string>());
boost::weak_ptr<Session> m_me;
boost::shared_ptr<InfoReq> m_passwordRequest;
void passwordRequest(const std::string &descr, const ConfigPasswordKey &key);
void sendViaConnection(const GDBusCXX::DBusArray<uint8_t> buffer,
const std::string &type,
const std::string &url);
void shutdownConnection();
void storeMessage(const GDBusCXX::DBusArray<uint8_t> &message,
const std::string &type);
void connectionState(const std::string &error);
/**
* Sends all messages via D-Bus, as if they came from the session
* helper. To be activated only temporarily while executing code
* in the server which is related to the session.
*/
virtual void messagev(const MessageOptions &options,
const char *format,
va_list args);
public:
enum {
PRI_CMDLINE = -10,
PRI_DEFAULT = 0,
PRI_CONNECTION = 10,
PRI_AUTOSYNC = 20
};
/**
* Default priority is 0. Higher means less important.
*/
void setPriority(int priority) { m_priority = priority; }
int getPriority() const { return m_priority; }
bool isServerAlerted() const { return m_serverAlerted; }
void setServerAlerted(bool serverAlerted) { m_serverAlerted = serverAlerted; }
void initServer(SharedBuffer data, const std::string &messageType);
void setStubConnection(const boost::shared_ptr<Connection> c) { m_connection = c; m_useConnection = static_cast<bool>(c); }
boost::weak_ptr<Connection> getStubConnection() { return m_connection; }
bool useStubConnection() { return m_useConnection; }
/**
* After the connection closes, the Connection instance is
* destructed immediately. This is necessary so that the
* corresponding cleanup can remove all other classes
* only referenced by the Connection.
*
* This leads to the problem that an active sync cannot
* query the final error code of the connection. This
* is solved by setting a generic error code here when
* the sync starts and overwriting it when the connection
* closes.
*/
void setStubConnectionError(const std::string &error) { m_connectionError = error; }
std::string getStubConnectionError() { return m_connectionError; }
Server &getServer() { return ReadOperations::m_server; }
std::string getConfigName() { return m_configName; }
std::string getSessionID() const { return m_sessionID; }
std::string getPeerDeviceID() const { return m_peerDeviceID; }
/** Session.GetFlags() */
std::vector<std::string> getFlags() { return m_flags; }
/** Session.GetConfigName() */
std::string getNormalConfigName() { return SyncConfig::normalizeConfigString(m_configName); }
/** Session.SetConfig() */
void setConfig(bool update, bool temporary,
const ReadOperations::Config_t &config);
/** Session.SetNamedConfig() */
void setNamedConfig(const std::string &configName,
bool update, bool temporary,
const ReadOperations::Config_t &config);
/** Session.Sync() */
void sync(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes) {
syncExtended(mode, sourceModes, StringMap());
}
void syncExtended(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes,
const StringMap &env);
/**
* finish the work started by sync once helper is ready (invoked
* by useHelperAsync() and thus may throw exceptions)
*/
void sync2(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes);
/** Session.Abort() */
void abort();
/** abort active session, trigger result once done */
void abortAsync(const SimpleResult &result);
/** Session.Suspend() */
void suspend();
/** Change freeze state, trigger result once done. */
void setFreezeAsync(bool freeze, const Result<void (bool)> &result);
bool getFreeze() const { return m_freeze; }
/**
* step info for engine: whether the engine is blocked by something
* If yes, 'waiting' will be appended as specifiers in the status string.
* see GetStatus documentation.
*/
void setWaiting(bool isWaiting);
SyncStatus getSyncStatus() const { return m_syncStatus; }
/** session was just activated */
typedef boost::signals2::signal<void ()> SessionActiveSignal_t;
SessionActiveSignal_t m_sessionActiveSignal;
/** sync is successfully started */
typedef boost::signals2::signal<void ()> SyncSuccessStartSignal_t;
SyncSuccessStartSignal_t m_syncSuccessStartSignal;
/** sync completed (may have failed) */
typedef boost::signals2::signal<void (SyncMLStatus, SyncReport)> DoneSignal_t;
DoneSignal_t m_doneSignal;
/** a source was synced, emitted multiple times during a multi-cycle sync */
typedef boost::signals2::signal<void (const std::string &, const SyncSourceReport &)> SourceSyncedSignal_t;
SourceSyncedSignal_t m_sourceSynced;
/** gets latest SyncSourceReport of a source, returns false if none available yet */
bool getSyncSourceReport(const std::string &sourceName, SyncSourceReport &report) const;
/**
* Called by server when the session is ready to run.
* Only the session itself can deactivate itself.
*/
void activateSession();
/**
* Called by server when it has a password response for the
* session. The session ensures that it only has one pending
* request at a time, so these parameters are enough to identify
* the request.
*/
void passwordResponse(bool timedOut, bool aborted, const std::string &password);
void setRemoteInitiated (bool remote) { m_remoteInitiated = remote;}
private:
/** set m_syncFilter and m_sourceFilters to config */
virtual bool setFilters(SyncConfig &config);
void dbusResultCb(const std::string &operation, bool success, const SyncReport &report, const std::string &error) throw();
void setFreezeDone(bool changed, const std::string &error,
bool freeze,
const Result<void (bool)> &result);
/**
* to be called inside a catch() clause: returns error for any
* pending D-Bus method and then calls doneCb()
*/
void failureCb() throw();
/**
* explicitly mark the session as completed, even if it doesn't
* get deleted yet (invoked directly or indirectly from event
* loop and thus must not throw exceptions)
*
* @param success if false, then ensure that m_error is set
* before finalizing the session
* @param report valid only in case of success
*/
void doneCb(bool success, const SyncReport &report = SyncReport()) throw();
};
SE_END_CXX
#endif // SESSION_H