diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index 53fbff2a..71de6328 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -17,7 +17,6 @@ #include #include #include "Log.h" -#include "FS.h" #include "I2PEndian.h" #include "ClientContext.h" #include "Timestamp.h" @@ -564,7 +563,7 @@ namespace torrents m_IsComplete = true; } - void Torrent::SaveTorrentResumeFile (const std::string& fullPath) + void Torrent::SaveTorrentResumeFile (const std::filesystem::path& fullPath) { auto [bitfield, empty] = CreateBitfield (); if (empty) return; @@ -595,8 +594,11 @@ namespace torrents { if (index < 0 || index >= (int)m_Pieces.size ()) return {}; if (m_Files.empty ()) - return { { IsComplete () ? m_FullPath : (m_FullPath + ".part"), - index*m_PieceLength, 0, m_Pieces[index].GetSize () } }; + { + auto fullPath = m_FullPath; + if (!IsComplete ()) fullPath += ".part"; + return { { fullPath, index*m_PieceLength, 0, m_Pieces[index].GetSize () } }; + } else { std::vector ret; @@ -615,16 +617,18 @@ namespace torrents size_t size = m_Pieces[index].GetSize (), fragmentOffset = 0; while (size > 0) { + auto filePath = it->first; + if (!IsComplete ()) filePath += ".part"; if (offset + size <= it->second) { // last fragment - ret.emplace_back (IsComplete () ? it->first : (it->first + ".part"), offset, fragmentOffset, size); + ret.emplace_back (filePath, offset, fragmentOffset, size); size = 0; } else { size_t l = it->second - offset; - ret.emplace_back (IsComplete () ? it->first : (it->first + ".part"), offset, fragmentOffset, l); + ret.emplace_back (filePath, offset, fragmentOffset, l); size -= l; fragmentOffset += l; offset = 0; it++; if (it == m_Files.end ()) break; @@ -1069,7 +1073,8 @@ namespace torrents Piece& piece = torrent->GetPiece (index); for (auto& it: fragments) piece.Dump (std::move (it)); - torrent->SaveTorrentResumeFile (torrent->GetFullPath () + ".resume"); + auto resumeFilePath = torrent->GetFullPath (); resumeFilePath += ".resume"; + torrent->SaveTorrentResumeFile (resumeFilePath); }); // send have auto conns = GetTorrentsTunnel ()->GetTorrentConnections (m_Torrent); @@ -1299,8 +1304,8 @@ namespace torrents TorrentsTunnel::TorrentsTunnel (std::string_view name, std::shared_ptr localDestination, std::string_view torrentsDir, std::string_view trackers): - i2p::client::I2PService (localDestination), m_Name (name), m_TorrentsDir (torrentsDir), - m_PeerID ("-I2PD-"), m_TrackerRequestsCheckTimer (GetService ()), + i2p::client::I2PService (localDestination), m_Name (name), m_PeerID ("-I2PD-"), + m_TorrentsDir (torrentsDir), m_TrackerRequestsCheckTimer (GetService ()), m_KeepAliveCheckTimer (GetService ()), m_ReconnectCheckTimer (GetService ()), m_TorrentsStatusUpdateTimer (GetService ()) { @@ -1350,7 +1355,7 @@ namespace torrents m_Torrents.clear (); for (auto it: m_Torrents) { - auto fullPath = it.second->GetFullPath () + ".resume"; + auto fullPath = it.second->GetFullPath (); fullPath += ".resume"; boost::asio::post (m_DiskIOService.GetService (), [torrent = it.second, fullPath]() { torrent->SaveTorrentResumeFile (fullPath); @@ -1374,17 +1379,17 @@ namespace torrents s.read(buf, len); auto torrent = std::make_shared(std::string_view{buf, len}); delete[] buf; - torrent->SetFullPath (GetTorrentFilePath (torrent->GetName ())); + torrent->SetFullPath (m_TorrentsDir / std::filesystem::path (torrent->GetName ())); m_Torrents.emplace (torrent->GetInfoHash (), torrent); if (torrent->GetFiles ().empty ()) { - if (i2p::fs::Exists (torrent->GetFullPath ())) + if (std::filesystem::exists (torrent->GetFullPath ())) torrent->SetComplete (); else { - auto partFilePath = torrent->GetFullPath () + ".part"; - if (!i2p::fs::Exists (partFilePath)) - i2p::fs::CreateAndReserveFile (partFilePath, torrent->GetLength ()); + auto partFilePath = torrent->GetFullPath (); partFilePath += ".part"; + if (!std::filesystem::exists (partFilePath)) + CreateAndReserveFile (partFilePath, torrent->GetLength ()); } } else @@ -1392,19 +1397,19 @@ namespace torrents bool completed = true; for (auto& [filePath, fileLength]: torrent->GetFiles ()) { - filePath = GetTorrentFilePath (torrent->GetName (), filePath); - if (!i2p::fs::Exists (filePath)) + filePath = std::filesystem::path (torrent->GetName ())/filePath; + if (!std::filesystem::exists (filePath)) { - auto partFilePath = filePath + ".part"; - if (!i2p::fs::Exists (partFilePath)) - i2p::fs::CreateAndReserveFile (partFilePath, fileLength); + auto partFilePath = filePath; partFilePath += ".part"; + if (!std::filesystem::exists (partFilePath)) + CreateAndReserveFile (partFilePath, fileLength); completed = false; } } if (completed) torrent->SetComplete (); } - auto resumeFilePath = torrent->GetFullPath () + ".resume"; - if (i2p::fs::Exists (resumeFilePath )) + auto resumeFilePath = torrent->GetFullPath (); resumeFilePath += ".resume"; + if (std::filesystem::exists (resumeFilePath )) { if (!torrent->IsComplete ()) { @@ -1424,7 +1429,7 @@ namespace torrents } } else - i2p::fs::Remove (resumeFilePath); + std::filesystem::remove (resumeFilePath); } } else @@ -1434,6 +1439,43 @@ namespace torrents LogPrint (eLogError, "Torrents: Can't open file ", path); } + bool TorrentsTunnel::CreateAndReserveFile (const std::filesystem::path& filePath, size_t reserve) + { + if (std::filesystem::exists (filePath)) return false; + auto subdirs = filePath.parent_path (); + if (!subdirs.empty ()) + { + // try to create all subdirs + try + { + std::filesystem::create_directories (subdirs); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "FS: Can't create subdirs ", subdirs, " : ", ex.what()); + return false; + } + } + // create file + std::ofstream f(filePath, std::ios::binary); + if (!f) return false; + f.close (); + if (reserve > 0) + { + // resize + try + { + std::filesystem::resize_file (filePath, reserve); + } + catch (std::exception& ex) + { + LogPrint (eLogError, "FS: Can't resize file ", filePath, " to ", reserve, " : ", ex.what()); + return false; + } + } + return true; + } + void TorrentsTunnel::CompleteTorrent (std::shared_ptr torrent) { boost::asio::post (GetDiskIOService (), [this, torrent]() @@ -1441,8 +1483,10 @@ namespace torrents bool completed = false; if (torrent->GetFiles ().empty ()) { - auto partFilePath = torrent->GetFullPath () + ".part"; - if (i2p::fs::Rename (partFilePath, torrent->GetFullPath ())) + auto partFilePath = torrent->GetFullPath (); partFilePath += ".part"; + std::error_code ec; + std::filesystem::rename (partFilePath, torrent->GetFullPath (), ec); + if (!ec) completed = true; else LogPrint (eLogError, "Torrents: Can't rename ", partFilePath); @@ -1452,8 +1496,10 @@ namespace torrents completed = true; for (const auto& [filePath, fileSize]: torrent->GetFiles ()) { - auto partFilePath = filePath + ".part"; - if (!i2p::fs::Rename (partFilePath, filePath)) + auto partFilePath = filePath; partFilePath += ".part"; + std::error_code ec; + std::filesystem::rename (partFilePath, filePath, ec); + if (ec) { completed = false; LogPrint (eLogError, "Torrents: Can't rename ", partFilePath); @@ -1464,8 +1510,8 @@ namespace torrents { torrent->SetComplete (); RequestTracker (torrent, "completed"); - auto resumeFilePath = torrent->GetFullPath () + ".resume"; - if (!i2p::fs::Remove (resumeFilePath)) + auto resumeFilePath = torrent->GetFullPath (); resumeFilePath += ".resume"; + if (!std::filesystem::remove (resumeFilePath)) LogPrint (eLogError, "Torrents: Can't delete resume file ", resumeFilePath); LogPrint (eLogInfo, "Torrents: Download complete ", torrent->GetFullPath ()); // close connections with seeds @@ -1610,22 +1656,6 @@ namespace torrents return peersToConnect.size (); } - std::string TorrentsTunnel::GetTorrentFilePath (std::string_view filename) const - { - std::stringstream s(""); - s << m_TorrentsDir; - i2p::fs::_ExpandPath(s, filename); - return s.str (); - } - - std::string TorrentsTunnel::GetTorrentFilePath (std::string_view subdir, std::string_view filename) const - { - std::stringstream s(""); - s << m_TorrentsDir; - i2p::fs::_ExpandPath(s, subdir, filename); - return s.str (); - } - void TorrentsTunnel::ScheduleTrackerRequestsCheck () { m_TrackerRequestsCheckTimer.expires_after (std::chrono::milliseconds(TRACKER_REQUESTS_CHECK_TIMEOUT)); diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index ccf353ab..60238449 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -23,6 +23,7 @@ #include #include #include +#include #include "util.h" #include "Streaming.h" #include "HTTP.h" @@ -76,12 +77,12 @@ namespace torrents struct PieceFileFragment // fragment to save to/load from file { - std::string fullFilePath; + std::filesystem::path fullFilePath; size_t fileOffset; size_t fragmentOffset; // from start of piece size_t fragmentSize; - PieceFileFragment (const std::string& fullFilePath1, size_t fileOffset1, size_t fragmentOffset1, size_t fragmentSize1): + PieceFileFragment (const std::filesystem::path& fullFilePath1, size_t fileOffset1, size_t fragmentOffset1, size_t fragmentSize1): fullFilePath (fullFilePath1), fileOffset (fileOffset1), fragmentOffset (fragmentOffset1), fragmentSize (fragmentSize1) {}; PieceFileFragment (PieceFileFragment&& ) = default; PieceFileFragment (const PieceFileFragment& ) = default; @@ -152,10 +153,10 @@ namespace torrents std::string_view GetAnnounce () const { return m_Announce; } std::string_view GetName () const { return m_Name; } - const std::string& GetFullPath () const { return m_FullPath; } - void SetFullPath (std::string_view fullPath) { m_FullPath = fullPath; } - const std::list >& GetFiles () const { return m_Files; } - std::list >& GetFiles () { return m_Files; } + const std::filesystem::path& GetFullPath () const { return m_FullPath; } + void SetFullPath (const std::filesystem::path& fullPath) { m_FullPath = fullPath; } + const std::list >& GetFiles () const { return m_Files; } + std::list >& GetFiles () { return m_Files; } size_t GetLength () const { return m_Length; } size_t GetPieceLength () const { return m_PieceLength; } int GetInterval () const { return m_Interval; } @@ -176,7 +177,7 @@ namespace torrents size_t GetUploaded () const { return m_Uploaded; } void AddUploaded (size_t add) { m_Uploaded += add; } - void SaveTorrentResumeFile (const std::string& fullPath); + void SaveTorrentResumeFile (const std::filesystem::path& fullPath); void StartCountingPeers (); void ApplyPeerRemoteBitfield (const boost::dynamic_bitset<>& peerRemoteBitfield); @@ -190,7 +191,8 @@ namespace torrents private: - std::string m_Name, m_FullPath, m_Announce; + std::string m_Name, m_Announce; + std::filesystem::path m_FullPath; size_t m_Length, m_PieceLength; int m_Interval; // in miiliseconds uint64_t m_NextTrackerRequestTime; // monotonic millicesonds @@ -198,7 +200,7 @@ namespace torrents std::vector m_Pieces; std::unordered_set m_Peers; bool m_IsComplete; - std::list > m_Files; // list of (path, length) + std::list > m_Files; // list of (path, length) size_t m_Uploaded; }; @@ -314,11 +316,10 @@ namespace torrents private: - std::string GetTorrentFilePath (std::string_view filename) const; - std::string GetTorrentFilePath (std::string_view subdir, std::string_view filename) const; void Accept (); void ReadTorrentFile (const std::string& path); + bool CreateAndReserveFile (const std::filesystem::path& filePath, size_t reserve); void CompleteTorrent (std::shared_ptr torrent); void RequestTracker (std::shared_ptr torrent, std::string_view event = ""); void TrackerRequestSent (const boost::beast::error_code& ecode, size_t bytes_transferred, @@ -344,7 +345,8 @@ namespace torrents private: - std::string m_Name, m_TorrentsDir, m_PeerID; // 20 characters + std::string m_Name, m_PeerID; // 20 characters + std::filesystem::path m_TorrentsDir; std::vector m_Trackers; std::map > m_Torrents; boost::asio::steady_timer m_TrackerRequestsCheckTimer, m_KeepAliveCheckTimer,