From b8a1c56d25d4b09ff9c0637b886d314e19e83a49 Mon Sep 17 00:00:00 2001 From: orignal Date: Sun, 13 Sep 2026 18:38:45 -0400 Subject: [PATCH] use TorrentFile struct for files. Common logic for single and multiple files --- libi2pd_client/Torrents.cpp | 137 +++++++++++++++++------------- libi2pd_client/Torrents.h | 27 ++++-- libi2pd_client/TorrentsRPC.cpp | 29 ++----- libi2pd_client/TorrentsTunnel.cpp | 69 +++++---------- 4 files changed, 130 insertions(+), 132 deletions(-) diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index d72d2823..7cffe759 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -172,6 +172,36 @@ namespace torrents //------------------------------------ + bool TorrentFile::Save (size_t offset, const uint8_t * buf, size_t len) + { + auto filePath = fullFilePath; + if (isPart) filePath += ".part"; + std::fstream f(filePath, std::ios::binary | std::ios::in | std::ios::out ); + if (f) + { + f.seekp (offset, std::ios::beg); + f.write ((const char *)buf, len); + } + else + return false; + return true; + } + + bool TorrentFile::Load (size_t offset, uint8_t * buf, size_t len) + { + auto filePath = fullFilePath; + if (isPart) filePath += ".part"; + std::ifstream f(filePath, std::ifstream::binary); + if (f) + { + f.seekg (offset, std::ios::beg); + f.read ((char *)buf, len); + } + else + return false; + return true; + } + Piece::Piece (size_t size, const uint8_t * hash): m_Size (size), m_Data (nullptr), m_IsSending (false), m_IsRequested (false), m_LastActivityTimestamp (0), m_NumPeers (0) @@ -246,13 +276,8 @@ namespace torrents m_IsSending = true; if (m_Data && fragment.fragmentOffset + fragment.fragmentSize <= m_Size) { - std::fstream f(fragment.fullFilePath, std::ios::binary | std::ios::in | std::ios::out ); - if (f) - { - f.seekp (fragment.fileOffset, std::ios::beg); - f.write ((const char *)m_Data + fragment.fragmentOffset, fragment.fragmentSize); - LogPrint (eLogDebug, "Torrents: Saved bytes ", fragment.fileOffset, " - ", fragment.fileOffset + fragment.fragmentSize - 1, " to ", fragment.fullFilePath); - } + if (fragment.file->Save (fragment.fileOffset, m_Data + fragment.fragmentOffset, fragment.fragmentSize)) + LogPrint (eLogDebug, "Torrents: Saved bytes ", fragment.fileOffset, " - ", fragment.fileOffset + fragment.fragmentSize - 1, " to ", fragment.file->fullFilePath); } m_IsSending = false; } @@ -260,17 +285,14 @@ namespace torrents bool Piece::Load (PieceFileFragment&& fragment) { if (fragment.fragmentOffset + fragment.fragmentSize > m_Size) return false; - std::ifstream f(fragment.fullFilePath, std::ifstream::binary); - if (f) + if (!m_Data) NewDataBuffer (); + if (fragment.file->Load (fragment.fileOffset, m_Data + fragment.fragmentOffset, fragment.fragmentSize)) { - if (!m_Data) NewDataBuffer (); - f.seekg (fragment.fileOffset, std::ios::beg); - f.read ((char *)m_Data + fragment.fragmentOffset, fragment.fragmentSize); - LogPrint (eLogDebug, "Torrents: Loaded bytes ", fragment.fileOffset, " - ", fragment.fileOffset + fragment.fragmentSize - 1, " from ", fragment.fullFilePath); + LogPrint (eLogDebug, "Torrents: Loaded bytes ", fragment.fileOffset, " - ", fragment.fileOffset + fragment.fragmentSize - 1, " from ", fragment.file->fullFilePath); + return true; } else return false; - return true; } void Piece::NewDataBuffer () @@ -343,7 +365,7 @@ namespace torrents } Torrent::Torrent (): - m_Length (0), m_PieceLength (0), m_IsComplete (false), m_IsStopped (false), + m_Length (0), m_PieceLength (0), m_IsComplete (false), m_IsStopped (false), m_IsSingleFile (true), m_Uploaded (0), m_Downloaded (0), m_NextUpdateStatusTime (0), m_NextReconnectTime (0) { ResetStats (); @@ -429,6 +451,8 @@ namespace torrents return 0; }); if (!len) return 0; + if (m_IsSingleFile && !m_Name.empty ()) // single file + m_Files.emplace_back (std::make_shared (m_Name, m_Length)); // save info m_Info.resize (len); memcpy (m_Info.data (), (const uint8_t *)buf.data (), len); @@ -477,6 +501,7 @@ namespace torrents size_t Torrent::ParseFiles (std::string_view buf) { + m_IsSingleFile = false; m_Length = 0; return ParseList (buf, [this](std::string_view file)->size_t { @@ -510,7 +535,7 @@ namespace torrents }); if (len && fileLength && !filePath.empty ()) { - m_Files.emplace_back (filePath, fileLength); + m_Files.emplace_back (std::make_shared (filePath, fileLength)); m_Length += fileLength; } return len; @@ -758,6 +783,8 @@ namespace torrents for (auto& it: m_Pieces) if (!it.IsComplete ()) it.Complete (); + for (auto it: m_Files) + it->isPart = false; } void Torrent::SaveTorrentResumeFile () @@ -813,52 +840,44 @@ namespace torrents std::vector Torrent::GetPieceFileFragments (int index) const { if (index < 0 || index >= (int)m_Pieces.size ()) return {}; - if (m_Files.empty ()) + + std::vector ret; + // first file and offset for start of piece + size_t offset = index*m_PieceLength; + auto it = m_Files.begin (); + while (it != m_Files.end ()) { - auto fullPath = m_FullPath; - if (!IsComplete ()) fullPath += ".part"; - return { { fullPath, index*m_PieceLength, 0, m_Pieces[index].GetSize () } }; + if (offset < (*it)->fileLength) break; + offset -= (*it)->fileLength; + it++; } - else + if (it != m_Files.end ()) { - std::vector ret; - // first file and offset for start of piece - size_t offset = index*m_PieceLength; - auto it = m_Files.begin (); - while (it != m_Files.end ()) + // split piece by files + size_t size = m_Pieces[index].GetSize (), fragmentOffset = 0; + while (size > 0) { - if (offset < it->second) break; - offset -= it->second; - it++; - } - if (it != m_Files.end ()) - { - // split piece by files - size_t size = m_Pieces[index].GetSize (), fragmentOffset = 0; - while (size > 0) + auto file = *it; + file->isPart = !IsComplete (); + if (offset + size <= file->fileLength) { - auto filePath = it->first; - if (!IsComplete ()) filePath += ".part"; - if (offset + size <= it->second) - { - // last fragment - ret.emplace_back (filePath, offset, fragmentOffset, size); - size = 0; - } - else - { - size_t l = it->second - offset; - ret.emplace_back (filePath, offset, fragmentOffset, l); - size -= l; fragmentOffset += l; - offset = 0; it++; - if (it == m_Files.end ()) break; - } + // last fragment + ret.emplace_back (file, offset, fragmentOffset, size); + size = 0; + } + else + { + size_t l = file->fileLength - offset; + ret.emplace_back (file, offset, fragmentOffset, l); + size -= l; fragmentOffset += l; + offset = 0; it++; + if (it == m_Files.end ()) break; } - if (size > 0) - LogPrint (eLogError, "Torrents: Piece ", index, " is beyond files"); } - return ret; + if (size > 0) + LogPrint (eLogError, "Torrents: Piece ", index, " is beyond files"); } + return ret; } std::vector Torrent::GetFilesCompleted () const @@ -870,22 +889,22 @@ namespace torrents size_t currentSize = 0, currentCompletedSize = 0; for (const auto& piece: m_Pieces) { - if (currentSize + m_PieceLength < filesIT->second) + if (currentSize + m_PieceLength < (*filesIT)->fileLength) { currentSize += m_PieceLength; if (piece.IsComplete ()) currentCompletedSize += m_PieceLength; } else { - size_t leftoverSize = filesIT->second - currentSize; + size_t leftoverSize = (*filesIT)->fileLength - currentSize; if (piece.IsComplete ()) currentCompletedSize += leftoverSize; completed.push_back (currentCompletedSize); currentSize = m_PieceLength - leftoverSize; filesIT++; - while (filesIT != m_Files.end () && filesIT->second <= currentSize) + while (filesIT != m_Files.end () && (*filesIT)->fileLength <= currentSize) { - completed.push_back (filesIT->second); - currentSize -= filesIT->second; + completed.push_back ((*filesIT)->fileLength); + currentSize -= (*filesIT)->fileLength; filesIT++; } currentCompletedSize = piece.IsComplete () ? currentSize : 0; diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index 4bf2e360..5b036afa 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -88,15 +88,27 @@ namespace torrents eMessageTypeExtended = 20 }; - struct PieceFileFragment // fragment to save to/load from file + struct TorrentFile { std::filesystem::path fullFilePath; + size_t fileLength; + bool isPart = true; + + TorrentFile (const std::filesystem::path & fullFilePath1, size_t fileLength1): + fullFilePath (fullFilePath1), fileLength (fileLength1) {}; + bool Save (size_t offset, const uint8_t * buf, size_t len); + bool Load (size_t offset, uint8_t * buf, size_t len); + }; + + struct PieceFileFragment // fragment to save to/load from file + { + std::shared_ptr file; size_t fileOffset; size_t fragmentOffset; // from start of piece size_t fragmentSize; - PieceFileFragment (const std::filesystem::path& fullFilePath1, size_t fileOffset1, size_t fragmentOffset1, size_t fragmentSize1): - fullFilePath (fullFilePath1), fileOffset (fileOffset1), fragmentOffset (fragmentOffset1), fragmentSize (fragmentSize1) {}; + PieceFileFragment (std::shared_ptr file1, size_t fileOffset1, size_t fragmentOffset1, size_t fragmentSize1): + file (file1), fileOffset (fileOffset1), fragmentOffset (fragmentOffset1), fragmentSize (fragmentSize1) {}; PieceFileFragment (PieceFileFragment&& ) = default; PieceFileFragment (const PieceFileFragment& ) = default; }; @@ -191,6 +203,7 @@ namespace torrents void SetStopped (bool stopped) { m_IsStopped = stopped; } bool IsActive () const { return !m_Connections.empty (); } TorrentStatus GetStatus () const; + bool IsSingleFile () const { return m_IsSingleFile; }; std::string_view GetAnnounce () const { return m_Announce; } void SetAnnounce (std::string_view announce) { m_Announce = announce; } @@ -199,8 +212,8 @@ namespace torrents bool IsValid () const { return !m_Name.empty () && m_PieceLength && (m_Length || !m_Files.empty ()); } 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; } + 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 (size_t trackerID) const { return (trackerID < m_TrackerStats.size ()) ? std::get<1>(m_TrackerStats[trackerID]) : MIN_TRACKER_REQUESTS_INTERVAL; } @@ -276,8 +289,8 @@ namespace torrents std::vector m_Pieces; std::vector m_TrackerStats; std::unordered_map > m_Connections; // remote ident hash -> connection - bool m_IsComplete, m_IsStopped; - std::list > m_Files; // list of (path, length) + bool m_IsComplete, m_IsStopped, m_IsSingleFile; + std::list > m_Files; size_t m_Uploaded, m_Downloaded; uint64_t m_NextUpdateStatusTime, m_NextReconnectTime; // in monotonic seconds // stats diff --git a/libi2pd_client/TorrentsRPC.cpp b/libi2pd_client/TorrentsRPC.cpp index fc3347be..ae3b9e15 100644 --- a/libi2pd_client/TorrentsRPC.cpp +++ b/libi2pd_client/TorrentsRPC.cpp @@ -288,7 +288,7 @@ namespace torrents }, { "pieces", [](std::shared_ptr torrent) { - auto [bitfield, empry] = torrent->CreateBitfield (); + auto [bitfield, have] = torrent->CreateBitfield (); std::string b64pieces; b64pieces.resize (boost::beast::detail::base64::encoded_size (bitfield.size())); boost::beast::detail::base64::encode (b64pieces.data (), bitfield.data(), bitfield.size()); @@ -479,28 +479,17 @@ namespace torrents { boost::json::array files; const auto& torrentFiles = torrent->GetFiles (); - if (torrentFiles.empty ()) + const auto& torrentsDir = m_Tunnel->GetTorrentsDir (); + auto filesCompleted = torrent->GetFilesCompleted (); + size_t ind = 0; + for (const auto& it: torrentFiles) { boost::json::object file; - file["name"] = torrent->GetName (); - file["length"] = torrent->GetLength (); - file["bytesCompleted"] = torrent->GetLength () - torrent->GetLeft (); + file["name"] = std::filesystem::relative (it->fullFilePath, torrentsDir).string (); + file["length"] = it->fileLength; + file["bytesCompleted"] = (ind < filesCompleted.size ()) ? filesCompleted[ind] : 0; files.push_back (file); - } - else - { - const auto& torrentsDir = m_Tunnel->GetTorrentsDir (); - auto filesCompleted = torrent->GetFilesCompleted (); - size_t ind = 0; - for (const auto& [filePath, fileSize]: torrentFiles) - { - boost::json::object file; - file["name"] = std::filesystem::relative (filePath, torrentsDir).string (); - file["length"] = fileSize; - file["bytesCompleted"] = (ind < filesCompleted.size ()) ? filesCompleted[ind] : 0; - files.push_back (file); - ind++; - } + ind++; } return files; } diff --git a/libi2pd_client/TorrentsTunnel.cpp b/libi2pd_client/TorrentsTunnel.cpp index c48f1507..2a0aa6c4 100644 --- a/libi2pd_client/TorrentsTunnel.cpp +++ b/libi2pd_client/TorrentsTunnel.cpp @@ -147,33 +147,21 @@ namespace torrents void TorrentsTunnel::InitTorrentFiles (std::shared_ptr torrent) { if (!torrent) return; - if (torrent->GetFiles ().empty ()) + + bool completed = true; + for (auto it: torrent->GetFiles ()) { - if (std::filesystem::exists (torrent->GetFullPath ())) - torrent->SetComplete (); - else + it->fullFilePath = torrent->IsSingleFile () ? torrent->GetFullPath () : torrent->GetFullPath ()/it->fullFilePath; + if (!std::filesystem::exists (it->fullFilePath)) { - auto partFilePath = torrent->GetFullPath (); partFilePath += ".part"; + auto partFilePath = it->fullFilePath; partFilePath += ".part"; if (!std::filesystem::exists (partFilePath)) - CreateAndReserveFile (partFilePath, torrent->GetLength ()); + CreateAndReserveFile (partFilePath, it->fileLength); + completed = false; } } - else - { - bool completed = true; - for (auto& [filePath, fileLength]: torrent->GetFiles ()) - { - filePath = torrent->GetFullPath ()/filePath; - if (!std::filesystem::exists (filePath)) - { - auto partFilePath = filePath; partFilePath += ".part"; - if (!std::filesystem::exists (partFilePath)) - CreateAndReserveFile (partFilePath, fileLength); - completed = false; - } - } - if (completed) torrent->SetComplete (); - } + if (completed) torrent->SetComplete (); + auto resumeFilePath = torrent->GetFullPath (); resumeFilePath += ".resume"; if (std::filesystem::exists (resumeFilePath)) { @@ -240,37 +228,23 @@ namespace torrents { boost::asio::post (GetDiskIOService (), [this, torrent]() { - bool completed = false; - if (torrent->GetFiles ().empty ()) + bool completed = true; + for (auto it: torrent->GetFiles ()) { - auto partFilePath = torrent->GetFullPath (); partFilePath += ".part"; - std::error_code ec; + auto partFilePath = it->fullFilePath; partFilePath += ".part"; if (std::filesystem::exists (partFilePath)) { - std::filesystem::rename (partFilePath, torrent->GetFullPath (), ec); - if (!ec) - completed = true; - else - LogPrint (eLogError, "TorrentsTunnel: Can't rename ", partFilePath); - } - else if (std::filesystem::exists (torrent->GetFullPath ())) - completed = true; - } - else - { - completed = true; - for (const auto& [filePath, fileSize]: torrent->GetFiles ()) - { - auto partFilePath = filePath; partFilePath += ".part"; std::error_code ec; - std::filesystem::rename (partFilePath, filePath, ec); + std::filesystem::rename (partFilePath, it->fullFilePath, ec); if (ec) { completed = false; LogPrint (eLogError, "TorrentsTunnel: Can't rename ", partFilePath); } } + if (completed) it->isPart = false; } + if (completed) { torrent->SetComplete (); @@ -460,15 +434,15 @@ namespace torrents if (ec) LogPrint (eLogError, "TorrentsTunnel: Can't delete ", resumeFilePath); } - if (torrent->IsComplete () || !torrent->GetFiles ().empty ()) + if (std::filesystem::exists (fullPath)) { std::filesystem::remove_all (fullPath, ec); if (ec) LogPrint (eLogError, "TorrentsTunnel: Can't delete ", fullPath); } - else + auto partFilePath = fullPath; partFilePath += ".part"; + if (std::filesystem::exists (partFilePath)) { - auto partFilePath = fullPath; partFilePath += ".part"; std::filesystem::remove (partFilePath, ec); if (ec) LogPrint (eLogError, "TorrentsTunnel: Can't delete ", partFilePath); @@ -837,7 +811,10 @@ namespace torrents if (!it.second->IsStopped () && (it.second->IsActive () || !it.second->IsComplete ())) { if (it.second->UpdateStatus (ts)) - CompleteTorrent (it.second); + { + if (!it.second->IsComplete ()) + CompleteTorrent (it.second); + } else UpdatePeersPerPiece (it.second); }