Compare commits

..

5 Commits

Author SHA1 Message Date
rakshasa 9507ac17b3 Stuff. 2026-08-04 11:08:52 +02:00
rakshasa 49d45c1088 Stuff. 2026-08-04 11:04:20 +02:00
rakshasa 5ab973504a Stuff. 2026-08-04 10:47:23 +02:00
rakshasa ebe28e07b1 Stuff. 2026-08-04 10:40:43 +02:00
rakshasa 06a391f9d8 Stuff. 2026-08-03 09:11:23 +02:00
46 changed files with 105 additions and 1315 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
# 2. Check for multiple consecutive blank lines # 2. Check for multiple consecutive blank lines
# This regex looks for 2 or more empty lines anywhere in the message # This regex looks for 2 or more empty lines anywhere in the message
if echo "$CLEAN_MSG" | grep -Pz '(\r?\n){3,}'; then if echo "$CLEAN_MSG" | grep -pz '(\r?\n){3,}'; then
echo "❌ Error: Commit message contains multiple consecutive line breaks." echo "❌ Error: Commit message contains multiple consecutive line breaks."
echo " Commit: '$SUBJECT'" echo " Commit: '$SUBJECT'"
FAILED=1 FAILED=1
+4 -6
View File
@@ -1,6 +1,6 @@
m4_pattern_allow([PKG_CHECK_EXISTS]) m4_pattern_allow([PKG_CHECK_EXISTS])
AC_INIT([rtorrent],[0.16.20],[sundell.software@gmail.com]) AC_INIT([rtorrent],[0.16.19],[sundell.software@gmail.com])
AC_CONFIG_HEADERS([config.h]) AC_CONFIG_HEADERS([config.h])
AC_CONFIG_MACRO_DIRS([scripts]) AC_CONFIG_MACRO_DIRS([scripts])
@@ -14,7 +14,7 @@ AX_CXX_COMPILE_STDCXX(20, noext, mandatory)
PKG_PROG_PKG_CONFIG PKG_PROG_PKG_CONFIG
AC_DEFINE([API_VERSION], [25], [api version]) AC_DEFINE([API_VERSION], [24], [api version])
RAK_CHECK_CFLAGS RAK_CHECK_CFLAGS
RAK_CHECK_CXXFLAGS RAK_CHECK_CXXFLAGS
@@ -22,8 +22,6 @@ RAK_ENABLE_DEBUG
RAK_ENABLE_EXTRA_DEBUG RAK_ENABLE_EXTRA_DEBUG
RAK_ENABLE_WERROR RAK_ENABLE_WERROR
TORRENT_CHECK_ATOMIC
AC_ARG_ENABLE(execinfo, AC_ARG_ENABLE(execinfo,
AS_HELP_STRING([--disable-execinfo], AS_HELP_STRING([--disable-execinfo],
[disable libexecinfo [[default=enable]]]), [disable libexecinfo [[default=enable]]]),
@@ -49,7 +47,7 @@ fi
PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"]) PKG_CHECK_MODULES([CPPUNIT], [cppunit],, [no_cppunit="yes"])
PKG_CHECK_MODULES([ZLIB], [zlib]) PKG_CHECK_MODULES([ZLIB], [zlib])
PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.20]) PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.19])
AC_LANG_PUSH(C++) AC_LANG_PUSH(C++)
TORRENT_WITH_XMLRPC_C TORRENT_WITH_XMLRPC_C
@@ -70,7 +68,7 @@ AC_DEFINE(USER_AGENT, [std::string(PACKAGE "/" VERSION)], Http user agent)
dnl Only update global build variables immediately before generating the output, dnl Only update global build variables immediately before generating the output,
dnl to avoid affecting the global build environment for other autoconf checks. dnl to avoid affecting the global build environment for other autoconf checks.
LIBS="$ATOMIC_LIBS $PTHREAD_LIBS $CURSES_LIB $CURSES_LIBS $ZLIB_LIBS $DEPENDENCIES_LIBS $LIBS" LIBS="$PTHREAD_LIBS $CURSES_LIB $CURSES_LIBS $ZLIB_LIBS $DEPENDENCIES_LIBS $LIBS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS"
CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS" CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS $CURSES_CFLAGS $ZLIB_CFLAGS $DEPENDENCIES_CFLAGS"
-60
View File
@@ -1,60 +0,0 @@
# Resolving paths
Commands that return a path have `.realpath` variants that resolve it to a
canonical one, with symlinks followed and any `.` or `..` components removed.
The intent is to make paths safer to hand to an external script.
A script called through `execute` receives whatever path rtorrent gives it, and
many scripts do no sanity checking of their own, so resolving the path before it
leaves rtorrent removes a class of surprises: a download directory that is a
symlink into somewhere unexpected, or a torrent whose name walks upwards out of
the directory it is supposed to live in.
# Instead of this
execute = ~/bin/on-finished, (d.base_path)
# Pass the resolved path
execute = ~/bin/on-finished, (d.base_path.realpath.or_throw)
## Available variants
Each command below comes in an `.or_empty` and an `.or_throw` form.
| Command | Resolves |
| --- | --- |
| `d.base_path.realpath.*` | `d.base_path` |
| `d.directory.realpath.*` | `d.directory` |
| `d.tied_to_file.realpath.*` | `d.tied_to_file` |
| `d.loaded_file.realpath.*` | `d.loaded_file` |
| `f.frozen_path.realpath.*` | `f.frozen_path` |
| `session.path.realpath.*` | `session.path` |
| `directory.default.realpath.*` | `directory.default` |
A leading `~` is expanded first, exactly as it is for `execute`, so
`~/downloads` resolves the same way it would on the command line.
## Paths that do not exist
Resolving requires the path to name an existing file or directory, which is
often not the case for a download whose data has not been written yet.
The two forms differ only in what they do about it.
`.or_empty` returns an empty string:
print = (d.base_path.realpath.or_empty) # ""
This keeps `d.multicall` usable over a view that mixes started and unstarted
downloads, since a single unresolvable path does not abort the whole call.
A script receiving one of these paths should still check that it is not empty
before acting on it.
`.or_throw` raises an error instead:
print = (d.base_path.realpath.or_throw) # Could not resolve path: '...'
Prefer this one wherever an unresolvable path means the command should not run
at all, such as a single `execute` on `event.download.finished`.
Note that a download only has a file list once it has been opened, so
`d.base_path` and `f.frozen_path` are empty at `event.download.inserted` time
and their `.realpath` variants resolve nothing.
-139
View File
@@ -1,139 +0,0 @@
# String functions
The `string.*` commands manipulate and inspect text from within the
configuration file.
They are pure functions with no side effects, so they can be nested freely and
are safe to call over RPC.
Every argument is converted to its string representation before use, which means
numbers can be passed where text is expected.
Commands that count characters, such as `string.length` and `string.substr`, count
utf-8 characters rather than bytes.
## string.length
# string.length = «text»
string.length = "héllo" # 5
Returns the number of utf-8 characters in the text.
## string.equals
# string.equals = «text», «other»[, ...]
string.equals = (d.name), "first.iso", "second.iso"
Returns `1` if the first argument equals any of the following arguments,
otherwise `0`.
## string.starts_with, string.ends_with
# string.starts_with = «text», «prefix»[, ...]
# string.ends_with = «text», «tail»[, ...]
string.starts_with = (t.url), "http://", "https://"
string.ends_with = (d.name), ".iso"
Returns `1` if the text begins, or ends, with any of the given prefixes or
tails.
## string.contains, string.contains_i
# string.contains = «haystack», «needle»[, ...]
# string.contains_i = «haystack», «needle»[, ...]
string.contains = (t.url), "retracker.local"
string.contains_i = (t.url), "RETRACKER.local"
Returns `1` if the haystack contains any of the needles.
The `_i` variant compares case-insensitively, and only handles ascii.
## string.substr
# string.substr = «text»[, «position»[, «count»[, «default»]]]
string.substr = "abcdef", 2, 3 # "cde"
string.substr = "abcdef", -2 # "ef"
string.substr = "abcdef", 10, 1, "?" # "?"
Extracts a part of the text, starting at `position` and spanning `count`
characters.
The position defaults to the start of the text, and the count to the rest of it.
A negative position is relative to the end of the text.
If the position falls outside the text, the default value is returned instead,
which is the empty string unless given.
## string.split
# string.split = «text», «delimiter»
string.split = "a.b.c", "." # {"a", "b", "c"}
string.split = "abc", "" # {"a", "b", "c"}
Splits the text into a list, keeping empty fields.
An empty delimiter splits the text into its utf-8 characters.
## string.join
# string.join = «delimiter»[, «object»[, ...]]
string.join = "-", (string.split, "a.b.c", ".") # "a-b-c"
Concatenates the objects, inserting the delimiter between them.
Lists are flattened, so the result of `string.split` can be joined back
together.
## string.lpad, string.rpad
# string.lpad = «text», «length»[, «padding»]
# string.rpad = «text», «length»[, «padding»]
string.lpad = 7, 3, 0 # "007"
string.rpad = "a", 3 # "a "
Pads the text at the start, or the end, until it is `length` characters long.
The padding defaults to a single space and is repeated as needed.
Text that is already long enough is returned unchanged.
## string.strip, string.lstrip, string.rstrip
# string.strip = «text»[, «strippable»[, ...]]
# string.lstrip = «text»[, «head»[, ...]]
# string.rstrip = «text»[, «tail»[, ...]]
string.strip = " padded " # "padded"
string.strip = "//path//", "/" # "path"
Removes characters from both ends of the text, or from only the start or the
end.
The arguments after the text form a set of utf-8 characters to remove.
Without them, whitespace is removed.
## string.map
# string.map = «text», {«old», «new»}[, ...]
string.map = (d.state), {0, "stopped"}, {1, "started"}
Returns the replacement of the first pair whose `old` value equals the whole
text.
If no pair matches, the text is returned unchanged.
## string.replace
# string.replace = «text», {«old», «new»}[, ...]
string.replace = "a-b-c", {"-", "+"} # "a+b+c"
Replaces every occurrence of `old` with `new`.
The pairs are applied in order, so a later pair operates on the result of the
earlier ones.
## Example: dropping unwanted trackers
The following disables every tracker that points at `retracker.local` as soon as
a download is inserted.
method.set_key = event.download.inserted, drop_retracker, \
((t.multicall, default, "branch=(string.contains,(t.url),retracker.local),((t.disable))"))
+2 -2
View File
@@ -71,11 +71,11 @@
# Port range to use for listening. # Port range to use for listening.
# #
#network.listen.port.range.set = 6890-6999 #network.port_range.set = 6890-6999
# Start opening ports at a random position within the port range. # Start opening ports at a random position within the port range.
# #
#network.listen.port.random.set = no #network.port_random.set = no
# Set RPC type # Set RPC type
#network.rpc.use_xmlrpc.set = true #network.rpc.use_xmlrpc.set = true
+2 -2
View File
@@ -23,8 +23,8 @@ execute.throw = sh, -c, (cat,\
"\"",(cfg.watch),"/start\" ") "\"",(cfg.watch),"/start\" ")
# Listening port for incoming peer traffic (fixed; you can also randomize it) # Listening port for incoming peer traffic (fixed; you can also randomize it)
network.listen.port.range.set = 50000-50000 network.port_range.set = 50000-50000
network.listen.port.random.set = no network.port_random.set = no
# Tracker-less torrent and UDP tracker support # Tracker-less torrent and UDP tracker support
# (conservative settings for 'private' trackers, change for 'public') # (conservative settings for 'private' trackers, change for 'public')
+2 -2
View File
@@ -53,8 +53,8 @@ rc.execute.throw(
cfg.watch..'/start'})) cfg.watch..'/start'}))
-- Listening port for incoming peer traffic (fixed; you can also randomize it) -- Listening port for incoming peer traffic (fixed; you can also randomize it)
rc.network.listen.port.range = '50000-50000' rc.network.port_range = '50000-50000'
rc.network.listen.port.random = false rc.network.port_random = false
-- Tracker-less torrent and UDP tracker support -- Tracker-less torrent and UDP tracker support
-- (conservative settings for 'private' trackers, change for 'public') -- (conservative settings for 'private' trackers, change for 'public')
-1
View File
@@ -194,7 +194,6 @@ libsub_root_a_SOURCES = \
command_throttle.cc \ command_throttle.cc \
command_tracker.cc \ command_tracker.cc \
command_scheduler.cc \ command_scheduler.cc \
command_string.cc \
command_ui.cc \ command_ui.cc \
control.cc \ control.cc \
control.h \ control.h \
-17
View File
@@ -655,8 +655,6 @@ initialize_command_download() {
CMD2_DL("d.base_path.as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_as_binary(); }); CMD2_DL("d.base_path.as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_as_binary(); });
CMD2_DL("d.base_path.or_base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_base64(); }); CMD2_DL("d.base_path.or_base64", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_base64(); });
CMD2_DL("d.base_path.or_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_as_binary(); }); CMD2_DL("d.base_path.or_as_binary", [](auto* download, auto) { return retrieve_d_base_path(download).object_utf8_or_as_binary(); });
CMD2_DL("d.base_path.realpath.or_empty", [](auto* download, auto) { return resolve_path(retrieve_d_base_path(download).str()); });
CMD2_DL("d.base_path.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(retrieve_d_base_path(download).str()); });
CMD2_DL("d.base_filename", [](auto* download, auto) { return retrieve_d_base_filename(download).str(); }); CMD2_DL("d.base_filename", [](auto* download, auto) { return retrieve_d_base_filename(download).str(); });
CMD2_DL("d.base_filename.hex", [](auto* download, auto) { return retrieve_d_base_filename(download).object_hex(); }); CMD2_DL("d.base_filename.hex", [](auto* download, auto) { return retrieve_d_base_filename(download).object_hex(); });
CMD2_DL("d.base_filename.base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64(); }); CMD2_DL("d.base_filename.base64", [](auto* download, auto) { return retrieve_d_base_filename(download).object_base64(); });
@@ -773,11 +771,6 @@ initialize_command_download() {
CMD2_DL_VAR_STRING_PUBLIC("d.tied_to_file", "rtorrent", "tied_to_file"); CMD2_DL_VAR_STRING_PUBLIC("d.tied_to_file", "rtorrent", "tied_to_file");
CMD2_DL_VAR_STRING("d.loaded_file", "rtorrent", "loaded_file"); CMD2_DL_VAR_STRING("d.loaded_file", "rtorrent", "loaded_file");
CMD2_DL("d.tied_to_file.realpath.or_empty", [](auto* download, auto) { return resolve_path(rpc::convert_to_string(download_get_variable(download, "rtorrent", "tied_to_file"))); });
CMD2_DL("d.tied_to_file.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(rpc::convert_to_string(download_get_variable(download, "rtorrent", "tied_to_file"))); });
CMD2_DL("d.loaded_file.realpath.or_empty", [](auto* download, auto) { return resolve_path(rpc::convert_to_string(download_get_variable(download, "rtorrent", "loaded_file"))); });
CMD2_DL("d.loaded_file.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(rpc::convert_to_string(download_get_variable(download, "rtorrent", "loaded_file"))); });
// The "state_changed" variable is required to be a valid unix time // The "state_changed" variable is required to be a valid unix time
// value, it indicates the last time the torrent changed its state, // value, it indicates the last time the torrent changed its state,
// resume/pause. // resume/pause.
@@ -885,8 +878,6 @@ initialize_command_download() {
CMD2_DL_VALUE_V ("d.tracker.send_scrape", [](auto download, uint64_t arg) { download->tracker_controller().scrape_request(arg); }); CMD2_DL_VALUE_V ("d.tracker.send_scrape", [](auto download, uint64_t arg) { download->tracker_controller().scrape_request(arg); });
CMD2_DL ("d.directory", CMD2_ON_FL(root_dir)); CMD2_DL ("d.directory", CMD2_ON_FL(root_dir));
CMD2_DL ("d.directory.realpath.or_empty", [](auto* download, auto) { return resolve_path(download->file_list()->root_dir()); });
CMD2_DL ("d.directory.realpath.or_throw", [](auto* download, auto) { return resolve_path_or_throw(download->file_list()->root_dir()); });
CMD2_DL_STRING_V("d.directory.set", std::bind(&apply_d_directory, std::placeholders::_1, std::placeholders::_2)); CMD2_DL_STRING_V("d.directory.set", std::bind(&apply_d_directory, std::placeholders::_1, std::placeholders::_2));
CMD2_DL ("d.directory_base", CMD2_ON_FL(root_dir)); CMD2_DL ("d.directory_base", CMD2_ON_FL(root_dir));
CMD2_DL_STRING_V("d.directory_base.set", std::bind(&core::Download::set_root_directory, std::placeholders::_1, std::placeholders::_2)); CMD2_DL_STRING_V("d.directory_base.set", std::bind(&core::Download::set_root_directory, std::placeholders::_1, std::placeholders::_2));
@@ -921,14 +912,6 @@ initialize_command_download() {
rpc::rpc.mark_safe("d.local_id_html"); rpc::rpc.mark_safe("d.local_id_html");
rpc::rpc.mark_safe("d.bitfield"); rpc::rpc.mark_safe("d.bitfield");
rpc::rpc.mark_safe("d.base_path"); rpc::rpc.mark_safe("d.base_path");
rpc::rpc.mark_safe("d.base_path.realpath.or_empty");
rpc::rpc.mark_safe("d.base_path.realpath.or_throw");
rpc::rpc.mark_safe("d.directory.realpath.or_empty");
rpc::rpc.mark_safe("d.directory.realpath.or_throw");
rpc::rpc.mark_safe("d.tied_to_file.realpath.or_empty");
rpc::rpc.mark_safe("d.tied_to_file.realpath.or_throw");
rpc::rpc.mark_safe("d.loaded_file.realpath.or_empty");
rpc::rpc.mark_safe("d.loaded_file.realpath.or_throw");
rpc::rpc.mark_safe("d.base_path.hex"); rpc::rpc.mark_safe("d.base_path.hex");
rpc::rpc.mark_safe("d.base_path.base64"); rpc::rpc.mark_safe("d.base_path.base64");
rpc::rpc.mark_safe("d.base_path.base64_as_binary"); rpc::rpc.mark_safe("d.base_path.base64_as_binary");
-4
View File
@@ -116,8 +116,6 @@ initialize_command_file() {
CMD2_FILE("f.frozen_path.as_binary", [](auto* file, auto) { return file->frozen_path().object_as_binary(); }); CMD2_FILE("f.frozen_path.as_binary", [](auto* file, auto) { return file->frozen_path().object_as_binary(); });
CMD2_FILE("f.frozen_path.or_base64", [](auto* file, auto) { return file->frozen_path().object_utf8_or_base64(); }); CMD2_FILE("f.frozen_path.or_base64", [](auto* file, auto) { return file->frozen_path().object_utf8_or_base64(); });
CMD2_FILE("f.frozen_path.or_as_binary", [](auto* file, auto) { return file->frozen_path().object_utf8_or_as_binary(); }); CMD2_FILE("f.frozen_path.or_as_binary", [](auto* file, auto) { return file->frozen_path().object_utf8_or_as_binary(); });
CMD2_FILE("f.frozen_path.realpath.or_empty", [](auto* file, auto) { return resolve_path(file->frozen_path().str()); });
CMD2_FILE("f.frozen_path.realpath.or_throw", [](auto* file, auto) { return resolve_path_or_throw(file->frozen_path().str()); });
CMD2_FILE("f.match_depth_prev", std::bind(&torrent::File::match_depth_prev, std::placeholders::_1)); CMD2_FILE("f.match_depth_prev", std::bind(&torrent::File::match_depth_prev, std::placeholders::_1));
CMD2_FILE("f.match_depth_next", std::bind(&torrent::File::match_depth_next, std::placeholders::_1)); CMD2_FILE("f.match_depth_next", std::bind(&torrent::File::match_depth_next, std::placeholders::_1));
@@ -131,8 +129,6 @@ initialize_command_file() {
rpc::rpc.mark_safe("f.path_components"); rpc::rpc.mark_safe("f.path_components");
rpc::rpc.mark_safe("f.path_depth"); rpc::rpc.mark_safe("f.path_depth");
rpc::rpc.mark_safe("f.frozen_path"); rpc::rpc.mark_safe("f.frozen_path");
rpc::rpc.mark_safe("f.frozen_path.realpath.or_empty");
rpc::rpc.mark_safe("f.frozen_path.realpath.or_throw");
rpc::rpc.mark_safe("f.frozen_path.hex"); rpc::rpc.mark_safe("f.frozen_path.hex");
rpc::rpc.mark_safe("f.frozen_path.base64"); rpc::rpc.mark_safe("f.frozen_path.base64");
rpc::rpc.mark_safe("f.frozen_path.base64_as_binary"); rpc::rpc.mark_safe("f.frozen_path.base64_as_binary");
+9 -11
View File
@@ -1,7 +1,5 @@
#include "config.h" #include "config.h"
#include <memory>
#include <torrent/download/resource_manager.h> #include <torrent/download/resource_manager.h>
#include <torrent/download/choke_group.h> #include <torrent/download/choke_group.h>
#include <torrent/download/choke_queue.h> #include <torrent/download/choke_queue.h>
@@ -113,7 +111,7 @@ apply_cg_all_update_balance(bool is_up) {
// //
#else #else
std::vector<std::unique_ptr<torrent::choke_group>> cg_list_hack; std::vector<torrent::choke_group*> cg_list_hack;
int64_t int64_t
cg_get_index(const torrent::Object& raw_args) { cg_get_index(const torrent::Object& raw_args) {
@@ -123,7 +121,7 @@ cg_get_index(const torrent::Object& raw_args) {
if (arg.is_string()) { if (arg.is_string()) {
if (!rpc::parse_whole_value_nothrow(arg.as_string().c_str(), &index)) { if (!rpc::parse_whole_value_nothrow(arg.as_string().c_str(), &index)) {
auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](const auto& cg) { return arg.as_string() == cg->name(); }); auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](torrent::choke_group* cg) { return arg.as_string() == cg->name(); });
if (itr == cg_list_hack.end()) if (itr == cg_list_hack.end())
throw torrent::input_error("Choke group not found."); throw torrent::input_error("Choke group not found.");
@@ -151,7 +149,7 @@ cg_get_group(const torrent::Object& raw_args) {
if ((size_t)index >= cg_list_hack.size()) if ((size_t)index >= cg_list_hack.size())
throw torrent::input_error("Choke group not found."); throw torrent::input_error("Choke group not found.");
return cg_list_hack.at(index).get(); return cg_list_hack.at(index);
} }
int64_t cg_d_group(core::Download* download) { return download->group(); } int64_t cg_d_group(core::Download* download) { return download->group(); }
@@ -164,7 +162,7 @@ torrent::Object
apply_cg_list() { apply_cg_list() {
torrent::Object::list_type result; torrent::Object::list_type result;
for (const auto& itr : cg_list_hack) for (auto itr : cg_list_hack)
result.push_back(itr->name()); result.push_back(itr->name());
return torrent::Object::from_list(result); return torrent::Object::from_list(result);
@@ -182,10 +180,10 @@ apply_cg_insert(const std::string& arg) {
if (rpc::parse_whole_value_nothrow(arg.c_str(), &dummy)) if (rpc::parse_whole_value_nothrow(arg.c_str(), &dummy))
throw torrent::input_error("Cannot use a value string as choke group name."); throw torrent::input_error("Cannot use a value string as choke group name.");
if (arg.empty() || std::any_of(cg_list_hack.begin(), cg_list_hack.end(), [&arg](const auto& cg) { return arg == cg->name(); })) if (arg.empty() || std::any_of(cg_list_hack.begin(), cg_list_hack.end(), [&arg](auto cg) { return arg == cg->name(); }))
throw torrent::input_error("Duplicate name for choke group."); throw torrent::input_error("Duplicate name for choke group.");
cg_list_hack.push_back(std::make_unique<torrent::choke_group>()); cg_list_hack.push_back(new torrent::choke_group());
cg_list_hack.back()->set_name(arg); cg_list_hack.back()->set_name(arg);
cg_list_hack.back()->up_queue()->set_heuristics(torrent::HEURISTICS_UPLOAD_LEECH); cg_list_hack.back()->up_queue()->set_heuristics(torrent::HEURISTICS_UPLOAD_LEECH);
@@ -196,7 +194,7 @@ apply_cg_insert(const std::string& arg) {
torrent::Object torrent::Object
apply_cg_index_of(const std::string& arg) { apply_cg_index_of(const std::string& arg) {
auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](const auto& cg) { return arg == cg->name(); }); auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](torrent::choke_group* cg) { return arg == cg->name(); });
if (itr == cg_list_hack.end()) if (itr == cg_list_hack.end())
throw torrent::input_error("Choke group not found."); throw torrent::input_error("Choke group not found.");
@@ -208,7 +206,7 @@ torrent::Object
apply_cg_all_update_balance(bool is_up) { apply_cg_all_update_balance(bool is_up) {
LT_LOG_SUBSYSTEM("apply update balance: hack is_up:%i", (int)is_up); LT_LOG_SUBSYSTEM("apply update balance: hack is_up:%i", (int)is_up);
for (const auto& itr : cg_list_hack) { for (auto itr : cg_list_hack) {
if (is_up) if (is_up)
itr->up_queue()->balance(); itr->up_queue()->balance();
else else
@@ -348,7 +346,7 @@ initialize_command_groups() {
#else #else
apply_cg_insert("default"); apply_cg_insert("default");
CMD_ANY ("choke_group.size", [](auto, auto) { return (int64_t)cg_list_hack.size(); }); CMD_ANY ("choke_group.size", std::bind(&std::vector<torrent::choke_group*>::size, cg_list_hack));
CMD_ANY_STRING ("choke_group.index_of", std::bind(&apply_cg_index_of, std::placeholders::_2)); CMD_ANY_STRING ("choke_group.index_of", std::bind(&apply_cg_index_of, std::placeholders::_2));
#endif #endif
-2
View File
@@ -19,7 +19,6 @@ void initialize_command_groups();
void initialize_command_throttle(); void initialize_command_throttle();
void initialize_command_tracker(); void initialize_command_tracker();
void initialize_command_scheduler(); void initialize_command_scheduler();
void initialize_command_string();
void initialize_command_ui(); void initialize_command_ui();
void void
@@ -38,5 +37,4 @@ initialize_commands() {
initialize_command_throttle(); initialize_command_throttle();
initialize_command_tracker(); initialize_command_tracker();
initialize_command_scheduler(); initialize_command_scheduler();
initialize_command_string();
} }
+2 -22
View File
@@ -243,8 +243,6 @@ initialize_command_local() {
CMD_ANY ("system.sockets.max_size", [](auto, auto) { return torrent::runtime::socket_manager()->max_size(); }); CMD_ANY ("system.sockets.max_size", [](auto, auto) { return torrent::runtime::socket_manager()->max_size(); });
CMD_ANY_VALUE_V ("system.sockets.max_size.set", [](auto, auto& value) { return torrent::runtime::socket_manager()->set_max_size_and_adjust(value); }); CMD_ANY_VALUE_V ("system.sockets.max_size.set", [](auto, auto& value) { return torrent::runtime::socket_manager()->set_max_size_and_adjust(value); });
CMD_ANY_V ("system.sockets.adjust_alloc", [](auto, auto) { torrent::runtime::socket_manager()->adjust_allocation(); }); CMD_ANY_V ("system.sockets.adjust_alloc", [](auto, auto) { torrent::runtime::socket_manager()->adjust_allocation(); });
CMD_ANY ("system.sockets.reserved_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->reserved_allocation(); });
CMD_ANY ("system.sockets.available_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->available_allocation(); });
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) { for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
auto category = static_cast<torrent::runtime::socket_manager_category_t>(i); auto category = static_cast<torrent::runtime::socket_manager_category_t>(i);
@@ -253,13 +251,9 @@ initialize_command_local() {
CMD_ANY (category_name + ".size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_managed_size(category); }); CMD_ANY (category_name + ".size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_managed_size(category); });
CMD_ANY (category_name + ".max_size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_size(category); }); CMD_ANY (category_name + ".max_size", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_size(category); });
if (i == 0) { if (i == 0)
CMD_ANY (category_name + ".min_alloc", [](auto, auto) { return torrent::runtime::socket_manager()->generic_min_allocation(); });
continue; continue;
}
CMD_ANY (category_name + ".max_alloc.limit", [category](auto, auto) { return torrent::runtime::socket_manager()->category_alloc_limit(category); });
CMD_ANY (category_name + ".min_alloc.limit", [category](auto, auto) { return torrent::runtime::socket_manager()->category_alloc_minimum(category); });
CMD_ANY (category_name + ".min_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_min_allocation(category); }); CMD_ANY (category_name + ".min_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_min_allocation(category); });
CMD_ANY (category_name + ".max_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_allocation(category); }); CMD_ANY (category_name + ".max_alloc", [category](auto, auto) { return torrent::runtime::socket_manager()->category_max_allocation(category); });
CMD_ANY_VALUE_V(category_name + ".min_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_min_allocation(category, value); }); CMD_ANY_VALUE_V(category_name + ".min_alloc.set", [category](auto, auto& value) { torrent::runtime::socket_manager()->set_category_min_allocation(category, value); });
@@ -298,13 +292,9 @@ initialize_command_local() {
CMD_VAR_BOOL ("pieces.hash.on_completion", true); CMD_VAR_BOOL ("pieces.hash.on_completion", true);
CMD_VAR_STRING ("directory.default", "./"); CMD_VAR_STRING ("directory.default", "./");
CMD_ANY ("directory.default.realpath.or_empty", [](auto, auto) { return resolve_path(rpc::call_command_string("directory.default")); });
CMD_ANY ("directory.default.realpath.or_throw", [](auto, auto) { return resolve_path_or_throw(rpc::call_command_string("directory.default")); });
CMD_VAR_STRING ("session.name", ""); CMD_VAR_STRING ("session.name", "");
CMD_ANY ("session.path", [](auto, auto) { return session_thread::manager()->path(); }); CMD_ANY ("session.path", [](auto, auto) { return session_thread::manager()->path(); });
CMD_ANY ("session.path.realpath.or_empty", [](auto, auto) { return resolve_path(session_thread::manager()->path()); });
CMD_ANY ("session.path.realpath.or_throw", [](auto, auto) { return resolve_path_or_throw(session_thread::manager()->path()); });
CMD_ANY_STRING_V("session.path.set", [](auto, auto& str) { return session_thread::manager()->set_path(str); }); CMD_ANY_STRING_V("session.path.set", [](auto, auto& str) { return session_thread::manager()->set_path(str); });
CMD_ANY ("session.use_lock", [](auto, auto) { return session_thread::manager()->use_lock(); }); CMD_ANY ("session.use_lock", [](auto, auto) { return session_thread::manager()->use_lock(); });
CMD_ANY_VALUE_V ("session.use_lock.set", [](auto, auto& value) { return session_thread::manager()->set_use_lock(value); }); CMD_ANY_VALUE_V ("session.use_lock.set", [](auto, auto& value) { return session_thread::manager()->set_use_lock(value); });
@@ -358,8 +348,6 @@ initialize_command_local() {
rpc::rpc.mark_safe("system.sockets.size"); rpc::rpc.mark_safe("system.sockets.size");
rpc::rpc.mark_safe("system.sockets.max_size"); rpc::rpc.mark_safe("system.sockets.max_size");
rpc::rpc.mark_safe("system.sockets.reserved_alloc");
rpc::rpc.mark_safe("system.sockets.available_alloc");
for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) { for (uint32_t i = 0; i < torrent::runtime::SocketManager::category_count; ++i) {
auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i); auto category_name = "system.sockets." + torrent::option_to_str_or_throw(torrent::OPTION_SOCKET_CATEGORY, i);
@@ -367,23 +355,15 @@ initialize_command_local() {
rpc::rpc.mark_safe(category_name + ".size"); rpc::rpc.mark_safe(category_name + ".size");
rpc::rpc.mark_safe(category_name + ".max_size"); rpc::rpc.mark_safe(category_name + ".max_size");
if (i == 0) { if (i == 0)
rpc::rpc.mark_safe(category_name + ".min_alloc");
continue; continue;
}
rpc::rpc.mark_safe(category_name + ".max_alloc.limit");
rpc::rpc.mark_safe(category_name + ".min_alloc.limit");
rpc::rpc.mark_safe(category_name + ".min_alloc"); rpc::rpc.mark_safe(category_name + ".min_alloc");
rpc::rpc.mark_safe(category_name + ".max_alloc"); rpc::rpc.mark_safe(category_name + ".max_alloc");
} }
rpc::rpc.mark_safe("directory.default"); rpc::rpc.mark_safe("directory.default");
rpc::rpc.mark_safe("session.path"); rpc::rpc.mark_safe("session.path");
rpc::rpc.mark_safe("session.path.realpath.or_empty");
rpc::rpc.mark_safe("session.path.realpath.or_throw");
rpc::rpc.mark_safe("directory.default.realpath.or_empty");
rpc::rpc.mark_safe("directory.default.realpath.or_throw");
rpc::rpc.mark_safe("session.use_lock"); rpc::rpc.mark_safe("session.use_lock");
rpc::rpc.mark_safe("session.on_completion"); rpc::rpc.mark_safe("session.on_completion");
-365
View File
@@ -1,365 +0,0 @@
#include "config.h"
#include <algorithm>
#include <cctype>
#include <iterator>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <torrent/exceptions.h>
#include <torrent/object.h>
#include "rpc/parse.h"
#include "rpc/rpc_manager.h"
#include "globals.h"
#include "control.h"
#include "command_helpers.h"
namespace {
constexpr int64_t max_string_pad_size = 1 << 14;
const std::string whitespace_characters = " \t\n\r\f\v";
// The byte offset of every utf-8 character in 'text', terminated by the offset
// past the last character. Bytes that are not valid utf-8 lead bytes are
// treated as single characters.
std::vector<size_t>
utf8_offsets(const std::string& text) {
std::vector<size_t> offsets;
for (size_t i = 0; i < text.size(); i++)
if (i == 0 || (static_cast<unsigned char>(text[i]) & 0xC0) != 0x80)
offsets.push_back(i);
offsets.push_back(text.size());
return offsets;
}
int64_t
utf8_length(const std::string& text) {
int64_t result = 0;
for (size_t i = 0; i < text.size(); i++)
if (i == 0 || (static_cast<unsigned char>(text[i]) & 0xC0) != 0x80)
result++;
return result;
}
std::string
utf8_substr(const std::string& text, const std::vector<size_t>& offsets, size_t first, size_t last) {
return text.substr(offsets[first], offsets[last] - offsets[first]);
}
std::set<std::string>
utf8_character_set(const std::string& text) {
auto offsets = utf8_offsets(text);
std::set<std::string> result;
for (size_t i = 0; i + 1 < offsets.size(); i++)
result.insert(utf8_substr(text, offsets, i, i + 1));
return result;
}
std::string
ascii_lowercase(std::string text) {
std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return std::tolower(c); });
return text;
}
// A 'max_count' of zero means the command accepts any number of arguments.
void
verify_argument_count(const char* name, const torrent::Object::list_type& args, size_t min_count, size_t max_count) {
if (args.size() < min_count || (max_count != 0 && args.size() > max_count))
throw torrent::input_error(std::string(name) + ": invalid number of arguments.");
}
const torrent::Object&
argument_at(const torrent::Object::list_type& args, size_t index) {
return *std::next(args.begin(), index);
}
void
flatten_argument(const torrent::Object& arg, std::vector<std::string>* dest) {
if (!arg.is_list()) {
dest->push_back(rpc::convert_to_string(arg));
return;
}
for (const auto& child : arg.as_list())
flatten_argument(child, dest);
}
// The {old, new} pairs shared by 'string.map' and 'string.replace'.
std::pair<std::string, std::string>
argument_to_pair(const char* name, const torrent::Object& arg) {
if (!arg.is_list() || arg.as_list().size() != 2)
throw torrent::input_error(std::string(name) + ": arguments after the text must be {old, new} pairs.");
return {rpc::convert_to_string(arg.as_list().front()), rpc::convert_to_string(arg.as_list().back())};
}
// Compares the first argument against every remaining argument, returning true
// as soon as one of them matches.
torrent::Object
apply_string_predicate(const char* name, const torrent::Object::list_type& args, bool (*predicate)(const std::string&, const std::string&)) {
verify_argument_count(name, args, 2, 0);
auto text = rpc::convert_to_string(args.front());
for (auto itr = std::next(args.begin()); itr != args.end(); itr++)
if (predicate(text, rpc::convert_to_string(*itr)))
return int64_t(1);
return int64_t(0);
}
bool
text_equals(const std::string& text, const std::string& other) {
return text == other;
}
bool
text_starts_with(const std::string& text, const std::string& prefix) {
return text.size() >= prefix.size() && text.compare(0, prefix.size(), prefix) == 0;
}
bool
text_ends_with(const std::string& text, const std::string& tail) {
return text.size() >= tail.size() && text.compare(text.size() - tail.size(), tail.size(), tail) == 0;
}
bool
text_contains(const std::string& text, const std::string& needle) {
return text.find(needle) != std::string::npos;
}
bool
text_contains_i(const std::string& text, const std::string& needle) {
return ascii_lowercase(text).find(ascii_lowercase(needle)) != std::string::npos;
}
torrent::Object
apply_string_length(const torrent::Object::list_type& args) {
verify_argument_count("string.length", args, 1, 1);
return utf8_length(rpc::convert_to_string(args.front()));
}
torrent::Object
apply_string_substr(const torrent::Object::list_type& args) {
verify_argument_count("string.substr", args, 1, 4);
auto text = rpc::convert_to_string(args.front());
auto offsets = utf8_offsets(text);
auto text_length = static_cast<int64_t>(offsets.size() - 1);
auto position = args.size() > 1 ? rpc::convert_to_value(argument_at(args, 1)) : 0;
auto fallback = args.size() > 3 ? rpc::convert_to_string(argument_at(args, 3)) : std::string();
// Negative positions are relative to the end of the string.
if (position < 0)
position += text_length;
if (position < 0 || position > text_length)
return fallback;
auto last = text_length;
if (args.size() > 2) {
auto count = rpc::convert_to_value(argument_at(args, 2));
if (count < 0)
throw torrent::input_error("string.substr: the character count cannot be negative.");
last = std::min(text_length, position + std::min(count, text_length));
}
return utf8_substr(text, offsets, position, last);
}
torrent::Object
apply_string_split(const torrent::Object::list_type& args) {
verify_argument_count("string.split", args, 2, 2);
auto text = rpc::convert_to_string(args.front());
auto delim = rpc::convert_to_string(args.back());
auto result = torrent::Object::create_list();
// An empty delimiter splits the text into its utf-8 characters.
if (delim.empty()) {
auto offsets = utf8_offsets(text);
for (size_t i = 0; i + 1 < offsets.size(); i++)
result.as_list().push_back(utf8_substr(text, offsets, i, i + 1));
return result;
}
size_t first = 0;
while (true) {
auto pos = text.find(delim, first);
if (pos == std::string::npos) {
result.as_list().push_back(text.substr(first));
return result;
}
result.as_list().push_back(text.substr(first, pos - first));
first = pos + delim.size();
}
}
torrent::Object
apply_string_join(const torrent::Object::list_type& args) {
verify_argument_count("string.join", args, 1, 0);
auto delim = rpc::convert_to_string(args.front());
std::vector<std::string> parts;
for (auto itr = std::next(args.begin()); itr != args.end(); itr++)
flatten_argument(*itr, &parts);
std::string result;
for (auto itr = parts.begin(); itr != parts.end(); itr++) {
if (itr != parts.begin())
result += delim;
result += *itr;
}
return result;
}
torrent::Object
apply_string_pad(const char* name, const torrent::Object::list_type& args, bool pad_start) {
verify_argument_count(name, args, 2, 3);
auto text = rpc::convert_to_string(args.front());
auto pad_size = rpc::convert_to_value(argument_at(args, 1));
auto padding = args.size() > 2 ? rpc::convert_to_string(argument_at(args, 2)) : std::string(" ");
auto text_length = utf8_length(text);
if (pad_size <= text_length || padding.empty())
return text;
if (pad_size - text_length > max_string_pad_size)
throw torrent::input_error(std::string(name) + ": padding is too large.");
auto padding_offsets = utf8_offsets(padding);
auto padding_length = static_cast<int64_t>(padding_offsets.size() - 1);
std::string result;
result.reserve(pad_size - text_length);
for (int64_t i = 0; i < pad_size - text_length; i++) {
auto index = static_cast<size_t>(i % padding_length);
result += utf8_substr(padding, padding_offsets, index, index + 1);
}
return pad_start ? result + text : text + result;
}
torrent::Object
apply_string_strip(const char* name, const torrent::Object::list_type& args, bool strip_start, bool strip_end) {
verify_argument_count(name, args, 1, 0);
auto text = rpc::convert_to_string(args.front());
std::string strippable;
for (auto itr = std::next(args.begin()); itr != args.end(); itr++)
strippable += rpc::convert_to_string(*itr);
if (args.size() == 1)
strippable = whitespace_characters;
auto characters = utf8_character_set(strippable);
auto offsets = utf8_offsets(text);
size_t first = 0;
size_t last = offsets.size() - 1;
while (strip_start && first < last && characters.count(utf8_substr(text, offsets, first, first + 1)) != 0)
first++;
while (strip_end && last > first && characters.count(utf8_substr(text, offsets, last - 1, last)) != 0)
last--;
return utf8_substr(text, offsets, first, last);
}
torrent::Object
apply_string_map(const torrent::Object::list_type& args) {
verify_argument_count("string.map", args, 2, 0);
auto text = rpc::convert_to_string(args.front());
for (auto itr = std::next(args.begin()); itr != args.end(); itr++) {
auto pair = argument_to_pair("string.map", *itr);
if (text == pair.first)
return pair.second;
}
return text;
}
torrent::Object
apply_string_replace(const torrent::Object::list_type& args) {
verify_argument_count("string.replace", args, 2, 0);
auto text = rpc::convert_to_string(args.front());
for (auto itr = std::next(args.begin()); itr != args.end(); itr++) {
auto pair = argument_to_pair("string.replace", *itr);
if (pair.first.empty())
throw torrent::input_error("string.replace: the replaced text cannot be empty.");
for (auto pos = text.find(pair.first); pos != std::string::npos; pos = text.find(pair.first, pos + pair.second.size()))
text.replace(pos, pair.first.size(), pair.second);
}
return text;
}
}
void
initialize_command_string() {
// clang-format off
CMD2_ANY_LIST("string.length", [](auto, const auto& args) { return apply_string_length(args); });
CMD2_ANY_LIST("string.substr", [](auto, const auto& args) { return apply_string_substr(args); });
CMD2_ANY_LIST("string.split", [](auto, const auto& args) { return apply_string_split(args); });
CMD2_ANY_LIST("string.join", [](auto, const auto& args) { return apply_string_join(args); });
CMD2_ANY_LIST("string.map", [](auto, const auto& args) { return apply_string_map(args); });
CMD2_ANY_LIST("string.replace", [](auto, const auto& args) { return apply_string_replace(args); });
CMD2_ANY_LIST("string.equals", [](auto, const auto& args) { return apply_string_predicate("string.equals", args, &text_equals); });
CMD2_ANY_LIST("string.starts_with", [](auto, const auto& args) { return apply_string_predicate("string.starts_with", args, &text_starts_with); });
CMD2_ANY_LIST("string.ends_with", [](auto, const auto& args) { return apply_string_predicate("string.ends_with", args, &text_ends_with); });
CMD2_ANY_LIST("string.contains", [](auto, const auto& args) { return apply_string_predicate("string.contains", args, &text_contains); });
CMD2_ANY_LIST("string.contains_i", [](auto, const auto& args) { return apply_string_predicate("string.contains_i", args, &text_contains_i); });
CMD2_ANY_LIST("string.lpad", [](auto, const auto& args) { return apply_string_pad("string.lpad", args, true); });
CMD2_ANY_LIST("string.rpad", [](auto, const auto& args) { return apply_string_pad("string.rpad", args, false); });
CMD2_ANY_LIST("string.strip", [](auto, const auto& args) { return apply_string_strip("string.strip", args, true, true); });
CMD2_ANY_LIST("string.lstrip", [](auto, const auto& args) { return apply_string_strip("string.lstrip", args, true, false); });
CMD2_ANY_LIST("string.rstrip", [](auto, const auto& args) { return apply_string_strip("string.rstrip", args, false, true); });
// clang-format on
for (const auto name : {"string.length", "string.substr", "string.split", "string.join", "string.map", "string.replace",
"string.equals", "string.starts_with", "string.ends_with", "string.contains", "string.contains_i",
"string.lpad", "string.rpad", "string.strip", "string.lstrip", "string.rstrip"})
rpc::rpc.mark_safe(name);
}
+3 -3
View File
@@ -6,7 +6,7 @@
#include <memory> #include <memory>
#include <sys/types.h> #include <sys/types.h>
#include <torrent/torrent.h> #include <torrent/torrent.h>
#include <torrent/system/scheduler.h> #include <torrent/utils/scheduler.h>
namespace ui { namespace ui {
class Root; class Root;
@@ -107,8 +107,8 @@ private:
mode_t m_umask; mode_t m_umask;
std::string m_workingDirectory; std::string m_workingDirectory;
torrent::system::SchedulerEntry m_task_shutdown; torrent::utils::SchedulerEntry m_task_shutdown;
torrent::system::SchedulerEntry m_task_shutdown_clear_requests; torrent::utils::SchedulerEntry m_task_shutdown_clear_requests;
int m_clear_requests_count{}; int m_clear_requests_count{};
+3 -3
View File
@@ -2,7 +2,7 @@
#define RTORRENT_CORE_DHT_MANAGER_H #define RTORRENT_CORE_DHT_MANAGER_H
#include <torrent/object.h> #include <torrent/object.h>
#include <torrent/system/scheduler.h> #include <torrent/utils/scheduler.h>
namespace core { namespace core {
@@ -42,8 +42,8 @@ private:
uint64_t m_dhtPrevBytesUp; uint64_t m_dhtPrevBytesUp;
uint64_t m_dhtPrevBytesDown; uint64_t m_dhtPrevBytesDown;
torrent::system::SchedulerEntry m_update_timeout; torrent::utils::SchedulerEntry m_update_timeout;
torrent::system::SchedulerEntry m_stop_timeout; torrent::utils::SchedulerEntry m_stop_timeout;
bool m_warned{}; bool m_warned{};
bool m_set_by_user{}; bool m_set_by_user{};
+7 -37
View File
@@ -35,10 +35,8 @@ is_network_uri(const std::string& uri) {
std::strncmp(uri.c_str(), "ftp://", 6) == 0; std::strncmp(uri.c_str(), "ftp://", 6) == 0;
} }
static constexpr const char* session_invalid_message = "Session data is invalid, ignoring it";
static std::unique_ptr<torrent::Object> static std::unique_ptr<torrent::Object>
download_factory_load_stream(const char* filename, bool* is_invalid) { download_factory_load_stream(const char* filename) {
std::fstream stream(filename, std::ios::in | std::ios::binary); std::fstream stream(filename, std::ios::in | std::ios::binary);
if (!stream.is_open()) if (!stream.is_open())
@@ -47,10 +45,8 @@ download_factory_load_stream(const char* filename, bool* is_invalid) {
auto obj = std::make_unique<torrent::Object>(); auto obj = std::make_unique<torrent::Object>();
stream >> *obj; stream >> *obj;
if (!stream.good() || !obj->is_map()) { if (!stream.good())
*is_invalid = true;
return std::unique_ptr<torrent::Object>(); return std::unique_ptr<torrent::Object>();
}
return obj; return obj;
} }
@@ -165,13 +161,8 @@ DownloadFactory::receive_commit() {
void void
DownloadFactory::receive_success() { DownloadFactory::receive_success() {
bool session_invalid = false; auto rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str());
auto libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str());
auto rtorrent_object = download_factory_load_stream((expand_path(m_uri) + ".rtorrent").c_str(), &session_invalid);
auto libtorrent_resume_object = download_factory_load_stream((expand_path(m_uri) + ".libtorrent_resume").c_str(), &session_invalid);
if (session_invalid)
lt_log_print(torrent::LOG_ERROR, "%s: %s", session_invalid_message, m_uri.c_str());
uint32_t tracker_key; uint32_t tracker_key;
@@ -193,14 +184,6 @@ DownloadFactory::receive_success() {
return; return;
} }
if (session_invalid) {
download->set_hash_failed(true);
download->set_message(session_invalid_message);
if (m_printLog)
m_manager->push_log_std(std::string(session_invalid_message) + ": \"" + m_uri + "\"");
}
torrent::Object* root = download->bencode(); torrent::Object* root = download->bencode();
if (download->download()->info()->is_meta_download()) { if (download->download()->info()->is_meta_download()) {
@@ -291,22 +274,9 @@ DownloadFactory::receive_success() {
rpc::call_command("d.peer_exchange.set", torrent::runtime::client_config()->is_pex_enabled(), rpc::make_target(download)); rpc::call_command("d.peer_exchange.set", torrent::runtime::client_config()->is_pex_enabled(), rpc::make_target(download));
try { torrent::resume_load_addresses(*download->download(), resumeObject);
torrent::resume_load_addresses(*download->download(), resumeObject); torrent::resume_load_file_priorities(*download->download(), resumeObject);
torrent::resume_load_file_priorities(*download->download(), resumeObject); torrent::resume_load_tracker_settings(*download->download(), resumeObject);
torrent::resume_load_tracker_settings(*download->download(), resumeObject);
} catch (const torrent::input_error& e) {
std::string msg = std::string(session_invalid_message) + ": " + e.what();
lt_log_print(torrent::LOG_ERROR, "%s: %s", msg.c_str(), m_uri.c_str());
if (m_printLog)
m_manager->push_log_std(msg + ": \"" + m_uri + "\"");
download->set_hash_failed(true);
download->set_message(msg);
}
// The action of inserting might cause the torrent to be // The action of inserting might cause the torrent to be
// opened/started or such. Figure out a nicer way of handling this. // opened/started or such. Figure out a nicer way of handling this.
+4 -4
View File
@@ -9,7 +9,7 @@
#include <iosfwd> #include <iosfwd>
#include <torrent/object.h> #include <torrent/object.h>
#include <torrent/system/scheduler.h> #include <torrent/utils/scheduler.h>
#include "http_queue.h" #include "http_queue.h"
@@ -79,9 +79,9 @@ private:
command_list_type m_commands; command_list_type m_commands;
torrent::Object::map_type m_variables; torrent::Object::map_type m_variables;
slot_void m_slot_finished; slot_void m_slot_finished;
torrent::system::SchedulerEntry m_task_load; torrent::utils::SchedulerEntry m_task_load;
torrent::system::SchedulerEntry m_task_commit; torrent::utils::SchedulerEntry m_task_commit;
}; };
bool is_network_uri(const std::string& uri); bool is_network_uri(const std::string& uri);
+3 -3
View File
@@ -18,7 +18,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <torrent/object.h> #include <torrent/object.h>
#include <torrent/system/scheduler.h> #include <torrent/utils/scheduler.h>
#include "globals.h" #include "globals.h"
@@ -153,8 +153,8 @@ private:
std::chrono::microseconds m_last_changed{}; std::chrono::microseconds m_last_changed{};
signal_void m_signal_changed; signal_void m_signal_changed;
torrent::system::SchedulerEntry m_delay_changed; torrent::utils::SchedulerEntry m_delay_changed;
}; };
} // namespace core } // namespace core
+4 -4
View File
@@ -1,7 +1,7 @@
#ifndef RTORRENT_DISPLAY_MANAGER_H #ifndef RTORRENT_DISPLAY_MANAGER_H
#define RTORRENT_DISPLAY_MANAGER_H #define RTORRENT_DISPLAY_MANAGER_H
#include <torrent/system/scheduler.h> #include <torrent/utils/scheduler.h>
#include "display/frame.h" #include "display/frame.h"
@@ -31,9 +31,9 @@ private:
bool m_force_redraw{false}; bool m_force_redraw{false};
Frame m_root_frame; Frame m_root_frame;
std::chrono::microseconds m_time_last_update{}; std::chrono::microseconds m_time_last_update{};
torrent::system::ExternalScheduler m_scheduler; torrent::utils::ExternalScheduler m_scheduler;
torrent::system::SchedulerEntry m_task_update; torrent::utils::SchedulerEntry m_task_update;
}; };
} }
+2 -2
View File
@@ -345,7 +345,7 @@ print_status_info(char* first, char* last) {
if (!torrent::up_throttle_global()->is_throttled()) { if (!torrent::up_throttle_global()->is_throttled()) {
first = print_buffer(first, last, "[Throttle off"); first = print_buffer(first, last, "[Throttle off");
} else { } else {
first = print_buffer(first, last, "[Throttle %3i", (int)(torrent::up_throttle_global()->max_rate() / 1024)); first = print_buffer(first, last, "[Throttle %3i", torrent::up_throttle_global()->max_rate() / 1024);
if (!throttle_up_names.empty()) if (!throttle_up_names.empty())
first = print_status_throttle_limit(first, last, true, throttle_up_names); first = print_status_throttle_limit(first, last, true, throttle_up_names);
@@ -354,7 +354,7 @@ print_status_info(char* first, char* last) {
if (!torrent::down_throttle_global()->is_throttled()) { if (!torrent::down_throttle_global()->is_throttled()) {
first = print_buffer(first, last, " / off KB]"); first = print_buffer(first, last, " / off KB]");
} else { } else {
first = print_buffer(first, last, " / %3i", (int)(torrent::down_throttle_global()->max_rate() / 1024)); first = print_buffer(first, last, " / %3i", torrent::down_throttle_global()->max_rate() / 1024);
if (!throttle_down_names.empty()) if (!throttle_down_names.empty())
first = print_status_throttle_limit(first, last, false, throttle_down_names); first = print_status_throttle_limit(first, last, false, throttle_down_names);
+2 -2
View File
@@ -2,7 +2,7 @@
#define RTORRENT_WINDOW_BASE_H #define RTORRENT_WINDOW_BASE_H
#include <functional> #include <functional>
#include <torrent/system/scheduler.h> #include <torrent/utils/scheduler.h>
#include <torrent/system/thread.h> #include <torrent/system/thread.h>
#include "canvas.h" #include "canvas.h"
@@ -99,7 +99,7 @@ protected:
extent_type m_max_width; extent_type m_max_width;
extent_type m_max_height; extent_type m_max_height;
torrent::system::SchedulerEntry m_task_update; torrent::utils::SchedulerEntry m_task_update;
}; };
// Return a range with a distance of no more than __distance and // Return a range with a distance of no more than __distance and
+1 -1
View File
@@ -47,7 +47,7 @@ private:
signal_curl_get::iterator m_conn_insert; signal_curl_get::iterator m_conn_insert;
signal_curl_get::iterator m_conn_erase; signal_curl_get::iterator m_conn_erase;
torrent::system::SchedulerEntry m_task_deactivate; torrent::utils::SchedulerEntry m_task_deactivate;
}; };
} }
+4 -6
View File
@@ -1,8 +1,8 @@
#ifndef RTORRENT_DISPLAY_WINDOW_LOG_H #ifndef RTORRENT_DISPLAY_WINDOW_LOG_H
#define RTORRENT_DISPLAY_WINDOW_LOG_H #define RTORRENT_DISPLAY_WINDOW_LOG_H
#include <torrent/system/scheduler.h>
#include <torrent/utils/log_buffer.h> #include <torrent/utils/log_buffer.h>
#include <torrent/utils/scheduler.h>
#include "window.h" #include "window.h"
@@ -22,12 +22,10 @@ public:
private: private:
inline iterator find_older(); inline iterator find_older();
torrent::log_buffer* m_log; torrent::log_buffer* m_log;
torrent::system::SchedulerEntry m_task_update; torrent::utils::SchedulerEntry m_task_update;
align_cacheline align_cacheline std::atomic<bool> m_log_updating{};
std::atomic<bool> m_log_updating{};
}; };
} }
-30
View File
@@ -3,7 +3,6 @@
#include "globals.h" #include "globals.h"
#include <cstdlib> #include <cstdlib>
#include <stdlib.h>
#include <torrent/exceptions.h> #include <torrent/exceptions.h>
rpc::ip_table_list ip_tables; rpc::ip_table_list ip_tables;
@@ -29,32 +28,3 @@ expand_path(const std::string& path) {
return path; return path;
} }
// Resolves a path to a canonical one with no symlinks or relative components,
// so it can safely be handed to an external script. Returns an empty string if
// the path does not name an existing file or directory.
std::string
resolve_path(const std::string& path) {
if (path.empty())
return std::string();
char* resolved = ::realpath(expand_path(path).c_str(), nullptr);
if (resolved == nullptr)
return std::string();
std::string result(resolved);
std::free(resolved);
return result;
}
std::string
resolve_path_or_throw(const std::string& path) {
auto result = resolve_path(path);
if (result.empty())
throw torrent::input_error("Could not resolve path: '" + path + "'.");
return result;
}
-2
View File
@@ -11,8 +11,6 @@ extern rpc::ip_table_list ip_tables;
extern Control* control; extern Control* control;
std::string expand_path(const std::string& path); std::string expand_path(const std::string& path);
std::string resolve_path(const std::string& path);
std::string resolve_path_or_throw(const std::string& path);
namespace rpc { namespace rpc {
class SCgi; class SCgi;
+3 -2
View File
@@ -392,12 +392,13 @@ main(int argc, char** argv) {
CMD_REDIRECT("bind", "network.bind_address.set"); CMD_REDIRECT("bind", "network.bind_address.set");
CMD_REDIRECT("ip", "network.local_address.set"); CMD_REDIRECT("ip", "network.local_address.set");
CMD_REDIRECT("port_range", "network.port_range.set");
// TODO: Check if dht is on by default. // TODO: Check if dht is on by default.
CMD_REDIRECT("dht", "dht.mode.set"); CMD_REDIRECT("dht", "dht.mode.set");
CMD_REDIRECT("port_random", "network.listen.port.random.set"); CMD_REDIRECT("port_random", "network.port_random.set");
CMD_REDIRECT("proxy_address", "network.proxy.http.set"); CMD_REDIRECT("proxy_address", "network.proxy_address.set");
CMD_REDIRECT("key_layout", "keys.layout.set"); CMD_REDIRECT("key_layout", "keys.layout.set");
+2 -2
View File
@@ -5,7 +5,7 @@
#include <functional> #include <functional>
#include <torrent/object.h> #include <torrent/object.h>
#include <torrent/system/scheduler.h> #include <torrent/utils/scheduler.h>
namespace rpc { namespace rpc {
@@ -43,7 +43,7 @@ private:
uint32_t m_interval{}; uint32_t m_interval{};
std::chrono::microseconds m_time_scheduled; std::chrono::microseconds m_time_scheduled;
torrent::system::SchedulerEntry m_task; torrent::utils::SchedulerEntry m_task;
// Flags for various things. // Flags for various things.
}; };
-57
View File
@@ -76,61 +76,4 @@ ExecFile::execute(const char* file, char* const* argv, int flags) {
return spawn_process.wait_for_child(); return spawn_process.wait_for_child();
} }
torrent::Object
ExecFile::execute_object(const torrent::Object& rawArgs, int flags) {
char* argsBuffer[max_args];
char** argsCurrent = argsBuffer;
// Size of value strings are less than 24.
char valueBuffer[buffer_size+1];
char* valueCurrent = valueBuffer;
if (rawArgs.is_list()) {
const torrent::Object::list_type& args = rawArgs.as_list();
if (args.empty())
throw torrent::input_error("Too few arguments.");
for (torrent::Object::list_const_iterator itr = args.begin(), last = args.end(); itr != last; itr++, argsCurrent++) {
if (argsCurrent == argsBuffer + max_args - 1)
throw torrent::input_error("Too many arguments.");
if (itr->is_string() && (!(flags & flag_expand_tilde) || *itr->as_string().c_str() != '~')) {
*argsCurrent = const_cast<char*>(itr->as_string().c_str());
} else {
*argsCurrent = valueCurrent;
valueCurrent = print_object(valueCurrent, valueBuffer + buffer_size, &*itr, flags) + 1;
if (valueCurrent >= valueBuffer + buffer_size)
throw torrent::input_error("Overflowed execute arg buffer.");
}
}
} else {
const torrent::Object::string_type& args = rawArgs.as_string();
if ((flags & flag_expand_tilde) && args.c_str()[0] == '~') {
*argsCurrent = valueCurrent;
valueCurrent = print_object(valueCurrent, valueBuffer + buffer_size, &rawArgs, flags) + 1;
} else {
*argsCurrent = const_cast<char*>(args.c_str());
}
argsCurrent++;
}
*argsCurrent = NULL;
int status = execute(argsBuffer[0], argsBuffer, flags);
if ((flags & flag_throw) && status != 0)
throw torrent::input_error("Bad return code.");
if (flags & flag_capture)
return m_capture;
return torrent::Object((int64_t)status);
}
} // namespace rpc } // namespace rpc
+5 -2
View File
@@ -258,8 +258,11 @@ JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
return callback(response_str.c_str(), response_str.size()); return callback(response_str.c_str(), response_str.size());
} catch (json::exception& e) { } catch (json::parse_error& e) {
// Exception strings may contain invalid UTF-8, hence the ::replace auto err_str = json_error(JSONRPC_PARSE_ERROR, e.what(), nullptr).dump(-1, ' ', false, json::error_handler_t::replace);
return callback(err_str.c_str(), err_str.size());
} catch (json::type_error& e) {
// Type errors may be caused by invalid UTF-8 strings in exception strings, hence the ::replace
auto err_str = json_error(JSONRPC_PARSE_ERROR, e.what(), nullptr).dump(-1, ' ', false, json::error_handler_t::replace); auto err_str = json_error(JSONRPC_PARSE_ERROR, e.what(), nullptr).dump(-1, ' ', false, json::error_handler_t::replace);
return callback(err_str.c_str(), err_str.size()); return callback(err_str.c_str(), err_str.size());
} }
+1 -5
View File
@@ -94,12 +94,8 @@ SCgiTask::event_read() {
if (m_content_length == 0) if (m_content_length == 0)
read_length--; read_length--;
if (read_length <= 0) { if (read_length <= 0)
if (m_content_length == 0)
return close();
throw torrent::internal_error("SCgiTask::event_read() no space in buffer for event_read."); throw torrent::internal_error("SCgiTask::event_read() no space in buffer for event_read.");
}
int bytes = ::recv(file_descriptor(), m_buffer.data() + m_position, read_length, 0); int bytes = ::recv(file_descriptor(), m_buffer.data() + m_position, read_length, 0);
-5
View File
@@ -451,11 +451,6 @@ XmlRpc::process(const char* inBuffer, uint32_t length, slot_write slotWrite) {
if (local_env.fault_occurred && local_env.fault_code == XMLRPC_INTERNAL_ERROR) if (local_env.fault_occurred && local_env.fault_code == XMLRPC_INTERNAL_ERROR)
throw torrent::internal_error("Internal error in XMLRPC."); throw torrent::internal_error("Internal error in XMLRPC.");
if (memblock == nullptr) {
xmlrpc_env_clean(&local_env);
return false;
}
bool result = slotWrite((const char*)xmlrpc_mem_block_contents(memblock), bool result = slotWrite((const char*)xmlrpc_mem_block_contents(memblock),
xmlrpc_mem_block_size(memblock)); xmlrpc_mem_block_size(memblock));
+5 -26
View File
@@ -49,23 +49,13 @@ element_access(const tinyxml2::XMLElement* elem, std::initializer_list<std::stri
return result; return result;
} }
const char*
element_text_value(const tinyxml2::XMLNode* node) {
auto text = node->ToText();
if (text == nullptr)
throw rpc_error(XMLRPC_TYPE_ERROR, "expected a text value");
return text->Value();
}
long long long long
element_to_int(const tinyxml2::XMLNode* elem) { element_to_int(const tinyxml2::XMLNode* elem) {
char* pos; char* pos;
if (elem->FirstChild() == nullptr) { if (elem->FirstChild() == nullptr) {
throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse empty integer"); throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse empty integer");
} }
auto str = element_text_value(elem->FirstChild()); auto str = elem->FirstChild()->ToText()->Value();
auto result = std::strtoll(str, &pos, 10); auto result = std::strtoll(str, &pos, 10);
if (pos == str || *pos != '\0') if (pos == str || *pos != '\0')
throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse integer value"); throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse integer value");
@@ -95,7 +85,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (child_element == nullptr) if (child_element == nullptr)
return torrent::Object(""); return torrent::Object("");
return torrent::Object(element_text_value(child_element)); return torrent::Object(child_element->ToText()->Value());
} else if (std::strncmp(root_type, "int", sizeof("int")) == 0 || } else if (std::strncmp(root_type, "int", sizeof("int")) == 0 ||
std::strncmp(root_type, "i4", sizeof("i4")) == 0 || std::strncmp(root_type, "i4", sizeof("i4")) == 0 ||
@@ -108,7 +98,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (child_element == nullptr) if (child_element == nullptr)
throw rpc_error(XMLRPC_TYPE_ERROR, "empty boolean element"); throw rpc_error(XMLRPC_TYPE_ERROR, "empty boolean element");
auto boolean_text = std::string(element_text_value(child_element)); auto boolean_text = std::string(child_element->ToText()->Value());
if (boolean_text == "1") if (boolean_text == "1")
return torrent::Object((int64_t)1); return torrent::Object((int64_t)1);
@@ -139,12 +129,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (name_element == nullptr) if (name_element == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "struct member missing name element"); throw rpc_error(XMLRPC_PARSE_ERROR, "struct member missing name element");
auto name_text = name_element->GetText(); map[name_element->GetText()] = std::move(xml_value_to_object(child->FirstChildElement("value")));
if (name_text == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "struct member has an empty name element");
map[name_text] = std::move(xml_value_to_object(child->FirstChildElement("value")));
} }
return map_raw; return map_raw;
@@ -155,7 +140,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
if (child_element == nullptr) if (child_element == nullptr)
return torrent::Object(""); return torrent::Object("");
return torrent::Object(utils::decode_base64(utils::remove_newlines(element_text_value(child_element)))); return torrent::Object(utils::decode_base64(utils::remove_newlines(child_element->ToText()->Value())));
} else { } else {
throw rpc_error(XMLRPC_INTERNAL_ERROR, "received unsupported value type: " + std::string(root_type)); throw rpc_error(XMLRPC_INTERNAL_ERROR, "received unsupported value type: " + std::string(root_type));
@@ -315,9 +300,6 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer
if (doc->FirstChildElement("methodCall")->FirstChildElement("methodName") == nullptr) if (doc->FirstChildElement("methodCall")->FirstChildElement("methodName") == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element not found"); throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element not found");
auto method_name = doc->FirstChildElement("methodCall")->FirstChildElement("methodName")->GetText(); auto method_name = doc->FirstChildElement("methodCall")->FirstChildElement("methodName")->GetText();
if (method_name == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element is empty");
torrent::Object result; torrent::Object result;
// Add a shim here for system.multicall to allow better code reuse, and // Add a shim here for system.multicall to allow better code reuse, and
@@ -328,9 +310,6 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer
auto parent_elements = element_access(doc->RootElement(), {"params", "param", "value", "array", "data"}); auto parent_elements = element_access(doc->RootElement(), {"params", "param", "value", "array", "data"});
for (auto child = parent_elements->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) { for (auto child = parent_elements->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) {
auto sub_method_name = element_access(child, {"struct", "member", "value", "string"})->GetText(); auto sub_method_name = element_access(child, {"struct", "member", "value", "string"})->GetText();
if (sub_method_name == nullptr)
throw rpc_error(XMLRPC_PARSE_ERROR, "multicall methodName element is empty");
// If sub_params ends up a nullptr at the end of this if-chian, // If sub_params ends up a nullptr at the end of this if-chian,
// execute_command will turn it into an empty list // execute_command will turn it into an empty list
auto sub_params = element_access(child, {"struct", "member"}); auto sub_params = element_access(child, {"struct", "member"});
+1 -1
View File
@@ -89,7 +89,7 @@ DownloadList::unfocus_download(core::Download* d) {
if (m_state == DISPLAY_DOWNLOAD && d == static_cast<Download*>(m_uiArray[DISPLAY_DOWNLOAD])->download()) if (m_state == DISPLAY_DOWNLOAD && d == static_cast<Download*>(m_uiArray[DISPLAY_DOWNLOAD])->download())
activate_display(DISPLAY_DOWNLOAD_LIST); activate_display(DISPLAY_DOWNLOAD_LIST);
if (current_view()->focus() < current_view()->end_visible() && *current_view()->focus() == d) if (*current_view()->focus() == d && current_view()->focus() < current_view()->end_visible())
current_view()->next_focus(); current_view()->next_focus();
} }
+2 -4
View File
@@ -4,7 +4,6 @@
#include <fstream> #include <fstream>
#include <stdexcept> #include <stdexcept>
#include <string>
#include <string.h> #include <string.h>
#include <torrent/throttle.h> #include <torrent/throttle.h>
#include <torrent/torrent.h> #include <torrent/torrent.h>
@@ -355,9 +354,8 @@ Root::reset_input_history_attributes(ui::DownloadList::Input type) {
void void
Root::set_input_history_size(int size) { Root::set_input_history_size(int size) {
if (size < 1 || size > max_input_history_size) if (size < 1)
throw torrent::input_error("Input history size must be between 1 and " + throw torrent::input_error("Invalid input history size.");
std::to_string(max_input_history_size) + ".");
for (auto& [entry, category] : m_input_history) { for (auto& [entry, category] : m_input_history) {
// Reserve the latest input history entries if new size is smaller than original. // Reserve the latest input history entries if new size is smaller than original.
-2
View File
@@ -54,8 +54,6 @@ public:
typedef std::vector<std::string> InputHistoryCategory; typedef std::vector<std::string> InputHistoryCategory;
typedef std::map<int, InputHistoryCategory> InputHistory; typedef std::map<int, InputHistoryCategory> InputHistory;
static constexpr int max_input_history_size = 4096;
Root(); Root();
void init(Control* c); void init(Control* c);
+22 -14
View File
@@ -9,37 +9,38 @@ namespace utils {
WaitpidQueue::WaitpidQueue() { WaitpidQueue::WaitpidQueue() {
m_worker = std::async(std::launch::async, [this]() { m_worker = std::async(std::launch::async, [this]() {
bool is_running = true;
auto wait_time = 50ms; auto wait_time = 50ms;
while (true) { while (is_running) {
if (!m_queue.empty()) { if (!m_queue.empty()) {
auto start_time = std::chrono::steady_clock::now(); auto start_time = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - start_time < wait_time) { while (!m_wakeup_worker.load(std::memory_order_acquire) && !m_should_shutdown) {
if (m_should_shutdown.load(std::memory_order_acquire)) auto elapsed = std::chrono::steady_clock::now() - start_time;
return;
if (elapsed >= wait_time)
break;
std::this_thread::sleep_for(50ms); std::this_thread::sleep_for(50ms);
if (m_wakeup_worker.load(std::memory_order_acquire))
break;
} }
} else { } else {
m_wakeup_worker.wait(false, std::memory_order_acquire); m_wakeup_worker.wait(false, std::memory_order_acquire);
std::this_thread::sleep_for(50ms);
} }
// Adds a small delay to allow new processes to finish if they're quickly spawned and
// terminated.
std::this_thread::sleep_for(50ms);
std::set<pid_t> queue; std::set<pid_t> queue;
{ {
std::lock_guard<std::mutex> guard(m_mutex); std::lock_guard<std::mutex> guard(m_mutex);
if (m_should_shutdown) if (m_should_shutdown) {
return; if (m_queue.empty())
return;
is_running = false;
}
if (m_queue.empty()) if (m_queue.empty())
throw torrent::internal_error("WaitpidQueue worker thread woke up but queue is empty."); throw torrent::internal_error("WaitpidQueue worker thread woke up but queue is empty.");
@@ -71,7 +72,6 @@ WaitpidQueue::WaitpidQueue() {
}); });
} }
// We don't wait for the worker thread to finish as waitpid isn't needed to be called on shutdown.
WaitpidQueue::~WaitpidQueue() { WaitpidQueue::~WaitpidQueue() {
{ {
std::lock_guard<std::mutex> guard(m_mutex); std::lock_guard<std::mutex> guard(m_mutex);
@@ -80,6 +80,8 @@ WaitpidQueue::~WaitpidQueue() {
m_wakeup_worker.store(true, std::memory_order_release); m_wakeup_worker.store(true, std::memory_order_release);
m_wakeup_worker.notify_all(); m_wakeup_worker.notify_all();
// m_worker.wait();
} }
void void
@@ -91,6 +93,12 @@ WaitpidQueue::close_pid(pid_t pid) {
{ {
std::lock_guard<std::mutex> guard(m_mutex); std::lock_guard<std::mutex> guard(m_mutex);
// if (!m_queue.empty()) {
// m_queue.push_back(pid);
// return;
// }
m_queue.insert(pid); m_queue.insert(pid);
} }
+2 -4
View File
@@ -18,8 +18,6 @@ public:
void wait_for(uint32_t max_remaining); void wait_for(uint32_t max_remaining);
// TODO: Add a signal handler to tell the worker thread to wake up. Use a counter to batch wakeups.
private: private:
WaitpidQueue(const WaitpidQueue&) = delete; WaitpidQueue(const WaitpidQueue&) = delete;
WaitpidQueue& operator=(const WaitpidQueue&) = delete; WaitpidQueue& operator=(const WaitpidQueue&) = delete;
@@ -31,11 +29,11 @@ private:
std::mutex m_mutex; std::mutex m_mutex;
std::set<pid_t> m_queue; std::set<pid_t> m_queue;
bool m_should_shutdown{};
align_cacheline align_cacheline
std::atomic<bool> m_wakeup_worker{}; std::atomic<bool> m_wakeup_worker{};
std::atomic<bool> m_should_shutdown{};
std::atomic<uint32_t> m_remaining{}; std::atomic<uint32_t> m_remaining{};
}; };
+5 -7
View File
@@ -9,7 +9,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <torrent/system/scheduler.h> #include <torrent/utils/scheduler.h>
namespace utils { namespace utils {
@@ -49,12 +49,10 @@ private:
void update_status(Entry* entry); void update_status(Entry* entry);
void schedule(); void schedule();
std::map<std::string, Entry> m_entries; std::map<std::string, Entry> m_entries;
std::vector<Entry*> m_entry_queue; std::vector<Entry*> m_entry_queue;
torrent::utils::SchedulerEntry m_task_process;
bool m_active{true}; bool m_active{true};
torrent::system::SchedulerEntry m_task_process;
}; };
} }
-4
View File
@@ -51,10 +51,6 @@ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
src/test_command_dynamic.h \ src/test_command_dynamic.h \
src/test_command_local.cc \ src/test_command_local.cc \
src/test_command_local.h \ src/test_command_local.h \
src/test_command_path.cc \
src/test_command_path.h \
src/test_command_string.cc \
src/test_command_string.h \
src/test_watch_ready_queue.cc \ src/test_watch_ready_queue.cc \
src/test_watch_ready_queue.h src/test_watch_ready_queue.h
+1
View File
@@ -8,6 +8,7 @@
#include "torrent/exceptions.h" #include "torrent/exceptions.h"
#include "torrent/net/resolver.h" #include "torrent/net/resolver.h"
#include "torrent/utils/log.h" #include "torrent/utils/log.h"
#include "torrent/utils/scheduler.h"
std::unique_ptr<TestMainThread> std::unique_ptr<TestMainThread>
TestMainThread::create() { TestMainThread::create() {
+1 -1
View File
@@ -45,8 +45,8 @@ TestCommandLocal::test_socket_category_commands() {
CPPUNIT_ASSERT(rpc::commands.has(name + ".size")); CPPUNIT_ASSERT(rpc::commands.has(name + ".size"));
CPPUNIT_ASSERT(rpc::commands.has(name + ".max_size")); CPPUNIT_ASSERT(rpc::commands.has(name + ".max_size"));
CPPUNIT_ASSERT(rpc::commands.has(name + ".min_alloc"));
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".min_alloc"));
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".max_alloc")); CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".max_alloc"));
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".min_alloc.set")); CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".min_alloc.set"));
CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".max_alloc.set")); CPPUNIT_ASSERT_EQUAL(has_allocation, rpc::commands.has(name + ".max_alloc.set"));
-116
View File
@@ -1,116 +0,0 @@
#include "config.h"
#include "test/src/test_command_path.h"
#include <cstdlib>
#include <sys/stat.h>
#include <unistd.h>
#include <torrent/exceptions.h>
#include <torrent/torrent.h>
#include "control.h"
#include "globals.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandPath);
void initialize_command_local();
void
TestCommandPath::setUp() {
char temp_dir[] = "/tmp/rtorrent_test_path_XXXXXX";
CPPUNIT_ASSERT(mkdtemp(temp_dir) != nullptr);
// The temporary directory itself may sit behind a symlink, as /tmp does on
// macOS, so resolve it up front to keep the expected values exact.
m_temp_dir = resolve_path(temp_dir);
CPPUNIT_ASSERT(!m_temp_dir.empty());
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/data").c_str(), 0755));
CPPUNIT_ASSERT_EQUAL(0, symlink((m_temp_dir + "/data").c_str(), (m_temp_dir + "/link").c_str()));
}
void
TestCommandPath::tearDown() {
unlink((m_temp_dir + "/link").c_str());
rmdir((m_temp_dir + "/data").c_str());
rmdir(m_temp_dir.c_str());
}
void
TestCommandPath::test_resolves_symlink() {
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/link"));
// A path that lies below a symlinked directory is resolved as well.
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/data/below").c_str(), 0755));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data/below", resolve_path(m_temp_dir + "/link/below"));
rmdir((m_temp_dir + "/data/below").c_str());
}
void
TestCommandPath::test_removes_relative_components() {
CPPUNIT_ASSERT_EQUAL(m_temp_dir, resolve_path(m_temp_dir + "/data/.."));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/./data"));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "/data/"));
// Trailing slashes and duplicated separators collapse.
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path(m_temp_dir + "//data//"));
}
void
TestCommandPath::test_expands_tilde() {
const char* home = std::getenv("HOME");
if (home == nullptr || *home == '\0')
return;
CPPUNIT_ASSERT_EQUAL(resolve_path(home), resolve_path("~"));
CPPUNIT_ASSERT_THROW(resolve_path("~root/somewhere"), torrent::input_error);
}
void
TestCommandPath::test_missing_path_throws() {
CPPUNIT_ASSERT_THROW(resolve_path_or_throw(""), torrent::input_error);
CPPUNIT_ASSERT_THROW(resolve_path_or_throw(m_temp_dir + "/does_not_exist"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", resolve_path_or_throw(m_temp_dir + "/link"));
}
void
TestCommandPath::test_missing_path_is_empty() {
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(""));
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/does_not_exist"));
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/does_not_exist/below"));
// A dangling symlink does not name an existing path either.
CPPUNIT_ASSERT_EQUAL(0, symlink((m_temp_dir + "/gone").c_str(), (m_temp_dir + "/dangling").c_str()));
CPPUNIT_ASSERT_EQUAL(std::string(), resolve_path(m_temp_dir + "/dangling"));
unlink((m_temp_dir + "/dangling").c_str());
}
void
TestCommandPath::test_commands() {
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has("directory.default.realpath.or_empty"))
initialize_command_local();
rpc::commands.call_command("directory.default.set", m_temp_dir + "/link");
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/link", rpc::commands.call_command("directory.default", torrent::Object()).as_string());
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", rpc::commands.call_command("directory.default.realpath.or_empty", torrent::Object()).as_string());
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/data", rpc::commands.call_command("directory.default.realpath.or_throw", torrent::Object()).as_string());
// A directory that has not been created yet resolves to nothing, and the
// or_throw variant reports it instead.
rpc::commands.call_command("directory.default.set", m_temp_dir + "/missing");
CPPUNIT_ASSERT_EQUAL(std::string(), rpc::commands.call_command("directory.default.realpath.or_empty", torrent::Object()).as_string());
CPPUNIT_ASSERT_THROW(rpc::commands.call_command("directory.default.realpath.or_throw", torrent::Object()), torrent::input_error);
torrent::cleanup();
}
-30
View File
@@ -1,30 +0,0 @@
#include "test/helpers/test_fixture.h"
#include <string>
class TestCommandPath : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandPath);
CPPUNIT_TEST(test_resolves_symlink);
CPPUNIT_TEST(test_removes_relative_components);
CPPUNIT_TEST(test_expands_tilde);
CPPUNIT_TEST(test_missing_path_is_empty);
CPPUNIT_TEST(test_missing_path_throws);
CPPUNIT_TEST(test_commands);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_resolves_symlink();
void test_removes_relative_components();
void test_expands_tilde();
void test_missing_path_is_empty();
void test_missing_path_throws();
void test_commands();
private:
std::string m_temp_dir;
};
-261
View File
@@ -1,261 +0,0 @@
#include "config.h"
#include "test/src/test_command_string.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandString);
void initialize_command_string();
namespace {
torrent::Object
args(std::initializer_list<torrent::Object> objects) {
auto result = torrent::Object::create_list();
for (const auto& object : objects)
result.as_list().push_back(object);
return result;
}
std::string
call_string(const char* key, std::initializer_list<torrent::Object> objects) {
return rpc::commands.call_command(key, args(objects)).as_string();
}
int64_t
call_value(const char* key, std::initializer_list<torrent::Object> objects) {
return rpc::commands.call_command(key, args(objects)).as_value();
}
torrent::Object::list_type
call_list(const char* key, std::initializer_list<torrent::Object> objects) {
return rpc::commands.call_command(key, args(objects)).as_list();
}
// Runs a command the way a line in the configuration file would.
torrent::Object
parse(const char* command) {
return rpc::parse_command_single(rpc::make_target(), command);
}
}
void
TestCommandString::setUp() {
if (!rpc::commands.has("string.length"))
initialize_command_string();
}
void
TestCommandString::tearDown() {
}
void
TestCommandString::test_length() {
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.length", {""}));
CPPUNIT_ASSERT_EQUAL(int64_t(3), call_value("string.length", {"abc"}));
// The length is counted in utf-8 characters, not bytes.
CPPUNIT_ASSERT_EQUAL(int64_t(5), call_value("string.length", {"héllo"}));
CPPUNIT_ASSERT_EQUAL(int64_t(3), call_value("string.length", {"日本語"}));
// Values are converted to their string representation.
CPPUNIT_ASSERT_EQUAL(int64_t(4), call_value("string.length", {int64_t(1234)}));
}
void
TestCommandString::test_equals() {
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.equals", {"abc", "abc"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.equals", {"abc", "abd"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.equals", {"abc", "ab"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.equals", {"abc", "x", "abc"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.equals", {"abc", "x", "y"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.equals", {int64_t(42), "42"}));
}
void
TestCommandString::test_starts_with() {
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.starts_with", {"abcdef", "abc"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.starts_with", {"abcdef", ""}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.starts_with", {"abcdef", "bcd"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.starts_with", {"ab", "abc"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.starts_with", {"abcdef", "x", "ab"}));
}
void
TestCommandString::test_ends_with() {
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.ends_with", {"abcdef", "def"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.ends_with", {"abcdef", ""}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.ends_with", {"abcdef", "cde"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.ends_with", {"ef", "def"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.ends_with", {"a.torrent", ".rar", ".torrent"}));
}
void
TestCommandString::test_contains() {
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains", {"abcdef", "cde"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.contains", {"abcdef", "ace"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains", {"abcdef", "x", "bcd"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.contains", {"retracker.local", "RETRACKER"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains_i", {"retracker.local", "RETRACKER"}));
CPPUNIT_ASSERT_EQUAL(int64_t(1), call_value("string.contains_i", {"RETRACKER.LOCAL", "retracker"}));
CPPUNIT_ASSERT_EQUAL(int64_t(0), call_value("string.contains_i", {"abcdef", "xyz"}));
}
void
TestCommandString::test_substr() {
CPPUNIT_ASSERT_EQUAL(std::string("abcdef"), call_string("string.substr", {"abcdef"}));
CPPUNIT_ASSERT_EQUAL(std::string("cdef"), call_string("string.substr", {"abcdef", int64_t(2)}));
CPPUNIT_ASSERT_EQUAL(std::string("cde"), call_string("string.substr", {"abcdef", int64_t(2), int64_t(3)}));
CPPUNIT_ASSERT_EQUAL(std::string("cdef"), call_string("string.substr", {"abcdef", int64_t(2), int64_t(100)}));
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.substr", {"abcdef", int64_t(2), int64_t(0)}));
// Negative positions are relative to the end of the string.
CPPUNIT_ASSERT_EQUAL(std::string("ef"), call_string("string.substr", {"abcdef", int64_t(-2)}));
CPPUNIT_ASSERT_EQUAL(std::string("e"), call_string("string.substr", {"abcdef", int64_t(-2), int64_t(1)}));
// Out-of-bounds positions return the default value.
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.substr", {"abcdef", int64_t(10)}));
CPPUNIT_ASSERT_EQUAL(std::string("n/a"), call_string("string.substr", {"abcdef", int64_t(10), int64_t(1), "n/a"}));
CPPUNIT_ASSERT_EQUAL(std::string("n/a"), call_string("string.substr", {"abcdef", int64_t(-10), int64_t(1), "n/a"}));
// Positions and counts are in utf-8 characters, not bytes.
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.substr", {"日本語", int64_t(1), int64_t(1)}));
CPPUNIT_ASSERT_EQUAL(std::string("本語"), call_string("string.substr", {"日本語", int64_t(-2)}));
}
void
TestCommandString::test_split() {
auto parts = call_list("string.split", {"a,b,c", ","});
CPPUNIT_ASSERT_EQUAL(size_t(3), parts.size());
CPPUNIT_ASSERT_EQUAL(std::string("a"), parts.front().as_string());
CPPUNIT_ASSERT_EQUAL(std::string("c"), parts.back().as_string());
// Empty fields are preserved.
CPPUNIT_ASSERT_EQUAL(size_t(3), call_list("string.split", {"a,,b", ","}).size());
CPPUNIT_ASSERT_EQUAL(size_t(1), call_list("string.split", {"abc", ","}).size());
// A multi-character delimiter is matched as a whole.
CPPUNIT_ASSERT_EQUAL(size_t(2), call_list("string.split", {"a::b", "::"}).size());
// An empty delimiter splits the text into its utf-8 characters.
auto characters = call_list("string.split", {"日本語", ""});
CPPUNIT_ASSERT_EQUAL(size_t(3), characters.size());
CPPUNIT_ASSERT_EQUAL(std::string(""), characters.front().as_string());
}
void
TestCommandString::test_join() {
CPPUNIT_ASSERT_EQUAL(std::string("a, b"), call_string("string.join", {", ", "a", "b"}));
CPPUNIT_ASSERT_EQUAL(std::string("ab"), call_string("string.join", {"", "a", "b"}));
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.join", {", "}));
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.join", {", ", "a"}));
CPPUNIT_ASSERT_EQUAL(std::string("1-2"), call_string("string.join", {"-", int64_t(1), int64_t(2)}));
// Lists are flattened, so the output of string.split can be joined again.
CPPUNIT_ASSERT_EQUAL(std::string("a-b-c"), call_string("string.join", {"-", args({"a", "b"}), "c"}));
}
void
TestCommandString::test_pad() {
CPPUNIT_ASSERT_EQUAL(std::string(" 7"), call_string("string.lpad", {"7", int64_t(3)}));
CPPUNIT_ASSERT_EQUAL(std::string("7 "), call_string("string.rpad", {"7", int64_t(3)}));
CPPUNIT_ASSERT_EQUAL(std::string("007"), call_string("string.lpad", {"7", int64_t(3), "0"}));
CPPUNIT_ASSERT_EQUAL(std::string("700"), call_string("string.rpad", {"7", int64_t(3), "0"}));
// Text that is already long enough is returned unchanged.
CPPUNIT_ASSERT_EQUAL(std::string("abcd"), call_string("string.lpad", {"abcd", int64_t(2)}));
CPPUNIT_ASSERT_EQUAL(std::string("abcd"), call_string("string.rpad", {"abcd", int64_t(4)}));
// Multi-character padding is repeated, and an empty padding is a no-op.
CPPUNIT_ASSERT_EQUAL(std::string("axyx"), call_string("string.rpad", {"a", int64_t(4), "xy"}));
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.rpad", {"a", int64_t(4), ""}));
// Padding is counted in utf-8 characters, not bytes.
CPPUNIT_ASSERT_EQUAL(std::string("00日"), call_string("string.lpad", {"", int64_t(3), "0"}));
}
void
TestCommandString::test_strip() {
CPPUNIT_ASSERT_EQUAL(std::string("a b"), call_string("string.strip", {" a b "}));
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"\t\n a \r\n"}));
CPPUNIT_ASSERT_EQUAL(std::string("a "), call_string("string.lstrip", {" a "}));
CPPUNIT_ASSERT_EQUAL(std::string(" a"), call_string("string.rstrip", {" a "}));
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"xxaxx", "x"}));
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"/x/a/x/", "/", "x"}));
CPPUNIT_ASSERT_EQUAL(std::string(""), call_string("string.strip", {"aaa", "a"}));
CPPUNIT_ASSERT_EQUAL(std::string(" a "), call_string("string.strip", {" a ", ""}));
// The strippable argument is a set of utf-8 characters.
CPPUNIT_ASSERT_EQUAL(std::string("a"), call_string("string.strip", {"日a日", ""}));
}
void
TestCommandString::test_map() {
CPPUNIT_ASSERT_EQUAL(std::string("b"), call_string("string.map", {"a", args({"a", "b"})}));
CPPUNIT_ASSERT_EQUAL(std::string("c"), call_string("string.map", {"c", args({"a", "b"})}));
// Only whole-string matches are replaced.
CPPUNIT_ASSERT_EQUAL(std::string("ab"), call_string("string.map", {"ab", args({"a", "b"})}));
// The first matching pair wins.
CPPUNIT_ASSERT_EQUAL(std::string("y"), call_string("string.map", {"x", args({"a", "b"}), args({"x", "y"}), args({"x", "z"})}));
}
void
TestCommandString::test_replace() {
CPPUNIT_ASSERT_EQUAL(std::string("a+b+c"), call_string("string.replace", {"a-b-c", args({"-", "+"})}));
CPPUNIT_ASSERT_EQUAL(std::string("abc"), call_string("string.replace", {"a-b-c", args({"-", ""})}));
CPPUNIT_ASSERT_EQUAL(std::string("a-b-c"), call_string("string.replace", {"a-b-c", args({"x", "y"})}));
// Pairs are applied in order, left to right.
CPPUNIT_ASSERT_EQUAL(std::string("xby"), call_string("string.replace", {"abc", args({"a", "x"}), args({"c", "y"})}));
// A replacement that contains the replaced text terminates.
CPPUNIT_ASSERT_EQUAL(std::string("aaaa"), call_string("string.replace", {"aa", args({"a", "aa"})}));
}
void
TestCommandString::test_invalid_arguments() {
CPPUNIT_ASSERT_THROW(rpc::commands.call_command("string.length", torrent::Object()), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_string("string.length", {"a", "b"}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_value("string.equals", {"a"}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_value("string.contains", {"a"}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_list("string.split", {"a"}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_string("string.lpad", {"a"}), torrent::input_error);
// The character count of string.substr cannot be negative.
CPPUNIT_ASSERT_THROW(call_string("string.substr", {"abc", int64_t(0), int64_t(-1)}), torrent::input_error);
// Both string.map and string.replace require {old, new} pairs.
CPPUNIT_ASSERT_THROW(call_string("string.map", {"a", "b"}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_string("string.replace", {"a", args({"a", "b", "c"})}), torrent::input_error);
CPPUNIT_ASSERT_THROW(call_string("string.replace", {"a", args({"", "b"})}), torrent::input_error);
}
void
TestCommandString::test_config_syntax() {
CPPUNIT_ASSERT_EQUAL(int64_t(3), parse("string.length=abc").as_value());
CPPUNIT_ASSERT_EQUAL(int64_t(1), parse("string.contains=retracker.local,retracker").as_value());
CPPUNIT_ASSERT_EQUAL(int64_t(1), parse("string.starts_with=udp://tracker.example.com,http://,udp://").as_value());
CPPUNIT_ASSERT_EQUAL(std::string("cde"), parse("string.substr=abcdef,2,3").as_string());
CPPUNIT_ASSERT_EQUAL(std::string("padded"), parse("string.strip=\" padded \"").as_string());
// The {old, new} pairs are written as a block in the configuration file.
CPPUNIT_ASSERT_EQUAL(std::string("a+b+c"), parse("string.replace=a-b-c,{-,+}").as_string());
CPPUNIT_ASSERT_EQUAL(std::string("y"), parse("string.map=x,{a,b},{x,y}").as_string());
// Commands nest, so a split can be joined back together.
CPPUNIT_ASSERT_EQUAL(std::string("a-b-c"), parse("string.join=-,(string.split,a.b.c,.)").as_string());
}
-41
View File
@@ -1,41 +0,0 @@
#include "test/helpers/test_fixture.h"
class TestCommandString : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandString);
CPPUNIT_TEST(test_length);
CPPUNIT_TEST(test_equals);
CPPUNIT_TEST(test_starts_with);
CPPUNIT_TEST(test_ends_with);
CPPUNIT_TEST(test_contains);
CPPUNIT_TEST(test_substr);
CPPUNIT_TEST(test_split);
CPPUNIT_TEST(test_join);
CPPUNIT_TEST(test_pad);
CPPUNIT_TEST(test_strip);
CPPUNIT_TEST(test_map);
CPPUNIT_TEST(test_replace);
CPPUNIT_TEST(test_invalid_arguments);
CPPUNIT_TEST(test_config_syntax);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_length();
void test_equals();
void test_starts_with();
void test_ends_with();
void test_contains();
void test_substr();
void test_split();
void test_join();
void test_pad();
void test_strip();
void test_map();
void test_replace();
void test_invalid_arguments();
void test_config_syntax();
};