From 17524a8c42b7a711fa5f8a9ae67a2198e8a4d2be Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois-Simon=20Fauteux-Chapleau?=
 <francois-simon.fauteux-chapleau@savoirfairelinux.com>
Date: Fri, 18 Apr 2025 17:16:15 -0400
Subject: [PATCH] conversation: fix partial file transfer issues

GitLab: #1118
Change-Id: I4de38d343022ec2712a7e649d73fbdb1da068eb3
---
 src/jamidht/conversation.cpp                | 33 +++++---
 src/jamidht/conversation.h                  |  6 +-
 src/jamidht/conversation_module.cpp         |  6 +-
 src/jamidht/conversation_module.h           |  4 +-
 test/unitTest/fileTransfer/fileTransfer.cpp | 94 ++++++++++++++++++++-
 5 files changed, 118 insertions(+), 25 deletions(-)

diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index c37aa11d66..a41545e990 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -1943,12 +1943,7 @@ Conversation::sync(const std::string& member,
         auto sthis = w.lock();
         // For waiting request, downloadFile
         for (const auto& wr : sthis->dataTransfer()->waitingRequests()) {
-            auto path = fileutils::get_data_dir() / sthis->pimpl_->accountId_
-                        / "conversation_data" / sthis->id() / wr.fileId;
-            auto start = fileutils::size(path);
-            if (start < 0)
-                start = 0;
-            sthis->downloadFile(wr.interactionId, wr.fileId, wr.path, member, deviceId, start);
+            sthis->downloadFile(wr.interactionId, wr.fileId, wr.path, member, deviceId);
         }
     });
 }
