From 4a65956cc91ef8fa65f39603325b7980ce4ddbf9 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 12 Apr 2022 10:00:02 -0500 Subject: [PATCH] refactor: extract some file management into tr_files class (#2906) --- Transmission.xcodeproj/project.pbxproj | 8 ++ libtransmission/CMakeLists.txt | 2 + libtransmission/files.cc | 88 ++++++++++++++++++++ libtransmission/files.h | 97 ++++++++++++++++++++++ libtransmission/inout.cc | 4 +- libtransmission/torrent-metainfo.cc | 25 +----- libtransmission/torrent-metainfo.h | 71 +++++++--------- libtransmission/torrent.cc | 51 +----------- libtransmission/torrent.h | 29 +++---- libtransmission/verify.cc | 2 +- tests/libtransmission/CMakeLists.txt | 1 + tests/libtransmission/files-test.cc | 108 +++++++++++++++++++++++++ 12 files changed, 353 insertions(+), 133 deletions(-) create mode 100644 libtransmission/files.cc create mode 100644 libtransmission/files.h create mode 100644 tests/libtransmission/files-test.cc diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index a320f651b..bc9bcaed6 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -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 = ""; }; 2B9BA6C508B488FE586A0AB3 /* torrents.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = torrents.h; sourceTree = ""; }; + A47A7C87B8B57BE50DF0D411 /* files.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = files.cc; sourceTree = ""; }; + A47A7C87B8B57BE50DF0D413 /* files.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = files.h; sourceTree = ""; }; 32CA4F630368D1EE00C91783 /* Transmission_Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Transmission_Prefix.pch; sourceTree = ""; }; 35F373000C2DA88F00DAA8F2 /* FilePriorityCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FilePriorityCell.h; sourceTree = ""; }; 35F373010C2DA88F00DAA8F2 /* FilePriorityCell.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FilePriorityCell.mm; sourceTree = ""; }; @@ -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 */, diff --git a/libtransmission/CMakeLists.txt b/libtransmission/CMakeLists.txt index a0c79e3af..29fefd7ff 100644 --- a/libtransmission/CMakeLists.txt +++ b/libtransmission/CMakeLists.txt @@ -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 diff --git a/libtransmission/files.cc b/libtransmission/files.cc new file mode 100644 index 000000000..483553203 --- /dev/null +++ b/libtransmission/files.cc @@ -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 +#include +#include + +#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(std::size(files_)); + files_.emplace_back(path, size); + return file_index; +} + +void tr_files::clear() noexcept +{ + files_.clear(); +} + +std::optional 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 {}; +} diff --git a/libtransmission/files.h b/libtransmission/files.h new file mode 100644 index 000000000..2bf7655f9 --- /dev/null +++ b/libtransmission/files.h @@ -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 +#include +#include +#include +#include + +#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 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 files_; +}; diff --git a/libtransmission/inout.cc b/libtransmission/inout.cc index 784137f14..30d4aa876 100644 --- a/libtransmission/inout.cc +++ b/libtransmission/inout.cc @@ -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))); } diff --git a/libtransmission/torrent-metainfo.cc b/libtransmission/torrent-metainfo.cc index 08c6cf354..5d9a660ab 100644 --- a/libtransmission/torrent-metainfo.cc +++ b/libtransmission/torrent-metainfo.cc @@ -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(); -} diff --git a/libtransmission/torrent-metainfo.h b/libtransmission/torrent-metainfo.h index 8ff3d958b..eacdfb4a5 100644 --- a/libtransmission/torrent-metainfo.h +++ b/libtransmission/torrent-metainfo.h @@ -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* 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 pieces_; - std::vector files_; std::string comment_; std::string creator_; diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index 933c7c438..4b0d11dac 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -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::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. */ diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index 98e54cb8b..df1ca82c5 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -9,6 +9,7 @@ #error only libtransmission should #include this header. #endif +#include #include // size_t #include #include @@ -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{}; - 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 findFile(tr_file_index_t i) const; + return metainfo_.files().find(file_index, std::data(paths), n_paths); + } /// METAINFO - TRACKERS diff --git a/libtransmission/verify.cc b/libtransmission/verify.cc index 1de39a8ae..5a2803ebd 100644 --- a/libtransmission/verify.cc +++ b/libtransmission/verify.cc @@ -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; } diff --git a/tests/libtransmission/CMakeLists.txt b/tests/libtransmission/CMakeLists.txt index 5a78eff6b..9f7365258 100644 --- a/tests/libtransmission/CMakeLists.txt +++ b/tests/libtransmission/CMakeLists.txt @@ -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 diff --git a/tests/libtransmission/files-test.cc b/tests/libtransmission/files-test.cc new file mode 100644 index 000000000..cdc0933a4 --- /dev/null +++ b/tests/libtransmission/files-test.cc @@ -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{ 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{ 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{ 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{ 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))); +}