From e15270271de2e35b3d41133b934e9c2b93ad74ba Mon Sep 17 00:00:00 2001 From: Toff Date: Mon, 31 Oct 2016 12:19:41 +0100 Subject: [PATCH 1/4] Add a temporary name filter for the main view. By using 'F' in the main view you can enter a regex that will be used to filter the view based on the torrent name. To remove the filter set the input to empty. This is adding a 'view.temp_filter' property and a 'match{}' function. For example with Ctrl+X you could do: command> view.temp_filter=main,"match={d.name=,.*linux.*iso}" --- doc/old/rtorrent.1 | 3 +++ doc/old/rtorrent.1.xml | 7 ++++++ src/command_ui.cc | 51 ++++++++++++++++++++++++++++++++++++++++ src/core/view.cc | 23 +++++++++++------- src/core/view.h | 3 +++ src/core/view_manager.cc | 8 +++++++ src/core/view_manager.h | 1 + src/ui/download_list.cc | 24 +++++++++++++++++++ src/ui/download_list.h | 3 ++- 9 files changed, 113 insertions(+), 10 deletions(-) diff --git a/doc/old/rtorrent.1 b/doc/old/rtorrent.1 index fa5477f2..f1ef638a 100644 --- a/doc/old/rtorrent.1 +++ b/doc/old/rtorrent.1 @@ -99,6 +99,9 @@ Delete the file the torrent is tied to, and clear the association. .TP \fBI\fR Toggle whether torrent ignores ratio settings. +.TP +\fBF\fR +Add a temporary name based regex filter to the current view. .SS "DOWNLOAD VIEW KEYS" .TP \fB->\fR diff --git a/doc/old/rtorrent.1.xml b/doc/old/rtorrent.1.xml index e8093896..6968a94b 100644 --- a/doc/old/rtorrent.1.xml +++ b/doc/old/rtorrent.1.xml @@ -225,6 +225,13 @@ Toggle whether torrent ignores ratio settings. + + F + +Add a temporary name based regex filter to the current view. + + + diff --git a/src/command_ui.cc b/src/command_ui.cc index 69ee2f54..c6f7d311 100644 --- a/src/command_ui.cc +++ b/src/command_ui.cc @@ -39,6 +39,8 @@ #include #include +#include + #include #include @@ -282,6 +284,53 @@ torrent::Object apply_equal(rpc::target_type target, const torrent::Object::list return result.is_value() ? result.as_value() == 0 : (int64_t)false; } +// Regexp based 'match' function. +// arg1: the text to match. +// arg2: the regexp pattern. +// eg: match{d.name=,.*linux.*iso} +torrent::Object apply_match(rpc::target_type target, const torrent::Object::list_type& args) { + if (args.size() != 2) + throw torrent::input_error("Wrong argument count for 'match': 2 arguments needed."); + + // This really should be converted to using args flagged as + // commands, so that we can compare commands and statics values. + + torrent::Object result1; + torrent::Object result2; + + rpc::target_type target1 = rpc::is_target_pair(target) ? rpc::get_target_left(target) : target; + rpc::target_type target2 = rpc::is_target_pair(target) ? rpc::get_target_right(target) : target; + + if (args.front().is_dict_key()) + result1 = rpc::commands.call_command(args.front().as_dict_key().c_str(), args.front().as_dict_obj(), target1); + else + result1 = rpc::parse_command_single(target1, args.front().as_string()); + + if (args.back().is_dict_key()) + result2 = rpc::commands.call_command(args.back().as_dict_key().c_str(), args.back().as_dict_obj(), target2); + else + result2 = args.back().as_string(); + + if (result1.type() != result2.type()) + throw torrent::input_error("Type mismatch for 'match' arguments."); + + std::string text = result1.as_string(); + std::string pattern = result2.as_string(); + + std::transform(text.begin(), text.end(), text.begin(), ::tolower); + std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower); + + bool match = false; + try { + std::regex re(pattern); + match = std::regex_match(text, re); + } catch (const std::regex_error& exc) { + control->core()->push_log_std("regex_error: " + std::string(exc.what())); + } + + return match ? (int64_t)true : (int64_t)false; +} + torrent::Object apply_to_time(const torrent::Object& rawArgs, int flags) { std::tm *u; @@ -529,6 +578,7 @@ initialize_command_ui() { CMD2_ANY_LIST("view.set", std::bind(&apply_view_set, std::placeholders::_2)); CMD2_ANY_LIST("view.filter", std::bind(&apply_view_event, &core::ViewManager::set_filter, std::placeholders::_2)); + CMD2_ANY_LIST("view.temp_filter", std::bind(&apply_view_event, &core::ViewManager::set_temp_filter, std::placeholders::_2)); CMD2_ANY_LIST("view.filter_on", std::bind(&apply_view_filter_on, std::placeholders::_2)); CMD2_ANY_LIST("view.sort", std::bind(&apply_view_sort, std::placeholders::_2)); @@ -574,6 +624,7 @@ initialize_command_ui() { CMD2_ANY_LIST("less", &apply_less); CMD2_ANY_LIST("greater", &apply_greater); CMD2_ANY_LIST("equal", &apply_equal); + CMD2_ANY_LIST("match", &apply_match); CMD2_ANY_VALUE("convert.gm_time", std::bind(&apply_to_time, std::placeholders::_2, 0)); CMD2_ANY_VALUE("convert.gm_date", std::bind(&apply_to_time, std::placeholders::_2, 0x2)); diff --git a/src/core/view.cc b/src/core/view.cc index 842fdbbf..8859f452 100644 --- a/src/core/view.cc +++ b/src/core/view.cc @@ -89,17 +89,21 @@ struct view_downloads_compare : std::binary_function }; struct view_downloads_filter : std::unary_function { - view_downloads_filter(const torrent::Object& cmd) : m_command(cmd) {} + view_downloads_filter(const torrent::Object& cmd, const torrent::Object& cmd2) : m_command(cmd), m_command2(cmd2) {} bool operator () (Download* d1) const { - if (m_command.is_empty()) + return this->evalCmd(m_command, d1) && this->evalCmd(m_command2, d1); + } + + bool evalCmd(const torrent::Object& cmd, Download* d1) const { + if (cmd.is_empty()) return true; try { torrent::Object result; - if (m_command.is_dict_key()) { - // torrent::Object tmp_command = m_command; + if (cmd.is_dict_key()) { + // torrent::Object tmp_command = cmd; // uint32_t flags = tmp_command.flags() & torrent::Object::mask_function; // tmp_command.unset_flags(torrent::Object::mask_function); @@ -109,10 +113,10 @@ struct view_downloads_filter : std::unary_function { // result = rpc::commands.call_command(tmp_command.as_dict_key().c_str(), tmp_command.as_dict_obj(), // rpc::make_target(d1)); - result = rpc::commands.call_command(m_command.as_dict_key().c_str(), m_command.as_dict_obj(), rpc::make_target(d1)); + result = rpc::commands.call_command(cmd.as_dict_key().c_str(), cmd.as_dict_obj(), rpc::make_target(d1)); } else { - result = rpc::parse_command_single(rpc::make_target(d1), m_command.as_string()); + result = rpc::parse_command_single(rpc::make_target(d1), cmd.as_string()); } switch (result.type()) { @@ -136,6 +140,7 @@ struct view_downloads_filter : std::unary_function { } const torrent::Object& m_command; + const torrent::Object& m_command2; }; void @@ -262,8 +267,8 @@ View::filter() { return; // Parition the list in two steps so we know which elements changed. - iterator splitVisible = std::stable_partition(begin_visible(), end_visible(), view_downloads_filter(m_filter)); - iterator splitFiltered = std::stable_partition(begin_filtered(), end_filtered(), view_downloads_filter(m_filter)); + iterator splitVisible = std::stable_partition(begin_visible(), end_visible(), view_downloads_filter(m_filter, m_tempFilter)); + iterator splitFiltered = std::stable_partition(begin_filtered(), end_filtered(), view_downloads_filter(m_filter, m_tempFilter)); base_type changed(splitVisible, splitFiltered); iterator splitChanged = changed.begin() + std::distance(splitVisible, end_visible()); @@ -302,7 +307,7 @@ View::filter_download(core::Download* download) { if (itr == base_type::end()) throw torrent::internal_error("View::filter_download(...) could not find download."); - if (view_downloads_filter(m_filter)(download)) { + if (view_downloads_filter(m_filter, m_tempFilter)(download)) { if (itr >= end_visible()) { erase_internal(itr); diff --git a/src/core/view.h b/src/core/view.h index 1196de3e..4f93ab02 100644 --- a/src/core/view.h +++ b/src/core/view.h @@ -124,6 +124,8 @@ public: const torrent::Object& get_filter() const { return m_filter; } void set_filter(const torrent::Object& s) { m_filter = s; } + const torrent::Object& get_temp_filter() const { return m_tempFilter; } + void set_temp_filter(const torrent::Object& s) { m_tempFilter = s; } void set_filter_on_event(const std::string& event); void clear_filter_on(); @@ -172,6 +174,7 @@ private: torrent::Object m_sortCurrent; torrent::Object m_filter; + torrent::Object m_tempFilter; // Temporary view filter (eg: name based filter) torrent::Object m_event_added; torrent::Object m_event_removed; diff --git a/src/core/view_manager.cc b/src/core/view_manager.cc index 355d8809..49a82fd0 100644 --- a/src/core/view_manager.cc +++ b/src/core/view_manager.cc @@ -109,6 +109,14 @@ ViewManager::set_filter(const std::string& name, const torrent::Object& cmd) { (*viewItr)->filter(); } +void +ViewManager::set_temp_filter(const std::string& name, const torrent::Object& cmd) { + iterator viewItr = find_throw(name); + + (*viewItr)->set_temp_filter(cmd); + (*viewItr)->filter(); +} + void ViewManager::set_filter_on(const std::string& name, const filter_args& args) { iterator viewItr = find_throw(name); diff --git a/src/core/view_manager.h b/src/core/view_manager.h index 750d210b..8b44b266 100644 --- a/src/core/view_manager.h +++ b/src/core/view_manager.h @@ -93,6 +93,7 @@ public: void set_sort_current(const std::string& name, const torrent::Object& cmd) { (*find_throw(name))->set_sort_current(cmd); } void set_filter(const std::string& name, const torrent::Object& cmd); + void set_temp_filter(const std::string& name, const torrent::Object& cmd); void set_filter_on(const std::string& name, const filter_args& args); void set_event_added(const std::string& name, const torrent::Object& cmd) { (*find_throw(name))->set_event_added(cmd); } diff --git a/src/ui/download_list.cc b/src/ui/download_list.cc index 7a070f62..a8dd3210 100644 --- a/src/ui/download_list.cc +++ b/src/ui/download_list.cc @@ -262,6 +262,10 @@ DownloadList::receive_view_input(Input type) { title = "command"; break; + case INPUT_FILTER: + title = "filter"; + break; + default: throw torrent::internal_error("DownloadList::receive_view_input(...) Invalid input type."); } @@ -325,6 +329,25 @@ DownloadList::receive_exit_input(Input type) { input->str()); break; + case INPUT_FILTER: + if (input->str().empty()) { + control->core()->push_log_std("Clear temporary filter."); + current_view()->set_temp_filter(torrent::Object()); + current_view()->filter(); + } else { + std::string pattern = input->str(); + if (pattern.back() != '$') + pattern = pattern + ".*"; + if (pattern.front() != '^') + pattern = ".*" + pattern; + std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower); + std::string tempFilter = "match={d.name=," + pattern + "}"; + control->core()->push_log_std("Temporary filter: " + pattern); + current_view()->set_temp_filter(tempFilter); + current_view()->filter(); + } + break; + default: throw torrent::internal_error("DownloadList::receive_exit_input(...) Invalid input type."); } @@ -346,6 +369,7 @@ DownloadList::setup_keys() { m_bindings[KEY_ENTER] = std::bind(&DownloadList::receive_view_input, this, INPUT_LOAD_MODIFIED); m_bindings['\x0F'] = std::bind(&DownloadList::receive_view_input, this, INPUT_CHANGE_DIRECTORY); m_bindings['X' - '@'] = std::bind(&DownloadList::receive_view_input, this, INPUT_COMMAND); + m_bindings['F'] = std::bind(&DownloadList::receive_view_input, this, INPUT_FILTER); m_uiArray[DISPLAY_LOG]->bindings()[KEY_LEFT] = m_uiArray[DISPLAY_LOG]->bindings()['B' - '@'] = diff --git a/src/ui/download_list.h b/src/ui/download_list.h index 72ab5ae5..f766e63b 100644 --- a/src/ui/download_list.h +++ b/src/ui/download_list.h @@ -86,7 +86,8 @@ public: INPUT_LOAD_DEFAULT, INPUT_LOAD_MODIFIED, INPUT_CHANGE_DIRECTORY, - INPUT_COMMAND + INPUT_COMMAND, + INPUT_FILTER } Input; DownloadList(); From 6ee61ee6b50f3f12a6901d450ab2a6b689dd81dc Mon Sep 17 00:00:00 2001 From: Toff Date: Sun, 8 Jan 2017 19:45:46 +0100 Subject: [PATCH 2/4] Merge chros73 suggestions (filter indicator, "start/stopped" view case) - Suffix filtered view name with "(filtered)" - Add a check to not filter "start" and "stopped" view --- src/command_ui.cc | 6 +++--- src/display/window_download_list.cc | 2 +- src/ui/download_list.cc | 10 ++++++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/command_ui.cc b/src/command_ui.cc index c6f7d311..58abf54e 100644 --- a/src/command_ui.cc +++ b/src/command_ui.cc @@ -320,15 +320,15 @@ torrent::Object apply_match(rpc::target_type target, const torrent::Object::list std::transform(text.begin(), text.end(), text.begin(), ::tolower); std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower); - bool match = false; + bool isAMatch = false; try { std::regex re(pattern); - match = std::regex_match(text, re); + isAMatch = std::regex_match(text, re); } catch (const std::regex_error& exc) { control->core()->push_log_std("regex_error: " + std::string(exc.what())); } - return match ? (int64_t)true : (int64_t)false; + return isAMatch ? (int64_t)true : (int64_t)false; } torrent::Object diff --git a/src/display/window_download_list.cc b/src/display/window_download_list.cc index 74911555..b4a94324 100644 --- a/src/display/window_download_list.cc +++ b/src/display/window_download_list.cc @@ -81,7 +81,7 @@ WindowDownloadList::redraw() { if (m_view == NULL) return; - m_canvas->print(0, 0, "%s", ("[View: " + m_view->name() + "]").c_str()); + m_canvas->print(0, 0, "%s", ("[View: " + m_view->name() + (m_view->get_temp_filter().is_empty() ? "" : " (filtered)") + "]").c_str()); if (m_view->empty_visible() || m_canvas->width() < 5 || m_canvas->height() < 2) return; diff --git a/src/ui/download_list.cc b/src/ui/download_list.cc index a8dd3210..c88079af 100644 --- a/src/ui/download_list.cc +++ b/src/ui/download_list.cc @@ -263,6 +263,11 @@ DownloadList::receive_view_input(Input type) { break; case INPUT_FILTER: + // STARTED and STOPPED views are not allowed to being filtered: they are special + if (current_view()->name() == "started" || current_view()->name() == "stopped") { + control->core()->push_log_std("View '" + current_view()->name() + "' can't be filtered."); + return; + } title = "filter"; break; @@ -331,9 +336,10 @@ DownloadList::receive_exit_input(Input type) { case INPUT_FILTER: if (input->str().empty()) { - control->core()->push_log_std("Clear temporary filter."); + control->core()->push_log_std("Clear temporary filter on '" + current_view()->name() + "' view."); current_view()->set_temp_filter(torrent::Object()); current_view()->filter(); + current_view()->sort(); } else { std::string pattern = input->str(); if (pattern.back() != '$') @@ -342,7 +348,7 @@ DownloadList::receive_exit_input(Input type) { pattern = ".*" + pattern; std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower); std::string tempFilter = "match={d.name=," + pattern + "}"; - control->core()->push_log_std("Temporary filter: " + pattern); + control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern); current_view()->set_temp_filter(tempFilter); current_view()->filter(); } From d5de8a91d16e0945e471937953b903ac76cb020f Mon Sep 17 00:00:00 2001 From: Toff Date: Mon, 17 Apr 2017 14:56:11 +0200 Subject: [PATCH 3/4] Merge chros73 suggestions (settings to exclude views and hide info logs) - Certain views can be excluded from subfiltering via view.temp_filter.excluded config option - its default value: "default,started,stopped" - example usage in config: view.temp_filter.excluded.set="default,started,stopped,leeching" - Log messages can be displayed when temp filtering occured - disabled by default - can be enabled with: ui.console.log.tempfilter.set=1 --- src/command_ui.cc | 3 +++ src/ui/download_list.cc | 28 +++++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/command_ui.cc b/src/command_ui.cc index 58abf54e..eba8f137 100644 --- a/src/command_ui.cc +++ b/src/command_ui.cc @@ -605,6 +605,9 @@ initialize_command_ui() { CMD2_ANY ("ui.current_view", std::bind(&cmd_ui_current_view)); CMD2_ANY_STRING("ui.current_view.set", std::bind(&cmd_ui_set_view, std::placeholders::_2)); + CMD2_VAR_STRING("view.temp_filter.excluded", "default,started,stopped"); + CMD2_VAR_BOOL ("ui.console.log.tempfilter", 0); + // TODO: Add 'option_string' for rtorrent-specific options. CMD2_VAR_STRING("ui.torrent_list.layout", "full"); diff --git a/src/ui/download_list.cc b/src/ui/download_list.cc index c88079af..9be1da1e 100644 --- a/src/ui/download_list.cc +++ b/src/ui/download_list.cc @@ -36,6 +36,8 @@ #include "config.h" +#include + #include #include #include @@ -263,12 +265,22 @@ DownloadList::receive_view_input(Input type) { break; case INPUT_FILTER: - // STARTED and STOPPED views are not allowed to being filtered: they are special - if (current_view()->name() == "started" || current_view()->name() == "stopped") { - control->core()->push_log_std("View '" + current_view()->name() + "' can't be filtered."); - return; + { + // Do not allow to subfilter the defined excluded views + const std::string excluded_views = rpc::call_command_string("view.temp_filter.excluded"); + std::stringstream ss(excluded_views); + std::string view_name_var; + + while(ss.good()) { + std::getline(ss, view_name_var, ','); + if (current_view()->name() == rak::trim(view_name_var)) { + control->core()->push_log_std("View '" + current_view()->name() + "' can't be filtered."); + return; + } + } + + title = "filter"; } - title = "filter"; break; default: @@ -336,7 +348,8 @@ DownloadList::receive_exit_input(Input type) { case INPUT_FILTER: if (input->str().empty()) { - control->core()->push_log_std("Clear temporary filter on '" + current_view()->name() + "' view."); + if (rpc::call_command_value("ui.console.log.tempfilter")) + control->core()->push_log_std("Clear temporary filter on '" + current_view()->name() + "' view."); current_view()->set_temp_filter(torrent::Object()); current_view()->filter(); current_view()->sort(); @@ -348,7 +361,8 @@ DownloadList::receive_exit_input(Input type) { pattern = ".*" + pattern; std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower); std::string tempFilter = "match={d.name=," + pattern + "}"; - control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern); + if (rpc::call_command_value("ui.console.log.tempfilter")) + control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern); current_view()->set_temp_filter(tempFilter); current_view()->filter(); } From 7ec9b6685bad9923386637e66a41963e82165911 Mon Sep 17 00:00:00 2001 From: Toff Date: Sat, 6 May 2017 11:17:25 +0200 Subject: [PATCH 4/4] Merge pyroscope and chros73 suggestion: Command name uniformisation. ui.console.log.tempfilter => view.temp_filter.log --- src/command_ui.cc | 2 +- src/ui/download_list.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/command_ui.cc b/src/command_ui.cc index eba8f137..d65859e0 100644 --- a/src/command_ui.cc +++ b/src/command_ui.cc @@ -606,7 +606,7 @@ initialize_command_ui() { CMD2_ANY_STRING("ui.current_view.set", std::bind(&cmd_ui_set_view, std::placeholders::_2)); CMD2_VAR_STRING("view.temp_filter.excluded", "default,started,stopped"); - CMD2_VAR_BOOL ("ui.console.log.tempfilter", 0); + CMD2_VAR_BOOL ("view.temp_filter.log", 0); // TODO: Add 'option_string' for rtorrent-specific options. CMD2_VAR_STRING("ui.torrent_list.layout", "full"); diff --git a/src/ui/download_list.cc b/src/ui/download_list.cc index 9be1da1e..7f388e5e 100644 --- a/src/ui/download_list.cc +++ b/src/ui/download_list.cc @@ -348,7 +348,7 @@ DownloadList::receive_exit_input(Input type) { case INPUT_FILTER: if (input->str().empty()) { - if (rpc::call_command_value("ui.console.log.tempfilter")) + if (rpc::call_command_value("view.temp_filter.log")) control->core()->push_log_std("Clear temporary filter on '" + current_view()->name() + "' view."); current_view()->set_temp_filter(torrent::Object()); current_view()->filter(); @@ -361,7 +361,7 @@ DownloadList::receive_exit_input(Input type) { pattern = ".*" + pattern; std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower); std::string tempFilter = "match={d.name=," + pattern + "}"; - if (rpc::call_command_value("ui.console.log.tempfilter")) + if (rpc::call_command_value("view.temp_filter.log")) control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern); current_view()->set_temp_filter(tempFilter); current_view()->filter();