Compare commits

...

27 Commits

Author SHA1 Message Date
rakshasa 5aa45bbaf4 Removed old LOG_TORRENT_* categories and replaced with LOG_TORRENT_{DEBUG,EVENTS}. 2026-10-08 15:31:52 +02:00
Scott Pope 728790a2b4 Parse multicall commands once per call
Keep the change to d, f, p and t multicalls. Parse each column lazily on first use and evaluate it through rpc::call_object, preserving per-target argument expansion.

Compared unpatched and patched builds on macOS: output was byte-identical across the test harness, including an erase-mid-call check.
2026-10-06 11:19:48 +02:00
Nicolas PARLANT e652a3cbab add missing header
cstdint for uint32_t
2026-10-05 16:59:00 +02:00
rakshasa 15ccae27de Tagged release 0.16.25. 2026-10-05 08:42:50 +02:00
Silas Mariusz 4c0535b1fa Align the XML-RPC command stack so torrent::Object is not constructed on a 4-byte boundary. 2026-10-04 13:37:01 +02:00
Jari Sundell d59765fda4 Added legacy UTF-8 torrent name and path handling. 2026-10-04 20:36:29 +09:00
Silas Mariusz 66c49d24e5 Use heap allocated buffer for rpc::ExecFile::execute_object() and increase size to 128Kb. 2026-10-04 10:18:58 +02:00
rakshasa 3af0410a6e Restrict parse value to strtoll with restrictions on input. 2026-10-01 10:32:43 +02:00
rakshasa 3c94b03323 Restrict parse value to strtoll with restrictions on input. 2026-10-01 10:32:43 +02:00
rakshasa c5d54fbb97 Restrict parse value to strtoll with restrictions on input. 2026-10-01 10:32:43 +02:00
Silas Mariusz 44d51171e1 Mark the system.file.allocate getter untrusted-safe, because d.open reads it. 2026-09-30 10:55:48 +02:00
noctuum dc916278c2 Do not switch an active download to initial_seed
`confirm_finished` did, and the `input_error` reached `main`.
2026-09-29 10:51:58 +02:00
noctuum 366b936ded Check m_entry against size() in set_entry 2026-09-29 10:18:35 +02:00
noctuum 8bcc4c6e92 Read the unpacked object in the raw string case
A list holding one raw string reached this case and threw on `src`.
2026-09-29 10:00:30 +02:00
noctuum 5421a35349 Remove code that has no remaining user
Three declarations had no definition and the rest had no user left.
2026-09-28 18:40:17 +02:00
noctuum d5ce0984fc Reset the freed pointers in XmlRpc::cleanup
`is_valid()` tests `m_env`, so it stayed true after `cleanup()`.
2026-09-28 18:01:05 +02:00
noctuum a89ce3cd7b Check first against last in parse_object
A command ending in `=` reaches it with an empty range.
2026-09-28 17:34:06 +02:00
noctuum d98a441dc4 Sort the transfer chunks by index
The default comparison ordered the pointers, not the chunk indexes.
2026-09-28 17:11:08 +02:00
noctuum fc6e57c3fa Check pthread_sigmask by its return value
`pthread_sigmask` returns the error number and does not set `errno`.
2026-09-28 16:46:02 +02:00
noctuum 8e23ba8b17 Reject an empty argument list in execute_lua
`lua.execute` with no arguments dereferenced `args.begin()`.
2026-09-28 11:36:53 +02:00
noctuum e656801cfa Limit tab completion to text before the cursor
`substr` takes a count, so the text after the cursor joined the prefix.
2026-09-28 11:00:25 +02:00
noctuum 9f6fd7994e Guard control in the main catch handlers
A start without HOME throws before control is constructed.
2026-09-28 10:46:13 +02:00
noctuum bb77aa644c Stop at max_active in scheduler.simple.removed
The limit was checked once, then every download was resumed.
2026-09-28 10:28:46 +02:00
xirvik 83a5e7687b Sync the session directory after committing the renames
Syncing the files alone does not make the new names survive a crash.
2026-09-25 09:31:08 +02:00
Jari Sundell 910b9b35fc Reorder mutex and atomic variable declarations 2026-09-25 08:52:56 +02:00
xirvik e3051df78c Give SessionManager::m_active its own cache line
Prevents false sharing with the adjacent m_mutex under concurrent access.
2026-09-25 08:52:56 +02:00
xirvik 21287a1355 Make SessionManager::m_active atomic
It is written under m_mutex but read under m_pending_builds_mutex in one place.
2026-09-25 08:52:56 +02:00
53 changed files with 969 additions and 556 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
m4_pattern_allow([PKG_CHECK_EXISTS]) m4_pattern_allow([PKG_CHECK_EXISTS])
AC_INIT([rtorrent],[0.16.24],[sundell.software@gmail.com]) AC_INIT([rtorrent],[0.16.25],[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], [28], [api version]) AC_DEFINE([API_VERSION], [29], [api version])
RAK_CHECK_CFLAGS RAK_CHECK_CFLAGS
RAK_CHECK_CXXFLAGS RAK_CHECK_CXXFLAGS
@@ -49,7 +49,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.24]) PKG_CHECK_MODULES([DEPENDENCIES], [libtorrent >= 0.16.25])
AC_LANG_PUSH(C++) AC_LANG_PUSH(C++)
TORRENT_WITH_XMLRPC_C TORRENT_WITH_XMLRPC_C
-2
View File
@@ -17,7 +17,6 @@ libsub_root_a_SOURCES = \
core/http_queue.h \ core/http_queue.h \
core/manager.cc \ core/manager.cc \
core/manager.h \ core/manager.h \
core/range_map.h \
core/view.cc \ core/view.cc \
core/view.h \ core/view.h \
core/view_manager.cc \ core/view_manager.cc \
@@ -171,7 +170,6 @@ libsub_root_a_SOURCES = \
utils/functional.h \ utils/functional.h \
utils/gzip.cc \ utils/gzip.cc \
utils/gzip.h \ utils/gzip.h \
utils/list_focus.h \
utils/lockfile.cc \ utils/lockfile.cc \
utils/lockfile.h \ utils/lockfile.h \
utils/waitpid_queue.cc \ utils/waitpid_queue.cc \
+31 -17
View File
@@ -107,7 +107,7 @@ apply_d_change_link(core::Download* download, const torrent::Object::list_type&
switch (changeType) { switch (changeType) {
case 0: case 0:
if (symlink(target.c_str(), link.c_str()) == -1) if (symlink(target.c_str(), link.c_str()) == -1)
lt_log_print(torrent::LOG_TORRENT_WARN, "create_link failed: %s", std::strerror(errno)); lt_log_print(torrent::LOG_TORRENT_DEBUG, "create_link failed: %s", std::strerror(errno));
break; break;
@@ -117,7 +117,7 @@ apply_d_change_link(core::Download* download, const torrent::Object::list_type&
errno = 0; errno = 0;
if (!fileStat.update_link(link) || !fileStat.is_link() || unlink(link.c_str()) == -1) if (!fileStat.update_link(link) || !fileStat.is_link() || unlink(link.c_str()) == -1)
lt_log_print(torrent::LOG_TORRENT_WARN, "delete_link failed: %s", std::strerror(errno)); lt_log_print(torrent::LOG_TORRENT_DEBUG, "delete_link failed: %s", std::strerror(errno));
break; break;
} }
@@ -135,7 +135,7 @@ apply_d_delete_tied(core::Download* download) {
if (tie.empty()) if (tie.empty())
return torrent::Object(); return torrent::Object();
if (::unlink(expand_path(tie).c_str()) == -1) if (unlink(expand_path(tie).c_str()) == -1)
control->core()->push_log_std("Could not unlink tied file: " + std::string(std::strerror(errno))); control->core()->push_log_std("Could not unlink tied file: " + std::string(std::strerror(errno)));
rpc::call_command("d.tied_to_file.set", std::string(), rpc::make_target(download)); rpc::call_command("d.tied_to_file.set", std::string(), rpc::make_target(download));
@@ -293,7 +293,7 @@ apply_d_add_peer(core::Download* download, const std::string& arg) {
// Currently discarding SOCK_STREAM. // Currently discarding SOCK_STREAM.
torrent::this_thread::resolver()->resolve_preferred(callback_id, host, AF_UNSPEC, AF_INET, [download, port](torrent::c_sa_shared_ptr sa, int err) { torrent::this_thread::resolver()->resolve_preferred(callback_id, host, AF_UNSPEC, AF_INET, [download, port](torrent::c_sa_shared_ptr sa, int err) {
if (sa == nullptr) { if (sa == nullptr) {
lt_log_print(torrent::LOG_TORRENT_WARN, "could not resolve hostname for added peer: %s", gai_strerror(err)); lt_log_print(torrent::LOG_TORRENT_DEBUG, "could not resolve hostname for added peer: %s", gai_strerror(err));
return; return;
} }
@@ -334,6 +334,11 @@ f_multicall(core::Download* download, const torrent::Object::list_type& args) {
bool use_regex = true; bool use_regex = true;
rpc::preparsed_commands commands([&args](auto& cmds) {
for (auto cItr = ++args.begin(); cItr != args.end(); ++cItr)
cmds.push_back(rpc::parse_command_object(cItr->as_string()));
});
if (args.front().is_list()) if (args.front().is_list())
for (const auto& o : args.front().as_list()) for (const auto& o : args.front().as_list())
regex_list.push_back(o.as_string_c()); regex_list.push_back(o.as_string_c());
@@ -349,10 +354,10 @@ f_multicall(core::Download* download, const torrent::Object::list_type& args) {
torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list(); torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list();
for (torrent::Object::list_const_iterator cItr = ++args.begin(); cItr != args.end(); cItr++) { // Defer parsing until a file actually matches the multicall selection.
const std::string& cmd = cItr->as_string(); commands.prepare_if_needed();
row.push_back(rpc::parse_command(rpc::make_target(file.get()), cmd.c_str(), cmd.c_str() + cmd.size()).first); for (auto& itr : commands)
} row.push_back(rpc::call_object(itr, rpc::make_target(file.get())));
} }
return resultRaw; return resultRaw;
@@ -372,6 +377,11 @@ t_multicall(core::Download* download, const torrent::Object::list_type& args) {
auto result_raw = torrent::Object::create_list(); auto result_raw = torrent::Object::create_list();
auto& result = result_raw.as_list(); auto& result = result_raw.as_list();
rpc::preparsed_commands commands([&args](auto& cmds) {
for (auto cItr = ++args.begin(); cItr != args.end(); ++cItr)
cmds.push_back(rpc::parse_command_object(cItr->as_string()));
});
for (uint32_t idx = 0, last = download->tracker_list_size(); idx < last; idx++) { for (uint32_t idx = 0, last = download->tracker_list_size(); idx < last; idx++) {
auto& row = result.insert(result.end(), torrent::Object::create_list())->as_list(); auto& row = result.insert(result.end(), torrent::Object::create_list())->as_list();
auto tracker = download->tracker_controller().at(idx); auto tracker = download->tracker_controller().at(idx);
@@ -379,11 +389,10 @@ t_multicall(core::Download* download, const torrent::Object::list_type& args) {
if (!tracker.is_valid()) if (!tracker.is_valid())
continue; continue;
for (auto cItr = ++args.begin(); cItr != args.end(); cItr++) { // Do not parse columns when there are no valid tracker targets.
auto& cmd = cItr->as_string(); commands.prepare_if_needed();
for (auto& itr : commands)
row.push_back(rpc::parse_command(rpc::make_target(&tracker), cmd.c_str(), cmd.c_str() + cmd.size()).first); row.push_back(rpc::call_object(itr, rpc::make_target(&tracker)));
}
} }
return result_raw; return result_raw;
@@ -405,13 +414,18 @@ p_multicall(core::Download* download, const torrent::Object::list_type& args) {
auto* connection_list = download->connection_list(); auto* connection_list = download->connection_list();
const auto change_counter = connection_list->change_counter(); const auto change_counter = connection_list->change_counter();
rpc::preparsed_commands commands([&args](auto& cmds) {
for (auto cItr = ++args.begin(); cItr != args.end(); ++cItr)
cmds.push_back(rpc::parse_command_object(cItr->as_string()));
});
for (const auto& connection : *connection_list) { for (const auto& connection : *connection_list) {
torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list(); torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list();
for (auto cItr = ++args.begin(); cItr != args.end(); cItr++) { // Prepare only after a peer exists, preserving empty-list laziness.
const std::string& cmd = cItr->as_string(); commands.prepare_if_needed();
for (auto& itr : commands) {
row.push_back(rpc::parse_command(rpc::make_target(connection), cmd.c_str(), cmd.c_str() + cmd.size()).first); row.push_back(rpc::call_object(itr, rpc::make_target(connection)));
// Erasing a peer frees it and swaps the last element into its place, so // Erasing a peer frees it and swaps the last element into its place, so
// neither this peer nor the iteration survives a change to the list. // neither this peer nor the iteration survives a change to the list.
+13 -7
View File
@@ -169,7 +169,7 @@ void apply_try_import(const std::string& path) { if (!rpc::parse_command_file(pa
torrent::Object torrent::Object
apply_close_low_diskspace(int64_t arg, uint32_t skip_priority) { apply_close_low_diskspace(int64_t arg, uint32_t skip_priority) {
bool closed = false; int closed{};
torrent::FileList::cache_list cache; torrent::FileList::cache_list cache;
@@ -186,11 +186,11 @@ apply_close_low_diskspace(int64_t arg, uint32_t skip_priority) {
download->set_hash_failed(true); download->set_hash_failed(true);
download->set_message(std::string("Low diskspace.")); download->set_message(std::string("Low diskspace."));
closed = true; closed++;
} }
if (closed) if (closed != 0)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Closed torrents due to low diskspace."); lt_log_print(torrent::LOG_TORRENT_EVENTS, "closed torrents due to low diskspace : count:%i", closed);
return torrent::Object(); return torrent::Object();
} }
@@ -247,20 +247,26 @@ d_multicall(const torrent::Object::list_type& args) {
torrent::Object resultRaw = torrent::Object::create_list(); torrent::Object resultRaw = torrent::Object::create_list();
torrent::Object::list_type& result = resultRaw.as_list(); torrent::Object::list_type& result = resultRaw.as_list();
rpc::preparsed_commands commands([&args](auto& cmds) {
for (auto cItr = ++args.begin(); cItr != args.end(); ++cItr)
cmds.push_back(rpc::parse_command_object(cItr->as_string()));
});
for (const auto& download : dlist) { for (const auto& download : dlist) {
if (download.use_count() == 1) if (download.use_count() == 1)
continue; continue;
torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list(); torrent::Object::list_type& row = result.insert(result.end(), torrent::Object::create_list())->as_list();
for (torrent::Object::list_const_iterator cItr = ++args.begin(); cItr != args.end(); cItr++) { // Skip parsing if there are no usable download targets in the view.
commands.prepare_if_needed();
for (auto& itr : commands) {
// A command may erase this download, which destroys the torrent object it // A command may erase this download, which destroys the torrent object it
// wraps; the list dropping its reference is what tells us. // wraps; the list dropping its reference is what tells us.
if (download.use_count() == 1) if (download.use_count() == 1)
break; break;
auto& cmd = cItr->as_string(); row.push_back(rpc::call_object(itr, rpc::make_target(download)));
row.push_back(rpc::parse_command(rpc::make_target(download), cmd.c_str(), cmd.c_str() + cmd.size()).first);
} }
} }
+5 -4
View File
@@ -1,5 +1,6 @@
#include "config.h" #include "config.h"
#include <limits>
#include <memory> #include <memory>
#include <torrent/download/resource_manager.h> #include <torrent/download/resource_manager.h>
@@ -19,9 +20,6 @@
// For cg_d_group. // For cg_d_group.
#include "core/download.h" #include "core/download.h"
#define LT_LOG_SUBSYSTEM(log_fmt, ...) \
lt_log_print_subsystem(torrent::LOG_TORRENT_INFO, "choke_queue", log_fmt, __VA_ARGS__);
// A hack to allow testing of the new choke_group API without the // A hack to allow testing of the new choke_group API without the
// working parts present. // working parts present.
#define USE_CHOKE_GROUP 0 #define USE_CHOKE_GROUP 0
@@ -206,7 +204,7 @@ apply_cg_index_of(const std::string& arg) {
torrent::Object 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_print(torrent::LOG_TORRENT_DEBUG, "choke_queue : applying update balance : is_up:%i", (int)is_up);
for (const auto& itr : cg_list_hack) { for (const auto& itr : cg_list_hack) {
if (is_up) if (is_up)
@@ -232,6 +230,9 @@ apply_cg_max_set(const torrent::Object::list_type& args, bool is_up) {
int64_t second_arg = 0; int64_t second_arg = 0;
rpc::parse_whole_value(args.back().as_string().c_str(), &second_arg); rpc::parse_whole_value(args.back().as_string().c_str(), &second_arg);
if (second_arg < -1 || second_arg > std::numeric_limits<uint32_t>::max())
throw torrent::input_error("Max unchoked must be between -1 and 4294967295.");
if (is_up) if (is_up)
cg_get_group(args.front())->up_queue()->set_max_unchoked(second_arg); cg_get_group(args.front())->up_queue()->set_max_unchoked(second_arg);
else else
+13
View File
@@ -1,12 +1,25 @@
#ifndef RTORRENT_UTILS_COMMAND_HELPERS_H #ifndef RTORRENT_UTILS_COMMAND_HELPERS_H
#define RTORRENT_UTILS_COMMAND_HELPERS_H #define RTORRENT_UTILS_COMMAND_HELPERS_H
#include <cstdint>
#include <limits>
#include <string>
#include <torrent/exceptions.h>
#include "rpc/command.h" #include "rpc/command.h"
#include "rpc/parse_commands.h" #include "rpc/parse_commands.h"
#include "rpc/object_storage.h" #include "rpc/object_storage.h"
void initialize_commands(); void initialize_commands();
inline uint16_t
checked_port_value(int64_t value, const char* label) {
if (value < 0 || value > std::numeric_limits<uint16_t>::max())
throw torrent::input_error(std::string("Invalid ") + label + " port number.");
return static_cast<uint16_t>(value);
}
// //
// Aliases with CMD_* for the below // Aliases with CMD_* for the below
// //
-48
View File
@@ -77,54 +77,6 @@ group_insert(const torrent::Object::list_type& args) {
return name; return name;
} }
static const int file_print_use_space = 0x1;
static const int file_print_delim_space = 0x2;
void
file_print_list(torrent::Object::list_const_iterator first, torrent::Object::list_const_iterator last, FILE* output, int flags) {
while (first != last) {
switch (first->type()) {
case torrent::Object::TYPE_STRING:
fprintf(output, (const char*)" %s" + !(flags & file_print_use_space), first->as_string().c_str());
break;
case torrent::Object::TYPE_VALUE:
fprintf(output, (const char*)" %" PRIi64 + !(flags & file_print_use_space), first->as_value());
break;
case torrent::Object::TYPE_LIST:
file_print_list(first->as_list().begin(), first->as_list().end(), output, 0);
break;
case torrent::Object::TYPE_NONE:
break;
default:
throw torrent::input_error("Invalid type.");
}
flags |= (flags & file_print_delim_space) >> 1;
first++;
}
}
torrent::Object
cmd_file_append(const torrent::Object::list_type& args) {
if (args.empty())
throw torrent::input_error("Invalid number of arguments.");
FILE* output = fopen(args.front().as_string().c_str(), "a");
if (output == nullptr)
throw torrent::input_error("Could not append to file '" + args.front().as_string() + "': " + std::strerror(errno));
try {
file_print_list(++args.begin(), args.end(), output, file_print_delim_space);
fprintf(output, "\n");
} catch (...) {
fclose(output);
throw;
}
fclose(output);
return torrent::Object();
}
void void
initialize_command_local() { initialize_command_local() {
core::DownloadList* dList = control->core()->download_list(); core::DownloadList* dList = control->core()->download_list();
+4 -12
View File
@@ -54,14 +54,6 @@ set_listen_port_range(const std::string& arg) {
torrent::runtime::client_config()->set_listen_port_range(port_first, port_last); torrent::runtime::client_config()->set_listen_port_range(port_first, port_last);
} }
uint16_t
checked_local_port_value(int64_t value, const char* label) {
if (value < 0 || value > 65535)
throw torrent::input_error(std::string("Invalid ") + label + " port number.");
return static_cast<uint16_t>(value);
}
torrent::Object torrent::Object
get_encryption() { get_encryption() {
auto encryption_modes = torrent::runtime::network_config()->encryption_modes(); auto encryption_modes = torrent::runtime::network_config()->encryption_modes();
@@ -325,7 +317,7 @@ initialize_command_network() {
auto nw_config = torrent::runtime::network_config(); auto nw_config = torrent::runtime::network_config();
CMD_ANY ("network.listen.port", [](auto, auto) { return torrent::runtime::network_manager()->listen_port(); }); CMD_ANY ("network.listen.port", [](auto, auto) { return torrent::runtime::network_manager()->listen_port(); });
CMD_ANY_VALUE_V ("network.listen.port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_listen_port(value); }); CMD_ANY_VALUE_V ("network.listen.port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_listen_port(checked_port_value(value, "listen")); });
CMD_ANY ("network.listen.port.random", [](auto, auto) { return torrent::runtime::client_config()->listen_port_random(); }); CMD_ANY ("network.listen.port.random", [](auto, auto) { return torrent::runtime::client_config()->listen_port_random(); });
CMD_ANY_VALUE_V ("network.listen.port.random.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_listen_port_random(value); }); CMD_ANY_VALUE_V ("network.listen.port.random.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_listen_port_random(value); });
CMD_ANY ("network.listen.port.range", [](auto, auto) { return listen_port_range(); }); CMD_ANY ("network.listen.port.range", [](auto, auto) { return listen_port_range(); });
@@ -394,11 +386,11 @@ initialize_command_network() {
CMD_ANY_STRING_V("network.local_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet6_address(str); }); CMD_ANY_STRING_V("network.local_address.ipv6.set", [nw_config](auto, auto& str) { return nw_config->set_local_inet6_address(str); });
CMD_ANY ("network.local_port", [nw_config](auto, auto) { return nw_config->local_port_best_match(); }); CMD_ANY ("network.local_port", [nw_config](auto, auto) { return nw_config->local_port_best_match(); });
CMD_ANY_VALUE_V ("network.local_port.set", [nw_config](auto, auto& value) { return nw_config->set_local_port(checked_local_port_value(value, "local")); }); CMD_ANY_VALUE_V ("network.local_port.set", [nw_config](auto, auto& value) { return nw_config->set_local_port(checked_port_value(value, "local")); });
CMD_ANY ("network.local_port.ipv4", [nw_config](auto, auto) { return nw_config->local_inet_port(); }); CMD_ANY ("network.local_port.ipv4", [nw_config](auto, auto) { return nw_config->local_inet_port(); });
CMD_ANY_VALUE_V ("network.local_port.ipv4.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet_port(checked_local_port_value(value, "local ipv4")); }); CMD_ANY_VALUE_V ("network.local_port.ipv4.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet_port(checked_port_value(value, "local ipv4")); });
CMD_ANY ("network.local_port.ipv6", [nw_config](auto, auto) { return nw_config->local_inet6_port(); }); CMD_ANY ("network.local_port.ipv6", [nw_config](auto, auto) { return nw_config->local_inet6_port(); });
CMD_ANY_VALUE_V ("network.local_port.ipv6.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet6_port(checked_local_port_value(value, "local ipv6")); }); CMD_ANY_VALUE_V ("network.local_port.ipv6.set", [nw_config](auto, auto& value) { return nw_config->set_local_inet6_port(checked_port_value(value, "local ipv6")); });
CMD_ANY ("network.proxy.global", [](auto, auto) { return torrent::runtime::proxy_manager()->proxy_url(); }); CMD_ANY ("network.proxy.global", [](auto, auto) { return torrent::runtime::proxy_manager()->proxy_url(); });
CMD_ANY_STRING_V("network.proxy.global.set", [](auto, auto& str) { return torrent::runtime::proxy_manager()->set_proxy_url(str); }); CMD_ANY_STRING_V("network.proxy.global.set", [](auto, auto& str) { return torrent::runtime::proxy_manager()->set_proxy_url(str); });
+5 -1
View File
@@ -28,9 +28,10 @@ cmd_scheduler_simple_removed(core::Download* download) {
control->core()->download_list()->pause(download); control->core()->download_list()->pause(download);
core::View* viewActive = *control->view_manager()->find("active"); core::View* viewActive = *control->view_manager()->find("active");
unsigned int numActive = viewActive->size_visible();
int64_t maxActive = rpc::call_command("scheduler.max_active", torrent::Object()).as_value(); int64_t maxActive = rpc::call_command("scheduler.max_active", torrent::Object()).as_value();
if ((int64_t)viewActive->size_visible() >= maxActive) if ((int64_t)numActive >= maxActive)
return torrent::Object(); return torrent::Object();
// The 'started' view contains all the views we may choose amongst. // The 'started' view contains all the views we may choose amongst.
@@ -41,6 +42,9 @@ cmd_scheduler_simple_removed(core::Download* download) {
continue; continue;
control->core()->download_list()->resume(*itr); control->core()->download_list()->resume(*itr);
if ((int64_t)++numActive >= maxActive)
break;
} }
return torrent::Object(); return torrent::Object();
+6
View File
@@ -88,6 +88,8 @@ initialize_command_system() {
CMD_ANY ("system.file_name.replace_slash", [](auto, auto) { return torrent::runtime::client_config()->file_name_replace_slash(); }); CMD_ANY ("system.file_name.replace_slash", [](auto, auto) { return torrent::runtime::client_config()->file_name_replace_slash(); });
CMD_ANY_STRING_V("system.file_name.replace_slash.set", [](auto, auto& str) { return torrent::runtime::client_config()->set_file_name_replace_slash(str); }); CMD_ANY_STRING_V("system.file_name.replace_slash.set", [](auto, auto& str) { return torrent::runtime::client_config()->set_file_name_replace_slash(str); });
CMD_ANY ("system.file_name.allow_legacy_utf8", [](auto, auto) { return torrent::runtime::client_config()->file_name_allow_legacy_utf8(); });
CMD_ANY_VALUE_V ("system.file_name.allow_legacy_utf8.set", [](auto, auto& value) { return torrent::runtime::client_config()->set_file_name_allow_legacy_utf8(value); });
CMD_ANY ("system.file_status_cache.size", [](auto, auto) { return control->core()->file_status_cache()->size(); }); CMD_ANY ("system.file_status_cache.size", [](auto, auto) { return control->core()->file_status_cache()->size(); });
CMD_ANY_V ("system.file_status_cache.prune", [](auto, auto) { return control->core()->file_status_cache()->prune(); }); CMD_ANY_V ("system.file_status_cache.prune", [](auto, auto) { return control->core()->file_status_cache()->prune(); });
@@ -167,9 +169,13 @@ initialize_command_system() {
rpc::rpc.mark_safe("system.time"); rpc::rpc.mark_safe("system.time");
rpc::rpc.mark_safe("system.time_seconds"); rpc::rpc.mark_safe("system.time_seconds");
rpc::rpc.mark_safe("system.time_usec"); rpc::rpc.mark_safe("system.time_usec");
rpc::rpc.mark_safe("system.torrent_name.use_sanitized");
rpc::rpc.mark_safe("system.file.allocate");
rpc::rpc.mark_safe("system.file.max_size"); rpc::rpc.mark_safe("system.file.max_size");
rpc::rpc.mark_safe("system.file.split_size"); rpc::rpc.mark_safe("system.file.split_size");
rpc::rpc.mark_safe("system.file.split_suffix"); rpc::rpc.mark_safe("system.file.split_suffix");
rpc::rpc.mark_safe("system.file_name.replace_slash");
rpc::rpc.mark_safe("system.file_name.allow_legacy_utf8");
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");
+1 -1
View File
@@ -142,7 +142,7 @@ initialize_command_tracker() {
lt_log_print(torrent::LOG_DHT_ERROR, "dht.port.set is no longer supported, use dht.override_port.set", 0); lt_log_print(torrent::LOG_DHT_ERROR, "dht.port.set is no longer supported, use dht.override_port.set", 0);
}); });
CMD2_ANY ("dht.override_port", [](auto, auto) { return torrent::runtime::network_config()->override_dht_port(); }); CMD2_ANY ("dht.override_port", [](auto, auto) { return torrent::runtime::network_config()->override_dht_port(); });
CMD2_ANY_VALUE_V ("dht.override_port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_dht_port(value); }); CMD2_ANY_VALUE_V ("dht.override_port.set", [](auto, auto& value) { return torrent::runtime::network_manager()->set_dht_port(checked_port_value(value, "DHT override")); });
CMD2_ANY_STRING ("dht.add_node", [](auto, auto& str) { return apply_dht_add_node(str); }); CMD2_ANY_STRING ("dht.add_node", [](auto, auto& str) { return apply_dht_add_node(str); });
CMD2_ANY ("dht.statistics", [](auto, auto) { return control->dht_manager()->dht_statistics(); }); CMD2_ANY ("dht.statistics", [](auto, auto) { return control->dht_manager()->dht_statistics(); });
+41 -27
View File
@@ -29,6 +29,11 @@
#include "session/session_manager.h" #include "session/session_manager.h"
#include "ui/root.h" #include "ui/root.h"
#define LT_LOG_DOWNLOAD_DEBUG(log_fmt, ...) \
lt_log_print_hash_only(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "download_list : " log_fmt, __VA_ARGS__);
#define LT_LOG_DOWNLOAD_EVENTS(log_fmt, ...) \
lt_log_print_info(torrent::LOG_TORRENT_EVENTS, download->info(), "download_list", log_fmt, __VA_ARGS__);
#define DL_TRIGGER_EVENT(download, event_name) \ #define DL_TRIGGER_EVENT(download, event_name) \
rpc::commands.call_catch(event_name, rpc::make_target(download), torrent::Object(), "Event '" event_name "' failed: "); rpc::commands.call_catch(event_name, rpc::make_target(download), torrent::Object(), "Event '" event_name "' failed: ");
@@ -82,7 +87,7 @@ DownloadList::find(const torrent::HashString& hash) {
DownloadList::iterator DownloadList::iterator
DownloadList::find_hex(const char* hash) { DownloadList::find_hex(const char* hash) {
if (strlen(hash) < 40) if (strlen(hash) != 40)
return end(); return end();
torrent::HashString key; torrent::HashString key;
@@ -109,7 +114,7 @@ DownloadList::create(torrent::Object* obj, uint32_t tracker_key, bool printLog)
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
if (printLog) if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download: %s", e.what()); lt_log_print(torrent::LOG_TORRENT_EVENTS, "download_list : could not create download : %s", e.what());
delete obj; delete obj;
@@ -135,7 +140,7 @@ DownloadList::create(std::istream* str, uint32_t tracker_key, bool printLog) {
delete object; delete object;
if (printLog) if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, the input is not a valid torrent."); lt_log_print(torrent::LOG_TORRENT_EVENTS, "download_list : could not create download : invalid or incomplete bencode data");
return NULL; return NULL;
} }
@@ -146,7 +151,7 @@ DownloadList::create(std::istream* str, uint32_t tracker_key, bool printLog) {
delete object; delete object;
if (printLog) if (printLog)
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download: %s", e.what()); lt_log_print(torrent::LOG_TORRENT_EVENTS, "download_list : could not create download : %s", e.what());
return NULL; return NULL;
} }
@@ -160,7 +165,7 @@ DownloadList::iterator
DownloadList::insert(Download* download) { DownloadList::insert(Download* download) {
iterator itr = base_type::insert(end(), std::shared_ptr<Download>(download)); iterator itr = base_type::insert(end(), std::shared_ptr<Download>(download));
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Inserting download."); LT_LOG_DOWNLOAD_DEBUG("inserting download", 0);
try { try {
(*itr)->data()->slot_initial_hash() = std::bind(&DownloadList::hash_done, this, download); (*itr)->data()->slot_initial_hash() = std::bind(&DownloadList::hash_done, this, download);
@@ -200,7 +205,7 @@ DownloadList::erase(iterator itr) {
(*itr)->set_erasing(); (*itr)->set_erasing();
lt_log_print_info(torrent::LOG_TORRENT_INFO, (*itr)->info(), "download_list", "Erasing download."); lt_log_print_hash_only(torrent::LOG_TORRENT_DEBUG, (*itr)->info()->hash(), "download_list : removing download");
// Makes sure close doesn't restart hashing of this download. // Makes sure close doesn't restart hashing of this download.
(*itr)->set_hash_failed(true); (*itr)->set_hash_failed(true);
@@ -228,7 +233,7 @@ DownloadList::open(Download* download) {
return true; return true;
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not open download: %s", e.what()); lt_log_print(torrent::LOG_TORRENT_EVENTS, "download_list : could not open download : %s", e.what());
return false; return false;
} }
} }
@@ -237,7 +242,7 @@ void
DownloadList::open_throw(Download* download) { DownloadList::open_throw(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Opening download."); lt_log_print_hash_only(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "download_list : opening download");
if (download->download()->info()->is_open()) if (download->download()->info()->is_open())
return; return;
@@ -248,6 +253,7 @@ DownloadList::open_throw(Download* download) {
openFlags |= torrent::Download::open_enable_fallocate; openFlags |= torrent::Download::open_enable_fallocate;
download->download()->open(openFlags); download->download()->open(openFlags);
DL_TRIGGER_EVENT(download, "event.download.opened"); DL_TRIGGER_EVENT(download, "event.download.opened");
} }
@@ -257,7 +263,7 @@ DownloadList::close(Download* download) {
close_throw(download); close_throw(download);
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not close download: %s", e.what()); lt_log_print(torrent::LOG_TORRENT_EVENTS, "download_list : could not close download : %s", e.what());
} }
} }
@@ -265,7 +271,7 @@ DownloadList::close(Download* download) {
// need the files closed and will keep using the download. // need the files closed and will keep using the download.
void void
DownloadList::close_files(Download* download) { DownloadList::close_files(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download files."); lt_log_print_hash_only(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "download_list : closing download files");
if (download->download()->info()->is_active()) { if (download->download()->info()->is_active()) {
download->download()->stop(torrent::Download::stop_skip_tracker); download->download()->stop(torrent::Download::stop_skip_tracker);
@@ -280,7 +286,7 @@ DownloadList::close_files(Download* download) {
void void
DownloadList::close_directly(Download* download) { DownloadList::close_directly(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download directly."); lt_log_print_hash_only(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "download_list : closing download directly");
auto lifetime = download->lifetime(); auto lifetime = download->lifetime();
bool was_active = download->download()->info()->is_active(); bool was_active = download->download()->info()->is_active();
@@ -327,7 +333,8 @@ DownloadList::update_paused_state(Download* download) {
void void
DownloadList::close_quick(Download* download) { DownloadList::close_quick(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download quickly."); lt_log_print_hash_only(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "download_list : closing download quickly.");
close(download); close(download);
// Make sure we cancel any tracker requests. This should rather be // Make sure we cancel any tracker requests. This should rather be
@@ -341,7 +348,7 @@ void
DownloadList::close_throw(Download* download) { DownloadList::close_throw(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Closing download with throw."); LT_LOG_DOWNLOAD_DEBUG("closing download with throw", 0);
// When pause gets called it will clear the initial hash check state // When pause gets called it will clear the initial hash check state
// and set hash failed. This should ensure hashing doesn't restart // and set hash failed. This should ensure hashing doesn't restart
@@ -383,7 +390,7 @@ void
DownloadList::resume(Download* download, int flags) { DownloadList::resume(Download* download, int flags) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Resuming download: flags:%0x.", flags); LT_LOG_DOWNLOAD_DEBUG("resuming download : flags:%0x.", flags);
try { try {
@@ -472,7 +479,7 @@ DownloadList::resume(Download* download, int flags) {
DL_TRIGGER_EVENT(download, "event.download.resumed"); DL_TRIGGER_EVENT(download, "event.download.resumed");
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not resume download: %s", e.what()); LT_LOG_DOWNLOAD_EVENTS("could not resume download : %s", e.what());
} }
} }
@@ -480,7 +487,7 @@ void
DownloadList::pause(Download* download, int flags) { DownloadList::pause(Download* download, int flags) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Pausing download: flags:%0x.", flags); LT_LOG_DOWNLOAD_DEBUG("pausing download : flags:%0x.", flags);
auto lifetime = download->lifetime(); auto lifetime = download->lifetime();
@@ -523,7 +530,7 @@ DownloadList::pause(Download* download, int flags) {
//control->core()->download_store()->save(download); //control->core()->download_store()->save(download);
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not pause download: %s", e.what()); LT_LOG_DOWNLOAD_EVENTS("could not pause download : %s", e.what());
} }
} }
@@ -531,7 +538,7 @@ void
DownloadList::check_hash(Download* download) { DownloadList::check_hash(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Checking hash."); LT_LOG_DOWNLOAD_DEBUG("checking hash", 0);
try { try {
if (rpc::call_command_value("d.hashing", rpc::make_target(download)) != Download::variable_hashing_stopped) if (rpc::call_command_value("d.hashing", rpc::make_target(download)) != Download::variable_hashing_stopped)
@@ -540,7 +547,7 @@ DownloadList::check_hash(Download* download) {
hash_queue(download, Download::variable_hashing_rehash); hash_queue(download, Download::variable_hashing_rehash);
} catch (torrent::local_error& e) { } catch (torrent::local_error& e) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not check hash: %s", e.what()); LT_LOG_DOWNLOAD_EVENTS("could not check hash : %s", e.what());
} }
} }
@@ -548,7 +555,7 @@ void
DownloadList::hash_done(Download* download) { DownloadList::hash_done(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Hash done."); LT_LOG_DOWNLOAD_DEBUG("hash done", 0);
if (download->is_hash_checking() || download->is_active()) if (download->is_hash_checking() || download->is_active())
throw torrent::internal_error("DownloadList::hash_done(...) download in invalid state."); throw torrent::internal_error("DownloadList::hash_done(...) download in invalid state.");
@@ -613,7 +620,8 @@ DownloadList::hash_done(Download* download) {
confirm_finished(download); confirm_finished(download);
} else { } else {
download->set_message("Hash check on download completion found bad chunks."); download->set_message("Hash check on download completion found bad chunks.");
lt_log_print(torrent::LOG_TORRENT_ERROR, "Hash check on download completion found bad chunks."); LT_LOG_DOWNLOAD_EVENTS("hash check on download completion found bad chunks", 0);
DL_TRIGGER_EVENT(download, "event.download.hash_final_failed"); DL_TRIGGER_EVENT(download, "event.download.hash_final_failed");
} }
@@ -634,7 +642,7 @@ void
DownloadList::hash_queue(Download* download, int type) { DownloadList::hash_queue(Download* download, int type) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Hash queue."); LT_LOG_DOWNLOAD_DEBUG("hash queue : type:%d", type);
if (rpc::call_command_value("d.hashing", rpc::make_target(download)) != Download::variable_hashing_stopped) if (rpc::call_command_value("d.hashing", rpc::make_target(download)) != Download::variable_hashing_stopped)
throw torrent::internal_error("DownloadList::hash_queue(...) hashing already queued."); throw torrent::internal_error("DownloadList::hash_queue(...) hashing already queued.");
@@ -678,7 +686,7 @@ void
DownloadList::received_finished(Download* download) { DownloadList::received_finished(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Received finished."); LT_LOG_DOWNLOAD_DEBUG("received finished", 0);
if (rpc::call_command_value("pieces.hash.on_completion")) if (rpc::call_command_value("pieces.hash.on_completion"))
// Set some 'checking_finished_thingie' variable to make hash_done // Set some 'checking_finished_thingie' variable to make hash_done
@@ -693,7 +701,7 @@ void
DownloadList::confirm_finished(Download* download) { DownloadList::confirm_finished(Download* download) {
check_contains(download); check_contains(download);
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Confirming finished."); LT_LOG_DOWNLOAD_DEBUG("confirming finished", 0);
if (download->download()->info()->is_meta_download()) if (download->download()->info()->is_meta_download())
return process_meta_download(download); return process_meta_download(download);
@@ -709,6 +717,10 @@ DownloadList::confirm_finished(Download* download) {
if (choke_up.is_string_empty()) choke_up = rpc::call_command("protocol.choke_heuristics.up.seed", torrent::Object(), rpc::make_target(download)); if (choke_up.is_string_empty()) choke_up = rpc::call_command("protocol.choke_heuristics.up.seed", torrent::Object(), rpc::make_target(download));
if (choke_down.is_string_empty()) choke_down = rpc::call_command("protocol.choke_heuristics.down.seed", torrent::Object(), rpc::make_target(download)); if (choke_down.is_string_empty()) choke_down = rpc::call_command("protocol.choke_heuristics.down.seed", torrent::Object(), rpc::make_target(download));
// libtorrent refuses initial seeding on an active download.
if (download->is_active() && conn_current.as_string() == "initial_seed")
conn_current = "seed";
rpc::call_command("d.connection_current.set", conn_current, rpc::make_target(download)); rpc::call_command("d.connection_current.set", conn_current, rpc::make_target(download));
rpc::call_command("d.up.choke_heuristics.set", choke_up, rpc::make_target(download)); rpc::call_command("d.up.choke_heuristics.set", choke_up, rpc::make_target(download));
rpc::call_command("d.down.choke_heuristics.set", choke_down, rpc::make_target(download)); rpc::call_command("d.down.choke_heuristics.set", choke_down, rpc::make_target(download));
@@ -764,7 +776,7 @@ DownloadList::confirm_finished(Download* download) {
void void
DownloadList::process_meta_download(Download* download) { DownloadList::process_meta_download(Download* download) {
lt_log_print_info(torrent::LOG_TORRENT_INFO, download->info(), "download_list", "Processing meta download."); LT_LOG_DOWNLOAD_DEBUG("processing meta download", 0);
rpc::call_command("d.stop", torrent::Object(), rpc::make_target(download)); rpc::call_command("d.stop", torrent::Object(), rpc::make_target(download));
rpc::call_command("d.close", torrent::Object(), rpc::make_target(download)); rpc::call_command("d.close", torrent::Object(), rpc::make_target(download));
@@ -774,7 +786,7 @@ DownloadList::process_meta_download(Download* download) {
std::fstream file(metafile.c_str(), std::ios::in | std::ios::binary); std::fstream file(metafile.c_str(), std::ios::in | std::ios::binary);
if (!file.is_open()) { if (!file.is_open()) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not read download metadata."); LT_LOG_DOWNLOAD_EVENTS("could not read download metadata : %s", metafile.c_str());
return; return;
} }
@@ -782,7 +794,7 @@ DownloadList::process_meta_download(Download* download) {
file >> bencode->insert_key("info", torrent::Object()); file >> bencode->insert_key("info", torrent::Object());
if (file.fail()) { if (file.fail()) {
lt_log_print(torrent::LOG_TORRENT_ERROR, "Could not create download, the input is not a valid torrent."); LT_LOG_DOWNLOAD_EVENTS("could not create download, the input is not a valid torrent : %s", metafile.c_str());
return; return;
} }
@@ -790,8 +802,10 @@ DownloadList::process_meta_download(Download* download) {
// Steal the keys we still need. The old download has no use for them. // Steal the keys we still need. The old download has no use for them.
bencode->insert_key("rtorrent_meta_download", torrent::Object()).swap(download->bencode()->get_key("rtorrent_meta_download")); bencode->insert_key("rtorrent_meta_download", torrent::Object()).swap(download->bencode()->get_key("rtorrent_meta_download"));
if (download->bencode()->has_key("announce")) if (download->bencode()->has_key("announce"))
bencode->insert_key("announce", torrent::Object()).swap(download->bencode()->get_key("announce")); bencode->insert_key("announce", torrent::Object()).swap(download->bencode()->get_key("announce"));
if (download->bencode()->has_key("announce-list")) if (download->bencode()->has_key("announce-list"))
bencode->insert_key("announce-list", torrent::Object()).swap(download->bencode()->get_key("announce-list")); bencode->insert_key("announce-list", torrent::Object()).swap(download->bencode()->get_key("announce-list"));
+1
View File
@@ -1,6 +1,7 @@
#ifndef RTORRENT_CORE_DOWNLOAD_LIST_H #ifndef RTORRENT_CORE_DOWNLOAD_LIST_H
#define RTORRENT_CORE_DOWNLOAD_LIST_H #define RTORRENT_CORE_DOWNLOAD_LIST_H
#include <cstdint>
#include <iosfwd> #include <iosfwd>
#include <list> #include <list>
#include <memory> #include <memory>
+2 -1
View File
@@ -440,7 +440,8 @@ Manager::receive_hashing_changed() {
} else { } else {
(*itr)->set_hash_failed(true); (*itr)->set_hash_failed(true);
(*itr)->set_message("Hashing failed: " + std::string(e.what())); (*itr)->set_message("Hashing failed: " + std::string(e.what()));
lt_log_print(torrent::LOG_TORRENT_ERROR, "Hashing failed: %s", e.what());
lt_log_print_hash_only(torrent::LOG_TORRENT_EVENTS, (*itr)->info()->hash(), "hashing failed : %s", e.what());
} }
} }
} }
-1
View File
@@ -8,7 +8,6 @@
#include <torrent/object.h> #include <torrent/object.h>
#include "download_list.h" #include "download_list.h"
#include "range_map.h"
namespace torrent { namespace torrent {
class Bencode; class Bencode;
-199
View File
@@ -1,199 +0,0 @@
// rTorrent - BitTorrent client
// Copyright (C) 2005-2008, Jari Sundell
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations
// including the two.
//
// You must obey the GNU General Public License in all respects for
// all of the code used other than OpenSSL. If you modify file(s)
// with this exception, you may extend this exception to your version
// of the file(s), but you are not obligated to do so. If you do not
// wish to do so, delete this exception statement from your version.
// If you delete this exception statement from all source files in the
// program, then also delete it here.
//
// Contact: Jari Sundell <sundell.software@gmail.com>
#ifndef RTORRENT_CORE_RANGE_MAP_H
#define RTORRENT_CORE_RANGE_MAP_H
#include <map>
#include <stdexcept>
namespace core {
// Associate values with a range of keys, and retrieve for any key in the range.
// The template arguments have the same semantics as std::map.
// Exception: if set_merge is used, the value type must have a defined operator ==.
template<typename Key, typename T, typename Compare = std::less<Key>,
typename Alloc = std::allocator<std::pair<const Key, T> > >
class RangeMap : private std::map<Key, std::pair<Key, T>, Compare,
typename std::allocator_traits<Alloc>::template rebind_alloc<std::pair<const Key, std::pair<Key, T>>>> {
typedef std::map<Key, std::pair<Key, T>, Compare,
typename std::allocator_traits<Alloc>::template rebind_alloc<std::pair<const Key, std::pair<Key, T>>>> base_type;
//std::allocator_traits<Alloc>::template rebind_alloc<std::pair<const Key, std::pair<Key, T>>>
public:
RangeMap() = default;
RangeMap(const Compare& c) : base_type(c) {}
typedef typename base_type::iterator iterator;
typedef typename base_type::reverse_iterator reverse_iterator;
typedef typename base_type::const_iterator const_iterator;
typedef typename base_type::const_reverse_iterator const_reverse_iterator;
// using typename base_type::const_iterator;
// using typename base_type::const_reverse_iterator;
using base_type::clear;
using base_type::swap;
using base_type::size;
using base_type::empty;
using base_type::begin;
using base_type::end;
using base_type::rbegin;
using base_type::rend;
using base_type::key_comp;
using base_type::value_comp;
// Store a value for the range [begin, end). Returns iterator for the range.
const_iterator set_range(const Key& begin, const Key& end, const T& value);
// Same, but merge adjacent ranges having the same value. Returns iterator for the merged range.
const_iterator set_merge(Key begin, const Key& end, const T& value);
// Find range containing the given key, or end().
const_iterator find(const Key& key) const;
// Retrieve value for key in a range, throw std::out_of_range if range does not exist.
const T& get(const Key& key) const;
// Retrieve value for key in a range, return def if range does not exist.
T get(const Key& key, T def) const;
private:
iterator crop_overlap(const Key& begin, const Key& end);
};
// Semantics of an entry:
// .first End of range (exclusive), map key.
// .second.first Beginning of range.
// .second.second Value.
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::iterator
RangeMap<Key,T,C,A>::crop_overlap(const Key& _begin, const Key& _end) {
typename RangeMap::iterator itr = base_type::upper_bound(_begin);
while (itr != end() && key_comp()(itr->second.first, _end)) {
// There's a subrange before the new begin: need new entry (new range end means new key).
if (key_comp()(itr->second.first, _begin))
base_type::insert(itr, typename RangeMap::value_type(_begin, itr->second));
// Old end is within our range: erase entry.
if (!key_comp()(_end, itr->first)) {
base_type::erase(itr++);
// Otherwise simply set the new begin of the old range.
} else {
itr->second.first = _end;
++itr;
}
}
return itr;
}
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::const_iterator
RangeMap<Key,T,C,A>::set_merge(Key _begin, const Key& _end, const T& value) {
if (!key_comp()(_begin, _end))
return end();
// Crop overlapping ranges and return iterator to first range after the one we're inserting.
typename RangeMap::iterator itr = crop_overlap(_begin, _end);
// Check if range before new one is adjacent and has same value: if so erase it and use its beginning.
if (itr != begin()) {
typename RangeMap::iterator prev = itr;
if (!key_comp()((--prev)->first, _begin) && prev->second.second == value) {
_begin = prev->second.first;
base_type::erase(prev);
}
}
// Range after new one is adjacent and has same value: set new beginning.
if (itr != end() && !key_comp()(_end, itr->second.first) && itr->second.second == value) {
itr->second.first = _begin;
return itr;
}
// Otherwise, this range isn't mergeable, make new entry.
return base_type::insert(itr, typename RangeMap::value_type(_end, typename RangeMap::mapped_type(_begin, value)));
}
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::const_iterator
RangeMap<Key,T,C,A>::set_range(const Key& _begin, const Key& _end, const T& value) {
if (!key_comp()(_begin, _end))
return end();
return base_type::insert(crop_overlap(_begin, _end), typename RangeMap::value_type(_end, typename RangeMap::mapped_type(_begin, value)));
}
template<typename Key, typename T, typename C, typename A>
inline typename RangeMap<Key,T,C,A>::const_iterator
RangeMap<Key,T,C,A>::find(const Key& key) const {
typename RangeMap::const_iterator itr = base_type::upper_bound(key);
if (itr != end() && key_comp()(key, itr->second.first))
itr = end();
return itr;
}
template<typename Key, typename T, typename C, typename A>
inline const T&
RangeMap<Key,T,C,A>::get(const Key& key) const {
typename RangeMap::const_iterator itr = find(key);
if (itr == end())
throw std::out_of_range("RangeMap::get");
return itr->second.second;
}
template<typename Key, typename T, typename C, typename A>
inline T
RangeMap<Key,T,C,A>::get(const Key& key, T def) const {
typename RangeMap::const_iterator itr = find(key);
return (itr == end() ? def : itr->second.second);
}
}
#endif
-6
View File
@@ -16,15 +16,12 @@ namespace utils {
namespace torrent { namespace torrent {
class ClientInfo; class ClientInfo;
class Entry;
} }
class Control; class Control;
namespace display { namespace display {
char* print_string(char* first, char* last, char* str);
char* print_hhmmss(char* first, char* last, time_t t); char* print_hhmmss(char* first, char* last, time_t t);
char* print_hhmmss_local(char* first, char* last, time_t t); char* print_hhmmss_local(char* first, char* last, time_t t);
char* print_ddhhmm(char* first, char* last, time_t t); char* print_ddhhmm(char* first, char* last, time_t t);
@@ -42,9 +39,6 @@ char* print_download_percentage_done(char* first, char* last, core::Downlo
char* print_client_version(char* first, char* last, const torrent::ClientInfo& clientInfo); char* print_client_version(char* first, char* last, const torrent::ClientInfo& clientInfo);
char* print_entry_tags(char* first, char* last);
char* print_entry_file(char* first, char* last, const torrent::Entry& entry);
char* print_status_throttle_limit(char* first, char* last, bool up, const std::vector<std::string>& throttle_names); char* print_status_throttle_limit(char* first, char* last, bool up, const std::vector<std::string>& throttle_names);
char* print_status_throttle_rate(char* first, char* last, bool up, const std::vector<std::string>& throttle_names, const double& global_rate); char* print_status_throttle_rate(char* first, char* last, bool up, const std::vector<std::string>& throttle_names, const double& global_rate);
+2 -1
View File
@@ -57,7 +57,8 @@ WindowDownloadChunksSeen::redraw() {
const torrent::TransferList* transfers = m_download->download()->transfer_list(); const torrent::TransferList* transfers = m_download->download()->transfer_list();
std::vector<torrent::BlockList*> transferChunks(transfers->begin(), transfers->end()); std::vector<torrent::BlockList*> transferChunks(transfers->begin(), transfers->end());
std::sort(transferChunks.begin(), transferChunks.end()); std::sort(transferChunks.begin(), transferChunks.end(),
[](const auto& left, const auto& right) { return left->index() < right->index(); });
std::vector<torrent::BlockList*>::const_iterator itrTransfer = transferChunks.begin(); std::vector<torrent::BlockList*>::const_iterator itrTransfer = transferChunks.begin();
-2
View File
@@ -69,8 +69,6 @@ public:
bool pressed(int key); bool pressed(int key);
void ignore(int key) { (*this)[key] = slot_void(); }
private: private:
bool m_enabled{true}; bool m_enabled{true};
}; };
+1 -1
View File
@@ -100,7 +100,7 @@ PathInput::receive_do_complete() {
entry.s_name += '/'; entry.s_name += '/';
} }
range_type r = find_incomplete(dir, str().substr(dirEnd, get_pos())); range_type r = find_incomplete(dir, str().substr(dirEnd, get_pos() - dirEnd));
if (r.first == r.second) if (r.first == r.second)
return; // Show some nice colors here. return; // Show some nice colors here.
+2
View File
@@ -492,6 +492,7 @@ main(int argc, char** argv) {
control->cleanup(); control->cleanup();
} catch (torrent::internal_error& e) { } catch (torrent::internal_error& e) {
if (control != nullptr)
control->cleanup_exception(); control->cleanup_exception();
std::cout << "rtorrent: caught torrent::internal_error: " std::cout << "rtorrent: caught torrent::internal_error: "
@@ -505,6 +506,7 @@ main(int argc, char** argv) {
return -1; return -1;
} catch (std::exception& e) { } catch (std::exception& e) {
if (control != nullptr)
control->cleanup_exception(); control->cleanup_exception();
std::cout << "rtorrent: caught" << typeid(e).name() << " : " << e.what() << std::endl; std::cout << "rtorrent: caught" << typeid(e).name() << " : " << e.what() << std::endl;
+1 -1
View File
@@ -108,7 +108,7 @@ public:
static stack_type* from_data(char* data) { return reinterpret_cast<stack_type*>(data); } static stack_type* from_data(char* data) { return reinterpret_cast<stack_type*>(data); }
char buffer[sizeof(torrent::Object) * max_arguments]; alignas(optimal_alignment) char buffer[sizeof(torrent::Object) * max_arguments];
}; };
command_base() : m_copy_helper(nullptr), m_dest_helper(nullptr) {} command_base() : m_copy_helper(nullptr), m_dest_helper(nullptr) {}
+5 -2
View File
@@ -1,5 +1,7 @@
#include "config.h" #include "config.h"
#include <memory>
#include "rpc/exec_file.h" #include "rpc/exec_file.h"
// #include <cassert> // #include <cassert>
@@ -81,8 +83,9 @@ ExecFile::execute_object(const torrent::Object& rawArgs, int flags) {
char* argsBuffer[max_args]; char* argsBuffer[max_args];
char** argsCurrent = argsBuffer; char** argsCurrent = argsBuffer;
// Size of value strings are less than 24. // On the heap: a buffer of buffer_size does not belong on the stack of whichever thread runs the command.
char valueBuffer[buffer_size+1]; auto valueStorage = std::make_unique<char[]>(buffer_size + 1);
char* valueBuffer = valueStorage.get();
char* valueCurrent = valueBuffer; char* valueCurrent = valueBuffer;
if (rawArgs.is_list()) { if (rawArgs.is_list()) {
+1 -1
View File
@@ -10,7 +10,7 @@ namespace rpc {
class ExecFile { class ExecFile {
public: public:
static constexpr unsigned int max_args = 128; static constexpr unsigned int max_args = 128;
static constexpr unsigned int buffer_size = 4096; static constexpr unsigned int buffer_size = 128 * 1024;
static constexpr int flag_expand_tilde = 0x1; static constexpr int flag_expand_tilde = 0x1;
static constexpr int flag_throw = 0x2; static constexpr int flag_throw = 0x2;
+4
View File
@@ -394,6 +394,10 @@ execute_lua(LuaEngine* engine, rpc::target_type target_type, torrent::Object con
switch (raw_args.type()) { switch (raw_args.type()) {
case torrent::Object::TYPE_LIST: { case torrent::Object::TYPE_LIST: {
const torrent::Object::list_type& args = raw_args.as_list(); const torrent::Object::list_type& args = raw_args.as_list();
if (args.empty())
throw torrent::input_error("Too few arguments.");
if (flags & LuaEngine::flag_string) { if (flags & LuaEngine::flag_string) {
check_lua_status(l_state, luaL_loadstring(l_state, args.begin()->as_string().c_str())); check_lua_status(l_state, luaL_loadstring(l_state, args.begin()->as_string().c_str()));
} else { } else {
+40 -9
View File
@@ -1,5 +1,7 @@
#include "config.h" #include "config.h"
#include <cctype>
#include <charconv>
#include <cstring> #include <cstring>
#include <cstdio> #include <cstdio>
#include <limits> #include <limits>
@@ -113,16 +115,45 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
if (unit <= 0) if (unit <= 0)
throw torrent::input_error("Command::string_to_value_unit(...) received unit <= 0."); throw torrent::input_error("Command::string_to_value_unit(...) received unit <= 0.");
char* last; if (base != 0 && base != 8 && base != 10 && base != 16)
throw torrent::input_error("Command::string_to_value_unit(...) received invalid base.");
const char* first = src;
while (parse_is_space(*src))
src++;
if (src[0] == '+')
return first;
if (src[0] == '-') {
if (base == 8 || base == 16)
return first;
if (src[1] == '0')
return first;
}
if (base == 10 && src[0] == '0' && (src[1] >= '0' && src[1] <= '9'))
return first;
char* last{};
errno = 0;
*value = strtoll(src, &last, base); *value = strtoll(src, &last, base);
if (errno == ERANGE)
return first;
if (last == src) { if (last == src) {
*value = 0;
if (strcasecmp(src, "no") == 0) { *value = 0; return src + strlen("no"); } if (strcasecmp(src, "no") == 0) { *value = 0; return src + strlen("no"); }
if (strcasecmp(src, "yes") == 0) { *value = 1; return src + strlen("yes"); } if (strcasecmp(src, "yes") == 0) { *value = 1; return src + strlen("yes"); }
if (strcasecmp(src, "true") == 0) { *value = 1; return src + strlen("true"); } if (strcasecmp(src, "true") == 0) { *value = 1; return src + strlen("true"); }
if (strcasecmp(src, "false") == 0) { *value = 0; return src + strlen("false"); } if (strcasecmp(src, "false") == 0) { *value = 0; return src + strlen("false"); }
return src; return first;
} }
switch (*last) { switch (*last) {
@@ -130,15 +161,15 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
case 'B': ++last; break; case 'B': ++last; break;
case 'k': case 'k':
case 'K': case 'K':
if (!value_fits_shifted(*value, 10)) return src; // overflow guard if (!value_fits_shifted(*value, 10)) return first; // overflow guard
*value = *value << 10; ++last; break; *value = *value << 10; ++last; break;
case 'm': case 'm':
case 'M': case 'M':
if (!value_fits_shifted(*value, 20)) return src; // overflow guard if (!value_fits_shifted(*value, 20)) return first; // overflow guard
*value = *value << 20; ++last; break; *value = *value << 20; ++last; break;
case 'g': case 'g':
case 'G': case 'G':
if (!value_fits_shifted(*value, 30)) return src; // overflow guard if (!value_fits_shifted(*value, 30)) return first; // overflow guard
*value = *value << 30; ++last; break; *value = *value << 30; ++last; break;
// case ' ': // case ' ':
// case '\0': *value = *value * unit; break; // case '\0': *value = *value * unit; break;
@@ -146,7 +177,7 @@ parse_value_nothrow(const char* src, int64_t* value, int base, int unit) {
default: default:
if (*value > std::numeric_limits<int64_t>::max() / unit || if (*value > std::numeric_limits<int64_t>::max() / unit ||
*value < std::numeric_limits<int64_t>::min() / unit) *value < std::numeric_limits<int64_t>::min() / unit)
return src; // overflow guard return first; // overflow guard
*value = *value * unit; *value = *value * unit;
break; break;
@@ -161,7 +192,7 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (*
if (++depth >= max_parse_depth) if (++depth >= max_parse_depth)
throw torrent::input_error("Max parse depth reached."); throw torrent::input_error("Max parse depth reached.");
if (*first == '{') { if (first != last && *first == '{') {
*dest = torrent::Object::create_list(); *dest = torrent::Object::create_list();
first = parse_list(first + 1, last, dest, &parse_is_delim_block, depth); first = parse_list(first + 1, last, dest, &parse_is_delim_block, depth);
first = parse_skip_wspace(first, last); first = parse_skip_wspace(first, last);
@@ -171,7 +202,7 @@ parse_object(const char* first, const char* last, torrent::Object* dest, bool (*
return ++first; return ++first;
} else if (*first == '(') { } else if (first != last && *first == '(') {
int32_t parentheses = 1; int32_t parentheses = 1;
while (first + 1 != last && *(first + 1) == '(') { while (first + 1 != last && *(first + 1) == '(') {
@@ -393,7 +424,7 @@ convert_to_value_nothrow(const torrent::Object& src, int64_t* value, int base, i
== unpacked.as_string().c_str() + unpacked.as_string().size(); == unpacked.as_string().c_str() + unpacked.as_string().size();
case torrent::Object::TYPE_RAW_STRING: { case torrent::Object::TYPE_RAW_STRING: {
const torrent::raw_string& str = src.as_raw_string(); const torrent::raw_string& str = unpacked.as_raw_string();
auto buffer = std::make_unique<char[]>(str.size() + 1); auto buffer = std::make_unique<char[]>(str.size() + 1);
std::memcpy(buffer.get(), str.data(), str.size()); std::memcpy(buffer.get(), str.data(), str.size());
-34
View File
@@ -1,37 +1,3 @@
// rTorrent - BitTorrent client
// Copyright (C) 2005-2011, Jari Sundell
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations
// including the two.
//
// You must obey the GNU General Public License in all respects for
// all of the code used other than OpenSSL. If you modify file(s)
// with this exception, you may extend this exception to your version
// of the file(s), but you are not obligated to do so. If you do not
// wish to do so, delete this exception statement from your version.
// If you delete this exception statement from all source files in the
// program, then also delete it here.
//
// Contact: Jari Sundell <sundell.software@gmail.com>
#ifndef RTORRENT_RPC_PARSE_H #ifndef RTORRENT_RPC_PARSE_H
#define RTORRENT_RPC_PARSE_H #define RTORRENT_RPC_PARSE_H
+33
View File
@@ -123,6 +123,39 @@ parse_command(target_type target, const char* first, const char* last) {
return std::make_pair(commands.call_command(key, args, target), first); return std::make_pair(commands.call_command(key, args, target), first);
} }
torrent::Object
parse_command_object(const char* first, const char* last) {
first = std::find_if(first, last, [&](char c) { return !command_map_is_space(c); });
if (first == last || *first == '#')
return torrent::Object();
char key[128];
first = parse_command_name(first, last, key, key + 128);
first = std::find_if(first, last, [&](char c) { return !command_map_is_space(c); });
if (first == last || *first != '=')
throw torrent::input_error("Could not find '=' in command '" + std::string(key) + "'.");
torrent::Object result = torrent::Object::create_dict_key();
result.as_dict_key() = key;
first = parse_whole_list(first + 1, last, &result.as_dict_obj(), &parse_is_delim_command);
// Find the last character that is part of this command, skipping
// the whitespace at the end.
first = std::find_if(first, last, [&](char c) { return !command_map_is_space(c); });
// This helper accepts exactly one command and cannot return where a next
// command begins, so reject every non-whitespace suffix, including ';'.
if (first != last && *first != '\0')
throw torrent::input_error("Junk at end of input.");
return result;
}
torrent::Object torrent::Object
parse_command_multiple(target_type target, const char* first, const char* last) { parse_command_multiple(target_type target, const char* first, const char* last) {
parse_command_type result; parse_command_type result;
+31
View File
@@ -37,6 +37,9 @@
#include <string> #include <string>
#include <cstring> #include <cstring>
#include <functional>
#include <utility>
#include <vector>
#include "xmlrpc.h" #include "xmlrpc.h"
#include "rpc_manager.h" #include "rpc_manager.h"
@@ -67,6 +70,34 @@ parse_command_single(target_type target, const std::string& cmd) {
return parse_command(target, cmd.c_str(), cmd.c_str() + cmd.size()).first; return parse_command(target, cmd.c_str(), cmd.c_str() + cmd.size()).first;
} }
// Parse one RPC command without executing it. Repeated evaluations can use
// call_object on the result, which handles per-target argument expansion.
torrent::Object parse_command_object(const char* first, const char* last);
inline torrent::Object parse_command_object(const std::string& cmd) {
return parse_command_object(cmd.c_str(), cmd.c_str() + cmd.size());
}
// Prepare a multicall's commands once, on the first target that uses them. This
// keeps empty target lists from parsing commands that would never be evaluated.
struct preparsed_commands : public std::vector<torrent::Object> {
explicit preparsed_commands(std::function<void(preparsed_commands&)> prepare)
: m_prepare(std::move(prepare)) {}
void prepare_if_needed() {
if (m_prepare) {
// Clear before invoking: the callback may inspect this vector, and a
// throwing callback must not be run again against partially added items.
auto prepare = std::move(m_prepare);
m_prepare = {};
prepare(*this);
}
}
private:
std::function<void(preparsed_commands&)> m_prepare;
};
inline torrent::Object inline torrent::Object
parse_command_multiple_std(const std::string& cmd, target_type target = rpc::make_target()) { parse_command_multiple_std(const std::string& cmd, target_type target = rpc::make_target()) {
return parse_command_multiple(target, cmd.c_str(), cmd.c_str() + cmd.size()); return parse_command_multiple(target, cmd.c_str(), cmd.c_str() + cmd.size());
+2
View File
@@ -440,6 +440,8 @@ XmlRpc::cleanup() {
xmlrpc_registry_free((xmlrpc_registry*)m_registry); xmlrpc_registry_free((xmlrpc_registry*)m_registry);
xmlrpc_env_clean((xmlrpc_env*)m_env); xmlrpc_env_clean((xmlrpc_env*)m_env);
delete (xmlrpc_env*)m_env; delete (xmlrpc_env*)m_env;
m_env = nullptr;
m_registry = nullptr;
} }
bool bool
+32
View File
@@ -17,6 +17,13 @@
#include "core/download.h" #include "core/download.h"
#include "utils/directory.h" #include "utils/directory.h"
// O_DIRECTORY makes the open refuse anything that is not a directory. A
// platform that does not define it still gets the read-only directory
// handle fsync needs, so fall back to no extra flag.
#ifndef O_DIRECTORY
#define O_DIRECTORY 0
#endif
namespace session { namespace session {
DownloadStorer::DownloadStorer(core::Download* download) DownloadStorer::DownloadStorer(core::Download* download)
@@ -166,6 +173,26 @@ save_stream(const std::string& path, bool use_fsyncdisk, const std::stringstream
throw torrent::storage_error("failed to close file descriptor : " + path); throw torrent::storage_error("failed to close file descriptor : " + path);
} }
void
sync_directory_of(const std::string& path) {
auto separator = path.rfind('/');
auto directory = separator == std::string::npos ? std::string(".") :
separator == 0 ? std::string("/") : path.substr(0, separator);
int fd = ::open(directory.c_str(), O_RDONLY | O_DIRECTORY);
if (fd < 0)
throw torrent::storage_error("failed to open session directory for sync : " + directory);
if (::fsync(fd) == -1) {
::close(fd);
throw torrent::storage_error("failed to sync session directory : " + directory);
}
if (::close(fd) == -1)
throw torrent::storage_error("failed to close session directory : " + directory);
}
} // namespace anonymous } // namespace anonymous
void void
@@ -193,6 +220,11 @@ DownloadStorer::save_and_move_streams(const std::string& path, bool use_fsyncdis
if (::rename((rtorrent_path + ".new").c_str(), rtorrent_path.c_str()) == -1) if (::rename((rtorrent_path + ".new").c_str(), rtorrent_path.c_str()) == -1)
throw torrent::storage_error("failed to rename rtorrent resume file : " + rtorrent_path); throw torrent::storage_error("failed to rename rtorrent resume file : " + rtorrent_path);
// Syncing the files themselves does not persist the renames; the directory
// holding them has to be synced for the new names to survive a crash.
if (use_fsyncdisk)
sync_directory_of(path);
} }
utils::Directory utils::Directory
-1
View File
@@ -75,7 +75,6 @@ SessionManager::save_resume_download(core::Download* download) {
{ {
std::unique_lock<std::mutex> lock(m_pending_builds_mutex); std::unique_lock<std::mutex> lock(m_pending_builds_mutex);
// TODO: This is under the wrong lock.
if (!m_active) if (!m_active)
throw torrent::internal_error("SessionManager::save_resume_download() called while not active."); throw torrent::internal_error("SessionManager::save_resume_download() called while not active.");
+7 -2
View File
@@ -1,6 +1,7 @@
#ifndef RTORRENT_SESSION_SESSION_MANAGER_H #ifndef RTORRENT_SESSION_SESSION_MANAGER_H
#define RTORRENT_SESSION_SESSION_MANAGER_H #define RTORRENT_SESSION_SESSION_MANAGER_H
#include <atomic>
#include <condition_variable> #include <condition_variable>
#include <deque> #include <deque>
#include <future> #include <future>
@@ -99,9 +100,13 @@ private:
bool m_use_fsyncdisk{true}; bool m_use_fsyncdisk{true};
bool m_use_lock{true}; bool m_use_lock{true};
align_cacheline std::mutex m_mutex; align_cacheline
bool m_active{}; std::atomic<bool> m_active{};
align_cacheline
std::mutex m_mutex;
std::deque<SaveRequest> m_save_requests; std::deque<SaveRequest> m_save_requests;
std::atomic<size_t> m_save_request_counter{}; std::atomic<size_t> m_save_request_counter{};
+8 -4
View File
@@ -63,8 +63,10 @@ SignalHandler::set_block(unsigned int signum) {
sigemptyset(&mask); sigemptyset(&mask);
sigaddset(&mask, signum); sigaddset(&mask, signum);
if (pthread_sigmask(SIG_BLOCK, &mask, NULL) == -1) int result = pthread_sigmask(SIG_BLOCK, &mask, NULL);
throw std::logic_error("Could not block signal: " + std::string(std::strerror(errno)));
if (result != 0)
throw std::logic_error("Could not block signal: " + std::string(std::strerror(result)));
} }
void void
@@ -77,8 +79,10 @@ SignalHandler::set_unblock(unsigned int signum) {
sigemptyset(&mask); sigemptyset(&mask);
sigaddset(&mask, signum); sigaddset(&mask, signum);
if (pthread_sigmask(SIG_UNBLOCK, &mask, NULL) == -1) int result = pthread_sigmask(SIG_UNBLOCK, &mask, NULL);
throw std::logic_error("Could not unblock signal: " + std::string(std::strerror(errno)));
if (result != 0)
throw std::logic_error("Could not unblock signal: " + std::string(std::strerror(result)));
} }
void void
-1
View File
@@ -6,7 +6,6 @@
#include <torrent/peer/peer.h> #include <torrent/peer/peer.h>
#include "display/manager.h" #include "display/manager.h"
#include "utils/list_focus.h"
#include "element_base.h" #include "element_base.h"
+2 -1
View File
@@ -197,8 +197,9 @@ ElementDownloadList::receive_cycle_throttle() {
return; return;
core::Download* download = m_view->focus()->get(); core::Download* download = m_view->focus()->get();
if (download->is_active()) { if (download->is_active()) {
lt_log_print(torrent::LOG_TORRENT_WARN, "Cannot change throttle on active download."); lt_log_print_hash_only(torrent::LOG_TORRENT_DEBUG, download->info()->hash(), "cannot change throttle on active download");
return; return;
} }
+1 -1
View File
@@ -179,7 +179,7 @@ ElementMenu::set_entry(size_type idx, bool triggerSlot) {
m_entry = idx; m_entry = idx;
focus_entry(m_entry); focus_entry(m_entry);
if (triggerSlot) if (triggerSlot && m_entry < size())
base_type::operator[](m_entry).m_slotFocus(); base_type::operator[](m_entry).m_slotFocus();
m_window->mark_dirty(); m_window->mark_dirty();
-158
View File
@@ -1,158 +0,0 @@
// rTorrent - BitTorrent client
// Copyright (C) 2005-2011, Jari Sundell
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations
// including the two.
//
// You must obey the GNU General Public License in all respects for
// all of the code used other than OpenSSL. If you modify file(s)
// with this exception, you may extend this exception to your version
// of the file(s), but you are not obligated to do so. If you do not
// wish to do so, delete this exception statement from your version.
// If you delete this exception statement from all source files in the
// program, then also delete it here.
//
// Contact: Jari Sundell <sundell.software@gmail.com>
#ifndef RTORRENT_UTILS_LIST_FOCUS_H
#define RTORRENT_UTILS_LIST_FOCUS_H
#include <functional>
#include <list>
namespace utils {
// Can't make this class inherit privately due to gcc PR 14258.
template <typename Base>
class ListFocus {
public:
typedef Base base_type;
typedef std::function<void ()> slot_void;
typedef std::list<slot_void> signal_void;
typedef typename base_type::iterator iterator;
typedef typename base_type::const_iterator const_iterator;
typedef typename base_type::reverse_iterator reverse_iterator;
typedef typename base_type::const_reverse_iterator const_reverse_iterator;
typedef typename base_type::value_type value_type;
ListFocus(base_type* b = nullptr) : m_base(b) { if (b) m_focus = b->end(); }
// Convinience functions, would have added more through using, but
// can't.
iterator begin() { return m_base->begin(); }
iterator end() { return m_base->end(); }
reverse_iterator rbegin() { return m_base->rbegin(); }
reverse_iterator rend() { return m_base->rend(); }
// Don't do erase on this object without making sure focus is right.
base_type& base() { return *m_base; }
iterator get_focus() { return m_focus; }
void set_focus(iterator itr);
// These are looping increment/decrements.
iterator inc_focus();
iterator dec_focus();
iterator erase(iterator itr);
void remove(const value_type& v);
// Be careful with copying signals.
signal_void& signal_changed() { return m_signal_changed; }
private:
void emit_changed();
base_type* m_base;
iterator m_focus;
signal_void m_signal_changed;
};
template <typename Base>
void
ListFocus<Base>::set_focus(iterator itr) {
m_focus = itr;
emit_changed();
}
template <typename Base>
typename ListFocus<Base>::iterator
ListFocus<Base>::inc_focus() {
if (m_focus != end())
++m_focus;
else
m_focus = begin();
emit_changed();
return m_focus;
}
template <typename Base>
typename ListFocus<Base>::iterator
ListFocus<Base>::dec_focus() {
if (m_focus != begin())
--m_focus;
else
m_focus = end();
emit_changed();
return m_focus;
}
template <typename Base>
typename ListFocus<Base>::iterator
ListFocus<Base>::erase(iterator itr) {
if (itr == m_focus) {
m_focus = m_base->erase(itr);
emit_changed();
return m_focus;
}
return m_base->erase(itr);
}
template <typename Base>
void
ListFocus<Base>::remove(const value_type& v) {
iterator first = begin();
iterator last = end();
while (first != last)
if (*first == v)
first = erase(first);
else
++first;
}
template <typename Base>
void
ListFocus<Base>::emit_changed() {
for (const auto& itr : m_signal_changed)
itr();
}
}
#endif
+12
View File
@@ -51,6 +51,8 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rpc/test_command_slot.h \ rpc/test_command_slot.h \
rpc/test_object_storage.cc \ rpc/test_object_storage.cc \
rpc/test_object_storage.h \ rpc/test_object_storage.h \
rpc/test_parse.cc \
rpc/test_parse.h \
rpc/test_parse_options.cc \ rpc/test_parse_options.cc \
rpc/test_parse_options.h \ rpc/test_parse_options.h \
rpc/test_rpc_manager.cc \ rpc/test_rpc_manager.cc \
@@ -59,6 +61,8 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
src/test_command_dynamic.cc \ src/test_command_dynamic.cc \
src/test_command_dynamic.h \ src/test_command_dynamic.h \
src/test_command_groups.cc \
src/test_command_groups.h \
src/test_command_ip.cc \ src/test_command_ip.cc \
src/test_command_ip.h \ src/test_command_ip.h \
src/test_command_system.cc \ src/test_command_system.cc \
@@ -69,6 +73,14 @@ rtorrent_Test_Src_SOURCES = $(rtorrent_Test_Common) \
src/test_command_string.h \ src/test_command_string.h \
src/test_command_throttle.cc \ src/test_command_throttle.cc \
src/test_command_throttle.h \ src/test_command_throttle.h \
src/test_command_tracker.cc \
src/test_command_tracker.h \
src/test_download_list.cc \
src/test_download_list.h \
src/test_input_path_input.cc \
src/test_input_path_input.h \
src/test_session_commit.cc \
src/test_session_commit.h \
src/test_session_storer.cc \ src/test_session_storer.cc \
src/test_session_storer.h \ src/test_session_storer.h \
src/test_setup.cc \ src/test_setup.cc \
+50
View File
@@ -2,7 +2,10 @@
#include "test/rpc/test_command.h" #include "test/rpc/test_command.h"
#include <torrent/exceptions.h>
#include "rpc/command.h" #include "rpc/command.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommand); CPPUNIT_TEST_SUITE_REGISTRATION(TestCommand);
@@ -83,3 +86,50 @@ TestCommand::test_stack_double() {
rpc::command_base::pop_stack(&stack_first, last_stack_first); rpc::command_base::pop_stack(&stack_first, last_stack_first);
CPPUNIT_ASSERT(command_stack_all_empty()); CPPUNIT_ASSERT(command_stack_all_empty());
} }
void
TestCommand::test_preparsed_commands() {
unsigned int prepare_count = 0;
rpc::preparsed_commands commands([&prepare_count](auto& prepared) {
++prepare_count;
// Reentrant access must not invoke the same callback recursively.
prepared.prepare_if_needed();
prepared.push_back(rpc::parse_command_object("string.length=abc"));
});
CPPUNIT_ASSERT_EQUAL(0u, prepare_count);
CPPUNIT_ASSERT(commands.empty());
commands.prepare_if_needed();
CPPUNIT_ASSERT_EQUAL(1u, prepare_count);
size_t count = 0;
for (auto& itr : commands) {
CPPUNIT_ASSERT(itr.is_dict_key());
++count;
}
CPPUNIT_ASSERT_EQUAL(size_t(1), count);
CPPUNIT_ASSERT_EQUAL(1u, prepare_count);
for (auto& itr : commands)
CPPUNIT_ASSERT(itr.is_dict_key());
CPPUNIT_ASSERT_EQUAL(1u, prepare_count);
}
void
TestCommand::test_parse_command_object() {
auto command = rpc::parse_command_object("\tstring.length=abc ");
CPPUNIT_ASSERT(command.is_dict_key());
CPPUNIT_ASSERT_EQUAL(std::string("string.length"), command.as_dict_key());
// This helper has no way to return the next-command pointer, so it must not
// silently accept a multipart command separated by ';'.
CPPUNIT_ASSERT_THROW(rpc::parse_command_object("string.length=abc;string.length=def"), torrent::input_error);
// Unlike parse_command (which parses command files), this helper handles one
// multicall command and must reject newline boundaries, including CRLF.
CPPUNIT_ASSERT_THROW(rpc::parse_command_object("string.length=abc\nstring.length=def"), torrent::input_error);
CPPUNIT_ASSERT_THROW(rpc::parse_command_object("string.length=abc\r\nstring.length=def"), torrent::input_error);
}
+4
View File
@@ -5,10 +5,14 @@ class TestCommand : public test_fixture {
CPPUNIT_TEST(test_stack); CPPUNIT_TEST(test_stack);
CPPUNIT_TEST(test_stack_double); CPPUNIT_TEST(test_stack_double);
CPPUNIT_TEST(test_preparsed_commands);
CPPUNIT_TEST(test_parse_command_object);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
public: public:
void test_stack(); void test_stack();
void test_stack_double(); void test_stack_double();
void test_preparsed_commands();
void test_parse_command_object();
}; };
+95
View File
@@ -0,0 +1,95 @@
#include "config.h"
#include "test/rpc/test_parse.h"
#include <cstdint>
#include <limits>
#include "rpc/parse.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestParse);
void
TestParse::test_whole_value_in_range() {
int64_t value = 0;
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("9223372036854775807", &value));
CPPUNIT_ASSERT_EQUAL(std::numeric_limits<int64_t>::max(), value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("-9223372036854775808", &value));
CPPUNIT_ASSERT_EQUAL(std::numeric_limits<int64_t>::min(), value);
}
void
TestParse::test_whole_value_out_of_range() {
int64_t value = 0;
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("9223372036854775808", &value));
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("-9223372036854775809", &value));
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("99999999999999999999999999", &value));
}
void
TestParse::test_whole_value_bases() {
int64_t value = 0;
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0x1f", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{31}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0X1F", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{31}, value);
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("-0x1f", &value));
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0022", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{022}, value);
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("0028", &value));
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("+022", &value));
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("-022", &value));
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("22", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{22}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("1f", &value, 16));
CPPUNIT_ASSERT_EQUAL(int64_t{31}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("0x1f", &value, 16));
CPPUNIT_ASSERT_EQUAL(int64_t{31}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("22", &value, 8));
CPPUNIT_ASSERT_EQUAL(int64_t{022}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("022", &value, 8));
CPPUNIT_ASSERT_EQUAL(int64_t{022}, value);
const char* no_digits = "0x";
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow(no_digits, &value));
CPPUNIT_ASSERT_EQUAL(no_digits + 1, rpc::parse_value_nothrow(no_digits, &value));
const char* bad_digits = "0xzz";
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow(bad_digits, &value));
CPPUNIT_ASSERT_EQUAL(bad_digits + 1, rpc::parse_value_nothrow(bad_digits, &value));
}
void
TestParse::test_whole_value_prefixes() {
int64_t value = 0;
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("+5", &value));
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow(" 5 ", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{5}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("5k", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{5120}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("yes", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{1}, value);
CPPUNIT_ASSERT(rpc::parse_whole_value_nothrow("false", &value));
CPPUNIT_ASSERT_EQUAL(int64_t{0}, value);
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("junk", &value));
CPPUNIT_ASSERT(!rpc::parse_whole_value_nothrow("", &value));
}
+18
View File
@@ -0,0 +1,18 @@
#include "test/helpers/test_fixture.h"
class TestParse : public test_fixture {
CPPUNIT_TEST_SUITE(TestParse);
CPPUNIT_TEST(test_whole_value_in_range);
CPPUNIT_TEST(test_whole_value_out_of_range);
CPPUNIT_TEST(test_whole_value_bases);
CPPUNIT_TEST(test_whole_value_prefixes);
CPPUNIT_TEST_SUITE_END();
public:
void test_whole_value_in_range();
void test_whole_value_out_of_range();
void test_whole_value_bases();
void test_whole_value_prefixes();
};
+65
View File
@@ -0,0 +1,65 @@
#include "config.h"
#include "test/src/test_command_groups.h"
#include <torrent/torrent.h>
#include "control.h"
#include "globals.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandGroups);
void initialize_command_groups();
static void
call_set(const char* value) {
torrent::Object::list_type args;
args.push_back(torrent::Object(int64_t{0}));
args.push_back(torrent::Object(std::string(value)));
rpc::commands.call_command("choke_group.up.max.set", torrent::Object::create_list_range(args.begin(), args.end()));
}
static int64_t
call_get(const char* key) {
return rpc::commands.call_command(key, torrent::Object(int64_t{0})).as_value();
}
void
TestCommandGroups::setUp() {
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has("choke_group.up.max.set"))
initialize_command_groups();
}
void
TestCommandGroups::tearDown() {
torrent::cleanup();
}
void
TestCommandGroups::test_max_unchoked_in_range() {
call_set("50");
CPPUNIT_ASSERT_EQUAL(int64_t{50}, call_get("choke_group.up.max"));
CPPUNIT_ASSERT_EQUAL(int64_t{0}, call_get("choke_group.up.max.unlimited"));
call_set("-1");
CPPUNIT_ASSERT_EQUAL(int64_t{1}, call_get("choke_group.up.max.unlimited"));
}
void
TestCommandGroups::test_max_unchoked_out_of_range() {
call_set("50");
CPPUNIT_ASSERT_THROW(call_set("4294967296"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(int64_t{50}, call_get("choke_group.up.max"));
CPPUNIT_ASSERT_THROW(call_set("-2"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(int64_t{50}, call_get("choke_group.up.max"));
}
+17
View File
@@ -0,0 +1,17 @@
#include "test/helpers/test_fixture.h"
class TestCommandGroups : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandGroups);
CPPUNIT_TEST(test_max_unchoked_in_range);
CPPUNIT_TEST(test_max_unchoked_out_of_range);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_max_unchoked_in_range();
void test_max_unchoked_out_of_range();
};
+73
View File
@@ -0,0 +1,73 @@
#include "config.h"
#include "test/src/test_command_tracker.h"
#include <torrent/runtime/network_config.h>
#include <torrent/torrent.h>
#include "control.h"
#include "globals.h"
#include "command_helpers.h"
#include "rpc/parse_commands.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandTracker);
void initialize_command_tracker();
static void
call_set(const char* value) {
rpc::commands.call_command("dht.override_port.set", torrent::Object(std::string(value)));
}
static uint16_t
override_port() {
return torrent::runtime::network_config()->override_dht_port();
}
void
TestCommandTracker::setUp() {
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
if (!rpc::commands.has("dht.override_port.set"))
initialize_command_tracker();
}
void
TestCommandTracker::tearDown() {
torrent::cleanup();
}
void
TestCommandTracker::test_dht_override_port_in_range() {
call_set("6881");
CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, override_port());
call_set("65535");
CPPUNIT_ASSERT_EQUAL(uint16_t{65535}, override_port());
}
void
TestCommandTracker::test_dht_override_port_out_of_range() {
call_set("6881");
CPPUNIT_ASSERT_THROW(call_set("70000"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, override_port());
CPPUNIT_ASSERT_THROW(call_set("-1"), torrent::input_error);
CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, override_port());
}
void
TestCommandTracker::test_checked_port_value() {
CPPUNIT_ASSERT_EQUAL(uint16_t{0}, checked_port_value(0, "test"));
CPPUNIT_ASSERT_EQUAL(uint16_t{6881}, checked_port_value(6881, "test"));
CPPUNIT_ASSERT_EQUAL(uint16_t{65535}, checked_port_value(65535, "test"));
CPPUNIT_ASSERT_THROW(checked_port_value(-1, "test"), torrent::input_error);
CPPUNIT_ASSERT_THROW(checked_port_value(65536, "test"), torrent::input_error);
CPPUNIT_ASSERT_THROW(checked_port_value(4294967296, "test"), torrent::input_error);
}
+19
View File
@@ -0,0 +1,19 @@
#include "test/helpers/test_fixture.h"
class TestCommandTracker : public test_fixture {
CPPUNIT_TEST_SUITE(TestCommandTracker);
CPPUNIT_TEST(test_dht_override_port_in_range);
CPPUNIT_TEST(test_dht_override_port_out_of_range);
CPPUNIT_TEST(test_checked_port_value);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_dht_override_port_in_range();
void test_dht_override_port_out_of_range();
void test_checked_port_value();
};
+68
View File
@@ -0,0 +1,68 @@
#include "config.h"
#include "test/src/test_download_list.h"
#include <cstdio>
#include <torrent/download.h>
#include <torrent/download_info.h>
#include <torrent/hash_string.h>
#include <torrent/object.h>
#include <torrent/torrent.h>
#include "control.h"
#include "core/download.h"
#include "globals.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestDownloadList);
static std::string
insert_download(core::DownloadList* list) {
torrent::Object info = torrent::Object::create_map();
info.insert_key("name", std::string("test_download_list"));
info.insert_key("length", int64_t{16});
info.insert_key("piece length", int64_t{262144});
info.insert_key("pieces", std::string(20, char(0)));
auto* object = new torrent::Object(torrent::Object::create_map());
object->insert_key("info", info);
auto download = torrent::download_add(object, 0);
list->insert(new core::Download(download));
char buffer[41];
for (unsigned int i = 0; i < torrent::HashString::size_data; i++)
snprintf(buffer + i * 2, 3, "%02x", static_cast<unsigned char>(download.info()->hash()[i]));
return std::string(buffer, 40);
}
void
TestDownloadList::setUp() {
torrent::initialize_main_thread();
torrent::initialize();
if (control == nullptr)
control = new Control;
m_hex = insert_download(&m_list);
}
void
TestDownloadList::tearDown() {
m_list.clear();
torrent::cleanup();
}
void
TestDownloadList::test_find_hex() {
CPPUNIT_ASSERT(m_list.find_hex(m_hex.c_str()) != m_list.end());
}
void
TestDownloadList::test_find_hex_wrong_length() {
CPPUNIT_ASSERT(m_list.find_hex((m_hex + "f").c_str()) == m_list.end());
CPPUNIT_ASSERT(m_list.find_hex((m_hex + m_hex).c_str()) == m_list.end());
CPPUNIT_ASSERT(m_list.find_hex(m_hex.substr(0, 39).c_str()) == m_list.end());
}
+24
View File
@@ -0,0 +1,24 @@
#include <string>
#include "core/download_list.h"
#include "test/helpers/test_fixture.h"
class TestDownloadList : public test_fixture {
CPPUNIT_TEST_SUITE(TestDownloadList);
CPPUNIT_TEST(test_find_hex);
CPPUNIT_TEST(test_find_hex_wrong_length);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_find_hex();
void test_find_hex_wrong_length();
private:
core::DownloadList m_list;
std::string m_hex;
};
+77
View File
@@ -0,0 +1,77 @@
#include "config.h"
#include "test/src/test_input_path_input.h"
#include <cstdlib>
#include <sys/stat.h>
#include <unistd.h>
#include "input/path_input.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestInputPathInput);
void
TestInputPathInput::setUp() {
test_fixture::setUp();
char temp_dir[] = "/tmp/rtorrent_test_path_input_XXXXXX";
CPPUNIT_ASSERT(mkdtemp(temp_dir) != nullptr);
m_temp_dir = temp_dir;
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/alpha").c_str(), 0755));
CPPUNIT_ASSERT_EQUAL(0, mkdir((m_temp_dir + "/alpha/beta").c_str(), 0755));
}
void
TestInputPathInput::tearDown() {
rmdir((m_temp_dir + "/alpha/beta").c_str());
rmdir((m_temp_dir + "/alpha").c_str());
rmdir(m_temp_dir.c_str());
test_fixture::tearDown();
}
// Tab at the end of the line completes the trailing component.
void
TestInputPathInput::test_complete_at_end() {
input::PathInput input;
input.str() = m_temp_dir + "/al";
input.set_pos(input.str().size());
CPPUNIT_ASSERT(input.pressed('\t'));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/alpha/", input.str());
CPPUNIT_ASSERT_EQUAL(input.str().size(), input.get_pos());
}
// The same completion with the cursor left inside the line. Only the text up to
// the cursor selects the entry, and the rest of the line is discarded.
void
TestInputPathInput::test_complete_mid_line() {
input::PathInput input;
input.str() = m_temp_dir + "/alZZZ";
input.set_pos(m_temp_dir.size() + 3);
CPPUNIT_ASSERT(input.pressed('\t'));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/alpha/", input.str());
CPPUNIT_ASSERT_EQUAL(input.str().size(), input.get_pos());
}
// A cursor resting on a separator keeps completing the component after it.
void
TestInputPathInput::test_cursor_on_separator() {
input::PathInput input;
input.str() = m_temp_dir + "/alpha/be";
input.set_pos(m_temp_dir.size() + 6);
CPPUNIT_ASSERT(input.pressed('\t'));
CPPUNIT_ASSERT_EQUAL(m_temp_dir + "/alpha/beta/", input.str());
CPPUNIT_ASSERT_EQUAL(input.str().size(), input.get_pos());
}
+24
View File
@@ -0,0 +1,24 @@
#include "test/helpers/test_fixture.h"
#include <string>
class TestInputPathInput : public test_fixture {
CPPUNIT_TEST_SUITE(TestInputPathInput);
CPPUNIT_TEST(test_complete_at_end);
CPPUNIT_TEST(test_complete_mid_line);
CPPUNIT_TEST(test_cursor_on_separator);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_complete_at_end();
void test_complete_mid_line();
void test_cursor_on_separator();
private:
std::string m_temp_dir;
};
+94
View File
@@ -0,0 +1,94 @@
#include "config.h"
#include "test/src/test_session_commit.h"
#include <cstdlib>
#include <dirent.h>
#include <fstream>
#include <sstream>
#include <sys/stat.h>
#include <unistd.h>
#include "session/download_storer.h"
CPPUNIT_TEST_SUITE_REGISTRATION(TestSessionCommit);
namespace {
const char* entry_name = "0123456789ABCDEF0123456789ABCDEF01234567.torrent";
std::string
read_file(const std::string& path) {
std::ifstream file(path.c_str());
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
void
remove_directory(const std::string& path) {
DIR* d = ::opendir(path.c_str());
if (d == NULL)
return;
struct dirent* entry;
while ((entry = ::readdir(d)) != NULL) {
if (entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0')))
continue;
::unlink((path + "/" + entry->d_name).c_str());
}
::closedir(d);
::rmdir(path.c_str());
}
} // namespace
void
TestSessionCommit::setUp() {
test_fixture::setUp();
char temp_dir[] = "/tmp/rtorrent_test_commit_XXXXXX";
CPPUNIT_ASSERT(mkdtemp(temp_dir) != nullptr);
m_session_dir = temp_dir;
}
void
TestSessionCommit::tearDown() {
remove_directory(m_session_dir);
test_fixture::tearDown();
}
void
TestSessionCommit::commit_and_verify(bool use_fsyncdisk) {
auto path = m_session_dir + "/" + entry_name;
std::stringstream torrent_stream("torrent-data");
std::stringstream rtorrent_stream("rtorrent-data");
std::stringstream libtorrent_stream("libtorrent-data");
session::DownloadStorer::save_and_move_streams(path, use_fsyncdisk, &torrent_stream, &rtorrent_stream, &libtorrent_stream);
CPPUNIT_ASSERT_EQUAL(std::string("torrent-data"), read_file(path));
CPPUNIT_ASSERT_EQUAL(std::string("rtorrent-data"), read_file(path + ".rtorrent"));
CPPUNIT_ASSERT_EQUAL(std::string("libtorrent-data"), read_file(path + ".libtorrent_resume"));
struct stat st;
CPPUNIT_ASSERT(::stat((path + ".new").c_str(), &st) == -1);
}
void
TestSessionCommit::test_commit_publishes_all_three_files() {
commit_and_verify(false);
}
void
TestSessionCommit::test_commit_with_fsync_publishes_all_three_files() {
commit_and_verify(true);
}
+24
View File
@@ -0,0 +1,24 @@
#include "test/helpers/test_fixture.h"
#include <string>
class TestSessionCommit : public test_fixture {
CPPUNIT_TEST_SUITE(TestSessionCommit);
CPPUNIT_TEST(test_commit_publishes_all_three_files);
CPPUNIT_TEST(test_commit_with_fsync_publishes_all_three_files);
CPPUNIT_TEST_SUITE_END();
public:
void setUp();
void tearDown();
void test_commit_publishes_all_three_files();
void test_commit_with_fsync_publishes_all_three_files();
private:
void commit_and_verify(bool use_fsyncdisk);
std::string m_session_dir;
};