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