Line data Source code
1 : /*
2 : * Copyright (C) 2021-2024 Savoir-faire Linux Inc.
3 : *
4 : * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
5 : *
6 : * This program is free software; you can redistribute it and/or modify
7 : * it under the terms of the GNU General Public License as published by
8 : * the Free Software Foundation; either version 3 of the License, or
9 : * (at your option) any later version.
10 : *
11 : * This program is distributed in the hope that it will be useful,
12 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : * GNU General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU General Public License
17 : * along with this program; if not, write to the Free Software
18 : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 : */
20 :
21 : #pragma once
22 :
23 : #include "scheduled_executor.h"
24 : #include "jamidht/account_manager.h"
25 : #include "jamidht/conversation.h"
26 : #include "jamidht/conversationrepository.h"
27 : #include "jamidht/jami_contact.h"
28 :
29 : #include <mutex>
30 : #include <msgpack.hpp>
31 :
32 : namespace jami {
33 : static constexpr const char MIME_TYPE_INVITE[] {"application/invite"};
34 : static constexpr const char MIME_TYPE_GIT[] {"application/im-gitmessage-id"};
35 :
36 : class SIPCall;
37 :
38 : struct SyncMsg
39 : {
40 : DeviceSync ds;
41 : std::map<std::string, ConvInfo> c;
42 : std::map<std::string, ConversationRequest> cr;
43 : // p is conversation's preferences. It's not stored in c, as
44 : // we can update the preferences without touching any confInfo.
45 : std::map<std::string, std::map<std::string, std::string>> p;
46 : // Last displayed messages [[deprecated]]
47 : std::map<std::string, std::map<std::string, std::string>> ld;
48 : // Read & fetched status
49 : /*
50 : * {{ convId,
51 : * { memberUri,, {
52 : * {"fetch", "commitId"},
53 : * {"fetched_ts", "timestamp"},
54 : * {"read", "commitId"},
55 : * {"read_ts", "timestamp"}
56 : * }
57 : * }}
58 : */
59 : std::map<std::string, std::map<std::string, std::map<std::string, std::string>>> ms;
60 1014 : MSGPACK_DEFINE(ds, c, cr, p, ld, ms)
61 : };
62 :
63 : using ChannelCb = std::function<bool(const std::shared_ptr<dhtnet::ChannelSocket>&)>;
64 : using NeedSocketCb
65 : = std::function<void(const std::string&, const std::string&, ChannelCb&&, const std::string&)>;
66 : using SengMsgCb = std::function<
67 : uint64_t(const std::string&, const DeviceId&, std::map<std::string, std::string>, uint64_t)>;
68 : using NeedsSyncingCb = std::function<void(std::shared_ptr<SyncMsg>&&)>;
69 : using OneToOneRecvCb = std::function<void(const std::string&, const std::string&)>;
70 :
71 : class ConversationModule
72 : {
73 : public:
74 : ConversationModule(std::shared_ptr<JamiAccount> account,
75 : std::shared_ptr<AccountManager> accountManager,
76 : NeedsSyncingCb&& needsSyncingCb,
77 : SengMsgCb&& sendMsgCb,
78 : NeedSocketCb&& onNeedSocket,
79 : NeedSocketCb&& onNeedSwarmSocket,
80 : OneToOneRecvCb&& oneToOneRecvCb,
81 : bool autoLoadConversations = true);
82 587 : ~ConversationModule() = default;
83 :
84 : void setAccountManager(std::shared_ptr<AccountManager> accountManager);
85 :
86 : /**
87 : * Refresh information about conversations
88 : */
89 : void loadConversations();
90 :
91 : void loadSingleConversation(const std::string& convId);
92 :
93 : #ifdef LIBJAMI_TESTABLE
94 : void onBootstrapStatus(const std::function<void(std::string, Conversation::BootstrapStatus)>& cb);
95 : #endif
96 :
97 : void monitor();
98 :
99 : /**
100 : * Bootstrap swarm managers to other peers
101 : */
102 : void bootstrap(const std::string& convId = "");
103 :
104 : /**
105 : * Clear not removed fetch
106 : */
107 : void clearPendingFetch();
108 :
109 : /**
110 : * Reload requests from file
111 : */
112 : void reloadRequests();
113 :
114 : /**
115 : * Return all conversation's id (including syncing ones)
116 : */
117 : std::vector<std::string> getConversations() const;
118 :
119 : /**
120 : * Get related conversation with member
121 : * @param uri The member to search for
122 : * @return the conversation id if found else empty
123 : */
124 : std::string getOneToOneConversation(const std::string& uri) const noexcept;
125 :
126 : /**
127 : * Replace linked conversation in contact's details
128 : * @param uri Of the contact
129 : * @param oldConv Current conversation
130 : * @param newConv
131 : * @return if replaced
132 : */
133 : bool updateConvForContact(const std::string& uri,
134 : const std::string& oldConv,
135 : const std::string& newConv);
136 :
137 : /**
138 : * Return conversation's requests
139 : */
140 : std::vector<std::map<std::string, std::string>> getConversationRequests() const;
141 :
142 : /**
143 : * Called when detecting a new trust request with linked one to one
144 : * @param uri Sender's URI
145 : * @param conversationId Related conversation's id
146 : * @param payload VCard
147 : * @param received Received time
148 : */
149 : void onTrustRequest(const std::string& uri,
150 : const std::string& conversationId,
151 : const std::vector<uint8_t>& payload,
152 : time_t received);
153 :
154 : /**
155 : * Called when receiving a new conversation's request
156 : * @param from Sender
157 : * @param value Conversation's request
158 : */
159 : void onConversationRequest(const std::string& from, const Json::Value& value);
160 :
161 : /**
162 : * Retrieve author of a conversation request
163 : * @param convId Conversation's id
164 : * @return the author of the conversation request
165 : */
166 : std::string peerFromConversationRequest(const std::string& convId) const;
167 :
168 : /**
169 : * Called when a peer needs an invite for a conversation (generally after that they received
170 : * a commit notification for a conversation they don't have yet)
171 : * @param from
172 : * @param conversationId
173 : */
174 : void onNeedConversationRequest(const std::string& from, const std::string& conversationId);
175 :
176 : /**
177 : * Accept a conversation's request
178 : * @param convId
179 : * @param deviceId If a trust request is accepted from a device (can help to sync)
180 : */
181 : void acceptConversationRequest(const std::string& conversationId,
182 : const std::string& deviceId = "");
183 :
184 : /**
185 : * Decline a conversation's request
186 : * @param convId
187 : */
188 : void declineConversationRequest(const std::string& conversationId);
189 :
190 : /**
191 : * Clone conversation from a member
192 : * @note used to clone an old conversation after deleting/re-adding a contact
193 : * @param conversationId
194 : * @param uri
195 : * @param oldConvId
196 : */
197 : void cloneConversationFrom(const std::string& conversationId,
198 : const std::string& uri,
199 : const std::string& oldConvId = "");
200 :
201 : /**
202 : * Starts a new conversation
203 : * @param mode Wanted mode
204 : * @param otherMember If needed (one to one)
205 : * @return conversation's id
206 : */
207 : std::string startConversation(ConversationMode mode = ConversationMode::INVITES_ONLY,
208 : const dht::InfoHash& otherMember = {});
209 :
210 : // Message send/load
211 : void sendMessage(const std::string& conversationId,
212 : Json::Value&& value,
213 : const std::string& replyTo = "",
214 : bool announce = true,
215 : OnCommitCb&& onCommit = {},
216 : OnDoneCb&& cb = {});
217 :
218 : void sendMessage(const std::string& conversationId,
219 : std::string message,
220 : const std::string& replyTo = "",
221 : const std::string& type = "text/plain",
222 : bool announce = true,
223 : OnCommitCb&& onCommit = {},
224 : OnDoneCb&& cb = {});
225 :
226 : void editMessage(const std::string& conversationId,
227 : const std::string& newBody,
228 : const std::string& editedId);
229 : void reactToMessage(const std::string& conversationId,
230 : const std::string& newBody,
231 : const std::string& reactToId);
232 :
233 : /**
234 : * Add to the related conversation the call history message
235 : * @param uri Peer number
236 : * @param duration_ms The call duration in ms
237 : * @param reason
238 : */
239 : void addCallHistoryMessage(const std::string& uri, uint64_t duration_ms, const std::string& reason);
240 :
241 : // Received that a peer displayed a message
242 : bool onMessageDisplayed(const std::string& peer,
243 : const std::string& conversationId,
244 : const std::string& interactionId);
245 : std::map<std::string, std::map<std::string, std::map<std::string, std::string>>> convMessageStatus() const;
246 :
247 : /**
248 : * Load conversation's messages
249 : * @param conversationId Conversation to load
250 : * @param fromMessage
251 : * @param n Max interactions to load
252 : * @return id of the operation
253 : */
254 : uint32_t loadConversationMessages(const std::string& conversationId,
255 : const std::string& fromMessage = "",
256 : size_t n = 0);
257 : uint32_t loadConversation(const std::string& conversationId,
258 : const std::string& fromMessage = "",
259 : size_t n = 0);
260 : uint32_t loadConversationUntil(const std::string& conversationId,
261 : const std::string& fromMessage,
262 : const std::string& to);
263 : uint32_t loadSwarmUntil(const std::string& conversationId,
264 : const std::string& fromMessage,
265 : const std::string& toMessage);
266 : /**
267 : * Clear loaded interactions
268 : * @param conversationId
269 : */
270 : void clearCache(const std::string& conversationId);
271 :
272 : // File transfer
273 : /**
274 : * Returns related transfer manager
275 : * @param id Conversation's id
276 : * @return nullptr if not found, else the manager
277 : */
278 : std::shared_ptr<TransferManager> dataTransfer(const std::string& id) const;
279 :
280 : /**
281 : * Choose if we can accept channel request
282 : * @param member Member to check
283 : * @param fileId File transfer to check (needs to be waiting)
284 : * @param verifyShaSum For debug only
285 : * @return if we accept the channel request
286 : */
287 : bool onFileChannelRequest(const std::string& conversationId,
288 : const std::string& member,
289 : const std::string& fileId,
290 : bool verifyShaSum = true) const;
291 :
292 : /**
293 : * Ask conversation's members to send a file to this device
294 : * @param conversationId Related conversation
295 : * @param interactionId Related interaction
296 : * @param fileId Related fileId
297 : * @param path where to download the file
298 : */
299 : bool downloadFile(const std::string& conversationId,
300 : const std::string& interactionId,
301 : const std::string& fileId,
302 : const std::string& path,
303 : size_t start = 0,
304 : size_t end = 0);
305 :
306 : // Sync
307 : /**
308 : * Sync conversations with detected peer
309 : */
310 : void syncConversations(const std::string& peer, const std::string& deviceId);
311 :
312 : /**
313 : * Detect new conversations and request from other devices
314 : * @param msg Received data
315 : * @param peerId Sender
316 : * @param deviceId
317 : */
318 : void onSyncData(const SyncMsg& msg, const std::string& peerId, const std::string& deviceId);
319 :
320 : /**
321 : * Check if we need to share infos with a contact
322 : * @param memberUri
323 : * @param deviceId
324 : */
325 : bool needsSyncingWith(const std::string& memberUri, const std::string& deviceId) const;
326 :
327 : /**
328 : * Notify that a peer fetched a commit
329 : * @note: this definitely remove the repository when needed (when we left and someone fetched
330 : * the information)
331 : * @param conversationId Related conv
332 : * @param deviceId Device who synced
333 : * @param commit HEAD synced
334 : */
335 : void setFetched(const std::string& conversationId,
336 : const std::string& deviceId,
337 : const std::string& commit);
338 :
339 : /**
340 : * Launch fetch on new commit
341 : * @param peer Who sent the notification
342 : * @param deviceId Who sent the notification
343 : * @param conversationId Related conversation
344 : * @param commitId Commit to retrieve
345 : */
346 : void fetchNewCommits(const std::string& peer,
347 : const std::string& deviceId,
348 : const std::string& conversationId,
349 : const std::string& commitId);
350 :
351 : // Conversation's member
352 : /**
353 : * Adds a new member to a conversation (this will triggers a member event + new message on success)
354 : * @param conversationId
355 : * @param contactUri
356 : * @param sendRequest If we need to inform the peer (used for tests)
357 : */
358 : void addConversationMember(const std::string& conversationId,
359 : const dht::InfoHash& contactUri,
360 : bool sendRequest = true);
361 : /**
362 : * Remove a member from a conversation (this will trigger a member event + new message on success)
363 : * @param conversationId
364 : * @param contactUri
365 : * @param isDevice
366 : */
367 : void removeConversationMember(const std::string& conversationId,
368 : const dht::InfoHash& contactUri,
369 : bool isDevice = false);
370 : /**
371 : * Get members
372 : * @param conversationId
373 : * @param includeBanned
374 : * @return a map of members with their role and details
375 : */
376 : std::vector<std::map<std::string, std::string>> getConversationMembers(
377 : const std::string& conversationId, bool includeBanned = false) const;
378 : /**
379 : * Retrieve the number of interactions from interactionId to HEAD
380 : * @param convId
381 : * @param interactionId "" for getting the whole history
382 : * @param authorUri Stop when detect author
383 : * @return number of interactions since interactionId
384 : */
385 : uint32_t countInteractions(const std::string& convId,
386 : const std::string& toId,
387 : const std::string& fromId,
388 : const std::string& authorUri) const;
389 :
390 : /**
391 : * Search in conversations via a filter
392 : * @param req Id of the request
393 : * @param convId Leave empty to search in all conversation, else add the conversation's id
394 : * @param filter Parameters for the search
395 : * @note triggers messagesFound
396 : */
397 : void search(uint32_t req, const std::string& convId, const Filter& filter) const;
398 :
399 : // Conversation's infos management
400 : /**
401 : * Update metadatas from conversations (like title, avatar, etc)
402 : * @param conversationId
403 : * @param infos
404 : * @param sync If we need to sync with others (used for tests)
405 : */
406 : void updateConversationInfos(const std::string& conversationId,
407 : const std::map<std::string, std::string>& infos,
408 : bool sync = true);
409 : std::map<std::string, std::string> conversationInfos(const std::string& conversationId) const;
410 : /**
411 : * Update user's preferences (like color, notifications, etc) to be synced across devices
412 : * @param conversationId
413 : * @param preferences
414 : */
415 : void setConversationPreferences(const std::string& conversationId,
416 : const std::map<std::string, std::string>& prefs);
417 : std::map<std::string, std::string> getConversationPreferences(const std::string& conversationId,
418 : bool includeCreated = false) const;
419 : /**
420 : * Retrieve all conversation preferences to sync with other devices
421 : */
422 : std::map<std::string, std::map<std::string, std::string>> convPreferences() const;
423 : // Get the map into a VCard format for storing
424 : std::vector<uint8_t> conversationVCard(const std::string& conversationId) const;
425 :
426 : /**
427 : * Return if a device or member is banned from a conversation
428 : * @param convId
429 : * @param uri
430 : */
431 : bool isBanned(const std::string& convId, const std::string& uri) const;
432 :
433 : // Remove swarm
434 : /**
435 : * Remove one to one conversations related to a contact
436 : * @param uri Of the contact
437 : * @param ban If banned
438 : */
439 : void removeContact(const std::string& uri, bool ban);
440 :
441 : /**
442 : * Remove a conversation, but not the contact
443 : * @param conversationId
444 : * @return if successfully removed
445 : */
446 : bool removeConversation(const std::string& conversationId);
447 : void initReplay(const std::string& oldConvId, const std::string& newConvId);
448 : /**
449 : * Check if we're hosting a specific conference
450 : * @param conversationId (empty to search all conv)
451 : * @param confId
452 : * @return true if hosting this conference
453 : */
454 : bool isHosting(const std::string& conversationId, const std::string& confId) const;
455 : /**
456 : * Return active calls
457 : * @param convId Which conversation to choose
458 : * @return {{"id":id}, {"uri":uri}, {"device":device}}
459 : */
460 : std::vector<std::map<std::string, std::string>> getActiveCalls(
461 : const std::string& conversationId) const;
462 : /**
463 : * Call the conversation
464 : * @param url Url to call (swarm:conversation or swarm:conv/account/device/conf to join)
465 : * @param mediaList The media list
466 : * @param cb Callback to pass which device to call (called in the same thread)
467 : * @return call if a call is started, else nullptr
468 : */
469 : std::shared_ptr<SIPCall> call(const std::string& url,
470 : const std::vector<libjami::MediaMap>& mediaList,
471 : std::function<void(const std::string&, const DeviceId&, const std::shared_ptr<SIPCall>&)>&& cb);
472 : void hostConference(const std::string& conversationId,
473 : const std::string& confId,
474 : const std::string& callId,
475 : const std::vector<libjami::MediaMap>& mediaList = {});
476 :
477 : // The following methods modify what is stored on the disk
478 : static void saveConvInfos(const std::string& accountId,
479 : const std::map<std::string, ConvInfo>& conversations);
480 : static void saveConvInfosToPath(const std::filesystem::path& path,
481 : const std::map<std::string, ConvInfo>& conversations);
482 : static void saveConvRequests(
483 : const std::string& accountId,
484 : const std::map<std::string, ConversationRequest>& conversationsRequests);
485 : static void saveConvRequestsToPath(
486 : const std::filesystem::path& path,
487 : const std::map<std::string, ConversationRequest>& conversationsRequests);
488 :
489 : static std::map<std::string, ConvInfo> convInfos(const std::string& accountId);
490 : static std::map<std::string, ConvInfo> convInfosFromPath(const std::filesystem::path& path);
491 : static std::map<std::string, ConversationRequest> convRequests(const std::string& accountId);
492 : static std::map<std::string, ConversationRequest> convRequestsFromPath(
493 : const std::filesystem::path& path);
494 : void addConvInfo(const ConvInfo& info);
495 :
496 : /**
497 : * Get a conversation
498 : * @param convId
499 : */
500 : std::shared_ptr<Conversation> getConversation(const std::string& convId);
501 : /**
502 : * Return current git socket used for a conversation
503 : * @param deviceId Related device
504 : * @param conversationId Related conversation
505 : * @return the related socket
506 : */
507 : std::shared_ptr<dhtnet::ChannelSocket> gitSocket(std::string_view deviceId,
508 : std::string_view convId) const;
509 : void removeGitSocket(std::string_view deviceId, std::string_view convId);
510 : void addGitSocket(std::string_view deviceId,
511 : std::string_view convId,
512 : const std::shared_ptr<dhtnet::ChannelSocket>& channel);
513 : /**
514 : * Clear all connection (swarm channels)
515 : */
516 : void shutdownConnections();
517 : /**
518 : * Add a swarm connection
519 : * @param conversationId
520 : * @param socket
521 : */
522 : void addSwarmChannel(const std::string& conversationId,
523 : std::shared_ptr<dhtnet::ChannelSocket> socket);
524 : /**
525 : * Triggers a bucket maintainance for DRTs
526 : */
527 : void connectivityChanged();
528 :
529 : /**
530 : * Get Typers object for a conversation
531 : * @param convId
532 : * @return the Typer object
533 : */
534 : std::shared_ptr<Typers> getTypers(const std::string& convId);
535 :
536 : private:
537 : class Impl;
538 : std::shared_ptr<Impl> pimpl_;
539 : };
540 :
541 : } // namespace jami
|