diff options
-rw-r--r-- | PKGBUILD | 12 | ||||
-rw-r--r-- | command_pyroscope.cc | 345 | ||||
-rw-r--r-- | ps-event-view_all.patch | 30 | ||||
-rw-r--r-- | ps-fix-log-xmlrpc-close_all.patch | 22 | ||||
-rw-r--r-- | ui_pyroscope.cc | 111 | ||||
-rw-r--r-- | ui_pyroscope.h | 7 |
6 files changed, 470 insertions, 57 deletions
@@ -3,7 +3,7 @@ _pkgname=rtorrent pkgname=rtorrent-ps _pkgver=0.9.6 -pkgver=1.0.r273.g8b4cb6e +pkgver=1.1.r15.g56057fc pkgrel=1 pkgdesc='Extended rTorrent distribution with UI enhancements, colorization, and some added features' url='https://github.com/pyroscope/rtorrent-ps' @@ -17,6 +17,7 @@ source=("https://github.com/rakshasa/$_pkgname/archive/$_pkgver.tar.gz" 'command_pyroscope.cc' 'ps-event-view_all.patch' 'ps-fix-double-slash-319_all.patch' + 'ps-fix-log-xmlrpc-close_all.patch' 'ps-fix-sort-started-stopped-views_all.patch' 'ps-fix-throttle-args_all.patch' 'ps-handle-sighup-578_all.patch' @@ -39,9 +40,10 @@ source=("https://github.com/rakshasa/$_pkgname/archive/$_pkgver.tar.gz" 'ui_pyroscope.patch') md5sums=('b8b4009f95f8543244ae1d23b1810d7c' 'b49903d3fa25a66c72db69570dfe8b47' - '313581839dd8bcbd14b4759789adcada' - '56701bca42cc9b309637bf3f918ede12' + 'e7f737e4b2c4db9659faf2609a17732e' + 'fbe511a1dfe89fe0510a077e61ae6ec7' '22fae392c6e281dc438b39a5019e7e1b' + '1b4f82e6123c5baa0cd07056e368064e' '3fd739c0d5a9442f0cdec9ed5a720eaa' 'ab490d1d1df9c27f3cf624966f7f03f6' '2137e16f8b881170fb92fb7a6c276193' @@ -59,8 +61,8 @@ md5sums=('b8b4009f95f8543244ae1d23b1810d7c' '26faff00b306b6ef276a7d9e6d964994' 'bd04a0699b80c8042e1cf63a7e0e4222' 'd0a956f0eb4b53b66d83df2a8a4d16dc' - '4f51b9e66d63e24570d7f4c88fb6bf1d' - '2a71f0c478e8fe7bce464ac85c4bec44' + '88c3520c6ba51a31ea29903b5ee6c484' + 'a572a0fd087c89d8f50f16544bb1ec69' '0a2bbaf74c7160ba33876dcc2f050f14') prepare() { diff --git a/command_pyroscope.cc b/command_pyroscope.cc index e432cffe6473..80fa52b54f0b 100644 --- a/command_pyroscope.cc +++ b/command_pyroscope.cc @@ -180,16 +180,19 @@ torrent::Object apply_random(rpc::target_type target, const torrent::Object::lis torrent::Tracker* get_active_tracker(torrent::Download* item) { torrent::TrackerList* tl = item->tracker_list(); torrent::Tracker* tracker = 0; + torrent::Tracker* fallback = 0; 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) { - break; + if (tracker->is_usable() && tracker->type() == torrent::Tracker::TRACKER_HTTP) { + if (!fallback) fallback = tracker; + if (tracker->scrape_complete() || tracker->scrape_incomplete()) { + break; + } } tracker = 0; } - if (!tracker && tl->size()) tracker = tl->at(0); + if (!tracker && tl->size()) tracker = fallback ? fallback : tl->at(0); return tracker; } @@ -497,6 +500,11 @@ torrent::Object cmd_import_return(rpc::target_type target, const torrent::Object } +torrent::Object cmd_do(rpc::target_type target, const torrent::Object& args) { + return rpc::call_object(args, target); +} + + 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()) @@ -508,13 +516,53 @@ torrent::Object retrieve_d_custom_if_z(core::Download* download, const torrent:: 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); + const std::string& val = download->bencode()->get_key("rtorrent").get_key("custom").get_key_string(key); + return val.empty() ? itr->as_string() : val; } catch (torrent::bencode_error& e) { return itr->as_string(); } } +torrent::Object cmd_d_custom_set_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.set_if_z: Missing key argument."); + const std::string& key = (itr++)->as_string(); + if (key.empty()) + throw torrent::bencode_error("d.custom.set_if_z: Empty key argument."); + if (itr == args.end()) + throw torrent::bencode_error("d.custom.set_if_z: Missing value argument."); + + bool set_it = false; + try { + const std::string& val = download->bencode()->get_key("rtorrent").get_key("custom").get_key_string(key); + set_it = val.empty(); + } catch (torrent::bencode_error& e) { + set_it = true; + } + if (set_it) + download->bencode()->get_key("rtorrent"). + insert_preserve_copy("custom", torrent::Object::create_map()).first->second. + insert_key(key, itr->as_string()); + + return torrent::Object(); +} + + +torrent::Object cmd_d_custom_erase(core::Download* download, const torrent::Object::list_type& args) { + for (torrent::Object::list_type::const_iterator itr = args.begin(), last = args.end(); itr != last; itr++) { + const std::string& key = itr->as_string(); + if (key.empty()) + throw torrent::bencode_error("d.custom.erase: Empty key argument."); + + download->bencode()->get_key("rtorrent").get_key("custom").erase_key(key); + } + + return torrent::Object(); +} + + 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."); @@ -531,6 +579,44 @@ torrent::Object retrieve_d_custom_map(core::Download* download, bool keys_only, } +torrent::Object cmd_d_custom_toggle(core::Download* download, const std::string& key) { + bool result = true; + try { + const std::string& strval = download->bencode()->get_key("rtorrent").get_key("custom").get_key_string(key); + if (!strval.empty()) { + char* junk = 0; + long number = strtol(strval.c_str(), &junk, 10); + while (std::isspace(*junk)) ++junk; + result = !*junk && number == 0; + } + } catch (torrent::bencode_error& e) { + // true + } + + download->bencode()->get_key("rtorrent"). + insert_preserve_copy("custom", torrent::Object::create_map()).first->second. + insert_key(key, result ? "1" : "0"); + return (int64_t) (result ? 1 : 0); +} + + +torrent::Object retrieve_d_custom_as_value(core::Download* download, const std::string& key) { + try { + const std::string& strval = download->bencode()->get_key("rtorrent").get_key("custom").get_key_string(key); + if (strval.empty()) + return (int64_t) 0; + + char* junk = 0; + long result = strtol(strval.c_str(), &junk, 10); + if (*junk) + throw torrent::input_error("d.custom.as_value(" + key + "): junk at end of '" + strval + "'!"); + return (int64_t) result; + } catch (torrent::bencode_error& e) { + return (int64_t) 0; + } +} + + torrent::Object d_multicall_filtered(const torrent::Object::list_type& args) { if (args.size() < 2) @@ -539,12 +625,7 @@ d_multicall_filtered(const torrent::Object::list_type& args) { // Find the given view core::ViewManager* viewManager = control->view_manager(); - core::ViewManager::iterator viewItr; - - if (!arg->as_string().empty()) - viewItr = viewManager->find(arg->as_string()); - else - viewItr = viewManager->find("default"); + core::ViewManager::iterator viewItr = viewManager->find(arg->as_string().empty() ? "default" : arg->as_string()); if (viewItr == viewManager->end()) throw torrent::input_error("Could not find view '" + arg->as_string() + "'."); @@ -594,15 +675,39 @@ 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!"); +// Get length of an UTF8-encoded std::string +size_t u8_length(const std::string& text) { + // Take total length and subtract number of non-leading multi-bytes + return text.length() - count_if(text.begin(), text.end(), + [](char c)->bool { return (c & 0xC0) == 0x80; }); +} + + +// Chop off an UTF-8 string +std::string u8_chop(const std::string& text, size_t glyphs) { + std::mbstate_t mbs = std::mbstate_t(); + size_t bytes = 0, skip; + const char* pos = text.c_str(); + + while (*pos && glyphs-- > 0 && (skip = std::mbrlen(pos, text.length() - bytes, &mbs)) > 0) { + pos += skip; + bytes += skip; } + + return bytes < text.length() ? text.substr(0, bytes) : text; +} + + +static const std::string& string_get_first_arg(const char* name, const torrent::Object::list_type& args) { torrent::Object::list_const_iterator itr = args.begin(); + if (args.size() < 1 || !itr->is_string()) { + throw torrent::input_error("string." + std::string(name) + " needs a string argument.0!"); + } return itr->as_string(); } +// get a numeric arg from a string or value, advancing the passed iterator static int64_t string_get_value_arg(const char* name, torrent::Object::list_const_iterator& itr) { int64_t result = 0; if (itr->is_string()) { @@ -637,6 +742,147 @@ torrent::Object cmd_string_len(rpc::target_type target, const torrent::Object::l } +torrent::Object cmd_string_join(rpc::target_type target, const torrent::Object::list_type& args) { + std::string delim = string_get_first_arg("join", args); + std::string result; + torrent::Object::list_const_iterator first = args.begin() + 1, last = args.end(); + + for (torrent::Object::list_const_iterator itr = first; itr != last; ++itr) { + if (itr != first) result += delim; + rpc::print_object_std(&result, &*itr, 0); + } + + return result; +} + + +torrent::Object cmd_string_strip(int where, const torrent::Object::list_type& args) { + std::string text = string_get_first_arg("[lr]strip", args); + torrent::Object::list_const_iterator first = args.begin() + 1, last = args.end(); + + if (args.size() == 1) { + // Strip whitespace + if (where <= 0) { + text.erase(text.begin(), + std::find_if(text.begin(), text.end(), + std::not1(std::ptr_fun<int, int>(std::isspace)))); + } + if (where >= 0) { + text.erase(std::find_if(text.rbegin(), text.rend(), + std::not1(std::ptr_fun<int, int>(std::isspace))).base(), + text.end()); + } + } else { + size_t lpos = 0, rpos = text.length(); + bool changed; + do { + changed = false; + for (torrent::Object::list_const_iterator itr = first; itr != last; ++itr) { + const std::string& strippable = itr->as_string(); + if (strippable.empty()) continue; + + bool found; + do { + found = false; + + if (where <= 0) { + if (0 == strncmp(text.c_str() + lpos, strippable.c_str(), strippable.length())) { + lpos += strippable.length(); + changed = found = true; + } + } + if (where >= 0 && lpos <= rpos - strippable.length()) { + if (0 == strncmp(text.c_str() + rpos - strippable.length(), strippable.c_str(), strippable.length())) { + rpos -= strippable.length(); + changed = found = true; + } + } + } while (found && lpos < rpos); + } + } while (changed && lpos < rpos); + text = lpos < rpos ? text.substr(lpos, rpos - lpos) : ""; + } + + return text; +} + + +torrent::Object cmd_string_pad(bool at_end, const torrent::Object::list_type& args) { + std::string text; + if (args.size() > 0 && args.begin()->is_value()) { + char buf[65]; + snprintf(buf, sizeof(buf), "%ld", (long)args.begin()->as_value()); + text = buf; + } else { + text = string_get_first_arg("[lr]pad", args); + } + + torrent::Object::list_const_iterator itr = args.begin() + 1; + int64_t pad_len = 0; + std::string filler; + if (itr != args.end()) pad_len = string_get_value_arg("[lr]pad(pad_len)", itr); + if (itr != args.end()) filler = (itr++)->as_string(); + if (pad_len < 0) + throw torrent::input_error("string.[lr]pad: Invalid negative padding length!"); + if (filler.empty()) filler = " "; + size_t text_len = u8_length(text), filler_len = u8_length(filler); + + if (size_t(pad_len) > text_len) { + std::string pad; + size_t count = size_t(pad_len) - text_len; + + if (filler.length() == 1) { // optimize the common case + pad.insert(0, count, filler.at(0)); + } else while (count > 0) { + if (count >= filler_len) { + pad += filler; + count -= filler_len; + } else { + pad += u8_chop(filler, count); + count = 0; + } + } + + return at_end ? text + pad : pad + text; + } + + return text; +} + + +torrent::Object cmd_string_split(rpc::target_type target, const torrent::Object::list_type& args) { + const std::string text = string_get_first_arg("split", args); + if (args.size() != 2 || !args.rbegin()->is_string()) { + throw torrent::input_error("string.split needs a string argument.1!"); + } + const std::string delim = args.rbegin()->as_string(); + torrent::Object result = torrent::Object::create_list(); + torrent::Object::list_type& resultList = result.as_list(); + + if (delim.length()) { + size_t pos = 0, next = 0; + + while ((next = text.find(delim, pos)) != std::string::npos) { + resultList.push_back(text.substr(pos, next - pos)); + pos = next + delim.length(); + } + resultList.push_back(text.substr(pos)); + } else { + std::mbstate_t mbs = std::mbstate_t(); + const char* cpos = text.c_str(); + int bytes = 0, skip; + + while (*cpos && (skip = std::mbrlen(cpos, text.length() - bytes, &mbs)) > 0) { + resultList.push_back(std::string(cpos, skip)); + cpos += skip; + bytes += skip; + } + } + + return result; +} + + 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); @@ -756,6 +1002,36 @@ torrent::Object cmd_string_replace(rpc::target_type target, const torrent::Objec } +torrent::Object cmd_string_compare(int mode, const torrent::Object::list_type& args) { + const char* opnames[] = {"equals", "startswith", "endswith"}; + if (args.size() < 2) { + throw torrent::input_error("string." + std::string(opnames[mode]) + " takes at least two arguments!"); + } + + std::string value = string_get_first_arg(opnames[mode], args); + torrent::Object::list_const_iterator first = args.begin() + 1, last = args.end(); + + for (torrent::Object::list_const_iterator itr = first; itr != last; ++itr) { + const std::string& cmp = itr->as_string(); + switch (mode) { + case 0: + if (value == cmp) return (int64_t) 1; + break; + case 1: + if (value.substr(0, cmp.length()) == cmp) return (int64_t) 1; + break; + case 2: + if (value.length() >= cmp.length() && value.substr(value.length() - cmp.length()) == cmp) return (int64_t) 1; + break; + default: + throw torrent::input_error("string comparison: internal error (unknown mode)"); + } + } + + return (int64_t) 0; +} + + 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!"); @@ -820,6 +1096,20 @@ torrent::Object cmd_system_has_methods(bool filter_public) { } +torrent::Object cmd_system_client_version_as_value() { + int64_t result = 0; + const char* pos = PACKAGE_VERSION; + + while (*pos) { + result = 100 * result + strtol(pos, (char**)&pos, 10); + if (*pos && *pos != '.') + throw torrent::input_error("INTERNAL ERROR: Bad version " PACKAGE_VERSION); + if (*pos) ++pos; + } + 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!"); @@ -1005,20 +1295,34 @@ void initialize_command_pyroscope() { */ #if RT_HEX_VERSION <= 0x000906 - // these are merged into 0.9.7+ mainline! (well, maybe, PRs are ignored) + // these are merged into 0.9.7+ mainline! CMD2_ANY_STRING("system.env", _cxxstd_::bind(&cmd_system_env, _cxxstd_::placeholders::_2)); CMD2_ANY("ui.current_view", _cxxstd_::bind(&cmd_ui_current_view)); +#endif + +#if RT_HEX_VERSION <= 0x000907 + // these are merged into 0.9.8+ mainline! (well, maybe, PRs are mostly ignored) CMD2_ANY_LIST("system.random", &apply_random); CMD2_ANY_LIST("d.multicall.filtered", _cxxstd_::bind(&d_multicall_filtered, _cxxstd_::placeholders::_2)); #endif // string.* group CMD2_ANY_LIST("string.len", &cmd_string_len); + CMD2_ANY_LIST("string.join", &cmd_string_join); + CMD2_ANY_LIST("string.split", &cmd_string_split); 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); + CMD2_ANY_LIST("string.equals", std::bind(&cmd_string_compare, 0, std::placeholders::_2)); + CMD2_ANY_LIST("string.startswith", std::bind(&cmd_string_compare, 1, std::placeholders::_2)); + CMD2_ANY_LIST("string.endswith", std::bind(&cmd_string_compare, 2, std::placeholders::_2)); + CMD2_ANY_LIST("string.strip", std::bind(&cmd_string_strip, 0, std::placeholders::_2)); + CMD2_ANY_LIST("string.lstrip", std::bind(&cmd_string_strip, -1, std::placeholders::_2)); + CMD2_ANY_LIST("string.rstrip", std::bind(&cmd_string_strip, 1, std::placeholders::_2)); + CMD2_ANY_LIST("string.lpad", std::bind(&cmd_string_pad, false, std::placeholders::_2)); + CMD2_ANY_LIST("string.rpad", std::bind(&cmd_string_pad, true, std::placeholders::_2)); // array.* group CMD2_ANY_LIST("array.at", &cmd_array_at); @@ -1047,14 +1351,23 @@ void initialize_command_pyroscope() { 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)); + CMD2_ANY("system.client_version.as_value", _cxxstd_::bind(&cmd_system_client_version_as_value)); // 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.set_if_z", _cxxstd_::bind(&cmd_d_custom_set_if_z, + _cxxstd_::placeholders::_1, _cxxstd_::placeholders::_2)); + CMD2_DL_LIST("d.custom.erase", _cxxstd_::bind(&cmd_d_custom_erase, + _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)); + CMD2_DL_STRING("d.custom.toggle", _cxxstd_::bind(&cmd_d_custom_toggle, + _cxxstd_::placeholders::_1, _cxxstd_::placeholders::_2)); + CMD2_DL_STRING("d.custom.as_value", _cxxstd_::bind(&retrieve_d_custom_as_value, + _cxxstd_::placeholders::_1, _cxxstd_::placeholders::_2)); // Misc commands CMD2_ANY_LIST("value", &cmd_value); @@ -1069,6 +1382,7 @@ void initialize_command_pyroscope() { CMD2_ANY_STRING("log.messages", _cxxstd_::bind(&cmd_log_messages, _cxxstd_::placeholders::_2)); CMD2_ANY_P("import.return", &cmd_import_return); + CMD2_ANY("do", _cxxstd_::bind(&cmd_do, _cxxstd_::placeholders::_1, _cxxstd_::placeholders::_2)); CMD2_DL("d.is_meta", _cxxstd_::bind(&torrent::DownloadInfo::is_meta_download, _cxxstd_::bind(&core::Download::info, _cxxstd_::placeholders::_1))); @@ -1078,4 +1392,5 @@ void initialize_command_pyroscope() { add_capability("colors"); // not monochrome add_capability("canvas_v2"); // new PS 1.1 canvas with fully dynamic columns add_capability("collapsed-views"); // pre-collapsed views + add_capability("fixed-log-xmlrpc-close"); } diff --git a/ps-event-view_all.patch b/ps-event-view_all.patch index 6d38109c667d..1981d08c03ea 100644 --- a/ps-event-view_all.patch +++ b/ps-event-view_all.patch @@ -1,21 +1,31 @@ ---- orig-096/src/main.cc 2015-09-03 21:03:30.000000000 +0200 -+++ rtorrent-0.9.6/src/main.cc 2017-04-09 14:47:56.000000000 +0200 -@@ -248,2 +249,5 @@ +--- a/src/main.cc ++++ b/src/main.cc +@@ -249,6 +249,9 @@ main(int argc, char** argv) { + + rpc::parse_command_multiple (rpc::make_target(), + "method.insert = event.view.hide,multi|rlookup|static\n" + "method.insert = event.view.show,multi|rlookup|static\n" + "method.insert = event.download.inserted,multi|rlookup|static\n" ---- orig-096/src/ui/element_download_list.cc 2015-09-03 21:03:30.000000000 +0200 -+++ rtorrent-0.9.6/src/ui/element_download_list.cc 2017-04-09 14:51:32.000000000 +0200 -@@ -219,5 +219,10 @@ + "method.insert = event.download.inserted_new,multi|rlookup|static\n" + "method.insert = event.download.inserted_session,multi|rlookup|static\n" +diff --git a/src/ui/element_download_list.cc b/src/ui/element_download_list.cc +index 3f34bb9..90a769a 100644 +--- a/src/ui/element_download_list.cc ++++ b/src/ui/element_download_list.cc +@@ -220,7 +220,14 @@ ElementDownloadList::receive_change_view(const std::string& name) { + return; } + std::string old_name = view() ? view()->name() : ""; -+ rpc::commands.call_catch("event.view.hide", rpc::make_target(), name, -+ "View hide event action failed: "); ++ if (!old_name.empty()) ++ rpc::commands.call_catch("event.view.hide", rpc::make_target(), name, ++ "View hide event action failed: "); set_view(*itr); -+ rpc::commands.call_catch("event.view.show", rpc::make_target(), old_name, -+ "View show event action failed: "); ++ if (!old_name.empty()) ++ rpc::commands.call_catch("event.view.show", rpc::make_target(), old_name, ++ "View show event action failed: "); } + void diff --git a/ps-fix-log-xmlrpc-close_all.patch b/ps-fix-log-xmlrpc-close_all.patch new file mode 100644 index 000000000000..1f27201b458d --- /dev/null +++ b/ps-fix-log-xmlrpc-close_all.patch @@ -0,0 +1,22 @@ +--- a/src/thread_worker.cc ++++ b/src/thread_worker.cc +@@ -113,14 +113,15 @@ ThreadWorker::change_xmlrpc_log() { + if (scgi() == NULL) + return; + +- if (scgi()->log_fd() != -1) ++ if (scgi()->log_fd() != -1) { + ::close(scgi()->log_fd()); +- +- if (m_xmlrpcLog.empty()) { ++ scgi()->set_log_fd(-1); + control->core()->push_log("Closed XMLRPC log."); +- return; + } + ++ if (m_xmlrpcLog.empty()) ++ return; ++ + scgi()->set_log_fd(open(rak::path_expand(m_xmlrpcLog).c_str(), O_WRONLY | O_APPEND | O_CREAT, 0644)); + + if (scgi()->log_fd() == -1) { diff --git a/ui_pyroscope.cc b/ui_pyroscope.cc index a535f8fee300..06db8c6725c7 100644 --- a/ui_pyroscope.cc +++ b/ui_pyroscope.cc @@ -1,6 +1,8 @@ /* ⋅ ⋅⋅ ” ’ ♯ ☢ ☍ ⌘ ✰ ⣿ ⚡ ☯ ⚑ ↺ ⤴ ⤵ ∆ ⌚ ≀∇ ✇ ⚠ ◔ ⚡ ↯ ¿ ⨂ ✖ ⇣ ⇡ ⠁ ⠉ ⠋ ⠛ ⠟ ⠿ ⡿ ⣿ ☹ ➀ ➁ ➂ ➃ ➄ ➅ ➆ ➇ ➈ ➉ ▹ ╍ ▪ ⚯ ⚒ ◌ ⇅ ↡ ↟ ⊛ ♺ +⎆ ㋛ ㋡ + ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ @@ -141,21 +143,6 @@ static std::string network_history_up_str; 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(); - size_t bytes = 0, skip; - const char* pos = text.c_str(); - - while (*pos && glyphs-- > 0 && (skip = std::mbrlen(pos, text.length() - bytes, &mbs)) > 0) { - pos += skip; - bytes += skip; - } - - return bytes < text.length() ? text.substr(0, bytes) : text; -} - - // get custom field contaioning a long (time_t) unsigned long get_custom_long(core::Download* d, const char* name) { try { @@ -416,7 +403,7 @@ int64_t cmd_d_message_alert(core::Download* d) { 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) + || d->message().find("nregistered") != 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 @@ -424,12 +411,32 @@ int64_t cmd_d_message_alert(core::Download* d) { || d->message().find("limit exceeded") != std::string::npos || d->message().find("active torrents are enough") != std::string::npos) alert = ps::ALERT_PERMS; + else if (d->message().find("tracker is down") != std::string::npos) + alert = ps::ALERT_DOWN; + else if (d->message().find("n't resolve host name") != std::string::npos) + alert = ps::ALERT_DNS; } return alert; } +std::string get_active_tracker_alias(torrent::Download* item) { + std::string url = get_active_tracker_domain(item); + if (!url.empty()) { + std::string alias = tracker_aliases[url]; + if (!alias.empty()) url = alias; + } + + return url; +} + + +torrent::Object cmd_d_tracker_alias(core::Download* download) { + return get_active_tracker_alias(download->download()); +} + + 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); @@ -451,10 +458,8 @@ static void decorate_download_title(Window* window, display::Canvas* canvas, cor // 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()); + std::string url = get_active_tracker_alias((*range.first)->download()); 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, @@ -575,6 +580,27 @@ void ui_pyroscope_download_list_redraw_item(Window* window, display::Canvas* can } +torrent::Object ui_column_spec(rpc::target_type target, const torrent::Object::list_type& args) { + if (args.size() != 1) { + throw torrent::input_error("ui.column.spec takes exactly one argument!"); + } + int64_t colidx_wanted = parse_value_arg(*args.begin()); + std::string spec; + + const torrent::Object::map_type& column_defs = control->object_storage()->get_str("ui.column.render").as_map(); + torrent::Object::map_const_iterator cols_itr, last_col = column_defs.end(); + + for (cols_itr = column_defs.begin(); cols_itr != last_col; ++cols_itr) { + char* header_pos = 0; + int64_t colidx = strtol(cols_itr->first.c_str(), &header_pos, 10); + if (header_pos[0] == ':' && colidx == colidx_wanted) + spec = cols_itr->first; + } + + return spec; +} + + torrent::Object ui_column_hide(rpc::target_type target, const torrent::Object::list_type& args) { for(torrent::Object::list_const_iterator itr = args.begin(), last = args.end(); itr != last; ++itr) { int64_t colidx = parse_value_arg(*itr); @@ -617,6 +643,24 @@ torrent::Object ui_column_hidden_list() { } +torrent::Object ui_column_sacrificial_list() { + torrent::Object result = torrent::Object::create_list(); + torrent::Object::list_type& resultList = result.as_list(); + + const torrent::Object::map_type& column_defs = control->object_storage()->get_str("ui.column.render").as_map(); + torrent::Object::map_const_iterator cols_itr, last_col = column_defs.end(); + + for (cols_itr = column_defs.begin(); cols_itr != last_col; ++cols_itr) { + char* header_pos = 0; + int64_t colidx = strtol(cols_itr->first.c_str(), &header_pos, 10); + if (header_pos[0] == ':' && header_pos[1] == '?') + resultList.push_back(colidx); + } + + return result; +} + + // Render columns from `column_defs`, return total length int render_columns(bool headers, bool narrow, rpc::target_type target, core::Download* item, display::Canvas* canvas, int column, int pos, int offset, @@ -1032,16 +1076,20 @@ 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_DL("d.tracker_alias", _cxxstd_::bind(&display::cmd_d_tracker_alias, _cxxstd_::placeholders::_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)); + CMD2_ANY_LIST("ui.column.spec", &display::ui_column_spec); CMD2_ANY_LIST("ui.column.hide", &display::ui_column_hide); CMD2_ANY_LIST("ui.column.show", &display::ui_column_show); CMD2_ANY_LIST("ui.column.is_hidden", &display::ui_column_is_hidden); CMD2_ANY("ui.column.hidden.list", _cxxstd_::bind(&display::ui_column_hidden_list)); + CMD2_ANY("ui.column.sacrificial.list", _cxxstd_::bind(&display::ui_column_sacrificial_list)); + CMD2_VAR_VALUE("ui.column.sacrificed", 0); PS_VARIABLE_COLOR("ui.color.progress0", "red"); PS_VARIABLE_COLOR("ui.color.progress20", "bold bright red"); @@ -1101,8 +1149,18 @@ void initialize_command_ui_pyroscope() { // Multi-method to store column definitions "method.insert = ui.column.render, multi|rlookup|static\n" + // Toggle sacrificial columns manually (bound to '/' key) + "method.insert = ui.column.sacrificed.toggle, simple, \"" + "branch = (ui.column.sacrificed), ((ui.column.sacrificed.set, 0)), ((ui.column.sacrificed.set, 1)) ; " + "branch = (ui.column.sacrificed)," + " \\\"ui.column.show = (ui.column.sacrificial.list)\\\"," + " \\\"ui.column.hide = (ui.column.sacrificial.list)\\\" ; " + "ui.current_view.set = (ui.current_view)\"\n" + "schedule2 = column_sacrificed_toggle, 0, 0, ((ui.bind_key,download_list,/,ui.column.sacrificed.toggle=))\n" + // Bind '*' to toggle between collapsed and expanded display - "schedule2 = collapsed_view_toggle, 0, 0, ((ui.bind_key,download_list,*,view.collapsed.toggle=))\n" + "schedule2 = collapsed_view_toggle, 0, 0, ((ui.bind_key, download_list, *, \"" + "view.collapsed.toggle= ; ui.current_view.set = (ui.current_view)\"))\n" // Collapse built-in views "view.collapsed.toggle = main\n" @@ -1155,7 +1213,8 @@ void initialize_command_ui_pyroscope() { // Status flags (❢ ☢ ☍ ⌘) "method.set_key = ui.column.render, \"100:3C95/2:❢ \"," - " ((array.at, {\" \", \"♺ \", \"⚠ \", \"◔ \", \"⚡ \", \"↯ \", \"¿?\", \"⨂ \"}, ((d.message.alert)) ))\n" + " ((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:☍ \"," @@ -1169,7 +1228,7 @@ void initialize_command_ui_pyroscope() { "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:⚡ \"," + "method.set_key = ui.column.render, \"500:?2:↕ \"," " ((string.map, ((cat, ((not, ((d.up.rate)) )), ((not, ((d.down.rate)) )) ))," " {00, \"⇅ \"}, {01, \"↟ \"}, {10, \"↡ \"}, {11, \" \"} ))\n" @@ -1179,13 +1238,13 @@ void initialize_command_ui_pyroscope() { // 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:∆⋮ ⌛ \"," + "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:∇⋮ ⌚ \"," + "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)) )) )) ))" @@ -1204,10 +1263,10 @@ void initialize_command_ui_pyroscope() { //⠀" ▁ ▂ ▃ ▄ ▅ ▆ ▇ █ " "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: ✇ \"," + "method.set_key = ui.column.render, \"930:5C15/3C21/2: ⛁ \"," " ((convert.human_size, ((d.size_bytes)) ))\n" // Explicitly managed status (✰ = prio; ⚑ = tagged) diff --git a/ui_pyroscope.h b/ui_pyroscope.h index cb831c449891..aee5ada5afde 100644 --- a/ui_pyroscope.h +++ b/ui_pyroscope.h @@ -17,6 +17,8 @@ enum AlertKind { ALERT_REQUEST, ALERT_GONE, ALERT_PERMS, + ALERT_DOWN, + ALERT_DNS, ALERT_MAX }; @@ -67,6 +69,9 @@ enum ColorKind { } // namespace -extern void add_capability(const char* name); // defined in command_pyroscope.cc +// defined in command_pyroscope.cc (exported here so we only have to patch in one .h) +extern void add_capability(const char* name); +extern size_t u8_length(const std::string& text); +extern std::string u8_chop(const std::string& text, size_t glyphs); #endif |