refactor: tr_completion (#2220)
* refactor: refactor tr_completion + add test coverage for it
This commit is contained in:
parent
06090f113a
commit
de169c7ec3
|
@ -920,7 +920,7 @@ static tr_announce_request* announce_request_new(
|
|||
req->up = tier->byteCounts[TR_ANN_UP];
|
||||
req->down = tier->byteCounts[TR_ANN_DOWN];
|
||||
req->corrupt = tier->byteCounts[TR_ANN_CORRUPT];
|
||||
req->leftUntilComplete = tr_torrentHasMetadata(tor) ? tor->info.totalSize - tr_torrentHaveTotal(tor) : INT64_MAX;
|
||||
req->leftUntilComplete = tr_torrentHasMetadata(tor) ? tor->info.totalSize - tor->hasTotal() : INT64_MAX;
|
||||
req->event = event;
|
||||
req->numwant = event == TR_ANNOUNCE_EVENT_STOPPED ? 0 : Numwant;
|
||||
req->key = announcer->key;
|
||||
|
|
|
@ -357,7 +357,7 @@ void tr_bitfield::set(size_t nth, bool value)
|
|||
}
|
||||
|
||||
/* Sets bit range [begin, end) to 1 */
|
||||
void tr_bitfield::setRange(size_t begin, size_t end, bool value)
|
||||
void tr_bitfield::setSpan(size_t begin, size_t end, bool value)
|
||||
{
|
||||
// did anything change?
|
||||
size_t const old_count = count(begin, end);
|
||||
|
|
|
@ -49,14 +49,14 @@ public:
|
|||
|
||||
// set one or more bits
|
||||
void set(size_t bit, bool value = true);
|
||||
void setRange(size_t begin, size_t end, bool value = true);
|
||||
void setSpan(size_t begin, size_t end, bool value = true);
|
||||
void unset(size_t bit)
|
||||
{
|
||||
set(bit, false);
|
||||
}
|
||||
void unsetRange(size_t begin, size_t end)
|
||||
void unsetSpan(size_t begin, size_t end)
|
||||
{
|
||||
setRange(begin, end, false);
|
||||
setSpan(begin, end, false);
|
||||
}
|
||||
void setFromBools(bool const* bytes, size_t n);
|
||||
|
||||
|
@ -93,6 +93,11 @@ public:
|
|||
return bit_count_;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr size_t empty() const
|
||||
{
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
#ifdef TR_ENABLE_ASSERTS
|
||||
bool assertValid() const;
|
||||
#endif
|
||||
|
|
|
@ -30,9 +30,9 @@ struct tr_block_info
|
|||
uint32_t final_piece_size = 0;
|
||||
|
||||
tr_block_info() = default;
|
||||
tr_block_info(uint64_t total_size, uint64_t piece_size)
|
||||
tr_block_info(uint64_t total_size_in, uint64_t piece_size_in)
|
||||
{
|
||||
initSizes(total_size, piece_size);
|
||||
initSizes(total_size_in, piece_size_in);
|
||||
}
|
||||
|
||||
void initSizes(uint64_t total_size_in, uint64_t piece_size_in);
|
||||
|
@ -54,9 +54,16 @@ struct tr_block_info
|
|||
return block + 1 == n_blocks ? final_block_size : block_size;
|
||||
}
|
||||
|
||||
constexpr tr_piece_index_t pieceOf(uint64_t offset) const
|
||||
{
|
||||
// handle 0-byte files at the end of a torrent
|
||||
return offset == total_size ? n_pieces - 1 : offset / piece_size;
|
||||
}
|
||||
|
||||
constexpr tr_block_index_t blockOf(uint64_t offset) const
|
||||
{
|
||||
return offset / block_size;
|
||||
// handle 0-byte files at the end of a torrent
|
||||
return offset == total_size ? n_blocks - 1 : offset / block_size;
|
||||
}
|
||||
|
||||
constexpr uint64_t offset(tr_piece_index_t piece, uint32_t offset, uint32_t length = 0) const
|
||||
|
@ -73,20 +80,16 @@ struct tr_block_info
|
|||
return blockOf(this->offset(piece, offset, length));
|
||||
}
|
||||
|
||||
constexpr tr_block_range_t blockRangeForPiece(tr_piece_index_t piece) const
|
||||
constexpr tr_block_span_t blockSpanForPiece(tr_piece_index_t piece) const
|
||||
{
|
||||
if (block_size == 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
uint64_t offset = piece_size;
|
||||
offset *= piece;
|
||||
tr_block_index_t const first_block = offset / block_size;
|
||||
offset += countBytesInPiece(piece) - 1;
|
||||
tr_block_index_t const final_block = offset / block_size;
|
||||
|
||||
return { first_block, final_block };
|
||||
auto const begin = blockOf(offset(piece, 0));
|
||||
auto const end = 1 + blockOf(offset(piece, countBytesInPiece(piece) - 1));
|
||||
return { begin, end };
|
||||
}
|
||||
|
||||
static uint32_t bestBlockSize(uint64_t piece_size);
|
||||
|
|
|
@ -100,7 +100,7 @@ static int getBlockRun(tr_cache const* cache, int pos, struct run_info* info)
|
|||
{
|
||||
struct cache_block const* b = blocks[pos + len - 1];
|
||||
info->last_block_time = b->time;
|
||||
info->is_piece_done = tr_torrentPieceIsComplete(b->tor, b->piece);
|
||||
info->is_piece_done = b->tor->hasPiece(b->piece);
|
||||
info->is_multi_piece = b->piece != blocks[pos]->piece;
|
||||
info->len = len;
|
||||
info->pos = pos;
|
||||
|
@ -426,10 +426,10 @@ int tr_cacheFlushDone(tr_cache* cache)
|
|||
|
||||
int tr_cacheFlushFile(tr_cache* cache, tr_torrent* torrent, tr_file_index_t i)
|
||||
{
|
||||
auto const [first, last] = tr_torGetFileBlockRange(torrent, i);
|
||||
auto const [begin, end] = tr_torGetFileBlockSpan(torrent, i);
|
||||
|
||||
int pos = findBlockPos(cache, torrent, first);
|
||||
dbgmsg("flushing file %d from cache to disk: blocks [%zu...%zu]", (int)i, (size_t)first, (size_t)last);
|
||||
int pos = findBlockPos(cache, torrent, begin);
|
||||
dbgmsg("flushing file %d from cache to disk: blocks [%zu...%zu)", (int)i, (size_t)begin, (size_t)end);
|
||||
|
||||
/* flush out all the blocks in that file */
|
||||
int err = 0;
|
||||
|
@ -442,7 +442,7 @@ int tr_cacheFlushFile(tr_cache* cache, tr_torrent* torrent, tr_file_index_t i)
|
|||
break;
|
||||
}
|
||||
|
||||
if (b->block < first || b->block > last)
|
||||
if (b->block < begin || b->block >= end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,75 +1,127 @@
|
|||
/*
|
||||
* This file Copyright (C) 2009-2014 Mnemosyne LLC
|
||||
* This file Copyright (C) Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "completion.h"
|
||||
#include "torrent.h"
|
||||
#include "tr-assert.h"
|
||||
#include "utils.h"
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
static void tr_cpReset(tr_completion* cp)
|
||||
uint64_t tr_completion::leftUntilDone() const
|
||||
{
|
||||
cp->sizeNow = 0;
|
||||
cp->sizeWhenDoneIsDirty = true;
|
||||
cp->haveValidIsDirty = true;
|
||||
cp->blockBitfield->setHasNone();
|
||||
auto const size_when_done = sizeWhenDone();
|
||||
auto const has_total = hasTotal();
|
||||
return size_when_done - has_total;
|
||||
}
|
||||
|
||||
void tr_cpConstruct(tr_completion* cp, tr_torrent* tor)
|
||||
uint64_t tr_completion::computeHasValid() const
|
||||
{
|
||||
cp->tor = tor;
|
||||
cp->blockBitfield = new tr_bitfield(tor->n_blocks);
|
||||
tr_cpReset(cp);
|
||||
}
|
||||
uint64_t size = 0;
|
||||
|
||||
void tr_cpBlockInit(tr_completion* cp, tr_bitfield const& b)
|
||||
{
|
||||
tr_cpReset(cp);
|
||||
|
||||
// set blockBitfield
|
||||
*(cp->blockBitfield) = b;
|
||||
|
||||
// set sizeNow
|
||||
cp->sizeNow = cp->blockBitfield->count();
|
||||
TR_ASSERT(cp->sizeNow <= cp->tor->n_blocks);
|
||||
cp->sizeNow *= cp->tor->block_size;
|
||||
|
||||
if (b.test(cp->tor->n_blocks - 1))
|
||||
for (tr_piece_index_t piece = 0, n = block_info_->n_pieces; piece < n; ++piece)
|
||||
{
|
||||
cp->sizeNow -= (cp->tor->block_size - cp->tor->final_block_size);
|
||||
if (hasPiece(piece))
|
||||
{
|
||||
size += block_info_->countBytesInPiece(piece);
|
||||
}
|
||||
}
|
||||
|
||||
TR_ASSERT(cp->sizeNow <= cp->tor->info.totalSize);
|
||||
return size;
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
tr_completeness tr_cpGetStatus(tr_completion const* cp)
|
||||
uint64_t tr_completion::hasValid() const
|
||||
{
|
||||
if (tr_cpHasAll(cp))
|
||||
if (!has_valid_)
|
||||
{
|
||||
return TR_SEED;
|
||||
has_valid_ = computeHasValid();
|
||||
}
|
||||
|
||||
if (!tr_torrentHasMetadata(cp->tor))
|
||||
return *has_valid_;
|
||||
}
|
||||
|
||||
uint64_t tr_completion::computeSizeWhenDone() const
|
||||
{
|
||||
if (hasAll())
|
||||
{
|
||||
return block_info_->total_size;
|
||||
}
|
||||
|
||||
// count bytes that we want or that we already have
|
||||
auto size = size_t{ 0 };
|
||||
for (tr_piece_index_t piece = 0; piece < block_info_->n_pieces; ++piece)
|
||||
{
|
||||
if (!tor_->pieceIsDnd(piece))
|
||||
{
|
||||
size += block_info_->countBytesInPiece(piece);
|
||||
}
|
||||
else
|
||||
{
|
||||
size += countHasBytesInSpan(block_info_->blockSpanForPiece(piece));
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
uint64_t tr_completion::sizeWhenDone() const
|
||||
{
|
||||
if (!size_when_done_)
|
||||
{
|
||||
size_when_done_ = computeSizeWhenDone();
|
||||
}
|
||||
|
||||
return *size_when_done_;
|
||||
}
|
||||
|
||||
void tr_completion::amountDone(float* tab, size_t n_tabs) const
|
||||
{
|
||||
if (n_tabs < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto const blocks_per_tab = std::size(blocks_) / n_tabs;
|
||||
for (size_t i = 0; i < n_tabs; ++i)
|
||||
{
|
||||
auto const begin = i * n_tabs;
|
||||
auto const end = std::min(begin + blocks_per_tab, std::size(blocks_));
|
||||
auto const numerator = blocks_.count(begin, end);
|
||||
tab[i] = (double)numerator / (end - begin);
|
||||
}
|
||||
}
|
||||
|
||||
size_t tr_completion::countMissingBlocksInPiece(tr_piece_index_t piece) const
|
||||
{
|
||||
auto const [begin, end] = block_info_->blockSpanForPiece(piece);
|
||||
return (end - begin) - blocks_.count(begin, end);
|
||||
}
|
||||
|
||||
size_t tr_completion::countMissingBytesInPiece(tr_piece_index_t piece) const
|
||||
{
|
||||
return block_info_->countBytesInPiece(piece) - countHasBytesInSpan(block_info_->blockSpanForPiece(piece));
|
||||
}
|
||||
|
||||
tr_completeness tr_completion::status() const
|
||||
{
|
||||
if (!hasMetainfo())
|
||||
{
|
||||
return TR_LEECH;
|
||||
}
|
||||
|
||||
if (cp->sizeNow == tr_cpSizeWhenDone(cp))
|
||||
if (hasAll())
|
||||
{
|
||||
return TR_SEED;
|
||||
}
|
||||
|
||||
if (size_now_ == sizeWhenDone())
|
||||
{
|
||||
return TR_PARTIAL_SEED;
|
||||
}
|
||||
|
@ -77,255 +129,76 @@ tr_completeness tr_cpGetStatus(tr_completion const* cp)
|
|||
return TR_LEECH;
|
||||
}
|
||||
|
||||
void tr_cpPieceRem(tr_completion* cp, tr_piece_index_t piece)
|
||||
std::vector<uint8_t> tr_completion::createPieceBitfield() const
|
||||
{
|
||||
tr_torrent const* tor = cp->tor;
|
||||
auto const [first, last] = cp->tor->blockRangeForPiece(piece);
|
||||
for (tr_block_index_t block = first; block <= last; ++block)
|
||||
{
|
||||
if (tr_cpBlockIsComplete(cp, block))
|
||||
{
|
||||
cp->sizeNow -= tor->countBytesInBlock(block);
|
||||
}
|
||||
}
|
||||
|
||||
cp->haveValidIsDirty = true;
|
||||
cp->sizeWhenDoneIsDirty = true;
|
||||
cp->blockBitfield->unsetRange(first, last + 1);
|
||||
}
|
||||
|
||||
void tr_cpPieceAdd(tr_completion* cp, tr_piece_index_t piece)
|
||||
{
|
||||
auto const [first, last] = cp->tor->blockRangeForPiece(piece);
|
||||
for (tr_block_index_t i = first; i <= last; ++i)
|
||||
{
|
||||
tr_cpBlockAdd(cp, i);
|
||||
}
|
||||
}
|
||||
|
||||
void tr_cpBlockAdd(tr_completion* cp, tr_block_index_t block)
|
||||
{
|
||||
tr_torrent const* tor = cp->tor;
|
||||
|
||||
if (!tr_cpBlockIsComplete(cp, block))
|
||||
{
|
||||
tr_piece_index_t const piece = cp->tor->pieceForBlock(block);
|
||||
|
||||
cp->blockBitfield->set(block);
|
||||
cp->sizeNow += tor->countBytesInBlock(block);
|
||||
|
||||
cp->haveValidIsDirty = true;
|
||||
cp->sizeWhenDoneIsDirty = cp->sizeWhenDoneIsDirty || tor->pieceIsDnd(piece);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
uint64_t tr_cpHaveValid(tr_completion const* ccp)
|
||||
{
|
||||
if (ccp->haveValidIsDirty)
|
||||
{
|
||||
uint64_t size = 0;
|
||||
tr_completion* cp = const_cast<tr_completion*>(ccp); /* mutable */
|
||||
tr_torrent const* tor = ccp->tor;
|
||||
tr_info const* info = &tor->info;
|
||||
|
||||
for (tr_piece_index_t i = 0; i < info->pieceCount; ++i)
|
||||
{
|
||||
if (tr_cpPieceIsComplete(ccp, i))
|
||||
{
|
||||
size += tor->countBytesInPiece(i);
|
||||
}
|
||||
}
|
||||
|
||||
cp->haveValidLazy = size;
|
||||
cp->haveValidIsDirty = false;
|
||||
}
|
||||
|
||||
return ccp->haveValidLazy;
|
||||
}
|
||||
|
||||
uint64_t tr_cpSizeWhenDone(tr_completion const* ccp)
|
||||
{
|
||||
if (ccp->sizeWhenDoneIsDirty)
|
||||
{
|
||||
uint64_t size = 0;
|
||||
tr_torrent const* tor = ccp->tor;
|
||||
tr_info const* inf = tr_torrentInfo(tor);
|
||||
tr_completion* cp = const_cast<tr_completion*>(ccp); /* mutable */
|
||||
|
||||
if (tr_cpHasAll(ccp))
|
||||
{
|
||||
size = inf->totalSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (tr_piece_index_t p = 0; p < inf->pieceCount; ++p)
|
||||
{
|
||||
uint64_t n = 0;
|
||||
uint64_t const pieceSize = tor->countBytesInPiece(p);
|
||||
|
||||
if (!tor->pieceIsDnd(p))
|
||||
{
|
||||
n = pieceSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const [first, last] = cp->tor->blockRangeForPiece(p);
|
||||
n = cp->blockBitfield->count(first, last + 1);
|
||||
n *= cp->tor->block_size;
|
||||
|
||||
if (last == cp->tor->n_blocks - 1 && cp->blockBitfield->test(last))
|
||||
{
|
||||
n -= cp->tor->block_size - cp->tor->final_block_size;
|
||||
}
|
||||
}
|
||||
|
||||
TR_ASSERT(n <= tor->countBytesInPiece(p));
|
||||
size += n;
|
||||
}
|
||||
}
|
||||
|
||||
TR_ASSERT(size <= inf->totalSize);
|
||||
TR_ASSERT(size >= cp->sizeNow);
|
||||
|
||||
cp->sizeWhenDoneLazy = size;
|
||||
cp->sizeWhenDoneIsDirty = false;
|
||||
}
|
||||
|
||||
return ccp->sizeWhenDoneLazy;
|
||||
}
|
||||
|
||||
uint64_t tr_cpLeftUntilDone(tr_completion const* cp)
|
||||
{
|
||||
uint64_t const sizeWhenDone = tr_cpSizeWhenDone(cp);
|
||||
|
||||
TR_ASSERT(sizeWhenDone >= cp->sizeNow);
|
||||
|
||||
return sizeWhenDone - cp->sizeNow;
|
||||
}
|
||||
|
||||
void tr_cpGetAmountDone(tr_completion const* cp, float* tab, int tabCount)
|
||||
{
|
||||
bool const seed = tr_cpHasAll(cp);
|
||||
float const interval = cp->tor->info.pieceCount / (float)tabCount;
|
||||
|
||||
for (int i = 0; i < tabCount; ++i)
|
||||
{
|
||||
if (seed)
|
||||
{
|
||||
tab[i] = 1.0F;
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_piece_index_t const piece = (tr_piece_index_t)i * interval;
|
||||
auto const [first, last] = cp->tor->blockRangeForPiece(piece);
|
||||
tab[i] = cp->blockBitfield->count(first, last + 1) / (float)(last + 1 - first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t tr_cpMissingBlocksInPiece(tr_completion const* cp, tr_piece_index_t piece)
|
||||
{
|
||||
if (tr_cpHasAll(cp))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto const [first, last] = cp->tor->blockRangeForPiece(piece);
|
||||
return (last + 1 - first) - cp->blockBitfield->count(first, last + 1);
|
||||
}
|
||||
|
||||
size_t tr_cpMissingBytesInPiece(tr_completion const* cp, tr_piece_index_t piece)
|
||||
{
|
||||
if (tr_cpHasAll(cp))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t const pieceByteSize = cp->tor->countBytesInPiece(piece);
|
||||
auto const [first, last] = cp->tor->blockRangeForPiece(piece);
|
||||
|
||||
auto haveBytes = size_t{};
|
||||
if (first != last)
|
||||
{
|
||||
/* nb: we don't pass the usual l+1 here to Bitfield::countRange().
|
||||
It's faster to handle the last block separately because its size
|
||||
needs to be checked separately. */
|
||||
haveBytes = cp->blockBitfield->count(first, last);
|
||||
haveBytes *= cp->tor->block_size;
|
||||
}
|
||||
|
||||
if (cp->blockBitfield->test(last)) /* handle the last block */
|
||||
{
|
||||
haveBytes += cp->tor->countBytesInBlock(last);
|
||||
}
|
||||
|
||||
TR_ASSERT(haveBytes <= pieceByteSize);
|
||||
return pieceByteSize - haveBytes;
|
||||
}
|
||||
|
||||
bool tr_cpFileIsComplete(tr_completion const* cp, tr_file_index_t i)
|
||||
{
|
||||
if (cp->tor->info.files[i].length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
auto const [first, last] = tr_torGetFileBlockRange(cp->tor, i);
|
||||
return cp->blockBitfield->count(first, last + 1) == (last + 1 - first);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> tr_cpCreatePieceBitfield(tr_completion const* cp)
|
||||
{
|
||||
TR_ASSERT(tr_torrentHasMetadata(cp->tor));
|
||||
|
||||
auto const n = cp->tor->info.pieceCount;
|
||||
|
||||
size_t const n = block_info_->n_pieces;
|
||||
auto pieces = tr_bitfield{ n };
|
||||
|
||||
if (tr_cpHasAll(cp))
|
||||
bool* const flags = new bool[n];
|
||||
for (tr_piece_index_t piece = 0; piece < n; ++piece)
|
||||
{
|
||||
pieces.setHasAll();
|
||||
}
|
||||
else if (!tr_cpHasNone(cp))
|
||||
{
|
||||
bool* flags = tr_new(bool, n);
|
||||
|
||||
for (tr_piece_index_t i = 0; i < n; ++i)
|
||||
{
|
||||
flags[i] = tr_cpPieceIsComplete(cp, i);
|
||||
}
|
||||
|
||||
pieces.setFromBools(flags, n);
|
||||
tr_free(flags);
|
||||
flags[piece] = hasPiece(piece);
|
||||
}
|
||||
pieces.setFromBools(flags, n);
|
||||
delete[] flags;
|
||||
|
||||
return pieces.raw();
|
||||
}
|
||||
|
||||
double tr_cpPercentComplete(tr_completion const* cp)
|
||||
{
|
||||
double const ratio = tr_getRatio(cp->sizeNow, cp->tor->info.totalSize);
|
||||
/// mutators
|
||||
|
||||
if ((int)ratio == TR_RATIO_NA)
|
||||
void tr_completion::addBlock(tr_block_index_t block)
|
||||
{
|
||||
if (hasBlock(block))
|
||||
{
|
||||
return 0.0;
|
||||
return; // already had it
|
||||
}
|
||||
|
||||
if ((int)ratio == TR_RATIO_INF)
|
||||
blocks_.set(block);
|
||||
size_now_ += block_info_->countBytesInBlock(block);
|
||||
|
||||
has_valid_.reset();
|
||||
}
|
||||
|
||||
void tr_completion::setBlocks(tr_bitfield blocks)
|
||||
{
|
||||
TR_ASSERT(std::size(blocks_) == std::size(blocks));
|
||||
|
||||
blocks_ = std::move(blocks);
|
||||
size_now_ = countHasBytesInSpan({ 0, tr_block_index_t(std::size(blocks_)) });
|
||||
size_when_done_.reset();
|
||||
has_valid_.reset();
|
||||
}
|
||||
|
||||
void tr_completion::addPiece(tr_piece_index_t piece)
|
||||
{
|
||||
auto const [begin, end] = block_info_->blockSpanForPiece(piece);
|
||||
|
||||
for (tr_block_index_t block = begin; block < end; ++block)
|
||||
{
|
||||
return 1.0;
|
||||
addBlock(block);
|
||||
}
|
||||
}
|
||||
|
||||
void tr_completion::removePiece(tr_piece_index_t piece)
|
||||
{
|
||||
auto const [begin, end] = block_info_->blockSpanForPiece(piece);
|
||||
size_now_ -= countHasBytesInSpan(block_info_->blockSpanForPiece(piece));
|
||||
has_valid_.reset();
|
||||
blocks_.unsetSpan(begin, end);
|
||||
}
|
||||
|
||||
uint64_t tr_completion::countHasBytesInSpan(tr_block_span_t span) const
|
||||
{
|
||||
auto const [begin, end] = span;
|
||||
|
||||
auto n = blocks_.count(begin, end);
|
||||
n *= block_info_->block_size;
|
||||
|
||||
if (end == block_info_->n_blocks && blocks_.test(end - 1))
|
||||
{
|
||||
n -= block_info_->block_size - block_info_->final_block_size;
|
||||
}
|
||||
|
||||
return ratio;
|
||||
}
|
||||
|
||||
double tr_cpPercentDone(tr_completion const* cp)
|
||||
{
|
||||
double const ratio = tr_getRatio(cp->sizeNow, tr_cpSizeWhenDone(cp));
|
||||
int const iratio = (int)ratio;
|
||||
return (iratio == TR_RATIO_NA || iratio == TR_RATIO_INF) ? 0.0 : ratio;
|
||||
return n;
|
||||
}
|
||||
|
|
|
@ -12,122 +12,147 @@
|
|||
#error only libtransmission should #include this header.
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "block-info.h"
|
||||
#include "bitfield.h"
|
||||
|
||||
/**
|
||||
* @brief knows which blocks and pieces we have
|
||||
*/
|
||||
struct tr_completion
|
||||
{
|
||||
tr_torrent* tor;
|
||||
struct torrent_view
|
||||
{
|
||||
virtual bool pieceIsDnd(tr_piece_index_t piece) const = 0;
|
||||
};
|
||||
|
||||
// Changed to non-owning pointer temporarily till tr_completion becomes C++-constructible and destructible
|
||||
// TODO: remove * and own the value
|
||||
tr_bitfield* blockBitfield;
|
||||
explicit tr_completion(torrent_view const* tor, tr_block_info const* block_info)
|
||||
: tor_{ tor }
|
||||
, block_info_{ block_info }
|
||||
, blocks_{ block_info_->n_blocks }
|
||||
{
|
||||
blocks_.setHasNone();
|
||||
}
|
||||
|
||||
/* number of bytes we'll have when done downloading. [0..info.totalSize]
|
||||
DON'T access this directly; it's a lazy field.
|
||||
use tr_cpSizeWhenDone() instead! */
|
||||
uint64_t sizeWhenDoneLazy;
|
||||
[[nodiscard]] constexpr tr_bitfield const& blocks() const
|
||||
{
|
||||
return blocks_;
|
||||
}
|
||||
|
||||
/* whether or not sizeWhenDone needs to be recalculated */
|
||||
bool sizeWhenDoneIsDirty;
|
||||
[[nodiscard]] constexpr bool hasAll() const
|
||||
{
|
||||
return hasMetainfo() && blocks_.hasAll();
|
||||
}
|
||||
|
||||
/* number of bytes we'll have when done downloading. [0..info.totalSize]
|
||||
DON'T access this directly; it's a lazy field.
|
||||
use tr_cpHaveValid() instead! */
|
||||
uint64_t haveValidLazy;
|
||||
[[nodiscard]] bool hasBlock(tr_block_index_t block) const
|
||||
{
|
||||
return blocks_.test(block);
|
||||
}
|
||||
|
||||
/* whether or not haveValidLazy needs to be recalculated */
|
||||
bool haveValidIsDirty;
|
||||
[[nodiscard]] bool hasBlocks(tr_block_span_t span) const
|
||||
{
|
||||
return blocks_.count(span.begin, span.end) == span.end - span.begin;
|
||||
}
|
||||
|
||||
/* number of bytes we want or have now. [0..sizeWhenDone] */
|
||||
uint64_t sizeNow;
|
||||
[[nodiscard]] constexpr bool hasNone() const
|
||||
{
|
||||
return !hasMetainfo() || blocks_.hasNone();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool hasPiece(tr_piece_index_t piece) const
|
||||
{
|
||||
return block_info_->piece_size != 0 && countMissingBlocksInPiece(piece) == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr uint64_t hasTotal() const
|
||||
{
|
||||
return size_now_;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t hasValid() const;
|
||||
|
||||
[[nodiscard]] bool isDone() const
|
||||
{
|
||||
return hasMetainfo() && leftUntilDone() == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t leftUntilDone() const;
|
||||
|
||||
[[nodiscard]] constexpr double percentComplete() const
|
||||
{
|
||||
auto const denom = block_info_->total_size;
|
||||
return denom ? std::clamp(double(size_now_) / denom, 0.0, 1.0) : 0.0;
|
||||
}
|
||||
|
||||
[[nodiscard]] double percentDone() const
|
||||
{
|
||||
auto const denom = sizeWhenDone();
|
||||
return denom ? std::clamp(double(size_now_) / denom, 0.0, 1.0) : 0.0;
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t sizeWhenDone() const;
|
||||
|
||||
[[nodiscard]] tr_completeness status() const;
|
||||
|
||||
[[nodiscard]] std::vector<uint8_t> createPieceBitfield() const;
|
||||
|
||||
[[nodiscard]] size_t countMissingBlocksInPiece(tr_piece_index_t) const;
|
||||
[[nodiscard]] size_t countMissingBytesInPiece(tr_piece_index_t) const;
|
||||
|
||||
void amountDone(float* tab, size_t n_tabs) const;
|
||||
|
||||
void addBlock(tr_block_index_t i);
|
||||
void addPiece(tr_piece_index_t i);
|
||||
void removePiece(tr_piece_index_t i);
|
||||
|
||||
void setHasPiece(tr_piece_index_t i, bool has)
|
||||
{
|
||||
if (has)
|
||||
{
|
||||
addPiece(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
removePiece(i);
|
||||
}
|
||||
}
|
||||
|
||||
void setBlocks(tr_bitfield blocks);
|
||||
|
||||
void invalidateSizeWhenDone()
|
||||
{
|
||||
size_when_done_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] constexpr bool hasMetainfo() const
|
||||
{
|
||||
return !std::empty(blocks_);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t computeHasValid() const;
|
||||
[[nodiscard]] uint64_t computeSizeWhenDone() const;
|
||||
[[nodiscard]] uint64_t countHasBytesInSpan(tr_block_span_t) const;
|
||||
|
||||
torrent_view const* tor_;
|
||||
tr_block_info const* block_info_;
|
||||
|
||||
tr_bitfield blocks_{ 0 };
|
||||
|
||||
// Number of bytes we'll have when done downloading. [0..info.totalSize]
|
||||
// Mutable because lazy-calculated
|
||||
mutable std::optional<uint64_t> size_when_done_;
|
||||
|
||||
// Number of verified bytes we have right now. [0..info.totalSize]
|
||||
// Mutable because lazy-calculated
|
||||
mutable std::optional<uint64_t> has_valid_;
|
||||
|
||||
// Number of bytes we have now. [0..sizeWhenDone]
|
||||
uint64_t size_now_ = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
*** Life Cycle
|
||||
**/
|
||||
|
||||
void tr_cpConstruct(tr_completion*, tr_torrent*);
|
||||
|
||||
void tr_cpBlockInit(tr_completion* cp, tr_bitfield const& blocks);
|
||||
|
||||
static inline void tr_cpDestruct(tr_completion* cp)
|
||||
{
|
||||
delete cp->blockBitfield;
|
||||
}
|
||||
|
||||
/**
|
||||
*** General
|
||||
**/
|
||||
|
||||
double tr_cpPercentComplete(tr_completion const* cp);
|
||||
|
||||
double tr_cpPercentDone(tr_completion const* cp);
|
||||
|
||||
tr_completeness tr_cpGetStatus(tr_completion const*);
|
||||
|
||||
uint64_t tr_cpHaveValid(tr_completion const*);
|
||||
|
||||
uint64_t tr_cpSizeWhenDone(tr_completion const*);
|
||||
|
||||
uint64_t tr_cpLeftUntilDone(tr_completion const*);
|
||||
|
||||
void tr_cpGetAmountDone(tr_completion const* completion, float* tab, int tabCount);
|
||||
|
||||
constexpr uint64_t tr_cpHaveTotal(tr_completion const* cp)
|
||||
{
|
||||
return cp->sizeNow;
|
||||
}
|
||||
|
||||
static inline bool tr_cpHasAll(tr_completion const* cp)
|
||||
{
|
||||
return tr_torrentHasMetadata(cp->tor) && cp->blockBitfield->hasAll();
|
||||
}
|
||||
|
||||
static inline bool tr_cpHasNone(tr_completion const* cp)
|
||||
{
|
||||
return !tr_torrentHasMetadata(cp->tor) || cp->blockBitfield->hasNone();
|
||||
}
|
||||
|
||||
/**
|
||||
*** Pieces
|
||||
**/
|
||||
|
||||
void tr_cpPieceAdd(tr_completion* cp, tr_piece_index_t i);
|
||||
|
||||
void tr_cpPieceRem(tr_completion* cp, tr_piece_index_t i);
|
||||
|
||||
size_t tr_cpMissingBlocksInPiece(tr_completion const*, tr_piece_index_t);
|
||||
|
||||
size_t tr_cpMissingBytesInPiece(tr_completion const*, tr_piece_index_t);
|
||||
|
||||
static inline bool tr_cpPieceIsComplete(tr_completion const* cp, tr_piece_index_t i)
|
||||
{
|
||||
return tr_cpMissingBlocksInPiece(cp, i) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*** Blocks
|
||||
**/
|
||||
|
||||
void tr_cpBlockAdd(tr_completion* cp, tr_block_index_t i);
|
||||
|
||||
static inline bool tr_cpBlockIsComplete(tr_completion const* cp, tr_block_index_t i)
|
||||
{
|
||||
return cp->blockBitfield->test(i);
|
||||
}
|
||||
|
||||
/***
|
||||
**** Misc
|
||||
***/
|
||||
|
||||
bool tr_cpFileIsComplete(tr_completion const* cp, tr_file_index_t);
|
||||
|
||||
std::vector<uint8_t> tr_cpCreatePieceBitfield(tr_completion const* cp);
|
||||
|
||||
constexpr void tr_cpInvalidateDND(tr_completion* cp)
|
||||
{
|
||||
cp->sizeWhenDoneIsDirty = true;
|
||||
}
|
||||
|
|
|
@ -102,38 +102,38 @@ std::vector<Candidate> getCandidates(Wishlist::PeerInfo const& peer_info)
|
|||
return candidates;
|
||||
}
|
||||
|
||||
static std::vector<tr_block_range_t> makeRanges(tr_block_index_t const* sorted_blocks, size_t n_blocks)
|
||||
static std::vector<tr_block_span_t> makeSpans(tr_block_index_t const* sorted_blocks, size_t n_blocks)
|
||||
{
|
||||
if (n_blocks == 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto ranges = std::vector<tr_block_range_t>{};
|
||||
auto cur = tr_block_range_t{ sorted_blocks[0], sorted_blocks[0] };
|
||||
auto spans = std::vector<tr_block_span_t>{};
|
||||
auto cur = tr_block_span_t{ sorted_blocks[0], sorted_blocks[0] + 1 };
|
||||
for (size_t i = 1; i < n_blocks; ++i)
|
||||
{
|
||||
if (cur.last + 1 == sorted_blocks[i])
|
||||
if (cur.end == sorted_blocks[i])
|
||||
{
|
||||
cur.last = sorted_blocks[i];
|
||||
++cur.end;
|
||||
}
|
||||
else
|
||||
{
|
||||
ranges.push_back(cur);
|
||||
cur = tr_block_range_t{ sorted_blocks[i], sorted_blocks[i] };
|
||||
spans.push_back(cur);
|
||||
cur = tr_block_span_t{ sorted_blocks[i], sorted_blocks[i] + 1 };
|
||||
}
|
||||
}
|
||||
ranges.push_back(cur);
|
||||
spans.push_back(cur);
|
||||
|
||||
return ranges;
|
||||
return spans;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<tr_block_range_t> Wishlist::next(Wishlist::PeerInfo const& peer_info, size_t n_wanted_blocks)
|
||||
std::vector<tr_block_span_t> Wishlist::next(Wishlist::PeerInfo const& peer_info, size_t n_wanted_blocks)
|
||||
{
|
||||
size_t n_blocks = 0;
|
||||
auto ranges = std::vector<tr_block_range_t>{};
|
||||
auto spans = std::vector<tr_block_span_t>{};
|
||||
|
||||
// sanity clause
|
||||
TR_ASSERT(n_wanted_blocks > 0);
|
||||
|
@ -154,10 +154,10 @@ std::vector<tr_block_range_t> Wishlist::next(Wishlist::PeerInfo const& peer_info
|
|||
}
|
||||
|
||||
// walk the blocks in this piece
|
||||
auto const [first, last] = peer_info.blockRange(candidate.piece);
|
||||
auto const [begin, end] = peer_info.blockSpan(candidate.piece);
|
||||
auto blocks = std::vector<tr_block_index_t>{};
|
||||
blocks.reserve(last + 1 - first);
|
||||
for (tr_block_index_t block = first; block <= last && n_blocks + std::size(blocks) < n_wanted_blocks; ++block)
|
||||
blocks.reserve(end - begin);
|
||||
for (tr_block_index_t block = begin; block < end && n_blocks + std::size(blocks) < n_wanted_blocks; ++block)
|
||||
{
|
||||
// don't request blocks we've already got
|
||||
if (!peer_info.clientCanRequestBlock(block))
|
||||
|
@ -181,19 +181,19 @@ std::vector<tr_block_range_t> Wishlist::next(Wishlist::PeerInfo const& peer_info
|
|||
continue;
|
||||
}
|
||||
|
||||
// copy the ranges into `ranges`
|
||||
auto const tmp = makeRanges(std::data(blocks), std::size(blocks));
|
||||
std::copy(std::begin(tmp), std::end(tmp), std::back_inserter(ranges));
|
||||
// copy the spans into `spans`
|
||||
auto const tmp = makeSpans(std::data(blocks), std::size(blocks));
|
||||
std::copy(std::begin(tmp), std::end(tmp), std::back_inserter(spans));
|
||||
n_blocks += std::accumulate(
|
||||
std::begin(tmp),
|
||||
std::end(tmp),
|
||||
size_t{},
|
||||
[](size_t sum, auto range) { return sum + range.last + 1 - range.first; });
|
||||
[](size_t sum, auto span) { return sum + span.end - span.begin; });
|
||||
if (n_blocks >= n_wanted_blocks)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ranges;
|
||||
return spans;
|
||||
}
|
||||
|
|
|
@ -28,11 +28,11 @@ public:
|
|||
virtual bool isEndgame() const = 0;
|
||||
virtual size_t countActiveRequests(tr_block_index_t block) const = 0;
|
||||
virtual size_t countMissingBlocks(tr_piece_index_t piece) const = 0;
|
||||
virtual tr_block_range_t blockRange(tr_piece_index_t) const = 0;
|
||||
virtual tr_block_span_t blockSpan(tr_piece_index_t) const = 0;
|
||||
virtual tr_piece_index_t countAllPieces() const = 0;
|
||||
virtual tr_priority_t priority(tr_piece_index_t) const = 0;
|
||||
};
|
||||
|
||||
// get a list of the next blocks that we should request from a peer
|
||||
std::vector<tr_block_range_t> next(PeerInfo const& peer_info, size_t n_wanted_blocks);
|
||||
std::vector<tr_block_span_t> next(PeerInfo const& peer_info, size_t n_wanted_blocks);
|
||||
};
|
||||
|
|
|
@ -539,12 +539,12 @@ static int countActiveWebseeds(tr_swarm* s)
|
|||
}
|
||||
|
||||
// TODO: if we keep this, add equivalent API to ActiveRequest
|
||||
void tr_peerMgrClientSentRequests(tr_torrent* torrent, tr_peer* peer, tr_block_range_t range)
|
||||
void tr_peerMgrClientSentRequests(tr_torrent* torrent, tr_peer* peer, tr_block_span_t span)
|
||||
{
|
||||
// std::cout << __FILE__ << ':' << __LINE__ << " tr_peerMgrClientSentRequests [" << range.first << "..." << range.last << ']' << std::endl;
|
||||
// std::cout << __FILE__ << ':' << __LINE__ << " tr_peerMgrClientSentRequests [" << range.begin << "..." << range.end << ')' << std::endl;
|
||||
auto const now = tr_time();
|
||||
|
||||
for (tr_block_index_t block = range.first; block <= range.last; ++block)
|
||||
for (tr_block_index_t block = span.begin; block < span.end; ++block)
|
||||
{
|
||||
torrent->swarm->active_requests.add(block, peer, now);
|
||||
}
|
||||
|
@ -554,10 +554,10 @@ static void updateEndgame(tr_swarm* s)
|
|||
{
|
||||
/* we consider ourselves to be in endgame if the number of bytes
|
||||
we've got requested is >= the number of bytes left to download */
|
||||
s->endgame = uint64_t(std::size(s->active_requests)) * s->tor->block_size >= tr_torrentGetLeftUntilDone(s->tor);
|
||||
s->endgame = uint64_t(std::size(s->active_requests)) * s->tor->block_size >= s->tor->leftUntilDone();
|
||||
}
|
||||
|
||||
std::vector<tr_block_range_t> tr_peerMgrGetNextRequests(tr_torrent* torrent, tr_peer* peer, size_t numwant)
|
||||
std::vector<tr_block_span_t> tr_peerMgrGetNextRequests(tr_torrent* torrent, tr_peer* peer, size_t numwant)
|
||||
{
|
||||
class PeerInfoImpl : public Wishlist::PeerInfo
|
||||
{
|
||||
|
@ -571,7 +571,7 @@ std::vector<tr_block_range_t> tr_peerMgrGetNextRequests(tr_torrent* torrent, tr_
|
|||
|
||||
bool clientCanRequestBlock(tr_block_index_t block) const override
|
||||
{
|
||||
return !tr_torrentBlockIsComplete(torrent_, block) && !swarm_->active_requests.has(block, peer_);
|
||||
return !torrent_->hasBlock(block) && !swarm_->active_requests.has(block, peer_);
|
||||
}
|
||||
|
||||
bool clientCanRequestPiece(tr_piece_index_t piece) const override
|
||||
|
@ -591,12 +591,12 @@ std::vector<tr_block_range_t> tr_peerMgrGetNextRequests(tr_torrent* torrent, tr_
|
|||
|
||||
size_t countMissingBlocks(tr_piece_index_t piece) const override
|
||||
{
|
||||
return tr_torrentMissingBlocksInPiece(torrent_, piece);
|
||||
return torrent_->countMissingBlocksInPiece(piece);
|
||||
}
|
||||
|
||||
tr_block_range_t blockRange(tr_piece_index_t piece) const override
|
||||
tr_block_span_t blockSpan(tr_piece_index_t piece) const override
|
||||
{
|
||||
return torrent_->blockRangeForPiece(piece);
|
||||
return torrent_->blockSpanForPiece(piece);
|
||||
}
|
||||
|
||||
tr_piece_index_t countAllPieces() const override
|
||||
|
@ -705,7 +705,7 @@ static void peerSuggestedPiece(tr_swarm* /*s*/, tr_peer* /*peer*/, tr_piece_inde
|
|||
}
|
||||
|
||||
/* don't ask for it if we've already got it */
|
||||
if (tr_torrentPieceIsComplete(t->tor, pieceIndex))
|
||||
if (t->tor->hasPiece(pieceIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -725,11 +725,11 @@ static void peerSuggestedPiece(tr_swarm* /*s*/, tr_peer* /*peer*/, tr_piece_inde
|
|||
/* request the blocks that we don't have in this piece */
|
||||
{
|
||||
tr_torrent const* tor = t->tor;
|
||||
auto const [first, last] = tor->blockRangeForPiece(pieceIndex);
|
||||
auto const [begin, end] = tor->blockSpanForPiece(pieceIndex);
|
||||
|
||||
for (tr_block_index_t b = first; b <= last; ++b)
|
||||
for (tr_block_index_t b = begin; b < end; ++b)
|
||||
{
|
||||
if (tr_torrentBlockIsComplete(tor, b))
|
||||
if (tor->hasBlock(b))
|
||||
{
|
||||
uint32_t const offset = getBlockOffsetInPiece(tor, b);
|
||||
uint32_t const length = tor->countBytesInBlock(b);
|
||||
|
@ -1590,7 +1590,7 @@ void tr_peerMgrTorrentAvailability(tr_torrent const* tor, int8_t* tab, unsigned
|
|||
{
|
||||
int const piece = i * interval;
|
||||
|
||||
if (isSeed || tr_torrentPieceIsComplete(tor, piece))
|
||||
if (isSeed || tor->hasPiece(piece))
|
||||
{
|
||||
tab[i] = -1;
|
||||
}
|
||||
|
@ -1669,7 +1669,7 @@ uint64_t tr_peerMgrGetDesiredAvailable(tr_torrent const* tor)
|
|||
{
|
||||
if (peers[i]->atom != nullptr && atomIsSeed(peers[i]->atom))
|
||||
{
|
||||
return tr_torrentGetLeftUntilDone(tor);
|
||||
return tor->leftUntilDone();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1695,7 +1695,7 @@ uint64_t tr_peerMgrGetDesiredAvailable(tr_torrent const* tor)
|
|||
{
|
||||
if (!tor->pieceIsDnd(i) && have.at(i))
|
||||
{
|
||||
desired_available += tr_torrentMissingBytesInPiece(tor, i);
|
||||
desired_available += tor->countMissingBytesInPiece(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2021,7 +2021,7 @@ static void rechokeDownloads(tr_swarm* s)
|
|||
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
piece_is_interesting[i] = !tor->pieceIsDnd(i) && !tr_torrentPieceIsComplete(tor, i);
|
||||
piece_is_interesting[i] = !tor->pieceIsDnd(i) && !tor->hasPiece(i);
|
||||
}
|
||||
|
||||
/* decide WHICH peers to be interested in (based on their cancel-to-block ratio) */
|
||||
|
|
|
@ -79,11 +79,11 @@ void tr_peerMgrSetUtpSupported(tr_torrent* tor, tr_address const* addr);
|
|||
|
||||
void tr_peerMgrSetUtpFailed(tr_torrent* tor, tr_address const* addr, bool failed);
|
||||
|
||||
std::vector<tr_block_range_t> tr_peerMgrGetNextRequests(tr_torrent* torrent, tr_peer* peer, size_t numwant);
|
||||
std::vector<tr_block_span_t> tr_peerMgrGetNextRequests(tr_torrent* torrent, tr_peer* peer, size_t numwant);
|
||||
|
||||
bool tr_peerMgrDidPeerRequest(tr_torrent const* torrent, tr_peer const* peer, tr_block_index_t block);
|
||||
|
||||
void tr_peerMgrClientSentRequests(tr_torrent* torrent, tr_peer* peer, tr_block_range_t range);
|
||||
void tr_peerMgrClientSentRequests(tr_torrent* torrent, tr_peer* peer, tr_block_span_t span);
|
||||
|
||||
size_t tr_peerMgrCountActiveRequestsToPeer(tr_torrent const* torrent, tr_peer const* peer);
|
||||
|
||||
|
|
|
@ -1478,7 +1478,7 @@ static void peerMadeRequest(tr_peerMsgsImpl* msgs, struct peer_request const* re
|
|||
{
|
||||
bool const fext = tr_peerIoSupportsFEXT(msgs->io);
|
||||
bool const reqIsValid = requestIsValid(msgs, req);
|
||||
bool const clientHasPiece = reqIsValid && tr_torrentPieceIsComplete(msgs->torrent, req->index);
|
||||
bool const clientHasPiece = reqIsValid && msgs->torrent->hasPiece(req->index);
|
||||
bool const peerIsChoked = msgs->peer_is_choked_;
|
||||
|
||||
bool allow = false;
|
||||
|
@ -1907,7 +1907,7 @@ static int clientGotBlock(tr_peerMsgsImpl* msgs, struct evbuffer* data, struct p
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (tr_torrentPieceIsComplete(msgs->torrent, req->index))
|
||||
if (msgs->torrent->hasPiece(req->index))
|
||||
{
|
||||
dbgmsg(msgs, "we did ask for this message, but the piece is already complete...");
|
||||
return 0;
|
||||
|
@ -2086,15 +2086,15 @@ static void updateBlockRequests(tr_peerMsgsImpl* msgs)
|
|||
TR_ASSERT(!msgs->is_client_choked());
|
||||
|
||||
// std::cout << __FILE__ << ':' << __LINE__ << " wants " << n_wanted << " blocks to request" << std::endl;
|
||||
for (auto const range : tr_peerMgrGetNextRequests(msgs->torrent, msgs, n_wanted))
|
||||
for (auto const span : tr_peerMgrGetNextRequests(msgs->torrent, msgs, n_wanted))
|
||||
{
|
||||
for (tr_block_index_t block = range.first; block <= range.last; ++block)
|
||||
for (tr_block_index_t block = span.begin; block < span.end; ++block)
|
||||
{
|
||||
protocolSendRequest(msgs, blockToReq(msgs->torrent, block));
|
||||
}
|
||||
|
||||
// std::cout << __FILE__ << ':' << __LINE__ << " peer " << (void*)msgs << " requested " << range.last + 1 - range.first << " blocks" << std::endl;
|
||||
tr_peerMgrClientSentRequests(msgs->torrent, msgs, range);
|
||||
// std::cout << __FILE__ << ':' << __LINE__ << " peer " << (void*)msgs << " requested " << span.end - span.begin << " blocks" << std::endl;
|
||||
tr_peerMgrClientSentRequests(msgs->torrent, msgs, span);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2198,7 +2198,7 @@ static size_t fillOutputBuffer(tr_peerMsgsImpl* msgs, time_t now)
|
|||
{
|
||||
--msgs->prefetchCount;
|
||||
|
||||
if (requestIsValid(msgs, &req) && tr_torrentPieceIsComplete(msgs->torrent, req.index))
|
||||
if (requestIsValid(msgs, &req) && msgs->torrent->hasPiece(req.index))
|
||||
{
|
||||
uint32_t const msglen = 4 + 1 + 4 + 4 + req.length;
|
||||
struct evbuffer_iovec iovec[1];
|
||||
|
@ -2330,7 +2330,7 @@ static void sendBitfield(tr_peerMsgsImpl* msgs)
|
|||
|
||||
struct evbuffer* out = msgs->outMessages;
|
||||
|
||||
auto bytes = tr_torrentCreatePieceBitfield(msgs->torrent);
|
||||
auto bytes = msgs->torrent->createPieceBitfield();
|
||||
evbuffer_add_uint32(out, sizeof(uint8_t) + bytes.size());
|
||||
evbuffer_add_uint8(out, BtBitfield);
|
||||
evbuffer_add(out, bytes.data(), std::size(bytes));
|
||||
|
@ -2342,15 +2342,15 @@ static void tellPeerWhatWeHave(tr_peerMsgsImpl* msgs)
|
|||
{
|
||||
bool const fext = tr_peerIoSupportsFEXT(msgs->io);
|
||||
|
||||
if (fext && tr_torrentHasAll(msgs->torrent))
|
||||
if (fext && msgs->torrent->hasAll())
|
||||
{
|
||||
protocolSendHaveAll(msgs);
|
||||
}
|
||||
else if (fext && tr_torrentHasNone(msgs->torrent))
|
||||
else if (fext && msgs->torrent->hasNone())
|
||||
{
|
||||
protocolSendHaveNone(msgs);
|
||||
}
|
||||
else if (!tr_torrentHasNone(msgs->torrent))
|
||||
else if (!msgs->torrent->hasNone())
|
||||
{
|
||||
sendBitfield(msgs);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <vector>
|
||||
|
||||
#include "transmission.h"
|
||||
#include "completion.h"
|
||||
#include "error.h"
|
||||
#include "file.h"
|
||||
#include "log.h"
|
||||
|
@ -454,19 +453,19 @@ static uint64_t loadFilenames(tr_variant* dict, tr_torrent* tor)
|
|||
****
|
||||
***/
|
||||
|
||||
static void bitfieldToRaw(tr_bitfield const* b, tr_variant* benc)
|
||||
static void bitfieldToRaw(tr_bitfield const& b, tr_variant* benc)
|
||||
{
|
||||
if (b->hasNone() || std::size(*b) == 0)
|
||||
if (b.hasNone() || std::empty(b))
|
||||
{
|
||||
tr_variantInitStr(benc, "none"sv);
|
||||
}
|
||||
else if (b->hasAll())
|
||||
else if (b.hasAll())
|
||||
{
|
||||
tr_variantInitStrView(benc, "all"sv);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const raw = b->raw();
|
||||
auto const raw = b.raw();
|
||||
tr_variantInitRaw(benc, raw.data(), std::size(raw));
|
||||
}
|
||||
}
|
||||
|
@ -502,7 +501,7 @@ static void saveProgress(tr_variant* dict, tr_torrent* tor)
|
|||
}
|
||||
|
||||
// add the 'checked pieces' bitfield
|
||||
bitfieldToRaw(&tor->checked_pieces_, tr_variantDictAdd(prog, TR_KEY_pieces));
|
||||
bitfieldToRaw(tor->checked_pieces_, tr_variantDictAdd(prog, TR_KEY_pieces));
|
||||
|
||||
/* add the progress */
|
||||
if (tor->completeness == TR_SEED)
|
||||
|
@ -511,7 +510,7 @@ static void saveProgress(tr_variant* dict, tr_torrent* tor)
|
|||
}
|
||||
|
||||
/* add the blocks bitfield */
|
||||
bitfieldToRaw(tor->completion.blockBitfield, tr_variantDictAdd(prog, TR_KEY_blocks));
|
||||
bitfieldToRaw(tor->blocks(), tr_variantDictAdd(prog, TR_KEY_blocks));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -659,7 +658,7 @@ static uint64_t loadProgress(tr_variant* dict, tr_torrent* tor)
|
|||
}
|
||||
else
|
||||
{
|
||||
tr_cpBlockInit(&tor->completion, blocks);
|
||||
tor->setBlocks(blocks);
|
||||
}
|
||||
|
||||
ret = TR_FR_PROGRESS;
|
||||
|
@ -888,7 +887,7 @@ static uint64_t loadFromFile(tr_torrent* tor, uint64_t fieldsToLoad, bool* didRe
|
|||
// Only load file priorities if we are actually downloading.
|
||||
// If we're a seed or partial seed, loading it is a waste of time.
|
||||
// NB: this is why loadProgress() comes before loadFilePriorities()
|
||||
if ((tr_cpLeftUntilDone(&tor->completion) != 0) && (fieldsToLoad & TR_FR_FILE_PRIORITIES) != 0)
|
||||
if (tor->isDone() && (fieldsToLoad & TR_FR_FILE_PRIORITIES) != 0)
|
||||
{
|
||||
fieldsLoaded |= loadFilePriorities(&top, tor);
|
||||
}
|
||||
|
|
|
@ -690,7 +690,7 @@ static void initField(
|
|||
case TR_KEY_pieces:
|
||||
if (tr_torrentHasMetadata(tor))
|
||||
{
|
||||
auto const bytes = tr_torrentCreatePieceBitfield(tor);
|
||||
auto const bytes = tor->createPieceBitfield();
|
||||
auto* enc = static_cast<char*>(tr_base64_encode(bytes.data(), std::size(bytes), nullptr));
|
||||
tr_variantInitStr(initme, enc != nullptr ? std::string_view{ enc } : ""sv);
|
||||
tr_free(enc);
|
||||
|
|
|
@ -297,7 +297,7 @@ void tr_torrentSetMetadataPiece(tr_torrent* tor, int piece, void const* data, in
|
|||
|
||||
auto info = tr_metainfoParse(tor->session, &newMetainfo, nullptr);
|
||||
success = !!info;
|
||||
if (info && tr_getBlockSize(info->info.pieceSize) == 0)
|
||||
if (info && tr_block_info::bestBlockSize(info->info.pieceSize) == 0)
|
||||
{
|
||||
tr_torrentSetLocalError(tor, "%s", _("Magnet torrent's metadata is not usable"));
|
||||
success = false;
|
||||
|
|
|
@ -350,7 +350,7 @@ static bool tr_torrentGetSeedRatioBytes(tr_torrent const* tor, uint64_t* setmeLe
|
|||
{
|
||||
uint64_t const u = tor->uploadedCur + tor->uploadedPrev;
|
||||
uint64_t const d = tor->downloadedCur + tor->downloadedPrev;
|
||||
uint64_t const baseline = d != 0 ? d : tr_cpSizeWhenDone(&tor->completion);
|
||||
uint64_t const baseline = d != 0 ? d : tor->completion.sizeWhenDone();
|
||||
uint64_t const goal = baseline * seedRatio;
|
||||
|
||||
if (setmeLeft != nullptr)
|
||||
|
@ -578,33 +578,17 @@ static void onTrackerResponse(tr_torrent* tor, tr_tracker_event const* event, vo
|
|||
****
|
||||
***/
|
||||
|
||||
static constexpr tr_piece_index_t getBytePiece(tr_info const* info, uint64_t byteOffset)
|
||||
static constexpr void initFilePieces(tr_torrent* tor, tr_file_index_t fileIndex)
|
||||
{
|
||||
TR_ASSERT(info != nullptr);
|
||||
TR_ASSERT(info->pieceSize != 0);
|
||||
TR_ASSERT(tor != nullptr);
|
||||
TR_ASSERT(fileIndex < tor->info.fileCount);
|
||||
|
||||
tr_piece_index_t piece = byteOffset / info->pieceSize;
|
||||
tr_file* file = &tor->info.files[fileIndex];
|
||||
uint64_t first_byte = file->offset;
|
||||
uint64_t last_byte = first_byte + (file->length != 0 ? file->length - 1 : 0);
|
||||
|
||||
/* handle 0-byte files at the end of a torrent */
|
||||
if (byteOffset == info->totalSize)
|
||||
{
|
||||
piece = info->pieceCount - 1;
|
||||
}
|
||||
|
||||
return piece;
|
||||
}
|
||||
|
||||
static constexpr void initFilePieces(tr_info* info, tr_file_index_t fileIndex)
|
||||
{
|
||||
TR_ASSERT(info != nullptr);
|
||||
TR_ASSERT(fileIndex < info->fileCount);
|
||||
|
||||
tr_file* file = &info->files[fileIndex];
|
||||
uint64_t firstByte = file->offset;
|
||||
uint64_t lastByte = firstByte + (file->length != 0 ? file->length - 1 : 0);
|
||||
|
||||
file->firstPiece = getBytePiece(info, firstByte);
|
||||
file->lastPiece = getBytePiece(info, lastByte);
|
||||
file->firstPiece = tor->pieceOf(first_byte);
|
||||
file->lastPiece = tor->pieceOf(last_byte);
|
||||
}
|
||||
|
||||
static constexpr bool pieceHasFile(tr_piece_index_t piece, tr_file const* file)
|
||||
|
@ -659,7 +643,7 @@ static void tr_torrentInitFilePieces(tr_torrent* tor)
|
|||
{
|
||||
inf->files[f].offset = offset;
|
||||
offset += inf->files[f].length;
|
||||
initFilePieces(inf, f);
|
||||
initFilePieces(tor, f);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -699,40 +683,13 @@ static void tr_torrentInitPiecePriorities(tr_torrent* tor)
|
|||
|
||||
static void torrentStart(tr_torrent* tor, bool bypass_queue);
|
||||
|
||||
/**
|
||||
* Decide on a block size. Constraints:
|
||||
* (1) most clients decline requests over 16 KiB
|
||||
* (2) pieceSize must be a multiple of block size
|
||||
*/
|
||||
uint32_t tr_getBlockSize(uint32_t pieceSize)
|
||||
{
|
||||
uint32_t b = pieceSize;
|
||||
|
||||
while (b > MAX_BLOCK_SIZE)
|
||||
{
|
||||
b /= 2U;
|
||||
}
|
||||
|
||||
if (b == 0 || pieceSize % b != 0) /* not cleanly divisible */
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
static void torrentInitFromInfo(tr_torrent* tor)
|
||||
{
|
||||
tor->initSizes(tor->info.totalSize, tor->info.pieceSize);
|
||||
tr_cpConstruct(&tor->completion, tor);
|
||||
tr_torrentInitFilePieces(tor);
|
||||
}
|
||||
|
||||
static void tr_torrentFireMetadataCompleted(tr_torrent* tor);
|
||||
|
||||
void tr_torrentGotNewInfoDict(tr_torrent* tor)
|
||||
{
|
||||
torrentInitFromInfo(tor);
|
||||
tor->initSizes(tor->info.totalSize, tor->info.pieceSize);
|
||||
tor->completion = tr_completion{ tor, tor };
|
||||
tr_torrentInitFilePieces(tor);
|
||||
|
||||
tr_peerMgrOnTorrentGotMetainfo(tor);
|
||||
|
||||
|
@ -756,7 +713,7 @@ static bool hasAnyLocalData(tr_torrent const* tor)
|
|||
|
||||
static bool setLocalErrorIfFilesDisappeared(tr_torrent* tor)
|
||||
{
|
||||
bool const disappeared = tr_torrentHaveTotal(tor) > 0 && !hasAnyLocalData(tor);
|
||||
bool const disappeared = tor->hasTotal() > 0 && !hasAnyLocalData(tor);
|
||||
|
||||
if (disappeared)
|
||||
{
|
||||
|
@ -833,7 +790,9 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
|
|||
|
||||
tr_torrentSetDateAdded(tor, tr_time()); /* this is a default value to be overwritten by the resume file */
|
||||
|
||||
torrentInitFromInfo(tor);
|
||||
tor->initSizes(tor->info.totalSize, tor->info.pieceSize);
|
||||
tor->completion = tr_completion{ tor, tor };
|
||||
tr_torrentInitFilePieces(tor);
|
||||
|
||||
// tr_torrentLoadResume() calls a lot of tr_torrentSetFoo() methods
|
||||
// that set things as dirty, but... these settings being loaded are
|
||||
|
@ -850,7 +809,7 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
|
|||
tr_metainfoMigrateFile(session, &tor->info, TR_METAINFO_BASENAME_NAME_AND_PARTIAL_HASH, TR_METAINFO_BASENAME_HASH);
|
||||
}
|
||||
|
||||
tor->completeness = tr_cpGetStatus(&tor->completion);
|
||||
tor->completeness = tor->completion.status();
|
||||
setLocalErrorIfFilesDisappeared(tor);
|
||||
|
||||
tr_ctorInitTorrentPriorities(ctor, tor);
|
||||
|
@ -988,7 +947,7 @@ tr_torrent* tr_torrentNew(tr_ctor const* ctor, int* setme_error, int* setme_dupl
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
auto* tor = new tr_torrent{};
|
||||
auto* tor = new tr_torrent{ parsed->info };
|
||||
tor->swapMetainfo(*parsed);
|
||||
torrentInit(tor, ctor);
|
||||
return tor;
|
||||
|
@ -1175,12 +1134,12 @@ tr_stat const* tr_torrentStat(tr_torrent* tor)
|
|||
auto const pieceDownloadSpeed_Bps = tor->bandwidth->getPieceSpeedBytesPerSecond(now, TR_DOWN);
|
||||
s->pieceDownloadSpeed_KBps = toSpeedKBps(pieceDownloadSpeed_Bps);
|
||||
|
||||
s->percentComplete = tr_cpPercentComplete(&tor->completion);
|
||||
s->percentComplete = tor->completion.percentComplete();
|
||||
s->metadataPercentComplete = tr_torrentGetMetadataPercent(tor);
|
||||
|
||||
s->percentDone = tr_cpPercentDone(&tor->completion);
|
||||
s->leftUntilDone = tr_torrentGetLeftUntilDone(tor);
|
||||
s->sizeWhenDone = tr_cpSizeWhenDone(&tor->completion);
|
||||
s->percentDone = tor->completion.percentDone();
|
||||
s->leftUntilDone = tor->completion.leftUntilDone();
|
||||
s->sizeWhenDone = tor->completion.sizeWhenDone();
|
||||
s->recheckProgress = s->activity == TR_STATUS_CHECK ? getVerifyProgress(tor) : 0;
|
||||
s->activityDate = tor->activityDate;
|
||||
s->addedDate = tor->addedDate;
|
||||
|
@ -1193,8 +1152,8 @@ tr_stat const* tr_torrentStat(tr_torrent* tor)
|
|||
s->corruptEver = tor->corruptCur + tor->corruptPrev;
|
||||
s->downloadedEver = tor->downloadedCur + tor->downloadedPrev;
|
||||
s->uploadedEver = tor->uploadedCur + tor->uploadedPrev;
|
||||
s->haveValid = tr_cpHaveValid(&tor->completion);
|
||||
s->haveUnchecked = tr_torrentHaveTotal(tor) - s->haveValid;
|
||||
s->haveValid = tor->completion.hasValid();
|
||||
s->haveUnchecked = tor->hasTotal() - s->haveValid;
|
||||
s->desiredAvailable = tr_peerMgrGetDesiredAvailable(tor);
|
||||
|
||||
s->ratio = tr_getRatio(s->uploadedEver, s->downloadedEver != 0 ? s->downloadedEver : s->haveValid);
|
||||
|
@ -1311,33 +1270,39 @@ static uint64_t countFileBytesCompleted(tr_torrent const* tor, tr_file_index_t i
|
|||
return 0;
|
||||
}
|
||||
|
||||
auto const [first, last] = tr_torGetFileBlockRange(tor, index);
|
||||
auto const [begin, end] = tr_torGetFileBlockSpan(tor, index);
|
||||
auto const n = end - begin;
|
||||
|
||||
if (first == last)
|
||||
if (n == 0)
|
||||
{
|
||||
return tr_torrentBlockIsComplete(tor, first) ? f.length : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (n == 1)
|
||||
{
|
||||
return tor->hasBlock(begin) ? f.length : 0;
|
||||
}
|
||||
|
||||
auto total = uint64_t{};
|
||||
|
||||
// the first block
|
||||
if (tr_torrentBlockIsComplete(tor, first))
|
||||
if (tor->hasBlock(begin))
|
||||
{
|
||||
total += tor->block_size - f.offset % tor->block_size;
|
||||
}
|
||||
|
||||
// the middle blocks
|
||||
if (first + 1 < last)
|
||||
if (begin + 1 < end)
|
||||
{
|
||||
uint64_t u = tor->completion.blockBitfield->count(first + 1, last);
|
||||
uint64_t u = tor->completion.blocks().count(begin + 1, end - 1);
|
||||
u *= tor->block_size;
|
||||
total += u;
|
||||
}
|
||||
|
||||
// the last block
|
||||
if (tr_torrentBlockIsComplete(tor, last))
|
||||
if (tor->hasBlock(end - 1))
|
||||
{
|
||||
total += f.offset + f.length - (uint64_t)tor->block_size * last;
|
||||
total += f.offset + f.length - (uint64_t)tor->block_size * (end - 1);
|
||||
}
|
||||
|
||||
return total;
|
||||
|
@ -1404,9 +1369,9 @@ void tr_torrentAvailability(tr_torrent const* tor, int8_t* tab, int size)
|
|||
}
|
||||
}
|
||||
|
||||
void tr_torrentAmountFinished(tr_torrent const* tor, float* tab, int size)
|
||||
void tr_torrentAmountFinished(tr_torrent const* tor, float* tabs, int n_tabs)
|
||||
{
|
||||
tr_cpGetAmountDone(&tor->completion, tab, size);
|
||||
return tor->amountDoneBins(tabs, n_tabs);
|
||||
}
|
||||
|
||||
static void tr_torrentResetTransferStats(tr_torrent* tor)
|
||||
|
@ -1423,21 +1388,6 @@ static void tr_torrentResetTransferStats(tr_torrent* tor)
|
|||
tr_torrentSetDirty(tor);
|
||||
}
|
||||
|
||||
void tr_torrentSetHasPiece(tr_torrent* tor, tr_piece_index_t pieceIndex, bool has)
|
||||
{
|
||||
TR_ASSERT(tr_isTorrent(tor));
|
||||
TR_ASSERT(pieceIndex < tor->info.pieceCount);
|
||||
|
||||
if (has)
|
||||
{
|
||||
tr_cpPieceAdd(&tor->completion, pieceIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
tr_cpPieceRem(&tor->completion, pieceIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
@ -1460,8 +1410,6 @@ static void freeTorrent(tr_torrent* tor)
|
|||
|
||||
tr_announcerRemoveTorrent(session->announcer, tor);
|
||||
|
||||
tr_cpDestruct(&tor->completion);
|
||||
|
||||
tr_free(tor->downloadDir);
|
||||
tr_free(tor->incompleteDir);
|
||||
|
||||
|
@ -1508,7 +1456,7 @@ static void torrentStartImpl(void* vtor)
|
|||
time_t const now = tr_time();
|
||||
|
||||
tor->isRunning = true;
|
||||
tor->completeness = tr_cpGetStatus(&tor->completion);
|
||||
tor->completeness = tor->completion.status();
|
||||
tor->startDate = now;
|
||||
tor->anyDate = now;
|
||||
tr_torrentClearError(tor);
|
||||
|
@ -2018,7 +1966,7 @@ void tr_torrentRecheckCompleteness(tr_torrent* tor)
|
|||
{
|
||||
auto const lock = tor->unique_lock();
|
||||
|
||||
auto const completeness = tr_cpGetStatus(&tor->completion);
|
||||
auto const completeness = tor->completion.status();
|
||||
|
||||
if (completeness != tor->completeness)
|
||||
{
|
||||
|
@ -2233,7 +2181,7 @@ void tr_torrentInitFileDLs(tr_torrent* tor, tr_file_index_t const* files, tr_fil
|
|||
}
|
||||
}
|
||||
|
||||
tr_cpInvalidateDND(&tor->completion);
|
||||
tor->completion.invalidateSizeWhenDone();
|
||||
}
|
||||
|
||||
void tr_torrentSetFileDLs(tr_torrent* tor, tr_file_index_t const* files, tr_file_index_t fileCount, bool doDownload)
|
||||
|
@ -2379,20 +2327,20 @@ uint64_t tr_pieceOffset(tr_torrent const* tor, tr_piece_index_t index, uint32_t
|
|||
return ret;
|
||||
}
|
||||
|
||||
tr_block_range_t tr_torGetFileBlockRange(tr_torrent const* tor, tr_file_index_t const file)
|
||||
tr_block_span_t tr_torGetFileBlockSpan(tr_torrent const* tor, tr_file_index_t const file)
|
||||
{
|
||||
tr_file const* f = &tor->info.files[file];
|
||||
|
||||
uint64_t offset = f->offset;
|
||||
tr_block_index_t const first = offset / tor->block_size;
|
||||
tr_block_index_t const begin = offset / tor->block_size;
|
||||
if (f->length == 0)
|
||||
{
|
||||
return { first, first };
|
||||
return { begin, begin };
|
||||
}
|
||||
|
||||
offset += f->length - 1;
|
||||
tr_block_index_t const last = offset / tor->block_size;
|
||||
return { first, last };
|
||||
tr_block_index_t const end = 1 + offset / tor->block_size;
|
||||
return { begin, end };
|
||||
}
|
||||
|
||||
/***
|
||||
|
@ -3054,7 +3002,8 @@ static void tr_torrentPieceCompleted(tr_torrent* tor, tr_piece_index_t pieceInde
|
|||
{
|
||||
tr_file const* file = &tor->info.files[i];
|
||||
|
||||
if ((file->firstPiece <= pieceIndex) && (pieceIndex <= file->lastPiece) && tr_cpFileIsComplete(&tor->completion, i))
|
||||
if ((file->firstPiece <= pieceIndex) && (pieceIndex <= file->lastPiece) &&
|
||||
tor->completion.hasBlocks(tr_torGetFileBlockSpan(tor, i)))
|
||||
{
|
||||
tr_torrentFileCompleted(tor, i);
|
||||
}
|
||||
|
@ -3066,16 +3015,16 @@ void tr_torrentGotBlock(tr_torrent* tor, tr_block_index_t block)
|
|||
TR_ASSERT(tr_isTorrent(tor));
|
||||
TR_ASSERT(tr_amInEventThread(tor->session));
|
||||
|
||||
bool const block_is_new = !tr_torrentBlockIsComplete(tor, block);
|
||||
bool const block_is_new = !tor->hasBlock(block);
|
||||
|
||||
if (block_is_new)
|
||||
{
|
||||
tr_cpBlockAdd(&tor->completion, block);
|
||||
tor->completion.addBlock(block);
|
||||
tr_torrentSetDirty(tor);
|
||||
|
||||
tr_piece_index_t const p = tor->pieceForBlock(block);
|
||||
|
||||
if (tr_torrentPieceIsComplete(tor, p))
|
||||
if (tor->hasPiece(p))
|
||||
{
|
||||
if (tor->checkPiece(p))
|
||||
{
|
||||
|
|
|
@ -65,8 +65,6 @@ void tr_torrentSetLabels(tr_torrent* tor, tr_labels_t&& labels);
|
|||
|
||||
void tr_torrentRecheckCompleteness(tr_torrent*);
|
||||
|
||||
void tr_torrentSetHasPiece(tr_torrent* tor, tr_piece_index_t pieceIndex, bool has);
|
||||
|
||||
void tr_torrentChangeMyPort(tr_torrent* session);
|
||||
|
||||
tr_sha1_digest_t tr_torrentInfoHash(tr_torrent const* torrent);
|
||||
|
@ -90,7 +88,7 @@ void tr_torrentGetBlockLocation(
|
|||
uint32_t* offset,
|
||||
uint32_t* length);
|
||||
|
||||
tr_block_range_t tr_torGetFileBlockRange(tr_torrent const* tor, tr_file_index_t const file);
|
||||
tr_block_span_t tr_torGetFileBlockSpan(tr_torrent const* tor, tr_file_index_t const file);
|
||||
|
||||
void tr_torrentInitFilePriority(tr_torrent* tor, tr_file_index_t fileIndex, tr_priority_t priority);
|
||||
|
||||
|
@ -125,9 +123,19 @@ tr_torrent_activity tr_torrentGetActivity(tr_torrent const* tor);
|
|||
struct tr_incomplete_metadata;
|
||||
|
||||
/** @brief Torrent object */
|
||||
struct tr_torrent : public tr_block_info
|
||||
struct tr_torrent
|
||||
: public tr_block_info
|
||||
, public tr_completion::torrent_view
|
||||
{
|
||||
public:
|
||||
tr_torrent(tr_info const& inf)
|
||||
: tr_block_info{ inf.totalSize, inf.pieceSize }
|
||||
, completion{ this, this }
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~tr_torrent() = default;
|
||||
|
||||
void setLocation(
|
||||
std::string_view location,
|
||||
bool move_from_current_location,
|
||||
|
@ -159,34 +167,89 @@ public:
|
|||
return session->unique_lock();
|
||||
}
|
||||
|
||||
tr_session* session;
|
||||
tr_info info;
|
||||
/// COMPLETION
|
||||
|
||||
int magicNumber;
|
||||
[[nodiscard]] uint64_t leftUntilDone() const
|
||||
{
|
||||
return completion.leftUntilDone();
|
||||
}
|
||||
|
||||
std::optional<double> verify_progress;
|
||||
[[nodiscard]] bool hasAll() const
|
||||
{
|
||||
return completion.hasAll();
|
||||
}
|
||||
|
||||
tr_stat_errtype error;
|
||||
char errorString[128];
|
||||
tr_quark error_announce_url;
|
||||
[[nodiscard]] bool hasNone() const
|
||||
{
|
||||
return completion.hasNone();
|
||||
}
|
||||
|
||||
/// DND
|
||||
[[nodiscard]] bool hasPiece(tr_piece_index_t piece) const
|
||||
{
|
||||
return completion.hasPiece(piece);
|
||||
}
|
||||
|
||||
tr_bitfield dnd_pieces_ = tr_bitfield{ 0 };
|
||||
[[nodiscard]] bool hasBlock(tr_block_index_t block) const
|
||||
{
|
||||
return completion.hasBlock(block);
|
||||
}
|
||||
|
||||
bool pieceIsDnd(tr_piece_index_t piece) const
|
||||
[[nodiscard]] size_t countMissingBlocksInPiece(tr_piece_index_t piece) const
|
||||
{
|
||||
return completion.countMissingBlocksInPiece(piece);
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t countMissingBytesInPiece(tr_piece_index_t piece) const
|
||||
{
|
||||
return completion.countMissingBytesInPiece(piece);
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64_t hasTotal() const
|
||||
{
|
||||
return completion.hasTotal();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<uint8_t> createPieceBitfield() const
|
||||
{
|
||||
return completion.createPieceBitfield();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isDone() const
|
||||
{
|
||||
return completion.isDone();
|
||||
}
|
||||
|
||||
[[nodiscard]] tr_bitfield const& blocks() const
|
||||
{
|
||||
return completion.blocks();
|
||||
}
|
||||
|
||||
void amountDoneBins(float* tab, int n_tabs) const
|
||||
{
|
||||
return completion.amountDone(tab, n_tabs);
|
||||
}
|
||||
|
||||
void setBlocks(tr_bitfield blocks)
|
||||
{
|
||||
completion.setBlocks(std::move(blocks));
|
||||
}
|
||||
|
||||
void setHasPiece(tr_piece_index_t piece, bool has)
|
||||
{
|
||||
completion.setHasPiece(piece, has);
|
||||
}
|
||||
|
||||
bool pieceIsDnd(tr_piece_index_t piece) const final
|
||||
{
|
||||
return dnd_pieces_.test(piece);
|
||||
}
|
||||
|
||||
/// PRIORITIES
|
||||
|
||||
// since 'TR_PRI_NORMAL' is by far the most common, save some
|
||||
// space by treating anything not in the map as normal
|
||||
std::unordered_map<tr_piece_index_t, tr_priority_t> piece_priorities_;
|
||||
|
||||
void setPiecePriority(tr_piece_index_t piece, tr_priority_t priority)
|
||||
{
|
||||
// since 'TR_PRI_NORMAL' is by far the most common, save some
|
||||
// space by treating anything not in the map as normal
|
||||
if (priority == TR_PRI_NORMAL)
|
||||
{
|
||||
piece_priorities_.erase(piece);
|
||||
|
@ -215,10 +278,6 @@ public:
|
|||
|
||||
/// CHECKSUMS
|
||||
|
||||
tr_bitfield checked_pieces_ = tr_bitfield{ 0 };
|
||||
|
||||
bool checkPiece(tr_piece_index_t piece);
|
||||
|
||||
bool ensurePieceIsChecked(tr_piece_index_t piece)
|
||||
{
|
||||
TR_ASSERT(piece < info.pieceCount);
|
||||
|
@ -254,7 +313,7 @@ public:
|
|||
{
|
||||
auto const begin = info.files[i].firstPiece;
|
||||
auto const end = info.files[i].lastPiece + 1;
|
||||
checked_pieces_.unsetRange(begin, end);
|
||||
checked_pieces_.unsetSpan(begin, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -278,14 +337,44 @@ public:
|
|||
|
||||
std::optional<tr_found_file_t> findFile(std::string& filename, tr_file_index_t i) const;
|
||||
|
||||
///
|
||||
public:
|
||||
tr_info info = {};
|
||||
|
||||
uint8_t obfuscatedHash[SHA_DIGEST_LENGTH];
|
||||
tr_bitfield dnd_pieces_ = tr_bitfield{ 0 };
|
||||
|
||||
tr_bitfield checked_pieces_ = tr_bitfield{ 0 };
|
||||
|
||||
// TODO(ckerr): make private once some of torrent.cc's `tr_torrentFoo()` methods are member functions
|
||||
tr_completion completion;
|
||||
|
||||
tr_session* session = nullptr;
|
||||
|
||||
struct tr_torrent_tiers* tiers = nullptr;
|
||||
|
||||
// Changed to non-owning pointer temporarily till tr_torrent becomes C++-constructible and destructible
|
||||
// TODO: change tr_bandwidth* to owning pointer to the bandwidth, or remove * and own the value
|
||||
Bandwidth* bandwidth = nullptr;
|
||||
|
||||
tr_swarm* swarm = nullptr;
|
||||
|
||||
int magicNumber;
|
||||
|
||||
std::optional<double> verify_progress;
|
||||
|
||||
std::unordered_map<tr_piece_index_t, tr_priority_t> piece_priorities_;
|
||||
|
||||
tr_stat_errtype error = TR_STAT_OK;
|
||||
char errorString[128] = {};
|
||||
tr_quark error_announce_url = TR_KEY_NONE;
|
||||
|
||||
bool checkPiece(tr_piece_index_t piece);
|
||||
|
||||
uint8_t obfuscatedHash[SHA_DIGEST_LENGTH] = {};
|
||||
|
||||
/* Used when the torrent has been created with a magnet link
|
||||
* and we're in the process of downloading the metainfo from
|
||||
* other peers */
|
||||
struct tr_incomplete_metadata* incompleteMetadata;
|
||||
struct tr_incomplete_metadata* incompleteMetadata = nullptr;
|
||||
|
||||
/* If the initiator of the connection receives a handshake in which the
|
||||
* peer_id does not match the expected peerid, then the initiator is
|
||||
|
@ -296,118 +385,108 @@ public:
|
|||
*/
|
||||
std::optional<tr_peer_id_t> peer_id;
|
||||
|
||||
time_t peer_id_creation_time;
|
||||
time_t peer_id_creation_time = 0;
|
||||
|
||||
/* Where the files will be when it's complete */
|
||||
char* downloadDir;
|
||||
char* downloadDir = nullptr;
|
||||
|
||||
/* Where the files are when the torrent is incomplete */
|
||||
char* incompleteDir;
|
||||
char* incompleteDir = nullptr;
|
||||
|
||||
/* Where the files are now.
|
||||
* This pointer will be equal to downloadDir or incompleteDir */
|
||||
char const* currentDir = nullptr;
|
||||
|
||||
/* Length, in bytes, of the "info" dict in the .torrent file. */
|
||||
uint64_t infoDictLength;
|
||||
uint64_t infoDictLength = 0;
|
||||
|
||||
/* Offset, in bytes, of the beginning of the "info" dict in the .torrent file.
|
||||
*
|
||||
* Used by the torrent-magnet code for serving metainfo to peers.
|
||||
* This field is lazy-generated and might not be initialized yet. */
|
||||
size_t infoDictOffset;
|
||||
size_t infoDictOffset = 0;
|
||||
|
||||
/* Where the files are now.
|
||||
* This pointer will be equal to downloadDir or incompleteDir */
|
||||
char const* currentDir;
|
||||
tr_completeness completeness = TR_LEECH;
|
||||
|
||||
struct tr_completion completion;
|
||||
time_t dhtAnnounceAt = 0;
|
||||
time_t dhtAnnounce6At = 0;
|
||||
bool dhtAnnounceInProgress = false;
|
||||
bool dhtAnnounce6InProgress = false;
|
||||
|
||||
tr_completeness completeness;
|
||||
time_t lpdAnnounceAt = 0;
|
||||
|
||||
struct tr_torrent_tiers* tiers;
|
||||
|
||||
time_t dhtAnnounceAt;
|
||||
time_t dhtAnnounce6At;
|
||||
bool dhtAnnounceInProgress;
|
||||
bool dhtAnnounce6InProgress;
|
||||
|
||||
time_t lpdAnnounceAt;
|
||||
|
||||
uint64_t downloadedCur;
|
||||
uint64_t downloadedPrev;
|
||||
uint64_t uploadedCur;
|
||||
uint64_t uploadedPrev;
|
||||
uint64_t corruptCur;
|
||||
uint64_t downloadedCur = 0;
|
||||
uint64_t downloadedPrev = 0;
|
||||
uint64_t uploadedCur = 0;
|
||||
uint64_t uploadedPrev = 0;
|
||||
uint64_t corruptCur = 0;
|
||||
uint64_t corruptPrev;
|
||||
|
||||
uint64_t etaDLSpeedCalculatedAt;
|
||||
unsigned int etaDLSpeed_Bps;
|
||||
uint64_t etaULSpeedCalculatedAt;
|
||||
unsigned int etaULSpeed_Bps;
|
||||
uint64_t etaDLSpeedCalculatedAt = 0;
|
||||
uint64_t etaULSpeedCalculatedAt = 0;
|
||||
unsigned int etaDLSpeed_Bps = 0;
|
||||
unsigned int etaULSpeed_Bps = 0;
|
||||
|
||||
time_t activityDate;
|
||||
time_t addedDate;
|
||||
time_t anyDate;
|
||||
time_t doneDate;
|
||||
time_t editDate;
|
||||
time_t startDate;
|
||||
time_t activityDate = 0;
|
||||
time_t addedDate = 0;
|
||||
time_t anyDate = 0;
|
||||
time_t doneDate = 0;
|
||||
time_t editDate = 0;
|
||||
time_t startDate = 0;
|
||||
|
||||
int secondsDownloading;
|
||||
int secondsSeeding;
|
||||
int secondsDownloading = 0;
|
||||
int secondsSeeding = 0;
|
||||
|
||||
int queuePosition;
|
||||
int queuePosition = 0;
|
||||
|
||||
tr_torrent_metadata_func metadata_func;
|
||||
void* metadata_func_user_data;
|
||||
tr_torrent_metadata_func metadata_func = nullptr;
|
||||
void* metadata_func_user_data = nullptr;
|
||||
|
||||
tr_torrent_completeness_func completeness_func;
|
||||
void* completeness_func_user_data;
|
||||
tr_torrent_completeness_func completeness_func = nullptr;
|
||||
void* completeness_func_user_data = nullptr;
|
||||
|
||||
tr_torrent_ratio_limit_hit_func ratio_limit_hit_func;
|
||||
void* ratio_limit_hit_func_user_data;
|
||||
tr_torrent_ratio_limit_hit_func ratio_limit_hit_func = nullptr;
|
||||
void* ratio_limit_hit_func_user_data = nullptr;
|
||||
|
||||
tr_torrent_idle_limit_hit_func idle_limit_hit_func;
|
||||
void* idle_limit_hit_func_user_data;
|
||||
tr_torrent_idle_limit_hit_func idle_limit_hit_func = nullptr;
|
||||
void* idle_limit_hit_func_user_data = nullptr;
|
||||
|
||||
void* queue_started_user_data;
|
||||
void (*queue_started_callback)(tr_torrent*, void* queue_started_user_data);
|
||||
void* queue_started_user_data = nullptr;
|
||||
void (*queue_started_callback)(tr_torrent*, void* queue_started_user_data) = nullptr;
|
||||
|
||||
bool isRunning;
|
||||
bool isStopping;
|
||||
bool isDeleting;
|
||||
bool startAfterVerify;
|
||||
bool isDirty;
|
||||
bool isDeleting = false;
|
||||
bool isDirty = false;
|
||||
bool isQueued = false;
|
||||
bool isRunning = false;
|
||||
bool isStopping = false;
|
||||
bool startAfterVerify = false;
|
||||
|
||||
bool prefetchMagnetMetadata = false;
|
||||
bool magnetVerify = false;
|
||||
|
||||
// TODO(ckerr) use std::optional
|
||||
bool infoDictOffsetIsCached = false;
|
||||
|
||||
void setDirty()
|
||||
{
|
||||
this->isDirty = true;
|
||||
}
|
||||
|
||||
bool isQueued;
|
||||
uint16_t maxConnectedPeers = TR_DEFAULT_PEER_LIMIT_TORRENT;
|
||||
|
||||
bool prefetchMagnetMetadata;
|
||||
bool magnetVerify;
|
||||
tr_verify_state verifyState = TR_VERIFY_NONE;
|
||||
|
||||
bool infoDictOffsetIsCached;
|
||||
time_t lastStatTime = 0;
|
||||
tr_stat stats = {};
|
||||
|
||||
uint16_t maxConnectedPeers;
|
||||
int uniqueId = 0;
|
||||
|
||||
tr_verify_state verifyState;
|
||||
float desiredRatio = 0.0F;
|
||||
tr_ratiolimit ratioLimitMode = TR_RATIOLIMIT_GLOBAL;
|
||||
|
||||
time_t lastStatTime;
|
||||
tr_stat stats;
|
||||
|
||||
int uniqueId;
|
||||
|
||||
// Changed to non-owning pointer temporarily till tr_torrent becomes C++-constructible and destructible
|
||||
// TODO: change tr_bandwidth* to owning pointer to the bandwidth, or remove * and own the value
|
||||
Bandwidth* bandwidth;
|
||||
|
||||
tr_swarm* swarm;
|
||||
|
||||
float desiredRatio;
|
||||
tr_ratiolimit ratioLimitMode;
|
||||
|
||||
uint16_t idleLimitMinutes;
|
||||
tr_idlelimit idleLimitMode;
|
||||
bool finishedSeedingByIdle;
|
||||
uint16_t idleLimitMinutes = 0;
|
||||
tr_idlelimit idleLimitMode = TR_IDLELIMIT_GLOBAL;
|
||||
bool finishedSeedingByIdle = false;
|
||||
|
||||
tr_labels_t labels;
|
||||
|
||||
|
@ -482,8 +561,6 @@ static inline void tr_torrentMarkEdited(tr_torrent* tor)
|
|||
tor->editDate = tr_time();
|
||||
}
|
||||
|
||||
uint32_t tr_getBlockSize(uint32_t pieceSize);
|
||||
|
||||
/**
|
||||
* Tell the tr_torrent that it's gotten a block
|
||||
*/
|
||||
|
@ -527,51 +604,6 @@ uint64_t tr_torrentGetCurrentSizeOnDisk(tr_torrent const* tor);
|
|||
|
||||
tr_peer_id_t const& tr_torrentGetPeerId(tr_torrent* tor);
|
||||
|
||||
static inline uint64_t tr_torrentGetLeftUntilDone(tr_torrent const* tor)
|
||||
{
|
||||
return tr_cpLeftUntilDone(&tor->completion);
|
||||
}
|
||||
|
||||
static inline bool tr_torrentHasAll(tr_torrent const* tor)
|
||||
{
|
||||
return tr_cpHasAll(&tor->completion);
|
||||
}
|
||||
|
||||
static inline bool tr_torrentHasNone(tr_torrent const* tor)
|
||||
{
|
||||
return tr_cpHasNone(&tor->completion);
|
||||
}
|
||||
|
||||
static inline bool tr_torrentPieceIsComplete(tr_torrent const* tor, tr_piece_index_t i)
|
||||
{
|
||||
return tr_cpPieceIsComplete(&tor->completion, i);
|
||||
}
|
||||
|
||||
static inline bool tr_torrentBlockIsComplete(tr_torrent const* tor, tr_block_index_t i)
|
||||
{
|
||||
return tr_cpBlockIsComplete(&tor->completion, i);
|
||||
}
|
||||
|
||||
static inline size_t tr_torrentMissingBlocksInPiece(tr_torrent const* tor, tr_piece_index_t i)
|
||||
{
|
||||
return tr_cpMissingBlocksInPiece(&tor->completion, i);
|
||||
}
|
||||
|
||||
static inline size_t tr_torrentMissingBytesInPiece(tr_torrent const* tor, tr_piece_index_t i)
|
||||
{
|
||||
return tr_cpMissingBytesInPiece(&tor->completion, i);
|
||||
}
|
||||
|
||||
static inline std::vector<uint8_t> tr_torrentCreatePieceBitfield(tr_torrent const* tor)
|
||||
{
|
||||
return tr_cpCreatePieceBitfield(&tor->completion);
|
||||
}
|
||||
|
||||
constexpr uint64_t tr_torrentHaveTotal(tr_torrent const* tor)
|
||||
{
|
||||
return tr_cpHaveTotal(&tor->completion);
|
||||
}
|
||||
|
||||
constexpr bool tr_torrentIsQueued(tr_torrent const* tor)
|
||||
{
|
||||
return tor->isQueued;
|
||||
|
|
|
@ -35,10 +35,10 @@ using tr_block_index_t = uint32_t;
|
|||
using tr_port = uint16_t;
|
||||
using tr_tracker_tier_t = uint32_t;
|
||||
|
||||
struct tr_block_range_t
|
||||
struct tr_block_span_t
|
||||
{
|
||||
tr_block_index_t first;
|
||||
tr_block_index_t last;
|
||||
tr_block_index_t begin;
|
||||
tr_block_index_t end;
|
||||
};
|
||||
|
||||
struct tr_ctor;
|
||||
|
@ -116,6 +116,7 @@ char const* tr_getDefaultDownloadDir(void);
|
|||
#define TR_DEFAULT_PEER_SOCKET_TOS_STR "default"
|
||||
#define TR_DEFAULT_PEER_LIMIT_GLOBAL_STR "200"
|
||||
#define TR_DEFAULT_PEER_LIMIT_TORRENT_STR "50"
|
||||
#define TR_DEFAULT_PEER_LIMIT_TORRENT 50
|
||||
|
||||
/**
|
||||
* Add libtransmission's default settings to the benc dictionary.
|
||||
|
|
|
@ -39,7 +39,7 @@ static bool verifyTorrent(tr_torrent* tor, bool* stopFlag)
|
|||
uint32_t piecePos = 0;
|
||||
tr_file_index_t fileIndex = 0;
|
||||
tr_file_index_t prevFileIndex = !fileIndex;
|
||||
tr_piece_index_t pieceIndex = 0;
|
||||
tr_piece_index_t piece = 0;
|
||||
time_t const begin = tr_time();
|
||||
size_t const buflen = 1024 * 128; // 128 KiB buffer
|
||||
auto* const buffer = static_cast<uint8_t*>(tr_malloc(buflen));
|
||||
|
@ -49,14 +49,14 @@ static bool verifyTorrent(tr_torrent* tor, bool* stopFlag)
|
|||
tr_logAddTorDbg(tor, "%s", "verifying torrent...");
|
||||
tor->verify_progress = 0;
|
||||
|
||||
while (!*stopFlag && pieceIndex < tor->info.pieceCount)
|
||||
while (!*stopFlag && piece < tor->info.pieceCount)
|
||||
{
|
||||
tr_file const* file = &tor->info.files[fileIndex];
|
||||
|
||||
/* if we're starting a new piece... */
|
||||
if (piecePos == 0)
|
||||
{
|
||||
hadPiece = tr_torrentPieceIsComplete(tor, pieceIndex);
|
||||
hadPiece = tor->hasPiece(piece);
|
||||
}
|
||||
|
||||
/* if we're starting a new file... */
|
||||
|
@ -70,7 +70,7 @@ static bool verifyTorrent(tr_torrent* tor, bool* stopFlag)
|
|||
}
|
||||
|
||||
/* figure out how much we can read this pass */
|
||||
uint64_t leftInPiece = tor->countBytesInPiece(pieceIndex) - piecePos;
|
||||
uint64_t leftInPiece = tor->countBytesInPiece(piece) - piecePos;
|
||||
uint64_t leftInFile = file->length - filePos;
|
||||
uint64_t bytesThisPass = std::min(leftInFile, leftInPiece);
|
||||
bytesThisPass = std::min(bytesThisPass, uint64_t{ buflen });
|
||||
|
@ -97,11 +97,11 @@ static bool verifyTorrent(tr_torrent* tor, bool* stopFlag)
|
|||
if (leftInPiece == 0)
|
||||
{
|
||||
auto hash = tr_sha1_final(sha);
|
||||
auto const hasPiece = hash && *hash == tor->pieceHash(pieceIndex);
|
||||
auto const hasPiece = hash && *hash == tor->pieceHash(piece);
|
||||
|
||||
if (hasPiece || hadPiece)
|
||||
{
|
||||
tr_torrentSetHasPiece(tor, pieceIndex, hasPiece);
|
||||
tor->setHasPiece(piece, hasPiece);
|
||||
changed |= hasPiece != hadPiece;
|
||||
}
|
||||
|
||||
|
@ -117,8 +117,8 @@ static bool verifyTorrent(tr_torrent* tor, bool* stopFlag)
|
|||
}
|
||||
|
||||
sha = tr_sha1_init();
|
||||
++pieceIndex;
|
||||
tor->verify_progress = pieceIndex / double(tor->info.pieceCount);
|
||||
++piece;
|
||||
tor->verify_progress = piece / double(tor->info.pieceCount);
|
||||
piecePos = 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -212,7 +212,7 @@ static void write_block_func(void* vdata)
|
|||
tr_cache* cache = data->session->cache;
|
||||
tr_piece_index_t const piece = data->piece_index;
|
||||
|
||||
if (!tr_torrentPieceIsComplete(tor, piece))
|
||||
if (!tor->hasPiece(piece))
|
||||
{
|
||||
while (len > 0)
|
||||
{
|
||||
|
@ -364,16 +364,16 @@ static void on_idle(tr_webseed* w)
|
|||
{
|
||||
auto n_tasks = size_t{};
|
||||
|
||||
for (auto const range : tr_peerMgrGetNextRequests(tor, w, want))
|
||||
for (auto const span : tr_peerMgrGetNextRequests(tor, w, want))
|
||||
{
|
||||
auto const [first, last] = range;
|
||||
auto const [begin, end] = span;
|
||||
auto* const task = tr_new0(tr_webseed_task, 1);
|
||||
task->session = tor->session;
|
||||
task->webseed = w;
|
||||
task->block = first;
|
||||
task->piece_index = tor->pieceForBlock(first);
|
||||
task->piece_offset = tor->block_size * first - tor->info.pieceSize * task->piece_index;
|
||||
task->length = (last - first) * tor->block_size + tor->countBytesInBlock(last);
|
||||
task->block = begin;
|
||||
task->piece_index = tor->pieceForBlock(begin);
|
||||
task->piece_offset = tor->block_size * begin - tor->info.pieceSize * task->piece_index;
|
||||
task->length = (end - 1 - begin) * tor->block_size + tor->countBytesInBlock(end - 1);
|
||||
task->blocks_done = 0;
|
||||
task->response_code = 0;
|
||||
task->block_size = tor->block_size;
|
||||
|
@ -384,7 +384,7 @@ static void on_idle(tr_webseed* w)
|
|||
|
||||
--w->idle_connections;
|
||||
++n_tasks;
|
||||
tr_peerMgrClientSentRequests(tor, w, range);
|
||||
tr_peerMgrClientSentRequests(tor, w, span);
|
||||
}
|
||||
|
||||
if (w->retry_tickcount >= FAILURE_RETRY_INTERVAL && n_tasks > 0)
|
||||
|
@ -460,7 +460,7 @@ static void web_response_func(
|
|||
}
|
||||
else
|
||||
{
|
||||
if (buf_len != 0 && !tr_torrentPieceIsComplete(tor, t->piece_index))
|
||||
if (buf_len != 0 && !tor->hasPiece(t->piece_index))
|
||||
{
|
||||
/* on_content_changed() will not write a block if it is smaller than
|
||||
the torrent's block size, i.e. the torrent's very last block */
|
||||
|
|
|
@ -3,6 +3,7 @@ add_executable(libtransmission-test
|
|||
block-info-test.cc
|
||||
blocklist-test.cc
|
||||
clients-test.cc
|
||||
completion-test.cc
|
||||
copy-test.cc
|
||||
crypto-test-ref.h
|
||||
crypto-test.cc
|
||||
|
|
|
@ -136,8 +136,8 @@ TEST(Bitfield, bitfields)
|
|||
EXPECT_EQ(field.test(i), (i % 7 == 0));
|
||||
}
|
||||
|
||||
/* test tr_bitfield::setRange */
|
||||
field.setRange(0, bitcount);
|
||||
/* test tr_bitfield::setSpan */
|
||||
field.setSpan(0, bitcount);
|
||||
|
||||
for (unsigned int i = 0; i < bitcount; i++)
|
||||
{
|
||||
|
@ -159,8 +159,8 @@ TEST(Bitfield, bitfields)
|
|||
}
|
||||
|
||||
/* test tr_bitfield::clearBitRange in the middle of a boundary */
|
||||
field.setRange(0, 64);
|
||||
field.unsetRange(4, 21);
|
||||
field.setSpan(0, 64);
|
||||
field.unsetSpan(4, 21);
|
||||
|
||||
for (unsigned int i = 0; i < 64; i++)
|
||||
{
|
||||
|
@ -168,8 +168,8 @@ TEST(Bitfield, bitfields)
|
|||
}
|
||||
|
||||
/* test tr_bitfield::clearBitRange on the boundaries */
|
||||
field.setRange(0, 64);
|
||||
field.unsetRange(8, 24);
|
||||
field.setSpan(0, 64);
|
||||
field.unsetSpan(8, 24);
|
||||
|
||||
for (unsigned int i = 0; i < 64; i++)
|
||||
{
|
||||
|
@ -177,35 +177,35 @@ TEST(Bitfield, bitfields)
|
|||
}
|
||||
|
||||
/* test tr_bitfield::clearBitRange when begin & end is on the same word */
|
||||
field.setRange(0, 64);
|
||||
field.unsetRange(4, 5);
|
||||
field.setSpan(0, 64);
|
||||
field.unsetSpan(4, 5);
|
||||
|
||||
for (unsigned int i = 0; i < 64; i++)
|
||||
{
|
||||
EXPECT_EQ(field.test(i), (i < 4 || i >= 5));
|
||||
}
|
||||
|
||||
/* test tr_bitfield::setRange */
|
||||
field.unsetRange(0, 64);
|
||||
field.setRange(4, 21);
|
||||
/* test tr_bitfield::setSpan */
|
||||
field.unsetSpan(0, 64);
|
||||
field.setSpan(4, 21);
|
||||
|
||||
for (unsigned int i = 0; i < 64; i++)
|
||||
{
|
||||
EXPECT_EQ(field.test(i), (4 <= i && i < 21));
|
||||
}
|
||||
|
||||
/* test tr_bitfield::setRange on the boundaries */
|
||||
field.unsetRange(0, 64);
|
||||
field.setRange(8, 24);
|
||||
/* test tr_bitfield::setSpan on the boundaries */
|
||||
field.unsetSpan(0, 64);
|
||||
field.setSpan(8, 24);
|
||||
|
||||
for (unsigned int i = 0; i < 64; i++)
|
||||
{
|
||||
EXPECT_EQ(field.test(i), (8 <= i && i < 24));
|
||||
}
|
||||
|
||||
/* test tr_bitfield::setRange when begin & end is on the same word */
|
||||
field.unsetRange(0, 64);
|
||||
field.setRange(4, 5);
|
||||
/* test tr_bitfield::setSpan when begin & end is on the same word */
|
||||
field.unsetSpan(0, 64);
|
||||
field.setSpan(4, 5);
|
||||
|
||||
for (unsigned int i = 0; i < 64; i++)
|
||||
{
|
||||
|
|
|
@ -130,7 +130,7 @@ TEST_F(BlockInfoTest, offset)
|
|||
EXPECT_EQ(info.n_blocks - 1, info.blockOf(info.total_size - 1));
|
||||
}
|
||||
|
||||
TEST_F(BlockInfoTest, blockRangeForPiece)
|
||||
TEST_F(BlockInfoTest, blockSpanForPiece)
|
||||
{
|
||||
auto info = tr_block_info{};
|
||||
|
||||
|
@ -141,10 +141,10 @@ TEST_F(BlockInfoTest, blockRangeForPiece)
|
|||
uint64_t constexpr TotalSize = PieceSize * (PieceCount - 1) + 1;
|
||||
info.initSizes(TotalSize, PieceSize);
|
||||
|
||||
EXPECT_EQ(0, info.blockRangeForPiece(0).first);
|
||||
EXPECT_EQ(3, info.blockRangeForPiece(0).last);
|
||||
EXPECT_EQ(12, info.blockRangeForPiece(3).first);
|
||||
EXPECT_EQ(15, info.blockRangeForPiece(3).last);
|
||||
EXPECT_EQ(16, info.blockRangeForPiece(4).first);
|
||||
EXPECT_EQ(16, info.blockRangeForPiece(4).last);
|
||||
EXPECT_EQ(0, info.blockSpanForPiece(0).begin);
|
||||
EXPECT_EQ(4, info.blockSpanForPiece(0).end);
|
||||
EXPECT_EQ(12, info.blockSpanForPiece(3).begin);
|
||||
EXPECT_EQ(16, info.blockSpanForPiece(3).end);
|
||||
EXPECT_EQ(16, info.blockSpanForPiece(4).begin);
|
||||
EXPECT_EQ(17, info.blockSpanForPiece(4).end);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,480 @@
|
|||
/*
|
||||
* This file Copyright (C) Mnemosyne LLC
|
||||
*
|
||||
* It may be used under the GNU GPL versions 2 or 3
|
||||
* or any future license endorsed by Mnemosyne LLC.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "block-info.h"
|
||||
#include "completion.h"
|
||||
#include "crypto-utils.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
using CompletionTest = ::testing::Test;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct TestTorrent : public tr_completion::torrent_view
|
||||
{
|
||||
std::set<tr_piece_index_t> dnd_pieces;
|
||||
|
||||
[[nodiscard]] bool pieceIsDnd(tr_piece_index_t piece) const final
|
||||
{
|
||||
return dnd_pieces.count(piece) != 0;
|
||||
}
|
||||
};
|
||||
|
||||
auto constexpr BlockSize = uint64_t{ 16 * 1024 };
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_F(CompletionTest, MagnetLink)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto block_info = tr_block_info{};
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
|
||||
EXPECT_FALSE(completion.hasAll());
|
||||
EXPECT_TRUE(completion.hasNone());
|
||||
EXPECT_FALSE(completion.hasBlocks({ 0, 1 }));
|
||||
EXPECT_FALSE(completion.hasBlocks({ 0, 1000 }));
|
||||
EXPECT_FALSE(completion.hasPiece(0));
|
||||
EXPECT_FALSE(completion.isDone());
|
||||
EXPECT_DOUBLE_EQ(0.0, completion.percentDone());
|
||||
EXPECT_DOUBLE_EQ(0.0, completion.percentComplete());
|
||||
EXPECT_EQ(TR_LEECH, completion.status());
|
||||
EXPECT_EQ(0, completion.hasTotal());
|
||||
EXPECT_EQ(0, completion.hasValid());
|
||||
EXPECT_EQ(0, completion.leftUntilDone());
|
||||
EXPECT_EQ(0, completion.sizeWhenDone());
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, setBlocks)
|
||||
{
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 };
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
|
||||
auto torrent = TestTorrent{};
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
EXPECT_FALSE(completion.blocks().hasAll());
|
||||
EXPECT_FALSE(completion.hasAll());
|
||||
EXPECT_EQ(0, completion.hasTotal());
|
||||
|
||||
auto bitfield = tr_bitfield{ block_info.n_blocks };
|
||||
bitfield.setHasAll();
|
||||
|
||||
// test that the bitfield did get replaced
|
||||
completion.setBlocks(bitfield);
|
||||
EXPECT_TRUE(completion.blocks().hasAll());
|
||||
EXPECT_TRUE(completion.hasAll());
|
||||
EXPECT_EQ(block_info.total_size, completion.hasTotal());
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, hasBlock)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 };
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
|
||||
EXPECT_FALSE(completion.hasBlock(0));
|
||||
EXPECT_FALSE(completion.hasBlock(1));
|
||||
|
||||
completion.addBlock(0);
|
||||
EXPECT_TRUE(completion.hasBlock(0));
|
||||
EXPECT_FALSE(completion.hasBlock(1));
|
||||
|
||||
completion.addPiece(0);
|
||||
EXPECT_TRUE(completion.hasBlock(0));
|
||||
EXPECT_TRUE(completion.hasBlock(1));
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, hasBlocks)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 };
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
EXPECT_FALSE(completion.hasBlocks({ 0, 1 }));
|
||||
EXPECT_FALSE(completion.hasBlocks({ 0, 2 }));
|
||||
|
||||
completion.addBlock(0);
|
||||
EXPECT_TRUE(completion.hasBlocks({ 0, 1 }));
|
||||
EXPECT_FALSE(completion.hasBlocks({ 0, 2 }));
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, hasNone)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 };
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
EXPECT_TRUE(completion.hasNone());
|
||||
|
||||
completion.addBlock(0);
|
||||
EXPECT_FALSE(completion.hasNone());
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, hasPiece)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 };
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
|
||||
// check that the initial state does not have it
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
EXPECT_FALSE(completion.hasPiece(0));
|
||||
EXPECT_FALSE(completion.hasPiece(1));
|
||||
EXPECT_EQ(0, completion.hasValid());
|
||||
|
||||
// check that adding a piece means we have it
|
||||
completion.addPiece(0);
|
||||
EXPECT_TRUE(completion.hasPiece(0));
|
||||
EXPECT_FALSE(completion.hasPiece(1));
|
||||
EXPECT_EQ(PieceSize, completion.hasValid());
|
||||
|
||||
// check that removing a piece means we don't have it
|
||||
completion.removePiece(0);
|
||||
EXPECT_FALSE(completion.hasPiece(0));
|
||||
EXPECT_FALSE(completion.hasPiece(1));
|
||||
EXPECT_EQ(0, completion.hasValid());
|
||||
|
||||
// check that adding all the blocks in a piece means we have it
|
||||
for (size_t i = 1; i < block_info.n_blocks_in_piece; ++i)
|
||||
{
|
||||
completion.addBlock(i);
|
||||
}
|
||||
EXPECT_FALSE(completion.hasPiece(0));
|
||||
EXPECT_EQ(0, completion.hasValid());
|
||||
completion.addBlock(0);
|
||||
EXPECT_TRUE(completion.hasPiece(0));
|
||||
EXPECT_EQ(PieceSize, completion.hasValid());
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, isDone)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 };
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
|
||||
// check that in blank-slate initial state, isDone() is false
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
EXPECT_FALSE(completion.isDone());
|
||||
EXPECT_EQ(TR_LEECH, completion.status());
|
||||
EXPECT_EQ(block_info.total_size, completion.leftUntilDone());
|
||||
|
||||
// check that we're done if we have all the blocks
|
||||
auto left = block_info.total_size;
|
||||
for (size_t i = 1; i < block_info.n_blocks; ++i)
|
||||
{
|
||||
completion.addBlock(i);
|
||||
left -= block_info.block_size;
|
||||
EXPECT_EQ(left, completion.leftUntilDone());
|
||||
}
|
||||
EXPECT_FALSE(completion.isDone());
|
||||
completion.addBlock(0);
|
||||
EXPECT_EQ(0, completion.leftUntilDone());
|
||||
EXPECT_TRUE(completion.isDone());
|
||||
EXPECT_EQ(TR_SEED, completion.status());
|
||||
|
||||
// check that not having all the pieces (and we want all) means we're not done
|
||||
completion.removePiece(0);
|
||||
EXPECT_FALSE(completion.isDone());
|
||||
EXPECT_EQ(TR_LEECH, completion.status());
|
||||
|
||||
// check that having all the pieces we want, even if it's not ALL pieces, means we're done
|
||||
torrent.dnd_pieces.insert(0);
|
||||
completion.invalidateSizeWhenDone();
|
||||
EXPECT_TRUE(completion.isDone());
|
||||
EXPECT_EQ(TR_PARTIAL_SEED, completion.status());
|
||||
|
||||
// but if we decide we do want that missing piece after all, then we're not done
|
||||
torrent.dnd_pieces.erase(0);
|
||||
completion.invalidateSizeWhenDone();
|
||||
EXPECT_FALSE(completion.isDone());
|
||||
EXPECT_EQ(TR_LEECH, completion.status());
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, percentCompleteAndDone)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 };
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
|
||||
// check that in blank-slate initial state, isDone() is false
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
EXPECT_DOUBLE_EQ(0.0, completion.percentComplete());
|
||||
EXPECT_DOUBLE_EQ(0.0, completion.percentDone());
|
||||
|
||||
// add half the pieces
|
||||
for (size_t i = 0; i < 32; ++i)
|
||||
{
|
||||
completion.addPiece(i);
|
||||
}
|
||||
EXPECT_DOUBLE_EQ(0.5, completion.percentComplete());
|
||||
EXPECT_DOUBLE_EQ(0.5, completion.percentDone());
|
||||
|
||||
// but marking some of the pieces we have as unwanted
|
||||
// should not change percentDone
|
||||
for (size_t i = 0; i < 16; ++i)
|
||||
{
|
||||
torrent.dnd_pieces.insert(i);
|
||||
}
|
||||
completion.invalidateSizeWhenDone();
|
||||
EXPECT_DOUBLE_EQ(0.5, completion.percentComplete());
|
||||
EXPECT_DOUBLE_EQ(0.5, completion.percentDone());
|
||||
|
||||
// but marking some of the pieces we DON'T have as unwanted
|
||||
// SHOULD change percentDone
|
||||
for (size_t i = 32; i < 48; ++i)
|
||||
{
|
||||
torrent.dnd_pieces.insert(i);
|
||||
}
|
||||
completion.invalidateSizeWhenDone();
|
||||
EXPECT_DOUBLE_EQ(0.5, completion.percentComplete());
|
||||
EXPECT_DOUBLE_EQ(2.0 / 3.0, completion.percentDone());
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, hasTotalAndValid)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1;
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
|
||||
// check that the initial blank-slate state has nothing
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
EXPECT_EQ(0, completion.hasTotal());
|
||||
EXPECT_EQ(completion.hasValid(), completion.hasTotal());
|
||||
|
||||
// check that adding the final piece adjusts by block_info.final_piece_size
|
||||
completion.setHasPiece(block_info.n_pieces - 1, true);
|
||||
EXPECT_EQ(block_info.final_piece_size, completion.hasTotal());
|
||||
EXPECT_EQ(completion.hasValid(), completion.hasTotal());
|
||||
|
||||
// check that adding a non-final piece adjusts by block_info.piece_size
|
||||
completion.setHasPiece(0, true);
|
||||
EXPECT_EQ(block_info.final_piece_size + block_info.piece_size, completion.hasTotal());
|
||||
EXPECT_EQ(completion.hasValid(), completion.hasTotal());
|
||||
|
||||
// check that removing the final piece adjusts by block_info.final_piece_size
|
||||
completion.setHasPiece(block_info.n_pieces - 1, false);
|
||||
EXPECT_EQ(block_info.piece_size, completion.hasValid());
|
||||
EXPECT_EQ(completion.hasValid(), completion.hasTotal());
|
||||
|
||||
// check that removing a non-final piece adjusts by block_info.piece_size
|
||||
completion.setHasPiece(0, false);
|
||||
EXPECT_EQ(0, completion.hasValid());
|
||||
EXPECT_EQ(completion.hasValid(), completion.hasTotal());
|
||||
|
||||
// check that adding an incomplete piece adjusts hasTotal but not hasValid
|
||||
completion.addBlock(0);
|
||||
EXPECT_EQ(0, completion.hasValid());
|
||||
EXPECT_EQ(BlockSize, completion.hasTotal());
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, leftUntilDone)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1;
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
|
||||
// check that the initial blank-slate state has nothing
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
EXPECT_EQ(block_info.total_size, completion.leftUntilDone());
|
||||
|
||||
// check that adding the final piece adjusts by block_info.final_piece_size
|
||||
completion.addPiece(block_info.n_pieces - 1);
|
||||
EXPECT_EQ(block_info.total_size - block_info.final_piece_size, completion.leftUntilDone());
|
||||
|
||||
// check that adding a non-final piece adjusts by block_info.piece_size
|
||||
completion.addPiece(0);
|
||||
EXPECT_EQ(block_info.total_size - block_info.final_piece_size - block_info.piece_size, completion.leftUntilDone());
|
||||
|
||||
// check that removing the final piece adjusts by block_info.final_piece_size
|
||||
completion.removePiece(block_info.n_pieces - 1);
|
||||
EXPECT_EQ(block_info.total_size - block_info.piece_size, completion.leftUntilDone());
|
||||
|
||||
// check that dnd-flagging a piece we already have affects nothing
|
||||
torrent.dnd_pieces.insert(0);
|
||||
completion.invalidateSizeWhenDone();
|
||||
EXPECT_EQ(block_info.total_size - block_info.piece_size, completion.leftUntilDone());
|
||||
torrent.dnd_pieces.clear();
|
||||
completion.invalidateSizeWhenDone();
|
||||
|
||||
// check that dnd-flagging a piece we DON'T already have adjusts by block_info.piece_size
|
||||
torrent.dnd_pieces.insert(1);
|
||||
completion.invalidateSizeWhenDone();
|
||||
EXPECT_EQ(block_info.total_size - block_info.piece_size * 2, completion.leftUntilDone());
|
||||
torrent.dnd_pieces.clear();
|
||||
completion.invalidateSizeWhenDone();
|
||||
|
||||
// check that removing a non-final piece adjusts by block_info.piece_size
|
||||
completion.removePiece(0);
|
||||
EXPECT_EQ(block_info.total_size, completion.leftUntilDone());
|
||||
|
||||
// check that adding a block adjusts by block_info.block_size
|
||||
completion.addBlock(0);
|
||||
EXPECT_EQ(block_info.total_size - block_info.block_size, completion.leftUntilDone());
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, sizeWhenDone)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1;
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
|
||||
// check that adding or removing blocks or pieces does not affect sizeWhenDone
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
EXPECT_EQ(block_info.total_size, completion.sizeWhenDone());
|
||||
completion.addBlock(0);
|
||||
EXPECT_EQ(block_info.total_size, completion.sizeWhenDone());
|
||||
completion.addPiece(0);
|
||||
EXPECT_EQ(block_info.total_size, completion.sizeWhenDone());
|
||||
completion.removePiece(0);
|
||||
EXPECT_EQ(block_info.total_size, completion.sizeWhenDone());
|
||||
|
||||
// check that flagging complete pieces as dnd does not affect sizeWhenDone
|
||||
for (size_t i = 0; i < 32; ++i)
|
||||
{
|
||||
completion.addPiece(i);
|
||||
torrent.dnd_pieces.insert(i);
|
||||
}
|
||||
completion.invalidateSizeWhenDone();
|
||||
EXPECT_EQ(block_info.total_size, completion.sizeWhenDone());
|
||||
|
||||
// check that flagging missing pieces as dnd does not affect sizeWhenDone
|
||||
for (size_t i = 32; i < 48; ++i)
|
||||
{
|
||||
torrent.dnd_pieces.insert(i);
|
||||
}
|
||||
completion.invalidateSizeWhenDone();
|
||||
EXPECT_EQ(block_info.total_size - 16 * block_info.piece_size, completion.sizeWhenDone());
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, createPieceBitfield)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1;
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
|
||||
// make a completion object that has a random assortment of pieces
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
auto buf = std::array<char, 64>{};
|
||||
EXPECT_TRUE(tr_rand_buffer(std::data(buf), std::size(buf)));
|
||||
for (uint64_t i = 0; i < block_info.n_pieces; ++i)
|
||||
{
|
||||
if (buf[i] % 2)
|
||||
{
|
||||
completion.addPiece(i);
|
||||
}
|
||||
}
|
||||
|
||||
// serialize it to a raw bitfield, read it back into a bitfield,
|
||||
// and test that the new bitfield matches
|
||||
auto const pieces_raw_bitfield = completion.createPieceBitfield();
|
||||
tr_bitfield pieces{ size_t(block_info.n_pieces) };
|
||||
pieces.setRaw(std::data(pieces_raw_bitfield), std::size(pieces_raw_bitfield));
|
||||
for (uint64_t i = 0; i < block_info.n_pieces; ++i)
|
||||
{
|
||||
EXPECT_EQ(completion.hasPiece(i), pieces.test(i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, setHasPiece)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, countMissingBytesInPiece)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1;
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
|
||||
EXPECT_EQ(block_info.countBytesInPiece(0), completion.countMissingBytesInPiece(0));
|
||||
completion.addBlock(0);
|
||||
EXPECT_EQ(block_info.countBytesInPiece(0) - block_info.block_size, completion.countMissingBytesInPiece(0));
|
||||
completion.addPiece(0);
|
||||
EXPECT_EQ(0, completion.countMissingBytesInPiece(0));
|
||||
|
||||
auto const final_piece = block_info.n_pieces - 1;
|
||||
auto const final_block = block_info.n_blocks - 1;
|
||||
EXPECT_EQ(block_info.countBytesInPiece(final_piece), completion.countMissingBytesInPiece(final_piece));
|
||||
completion.addBlock(final_block);
|
||||
EXPECT_EQ(1, block_info.final_piece_size);
|
||||
EXPECT_EQ(1, block_info.final_block_size);
|
||||
EXPECT_EQ(1, block_info.n_blocks_in_final_piece);
|
||||
EXPECT_TRUE(completion.hasPiece(final_piece));
|
||||
EXPECT_EQ(0, completion.countMissingBytesInPiece(final_piece));
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, amountDone)
|
||||
{
|
||||
auto torrent = TestTorrent{};
|
||||
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1;
|
||||
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
|
||||
auto const block_info = tr_block_info{ TotalSize, PieceSize };
|
||||
auto completion = tr_completion(&torrent, &block_info);
|
||||
|
||||
// make bins s.t. each bin is a single piece
|
||||
auto bins = std::array<float, TotalSize / PieceSize>{};
|
||||
|
||||
for (tr_piece_index_t piece = 0; piece < block_info.n_pieces; ++piece)
|
||||
{
|
||||
completion.removePiece(piece);
|
||||
}
|
||||
completion.amountDone(std::data(bins), std::size(bins));
|
||||
std::for_each(std::begin(bins), std::end(bins), [](float bin) { EXPECT_DOUBLE_EQ(0.0, bin); });
|
||||
|
||||
// one block
|
||||
completion.addBlock(0);
|
||||
completion.amountDone(std::data(bins), std::size(bins));
|
||||
EXPECT_DOUBLE_EQ(1.0 / block_info.n_blocks_in_piece, bins[0]);
|
||||
EXPECT_DOUBLE_EQ(0.0, bins[1]);
|
||||
|
||||
// one piece
|
||||
completion.addPiece(0);
|
||||
completion.amountDone(std::data(bins), std::size(bins));
|
||||
EXPECT_DOUBLE_EQ(1.0, bins[0]);
|
||||
EXPECT_DOUBLE_EQ(0.0, bins[1]);
|
||||
|
||||
// all pieces
|
||||
for (tr_piece_index_t piece = 0; piece < block_info.n_pieces; ++piece)
|
||||
{
|
||||
completion.addPiece(piece);
|
||||
}
|
||||
completion.amountDone(std::data(bins), std::size(bins));
|
||||
std::for_each(std::begin(bins), std::end(bins), [](float bin) { EXPECT_DOUBLE_EQ(1.0, bin); });
|
||||
|
||||
// don't do anything if fed bad input
|
||||
auto const backup = bins;
|
||||
completion.amountDone(std::data(bins), 0);
|
||||
EXPECT_EQ(backup, bins);
|
||||
}
|
||||
|
||||
TEST_F(CompletionTest, status)
|
||||
{
|
||||
}
|
|
@ -94,9 +94,9 @@ TEST_P(IncompleteDirTest, incompleteDir)
|
|||
data.tor = tor;
|
||||
data.buf = evbuffer_new();
|
||||
|
||||
auto const [first, last] = tor->blockRangeForPiece(data.pieceIndex);
|
||||
auto const [begin, end] = tor->blockSpanForPiece(data.pieceIndex);
|
||||
|
||||
for (tr_block_index_t block_index = first; block_index <= last; ++block_index)
|
||||
for (tr_block_index_t block_index = begin; block_index < end; ++block_index)
|
||||
{
|
||||
evbuffer_add(data.buf, zero_block, tor->block_size);
|
||||
data.block = block_index;
|
||||
|
|
|
@ -24,7 +24,7 @@ protected:
|
|||
{
|
||||
mutable std::map<tr_block_index_t, size_t> active_request_count_;
|
||||
mutable std::map<tr_piece_index_t, size_t> missing_block_count_;
|
||||
mutable std::map<tr_piece_index_t, tr_block_range_t> block_range_;
|
||||
mutable std::map<tr_piece_index_t, tr_block_span_t> block_span_;
|
||||
mutable std::map<tr_piece_index_t, tr_priority_t> piece_priority_;
|
||||
mutable std::set<tr_block_index_t> can_request_block_;
|
||||
mutable std::set<tr_piece_index_t> can_request_piece_;
|
||||
|
@ -56,9 +56,9 @@ protected:
|
|||
return missing_block_count_[piece];
|
||||
}
|
||||
|
||||
[[nodiscard]] tr_block_range_t blockRange(tr_piece_index_t piece) const final
|
||||
[[nodiscard]] tr_block_span_t blockSpan(tr_piece_index_t piece) const final
|
||||
{
|
||||
return block_range_[piece];
|
||||
return block_span_[piece];
|
||||
}
|
||||
|
||||
[[nodiscard]] tr_piece_index_t countAllPieces() const final
|
||||
|
@ -83,22 +83,22 @@ TEST_F(PeerMgrWishlistTest, doesNotRequestPiecesThatCannotBeRequested)
|
|||
peer_info.missing_block_count_[0] = 100;
|
||||
peer_info.missing_block_count_[1] = 100;
|
||||
peer_info.missing_block_count_[2] = 50;
|
||||
peer_info.block_range_[0] = { 0, 99 };
|
||||
peer_info.block_range_[1] = { 100, 199 };
|
||||
peer_info.block_range_[2] = { 200, 250 };
|
||||
peer_info.block_span_[0] = { 0, 100 };
|
||||
peer_info.block_span_[1] = { 100, 200 };
|
||||
peer_info.block_span_[2] = { 200, 251 };
|
||||
|
||||
// but we only want the first piece
|
||||
peer_info.can_request_piece_.insert(0);
|
||||
for (tr_block_index_t i = peer_info.block_range_[0].first; i <= peer_info.block_range_[0].last; ++i)
|
||||
for (tr_block_index_t i = peer_info.block_span_[0].begin; i < peer_info.block_span_[0].end; ++i)
|
||||
{
|
||||
peer_info.can_request_block_.insert(i);
|
||||
}
|
||||
|
||||
// we should only get the first piece back
|
||||
auto ranges = wishlist.next(peer_info, 1000);
|
||||
ASSERT_EQ(1, std::size(ranges));
|
||||
EXPECT_EQ(peer_info.block_range_[0].first, ranges[0].first);
|
||||
EXPECT_EQ(peer_info.block_range_[0].last, ranges[0].last);
|
||||
auto spans = wishlist.next(peer_info, 1000);
|
||||
ASSERT_EQ(1, std::size(spans));
|
||||
EXPECT_EQ(peer_info.block_span_[0].begin, spans[0].begin);
|
||||
EXPECT_EQ(peer_info.block_span_[0].end, spans[0].end);
|
||||
}
|
||||
|
||||
TEST_F(PeerMgrWishlistTest, doesNotRequestBlocksThatCannotBeRequested)
|
||||
|
@ -111,9 +111,9 @@ TEST_F(PeerMgrWishlistTest, doesNotRequestBlocksThatCannotBeRequested)
|
|||
peer_info.missing_block_count_[0] = 100;
|
||||
peer_info.missing_block_count_[1] = 100;
|
||||
peer_info.missing_block_count_[2] = 50;
|
||||
peer_info.block_range_[0] = { 0, 99 };
|
||||
peer_info.block_range_[1] = { 100, 199 };
|
||||
peer_info.block_range_[2] = { 200, 249 };
|
||||
peer_info.block_span_[0] = { 0, 100 };
|
||||
peer_info.block_span_[1] = { 100, 200 };
|
||||
peer_info.block_span_[2] = { 200, 251 };
|
||||
|
||||
// and we want all three pieces
|
||||
peer_info.can_request_piece_.insert(0);
|
||||
|
@ -129,11 +129,11 @@ TEST_F(PeerMgrWishlistTest, doesNotRequestBlocksThatCannotBeRequested)
|
|||
|
||||
// even if we ask wishlist for more blocks than exist,
|
||||
// it should omit blocks 1-10 from the return set
|
||||
auto ranges = wishlist.next(peer_info, 1000);
|
||||
auto spans = wishlist.next(peer_info, 1000);
|
||||
auto requested = tr_bitfield(250);
|
||||
for (auto const& range : ranges)
|
||||
for (auto const& span : spans)
|
||||
{
|
||||
requested.setRange(range.first, range.last + 1);
|
||||
requested.setSpan(span.begin, span.end);
|
||||
}
|
||||
EXPECT_EQ(240, requested.count());
|
||||
EXPECT_EQ(0, requested.count(0, 10));
|
||||
|
@ -150,9 +150,9 @@ TEST_F(PeerMgrWishlistTest, doesNotRequestTooManyBlocks)
|
|||
peer_info.missing_block_count_[0] = 100;
|
||||
peer_info.missing_block_count_[1] = 100;
|
||||
peer_info.missing_block_count_[2] = 50;
|
||||
peer_info.block_range_[0] = { 0, 99 };
|
||||
peer_info.block_range_[1] = { 100, 199 };
|
||||
peer_info.block_range_[2] = { 200, 249 };
|
||||
peer_info.block_span_[0] = { 0, 100 };
|
||||
peer_info.block_span_[1] = { 100, 200 };
|
||||
peer_info.block_span_[2] = { 200, 251 };
|
||||
|
||||
// and we want everything
|
||||
for (tr_piece_index_t i = 0; i < 3; ++i)
|
||||
|
@ -167,11 +167,11 @@ TEST_F(PeerMgrWishlistTest, doesNotRequestTooManyBlocks)
|
|||
// but we only ask for 10 blocks,
|
||||
// so that's how many we should get back
|
||||
auto const n_wanted = 10;
|
||||
auto ranges = wishlist.next(peer_info, n_wanted);
|
||||
auto const spans = wishlist.next(peer_info, n_wanted);
|
||||
auto n_got = size_t{};
|
||||
for (auto const& range : ranges)
|
||||
for (auto const& span : spans)
|
||||
{
|
||||
n_got += range.last + 1 - range.first;
|
||||
n_got += span.end - span.begin;
|
||||
}
|
||||
EXPECT_EQ(n_wanted, n_got);
|
||||
}
|
||||
|
@ -186,9 +186,9 @@ TEST_F(PeerMgrWishlistTest, prefersHighPriorityPieces)
|
|||
peer_info.missing_block_count_[0] = 100;
|
||||
peer_info.missing_block_count_[1] = 100;
|
||||
peer_info.missing_block_count_[2] = 100;
|
||||
peer_info.block_range_[0] = { 0, 99 };
|
||||
peer_info.block_range_[1] = { 100, 199 };
|
||||
peer_info.block_range_[2] = { 200, 299 };
|
||||
peer_info.block_span_[0] = { 0, 100 };
|
||||
peer_info.block_span_[1] = { 100, 200 };
|
||||
peer_info.block_span_[2] = { 200, 300 };
|
||||
|
||||
// and we want everything
|
||||
for (tr_piece_index_t i = 0; i < 3; ++i)
|
||||
|
@ -212,16 +212,16 @@ TEST_F(PeerMgrWishlistTest, prefersHighPriorityPieces)
|
|||
for (int run = 0; run < num_runs; ++run)
|
||||
{
|
||||
auto const n_wanted = 10;
|
||||
auto ranges = wishlist.next(peer_info, n_wanted);
|
||||
auto spans = wishlist.next(peer_info, n_wanted);
|
||||
auto n_got = size_t{};
|
||||
for (auto const& range : ranges)
|
||||
for (auto const& span : spans)
|
||||
{
|
||||
for (auto block = range.first; block <= range.last; ++block)
|
||||
for (auto block = span.begin; block < span.end; ++block)
|
||||
{
|
||||
EXPECT_LE(peer_info.block_range_[1].first, block);
|
||||
EXPECT_LE(block, peer_info.block_range_[1].last);
|
||||
EXPECT_LE(peer_info.block_span_[1].begin, block);
|
||||
EXPECT_LT(block, peer_info.block_span_[1].end);
|
||||
}
|
||||
n_got += range.last + 1 - range.first;
|
||||
n_got += span.end - span.begin;
|
||||
}
|
||||
EXPECT_EQ(n_wanted, n_got);
|
||||
}
|
||||
|
@ -237,9 +237,9 @@ TEST_F(PeerMgrWishlistTest, onlyRequestsDupesDuringEndgame)
|
|||
peer_info.missing_block_count_[0] = 100;
|
||||
peer_info.missing_block_count_[1] = 100;
|
||||
peer_info.missing_block_count_[2] = 100;
|
||||
peer_info.block_range_[0] = { 0, 99 };
|
||||
peer_info.block_range_[1] = { 100, 199 };
|
||||
peer_info.block_range_[2] = { 200, 299 };
|
||||
peer_info.block_span_[0] = { 0, 100 };
|
||||
peer_info.block_span_[1] = { 100, 200 };
|
||||
peer_info.block_span_[2] = { 200, 300 };
|
||||
|
||||
// and we want everything
|
||||
for (tr_piece_index_t i = 0; i < 3; ++i)
|
||||
|
@ -259,11 +259,11 @@ TEST_F(PeerMgrWishlistTest, onlyRequestsDupesDuringEndgame)
|
|||
|
||||
// even if we ask wishlist to list more blocks than exist,
|
||||
// those first 150 should be omitted from the return list
|
||||
auto ranges = wishlist.next(peer_info, 1000);
|
||||
auto spans = wishlist.next(peer_info, 1000);
|
||||
auto requested = tr_bitfield(300);
|
||||
for (auto const& range : ranges)
|
||||
for (auto const& span : spans)
|
||||
{
|
||||
requested.setRange(range.first, range.last + 1);
|
||||
requested.setSpan(span.begin, span.end);
|
||||
}
|
||||
EXPECT_EQ(150, requested.count());
|
||||
EXPECT_EQ(0, requested.count(0, 150));
|
||||
|
@ -272,11 +272,11 @@ TEST_F(PeerMgrWishlistTest, onlyRequestsDupesDuringEndgame)
|
|||
// BUT during endgame it's OK to request dupes,
|
||||
// so then we _should_ see the first 150 in the list
|
||||
peer_info.is_endgame_ = true;
|
||||
ranges = wishlist.next(peer_info, 1000);
|
||||
spans = wishlist.next(peer_info, 1000);
|
||||
requested = tr_bitfield(300);
|
||||
for (auto const& range : ranges)
|
||||
for (auto const& span : spans)
|
||||
{
|
||||
requested.setRange(range.first, range.last + 1);
|
||||
requested.setSpan(span.begin, span.end);
|
||||
}
|
||||
EXPECT_EQ(300, requested.count());
|
||||
EXPECT_EQ(150, requested.count(0, 150));
|
||||
|
@ -290,9 +290,9 @@ TEST_F(PeerMgrWishlistTest, prefersNearlyCompletePieces)
|
|||
|
||||
// setup: three pieces, same size
|
||||
peer_info.piece_count_ = 3;
|
||||
peer_info.block_range_[0] = { 0, 99 };
|
||||
peer_info.block_range_[1] = { 100, 199 };
|
||||
peer_info.block_range_[2] = { 200, 299 };
|
||||
peer_info.block_span_[0] = { 0, 100 };
|
||||
peer_info.block_span_[1] = { 100, 200 };
|
||||
peer_info.block_span_[2] = { 200, 300 };
|
||||
|
||||
// and we want everything
|
||||
for (tr_piece_index_t i = 0; i < 3; ++i)
|
||||
|
@ -306,12 +306,12 @@ TEST_F(PeerMgrWishlistTest, prefersNearlyCompletePieces)
|
|||
peer_info.missing_block_count_[2] = 100;
|
||||
for (tr_piece_index_t piece = 0; piece < 3; ++piece)
|
||||
{
|
||||
auto const& range = peer_info.block_range_[piece];
|
||||
auto const& span = peer_info.block_span_[piece];
|
||||
auto const& n_missing = peer_info.missing_block_count_[piece];
|
||||
|
||||
for (size_t i = 0; i < n_missing; ++i)
|
||||
{
|
||||
peer_info.can_request_block_.insert(range.first + i);
|
||||
peer_info.can_request_block_.insert(span.begin + i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,7 +327,7 @@ TEST_F(PeerMgrWishlistTest, prefersNearlyCompletePieces)
|
|||
auto requested = tr_bitfield(300);
|
||||
for (auto const& range : ranges)
|
||||
{
|
||||
requested.setRange(range.first, range.last + 1);
|
||||
requested.setSpan(range.begin, range.end);
|
||||
}
|
||||
EXPECT_EQ(10, requested.count());
|
||||
EXPECT_EQ(10, requested.count(0, 100));
|
||||
|
@ -343,7 +343,7 @@ TEST_F(PeerMgrWishlistTest, prefersNearlyCompletePieces)
|
|||
auto requested = tr_bitfield(300);
|
||||
for (auto const& range : ranges)
|
||||
{
|
||||
requested.setRange(range.first, range.last + 1);
|
||||
requested.setSpan(range.begin, range.end);
|
||||
}
|
||||
EXPECT_EQ(20, requested.count());
|
||||
EXPECT_EQ(10, requested.count(0, 100));
|
||||
|
|
Loading…
Reference in New Issue