diff options
author | xsmile | 2017-03-09 18:46:37 +0100 |
---|---|---|
committer | xsmile | 2017-03-09 18:46:37 +0100 |
commit | a386ce87b56799cfb29b1667f56a63695fe781ed (patch) | |
tree | 70cbf797d882faad63eb3ff1561f0b79f247b34e | |
download | aur-a386ce87b56799cfb29b1667f56a63695fe781ed.tar.gz |
init
-rw-r--r-- | .SRCINFO | 48 | ||||
-rw-r--r-- | PKGBUILD | 85 | ||||
-rw-r--r-- | command_pyroscope.cc | 502 | ||||
-rw-r--r-- | ps-info-pane-xb-sizes_all.patch | 21 | ||||
-rw-r--r-- | ps-item-stats-human-sizes_all.patch | 35 | ||||
-rw-r--r-- | ps-ssl_verify_host_all.patch | 28 | ||||
-rw-r--r-- | ps-throttle-steps_all.patch | 68 | ||||
-rw-r--r-- | ps-ui_pyroscope_all.patch | 7 | ||||
-rw-r--r-- | pyroscope.patch | 26 | ||||
-rw-r--r-- | ui_pyroscope.cc | 807 | ||||
-rw-r--r-- | ui_pyroscope.h | 34 | ||||
-rw-r--r-- | ui_pyroscope.patch | 51 |
12 files changed, 1712 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO new file mode 100644 index 000000000000..a008f3b6bba6 --- /dev/null +++ b/.SRCINFO @@ -0,0 +1,48 @@ +# Generated by mksrcinfo v8 +# Thu Mar 9 17:43:32 UTC 2017 +pkgbase = rtorrent-ps + pkgdesc = Extended rTorrent distribution with UI enhancements, colorization, and some added features + pkgver = 0.9.6 + pkgrel = 1 + url = https://github.com/pyroscope/rtorrent-ps + arch = any + license = GPL + depends = cppunit + depends = curl>=7.15.4 + depends = libtorrent-ps>=0.13.6 + depends = ncurses + depends = xmlrpc-c + provides = rtorrent + conflicts = rtorrent + conflicts = rtorrent-cdl + conflicts = rtorrent-color + conflicts = rtorrent-git + conflicts = rtorrent-ipv6 + conflicts = rtorrent-ps-git + conflicts = rtorrent-pyro-git + conflicts = rtorrent-vi-color + source = https://github.com/rakshasa/rtorrent/archive/0.9.6.tar.gz + source = command_pyroscope.cc + source = ps-info-pane-xb-sizes_all.patch + source = ps-item-stats-human-sizes_all.patch + source = ps-ssl_verify_host_all.patch + source = ps-throttle-steps_all.patch + source = ps-ui_pyroscope_all.patch + source = pyroscope.patch + source = ui_pyroscope.cc + source = ui_pyroscope.h + source = ui_pyroscope.patch + md5sums = b8b4009f95f8543244ae1d23b1810d7c + md5sums = e7e55ab66fbb8663a466723b8d6b7c10 + md5sums = f1539d70c74e5c74d8a15d51675aa26c + md5sums = 2d34e8c86c1c6ed1354b55ca21819886 + md5sums = cef14e9011d4b4af92536b02f8b611c2 + md5sums = ee76d57dfbc40e09eeaee3845d327d94 + md5sums = 7a88f8ab5d41242fdf1428de0e2ca182 + md5sums = bd04a0699b80c8042e1cf63a7e0e4222 + md5sums = afd9f0e9ed816069b584e19c88b0a4bb + md5sums = 1258acfc82c50a8f452ace87fef0b416 + md5sums = 0a2bbaf74c7160ba33876dcc2f050f14 + +pkgname = rtorrent-ps + diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 000000000000..dff9c5a7cb27 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,85 @@ +# Maintainer: xsmile <sascha_r gmx de> + +_pkgname=rtorrent +pkgname=rtorrent-ps +pkgver=0.9.6 +pkgrel=1 +pkgdesc='Extended rTorrent distribution with UI enhancements, colorization, and some added features' +url='https://github.com/pyroscope/rtorrent-ps' +license=('GPL') +arch=('any') +depends=('cppunit' 'curl>=7.15.4' 'libtorrent-ps>=0.13.6' 'ncurses' 'xmlrpc-c') +provides=('rtorrent') +conflicts=('rtorrent' 'rtorrent-cdl' 'rtorrent-color' 'rtorrent-git' 'rtorrent-ipv6' 'rtorrent-ps-git' 'rtorrent-pyro-git' 'rtorrent-vi-color') +source=("https://github.com/rakshasa/$_pkgname/archive/$pkgver.tar.gz" + "command_pyroscope.cc" + "ps-info-pane-xb-sizes_all.patch" + "ps-item-stats-human-sizes_all.patch" + "ps-ssl_verify_host_all.patch" + "ps-throttle-steps_all.patch" + "ps-ui_pyroscope_all.patch" + "pyroscope.patch" + "ui_pyroscope.cc" + "ui_pyroscope.h" + "ui_pyroscope.patch") +md5sums=('b8b4009f95f8543244ae1d23b1810d7c' + 'e7e55ab66fbb8663a466723b8d6b7c10' + 'f1539d70c74e5c74d8a15d51675aa26c' + '2d34e8c86c1c6ed1354b55ca21819886' + 'cef14e9011d4b4af92536b02f8b611c2' + 'ee76d57dfbc40e09eeaee3845d327d94' + '7a88f8ab5d41242fdf1428de0e2ca182' + 'bd04a0699b80c8042e1cf63a7e0e4222' + 'afd9f0e9ed816069b584e19c88b0a4bb' + '1258acfc82c50a8f452ace87fef0b416' + '0a2bbaf74c7160ba33876dcc2f050f14') + +prepare() { + cd "$srcdir/$_pkgname-$pkgver" + + # Version handling + RT_HEX_VERSION=$(printf "0x%02X%02X%02X" ${pkgver//./ }) + sed -i -e "s:\\(AC_DEFINE(HAVE_CONFIG_H.*\\):\1 AC_DEFINE(RT_HEX_VERSION, "$RT_HEX_VERSION", for CPP if checks):" configure.ac + grep "AC_DEFINE.*API_VERSION" configure.ac >/dev/null || sed -i -e "s:\\(AC_DEFINE(HAVE_CONFIG_H.*\\):\1 AC_DEFINE(API_VERSION, 0, api version):" configure.ac + + # Patch rTorrent + for patch in "$srcdir"/ps-*.patch; do + msg2 "$patch" + patch -uNlp1 -i "$patch" + done + + msg2 "pyroscope.patch" + patch -uNp1 -i "$srcdir/pyroscope.patch" + for i in "$srcdir"/*.{cc,h}; do + ln -nsf "$i" src + done + + msg2 "ui_pyroscope.patch" + patch -uNp1 -i "$srcdir/ui_pyroscope.patch" + + sed -i -e 's/rTorrent \" VERSION/rTorrent-PS " VERSION/' src/ui/download_list.cc + + ./autogen.sh +} + +build() { + cd "$srcdir/$_pkgname-$pkgver" + + ./configure \ + --prefix=/usr \ + --with-xmlrpc-c \ + --disable-debug \ + --disable-instrumentation + make +} + +package() { + cd "$srcdir/$_pkgname-$pkgver" + + make DESTDIR="$pkgdir" install + + install -Dm644 "doc/faq.xml" "$pkgdir/usr/share/doc/$_pkgname/faq.xml" + install -Dm644 "doc/old/rtorrent.1" "$pkgdir/usr/share/man/man1/$_pkgname.1" + install -Dm644 "doc/rtorrent.rc" "$pkgdir/usr/share/doc/$_pkgname/rtorrent.rc" + install -Dm644 "doc/rtorrent_fast_resume.pl" "$pkgdir/usr/share/doc/$_pkgname/rtorrent_fast_resume.pl" +} diff --git a/command_pyroscope.cc b/command_pyroscope.cc new file mode 100644 index 000000000000..14fd8f96defa --- /dev/null +++ b/command_pyroscope.cc @@ -0,0 +1,502 @@ +// PyroScope - rTorrent Command Extensions +// Copyright (c) 2011 The PyroScope Project <pyroscope.project@gmail.com> +// +// 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., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include "config.h" +#include "globals.h" + +#include <cstdio> +#include <climits> +#include <ctime> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <rak/path.h> +#include <rak/functional.h> +#include <rak/functional_fun.h> +#if RT_HEX_VERSION < 0x000904 + #include <sigc++/adaptors/bind.h> +#endif + +#include "core/download.h" +#include "core/manager.h" +#include "core/view_manager.h" +#include "rpc/parse.h" +#include "torrent/tracker.h" +#include "torrent/tracker_list.h" +#include "ui/root.h" +#include "ui/download_list.h" +#include "ui/element_base.h" +#include "ui/element_download_list.h" + +#include "globals.h" +#include "control.h" +#include "command_helpers.h" + +#if (RT_HEX_VERSION >= 0x000901) + #define _cxxstd_ tr1 +#else + #define _cxxstd_ std +#endif + + +// handle for message log file +namespace core { +int log_messages_fd = -1; +}; + +#define RANDOM_STATESIZE 128 +static char system_random_state[RANDOM_STATESIZE]; +static struct random_data system_random_data; + + +// return the "main" tracker for this download item +torrent::Tracker* get_active_tracker(torrent::Download* item) { + torrent::TrackerList* tl = item->tracker_list(); + torrent::Tracker* tracker = 0; + + for (int trkidx = 0; trkidx < tl->size(); trkidx++) { + tracker = tl->at(trkidx); + if (tracker->is_usable() && tracker->type() == torrent::Tracker::TRACKER_HTTP + && tracker->scrape_complete() + tracker->scrape_incomplete() > 0) { + break; + } + tracker = 0; + } + if (!tracker && tl->size()) tracker = tl->at(0); + + return tracker; +} + + +// return the domain name of the "main" tracker of the given download item +std::string get_active_tracker_domain(torrent::Download* item) { + std::string url; + torrent::Tracker* tracker = get_active_tracker(item); + + if (tracker && !tracker->url().empty()) { + url = tracker->url(); + + // snip url to domain name + if (url.compare(0, 7, "http://") == 0) url = url.substr(7); + if (url.compare(0, 8, "https://") == 0) url = url.substr(8); + if (url.find('/') > 0) url = url.substr(0, url.find('/')); + if (url.find(':') > 0) url = url.substr(0, url.find(':')); + + // remove some common cruft + const char* domain_cruft[] = { + "tracker", "1.", "2.", "001.", ".", + "www.", "cfdata.", + 0 + }; + for (const char** cruft = domain_cruft; *cruft; cruft++) { + int cruft_len = strlen(*cruft); + if (url.compare(0, cruft_len, *cruft) == 0) url = url.substr(cruft_len); + } + } + + return url; +} + + +/* @DOC + `compare = <order>, <sort_key>=[, ...]` + + Compares two items like `less=` or `greater=`, but allows to compare + by several different sort criteria, and ascending or descending + order per given field. The first parameter is a string of order + indicators, either `aA+` for ascending or `dD-` for descending. + The default, i.e. when there's more fields than indicators, is + ascending. Field types other than value or string are treated + as equal (or in other words, they're ignored). + + If all fields are equal, then items are ordered in a random, but + stable fashion. + + Configuration example: + + # VIEW: Show active and incomplete torrents (in view #9) and update every 20 seconds + # Items are grouped into complete, incomplete, and queued, in that order. + # Within each group, they're sorted by upload and then download speed. + view_sort_current = active,"compare=----,d.is_open=,d.complete=,d.up.rate=,d.down.rate=" + schedule = filter_active,12,20,"view.filter = active,\"or={d.up.rate=,d.down.rate=,not=$d.complete=}\" ;view.sort=active" +*/ +torrent::Object apply_compare(rpc::target_type target, const torrent::Object::list_type& args) { + if (!rpc::is_target_pair(target)) + throw torrent::input_error("Can only compare a target pair."); + + if (args.size() < 2) + throw torrent::input_error("Need at least order and one field."); + + torrent::Object::list_const_iterator itr = args.begin(); + std::string order = (itr++)->as_string(); + const char* current = order.c_str(); + + torrent::Object result1; + torrent::Object result2; + + for (torrent::Object::list_const_iterator last = args.end(); itr != last; itr++) { + std::string field = itr->as_string(); + result1 = rpc::parse_command_single(rpc::get_target_left(target), field); + result2 = rpc::parse_command_single(rpc::get_target_right(target), field); + + if (result1.type() != result2.type()) + throw torrent::input_error(std::string("Type mismatch in compare of ") + field); + + bool descending = *current == 'd' || *current == 'D' || *current == '-'; + if (*current) { + if (!descending && !(*current == 'a' || *current == 'A' || *current == '+')) + throw torrent::input_error(std::string("Bad order '") + *current + "' in " + order); + ++current; + } + + switch (result1.type()) { + case torrent::Object::TYPE_VALUE: + if (result1.as_value() != result2.as_value()) + return (int64_t) (descending ^ (result1.as_value() < result2.as_value())); + break; + + case torrent::Object::TYPE_STRING: + if (result1.as_string() != result2.as_string()) + return (int64_t) (descending ^ (result1.as_string() < result2.as_string())); + break; + + default: + break; // treat unknown types as equal + } + } + + // if all else is equal, ensure stable sort order based on memory location + return (int64_t) (target.second < target.third); +} + + +/* @DOC + `system.random = [[<lower>,] <upper>]` + + Generate *uniformly* distributed random numbers in the range + defined by `lower`..`upper`. + + The default range is `0`..`RAND_MAX`, providing just one + argument sets the upper bound. The range is inclusive. + + An example use-case is adding jitter to time values that you + later check with `elapsed.greater`, to avoid load spikes and + similar effects of clustered time triggers. +*/ +torrent::Object apply_random(rpc::target_type target, const torrent::Object::list_type& args) { + int64_t lo = 0, hi = RAND_MAX; + + torrent::Object::list_const_iterator itr = args.begin(); + if (args.size() > 0) { + hi = (itr++)->as_value(); + } + if (args.size() > 1) { + lo = hi; + hi = (itr++)->as_value(); + } + if (args.size() > 2) { + throw torrent::input_error("system.random accepts at most two arguments!"); + } + if (lo > hi) { + throw torrent::input_error("Empty interval passed to system.random (low > high)!"); + } + if (lo < 0 || RAND_MAX < lo) { + throw torrent::input_error("Lower bound of system.random range outside 0..RAND_MAX!"); + } + if (hi < 0 || RAND_MAX < hi) { + throw torrent::input_error("Upper bound of system.random range outside 0..RAND_MAX!"); + } + + int32_t rval; + const int64_t range = 1 + hi - lo; + const int64_t buckets = RAND_MAX / range; + const int64_t limit = buckets * range; + + /* Create equal size buckets all in a row, then fire randomly towards + * the buckets until you land in one of them. All buckets are equally + * likely. If you land off the end of the line of buckets, try again. */ + do { + if (random_r(&system_random_data, &rval) == -1) { + throw torrent::input_error("system.random: random_r() failure!"); + } + } while (rval >= limit); + + return (int64_t) lo + (rval / buckets); +} + + +static std::map<int, std::string> bound_commands[ui::DownloadList::DISPLAY_MAX_SIZE]; + +/* @DOC + ui.bind_key=display,key,"command1=[,...]" + + Binds the given key on a specified display to execute the commands when pressed. + + "display" must be one of "download_list", ... + "key" can be either a single character for normal keys, + ^ plus a character for control keys, or a 4 digit octal key code. + + Configuration example: + # VIEW: Bind view #7 to the "rtcontrol" result + schedule = bind_7,1,0,"ui.bind_key=download_list,7,ui.current_view.set=rtcontrol" +*/ +torrent::Object apply_ui_bind_key(rpc::target_type target, const torrent::Object& rawArgs) { + const torrent::Object::list_type& args = rawArgs.as_list(); + + if (args.size() != 3) + throw torrent::input_error("Expecting display, key, and commands."); + + // Parse positional arguments + torrent::Object::list_const_iterator itr = args.begin(); + const std::string& element = (itr++)->as_string(); + const std::string& keydef = (itr++)->as_string(); + const std::string& commands = (itr++)->as_string(); + + // Get key index from definition + if (keydef.empty() || keydef.size() > (keydef[0] == '0' ? 4 : keydef[0] == '^' ? 2 : 1)) + throw torrent::input_error("Bad key definition."); + int key = keydef[0]; + if (key == '^' && keydef.size() > 1) key = keydef[1] & 31; + if (key == '0' && keydef.size() != 1) { + if (keydef.size() != 4) + throw torrent::input_error("Bad key definition (expected 4 digit octal code)."); + key = (int) strtol(keydef.c_str(), (char **) NULL, 8); + } + + // Look up display + ui::DownloadList::Display displayType = ui::DownloadList::DISPLAY_MAX_SIZE; + if (element == "download_list") { + displayType = ui::DownloadList::DISPLAY_DOWNLOAD_LIST; + } else { + throw torrent::input_error(std::string("Unknown display ") + element); + } + ui::DownloadList* dl_list = control->ui()->download_list(); + if (!dl_list) + throw torrent::input_error("No download list."); + ui::ElementBase* display = dl_list->display(displayType); + if (!display) + throw torrent::input_error("Display not found."); + + // Bind the key to the given commands + bool new_binding = display->bindings().find(key) == display->bindings().end(); + bound_commands[displayType][key] = commands; // keep hold of the string, so the c_str() below remains valid + switch (displayType) { + case ui::DownloadList::DISPLAY_DOWNLOAD_LIST: + display->bindings()[key] = +#if RT_HEX_VERSION < 0x000904 + sigc::bind(sigc::mem_fun(*(ui::ElementDownloadList*)display, &ui::ElementDownloadList::receive_command), +#else + _cxxstd_::bind(&ui::ElementDownloadList::receive_command, (ui::ElementDownloadList*)display, +#endif + bound_commands[displayType][key].c_str()); + break; + default: + return torrent::Object(); + } + + if (!new_binding) { + std::string msg = "Replaced key binding"; + msg += " for " + keydef + " in " + element + " with " + commands.substr(0, 30); + if (commands.size() > 30) msg += "..."; + control->core()->push_log(msg.c_str()); + } + + return torrent::Object(); +} + + +torrent::Object cmd_ui_focus_home() { + ui::DownloadList* dl_list = control->ui()->download_list(); + core::View* dl_view = dl_list->current_view(); + + if (!dl_view->empty_visible()) { + dl_view->set_focus(dl_view->begin_visible()); + dl_view->set_last_changed(); + } + + return torrent::Object(); +} + + +torrent::Object cmd_ui_focus_end() { + ui::DownloadList* dl_list = control->ui()->download_list(); + core::View* dl_view = dl_list->current_view(); + + if (!dl_view->empty_visible()) { + dl_view->set_focus(dl_view->end_visible() - 1); + dl_view->set_last_changed(); + } + + return torrent::Object(); +} + + +static int ui_page_size() { + // TODO: map 0 to the current view size, for adaptive scrolling + return std::max(1, (int) rpc::call_command_value("ui.focus.page_size")); +} + + +torrent::Object cmd_ui_focus_pgup() { + ui::DownloadList* dl_list = control->ui()->download_list(); + core::View* dl_view = dl_list->current_view(); + + int skip = ui_page_size(); + if (!dl_view->empty_visible()) { + if (dl_view->focus() == dl_view->end_visible()) + dl_view->set_focus(dl_view->end_visible() - 1); + else if (dl_view->focus() - dl_view->begin_visible() >= skip) + dl_view->set_focus(dl_view->focus() - skip); + else + dl_view->set_focus(dl_view->begin_visible()); + dl_view->set_last_changed(); + } + + return torrent::Object(); +} + + +torrent::Object cmd_ui_focus_pgdn() { + ui::DownloadList* dl_list = control->ui()->download_list(); + core::View* dl_view = dl_list->current_view(); + + int skip = ui_page_size(); + if (!dl_view->empty_visible()) { + if (dl_view->focus() == dl_view->end_visible()) + dl_view->set_focus(dl_view->begin_visible()); + else if (dl_view->end_visible() - dl_view->focus() > skip) + dl_view->set_focus(dl_view->focus() + skip); + else + dl_view->set_focus(dl_view->end_visible() - 1); + dl_view->set_last_changed(); + } + + return torrent::Object(); +} + + +torrent::Object cmd_log_messages(const torrent::Object::string_type& arg) { + if (arg.empty()) { + control->core()->push_log_std("Closing message log file."); + } + + if (core::log_messages_fd >= 0) { + ::close(core::log_messages_fd); + core::log_messages_fd = -1; + } + + if (!arg.empty()) { + core::log_messages_fd = open(rak::path_expand(arg).c_str(), O_WRONLY | O_APPEND | O_CREAT, 0644); + + if (core::log_messages_fd < 0) { + throw torrent::input_error("Could not open message log file."); + } + + control->core()->push_log_std("Opened message log file '" + rak::path_expand(arg) + "'."); + } + + return torrent::Object(); +} + + +// Backports from 0.9.2 +#if (API_VERSION < 3) +template <typename InputIterator, typename OutputIterator> OutputIterator +pyro_transform_hex(InputIterator first, InputIterator last, OutputIterator dest) { + const char* hex = "0123456789abcdef"; + while (first != last) { + *(dest++) = (*first >> 4)[hex]; + *(dest++) = (*first & 15)[hex]; + + ++first; + } + + return dest; +} + + +torrent::Object d_chunks_seen(core::Download* download) { + const uint8_t* seen = download->download()->chunks_seen(); + + if (seen == NULL) + return std::string(); + + uint32_t size = download->download()->file_list()->size_chunks(); + std::string result; + result.resize(size * 2); + pyro_transform_hex((const char*)seen, (const char*)seen + size, result.begin()); + + return result; +} +#endif + + +torrent::Object cmd_d_tracker_domain(core::Download* download) { + return get_active_tracker_domain(download->download()); +} + + +#if RT_HEX_VERSION <= 0x000906 +// https://github.com/rakshasa/rtorrent/commit/1f5e4d37d5229b63963bb66e76c07ec3e359ecba +torrent::Object cmd_system_env(const torrent::Object::string_type& arg) { + if (arg.empty()) { + throw torrent::input_error("system.env: Missing variable name."); + } + + char* val = getenv(arg.c_str()); + return std::string(val ? val : ""); +} + +// https://github.com/rakshasa/rtorrent/commit/30d8379391ad4cb3097d57aa56a488d061e68662 +torrent::Object cmd_ui_current_view() { + return control->ui()->download_list()->current_view()->name(); +} +#endif + + +void initialize_command_pyroscope() { + unsigned int seed = cachedTime.usec() ^ (getpid() << 16) ^ getppid(); + initstate_r(seed, system_random_state, RANDOM_STATESIZE, &system_random_data); + +// Backports from 0.9.2 +#if (API_VERSION < 3) + // https://github.com/rakshasa/rtorrent/commit/b28f2ea8070 + // https://github.com/rakshasa/rtorrent/commit/020de10f38210a07a567aeebbe385a4faaf4b517 + CMD2_DL("d.chunks_seen", _cxxstd_::bind(&d_chunks_seen, _cxxstd_::placeholders::_1)); + + // https://github.com/rakshasa/rtorrent/commit/5bed4f01ad + CMD2_TRACKER("t.is_usable", _cxxstd_::bind(&torrent::Tracker::is_usable, _cxxstd_::placeholders::_1)); + CMD2_TRACKER("t.is_busy", _cxxstd_::bind(&torrent::Tracker::is_busy, _cxxstd_::placeholders::_1)); +#endif + +#if RT_HEX_VERSION <= 0x000906 + // these are merged into 0.9.7+ mainline! + CMD2_ANY_STRING("system.env", _cxxstd_::bind(&cmd_system_env, _cxxstd_::placeholders::_2)); + CMD2_ANY("ui.current_view", _cxxstd_::bind(&cmd_ui_current_view)); +#endif + + CMD2_ANY_LIST("compare", &apply_compare); + CMD2_ANY_LIST("system.random", &apply_random); + CMD2_ANY("ui.bind_key", &apply_ui_bind_key); + CMD2_DL("d.tracker_domain", _cxxstd_::bind(&cmd_d_tracker_domain, _cxxstd_::placeholders::_1)); + CMD2_ANY_STRING("log.messages", _cxxstd_::bind(&cmd_log_messages, _cxxstd_::placeholders::_2)); + CMD2_ANY("ui.focus.home", _cxxstd_::bind(&cmd_ui_focus_home)); + CMD2_ANY("ui.focus.end", _cxxstd_::bind(&cmd_ui_focus_end)); + CMD2_ANY("ui.focus.pgup", _cxxstd_::bind(&cmd_ui_focus_pgup)); + CMD2_ANY("ui.focus.pgdn", _cxxstd_::bind(&cmd_ui_focus_pgdn)); + CMD2_VAR_VALUE("ui.focus.page_size", 50); +} diff --git a/ps-info-pane-xb-sizes_all.patch b/ps-info-pane-xb-sizes_all.patch new file mode 100644 index 000000000000..1267d3f8a3e8 --- /dev/null +++ b/ps-info-pane-xb-sizes_all.patch @@ -0,0 +1,21 @@ +--- rel-0.9.4/src/ui/download.cc 2014-05-14 16:30:51.000000000 +0200 ++++ rtorrent-0.9.4/src/ui/download.cc 2015-08-11 03:31:14.000000000 +0200 +@@ -171,6 +171,6 @@ + element->push_back(""); + element->push_column("Memory usage:", te_command("cat=$convert.mb=$pieces.memory.current=,\" MB\"")); +- element->push_column("Max memory usage:", te_command("cat=$convert.mb=$pieces.memory.max=,\" MB\"")); +- element->push_column("Free diskspace:", te_command("cat=$convert.mb=$d.free_diskspace=,\" MB\"")); ++ element->push_column("Max memory usage:", te_command("cat=$convert.mb=$pieces.memory.max=,\" MB = \",$convert.xb=$pieces.memory.max=")); ++ element->push_column("Free diskspace:", te_command("cat=$convert.mb=$d.free_diskspace=,\" MB = \",$convert.xb=$d.free_diskspace=")); + element->push_column("Safe diskspace:", te_command("cat=$convert.mb=$pieces.sync.safe_free_diskspace=,\" MB\"")); + +@@ -192,7 +192,7 @@ + + element->push_back(""); +- element->push_column("Upload:", te_command("cat=$convert.kb=$d.up.rate=,\" KB / \",$convert.xb=$d.up.total=")); +- element->push_column("Download:", te_command("cat=$convert.kb=$d.down.rate=,\" KB / \",$convert.xb=$d.down.total=")); +- element->push_column("Skipped:", te_command("cat=$convert.kb=$d.skip.rate=,\" KB / \",$convert.xb=$d.skip.total=")); ++ element->push_column("Upload:", te_command("cat=$convert.xb=$d.up.rate=,\" / \",$convert.xb=$d.up.total=")); ++ element->push_column("Download:", te_command("cat=$convert.xb=$d.down.rate=,\" / \",$convert.xb=$d.down.total=")); ++ element->push_column("Skipped:", te_command("cat=$convert.xb=$d.skip.rate=,\" / \",$convert.xb=$d.skip.total=")); + element->push_column("Preload:", te_command("cat=$pieces.preload.type=,\" / \",$pieces.stats_preloaded=,\" / \",$pieces.stats_preloaded=")); diff --git a/ps-item-stats-human-sizes_all.patch b/ps-item-stats-human-sizes_all.patch new file mode 100644 index 000000000000..4d35520d7f9f --- /dev/null +++ b/ps-item-stats-human-sizes_all.patch @@ -0,0 +1,35 @@ +--- rel-0.9.4/src/display/utils.cc 2012-02-14 04:32:01.000000000 +0100 ++++ rtorrent-0.9.4/src/display/utils.cc 2015-08-11 04:35:46.000000000 +0200 +@@ -133,4 +133,6 @@ + } + ++std::string human_size(int64_t bytes, unsigned int format=0); ++ + char* + print_download_info(char* first, char* last, core::Download* d) { +@@ -142,15 +144,16 @@ + first = print_buffer(first, last, " "); + ++ std::string h_size = human_size(d->download()->file_list()->size_bytes(), 0); ++ std::string h_done = human_size(d->download()->bytes_done(), 0); + if (d->is_done()) +- first = print_buffer(first, last, "done %10.1f MB", (double)d->download()->file_list()->size_bytes() / (double)(1 << 20)); ++ first = print_buffer(first, last, " done %s ", h_size.c_str()); + else +- first = print_buffer(first, last, "%6.1f / %6.1f MB", +- (double)d->download()->bytes_done() / (double)(1 << 20), +- (double)d->download()->file_list()->size_bytes() / (double)(1 << 20)); +- +- first = print_buffer(first, last, " Rate: %5.1f / %5.1f KB Uploaded: %7.1f MB", +- (double)d->info()->up_rate()->rate() / (1 << 10), +- (double)d->info()->down_rate()->rate() / (1 << 10), +- (double)d->info()->up_rate()->total() / (1 << 20)); ++ first = print_buffer(first, last, "%s / %s ", h_done.c_str(), h_size.c_str()); ++ ++ std::string h_up = human_size(d->info()->up_rate()->rate(), 0); ++ std::string h_down = human_size(d->info()->down_rate()->rate(), 0); ++ std::string h_sum = human_size(d->info()->up_rate()->total(), 0); ++ first = print_buffer(first, last, " Rate: %s / %s Uploaded: %s ", ++ h_up.c_str(), h_down.c_str(), h_sum.c_str()); + + if (d->download()->info()->is_active() && !d->is_done()) { diff --git a/ps-ssl_verify_host_all.patch b/ps-ssl_verify_host_all.patch new file mode 100644 index 000000000000..adb5d66f9ae0 --- /dev/null +++ b/ps-ssl_verify_host_all.patch @@ -0,0 +1,28 @@ +--- rel-0.9.5/src/core/curl_stack.h 2015-07-02 00:32:45.000000000 +0200 ++++ rtorrent-0.9.5/src/core/curl_stack.h 2015-08-08 22:44:11.000000000 +0200 +@@ -104,2 +104,4 @@ + void set_ssl_verify_peer(bool s) { m_ssl_verify_peer = s; } ++ bool ssl_verify_host() const { return m_ssl_verify_host; } ++ void set_ssl_verify_host(bool s) { m_ssl_verify_host = s; } + +@@ -143,2 +145,3 @@ + bool m_ssl_verify_peer; ++ bool m_ssl_verify_host; + long m_dns_timeout; +--- rel-0.9.5/src/core/curl_stack.cc 2015-07-02 00:32:45.000000000 +0200 ++++ rtorrent-0.9.5/src/core/curl_stack.cc 2015-08-08 22:47:37.000000000 +0200 +@@ -54,2 +54,3 @@ + m_ssl_verify_peer(true), ++ m_ssl_verify_host(true), + m_dns_timeout(60) { +@@ -183,2 +184,3 @@ + curl_easy_setopt(get->handle(), CURLOPT_SSL_VERIFYPEER, (long)m_ssl_verify_peer); ++ curl_easy_setopt(get->handle(), CURLOPT_SSL_VERIFYHOST, (long)(m_ssl_verify_host ? 2 : 0)); + curl_easy_setopt(get->handle(), CURLOPT_DNS_CACHE_TIMEOUT, m_dns_timeout); +--- rel-0.9.5/src/command_network.cc 2015-07-02 00:32:45.000000000 +0200 ++++ rtorrent-0.9.5/src/command_network.cc 2015-08-08 22:49:39.000000000 +0200 +@@ -276,2 +276,4 @@ + CMD2_ANY_VALUE_V ("network.http.ssl_verify_peer.set", tr1::bind(&core::CurlStack::set_ssl_verify_peer, httpStack, tr1::placeholders::_2)); ++ CMD2_ANY ("network.http.ssl_verify_host", tr1::bind(&core::CurlStack::ssl_verify_host, httpStack)); ++ CMD2_ANY_VALUE_V ("network.http.ssl_verify_host.set", tr1::bind(&core::CurlStack::set_ssl_verify_host, httpStack, tr1::placeholders::_2)); + CMD2_ANY ("network.http.dns_cache_timeout", tr1::bind(&core::CurlStack::dns_timeout, httpStack)); diff --git a/ps-throttle-steps_all.patch b/ps-throttle-steps_all.patch new file mode 100644 index 000000000000..a68d6a4d09a5 --- /dev/null +++ b/ps-throttle-steps_all.patch @@ -0,0 +1,68 @@ +--- rel-0.9.5/src/ui/root.cc 2015-07-02 00:32:45.000000000 +0200 ++++ rtorrent-0.9.5/src/ui/root.cc 2015-07-25 18:14:20.000000000 +0200 +@@ -141,18 +141,18 @@ + const char* keys = get_throttle_keys(); + +- m_bindings[keys[ 0]] = std::tr1::bind(&Root::adjust_up_throttle, this, 1); +- m_bindings[keys[ 1]] = std::tr1::bind(&Root::adjust_up_throttle, this, -1); +- m_bindings[keys[ 2]] = std::tr1::bind(&Root::adjust_down_throttle, this, 1); +- m_bindings[keys[ 3]] = std::tr1::bind(&Root::adjust_down_throttle, this, -1); +- +- m_bindings[keys[ 4]] = std::tr1::bind(&Root::adjust_up_throttle, this, 5); +- m_bindings[keys[ 5]] = std::tr1::bind(&Root::adjust_up_throttle, this, -5); +- m_bindings[keys[ 6]] = std::tr1::bind(&Root::adjust_down_throttle, this, 5); +- m_bindings[keys[ 7]] = std::tr1::bind(&Root::adjust_down_throttle, this, -5); +- +- m_bindings[keys[ 8]] = std::tr1::bind(&Root::adjust_up_throttle, this, 50); +- m_bindings[keys[ 9]] = std::tr1::bind(&Root::adjust_up_throttle, this, -50); +- m_bindings[keys[10]] = std::tr1::bind(&Root::adjust_down_throttle, this, 50); +- m_bindings[keys[11]] = std::tr1::bind(&Root::adjust_down_throttle, this, -50); ++ m_bindings[keys[ 0]] = std::tr1::bind(&Root::adjust_up_throttle, this, 5); ++ m_bindings[keys[ 1]] = std::tr1::bind(&Root::adjust_up_throttle, this, -5); ++ m_bindings[keys[ 2]] = std::tr1::bind(&Root::adjust_down_throttle, this, 5); ++ m_bindings[keys[ 3]] = std::tr1::bind(&Root::adjust_down_throttle, this, -5); ++ ++ m_bindings[keys[ 4]] = std::tr1::bind(&Root::adjust_up_throttle, this, 50); ++ m_bindings[keys[ 5]] = std::tr1::bind(&Root::adjust_up_throttle, this, -50); ++ m_bindings[keys[ 6]] = std::tr1::bind(&Root::adjust_down_throttle, this, 50); ++ m_bindings[keys[ 7]] = std::tr1::bind(&Root::adjust_down_throttle, this, -50); ++ ++ m_bindings[keys[ 8]] = std::tr1::bind(&Root::adjust_up_throttle, this, 500); ++ m_bindings[keys[ 9]] = std::tr1::bind(&Root::adjust_up_throttle, this, -500); ++ m_bindings[keys[10]] = std::tr1::bind(&Root::adjust_down_throttle, this, 500); ++ m_bindings[keys[11]] = std::tr1::bind(&Root::adjust_down_throttle, this, -500); + + m_bindings['\x0C'] = std::tr1::bind(&display::Manager::force_redraw, m_control->display()); // ^L +--- rel-0.9.5/src/ui/download.cc 2015-07-02 00:32:45.000000000 +0200 ++++ rtorrent-0.9.5/src/ui/download.cc 2015-07-25 18:06:34.000000000 +0200 +@@ -396,18 +396,18 @@ + const char* keys = control->ui()->get_throttle_keys(); + +- m_bindings[keys[ 0]] = std::tr1::bind(&Download::adjust_up_throttle, this, 1); +- m_bindings[keys[ 1]] = std::tr1::bind(&Download::adjust_up_throttle, this, -1); +- m_bindings[keys[ 2]] = std::tr1::bind(&Download::adjust_down_throttle, this, 1); +- m_bindings[keys[ 3]] = std::tr1::bind(&Download::adjust_down_throttle, this, -1); ++ m_bindings[keys[ 0]] = std::tr1::bind(&Download::adjust_up_throttle, this, 5); ++ m_bindings[keys[ 1]] = std::tr1::bind(&Download::adjust_up_throttle, this, -5); ++ m_bindings[keys[ 2]] = std::tr1::bind(&Download::adjust_down_throttle, this, 5); ++ m_bindings[keys[ 3]] = std::tr1::bind(&Download::adjust_down_throttle, this, -5); + +- m_bindings[keys[ 4]] = std::tr1::bind(&Download::adjust_up_throttle, this, 5); +- m_bindings[keys[ 5]] = std::tr1::bind(&Download::adjust_up_throttle, this, -5); +- m_bindings[keys[ 6]] = std::tr1::bind(&Download::adjust_down_throttle, this, 5); +- m_bindings[keys[ 7]] = std::tr1::bind(&Download::adjust_down_throttle, this, -5); ++ m_bindings[keys[ 4]] = std::tr1::bind(&Download::adjust_up_throttle, this, 50); ++ m_bindings[keys[ 5]] = std::tr1::bind(&Download::adjust_up_throttle, this, -50); ++ m_bindings[keys[ 6]] = std::tr1::bind(&Download::adjust_down_throttle, this, 50); ++ m_bindings[keys[ 7]] = std::tr1::bind(&Download::adjust_down_throttle, this, -50); + +- m_bindings[keys[ 8]] = std::tr1::bind(&Download::adjust_up_throttle, this, 50); +- m_bindings[keys[ 9]] = std::tr1::bind(&Download::adjust_up_throttle, this, -50); +- m_bindings[keys[10]] = std::tr1::bind(&Download::adjust_down_throttle, this, 50); +- m_bindings[keys[11]] = std::tr1::bind(&Download::adjust_down_throttle, this, -50); ++ m_bindings[keys[ 8]] = std::tr1::bind(&Download::adjust_up_throttle, this, 500); ++ m_bindings[keys[ 9]] = std::tr1::bind(&Download::adjust_up_throttle, this, -500); ++ m_bindings[keys[10]] = std::tr1::bind(&Download::adjust_down_throttle, this, 500); ++ m_bindings[keys[11]] = std::tr1::bind(&Download::adjust_down_throttle, this, -500); + } + diff --git a/ps-ui_pyroscope_all.patch b/ps-ui_pyroscope_all.patch new file mode 100644 index 000000000000..4fb11b74a31e --- /dev/null +++ b/ps-ui_pyroscope_all.patch @@ -0,0 +1,7 @@ +--- orig/src/rpc/object_storage.h 2011-04-07 09:44:06.000000000 +0200 ++++ rtorrent-0.8.8/src/rpc/object_storage.h 2011-06-05 13:20:24.000000000 +0200 +@@ -124,2 +124,4 @@ + ++ const torrent::Object& set_color_string(const torrent::raw_string& key, const std::string& object); ++ + // Functions callers: diff --git a/pyroscope.patch b/pyroscope.patch new file mode 100644 index 000000000000..38ff3a5bdfac --- /dev/null +++ b/pyroscope.patch @@ -0,0 +1,26 @@ +--- rtorrent-0.8.6/src/ui/download_list.h 2011-05-03 04:05:34.000000000 +0200 ++++ rtorrent-0.8.6/src/ui/download_list.h,pyro 2011-05-03 04:11:23.000000000 +0200 +@@ -99,6 +99,7 @@ + void disable(); + + void activate_display(Display d); ++ ElementBase* display(Display d) { return d < DISPLAY_MAX_SIZE ? m_uiArray[d] : 0; } + + core::View* current_view(); + void set_current_view(const std::string& name); +--- rtorrent-0.8.7/src/command_helpers.cc.orig 2010-06-26 14:05:08.000000000 +0200 ++++ rtorrent-0.8.7/src/command_helpers.cc 2011-05-06 19:42:58.000000000 +0200 +@@ -50,2 +50,3 @@ + void initialize_command_local(); ++void initialize_command_pyroscope(); + void initialize_command_network(); +@@ -61,2 +62,3 @@ + initialize_command_local(); ++ initialize_command_pyroscope(); + initialize_command_ui(); +--- rtorrent-0.8.7/src/Makefile.am.orig 2010-06-26 14:05:08.000000000 +0200 ++++ rtorrent-0.8.7/src/Makefile.am 2011-05-06 19:40:03.000000000 +0200 +@@ -23,2 +23,3 @@ + command_ui.cc \ ++ command_pyroscope.cc \ + control.cc \ diff --git a/ui_pyroscope.cc b/ui_pyroscope.cc new file mode 100644 index 000000000000..fd80f174b584 --- /dev/null +++ b/ui_pyroscope.cc @@ -0,0 +1,807 @@ +/* + ⋅ ⋅⋅ ” ’ ♯ ☢ ☍ ⌘ ✰ ⣿ ⚡ ☯ ⚑ ↺ ⤴ ⤵ ∆ ⌚ ≀∇ ✇ ⚠ ◔ ⚡ ↯ ¿ ⨂ ✖ ⇣ ⇡ ⠁ ⠉ ⠋ ⠛ ⠟ ⠿ ⡿ ⣿ ☹ ➀ ➁ ➂ ➃ ➄ ➅ ➆ ➇ ➈ ➉ ▹ ╍ ▪ ⚯ ⚒ ◌ ⇅ ↡ ↟ ⊛ ♺ + + ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ + + +python -c 'print u"\u22c5 \u22c5\u22c5 \u201d \u2019 \u266f \u2622 \u260d \u2318 \u2730 " \ + u"\u28ff \u26a1 \u262f \u2691 \u21ba \u2934 \u2935 \u2206 \u231a \u2240\u2207 \u2707 " \ + u"\u26a0\xa0\u25d4 \u26a1\xa0\u21af \xbf \u2a02 \u2716 \u21e3 \u21e1 \u2801 \u2809 " \ + u"\u280b \u281b \u281f \u283f \u287f \u28ff \u2639 \u2780 \u2781 \u2782 \u2783 \u2784 " \ + u"\u2785 \u2786 \u2787 \u2788 \u2789 \u25b9\xa0\u254d \u25aa \u26af \u2692 \u25cc " \ + u"\u21c5 \u21a1 \u219f \u229b \u267a ".encode("utf8")' +*/ + +#include "ui_pyroscope.h" + +#include "config.h" +#include "globals.h" + +#include <cstdio> +#include <list> +#include <stdlib.h> +#include <unistd.h> + +#include <rak/algorithm.h> + +#include "core/view.h" +#include "core/manager.h" +#include "core/download.h" +#include "torrent/tracker.h" +#include "torrent/rate.h" +#include "display/window.h" +#include "display/canvas.h" +#include "display/utils.h" +#include "ui/root.h" +#include "ui/download_list.h" + +#include "control.h" +#include "command_helpers.h" + +#if (RT_HEX_VERSION >= 0x000901) + #define _cxxstd_ tr1 +#else + #define _cxxstd_ std +#endif + +#define D_INFO(item) (item->info()) +#include "rpc/object_storage.h" + +// from command_pyroscope.cc +extern torrent::Tracker* get_active_tracker(torrent::Download* item); +extern std::string get_active_tracker_domain(torrent::Download* item); + + +#define TRACKER_LABEL_WIDTH 20U + +// definition from display/window_download_list.cc that is not in the header file +typedef std::pair<core::View::iterator, core::View::iterator> Range; + +// display attribute map (normal, even, odd) +static unsigned long attr_map[3 * ps::COL_MAX] = {0}; + +// color indices for progress indication +int ratio_col[] = { + ps::COL_PROGRESS0, ps::COL_PROGRESS20, ps::COL_PROGRESS40, ps::COL_PROGRESS60, ps::COL_PROGRESS80, + ps::COL_PROGRESS100, ps::COL_PROGRESS120, +}; + +// basic color names +static const char* color_names[] = { + "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white" +}; + +// list of color configuration variables, the order MUST correspond to the ColorKind enum +static const char* color_vars[ps::COL_MAX] = { + 0, + "ui.color.progress0", + "ui.color.progress20", + "ui.color.progress40", + "ui.color.progress60", + "ui.color.progress80", + "ui.color.progress100", + "ui.color.progress120", + "ui.color.complete", + "ui.color.seeding", + "ui.color.stopped", + "ui.color.queued", + "ui.color.incomplete", + "ui.color.leeching", + "ui.color.alarm", + "ui.color.title", + "ui.color.footer", + "ui.color.label", + "ui.color.odd", + "ui.color.even", + "ui.color.info", + "ui.color.focus", +}; + +// collapsed state of views (default is false) +static std::map<std::string, bool> is_collapsed; + +// Traffic history +static int network_history_depth = 0; +static uint32_t network_history_count = 0; +static uint32_t* network_history_up = 0; +static uint32_t* network_history_down = 0; +static std::string network_history_up_str; +static std::string network_history_down_str; + + +// get custom field contaioning a long (time_t) +unsigned long get_custom_long(core::Download* d, const char* name) { + try { + return atol(d->bencode()->get_key("rtorrent").get_key("custom").get_key_string(name).c_str()); + } catch (torrent::bencode_error& e) { + return 0UL; + } +} + + +// get custom field contaioning a string +std::string get_custom_string(core::Download* d, const char* name) { + try { + return d->bencode()->get_key("rtorrent").get_key("custom").get_key_string(name); + } catch (torrent::bencode_error& e) { + return ""; + } +} + + +// convert absolute timestamp to approximate human readable time diff (5 chars wide) +std::string elapsed_time(unsigned long dt) { + if (dt == 0) return std::string("⋅ ⋅⋅ "); + + const char* unit[] = {"”", "’", "h", "d", "w", "m", "y"}; + unsigned long threshold[] = {1, 60, 3600, 86400, 7*86400, 30*86400, 365*86400, 0}; + + int dim = 0; + dt = time(NULL) - dt; + while (threshold[dim] && dt >= threshold[dim]) ++dim; + if (dim) --dim; + float val = float(dt) / float(threshold[dim]); + + char buffer[15]; + if (val < 10.0 && dim) { + snprintf(buffer, sizeof(buffer), "%1d%s%2d%s", int(val), unit[dim], + int(dt % threshold[dim] / threshold[dim-1]), unit[dim-1]); + } else { + snprintf(buffer, sizeof(buffer), "%4d%s", int(val), unit[dim]); + } + return std::string(buffer); +} + + +// return 2-digits number, or digit + dimension indicator +std::string num2(int64_t num) { + if (num < 0 || 10*1000*1000 <= num) return std::string("♯♯"); + if (!num) return std::string(" ·"); + + char buffer[10]; + if (num < 100) { + snprintf(buffer, sizeof(buffer), "%2d", int(num)); + } else { + // Roman numeral multipliers 10, 100, 1000, 10x1000, 100x1000, 1000x1000 + const char* roman = " xcmXCM"; + int dim = 0; + while (num > 9) { ++dim; num /= 10; } + snprintf(buffer, sizeof(buffer), "%1d%c", int(num), roman[dim]); + } + + return std::string(buffer); +} + + +namespace display { + + +// function wrapper for what possibly is a macro +static int get_colors() { + return COLORS; +} + + +// format byte size for humans, if format = 0 use 6 chars (one decimal place), +// if = 1 just print the rounded value (4 chars), if = 2 combine the two formats +// into 4 chars by rounding for values >= 9.95. +// set bit 8 of format and 0 values will return a whitespace string of the correct length. +std::string human_size(int64_t bytes, unsigned int format=0) { + if (format & 8 && bytes <= 0) return std::string((format & 7) ? 4 : 6, ' '); + format &= 7; + + int exp; + char unit; + + if (bytes < (int64_t(1000) << 10)) { exp = 10; unit = 'K'; } + else if (bytes < (int64_t(1000) << 20)) { exp = 20; unit = 'M'; } + else if (bytes < (int64_t(1000) << 30)) { exp = 30; unit = 'G'; } + else { exp = 40; unit = 'T'; } + + char buffer[48]; + double value = double(bytes) / (int64_t(1) << exp); + const char* formats[] = {"%5.1f%c", "%3.0f%c", "%3.1f%c"}; + + if (format > 2) format = 0; + if (format == 2 and value >= 9.949999) format = 1; + if (format == 1) value = int(value + 0.50002); + snprintf(buffer, sizeof(buffer), formats[format], value, unit); + + return std::string(buffer); +} + + +// split a given string into words separated by delim, and add them to the provided vector +void split(std::vector<std::string>& words, const char* str, char delim = ' ') { + do { + const char* begin = str; + while (*str && *str != delim) str++; + words.push_back(std::string(begin, str)); + } while (*str++); +} + + +void ui_pyroscope_canvas_init(); // forward +static bool color_init_recursion = false; + +// create color map from configuration strings +void ui_pyroscope_colormap_init() { + // if in early startup stage (configuration), then init the screen so we can query system constants + if (!get_colors()) { + if (color_init_recursion) { + color_init_recursion = false; + control->core()->push_log("Terminal color initialization failed, does your terminal have none?!"); + } else { + color_init_recursion = true; + initscr(); + ui_pyroscope_canvas_init(); // this calls us again! + } + return; + } + color_init_recursion = false; + + // Those hold the background colors of "odd" and "even" + int bg_odd = -1; + int bg_even = -1; + + // read the definition for basic colors from configuration + for (int k = 1; k < ps::COL_MAX; k++) { + init_pair(k, -1, -1); + std::string col_def = rpc::call_command_string(color_vars[k]); + if (col_def.empty()) continue; // use terminal default if definition is empty + + std::vector<std::string> words; + split(words, col_def.c_str()); + + short col[2] = {-1, -1}; // fg, bg + short col_idx = 0; // 0 = fg; 1 = bg + short bright = 0; + unsigned long attr = A_NORMAL; + for (int i = 0; i < words.size(); i++) { // look at all the words + if (words[i] == "bold") attr |= A_BOLD; + else if (words[i] == "standout") attr |= A_STANDOUT; + else if (words[i] == "underline") attr |= A_UNDERLINE; + else if (words[i] == "reverse") attr |= A_REVERSE; + else if (words[i] == "blink") attr |= A_BLINK; + else if (words[i] == "dim") attr |= A_DIM; + else if (words[i] == "on") { col_idx = 1; bright = 0; } // switch to background color + else if (words[i] == "gray") col[col_idx] = bright ? 7 : 8; // bright gray is white + else if (words[i] == "bright") bright = 8; + else if (words[i].find_first_not_of("0123456789") == std::string::npos) { + // handle numeric index + short c = -1; + sscanf(words[i].c_str(), "%hd", &c); + col[col_idx] = c; + } else for (short c = 0; c < 8; c++) { // check for basic color names + if (words[i] == color_names[c]) { + col[col_idx] = bright + c; + break; + } + } + } + + // check that fg & bg color index is valid + if (col[0] != -1 && col[0] >= get_colors() || col[1] != -1 && col[1] >= get_colors()) { + char buf[33]; + sprintf(buf, "%d", get_colors()); + Canvas::cleanup(); + throw torrent::input_error(col_def + ": your terminal only supports " + buf + " colors."); + } + + // store the parsed color definition + attr_map[k] = attr; + init_pair(k, col[0], col[1]); + if (k == ps::COL_EVEN) bg_even = col[1]; + if (k == ps::COL_ODD) bg_odd = col[1]; + } + + // now make copies of the basic colors with the "odd" and "even" definitions mixed in + for (int k = 1; k < ps::COL_MAX; k++) { + short fg, bg; + pair_content(k, &fg, &bg); + + // replace the background color, and mix in the attributes + attr_map[k + 1 * ps::COL_MAX] = attr_map[k] | attr_map[ps::COL_EVEN]; + attr_map[k + 2 * ps::COL_MAX] = attr_map[k] | attr_map[ps::COL_ODD]; + init_pair(k + 1 * ps::COL_MAX, fg, bg == -1 ? bg_even : bg); + init_pair(k + 2 * ps::COL_MAX, fg, bg == -1 ? bg_odd : bg); + } +} + + +// add color handling to canvas initialization +void ui_pyroscope_canvas_init() { + start_color(); + use_default_colors(); + ui_pyroscope_colormap_init(); +} + + +// offset into the color index table, depending on whether this is an odd or even item +static int row_offset(core::View* view, Range& range) { + return (((range.first - view->begin_visible()) & 1) + 1) * ps::COL_MAX; +} + + +static void decorate_download_title(Window* window, display::Canvas* canvas, core::View* view, int pos, Range& range) { + int offset = row_offset(view, range); + core::Download* item = *range.first; + bool active = item->is_open() && item->is_active(); + + // download title color + int title_col; + unsigned long focus_attr = range.first == view->focus() ? attr_map[ps::COL_FOCUS] : 0; + if ((*range.first)->is_done()) + title_col = (active ? D_INFO(item)->up_rate()->rate() ? ps::COL_SEEDING : ps::COL_COMPLETE : ps::COL_STOPPED) + offset; + else + title_col = (active ? D_INFO(item)->down_rate()->rate() ? ps::COL_LEECHING : ps::COL_INCOMPLETE : ps::COL_QUEUED) + offset; + canvas->set_attr(2, pos, -1, attr_map[title_col] | focus_attr, title_col); + + // show label for tracker in focus + std::string url = get_active_tracker_domain((*range.first)->download()); + if (!url.empty()) { + // shorten label if too long + int len = url.length(); + if (len > TRACKER_LABEL_WIDTH) { + url = "…" + url.substr(len - TRACKER_LABEL_WIDTH); + len = TRACKER_LABEL_WIDTH + 1; + } + + // print it right-justified and in braces + int td_col = ps::COL_INFO; + //int td_col = active ? ps::COL_INFO : (*range.first)->is_done() ? ps::COL_STOPPED : ps::COL_QUEUED; + int xpos = canvas->width() - len - 2; + canvas->print(xpos, pos, "{%s}", url.c_str()); + canvas->set_attr(xpos + 1, pos, len, attr_map[td_col + offset] | focus_attr, td_col + offset); + canvas->set_attr(xpos, pos, 1, (attr_map[td_col + offset] | focus_attr) ^ A_BOLD, td_col + offset); + canvas->set_attr(canvas->width() - 1, pos, 1, (attr_map[td_col + offset] | focus_attr) ^ A_BOLD, td_col + offset); + } +} + + +// show ratio progress by color (ratio is scaled x1000) +static int ratio_color(int ratio) { + int rcol = sizeof(ratio_col) / sizeof(*ratio_col) - 1; + return ratio_col[std::min(rcol, ratio * rcol / 1200)]; +} + + +// patch hook for download list canvas redraw of a single item; "pos" is placed AFTER the item +void ui_pyroscope_download_list_redraw_item(Window* window, display::Canvas* canvas, core::View* view, int pos, Range& range) { + int offset = row_offset(view, range); + torrent::Download* item = (*range.first)->download(); + + pos -= 3; + + // is this the item in focus? + if (range.first == view->focus()) { + for (int i = 0; i < 3; i++ ) { + canvas->set_attr(0, pos+i, 1, attr_map[ps::COL_FOCUS], ps::COL_FOCUS); + } + } + + decorate_download_title(window, canvas, view, pos, range); + + // better handling for trail of line 2 (ratio etc.) + int status_pos = 91; + int ratio = rpc::call_command_value("d.get_ratio", rpc::make_target(*range.first)); + + if (status_pos < canvas->width()) { + canvas->print(status_pos, pos+1, "R:%6.2f [%c%c] %-4.4s ", + float(ratio) / 1000.0, + rpc::call_command_string("d.get_tied_to_file", rpc::make_target(*range.first)).empty() ? ' ' : 'T', + (rpc::call_command_value("d.get_ignore_commands", rpc::make_target(*range.first)) == 0) ? ' ' : 'I', + (*range.first)->priority() == 2 ? "" : + rpc::call_command_string("d.get_priority_str", rpc::make_target(*range.first)).c_str() + ); + status_pos += 9 + 5 + 5; + } + + // if space is left, show throttle name + if (status_pos < canvas->width()) { + std::string item_status; + + if (!(*range.first)->bencode()->get_key("rtorrent").get_key_string("throttle_name").empty()) { + //item_status += "T="; + item_status += rpc::call_command_string("d.get_throttle_name", rpc::make_target(*range.first)) + ' '; + } + + // left-justifying this also overwrites any junk from the original display that we overwrite + int chars_left = canvas->width() - status_pos - item_status.length(); + if (chars_left < 0) { + item_status = item_status.substr(0, 1-chars_left) + "…"; + } else if (chars_left > 0) { + item_status = std::string(chars_left, ' ') + item_status; + } + canvas->print(status_pos, pos+1, "%s", item_status.c_str()); + } + + //.........1.........2.........3.........4.........5.........6.........7.........8.........9.........0.........1 + //12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + // [CLOSED] 0,0 / 15,9 MB Rate: 0,0 / 0,0 KB Uploaded: 0,0 MB [ 0%] --d --:-- R:nnnnnn [TI] + // [CLOSED] 0.0K / 0.0K U/D: 0.0K / 0.0K Uploaded: 0.0K R: 0.00 [T ] + int label_pos[] = {19, 1, 31, 5, 44, 1, 54, 9, 75, 1, 79, 1, 91, 2, 100, 1, 103, 1}; + const char* labels[sizeof(label_pos) / sizeof(int) / 2] = {0, " U/D:"}; + int col_active = ps::COL_INFO; + //int col_active = item->is_open() && item->is_active() ? ps::COL_INFO : (*range.first)->is_done() ? ps::COL_STOPPED : ps::COL_QUEUED; + + // apply basic "info" style, and then revert static text to "label" + canvas->set_attr(2, pos+1, canvas->width() - 1, attr_map[col_active + offset], col_active + offset); + for (int label_idx = 0; label_idx < sizeof(label_pos) / sizeof(int); label_idx += 2) { + if (labels[label_idx/2]) canvas->print(label_pos[label_idx], pos+1, labels[label_idx/2]); + canvas->set_attr(label_pos[label_idx], pos+1, label_pos[label_idx+1], attr_map[ps::COL_LABEL + offset], ps::COL_LABEL + offset); + } + + // apply progress color to completion indicator + int pcol = ratio_color(item->file_list()->completed_chunks() * 1000 / item->file_list()->size_chunks()); + canvas->set_attr(76, pos+1, 3, attr_map[pcol + offset], pcol + offset); + + // show ratio progress by color + int rcol = ratio_color(ratio); + canvas->set_attr(93, pos+1, 6, attr_map[rcol + offset], rcol + offset); + + // mark active up / down ("focus", plus "seeding" or "leeching"), and dim inactive numbers (i.e. 0) + canvas->set_attr(37, pos+1, 6, attr_map[ps::COL_SEEDING + offset] | (D_INFO(item)->up_rate()->rate() ? attr_map[ps::COL_FOCUS] : 0), + (D_INFO(item)->up_rate()->rate() ? ps::COL_SEEDING : ps::COL_LABEL) + offset); + canvas->set_attr(46, pos+1, 6, attr_map[ps::COL_LEECHING + offset] | (D_INFO(item)->down_rate()->rate() ? attr_map[ps::COL_FOCUS] : 0), + (D_INFO(item)->down_rate()->rate() ? ps::COL_LEECHING : ps::COL_LABEL) + offset); + + // mark non-trivial messages + if (!(*range.first)->message().empty() && (*range.first)->message().find("Tried all trackers") == std::string::npos) { + canvas->set_attr(1, pos, 1, attr_map[ps::COL_ALARM + offset], ps::COL_ALARM + offset); + canvas->set_attr(1, pos+1, 1, attr_map[ps::COL_ALARM + offset], ps::COL_ALARM + offset); + canvas->set_attr(1, pos+2, -1, attr_map[ps::COL_ALARM + offset], ps::COL_ALARM + offset); + } +} + + +// patch hook for download list canvas redraw; if this returns true, the calling +// function is left immediately (i.e. true indicates we took over ALL redrawing) +bool ui_pyroscope_download_list_redraw(Window* window, display::Canvas* canvas, core::View* view) { + // show "X of Y" + if (canvas->width() > 16) { + int item_idx = view->focus() - view->begin_visible(); + if (item_idx == view->size()) + canvas->print(canvas->width() - 16, 0, "[ none of %-5d]", view->size()); + else + canvas->print(canvas->width() - 16, 0, "[%5d of %-5d]", item_idx + 1, view->size()); + } + canvas->set_attr(0, 0, -1, attr_map[ps::COL_TITLE], ps::COL_TITLE); + + if (is_collapsed.find(view->name()) == is_collapsed.end() || !is_collapsed[view->name()]) + return false; // continue in calling function + + if (view->empty_visible() || canvas->width() < 5 || canvas->height() < 2) + return true; + + // show column headers + int pos = 1; + canvas->print(2, pos, " ☢ ☍ ⌘ ✰ ⣿ ⚡ ☯ ⚑ ↺ ⤴ ⤵ ∆ ⌚ ≀∇ ✇ Name"); + if (canvas->width() > TRACKER_LABEL_WIDTH) { + canvas->print(canvas->width() - 14, 1, "Tracker Domain"); + } + canvas->set_attr(0, pos, -1, attr_map[ps::COL_LABEL], ps::COL_LABEL); + + // network traffic + int network_history_lines = 0; + if (network_history_depth) { + network_history_lines = 2; + pos = canvas->height() - 2; + + canvas->print(0, pos, "%s", network_history_up_str.c_str()); + canvas->set_attr(0, pos, -1, attr_map[ps::COL_SEEDING], ps::COL_SEEDING); + canvas->print(0, pos+1, "%s", network_history_down_str.c_str()); + canvas->set_attr(0, pos+1, -1, attr_map[ps::COL_LEECHING], ps::COL_LEECHING); + } + + // Styles + #define PROGRESS_STEPS 9 + const char* progress[3][PROGRESS_STEPS] = { + {}, + {"⠀ ", "⠁ ", "⠉ ", "⠋ ", "⠛ ", "⠟ ", "⠿ ", "⡿ ", "⣿ "}, + {"⠀ ", "▁ ", "▂ ", "▃ ", "▄ ", "▅ ", "▆ ", "▇ ", "█ "}, + }; + unsigned int progress_style = std::min<unsigned int>(rpc::call_command_value("ui.style.progress"), 2); + #define YING_YANG_STEPS 11 + const char* ying_yang[4][YING_YANG_STEPS] = { + {}, + {"☹ ", "➀ ", "➁ ", "➂ ", "➃ ", "➄ ", "➅ ", "➆ ", "➇ ", "➈ ", "➉ "}, + {"☹ ", "① ", "② ", "③ ", "④ ", "⑤ ", "⑥ ", "⑦ ", "⑧ ", "⑨ ", "⑩ "}, + {"☹ ", "➊ ", "➋ ", "➌ ", "➍ ", "➎ ", "➏ ", "➐ ", "➑ ", "➒ ", "➓ "}, + }; + unsigned int ying_yang_style = std::min<unsigned int>(rpc::call_command_value("ui.style.ratio"), 3); + + // define iterator range + Range range = rak::advance_bidirectional( + view->begin_visible(), + view->focus() != view->end_visible() ? view->focus() : view->begin_visible(), + view->end_visible(), + canvas->height()-2-2-network_history_lines); + + pos = 2; + while (range.first != range.second) { + core::Download* d = *range.first; + core::Download* item = d; + torrent::Tracker* tracker = get_active_tracker((*range.first)->download()); + int ratio = rpc::call_command_value("d.get_ratio", rpc::make_target(d)); + bool has_msg = !d->message().empty(); + bool has_alert = has_msg && d->message().find("Tried all trackers") == std::string::npos; + int offset = row_offset(view, range); + int col_active = ps::COL_INFO; + //int col_active = item->is_open() && item->is_active() ? ps::COL_INFO : d->is_done() ? ps::COL_STOPPED : ps::COL_QUEUED; + + const char* alert = "⚠ "; + if (has_alert) { + if (d->message().find("Timeout was reached") != std::string::npos + || d->message().find("Timed out") != std::string::npos) + alert = "◔ "; + else if (d->message().find("Connecting to") != std::string::npos) + alert = "⚡ "; + else if (d->message().find("Could not parse bencoded data") != std::string::npos + || d->message().find("Failed sending data") != std::string::npos + || d->message().find("Server returned nothing") != std::string::npos + || d->message().find("Couldn't connect to server") != std::string::npos) + alert = "↯ "; + else if (d->message().find("not registered") != std::string::npos + || d->message().find("torrent cannot be found") != std::string::npos + || d->message().find("unregistered") != std::string::npos) + alert = "¿?"; + else if (d->message().find("not authorized") != std::string::npos + || d->message().find("blocked from") != std::string::npos + || d->message().find("denied") != std::string::npos + || d->message().find("limit exceeded") != std::string::npos + || d->message().find("active torrents are enough") != std::string::npos) + alert = "⨂ "; + } + + const char* prios[] = {"✖ ", "⇣ ", " ", "⇡ "}; + + std::string displayname = get_custom_string(d, "displayname"); + int is_tagged = rpc::commands.call_command_d("d.views.has", d, torrent::Object("tagged")).as_value() == 1; + uint32_t down_rate = D_INFO(item)->down_rate()->rate(); + char buffer[canvas->width() + 1]; + char* last = buffer + canvas->width() - 2 + 1; + print_download_title(buffer, last, d); + + char progress_str[6] = "##"; + char ying_yang_str[6] = "##"; + if (progress_style == 0) { + sprintf(progress_str, item->file_list()->completed_chunks() ? "%2.2d" : "--", + item->file_list()->completed_chunks() * 100 / item->file_list()->size_chunks()); + } + if (ying_yang_style == 0 && ratio < 9949) { + sprintf(ying_yang_str, ratio ? "%2.2d" : "--", ratio / 100); + } + + canvas->print(0, pos, "%s %s%s%s%s%s%s%s%s %s %s %s %s %s%s %s%s%s", + range.first == view->focus() ? "»" : " ", + item->is_open() ? item->is_active() ? "▹ " : "╍ " : "▪ ", + rpc::call_command_string("d.get_tied_to_file", rpc::make_target(d)).empty() ? " " : "⚯ ", + rpc::call_command_value("d.get_ignore_commands", rpc::make_target(d)) == 0 ? "⚒ " : "◌ ", + prios[d->priority() % 4], + d->is_done() ? "✔ " : progress_style == 0 ? progress_str : progress[progress_style][ + item->file_list()->completed_chunks() * PROGRESS_STEPS + / item->file_list()->size_chunks()], + D_INFO(item)->down_rate()->rate() ? + (D_INFO(item)->up_rate()->rate() ? "⇅ " : "↡ ") : + (D_INFO(item)->up_rate()->rate() ? "↟ " : " "), + ying_yang_style == 0 ? ying_yang_str : + ratio >= YING_YANG_STEPS * 1000 ? "⊛ " : ying_yang[ying_yang_style][ratio / 1000], + has_msg ? has_alert ? alert : "♺ " : is_tagged ? "⚑ " : " ", + tracker ? num2(tracker->scrape_downloaded()).c_str() : " ", + tracker ? num2(tracker->scrape_complete()).c_str() : " ", + tracker ? num2(tracker->scrape_incomplete()).c_str() : " ", + human_size(D_INFO(item)->up_rate()->rate(), 2 | 8).c_str(), + d->is_done() || !down_rate ? "" : " ", + d->is_done() ? elapsed_time(get_custom_long(d, "tm_completed")).c_str() : + !down_rate ? elapsed_time(get_custom_long(d, "tm_loaded")).c_str() : + human_size(down_rate, 2 | 8).c_str(), + human_size(item->file_list()->size_bytes(), 2).c_str(), + displayname.empty() ? "" : " ", + displayname.empty() ? buffer : displayname.c_str() + ); + + int x_scrape = 3 + 8*2 + 1; // lead, 8 status columns, gap + int x_rate = x_scrape + 3*3; // skip 3 scrape columns + int x_name = x_rate + 3*5 + 1; // skip 3 rate/size columns + decorate_download_title(window, canvas, view, pos, range); + canvas->set_attr(2, pos, x_name-2, attr_map[col_active + offset], col_active + offset); + if (has_alert) canvas->set_attr(x_scrape-3, pos, 2, attr_map[ps::COL_ALARM + offset], ps::COL_ALARM + offset); + + // apply progress color to completion indicator + int pcol = ratio_color(item->file_list()->completed_chunks() * 1000 / item->file_list()->size_chunks()); + canvas->set_attr(x_scrape-9, pos, 2, attr_map[pcol + offset], pcol + offset); + + // show ratio progress by color + int rcol = ratio_color(ratio); + canvas->set_attr(x_scrape-5, pos, 2, attr_map[rcol + offset], rcol + offset); + + // color up/down rates + canvas->set_attr(x_rate+0, pos, 4, attr_map[ps::COL_SEEDING + offset], ps::COL_SEEDING + offset); + if (d->is_done() || !down_rate) { + // time display + int tm_color = (d->is_done() ? ps::COL_SEEDING : ps::COL_INCOMPLETE) + offset; + canvas->set_attr(x_rate+5+1, pos, 1, attr_map[tm_color], tm_color); + canvas->set_attr(x_rate+5+4, pos, 1, attr_map[tm_color], tm_color); + } else { + // down rate + canvas->set_attr(x_rate+5, pos, 5, attr_map[ps::COL_LEECHING + offset], ps::COL_LEECHING + offset); + } + + // is this the item in focus? + if (range.first == view->focus()) { + canvas->set_attr(0, pos, 1, attr_map[ps::COL_FOCUS], ps::COL_FOCUS); + } + + ++pos; + ++range.first; + } + + if (view->focus() != view->end_visible()) { + char buffer[canvas->width() + 1]; + char* last = buffer + canvas->width() + 1; + + pos = canvas->height() - 2 - network_history_lines; + print_download_info(buffer, last, *view->focus()); + canvas->print(3, pos, "%s", buffer); + canvas->set_attr(0, pos, -1, attr_map[ps::COL_LABEL], ps::COL_LABEL); + print_download_status(buffer, last, *view->focus()); + canvas->print(3, pos+1, "%s", buffer); + canvas->set_attr(0, pos+1, -1, attr_map[ps::COL_LABEL], ps::COL_LABEL); + } + + return true; +} + + +// patch hook for window title canvas redraw +void ui_pyroscope_statusbar_redraw(Window* window, display::Canvas* canvas) { + canvas->set_attr(0, 0, -1, attr_map[ps::COL_FOOTER], ps::COL_FOOTER); +} + + +} // namespace + + +torrent::Object cmd_view_collapsed_toggle(const torrent::Object::string_type& args) { + std::string view_name = args; + + if (view_name.empty()) { + view_name = control->ui()->download_list()->current_view()->name(); + } + + is_collapsed[view_name] = is_collapsed.find(view_name) == is_collapsed.end() ? true : !is_collapsed[view_name]; + + return is_collapsed[view_name]; +} + + +// implementation of method we patched into rpc::object_storage +const torrent::Object& rpc::object_storage::set_color_string(const torrent::raw_string& key, const std::string& object) { + const torrent::Object& result = rpc::object_storage::set_string(key, object); + display::ui_pyroscope_colormap_init(); + return result; +} + + +// Traffic history +int network_history_depth_get() { + return network_history_depth; +} + +torrent::Object network_history_depth_set(int arg) { + if (network_history_depth) { + delete[] network_history_up; + delete[] network_history_down; + network_history_up = network_history_down = 0; + } + + network_history_depth = arg; + network_history_count = 0; + + if (network_history_depth) { + network_history_up = new uint32_t[network_history_depth]; + network_history_down = new uint32_t[network_history_depth]; + } + + return torrent::Object(); +} + + +void network_history_format(std::string& buf, char kind, uint32_t* data) { + uint32_t samples = std::min(network_history_count, (uint32_t) network_history_depth); + uint32_t min_rate = *std::min_element(data, data + samples); + uint32_t max_rate = *std::max_element(data, data + samples); + char buffer[80]; + + snprintf(buffer, sizeof(buffer), "%c ⌈%s⌉⌊%s⌋%s", kind, + display::human_size(max_rate, 0).c_str(), display::human_size(min_rate, 0).c_str(), + rpc::call_command_value("network.history.auto_scale") ? "↨ " : " "); + buf = buffer; + + if (max_rate > 102) { + const char* meter[] = {"⠀", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"}; + uint32_t base = rpc::call_command_value("network.history.auto_scale") ? min_rate : 0; + for (int i = 1; i <= samples; ++i) { + uint32_t idx = (network_history_count - i) % network_history_depth; + if (max_rate > base) + buf += meter[std::min(8U, (data[idx] - base) * 9 / (max_rate - base))]; + else + buf += " "; + } + } + buf += " "; +} + + +// You MUST call this after changing the auto_scale flag, to see any changes immediately! +torrent::Object network_history_refresh() { + if (network_history_depth) { + network_history_format(network_history_up_str, 'U', network_history_up); + network_history_format(network_history_down_str, 'D', network_history_down); + } + + return torrent::Object(); +} + + +torrent::Object network_history_sample() { + if (network_history_depth) { + network_history_up[network_history_count % network_history_depth] = torrent::up_rate()->rate(); + network_history_down[network_history_count % network_history_depth] = torrent::down_rate()->rate(); + ++network_history_count; + } + + return network_history_refresh(); +} + + +// register our commands +void initialize_command_ui_pyroscope() { + #define PS_VARIABLE_COLOR(key, value) \ + control->object_storage()->insert_c_str(key, value, rpc::object_storage::flag_string_type); \ + CMD2_ANY(key, _cxxstd_::bind(&rpc::object_storage::get, control->object_storage(), \ + torrent::raw_string::from_c_str(key))); \ + CMD2_ANY_STRING(key ".set", _cxxstd_::bind(&rpc::object_storage::set_color_string, control->object_storage(), \ + torrent::raw_string::from_c_str(key), _cxxstd_::placeholders::_2)); + + #define PS_CMD_ANY_FUN(key, func) \ + CMD2_ANY(key, _cxxstd_::bind(&func)) + + CMD2_ANY ("network.history.depth", _cxxstd_::bind(&network_history_depth_get)); + CMD2_ANY_VALUE_V("network.history.depth.set", _cxxstd_::bind(&network_history_depth_set, _cxxstd_::placeholders::_2)); + CMD2_ANY ("network.history.refresh", _cxxstd_::bind(&network_history_refresh)); + CMD2_ANY ("network.history.sample", _cxxstd_::bind(&network_history_sample)); + CMD2_VAR_BOOL ("network.history.auto_scale", true); + + CMD2_ANY_STRING("view.collapsed.toggle", _cxxstd_::bind(&cmd_view_collapsed_toggle, _cxxstd_::placeholders::_2)); + + CMD2_VAR_VALUE("ui.style.progress", 1); + CMD2_VAR_VALUE("ui.style.ratio", 1); + + PS_VARIABLE_COLOR("ui.color.progress0", "red"); + PS_VARIABLE_COLOR("ui.color.progress20", "bold bright red"); + PS_VARIABLE_COLOR("ui.color.progress40", "bold bright magenta"); + PS_VARIABLE_COLOR("ui.color.progress60", "yellow"); + PS_VARIABLE_COLOR("ui.color.progress80", "bold bright yellow"); + PS_VARIABLE_COLOR("ui.color.progress100", "green"); + PS_VARIABLE_COLOR("ui.color.progress120", "bold bright green"); + PS_VARIABLE_COLOR("ui.color.complete", "bright green"); + PS_VARIABLE_COLOR("ui.color.seeding", "bold bright green"); + PS_VARIABLE_COLOR("ui.color.stopped", "blue"); + PS_VARIABLE_COLOR("ui.color.queued", "magenta"); + PS_VARIABLE_COLOR("ui.color.incomplete", "yellow"); + PS_VARIABLE_COLOR("ui.color.leeching", "bold bright yellow"); + PS_VARIABLE_COLOR("ui.color.alarm", "bold white on red"); + PS_VARIABLE_COLOR("ui.color.title", "bold bright white on blue"); + PS_VARIABLE_COLOR("ui.color.footer", "bold bright cyan on blue"); + PS_VARIABLE_COLOR("ui.color.label", "gray"); + PS_VARIABLE_COLOR("ui.color.odd", ""); + PS_VARIABLE_COLOR("ui.color.even", ""); + PS_VARIABLE_COLOR("ui.color.info", "white"); + PS_VARIABLE_COLOR("ui.color.focus", "reverse"); + + PS_CMD_ANY_FUN("system.colors.max", display::get_colors); + PS_CMD_ANY_FUN("system.colors.enabled", has_colors); + PS_CMD_ANY_FUN("system.colors.rgb", can_change_color); +} diff --git a/ui_pyroscope.h b/ui_pyroscope.h new file mode 100644 index 000000000000..d6608f344e46 --- /dev/null +++ b/ui_pyroscope.h @@ -0,0 +1,34 @@ +#ifndef UI_PYROSCOPE_H +#define UI_PYROSCOPE_H + +namespace ps { + +enum ColorKind { + COL_DEFAULT, + COL_PROGRESS0, + COL_PROGRESS20, + COL_PROGRESS40, + COL_PROGRESS60, + COL_PROGRESS80, + COL_PROGRESS100, + COL_PROGRESS120, + COL_COMPLETE, + COL_SEEDING, + COL_STOPPED, + COL_QUEUED, + COL_INCOMPLETE, + COL_LEECHING, + COL_ALARM, + COL_TITLE, + COL_FOOTER, + COL_LABEL, + COL_ODD, + COL_EVEN, + COL_INFO, + COL_FOCUS, + COL_MAX +}; + +} // namespace + +#endif diff --git a/ui_pyroscope.patch b/ui_pyroscope.patch new file mode 100644 index 000000000000..48dddfdb52c5 --- /dev/null +++ b/ui_pyroscope.patch @@ -0,0 +1,51 @@ +--- orig/src/Makefile.am 2009-11-14 08:34:04.000000000 +0100 ++++ rtorrent-0.8.6/src/Makefile.am 2011-05-27 00:38:37.000000000 +0200 +@@ -37,2 +38,3 @@ + main.cc \ ++ ui_pyroscope.cc \ + option_parser.cc \ +--- orig/src/command_helpers.cc 2009-11-12 09:03:48.000000000 +0100 ++++ rtorrent-0.8.6/src/command_helpers.cc 2011-05-27 00:42:07.000000000 +0200 +@@ -77,2 +78,3 @@ + void initialize_command_ui(); ++void initialize_command_ui_pyroscope(); + +@@ -91,2 +94,3 @@ + initialize_command_scheduler(); ++ initialize_command_ui_pyroscope(); + +--- orig/src/display/canvas.h 2009-11-12 09:03:47.000000000 +0100 ++++ rtorrent-0.8.6/src/display/canvas.h 2011-05-28 13:42:58.000000000 +0200 +@@ -134,4 +134,6 @@ + va_start(arglist, str); ++ if (y < height()) { + wmove(m_window, y, x); + vw_printw(m_window, const_cast<char*>(str), arglist); ++ } + va_end(arglist); +--- orig/src/display/window_statusbar.cc 2009-11-12 09:03:47.000000000 +0100 ++++ rtorrent-0.8.6/src/display/window_statusbar.cc 2011-05-28 19:38:52.000000000 +0200 +@@ -69,2 +69,4 @@ + ++ void ui_pyroscope_statusbar_redraw(Window* window, display::Canvas* canvas); ++ ui_pyroscope_statusbar_redraw(this, m_canvas); + m_lastTick = control->tick(); +--- orig/src/display/window_download_list.cc 2009-11-12 09:03:47.000000000 +0100 ++++ rtorrent-0.8.6/src/display/window_download_list.cc 2011-05-30 20:34:59.000000000 +0200 +@@ -75,2 +75,4 @@ + ++ bool ui_pyroscope_download_list_redraw(Window* window, display::Canvas* canvas, core::View* view); ++ if (ui_pyroscope_download_list_redraw(this, m_canvas, m_view)) return; + if (m_view->empty_visible() || m_canvas->width() < 5 || m_canvas->height() < 2) +@@ -107,2 +109,4 @@ + ++ void ui_pyroscope_download_list_redraw_item(Window* window, display::Canvas* canvas, core::View* view, int pos, Range& range); ++ ui_pyroscope_download_list_redraw_item(this, m_canvas, m_view, pos, range); + ++range.first; +--- orig/src/display/canvas.cc 2009-11-12 09:03:47.000000000 +0100 ++++ rtorrent-0.8.6/src/display/canvas.cc 2011-05-30 21:07:29.000000000 +0200 +@@ -94,2 +94,4 @@ + initscr(); ++ extern void ui_pyroscope_canvas_init(); ++ ui_pyroscope_canvas_init(); + raw(); |