mirror of
https://github.com/rakshasa/rtorrent.git
synced 2026-08-17 07:32:32 +00:00
Merge branch 'master' into feature/ipc-worker
This commit is contained in:
@@ -53,8 +53,8 @@ rc.execute.throw(
|
||||
cfg.watch..'/start'}))
|
||||
|
||||
-- Listening port for incoming peer traffic (fixed; you can also randomize it)
|
||||
rc.network.port_range = '50000-50000'
|
||||
rc.network.port_random = false
|
||||
rc.network.listen.port.range = '50000-50000'
|
||||
rc.network.listen.port.random = false
|
||||
|
||||
-- Tracker-less torrent and UDP tracker support
|
||||
-- (conservative settings for 'private' trackers, change for 'public')
|
||||
|
||||
@@ -128,7 +128,7 @@ apply_remove_untied() {
|
||||
}
|
||||
|
||||
torrent::Object
|
||||
apply_schedule(const torrent::Object::list_type& args) {
|
||||
apply_schedule(const torrent::Object::list_type& args, bool if_absent) {
|
||||
if (args.size() != 4)
|
||||
throw torrent::input_error("Wrong number of arguments.");
|
||||
|
||||
@@ -138,6 +138,9 @@ apply_schedule(const torrent::Object::list_type& args) {
|
||||
auto& arg2 = (itr++)->as_string();
|
||||
auto& arg3 = (itr++)->as_string();
|
||||
|
||||
if (if_absent && control->command_scheduler()->find(arg1) != control->command_scheduler()->end())
|
||||
return torrent::Object();
|
||||
|
||||
control->command_scheduler()->parse(arg1, arg2, arg3, *itr);
|
||||
|
||||
return torrent::Object();
|
||||
@@ -340,7 +343,8 @@ initialize_command_events() {
|
||||
CMD2_ANY ("close_untied", [](auto, auto) { return apply_close_untied(); });
|
||||
CMD2_ANY ("remove_untied", [](auto, auto) { return apply_remove_untied(); });
|
||||
|
||||
CMD2_ANY_LIST ("schedule", [](auto, auto& args) { return apply_schedule(args); });
|
||||
CMD2_ANY_LIST ("schedule", [](auto, auto& args) { return apply_schedule(args, false); });
|
||||
CMD2_ANY_LIST ("schedule.if_absent", [](auto, auto& args) { return apply_schedule(args, true); });
|
||||
CMD2_ANY_STRING_V("schedule.remove", [](auto, auto& str) { return control->command_scheduler()->erase_str(str); });
|
||||
|
||||
CMD2_ANY_STRING_V("import", [](auto, auto& str) { return apply_import(str); });
|
||||
|
||||
+11
-9
@@ -1,5 +1,7 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <torrent/download/resource_manager.h>
|
||||
#include <torrent/download/choke_group.h>
|
||||
#include <torrent/download/choke_queue.h>
|
||||
@@ -111,7 +113,7 @@ apply_cg_all_update_balance(bool is_up) {
|
||||
//
|
||||
#else
|
||||
|
||||
std::vector<torrent::choke_group*> cg_list_hack;
|
||||
std::vector<std::unique_ptr<torrent::choke_group>> cg_list_hack;
|
||||
|
||||
int64_t
|
||||
cg_get_index(const torrent::Object& raw_args) {
|
||||
@@ -121,7 +123,7 @@ cg_get_index(const torrent::Object& raw_args) {
|
||||
|
||||
if (arg.is_string()) {
|
||||
if (!rpc::parse_whole_value_nothrow(arg.as_string().c_str(), &index)) {
|
||||
auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](torrent::choke_group* cg) { return arg.as_string() == cg->name(); });
|
||||
auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](const auto& cg) { return arg.as_string() == cg->name(); });
|
||||
|
||||
if (itr == cg_list_hack.end())
|
||||
throw torrent::input_error("Choke group not found.");
|
||||
@@ -149,7 +151,7 @@ cg_get_group(const torrent::Object& raw_args) {
|
||||
if ((size_t)index >= cg_list_hack.size())
|
||||
throw torrent::input_error("Choke group not found.");
|
||||
|
||||
return cg_list_hack.at(index);
|
||||
return cg_list_hack.at(index).get();
|
||||
}
|
||||
|
||||
int64_t cg_d_group(core::Download* download) { return download->group(); }
|
||||
@@ -162,7 +164,7 @@ torrent::Object
|
||||
apply_cg_list() {
|
||||
torrent::Object::list_type result;
|
||||
|
||||
for (auto itr : cg_list_hack)
|
||||
for (const auto& itr : cg_list_hack)
|
||||
result.push_back(itr->name());
|
||||
|
||||
return torrent::Object::from_list(result);
|
||||
@@ -180,10 +182,10 @@ apply_cg_insert(const std::string& arg) {
|
||||
if (rpc::parse_whole_value_nothrow(arg.c_str(), &dummy))
|
||||
throw torrent::input_error("Cannot use a value string as choke group name.");
|
||||
|
||||
if (arg.empty() || std::any_of(cg_list_hack.begin(), cg_list_hack.end(), [&arg](auto cg) { return arg == cg->name(); }))
|
||||
if (arg.empty() || std::any_of(cg_list_hack.begin(), cg_list_hack.end(), [&arg](const auto& cg) { return arg == cg->name(); }))
|
||||
throw torrent::input_error("Duplicate name for choke group.");
|
||||
|
||||
cg_list_hack.push_back(new torrent::choke_group());
|
||||
cg_list_hack.push_back(std::make_unique<torrent::choke_group>());
|
||||
cg_list_hack.back()->set_name(arg);
|
||||
|
||||
cg_list_hack.back()->up_queue()->set_heuristics(torrent::HEURISTICS_UPLOAD_LEECH);
|
||||
@@ -194,7 +196,7 @@ apply_cg_insert(const std::string& arg) {
|
||||
|
||||
torrent::Object
|
||||
apply_cg_index_of(const std::string& arg) {
|
||||
auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](torrent::choke_group* cg) { return arg == cg->name(); });
|
||||
auto itr = std::find_if(cg_list_hack.begin(), cg_list_hack.end(), [&arg](const auto& cg) { return arg == cg->name(); });
|
||||
|
||||
if (itr == cg_list_hack.end())
|
||||
throw torrent::input_error("Choke group not found.");
|
||||
@@ -206,7 +208,7 @@ torrent::Object
|
||||
apply_cg_all_update_balance(bool is_up) {
|
||||
LT_LOG_SUBSYSTEM("apply update balance: hack is_up:%i", (int)is_up);
|
||||
|
||||
for (auto itr : cg_list_hack) {
|
||||
for (const auto& itr : cg_list_hack) {
|
||||
if (is_up)
|
||||
itr->up_queue()->balance();
|
||||
else
|
||||
@@ -346,7 +348,7 @@ initialize_command_groups() {
|
||||
#else
|
||||
apply_cg_insert("default");
|
||||
|
||||
CMD_ANY ("choke_group.size", std::bind(&std::vector<torrent::choke_group*>::size, cg_list_hack));
|
||||
CMD_ANY ("choke_group.size", [](auto, auto) { return (int64_t)cg_list_hack.size(); });
|
||||
CMD_ANY_STRING ("choke_group.index_of", std::bind(&apply_cg_index_of, std::placeholders::_2));
|
||||
#endif
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int64_t max_string_pad_size = 1 << 14;
|
||||
|
||||
const std::string whitespace_characters = " \t\n\r\f\v";
|
||||
|
||||
// The byte offset of every utf-8 character in 'text', terminated by the offset
|
||||
@@ -249,10 +251,14 @@ apply_string_pad(const char* name, const torrent::Object::list_type& args, bool
|
||||
if (pad_size <= text_length || padding.empty())
|
||||
return text;
|
||||
|
||||
if (pad_size - text_length > max_string_pad_size)
|
||||
throw torrent::input_error(std::string(name) + ": padding is too large.");
|
||||
|
||||
auto padding_offsets = utf8_offsets(padding);
|
||||
auto padding_length = static_cast<int64_t>(padding_offsets.size() - 1);
|
||||
|
||||
std::string result;
|
||||
result.reserve(pad_size - text_length);
|
||||
|
||||
for (int64_t i = 0; i < pad_size - text_length; i++) {
|
||||
auto index = static_cast<size_t>(i % padding_length);
|
||||
|
||||
+2
-5
@@ -258,11 +258,8 @@ JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
|
||||
|
||||
return callback(response_str.c_str(), response_str.size());
|
||||
|
||||
} catch (json::parse_error& e) {
|
||||
auto err_str = json_error(JSONRPC_PARSE_ERROR, e.what(), nullptr).dump(-1, ' ', false, json::error_handler_t::replace);
|
||||
return callback(err_str.c_str(), err_str.size());
|
||||
} catch (json::type_error& e) {
|
||||
// Type errors may be caused by invalid UTF-8 strings in exception strings, hence the ::replace
|
||||
} catch (json::exception& e) {
|
||||
// Exception strings may contain invalid UTF-8, hence the ::replace
|
||||
auto err_str = json_error(JSONRPC_PARSE_ERROR, e.what(), nullptr).dump(-1, ' ', false, json::error_handler_t::replace);
|
||||
return callback(err_str.c_str(), err_str.size());
|
||||
}
|
||||
|
||||
@@ -93,8 +93,12 @@ SCgiTask::event_read() {
|
||||
if (m_content_length == 0)
|
||||
read_length--;
|
||||
|
||||
if (read_length <= 0)
|
||||
if (read_length <= 0) {
|
||||
if (m_content_length == 0)
|
||||
return close();
|
||||
|
||||
throw torrent::internal_error("SCgiTask::event_read() no space in buffer for event_read.");
|
||||
}
|
||||
|
||||
int bytes = ::recv(file_descriptor(), m_buffer.data() + m_position, read_length, 0);
|
||||
|
||||
|
||||
@@ -49,13 +49,23 @@ element_access(const tinyxml2::XMLElement* elem, std::initializer_list<std::stri
|
||||
return result;
|
||||
}
|
||||
|
||||
const char*
|
||||
element_text_value(const tinyxml2::XMLNode* node) {
|
||||
auto text = node->ToText();
|
||||
|
||||
if (text == nullptr)
|
||||
throw rpc_error(XMLRPC_TYPE_ERROR, "expected a text value");
|
||||
|
||||
return text->Value();
|
||||
}
|
||||
|
||||
long long
|
||||
element_to_int(const tinyxml2::XMLNode* elem) {
|
||||
char* pos;
|
||||
if (elem->FirstChild() == nullptr) {
|
||||
throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse empty integer");
|
||||
}
|
||||
auto str = elem->FirstChild()->ToText()->Value();
|
||||
auto str = element_text_value(elem->FirstChild());
|
||||
auto result = std::strtoll(str, &pos, 10);
|
||||
if (pos == str || *pos != '\0')
|
||||
throw rpc_error(XMLRPC_TYPE_ERROR, "unable to parse integer value");
|
||||
@@ -85,7 +95,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
|
||||
if (child_element == nullptr)
|
||||
return torrent::Object("");
|
||||
|
||||
return torrent::Object(child_element->ToText()->Value());
|
||||
return torrent::Object(element_text_value(child_element));
|
||||
|
||||
} else if (std::strncmp(root_type, "int", sizeof("int")) == 0 ||
|
||||
std::strncmp(root_type, "i4", sizeof("i4")) == 0 ||
|
||||
@@ -98,7 +108,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
|
||||
if (child_element == nullptr)
|
||||
throw rpc_error(XMLRPC_TYPE_ERROR, "empty boolean element");
|
||||
|
||||
auto boolean_text = std::string(child_element->ToText()->Value());
|
||||
auto boolean_text = std::string(element_text_value(child_element));
|
||||
|
||||
if (boolean_text == "1")
|
||||
return torrent::Object((int64_t)1);
|
||||
@@ -129,7 +139,12 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
|
||||
if (name_element == nullptr)
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, "struct member missing name element");
|
||||
|
||||
map[name_element->GetText()] = std::move(xml_value_to_object(child->FirstChildElement("value")));
|
||||
auto name_text = name_element->GetText();
|
||||
|
||||
if (name_text == nullptr)
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, "struct member has an empty name element");
|
||||
|
||||
map[name_text] = std::move(xml_value_to_object(child->FirstChildElement("value")));
|
||||
}
|
||||
|
||||
return map_raw;
|
||||
@@ -140,7 +155,7 @@ xml_value_to_object(const tinyxml2::XMLNode* elem) {
|
||||
if (child_element == nullptr)
|
||||
return torrent::Object("");
|
||||
|
||||
return torrent::Object(utils::decode_base64(utils::remove_newlines(child_element->ToText()->Value())));
|
||||
return torrent::Object(utils::decode_base64(utils::remove_newlines(element_text_value(child_element))));
|
||||
|
||||
} else {
|
||||
throw rpc_error(XMLRPC_INTERNAL_ERROR, "received unsupported value type: " + std::string(root_type));
|
||||
@@ -300,6 +315,9 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer
|
||||
if (doc->FirstChildElement("methodCall")->FirstChildElement("methodName") == nullptr)
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element not found");
|
||||
auto method_name = doc->FirstChildElement("methodCall")->FirstChildElement("methodName")->GetText();
|
||||
|
||||
if (method_name == nullptr)
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, "methodName element is empty");
|
||||
torrent::Object result;
|
||||
|
||||
// Add a shim here for system.multicall to allow better code reuse, and
|
||||
@@ -310,6 +328,9 @@ process_document(const tinyxml2::XMLDocument* doc, tinyxml2::XMLPrinter* printer
|
||||
auto parent_elements = element_access(doc->RootElement(), {"params", "param", "value", "array", "data"});
|
||||
for (auto child = parent_elements->FirstChildElement("value"); child; child = child->NextSiblingElement("value")) {
|
||||
auto sub_method_name = element_access(child, {"struct", "member", "value", "string"})->GetText();
|
||||
|
||||
if (sub_method_name == nullptr)
|
||||
throw rpc_error(XMLRPC_PARSE_ERROR, "multicall methodName element is empty");
|
||||
// If sub_params ends up a nullptr at the end of this if-chian,
|
||||
// execute_command will turn it into an empty list
|
||||
auto sub_params = element_access(child, {"struct", "member"});
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ parse_main_options(int argc, char** argv) {
|
||||
|
||||
optionParser.insert_option('b', [](auto& arg) { rpc::call_command_set_string("network.bind_address.set", arg); });
|
||||
optionParser.insert_option('d', [](auto& arg) { rpc::call_command_set_string("directory.default.set", arg); });
|
||||
optionParser.insert_option('i', [](auto& arg) { rpc::call_command_set_string("ip", arg); });
|
||||
optionParser.insert_option('i', [](auto& arg) { rpc::call_command_set_string("network.local_address.set", arg); });
|
||||
optionParser.insert_option('p', [](auto& arg) { rpc::call_command_set_string("network.listen.port.range.set", arg); });
|
||||
optionParser.insert_option('s', [](auto& arg) { rpc::call_command_set_string("session", arg); });
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ DownloadList::unfocus_download(core::Download* d) {
|
||||
if (m_state == DISPLAY_DOWNLOAD && d == static_cast<Download*>(m_uiArray[DISPLAY_DOWNLOAD])->download())
|
||||
activate_display(DISPLAY_DOWNLOAD_LIST);
|
||||
|
||||
if (*current_view()->focus() == d && current_view()->focus() < current_view()->end_visible())
|
||||
if (current_view()->focus() < current_view()->end_visible() && *current_view()->focus() == d)
|
||||
current_view()->next_focus();
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ rtorrent_Test_Rpc_SOURCES = $(rtorrent_Test_Common) \
|
||||
rpc/test_command.h \
|
||||
rpc/test_command_map.cc \
|
||||
rpc/test_command_map.h \
|
||||
rpc/test_command_scheduler.cc \
|
||||
rpc/test_command_scheduler.h \
|
||||
rpc/test_jsonrpc.cc \
|
||||
rpc/test_jsonrpc.h \
|
||||
rpc/test_xmlrpc.cc \
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "test/rpc/test_command_scheduler.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "rpc/command_scheduler.h"
|
||||
#include "rpc/command_scheduler_item.h"
|
||||
#include "torrent/object.h"
|
||||
|
||||
CPPUNIT_TEST_SUITE_REGISTRATION(TestCommandScheduler);
|
||||
|
||||
namespace {
|
||||
|
||||
const torrent::Object test_command = torrent::Object(std::string("print=scheduled"));
|
||||
|
||||
std::chrono::microseconds
|
||||
time_scheduled(rpc::CommandScheduler& scheduler, const std::string& key) {
|
||||
auto itr = scheduler.find(key);
|
||||
|
||||
CPPUNIT_ASSERT(itr != scheduler.end());
|
||||
|
||||
return (*itr)->time_scheduled();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandScheduler::setUp() {
|
||||
TestFixtureWithMainThread::setUp();
|
||||
|
||||
m_main_thread->test_set_cached_time(std::chrono::seconds(0));
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandScheduler::tearDown() {
|
||||
TestFixtureWithMainThread::tearDown();
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandScheduler::test_parse_rearms_existing_key() {
|
||||
rpc::CommandScheduler scheduler;
|
||||
|
||||
scheduler.parse("key", "3600", "3600", test_command);
|
||||
|
||||
auto first = time_scheduled(scheduler, "key");
|
||||
|
||||
m_main_thread->test_add_cached_time(std::chrono::seconds(600));
|
||||
scheduler.parse("key", "3600", "3600", test_command);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(size_t{1}, scheduler.size());
|
||||
CPPUNIT_ASSERT(time_scheduled(scheduler, "key") == first + std::chrono::seconds(600));
|
||||
}
|
||||
|
||||
void
|
||||
TestCommandScheduler::test_find_locates_a_scheduled_key() {
|
||||
rpc::CommandScheduler scheduler;
|
||||
|
||||
CPPUNIT_ASSERT(scheduler.find("key") == scheduler.end());
|
||||
|
||||
scheduler.parse("key", "3600", "3600", test_command);
|
||||
|
||||
CPPUNIT_ASSERT(scheduler.find("key") != scheduler.end());
|
||||
CPPUNIT_ASSERT(scheduler.find("other") == scheduler.end());
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "test/helpers/test_main_thread.h"
|
||||
|
||||
class TestCommandScheduler : public TestFixtureWithMainThread {
|
||||
CPPUNIT_TEST_SUITE(TestCommandScheduler);
|
||||
|
||||
CPPUNIT_TEST(test_parse_rearms_existing_key);
|
||||
CPPUNIT_TEST(test_find_locates_a_scheduled_key);
|
||||
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
public:
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
void test_parse_rearms_existing_key();
|
||||
void test_find_locates_a_scheduled_key();
|
||||
};
|
||||
Reference in New Issue
Block a user