LCOV - code coverage report
Current view: top level - foo/src - data_transfer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 341 384 88.8 %
Date: 2025-12-18 10:07:43 Functions: 49 65 75.4 %

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

Generated by: LCOV version 1.14