mirror of
https://github.com/transmission/transmission.git
synced 2025-12-12 20:35:49 +01:00
refactor: dedicated class for torrent queue (#7332)
* Revert "feat: save queue order between sessions (#6753)"
This reverts commit 4db50dae10.
* refactor: new torrent queue class
* refactor: replace queue code with new class
* test: new tests for new class
* feat: store and load queue order across sessions
* build: xcode
* refactor: use set_difference instead of unordered_set
* code review: use `tr_torrent_id_t` as key
* fix: don't overflow when moving up/down
---------
Co-authored-by: Dzmitry Neviadomski <nevack.d@gmail.com>
This commit is contained in:
@@ -454,6 +454,8 @@
|
||||
ED8A16412735A8AA000D61F9 /* peer-mgr-wishlist.h in Headers */ = {isa = PBXBuildFile; fileRef = ED8A163D2735A8AA000D61F9 /* peer-mgr-wishlist.h */; };
|
||||
ED8A16422735A8AA000D61F9 /* peer-mgr-wishlist.cc in Sources */ = {isa = PBXBuildFile; fileRef = ED8A163E2735A8AA000D61F9 /* peer-mgr-wishlist.cc */; };
|
||||
ED9862972B979AA2002F3035 /* Utils.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED9862962B979AA2002F3035 /* Utils.mm */; };
|
||||
EDBA61FF2D4180D5001470F8 /* torrent-queue.h in Headers */ = {isa = PBXBuildFile; fileRef = EDBA61FD2D4180D5001470F8 /* torrent-queue.h */; };
|
||||
EDBA62002D4180D5001470F8 /* torrent-queue.cc in Sources */ = {isa = PBXBuildFile; fileRef = EDBA61FE2D4180D5001470F8 /* torrent-queue.cc */; };
|
||||
EDBAAC8C29E486BC00D9495F /* ip-cache.h in Headers */ = {isa = PBXBuildFile; fileRef = EDBAAC8B29E486BC00D9495F /* ip-cache.h */; };
|
||||
EDBAAC8E29E486C200D9495F /* ip-cache.cc in Sources */ = {isa = PBXBuildFile; fileRef = EDBAAC8D29E486C200D9495F /* ip-cache.cc */; };
|
||||
EDBDFA9E25AFCCA60093D9C1 /* evutil_time.c in Sources */ = {isa = PBXBuildFile; fileRef = EDBDFA9D25AFCCA60093D9C1 /* evutil_time.c */; };
|
||||
@@ -1372,6 +1374,8 @@
|
||||
ED8A163E2735A8AA000D61F9 /* peer-mgr-wishlist.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "peer-mgr-wishlist.cc"; sourceTree = "<group>"; };
|
||||
ED9862952B979AA2002F3035 /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utils.h; sourceTree = "<group>"; };
|
||||
ED9862962B979AA2002F3035 /* Utils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Utils.mm; sourceTree = "<group>"; };
|
||||
EDBA61FD2D4180D5001470F8 /* torrent-queue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "torrent-queue.h"; sourceTree = "<group>"; };
|
||||
EDBA61FE2D4180D5001470F8 /* torrent-queue.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "torrent-queue.cc"; sourceTree = "<group>"; };
|
||||
EDBAAC8B29E486BC00D9495F /* ip-cache.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "ip-cache.h"; sourceTree = "<group>"; };
|
||||
EDBAAC8D29E486C200D9495F /* ip-cache.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "ip-cache.cc"; sourceTree = "<group>"; };
|
||||
EDBDFA9D25AFCCA60093D9C1 /* evutil_time.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = evutil_time.c; sourceTree = "<group>"; };
|
||||
@@ -1903,6 +1907,8 @@
|
||||
4D8017E910BBC073008A4AF2 /* torrent-magnet.h */,
|
||||
0A89346B736DBCF81F3A4851 /* torrent-metainfo.cc */,
|
||||
0A89346B736DBCF81F3A4853 /* torrent-metainfo.h */,
|
||||
EDBA61FD2D4180D5001470F8 /* torrent-queue.h */,
|
||||
EDBA61FE2D4180D5001470F8 /* torrent-queue.cc */,
|
||||
BEFC1DF90C07861A00B0BB3C /* torrent.cc */,
|
||||
A29DF8B80DB2544C00D04E5A /* torrent.h */,
|
||||
2B9BA6C508B488FE586A0AB1 /* torrents.cc */,
|
||||
@@ -2407,6 +2413,7 @@
|
||||
4D36BA780CA2F00800A63CA5 /* peer-mgr.h in Headers */,
|
||||
4D36BA7A0CA2F00800A63CA5 /* peer-msgs.h in Headers */,
|
||||
C11DEA171FCD31C0009E22B9 /* subprocess.h in Headers */,
|
||||
EDBA61FF2D4180D5001470F8 /* torrent-queue.h in Headers */,
|
||||
A25D2CBE0CF4C73E0096A262 /* stats.h in Headers */,
|
||||
C1033E0A1A3279B800EF44D8 /* crypto-utils.h in Headers */,
|
||||
C17740D6273A002C00E455D2 /* web-utils.h in Headers */,
|
||||
@@ -3213,6 +3220,7 @@
|
||||
C17740D5273A002C00E455D2 /* web-utils.cc in Sources */,
|
||||
A2679294130E00A000CB7464 /* tr-utp.cc in Sources */,
|
||||
A23F29A2132A447400E9A83B /* announcer-http.cc in Sources */,
|
||||
EDBA62002D4180D5001470F8 /* torrent-queue.cc in Sources */,
|
||||
C1FEE5791C3223CC00D62832 /* watchdir-kqueue.cc in Sources */,
|
||||
A2AA9BE1132CAC8E00FA131E /* announcer-udp.cc in Sources */,
|
||||
A25BFD69167BED3B0039D1AA /* variant-benc.cc in Sources */,
|
||||
|
||||
@@ -134,6 +134,8 @@ target_sources(${TR_NAME}
|
||||
torrent-magnet.h
|
||||
torrent-metainfo.cc
|
||||
torrent-metainfo.h
|
||||
torrent-queue.cc
|
||||
torrent-queue.h
|
||||
torrent.cc
|
||||
torrent.h
|
||||
torrents.cc
|
||||
|
||||
@@ -170,7 +170,6 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
|
||||
"isStalled"sv,
|
||||
"isUTP"sv,
|
||||
"isUploadingTo"sv,
|
||||
"is_queued"sv,
|
||||
"labels"sv,
|
||||
"lastAnnouncePeerCount"sv,
|
||||
"lastAnnounceResult"sv,
|
||||
|
||||
@@ -172,7 +172,6 @@ enum
|
||||
TR_KEY_isStalled,
|
||||
TR_KEY_isUTP,
|
||||
TR_KEY_isUploadingTo,
|
||||
TR_KEY_is_queued,
|
||||
TR_KEY_labels,
|
||||
TR_KEY_lastAnnouncePeerCount,
|
||||
TR_KEY_lastAnnounceResult,
|
||||
|
||||
@@ -412,44 +412,6 @@ tr_resume::fields_t loadFilenames(tr_variant* dict, tr_torrent* tor)
|
||||
|
||||
// ---
|
||||
|
||||
void saveQueueState(tr_variant* dict, tr_torrent const* tor)
|
||||
{
|
||||
auto* const map = dict->get_if<tr_variant::Map>();
|
||||
if (map == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
map->try_emplace(TR_KEY_queuePosition, tor->queue_position());
|
||||
map->try_emplace(TR_KEY_is_queued, tor->is_queued(tor->queue_direction()));
|
||||
}
|
||||
|
||||
auto loadQueueState(tr_variant* dict, tr_torrent* tor, tr_torrent::ResumeHelper& helper)
|
||||
{
|
||||
auto ret = tr_resume::fields_t{};
|
||||
auto const* const map = dict->get_if<tr_variant::Map>();
|
||||
if (map == nullptr)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (auto val = map->value_if<int64_t>(TR_KEY_queuePosition))
|
||||
{
|
||||
helper.load_queue_position(*val);
|
||||
ret = tr_resume::QueueState;
|
||||
}
|
||||
|
||||
if (auto val = map->value_if<bool>(TR_KEY_is_queued))
|
||||
{
|
||||
tor->set_is_queued(*val);
|
||||
ret = tr_resume::QueueState;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
void bitfieldToRaw(tr_bitfield const& b, tr_variant* benc)
|
||||
{
|
||||
if (b.has_none() || std::empty(b))
|
||||
@@ -830,11 +792,6 @@ tr_resume::fields_t load_from_file(tr_torrent* tor, tr_torrent::ResumeHelper& he
|
||||
fields_loaded |= loadGroup(&top, tor);
|
||||
}
|
||||
|
||||
if ((fields_to_load & tr_resume::QueueState) != 0)
|
||||
{
|
||||
fields_loaded |= loadQueueState(&top, tor, helper);
|
||||
}
|
||||
|
||||
return fields_loaded;
|
||||
}
|
||||
|
||||
@@ -956,7 +913,6 @@ void save(tr_torrent* const tor, tr_torrent::ResumeHelper const& helper)
|
||||
saveName(&top, tor);
|
||||
saveLabels(&top, tor);
|
||||
saveGroup(&top, tor);
|
||||
saveQueueState(&top, tor);
|
||||
|
||||
auto serde = tr_variant_serde::benc();
|
||||
if (!serde.to_file(top, tor->resume_file()))
|
||||
|
||||
@@ -45,7 +45,6 @@ auto inline constexpr Name = fields_t{ 1 << 21 };
|
||||
auto inline constexpr Labels = fields_t{ 1 << 22 };
|
||||
auto inline constexpr Group = fields_t{ 1 << 23 };
|
||||
auto inline constexpr SequentialDownload = fields_t{ 1 << 24 };
|
||||
auto inline constexpr QueueState = fields_t{ 1 << 25 };
|
||||
|
||||
auto inline constexpr All = ~fields_t{ 0 };
|
||||
|
||||
|
||||
@@ -245,6 +245,14 @@ void tr_session::DhtMediator::add_pex(tr_sha1_digest_t const& info_hash, tr_pex
|
||||
|
||||
// ---
|
||||
|
||||
std::string tr_session::QueueMediator::store_filename(tr_torrent_id_t id) const
|
||||
{
|
||||
auto const* const tor = session_.torrents().get(id);
|
||||
return tor != nullptr ? tor->store_filename() : std::string{};
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
bool tr_session::LpdMediator::onPeerFound(std::string_view info_hash_str, tr_address address, tr_port port)
|
||||
{
|
||||
auto const digest = tr_sha1_from_string(info_hash_str);
|
||||
@@ -1336,6 +1344,8 @@ void tr_session::closeImplPart1(std::promise<void>* closed_promise, std::chrono:
|
||||
bound_ipv6_.reset();
|
||||
bound_ipv4_.reset();
|
||||
|
||||
torrent_queue().to_file();
|
||||
|
||||
// Close the torrents in order of most active to least active
|
||||
// so that the most important announce=stopped events are
|
||||
// fired out first...
|
||||
@@ -1424,32 +1434,58 @@ namespace
|
||||
{
|
||||
namespace load_torrents_helpers
|
||||
{
|
||||
auto get_remaining_files(std::string_view folder, std::vector<std::string>& queue_order)
|
||||
{
|
||||
auto files = tr_sys_dir_get_files(folder);
|
||||
auto ret = std::vector<std::string>{};
|
||||
ret.reserve(std::size(files));
|
||||
std::sort(std::begin(queue_order), std::end(queue_order));
|
||||
std::sort(std::begin(files), std::end(files));
|
||||
|
||||
std::set_difference(
|
||||
std::begin(files),
|
||||
std::end(files),
|
||||
std::begin(queue_order),
|
||||
std::end(queue_order),
|
||||
std::back_inserter(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
void session_load_torrents(tr_session* session, tr_ctor* ctor, std::promise<size_t>* loaded_promise)
|
||||
{
|
||||
auto n_torrents = size_t{};
|
||||
auto const& folder = session->torrentDir();
|
||||
|
||||
for (auto const& name : tr_sys_dir_get_files(folder, [](auto name) { return tr_strv_ends_with(name, ".torrent"sv); }))
|
||||
auto load_func = [&folder, &n_torrents, ctor, buf = std::vector<char>{}](std::string_view name) mutable
|
||||
{
|
||||
auto const path = tr_pathbuf{ folder, '/', name };
|
||||
|
||||
if (ctor->set_metainfo_from_file(path.sv()) && tr_torrentNew(ctor, nullptr) != nullptr)
|
||||
if (tr_strv_ends_with(name, ".torrent"sv))
|
||||
{
|
||||
++n_torrents;
|
||||
auto const path = tr_pathbuf{ folder, '/', name };
|
||||
if (ctor->set_metainfo_from_file(path.sv()) && tr_torrentNew(ctor, nullptr) != nullptr)
|
||||
{
|
||||
++n_torrents;
|
||||
}
|
||||
}
|
||||
else if (tr_strv_ends_with(name, ".magnet"sv))
|
||||
{
|
||||
auto const path = tr_pathbuf{ folder, '/', name };
|
||||
if (tr_file_read(path, buf) &&
|
||||
ctor->set_metainfo_from_magnet_link(std::string_view{ std::data(buf), std::size(buf) }, nullptr) &&
|
||||
tr_torrentNew(ctor, nullptr) != nullptr)
|
||||
{
|
||||
++n_torrents;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto queue_order = session->torrent_queue().from_file();
|
||||
for (auto const& filename : queue_order)
|
||||
{
|
||||
load_func(filename);
|
||||
}
|
||||
|
||||
auto buf = std::vector<char>{};
|
||||
for (auto const& name : tr_sys_dir_get_files(folder, [](auto name) { return tr_strv_ends_with(name, ".magnet"sv); }))
|
||||
for (auto const& filename : get_remaining_files(folder, queue_order))
|
||||
{
|
||||
auto const path = tr_pathbuf{ folder, '/', name };
|
||||
|
||||
if (tr_file_read(path, buf) &&
|
||||
ctor->set_metainfo_from_magnet_link(std::string_view{ std::data(buf), std::size(buf) }, nullptr) &&
|
||||
tr_torrentNew(ctor, nullptr) != nullptr)
|
||||
{
|
||||
++n_torrents;
|
||||
}
|
||||
load_func(filename);
|
||||
}
|
||||
|
||||
if (n_torrents != 0U)
|
||||
@@ -2130,6 +2166,7 @@ void tr_session::addIncoming(tr_peer_socket&& socket)
|
||||
void tr_session::addTorrent(tr_torrent* tor)
|
||||
{
|
||||
tor->init_id(torrents().add(tor));
|
||||
torrent_queue_.add(tor->id());
|
||||
|
||||
tr_peerMgrAddTorrent(peer_mgr_.get(), tor);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
#include "libtransmission/settings.h"
|
||||
#include "libtransmission/stats.h"
|
||||
#include "libtransmission/timer.h"
|
||||
#include "libtransmission/torrent-queue.h"
|
||||
#include "libtransmission/torrents.h"
|
||||
#include "libtransmission/tr-assert.h"
|
||||
#include "libtransmission/tr-dht.h"
|
||||
@@ -230,6 +231,25 @@ private:
|
||||
tr_session& session_;
|
||||
};
|
||||
|
||||
class QueueMediator final : public tr_torrent_queue::Mediator
|
||||
{
|
||||
public:
|
||||
explicit QueueMediator(tr_session& session) noexcept
|
||||
: session_{ session }
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string config_dir() const override
|
||||
{
|
||||
return session_.configDir();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string store_filename(tr_torrent_id_t id) const override;
|
||||
|
||||
private:
|
||||
tr_session& session_;
|
||||
};
|
||||
|
||||
class WebMediator final : public tr_web::Mediator
|
||||
{
|
||||
public:
|
||||
@@ -525,16 +545,26 @@ public:
|
||||
return session_thread_->event_base();
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto& torrents()
|
||||
[[nodiscard]] constexpr tr_torrents& torrents()
|
||||
{
|
||||
return torrents_;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto const& torrents() const
|
||||
[[nodiscard]] constexpr tr_torrents const& torrents() const
|
||||
{
|
||||
return torrents_;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto& torrent_queue()
|
||||
{
|
||||
return torrent_queue_;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto const& torrent_queue() const
|
||||
{
|
||||
return torrent_queue_;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto unique_lock() const
|
||||
{
|
||||
return std::unique_lock(session_mutex_);
|
||||
@@ -547,7 +577,7 @@ public:
|
||||
|
||||
// paths
|
||||
|
||||
[[nodiscard]] constexpr auto const& configDir() const noexcept
|
||||
[[nodiscard]] constexpr std::string const& configDir() const noexcept
|
||||
{
|
||||
return config_dir_;
|
||||
}
|
||||
@@ -1263,6 +1293,9 @@ private:
|
||||
|
||||
libtransmission::Blocklists blocklists_;
|
||||
|
||||
QueueMediator torrent_queue_mediator_{ *this };
|
||||
tr_torrent_queue torrent_queue_{ torrent_queue_mediator_ };
|
||||
|
||||
private:
|
||||
/// other fields
|
||||
|
||||
|
||||
@@ -663,9 +663,9 @@ std::string tr_torrent_metainfo::make_filename(
|
||||
BasenameFormat format,
|
||||
std::string_view suffix)
|
||||
{
|
||||
// `${dirname}/${name}.${info_hash}${suffix}`
|
||||
// `${dirname}/${info_hash}${suffix}`
|
||||
auto filename = tr_pathbuf{ dirname, '/' };
|
||||
// `[${dirname}/]${name}.${info_hash}${suffix}`
|
||||
// `[${dirname}/]${info_hash}${suffix}`
|
||||
auto filename = std::empty(dirname) ? tr_pathbuf{} : tr_pathbuf{ dirname, '/' };
|
||||
if (format == BasenameFormat::Hash)
|
||||
{
|
||||
filename.append(info_hash_string);
|
||||
|
||||
@@ -167,17 +167,17 @@ public:
|
||||
|
||||
// UTILS
|
||||
|
||||
[[nodiscard]] auto torrent_file(std::string_view torrent_dir) const
|
||||
[[nodiscard]] auto torrent_file(std::string_view torrent_dir = {}) const
|
||||
{
|
||||
return make_filename(torrent_dir, name(), info_hash_string(), BasenameFormat::Hash, ".torrent");
|
||||
}
|
||||
|
||||
[[nodiscard]] auto magnet_file(std::string_view torrent_dir) const
|
||||
[[nodiscard]] auto magnet_file(std::string_view torrent_dir = {}) const
|
||||
{
|
||||
return make_filename(torrent_dir, name(), info_hash_string(), BasenameFormat::Hash, ".magnet");
|
||||
}
|
||||
|
||||
[[nodiscard]] auto resume_file(std::string_view resume_dir) const
|
||||
[[nodiscard]] auto resume_file(std::string_view resume_dir = {}) const
|
||||
{
|
||||
return make_filename(resume_dir, name(), info_hash_string(), BasenameFormat::Hash, ".resume");
|
||||
}
|
||||
|
||||
134
libtransmission/torrent-queue.cc
Normal file
134
libtransmission/torrent-queue.cc
Normal file
@@ -0,0 +1,134 @@
|
||||
// This file Copyright © Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "libtransmission/torrent-queue.h"
|
||||
#include "libtransmission/tr-strbuf.h"
|
||||
#include "libtransmission/variant.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace std::literals;
|
||||
|
||||
[[nodiscard]] auto get_file_path(std::string_view config_dir) noexcept
|
||||
{
|
||||
return tr_pathbuf{ config_dir, '/', "queue.json"sv };
|
||||
}
|
||||
} // namespace
|
||||
|
||||
size_t tr_torrent_queue::add(tr_torrent_id_t const id)
|
||||
{
|
||||
queue_.push_back(id);
|
||||
return std::size(queue_) - 1U;
|
||||
}
|
||||
|
||||
void tr_torrent_queue::remove(tr_torrent_id_t const id)
|
||||
{
|
||||
auto const uid = static_cast<size_t>(id);
|
||||
auto const pos = uid < std::size(pos_cache_) ? pos_cache_[uid] : 0U;
|
||||
if (pos < std::size(queue_) && queue_[pos] == id)
|
||||
{
|
||||
queue_.erase(std::begin(queue_) + pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const remove_it = std::remove(std::begin(queue_), std::end(queue_), id);
|
||||
queue_.erase(remove_it, std::end(queue_));
|
||||
}
|
||||
}
|
||||
|
||||
size_t tr_torrent_queue::get_pos(tr_torrent_id_t const id)
|
||||
{
|
||||
auto const uid = static_cast<size_t>(id);
|
||||
if (auto n_cache = std::size(pos_cache_);
|
||||
uid >= n_cache || pos_cache_[uid] >= std::size(queue_) || id != queue_[pos_cache_[uid]])
|
||||
{
|
||||
auto const begin = std::begin(queue_);
|
||||
auto const end = std::end(queue_);
|
||||
auto it = std::find(begin, end, id);
|
||||
if (it == end)
|
||||
{
|
||||
return MaxQueuePosition;
|
||||
}
|
||||
|
||||
pos_cache_.resize(std::max(uid + 1U, n_cache));
|
||||
pos_cache_[uid] = it - begin;
|
||||
}
|
||||
|
||||
return pos_cache_[uid];
|
||||
}
|
||||
|
||||
void tr_torrent_queue::set_pos(tr_torrent_id_t const id, size_t new_pos)
|
||||
{
|
||||
auto const old_pos = get_pos(id);
|
||||
auto const n_queue = std::size(queue_);
|
||||
if (old_pos >= n_queue || queue_[old_pos] != id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
new_pos = std::min(new_pos, n_queue - 1U);
|
||||
|
||||
if (old_pos == new_pos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto const begin = std::begin(queue_);
|
||||
auto const old_it = std::next(begin, old_pos);
|
||||
auto const next_it = std::next(old_it);
|
||||
auto const new_it = std::next(begin, new_pos);
|
||||
if (old_pos > new_pos)
|
||||
{
|
||||
std::rotate(new_it, old_it, next_it);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::rotate(old_it, next_it, std::next(new_it));
|
||||
}
|
||||
}
|
||||
|
||||
bool tr_torrent_queue::to_file() const
|
||||
{
|
||||
auto vec = tr_variant::Vector{};
|
||||
vec.reserve(std::size(queue_));
|
||||
for (auto const id : queue_)
|
||||
{
|
||||
vec.emplace_back(mediator_.store_filename(id));
|
||||
}
|
||||
|
||||
return tr_variant_serde::json().to_file(std::move(vec), get_file_path(mediator_.config_dir()));
|
||||
}
|
||||
|
||||
std::vector<std::string> tr_torrent_queue::from_file()
|
||||
{
|
||||
auto top = tr_variant_serde::json().parse_file(get_file_path(mediator_.config_dir()));
|
||||
if (!top)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto const* const vec = top->get_if<tr_variant::Vector>();
|
||||
if (vec == nullptr)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto ret = std::vector<std::string>{};
|
||||
ret.reserve(std::size(*vec));
|
||||
for (auto const& var : *vec)
|
||||
{
|
||||
if (auto file = var.value_if<std::string_view>(); file)
|
||||
{
|
||||
ret.emplace_back(*file);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
56
libtransmission/torrent-queue.h
Normal file
56
libtransmission/torrent-queue.h
Normal file
@@ -0,0 +1,56 @@
|
||||
// This file Copyright © Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __TRANSMISSION__
|
||||
#error only libtransmission should #include this header.
|
||||
#endif
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "libtransmission/transmission.h"
|
||||
|
||||
class tr_torrent_queue
|
||||
{
|
||||
public:
|
||||
struct Mediator
|
||||
{
|
||||
virtual ~Mediator() = default;
|
||||
|
||||
[[nodiscard]] virtual std::string config_dir() const = 0;
|
||||
[[nodiscard]] virtual std::string store_filename(tr_torrent_id_t id) const = 0;
|
||||
};
|
||||
|
||||
explicit tr_torrent_queue(Mediator const& mediator)
|
||||
: mediator_{ mediator }
|
||||
{
|
||||
}
|
||||
tr_torrent_queue(tr_torrent_queue const&) = delete;
|
||||
tr_torrent_queue(tr_torrent_queue&&) = delete;
|
||||
tr_torrent_queue& operator=(tr_torrent_queue const&) = delete;
|
||||
tr_torrent_queue& operator=(tr_torrent_queue&&) = delete;
|
||||
|
||||
size_t add(tr_torrent_id_t id);
|
||||
void remove(tr_torrent_id_t id);
|
||||
|
||||
[[nodiscard]] size_t get_pos(tr_torrent_id_t id);
|
||||
void set_pos(tr_torrent_id_t id, size_t new_pos);
|
||||
|
||||
bool to_file() const;
|
||||
[[nodiscard]] std::vector<std::string> from_file();
|
||||
|
||||
static auto constexpr MinQueuePosition = size_t{};
|
||||
static auto constexpr MaxQueuePosition = ~size_t{};
|
||||
|
||||
private:
|
||||
std::vector<tr_torrent_id_t> queue_;
|
||||
std::vector<size_t> pos_cache_;
|
||||
|
||||
Mediator const& mediator_;
|
||||
};
|
||||
@@ -6,10 +6,8 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cerrno> // EINVAL
|
||||
#include <climits> /* INT_MAX */
|
||||
#include <cstddef> // size_t
|
||||
#include <ctime>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -464,132 +462,59 @@ void tr_torrent::stop_if_seed_limit_reached()
|
||||
|
||||
// --- Queue
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace queue_helpers
|
||||
{
|
||||
constexpr auto MinQueuePosition = std::numeric_limits<size_t>::min();
|
||||
constexpr auto MaxQueuePosition = std::numeric_limits<size_t>::max();
|
||||
|
||||
#ifdef TR_ENABLE_ASSERTS
|
||||
[[nodiscard]] bool torrents_are_sorted_by_queue_position(std::vector<tr_torrent*> torrents)
|
||||
{
|
||||
std::sort(std::begin(torrents), std::end(torrents), tr_torrent::CompareQueuePosition);
|
||||
|
||||
for (size_t idx = 0, end_idx = std::size(torrents); idx < end_idx; ++idx)
|
||||
{
|
||||
if (torrents[idx]->queue_position() != idx)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
} // namespace queue_helpers
|
||||
} // namespace
|
||||
|
||||
size_t tr_torrentGetQueuePosition(tr_torrent const* tor)
|
||||
{
|
||||
return tor->queue_position();
|
||||
}
|
||||
|
||||
void tr_torrent::set_unique_queue_position(size_t const new_pos)
|
||||
{
|
||||
using namespace queue_helpers;
|
||||
|
||||
auto max_pos = size_t{};
|
||||
auto const old_pos = queue_position_;
|
||||
|
||||
auto& torrents = session->torrents();
|
||||
for (auto* const walk : torrents)
|
||||
{
|
||||
if (walk == this)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((old_pos < new_pos) && (old_pos < walk->queue_position_) && (walk->queue_position_ <= new_pos))
|
||||
{
|
||||
--walk->queue_position_;
|
||||
walk->mark_changed();
|
||||
walk->set_dirty();
|
||||
}
|
||||
|
||||
if ((old_pos > new_pos) && (new_pos <= walk->queue_position_) && (walk->queue_position_ < old_pos))
|
||||
{
|
||||
++walk->queue_position_;
|
||||
walk->mark_changed();
|
||||
walk->set_dirty();
|
||||
}
|
||||
|
||||
max_pos = std::max(max_pos, walk->queue_position_);
|
||||
}
|
||||
|
||||
queue_position_ = std::min(new_pos, max_pos + 1);
|
||||
mark_changed();
|
||||
set_dirty();
|
||||
|
||||
TR_ASSERT(torrents_are_sorted_by_queue_position(torrents.get_all()));
|
||||
}
|
||||
|
||||
void tr_torrentSetQueuePosition(tr_torrent* tor, size_t queue_position)
|
||||
{
|
||||
tor->set_unique_queue_position(queue_position);
|
||||
tor->set_queue_position(queue_position);
|
||||
}
|
||||
|
||||
void tr_torrentsQueueMoveTop(tr_torrent* const* torrents_in, size_t torrent_count)
|
||||
{
|
||||
using namespace queue_helpers;
|
||||
|
||||
auto torrents = std::vector<tr_torrent*>(torrents_in, torrents_in + torrent_count);
|
||||
std::sort(std::rbegin(torrents), std::rend(torrents), tr_torrent::CompareQueuePosition);
|
||||
for (auto* const tor : torrents)
|
||||
{
|
||||
tor->set_unique_queue_position(MinQueuePosition);
|
||||
tor->set_queue_position(tr_torrent_queue::MinQueuePosition);
|
||||
}
|
||||
}
|
||||
|
||||
void tr_torrentsQueueMoveUp(tr_torrent* const* torrents_in, size_t torrent_count)
|
||||
{
|
||||
using namespace queue_helpers;
|
||||
|
||||
auto torrents = std::vector<tr_torrent*>(torrents_in, torrents_in + torrent_count);
|
||||
std::sort(std::begin(torrents), std::end(torrents), tr_torrent::CompareQueuePosition);
|
||||
for (auto* const tor : torrents)
|
||||
{
|
||||
if (auto const pos = tor->queue_position(); pos > MinQueuePosition)
|
||||
if (auto const pos = tor->queue_position(); pos > tr_torrent_queue::MinQueuePosition)
|
||||
{
|
||||
tor->set_unique_queue_position(pos - 1U);
|
||||
tor->set_queue_position(pos - 1U);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tr_torrentsQueueMoveDown(tr_torrent* const* torrents_in, size_t torrent_count)
|
||||
{
|
||||
using namespace queue_helpers;
|
||||
|
||||
auto torrents = std::vector<tr_torrent*>(torrents_in, torrents_in + torrent_count);
|
||||
std::sort(std::rbegin(torrents), std::rend(torrents), tr_torrent::CompareQueuePosition);
|
||||
for (auto* const tor : torrents)
|
||||
{
|
||||
if (auto const pos = tor->queue_position(); pos < MaxQueuePosition)
|
||||
if (auto const pos = tor->queue_position(); pos < tr_torrent_queue::MaxQueuePosition)
|
||||
{
|
||||
tor->set_unique_queue_position(pos + 1U);
|
||||
tor->set_queue_position(pos + 1U);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tr_torrentsQueueMoveBottom(tr_torrent* const* torrents_in, size_t torrent_count)
|
||||
{
|
||||
using namespace queue_helpers;
|
||||
|
||||
auto torrents = std::vector<tr_torrent*>(torrents_in, torrents_in + torrent_count);
|
||||
std::sort(std::begin(torrents), std::end(torrents), tr_torrent::CompareQueuePosition);
|
||||
for (auto* const tor : torrents)
|
||||
{
|
||||
tor->set_unique_queue_position(MaxQueuePosition);
|
||||
tor->set_queue_position(tr_torrent_queue::MaxQueuePosition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,8 +538,6 @@ bool removeTorrentFile(char const* filename, void* /*user_data*/, tr_error* erro
|
||||
|
||||
void freeTorrent(tr_torrent* tor)
|
||||
{
|
||||
using namespace queue_helpers;
|
||||
|
||||
auto const lock = tor->unique_lock();
|
||||
|
||||
TR_ASSERT(!tor->is_running());
|
||||
@@ -629,9 +552,7 @@ void freeTorrent(tr_torrent* tor)
|
||||
|
||||
if (!session->isClosing())
|
||||
{
|
||||
// move the torrent being freed to the end of the queue so that
|
||||
// all the torrents queued after it will move up one position
|
||||
tor->set_unique_queue_position(queue_helpers::MaxQueuePosition);
|
||||
session->torrent_queue().remove(tor->id());
|
||||
}
|
||||
|
||||
delete tor;
|
||||
@@ -970,8 +891,6 @@ void tr_torrent::init(tr_ctor const& ctor)
|
||||
|
||||
auto const now_sec = tr_time();
|
||||
|
||||
queue_position_ = std::size(session->torrents());
|
||||
|
||||
on_metainfo_updated();
|
||||
|
||||
if (auto dir = ctor.download_dir(TR_FORCE); !std::empty(dir))
|
||||
@@ -1058,11 +977,11 @@ void tr_torrent::init(tr_ctor const& ctor)
|
||||
[](auto mtime) { return mtime > 0; });
|
||||
}
|
||||
|
||||
auto const filename = has_metainfo() ? torrent_file() : magnet_file();
|
||||
auto const file_path = store_file();
|
||||
|
||||
// if we don't have a local .torrent or .magnet file already,
|
||||
// assume the torrent is new
|
||||
bool const is_new_torrent = !tr_sys_path_exists(filename);
|
||||
bool const is_new_torrent = !tr_sys_path_exists(file_path);
|
||||
|
||||
if (is_new_torrent)
|
||||
{
|
||||
@@ -1070,19 +989,19 @@ void tr_torrent::init(tr_ctor const& ctor)
|
||||
|
||||
if (has_metainfo()) // torrent file
|
||||
{
|
||||
ctor.save(filename, &error);
|
||||
ctor.save(file_path, &error);
|
||||
}
|
||||
else // magnet link
|
||||
{
|
||||
auto const magnet_link = magnet();
|
||||
tr_file_save(filename, magnet_link, &error);
|
||||
tr_file_save(file_path, magnet_link, &error);
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
this->error().set_local_error(fmt::format(
|
||||
_("Couldn't save '{path}': {error} ({error_code})"),
|
||||
fmt::arg("path", filename),
|
||||
fmt::arg("path", file_path),
|
||||
fmt::arg("error", error.message()),
|
||||
fmt::arg("error_code", error.code())));
|
||||
}
|
||||
@@ -2737,13 +2656,6 @@ void tr_torrent::ResumeHelper::load_incomplete_dir(std::string_view const dir) n
|
||||
|
||||
// ---
|
||||
|
||||
void tr_torrent::ResumeHelper::load_queue_position(size_t pos) noexcept
|
||||
{
|
||||
tor_.queue_position_ = pos;
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
void tr_torrent::ResumeHelper::load_start_when_stable(bool const val) noexcept
|
||||
{
|
||||
tor_.start_when_stable_ = val;
|
||||
|
||||
@@ -76,7 +76,6 @@ struct tr_torrent
|
||||
void load_date_done(time_t when) noexcept;
|
||||
void load_download_dir(std::string_view dir) noexcept;
|
||||
void load_incomplete_dir(std::string_view dir) noexcept;
|
||||
void load_queue_position(size_t pos) noexcept;
|
||||
void load_seconds_downloading_before_current_start(time_t when) noexcept;
|
||||
void load_seconds_seeding_before_current_start(time_t when) noexcept;
|
||||
void load_start_when_stable(bool val) noexcept;
|
||||
@@ -560,6 +559,21 @@ struct tr_torrent
|
||||
return metainfo_.date_created();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto torrent_filename() const
|
||||
{
|
||||
return metainfo_.torrent_file();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto magnet_filename() const
|
||||
{
|
||||
return metainfo_.magnet_file();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto store_filename() const
|
||||
{
|
||||
return has_metainfo() ? torrent_filename() : magnet_filename();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto torrent_file() const
|
||||
{
|
||||
return metainfo_.torrent_file(session->torrentDir());
|
||||
@@ -570,6 +584,11 @@ struct tr_torrent
|
||||
return metainfo_.magnet_file(session->torrentDir());
|
||||
}
|
||||
|
||||
[[nodiscard]] auto store_file() const
|
||||
{
|
||||
return has_metainfo() ? torrent_file() : magnet_file();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto resume_file() const
|
||||
{
|
||||
return metainfo_.resume_file(session->resumeDir());
|
||||
@@ -941,18 +960,21 @@ struct tr_torrent
|
||||
|
||||
// --- queue position
|
||||
|
||||
[[nodiscard]] constexpr auto queue_position() const noexcept
|
||||
[[nodiscard]] auto queue_position() const noexcept
|
||||
{
|
||||
return queue_position_;
|
||||
return session->torrent_queue().get_pos(id());
|
||||
}
|
||||
|
||||
void set_unique_queue_position(size_t new_pos);
|
||||
void set_queue_position(size_t new_pos)
|
||||
{
|
||||
session->torrent_queue().set_pos(id(), new_pos);
|
||||
}
|
||||
|
||||
static constexpr struct
|
||||
{
|
||||
constexpr bool operator()(tr_torrent const* a, tr_torrent const* b) const noexcept
|
||||
bool operator()(tr_torrent const* a, tr_torrent const* b) const noexcept
|
||||
{
|
||||
return a->queue_position_ < b->queue_position_;
|
||||
return a->queue_position() < b->queue_position();
|
||||
}
|
||||
} CompareQueuePosition{};
|
||||
|
||||
@@ -1344,8 +1366,6 @@ private:
|
||||
*/
|
||||
tr_peer_id_t peer_id_ = tr_peerIdInit();
|
||||
|
||||
size_t queue_position_ = 0;
|
||||
|
||||
time_t date_active_ = 0;
|
||||
time_t date_added_ = 0;
|
||||
time_t date_changed_ = 0;
|
||||
|
||||
@@ -49,6 +49,7 @@ target_sources(libtransmission-test
|
||||
torrent-files-test.cc
|
||||
torrent-magnet-test.cc
|
||||
torrent-metainfo-test.cc
|
||||
torrent-queue-test.cc
|
||||
torrents-test.cc
|
||||
tr-peer-info-test.cc
|
||||
utils-test.cc
|
||||
|
||||
167
tests/libtransmission/torrent-queue-test.cc
Normal file
167
tests/libtransmission/torrent-queue-test.cc
Normal file
@@ -0,0 +1,167 @@
|
||||
// This file Copyright © Mnemosyne LLC.
|
||||
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
||||
// or any future license endorsed by Mnemosyne LLC.
|
||||
// License text can be found in the licenses/ folder.
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <libtransmission/torrent-queue.h>
|
||||
#include <libtransmission/torrent.h>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "test-fixtures.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
struct TorrentQueueTest : public libtransmission::test::SandboxedTest
|
||||
{
|
||||
class MockMediator final : public tr_torrent_queue::Mediator
|
||||
{
|
||||
public:
|
||||
explicit MockMediator(TorrentQueueTest const& test)
|
||||
: test_{ test }
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string config_dir() const override
|
||||
{
|
||||
return test_.sandboxDir();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string store_filename(tr_torrent_id_t id) const override
|
||||
{
|
||||
if (auto it = test_.torrents_.find(id); it != std::end(test_.torrents_))
|
||||
{
|
||||
return it->second.store_filename();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
TorrentQueueTest const& test_;
|
||||
};
|
||||
|
||||
std::map<tr_torrent_id_t, tr_torrent const&> torrents_;
|
||||
|
||||
MockMediator mediator_{ *this };
|
||||
|
||||
static auto constexpr TorFilenames = std::array{
|
||||
"Android-x86 8.1 r6 iso.torrent"sv,
|
||||
"debian-11.2.0-amd64-DVD-1.iso.torrent"sv,
|
||||
"ubuntu-18.04.6-desktop-amd64.iso.torrent"sv,
|
||||
"ubuntu-20.04.4-desktop-amd64.iso.torrent"sv,
|
||||
};
|
||||
};
|
||||
|
||||
TEST_F(TorrentQueueTest, addRemoveToFromQueue)
|
||||
{
|
||||
auto queue = tr_torrent_queue{ mediator_ };
|
||||
|
||||
auto owned = std::vector<std::unique_ptr<tr_torrent>>{};
|
||||
for (auto const& name : TorFilenames)
|
||||
{
|
||||
auto const path = tr_pathbuf{ LIBTRANSMISSION_TEST_ASSETS_DIR, '/', name };
|
||||
auto tm = tr_torrent_metainfo{};
|
||||
EXPECT_TRUE(tm.parse_torrent_file(path));
|
||||
|
||||
auto& tor = owned.emplace_back(std::make_unique<tr_torrent>(std::move(tm)));
|
||||
tor->init_id(std::size(owned));
|
||||
torrents_.try_emplace(tor->id(), *tor);
|
||||
queue.add(tor->id());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < std::size(owned); ++i)
|
||||
{
|
||||
EXPECT_EQ(i, queue.get_pos(owned[i]->id()));
|
||||
}
|
||||
|
||||
queue.remove(owned[1]->id());
|
||||
queue.remove(owned[2]->id());
|
||||
owned.erase(std::begin(owned) + 1, std::begin(owned) + 3);
|
||||
for (size_t i = 0; i < std::size(owned); ++i)
|
||||
{
|
||||
EXPECT_EQ(i, queue.get_pos(owned[i]->id()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TorrentQueueTest, setQueuePos)
|
||||
{
|
||||
static auto constexpr QueuePos = std::array{ 1U, 3U, 0U, 2U };
|
||||
|
||||
auto queue = tr_torrent_queue{ mediator_ };
|
||||
|
||||
auto owned = std::vector<std::unique_ptr<tr_torrent>>{};
|
||||
for (auto const& name : TorFilenames)
|
||||
{
|
||||
auto const path = tr_pathbuf{ LIBTRANSMISSION_TEST_ASSETS_DIR, '/', name };
|
||||
auto tm = tr_torrent_metainfo{};
|
||||
EXPECT_TRUE(tm.parse_torrent_file(path));
|
||||
|
||||
auto& tor = owned.emplace_back(std::make_unique<tr_torrent>(std::move(tm)));
|
||||
tor->init_id(std::size(owned));
|
||||
torrents_.try_emplace(tor->id(), *tor);
|
||||
queue.add(tor->id());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < std::size(owned); ++i)
|
||||
{
|
||||
EXPECT_EQ(i, queue.get_pos(owned[i]->id()));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < std::size(owned); ++i)
|
||||
{
|
||||
auto const id = owned[i]->id();
|
||||
auto const pos = QueuePos[i];
|
||||
queue.set_pos(id, pos);
|
||||
EXPECT_EQ(queue.get_pos(id), pos);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < std::size(owned); ++i)
|
||||
{
|
||||
EXPECT_EQ(queue.get_pos(owned[i]->id()), QueuePos[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TorrentQueueTest, toFromFile)
|
||||
{
|
||||
static auto constexpr ExpectedContents =
|
||||
"[\n"
|
||||
" \"70341e8e1fe8778af23f6318ca75a22f8b1f1c05.torrent\",\n"
|
||||
" \"c9a337562cb0360fd6f5ab40fd2b1b81d5325dbd.torrent\",\n"
|
||||
" \"bc26c6bc83d0ca1a7bf9875df1ffc3fed81ff555.torrent\",\n"
|
||||
" \"f09c8d0884590088f4004e010a928f8b6178c2fd.torrent\"\n"
|
||||
"]"sv;
|
||||
|
||||
auto queue = tr_torrent_queue{ mediator_ };
|
||||
|
||||
auto owned = std::vector<std::unique_ptr<tr_torrent>>{};
|
||||
for (auto const& name : TorFilenames)
|
||||
{
|
||||
auto const path = tr_pathbuf{ LIBTRANSMISSION_TEST_ASSETS_DIR, '/', name };
|
||||
auto tm = tr_torrent_metainfo{};
|
||||
EXPECT_TRUE(tm.parse_torrent_file(path));
|
||||
|
||||
auto& tor = owned.emplace_back(std::make_unique<tr_torrent>(std::move(tm)));
|
||||
tor->init_id(std::size(owned));
|
||||
torrents_.try_emplace(tor->id(), *tor);
|
||||
queue.add(tor->id());
|
||||
}
|
||||
|
||||
queue.to_file();
|
||||
|
||||
auto f = std::ifstream{ sandboxDir() + "/queue.json" };
|
||||
auto const contents = std::string{ std::istreambuf_iterator{ f }, std::istreambuf_iterator<decltype(f)::char_type>{} };
|
||||
EXPECT_EQ(contents, ExpectedContents);
|
||||
f.close();
|
||||
|
||||
auto const filenames = queue.from_file();
|
||||
ASSERT_EQ(std::size(filenames), std::size(owned));
|
||||
for (size_t i = 0; i < std::size(filenames); ++i)
|
||||
{
|
||||
EXPECT_EQ(filenames[i], owned[i]->store_filename());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user