Presence detection: connman part (MB#7700)

This patch implements the presence detection dbus api
(checkPresence and presence signal). It also takes account of the
possiblity that a peer might have multiple contacting mechanisms (HTTP,
Bluetooth..). It maintains a StatusMap, for each configured peer, a list of
<transport, status> is recorded. The StatusMap will change either via
Session.setConfig or network status changes, in both cases the presence signal
will be sent out for each changed peers.

CheckPresence API will return explictly about the presence status of a
corresponding peer.

Also added python unit tests by faking a connman service on session bus.

Limitaions:
The detection only detects local network adpater (wifi/ethernet connected or
bluetooth adapter enabled).
The detection works by regularly polling connman, however it should be improved
by listening signals sending out by connman.
The detection only queries connman, therefore on a distribution where connman is
not available the peers will be always "presence".
This commit is contained in:
Chen Congwu 2010-02-03 09:50:56 +08:00 committed by Patrick Ohly
parent 4be60e7776
commit 8a66a48046
2 changed files with 416 additions and 2 deletions

View File

@ -337,6 +337,9 @@ class DBusServer : public DBusObjectHelper
*/
std::list<std::pair<Caller_t, int> > m_attachedClients;
/* Event source that regurally pool network manager
* */
GLibEvent m_pollConnman;
/**
* The session which currently holds the main lock on the server.
* To avoid issues with concurrent modification of data or configs,
@ -504,6 +507,152 @@ class DBusServer : public DBusObjectHelper
const std::string &,
const std::map<string, string> &> infoRequest;
static gboolean connman_poll (gpointer dbus_server);
DBusConnectionPtr m_connmanConn;
friend class Session;
class PresenceStatus {
bool m_httpPresence;
bool m_btPresence;
bool m_initiated;
DBusServer &m_server;
enum PeerStatus {
/* The transport is not available (local problem) */
NOTRANSPORT,
/* The peer is not contactable (remote problem) */
UNREACHABLE,
/* Not for sure whether the peer is presence but likely*/
MIGHTWORK,
INVALID
};
typedef map<string, vector<pair <string, PeerStatus> > > StatusMap;
typedef pair<const string, vector<pair <string, PeerStatus> > > StatusPair;
typedef pair <string, PeerStatus> PeerStatusPair;
StatusMap m_peers;
static std::string status2string (PeerStatus status) {
switch (status) {
case NOTRANSPORT:
return "no transport";
break;
case UNREACHABLE:
return "not present";
break;
case MIGHTWORK:
return "";
break;
case INVALID:
return "invalid transport status";
}
// not reached, keep compiler happy
return "";
}
public:
PresenceStatus (DBusServer &server)
:m_httpPresence (false), m_btPresence (false), m_initiated (false), m_server (server)
{}
/* Implement DBusServer::checkPresence*/
void checkPresence (const string &peer, string& status, std::vector<std::string> &transport) {
vector< pair<string, PeerStatus> > mytransports = m_peers[peer];
if (mytransports.empty()) {
//wrong config name?
status = status2string(NOTRANSPORT);
transport.clear();
return;
}
PeerStatus mystatus = MIGHTWORK;
transport.clear();
//only if all transports are unavailable can we declare the peer
//status as unavailable
BOOST_FOREACH (PeerStatusPair &mytransport, mytransports) {
if (mytransport.second == MIGHTWORK){
transport.push_back (mytransport.first);
}
}
if (transport.empty()) {
mystatus = NOTRANSPORT;
}
status = status2string(mystatus);
}
void updateConfigPeers (const std::string &peer, const ReadOperations::Config_t &config) {
ReadOperations::Config_t::const_iterator iter = config.find ("");
if (iter != config.end()) {
map<string,string> localConfigs = (*iter).second;
BOOST_FOREACH (const StringPair &entry, localConfigs) {
if (boost::iequals ("SyncURL", entry.first)) {
//TODO handling multiple urls
m_peers[peer].clear();
m_peers[peer].push_back(make_pair(entry.second,MIGHTWORK));
//As a simple approach, just reinitialize the whole STATUSMAP
//it will cause later updatePresenceStatus resend all signals
m_initiated = false;
break;
}
}
}
}
void updatePresenceStatus (bool httpPresence, bool btPresence) {
bool httpChanged = (m_httpPresence != httpPresence);
bool btChanged = (m_btPresence != btPresence);
m_httpPresence = httpPresence;
m_btPresence = btPresence;
if (m_initiated && !httpChanged && !btChanged) {
//nothing changed
return;
}
//initialize the configured peer list
if (!m_initiated) {
SyncConfig::ConfigList list = SyncConfig::getConfigs();
BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server, list) {
ReadOperations::Config_t config;
m_server.getConfig(server.first, false, config);
BOOST_FOREACH (const StringPair &entry, config[""]) {
if (boost::iequals ("SyncURL", entry.first)) {
//TODO handling multiple urls
m_peers[server.first].push_back(make_pair(entry.second,MIGHTWORK));
break;
}
}
}
}
//iterate all configured peers and fire singals
BOOST_FOREACH (StatusPair &peer, m_peers) {
//iterate all possible transports
//TODO One peer might got more than one signals, avoid this
std::vector<pair<string, PeerStatus> > &transports = peer.second;
BOOST_FOREACH (PeerStatusPair &entry, transports) {
string url = entry.first;
if (boost::starts_with (url, "http") && (httpChanged || !m_initiated)) {
entry.second = m_httpPresence ? MIGHTWORK: NOTRANSPORT;
m_server.presence (peer.first, status2string (entry.second), entry.first);
SE_LOG_DEBUG(NULL, NULL,
"http presence signal %s,%s,%s",
peer.first.c_str(),
status2string (entry.second).c_str(), entry.first.c_str());
} else if (boost::starts_with (url, "obex-bt") && (btChanged || !m_initiated)) {
entry.second = m_btPresence ? MIGHTWORK: NOTRANSPORT;
m_server.presence (peer.first, status2string (entry.second), entry.first);
SE_LOG_DEBUG(NULL, NULL,
"bluetooth presence signal %s,%s,%s",
peer.first.c_str(),
status2string (entry.second).c_str(), entry.first.c_str());
}
}
}
m_initiated = true;
}
}m_presence;
public:
DBusServer(GMainLoop *loop, const DBusConnectionPtr &conn, int duration);
~DBusServer();
@ -567,6 +716,13 @@ public:
/** callback to reset for auto termination checking */
void autoTermCallback() { m_autoTerm.reset(); }
/** poll_nm callback for connman, used for presence detection*/
void connman_cb(const std::map <std::string, boost::variant <std::vector <std::string> > >& props, const string &error);
PresenceStatus& getPresenceStatus() {return m_presence;}
DBusConnectionPtr getConnmanConnection() {return m_connmanConn;}
};
@ -1103,6 +1259,9 @@ class Session : public DBusObjectHelper,
/** Session.Restore() */
void restore(const string &dir, bool before,const std::vector<std::string> &sources);
/** Session.checkPresence() */
void checkPresence (string &status);
/**
* Must be called each time that properties changing the
* overall status are changed. Ensures that the corresponding
@ -1958,6 +2117,7 @@ void Session::setConfig(bool update, bool temporary,
throw std::runtime_error("Clearing existing configuration and temporary configuration changes which only affects the duration of the session are mutually exclusive");
}
m_server.getPresenceStatus().updateConfigPeers (m_configName, config);
/** check whether we need remove the entire configuration */
if(!update && config.empty()) {
boost::shared_ptr<SyncConfig> syncConfig(new SyncConfig(getConfigName()));
@ -2221,6 +2381,7 @@ Session::Session(DBusServer &server,
add(this, &Session::getStatus, "GetStatus");
add(this, &Session::getProgress, "GetProgress");
add(this, &Session::restore, "Restore");
add(this, &Session::checkPresence, "checkPresence");
add(emitStatus);
add(emitProgress);
}
@ -2532,6 +2693,13 @@ string Session::askPassword(const string &passwordName,
return "";
}
/*Implementation of Session.CheckPresence */
void Session::checkPresence (string &status)
{
vector<string> transport;
m_server.m_presence.checkPresence (m_configName, status, transport);
}
/************************ ProgressData implementation *****************/
ProgressData::ProgressData(int32_t &progress)
: m_progress(progress),
@ -3454,7 +3622,7 @@ void DBusServer::checkPresence(const std::string &server,
std::string &status,
std::vector<std::string> &transports)
{
// TODO: implement this, right now always return status = "" = available
return m_presence.checkPresence(server, status, transports);
}
void DBusServer::getSessions(std::vector<std::string> &sessions)
@ -3483,7 +3651,8 @@ DBusServer::DBusServer(GMainLoop *loop, const DBusConnectionPtr &conn, int durat
m_autoTerm(duration),
sessionChanged(*this, "SessionChanged"),
presence(*this, "Presence"),
infoRequest(*this, "InfoRequest")
infoRequest(*this, "InfoRequest"),
m_presence(*this)
{
struct timeval tv;
gettimeofday(&tv, NULL);
@ -3503,6 +3672,13 @@ DBusServer::DBusServer(GMainLoop *loop, const DBusConnectionPtr &conn, int durat
add(sessionChanged);
add(presence);
add(infoRequest);
const char *connmanTest = getenv ("DBUS_TEST_CONNMAN");
m_connmanConn = g_dbus_setup_bus (connmanTest ? DBUS_BUS_SESSION: DBUS_BUS_SYSTEM, NULL, true, NULL);
if (m_connmanConn) {
m_pollConnman = g_timeout_add_seconds (10, connman_poll, static_cast <gpointer> (this));
}
}
DBusServer::~DBusServer()
@ -3742,6 +3918,70 @@ void DBusServer::removeInfoReq(const InfoReq &req)
}
}
gboolean DBusServer::connman_poll (gpointer dbusserver)
{
DBusServer *me = static_cast<DBusServer *>(dbusserver);
DBusConnectionPtr conn = me->getConnmanConnection();
struct ConnmanClient : public DBusCallObject
{
DBusConnectionPtr m_connection;
ConnmanClient (DBusConnectionPtr conn ) : m_connection (conn) {}
virtual const char *getDestination() const {return "org.moblin.connman";}
virtual const char *getPath() const {return "/";}
virtual const char *getInterface() const {return "org.moblin.connman.Manager";}
virtual const char *getMethod() const {return "GetProperties"; }
virtual DBusConnection *getConnection() const {return m_connection.get();}
}connman (conn);
typedef std::map <std::string, boost::variant <std::vector <std::string> > > PropDict;
DBusClientCall0<PropDict> getProp(connman);
getProp (boost::bind(&DBusServer::connman_cb, me, _1, _2));
return TRUE;
}
void DBusServer::connman_cb (const std::map <std::string, boost::variant <std::vector <std::string> > >& props, const string &error)
{
if (!error.empty()) {
if (error == "org.freedesktop.DBus.Error.ServiceUnknown") {
// no connman available, remove connman_poll.
m_pollConnman.set(0);
SE_LOG_DEBUG (NULL, NULL, "No connman service available %s", error.c_str());
return;
}
SE_LOG_DEBUG (NULL, NULL, "error in connman_cb %s", error.c_str());
return;
}
typedef std::pair <std::string, boost::variant <std::vector <std::string> > > element;
bool httpPresence = false, btPresence = false;
BOOST_FOREACH (element entry, props) {
//match connected for HTTP based peers (wifi/wimax/ethernet)
if (entry.first == "ConnectedTechnologies") {
std::vector <std::string> connected = boost::get <std::vector <std::string> > (entry.second);
BOOST_FOREACH (std::string tech, connected) {
if (boost::iequals (tech, "wifi") || boost::iequals (tech, "ethernet")
|| boost::iequals (tech, "wimax")) {
httpPresence = true;
break;
}
}
} else if (entry.first == "EnabledTechnologies") {
std::vector <std::string> enabled = boost::get <std::vector <std::string> > (entry.second);
BOOST_FOREACH (std::string tech, enabled){
if (boost::iequals (tech, "bluetooth")) {
btPresence = true;
break;
}
}
} else {
continue;
}
}
//now delivering the signals
m_presence.updatePresenceStatus (httpPresence, btPresence);
}
/********************** InfoReq implementation ******************/
InfoReq::InfoReq(DBusServer &server,
const string &type,

View File

@ -29,6 +29,7 @@ import heapq
import dbus
from dbus.mainloop.glib import DBusGMainLoop
import dbus.service
import gobject
import sys
@ -656,6 +657,179 @@ class TestDBusServerTerm(unittest.TestCase, DBusUtil):
else:
self.fail("no exception thrown")
class Connman (dbus.service.Object):
count = 0
@dbus.service.method(dbus_interface='org.moblin.connman.Manager', in_signature='', out_signature='a{sv}')
def GetProperties(self):
self.count = self.count+1
if (self.count == 1):
loop.quit()
return {"ConnectedTechnologies":["ethernet", "some other stuff"],
"EnabledTechnologies": ["bluetooth"]}
elif (self.count == 2):
""" unplug the ethernet cable """
loop.quit()
return {"ConnectedTechnologies":["some other stuff"],
"EnabledTechnologies": ["bluetooth"]}
elif (self.count == 3):
""" replug the ethernet cable """
loop.quit()
return {"ConnectedTechnologies":["ethernet", "some other stuff"],
"EnabledTechnologies": ["bluetooth"]}
elif (self.count == 4):
""" nothing presence """
loop.quit()
return {"ConnectedTechnologies":[""],
"EnabledTechnologies": [""]}
elif (self.count == 5):
""" come back the same time """
loop.quit()
return {"ConnectedTechnologies":["ethernet", "some other stuff"],
"EnabledTechnologies": ["bluetooth"]}
def reset(self):
self.count = 0
class TestDBusServerPresence(unittest.TestCase, DBusUtil):
"""Tests Presence signal and checkPresence API"""
name = dbus.service.BusName ("org.moblin.connman", bus);
conn = Connman (bus, "/")
def setUp(self):
self.setUpServer()
@timeout(100)
def testPresenceSignal(self):
self.conn.reset()
self.setUpSession("foo")
self.session.SetConfig(False, False, {"" : {"syncURL":
"http://http-only-1"}})
self.session.Detach()
def cb_http_presence(server, status, transport):
self.failUnlessEqual (status, "")
self.failUnlessEqual (server, "foo")
self.failUnlessEqual (transport, "http://http-only-1")
match = bus.add_signal_receiver(cb_http_presence,
'Presence',
'org.syncevolution.Server',
'org.syncevolution',
None,
byte_arrays=True,
utf8_strings=True)
loop.run()
time.sleep(1)
self.setUpSession("foo")
self.session.SetConfig(True, True, {"" : {"syncURL":
"obex-bt://temp-bluetooth-peer-changed-from-http"}})
def cb_bt_presence(server, status, transport):
self.failUnlessEqual (status, "")
self.failUnlessEqual (server, "foo")
self.failUnlessEqual (transport,
"obex-bt://temp-bluetooth-peer-changed-from-http")
match.remove()
match = bus.add_signal_receiver(cb_bt_presence,
'Presence',
'org.syncevolution.Server',
'org.syncevolution',
None,
byte_arrays=True,
utf8_strings=True)
loop.run()
time.sleep(1)
self.session.Detach()
self.setUpSession("bar")
self.session.SetConfig(False, False, {"" : {"syncURL":
"http://http-client-2"}})
self.session.Detach()
self.foo = "random string"
self.bar = "random string"
def cb_bt_http_presence(server, status, transport):
if (server == "foo"):
self.foo = status
elif (server == "bar"):
self.bar = status
else:
self.fail("wrong server config")
match.remove()
match = bus.add_signal_receiver(cb_bt_http_presence,
'Presence',
'org.syncevolution.Server',
'org.syncevolution',
None,
byte_arrays=True,
utf8_strings=True)
loop.run()
loop.run()
time.sleep(1)
self.failUnlessEqual (self.foo, "")
self.failUnlessEqual (self.bar, "")
match.remove()
@timeout(100)
def testServerCheckPresence(self):
self.conn.reset()
self.setUpSession("foo")
self.session.SetConfig(False, False, {"" : {"syncURL":
"http://http-client"}})
self.session.Detach()
self.setUpSession("bar")
self.session.SetConfig(False, False, {"" : {"syncURL":
"obex-bt://bt-client"}})
self.session.Detach()
self.setUpSession("foobar")
self.session.SetConfig(False, False, {"" : {"syncURL":
"obex-bt://bt-client-mixed http://http-client-mixed"}})
self.session.Detach()
#let dbus server get the first presence
loop.run()
time.sleep(1)
(status, transports) = self.server.CheckPresence ("foo")
self.failUnlessEqual (status, "")
self.failUnlessEqual (transports, ["http://http-client"])
(status, transports) = self.server.CheckPresence ("bar")
self.failUnlessEqual (status, "")
self.failUnlessEqual (transports, ["obex-bt://bt-client"])
(status, transports) = self.server.CheckPresence ("foobar")
self.failUnlessEqual (status, "")
self.failUnlessEqual (transports, ["obex-bt://bt-client-mixed",
"http://http-client-mixed"])
#count = 2
loop.run()
time.sleep(1)
(status, transports) = self.server.CheckPresence ("foo")
self.failUnlessEqual (status, "no transport")
(status, transports) = self.server.CheckPresence ("bar")
self.failUnlessEqual (status, "")
self.failUnlessEqual (transports, ["obex-bt://bt-client"])
(status, transports) = self.server.CheckPresence ("foobar")
self.failUnlessEqual (status, "")
self.failUnlessEqual (transports, ["obex-bt://bt-client-mixed"])
@timeout(100)
def testSessionCheckPresence(self):
self.conn.reset()
self.setUpSession("foobar")
self.session.SetConfig(False, False, {"" : {"syncURL":
"obex-bt://bt-client-mixed http://http-client-mixed"}})
loop.run()
time.sleep(1)
status = self.session.checkPresence()
self.failUnlessEqual (status, "")
loop.run()
loop.run()
loop.run()
#count = 4
time.sleep(1)
status = self.session.checkPresence()
self.failUnlessEqual (status, "no transport")
def run(self, result):
os.environ["DBUS_TEST_CONNMAN"] = "session"
self.runTest(result, True)
class TestDBusSession(unittest.TestCase, DBusUtil):
"""Tests that work with an active session."""