LCOV - code coverage report
Current view: top level - src - data_transfer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 333 380 87.6 %
Date: 2024-04-25 08:05:53 Functions: 36 41 87.8 %

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

Generated by: LCOV version 1.14