LCOV - code coverage report
Current view: top level - foo/src - data_transfer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 348 395 88.1 %
Date: 2025-08-24 09:11:10 Functions: 44 57 77.2 %

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

Generated by: LCOV version 1.14