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