mirror of
https://github.com/transmission/transmission
synced 2024-12-23 00:04:06 +00:00
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:
parent
84d65d8e61
commit
429961a335
2 changed files with 183 additions and 4 deletions
161
utils/remote.cc
161
utils/remote.cc
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue