diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index 92872d147..9b3dd7760 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -1720,6 +1720,28 @@
+
+
+
+ Update conversation's preferences (synced across devices) such as color, notifications, etc.
+
+
+
+
+
+
+
+
+
+
+ Get conversation's preferences
+
+
+
+
+
+
+
@@ -2119,6 +2141,29 @@
+
+
+
+ Notify clients when preferences for a conversation are updated
+
+
+
+ Account id related
+
+
+
+
+ Conversation id
+
+
+
+
+
+ New preferences
+
+
+
+
diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp
index 1f822c24c..5170d948d 100644
--- a/bin/dbus/dbusclient.cpp
+++ b/bin/dbus/dbusclient.cpp
@@ -317,6 +317,8 @@ DBusClient::initLibrary(int flags)
bind(&DBusConfigurationManager::conversationMemberEvent, confM, _1, _2, _3, _4)),
exportable_callback(
bind(&DBusConfigurationManager::onConversationError, confM, _1, _2, _3, _4)),
+ exportable_callback(
+ bind(&DBusConfigurationManager::conversationPreferencesUpdated, confM, _1, _2, _3)),
};
#ifdef ENABLE_VIDEO
diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp
index 3ebf647c7..3effef201 100644
--- a/bin/dbus/dbusconfigurationmanager.cpp
+++ b/bin/dbus/dbusconfigurationmanager.cpp
@@ -879,6 +879,21 @@ DBusConfigurationManager::conversationInfos(const std::string& accountId,
return DRing::conversationInfos(accountId, conversationId);
}
+void
+DBusConfigurationManager::setConversationPreferences(const std::string& accountId,
+ const std::string& conversationId,
+ const std::map& infos)
+{
+ DRing::setConversationPreferences(accountId, conversationId, infos);
+}
+
+std::map
+DBusConfigurationManager::getConversationPreferences(const std::string& accountId,
+ const std::string& conversationId)
+{
+ return DRing::getConversationPreferences(accountId, conversationId);
+}
+
void
DBusConfigurationManager::addConversationMember(const std::string& accountId,
const std::string& conversationId,
diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h
index 1316b9a72..b14548730 100644
--- a/bin/dbus/dbusconfigurationmanager.h
+++ b/bin/dbus/dbusconfigurationmanager.h
@@ -265,6 +265,11 @@ public:
const std::map& infos);
std::map conversationInfos(const std::string& accountId,
const std::string& conversationId);
+ void setConversationPreferences(const std::string& accountId,
+ const std::string& conversationId,
+ const std::map& prefs);
+ std::map getConversationPreferences(const std::string& accountId,
+ const std::string& conversationId);
void addConversationMember(const std::string& accountId,
const std::string& conversationId,
const std::string& contactUri);
diff --git a/bin/jni/conversation.i b/bin/jni/conversation.i
index cfd130f80..4437faaf3 100644
--- a/bin/jni/conversation.i
+++ b/bin/jni/conversation.i
@@ -35,6 +35,7 @@ public:
virtual void conversationRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */){}
virtual void conversationMemberEvent(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* memberUri */, int /* event */){}
virtual void onConversationError(const std::string& /*accountId*/, const std::string& /* conversationId */, uint32_t /* code */, const std::string& /* what */){}
+ virtual void conversationPreferencesUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map /* preferences */){}
};
%}
@@ -51,6 +52,8 @@ namespace DRing {
std::vector> getConversationRequests(const std::string& accountId);
void updateConversationInfos(const std::string& accountId, const std::string& conversationId, const std::map& infos);
std::map conversationInfos(const std::string& accountId, const std::string& conversationId);
+ void setConversationPreferences(const std::string& accountId, const std::string& conversationId, const std::map& prefs);
+ std::map getConversationPreferences(const std::string& accountId, const std::string& conversationId);
// Member management
void addConversationMember(const std::string& accountId, const std::string& conversationId, const std::string& contactUri);
@@ -86,4 +89,5 @@ public:
virtual void conversationRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */){}
virtual void conversationMemberEvent(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* memberUri */, int /* event */){}
virtual void onConversationError(const std::string& /*accountId*/, const std::string& /* conversationId */, uint32_t /* code */, const std::string& /* what */){}
+ virtual void conversationPreferencesUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map /* preferences */){}
};
\ No newline at end of file
diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index b0dcb92fc..120ec30a5 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -328,7 +328,8 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM
exportable_callback(bind(&ConversationCallback::conversationReady, convM, _1, _2)),
exportable_callback(bind(&ConversationCallback::conversationRemoved, convM, _1, _2)),
exportable_callback(bind(&ConversationCallback::conversationMemberEvent, convM, _1, _2, _3, _4)),
- exportable_callback(bind(&ConversationCallback::onConversationError, convM, _1, _2, _3, _4))
+ exportable_callback(bind(&ConversationCallback::onConversationError, convM, _1, _2, _3, _4)),
+ exportable_callback(bind(&ConversationCallback::conversationPreferencesUpdated, convM, _1, _2, _3))
};
if (!DRing::init(static_cast(DRing::DRING_FLAG_DEBUG)))
diff --git a/bin/nodejs/callback.h b/bin/nodejs/callback.h
index cb1c5d759..5feaf731f 100644
--- a/bin/nodejs/callback.h
+++ b/bin/nodejs/callback.h
@@ -42,6 +42,7 @@ Persistent conferenceCreatedCb;
Persistent conferenceChangedCb;
Persistent conferenceRemovedCb;
Persistent onConferenceInfosUpdatedCb;
+Persistent conversationPreferencesUpdatedCb;
std::queue> pendingSignals;
std::mutex pendingSignalsLock;
@@ -117,6 +118,8 @@ getPresistentCb(std::string_view signal)
return &conferenceRemovedCb;
else if (signal == "OnConferenceInfosUpdated")
return &onConferenceInfosUpdatedCb;
+ else if (signal == "ConversationPreferencesUpdated")
+ return &conversationPreferencesUpdatedCb;
else
return nullptr;
}
@@ -131,7 +134,7 @@ getPresistentCb(std::string_view signal)
inline std::string_view
toView(const String::Utf8Value& utf8)
{
- return {*utf8, (size_t)utf8.length()};
+ return {*utf8, (size_t) utf8.length()};
}
inline SWIGV8_ARRAY
@@ -237,8 +240,7 @@ composingStatusChanged(const std::string& accountId,
{
std::lock_guard lock(pendingSignalsLock);
pendingSignals.emplace([accountId, conversationId, from, state]() {
- Local func = Local::New(Isolate::GetCurrent(),
- composingStatusChangedCb);
+ Local func = Local::New(Isolate::GetCurrent(), composingStatusChangedCb);
if (!func.IsEmpty()) {
SWIGV8_VALUE callback_args[] = {V8_STRING_NEW_LOCAL(accountId),
V8_STRING_NEW_LOCAL(conversationId),
@@ -592,7 +594,10 @@ conversationLoaded(uint32_t id,
}
void
-messagesFound(uint32_t id, const std::string& accountId, const std::string& conversationId, const std::vector>& messages)
+messagesFound(uint32_t id,
+ const std::string& accountId,
+ const std::string& conversationId,
+ const std::vector>& messages)
{
std::lock_guard lock(pendingSignalsLock);
pendingSignals.emplace([id, accountId, conversationId, messages]() {
@@ -628,12 +633,13 @@ messageReceived(const std::string& accountId,
void
conversationProfileUpdated(const std::string& accountId,
- const std::string& conversationId ,
- const std::map& profile)
+ const std::string& conversationId,
+ const std::map& profile)
{
std::lock_guard lock(pendingSignalsLock);
pendingSignals.emplace([accountId, conversationId, profile]() {
- Local func = Local::New(Isolate::GetCurrent(), conversationProfileUpdatedCb);
+ Local func = Local::New(Isolate::GetCurrent(),
+ conversationProfileUpdatedCb);
if (!func.IsEmpty()) {
SWIGV8_VALUE callback_args[] = {V8_STRING_NEW_LOCAL(accountId),
V8_STRING_NEW_LOCAL(conversationId),
@@ -818,3 +824,22 @@ onConferenceInfosUpdated(const std::string& accountId,
});
uv_async_send(&signalAsync);
}
+
+void
+conversationPreferencesUpdated(const std::string& accountId,
+ const std::string& convId,
+ const std::map& preferences)
+{
+ std::lock_guard lock(pendingSignalsLock);
+ pendingSignals.emplace([accountId, convId, preferences]() {
+ Local func = Local::New(Isolate::GetCurrent(),
+ conversationPreferencesUpdatedCb);
+ if (!func.IsEmpty()) {
+ SWIGV8_VALUE callback_args[] = {V8_STRING_NEW_LOCAL(accountId),
+ V8_STRING_NEW_LOCAL(convId),
+ stringMapToJsMap(preferences)};
+ func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 3, callback_args);
+ }
+ });
+ uv_async_send(&signalAsync);
+}
diff --git a/bin/nodejs/conversation.i b/bin/nodejs/conversation.i
index 8ae3b712f..f645253f6 100644
--- a/bin/nodejs/conversation.i
+++ b/bin/nodejs/conversation.i
@@ -35,6 +35,7 @@ public:
virtual void conversationRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */){}
virtual void conversationMemberEvent(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* memberUri */, int /* event */){}
virtual void onConversationError(const std::string& /*accountId*/, const std::string& /* conversationId */, uint32_t /* code */, const std::string& /* what */){}
+ virtual void conversationPreferencesUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map /* preferences */){}
};
%}
@@ -51,6 +52,8 @@ namespace DRing {
std::vector> getConversationRequests(const std::string& accountId);
void updateConversationInfos(const std::string& accountId, const std::string& conversationId, const std::map& infos);
std::map conversationInfos(const std::string& accountId, const std::string& conversationId);
+ void setConversationPreferences(const std::string& accountId, const std::string& conversationId, const std::map& prefs);
+ std::map getConversationPreferences(const std::string& accountId, const std::string& conversationId);
// Member management
void addConversationMember(const std::string& accountId, const std::string& conversationId, const std::string& contactUri);
@@ -87,4 +90,5 @@ public:
virtual void conversationRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */){}
virtual void conversationMemberEvent(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* memberUri */, int /* event */){}
virtual void onConversationError(const std::string& /*accountId*/, const std::string& /* conversationId */, uint32_t /* code */, const std::string& /* what */){}
+ virtual void conversationPreferencesUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map /* preferences */){}
};
\ No newline at end of file
diff --git a/bin/nodejs/nodejs_interface.i b/bin/nodejs/nodejs_interface.i
index bbc909576..8da4ace3f 100644
--- a/bin/nodejs/nodejs_interface.i
+++ b/bin/nodejs/nodejs_interface.i
@@ -155,7 +155,8 @@ void init(const SWIGV8_VALUE& funcMap){
exportable_callback(bind(&conversationReady, _1, _2)),
exportable_callback(bind(&conversationRemoved, _1, _2)),
exportable_callback(bind(&conversationMemberEvent, _1, _2, _3, _4)),
- exportable_callback(bind(&onConversationError, _1, _2, _3, _4))
+ exportable_callback(bind(&onConversationError, _1, _2, _3, _4)),
+ exportable_callback(bind(&ConversationCallback::conversationPreferencesUpdated, convM, _1, _2, _3))
};
if (!DRing::init(static_cast(DRing::DRING_FLAG_DEBUG)))
diff --git a/configure.ac b/configure.ac
index 80e73d235..3b287c9ed 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ dnl Jami - configure.ac
dnl Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
-AC_INIT([Jami Daemon],[13.4.0],[jami@gnu.org],[jami])
+AC_INIT([Jami Daemon],[13.5.0],[jami@gnu.org],[jami])
dnl Clear the implicit flags that default to '-g -O2', otherwise they
dnl take precedence over the values we set via the
diff --git a/meson.build b/meson.build
index 65f560514..bd14e1b37 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
project('jami-daemon', ['c', 'cpp'],
- version: '13.4.0',
+ version: '13.5.0',
license: 'GPL3+',
default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'],
meson_version:'>= 0.56'
diff --git a/src/client/conversation_interface.cpp b/src/client/conversation_interface.cpp
index 67a4a6404..47607790a 100644
--- a/src/client/conversation_interface.cpp
+++ b/src/client/conversation_interface.cpp
@@ -106,6 +106,25 @@ conversationInfos(const std::string& accountId, const std::string& conversationI
return {};
}
+void
+setConversationPreferences(const std::string& accountId,
+ const std::string& conversationId,
+ const std::map& prefs)
+{
+ if (auto acc = jami::Manager::instance().getAccount(accountId))
+ if (auto convModule = acc->convModule())
+ convModule->setConversationPreferences(conversationId, prefs);
+}
+
+std::map
+getConversationPreferences(const std::string& accountId, const std::string& conversationId)
+{
+ if (auto acc = jami::Manager::instance().getAccount(accountId))
+ if (auto convModule = acc->convModule())
+ return convModule->getConversationPreferences(conversationId);
+ return {};
+}
+
// Member management
void
addConversationMember(const std::string& accountId,
diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index 53fd6858e..274a3e73a 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -138,6 +138,7 @@ getSignalHandlers()
exported_callback(),
exported_callback(),
exported_callback(),
+ exported_callback(),
#ifdef ENABLE_PLUGIN
exported_callback(),
diff --git a/src/fileutils.cpp b/src/fileutils.cpp
index 324e26b2d..aa7ff713b 100644
--- a/src/fileutils.cpp
+++ b/src/fileutils.cpp
@@ -1119,5 +1119,20 @@ accessFile(const std::string& file, int mode)
#endif
}
+uint64_t
+lastWriteTime(const std::string& p)
+{
+#if USE_STD_FILESYSTEM
+ return std::chrono::duration_cast(
+ std::filesystem::last_write_time(std::filesystem::path(p)).time_since_epoch())
+ .count();
+#else
+ struct stat result;
+ if (stat(p.c_str(), &result) == 0)
+ return result.st_mtime;
+ return 0;
+#endif
+}
+
} // namespace fileutils
} // namespace jami
diff --git a/src/fileutils.h b/src/fileutils.h
index 3b798758b..baad2dda8 100644
--- a/src/fileutils.h
+++ b/src/fileutils.h
@@ -155,5 +155,7 @@ std::string sha3sum(const std::vector& buffer);
*/
int accessFile(const std::string& file, int mode);
+uint64_t lastWriteTime(const std::string& p);
+
} // namespace fileutils
} // namespace jami
diff --git a/src/jami/conversation_interface.h b/src/jami/conversation_interface.h
index bc146e8ba..bb05c1920 100644
--- a/src/jami/conversation_interface.h
+++ b/src/jami/conversation_interface.h
@@ -49,6 +49,11 @@ DRING_PUBLIC void updateConversationInfos(const std::string& accountId,
const std::map& infos);
DRING_PUBLIC std::map conversationInfos(const std::string& accountId,
const std::string& conversationId);
+DRING_PUBLIC void setConversationPreferences(const std::string& accountId,
+ const std::string& conversationId,
+ const std::map& prefs);
+DRING_PUBLIC std::map getConversationPreferences(
+ const std::string& accountId, const std::string& conversationId);
// Member management
DRING_PUBLIC void addConversationMember(const std::string& accountId,
@@ -176,6 +181,15 @@ struct DRING_PUBLIC ConversationSignal
int code,
const std::string& what);
};
+
+ // Preferences
+ struct DRING_PUBLIC ConversationPreferencesUpdated
+ {
+ constexpr static const char* name = "ConversationPreferencesUpdated";
+ using cb_type = void(const std::string& /*accountId*/,
+ const std::string& /*conversationId*/,
+ std::map /*preferences*/);
+ };
};
} // namespace DRing
diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index 0f6c193ac..bf281b4e2 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -38,6 +38,8 @@
namespace jami {
+static const char* const LAST_MODIFIED = "lastModified";
+
ConvInfo::ConvInfo(const Json::Value& json)
{
id = json[ConversationMapKeys::ID].asString();
@@ -157,6 +159,7 @@ public:
void init()
{
if (auto shared = account_.lock()) {
+ accountId_ = shared->getAccountID();
transferManager_ = std::make_shared(shared->getAccountID(),
repository_->id());
conversationDataPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR
@@ -166,6 +169,8 @@ public:
sendingPath_ = conversationDataPath_ + DIR_SEPARATOR_STR + "sending";
lastDisplayedPath_ = conversationDataPath_ + DIR_SEPARATOR_STR
+ ConversationMapKeys::LAST_DISPLAYED;
+ preferencesPath_ = conversationDataPath_ + DIR_SEPARATOR_STR
+ + ConversationMapKeys::PREFERENCES;
loadFetched();
loadSending();
loadLastDisplayed();
@@ -202,9 +207,8 @@ public:
void announce(const std::vector>& commits) const
{
auto shared = account_.lock();
- if (!shared or !repository_) {
+ if (!shared or !repository_)
return;
- }
auto convId = repository_->id();
auto ok = !commits.empty();
auto lastId = ok ? commits.rbegin()->at(ConversationMapKeys::ID) : "";
@@ -229,8 +233,10 @@ public:
action = 4;
if (action != -1) {
announceMember = true;
- emitSignal(
- shared->getAccountID(), convId, uri, action);
+ emitSignal(accountId_,
+ convId,
+ uri,
+ action);
}
}
}
@@ -238,7 +244,7 @@ public:
auto& pluginChatManager
= Manager::instance().getJamiPluginManager().getChatServicesManager();
if (pluginChatManager.hasHandlers()) {
- auto cm = std::make_shared(shared->getAccountID(),
+ auto cm = std::make_shared(accountId_,
convId,
c.at("author") != shared->getUsername(),
c,
@@ -248,9 +254,7 @@ public:
}
#endif
// announce message
- emitSignal(shared->getAccountID(),
- convId,
- c);
+ emitSignal(accountId_, convId, c);
// check if we should update lastDisplayed
// ignore merge commits as it's not generated by the user
if (c.at("type") == "merge")
@@ -348,9 +352,6 @@ public:
std::string bannedType(const std::string& uri) const
{
- auto shared = account_.lock();
- if (!shared)
- return {};
auto bannedMember = fmt::format("{}/banned/members/{}.crt", repoPath(), uri);
if (fileutils::isFile(bannedMember))
return "members";
@@ -386,7 +387,10 @@ public:
// Manage last message displayed and status
std::string sendingPath_ {};
std::vector sending_ {};
+ // Manage last message displayed
+ std::string accountId_ {};
std::string lastDisplayedPath_ {};
+ std::string preferencesPath_ {};
mutable std::mutex lastDisplayedMtx_ {}; // for lastDisplayed_
mutable std::map lastDisplayed_ {};
std::function lastDisplayedUpdatedCb_ {};
@@ -410,11 +414,8 @@ Conversation::Impl::isAdmin() const
std::string
Conversation::Impl::repoPath() const
{
- auto shared = account_.lock();
- if (!shared)
- return {};
- return fileutils::get_data_dir() + DIR_SEPARATOR_STR + shared->getAccountID()
- + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository_->id();
+ return fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId_ + DIR_SEPARATOR_STR
+ + "conversations" + DIR_SEPARATOR_STR + repository_->id();
}
std::vector>
@@ -655,19 +656,12 @@ Conversation::memberUris(std::string_view filter, const std::set& fi
std::string
Conversation::join()
{
- auto shared = pimpl_->account_.lock();
- if (!shared)
- return {};
return pimpl_->repository_->join();
}
bool
Conversation::isMember(const std::string& uri, bool includeInvited) const
{
- auto shared = pimpl_->account_.lock();
- if (!shared)
- return false;
-
auto invitedPath = pimpl_->repoPath() + DIR_SEPARATOR_STR + "invited";
auto adminsPath = pimpl_->repoPath() + DIR_SEPARATOR_STR + "admins";
auto membersPath = pimpl_->repoPath() + DIR_SEPARATOR_STR + "members";
@@ -729,8 +723,8 @@ Conversation::sendMessage(Json::Value&& value, const std::string& replyTo, OnDon
}
dht::ThreadPool::io().run([w = weak(), value = std::move(value), cb = std::move(cb)] {
if (auto sthis = w.lock()) {
- auto shared = sthis->pimpl_->account_.lock();
- if (!shared)
+ auto acc = sthis->pimpl_->account_.lock();
+ if (!acc)
return;
std::unique_lock lk(sthis->pimpl_->writeMtx_);
Json::StreamWriterBuilder wbuilder;
@@ -744,9 +738,9 @@ Conversation::sendMessage(Json::Value&& value, const std::string& replyTo, OnDon
lk.unlock();
sthis->pimpl_->announce(commit);
emitSignal(
- shared->getAccountID(),
+ acc->getAccountID(),
sthis->id(),
- shared->getUsername(),
+ acc->getUsername(),
commit,
static_cast(DRing::Account::MessageStates::SENDING));
if (cb)
@@ -760,9 +754,6 @@ Conversation::sendMessages(std::vector&& messages, OnMultiDoneCb&&
{
dht::ThreadPool::io().run([w = weak(), messages = std::move(messages), cb = std::move(cb)] {
if (auto sthis = w.lock()) {
- auto shared = sthis->pimpl_->account_.lock();
- if (!shared)
- return;
std::vector commits;
commits.reserve(messages.size());
Json::StreamWriterBuilder wbuilder;
@@ -1095,6 +1086,49 @@ Conversation::infos() const
return pimpl_->repository_->infos();
}
+void
+Conversation::updatePreferences(const std::map& map)
+{
+ auto filePath = fmt::format("{}/preferences", pimpl_->conversationDataPath_);
+ auto prefs = map;
+ auto itLast = prefs.find(LAST_MODIFIED);
+ if (itLast != prefs.end()) {
+ if (fileutils::isFile(filePath)) {
+ auto lastModified = fileutils::lastWriteTime(filePath);
+ try {
+ if (lastModified >= std::stoul(itLast->second))
+ return;
+ } catch (...) {
+ return;
+ }
+ }
+ prefs.erase(itLast);
+ }
+
+ std::ofstream file(filePath, std::ios::trunc | std::ios::binary);
+ msgpack::pack(file, prefs);
+ emitSignal(pimpl_->accountId_,
+ id(),
+ std::move(prefs));
+}
+
+std::map
+Conversation::preferences(bool includeLastModified) const
+{
+ try {
+ std::map preferences;
+ auto filePath = fmt::format("{}/preferences", pimpl_->conversationDataPath_);
+ auto file = fileutils::loadFile(filePath);
+ msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
+ oh.get().convert(preferences);
+ if (includeLastModified)
+ preferences[LAST_MODIFIED] = fmt::format("{}", fileutils::lastWriteTime(filePath));
+ return preferences;
+ } catch (const std::exception& e) {
+ }
+ return {};
+}
+
std::vector
Conversation::vCard() const
{
@@ -1119,10 +1153,6 @@ Conversation::onFileChannelRequest(const std::string& member,
if (!isMember(member))
return false;
- auto account = pimpl_->account_.lock();
- if (!account)
- return false;
-
auto sep = fileId.find('_');
if (sep == std::string::npos)
return false;
@@ -1142,7 +1172,7 @@ Conversation::onFileChannelRequest(const std::string& member,
fileutils::remove(path, true);
}
JAMI_DBG("[Account %s] %s asked for non existing file %s in %s",
- account->getAccountID().c_str(),
+ pimpl_->accountId_.c_str(),
member.c_str(),
fileId.c_str(),
id().c_str());
@@ -1151,7 +1181,7 @@ Conversation::onFileChannelRequest(const std::string& member,
// Check that our file is correct before sending
if (verifyShaSum && commit->at("sha3sum") != fileutils::sha3File(path)) {
JAMI_DBG("[Account %s] %s asked for file %s in %s, but our version is not complete",
- account->getAccountID().c_str(),
+ pimpl_->accountId_.c_str(),
member.c_str(),
fileId.c_str(),
id().c_str());
@@ -1285,7 +1315,7 @@ Conversation::updateLastDisplayed(const std::string& lastDisplayed)
pimpl_->saveLastDisplayed();
lk.unlock();
if (pimpl_->lastDisplayedUpdatedCb_)
- pimpl_->lastDisplayedUpdatedCb_(pimpl_->repository_->id(), lastDisplayed);
+ pimpl_->lastDisplayedUpdatedCb_(id(), lastDisplayed);
};
auto hasCommit = pimpl_->repository_->getCommit(lastDisplayed, false) != std::nullopt;
diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h
index 01d4e1488..0c6003f07 100644
--- a/src/jamidht/conversation.h
+++ b/src/jamidht/conversation.h
@@ -40,6 +40,7 @@ static constexpr const char* REMOVED = "removed";
static constexpr const char* ERASED = "erased";
static constexpr const char* MEMBERS = "members";
static constexpr const char* LAST_DISPLAYED = "lastDisplayed";
+static constexpr const char* PREFERENCES = "preferences";
static constexpr const char* CACHED = "cached";
static constexpr const char* RECEIVED = "received";
static constexpr const char* DECLINED = "declined";
@@ -288,11 +289,23 @@ public:
*/
void updateInfos(const std::map& map, const OnDoneCb& cb = {});
+ /**
+ * Change user's preferences
+ * @param map New preferences
+ */
+ void updatePreferences(const std::map& map);
+
/**
* Retrieve current infos (title, description, avatar, mode)
* @return infos
*/
std::map infos() const;
+ /**
+ * Retrieve current preferences (color, notification, etc)
+ * @param includeLastModified If we want to know when the preferences were modified
+ * @return preferences
+ */
+ std::map preferences(bool includeLastModified) const;
std::vector vCard() const;
/////// File transfer
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
index f06459686..382d5b75a 100644
--- a/src/jamidht/conversation_module.cpp
+++ b/src/jamidht/conversation_module.cpp
@@ -557,7 +557,7 @@ ConversationModule::Impl::handlePendingConversation(const std::string& conversat
sendMessageNotification(conversationId, commitId, false);
// Inform user that the conversation is ready
emitSignal(accountId_, conversationId);
- needsSyncingCb_();
+ needsSyncingCb_({});
std::vector values;
values.reserve(messages.size());
for (const auto& message : messages) {
@@ -696,7 +696,7 @@ ConversationModule::Impl::removeRepository(const std::string& conversationId, bo
auto convIt = convInfos_.find(conversationId);
if (convIt != convInfos_.end()) {
convIt->second.erased = std::time(nullptr);
- needsSyncingCb_();
+ needsSyncingCb_({});
}
saveConvInfos();
}
@@ -722,7 +722,7 @@ ConversationModule::Impl::removeConversation(const std::string& conversationId)
itConv->second.erased = std::time(nullptr);
// Sync now, because it can take some time to really removes the datas
if (hasMembers)
- needsSyncingCb_();
+ needsSyncingCb_({});
saveConvInfos();
lockCi.unlock();
emitSignal(accountId_, conversationId);
@@ -1124,7 +1124,7 @@ ConversationModule::declineConversationRequest(const std::string& conversationId
}
emitSignal(pimpl_->accountId_,
conversationId);
- pimpl_->needsSyncingCb_();
+ pimpl_->needsSyncingCb_({});
}
std::string
@@ -1157,7 +1157,7 @@ ConversationModule::startConversation(ConversationMode mode, const std::string&
info.members.emplace_back(otherMember);
addConvInfo(info);
- pimpl_->needsSyncingCb_();
+ pimpl_->needsSyncingCb_({});
emitSignal(pimpl_->accountId_, convId);
return convId;
@@ -1464,6 +1464,14 @@ ConversationModule::onSyncData(const SyncMsg& msg,
convId,
req.toMap());
}
+
+ // Updates preferences for conversations
+ std::lock_guard lk(pimpl_->conversationsMtx_);
+ for (const auto& [convId, p] : msg.p) {
+ auto itConv = pimpl_->conversations_.find(convId);
+ if (itConv != pimpl_->conversations_.end() && itConv->second)
+ itConv->second->updatePreferences(p);
+ }
}
bool
@@ -1648,7 +1656,6 @@ ConversationModule::updateConversationInfos(const std::string& conversationId,
bool sync)
{
std::lock_guard lk(pimpl_->conversationsMtx_);
- // Add a new member in the conversation
auto it = pimpl_->conversations_.find(conversationId);
if (it == pimpl_->conversations_.end()) {
JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
@@ -1674,7 +1681,6 @@ ConversationModule::conversationInfos(const std::string& conversationId) const
return itReq->second.metadatas;
}
std::lock_guard lk(pimpl_->conversationsMtx_);
- // Add a new member in the conversation
auto it = pimpl_->conversations_.find(conversationId);
if (it == pimpl_->conversations_.end() or not it->second) {
std::lock_guard lkCi(pimpl_->convInfosMtx_);
@@ -1689,6 +1695,52 @@ ConversationModule::conversationInfos(const std::string& conversationId) const
return it->second->infos();
}
+void
+ConversationModule::setConversationPreferences(const std::string& conversationId,
+ const std::map& prefs)
+{
+ std::unique_lock lk(pimpl_->conversationsMtx_);
+ auto it = pimpl_->conversations_.find(conversationId);
+ if (it == pimpl_->conversations_.end()) {
+ JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
+ return;
+ }
+
+ it->second->updatePreferences(prefs);
+ auto msg = std::make_shared();
+ std::map> p;
+ p[conversationId] = it->second->preferences(true);
+ msg->p = std::move(p);
+ lk.unlock();
+ pimpl_->needsSyncingCb_(std::move(msg));
+}
+
+std::map
+ConversationModule::getConversationPreferences(const std::string& conversationId) const
+{
+ std::lock_guard lk(pimpl_->conversationsMtx_);
+ auto it = pimpl_->conversations_.find(conversationId);
+ if (it == pimpl_->conversations_.end() or not it->second)
+ return {};
+
+ return it->second->preferences(false);
+}
+
+std::map>
+ConversationModule::getAllConversationsPreferences() const
+{
+ std::map> p;
+ std::lock_guard lk(pimpl_->conversationsMtx_);
+ for (const auto& [id, conv] : pimpl_->conversations_) {
+ if (conv) {
+ auto prefs = conv->preferences(true);
+ if (!prefs.empty())
+ p[id] = std::move(prefs);
+ }
+ }
+ return p;
+}
+
std::vector
ConversationModule::conversationVCard(const std::string& conversationId) const
{
@@ -1702,7 +1754,6 @@ ConversationModule::conversationVCard(const std::string& conversationId) const
return it->second->vCard();
}
-
bool
ConversationModule::isBannedDevice(const std::string& convId, const std::string& deviceId) const
{
diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h
index 0875367b2..20c4c803e 100644
--- a/src/jamidht/conversation_module.h
+++ b/src/jamidht/conversation_module.h
@@ -35,14 +35,17 @@ struct SyncMsg
jami::DeviceSync ds;
std::map c;
std::map cr;
- MSGPACK_DEFINE(ds, c, cr)
+ // p is conversation's preferences. It's not stored in c, as
+ // we can update the preferences without touching any confInfo.
+ std::map> p;
+ MSGPACK_DEFINE(ds, c, cr, p)
};
using ChannelCb = std::function&)>;
using NeedSocketCb = std::function;
using SengMsgCb
= std::function&&, uint64_t)>;
-using NeedsSyncingCb = std::function;
+using NeedsSyncingCb = std::function&&)>;
using UpdateConvReq = std::function;
class ConversationModule
@@ -309,7 +312,7 @@ public:
// Conversation's infos management
/**
- * Update metadata from conversations (like title, avatar, etc)
+ * Update metadatas from conversations (like title, avatar, etc)
* @param conversationId
* @param infos
* @param sync If we need to sync with others (used for tests)
@@ -318,6 +321,19 @@ public:
const std::map& infos,
bool sync = true);
std::map conversationInfos(const std::string& conversationId) const;
+ /**
+ * Update user's preferences (like color, notifications, etc) to be synced across devices
+ * @param conversationId
+ * @param preferences
+ */
+ void setConversationPreferences(const std::string& conversationId,
+ const std::map& prefs);
+ std::map getConversationPreferences(
+ const std::string& conversationId) const;
+ /**
+ * Retrieve all conversation preferences to sync with other devices
+ */
+ std::map> getAllConversationsPreferences() const;
// Get the map into a VCard format for storing
std::vector conversationVCard(const std::string& conversationId) const;
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 24061b8e4..66b3f37e8 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -2337,10 +2337,10 @@ JamiAccount::convModule()
if (!convModule_) {
convModule_ = std::make_unique(
weak(),
- [this] {
- runOnMainThread([w = weak()] {
+ [this](auto&& syncMsg) {
+ runOnMainThread([w = weak(), syncMsg] {
if (auto shared = w.lock())
- shared->syncModule()->syncWithConnected();
+ shared->syncModule()->syncWithConnected(syncMsg);
});
},
[this](auto&& uri, auto&& msg, auto token = 0) {
diff --git a/src/jamidht/sync_module.cpp b/src/jamidht/sync_module.cpp
index 61dbce5af..348a1d629 100644
--- a/src/jamidht/sync_module.cpp
+++ b/src/jamidht/sync_module.cpp
@@ -43,7 +43,8 @@ public:
* Build SyncMsg and send it on socket
* @param socket
*/
- void syncInfos(const std::shared_ptr& socket);
+ void syncInfos(const std::shared_ptr& socket,
+ const std::shared_ptr& syncMsg);
};
SyncModule::Impl::Impl(std::weak_ptr&& account)
@@ -51,7 +52,8 @@ SyncModule::Impl::Impl(std::weak_ptr&& account)
{}
void
-SyncModule::Impl::syncInfos(const std::shared_ptr& socket)
+SyncModule::Impl::syncInfos(const std::shared_ptr& socket,
+ const std::shared_ptr& syncMsg)
{
auto acc = account_.lock();
if (!acc)
@@ -59,13 +61,30 @@ SyncModule::Impl::syncInfos(const std::shared_ptr& socket)
Json::Value syncValue;
msgpack::sbuffer buffer(UINT16_MAX); // Use max pkt size
std::error_code ec;
- // Send contacts infos
- // This message can be big. TODO rewrite to only take UINT16_MAX bytes max or split it multiple
- // messages. For now, write 3 messages (UINT16_MAX*3 should be enough for all informations).
- if (auto info = acc->accountManager()->getInfo()) {
- if (info->contacts) {
+ if (!syncMsg) {
+ // Send contacts infos
+ // This message can be big. TODO rewrite to only take UINT16_MAX bytes max or split it multiple
+ // messages. For now, write 3 messages (UINT16_MAX*3 should be enough for all informations).
+ if (auto info = acc->accountManager()->getInfo()) {
+ if (info->contacts) {
+ SyncMsg msg;
+ msg.ds = info->contacts->getSyncData();
+ msgpack::pack(buffer, msg);
+ socket->write(reinterpret_cast(buffer.data()),
+ buffer.size(),
+ ec);
+ if (ec) {
+ JAMI_ERR("%s", ec.message().c_str());
+ return;
+ }
+ }
+ }
+ buffer.clear();
+ // Sync conversations
+ auto c = ConversationModule::convInfos(acc->getAccountID());
+ if (!c.empty()) {
SyncMsg msg;
- msg.ds = info->contacts->getSyncData();
+ msg.c = std::move(c);
msgpack::pack(buffer, msg);
socket->write(reinterpret_cast(buffer.data()), buffer.size(), ec);
if (ec) {
@@ -73,32 +92,24 @@ SyncModule::Impl::syncInfos(const std::shared_ptr& socket)
return;
}
}
- }
- buffer.clear();
- // Sync conversations
- auto c = ConversationModule::convInfos(acc->getAccountID());
- if (!c.empty()) {
- SyncMsg msg;
- msg.c = std::move(c);
- msgpack::pack(buffer, msg);
- socket->write(reinterpret_cast(buffer.data()), buffer.size(), ec);
- if (ec) {
- JAMI_ERR("%s", ec.message().c_str());
- return;
+ buffer.clear();
+ // Sync requests
+ auto cr = ConversationModule::convRequests(acc->getAccountID());
+ if (!cr.empty()) {
+ SyncMsg msg;
+ msg.cr = std::move(cr);
+ msgpack::pack(buffer, msg);
+ socket->write(reinterpret_cast(buffer.data()), buffer.size(), ec);
+ if (ec) {
+ JAMI_ERR("%s", ec.message().c_str());
+ return;
+ }
}
- }
- buffer.clear();
- // Sync requests
- auto cr = ConversationModule::convRequests(acc->getAccountID());
- if (!cr.empty()) {
- SyncMsg msg;
- msg.cr = std::move(cr);
- msgpack::pack(buffer, msg);
+ } else {
+ msgpack::pack(buffer, *syncMsg);
socket->write(reinterpret_cast(buffer.data()), buffer.size(), ec);
- if (ec) {
+ if (ec)
JAMI_ERR("%s", ec.message().c_str());
- return;
- }
}
}
@@ -149,14 +160,16 @@ SyncModule::cacheSyncConnection(std::shared_ptr&& socket,
if (auto manager = dynamic_cast(acc->accountManager()))
manager->onSyncData(std::move(msg.ds), false);
- if (!msg.c.empty() || !msg.cr.empty())
+ if (!msg.c.empty() || !msg.cr.empty() || !msg.p.empty())
acc->convModule()->onSyncData(msg, peerId, device.toString());
return len;
});
}
void
-SyncModule::syncWith(const DeviceId& deviceId, const std::shared_ptr& socket)
+SyncModule::syncWith(const DeviceId& deviceId,
+ const std::shared_ptr& socket,
+ const std::shared_ptr& syncMsg)
{
if (!socket)
return;
@@ -182,16 +195,16 @@ SyncModule::syncWith(const DeviceId& deviceId, const std::shared_ptrsyncConnections_[deviceId].emplace_back(socket);
}
- pimpl_->syncInfos(socket);
+ pimpl_->syncInfos(socket, syncMsg);
}
void
-SyncModule::syncWithConnected()
+SyncModule::syncWithConnected(const std::shared_ptr& syncMsg)
{
std::lock_guard lk(pimpl_->syncConnectionsMtx_);
for (auto& [_deviceId, sockets] : pimpl_->syncConnections_) {
if (not sockets.empty())
- pimpl_->syncInfos(sockets[0]);
+ pimpl_->syncInfos(sockets[0], syncMsg);
}
}
} // namespace jami
\ No newline at end of file
diff --git a/src/jamidht/sync_module.h b/src/jamidht/sync_module.h
index 21d774fb3..bde491537 100644
--- a/src/jamidht/sync_module.h
+++ b/src/jamidht/sync_module.h
@@ -44,13 +44,17 @@ public:
* Send sync informations to connected device
* @param deviceId Connected device
* @param socket Related socket
+ * @param syncMsg Default message
*/
- void syncWith(const DeviceId& deviceId, const std::shared_ptr& socket);
+ void syncWith(const DeviceId& deviceId,
+ const std::shared_ptr& socket,
+ const std::shared_ptr& syncMsg = nullptr);
/**
* Send sync to all connected devices
+ * @param syncMsg Default message
*/
- void syncWithConnected();
+ void syncWithConnected(const std::shared_ptr& syncMsg = nullptr);
private:
class Impl;
diff --git a/test/agent/src/bindings/signal.cpp b/test/agent/src/bindings/signal.cpp
index d71bb0efc..8b509ec46 100644
--- a/test/agent/src/bindings/signal.cpp
+++ b/test/agent/src/bindings/signal.cpp
@@ -540,5 +540,10 @@ install_signal_primitives(void*)
int,
const std::string&>(handlers, "conversation-error");
+ add_handler>(handlers, "conversation-preferences-updated");
+
DRing::registerSignalHandlers(handlers);
}
diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp
index 9ced2bdb6..4be659480 100644
--- a/test/unitTest/conversation/conversation.cpp
+++ b/test/unitTest/conversation/conversation.cpp
@@ -112,6 +112,8 @@ private:
void testRemoveReaddMultipleDevice();
void testSendReply();
void testSearchInConv();
+ void testConversationPreferences();
+ void testConversationPreferencesMultiDevices();
CPPUNIT_TEST_SUITE(ConversationTest);
CPPUNIT_TEST(testCreateConversation);
@@ -154,6 +156,8 @@ private:
CPPUNIT_TEST(testRemoveReaddMultipleDevice);
CPPUNIT_TEST(testSendReply);
CPPUNIT_TEST(testSearchInConv);
+ CPPUNIT_TEST(testConversationPreferences);
+ CPPUNIT_TEST(testConversationPreferencesMultiDevices);
CPPUNIT_TEST_SUITE_END();
};
@@ -3175,6 +3179,140 @@ ConversationTest::testSearchInConv()
CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messages.size() == 0 && finished; }));
}
+void
+ConversationTest::testConversationPreferences()
+{
+ auto aliceAccount = Manager::instance().getAccount(aliceId);
+ auto uri = aliceAccount->getUsername();
+ std::mutex mtx;
+ std::unique_lock lk {mtx};
+ std::condition_variable cv;
+ std::map> confHandlers;
+ bool conversationReady = false, conversationRemoved = false;
+ confHandlers.insert(DRing::exportable_callback(
+ [&](const std::string& accountId, const std::string& /* conversationId */) {
+ if (accountId == aliceId) {
+ conversationReady = true;
+ cv.notify_one();
+ }
+ }));
+ confHandlers.insert(DRing::exportable_callback(
+ [&](const std::string& accountId, const std::string&) {
+ if (accountId == aliceId)
+ conversationRemoved = true;
+ cv.notify_one();
+ }));
+ DRing::registerSignalHandlers(confHandlers);
+ // Start conversation and set preferences
+ auto convId = DRing::startConversation(aliceId);
+ cv.wait_for(lk, 30s, [&]() { return conversationReady; });
+ CPPUNIT_ASSERT(DRing::getConversationPreferences(aliceId, convId).size() == 0);
+ DRing::setConversationPreferences(aliceId, convId, {{"foo", "bar"}});
+ auto preferences = DRing::getConversationPreferences(aliceId, convId);
+ CPPUNIT_ASSERT(preferences.size() == 1);
+ CPPUNIT_ASSERT(preferences["foo"] == "bar");
+ // Update
+ DRing::setConversationPreferences(aliceId, convId, {{"foo", "bar2"}, {"bar", "foo"}});
+ preferences = DRing::getConversationPreferences(aliceId, convId);
+ CPPUNIT_ASSERT(preferences.size() == 2);
+ CPPUNIT_ASSERT(preferences["foo"] == "bar2");
+ CPPUNIT_ASSERT(preferences["bar"] == "foo");
+ // Remove conversations removes its preferences.
+ CPPUNIT_ASSERT(DRing::removeConversation(aliceId, convId));
+ CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRemoved; }));
+ CPPUNIT_ASSERT(DRing::getConversationPreferences(aliceId, convId).size() == 0);
+}
+void
+ConversationTest::testConversationPreferencesMultiDevices()
+{
+ auto aliceAccount = Manager::instance().getAccount(aliceId);
+ auto bobAccount = Manager::instance().getAccount(bobId);
+ auto bobUri = bobAccount->getUsername();
+ auto aliceUri = aliceAccount->getUsername();
+ std::mutex mtx;
+ std::unique_lock lk {mtx};
+ std::condition_variable cv;
+ std::map> confHandlers;
+ auto requestReceived = false, requestReceivedBob2 = false;
+ confHandlers.insert(
+ DRing::exportable_callback(
+ [&](const std::string& accountId,
+ const std::string& conversationId,
+ std::map /*metadatas*/) {
+ if (accountId == bobId)
+ requestReceived = true;
+ else if (accountId == bob2Id)
+ requestReceivedBob2 = true;
+ cv.notify_one();
+ }));
+ std::string convId = "";
+ auto conversationReadyBob = false, conversationReadyBob2 = false;
+ confHandlers.insert(DRing::exportable_callback(
+ [&](const std::string& accountId, const std::string& conversationId) {
+ if (accountId == aliceId) {
+ convId = conversationId;
+ } else if (accountId == bobId) {
+ conversationReadyBob = true;
+ } else if (accountId == bob2Id) {
+ conversationReadyBob2 = true;
+ }
+ cv.notify_one();
+ }));
+ auto bob2Started = false;
+ confHandlers.insert(
+ DRing::exportable_callback(
+ [&](const std::string& accountId, const std::map& details) {
+ if (accountId == bob2Id) {
+ auto daemonStatus = details.at(
+ DRing::Account::VolatileProperties::DEVICE_ANNOUNCED);
+ if (daemonStatus == "true")
+ bob2Started = true;
+ }
+ cv.notify_one();
+ }));
+ std::map preferencesBob, preferencesBob2;
+ confHandlers.insert(
+ DRing::exportable_callback(
+ [&](const std::string& accountId,
+ const std::string& conversationId,
+ std::map preferences) {
+ if (accountId == bobId)
+ preferencesBob = preferences;
+ else if (accountId == bob2Id)
+ preferencesBob2 = preferences;
+ cv.notify_one();
+ }));
+ DRing::registerSignalHandlers(confHandlers);
+ // Bob creates a second device
+ auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
+ std::remove(bobArchive.c_str());
+ bobAccount->exportArchive(bobArchive);
+ std::map details = DRing::getAccountTemplate("RING");
+ details[ConfProperties::TYPE] = "RING";
+ details[ConfProperties::DISPLAYNAME] = "BOB2";
+ details[ConfProperties::ALIAS] = "BOB2";
+ details[ConfProperties::UPNP_ENABLED] = "true";
+ details[ConfProperties::ARCHIVE_PASSWORD] = "";
+ details[ConfProperties::ARCHIVE_PIN] = "";
+ details[ConfProperties::ARCHIVE_PATH] = bobArchive;
+ bob2Id = Manager::instance().addAccount(details);
+ CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Started; }));
+ // Alice adds bob
+ requestReceived = false;
+ aliceAccount->addContact(bobUri);
+ aliceAccount->sendTrustRequest(bobUri, {});
+ CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived && requestReceivedBob2; }));
+ DRing::acceptConversationRequest(bobId, convId);
+ CPPUNIT_ASSERT(
+ cv.wait_for(lk, 30s, [&]() { return conversationReadyBob && conversationReadyBob2; }));
+ DRing::setConversationPreferences(bobId, convId, {{"foo", "bar"}, {"bar", "foo"}});
+ CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+ return preferencesBob.size() == 2 && preferencesBob2.size() == 2;
+ }));
+ CPPUNIT_ASSERT(preferencesBob["foo"] == "bar" && preferencesBob["bar"] == "foo");
+ CPPUNIT_ASSERT(preferencesBob2["foo"] == "bar" && preferencesBob2["bar"] == "foo");
+}
+
} // namespace test
} // namespace jami
diff --git a/tools/jamictrl/controller.py b/tools/jamictrl/controller.py
index 0f778f819..beb61bba7 100644
--- a/tools/jamictrl/controller.py
+++ b/tools/jamictrl/controller.py
@@ -129,6 +129,7 @@ class DRingCtrl(Thread):
proxy_confmgr.connect_to_signal('dataTransferEvent', self.onDataTransferEvent)
proxy_confmgr.connect_to_signal('conversationReady', self.onConversationReady)
proxy_confmgr.connect_to_signal('conversationRequestReceived', self.onConversationRequestReceived)
+ proxy_confmgr.connect_to_signal('conversationPreferencesUpdated', self.onConversationPreferencesUpdated)
proxy_confmgr.connect_to_signal('messageReceived', self.onMessageReceived)
except dbus.DBusException as e:
@@ -314,6 +315,9 @@ class DRingCtrl(Thread):
def onConversationRequestReceived(self, account, conversationId, metadatas):
print(f'New conversation request for {account} with id {conversationId}')
+ def onConversationPreferencesUpdated(self, account, conversationId, metadatas):
+ print(f'New conversation preferences for {account} with id {conversationId}')
+
def onMessageReceived(self, account, conversationId, message):
print(f'New message for {account} in conversation {conversationId} with id {message["id"]}')
for key in message: