syncevolution/src/dbus/server/auto-sync-manager.cpp

515 lines
21 KiB
C++

/*
* Copyright (C) 2011 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include "auto-sync-manager.h"
#include "session.h"
#include "server.h"
#include "dbus-callbacks.h"
#include "presence-status.h"
#include <glib.h>
#include <glib/gi18n.h>
#include <boost/tokenizer.hpp>
SE_BEGIN_CXX
AutoSyncManager::AutoSyncManager(Server &server) :
m_server(server),
m_autoTermLocked(false)
{
}
static void updatePresence(Timespec *t, bool present)
{
*t = present ?
Timespec::monotonic() :
Timespec();
}
std::shared_ptr<AutoSyncManager> AutoSyncManager::createAutoSyncManager(Server &server)
{
auto result = make_weak_shared::make<AutoSyncManager>(server);
result->init();
// update cached information about a config each time it changes
server.m_configChangedSignal.connect(Server::ConfigChangedSignal_t::slot_type(&AutoSyncManager::initConfig, result.get(), _1).track_foreign(result));
// monitor running sessions
server.m_newSyncSessionSignal.connect(Server::NewSyncSessionSignal_t::slot_type(&AutoSyncManager::sessionStarted, result.get(), _1).track_foreign(result));
// Keep track of the time when a transport became online. As with
// time of last sync, we are pessimistic here and assume that the
// transport just now became available.
PresenceStatus &p = server.getPresenceStatus();
Timespec now = Timespec::monotonic();
if (p.getBtPresence()) {
result->m_btStartTime = now;
}
p.m_btPresenceSignal.connect(PresenceStatus::PresenceSignal_t::slot_type(updatePresence,
&result->m_btStartTime,
_1).track_foreign(result));
if (p.getHttpPresence()) {
result->m_httpStartTime = now;
}
p.m_httpPresenceSignal.connect(PresenceStatus::PresenceSignal_t::slot_type(updatePresence,
&result->m_httpStartTime,
_1).track_foreign(result));
return result;
}
void AutoSyncManager::init()
{
m_notificationManager = NotificationManagerFactory::createManager();
m_notificationManager->init();
m_peerMap.clear();
SyncConfig::ConfigList list = SyncConfig::getConfigs();
for (const auto &server: list) {
initConfig(server.first);
}
}
void AutoSyncManager::initConfig(const std::string &configName)
{
SE_LOG_DEBUG(NULL, "auto sync: updating info about config %s", configName.c_str());
if (configName.empty()) {
// Anything might have changed. Check all configs we know
// about (might have been removed) and all existing configs
// (might have been modified).
std::set<std::string> configs;
for (const auto &entry: m_peerMap) {
const std::string &configName = entry.first;
configs.insert(configName);
}
for (const auto &entry: SyncConfig::getConfigs()) {
const std::string &configName = entry.first;
configs.insert(configName);
}
for (const std::string &configName: configs) {
if (!configName.empty()) {
initConfig(configName);
}
}
// TODO: only call schedule() once in this case, instead
// once per recursive initConfig().
}
// TODO: once we depend on shared settings, remember to check
// all other configs which share the same set of settings.
// Not currently the case.
// Create anew or update, directly in map. Never remove
// old entries, because we want to keep the m_lastSyncTime
// in cases where configs get removed and recreated.
std::shared_ptr<AutoSyncTask> &task = m_peerMap[configName];
if (!task) {
task.reset(new AutoSyncTask(configName));
// We should check past sessions here. Instead we assume
// the "worst" case, which is that the session ran zero
// seconds ago. This has the additional benefit that we
// don't run automatic sync sessions directly after
// starting up (the system or syncevo-dbus-server).
task->m_lastSyncTime = Timespec::monotonic();
}
SyncConfig config (configName);
if (config.exists()) {
std::vector<std::string> urls = config.getSyncURL();
std::string autoSync = config.getAutoSync();
//enable http and bt?
bool http = false, bt = false;
bool any = false;
if (autoSync.empty() ||
boost::iequals(autoSync, "0") ||
boost::iequals(autoSync, "f")) {
http = false;
bt = false;
any = false;
} else if (boost::iequals(autoSync, "1") ||
boost::iequals(autoSync, "t")) {
http = true;
bt = true;
any = true;
} else {
for (std::string op: boost::tokenizer< boost::char_separator<char> >(autoSync,
boost::char_separator<char>(","))) {
if(boost::iequals(op, "http")) {
http = true;
} else if(boost::iequals(op, "obex-bt")) {
bt = true;
}
}
}
task->m_peerName = config.getPeerName();
if (task->m_peerName.empty()) {
task->m_peerName = configName;
}
task->m_interval = config.getAutoSyncInterval();
task->m_delay = config.getAutoSyncDelay();
task->m_remoteDeviceId = config.getRemoteDevID();
task->m_notifyLevel = config.getNotifyLevel();
// Assume that whatever change was made might have resolved
// the past problem -> allow auto syncing again.
task->m_permanentFailure = false;
SE_LOG_DEBUG(NULL,
"auto sync: %s: auto sync '%s', %s, %s, %d seconds repeat interval, %d seconds online delay",
configName.c_str(),
autoSync.c_str(),
bt ? "Bluetooth" : "no Bluetooth",
http ? "HTTP" : "no HTTP",
task->m_interval, task->m_delay);
task->m_urls.clear();
for (const std::string &url: urls) {
AutoSyncTask::Transport transport = AutoSyncTask::NEEDS_OTHER; // fallback for unknown sync URL
if (boost::istarts_with(url, "http")) {
transport = AutoSyncTask::NEEDS_HTTP;
} else if (boost::istarts_with(url, "local")) {
// TODO: instead of assuming that local sync needs HTTP, really look into the target config
// and determine what the peerType is
transport = AutoSyncTask::NEEDS_HTTP;
} else if (boost::istarts_with(url, "obex-bt")) {
transport = AutoSyncTask::NEEDS_BT;
}
if((transport == AutoSyncTask::NEEDS_HTTP && http) ||
(transport == AutoSyncTask::NEEDS_BT && bt) ||
(transport == AutoSyncTask::NEEDS_OTHER && any)) {
task->m_urls.push_back(std::make_pair(transport, url));
SE_LOG_DEBUG(NULL,
"auto sync: adding config %s url %s",
configName.c_str(),
url.c_str());
}
}
} else {
// Just clear urls, which disables auto syncing.
task->m_urls.clear();
}
bool lock = preventTerm();
if (m_autoTermLocked && !lock) {
SE_LOG_DEBUG(NULL, "auto sync: allow auto shutdown");
m_server.autoTermUnref();
m_autoTermLocked = false;
} else if (!m_autoTermLocked && lock) {
SE_LOG_DEBUG(NULL, "auto sync: prevent auto shutdown");
m_server.autoTermRef();
m_autoTermLocked = true;
}
// reschedule
schedule("initConfig() for " + configName);
}
void AutoSyncManager::schedule(const std::string &reason)
{
SE_LOG_DEBUG(NULL, "auto sync: reschedule, %s", reason.c_str());
// idle callback will be (re)set if needed
m_idleConnection.disconnect();
if (!preventTerm()) {
SE_LOG_DEBUG(NULL, "auto sync: nothing to do");
return;
}
if (!m_server.isIdle()) {
// Only schedule automatic syncs when nothing else is
// going on or pending.
SE_LOG_DEBUG(NULL, "auto sync: server not idle");
connectIdle();
return;
}
// Now look for a suitable task that is ready to run.
Timespec now = Timespec::monotonic();
for (const auto &entry: m_peerMap) {
const std::string &configName = entry.first;
const std::shared_ptr<AutoSyncTask> &task = entry.second;
if (task->m_interval <= 0 || // not enabled
task->m_permanentFailure) { // don't try again
continue;
}
if (task->m_lastSyncTime + task->m_interval > now) {
// Ran too recently, check again in the future. Always
// reset timer, because both m_lastSyncTime and m_interval
// may have changed.
//
// Adds two seconds just to be on the safe side and not
// wake up again too soon.
int seconds = (task->m_lastSyncTime + task->m_interval - now).seconds() + 2;
SE_LOG_DEBUG(NULL, "auto sync: %s: interval expires in %ds",
configName.c_str(),
seconds);
task->m_intervalTimeout.runOnce(seconds,
[this, configName] () { schedule(configName + " interval timer"); });
continue;
}
std::string readyURL;
for (const auto &urlinfo: task->m_urls) {
// check m_delay against presence of transport
Timespec *starttime = NULL;
PresenceStatus::PresenceSignal_t *signal = NULL;
Timeout *timeout = NULL;
switch (urlinfo.first) {
case AutoSyncTask::NEEDS_HTTP:
SE_LOG_DEBUG(NULL, "auto sync: %s: %s uses HTTP",
configName.c_str(),
urlinfo.second.c_str());
starttime = &m_httpStartTime;
signal = &m_server.getPresenceStatus().m_httpPresenceSignal;
timeout = &task->m_httpTimeout;
break;
case AutoSyncTask::NEEDS_BT:
SE_LOG_DEBUG(NULL, "auto sync: %s: %s uses Bluetooth",
configName.c_str(),
urlinfo.second.c_str());
starttime = &m_btStartTime;
signal = &m_server.getPresenceStatus().m_btPresenceSignal;
timeout = &task->m_btTimeout;
break;
case AutoSyncTask::NEEDS_OTHER:
SE_LOG_DEBUG(NULL, "auto sync: %s: %s uses some unknown transport",
configName.c_str(),
urlinfo.second.c_str());
break;
}
if (!starttime || // some other transport, assumed to be online, use it
(*starttime && // present
(task->m_delay <= 0 || *starttime + task->m_delay <= now))) { // present long enough
SE_LOG_DEBUG(NULL, "auto sync: %s: ready to run via %s (transport present for %lds > %ds auto sync delay)",
configName.c_str(),
urlinfo.second.c_str(),
starttime ? (long)(*starttime - now).seconds() : -1,
task->m_delay);
readyURL = urlinfo.second;
break;
}
if (!*starttime) {
// check again when it becomes present
signal->connect(PresenceStatus::PresenceSignal_t::slot_type(&AutoSyncManager::schedule,
this,
"presence change").track_foreign(shared_from_this()));
SE_LOG_DEBUG(NULL, "auto sync: %s: transport for %s not present",
configName.c_str(),
urlinfo.second.c_str());
} else {
// check again after waiting the requested amount of time
int seconds = (*starttime + task->m_delay - now).seconds() + 2;
SE_LOG_DEBUG(NULL, "auto sync: %s: presence delay of transport for %s expires in %ds",
configName.c_str(),
urlinfo.second.c_str(),
seconds);
timeout->runOnce(seconds,
[this, configName] () { schedule(configName + " transport timer"); });
}
}
if (!readyURL.empty()) {
// Found a task, run it. The session is not attached to any client,
// but we keep a pointer to it, so it won't go away.
// m_task = task;
// Just in case... also done in syncDone() when we detect
// that session is completed.
m_server.delaySessionDestruction(m_session);
task->m_syncSuccessStart = false;
m_session = make_weak_shared::make<Session>(m_server,
task->m_remoteDeviceId,
configName,
m_server.getNextSession());
// Temporarily set sync URL to the one which we picked above
// once the session is active (setConfig() not allowed earlier).
// Run sync as soon as it is active.
m_session->m_sessionActiveSignal.connect([session=m_session.get(), readyURL] () {
ReadOperations::Config_t config;
config[""]["syncURL"] = readyURL;
session->setConfig(true, true, config);
session->sync("", {});
});
// Now run it.
m_session->activate();
m_server.enqueue(m_session);
// Reschedule when server is idle again.
connectIdle();
return;
}
}
SE_LOG_DEBUG(NULL, "auto sync: nothing to do now");
}
void AutoSyncManager::connectIdle()
{
m_idleConnection =
m_server.m_idleSignal.connect(Server::IdleSignal_t::slot_type(&AutoSyncManager::schedule,
this,
"server is idle").track_foreign(shared_from_this()));
}
void AutoSyncManager::sessionStarted(const std::shared_ptr<Session> &session)
{
// Do we have a task for this config?
std::string configName = session->getConfigName();
PeerMap::iterator it = m_peerMap.find(configName);
if (it == m_peerMap.end()) {
SE_LOG_DEBUG(NULL, "auto sync: ignore running sync %s without config",
configName.c_str());
return;
}
std::shared_ptr<AutoSyncManager> me;
try {
me = shared_from_this();
} catch (...) {
SE_LOG_DEBUG(NULL, "auto sync: already destructing, ignore new sync %s",
configName.c_str());
return;
}
const std::shared_ptr<AutoSyncTask> &task = it->second;
task->m_lastSyncTime = Timespec::monotonic();
// track permanent failure
session->m_doneSignal.connect(Session::DoneSignal_t::slot_type(&AutoSyncManager::anySyncDone, this, task.get(), _1).track_foreign(task).track_foreign(me));
if (m_session == session) {
// Only for our own auto sync session: notify user once session starts successful.
//
// In the (unlikely) case that the AutoSyncTask gets deleted, the
// slot won't get involved, thus skipping user notifications.
// Also protects against manager destructing before session.
session->m_syncSuccessStartSignal.connect(Session::SyncSuccessStartSignal_t::slot_type(&AutoSyncManager::autoSyncSuccessStart, this, task.get()).track_foreign(task).track_foreign(me));
// Notify user once session ends, with or without failure.
// Same instance tracking as for sync success start.
session->m_doneSignal.connect(Session::DoneSignal_t::slot_type(&AutoSyncManager::autoSyncDone, this, task.get(), _1).track_foreign(task).track_foreign(me));
}
}
bool AutoSyncManager::preventTerm()
{
for (const auto &entry: m_peerMap) {
const std::shared_ptr<AutoSyncTask> &task = entry.second;
if (task->m_interval > 0 &&
!task->m_permanentFailure &&
!task->m_urls.empty()) {
// that task might run
return true;
}
}
return false;
}
void AutoSyncManager::autoSyncSuccessStart(AutoSyncTask *task)
{
task->m_syncSuccessStart = true;
SE_LOG_INFO(NULL,"Automatic sync for '%s' has been successfully started.\n",
task->m_peerName.c_str());
if (m_server.notificationsEnabled() &&
task->m_notifyLevel >= SyncConfig::NOTIFY_ALL) {
std::string summary = StringPrintf(_("%s is syncing"), task->m_peerName.c_str());
std::string body = StringPrintf(_("We have just started to sync your computer with the %s sync service."),
task->m_peerName.c_str());
// TODO: set config information for 'sync-ui'
m_notificationManager->publish(summary, body);
}
}
/**
* True if the error is likely to go away by itself when continuing
* with auto-syncing. This errs on the side of showing notifications
* too often rather than not often enough.
*/
static bool ErrorIsTemporary(SyncMLStatus status)
{
switch (status) {
case STATUS_TRANSPORT_FAILURE:
return true;
default:
// pretty much everying this not temporary
return false;
}
}
void AutoSyncManager::autoSyncDone(AutoSyncTask *task, SyncMLStatus status)
{
SE_LOG_INFO(NULL,"Automatic sync for '%s' has been done.\n", task->m_peerName.c_str());
if (m_server.notificationsEnabled()) {
// send a notification to notification server
std::string summary, body;
if (task->m_syncSuccessStart && status == STATUS_OK) {
if (task->m_notifyLevel >= SyncConfig::NOTIFY_ALL) {
// if sync is successfully started and done
summary = StringPrintf(_("%s sync complete"), task->m_peerName.c_str());
body = StringPrintf(_("We have just finished syncing your computer with the %s sync service."),
task->m_peerName.c_str());
//TODO: set config information for 'sync-ui'
m_notificationManager->publish(summary, body);
}
} else if (task->m_syncSuccessStart || !ErrorIsTemporary(status)) {
if (task->m_notifyLevel >= SyncConfig::NOTIFY_ERROR) {
// if sync is successfully started and has errors, or not started successful with a permanent error
// that needs attention
summary = StringPrintf(_("Sync problem."));
body = StringPrintf(_("Sorry, there's a problem with your sync that you need to attend to."));
//TODO: set config information for 'sync-ui'
m_notificationManager->publish(summary, body);
}
}
}
// keep session around to give clients a chance to query it
m_server.delaySessionDestruction(m_session);
m_session.reset();
}
void AutoSyncManager::anySyncDone(AutoSyncTask *task, SyncMLStatus status)
{
// set "permanently failed" flag according to most recent result
task->m_permanentFailure = status != STATUS_OK && !ErrorIsTemporary(status);
SE_LOG_DEBUG(NULL, "auto sync: sync session %s done, result %d %s",
task->m_configName.c_str(),
status,
task->m_permanentFailure ?
"is a permanent failure" :
status == STATUS_OK ?
"is success" :
"is temporary failure");
}
SE_END_CXX