From 14f286bab81ff2415afe9919531ee7337482b1b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Blin?= <sebastien.blin@savoirfairelinux.com> Date: Wed, 21 Apr 2021 11:58:55 -0400 Subject: [PATCH] datatransfer: implement downloadFile logic Add mechanism to re-ask for transfers and sendProfile with new logic GitLab: #524 Change-Id: I9f10328ff0e2bd9128e2d7d1afd7fa272dd14cdf --- .../cx.ring.Ring.ConfigurationManager.xml | 44 +- bin/dbus/dbusclient.cpp | 2 +- bin/dbus/dbusconfigurationmanager.cpp | 63 +- bin/dbus/dbusconfigurationmanager.h | 42 +- bin/jni/datatransfer.i | 40 +- bin/jni/jni_interface.i | 2 +- src/account.h | 9 - src/client/datatransfer.cpp | 94 +- src/data_transfer.cpp | 620 ++++++++++--- src/data_transfer.h | 187 +++- src/dring/datatransfer_interface.h | 47 +- src/fileutils.cpp | 32 +- src/fileutils.h | 2 + src/ftp_server.cpp | 2 +- src/jamidht/connectionmanager.cpp | 49 +- src/jamidht/connectionmanager.h | 9 +- src/jamidht/conversation.cpp | 138 ++- src/jamidht/conversation.h | 55 +- src/jamidht/jamiaccount.cpp | 874 ++++++++++-------- src/jamidht/jamiaccount.h | 114 ++- src/jamidht/multiplexed_socket.cpp | 6 +- src/sip/sipaccountbase.cpp | 34 +- src/sip/sipaccountbase.h | 1 - test/unitTest/conversation/compability.cpp | 10 +- test/unitTest/fileTransfer/fileTransfer.cpp | 758 ++++++++------- 25 files changed, 2108 insertions(+), 1126 deletions(-) diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml index 2d2ebcf5db..2425d163c6 100644 --- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml +++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml @@ -1584,29 +1584,38 @@ </method> <method name="sendFile" tp:name-for-bindings="sendFile"> + <tp:added version="10.0.0"/> + <arg type="s" name="accountId" direction="in"/> + <arg type="s" name="conversationId" direction="in"/> + <arg type="s" name="filePath" direction="in"/> + <arg type="s" name="fileDisplayName" direction="in"/> + <arg type="s" name="parent" direction="in"/> + </method> + + <method name="sendFileLegacy" tp:name-for-bindings="sendFileLegacy"> <tp:added version="10.0.0"/> <arg type="u" name="dataTransferError" direction="out"/> <arg type="(suuxxssssss)" name="DataTransferInfo" direction="in"/> - <arg type="t" name="dataTransferId" direction="out"/> <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="DataTransferInfo"/> + <arg type="t" name="dataTransferId" direction="out"/> </method> <method name="dataTransferInfo" tp:name-for-bindings="dataTransferInfo"> <tp:added version="10.0.0"/> <arg type="u" name="dataTransferError" direction="out"/> <arg type="s" name="accountId" direction="in"/> - <arg type="s" name="to" direction="in"/> - <arg type="t" name="dataTransferId" direction="in"/> + <arg type="s" name="fileId" direction="in"/> <arg type="(suuxxssssss)" name="dataTransferInfo" direction="out"/> <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="DataTransferInfo"/> </method> - <method name="dataTransferBytesProgress" tp:name-for-bindings="dataTransferBytesProgress"> + <method name="fileTransferInfo" tp:name-for-bindings="fileTransferInfo"> <tp:added version="10.0.0"/> <arg type="u" name="dataTransferError" direction="out"/> <arg type="s" name="accountId" direction="in"/> <arg type="s" name="to" direction="in"/> - <arg type="t" name="dataTransferId" direction="in"/> + <arg type="s" name="fileId" direction="in"/> + <arg type="s" name="path" direction="out"/> <arg type="x" name="totalSize" direction="out"/> <arg type="x" name="bytesProgress" direction="out"/> </method> @@ -1615,17 +1624,17 @@ <tp:added version="10.0.0"/> <arg type="u" name="dataTransferError" direction="out"/> <arg type="s" name="accountId" direction="in"/> - <arg type="s" name="to" direction="in"/> - <arg type="t" name="dataTransferId" direction="in"/> + <arg type="s" name="fileId" direction="in"/> <arg type="s" name="filePath" direction="in"/> - <arg type="x" name="offset" direction="in"/> </method> - <method name="askForTransfer" tp:name-for-bindings="askForTransfer"> + <method name="downloadFile" tp:name-for-bindings="downloadFile"> <tp:added version="10.1.0"/> + <arg type="b" name="result" direction="out"/> <arg type="s" name="accountId" direction="in"/> - <arg type="s" name="conversationUri" direction="in"/> + <arg type="s" name="conversationId" direction="in"/> <arg type="s" name="interactionId" direction="in"/> + <arg type="s" name="fileId" direction="in"/> <arg type="s" name="path" direction="in"/> </method> @@ -1634,7 +1643,7 @@ <arg type="u" name="dataTransferError" direction="out"/> <arg type="s" name="accountId" direction="in"/> <arg type="s" name="to" direction="in"/> - <arg type="t" name="dataTransferId" direction="in"/> + <arg type="s" name="fileId" direction="in"/> </method> <method name="monitor" tp:name-for-bindings="monitor"> @@ -1829,14 +1838,19 @@ An account id. </tp:docstring> </arg> - <arg type="s" name="to"> + <arg type="s" name="conversationId"> + <tp:docstring> + Conversation id (empty for non swarm) + </tp:docstring> + </arg> + <arg type="s" name="interactionId"> <tp:docstring> - The peer or conversation id. + Interaction id (empty for non swarm) </tp:docstring> </arg> - <arg type="t" name="id"> + <arg type="s" name="fileId"> <tp:docstring> - Data transfer unique id. + File transfer unique id. </tp:docstring> </arg> <arg type="i" name="code"> diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp index ea716ce0fb..c4fe96441d 100644 --- a/bin/dbus/dbusclient.cpp +++ b/bin/dbus/dbusclient.cpp @@ -293,7 +293,7 @@ DBusClient::initLibrary(int flags) const std::map<std::string, SharedCallback> dataXferEvHandlers = { exportable_callback<DataTransferSignal::DataTransferEvent>( - bind(&DBusConfigurationManager::dataTransferEvent, confM, _1, _2, _3, _4)), + bind(&DBusConfigurationManager::dataTransferEvent, confM, _1, _2, _3, _4, _5)), }; const std::map<std::string, SharedCallback> convEvHandlers = { diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp index 515907e47a..6ff5f7315c 100644 --- a/bin/dbus/dbusconfigurationmanager.cpp +++ b/bin/dbus/dbusconfigurationmanager.cpp @@ -745,9 +745,9 @@ DBusConfigurationManager::connectivityChanged() } void -DBusConfigurationManager::sendFile(const RingDBusDataTransferInfo& in, - uint32_t& error, - DRing::DataTransferId& id) +DBusConfigurationManager::sendFileLegacy(const RingDBusDataTransferInfo& in, + uint32_t& error, + DRing::DataTransferId& id) { DRing::DataTransferInfo info; info.accountId = in._1; @@ -761,18 +761,27 @@ DBusConfigurationManager::sendFile(const RingDBusDataTransferInfo& in, info.displayName = in._9; info.path = in._10; info.mimetype = in._11; - error = uint32_t(DRing::sendFile(info, id)); + error = uint32_t(DRing::sendFileLegacy(info, id)); +} + +void +DBusConfigurationManager::sendFile(const std::string& accountId, + const std::string& conversationId, + const std::string& path, + const std::string& displayName, + const std::string& parent) +{ + DRing::sendFile(accountId, conversationId, path, displayName, parent); } void DBusConfigurationManager::dataTransferInfo(const std::string& accountId, - const std::string& conversationId, - const DRing::DataTransferId& id, + const std::string& fileId, uint32_t& error, RingDBusDataTransferInfo& out) { DRing::DataTransferInfo info; - auto res = DRing::dataTransferInfo(accountId, conversationId, id, info); + auto res = DRing::dataTransferInfo(accountId, fileId, info); if (res == DRing::DataTransferError::success) { out._1 = info.accountId; out._2 = uint32_t(info.lastEvent); @@ -790,42 +799,42 @@ DBusConfigurationManager::dataTransferInfo(const std::string& accountId, } void -DBusConfigurationManager::dataTransferBytesProgress(const std::string& accountId, - const std::string& conversationId, - const uint64_t& id, - uint32_t& error, - int64_t& total, - int64_t& progress) +DBusConfigurationManager::fileTransferInfo(const std::string& accountId, + const std::string& conversationId, + const std::string& fileId, + uint32_t& error, + std::string& path, + int64_t& total, + int64_t& progress) { error = uint32_t( - DRing::dataTransferBytesProgress(accountId, conversationId, id, total, progress)); + DRing::fileTransferInfo(accountId, conversationId, fileId, path, total, progress)); } uint32_t DBusConfigurationManager::acceptFileTransfer(const std::string& accountId, - const std::string& conversationId, - const uint64_t& id, - const std::string& file_path, - const int64_t& offset) + const std::string& fileId, + const std::string& file_path) { - return uint32_t(DRing::acceptFileTransfer(accountId, conversationId, id, file_path, offset)); + return uint32_t(DRing::acceptFileTransfer(accountId, fileId, file_path)); } -void -DBusConfigurationManager::askForTransfer(const std::string& accountId, - const std::string& conversationUri, - const std::string& interactionId, - const std::string& path) +bool +DBusConfigurationManager::downloadFile(const std::string& accountId, + const std::string& conversationUri, + const std::string& interactionId, + const std::string& fileId, + const std::string& path) { - DRing::askForTransfer(accountId, conversationUri, interactionId, path); + return DRing::downloadFile(accountId, conversationUri, interactionId, fileId, path); } uint32_t DBusConfigurationManager::cancelDataTransfer(const std::string& accountId, const std::string& conversationId, - const uint64_t& id) + const std::string& fileId) { - return uint32_t(DRing::cancelDataTransfer(accountId, conversationId, id)); + return uint32_t(DRing::cancelDataTransfer(accountId, conversationId, fileId)); } std::string diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h index 5f7e7abb15..2228ba20b2 100644 --- a/bin/dbus/dbusconfigurationmanager.h +++ b/bin/dbus/dbusconfigurationmanager.h @@ -215,30 +215,36 @@ public: const std::string& uri); std::vector<std::map<std::string, std::string>> getContacts(const std::string& accountId); void connectivityChanged(); - void sendFile(const RingDBusDataTransferInfo& info, uint32_t& error, DRing::DataTransferId& id); + void sendFileLegacy(const RingDBusDataTransferInfo& info, + uint32_t& error, + DRing::DataTransferId& id); + void sendFile(const std::string& accountId, + const std::string& conversationId, + const std::string& path, + const std::string& displayName, + const std::string& parent); void dataTransferInfo(const std::string& accountId, - const std::string& conversationId, - const DRing::DataTransferId& id, + const std::string& fileId, uint32_t& error, RingDBusDataTransferInfo& info); - void dataTransferBytesProgress(const std::string& accountId, - const std::string& conversationId, - const uint64_t& id, - uint32_t& error, - int64_t& total, - int64_t& progress); + void fileTransferInfo(const std::string& accountId, + const std::string& conversationId, + const std::string& fileId, + uint32_t& error, + std::string& path, + int64_t& total, + int64_t& progress); uint32_t acceptFileTransfer(const std::string& accountId, - const std::string& conversationId, - const uint64_t& id, - const std::string& file_path, - const int64_t& offset); - void askForTransfer(const std::string& accountId, - const std::string& conversationUri, - const std::string& interactionId, - const std::string& path); + const std::string& fileId, + const std::string& file_path); + bool downloadFile(const std::string& accountId, + const std::string& conversationId, + const std::string& interactionId, + const std::string& fileId, + const std::string& path); uint32_t cancelDataTransfer(const std::string& accountId, const std::string& conversationId, - const uint64_t& id); + const std::string& fileId); bool isAudioMeterActive(const std::string& id); void setAudioMeterState(const std::string& id, const bool& state); void setDefaultModerator(const std::string& accountID, diff --git a/bin/jni/datatransfer.i b/bin/jni/datatransfer.i index 469e2e8793..cdc9693c14 100644 --- a/bin/jni/datatransfer.i +++ b/bin/jni/datatransfer.i @@ -33,12 +33,34 @@ class DataTransferCallback { public: virtual ~DataTransferCallback(){} - virtual void dataTransferEvent(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId transferId, int eventCode){} + virtual void dataTransferEvent(const std::string& accountId, const std::string& conversationId, const std::string& interactionId, const std::string& fileId, int eventCode){} }; %} %feature("director") DataTransferCallback; +%typemap(jstype) std::string& OUTPUT "String[]" +%typemap(jtype) std::string& OUTPUT "String[]" +%typemap(jni) std::string& OUTPUT "jobjectArray" +%typemap(javain) std::string& OUTPUT "$javainput" +%typemap(in) std::string& OUTPUT (std::string temp) { + if (!$input) { + SWIG_JavaThrowException(jenv, SWIG_JavaNullPointerException, "array null"); + return $null; + } + if (JCALL1(GetArrayLength, jenv, $input) == 0) { + SWIG_JavaThrowException(jenv, SWIG_JavaIndexOutOfBoundsException, "Array must contain at least 1 element"); + } + $1 = &temp; +} +%typemap(argout) std::string& OUTPUT { + jstring jvalue = JCALL1(NewStringUTF, jenv, temp$argnum.c_str()); + JCALL3(SetObjectArrayElement, jenv, $input, 0, jvalue); +} +%apply std::string& OUTPUT { std::string& path_out } +%apply int64_t& OUTPUT { int64_t& total_out } +%apply int64_t& OUTPUT { int64_t& progress_out } + namespace DRing { struct DataTransferInfo @@ -56,17 +78,19 @@ namespace DRing { std::string mimetype; }; - DRing::DataTransferError sendFile(const DRing::DataTransferInfo info, DRing::DataTransferId& id); - DRing::DataTransferError acceptFileTransfer(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId id, const std::string file_path, int64_t offset); - void askForTransfer(const std::string& accountId, const std::string& conversationUri, const std::string& interactionId, const std::string& path); - DRing::DataTransferError cancelDataTransfer(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId id); - DRing::DataTransferError dataTransferInfo(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId id, DRing::DataTransferInfo &info); - DRing::DataTransferError dataTransferBytesProgress(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId id, int64_t &total, int64_t &progress); + void sendFile(const std::string& accountId, const std::string& conversationId, const std::string& path, const std::string& displayName, const std::string& parent); + + DRing::DataTransferError sendFileLegacy(const DRing::DataTransferInfo info, DRing::DataTransferId& id); + DRing::DataTransferError acceptFileTransfer(const std::string& accountId, const std::string& fileId, const std::string& file_path); + uint64_t downloadFile(const std::string& accountId, const std::string& conversationId, const std::string& interactionId,const std::string& fileId, const std::string& path); + DRing::DataTransferError cancelDataTransfer(const std::string& accountId, const std::string& conversationId, const std::string& fileId); + DRing::DataTransferError dataTransferInfo(const std::string& accountId, const std::string& fileId, DRing::DataTransferInfo &info); + DRing::DataTransferError fileTransferInfo(const std::string& accountId, const std::string& conversationId, const std::string& fileId, std::string &path, int64_t &total, int64_t &progress); } class DataTransferCallback { public: virtual ~DataTransferCallback(){} - virtual void dataTransferEvent(const std::string& accountId, const std::string& conversationId, const DRing::DataTransferId transferId, int eventCode){} + virtual void dataTransferEvent(const std::string& accountId, const std::string& conversationId, const std::string& interactionId, const std::string& fileId, int eventCode){} }; diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i index 0c3e2dd30e..2df38879e2 100644 --- a/bin/jni/jni_interface.i +++ b/bin/jni/jni_interface.i @@ -302,7 +302,7 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM }; const std::map<std::string, SharedCallback> dataTransferEvHandlers = { - exportable_callback<DataTransferSignal::DataTransferEvent>(bind(&DataTransferCallback::dataTransferEvent, dataM, _1, _2, _3, _4)), + exportable_callback<DataTransferSignal::DataTransferEvent>(bind(&DataTransferCallback::dataTransferEvent, dataM, _1, _2, _3, _4, _5)), }; const std::map<std::string, SharedCallback> videoEvHandlers = { diff --git a/src/account.h b/src/account.h index 895efdf432..6aa17129d5 100644 --- a/src/account.h +++ b/src/account.h @@ -181,10 +181,6 @@ public: virtual void setIsComposing(const std::string& /*conversationUri*/, bool /*isWriting*/) {}; - virtual void askForTransfer(const std::string& /*conversationUri*/, - const std::string& /*interactionId*/, - const std::string& /*path*/) {}; - virtual void onIsComposing(const std::string& /*conversationId*/, const std::string& /*peer*/, bool /*isWriting*/); @@ -322,11 +318,6 @@ public: const std::string& /*conversationId*/, const std::string& /*commitId*/) {}; - virtual void onAskForTransfer(const std::string& /*peer*/, - const std::string& /*deviceId*/, - const std::string& /*conversationId*/, - const std::string& /*interactionId*/) {}; - // Invites virtual void onConversationRequest(const std::string& /*from*/, const Json::Value&) {}; virtual void onNeedConversationRequest(const std::string& /*from*/, diff --git a/src/client/datatransfer.cpp b/src/client/datatransfer.cpp index 230125f096..7383d6e718 100644 --- a/src/client/datatransfer.cpp +++ b/src/client/datatransfer.cpp @@ -34,80 +34,104 @@ registerDataXferHandlers(const std::map<std::string, std::shared_ptr<CallbackWra } DataTransferError -sendFile(const DataTransferInfo& info, DataTransferId& id) noexcept +sendFileLegacy(const DataTransferInfo& info, DataTransferId& tid) noexcept { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(info.accountId)) { - auto to = info.conversationId; - if (to.empty()) - to = info.peer; - id = acc->sendFile(to, info.path); - if (id != 0) - return DRing::DataTransferError::success; + tid = acc->sendFile(info.peer, info.path); + return DRing::DataTransferError::success; } + return DRing::DataTransferError::invalid_argument; } +void +sendFile(const std::string& accountId, + const std::string& conversationId, + const std::string& path, + const std::string& displayName, + const std::string& parent) noexcept +{ + if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) { + acc->sendFile(conversationId, path, displayName, parent); + } +} + DataTransferError acceptFileTransfer(const std::string& accountId, - const std::string& conversationId, - const DataTransferId& id, - const std::string& file_path, - int64_t offset) noexcept + const std::string& fileId, + const std::string& file_path) noexcept { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) { - return acc->acceptFile(conversationId, id, file_path, offset) - ? DRing::DataTransferError::success - : DRing::DataTransferError::invalid_argument; + if (auto dt = acc->dataTransfer()) { + try { + return dt->acceptFile(std::stoull(fileId), file_path) + ? DRing::DataTransferError::success + : DRing::DataTransferError::invalid_argument; + } catch (...) { + JAMI_ERR() << "Invalid file Id" << fileId; + } + } } return DRing::DataTransferError::invalid_argument; } -void -askForTransfer(const std::string& accountId, - const std::string& conversationUri, - const std::string& interactionId, - const std::string& path) noexcept +bool +downloadFile(const std::string& accountId, + const std::string& conversationId, + const std::string& interactionId, + const std::string& fileId, + const std::string& path) noexcept { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - acc->askForTransfer(conversationUri, interactionId, path); + return acc->downloadFile(conversationId, interactionId, fileId, path); + return {}; } DataTransferError cancelDataTransfer(const std::string& accountId, const std::string& conversationId, - const DataTransferId& id) noexcept + const std::string& fileId) noexcept { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) { - return acc->cancel(conversationId, id) ? DRing::DataTransferError::success - : DRing::DataTransferError::invalid_argument; + if (auto dt = acc->dataTransfer(conversationId)) + return dt->cancel(fileId) ? DRing::DataTransferError::success + : DRing::DataTransferError::invalid_argument; } return DRing::DataTransferError::invalid_argument; } DataTransferError -dataTransferBytesProgress(const std::string& accountId, - const std::string& conversationId, - const DataTransferId& id, - int64_t& total, - int64_t& progress) noexcept +fileTransferInfo(const std::string& accountId, + const std::string& conversationId, + const std::string& fileId, + std::string& path, + int64_t& total, + int64_t& progress) noexcept { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) { - return acc->bytesProgress(conversationId, id, total, progress) - ? DRing::DataTransferError::success - : DRing::DataTransferError::invalid_argument; + if (auto dt = acc->dataTransfer(conversationId)) + return dt->info(fileId, path, total, progress) + ? DRing::DataTransferError::success + : DRing::DataTransferError::invalid_argument; } return DRing::DataTransferError::invalid_argument; } DataTransferError dataTransferInfo(const std::string& accountId, - const std::string& conversationId, - const DataTransferId& id, + const std::string& fileId, DataTransferInfo& info) noexcept { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) { - return acc->info(conversationId, id, info) ? DRing::DataTransferError::success - : DRing::DataTransferError::invalid_argument; + if (auto dt = acc->dataTransfer()) { + try { + return dt->info(std::stoull(fileId), info) + ? DRing::DataTransferError::success + : DRing::DataTransferError::invalid_argument; + } catch (...) { + JAMI_ERR() << "Invalid fileId: " << fileId; + } + } } return DRing::DataTransferError::invalid_argument; } diff --git a/src/data_transfer.cpp b/src/data_transfer.cpp index efe706bad4..c977b542ba 100644 --- a/src/data_transfer.cpp +++ b/src/data_transfer.cpp @@ -39,14 +39,16 @@ #include <mutex> #include <future> #include <atomic> -#include <cstdlib> // mkstemp +#include <charconv> // std::from_chars +#include <cstdlib> // mkstemp +#include <filesystem> #include <opendht/rng.h> #include <opendht/thread_pool.h> namespace jami { -static DRing::DataTransferId +DRing::DataTransferId generateUID() { thread_local dht::crypto::random_device rd; @@ -143,7 +145,11 @@ DataTransfer::emit(DRing::DataTransferEventCode code) const if (internalCompletionCb_) return; // VCard transfer is just for the daemon runOnMainThread([id = id, code, accountId, to]() { - emitSignal<DRing::DataTransferSignal::DataTransferEvent>(accountId, to, id, uint32_t(code)); + emitSignal<DRing::DataTransferSignal::DataTransferEvent>(accountId, + "", + "", + std::to_string(id), + uint32_t(code)); }); } @@ -319,10 +325,11 @@ private: void sendHeader() const { - auto header = fmt::format( - "Content-Length: {}\n" - "Display-Name: {}\n" - "Offset: 0\n\n", info_.totalSize, info_.displayName); + auto header = fmt::format("Content-Length: {}\n" + "Display-Name: {}\n" + "Offset: 0\n\n", + info_.totalSize, + info_.displayName); headerSent_ = true; emit(DRing::DataTransferEventCode::wait_peer_acceptance); if (onRecvCb_) @@ -376,7 +383,7 @@ SubOutgoingFileTransfer::SubOutgoingFileTransfer(DRing::DataTransferId tid, , peerUri_(peerUri) { info_ = metaInfo_->info(); - fileutils::openStream(input_, info_.path, std::ios::binary); + fileutils::openStream(input_, info_.path, std::ios::in | std::ios::binary); if (!input_) throw std::runtime_error("input file open failed"); metaInfo_->addLinkedTransfer(this); @@ -549,8 +556,6 @@ public: void accept(const std::string&, std::size_t offset) override; - void setVerifyShaSum(const OnVerifyCb& vcb) { vcb_ = std::move(vcb); } - bool write(std::string_view data) override; void setFilename(const std::string& filename); @@ -566,7 +571,6 @@ private: IncomingFileTransfer() = delete; DRing::DataTransferId internalId_; - OnVerifyCb vcb_ {}; std::ofstream fout_; std::mutex cbMtx_ {}; @@ -650,14 +654,6 @@ IncomingFileTransfer::close() noexcept JAMI_DBG() << "[FTP] file closed, rx " << info_.bytesProgress << " on " << info_.totalSize; if (info_.bytesProgress >= info_.totalSize) { - if (vcb_) { - if (!vcb_(fileutils::sha3File(info_.path))) { - JAMI_DBG() << "[FTP] Incorrect sha3sum - erasing " << info_.path; - fileutils::remove(info_.path, true); - emit(DRing::DataTransferEventCode::invalid); - return; - } - } if (internalCompletionCb_) internalCompletionCb_(info_.path); emit(DRing::DataTransferEventCode::finished); @@ -695,45 +691,251 @@ IncomingFileTransfer::write(std::string_view buffer) } //============================================================================== +// With Swarm +//============================================================================== + +FileInfo::FileInfo(const std::shared_ptr<ChannelSocket>& channel, + const std::string& fileId, + const std::string& interactionId, + const DRing::DataTransferInfo& info) + : fileId_(fileId) + , interactionId_(interactionId) + , info_(info) + , channel_(channel) +{} + +void +FileInfo::emit(DRing::DataTransferEventCode code) +{ + if (finishedCb_ && code >= DRing::DataTransferEventCode::finished) + finishedCb_(uint32_t(code)); + if (fileId_ != "profile.vcf") { + // Else it's an internal transfer + runOnMainThread([info = info_, iid = interactionId_, fid = fileId_, code]() { + emitSignal<DRing::DataTransferSignal::DataTransferEvent>(info.accountId, + info.conversationId, + iid, + fid, + uint32_t(code)); + }); + } +} -struct WaitingRequest +OutgoingFile::OutgoingFile(const std::shared_ptr<ChannelSocket>& channel, + const std::string& fileId, + const std::string& interactionId, + const DRing::DataTransferInfo& info, + size_t start, + size_t end) + : FileInfo(channel, fileId, interactionId, info) + , start_(start) + , end_(end) { - std::string sha3sum; - std::string path; -}; + if (!fileutils::isFile(info_.path)) { + channel_->shutdown(); + return; + } + fileutils::openStream(stream_, info_.path, std::ios::binary | std::ios::in); + if (!stream_ || !stream_.is_open()) { + channel_->shutdown(); + return; + } +} + +OutgoingFile::~OutgoingFile() +{ + if (stream_ && stream_.is_open()) + stream_.close(); + if (channel_) + channel_->shutdown(); +} + +void +OutgoingFile::process() +{ + if (!channel_ or !stream_ or !stream_.is_open()) + return; + auto correct = false; + stream_.seekg(start_, std::ios::beg); + try { + std::vector<char> buffer(UINT16_MAX, 0); + std::error_code ec; + auto pos = start_; + while (!stream_.eof()) { + stream_.read(buffer.data(), + end_ > start_ ? std::min(end_ - pos, buffer.size()) : buffer.size()); + auto gcount = stream_.gcount(); + pos += gcount; + channel_->write(reinterpret_cast<const uint8_t*>(buffer.data()), gcount, ec); + if (ec) + break; + } + if (!ec) + correct = true; + stream_.close(); + } catch (...) { + } + if (!isUserCancelled_) { + auto code = correct ? DRing::DataTransferEventCode::finished + : DRing::DataTransferEventCode::closed_by_peer; + emit(code); + } +} + +void +OutgoingFile::cancel() +{ + // Remove link, not original file + auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + "conversation_data" + + DIR_SEPARATOR_STR + info_.accountId + DIR_SEPARATOR_STR + info_.conversationId + + DIR_SEPARATOR_STR + fileId_; + if (fileutils::isSymLink(path)) + fileutils::remove(path); + isUserCancelled_ = true; + emit(DRing::DataTransferEventCode::closed_by_host); +} + +IncomingFile::IncomingFile(const std::shared_ptr<ChannelSocket>& channel, + const DRing::DataTransferInfo& info, + const std::string& fileId, + const std::string& interactionId, + const std::string& sha3Sum) + : FileInfo(channel, fileId, interactionId, info) + , sha3Sum_(sha3Sum) +{ + fileutils::openStream(stream_, info_.path, std::ios::binary | std::ios::out); + if (!stream_) + return; + + emit(DRing::DataTransferEventCode::ongoing); +} + +IncomingFile::~IncomingFile() +{ + if (channel_) + channel_->setOnRecv({}); + if (stream_ && stream_.is_open()) + stream_.close(); + if (channel_) + channel_->shutdown(); +} + +void +IncomingFile::cancel() +{ + isUserCancelled_ = true; + emit(DRing::DataTransferEventCode::closed_by_peer); + if (channel_) + channel_->shutdown(); +} + +void +IncomingFile::process() +{ + channel_->setOnRecv([this](const uint8_t* buf, size_t len) { + if (stream_.is_open()) + stream_ << std::string_view((const char*) buf, len); + info_.bytesProgress = stream_.tellp(); + return len; + }); + channel_->onShutdown([this] { + auto correct = sha3Sum_.empty(); + if (!correct) { + if (stream_ && stream_.is_open()) + stream_.close(); + // Verify shaSum + auto sha3Sum = fileutils::sha3File(info_.path); + if (sha3Sum_ == sha3Sum) { + JAMI_INFO() << "New file received: " << info_.path; + correct = true; + } else { + JAMI_WARN() << "Remove file, invalid sha3sum detected for " << info_.path; + fileutils::remove(info_.path, true); + } + } + if (isUserCancelled_) + return; + auto code = correct ? DRing::DataTransferEventCode::finished + : DRing::DataTransferEventCode::closed_by_host; + emit(code); + }); +} + +//============================================================================== class TransferManager::Impl { public: - Impl(const std::string& accountId, const std::string& to, bool isConversation) + Impl(const std::string& accountId, const std::string& to) : accountId_(accountId) , to_(to) - , isConversation_(isConversation) - {} + { + if (!to_.empty()) { + conversationDataPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId_ + + DIR_SEPARATOR_STR + "conversation_data" + DIR_SEPARATOR_STR + + to_; + fileutils::check_dir(conversationDataPath_.c_str()); + waitingPath_ = conversationDataPath_ + DIR_SEPARATOR_STR + "waiting"; + } + loadWaiting(); + } + + ~Impl() + { + std::lock_guard<std::mutex> lk {mapMutex_}; + for (const auto& [channel, _of] : outgoings_) { + channel->shutdown(); + } + outgoings_.clear(); + incomings_.clear(); + vcards_.clear(); + } + + void loadWaiting() + { + try { + // read file + auto file = fileutils::loadFile(waitingPath_); + // load values + msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size()); + std::lock_guard<std::mutex> lk {mapMutex_}; + oh.get().convert(waitingIds_); + } catch (const std::exception& e) { + return; + } + } + void saveWaiting() + { + std::ofstream file(waitingPath_, std::ios::trunc | std::ios::binary); + msgpack::pack(file, waitingIds_); + } std::string accountId_ {}; std::string to_ {}; - bool isConversation_ {true}; + std::string waitingPath_ {}; + std::string conversationDataPath_ {}; - std::mutex mapMutex_ {}; + // Pre swarm std::map<DRing::DataTransferId, std::shared_ptr<OutgoingFileTransfer>> oMap_ {}; std::map<DRing::DataTransferId, std::shared_ptr<IncomingFileTransfer>> iMap_ {}; - std::map<DRing::DataTransferId, WaitingRequest> waitingIds_ {}; + + std::mutex mapMutex_ {}; + std::map<std::string, WaitingRequest> waitingIds_ {}; + std::map<std::shared_ptr<ChannelSocket>, std::shared_ptr<OutgoingFile>> outgoings_ {}; + std::map<std::string, std::shared_ptr<IncomingFile>> incomings_ {}; + std::map<std::string, std::shared_ptr<IncomingFile>> vcards_ {}; }; -TransferManager::TransferManager(const std::string& accountId, - const std::string& to, - bool isConversation) - : pimpl_ {std::make_unique<Impl>(accountId, to, isConversation)} +TransferManager::TransferManager(const std::string& accountId, const std::string& to) + : pimpl_ {std::make_unique<Impl>(accountId, to)} {} TransferManager::~TransferManager() {} DRing::DataTransferId TransferManager::sendFile(const std::string& path, - const InternalCompletionCb& icb, - const std::string& deviceId, - DRing::DataTransferId resendId) + const std::string& peer, + const InternalCompletionCb& icb) { // IMPLEMENTATION NOTE: requestPeerConnection() may call the given callback a multiple time. // This happen when multiple agents handle communications of the given peer for the given @@ -743,17 +945,14 @@ TransferManager::sendFile(const std::string& path, return {}; } - auto tid = resendId ? resendId : generateUID(); + auto tid = generateUID(); std::size_t found = path.find_last_of(DIR_SEPARATOR_CH); auto filename = path.substr(found + 1); DRing::DataTransferInfo info; info.accountId = pimpl_->accountId_; info.author = account->getUsername(); - if (pimpl_->isConversation_) { - info.conversationId = pimpl_->to_; - } else - info.peer = pimpl_->to_; + info.peer = peer; info.path = path; info.displayName = filename; info.bytesProgress = 0; @@ -791,8 +990,7 @@ TransferManager::sendFile(const std::string& path, transfer->cancel(); transfer->close(); } - }, - !resendId /* only add to history if we not resend a file */); + }); } catch (const std::exception& ex) { JAMI_ERR() << "[XFER] exception during sendFile(): " << ex.what(); return {}; @@ -814,21 +1012,71 @@ TransferManager::acceptFile(const DRing::DataTransferId& id, const std::string& return true; } +void +TransferManager::transferFile(const std::shared_ptr<ChannelSocket>& channel, + const std::string& fileId, + const std::string& interactionId, + const std::string& path, + size_t start, + size_t end) +{ + std::lock_guard<std::mutex> lk {pimpl_->mapMutex_}; + if (pimpl_->outgoings_.find(channel) != pimpl_->outgoings_.end()) + return; + DRing::DataTransferInfo info; + info.accountId = pimpl_->accountId_; + info.conversationId = pimpl_->to_; + info.path = path; + auto f = std::make_shared<OutgoingFile>(channel, fileId, interactionId, info, start, end); + f->onFinished([w = weak(), channel](uint32_t) { + // schedule destroy outgoing transfer as not needed + dht::ThreadPool().computation().run([w, channel] { + if (auto sthis_ = w.lock()) { + auto& pimpl = sthis_->pimpl_; + std::lock_guard<std::mutex> lk {pimpl->mapMutex_}; + auto itO = pimpl->outgoings_.find(channel); + if (itO != pimpl->outgoings_.end()) + pimpl->outgoings_.erase(itO); + } + }); + }); + pimpl_->outgoings_.emplace(channel, f); + dht::ThreadPool::io().run([w = std::weak_ptr<OutgoingFile>(f)] { + if (auto of = w.lock()) + of->process(); + }); +} + bool -TransferManager::cancel(const DRing::DataTransferId& id) +TransferManager::cancel(const std::string& fileId) { + std::shared_ptr<ChannelSocket> channel; std::lock_guard<std::mutex> lk {pimpl_->mapMutex_}; - auto it = pimpl_->iMap_.find(id); - if (it != pimpl_->iMap_.end()) { - if (it->second) - it->second->close(); + if (!pimpl_->to_.empty()) { + // Note: For now, there is no cancel for outgoings. + // The client can just remove the file. + auto itC = pimpl_->incomings_.find(fileId); + if (itC == pimpl_->incomings_.end()) + return false; + itC->second->cancel(); return true; } - auto itO = pimpl_->oMap_.find(id); - if (itO != pimpl_->oMap_.end()) { - if (itO->second) - itO->second->close(); - return true; + // Else, this is fallack. + try { + auto it = pimpl_->iMap_.find(std::stoull(fileId)); + if (it != pimpl_->iMap_.end()) { + if (it->second) + it->second->close(); + return true; + } + auto itO = pimpl_->oMap_.find(std::stoull(fileId)); + if (itO != pimpl_->oMap_.end()) { + if (itO->second) + itO->second->close(); + return true; + } + } catch (...) { + JAMI_ERR() << "Invalid fileId: " << fileId; } return false; } @@ -836,40 +1084,58 @@ TransferManager::cancel(const DRing::DataTransferId& id) bool TransferManager::info(const DRing::DataTransferId& id, DRing::DataTransferInfo& info) const noexcept { - std::lock_guard<std::mutex> lk {pimpl_->mapMutex_}; - auto it = pimpl_->iMap_.find(id); - if (it != pimpl_->iMap_.end()) { + std::unique_lock<std::mutex> lk {pimpl_->mapMutex_}; + if (!pimpl_->to_.empty()) + return false; + // Else it's fallback + if (auto it = pimpl_->iMap_.find(id); it != pimpl_->iMap_.end()) { if (it->second) it->second->info(info); return true; } - auto itO = pimpl_->oMap_.find(id); - if (itO != pimpl_->oMap_.end()) { - if (itO->second) - itO->second->info(info); + if (auto it = pimpl_->oMap_.find(id); it != pimpl_->oMap_.end()) { + if (it->second) + it->second->info(info); return true; } return false; } bool -TransferManager::bytesProgress(const DRing::DataTransferId& id, - int64_t& total, - int64_t& progress) const noexcept +TransferManager::info(const std::string& fileId, + std::string& path, + int64_t& total, + int64_t& progress) const noexcept { - std::lock_guard<std::mutex> lk {pimpl_->mapMutex_}; - auto it = pimpl_->iMap_.find(id); - if (it != pimpl_->iMap_.end()) { - if (it->second) - it->second->bytesProgress(total, progress); + std::unique_lock<std::mutex> lk {pimpl_->mapMutex_}; + if (pimpl_->to_.empty()) + return false; + + auto itI = pimpl_->incomings_.find(fileId); + auto itW = pimpl_->waitingIds_.find(fileId); + path = this->path(fileId); + if (itI != pimpl_->incomings_.end()) { + total = itI->second->info().totalSize; + progress = itI->second->info().bytesProgress; return true; - } - auto itO = pimpl_->oMap_.find(id); - if (itO != pimpl_->oMap_.end()) { - if (itO->second) - itO->second->bytesProgress(total, progress); + } else if (fileutils::isFile(path)) { + std::ifstream transfer(path, std::ios::binary); + transfer.seekg(0, std::ios::end); + progress = transfer.tellg(); + if (itW != pimpl_->waitingIds_.end()) { + total = itW->second.totalSize; + } else { + // If not waiting it's finished + total = progress; + } + return true; + } else if (itW != pimpl_->waitingIds_.end()) { + total = itW->second.totalSize; + progress = 0; return true; } + // Else we don't know infos there. + progress = 0; return false; } @@ -879,65 +1145,175 @@ TransferManager::onIncomingFileRequest(const DRing::DataTransferInfo& info, const std::function<void(const IncomingFileInfo&)>& cb, const InternalCompletionCb& icb) { - std::string filename; - std::shared_ptr<IncomingFileTransfer> transfer; + auto transfer = std::make_shared<IncomingFileTransfer>(info, id, icb); { std::lock_guard<std::mutex> lk {pimpl_->mapMutex_}; - auto it = pimpl_->iMap_.find(id); - if (it != pimpl_->iMap_.end()) { - // If the transfer is already in progress (aka not finished) - // we do not need to accept the request and can ignore it. - if (!it->second->isFinished()) { - JAMI_DBG("Declining request for %lu. Already downloading the file", id); - if (cb) - cb({id, nullptr}); - return; - } - // Else, we can handle a new incoming transfer - pimpl_->iMap_.erase(it); - } - // If we wait this id, we can accept it - auto itW = pimpl_->waitingIds_.find(id); - if (itW != pimpl_->waitingIds_.end()) - filename = itW->second.path; - transfer = std::make_shared<IncomingFileTransfer>(info, id, icb); - auto p = pimpl_->iMap_.emplace(id, transfer); - } - - transfer->setVerifyShaSum([this, id](const std::string& sha3sum) { - std::lock_guard<std::mutex> lk(pimpl_->mapMutex_); - auto it = pimpl_->waitingIds_.find(id); - if (it == pimpl_->waitingIds_.end()) - return true; // No validation to do - auto res = it->second.sha3sum == sha3sum; - pimpl_->waitingIds_.erase(it); - return res; - }); + pimpl_->iMap_.emplace(id, transfer); + } transfer->emit(DRing::DataTransferEventCode::created); - if (!filename.empty()) { - transfer->setFilename(filename); - if (transfer->start()) { - JAMI_DBG("Accepting request for %lu. Download %s", id, filename.c_str()); - if (cb) - cb({id, std::static_pointer_cast<Stream>(transfer)}); - } - } else { - transfer->requestFilename([transfer, id, cb = std::move(cb)](const std::string& filename) { - if (!filename.empty() && transfer->start()) - cb({id, std::static_pointer_cast<Stream>(transfer)}); - else - cb({id, nullptr}); + transfer->requestFilename([transfer, id, cb = std::move(cb)](const std::string& filename) { + if (!filename.empty() && transfer->start()) + cb({id, std::static_pointer_cast<Stream>(transfer)}); + else + cb({id, nullptr}); + }); +} + +void +TransferManager::waitForTransfer(const std::string& fileId, + const std::string& interactionId, + const std::string& sha3sum, + const std::string& path, + std::size_t total) +{ + std::unique_lock<std::mutex> lk(pimpl_->mapMutex_); + auto itW = pimpl_->waitingIds_.find(fileId); + if (itW != pimpl_->waitingIds_.end()) + return; + pimpl_->waitingIds_[fileId] = {fileId, interactionId, sha3sum, path, total}; + JAMI_DBG() << "Wait for " << fileId; + if (!pimpl_->to_.empty()) + pimpl_->saveWaiting(); + lk.unlock(); + emitSignal<DRing::DataTransferSignal::DataTransferEvent>( + pimpl_->accountId_, + pimpl_->to_, + interactionId, + fileId, + uint32_t(DRing::DataTransferEventCode::wait_peer_acceptance)); +} + +void +TransferManager::onIncomingFileTransfer(const std::string& fileId, + const std::shared_ptr<ChannelSocket>& channel) +{ + std::lock_guard<std::mutex> lk(pimpl_->mapMutex_); + // Check if not already an incoming file for this id and that we are waiting this file + auto itC = pimpl_->incomings_.find(fileId); + if (itC != pimpl_->incomings_.end()) { + channel->shutdown(); + return; + } + auto itW = pimpl_->waitingIds_.find(fileId); + if (itW == pimpl_->waitingIds_.end()) { + channel->shutdown(); + return; + } + + auto symlinkPath = path(fileId); + + DRing::DataTransferInfo info; + info.accountId = pimpl_->accountId_; + info.conversationId = pimpl_->to_; + info.path = itW->second.path; + if (info.path.empty()) + info.path = symlinkPath; + info.totalSize = itW->second.totalSize; + info.bytesProgress = 0; + + // Create symlink for future transfers + if (info.path != symlinkPath && !fileutils::isSymLink(symlinkPath)) + fileutils::createSymLink(symlinkPath, info.path); + + auto ifile = std::make_shared<IncomingFile>(std::move(channel), + info, + fileId, + itW->second.interactionId, + itW->second.sha3sum); + auto res = pimpl_->incomings_.emplace(fileId, std::move(ifile)); + if (res.second) { + res.first->second->onFinished([w = weak(), fileId](uint32_t code) { + // schedule destroy transfer as not needed + dht::ThreadPool().computation().run([w, fileId, code] { + if (auto sthis_ = w.lock()) { + auto& pimpl = sthis_->pimpl_; + std::lock_guard<std::mutex> lk {pimpl->mapMutex_}; + auto itO = pimpl->incomings_.find(fileId); + if (itO != pimpl->incomings_.end()) + pimpl->incomings_.erase(itO); + if (code == uint32_t(DRing::DataTransferEventCode::finished)) { + auto itW = pimpl->waitingIds_.find(fileId); + if (itW != pimpl->waitingIds_.end()) { + pimpl->waitingIds_.erase(itW); + pimpl->saveWaiting(); + } + } + } + }); }); + res.first->second->process(); } } +std::string +TransferManager::path(const std::string& fileId) const +{ + return pimpl_->conversationDataPath_ + DIR_SEPARATOR_STR + fileId; +} + void -TransferManager::waitForTransfer(const DRing::DataTransferId& id, - const std::string& sha3sum, - const std::string& path) +TransferManager::onIncomingProfile(const std::shared_ptr<ChannelSocket>& channel) +{ + if (!channel) + return; + auto deviceId = channel->deviceId().toString(); + std::lock_guard<std::mutex> lk(pimpl_->mapMutex_); + // Check if not already an incoming file for this id and that we are waiting this file + auto itV = pimpl_->vcards_.find(deviceId); + if (itV != pimpl_->vcards_.end()) { + channel->shutdown(); + return; + } + + DRing::DataTransferInfo info; + info.accountId = pimpl_->accountId_; + info.conversationId = pimpl_->to_; + info.path = fileutils::get_cache_dir() + DIR_SEPARATOR_STR + pimpl_->accountId_ + + DIR_SEPARATOR_STR + "vcard" + DIR_SEPARATOR_STR + deviceId; + + auto ifile = std::make_shared<IncomingFile>(std::move(channel), info, "profile.vcf", ""); + auto res = pimpl_->vcards_.emplace(deviceId, std::move(ifile)); + if (res.second) { + res.first->second->onFinished([w = weak(), + deviceId = std::move(deviceId), + accountId = pimpl_->accountId_, + path = info.path](uint32_t code) { + // schedule destroy transfer as not needed + dht::ThreadPool().computation().run([w, + deviceId = std::move(deviceId), + accountId = std::move(accountId), + path = std::move(path), + code] { + if (auto sthis_ = w.lock()) { + auto& pimpl = sthis_->pimpl_; + std::lock_guard<std::mutex> lk {pimpl->mapMutex_}; + auto itO = pimpl->vcards_.find(deviceId); + if (itO != pimpl->vcards_.end()) + pimpl->vcards_.erase(itO); + if (code == uint32_t(DRing::DataTransferEventCode::finished)) { + auto cert = tls::CertificateStore::instance().getCertificate(deviceId); + if (!cert) + return emitSignal<DRing::ConfigurationSignal::ProfileReceived>( + accountId, cert->getIssuerUID(), path); + } + } + }); + }); + res.first->second->process(); + } +} + +std::vector<WaitingRequest> +TransferManager::waitingRequests() const { + std::vector<WaitingRequest> res; std::lock_guard<std::mutex> lk(pimpl_->mapMutex_); - pimpl_->waitingIds_[id] = {sha3sum, path}; + for (const auto& [fileId, req] : pimpl_->waitingIds_) { + auto itC = pimpl_->incomings_.find(fileId); + if (itC == pimpl_->incomings_.end()) + res.emplace_back(req); + } + return res; } } // namespace jami diff --git a/src/data_transfer.h b/src/data_transfer.h index 75c6be8c6d..9b7aae9054 100644 --- a/src/data_transfer.h +++ b/src/data_transfer.h @@ -21,13 +21,18 @@ #pragma once #include "dring/datatransfer_interface.h" +#include "jamidht/multiplexed_socket.h" #include "noncopyable.h" #include <memory> #include <string> +#include <fstream> +#include <optional> namespace jami { +DRing::DataTransferId generateUID(); + class Stream; struct IncomingFileInfo @@ -36,41 +41,115 @@ struct IncomingFileInfo std::shared_ptr<Stream> stream; }; +struct WaitingRequest +{ + std::string fileId; + std::string interactionId; + std::string sha3sum; + std::string path; + std::size_t totalSize; + MSGPACK_DEFINE(fileId, interactionId, sha3sum, path, totalSize) +}; + typedef std::function<void(const std::string&)> InternalCompletionCb; -typedef std::function<bool(const std::string&)> OnVerifyCb; typedef std::function<void(const DRing::DataTransferId&, const DRing::DataTransferEventCode&)> OnStateChangedCb; -class TransferManager +class FileInfo { public: - TransferManager(const std::string& accountId, const std::string& to, bool isConversation = true); + FileInfo(const std::shared_ptr<ChannelSocket>& channel, + const std::string& fileId, + const std::string& interactionId, + const DRing::DataTransferInfo& info); + virtual ~FileInfo() {} + virtual void process() = 0; + std::shared_ptr<ChannelSocket> channel() const { return channel_; } + DRing::DataTransferInfo info() const { return info_; } + virtual void cancel() = 0; + void onFinished(std::function<void(uint32_t)>&& cb) { finishedCb_ = std::move(cb); } + void emit(DRing::DataTransferEventCode code); + +protected: + std::atomic_bool isUserCancelled_ {false}; + std::string fileId_ {}; + std::string interactionId_ {}; + DRing::DataTransferInfo info_ {}; + std::shared_ptr<ChannelSocket> channel_ {}; + std::function<void(uint32_t)> finishedCb_ {}; +}; + +class IncomingFile : public FileInfo +{ +public: + IncomingFile(const std::shared_ptr<ChannelSocket>& channel, + const DRing::DataTransferInfo& info, + const std::string& fileId, + const std::string& interactionId, + const std::string& sha3Sum = ""); + ~IncomingFile(); + void process() override; + void cancel() override; + +private: + std::ofstream stream_; + std::string sha3Sum_ {}; +}; + +class OutgoingFile : public FileInfo +{ +public: + OutgoingFile(const std::shared_ptr<ChannelSocket>& channel, + const std::string& fileId, + const std::string& interactionId, + const DRing::DataTransferInfo& info, + size_t start = 0, + size_t end = 0); + ~OutgoingFile(); + void process() override; + void cancel() override; + +private: + std::ifstream stream_; + size_t start_ {0}; + size_t end_ {0}; +}; + +class TransferManager : public std::enable_shared_from_this<TransferManager> +{ +public: + TransferManager(const std::string& accountId, const std::string& to); ~TransferManager(); /** * Send a file * @param path of the file + * @param peer DeviceId for vcard or dest * @param icb used for internal files (like vcard) - * @param deviceId if we only want to transmit to one device - * @param resendId if we need to resend a file, just specify previous id there. */ - DRing::DataTransferId sendFile(const std::string& path, - const InternalCompletionCb& icb = {}, - const std::string& deviceId = {}, - DRing::DataTransferId resendId = {0}); + /*[[deprecated("Non swarm method")]]*/ DRing::DataTransferId sendFile( + const std::string& path, const std::string& peer, const InternalCompletionCb& icb = {}); /** * Accepts a transfer * @param id of the transfer * @param path of the file */ - bool acceptFile(const DRing::DataTransferId& id, const std::string& path); + /*[[deprecated("Non swarm method")]]*/ bool acceptFile(const DRing::DataTransferId& id, + const std::string& path); /** - * Refuse a transfer + * Inform the transfer manager that a new file is incoming + * @param info of the transfer * @param id of the transfer + * @param cb callback to trigger when connected + * @param icb used for vcard */ - bool cancel(const DRing::DataTransferId& id); + /*[[deprecated("Non swarm method")]]*/ void onIncomingFileRequest( + const DRing::DataTransferInfo& info, + const DRing::DataTransferId& id, + const std::function<void(const IncomingFileInfo&)>& cb, + const InternalCompletionCb& icb = {}); /** * Get current transfer infos @@ -78,42 +157,84 @@ public: * @param info to fill * @return if found */ - bool info(const DRing::DataTransferId& id, DRing::DataTransferInfo& info) const noexcept; + /*[[deprecated("Non swarm method")]]*/ bool info(const DRing::DataTransferId& id, + DRing::DataTransferInfo& info) const noexcept; /** - * Get current transfer progress + * Send a file to a channel + * @param channel channel to use + * @param fileId fileId of the transfer + * @param interactionId interactionId of the transfer + * @param path path of the file + * @param start start offset + * @param end end + */ + void transferFile(const std::shared_ptr<ChannelSocket>& channel, + const std::string& fileId, + const std::string& interactionId, + const std::string& path, + size_t start = 0, + size_t end = 0); + + /** + * Refuse a transfer * @param id of the transfer - * @param total size - * @param progress current progress - * @return if found */ - bool bytesProgress(const DRing::DataTransferId& id, - int64_t& total, - int64_t& progress) const noexcept; + bool cancel(const std::string& fileId); /** - * Inform the transfer manager that a new file is incoming - * @param info of the transfer + * Get current transfer info * @param id of the transfer - * @param cb callback to trigger when connected - * @param icb used for vcard + * @param total size + * @param path path of the file + * @param progress current progress + * @return if found */ - void onIncomingFileRequest(const DRing::DataTransferInfo& info, - const DRing::DataTransferId& id, - const std::function<void(const IncomingFileInfo&)>& cb, - const InternalCompletionCb& icb = {}); + bool info(const std::string& fileId, + std::string& path, + int64_t& total, + int64_t& progress) const noexcept; /** * Inform the transfer manager that a transfer is waited (and will be automatically accepted) - * @param id of the transfer - * @param sha3sum attended sha3sum - * @param path where the file will be downloaded + * @param id of the transfer + * @param interactionId linked interaction + * @param sha3sum attended sha3sum + * @param path where the file will be downloaded + * @param total total size of the file */ - void waitForTransfer(const DRing::DataTransferId& id, + void waitForTransfer(const std::string& fileId, + const std::string& interactionId, const std::string& sha3sum, - const std::string& path); + const std::string& path, + std::size_t total); + + /** + * Handle incoming transfer + * @param id Related id + * @param channel Related channel + */ + void onIncomingFileTransfer(const std::string& fileId, + const std::shared_ptr<ChannelSocket>& channel); + + /** + * Retrieve path of a file + * @param id + */ + std::string path(const std::string& fileId) const; + + /** + * Retrieve waiting files + * @return waiting list + */ + std::vector<WaitingRequest> waitingRequests() const; + void onIncomingProfile(const std::shared_ptr<ChannelSocket>& channel); private: + std::weak_ptr<TransferManager> weak() + { + return std::static_pointer_cast<TransferManager>(shared_from_this()); + } NON_COPYABLE(TransferManager); class Impl; std::unique_ptr<Impl> pimpl_; diff --git a/src/dring/datatransfer_interface.h b/src/dring/datatransfer_interface.h index ce59a3b8d7..c462ff36ad 100644 --- a/src/dring/datatransfer_interface.h +++ b/src/dring/datatransfer_interface.h @@ -108,7 +108,14 @@ struct DRING_PUBLIC DataTransferInfo /// DataTransferEvent signal for such event. There is no reserved or special values on /// DataTransferId type. /// -DRING_PUBLIC DataTransferError sendFile(const DataTransferInfo& info, DataTransferId& id) noexcept; +DRING_PUBLIC DataTransferError sendFileLegacy(const DataTransferInfo& info, + DataTransferId& tid) noexcept; + +DRING_PUBLIC void sendFile(const std::string& accountId, + const std::string& conversationId, + const std::string& path, + const std::string& displayName, + const std::string& parent) noexcept; /// Accept an incoming file transfer. /// @@ -119,30 +126,27 @@ DRING_PUBLIC DataTransferError sendFile(const DataTransferInfo& info, DataTransf /// /// \param id data transfer identification value as given by a DataTransferEvent signal. /// \param file_path file path going to be open in binary write mode to put incoming data. -/// \param offset used to indicate the remote side about the number of bytes already received in -/// a previous transfer session, useful in transfer continuation mode. /// /// \return DataTransferError::invalid_argument if id is unknown. /// \note unknown \a id results to a no-op call. /// DRING_PUBLIC DataTransferError acceptFileTransfer(const std::string& accountId, - const std::string& conversationId, - const DataTransferId& id, - const std::string& file_path, - int64_t offset) noexcept; + const std::string& fileId, + const std::string& file_path) noexcept; /// Asks for retransferring a file. Generally this means that the file is missing /// from the conversation /// /// \param accountId /// \param conversationId -/// \param interactionId +/// \param fileId /// \param path /// -DRING_PUBLIC void askForTransfer(const std::string& accountId, - const std::string& conversationUri, - const std::string& interactionId, - const std::string& path) noexcept; +DRING_PUBLIC bool downloadFile(const std::string& accountId, + const std::string& conversationId, + const std::string& interactionId, + const std::string& fileId, + const std::string& path) noexcept; /// Refuse or abort an outgoing or an incoming file transfer. /// @@ -158,7 +162,7 @@ DRING_PUBLIC void askForTransfer(const std::string& accountId, /// DataTransferError cancelDataTransfer(const std::string& accountId, const std::string& conversationId, - const DataTransferId& id) noexcept DRING_PUBLIC; + const std::string& fileId) noexcept DRING_PUBLIC; /// Return some information on given data transfer. /// @@ -169,8 +173,7 @@ DataTransferError cancelDataTransfer(const std::string& accountId, /// \note \a info structure is in undefined state in case of error. /// DRING_PUBLIC DataTransferError dataTransferInfo(const std::string& accountId, - const std::string& conversationId, - const DataTransferId& id, + const std::string& fileId, DataTransferInfo& info) noexcept; /// Return the amount of sent/received bytes of an existing data transfer. @@ -182,11 +185,12 @@ DRING_PUBLIC DataTransferError dataTransferInfo(const std::string& accountId, /// \return DataTransferError::success if \a total and \a progress is set with valid values. /// DataTransferError::invalid_argument if the id is unknown. /// -DRING_PUBLIC DataTransferError dataTransferBytesProgress(const std::string& accountId, - const std::string& conversationId, - const DataTransferId& id, - int64_t& total, - int64_t& progress) noexcept; +DRING_PUBLIC DataTransferError fileTransferInfo(const std::string& accountId, + const std::string& conversationId, + const std::string& fileId, + std::string& path, + int64_t& total, + int64_t& progress) noexcept; // Signals struct DRING_PUBLIC DataTransferSignal @@ -196,7 +200,8 @@ struct DRING_PUBLIC DataTransferSignal constexpr static const char* name = "DataTransferEvent"; using cb_type = void(const std::string& accountId, const std::string& conversationId, - const DataTransferId& transferId, + const std::string& interactionId, + const std::string& fileId, int eventCode); }; }; diff --git a/src/fileutils.cpp b/src/fileutils.cpp index 74a89d4cb5..c802152103 100644 --- a/src/fileutils.cpp +++ b/src/fileutils.cpp @@ -65,6 +65,7 @@ #ifndef _WIN32 #include <pwd.h> #else +#include <filesystem> #include <shlobj.h> #define NAME_MAX 255 #endif @@ -76,7 +77,6 @@ #include <sstream> #include <fstream> -#include <filesystem> #include <iostream> #include <stdexcept> #include <limits> @@ -317,11 +317,29 @@ writeTime(const std::string& path) } void -createSymLink(const std::string& src, const std::string& dest) +createSymLink(const std::string& linkFile, const std::string& target) { - check_dir(std::string(std::filesystem::path(src).parent_path()).c_str()); - auto absolute_dest = std::string(std::filesystem::absolute(std::filesystem::path(dest))); - std::filesystem::create_symlink(absolute_dest, src); + auto sep = target.find_last_of('/'); + if (sep != std::string::npos) + check_dir(target.substr(0, sep).c_str()); +#ifndef _WIN32 + symlink(target.c_str(), linkFile.c_str()); +#else + std::error_code ec; + std::filesystem::create_symlink(target, linkFile, ec); +#endif +} + +std::string +getFileExtension(const std::string& filename) +{ + std::string result = ""; + auto sep = filename.find_last_of('.'); + if (sep != std::string::npos && sep != filename.size() - 1) + result = filename.substr(sep + 1); + if (result.size() >= 8) + return {}; + return result; } bool @@ -961,7 +979,7 @@ size(const std::string& path) std::ifstream file; int64_t size; try { - openStream(file, path); + openStream(file, path, std::ios::binary | std::ios::in); file.seekg(0, std::ios_base::end); size = file.tellg(); file.close(); @@ -980,7 +998,7 @@ sha3File(const std::string& path) try { if (!fileutils::isFile(path)) return {}; - openStream(file, path); + openStream(file, path, std::ios::binary | std::ios::in); if (!file) return {}; std::vector<char> buffer(8192, 0); diff --git a/src/fileutils.h b/src/fileutils.h index c8f380d6fa..677ea18fed 100644 --- a/src/fileutils.h +++ b/src/fileutils.h @@ -87,6 +87,8 @@ std::chrono::system_clock::time_point writeTime(const std::string& path); void createSymLink(const std::string& src, const std::string& dest); +std::string getFileExtension(const std::string& filename); + /** * Read content of the directory. * The result is a list of relative (to @param dir) paths of all entries diff --git a/src/ftp_server.cpp b/src/ftp_server.cpp index 4af5d2f62e..385225fb3b 100644 --- a/src/ftp_server.cpp +++ b/src/ftp_server.cpp @@ -79,7 +79,7 @@ FtpServer::startNewFile() to = info_.peer; if (auto acc = Manager::instance().getAccount<JamiAccount>(info_.accountId)) { - acc->onIncomingFileRequest( + acc->dataTransfer()->onIncomingFileRequest( info_, transferId_, [w = weak()](const IncomingFileInfo& fileInfo) { diff --git a/src/jamidht/connectionmanager.cpp b/src/jamidht/connectionmanager.cpp index 12f474cbd7..c82fa823ea 100644 --- a/src/jamidht/connectionmanager.cpp +++ b/src/jamidht/connectionmanager.cpp @@ -114,10 +114,14 @@ public: const std::string& name, const dht::Value::Id& vid, const std::shared_ptr<dht::crypto::Certificate>& cert); - void connectDevice(const DeviceId& deviceId, const std::string& uri, ConnectCallback cb); + void connectDevice(const DeviceId& deviceId, + const std::string& uri, + ConnectCallback cb, + bool noNewSocket = false); void connectDevice(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name, - ConnectCallback cb); + ConnectCallback cb, + bool noNewSocket = false); /** * Send a ChannelRequest on the TLS socket. Triggers cb when ready * @param sock socket used to send the request @@ -378,7 +382,8 @@ ConnectionManager::Impl::connectDeviceOnNegoDone( void ConnectionManager::Impl::connectDevice(const DeviceId& deviceId, const std::string& name, - ConnectCallback cb) + ConnectCallback cb, + bool noNewSocket) { if (!account.dht()) { cb(nullptr, deviceId); @@ -389,7 +394,7 @@ ConnectionManager::Impl::connectDevice(const DeviceId& deviceId, return; } account.findCertificate(deviceId, - [w = weak(), deviceId, name, cb = std::move(cb)]( + [w = weak(), deviceId, name, cb = std::move(cb), noNewSocket]( const std::shared_ptr<dht::crypto::Certificate>& cert) { if (!cert) { JAMI_ERR("Invalid certificate found for device %s", @@ -398,7 +403,7 @@ ConnectionManager::Impl::connectDevice(const DeviceId& deviceId, return; } if (auto shared = w.lock()) { - shared->connectDevice(cert, name, std::move(cb)); + shared->connectDevice(cert, name, std::move(cb), noNewSocket); } }); } @@ -406,10 +411,15 @@ ConnectionManager::Impl::connectDevice(const DeviceId& deviceId, void ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name, - ConnectCallback cb) + ConnectCallback cb, + bool noNewSocket) { // Avoid dht operation in a DHT callback to avoid deadlocks - runOnMainThread([w = weak(), name = std::move(name), cert = std::move(cert), cb = std::move(cb)] { + runOnMainThread([w = weak(), + name = std::move(name), + cert = std::move(cert), + cb = std::move(cb), + noNewSocket] { auto deviceId = cert->getId(); auto sthis = w.lock(); if (!sthis || sthis->isDestroying_) { @@ -460,6 +470,12 @@ ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certif JAMI_DBG("Already connecting to %s, wait for the ICE negotiation", deviceId.to_c_str()); return; } + if (noNewSocket) { + // If no new socket is specified, we don't try to generate a new socket + for (const auto& pending : sthis->extractPendingCallbacks(deviceId)) + pending.cb(nullptr, deviceId); + return; + } // Note: used when the ice negotiation fails to erase // all stored structures. @@ -542,8 +558,9 @@ ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certif ice_config.master = false; ice_config.streamsCount = JamiAccount::ICE_STREAMS_COUNT; ice_config.compCountPerStream = JamiAccount::ICE_COMP_COUNT_PER_STREAM; - info->ice_ = Manager::instance().getIceTransportFactory().createUTransport( - sthis->account.getAccountID().c_str(), ice_config); + info->ice_ = Manager::instance() + .getIceTransportFactory() + .createUTransport(sthis->account.getAccountID().c_str(), ice_config); if (!info->ice_) { JAMI_ERR("Cannot initialize ICE session."); @@ -894,8 +911,8 @@ ConnectionManager::Impl::onDhtPeerRequest(const PeerConnectionRequest& req, ice_config.compCountPerStream = JamiAccount::ICE_COMP_COUNT_PER_STREAM; ice_config.master = true; info->ice_ = Manager::instance() - .getIceTransportFactory() - .createUTransport(shared->account.getAccountID().c_str(), ice_config); + .getIceTransportFactory() + .createUTransport(shared->account.getAccountID().c_str(), ice_config); if (not info->ice_) { JAMI_ERR("Cannot initialize ICE session."); if (shared->connReadyCb_) @@ -965,17 +982,19 @@ ConnectionManager::~ConnectionManager() void ConnectionManager::connectDevice(const DeviceId& deviceId, const std::string& name, - ConnectCallback cb) + ConnectCallback cb, + bool noNewSocket) { - pimpl_->connectDevice(deviceId, name, std::move(cb)); + pimpl_->connectDevice(deviceId, name, std::move(cb), noNewSocket); } void ConnectionManager::connectDevice(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name, - ConnectCallback cb) + ConnectCallback cb, + bool noNewSocket) { - pimpl_->connectDevice(cert, name, std::move(cb)); + pimpl_->connectDevice(cert, name, std::move(cb), noNewSocket); } bool diff --git a/src/jamidht/connectionmanager.h b/src/jamidht/connectionmanager.h index 9239601a7d..8f2c649284 100644 --- a/src/jamidht/connectionmanager.h +++ b/src/jamidht/connectionmanager.h @@ -84,11 +84,16 @@ public: * @param deviceId Remote device * @param name Name of the channel * @param cb Callback called when socket is ready ready + * @param noNewSocket Do not negotiate a new socekt if there is none */ - void connectDevice(const DeviceId& deviceId, const std::string& name, ConnectCallback cb); + void connectDevice(const DeviceId& deviceId, + const std::string& name, + ConnectCallback cb, + bool noNewSocket = false); void connectDevice(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name, - ConnectCallback cb); + ConnectCallback cb, + bool noNewSocket = false); /** * Check if we are already connecting to a device with a specific name diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp index 793c680468..b5a2948e18 100644 --- a/src/jamidht/conversation.cpp +++ b/src/jamidht/conversation.cpp @@ -24,6 +24,7 @@ #include "conversationrepository.h" #include "client/ring_signal.h" +#include <charconv> #include <json/json.h> #include <string_view> #include <opendht/thread_pool.h> @@ -118,6 +119,9 @@ public: if (!repository_) { throw std::logic_error("Couldn't create repository"); } + if (auto shared = account_.lock()) + transferManager_ = std::make_shared<TransferManager>(shared->getAccountID(), + repository_->id()); } Impl(const std::weak_ptr<JamiAccount>& account, const std::string& conversationId) @@ -127,6 +131,9 @@ public: if (!repository_) { throw std::logic_error("Couldn't create repository"); } + if (auto shared = account_.lock()) + transferManager_ = std::make_shared<TransferManager>(shared->getAccountID(), + repository_->id()); } Impl(const std::weak_ptr<JamiAccount>& account, @@ -144,6 +151,9 @@ public: } throw std::logic_error("Couldn't clone repository"); } + if (auto shared = account_.lock()) + transferManager_ = std::make_shared<TransferManager>(shared->getAccountID(), + repository_->id()); } ~Impl() = default; @@ -237,6 +247,7 @@ public: std::mutex pullcbsMtx_ {}; std::set<std::string> fetchingRemotes_ {}; // store current remote in fetch std::deque<std::tuple<std::string, std::string, OnPullCb>> pullcbs_ {}; + std::shared_ptr<TransferManager> transferManager_ {}; }; bool @@ -320,6 +331,13 @@ Conversation::Impl::convCommitToMap(const ConversationCommit& commit) const JAMI_WARN("%s", err.c_str()); } } + if (type == "application/data-transfer+json") { + // Avoid the client to do the concatenation + message["fileId"] = commit.id + "_" + message["tid"]; + auto extension = fileutils::getFileExtension(message["displayName"]); + if (!extension.empty()) + message["fileId"] += "." + extension; + } auto linearizedParent = repository_->linearizedParent(commit.id); message["id"] = commit.id; message["parents"] = parents; @@ -698,12 +716,12 @@ Conversation::mergeHistory(const std::string& uri) } void -Conversation::pull(const std::string& uri, OnPullCb&& cb, std::string commitId) +Conversation::pull(const std::string& deviceId, OnPullCb&& cb, std::string commitId) { std::lock_guard<std::mutex> lk(pimpl_->pullcbsMtx_); auto isInProgress = not pimpl_->pullcbs_.empty(); pimpl_->pullcbs_.emplace_back( - std::make_tuple<std::string, std::string, OnPullCb>(std::string(uri), + std::make_tuple<std::string, std::string, OnPullCb>(std::string(deviceId), std::move(commitId), std::move(cb))); if (isInProgress) @@ -775,6 +793,28 @@ Conversation::pull(const std::string& uri, OnPullCb&& cb, std::string commitId) }); } +void +Conversation::sync(const std::string& member, + const std::string& deviceId, + OnPullCb&& cb, + std::string commitId) +{ + JAMI_INFO() << "Sync " << id() << " with " << deviceId; + pull(deviceId, std::move(cb), commitId); + // For waiting request, downloadFile + for (const auto& wr : dataTransfer()->waitingRequests()) + downloadFile(wr.interactionId, wr.fileId, wr.path, member, deviceId); + // VCard sync for member + if (auto account = pimpl_->account_.lock()) { + if (not account->needToSendProfile(deviceId)) { + JAMI_INFO() << "Peer " << deviceId << " already got an up-to-date vcard"; + return; + } + // We need a new channel + account->transferFile(id(), std::string(account->profilePath()), deviceId, "profile.vcf", ""); + } +} + std::map<std::string, std::string> Conversation::generateInvitation() const { @@ -871,4 +911,98 @@ Conversation::vCard() const return {}; } +std::shared_ptr<TransferManager> +Conversation::dataTransfer() const +{ + return pimpl_->transferManager_; +} + +bool +Conversation::onFileChannelRequest(const std::string& member, + const std::string& fileId, + bool verifyShaSum) const +{ + if (!isMember(member)) + return false; + + auto account = pimpl_->account_.lock(); + if (!account) + return false; + + auto sep = fileId.find('_'); + if (sep == std::string::npos) + return false; + + auto interactionId = fileId.substr(0, sep); + auto commit = getCommit(interactionId); + if (commit == std::nullopt || commit->find("type") == commit->end() + || commit->find("tid") == commit->end() || commit->find("sha3sum") == commit->end() + || commit->at("type") != "application/data-transfer+json") + return false; + + auto path = dataTransfer()->path(fileId); + + if (!fileutils::isFile(path)) { + // Check if dangling symlink + if (fileutils::isSymLink(path)) { + fileutils::remove(path, true); + } + JAMI_DBG("[Account %s] %s asked for non existing file %s in %s", + account->getAccountID().c_str(), + member.c_str(), + fileId.c_str(), + id().c_str()); + return false; + } + // Check that our file is correct before sending + if (verifyShaSum && commit->at("sha3sum") != fileutils::sha3File(path)) { + JAMI_DBG("[Account %s] %s asked for file %s in %s, but our version is not complete", + account->getAccountID().c_str(), + member.c_str(), + fileId.c_str(), + id().c_str()); + return false; + } + return true; +} + +bool +Conversation::downloadFile(const std::string& interactionId, + const std::string& fileId, + const std::string& path, + const std::string&, + const std::string& deviceId, + std::size_t start, + std::size_t end) +{ + auto commit = getCommit(interactionId); + if (commit == std::nullopt || commit->find("type") == commit->end() + || commit->find("sha3sum") == commit->end() || commit->find("tid") == commit->end() + || commit->at("type") != "application/data-transfer+json") { + JAMI_ERR() << "Cannot download file without linked interaction " << fileId; + return false; + } + auto sha3sum = commit->at("sha3sum"); + auto size_str = commit->at("totalSize"); + std::size_t totalSize; + std::from_chars(size_str.data(), size_str.data() + size_str.size(), totalSize); + + // Be sure to not lock conversation + dht::ThreadPool().io().run( + [w = weak(), deviceId, fileId, interactionId, sha3sum, path, totalSize, start, end] { + if (auto shared = w.lock()) { + auto acc = shared->pimpl_->account_.lock(); + if (!acc) + return; + shared->dataTransfer()->waitForTransfer(fileId, + interactionId, + sha3sum, + path, + totalSize); + acc->askForFileChannel(shared->id(), deviceId, fileId, start, end); + } + }); + return true; +} + } // namespace jami \ No newline at end of file diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h index 84719ad023..d63046b494 100644 --- a/src/jamidht/conversation.h +++ b/src/jamidht/conversation.h @@ -27,6 +27,8 @@ #include <json/json.h> #include <msgpack.hpp> +#include "dring/datatransfer_interface.h" + namespace jami { /** @@ -71,6 +73,8 @@ struct ConvInfo class JamiAccount; class ConversationRepository; +class TransferManager; +class ChannelSocket; enum class ConversationMode; using OnPullCb = std::function<void(bool fetchOk)>; @@ -177,11 +181,22 @@ public: /** * Fetch and merge from peer - * @param uri Peer + * @param deviceId Peer device * @param cb On pulled callback * @param commitId Commit id that triggered this fetch */ - void pull(const std::string& uri, OnPullCb&& cb, std::string commitId = ""); + void pull(const std::string& deviceId, OnPullCb&& cb, std::string commitId = ""); + /** + * Fetch new commits and re-ask for waiting files + * @param member + * @param deviceId + * @param cb cf pull() + * @param commitId cf pull() + */ + void sync(const std::string& member, + const std::string& deviceId, + OnPullCb&& cb, + std::string commitId = ""); /** * Generate an invitation to send to new contacts @@ -240,6 +255,42 @@ public: std::map<std::string, std::string> infos() const; std::vector<uint8_t> vCard() const; + /////// File transfer + + /** + * Access to transfer manager + */ + std::shared_ptr<TransferManager> dataTransfer() const; + + /** + * Choose if we can accept channel request + * @param member member to check + * @param fileId file transfer to check (needs to be waiting) + * @param verifyShaSum for debug only + * @return if we accept the channel request + */ + bool onFileChannelRequest(const std::string& member, + const std::string& fileId, + bool verifyShaSum = true) const; + /** + * Adds a file to the waiting list and ask members + * @param interactionId Related interaction id + * @param fileId Related id + * @param path Destination + * @param member Member if we know from who to pull file + * @param deviceId Device if we know from who to pull file + * @param start Offset (unused for now) + * @param end Offset (unused) + * @return id of the file + */ + bool downloadFile(const std::string& interactionId, + const std::string& fileId, + const std::string& path, + const std::string& member = "", + const std::string& deviceId = "", + std::size_t start = 0, + std::size_t end = 0); + private: std::shared_ptr<Conversation> shared() { diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp index f663caca88..edea76f3d7 100644 --- a/src/jamidht/jamiaccount.cpp +++ b/src/jamidht/jamiaccount.cpp @@ -314,6 +314,7 @@ JamiAccount::JamiAccount(const std::string& accountID, bool /* presenceEnabled * proxyListUrl_ = DHT_DEFAULT_PROXY_LIST_URL; proxyServer_ = DHT_DEFAULT_PROXY; + nonSwarmTransferManager_ = std::make_shared<TransferManager>(getAccountID(), ""); try { std::istringstream is(fileutils::loadCacheTextFile(cachePath_ + DIR_SEPARATOR_STR "dhtproxy", @@ -359,10 +360,6 @@ JamiAccount::shutdownConnections() std::move(connectionManager_))] {}); connectionManager_.reset(); } - { - std::unique_lock<std::mutex> lk(transferMutex_); - transferManagers_.clear(); - } { std::unique_lock<std::mutex> lk(syncConnectionsMtx_); syncConnections_.clear(); @@ -379,8 +376,8 @@ JamiAccount::flush() // Class base method SIPAccountBase::flush(); - fileutils::removeAll(dataPath_); fileutils::removeAll(cachePath_); + fileutils::removeAll(dataPath_); fileutils::removeAll(idPath_, true); } @@ -1188,12 +1185,25 @@ JamiAccount::loadAccount(const std::string& archive_password, return; } } + if (saveReq && !conversationId.empty()) { + ConversationRequest req; + req.from = uri; + req.conversationId = conversationId; + req.received = std::time(nullptr); + auto details = vCard::utils::toMap( + std::string_view(reinterpret_cast<const char*>(payload.data()), payload.size())); + req.metadatas = ConversationRepository::infosFromVCard(details); + accountManager_->addConversationRequest(conversationId, std::move(req)); + emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(getAccountID(), + conversationId, + req.toMap()); + } emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(getAccountID(), conversationId, uri, payload, received); - if (!saveReq && !conversationId.empty()) + if (!saveReq || conversationId.empty()) return; dht::ThreadPool::io().run( [w = weak(), uri, payload = std::move(payload), received, conversationId] { @@ -1927,7 +1937,7 @@ JamiAccount::trackPresence(const dht::InfoHash& h, BuddyInfo& buddy) return; } buddy.listenToken - = dht->listen<DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&&, bool expired) { + = dht->listen<DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&& dev, bool expired) { bool wasConnected, isConnected; { std::lock_guard<std::mutex> lock(buddyInfoMtx); @@ -1944,6 +1954,7 @@ JamiAccount::trackPresence(const dht::InfoHash& h, BuddyInfo& buddy) if (not expired) { // Retry messages every time a new device announce its presence messageEngine_.onPeerOnline(h.toString()); + requestSIPConnection(h.toString(), dev.dev); } if (isConnected and not wasConnected) { onTrackedBuddyOnline(h); @@ -1997,6 +2008,22 @@ JamiAccount::onTrackedBuddyOffline(const dht::InfoHash& contactId) ""); } +void +JamiAccount::syncConversations(const std::string& peer, const std::string& deviceId) +{ + // Sync conversations where peer is member + std::set<std::string> convIds; + { + std::unique_lock<std::mutex> lk(conversationsMtx_); + for (const auto& [cid, conv] : conversations_) { + if (conv->isMember(peer, false)) + convIds.emplace(cid); + } + } + for (const auto& cid : convIds) + fetchNewCommits(peer, deviceId, cid); +} + void JamiAccount::doRegister_() { @@ -2153,35 +2180,37 @@ JamiAccount::doRegister_() context.identityAnnouncedCb = [this](bool ok) { if (!ok) return; - accountManager_->startSync([this](const std::shared_ptr<dht::crypto::Certificate>& crt) { - if (!crt) - return; - auto deviceId = crt->getId().toString(); - if (accountManager_->getInfo()->deviceId == deviceId) - return; + accountManager_->startSync( + [this](const std::shared_ptr<dht::crypto::Certificate>& crt) { + if (!crt) + return; + auto deviceId = crt->getId().toString(); + if (accountManager_->getInfo()->deviceId == deviceId) + return; - std::lock_guard<std::mutex> lk(connManagerMtx_); - if (!connectionManager_) - connectionManager_ = std::make_unique<ConnectionManager>(*this); - auto channelName = "sync://" + deviceId; - if (connectionManager_->isConnecting(crt->getId(), channelName)) { - JAMI_INFO("[Account %s] Already connecting to %s", - getAccountID().c_str(), - deviceId.c_str()); - return; - } - connectionManager_->connectDevice(crt, - channelName, - [this](std::shared_ptr<ChannelSocket> socket, - const DeviceId& deviceId) { - if (socket) - syncWith(deviceId.toString(), socket); - }); - }, [this] { - deviceAnnounced_ = true; - emitSignal<DRing::ConfigurationSignal::VolatileDetailsChanged>( - accountID_, getVolatileAccountDetails()); - }); + std::lock_guard<std::mutex> lk(connManagerMtx_); + if (!connectionManager_) + connectionManager_ = std::make_unique<ConnectionManager>(*this); + auto channelName = "sync://" + deviceId; + if (connectionManager_->isConnecting(crt->getId(), channelName)) { + JAMI_INFO("[Account %s] Already connecting to %s", + getAccountID().c_str(), + deviceId.c_str()); + return; + } + connectionManager_->connectDevice(crt, + channelName, + [this](std::shared_ptr<ChannelSocket> socket, + const DeviceId& deviceId) { + if (socket) + syncWith(deviceId.toString(), socket); + }); + }, + [this] { + deviceAnnounced_ = true; + emitSignal<DRing::ConfigurationSignal::VolatileDetailsChanged>( + accountID_, getVolatileAccountDetails()); + }); }; setRegistrationState(RegistrationState::TRYING); @@ -2246,15 +2275,53 @@ JamiAccount::doRegister_() auto result = fut.get(); return result; } else if (isFile or isVCard) { - auto tid_str = isFile ? name.substr(7) : name.substr(8); - uint64_t tid; - std::istringstream iss(tid_str); - iss >> tid; + auto tid = isFile ? name.substr(7) : name.substr(8); std::lock_guard<std::mutex> lk(transfersMtx_); - incomingFileTransfers_.emplace(tid_str); + incomingFileTransfers_.emplace(tid); return true; } else if (isDataTransfer) { - return true; // Nothing to do there, will pass the signal when the co will be ready + // Check if sync request is from same account + std::promise<bool> accept; + std::future<bool> fut = accept.get_future(); + + auto idstr = name.substr(16); + auto sep = idstr.find('/'); + auto lastSep = idstr.find_last_of('/'); + auto conversationId = idstr.substr(0, sep); + auto fileHost = idstr.substr(sep + 1, lastSep - sep - 1); + auto fileId = idstr.substr(lastSep + 1); + if (fileHost == currentDeviceId()) + return false; + + sep = fileId.find_last_of('?'); + if (sep != std::string::npos) { + fileId = fileId.substr(0, sep); + } + + accountManager_->findCertificate( + deviceId, [&](const std::shared_ptr<dht::crypto::Certificate>& cert) { + if (not cert) { + accept.set_value(false); + return; + } + // Check if peer is member of the conversation + std::unique_lock<std::mutex> lk(conversationsMtx_); + auto conversation = conversations_.find(conversationId); + if (conversation == conversations_.end() or not conversation->second) { + accept.set_value(false); + return; + } + if (fileId == "profile.vcf") { + accept.set_value(conversation->second->isMember(cert->getIssuerUID())); + } else { + accept.set_value( + conversation->second->onFileChannelRequest(cert->getIssuerUID(), + fileId, + !noSha3sumVerification_)); + } + }); + fut.wait(); + return fut.get(); } return false; }); @@ -2273,17 +2340,14 @@ JamiAccount::doRegister_() } else if (name.find("sync://") == 0) { cacheSyncConnection(std::move(channel), peerId, deviceId); } else if (isFile or isVCard) { - auto tid_str = isFile ? name.substr(7) : name.substr(8); + auto tid = isFile ? name.substr(7) : name.substr(8); std::unique_lock<std::mutex> lk(transfersMtx_); - auto it = incomingFileTransfers_.find(tid_str); + auto it = incomingFileTransfers_.find(tid); // Note, outgoing file transfers are ignored. if (it == incomingFileTransfers_.end()) return; incomingFileTransfers_.erase(it); lk.unlock(); - uint64_t tid; - std::istringstream iss(tid_str); - iss >> tid; std::function<void(const std::string&)> cb; if (isVCard) cb = [peerId, accountId = getAccountID()](const std::string& path) { @@ -2292,29 +2356,17 @@ JamiAccount::doRegister_() path); }; - std::unique_lock<std::mutex> lk2(transferMutex_); - auto itManager = transferManagers_.find(peerId); - if (itManager == transferManagers_.end()) { - std::string accId = getAccountID(); - auto res = transferManagers_.emplace(std::piecewise_construct, - std::forward_as_tuple(peerId), - std::forward_as_tuple(accId, - peerId, - false)); - if (!res.second) { - JAMI_ERR("Couldn't create manager for peer %s", peerId.c_str()); - return; - } - itManager = res.first; - } - DRing::DataTransferInfo info; info.accountId = getAccountID(); info.peer = peerId; - dhtPeerConnector_->onIncomingConnection(info, - tid, - std::move(channel), - std::move(cb)); + try { + dhtPeerConnector_->onIncomingConnection(info, + std::stoull(tid), + std::move(channel), + std::move(cb)); + } catch (...) { + JAMI_ERR() << "Invalid tid: " << tid; + } } else if (name.find("git://") == 0) { auto sep = name.find_last_of('/'); @@ -2339,6 +2391,7 @@ JamiAccount::doRegister_() "conversation (%s)", getAccountID().c_str(), conversationId.c_str()); + channel->shutdown(); return; } if (conversation->second->isBanned(remoteDevice)) { @@ -2346,6 +2399,7 @@ JamiAccount::doRegister_() getAccountID().c_str(), remoteDevice.c_str(), conversationId.c_str()); + channel->shutdown(); return; } } @@ -2394,32 +2448,48 @@ JamiAccount::doRegister_() auto fileId = idstr.substr(lastSep + 1); if (fileHost == currentDeviceId()) // This means we are the host, so the file is // outgoing, ignore + { return; - std::unique_lock<std::mutex> lk(transferMutex_); - auto it = transferManagers_.find(conversationId); - if (it == transferManagers_.end()) { - std::string accId = getAccountID(); - auto res = transferManagers_.emplace(std::piecewise_construct, - std::forward_as_tuple(conversationId), - std::forward_as_tuple(accId, - conversationId)); - if (!res.second) { - JAMI_ERR("Couldn't create manager for conversation %s", - conversationId.c_str()); - return; + } + + sep = fileId.find_last_of('?'); + std::string arguments; + if (sep != std::string::npos) { + arguments = fileId.substr(sep + 1); + fileId = fileId.substr(0, sep); + } + + if (fileId == "profile.vcf") { + std::string path = fileutils::sha3File(idPath_ + DIR_SEPARATOR_STR + + "profile.vcf"); + dataTransfer()->transferFile(channel, fileId, "", path); + return; + } + auto dt = dataTransfer(conversationId); + sep = fileId.find('_'); + if (!dt or sep == std::string::npos) { + channel->shutdown(); + return; + } + auto interactionId = fileId.substr(0, sep); + std::string path = dt->path(fileId); + auto start = 0u, end = 0u; + for (const auto arg : jami::split_string(arguments, '&')) { + auto keyVal = jami::split_string(arg, '='); + if (keyVal.size() == 2) { + if (keyVal[0] == "start") { + std::from_chars(keyVal[1].data(), + keyVal[1].data() + keyVal[1].size(), + start); + } else if (keyVal[0] == "end") { + std::from_chars(keyVal[1].data(), + keyVal[1].data() + keyVal[1].size(), + end); + } } - it = res.first; } - uint64_t tid; - std::istringstream iss(fileId); - iss >> tid; - DRing::DataTransferInfo info; - info.accountId = getAccountID(); - info.author = peerId; - info.peer = peerId; - info.conversationId = conversationId; - dhtPeerConnector_->onIncomingConnection(info, tid, std::move(channel)); + dt->transferFile(channel, fileId, interactionId, path, start, end); } } }); @@ -2495,6 +2565,7 @@ JamiAccount::doRegister_() if (!shared) return; shared->cloneConversation(dev.toString(), + member, convId); }); }, @@ -2541,12 +2612,14 @@ JamiAccount::doUnregister(std::function<void(bool)> released_cb) bool shutdown_complete {false}; JAMI_WARN("[Account %s] unregistering account %p", getAccountID().c_str(), this); - dht_->shutdown([&] { - JAMI_WARN("[Account %s] dht shutdown complete", getAccountID().c_str()); - std::lock_guard<std::mutex> lock(mtx); - shutdown_complete = true; - cv.notify_all(); - }, true); + dht_->shutdown( + [&] { + JAMI_WARN("[Account %s] dht shutdown complete", getAccountID().c_str()); + std::lock_guard<std::mutex> lock(mtx); + shutdown_complete = true; + cv.notify_all(); + }, + true); { std::lock_guard<std::mutex> lk(pendingCallsMutex_); @@ -2567,7 +2640,7 @@ JamiAccount::doUnregister(std::function<void(bool)> released_cb) { std::unique_lock<std::mutex> lock(mtx); - cv.wait(lock, [&]{ return shutdown_complete; }); + cv.wait(lock, [&] { return shutdown_complete; }); } dht_->join(); setRegistrationState(RegistrationState::UNREGISTERED); @@ -2720,8 +2793,9 @@ JamiAccount::saveTreatedMessages() const auto& this_ = *sthis; std::lock_guard<std::mutex> lock(this_.messageMutex_); fileutils::check_dir(this_.cachePath_.c_str()); - saveIdList<decltype(this_.treatedMessages_)>(this_.cachePath_ + DIR_SEPARATOR_STR "treatedMessages", - this_.treatedMessages_); + saveIdList<decltype(this_.treatedMessages_)>(this_.cachePath_ + + DIR_SEPARATOR_STR "treatedMessages", + this_.treatedMessages_); } }); } @@ -3385,6 +3459,67 @@ JamiAccount::sendTextMessage(const std::string& to, std::chrono::minutes(1)); } +void +JamiAccount::sendSIPMessageToDevice(const std::string& to, + const DeviceId& deviceId, + const std::map<std::string, std::string>& payloads) +{ + std::lock_guard<std::mutex> lk(sipConnsMtx_); + sip_utils::register_thread(); + + for (auto it = sipConns_.begin(); it != sipConns_.end();) { + auto& [key, value] = *it; + if (key.first == to && key.second != deviceId) { + ++it; + continue; + } + auto& conn = value.back(); + auto& channel = conn.channel; + + // Set input token into callback + std::unique_ptr<TextMessageCtx> ctx {std::make_unique<TextMessageCtx>()}; + ctx->acc = weak(); + ctx->to = to; + ctx->deviceId = key.second; + ctx->channel = channel; + + try { + auto res = sendSIPMessage( + conn, to, ctx.release(), {}, payloads, [](void* token, pjsip_event* event) { + std::unique_ptr<TextMessageCtx> c {(TextMessageCtx*) token}; + auto code = event->body.tsx_state.tsx->status_code; + auto acc = c->acc.lock(); + if (not acc) + return; + + if (code == PJSIP_SC_OK) { + std::unique_lock<std::mutex> l(c->confirmation->lock); + c->confirmation->replied = true; + l.unlock(); + if (!c->onlyConnected) + acc->messageEngine_.onMessageSent(c->to, c->id, true); + } else { + JAMI_WARN("Timeout when send a message, close current connection"); + acc->shutdownSIPConnection(c->channel, c->to, c->deviceId); + } + }); + if (!res) { + ++it; + continue; + } + break; + } catch (const std::runtime_error& ex) { + JAMI_WARN("%s", ex.what()); + ++it; + // Remove connection in incorrect state + shutdownSIPConnection(channel, to, key.second); + continue; + } + + ++it; + } +} + void JamiAccount::onIsComposing(const std::string& conversationId, const std::string& peer, @@ -3460,31 +3595,10 @@ JamiAccount::requestConnection( bool isVCard, const std::function<void(const std::shared_ptr<ChanneledOutgoingTransfer>&)>& channeledConnectedCb, - const std::function<void(const std::string&)>& onChanneledCancelled, - bool addToHistory) + const std::function<void(const std::string&)>& onChanneledCancelled) { if (not dhtPeerConnector_) { - runOnMainThread([w = weak(), onChanneledCancelled, info] { - if (auto shared = w.lock()) { - if (!info.conversationId.empty()) { - std::unique_lock<std::mutex> lk(shared->conversationsMtx_); - auto it = shared->conversations_.find(info.conversationId); - if (it == shared->conversations_.end()) { - JAMI_ERR("Conversation %s doesn't exist", info.conversationId.c_str()); - return; - } - auto isOneToOne = it->second->mode() == ConversationMode::ONE_TO_ONE; - - auto members = it->second->getMembers( - isOneToOne /* In this case, also send to the invited */); - lk.unlock(); - for (const auto& member : members) - onChanneledCancelled(member.at("uri")); - } else { - onChanneledCancelled(info.peer); - } - } - }); + runOnMainThread([onChanneledCancelled, info] { onChanneledCancelled(info.peer); }); return; } dhtPeerConnector_->requestConnection(info, @@ -3492,22 +3606,6 @@ JamiAccount::requestConnection( isVCard, channeledConnectedCb, onChanneledCancelled); - if (!info.conversationId.empty() && addToHistory) { - // NOTE: this sendMessage is in a computation thread because - // sha3sum can take quite some time to computer if the user decide - // to send a big file - dht::ThreadPool::computation().run([w = weak(), info, tid]() { - if (auto shared = w.lock()) { - Json::Value value; - value["tid"] = std::to_string(tid); - value["displayName"] = info.displayName; - value["totalSize"] = std::to_string(fileutils::size(info.path)); - value["sha3sum"] = fileutils::sha3File(info.path); - value["type"] = "application/data-transfer+json"; - shared->sendMessage(info.conversationId, value); - } - }); - } } void @@ -4063,19 +4161,21 @@ JamiAccount::sendMessage(const std::string& conversationId, const std::string& message, const std::string& parent, const std::string& type, - bool announce) + bool announce, + const OnDoneCb& cb) { Json::Value json; json["body"] = message; json["type"] = type; - sendMessage(conversationId, json, parent, announce); + sendMessage(conversationId, json, parent, announce, cb); } void JamiAccount::sendMessage(const std::string& conversationId, const Json::Value& value, const std::string& parent, - bool announce) + bool announce, + const OnDoneCb& cb) { std::lock_guard<std::mutex> lk(conversationsMtx_); auto conversation = conversations_.find(conversationId); @@ -4083,7 +4183,10 @@ JamiAccount::sendMessage(const std::string& conversationId, conversation->second->sendMessage( value, parent, - [w = weak(), conversationId, announce](bool ok, const std::string& commitId) { + [w = weak(), conversationId, announce, cb = std::move(cb)](bool ok, + const std::string& commitId) { + if (cb) + cb(ok, commitId); if (!announce) return; if (ok) { @@ -4168,65 +4271,6 @@ JamiAccount::onNewGitCommit(const std::string& peer, fetchNewCommits(peer, deviceId, conversationId, commitId); } -void -JamiAccount::onAskForTransfer(const std::string& peer, - const std::string& deviceId, - const std::string& conversationId, - const std::string& interactionId) -{ - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(conversationId); - if (conversation == conversations_.end() or not conversation->second) - return; - - if (!conversation->second->isMember(peer, true)) - return; - - auto commit = conversation->second->getCommit(interactionId); - if (commit == std::nullopt || commit->find("type") == commit->end() - || commit->find("tid") == commit->end() || commit->find("sha3sum") == commit->end() - || commit->at("type") != "application/data-transfer+json") - return; - DRing::DataTransferId tid; - auto tid_str = commit->at("tid"); - std::from_chars(tid_str.data(), tid_str.data() + tid_str.size(), tid); - auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID() + DIR_SEPARATOR_STR - + "conversation_data" + DIR_SEPARATOR_STR + conversationId + DIR_SEPARATOR_STR - + tid_str; - if (!fileutils::isFile(path)) { - // Check if dangling symlink - if (fileutils::isSymLink(path)) { - fileutils::remove(path, true); - } - JAMI_DBG("[Account %s] %s asked for non existing file %s in %s", - getAccountID().c_str(), - peer.c_str(), - interactionId.c_str(), - conversationId.c_str()); - return; - } - // Check that our file is correct before sending - if (!noSha3sumVerification_ && commit->at("sha3sum") != fileutils::sha3File(path)) { - JAMI_DBG("[Account %s] %s asked for file %s in %s, but our version is not complete", - getAccountID().c_str(), - peer.c_str(), - interactionId.c_str(), - conversationId.c_str()); - return; - } - - JAMI_WARN("[Account %s] %s asked for file %s in %s", - getAccountID().c_str(), - peer.c_str(), - interactionId.c_str(), - conversationId.c_str()); - dht::ThreadPool::io().run([w = weak(), conversationId, path, deviceId, tid] { - if (auto shared = w.lock()) { - shared->sendFile(conversationId, path, {}, deviceId, tid); - } - }); -} - void JamiAccount::fetchNewCommits(const std::string& peer, const std::string& deviceId, @@ -4264,7 +4308,8 @@ JamiAccount::fetchNewCommits(const std::string& peer, } if (hasGitSocket(deviceId, conversationId)) { - conversation->second->pull( + conversation->second->sync( + peer, deviceId, [peer, deviceId, conversationId, commitId, w = weak()](bool ok) { auto shared = w.lock(); @@ -4301,50 +4346,53 @@ JamiAccount::fetchNewCommits(const std::string& peer, connectionManager_->connectDevice( DeviceId(deviceId), "git://" + deviceId + "/" + conversationId, - [this, conversationId, commitId](std::shared_ptr<ChannelSocket> socket, - const DeviceId& deviceId) { - dht::ThreadPool::io().run( - [w = weak(), conversationId, socket = std::move(socket), deviceId, commitId] { - auto shared = w.lock(); - if (!shared) - return; - std::unique_lock<std::mutex> lk(shared->conversationsMtx_); - auto conversation = shared->conversations_.find(conversationId); - if (!conversation->second) - return; - if (socket) { - shared->addGitSocket(deviceId.toString(), conversationId, socket); - - conversation->second->pull( - deviceId.toString(), - [deviceId, conversationId, w](bool ok) { - auto shared = w.lock(); - if (!shared) - return; - if (!ok) { - JAMI_WARN("[Account %s] Could not fetch new commit " - "from %s for %s", - shared->getAccountID().c_str(), - deviceId.to_c_str(), - conversationId.c_str()); - shared->removeGitSocket(deviceId.toString(), - conversationId); - } - }, - commitId); - } else { - JAMI_ERR("[Account %s] Couldn't open a new git channel with %s for " - "conversation %s", - shared->getAccountID().c_str(), - deviceId.to_c_str(), - conversationId.c_str()); - } - { - std::lock_guard<std::mutex> lk( - shared->pendingConversationsFetchMtx_); - shared->pendingConversationsFetch_.erase(conversationId); - } - }); + [this, conversationId, commitId, peer](std::shared_ptr<ChannelSocket> socket, + const DeviceId& deviceId) { + dht::ThreadPool::io().run([w = weak(), + conversationId, + socket = std::move(socket), + peer, + deviceId, + commitId] { + auto shared = w.lock(); + if (!shared) + return; + std::unique_lock<std::mutex> lk(shared->conversationsMtx_); + auto conversation = shared->conversations_.find(conversationId); + if (!conversation->second) + return; + if (socket) { + shared->addGitSocket(deviceId.toString(), conversationId, socket); + + conversation->second->sync( + peer, + deviceId.toString(), + [deviceId, conversationId, w](bool ok) { + auto shared = w.lock(); + if (!shared) + return; + if (!ok) { + JAMI_WARN("[Account %s] Could not fetch new commit " + "from %s for %s", + shared->getAccountID().c_str(), + deviceId.to_c_str(), + conversationId.c_str()); + shared->removeGitSocket(deviceId.toString(), conversationId); + } + }, + commitId); + } else { + JAMI_ERR("[Account %s] Couldn't open a new git channel with %s for " + "conversation %s", + shared->getAccountID().c_str(), + deviceId.to_c_str(), + conversationId.c_str()); + } + { + std::lock_guard<std::mutex> lk(shared->pendingConversationsFetchMtx_); + shared->pendingConversationsFetch_.erase(conversationId); + } + }); }); } } else { @@ -4362,7 +4410,7 @@ JamiAccount::fetchNewCommits(const std::string& peer, return; for (const auto& ci : infos->conversations) { if (ci.id == conversationId) { - cloneConversation(deviceId, conversationId); + cloneConversation(deviceId, peer, conversationId); return; } } @@ -4760,6 +4808,12 @@ JamiAccount::sendProfile(const std::string& deviceId) } } +std::string +JamiAccount::profilePath() const +{ + return idPath_ + DIR_SEPARATOR_STR + "profile.vcf"; +} + void JamiAccount::cacheSIPConnection(std::shared_ptr<ChannelSocket>&& socket, const std::string& peerId, @@ -4804,6 +4858,8 @@ JamiAccount::cacheSIPConnection(std::shared_ptr<ChannelSocket>&& socket, sendProfile(deviceId.toString()); + syncConversations(peerId, deviceId.toString()); + // Retry messages messageEngine_.onPeerOnline(peerId); @@ -4884,7 +4940,7 @@ JamiAccount::cacheSyncConnection(std::shared_ptr<ChannelSocket>&& socket, } }); - socket->setOnRecv([this, deviceId](const uint8_t* buf, size_t len) { + socket->setOnRecv([this, deviceId, peerId](const uint8_t* buf, size_t len) { if (!buf) return len; @@ -4915,7 +4971,7 @@ JamiAccount::cacheSyncConnection(std::shared_ptr<ChannelSocket>&& socket, } } if (!wasRemoved) - cloneConversation(deviceId, convId); + cloneConversation(deviceId, peerId, convId); } else { { std::lock_guard<std::mutex> lk(conversationsMtx_); @@ -5125,6 +5181,18 @@ JamiAccount::loadConversations() JAMI_INFO("[Account %s] Conversations loaded!", getAccountID().c_str()); } +std::shared_ptr<TransferManager> +JamiAccount::dataTransfer(const std::string& id) const +{ + if (id.empty()) + return nonSwarmTransferManager_; + std::unique_lock<std::mutex> lk(conversationsMtx_); + auto it = conversations_.find(id); + if (it != conversations_.end() && it->second) + return it->second->dataTransfer(); + return nullptr; +} + void JamiAccount::removeRepository(const std::string& conversationId, bool sync, bool force) { @@ -5229,7 +5297,9 @@ JamiAccount::monitor() const } void -JamiAccount::cloneConversation(const std::string& deviceId, const std::string& convId) +JamiAccount::cloneConversation(const std::string& deviceId, + const std::string&, + const std::string& convId) { JAMI_DBG("[Account %s] Clone conversation on device %s", getAccountID().c_str(), @@ -5284,192 +5354,182 @@ JamiAccount::cloneConversation(const std::string& deviceId, const std::string& c } } -DRing::DataTransferId -JamiAccount::sendFile(const std::string& to, +void +JamiAccount::sendFile(const std::string& conversationId, const std::string& path, - const InternalCompletionCb& icb, - const std::string& deviceId, - DRing::DataTransferId resendId) + const std::string& name, + const std::string& parent) { - // TODO erase manager when remove conversation or contact if (!fileutils::isFile(path)) { JAMI_ERR() << "invalid filename '" << path << "'"; - return {}; - } - - bool isConversation; - { - std::lock_guard<std::mutex> lk(conversationsMtx_); - isConversation = conversations_.find(to) != conversations_.end(); - } - - std::unique_lock<std::mutex> lk(transferMutex_); - auto it = transferManagers_.find(to); - if (it == transferManagers_.end()) { - std::string accId = getAccountID(); - auto res = transferManagers_.emplace(std::piecewise_construct, - std::forward_as_tuple(to), - std::forward_as_tuple(accId, to, isConversation)); - if (!res.second) { - JAMI_ERR("Couldn't send file %s to %s", path.c_str(), to.c_str()); - return {}; - } - it = res.first; + return; } - - auto tid = it->second.sendFile(path, icb, deviceId, resendId); - - if (isConversation) { - // Create a symlink to answer to re-ask - auto symlinkPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID() - + DIR_SEPARATOR_STR + "conversation_data" + DIR_SEPARATOR_STR + to - + DIR_SEPARATOR_STR + std::to_string(tid); - if (path != symlinkPath && !fileutils::isSymLink(symlinkPath)) { - fileutils::createSymLink(symlinkPath, path); + // NOTE: this sendMessage is in a computation thread because + // sha3sum can take quite some time to computer if the user decide + // to send a big file + dht::ThreadPool::computation().run([w = weak(), conversationId, path, name, parent]() { + if (auto shared = w.lock()) { + Json::Value value; + auto tid = jami::generateUID(); + value["tid"] = std::to_string(tid); + std::size_t found = path.find_last_of(DIR_SEPARATOR_CH); + value["displayName"] = name.empty() ? path.substr(found + 1) : name; + value["totalSize"] = std::to_string(fileutils::size(path)); + value["sha3sum"] = fileutils::sha3File(path); + value["type"] = "application/data-transfer+json"; + + shared->sendMessage(conversationId, + value, + parent, + true, + [accId = shared->getAccountID(), + conversationId, + tid, + path](bool, const std::string& commitId) { + // Create a symlink to answer to re-ask + auto symlinkPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + + accId + DIR_SEPARATOR_STR + + "conversation_data" + DIR_SEPARATOR_STR + + conversationId + DIR_SEPARATOR_STR + + commitId + "_" + std::to_string(tid); + auto extension = fileutils::getFileExtension(path); + if (!extension.empty()) + symlinkPath += "." + extension; + if (path != symlinkPath && !fileutils::isSymLink(symlinkPath)) + fileutils::createSymLink(symlinkPath, path); + }); } - } - return tid; + }); } -void -JamiAccount::askForTransfer(const std::string& conversationUri, - const std::string& interactionId, - const std::string& path) +DRing::DataTransferId +JamiAccount::sendFile(const std::string& peer, + const std::string& path, + const InternalCompletionCb& icb) { - Uri uri(conversationUri); - if (uri.scheme() != Uri::Scheme::SWARM) - return; - const auto& conversationId = uri.authority(); - DRing::DataTransferId tid; - std::string sha3sum = {}; - { - std::lock_guard<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(conversationId); - if (conversation == conversations_.end() || !conversation->second) - return; - auto commit = conversation->second->getCommit(interactionId); - if (commit == std::nullopt || commit->find("type") == commit->end() - || commit->find("sha3sum") == commit->end() || commit->find("tid") == commit->end() - || commit->at("type") != "application/data-transfer+json") - return; - sha3sum = commit->at("sha3sum"); - auto tid_str = commit->at("tid"); - std::from_chars(tid_str.data(), tid_str.data() + tid_str.size(), tid); - } - - std::unique_lock<std::mutex> lk(transferMutex_); - auto it = transferManagers_.find(conversationId); - if (it == transferManagers_.end()) { - auto res = transferManagers_.emplace(std::piecewise_construct, - std::forward_as_tuple(conversationId), - std::forward_as_tuple(getAccountID(), conversationId)); - if (!res.second) { - JAMI_ERR("Couldn't create manager for conversation %s", conversationId.c_str()); - return; - } - it = res.first; + if (!fileutils::isFile(path)) { + JAMI_ERR() << "invalid filename '" << path << "'"; + return {}; } - it->second.waitForTransfer(tid, sha3sum, path); - Json::Value askTransferValue; - askTransferValue["conversation"] = conversationId; - askTransferValue["interaction"] = interactionId; - askTransferValue["deviceId"] = std::string(currentDeviceId()); - Json::StreamWriterBuilder builder; - sendInstantMessage(conversationId, - {{MIME_TYPE_ASK_TRANSFER, Json::writeString(builder, askTransferValue)}}); + return nonSwarmTransferManager_->sendFile(path, peer, icb); } void -JamiAccount::onIncomingFileRequest(const DRing::DataTransferInfo& info, - const DRing::DataTransferId& id, - const std::function<void(const IncomingFileInfo&)>& cb, - const InternalCompletionCb& icb) -{ - auto to = info.conversationId.empty() ? info.peer : info.conversationId; - std::unique_lock<std::mutex> lk(transferMutex_); - auto it = transferManagers_.find(to); - if (it == transferManagers_.end()) { - JAMI_ERR("Couldn't accept file %lu to %s", id, to.c_str()); +JamiAccount::transferFile(const std::string& conversationId, + const std::string& path, + const std::string& deviceId, + const std::string& fileId, + const std::string& interactionId, + size_t start, + size_t end) +{ + auto channelName = "data-transfer://" + conversationId + "/" + currentDeviceId() + "/" + fileId; + std::lock_guard<std::mutex> lkCM(connManagerMtx_); + if (!connectionManager_) return; - } - - it->second.onIncomingFileRequest(info, id, cb, icb); -} - -bool -JamiAccount::acceptFile(const std::string& to, - DRing::DataTransferId id, - const std::string& path, - int64_t) -{ - std::unique_lock<std::mutex> lk(transferMutex_); - auto it = transferManagers_.find(to); - if (it == transferManagers_.end()) { - JAMI_ERR("Couldn't accept file %s to %s", path.c_str(), to.c_str()); - return false; - } - - auto res = it->second.acceptFile(id, path); - - bool isConversation; - { - std::lock_guard<std::mutex> lk(conversationsMtx_); - isConversation = conversations_.find(to) != conversations_.end(); - } - if (isConversation) { - // Create a symlink to answer to re-ask - auto symlinkPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID() - + DIR_SEPARATOR_STR + "conversation_data" + DIR_SEPARATOR_STR + to - + DIR_SEPARATOR_STR + std::to_string(id); - if (path != symlinkPath && !fileutils::isSymLink(symlinkPath)) { - fileutils::createSymLink(symlinkPath, path); - } - } - - return res; + connectionManager_->connectDevice( + DeviceId(deviceId), + channelName, + [this, conversationId, path = std::move(path), fileId, interactionId, start, end]( + std::shared_ptr<ChannelSocket> socket, const DeviceId&) { + if (!socket) + return; + dht::ThreadPool::io().run([w = weak(), + path = std::move(path), + socket = std::move(socket), + conversationId = std::move(conversationId), + fileId, + interactionId, + start, + end] { + if (auto shared = w.lock()) + if (auto dt = shared->dataTransfer(conversationId)) + dt->transferFile(socket, fileId, interactionId, path, start, end); + }); + }); } bool -JamiAccount::cancel(const std::string& to, DRing::DataTransferId id) +JamiAccount::downloadFile(const std::string& conversationId, + const std::string& interactionId, + const std::string& fileId, + const std::string& path, + size_t start, + size_t end) { - std::unique_lock<std::mutex> lk(transferMutex_); - auto it = transferManagers_.find(to); - if (it == transferManagers_.end()) { - JAMI_ERR("Couldn't cancel file %lu to %s", id, to.c_str()); + std::string sha3sum = {}; + std::lock_guard<std::mutex> lk(conversationsMtx_); + auto conversation = conversations_.find(conversationId); + if (conversation == conversations_.end() || !conversation->second) return false; - } - return it->second.cancel(id); + return conversation->second->downloadFile(interactionId, fileId, path, "", "", start, end); } -bool -JamiAccount::info(const std::string& to, DRing::DataTransferId id, DRing::DataTransferInfo& info) +void +JamiAccount::askForFileChannel(const std::string& conversationId, + const std::string& deviceId, + const std::string& fileId, + size_t start, + size_t end) { - std::unique_lock<std::mutex> lk(transferMutex_); - auto it = transferManagers_.find(to); - if (it == transferManagers_.end()) { - JAMI_ERR("Couldn't get file %lu to %s", id, to.c_str()); - return false; - } + auto tryDevice = [=](const auto& did) { + std::lock_guard<std::mutex> lkCM(connManagerMtx_); + if (!connectionManager_) + return; - return it->second.info(id, info); -} + auto channelName = "data-transfer://" + conversationId + "/" + currentDeviceId() + "/" + + fileId; + if (start != 0 || end != 0) { + channelName += "?start=" + std::to_string(start) + "&end=" + std::to_string(end); + } + // We can avoid to negotiate new sessions, as the file notif + // probably come from an online device or last connected device. + connectionManager_->connectDevice( + did, + channelName, + [this, conversationId, fileId](std::shared_ptr<ChannelSocket> channel, const DeviceId&) { + if (!channel) + return; + dht::ThreadPool::io().run([w = weak(), conversationId, channel, fileId] { + auto shared = w.lock(); + if (!shared) + return; + auto dt = shared->dataTransfer(conversationId); + if (!dt) + return; + if (fileId == "profile.vcf") + dt->onIncomingProfile(channel); + else + dt->onIncomingFileTransfer(fileId, channel); + }); + }, + false); + }; -bool -JamiAccount::bytesProgress(const std::string& to, - DRing::DataTransferId id, - int64_t& total, - int64_t& progress) -{ - std::unique_lock<std::mutex> lk(transferMutex_); - auto it = transferManagers_.find(to); - if (it == transferManagers_.end()) { - JAMI_ERR("Couldn't get file %lu to %s", id, to.c_str()); - return false; + if (!deviceId.empty()) { + // Only ask for device + tryDevice(DeviceId(deviceId)); + } else { + // Only ask for connected devices. For others we will try + // on new peer online + std::vector<std::string> members; + { + std::lock_guard<std::mutex> lk(conversationsMtx_); + auto conversation = conversations_.find(conversationId); + if (conversation != conversations_.end() && conversation->second) { + for (const auto& m : conversation->second->getMembers()) { + members.emplace_back(m.at("uri")); + } + } + } + for (const auto& member : members) { + accountManager_->forEachDevice(dht::InfoHash(member), + [this, tryDevice = std::move(tryDevice)]( + const DeviceId& dev) { tryDevice(dev); }); + } } - - return it->second.bytesProgress(id, total, progress); } } // namespace jami diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h index 251b415c13..6b4c07391f 100644 --- a/src/jamidht/jamiaccount.h +++ b/src/jamidht/jamiaccount.h @@ -340,6 +340,9 @@ public: const std::map<std::string, std::string>& payloads) override; void sendInstantMessage(const std::string& convId, const std::map<std::string, std::string>& msg) override; + void sendSIPMessageToDevice(const std::string& to, + const DeviceId& deviceId, + const std::map<std::string, std::string>& payloads); void onIsComposing(const std::string& conversationId, const std::string& peer, bool isWriting) override; @@ -380,7 +383,6 @@ public: * @param isVcard if transfer is a vcard transfer * @param channeledConnectedCb callback when channel is connected * @param onChanneledCancelled callback when channel is canceled - * @param addToHistory if we need to add the transfer to the history */ void requestConnection( const DRing::DataTransferInfo& info, @@ -388,8 +390,7 @@ public: bool isVCard, const std::function<void(const std::shared_ptr<ChanneledOutgoingTransfer>&)>& channeledConnectedCb, - const std::function<void(const std::string&)>& onChanneledCancelled, - bool addToHistory); + const std::function<void(const std::string&)>& onChanneledCancelled); /// /// Close a E2E connection between a given peer and a given transfer id. @@ -548,12 +549,14 @@ public: void sendMessage(const std::string& conversationId, const Json::Value& value, const std::string& parent = "", - bool announce = true); + bool announce = true, + const OnDoneCb& cb = {}); void sendMessage(const std::string& conversationId, const std::string& message, const std::string& parent = "", const std::string& type = "text/plain", - bool announce = true); + bool announce = true, + const OnDoneCb& cb = {}); /** * Add to the related conversation the call history message * @param uri Peer number @@ -569,18 +572,6 @@ public: const std::string& deviceId, const std::string& conversationId, const std::string& commitId) override; - - /** - * When a new transfer is asked by a peer - * @param peer account's uri of the peer - * @param deviceId peer device - * @param conversationId related conversation - * @param interactionId interaction corresponding to the transfer asked. - */ - virtual void onAskForTransfer(const std::string& peer, - const std::string& deviceId, - const std::string& conversationId, - const std::string& interactionId) override; /** * Pull remote device (do not do it if commitId is already in the current repo) * @param peer Contact URI @@ -606,42 +597,66 @@ public: * @param deviceId * @param convId */ - void cloneConversation(const std::string& deviceId, const std::string& convId); + void cloneConversation(const std::string& deviceId, + const std::string& peer, + const std::string& convId); // File transfer - DRing::DataTransferId sendFile(const std::string& to, + void sendFile(const std::string& conversationId, + const std::string& path, + const std::string& name, + const std::string& parent); + + // non-swarm version + DRing::DataTransferId sendFile(const std::string& peer, const std::string& path, - const InternalCompletionCb& icb = {}, - const std::string& deviceId = {}, - DRing::DataTransferId resendId = {}); + const InternalCompletionCb& icb = {}); + + void transferFile(const std::string& conversationId, + const std::string& path, + const std::string& deviceId, + const std::string& fileId, + const std::string& interactionId, + size_t start = 0, + size_t end = 0); /** * Ask conversation's members to send back a previous transfer to this deviec - * @param conversationUri Related conversation + * @param conversationId Related conversation * @param interactionId Related interaction + * @param fileId Related fileId * @param path where to download the file */ - void askForTransfer(const std::string& conversationUri, - const std::string& interactionId, - const std::string& path) override; - - void onIncomingFileRequest(const DRing::DataTransferInfo& info, - const DRing::DataTransferId& id, - const std::function<void(const IncomingFileInfo&)>& cb, - const InternalCompletionCb& icb); - - bool acceptFile(const std::string& to, - DRing::DataTransferId id, - const std::string& path, - int64_t progress); - - bool cancel(const std::string& to, DRing::DataTransferId id); - bool info(const std::string& to, DRing::DataTransferId id, DRing::DataTransferInfo& info); - bool bytesProgress(const std::string& to, - DRing::DataTransferId id, - int64_t& total, - int64_t& progress); + bool downloadFile(const std::string& conversationId, + const std::string& interactionId, + const std::string& fileId, + const std::string& path, + size_t start = 0, + size_t end = 0); + + void askForFileChannel(const std::string& conversationId, + const std::string& deviceId, + const std::string& fileId, + size_t start = 0, + size_t end = 0); + void loadConversations(); + /** + * Retrieve linked transfer manager + * @param id conversationId or empty for fallback + * @return linked transfer manager + */ + std::shared_ptr<TransferManager> dataTransfer(const std::string& id = "") const; + + /** + * Send Profile via cached SIP connection + * @param deviceId Device that will receive the profile + */ + // Note: when swarm will be merged, this can be moved in transferManager + bool needToSendProfile(const std::string& deviceId); + + std::string profilePath() const; + private: NON_COPYABLE(JamiAccount); @@ -711,6 +726,11 @@ private: */ void onTrackedBuddyOnline(const dht::InfoHash&); + /** + * Sync conversations with detected peer + */ + void syncConversations(const std::string& peer, const std::string& deviceId); + /** * Maps require port via UPnP and other async ops */ @@ -992,11 +1012,6 @@ private: const std::map<std::string, std::string>& data, pjsip_endpt_send_callback cb); - /** - * Send Profile via cached SIP connection - * @param deviceId Device that will receive the profile - */ - bool needToSendProfile(const std::string& deviceId); /** * Send Profile via cached SIP connection * @param deviceId Device that will receive the profile @@ -1047,8 +1062,9 @@ private: std::atomic_bool deviceAnnounced_ {false}; //// File transfer + std::shared_ptr<TransferManager> nonSwarmTransferManager_; std::mutex transferMutex_ {}; - std::map<std::string, TransferManager> transferManagers_ {}; + std::map<std::string, std::shared_ptr<TransferManager>> transferManagers_ {}; bool noSha3sumVerification_ {false}; }; diff --git a/src/jamidht/multiplexed_socket.cpp b/src/jamidht/multiplexed_socket.cpp index 8345e81105..4192701a8f 100644 --- a/src/jamidht/multiplexed_socket.cpp +++ b/src/jamidht/multiplexed_socket.cpp @@ -484,7 +484,7 @@ MultiplexedSocket::Impl::handleChannelPacket(uint16_t channel, std::vector<uint8 } } else if (pkt.size() != 0) { std::string p = std::string(pkt.begin(), pkt.end()); - JAMI_WARN("Non existing channel: %u - %.*s", channel, (int) pkt.size(), p.c_str()); + JAMI_WARN("Non existing channel: %u", channel); } } @@ -965,6 +965,10 @@ ChannelSocket::read(ValueType* buf, std::size_t len, std::error_code& ec) std::size_t ChannelSocket::write(const ValueType* buf, std::size_t len, std::error_code& ec) { + if (pimpl_->isShutdown_) { + ec = std::make_error_code(std::errc::broken_pipe); + return -1; + } if (auto ep = pimpl_->endpoint.lock()) { std::size_t sent = 0; do { diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp index f60abdda0c..683da76ffc 100644 --- a/src/sip/sipaccountbase.cpp +++ b/src/sip/sipaccountbase.cpp @@ -133,20 +133,23 @@ getIsComposing(const std::string& conversationId, bool isWriting) return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" "<isComposing><state>{}</state>{}</isComposing>", isWriting ? "active"sv : "idle"sv, - conversationId.empty()? "" : "<conversation>" + conversationId + "</conversation>"); + conversationId.empty() + ? "" + : "<conversation>" + conversationId + "</conversation>"); } std::string getDisplayed(const std::string& conversationId, const std::string& messageId) { // implementing https://tools.ietf.org/rfc/rfc5438.txt - return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" - "<imdn><message-id>{}</message-id>\n" - "{}" - "<display-notification><status><displayed/></status></display-notification>\n" - "</imdn>", - messageId, - conversationId.empty()? "" : "<conversation>" + conversationId + "</conversation>"); + return fmt::format( + "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + "<imdn><message-id>{}</message-id>\n" + "{}" + "<display-notification><status><displayed/></status></display-notification>\n" + "</imdn>", + messageId, + conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>"); } void @@ -549,7 +552,6 @@ SIPAccountBase::onTextMessage(const std::string& id, && matched_pattern[1].matched) { conversationId = matched_pattern[1]; } - JAMI_WARN("@@@ %s", m.second.c_str()); onIsComposing(conversationId, from, isComposing); if (payloads.size() == 1) return; @@ -647,20 +649,6 @@ SIPAccountBase::onTextMessage(const std::string& id, // check if we have a swarm created. It can be the case // when the trust request confirm was not received. checkIfRemoveForCompat(from); - } else if (m.first == MIME_TYPE_ASK_TRANSFER) { - Json::Value json; - std::string err; - Json::CharReaderBuilder rbuilder; - auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader()); - if (!reader->parse(m.second.data(), m.second.data() + m.second.size(), &json, &err)) { - JAMI_ERR("Can't parse server response: %s", err.c_str()); - return; - } - onAskForTransfer(from, - json["deviceId"].asString(), - json["conversation"].asString(), - json["interaction"].asString()); - return; } } diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h index b4071cdb71..c96e92997b 100644 --- a/src/sip/sipaccountbase.h +++ b/src/sip/sipaccountbase.h @@ -52,7 +52,6 @@ struct pjmedia_sdp_session; } static constexpr const char MIME_TYPE_TEXT_PLAIN[] {"text/plain"}; -static constexpr const char MIME_TYPE_ASK_TRANSFER[] {"application/data-transfer-request+json"}; namespace jami { diff --git a/test/unitTest/conversation/compability.cpp b/test/unitTest/conversation/compability.cpp index c3a6529e5a..70eabf950c 100644 --- a/test/unitTest/conversation/compability.cpp +++ b/test/unitTest/conversation/compability.cpp @@ -371,15 +371,7 @@ CompabilityTest::testSendFileCompatibility() sendFile.close(); // Send File - DRing::DataTransferInfo info; - uint64_t id; - info.accountId = aliceAccount->getAccountID(); - info.conversationId = convId; - info.path = "SEND"; - info.displayName = "SEND"; - info.bytesProgress = 0; - - CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success); + DRing::sendFile(aliceId, convId, "SEND", "SEND", ""); cv.wait_for(lk, std::chrono::seconds(30), [&]() { return successfullyReceive; }); std::remove("SEND"); diff --git a/test/unitTest/fileTransfer/fileTransfer.cpp b/test/unitTest/fileTransfer/fileTransfer.cpp index 223aefe6c8..10eefeb87f 100644 --- a/test/unitTest/fileTransfer/fileTransfer.cpp +++ b/test/unitTest/fileTransfer/fileTransfer.cpp @@ -61,23 +61,28 @@ public: private: void testFileTransfer(); + void testDataTransferInfo(); void testMultipleFileTransfer(); void testConversationFileTransfer(); void testFileTransferInConversation(); - void testReaskForTransfer(); void testBadSha3sumOut(); void testBadSha3sumIn(); void testAskToMultipleParticipants(); + void testCancelInTransfer(); + void testCancelOutTransfer(); + void testTransferInfo(); CPPUNIT_TEST_SUITE(FileTransferTest); CPPUNIT_TEST(testFileTransfer); + CPPUNIT_TEST(testDataTransferInfo); CPPUNIT_TEST(testMultipleFileTransfer); CPPUNIT_TEST(testConversationFileTransfer); CPPUNIT_TEST(testFileTransferInConversation); - CPPUNIT_TEST(testReaskForTransfer); CPPUNIT_TEST(testBadSha3sumOut); CPPUNIT_TEST(testBadSha3sumIn); CPPUNIT_TEST(testAskToMultipleParticipants); + CPPUNIT_TEST(testCancelInTransfer); + CPPUNIT_TEST(testTransferInfo); CPPUNIT_TEST_SUITE_END(); }; @@ -129,19 +134,101 @@ FileTransferTest::testFileTransfer() std::condition_variable cv2; std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; bool transferWaiting = false, transferFinished = false; - DRing::DataTransferId finalId; + std::string finalId; // Watch signals confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>( - [&](const std::string& accountId, const std::string&, const long unsigned int& id, int code) { + [&](const std::string& accountId, + const std::string&, + const std::string&, + const std::string& fileId, + int code) { + if (accountId == bobId + && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) { + transferWaiting = true; + finalId = fileId; + cv.notify_one(); + } else if (accountId == aliceId + && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { + transferFinished = true; + finalId = fileId; + cv.notify_one(); + } + })); + DRing::registerSignalHandlers(confHandlers); + + // Create file to send + std::ofstream sendFile("SEND"); + CPPUNIT_ASSERT(sendFile.is_open()); + sendFile << std::string(64000, 'A'); + sendFile.close(); + + // Send File + DRing::DataTransferInfo info; + uint64_t id; + info.accountId = aliceAccount->getAccountID(); + info.peer = bobUri; + info.path = "SEND"; + info.displayName = "SEND"; + info.bytesProgress = 0; + CPPUNIT_ASSERT(DRing::sendFileLegacy(info, id) == DRing::DataTransferError::success); + + cv.wait_for(lk, std::chrono::seconds(30)); + CPPUNIT_ASSERT(transferWaiting); + + auto rcv_path = "RECV"; + CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, finalId, rcv_path) + == DRing::DataTransferError::success); + + // Wait 2 times, both sides will got a finished status + cv.wait_for(lk, std::chrono::seconds(30)); + cv.wait_for(lk, std::chrono::seconds(30)); + CPPUNIT_ASSERT(transferFinished); + + CPPUNIT_ASSERT(compare(info.path, rcv_path)); + + // TODO FIX ME. The ICE take some time to stop and it doesn't seems to like + // when stopping the daemon and removing the accounts to soon. + std::remove("SEND"); + std::remove("RECV"); + JAMI_INFO("Waiting...."); + std::this_thread::sleep_for(std::chrono::seconds(3)); +} + +void +FileTransferTest::testDataTransferInfo() +{ + std::this_thread::sleep_for(std::chrono::seconds(5)); + // TODO remove. This sleeps is because it take some time for the DHT to be connected + // and account announced + JAMI_INFO("Waiting...."); + auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); + auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); + auto bobUri = bobAccount->getUsername(); + auto aliceUri = aliceAccount->getUsername(); + + std::mutex mtx; + std::unique_lock<std::mutex> lk {mtx}; + std::condition_variable cv; + std::condition_variable cv2; + std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; + bool transferWaiting = false, transferFinished = false; + std::string finalId; + // Watch signals + confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>( + [&](const std::string& accountId, + const std::string&, + const std::string&, + const std::string& fileId, + int code) { if (accountId == bobId && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) { transferWaiting = true; - finalId = id; + finalId = fileId; cv.notify_one(); } else if (accountId == aliceId && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { transferFinished = true; - finalId = id; + finalId = fileId; cv.notify_one(); } })); @@ -161,13 +248,27 @@ FileTransferTest::testFileTransfer() info.path = "SEND"; info.displayName = "SEND"; info.bytesProgress = 0; - CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success); + CPPUNIT_ASSERT(DRing::sendFileLegacy(info, id) == DRing::DataTransferError::success); cv.wait_for(lk, std::chrono::seconds(30)); CPPUNIT_ASSERT(transferWaiting); + CPPUNIT_ASSERT(DRing::dataTransferInfo(bobId, std::to_string(id), info) + == DRing::DataTransferError::success); + + CPPUNIT_ASSERT(info.lastEvent == DRing::DataTransferEventCode::wait_host_acceptance); + CPPUNIT_ASSERT(info.bytesProgress == 0); + CPPUNIT_ASSERT(info.totalSize == 64000); + + CPPUNIT_ASSERT(DRing::dataTransferInfo(aliceId, std::to_string(id), info) + == DRing::DataTransferError::success); + + CPPUNIT_ASSERT(info.lastEvent == DRing::DataTransferEventCode::wait_peer_acceptance); + CPPUNIT_ASSERT(info.bytesProgress == 0); + CPPUNIT_ASSERT(info.totalSize == 64000); + auto rcv_path = "RECV"; - CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, aliceUri, finalId, rcv_path, 0) + CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, finalId, rcv_path) == DRing::DataTransferError::success); // Wait 2 times, both sides will got a finished status @@ -177,6 +278,13 @@ FileTransferTest::testFileTransfer() CPPUNIT_ASSERT(compare(info.path, rcv_path)); + CPPUNIT_ASSERT(DRing::dataTransferInfo(bobId, std::to_string(id), info) + == DRing::DataTransferError::success); + + CPPUNIT_ASSERT(info.lastEvent == DRing::DataTransferEventCode::finished); + CPPUNIT_ASSERT(info.bytesProgress == 64000); + CPPUNIT_ASSERT(info.totalSize == 64000); + // TODO FIX ME. The ICE take some time to stop and it doesn't seems to like // when stopping the daemon and removing the accounts to soon. std::remove("SEND"); @@ -199,19 +307,23 @@ FileTransferTest::testMultipleFileTransfer() std::condition_variable cv2; std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; bool transferWaiting = false, transferFinished = false; - DRing::DataTransferId finalId; + std::string finalId; // Watch signals confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>( - [&](const std::string& accountId, const std::string&, const long unsigned int& id, int code) { + [&](const std::string& accountId, + const std::string&, + const std::string&, + const std::string& fileId, + int code) { if (accountId == bobId && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) { transferWaiting = true; - finalId = id; + finalId = fileId; cv.notify_one(); } else if (accountId == aliceId && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { transferFinished = true; - finalId = id; + finalId = fileId; cv.notify_one(); } })); @@ -235,14 +347,14 @@ FileTransferTest::testMultipleFileTransfer() info.path = "SEND"; info.displayName = "SEND"; info.bytesProgress = 0; - CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success); + CPPUNIT_ASSERT(DRing::sendFileLegacy(info, id) == DRing::DataTransferError::success); cv.wait_for(lk, std::chrono::seconds(30)); CPPUNIT_ASSERT(transferWaiting); transferWaiting = false; auto rcv_path = "RECV"; - CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, aliceUri, finalId, rcv_path, 0) + CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, finalId, rcv_path) == DRing::DataTransferError::success); // Wait 2 times, both sides will got a finished status @@ -259,13 +371,13 @@ FileTransferTest::testMultipleFileTransfer() info2.path = "SEND2"; info2.displayName = "SEND2"; info2.bytesProgress = 0; - CPPUNIT_ASSERT(DRing::sendFile(info2, id) == DRing::DataTransferError::success); + CPPUNIT_ASSERT(DRing::sendFileLegacy(info2, id) == DRing::DataTransferError::success); cv.wait_for(lk, std::chrono::seconds(30)); CPPUNIT_ASSERT(transferWaiting); rcv_path = "RECV2"; - CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, aliceUri, finalId, rcv_path, 0) + CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, finalId, rcv_path) == DRing::DataTransferError::success); // Wait 2 times, both sides will got a finished status @@ -316,23 +428,28 @@ FileTransferTest::testConversationFileTransfer() confHandlers.clear(); DRing::unregisterSignalHandlers(); - auto messageReceivedAlice = 0; - auto messageReceivedBob = 0; - auto messageReceivedCarla = 0; auto requestReceived = 0; auto conversationReady = 0; - long unsigned int hostAcceptanceBob = {0}, hostAcceptanceCarla = {0}; - std::vector<long unsigned int> peerAcceptance = {}, finished = {}; + auto memberJoined = 0; + std::string tidBob, tidCarla, iidBob, iidCarla; + std::string hostAcceptanceBob = {}, hostAcceptanceCarla = {}; + std::vector<std::string> peerAcceptance = {}, finished = {}; confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>( [&](const std::string& accountId, const std::string& /* conversationId */, - std::map<std::string, std::string> /*message*/) { - if (accountId == aliceId) - messageReceivedAlice += 1; - if (accountId == bobId) - messageReceivedBob += 1; - if (accountId == carlaId) - messageReceivedCarla += 1; + std::map<std::string, std::string> message) { + if (message["type"] == "application/data-transfer+json") { + if (accountId == bobId) { + tidBob = message["fileId"]; + iidBob = message["id"]; + } else if (accountId == carlaId) { + tidCarla = message["fileId"]; + iidCarla = message["id"]; + } + } else if (accountId == aliceId && message["type"] == "member" + && message["action"] == "join") { + memberJoined += 1; + } cv.notify_one(); })); confHandlers.insert( @@ -351,19 +468,23 @@ FileTransferTest::testConversationFileTransfer() cv.notify_one(); })); confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>( - [&](const std::string& accountId, const std::string&, const long unsigned int& id, int code) { + [&](const std::string& accountId, + const std::string&, + const std::string&, + const std::string& fileId, + int code) { if (code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) { if (accountId == bobId) - hostAcceptanceBob = id; + hostAcceptanceBob = fileId; else if (accountId == carlaId) - hostAcceptanceCarla = id; + hostAcceptanceCarla = fileId; cv.notify_one(); } else if (code == static_cast<int>(DRing::DataTransferEventCode::wait_peer_acceptance)) { - peerAcceptance.emplace_back(id); + peerAcceptance.emplace_back(fileId); cv.notify_one(); } else if (code == static_cast<int>(DRing::DataTransferEventCode::finished)) { - finished.emplace_back(id); + finished.emplace_back(fileId); cv.notify_one(); } })); @@ -375,49 +496,35 @@ FileTransferTest::testConversationFileTransfer() aliceAccount->addConversationMember(convId, carlaUri); cv.wait_for(lk, std::chrono::seconds(60), [&]() { return requestReceived == 2; }); - messageReceivedAlice = 0; bobAccount->acceptConversationRequest(convId); carlaAccount->acceptConversationRequest(convId); cv.wait_for(lk, std::chrono::seconds(30), [&]() { - return conversationReady == 3 && messageReceivedAlice >= 2; + return conversationReady == 3 && memberJoined == 2; }); // Send file std::ofstream sendFile("SEND"); CPPUNIT_ASSERT(sendFile.is_open()); - // Avoid ASAN error on big alloc sendFile << std::string("A", 64000); - for (int i = 0; i < 64000; ++i) - sendFile << "A"; + sendFile << std::string(64000, 'A'); sendFile.close(); - // Send File - DRing::DataTransferInfo info; - uint64_t id; - info.accountId = aliceAccount->getAccountID(); - info.conversationId = convId; - info.path = "SEND"; - info.displayName = "SEND"; - info.bytesProgress = 0; - - CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success); + DRing::sendFile(aliceId, convId, "SEND", "SEND", ""); - cv.wait_for(lk, std::chrono::seconds(45), [&]() { - return peerAcceptance.size() == 1 && hostAcceptanceBob != 0 && hostAcceptanceCarla != 0; - }); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(45), [&]() { + return !tidBob.empty() && !tidCarla.empty(); + })); - CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, convId, hostAcceptanceBob, "RCV", 0) - == DRing::DataTransferError::success); - CPPUNIT_ASSERT(DRing::acceptFileTransfer(carlaId, convId, hostAcceptanceCarla, "RCV2", 0) - == DRing::DataTransferError::success); + DRing::downloadFile(bobId, convId, iidBob, tidBob, "RCV"); + DRing::downloadFile(carlaId, convId, iidCarla, tidCarla, "RCV2"); - cv.wait_for(lk, std::chrono::seconds(45), [&]() { return finished.size() == 3; }); + CPPUNIT_ASSERT( + cv.wait_for(lk, std::chrono::seconds(45), [&]() { return finished.size() == 3; })); std::remove("SEND"); std::remove("RCV"); std::remove("RCV2"); DRing::unregisterSignalHandlers(); - std::this_thread::sleep_for(std::chrono::seconds(10)); } void @@ -437,17 +544,22 @@ FileTransferTest::testFileTransferInConversation() std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; - auto messageBobReceived = 0, messageAliceReceived = 0; bool requestReceived = false; bool conversationReady = false; + bool bobJoined = false; + std::string tidBob, iidBob; confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>( [&](const std::string& accountId, const std::string& /* conversationId */, - std::map<std::string, std::string> /*message*/) { - if (accountId == bobId) { - messageBobReceived += 1; - } else { - messageAliceReceived += 1; + std::map<std::string, std::string> message) { + if (message["type"] == "application/data-transfer+json") { + if (accountId == bobId) { + tidBob = message["fileId"]; + iidBob = message["id"]; + } + } + if (accountId == aliceId && message["type"] == "member" && message["action"] == "join") { + bobJoined = true; } cv.notify_one(); })); @@ -466,25 +578,22 @@ FileTransferTest::testFileTransferInConversation() cv.notify_one(); } })); - bool transferWaiting = false, transferFinished = false; - DRing::DataTransferId finalId; + bool transferAFinished = false, transferBFinished = false; // Watch signals confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>( [&](const std::string& accountId, const std::string& conversationId, - const long unsigned int& id, + const std::string&, + const std::string&, int code) { - if (accountId == bobId && conversationId == convId - && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) { - transferWaiting = true; - finalId = id; - cv.notify_one(); - } else if (accountId == aliceId && conversationId == convId - && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { - transferFinished = true; - finalId = id; - cv.notify_one(); + if (code == static_cast<int>(DRing::DataTransferEventCode::finished) + && conversationId == convId) { + if (accountId == aliceId) + transferAFinished = true; + else if (accountId == bobId) + transferBFinished = true; } + cv.notify_one(); })); DRing::registerSignalHandlers(confHandlers); @@ -493,32 +602,25 @@ FileTransferTest::testFileTransferInConversation() bobAccount->acceptConversationRequest(convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { - return conversationReady && messageAliceReceived == 1; + return conversationReady && bobJoined; })); // Create file to send std::ofstream sendFile("SEND"); CPPUNIT_ASSERT(sendFile.is_open()); - // Avoid ASAN error on big alloc sendFile << std::string("A", 64000); - for (int i = 0; i < 64000; ++i) - sendFile << "A"; + sendFile << std::string(64000, 'A'); sendFile.close(); - // Send File - DRing::DataTransferInfo info; - uint64_t id; - info.accountId = aliceAccount->getAccountID(); - info.conversationId = convId; - info.path = "SEND"; - info.displayName = "SEND"; - info.bytesProgress = 0; - CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success); - CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferWaiting; })); - std::this_thread::sleep_for(std::chrono::seconds(30)); + DRing::sendFile(aliceId, convId, "SEND", "SEND", ""); - CPPUNIT_ASSERT(DRing::acceptFileTransfer(bobId, convId, id, "RECV", 0) - == DRing::DataTransferError::success); - CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferFinished; })); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return !tidBob.empty(); })); + + transferAFinished = false; + transferBFinished = false; + DRing::downloadFile(bobId, convId, iidBob, tidBob, "RECV"); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { + return transferAFinished && transferBFinished; + })); std::remove("SEND"); std::remove("RECV"); @@ -527,7 +629,7 @@ FileTransferTest::testFileTransferInConversation() } void -FileTransferTest::testReaskForTransfer() +FileTransferTest::testBadSha3sumOut() { std::this_thread::sleep_for(std::chrono::seconds(5)); // TODO remove. This sleeps is because it take some time for the DHT to be connected @@ -546,14 +648,15 @@ FileTransferTest::testReaskForTransfer() bool requestReceived = false; bool conversationReady = false; bool memberJoin = false; - std::string mid = {}; + std::string mid = {}, iid; confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>( [&](const std::string& accountId, const std::string& /* conversationId */, std::map<std::string, std::string> message) { if (accountId == bobId) { if (message["type"] == "application/data-transfer+json") { - mid = message["id"]; + mid = message["fileId"]; + iid = message["id"]; } } cv.notify_one(); @@ -573,25 +676,22 @@ FileTransferTest::testReaskForTransfer() cv.notify_one(); } })); - bool transferWaiting = false, transferFinished = false; - DRing::DataTransferId finalId; + bool transferAFinished = false, transferBFinished = false; // Watch signals confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>( [&](const std::string& accountId, const std::string& conversationId, - const long unsigned int& id, + const std::string&, + const std::string&, int code) { - if (accountId == bobId && conversationId == convId - && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) { - transferWaiting = true; - finalId = id; - cv.notify_one(); - } else if (accountId == aliceId && conversationId == convId - && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { - transferFinished = true; - finalId = id; - cv.notify_one(); + if (conversationId == convId + && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { + if (accountId == aliceId) + transferAFinished = true; + if (accountId == bobId) + transferBFinished = true; } + cv.notify_one(); })); confHandlers.insert( DRing::exportable_callback<DRing::ConversationSignal::ConversationMemberEvent>( @@ -618,43 +718,35 @@ FileTransferTest::testReaskForTransfer() // Create file to send std::ofstream sendFile("SEND"); CPPUNIT_ASSERT(sendFile.is_open()); - // Avoid ASAN error on big alloc sendFile << std::string("A", 64000); - for (int i = 0; i < 64000; ++i) - sendFile << "A"; + sendFile << std::string(64000, 'A'); sendFile.close(); - // Send File - DRing::DataTransferInfo info; - uint64_t id; - info.accountId = aliceAccount->getAccountID(); - info.conversationId = convId; - info.path = "SEND"; - info.displayName = "SEND"; - info.bytesProgress = 0; - CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success); - CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferWaiting; })); - std::this_thread::sleep_for(std::chrono::seconds(5)); + DRing::sendFile(aliceId, convId, "SEND", "SEND", ""); - CPPUNIT_ASSERT(DRing::cancelDataTransfer(bobId, convId, id) - == DRing::DataTransferError::success); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return !mid.empty(); })); - std::this_thread::sleep_for(std::chrono::seconds(10)); - CPPUNIT_ASSERT(mid != ""); - transferWaiting = false; - transferFinished = false; - DRing::askForTransfer(bobId, "swarm:" + convId, mid, "RECV"); + // modifiy file + sendFile = std::ofstream("SEND"); + CPPUNIT_ASSERT(sendFile.is_open()); + // Avoid ASAN error on big alloc sendFile << std::string("B", 64000); + for (int i = 0; i < 64000; ++i) + sendFile << "B"; + sendFile.close(); - CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferFinished; })); - CPPUNIT_ASSERT(fileutils::isFile("RECV")); + transferAFinished = false; + transferBFinished = false; + DRing::downloadFile(bobId, convId, iid, mid, "RECV"); + + // The file transfer will not be sent as modified + CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferAFinished; })); + CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferBFinished; })); std::remove("SEND"); - std::remove("RECV"); DRing::unregisterSignalHandlers(); - std::this_thread::sleep_for(std::chrono::seconds(5)); } void -FileTransferTest::testBadSha3sumOut() +FileTransferTest::testBadSha3sumIn() { std::this_thread::sleep_for(std::chrono::seconds(5)); // TODO remove. This sleeps is because it take some time for the DHT to be connected @@ -673,14 +765,15 @@ FileTransferTest::testBadSha3sumOut() bool requestReceived = false; bool conversationReady = false; bool memberJoin = false; - std::string mid = {}; + std::string mid = {}, iid; confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>( [&](const std::string& accountId, const std::string& /* conversationId */, std::map<std::string, std::string> message) { if (accountId == bobId) { if (message["type"] == "application/data-transfer+json") { - mid = message["id"]; + mid = message["fileId"]; + iid = message["id"]; } } cv.notify_one(); @@ -700,25 +793,22 @@ FileTransferTest::testBadSha3sumOut() cv.notify_one(); } })); - bool transferWaiting = false, transferFinished = false; - DRing::DataTransferId finalId; + bool transferAFinished = false, transferBFinished = false; // Watch signals confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>( [&](const std::string& accountId, const std::string& conversationId, - const long unsigned int& id, + const std::string&, + const std::string&, int code) { - if (accountId == bobId && conversationId == convId - && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) { - transferWaiting = true; - finalId = id; - cv.notify_one(); - } else if (accountId == aliceId && conversationId == convId - && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { - transferFinished = true; - finalId = id; - cv.notify_one(); + if (conversationId == convId + && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { + if (accountId == aliceId) + transferAFinished = true; + if (accountId == bobId) + transferBFinished = true; } + cv.notify_one(); })); confHandlers.insert( DRing::exportable_callback<DRing::ConversationSignal::ConversationMemberEvent>( @@ -738,57 +828,41 @@ FileTransferTest::testBadSha3sumOut() CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); bobAccount->acceptConversationRequest(convId); - CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return conversationReady && memberJoin; })); // Create file to send std::ofstream sendFile("SEND"); CPPUNIT_ASSERT(sendFile.is_open()); - // Avoid ASAN error on big alloc sendFile << std::string("A", 64000); - for (int i = 0; i < 64000; ++i) - sendFile << "A"; + sendFile << std::string(64000, 'A'); sendFile.close(); - // Send File - DRing::DataTransferInfo info; - uint64_t id; - info.accountId = aliceAccount->getAccountID(); - info.conversationId = convId; - info.path = "SEND"; - info.displayName = "SEND"; - info.bytesProgress = 0; - CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success); - CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferWaiting; })); - std::this_thread::sleep_for(std::chrono::seconds(5)); - - CPPUNIT_ASSERT(DRing::cancelDataTransfer(bobId, convId, id) - == DRing::DataTransferError::success); + aliceAccount->noSha3sumVerification(true); + DRing::sendFile(aliceId, convId, "SEND", "SEND", ""); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return !mid.empty(); })); - std::this_thread::sleep_for(std::chrono::seconds(10)); // modifiy file sendFile = std::ofstream("SEND"); CPPUNIT_ASSERT(sendFile.is_open()); // Avoid ASAN error on big alloc sendFile << std::string("B", 64000); - for (int i = 0; i < 64000; ++i) - sendFile << "B"; + sendFile << std::string(64000, 'B'); sendFile.close(); - CPPUNIT_ASSERT(mid != ""); - transferWaiting = false; - transferFinished = false; - DRing::askForTransfer(bobId, "swarm:" + convId, mid, "RECV"); + transferAFinished = false; + transferBFinished = false; + DRing::downloadFile(bobId, convId, iid, mid, "RECV"); - // The file transfer will not be sent as modified - CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferFinished; })); + // The file transfer will be sent but refused by bob + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferAFinished; })); + CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferBFinished; })); std::remove("SEND"); DRing::unregisterSignalHandlers(); - std::this_thread::sleep_for(std::chrono::seconds(5)); } void -FileTransferTest::testBadSha3sumIn() +FileTransferTest::testAskToMultipleParticipants() { std::this_thread::sleep_for(std::chrono::seconds(5)); // TODO remove. This sleeps is because it take some time for the DHT to be connected @@ -796,8 +870,10 @@ FileTransferTest::testBadSha3sumIn() JAMI_INFO("Waiting...."); auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); - auto bobUri = bobAccount->getUsername(); + auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); auto aliceUri = aliceAccount->getUsername(); + auto bobUri = bobAccount->getUsername(); + auto carlaUri = carlaAccount->getUsername(); auto convId = aliceAccount->startConversation(); std::mutex mtx; @@ -807,14 +883,18 @@ FileTransferTest::testBadSha3sumIn() bool requestReceived = false; bool conversationReady = false; bool memberJoin = false; - std::string mid = {}; + std::string bobTid, carlaTid, iidBob, iidCarla; confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>( [&](const std::string& accountId, const std::string& /* conversationId */, std::map<std::string, std::string> message) { - if (accountId == bobId) { - if (message["type"] == "application/data-transfer+json") { - mid = message["id"]; + if (message["type"] == "application/data-transfer+json") { + if (accountId == bobId) { + bobTid = message["fileId"]; + iidBob = message["id"]; + } else if (accountId == carlaId) { + carlaTid = message["fileId"]; + iidCarla = message["id"]; } } cv.notify_one(); @@ -829,29 +909,25 @@ FileTransferTest::testBadSha3sumIn() })); confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>( [&](const std::string& accountId, const std::string& /* conversationId */) { - if (accountId == bobId) { + if (accountId == bobId || accountId == carlaId) { conversationReady = true; cv.notify_one(); } })); - bool transferWaiting = false, transferInFinished = false, transferOutFinished = false; - DRing::DataTransferId finalId; + bool transferBFinished = false, transferCFinished = false; // Watch signals confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>( [&](const std::string& accountId, const std::string& conversationId, - const long unsigned int& id, + const std::string&, + const std::string&, int code) { - finalId = id; - if (accountId == bobId && conversationId == convId - && code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) { - transferWaiting = true; - } else if (accountId == bobId && conversationId == convId - && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { - transferInFinished = true; - } else if (accountId == aliceId && conversationId == convId - && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { - transferOutFinished = true; + if (conversationId == convId + && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { + if (accountId == carlaId) + transferCFinished = true; + if (accountId == bobId) + transferBFinished = true; } cv.notify_one(); })); @@ -861,8 +937,8 @@ FileTransferTest::testBadSha3sumIn() const std::string& conversationId, const std::string& uri, int event) { - if (accountId == aliceId && conversationId == convId && uri == bobUri - && event == 1) { + if (accountId == aliceId && conversationId == convId + && (uri == bobUri || uri == carlaUri) && event == 1) { memberJoin = true; } cv.notify_one(); @@ -873,61 +949,52 @@ FileTransferTest::testBadSha3sumIn() CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); bobAccount->acceptConversationRequest(convId); - CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return conversationReady && memberJoin; })); - // Create file to send - std::ofstream sendFile("SEND"); - CPPUNIT_ASSERT(sendFile.is_open()); - // Avoid ASAN error on big alloc sendFile << std::string("A", 64000); - for (int i = 0; i < 64000; ++i) - sendFile << "A"; - sendFile.close(); + requestReceived = false; + conversationReady = false; + memberJoin = false; - // Send File - DRing::DataTransferInfo info; - uint64_t id; - info.accountId = aliceAccount->getAccountID(); - info.conversationId = convId; - info.path = "SEND"; - info.displayName = "SEND"; - info.bytesProgress = 0; - CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success); - CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferWaiting; })); - std::this_thread::sleep_for(std::chrono::seconds(5)); + aliceAccount->addConversationMember(convId, carlaUri); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - CPPUNIT_ASSERT(DRing::cancelDataTransfer(bobId, convId, id) - == DRing::DataTransferError::success); + carlaAccount->acceptConversationRequest(convId); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { + return conversationReady && memberJoin; + })); - std::this_thread::sleep_for(std::chrono::seconds(10)); - // modifiy file - sendFile = std::ofstream("SEND"); + // Create file to send + std::ofstream sendFile("SEND"); CPPUNIT_ASSERT(sendFile.is_open()); - // Avoid ASAN error on big alloc sendFile << std::string("B", 64000); - for (int i = 0; i < 64000; ++i) - sendFile << "B"; + sendFile << std::string(64000, 'A'); sendFile.close(); - CPPUNIT_ASSERT(mid != ""); - transferWaiting = false; - transferInFinished = false; - transferOutFinished = false; - DRing::askForTransfer(bobId, "swarm:" + convId, mid, "RECV"); - aliceAccount->noSha3sumVerification(true); + DRing::sendFile(aliceId, convId, "SEND", "SEND", ""); - CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { - return transferOutFinished && transferInFinished; + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { + return !bobTid.empty() && !carlaTid.empty(); })); - CPPUNIT_ASSERT(!fileutils::isFile("RECV")); + + transferCFinished = false; + DRing::downloadFile(carlaId, convId, iidCarla, carlaTid, "RECV2"); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferCFinished; })); + CPPUNIT_ASSERT(fileutils::isFile("RECV2")); + + transferBFinished = false; + DRing::downloadFile(bobId, convId, iidBob, bobTid, "RECV"); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferBFinished; })); + CPPUNIT_ASSERT(fileutils::isFile("RECV")); std::remove("SEND"); + std::remove("RECV"); + std::remove("RECV2"); DRing::unregisterSignalHandlers(); - std::this_thread::sleep_for(std::chrono::seconds(5)); } void -FileTransferTest::testAskToMultipleParticipants() +FileTransferTest::testCancelInTransfer() { std::this_thread::sleep_for(std::chrono::seconds(5)); // TODO remove. This sleeps is because it take some time for the DHT to be connected @@ -935,10 +1002,8 @@ FileTransferTest::testAskToMultipleParticipants() JAMI_INFO("Waiting...."); auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); - auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); - auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto carlaUri = carlaAccount->getUsername(); + auto aliceUri = aliceAccount->getUsername(); auto convId = aliceAccount->startConversation(); std::mutex mtx; @@ -947,17 +1012,21 @@ FileTransferTest::testAskToMultipleParticipants() std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; bool requestReceived = false; bool conversationReady = false; - bool memberJoin = false; - std::string mid = {}; + bool bobJoined = false; + std::string tidBob, iidBob; confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>( [&](const std::string& accountId, const std::string& /* conversationId */, std::map<std::string, std::string> message) { - if (accountId == bobId) { - if (message["type"] == "application/data-transfer+json") { - mid = message["id"]; + if (message["type"] == "application/data-transfer+json") { + if (accountId == bobId) { + iidBob = message["id"]; + tidBob = message["fileId"]; } } + if (accountId == aliceId && message["type"] == "member" && message["action"] == "join") { + bobJoined = true; + } cv.notify_one(); })); confHandlers.insert( @@ -970,53 +1039,30 @@ FileTransferTest::testAskToMultipleParticipants() })); confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>( [&](const std::string& accountId, const std::string& /* conversationId */) { - if (accountId == bobId || accountId == carlaId) { + if (accountId == bobId) { conversationReady = true; cv.notify_one(); } })); - bool transferWaiting = false, transferFinished = false, transferCarlaFinished = false; - long unsigned int hostAcceptanceBob = {0}, hostAcceptanceCarla = {0}; - DRing::DataTransferId finalId; + bool transferBOngoing = false, transferBFinished = false; // Watch signals confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>( [&](const std::string& accountId, const std::string& conversationId, - const long unsigned int& id, + const std::string&, + const std::string&, int code) { - if (code == static_cast<int>(DRing::DataTransferEventCode::wait_host_acceptance)) { + if (code == static_cast<int>(DRing::DataTransferEventCode::ongoing) + && conversationId == convId) { if (accountId == bobId) - hostAcceptanceBob = id; - else if (accountId == carlaId) - hostAcceptanceCarla = id; - } else if (accountId == bobId && conversationId == convId - && code - == static_cast<int>( - DRing::DataTransferEventCode::wait_host_acceptance)) { - transferWaiting = true; - hostAcceptanceBob = id; - } else if (accountId == bobId && conversationId == convId - && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { - transferFinished = true; - finalId = id; - } else if (accountId == carlaId && conversationId == convId - && code == static_cast<int>(DRing::DataTransferEventCode::finished)) { - transferCarlaFinished = true; + transferBOngoing = true; + } else if (code >= static_cast<int>(DRing::DataTransferEventCode::finished) + && conversationId == convId) { + if (accountId == bobId) + transferBFinished = true; } cv.notify_one(); })); - confHandlers.insert( - DRing::exportable_callback<DRing::ConversationSignal::ConversationMemberEvent>( - [&](const std::string& accountId, - const std::string& conversationId, - const std::string& uri, - int event) { - if (accountId == aliceId && conversationId == convId - && (uri == bobUri || uri == carlaUri) && event == 1) { - memberJoin = true; - } - cv.notify_one(); - })); DRing::registerSignalHandlers(confHandlers); aliceAccount->addConversationMember(convId, bobUri); @@ -1024,63 +1070,141 @@ FileTransferTest::testAskToMultipleParticipants() bobAccount->acceptConversationRequest(convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { - return conversationReady && memberJoin; + return conversationReady && bobJoined; })); - requestReceived = false; - conversationReady = false; - memberJoin = false; + // Create file to send + std::ofstream sendFile("SEND"); + CPPUNIT_ASSERT(sendFile.is_open()); + sendFile << std::string(64000, 'A'); + sendFile.close(); - JAMI_ERR("@@@"); + DRing::sendFile(aliceId, convId, "SEND", "SEND", ""); - aliceAccount->addConversationMember(convId, carlaUri); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return !tidBob.empty(); })); + + transferBOngoing = false; + CPPUNIT_ASSERT(DRing::downloadFile(bobId, convId, iidBob, tidBob, "RECV")); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferBOngoing; })); + transferBFinished = false; + DRing::cancelDataTransfer(bobId, convId, tidBob); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferBFinished; })); + CPPUNIT_ASSERT(!fileutils::isFile("RECV")); + + std::remove("SEND"); + DRing::unregisterSignalHandlers(); +} + +void +FileTransferTest::testTransferInfo() +{ + std::this_thread::sleep_for(std::chrono::seconds(5)); + // TODO remove. This sleeps is because it take some time for the DHT to be connected + // and account announced + JAMI_INFO("Waiting...."); + auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); + auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); + auto bobUri = bobAccount->getUsername(); + auto aliceUri = aliceAccount->getUsername(); + auto convId = aliceAccount->startConversation(); + + std::mutex mtx; + std::unique_lock<std::mutex> lk {mtx}; + std::condition_variable cv; + std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; + bool requestReceived = false; + bool conversationReady = false; + bool bobJoined = false; + std::string tidBob, iidBob; + confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>( + [&](const std::string& accountId, + const std::string& /* conversationId */, + std::map<std::string, std::string> message) { + if (message["type"] == "application/data-transfer+json") { + if (accountId == bobId) { + tidBob = message["fileId"]; + iidBob = message["id"]; + } + } + if (accountId == aliceId && message["type"] == "member" && message["action"] == "join") { + bobJoined = true; + } + cv.notify_one(); + })); + confHandlers.insert( + DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>( + [&](const std::string& /*accountId*/, + const std::string& /* conversationId */, + std::map<std::string, std::string> /*metadatas*/) { + requestReceived = true; + cv.notify_one(); + })); + confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>( + [&](const std::string& accountId, const std::string& /* conversationId */) { + if (accountId == bobId) { + conversationReady = true; + cv.notify_one(); + } + })); + bool transferAFinished = false, transferBFinished = false; + // Watch signals + confHandlers.insert(DRing::exportable_callback<DRing::DataTransferSignal::DataTransferEvent>( + [&](const std::string& accountId, + const std::string& conversationId, + const std::string&, + const std::string&, + int code) { + if (code == static_cast<int>(DRing::DataTransferEventCode::finished) + && conversationId == convId) { + if (accountId == aliceId) + transferAFinished = true; + else if (accountId == bobId) + transferBFinished = true; + } + cv.notify_one(); + })); + DRing::registerSignalHandlers(confHandlers); + + aliceAccount->addConversationMember(convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - carlaAccount->acceptConversationRequest(convId); + bobAccount->acceptConversationRequest(convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { - return conversationReady && memberJoin; + return conversationReady && bobJoined; })); // Create file to send std::ofstream sendFile("SEND"); CPPUNIT_ASSERT(sendFile.is_open()); - // Avoid ASAN error on big alloc sendFile << std::string("A", 64000); - for (int i = 0; i < 64000; ++i) - sendFile << "A"; + sendFile << std::string(64000, 'A'); sendFile.close(); - // Send File - DRing::DataTransferInfo info; - uint64_t id; - info.accountId = aliceAccount->getAccountID(); - info.conversationId = convId; - info.path = "SEND"; - info.displayName = "SEND"; - info.bytesProgress = 0; - CPPUNIT_ASSERT(DRing::sendFile(info, id) == DRing::DataTransferError::success); + DRing::sendFile(aliceId, convId, "SEND", "SEND", ""); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return !tidBob.empty(); })); + + int64_t totalSize, bytesProgress; + std::string path; + CPPUNIT_ASSERT(DRing::fileTransferInfo(bobId, convId, tidBob, path, totalSize, bytesProgress) + == DRing::DataTransferError::invalid_argument); + CPPUNIT_ASSERT(bytesProgress == 0); + CPPUNIT_ASSERT(!fileutils::isFile(path)); + // No check for total as not started + + transferAFinished = false; + transferBFinished = false; + DRing::downloadFile(bobId, convId, iidBob, tidBob, "RECV"); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { - return hostAcceptanceBob != 0 && hostAcceptanceCarla != 0; + return transferAFinished && transferBFinished; })); - - CPPUNIT_ASSERT(DRing::cancelDataTransfer(bobId, convId, hostAcceptanceBob) - == DRing::DataTransferError::success); - CPPUNIT_ASSERT(DRing::acceptFileTransfer(carlaId, convId, hostAcceptanceCarla, "RECV2", 0) + CPPUNIT_ASSERT(DRing::fileTransferInfo(bobId, convId, tidBob, path, totalSize, bytesProgress) == DRing::DataTransferError::success); - CPPUNIT_ASSERT( - cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferCarlaFinished; })); - CPPUNIT_ASSERT(fileutils::isFile("RECV2")); - std::this_thread::sleep_for(std::chrono::seconds(10)); - CPPUNIT_ASSERT(mid != ""); - transferFinished = false; - DRing::askForTransfer(bobId, "swarm:" + convId, mid, "RECV"); - - CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return transferFinished; })); - CPPUNIT_ASSERT(fileutils::isFile("RECV")); + CPPUNIT_ASSERT(bytesProgress == 64000); + CPPUNIT_ASSERT(totalSize == 64000); + CPPUNIT_ASSERT(fileutils::isFile(path)); std::remove("SEND"); std::remove("RECV"); - std::remove("RECV2"); DRing::unregisterSignalHandlers(); std::this_thread::sleep_for(std::chrono::seconds(5)); } -- GitLab