Ring Daemon
Loading...
Searching...
No Matches
chatservicesmanager.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include "chatservicesmanager.h"
19#include "pluginmanager.h"
20#include "logger.h"
21#include "manager.h"
22#include "jamidht/jamiaccount.h"
23#include "fileutils.h"
24
25namespace jami {
26
28{
29 registerComponentsLifeCycleManagers(pluginManager);
30 registerChatService(pluginManager);
32}
33
34void
35ChatServicesManager::registerComponentsLifeCycleManagers(PluginManager& pluginManager)
36{
37 // registerChatHandler may be called by the PluginManager upon loading a plugin.
38 auto registerChatHandler = [this](void* data, std::mutex& pmMtx_) {
39 std::lock_guard lk(pmMtx_);
40 ChatHandlerPtr ptr {(static_cast<ChatHandler*>(data))};
41
42 if (!ptr)
43 return -1;
44 handlersNameMap_[ptr->getChatHandlerDetails().at("name")] = (uintptr_t) ptr.get();
45 std::size_t found = ptr->id().find_last_of(DIR_SEPARATOR_CH);
46 // Adding preference that tells us to automatically activate a ChatHandler.
47 PluginPreferencesUtils::addAlwaysHandlerPreference(ptr->getChatHandlerDetails().at("name"),
48 ptr->id().substr(0, found));
49 chatHandlers_.emplace_back(std::move(ptr));
50 return 0;
51 };
52
53 // unregisterChatHandler may be called by the PluginManager while unloading.
54 auto unregisterChatHandler = [this](void* data, std::mutex& pmMtx_) {
55 std::lock_guard lk(pmMtx_);
56 auto handlerIt = std::find_if(chatHandlers_.begin(), chatHandlers_.end(), [data](ChatHandlerPtr& handler) {
57 return (handler.get() == data);
58 });
59
60 if (handlerIt != chatHandlers_.end()) {
61 for (auto& toggledList : chatHandlerToggled_) {
62 auto handlerId = std::find_if(toggledList.second.begin(),
63 toggledList.second.end(),
64 [id = (uintptr_t) handlerIt->get()](uintptr_t handlerId) {
65 return (handlerId == id);
66 });
67 // If ChatHandler is attempting to destroy one which is currently in use, we deactivate it.
68 if (handlerId != toggledList.second.end()) {
69 (*handlerIt)->detach(chatSubjects_[toggledList.first]);
70 toggledList.second.erase(handlerId);
71 }
72 }
73 handlersNameMap_.erase((*handlerIt)->getChatHandlerDetails().at("name"));
74 chatHandlers_.erase(handlerIt);
75 }
76 return true;
77 };
78
79 // Services are registered to the PluginManager.
80 pluginManager.registerComponentManager("ChatHandlerManager", registerChatHandler, unregisterChatHandler);
81}
82
83void
84ChatServicesManager::registerChatService(PluginManager& pluginManager)
85{
86 // sendTextMessage is a service that allows plugins to send a message in a conversation.
87 auto sendTextMessage = [](const DLPlugin*, void* data) {
88 auto* cm = static_cast<JamiMessage*>(data);
89 if (const auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(cm->accountId)) {
90 try {
91 if (cm->isSwarm)
92 acc->convModule()->sendMessage(cm->peerId, cm->data.at("body"));
93 else
94 jami::Manager::instance().sendTextMessage(cm->accountId, cm->peerId, cm->data, true);
95 } catch (const std::exception& e) {
96 JAMI_ERR("Exception during text message sending: %s", e.what());
97 }
98 }
99 return 0;
100 };
101
102 // Services are registered to the PluginManager.
103 pluginManager.registerService("sendTextMessage", sendTextMessage);
104}
105
106bool
108{
109 return not chatHandlers_.empty();
110}
111
112std::vector<std::string>
114{
115 std::vector<std::string> res;
116 res.reserve(chatHandlers_.size());
117 for (const auto& chatHandler : chatHandlers_) {
118 res.emplace_back(std::to_string((uintptr_t) chatHandler.get()));
119 }
120 return res;
121}
122
123void
125{
126 if (message->fromPlugin or chatHandlers_.empty())
127 return;
128
129 std::pair<std::string, std::string> mPair(message->accountId, message->peerId);
130 auto& handlers = chatHandlerToggled_[mPair];
131 auto& chatAllowDenySet = allowDenyList_[mPair];
132
133 // Search for activation flag.
134 for (auto& chatHandler : chatHandlers_) {
135 std::string chatHandlerName = chatHandler->getChatHandlerDetails().at("name");
136 std::size_t found = chatHandler->id().find_last_of(DIR_SEPARATOR_CH);
137 // toggle is true if we should automatically activate the ChatHandler.
140 message->accountId);
141 // toggle is overwritten if we have previously activated/deactivated the ChatHandler
142 // for the given conversation.
144 if (allowedIt != chatAllowDenySet.end())
145 toggle = (*allowedIt).second;
146 bool toggled = handlers.find((uintptr_t) chatHandler.get()) != handlers.end();
147 if (toggle || toggled) {
148 // Creates chat subjects if it doesn't exist yet.
149 auto& subject = chatSubjects_.emplace(mPair, std::make_shared<PublishObservable<pluginMessagePtr>>())
150 .first->second;
151 if (!toggled) {
152 // If activation is expected, and not yet performed, we perform activation
153 handlers.insert((uintptr_t) chatHandler.get());
154 chatHandler->notifyChatSubject(mPair, subject);
157 }
158 // Finally we feed Chat subject with the message.
159 subject->publish(message);
160 }
161 }
162}
163
164void
165ChatServicesManager::cleanChatSubjects(const std::string& accountId, const std::string& peerId)
166{
167 std::pair<std::string, std::string> mPair(accountId, peerId);
168 for (auto it = chatSubjects_.begin(); it != chatSubjects_.end();) {
169 if (peerId.empty() && it->first.first == accountId)
170 it = chatSubjects_.erase(it);
171 else if (!peerId.empty() && it->first == mPair)
172 it = chatSubjects_.erase(it);
173 else
174 ++it;
175 }
176}
177
178void
180 const std::string& accountId,
181 const std::string& peerId,
182 const bool toggle)
183{
184 toggleChatHandler(std::stoull(chatHandlerId), accountId, peerId, toggle);
185}
186
187std::vector<std::string>
188ChatServicesManager::getChatHandlerStatus(const std::string& accountId, const std::string& peerId)
189{
190 std::pair<std::string, std::string> mPair(accountId, peerId);
191 const auto& it = allowDenyList_.find(mPair);
192 std::vector<std::string> ret;
193 if (it != allowDenyList_.end()) {
194 for (const auto& chatHandlerName : it->second)
195 if (chatHandlerName.second
196 && handlersNameMap_.find(chatHandlerName.first)
197 != handlersNameMap_.end()) { // We only return active ChatHandler ids
198 ret.emplace_back(std::to_string(handlersNameMap_.at(chatHandlerName.first)));
199 }
200 }
201
202 return ret;
203}
204
205std::map<std::string, std::string>
207{
208 auto chatHandlerId = std::stoull(chatHandlerIdStr);
209 for (auto& chatHandler : chatHandlers_) {
210 if ((uintptr_t) chatHandler.get() == chatHandlerId) {
211 return chatHandler->getChatHandlerDetails();
212 }
213 }
214 return {};
215}
216
217bool
218ChatServicesManager::setPreference(const std::string& key, const std::string& value, const std::string& rootPath)
219{
220 bool status {true};
221 for (auto& chatHandler : chatHandlers_) {
222 if (chatHandler->id().find(rootPath) != std::string::npos) {
223 if (chatHandler->preferenceMapHasKey(key)) {
224 chatHandler->setPreferenceAttribute(key, value);
225 status &= false;
226 }
227 }
228 }
229 return status;
230}
231
232void
234 const std::string& accountId,
235 const std::string& peerId,
236 const bool toggle)
237{
238 std::pair<std::string, std::string> mPair(accountId, peerId);
239 auto& handlers = chatHandlerToggled_[mPair];
240 auto& chatAllowDenySet = allowDenyList_[mPair];
241 chatSubjects_.emplace(mPair, std::make_shared<PublishObservable<pluginMessagePtr>>());
242
243 auto chatHandlerIt = std::find_if(chatHandlers_.begin(),
244 chatHandlers_.end(),
246 return ((uintptr_t) handler.get() == chatHandlerId);
247 });
248
249 if (chatHandlerIt != chatHandlers_.end()) {
250 if (toggle) {
251 (*chatHandlerIt)->notifyChatSubject(mPair, chatSubjects_[mPair]);
252 if (handlers.find(chatHandlerId) == handlers.end())
253 handlers.insert(chatHandlerId);
254 chatAllowDenySet[(*chatHandlerIt)->getChatHandlerDetails().at("name")] = true;
255 } else {
256 (*chatHandlerIt)->detach(chatSubjects_[mPair]);
257 handlers.erase(chatHandlerId);
258 chatAllowDenySet[(*chatHandlerIt)->getChatHandlerDetails().at("name")] = false;
259 }
261 }
262}
263} // namespace jami
This abstract class is an API we need to implement from plugin side.
Definition chathandler.h:35
ChatServicesManager(PluginManager &pluginManager)
Constructor registers ChatHandler API services to the PluginManager instance.
void publishMessage(const pluginMessagePtr &message)
Publishes every message sent or received in a conversation that has (or should have) an active ChatHa...
std::vector< std::string > getChatHandlerStatus(const std::string &accountId, const std::string &peerId)
Returns a list of active ChatHandlers for a given accountId, peerId pair.
bool setPreference(const std::string &key, const std::string &value, const std::string &rootPath)
Sets a preference that may be changed while ChatHandler is active.
std::vector< std::string > getChatHandlers() const
List all ChatHandlers available.
void cleanChatSubjects(const std::string &accountId, const std::string &peerId="")
If an account is unregistered or a contact is erased, we clear all chat subjects related to that acco...
std::map< std::string, std::string > getChatHandlerDetails(const std::string &chatHandlerIdStr)
Gets details from ChatHandler implementation.
void toggleChatHandler(const std::string &chatHandlerId, const std::string &accountId, const std::string &peerId, const bool toggle)
Activates or deactivate a given ChatHandler to a given accountId, peerId pair.
Ring Account is build on top of SIPAccountBase and uses DHT to handle call connectivity.
Definition jamiaccount.h:93
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:694
std::shared_ptr< T > getAccount(std::string_view accountId) const
Get an account pointer, looks for account of type T.
Definition manager.h:753
uint64_t sendTextMessage(const std::string &accountID, const std::string &to, const std::map< std::string, std::string > &payloads, bool fromPlugin=false, bool onlyConnected=false)
Definition manager.cpp:2976
This class manages plugin (un)loading.
static void getAllowDenyListPreferences(ChatHandlerList &list)
Reads ChantHandlers status from allowdeny.msgpack file.
static bool getAlwaysPreference(const std::string &rootPath, const std::string &handlerName, const std::string &accountId)
Read plugin's preferences and returns wheter a specific handler "always" preference is True or False.
static void setAllowDenyListPreferences(const ChatHandlerList &list)
Saves ChantHandlers status provided by list.
static void addAlwaysHandlerPreference(const std::string &handlerName, const std::string &rootPath)
Creates a "always" preference for a handler if this preference doesn't exist yet.
#define DIR_SEPARATOR_CH
Definition fileutils.h:34
#define JAMI_ERR(...)
Definition logger.h:230
std::unique_ptr< ChatHandler > ChatHandlerPtr
void emitSignal(Args... args)
Definition jami_signal.h:64
std::shared_ptr< JamiMessage > pluginMessagePtr
Definition chathandler.h:26
void sendTextMessage(const std::string &accountId, const std::string &callId, const std::map< std::string, std::string > &messages, const std::string &from, bool isMixed)
Contains information about an exchanged message.
Definition streamdata.h:67