diff options
author | xsmile | 2017-03-09 18:46:37 +0100 |
---|---|---|
committer | xsmile | 2017-03-09 18:46:37 +0100 |
commit | a386ce87b56799cfb29b1667f56a63695fe781ed (patch) | |
tree | 70cbf797d882faad63eb3ff1561f0b79f247b34e /command_pyroscope.cc | |
download | aur-a386ce87b56799cfb29b1667f56a63695fe781ed.tar.gz |
init
Diffstat (limited to 'command_pyroscope.cc')
-rw-r--r-- | command_pyroscope.cc | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/command_pyroscope.cc b/command_pyroscope.cc new file mode 100644 index 000000000000..14fd8f96defa --- /dev/null +++ b/command_pyroscope.cc @@ -0,0 +1,502 @@ +// PyroScope - rTorrent Command Extensions +// Copyright (c) 2011 The PyroScope Project <pyroscope.project@gmail.com> +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#include "config.h" +#include "globals.h" + +#include <cstdio> +#include <climits> +#include <ctime> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <rak/path.h> +#include <rak/functional.h> +#include <rak/functional_fun.h> +#if RT_HEX_VERSION < 0x000904 + #include <sigc++/adaptors/bind.h> +#endif + +#include "core/download.h" +#include "core/manager.h" +#include "core/view_manager.h" +#include "rpc/parse.h" +#include "torrent/tracker.h" +#include "torrent/tracker_list.h" +#include "ui/root.h" +#include "ui/download_list.h" +#include "ui/element_base.h" +#include "ui/element_download_list.h" + +#include "globals.h" +#include "control.h" +#include "command_helpers.h" + +#if (RT_HEX_VERSION >= 0x000901) + #define _cxxstd_ tr1 +#else + #define _cxxstd_ std +#endif + + +// handle for message log file +namespace core { +int log_messages_fd = -1; +}; + +#define RANDOM_STATESIZE 128 +static char system_random_state[RANDOM_STATESIZE]; +static struct random_data system_random_data; + + +// return the "main" tracker for this download item +torrent::Tracker* get_active_tracker(torrent::Download* item) { + torrent::TrackerList* tl = item->tracker_list(); + torrent::Tracker* tracker = 0; + + for (int trkidx = 0; trkidx < tl->size(); trkidx++) { + tracker = tl->at(trkidx); + if (tracker->is_usable() && tracker->type() == torrent::Tracker::TRACKER_HTTP + && tracker->scrape_complete() + tracker->scrape_incomplete() > 0) { + break; + } + tracker = 0; + } + if (!tracker && tl->size()) tracker = tl->at(0); + + return tracker; +} + + +// return the domain name of the "main" tracker of the given download item +std::string get_active_tracker_domain(torrent::Download* item) { + std::string url; + torrent::Tracker* tracker = get_active_tracker(item); + + if (tracker && !tracker->url().empty()) { + url = tracker->url(); + + // snip url to domain name + if (url.compare(0, 7, "http://") == 0) url = url.substr(7); + if (url.compare(0, 8, "https://") == 0) url = url.substr(8); + if (url.find('/') > 0) url = url.substr(0, url.find('/')); + if (url.find(':') > 0) url = url.substr(0, url.find(':')); + + // remove some common cruft + const char* domain_cruft[] = { + "tracker", "1.", "2.", "001.", ".", + "www.", "cfdata.", + 0 + }; + for (const char** cruft = domain_cruft; *cruft; cruft++) { + int cruft_len = strlen(*cruft); + if (url.compare(0, cruft_len, *cruft) == 0) url = url.substr(cruft_len); + } + } + + return url; +} + + +/* @DOC + `compare = <order>, <sort_key>=[, ...]` + + Compares two items like `less=` or `greater=`, but allows to compare + by several different sort criteria, and ascending or descending + order per given field. The first parameter is a string of order + indicators, either `aA+` for ascending or `dD-` for descending. + The default, i.e. when there's more fields than indicators, is + ascending. Field types other than value or string are treated + as equal (or in other words, they're ignored). + + If all fields are equal, then items are ordered in a random, but + stable fashion. + + Configuration example: + + # VIEW: Show active and incomplete torrents (in view #9) and update every 20 seconds + # Items are grouped into complete, incomplete, and queued, in that order. + # Within each group, they're sorted by upload and then download speed. + view_sort_current = active,"compare=----,d.is_open=,d.complete=,d.up.rate=,d.down.rate=" + schedule = filter_active,12,20,"view.filter = active,\"or={d.up.rate=,d.down.rate=,not=$d.complete=}\" ;view.sort=active" +*/ +torrent::Object apply_compare(rpc::target_type target, const torrent::Object::list_type& args) { + if (!rpc::is_target_pair(target)) + throw torrent::input_error("Can only compare a target pair."); + + if (args.size() < 2) + throw torrent::input_error("Need at least order and one field."); + + torrent::Object::list_const_iterator itr = args.begin(); + std::string order = (itr++)->as_string(); + const char* current = order.c_str(); + + torrent::Object result1; + torrent::Object result2; + + for (torrent::Object::list_const_iterator last = args.end(); itr != last; itr++) { + std::string field = itr->as_string(); + result1 = rpc::parse_command_single(rpc::get_target_left(target), field); + result2 = rpc::parse_command_single(rpc::get_target_right(target), field); + + if (result1.type() != result2.type()) + throw torrent::input_error(std::string("Type mismatch in compare of ") + field); + + bool descending = *current == 'd' || *current == 'D' || *current == '-'; + if (*current) { + if (!descending && !(*current == 'a' || *current == 'A' || *current == '+')) + throw torrent::input_error(std::string("Bad order '") + *current + "' in " + order); + ++current; + } + + switch (result1.type()) { + case torrent::Object::TYPE_VALUE: + if (result1.as_value() != result2.as_value()) + return (int64_t) (descending ^ (result1.as_value() < result2.as_value())); + break; + + case torrent::Object::TYPE_STRING: + if (result1.as_string() != result2.as_string()) + return (int64_t) (descending ^ (result1.as_string() < result2.as_string())); + break; + + default: + break; // treat unknown types as equal + } + } + + // if all else is equal, ensure stable sort order based on memory location + return (int64_t) (target.second < target.third); +} + + +/* @DOC + `system.random = [[<lower>,] <upper>]` + + Generate *uniformly* distributed random numbers in the range + defined by `lower`..`upper`. + + The default range is `0`..`RAND_MAX`, providing just one + argument sets the upper bound. The range is inclusive. + + An example use-case is adding jitter to time values that you + later check with `elapsed.greater`, to avoid load spikes and + similar effects of clustered time triggers. +*/ +torrent::Object apply_random(rpc::target_type target, const torrent::Object::list_type& args) { + int64_t lo = 0, hi = RAND_MAX; + + torrent::Object::list_const_iterator itr = args.begin(); + if (args.size() > 0) { + hi = (itr++)->as_value(); + } + if (args.size() > 1) { + lo = hi; + hi = (itr++)->as_value(); + } + if (args.size() > 2) { + throw torrent::input_error("system.random accepts at most two arguments!"); + } + if (lo > hi) { + throw torrent::input_error("Empty interval passed to system.random (low > high)!"); + } + if (lo < 0 || RAND_MAX < lo) { + throw torrent::input_error("Lower bound of system.random range outside 0..RAND_MAX!"); + } + if (hi < 0 || RAND_MAX < hi) { + throw torrent::input_error("Upper bound of system.random range outside 0..RAND_MAX!"); + } + + int32_t rval; + const int64_t range = 1 + hi - lo; + const int64_t buckets = RAND_MAX / range; + const int64_t limit = buckets * range; + + /* Create equal size buckets all in a row, then fire randomly towards + * the buckets until you land in one of them. All buckets are equally + * likely. If you land off the end of the line of buckets, try again. */ + do { + if (random_r(&system_random_data, &rval) == -1) { + throw torrent::input_error("system.random: random_r() failure!"); + } + } while (rval >= limit); + + return (int64_t) lo + (rval / buckets); +} + + +static std::map<int, std::string> bound_commands[ui::DownloadList::DISPLAY_MAX_SIZE]; + +/* @DOC + ui.bind_key=display,key,"command1=[,...]" + + Binds the given key on a specified display to execute the commands when pressed. + + "display" must be one of "download_list", ... + "key" can be either a single character for normal keys, + ^ plus a character for control keys, or a 4 digit octal key code. + + Configuration example: + # VIEW: Bind view #7 to the "rtcontrol" result + schedule = bind_7,1,0,"ui.bind_key=download_list,7,ui.current_view.set=rtcontrol" +*/ +torrent::Object apply_ui_bind_key(rpc::target_type target, const torrent::Object& rawArgs) { + const torrent::Object::list_type& args = rawArgs.as_list(); + + if (args.size() != 3) + throw torrent::input_error("Expecting display, key, and commands."); + + // Parse positional arguments + torrent::Object::list_const_iterator itr = args.begin(); + const std::string& element = (itr++)->as_string(); + const std::string& keydef = (itr++)->as_string(); + const std::string& commands = (itr++)->as_string(); + + // Get key index from definition + if (keydef.empty() || keydef.size() > (keydef[0] == '0' ? 4 : keydef[0] == '^' ? 2 : 1)) + throw torrent::input_error("Bad key definition."); + int key = keydef[0]; + if (key == '^' && keydef.size() > 1) key = keydef[1] & 31; + if (key == '0' && keydef.size() != 1) { + if (keydef.size() != 4) + throw torrent::input_error("Bad key definition (expected 4 digit octal code)."); + key = (int) strtol(keydef.c_str(), (char **) NULL, 8); + } + + // Look up display + ui::DownloadList::Display displayType = ui::DownloadList::DISPLAY_MAX_SIZE; + if (element == "download_list") { + displayType = ui::DownloadList::DISPLAY_DOWNLOAD_LIST; + } else { + throw torrent::input_error(std::string("Unknown display ") + element); + } + ui::DownloadList* dl_list = control->ui()->download_list(); + if (!dl_list) + throw torrent::input_error("No download list."); + ui::ElementBase* display = dl_list->display(displayType); + if (!display) + throw torrent::input_error("Display not found."); + + // Bind the key to the given commands + bool new_binding = display->bindings().find(key) == display->bindings().end(); + bound_commands[displayType][key] = commands; // keep hold of the string, so the c_str() below remains valid + switch (displayType) { + case ui::DownloadList::DISPLAY_DOWNLOAD_LIST: + display->bindings()[key] = +#if RT_HEX_VERSION < 0x000904 + sigc::bind(sigc::mem_fun(*(ui::ElementDownloadList*)display, &ui::ElementDownloadList::receive_command), +#else + _cxxstd_::bind(&ui::ElementDownloadList::receive_command, (ui::ElementDownloadList*)display, +#endif + bound_commands[displayType][key].c_str()); + break; + default: + return torrent::Object(); + } + + if (!new_binding) { + std::string msg = "Replaced key binding"; + msg += " for " + keydef + " in " + element + " with " + commands.substr(0, 30); + if (commands.size() > 30) msg += "..."; + control->core()->push_log(msg.c_str()); + } + + return torrent::Object(); +} + + +torrent::Object cmd_ui_focus_home() { + ui::DownloadList* dl_list = control->ui()->download_list(); + core::View* dl_view = dl_list->current_view(); + + if (!dl_view->empty_visible()) { + dl_view->set_focus(dl_view->begin_visible()); + dl_view->set_last_changed(); + } + + return torrent::Object(); +} + + +torrent::Object cmd_ui_focus_end() { + ui::DownloadList* dl_list = control->ui()->download_list(); + core::View* dl_view = dl_list->current_view(); + + if (!dl_view->empty_visible()) { + dl_view->set_focus(dl_view->end_visible() - 1); + dl_view->set_last_changed(); + } + + return torrent::Object(); +} + + +static int ui_page_size() { + // TODO: map 0 to the current view size, for adaptive scrolling + return std::max(1, (int) rpc::call_command_value("ui.focus.page_size")); +} + + +torrent::Object cmd_ui_focus_pgup() { + ui::DownloadList* dl_list = control->ui()->download_list(); + core::View* dl_view = dl_list->current_view(); + + int skip = ui_page_size(); + if (!dl_view->empty_visible()) { + if (dl_view->focus() == dl_view->end_visible()) + dl_view->set_focus(dl_view->end_visible() - 1); + else if (dl_view->focus() - dl_view->begin_visible() >= skip) + dl_view->set_focus(dl_view->focus() - skip); + else + dl_view->set_focus(dl_view->begin_visible()); + dl_view->set_last_changed(); + } + + return torrent::Object(); +} + + +torrent::Object cmd_ui_focus_pgdn() { + ui::DownloadList* dl_list = control->ui()->download_list(); + core::View* dl_view = dl_list->current_view(); + + int skip = ui_page_size(); + if (!dl_view->empty_visible()) { + if (dl_view->focus() == dl_view->end_visible()) + dl_view->set_focus(dl_view->begin_visible()); + else if (dl_view->end_visible() - dl_view->focus() > skip) + dl_view->set_focus(dl_view->focus() + skip); + else + dl_view->set_focus(dl_view->end_visible() - 1); + dl_view->set_last_changed(); + } + + return torrent::Object(); +} + + +torrent::Object cmd_log_messages(const torrent::Object::string_type& arg) { + if (arg.empty()) { + control->core()->push_log_std("Closing message log file."); + } + + if (core::log_messages_fd >= 0) { + ::close(core::log_messages_fd); + core::log_messages_fd = -1; + } + + if (!arg.empty()) { + core::log_messages_fd = open(rak::path_expand(arg).c_str(), O_WRONLY | O_APPEND | O_CREAT, 0644); + + if (core::log_messages_fd < 0) { + throw torrent::input_error("Could not open message log file."); + } + + control->core()->push_log_std("Opened message log file '" + rak::path_expand(arg) + "'."); + } + + return torrent::Object(); +} + + +// Backports from 0.9.2 +#if (API_VERSION < 3) +template <typename InputIterator, typename OutputIterator> OutputIterator +pyro_transform_hex(InputIterator first, InputIterator last, OutputIterator dest) { + const char* hex = "0123456789abcdef"; + while (first != last) { + *(dest++) = (*first >> 4)[hex]; + *(dest++) = (*first & 15)[hex]; + + ++first; + } + + return dest; +} + + +torrent::Object d_chunks_seen(core::Download* download) { + const uint8_t* seen = download->download()->chunks_seen(); + + if (seen == NULL) + return std::string(); + + uint32_t size = download->download()->file_list()->size_chunks(); + std::string result; + result.resize(size * 2); + pyro_transform_hex((const char*)seen, (const char*)seen + size, result.begin()); + + return result; +} +#endif + + +torrent::Object cmd_d_tracker_domain(core::Download* download) { + return get_active_tracker_domain(download->download()); +} + + +#if RT_HEX_VERSION <= 0x000906 +// https://github.com/rakshasa/rtorrent/commit/1f5e4d37d5229b63963bb66e76c07ec3e359ecba +torrent::Object cmd_system_env(const torrent::Object::string_type& arg) { + if (arg.empty()) { + throw torrent::input_error("system.env: Missing variable name."); + } + + char* val = getenv(arg.c_str()); + return std::string(val ? val : ""); +} + +// https://github.com/rakshasa/rtorrent/commit/30d8379391ad4cb3097d57aa56a488d061e68662 +torrent::Object cmd_ui_current_view() { + return control->ui()->download_list()->current_view()->name(); +} +#endif + + +void initialize_command_pyroscope() { + unsigned int seed = cachedTime.usec() ^ (getpid() << 16) ^ getppid(); + initstate_r(seed, system_random_state, RANDOM_STATESIZE, &system_random_data); + +// Backports from 0.9.2 +#if (API_VERSION < 3) + // https://github.com/rakshasa/rtorrent/commit/b28f2ea8070 + // https://github.com/rakshasa/rtorrent/commit/020de10f38210a07a567aeebbe385a4faaf4b517 + CMD2_DL("d.chunks_seen", _cxxstd_::bind(&d_chunks_seen, _cxxstd_::placeholders::_1)); + + // https://github.com/rakshasa/rtorrent/commit/5bed4f01ad + CMD2_TRACKER("t.is_usable", _cxxstd_::bind(&torrent::Tracker::is_usable, _cxxstd_::placeholders::_1)); + CMD2_TRACKER("t.is_busy", _cxxstd_::bind(&torrent::Tracker::is_busy, _cxxstd_::placeholders::_1)); +#endif + +#if RT_HEX_VERSION <= 0x000906 + // these are merged into 0.9.7+ mainline! + CMD2_ANY_STRING("system.env", _cxxstd_::bind(&cmd_system_env, _cxxstd_::placeholders::_2)); + CMD2_ANY("ui.current_view", _cxxstd_::bind(&cmd_ui_current_view)); +#endif + + CMD2_ANY_LIST("compare", &apply_compare); + CMD2_ANY_LIST("system.random", &apply_random); + CMD2_ANY("ui.bind_key", &apply_ui_bind_key); + CMD2_DL("d.tracker_domain", _cxxstd_::bind(&cmd_d_tracker_domain, _cxxstd_::placeholders::_1)); + CMD2_ANY_STRING("log.messages", _cxxstd_::bind(&cmd_log_messages, _cxxstd_::placeholders::_2)); + CMD2_ANY("ui.focus.home", _cxxstd_::bind(&cmd_ui_focus_home)); + CMD2_ANY("ui.focus.end", _cxxstd_::bind(&cmd_ui_focus_end)); + CMD2_ANY("ui.focus.pgup", _cxxstd_::bind(&cmd_ui_focus_pgup)); + CMD2_ANY("ui.focus.pgdn", _cxxstd_::bind(&cmd_ui_focus_pgdn)); + CMD2_VAR_VALUE("ui.focus.page_size", 50); +} |