#include "config.h" #include #include #include #include #include #include "ui/root.h" #include "rpc/parse.h" #include "rpc/parse_commands.h" #include "globals.h" #include "control.h" #include "command_helpers.h" // For cg_d_group. #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 // working parts present. #define USE_CHOKE_GROUP 0 #if USE_CHOKE_GROUP int64_t cg_get_index(const torrent::Object& raw_args) { const torrent::Object& arg = (raw_args.is_list() && !raw_args.as_list().empty()) ? raw_args.as_list().front() : raw_args; int64_t index = 0; if (arg.is_string()) { if (!rpc::parse_whole_value_nothrow(arg.as_string().c_str(), &index)) return torrent::resource_manager()->group_index_of(arg.as_string()); } else { index = arg.as_value(); } if (index < 0) index = (int64_t)torrent::resource_manager()->group_size() + index; return std::min(index, torrent::resource_manager()->group_size()); } torrent::choke_group* cg_get_group(const torrent::Object& raw_args) { return torrent::resource_manager()->group_at(cg_get_index(raw_args)); } int64_t cg_d_group(core::Download* download) { return torrent::resource_manager()->entry_at(download->main()).group(); } const std::string& cg_d_group_name(core::Download* download) { return torrent::resource_manager()->group_at(torrent::resource_manager()->entry_at(download->main()).group())->name(); } void cg_d_group_set(core::Download* download, const torrent::Object& arg) { torrent::resource_manager()->set_group(torrent::resource_manager()->find_throw(download->main()), cg_get_index(arg)); } torrent::Object apply_cg_list() { torrent::Object::list_type result; for (torrent::ResourceManager::group_iterator itr = torrent::resource_manager()->group_begin(), last = torrent::resource_manager()->group_end(); itr != last; itr++) result.push_back((*itr)->name()); return torrent::Object::from_list(result); } torrent::Object apply_cg_insert(const std::string& arg) { int64_t dummy; if (rpc::parse_whole_value_nothrow(arg.c_str(), &dummy)) throw torrent::input_error("Cannot use a value string as choke group name."); torrent::resource_manager()->push_group(arg); return torrent::Object(); } torrent::Object apply_cg_all_update_balance(bool is_up) { LT_LOG_SUBSYSTEM("apply update balance: resource_manager is_up:%i", (int)is_up); for (torrent::ResourceManager::group_iterator itr = torrent::resource_manager()->group_begin(), last = torrent::resource_manager()->group_end(); itr != last; itr++) { if (is_up) itr->up_queue()->balance(); else itr->down_queue()->balance(); } return torrent::Object(); } // // The hacked version: // #else std::vector cg_list_hack; int64_t cg_get_index(const torrent::Object& raw_args) { const torrent::Object& arg = (raw_args.is_list() && !raw_args.as_list().empty()) ? raw_args.as_list().front() : raw_args; int64_t index = 0; 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(); }); if (itr == cg_list_hack.end()) throw torrent::input_error("Choke group not found."); return std::distance(cg_list_hack.begin(), itr); } } else { index = arg.as_value(); } if (index < 0) index = (int64_t)cg_list_hack.size() + index; if ((size_t)index >= cg_list_hack.size()) throw torrent::input_error("Choke group not found."); return index; } torrent::choke_group* cg_get_group(const torrent::Object& raw_args) { int64_t index = cg_get_index(raw_args); if ((size_t)index >= cg_list_hack.size()) throw torrent::input_error("Choke group not found."); return cg_list_hack.at(index); } int64_t cg_d_group(core::Download* download) { return download->group(); } const std::string& cg_d_group_name(core::Download* download) { return cg_list_hack.at(download->group())->name(); } void cg_d_group_set(core::Download* download, const torrent::Object& arg) { download->set_group(cg_get_index(arg)); } torrent::Object apply_cg_list() { torrent::Object::list_type result; for (auto itr : cg_list_hack) result.push_back(itr->name()); return torrent::Object::from_list(result); } int cg_get_can_unchoke(torrent::choke_queue* cq) { return cq->max_unchoked_signed() - (int)cq->size_unchoked(); } torrent::Object apply_cg_insert(const std::string& arg) { int64_t dummy; 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(); })) throw torrent::input_error("Duplicate name for choke group."); cg_list_hack.push_back(new torrent::choke_group()); cg_list_hack.back()->set_name(arg); cg_list_hack.back()->up_queue()->set_heuristics(torrent::choke_queue::HEURISTICS_UPLOAD_LEECH); cg_list_hack.back()->down_queue()->set_heuristics(torrent::choke_queue::HEURISTICS_DOWNLOAD_LEECH); return torrent::Object(); } 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(); }); if (itr == cg_list_hack.end()) throw torrent::input_error("Choke group not found."); return std::distance(cg_list_hack.begin(), itr); } 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) { if (is_up) itr->up_queue()->balance(); else itr->down_queue()->balance(); } return torrent::Object(); } // // End of choke group hack. // #endif torrent::Object apply_cg_max_set(const torrent::Object::list_type& args, bool is_up) { if (args.size() != 2) throw torrent::input_error("Incorrect number of arguments."); int64_t second_arg = 0; rpc::parse_whole_value(args.back().as_string().c_str(), &second_arg); if (is_up) cg_get_group(args.front())->up_queue()->set_max_unchoked(second_arg); else cg_get_group(args.front())->down_queue()->set_max_unchoked(second_arg); return torrent::Object(); } torrent::Object apply_cg_heuristics_set(const torrent::Object::list_type& args, bool is_up) { if (args.size() != 2) throw torrent::input_error("Incorrect number of arguments."); int t = torrent::option_find_string(is_up ? torrent::OPTION_CHOKE_HEURISTICS_UPLOAD : torrent::OPTION_CHOKE_HEURISTICS_DOWNLOAD, args.back().as_string().c_str()); if (is_up) cg_get_group(args.front())->up_queue()->set_heuristics((torrent::choke_queue::heuristics_enum)t); else cg_get_group(args.front())->down_queue()->set_heuristics((torrent::choke_queue::heuristics_enum)t); return torrent::Object(); } torrent::Object apply_cg_tracker_mode_set(const torrent::Object::list_type& args) { if (args.size() != 2) throw torrent::input_error("Incorrect number of arguments."); int t = torrent::option_find_string(torrent::OPTION_TRACKER_MODE, args.back().as_string().c_str()); cg_get_group(args.front())->set_tracker_mode((torrent::choke_group::tracker_mode_enum)t); return torrent::Object(); } #define CG_GROUP_AT() std::bind(&cg_get_group, std::placeholders::_2) #define CHOKE_GROUP(direction) std::bind(direction, CG_GROUP_AT()) /* -> '0'..'(choke_group.size)' -> '-1'..'-(choke_group.size)' -> '' (choke_group.list) -> List of group names. (choke_group.size) -> Number of groups. (choke_group.insert,"group_name") Adds a new group with default settings, use index '-1' to accessing it immediately afterwards. (choke_group.index_of,"group_name") -> Throws if the group name was not found. (choke_group.general.size,) -> Number of torrents in this group. (choke_group.tracker.mode,) -> "tracker_mode" (choke_group.tracker.mode.set,,"tracker_mode") Decide on how aggressive a tracker should be, see 'strings.tracker_mode' for list of available options (choke_group.up.rate,) -> (choke_group.down.rate,) -> Upload / download rate for the aggregate of all torrents in this particular group. (choke_group.up.max,) -> (choke_group.up.max.unlimited,) -> (choke_group.up.max.set,, ) (choke_group.down.max,) -> (choke_group.down.max.unlimited,) -> (choke_group.down.max.set,, ) -> (choke_group.up.queued,) -> (choke_group.up.unchoked,) -> (choke_group.down.total,) -> (choke_group.down.queued,) -> (choke_group.down.unchoked,) -> (choke_group.up.heuristics,) -> "heuristics" (choke_group.up.heuristics.set,,"heuristics") (choke_group.down.heuristics,) -> "heuristics" (choke_group.down.heuristics.set,,"heuristics") Heuristics are used for deciding what peers to choke and unchoke, see 'strings.choke_heuristics{,_download,_upload}' for a list of available options. (d.group) -> (d.group.name) -> "choke_group_name" (d.group.set,) */ void initialize_command_groups() { CMD2_ANY ("choke_group.list", std::bind(&apply_cg_list)); CMD2_ANY_STRING ("choke_group.insert", std::bind(&apply_cg_insert, std::placeholders::_2)); #if USE_CHOKE_GROUP CMD2_ANY ("choke_group.size", std::bind(&torrent::ResourceManager::group_size, torrent::resource_manager())); CMD2_ANY_STRING ("choke_group.index_of", std::bind(&torrent::ResourceManager::group_index_of, torrent::resource_manager(), std::placeholders::_2)); #else apply_cg_insert("default"); CMD2_ANY ("choke_group.size", std::bind(&std::vector::size, cg_list_hack)); CMD2_ANY_STRING ("choke_group.index_of", std::bind(&apply_cg_index_of, std::placeholders::_2)); #endif // Commands specific for a group. Supports as the first argument the // name, the index or a negative index. CMD2_ANY ("choke_group.general.size", std::bind(&torrent::choke_group::size, CG_GROUP_AT())); CMD2_ANY ("choke_group.tracker.mode", std::bind(&torrent::option_as_string, torrent::OPTION_TRACKER_MODE, std::bind(&torrent::choke_group::tracker_mode, CG_GROUP_AT()))); CMD2_ANY_LIST ("choke_group.tracker.mode.set", std::bind(&apply_cg_tracker_mode_set, std::placeholders::_2)); CMD2_ANY ("choke_group.all.up.update_balance", std::bind(&apply_cg_all_update_balance, true)); CMD2_ANY ("choke_group.all.down.update_balance", std::bind(&apply_cg_all_update_balance, false)); CMD2_ANY ("choke_group.up.rate", std::bind(&torrent::choke_group::up_rate, CG_GROUP_AT())); CMD2_ANY ("choke_group.down.rate", std::bind(&torrent::choke_group::down_rate, CG_GROUP_AT())); CMD2_ANY ("choke_group.up.max.unlimited", std::bind(&torrent::choke_queue::is_unlimited, CHOKE_GROUP(&torrent::choke_group::up_queue))); CMD2_ANY ("choke_group.up.max", std::bind(&torrent::choke_queue::max_unchoked_signed, CHOKE_GROUP(&torrent::choke_group::up_queue))); CMD2_ANY_LIST ("choke_group.up.max.set", std::bind(&apply_cg_max_set, std::placeholders::_2, true)); CMD2_ANY ("choke_group.up.total", std::bind(&torrent::choke_queue::size_total, CHOKE_GROUP(&torrent::choke_group::up_queue))); CMD2_ANY ("choke_group.up.queued", std::bind(&torrent::choke_queue::size_queued, CHOKE_GROUP(&torrent::choke_group::up_queue))); CMD2_ANY ("choke_group.up.unchoked", std::bind(&torrent::choke_queue::size_unchoked, CHOKE_GROUP(&torrent::choke_group::up_queue))); CMD2_ANY ("choke_group.up.heuristics", std::bind(&torrent::option_as_string, torrent::OPTION_CHOKE_HEURISTICS, std::bind(&torrent::choke_queue::heuristics, CHOKE_GROUP(&torrent::choke_group::up_queue)))); CMD2_ANY_LIST ("choke_group.up.heuristics.set", std::bind(&apply_cg_heuristics_set, std::placeholders::_2, true)); CMD2_ANY ("choke_group.down.max.unlimited", std::bind(&torrent::choke_queue::is_unlimited, CHOKE_GROUP(&torrent::choke_group::down_queue))); CMD2_ANY ("choke_group.down.max", std::bind(&torrent::choke_queue::max_unchoked_signed, CHOKE_GROUP(&torrent::choke_group::down_queue))); CMD2_ANY_LIST ("choke_group.down.max.set", std::bind(&apply_cg_max_set, std::placeholders::_2, false)); CMD2_ANY ("choke_group.down.total", std::bind(&torrent::choke_queue::size_total, CHOKE_GROUP(&torrent::choke_group::down_queue))); CMD2_ANY ("choke_group.down.queued", std::bind(&torrent::choke_queue::size_queued, CHOKE_GROUP(&torrent::choke_group::down_queue))); CMD2_ANY ("choke_group.down.unchoked", std::bind(&torrent::choke_queue::size_unchoked, CHOKE_GROUP(&torrent::choke_group::down_queue))); CMD2_ANY ("choke_group.down.heuristics", std::bind(&torrent::option_as_string, torrent::OPTION_CHOKE_HEURISTICS, std::bind(&torrent::choke_queue::heuristics, CHOKE_GROUP(&torrent::choke_group::down_queue)))); CMD2_ANY_LIST ("choke_group.down.heuristics.set", std::bind(&apply_cg_heuristics_set, std::placeholders::_2, false)); rpc::rpc.mark_safe("choke_group.list"); rpc::rpc.mark_safe("choke_group.size"); rpc::rpc.mark_safe("choke_group.index_of"); rpc::rpc.mark_safe("choke_group.general.size"); rpc::rpc.mark_safe("choke_group.tracker.mode"); rpc::rpc.mark_safe("choke_group.up.rate"); rpc::rpc.mark_safe("choke_group.down.rate"); rpc::rpc.mark_safe("choke_group.up.max"); rpc::rpc.mark_safe("choke_group.up.max.unlimited"); rpc::rpc.mark_safe("choke_group.up.total"); rpc::rpc.mark_safe("choke_group.up.queued"); rpc::rpc.mark_safe("choke_group.up.unchoked"); rpc::rpc.mark_safe("choke_group.up.heuristics"); rpc::rpc.mark_safe("choke_group.down.max"); rpc::rpc.mark_safe("choke_group.down.max.unlimited"); rpc::rpc.mark_safe("choke_group.down.total"); rpc::rpc.mark_safe("choke_group.down.queued"); rpc::rpc.mark_safe("choke_group.down.unchoked"); rpc::rpc.mark_safe("choke_group.down.heuristics"); }