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