syncevolution/src/dbus/server/server.h

682 lines
24 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 SYNCEVO_DBUS_SERVER_H
#define SYNCEVO_DBUS_SERVER_H
#include <set>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/signals2.hpp>
#include <syncevo/SyncConfig.h>
#include <syncevo/timeout.h>
#include "exceptions.h"
#include "auto-term.h"
#include "dbus-callbacks.h"
#include "read-operations.h"
#include <syncevo/declarations.h>
SE_BEGIN_CXX
class Resource;
class Session;
class Server;
class InfoReq;
class BluezManager;
class Restart;
class Client;
class GLibNotify;
class AutoSyncManager;
class PresenceStatus;
class ConnmanClient;
class NetworkManagerClient;
// TODO: avoid polluting namespace
using namespace std;
class ServerLogger;
/**
* Implements the main org.syncevolution.Server interface.
*
* The Server class is responsible for listening to clients and
* spinning of sync sessions as requested by clients.
*/
class Server : public GDBusCXX::DBusObjectHelper
{
GMainLoop *m_loop;
guint m_suspendFlagsSource;
bool m_shutdownRequested;
Timespec m_lastFileMod;
boost::shared_ptr<SyncEvo::Restart> &m_restart;
GDBusCXX::DBusConnectionPtr m_conn;
uint32_t m_lastSession;
typedef std::list< std::pair< boost::shared_ptr<GDBusCXX::Watch>, boost::shared_ptr<Client> > > Clients_t;
Clients_t m_clients;
/**
* Functor will never be called, important are the shared pointers
* bound to it. m_delayDeletion will be cleared in idle and when
* server terminates, thus unrefing anything encapsulated inside
* it.
*/
std::list< boost::function<void ()> > m_delayDeletion;
/**
* Watch all files mapped into our address space. When
* modifications are seen (as during a package upgrade), sets
* m_shutdownRequested. This prevents adding new sessions and
* prevents running already queued ones, because future sessions
* might not be able to execute correctly without a restart. For example, a
* sync with libsynthesis from 1.1 does not work with
* SyncEvolution XML files from 1.2. The daemon then waits
* for the changes to settle (see SHUTDOWN_QUIESENCE_SECONDS) and either shuts
* down or restarts. The latter is necessary if the daemon has
* automatic syncing enabled in a config.
*/
list< boost::shared_ptr<GLibNotify> > m_files;
void fileModified(const std::string &file);
bool shutdown();
/**
* timer which counts seconds until server is meant to shut down
*/
Timeout m_shutdownTimer;
/**
* The session which currently holds the main lock on the server.
* To avoid issues with concurrent modification of data or configs,
* only one session may make such modifications at a time. A
* plain pointer which is reset by the session's deconstructor.
*
* The server doesn't hold a shared pointer to the session so
* that it can be deleted when the last client detaches from it.
*
* A weak pointer alone did not work because it does not provide access
* to the underlying pointer after the last corresponding shared
* pointer is gone (which triggers the deconstructing of the session).
*/
Session *m_activeSession;
/**
* The weak pointer that corresponds to m_activeSession.
*/
boost::weak_ptr<Session> m_activeSessionRef;
/**
* The running sync session. Having a separate reference to it
* ensures that the object won't go away prematurely, even if all
* clients disconnect.
*
* The session itself needs to request this special treatment with
* addSyncSession() and remove itself with removeSyncSession() when
* done.
*/
boost::shared_ptr<Session> m_syncSession;
typedef std::list< boost::weak_ptr<Session> > WorkQueue_t;
/**
* A queue of pending, idle Sessions. Sorted by priority, most
* important one first. Currently this is used to give client
* requests a boost over remote connections and (in the future)
* automatic syncs.
*
* Active sessions are removed from this list and then continue
* to exist as long as a client in m_clients references it or
* it is the currently running sync session (m_syncSession).
*/
WorkQueue_t m_workQueue;
/**
* a hash of pending InfoRequest
*/
typedef std::map<string, boost::weak_ptr<InfoReq> > InfoReqMap;
// hash map of pending info requests
InfoReqMap m_infoReqMap;
// the index of last info request
uint32_t m_lastInfoReq;
// a hash to represent matched templates for devices, the key is
// the peer name
typedef std::map<string, boost::shared_ptr<SyncConfig::TemplateDescription> > MatchedTemplates;
MatchedTemplates m_matchedTempls;
boost::shared_ptr<BluezManager> m_bluezManager;
/** devices which have sync services */
SyncConfig::DeviceList m_syncDevices;
/**
* Watch callback for a specific client or connection.
*/
void clientGone(Client *c);
public:
// D-Bus API, also usable directly
/** Server.GetCapabilities() */
vector<string> getCapabilities();
/** Server.GetVersions() */
StringMap getVersions();
/** Server.Attach() */
void attachClient(const GDBusCXX::Caller_t &caller,
const boost::shared_ptr<GDBusCXX::Watch> &watch);
/** Server.Detach() */
void detachClient(const GDBusCXX::Caller_t &caller);
/** Server.DisableNotifications() */
void disableNotifications(const GDBusCXX::Caller_t &caller,
const string &notifications) {
setNotifications(false, caller, notifications);
}
/** Server.EnableNotifications() */
void enableNotifications(const GDBusCXX::Caller_t &caller,
const string &notifications) {
setNotifications(true, caller, notifications);
}
/** Server.NotificationAction() */
void notificationAction(const GDBusCXX::Caller_t &caller) {
pid_t pid;
if((pid = fork()) == 0) {
// search sync-ui from $PATH
execlp("sync-ui", "sync-ui", (const char*)0);
// Failing that, try meego-ux-settings/Sync
execlp("meego-qml-launcher",
"meego-qml-launcher",
"--opengl", "--fullscreen", "--app", "meego-ux-settings",
"--cmd", "showPage", "--cdata", "Sync", (const char*)0);
// Failing that, simply exit
exit(0);
}
}
/** actual implementation of enable and disable */
void setNotifications(bool enable,
const GDBusCXX::Caller_t &caller,
const string &notifications);
/** Server.Connect() */
void connect(const GDBusCXX::Caller_t &caller,
const boost::shared_ptr<GDBusCXX::Watch> &watch,
const StringMap &peer,
bool must_authenticate,
const std::string &session,
GDBusCXX::DBusObject_t &object);
/** Server.StartSession() */
void startSession(const GDBusCXX::Caller_t &caller,
const boost::shared_ptr<GDBusCXX::Watch> &watch,
const std::string &server,
GDBusCXX::DBusObject_t &object) {
startSessionWithFlags(caller, watch, server, std::vector<std::string>(), object);
}
/** Server.StartSessionWithFlags() */
void startSessionWithFlags(const GDBusCXX::Caller_t &caller,
const boost::shared_ptr<GDBusCXX::Watch> &watch,
const std::string &server,
const std::vector<std::string> &flags,
GDBusCXX::DBusObject_t &object);
/** internal representation of D-Bus API Server.StartSessionWithFlags() */
enum SessionFlags {
SESSION_FLAG_NONE = 0,
SESSION_FLAG_NO_SYNC = 1<<0,
SESSION_FLAG_ALL_CONFIGS = 1<<1
};
/**
* Creates a session, queues it, then invokes the callback
* once the session is active. The caller is responsible
* for holding a reference to the session. If it drops
* that reference, the session gets deleted and the callback
* will not be called.
*/
boost::shared_ptr<Session> startInternalSession(const std::string &server,
SessionFlags flags,
const boost::function<void (const boost::weak_ptr<Session> &session)> &callback);
/** Server.GetConfig() */
void getConfig(const std::string &config_name,
bool getTemplate,
ReadOperations::Config_t &config)
{
ReadOperations ops(config_name, *this);
ops.getConfig(getTemplate , config);
}
/** Server.GetReports() */
void getReports(const std::string &config_name,
uint32_t start, uint32_t count,
ReadOperations::Reports_t &reports)
{
ReadOperations ops(config_name, *this);
ops.getReports(start, count, reports);
}
/** Server.CheckSource() */
void checkSource(const std::string &configName,
const std::string &sourceName)
{
ReadOperations ops(configName, *this);
ops.checkSource(sourceName);
}
/** Server.GetDatabases() */
void getDatabases(const std::string &configName,
const string &sourceName,
ReadOperations::SourceDatabases_t &databases)
{
ReadOperations ops(configName, *this);
ops.getDatabases(sourceName, databases);
}
/** Server.GetConfigs() */
void getConfigs(bool getTemplates,
std::vector<std::string> &configNames)
{
ReadOperations ops("", *this);
ops.getConfigs(getTemplates, configNames);
}
/** Server.CheckPresence() */
void checkPresence(const std::string &server,
std::string &status,
std::vector<std::string> &transports);
/** Server.GetSessions() */
void getSessions(std::vector<GDBusCXX::DBusObject_t> &sessions);
/** Server.InfoResponse() */
void infoResponse(const GDBusCXX::Caller_t &caller,
const std::string &id,
const std::string &state,
const std::map<string, string> &response);
/** Server.SessionChanged */
GDBusCXX::EmitSignal2<const GDBusCXX::DBusObject_t &,
bool> sessionChanged;
/** Server.PresenceChanged */
GDBusCXX::EmitSignal3<const std::string &,
const std::string &,
const std::string &> presence;
/**
* Server.TemplatesChanged, triggered each time m_syncDevices, the
* input for the templates, is changed
*/
GDBusCXX::EmitSignal0 templatesChanged;
/**
* Server.ConfigChanged, triggered each time a session ends
* which modified its configuration
*/
GDBusCXX::EmitSignal0 configChanged;
/** Server.InfoRequest */
GDBusCXX::EmitSignal6<const std::string &,
const GDBusCXX::DBusObject_t &,
const std::string &,
const std::string &,
const std::string &,
const std::map<string, string> &> infoRequest;
/** wrapper around Server.LogOutput, filters by DBusLogLevel */
void logOutput(const GDBusCXX::DBusObject_t &path,
Logger::Level level,
const std::string &explanation,
const std::string &procname);
void setDBusLogLevel(Logger::Level level) { m_dbusLogLevel = level; }
Logger::Level getDBusLogLevel() const { return m_dbusLogLevel; }
private:
/** Server.LogOutput */
GDBusCXX::EmitSignal4<const GDBusCXX::DBusObject_t &,
const std::string &,
const std::string &,
const std::string &> m_logOutputSignal;
friend class InfoReq;
/** emit InfoRequest */
void emitInfoReq(const InfoReq &);
/** get the next id of InfoRequest */
std::string getNextInfoReq();
/** remove InfoReq from hash map */
void removeInfoReq(const std::string &infoReqId);
boost::scoped_ptr<PresenceStatus> m_presence;
boost::scoped_ptr<ConnmanClient> m_connman;
boost::scoped_ptr<NetworkManagerClient> m_networkManager;
/** Manager to automatic sync */
boost::shared_ptr<AutoSyncManager> m_autoSync;
//automatic termination
AutoTerm m_autoTerm;
// The level of detail for D-Bus logging signals.
Logger::Level m_dbusLogLevel;
// Created in constructor and captures parent logger there,
// then pushed as default logger in activate().
boost::shared_ptr<ServerLogger> m_logger;
PushLogger<ServerLogger> m_pushLogger;
/**
* All active timeouts created by addTimeout().
* Each timeout which requests to be not called
* again will be removed from this list.
*/
list< boost::shared_ptr<Timeout> > m_timeouts;
/**
* called each time a timeout triggers,
* removes those which are done
*/
bool callTimeout(const boost::shared_ptr<Timeout> &timeout, const boost::function<void ()> &callback);
/** called 1 minute after last client detached from a session */
static void sessionExpired(const boost::shared_ptr<Session> &session);
/** hooked into m_idleSignal, controls auto-termination */
void onIdleChange(bool idle);
/** hooked up to SuspendFlags fd via g_io_add_watch */
static gboolean onSuspendFlagsChange(GIOChannel *source,
GIOCondition condition,
gpointer data) throw ();
public:
Server(GMainLoop *loop,
boost::shared_ptr<Restart> &restart,
const GDBusCXX::DBusConnectionPtr &conn,
int duration);
void activate();
~Server();
/** access to the GMainLoop reference used by this Server instance */
GMainLoop *getLoop() { return m_loop; }
/** process D-Bus calls until the server is ready to quit */
void run();
/** currently running operation */
boost::shared_ptr<Session> getSyncSession() const { return m_syncSession; }
/** true iff no work is pending */
bool isIdle() const { return !m_activeSession && m_workQueue.empty(); }
/** isIdle() has changed its value, current value included */
typedef boost::signals2::signal<void (bool isIdle)> IdleSignal_t;
IdleSignal_t m_idleSignal;
/**
* More specific "config changed signal", called with normalized
* config name as parameter. Config name is empty if all configs
* were affected.
*/
typedef boost::signals2::signal<void (const std::string &configName)> ConfigChangedSignal_t;
ConfigChangedSignal_t m_configChangedSignal;
/**
* Called when a session starts its real work (= calls addSyncSession()).
*/
typedef boost::signals2::signal<void (const boost::shared_ptr<Session> &)> NewSyncSessionSignal_t;
NewSyncSessionSignal_t m_newSyncSessionSignal;
/**
* look up client by its ID
*/
boost::shared_ptr<Client> findClient(const GDBusCXX::Caller_t &ID);
/**
* find client by its ID or create one anew
*/
boost::shared_ptr<Client> addClient(const GDBusCXX::Caller_t &ID,
const boost::shared_ptr<GDBusCXX::Watch> &watch);
/** detach this resource from all clients which own it */
void detach(Resource *resource);
/**
* Enqueue a session. Might also make it ready immediately,
* if nothing else is first in the queue. To be called
* by the creator of the session, *after* the session is
* ready to run.
*/
void enqueue(const boost::shared_ptr<Session> &session);
/**
* Remove all sessions with this device ID from the
* queue. If the active session also has this ID,
* the session will be aborted and/or deactivated.
*
* Has to be asynchronous because it might involve ensuring that
* there is no running helper for this device ID, which requires
* communicating with the helper.
*/
void killSessionsAsync(const std::string &peerDeviceID,
const SimpleResult &result);
/**
* Remove a session from the work queue. If it is running a sync,
* it will keep running and nothing will change. Otherwise, if it
* is "ready" (= holds a lock on its configuration), then release
* that lock.
*/
void dequeue(Session *session);
/**
* Remember that the session is running a sync (or some other
* important operation) and keeps a pointer to it, to prevent
* deleting it. Currently can only called by the active sync
* session. Will fail if all clients have detached already.
*
* If successful, it triggers m_newSyncSessionSignal.
*/
void addSyncSession(Session *session);
/**
* Session is done, ready to be deleted again.
*/
void removeSyncSession(Session *session);
/**
* Checks whether the server is ready to run another session
* and if so, activates the first one in the queue.
*/
void checkQueue();
/**
* Special behavior for sessions: keep them around for another
* minute after the are no longer needed. Must be called by the
* creator of the session right before it would normally cause the
* destruction of the session.
*
* This allows another client to attach and/or get information
* about the session.
*
* This is implemented as a timeout which holds a reference to the
* session. Once the timeout fires, it is called and then removed,
* which removes the reference.
*/
void delaySessionDestruction(const boost::shared_ptr<Session> &session);
/**
* Works for any kind of object: keep shared pointer until the
* event loop is idle, then unref it inside. Useful for instances
* which need to delete themselves.
*/
template <class T> void delayDeletion(const boost::shared_ptr<T> &t) {
// The functor will never be called, important here is only
// that it contains a copy of the shared pointer.
m_delayDeletion.push_back(boost::bind(delayDeletionDummy<T>, t));
g_idle_add(&Server::delayDeletionCb, this);
}
template <class T> static void delayDeletionDummy(const boost::shared_ptr<T> &t) throw () {}
static gboolean delayDeletionCb(gpointer userData) throw () {
Server *me = static_cast<Server *>(userData);
try {
me->m_delayDeletion.clear();
} catch (...) {
// Something unexpected went wrong, can only shut down.
Exception::handle(HANDLE_EXCEPTION_FATAL);
}
return false;
}
/**
* Handle the password request from a specific session. Ask our
* clients, relay answer to session if it is still around at the
* time when we get the response.
*
* Server does not keep a strong reference to info request,
* caller must do that or the request will automatically be
* deleted.
*/
boost::shared_ptr<InfoReq> passwordRequest(const std::string &descr,
const ConfigPasswordKey &key,
const boost::weak_ptr<Session> &session);
/** got response for earlier request, need to extract password and tell session */
void passwordResponse(const StringMap &response,
const boost::weak_ptr<Session> &session);
/**
* Invokes the given callback once in the given amount of seconds.
* Keeps a copy of the callback. If the Server is destructed
* before that time, then the callback will be deleted without
* being called.
*/
void addTimeout(const boost::function<void ()> &callback,
int seconds);
/**
* InfoReq will be added to map automatically and removed again
* when it completes or times out. Caller is responsible for
* calling removeInfoReq() when the request becomes obsolete
* sooner than that.
*/
boost::shared_ptr<InfoReq> createInfoReq(const string &type,
const std::map<string, string> &parameters,
const Session &session);
void autoTermRef(int counts = 1) { m_autoTerm.ref(counts); }
void autoTermUnref(int counts = 1) { m_autoTerm.unref(counts); }
/** callback to reset for auto termination checking */
void autoTermCallback() { m_autoTerm.reset(); }
/** poll_nm callback for connman, used for presence detection*/
void connmanCallback(const std::map <std::string, boost::variant <std::vector <std::string> > >& props, const string &error);
PresenceStatus& getPresenceStatus();
void clearPeerTempls() { m_matchedTempls.clear(); }
void addPeerTempl(const string &templName, const boost::shared_ptr<SyncConfig::TemplateDescription> peerTempl);
boost::shared_ptr<SyncConfig::TemplateDescription> getPeerTempl(const string &peer);
/**
* methods to operate device list. See DeviceList definition.
* The device id here is the identifier of device, the same as definition in DeviceList.
* In bluetooth devices, it refers to actually the mac address of the bluetooth.
* The finger print and match mode is used to match templates.
*/
/** get sync devices */
void getDeviceList(SyncConfig::DeviceList &devices);
/** get a device according to device id. If not found, return false. */
bool getDevice(const string &deviceId, SyncConfig::DeviceDescription &device);
/** add a device */
void addDevice(const SyncConfig::DeviceDescription &device);
/** remove a device by device id. If not found, do nothing */
void removeDevice(const string &deviceId);
/** update a device with the given device information. If not found, do nothing */
void updateDevice(const string &deviceId, const SyncConfig::DeviceDescription &device);
/** emit a presence signal */
void emitPresence(const string &server, const string &status, const string &transport)
{
presence(server, status, transport);
}
/**
* Returns new unique session ID. Implemented with a running
* counter. Checks for overflow, but not currently for active
* sessions.
*/
std::string getNextSession();
/**
* Number of seconds to wait after file modifications are observed
* before shutting down or restarting. Shutting down could be done
* immediately, but restarting might not work right away. 10
* seconds was chosen because every single package is expected to
* be upgraded on disk in that interval. If a long-running system
* upgrade replaces additional packages later, then the server
* might restart multiple times during a system upgrade. Because it
* never runs operations directly after starting, that shouldn't
* be a problem.
*/
static const int SHUTDOWN_QUIESENCE_SECONDS = 10;
/**
* false if any client requested suppression of notifications
*/
bool notificationsEnabled();
void message2DBus(const Logger::MessageOptions &options,
const char *format,
va_list args,
const std::string &dbusPath,
const std::string &procname);
};
// extensions to the D-Bus server, created dynamically by main()
#ifdef ENABLE_DBUS_PIM
boost::shared_ptr<GDBusCXX::DBusObjectHelper> CreateContactManager(const boost::shared_ptr<Server> &server, bool start);
#endif
SE_END_CXX
#endif // SYNCEVO_DBUS_SERVER_H