Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 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 "typers.h"
19 : #include "manager.h"
20 : #include "jamidht/jamiaccount.h"
21 :
22 : static constexpr std::chrono::steady_clock::duration COMPOSING_TIMEOUT {std::chrono::seconds(12)};
23 :
24 : namespace jami {
25 :
26 390 : Typers::Typers(const std::shared_ptr<JamiAccount>& acc, const std::string& convId)
27 390 : : ioContext_(Manager::instance().ioContext())
28 390 : , acc_(acc)
29 390 : , accountId_(acc->getAccountID())
30 390 : , convId_(convId)
31 1170 : , selfUri_(acc->getUsername())
32 390 : {}
33 :
34 390 : Typers::~Typers()
35 : {
36 391 : for (auto& watcher : watcher_) {
37 1 : watcher.second.cancel();
38 : }
39 390 : watcher_.clear();
40 390 : }
41 :
42 : std::string
43 4 : getIsComposing(const std::string& conversationId, bool isWriting)
44 : {
45 : // implementing https://tools.ietf.org/rfc/rfc3994.txt
46 : return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
47 : "<isComposing><state>{}</state>{}</isComposing>",
48 4 : isWriting ? "active"sv : "idle"sv,
49 12 : conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>");
50 : }
51 :
52 : void
53 7 : Typers::addTyper(const std::string& typer, bool sendMessage)
54 : {
55 7 : auto acc = acc_.lock();
56 7 : if (!acc || !acc->isComposingEnabled())
57 1 : return;
58 6 : auto [it, res] = watcher_.emplace(typer, asio::steady_timer {*ioContext_});
59 6 : if (res) {
60 6 : auto& watcher = it->second;
61 : // Check next member
62 6 : watcher.expires_at(std::chrono::steady_clock::now() + COMPOSING_TIMEOUT);
63 6 : watcher.async_wait(std::bind(&Typers::onTyperTimeout, shared_from_this(), std::placeholders::_1, typer));
64 :
65 6 : if (typer != selfUri_)
66 3 : emitSignal<libjami::ConfigurationSignal::ComposingStatusChanged>(accountId_, convId_, typer, 1);
67 : }
68 6 : if (sendMessage) {
69 : // In this case we should emit for remote to update the timer
70 12 : acc->sendInstantMessage(convId_, {{MIME_TYPE_IM_COMPOSING, getIsComposing(convId_, true)}});
71 : }
72 10 : }
73 :
74 : void
75 22 : Typers::removeTyper(const std::string& typer, bool sendMessage)
76 : {
77 22 : auto acc = acc_.lock();
78 22 : if (!acc || !acc->isComposingEnabled())
79 1 : return;
80 21 : if (watcher_.erase(typer)) {
81 5 : if (sendMessage) {
82 4 : acc->sendInstantMessage(convId_, {{MIME_TYPE_IM_COMPOSING, getIsComposing(convId_, false)}});
83 : }
84 5 : if (typer != selfUri_) {
85 3 : emitSignal<libjami::ConfigurationSignal::ComposingStatusChanged>(accountId_, convId_, typer, 0);
86 : }
87 : }
88 23 : }
89 :
90 : void
91 6 : Typers::onTyperTimeout(const asio::error_code& ec, const std::string& typer)
92 : {
93 6 : if (ec == asio::error::operation_aborted)
94 3 : return;
95 3 : removeTyper(typer);
96 : }
97 :
98 : } // namespace jami
|