@@ -2139,9 +2134,7 @@ 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)
+                           const std::string& deviceId)
 {
     auto commit = getCommit(interactionId);
     if (commit == std::nullopt || commit->at("type") != "application/data-transfer+json") {
@@ -2170,10 +2163,26 @@ Conversation::downloadFile(const std::string& interactionId,
                                 interactionId,
                                 sha3sum = sha3sum->second,
                                 path,
-                                totalSize,
-                                start,
-                                end] {
+                                totalSize] {
         if (auto shared = w.lock()) {
+            std::filesystem::path filePath(path);
+            if (filePath.empty()) {
+                filePath = shared->dataTransfer()->path(fileId);
+            }
+
+            if (fileutils::size(filePath) == totalSize) {
+                if (fileutils::sha3File(filePath) == sha3sum) {
+                    JAMI_WARNING("Ignoring request to download existing file: {}", filePath);
+                    return;
+                }
+            }
+
+            auto tempFilePath = filePath + std::string(".tmp");
+            auto start = fileutils::size(tempFilePath);
+            if (start < 0)
+                start = 0;
+            size_t end = 0;
+
             auto acc = shared->pimpl_->account_.lock();
             if (!acc)
                 return;
diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h
index f4458e7da4..62cf467f6c 100644
--- a/src/jamidht/conversation.h
+++ b/src/jamidht/conversation.h
@@ -426,17 +426,13 @@ public:
      * @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);
+                      const std::string& deviceId = "");
 
     /**
      * Reset fetched information
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
index a973d08102..c80b32209b 100644
--- a/src/jamidht/conversation_module.cpp
+++ b/src/jamidht/conversation_module.cpp
@@ -2409,14 +2409,12 @@ bool
 ConversationModule::downloadFile(const std::string& conversationId,
                                  const std::string& interactionId,
                                  const std::string& fileId,
-                                 const std::string& path,
-                                 size_t start,
-                                 size_t end)
+                                 const std::string& path)
 {
     if (auto conv = pimpl_->getConversation(conversationId)) {
         std::lock_guard lk(conv->mtx);
         if (conv->conversation)
-            return conv->conversation->downloadFile(interactionId, fileId, path, "", "", start, end);
+            return conv->conversation->downloadFile(interactionId, fileId, path, "", "");
     }
     return false;
 }
diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h
index b3024b70fa..71fed14091 100644
--- a/src/jamidht/conversation_module.h
+++ b/src/jamidht/conversation_module.h
@@ -296,9 +296,7 @@ public:
     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);
+                      const std::string& path);
 
     // Sync
     /**
diff --git a/test/unitTest/fileTransfer/fileTransfer.cpp b/test/unitTest/fileTransfer/fileTransfer.cpp
index 81f5701fd7..50d5fcc667 100644
--- a/test/unitTest/fileTransfer/fileTransfer.cpp
+++ b/test/unitTest/fileTransfer/fileTransfer.cpp
@@ -97,7 +97,8 @@ private:
     void testBadSha3sumIn();
     void testAskToMultipleParticipants();
     void testCancelInTransfer();
-    void testCancelOutTransfer();
+    void testResumeTransferAfterInterruption();
+    void testDontDownloadExistingFile();
     void testTransferInfo();
     void testRemoveHardLink();
     void testTooLarge();
@@ -111,6 +112,8 @@ private:
     CPPUNIT_TEST(testBadSha3sumIn);
     CPPUNIT_TEST(testAskToMultipleParticipants);
     CPPUNIT_TEST(testCancelInTransfer);
+    CPPUNIT_TEST(testResumeTransferAfterInterruption);
+    CPPUNIT_TEST(testDontDownloadExistingFile);
     CPPUNIT_TEST(testTransferInfo);
     CPPUNIT_TEST(testRemoveHardLink);
     CPPUNIT_TEST(testTooLarge);
@@ -532,6 +535,95 @@ FileTransferTest::testCancelInTransfer()
     CPPUNIT_ASSERT(!bobAccount->dataTransfer(convId)->isWaiting(fileId));
 }
 
+void
+FileTransferTest::testResumeTransferAfterInterruption()
+{
+    // Create conversation
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
+    connectSignals();
+    auto convId = libjami::startConversation(aliceId);
+
+    libjami::addConversationMember(aliceId, convId, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::acceptConversationRequest(bobId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
+
+    // Create file to send
+    constexpr int64_t totalSize = 12800000;
+    std::ofstream sendFile(sendPath);
+    CPPUNIT_ASSERT(sendFile.is_open());
+    sendFile << std::string(totalSize, 'A');
+    sendFile.close();
+
+    // Send file info
+    auto bobMsgSize = bobData.messages.size();
+    libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
+    auto id = bobData.messages.rbegin()->id;
+    auto fileId = bobData.messages.rbegin()->body["fileId"];
+
+    libjami::downloadFile(bobId, convId, id, fileId, recvPath);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.code == static_cast<int>(libjami::DataTransferEventCode::ongoing); }));
+
+    Manager::instance().sendRegister(aliceId, false);
+
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.code == static_cast<int>(libjami::DataTransferEventCode::closed_by_host); }));
+
+    int64_t receivedSize = fileutils::size(recvPath + std::string(".tmp"));
+    CPPUNIT_ASSERT(receivedSize < totalSize);
+    CPPUNIT_ASSERT(0 < receivedSize);
+    CPPUNIT_ASSERT(bobAccount->dataTransfer(convId)->isWaiting(fileId));
+
+    Manager::instance().sendRegister(aliceId, true);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.code == static_cast<int>(libjami::DataTransferEventCode::finished); }));
+    CPPUNIT_ASSERT(!bobAccount->dataTransfer(convId)->isWaiting(fileId));
+}
+
+void
+FileTransferTest::testDontDownloadExistingFile()
+{
+    // Create conversation
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
+    connectSignals();
+    auto convId = libjami::startConversation(aliceId);
+
+    libjami::addConversationMember(aliceId, convId, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::acceptConversationRequest(bobId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
+
+    // Create file to send
+    constexpr int64_t totalSize = 128000;
+    std::ofstream sendFile(sendPath);
+    CPPUNIT_ASSERT(sendFile.is_open());
+    sendFile << std::string(totalSize, 'A');
+    sendFile.close();
+
+    std::filesystem::copy(sendPath, recvPath);
+
+    // Send file info
+    auto bobMsgSize = bobData.messages.size();
+    libjami::sendFile(aliceId, convId, sendPath, "SEND", "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
+    auto id = bobData.messages.rbegin()->id;
+    auto fileId = bobData.messages.rbegin()->body["fileId"];
+
+    libjami::downloadFile(bobId, convId, id, fileId, recvPath);
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobData.code > 0; }));
+    CPPUNIT_ASSERT(!bobAccount->dataTransfer(convId)->isWaiting(fileId));
+    CPPUNIT_ASSERT(fileutils::size(recvPath) == totalSize);
+}
+
 void
 FileTransferTest::testTransferInfo()
 {
-- 
GitLab