Commit 55e892f1 authored by Simon Désaulniers's avatar Simon Désaulniers

ringaccount: introduce buddy presence on DHT

This functionality of RingAccount exposes methods to "track" a buddy with it's
ringid. The RingAccount keeps a map of BuddyInfo for all buddies to track. The
BuddyInfo struct contains the last time the device has been seen online.
RingAccount exposes the following methods:

* ::trackBuddyPresence which will look for a buddy's DeviceAnnouncement on the
  DHT to deduce the buddy's presence. This will be done each 10 minutes
  (DeviceAnnouncement expiration time).
* ::getTrackedBuddyPresence which returns the relevant content from the map of
  BuddyInfo, i.e. if each of the buddy is online or not.

Change-Id: Ib149585c4835da88b0b0248b4a866770d35afe90
parent 7b10c356
......@@ -3,6 +3,7 @@
*
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
* Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
* Author: Simon Désaulniers <simon.desaulniers@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -79,6 +80,20 @@ namespace ring {
using sip_utils::CONST_PJ_STR;
using std::chrono::system_clock;
struct RingAccount::BuddyInfo
{
/* the buddy id */
dht::InfoHash id;
/* the presence timestamps */
std::map<dht::InfoHash, std::chrono::steady_clock::time_point> devicesTimestamps;
/* The callable object to update buddy info */
std::function<void()> updateInfo {};
BuddyInfo(dht::InfoHash id) : id(id) {}
};
struct RingAccount::PendingCall
{
std::chrono::steady_clock::time_point start;
......@@ -1272,6 +1287,7 @@ RingAccount::getAccountDetails() const
a.emplace(Conf::CONFIG_DHT_PUBLIC_IN_CALLS, dhtPublicInCalls_ ? TRUE_STR : FALSE_STR);
a.emplace(DRing::Account::ConfProperties::RING_DEVICE_ID, ringDeviceId_);
a.emplace(DRing::Account::ConfProperties::RING_DEVICE_NAME, ringDeviceName_);
a.emplace(DRing::Account::ConfProperties::Presence::SUPPORT_SUBSCRIBE, TRUE_STR);
/* these settings cannot be changed (read only), but clients should still be
* able to read what they are */
......@@ -1628,6 +1644,84 @@ RingAccount::loadBootstrap() const
return bootstrap;
}
void
RingAccount::trackBuddyPresence(const std::string& buddy_id)
{
if (not dht_.isRunning()) {
RING_ERR("DHT node not running. Cannot track buddy %s", buddy_id.c_str());
return;
}
std::weak_ptr<RingAccount> weak_this = std::static_pointer_cast<RingAccount>(shared_from_this());
auto h = dht::InfoHash(parseRingUri(buddy_id));
auto buddy_infop = trackedBuddies_.emplace(h, decltype(trackedBuddies_)::mapped_type {h});
if (buddy_infop.second) {
auto& buddy_info = buddy_infop.first->second;
buddy_info.updateInfo = Manager::instance().scheduleTask([h,weak_this]() {
if (auto shared_this = weak_this.lock()) {
/* ::forEachDevice call will update buddy info accordingly. */
shared_this->forEachDevice(h, {}, [h, weak_this] (bool /* ok */) {
if (auto shared_this = weak_this.lock()) {
std::lock_guard<std::recursive_mutex> lock(shared_this->buddyInfoMtx);
auto buddy_info_it = shared_this->trackedBuddies_.find(h);
if (buddy_info_it == shared_this->trackedBuddies_.end()) return;
auto& buddy_info = buddy_info_it->second;
if (buddy_info.updateInfo) {
auto cb = buddy_info.updateInfo;
Manager::instance().scheduleTask(
std::move(cb),
std::chrono::steady_clock::now() + DeviceAnnouncement::TYPE.expiration
);
}
}
});
}
}, std::chrono::steady_clock::now())->cb;
RING_DBG("Now tracking buddy %s", h.toString().c_str());
} else
RING_WARN("Buddy %s is already being tracked.", h.toString().c_str());
}
std::map<std::string, bool>
RingAccount::getTrackedBuddyPresence()
{
std::lock_guard<std::recursive_mutex> lock(buddyInfoMtx);
std::map<std::string, bool> presence_info;
const auto shared_this = std::static_pointer_cast<const RingAccount>(shared_from_this());
for (const auto& buddy_info_p : shared_this->trackedBuddies_) {
const auto& devices_ts = buddy_info_p.second.devicesTimestamps;
const auto& last_seen_device_id = std::max_element(devices_ts.cbegin(), devices_ts.cend(),
[](decltype(buddy_info_p.second.devicesTimestamps)::value_type ld,
decltype(buddy_info_p.second.devicesTimestamps)::value_type rd)
{
return ld.second < rd.second;
}
);
presence_info.emplace(buddy_info_p.first.toString(), last_seen_device_id != devices_ts.cend()
? last_seen_device_id->second > std::chrono::steady_clock::now() - DeviceAnnouncement::TYPE.expiration
: false);
}
return presence_info;
}
void
RingAccount::onTrackedBuddyOnline(std::map<dht::InfoHash, BuddyInfo>::iterator& buddy_info_it, const dht::InfoHash& device_id)
{
std::lock_guard<std::recursive_mutex> lock(buddyInfoMtx);
RING_DBG("Buddy %s online: (device: %s)", buddy_info_it->second.id.toString().c_str(), device_id.toString().c_str());
buddy_info_it->second.devicesTimestamps[device_id] = std::chrono::steady_clock::now();
emitSignal<DRing::PresenceSignal::NewBuddyNotification>(getAccountID(), buddy_info_it->second.id.toString(), 1, "");
}
void
RingAccount::onTrackedBuddyOffline(std::map<dht::InfoHash, BuddyInfo>::iterator& buddy_info_it)
{
std::lock_guard<std::recursive_mutex> lock(buddyInfoMtx);
RING_DBG("Buddy %s offline", buddy_info_it->first.toString().c_str());
emitSignal<DRing::PresenceSignal::NewBuddyNotification>(getAccountID(), buddy_info_it->first.toString(), 0, "");
buddy_info_it->second.devicesTimestamps.clear();
}
void
RingAccount::doRegister_()
{
......@@ -2418,23 +2512,6 @@ RingAccount::getContactHeader(pjsip_transport* t)
return contact_;
}
/**
* Enable the presence module
*/
void
RingAccount::enablePresence(const bool& /* enabled */)
{
}
/**
* Set the presence (PUBLISH/SUBSCRIBE) support flags
* and process the change.
*/
void
RingAccount::supportPresence(int /* function */, bool /* enabled*/)
{
}
/* trust requests */
std::map<std::string, std::string>
RingAccount::getTrustRequests() const
......@@ -2569,6 +2646,17 @@ RingAccount::forEachDevice(const dht::InfoHash& to,
op(shared, dev.dev);
return true;
}, [=](bool /*ok*/){
{
std::lock_guard<std::recursive_mutex> lock(shared->buddyInfoMtx);
auto buddy_info_it = shared->trackedBuddies_.find(to);
if (buddy_info_it != shared->trackedBuddies_.end()) {
if (not treatedDevices->empty()) {
for (auto& device_id : *treatedDevices)
shared->onTrackedBuddyOnline(buddy_info_it, device_id);
} else
shared->onTrackedBuddyOffline(buddy_info_it);
}
}
RING_WARN("forEachDevice: found %lu devices", treatedDevices->size());
if (end) end(not treatedDevices->empty());
});
......
......@@ -2,6 +2,7 @@
* Copyright (C) 2014-2016 Savoir-faire Linux Inc.
*
* Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
* Author: Simon Désaulniers <simon.desaulniers@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -138,6 +139,22 @@ class RingAccount : public SIPAccountBase {
*/
void loadConfig() override {}
/**
* Adds an account id to the list of accounts to track on the DHT for
* buddy presence.
*
* @param buddy_id The buddy id.
*/
void trackBuddyPresence(const std::string& buddy_id);
/**
* Tells for each tracked account id if it has been seen online so far
* in the last DeviceAnnouncement::TYPE.expiration minutes.
*
* @return map of buddy_uri to bool (online or not)
*/
std::map<std::string, bool> getTrackedBuddyPresence();
/**
* Connect to the DHT.
*/
......@@ -203,18 +220,6 @@ class RingAccount : public SIPAccountBase {
/* Returns true if the username and/or hostname match this account */
MatchRank matches(const std::string &username, const std::string &hostname) const override;
/**
* Activate the module.
* @param function Publish or subscribe to enable
* @param enable Flag
*/
void enablePresence(const bool& enable);
/**
* Activate the publish/subscribe.
* @param enable Flag
*/
void supportPresence(int function, bool enable);
/**
* Implementation of Account::newOutgoingCall()
* Note: keep declaration before newOutgoingCall template.
......@@ -308,6 +313,7 @@ class RingAccount : public SIPAccountBase {
struct ArchiveContent;
struct DeviceAnnouncement;
struct DeviceSync;
struct BuddyInfo;
void syncDevices();
void onReceiveDeviceSync(DeviceSync&& sync);
......@@ -323,6 +329,21 @@ class RingAccount : public SIPAccountBase {
*/
static std::pair<std::vector<uint8_t>, dht::InfoHash> computeKeys(const std::string& password, const std::string& pin, bool previous=false);
/**
* Update tracking info when buddy appears offline.
*
* @param buddy_info_it An iterator over the map trackedBuddies_
*/
void onTrackedBuddyOffline(std::map<dht::InfoHash, BuddyInfo>::iterator& buddy_info_it);
/**
* Update tracking info when buddy appears offline.
*
* @param buddy_info_it An iterator over the map trackedBuddies_
* @param device_id The device id
*/
void onTrackedBuddyOnline(std::map<dht::InfoHash, BuddyInfo>::iterator& buddy_info_it, const dht::InfoHash& device_id);
void doRegister_();
void incomingCall(dht::IceCandidates&& msg, std::shared_ptr<dht::crypto::Certificate> from);
......@@ -402,8 +423,13 @@ class RingAccount : public SIPAccountBase {
std::shared_ptr<dht::Value> announce_;
/* this ring account associated devices */
std::map<dht::InfoHash, KnownDevice> knownDevices_;
/* tracked buddies presence */
std::recursive_mutex buddyInfoMtx;
std::map<dht::InfoHash, BuddyInfo> trackedBuddies_;
void loadAccount(const std::string& archive_password = {}, const std::string& archive_pin = {});
void loadAccountFromDHT(const std::string& archive_password, const std::string& archive_pin);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment