mirror of
https://github.com/PurpleI2P/i2pd.git
synced 2026-10-22 17:31:32 +00:00
use TorrentFile struct for files. Common logic for single and multiple files
This commit is contained in:
1 parent
4b42c6b76a
commit
b8a1c56d25
4 files changed
+130
-132
No files matched your search
+78
-59
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user