transmission/cli/cli.cc

467 lines
13 KiB
C++
Raw Normal View History

// This file Copyright © Transmission authors and contributors.
// It may be used under the MIT (SPDX: MIT) license.
// License text can be found in the licenses/ folder.
2006-07-16 19:39:23 +00:00
2021-12-17 20:48:02 +00:00
#include <array>
#include <cstdio> /* fprintf () */
#include <cstdlib> /* atoi () */
#include <string>
#include <string_view>
#include <thread>
#include <signal.h>
#include <fmt/core.h>
#include <libtransmission/transmission.h>
#include <libtransmission/error.h>
#include <libtransmission/file.h>
2008-07-08 16:50:34 +00:00
#include <libtransmission/tr-getopt.h>
#include <libtransmission/utils.h> // _()
#include <libtransmission/values.h>
#include <libtransmission/variant.h>
#include <libtransmission/version.h>
#include <libtransmission/web-utils.h>
#include <libtransmission/web.h> // tr_sessionFetch()
2006-07-16 19:39:23 +00:00
using namespace std::chrono_literals;
using namespace libtransmission::Values;
#define SPEED_K_STR "kB/s"
static auto constexpr LineWidth = int{ 80 };
2021-12-17 20:48:02 +00:00
static char constexpr MyConfigName[] = "transmission";
static char constexpr MyReadableName[] = "transmission-cli";
static char constexpr Usage
[] = "A fast and easy BitTorrent client\n"
"\n"
"Usage: transmission-cli [options] <file|url|magnet>";
static bool showVersion = false;
static bool verify = false;
static sig_atomic_t gotsig = false;
static sig_atomic_t manualUpdate = false;
static char const* torrentPath = nullptr;
2023-04-14 16:47:54 +00:00
static auto constexpr Options = std::array<tr_option, 20>{
2021-12-17 20:48:02 +00:00
{ { '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>" },
2021-12-17 20:48:02 +00:00
{ '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>" },
2023-04-14 16:47:54 +00:00
{ 500, "sequential-download", "Download pieces sequentially", "seq", false, nullptr },
2021-12-17 20:48:02 +00:00
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
};
static int parseCommandLine(tr_variant*, int argc, char const** argv);
static void sigHandler(int signal);
2006-07-16 19:39:23 +00:00
static std::string tr_strlratio(double ratio)
{
if (static_cast<int>(ratio) == TR_RATIO_NA)
{
return _("None");
}
if (static_cast<int>(ratio) == TR_RATIO_INF)
{
return _("Inf");
}
if (ratio < 10.0)
{
return fmt::format(FMT_STRING("{:.2f}"), ratio);
}
if (ratio < 100.0)
{
return fmt::format(FMT_STRING("{:.1f}"), ratio);
}
return fmt::format(FMT_STRING("{:.0f}"), ratio);
}
static bool waitingOnWeb;
static void onTorrentFileDownloaded(tr_web::FetchResponse const& response)
{
auto* ctor = static_cast<tr_ctor*>(response.user_data);
tr_ctorSetMetainfo(ctor, std::data(response.body), std::size(response.body), nullptr);
waitingOnWeb = false;
}
static std::string getStatusStr(tr_stat const* st)
2008-07-09 01:48:29 +00:00
{
if (st->activity == TR_STATUS_CHECK_WAIT)
2008-07-09 01:48:29 +00:00
{
return "Waiting to verify local files";
2008-07-09 01:48:29 +00:00
}
if (st->activity == TR_STATUS_CHECK)
2008-07-09 01:48:29 +00:00
{
return fmt::format(
FMT_STRING("Verifying local files ({:.2f}%, {:.2f}% valid)"),
tr_truncd(100 * st->recheckProgress, 2),
tr_truncd(100 * st->percentDone, 2));
2008-07-09 01:48:29 +00:00
}
if (st->activity == TR_STATUS_DOWNLOAD)
{
return fmt::format(
FMT_STRING("Progress: {:.1f}%, dl from {:d} of {:d} peers ({:s}), ul to {:d} ({:s}) [{:s}]"),
tr_truncd(100 * st->percentDone, 1),
st->peersSendingToUs,
st->peersConnected,
Speed{ st->pieceDownloadSpeed_KBps, Speed::Units::KByps }.to_string(),
st->peersGettingFromUs,
Speed{ st->pieceUploadSpeed_KBps, Speed::Units::KByps }.to_string(),
tr_strlratio(st->ratio));
2008-07-09 01:48:29 +00:00
}
if (st->activity == TR_STATUS_SEED)
{
return fmt::format(
FMT_STRING("Seeding, uploading to {:d} of {:d} peer(s), {:s} [{:s}]"),
st->peersGettingFromUs,
st->peersConnected,
Speed{ st->pieceUploadSpeed_KBps, Speed::Units::KByps }.to_string(),
tr_strlratio(st->ratio));
2008-07-09 01:48:29 +00:00
}
return "";
2008-07-09 01:48:29 +00:00
}
static std::string getConfigDir(int argc, char const** argv)
{
int c;
char const* my_optarg;
int const ind = tr_optind;
2021-12-17 20:48:02 +00:00
while ((c = tr_getopt(Usage, argc, argv, std::data(Options), &my_optarg)) != TR_OPT_DONE)
{
if (c == 'g')
{
return my_optarg;
break;
}
}
tr_optind = ind;
return tr_getDefaultConfigDir(MyConfigName);
}
int tr_main(int argc, char* argv[])
2006-07-16 19:39:23 +00:00
{
auto const init_mgr = tr_lib_init();
tr_locale_set_global("");
2021-12-17 20:48:02 +00:00
printf("%s %s\n", MyReadableName, LONG_VERSION_STRING);
/* user needs to pass in at least one argument */
if (argc < 2)
{
2021-12-17 20:48:02 +00:00
tr_getopt_usage(MyReadableName, Usage, std::data(Options));
return EXIT_FAILURE;
}
/* load the defaults from config file + libtransmission defaults */
auto const config_dir = getConfigDir(argc, (char const**)argv);
auto settings = tr_sessionLoadSettings(config_dir.c_str(), MyConfigName);
/* the command line overrides defaults */
if (parseCommandLine(&settings, argc, (char const**)argv) != 0)
{
return EXIT_FAILURE;
}
if (showVersion)
{
return EXIT_SUCCESS;
}
/* Check the options for validity */
if (torrentPath == nullptr)
{
fprintf(stderr, "No torrent specified!\n");
return EXIT_FAILURE;
2006-07-16 19:39:23 +00:00
}
fix: some sonarcloud warnings (#2189) * fix: pass-large-object-by-reference-to-const warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0Gz8ze7dx8i2lkKEvq\&open\=AX0Gz8ze7dx8i2lkKEvq * fix: use-std-array-instead-of-C-array warning Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0L8qcgUnpKTxYiu5B-\&open\=AX0L8qcgUnpKTxYiu5B- * fix: extract-assignment-from-expression Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0L8qcgUnpKTxYiu5CH\&open\=AX0L8qcgUnpKTxYiu5CH * fix: move-include-directives-to-top Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0L8qlYUnpKTxYiu5CQ\&open\=AX0L8qlYUnpKTxYiu5CQ * fix: use-init-statement Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0L8qcgUnpKTxYiu5CK\&open\=AX0L8qcgUnpKTxYiu5CK * fix: use-init-statement Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0WPlmBwxvzFbF6whWh\&open\=AX0WPlmBwxvzFbF6whWh * fix: ensure-move-constructor-is-noexcept https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0WPlWzwxvzFbF6whWU\&open\=AX0WPlWzwxvzFbF6whWU * fix: use-init-statement Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0bZP20jI_aUyxGBxCD\&open\=AX0bZP20jI_aUyxGBxCD * fix: replace-redundant-type-with-auto Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0bZP0rjI_aUyxGBxCB\&open\=AX0bZP0rjI_aUyxGBxCB * fix: make-parameter-const Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0gkQqqPDVGqE4PY-io\&open\=AX0gkQqqPDVGqE4PY-io * fix: use-init-statement https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0rARJbfJ-O-YIDS9xM\&open\=AX0rARJbfJ-O-YIDS9xM * fix: use-init-statement Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0WPlmBwxvzFbF6whWh\&open\=AX0WPlmBwxvzFbF6whWh * fix: explicitly-delete-copy-ctor Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0gkQr6PDVGqE4PY-i4\&open\=AX0gkQr6PDVGqE4PY-i4 * fix: use-init-statement Xref: https://sonarcloud.io/project/issues\?id\=transmission_transmission\&issues\=AX0bZP9wjI_aUyxGBxCF\&open\=AX0bZP9wjI_aUyxGBxCF
2021-11-18 00:17:09 +00:00
if (auto sv = std::string_view{}; tr_variantDictFindStrView(&settings, TR_KEY_download_dir, &sv))
{
auto const sz_download_dir = std::string{ sv };
if (!tr_sys_path_exists(sz_download_dir))
{
if (auto error = tr_error{}; !tr_sys_dir_create(sz_download_dir, TR_SYS_DIR_CREATE_PARENTS, 0700, &error) && error)
{
auto const errmsg = fmt::format(
"Couldn't create '{path}': {error} ({error_code})",
fmt::arg("path", sz_download_dir),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code()));
fmt::print(stderr, "{:s}\n", errmsg);
return EXIT_FAILURE;
}
}
}
auto* const h = tr_sessionInit(config_dir.c_str(), false, settings);
auto* const ctor = tr_ctorNew(h);
tr_ctorSetPaused(ctor, TR_FORCE, false);
if (tr_sys_path_exists(torrentPath) ? tr_ctorSetMetainfoFromFile(ctor, torrentPath, nullptr) :
tr_ctorSetMetainfoFromMagnetLink(ctor, torrentPath, nullptr))
{
// all good
}
else if (tr_urlIsValid(torrentPath))
{
// fetch it
tr_sessionFetch(h, { torrentPath, onTorrentFileDownloaded, ctor });
waitingOnWeb = true;
while (waitingOnWeb)
{
std::this_thread::sleep_for(1s);
}
}
else
{
fprintf(stderr, "ERROR: Unrecognized torrent \"%s\".\n", torrentPath);
fprintf(stderr, " * If you're trying to create a torrent, use transmission-create.\n");
fprintf(stderr, " * If you're trying to see a torrent's info, use transmission-show.\n");
tr_sessionClose(h);
return EXIT_FAILURE;
}
tr_torrent* tor = tr_torrentNew(ctor, nullptr);
tr_ctorFree(ctor);
if (tor == nullptr)
{
fprintf(stderr, "Failed opening torrent file `%s'\n", torrentPath);
tr_sessionClose(h);
return EXIT_FAILURE;
}
signal(SIGINT, sigHandler);
#ifndef _WIN32
signal(SIGHUP, sigHandler);
2008-09-05 15:13:06 +00:00
#endif
tr_torrentStart(tor);
2006-07-16 19:39:23 +00:00
if (verify)
{
verify = false;
tr_torrentVerify(tor);
2008-07-09 01:48:29 +00:00
}
for (;;)
2006-07-16 19:39:23 +00:00
{
fix: sonarcloud (#2860) * fix: break will never be executed * fix: rewrite rimraf() to be non-throwing * fix: conditional operation returns same value whether condition is true or false * fix: use std::array instead of a C-style array * fix: remove redundant access specifier * fix: replace switch with if for readability * fix: convert integer literal to a bool literal * fix: replace const std::string reference to std::string_view * fix: remove redundant access specifier * fix: replace const std::string reference to std::string_view * fix: remove unused parameter * fix: remove redundant access specifier * fix: use std::array instead of C-style array * fix: remove redundant access specifier * fix: replace const std::string reference with std::string_view * fix: remove redundant access specifier * fix: use std::array instead of C-style array * fix: remove redundant access specifier * fix: replace const std::string reference to std::string_view * fix: remove redundant access specifier * fix: merge if statement with enclosing one * chore: clang-format * chore: clang-format * Revert "fix: remove redundant access specifier" This reverts commit 054e4e7eecbbc6d1108fd15de10f39cd8aad2aa6. * Revert "fix: remove redundant access specifier" This reverts commit 2c92f227e8220447a5a2658c758c61888d5a62a1. * Revert "fix: remove redundant access specifier" This reverts commit a0710202a8195a8617f454f21450040d8ae07813. * Revert "fix: remove redundant access specifier" This reverts commit 54da1d93972f662697a2811631d9760757319abc. * Revert "fix: remove redundant access specifier" This reverts commit f7b1777578acf0caa5f5b87af0b5715b0a0d1c1a. * Revert "fix: remove redundant access specifier" This reverts commit ad8e3dfff4a7fb41a7d14ed30759317f7f77b455. * chore: revert access specifier change
2022-04-01 19:16:33 +00:00
static auto constexpr messageName = std::array<char const*, 4>{
nullptr,
"Tracker gave a warning:",
"Tracker gave an error:",
"Error:",
};
2006-07-16 19:39:23 +00:00
std::this_thread::sleep_for(200ms);
2006-07-16 19:39:23 +00:00
if (gotsig)
{
gotsig = false;
printf("\nStopping torrent...\n");
tr_torrentStop(tor);
2007-02-13 05:20:52 +00:00
}
if (manualUpdate)
{
manualUpdate = false;
if (!tr_torrentCanManualUpdate(tor))
{
fprintf(stderr, "\nReceived SIGHUP, but can't send a manual update now\n");
}
else
{
fprintf(stderr, "\nReceived SIGHUP: manual update scheduled\n");
tr_torrentManualUpdate(tor);
}
}
2006-07-16 19:39:23 +00:00
auto const* const st = tr_torrentStat(tor);
if (st->activity == TR_STATUS_STOPPED)
{
break;
}
auto const status_str = getStatusStr(st);
printf("\r%-*s", TR_ARG_TUPLE(LineWidth, status_str.c_str()));
if (messageName[st->error])
{
fprintf(stderr, "\n%s: %s\n", messageName[st->error], st->errorString);
}
2006-07-16 19:39:23 +00:00
}
tr_sessionSaveSettings(h, config_dir.c_str(), settings);
printf("\n");
tr_sessionClose(h);
return EXIT_SUCCESS;
2006-07-16 19:39:23 +00:00
}
2008-07-08 16:50:34 +00:00
/***
****
****
***/
static int parseCommandLine(tr_variant* d, int argc, char const** argv)
2008-07-08 16:50:34 +00:00
{
int c;
char const* my_optarg;
2008-07-08 16:50:34 +00:00
2021-12-17 20:48:02 +00:00
while ((c = tr_getopt(Usage, argc, argv, std::data(Options), &my_optarg)) != TR_OPT_DONE)
2006-07-16 19:39:23 +00:00
{
switch (c)
2006-07-16 19:39:23 +00:00
{
case 'b':
tr_variantDictAddBool(d, TR_KEY_blocklist_enabled, true);
break;
case 'B':
tr_variantDictAddBool(d, TR_KEY_blocklist_enabled, false);
break;
case 'd':
tr_variantDictAddInt(d, TR_KEY_speed_limit_down, atoi(my_optarg));
tr_variantDictAddBool(d, TR_KEY_speed_limit_down_enabled, true);
break;
case 'D':
tr_variantDictAddBool(d, TR_KEY_speed_limit_down_enabled, false);
break;
case 'f':
tr_variantDictAddStr(d, TR_KEY_script_torrent_done_filename, my_optarg);
tr_variantDictAddBool(d, TR_KEY_script_torrent_done_enabled, true);
break;
case 'g': /* handled above */
break;
case 'm':
tr_variantDictAddBool(d, TR_KEY_port_forwarding_enabled, true);
break;
case 'M':
tr_variantDictAddBool(d, TR_KEY_port_forwarding_enabled, false);
break;
case 'p':
tr_variantDictAddInt(d, TR_KEY_peer_port, atoi(my_optarg));
break;
case 't':
tr_variantDictAddStr(d, TR_KEY_peer_socket_tos, my_optarg);
break;
case 'u':
tr_variantDictAddInt(d, TR_KEY_speed_limit_up, atoi(my_optarg));
tr_variantDictAddBool(d, TR_KEY_speed_limit_up_enabled, true);
break;
case 'U':
tr_variantDictAddBool(d, TR_KEY_speed_limit_up_enabled, false);
break;
case 'v':
verify = true;
break;
case 'V':
showVersion = true;
break;
case 'w':
tr_variantDictAddStr(d, TR_KEY_download_dir, my_optarg);
break;
case 910:
tr_variantDictAddInt(d, TR_KEY_encryption, TR_ENCRYPTION_REQUIRED);
break;
case 911:
tr_variantDictAddInt(d, TR_KEY_encryption, TR_ENCRYPTION_PREFERRED);
break;
case 912:
tr_variantDictAddInt(d, TR_KEY_encryption, TR_CLEAR_PREFERRED);
break;
2023-04-14 16:47:54 +00:00
case 500:
tr_variantDictAddBool(d, TR_KEY_sequentialDownload, true);
break;
case TR_OPT_UNK:
if (torrentPath == nullptr)
{
torrentPath = my_optarg;
}
break;
default:
return 1;
2006-07-16 19:39:23 +00:00
}
}
return 0;
2006-07-16 19:39:23 +00:00
}
static void sigHandler(int signal)
2006-07-16 19:39:23 +00:00
{
switch (signal)
2006-07-16 19:39:23 +00:00
{
case SIGINT:
gotsig = true;
break;
#ifndef _WIN32
case SIGHUP:
manualUpdate = true;
break;
2008-09-05 15:13:06 +00:00
#endif
default:
break;
2006-07-16 19:39:23 +00:00
}
}