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