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