summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorxsmile2017-03-09 18:46:37 +0100
committerxsmile2017-03-09 18:46:37 +0100
commita386ce87b56799cfb29b1667f56a63695fe781ed (patch)
tree70cbf797d882faad63eb3ff1561f0b79f247b34e
downloadaur-a386ce87b56799cfb29b1667f56a63695fe781ed.tar.gz
init
-rw-r--r--.SRCINFO48
-rw-r--r--PKGBUILD85
-rw-r--r--command_pyroscope.cc502
-rw-r--r--ps-info-pane-xb-sizes_all.patch21
-rw-r--r--ps-item-stats-human-sizes_all.patch35
-rw-r--r--ps-ssl_verify_host_all.patch28
-rw-r--r--ps-throttle-steps_all.patch68
-rw-r--r--ps-ui_pyroscope_all.patch7
-rw-r--r--pyroscope.patch26
-rw-r--r--ui_pyroscope.cc807
-rw-r--r--ui_pyroscope.h34
-rw-r--r--ui_pyroscope.patch51
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();