Line data Source code
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 :
25 : namespace jami {
26 :
27 32 : ChatServicesManager::ChatServicesManager(PluginManager& pluginManager)
28 : {
29 32 : registerComponentsLifeCycleManagers(pluginManager);
30 32 : registerChatService(pluginManager);
31 32 : PluginPreferencesUtils::getAllowDenyListPreferences(allowDenyList_);
32 32 : }
33 :
34 : void
35 32 : ChatServicesManager::registerComponentsLifeCycleManagers(PluginManager& pluginManager)
36 : {
37 : // registerChatHandler may be called by the PluginManager upon loading a plugin.
38 0 : auto registerChatHandler = [this](void* data, std::mutex& pmMtx_) {
39 0 : std::lock_guard lk(pmMtx_);
40 0 : ChatHandlerPtr ptr {(static_cast<ChatHandler*>(data))};
41 :
42 0 : if (!ptr)
43 0 : return -1;
44 0 : handlersNameMap_[ptr->getChatHandlerDetails().at("name")] = (uintptr_t) ptr.get();
45 0 : std::size_t found = ptr->id().find_last_of(DIR_SEPARATOR_CH);
46 : // Adding preference that tells us to automatically activate a ChatHandler.
47 0 : PluginPreferencesUtils::addAlwaysHandlerPreference(ptr->getChatHandlerDetails().at("name"),
48 0 : ptr->id().substr(0, found));
49 0 : chatHandlers_.emplace_back(std::move(ptr));
50 0 : return 0;
51 0 : };
52 :
53 : // unregisterChatHandler may be called by the PluginManager while unloading.
54 0 : auto unregisterChatHandler = [this](void* data, std::mutex& pmMtx_) {
55 0 : std::lock_guard lk(pmMtx_);
56 0 : auto handlerIt = std::find_if(chatHandlers_.begin(), chatHandlers_.end(), [data](ChatHandlerPtr& handler) {
57 0 : return (handler.get() == data);
58 : });
59 :
60 0 : if (handlerIt != chatHandlers_.end()) {
61 0 : for (auto& toggledList : chatHandlerToggled_) {
62 0 : auto handlerId = std::find_if(toggledList.second.begin(),
63 : toggledList.second.end(),
64 0 : [id = (uintptr_t) handlerIt->get()](uintptr_t handlerId) {
65 0 : return (handlerId == id);
66 : });
67 : // If ChatHandler is attempting to destroy one which is currently in use, we deactivate it.
68 0 : if (handlerId != toggledList.second.end()) {
69 0 : (*handlerIt)->detach(chatSubjects_[toggledList.first]);
70 0 : toggledList.second.erase(handlerId);
71 : }
72 : }
73 0 : handlersNameMap_.erase((*handlerIt)->getChatHandlerDetails().at("name"));
74 0 : chatHandlers_.erase(handlerIt);
75 : }
76 0 : return true;
77 0 : };
78 :
79 : // Services are registered to the PluginManager.
80 32 : pluginManager.registerComponentManager("ChatHandlerManager", registerChatHandler, unregisterChatHandler);
81 32 : }
82 :
83 : void
84 32 : ChatServicesManager::registerChatService(PluginManager& pluginManager)
85 : {
86 : // sendTextMessage is a service that allows plugins to send a message in a conversation.
87 0 : auto sendTextMessage = [](const DLPlugin*, void* data) {
88 0 : auto cm = static_cast<JamiMessage*>(data);
89 0 : if (const auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(cm->accountId)) {
90 : try {
91 0 : if (cm->isSwarm)
92 0 : acc->convModule()->sendMessage(cm->peerId, cm->data.at("body"));
93 : else
94 0 : jami::Manager::instance().sendTextMessage(cm->accountId, cm->peerId, cm->data, true);
95 0 : } catch (const std::exception& e) {
96 0 : JAMI_ERR("Exception during text message sending: %s", e.what());
97 0 : }
98 0 : }
99 0 : return 0;
100 : };
101 :
102 : // Services are registered to the PluginManager.
103 32 : pluginManager.registerService("sendTextMessage", sendTextMessage);
104 32 : }
105 :
106 : bool
107 1252 : ChatServicesManager::hasHandlers() const
108 : {
109 1252 : return not chatHandlers_.empty();
110 : }
111 :
112 : std::vector<std::string>
113 0 : ChatServicesManager::getChatHandlers() const
114 : {
115 0 : std::vector<std::string> res;
116 0 : res.reserve(chatHandlers_.size());
117 0 : for (const auto& chatHandler : chatHandlers_) {
118 0 : res.emplace_back(std::to_string((uintptr_t) chatHandler.get()));
119 : }
120 0 : return res;
121 0 : }
122 :
123 : void
124 0 : ChatServicesManager::publishMessage(pluginMessagePtr message)
125 : {
126 0 : if (message->fromPlugin or chatHandlers_.empty())
127 0 : return;
128 :
129 0 : std::pair<std::string, std::string> mPair(message->accountId, message->peerId);
130 0 : auto& handlers = chatHandlerToggled_[mPair];
131 0 : auto& chatAllowDenySet = allowDenyList_[mPair];
132 :
133 : // Search for activation flag.
134 0 : for (auto& chatHandler : chatHandlers_) {
135 0 : std::string chatHandlerName = chatHandler->getChatHandlerDetails().at("name");
136 0 : std::size_t found = chatHandler->id().find_last_of(DIR_SEPARATOR_CH);
137 : // toggle is true if we should automatically activate the ChatHandler.
138 0 : bool toggle = PluginPreferencesUtils::getAlwaysPreference(chatHandler->id().substr(0, found),
139 : chatHandlerName,
140 0 : message->accountId);
141 : // toggle is overwritten if we have previously activated/deactivated the ChatHandler
142 : // for the given conversation.
143 0 : auto allowedIt = chatAllowDenySet.find(chatHandlerName);
144 0 : if (allowedIt != chatAllowDenySet.end())
145 0 : toggle = (*allowedIt).second;
146 0 : bool toggled = handlers.find((uintptr_t) chatHandler.get()) != handlers.end();
147 0 : if (toggle || toggled) {
148 : // Creates chat subjects if it doesn't exist yet.
149 0 : auto& subject = chatSubjects_.emplace(mPair, std::make_shared<PublishObservable<pluginMessagePtr>>())
150 0 : .first->second;
151 0 : if (!toggled) {
152 : // If activation is expected, and not yet performed, we perform activation
153 0 : handlers.insert((uintptr_t) chatHandler.get());
154 0 : chatHandler->notifyChatSubject(mPair, subject);
155 0 : chatAllowDenySet[chatHandlerName] = true;
156 0 : PluginPreferencesUtils::setAllowDenyListPreferences(allowDenyList_);
157 : }
158 : // Finally we feed Chat subject with the message.
159 0 : subject->publish(message);
160 : }
161 0 : }
162 0 : }
163 :
164 : void
165 832 : ChatServicesManager::cleanChatSubjects(const std::string& accountId, const std::string& peerId)
166 : {
167 832 : std::pair<std::string, std::string> mPair(accountId, peerId);
168 832 : for (auto it = chatSubjects_.begin(); it != chatSubjects_.end();) {
169 0 : if (peerId.empty() && it->first.first == accountId)
170 0 : it = chatSubjects_.erase(it);
171 0 : else if (!peerId.empty() && it->first == mPair)
172 0 : it = chatSubjects_.erase(it);
173 : else
174 0 : ++it;
175 : }
176 832 : }
177 :
178 : void
179 0 : ChatServicesManager::toggleChatHandler(const std::string& chatHandlerId,
180 : const std::string& accountId,
181 : const std::string& peerId,
182 : const bool toggle)
183 : {
184 0 : toggleChatHandler(std::stoull(chatHandlerId), accountId, peerId, toggle);
185 0 : }
186 :
187 : std::vector<std::string>
188 0 : ChatServicesManager::getChatHandlerStatus(const std::string& accountId, const std::string& peerId)
189 : {
190 0 : std::pair<std::string, std::string> mPair(accountId, peerId);
191 0 : const auto& it = allowDenyList_.find(mPair);
192 0 : std::vector<std::string> ret;
193 0 : if (it != allowDenyList_.end()) {
194 0 : for (const auto& chatHandlerName : it->second)
195 0 : if (chatHandlerName.second
196 0 : && handlersNameMap_.find(chatHandlerName.first)
197 0 : != handlersNameMap_.end()) { // We only return active ChatHandler ids
198 0 : ret.emplace_back(std::to_string(handlersNameMap_.at(chatHandlerName.first)));
199 : }
200 : }
201 :
202 0 : return ret;
203 0 : }
204 :
205 : std::map<std::string, std::string>
206 0 : ChatServicesManager::getChatHandlerDetails(const std::string& chatHandlerIdStr)
207 : {
208 0 : auto chatHandlerId = std::stoull(chatHandlerIdStr);
209 0 : for (auto& chatHandler : chatHandlers_) {
210 0 : if ((uintptr_t) chatHandler.get() == chatHandlerId) {
211 0 : return chatHandler->getChatHandlerDetails();
212 : }
213 : }
214 0 : return {};
215 : }
216 :
217 : bool
218 0 : ChatServicesManager::setPreference(const std::string& key, const std::string& value, const std::string& rootPath)
219 : {
220 0 : bool status {true};
221 0 : for (auto& chatHandler : chatHandlers_) {
222 0 : if (chatHandler->id().find(rootPath) != std::string::npos) {
223 0 : if (chatHandler->preferenceMapHasKey(key)) {
224 0 : chatHandler->setPreferenceAttribute(key, value);
225 0 : status &= false;
226 : }
227 : }
228 : }
229 0 : return status;
230 : }
231 :
232 : void
233 0 : ChatServicesManager::toggleChatHandler(const uintptr_t chatHandlerId,
234 : const std::string& accountId,
235 : const std::string& peerId,
236 : const bool toggle)
237 : {
238 0 : std::pair<std::string, std::string> mPair(accountId, peerId);
239 0 : auto& handlers = chatHandlerToggled_[mPair];
240 0 : auto& chatAllowDenySet = allowDenyList_[mPair];
241 0 : chatSubjects_.emplace(mPair, std::make_shared<PublishObservable<pluginMessagePtr>>());
242 :
243 0 : auto chatHandlerIt = std::find_if(chatHandlers_.begin(),
244 : chatHandlers_.end(),
245 0 : [chatHandlerId](ChatHandlerPtr& handler) {
246 0 : return ((uintptr_t) handler.get() == chatHandlerId);
247 : });
248 :
249 0 : if (chatHandlerIt != chatHandlers_.end()) {
250 0 : if (toggle) {
251 0 : (*chatHandlerIt)->notifyChatSubject(mPair, chatSubjects_[mPair]);
252 0 : if (handlers.find(chatHandlerId) == handlers.end())
253 0 : handlers.insert(chatHandlerId);
254 0 : chatAllowDenySet[(*chatHandlerIt)->getChatHandlerDetails().at("name")] = true;
255 : } else {
256 0 : (*chatHandlerIt)->detach(chatSubjects_[mPair]);
257 0 : handlers.erase(chatHandlerId);
258 0 : chatAllowDenySet[(*chatHandlerIt)->getChatHandlerDetails().at("name")] = false;
259 : }
260 0 : PluginPreferencesUtils::setAllowDenyListPreferences(allowDenyList_);
261 : }
262 0 : }
263 : } // namespace jami
|