Filtering torrents in transmission-remote (#3125)

* Filtering torrents in transmission-remote

- Add `-F <filter>` 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
This commit is contained in:
Matan Ziv-Av 2022-06-01 05:57:44 +03:00 committed by GitHub
parent 84d65d8e61
commit 429961a335
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 183 additions and 4 deletions

View File

@ -12,6 +12,7 @@
#include <cstdio>
#include <cstdlib>
#include <cstring> /* strcmp */
#include <set>
#include <string>
#include <string_view>
@ -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<tr_option, 94>{
static auto constexpr Options = std::array<tr_option, 96>{
{ { '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<tr_option, 94>{
{ 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, "<files>" },
{ 'G', "no-get", "Mark files for not downloading", "G", true, "<files>" },
{ '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<int> 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);

View File

@ -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