refactor: extract some file management into tr_files class (#2906)

This commit is contained in:
Charles Kerr 2022-04-12 10:00:02 -05:00 committed by GitHub
parent 8c51c48472
commit 4a65956cc9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 353 additions and 133 deletions

View File

@ -14,6 +14,8 @@
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 */; };
2856E0656A49F2665D69E760 /* benc.h in Headers */ = {isa = PBXBuildFile; fileRef = 2856E0656A49F2665D69E761 /* benc.h */; };
A47A7C87B8B57BE50DF0D410 /* files.cc in Sources */ = {isa = PBXBuildFile; fileRef = A47A7C87B8B57BE50DF0D411 /* files.cc */; };
A47A7C87B8B57BE50DF0D412 /* files.h in Headers */ = {isa = PBXBuildFile; fileRef = A47A7C87B8B57BE50DF0D413 /* files.h */; };
2B9BA6C508B488FE586A0AB0 /* torrents.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2B9BA6C508B488FE586A0AB1 /* torrents.cc */; };
2B9BA6C508B488FE586A0AB2 /* torrents.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B9BA6C508B488FE586A0AB3 /* torrents.h */; };
35F373030C2DA89000DAA8F2 /* FilePriorityCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35F373010C2DA88F00DAA8F2 /* FilePriorityCell.mm */; };
@ -569,6 +571,8 @@
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
2B9BA6C508B488FE586A0AB1 /* torrents.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = torrents.cc; sourceTree = "<group>"; };
2B9BA6C508B488FE586A0AB3 /* torrents.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = torrents.h; sourceTree = "<group>"; };
A47A7C87B8B57BE50DF0D411 /* files.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = files.cc; sourceTree = "<group>"; };
A47A7C87B8B57BE50DF0D413 /* files.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = files.h; sourceTree = "<group>"; };
32CA4F630368D1EE00C91783 /* Transmission_Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Transmission_Prefix.pch; sourceTree = "<group>"; };
35F373000C2DA88F00DAA8F2 /* FilePriorityCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FilePriorityCell.h; sourceTree = "<group>"; };
35F373010C2DA88F00DAA8F2 /* FilePriorityCell.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FilePriorityCell.mm; sourceTree = "<group>"; };
@ -1560,6 +1564,7 @@
A29DF8B70DB2544C00D04E5A /* resume.h */,
A29DF8B80DB2544C00D04E5A /* torrent.h */,
2B9BA6C508B488FE586A0AB3 /* torrents.h */,
A47A7C87B8B57BE50DF0D413 /* files.h */,
C1033E031A3279B800EF44D8 /* crypto-utils-fallback.cc */,
C1033E041A3279B800EF44D8 /* crypto-utils-ccrypto.cc */,
C1033E051A3279B800EF44D8 /* crypto-utils.cc */,
@ -1607,6 +1612,7 @@
A2AA9BE0132CAC8D00FA131E /* announcer-udp.cc */,
BEFC1DF90C07861A00B0BB3C /* torrent.cc */,
2B9BA6C508B488FE586A0AB1 /* torrents.cc */,
A47A7C87B8B57BE50DF0D411 /* files.cc */,
BEFC1DFC0C07861A00B0BB3C /* port-forwarding.h */,
BEFC1DFD0C07861A00B0BB3C /* port-forwarding.cc */,
A21FBBA90EDA78C300BC3C51 /* bandwidth.h */,
@ -2096,6 +2102,7 @@
A29DF8BA0DB2544C00D04E5A /* resume.h in Headers */,
A29DF8BB0DB2544C00D04E5A /* torrent.h in Headers */,
2B9BA6C508B488FE586A0AB2 /* torrents.h in Headers */,
A47A7C87B8B57BE50DF0D412 /* files.h in Headers */,
A29DF8BE0DB2545F00D04E5A /* verify.h in Headers */,
C1FEE57B1C3223CC00D62832 /* watchdir.h in Headers */,
A2AAB6650DE0D08B00E04DDA /* blocklist.h in Headers */,
@ -2772,6 +2779,7 @@
BEFC1E2F0C07861A00B0BB3C /* session.cc in Sources */,
BEFC1E320C07861A00B0BB3C /* torrent.cc in Sources */,
2B9BA6C508B488FE586A0AB0 /* torrents.cc in Sources */,
A47A7C87B8B57BE50DF0D410 /* files.cc */,
BEFC1E360C07861A00B0BB3C /* port-forwarding.cc in Sources */,
BEFC1E3C0C07861A00B0BB3C /* platform.cc in Sources */,
BEFC1E460C07861A00B0BB3C /* net.cc in Sources */,

View File

@ -31,6 +31,7 @@ set(PROJECT_FILES
file-posix.cc
file-win32.cc
file.cc
files.cc
handshake.cc
inout.cc
log.cc
@ -170,6 +171,7 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
fdlimit.h
file-info.h
file-piece-map.h
files.h
handshake.h
history.h
inout.h

88
libtransmission/files.cc Normal file
View File

@ -0,0 +1,88 @@
// This file Copyright © 2022 Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include <optional>
#include <string>
#include <string_view>
#include "transmission.h"
#include "files.h"
bool tr_files::empty() const noexcept
{
return std::empty(files_);
}
size_t tr_files::size() const noexcept
{
return std::size(files_);
}
uint64_t tr_files::size(tr_file_index_t file_index) const
{
return files_.at(file_index).size_;
}
std::string const& tr_files::path(tr_file_index_t file_index) const
{
return files_.at(file_index).path_;
}
void tr_files::setPath(tr_file_index_t file_index, std::string_view path)
{
files_.at(file_index).setPath(path);
}
void tr_files::reserve(size_t n_files)
{
files_.reserve(n_files);
}
void tr_files::shrinkToFit()
{
files_.shrink_to_fit();
}
tr_file_index_t tr_files::add(std::string_view path, uint64_t size)
{
auto const file_index = static_cast<tr_file_index_t>(std::size(files_));
files_.emplace_back(path, size);
return file_index;
}
void tr_files::clear() noexcept
{
files_.clear();
}
std::optional<tr_files::FoundFile> tr_files::find(
tr_file_index_t file_index,
std::string_view const* search_paths,
size_t n_paths) const
{
auto filename = tr_pathbuf{};
auto file_info = tr_sys_path_info{};
auto const& subpath = path(file_index);
for (size_t path_idx = 0; path_idx < n_paths; ++path_idx)
{
auto const base = search_paths[path_idx];
filename.assign(base, '/', subpath);
if (tr_sys_path_get_info(filename, 0, &file_info))
{
return FoundFile{ file_info, std::move(filename), std::size(base) };
}
filename.assign(filename, base, '/', subpath, PartialFileSuffix);
if (tr_sys_path_get_info(filename, 0, &file_info))
{
return FoundFile{ file_info, std::move(filename), std::size(base) };
}
}
return {};
}

97
libtransmission/files.h Normal file
View File

@ -0,0 +1,97 @@
// This file Copyright © 2022 Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#pragma once
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "transmission.h"
#include "file.h"
#include "tr-strbuf.h"
/**
* A simple ordered collection of files.
*/
struct tr_files
{
public:
[[nodiscard]] bool empty() const noexcept;
[[nodiscard]] size_t size() const noexcept;
[[nodiscard]] uint64_t size(tr_file_index_t) const;
[[nodiscard]] std::string const& path(tr_file_index_t) const;
void setPath(tr_file_index_t, std::string_view path);
void reserve(size_t);
tr_file_index_t add(std::string_view path, uint64_t size);
void shrinkToFit();
void clear() noexcept;
struct FoundFile : public tr_sys_path_info
{
public:
FoundFile(tr_sys_path_info info, tr_pathbuf&& filename_in, size_t base_len_in)
: tr_sys_path_info{ info }
, filename_{ std::move(filename_in) }
, base_len_{ base_len_in }
{
}
[[nodiscard]] constexpr auto const& filename() const noexcept
{
// /home/foo/Downloads/torrent/01-file-one.txt
return filename_;
}
[[nodiscard]] constexpr auto base() const noexcept
{
// /home/foo/Downloads
return filename_.sv().substr(0, base_len_);
}
[[nodiscard]] constexpr auto subpath() const noexcept
{
// torrent/01-file-one.txt
return filename_.sv().substr(base_len_ + 1);
}
private:
tr_pathbuf filename_;
size_t base_len_;
};
[[nodiscard]] std::optional<FoundFile> find(
tr_file_index_t file_index,
std::string_view const* search_paths,
size_t n_paths) const;
static constexpr std::string_view PartialFileSuffix = ".part";
private:
struct file_t
{
public:
void setPath(std::string_view subpath)
{
path_ = subpath;
}
file_t(std::string_view path, uint64_t size)
: path_{ path }
, size_{ size }
{
}
std::string path_;
uint64_t size_ = 0;
};
std::vector<file_t> files_;
};

View File

@ -126,7 +126,7 @@ int readOrWriteBytes(
auto const prealloc = (!do_write || !tor->fileIsWanted(file_index)) ? TR_PREALLOCATE_NONE :
tor->session->preallocationMode;
fd = tr_fdFileCheckout(session, tor->uniqueId, file_index, found->filename, do_write, prealloc, file_size);
fd = tr_fdFileCheckout(session, tor->uniqueId, file_index, found->filename(), do_write, prealloc, file_size);
if (fd == TR_BAD_SYS_FILE)
{
err = errno;
@ -134,7 +134,7 @@ int readOrWriteBytes(
tor,
fmt::format(
_("Couldn't get '{path}': {error} ({error_code})"),
fmt::arg("path", found->filename),
fmt::arg("path", found->filename()),
fmt::arg("error", tr_strerror(err)),
fmt::arg("error_code", err)));
}

View File

@ -242,7 +242,7 @@ std::string_view tr_torrent_metainfo::parseFiles(tr_torrent_metainfo& setme, tr_
if (tr_variantDictFindInt(info_dict, TR_KEY_length, &len))
{
total_size = len;
setme.files_.emplace_back(root_name, len);
setme.files_.add(root_name, len);
}
// "For the purposes of the other keys, the multi-file case is treated as
@ -285,7 +285,7 @@ std::string_view tr_torrent_metainfo::parseFiles(tr_torrent_metainfo& setme, tr_
return "path";
}
setme.files_.emplace_back(buf, len);
setme.files_.add(buf, len);
total_size += len;
}
}
@ -559,24 +559,3 @@ void tr_torrent_metainfo::removeFile(
tr_sys_path_remove(makeFilename(dirname, name, info_hash_string, BasenameFormat::NameAndPartialHash, suffix));
tr_sys_path_remove(makeFilename(dirname, name, info_hash_string, BasenameFormat::Hash, suffix));
}
std::string const& tr_torrent_metainfo::fileSubpath(tr_file_index_t i) const
{
TR_ASSERT(i < fileCount());
return files_.at(i).path();
}
void tr_torrent_metainfo::setFileSubpath(tr_file_index_t i, std::string_view subpath)
{
TR_ASSERT(i < fileCount());
files_.at(i).setSubpath(subpath);
}
uint64_t tr_torrent_metainfo::fileSize(tr_file_index_t i) const
{
TR_ASSERT(i < fileCount());
return files_.at(i).size();
}

View File

@ -14,6 +14,7 @@
#include "transmission.h"
#include "block-info.h"
#include "files.h"
#include "magnet-metainfo.h"
#include "tr-strbuf.h"
@ -35,6 +36,29 @@ public:
// load multiple files.
bool parseTorrentFile(std::string_view benc_filename, std::vector<char>* buffer = nullptr, tr_error** error = nullptr);
// FILES
[[nodiscard]] constexpr auto const& files() const noexcept
{
return files_;
}
[[nodiscard]] auto fileCount() const noexcept
{
return std::size(files());
}
[[nodiscard]] auto fileSize(tr_file_index_t i) const
{
return files().size(i);
}
[[nodiscard]] auto const& fileSubpath(tr_file_index_t i) const
{
return files().path(i);
}
void setFileSubpath(tr_file_index_t i, std::string_view subpath)
{
files_.setPath(i, subpath);
}
/// BLOCK INFO
[[nodiscard]] constexpr auto const& blockInfo() const noexcept
@ -83,6 +107,8 @@ public:
return blockInfo().totalSize();
}
// OTHER PROPERTIES
[[nodiscard]] constexpr auto const& comment() const noexcept
{
return comment_;
@ -96,17 +122,6 @@ public:
return source_;
}
[[nodiscard]] constexpr auto fileCount() const noexcept
{
return std::size(files_);
}
[[nodiscard]] uint64_t fileSize(tr_file_index_t i) const;
[[nodiscard]] std::string const& fileSubpath(tr_file_index_t i) const;
void setFileSubpath(tr_file_index_t i, std::string_view subpath);
[[nodiscard]] constexpr auto const& isPrivate() const noexcept
{
return is_private_;
@ -136,6 +151,8 @@ public:
return pieces_offset_;
}
// UTILS
[[nodiscard]] auto torrentFile(std::string_view torrent_dir) const
{
return makeFilename(torrent_dir, name(), infoHashString(), BasenameFormat::Hash, ".torrent");
@ -189,39 +206,11 @@ private:
return makeFilename(dirname, name(), infoHashString(), format, suffix);
}
struct file_t
{
public:
[[nodiscard]] std::string const& path() const noexcept
{
return path_;
}
void setSubpath(std::string_view subpath)
{
path_ = subpath;
}
[[nodiscard]] uint64_t size() const noexcept
{
return size_;
}
file_t(std::string_view path, uint64_t size)
: path_{ path }
, size_{ size }
{
}
private:
std::string path_;
uint64_t size_ = 0;
};
tr_block_info block_info_ = tr_block_info{ 0, 0 };
tr_files files_;
std::vector<tr_sha1_digest_t> pieces_;
std::vector<file_t> files_;
std::string comment_;
std::string creator_;

