refactor: add tr_torrent_files::move() and remove() (#2919)
This commit is contained in:
parent
4590d172de
commit
2866638e1b
|
@ -14,8 +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 */; };
|
||||
A47A7C87B8B57BE50DF0D410 /* torrent-files.cc in Sources */ = {isa = PBXBuildFile; fileRef = A47A7C87B8B57BE50DF0D411 /* torrent-files.cc */; };
|
||||
A47A7C87B8B57BE50DF0D412 /* torrent-files.h in Headers */ = {isa = PBXBuildFile; fileRef = A47A7C87B8B57BE50DF0D413 /* torrent-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 */; };
|
||||
|
@ -571,8 +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>"; };
|
||||
A47A7C87B8B57BE50DF0D411 /* torrent-files.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = torrent-files.cc; sourceTree = "<group>"; };
|
||||
A47A7C87B8B57BE50DF0D413 /* torrent-files.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = torrent-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>"; };
|
||||
|
@ -1567,7 +1567,7 @@
|
|||
A29DF8B70DB2544C00D04E5A /* resume.h */,
|
||||
A29DF8B80DB2544C00D04E5A /* torrent.h */,
|
||||
2B9BA6C508B488FE586A0AB3 /* torrents.h */,
|
||||
A47A7C87B8B57BE50DF0D413 /* files.h */,
|
||||
A47A7C87B8B57BE50DF0D413 /* torrent-files.h */,
|
||||
C1033E031A3279B800EF44D8 /* crypto-utils-fallback.cc */,
|
||||
C1033E041A3279B800EF44D8 /* crypto-utils-ccrypto.cc */,
|
||||
C1033E051A3279B800EF44D8 /* crypto-utils.cc */,
|
||||
|
@ -1615,7 +1615,7 @@
|
|||
A2AA9BE0132CAC8D00FA131E /* announcer-udp.cc */,
|
||||
BEFC1DF90C07861A00B0BB3C /* torrent.cc */,
|
||||
2B9BA6C508B488FE586A0AB1 /* torrents.cc */,
|
||||
A47A7C87B8B57BE50DF0D411 /* files.cc */,
|
||||
A47A7C87B8B57BE50DF0D411 /* torrent-files.cc */,
|
||||
BEFC1DFC0C07861A00B0BB3C /* port-forwarding.h */,
|
||||
BEFC1DFD0C07861A00B0BB3C /* port-forwarding.cc */,
|
||||
A21FBBA90EDA78C300BC3C51 /* bandwidth.h */,
|
||||
|
@ -2109,7 +2109,7 @@
|
|||
A29DF8BA0DB2544C00D04E5A /* resume.h in Headers */,
|
||||
A29DF8BB0DB2544C00D04E5A /* torrent.h in Headers */,
|
||||
2B9BA6C508B488FE586A0AB2 /* torrents.h in Headers */,
|
||||
A47A7C87B8B57BE50DF0D412 /* files.h in Headers */,
|
||||
A47A7C87B8B57BE50DF0D412 /* torrent-files.h in Headers */,
|
||||
A29DF8BE0DB2545F00D04E5A /* verify.h in Headers */,
|
||||
C1FEE57B1C3223CC00D62832 /* watchdir.h in Headers */,
|
||||
A2AAB6650DE0D08B00E04DDA /* blocklist.h in Headers */,
|
||||
|
@ -2786,7 +2786,7 @@
|
|||
BEFC1E2F0C07861A00B0BB3C /* session.cc in Sources */,
|
||||
BEFC1E320C07861A00B0BB3C /* torrent.cc in Sources */,
|
||||
2B9BA6C508B488FE586A0AB0 /* torrents.cc in Sources */,
|
||||
A47A7C87B8B57BE50DF0D410 /* files.cc */,
|
||||
A47A7C87B8B57BE50DF0D410 /* torrent-files.cc */,
|
||||
BEFC1E360C07861A00B0BB3C /* port-forwarding.cc in Sources */,
|
||||
BEFC1E3C0C07861A00B0BB3C /* platform.cc in Sources */,
|
||||
BEFC1E460C07861A00B0BB3C /* net.cc in Sources */,
|
||||
|
|
|
@ -31,7 +31,6 @@ set(PROJECT_FILES
|
|||
file-posix.cc
|
||||
file-win32.cc
|
||||
file.cc
|
||||
files.cc
|
||||
handshake.cc
|
||||
inout.cc
|
||||
log.cc
|
||||
|
@ -58,6 +57,7 @@ set(PROJECT_FILES
|
|||
subprocess-posix.cc
|
||||
subprocess-win32.cc
|
||||
torrent-ctor.cc
|
||||
torrent-files.cc
|
||||
torrent-magnet.cc
|
||||
torrent-metainfo.cc
|
||||
torrent.cc
|
||||
|
@ -171,7 +171,6 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
|
|||
fdlimit.h
|
||||
file-info.h
|
||||
file-piece-map.h
|
||||
files.h
|
||||
handshake.h
|
||||
history.h
|
||||
inout.h
|
||||
|
@ -195,6 +194,7 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
|
|||
session.h
|
||||
stats.h
|
||||
subprocess.h
|
||||
torrent-files.h
|
||||
torrent-magnet.h
|
||||
torrent-metainfo.h
|
||||
torrent.h
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
// 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 {};
|
||||
}
|
||||
|
||||
bool tr_files::hasAnyLocalData(std::string_view const* search_paths, size_t n_paths) const
|
||||
{
|
||||
for (tr_file_index_t i = 0, n = size(); i < n; ++i)
|
||||
{
|
||||
if (find(i, search_paths, n_paths))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
|
@ -116,7 +116,7 @@ int readOrWriteBytes(
|
|||
// We didn't find the file that we want to write to.
|
||||
// Let's figure out where it goes so that we can create it.
|
||||
auto const base = tor->currentDir();
|
||||
auto const suffix = tor->session->isIncompleteFileNamingEnabled ? tr_files::PartialFileSuffix : ""sv;
|
||||
auto const suffix = tor->session->isIncompleteFileNamingEnabled ? tr_torrent_files::PartialFileSuffix : ""sv;
|
||||
found = { {}, tr_pathbuf{ base, "/"sv, tor->fileSubpath(file_index), suffix }, std::size(base) };
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
// 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 <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "log.h"
|
||||
#include "torrent-files.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using file_func_t = std::function<void(char const* filename)>;
|
||||
|
||||
bool isDirectory(char const* path)
|
||||
{
|
||||
auto info = tr_sys_path_info{};
|
||||
return tr_sys_path_get_info(path, 0, &info) && (info.type == TR_SYS_PATH_IS_DIRECTORY);
|
||||
}
|
||||
|
||||
bool isEmptyDirectory(char const* path)
|
||||
{
|
||||
if (!isDirectory(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto const odir = tr_sys_dir_open(path); odir != TR_BAD_SYS_DIR)
|
||||
{
|
||||
char const* name_cstr = nullptr;
|
||||
while ((name_cstr = tr_sys_dir_read_name(odir)) != nullptr)
|
||||
{
|
||||
auto const name = std::string_view{ name_cstr };
|
||||
if (name != "." && name != "..")
|
||||
{
|
||||
tr_sys_dir_close(odir);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
tr_sys_dir_close(odir);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void depthFirstWalk(char const* path, file_func_t const& func, std::optional<int> max_depth = {})
|
||||
{
|
||||
if (isDirectory(path) && (!max_depth || *max_depth > 0))
|
||||
{
|
||||
if (auto const odir = tr_sys_dir_open(path); odir != TR_BAD_SYS_DIR)
|
||||
{
|
||||
char const* name_cstr = nullptr;
|
||||
while ((name_cstr = tr_sys_dir_read_name(odir)) != nullptr)
|
||||
{
|
||||
auto const name = std::string_view{ name_cstr };
|
||||
if (name == "." || name == "..")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
depthFirstWalk(tr_pathbuf{ path, '/', name }.c_str(), func, max_depth ? *max_depth - 1 : max_depth);
|
||||
}
|
||||
|
||||
tr_sys_dir_close(odir);
|
||||
}
|
||||
}
|
||||
|
||||
func(path);
|
||||
}
|
||||
|
||||
bool isJunkFile(std::string_view filename)
|
||||
{
|
||||
auto const base = tr_sys_path_basename(filename);
|
||||
|
||||
#ifdef __APPLE__
|
||||
// check for resource forks. <http://support.apple.com/kb/TA20578>
|
||||
if (tr_strvStartsWith(base, "._"sv))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto constexpr Files = std::array<std::string_view, 3>{
|
||||
".DS_Store"sv,
|
||||
"Thumbs.db"sv,
|
||||
"desktop.ini"sv,
|
||||
};
|
||||
|
||||
return std::find(std::begin(Files), std::end(Files), base) != std::end(Files);
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
///
|
||||
|
||||
std::optional<tr_torrent_files::FoundFile> tr_torrent_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 {};
|
||||
}
|
||||
|
||||
bool tr_torrent_files::hasAnyLocalData(std::string_view const* search_paths, size_t n_paths) const
|
||||
{
|
||||
for (tr_file_index_t i = 0, n = fileCount(); i < n; ++i)
|
||||
{
|
||||
if (find(i, search_paths, n_paths))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
bool tr_torrent_files::move(
|
||||
std::string_view old_parent_in,
|
||||
std::string_view parent_in,
|
||||
double volatile* setme_progress,
|
||||
std::string_view log_name,
|
||||
tr_error** error) const
|
||||
{
|
||||
if (setme_progress != nullptr)
|
||||
{
|
||||
*setme_progress = 0.0;
|
||||
}
|
||||
|
||||
auto const old_parent = tr_pathbuf{ old_parent_in };
|
||||
auto const parent = tr_pathbuf{ parent_in };
|
||||
tr_logAddTrace(fmt::format(FMT_STRING("Moving files from '{:s}' to '{:s}'"), old_parent, parent), log_name);
|
||||
|
||||
if (tr_sys_path_is_same(old_parent, parent))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!tr_sys_dir_create(parent, TR_SYS_DIR_CREATE_PARENTS, 0777, error))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const search_paths = std::array<std::string_view, 1>{ old_parent.sv() };
|
||||
|
||||
auto const total_size = totalSize();
|
||||
auto err = bool{};
|
||||
auto bytes_moved = uint64_t{};
|
||||
|
||||
for (tr_file_index_t i = 0, n = fileCount(); i < n; ++i)
|
||||
{
|
||||
auto const found = find(i, std::data(search_paths), std::size(search_paths));
|
||||
if (!found)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& old_path = found->filename();
|
||||
auto const path = tr_pathbuf{ parent, '/', found->subpath() };
|
||||
tr_logAddTrace(fmt::format(FMT_STRING("Found file #{:d} '{:s}'"), i, old_path), log_name);
|
||||
|
||||
if (tr_sys_path_is_same(old_path, path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
tr_logAddTrace(fmt::format(FMT_STRING("Moving file #{:d} to '{:s}'"), i, old_path, path), log_name);
|
||||
if (!tr_moveFile(old_path, path, error))
|
||||
{
|
||||
err = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (setme_progress != nullptr && total_size > 0U)
|
||||
{
|
||||
bytes_moved += fileSize(i);
|
||||
*setme_progress = static_cast<double>(bytes_moved) / total_size;
|
||||
}
|
||||
}
|
||||
|
||||
// after moving the files, remove any leftover empty directories
|
||||
if (!err)
|
||||
{
|
||||
auto const remove_empty_directories = [](char const* filename)
|
||||
{
|
||||
if (isEmptyDirectory(filename))
|
||||
{
|
||||
tr_sys_path_remove(filename, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
remove(old_parent, "transmission-removed", remove_empty_directories);
|
||||
}
|
||||
|
||||
return !err;
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
/**
|
||||
* This convoluted code does something (seemingly) simple:
|
||||
* remove the torrent's local files.
|
||||
*
|
||||
* Fun complications:
|
||||
* 1. Try to preserve the directory hierarchy in the recycle bin.
|
||||
* 2. If there are nontorrent files, don't delete them...
|
||||
* 3. ...unless the other files are "junk", such as .DS_Store
|
||||
*/
|
||||
void tr_torrent_files::remove(std::string_view parent_in, std::string_view tmpdir_prefix, FileFunc const& func) const
|
||||
{
|
||||
auto const parent = tr_pathbuf{ parent_in };
|
||||
|
||||
// don't try to delete local data if the directory's gone missing
|
||||
if (!tr_sys_path_exists(parent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// make a tmpdir
|
||||
auto tmpdir = tr_pathbuf{ parent, '/', tmpdir_prefix, "__XXXXXX"sv };
|
||||
tr_sys_dir_create_temp(std::data(tmpdir));
|
||||
|
||||
// move the local data to the tmpdir
|
||||
auto const search_paths = std::array<std::string_view, 1>{ parent.sv() };
|
||||
for (tr_file_index_t idx = 0, n_files = fileCount(); idx < n_files; ++idx)
|
||||
{
|
||||
if (auto const found = find(idx, std::data(search_paths), std::size(search_paths)); found)
|
||||
{
|
||||
tr_moveFile(found->filename(), tr_pathbuf{ tmpdir, '/', found->subpath() });
|
||||
}
|
||||
}
|
||||
|
||||
// Make a list of the top-level torrent files & folders
|
||||
// because we'll need it below in the 'remove junk' phase
|
||||
auto top_files = std::set<std::string>{};
|
||||
depthFirstWalk(
|
||||
tmpdir,
|
||||
[&parent, &tmpdir, &top_files](char const* filename)
|
||||
{
|
||||
if (tmpdir != filename)
|
||||
{
|
||||
top_files.emplace(tr_pathbuf{ parent, '/', tr_sys_path_basename(filename) });
|
||||
}
|
||||
},
|
||||
1);
|
||||
|
||||
auto const func_wrapper = [&tmpdir, &func](char const* filename)
|
||||
{
|
||||
if (tmpdir != filename)
|
||||
{
|
||||
func(filename);
|
||||
}
|
||||
};
|
||||
|
||||
// Remove the tmpdir.
|
||||
// Since `func` might send files to a recycle bin, try to preserve
|
||||
// the folder hierarchy by removing top-level files & folders first.
|
||||
// But that can fail -- e.g. `func` might refuse to remove nonempty
|
||||
// directories -- so plan B is to remove everything bottom-up.
|
||||
depthFirstWalk(tmpdir, func_wrapper, 1);
|
||||
depthFirstWalk(tmpdir, func_wrapper);
|
||||
tr_sys_path_remove(tmpdir);
|
||||
|
||||
// OK we've removed the local data.
|
||||
// What's left are empty folders, junk, and user-generated files.
|
||||
// Remove the first two categories and leave the third alone.
|
||||
auto const remove_junk = [](char const* filename)
|
||||
{
|
||||
if (isEmptyDirectory(filename) || isJunkFile(filename))
|
||||
{
|
||||
tr_sys_path_remove(filename);
|
||||
}
|
||||
};
|
||||
for (auto const& filename : top_files)
|
||||
{
|
||||
depthFirstWalk(filename.c_str(), remove_junk);
|
||||
}
|
||||
}
|
|
@ -5,7 +5,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdint> // uint64_t
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
@ -16,23 +17,77 @@
|
|||
#include "file.h"
|
||||
#include "tr-strbuf.h"
|
||||
|
||||
struct tr_error;
|
||||
|
||||
/**
|
||||
* A simple ordered collection of files.
|
||||
* A simple collection of files & utils for finding them, moving them, etc.
|
||||
*/
|
||||
struct tr_files
|
||||
struct tr_torrent_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;
|
||||
[[nodiscard]] bool empty() const noexcept
|
||||
{
|
||||
return std::empty(files_);
|
||||
}
|
||||
|
||||
void setPath(tr_file_index_t, std::string_view path);
|
||||
[[nodiscard]] size_t fileCount() const noexcept
|
||||
{
|
||||
return std::size(files_);
|
||||
}
|
||||
|
||||
void reserve(size_t);
|
||||
void shrinkToFit();
|
||||
void clear() noexcept;
|
||||
tr_file_index_t add(std::string_view path, uint64_t size);
|
||||
[[nodiscard]] uint64_t fileSize(tr_file_index_t file_index) const
|
||||
{
|
||||
return files_.at(file_index).size_;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto totalSize() const noexcept
|
||||
{
|
||||
return total_size_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string const& path(tr_file_index_t file_index) const
|
||||
{
|
||||
return files_.at(file_index).path_;
|
||||
}
|
||||
|
||||
void setPath(tr_file_index_t file_index, std::string_view path)
|
||||
{
|
||||
files_.at(file_index).setPath(path);
|
||||
}
|
||||
|
||||
void reserve(size_t n_files)
|
||||
{
|
||||
files_.reserve(n_files);
|
||||
}
|
||||
|
||||
void shrinkToFit()
|
||||
{
|
||||
files_.shrink_to_fit();
|
||||
}
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
files_.clear();
|
||||
total_size_ = uint64_t{};
|
||||
}
|
||||
|
||||
tr_file_index_t add(std::string_view path, uint64_t file_size)
|
||||
{
|
||||
auto const ret = static_cast<tr_file_index_t>(std::size(files_));
|
||||
files_.emplace_back(path, file_size);
|
||||
total_size_ += file_size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool move(
|
||||
std::string_view old_top_in,
|
||||
std::string_view top_in,
|
||||
double volatile* setme_progress,
|
||||
std::string_view log_name = "",
|
||||
tr_error** error = nullptr) const;
|
||||
|
||||
using FileFunc = std::function<void(char const* filename)>;
|
||||
void remove(std::string_view top_in, std::string_view tmpdir_prefix, FileFunc const& func) const;
|
||||
|
||||
struct FoundFile : public tr_sys_path_info
|
||||
{
|
||||
|
@ -92,4 +147,5 @@ private:
|
|||
};
|
||||
|
||||
std::vector<file_t> files_;
|
||||
uint64_t total_size_;
|
||||
};
|
|
@ -148,7 +148,7 @@ std::string tr_torrent_metainfo::fixWebseedUrl(tr_torrent_metainfo const& tm, st
|
|||
{
|
||||
url = tr_strvStrip(url);
|
||||
|
||||
if (std::size(tm.files_) > 1 && !std::empty(url) && url.back() != '/')
|
||||
if (tm.fileCount() > 1U && !std::empty(url) && url.back() != '/')
|
||||
{
|
||||
return std::string{ url } + '/';
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
#include "transmission.h"
|
||||
|
||||
#include "block-info.h"
|
||||
#include "files.h"
|
||||
#include "magnet-metainfo.h"
|
||||
#include "torrent-files.h"
|
||||
#include "tr-strbuf.h"
|
||||
|
||||
struct tr_error;
|
||||
|
@ -44,11 +44,11 @@ public:
|
|||
}
|
||||
[[nodiscard]] auto fileCount() const noexcept
|
||||
{
|
||||
return std::size(files());
|
||||
return files().fileCount();
|
||||
}
|
||||
[[nodiscard]] auto fileSize(tr_file_index_t i) const
|
||||
{
|
||||
return files().size(i);
|
||||
return files().fileSize(i);
|
||||
}
|
||||
[[nodiscard]] auto const& fileSubpath(tr_file_index_t i) const
|
||||
{
|
||||
|
@ -208,7 +208,7 @@ private:
|
|||
|
||||
tr_block_info block_info_ = tr_block_info{ 0, 0 };
|
||||
|
||||
tr_files files_;
|
||||
tr_torrent_files files_;
|
||||
|
||||
std::vector<tr_sha1_digest_t> pieces_;
|
||||
|
||||
|
|
|
@ -611,7 +611,7 @@ static size_t buildSearchPathArray(tr_torrent const* tor, std::string_view* path
|
|||
return walk - paths;
|
||||
}
|
||||
|
||||
std::optional<tr_files::FoundFile> tr_torrent::findFile(tr_file_index_t file_index) const
|
||||
std::optional<tr_torrent_files::FoundFile> tr_torrent::findFile(tr_file_index_t file_index) const
|
||||
{
|
||||
auto paths = std::array<std::string_view, 4>{};
|
||||
auto const n_paths = buildSearchPathArray(this, std::data(paths));
|
||||
|
@ -663,7 +663,7 @@ static bool isNewTorrentASeed(tr_torrent* tor)
|
|||
}
|
||||
|
||||
// it's not a new seed if a file is partial
|
||||
if (tr_strvEndsWith(found->filename(), tr_files::PartialFileSuffix))
|
||||
if (tr_strvEndsWith(found->filename(), tr_torrent_files::PartialFileSuffix))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -685,8 +685,6 @@ static bool isNewTorrentASeed(tr_torrent* tor)
|
|||
return tor->ensurePieceIsChecked(0);
|
||||
}
|
||||
|
||||
static void refreshCurrentDir(tr_torrent* tor);
|
||||
|
||||
static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
|
||||
{
|
||||
auto const lock = tor->unique_lock();
|
||||
|
@ -751,7 +749,7 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
|
|||
tr_ctorInitTorrentPriorities(ctor, tor);
|
||||
tr_ctorInitTorrentWanted(ctor, tor);
|
||||
|
||||
refreshCurrentDir(tor);
|
||||
tor->refreshCurrentDir();
|
||||
|
||||
bool const doStart = tor->isRunning;
|
||||
tor->isRunning = false;
|
||||
|
@ -904,12 +902,8 @@ void tr_torrentSetDownloadDir(tr_torrent* tor, char const* path)
|
|||
|
||||
if (tor->download_dir != path)
|
||||
{
|
||||
tor->download_dir = path;
|
||||
tor->markEdited();
|
||||
tor->setDirty();
|
||||
tor->setDownloadDir(path);
|
||||
}
|
||||
|
||||
refreshCurrentDir(tor);
|
||||
}
|
||||
|
||||
char const* tr_torrentGetDownloadDir(tr_torrent const* tor)
|
||||
|
@ -1576,7 +1570,7 @@ static void stopTorrent(tr_torrent* const tor)
|
|||
{
|
||||
tor->magnetVerify = false;
|
||||
tr_logAddTraceTor(tor, "Magnet Verify");
|
||||
refreshCurrentDir(tor);
|
||||
tor->refreshCurrentDir();
|
||||
tr_torrentVerify(tor);
|
||||
|
||||
callScriptIfEnabled(tor, TR_SCRIPT_ON_TORRENT_ADDED);
|
||||
|
@ -1640,40 +1634,40 @@ void tr_torrentFree(tr_torrent* tor)
|
|||
}
|
||||
}
|
||||
|
||||
struct remove_data
|
||||
static void removeTorrentInEventThread(tr_torrent* tor, bool delete_flag, tr_fileFunc delete_func)
|
||||
{
|
||||
tr_torrent* tor;
|
||||
bool deleteFlag;
|
||||
tr_fileFunc deleteFunc;
|
||||
auto const lock = tor->unique_lock();
|
||||
|
||||
if (delete_flag && tor->hasMetainfo())
|
||||
{
|
||||
// ensure the files are all closed and idle before moving
|
||||
tr_cacheFlushTorrent(tor->session->cache, tor);
|
||||
tr_fdTorrentClose(tor->session, tor->uniqueId);
|
||||
tr_verifyRemove(tor);
|
||||
|
||||
if (delete_func == nullptr)
|
||||
{
|
||||
delete_func = tr_sys_path_remove;
|
||||
}
|
||||
|
||||
auto const delete_func_wrapper = [&delete_func](char const* filename)
|
||||
{
|
||||
delete_func(filename, nullptr);
|
||||
};
|
||||
|
||||
static void tr_torrentDeleteLocalData(tr_torrent* /*tor*/, tr_fileFunc /*func*/);
|
||||
|
||||
static void removeTorrent(struct remove_data* const data)
|
||||
{
|
||||
auto const lock = data->tor->unique_lock();
|
||||
|
||||
if (data->deleteFlag)
|
||||
{
|
||||
tr_torrentDeleteLocalData(data->tor, data->deleteFunc);
|
||||
tor->metainfo_.files().remove(tor->currentDir(), tor->name(), delete_func_wrapper);
|
||||
}
|
||||
|
||||
tr_torrentClearCompletenessCallback(data->tor);
|
||||
closeTorrent(data->tor);
|
||||
tr_free(data);
|
||||
tr_torrentClearCompletenessCallback(tor);
|
||||
closeTorrent(tor);
|
||||
}
|
||||
|
||||
void tr_torrentRemove(tr_torrent* tor, bool deleteFlag, tr_fileFunc deleteFunc)
|
||||
void tr_torrentRemove(tr_torrent* tor, bool delete_flag, tr_fileFunc delete_func)
|
||||
{
|
||||
TR_ASSERT(tr_isTorrent(tor));
|
||||
|
||||
tor->isDeleting = true;
|
||||
|
||||
auto* const data = tr_new0(struct remove_data, 1);
|
||||
data->tor = tor;
|
||||
data->deleteFlag = deleteFlag;
|
||||
data->deleteFunc = deleteFunc;
|
||||
tr_runInEventThread(tor->session, removeTorrent, data);
|
||||
tr_runInEventThread(tor->session, removeTorrentInEventThread, tor, delete_flag, delete_func);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2176,328 +2170,66 @@ uint64_t tr_torrentGetBytesLeftToAllocate(tr_torrent const* tor)
|
|||
return bytes_left;
|
||||
}
|
||||
|
||||
/****
|
||||
***** Removing the torrent's local data
|
||||
****/
|
||||
///
|
||||
|
||||
static bool isJunkFile(std::string_view base)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
// check for resource forks. <http://support.apple.com/kb/TA20578>
|
||||
if (tr_strvStartsWith(base, "._"sv))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
auto constexpr Files = std::array<std::string_view, 3>{
|
||||
".DS_Store"sv,
|
||||
"Thumbs.db"sv,
|
||||
"desktop.ini"sv,
|
||||
};
|
||||
|
||||
return std::find(std::begin(Files), std::end(Files), base) != std::end(Files);
|
||||
}
|
||||
|
||||
static void removeEmptyFoldersAndJunkFiles(char const* folder)
|
||||
{
|
||||
auto const odir = tr_sys_dir_open(folder);
|
||||
if (odir == TR_BAD_SYS_DIR)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
char const* name = nullptr;
|
||||
while ((name = tr_sys_dir_read_name(odir)) != nullptr)
|
||||
{
|
||||
if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
|
||||
{
|
||||
auto const filename = tr_strvPath(folder, name);
|
||||
|
||||
auto info = tr_sys_path_info{};
|
||||
if (tr_sys_path_get_info(filename.c_str(), TR_SYS_PATH_NO_FOLLOW, &info) && info.type == TR_SYS_PATH_IS_DIRECTORY)
|
||||
{
|
||||
removeEmptyFoldersAndJunkFiles(filename.c_str());
|
||||
}
|
||||
else if (isJunkFile(name))
|
||||
{
|
||||
tr_sys_path_remove(filename.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tr_sys_path_remove(folder);
|
||||
tr_sys_dir_close(odir);
|
||||
}
|
||||
|
||||
/**
|
||||
* This convoluted code does something (seemingly) simple:
|
||||
* remove the torrent's local files.
|
||||
*
|
||||
* Fun complications:
|
||||
* 1. Try to preserve the directory hierarchy in the recycle bin.
|
||||
* 2. If there are nontorrent files, don't delete them...
|
||||
* 3. ...unless the other files are "junk", such as .DS_Store
|
||||
*/
|
||||
static void deleteLocalData(tr_torrent const* tor, tr_fileFunc func)
|
||||
{
|
||||
auto files = std::vector<std::string>{};
|
||||
auto folders = std::set<std::string>{};
|
||||
auto const top = std::string{ tor->currentDir() };
|
||||
|
||||
/* don't try to delete local data if the directory's gone missing */
|
||||
if (!tr_sys_path_exists(top.c_str()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* if it's a magnet link, there's nothing to move... */
|
||||
if (!tor->hasMetainfo())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/***
|
||||
**** Move the local data to a new tmpdir
|
||||
***/
|
||||
|
||||
auto tmpdir = tr_strvPath(top, tr_torrentName(tor) + "__XXXXXX"s);
|
||||
tr_sys_dir_create_temp(std::data(tmpdir));
|
||||
|
||||
for (tr_file_index_t f = 0, n = tor->fileCount(); f < n; ++f)
|
||||
{
|
||||
/* try to find the file, looking in the partial and download dirs */
|
||||
auto filename = tr_strvPath(top, tor->fileSubpath(f));
|
||||
|
||||
if (!tr_sys_path_exists(filename.c_str()))
|
||||
{
|
||||
filename += tr_files::PartialFileSuffix;
|
||||
|
||||
if (!tr_sys_path_exists(filename.c_str()))
|
||||
{
|
||||
filename.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/* if we found the file, move it */
|
||||
if (!std::empty(filename))
|
||||
{
|
||||
auto target = tr_strvPath(tmpdir, tor->fileSubpath(f));
|
||||
tr_moveFile(filename, target);
|
||||
files.emplace_back(target);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
**** Remove tmpdir.
|
||||
****
|
||||
**** Try deleting the top-level files & folders to preserve
|
||||
**** the directory hierarchy in the recycle bin.
|
||||
**** If case that fails -- for example, rmdir () doesn't
|
||||
**** delete nonempty folders -- go from the bottom up too.
|
||||
***/
|
||||
|
||||
/* try deleting the local data's top-level files & folders */
|
||||
if (auto const odir = tr_sys_dir_open(tmpdir.c_str()); odir != TR_BAD_SYS_DIR)
|
||||
{
|
||||
char const* name = nullptr;
|
||||
while ((name = tr_sys_dir_read_name(odir)) != nullptr)
|
||||
{
|
||||
if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
|
||||
{
|
||||
auto const file = tr_strvPath(tmpdir, name);
|
||||
(*func)(file.c_str(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
tr_sys_dir_close(odir);
|
||||
}
|
||||
|
||||
/* go from the bottom up */
|
||||
for (auto const& file : files)
|
||||
{
|
||||
auto walk = file;
|
||||
|
||||
while (tr_sys_path_exists(walk.c_str()) && !tr_sys_path_is_same(tmpdir.c_str(), walk.c_str()))
|
||||
{
|
||||
(*func)(walk.c_str(), nullptr);
|
||||
|
||||
walk = tr_sys_path_dirname(walk);
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
**** The local data has been removed.
|
||||
**** What's left in top are empty folders, junk, and user-generated files.
|
||||
**** Remove the first two categories and leave the third.
|
||||
***/
|
||||
|
||||
/* build a list of 'top's child directories that belong to this torrent */
|
||||
for (tr_file_index_t f = 0, n = tor->fileCount(); f < n; ++f)
|
||||
{
|
||||
/* get the directory that this file goes in... */
|
||||
auto const filename = tr_strvPath(top, tor->fileSubpath(f));
|
||||
auto dir = tr_sys_path_dirname(filename);
|
||||
if (std::empty(dir))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* walk up the directory tree until we reach 'top' */
|
||||
if (!tr_sys_path_is_same(top.c_str(), dir.c_str()) && dir == top)
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
auto const parent = tr_sys_path_dirname(dir);
|
||||
|
||||
if (tr_sys_path_is_same(top.c_str(), parent.c_str()) || parent == top)
|
||||
{
|
||||
folders.emplace(dir);
|
||||
break;
|
||||
}
|
||||
|
||||
/* walk upwards to parent */
|
||||
dir = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& folder : folders)
|
||||
{
|
||||
removeEmptyFoldersAndJunkFiles(folder.c_str());
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
tr_sys_path_remove(tmpdir.c_str());
|
||||
}
|
||||
|
||||
static void tr_torrentDeleteLocalData(tr_torrent* tor, tr_fileFunc func)
|
||||
static void setLocationInEventThread(
|
||||
tr_torrent* tor,
|
||||
std::string const& path,
|
||||
bool move_from_old_path,
|
||||
double volatile* setme_progress,
|
||||
int volatile* setme_state)
|
||||
{
|
||||
TR_ASSERT(tr_isTorrent(tor));
|
||||
TR_ASSERT(tr_amInEventThread(tor->session));
|
||||
|
||||
if (func == nullptr)
|
||||
auto ok = bool{ true };
|
||||
if (move_from_old_path)
|
||||
{
|
||||
func = tr_sys_path_remove;
|
||||
if (setme_state != nullptr)
|
||||
{
|
||||
*setme_state = TR_LOC_MOVING;
|
||||
}
|
||||
|
||||
/* close all the files because we're about to delete them */
|
||||
// ensure the files are all closed and idle before moving
|
||||
tr_cacheFlushTorrent(tor->session->cache, tor);
|
||||
tr_fdTorrentClose(tor->session, tor->uniqueId);
|
||||
|
||||
deleteLocalData(tor, func);
|
||||
}
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
||||
struct LocationData
|
||||
{
|
||||
std::string location;
|
||||
|
||||
tr_torrent* tor = nullptr;
|
||||
double volatile* setme_progress = nullptr;
|
||||
int volatile* setme_state = nullptr;
|
||||
|
||||
bool move_from_old_location = false;
|
||||
};
|
||||
|
||||
static void setLocationImpl(struct LocationData* const data)
|
||||
{
|
||||
auto* const tor = data->tor;
|
||||
TR_ASSERT(tr_isTorrent(tor));
|
||||
auto const lock = tor->unique_lock();
|
||||
|
||||
bool err = false;
|
||||
bool const do_move = data->move_from_old_location;
|
||||
auto const& location = data->location;
|
||||
double bytesHandled = 0;
|
||||
|
||||
tr_logAddTraceTor(
|
||||
tor,
|
||||
fmt::format("Moving '{}' location from currentDir '{}' to '{}'", tor->name(), tor->currentDir(), location));
|
||||
|
||||
tr_sys_dir_create(location.c_str(), TR_SYS_DIR_CREATE_PARENTS, 0777);
|
||||
|
||||
if (!tr_sys_path_is_same(location.c_str(), tor->currentDir().c_str()))
|
||||
{
|
||||
/* bad idea to move files while they're being verified... */
|
||||
tr_verifyRemove(tor);
|
||||
|
||||
/* try to move the files.
|
||||
* FIXME: there are still all kinds of nasty cases, like what
|
||||
* if the target directory runs out of space halfway through... */
|
||||
for (tr_file_index_t i = 0, n = tor->fileCount(); !err && i < n; ++i)
|
||||
{
|
||||
auto const file_size = tor->fileSize(i);
|
||||
|
||||
if (auto found = tor->findFile(i); found)
|
||||
{
|
||||
auto const& oldpath = found->filename();
|
||||
auto const newpath = tr_pathbuf{ location, '/', found->subpath() };
|
||||
|
||||
tr_logAddTraceTor(tor, fmt::format("Found file #{}: '{}'", i, oldpath));
|
||||
|
||||
if (do_move && !tr_sys_path_is_same(oldpath, newpath))
|
||||
{
|
||||
tr_error* error = nullptr;
|
||||
|
||||
tr_logAddTraceTor(tor, fmt::format("moving '{}' to '{}'", oldpath, newpath));
|
||||
|
||||
if (!tr_moveFile(oldpath, newpath, &error))
|
||||
ok = tor->metainfo_.files().move(tor->currentDir(), path, setme_progress, tor->name(), &error);
|
||||
if (error != nullptr)
|
||||
{
|
||||
err = true;
|
||||
tr_logAddErrorTor(
|
||||
tor,
|
||||
fmt::format(
|
||||
tr_logAddError(fmt::format(
|
||||
_("Couldn't move '{old_path}' to '{path}': {error} ({error_code})"),
|
||||
fmt::arg("old_path", oldpath),
|
||||
fmt::arg("path", newpath),
|
||||
fmt::arg("old_path", tor->currentDir()),
|
||||
fmt::arg("path", path),
|
||||
fmt::arg("error", error->message),
|
||||
fmt::arg("error_code", error->code)));
|
||||
tr_error_free(error);
|
||||
}
|
||||
tr_error_clear(&error);
|
||||
}
|
||||
}
|
||||
|
||||
if (data->setme_progress != nullptr)
|
||||
// tell the torrent where the files are
|
||||
if (ok)
|
||||
{
|
||||
bytesHandled += file_size;
|
||||
*data->setme_progress = bytesHandled / tor->totalSize();
|
||||
}
|
||||
}
|
||||
tor->setDownloadDir(path);
|
||||
|
||||
if (!err && do_move)
|
||||
{
|
||||
/* blow away the leftover subdirectories in the old location */
|
||||
tr_torrentDeleteLocalData(tor, tr_sys_path_remove);
|
||||
}
|
||||
}
|
||||
|
||||
if (!err)
|
||||
{
|
||||
/* set the new location and reverify */
|
||||
tr_torrentSetDownloadDir(tor, location.c_str());
|
||||
|
||||
if (do_move)
|
||||
if (move_from_old_path)
|
||||
{
|
||||
tor->incomplete_dir.clear();
|
||||
tor->current_dir = tor->downloadDir();
|
||||
}
|
||||
}
|
||||
|
||||
if (data->setme_state != nullptr)
|
||||
if (setme_state != nullptr)
|
||||
{
|
||||
*data->setme_state = err ? TR_LOC_ERROR : TR_LOC_DONE;
|
||||
*setme_state = ok ? TR_LOC_DONE : TR_LOC_ERROR;
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
delete data;
|
||||
}
|
||||
|
||||
void tr_torrent::setLocation(
|
||||
std::string_view location,
|
||||
bool move_from_old_location,
|
||||
std::string_view path,
|
||||
bool move_from_old_path,
|
||||
double volatile* setme_progress,
|
||||
int volatile* setme_state)
|
||||
{
|
||||
|
@ -2506,34 +2238,31 @@ void tr_torrent::setLocation(
|
|||
*setme_state = TR_LOC_MOVING;
|
||||
}
|
||||
|
||||
if (setme_progress != nullptr)
|
||||
{
|
||||
*setme_progress = 0;
|
||||
}
|
||||
|
||||
/* run this in the libtransmission thread */
|
||||
auto* const data = new LocationData{};
|
||||
data->tor = this;
|
||||
data->location = location;
|
||||
data->move_from_old_location = move_from_old_location;
|
||||
data->setme_state = setme_state;
|
||||
data->setme_progress = setme_progress;
|
||||
tr_runInEventThread(this->session, setLocationImpl, data);
|
||||
tr_runInEventThread(
|
||||
this->session,
|
||||
setLocationInEventThread,
|
||||
this,
|
||||
std::string{ path },
|
||||
move_from_old_path,
|
||||
setme_progress,
|
||||
setme_state);
|
||||
}
|
||||
|
||||
void tr_torrentSetLocation(
|
||||
tr_torrent* tor,
|
||||
char const* location,
|
||||
bool move_from_old_location,
|
||||
char const* path,
|
||||
bool move_from_old_path,
|
||||
double volatile* setme_progress,
|
||||
int volatile* setme_state)
|
||||
{
|
||||
TR_ASSERT(tr_isTorrent(tor));
|
||||
TR_ASSERT(!tr_str_is_empty(location));
|
||||
TR_ASSERT(!tr_str_is_empty(path));
|
||||
|
||||
return tor->setLocation(location != nullptr ? location : "", move_from_old_location, setme_progress, setme_state);
|
||||
tor->setLocation(path, move_from_old_path, setme_progress, setme_state);
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
std::string_view tr_torrent::primaryMimeType() const
|
||||
{
|
||||
// count up how many bytes there are for each mime-type in the torrent
|
||||
|
@ -2667,29 +2396,29 @@ char* tr_torrentFindFile(tr_torrent const* tor, tr_file_index_t fileNum)
|
|||
return found ? tr_strdup(found->filename()) : nullptr;
|
||||
}
|
||||
|
||||
/* Decide whether we should be looking for files in downloadDir or incompleteDir. */
|
||||
static void refreshCurrentDir(tr_torrent* tor)
|
||||
// decide whether we should be looking for files in downloadDir or incompleteDir
|
||||
void tr_torrent::refreshCurrentDir()
|
||||
{
|
||||
auto dir = tr_interned_string{};
|
||||
|
||||
if (std::empty(tor->incompleteDir()))
|
||||
if (std::empty(incompleteDir()))
|
||||
{
|
||||
dir = tor->downloadDir();
|
||||
dir = downloadDir();
|
||||
}
|
||||
else if (!tor->hasMetainfo()) /* no files to find */
|
||||
else if (!hasMetainfo()) // no files to find
|
||||
{
|
||||
dir = tor->incompleteDir();
|
||||
dir = incompleteDir();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const found = tor->findFile(0);
|
||||
dir = found ? tr_interned_string{ found->base() } : tor->incompleteDir();
|
||||
auto const found = findFile(0);
|
||||
dir = found ? tr_interned_string{ found->base() } : incompleteDir();
|
||||
}
|
||||
|
||||
TR_ASSERT(!std::empty(dir));
|
||||
TR_ASSERT(dir == tor->downloadDir() || dir == tor->incompleteDir());
|
||||
TR_ASSERT(dir == downloadDir() || dir == incompleteDir());
|
||||
|
||||
tor->current_dir = dir;
|
||||
current_dir = dir;
|
||||
}
|
||||
|
||||
/***
|
||||
|
@ -2881,14 +2610,14 @@ static int renamePath(tr_torrent* tor, char const* oldpath, char const* newname)
|
|||
|
||||
if (!tr_sys_path_exists(src.c_str())) /* check for it as a partial */
|
||||
{
|
||||
src += tr_files::PartialFileSuffix;
|
||||
src += tr_torrent_files::PartialFileSuffix;
|
||||
}
|
||||
|
||||
if (tr_sys_path_exists(src.c_str()))
|
||||
{
|
||||
auto const parent = tr_sys_path_dirname(src);
|
||||
auto const tgt = tr_strvEndsWith(src, tr_files::PartialFileSuffix) ?
|
||||
tr_pathbuf{ parent, '/', newname, tr_files::PartialFileSuffix } :
|
||||
auto const tgt = tr_strvEndsWith(src, tr_torrent_files::PartialFileSuffix) ?
|
||||
tr_pathbuf{ parent, '/', newname, tr_torrent_files::PartialFileSuffix } :
|
||||
tr_pathbuf{ parent, '/', newname };
|
||||
|
||||
auto tmp = errno;
|
||||
|
|
|
@ -368,7 +368,7 @@ public:
|
|||
metainfo_.setFileSubpath(i, subpath);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<tr_files::FoundFile> findFile(tr_file_index_t file_index) const;
|
||||
[[nodiscard]] std::optional<tr_torrent_files::FoundFile> findFile(tr_file_index_t file_index) const;
|
||||
|
||||
[[nodiscard]] bool hasAnyLocalData() const;
|
||||
|
||||
|
@ -559,6 +559,16 @@ public:
|
|||
this->error_string = errmsg;
|
||||
}
|
||||
|
||||
void setDownloadDir(std::string_view path)
|
||||
{
|
||||
download_dir = path;
|
||||
markEdited();
|
||||
setDirty();
|
||||
refreshCurrentDir();
|
||||
}
|
||||
|
||||
void refreshCurrentDir();
|
||||
|
||||
void setVerifyState(tr_verify_state state);
|
||||
|
||||
void setDateActive(time_t t);
|
||||
|
|
|
@ -306,7 +306,7 @@ template<typename... T, typename std::enable_if_t<(std::is_convertible_v<T, std:
|
|||
}
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] constexpr bool tr_strvContains(std::string_view sv, T key) // c++23
|
||||
[[nodiscard]] constexpr bool tr_strvContains(std::string_view sv, T key) noexcept // c++23
|
||||
{
|
||||
return sv.find(key) != sv.npos;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ add_executable(libtransmission-test
|
|||
peer-mgr-wishlist-test.cc
|
||||
peer-msgs-test.cc
|
||||
quark-test.cc
|
||||
remove-test.cc
|
||||
rename-test.cc
|
||||
rpc-test.cc
|
||||
session-test.cc
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "files.h"
|
||||
#include "torrent-files.h"
|
||||
|
||||
#include "test-fixtures.h"
|
||||
|
||||
|
@ -20,14 +20,14 @@ 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));
|
||||
auto files = tr_torrent_files{};
|
||||
EXPECT_EQ(size_t{ 0U }, files.fileCount());
|
||||
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(size_t{ 1U }, files.fileCount());
|
||||
EXPECT_EQ(Size, files.fileSize(file_index));
|
||||
EXPECT_EQ(Path, files.path(file_index));
|
||||
EXPECT_FALSE(std::empty(files));
|
||||
}
|
||||
|
@ -38,14 +38,14 @@ TEST_F(FilesTest, setPath)
|
|||
auto constexpr Path2 = "/hello/there"sv;
|
||||
auto constexpr Size = size_t{ 2048 };
|
||||
|
||||
auto files = tr_files{};
|
||||
auto files = tr_torrent_files{};
|
||||
auto const file_index = files.add(Path1, Size);
|
||||
EXPECT_EQ(Path1, files.path(file_index));
|
||||
EXPECT_EQ(Size, files.size(file_index));
|
||||
EXPECT_EQ(Size, files.fileSize(file_index));
|
||||
|
||||
files.setPath(file_index, Path2);
|
||||
EXPECT_EQ(Path2, files.path(file_index));
|
||||
EXPECT_EQ(Size, files.size(file_index));
|
||||
EXPECT_EQ(Size, files.fileSize(file_index));
|
||||
}
|
||||
|
||||
TEST_F(FilesTest, clear)
|
||||
|
@ -54,15 +54,15 @@ TEST_F(FilesTest, clear)
|
|||
auto constexpr Path2 = "/hello/there"sv;
|
||||
auto constexpr Size = size_t{ 2048 };
|
||||
|
||||
auto files = tr_files{};
|
||||
auto files = tr_torrent_files{};
|
||||
files.add(Path1, Size);
|
||||
EXPECT_EQ(size_t{ 1U }, std::size(files));
|
||||
EXPECT_EQ(size_t{ 1U }, files.fileCount());
|
||||
files.add(Path2, Size);
|
||||
EXPECT_EQ(size_t{ 2U }, std::size(files));
|
||||
EXPECT_EQ(size_t{ 2U }, files.fileCount());
|
||||
|
||||
files.clear();
|
||||
EXPECT_TRUE(std::empty(files));
|
||||
EXPECT_EQ(size_t{ 0U }, std::size(files));
|
||||
EXPECT_EQ(size_t{ 0U }, files.fileCount());
|
||||
}
|
||||
|
||||
TEST_F(FilesTest, find)
|
||||
|
@ -71,7 +71,7 @@ TEST_F(FilesTest, find)
|
|||
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 files = tr_torrent_files{};
|
||||
auto const file_index = files.add("first_dir/hello.txt", 1024);
|
||||
|
||||
auto const search_path_1 = tr_pathbuf{ sandboxDir() };
|
||||
|
@ -89,7 +89,7 @@ TEST_F(FilesTest, find)
|
|||
EXPECT_EQ(filename, found->filename());
|
||||
|
||||
// now make it an incomplete file
|
||||
auto const partial_filename = tr_pathbuf{ filename, tr_files::PartialFileSuffix };
|
||||
auto const partial_filename = tr_pathbuf{ filename, tr_torrent_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));
|
||||
|
@ -113,7 +113,7 @@ TEST_F(FilesTest, hasAnyLocalData)
|
|||
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 files = tr_torrent_files{};
|
||||
files.add("first_dir/hello.txt", 1024);
|
||||
|
||||
auto const search_path_1 = tr_pathbuf{ sandboxDir() };
|
||||
|
|
|
@ -58,7 +58,7 @@ TEST_P(IncompleteDirTest, incompleteDir)
|
|||
auto* const tor = zeroTorrentInit(ZeroTorrentState::Partial);
|
||||
auto path = tr_pathbuf{};
|
||||
|
||||
path.assign(incomplete_dir, '/', tr_torrentFile(tor, 0).name, ".part"sv);
|
||||
path.assign(incomplete_dir, '/', tr_torrentFile(tor, 0).name, tr_torrent_files::PartialFileSuffix);
|
||||
EXPECT_EQ(path, makeString(tr_torrentFindFile(tor, 0)));
|
||||
path.assign(incomplete_dir, '/', tr_torrentFile(tor, 1).name);
|
||||
EXPECT_EQ(path, makeString(tr_torrentFindFile(tor, 1)));
|
||||
|
|
|
@ -0,0 +1,341 @@
|
|||
// 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 <array>
|
||||
#include <cstdio>
|
||||
#include <set>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "transmission.h"
|
||||
|
||||
#include "file.h"
|
||||
#include "torrent-files.h"
|
||||
#include "tr-strbuf.h"
|
||||
|
||||
#include "test-fixtures.h"
|
||||
|
||||
using namespace std::literals;
|
||||
using SubpathAndSize = std::pair<std::string_view, uint64_t>;
|
||||
|
||||
class RemoveTest : public libtransmission::test::SandboxedTest
|
||||
{
|
||||
protected:
|
||||
static constexpr std::string_view Content = "Hello, World!"sv;
|
||||
static constexpr std::string_view JunkBasename = ".DS_Store"sv;
|
||||
static constexpr std::string_view NonJunkBasename = "passwords.txt"sv;
|
||||
|
||||
static void sysPathRemove(char const* filename)
|
||||
{
|
||||
tr_sys_path_remove(filename, nullptr);
|
||||
}
|
||||
|
||||
static auto aliceFiles()
|
||||
{
|
||||
static constexpr std::array<SubpathAndSize, 106> AliceFiles = { {
|
||||
{ "alice_in_wonderland_librivox/AliceInWonderland_librivox.m4b", 87525736ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland.jpg", 81464ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland.pdf", 185367ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland_abbyy.gz", 24582ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland_chocr.html.gz", 22527ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland_djvu.txt", 2039ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland_djvu.xml", 28144ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland_hocr.html", 56942ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland_hocr_pageindex.json.gz", 40ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland_hocr_searchtext.txt.gz", 943ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland_jp2.zip", 1499986ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland_page_numbers.json", 136ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland_scandata.xml", 538ULL },
|
||||
{ "alice_in_wonderland_librivox/Alice_in_Wonderland_thumb.jpg", 26987ULL },
|
||||
{ "alice_in_wonderland_librivox/__ia_thumb.jpg", 16557ULL },
|
||||
{ "alice_in_wonderland_librivox/alice_in_wonderland_librivox.json", 13740ULL },
|
||||
{ "alice_in_wonderland_librivox/alice_in_wonderland_librivox.storj-store.trigger", 0ULL },
|
||||
{ "alice_in_wonderland_librivox/alice_in_wonderland_librivox_128kb.m3u", 984ULL },
|
||||
{ "alice_in_wonderland_librivox/alice_in_wonderland_librivox_64kb.m3u", 1044ULL },
|
||||
{ "alice_in_wonderland_librivox/alice_in_wonderland_librivox_meta.sqlite", 20480ULL },
|
||||
{ "alice_in_wonderland_librivox/alice_in_wonderland_librivox_meta.xml", 2805ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_01.mp3", 10249859ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_01.ogg", 7509828ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_01.png", 10779ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_01_64kb.mp3", 5124992ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_01_esshigh.json.gz", 1977ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_01_esslow.json.gz", 29258ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_01_spectrogram.png", 234022ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_02.mp3", 11772312ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_02.ogg", 5148365ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_02.png", 10962ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_02_64kb.mp3", 5886455ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_02_esshigh.json.gz", 1980ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_02_esslow.json.gz", 30287ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_02_spectrogram.png", 326161ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_03.mp3", 17024560ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_03.ogg", 12046177ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_03.png", 8725ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_03_64kb.mp3", 8512448ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_03_esshigh.json.gz", 1966ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_03_esslow.json.gz", 34110ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_03_spectrogram.png", 218264ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_04.mp3", 19087768ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_04.ogg", 9880920ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_04.png", 6055ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_04_64kb.mp3", 9544016ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_04_esshigh.json.gz", 1967ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_04_esslow.json.gz", 36154ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_04_spectrogram.png", 282145ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_05.mp3", 12946949ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_05.ogg", 7470734ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_05.png", 12061ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_05_64kb.mp3", 6473687ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_05_esshigh.json.gz", 1963ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_05_esslow.json.gz", 31048ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_05_spectrogram.png", 304150ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_06.mp3", 12413304ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_06.ogg", 7154040ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_06.png", 11383ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_06_64kb.mp3", 6206820ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_06_esshigh.json.gz", 1942ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_06_esslow.json.gz", 30295ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_06_spectrogram.png", 288601ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_07.mp3", 16742808ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_07.ogg", 10513847ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_07.png", 8180ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_07_64kb.mp3", 8371136ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_07_esshigh.json.gz", 1963ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_07_esslow.json.gz", 33992ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_07_spectrogram.png", 233725ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_08.mp3", 12784781ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_08.ogg", 9306961ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_08.png", 11470ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_08_64kb.mp3", 6392576ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_08_esshigh.json.gz", 1973ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_08_esslow.json.gz", 31964ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_08_spectrogram.png", 233626ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_09.mp3", 14528920ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_09.ogg", 8062952ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_09.png", 9439ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_09_64kb.mp3", 7264466ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_09_esshigh.json.gz", 1946ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_09_esslow.json.gz", 32295ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_09_spectrogram.png", 282898ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_10.mp3", 21894203ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_10.ogg", 15226220ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_10.png", 8796ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_10_64kb.mp3", 10947200ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_10_esshigh.json.gz", 1970ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_10_esslow.json.gz", 37062ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_10_spectrogram.png", 221277ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_11.mp3", 9919894ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_11.ogg", 7676067ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_11.png", 7140ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_11_64kb.mp3", 4959296ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_11_esshigh.json.gz", 1959ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_11_esslow.json.gz", 28694ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_11_spectrogram.png", 229471ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_12.mp3", 12359368ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_12.ogg", 8065179ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_12.png", 11074ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_12_64kb.mp3", 6179840ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_12_esshigh.json.gz", 1981ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_12_esslow.json.gz", 30975ULL },
|
||||
{ "alice_in_wonderland_librivox/wonderland_ch_12_spectrogram.png", 235568ULL },
|
||||
{ "alice_in_wonderland_librivox/history/files/alice_in_wonderland_librivox.storj-store.trigger.~1~", 0ULL },
|
||||
} };
|
||||
|
||||
auto files = tr_torrent_files{};
|
||||
|
||||
for (auto const& file : AliceFiles)
|
||||
{
|
||||
auto const& [filename, size] = file;
|
||||
files.add(filename, size);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
static auto ubuntuFiles()
|
||||
{
|
||||
static auto constexpr Files = std::array<SubpathAndSize, 1>{ { { "ubuntu-20.04.4-desktop-amd64.iso"sv,
|
||||
3379068928ULL } } };
|
||||
|
||||
auto files = tr_torrent_files{};
|
||||
|
||||
for (auto const& file : Files)
|
||||
{
|
||||
auto const& [filename, size] = file;
|
||||
files.add(filename, size);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
auto createFiles(tr_torrent_files const& files, char const* parent)
|
||||
{
|
||||
auto paths = std::set<std::string>{};
|
||||
|
||||
for (tr_file_index_t i = 0, n = files.fileCount(); i < n; ++i)
|
||||
{
|
||||
auto filename = tr_pathbuf{ parent, '/', files.path(i) };
|
||||
createFileWithContents(filename, std::data(Content), std::size(Content));
|
||||
paths.emplace(filename);
|
||||
|
||||
while (!tr_sys_path_is_same(parent, filename))
|
||||
{
|
||||
filename = tr_sys_path_dirname(filename);
|
||||
paths.emplace(filename);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
static auto getSubtreeContents(std::string_view parent_dir)
|
||||
{
|
||||
auto filenames = std::set<std::string>{};
|
||||
|
||||
auto file_func = [&filenames](char const* filename)
|
||||
{
|
||||
filenames.emplace(filename);
|
||||
};
|
||||
|
||||
libtransmission::test::depthFirstWalk(tr_pathbuf{ parent_dir }, file_func);
|
||||
|
||||
return filenames;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(RemoveTest, RemovesSingleFile)
|
||||
{
|
||||
auto const parent = sandboxDir();
|
||||
auto expected_tree = std::set<std::string>{ parent };
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
auto const files = ubuntuFiles();
|
||||
expected_tree = createFiles(files, parent.c_str());
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
files.remove(parent, "tmpdir_prefix"sv, sysPathRemove);
|
||||
expected_tree = { parent };
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
}
|
||||
|
||||
TEST_F(RemoveTest, RemovesSubtree)
|
||||
{
|
||||
auto const parent = sandboxDir();
|
||||
auto expected_tree = std::set<std::string>{ parent };
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
auto const files = aliceFiles();
|
||||
expected_tree = createFiles(files, parent.c_str());
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
files.remove(parent, "tmpdir_prefix"sv, sysPathRemove);
|
||||
expected_tree = { parent };
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
}
|
||||
|
||||
TEST_F(RemoveTest, RemovesLeftoverJunk)
|
||||
{
|
||||
auto const parent = sandboxDir();
|
||||
auto expected_tree = std::set<std::string>{ parent };
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
auto const files = aliceFiles();
|
||||
expected_tree = createFiles(files, parent.c_str());
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
// add a junk file *inside of* the torrent's top directory.
|
||||
auto const junk_file = tr_pathbuf{ parent, "/alice_in_wonderland_librivox/", JunkBasename };
|
||||
createFileWithContents(junk_file, std::data(Content), std::size(Content));
|
||||
expected_tree.emplace(junk_file);
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
files.remove(parent, "tmpdir_prefix"sv, sysPathRemove);
|
||||
expected_tree = { parent };
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
}
|
||||
|
||||
TEST_F(RemoveTest, LeavesSiblingsAlone)
|
||||
{
|
||||
auto const parent = sandboxDir();
|
||||
auto expected_tree = std::set<std::string>{ parent };
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
auto const files = aliceFiles();
|
||||
expected_tree = createFiles(files, parent.c_str());
|
||||
EXPECT_GT(std::size(expected_tree), 100U);
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
// add a junk file *as a sibling of* the torrent's top directory.
|
||||
auto const junk_file = tr_pathbuf{ parent, '/', JunkBasename };
|
||||
createFileWithContents(junk_file, std::data(Content), std::size(Content));
|
||||
expected_tree.emplace(junk_file);
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
// add a non-junk file *as a sibling of* the torrent's top directory.
|
||||
auto const non_junk_file = tr_pathbuf{ parent, '/', NonJunkBasename };
|
||||
createFileWithContents(non_junk_file, std::data(Content), std::size(Content));
|
||||
expected_tree.emplace(non_junk_file);
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
files.remove(parent, "tmpdir_prefix"sv, sysPathRemove);
|
||||
expected_tree = { parent, junk_file.c_str(), non_junk_file.c_str() };
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
}
|
||||
|
||||
TEST_F(RemoveTest, LeavesNonJunkAlone)
|
||||
{
|
||||
auto const parent = sandboxDir();
|
||||
auto expected_tree = std::set<std::string>{ parent };
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
auto const files = aliceFiles();
|
||||
expected_tree = createFiles(files, parent.c_str());
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
// add a non-junk file.
|
||||
auto const nonjunk_file = tr_pathbuf{ parent, "/alice_in_wonderland_librivox/", NonJunkBasename };
|
||||
createFileWithContents(nonjunk_file, std::data(Content), std::size(Content));
|
||||
expected_tree.emplace(nonjunk_file);
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
files.remove(parent, "tmpdir_prefix"sv, sysPathRemove);
|
||||
expected_tree = { parent, tr_sys_path_dirname(nonjunk_file), nonjunk_file.c_str() };
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
}
|
||||
|
||||
TEST_F(RemoveTest, PreservesDirectoryHierarchyIfPossible)
|
||||
{
|
||||
auto const parent = sandboxDir();
|
||||
auto expected_tree = std::set<std::string>{ parent };
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
// add a recycle bin
|
||||
auto const recycle_bin = tr_pathbuf{ parent, "/Trash"sv };
|
||||
tr_sys_dir_create(recycle_bin, TR_SYS_DIR_CREATE_PARENTS, 0777);
|
||||
expected_tree.emplace(recycle_bin);
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
auto const files = aliceFiles();
|
||||
expected_tree = createFiles(files, parent.c_str());
|
||||
expected_tree.emplace(recycle_bin);
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
|
||||
auto const recycle_func = [&recycle_bin](char const* filename)
|
||||
{
|
||||
tr_sys_path_rename(filename, tr_pathbuf{ recycle_bin, '/', tr_sys_path_basename(filename) });
|
||||
};
|
||||
files.remove(parent, "tmpdir_prefix"sv, recycle_func);
|
||||
|
||||
// after remove, the subtree should be:
|
||||
expected_tree = { parent, recycle_bin.c_str() };
|
||||
for (tr_file_index_t i = 0, n = files.fileCount(); i < n; ++i)
|
||||
{
|
||||
expected_tree.emplace(tr_pathbuf{ recycle_bin, '/', files.path(i) });
|
||||
}
|
||||
expected_tree.emplace(tr_pathbuf{ recycle_bin, "/alice_in_wonderland_librivox"sv });
|
||||
expected_tree.emplace(tr_pathbuf{ recycle_bin, "/alice_in_wonderland_librivox/history"sv });
|
||||
expected_tree.emplace(tr_pathbuf{ recycle_bin, "/alice_in_wonderland_librivox/history/files"sv });
|
||||
EXPECT_EQ(expected_tree, getSubtreeContents(parent));
|
||||
}
|
|
@ -39,6 +39,32 @@ namespace libtransmission
|
|||
namespace test
|
||||
{
|
||||
|
||||
using file_func_t = std::function<void(char const* filename)>;
|
||||
|
||||
static void depthFirstWalk(char const* path, file_func_t func)
|
||||
{
|
||||
auto info = tr_sys_path_info{};
|
||||
if (tr_sys_path_get_info(path, 0, &info) && (info.type == TR_SYS_PATH_IS_DIRECTORY))
|
||||
{
|
||||
if (auto const odir = tr_sys_dir_open(path); odir != TR_BAD_SYS_DIR)
|
||||
{
|
||||
char const* name;
|
||||
while ((name = tr_sys_dir_read_name(odir)) != nullptr)
|
||||
{
|
||||
if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
|
||||
{
|
||||
auto const filename = tr_strvPath(path, name);
|
||||
depthFirstWalk(tr_strvPath(path, name).c_str(), func);
|
||||
}
|
||||
}
|
||||
|
||||
tr_sys_dir_close(odir);
|
||||
}
|
||||
}
|
||||
|
||||
func(path);
|
||||
}
|
||||
|
||||
inline std::string makeString(char*&& s)
|
||||
{
|
||||
auto const ret = std::string(s != nullptr ? s : "");
|
||||
|
@ -116,32 +142,6 @@ protected:
|
|||
return path;
|
||||
}
|
||||
|
||||
using file_func_t = std::function<void(char const* filename)>;
|
||||
|
||||
static void depthFirstWalk(char const* path, file_func_t func)
|
||||
{
|
||||
auto info = tr_sys_path_info{};
|
||||
if (tr_sys_path_get_info(path, 0, &info) && (info.type == TR_SYS_PATH_IS_DIRECTORY))
|
||||
{
|
||||
if (auto const odir = tr_sys_dir_open(path); odir != TR_BAD_SYS_DIR)
|
||||
{
|
||||
char const* name;
|
||||
while ((name = tr_sys_dir_read_name(odir)) != nullptr)
|
||||
{
|
||||
if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
|
||||
{
|
||||
auto const filename = tr_strvPath(path, name);
|
||||
depthFirstWalk(tr_strvPath(path, name).c_str(), func);
|
||||
}
|
||||
}
|
||||
|
||||
tr_sys_dir_close(odir);
|
||||
}
|
||||
}
|
||||
|
||||
func(path);
|
||||
}
|
||||
|
||||
static void rimraf(std::string const& path, bool verbose = false)
|
||||
{
|
||||
auto remove = [verbose](char const* filename)
|
||||
|
@ -227,14 +227,14 @@ protected:
|
|||
errno = tmperr;
|
||||
}
|
||||
|
||||
void createFileWithContents(std::string const& path, void const* payload, size_t n) const
|
||||
void createFileWithContents(std::string_view path, void const* payload, size_t n) const
|
||||
{
|
||||
auto const tmperr = errno;
|
||||
|
||||
buildParentDir(path);
|
||||
|
||||
auto const fd = tr_sys_file_open(
|
||||
path.c_str(),
|
||||
tr_pathbuf{ path },
|
||||
TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | TR_SYS_FILE_TRUNCATE,
|
||||
0600,
|
||||
nullptr);
|
||||
|
|
|
@ -541,7 +541,6 @@ TEST_F(VariantTest, dictFindType)
|
|||
TEST_F(VariantTest, variantFromBufFuzz)
|
||||
{
|
||||
auto buf = std::vector<char>{};
|
||||
auto top = tr_variant{};
|
||||
|
||||
for (size_t i = 0; i < 100000; ++i)
|
||||
{
|
||||
|
@ -550,7 +549,16 @@ TEST_F(VariantTest, variantFromBufFuzz)
|
|||
auto const sv = std::string_view{ std::data(buf), std::size(buf) };
|
||||
// std::cerr << '[' << tr_base64_encode({ std::data(buf), std::size(buf) }) << ']' << std::endl;
|
||||
|
||||
tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, sv, nullptr, nullptr);
|
||||
tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, sv, nullptr, nullptr);
|
||||
if (auto top = tr_variant{};
|
||||
tr_variantFromBuf(&top, TR_VARIANT_PARSE_JSON | TR_VARIANT_PARSE_INPLACE, sv, nullptr, nullptr))
|
||||
{
|
||||
tr_variantFree(&top);
|
||||
}
|
||||
|
||||
if (auto top = tr_variant{};
|
||||
tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, sv, nullptr, nullptr))
|
||||
{
|
||||
tr_variantFree(&top);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue