diff options
author | xsmile | 2018-06-04 15:20:31 +0200 |
---|---|---|
committer | xsmile | 2018-06-04 15:20:31 +0200 |
commit | d5f9faa36269d5ce2144d93ab4622f3d67e102fb (patch) | |
tree | ee3300867bac5924804e985b1105a1425580ecb8 | |
parent | 8a6e454d1f806fd9e7ca74d922b24e76e7a5e79e (diff) | |
download | aur-d5f9faa36269d5ce2144d93ab4622f3d67e102fb.tar.gz |
update
-rw-r--r-- | .SRCINFO | 16 | ||||
-rw-r--r-- | PKGBUILD | 27 | ||||
-rw-r--r-- | backport_0.9.6_algorithm_median.patch | 29 | ||||
-rw-r--r-- | command_pyroscope.cc | 458 | ||||
-rw-r--r-- | ps-import.return_all.patch | 10 | ||||
-rw-r--r-- | ps-object_std-map-serialization_all.patch | 24 | ||||
-rw-r--r-- | ps-silent-catch_all.patch | 18 | ||||
-rw-r--r-- | ui_pyroscope.cc | 579 | ||||
-rw-r--r-- | ui_pyroscope.h | 54 |
9 files changed, 963 insertions, 252 deletions
@@ -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 @@ -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 |