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 "data_transfer.h"
19 :
20 : #include "base64.h"
21 : #include "fileutils.h"
22 : #include "manager.h"
23 : #include "client/ring_signal.h"
24 :
25 : #include <mutex>
26 : #include <cstdlib> // mkstemp
27 : #include <filesystem>
28 :
29 : #include <opendht/rng.h>
30 : #include <opendht/thread_pool.h>
31 :
32 : namespace jami {
33 :
34 : libjami::DataTransferId
35 59 : generateUID(std::mt19937_64& engine)
36 : {
37 59 : return std::uniform_int_distribution<libjami::DataTransferId> {1, JAMI_ID_MAX_VAL}(engine);
38 : }
39 :
40 122 : FileInfo::FileInfo(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
41 : const std::string& fileId,
42 : const std::string& interactionId,
43 122 : const libjami::DataTransferInfo& info)
44 122 : : fileId_(fileId)
45 122 : , interactionId_(interactionId)
46 122 : , info_(info)
47 244 : , channel_(channel)
48 122 : {}
49 :
50 : void
51 145 : FileInfo::emit(libjami::DataTransferEventCode code)
52 : {
53 145 : if (finishedCb_ && code >= libjami::DataTransferEventCode::finished)
54 86 : finishedCb_(uint32_t(code));
55 145 : if (interactionId_ != "") {
56 : // Else it's an internal transfer
57 33 : runOnMainThread([info = info_, iid = interactionId_, fid = fileId_, code]() {
58 33 : emitSignal<libjami::DataTransferSignal::DataTransferEvent>(info.accountId,
59 33 : info.conversationId,
60 33 : iid,
61 33 : fid,
62 : uint32_t(code));
63 33 : });
64 : }
65 145 : }
66 :
67 63 : OutgoingFile::OutgoingFile(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
68 : const std::string& fileId,
69 : const std::string& interactionId,
70 : const libjami::DataTransferInfo& info,
71 : size_t start,
72 63 : size_t end)
73 : : FileInfo(channel, fileId, interactionId, info)
74 63 : , start_(start)
75 63 : , end_(end)
76 : {
77 63 : std::filesystem::path fpath(info_.path);
78 63 : if (!std::filesystem::is_regular_file(fpath)) {
79 36 : channel_->shutdown();
80 36 : return;
81 : }
82 27 : stream_.open(fpath, std::ios::binary | std::ios::in);
83 27 : if (!stream_ || !stream_.is_open()) {
84 0 : channel_->shutdown();
85 0 : return;
86 : }
87 63 : }
88 :
89 63 : OutgoingFile::~OutgoingFile()
90 : {
91 63 : if (stream_ && stream_.is_open())
92 0 : stream_.close();
93 63 : if (channel_)
94 63 : channel_->shutdown();
95 63 : }
96 :
97 : void
98 63 : OutgoingFile::process()
99 : {
100 63 : if (!channel_ or !stream_ or !stream_.is_open())
101 36 : return;
102 27 : auto correct = false;
103 27 : stream_.seekg(start_, std::ios::beg);
104 : try {
105 27 : std::vector<char> buffer(UINT16_MAX, 0);
106 27 : std::error_code ec;
107 27 : auto pos = start_;
108 72 : while (!stream_.eof()) {
109 45 : stream_.read(buffer.data(),
110 45 : end_ > start_ ? std::min(end_ - pos, buffer.size()) : buffer.size());
111 45 : auto gcount = stream_.gcount();
112 45 : pos += gcount;
113 45 : channel_->write(reinterpret_cast<const uint8_t*>(buffer.data()), gcount, ec);
114 45 : if (ec)
115 0 : break;
116 : }
117 27 : if (!ec)
118 27 : correct = true;
119 27 : stream_.close();
120 27 : } catch (...) {
121 0 : }
122 27 : if (!isUserCancelled_) {
123 : // NOTE: emit(code) MUST be changed to improve handling of multiple destinations
124 : // But for now, we can just avoid to emit errors to the client, because for outgoing
125 : // transfer in a swarm, for outgoingFiles, we know that the file is ok. And the peer
126 : // will retry the transfer if they need, so we don't need to show errors.
127 27 : if (!interactionId_.empty() && !correct)
128 0 : return;
129 27 : auto code = correct ? libjami::DataTransferEventCode::finished
130 : : libjami::DataTransferEventCode::closed_by_peer;
131 27 : emit(code);
132 : }
133 : }
134 :
135 : void
136 0 : OutgoingFile::cancel()
137 : {
138 : // Remove link, not original file
139 0 : auto path = fileutils::get_data_dir() / "conversation_data" / info_.accountId
140 0 : / info_.conversationId / fileId_;
141 0 : if (std::filesystem::is_symlink(path))
142 0 : dhtnet::fileutils::remove(path);
143 0 : isUserCancelled_ = true;
144 0 : emit(libjami::DataTransferEventCode::closed_by_host);
145 0 : }
146 :
147 59 : IncomingFile::IncomingFile(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
148 : const libjami::DataTransferInfo& info,
149 : const std::string& fileId,
150 : const std::string& interactionId,
151 59 : const std::string& sha3Sum)
152 : : FileInfo(channel, fileId, interactionId, info)
153 59 : , sha3Sum_(sha3Sum)
154 118 : , path_(info.path + ".tmp")
155 : {
156 59 : stream_.open(path_,
157 : std::ios::binary | std::ios::out | std::ios::app);
158 59 : if (!stream_)
159 0 : return;
160 :
161 59 : emit(libjami::DataTransferEventCode::ongoing);
162 0 : }
163 :
164 59 : IncomingFile::~IncomingFile()
165 : {
166 59 : if (channel_)
167 59 : channel_->setOnRecv({});
168 : {
169 59 : std::lock_guard<std::mutex> lk(streamMtx_);
170 59 : if (stream_ && stream_.is_open())
171 0 : stream_.close();
172 59 : }
173 59 : if (channel_)
174 59 : channel_->shutdown();
175 59 : }
176 :
177 : void
178 1 : IncomingFile::cancel()
179 : {
180 1 : isUserCancelled_ = true;
181 1 : emit(libjami::DataTransferEventCode::closed_by_peer);
182 1 : if (channel_)
183 1 : channel_->shutdown();
184 1 : }
185 :
186 : void
187 59 : IncomingFile::process()
188 : {
189 59 : channel_->setOnRecv([w = weak_from_this()](const uint8_t* buf, size_t len) {
190 26 : if (auto shared = w.lock()) {
191 : // No need to lock, setOnRecv is resetted before closing
192 26 : if (shared->stream_.is_open())
193 26 : shared->stream_.write(reinterpret_cast<const char*>(buf), len);
194 26 : shared->info_.bytesProgress = shared->stream_.tellp();
195 26 : }
196 26 : return len;
197 : });
198 59 : channel_->onShutdown([w = weak_from_this()] {
199 59 : auto shared = w.lock();
200 59 : if (!shared)
201 0 : return;
202 : {
203 59 : std::lock_guard<std::mutex> lk(shared->streamMtx_);
204 59 : if (shared->stream_ && shared->stream_.is_open())
205 59 : shared->stream_.close();
206 59 : }
207 59 : auto correct = shared->sha3Sum_.empty();
208 59 : std::error_code ec;
209 59 : if (!correct) {
210 16 : if (shared->isUserCancelled_) {
211 1 : std::filesystem::remove(shared->path_, ec);
212 : } else {
213 15 : auto sha3Sum = fileutils::sha3File(shared->path_);
214 15 : if (shared->sha3Sum_ == sha3Sum) {
215 36 : JAMI_LOG("New file received: {}", shared->info_.path);
216 12 : correct = true;
217 : } else {
218 3 : if (shared->info_.totalSize != 0
219 3 : && shared->info_.totalSize < shared->info_.bytesProgress) {
220 6 : JAMI_WARNING("Removing {} larger than announced: {}/{}", shared->info_.path, shared->info_.bytesProgress, shared->info_.totalSize);
221 2 : std::filesystem::remove(shared->path_, ec);
222 : } else {
223 3 : JAMI_WARNING("Invalid sha3sum detected for {}, incomplete file: {}/{}", shared->info_.path, shared->info_.bytesProgress, shared->info_.totalSize);
224 : }
225 : }
226 15 : }
227 16 : if (ec) {
228 0 : JAMI_ERROR("Failed to remove file {}: {}", shared->path_, ec.message());
229 : }
230 : }
231 59 : if (correct) {
232 55 : std::filesystem::rename(shared->path_, shared->info_.path, ec);
233 55 : if (ec) {
234 0 : JAMI_ERROR("Failed to rename file from {} to {}: {}", shared->path_, shared->info_.path, ec.message());
235 0 : correct = false;
236 : }
237 : }
238 59 : if (shared->isUserCancelled_)
239 1 : return;
240 58 : auto code = correct ? libjami::DataTransferEventCode::finished
241 : : libjami::DataTransferEventCode::closed_by_host;
242 58 : shared->emit(code);
243 59 : });
244 59 : }
245 :
246 : //==============================================================================
247 :
248 : class TransferManager::Impl
249 : {
250 : public:
251 1051 : Impl(const std::string& accountId,
252 : const std::string& accountUri,
253 : const std::string& to,
254 : const std::mt19937_64& rand)
255 1051 : : accountId_(accountId)
256 1051 : , accountUri_(accountUri)
257 1051 : , to_(to)
258 1051 : , rand_(rand)
259 : {
260 1051 : if (!to_.empty()) {
261 766 : conversationDataPath_ = fileutils::get_data_dir() / accountId_ / "conversation_data"
262 1149 : / to_;
263 383 : dhtnet::fileutils::check_dir(conversationDataPath_);
264 383 : waitingPath_ = conversationDataPath_ / "waiting";
265 : }
266 1051 : profilesPath_ = fileutils::get_data_dir() / accountId_ / "profiles";
267 1051 : accountProfilePath_ = fileutils::get_data_dir() / accountId / "profile.vcf";
268 1051 : loadWaiting();
269 1051 : }
270 :
271 1051 : ~Impl()
272 : {
273 1051 : std::lock_guard lk {mapMutex_};
274 1087 : for (const auto& [channel, _of] : outgoings_) {
275 36 : channel->shutdown();
276 : }
277 1051 : outgoings_.clear();
278 1051 : incomings_.clear();
279 1051 : vcards_.clear();
280 1051 : }
281 :
282 1051 : void loadWaiting()
283 : {
284 : try {
285 : // read file
286 2102 : auto file = fileutils::loadFile(waitingPath_);
287 : // load values
288 0 : msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
289 0 : std::lock_guard lk {mapMutex_};
290 0 : oh.get().convert(waitingIds_);
291 1051 : } catch (const std::exception& e) {
292 1051 : return;
293 1051 : }
294 : }
295 19 : void saveWaiting()
296 : {
297 19 : std::ofstream file(waitingPath_, std::ios::trunc | std::ios::binary);
298 19 : msgpack::pack(file, waitingIds_);
299 19 : }
300 :
301 : std::string accountId_ {};
302 : std::string accountUri_ {};
303 : std::string to_ {};
304 : std::filesystem::path waitingPath_ {};
305 : std::filesystem::path profilesPath_ {};
306 : std::filesystem::path accountProfilePath_ {};
307 : std::filesystem::path conversationDataPath_ {};
308 :
309 : std::mutex mapMutex_ {};
310 : std::map<std::string, WaitingRequest> waitingIds_ {};
311 : std::map<std::shared_ptr<dhtnet::ChannelSocket>, std::shared_ptr<OutgoingFile>> outgoings_ {};
312 : std::map<std::string, std::shared_ptr<IncomingFile>> incomings_ {};
313 : std::map<std::pair<std::string, std::string>, std::shared_ptr<IncomingFile>> vcards_ {};
314 :
315 : std::mt19937_64 rand_;
316 : };
317 :
318 1051 : TransferManager::TransferManager(const std::string& accountId,
319 : const std::string& accountUri,
320 : const std::string& to,
321 1051 : const std::mt19937_64& rand)
322 1051 : : pimpl_ {std::make_unique<Impl>(accountId, accountUri, to, rand)}
323 1051 : {}
324 :
325 1051 : TransferManager::~TransferManager() {}
326 :
327 : void
328 65 : TransferManager::transferFile(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
329 : const std::string& fileId,
330 : const std::string& interactionId,
331 : const std::string& path,
332 : size_t start,
333 : size_t end,
334 : OnFinishedCb onFinished)
335 : {
336 65 : std::lock_guard lk {pimpl_->mapMutex_};
337 65 : if (pimpl_->outgoings_.find(channel) != pimpl_->outgoings_.end())
338 2 : return;
339 63 : libjami::DataTransferInfo info;
340 63 : info.accountId = pimpl_->accountId_;
341 63 : info.conversationId = pimpl_->to_;
342 63 : info.path = path;
343 63 : auto f = std::make_shared<OutgoingFile>(channel, fileId, interactionId, info, start, end);
344 63 : f->onFinished([w = weak(), channel, onFinished = std::move(onFinished)](uint32_t code) {
345 27 : if (code == uint32_t(libjami::DataTransferEventCode::finished) && onFinished) {
346 5 : onFinished();
347 : }
348 : // schedule destroy outgoing transfer as not needed
349 27 : dht::ThreadPool().computation().run([w, channel] {
350 27 : if (auto sthis_ = w.lock()) {
351 27 : auto& pimpl = sthis_->pimpl_;
352 27 : std::lock_guard lk {pimpl->mapMutex_};
353 27 : auto itO = pimpl->outgoings_.find(channel);
354 27 : if (itO != pimpl->outgoings_.end())
355 27 : pimpl->outgoings_.erase(itO);
356 54 : }
357 27 : });
358 27 : });
359 63 : pimpl_->outgoings_.emplace(channel, f);
360 63 : dht::ThreadPool::io().run([w = std::weak_ptr<OutgoingFile>(f)] {
361 63 : if (auto of = w.lock())
362 63 : of->process();
363 63 : });
364 65 : }
365 :
366 : bool
367 1 : TransferManager::cancel(const std::string& fileId)
368 : {
369 1 : std::lock_guard lk {pimpl_->mapMutex_};
370 : // Remove from waiting, this avoid auto-download
371 1 : auto itW = pimpl_->waitingIds_.find(fileId);
372 1 : if (itW != pimpl_->waitingIds_.end()) {
373 1 : pimpl_->waitingIds_.erase(itW);
374 1 : JAMI_DBG() << "Cancel " << fileId;
375 1 : pimpl_->saveWaiting();
376 : }
377 1 : auto itC = pimpl_->incomings_.find(fileId);
378 1 : if (itC == pimpl_->incomings_.end())
379 0 : return false;
380 1 : itC->second->cancel();
381 1 : return true;
382 1 : }
383 :
384 : bool
385 2 : TransferManager::info(const std::string& fileId,
386 : std::string& path,
387 : int64_t& total,
388 : int64_t& progress) const noexcept
389 : {
390 2 : std::unique_lock lk {pimpl_->mapMutex_};
391 2 : if (pimpl_->to_.empty())
392 0 : return false;
393 :
394 2 : auto itI = pimpl_->incomings_.find(fileId);
395 2 : auto itW = pimpl_->waitingIds_.find(fileId);
396 2 : path = this->path(fileId).string();
397 2 : if (itI != pimpl_->incomings_.end()) {
398 0 : total = itI->second->info().totalSize;
399 0 : progress = itI->second->info().bytesProgress;
400 0 : return true;
401 2 : } else if (std::filesystem::is_regular_file(path)) {
402 1 : std::ifstream transfer(path, std::ios::binary);
403 1 : transfer.seekg(0, std::ios::end);
404 1 : progress = transfer.tellg();
405 1 : if (itW != pimpl_->waitingIds_.end()) {
406 0 : total = itW->second.totalSize;
407 : } else {
408 : // If not waiting it's finished
409 1 : total = progress;
410 : }
411 1 : return true;
412 2 : } else if (itW != pimpl_->waitingIds_.end()) {
413 0 : total = itW->second.totalSize;
414 0 : progress = 0;
415 0 : return true;
416 : }
417 : // Else we don't know infos there.
418 1 : progress = 0;
419 1 : return false;
420 2 : }
421 :
422 : void
423 11 : TransferManager::waitForTransfer(const std::string& fileId,
424 : const std::string& interactionId,
425 : const std::string& sha3sum,
426 : const std::string& path,
427 : std::size_t total)
428 : {
429 11 : std::unique_lock lk(pimpl_->mapMutex_);
430 11 : auto itW = pimpl_->waitingIds_.find(fileId);
431 11 : if (itW != pimpl_->waitingIds_.end())
432 0 : return;
433 11 : pimpl_->waitingIds_[fileId] = {fileId, interactionId, sha3sum, path, total};
434 11 : pimpl_->saveWaiting();
435 11 : }
436 :
437 : void
438 11 : TransferManager::onIncomingFileTransfer(const std::string& fileId,
439 : const std::shared_ptr<dhtnet::ChannelSocket>& channel)
440 : {
441 11 : std::lock_guard lk(pimpl_->mapMutex_);
442 : // Check if not already an incoming file for this id and that we are waiting this file
443 11 : auto itC = pimpl_->incomings_.find(fileId);
444 11 : if (itC != pimpl_->incomings_.end()) {
445 0 : channel->shutdown();
446 0 : return;
447 : }
448 11 : auto itW = pimpl_->waitingIds_.find(fileId);
449 11 : if (itW == pimpl_->waitingIds_.end()) {
450 0 : channel->shutdown();
451 0 : return;
452 : }
453 :
454 11 : libjami::DataTransferInfo info;
455 11 : info.accountId = pimpl_->accountId_;
456 11 : info.conversationId = pimpl_->to_;
457 11 : info.path = itW->second.path;
458 11 : info.totalSize = itW->second.totalSize;
459 :
460 : // Generate the file path within the conversation data directory
461 : // using the file id if no path has been specified, otherwise create
462 : // a symlink(Note: this will not work on Windows).
463 11 : auto filePath = path(fileId);
464 11 : if (info.path.empty()) {
465 0 : info.path = filePath.string();
466 : } else {
467 : // We don't need to check if this is an existing symlink here, as
468 : // the attempt to create one should report the error string correctly.
469 11 : fileutils::createFileLink(filePath, info.path);
470 : }
471 11 : info.bytesProgress = fileutils::size(info.path);
472 11 : if (info.bytesProgress < 0)
473 11 : info.bytesProgress = 0;
474 :
475 11 : auto ifile = std::make_shared<IncomingFile>(std::move(channel),
476 : info,
477 : fileId,
478 11 : itW->second.interactionId,
479 22 : itW->second.sha3sum);
480 11 : auto res = pimpl_->incomings_.emplace(fileId, std::move(ifile));
481 11 : if (res.second) {
482 11 : res.first->second->onFinished([w = weak(), fileId](uint32_t code) {
483 : // schedule destroy transfer as not needed
484 11 : dht::ThreadPool().computation().run([w, fileId, code] {
485 11 : if (auto sthis_ = w.lock()) {
486 11 : auto& pimpl = sthis_->pimpl_;
487 11 : std::lock_guard lk {pimpl->mapMutex_};
488 11 : auto itO = pimpl->incomings_.find(fileId);
489 11 : if (itO != pimpl->incomings_.end())
490 11 : pimpl->incomings_.erase(itO);
491 11 : if (code == uint32_t(libjami::DataTransferEventCode::finished)) {
492 7 : auto itW = pimpl->waitingIds_.find(fileId);
493 7 : if (itW != pimpl->waitingIds_.end()) {
494 7 : pimpl->waitingIds_.erase(itW);
495 7 : pimpl->saveWaiting();
496 : }
497 : }
498 22 : }
499 11 : });
500 11 : });
501 11 : res.first->second->process();
502 : }
503 11 : }
504 :
505 : std::filesystem::path
506 37 : TransferManager::path(const std::string& fileId) const
507 : {
508 37 : return pimpl_->conversationDataPath_ / fileId;
509 : }
510 :
511 : void
512 48 : TransferManager::onIncomingProfile(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
513 : const std::string& sha3Sum)
514 : {
515 48 : if (!channel)
516 0 : return;
517 :
518 48 : auto chName = channel->name();
519 48 : std::string_view name = chName;
520 48 : auto sep = name.find_last_of('?');
521 48 : if (sep != std::string::npos)
522 5 : name = name.substr(0, sep);
523 :
524 48 : auto lastSep = name.find_last_of('/');
525 48 : auto fileId = name.substr(lastSep + 1);
526 :
527 48 : auto deviceId = channel->deviceId().toString();
528 48 : auto cert = channel->peerCertificate();
529 48 : if (!cert || !cert->issuer || fileId.find(".vcf") == std::string::npos)
530 0 : return;
531 :
532 53 : auto uri = fileId == "profile.vcf" ? cert->issuer->getId().toString()
533 53 : : std::string(fileId.substr(0, fileId.size() - 4 /*.vcf*/));
534 :
535 48 : std::lock_guard lk(pimpl_->mapMutex_);
536 48 : auto idx = std::make_pair(deviceId, uri);
537 : // Check if not already an incoming file for this id and that we are waiting this file
538 48 : auto itV = pimpl_->vcards_.find(idx);
539 48 : if (itV != pimpl_->vcards_.end()) {
540 0 : channel->shutdown();
541 0 : return;
542 : }
543 :
544 48 : auto tid = generateUID(pimpl_->rand_);
545 48 : libjami::DataTransferInfo info;
546 48 : info.accountId = pimpl_->accountId_;
547 48 : info.conversationId = pimpl_->to_;
548 :
549 96 : auto recvDir = fileutils::get_cache_dir() / pimpl_->accountId_ / "vcard";
550 48 : dhtnet::fileutils::recursive_mkdir(recvDir);
551 48 : info.path = (recvDir / fmt::format("{:s}_{:s}_{}", deviceId, uri, tid)).string();
552 :
553 48 : auto ifile = std::make_shared<IncomingFile>(std::move(channel), info, "profile.vcf", "", sha3Sum);
554 48 : auto res = pimpl_->vcards_.emplace(idx, std::move(ifile));
555 48 : if (res.second) {
556 192 : res.first->second->onFinished([w = weak(),
557 48 : uri = std::move(uri),
558 48 : deviceId = std::move(deviceId),
559 48 : accountId = pimpl_->accountId_,
560 48 : cert = std::move(cert),
561 : path = info.path](uint32_t code) {
562 240 : dht::ThreadPool().computation().run([w,
563 48 : uri = std::move(uri),
564 48 : deviceId = std::move(deviceId),
565 48 : accountId = std::move(accountId),
566 48 : path = std::move(path),
567 48 : code] {
568 48 : if (auto sthis_ = w.lock()) {
569 48 : auto& pimpl = sthis_->pimpl_;
570 :
571 48 : auto destPath = sthis_->profilePath(uri);
572 : try {
573 : // Move profile to destination path
574 48 : std::lock_guard lock(dhtnet::fileutils::getFileLock(destPath));
575 48 : dhtnet::fileutils::recursive_mkdir(destPath.parent_path());
576 48 : std::filesystem::rename(path, destPath);
577 48 : if (!pimpl->accountUri_.empty() && uri == pimpl->accountUri_) {
578 : // If this is the account profile, link or copy it to the account profile path
579 1 : if (!fileutils::createFileLink(pimpl->accountProfilePath_, destPath)) {
580 0 : std::error_code ec;
581 0 : std::filesystem::copy_file(destPath, pimpl->accountProfilePath_, ec);
582 : }
583 : }
584 48 : } catch (const std::exception& e) {
585 0 : JAMI_ERROR("{}", e.what());
586 0 : }
587 :
588 48 : std::lock_guard lk {pimpl->mapMutex_};
589 48 : auto itO = pimpl->vcards_.find({deviceId, uri});
590 48 : if (itO != pimpl->vcards_.end())
591 48 : pimpl->vcards_.erase(itO);
592 48 : if (code == uint32_t(libjami::DataTransferEventCode::finished)) {
593 48 : emitSignal<libjami::ConfigurationSignal::ProfileReceived>(accountId,
594 48 : uri,
595 96 : destPath.string());
596 : }
597 96 : }
598 48 : });
599 48 : });
600 48 : res.first->second->process();
601 : }
602 48 : }
603 :
604 : std::filesystem::path
605 57 : TransferManager::profilePath(const std::string& contactId) const
606 : {
607 114 : return pimpl_->profilesPath_ / fmt::format("{}.vcf", base64::encode(contactId));
608 : }
609 :
610 : std::vector<WaitingRequest>
611 2422 : TransferManager::waitingRequests() const
612 : {
613 2422 : std::vector<WaitingRequest> res;
614 2422 : std::lock_guard lk(pimpl_->mapMutex_);
615 2422 : for (const auto& [fileId, req] : pimpl_->waitingIds_) {
616 0 : auto itC = pimpl_->incomings_.find(fileId);
617 0 : if (itC == pimpl_->incomings_.end())
618 0 : res.emplace_back(req);
619 : }
620 4844 : return res;
621 2422 : }
622 :
623 : bool
624 1 : TransferManager::isWaiting(const std::string& fileId) const
625 : {
626 1 : std::lock_guard lk(pimpl_->mapMutex_);
627 2 : return pimpl_->waitingIds_.find(fileId) != pimpl_->waitingIds_.end();
628 1 : }
629 :
630 : } // namespace jami
|