From 6030be0dfcac9b44dcc9410970f2057427e85ea9 Mon Sep 17 00:00:00 2001
From: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
Date: Wed, 5 Jun 2024 15:54:39 -0400
Subject: [PATCH] data transfer: download to temporary file, rename on
 completion

GitLab: #972
Change-Id: Ic3f03fdc8fc28b387add46d1a59bf0bc15418595
---
 src/data_transfer.cpp                    | 44 +++++++++++++++---------
 src/data_transfer.h                      |  7 ++--
 src/jamidht/jamiaccount.cpp              |  6 ++--
 src/jamidht/transfer_channel_handler.cpp |  7 ++--
 4 files changed, 38 insertions(+), 26 deletions(-)

diff --git a/src/data_transfer.cpp b/src/data_transfer.cpp
index 638d542cd1..e579b96fb9 100644
--- a/src/data_transfer.cpp
+++ b/src/data_transfer.cpp
@@ -154,8 +154,9 @@ IncomingFile::IncomingFile(const std::shared_ptr<dhtnet::ChannelSocket>& channel
                            const std::string& sha3Sum)
     : FileInfo(channel, fileId, interactionId, info)
     , sha3Sum_(sha3Sum)
+    , path_(info.path + ".tmp")
 {
-    stream_.open(std::filesystem::path(info_.path),
+    stream_.open(path_,
                  std::ios::binary | std::ios::out | std::ios::app);
     if (!stream_)
         return;
@@ -188,7 +189,7 @@ IncomingFile::cancel()
 void
 IncomingFile::process()
 {
-    channel_->setOnRecv([w = weak()](const uint8_t* buf, size_t len) {
+    channel_->setOnRecv([w = weak_from_this()](const uint8_t* buf, size_t len) {
         if (auto shared = w.lock()) {
             // No need to lock, setOnRecv is resetted before closing
             if (shared->stream_.is_open())
@@ -197,7 +198,7 @@ IncomingFile::process()
         }
         return len;
     });
-    channel_->onShutdown([w = weak()] {
+    channel_->onShutdown([w = weak_from_this()] {
         auto shared = w.lock();
         if (!shared)
             return;
@@ -207,24 +208,35 @@ IncomingFile::process()
                 shared->stream_.close();
         }
         auto correct = shared->sha3Sum_.empty();
+        std::error_code ec;
         if (!correct) {
-            // Verify shaSum
-            auto sha3Sum = fileutils::sha3File(shared->info_.path);
             if (shared->isUserCancelled_) {
-                JAMI_WARN() << "Remove file, invalid sha3sum detected for " << shared->info_.path;
-                dhtnet::fileutils::remove(shared->info_.path, true);
-            } else if (shared->sha3Sum_ == sha3Sum) {
-                JAMI_INFO() << "New file received: " << shared->info_.path;
-                correct = true;
+                std::filesystem::remove(shared->path_, ec);
             } else {
-                JAMI_WARN() << "Invalid sha3sum detected, unfinished file: " << shared->info_.path;
-                if (shared->info_.totalSize != 0
-                    && shared->info_.totalSize < shared->info_.bytesProgress) {
-                    JAMI_WARN() << "Remove file, larger file than announced for "
-                                << shared->info_.path;
-                    dhtnet::fileutils::remove(shared->info_.path, true);
+                auto sha3Sum = fileutils::sha3File(shared->path_);
+                if (shared->sha3Sum_ == sha3Sum) {
+                    JAMI_LOG("New file received: {}", shared->info_.path);
+                    correct = true;
+                } else {
+                    if (shared->info_.totalSize != 0
+                        && shared->info_.totalSize < shared->info_.bytesProgress) {
+                        JAMI_WARNING("Removing {} larger than announced: {}/{}", shared->info_.path, shared->info_.bytesProgress, shared->info_.totalSize);
+                        std::filesystem::remove(shared->path_, ec);
+                    } else {
+                        JAMI_WARNING("Invalid sha3sum detected for {}, incomplete file: {}/{}", shared->info_.path, shared->info_.bytesProgress, shared->info_.totalSize);
+                    }
                 }
             }
+            if (ec) {
+                JAMI_ERROR("Failed to remove file {}: {}", shared->path_, ec.message());
+            }
+        }
+        if (correct) {
+            std::filesystem::rename(shared->path_, shared->info_.path, ec);
+            if (ec) {
+                JAMI_ERROR("Failed to rename file from {} to {}: {}", shared->path_, shared->info_.path, ec.message());
+                correct = false;
+            }
         }
         if (shared->isUserCancelled_)
             return;
diff --git a/src/data_transfer.h b/src/data_transfer.h
index fb6e9191a3..9d3a312cc8 100644
--- a/src/data_transfer.h
+++ b/src/data_transfer.h
@@ -87,19 +87,16 @@ public:
                  const libjami::DataTransferInfo& info,
                  const std::string& fileId,
                  const std::string& interactionId,
-                 const std::string& sha3Sum = "");
+                 const std::string& sha3Sum);
     ~IncomingFile();
     void process() override;
     void cancel() override;
 
 private:
-    std::weak_ptr<IncomingFile> weak()
-    {
-        return std::static_pointer_cast<IncomingFile>(shared_from_this());
-    }
     std::mutex streamMtx_;
     std::ofstream stream_;
     std::string sha3Sum_ {};
+    std::filesystem::path path_;
 };
 
 class OutgoingFile : public FileInfo
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 90dfd41afc..1bf50809ae 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -4088,21 +4088,21 @@ JamiAccount::askForFileChannel(const std::string& conversationId,
                                        currentDeviceId(),
                                        fileId);
         if (start != 0 || end != 0) {
-            channelName += "?start=" + std::to_string(start) + "&end=" + std::to_string(end);
+            channelName += fmt::format("?start={}&end={}", start, 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,
+            [w = weak(),
              conversationId,
              fileId,
              interactionId](std::shared_ptr<dhtnet::ChannelSocket> channel, const DeviceId&) {
                 if (!channel)
                     return;
                 dht::ThreadPool::io().run(
-                    [w = weak(), conversationId, channel, fileId, interactionId] {
+                    [w, conversationId, channel, fileId, interactionId] {
                         auto shared = w.lock();
                         if (!shared)
                             return;
diff --git a/src/jamidht/transfer_channel_handler.cpp b/src/jamidht/transfer_channel_handler.cpp
index 187cccfd96..8eacf2882b 100644
--- a/src/jamidht/transfer_channel_handler.cpp
+++ b/src/jamidht/transfer_channel_handler.cpp
@@ -122,8 +122,11 @@ TransferChannelHandler::onReady(const std::shared_ptr<dht::crypto::Certificate>&
                 sha3Sum = keyVal[1];
             } else if (keyVal[0] == "modified") {
                 try {
-                    lastModified = std::stoull(std::string(keyVal[1]));
-                } catch (...) {}
+                    lastModified = jami::to_int<uint64_t>(keyVal[1]);
+                } catch (const std::exception& e) {
+                    JAMI_WARNING("TransferChannel: can't parse modified date: {}: {}",
+                                 keyVal[1], e.what());
+                }
             }
         }
     }
-- 
GitLab