diff --git a/libtransmission/announcer.cc b/libtransmission/announcer.cc index d8a3db7e2..1d9a910e9 100644 --- a/libtransmission/announcer.cc +++ b/libtransmission/announcer.cc @@ -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; diff --git a/libtransmission/bitfield.cc b/libtransmission/bitfield.cc index 9045e6f9c..08e0e6a37 100644 --- a/libtransmission/bitfield.cc +++ b/libtransmission/bitfield.cc @@ -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); diff --git a/libtransmission/bitfield.h b/libtransmission/bitfield.h index 6d0a9cb76..bd43e6745 100644 --- a/libtransmission/bitfield.h +++ b/libtransmission/bitfield.h @@ -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 diff --git a/libtransmission/block-info.h b/libtransmission/block-info.h index 60a82fadc..13ec20ba4 100644 --- a/libtransmission/block-info.h +++ b/libtransmission/block-info.h @@ -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); diff --git a/libtransmission/cache.cc b/libtransmission/cache.cc index 438608613..df646ce4a 100644 --- a/libtransmission/cache.cc +++ b/libtransmission/cache.cc @@ -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; } diff --git a/libtransmission/completion.cc b/libtransmission/completion.cc index 51d76bca0..64ab888db 100644 --- a/libtransmission/completion.cc +++ b/libtransmission/completion.cc @@ -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 #include #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 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(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(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 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; } diff --git a/libtransmission/completion.h b/libtransmission/completion.h index cdcc1e1c7..33eaad175 100644 --- a/libtransmission/completion.h +++ b/libtransmission/completion.h @@ -12,122 +12,147 @@ #error only libtransmission should #include this header. #endif +#include +#include +#include #include #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 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 size_when_done_; + + // Number of verified bytes we have right now. [0..info.totalSize] + // Mutable because lazy-calculated + mutable std::optional 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 tr_cpCreatePieceBitfield(tr_completion const* cp); - -constexpr void tr_cpInvalidateDND(tr_completion* cp) -{ - cp->sizeWhenDoneIsDirty = true; -} diff --git a/libtransmission/peer-mgr-wishlist.cc b/libtransmission/peer-mgr-wishlist.cc index c3932f19f..bfbae912b 100644 --- a/libtransmission/peer-mgr-wishlist.cc +++ b/libtransmission/peer-mgr-wishlist.cc @@ -102,38 +102,38 @@ std::vector getCandidates(Wishlist::PeerInfo const& peer_info) return candidates; } -static std::vector makeRanges(tr_block_index_t const* sorted_blocks, size_t n_blocks) +static std::vector makeSpans(tr_block_index_t const* sorted_blocks, size_t n_blocks) { if (n_blocks == 0) { return {}; } - auto ranges = std::vector{}; - auto cur = tr_block_range_t{ sorted_blocks[0], sorted_blocks[0] }; + auto spans = std::vector{}; + 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 Wishlist::next(Wishlist::PeerInfo const& peer_info, size_t n_wanted_blocks) +std::vector Wishlist::next(Wishlist::PeerInfo const& peer_info, size_t n_wanted_blocks) { size_t n_blocks = 0; - auto ranges = std::vector{}; + auto spans = std::vector{}; // sanity clause TR_ASSERT(n_wanted_blocks > 0); @@ -154,10 +154,10 @@ std::vector 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{}; - 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 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; } diff --git a/libtransmission/peer-mgr-wishlist.h b/libtransmission/peer-mgr-wishlist.h index ec1aad513..b3f47b162 100644 --- a/libtransmission/peer-mgr-wishlist.h +++ b/libtransmission/peer-mgr-wishlist.h @@ -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 next(PeerInfo const& peer_info, size_t n_wanted_blocks); + std::vector next(PeerInfo const& peer_info, size_t n_wanted_blocks); }; diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index 61395e5cf..26b9eaa31 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -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_peerMgrGetNextRequests(tr_torrent* torrent, tr_peer* peer, size_t numwant) +std::vector tr_peerMgrGetNextRequests(tr_torrent* torrent, tr_peer* peer, size_t numwant) { class PeerInfoImpl : public Wishlist::PeerInfo { @@ -571,7 +571,7 @@ std::vector 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_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) */ diff --git a/libtransmission/peer-mgr.h b/libtransmission/peer-mgr.h index 721892285..0a44c33d3 100644 --- a/libtransmission/peer-mgr.h +++ b/libtransmission/peer-mgr.h @@ -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_peerMgrGetNextRequests(tr_torrent* torrent, tr_peer* peer, size_t numwant); +std::vector 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); diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index 81b7afb94..2a1e9bb0d 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -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); } diff --git a/libtransmission/resume.cc b/libtransmission/resume.cc index 6055ed142..f3a41373d 100644 --- a/libtransmission/resume.cc +++ b/libtransmission/resume.cc @@ -12,7 +12,6 @@ #include #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); } diff --git a/libtransmission/rpcimpl.cc b/libtransmission/rpcimpl.cc index 2a5fda7b4..2cc97d781 100644 --- a/libtransmission/rpcimpl.cc +++ b/libtransmission/rpcimpl.cc @@ -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(tr_base64_encode(bytes.data(), std::size(bytes), nullptr)); tr_variantInitStr(initme, enc != nullptr ? std::string_view{ enc } : ""sv); tr_free(enc); diff --git a/libtransmission/torrent-magnet.cc b/libtransmission/torrent-magnet.cc index 71328d135..071167785 100644 --- a/libtransmission/torrent-magnet.cc +++ b/libtransmission/torrent-magnet.cc @@ -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; diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index 285366a7a..1a1ce6031 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -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)) { diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 2a0c331b5..215894cea 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -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 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 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 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 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 verify_progress; + + std::unordered_map 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 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 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; diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index 2e4f01044..1ad9d7975 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -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. diff --git a/libtransmission/verify.cc b/libtransmission/verify.cc index 45e9de69f..77b997190 100644 --- a/libtransmission/verify.cc +++ b/libtransmission/verify.cc @@ -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(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; } diff --git a/libtransmission/webseed.cc b/libtransmission/webseed.cc index 853c3c2a1..6273491cb 100644 --- a/libtransmission/webseed.cc +++ b/libtransmission/webseed.cc @@ -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 */ diff --git a/tests/libtransmission/CMakeLists.txt b/tests/libtransmission/CMakeLists.txt index 257187910..9de7eb45d 100644 --- a/tests/libtransmission/CMakeLists.txt +++ b/tests/libtransmission/CMakeLists.txt @@ -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 diff --git a/tests/libtransmission/bitfield-test.cc b/tests/libtransmission/bitfield-test.cc index be071b54e..0f5872840 100644 --- a/tests/libtransmission/bitfield-test.cc +++ b/tests/libtransmission/bitfield-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++) { diff --git a/tests/libtransmission/block-info-test.cc b/tests/libtransmission/block-info-test.cc index 05bcff342..82a062b95 100644 --- a/tests/libtransmission/block-info-test.cc +++ b/tests/libtransmission/block-info-test.cc @@ -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); } diff --git a/tests/libtransmission/completion-test.cc b/tests/libtransmission/completion-test.cc new file mode 100644 index 000000000..e474df7b3 --- /dev/null +++ b/tests/libtransmission/completion-test.cc @@ -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 +#include +#include + +#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 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{}; + 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{}; + + 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) +{ +} diff --git a/tests/libtransmission/move-test.cc b/tests/libtransmission/move-test.cc index b56774e97..c75a40f1e 100644 --- a/tests/libtransmission/move-test.cc +++ b/tests/libtransmission/move-test.cc @@ -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; diff --git a/tests/libtransmission/peer-mgr-wishlist-test.cc b/tests/libtransmission/peer-mgr-wishlist-test.cc index 65519401f..ad4f25c77 100644 --- a/tests/libtransmission/peer-mgr-wishlist-test.cc +++ b/tests/libtransmission/peer-mgr-wishlist-test.cc @@ -24,7 +24,7 @@ protected: { mutable std::map active_request_count_; mutable std::map missing_block_count_; - mutable std::map block_range_; + mutable std::map block_span_; mutable std::map piece_priority_; mutable std::set can_request_block_; mutable std::set 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));