Ring Daemon 16.0.0
Loading...
Searching...
No Matches
chatservicesmanager.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 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(),
57 chatHandlers_.end(),
58 [data](ChatHandlerPtr& handler) {
59 return (handler.get() == data);
60 });
61
62 if (handlerIt != chatHandlers_.end()) {
63 for (auto& toggledList : chatHandlerToggled_) {
64 auto handlerId = std::find_if(toggledList.second.begin(),
65 toggledList.second.end(),
66 [id = (uintptr_t) handlerIt->get()](
68 return (handlerId == id);
69 });
70 // If ChatHandler is attempting to destroy one which is currently in use, we deactivate it.
71 if (handlerId != toggledList.second.end()) {
72 (*handlerIt)->detach(chatSubjects_[toggledList.first]);
73 toggledList.second.erase(handlerId);
74 }
75 }
76 handlersNameMap_.erase((*handlerIt)->getChatHandlerDetails().at("name"));
77 chatHandlers_.erase(handlerIt);
78 }
79 return true;
80 };
81
82 // Services are registered to the PluginManager.
83 pluginManager.registerComponentManager("ChatHandlerManager",
86}
87
88void
89ChatServicesManager::registerChatService(PluginManager& pluginManager)
90{
91 // sendTextMessage is a service that allows plugins to send a message in a conversation.
92 auto sendTextMessage = [](const DLPlugin*, void* data) {
93 auto cm = static_cast<JamiMessage*>(data);
95 cm->accountId)) {
96 try {
97 if (cm->isSwarm)
98 acc->convModule()->sendMessage(cm->peerId, cm->data.at("body"));
99 else
101 cm->peerId,
102 cm->data,
103 true);
104 } catch (const std::exception& e) {
105 JAMI_ERR("Exception during text message sending: %s", e.what());
106 }
107 }
108 return 0;
109 };
110
111 // Services are registered to the PluginManager.
112 pluginManager.registerService("sendTextMessage", sendTextMessage);
113}
114
115bool
117{
118 return not chatHandlers_.empty();
119}
120
121
122std::vector<std::string>
124{
125 std::vector<std::string> res;
126 res.reserve(chatHandlers_.size());
127 for (const auto& chatHandler : chatHandlers_) {
128 res.emplace_back(std::to_string((uintptr_t) chatHandler.get()));
129 }
130 return res;
131}
132
133void
135{
136 if (message->fromPlugin or chatHandlers_.empty())
137 return;
138
139 std::pair<std::string, std::string> mPair(message->accountId, message->peerId);
140 auto& handlers = chatHandlerToggled_[mPair];
141 auto& chatAllowDenySet = allowDenyList_[mPair];
142
143 // Search for activation flag.
144 for (auto& chatHandler : chatHandlers_) {
145 std::string chatHandlerName = chatHandler->getChatHandlerDetails().at("name");
146 std::size_t found = chatHandler->id().find_last_of(DIR_SEPARATOR_CH);
147 // toggle is true if we should automatically activate the ChatHandler.
150 message->accountId);
151 // toggle is overwritten if we have previously activated/deactivated the ChatHandler
152 // for the given conversation.
154 if (allowedIt != chatAllowDenySet.end())
155 toggle = (*allowedIt).second;
156 bool toggled = handlers.find((uintptr_t) chatHandler.get()) != handlers.end();
157 if (toggle || toggled) {
158 // Creates chat subjects if it doesn't exist yet.
159 auto& subject = chatSubjects_.emplace(mPair, std::make_shared<PublishObservable<pluginMessagePtr>>()).first->second;
160 if (!toggled) {
161 // If activation is expected, and not yet performed, we perform activation
162 handlers.insert((uintptr_t) chatHandler.get());
163 chatHandler->notifyChatSubject(mPair, subject);
166 }
167 // Finally we feed Chat subject with the message.
168 subject->publish(message);
169 }
170 }
171}
172
173void
174ChatServicesManager::cleanChatSubjects(const std::string& accountId, const std::string& peerId)
175{
176 std::pair<std::string, std::string> mPair(accountId, peerId);
177 for (auto it = chatSubjects_.begin(); it != chatSubjects_.end();) {
178 if (peerId.empty() && it->first.first == accountId)
179 it = chatSubjects_.erase(it);
180 else if (!peerId.empty() && it->first == mPair)
181 it = chatSubjects_.erase(it);
182 else
183 ++it;
184 }
185}
186
187void
189 const std::string& accountId,
190 const std::string& peerId,
191 const bool toggle)
192{
193 toggleChatHandler(std::stoull(chatHandlerId), accountId, peerId, toggle);
194}
195
196std::vector<std::string>
197ChatServicesManager::getChatHandlerStatus(const std::string& accountId, const std::string& peerId)
198{
199 std::pair<std::string, std::string> mPair(accountId, peerId);
200 const auto& it = allowDenyList_.find(mPair);
201 std::vector<std::string> ret;
202 if (it != allowDenyList_.end()) {
203 for (const auto& chatHandlerName : it->second)
204 if (chatHandlerName.second && handlersNameMap_.find(chatHandlerName.first) != handlersNameMap_.end()) { // We only return active ChatHandler ids
205 ret.emplace_back(std::to_string(handlersNameMap_.at(chatHandlerName.first)));
206 }
207 }
208
209 return ret;
210}
211
212std::map<std::string, std::string>
214{
215 auto chatHandlerId = std::stoull(chatHandlerIdStr);
216 for (auto& chatHandler : chatHandlers_) {
217 if ((uintptr_t) chatHandler.get() == chatHandlerId) {
218 return chatHandler->getChatHandlerDetails();
219 }
220 }
221 return {};
222}
223
224bool
226 const std::string& value,
227 const std::string& rootPath)
228{
229 bool status {true};
230 for (auto& chatHandler : chatHandlers_) {
231 if (chatHandler->id().find(rootPath) != std::string::npos) {
232 if (chatHandler->preferenceMapHasKey(key)) {
233 chatHandler->setPreferenceAttribute(key, value);
234 status &= false;
235 }
236 }
237 }
238 return status;
239}
240
241void
243 const std::string& accountId,
244 const std::string& peerId,
245 const bool toggle)
246{
247 std::pair<std::string, std::string> mPair(accountId, peerId);
248 auto& handlers = chatHandlerToggled_[mPair];
249 auto& chatAllowDenySet = allowDenyList_[mPair];
250 chatSubjects_.emplace(mPair, std::make_shared<PublishObservable<pluginMessagePtr>>());
251
252 auto chatHandlerIt = std::find_if(chatHandlers_.begin(),
253 chatHandlers_.end(),
255 return ((uintptr_t) handler.get() == chatHandlerId);
256 });
257
258 if (chatHandlerIt != chatHandlers_.end()) {
259 if (toggle) {
260 (*chatHandlerIt)->notifyChatSubject(mPair, chatSubjects_[mPair]);
261 if (handlers.find(chatHandlerId) == handlers.end())
262 handlers.insert(chatHandlerId);
263 chatAllowDenySet[(*chatHandlerIt)->getChatHandlerDetails().at("name")] = true;
264 } else {
265 (*chatHandlerIt)->detach(chatSubjects_[mPair]);
266 handlers.erase(chatHandlerId);
267 chatAllowDenySet[(*chatHandlerIt)->getChatHandlerDetails().at("name")] = false;
268 }
270 }
271}
272} // 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.
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.
void publishMessage(pluginMessagePtr message)
Publishes every message sent or received in a conversation that has (or should have) an active ChatHa...
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:96
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:676
std::shared_ptr< T > getAccount(std::string_view accountId) const
Get an account pointer, looks for account of type T.
Definition manager.h:721
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:2937
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:218
std::unique_ptr< ChatHandler > ChatHandlerPtr
void emitSignal(Args... args)
Definition ring_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:66