From 855bf9a7810b66f91f5c86d851f8ba6ab8667025 Mon Sep 17 00:00:00 2001 From: rakshasa Date: Thu, 14 May 2009 09:49:18 +0000 Subject: [PATCH] * Enable custom throttles, both per-download or per-IP. See http://libtorrent.rakshasa.no/ticket/20 for info and instructions. Patch by Josef Drexler. git-svn-id: svn://rakshasa.no/libtorrent/trunk/rtorrent@1091 e378c898-3ddf-0310-93e7-cc216c733640 --- doc/rtorrent.1.xml | 23 ++++ src/command_download.cc | 4 + src/command_network.cc | 124 ++++++++++++++++++++ src/command_ui.cc | 14 +++ src/core/Makefile.am | 1 + src/core/dht_manager.cc | 13 +++ src/core/dht_manager.h | 4 + src/core/download.cc | 12 ++ src/core/download.h | 2 + src/core/download_factory.cc | 4 + src/core/manager.cc | 31 +++++ src/core/manager.h | 17 +++ src/core/range_map.h | 194 ++++++++++++++++++++++++++++++++ src/display/utils.cc | 13 ++- src/ui/Makefile.am | 1 + src/ui/download.cc | 54 +++++++++ src/ui/download.h | 3 + src/ui/element_base.cc | 54 +++++++++ src/ui/element_base.h | 2 + src/ui/element_download_list.cc | 22 ++++ src/ui/element_download_list.h | 2 + src/ui/root.cc | 73 +++++------- src/ui/root.h | 2 + 23 files changed, 618 insertions(+), 51 deletions(-) create mode 100644 src/core/range_map.h create mode 100644 src/ui/element_base.cc diff --git a/doc/rtorrent.1.xml b/doc/rtorrent.1.xml index ccea8875..9c455859 100644 --- a/doc/rtorrent.1.xml +++ b/doc/rtorrent.1.xml @@ -677,6 +677,29 @@ setting 0. + + throttle_up = name, upload_rate + throttle_down = name, download_rate + +Define secondary throttle and/or set the given upload or download rate. Attach to a download with the d.set_throttle_name=name command +or switch throttles with Ctrl-T. Download must be stopped when changing throttles. Note that secondary throttles only work if the +global upload/download is throttled. Setting a download to use the NULL throttle makes the download unthrottled +even when there is a global throttle. Note that this special case bypasses the global throttle entirely, and as such its rate and +transfer amounts are not included in the global statistics. + + + + + throttle_ip = name, host + throttle_ip = name, network/prefix + throttle_ip = name, start, end + +Use the given secondary throttle for a host, CIDR network or IP range. All peers with a matching IP will use this throttle instead +of the global throttle or a custom download throttle. The name may be NULL to make these peers unthrottled, with +the same caveats as explained above. + + + diff --git a/src/command_download.cc b/src/command_download.cc index b9816c42..de40114d 100644 --- a/src/command_download.cc +++ b/src/command_download.cc @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -667,6 +668,9 @@ initialize_command_download() { ADD_CD_VALUE_MEM_UNI("skip_rate", &torrent::Download::mutable_skip_rate, &torrent::Rate::rate); ADD_CD_VALUE_MEM_UNI("skip_total", &torrent::Download::mutable_skip_rate, &torrent::Rate::total); + ADD_CD_STRING("set_throttle_name", std::mem_fun(&core::Download::set_throttle_name)); + ADD_CD_SLOT_PUBLIC("d.get_throttle_name", call_unknown, rpc::get_variable_d_fn("rtorrent", "throttle_name"), "i:", ""); + ADD_CD_VALUE_UNI("creation_date", rak::on(std::mem_fun(&core::Download::download), std::mem_fun(&torrent::Download::creation_date))); ADD_CD_VALUE_UNI("bytes_done", rak::on(std::mem_fun(&core::Download::download), std::mem_fun(&torrent::Download::bytes_done))); ADD_CD_VALUE_UNI("ratio", std::ptr_fun(&retrieve_d_ratio)); diff --git a/src/command_network.cc b/src/command_network.cc index 09e4915d..cce13d36 100644 --- a/src/command_network.cc +++ b/src/command_network.cc @@ -61,6 +61,120 @@ #include "control.h" #include "command_helpers.h" +torrent::Object +apply_throttle(bool up, const torrent::Object& rawArgs) { + const torrent::Object::list_type& args = rawArgs.as_list(); + torrent::Object::list_const_iterator argItr = args.begin(); + + const std::string& name = argItr->as_string(); + if (name.empty() || name == "NULL") + throw torrent::input_error("Invalid throttle name."); + + if ((++argItr)->as_string().empty()) + return torrent::Object(); + + int64_t rate; + rpc::parse_whole_value_nothrow(argItr->as_string().c_str(), &rate); + + if (rate < 0) + throw torrent::input_error("Throttle rate must be non-negative."); + + core::ThrottleMap::iterator itr = control->core()->throttles().find(name); + if (itr == control->core()->throttles().end()) + itr = control->core()->throttles().insert(std::make_pair(name, torrent::ThrottlePair(NULL, NULL))).first; + + torrent::Throttle*& throttle = up ? itr->second.first : itr->second.second; + if (rate != 0 && throttle == NULL) + throttle = (up ? torrent::up_throttle_global() : torrent::down_throttle_global())->create_slave(); + + if (throttle != NULL) + throttle->set_max_rate(rate * 1024); + + return torrent::Object(); +} + +static const int throttle_info_up = (1 << 0); +static const int throttle_info_down = (1 << 1); +static const int throttle_info_max = (1 << 2); +static const int throttle_info_rate = (1 << 3); + +torrent::Object +retrieve_throttle_info(int flags, const torrent::Object& rawArgs) { + const std::string& name = rawArgs.as_string(); + core::ThrottleMap::iterator itr = control->core()->throttles().find(name); + torrent::ThrottlePair throttles = itr == control->core()->throttles().end() ? torrent::ThrottlePair(NULL, NULL) : itr->second; + torrent::Throttle* throttle = flags & throttle_info_down ? throttles.second : throttles.first; + torrent::Throttle* global = flags & throttle_info_down ? torrent::down_throttle_global() : torrent::up_throttle_global(); + + if (throttle == NULL && name.empty()) + throttle = global; + + if (throttle == NULL) + return flags & throttle_info_rate ? (int64_t)0 : (int64_t)-1; + else if (!throttle->is_throttled() || !global->is_throttled()) + return (int64_t)0; + else if (flags & throttle_info_rate) + return (int64_t)throttle->rate()->rate(); + else + return (int64_t)throttle->max_rate(); +} + +std::pair +parse_address_range(const torrent::Object::list_type& args, torrent::Object::list_type::const_iterator itr) { + unsigned int prefixWidth, ret; + char dummy; + char host[1024]; + rak::address_info* ai; + + ret = std::sscanf(itr->as_string().c_str(), "%1023[^/]/%d%c", host, &prefixWidth, &dummy); + if (ret < 1 || rak::address_info::get_address_info(host, PF_INET, SOCK_STREAM, &ai) != 0) + throw torrent::input_error("Could not resolve host."); + + uint32_t begin, end; + rak::socket_address sa; + sa.copy(*ai->address(), ai->length()); + begin = end = sa.sa_inet()->address_h(); + rak::address_info::free_address_info(ai); + + if (ret == 2) { + if (++itr != args.end()) + throw torrent::input_error("Cannot specify both network and range end."); + + uint32_t netmask = std::numeric_limits::max() << (32 - prefixWidth); + if (prefixWidth >= 32 || sa.sa_inet()->address_h() & ~netmask) + throw torrent::input_error("Invalid address/prefix."); + + end = sa.sa_inet()->address_h() | ~netmask; + + } else if (++itr != args.end()) { + if (rak::address_info::get_address_info(itr->as_string().c_str(), PF_INET, SOCK_STREAM, &ai) != 0) + throw torrent::input_error("Could not resolve host."); + + sa.copy(*ai->address(), ai->length()); + rak::address_info::free_address_info(ai); + end = sa.sa_inet()->address_h(); + } + + // convert to [begin, end) making sure the end doesn't overflow + // (this precludes 255.255.255.255 from ever matching, but that's not a real IP anyway) + return std::make_pair(begin, std::max(end, end + 1)); +} + +torrent::Object +apply_address_throttle(const torrent::Object& rawArgs) { + const torrent::Object::list_type& args = rawArgs.as_list(); + if (args.size() < 2 || args.size() > 3) + throw torrent::input_error("Incorrect number of arguments."); + + std::pair range = parse_address_range(args, ++args.begin()); + core::ThrottleMap::iterator throttleItr = control->core()->throttles().find(args.begin()->as_string().c_str()); + if (throttleItr == control->core()->throttles().end()) + throw torrent::input_error("Throttle not found."); + + control->core()->set_address_throttle(range.first, range.second, throttleItr->second); + return torrent::Object(); +} + torrent::Object apply_encryption(const torrent::Object& rawArgs) { const torrent::Object::list_type& args = rawArgs.as_list(); @@ -328,6 +442,15 @@ initialize_command_network() { ADD_VARIABLE_VALUE("tracker_numwant", -1); + ADD_COMMAND_LIST("throttle_up", rak::bind_ptr_fn(&apply_throttle, true)); + ADD_COMMAND_LIST("throttle_down", rak::bind_ptr_fn(&apply_throttle, false)); + ADD_COMMAND_LIST("throttle_ip", rak::ptr_fn(&apply_address_throttle)); + + ADD_COMMAND_STRING("get_throttle_up_max", rak::bind_ptr_fn(&retrieve_throttle_info, throttle_info_up | throttle_info_max)); + ADD_COMMAND_STRING("get_throttle_up_rate", rak::bind_ptr_fn(&retrieve_throttle_info, throttle_info_up | throttle_info_rate)); + ADD_COMMAND_STRING("get_throttle_down_max", rak::bind_ptr_fn(&retrieve_throttle_info, throttle_info_down | throttle_info_max)); + ADD_COMMAND_STRING("get_throttle_down_rate", rak::bind_ptr_fn(&retrieve_throttle_info, throttle_info_down | throttle_info_rate)); + ADD_COMMAND_LIST("encryption", rak::ptr_fn(&apply_encryption)); ADD_COMMAND_STRING("tos", rak::ptr_fn(&apply_tos)); @@ -363,6 +486,7 @@ initialize_command_network() { ADD_COMMAND_STRING_UN("dht", rak::make_mem_fun(control->dht_manager(), &core::DhtManager::set_start)); ADD_COMMAND_STRING_UN("dht_add_node", std::ptr_fun(&apply_dht_add_node)); ADD_COMMAND_VOID("dht_statistics", rak::make_mem_fun(control->dht_manager(), &core::DhtManager::dht_statistics)); + ADD_COMMAND_STRING_TRI("dht_throttle", rak::make_mem_fun(control->dht_manager(), &core::DhtManager::set_throttle_name), rak::make_mem_fun(control->dht_manager(), &core::DhtManager::throttle_name)); ADD_VARIABLE_BOOL("peer_exchange", true); diff --git a/src/command_ui.cc b/src/command_ui.cc index fa4653ec..9d393601 100644 --- a/src/command_ui.cc +++ b/src/command_ui.cc @@ -315,6 +315,19 @@ apply_to_xb(const torrent::Object& rawArgs) { return std::string(buffer); } +torrent::Object +apply_to_throttle(const torrent::Object& rawArgs) { + int64_t arg = rawArgs.as_value(); + if (arg < 0) + return "---"; + else if (arg == 0) + return "off"; + + char buffer[32]; + snprintf(buffer, 32, "%3d", (int)(arg / (1 << 10))); + return std::string(buffer); +} + // A series of if/else statements. Every even arguments are // conditionals and odd arguments are branches to be executed, except // the last one which is always a branch. @@ -503,4 +516,5 @@ initialize_command_ui() { ADD_COMMAND_VALUE("to_kb", rak::ptr_fn(&apply_to_kb)); ADD_COMMAND_VALUE("to_mb", rak::ptr_fn(&apply_to_mb)); ADD_COMMAND_VALUE("to_xb", rak::ptr_fn(&apply_to_xb)); + ADD_COMMAND_VALUE("to_throttle", rak::ptr_fn(&apply_to_throttle)); } diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 884356bb..fe07cbfe 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -32,6 +32,7 @@ libsub_core_a_SOURCES = \ poll_manager_kqueue.h \ poll_manager_select.cc \ poll_manager_select.h \ + range_map.h \ view.cc \ view.h \ view_manager.cc \ diff --git a/src/core/dht_manager.cc b/src/core/dht_manager.cc index 9a76a8fc..5f68f566 100644 --- a/src/core/dht_manager.cc +++ b/src/core/dht_manager.cc @@ -95,6 +95,10 @@ DhtManager::start_dht() { if (!torrent::dht_manager()->is_valid() || torrent::dht_manager()->is_active()) return; + torrent::ThrottlePair throttles = control->core()->get_throttle(m_throttleName); + torrent::dht_manager()->set_upload_throttle(throttles.first); + torrent::dht_manager()->set_download_throttle(throttles.second); + int port = rpc::call_command_value("get_dht_port"); if (port <= 0) return; @@ -283,6 +287,7 @@ DhtManager::dht_statistics() { dhtStats.insert_key("dht", dht_settings[m_start]); dhtStats.insert_key("active", torrent::dht_manager()->is_active()); + dhtStats.insert_key("throttle", m_throttleName); if (torrent::dht_manager()->is_active()) { torrent::DhtManager::statistics_type stats = torrent::dht_manager()->get_statistics(); @@ -303,4 +308,12 @@ DhtManager::dht_statistics() { return dhtStats; } +void +DhtManager::set_throttle_name(const std::string& throttleName) { + if (torrent::dht_manager()->is_active()) + throw torrent::input_error("Cannot set DHT throttle while active."); + + m_throttleName = throttleName; +} + } diff --git a/src/core/dht_manager.h b/src/core/dht_manager.h index 076e6f90..093c54bc 100644 --- a/src/core/dht_manager.h +++ b/src/core/dht_manager.h @@ -58,6 +58,9 @@ public: void set_start(const std::string& arg); + void set_throttle_name(const std::string& throttleName); + const std::string& throttle_name() const { return m_throttleName; } + private: static const int dht_disable = 0; static const int dht_off = 1; @@ -82,6 +85,7 @@ private: bool m_warned; int m_start; + std::string m_throttleName; }; } diff --git a/src/core/download.cc b/src/core/download.cc index 0a2254bb..0e2185b0 100644 --- a/src/core/download.cc +++ b/src/core/download.cc @@ -155,6 +155,18 @@ Download::receive_chunk_failed(__UNUSED uint32_t idx) { m_chunksFailed++; } +void +Download::set_throttle_name(const std::string& throttleName) { + if (m_download.is_active()) + throw torrent::input_error("Cannot set throttle on active download."); + + torrent::ThrottlePair throttles = control->core()->get_throttle(throttleName); + m_download.set_upload_throttle(throttles.first); + m_download.set_download_throttle(throttles.second); + + m_download.bencode()->get_key("rtorrent").insert_key("throttle_name", throttleName); +} + void Download::set_root_directory(const std::string& path) { torrent::FileList* fileList = m_download.file_list(); diff --git a/src/core/download.h b/src/core/download.h index 64d081ec..20a131db 100644 --- a/src/core/download.h +++ b/src/core/download.h @@ -117,6 +117,8 @@ public: void set_root_directory(const std::string& path); + void set_throttle_name(const std::string& throttleName); + bool operator == (const std::string& str) const; float distributed_copies() const; diff --git a/src/core/download_factory.cc b/src/core/download_factory.cc index 25590882..e2d8ee88 100644 --- a/src/core/download_factory.cc +++ b/src/core/download_factory.cc @@ -336,6 +336,10 @@ DownloadFactory::initialize_rtorrent(Download* download, torrent::Object* rtorre download->download()->set_chunks_done(std::min(rtorrent->get_key_value("chunks_done"), download->download()->file_list()->size_chunks())); + download->set_throttle_name(rtorrent->has_key_string("throttle_name") + ? rtorrent->get_key_string("throttle_name") + : std::string()); + rtorrent->insert_preserve_copy("ignore_commands", (int64_t)0); rtorrent->insert_preserve_copy("views", torrent::Object::create_list()); diff --git a/src/core/manager.cc b/src/core/manager.cc index 41991d28..3886c4fa 100644 --- a/src/core/manager.cc +++ b/src/core/manager.cc @@ -54,6 +54,7 @@ #include #include #include +#include #include "rpc/parse_commands.h" #include "utils/directory.h" @@ -166,9 +167,14 @@ Manager::Manager() : m_fileStatusCache = new FileStatusCache(); m_httpQueue = new HttpQueue(); m_httpStack = new CurlStack(); + + torrent::Throttle* unthrottled = torrent::Throttle::create_throttle(); + unthrottled->set_max_rate(0); + m_throttles["NULL"] = std::make_pair(unthrottled, unthrottled); } Manager::~Manager() { + torrent::Throttle::destroy_throttle(m_throttles["NULL"].first); delete m_downloadList; delete m_downloadStore; @@ -185,6 +191,31 @@ Manager::set_hashing_view(View* v) { v->signal_changed().connect(sigc::mem_fun(this, &Manager::receive_hashing_changed)); } +torrent::ThrottlePair +Manager::get_throttle(const std::string& name) { + ThrottleMap::const_iterator itr = m_throttles.find(name); + torrent::ThrottlePair throttles = (itr == m_throttles.end() ? torrent::ThrottlePair(NULL, NULL) : itr->second); + + if (throttles.first == NULL) + throttles.first = torrent::up_throttle_global(); + + if (throttles.second == NULL) + throttles.second = torrent::down_throttle_global(); + + return throttles; +} + +void +Manager::set_address_throttle(uint32_t begin, uint32_t end, torrent::ThrottlePair throttles) { + m_addressThrottles.set_merge(begin, end, throttles); + torrent::connection_manager()->set_address_throttle(sigc::mem_fun(control->core(), &core::Manager::get_address_throttle)); +} + +torrent::ThrottlePair +Manager::get_address_throttle(const sockaddr* addr) { + return m_addressThrottles.get(rak::socket_address::cast_from(addr)->sa_inet()->address_h(), torrent::ThrottlePair(NULL, NULL)); +} + void Manager::initialize_first() { const char* poll = getenv("RTORRENT_POLL"); diff --git a/src/core/manager.h b/src/core/manager.h index 43c10156..595f4a4b 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -40,8 +40,11 @@ #include #include +#include + #include "download_list.h" #include "poll_manager.h" +#include "range_map.h" #include "log.h" namespace torrent { @@ -57,6 +60,8 @@ namespace core { class DownloadStore; class HttpQueue; +typedef std::map ThrottleMap; + class View; class Manager { @@ -84,6 +89,13 @@ public: Log& get_log_important() { return m_logImportant; } Log& get_log_complete() { return m_logComplete; } + ThrottleMap& throttles() { return m_throttles; } + torrent::ThrottlePair get_throttle(const std::string& name); + + // Use custom throttle for the given range of IP addresses. + void set_address_throttle(uint32_t begin, uint32_t end, torrent::ThrottlePair throttles); + torrent::ThrottlePair get_address_throttle(const sockaddr* addr); + // Really should find a more descriptive name. void initialize_first(); void initialize_second(); @@ -120,6 +132,8 @@ public: void try_create_download_expand(const std::string& uri, int flags, command_list_type commands = command_list_type()); private: + typedef RangeMap AddressThrottleMap; + void create_http(const std::string& uri); void create_final(std::istream* s); @@ -136,6 +150,9 @@ private: View* m_hashingView; + ThrottleMap m_throttles; + AddressThrottleMap m_addressThrottles; + PollManager* m_pollManager; Log m_logImportant; Log m_logComplete; diff --git a/src/core/range_map.h b/src/core/range_map.h new file mode 100644 index 00000000..1f1bf8fc --- /dev/null +++ b/src/core/range_map.h @@ -0,0 +1,194 @@ +// 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 +// +// Skomakerveien 33 +// 3185 Skoppum, NORWAY + +#ifndef RTORRENT_CORE_RANGE_MAP_H +#define RTORRENT_CORE_RANGE_MAP_H + +#include +#include + +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 Alloc = std::allocator > > +class RangeMap : private std::map, Compare, + typename Alloc::template rebind > >::other> { + + typedef std::map, Compare, + typename Alloc::template rebind > >::other> base_type; + +public: + RangeMap() {} + RangeMap(const Compare& c) : base_type(c) {} + + using base_type::const_iterator; + using 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. + typename RangeMap::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. + typename RangeMap::const_iterator set_merge(Key begin, const Key& end, const T& value); + + // Find range containing the given key, or end(). + typename RangeMap::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: + typename RangeMap::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 +inline typename RangeMap::iterator +RangeMap::crop_overlap(const Key& _begin, const Key& _end) { + typename RangeMap::iterator itr = 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)) + insert(itr, typename RangeMap::value_type(_begin, itr->second)); + + // Old end is within our range: erase entry. + if (!key_comp()(_end, itr->first)) { + erase(itr++); + + // Otherwise simply set the new begin of the old range. + } else { + itr->second.first = _end; + ++itr; + } + } + + return itr; +} + +template +inline typename RangeMap::const_iterator +RangeMap::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; + 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 insert(itr, typename RangeMap::value_type(_end, typename RangeMap::mapped_type(_begin, value))); +} + +template +inline typename RangeMap::const_iterator +RangeMap::set_range(const Key& _begin, const Key& _end, const T& value) { + if (!key_comp()(_begin, _end)) + return end(); + + return insert(crop_overlap(_begin, _end), typename RangeMap::value_type(_end, typename RangeMap::mapped_type(_begin, value))); +} + +template +inline typename RangeMap::const_iterator +RangeMap::find(const Key& key) const { + typename RangeMap::const_iterator itr = upper_bound(key); + + if (itr != end() && key_comp()(key, itr->second.first)) + itr = end(); + + return itr; +} + +template +inline const T& +RangeMap::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 +inline T +RangeMap::get(const Key& key, T def) const { + typename RangeMap::const_iterator itr = find(key); + return (itr == end() ? def : itr->second.second); +} + +} + +#endif diff --git a/src/display/utils.cc b/src/display/utils.cc index b39f9c26..af24fa1b 100644 --- a/src/display/utils.cc +++ b/src/display/utils.cc @@ -166,9 +166,12 @@ print_download_info(char* first, char* last, core::Download* d) { (double)rpc::call_command_value("d.get_ratio", rpc::make_target(d)) / 1000.0); if (d->priority() != 2) - first = print_buffer(first, last, " %s]", rpc::call_command_string("d.get_priority_str", rpc::make_target(d)).c_str()); - else - first = print_buffer(first, last, "]"); + first = print_buffer(first, last, " %s", rpc::call_command_string("d.get_priority_str", rpc::make_target(d)).c_str()); + + if (!d->bencode()->get_key("rtorrent").get_key_string("throttle_name").empty()) + first = print_buffer(first, last , " %s", rpc::call_command_string("d.get_throttle_name", rpc::make_target(d)).c_str()); + + first = print_buffer(first, last , "]"); if (first > last) throw torrent::internal_error("print_download_info(...) wrote past end of the buffer."); @@ -251,12 +254,12 @@ print_client_version(char* first, char* last, const torrent::ClientInfo& clientI char* print_status_info(char* first, char* last) { - if (torrent::up_throttle_global()->max_rate() == 0) + if (!torrent::up_throttle_global()->is_throttled()) first = print_buffer(first, last, "[Throttle off"); else first = print_buffer(first, last, "[Throttle %3i", torrent::up_throttle_global()->max_rate() / 1024); - if (torrent::down_throttle_global()->max_rate() == 0) + if (!torrent::down_throttle_global()->is_throttled()) first = print_buffer(first, last, "/off KB]"); else first = print_buffer(first, last, "/%3i KB]", torrent::down_throttle_global()->max_rate() / 1024); diff --git a/src/ui/Makefile.am b/src/ui/Makefile.am index 857ce9d6..1b493151 100644 --- a/src/ui/Makefile.am +++ b/src/ui/Makefile.am @@ -6,6 +6,7 @@ libsub_ui_a_SOURCES = \ download_list.cc \ download_list.h \ element_base.h \ + element_base.cc \ element_chunks_seen.cc \ element_chunks_seen.h \ element_download_list.cc \ diff --git a/src/ui/download.cc b/src/ui/download.cc index dac1dac3..9bf9f18a 100644 --- a/src/ui/download.cc +++ b/src/ui/download.cc @@ -42,12 +42,14 @@ #include #include #include +#include #include #include #include #include #include "core/download.h" +#include "core/manager.h" #include "input/manager.h" #include "display/window_title.h" #include "display/window_download_statusbar.h" @@ -179,6 +181,15 @@ Download::create_info() { element->push_column("Send buffer:", te_command("cat=$to_kb=$get_send_buffer_size=,\" KB\"")); element->push_column("Receive buffer:", te_command("cat=$to_kb=$get_receive_buffer_size=,\" KB\"")); + // TODO: Define a custom command for this and use $argument.0 instead of looking up the name multiple times? + element->push_column("Throttle:", te_command("branch=d.get_throttle_name=,\"" + "cat=$d.get_throttle_name=,\\\" [Max \\\"," + "$to_throttle=$get_throttle_up_max=$d.get_throttle_name=,\\\"/\\\"," + "$to_throttle=$get_throttle_down_max=$d.get_throttle_name=,\\\" KB] [Rate \\\"," + "$to_kb=$get_throttle_up_rate=$d.get_throttle_name=,\\\"/\\\"," + "$to_kb=$get_throttle_down_rate=$d.get_throttle_name=,\\\" KB]\\\"\"," + "cat=\"global\"")); + element->push_back(""); element->push_column("Upload:", te_command("cat=$to_kb=$d.get_up_rate=,\" KB / \",$to_xb=$d.get_up_total=")); element->push_column("Download:", te_command("cat=$to_kb=$d.get_down_rate=,\" KB / \",$to_xb=$d.get_down_total=")); @@ -315,6 +326,32 @@ Download::receive_prev_priority() { m_download->set_priority((m_download->priority() - 1) % 4); } +void +Download::adjust_down_throttle(int throttle) { + core::ThrottleMap::iterator itr = control->core()->throttles().find(m_download->bencode()->get_key("rtorrent").get_key_string("throttle_name")); + + if (itr == control->core()->throttles().end() || itr->second.second == NULL || itr->first == "NULL") + control->ui()->adjust_down_throttle(throttle); + else + itr->second.second->set_max_rate(std::max((itr->second.second->is_throttled() ? itr->second.second->max_rate() : 0) + throttle * 1024, 0)); + + if (m_uiArray[DISPLAY_INFO]->is_active()) + m_uiArray[DISPLAY_INFO]->mark_dirty(); +} + +void +Download::adjust_up_throttle(int throttle) { + core::ThrottleMap::iterator itr = control->core()->throttles().find(m_download->bencode()->get_key("rtorrent").get_key_string("throttle_name")); + + if (itr == control->core()->throttles().end() || itr->second.first == NULL || itr->first == "NULL") + control->ui()->adjust_up_throttle(throttle); + else + itr->second.first->set_max_rate(std::max((itr->second.first->is_throttled() ? itr->second.first->max_rate() : 0) + throttle * 1024, 0)); + + if (m_uiArray[DISPLAY_INFO]->is_active()) + m_uiArray[DISPLAY_INFO]->mark_dirty(); +} + void Download::bind_keys() { m_bindings['1'] = sigc::bind(sigc::mem_fun(this, &Download::receive_max_uploads), -1); @@ -328,6 +365,23 @@ Download::bind_keys() { m_bindings['t'] = sigc::bind(sigc::mem_fun(m_download->tracker_list(), &torrent::TrackerList::manual_request), false); m_bindings['T'] = sigc::bind(sigc::mem_fun(m_download->tracker_list(), &torrent::TrackerList::manual_request), true); + + const char* keys = control->ui()->get_throttle_keys(); + + m_bindings[keys[ 0]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_up_throttle), 1); + m_bindings[keys[ 1]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_up_throttle), -1); + m_bindings[keys[ 2]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_down_throttle), 1); + m_bindings[keys[ 3]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_down_throttle), -1); + + m_bindings[keys[ 4]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_up_throttle), 5); + m_bindings[keys[ 5]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_up_throttle), -5); + m_bindings[keys[ 6]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_down_throttle), 5); + m_bindings[keys[ 7]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_down_throttle), -5); + + m_bindings[keys[ 8]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_up_throttle), 50); + m_bindings[keys[ 9]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_up_throttle), -50); + m_bindings[keys[10]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_down_throttle), 50); + m_bindings[keys[11]] = sigc::bind(sigc::mem_fun(this, &Download::adjust_down_throttle), -50); } } diff --git a/src/ui/download.h b/src/ui/download.h index 791faf9f..3a21da0d 100644 --- a/src/ui/download.h +++ b/src/ui/download.h @@ -87,6 +87,9 @@ public: void receive_next_priority(); void receive_prev_priority(); + void adjust_up_throttle(int throttle); + void adjust_down_throttle(int throttle); + display::Window* window() { return NULL; } private: diff --git a/src/ui/element_base.cc b/src/ui/element_base.cc new file mode 100644 index 00000000..d7d530d5 --- /dev/null +++ b/src/ui/element_base.cc @@ -0,0 +1,54 @@ +// rTorrent - BitTorrent client +// Copyright (C) 2005-2007, 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 +// +// Skomakerveien 33 +// 3185 Skoppum, NORWAY + +#include "config.h" + +#include + +#include "display/frame.h" +#include "display/window.h" + +#include "element_base.h" + +namespace ui { + +void +ElementBase::mark_dirty() { + if (is_active()) + m_frame->window()->mark_dirty(); +} + +} diff --git a/src/ui/element_base.h b/src/ui/element_base.h index 0774b030..10517ef2 100644 --- a/src/ui/element_base.h +++ b/src/ui/element_base.h @@ -62,6 +62,8 @@ public: void slot_exit(const slot_type& s) { m_slotExit = s; } + void mark_dirty(); + protected: display::Frame* m_frame; bool m_focus; diff --git a/src/ui/element_download_list.cc b/src/ui/element_download_list.cc index 378aaa70..1dc9ece0 100644 --- a/src/ui/element_download_list.cc +++ b/src/ui/element_download_list.cc @@ -72,6 +72,7 @@ ElementDownloadList::ElementDownloadList() : m_bindings['+'] = sigc::mem_fun(*this, &ElementDownloadList::receive_next_priority); m_bindings['-'] = sigc::mem_fun(*this, &ElementDownloadList::receive_prev_priority); + m_bindings['T'-'@']= sigc::mem_fun(*this, &ElementDownloadList::receive_cycle_throttle); m_bindings['I'] = sigc::bind(sigc::mem_fun(*this, &ElementDownloadList::receive_command), "branch=d.get_ignore_commands=," "{d.set_ignore_commands=0, print=\"Torrent set to heed commands.\"}," @@ -185,6 +186,27 @@ ElementDownloadList::receive_prev_priority() { m_window->mark_dirty(); } +void +ElementDownloadList::receive_cycle_throttle() { + if (m_view->focus() == m_view->end_visible()) + return; + + core::Download* download = *m_view->focus(); + if (download->is_active()) { + control->core()->push_log("Cannot change throttle on active download."); + return; + } + + core::ThrottleMap::const_iterator itr = control->core()->throttles().find(download->bencode()->get_key("rtorrent").get_key_string("throttle_name")); + if (itr == control->core()->throttles().end()) + itr = control->core()->throttles().begin(); + else + ++itr; + + download->set_throttle_name(itr == control->core()->throttles().end() ? std::string() : itr->first); + m_window->mark_dirty(); +} + void ElementDownloadList::receive_change_view(const std::string& name) { core::ViewManager::iterator itr = control->view_manager()->find(name); diff --git a/src/ui/element_download_list.h b/src/ui/element_download_list.h index c14dd8e4..ed5de30b 100644 --- a/src/ui/element_download_list.h +++ b/src/ui/element_download_list.h @@ -73,6 +73,8 @@ public: void receive_next_priority(); void receive_prev_priority(); + void receive_cycle_throttle(); + void receive_change_view(const std::string& name); private: diff --git a/src/ui/root.cc b/src/ui/root.cc index 94c1d750..b01f4ed4 100644 --- a/src/ui/root.cc +++ b/src/ui/root.cc @@ -120,59 +120,40 @@ Root::cleanup() { m_control = NULL; } +const char* +Root::get_throttle_keys() { + const std::string& keyLayout = rpc::call_command_string("get_key_layout"); + + if (strcasecmp(keyLayout.c_str(), "azerty") == 0) + return "qwQWsxSXdcDC"; + else if (strcasecmp(keyLayout.c_str(), "qwertz") == 0) + return "ayAYsxSXdcDC"; + else if (strcasecmp(keyLayout.c_str(), "dvorak") == 0) + return "a;A:oqOQejEJ"; + else + return "azAZsxSXdcDC"; +} + void Root::setup_keys() { m_control->input()->push_back(&m_bindings); - const std::string& keyLayout = rpc::call_command_string("get_key_layout"); + const char* keys = get_throttle_keys(); - if (strcasecmp(keyLayout.c_str(), "azerty") == 0) { - m_bindings['q'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), 1); - m_bindings['w'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), -1); - m_bindings['Q'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), 1); - m_bindings['W'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), -1); + m_bindings[keys[ 0]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), 1); + m_bindings[keys[ 1]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), -1); + m_bindings[keys[ 2]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), 1); + m_bindings[keys[ 3]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), -1); - } else if (strcasecmp(keyLayout.c_str(), "qwertz") == 0) { - m_bindings['a'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), 1); - m_bindings['y'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), -1); - m_bindings['A'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), 1); - m_bindings['Y'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), -1); + m_bindings[keys[ 4]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), 5); + m_bindings[keys[ 5]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), -5); + m_bindings[keys[ 6]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), 5); + m_bindings[keys[ 7]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), -5); - } else if (strcasecmp(keyLayout.c_str(), "dvorak") == 0) { - m_bindings['a'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), 1); - m_bindings[';'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), -1); - m_bindings['A'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), 1); - m_bindings[':'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), -1); - - } else { - m_bindings['a'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), 1); - m_bindings['z'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), -1); - m_bindings['A'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), 1); - m_bindings['Z'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), -1); - } - - if (strcasecmp(keyLayout.c_str(), "dvorak") != 0) { - m_bindings['s'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), 5); - m_bindings['x'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), -5); - m_bindings['S'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), 5); - m_bindings['X'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), -5); - - m_bindings['d'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), 50); - m_bindings['c'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), -50); - m_bindings['D'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), 50); - m_bindings['C'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), -50); - - } else { - m_bindings['o'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), 5); - m_bindings['q'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), -5); - m_bindings['O'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), 5); - m_bindings['Q'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), -5); - - m_bindings['e'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), 50); - m_bindings['j'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), -50); - m_bindings['E'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), 50); - m_bindings['J'] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), -50); - } + m_bindings[keys[ 8]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), 50); + m_bindings[keys[ 9]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_up_throttle), -50); + m_bindings[keys[10]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), 50); + m_bindings[keys[11]] = sigc::bind(sigc::mem_fun(*this, &Root::adjust_down_throttle), -50); m_bindings['\x0C'] = sigc::mem_fun(m_control->display(), &display::Manager::force_redraw); // ^L m_bindings['\x11'] = sigc::mem_fun(m_control, &Control::receive_normal_shutdown); // ^Q diff --git a/src/ui/root.h b/src/ui/root.h index 273dc5ce..e9a79075 100644 --- a/src/ui/root.h +++ b/src/ui/root.h @@ -86,6 +86,8 @@ public: void adjust_down_throttle(int throttle); void adjust_up_throttle(int throttle); + const char* get_throttle_keys(); + void enable_input(const std::string& title, input::TextInput* input); void disable_input();