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