diff --git a/doc/old/rtorrent.1 b/doc/old/rtorrent.1 index 047d9ffc..5cdf2bdd 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 72e8850c..68f5ae63 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 12f80b52..38ebfd79 100644 --- a/src/command_ui.cc +++ b/src/command_ui.cc @@ -39,6 +39,8 @@ #include #include +#include + #include #include #include @@ -369,6 +371,53 @@ apply_compare(rpc::target_type target, const torrent::Object::list_type& args) { return (int64_t) (target.second < target.third); } +// 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 isAMatch = false; + try { + std::regex re(pattern); + isAMatch = std::regex_match(text, re); + } catch (const std::regex_error& exc) { + control->core()->push_log_std("regex_error: " + std::string(exc.what())); + } + + return isAMatch ? (int64_t)true : (int64_t)false; +} + torrent::Object apply_to_time(const torrent::Object& rawArgs, int flags) { std::tm *u; @@ -737,8 +786,11 @@ initialize_command_ui() { CMD2_ANY_L ("view.list", std::bind(&apply_view_list)); 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.filter_on", std::bind(&apply_view_filter_on, std::placeholders::_2)); + CMD2_ANY_LIST ("view.filter", std::bind(&apply_view_event, &core::ViewManager::set_filter, std::placeholders::_2)); + CMD2_ANY_LIST ("view.filter_on", std::bind(&apply_view_filter_on, std::placeholders::_2)); + CMD2_ANY_LIST ("view.filter.temp", std::bind(&apply_view_event, &core::ViewManager::set_filter_temp, std::placeholders::_2)); + CMD2_VAR_STRING("view.filter.temp.excluded", "default,started,stopped"); + CMD2_VAR_BOOL ("view.filter.temp.log", 0); CMD2_ANY_LIST("view.sort", std::bind(&apply_view_sort, std::placeholders::_2)); CMD2_ANY_LIST("view.sort_new", std::bind(&apply_view_event, &core::ViewManager::set_sort_new, std::placeholders::_2)); @@ -794,6 +846,7 @@ initialize_command_ui() { CMD2_ANY_LIST("greater", &apply_greater); CMD2_ANY_LIST("equal", &apply_equal); CMD2_ANY_LIST("compare", &apply_compare); + 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 0c77f2a6..272cd87c 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_temp_filter)); + iterator splitFiltered = std::stable_partition(begin_filtered(), end_filtered(), view_downloads_filter(m_filter, m_temp_filter)); base_type changed(splitVisible, splitFiltered); iterator splitChanged = changed.begin() + std::distance(splitVisible, end_visible()); @@ -296,10 +301,10 @@ View::filter() { } void -View::filter_by(const torrent::Object& condition, View::base_type& result) -{ +View::filter_by(const torrent::Object& condition, View::base_type& result) { // std::copy_if(begin_visible(), end_visible(), result.begin(), view_downloads_filter(condition)); - view_downloads_filter matches = view_downloads_filter(condition); + view_downloads_filter matches = view_downloads_filter(condition, m_temp_filter); + for (iterator itr = begin_visible(); itr != end_visible(); ++itr) if (matches(*itr)) result.push_back(*itr); @@ -312,8 +317,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_temp_filter)(download)) { if (itr >= end_visible()) { erase_internal(itr); insert_visible(download); diff --git a/src/core/view.h b/src/core/view.h index 6d86ab6d..f69baac5 100644 --- a/src/core/view.h +++ b/src/core/view.h @@ -124,7 +124,9 @@ public: void filter_download(core::Download* download); const torrent::Object& get_filter() const { return m_filter; } - void set_filter(const torrent::Object& s) { m_filter = s; } + void set_filter(const torrent::Object& s) { m_filter = s; } + const torrent::Object& get_filter_temp() const { return m_temp_filter; } + void set_filter_temp(const torrent::Object& s) { m_temp_filter = s; } void set_filter_on_event(const std::string& event); void clear_filter_on(); @@ -173,6 +175,7 @@ private: torrent::Object m_sortCurrent; torrent::Object m_filter; + torrent::Object m_temp_filter; // 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..7f711c37 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_filter_temp(const std::string& name, const torrent::Object& cmd) { + iterator viewItr = find_throw(name); + + (*viewItr)->set_filter_temp(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..92aa3c31 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_filter_temp(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/display/window_download_list.cc b/src/display/window_download_list.cc index 7eaf3800..6b4936d2 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_filter_temp().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 25f89fc4..f1d6af5c 100644 --- a/src/ui/download_list.cc +++ b/src/ui/download_list.cc @@ -36,6 +36,8 @@ #include "config.h" +#include + #include #include #include @@ -259,6 +261,25 @@ DownloadList::receive_view_input(Input type) { title = "command"; break; + case INPUT_FILTER: + { + // Do not allow to subfilter the defined excluded views + const std::string excluded_views = rpc::call_command_string("view.filter.temp.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"; + } + break; + default: throw torrent::internal_error("DownloadList::receive_view_input(...) Invalid input type."); } @@ -332,6 +353,28 @@ DownloadList::receive_exit_input(Input type) { input->str()); break; + case INPUT_FILTER: + if (input->str().empty()) { + if (rpc::call_command_value("view.filter.temp.log")) + control->core()->push_log_std("Clear temporary filter on '" + current_view()->name() + "' view."); + current_view()->set_filter_temp(torrent::Object()); + current_view()->filter(); + current_view()->sort(); + } 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 temp_filter = "match={d.name=," + pattern + "}"; + if (rpc::call_command_value("view.filter.temp.log")) + control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern); + current_view()->set_filter_temp(temp_filter); + current_view()->filter(); + } + break; + default: throw torrent::internal_error("DownloadList::receive_exit_input(...) Invalid input type."); } @@ -353,6 +396,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 7dbb8bfd..791ee616 100644 --- a/src/ui/download_list.h +++ b/src/ui/download_list.h @@ -87,6 +87,7 @@ public: INPUT_LOAD_MODIFIED, INPUT_CHANGE_DIRECTORY, INPUT_COMMAND, + INPUT_FILTER, INPUT_EOI } Input;