d0c08bf0dd
It saved some typing, but isn't good style. Signed-off-by: Patrick Ohly <patrick.ohly@intel.com>
1132 lines
37 KiB
C++
1132 lines
37 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
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include <stddef.h>
|
|
#include <iostream>
|
|
#include <memory>
|
|
|
|
#include <libgen.h>
|
|
#ifdef HAVE_GLIB
|
|
#include <glib-object.h>
|
|
#endif
|
|
|
|
#ifdef DBUS_SERVICE
|
|
|
|
// must come before other header files which pull in boost/intrusive_ptr.hpp
|
|
// because it defines some intrusive_ptr_release implementations
|
|
#include <gdbus-cxx-bridge.h>
|
|
|
|
#include <syncevo/declarations.h>
|
|
|
|
SE_BEGIN_CXX
|
|
struct SourceStatus {
|
|
std::string m_mode;
|
|
std::string m_status;
|
|
uint32_t m_error;
|
|
};
|
|
SE_END_CXX
|
|
|
|
namespace GDBusCXX {
|
|
template<> struct dbus_traits<SyncEvo::SourceStatus> :
|
|
public dbus_struct_traits<SyncEvo::SourceStatus,
|
|
dbus_member<SyncEvo::SourceStatus, std::string, &SyncEvo::SourceStatus::m_mode,
|
|
dbus_member<SyncEvo::SourceStatus, std::string, &SyncEvo::SourceStatus::m_status,
|
|
dbus_member_single<SyncEvo::SourceStatus, uint32_t, &SyncEvo::SourceStatus::m_error> > > >
|
|
{};
|
|
}
|
|
|
|
using namespace GDBusCXX;
|
|
|
|
#endif
|
|
|
|
#include <boost/typeof/typeof.hpp>
|
|
|
|
#include <syncevo/Cmdline.h>
|
|
#include <syncevo/SyncContext.h>
|
|
#include <syncevo/SuspendFlags.h>
|
|
#include <syncevo/LogRedirect.h>
|
|
#include <syncevo/LocalTransportAgent.h>
|
|
#include <syncevo/CmdlineSyncClient.h>
|
|
|
|
#include <dlfcn.h>
|
|
#include <signal.h>
|
|
|
|
SE_BEGIN_CXX
|
|
|
|
#if defined(ENABLE_MAEMO) && defined (ENABLE_EBOOK)
|
|
|
|
// really override the symbol, even if redefined by EDSAbiWrapper
|
|
#undef e_contact_new_from_vcard
|
|
extern "C" EContact *e_contact_new_from_vcard(const char *vcard)
|
|
{
|
|
static BOOST_TYPEOF(e_contact_new_from_vcard) *impl;
|
|
|
|
if (!impl) {
|
|
impl = (BOOST_TYPEOF(impl))dlsym(RTLD_NEXT, "e_contact_new_from_vcard");
|
|
}
|
|
|
|
// Old versions of EDS-DBus parse_changes_array() call
|
|
// e_contact_new_from_vcard() with a pointer which starts
|
|
// with a line break; Evolution is not happy with that and
|
|
// refuses to parse it. This code forwards until it finds
|
|
// the first non-whitespace, presumably the BEGIN:VCARD.
|
|
while (*vcard && isspace(*vcard)) {
|
|
vcard++;
|
|
}
|
|
|
|
return impl ? impl(vcard) : NULL;
|
|
}
|
|
#endif
|
|
|
|
#ifdef DBUS_SERVICE
|
|
class RemoteSession;
|
|
typedef std::map<std::string, StringMap> Config_t;
|
|
|
|
/**
|
|
* Act as a dbus server. All requests to dbus server
|
|
* are passed through this class.
|
|
*/
|
|
class RemoteDBusServer : public DBusRemoteObject
|
|
{
|
|
public:
|
|
RemoteDBusServer();
|
|
|
|
GMainLoop *getLoop() { return m_loop; }
|
|
|
|
/**
|
|
* Check whether the server is started and can be attached.
|
|
* Printing an error message is optional, some callers might
|
|
* prefer a different kind of error handling.
|
|
*/
|
|
bool checkStarted(bool printError = true);
|
|
|
|
/**
|
|
* execute arguments from command line
|
|
* @param args the arguments of command line
|
|
* @param config the config name parsed from arguments if has
|
|
* @param runSync arguments to run a sync
|
|
* @return true if successfully
|
|
*/
|
|
bool execute(const std::vector<std::string> &args, const std::string &config, bool runSync);
|
|
|
|
/**
|
|
* To implement the feature of '--monitor' option, monitor a
|
|
* given config if there is a session running.
|
|
* If config is empty, then peak a running session to monitor.
|
|
* @param config the config name parsed from arguments if has
|
|
* @return true if successfully
|
|
*/
|
|
bool monitor(const std::string &config);
|
|
|
|
/**
|
|
* To implement the feature of '--status' without a server.
|
|
* get and print all running sessions in the dbus server
|
|
*/
|
|
void runningSessions();
|
|
|
|
/** whether the dbus call(s) has/have completed */
|
|
bool done() { return m_replyTotal == m_replyCounter; }
|
|
|
|
/** one reply returns. Increase reply counter. */
|
|
void replyInc();
|
|
|
|
/** set whether there is an error */
|
|
void setResult(bool result) { m_result = result; }
|
|
|
|
/** call 'Server.InfoResponse' */
|
|
void infoResponse(const std::string &id, const std::string &state, const StringMap &resp);
|
|
|
|
private:
|
|
/** call 'Attach' until it returns */
|
|
void attachSync();
|
|
|
|
/**
|
|
* callback of 'Server.Attach':
|
|
* also set up a watch and add watch callback when the daemon is gone,
|
|
* then do version check before returning
|
|
*/
|
|
void attachCb(const boost::shared_ptr<Watch> &watch, const std::string &error);
|
|
|
|
/**
|
|
* second half of attaching: check version and print warning
|
|
*/
|
|
void versionCb(const StringMap &versions, const std::string &error);
|
|
|
|
/** callback of 'Server.SessionChanged' */
|
|
void sessionChangedCb(const DBusObject_t &object, bool active);
|
|
|
|
/** callback of 'Server.LogOutput' */
|
|
void logOutputCb(const DBusObject_t &object, const std::string &level, const std::string &log, const std::string &procname);
|
|
|
|
/** callback of 'Server.InfoRequest' */
|
|
void infoReqCb(const std::string &,
|
|
const DBusObject_t &,
|
|
const std::string &,
|
|
const std::string &,
|
|
const std::string &,
|
|
const StringMap &);
|
|
|
|
/** callback of Server.InfoResponse */
|
|
void infoResponseCb(const std::string &error);
|
|
|
|
/** callback of calling 'Server.StartSession' */
|
|
void startSessionCb(const DBusObject_t &session, const std::string &error);
|
|
|
|
/**
|
|
* receives org.freedesktop.DBus.NameOwnerChanged signals,
|
|
* watches for changes of org.syncevolution.server
|
|
*/
|
|
void nameOwnerChangedCB(const std::string &name,
|
|
const std::string &oldOwner,
|
|
const std::string &newOwner)
|
|
{
|
|
if (name == "org.syncevolution") {
|
|
SE_LOG_ERROR(NULL, "The SyncEvolution D-Bus service died unexpectedly. A running sync might still be able to complete normally, but the command line cannot report progress anymore and has to quit.");
|
|
m_result = false;
|
|
g_main_loop_quit(m_loop);
|
|
}
|
|
}
|
|
|
|
|
|
/** update active session vector according to 'SessionChanged' signal */
|
|
void updateSessions(const std::string &session, bool active);
|
|
|
|
/** check m_session is active */
|
|
bool isActive();
|
|
|
|
/** get all running sessions. Used internally. */
|
|
void getRunningSessions();
|
|
|
|
/** called when daemon has gone */
|
|
void daemonGone();
|
|
|
|
/** set the total number of replies we must wait */
|
|
void resetReplies(int total = 1)
|
|
{
|
|
m_replyTotal = total;
|
|
m_replyCounter = 0;
|
|
}
|
|
|
|
/** signal handler for 'CTRL-C' */
|
|
static void handleSignal(int sig);
|
|
|
|
// session used for signal handler,
|
|
// used to call 'suspend' and 'abort'
|
|
static boost::weak_ptr<RemoteSession> g_session;
|
|
|
|
// the main loop
|
|
GMainLoop *m_loop;
|
|
// whether client can attach to the daemon.
|
|
// It is also used to indicate whether daemon is ready to use.
|
|
bool m_attached;
|
|
// error flag
|
|
bool m_result;
|
|
// config name
|
|
std::string m_configName;
|
|
// active session object path
|
|
boost::shared_ptr<std::string> m_activeSession;
|
|
// session created or monitored
|
|
boost::shared_ptr<RemoteSession> m_session;
|
|
// active sessions after listening to 'SessionChanged' signals
|
|
std::vector<std::string> m_activeSessions;
|
|
// the number of total dbus calls
|
|
unsigned int m_replyTotal;
|
|
// the number of returned dbus calls
|
|
unsigned int m_replyCounter;
|
|
// listen to dbus server signal 'SessionChanged'
|
|
SignalWatch<DBusObject_t, bool> m_sessionChanged;
|
|
// listen to dbus server signal 'LogOutput'
|
|
SignalWatch<DBusObject_t, std::string, std::string, std::string> m_logOutput;
|
|
// listen to dbus server signal 'InfoRequest'
|
|
SignalWatch<std::string,
|
|
DBusObject_t,
|
|
std::string,
|
|
std::string,
|
|
std::string,
|
|
StringMap > m_infoReq;
|
|
|
|
/** watch daemon whether it is gone */
|
|
boost::shared_ptr<Watch> m_daemonWatch;
|
|
};
|
|
|
|
/**
|
|
* Act as a session. All requests to a session are passed
|
|
* through this class.
|
|
*/
|
|
class RemoteSession : public DBusRemoteObject
|
|
{
|
|
public:
|
|
RemoteSession(RemoteDBusServer &server, const std::string &path);
|
|
RemoteDBusServer &getServer() { return m_server; }
|
|
|
|
/**
|
|
* call 'Execute' method of 'Session' in dbus server
|
|
* without waiting for return
|
|
*/
|
|
void executeAsync(const std::vector<std::string> &args);
|
|
|
|
/**
|
|
* call 'Suspend' or 'Abort' method of 'Session' in dbus server
|
|
* without waiting for return
|
|
*/
|
|
void interruptAsync(const char *operation);
|
|
|
|
/** copy config name from server's config */
|
|
void setConfigName(const Config_t &config);
|
|
|
|
/** get config name of this session */
|
|
std::string configName() { return m_configName; }
|
|
|
|
/** status 'done' is sent by session */
|
|
bool statusDone() { return boost::iequals(m_status, "done"); }
|
|
|
|
/** get current status */
|
|
std::string status() { return m_status; }
|
|
|
|
/** set the flag to indicate the session is running sync */
|
|
void setRunSync(bool runSync) { m_runSync = runSync; }
|
|
|
|
/** pass through logoutput and print them if m_output is true */
|
|
void logOutput(Logger::Level level, const std::string &log, const std::string &procname);
|
|
|
|
/** set whether to print output */
|
|
void setOutput(bool output) { m_output = output; }
|
|
|
|
/** process signals from daemon */
|
|
void infoReq(const std::string &id,
|
|
const DBusObject_t &session,
|
|
const std::string &state,
|
|
const std::string &handler,
|
|
const std::string &type,
|
|
const StringMap ¶ms);
|
|
|
|
/** remove InfoReq objects from map */
|
|
void removeInfoReq(const std::string &id);
|
|
|
|
typedef std::map<std::string, SourceStatus> SourceStatuses_t;
|
|
|
|
private:
|
|
/**
|
|
* InfoReq to handle info requests from daemon and
|
|
* call 'Server.InfoResponse' to send its response
|
|
*/
|
|
class InfoReq
|
|
{
|
|
/** the session reference */
|
|
RemoteSession &m_session;
|
|
/** the id of InfoRequest */
|
|
std::string m_id;
|
|
/** the type of InfoRequest */
|
|
std::string m_type;
|
|
/** the response map sent to the daemon*/
|
|
StringMap m_resp;
|
|
|
|
/** InfoRequest state */
|
|
enum State {
|
|
INIT, // init
|
|
WORKING, //'working'
|
|
RESPONSE, // 'response'
|
|
DONE // 'done'
|
|
};
|
|
/** the current state of InfoRequest */
|
|
State m_state;
|
|
public:
|
|
InfoReq(RemoteSession &session, const std::string &id, const std::string &type);
|
|
|
|
/**
|
|
* process the info request dispatched by session
|
|
*/
|
|
void process(const std::string &id,
|
|
const DBusObject_t &session,
|
|
const std::string &state,
|
|
const std::string &handler,
|
|
const std::string &type,
|
|
const StringMap ¶ms);
|
|
};
|
|
|
|
/** callback of calling 'Session.Execute' */
|
|
void executeCb(const std::string &error);
|
|
|
|
/** callback of 'Session.StatusChanged' */
|
|
void statusChangedCb(const std::string &status,
|
|
uint32_t errorCode,
|
|
const SourceStatuses_t &sourceStatus);
|
|
|
|
/** callback of 'Session.Suspend' */
|
|
void suspendCb(const std::string &);
|
|
|
|
/** callback of 'Session.Abort' */
|
|
void abortCb(const std::string &);
|
|
|
|
/**
|
|
* implement requirements from info req. Called by InfoReq.
|
|
*/
|
|
void handleInfoReq(const std::string &type, const StringMap ¶ms, StringMap &resp);
|
|
|
|
/** dbus server */
|
|
RemoteDBusServer &m_server;
|
|
|
|
/* whether to log output */
|
|
bool m_output;
|
|
|
|
/** config name of the session */
|
|
std::string m_configName;
|
|
|
|
/** current status */
|
|
std::string m_status;
|
|
|
|
/** session is running sync */
|
|
bool m_runSync;
|
|
|
|
/** signal watch 'StatusChanged' */
|
|
SignalWatch<std::string, uint32_t, SourceStatuses_t> m_statusChanged;
|
|
|
|
/** InfoReq map. store all infoReq belongs to this session */
|
|
std::map<std::string, boost::shared_ptr<InfoReq> > m_infoReqs;
|
|
};
|
|
|
|
/**
|
|
* Get current known environment variables, which might be used
|
|
* in executing command line arguments. This is only necessary
|
|
* when using dbus daemon.
|
|
* @param vars the returned environment variables
|
|
*/
|
|
static void getEnvVars(std::map<std::string, std::string> &vars);
|
|
|
|
#endif
|
|
|
|
extern "C"
|
|
int main( int argc, char **argv )
|
|
{
|
|
if (boost::ends_with(argv[0], "syncevo-local-sync")) {
|
|
return LocalTransportMain(argc, argv);
|
|
}
|
|
|
|
// Intercept stderr and route it through our logging.
|
|
// stdout is printed normally. Deconstructing it when
|
|
// leaving main() does one final processing of pending
|
|
// output.
|
|
PushLogger<LogRedirect> redirect(new LogRedirect(LogRedirect::STDERR));
|
|
setvbuf(stderr, NULL, _IONBF, 0);
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
|
|
SyncContext::initMain("syncevolution");
|
|
|
|
// Expand PATH to cover the directory we were started from?
|
|
// This might be needed to find normalize_vcard.
|
|
char *exe = strdup(argv[0]);
|
|
if (!exe) {
|
|
SE_THROW("out of memory");
|
|
}
|
|
if (strchr(exe, '/') ) {
|
|
char *dir = dirname(exe);
|
|
std::string path;
|
|
char *oldpath = getenv("PATH");
|
|
if (oldpath) {
|
|
path += oldpath;
|
|
path += ":";
|
|
}
|
|
path += dir;
|
|
setenv("PATH", path.c_str(), 1);
|
|
}
|
|
free(exe);
|
|
|
|
try {
|
|
if (getenv("SYNCEVOLUTION_DEBUG")) {
|
|
Logger::instance().setLevel(Logger::DEBUG);
|
|
}
|
|
|
|
SyncEvo::KeyringSyncCmdline cmdline(argc, argv);
|
|
std::vector<std::string> parsedArgs;
|
|
if(!cmdline.parse(parsedArgs)) {
|
|
return 1;
|
|
}
|
|
|
|
if (cmdline.dontRun()) {
|
|
return 0;
|
|
}
|
|
|
|
Bool useDaemon = cmdline.useDaemon();
|
|
|
|
if(cmdline.monitor()) {
|
|
|
|
#ifdef DBUS_SERVICE
|
|
// monitor a session
|
|
RemoteDBusServer server;
|
|
if(server.checkStarted() && server.monitor(cmdline.getConfigName())) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
#else
|
|
SE_LOG_ERROR(NULL, "this syncevolution binary was compiled without support for monitoring a background sync");
|
|
return 1;
|
|
#endif
|
|
} else if(cmdline.status() &&
|
|
cmdline.getConfigName().empty()) {
|
|
|
|
#ifdef DBUS_SERVICE
|
|
// '--status' and no server name, try to get running sessions
|
|
RemoteDBusServer server;
|
|
if(server.checkStarted()) {
|
|
server.runningSessions();
|
|
return 0;
|
|
}
|
|
return 1;
|
|
#else
|
|
SE_LOG_SHOW(NULL, "this syncevolution binary was compiled without support for monitoring a background sync");
|
|
return 1;
|
|
#endif
|
|
} else if (useDaemon ||
|
|
!useDaemon.wasSet()) {
|
|
#ifdef DBUS_SERVICE
|
|
RemoteDBusServer server;
|
|
|
|
// Running execute() without the server available will print errors.
|
|
// Avoid that unless the user explicitly asked for the daemon.
|
|
bool result = server.checkStarted(false);
|
|
if (useDaemon.wasSet() || result) {
|
|
return !server.execute(parsedArgs, cmdline.getConfigName(), cmdline.isSync());
|
|
} else {
|
|
// User didn't select --use-daemon and thus doesn't need to know about it
|
|
// not being available.
|
|
// SE_LOG_SHOW(NULL, "WARNING: cannot run syncevolution as daemon. "
|
|
// "Trying to run it without daemon.");
|
|
}
|
|
#else
|
|
if (useDaemon.wasSet()) {
|
|
SE_LOG_SHOW(NULL, "ERROR: this syncevolution binary was compiled without support of daemon. "
|
|
"Either run syncevolution with '--use-daemon=no' or without that option.");
|
|
return 1;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// if forcing not using daemon or trying to use daemon with failures,
|
|
// run arguments in the process
|
|
if (!useDaemon.wasSet() ||
|
|
!useDaemon) {
|
|
EDSAbiWrapperInit();
|
|
|
|
/*
|
|
* don't log errors to cerr: LogRedirect cannot distinguish
|
|
* between our valid error messages and noise from other
|
|
* libs, therefore it would get suppressed (logged at
|
|
* level DEVELOPER, while output is at most INFO)
|
|
*/
|
|
if (cmdline.run()) {
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
} catch ( const std::exception &ex ) {
|
|
SE_LOG_ERROR(NULL, "%s", ex.what());
|
|
} catch (...) {
|
|
SE_LOG_ERROR(NULL, "unknown error");
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#ifdef DBUS_SERVICE
|
|
/********************** RemoteDBusServer implementation **************************/
|
|
RemoteDBusServer::RemoteDBusServer() :
|
|
DBusRemoteObject(dbus_get_bus_connection("SESSION", NULL, true, NULL),
|
|
"/org/syncevolution/Server",
|
|
"org.syncevolution.Server",
|
|
"org.syncevolution",
|
|
true),
|
|
m_attached(false), m_result(true),
|
|
m_replyTotal(0), m_replyCounter(0),
|
|
m_sessionChanged(*this,"SessionChanged"),
|
|
m_logOutput(*this, "LogOutput"),
|
|
m_infoReq(*this, "InfoRequest")
|
|
{
|
|
m_loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
if (getConnection()) {
|
|
//check whether we can attach to the daemon
|
|
//also set up the daemon watch when attaching to server
|
|
attachSync();
|
|
if(m_attached) {
|
|
m_sessionChanged.activate(boost::bind(&RemoteDBusServer::sessionChangedCb, this, _1, _2));
|
|
m_logOutput.activate(boost::bind(&RemoteDBusServer::logOutputCb, this, _1, _2, _3, _4));
|
|
m_infoReq.activate(boost::bind(&RemoteDBusServer::infoReqCb, this, _1, _2, _3, _4, _5, _6));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool RemoteDBusServer::checkStarted(bool printError)
|
|
{
|
|
if(!m_attached) {
|
|
if (printError) {
|
|
SE_LOG_ERROR(NULL, "SyncEvolution D-Bus server not available.");
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void RemoteDBusServer::attachSync()
|
|
{
|
|
resetReplies();
|
|
DBusClientCall<boost::shared_ptr<Watch> > attach(*this, "Attach");
|
|
attach.start(boost::bind(&RemoteDBusServer::attachCb, this, _1, _2));
|
|
while(!done()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
}
|
|
|
|
void RemoteDBusServer::attachCb(const boost::shared_ptr<Watch> &watch, const std::string &error)
|
|
{
|
|
if(error.empty()) {
|
|
//if attach is successful, watch server whether it is gone
|
|
m_daemonWatch = watch;
|
|
m_daemonWatch->setCallback(boost::bind(&RemoteDBusServer::daemonGone,this));
|
|
|
|
// don't print error information, leave it to caller
|
|
m_attached = true;
|
|
|
|
// do a version check now before calling replyInc()
|
|
DBusClientCall< StringMap > getVersions(*this, "GetVersions");
|
|
getVersions.start(boost::bind(&RemoteDBusServer::versionCb, this, _1, _2));
|
|
} else {
|
|
// done with attach phase, skip version check
|
|
replyInc();
|
|
}
|
|
}
|
|
|
|
void RemoteDBusServer::versionCb(const StringMap &versions,
|
|
const std::string &error)
|
|
{
|
|
replyInc();
|
|
if (!error.empty()) {
|
|
SE_LOG_DEBUG(NULL, "Server.GetVersions(): %s", error.c_str());
|
|
} else {
|
|
StringMap::const_iterator it = versions.find("version");
|
|
if (it != versions.end() &&
|
|
it->second != VERSION) {
|
|
SE_LOG_INFO(NULL,
|
|
"proceeding despite version mismatch between command line client 'syncevolution' and 'syncevo-dbus-server' (%s != %s)",
|
|
it->second.c_str(),
|
|
VERSION);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteDBusServer::logOutputCb(const DBusObject_t &object,
|
|
const std::string &level,
|
|
const std::string &log,
|
|
const std::string &procname)
|
|
{
|
|
if (m_session &&
|
|
(boost::equals(object, getPath()) ||
|
|
boost::equals(object, m_session->getPath()))) {
|
|
m_session->logOutput(Logger::strToLevel(level.c_str()), log, procname);
|
|
}
|
|
}
|
|
|
|
void RemoteDBusServer::infoReqCb(const std::string &id,
|
|
const DBusObject_t &session,
|
|
const std::string &state,
|
|
const std::string &handler,
|
|
const std::string &type,
|
|
const StringMap ¶ms)
|
|
{
|
|
// if m_session is null, just ignore
|
|
if(m_session) {
|
|
m_session->infoReq(id, session, state, handler, type, params);
|
|
}
|
|
}
|
|
|
|
void RemoteDBusServer::infoResponse(const std::string &id,
|
|
const std::string &state,
|
|
const StringMap &resp)
|
|
{
|
|
//call Server.InfoResponse
|
|
DBusClientCall<> call(*this, "InfoResponse");
|
|
call.start(boost::bind(&RemoteDBusServer::infoResponseCb, this, _1), id, state, resp);
|
|
}
|
|
|
|
void RemoteDBusServer::infoResponseCb(const std::string &error)
|
|
{
|
|
replyInc();
|
|
if(!error.empty()) {
|
|
SE_LOG_ERROR(NULL, "information response failed.");
|
|
m_result = false;
|
|
}
|
|
g_main_loop_quit(m_loop);
|
|
}
|
|
|
|
void RemoteDBusServer::sessionChangedCb(const DBusObject_t &object, bool active)
|
|
{
|
|
// update active sessions if needed
|
|
updateSessions(object, active);
|
|
g_main_loop_quit(m_loop);
|
|
}
|
|
|
|
void RemoteDBusServer::daemonGone()
|
|
{
|
|
//print error info and exit
|
|
SE_LOG_ERROR(NULL, "Background sync daemon has gone.");
|
|
exit(1);
|
|
}
|
|
|
|
static void SuspendFlagsChanged(RemoteSession *session,
|
|
SuspendFlags &flags)
|
|
{
|
|
if (flags.getState() == SuspendFlags::SUSPEND) {
|
|
session->interruptAsync("Suspend");
|
|
} else if(flags.getState() == SuspendFlags::ABORT) {
|
|
session->interruptAsync("Abort");
|
|
}
|
|
}
|
|
|
|
bool RemoteDBusServer::execute(const std::vector<std::string> &args, const std::string &peer, bool runSync)
|
|
{
|
|
//the basic workflow is:
|
|
//1) start a session
|
|
//2) waiting for the session becomes active
|
|
//3) execute 'arguments' once it is active
|
|
|
|
// start a new session
|
|
DBusClientCall<DBusObject_t> startSession(*this, "StartSessionWithFlags");
|
|
std::vector<std::string> flags;
|
|
if (!runSync) {
|
|
flags.push_back("no-sync");
|
|
}
|
|
startSession.start(boost::bind(&RemoteDBusServer::startSessionCb, this, _1, _2), peer, flags);
|
|
|
|
// wait until 'StartSession' returns
|
|
resetReplies();
|
|
while(!done()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
|
|
if(m_session) {
|
|
m_session->setRunSync(true);
|
|
|
|
// If the syncevo-dbus-server dies while we wait for some
|
|
// output from it, then we used to hang forever. Worse, if it
|
|
// happened while signal handling was active, then the command
|
|
// line tool couldn't even be killed with CTRL-C.
|
|
// To detect this case, we watch name owner changes for org.syncevolution.server.
|
|
// If it changes from now on, we know that our m_session became
|
|
// invalid. If it already changed, then the next calls for that
|
|
// session will fail.
|
|
#if 0
|
|
GDBusCXX::DBusRemoteObject daemon(getConnection(),
|
|
"/org/freedesktop/DBus",
|
|
"org.freedesktop.DBus",
|
|
"");
|
|
GDBusCXX::SignalWatch<std::string, std::string, std::string> nameOwnerChanged(daemon,
|
|
"NameOwnerChanged");
|
|
nameOwnerChanged.activate(boost::bind(&RemoteDBusServer::nameOwnerChangedCB, this,
|
|
_1, _2, _3));
|
|
#endif
|
|
|
|
//if session is not active, just wait
|
|
while(!isActive()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
// Logger::Level level = Logger::instance().getLevel();
|
|
// Logger::instance().setLevel(Logger::DEBUG);
|
|
resetReplies();
|
|
m_session->executeAsync(args);
|
|
|
|
|
|
while(m_result && !done()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
|
|
//if encoutering errors, return
|
|
if(!m_result) {
|
|
return m_result;
|
|
}
|
|
|
|
// Acticate signal handling in all cases.
|
|
// We let SuspendFlags catch them and then
|
|
// react in the normal event loop.
|
|
SuspendFlags &flags(SuspendFlags::getSuspendFlags());
|
|
boost::shared_ptr<SuspendFlags::Guard> signalGuard = flags.activate();
|
|
flags.m_stateChanged.connect(SuspendFlags::StateChanged_t::slot_type(SuspendFlagsChanged, m_session.get(), _1).track(m_session));
|
|
|
|
//wait until status is 'done'
|
|
while(m_result && !m_session->statusDone()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
|
|
//restore logging level
|
|
// Logger::instance().setLevel(level);
|
|
m_session->setRunSync(false);
|
|
}
|
|
return m_result;
|
|
}
|
|
|
|
void RemoteDBusServer::startSessionCb(const DBusObject_t &sessionPath, const std::string &error)
|
|
{
|
|
replyInc();
|
|
if(!error.empty()) {
|
|
SE_LOG_ERROR(NULL, "starting D-Bus session failed: %s", error.c_str());
|
|
if (error.find("org.freedesktop.DBus.Error.UnknownMethod") != error.npos) {
|
|
SE_LOG_INFO(NULL, "syncevo-dbus-server is most likely too old");
|
|
}
|
|
m_result = false;
|
|
g_main_loop_quit(m_loop);
|
|
return;
|
|
}
|
|
m_session.reset(new RemoteSession(*this, sessionPath));
|
|
g_main_loop_quit(m_loop);
|
|
}
|
|
|
|
bool RemoteDBusServer::isActive()
|
|
{
|
|
/** if current session is active and then start to call 'Execute' method */
|
|
if(m_session) {
|
|
for (const std::string &session: m_activeSessions) {
|
|
if(boost::equals(m_session->getPath(), session.c_str())) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RemoteDBusServer::runningSessions()
|
|
{
|
|
//the basic working flow is:
|
|
//1) get all sessions
|
|
//2) check each session and collect running sessions
|
|
//3) get config name of running sessions and print them
|
|
std::vector<DBusObject_t> sessions = DBusClientCall< std::vector<DBusObject_t> >(*this, "GetSessions")();
|
|
|
|
if (sessions.empty()) {
|
|
SE_LOG_SHOW(NULL, "Background sync daemon is idle.");
|
|
} else {
|
|
SE_LOG_SHOW(NULL, "Running session(s): ");
|
|
|
|
// create local objects for sessions
|
|
for (const DBusObject_t &path: sessions) {
|
|
RemoteSession session(*this, path);
|
|
|
|
// Get status. Slight race condition here, session might
|
|
// disappear before we can ask. In that case we fail by
|
|
// showing the exception string instead of showing some
|
|
// more comprehensible error message. Unlikely, so don't
|
|
// bother...
|
|
std::tuple<std::string, uint32_t, RemoteSession::SourceStatuses_t> status =
|
|
DBusClientCall<std::string, uint32_t, RemoteSession::SourceStatuses_t>(session, "GetStatus")();
|
|
std::string syncStatus = std::get<0>(status);
|
|
if (boost::istarts_with(syncStatus, "running")) {
|
|
Config_t config = DBusClientCall<Config_t>(session, "GetConfig")(false);
|
|
session.setConfigName(config);
|
|
|
|
if (!session.configName().empty()) {
|
|
SE_LOG_SHOW(NULL, " %s (%s)", session.configName().c_str(), session.getPath());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteDBusServer::updateSessions(const std::string &session, bool active)
|
|
{
|
|
if(active) {
|
|
//add it into active list
|
|
m_activeSessions.push_back(session);
|
|
} else {
|
|
//if inactive, remove it from active list
|
|
for(std::vector<std::string>::iterator it = m_activeSessions.begin();
|
|
it != m_activeSessions.end(); ++it) {
|
|
if(boost::equals(session, *it)) {
|
|
m_activeSessions.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteDBusServer::replyInc()
|
|
{
|
|
// increase counter and check whether all replies are returned
|
|
m_replyCounter++;
|
|
if(done()) {
|
|
g_main_loop_quit(m_loop);
|
|
}
|
|
}
|
|
|
|
bool RemoteDBusServer::monitor(const std::string &peer)
|
|
{
|
|
//the basic working flow is:
|
|
//1) get all sessions
|
|
//2) check each session and collect running sessions or
|
|
//3) peak one session with the given peer and monitor it
|
|
std::vector<DBusObject_t> sessions = DBusClientCall< std::vector<DBusObject_t> >(*this, "GetSessions")();
|
|
|
|
if (sessions.empty()) {
|
|
SE_LOG_SHOW(NULL, "Background sync daemon is idle, no session available to be be monitored.");
|
|
} else {
|
|
// cheating: client and server might normalize the peer name differently...
|
|
std::string peerNorm = SyncConfig::normalizeConfigString(peer);
|
|
|
|
// create local objects for sessions
|
|
for (const DBusObject_t &path: sessions) {
|
|
boost::shared_ptr<RemoteSession> session(new RemoteSession(*this, path));
|
|
|
|
std::tuple<std::string, uint32_t, RemoteSession::SourceStatuses_t> status =
|
|
DBusClientCall<std::string, uint32_t, RemoteSession::SourceStatuses_t>(*session, "GetStatus")();
|
|
std::string syncStatus = std::get<0>(status);
|
|
if (boost::istarts_with(syncStatus, "running")) {
|
|
Config_t config = DBusClientCall<Config_t>(*session, "GetConfig")(false);
|
|
session->setConfigName(config);
|
|
|
|
if (peer.empty() ||
|
|
peerNorm == session->configName()) {
|
|
SE_LOG_SHOW(NULL, "Monitoring '%s' (%s)\n",
|
|
session->configName().c_str(),
|
|
session->getPath());
|
|
// set DBusServer::m_session so that RemoteSession::logOutput gets called
|
|
// and enable printing that output
|
|
m_session = session;
|
|
session->setOutput(true);
|
|
|
|
// now wait for session to complete
|
|
while (!session->statusDone()) {
|
|
g_main_loop_run(getLoop());
|
|
}
|
|
|
|
SE_LOG_SHOW(NULL, "Monitoring done");
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
SE_LOG_SHOW(NULL, "'%s' is not running.", peer.c_str());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/********************** RemoteSession implementation **************************/
|
|
RemoteSession::RemoteSession(RemoteDBusServer &server,
|
|
const std::string &path) :
|
|
DBusRemoteObject(server.getConnection(),
|
|
path,
|
|
"org.syncevolution.Session",
|
|
"org.syncevolution"),
|
|
m_server(server), m_output(false), m_runSync(false),
|
|
m_statusChanged(*this, "StatusChanged")
|
|
{
|
|
m_statusChanged.activate(boost::bind(&RemoteSession::statusChangedCb, this, _1, _2, _3));
|
|
}
|
|
|
|
void RemoteSession::executeAsync(const std::vector<std::string> &args)
|
|
{
|
|
//start to print outputs
|
|
m_output = true;
|
|
std::map<std::string, std::string> vars;
|
|
getEnvVars(vars);
|
|
DBusClientCall<> call(*this, "Execute");
|
|
call.start(boost::bind(&RemoteSession::executeCb, this, _1), args, vars);
|
|
}
|
|
|
|
void RemoteSession::executeCb(const std::string &error)
|
|
{
|
|
m_server.replyInc();
|
|
if(!error.empty()) {
|
|
SE_LOG_ERROR(NULL, "running the command line inside the D-Bus server failed");
|
|
m_server.setResult(false);
|
|
//end to print outputs
|
|
m_output = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void RemoteSession::statusChangedCb(const std::string &status,
|
|
uint32_t errorCode,
|
|
const SourceStatuses_t &sourceStatus)
|
|
{
|
|
m_status = status;
|
|
|
|
if (errorCode) {
|
|
m_server.setResult(false);
|
|
g_main_loop_quit(m_server.getLoop());
|
|
}
|
|
|
|
if(status == "done") {
|
|
//if session is done, quit the loop
|
|
g_main_loop_quit(m_server.getLoop());
|
|
m_output = false;
|
|
}
|
|
}
|
|
|
|
void RemoteSession::setConfigName(const Config_t &config)
|
|
{
|
|
Config_t::const_iterator it = config.find("");
|
|
if(it != config.end()) {
|
|
StringMap global = it->second;
|
|
StringMap::iterator git = global.find("configName");
|
|
if(git != global.end()) {
|
|
m_configName = git->second;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void interruptCb(const std::string &error)
|
|
{
|
|
if (!error.empty()) {
|
|
SE_LOG_DEBUG(NULL, "interruptAsync() error from remote: %s", error.c_str());
|
|
}
|
|
}
|
|
|
|
void RemoteSession::interruptAsync(const char *operation)
|
|
{
|
|
// call Suspend() without checking result
|
|
DBusClientCall<> suspend(*this, operation);
|
|
suspend.start(interruptCb);
|
|
}
|
|
|
|
void RemoteSession::logOutput(Logger::Level level, const std::string &log, const std::string &procname)
|
|
{
|
|
if(m_output) {
|
|
Logger::MessageOptions options(level);
|
|
options.m_processName = &procname;
|
|
SyncEvo::Logger::instance().messageWithOptions(options, "%s", log.c_str());
|
|
}
|
|
}
|
|
|
|
void RemoteSession::infoReq(const std::string &id,
|
|
const DBusObject_t &session,
|
|
const std::string &state,
|
|
const std::string &handler,
|
|
const std::string &type,
|
|
const StringMap ¶ms)
|
|
{
|
|
//if command line runs a sync, then try to handle req
|
|
if (m_runSync && boost::iequals(session, getPath())) {
|
|
//only handle password now
|
|
if (boost::iequals("password", type)) {
|
|
std::map<std::string, boost::shared_ptr<InfoReq> >::iterator it = m_infoReqs.find(id);
|
|
if (it != m_infoReqs.end()) {
|
|
it->second->process(id, session, state, handler, type, params);
|
|
} else {
|
|
boost::shared_ptr<InfoReq> passwd(new InfoReq(*this, id, type));
|
|
m_infoReqs[id] = passwd;
|
|
passwd->process(id, session, state, handler, type, params);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteSession::handleInfoReq(const std::string &type, const StringMap ¶ms, StringMap &resp)
|
|
{
|
|
if (boost::iequals(type, "password")) {
|
|
char buffer[256];
|
|
|
|
std::string descr;
|
|
StringMap::const_iterator it = params.find("description");
|
|
if (it != params.end()) {
|
|
descr = it->second;
|
|
}
|
|
printf("Enter password for %s: ", descr.c_str());
|
|
fflush(stdout);
|
|
if (fgets(buffer, sizeof(buffer), stdin) &&
|
|
strcmp(buffer, "\n")) {
|
|
size_t len = strlen(buffer);
|
|
if (len && buffer[len - 1] == '\n') {
|
|
buffer[len - 1] = 0;
|
|
}
|
|
resp["password"] = std::string(buffer);
|
|
} else {
|
|
SE_LOG_ERROR(NULL, "could not read password for %s", descr.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteSession::removeInfoReq(const std::string &id)
|
|
{
|
|
std::map<std::string, boost::shared_ptr<InfoReq> >::iterator it = m_infoReqs.find(id);
|
|
if (it != m_infoReqs.end()) {
|
|
m_infoReqs.erase(it);
|
|
}
|
|
}
|
|
|
|
/********************** InfoReq implementation **************************/
|
|
RemoteSession::InfoReq::InfoReq(RemoteSession &session,
|
|
const std::string &id,
|
|
const std::string &type)
|
|
:m_session(session), m_id(id), m_type(type), m_state(INIT)
|
|
{
|
|
}
|
|
|
|
void RemoteSession::InfoReq::process(const std::string &id,
|
|
const DBusObject_t &session,
|
|
const std::string &state,
|
|
const std::string &handler,
|
|
const std::string &type,
|
|
const StringMap ¶ms)
|
|
{
|
|
//only handle info belongs to this InfoReq
|
|
if (boost::equals(m_id, id)) {
|
|
//check the state and response if necessary
|
|
if (m_state == INIT && boost::iequals("request", state)) {
|
|
m_session.getServer().infoResponse(m_id, "working", StringMap());
|
|
m_state = WORKING;
|
|
m_session.handleInfoReq(type, params, m_resp);
|
|
} else if ((m_state == WORKING) && boost::iequals("waiting", state)) {
|
|
m_session.getServer().infoResponse(m_id, "response", m_resp);
|
|
m_state = RESPONSE;
|
|
} else if (boost::iequals("done", state)) {
|
|
//if request is 'done', remove it
|
|
m_session.removeInfoReq(m_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void getEnvVars(std::map<std::string, std::string> &vars)
|
|
{
|
|
//environment variables used to run command line
|
|
static const char *varNames[] = {
|
|
"http_proxy",
|
|
"HOME",
|
|
"PATH",
|
|
"SYNCEVOLUTION_BACKEND_DIR",
|
|
"SYNCEVOLUTION_DEBUG",
|
|
"SYNCEVOLUTION_GNUTLS_DEBUG",
|
|
"SYNCEVOLUTION_TEMPLATE_DIR",
|
|
"SYNCEVOLUTION_XML_CONFIG_DIR",
|
|
"SYNC_EVOLUTION_EVO_CALENDAR_DELAY",
|
|
"XDG_CACHE_HOME",
|
|
"XDG_CONFIG_HOME",
|
|
"XDG_DATA_HOME"
|
|
};
|
|
|
|
for (unsigned int i = 0; i < sizeof(varNames) / sizeof(const char*); i++) {
|
|
const char *value;
|
|
//get values of environment variables if they are set
|
|
if ((value = getenv(varNames[i])) != NULL) {
|
|
vars.insert(make_pair(varNames[i], value));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
SE_END_CXX
|