mirror of
https://github.com/transmission/transmission.git
synced 2025-12-20 02:18:42 +00:00
refactor: add tr_torrent_files::move() and remove() (#2919)
This commit is contained in:
@@ -14,8 +14,8 @@
|
|||||||
1BB44E07B1B52E28291B4E32 /* file-piece-map.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1BB44E07B1B52E28291B4E30 /* file-piece-map.cc */; };
|
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 */; };
|
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 */; };
|
2856E0656A49F2665D69E760 /* benc.h in Headers */ = {isa = PBXBuildFile; fileRef = 2856E0656A49F2665D69E761 /* benc.h */; };
|
||||||
A47A7C87B8B57BE50DF0D410 /* files.cc in Sources */ = {isa = PBXBuildFile; fileRef = A47A7C87B8B57BE50DF0D411 /* files.cc */; };
|
A47A7C87B8B57BE50DF0D410 /* torrent-files.cc in Sources */ = {isa = PBXBuildFile; fileRef = A47A7C87B8B57BE50DF0D411 /* torrent-files.cc */; };
|
||||||
A47A7C87B8B57BE50DF0D412 /* files.h in Headers */ = {isa = PBXBuildFile; fileRef = A47A7C87B8B57BE50DF0D413 /* files.h */; };
|
A47A7C87B8B57BE50DF0D412 /* torrent-files.h in Headers */ = {isa = PBXBuildFile; fileRef = A47A7C87B8B57BE50DF0D413 /* torrent-files.h */; };
|
||||||
2B9BA6C508B488FE586A0AB0 /* torrents.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2B9BA6C508B488FE586A0AB1 /* torrents.cc */; };
|
2B9BA6C508B488FE586A0AB0 /* torrents.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2B9BA6C508B488FE586A0AB1 /* torrents.cc */; };
|
||||||
2B9BA6C508B488FE586A0AB2 /* torrents.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B9BA6C508B488FE586A0AB3 /* torrents.h */; };
|
2B9BA6C508B488FE586A0AB2 /* torrents.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B9BA6C508B488FE586A0AB3 /* torrents.h */; };
|
||||||
35F373030C2DA89000DAA8F2 /* FilePriorityCell.mm in Sources */ = {isa = PBXBuildFile; fileRef = 35F373010C2DA88F00DAA8F2 /* FilePriorityCell.mm */; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
A47A7C87B8B57BE50DF0D411 /* torrent-files.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = torrent-files.cc; sourceTree = "<group>"; };
|
||||||
A47A7C87B8B57BE50DF0D413 /* files.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = files.h; 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>"; };
|
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>"; };
|
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>"; };
|
35F373010C2DA88F00DAA8F2 /* FilePriorityCell.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FilePriorityCell.mm; sourceTree = "<group>"; };
|
||||||
@@ -1567,7 +1567,7 @@
|
|||||||
A29DF8B70DB2544C00D04E5A /* resume.h */,
|
A29DF8B70DB2544C00D04E5A /* resume.h */,
|
||||||
A29DF8B80DB2544C00D04E5A /* torrent.h */,
|
A29DF8B80DB2544C00D04E5A /* torrent.h */,
|
||||||
2B9BA6C508B488FE586A0AB3 /* torrents.h */,
|
2B9BA6C508B488FE586A0AB3 /* torrents.h */,
|
||||||
A47A7C87B8B57BE50DF0D413 /* files.h */,
|
A47A7C87B8B57BE50DF0D413 /* torrent-files.h */,
|
||||||
C1033E031A3279B800EF44D8 /* crypto-utils-fallback.cc */,
|
C1033E031A3279B800EF44D8 /* crypto-utils-fallback.cc */,
|
||||||
C1033E041A3279B800EF44D8 /* crypto-utils-ccrypto.cc */,
|
C1033E041A3279B800EF44D8 /* crypto-utils-ccrypto.cc */,
|
||||||
C1033E051A3279B800EF44D8 /* crypto-utils.cc */,
|
C1033E051A3279B800EF44D8 /* crypto-utils.cc */,
|
||||||
@@ -1615,7 +1615,7 @@
|
|||||||
A2AA9BE0132CAC8D00FA131E /* announcer-udp.cc */,
|
A2AA9BE0132CAC8D00FA131E /* announcer-udp.cc */,
|
||||||
BEFC1DF90C07861A00B0BB3C /* torrent.cc */,
|
BEFC1DF90C07861A00B0BB3C /* torrent.cc */,
|
||||||
2B9BA6C508B488FE586A0AB1 /* torrents.cc */,
|
2B9BA6C508B488FE586A0AB1 /* torrents.cc */,
|
||||||
A47A7C87B8B57BE50DF0D411 /* files.cc */,
|
A47A7C87B8B57BE50DF0D411 /* torrent-files.cc */,
|
||||||
BEFC1DFC0C07861A00B0BB3C /* port-forwarding.h */,
|
BEFC1DFC0C07861A00B0BB3C /* port-forwarding.h */,
|
||||||
BEFC1DFD0C07861A00B0BB3C /* port-forwarding.cc */,
|
BEFC1DFD0C07861A00B0BB3C /* port-forwarding.cc */,
|
||||||
A21FBBA90EDA78C300BC3C51 /* bandwidth.h */,
|
A21FBBA90EDA78C300BC3C51 /* bandwidth.h */,
|
||||||
@@ -2109,7 +2109,7 @@
|
|||||||
A29DF8BA0DB2544C00D04E5A /* resume.h in Headers */,
|
A29DF8BA0DB2544C00D04E5A /* resume.h in Headers */,
|
||||||
A29DF8BB0DB2544C00D04E5A /* torrent.h in Headers */,
|
A29DF8BB0DB2544C00D04E5A /* torrent.h in Headers */,
|
||||||
2B9BA6C508B488FE586A0AB2 /* torrents.h in Headers */,
|
2B9BA6C508B488FE586A0AB2 /* torrents.h in Headers */,
|
||||||
A47A7C87B8B57BE50DF0D412 /* files.h in Headers */,
|
A47A7C87B8B57BE50DF0D412 /* torrent-files.h in Headers */,
|
||||||
A29DF8BE0DB2545F00D04E5A /* verify.h in Headers */,
|
A29DF8BE0DB2545F00D04E5A /* verify.h in Headers */,
|
||||||
C1FEE57B1C3223CC00D62832 /* watchdir.h in Headers */,
|
C1FEE57B1C3223CC00D62832 /* watchdir.h in Headers */,
|
||||||
A2AAB6650DE0D08B00E04DDA /* blocklist.h in Headers */,
|
A2AAB6650DE0D08B00E04DDA /* blocklist.h in Headers */,
|
||||||
@@ -2786,7 +2786,7 @@
|
|||||||
BEFC1E2F0C07861A00B0BB3C /* session.cc in Sources */,
|
BEFC1E2F0C07861A00B0BB3C /* session.cc in Sources */,
|
||||||
BEFC1E320C07861A00B0BB3C /* torrent.cc in Sources */,
|
BEFC1E320C07861A00B0BB3C /* torrent.cc in Sources */,
|
||||||
2B9BA6C508B488FE586A0AB0 /* torrents.cc in Sources */,
|
2B9BA6C508B488FE586A0AB0 /* torrents.cc in Sources */,
|
||||||
A47A7C87B8B57BE50DF0D410 /* files.cc */,
|
A47A7C87B8B57BE50DF0D410 /* torrent-files.cc */,
|
||||||
BEFC1E360C07861A00B0BB3C /* port-forwarding.cc in Sources */,
|
BEFC1E360C07861A00B0BB3C /* port-forwarding.cc in Sources */,
|
||||||
BEFC1E3C0C07861A00B0BB3C /* platform.cc in Sources */,
|
BEFC1E3C0C07861A00B0BB3C /* platform.cc in Sources */,
|
||||||
BEFC1E460C07861A00B0BB3C /* net.cc in Sources */,
|
BEFC1E460C07861A00B0BB3C /* net.cc in Sources */,
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ set(PROJECT_FILES
|
|||||||
file-posix.cc
|
file-posix.cc
|
||||||
file-win32.cc
|
file-win32.cc
|
||||||
file.cc
|
file.cc
|
||||||
files.cc
|
|
||||||
handshake.cc
|
handshake.cc
|
||||||
inout.cc
|
inout.cc
|
||||||
log.cc
|
log.cc
|
||||||
@@ -58,6 +57,7 @@ set(PROJECT_FILES
|
|||||||
subprocess-posix.cc
|
subprocess-posix.cc
|
||||||
subprocess-win32.cc
|
subprocess-win32.cc
|
||||||
torrent-ctor.cc
|
torrent-ctor.cc
|
||||||
|
torrent-files.cc
|
||||||
torrent-magnet.cc
|
torrent-magnet.cc
|
||||||
torrent-metainfo.cc
|
torrent-metainfo.cc
|
||||||
torrent.cc
|
torrent.cc
|
||||||
@@ -171,7 +171,6 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
|
|||||||
fdlimit.h
|
fdlimit.h
|
||||||
file-info.h
|
file-info.h
|
||||||
file-piece-map.h
|
file-piece-map.h
|
||||||
files.h
|
|
||||||
handshake.h
|
handshake.h
|
||||||
history.h
|
history.h
|
||||||
inout.h
|
inout.h
|
||||||
@@ -195,6 +194,7 @@ set(${PROJECT_NAME}_PRIVATE_HEADERS
|
|||||||
session.h
|
session.h
|
||||||
stats.h
|
stats.h
|
||||||
subprocess.h
|
subprocess.h
|
||||||
|
torrent-files.h
|
||||||
torrent-magnet.h
|
torrent-magnet.h
|
||||||
torrent-metainfo.h
|
torrent-metainfo.h
|
||||||
torrent.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.
|
// 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.
|
// Let's figure out where it goes so that we can create it.
|
||||||
auto const base = tor->currentDir();
|
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) };
|
found = { {}, tr_pathbuf{ base, "/"sv, tor->fileSubpath(file_index), suffix }, std::size(base) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
312
libtransmission/torrent-files.cc
Normal file
312
libtransmission/torrent-files.cc
Normal file
@@ -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
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint> // uint64_t
|
||||||
|
#include <functional>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -16,23 +17,77 @@
|
|||||||
#include "file.h"
|
#include "file.h"
|
||||||
#include "tr-strbuf.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:
|
public:
|
||||||
[[nodiscard]] bool empty() const noexcept;
|
[[nodiscard]] bool empty() const noexcept
|
||||||
[[nodiscard]] size_t size() const noexcept;
|
{
|
||||||
[[nodiscard]] uint64_t size(tr_file_index_t) const;
|
return std::empty(files_);
|
||||||
[[nodiscard]] std::string const& path(tr_file_index_t) const;
|
}
|
||||||
|
|
||||||
void setPath(tr_file_index_t, std::string_view path);
|
[[nodiscard]] size_t fileCount() const noexcept
|
||||||
|
{
|
||||||
|
return std::size(files_);
|
||||||
|
}
|
||||||
|
|
||||||
void reserve(size_t);
|
[[nodiscard]] uint64_t fileSize(tr_file_index_t file_index) const
|
||||||
void shrinkToFit();
|
{
|
||||||
void clear() noexcept;
|
return files_.at(file_index).size_;
|
||||||
tr_file_index_t add(std::string_view path, uint64_t 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
|
struct FoundFile : public tr_sys_path_info
|
||||||
{
|
{
|
||||||
@@ -92,4 +147,5 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::vector<file_t> files_;
|
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);
|
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 } + '/';
|
return std::string{ url } + '/';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
#include "transmission.h"
|
#include "transmission.h"
|
||||||
|
|
||||||
#include "block-info.h"
|
#include "block-info.h"
|
||||||
#include "files.h"
|
|
||||||
#include "magnet-metainfo.h"
|
#include "magnet-metainfo.h"
|
||||||
|
#include "torrent-files.h"
|
||||||
#include "tr-strbuf.h"
|
#include "tr-strbuf.h"
|
||||||
|
|
||||||
struct tr_error;
|
struct tr_error;
|
||||||
@@ -44,11 +44,11 @@ public:
|
|||||||
}
|
}
|
||||||
[[nodiscard]] auto fileCount() const noexcept
|
[[nodiscard]] auto fileCount() const noexcept
|
||||||
{
|
{
|
||||||
return std::size(files());
|
return files().fileCount();
|
||||||
}
|
}
|
||||||
[[nodiscard]] auto fileSize(tr_file_index_t i) const
|
[[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
|
[[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_block_info block_info_ = tr_block_info{ 0, 0 };
|
||||||
|
|
||||||
tr_files files_;
|
tr_torrent_files files_;
|
||||||
|
|
||||||
std::vector<tr_sha1_digest_t> pieces_;
|
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;
|
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 paths = std::array<std::string_view, 4>{};
|
||||||
auto const n_paths = buildSearchPathArray(this, std::data(paths));
|
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
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -685,8 +685,6 @@ static bool isNewTorrentASeed(tr_torrent* tor)
|
|||||||
return tor->ensurePieceIsChecked(0);
|
return tor->ensurePieceIsChecked(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void refreshCurrentDir(tr_torrent* tor);
|
|
||||||
|
|
||||||
static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
|
static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
|
||||||
{
|
{
|
||||||
auto const lock = tor->unique_lock();
|
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_ctorInitTorrentPriorities(ctor, tor);
|
||||||
tr_ctorInitTorrentWanted(ctor, tor);
|
tr_ctorInitTorrentWanted(ctor, tor);
|
||||||
|
|
||||||
refreshCurrentDir(tor);
|
tor->refreshCurrentDir();
|
||||||
|
|
||||||
bool const doStart = tor->isRunning;
|
bool const doStart = tor->isRunning;
|
||||||
tor->isRunning = false;
|
tor->isRunning = false;
|
||||||
@@ -904,12 +902,8 @@ void tr_torrentSetDownloadDir(tr_torrent* tor, char const* path)
|
|||||||
|
|
||||||
if (tor->download_dir != path)
|
if (tor->download_dir != path)
|
||||||
{
|
{
|
||||||
tor->download_dir = path;
|
tor->setDownloadDir(path);
|
||||||
tor->markEdited();
|
|
||||||
tor->setDirty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshCurrentDir(tor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
char const* tr_torrentGetDownloadDir(tr_torrent const* tor)
|
char const* tr_torrentGetDownloadDir(tr_torrent const* tor)
|
||||||
@@ -1576,7 +1570,7 @@ static void stopTorrent(tr_torrent* const tor)
|
|||||||
{
|
{
|
||||||
tor->magnetVerify = false;
|
tor->magnetVerify = false;
|
||||||
tr_logAddTraceTor(tor, "Magnet Verify");
|
tr_logAddTraceTor(tor, "Magnet Verify");
|
||||||
refreshCurrentDir(tor);
|
tor->refreshCurrentDir();
|
||||||
tr_torrentVerify(tor);
|
tr_torrentVerify(tor);
|
||||||
|
|
||||||
callScriptIfEnabled(tor, TR_SCRIPT_ON_TORRENT_ADDED);
|
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;
|
auto const lock = tor->unique_lock();
|
||||||
bool deleteFlag;
|
|
||||||
tr_fileFunc deleteFunc;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void tr_torrentDeleteLocalData(tr_torrent* /*tor*/, tr_fileFunc /*func*/);
|
if (delete_flag && tor->hasMetainfo())
|
||||||
|
|
||||||
static void removeTorrent(struct remove_data* const data)
|
|
||||||
{
|
|
||||||
auto const lock = data->tor->unique_lock();
|
|
||||||
|
|
||||||
if (data->deleteFlag)
|
|
||||||
{
|
{
|
||||||
tr_torrentDeleteLocalData(data->tor, data->deleteFunc);
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr_torrentClearCompletenessCallback(data->tor);
|
auto const delete_func_wrapper = [&delete_func](char const* filename)
|
||||||
closeTorrent(data->tor);
|
{
|
||||||
tr_free(data);
|
delete_func(filename, nullptr);
|
||||||
|
};
|
||||||
|
tor->metainfo_.files().remove(tor->currentDir(), tor->name(), delete_func_wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
TR_ASSERT(tr_isTorrent(tor));
|
||||||
|
|
||||||
tor->isDeleting = true;
|
tor->isDeleting = true;
|
||||||
|
|
||||||
auto* const data = tr_new0(struct remove_data, 1);
|
tr_runInEventThread(tor->session, removeTorrentInEventThread, tor, delete_flag, delete_func);
|
||||||
data->tor = tor;
|
|
||||||
data->deleteFlag = deleteFlag;
|
|
||||||
data->deleteFunc = deleteFunc;
|
|
||||||
tr_runInEventThread(tor->session, removeTorrent, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2176,328 +2170,66 @@ uint64_t tr_torrentGetBytesLeftToAllocate(tr_torrent const* tor)
|
|||||||
return bytes_left;
|
return bytes_left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/****
|
///
|
||||||
***** Removing the torrent's local data
|
|
||||||
****/
|
|
||||||
|
|
||||||
static bool isJunkFile(std::string_view base)
|
static void setLocationInEventThread(
|
||||||
{
|
tr_torrent* tor,
|
||||||
#ifdef __APPLE__
|
std::string const& path,
|
||||||
// check for resource forks. <http://support.apple.com/kb/TA20578>
|
bool move_from_old_path,
|
||||||
if (tr_strvStartsWith(base, "._"sv))
|
double volatile* setme_progress,
|
||||||
{
|
int volatile* setme_state)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
TR_ASSERT(tr_isTorrent(tor));
|
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_cacheFlushTorrent(tor->session->cache, tor);
|
||||||
tr_fdTorrentClose(tor->session, tor->uniqueId);
|
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);
|
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_error* error = nullptr;
|
||||||
|
ok = tor->metainfo_.files().move(tor->currentDir(), path, setme_progress, tor->name(), &error);
|
||||||
tr_logAddTraceTor(tor, fmt::format("moving '{}' to '{}'", oldpath, newpath));
|
if (error != nullptr)
|
||||||
|
|
||||||
if (!tr_moveFile(oldpath, newpath, &error))
|
|
||||||
{
|
{
|
||||||
err = true;
|
tr_logAddError(fmt::format(
|
||||||
tr_logAddErrorTor(
|
|
||||||
tor,
|
|
||||||
fmt::format(
|
|
||||||
_("Couldn't move '{old_path}' to '{path}': {error} ({error_code})"),
|
_("Couldn't move '{old_path}' to '{path}': {error} ({error_code})"),
|
||||||
fmt::arg("old_path", oldpath),
|
fmt::arg("old_path", tor->currentDir()),
|
||||||
fmt::arg("path", newpath),
|
fmt::arg("path", path),
|
||||||
fmt::arg("error", error->message),
|
fmt::arg("error", error->message),
|
||||||
fmt::arg("error_code", error->code)));
|
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;
|
tor->setDownloadDir(path);
|
||||||
*data->setme_progress = bytesHandled / tor->totalSize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!err && do_move)
|
if (move_from_old_path)
|
||||||
{
|
|
||||||
/* 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)
|
|
||||||
{
|
{
|
||||||
tor->incomplete_dir.clear();
|
tor->incomplete_dir.clear();
|
||||||
tor->current_dir = tor->downloadDir();
|
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(
|
void tr_torrent::setLocation(
|
||||||
std::string_view location,
|
std::string_view path,
|
||||||
bool move_from_old_location,
|
bool move_from_old_path,
|
||||||
double volatile* setme_progress,
|
double volatile* setme_progress,
|
||||||
int volatile* setme_state)
|
int volatile* setme_state)
|
||||||
{
|
{
|
||||||
@@ -2506,34 +2238,31 @@ void tr_torrent::setLocation(
|
|||||||
*setme_state = TR_LOC_MOVING;
|
*setme_state = TR_LOC_MOVING;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setme_progress != nullptr)
|
tr_runInEventThread(
|
||||||
{
|
this->session,
|
||||||
*setme_progress = 0;
|
setLocationInEventThread,
|
||||||
}
|
this,
|
||||||
|
std::string{ path },
|
||||||
/* run this in the libtransmission thread */
|
move_from_old_path,
|
||||||
auto* const data = new LocationData{};
|
setme_progress,
|
||||||
data->tor = this;
|
setme_state);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void tr_torrentSetLocation(
|
void tr_torrentSetLocation(
|
||||||
tr_torrent* tor,
|
tr_torrent* tor,
|
||||||
char const* location,
|
char const* path,
|
||||||
bool move_from_old_location,
|
bool move_from_old_path,
|
||||||
double volatile* setme_progress,
|
double volatile* setme_progress,
|
||||||
int volatile* setme_state)
|
int volatile* setme_state)
|
||||||
{
|
{
|
||||||
TR_ASSERT(tr_isTorrent(tor));
|
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
|
std::string_view tr_torrent::primaryMimeType() const
|
||||||
{
|
{
|
||||||
// count up how many bytes there are for each mime-type in the torrent
|
// 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;
|
return found ? tr_strdup(found->filename()) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Decide whether we should be looking for files in downloadDir or incompleteDir. */
|
// decide whether we should be looking for files in downloadDir or incompleteDir
|
||||||
static void refreshCurrentDir(tr_torrent* tor)
|
void tr_torrent::refreshCurrentDir()
|
||||||
{
|
{
|
||||||
auto dir = tr_interned_string{};
|
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
|
else
|
||||||
{
|
{
|
||||||
auto const found = tor->findFile(0);
|
auto const found = findFile(0);
|
||||||
dir = found ? tr_interned_string{ found->base() } : tor->incompleteDir();
|
dir = found ? tr_interned_string{ found->base() } : incompleteDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
TR_ASSERT(!std::empty(dir));
|
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 */
|
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()))
|
if (tr_sys_path_exists(src.c_str()))
|
||||||
{
|
{
|
||||||
auto const parent = tr_sys_path_dirname(src);
|
auto const parent = tr_sys_path_dirname(src);
|
||||||
auto const tgt = tr_strvEndsWith(src, tr_files::PartialFileSuffix) ?
|
auto const tgt = tr_strvEndsWith(src, tr_torrent_files::PartialFileSuffix) ?
|
||||||
tr_pathbuf{ parent, '/', newname, tr_files::PartialFileSuffix } :
|
tr_pathbuf{ parent, '/', newname, tr_torrent_files::PartialFileSuffix } :
|
||||||
tr_pathbuf{ parent, '/', newname };
|
tr_pathbuf{ parent, '/', newname };
|
||||||
|
|
||||||
auto tmp = errno;
|
auto tmp = errno;
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ public:
|
|||||||
metainfo_.setFileSubpath(i, subpath);
|
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;
|
[[nodiscard]] bool hasAnyLocalData() const;
|
||||||
|
|
||||||
@@ -559,6 +559,16 @@ public:
|
|||||||
this->error_string = errmsg;
|
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 setVerifyState(tr_verify_state state);
|
||||||
|
|
||||||
void setDateActive(time_t t);
|
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>
|
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;
|
return sv.find(key) != sv.npos;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ add_executable(libtransmission-test
|
|||||||
peer-mgr-wishlist-test.cc
|
peer-mgr-wishlist-test.cc
|
||||||
peer-msgs-test.cc
|
peer-msgs-test.cc
|
||||||
quark-test.cc
|
quark-test.cc
|
||||||
|
remove-test.cc
|
||||||
rename-test.cc
|
rename-test.cc
|
||||||
rpc-test.cc
|
rpc-test.cc
|
||||||
session-test.cc
|
session-test.cc
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "transmission.h"
|
#include "transmission.h"
|
||||||
|
|
||||||
#include "files.h"
|
#include "torrent-files.h"
|
||||||
|
|
||||||
#include "test-fixtures.h"
|
#include "test-fixtures.h"
|
||||||
|
|
||||||
@@ -20,14 +20,14 @@ TEST_F(FilesTest, add)
|
|||||||
auto constexpr Path = "/hello/world"sv;
|
auto constexpr Path = "/hello/world"sv;
|
||||||
auto constexpr Size = size_t{ 1024 };
|
auto constexpr Size = size_t{ 1024 };
|
||||||
|
|
||||||
auto files = tr_files{};
|
auto files = tr_torrent_files{};
|
||||||
EXPECT_EQ(size_t{ 0U }, std::size(files));
|
EXPECT_EQ(size_t{ 0U }, files.fileCount());
|
||||||
EXPECT_TRUE(std::empty(files));
|
EXPECT_TRUE(std::empty(files));
|
||||||
|
|
||||||
auto const file_index = files.add(Path, Size);
|
auto const file_index = files.add(Path, Size);
|
||||||
EXPECT_EQ(tr_file_index_t{ 0U }, file_index);
|
EXPECT_EQ(tr_file_index_t{ 0U }, file_index);
|
||||||
EXPECT_EQ(size_t{ 1U }, std::size(files));
|
EXPECT_EQ(size_t{ 1U }, files.fileCount());
|
||||||
EXPECT_EQ(Size, files.size(file_index));
|
EXPECT_EQ(Size, files.fileSize(file_index));
|
||||||
EXPECT_EQ(Path, files.path(file_index));
|
EXPECT_EQ(Path, files.path(file_index));
|
||||||
EXPECT_FALSE(std::empty(files));
|
EXPECT_FALSE(std::empty(files));
|
||||||
}
|
}
|
||||||
@@ -38,14 +38,14 @@ TEST_F(FilesTest, setPath)
|
|||||||
auto constexpr Path2 = "/hello/there"sv;
|
auto constexpr Path2 = "/hello/there"sv;
|
||||||
auto constexpr Size = size_t{ 2048 };
|
auto constexpr Size = size_t{ 2048 };
|
||||||
|
|
||||||
auto files = tr_files{};
|
auto files = tr_torrent_files{};
|
||||||
auto const file_index = files.add(Path1, Size);
|
auto const file_index = files.add(Path1, Size);
|
||||||
EXPECT_EQ(Path1, files.path(file_index));
|
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);
|
files.setPath(file_index, Path2);
|
||||||
EXPECT_EQ(Path2, files.path(file_index));
|
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)
|
TEST_F(FilesTest, clear)
|
||||||
@@ -54,15 +54,15 @@ TEST_F(FilesTest, clear)
|
|||||||
auto constexpr Path2 = "/hello/there"sv;
|
auto constexpr Path2 = "/hello/there"sv;
|
||||||
auto constexpr Size = size_t{ 2048 };
|
auto constexpr Size = size_t{ 2048 };
|
||||||
|
|
||||||
auto files = tr_files{};
|
auto files = tr_torrent_files{};
|
||||||
files.add(Path1, Size);
|
files.add(Path1, Size);
|
||||||
EXPECT_EQ(size_t{ 1U }, std::size(files));
|
EXPECT_EQ(size_t{ 1U }, files.fileCount());
|
||||||
files.add(Path2, Size);
|
files.add(Path2, Size);
|
||||||
EXPECT_EQ(size_t{ 2U }, std::size(files));
|
EXPECT_EQ(size_t{ 2U }, files.fileCount());
|
||||||
|
|
||||||
files.clear();
|
files.clear();
|
||||||
EXPECT_TRUE(std::empty(files));
|
EXPECT_TRUE(std::empty(files));
|
||||||
EXPECT_EQ(size_t{ 0U }, std::size(files));
|
EXPECT_EQ(size_t{ 0U }, files.fileCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(FilesTest, find)
|
TEST_F(FilesTest, find)
|
||||||
@@ -71,7 +71,7 @@ TEST_F(FilesTest, find)
|
|||||||
auto const filename = tr_pathbuf{ sandboxDir(), "/first_dir/hello.txt"sv };
|
auto const filename = tr_pathbuf{ sandboxDir(), "/first_dir/hello.txt"sv };
|
||||||
createFileWithContents(std::string{ filename }, std::data(Contents), std::size(Contents));
|
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 file_index = files.add("first_dir/hello.txt", 1024);
|
||||||
|
|
||||||
auto const search_path_1 = tr_pathbuf{ sandboxDir() };
|
auto const search_path_1 = tr_pathbuf{ sandboxDir() };
|
||||||
@@ -89,7 +89,7 @@ TEST_F(FilesTest, find)
|
|||||||
EXPECT_EQ(filename, found->filename());
|
EXPECT_EQ(filename, found->filename());
|
||||||
|
|
||||||
// now make it an incomplete file
|
// 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));
|
EXPECT_TRUE(tr_sys_path_rename(filename, partial_filename));
|
||||||
search_path = std::vector<std::string_view>{ search_path_1.sv(), search_path_2.sv() };
|
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));
|
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 };
|
auto const filename = tr_pathbuf{ sandboxDir(), "/first_dir/hello.txt"sv };
|
||||||
createFileWithContents(std::string{ filename }, std::data(Contents), std::size(Contents));
|
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);
|
files.add("first_dir/hello.txt", 1024);
|
||||||
|
|
||||||
auto const search_path_1 = tr_pathbuf{ sandboxDir() };
|
auto const search_path_1 = tr_pathbuf{ sandboxDir() };
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ TEST_P(IncompleteDirTest, incompleteDir)
|
|||||||
auto* const tor = zeroTorrentInit(ZeroTorrentState::Partial);
|
auto* const tor = zeroTorrentInit(ZeroTorrentState::Partial);
|
||||||
auto path = tr_pathbuf{};
|
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)));
|
EXPECT_EQ(path, makeString(tr_torrentFindFile(tor, 0)));
|
||||||
path.assign(incomplete_dir, '/', tr_torrentFile(tor, 1).name);
|
path.assign(incomplete_dir, '/', tr_torrentFile(tor, 1).name);
|
||||||
EXPECT_EQ(path, makeString(tr_torrentFindFile(tor, 1)));
|
EXPECT_EQ(path, makeString(tr_torrentFindFile(tor, 1)));
|
||||||
|
|||||||
341
tests/libtransmission/remove-test.cc
Normal file
341
tests/libtransmission/remove-test.cc
Normal file
@@ -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
|
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)
|
inline std::string makeString(char*&& s)
|
||||||
{
|
{
|
||||||
auto const ret = std::string(s != nullptr ? s : "");
|
auto const ret = std::string(s != nullptr ? s : "");
|
||||||
@@ -116,32 +142,6 @@ protected:
|
|||||||
return path;
|
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)
|
static void rimraf(std::string const& path, bool verbose = false)
|
||||||
{
|
{
|
||||||
auto remove = [verbose](char const* filename)
|
auto remove = [verbose](char const* filename)
|
||||||
@@ -227,14 +227,14 @@ protected:
|
|||||||
errno = tmperr;
|
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;
|
auto const tmperr = errno;
|
||||||
|
|
||||||
buildParentDir(path);
|
buildParentDir(path);
|
||||||
|
|
||||||
auto const fd = tr_sys_file_open(
|
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,
|
TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | TR_SYS_FILE_TRUNCATE,
|
||||||
0600,
|
0600,
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|||||||
@@ -541,7 +541,6 @@ TEST_F(VariantTest, dictFindType)
|
|||||||
TEST_F(VariantTest, variantFromBufFuzz)
|
TEST_F(VariantTest, variantFromBufFuzz)
|
||||||
{
|
{
|
||||||
auto buf = std::vector<char>{};
|
auto buf = std::vector<char>{};
|
||||||
auto top = tr_variant{};
|
|
||||||
|
|
||||||
for (size_t i = 0; i < 100000; ++i)
|
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) };
|
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;
|
// 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);
|
if (auto top = tr_variant{};
|
||||||
tr_variantFromBuf(&top, TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE, sv, nullptr, nullptr);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user