View File

@ -645,7 +645,7 @@ static bool isNewTorrentASeed(tr_torrent* tor)
}
// it's not a new seed if a file is partial
if (tr_strvEndsWith(found->filename, tr_torrent::PartialFileSuffix))
if (tr_strvEndsWith(found->filename(), tr_torrent::PartialFileSuffix))
{
return false;
}
@ -2415,7 +2415,7 @@ static void setLocationImpl(struct LocationData* const data)
if (auto found = tor->findFile(i); found)
{
auto const& oldpath = found->filename;
auto const& oldpath = found->filename();
auto const newpath = tr_pathbuf{ location, '/', found->subpath() };
tr_logAddTraceTor(tor, fmt::format("Found file #{}: '{}'", i, oldpath));
@ -2565,7 +2565,7 @@ static void tr_torrentFileCompleted(tr_torrent* tor, tr_file_index_t i)
{
if (auto const& file_subpath = tor->fileSubpath(i); file_subpath != found->subpath())
{
auto const& oldpath = found->filename;
auto const& oldpath = found->filename();
auto const newpath = tr_pathbuf{ found->base(), '/', file_subpath };
tr_error* error = nullptr;
@ -2642,54 +2642,11 @@ void tr_torrentGotBlock(tr_torrent* tor, tr_block_index_t block)
****
***/
std::optional<tr_torrent::tr_found_file_t> tr_torrent::findFile(tr_file_index_t i) const
{
auto filename = tr_pathbuf{};
auto const subpath = std::string_view{ this->fileSubpath(i) };
auto file_info = tr_sys_path_info{};
if (!std::empty(this->downloadDir()))
{
auto const base = this->downloadDir();
filename.assign(base, "/"sv, subpath);
if (tr_sys_path_get_info(filename, 0, &file_info))
{
return tr_found_file_t{ file_info, std::move(filename), std::size(base) };
}
filename.assign(filename, base, "/"sv, subpath, PartialFileSuffix);
if (tr_sys_path_get_info(filename, 0, &file_info))
{
return tr_found_file_t{ file_info, std::move(filename), std::size(base) };
}
}
if (!std::empty(this->incompleteDir()))
{
auto const base = this->incompleteDir();
filename.assign(base, "/"sv, subpath);
if (tr_sys_path_get_info(filename, 0, &file_info))
{
return tr_found_file_t{ file_info, std::move(filename), std::size(base) };
}
filename.assign(base, "/"sv, subpath, PartialFileSuffix);
if (tr_sys_path_get_info(filename, 0, &file_info))
{
return tr_found_file_t{ file_info, std::move(filename), std::size(base) };
}
}
return {};
}
// TODO: clients that call this should call tr_torrent::findFile() instead
char* tr_torrentFindFile(tr_torrent const* tor, tr_file_index_t fileNum)
{
auto const found = tor->findFile(fileNum);
return found ? tr_strdup(found->filename.c_str()) : nullptr;
return found ? tr_strdup(found->filename()) : nullptr;
}
/* Decide whether we should be looking for files in downloadDir or incompleteDir. */

View File

@ -9,6 +9,7 @@
#error only libtransmission should #include this header.
#endif
#include <array>
#include <cstddef> // size_t
#include <ctime>
#include <optional>
@ -368,33 +369,23 @@ public:
metainfo_.setFileSubpath(i, subpath);
}
struct tr_found_file_t : public tr_sys_path_info
[[nodiscard]] auto findFile(tr_file_index_t file_index) const
{
// /home/foo/Downloads/torrent/01-file-one.txt
tr_pathbuf filename;
size_t base_len;
auto n_paths = size_t{ 0U };
auto paths = std::array<std::string_view, 2>{};
tr_found_file_t(tr_sys_path_info info, tr_pathbuf&& filename_in, size_t base_len_in)
: tr_sys_path_info{ info }
, filename{ std::move(filename_in) }
, base_len{ base_len_in }
if (auto const path = downloadDir(); !std::empty(path))
{
paths[n_paths++] = path.sv();
}
[[nodiscard]] constexpr auto base() const
if (auto const path = incompleteDir(); !std::empty(path))
{
// /home/foo/Downloads
return filename.sv().substr(0, base_len);
paths[n_paths++] = path.sv();
}
[[nodiscard]] constexpr auto subpath() const
{
// torrent/01-file-one.txt
return filename.sv().substr(base_len + 1);
}
};
std::optional<tr_found_file_t> findFile(tr_file_index_t i) const;
return metainfo_.files().find(file_index, std::data(paths), n_paths);
}
/// METAINFO - TRACKERS

View File

@ -62,7 +62,7 @@ static bool verifyTorrent(tr_torrent* tor, bool const* stopFlag)
if (file_pos == 0 && fd == TR_BAD_SYS_FILE && file_index != prev_file_index)
{
auto const found = tor->findFile(file_index);
fd = !found ? TR_BAD_SYS_FILE : tr_sys_file_open(found->filename, TR_SYS_FILE_READ | TR_SYS_FILE_SEQUENTIAL, 0);
fd = !found ? TR_BAD_SYS_FILE : tr_sys_file_open(found->filename(), TR_SYS_FILE_READ | TR_SYS_FILE_SEQUENTIAL, 0);
prev_file_index = file_index;
}

View File

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

View File

@ -0,0 +1,108 @@
// This file Copyright (C) 2022 Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0), GPLv3 (SPDX: GPL-3.0),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include "transmission.h"
#include "files.h"
#include "test-fixtures.h"
using namespace std::literals;
class FilesTest : public ::libtransmission::test::SandboxedTest
{
};
TEST_F(FilesTest, add)
{
auto constexpr Path = "/hello/world"sv;
auto constexpr Size = size_t{ 1024 };
auto files = tr_files{};
EXPECT_EQ(size_t{ 0U }, std::size(files));
EXPECT_TRUE(std::empty(files));
auto const file_index = files.add(Path, Size);
EXPECT_EQ(tr_file_index_t{ 0U }, file_index);
EXPECT_EQ(size_t{ 1U }, std::size(files));
EXPECT_EQ(Size, files.size(file_index));
EXPECT_EQ(Path, files.path(file_index));
EXPECT_FALSE(std::empty(files));
}
TEST_F(FilesTest, setPath)
{
auto constexpr Path1 = "/hello/world"sv;
auto constexpr Path2 = "/hello/there"sv;
auto constexpr Size = size_t{ 2048 };
auto files = tr_files{};
auto const file_index = files.add(Path1, Size);
EXPECT_EQ(Path1, files.path(file_index));
EXPECT_EQ(Size, files.size(file_index));
files.setPath(file_index, Path2);
EXPECT_EQ(Path2, files.path(file_index));
EXPECT_EQ(Size, files.size(file_index));
}
TEST_F(FilesTest, clear)
{
auto constexpr Path1 = "/hello/world"sv;
auto constexpr Path2 = "/hello/there"sv;
auto constexpr Size = size_t{ 2048 };
auto files = tr_files{};
files.add(Path1, Size);
EXPECT_EQ(size_t{ 1U }, std::size(files));
files.add(Path2, Size);
EXPECT_EQ(size_t{ 2U }, std::size(files));
files.clear();
EXPECT_TRUE(std::empty(files));
EXPECT_EQ(size_t{ 0U }, std::size(files));
}
TEST_F(FilesTest, find)
{
static auto constexpr Contents = "hello"sv;
auto const filename = tr_pathbuf{ sandboxDir(), "/first_dir/hello.txt"sv };
createFileWithContents(std::string{ filename }, std::data(Contents), std::size(Contents));
auto files = tr_files{};
auto const file_index = files.add("first_dir/hello.txt", 1024);
auto const search_path_1 = tr_pathbuf{ sandboxDir() };
auto const search_path_2 = tr_pathbuf{ "/tmp"sv };
auto search_path = std::vector<std::string_view>{ search_path_1.sv(), search_path_2.sv() };
auto found = files.find(file_index, std::data(search_path), std::size(search_path));
EXPECT_TRUE(found);
EXPECT_EQ(filename, found->filename());
// same search, but with the search paths reversed
search_path = std::vector<std::string_view>{ search_path_2.sv(), search_path_1.sv() };
found = files.find(file_index, std::data(search_path), std::size(search_path));
EXPECT_TRUE(found);
EXPECT_EQ(filename, found->filename());
// now make it an incomplete file
auto const partial_filename = tr_pathbuf{ filename, tr_files::PartialFileSuffix };
EXPECT_TRUE(tr_sys_path_rename(filename, partial_filename));
search_path = std::vector<std::string_view>{ search_path_1.sv(), search_path_2.sv() };
found = files.find(file_index, std::data(search_path), std::size(search_path));
EXPECT_TRUE(found);
EXPECT_EQ(partial_filename, found->filename());
// same search, but with the search paths reversed
search_path = std::vector<std::string_view>{ search_path_2.sv(), search_path_1.sv() };
found = files.find(file_index, std::data(search_path), std::size(search_path));
EXPECT_TRUE(found);
EXPECT_EQ(partial_filename, found->filename());
// what about if we look for a file that does not exist
EXPECT_TRUE(tr_sys_path_remove(partial_filename));
EXPECT_FALSE(files.find(file_index, std::data(search_path), std::size(search_path)));
}