mirror of
https://github.com/transmission/transmission.git
synced 2025-12-12 20:35:49 +01:00
feat: allow optional arguments in tr_getopt() (#7510)
* feat: allow optional arguments in `tr_getopt()` * test: new tests for optional arg * code review: `using Arg = tr_option::Arg` * refactor: static assert option array size * test: add new tests for missing arguments in the middle * test: static auto constexpr
This commit is contained in:
61
cli/cli.cc
61
cli/cli.cc
@@ -48,36 +48,41 @@ sig_atomic_t manualUpdate = false;
|
||||
|
||||
char const* torrentPath = nullptr;
|
||||
|
||||
auto constexpr Options = std::array<tr_option, 20>{
|
||||
{ { 'b', "blocklist", "Enable peer blocklists", "b", false, nullptr },
|
||||
{ 'B', "no-blocklist", "Disable peer blocklists", "B", false, nullptr },
|
||||
{ 'd', "downlimit", "Set max download speed in " SPEED_K_STR, "d", true, "<speed>" },
|
||||
{ 'D', "no-downlimit", "Don't limit the download speed", "D", false, nullptr },
|
||||
{ 910, "encryption-required", "Encrypt all peer connections", "er", false, nullptr },
|
||||
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", false, nullptr },
|
||||
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", false, nullptr },
|
||||
{ 'f', "finish", "Run a script when the torrent finishes", "f", true, "<script>" },
|
||||
{ 'g', "config-dir", "Where to find configuration files", "g", true, "<path>" },
|
||||
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", false, nullptr },
|
||||
{ 'M', "no-portmap", "Disable portmapping", "M", false, nullptr },
|
||||
{ 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", true, "<port>" },
|
||||
{ 't',
|
||||
"tos",
|
||||
"Peer socket DSCP / ToS setting (number, or a DSCP string, e.g. 'af11' or 'cs0', default=" TR_DEFAULT_PEER_SOCKET_TOS_STR
|
||||
")",
|
||||
"t",
|
||||
true,
|
||||
"<dscp-or-tos>" },
|
||||
{ 'u', "uplimit", "Set max upload speed in " SPEED_K_STR, "u", true, "<speed>" },
|
||||
{ 'U', "no-uplimit", "Don't limit the upload speed", "U", false, nullptr },
|
||||
{ 'v', "verify", "Verify the specified torrent", "v", false, nullptr },
|
||||
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
|
||||
{ 'w', "download-dir", "Where to save downloaded data", "w", true, "<path>" },
|
||||
{ 500, "sequential-download", "Download pieces sequentially", "seq", false, nullptr },
|
||||
using Arg = tr_option::Arg;
|
||||
auto constexpr Options = std::array<tr_option, 20>{ {
|
||||
{ 'b', "blocklist", "Enable peer blocklists", "b", Arg::None, nullptr },
|
||||
{ 'B', "no-blocklist", "Disable peer blocklists", "B", Arg::None, nullptr },
|
||||
{ 'd', "downlimit", "Set max download speed in " SPEED_K_STR, "d", Arg::Required, "<speed>" },
|
||||
{ 'D', "no-downlimit", "Don't limit the download speed", "D", Arg::None, nullptr },
|
||||
{ 910, "encryption-required", "Encrypt all peer connections", "er", Arg::None, nullptr },
|
||||
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", Arg::None, nullptr },
|
||||
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", Arg::None, nullptr },
|
||||
{ 'f', "finish", "Run a script when the torrent finishes", "f", Arg::Required, "<script>" },
|
||||
{ 'g', "config-dir", "Where to find configuration files", "g", Arg::Required, "<path>" },
|
||||
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", Arg::None, nullptr },
|
||||
{ 'M', "no-portmap", "Disable portmapping", "M", Arg::None, nullptr },
|
||||
{ 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", Arg::Required, "<port>" },
|
||||
{ 't',
|
||||
"tos",
|
||||
"Peer socket DSCP / ToS setting (number, or a DSCP string, e.g. 'af11' or 'cs0', default=" TR_DEFAULT_PEER_SOCKET_TOS_STR
|
||||
")",
|
||||
"t",
|
||||
Arg::Required,
|
||||
"<dscp-or-tos>" },
|
||||
{ 'u', "uplimit", "Set max upload speed in " SPEED_K_STR, "u", Arg::Required, "<speed>" },
|
||||
{ 'U', "no-uplimit", "Don't limit the upload speed", "U", Arg::None, nullptr },
|
||||
{ 'v', "verify", "Verify the specified torrent", "v", Arg::None, nullptr },
|
||||
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
|
||||
{ 'w', "download-dir", "Where to save downloaded data", "w", Arg::Required, "<path>" },
|
||||
{ 500, "sequential-download", "Download pieces sequentially", "seq", Arg::None, nullptr },
|
||||
|
||||
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
|
||||
};
|
||||
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
|
||||
} };
|
||||
static_assert(Options[std::size(Options) - 2].val != 0);
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
int parseCommandLine(tr_variant*, int argc, char const** argv);
|
||||
|
||||
void sigHandler(int signal);
|
||||
|
||||
149
daemon/daemon.cc
149
daemon/daemon.cc
@@ -75,79 +75,86 @@ char constexpr Usage[] = "Transmission " LONG_VERSION_STRING
|
||||
"\n"
|
||||
"Usage: transmission-daemon [options]";
|
||||
|
||||
// --- Config File
|
||||
|
||||
auto constexpr Options = std::array<tr_option, 47>{
|
||||
{ { 'a', "allowed", "Allowed IP addresses. (Default: " TR_DEFAULT_RPC_WHITELIST ")", "a", true, "<list>" },
|
||||
{ 'b', "blocklist", "Enable peer blocklists", "b", false, nullptr },
|
||||
{ 'B', "no-blocklist", "Disable peer blocklists", "B", false, nullptr },
|
||||
{ 'c', "watch-dir", "Where to watch for new torrent files", "c", true, "<directory>" },
|
||||
{ 'C', "no-watch-dir", "Disable the watch-dir", "C", false, nullptr },
|
||||
{ 941, "incomplete-dir", "Where to store new torrents until they're complete", nullptr, true, "<directory>" },
|
||||
{ 942, "no-incomplete-dir", "Don't store incomplete torrents in a different location", nullptr, false, nullptr },
|
||||
{ 'd', "dump-settings", "Dump the settings and exit", "d", false, nullptr },
|
||||
{ 943, "default-trackers", "Trackers for public torrents to use automatically", nullptr, true, "<list>" },
|
||||
{ 'e', "logfile", "Dump the log messages to this filename", "e", true, "<filename>" },
|
||||
{ 'f', "foreground", "Run in the foreground instead of daemonizing", "f", false, nullptr },
|
||||
{ 'g', "config-dir", "Where to look for configuration files", "g", true, "<path>" },
|
||||
{ 'p', "port", "RPC port (Default: " TR_DEFAULT_RPC_PORT_STR ")", "p", true, "<port>" },
|
||||
{ 't', "auth", "Require authentication", "t", false, nullptr },
|
||||
{ 'T', "no-auth", "Don't require authentication", "T", false, nullptr },
|
||||
{ 'u', "username", "Set username for authentication", "u", true, "<username>" },
|
||||
{ 'v', "password", "Set password for authentication", "v", true, "<password>" },
|
||||
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
|
||||
{ 810, "log-level", "Must be 'critical', 'error', 'warn', 'info', 'debug', or 'trace'.", nullptr, true, "<level>" },
|
||||
{ 811, "log-error", "Deprecated. Use --log-level=error", nullptr, false, nullptr },
|
||||
{ 812, "log-info", "Deprecated. Use --log-level=info", nullptr, false, nullptr },
|
||||
{ 813, "log-debug", "Deprecated. Use --log-level=debug", nullptr, false, nullptr },
|
||||
{ 'w', "download-dir", "Where to save downloaded data", "w", true, "<path>" },
|
||||
{ 800, "paused", "Pause all torrents on startup", nullptr, false, nullptr },
|
||||
{ 'o', "dht", "Enable distributed hash tables (DHT)", "o", false, nullptr },
|
||||
{ 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", false, nullptr },
|
||||
{ 'y', "lpd", "Enable local peer discovery (LPD)", "y", false, nullptr },
|
||||
{ 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", false, nullptr },
|
||||
{ 830, "utp", "Enable µTP for peer connections", nullptr, false, nullptr },
|
||||
{ 831, "no-utp", "Disable µTP for peer connections", nullptr, false, nullptr },
|
||||
{ 'P', "peerport", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "P", true, "<port>" },
|
||||
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", false, nullptr },
|
||||
{ 'M', "no-portmap", "Disable portmapping", "M", false, nullptr },
|
||||
{ 'L',
|
||||
"peerlimit-global",
|
||||
"Maximum overall number of peers (Default: " TR_DEFAULT_PEER_LIMIT_GLOBAL_STR ")",
|
||||
"L",
|
||||
true,
|
||||
"<limit>" },
|
||||
{ 'l',
|
||||
"peerlimit-torrent",
|
||||
"Maximum number of peers per torrent (Default: " TR_DEFAULT_PEER_LIMIT_TORRENT_STR ")",
|
||||
"l",
|
||||
true,
|
||||
"<limit>" },
|
||||
{ 910, "encryption-required", "Encrypt all peer connections", "er", false, nullptr },
|
||||
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", false, nullptr },
|
||||
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", false, nullptr },
|
||||
{ 'i', "bind-address-ipv4", "Where to listen for peer connections", "i", true, "<ipv4 addr>" },
|
||||
{ 'I', "bind-address-ipv6", "Where to listen for peer connections", "I", true, "<ipv6 addr>" },
|
||||
{ 'r', "rpc-bind-address", "Where to listen for RPC connections", "r", true, "<ip addr>" },
|
||||
{ 953,
|
||||
"global-seedratio",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio",
|
||||
"gsr",
|
||||
true,
|
||||
"ratio" },
|
||||
{ 954,
|
||||
"no-global-seedratio",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio",
|
||||
"GSR",
|
||||
false,
|
||||
nullptr },
|
||||
{ 994, "sequential-download", "Enable sequential download by default", "seq", false, nullptr },
|
||||
{ 995, "no-sequential-download", "Disable sequential download by default", "SEQ", false, nullptr },
|
||||
{ 'x', "pid-file", "Enable PID file", "x", true, "<pid-file>" },
|
||||
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
|
||||
};
|
||||
using Arg = tr_option::Arg;
|
||||
auto constexpr Options = std::array<tr_option, 47>{ {
|
||||
{ 'a', "allowed", "Allowed IP addresses. (Default: " TR_DEFAULT_RPC_WHITELIST ")", "a", Arg::Required, "<list>" },
|
||||
{ 'b', "blocklist", "Enable peer blocklists", "b", Arg::None, nullptr },
|
||||
{ 'B', "no-blocklist", "Disable peer blocklists", "B", Arg::None, nullptr },
|
||||
{ 'c', "watch-dir", "Where to watch for new torrent files", "c", Arg::Required, "<directory>" },
|
||||
{ 'C', "no-watch-dir", "Disable the watch-dir", "C", Arg::None, nullptr },
|
||||
{ 941, "incomplete-dir", "Where to store new torrents until they're complete", nullptr, Arg::Required, "<directory>" },
|
||||
{ 942, "no-incomplete-dir", "Don't store incomplete torrents in a different location", nullptr, Arg::None, nullptr },
|
||||
{ 'd', "dump-settings", "Dump the settings and exit", "d", Arg::None, nullptr },
|
||||
{ 943, "default-trackers", "Trackers for public torrents to use automatically", nullptr, Arg::Required, "<list>" },
|
||||
{ 'e', "logfile", "Dump the log messages to this filename", "e", Arg::Required, "<filename>" },
|
||||
{ 'f', "foreground", "Run in the foreground instead of daemonizing", "f", Arg::None, nullptr },
|
||||
{ 'g', "config-dir", "Where to look for configuration files", "g", Arg::Required, "<path>" },
|
||||
{ 'p', "port", "RPC port (Default: " TR_DEFAULT_RPC_PORT_STR ")", "p", Arg::Required, "<port>" },
|
||||
{ 't', "auth", "Require authentication", "t", Arg::None, nullptr },
|
||||
{ 'T', "no-auth", "Don't require authentication", "T", Arg::None, nullptr },
|
||||
{ 'u', "username", "Set username for authentication", "u", Arg::Required, "<username>" },
|
||||
{ 'v', "password", "Set password for authentication", "v", Arg::Required, "<password>" },
|
||||
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
|
||||
{ 810,
|
||||
"log-level",
|
||||
"Must be 'critical', 'error', 'warn', 'info', 'debug', or 'trace'.",
|
||||
nullptr,
|
||||
Arg::Required,
|
||||
"<level>" },
|
||||
{ 811, "log-error", "Deprecated. Use --log-level=error", nullptr, Arg::None, nullptr },
|
||||
{ 812, "log-info", "Deprecated. Use --log-level=info", nullptr, Arg::None, nullptr },
|
||||
{ 813, "log-debug", "Deprecated. Use --log-level=debug", nullptr, Arg::None, nullptr },
|
||||
{ 'w', "download-dir", "Where to save downloaded data", "w", Arg::Required, "<path>" },
|
||||
{ 800, "paused", "Pause all torrents on startup", nullptr, Arg::None, nullptr },
|
||||
{ 'o', "dht", "Enable distributed hash tables (DHT)", "o", Arg::None, nullptr },
|
||||
{ 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", Arg::None, nullptr },
|
||||
{ 'y', "lpd", "Enable local peer discovery (LPD)", "y", Arg::None, nullptr },
|
||||
{ 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", Arg::None, nullptr },
|
||||
{ 830, "utp", "Enable µTP for peer connections", nullptr, Arg::None, nullptr },
|
||||
{ 831, "no-utp", "Disable µTP for peer connections", nullptr, Arg::None, nullptr },
|
||||
{ 'P', "peerport", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "P", Arg::Required, "<port>" },
|
||||
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", Arg::None, nullptr },
|
||||
{ 'M', "no-portmap", "Disable portmapping", "M", Arg::None, nullptr },
|
||||
{ 'L',
|
||||
"peerlimit-global",
|
||||
"Maximum overall number of peers (Default: " TR_DEFAULT_PEER_LIMIT_GLOBAL_STR ")",
|
||||
"L",
|
||||
Arg::Required,
|
||||
"<limit>" },
|
||||
{ 'l',
|
||||
"peerlimit-torrent",
|
||||
"Maximum number of peers per torrent (Default: " TR_DEFAULT_PEER_LIMIT_TORRENT_STR ")",
|
||||
"l",
|
||||
Arg::Required,
|
||||
"<limit>" },
|
||||
{ 910, "encryption-required", "Encrypt all peer connections", "er", Arg::None, nullptr },
|
||||
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", Arg::None, nullptr },
|
||||
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", Arg::None, nullptr },
|
||||
{ 'i', "bind-address-ipv4", "Where to listen for peer connections", "i", Arg::Required, "<ipv4 addr>" },
|
||||
{ 'I', "bind-address-ipv6", "Where to listen for peer connections", "I", Arg::Required, "<ipv6 addr>" },
|
||||
{ 'r', "rpc-bind-address", "Where to listen for RPC connections", "r", Arg::Required, "<ip addr>" },
|
||||
{ 953,
|
||||
"global-seedratio",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio",
|
||||
"gsr",
|
||||
Arg::Required,
|
||||
"ratio" },
|
||||
{ 954,
|
||||
"no-global-seedratio",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio",
|
||||
"GSR",
|
||||
Arg::None,
|
||||
nullptr },
|
||||
{ 994, "sequential-download", "Enable sequential download by default", "seq", Arg::None, nullptr },
|
||||
{ 995, "no-sequential-download", "Disable sequential download by default", "SEQ", Arg::None, nullptr },
|
||||
{ 'x', "pid-file", "Enable PID file", "x", Arg::Required, "<pid-file>" },
|
||||
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
|
||||
} };
|
||||
static_assert(Options[std::size(Options) - 2].val != 0);
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
[[nodiscard]] std::string getConfigDir(int argc, char const* const* argv)
|
||||
{
|
||||
int c;
|
||||
|
||||
@@ -21,19 +21,18 @@ int tr_optind = 1;
|
||||
|
||||
namespace
|
||||
{
|
||||
[[nodiscard]] constexpr std::string_view getArgName(tr_option const* opt)
|
||||
[[nodiscard]] std::string getArgName(tr_option const* opt)
|
||||
{
|
||||
if (!opt->has_arg)
|
||||
auto const* const arg_name = opt->argName != nullptr ? opt->argName : "<args>";
|
||||
switch (opt->arg)
|
||||
{
|
||||
return ""sv;
|
||||
case tr_option::Arg::None:
|
||||
return ""s;
|
||||
case tr_option::Arg::Optional:
|
||||
return fmt::format("[{}]", arg_name);
|
||||
default: // tr_option::Arg::Required
|
||||
return arg_name;
|
||||
}
|
||||
|
||||
if (opt->argName != nullptr)
|
||||
{
|
||||
return opt->argName;
|
||||
}
|
||||
|
||||
return "<args>"sv;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr size_t get_next_line_len(std::string_view description, size_t maxlen)
|
||||
@@ -113,7 +112,7 @@ tr_option const* findOption(tr_option const* opts, char const* str, char const**
|
||||
size_t len = o->longName != nullptr ? strlen(o->longName) : 0;
|
||||
|
||||
if (matchlen < len && str[0] == '-' && str[1] == '-' && strncmp(str + 2, o->longName, len) == 0 &&
|
||||
(str[len + 2] == '\0' || (o->has_arg && str[len + 2] == '=')))
|
||||
(str[len + 2] == '\0' || (o->has_arg() && str[len + 2] == '=')))
|
||||
{
|
||||
matchlen = len;
|
||||
match = o;
|
||||
@@ -122,7 +121,8 @@ tr_option const* findOption(tr_option const* opts, char const* str, char const**
|
||||
|
||||
len = o->shortName != nullptr ? strlen(o->shortName) : 0;
|
||||
|
||||
if (matchlen < len && str[0] == '-' && strncmp(str + 1, o->shortName, len) == 0 && (str[len + 1] == '\0' || o->has_arg))
|
||||
if (matchlen < len && str[0] == '-' && strncmp(str + 1, o->shortName, len) == 0 &&
|
||||
(str[len + 1] == '\0' || o->has_arg()))
|
||||
{
|
||||
matchlen = len;
|
||||
match = o;
|
||||
@@ -165,7 +165,7 @@ void tr_getopt_usage(char const* app_name, char const* description, struct tr_op
|
||||
maxWidth(o, long_width, short_width, arg_width);
|
||||
}
|
||||
|
||||
auto const help = tr_option{ -1, "help", "Display this help page and exit", "h", false, nullptr };
|
||||
auto const help = tr_option{ -1, "help", "Display this help page and exit", "h", tr_option::Arg::None, nullptr };
|
||||
maxWidth(&help, long_width, short_width, arg_width);
|
||||
|
||||
if (description == nullptr)
|
||||
@@ -215,7 +215,7 @@ int tr_getopt(char const* usage, int argc, char const* const* argv, tr_option co
|
||||
return TR_OPT_UNK;
|
||||
}
|
||||
|
||||
if (!o->has_arg)
|
||||
if (!o->has_arg())
|
||||
{
|
||||
/* no argument needed for this option, so we're done */
|
||||
if (arg != nullptr)
|
||||
@@ -223,28 +223,28 @@ int tr_getopt(char const* usage, int argc, char const* const* argv, tr_option co
|
||||
return TR_OPT_ERR;
|
||||
}
|
||||
|
||||
*setme_optarg = nullptr;
|
||||
++tr_optind;
|
||||
return o->val;
|
||||
}
|
||||
|
||||
/* option needed an argument, and it was embedded in this string */
|
||||
++tr_optind;
|
||||
|
||||
/* option allows an argument, and it was embedded in this string */
|
||||
if (arg != nullptr)
|
||||
{
|
||||
*setme_optarg = arg;
|
||||
++tr_optind;
|
||||
return o->val;
|
||||
}
|
||||
|
||||
/* throw an error if the option needed an argument but didn't get one */
|
||||
if (++tr_optind >= argc)
|
||||
if (tr_optind >= argc || findOption(opts, argv[tr_optind], nullptr) != nullptr)
|
||||
{
|
||||
return TR_OPT_ERR;
|
||||
}
|
||||
/* throw an error if the option needed an argument but didn't get one */
|
||||
if (o->arg == tr_option::Arg::Required)
|
||||
{
|
||||
return TR_OPT_ERR;
|
||||
}
|
||||
|
||||
if (findOption(opts, argv[tr_optind], nullptr) != nullptr)
|
||||
{
|
||||
return TR_OPT_ERR;
|
||||
return o->val;
|
||||
}
|
||||
|
||||
*setme_optarg = argv[tr_optind++];
|
||||
|
||||
@@ -17,11 +17,23 @@ extern int tr_optind;
|
||||
|
||||
struct tr_option
|
||||
{
|
||||
enum class Arg : uint8_t
|
||||
{
|
||||
None,
|
||||
Optional,
|
||||
Required
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr bool has_arg() const noexcept
|
||||
{
|
||||
return arg >= Arg::Optional;
|
||||
}
|
||||
|
||||
int val; /* the value to return from tr_getopt() */
|
||||
char const* longName; /* --long-form */
|
||||
char const* description; /* option's description for tr_getopt_usage() */
|
||||
char const* shortName; /* short form */
|
||||
bool has_arg; /* 0 for no argument, 1 for argument */
|
||||
Arg arg; /* See enum class Arg */
|
||||
char const* argName; /* argument's description for tr_getopt_usage() */
|
||||
};
|
||||
|
||||
|
||||
25
qt/main.cc
25
qt/main.cc
@@ -29,17 +29,22 @@ char const* const DisplayName = "transmission-qt";
|
||||
auto constexpr FileArgsSeparator = "--"sv;
|
||||
auto constexpr QtArgsSeparator = "---"sv;
|
||||
|
||||
std::array<tr_option, 8> const Opts = {
|
||||
tr_option{ 'g', "config-dir", "Where to look for configuration files", "g", true, "<path>" },
|
||||
{ 'm', "minimized", "Start minimized in system tray", "m", false, nullptr },
|
||||
{ 'p', "port", "Port to use when connecting to an existing session", "p", true, "<port>" },
|
||||
{ 'r', "remote", "Connect to an existing session at the specified hostname", "r", true, "<host>" },
|
||||
{ 'u', "username", "Username to use when connecting to an existing session", "u", true, "<username>" },
|
||||
{ 'v', "version", "Show version number and exit", "v", false, nullptr },
|
||||
{ 'w', "password", "Password to use when connecting to an existing session", "w", true, "<password>" },
|
||||
{ 0, nullptr, nullptr, nullptr, false, nullptr }
|
||||
};
|
||||
using Arg = tr_option::Arg;
|
||||
auto constexpr Opts = std::array<tr_option, 8>{ {
|
||||
{ 'g', "config-dir", "Where to look for configuration files", "g", Arg::Required, "<path>" },
|
||||
{ 'm', "minimized", "Start minimized in system tray", "m", Arg::None, nullptr },
|
||||
{ 'p', "port", "Port to use when connecting to an existing session", "p", Arg::Required, "<port>" },
|
||||
{ 'r', "remote", "Connect to an existing session at the specified hostname", "r", Arg::Required, "<host>" },
|
||||
{ 'u', "username", "Username to use when connecting to an existing session", "u", Arg::Required, "<username>" },
|
||||
{ 'v', "version", "Show version number and exit", "v", Arg::None, nullptr },
|
||||
{ 'w', "password", "Password to use when connecting to an existing session", "w", Arg::Required, "<password>" },
|
||||
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
|
||||
} };
|
||||
static_assert(Opts[std::size(Opts) - 2].val != 0);
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
char const* getUsage()
|
||||
{
|
||||
return "Usage:\n"
|
||||
|
||||
@@ -11,18 +11,24 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
auto const Options = std::array<tr_option, 8>{
|
||||
tr_option{ 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", false, nullptr },
|
||||
{ 'o', "outfile", "Save the generated .torrent to this filename", "o", true, "<file>" },
|
||||
{ 's', "piecesize", "Set how many KiB each piece should be, overriding the preferred default", "s", true, "<size in KiB>" },
|
||||
{ 'c', "comment", "Add a comment", "c", true, "<comment>" },
|
||||
{ 't', "tracker", "Add a tracker's announce URL", "t", true, "<url>" },
|
||||
{ 'q', "pooka", "Pooka", "pk", false, nullptr },
|
||||
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
|
||||
{ 0, nullptr, nullptr, nullptr, false, nullptr }
|
||||
};
|
||||
|
||||
using Arg = tr_option::Arg;
|
||||
auto constexpr Options = std::array<tr_option, 9>{ {
|
||||
{ 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", Arg::None, nullptr },
|
||||
{ 'o', "outfile", "Save the generated .torrent to this filename", "o", Arg::Required, "<file>" },
|
||||
{ 's',
|
||||
"piecesize",
|
||||
"Set how many KiB each piece should be, overriding the preferred default",
|
||||
"s",
|
||||
Arg::Required,
|
||||
"<size in KiB>" },
|
||||
{ 'c', "comment", "Add a comment", "c", Arg::Required, "<comment>" },
|
||||
{ 't', "tracker", "Add a tracker's announce URL", "t", Arg::Required, "<url>" },
|
||||
{ 'q', "pooka", "Pooka", "pk", Arg::None, nullptr },
|
||||
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
|
||||
{ 994, "sequential-download", "Download the torrent sequentially", "seq", Arg::Optional, "<piece>" },
|
||||
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
|
||||
} };
|
||||
static_assert(Options[std::size(Options) - 2].val != 0);
|
||||
} // namespace
|
||||
|
||||
class GetoptTest : public ::testing::Test
|
||||
@@ -54,102 +60,183 @@ protected:
|
||||
|
||||
TEST_F(GetoptTest, noOptions)
|
||||
{
|
||||
auto const args = std::array<char const*, 1>{ "/some/path/tr-getopt-test" };
|
||||
auto constexpr ExpectedN = 0;
|
||||
auto const expected_c = std::array<int, ExpectedN>{};
|
||||
auto const expected_opt_arg = std::array<char const*, ExpectedN>{};
|
||||
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
|
||||
static auto constexpr Args = std::array<char const*, 1>{ "/some/path/tr-getopt-test" };
|
||||
static auto constexpr ExpectedN = 0;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{};
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{};
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, shortNoarg)
|
||||
{
|
||||
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-p" };
|
||||
auto constexpr ExpectedN = 1;
|
||||
auto const expected_c = std::array<int, ExpectedN>{ 'p' };
|
||||
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ nullptr };
|
||||
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
|
||||
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-p" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'p' };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, longNoarg)
|
||||
{
|
||||
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--private" };
|
||||
auto constexpr ExpectedN = 1;
|
||||
auto const expected_c = std::array<int, ExpectedN>{ 'p' };
|
||||
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ nullptr };
|
||||
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
|
||||
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--private" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'p' };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, shortWithArg)
|
||||
TEST_F(GetoptTest, shortWithRequiredArg)
|
||||
{
|
||||
auto const args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "-o", "/tmp/outfile" };
|
||||
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "-o", "/tmp/outfile" };
|
||||
auto constexpr ExpectedN = 1;
|
||||
auto const expected_c = std::array<int, ExpectedN>{ 'o' };
|
||||
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
|
||||
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'o' };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, longWithArg)
|
||||
TEST_F(GetoptTest, longWithRequiredArg)
|
||||
{
|
||||
auto const args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "--outfile", "/tmp/outfile" };
|
||||
auto constexpr ExpectedN = 1;
|
||||
auto const expected_c = std::array<int, ExpectedN>{ 'o' };
|
||||
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
|
||||
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
|
||||
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "--outfile", "/tmp/outfile" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'o' };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, shortWithArgAfterEq)
|
||||
TEST_F(GetoptTest, shortWithRequiredArgAfterEq)
|
||||
{
|
||||
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-o=/tmp/outfile" };
|
||||
auto constexpr ExpectedN = 1;
|
||||
auto const expected_c = std::array<int, ExpectedN>{ 'o' };
|
||||
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
|
||||
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
|
||||
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-o=/tmp/outfile" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'o' };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, longWithArgAfterEq)
|
||||
TEST_F(GetoptTest, longWithRequiredArgAfterEq)
|
||||
{
|
||||
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--outfile=/tmp/outfile" };
|
||||
auto constexpr ExpectedN = 1;
|
||||
auto const expected_c = std::array<int, ExpectedN>{ 'o' };
|
||||
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
|
||||
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
|
||||
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--outfile=/tmp/outfile" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'o' };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "/tmp/outfile" };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, unknownOption)
|
||||
{
|
||||
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-z" };
|
||||
auto constexpr ExpectedN = 1;
|
||||
auto const expected_c = std::array<int, ExpectedN>{ TR_OPT_UNK };
|
||||
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "-z" };
|
||||
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
|
||||
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-z" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ TR_OPT_UNK };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "-z" };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, missingArg)
|
||||
TEST_F(GetoptTest, missingArgEnd)
|
||||
{
|
||||
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-o" };
|
||||
auto constexpr ExpectedN = 1;
|
||||
auto const expected_c = std::array<int, ExpectedN>{ TR_OPT_ERR };
|
||||
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ nullptr };
|
||||
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
|
||||
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-o" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ TR_OPT_ERR };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, missingArgMiddle)
|
||||
{
|
||||
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "-o", "-p" };
|
||||
static auto constexpr ExpectedN = 2;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ TR_OPT_ERR, 'p' };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr, nullptr };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, lotsOfOptions)
|
||||
{
|
||||
auto const args = std::array<char const*, 6>{
|
||||
static auto constexpr Args = std::array<char const*, 6>{
|
||||
"/some/path/tr-getopt-test", "--piecesize=4", "-c", "hello world", "-p", "--tracker=foo"
|
||||
};
|
||||
auto constexpr ExpectedN = 4;
|
||||
auto const expected_c = std::array<int, ExpectedN>{ 's', 'c', 'p', 't' };
|
||||
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ "4", "hello world", nullptr, "foo" };
|
||||
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
|
||||
static auto constexpr ExpectedN = 4;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 's', 'c', 'p', 't' };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "4", "hello world", nullptr, "foo" };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, matchLongerKey)
|
||||
{
|
||||
// confirm that this resolves to 'q' and not 'p'
|
||||
auto const args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-pk" };
|
||||
auto constexpr ExpectedN = 1;
|
||||
auto const expected_c = std::array<int, ExpectedN>{ 'q' };
|
||||
auto const expected_opt_arg = std::array<char const*, ExpectedN>{ nullptr };
|
||||
runTest(args.size(), args.data(), ExpectedN, expected_c.data(), expected_opt_arg.data());
|
||||
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-pk" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 'q' };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, shortWithOptionalArg)
|
||||
{
|
||||
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "-seq", "12" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "12" };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, longWithOptionalArg)
|
||||
{
|
||||
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "--sequential-download", "12" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "12" };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, shortWithOptionalArgAfterEq)
|
||||
{
|
||||
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-seq=12" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "12" };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, longWithOptionalArgAfterEq)
|
||||
{
|
||||
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--sequential-download=12" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ "12" };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, shortWithoutOptionalArgEnd)
|
||||
{
|
||||
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "-seq" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, longWithoutOptionalArgEnd)
|
||||
{
|
||||
static auto constexpr Args = std::array<char const*, 2>{ "/some/path/tr-getopt-test", "--sequential-download" };
|
||||
static auto constexpr ExpectedN = 1;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994 };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, shortWithoutOptionalArgMiddle)
|
||||
{
|
||||
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "-seq", "-p" };
|
||||
static auto constexpr ExpectedN = 2;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994, 'p' };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr, nullptr };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
TEST_F(GetoptTest, longWithoutOptionalArgMiddle)
|
||||
{
|
||||
static auto constexpr Args = std::array<char const*, 3>{ "/some/path/tr-getopt-test", "--sequential-download", "-p" };
|
||||
static auto constexpr ExpectedN = 2;
|
||||
static auto constexpr ExpectedC = std::array<int, ExpectedN>{ 994, 'p' };
|
||||
static auto constexpr ExpectedOptArg = std::array<char const*, ExpectedN>{ nullptr, nullptr };
|
||||
runTest(Args.size(), Args.data(), ExpectedN, ExpectedC.data(), ExpectedOptArg.data());
|
||||
}
|
||||
|
||||
@@ -42,19 +42,24 @@ char constexpr Usage[] = "Usage: transmission-create [options] <file|directory>"
|
||||
|
||||
uint32_t constexpr KiB = 1024;
|
||||
|
||||
auto constexpr Options = std::array<tr_option, 10>{
|
||||
{ { 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", false, nullptr },
|
||||
{ 'r', "source", "Set the source for private trackers", "r", true, "<source>" },
|
||||
{ 'o', "outfile", "Save the generated .torrent to this filename", "o", true, "<file>" },
|
||||
{ 's', "piecesize", "Set the piece size in KiB, overriding the preferred default", "s", true, "<KiB>" },
|
||||
{ 'c', "comment", "Add a comment", "c", true, "<comment>" },
|
||||
{ 't', "tracker", "Add a tracker's announce URL", "t", true, "<url>" },
|
||||
{ 'w', "webseed", "Add a webseed URL", "w", true, "<url>" },
|
||||
{ 'x', "anonymize", R"(Omit "Creation date" and "Created by" info)", nullptr, false, nullptr },
|
||||
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
|
||||
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
|
||||
};
|
||||
using Arg = tr_option::Arg;
|
||||
auto constexpr Options = std::array<tr_option, 10>{ {
|
||||
{ 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", Arg::None, nullptr },
|
||||
{ 'r', "source", "Set the source for private trackers", "r", Arg::Required, "<source>" },
|
||||
{ 'o', "outfile", "Save the generated .torrent to this filename", "o", Arg::Required, "<file>" },
|
||||
{ 's', "piecesize", "Set the piece size in KiB, overriding the preferred default", "s", Arg::Required, "<KiB>" },
|
||||
{ 'c', "comment", "Add a comment", "c", Arg::Required, "<comment>" },
|
||||
{ 't', "tracker", "Add a tracker's announce URL", "t", Arg::Required, "<url>" },
|
||||
{ 'w', "webseed", "Add a webseed URL", "w", Arg::Required, "<url>" },
|
||||
{ 'x', "anonymize", R"(Omit "Creation date" and "Created by" info)", nullptr, Arg::None, nullptr },
|
||||
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
|
||||
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
|
||||
} };
|
||||
static_assert(Options[std::size(Options) - 2].val != 0);
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
struct app_options
|
||||
{
|
||||
tr_announce_list trackers;
|
||||
|
||||
@@ -35,15 +35,20 @@ struct app_options
|
||||
bool show_version = false;
|
||||
};
|
||||
|
||||
auto constexpr Options = std::array<tr_option, 6>{
|
||||
{ { 'a', "add", "Add a tracker's announce URL", "a", true, "<url>" },
|
||||
{ 'd', "delete", "Delete a tracker's announce URL", "d", true, "<url>" },
|
||||
{ 'r', "replace", "Search and replace a substring in the announce URLs", "r", true, "<old> <new>" },
|
||||
{ 's', "source", "Set the source", "s", true, "<source>" },
|
||||
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
|
||||
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
|
||||
};
|
||||
using Arg = tr_option::Arg;
|
||||
auto constexpr Options = std::array<tr_option, 6>{ {
|
||||
{ 'a', "add", "Add a tracker's announce URL", "a", Arg::Required, "<url>" },
|
||||
{ 'd', "delete", "Delete a tracker's announce URL", "d", Arg::Required, "<url>" },
|
||||
{ 'r', "replace", "Search and replace a substring in the announce URLs", "r", Arg::Required, "<old> <new>" },
|
||||
{ 's', "source", "Set the source", "s", Arg::Required, "<source>" },
|
||||
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
|
||||
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
|
||||
} };
|
||||
static_assert(Options[std::size(Options) - 2].val != 0);
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
int parseCommandLine(app_options& opts, int argc, char const* const* argv)
|
||||
{
|
||||
int c;
|
||||
|
||||
320
utils/remote.cc
320
utils/remote.cc
@@ -211,166 +211,170 @@ enum
|
||||
|
||||
// --- Command-Line Arguments
|
||||
|
||||
auto constexpr Options = std::array<tr_option, 105>{
|
||||
{ { '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 },
|
||||
{ 972, "alt-speed-downlimit", "max alternate download speed (in " SPEED_K_STR ")", "asd", true, "<speed>" },
|
||||
{ 973, "alt-speed-uplimit", "max alternate upload speed (in " SPEED_K_STR ")", "asu", true, "<speed>" },
|
||||
{ 974, "alt-speed-scheduler", "Use the scheduled on/off times", "asc", false, nullptr },
|
||||
{ 975, "no-alt-speed-scheduler", "Don't use the scheduled on/off times", "ASC", false, nullptr },
|
||||
{ 976, "alt-speed-time-begin", "Time to start using the alt speed limits (in hhmm)", nullptr, true, "<time>" },
|
||||
{ 977, "alt-speed-time-end", "Time to stop using the alt speed limits (in hhmm)", nullptr, true, "<time>" },
|
||||
{ 978, "alt-speed-days", "Numbers for any/all days of the week - eg. \"1-7\"", nullptr, true, "<days>" },
|
||||
{ 963, "blocklist-update", "Blocklist update", nullptr, false, nullptr },
|
||||
{ 'c', "incomplete-dir", "Where to store new torrents until they're complete", "c", true, "<dir>" },
|
||||
{ 'C', "no-incomplete-dir", "Don't store incomplete torrents in a different location", "C", false, nullptr },
|
||||
{ 'b', "debug", "Print debugging information", "b", false, nullptr },
|
||||
{ 730, "bandwidth-group", "Set the current torrents' bandwidth group", "bwg", true, "<group>" },
|
||||
{ 731, "no-bandwidth-group", "Reset the current torrents' bandwidth group", "nwg", false, nullptr },
|
||||
{ 732, "list-groups", "Show bandwidth groups with their parameters", "lg", false, nullptr },
|
||||
{ 'd',
|
||||
"downlimit",
|
||||
"Set the max download speed in " SPEED_K_STR " for the current torrent(s) or globally",
|
||||
"d",
|
||||
true,
|
||||
"<speed>" },
|
||||
{ 'D', "no-downlimit", "Disable max download speed for the current torrent(s) or globally", "D", false, nullptr },
|
||||
{ 'e', "cache", "Set the maximum size of the session's memory cache (in " MEM_M_STR ")", "e", true, "<size>" },
|
||||
{ 910, "encryption-required", "Encrypt all peer connections", "er", false, nullptr },
|
||||
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", false, nullptr },
|
||||
{ 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 },
|
||||
{ 943, "info-trackers", "List the current torrent(s)' trackers", "it", false, nullptr },
|
||||
{ 'j', "json", "Return RPC response as a JSON string", "j", false, nullptr },
|
||||
{ 920, "session-info", "Show the session's details", "si", false, nullptr },
|
||||
{ 921, "session-stats", "Show the session's statistics", "st", false, nullptr },
|
||||
{ 'l', "list", "List all torrents", "l", false, nullptr },
|
||||
{ 'L', "labels", "Set the current torrents' labels", "L", true, "<label[,label...]>" },
|
||||
{ 960, "move", "Move current torrent's data to a new folder", nullptr, true, "<path>" },
|
||||
{ 968, "unix-socket", "Use a Unix domain socket", nullptr, true, "<path>" },
|
||||
{ 961, "find", "Tell Transmission where to find a torrent's data", nullptr, true, "<path>" },
|
||||
{ 964, "rename", "Rename torrents root folder or a file", nullptr, true, "<name>" },
|
||||
{ 965, "path", "Provide path for rename functions", nullptr, true, "<path>" },
|
||||
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", false, nullptr },
|
||||
{ 'M', "no-portmap", "Disable portmapping", "M", false, nullptr },
|
||||
{ 'n', "auth", "Set username and password", "n", true, "<user:pw>" },
|
||||
{ 810, "authenv", "Set authentication info from the TR_AUTH environment variable (user:pw)", "ne", false, nullptr },
|
||||
{ 'N', "netrc", "Set authentication info from a .netrc file", "N", true, "<file>" },
|
||||
{ 820, "ssl", "Use SSL when talking to daemon", nullptr, false, nullptr },
|
||||
{ 'o', "dht", "Enable distributed hash tables (DHT)", "o", false, nullptr },
|
||||
{ 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", false, nullptr },
|
||||
{ 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", true, "<port>" },
|
||||
{ 962, "port-test", "Port testing", "pt", false, nullptr },
|
||||
{ 'P', "random-port", "Random port for incoming peers", "P", false, nullptr },
|
||||
{ 900, "priority-high", "Try to download these file(s) first", "ph", true, "<files>" },
|
||||
{ 901, "priority-normal", "Try to download these file(s) normally", "pn", true, "<files>" },
|
||||
{ 902, "priority-low", "Try to download these file(s) last", "pl", true, "<files>" },
|
||||
{ 700, "bandwidth-high", "Give this torrent first chance at available bandwidth", "Bh", false, nullptr },
|
||||
{ 701, "bandwidth-normal", "Give this torrent bandwidth left over by high priority torrents", "Bn", false, nullptr },
|
||||
{ 702,
|
||||
"bandwidth-low",
|
||||
"Give this torrent bandwidth left over by high and normal priority torrents",
|
||||
"Bl",
|
||||
false,
|
||||
nullptr },
|
||||
{ 600, "reannounce", "Reannounce the current torrent(s)", nullptr, false, nullptr },
|
||||
{ 'r', "remove", "Remove the current torrent(s)", "r", false, nullptr },
|
||||
{ 930, "peers", "Set the maximum number of peers for the current torrent(s) or globally", "pr", true, "<max>" },
|
||||
{ 840, "remove-and-delete", "Remove the current torrent(s) and delete local data", "rad", false, nullptr },
|
||||
{ 800, "torrent-done-script", "A script to run when a torrent finishes downloading", nullptr, true, "<file>" },
|
||||
{ 801, "no-torrent-done-script", "Don't run the done-downloading script", nullptr, false, nullptr },
|
||||
{ 802, "torrent-done-seeding-script", "A script to run when a torrent finishes seeding", nullptr, true, "<file>" },
|
||||
{ 803, "no-torrent-done-seeding-script", "Don't run the done-seeding script", nullptr, false, nullptr },
|
||||
{ 950, "seedratio", "Let the current torrent(s) seed until a specific ratio", "sr", true, "ratio" },
|
||||
{ 951, "seedratio-default", "Let the current torrent(s) use the global seedratio settings", "srd", false, nullptr },
|
||||
{ 952, "no-seedratio", "Let the current torrent(s) seed regardless of ratio", "SR", false, nullptr },
|
||||
{ 953,
|
||||
"global-seedratio",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio",
|
||||
"gsr",
|
||||
true,
|
||||
"ratio" },
|
||||
{ 954,
|
||||
"no-global-seedratio",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio",
|
||||
"GSR",
|
||||
false,
|
||||
nullptr },
|
||||
{ 955,
|
||||
"idle-seeding-limit",
|
||||
"Let the current torrent(s) seed until a specific amount idle time",
|
||||
"isl",
|
||||
true,
|
||||
"<minutes>" },
|
||||
{ 956,
|
||||
"default-idle-seeding-limit",
|
||||
"Let the current torrent(s) use the default idle seeding settings",
|
||||
"isld",
|
||||
false,
|
||||
nullptr },
|
||||
{ 957, "no-idle-seeding-limit", "Let the current torrent(s) seed regardless of idle time", "ISL", false, nullptr },
|
||||
{ 958,
|
||||
"global-idle-seeding-limit",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed until a specific amount of idle time",
|
||||
"gisl",
|
||||
true,
|
||||
"<minutes>" },
|
||||
{ 959,
|
||||
"no-global-idle-seeding-limit",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed regardless of idle time",
|
||||
"GISL",
|
||||
false,
|
||||
nullptr },
|
||||
{ 710, "tracker-add", "Add a tracker to a torrent", "td", true, "<tracker>" },
|
||||
{ 712, "tracker-remove", "Remove a tracker from a torrent", "tr", true, "<trackerId>" },
|
||||
{ 's', "start", "Start the current torrent(s)", "s", false, nullptr },
|
||||
{ 'S', "stop", "Stop the current torrent(s)", "S", false, nullptr },
|
||||
{ 't', "torrent", "Set the current torrent(s)", "t", true, "<torrent>" },
|
||||
{ 990, "start-paused", "Start added torrents paused", nullptr, false, nullptr },
|
||||
{ 991, "no-start-paused", "Start added torrents unpaused", nullptr, false, nullptr },
|
||||
{ 992, "trash-torrent", "Delete torrents after adding", nullptr, false, nullptr },
|
||||
{ 993, "no-trash-torrent", "Do not delete torrents after adding", nullptr, false, nullptr },
|
||||
{ 994, "sequential-download", "Download the torrent sequentially", "seq", false, nullptr },
|
||||
{ 995, "no-sequential-download", "Download the torrent normally", "SEQ", false, nullptr },
|
||||
{ 984, "honor-session", "Make the current torrent(s) honor the session limits", "hl", false, nullptr },
|
||||
{ 985, "no-honor-session", "Make the current torrent(s) not honor the session limits", "HL", false, nullptr },
|
||||
{ 'u',
|
||||
"uplimit",
|
||||
"Set the max upload speed in " SPEED_K_STR " for the current torrent(s) or globally",
|
||||
"u",
|
||||
true,
|
||||
"<speed>" },
|
||||
{ 'U', "no-uplimit", "Disable max upload speed for the current torrent(s) or globally", "U", false, nullptr },
|
||||
{ 830, "utp", "Enable µTP for peer connections", nullptr, false, nullptr },
|
||||
{ 831, "no-utp", "Disable µTP for peer connections", nullptr, false, nullptr },
|
||||
{ 'v', "verify", "Verify the current torrent(s)", "v", false, nullptr },
|
||||
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
|
||||
{ 'w',
|
||||
"download-dir",
|
||||
"When used in conjunction with --add, set the new torrent's download folder. "
|
||||
"Otherwise, set the default download folder",
|
||||
"w",
|
||||
true,
|
||||
"<path>" },
|
||||
{ 'x', "pex", "Enable peer exchange (PEX)", "x", false, nullptr },
|
||||
{ 'X', "no-pex", "Disable peer exchange (PEX)", "X", false, nullptr },
|
||||
{ 'y', "lpd", "Enable local peer discovery (LPD)", "y", false, nullptr },
|
||||
{ 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", false, nullptr },
|
||||
{ 941, "peer-info", "List the current torrent(s)' peers", "pi", false, nullptr },
|
||||
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
|
||||
};
|
||||
using Arg = tr_option::Arg;
|
||||
auto constexpr Options = std::array<tr_option, 105>{ {
|
||||
{ 'a', "add", "Add torrent files by filename or URL", "a", Arg::None, nullptr },
|
||||
{ 970, "alt-speed", "Use the alternate Limits", "as", Arg::None, nullptr },
|
||||
{ 971, "no-alt-speed", "Don't use the alternate Limits", "AS", Arg::None, nullptr },
|
||||
{ 972, "alt-speed-downlimit", "max alternate download speed (in " SPEED_K_STR ")", "asd", Arg::Required, "<speed>" },
|
||||
{ 973, "alt-speed-uplimit", "max alternate upload speed (in " SPEED_K_STR ")", "asu", Arg::Required, "<speed>" },
|
||||
{ 974, "alt-speed-scheduler", "Use the scheduled on/off times", "asc", Arg::None, nullptr },
|
||||
{ 975, "no-alt-speed-scheduler", "Don't use the scheduled on/off times", "ASC", Arg::None, nullptr },
|
||||
{ 976, "alt-speed-time-begin", "Time to start using the alt speed limits (in hhmm)", nullptr, Arg::Required, "<time>" },
|
||||
{ 977, "alt-speed-time-end", "Time to stop using the alt speed limits (in hhmm)", nullptr, Arg::Required, "<time>" },
|
||||
{ 978, "alt-speed-days", "Numbers for any/all days of the week - eg. \"1-7\"", nullptr, Arg::Required, "<days>" },
|
||||
{ 963, "blocklist-update", "Blocklist update", nullptr, Arg::None, nullptr },
|
||||
{ 'c', "incomplete-dir", "Where to store new torrents until they're complete", "c", Arg::Required, "<dir>" },
|
||||
{ 'C', "no-incomplete-dir", "Don't store incomplete torrents in a different location", "C", Arg::None, nullptr },
|
||||
{ 'b', "debug", "Print debugging information", "b", Arg::None, nullptr },
|
||||
{ 730, "bandwidth-group", "Set the current torrents' bandwidth group", "bwg", Arg::Required, "<group>" },
|
||||
{ 731, "no-bandwidth-group", "Reset the current torrents' bandwidth group", "nwg", Arg::None, nullptr },
|
||||
{ 732, "list-groups", "Show bandwidth groups with their parameters", "lg", Arg::None, nullptr },
|
||||
{ 'd',
|
||||
"downlimit",
|
||||
"Set the max download speed in " SPEED_K_STR " for the current torrent(s) or globally",
|
||||
"d",
|
||||
Arg::Required,
|
||||
"<speed>" },
|
||||
{ 'D', "no-downlimit", "Disable max download speed for the current torrent(s) or globally", "D", Arg::None, nullptr },
|
||||
{ 'e', "cache", "Set the maximum size of the session's memory cache (in " MEM_M_STR ")", "e", Arg::Required, "<size>" },
|
||||
{ 910, "encryption-required", "Encrypt all peer connections", "er", Arg::None, nullptr },
|
||||
{ 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", Arg::None, nullptr },
|
||||
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", Arg::None, nullptr },
|
||||
{ 850, "exit", "Tell the transmission session to shut down", nullptr, Arg::None, nullptr },
|
||||
{ 940, "files", "List the current torrent(s)' files", "f", Arg::None, nullptr },
|
||||
{ 'F', "filter", "Filter the current torrent(s)", "F", Arg::Required, "criterion" },
|
||||
{ 'g', "get", "Mark files for download", "g", Arg::Required, "<files>" },
|
||||
{ 'G', "no-get", "Mark files for not downloading", "G", Arg::Required, "<files>" },
|
||||
{ 'i', "info", "Show the current torrent(s)' details", "i", Arg::None, nullptr },
|
||||
{ 944, "print-ids", "Print the current torrent(s)' ids", "ids", Arg::None, nullptr },
|
||||
{ 940, "info-files", "List the current torrent(s)' files", "if", Arg::None, nullptr },
|
||||
{ 941, "info-peers", "List the current torrent(s)' peers", "ip", Arg::None, nullptr },
|
||||
{ 942, "info-pieces", "List the current torrent(s)' pieces", "ic", Arg::None, nullptr },
|
||||
{ 943, "info-trackers", "List the current torrent(s)' trackers", "it", Arg::None, nullptr },
|
||||
{ 'j', "json", "Return RPC response as a JSON string", "j", Arg::None, nullptr },
|
||||
{ 920, "session-info", "Show the session's details", "si", Arg::None, nullptr },
|
||||
{ 921, "session-stats", "Show the session's statistics", "st", Arg::None, nullptr },
|
||||
{ 'l', "list", "List all torrents", "l", Arg::None, nullptr },
|
||||
{ 'L', "labels", "Set the current torrents' labels", "L", Arg::Required, "<label[,label...]>" },
|
||||
{ 960, "move", "Move current torrent's data to a new folder", nullptr, Arg::Required, "<path>" },
|
||||
{ 968, "unix-socket", "Use a Unix domain socket", nullptr, Arg::Required, "<path>" },
|
||||
{ 961, "find", "Tell Transmission where to find a torrent's data", nullptr, Arg::Required, "<path>" },
|
||||
{ 964, "rename", "Rename torrents root folder or a file", nullptr, Arg::Required, "<name>" },
|
||||
{ 965, "path", "Provide path for rename functions", nullptr, Arg::Required, "<path>" },
|
||||
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", Arg::None, nullptr },
|
||||
{ 'M', "no-portmap", "Disable portmapping", "M", Arg::None, nullptr },
|
||||
{ 'n', "auth", "Set username and password", "n", Arg::Required, "<user:pw>" },
|
||||
{ 810, "authenv", "Set authentication info from the TR_AUTH environment variable (user:pw)", "ne", Arg::None, nullptr },
|
||||
{ 'N', "netrc", "Set authentication info from a .netrc file", "N", Arg::Required, "<file>" },
|
||||
{ 820, "ssl", "Use SSL when talking to daemon", nullptr, Arg::None, nullptr },
|
||||
{ 'o', "dht", "Enable distributed hash tables (DHT)", "o", Arg::None, nullptr },
|
||||
{ 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", Arg::None, nullptr },
|
||||
{ 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", Arg::Required, "<port>" },
|
||||
{ 962, "port-test", "Port testing", "pt", Arg::None, nullptr },
|
||||
{ 'P', "random-port", "Random port for incoming peers", "P", Arg::None, nullptr },
|
||||
{ 900, "priority-high", "Try to download these file(s) first", "ph", Arg::Required, "<files>" },
|
||||
{ 901, "priority-normal", "Try to download these file(s) normally", "pn", Arg::Required, "<files>" },
|
||||
{ 902, "priority-low", "Try to download these file(s) last", "pl", Arg::Required, "<files>" },
|
||||
{ 700, "bandwidth-high", "Give this torrent first chance at available bandwidth", "Bh", Arg::None, nullptr },
|
||||
{ 701, "bandwidth-normal", "Give this torrent bandwidth left over by high priority torrents", "Bn", Arg::None, nullptr },
|
||||
{ 702,
|
||||
"bandwidth-low",
|
||||
"Give this torrent bandwidth left over by high and normal priority torrents",
|
||||
"Bl",
|
||||
Arg::None,
|
||||
nullptr },
|
||||
{ 600, "reannounce", "Reannounce the current torrent(s)", nullptr, Arg::None, nullptr },
|
||||
{ 'r', "remove", "Remove the current torrent(s)", "r", Arg::None, nullptr },
|
||||
{ 930, "peers", "Set the maximum number of peers for the current torrent(s) or globally", "pr", Arg::Required, "<max>" },
|
||||
{ 840, "remove-and-delete", "Remove the current torrent(s) and delete local data", "rad", Arg::None, nullptr },
|
||||
{ 800, "torrent-done-script", "A script to run when a torrent finishes downloading", nullptr, Arg::Required, "<file>" },
|
||||
{ 801, "no-torrent-done-script", "Don't run the done-downloading script", nullptr, Arg::None, nullptr },
|
||||
{ 802, "torrent-done-seeding-script", "A script to run when a torrent finishes seeding", nullptr, Arg::Required, "<file>" },
|
||||
{ 803, "no-torrent-done-seeding-script", "Don't run the done-seeding script", nullptr, Arg::None, nullptr },
|
||||
{ 950, "seedratio", "Let the current torrent(s) seed until a specific ratio", "sr", Arg::Required, "ratio" },
|
||||
{ 951, "seedratio-default", "Let the current torrent(s) use the global seedratio settings", "srd", Arg::None, nullptr },
|
||||
{ 952, "no-seedratio", "Let the current torrent(s) seed regardless of ratio", "SR", Arg::None, nullptr },
|
||||
{ 953,
|
||||
"global-seedratio",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio",
|
||||
"gsr",
|
||||
Arg::Required,
|
||||
"ratio" },
|
||||
{ 954,
|
||||
"no-global-seedratio",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio",
|
||||
"GSR",
|
||||
Arg::None,
|
||||
nullptr },
|
||||
{ 955,
|
||||
"idle-seeding-limit",
|
||||
"Let the current torrent(s) seed until a specific amount idle time",
|
||||
"isl",
|
||||
Arg::Required,
|
||||
"<minutes>" },
|
||||
{ 956,
|
||||
"default-idle-seeding-limit",
|
||||
"Let the current torrent(s) use the default idle seeding settings",
|
||||
"isld",
|
||||
Arg::None,
|
||||
nullptr },
|
||||
{ 957, "no-idle-seeding-limit", "Let the current torrent(s) seed regardless of idle time", "ISL", Arg::None, nullptr },
|
||||
{ 958,
|
||||
"global-idle-seeding-limit",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed until a specific amount of idle time",
|
||||
"gisl",
|
||||
Arg::Required,
|
||||
"<minutes>" },
|
||||
{ 959,
|
||||
"no-global-idle-seeding-limit",
|
||||
"All torrents, unless overridden by a per-torrent setting, should seed regardless of idle time",
|
||||
"GISL",
|
||||
Arg::None,
|
||||
nullptr },
|
||||
{ 710, "tracker-add", "Add a tracker to a torrent", "td", Arg::Required, "<tracker>" },
|
||||
{ 712, "tracker-remove", "Remove a tracker from a torrent", "tr", Arg::Required, "<trackerId>" },
|
||||
{ 's', "start", "Start the current torrent(s)", "s", Arg::None, nullptr },
|
||||
{ 'S', "stop", "Stop the current torrent(s)", "S", Arg::None, nullptr },
|
||||
{ 't', "torrent", "Set the current torrent(s)", "t", Arg::Required, "<torrent>" },
|
||||
{ 990, "start-paused", "Start added torrents paused", nullptr, Arg::None, nullptr },
|
||||
{ 991, "no-start-paused", "Start added torrents unpaused", nullptr, Arg::None, nullptr },
|
||||
{ 992, "trash-torrent", "Delete torrents after adding", nullptr, Arg::None, nullptr },
|
||||
{ 993, "no-trash-torrent", "Do not delete torrents after adding", nullptr, Arg::None, nullptr },
|
||||
{ 994, "sequential-download", "Download the torrent sequentially", "seq", Arg::None, nullptr },
|
||||
{ 995, "no-sequential-download", "Download the torrent normally", "SEQ", Arg::None, nullptr },
|
||||
{ 984, "honor-session", "Make the current torrent(s) honor the session limits", "hl", Arg::None, nullptr },
|
||||
{ 985, "no-honor-session", "Make the current torrent(s) not honor the session limits", "HL", Arg::None, nullptr },
|
||||
{ 'u',
|
||||
"uplimit",
|
||||
"Set the max upload speed in " SPEED_K_STR " for the current torrent(s) or globally",
|
||||
"u",
|
||||
Arg::Required,
|
||||
"<speed>" },
|
||||
{ 'U', "no-uplimit", "Disable max upload speed for the current torrent(s) or globally", "U", Arg::None, nullptr },
|
||||
{ 830, "utp", "Enable µTP for peer connections", nullptr, Arg::None, nullptr },
|
||||
{ 831, "no-utp", "Disable µTP for peer connections", nullptr, Arg::None, nullptr },
|
||||
{ 'v', "verify", "Verify the current torrent(s)", "v", Arg::None, nullptr },
|
||||
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
|
||||
{ 'w',
|
||||
"download-dir",
|
||||
"When used in conjunction with --add, set the new torrent's download folder. "
|
||||
"Otherwise, set the default download folder",
|
||||
"w",
|
||||
Arg::Required,
|
||||
"<path>" },
|
||||
{ 'x', "pex", "Enable peer exchange (PEX)", "x", Arg::None, nullptr },
|
||||
{ 'X', "no-pex", "Disable peer exchange (PEX)", "X", Arg::None, nullptr },
|
||||
{ 'y', "lpd", "Enable local peer discovery (LPD)", "y", Arg::None, nullptr },
|
||||
{ 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", Arg::None, nullptr },
|
||||
{ 941, "peer-info", "List the current torrent(s)' peers", "pi", Arg::None, nullptr },
|
||||
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
|
||||
} };
|
||||
static_assert(Options[std::size(Options) - 2].val != 0);
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
void show_usage()
|
||||
{
|
||||
tr_getopt_usage(MyName, Usage, std::data(Options));
|
||||
|
||||
@@ -41,29 +41,33 @@ using namespace libtransmission::Values;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
auto constexpr TimeoutSecs = std::chrono::seconds{ 30 };
|
||||
|
||||
char constexpr MyName[] = "transmission-show";
|
||||
char constexpr Usage[] = "Usage: transmission-show [options] <torrent-file>";
|
||||
|
||||
auto options = std::array<tr_option, 14>{
|
||||
{ { 'd', "header", "Show only header section", "d", false, nullptr },
|
||||
{ 'i', "info", "Show only info section", "i", false, nullptr },
|
||||
{ 't', "trackers", "Show only trackers section", "t", false, nullptr },
|
||||
{ 'f', "files", "Show only file list", "f", false, nullptr },
|
||||
{ 'D', "no-header", "Do not show header section", "D", false, nullptr },
|
||||
{ 'I', "no-info", "Do not show info section", "I", false, nullptr },
|
||||
{ 'T', "no-trackers", "Do not show trackers section", "T", false, nullptr },
|
||||
{ 'F', "no-files", "Do not show files section", "F", false, nullptr },
|
||||
{ 'b', "bytes", "Show file sizes in bytes", "b", false, nullptr },
|
||||
{ 'm', "magnet", "Give a magnet link for the specified torrent", "m", false, nullptr },
|
||||
{ 's', "scrape", "Ask the torrent's trackers how many peers are in the torrent's swarm", "s", false, nullptr },
|
||||
{ 'u', "unsorted", "Do not sort files by name", "u", false, nullptr },
|
||||
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
|
||||
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
|
||||
};
|
||||
using Arg = tr_option::Arg;
|
||||
auto constexpr Options = std::array<tr_option, 14>{ {
|
||||
{ 'd', "header", "Show only header section", "d", Arg::None, nullptr },
|
||||
{ 'i', "info", "Show only info section", "i", Arg::None, nullptr },
|
||||
{ 't', "trackers", "Show only trackers section", "t", Arg::None, nullptr },
|
||||
{ 'f', "files", "Show only file list", "f", Arg::None, nullptr },
|
||||
{ 'D', "no-header", "Do not show header section", "D", Arg::None, nullptr },
|
||||
{ 'I', "no-info", "Do not show info section", "I", Arg::None, nullptr },
|
||||
{ 'T', "no-trackers", "Do not show trackers section", "T", Arg::None, nullptr },
|
||||
{ 'F', "no-files", "Do not show files section", "F", Arg::None, nullptr },
|
||||
{ 'b', "bytes", "Show file sizes in bytes", "b", Arg::None, nullptr },
|
||||
{ 'm', "magnet", "Give a magnet link for the specified torrent", "m", Arg::None, nullptr },
|
||||
{ 's', "scrape", "Ask the torrent's trackers how many peers are in the torrent's swarm", "s", Arg::None, nullptr },
|
||||
{ 'u', "unsorted", "Do not sort files by name", "u", Arg::None, nullptr },
|
||||
{ 'V', "version", "Show version number and exit", "V", Arg::None, nullptr },
|
||||
{ 0, nullptr, nullptr, nullptr, Arg::None, nullptr },
|
||||
} };
|
||||
static_assert(Options[std::size(Options) - 2].val != 0);
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
struct app_opts
|
||||
{
|
||||
std::string_view filename;
|
||||
@@ -83,7 +87,7 @@ int parseCommandLine(app_opts& opts, int argc, char const* const* argv)
|
||||
int c;
|
||||
char const* optarg;
|
||||
|
||||
while ((c = tr_getopt(Usage, argc, argv, std::data(options), &optarg)) != TR_OPT_DONE)
|
||||
while ((c = tr_getopt(Usage, argc, argv, std::data(Options), &optarg)) != TR_OPT_DONE)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
@@ -426,7 +430,7 @@ int tr_main(int argc, char* argv[])
|
||||
if (std::empty(opts.filename))
|
||||
{
|
||||
fmt::print(stderr, "ERROR: No torrent file specified.\n");
|
||||
tr_getopt_usage(MyName, Usage, std::data(options));
|
||||
tr_getopt_usage(MyName, Usage, std::data(Options));
|
||||
fmt::print(stderr, "\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user