refactor: add file-piece-map (#2246)

* refactor: add file-piece-map

* refactor: use tr_file_priorities

* refactor: use tr_files_wanted
This commit is contained in:
Charles Kerr 2021-11-28 19:12:54 -06:00 committed by GitHub
parent ef154e48ae
commit 35fe175f2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 785 additions and 402 deletions

View File

@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
0A6169A70FE5C9A200C66CE6 /* bitfield.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0A6169A50FE5C9A200C66CE6 /* bitfield.cc */; };
0A6169A80FE5C9A200C66CE6 /* bitfield.h in Headers */ = {isa = PBXBuildFile; fileRef = 0A6169A60FE5C9A200C66CE6 /* bitfield.h */; };
1BB44E07B1B52E28291B4E32 /* file-piece-map.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BB44E07B1B52E28291B4E30 /* file-piece-map.cc */; };
1BB44E07B1B52E28291B4E33 /* file-piece-map.h in Headers */ = {isa = PBXBuildFile; fileRef = 1BB44E07B1B52E28291B4E31 /* file-piece-map.h */; };
35F373030C2DA89000DAA8F2 /* FilePriorityCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35F373010C2DA88F00DAA8F2 /* FilePriorityCell.mm */; };
3C7A11970D0B2EE300B5701F /* getgateway.c in Sources */ = {isa = PBXBuildFile; fileRef = 3C7A11910D0B2EE300B5701F /* getgateway.c */; };
3C7A11980D0B2EE300B5701F /* getgateway.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C7A11920D0B2EE300B5701F /* getgateway.h */; };
@ -492,6 +494,8 @@
/* Begin PBXFileReference section */
0A6169A50FE5C9A200C66CE6 /* bitfield.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = bitfield.cc; sourceTree = "<group>"; };
0A6169A60FE5C9A200C66CE6 /* bitfield.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = bitfield.h; sourceTree = "<group>"; };
1BB44E07B1B52E28291B4E30 /* file-piece-map.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = file-piece-map.cc; sourceTree = "<group>"; };
1BB44E07B1B52E28291B4E31 /* file-piece-map.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = file-piece-map.h; sourceTree = "<group>"; };
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
29B97316FDCFA39411CA2CEA /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
@ -1396,6 +1400,8 @@
4D8017E910BBC073008A4AF2 /* torrent-magnet.h */,
0A6169A50FE5C9A200C66CE6 /* bitfield.cc */,
0A6169A60FE5C9A200C66CE6 /* bitfield.h */,
1BB44E07B1B52E28291B4E30 /* file-piece-map.cc */,
1BB44E07B1B52E28291B4E31 /* file-piece-map.h */,
C1425B321EE9C5EA001DB85F /* tr-assert.cc */,
C1425B331EE9C5EA001DB85F /* tr-assert.h */,
A22CFCA60FC24ED80009BD3E /* tr-dht.cc */,
@ -1883,6 +1889,7 @@
A21FBBAB0EDA78C300BC3C51 /* bandwidth.h in Headers */,
A22CFCA90FC24ED80009BD3E /* tr-dht.h in Headers */,
0A6169A80FE5C9A200C66CE6 /* bitfield.h in Headers */,
1BB44E07B1B52E28291B4E33 /* file-piece-map.h in Headers */,
A25964A7106D73A800453B31 /* announcer.h in Headers */,
ED8A16412735A8AA000D61F9 /* peer-mgr-wishlist.h in Headers */,
4D8017EB10BBC073008A4AF2 /* torrent-magnet.h in Headers */,
@ -2479,6 +2486,7 @@
C1033E081A3279B800EF44D8 /* crypto-utils-ccrypto.cc in Sources */,
A22CFCA80FC24ED80009BD3E /* tr-dht.cc in Sources */,
0A6169A70FE5C9A200C66CE6 /* bitfield.cc in Sources */,
1BB44E07B1B52E28291B4E32 /* file-piece-map.cc in Sources */,
A25964A6106D73A800453B31 /* announcer.cc in Sources */,
4D8017EA10BBC073008A4AF2 /* torrent-magnet.cc in Sources */,
4D80185910BBC0B0008A4AF2 /* magnet-metainfo.cc in Sources */,

View File

