// This file Copyright © 2008-2023 Mnemosyne LLC. // It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), // or any future license endorsed by Mnemosyne LLC. // License text can be found in the licenses/ folder. #include #include #include /* isspace */ #include // PRId64 #include #include #include #include #include /* strcmp */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::literals; #define SPEED_K_STR "kB/s" #define MEM_M_STR "MiB" static auto constexpr DefaultPort = int{ TR_DEFAULT_RPC_PORT }; static char constexpr DefaultHost[] = "localhost"; static char constexpr DefaultUrl[] = TR_DEFAULT_RPC_URL_STR "rpc/"; static char constexpr MyName[] = "transmission-remote"; static char constexpr Usage[] = "transmission-remote " LONG_VERSION_STRING "\n" "A fast and easy BitTorrent client\n" "https://transmissionbt.com/\n" "\n" "Usage: transmission-remote [host] [options]\n" " transmission-remote [port] [options]\n" " transmission-remote [host:port] [options]\n" " transmission-remote [http(s?)://host:port/transmission/] [options]\n" "\n" "See the man page for detailed explanations and many examples."; static auto constexpr Arguments = TR_KEY_arguments; static auto constexpr MemK = size_t{ 1024 }; static char constexpr MemKStr[] = "KiB"; static char constexpr MemMStr[] = MEM_M_STR; static char constexpr MemGStr[] = "GiB"; static char constexpr MemTStr[] = "TiB"; static auto constexpr DiskK = size_t{ 1000 }; static char constexpr DiskKStr[] = "kB"; static char constexpr DiskMStr[] = "MB"; static char constexpr DiskGStr[] = "GB"; static char constexpr DiskTStr[] = "TB"; static auto constexpr SpeedK = size_t{ 1000 }; static auto constexpr SpeedKStr = SPEED_K_STR; static char constexpr SpeedMStr[] = "MB/s"; static char constexpr SpeedGStr[] = "GB/s"; static char constexpr SpeedTStr[] = "TB/s"; struct Config { std::string auth; std::string filter; std::string netrc; std::string session_id; std::string torrent_ids; std::string unix_socket_path; bool debug = false; bool json = false; bool use_ssl = false; }; /*** **** **** Display Utilities **** ***/ static std::string etaToString(int64_t eta) { if (eta < 0) { return "Unknown"; } if (eta < 60) { return fmt::format(FMT_STRING("{:d} sec"), eta); } if (eta < (60 * 60)) { return fmt::format(FMT_STRING("{:d} min"), eta / 60); } if (eta < (60 * 60 * 24)) { return fmt::format(FMT_STRING("{:d} hrs"), eta / (60 * 60)); } if (eta < (60 * 60 * 24 * 30)) { return fmt::format(FMT_STRING("{:d} days"), eta / (60 * 60 * 24)); } if (eta < (60 * 60 * 24 * 30 * 12)) { return fmt::format(FMT_STRING("{:d} months"), eta / (60 * 60 * 24 * 30)); } if (eta < (60 * 60 * 24 * 365 * 1000LL)) // up to 999 years { return fmt::format(FMT_STRING("{:d} years"), eta / (60 * 60 * 24 * 365)); } return "∞"; } static std::string tr_strltime(time_t seconds) { if (seconds < 0) { seconds = 0; } auto const total_seconds = seconds; auto const days = seconds / 86400; auto const hours = (seconds % 86400) / 3600; auto const minutes = (seconds % 3600) / 60; seconds = (seconds % 3600) % 60; auto tmpstr = std::string{}; auto const hstr = fmt::format(FMT_STRING("{:d} {:s}"), hours, tr_ngettext("hour", "hours", hours)); auto const mstr = fmt::format(FMT_STRING("{:d} {:s}"), minutes, tr_ngettext("minute", "minutes", minutes)); auto const sstr = fmt::format(FMT_STRING("{:d} {:s}"), seconds, tr_ngettext("seconds", "seconds", seconds)); if (days > 0) { auto const dstr = fmt::format(FMT_STRING("{:d} {:s}"), days, tr_ngettext("day", "days", days)); tmpstr = days >= 4 || hours == 0 ? dstr : fmt::format(FMT_STRING("{:s}, {:s}"), dstr, hstr); } else if (hours > 0) { tmpstr = hours >= 4 || minutes == 0 ? hstr : fmt::format(FMT_STRING("{:s}, {:s}"), hstr, mstr); } else if (minutes > 0) { tmpstr = minutes >= 4 || seconds == 0 ? mstr : fmt::format(FMT_STRING("{:s}, {:s}"), mstr, sstr); } else { tmpstr = sstr; } auto const totstr = fmt::format(FMT_STRING("{:d} {:s}"), total_seconds, tr_ngettext("seconds", "seconds", total_seconds)); return fmt::format(FMT_STRING("{:s} ({:s})"), tmpstr, totstr); } static std::string strlpercent(double x) { return tr_strpercent(x); } static std::string strlratio2(double ratio) { return tr_strratio(ratio, "Inf"); } static std::string strlratio(int64_t numerator, int64_t denominator) { return strlratio2(tr_getRatio(numerator, denominator)); } static std::string strlmem(int64_t bytes) { return bytes == 0 ? "None"s : tr_formatter_mem_B(bytes); } static std::string strlsize(int64_t bytes) { if (bytes < 0) { return "Unknown"s; } if (bytes == 0) { return "None"s; } return tr_formatter_size_B(bytes); } enum { TAG_SESSION, TAG_STATS, TAG_DETAILS, TAG_FILES, TAG_FILTER, TAG_GROUPS, TAG_LIST, TAG_PEERS, TAG_PIECES, TAG_PORTTEST, TAG_TORRENT_ADD, TAG_TRACKERS }; /*** **** **** Command-Line Arguments **** ***/ static auto constexpr Options = std::array{ { { 'a', "add", "Add torrent files by filename or URL", "a", false, nullptr }, { 970, "alt-speed", "Use the alternate Limits", "as", false, nullptr }, { 971, "no-alt-speed", "Don't use the alternate Limits", "AS", false, nullptr }, { 972, "alt-speed-downlimit", "max alternate download speed (in " SPEED_K_STR ")", "asd", true, "" }, { 973, "alt-speed-uplimit", "max alternate upload speed (in " SPEED_K_STR ")", "asu", true, "" }, { 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, "