use TorrentFile struct for files. Common logic for single and multiple files

This commit is contained in:
orignal committed 2026-09-13 18:38:45 -04:00
1 parent 4b42c6b76a
commit b8a1c56d25
4 files changed
+130 -132

No files matched your search

+78 -59
View File
@@ -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<TorrentFile> (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<TorrentFile> (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<PieceFileFragment> Torrent::GetPieceFileFragments (int index) const
{
if (index < 0 || index >= (int)m_Pieces.size ()) return {};
if (m_Files.empty ())
std::vector<PieceFileFragment> 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<PieceFileFragment> 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<size_t> 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;
+20 -7
View File
@@ -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<TorrentFile> 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<TorrentFile> 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<std::pair<std::filesystem::path, size_t> >& GetFiles () const { return m_Files; }
std::list<std::pair<std::filesystem::path, size_t> >& GetFiles () { return m_Files; }
const std::list<std::shared_ptr<TorrentFile> >& GetFiles () const { return m_Files; }
std::list<std::shared_ptr<TorrentFile> >& 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<Piece> m_Pieces;
std::vector<TrackerStats> m_TrackerStats;
std::unordered_map<i2p::data::IdentHash, std::weak_ptr<PeerConnection> > m_Connections; // remote ident hash -> connection
bool m_IsComplete, m_IsStopped;
std::list<std::pair<std::filesystem::path, size_t> > m_Files; // list of (path, length)
bool m_IsComplete, m_IsStopped, m_IsSingleFile;
std::list<std::shared_ptr<TorrentFile> > m_Files;
size_t m_Uploaded, m_Downloaded;
uint64_t m_NextUpdateStatusTime, m_NextReconnectTime; // in monotonic seconds
// stats
+9 -20
View File
@@ -288,7 +288,7 @@ namespace torrents
},
{ "pieces", [](std::shared_ptr<Torrent> 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;
}
+23 -46
View File
@@ -147,33 +147,21 @@ namespace torrents
void TorrentsTunnel::InitTorrentFiles (std::shared_ptr<Torrent> 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);
}