443a3b5925
When glib logging is invoked, it check the application name and complains if not set: ** (process:10130): WARNING **: g_set_application_name not set. With output redirection we hide this in the command line, but it showed up in client-test when the system was in a state were another glib WARNING was triggered. This patch sets the unlocalized program name instead of using a localized application name, because we don't have and don't need localization for these two programs. That seems to satisfy glib.
1264 lines
39 KiB
C++
1264 lines
39 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>
|
|
using namespace std;
|
|
|
|
#include <libgen.h>
|
|
#ifdef HAVE_GLIB
|
|
#include <glib-object.h>
|
|
#endif
|
|
|
|
#include <syncevo/Cmdline.h>
|
|
#include "EvolutionSyncSource.h"
|
|
#include <syncevo/SyncContext.h>
|
|
#include <syncevo/LogRedirect.h>
|
|
#include "CmdlineSyncClient.h"
|
|
|
|
#include <dlfcn.h>
|
|
#include <signal.h>
|
|
|
|
#include <syncevo/declarations.h>
|
|
|
|
#ifdef DBUS_SERVICE
|
|
|
|
#include <gdbus-cxx-bridge.h>
|
|
|
|
struct SourceStatus {
|
|
string m_mode;
|
|
string m_status;
|
|
uint32_t m_error;
|
|
};
|
|
template<> struct dbus_traits<SourceStatus> :
|
|
public dbus_struct_traits<SourceStatus,
|
|
dbus_member<SourceStatus, string, &SourceStatus::m_mode,
|
|
dbus_member<SourceStatus, string, &SourceStatus::m_status,
|
|
dbus_member_single<SourceStatus, uint32_t, &SourceStatus::m_error> > > >
|
|
{};
|
|
#endif
|
|
|
|
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 typeof(e_contact_new_from_vcard) *impl;
|
|
|
|
if (!impl) {
|
|
impl = (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
|
|
|
|
/**
|
|
* This is a class derived from Cmdline. The purpose
|
|
* is to implement the factory method 'createSyncClient' to create
|
|
* new implemented 'CmdlineSyncClient' objects.
|
|
*/
|
|
class KeyringSyncCmdline : public Cmdline {
|
|
public:
|
|
KeyringSyncCmdline(int argc, const char * const * argv, ostream &out, ostream &err):
|
|
Cmdline(argc, argv, out, err)
|
|
{}
|
|
/**
|
|
* create a user implemented sync client.
|
|
*/
|
|
SyncContext* createSyncClient() {
|
|
return new CmdlineSyncClient(m_server, true, m_keyring);
|
|
}
|
|
};
|
|
|
|
#ifdef DBUS_SERVICE
|
|
class RemoteSession;
|
|
typedef map<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();
|
|
|
|
virtual const char *getDestination() const {return "org.syncevolution";}
|
|
virtual const char *getPath() const {return "/org/syncevolution/Server";}
|
|
virtual const char *getInterface() const {return "org.syncevolution.Server";}
|
|
virtual DBusConnection *getConnection() const {return m_conn.get();}
|
|
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 vector<string> &args, const 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 string &config);
|
|
|
|
/**
|
|
* To implement the feature of '--status' without a server.
|
|
* get and print all running sessions in the dbus server
|
|
*/
|
|
bool 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 string &id, const 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
|
|
*/
|
|
void attachCb(const boost::shared_ptr<Watch> &watch, const string &error);
|
|
|
|
/** callback of 'Server.GetSessions' */
|
|
void getSessionsCb(const vector<string> &sessions, const 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 string &level, const string &log);
|
|
|
|
/** callback of 'Server.InfoRequest' */
|
|
void infoReqCb(const string &,
|
|
const DBusObject_t &,
|
|
const string &,
|
|
const string &,
|
|
const string &,
|
|
const StringMap &);
|
|
|
|
/** callback of Server.InfoResponse */
|
|
void infoResponseCb(const string &error);
|
|
|
|
/** callback of calling 'Server.StartSession' */
|
|
void startSessionCb(const DBusObject_t &session, const string &error);
|
|
|
|
/** update active session vector according to 'SessionChanged' signal */
|
|
void updateSessions(const 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;
|
|
// connection
|
|
DBusConnectionPtr m_conn;
|
|
// 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
|
|
string m_configName;
|
|
// active session object path
|
|
boost::shared_ptr<string> m_activeSession;
|
|
// session created or monitored
|
|
boost::shared_ptr<RemoteSession> m_session;
|
|
// active sessions after listening to 'SessionChanged' signals
|
|
vector<string> m_activeSessions;
|
|
// all sessions in dbus server
|
|
vector<boost::shared_ptr<RemoteSession> > m_sessions;
|
|
// the number of total dbus calls
|
|
unsigned int m_replyTotal;
|
|
// the number of returned dbus calls
|
|
unsigned int m_replyCounter;
|
|
// sessions which are running
|
|
vector<boost::weak_ptr<RemoteSession> > m_runSessions;
|
|
// listen to dbus server signal 'SessionChanged'
|
|
SignalWatch2<DBusObject_t, bool> m_sessionChanged;
|
|
// listen to dbus server signal 'LogOutput'
|
|
SignalWatch3<DBusObject_t, string, string> m_logOutput;
|
|
// listen to dbus server signal 'InfoRequest'
|
|
SignalWatch6<string,
|
|
DBusObject_t,
|
|
string,
|
|
string,
|
|
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);
|
|
virtual const char *getDestination() const {return "org.syncevolution";}
|
|
virtual const char *getPath() const {return m_path.c_str();}
|
|
virtual const char *getInterface() const {return "org.syncevolution.Session";}
|
|
virtual DBusConnection *getConnection() const {return m_server.getConnection();}
|
|
RemoteDBusServer &getServer() { return m_server; }
|
|
|
|
/**
|
|
* call 'Execute' method of 'Session' in dbus server
|
|
* without waiting for return
|
|
*/
|
|
void executeAsync(const vector<string> &args);
|
|
|
|
/**
|
|
* call 'GetStatus' method of 'Session' in dbus server
|
|
* without waiting for return
|
|
*/
|
|
void getStatusAsync();
|
|
|
|
/**
|
|
* call 'Suspend' method of 'Session' in dbus server
|
|
* without waiting for return
|
|
*/
|
|
void suspendAsync();
|
|
|
|
/**
|
|
* call 'Abort' method of 'Session' in dbus server
|
|
* without waiting for return
|
|
*/
|
|
void abortAsync();
|
|
|
|
/**
|
|
* call 'GetConfig' method of 'Session' in dbus server
|
|
* without waiting for return
|
|
*/
|
|
void getConfigAsync();
|
|
|
|
/** get config name of this session */
|
|
string configName() { return m_configName; }
|
|
|
|
/** status 'done' is sent by session */
|
|
bool statusDone() { return boost::iequals(m_status, "done"); }
|
|
|
|
/** get current status */
|
|
string status() { return m_status; }
|
|
|
|
/** set the flag to indicate the session is running sync */
|
|
void setRunSync(bool runSync) { m_runSync = runSync; }
|
|
|
|
/** monitor status of the sesion until it is done */
|
|
void monitorSync();
|
|
|
|
/** pass through logoutput and print them if m_output is true */
|
|
void logOutput(Logger::Level level, const string &log);
|
|
|
|
/** set whether to print output */
|
|
void setOutput(bool output) { m_output = output; }
|
|
|
|
/** process signals from daemon */
|
|
void infoReq(const string &id,
|
|
const DBusObject_t &session,
|
|
const string &state,
|
|
const string &handler,
|
|
const string &type,
|
|
const StringMap ¶ms);
|
|
|
|
/** remove InfoReq objects from map */
|
|
void removeInfoReq(const 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 */
|
|
string m_id;
|
|
/** the type of InfoRequest */
|
|
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 string &id, const string &type);
|
|
|
|
/**
|
|
* process the info request dispatched by session
|
|
*/
|
|
void process(const string &id,
|
|
const DBusObject_t &session,
|
|
const string &state,
|
|
const string &handler,
|
|
const string &type,
|
|
const StringMap ¶ms);
|
|
};
|
|
|
|
/** callback of calling 'Session.Execute' */
|
|
void executeCb(const string &error);
|
|
|
|
/** callback of 'Session.GetStatus' */
|
|
void getStatusCb(const string &status,
|
|
uint32_t errorCode,
|
|
const SourceStatuses_t &sourceStatus,
|
|
const string &error);
|
|
|
|
/** callback of 'Session.GetConfig' */
|
|
void getConfigCb(const Config_t &config, const string &error);
|
|
|
|
/** callback of 'Session.StatusChanged' */
|
|
void statusChangedCb(const string &status,
|
|
uint32_t errorCode,
|
|
const SourceStatuses_t &sourceStatus);
|
|
|
|
/** callback of 'Session.Suspend' */
|
|
void suspendCb(const string &);
|
|
|
|
/** callback of 'Session.Abort' */
|
|
void abortCb(const string &);
|
|
|
|
/**
|
|
* implement requirements from info req. Called by InfoReq.
|
|
*/
|
|
void handleInfoReq(const string &type, const StringMap ¶ms, StringMap &resp);
|
|
|
|
/** dbus server */
|
|
RemoteDBusServer &m_server;
|
|
|
|
/* whether to log output */
|
|
bool m_output;
|
|
|
|
/** object path */
|
|
string m_path;
|
|
|
|
/** config name of the session */
|
|
string m_configName;
|
|
|
|
/** current status */
|
|
string m_status;
|
|
|
|
/** session is running sync */
|
|
bool m_runSync;
|
|
|
|
/** signal watch 'StatusChanged' */
|
|
SignalWatch3<std::string, uint32_t, SourceStatuses_t> m_statusChanged;
|
|
|
|
/** InfoReq map. store all infoReq belongs to this session */
|
|
map<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(map<string, string> &vars);
|
|
|
|
#endif
|
|
|
|
extern "C"
|
|
int main( int argc, char **argv )
|
|
{
|
|
#ifdef ENABLE_MAEMO
|
|
// EDS-DBus uses potentially long-running calls which may fail due
|
|
// to the default 25s timeout. Some of these can be replaced by
|
|
// their async version, but e_book_async_get_changes() still
|
|
// triggered it.
|
|
//
|
|
// The workaround for this is to link the binary against a libdbus
|
|
// which has the dbus-timeout.patch and thus let's users and
|
|
// the application increase the default timeout.
|
|
setenv("DBUS_DEFAULT_TIMEOUT", "600000", 0);
|
|
#endif
|
|
|
|
// Intercept stderr and route it through our logging.
|
|
// stdout is printed normally. Deconstructing it when
|
|
// leaving main() does one final processing of pending
|
|
// output.
|
|
LogRedirect redirect(false);
|
|
|
|
#if defined(HAVE_GLIB)
|
|
// this is required when using glib directly or indirectly
|
|
g_type_init();
|
|
g_thread_init(NULL);
|
|
g_set_prgname("syncevolution");
|
|
#endif
|
|
|
|
setvbuf(stderr, NULL, _IONBF, 0);
|
|
setvbuf(stdout, NULL, _IONBF, 0);
|
|
|
|
// Expand PATH to cover the directory we were started from?
|
|
// This might be needed to find normalize_vcard.
|
|
char *exe = strdup(argv[0]);
|
|
if (strchr(exe, '/') ) {
|
|
char *dir = dirname(exe);
|
|
string path;
|
|
char *oldpath = getenv("PATH");
|
|
if (oldpath) {
|
|
path += oldpath;
|
|
path += ":";
|
|
}
|
|
path += dir;
|
|
setenv("PATH", path.c_str(), 1);
|
|
}
|
|
free(exe);
|
|
|
|
try {
|
|
/*
|
|
* 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)
|
|
*/
|
|
KeyringSyncCmdline cmdline(argc, argv, std::cout, std::cout);
|
|
vector<string> parsedArgs;
|
|
if(!cmdline.parse(parsedArgs)) {
|
|
return 1;
|
|
}
|
|
|
|
if (cmdline.dontRun()) {
|
|
return 0;
|
|
}
|
|
|
|
Cmdline::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, 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, 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, NULL, "WARNING: cannot run syncevolution as daemon. "
|
|
// "Trying to run it without daemon.");
|
|
}
|
|
#else
|
|
if (useDaemon.wasSet()) {
|
|
SE_LOG_SHOW(NULL, 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, NULL, "%s", ex.what());
|
|
} catch (...) {
|
|
SE_LOG_ERROR(NULL, NULL, "unknown error");
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#ifdef DBUS_SERVICE
|
|
/********************** RemoteDBusServer implementation **************************/
|
|
RemoteDBusServer::RemoteDBusServer()
|
|
: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);
|
|
m_conn = g_dbus_setup_bus(DBUS_BUS_SESSION, NULL, true, NULL);
|
|
|
|
if(m_conn) {
|
|
//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));
|
|
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, NULL, "SyncEvolution D-Bus server not available.");
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void RemoteDBusServer::attachSync()
|
|
{
|
|
resetReplies();
|
|
DBusClientCall1<boost::shared_ptr<Watch> > attach(*this, "Attach");
|
|
attach(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 string &error)
|
|
{
|
|
replyInc();
|
|
if(error.empty()) {
|
|
// don't print error information, leave it to caller
|
|
m_attached = true;
|
|
//if attach is successful, watch server whether it is gone
|
|
m_daemonWatch = watch;
|
|
m_daemonWatch->setCallback(boost::bind(&RemoteDBusServer::daemonGone,this));
|
|
}
|
|
}
|
|
|
|
void RemoteDBusServer::logOutputCb(const DBusObject_t &object,
|
|
const string &level,
|
|
const string &log)
|
|
{
|
|
if (m_session &&
|
|
(boost::equals(object, getPath()) ||
|
|
boost::equals(object, m_session->getPath()))) {
|
|
m_session->logOutput(Logger::strToLevel(level.c_str()), log);
|
|
}
|
|
}
|
|
|
|
void RemoteDBusServer::infoReqCb(const string &id,
|
|
const DBusObject_t &session,
|
|
const string &state,
|
|
const string &handler,
|
|
const 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 string &id,
|
|
const string &state,
|
|
const StringMap &resp)
|
|
{
|
|
//call Server.InfoResponse
|
|
DBusClientCall0 call(*this, "InfoResponse");
|
|
call(id, state, resp, boost::bind(&RemoteDBusServer::infoResponseCb, this, _1));
|
|
}
|
|
|
|
void RemoteDBusServer::infoResponseCb(const string &error)
|
|
{
|
|
replyInc();
|
|
if(!error.empty()) {
|
|
SE_LOG_ERROR(NULL, 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, NULL, "Background sync daemon has gone.");
|
|
exit(1);
|
|
}
|
|
|
|
/**
|
|
* Don't hang onto a shared_ptr here!
|
|
*
|
|
* RemoteSessions contain a reference to the
|
|
* RemoteDBusServer which created them. Once that
|
|
* server destructs, all sessions must have been
|
|
* deleted earlier, otherwise they'll call a destructed
|
|
* object.
|
|
*/
|
|
boost::weak_ptr<RemoteSession> RemoteDBusServer::g_session;
|
|
void RemoteDBusServer::handleSignal(int sig)
|
|
{
|
|
SyncContext::handleSignal(sig);
|
|
boost::shared_ptr<RemoteSession> session = g_session.lock();
|
|
if (session) {
|
|
const SuspendFlags &flags = SyncContext::getSuspendFlags();
|
|
if(flags.state == SuspendFlags::CLIENT_SUSPEND) {
|
|
session->suspendAsync();
|
|
} else if(flags.state == SuspendFlags::CLIENT_ABORT) {
|
|
session->abortAsync();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool RemoteDBusServer::execute(const vector<string> &args, const 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
|
|
DBusClientCall1<DBusObject_t> call(*this, "StartSession");
|
|
call(peer, boost::bind(&RemoteDBusServer::startSessionCb, this, _1, _2));
|
|
|
|
// wait until 'StartSession' returns
|
|
resetReplies();
|
|
while(!done()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
|
|
if(m_session) {
|
|
m_session->setRunSync(true);
|
|
|
|
//if session is not active, just wait
|
|
while(!isActive()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
// Logger::Level level = LoggerBase::instance().getLevel();
|
|
// LoggerBase::instance().setLevel(Logger::DEBUG);
|
|
resetReplies();
|
|
m_session->executeAsync(args);
|
|
|
|
while(!done()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
|
|
//if encoutering errors, return
|
|
if(!m_result) {
|
|
return m_result;
|
|
}
|
|
|
|
//g_session is used to pass 'abort' or 'suspend' commands
|
|
//make sure session is ready to run
|
|
g_session = m_session;
|
|
|
|
//set up signal handlers to send 'suspend' or 'abort' to dbus server
|
|
//only do this once session is executing and can suspend and abort
|
|
struct sigaction new_action, old_action;
|
|
struct sigaction old_term_action;
|
|
|
|
if(runSync) {
|
|
memset(&new_action, 0, sizeof(new_action));
|
|
new_action.sa_handler = handleSignal;
|
|
sigemptyset(&new_action.sa_mask);
|
|
sigaction(SIGINT, NULL, &old_action);
|
|
if (old_action.sa_handler == SIG_DFL) {
|
|
sigaction(SIGINT, &new_action, NULL);
|
|
}
|
|
|
|
sigaction(SIGTERM, NULL, &old_term_action);
|
|
if (old_term_action.sa_handler == SIG_DFL) {
|
|
sigaction(SIGTERM, &new_action, NULL);
|
|
}
|
|
}
|
|
|
|
//wait until status is 'done'
|
|
while(!m_session->statusDone()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
|
|
if(runSync) {
|
|
sigaction (SIGINT, &old_action, NULL);
|
|
sigaction (SIGTERM, &old_term_action, NULL);
|
|
}
|
|
|
|
//reset session
|
|
g_session.reset();
|
|
//restore logging level
|
|
// LoggerBase::instance().setLevel(level);
|
|
m_session->setRunSync(false);
|
|
}
|
|
return m_result;
|
|
}
|
|
|
|
void RemoteDBusServer::startSessionCb(const DBusObject_t &sessionPath, const string &error)
|
|
{
|
|
replyInc();
|
|
if(!error.empty()) {
|
|
SE_LOG_ERROR(NULL, NULL, "starting D-Bus session failed: %s", error.c_str());
|
|
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) {
|
|
BOOST_FOREACH(const string &session, m_activeSessions) {
|
|
if(boost::equals(m_session->getPath(), session.c_str())) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void RemoteDBusServer::getRunningSessions()
|
|
{
|
|
//get all sessions
|
|
DBusClientCall1<vector<string> > sessions(*this, "GetSessions");
|
|
sessions(boost::bind(&RemoteDBusServer::getSessionsCb, this, _1, _2));
|
|
resetReplies();
|
|
while(!done()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
|
|
// get status of each session
|
|
resetReplies(m_sessions.size());
|
|
BOOST_FOREACH(boost::shared_ptr<RemoteSession> &session, m_sessions) {
|
|
session->getStatusAsync();
|
|
}
|
|
|
|
// waiting for all sessions 'GetStatus'
|
|
while(!done()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
|
|
// collect running sessions
|
|
BOOST_FOREACH(boost::shared_ptr<RemoteSession> &session, m_sessions) {
|
|
if(boost::istarts_with(session->status(), "running")) {
|
|
m_runSessions.push_back(boost::weak_ptr<RemoteSession>(session));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool 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
|
|
getRunningSessions();
|
|
|
|
if(m_runSessions.empty()) {
|
|
SE_LOG_SHOW(NULL, NULL, "Background sync daemon is idle.");
|
|
} else {
|
|
SE_LOG_SHOW(NULL, NULL, "Running session(s): ");
|
|
|
|
resetReplies(m_runSessions.size());
|
|
BOOST_FOREACH(boost::weak_ptr<RemoteSession> &session, m_runSessions) {
|
|
boost::shared_ptr<RemoteSession> lock = session.lock();
|
|
if(lock) {
|
|
lock->getConfigAsync();
|
|
}
|
|
}
|
|
|
|
//wait for 'GetConfig' returns
|
|
while(!done()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
|
|
// print all running sessions
|
|
BOOST_FOREACH(boost::weak_ptr<RemoteSession> &session, m_runSessions) {
|
|
boost::shared_ptr<RemoteSession> lock = session.lock();
|
|
if(!lock->configName().empty()) {
|
|
SE_LOG_SHOW(NULL, NULL, " %s (%s)", lock->configName().c_str(), lock->getPath());
|
|
}
|
|
}
|
|
}
|
|
return m_result;
|
|
}
|
|
|
|
void RemoteDBusServer::getSessionsCb(const vector<string> &sessions, const string &error)
|
|
{
|
|
replyInc();
|
|
if(!error.empty()) {
|
|
SE_LOG_ERROR(NULL, NULL, "getting session failed: %s", error.c_str());
|
|
m_result = false;
|
|
g_main_loop_quit(m_loop);
|
|
return;
|
|
}
|
|
|
|
//create local objects for sessions
|
|
BOOST_FOREACH(const DBusObject_t &value, sessions) {
|
|
boost::shared_ptr<RemoteSession> session(new RemoteSession(*this, value));
|
|
m_sessions.push_back(session);
|
|
}
|
|
}
|
|
|
|
void RemoteDBusServer::updateSessions(const 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(vector<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 string &peer)
|
|
{
|
|
//the basic working flow is:
|
|
//1) get all sessions
|
|
//2) check each session and collect running sessions
|
|
//3) peak one session with the given peer and monitor it
|
|
getRunningSessions();
|
|
if(peer.empty()) {
|
|
//peak the first running sessions
|
|
BOOST_FOREACH(boost::weak_ptr<RemoteSession> &session, m_runSessions) {
|
|
boost::shared_ptr<RemoteSession> lock = session.lock();
|
|
if(lock) {
|
|
m_session = lock;
|
|
resetReplies();
|
|
m_session->getConfigAsync();
|
|
while(!done()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
m_session->monitorSync();
|
|
return m_result;
|
|
}
|
|
}
|
|
//if no running session
|
|
SE_LOG_SHOW(NULL, NULL, "Background sync daemon is idle, no session available to be be monitored.");
|
|
} else {
|
|
string peerNorm = SyncConfig::normalizeConfigString(peer);
|
|
|
|
// get config names of running sessions
|
|
resetReplies(m_runSessions.size());
|
|
BOOST_FOREACH(boost::weak_ptr<RemoteSession> &session, m_runSessions) {
|
|
boost::shared_ptr<RemoteSession> lock = session.lock();
|
|
lock->getConfigAsync();
|
|
}
|
|
//wait for 'GetConfig' returns
|
|
while(!done()) {
|
|
g_main_loop_run(m_loop);
|
|
}
|
|
|
|
//find a session with the given name
|
|
vector<boost::shared_ptr<RemoteSession> >::iterator it = m_sessions.begin();
|
|
while(it != m_sessions.end()) {
|
|
string tempNorm = (*it)->configName();
|
|
if (peerNorm == tempNorm) {
|
|
m_session = *it;
|
|
//monitor the session status
|
|
m_session->monitorSync();
|
|
return m_result;
|
|
}
|
|
it++;
|
|
}
|
|
SE_LOG_SHOW(NULL, NULL, "'%s' is not running.", peer.c_str());
|
|
}
|
|
return m_result;
|
|
}
|
|
|
|
|
|
/********************** RemoteSession implementation **************************/
|
|
RemoteSession::RemoteSession(RemoteDBusServer &server,
|
|
const string &path)
|
|
:m_server(server), m_output(false), m_path(path), m_runSync(false),
|
|
m_statusChanged(*this, "StatusChanged")
|
|
{
|
|
m_statusChanged.activate(boost::bind(&RemoteSession::statusChangedCb, this, _1, _2, _3));
|
|
}
|
|
|
|
void RemoteSession::executeAsync(const vector<string> &args)
|
|
{
|
|
//start to print outputs
|
|
m_output = true;
|
|
map<string, string> vars;
|
|
getEnvVars(vars);
|
|
DBusClientCall0 call(*this, "Execute");
|
|
call(args, vars, boost::bind(&RemoteSession::executeCb, this, _1));
|
|
}
|
|
|
|
void RemoteSession::executeCb(const string &error)
|
|
{
|
|
m_server.replyInc();
|
|
if(!error.empty()) {
|
|
SE_LOG_ERROR(NULL, 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 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::getStatusAsync()
|
|
{
|
|
DBusClientCall3<string, uint32_t, SourceStatuses_t> call(*this, "GetStatus");
|
|
call(boost::bind(&RemoteSession::getStatusCb, this, _1, _2, _3, _4));
|
|
}
|
|
|
|
void RemoteSession::getStatusCb(const string &status,
|
|
uint32_t errorCode,
|
|
const SourceStatuses_t &sourceStatus,
|
|
const string &error)
|
|
{
|
|
m_server.replyInc();
|
|
if(!error.empty()) {
|
|
//ignore the error
|
|
return;
|
|
}
|
|
m_status = status;
|
|
}
|
|
|
|
void RemoteSession::getConfigAsync()
|
|
{
|
|
DBusClientCall1<Config_t> call(*this, "GetConfig");
|
|
call(false, boost::bind(&RemoteSession::getConfigCb, this, _1, _2));
|
|
}
|
|
|
|
void RemoteSession::getConfigCb(const Config_t &config, const string &error)
|
|
{
|
|
m_server.replyInc();
|
|
if(!error.empty()) {
|
|
//ignore the error
|
|
return;
|
|
}
|
|
// set config name
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteSession::suspendAsync()
|
|
{
|
|
DBusClientCall0 suspend(*this, "Suspend");
|
|
suspend(boost::bind(&RemoteSession::suspendCb, this, _1));
|
|
}
|
|
|
|
void RemoteSession::suspendCb(const string &error)
|
|
{
|
|
//avoid logging messages in handleSignal
|
|
SyncContext::printSignals();
|
|
if(!error.empty()) {
|
|
m_server.setResult(false);
|
|
}
|
|
}
|
|
|
|
void RemoteSession::abortCb(const string &error)
|
|
{
|
|
//avoid logging messages in handleSignal
|
|
SyncContext::printSignals();
|
|
if(!error.empty()) {
|
|
m_server.setResult(false);
|
|
}
|
|
}
|
|
|
|
void RemoteSession::abortAsync()
|
|
{
|
|
DBusClientCall0 abort(*this, "Abort");
|
|
abort(boost::bind(&RemoteSession::abortCb, this, _1));
|
|
}
|
|
|
|
void RemoteSession::logOutput(Logger::Level level, const string &log)
|
|
{
|
|
if(m_output) {
|
|
SE_LOG(level, NULL, NULL, "%s", log.c_str());
|
|
}
|
|
}
|
|
|
|
void RemoteSession::monitorSync()
|
|
{
|
|
m_output = true;
|
|
// Logger::Level level = LoggerBase::instance().getLevel();
|
|
// LoggerBase::instance().setLevel(Logger::DEBUG);
|
|
SE_LOG(Logger::SHOW, NULL, NULL, "Monitoring '%s' (%s)\n", m_configName.c_str(), getPath());
|
|
|
|
while(!statusDone()) {
|
|
g_main_loop_run(m_server.getLoop());
|
|
}
|
|
|
|
SE_LOG(Logger::SHOW, NULL, NULL, "Monitoring done");
|
|
// LoggerBase::instance().setLevel(level);
|
|
m_output = false;
|
|
}
|
|
|
|
void RemoteSession::infoReq(const string &id,
|
|
const DBusObject_t &session,
|
|
const string &state,
|
|
const string &handler,
|
|
const 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)) {
|
|
map<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 string &type, const StringMap ¶ms, StringMap &resp)
|
|
{
|
|
if (boost::iequals(type, "password")) {
|
|
char buffer[256];
|
|
|
|
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"] = string(buffer);
|
|
} else {
|
|
SE_LOG_ERROR(NULL, NULL, "could not read password for %s", descr.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteSession::removeInfoReq(const string &id)
|
|
{
|
|
map<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 string &id,
|
|
const string &type)
|
|
:m_session(session), m_id(id), m_type(type), m_state(INIT)
|
|
{
|
|
}
|
|
|
|
void RemoteSession::InfoReq::process(const string &id,
|
|
const DBusObject_t &session,
|
|
const string &state,
|
|
const string &handler,
|
|
const 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(map<string, 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
|