@ -25,6 +25,7 @@ set(PROJECT_FILES
crypto.cc
error.cc
fdlimit.cc
file-piece-map.cc
file-posix.cc
file-win32.cc
file.cc
@ -156,6 +157,7 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
crypto-utils.h
crypto.h
fdlimit.h
file-piece-map.h
handshake.h
history.h
inout.h

View File

@ -59,7 +59,7 @@ uint64_t tr_completion::computeSizeWhenDone() const
auto size = size_t{ 0 };
for (tr_piece_index_t piece = 0; piece < block_info_->n_pieces; ++piece)
{
if (!tor_->pieceIsDnd(piece))
if (tor_->pieceIsWanted(piece))
{
size += block_info_->pieceSize(piece);
}

View File

@ -29,7 +29,7 @@ struct tr_completion
{
struct torrent_view
{
virtual bool pieceIsDnd(tr_piece_index_t piece) const = 0;
virtual bool pieceIsWanted(tr_piece_index_t piece) const = 0;
virtual ~torrent_view() = default;
};

View File

@ -0,0 +1,180 @@
/*
* This file Copyright (C) Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#include <algorithm>
#include <iterator>
#include <vector>
#include "transmission.h"
#include "block-info.h"
#include "file-piece-map.h"
void tr_file_piece_map::reset(tr_block_info const& block_info, uint64_t const* file_sizes, size_t n_files)
{
files_.resize(n_files);
files_.shrink_to_fit();
uint64_t offset = 0;
for (tr_file_index_t i = 0; i < n_files; ++i)
{
auto const file_size = file_sizes[i];
auto const begin_piece = block_info.pieceOf(offset);
tr_piece_index_t end_piece = 0;
if (file_size != 0)
{
auto const last_byte = offset + file_size - 1;
auto const final_piece = block_info.pieceOf(last_byte);
end_piece = final_piece + 1;
}
else
{
end_piece = begin_piece + 1;
}
files_[i] = piece_span_t{ begin_piece, end_piece };
offset += file_size;
}
}
void tr_file_piece_map::reset(tr_info const& info)
{
tr_file_index_t const n = info.fileCount;
auto file_sizes = std::vector<uint64_t>(n);
std::transform(info.files, info.files + n, std::begin(file_sizes), [](tr_file const& file) { return file.length; });
reset({ info.totalSize, info.pieceSize }, std::data(file_sizes), std::size(file_sizes));
}
tr_file_piece_map::piece_span_t tr_file_piece_map::pieceSpan(tr_file_index_t file) const
{
return files_[file];
}
tr_file_piece_map::file_span_t tr_file_piece_map::fileSpan(tr_piece_index_t piece) const
{
struct Compare
{
int compare(tr_piece_index_t piece, piece_span_t span) const // <=>
{
if (piece < span.begin)
{
return -1;
}
if (piece >= span.end)
{
return 1;
}
return 0;
}
bool operator()(tr_piece_index_t piece, piece_span_t span) const // <
{
return compare(piece, span) < 0;
}
int compare(piece_span_t span, tr_piece_index_t piece) const // <=>
{
return -compare(piece, span);
}
bool operator()(piece_span_t span, tr_piece_index_t piece) const // <
{
return compare(span, piece) < 0;
}
};
auto const begin = std::begin(files_);
auto const pair = std::equal_range(begin, std::end(files_), piece, Compare{});
return { tr_piece_index_t(std::distance(begin, pair.first)), tr_piece_index_t(std::distance(begin, pair.second)) };
}
/***
****
***/
tr_file_priorities::tr_file_priorities(tr_file_piece_map const* fpm)
{
reset(fpm);
}
void tr_file_priorities::reset(tr_file_piece_map const* fpm)
{
fpm_ = fpm;
auto const n = std::size(*fpm_);
priorities_.resize(n);
priorities_.shrink_to_fit();
std::fill_n(std::begin(priorities_), n, TR_PRI_NORMAL);
}
void tr_file_priorities::set(tr_file_index_t file, tr_priority_t priority)
{
priorities_[file] = priority;
}
void tr_file_priorities::set(tr_file_index_t const* files, size_t n, tr_priority_t priority)
{
for (size_t i = 0; i < n; ++i)
{
set(files[i], priority);
}
}
tr_priority_t tr_file_priorities::filePriority(tr_file_index_t file) const
{
return priorities_[file];
}
tr_priority_t tr_file_priorities::piecePriority(tr_piece_index_t piece) const
{
auto const [begin_idx, end_idx] = fpm_->fileSpan(piece);
auto const begin = std::begin(priorities_) + begin_idx;
auto const end = std::begin(priorities_) + end_idx;
auto const it = std::max_element(begin, end);
if (it == end)
{
return TR_PRI_NORMAL;
}
return *it;
}
/***
****
***/
void tr_files_wanted::reset(tr_file_piece_map const* fpm)
{
fpm_ = fpm;
wanted_ = tr_bitfield{ std::size(*fpm) };
wanted_.setHasAll(); // by default we want all files
}
void tr_files_wanted::set(tr_file_index_t file, bool wanted)
{
wanted_.set(file, wanted);
}
void tr_files_wanted::set(tr_file_index_t const* files, size_t n, bool wanted)
{
for (size_t i = 0; i < n; ++i)
{
set(files[i], wanted);
}
}
bool tr_files_wanted::fileWanted(tr_file_index_t file) const
{
return wanted_.test(file);
}
bool tr_files_wanted::pieceWanted(tr_piece_index_t piece) const
{
auto const [begin, end] = fpm_->fileSpan(piece);
return wanted_.count(begin, end) != 0;
}

View File

@ -0,0 +1,92 @@
/*
* 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.
*
*/
#pragma once
#ifndef __TRANSMISSION__
#error only libtransmission should #include this header.
#endif
#include <vector>
#include "transmission.h"
#include "bitfield.h"
struct tr_block_info;
class tr_file_piece_map
{
public:
template<typename T>
struct index_span_t
{
T begin;
T end;
};
using file_span_t = index_span_t<tr_file_index_t>;
using piece_span_t = index_span_t<tr_piece_index_t>;
tr_file_piece_map(tr_info const& info)
{
reset(info);
}
tr_file_piece_map(tr_block_info const& block_info, uint64_t const* file_sizes, size_t n_files)
{
reset(block_info, file_sizes, n_files);
}
void reset(tr_block_info const& block_info, uint64_t const* file_sizes, size_t n_files);
void reset(tr_info const& info);
[[nodiscard]] piece_span_t pieceSpan(tr_file_index_t file) const;
[[nodiscard]] file_span_t fileSpan(tr_piece_index_t piece) const;
[[nodiscard]] size_t size() const
{
return std::size(files_);
}
private:
std::vector<piece_span_t> files_;
};
class tr_file_priorities
{
public:
explicit tr_file_priorities(tr_file_piece_map const* fpm);
void reset(tr_file_piece_map const*);
void set(tr_file_index_t file, tr_priority_t priority);
void set(tr_file_index_t const* files, size_t n, tr_priority_t priority);
[[nodiscard]] tr_priority_t filePriority(tr_file_index_t file) const;
[[nodiscard]] tr_priority_t piecePriority(tr_piece_index_t piece) const;
private:
tr_file_piece_map const* fpm_;
std::vector<tr_priority_t> priorities_;
};
class tr_files_wanted
{
public:
explicit tr_files_wanted(tr_file_piece_map const* fpm)
: wanted_(std::size(*fpm))
{
reset(fpm);
}
void reset(tr_file_piece_map const* fpm);
void set(tr_file_index_t file, bool wanted);
void set(tr_file_index_t const* files, size_t n, bool wanted);
[[nodiscard]] bool fileWanted(tr_file_index_t file) const;
[[nodiscard]] bool pieceWanted(tr_piece_index_t piece) const;
private:
tr_file_piece_map const* fpm_;
tr_bitfield wanted_;
};

View File

@ -53,7 +53,7 @@ static int readOrWriteBytes(
bool const doWrite = ioMode >= TR_IO_WRITE;
TR_ASSERT(fileIndex < tr_torrentFileCount(tor));
auto const file = tr_torrentFile(tor, fileIndex);
auto const& file = tor->info.files[fileIndex];
TR_ASSERT(file.length == 0 || fileOffset < file.length);
TR_ASSERT(fileOffset + buflen <= file.length);
@ -91,8 +91,9 @@ static int readOrWriteBytes(
{
/* open (and maybe create) the file */
auto const filename = tr_strvPath(base, subpath);
tr_preallocation_mode const prealloc = (!file.wanted || !doWrite) ? TR_PREALLOCATE_NONE :
tor->session->preallocationMode;
tr_preallocation_mode const prealloc = (!doWrite || !tor->fileIsWanted(fileIndex)) ?
TR_PREALLOCATE_NONE :
tor->session->preallocationMode;
fd = tr_fdFileCheckout(session, tor->uniqueId, fileIndex, filename.c_str(), doWrite, prealloc, file.length);
if (fd == TR_BAD_SYS_FILE)

View File

@ -577,7 +577,7 @@ std::vector<tr_block_span_t> tr_peerMgrGetNextRequests(tr_torrent* torrent, tr_p
bool clientCanRequestPiece(tr_piece_index_t piece) const override
{
return !torrent_->pieceIsDnd(piece) && peer_->have.test(piece);
return torrent_->pieceIsWanted(piece) && peer_->have.test(piece);
}
bool isEndgame() const override
@ -1694,7 +1694,7 @@ uint64_t tr_peerMgrGetDesiredAvailable(tr_torrent const* tor)
for (size_t i = 0; i < n_pieces; ++i)
{
if (!tor->pieceIsDnd(i) && have.at(i))
if (tor->pieceIsWanted(i) && have.at(i))
{
desired_available += tor->countMissingBytesInPiece(i);
}
@ -2022,7 +2022,7 @@ static void rechokeDownloads(tr_swarm* s)
for (int i = 0; i < n; ++i)
{
piece_is_interesting[i] = !tor->pieceIsDnd(i) && !tor->hasPiece(i);
piece_is_interesting[i] = tor->pieceIsWanted(i) && !tor->hasPiece(i);
}
/* decide WHICH peers to be interested in (based on their cancel-to-block ratio) */
@ -2703,7 +2703,7 @@ static void bandwidthPulse(evutil_socket_t /*fd*/, short /*what*/, void* vmgr)
if (tor->swarm->needsCompletenessCheck)
{
tor->swarm->needsCompletenessCheck = false;
tr_torrentRecheckCompleteness(tor);
tor->recheckCompleteness();
}
/* stop torrents that are ready to stop, but couldn't be stopped

View File

@ -156,38 +156,27 @@ static uint64_t loadDND(tr_variant* dict, tr_torrent* tor)
if (tr_variantDictFindList(dict, TR_KEY_dnd, &list) && tr_variantListSize(list) == n)
{
tr_file_index_t* dl = tr_new(tr_file_index_t, n);
tr_file_index_t* dnd = tr_new(tr_file_index_t, n);
tr_file_index_t dlCount = 0;
tr_file_index_t dndCount = 0;
auto wanted = std::vector<tr_file_index_t>{};
auto unwanted = std::vector<tr_file_index_t>{};
wanted.reserve(n);
unwanted.reserve(n);
for (tr_file_index_t i = 0; i < n; ++i)
{
auto tmp = false;
if (tr_variantGetBool(tr_variantListChild(list, i), &tmp) && tmp)
{
dnd[dndCount++] = i;
unwanted.push_back(i);
}
else
{
dl[dlCount++] = i;
wanted.push_back(i);
}
}
if (dndCount != 0)
{
tr_torrentInitFileDLs(tor, dnd, dndCount, false);
tr_logAddTorDbg(tor, "Resume file found %d files listed as dnd", dndCount);
}
tor->initFilesWanted(std::data(unwanted), std::size(unwanted), false);
tor->initFilesWanted(std::data(wanted), std::size(wanted), true);
if (dlCount != 0)
{
tr_torrentInitFileDLs(tor, dl, dlCount, true);
tr_logAddTorDbg(tor, "Resume file found %d files marked for download", dlCount);
}
tr_free(dnd);
tr_free(dl);
ret = TR_FR_DND;
}
else
@ -232,7 +221,7 @@ static uint64_t loadFilePriorities(tr_variant* dict, tr_torrent* tor)
auto priority = int64_t{};
if (tr_variantGetInt(tr_variantListChild(list, i), &priority))
{
tr_torrentInitFilePriority(tor, i, priority);
tor->setFilePriority(i, priority);
}
}
@ -570,7 +559,6 @@ static uint64_t loadProgress(tr_variant* dict, tr_torrent* tor)
for (tr_file_index_t fi = 0; fi < n_files; ++fi)
{
tr_variant* const b = tr_variantListChild(l, fi);
tr_file* const f = &inf->files[fi];
auto time_checked = time_t{};
if (tr_variantIsInt(b))
@ -585,8 +573,8 @@ static uint64_t loadProgress(tr_variant* dict, tr_torrent* tor)
tr_variantGetInt(tr_variantListChild(b, 0), &offset);
time_checked = tr_time();
size_t const pieces = f->priv.lastPiece + 1 - f->priv.firstPiece;
for (size_t i = 0; i < pieces; ++i)
auto const [begin, end] = tor->piecesInFile(fi);
for (size_t i = 0, n = end - begin; i < n; ++i)
{
int64_t piece_time = 0;
tr_variantGetInt(tr_variantListChild(b, i + 1), &piece_time);

View File

@ -13,6 +13,7 @@
#include <cstdlib> /* strtol */
#include <cstring> /* strcmp */
#include <iterator>
#include <numeric>
#include <string_view>
#include <vector>
@ -983,11 +984,11 @@ static char const* setLabels(tr_torrent* tor, tr_variant* list)
static char const* setFilePriorities(tr_torrent* tor, int priority, tr_variant* list)
{
int fileCount = 0;
size_t const n = tr_variantListSize(list);
char const* errmsg = nullptr;
tr_file_index_t* files = tr_new0(tr_file_index_t, tor->info.fileCount);
auto files = std::vector<tr_file_index_t>{};
files.reserve(tr_torrentFileCount(tor));
size_t const n = tr_variantListSize(list);
if (n != 0)
{
for (size_t i = 0; i < n; ++i)
@ -997,7 +998,7 @@ static char const* setFilePriorities(tr_torrent* tor, int priority, tr_variant*
{
if (0 <= tmp && tmp < tor->info.fileCount)
{
files[fileCount++] = tmp;
files.push_back(tmp);
}
else
{
@ -1006,41 +1007,39 @@ static char const* setFilePriorities(tr_torrent* tor, int priority, tr_variant*
}
}
}
else /* if empty set, apply to all */
else // if empty set, apply to all
{
for (tr_file_index_t t = 0; t < tor->info.fileCount; ++t)
{
files[fileCount++] = t;
files.push_back(t);
}
}
if (fileCount != 0)
{
tr_torrentSetFilePriorities(tor, files, fileCount, priority);
}
tor->setFilePriorities(std::data(files), std::size(files), priority);
tr_free(files);
return errmsg;
}
static char const* setFileDLs(tr_torrent* tor, bool do_download, tr_variant* list)
static char const* setFileDLs(tr_torrent* tor, bool wanted, tr_variant* list)
{
char const* errmsg = nullptr;
int fileCount = 0;
size_t const n = tr_variantListSize(list);
tr_file_index_t* files = tr_new0(tr_file_index_t, tor->info.fileCount);
auto const n_files = tr_torrentFileCount(tor);
size_t const n_items = tr_variantListSize(list);
if (n != 0) /* if argument list, process them */
auto files = std::vector<tr_file_index_t>{};
files.reserve(n_files);
if (n_items != 0) // if argument list, process them
{
for (size_t i = 0; i < n; ++i)
for (size_t i = 0; i < n_items; ++i)
{
auto tmp = int64_t{};
if (tr_variantGetInt(tr_variantListChild(list, i), &tmp))
{
if (0 <= tmp && tmp < tor->info.fileCount)
{
files[fileCount++] = tmp;
files.push_back(tmp);
}
else
{
@ -1049,20 +1048,14 @@ static char const* setFileDLs(tr_torrent* tor, bool do_download, tr_variant* lis
}
}
}
else /* if empty set, apply to all */
else // if empty set, apply to all
{
for (tr_file_index_t t = 0; t < tor->info.fileCount; ++t)
{
files[fileCount++] = t;
}
files.resize(n_files);
std::iota(std::begin(files), std::end(files), 0);
}
if (fileCount != 0)
{
tr_torrentSetFileDLs(tor, files, fileCount, do_download);
}
tor->setFilesWanted(std::data(files), std::size(files), wanted);
tr_free(files);
return errmsg;
}

View File

@ -46,8 +46,8 @@ struct tr_ctor
std::string incomplete_dir;
std::vector<tr_file_index_t> want;
std::vector<tr_file_index_t> not_want;
std::vector<tr_file_index_t> wanted;
std::vector<tr_file_index_t> unwanted;
std::vector<tr_file_index_t> low;
std::vector<tr_file_index_t> normal;
std::vector<tr_file_index_t> high;
@ -188,32 +188,21 @@ void tr_ctorSetFilePriorities(tr_ctor* ctor, tr_file_index_t const* files, tr_fi
void tr_ctorInitTorrentPriorities(tr_ctor const* ctor, tr_torrent* tor)
{
for (auto file_index : ctor->low)
{
tr_torrentInitFilePriority(tor, file_index, TR_PRI_LOW);
}
for (auto file_index : ctor->normal)
{
tr_torrentInitFilePriority(tor, file_index, TR_PRI_NORMAL);
}
for (auto file_index : ctor->high)
{
tr_torrentInitFilePriority(tor, file_index, TR_PRI_HIGH);
}
tor->setFilePriorities(std::data(ctor->low), std::size(ctor->low), TR_PRI_LOW);
tor->setFilePriorities(std::data(ctor->normal), std::size(ctor->normal), TR_PRI_NORMAL);
tor->setFilePriorities(std::data(ctor->high), std::size(ctor->high), TR_PRI_HIGH);
}
void tr_ctorSetFilesWanted(tr_ctor* ctor, tr_file_index_t const* files, tr_file_index_t fileCount, bool wanted)
{
auto& indices = wanted ? ctor->want : ctor->not_want;
auto& indices = wanted ? ctor->wanted : ctor->unwanted;
indices.assign(files, files + fileCount);
}
void tr_ctorInitTorrentWanted(tr_ctor const* ctor, tr_torrent* tor)
{
tr_torrentInitFileDLs(tor, std::data(ctor->not_want), std::size(ctor->not_want), false);
tr_torrentInitFileDLs(tor, std::data(ctor->want), std::size(ctor->want), true);
tor->initFilesWanted(std::data(ctor->unwanted), std::size(ctor->unwanted), false);
tor->initFilesWanted(std::data(ctor->wanted), std::size(ctor->wanted), true);
}
/***

View File

@ -579,61 +579,6 @@ static void onTrackerResponse(tr_torrent* tor, tr_tracker_event const* event, vo
****
***/
static constexpr void initFilePieces(tr_torrent* tor, tr_file_index_t fileIndex)
{
TR_ASSERT(tor != nullptr);
TR_ASSERT(fileIndex < tor->info.fileCount);
tr_file& file = tor->info.files[fileIndex];
uint64_t first_byte = file.priv.offset;
uint64_t last_byte = first_byte + (file.length != 0 ? file.length - 1 : 0);
file.priv.firstPiece = tor->pieceOf(first_byte);
file.priv.lastPiece = tor->pieceOf(last_byte);
}
static constexpr bool pieceHasFile(tr_piece_index_t piece, tr_file const* file)
{
return file->priv.firstPiece <= piece && piece <= file->priv.lastPiece;
}
static tr_priority_t calculatePiecePriority(tr_info const& info, tr_piece_index_t piece, tr_file_index_t file_hint)
{
// safeguard against a bad arg
file_hint = std::min(file_hint, info.fileCount - 1);
// find the first file with data in this piece
tr_file_index_t first = file_hint;
while (first > 0 && pieceHasFile(piece, &info.files[first - 1]))
{
--first;
}
// the priority is the max of all the file priorities in the piece
tr_priority_t priority = TR_PRI_LOW;
for (tr_file_index_t i = first; i < info.fileCount; ++i)
{
tr_file const* file = &info.files[i];
if (!pieceHasFile(piece, file))
{
break;
}
priority = std::max(priority, file->priv.priority);
/* When dealing with multimedia files, getting the first and
last pieces can sometimes allow you to preview it a bit
before it's fully downloaded... */
if ((file->priv.priority >= TR_PRI_NORMAL) && (file->priv.firstPiece == piece || file->priv.lastPiece == piece))
{
priority = TR_PRI_HIGH;
}
}
return priority;
}
static void tr_torrentInitFilePieces(tr_torrent* tor)
{
uint64_t offset = 0;
@ -644,44 +589,9 @@ static void tr_torrentInitFilePieces(tr_torrent* tor)
{
inf->files[f].priv.offset = offset;
offset += inf->files[f].length;
initFilePieces(tor, f);
}
}
static void tr_torrentInitPiecePriorities(tr_torrent* tor)
{
tor->piece_priorities_.clear();
// throw away file prorities once we're done downloading,
// they just waste time & space
if (tr_torrentIsSeed(tor))
{
return;
}
/* build the array of first-file hints to give calculatePiecePriority */
tr_info* inf = &tor->info;
tr_file_index_t* firstFiles = tr_new(tr_file_index_t, inf->pieceCount);
tr_file_index_t f = 0;
for (tr_piece_index_t p = 0; p < inf->pieceCount; ++p)
{
while (inf->files[f].priv.lastPiece < p)
{
++f;
}
firstFiles[p] = f;
}
for (tr_piece_index_t p = 0; p < inf->pieceCount; ++p)
{
tor->setPiecePriority(p, calculatePiecePriority(*inf, p, firstFiles[p]));
}
tr_free(firstFiles);
}
static void torrentStart(tr_torrent* tor, bool bypass_queue);
static void tr_torrentFireMetadataCompleted(tr_torrent* tor);
@ -752,6 +662,10 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
static int nextUniqueId = 1;
tor->fpm_.reset(tor->info);
tor->file_priorities_.reset(&tor->fpm_);
tor->files_wanted_.reset(&tor->fpm_);
tor->session = session;
tor->uniqueId = nextUniqueId++;
tor->queuePosition = tr_sessionCountTorrents(session);
@ -814,7 +728,6 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
tr_ctorInitTorrentPriorities(ctor, tor);
tr_ctorInitTorrentWanted(ctor, tor);
tr_torrentInitPiecePriorities(tor);
refreshCurrentDir(tor);
@ -1315,8 +1228,8 @@ tr_file_view tr_torrentFile(tr_torrent const* torrent, tr_file_index_t i)
auto const& file = torrent->info.files[i];
auto const* const name = file.name;
auto const priority = file.priv.priority;
auto const wanted = !file.priv.dnd;
auto const priority = torrent->file_priorities_.filePriority(i);
auto const wanted = torrent->files_wanted_.fileWanted(i);
auto const length = file.length;
if (torrent->completeness == TR_SEED || length == 0)
@ -1461,7 +1374,7 @@ static void torrentStartImpl(void* vtor)
TR_ASSERT(tr_isTorrent(tor));
tr_torrentRecheckCompleteness(tor);
tor->recheckCompleteness();
torrentSetQueued(tor, false);
time_t const now = tr_time();
@ -1603,7 +1516,7 @@ static void onVerifyDoneThreadFunc(void* vdata)
{
if (!data->aborted)
{
tr_torrentRecheckCompleteness(tor);
tor->recheckCompleteness();
}
if (data->callback_func != nullptr)
@ -1973,65 +1886,65 @@ static void torrentCallScript(tr_torrent const* tor, char const* script)
tr_free(torrent_dir);
}
void tr_torrentRecheckCompleteness(tr_torrent* tor)
void tr_torrent::recheckCompleteness()
{
auto const lock = tor->unique_lock();
auto const lock = unique_lock();
auto const completeness = tor->completion.status();
auto const new_completeness = completion.status();
if (completeness != tor->completeness)
if (new_completeness != completeness)
{
bool const recentChange = tor->downloadedCur != 0;
bool const wasLeeching = !tr_torrentIsSeed(tor);
bool const wasRunning = tor->isRunning;
bool const recentChange = downloadedCur != 0;
bool const wasLeeching = !tr_torrentIsSeed(this);
bool const wasRunning = isRunning;
if (recentChange)
{
tr_logAddTorInfo(
tor,
this,
_("State changed from \"%1$s\" to \"%2$s\""),
getCompletionString(tor->completeness),
getCompletionString(this->completeness),
getCompletionString(completeness));
}
tor->completeness = completeness;
tr_fdTorrentClose(tor->session, tor->uniqueId);
this->completeness = new_completeness;
tr_fdTorrentClose(this->session, this->uniqueId);
if (tr_torrentIsSeed(tor))
if (tr_torrentIsSeed(this))
{
if (recentChange)
{
tr_announcerTorrentCompleted(tor);
tor->doneDate = tor->anyDate = tr_time();
tr_announcerTorrentCompleted(this);
this->doneDate = this->anyDate = tr_time();
}
if (wasLeeching && wasRunning)
{
/* clear interested flag on all peers */
tr_peerMgrClearInterest(tor);
tr_peerMgrClearInterest(this);
}
if (tor->currentDir == tor->incompleteDir)
if (this->currentDir == this->incompleteDir)
{
tor->setLocation(tor->downloadDir, true, nullptr, nullptr);
this->setLocation(this->downloadDir, true, nullptr, nullptr);
}
}
fireCompletenessChange(tor, completeness, wasRunning);
fireCompletenessChange(this, completeness, wasRunning);
if (tr_torrentIsSeed(tor) && wasLeeching && wasRunning)
if (tr_torrentIsSeed(this) && wasLeeching && wasRunning)
{
/* if completeness was TR_LEECH, the seed limit check
will have been skipped in bandwidthPulse */
tr_torrentCheckSeedLimit(tor);
tr_torrentCheckSeedLimit(this);
}
tr_torrentSetDirty(tor);
this->setDirty();
if (tr_torrentIsSeed(tor))
if (tr_torrentIsSeed(this))
{
tr_torrentSave(tor);
callScriptIfEnabled(tor, TR_SCRIPT_ON_TORRENT_DONE);
tr_torrentSave(this);
callScriptIfEnabled(this, TR_SCRIPT_ON_TORRENT_DONE);
}
}
}
@ -2058,151 +1971,15 @@ void tr_torrentSetMetadataCallback(tr_torrent* tor, tr_torrent_metadata_func fun
tor->metadata_func_user_data = user_data;
}
/**
*** File priorities
**/
void tr_torrentInitFilePriority(tr_torrent* tor, tr_file_index_t fileIndex, tr_priority_t priority)
{
TR_ASSERT(tr_isTorrent(tor));
TR_ASSERT(fileIndex < tor->info.fileCount);
TR_ASSERT(tr_isPriority(priority));
auto& info = tor->info;
tr_file* file = &info.files[fileIndex];
file->priv.priority = priority;
for (tr_piece_index_t i = file->priv.firstPiece; i <= file->priv.lastPiece; ++i)
{
tor->setPiecePriority(i, calculatePiecePriority(info, i, fileIndex));
}
}
void tr_torrentSetFilePriorities(
tr_torrent* tor,
tr_file_index_t const* files,
tr_file_index_t fileCount,
tr_priority_t priority)
{
TR_ASSERT(tr_isTorrent(tor));
auto const lock = tor->unique_lock();
for (tr_file_index_t i = 0; i < fileCount; ++i)
{
if (files[i] < tor->info.fileCount)
{
tr_torrentInitFilePriority(tor, files[i], priority);
}
}
tr_torrentSetDirty(tor);
}
tr_priority_t* tr_torrentGetFilePriorities(tr_torrent const* tor)
{
TR_ASSERT(tr_isTorrent(tor));
tr_priority_t* p = tr_new0(tr_priority_t, tor->info.fileCount);
for (tr_file_index_t i = 0; i < tor->info.fileCount; ++i)
{
p[i] = tor->info.files[i].priv.priority;
}
return p;
}
/**
*** File DND
**/
static void setFileDND(tr_torrent* tor, tr_file_index_t fileIndex, bool doDownload)
{
bool const dnd = !doDownload;
tr_file* file = &tor->info.files[fileIndex];
file->priv.dnd = dnd;
auto const firstPiece = file->priv.firstPiece;
auto const lastPiece = file->priv.lastPiece;
/* can't set the first piece to DND unless
every file using that piece is DND */
auto firstPieceDND = dnd;
if (fileIndex > 0)
{
for (tr_file_index_t i = fileIndex - 1; firstPieceDND; --i)
{
if (tor->info.files[i].priv.lastPiece != firstPiece)
{
break;
}
firstPieceDND = tor->info.files[i].priv.dnd;
if (i == 0)
{
break;
}
}
}
/* can't set the last piece to DND unless
every file using that piece is DND */
auto lastPieceDND = dnd;
for (tr_file_index_t i = fileIndex + 1; lastPieceDND && i < tor->info.fileCount; ++i)
{
if (tor->info.files[i].priv.firstPiece != lastPiece)
{
break;
}
lastPieceDND = tor->info.files[i].priv.dnd;
}
// update dnd_pieces_
if (firstPiece == lastPiece)
{
tor->dnd_pieces_.set(firstPiece, firstPieceDND && lastPieceDND);
}
else
{
tor->dnd_pieces_.set(firstPiece, firstPieceDND);
tor->dnd_pieces_.set(lastPiece, lastPieceDND);
for (tr_piece_index_t pp = firstPiece + 1; pp < lastPiece; ++pp)
{
tor->dnd_pieces_.set(pp, dnd);
}
}
}
void tr_torrentInitFileDLs(tr_torrent* tor, tr_file_index_t const* files, tr_file_index_t fileCount, bool doDownload)
void tr_torrentSetFileDLs(tr_torrent* tor, tr_file_index_t const* files, tr_file_index_t n_files, bool wanted)
{
TR_ASSERT(tr_isTorrent(tor));
auto const lock = tor->unique_lock();
for (tr_file_index_t i = 0; i < fileCount; ++i)
{
if (files[i] < tor->info.fileCount)
{
setFileDND(tor, files[i], doDownload);
}
}
tor->completion.invalidateSizeWhenDone();
}
void tr_torrentSetFileDLs(tr_torrent* tor, tr_file_index_t const* files, tr_file_index_t fileCount, bool doDownload)
{
TR_ASSERT(tr_isTorrent(tor));
auto const lock = tor->unique_lock();
tr_torrentInitFileDLs(tor, files, fileCount, doDownload);
tr_torrentSetDirty(tor);
tr_torrentRecheckCompleteness(tor);
tor->setFilesWanted(files, n_files, wanted);
}
/***
@ -2528,16 +2305,18 @@ uint64_t tr_torrentGetBytesLeftToAllocate(tr_torrent const* tor)
uint64_t bytesLeft = 0;
for (tr_file_index_t i = 0; i < tor->info.fileCount; ++i)
for (tr_file_index_t i = 0, n = tr_torrentFileCount(tor); i < n; ++i)
{
if (!tor->info.files[i].priv.dnd)
auto const file = tr_torrentFile(tor, i);
if (file.wanted)
{
tr_sys_path_info info;
uint64_t const length = tor->info.files[i].length;
uint64_t const length = file.length;
char* path = tr_torrentFindFile(tor, i);
bytesLeft += length;
tr_sys_path_info info;
if (path != nullptr && tr_sys_path_get_info(path, 0, &info, nullptr) && info.type == TR_SYS_PATH_IS_FILE &&
info.size <= length)
{
@ -3008,15 +2787,13 @@ static void tr_torrentPieceCompleted(tr_torrent* tor, tr_piece_index_t pieceInde
{
tr_peerMgrPieceCompleted(tor, pieceIndex);
/* if this piece completes any file, invoke the fileCompleted func for it */
for (tr_file_index_t i = 0; i < tor->info.fileCount; ++i)
// if this piece completes any file, invoke the fileCompleted func for it
auto const [begin, end] = tor->fpm_.fileSpan(pieceIndex);
for (tr_file_index_t file = begin; file < end; ++file)
{
tr_file const* file = &tor->info.files[i];
if ((file->priv.firstPiece <= pieceIndex) && (pieceIndex <= file->priv.lastPiece) &&
tor->completion.hasBlocks(tr_torGetFileBlockSpan(tor, i)))
if (tor->completion.hasBlocks(tr_torGetFileBlockSpan(tor, file)))
{
tr_torrentFileCompleted(tor, i);
tr_torrentFileCompleted(tor, file);
}
}
}
@ -3567,3 +3344,12 @@ void tr_torrent::swapMetainfo(tr_metainfo_parsed& parsed)
std::swap(this->piece_checksums_, parsed.pieces);
std::swap(this->infoDictLength, parsed.info_dict_length);
}
void tr_torrentSetFilePriorities(
tr_torrent* tor,
tr_file_index_t const* files,
tr_file_index_t fileCount,
tr_priority_t priority)
{
tor->setFilePriorities(files, fileCount, priority);
}

View File

@ -15,7 +15,6 @@
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@ -26,6 +25,7 @@
#include "block-info.h"
#include "completion.h"
#include "file.h"
#include "file-piece-map.h"
#include "quark.h"
#include "session.h"
#include "tr-assert.h"
@ -56,15 +56,10 @@ void tr_ctorInitTorrentWanted(tr_ctor const* ctor, tr_torrent* tor);
***
**/
/* just like tr_torrentSetFileDLs but doesn't trigger a fastresume save */
void tr_torrentInitFileDLs(tr_torrent* tor, tr_file_index_t const* files, tr_file_index_t fileCount, bool do_download);
using tr_labels_t = std::unordered_set<std::string>;
void tr_torrentSetLabels(tr_torrent* tor, tr_labels_t&& labels);
void tr_torrentRecheckCompleteness(tr_torrent*);
void tr_torrentChangeMyPort(tr_torrent* session);
tr_sha1_digest_t tr_torrentInfoHash(tr_torrent const* torrent);
@ -90,8 +85,6 @@ void tr_torrentGetBlockLocation(
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);
void tr_torrentCheckSeedLimit(tr_torrent* tor);
/** save a torrent's .resume file if it's changed since the last time it was saved */
@ -238,41 +231,54 @@ public:
completion.setHasPiece(piece, has);
}
bool pieceIsDnd(tr_piece_index_t piece) const final
/// FILE <-> PIECE
auto piecesInFile(tr_file_index_t file) const
{
return dnd_pieces_.test(piece);
return fpm_.pieceSpan(file);
}
/// WANTED
bool pieceIsWanted(tr_piece_index_t piece) const final
{
return files_wanted_.pieceWanted(piece);
}
bool fileIsWanted(tr_file_index_t file) const
{
return files_wanted_.fileWanted(file);
}
void initFilesWanted(tr_file_index_t const* files, size_t n_files, bool wanted)
{
setFilesWanted(files, n_files, wanted, /*is_bootstrapping*/ true);
}
void setFilesWanted(tr_file_index_t const* files, size_t n_files, bool wanted)
{
setFilesWanted(files, n_files, wanted, /*is_bootstrapping*/ false);
}
void recheckCompleteness(); // TODO(ckerr): should be private
/// 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);
if (std::empty(piece_priorities_))
{
// ensure we release piece_priorities_' internal memory
piece_priorities_ = decltype(piece_priorities_){};
}
}
else
{
piece_priorities_[piece] = priority;
}
}
tr_priority_t piecePriority(tr_piece_index_t piece) const
{
auto const it = piece_priorities_.find(piece);
if (it == std::end(piece_priorities_))
{
return TR_PRI_NORMAL;
}
return it->second;
return file_priorities_.piecePriority(piece);
}
void setFilePriorities(tr_file_index_t const* files, tr_file_index_t fileCount, tr_priority_t priority)
{
file_priorities_.set(files, fileCount, priority);
setDirty();
}
void setFilePriority(tr_file_index_t file, tr_priority_t priority)
{
file_priorities_.set(file, priority);
setDirty();
}
/// CHECKSUMS
@ -310,8 +316,7 @@ public:
// if a file has changed, mark its pieces as unchecked
if (mtime == 0 || mtime != mtimes[i])
{
auto const begin = info.files[i].priv.firstPiece;
auto const end = info.files[i].priv.lastPiece + 1;
auto const [begin, end] = piecesInFile(i);
checked_pieces_.unsetSpan(begin, end);
}
}
@ -360,8 +365,6 @@ public:
std::optional<double> verify_progress;
std::unordered_map<tr_piece_index_t, tr_priority_t> piece_priorities_;
tr_stat_errtype error = TR_STAT_OK;
char errorString[128] = {};
tr_quark error_announce_url = TR_KEY_NONE;
@ -491,7 +494,25 @@ public:
static auto constexpr MagicNumber = int{ 95549 };
tr_file_piece_map fpm_ = tr_file_piece_map{ info };
tr_file_priorities file_priorities_{ &fpm_ };
tr_files_wanted files_wanted_{ &fpm_ };
private:
void setFilesWanted(tr_file_index_t const* files, size_t n_files, bool wanted, bool is_bootstrapping)
{
auto const lock = unique_lock();
files_wanted_.set(files, n_files, wanted);
completion.invalidateSizeWhenDone();
if (!is_bootstrapping)
{
setDirty();
recheckCompleteness();
}
}
mutable std::vector<tr_sha1_digest_t> piece_checksums_;
};

View File

@ -1188,15 +1188,6 @@ void tr_torrentSetFilePriorities(
tr_file_index_t fileCount,
tr_priority_t priority);
/**
* @brief Get this torrent's file priorities.
*
* @return A malloc()ed array of tor->info.fileCount items,
* each holding a TR_PRI_NORMAL, TR_PRI_HIGH, or TR_PRI_LOW.
* It's the caller's responsibility to free() this.
*/
tr_priority_t* tr_torrentGetFilePriorities(tr_torrent const* torrent);
/** @brief Set a batch of files to be downloaded or not. */
void tr_torrentSetFileDLs(tr_torrent* torrent, tr_file_index_t const* files, tr_file_index_t fileCount, bool do_download);
@ -1603,10 +1594,6 @@ struct tr_file_priv
{
uint64_t offset; // file begins at the torrent's nth byte
time_t mtime;
tr_piece_index_t firstPiece; // We need pieces [firstPiece...
tr_piece_index_t lastPiece; // ...lastPiece] to dl this file
int8_t priority; // TR_PRI_HIGH, _NORMAL, or _LOW
bool dnd; // "do not download" flag
bool is_renamed; // true if we're using a different path from the one in the metainfo; ie, if the user has renamed it */
};
/** @brief a part of tr_info that represents a single file of the torrent's content */

View File

@ -9,6 +9,7 @@ add_executable(libtransmission-test
crypto-test.cc
error-test.cc
file-test.cc
file-piece-map-test.cc
getopt-test.cc
history-test.cc
json-test.cc

View File

@ -27,9 +27,9 @@ struct TestTorrent : public tr_completion::torrent_view
{
std::set<tr_piece_index_t> dnd_pieces;
[[nodiscard]] bool pieceIsDnd(tr_piece_index_t piece) const final
[[nodiscard]] bool pieceIsWanted(tr_piece_index_t piece) const final
{
return dnd_pieces.count(piece) != 0;
return dnd_pieces.count(piece) == 0;
}
};

View File

@ -0,0 +1,335 @@
/*
* This file Copyright (C) Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#include <array>
#include <numeric>
#include <cstdint>
#include "transmission.h"
#include "block-info.h"
#include "file-piece-map.h"
#include "gtest/gtest.h"
class FilePieceMapTest : public ::testing::Test
{
protected:
static constexpr size_t TotalSize{ 1001 };
static constexpr size_t PieceSize{ 100 };
tr_block_info const block_info_{ TotalSize, PieceSize };
static constexpr std::array<uint64_t, 17> FileSizes{
500, // [offset 0] begins and ends on a piece boundary
0, // [offset 500] zero-sized files
0, 0, 0,
50, // [offset 500] begins on a piece boundary
100, // [offset 550] neither begins nor ends on a piece boundary, spans >1 piece
10, // [offset 650] small files all contained in a single piece
9, 8, 7, 6,
311, // [offset 690] ends end-of-torrent
0, // [offset 1001] zero-sized files at the end-of-torrent
0, 0, 0,
// sum is 1001 == TotalSize
};
void SetUp() override
{
EXPECT_EQ(11, block_info_.n_pieces);
EXPECT_EQ(PieceSize, block_info_.piece_size);
EXPECT_EQ(TotalSize, block_info_.total_size);
EXPECT_EQ(TotalSize, std::accumulate(std::begin(FileSizes), std::end(FileSizes), uint64_t{ 0 }));
}
};
TEST_F(FilePieceMapTest, pieceSpan)
{
// Note to reviewers: it's easy to see a nonexistent fencepost error here.
// Remember everything is zero-indexed, so the 11 valid pieces are [0..10]
// and that last piece #10 has one byte in it. Piece #11 is the 'end' iterator position.
auto constexpr ExpectedPieceSpans = std::array<tr_file_piece_map::piece_span_t, 17>{ {
{ 0, 5 },
{ 5, 6 },
{ 5, 6 },
{ 5, 6 },
{ 5, 6 },
{ 5, 6 },
{ 5, 7 },
{ 6, 7 },
{ 6, 7 },
{ 6, 7 },
{ 6, 7 },
{ 6, 7 },
{ 6, 11 },
{ 10, 11 },
{ 10, 11 },
{ 10, 11 },
{ 10, 11 },
} };
EXPECT_EQ(std::size(FileSizes), std::size(ExpectedPieceSpans));
auto const fpm = tr_file_piece_map{ block_info_, std::data(FileSizes), std::size(FileSizes) };
tr_file_index_t const n = std::size(fpm);
EXPECT_EQ(std::size(FileSizes), n);
uint64_t offset = 0;
for (tr_file_index_t file = 0; file < n; ++file)
{
EXPECT_EQ(ExpectedPieceSpans[file].begin, fpm.pieceSpan(file).begin);
EXPECT_EQ(ExpectedPieceSpans[file].end, fpm.pieceSpan(file).end);
offset += FileSizes[file];
}
EXPECT_EQ(TotalSize, offset);
EXPECT_EQ(block_info_.n_pieces, fpm.pieceSpan(std::size(FileSizes) - 1).end);
}
TEST_F(FilePieceMapTest, priorities)
{
auto const fpm = tr_file_piece_map{ block_info_, std::data(FileSizes), std::size(FileSizes) };
auto file_priorities = tr_file_priorities(&fpm);
tr_file_index_t const n_files = std::size(FileSizes);
// make a helper to compare file & piece priorities
auto expected_file_priorities = std::vector<tr_priority_t>(n_files, TR_PRI_NORMAL);
auto expected_piece_priorities = std::vector<tr_priority_t>(block_info_.n_pieces, TR_PRI_NORMAL);
auto const compare_to_expected = [&, this]()
{
for (tr_file_index_t i = 0; i < n_files; ++i)
{
EXPECT_EQ(int(expected_file_priorities[i]), int(file_priorities.filePriority(i)));
}
for (tr_piece_index_t i = 0; i < block_info_.n_pieces; ++i)
{
EXPECT_EQ(int(expected_piece_priorities[i]), int(file_priorities.piecePriority(i)));
}
};
// check default priority is normal
compare_to_expected();
// set the first file as high priority.
// since this begins and ends on a piece boundary,
// this shouldn't affect any other files' pieces
auto pri = TR_PRI_HIGH;
file_priorities.set(0, pri);
expected_file_priorities[0] = pri;
for (size_t i = 0; i < 5; ++i)
{
expected_piece_priorities[i] = pri;
}
compare_to_expected();
// This file shares a piece with another file.
// If _either_ is set to high, the piece's priority should be high.
// file #5: byte [500..550) piece [5, 6)
// file #6: byte [550..650) piece [5, 7)
//
// first test setting file #5...
pri = TR_PRI_HIGH;
file_priorities.set(5, pri);
expected_file_priorities[5] = pri;
expected_piece_priorities[5] = pri;
compare_to_expected();
// ...and that shared piece should still be the same when both are high...
file_priorities.set(6, pri);
expected_file_priorities[6] = pri;
expected_piece_priorities[5] = pri;
expected_piece_priorities[6] = pri;
compare_to_expected();
// ...and that shared piece should still be the same when only 6 is high...
pri = TR_PRI_NORMAL;
file_priorities.set(5, pri);
expected_file_priorities[5] = pri;
compare_to_expected();
// setup for the next test: set all files to low priority
pri = TR_PRI_LOW;
for (tr_file_index_t i = 0; i < n_files; ++i)
{
file_priorities.set(i, pri);
}
std::fill(std::begin(expected_file_priorities), std::end(expected_file_priorities), pri);
std::fill(std::begin(expected_piece_priorities), std::end(expected_piece_priorities), pri);
compare_to_expected();
// Raise the priority of a small 1-piece file.
// Since it's the highest priority in the piece, piecePriority() should return its value.
// file #8: byte [650, 659) piece [6, 7)
pri = TR_PRI_NORMAL;
file_priorities.set(8, pri);
expected_file_priorities[8] = pri;
expected_piece_priorities[6] = pri;
compare_to_expected();
// Raise the priority of another small 1-piece file in the same piece.
// Since _it_ now has the highest priority in the piece, piecePriority should return _its_ value.
// file #9: byte [659, 667) piece [6, 7)
pri = TR_PRI_HIGH;
file_priorities.set(9, pri);
expected_file_priorities[9] = pri;
expected_piece_priorities[6] = pri;
compare_to_expected();
// Prep for the next test: set all files to normal priority
pri = TR_PRI_NORMAL;
for (tr_file_index_t i = 0; i < n_files; ++i)
{
file_priorities.set(i, pri);
}
std::fill(std::begin(expected_file_priorities), std::end(expected_file_priorities), pri);
std::fill(std::begin(expected_piece_priorities), std::end(expected_piece_priorities), pri);
compare_to_expected();
// *Sigh* OK what happens to piece priorities if you set the priority
// of a zero-byte file. Arguably nothing should happen since you can't
// download an empty file. But that would complicate the code for a
// pretty stupid use case, and treating 0-sized files the same as any
// other does no real harm. Let's KISS.
//
// Check that even zero-sized files can change a piece's priority
// file #1: byte [500, 500) piece [5, 6)
pri = TR_PRI_HIGH;
file_priorities.set(1, pri);
expected_file_priorities[1] = pri;
expected_piece_priorities[5] = pri;
compare_to_expected();
// Check that zero-sized files at the end of a torrent change the last piece's priority.
// file #16 byte [1001, 1001) piece [10, 11)
file_priorities.set(16, pri);
expected_file_priorities[16] = pri;
expected_piece_priorities[10] = pri;
compare_to_expected();
// test the batch API
auto file_indices = std::vector<tr_file_index_t>(n_files);
std::iota(std::begin(file_indices), std::end(file_indices), 0);
pri = TR_PRI_HIGH;
file_priorities.set(std::data(file_indices), std::size(file_indices), pri);
std::fill(std::begin(expected_file_priorities), std::end(expected_file_priorities), pri);
std::fill(std::begin(expected_piece_priorities), std::end(expected_piece_priorities), pri);
compare_to_expected();
pri = TR_PRI_LOW;
file_priorities.set(std::data(file_indices), std::size(file_indices), pri);
std::fill(std::begin(expected_file_priorities), std::end(expected_file_priorities), pri);
std::fill(std::begin(expected_piece_priorities), std::end(expected_piece_priorities), pri);
compare_to_expected();
}
TEST_F(FilePieceMapTest, wanted)
{
auto const fpm = tr_file_piece_map{ block_info_, std::data(FileSizes), std::size(FileSizes) };
auto files_wanted = tr_files_wanted(&fpm);
tr_file_index_t const n_files = std::size(FileSizes);
// make a helper to compare file & piece priorities
auto expected_files_wanted = tr_bitfield(n_files);
auto expected_pieces_wanted = tr_bitfield(block_info_.n_pieces);
auto const compare_to_expected = [&, this]()
{
for (tr_file_index_t i = 0; i < n_files; ++i)
{
EXPECT_EQ(int(expected_files_wanted.test(i)), int(files_wanted.fileWanted(i)));
}
for (tr_piece_index_t i = 0; i < block_info_.n_pieces; ++i)
{
EXPECT_EQ(int(expected_pieces_wanted.test(i)), int(files_wanted.pieceWanted(i)));
}
};
// check everything is wanted by default
expected_files_wanted.setHasAll();
expected_pieces_wanted.setHasAll();
compare_to_expected();
// set the first file as not wanted.
// since this begins and ends on a piece boundary,
// this shouldn't affect any other files' pieces
bool wanted = false;
files_wanted.set(0, wanted);
expected_files_wanted.set(0, wanted);
expected_pieces_wanted.setSpan(0, 5, wanted);
compare_to_expected();
// now test when a piece has >1 file.
// if *any* file in that piece is wanted, then we want the piece too.
// file #1: byte [100..100) piece [5, 6) (zero-byte file)
// file #2: byte [100..100) piece [5, 6) (zero-byte file)
// file #3: byte [100..100) piece [5, 6) (zero-byte file)
// file #4: byte [100..100) piece [5, 6) (zero-byte file)
// file #5: byte [500..550) piece [5, 6)
// file #6: byte [550..650) piece [5, 7)
//
// first test setting file #5...
files_wanted.set(5, false);
expected_files_wanted.unset(5);
compare_to_expected();
// marking all the files in the piece as unwanted
// should cause the piece to become unwanted
files_wanted.set(1, false);
files_wanted.set(2, false);
files_wanted.set(3, false);
files_wanted.set(4, false);
files_wanted.set(5, false);
files_wanted.set(6, false);
expected_files_wanted.setSpan(1, 7, false);
expected_pieces_wanted.unset(5);
compare_to_expected();
// but as soon as any of them is turned back to wanted,
// the piece should pop back.
files_wanted.set(6, true);
expected_files_wanted.set(6, true);
expected_pieces_wanted.set(5);
compare_to_expected();
files_wanted.set(5, true);
files_wanted.set(6, false);
expected_files_wanted.set(5);
expected_files_wanted.unset(6);
compare_to_expected();
files_wanted.set(4, true);
files_wanted.set(5, false);
expected_files_wanted.set(4);
expected_files_wanted.unset(5);
compare_to_expected();
// Prep for the next test: set all files to unwanted priority
for (tr_file_index_t i = 0; i < n_files; ++i)
{
files_wanted.set(i, false);
}
expected_files_wanted.setHasNone();
expected_pieces_wanted.setHasNone();
compare_to_expected();
// *Sigh* OK what happens to files_wanted if you say the only
// file you want is a zero-byte file? Arguably nothing should happen
// since you can't download a zero-byte file. But that would complicate
// the coe for a stupid use case, so let's KISS.
//
// Check that even zero-sized files can change a file's 'wanted' state
// file #1: byte [500, 500) piece [5, 6)
files_wanted.set(1, true);
expected_files_wanted.set(1);
expected_pieces_wanted.set(5);
compare_to_expected();
// Check that zero-sized files at the end of a torrent change the last piece's state.
// file #16 byte [1001, 1001) piece [10, 11)
files_wanted.set(16, true);
expected_files_wanted.set(16);
expected_pieces_wanted.set(10);
compare_to_expected();
// test the batch API
auto file_indices = std::vector<tr_file_index_t>(n_files);
std::iota(std::begin(file_indices), std::end(file_indices), 0);
files_wanted.set(std::data(file_indices), std::size(file_indices), true);
expected_files_wanted.setHasAll();
expected_pieces_wanted.setHasAll();
compare_to_expected();
files_wanted.set(std::data(file_indices), std::size(file_indices), false);
expected_files_wanted.setHasNone();
expected_pieces_wanted.setHasNone();
compare_to_expected();
}