From 1ba64684f908aaeedee2cf0aca6dc6e43ff59fc0 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Fri, 8 Oct 2021 21:46:10 -0500 Subject: [PATCH] refactor: C++ify clients.cc (#1907) * refactor: use C++17 for clients.cc --- code_style.sh | 1 + libtransmission/clients.cc | 1423 +++++++++------------- qt/DetailsDialog.cc.orig | 1569 ------------------------- tests/libtransmission/clients-test.cc | 89 +- 4 files changed, 606 insertions(+), 2476 deletions(-) delete mode 100644 qt/DetailsDialog.cc.orig diff --git a/code_style.sh b/code_style.sh index c59a78d4c..9e0e2149e 100755 --- a/code_style.sh +++ b/code_style.sh @@ -30,6 +30,7 @@ cfile_excludes=( 'macosx/VDKQueue/*' 'third-party/*' 'web/*' + '.git/*' ) get_find_path_args() { diff --git a/libtransmission/clients.cc b/libtransmission/clients.cc index 41f5c324e..4be2de975 100644 --- a/libtransmission/clients.cc +++ b/libtransmission/clients.cc @@ -8,63 +8,90 @@ /* thanks amc1! */ +#include +#include +#include +#include +#include #include /* isprint() */ #include /* strtol() */ #include +#include #include "transmission.h" #include "clients.h" #include "utils.h" /* tr_snprintf(), tr_strlcpy() */ -static int charint(uint8_t ch) +using namespace std::literals; // "foo"sv + +namespace { - if ('0' <= ch && ch <= '9') - { - return ch - '0'; - } - if ('A' <= ch && ch <= 'Z') +constexpr std::pair buf_append(char* buf, size_t buflen, char ch) +{ + if (buflen >= 2) { - return 10 + ch - 'A'; + *buf++ = ch; } - - if ('a' <= ch && ch <= 'z') - { - return 36 + ch - 'a'; - } - - return 0; + *buf = '\0'; + return { buf, buflen - 1 }; } -static bool getShadowInt(uint8_t ch, int* setme) +constexpr std::pair buf_append(char* buf, size_t buflen, std::string_view name) { - char const* str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-"; - char const* pch = strchr(str, ch); - - if (pch == nullptr) + auto const len = std::min(buflen - 1, std::size(name)); + for (size_t i = 0; i < len; ++i) { - return false; + *buf++ = name[i]; } - - *setme = pch - str; - return true; + *buf = '\0'; + return { buf, buflen - len }; } -static bool getFDMInt(uint8_t ch, int* setme) +constexpr std::pair buf_append(char* buf, size_t buflen, int n) { - char const* str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_.!~*()"; - char const* pch = strchr(str, ch); + auto mybuf = std::array{}; + auto const end = std::data(mybuf) + std::size(mybuf); + auto constexpr base = 10; + auto* ptr = end; - if (pch == nullptr) + while ((n / base) > 0) { - return false; + *--ptr = char('0' + (n % base)); + n /= base; } + *--ptr = char('0' + (n % base)); - *setme = pch - str; - return true; + return buf_append(buf, buflen, std::string_view(ptr, end - ptr)); } -static int strint(void const* pch, int span) +template +constexpr std::pair buf_append(char* buf, size_t buflen, T const t, ArgTypes... args) +{ + std::tie(buf, buflen) = buf_append(buf, buflen, t); + return buf_append(buf, buflen, args...); +} + +// ['0'..'9']: ch - '0' +// ['A'..'Z']: 10 + ch - '9' +// ['a'..'z']: 36 + ch - '9' +auto constexpr charints = std::array{ + { "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", + "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", + "x", "x", "x", "x", "x", "x", "x", "x", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "x", "x", + "x", "x", "x", "x", "x", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", + "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "x", "x", "x", "x", "x", "x", "36", "37", "38", + "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", + "59", "60", "61", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", + "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", + "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", + "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", + "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", + "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", + "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x", "x" } +}; + +int strint(void const* pch, int span) { char tmp[64]; memcpy(tmp, pch, span); @@ -72,894 +99,577 @@ static int strint(void const* pch, int span) return strtol(tmp, nullptr, 0); } -static char const* getMnemonicEnd(uint8_t ch) +constexpr std::string_view getMnemonicEnd(uint8_t ch) { switch (ch) { case 'b': case 'B': - return " (Beta)"; + return " (Beta)"sv; case 'd': - return " (Debug)"; + return " (Debug)"sv; case 'x': case 'X': case 'Z': - return " (Dev)"; + return " (Dev)"sv; default: - return ""; + return ""sv; } } -static void three_digits(char* buf, size_t buflen, char const* name, uint8_t const* digits) +void two_major_two_minor_formatter(char* buf, size_t buflen, std::string_view name, char const* id) { - tr_snprintf(buf, buflen, "%s %d.%d.%d", name, charint(digits[0]), charint(digits[1]), charint(digits[2])); + std::tie(buf, buflen) = buf_append(buf, buflen, name, ' ', strint(id + 3, 2), '.'); + tr_snprintf(buf, buflen, "%02d", strint(id + 5, 2)); } -static void four_digits(char* buf, size_t buflen, char const* name, uint8_t const* digits) +bool decodeShad0wClient(char* buf, size_t buflen, std::string_view peer_id) { - tr_snprintf( - buf, - buflen, - "%s %d.%d.%d.%d", - name, - charint(digits[0]), - charint(digits[1]), - charint(digits[2]), - charint(digits[3])); -} + // Shad0w with his experimental BitTorrent implementation and BitTornado + // introduced peer ids that begin with a character which is``T`` in the + // case of BitTornado followed by up to five ascii characters for version + // number, padded with dashes if less than 5, followed by ---. The ascii + // characters denoting version are limited to the following characters: + // 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.- + // For example: 'S58B-----'... for Shadow's 5.8.11 -static void two_major_two_minor(char* buf, size_t buflen, char const* name, uint8_t const* digits) -{ - tr_snprintf(buf, buflen, "%s %d.%02d", name, strint(digits, 2), strint(digits + 2, 2)); -} - -static void no_version(char* buf, size_t buflen, char const* name) -{ - tr_strlcpy(buf, name, buflen); -} - -static void mainline_style(char* buf, size_t buflen, char const* name, uint8_t const* id) -{ - if (id[4] == '-' && id[6] == '-') + auto constexpr get_shad0w_int = [](char ch) { - tr_snprintf(buf, buflen, "%s %c.%c.%c", name, id[1], id[3], id[5]); - } - else if (id[5] == '-') + auto constexpr str = std::string_view{ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-" }; + auto const pos = str.find(ch); + return pos != std::string_view::npos ? pos : std::optional{}; + }; + + if (std::size(peer_id) != 9 || peer_id[6] != '-' || peer_id[7] != '-' || peer_id[8] != '-') { - tr_snprintf(buf, buflen, "%s %c.%c%c.%c", name, id[1], id[3], id[4], id[6]); + return false; } + while (!std::empty(peer_id) && peer_id.back() == '-') + { + peer_id.remove_suffix(1); + } + auto vals = std::vector{}; + while (std::size(peer_id) > 1) + { + auto const num = get_shad0w_int(peer_id.back()); + if (!num) + { + return false; + } + vals.push_back(*num); + peer_id.remove_suffix(1); + } + + auto name = std::string_view{}; + switch (peer_id.front()) + { + case 'A': + name = "ABC"sv; + break; + case 'O': + name = "Osprey"; + break; + case 'Q': + name = "BTQueue"; + break; + case 'R': + name = "Tribler"; + break; + case 'S': + name = "Shad0w"; + break; + case 'T': + name = "BitTornado"; + break; + case 'U': + name = "UPnP NAT Bit Torrent"; + break; + default: + return false; + } + + std::tie(buf, buflen) = buf_append(buf, buflen, name, ' '); + std::for_each( + std::rbegin(vals), + std::rend(vals), + [&buf, &buflen](int num) { std::tie(buf, buflen) = buf_append(buf, buflen, num, '.'); }); + buf[-1] = '\0'; // remove trailing '.' + return true; } -static bool isMainlineStyle(uint8_t const* peer_id) +bool decodeBitCometClient(char* buf, size_t buflen, std::string_view peer_id) { - /** - * One of the following styles will be used: - * Mx-y-z-- - * Mx-yy-z- - */ - return peer_id[2] == '-' && peer_id[7] == '-' && (peer_id[4] == '-' || peer_id[5] == '-'); -} - -static bool decodeBitCometClient(char* buf, size_t buflen, uint8_t const* id) -{ - char const* chid = (char const*)id; - bool is_bitlord; - int major; - int minor; - char const* name; - char const* mod = nullptr; - - if (strncmp(chid, "exbc", 4) == 0) + // BitComet produces peer ids that consists of four ASCII characters exbc, + // followed by two bytes x and y, followed by random characters. The version + // number is x in decimal before the decimal point and y as two decimal + // digits after the decimal point. BitLord uses the same scheme, but adds + // LORD after the version bytes. An unofficial patch for BitComet once + // replaced exbc with FUTB. The encoding for BitComet Peer IDs changed + // to Azureus-style as of BitComet version 0.59. + auto mod = std::string_view{}; + auto const lead = std::string_view{ std::data(peer_id), std::min(std::size(peer_id), size_t{ 4 }) }; + if (lead == "exbc") { mod = ""; } - else if (strncmp(chid, "FUTB", 4) == 0) + else if (lead == "FUTB") { - mod = " (Solidox Mod) "; + mod = "(Solidox Mod) "; } - else if (strncmp(chid, "xUTB", 4) == 0) + else if (lead == "xUTB"sv) { - mod = " (Mod 2) "; + mod = "(Mod 2) "; } else { return false; } - is_bitlord = strncmp(chid + 6, "LORD", 4) == 0; - name = (is_bitlord) ? "BitLord " : "BitComet "; - major = id[4]; - minor = id[5]; - - /** - * Bitcomet, and older versions of BitLord, are of the form x.yy. - * Bitcoment 1.0 and onwards are of the form x.y. - */ - if (is_bitlord && major > 0) - { - tr_snprintf(buf, buflen, "%s%s%d.%d", name, mod, major, minor); - } - else - { - tr_snprintf(buf, buflen, "%s%s%d.%02d", name, mod, major, minor); - } + bool const is_bitlord = std::string_view(std::data(peer_id) + 6, 4) == "LORD"sv; + auto const name = is_bitlord ? "BitLord"sv : "BitComet"sv; + int const major = peer_id[4]; + int const minor = peer_id[5]; + std::tie(buf, buflen) = buf_append(buf, buflen, name, ' ', mod, major, '.'); + tr_snprintf(buf, buflen, "%02d", minor); return true; } +using format_func = void (*)(char* buf, size_t buflen, std::string_view name, char const* id); + +constexpr void three_digit_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', charints[id[3]], '.', charints[id[4]], '.', charints[id[5]]); +} + +constexpr void four_digit_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', charints[id[3]], '.', charints[id[4]], '.', charints[id[5]], '.', charints[id[6]]); +} + +constexpr void no_version_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + TR_UNUSED(id); + buf_append(buf, buflen, name); +} + +// specific clients + +constexpr void amazon_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', id[3], '.', id[5], '.', id[7]); +} + +constexpr void aria2_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + if (id[4] == '-' && id[6] == '-' && id[8] == '-') + { + buf_append(buf, buflen, name, ' ', id[3], '.', id[5], '.', id[7]); + } + else if (id[4] == '-' && id[7] == '-' && id[9] == '-') + { + buf_append(buf, buflen, name, ' ', id[3], '.', id[5], id[6], '.', id[8]); + } + else + { + buf_append(buf, buflen, name); + } +} + +constexpr void bitbuddy_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', id[3], '.', id[4], id[5], id[6]); +} + +constexpr void bitlord_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', id[3], '.', id[4], '.', id[5], '-', std::string_view(id + 6, 3)); +} + +constexpr void bitrocket_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', id[3], '.', id[4], ' ', '(', id[5], id[6], ')'); +} + +void bittorrent_dna_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + std::tie(buf, buflen) = buf_append(buf, buflen, name, ' '); + tr_snprintf(buf, buflen, "%d.%d.%d", strint(id + 3, 2), strint(id + 5, 2), strint(id + 7, 2)); +} + +void bits_on_wheels_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + // Bits on Wheels uses the pattern -BOWxxx-yyyyyyyyyyyy, where y is random + // (uppercase letters) and x depends on the version. + // Version 1.0.6 has xxx = A0C. + + if (strncmp(&id[4], "A0B", 3) == 0) + { + buf_append(buf, buflen, name, " 1.0.5"sv); + } + else if (strncmp(&id[4], "A0C", 3) == 0) + { + buf_append(buf, buflen, name, " 1.0.6"sv); + } + else + { + buf_append(buf, buflen, name, ' ', id[4], '.', id[5], '.', id[6]); + } +} + +constexpr void blizzard_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', int(id[3] + 1), int(id[4])); +} + +constexpr void btpd_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', std::string_view(id + 5, 3)); +} + +constexpr void burst_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', id[5], '.', id[7], '.', id[9]); +} + +constexpr void ctorrent_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', charints[id[3]], '.', charints[id[4]], '.', id[5], id[6]); +} + +constexpr void folx_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', charints[id[3]], '.', 'x'); +} + +constexpr void ktorrent_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + if (id[5] == 'D') + { + buf_append(buf, buflen, name, ' ', charints[id[3]], '.', charints[id[4]], " Dev "sv, charints[id[6]]); + } + else if (id[5] == 'R') + { + buf_append(buf, buflen, name, ' ', charints[id[3]], '.', charints[id[4]], " RC "sv, charints[id[6]]); + } + else + { + three_digit_formatter(buf, buflen, name, id); + } +} + +constexpr void mainline_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + // Queen Bee uses Bram`s new style: + // Q1-0-0-- or Q1-10-0- followed by random bytes. + + if (id[4] == '-' && id[6] == '-') // Mx-y-z-- + { + buf_append(buf, buflen, name, ' ', id[1], '.', id[3], '.', id[5]); + } + else if (id[5] == '-') // Mx-yy-z- + { + buf_append(buf, buflen, name, ' ', id[1], '.', id[3], id[4], '.', id[6]); + } + else + { + buf_append(buf, buflen, name); + } +} + +constexpr void mediaget_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', charints[id[3]], '.', charints[id[4]]); +} + +constexpr void mldonkey_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + // MLdonkey use the following peer_id scheme: the first characters are + // -ML followed by a dotted version then a - followed by randomness. + // e.g. -ML2.7.2-kgjjfkd + buf_append(buf, buflen, name, ' ', std::string_view(id + 3, 5)); +} + +constexpr void opera_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + // Opera 8 previews and Opera 9.x releases use the following peer_id + // scheme: The first two characters are OP and the next four digits equal + // the build number. All following characters are random lowercase + // hexdecimal digits. + buf_append(buf, buflen, name, ' ', std::string_view(id + 2, 4)); +} + +constexpr void picotorrent_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', charints[id[3]], '.', id[4], id[5], '.', charints[id[6]]); +} + +constexpr void plus_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', id[4], '.', id[5], id[6]); +} + +constexpr void qvod_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + four_digit_formatter(buf, buflen, name, id + 1); +} + +void transmission_formatter(char* buf, size_t buflen, std::string_view name, char const* chid) +{ + std::tie(buf, buflen) = buf_append(buf, buflen, name, ' '); + + if (strncmp(chid + 3, "000", 3) == 0) // very old client style: -TR0006- is 0.6 + { + tr_snprintf(buf, buflen, "0.%c", chid[6]); + } + else if (strncmp(chid + 3, "00", 2) == 0) // previous client style: -TR0072- is 0.72 + { + tr_snprintf(buf, buflen, "0.%02d", strint(chid + 5, 2)); + } + else // current client style: -TR111Z- is 1.11+ */ + { + tr_snprintf( + buf, + buflen, + "%d.%02d%s", + strint(chid + 3, 1), + strint(chid + 4, 2), + (chid[6] == 'Z' || chid[6] == 'X') ? "+" : ""); + } +} + +constexpr void utorrent_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + if (id[7] == '-') + { + buf_append(buf, buflen, name, ' ', id[3], '.', id[4], '.', id[5], getMnemonicEnd(id[6])); + } + else // uTorrent replaces the trailing dash with an extra digit for longer version numbers + { + buf_append(buf, buflen, name, ' ', id[3], '.', id[4], '.', id[5], id[6], getMnemonicEnd(id[6])); + } +} + +constexpr void xbt_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + buf_append(buf, buflen, name, ' ', id[3], '.', id[4], '.', id[5], getMnemonicEnd(id[6])); +} + +constexpr void xfplay_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + if (id[6] == '0') + { + three_digit_formatter(buf, buflen, name, id); + } + else + { + buf_append(buf, buflen, name, ' ', id[3], '.', id[4], '.', id[5], id[6]); + } +} + +void xtorrent_formatter(char* buf, size_t buflen, std::string_view name, char const* id) +{ + std::tie(buf, buflen) = buf_append(buf, buflen, name, ' ', charints[id[3]], '.', charints[id[4]], " ("sv); + tr_snprintf(buf, buflen, "%d)", strint(id + 5, 2)); +} + +struct Client +{ + std::string_view begins_with; + std::string_view name; + format_func formatter; +}; + +auto constexpr Clients = std::array{ { + { "-AG", "Ares", four_digit_formatter }, + { "-AR", "Arctic", four_digit_formatter }, + { "-AT", "Artemis", four_digit_formatter }, + { "-AV", "Avicora", four_digit_formatter }, + { "-AX", "BitPump", two_major_two_minor_formatter }, + { "-AZ", "Azureus / Vuze", four_digit_formatter }, + { "-A~", "Ares", three_digit_formatter }, + { "-BB", "BitBuddy", bitbuddy_formatter }, + { "-BC", "BitComet", two_major_two_minor_formatter }, + { "-BE", "BitTorrent SDK", four_digit_formatter }, + { "-BF", "BitFlu", no_version_formatter }, + { "-BG", "BTGetit", four_digit_formatter }, + { "-BH", "BitZilla", four_digit_formatter }, + { "-BI", "BiglyBT", four_digit_formatter }, + { "-BL", "BitLord", bitlord_formatter }, + { "-BM", "BitMagnet", four_digit_formatter }, + { "-BN", "Baidu Netdisk", no_version_formatter }, + { "-BOW", "Bits on Wheels", bits_on_wheels_formatter }, + { "-BP", "BitTorrent Pro (Azureus + Spyware)", four_digit_formatter }, + { "-BR", "BitRocket", bitrocket_formatter }, + { "-BS", "BTSlave", four_digit_formatter }, + { "-BT", "BitTorrent", utorrent_formatter }, + { "-BW", "BitWombat", four_digit_formatter }, + { "-BX", "BittorrentX", four_digit_formatter }, + { "-CD", "Enhanced CTorrent", two_major_two_minor_formatter }, + { "-CT", "CTorrent", ctorrent_formatter }, + { "-DE", "Deluge", four_digit_formatter }, + { "-DP", "Propagate Data Client", four_digit_formatter }, + { "-EB", "EBit", four_digit_formatter }, + { "-ES", "Electric Sheep", three_digit_formatter }, + { "-FC", "FileCroc", four_digit_formatter }, + { "-FD", "Free Download Manager", three_digit_formatter }, + { "-FG", "FlashGet", two_major_two_minor_formatter }, + { "-FL", "Folx", folx_formatter }, + { "-FT", "FoxTorrent/RedSwoosh", four_digit_formatter }, + { "-FW", "FrostWire", three_digit_formatter }, + { "-FX", "Freebox", four_digit_formatter }, + { "-G3", "G3 Torrent", no_version_formatter }, + { "-GR", "GetRight", four_digit_formatter }, + { "-GS", "GSTorrent", four_digit_formatter }, + { "-HK", "Hekate", four_digit_formatter }, + { "-HL", "Halite", three_digit_formatter }, + { "-HN", "Hydranode", four_digit_formatter }, + { "-KG", "KGet", four_digit_formatter }, + { "-KT", "KTorrent", ktorrent_formatter }, + { "-LC", "LeechCraft", four_digit_formatter }, + { "-LH", "LH-ABC", four_digit_formatter }, + { "-LP", "Lphant", two_major_two_minor_formatter }, + { "-LT", "libtorrent (Rasterbar)", three_digit_formatter }, + { "-LW", "LimeWire", no_version_formatter }, + { "-MG", "MediaGet", mediaget_formatter }, + { "-MK", "Meerkat", four_digit_formatter }, + { "-ML", "MLDonkey", mldonkey_formatter }, + { "-MO", "MonoTorrent", four_digit_formatter }, + { "-MP", "MooPolice", three_digit_formatter }, + { "-MR", "Miro", four_digit_formatter }, + { "-MT", "Moonlight", four_digit_formatter }, + { "-NE", "BT Next Evolution", four_digit_formatter }, + { "-NX", "Net Transport", four_digit_formatter }, + { "-OS", "OneSwarm", four_digit_formatter }, + { "-OT", "OmegaTorrent", four_digit_formatter }, + { "-PD", "Pando", four_digit_formatter }, + { "-PI", "PicoTorrent", picotorrent_formatter }, + { "-QD", "QQDownload", four_digit_formatter }, + { "-QT", "QT 4 Torrent example", four_digit_formatter }, + { "-RS", "Rufus", four_digit_formatter }, + { "-RT", "Retriever", four_digit_formatter }, + { "-RZ", "RezTorrent", four_digit_formatter }, + { "-SB", "~Swiftbit", four_digit_formatter }, + { "-SD", "Thunder", four_digit_formatter }, + { "-SM", "SoMud", four_digit_formatter }, + { "-SP", "BitSpirit", three_digit_formatter }, + { "-SS", "SwarmScope", four_digit_formatter }, + { "-ST", "SymTorrent", four_digit_formatter }, + { "-SZ", "Shareaza", four_digit_formatter }, + { "-S~", "Shareaza", four_digit_formatter }, + { "-TN", "Torrent .NET", four_digit_formatter }, + { "-TR", "Transmission", transmission_formatter }, + { "-TS", "Torrentstorm", four_digit_formatter }, + { "-TT", "TuoTu", four_digit_formatter }, + { "-UE", "\xc2\xb5Torrent Embedded", utorrent_formatter }, + { "-UL", "uLeecher!", four_digit_formatter }, + { "-UM", "\xc2\xb5Torrent Mac", utorrent_formatter }, + { "-UT", "\xc2\xb5Torrent", utorrent_formatter }, + { "-UW", "\xc2\xb5Torrent Web", utorrent_formatter }, + { "-VG", "Vagaa", four_digit_formatter }, + { "-WS", "HTTP Seed", no_version_formatter }, + { "-WT", "BitLet", four_digit_formatter }, + { "-WT-", "BitLet", no_version_formatter }, + { "-WW", "WebTorrent", four_digit_formatter }, + { "-WY", "FireTorrent", four_digit_formatter }, + { "-XC", "Xtorrent", xtorrent_formatter }, + { "-XF", "Xfplay", xfplay_formatter }, + { "-XL", "Xunlei", four_digit_formatter }, + { "-XS", "XSwifter", four_digit_formatter }, + { "-XT", "XanTorrent", four_digit_formatter }, + { "-XX", "Xtorrent", xtorrent_formatter }, + { "-ZO", "Zona", four_digit_formatter }, + { "-ZT", "Zip Torrent", four_digit_formatter }, + { "-bk", "BitKitten (libtorrent)", four_digit_formatter }, + { "-lt", "libTorrent (Rakshasa)", three_digit_formatter }, + { "-pb", "pbTorrent", three_digit_formatter }, + { "-qB", "qBittorrent", three_digit_formatter }, + { "-st", "SharkTorrent", four_digit_formatter }, + { "10-------", "JVtorrent", no_version_formatter }, + { "346-", "TorrentTopia", no_version_formatter }, + { "A2", "aria2", aria2_formatter }, + { "AZ2500BT", "BitTyrant (Azureus Mod)", no_version_formatter }, + { "BLZ", "Blizzard Downloader", blizzard_formatter }, + { "DNA", "BitTorrent DNA", bittorrent_dna_formatter }, + { "LIME", "Limewire", no_version_formatter }, + { "M", "BitTorrent", mainline_formatter }, + { "Mbrst", "burst!", burst_formatter }, + { "OP", "Opera", opera_formatter }, + { "Pando", "Pando", no_version_formatter }, + { "Plus", "Plus!", plus_formatter }, + { "Q", "Queen Bee", mainline_formatter }, + { "QVOD", "QVOD", qvod_formatter }, + { "S3", "Amazon S3", amazon_formatter }, + { "TIX", "Tixati", two_major_two_minor_formatter }, + { "XBT", "XBT Client", xbt_formatter }, + { "a00---0", "Swarmy", no_version_formatter }, + { "a02---0", "Swarmy", no_version_formatter }, + { "aria2-", "aria2", no_version_formatter }, + { "btpd", "BT Protocol Daemon", btpd_formatter }, + { "eX", "eXeem", no_version_formatter }, + { "martini", "Martini Man", no_version_formatter }, +} }; + +} // namespace + char* tr_clientForId(char* buf, size_t buflen, void const* id_in) { - auto const* id = static_cast(id_in); - auto const* chid = static_cast(id_in); - *buf = '\0'; + auto const* const id = static_cast(id_in); if (id == nullptr) { return buf; } - /* Azureus-style */ - if (id[0] == '-' && id[7] == '-') - { - if (strncmp(chid + 1, "TR", 2) == 0) - { - if (strncmp(chid + 3, "000", 3) == 0) /* very old client style: -TR0006- is 0.6 */ - { - tr_snprintf(buf, buflen, "Transmission 0.%c", id[6]); - } - else if (strncmp(chid + 3, "00", 2) == 0) /* previous client style: -TR0072- is 0.72 */ - { - tr_snprintf(buf, buflen, "Transmission 0.%02d", strint(id + 5, 2)); - } - else /* current client style: -TR111Z- is 1.11+ */ - { - tr_snprintf( - buf, - buflen, - "Transmission %d.%02d%s", - strint(id + 3, 1), - strint(id + 4, 2), - (id[6] == 'Z' || id[6] == 'X') ? "+" : ""); - } - } - else if (strncmp(chid + 1, "UT", 2) == 0) - { - tr_snprintf( - buf, - buflen, - "\xc2\xb5Torrent %d.%d.%d%s", - strint(id + 3, 1), - strint(id + 4, 1), - strint(id + 5, 1), - getMnemonicEnd(id[6])); - } - else if (strncmp(chid + 1, "BT", 2) == 0) - { - tr_snprintf( - buf, - buflen, - "BitTorrent %d.%d.%d%s", - strint(id + 3, 1), - strint(id + 4, 1), - strint(id + 5, 1), - getMnemonicEnd(id[6])); - } - else if (strncmp(chid + 1, "UM", 2) == 0) - { - tr_snprintf( - buf, - buflen, - "\xc2\xb5Torrent Mac %d.%d.%d%s", - strint(id + 3, 1), - strint(id + 4, 1), - strint(id + 5, 1), - getMnemonicEnd(id[6])); - } - else if (strncmp(chid + 1, "UE", 2) == 0) - { - tr_snprintf( - buf, - buflen, - "\xc2\xb5Torrent Embedded %d.%d.%d%s", - strint(id + 3, 1), - strint(id + 4, 1), - strint(id + 5, 1), - getMnemonicEnd(id[6])); - } - else if (strncmp(chid + 1, "UW", 2) == 0) - { - tr_snprintf( - buf, - buflen, - "\xc2\xb5Torrent Web %d.%d.%d%s", - strint(id + 3, 1), - strint(id + 4, 1), - strint(id + 5, 1), - getMnemonicEnd(id[6])); - } - /* */ - else if (strncmp(chid + 1, "AZ", 2) == 0) - { - if (id[3] > '3' || (id[3] == '3' && id[4] >= '1')) /* Vuze starts at version 3.1.0.0 */ - { - four_digits(buf, buflen, "Vuze", id + 3); - } - else - { - four_digits(buf, buflen, "Azureus", id + 3); - } - } - /* */ - else if (strncmp(chid + 1, "KT", 2) == 0) - { - if (id[5] == 'D') - { - tr_snprintf(buf, buflen, "KTorrent %d.%d Dev %d", charint(id[3]), charint(id[4]), charint(id[6])); - } - else if (id[5] == 'R') - { - tr_snprintf(buf, buflen, "KTorrent %d.%d RC %d", charint(id[3]), charint(id[4]), charint(id[6])); - } - else - { - three_digits(buf, buflen, "KTorrent", id + 3); - } - } - /* */ - else if (strncmp(chid + 1, "AG", 2) == 0) - { - four_digits(buf, buflen, "Ares", id + 3); - } - else if (strncmp(chid + 1, "AR", 2) == 0) - { - four_digits(buf, buflen, "Arctic", id + 3); - } - else if (strncmp(chid + 1, "AT", 2) == 0) - { - four_digits(buf, buflen, "Artemis", id + 3); - } - else if (strncmp(chid + 1, "AV", 2) == 0) - { - four_digits(buf, buflen, "Avicora", id + 3); - } - else if (strncmp(chid + 1, "BE", 2) == 0) - { - four_digits(buf, buflen, "BitTorrent SDK", id + 3); - } - else if (strncmp(chid + 1, "BG", 2) == 0) - { - four_digits(buf, buflen, "BTGetit", id + 3); - } - else if (strncmp(chid + 1, "BH", 2) == 0) - { - four_digits(buf, buflen, "BitZilla", id + 3); - } - else if (strncmp(chid + 1, "BI", 2) == 0) - { - four_digits(buf, buflen, "BiglyBT", id + 3); - } - else if (strncmp(chid + 1, "BM", 2) == 0) - { - four_digits(buf, buflen, "BitMagnet", id + 3); - } - else if (strncmp(chid + 1, "BP", 2) == 0) - { - four_digits(buf, buflen, "BitTorrent Pro (Azureus + Spyware)", id + 3); - } - else if (strncmp(chid + 1, "BX", 2) == 0) - { - four_digits(buf, buflen, "BittorrentX", id + 3); - } - else if (strncmp(chid + 1, "bk", 2) == 0) - { - four_digits(buf, buflen, "BitKitten (libtorrent)", id + 3); - } - else if (strncmp(chid + 1, "BS", 2) == 0) - { - four_digits(buf, buflen, "BTSlave", id + 3); - } - else if (strncmp(chid + 1, "BW", 2) == 0) - { - four_digits(buf, buflen, "BitWombat", id + 3); - } - else if (strncmp(chid + 1, "EB", 2) == 0) - { - four_digits(buf, buflen, "EBit", id + 3); - } - else if (strncmp(chid + 1, "DE", 2) == 0) - { - four_digits(buf, buflen, "Deluge", id + 3); - } - else if (strncmp(chid + 1, "DP", 2) == 0) - { - four_digits(buf, buflen, "Propagate Data Client", id + 3); - } - else if (strncmp(chid + 1, "FC", 2) == 0) - { - four_digits(buf, buflen, "FileCroc", id + 3); - } - else if (strncmp(chid + 1, "FT", 2) == 0) - { - four_digits(buf, buflen, "FoxTorrent/RedSwoosh", id + 3); - } - else if (strncmp(chid + 1, "GR", 2) == 0) - { - four_digits(buf, buflen, "GetRight", id + 3); - } - else if (strncmp(chid + 1, "GS", 2) == 0) - { - four_digits(buf, buflen, "GSTorrent", id + 3); - } - else if (strncmp(chid + 1, "HK", 2) == 0) - { - four_digits(buf, buflen, "Hekate", id + 3); - } - else if (strncmp(chid + 1, "HN", 2) == 0) - { - four_digits(buf, buflen, "Hydranode", id + 3); - } - else if (strncmp(chid + 1, "KG", 2) == 0) - { - four_digits(buf, buflen, "KGet", id + 3); - } - else if (strncmp(chid + 1, "LC", 2) == 0) - { - four_digits(buf, buflen, "LeechCraft", id + 3); - } - else if (strncmp(chid + 1, "LH", 2) == 0) - { - four_digits(buf, buflen, "LH-ABC", id + 3); - } - else if (strncmp(chid + 1, "NX", 2) == 0) - { - four_digits(buf, buflen, "Net Transport", id + 3); - } - else if (strncmp(chid + 1, "MK", 2) == 0) - { - four_digits(buf, buflen, "Meerkat", id + 3); - } - else if (strncmp(chid + 1, "MO", 2) == 0) - { - four_digits(buf, buflen, "MonoTorrent", id + 3); - } - else if (strncmp(chid + 1, "MR", 2) == 0) - { - four_digits(buf, buflen, "Miro", id + 3); - } - else if (strncmp(chid + 1, "MT", 2) == 0) - { - four_digits(buf, buflen, "Moonlight", id + 3); - } - else if (strncmp(chid + 1, "OS", 2) == 0) - { - four_digits(buf, buflen, "OneSwarm", id + 3); - } - else if (strncmp(chid + 1, "OT", 2) == 0) - { - four_digits(buf, buflen, "OmegaTorrent", id + 3); - } - else if (strncmp(chid + 1, "PD", 2) == 0) - { - four_digits(buf, buflen, "Pando", id + 3); - } - else if (strncmp(chid + 1, "QD", 2) == 0) - { - four_digits(buf, buflen, "QQDownload", id + 3); - } - else if (strncmp(chid + 1, "RS", 2) == 0) - { - four_digits(buf, buflen, "Rufus", id + 3); - } - else if (strncmp(chid + 1, "RT", 2) == 0) - { - four_digits(buf, buflen, "Retriever", id + 3); - } - else if (strncmp(chid + 1, "RZ", 2) == 0) - { - four_digits(buf, buflen, "RezTorrent", id + 3); - } - else if (strncmp(chid + 1, "SD", 2) == 0) - { - four_digits(buf, buflen, "Thunder", id + 3); - } - else if (strncmp(chid + 1, "SM", 2) == 0) - { - four_digits(buf, buflen, "SoMud", id + 3); - } - else if (strncmp(chid + 1, "SS", 2) == 0) - { - four_digits(buf, buflen, "SwarmScope", id + 3); - } - else if (strncmp(chid + 1, "ST", 2) == 0) - { - four_digits(buf, buflen, "SymTorrent", id + 3); - } - else if (strncmp(chid + 1, "SZ", 2) == 0) - { - four_digits(buf, buflen, "Shareaza", id + 3); - } - else if (strncmp(chid + 1, "S~", 2) == 0) - { - four_digits(buf, buflen, "Shareaza", id + 3); - } - else if (strncmp(chid + 1, "st", 2) == 0) - { - four_digits(buf, buflen, "SharkTorrent", id + 3); - } - else if (strncmp(chid + 1, "TN", 2) == 0) - { - four_digits(buf, buflen, "Torrent .NET", id + 3); - } - else if (strncmp(chid + 1, "TS", 2) == 0) - { - four_digits(buf, buflen, "TorrentStorm", id + 3); - } - else if (strncmp(chid + 1, "TT", 2) == 0) - { - four_digits(buf, buflen, "TuoTu", id + 3); - } - else if (strncmp(chid + 1, "UL", 2) == 0) - { - four_digits(buf, buflen, "uLeecher!", id + 3); - } - else if (strncmp(chid + 1, "VG", 2) == 0) - { - four_digits(buf, buflen, "Vagaa", id + 3); - } - else if (strncmp(chid + 1, "WT", 2) == 0) - { - four_digits(buf, buflen, "BitLet", id + 3); - } - else if (strncmp(chid + 1, "WY", 2) == 0) - { - four_digits(buf, buflen, "FireTorrent", id + 3); - } - else if (strncmp(chid + 1, "WW", 2) == 0) - { - four_digits(buf, buflen, "WebTorrent", id + 3); - } - else if (strncmp(chid + 1, "XL", 2) == 0) - { - four_digits(buf, buflen, "Xunlei", id + 3); - } - else if (strncmp(chid + 1, "XS", 2) == 0) - { - four_digits(buf, buflen, "XSwifter", id + 3); - } - else if (strncmp(chid + 1, "XT", 2) == 0) - { - four_digits(buf, buflen, "XanTorrent", id + 3); - } - else if (strncmp(chid + 1, "XX", 2) == 0) - { - four_digits(buf, buflen, "Xtorrent", id + 3); - } - else if (strncmp(chid + 1, "ZT", 2) == 0) - { - four_digits(buf, buflen, "Zip Torrent", id + 3); - } - else if (strncmp(chid + 1, "ZO", 2) == 0) - { - four_digits(buf, buflen, "Zona", id + 3); - } - /* */ - else if (strncmp(chid + 1, "A~", 2) == 0) - { - three_digits(buf, buflen, "Ares", id + 3); - } - else if (strncmp(chid + 1, "ES", 2) == 0) - { - three_digits(buf, buflen, "Electric Sheep", id + 3); - } - else if (strncmp(chid + 1, "FW", 2) == 0) - { - three_digits(buf, buflen, "FrostWire", id + 3); - } - else if (strncmp(chid + 1, "HL", 2) == 0) - { - three_digits(buf, buflen, "Halite", id + 3); - } - else if (strncmp(chid + 1, "LT", 2) == 0) - { - three_digits(buf, buflen, "libtorrent (Rasterbar)", id + 3); - } - else if (strncmp(chid + 1, "lt", 2) == 0) - { - three_digits(buf, buflen, "libTorrent (Rakshasa)", id + 3); - } - else if (strncmp(chid + 1, "MP", 2) == 0) - { - three_digits(buf, buflen, "MooPolice", id + 3); - } - else if (strncmp(chid + 1, "pb", 2) == 0) - { - three_digits(buf, buflen, "pbTorrent", id + 3); - } - else if (strncmp(chid + 1, "qB", 2) == 0) - { - three_digits(buf, buflen, "qBittorrent", id + 3); - } - /* */ - else if (strncmp(chid + 1, "AX", 2) == 0) - { - two_major_two_minor(buf, buflen, "BitPump", id + 3); - } - else if (strncmp(chid + 1, "BC", 2) == 0) - { - two_major_two_minor(buf, buflen, "BitComet", id + 3); - } - else if (strncmp(chid + 1, "CD", 2) == 0) - { - two_major_two_minor(buf, buflen, "Enhanced CTorrent", id + 3); - } - else if (strncmp(chid + 1, "LP", 2) == 0) - { - two_major_two_minor(buf, buflen, "Lphant", id + 3); - } - /* */ - else if (strncmp(chid + 1, "BF", 2) == 0) - { - no_version(buf, buflen, "BitFlu"); - } - else if (strncmp(chid + 1, "LW", 2) == 0) - { - no_version(buf, buflen, "LimeWire"); - } - /* */ - else if (strncmp(chid + 1, "BB", 2) == 0) - { - tr_snprintf(buf, buflen, "BitBuddy %c.%c%c%c", id[3], id[4], id[5], id[6]); - } - else if (strncmp(chid + 1, "BR", 2) == 0) - { - tr_snprintf(buf, buflen, "BitRocket %c.%c (%c%c)", id[3], id[4], id[5], id[6]); - } - else if (strncmp(chid + 1, "CT", 2) == 0) - { - tr_snprintf(buf, buflen, "CTorrent %d.%d.%02d", charint(id[3]), charint(id[4]), strint(id + 5, 2)); - } - else if (strncmp(chid + 1, "XC", 2) == 0 || strncmp(chid + 1, "XX", 2) == 0) - { - tr_snprintf(buf, buflen, "Xtorrent %d.%d (%d)", charint(id[3]), charint(id[4]), strint(id + 5, 2)); - } - else if (strncmp(chid + 1, "BOW", 3) == 0) - { - if (strncmp(&chid[4], "A0B", 3) == 0) - { - tr_snprintf(buf, buflen, "Bits on Wheels 1.0.5"); - } - else if (strncmp(&chid[4], "A0C", 3) == 0) - { - tr_snprintf(buf, buflen, "Bits on Wheels 1.0.6"); - } - else - { - tr_snprintf(buf, buflen, "Bits on Wheels %c.%c.%c", id[4], id[5], id[5]); - } - } - else if (strncmp(chid + 1, "MG", 2) == 0) - { - tr_snprintf(buf, buflen, "MediaGet %d.%02d", charint(id[3]), charint(id[4])); - } - else if (strncmp(chid + 1, "XF", 2) == 0) - { - if (chid[6] == '0') - { - three_digits(buf, buflen, "Xfplay", id + 3); - } - else - { - tr_snprintf(buf, buflen, "Xfplay %d.%d.%d", strint(id + 3, 1), strint(id + 4, 1), strint(id + 5, 2)); - } - } - else if (strncmp(chid + 1, "PI", 2) == 0) - { - tr_snprintf(buf, buflen, "PicoTorrent %d.%d%d.%d", charint(id[3]), charint(id[4]), charint(id[5]), charint(id[6])); - } - else if (strncmp(chid + 1, "FD", 2) == 0) - { - int c; + auto const key = std::string_view{ id }; - if (getFDMInt(id[5], &c)) - { - tr_snprintf(buf, buflen, "Free Download Manager %d.%d.%d", charint(id[3]), charint(id[4]), c); - } - else - { - tr_snprintf(buf, buflen, "Free Download Manager %d.%d.x", charint(id[3]), charint(id[4])); - } - } - else if (strncmp(chid + 1, "FL", 2) == 0) - { - tr_snprintf(buf, buflen, "Folx %d.x", charint(id[3])); - } - else if (strncmp(chid + 1, "BN", 2) == 0) - { - tr_snprintf(buf, buflen, "Baidu Netdisk"); - } - else if (strncmp(chid + 1, "WS", 2) == 0) - { - no_version(buf, buflen, "HTTP Seed"); - } - - if (!tr_str_is_empty(buf)) - { - return buf; - } - } - - /* uTorrent will replace the trailing dash with an extra digit for longer version numbers */ - if (id[0] == '-') - { - if (strncmp(chid + 1, "UT", 2) == 0) - { - tr_snprintf( - buf, - buflen, - "\xc2\xb5Torrent %d.%d.%d%s", - strint(id + 3, 1), - strint(id + 4, 1), - strint(id + 5, 2), - getMnemonicEnd(id[7])); - } - else if (strncmp(chid + 1, "UM", 2) == 0) - { - tr_snprintf( - buf, - buflen, - "\xc2\xb5Torrent Mac %d.%d.%d%s", - strint(id + 3, 1), - strint(id + 4, 1), - strint(id + 5, 2), - getMnemonicEnd(id[7])); - } - else if (strncmp(chid + 1, "UE", 2) == 0) - { - tr_snprintf( - buf, - buflen, - "\xc2\xb5Torrent Embedded %d.%d.%d%s", - strint(id + 3, 1), - strint(id + 4, 1), - strint(id + 5, 2), - getMnemonicEnd(id[7])); - } - else if (strncmp(chid + 1, "UW", 2) == 0) - { - tr_snprintf( - buf, - buflen, - "\xc2\xb5Torrent Web %d.%d.%d%s", - strint(id + 3, 1), - strint(id + 4, 1), - strint(id + 5, 2), - getMnemonicEnd(id[7])); - } - - if (!tr_str_is_empty(buf)) - { - return buf; - } - } - - /* Mainline */ - if (isMainlineStyle(id)) - { - if (*id == 'M') - { - mainline_style(buf, buflen, "BitTorrent", id); - } - - if (*id == 'Q') - { - mainline_style(buf, buflen, "Queen Bee", id); - } - - if (!tr_str_is_empty(buf)) - { - return buf; - } - } - - if (decodeBitCometClient(buf, buflen, id)) + if (decodeShad0wClient(buf, buflen, key) || decodeBitCometClient(buf, buflen, key)) { return buf; } - /* Clients with no version */ - if (strncmp(chid, "AZ2500BT", 8) == 0) + if (!*id && strncmp(id + 2, "BS", 2) == 0) { - no_version(buf, buflen, "BitTyrant (Azureus Mod)"); - } - else if (strncmp(chid, "LIME", 4) == 0) - { - no_version(buf, buflen, "Limewire"); - } - else if (strncmp(chid, "martini", 7) == 0) - { - no_version(buf, buflen, "Martini Man"); - } - else if (strncmp(chid, "Pando", 5) == 0) - { - no_version(buf, buflen, "Pando"); - } - else if (strncmp(chid, "a00---0", 7) == 0) - { - no_version(buf, buflen, "Swarmy"); - } - else if (strncmp(chid, "a02---0", 7) == 0) - { - no_version(buf, buflen, "Swarmy"); - } - else if (strncmp(chid, "-G3", 3) == 0) - { - no_version(buf, buflen, "G3 Torrent"); - } - else if (strncmp(chid, "10-------", 9) == 0) - { - no_version(buf, buflen, "JVtorrent"); - } - else if (strncmp(chid, "346-", 4) == 0) - { - no_version(buf, buflen, "TorrentTopia"); - } - else if (strncmp(chid, "eX", 2) == 0) - { - no_version(buf, buflen, "eXeem"); - } - else if (strncmp(chid, "aria2-", 6) == 0) - { - no_version(buf, buflen, "aria2"); - } - else if (strncmp(chid, "-WT-", 4) == 0) - { - no_version(buf, buflen, "BitLet"); - } - else if (strncmp(chid, "-FG", 3) == 0) - { - two_major_two_minor(buf, buflen, "FlashGet", id + 3); - } - /* Everything else */ - else if (strncmp(chid, "S3", 2) == 0 && id[2] == '-' && id[4] == '-' && id[6] == '-') - { - tr_snprintf(buf, buflen, "Amazon S3 %c.%c.%c", id[3], id[5], id[7]); - } - else if (strncmp(chid, "OP", 2) == 0) - { - tr_snprintf(buf, buflen, "Opera (Build %c%c%c%c)", id[2], id[3], id[4], id[5]); - } - else if (strncmp(chid, "-ML", 3) == 0) - { - tr_snprintf(buf, buflen, "MLDonkey %c%c%c%c%c", id[3], id[4], id[5], id[6], id[7]); - } - else if (strncmp(chid, "DNA", 3) == 0) - { - tr_snprintf(buf, buflen, "BitTorrent DNA %d.%d.%d", strint(id + 3, 2), strint(id + 5, 2), strint(id + 7, 2)); - } - else if (strncmp(chid, "Plus", 4) == 0) - { - tr_snprintf(buf, buflen, "Plus! v2 %c.%c%c", id[4], id[5], id[6]); - } - else if (strncmp(chid, "XBT", 3) == 0) - { - tr_snprintf(buf, buflen, "XBT Client %c.%c.%c%s", id[3], id[4], id[5], getMnemonicEnd(id[6])); - } - else if (strncmp(chid, "Mbrst", 5) == 0) - { - tr_snprintf(buf, buflen, "burst! %c.%c.%c", id[5], id[7], id[9]); - } - else if (strncmp(chid, "btpd", 4) == 0) - { - tr_snprintf(buf, buflen, "BT Protocol Daemon %c%c%c", id[5], id[6], id[7]); - } - else if (strncmp(chid, "BLZ", 3) == 0) - { - tr_snprintf(buf, buflen, "Blizzard Downloader %d.%d", id[3] + 1, id[4]); - } - else if (strncmp(chid, "-SP", 3) == 0) - { - three_digits(buf, buflen, "BitSpirit", id + 3); - } - else if ('\0' == id[0] && strncmp(chid + 2, "BS", 2) == 0) - { - tr_snprintf(buf, buflen, "BitSpirit %u", (id[1] == 0 ? 1 : id[1])); - } - else if (strncmp(chid, "QVOD", 4) == 0) - { - four_digits(buf, buflen, "QVOD", id + 4); - } - else if (strncmp(chid, "-NE", 3) == 0) - { - four_digits(buf, buflen, "BT Next Evolution", id + 3); - } - else if (strncmp(chid, "TIX", 3) == 0) - { - two_major_two_minor(buf, buflen, "Tixati", id + 3); - } - else if (strncmp(chid, "A2", 2) == 0) - { - if (id[4] == '-' && id[6] == '-' && id[8] == '-') - { - tr_snprintf(buf, buflen, "aria2 %c.%c.%c", id[3], id[5], id[7]); - } - else if (id[4] == '-' && id[7] == '-' && id[9] == '-') - { - tr_snprintf(buf, buflen, "aria2 %c.%c%c.%c", id[3], id[5], id[6], id[8]); - } - else - { - no_version(buf, buflen, "aria2"); - } - } - else if (strncmp(chid, "-BL", 3) == 0) - { - tr_snprintf(buf, buflen, "BitLord %c.%c.%c-%c%c%c", id[3], id[4], id[5], id[6], id[7], id[8]); + tr_snprintf(buf, buflen, "BitSpirit %d", id[1] == '\0' ? 1 : int(id[1])); + return buf; } - /* Shad0w-style */ + struct Compare + { + bool operator()(std::string_view const& key, Client const& client) const + { + auto const key_lhs = std::string_view{ std::data(key), std::min(std::size(key), std::size(client.begins_with)) }; + return key_lhs < client.begins_with; + } + bool operator()(Client const& client, std::string_view const& key) const + { + auto const key_lhs = std::string_view{ std::data(key), std::min(std::size(key), std::size(client.begins_with)) }; + return client.begins_with < key_lhs; + } + }; + + auto eq = std::equal_range(std::begin(Clients), std::end(Clients), key, Compare{}); + if (eq.first != std::end(Clients) && eq.first != eq.second) + { + eq.first->formatter(buf, buflen, eq.first->name, id); + return buf; + } + + // no match if (tr_str_is_empty(buf)) { - int a; - int b; - int c; - - if (strchr("AOQRSTU", id[0]) != nullptr && getShadowInt(id[1], &a) && getShadowInt(id[2], &b) && - getShadowInt(id[3], &c)) - { - char const* name = nullptr; - - switch (id[0]) - { - case 'A': - name = "ABC"; - break; - - case 'O': - name = "Osprey"; - break; - - case 'Q': - name = "BTQueue"; - break; - - case 'R': - name = "Tribler"; - break; - - case 'S': - name = "Shad0w"; - break; - - case 'T': - name = "BitTornado"; - break; - - case 'U': - name = "UPnP NAT Bit Torrent"; - break; - } - - if (name != nullptr) - { - tr_snprintf(buf, buflen, "%s %d.%d.%d", name, a, b, c); - return buf; - } - } - } - - /* No match */ - if (tr_str_is_empty(buf)) - { - char out[32]; - char* walk = out; + auto out = std::array{}; + char* walk = std::data(out); + char const* const begin = walk; + char const* const end = begin + std::size(out); for (size_t i = 0; i < 8; ++i) { - char const c = chid[i]; + char const c = id[i]; if (isprint((unsigned char)c)) { @@ -967,13 +677,12 @@ char* tr_clientForId(char* buf, size_t buflen, void const* id_in) } else { - tr_snprintf(walk, out + sizeof(out) - walk, "%%%02X", (unsigned int)c); + tr_snprintf(walk, end - walk, "%%%02X", (unsigned int)c); walk += 3; } } - *walk = '\0'; - tr_strlcpy(buf, out, buflen); + buf_append(buf, buflen, std::string_view(begin, walk - begin)); } return buf; diff --git a/qt/DetailsDialog.cc.orig b/qt/DetailsDialog.cc.orig deleted file mode 100644 index 89769fea3..000000000 --- a/qt/DetailsDialog.cc.orig +++ /dev/null @@ -1,1569 +0,0 @@ -/* - * This file Copyright (C) 2009-2015 Mnemosyne LLC - * - * It may be used under the GNU GPL versions 2 or 3 - * or any future license endorsed by Mnemosyne LLC. - * - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include // tr_getRatio() - -#include "ColumnResizer.h" -#include "DetailsDialog.h" -#include "Formatter.h" -#include "Prefs.h" -#include "Session.h" -#include "SqueezeLabel.h" -#include "Torrent.h" -#include "TorrentModel.h" -#include "TrackerDelegate.h" -#include "TrackerModel.h" -#include "TrackerModelFilter.h" -#include "Utils.h" - -class Prefs; -class Session; - -/**** -***** -****/ - -namespace -{ - -int constexpr DebounceIntervalMSec = 100; -int constexpr RefreshIntervalMSec = 4000; - -char const constexpr* const PrefKey = "pref_key"; - -enum // peer columns -{ - COL_LOCK, - COL_UP, - COL_DOWN, - COL_PERCENT, - COL_STATUS, - COL_ADDRESS, - COL_CLIENT, - N_COLUMNS -}; - -int measureViewItem(QTreeWidget const* view, int column, QString const& text) -{ - QTreeWidgetItem const* header_item = view->headerItem(); - - int const item_width = Utils::measureViewItem(view, text); - int const header_width = Utils::measureHeaderItem(view->header(), header_item->text(column)); - - return std::max(item_width, header_width); -} - -QString collateAddress(QString const& address) -{ - QString collated; - - QHostAddress ip_address; - if (ip_address.setAddress(address)) - { - if (ip_address.protocol() == QAbstractSocket::IPv4Protocol) - { - quint32 const ipv4_address = ip_address.toIPv4Address(); - collated = QStringLiteral("1-") + - QString::fromUtf8(QByteArray::number(ipv4_address, 16).rightJustified(8, '0')); - } - else if (ip_address.protocol() == QAbstractSocket::IPv6Protocol) - { - Q_IPV6ADDR const ipv6_address = ip_address.toIPv6Address(); - QByteArray tmp(16, '\0'); - - for (int i = 0; i < 16; ++i) - { - tmp[i] = ipv6_address[i]; - } - - collated = QStringLiteral("2-") + QString::fromUtf8(tmp.toHex()); - } - } - - if (collated.isEmpty()) - { - collated = QStringLiteral("3-") + address.toLower(); - } - - return collated; -} - -} // namespace - -/*** -**** -***/ - -class PeerItem : public QTreeWidgetItem -{ - Peer peer_; - QString mutable collated_address_; - QString status_; - -public: - explicit PeerItem(Peer p) : - peer_(std::move(p)) - { - } - - void refresh(Peer const& p) - { - if (p.address != peer_.address) - { - collated_address_.clear(); - } - - peer_ = p; - } - - void setStatus(QString const& s) - { - status_ = s; - } - - bool operator <(QTreeWidgetItem const& other) const override - { - auto const* i = dynamic_cast(&other); - auto const* tw = treeWidget(); - int const column = tw != nullptr ? tw->sortColumn() : 0; - - assert(i != nullptr); - - switch (column) - { - case COL_UP: - return peer_.rate_to_peer < i->peer_.rate_to_peer; - - case COL_DOWN: - return peer_.rate_to_client < i->peer_.rate_to_client; - - case COL_PERCENT: - return peer_.progress < i->peer_.progress; - - case COL_STATUS: - return status_ < i->status_; - - case COL_CLIENT: - return peer_.client_name < i->peer_.client_name; - - case COL_LOCK: - return peer_.is_encrypted && !i->peer_.is_encrypted; - - default: - return address() < i->address(); - } - } - -private: - QString const& address() const - { - if (collated_address_.isEmpty()) - { - collated_address_ = collateAddress(peer_.address); - } - - return collated_address_; - } -}; - -/*** -**** -***/ - -QIcon DetailsDialog::getStockIcon(QString const& freedesktop_name, int fallback) const -{ - QIcon icon = QIcon::fromTheme(freedesktop_name); - - if (icon.isNull()) - { - icon = style()->standardIcon(QStyle::StandardPixmap(fallback), nullptr, this); - } - - return icon; -} - -DetailsDialog::DetailsDialog(Session& session, Prefs& prefs, TorrentModel const& model, QWidget* parent) : - BaseDialog(parent), - session_(session), - prefs_(prefs), - model_(model) -{ - ui_.setupUi(this); - - initInfoTab(); - initPeersTab(); - initTrackerTab(); - initFilesTab(); - initOptionsTab(); - - adjustSize(); - ui_.commentBrowser->setMaximumHeight(QWIDGETSIZE_MAX); - - static std::array constexpr InitKeys = - { - Prefs::SHOW_TRACKER_SCRAPES, - Prefs::SHOW_BACKUP_TRACKERS - }; - - for (int const key : InitKeys) - { - refreshPref(key); - } - - connect(&model_, &TorrentModel::torrentsChanged, this, &DetailsDialog::onTorrentsChanged); - connect(&model_, &TorrentModel::torrentsEdited, this, &DetailsDialog::onTorrentsEdited); - connect(&prefs_, &Prefs::changed, this, &DetailsDialog::refreshPref); - - // call refreshModel periodically - connect(&model_timer_, &QTimer::timeout, this, &DetailsDialog::refreshModel); - model_timer_.setSingleShot(false); - model_timer_.start(RefreshIntervalMSec); - refreshModel(); - - // set up the debounce timer - connect(&ui_debounce_timer_, &QTimer::timeout, this, &DetailsDialog::refreshUI); - ui_debounce_timer_.setSingleShot(true); -} - -DetailsDialog::~DetailsDialog() -{ - tracker_delegate_->deleteLater(); - tracker_filter_->deleteLater(); - tracker_model_->deleteLater(); -} - -void DetailsDialog::setIds(torrent_ids_t const& ids) -{ - if (ids != ids_) - { - setEnabled(false); - ui_.filesView->clear(); - - ids_ = ids; - session_.refreshDetailInfo(ids_); - tracker_model_->refresh(model_, ids_); - - refreshModel(); - refreshUI(); - } -} - -void DetailsDialog::refreshPref(int key) -{ - QString str; - - switch (key) - { - case Prefs::SHOW_TRACKER_SCRAPES: - { - QItemSelectionModel* selection_model(ui_.trackersView->selectionModel()); - QItemSelection const selection(selection_model->selection()); - QModelIndex const current_index(selection_model->currentIndex()); - tracker_delegate_->setShowMore(prefs_.getBool(key)); - selection_model->clear(); - ui_.trackersView->reset(); - selection_model->select(selection, QItemSelectionModel::Select); - selection_model->setCurrentIndex(current_index, QItemSelectionModel::NoUpdate); - break; - } - - case Prefs::SHOW_BACKUP_TRACKERS: - tracker_filter_->setShowBackupTrackers(prefs_.getBool(key)); - break; - - default: - break; - } -} - -/*** -**** -***/ - -void DetailsDialog::refreshModel() -{ - if (!ids_.empty()) - { - session_.refreshExtraStats(ids_); - } -} - -void DetailsDialog::onTorrentsEdited(torrent_ids_t const& ids) -{ - // std::set_intersection requires sorted inputs - auto a = std::vector{ ids.begin(), ids.end() }; - std::sort(std::begin(a), std::end(a)); - auto b = std::vector{ ids_.begin(), ids_.end() }; - std::sort(std::begin(b), std::end(b)); - - // are any of the edited torrents on display here? - torrent_ids_t interesting_ids; - std::set_intersection(std::begin(a), std::end(a), - std::begin(b), std::end(b), - std::inserter(interesting_ids, std::begin(interesting_ids))); - - if (!interesting_ids.empty()) - { - session_.refreshDetailInfo(interesting_ids); - } -} - -void DetailsDialog::onTorrentsChanged(torrent_ids_t const& ids, Torrent::fields_t const& fields) -{ - Q_UNUSED(fields) - - if (ui_debounce_timer_.isActive()) - { - return; - } - - if (!std::any_of(ids.begin(), ids.end(), [this](auto const& id) { return ids_.count(id) != 0; })) - { - return; - } - - ui_debounce_timer_.start(DebounceIntervalMSec); -} - -void DetailsDialog::onSessionCalled(Session::Tag tag) -{ - if ((pending_changes_tags_.erase(tag) > 0) && canEdit()) - { - // no pending changes left, so stop listening - disconnect(pending_changes_connection_); - pending_changes_connection_ = {}; - - refreshModel(); - } -} - -namespace -{ - -void setIfIdle(QComboBox* box, int i) -{ - if (!box->hasFocus()) - { - box->blockSignals(true); - box->setCurrentIndex(i); - box->blockSignals(false); - } -} - -void setIfIdle(QDoubleSpinBox* spin, double value) -{ - if (!spin->hasFocus()) - { - spin->blockSignals(true); - spin->setValue(value); - spin->blockSignals(false); - } -} - -void setIfIdle(QSpinBox* spin, int value) -{ - if (!spin->hasFocus()) - { - spin->blockSignals(true); - spin->setValue(value); - spin->blockSignals(false); - } -} - -} // namespace - -void DetailsDialog::refreshUI() -{ - bool const single = ids_.size() == 1; - QString const blank; - QFontMetrics const fm(fontMetrics()); - QList torrents; - QString string; - QString const none = tr("None"); - QString const mixed = tr("Mixed"); - QString const unknown = tr("Unknown"); - auto const now = time(nullptr); - - // build a list of torrents - for (int const id : ids_) - { - Torrent const* tor = model_.getTorrentFromId(id); - - if (tor != nullptr) - { - torrents << tor; - } - } - - /// - /// activity tab - /// - - // myStateLabel - if (torrents.empty()) - { - string = none; - } - else - { - bool is_mixed = false; - bool all_paused = true; - bool all_finished = true; - tr_torrent_activity const baseline = torrents[0]->getActivity(); - - for (Torrent const* const t : torrents) - { - tr_torrent_activity const activity = t->getActivity(); - - if (activity != baseline) - { - is_mixed = true; - } - - if (activity != TR_STATUS_STOPPED) - { - all_paused = all_finished = false; - } - - if (!t->isFinished()) - { - all_finished = false; - } - } - - if (is_mixed) - { - string = mixed; - } - else if (all_finished) - { - string = tr("Finished"); - } - else if (all_paused) - { - string = tr("Paused"); - } - else - { - string = torrents[0]->activityString(); - } - } - - ui_.stateValueLabel->setText(string); - QString const state_string = string; - - // myHaveLabel - uint64_t size_when_done = 0; - uint64_t available = 0; - - if (torrents.empty()) - { - string = none; - } - else - { - uint64_t left_until_done = 0; - int64_t have_total = 0; - int64_t have_verified = 0; - int64_t have_unverified = 0; - int64_t verified_pieces = 0; - - for (Torrent const* const t : torrents) - { - if (t->hasMetadata()) - { - have_total += t->haveTotal(); - have_unverified += t->haveUnverified(); - uint64_t const v = t->haveVerified(); - have_verified += v; - - if (t->pieceSize()) - { - verified_pieces += v / t->pieceSize(); - } - - size_when_done += t->sizeWhenDone(); - left_until_done += t->leftUntilDone(); - available += t->sizeWhenDone() - t->leftUntilDone() + t->desiredAvailable(); - } - } - - double const d = size_when_done == 0 ? - 100.0 : - 100.0 * static_cast(size_when_done - left_until_done) / static_cast(size_when_done); - auto const pct = Formatter::get().percentToString(d); - - if (have_unverified == 0 && left_until_done == 0) - { - //: Text following the "Have:" label in torrent properties dialog; - //: %1 is amount of downloaded and verified data - string = tr("%1 (100%)").arg(Formatter::get().sizeToString(have_verified)); - } - else if (have_unverified == 0) - { - //: Text following the "Have:" label in torrent properties dialog; - //: %1 is amount of downloaded and verified data, - //: %2 is overall size of torrent data, - //: %3 is percentage (%1/%2*100) - string = tr("%1 of %2 (%3%)").arg(Formatter::get().sizeToString(have_verified)).arg(Formatter::get().sizeToString( - size_when_done)). - arg(pct); - } - else - { - //: Text following the "Have:" label in torrent properties dialog; - //: %1 is amount of downloaded data (both verified and unverified), - //: %2 is overall size of torrent data, - //: %3 is percentage (%1/%2*100), - //: %4 is amount of downloaded but not yet verified data - string = tr("%1 of %2 (%3%), %4 Unverified").arg(Formatter::get().sizeToString(have_verified + have_unverified)). - arg(Formatter::get().sizeToString(size_when_done)).arg(pct).arg(Formatter::get().sizeToString(have_unverified)); - } - } - - ui_.haveValueLabel->setText(string); - - // myAvailabilityLabel - if (torrents.empty() || size_when_done == 0) - { - string = none; - } - else - { - auto const percent = 100.0 * static_cast(available) / static_cast(size_when_done); - string = QStringLiteral("%1%").arg(Formatter::get().percentToString(percent)); - } - - ui_.availabilityValueLabel->setText(string); - - // myDownloadedLabel - if (torrents.empty()) - { - string = none; - } - else - { - uint64_t d = 0; - uint64_t f = 0; - - for (Torrent const* const t : torrents) - { - d += t->downloadedEver(); - f += t->failedEver(); - } - - QString const dstr = Formatter::get().sizeToString(d); - QString const fstr = Formatter::get().sizeToString(f); - - if (f != 0) - { - string = tr("%1 (%2 corrupt)").arg(dstr).arg(fstr); - } - else - { - string = dstr; - } - } - - ui_.downloadedValueLabel->setText(string); - - // myUploadedLabel - if (torrents.empty()) - { - string = none; - } - else - { - uint64_t u = 0; - uint64_t d = 0; - - for (Torrent const* const t : torrents) - { - u += t->uploadedEver(); - d += t->downloadedEver(); - } - - string = tr("%1 (Ratio: %2)").arg(Formatter::get().sizeToString(u)).arg(Formatter::get().ratioToString(tr_getRatio(u, - d))); - } - - ui_.uploadedValueLabel->setText(string); - - // myRunTimeLabel - if (torrents.empty()) - { - string = none; - } - else - { - bool all_paused = true; - auto baseline = torrents[0]->lastStarted(); - - for (Torrent const* const t : torrents) - { - if (baseline != t->lastStarted()) - { - baseline = 0; - } - - if (!t->isPaused()) - { - all_paused = false; - } - } - - if (all_paused) - { - string = state_string; // paused || finished - } - else if (baseline == 0) - { - string = mixed; - } - else - { - auto const seconds = int(std::difftime(now, baseline)); - string = Formatter::get().timeToString(seconds); - } - } - - ui_.runningTimeValueLabel->setText(string); - - // myETALabel - string.clear(); - - if (torrents.empty()) - { - string = none; - } - else - { - int baseline = torrents[0]->getETA(); - - for (Torrent const* const t : torrents) - { - if (baseline != t->getETA()) - { - string = mixed; - break; - } - } - - if (string.isEmpty()) - { - if (baseline < 0) - { - string = tr("Unknown"); - } - else - { - string = Formatter::get().timeToString(baseline); - } - } - } - - ui_.remainingTimeValueLabel->setText(string); - - // myLastActivityLabel - if (torrents.empty()) - { - string = none; - } - else - { - auto latest = torrents[0]->lastActivity(); - - for (Torrent const* const t : torrents) - { - auto const dt = t->lastActivity(); - - if (latest < dt) - { - latest = dt; - } - } - - auto const seconds = int(std::difftime(now, latest)); - - if (seconds < 0) - { - string = none; - } - else if (seconds < 5) - { - string = tr("Active now"); - } - else - { - string = tr("%1 ago").arg(Formatter::get().timeToString(seconds)); - } - } - - ui_.lastActivityValueLabel->setText(string); - - if (torrents.empty()) - { - string = none; - } - else - { - string = torrents[0]->getError(); - - for (Torrent const* const t : torrents) - { - if (string != t->getError()) - { - string = mixed; - break; - } - } - } - - if (string.isEmpty()) - { - string = none; - } - - ui_.errorValueLabel->setText(string); - - /// - /// information tab - /// - - // mySizeLabel - if (torrents.empty()) - { - string = none; - } - else - { - int pieces = 0; - uint64_t size = 0; - uint32_t piece_size = torrents[0]->pieceSize(); - - for (Torrent const* const t : torrents) - { - pieces += t->pieceCount(); - size += t->totalSize(); - - if (piece_size != t->pieceSize()) - { - piece_size = 0; - } - } - - if (size == 0) - { - string = none; - } - else if (piece_size > 0) - { - string = tr("%1 (%Ln pieces @ %2)", "", pieces).arg(Formatter::get().sizeToString(size)). - arg(Formatter::get().memToString(piece_size)); - } - else - { - string = tr("%1 (%Ln pieces)", "", pieces).arg(Formatter::get().sizeToString(size)); - } - } - - ui_.sizeValueLabel->setText(string); - - // myHashLabel - if (torrents.empty()) - { - string = none; - } - else if (torrents.size() > 1) - { - string = mixed; - } - else - { - string = torrents.front()->hash().toString(); - } - - ui_.hashValueLabel->setText(string); - - // myPrivacyLabel - string = none; - - if (!torrents.empty()) - { - bool b = torrents[0]->isPrivate(); - string = b ? tr("Private to this tracker -- DHT and PEX disabled") : tr("Public torrent"); - - for (Torrent const* const t : torrents) - { - if (b != t->isPrivate()) - { - string = mixed; - break; - } - } - } - - ui_.privacyValueLabel->setText(string); - - // myCommentBrowser - string = none; - bool is_comment_mixed = false; - - if (!torrents.empty()) - { - string = torrents[0]->comment(); - - for (Torrent const* const t : torrents) - { - if (string != t->comment()) - { - string = mixed; - is_comment_mixed = true; - break; - } - } - } - - if (ui_.commentBrowser->toPlainText() != string) - { - ui_.commentBrowser->setText(string); - } - - ui_.commentBrowser->setEnabled(!is_comment_mixed && !string.isEmpty()); - - // myOriginLabel - string = none; - - if (!torrents.empty()) - { - bool mixed_creator = false; - bool mixed_date = false; - QString const creator = torrents[0]->creator(); - auto const date = torrents[0]->dateCreated(); - - for (Torrent const* const t : torrents) - { - mixed_creator |= (creator != t->creator()); - mixed_date |= (date != t->dateCreated()); - } - - bool const empty_creator = creator.isEmpty(); - bool const empty_date = date <= 0; - - if (mixed_creator || mixed_date) - { - string = mixed; - } - else if (empty_creator && empty_date) - { - string = tr("N/A"); - } - else if (empty_date && !empty_creator) - { - string = tr("Created by %1").arg(creator); - } - else if (empty_creator && !empty_date) - { - auto const date_str = QDateTime::fromSecsSinceEpoch(date).toString(); - string = tr("Created on %1").arg(date_str); - } - else - { - auto const date_str = QDateTime::fromSecsSinceEpoch(date).toString(); - string = tr("Created by %1 on %2").arg(creator).arg(date_str); - } - } - - ui_.originValueLabel->setText(string); - - // myLocationLabel - string = none; - - if (!torrents.empty()) - { - string = torrents[0]->getPath(); - - for (Torrent const* const t : torrents) - { - if (string != t->getPath()) - { - string = mixed; - break; - } - } - } - - ui_.locationValueLabel->setText(string); - - /// - /// Options Tab - /// - - if (canEdit() && !torrents.empty()) - { - int i; - bool uniform; - bool baseline_flag; - int baseline_int; - Torrent const& baseline = *torrents.front(); - - // mySessionLimitCheck - uniform = true; - baseline_flag = baseline.honorsSessionLimits(); - - for (Torrent const* const tor : torrents) - { - if (baseline_flag != tor->honorsSessionLimits()) - { - uniform = false; - break; - } - } - - ui_.sessionLimitCheck->setChecked(uniform && baseline_flag); - - // mySingleDownCheck - uniform = true; - baseline_flag = baseline.downloadIsLimited(); - - for (Torrent const* const tor : torrents) - { - if (baseline_flag != tor->downloadIsLimited()) - { - uniform = false; - break; - } - } - - ui_.singleDownCheck->setChecked(uniform && baseline_flag); - - // mySingleUpCheck - uniform = true; - baseline_flag = baseline.uploadIsLimited(); - - for (Torrent const* const tor : torrents) - { - if (baseline_flag != tor->uploadIsLimited()) - { - uniform = false; - break; - } - } - - ui_.singleUpCheck->setChecked(uniform && baseline_flag); - - // myBandwidthPriorityCombo - uniform = true; - baseline_int = baseline.getBandwidthPriority(); - - for (Torrent const* const tor : torrents) - { - if (baseline_int != tor->getBandwidthPriority()) - { - uniform = false; - break; - } - } - - if (uniform) - { - i = ui_.bandwidthPriorityCombo->findData(baseline_int); - } - else - { - i = -1; - } - - setIfIdle(ui_.bandwidthPriorityCombo, i); - - setIfIdle(ui_.singleDownSpin, int(baseline.downloadLimit().getKBps())); - setIfIdle(ui_.singleUpSpin, int(baseline.uploadLimit().getKBps())); - setIfIdle(ui_.peerLimitSpin, baseline.peerLimit()); - } - - if (!torrents.empty()) - { - Torrent const& baseline = *torrents.front(); - - // ratio - bool uniform = true; - int baseline_int = baseline.seedRatioMode(); - - for (Torrent const* const tor : torrents) - { - if (baseline_int != tor->seedRatioMode()) - { - uniform = false; - break; - } - } - - setIfIdle(ui_.ratioCombo, uniform ? ui_.ratioCombo->findData(baseline_int) : -1); - ui_.ratioSpin->setVisible(uniform && baseline_int == TR_RATIOLIMIT_SINGLE); - - setIfIdle(ui_.ratioSpin, baseline.seedRatioLimit()); - - // idle - uniform = true; - baseline_int = baseline.seedIdleMode(); - - for (Torrent const* const tor : torrents) - { - if (baseline_int != tor->seedIdleMode()) - { - uniform = false; - break; - } - } - - setIfIdle(ui_.idleCombo, uniform ? ui_.idleCombo->findData(baseline_int) : -1); - ui_.idleSpin->setVisible(uniform && baseline_int == TR_RATIOLIMIT_SINGLE); - - setIfIdle(ui_.idleSpin, baseline.seedIdleLimit()); - onIdleLimitChanged(); - } - - /// - /// Tracker tab - /// - - tracker_model_->refresh(model_, ids_); - - /// - /// Peers tab - /// - - QMap peers2; - QList new_items; - - for (Torrent const* const t : torrents) - { - QString const id_str(QString::number(t->id())); - PeerList peers = t->peers(); - - for (Peer const& peer : peers) - { - QString const key = id_str + QLatin1Char(':') + peer.address; - PeerItem* item = static_cast(peers_.value(key, nullptr)); - - if (item == nullptr) // new peer has connected - { - item = new PeerItem(peer); - item->setTextAlignment(COL_UP, Qt::AlignRight | Qt::AlignVCenter); - item->setTextAlignment(COL_DOWN, Qt::AlignRight | Qt::AlignVCenter); - item->setTextAlignment(COL_PERCENT, Qt::AlignRight | Qt::AlignVCenter); - item->setIcon(COL_LOCK, peer.is_encrypted ? icon_encrypted_ : icon_unencrypted_); - item->setToolTip(COL_LOCK, peer.is_encrypted ? tr("Encrypted connection") : QString()); - item->setText(COL_ADDRESS, peer.address); - item->setText(COL_CLIENT, peer.client_name); - new_items << item; - } - - QString const code = peer.flags; - item->setStatus(code); - item->refresh(peer); - - QString code_tip; - - for (QChar const ch : code) - { - QString txt; - - switch (ch.unicode()) - { - case 'O': - txt = tr("Optimistic unchoke"); - break; - - case 'D': - txt = tr("Downloading from this peer"); - break; - - case 'd': - txt = tr("We would download from this peer if they would let us"); - break; - - case 'U': - txt = tr("Uploading to peer"); - break; - - case 'u': - txt = tr("We would upload to this peer if they asked"); - break; - - case 'K': - txt = tr("Peer has unchoked us, but we're not interested"); - break; - - case '?': - txt = tr("We unchoked this peer, but they're not interested"); - break; - - case 'E': - txt = tr("Encrypted connection"); - break; - - case 'H': - txt = tr("Peer was discovered through DHT"); - break; - - case 'X': - txt = tr("Peer was discovered through Peer Exchange (PEX)"); - break; - - case 'I': - txt = tr("Peer is an incoming connection"); - break; - - case 'T': - txt = tr("Peer is connected over uTP"); - break; - - default: - break; - } - - if (!txt.isEmpty()) - { - code_tip += QStringLiteral("%1: %2\n").arg(ch).arg(txt); - } - } - - if (!code_tip.isEmpty()) - { - code_tip.resize(code_tip.size() - 1); // eat the trailing linefeed - } - - item->setText(COL_UP, peer.rate_to_peer.isZero() ? QString() : Formatter::get().speedToString(peer.rate_to_peer)); - item->setText(COL_DOWN, - peer.rate_to_client.isZero() ? QString() : Formatter::get().speedToString(peer.rate_to_client)); - item->setText(COL_PERCENT, peer.progress > 0 ? QStringLiteral("%1%").arg(int(peer.progress * 100.0)) : - QString()); - item->setText(COL_STATUS, code); - item->setToolTip(COL_STATUS, code_tip); - - peers2.insert(key, item); - } - } - - ui_.peersView->addTopLevelItems(new_items); - - for (QString const& key : peers_.keys()) - { - if (!peers2.contains(key)) // old peer has disconnected - { - QTreeWidgetItem* item = peers_.value(key, nullptr); - ui_.peersView->takeTopLevelItem(ui_.peersView->indexOfTopLevelItem(item)); - delete item; - } - } - - peers_ = peers2; - - if (single) - { - ui_.filesView->update(torrents[0]->files(), canEdit()); - } - else - { - ui_.filesView->clear(); - } - - setEnabled(true); -} - -void DetailsDialog::setEnabled(bool enabled) -{ - ui_.tabs->setEnabled(enabled); -} - -/*** -**** -***/ - -void DetailsDialog::initInfoTab() -{ - int const h = QFontMetrics(ui_.commentBrowser->font()).lineSpacing() * 4; - ui_.commentBrowser->setFixedHeight(h); - - auto* cr = new ColumnResizer(this); - cr->addLayout(ui_.activitySectionLayout); - cr->addLayout(ui_.detailsSectionLayout); - cr->update(); -} - -/*** -**** -***/ - -void DetailsDialog::onShowTrackerScrapesToggled(bool val) -{ - prefs_.set(Prefs::SHOW_TRACKER_SCRAPES, val); -} - -void DetailsDialog::onShowBackupTrackersToggled(bool val) -{ - prefs_.set(Prefs::SHOW_BACKUP_TRACKERS, val); -} - -void DetailsDialog::onHonorsSessionLimitsToggled(bool val) -{ - torrentSet(TR_KEY_honorsSessionLimits, val); -} - -void DetailsDialog::onDownloadLimitedToggled(bool val) -{ - torrentSet(TR_KEY_downloadLimited, val); -} - -void DetailsDialog::onSpinBoxEditingFinished() -{ - QObject const* spin = sender(); - tr_quark const key = spin->property(PrefKey).toInt(); - auto const* d = qobject_cast(spin); - - if (d != nullptr) - { - torrentSet(key, d->value()); - } - else - { - torrentSet(key, qobject_cast(spin)->value()); - } -} - -void DetailsDialog::onUploadLimitedToggled(bool val) -{ - torrentSet(TR_KEY_uploadLimited, val); -} - -void DetailsDialog::onIdleModeChanged(int index) -{ - int const val = ui_.idleCombo->itemData(index).toInt(); - torrentSet(TR_KEY_seedIdleMode, val); -} - -void DetailsDialog::onIdleLimitChanged() -{ - //: Spin box suffix, "Stop seeding if idle for: [ 5 minutes ]" (includes leading space after the number, if needed) - QString const units_suffix = tr(" minute(s)", nullptr, ui_.idleSpin->value()); - - if (ui_.idleSpin->suffix() != units_suffix) - { - ui_.idleSpin->setSuffix(units_suffix); - } -} - -void DetailsDialog::onRatioModeChanged(int index) -{ - int const val = ui_.ratioCombo->itemData(index).toInt(); - torrentSet(TR_KEY_seedRatioMode, val); -} - -void DetailsDialog::onBandwidthPriorityChanged(int index) -{ - if (index != -1) - { - int const priority = ui_.bandwidthPriorityCombo->itemData(index).toInt(); - torrentSet(TR_KEY_bandwidthPriority, priority); - } -} - -void DetailsDialog::onTrackerSelectionChanged() -{ - int const selection_count = ui_.trackersView->selectionModel()->selectedRows().size(); - ui_.editTrackerButton->setEnabled(selection_count == 1); - ui_.removeTrackerButton->setEnabled(selection_count > 0); -} - -void DetailsDialog::onAddTrackerClicked() -{ - bool ok = false; - QString const url = QInputDialog::getText(this, tr("Add URL "), tr("Add tracker announce URL:"), QLineEdit::Normal, - QString(), &ok); - - if (!ok) - { - // user pressed "cancel" -- noop - } - else if (!QUrl(url).isValid()) - { - QMessageBox::warning(this, tr("Error"), tr("Invalid URL \"%1\"").arg(url)); - } - else - { - torrent_ids_t ids; - - for (int const id : ids_) - { - if (tracker_model_->find(id, url) == -1) - { - ids.insert(id); - } - } - - if (ids.empty()) // all the torrents already have this tracker - { - QMessageBox::warning(this, tr("Error"), tr("Tracker already exists.")); - } - else - { - auto const urls = QStringList{ url }; - torrentSet(ids, TR_KEY_trackerAdd, urls); - } - } -} - -void DetailsDialog::onEditTrackerClicked() -{ - QItemSelectionModel* selection_model = ui_.trackersView->selectionModel(); - QModelIndexList selected_rows = selection_model->selectedRows(); - assert(selected_rows.size() == 1); - QModelIndex i = selection_model->currentIndex(); - auto const tracker_info = ui_.trackersView->model()->data(i, TrackerModel::TrackerRole).value(); - - bool ok = false; - QString const newval = QInputDialog::getText(this, tr("Edit URL "), tr("Edit tracker announce URL:"), QLineEdit::Normal, - tracker_info.st.announce, &ok); - - if (!ok) - { - // user pressed "cancel" -- noop - } - else if (!QUrl(newval).isValid()) - { - QMessageBox::warning(this, tr("Error"), tr("Invalid URL \"%1\"").arg(newval)); - } - else - { - torrent_ids_t ids{ tracker_info.torrent_id }; - - QPair const id_url = qMakePair(tracker_info.st.id, newval); - - torrentSet(ids, TR_KEY_trackerReplace, id_url); - } -} - -void DetailsDialog::onRemoveTrackerClicked() -{ - // make a map of torrentIds to announce URLs to remove - QItemSelectionModel* selection_model = ui_.trackersView->selectionModel(); - QModelIndexList selected_rows = selection_model->selectedRows(); - QMap torrent_id_to_tracker_ids; - - for (QModelIndex const& i : selected_rows) - { - auto const inf = ui_.trackersView->model()->data(i, TrackerModel::TrackerRole).value(); - torrent_id_to_tracker_ids.insertMulti(inf.torrent_id, inf.st.id); - } - - // batch all of a tracker's torrents into one command - for (int const id : torrent_id_to_tracker_ids.uniqueKeys()) - { - torrent_ids_t const ids{ id }; - torrentSet(ids, TR_KEY_trackerRemove, torrent_id_to_tracker_ids.values(id)); - } - - selection_model->clearSelection(); -} - -void DetailsDialog::initOptionsTab() -{ - auto const speed_unit_str = Formatter::get().unitStr(Formatter::SPEED, Formatter::KB); - - ui_.singleDownSpin->setSuffix(QStringLiteral(" %1").arg(speed_unit_str)); - ui_.singleUpSpin->setSuffix(QStringLiteral(" %1").arg(speed_unit_str)); - - ui_.singleDownSpin->setProperty(PrefKey, TR_KEY_downloadLimit); - ui_.singleUpSpin->setProperty(PrefKey, TR_KEY_uploadLimit); - ui_.ratioSpin->setProperty(PrefKey, TR_KEY_seedRatioLimit); - ui_.idleSpin->setProperty(PrefKey, TR_KEY_seedIdleLimit); - ui_.peerLimitSpin->setProperty(PrefKey, TR_KEY_peer_limit); - - ui_.bandwidthPriorityCombo->addItem(tr("High"), TR_PRI_HIGH); - ui_.bandwidthPriorityCombo->addItem(tr("Normal"), TR_PRI_NORMAL); - ui_.bandwidthPriorityCombo->addItem(tr("Low"), TR_PRI_LOW); - - ui_.ratioCombo->addItem(tr("Use Global Settings"), TR_RATIOLIMIT_GLOBAL); - ui_.ratioCombo->addItem(tr("Seed regardless of ratio"), TR_RATIOLIMIT_UNLIMITED); - ui_.ratioCombo->addItem(tr("Stop seeding at ratio:"), TR_RATIOLIMIT_SINGLE); - - ui_.idleCombo->addItem(tr("Use Global Settings"), TR_IDLELIMIT_GLOBAL); - ui_.idleCombo->addItem(tr("Seed regardless of activity"), TR_IDLELIMIT_UNLIMITED); - ui_.idleCombo->addItem(tr("Stop seeding if idle for:"), TR_IDLELIMIT_SINGLE); - - auto* cr = new ColumnResizer(this); - cr->addLayout(ui_.speedSectionLayout); - cr->addLayout(ui_.seedingLimitsSectionRatioLayout); - cr->addLayout(ui_.seedingLimitsSectionIdleLayout); - cr->addLayout(ui_.peerConnectionsSectionLayout); - cr->update(); - - void (QComboBox::* combo_index_changed)(int) = &QComboBox::currentIndexChanged; - void (QSpinBox::* spin_value_changed)(int) = &QSpinBox::valueChanged; - connect(ui_.bandwidthPriorityCombo, combo_index_changed, this, &DetailsDialog::onBandwidthPriorityChanged); - connect(ui_.idleCombo, combo_index_changed, this, &DetailsDialog::onIdleModeChanged); - connect(ui_.idleSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished); - connect(ui_.idleSpin, spin_value_changed, this, &DetailsDialog::onIdleLimitChanged); - connect(ui_.peerLimitSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished); - connect(ui_.ratioCombo, combo_index_changed, this, &DetailsDialog::onRatioModeChanged); - connect(ui_.ratioSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished); - connect(ui_.sessionLimitCheck, &QCheckBox::clicked, this, &DetailsDialog::onHonorsSessionLimitsToggled); - connect(ui_.singleDownCheck, &QCheckBox::clicked, this, &DetailsDialog::onDownloadLimitedToggled); - connect(ui_.singleDownSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished); - connect(ui_.singleUpCheck, &QCheckBox::clicked, this, &DetailsDialog::onUploadLimitedToggled); - connect(ui_.singleUpSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished); -} - -/*** -**** -***/ - -void DetailsDialog::initTrackerTab() -{ - tracker_model_ = new TrackerModel(); - tracker_filter_ = new TrackerModelFilter(); - tracker_filter_->setSourceModel(tracker_model_); - tracker_delegate_ = new TrackerDelegate(); - - ui_.trackersView->setModel(tracker_filter_); - ui_.trackersView->setItemDelegate(tracker_delegate_); - - ui_.addTrackerButton->setIcon(getStockIcon(QStringLiteral("list-add"), QStyle::SP_DialogOpenButton)); - ui_.editTrackerButton->setIcon(getStockIcon(QStringLiteral("document-properties"), QStyle::SP_DesktopIcon)); - ui_.removeTrackerButton->setIcon(getStockIcon(QStringLiteral("list-remove"), QStyle::SP_TrashIcon)); - - ui_.showTrackerScrapesCheck->setChecked(prefs_.getBool(Prefs::SHOW_TRACKER_SCRAPES)); - ui_.showBackupTrackersCheck->setChecked(prefs_.getBool(Prefs::SHOW_BACKUP_TRACKERS)); - - connect(ui_.addTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onAddTrackerClicked); - connect(ui_.editTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onEditTrackerClicked); - connect(ui_.removeTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onRemoveTrackerClicked); - connect(ui_.showBackupTrackersCheck, &QAbstractButton::clicked, this, &DetailsDialog::onShowBackupTrackersToggled); - connect(ui_.showTrackerScrapesCheck, &QAbstractButton::clicked, this, &DetailsDialog::onShowTrackerScrapesToggled); - connect( - ui_.trackersView->selectionModel(), &QItemSelectionModel::selectionChanged, this, - &DetailsDialog::onTrackerSelectionChanged); - - onTrackerSelectionChanged(); -} - -/*** -**** -***/ - -void DetailsDialog::initPeersTab() -{ - ui_.peersView->setHeaderLabels({ QString(), tr("Up"), tr("Down"), tr("%"), tr("Status"), tr("Address"), tr("Client") }); - ui_.peersView->sortByColumn(COL_ADDRESS, Qt::AscendingOrder); - - ui_.peersView->setColumnWidth(COL_LOCK, 20); - ui_.peersView->setColumnWidth(COL_UP, measureViewItem(ui_.peersView, COL_UP, QStringLiteral("1024 MiB/s"))); - ui_.peersView->setColumnWidth(COL_DOWN, measureViewItem(ui_.peersView, COL_DOWN, QStringLiteral("1024 MiB/s"))); - ui_.peersView->setColumnWidth(COL_PERCENT, measureViewItem(ui_.peersView, COL_PERCENT, QStringLiteral("100%"))); - ui_.peersView->setColumnWidth(COL_STATUS, measureViewItem(ui_.peersView, COL_STATUS, QStringLiteral("ODUK?EXI"))); - ui_.peersView->setColumnWidth(COL_ADDRESS, measureViewItem(ui_.peersView, COL_ADDRESS, QStringLiteral("888.888.888.888"))); -} - -/*** -**** -***/ - -void DetailsDialog::initFilesTab() -{ - connect(ui_.filesView, &FileTreeView::openRequested, this, &DetailsDialog::onOpenRequested); - connect(ui_.filesView, &FileTreeView::pathEdited, this, &DetailsDialog::onPathEdited); - connect(ui_.filesView, &FileTreeView::priorityChanged, this, &DetailsDialog::onFilePriorityChanged); - connect(ui_.filesView, &FileTreeView::wantedChanged, this, &DetailsDialog::onFileWantedChanged); -} - -void DetailsDialog::onFilePriorityChanged(QSet const& indices, int priority) -{ - tr_quark key; - - switch (priority) - { - case TR_PRI_LOW: - key = TR_KEY_priority_low; - break; - - case TR_PRI_HIGH: - key = TR_KEY_priority_high; - break; - - default: - key = TR_KEY_priority_normal; - break; - } - - torrentSet(key, indices.values()); -} - -void DetailsDialog::onFileWantedChanged(QSet const& indices, bool wanted) -{ - tr_quark const key = wanted ? TR_KEY_files_wanted : TR_KEY_files_unwanted; - torrentSet(key, indices.values()); -} - -void DetailsDialog::onPathEdited(QString const& oldpath, QString const& newname) -{ - session_.torrentRenamePath(ids_, oldpath, newname); -} - -void DetailsDialog::onOpenRequested(QString const& path) -{ - if (!session_.isLocal()) - { - return; - } - - for (int const id : ids_) - { - Torrent const* const tor = model_.getTorrentFromId(id); - - if (tor == nullptr) - { - continue; - } - - QString const local_file_path = tor->getPath() + QLatin1Char('/') + path; - - if (!QFile::exists(local_file_path)) - { - continue; - } - - if (QDesktopServices::openUrl(QUrl::fromLocalFile(local_file_path))) - { - break; - } - } -} diff --git a/tests/libtransmission/clients-test.cc b/tests/libtransmission/clients-test.cc index 6ab5852ae..d1c8d57c5 100644 --- a/tests/libtransmission/clients-test.cc +++ b/tests/libtransmission/clients-test.cc @@ -21,58 +21,47 @@ TEST(Client, clientForId) char const* expected_client; }; - auto const tests = std::array{ - LocalTest{ "-BT791B-", "BitTorrent 7.9.1 (Beta)" }, - { "-BT791\0-", "BitTorrent 7.9.1" }, - { "-FC1013-", "FileCroc 1.0.1.3" }, - { "-FC1013-", "FileCroc 1.0.1.3" }, - { "-MR1100-", "Miro 1.1.0.0" }, - { "-TR0006-", "Transmission 0.6" }, - { "-TR0072-", "Transmission 0.72" }, - { "-TR111Z-", "Transmission 1.11+" }, - { "-UT341\0-", "\xc2\xb5Torrent 3.4.1" }, - { "O1008132", "Osprey 1.0.0" }, - { "TIX0193-", "Tixati 1.93" }, - - /* Xfplay 9.9.92 to 9.9.94 uses "-XF9992-" */ - { "-XF9992-", "Xfplay 9.9.92" }, - - /* Older Xfplay versions have three digit version number */ - { "-XF9990-", "Xfplay 9.9.9" }, - - /* PicoTorrent */ - { "-PI0091-", "PicoTorrent 0.09.1" }, - { "-PI0120-", "PicoTorrent 0.12.0" }, - - /* Free Download Manager */ - { "-FD51R\xFF-", "Free Download Manager 5.1.27" }, - { "-FD51W\xFF-", "Free Download Manager 5.1.32" }, - { "-FD51@\xFF-", "Free Download Manager 5.1.x" }, /* Negative test case */ - - /* Folx */ - { "-FL51FF-", "Folx 5.x" }, /* Folx v5.2.1.13690 */ - - /* Baidu Netdisk */ - { "-BN0001-", "Baidu Netdisk" }, /* Baidu Netdisk Client v5.5.4 */ - - /* gobbledygook */ - { "-IIO\x10\x2D\x04-", "-IIO%10-%04-" }, - { "-I\05O\x08\x03\x01-", "-I%05O%08%03%01-" }, - - { "\x65\x78\x62\x63\x00\x38\x7A\x44\x63\x10\x2D\x6E\x9A\xD6\x72\x3B\x33\x9F\x35\xA9", "BitComet 0.56" }, - { "\x65\x78\x62\x63\x00\x38\x4C\x4F\x52\x44\x32\x00\x04\x8E\xCE\xD5\x7B\xD7\x10\x28", "BitLord 0.56" }, - - { "-UW110Q-", "\xc2\xb5Torrent Web 1.1.0" }, - { "-FW6830-", "FrostWire 6.8.3" }, - { "-BI2300-", "BiglyBT 2.3.0.0" }, - { "A2-1-18-8-", "aria2 1.18.8" }, - { "A2-1-2-0-", "aria2 1.2.0" }, - { "-BL246326", "BitLord 2.4.6-326" }, // Style used after BitLord 0.59 - { "-WW0007-", "WebTorrent 0.0.0.7" }, - { "-WS1000-", "HTTP Seed" } + auto constexpr Tests = std::array{ + { { "-AZ8421-", "Azureus / Vuze 8.4.2.1" }, + { "-BC0241-", "BitComet 2.41" }, // two major, two minor + { "-BI2300-", "BiglyBT 2.3.0.0" }, + { "-BL246326", "BitLord 2.4.6-326" }, // Style used after BitLord 0.59 + { "-BN0001-", "Baidu Netdisk" }, // Baidu Netdisk Client v5.5.4 + { "-BT791B-", "BitTorrent 7.9.1 (Beta)" }, + { "-BT791\0-", "BitTorrent 7.9.1" }, + { "-FC1013-", "FileCroc 1.0.1.3" }, + { "-FC1013-", "FileCroc 1.0.1.3" }, + { "-FD51@\xFF-", "Free Download Manager 5.1.x" }, // Negative test case + { "-FD51R\xFF-", "Free Download Manager 5.1.27" }, + { "-FD51W\xFF-", "Free Download Manager 5.1.32" }, + { "-FL51FF-", "Folx 5.x" }, // Folx v5.2.1.13690 + { "-FW6830-", "FrostWire 6.8.3" }, + { "-IIO\x10\x2D\x04-", "-IIO%10-%04-" }, + { "-I\05O\x08\x03\x01-", "-I%05O%08%03%01-" }, + { "-KT33D1-", "KTorrent 3.3 Dev 1" }, + { "-MR1100-", "Miro 1.1.0.0" }, + { "-PI0091-", "PicoTorrent 0.09.1" }, + { "-PI0120-", "PicoTorrent 0.12.0" }, + { "-TR0006-", "Transmission 0.6" }, + { "-TR0072-", "Transmission 0.72" }, + { "-TR111Z-", "Transmission 1.11+" }, + { "-UT341\0-", "\xc2\xb5Torrent 3.4.1" }, + { "-UW110Q-", "\xc2\xb5Torrent Web 1.1.0" }, + { "-UW1110Q", "\xc2\xb5Torrent Web 1.1.10" }, // wider version + { "-WS1000-", "HTTP Seed" }, + { "-WW0007-", "WebTorrent 0.0.0.7" }, + { "-XF9990-", "Xfplay 9.9.9" }, // Older Xfplay versions have three digit version number + { "-XF9992-", "Xfplay 9.9.92" }, // Xfplay 9.9.92 to 9.9.94 uses "-XF9992-" + { "A2-1-18-8-", "aria2 1.18.8" }, + { "A2-1-2-0-", "aria2 1.2.0" }, + { "S58B-----", "Shad0w 5.8.11" }, + { "Q1-23-4-", "Queen Bee 1.23.4" }, + { "TIX0193-", "Tixati 1.93" }, + { "\x65\x78\x62\x63\x00\x38\x4C\x4F\x52\x44\x32\x00\x04\x8E\xCE\xD5\x7B\xD7\x10\x28", "BitLord 0.56" }, + { "\x65\x78\x62\x63\x00\x38\x7A\x44\x63\x10\x2D\x6E\x9A\xD6\x72\x3B\x33\x9F\x35\xA9", "BitComet 0.56" } } }; - for (auto const& test : tests) + for (auto const& test : Tests) { auto buf = std::array{}; tr_clientForId(buf.data(), buf.size(), test.peer_id);