Ring Daemon 16.0.0
Loading...
Searching...
No Matches
data_transfer.cpp
Go to the documentation of this file.
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
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
40FileInfo::FileInfo(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
41 const std::string& fileId,
42 const std::string& interactionId,
43 const libjami::DataTransferInfo& info)
44 : fileId_(fileId)
45 , interactionId_(interactionId)
46 , info_(info)
47 , channel_(channel)
48{}
49
50void
52{
53 if (finishedCb_ && code >= libjami::DataTransferEventCode::finished)
54 finishedCb_(uint32_t(code));
55 if (interactionId_ != "") {
56 // Else it's an internal transfer
60 iid,
61 fid,
62 uint32_t(code));
63 });
64 }
65}
66
67OutgoingFile::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 size_t end)
73 : FileInfo(channel, fileId, interactionId, info)
74 , start_(start)
75 , end_(end)
76{
77 std::filesystem::path fpath(info_.path);
78 if (!std::filesystem::is_regular_file(fpath)) {
79 dht::ThreadPool::io().run([channel = std::move(channel_)] {
80 channel->shutdown();
81 });
82 return;
83 }
84 stream_.open(fpath, std::ios::binary | std::ios::in);
85 if (!stream_ || !stream_.is_open()) {
86 dht::ThreadPool::io().run([channel = std::move(channel_)] {
87 channel->shutdown();
88 });
89 return;
90 }
91}
92
94{
95 if (stream_ && stream_.is_open())
96 stream_.close();
97 if (channel_) {
98 dht::ThreadPool::io().run([channel = std::move(channel_)] {
99 channel->shutdown();
100 });
101 }
102}
103
104void
106{
107 if (!channel_ or !stream_ or !stream_.is_open())
108 return;
109 auto correct = false;
110 stream_.seekg(start_, std::ios::beg);
111 try {
112 std::vector<char> buffer(UINT16_MAX, 0);
113 std::error_code ec;
114 auto pos = start_;
115 while (!stream_.eof()) {
116 stream_.read(buffer.data(),
117 end_ > start_ ? std::min(end_ - pos, buffer.size()) : buffer.size());
118 auto gcount = stream_.gcount();
119 pos += gcount;
120 channel_->write(reinterpret_cast<const uint8_t*>(buffer.data()), gcount, ec);
121 if (ec)
122 break;
123 }
124 if (!ec)
125 correct = true;
126 stream_.close();
127 } catch (...) {
128 }
129 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 if (!interactionId_.empty() && !correct)
135 return;
136 auto code = correct ? libjami::DataTransferEventCode::finished
137 : libjami::DataTransferEventCode::closed_by_peer;
138 emit(code);
139 }
140}
141
142void
144{
145 // Remove link, not original file
146 auto path = fileutils::get_data_dir() / "conversation_data" / info_.accountId
148 if (std::filesystem::is_symlink(path))
149 dhtnet::fileutils::remove(path);
150 isUserCancelled_ = true;
151 emit(libjami::DataTransferEventCode::closed_by_host);
152}
153
154IncomingFile::IncomingFile(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
155 const libjami::DataTransferInfo& info,
156 const std::string& fileId,
157 const std::string& interactionId,
158 const std::string& sha3Sum)
159 : FileInfo(channel, fileId, interactionId, info)
160 , sha3Sum_(sha3Sum)
161 , path_(info.path + ".tmp")
162{
163 stream_.open(path_,
164 std::ios::binary | std::ios::out | std::ios::app);
165 if (!stream_)
166 return;
167
168 emit(libjami::DataTransferEventCode::ongoing);
169}
170
172{
173 if (channel_)
174 channel_->setOnRecv({});
175 {
176 std::lock_guard<std::mutex> lk(streamMtx_);
177 if (stream_ && stream_.is_open())
178 stream_.close();
179 }
180 if (channel_)
181 channel_->shutdown();
182}
183
184void
186{
187 isUserCancelled_ = true;
188 emit(libjami::DataTransferEventCode::closed_by_peer);
189 if (channel_)
190 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 // No need to lock, setOnRecv is resetted before closing
199 if (shared->stream_.is_open())
200 shared->stream_.write(reinterpret_cast<const char*>(buf), len);
201 shared->info_.bytesProgress = shared->stream_.tellp();
202 }
203 return len;
204 });
205 channel_->onShutdown([w = weak_from_this()] {
206 auto shared = w.lock();
207 if (!shared)
208 return;
209 {
210 std::lock_guard<std::mutex> lk(shared->streamMtx_);
211 if (shared->stream_ && shared->stream_.is_open())
212 shared->stream_.close();
213 }
214 auto correct = shared->sha3Sum_.empty();
215 std::error_code ec;
216 if (!correct) {
217 if (shared->isUserCancelled_) {
218 std::filesystem::remove(shared->path_, ec);
219 } else if (shared->info_.bytesProgress < shared->info_.totalSize) {
220 JAMI_WARNING("Channel for {} shut down before transfer was complete (progress: {}/{})", shared->info_.path, shared->info_.bytesProgress, shared->info_.totalSize);
221 } else if (shared->info_.totalSize != 0 && shared->info_.bytesProgress > shared->info_.totalSize) {
222 JAMI_WARNING("Removing {} larger than announced: {}/{}", shared->path_, shared->info_.bytesProgress, shared->info_.totalSize);
223 std::filesystem::remove(shared->path_, ec);
224 } else {
225 auto sha3Sum = fileutils::sha3File(shared->path_);
226 if (shared->sha3Sum_ == sha3Sum) {
227 JAMI_LOG("New file received: {}", shared->info_.path);
228 correct = true;
229 } else {
230 JAMI_WARNING("Removing {} with expected size ({} bytes) but invalid sha3sum (expected: {}, actual: {})",
231 shared->path_, shared->info_.totalSize, shared->sha3Sum_, sha3Sum);
232 std::filesystem::remove(shared->path_, ec);
233 }
234 }
235 if (ec) {
236 JAMI_ERROR("Failed to remove file {}: {}", shared->path_, ec.message());
237 }
238 }
239 if (correct) {
240 std::filesystem::rename(shared->path_, shared->info_.path, ec);
241 if (ec) {
242 JAMI_ERROR("Failed to rename file from {} to {}: {}", shared->path_, shared->info_.path, ec.message());
243 correct = false;
244 }
245 }
246 if (shared->isUserCancelled_)
247 return;
248 auto code = correct ? libjami::DataTransferEventCode::finished
249 : libjami::DataTransferEventCode::closed_by_host;
250 shared->emit(code);
251 });
252}
253
254//==============================================================================
255
257{
258public:
259 Impl(const std::string& accountId,
260 const std::string& accountUri,
261 const std::string& to,
262 const std::mt19937_64& rand)
263 : accountId_(accountId)
265 , to_(to)
266 , rand_(rand)
267 {
268 if (!to_.empty()) {
270 / to_;
271 dhtnet::fileutils::check_dir(conversationDataPath_);
273 }
275 accountProfilePath_ = fileutils::get_data_dir() / accountId / "profile.vcf";
276 loadWaiting();
277 }
278
280 {
281 std::lock_guard lk {mapMutex_};
282 for (const auto& [channel, _of] : outgoings_) {
283 channel->shutdown();
284 }
285 outgoings_.clear();
286 incomings_.clear();
287 vcards_.clear();
288 }
289
291 {
292 try {
293 // read file
295 // load values
296 msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
297 std::lock_guard lk {mapMutex_};
298 oh.get().convert(waitingIds_);
299 } catch (const std::exception& e) {
300 return;
301 }
302 }
304 {
305 std::ofstream file(waitingPath_, std::ios::trunc | std::ios::binary);
306 msgpack::pack(file, waitingIds_);
307 }
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
326TransferManager::TransferManager(const std::string& accountId,
327 const std::string& accountUri,
328 const std::string& to,
329 const std::mt19937_64& rand)
330 : pimpl_ {std::make_unique<Impl>(accountId, accountUri, to, rand)}
331{}
332
334
335void
336TransferManager::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 std::lock_guard lk {pimpl_->mapMutex_};
345 if (pimpl_->outgoings_.find(channel) != pimpl_->outgoings_.end())
346 return;
348 info.accountId = pimpl_->accountId_;
349 info.conversationId = pimpl_->to_;
350 info.path = path;
351 auto f = std::make_shared<OutgoingFile>(channel, fileId, interactionId, info, start, end);
352 f->onFinished([w = weak(), channel, onFinished = std::move(onFinished)](uint32_t code) {
353 if (code == uint32_t(libjami::DataTransferEventCode::finished) && onFinished) {
354 onFinished();
355 }
356 // schedule destroy outgoing transfer as not needed
357 dht::ThreadPool().computation().run([w, channel] {
358 if (auto sthis_ = w.lock()) {
359 auto& pimpl = sthis_->pimpl_;
360 std::lock_guard lk {pimpl->mapMutex_};
361 auto itO = pimpl->outgoings_.find(channel);
362 if (itO != pimpl->outgoings_.end())
363 pimpl->outgoings_.erase(itO);
364 }
365 });
366 });
367 pimpl_->outgoings_.emplace(channel, f);
368 dht::ThreadPool::io().run([w = std::weak_ptr<OutgoingFile>(f)] {
369 if (auto of = w.lock())
370 of->process();
371 });
372}
373
374bool
375TransferManager::cancel(const std::string& fileId)
376{
377 std::lock_guard lk {pimpl_->mapMutex_};
378 // Remove from waiting, this avoid auto-download
379 auto itW = pimpl_->waitingIds_.find(fileId);
380 if (itW != pimpl_->waitingIds_.end()) {
381 pimpl_->waitingIds_.erase(itW);
382 JAMI_DBG() << "Cancel " << fileId;
383 pimpl_->saveWaiting();
384 }
385 auto itC = pimpl_->incomings_.find(fileId);
386 if (itC == pimpl_->incomings_.end())
387 return false;
388 itC->second->cancel();
389 return true;
390}
391
392bool
393TransferManager::info(const std::string& fileId,
394 std::string& path,
395 int64_t& total,
396 int64_t& progress) const noexcept
397{
398 std::unique_lock lk {pimpl_->mapMutex_};
399 if (pimpl_->to_.empty())
400 return false;
401
402 auto itI = pimpl_->incomings_.find(fileId);
403 auto itW = pimpl_->waitingIds_.find(fileId);
404 path = this->path(fileId).string();
405 if (itI != pimpl_->incomings_.end()) {
406 total = itI->second->info().totalSize;
407 progress = itI->second->info().bytesProgress;
408 return true;
409 } else if (std::filesystem::is_regular_file(path)) {
410 std::ifstream transfer(path, std::ios::binary);
411 transfer.seekg(0, std::ios::end);
412 progress = transfer.tellg();
413 if (itW != pimpl_->waitingIds_.end()) {
414 total = itW->second.totalSize;
415 } else {
416 // If not waiting it's finished
417 total = progress;
418 }
419 return true;
420 } else if (itW != pimpl_->waitingIds_.end()) {
421 total = itW->second.totalSize;
422 progress = 0;
423 return true;
424 }
425 // Else we don't know infos there.
426 progress = 0;
427 return false;
428}
429
430void
431TransferManager::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 std::unique_lock lk(pimpl_->mapMutex_);
438 auto itW = pimpl_->waitingIds_.find(fileId);
439 if (itW != pimpl_->waitingIds_.end())
440 return;
441 pimpl_->waitingIds_[fileId] = {fileId, interactionId, sha3sum, path, total};
442 pimpl_->saveWaiting();
443}
444
445void
446TransferManager::onIncomingFileTransfer(const std::string& fileId,
447 const std::shared_ptr<dhtnet::ChannelSocket>& channel,
448 size_t start)
449{
450 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 auto itC = pimpl_->incomings_.find(fileId);
453 if (itC != pimpl_->incomings_.end()) {
454 channel->shutdown();
455 return;
456 }
457 auto itW = pimpl_->waitingIds_.find(fileId);
458 if (itW == pimpl_->waitingIds_.end()) {
459 dht::ThreadPool().io().run([channel] {
460 channel->shutdown();
461 });
462 return;
463 }
464
466 info.accountId = pimpl_->accountId_;
467 info.conversationId = pimpl_->to_;
468 info.path = itW->second.path;
469 info.totalSize = itW->second.totalSize;
470 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 auto filePath = path(fileId);
476 if (info.path.empty()) {
477 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 fileutils::createFileLink(filePath, info.path);
482 }
483
484 auto ifile = std::make_shared<IncomingFile>(std::move(channel),
485 info,
486 fileId,
487 itW->second.interactionId,
488 itW->second.sha3sum);
489 auto res = pimpl_->incomings_.emplace(fileId, std::move(ifile));
490 if (res.second) {
491 res.first->second->onFinished([w = weak(), fileId](uint32_t code) {
492 // schedule destroy transfer as not needed
493 dht::ThreadPool().computation().run([w, fileId, code] {
494 if (auto sthis_ = w.lock()) {
495 auto& pimpl = sthis_->pimpl_;
496 std::lock_guard lk {pimpl->mapMutex_};
497 auto itO = pimpl->incomings_.find(fileId);
498 if (itO != pimpl->incomings_.end())
499 pimpl->incomings_.erase(itO);
500 if (code == uint32_t(libjami::DataTransferEventCode::finished)) {
501 auto itW = pimpl->waitingIds_.find(fileId);
502 if (itW != pimpl->waitingIds_.end()) {
503 pimpl->waitingIds_.erase(itW);
504 pimpl->saveWaiting();
505 }
506 }
507 }
508 });
509 });
510 res.first->second->process();
511 }
512}
513
514std::filesystem::path
515TransferManager::path(const std::string& fileId) const
516{
517 return pimpl_->conversationDataPath_ / fileId;
518}
519
520void
521TransferManager::onIncomingProfile(const std::shared_ptr<dhtnet::ChannelSocket>& channel,
522 const std::string& sha3Sum)
523{
524 if (!channel)
525 return;
526
527 auto chName = channel->name();
528 std::string_view name = chName;
529 auto sep = name.find_last_of('?');
530 if (sep != std::string::npos)
531 name = name.substr(0, sep);
532
533 auto lastSep = name.find_last_of('/');
534 auto fileId = name.substr(lastSep + 1);
535
536 auto deviceId = channel->deviceId().toString();
537 auto cert = channel->peerCertificate();
538 if (!cert || !cert->issuer || fileId.find(".vcf") == std::string::npos)
539 return;
540
541 auto uri = fileId == "profile.vcf" ? cert->issuer->getId().toString()
542 : std::string(fileId.substr(0, fileId.size() - 4 /*.vcf*/));
543
544 std::lock_guard lk(pimpl_->mapMutex_);
545 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 auto itV = pimpl_->vcards_.find(idx);
548 if (itV != pimpl_->vcards_.end()) {
549 channel->shutdown();
550 return;
551 }
552
553 auto tid = generateUID(pimpl_->rand_);
555 info.accountId = pimpl_->accountId_;
556 info.conversationId = pimpl_->to_;
557
558 auto recvDir = fileutils::get_cache_dir() / pimpl_->accountId_ / "vcard";
559 dhtnet::fileutils::recursive_mkdir(recvDir);
560 info.path = (recvDir / fmt::format("{:s}_{:s}_{}", deviceId, uri, tid)).string();
561
562 auto ifile = std::make_shared<IncomingFile>(std::move(channel), info, "profile.vcf", "", sha3Sum);
563 auto res = pimpl_->vcards_.emplace(idx, std::move(ifile));
564 if (res.second) {
565 res.first->second->onFinished([w = weak(),
566 uri = std::move(uri),
567 deviceId = std::move(deviceId),
568 accountId = pimpl_->accountId_,
569 cert = std::move(cert),
570 path = info.path](uint32_t code) {
571 dht::ThreadPool().computation().run([w,
572 uri = std::move(uri),
573 deviceId = std::move(deviceId),
574 accountId = std::move(accountId),
575 path = std::move(path),
576 code] {
577 if (auto sthis_ = w.lock()) {
578 auto& pimpl = sthis_->pimpl_;
579
580 auto destPath = sthis_->profilePath(uri);
581 try {
582 // Move profile to destination path
583 std::lock_guard lock(dhtnet::fileutils::getFileLock(destPath));
584 dhtnet::fileutils::recursive_mkdir(destPath.parent_path());
585 std::filesystem::rename(path, destPath);
586 if (!pimpl->accountUri_.empty() && uri == pimpl->accountUri_) {
587 // If this is the account profile, link or copy it to the account profile path
588 if (!fileutils::createFileLink(pimpl->accountProfilePath_, destPath)) {
589 std::error_code ec;
590 std::filesystem::copy_file(destPath, pimpl->accountProfilePath_, ec);
591 }
592 }
593 } catch (const std::exception& e) {
594 JAMI_ERROR("{}", e.what());
595 }
596
597 std::lock_guard lk {pimpl->mapMutex_};
598 auto itO = pimpl->vcards_.find({deviceId, uri});
599 if (itO != pimpl->vcards_.end())
600 pimpl->vcards_.erase(itO);
601 if (code == uint32_t(libjami::DataTransferEventCode::finished)) {
602 emitSignal<libjami::ConfigurationSignal::ProfileReceived>(accountId,
603 uri,
604 destPath.string());
605 }
606 }
607 });
608 });
609 res.first->second->process();
610 }
611}
612
613std::filesystem::path
614TransferManager::profilePath(const std::string& contactId) const
615{
616 return pimpl_->profilesPath_ / fmt::format("{}.vcf", base64::encode(contactId));
617}
618
619std::vector<WaitingRequest>
620TransferManager::waitingRequests() const
621{
622 std::vector<WaitingRequest> res;
623 std::lock_guard lk(pimpl_->mapMutex_);
624 for (const auto& [fileId, req] : pimpl_->waitingIds_) {
625 auto itC = pimpl_->incomings_.find(fileId);
626 if (itC == pimpl_->incomings_.end())
627 res.emplace_back(req);
628 }
629 return res;
630}
631
632bool
633TransferManager::isWaiting(const std::string& fileId) const
634{
635 std::lock_guard lk(pimpl_->mapMutex_);
636 return pimpl_->waitingIds_.find(fileId) != pimpl_->waitingIds_.end();
637}
638
639} // namespace jami
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:228
#define JAMI_DBG(...)
Definition logger.h:216
#define JAMI_WARNING(formatstr,...)
Definition logger.h:227
#define JAMI_LOG(formatstr,...)
Definition logger.h:225
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::function< void()> OnFinishedCb
void emitSignal(Args... args)
Definition ring_signal.h:64
libjami::DataTransferId generateUID(std::mt19937_64 &engine)
static constexpr uint64_t JAMI_ID_MAX_VAL
Definition account.h:62
static void runOnMainThread(Callback &&cb)
Definition manager.h:909
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)