summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorxsmile2018-06-04 15:20:31 +0200
committerxsmile2018-06-04 15:20:31 +0200
commitd5f9faa36269d5ce2144d93ab4622f3d67e102fb (patch)
treeee3300867bac5924804e985b1105a1425580ecb8
parent8a6e454d1f806fd9e7ca74d922b24e76e7a5e79e (diff)
downloadaur-d5f9faa36269d5ce2144d93ab4622f3d67e102fb.tar.gz
update
-rw-r--r--.SRCINFO16
-rw-r--r--PKGBUILD27
-rw-r--r--backport_0.9.6_algorithm_median.patch29
-rw-r--r--command_pyroscope.cc458
-rw-r--r--ps-import.return_all.patch10
-rw-r--r--ps-object_std-map-serialization_all.patch24
-rw-r--r--ps-silent-catch_all.patch18
-rw-r--r--ui_pyroscope.cc579
-rw-r--r--ui_pyroscope.h54
9 files changed, 963 insertions, 252 deletions
diff --git a/.SRCINFO b/.SRCINFO
index 7edb42863657..421fa4b3f3be 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,6 +1,6 @@
pkgbase = rtorrent-ps
pkgdesc = Extended rTorrent distribution with UI enhancements, colorization, and some added features
- pkgver = 1.0.r187.g5d5241a
+ pkgver = 1.0.r272.g388cfab
pkgrel = 1
url = https://github.com/pyroscope/rtorrent-ps
arch = any
@@ -12,17 +12,21 @@ pkgbase = rtorrent-ps
provides = rtorrent
conflicts = rtorrent
source = https://github.com/rakshasa/rtorrent/archive/0.9.6.tar.gz
+ source = backport_0.9.6_algorithm_median.patch
source = command_pyroscope.cc
source = ps-event-view_all.patch
source = ps-fix-double-slash-319_all.patch
source = ps-fix-sort-started-stopped-views_all.patch
source = ps-fix-throttle-args_all.patch
source = ps-handle-sighup-578_all.patch
+ source = ps-import.return_all.patch
source = ps-info-pane-is-default_all.patch
source = ps-info-pane-xb-sizes_all.patch
source = ps-issue-515_all.patch
source = ps-item-stats-human-sizes_all.patch
source = ps-log_messages_all.patch
+ source = ps-object_std-map-serialization_all.patch
+ source = ps-silent-catch_all.patch
source = ps-ssl_verify_host_all.patch
source = ps-throttle-steps_all.patch
source = ps-ui_pyroscope_all.patch
@@ -33,25 +37,29 @@ pkgbase = rtorrent-ps
source = ui_pyroscope.h
source = ui_pyroscope.patch
md5sums = b8b4009f95f8543244ae1d23b1810d7c
- md5sums = fa1ec27799203e8e63135aba22c06a87
+ md5sums = b49903d3fa25a66c72db69570dfe8b47
+ md5sums = b80e823797b0e14941f764014e01d132
md5sums = 56701bca42cc9b309637bf3f918ede12
md5sums = 22fae392c6e281dc438b39a5019e7e1b
md5sums = 3fd739c0d5a9442f0cdec9ed5a720eaa
md5sums = ab490d1d1df9c27f3cf624966f7f03f6
md5sums = 2137e16f8b881170fb92fb7a6c276193
+ md5sums = cc9bbf20acf855e551ca2f80cac91903
md5sums = 398c132d99dcf9f45252043ece176dd6
md5sums = f1539d70c74e5c74d8a15d51675aa26c
md5sums = c438a91cd3e58edebf39fce06587641a
md5sums = 2d34e8c86c1c6ed1354b55ca21819886
md5sums = a4f5a4da3397e4b1d71eb59a5e8e0279
+ md5sums = 0fa551b7cba264bd906e32827d06700c
+ md5sums = e3f367abe42d28168008f99a9bf0f1d6
md5sums = cef14e9011d4b4af92536b02f8b611c2
md5sums = ee76d57dfbc40e09eeaee3845d327d94
md5sums = 7a88f8ab5d41242fdf1428de0e2ca182
md5sums = 26faff00b306b6ef276a7d9e6d964994
md5sums = bd04a0699b80c8042e1cf63a7e0e4222
md5sums = d0a956f0eb4b53b66d83df2a8a4d16dc
- md5sums = 21fd4b912ddabe32356eef0a4e87c681
- md5sums = 1258acfc82c50a8f452ace87fef0b416
+ md5sums = e0d51843436d2aa348815251e21ebd15
+ md5sums = 2a71f0c478e8fe7bce464ac85c4bec44
md5sums = 0a2bbaf74c7160ba33876dcc2f050f14
pkgname = rtorrent-ps
diff --git a/PKGBUILD b/PKGBUILD
index 58d70b03b30f..6aee16f7f870 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -3,7 +3,7 @@
_pkgname=rtorrent
pkgname=rtorrent-ps
_pkgver=0.9.6
-pkgver=1.0.r187.g5d5241a
+pkgver=1.0.r272.g388cfab
pkgrel=1
pkgdesc='Extended rTorrent distribution with UI enhancements, colorization, and some added features'
url='https://github.com/pyroscope/rtorrent-ps'
@@ -13,17 +13,21 @@ depends=('curl>=7.15.4' 'libtorrent-ps>=1.0' 'ncurses' 'xmlrpc-c')
provides=('rtorrent')
conflicts=('rtorrent')
source=("https://github.com/rakshasa/$_pkgname/archive/$_pkgver.tar.gz"
+ 'backport_0.9.6_algorithm_median.patch'
'command_pyroscope.cc'
'ps-event-view_all.patch'
'ps-fix-double-slash-319_all.patch'
'ps-fix-sort-started-stopped-views_all.patch'
'ps-fix-throttle-args_all.patch'
'ps-handle-sighup-578_all.patch'
+ 'ps-import.return_all.patch'
'ps-info-pane-is-default_all.patch'
'ps-info-pane-xb-sizes_all.patch'
'ps-issue-515_all.patch'
'ps-item-stats-human-sizes_all.patch'
'ps-log_messages_all.patch'
+ 'ps-object_std-map-serialization_all.patch'
+ 'ps-silent-catch_all.patch'
'ps-ssl_verify_host_all.patch'
'ps-throttle-steps_all.patch'
'ps-ui_pyroscope_all.patch'
@@ -34,25 +38,29 @@ source=("https://github.com/rakshasa/$_pkgname/archive/$_pkgver.tar.gz"
'ui_pyroscope.h'
'ui_pyroscope.patch')
md5sums=('b8b4009f95f8543244ae1d23b1810d7c'
- 'fa1ec27799203e8e63135aba22c06a87'
+ 'b49903d3fa25a66c72db69570dfe8b47'
+ 'b80e823797b0e14941f764014e01d132'
'56701bca42cc9b309637bf3f918ede12'
'22fae392c6e281dc438b39a5019e7e1b'
'3fd739c0d5a9442f0cdec9ed5a720eaa'
'ab490d1d1df9c27f3cf624966f7f03f6'
'2137e16f8b881170fb92fb7a6c276193'
+ 'cc9bbf20acf855e551ca2f80cac91903'
'398c132d99dcf9f45252043ece176dd6'
'f1539d70c74e5c74d8a15d51675aa26c'
'c438a91cd3e58edebf39fce06587641a'
'2d34e8c86c1c6ed1354b55ca21819886'
'a4f5a4da3397e4b1d71eb59a5e8e0279'
+ '0fa551b7cba264bd906e32827d06700c'
+ 'e3f367abe42d28168008f99a9bf0f1d6'
'cef14e9011d4b4af92536b02f8b611c2'
'ee76d57dfbc40e09eeaee3845d327d94'
'7a88f8ab5d41242fdf1428de0e2ca182'
'26faff00b306b6ef276a7d9e6d964994'
'bd04a0699b80c8042e1cf63a7e0e4222'
'd0a956f0eb4b53b66d83df2a8a4d16dc'
- '21fd4b912ddabe32356eef0a4e87c681'
- '1258acfc82c50a8f452ace87fef0b416'
+ 'e0d51843436d2aa348815251e21ebd15'
+ '2a71f0c478e8fe7bce464ac85c4bec44'
'0a2bbaf74c7160ba33876dcc2f050f14')
prepare() {
@@ -63,14 +71,19 @@ prepare() {
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
- RT_BASE_PATCHES=( "$srcdir"/rt-base-cppunit-pkgconfig.patch )
- RT_PATCHES=( )
+ RT_BASE_PATCHES=("$srcdir"/rt-base-cppunit-pkgconfig.patch)
+ RT_PATCHES=()
# Patch rTorrent
- for corepatch in "${RT_BASE_PATCHES[@]}" $srcdir/ps-*_{${_pkgver},all}.patch "${RT_PATCHES[@]}"; do
+ for corepatch in "${RT_BASE_PATCHES[@]}" \
+ "$srcdir"/ps-*_{${_pkgver},all}.patch "${RT_PATCHES[@]}"; do
test ! -e "$corepatch" || { msg2 "$(basename $corepatch)"; patch -uNp1 -i "$corepatch"; }
done
+ for backport in "$srcdir"/backport*_${_pkgver}_*.patch; do
+ test ! -e "$backport" || { msg2 "$(basename $backport)"; patch -uNp1 -i "$backport"; }
+ done
+
msg2 "pyroscope.patch"
patch -uNp1 -i "$srcdir/pyroscope.patch"
for i in "$srcdir"/*.{cc,h}; do
diff --git a/backport_0.9.6_algorithm_median.patch b/backport_0.9.6_algorithm_median.patch
new file mode 100644
index 000000000000..f05fec0d07c3
--- /dev/null
+++ b/backport_0.9.6_algorithm_median.patch
@@ -0,0 +1,29 @@
+--- a/rak/algorithm.h 2016-11-04 21:58:44.000000000 +0000
++++ b/rak/algorithm.h 2017-03-10 21:16:32.039264146 +0000
+@@ -176,6 +176,26 @@ inline int popcount_wrapper(T t) {
+ #endif
+ }
+
++// Get the median of an unordered set of numbers of arbitrary
++// type by modifing the underlying dataset
++template <typename T = double, typename _InputIter>
++T median(_InputIter __first, _InputIter __last) {
++ T __med;
++
++ unsigned int __size = __last - __first;
++ unsigned int __middle = __size / 2;
++ _InputIter __target1 = __first + __middle;
++ std::nth_element(__first, __target1, __last);
++ __med = *__target1;
++
++ if (__size % 2 == 0) {
++ _InputIter __target2 = std::max_element(__first, __target1);
++ __med = (__med + *__target2) / 2.0;
++ }
++
++ return __med;
++}
++
+ }
+
+ #endif
diff --git a/command_pyroscope.cc b/command_pyroscope.cc
index 480158e09294..98e854329701 100644
--- a/command_pyroscope.cc
+++ b/command_pyroscope.cc
@@ -21,15 +21,15 @@
#include <cstdio>
#include <climits>
#include <ctime>
+#include <cwchar>
+#include <set>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <rak/path.h>
+#include <rak/algorithm.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"
@@ -46,12 +46,13 @@
#include "control.h"
#include "command_helpers.h"
-#if (RT_HEX_VERSION >= 0x000901)
- #define _cxxstd_ tr1
-#else
- #define _cxxstd_ std
-#endif
+// In 0.9.x this changed to 'tr1' (dropping sigc::bind), see https://stackoverflow.com/a/4682954/2748717
+// "C++ Technical Report 1" was later added to "C++11", using tr1 makes stuff compile on older GCC
+#define _cxxstd_ tr1
+
+// List of system capabilities for `system.has` command
+static std::set<std::string> system_capabilities;
// handle for message log file
namespace core {
@@ -180,7 +181,7 @@ 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++) {
+ for (size_t 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) {
@@ -224,6 +225,29 @@ std::string get_active_tracker_domain(torrent::Download* item) {
}
+// return various scrape information of the "main" tracker for this download item
+int64_t get_active_tracker_scrape_info(const int operation, torrent::Download* item) {
+ int64_t scrape_num = 0;
+ torrent::Tracker* tracker = get_active_tracker(item);
+
+ if (tracker) {
+ switch (operation) {
+ case 1:
+ scrape_num = tracker->scrape_downloaded();
+ break;
+ case 2:
+ scrape_num = tracker->scrape_complete();
+ break;
+ case 3:
+ scrape_num = tracker->scrape_incomplete();
+ break;
+ }
+ }
+
+ return scrape_num;
+}
+
+
/* @DOC
`compare = <order>, <sort_key>=[, ...]`
@@ -355,12 +379,8 @@ torrent::Object apply_ui_bind_key(rpc::target_type target, const torrent::Object
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());
+ bound_commands[displayType][key].c_str());
break;
default:
return torrent::Object();
@@ -471,6 +491,46 @@ torrent::Object cmd_log_messages(const torrent::Object::string_type& arg) {
}
+torrent::Object cmd_import_return(rpc::target_type target, const torrent::Object& args) {
+ // Handled in src/rpc/parse_commands.cc::parse_command_file via patch
+ throw torrent::input_error("import.return");
+}
+
+
+torrent::Object retrieve_d_custom_if_z(core::Download* download, const torrent::Object::list_type& args) {
+ torrent::Object::list_const_iterator itr = args.begin();
+ if (itr == args.end())
+ throw torrent::bencode_error("d.custom.if_z: Missing key argument.");
+ const std::string& key = (itr++)->as_string();
+ if (key.empty())
+ throw torrent::bencode_error("d.custom.if_z: Empty key argument.");
+ if (itr == args.end())
+ throw torrent::bencode_error("d.custom.if_z: Missing default argument.");
+
+ try {
+ return download->bencode()->get_key("rtorrent").get_key("custom").get_key_string(key);
+ } catch (torrent::bencode_error& e) {
+ return itr->as_string();
+ }
+}
+
+
+torrent::Object retrieve_d_custom_map(core::Download* download, bool keys_only, const torrent::Object::list_type& args) {
+ if (args.begin() != args.end())
+ throw torrent::bencode_error("d.custom.keys/items takes no arguments.");
+
+ torrent::Object result = keys_only ? torrent::Object::create_list() : torrent::Object::create_map();
+ torrent::Object::map_type& entries = download->bencode()->get_key("rtorrent").get_key("custom").as_map();
+
+ for (torrent::Object::map_type::const_iterator itr = entries.begin(), last = entries.end(); itr != last; itr++) {
+ if (keys_only) result.as_list().push_back(itr->first);
+ else result.as_map()[itr->first] = itr->second;
+ }
+
+ return result;
+}
+
+
torrent::Object
d_multicall_filtered(const torrent::Object::list_type& args) {
if (args.size() < 2)
@@ -534,6 +594,99 @@ torrent::Object cmd_throttle_names() {
}
+static const std::string& string_get_first_arg(const char* name, const torrent::Object::list_type& args) {
+ if (args.size() < 1) {
+ throw torrent::input_error("string." + std::string(name) + " needs a string argument!");
+ }
+ torrent::Object::list_const_iterator itr = args.begin();
+ return itr->as_string();
+}
+
+
+static int64_t string_get_value_arg(const char* name, torrent::Object::list_const_iterator& itr) {
+ int64_t result = 0;
+ if (itr->is_string()) {
+ char* junk = 0;
+ result = strtol(itr->as_string().c_str(), &junk, 10);
+ if (*junk) {
+ throw torrent::input_error("string." + std::string(name) + ": "
+ "junk at end of value: " + itr->as_string());
+ }
+ } else {
+ result = itr->as_value();
+ }
+
+ ++itr;
+ return result;
+}
+
+
+torrent::Object cmd_string_len(rpc::target_type target, const torrent::Object::list_type& args) {
+ std::mbstate_t mbs = std::mbstate_t();
+ std::string text = string_get_first_arg("len", args);
+ const char* pos = text.c_str();
+ int glyphs = 0, bytes = 0, skip;
+
+ while (*pos && (skip = std::mbrlen(pos, text.length() - bytes, &mbs)) > 0) {
+ pos += skip;
+ bytes += skip;
+ ++glyphs;
+ }
+
+ return (int64_t) glyphs;
+}
+
+
+torrent::Object cmd_string_substr(rpc::target_type target, const torrent::Object::list_type& args) {
+ const std::string text = string_get_first_arg("substr", args);
+
+ torrent::Object::list_const_iterator itr = args.begin() + 1;
+ int64_t glyphs = 0, count = text.length();
+ std::string fallback;
+ if (itr != args.end()) glyphs = string_get_value_arg("substr(pos)", itr);
+ if (itr != args.end()) count = string_get_value_arg("substr(count)", itr);
+ if (itr != args.end()) fallback = (itr++)->as_string();
+
+ if (count < 0) {
+ throw torrent::input_error("string.substr: Invalid negative count!");
+ }
+
+ std::mbstate_t mbs = std::mbstate_t();
+ const char* pos = text.c_str();
+ int bytes = 0, skip;
+
+ if (glyphs < 0) {
+ std::string::size_type offsets[text.length() + 1];
+ int64_t idx = 0;
+ while (*pos && (skip = std::mbrlen(pos, text.length() - bytes, &mbs)) > 0) {
+ offsets[idx++] = bytes;
+ pos += skip;
+ bytes += skip;
+ }
+ offsets[idx] = bytes;
+
+ int64_t begidx = std::max(idx + glyphs, 0L);
+ int64_t endidx = std::min(idx, begidx + count);
+ return text.substr(offsets[begidx], offsets[endidx] - offsets[begidx]);
+ }
+
+ while (glyphs-- > 0 && *pos && (skip = std::mbrlen(pos, text.length() - bytes, &mbs)) > 0) {
+ pos += skip;
+ bytes += skip;
+ }
+ if (!*pos) return fallback;
+
+ int bytes_pos = bytes, bytes_count = 0;
+ while (count-- > 0 && *pos && (skip = std::mbrlen(pos, text.length() - bytes, &mbs)) > 0) {
+ pos += skip;
+ bytes += skip;
+ bytes_count += skip;
+ }
+
+ return text.substr(bytes_pos, bytes_count);
+}
+
+
torrent::Object::value_type apply_string_contains(bool ignore_case, const torrent::Object::list_type& args) {
if (args.size() < 2) {
throw torrent::input_error("string.contains[_i] takes at least two arguments!");
@@ -603,6 +756,70 @@ torrent::Object cmd_string_replace(rpc::target_type target, const torrent::Objec
}
+torrent::Object cmd_array_at(rpc::target_type target, const torrent::Object::list_type& args) {
+ if (args.size() != 2) {
+ throw torrent::input_error("array.at takes at exactly two arguments!");
+ }
+
+ torrent::Object::list_const_iterator itr = args.begin();
+ torrent::Object::list_type array = (itr++)->as_list();
+ torrent::Object::value_type index = (itr++)->as_value();
+
+ if (array.empty()) {
+ throw torrent::input_error("array.at: array is empty!");
+ }
+ if (index < 0 || int(array.size()) <= index) {
+ throw torrent::input_error("array.at: index out of bounds!");
+ }
+
+ return array.at(index);
+}
+
+
+void add_capability(const char* name) {
+ system_capabilities.insert(name);
+}
+
+
+torrent::Object cmd_system_has(const torrent::Object::string_type& arg) {
+ if (arg.empty()) {
+ throw torrent::input_error("Passed empty string to 'system.has'!");
+ }
+
+ bool result = (system_capabilities.count(arg) != 0);
+ if (!result && '=' == arg.at(arg.size()-1)) {
+ result = rpc::commands.has(arg.substr(0, arg.size()-1));
+ }
+ return (int64_t) result;
+}
+
+
+torrent::Object cmd_system_has_list() {
+ torrent::Object result = torrent::Object::create_list();
+ torrent::Object::list_type& resultList = result.as_list();
+
+ for (std::set<std::string>::const_iterator itr = system_capabilities.begin(); itr != system_capabilities.end(); itr++) {
+ resultList.push_back(*itr);
+ }
+
+ return result;
+}
+
+
+torrent::Object cmd_system_has_methods(bool filter_public) {
+ torrent::Object result = torrent::Object::create_list();
+ torrent::Object::list_type& resultList = result.as_list();
+
+ for (rpc::CommandMap::const_iterator itr = rpc::commands.begin(), last = rpc::commands.end(); itr != last; itr++) {
+ if (bool(itr->second.m_flags & rpc::CommandMap::flag_public_xmlrpc) == filter_public) {
+ resultList.push_back(itr->first);
+ }
+ }
+
+ return result;
+}
+
+
torrent::Object cmd_value(rpc::target_type target, const torrent::Object::list_type& args) {
if (args.size() < 1) {
throw torrent::input_error("'value' takes at least a number argument!");
@@ -630,40 +847,130 @@ torrent::Object cmd_value(rpc::target_type target, const torrent::Object::list_t
}
-// 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];
+torrent::Object cmd_d_tracker_domain(core::Download* download) {
+ return get_active_tracker_domain(download->download());
+}
- ++first;
- }
- return dest;
+torrent::Object cmd_d_tracker_scrape_info(const int operation, core::Download* download) {
+ return get_active_tracker_scrape_info(operation, download->download());
}
-torrent::Object d_chunks_seen(core::Download* download) {
- const uint8_t* seen = download->download()->chunks_seen();
+// MATH FUNCTIONS
- if (seen == NULL)
- return std::string();
+inline std::vector<int64_t> as_vector(const torrent::Object::list_type& args) {
+ if (args.size() == 0)
+ throw torrent::input_error("Wrong argument count in as_vector.");
- 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());
+ std::vector<int64_t> result;
+
+ for (torrent::Object::list_const_iterator itr = args.begin(), last = args.end(); itr != last; itr++) {
+ if (itr->is_value()) {
+ result.push_back(itr->as_value());
+ } else if (itr->is_string()) {
+ result.push_back(rpc::convert_to_value(itr->as_string()));
+ } else if (itr->is_list()) {
+ std::vector<int64_t> subResult = as_vector(itr->as_list());
+ result.insert(result.end(), subResult.begin(), subResult.end());
+ } else {
+ throw torrent::input_error("Wrong type supplied to as_vector.");
+ }
+ }
return result;
}
-#endif
-torrent::Object cmd_d_tracker_domain(core::Download* download) {
- return get_active_tracker_domain(download->download());
+int64_t apply_math_basic(const char* name, const std::function<int64_t(int64_t,int64_t)> op,
+ const torrent::Object::list_type& args) {
+ int64_t val = 0, rhs = 0;
+ bool divides = !strcmp(name, "math.div") || !strcmp(name, "math.mod");
+
+ if (args.size() == 0)
+ throw torrent::input_error(std::string(name) + ": No arguments provided!");
+
+ for (torrent::Object::list_const_iterator itr = args.begin(), last = args.end(); itr != last; itr++) {
+ if (itr->is_value()) {
+ rhs = itr->as_value();
+ } else if (itr->is_string()) {
+ rhs = rpc::convert_to_value(itr->as_string());
+ } else if (itr->is_list()) {
+ rhs = apply_math_basic(name, op, itr->as_list());
+ } else {
+ throw torrent::input_error(std::string(name) + ": Wrong argument type");
+ }
+
+ if (divides && !rhs && itr != args.begin())
+ throw torrent::input_error(std::string(name) + ": Division by zero!");
+ val = itr == args.begin() ? rhs : op(val, rhs);
+ }
+
+ return val;
+}
+
+
+int64_t apply_arith_basic(const std::function<int64_t(int64_t,int64_t)> op,
+ const torrent::Object::list_type& args) {
+ if (args.size() == 0)
+ throw torrent::input_error("Wrong argument count in apply_arith_basic.");
+
+ int64_t val = 0;
+
+ for (torrent::Object::list_const_iterator itr = args.begin(), last = args.end(); itr != last; itr++) {
+ if (itr->is_value()) {
+ val = itr == args.begin() ? itr->as_value()
+ : (op(val, itr->as_value()) ? val : itr->as_value());
+ } else if (itr->is_string()) {
+ int64_t cval = rpc::convert_to_value(itr->as_string());
+ val = itr == args.begin() ? cval : (op(val, cval) ? val : cval);
+ } else if (itr->is_list()) {
+ int64_t fval = apply_arith_basic(op, itr->as_list());
+ val = itr == args.begin() ? fval : (op(val, fval) ? val : fval);
+ } else {
+ throw torrent::input_error("Wrong type supplied to apply_arith_basic.");
+ }
+ }
+
+ return val;
+}
+
+
+int64_t apply_arith_count(const torrent::Object::list_type& args) {
+ if (args.size() == 0)
+ throw torrent::input_error("Wrong argument count in apply_arith_count.");
+
+ int64_t val = 0;
+
+ for (torrent::Object::list_const_iterator itr = args.begin(), last = args.end(); itr != last; itr++) {
+ switch (itr->type()) {
+ case torrent::Object::TYPE_VALUE:
+ case torrent::Object::TYPE_STRING:
+ val++;
+ break;
+ case torrent::Object::TYPE_LIST:
+ val += apply_arith_count(itr->as_list());
+ break;
+ default:
+ throw torrent::input_error("Wrong type supplied to apply_arith_count.");
+ }
+ }
+
+ return val;
+}
+
+int64_t apply_arith_other(const char* op, const torrent::Object::list_type& args) {
+ if (args.size() == 0)
+ throw torrent::input_error("Wrong argument count in apply_arith_other.");
+
+ if (strcmp(op, "average") == 0) {
+ return (int64_t)(apply_math_basic(op, std::plus<int64_t>(), args) / apply_arith_count(args));
+ } else if (strcmp(op, "median") == 0) {
+ std::vector<int64_t> result = as_vector(args);
+ return (int64_t)rak::median(result.begin(), result.end());
+ } else {
+ throw torrent::input_error("Wrong operation supplied to apply_arith_other.");
+ }
}
@@ -688,16 +995,14 @@ torrent::Object cmd_ui_current_view() {
void initialize_command_pyroscope() {
-// 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
+ /*
+ *_ANY – no arguments (signature `cmd_*()`)
+ *_ANY_P – the 'P' means 'private'
+ *_STRING – takes (one?) string argument
+ *_LIST – takes any number of arguments
+ *_DL, *_DL_LIST – function gets a `core::Download*` as first parameter
+ *_VAR_VALUE – define a value, with getter and setter, and a default
+ */
#if RT_HEX_VERSION <= 0x000906
// these are merged into 0.9.7+ mainline! (well, maybe, PRs are ignored)
@@ -707,20 +1012,69 @@ void initialize_command_pyroscope() {
CMD2_ANY_LIST("d.multicall.filtered", _cxxstd_::bind(&d_multicall_filtered, _cxxstd_::placeholders::_2));
#endif
- CMD2_ANY("throttle.names", _cxxstd_::bind(&cmd_throttle_names));
+ // string.* group
+ CMD2_ANY_LIST("string.len", &cmd_string_len);
+ CMD2_ANY_LIST("string.substr", &cmd_string_substr);
CMD2_ANY_LIST("string.contains", &cmd_string_contains);
CMD2_ANY_LIST("string.contains_i", &cmd_string_contains_i);
CMD2_ANY_LIST("string.map", &cmd_string_map);
CMD2_ANY_LIST("string.replace", &cmd_string_replace);
+
+ // array.* group
+ CMD2_ANY_LIST("array.at", &cmd_array_at);
+
+ // math.* group
+ CMD2_ANY_LIST("math.add", std::bind(&apply_math_basic, "math.add", std::plus<int64_t>(), std::placeholders::_2));
+ CMD2_ANY_LIST("math.sub", std::bind(&apply_math_basic, "math.sub", std::minus<int64_t>(), std::placeholders::_2));
+ CMD2_ANY_LIST("math.mul", std::bind(&apply_math_basic, "math.mul", std::multiplies<int64_t>(), std::placeholders::_2));
+ CMD2_ANY_LIST("math.div", std::bind(&apply_math_basic, "math.div", std::divides<int64_t>(), std::placeholders::_2));
+ CMD2_ANY_LIST("math.mod", std::bind(&apply_math_basic, "math.mod", std::modulus<int64_t>(), std::placeholders::_2));
+ CMD2_ANY_LIST("math.min", std::bind(&apply_arith_basic, std::less<int64_t>(), std::placeholders::_2));
+ CMD2_ANY_LIST("math.max", std::bind(&apply_arith_basic, std::greater<int64_t>(), std::placeholders::_2));
+ CMD2_ANY_LIST("math.cnt", std::bind(&apply_arith_count, std::placeholders::_2));
+ CMD2_ANY_LIST("math.avg", std::bind(&apply_arith_other, "average", std::placeholders::_2));
+ CMD2_ANY_LIST("math.med", std::bind(&apply_arith_other, "median", std::placeholders::_2));
+
+ // ui.focus.* – quick paging
+ 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);
+
+ // system.has.*
+ CMD2_ANY_STRING("system.has", _cxxstd_::bind(&cmd_system_has, _cxxstd_::placeholders::_2));
+ CMD2_ANY("system.has.list", _cxxstd_::bind(&cmd_system_has_list));
+ CMD2_ANY("system.has.private_methods", _cxxstd_::bind(&cmd_system_has_methods, false));
+ CMD2_ANY("system.has.public_methods", _cxxstd_::bind(&cmd_system_has_methods, true));
+
+ // d.custom.* extensions
+ CMD2_DL_LIST("d.custom.if_z", _cxxstd_::bind(&retrieve_d_custom_if_z,
+ _cxxstd_::placeholders::_1, _cxxstd_::placeholders::_2));
+ CMD2_DL_LIST("d.custom.keys", _cxxstd_::bind(&retrieve_d_custom_map,
+ _cxxstd_::placeholders::_1, true, _cxxstd_::placeholders::_2));
+ CMD2_DL_LIST("d.custom.items", _cxxstd_::bind(&retrieve_d_custom_map,
+ _cxxstd_::placeholders::_1, false, _cxxstd_::placeholders::_2));
+
+ // Misc commands
CMD2_ANY_LIST("value", &cmd_value);
CMD2_ANY_LIST("compare", &apply_compare);
CMD2_ANY("ui.bind_key", &apply_ui_bind_key);
CMD2_VAR_VALUE("ui.bind_key.verbose", 1);
+ CMD2_ANY("throttle.names", _cxxstd_::bind(&cmd_throttle_names));
CMD2_DL("d.tracker_domain", _cxxstd_::bind(&cmd_d_tracker_domain, _cxxstd_::placeholders::_1));
+ CMD2_DL("d.tracker_scrape.downloaded", _cxxstd_::bind(&cmd_d_tracker_scrape_info, 1, _cxxstd_::placeholders::_1));
+ CMD2_DL("d.tracker_scrape.complete", _cxxstd_::bind(&cmd_d_tracker_scrape_info, 2, _cxxstd_::placeholders::_1));
+ CMD2_DL("d.tracker_scrape.incomplete", _cxxstd_::bind(&cmd_d_tracker_scrape_info, 3, _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);
+ CMD2_ANY_P("import.return", &cmd_import_return);
+ CMD2_DL("d.is_meta", _cxxstd_::bind(&torrent::DownloadInfo::is_meta_download,
+ _cxxstd_::bind(&core::Download::info, _cxxstd_::placeholders::_1)));
+
+ // List capabilities of this build
+ add_capability("system.has"); // self
+ add_capability("rtorrent-ps"); // obvious
+ add_capability("colors"); // not monochrome
+ add_capability("canvas_v2"); // new PS 1.1 canvas with fully dynamic columns
}
diff --git a/ps-import.return_all.patch b/ps-import.return_all.patch
new file mode 100644
index 000000000000..79a804e8cfc2
--- /dev/null
+++ b/ps-import.return_all.patch
@@ -0,0 +1,10 @@
+--- a/src/rpc/parse_commands.cc
++++ b/src/rpc/parse_commands.cc
+@@ -226,6 +226,7 @@ parse_command_file(const std::string& path) {
+ }
+
+ } catch (torrent::input_error& e) {
++ if (!strcmp(e.what(), "import.return")) return true;
+ snprintf(buffer, 2048, "Error in option file: %s:%u: %s", path.c_str(), lineNumber, e.what());
+
+ throw torrent::input_error(buffer);
diff --git a/ps-object_std-map-serialization_all.patch b/ps-object_std-map-serialization_all.patch
new file mode 100644
index 000000000000..b476f84cd700
--- /dev/null
+++ b/ps-object_std-map-serialization_all.patch
@@ -0,0 +1,24 @@
+--- a/src/rpc/parse.cc
++++ b/src/rpc/parse.cc
+@@ -506,6 +506,21 @@ print_object_std(std::string* dest, const torrent::Object* src, int flags) {
+
+ return;
+
++ case torrent::Object::TYPE_MAP:
++ {
++ bool first = true;
++ for (torrent::Object::map_const_iterator itr = src->as_map().begin(), itrEnd = src->as_map().end(); itr != itrEnd; itr++) {
++ if (!first) *dest += ", ";
++ *dest += itr->first;
++ *dest += ": \"";
++ print_object_std(dest, &(itr->second), flags);
++ *dest += '"';
++ first = false;
++ }
++
++ return;
++ }
++
+ case torrent::Object::TYPE_NONE:
+ return;
+ default:
diff --git a/ps-silent-catch_all.patch b/ps-silent-catch_all.patch
new file mode 100644
index 000000000000..7ae1ea945c68
--- /dev/null
+++ b/ps-silent-catch_all.patch
@@ -0,0 +1,18 @@
+--- a/src/command_dynamic.cc
++++ b/src/command_dynamic.cc
+@@ -425,10 +425,14 @@ system_method_list_keys(const torrent::Object::string_type& args) {
+
+ torrent::Object
+ cmd_catch(rpc::target_type target, const torrent::Object& args) {
++ bool silent = (args.is_list()
++ && !args.as_list().empty()
++ && args.as_list().front().is_string()
++ && args.as_list().front().as_string() == "false=");
+ try {
+ return rpc::call_object(args, target);
+ } catch (torrent::input_error& e) {
+- lt_log_print(torrent::LOG_WARN, "Caught exception: '%s'.", e.what());
++ if (!silent) lt_log_print(torrent::LOG_WARN, "Caught exception: '%s'.", e.what());
+ return torrent::Object();
+ }
+ }
diff --git a/ui_pyroscope.cc b/ui_pyroscope.cc
index cce7b375a9ed..c03f0e68ac84 100644
--- a/ui_pyroscope.cc
+++ b/ui_pyroscope.cc
@@ -39,11 +39,10 @@ python -c 'print u"\u22c5 \u22c5\u22c5 \u201d \u2019 \u266f \u2622 \u260d \u2318
#include "control.h"
#include "command_helpers.h"
-#if (RT_HEX_VERSION >= 0x000901)
- #define _cxxstd_ tr1
-#else
- #define _cxxstd_ std
-#endif
+
+// In 0.9.x this changed to 'tr1', see https://stackoverflow.com/a/4682954/2748717
+// "C++ Technical Report 1" was later added to "C++11", using tr1 makes stuff compile on older GCC
+#define _cxxstd_ tr1
#define D_INFO(item) (item->info())
#include "rpc/object_storage.h"
@@ -53,7 +52,10 @@ extern torrent::Tracker* get_active_tracker(torrent::Download* item);
extern std::string get_active_tracker_domain(torrent::Download* item);
-#define TRACKER_LABEL_WIDTH 20U
+#define CANVAS_POS_1ST_ITEM 2
+#define X_OF_Y_CANVAS_MIN_WIDTH 28
+#define NAME_RESERVED_WIDTH 6
+#define TRACKER_LABEL_WIDTH 20
// definition from display/window_download_list.cc that is not in the header file
typedef std::pair<core::View::iterator, core::View::iterator> Range;
@@ -67,35 +69,58 @@ int ratio_col[] = {
ps::COL_PROGRESS100, ps::COL_PROGRESS120,
};
+// ps::COL_PRIO
+static int col_idx_prio[] = {
+ ps::COL_PROGRESS0, ps::COL_PROGRESS60, ps::COL_INFO, ps::COL_PROGRESS120
+};
+
+// ps::COL_STATE
+static int col_idx_state[] = {
+ ps::COL_PROGRESS0, ps::COL_PROGRESS0, ps::COL_PROGRESS80, ps::COL_PROGRESS100
+};
+
+
// basic color names
static const char* color_names[] = {
"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"
};
+// color value for custom column rendering
+static std::string ui_canvas_color;
+
// 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.custom1",
+ "ui.color.custom2",
+ "ui.color.custom3",
+ "ui.color.custom4",
+ "ui.color.custom5",
+ "ui.color.custom6",
+ "ui.color.custom7",
+ "ui.color.custom8",
+ "ui.color.custom9",
+ "ui.color.progress0", // 10
"ui.color.progress20",
"ui.color.progress40",
"ui.color.progress60",
"ui.color.progress80",
"ui.color.progress100",
"ui.color.progress120",
+ "ui.color.title",
+ "ui.color.footer",
+ "ui.color.focus",
+ "ui.color.label", // 20
+ "ui.color.info",
+ "ui.color.alarm",
"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)
@@ -117,7 +142,7 @@ static std::string network_history_down_str;
// Chop off an UTF-8 string
std::string u8_chop(const std::string& text, size_t glyphs) {
std::mbstate_t mbs = std::mbstate_t();
- int bytes = 0, skip;
+ size_t bytes = 0, skip;
const char* pos = text.c_str();
while (*pos && glyphs-- > 0 && (skip = std::mbrlen(pos, text.length() - bytes, &mbs)) > 0) {
@@ -150,14 +175,15 @@ std::string get_custom_string(core::Download* d, const char* name) {
// 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("⋅ ⋅⋅ ");
+std::string elapsed_time(unsigned long dt, unsigned long t0) {
+ 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;
+ dt = std::labs((t0 ? t0 : time(NULL)) - dt);
+ if (dt == 0) return std::string("⋅ ⋅⋅ ");
while (threshold[dim] && dt >= threshold[dim]) ++dim;
if (dim) --dim;
float val = float(dt) / float(threshold[dim]);
@@ -176,7 +202,7 @@ std::string elapsed_time(unsigned long dt) {
// 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(" ·");
+ if (!num) return std::string(" ⋅");
char buffer[10];
if (num < 100) {
@@ -277,7 +303,7 @@ void ui_pyroscope_colormap_init() {
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
+ for (size_t 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;
@@ -301,7 +327,7 @@ void ui_pyroscope_colormap_init() {
}
// check that fg & bg color index is valid
- if (col[0] != -1 && col[0] >= get_colors() || col[1] != -1 && col[1] >= get_colors()) {
+ 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();
@@ -343,11 +369,59 @@ static int row_offset(core::View* view, Range& range) {
}
-static void decorate_download_title(Window* window, display::Canvas* canvas, core::View* view, int pos, Range& range) {
+torrent::Object ui_canvas_color_get() {
+ return ::ui_canvas_color;
+}
+
+
+torrent::Object ui_canvas_color_set(const torrent::Object::string_type& arg) {
+ ::ui_canvas_color = arg;
+ return torrent::Object();
+}
+
+
+int64_t cmd_d_message_alert(core::Download* d) {
+ int64_t alert = ps::ALERT_NORMAL;
+
+ if (!d->message().empty()) {
+ alert = ps::ALERT_GENERIC;
+
+ if (d->message().find("Tried all trackers") != std::string::npos)
+ alert = ps::ALERT_NORMAL_CYCLING;
+ else if (d->message().find("Timeout was reached") != std::string::npos
+ || d->message().find("Timed out") != std::string::npos)
+ alert = ps::ALERT_TIMEOUT;
+ else if (d->message().find("Connecting to") != std::string::npos)
+ alert = ps::ALERT_CONNECT;
+ 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 = ps::ALERT_REQUEST;
+ 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 = ps::ALERT_GONE;
+ 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 = ps::ALERT_PERMS;
+ }
+
+ return alert;
+}
+
+
+static void decorate_download_title(Window* window, display::Canvas* canvas, core::View* view,
+ int pos, Range& range, int x_title) {
int offset = row_offset(view, range);
core::Download* item = *range.first;
bool active = item->is_open() && item->is_active();
+ if (int(canvas->width()) <= x_title) return;
+
// download title color
int title_col;
unsigned long focus_attr = range.first == view->focus() ? attr_map[ps::COL_FOCUS] : 0;
@@ -357,19 +431,23 @@ static void decorate_download_title(Window* window, display::Canvas* canvas, cor
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);
+ canvas->set_attr(x_title, pos, -1, attr_map[title_col] | focus_attr, title_col);
// show label for active tracker (a/k/a in focus tracker)
+ if (int(canvas->width()) <= x_title + NAME_RESERVED_WIDTH + 3) return;
std::string url = get_active_tracker_domain((*range.first)->download());
- if (!url.empty()) {
- std::string alias = tracker_aliases[url];
- if (!alias.empty()) url = alias;
-
- // shorten label if too long
+ if (url.empty()) return;
+ std::string alias = tracker_aliases[url];
+ if (!alias.empty()) url = alias;
+
+ // shorten label if too long
+ int max_len = std::min(TRACKER_LABEL_WIDTH,
+ int(canvas->width()) - x_title - NAME_RESERVED_WIDTH - 3);
+ if (max_len > 0) {
int len = url.length();
- if (len > TRACKER_LABEL_WIDTH) {
- url = "…" + url.substr(len - TRACKER_LABEL_WIDTH);
- len = TRACKER_LABEL_WIDTH + 1;
+ if (len > max_len) {
+ url = "…" + url.substr(len - max_len);
+ len = max_len + 1;
}
// print it right-justified and in braces
@@ -379,7 +457,8 @@ static void decorate_download_title(Window* window, display::Canvas* canvas, cor
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);
+ canvas->set_attr(canvas->width() - 1, pos, 1,
+ (attr_map[td_col + offset] | focus_attr) ^ A_BOLD, td_col + offset);
}
}
@@ -405,13 +484,13 @@ void ui_pyroscope_download_list_redraw_item(Window* window, display::Canvas* can
}
}
- decorate_download_title(window, canvas, view, pos, range);
+ decorate_download_title(window, canvas, view, pos, range, 2);
// better handling for trail of line 2 (ratio etc.)
int status_pos = 91;
int ratio = rpc::call_command_value("d.ratio", rpc::make_target(*range.first));
- if (status_pos < canvas->width()) {
+ if (status_pos < int(canvas->width())) {
canvas->print(status_pos, pos+1, "R:%6.2f [%c%c] %-4.4s ",
float(ratio) / 1000.0,
rpc::call_command_string("d.tied_to_file", rpc::make_target(*range.first)).empty() ? ' ' : 'T',
@@ -423,7 +502,7 @@ void ui_pyroscope_download_list_redraw_item(Window* window, display::Canvas* can
}
// if space is left, show throttle name
- if (status_pos < canvas->width()) {
+ if (status_pos < int(canvas->width())) {
std::string item_status;
if (!(*range.first)->bencode()->get_key("rtorrent").get_key_string("throttle_name").empty()) {
@@ -452,7 +531,7 @@ void ui_pyroscope_download_list_redraw_item(Window* window, display::Canvas* can
// 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) {
+ for (size_t 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);
}
@@ -481,8 +560,8 @@ void ui_pyroscope_download_list_redraw_item(Window* window, display::Canvas* can
// Render columns from `column_defs`, return total length
-int render_columns(bool headers, rpc::target_type target,
- display::Canvas* canvas, int column, int pos,
+int render_columns(bool headers, bool narrow, rpc::target_type target, core::Download* item,
+ display::Canvas* canvas, int column, int pos, int offset,
const torrent::Object::map_type& column_defs) {
torrent::Object::map_const_iterator cols_itr, last_col = column_defs.end();
int total = 0;
@@ -491,25 +570,115 @@ int render_columns(bool headers, rpc::target_type target,
// Skip sort key (format is "sort:len:title")
size_t header_colon = cols_itr->first.find(':');
if (header_colon == std::string::npos) continue;
+ const char* header_pos = cols_itr->first.c_str() + header_colon + 1;
+
+ // Check for 'sacrificial' marker
+ if (*header_pos == '?') {
+ if (narrow) continue; // skip this column
+ ++header_pos;
+ }
// Parse header length
- const char* header_pos = cols_itr->first.c_str() + header_colon + 1;
char* header_text = 0;
int header_len = (int)strtol(header_pos, &header_text, 10);
- if (*header_text++ != ':') continue;
+
+ // Check available space
+ if (int(canvas->width()) - NAME_RESERVED_WIDTH < column + header_len) {
+ if (!narrow && headers) return -1; // trigger narrow mode
+ break; // all the space we have used up, get us out of here
+ }
+
+ // Do we have a colordef?
+ std::string color_def;
+ if (*header_text == 'C') {
+ int x = 0;
+ while (header_text[x] && header_text[x] != ':') x++;
+ color_def.assign(header_text, x);
+ header_text += x;
+ }
+ if (*header_text++ != ':') continue; // Header text is missing
// Render title text, or the result of the column command
+ ui_canvas_color = color_def;
if (headers) {
- canvas->print(column, pos, " %s", header_text);
+ canvas->print(column, pos, "%s", header_text);
} else {
- std::string text = rpc::call_object_nothrow(cols_itr->second, target).as_string();
- //std::string text = rpc::call_command_string(cols_itr->second.as_string().c_str(), target);
- canvas->print(column, pos, " %s", u8_chop(text, header_len).c_str());
+ std::string text;
+ try {
+ text = rpc::call_object(cols_itr->second, target).as_string();
+ } catch (torrent::input_error& e) {
+ // Rows will rotate through the error string (assuming it is thrown for each row)
+ char buf[10];
+ int what_pos = *e.what() ? (pos - CANVAS_POS_1ST_ITEM) * header_len % strlen(e.what()) : 0;
+ snprintf(buf, sizeof(buf), "C22/%d", header_len);
+ ui_canvas_color = buf;
+ text = std::string(e.what()).substr(what_pos, header_len);
+ }
+ canvas->print(column, pos, "%s", u8_chop(text, header_len).c_str());
+ //canvas->print(column, pos, " %s ", ui_canvas_color); // debug: print color index
+
+ // apply colorization
+ if (ui_canvas_color.empty()) {
+ canvas->set_attr(column, pos, header_len,
+ attr_map[ps::COL_INFO + offset], ps::COL_INFO + offset);
+ } else {
+ int attr_col = column;
+ for (const char* ptr = ui_canvas_color.c_str(); *ptr && *ptr++ == 'C'; ) {
+ char* next = 0;
+ int attr_idx = (int)strtol(ptr, &next, 10); if (next == ptr) break; ptr = next;
+ if (*ptr != '/') continue;
+
+ // System colors – these are mapped to a 'normal' color index
+ if (item) {
+ const char* c_down = "C28/4C27/2"; // leeching + incomplete
+ const char* c_seed = "C24/4C21/2"; // seeding + info
+ const char* c_done = "C21/1C24/1C21/2C24/2"; // info + seeding (is_done)
+ const char* c_part = "C21/1C27/1C21/2C27/2"; // info + incomplete
+
+ switch (attr_idx) {
+ case ps::COL_DOWN_TIME: // C90/6
+ ptr = item->is_done() ? c_done :
+ D_INFO(item)->down_rate()->rate() ? c_down : c_part;
+ continue; // with new color definition
+ case ps::COL_UP_TIME: // C96/6
+ ptr = D_INFO(item)->up_rate()->rate() ? c_seed :
+ item->is_done() ? c_done : c_part;
+ continue; // with new color definition
+ case ps::COL_PRIO:
+ attr_idx = col_idx_prio[std::min(3U, (uint32_t) item->priority())];
+ break;
+ case ps::COL_STATE:
+ attr_idx = col_idx_state[(item->is_open() << 1) | item->is_active()];
+ break;
+ case ps::COL_RATIO:
+ attr_idx = ratio_color(rpc::call_command_value("d.ratio", target));
+ break;
+ case ps::COL_PROGRESS:
+ attr_idx = ratio_color(item->file_list()->completed_chunks() * 1000 /
+ item->file_list()->size_chunks());
+ break;
+ case ps::COL_ALERT: // COL_ALARM is the actual color, this is the dynamic one
+ bool has_alert = !item->message().empty()
+ && item->message().find("Tried all trackers") == std::string::npos;
+ attr_idx = has_alert ? ps::COL_ALARM : ps::COL_INFO;
+ break;
+ }
+ }
+
+ // Get color area length, if both pos/len are ok, do it
+ int attr_len = (int)strtol(ptr + 1, &next, 10); if (next == ptr) break; ptr = next;
+ if (attr_idx && attr_len) {
+ if (attr_idx >= ps::COL_MAX) attr_idx = ps::COL_ALARM;
+ canvas->set_attr(attr_col, pos, attr_len, attr_map[attr_idx + offset], attr_idx + offset);
+ attr_col += attr_len;
+ }
+ }
+ }
}
- // Advance position
- column += header_len + 1;
- total += header_len + 1;
+ // Advance canvas column position, and add to length
+ column += header_len;
+ total += header_len;
}
return total;
}
@@ -519,8 +688,8 @@ int render_columns(bool headers, rpc::target_type target,
// 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 (canvas->width() >= X_OF_Y_CANVAS_MIN_WIDTH) {
+ size_t item_idx = view->focus() - view->begin_visible();
if (item_idx == view->size())
canvas->print(canvas->width() - 16, 0, "[ none of %-5d]", view->size());
else
@@ -534,18 +703,23 @@ bool ui_pyroscope_download_list_redraw(Window* window, display::Canvas* canvas,
if (view->empty_visible() || canvas->width() < 5 || canvas->height() < 2)
return true;
- // show column headers
+ // Prepare rendering
const torrent::Object::map_type& column_defs = control->object_storage()->get_str("ui.column.render").as_map();
- // x_base value depends on the static headers below!
- int pos = 1, x_base = 31, column = x_base;
-
- canvas->print(2, pos, " ⣿ ⚡ ☯ ⚑ ↺ ⤴ ⤵ ∆ ⌚ ≀∇ ");
- column += render_columns(true, rpc::make_target(), canvas, column, pos, column_defs);
- canvas->print(column, pos, " Name "); column += 6;
- if (canvas->width() - column > TRACKER_LABEL_WIDTH) {
- canvas->print(canvas->width() - 14, 1, "Tracker Domain");
+ int pos = 1, x_base = 2, column = x_base;
+ bool narrow = false;
+
+ // Render header line
+ canvas->print(0, pos, "⇳ ");
+ int custom_width = render_columns(true, narrow, rpc::make_target(), 0, canvas, column, pos, 0, column_defs);
+ if (custom_width < 0) { // enter narrow mode
+ canvas->print(x_base, pos, "%s", std::string(canvas->width() - x_base, ' ').c_str()); // clean slate
+ narrow = true;
+ custom_width = render_columns(true, narrow, rpc::make_target(), 0, canvas, column, pos, 0, column_defs);
}
- canvas->set_attr(0, pos, -1, attr_map[ps::COL_LABEL], ps::COL_LABEL);
+ column += custom_width; canvas->print(column, pos, " Name "); column += NAME_RESERVED_WIDTH;
+ if (int(canvas->width()) - 8 > column)
+ canvas->print(canvas->width() - 8, pos, " Tracker");
+ canvas->set_attr(0, pos, -1, attr_map[ps::COL_LABEL], ps::COL_LABEL); // header line unicolor
// network traffic
int network_history_lines = 0;
@@ -559,23 +733,6 @@ bool ui_pyroscope_download_list_redraw(Window* window, display::Canvas* canvas,
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(),
@@ -583,121 +740,36 @@ bool ui_pyroscope_download_list_redraw(Window* window, display::Canvas* canvas,
view->end_visible(),
canvas->height()-2-2-network_history_lines);
- pos = 2;
+ pos = CANVAS_POS_1ST_ITEM;
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.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 = "⨂ ";
- }
-
- 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 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 ",
- range.first == view->focus() ? "»" : " ",
- 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()
- );
+ // Render focus marker
+ canvas->print(0, pos, range.first == view->focus() ? "> " : " ");
// Render custom columns
+ canvas->set_attr(1, pos, -1, attr_map[col_active + offset], col_active + offset); // base color, whole line
column = x_base;
- int custom_len = render_columns(false, rpc::make_target(d), canvas, column, pos, column_defs);
- canvas->set_attr(column, pos, custom_len, attr_map[ps::COL_DEFAULT], ps::COL_DEFAULT);
- column += custom_len;
- int x_name = column + 1;
-
- // Render name
- canvas->print(column, pos, " %s", u8_chop(
- displayname.empty() ? d->info()->name() : displayname.c_str(),
- canvas->width() - x_name - 1).c_str());
-
- int x_scrape = 3 + 4*2 + 1; // lead, 4 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);
+ render_columns(false, narrow, rpc::make_target(d), d, canvas, column, pos, offset, column_defs);
+ column += custom_width;
+
+ // Render name + tracker
+ if (int(canvas->width()) > column) {
+ std::string displayname = get_custom_string(d, "displayname");
+ canvas->print(column, pos, " %s",
+ u8_chop(displayname.empty() ? d->info()->name() : displayname.c_str(),
+ canvas->width() - column - 1).c_str());
+ decorate_download_title(window, canvas, view, pos, range, column + 1);
}
- // is this the item in focus?
+ // Colorize focus marker
if (range.first == view->focus()) {
canvas->set_attr(0, pos, 1, attr_map[ps::COL_FOCUS], ps::COL_FOCUS);
}
+ // Advance to next item
++pos;
++range.first;
}
@@ -787,7 +859,7 @@ void network_history_format(std::string& buf, char kind, uint32_t* data) {
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) {
+ for (uint32_t 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))];
@@ -848,6 +920,18 @@ torrent::Object cmd_trackers_alias_items(rpc::target_type target) {
}
+torrent::Object apply_time_delta(const torrent::Object::list_type& args) {
+ if (args.size() != 1 && args.size() != 2)
+ throw torrent::input_error("convert.time_delta takes 1 or 2 arguments!");
+ if (!args.front().is_value())
+ throw torrent::input_error("convert.time_delta: time argument must be a value!");
+ if (args.size() == 2 && !args.back().is_value())
+ throw torrent::input_error("convert.time_delta: time-base argument must be a value!");
+
+ return elapsed_time(args.front().as_value(), args.size() == 2 ? args.back().as_value() : 0L);
+}
+
+
torrent::Object apply_human_size(const torrent::Object::list_type& args) {
if (args.size() != 1 && args.size() != 2)
throw torrent::input_error("convert.human_size takes 1 or 2 arguments!");
@@ -890,8 +974,10 @@ void initialize_command_ui_pyroscope() {
CMD2_ANY_LIST("trackers.alias.set_key", &cmd_trackers_alias_set_key);
CMD2_ANY("trackers.alias.items", _cxxstd_::bind(&cmd_trackers_alias_items, _cxxstd_::placeholders::_1));
- CMD2_VAR_VALUE("ui.style.progress", 1);
- CMD2_VAR_VALUE("ui.style.ratio", 1);
+ CMD2_DL("d.message.alert", _cxxstd_::bind(&display::cmd_d_message_alert, _cxxstd_::placeholders::_1));
+
+ CMD2_ANY ("ui.canvas_color", _cxxstd_::bind(&display::ui_canvas_color_get));
+ CMD2_ANY_STRING ("ui.canvas_color.set", _cxxstd_::bind(&display::ui_canvas_color_set, _cxxstd_::placeholders::_2));
PS_VARIABLE_COLOR("ui.color.progress0", "red");
PS_VARIABLE_COLOR("ui.color.progress20", "bold bright red");
@@ -914,16 +1000,147 @@ void initialize_command_ui_pyroscope() {
PS_VARIABLE_COLOR("ui.color.even", "");
PS_VARIABLE_COLOR("ui.color.info", "white");
PS_VARIABLE_COLOR("ui.color.focus", "reverse");
+ PS_VARIABLE_COLOR("ui.color.custom1", "");
+ PS_VARIABLE_COLOR("ui.color.custom2", "");
+ PS_VARIABLE_COLOR("ui.color.custom3", "");
+ PS_VARIABLE_COLOR("ui.color.custom4", "");
+ PS_VARIABLE_COLOR("ui.color.custom5", "");
+ PS_VARIABLE_COLOR("ui.color.custom6", "");
+ PS_VARIABLE_COLOR("ui.color.custom7", "");
+ PS_VARIABLE_COLOR("ui.color.custom8", "");
+ PS_VARIABLE_COLOR("ui.color.custom9", "");
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);
+ CMD2_ANY_LIST("convert.time_delta", _cxxstd_::bind(&apply_time_delta, _cxxstd_::placeholders::_2));
CMD2_ANY_LIST("convert.human_size", _cxxstd_::bind(&apply_human_size, _cxxstd_::placeholders::_2));
CMD2_ANY_LIST("convert.magnitude", _cxxstd_::bind(&apply_magnitude, _cxxstd_::placeholders::_2));
- rpc::parse_command_multiple
- (rpc::make_target(),
+ // TODO: deprecated and useless, remove these in v1.2
+ CMD2_VAR_VALUE("ui.style.progress", 1);
+ CMD2_VAR_VALUE("ui.style.ratio", 1);
+
+
+ // Set some defaults by executing an in-memory script
+ std::string init_commands;
+ for (int colidx = ps::COL_DEFAULT + 1; colidx < ps::COL_MAX; colidx++) {
+ char cmdbuf[80];
+ snprintf(cmdbuf, sizeof(cmdbuf),
+ "method.insert = %s.index, private|value|const, %d\n",
+ color_vars[colidx], colidx);
+ init_commands.append(cmdbuf);
+ }
+
+ init_commands.append(
+ // Multi-method to store column definitions
"method.insert = ui.column.render, multi|rlookup|static\n"
+
+ // Bind '*' to toggle between collapsed and expanded display
+ "schedule2 = collapsed_view_toggle, 0, 0, ((ui.bind_key,download_list,*,view.collapsed.toggle=))\n"
+
+ // TODO: copy (parts of) timestamp cfg here (~/.pyroscope/rtorrent.d/timestamps.rc)
+ // Do NOT move it, since then rT vanilla gets unusable with rtcontrol.
+ // 'system.has' allows to have both.
+
+ // 1: COL_CUSTOM1
+ // …
+ // 9: COL_CUSTOM9
+ // 10: COL_PROGRESS0
+ // 11: COL_PROGRESS20
+ // 12: COL_PROGRESS40
+ // 13: COL_PROGRESS60
+ // 14: COL_PROGRESS80
+ // 15: COL_PROGRESS100
+ // 16: COL_PROGRESS120
+ // 17: COL_TITLE
+ // 18: COL_FOOTER
+ // 19: COL_FOCUS
+ // 20: COL_LABEL
+ // 21: COL_INFO
+ // 22: COL_ALARM
+ // 23: COL_COMPLETE
+ // 24: COL_SEEDING
+ // 25: COL_STOPPED
+ // 26: COL_QUEUED
+ // 27: COL_INCOMPLETE
+ // 28: COL_LEECHING
+ // 29: COL_ODD
+ // 30: COL_EVEN
+
+ // 90: COL_DOWN_TIME
+ // 91: COL_PRIO
+ // 92: COL_STATE
+ // 93: COL_RATIO
+ // 94: COL_PROGRESS
+ // 95: COL_ALERT
+ // 96: COL_UP_TIME
+
+ // Status flags (❢ ☢ ☍ ⌘)
+ "method.set_key = ui.column.render, \"100:3C95/2:❢ \","
+ " ((array.at, {\" \", \"♺ \", \"⚠ \", \"◔ \", \"⚡ \", \"↯ \", \"¿?\", \"⨂ \"}, ((d.message.alert)) ))\n"
+ "method.set_key = ui.column.render, \"110:2C92/2:☢ \","
+ " ((string.map, ((cat, ((d.is_open)), ((d.is_active)))), {00, \"▪ \"}, {01, \"▪ \"}, {10, \"╍ \"}, {11, \"▹ \"}))\n"
+ "method.set_key = ui.column.render, \"120:?2:☍ \","
+ " ((array.at, {\"⚯ \", \"  \"}, ((not, ((d.tied_to_file)) )) ))\n"
+ "method.set_key = ui.column.render, \"130:?2:⌘ \","
+ " ((array.at, {\"⚒ \", \"◌ \"}, ((d.ignore_commands)) ))\n"
+
+ // Scrape info (↺ ⤴ ⤵)
+ "method.set_key = ui.column.render, \"400:?3C23/3: ↺ \", ((convert.magnitude, ((d.tracker_scrape.downloaded)) ))\n"
+ "method.set_key = ui.column.render, \"410:?3C24/3: ⤴ \", ((convert.magnitude, ((d.tracker_scrape.complete)) ))\n"
+ "method.set_key = ui.column.render, \"420:?3C14/3: ⤵ \", ((convert.magnitude, ((d.tracker_scrape.incomplete)) ))\n"
+
+ // Traffic indicator (⚡)
+ "method.set_key = ui.column.render, \"500:?2:⚡ \","
+ " ((string.map, ((cat, ((not, ((d.up.rate)) )), ((not, ((d.down.rate)) )) )),"
+ " {00, \"⇅ \"}, {01, \"↟ \"}, {10, \"↡ \"}, {11, \"  \"} ))\n"
+
+ // Number of connected peers (℞)
+ "method.set_key = ui.column.render, \"510:3C28/3:℞ \", ((convert.magnitude, ((d.peers_connected)) ))\n"
+
+ // Up|Leech Time / Down|Completion or Loaded Time
+ // TODO: Could use "d.timestamp.started" and "d.timestamp.finished" here, but need to check
+ // when they were introduced, and if they're always set (e.g. what about fast-resumed items?)
+ "method.set_key = ui.column.render, \"520:6C96/6:∆⋮ ⌛ \","
+ " ((if, ((d.up.rate)),"
+ " ((convert.human_size, ((d.up.rate)), ((value, 10)) )),"
+ " ((convert.time_delta, ((value, ((d.custom, tm_completed)) )),"
+ " ((value, ((d.custom.if_z, tm_started, ((d.custom, tm_loaded)) )) )) ))"
+ " ))\n"
+ "method.set_key = ui.column.render, \"530:6C90/6:∇⋮ ⌚ \","
+ " ((if, ((d.down.rate)),"
+ " ((convert.human_size, ((d.down.rate)), ((value, 10)) )),"
+ " ((convert.time_delta, ((value, ((d.custom.if_z, tm_completed, ((d.custom, tm_loaded)) )) )) ))"
+ " ))\n"
+
+ // Upload total, progress, ratio, and data size
+ "method.set_key = ui.column.render, \"900:?5C24/3C21/2: Σ⇈ \","
+ " ((if, ((d.up.total)),"
+ " ((convert.human_size, ((d.up.total)), (value, 10))),"
+ " ((cat, \" ⋅ \"))"
+ " ))\n"
+ "method.set_key = ui.column.render, \"910:2C94/2:⣿ \","
+ " ((string.substr, \" ⠁ ⠉ ⠋ ⠛ ⠟ ⠿ ⡿ ⣿ ❚ \", ((math.mul, 2, "
+ " ((math.div, ((math.mul, ((d.completed_chunks)), 10)), ((d.size_chunks)) )) )), 2, \"✔ \"))\n"
+ // " ⠁ ⠉ ⠋ ⠛ ⠟ ⠿ ⡿ ⣿ ❚ "
+ //⠀" ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ "
+ "method.set_key = ui.column.render, \"920:3C93/3:☯ \","
+ " ((string.substr, \"☹ ➀ ➁ ➂ ➃ ➄ ➅ ➆ ➇ ➈ ➉ \", ((math.mul, 2, ((math.div, ((d.ratio)), 1000)) )), 2, \"⊛ \"))\n"
+ // "☹ ➀ ➁ ➂ ➃ ➄ ➅ ➆ ➇ ➈ ➉ "
+ // "☹ ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ "
+ // "☹ ➊ ➋ ➌ ➍ ➎ ➏ ➐ ➑ ➒ ➓ "
+ "method.set_key = ui.column.render, \"930:5C15/3C21/2: ✇ \","
+ " ((convert.human_size, ((d.size_bytes)) ))\n"
+
+ // Explicitly managed status (✰ = prio; ⚑ = tagged)
+ "method.set_key = ui.column.render, \"970:2C91/2:✰ \","
+ " ((array.at, {\"✖ \", \"⇣ \", \" \", \"⇡ \"}, ((d.priority)) ))\n"
+ "method.set_key = ui.column.render, \"980:2C16/2:⚑ \","
+ " ((array.at, {\"  \", \"⚑ \"}, ((d.views.has, tagged)) ))\n"
);
+
+ //printf("%s", init_commands.c_str());
+ rpc::parse_command_multiple(rpc::make_target(), init_commands.c_str());
}
diff --git a/ui_pyroscope.h b/ui_pyroscope.h
index d6608f344e46..cb831c449891 100644
--- a/ui_pyroscope.h
+++ b/ui_pyroscope.h
@@ -1,34 +1,72 @@
#ifndef UI_PYROSCOPE_H
#define UI_PYROSCOPE_H
+#include <string>
+
+
namespace ps {
+#define COL_SYS_BASE 90
+
+enum AlertKind {
+ ALERT_NORMAL,
+ ALERT_NORMAL_CYCLING, // Tried all trackers
+ ALERT_GENERIC,
+ ALERT_TIMEOUT,
+ ALERT_CONNECT,
+ ALERT_REQUEST,
+ ALERT_GONE,
+ ALERT_PERMS,
+ ALERT_MAX
+};
+
+
enum ColorKind {
COL_DEFAULT,
- COL_PROGRESS0,
+ COL_CUSTOM1,
+ COL_CUSTOM2,
+ COL_CUSTOM3,
+ COL_CUSTOM4,
+ COL_CUSTOM5,
+ COL_CUSTOM6,
+ COL_CUSTOM7,
+ COL_CUSTOM8,
+ COL_CUSTOM9,
+ COL_PROGRESS0, // 10
COL_PROGRESS20,
COL_PROGRESS40,
COL_PROGRESS60,
COL_PROGRESS80,
COL_PROGRESS100,
COL_PROGRESS120,
+ COL_TITLE,
+ COL_FOOTER,
+ COL_FOCUS,
+ COL_LABEL, // 20
+ COL_INFO,
+ COL_ALARM,
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
+ COL_MAX,
+
+ COL_DOWN_TIME = COL_SYS_BASE,
+ COL_PRIO,
+ COL_STATE,
+ COL_RATIO,
+ COL_PROGRESS,
+ COL_ALERT,
+ COL_UP_TIME,
+ COL_SYS_MAX
};
} // namespace
+extern void add_capability(const char* name); // defined in command_pyroscope.cc
+
#endif