co-maps/libs/platform/chunks_download_strategy.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

239 lines
6.6 KiB
C++
Raw Normal View History

2025-11-22 13:58:55 +01:00
#include "platform/chunks_download_strategy.hpp"
#include "platform/platform.hpp"
#include "coding/file_reader.hpp"
#include "coding/file_writer.hpp"
#include "coding/varint.hpp"
#include "base/assert.hpp"
#include "base/logging.hpp"
#include "base/macros.hpp"
namespace downloader
{
ChunksDownloadStrategy::ChunksDownloadStrategy(std::vector<std::string> const & urls)
{
// init servers list
for (size_t i = 0; i < urls.size(); ++i)
m_servers.push_back(ServerT(urls[i], SERVER_READY));
}
std::pair<ChunksDownloadStrategy::ChunkT *, int> ChunksDownloadStrategy::GetChunk(RangeT const & range)
{
std::vector<ChunkT>::iterator i = lower_bound(m_chunks.begin(), m_chunks.end(), range.first, LessChunks());
if (i != m_chunks.end() && i->m_pos == range.first)
{
ASSERT_EQUAL((i + 1)->m_pos, range.second + 1, ());
return std::pair<ChunkT *, int>(&(*i), std::distance(m_chunks.begin(), i));
}
else
{
LOG(LERROR, ("Downloader error. Invalid chunk range: ", range));
return std::pair<ChunkT *, int>(static_cast<ChunkT *>(0), -1);
}
}
void ChunksDownloadStrategy::InitChunks(int64_t fileSize, int64_t chunkSize, ChunkStatusT status)
{
if (chunkSize == 0)
{
int64_t constexpr kMb = 1024 * 1024;
size_t const sizeMb = std::max(fileSize / kMb, static_cast<int64_t>(1));
size_t constexpr kTargetCount = 40;
size_t constexpr kMinMb = 1;
size_t constexpr kMaxMb = 16;
size_t const chunkMb = std::min(std::max(sizeMb / kTargetCount, kMinMb), kMaxMb);
size_t chunksCount = sizeMb / chunkMb;
if (static_cast<int64_t>(kMb * chunkMb * chunksCount) < fileSize)
++chunksCount;
ASSERT_GREATER_OR_EQUAL(static_cast<int64_t>(kMb * chunkMb * chunksCount), fileSize, ());
LOG(LINFO, ("File size", sizeMb, "MB; chunk size", chunkMb, "MB; chunks count", chunksCount));
m_chunks.reserve(chunksCount + 1);
chunkSize = chunkMb * kMb;
}
else
{
m_chunks.reserve(static_cast<size_t>(fileSize / chunkSize + 2));
}
for (int64_t i = 0; i < fileSize; i += chunkSize)
m_chunks.push_back(ChunkT(i, status));
// The last AUX chunk is just used to hold end of the range (eof) for the previous chunk.
m_chunks.push_back(ChunkT(fileSize, CHUNK_AUX));
}
void ChunksDownloadStrategy::AddChunk(RangeT const & range, ChunkStatusT status)
{
ASSERT_LESS_OR_EQUAL(range.first, range.second, ());
if (m_chunks.empty())
{
ASSERT_EQUAL(range.first, 0, ());
m_chunks.push_back(ChunkT(range.first, status));
}
else
{
ASSERT_EQUAL(m_chunks.back().m_pos, range.first, ());
m_chunks.back().m_status = status;
}
m_chunks.push_back(ChunkT(range.second + 1, CHUNK_AUX));
}
void ChunksDownloadStrategy::SaveChunks(int64_t fileSize, std::string const & fName)
{
if (!m_chunks.empty())
{
try
{
FileWriter w(fName);
WriteVarInt(w, fileSize);
w.Write(&m_chunks[0], sizeof(ChunkT) * m_chunks.size());
return;
}
catch (FileWriter::Exception const & e)
{
LOG(LWARNING, ("Can't save chunks statuses to file", e.Msg()));
}
}
// Delete if no chunks or some error occured.
UNUSED_VALUE(Platform::RemoveFileIfExists(fName));
}
int64_t ChunksDownloadStrategy::LoadOrInitChunks(std::string const & fName, int64_t fileSize, int64_t chunkSize)
{
ASSERT(fileSize > 0, ());
if (Platform::IsFileExistsByFullPath(fName))
{
try
{
FileReader r(fName);
ReaderSource<FileReader> src(r);
int64_t const readSize = ReadVarInt<int64_t>(src);
if (readSize == fileSize)
{
// Load chunks.
uint64_t const size = src.Size();
int const stSize = sizeof(ChunkT);
auto const count = static_cast<size_t>(size / stSize);
ASSERT_EQUAL(size, stSize * count, ());
m_chunks.resize(count);
src.Read(&m_chunks[0], stSize * count);
// Reset status "downloading" to "free".
int64_t downloadedSize = 0;
size_t completed = 0;
for (size_t i = 0; i < count - 1; ++i)
{
if (m_chunks[i].m_status != CHUNK_COMPLETE)
m_chunks[i].m_status = CHUNK_FREE;
else
{
downloadedSize += (m_chunks[i + 1].m_pos - m_chunks[i].m_pos);
++completed;
}
}
LOG(LINFO, ("Resumed file: downloaded", downloadedSize / 1024 / 1024, "out of", fileSize / 1024 / 1024,
"MB; completed chunks", completed, "out of", count - 1));
return downloadedSize;
}
}
catch (FileReader::Exception const & e)
{
LOG(LDEBUG, (e.Msg()));
}
}
InitChunks(fileSize, chunkSize);
return 0;
}
std::string ChunksDownloadStrategy::ChunkFinished(bool success, RangeT const & range)
{
std::pair<ChunkT *, int> res = GetChunk(range);
std::string url;
// find server which was downloading this chunk
if (res.first)
{
for (size_t s = 0; s < m_servers.size(); ++s)
{
if (m_servers[s].m_chunkIndex == res.second)
{
url = m_servers[s].m_url;
if (success)
{
LOG(LDEBUG, ("Completed chunk", m_servers[s].m_chunkIndex, "via", m_servers[s].m_url));
// mark server as free and chunk as ready
m_servers[s].m_chunkIndex = SERVER_READY;
res.first->m_status = CHUNK_COMPLETE;
}
else
{
LOG(LWARNING, ("Failed to dl chunk", m_servers[s].m_chunkIndex, "via", m_servers[s].m_url));
// remove failed server and mark chunk as free
m_servers.erase(m_servers.begin() + s);
res.first->m_status = CHUNK_FREE;
}
break;
}
}
}
return url;
}
ChunksDownloadStrategy::ResultT ChunksDownloadStrategy::NextChunk(std::string & outUrl, RangeT & range)
{
// If no servers at all.
if (m_servers.empty())
return EDownloadFailed;
// Find first free server.
ServerT * server = 0;
for (size_t i = 0; i < m_servers.size(); ++i)
{
if (m_servers[i].m_chunkIndex == SERVER_READY)
{
server = &m_servers[i];
break;
}
}
if (server == 0)
return ENoFreeServers;
bool allChunksDownloaded = true;
// Find first free chunk.
for (size_t i = 0; i < m_chunks.size() - 1; ++i)
{
switch (m_chunks[i].m_status)
{
case CHUNK_FREE:
server->m_chunkIndex = static_cast<int>(i);
outUrl = server->m_url;
range.first = m_chunks[i].m_pos;
range.second = m_chunks[i + 1].m_pos - 1;
m_chunks[i].m_status = CHUNK_DOWNLOADING;
LOG(LDEBUG, ("Download chunk", server->m_chunkIndex, "via", outUrl));
return ENextChunk;
case CHUNK_DOWNLOADING: allChunksDownloaded = false; break;
}
}
return (allChunksDownloaded ? EDownloadSucceeded : ENoFreeServers);
}
} // namespace downloader