From 429961a335dd1c038804652e48b38b5102721c36 Mon Sep 17 00:00:00 2001 From: Matan Ziv-Av Date: Wed, 1 Jun 2022 05:57:44 +0300 Subject: [PATCH] Filtering torrents in transmission-remote (#3125) * Filtering torrents in transmission-remote - Add `-F ` option to filter selected torrent - May filter on uploading/downloading, label, torrent name, and upload ratio, for now - Filters may be negated - Filters may be chained (logical and) - Add `-ids` option to print ids of filtered torrents. * Add "partially wanted" filter to transmission-remote --- utils/remote.cc | 161 +++++++++++++++++++++++++++++++++++- utils/transmission-remote.1 | 26 ++++++ 2 files changed, 183 insertions(+), 4 deletions(-) diff --git a/utils/remote.cc b/utils/remote.cc index f8d449628..6fa1c6d11 100644 --- a/utils/remote.cc +++ b/utils/remote.cc @@ -12,6 +12,7 @@ #include #include #include /* strcmp */ +#include #include #include @@ -76,6 +77,8 @@ static char constexpr SpeedMStr[] = "MB/s"; static char constexpr SpeedGStr[] = "GB/s"; static char constexpr SpeedTStr[] = "TB/s"; +static char id[4096]; + /*** **** **** Display Utilities @@ -204,13 +207,14 @@ enum TAG_STATS, TAG_DETAILS, TAG_FILES, + TAG_FILTER, + TAG_GROUPS, TAG_LIST, TAG_PEERS, TAG_PIECES, TAG_PORTTEST, TAG_TORRENT_ADD, - TAG_TRACKERS, - TAG_GROUPS + TAG_TRACKERS }; /*** @@ -219,7 +223,7 @@ enum **** ***/ -static auto constexpr Options = std::array{ +static auto constexpr Options = std::array{ { { 'a', "add", "Add torrent files by filename or URL", "a", false, nullptr }, { 970, "alt-speed", "Use the alternate Limits", "as", false, nullptr }, { 971, "no-alt-speed", "Don't use the alternate Limits", "AS", false, nullptr }, @@ -250,9 +254,11 @@ static auto constexpr Options = std::array{ { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", false, nullptr }, { 850, "exit", "Tell the transmission session to shut down", nullptr, false, nullptr }, { 940, "files", "List the current torrent(s)' files", "f", false, nullptr }, + { 'F', "filter", "Filter the current torrent(s)", "F", true, "criterion" }, { 'g', "get", "Mark files for download", "g", true, "" }, { 'G', "no-get", "Mark files for not downloading", "G", true, "" }, { 'i', "info", "Show the current torrent(s)' details", "i", false, nullptr }, + { 944, "print-ids", "Print the current torrent(s)' ids", "ids", false, nullptr }, { 940, "info-files", "List the current torrent(s)' files", "if", false, nullptr }, { 941, "info-peers", "List the current torrent(s)' peers", "ip", false, nullptr }, { 942, "info-pieces", "List the current torrent(s)' pieces", "ic", false, nullptr }, @@ -401,6 +407,7 @@ static int getOptMode(int val) case 820: /* UseSSL */ case 't': /* set current torrent */ case 'V': /* show version number */ + case 944: /* print selected torrents' ids */ return 0; case 'c': /* incomplete-dir */ @@ -476,6 +483,7 @@ static int getOptMode(int val) case 941: /* info-peer */ case 942: /* info-pieces */ case 943: /* info-tracker */ + case 'F': /* filter torrents */ return MODE_TORRENT_GET; case 'd': /* download speed limit */ @@ -537,6 +545,7 @@ static int getOptMode(int val) static bool debug = false; static char* auth = nullptr; +static char* filter = nullptr; static char* netrc = nullptr; static char* session_id = nullptr; static bool UseSSL = false; @@ -749,6 +758,7 @@ static tr_quark const details_keys[] = { TR_KEY_uploadedEver, TR_KEY_uploadLimit, TR_KEY_uploadLimited, + TR_KEY_uploadRatio, TR_KEY_webseeds, TR_KEY_webseedsSendingToUs, }; @@ -1974,8 +1984,132 @@ static void printGroups(tr_variant* top) } } } -static char id[4096]; +static void filterIds(tr_variant* top) +{ + tr_variant* args; + tr_variant* list; + + std::set ids; + + if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &list)) + { + size_t pos = 0; + bool negate = false; + std::string_view arg; + + if (filter[pos] == '~') + { + ++pos; + negate = true; + } + if (strlen(filter) > pos + 1 && filter[pos + 1] == ':') + { + arg = filter + pos + 2; + } + + for (int i = 0, n = tr_variantListSize(list); i < n; ++i) + { + tr_variant* d = tr_variantListChild(list, i); + int64_t torId; + if (!tr_variantDictFindInt(d, TR_KEY_id, &torId) || torId < 0) + { + continue; + } + bool include = negate; + auto const status = getStatusString(d); + switch (filter[pos]) + { + case 'i': // Status = Idle + if (status == "Idle") + { + include = !include; + } + break; + case 'd': // Downloading (Status is Downloading or Up&Down) + if (status.find("Down") != std::string::npos) + { + include = !include; + } + break; + case 'u': // Uploading (Status is Uploading, Up&Down or Seeding + if ((status.find("Up") != std::string::npos) || (status == "Seeding")) + { + include = !include; + } + break; + case 'l': // label + if (tr_variant * l; tr_variantDictFindList(d, TR_KEY_labels, &l)) + { + size_t child_pos = 0; + tr_variant const* child; + std::string_view sv; + while ((child = tr_variantListChild(l, child_pos++))) + { + if (tr_variantGetStrView(child, &sv)) + { + if (arg == sv) + { + include = !include; + break; + } + } + } + } + break; + case 'n': // Torrent name substring + if (std::string_view name; !tr_variantDictFindStrView(d, TR_KEY_name, &name)) + { + continue; + } + else if (name.find(arg) != std::string::npos) + { + include = !include; + } + break; + case 'r': // Minimal ratio + if (double ratio; !tr_variantDictFindReal(d, TR_KEY_uploadRatio, &ratio)) + { + continue; + } + else if (ratio >= std::stof(std::string(arg))) + { + include = !include; + } + break; + case 'w': // Not all torrent wanted + if (int64_t totalSize; !tr_variantDictFindInt(d, TR_KEY_totalSize, &totalSize) || totalSize < 0) + { + continue; + } + else if (int64_t sizeWhenDone; + !tr_variantDictFindInt(d, TR_KEY_sizeWhenDone, &sizeWhenDone) || sizeWhenDone < 0) + { + continue; + } + else if (totalSize > sizeWhenDone) + { + include = !include; + } + break; + } + if (include) + { + ids.insert(torId); + } + } + std::string res; + for (auto const& i : ids) + { + res += std::to_string(i) + ","; + } + if (res.empty()) + { + res = ","; // no selected torrents + } + tr_strlcpy(id, res.data(), 4096); + } +} static int processResponse(char const* rpcurl, std::string_view response) { tr_variant top; @@ -2053,6 +2187,10 @@ static int processResponse(char const* rpcurl, std::string_view response) printGroups(&top); break; + case TAG_FILTER: + filterIds(&top); + break; + case TAG_TORRENT_ADD: { int64_t i; @@ -2350,6 +2488,10 @@ static int processArgs(char const* rpcurl, int argc, char const* const* argv) fprintf(stderr, "%s %s\n", MyName, LONG_VERSION_STRING); exit(0); + case 944: + printf("%s\n", tr_str_is_empty(id) ? "all" : id); + break; + case TR_OPT_ERR: fprintf(stderr, "invalid option\n"); showUsage(); @@ -2398,6 +2540,17 @@ static int processArgs(char const* rpcurl, int argc, char const* const* argv) switch (c) { + case 'F': + filter = tr_strdup(optarg); /* Unnecessary dup? we will use it before optarg will be changed */ + tr_variantDictAddInt(top, TR_KEY_tag, TAG_FILTER); + + for (size_t i = 0; i < TR_N_ELEMENTS(details_keys); ++i) + { + tr_variantListAddQuark(fields, details_keys[i]); + } + + addIdArg(args, id, "all"); + break; case 'i': tr_variantDictAddInt(top, TR_KEY_tag, TAG_DETAILS); diff --git a/utils/transmission-remote.1 b/utils/transmission-remote.1 index 26834516a..f1c7edb02 100644 --- a/utils/transmission-remote.1 +++ b/utils/transmission-remote.1 @@ -25,6 +25,7 @@ and .Op Fl er | ep | et .Op Fl -exit .Op Fl f +.Op Fl F Ar filter .Op Fl g Ar files .Op Fl G Ar files .Op Fl gsr Ar ratio @@ -32,6 +33,7 @@ and .Op Fl h .Op Fl i .Op Fl ic +.Op Fl ids .Op Fl if .Op Fl ip .Op Fl it @@ -153,6 +155,19 @@ adds a single file to the download list, and .Ar files adds multiple files to the download list, such as "\-g1,3-5" to add files #1, #3, #4, and #5 to the download list. +.It Fl F Fl -filter Ar filter +Filter selected torrents. Further commands will use only torrents that satisfy +the filter condition. +.D1 i - currently idle +.D1 u - currently uploading +.D1 d - currently downloading +.D1 n:str - torrent name includes str +.D1 l:label - has label +.D1 r:ratio - Minimum upload ratio +.D1 w - Have some unwanted files +Prefixing the filter by "~" negates the filter. +-F may be specified more than once, and may be preceded by -t. Only torrents +that satisfy all the conditions are selected. .It Fl G Fl -no-get Ar all | file-index | files Mark file(s) for not downloading. .It Fl gsr Fl -global-seedratio Ar ratio @@ -178,6 +193,9 @@ List session information from the server List statistical information from the server .It Fl l Fl -list List all torrents +.It Fl ids Fl -print-ids +Print a list of the specified torrent's ids in a format suitable as a parameter for +.Ar -t .It Fl L Fl -labels Ar label1[,label2[,...]] Set the specified torrent's labels .It Fl m Fl -portmap @@ -332,6 +350,14 @@ List all active torrents: .Bd -literal -offset indent $ transmission-remote \-tactive \-l .Ed +List all torrents with label "abc": +.Bd -literal -offset indent +$ transmission-remote \-F l:abc \-l +.Ed +List all torrents with name containing "def" or with label "abc": +.Bd -literal -offset indent +$ transmission-remote -t $(\ transmission-remote \-F n:def \-ids )$(\ transmission-remote \-F l:abc \-ids ) \-l +.Ed Set download and upload limits to 400 kB/sec and 60 kB/sec: .Bd -literal -offset indent $ transmission-remote \-d400 \-u60