refactor: tr_completion (#2220)

* refactor: refactor tr_completion + add test coverage for it
This commit is contained in:
Charles Kerr 2021-11-25 12:26:51 -06:00 committed by GitHub
parent 06090f113a
commit de169c7ec3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1178 additions and 810 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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) */

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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;

View File

@ -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))
{

View File

@ -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;

View File

@ -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.

View File

@ -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;
}

View File

@ -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 */

View File

@ -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

View File

@ -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++)
{

View File

@ -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);
}

View File

@ -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)
{
}

View File

@ -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;

View File

@ -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));