diff --git a/libi2pd_client/Torrents.cpp b/libi2pd_client/Torrents.cpp index ab52ed58..d0fef0ed 100644 --- a/libi2pd_client/Torrents.cpp +++ b/libi2pd_client/Torrents.cpp @@ -369,6 +369,7 @@ namespace torrents size_t Torrent::ParseFiles (std::string_view buf) { + m_Length = 0; return ParseList (buf, [this](std::string_view file)->size_t { std::string filePath; size_t fileLength = 0; @@ -383,13 +384,17 @@ namespace torrents else if (key == "length") { auto [length, l] = ExtractInteger (value); - if (l) fileLength = length; + if (l) + fileLength = length; return l; } return 0; }); if (len && fileLength && !filePath.empty ()) + { m_Files.emplace_back (filePath, fileLength); + m_Length += fileLength; + } return len; }); } @@ -580,6 +585,52 @@ namespace torrents } } + std::vector Torrent::GetPieceFileFragments (int index) const + { + 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 () } }; + else + { + 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 ()) + { + 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) + { + if (offset + size <= it->second) + { + // last fragment + ret.emplace_back (IsComplete () ? it->first : (it->first + ".part"), offset, fragmentOffset, size); + size = 0; + } + else + { + size_t l = it->second - offset; + ret.emplace_back (IsComplete () ? it->first : (it->first + ".part"), 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; + } + } + PeerConnection::PeerConnection (i2p::client::I2PService * owner, std::shared_ptr stream): i2p::client::I2PServiceHandler (owner), m_Stream (stream), m_ReceiveBufferOffset (0), m_IsHandshakeSent (false), m_IsEstablished (false), m_IsChoked (true), @@ -996,14 +1047,14 @@ namespace torrents { if (piece.VerifyHash ()) { - PieceFileFragment fragment{ GetTorrentsTunnel ()->GetTorrentFilePath (m_Torrent->GetName () + ".part"), - index*m_Torrent->GetPieceLength (), 0, piece.GetSize () }; - auto resumePath = GetTorrentsTunnel ()->GetTorrentFilePath (m_Torrent->GetName () + ".resume"); boost::asio::post (GetTorrentsTunnel ()->GetDiskIOService (), - [&piece, fragment = std::move (fragment), resumePath, torrent = m_Torrent]() mutable // piece belongs to torrent + [index, torrent = m_Torrent]() mutable { - piece.Dump (std::move (fragment)); - torrent->SaveTorrentResumeFile (resumePath); + auto fragments = torrent->GetPieceFileFragments (index); + Piece& piece = torrent->GetPiece (index); + for (auto& it: fragments) + piece.Dump (std::move (it)); + torrent->SaveTorrentResumeFile (torrent->GetFullPath () + ".resume"); }); // send have auto conns = GetTorrentsTunnel ()->GetTorrentConnections (m_Torrent); @@ -1076,30 +1127,36 @@ namespace torrents else if (!SendRequestedBlock ({index, offset, length})) // block was not sent { // try to load from file - PieceFileFragment fragment{ GetTorrentsTunnel ()->GetTorrentFilePath ( - m_Torrent->GetName () + (m_Torrent->IsComplete () ? "" : ".part")), - index*m_Torrent->GetPieceLength (), 0, piece.GetSize () }; RequestedBlock requestBlock{index, offset, length}; boost::asio::post (GetTorrentsTunnel ()->GetDiskIOService (), - [&piece, fragment = std::move (fragment), requestBlock = std::move(requestBlock), - torrent = m_Torrent, s = shared_from_this ()]() mutable// piece belongs to torrent + [requestBlock = std::move(requestBlock), torrent = m_Torrent, s = shared_from_this ()]() mutable { + auto fragments = torrent->GetPieceFileFragments (requestBlock.index); + Piece& piece = torrent->GetPiece (requestBlock.index); piece.SetIsSending (true); - if (piece.Load (std::move (fragment))) + bool loaded = true; + for (auto& it: fragments) { - if (piece.VerifyHash ()) - boost::asio::post (s->GetTorrentsTunnel ()->GetService (), - [requestedBlock = std::move (requestBlock), s]() - { - if (s->m_NumPieces < MAX_NUM_PIECES) - s->SendRequestedBlock (requestedBlock); - else - s->m_IncomingRequestsQueue.emplace_back (std::move (requestedBlock)); - }); - else - LogPrint (eLogError, "Torrent: Corrupted piece ", requestBlock.index, " from ", fragment.fullFilePath); + if (!piece.Load (std::move (it))) + loaded = false; + } + if (!piece.VerifyHash ()) + { + LogPrint (eLogError, "Torrent: Corrupted piece ", requestBlock.index); + loaded = false; } piece.SetIsSending (false); + if (loaded) + boost::asio::post (s->GetTorrentsTunnel ()->GetService (), + [requestedBlock = std::move (requestBlock), s]() + { + if (s->m_NumPieces < MAX_NUM_PIECES) + s->SendRequestedBlock (requestedBlock); + else + s->m_IncomingRequestsQueue.emplace_back (std::move (requestedBlock)); + }); + else + LogPrint (eLogError, "Torrent: Failed to load piece ", requestBlock.index); }); } } @@ -1248,7 +1305,7 @@ namespace torrents m_Torrents.clear (); for (auto it: m_Torrents) { - auto fullPath = GetTorrentFilePath (it.second->GetName () + ".resume"); + auto fullPath = it.second->GetFullPath () + ".resume"; boost::asio::post (m_DiskIOService.GetService (), [torrent = it.second, fullPath]() { torrent->SaveTorrentResumeFile (fullPath); @@ -1272,27 +1329,40 @@ namespace torrents s.read(buf, len); auto torrent = std::make_shared(std::string_view{buf, len}); delete[] buf; - if (!torrent->GetFiles ().empty ()) - { - for (auto [filePath, fileLength]: torrent->GetFiles ()) - { - auto partFilePath = GetTorrentFilePath (torrent->GetName (), filePath + ".part"); - if (!i2p::fs::Exists (partFilePath)) - i2p::fs::CreateAndReserveFile (partFilePath, fileLength); - } - LogPrint (eLogError, "Torrents: Multi-file torrent ", path); - return; - } + torrent->SetFullPath (GetTorrentFilePath (torrent->GetName ())); m_Torrents.emplace (torrent->GetInfoHash (), torrent); - auto filePath = GetTorrentFilePath (torrent->GetName ()); - if (i2p::fs::Exists (filePath)) - torrent->SetComplete (); + if (torrent->GetFiles ().empty ()) + { + if (i2p::fs::Exists (torrent->GetFullPath ())) + torrent->SetComplete (); + else + { + auto partFilePath = torrent->GetFullPath () + ".part"; + if (!i2p::fs::Exists (partFilePath)) + i2p::fs::CreateAndReserveFile (partFilePath, torrent->GetLength ()); + } + } else { - auto partFilePath = GetTorrentFilePath (torrent->GetName () + ".part"); - if (i2p::fs::Exists (partFilePath)) + bool completed = true; + for (auto& [filePath, fileLength]: torrent->GetFiles ()) + { + filePath = GetTorrentFilePath (torrent->GetName (), filePath); + if (!i2p::fs::Exists (filePath)) + { + auto partFilePath = filePath + ".part"; + if (!i2p::fs::Exists (partFilePath)) + i2p::fs::CreateAndReserveFile (partFilePath, fileLength); + completed = false; + } + } + if (completed) torrent->SetComplete (); + } + auto resumeFilePath = torrent->GetFullPath () + ".resume"; + if (i2p::fs::Exists (resumeFilePath )) + { + if (!torrent->IsComplete ()) { - auto resumeFilePath = GetTorrentFilePath (torrent->GetName () + ".resume"); std::ifstream rs(resumeFilePath, std::ifstream::binary); if (rs) { @@ -1309,7 +1379,7 @@ namespace torrents } } else - i2p::fs::CreateAndReserveFile (partFilePath, torrent->GetLength ()); + i2p::fs::Remove (resumeFilePath); } } else @@ -1321,21 +1391,39 @@ namespace torrents void TorrentsTunnel::CompleteTorrent (std::shared_ptr torrent) { - boost::asio::post (GetDiskIOService (), [this, torrent, - filePath = GetTorrentFilePath (torrent->GetName ()), - partFilePath = GetTorrentFilePath (torrent->GetName () + ".part"), - resumeFilePath = GetTorrentFilePath (torrent->GetName () + ".resume")]() + boost::asio::post (GetDiskIOService (), [this, torrent]() { - if (i2p::fs::Rename (partFilePath, filePath)) + bool completed = false; + if (torrent->GetFiles ().empty ()) { - torrent->SetComplete (); - RequestTracker (torrent, "completed"); - if (!i2p::fs::Remove (resumeFilePath)) - LogPrint (eLogError, "Torrents: Can't delete resume file ", resumeFilePath); - LogPrint (eLogInfo, "Torrents: Download complete ", filePath); + auto partFilePath = torrent->GetFullPath () + ".part"; + if (i2p::fs::Rename (partFilePath, torrent->GetFullPath ())) + completed = true; + else + LogPrint (eLogError, "Torrents: Can't rename ", partFilePath); } else - LogPrint (eLogError, "Torrents: Can't rename ", partFilePath); + { + completed = true; + for (const auto& [filePath, fileSize]: torrent->GetFiles ()) + { + auto partFilePath = filePath + ".part"; + if (!i2p::fs::Rename (partFilePath, filePath)) + { + completed = false; + LogPrint (eLogError, "Torrents: Can't rename ", partFilePath); + } + } + } + if (completed) + { + torrent->SetComplete (); + RequestTracker (torrent, "completed"); + auto resumeFilePath = torrent->GetFullPath () + ".resume"; + if (!i2p::fs::Remove (resumeFilePath)) + LogPrint (eLogError, "Torrents: Can't delete resume file ", resumeFilePath); + LogPrint (eLogInfo, "Torrents: Download complete ", torrent->GetFullPath ()); + } }); } @@ -1472,7 +1560,7 @@ namespace torrents return peersToConnect.size (); } - std::string TorrentsTunnel::GetTorrentFilePath (const std::string& filename) const + std::string TorrentsTunnel::GetTorrentFilePath (std::string_view filename) const { std::stringstream s(""); s << m_TorrentsDir; @@ -1480,7 +1568,7 @@ namespace torrents return s.str (); } - std::string TorrentsTunnel::GetTorrentFilePath (const std::string& subdir, const std::string& filename) const + std::string TorrentsTunnel::GetTorrentFilePath (std::string_view subdir, std::string_view filename) const { std::stringstream s(""); s << m_TorrentsDir; diff --git a/libi2pd_client/Torrents.h b/libi2pd_client/Torrents.h index e0daff09..36822ebb 100644 --- a/libi2pd_client/Torrents.h +++ b/libi2pd_client/Torrents.h @@ -144,9 +144,12 @@ namespace torrents bool IsComplete () const { return m_IsComplete; } void SetComplete (); - const std::string& GetAnnounce () const { return m_Announce; } - const std::string& GetName () const { return m_Name; } + 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; } size_t GetLength () const { return m_Length; } size_t GetPieceLength () const { return m_PieceLength; } int GetInterval () const { return m_Interval; } @@ -159,6 +162,7 @@ namespace torrents bool ApplyBitfield (const std::vector& bitfield); // return true if complete const std::unordered_set& GetPeers () const { return m_Peers; } std::tuple GetNextBlockToRequest (std::shared_ptr conn); // return (index, offset, len) + std::vector GetPieceFileFragments (int index) const; bool UpdateStatus (uint64_t ts); // return true if complete uint64_t GetNextTrackerRequestTime () const { return m_NextTrackerRequestTime; } @@ -178,7 +182,7 @@ namespace torrents private: - std::string m_Name, m_Announce; + std::string m_Name, m_FullPath, m_Announce; size_t m_Length, m_PieceLength; int m_Interval; // in miiliseconds uint64_t m_NextTrackerRequestTime; // monotonic millicesonds @@ -292,8 +296,6 @@ namespace torrents auto& GetDiskIOService () { return m_DiskIOService.GetService (); }; const std::string& GetPeerID () const { return m_PeerID; } - std::string GetTorrentFilePath (const std::string& filename) const; - std::string GetTorrentFilePath (const std::string& subdir, const std::string& filename) const; std::shared_ptr FindTorrent (const Torrent::InfoHash& infoHash) const; std::list > GetTorrentConnections (std::shared_ptr torrent); @@ -301,6 +303,9 @@ 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); void CompleteTorrent (std::shared_ptr torrent);