diff --git a/libtransmission/announcer-http.cc b/libtransmission/announcer-http.cc index 4ab78a611..63c60d274 100644 --- a/libtransmission/announcer-http.cc +++ b/libtransmission/announcer-http.cc @@ -41,16 +41,9 @@ using namespace std::literals; -/**** -***** ANNOUNCE -****/ - -[[nodiscard]] static constexpr std::string_view get_event_string(tr_announce_request const& req) +namespace { - return req.partial_seed && (req.event != TR_ANNOUNCE_EVENT_STOPPED) ? "paused"sv : tr_announce_event_get_string(req.event); -} - -static void verboseLog(std::string_view description, tr_direction direction, std::string_view message) +void verboseLog(std::string_view description, tr_direction direction, std::string_view message) { auto& out = std::cerr; static bool const verbose = tr_env_key_exists("TR_CURL_VERBOSE"); @@ -76,7 +69,270 @@ static void verboseLog(std::string_view description, tr_direction direction, std out << std::endl << "[b64]"sv << direction_sv << tr_base64_encode(message) << std::endl; } -static auto constexpr MaxBencDepth = 8; +auto constexpr MaxBencDepth = 8; +} // namespace + +/**** +***** ANNOUNCE +****/ + +namespace +{ +namespace announce_helpers +{ +[[nodiscard]] constexpr std::string_view get_event_string(tr_announce_request const& req) +{ + return req.partial_seed && (req.event != TR_ANNOUNCE_EVENT_STOPPED) ? "paused"sv : tr_announce_event_get_string(req.event); +} + +struct http_announce_data +{ + http_announce_data(tr_sha1_digest_t info_hash_in, tr_announce_response_func on_response_in, std::string_view log_name_in) + : info_hash{ info_hash_in } + , on_response{ std::move(on_response_in) } + , log_name{ log_name_in } + { + } + + tr_sha1_digest_t info_hash = {}; + std::optional previous_response; + + tr_announce_response_func on_response; + bool http_success = false; + + uint8_t requests_sent_count = {}; + uint8_t requests_answered_count = {}; + + std::string log_name; +}; + +bool handleAnnounceResponse(tr_web::FetchResponse const& web_response, tr_announce_response* const response) +{ + auto const& [status, body, did_connect, did_timeout, vdata] = web_response; + auto const& log_name = static_cast(vdata)->log_name; + + response->did_connect = did_connect; + response->did_timeout = did_timeout; + tr_logAddTrace("Got announce response", log_name); + + if (status != HTTP_OK) + { + auto const* const response_str = tr_webGetResponseStr(status); + response->errmsg = fmt::format(FMT_STRING("Tracker HTTP response {:d} ({:s}"), status, response_str); + + return false; + } + + tr_announcerParseHttpAnnounceResponse(*response, body, log_name); + + if (!std::empty(response->pex6)) + { + tr_logAddTrace(fmt::format("got a peers6 length of {}", std::size(response->pex6)), log_name); + } + + if (!std::empty(response->pex)) + { + tr_logAddTrace(fmt::format("got a peers length of {}", std::size(response->pex)), log_name); + } + + return true; +} + +void onAnnounceDone(tr_web::FetchResponse const& web_response) +{ + auto const& [status, body, did_connect, did_timeout, vdata] = web_response; + auto* data = static_cast(vdata); + + ++data->requests_answered_count; + + // If another request already succeeded (or we don't have a registered callback), + // skip processing this response: + if (!data->http_success && data->on_response) + { + tr_announce_response response; + response.info_hash = data->info_hash; + + data->http_success = handleAnnounceResponse(web_response, &response); + + if (data->http_success) + { + data->on_response(response); + } + else if (data->requests_answered_count == data->requests_sent_count) + { + auto const* response_used = &response; + + // All requests have been answered, but none were successful. + // Choose the one that went further to report. + if (data->previous_response && !response.did_connect && !response.did_timeout) + { + response_used = &*data->previous_response; + } + + data->on_response(*response_used); + } + else + { + // There is still one request pending that might succeed, so store + // the response for later. There is only room for 1 previous response, + // because there can be at most 2 requests. + TR_ASSERT(!data->previous_response); + data->previous_response = std::move(response); + } + } + else + { + tr_logAddTrace("Ignoring redundant announce response", data->log_name); + } + + // Free data if no more responses are expected: + if (data->requests_answered_count == data->requests_sent_count) + { + delete data; + } +} + +void announce_url_new(tr_urlbuf& url, tr_session const* session, tr_announce_request const& req) +{ + url.clear(); + auto out = std::back_inserter(url); + + auto escaped_info_hash = tr_urlbuf{}; + tr_urlPercentEncode(std::back_inserter(escaped_info_hash), req.info_hash); + + fmt::format_to( + out, + "{url}" + "{sep}info_hash={info_hash}" + "&peer_id={peer_id}" + "&port={port}" + "&uploaded={uploaded}" + "&downloaded={downloaded}" + "&left={left}" + "&numwant={numwant}" + "&key={key}" + "&compact=1" + "&supportcrypto=1", + fmt::arg("url", req.announce_url), + fmt::arg("sep", tr_strvContains(req.announce_url.sv(), '?') ? '&' : '?'), + fmt::arg("info_hash", std::data(escaped_info_hash)), + fmt::arg("peer_id", std::string_view{ std::data(req.peer_id), std::size(req.peer_id) }), + fmt::arg("port", req.port.host()), + fmt::arg("uploaded", req.up), + fmt::arg("downloaded", req.down), + fmt::arg("left", req.leftUntilComplete), + fmt::arg("numwant", req.numwant), + fmt::arg("key", req.key)); + + if (session->encryptionMode() == TR_ENCRYPTION_REQUIRED) + { + fmt::format_to(out, "&requirecrypto=1"); + } + + if (req.corrupt != 0) + { + fmt::format_to(out, "&corrupt={}", req.corrupt); + } + + if (auto const str = get_event_string(req); !std::empty(str)) + { + fmt::format_to(out, "&event={}", str); + } + + if (!std::empty(req.tracker_id)) + { + fmt::format_to(out, "&trackerid={}", req.tracker_id); + } +} + +[[nodiscard]] std::string format_ip_arg(std::string_view ip) +{ + return fmt::format("&ip={:s}", ip); +} + +} // namespace announce_helpers +} // namespace + +void tr_tracker_http_announce( + tr_session const* session, + tr_announce_request const& request, + tr_announce_response_func on_response) +{ + using namespace announce_helpers; + + auto* const d = new http_announce_data{ request.info_hash, std::move(on_response), request.log_name }; + + /* There are two alternative techniques for announcing both IPv4 and + IPv6 addresses. Previous version of BEP-7 suggests adding "ipv4=" + and "ipv6=" parameters to the announce URL, while OpenTracker and + newer version of BEP-7 requires that peers announce once per each + public address they want to use. + + We should ensure that we send the announce both via IPv6 and IPv4, + and to be safe we also add the "ipv6=" and "ipv4=" parameters, if + we already have them. Our global IPv6 address is computed for the + LTEP handshake, so this comes for free. Our public IPv4 address + may have been returned from a previous announce and stored in the + session. + */ + auto url = tr_urlbuf{}; + announce_url_new(url, session, request); + auto options = tr_web::FetchOptions{ url.sv(), onAnnounceDone, d }; + options.timeout_secs = TR_ANNOUNCE_TIMEOUT_SEC; + options.sndbuf = 4096; + options.rcvbuf = 4096; + + auto do_make_request = [&](std::string_view const& protocol_name, tr_web::FetchOptions&& opt) + { + tr_logAddTrace(fmt::format("Sending {} announce to libcurl: '{}'", protocol_name, opt.url), request.log_name); + session->fetch(std::move(opt)); + }; + + auto const [ipv6, ipv6_is_any] = session->publicAddress(TR_AF_INET6); + + /* + * Before Curl 7.77.0, if we explicitly choose the IP version we want + * to use, it is still possible that the wrong one is used. The workaround + * is expensive (disabling DNS cache), so instead we have to make do with + * a request that we don't know if will go through IPv6 or IPv4. + */ + static bool const use_curl_workaround = curl_version_info(CURLVERSION_NOW)->version_num < CURL_VERSION_BITS(7, 77, 0); + if (use_curl_workaround) + { + if (session->useAnnounceIP()) + { + options.url += format_ip_arg(session->announceIP()); + } + + d->requests_sent_count = 1; + do_make_request(""sv, std::move(options)); + } + else + { + if (session->useAnnounceIP() || ipv6_is_any) + { + if (session->useAnnounceIP()) + { + options.url += format_ip_arg(session->announceIP()); + } + d->requests_sent_count = 1; + do_make_request(""sv, std::move(options)); + } + else + { + d->requests_sent_count = 2; + + // First try to send the announce via IPv4: + auto ipv4_options = options; + ipv4_options.ip_proto = tr_web::FetchOptions::IPProtocol::V4; + do_make_request("IPv4"sv, std::move(ipv4_options)); + + // Then try to send via IPv6: + options.ip_proto = tr_web::FetchOptions::IPProtocol::V6; + do_make_request("IPv6"sv, std::move(options)); + } + } +} void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::string_view benc, std::string_view log_name) { @@ -216,263 +472,113 @@ void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std:: } } -struct http_announce_data +// --- + +namespace { - http_announce_data(tr_sha1_digest_t info_hash_in, tr_announce_response_func on_response_in, std::string_view log_name_in) - : info_hash{ info_hash_in } - , on_response{ std::move(on_response_in) } - , log_name{ log_name_in } +namespace scrape_helpers +{ +class scrape_data +{ +public: + scrape_data(tr_scrape_response_func response_func, std::string_view log_name) + : response_func_{ std::move(response_func) } + , log_name_{ log_name } { } - tr_sha1_digest_t info_hash = {}; - std::optional previous_response; + [[nodiscard]] constexpr auto& response() noexcept + { + return response_; + } - tr_announce_response_func on_response; - bool http_success = false; + [[nodiscard]] constexpr auto const& log_name() const noexcept + { + return log_name_; + } - uint8_t requests_sent_count = {}; - uint8_t requests_answered_count = {}; + void invoke_callback() const + { + if (response_func_) + { + response_func_(response_); + } + } - std::string log_name; +private: + tr_scrape_response response_ = {}; + tr_scrape_response_func response_func_ = {}; + std::string log_name_; }; -static bool handleAnnounceResponse(tr_web::FetchResponse const& web_response, tr_announce_response* const response) +void onScrapeDone(tr_web::FetchResponse const& web_response) { auto const& [status, body, did_connect, did_timeout, vdata] = web_response; - auto const& log_name = static_cast(vdata)->log_name; + auto* const data = static_cast(vdata); - response->did_connect = did_connect; - response->did_timeout = did_timeout; - tr_logAddTrace("Got announce response", log_name); + auto& response = data->response(); + response.did_connect = did_connect; + response.did_timeout = did_timeout; + + auto const scrape_url_sv = response.scrape_url.sv(); + tr_logAddTrace(fmt::format("Got scrape response for '{}'", scrape_url_sv), data->log_name()); if (status != HTTP_OK) { auto const* const response_str = tr_webGetResponseStr(status); - response->errmsg = fmt::format(FMT_STRING("Tracker HTTP response {:d} ({:s}"), status, response_str); - - return false; + response.errmsg = fmt::format(FMT_STRING("Tracker HTTP response {:d} ({:s})"), status, response_str); } - - tr_announcerParseHttpAnnounceResponse(*response, body, log_name); - - if (!std::empty(response->pex6)) + else if (!std::empty(body)) { - tr_logAddTrace(fmt::format("got a peers6 length of {}", std::size(response->pex6)), log_name); + tr_announcerParseHttpScrapeResponse(response, body, data->log_name()); } - if (!std::empty(response->pex)) - { - tr_logAddTrace(fmt::format("got a peers length of {}", std::size(response->pex)), log_name); - } - - return true; + data->invoke_callback(); + delete data; } -static void onAnnounceDone(tr_web::FetchResponse const& web_response) +void scrape_url_new(tr_pathbuf& scrape_url, tr_scrape_request const& req) { - auto const& [status, body, did_connect, did_timeout, vdata] = web_response; - auto* data = static_cast(vdata); + scrape_url = req.scrape_url.sv(); + char delimiter = tr_strvContains(scrape_url, '?') ? '&' : '?'; - ++data->requests_answered_count; - - // If another request already succeeded (or we don't have a registered callback), - // skip processing this response: - if (!data->http_success && data->on_response) + for (int i = 0; i < req.info_hash_count; ++i) { - tr_announce_response response; - response.info_hash = data->info_hash; - - data->http_success = handleAnnounceResponse(web_response, &response); - - if (data->http_success) - { - data->on_response(response); - } - else if (data->requests_answered_count == data->requests_sent_count) - { - auto const* response_used = &response; - - // All requests have been answered, but none were successful. - // Choose the one that went further to report. - if (data->previous_response && !response.did_connect && !response.did_timeout) - { - response_used = &*data->previous_response; - } - - data->on_response(*response_used); - } - else - { - // There is still one request pending that might succeed, so store - // the response for later. There is only room for 1 previous response, - // because there can be at most 2 requests. - TR_ASSERT(!data->previous_response); - data->previous_response = std::move(response); - } - } - else - { - tr_logAddTrace("Ignoring redundant announce response", data->log_name); - } - - // Free data if no more responses are expected: - if (data->requests_answered_count == data->requests_sent_count) - { - delete data; + scrape_url.append(delimiter, "info_hash="); + tr_urlPercentEncode(std::back_inserter(scrape_url), req.info_hash[i]); + delimiter = '&'; } } +} // namespace scrape_helpers +} // namespace -namespace tr_tracker_announce_helpers +void tr_tracker_http_scrape(tr_session const* session, tr_scrape_request const& request, tr_scrape_response_func on_response) { + using namespace scrape_helpers; -void announce_url_new(tr_urlbuf& url, tr_session const* session, tr_announce_request const& req) -{ - url.clear(); - auto out = std::back_inserter(url); + auto* d = new scrape_data{ std::move(on_response), request.log_name }; - auto escaped_info_hash = tr_urlbuf{}; - tr_urlPercentEncode(std::back_inserter(escaped_info_hash), req.info_hash); - - fmt::format_to( - out, - "{url}" - "{sep}info_hash={info_hash}" - "&peer_id={peer_id}" - "&port={port}" - "&uploaded={uploaded}" - "&downloaded={downloaded}" - "&left={left}" - "&numwant={numwant}" - "&key={key}" - "&compact=1" - "&supportcrypto=1", - fmt::arg("url", req.announce_url), - fmt::arg("sep", tr_strvContains(req.announce_url.sv(), '?') ? '&' : '?'), - fmt::arg("info_hash", std::data(escaped_info_hash)), - fmt::arg("peer_id", std::string_view{ std::data(req.peer_id), std::size(req.peer_id) }), - fmt::arg("port", req.port.host()), - fmt::arg("uploaded", req.up), - fmt::arg("downloaded", req.down), - fmt::arg("left", req.leftUntilComplete), - fmt::arg("numwant", req.numwant), - fmt::arg("key", req.key)); - - if (session->encryptionMode() == TR_ENCRYPTION_REQUIRED) + auto& response = d->response(); + response.scrape_url = request.scrape_url; + response.row_count = request.info_hash_count; + for (int i = 0; i < response.row_count; ++i) { - fmt::format_to(out, "&requirecrypto=1"); + response.rows[i].info_hash = request.info_hash[i]; + response.rows[i].seeders = -1; + response.rows[i].leechers = -1; + response.rows[i].downloads = -1; } - if (req.corrupt != 0) - { - fmt::format_to(out, "&corrupt={}", req.corrupt); - } - - if (auto const str = get_event_string(req); !std::empty(str)) - { - fmt::format_to(out, "&event={}", str); - } - - if (!std::empty(req.tracker_id)) - { - fmt::format_to(out, "&trackerid={}", req.tracker_id); - } -} - -[[nodiscard]] std::string format_ip_arg(std::string_view ip) -{ - return fmt::format("&ip={:s}", ip); -} - -} // namespace tr_tracker_announce_helpers - -void tr_tracker_http_announce( - tr_session const* session, - tr_announce_request const& request, - tr_announce_response_func on_response) -{ - using namespace tr_tracker_announce_helpers; - - auto* const d = new http_announce_data{ request.info_hash, std::move(on_response), request.log_name }; - - /* There are two alternative techniques for announcing both IPv4 and - IPv6 addresses. Previous version of BEP-7 suggests adding "ipv4=" - and "ipv6=" parameters to the announce URL, while OpenTracker and - newer version of BEP-7 requires that peers announce once per each - public address they want to use. - - We should ensure that we send the announce both via IPv6 and IPv4, - and to be safe we also add the "ipv6=" and "ipv4=" parameters, if - we already have them. Our global IPv6 address is computed for the - LTEP handshake, so this comes for free. Our public IPv4 address - may have been returned from a previous announce and stored in the - session. - */ - auto url = tr_urlbuf{}; - announce_url_new(url, session, request); - auto options = tr_web::FetchOptions{ url.sv(), onAnnounceDone, d }; - options.timeout_secs = TR_ANNOUNCE_TIMEOUT_SEC; + auto scrape_url = tr_pathbuf{}; + scrape_url_new(scrape_url, request); + tr_logAddTrace(fmt::format("Sending scrape to libcurl: '{}'", scrape_url), request.log_name); + auto options = tr_web::FetchOptions{ scrape_url, onScrapeDone, d }; + options.timeout_secs = TR_SCRAPE_TIMEOUT_SEC; options.sndbuf = 4096; options.rcvbuf = 4096; - - auto do_make_request = [&](std::string_view const& protocol_name, tr_web::FetchOptions&& opt) - { - tr_logAddTrace(fmt::format("Sending {} announce to libcurl: '{}'", protocol_name, opt.url), request.log_name); - session->fetch(std::move(opt)); - }; - - auto const [ipv6, ipv6_is_any] = session->publicAddress(TR_AF_INET6); - - /* - * Before Curl 7.77.0, if we explicitly choose the IP version we want - * to use, it is still possible that the wrong one is used. The workaround - * is expensive (disabling DNS cache), so instead we have to make do with - * a request that we don't know if will go through IPv6 or IPv4. - */ - static bool const use_curl_workaround = curl_version_info(CURLVERSION_NOW)->version_num < CURL_VERSION_BITS(7, 77, 0); - if (use_curl_workaround) - { - if (session->useAnnounceIP()) - { - options.url += format_ip_arg(session->announceIP()); - } - - d->requests_sent_count = 1; - do_make_request(""sv, std::move(options)); - } - else - { - if (session->useAnnounceIP() || ipv6_is_any) - { - if (session->useAnnounceIP()) - { - options.url += format_ip_arg(session->announceIP()); - } - d->requests_sent_count = 1; - do_make_request(""sv, std::move(options)); - } - else - { - d->requests_sent_count = 2; - - // First try to send the announce via IPv4: - auto ipv4_options = options; - ipv4_options.ip_proto = tr_web::FetchOptions::IPProtocol::V4; - do_make_request("IPv4"sv, std::move(ipv4_options)); - - // Then try to send via IPv6: - options.ip_proto = tr_web::FetchOptions::IPProtocol::V6; - do_make_request("IPv6"sv, std::move(options)); - } - } + session->fetch(std::move(options)); } -/**** -***** -***** SCRAPE -***** -****/ - void tr_announcerParseHttpScrapeResponse(tr_scrape_response& response, std::string_view benc, std::string_view log_name) { verboseLog("Scrape response:", TR_DOWN, benc); @@ -576,100 +682,3 @@ void tr_announcerParseHttpScrapeResponse(tr_scrape_response& response, std::stri tr_error_clear(&error); } } - -class scrape_data -{ -public: - scrape_data(tr_scrape_response_func response_func, std::string_view log_name) - : response_func_{ std::move(response_func) } - , log_name_{ log_name } - { - } - - [[nodiscard]] constexpr auto& response() noexcept - { - return response_; - } - - [[nodiscard]] constexpr auto const& log_name() const noexcept - { - return log_name_; - } - - void invoke_callback() const - { - if (response_func_) - { - response_func_(response_); - } - } - -private: - tr_scrape_response response_ = {}; - tr_scrape_response_func response_func_ = {}; - std::string log_name_; -}; - -static void onScrapeDone(tr_web::FetchResponse const& web_response) -{ - auto const& [status, body, did_connect, did_timeout, vdata] = web_response; - auto* const data = static_cast(vdata); - - auto& response = data->response(); - response.did_connect = did_connect; - response.did_timeout = did_timeout; - - auto const scrape_url_sv = response.scrape_url.sv(); - tr_logAddTrace(fmt::format("Got scrape response for '{}'", scrape_url_sv), data->log_name()); - - if (status != HTTP_OK) - { - auto const* const response_str = tr_webGetResponseStr(status); - response.errmsg = fmt::format(FMT_STRING("Tracker HTTP response {:d} ({:s})"), status, response_str); - } - else if (!std::empty(body)) - { - tr_announcerParseHttpScrapeResponse(response, body, data->log_name()); - } - - data->invoke_callback(); - delete data; -} - -static void scrape_url_new(tr_pathbuf& scrape_url, tr_scrape_request const& req) -{ - scrape_url = req.scrape_url.sv(); - char delimiter = tr_strvContains(scrape_url, '?') ? '&' : '?'; - - for (int i = 0; i < req.info_hash_count; ++i) - { - scrape_url.append(delimiter, "info_hash="); - tr_urlPercentEncode(std::back_inserter(scrape_url), req.info_hash[i]); - delimiter = '&'; - } -} - -void tr_tracker_http_scrape(tr_session const* session, tr_scrape_request const& request, tr_scrape_response_func on_response) -{ - auto* d = new scrape_data{ std::move(on_response), request.log_name }; - - auto& response = d->response(); - response.scrape_url = request.scrape_url; - response.row_count = request.info_hash_count; - for (int i = 0; i < response.row_count; ++i) - { - response.rows[i].info_hash = request.info_hash[i]; - response.rows[i].seeders = -1; - response.rows[i].leechers = -1; - response.rows[i].downloads = -1; - } - - auto scrape_url = tr_pathbuf{}; - scrape_url_new(scrape_url, request); - tr_logAddTrace(fmt::format("Sending scrape to libcurl: '{}'", scrape_url), request.log_name); - auto options = tr_web::FetchOptions{ scrape_url, onScrapeDone, d }; - options.timeout_secs = TR_SCRAPE_TIMEOUT_SEC; - options.sndbuf = 4096; - options.rcvbuf = 4096; - session->fetch(std::move(options)); -} diff --git a/libtransmission/announcer.cc b/libtransmission/announcer.cc index 089a5502e..eeb0f223d 100644 --- a/libtransmission/announcer.cc +++ b/libtransmission/announcer.cc @@ -43,23 +43,20 @@ using namespace std::literals; #define tr_logAddDebugTier(tier, msg) tr_logAddDebug(msg, (tier)->buildLogName()) #define tr_logAddTraceTier(tier, msg) tr_logAddTrace(msg, (tier)->buildLogName()) -/* unless the tracker says otherwise, rescrape this frequently */ -static auto constexpr DefaultScrapeIntervalSec = int{ 60 * 30 }; - -/* the value of the 'numwant' argument passed in tracker requests. */ -static auto constexpr Numwant = int{ 80 }; - -/* how often to announce & scrape */ -static auto constexpr MaxAnnouncesPerUpkeep = int{ 20 }; -static auto constexpr MaxScrapesPerUpkeep = int{ 20 }; - -/* how many infohashes to remove when we get a scrape-too-long error */ -static auto constexpr TrMultiscrapeStep = int{ 5 }; - -// --- - namespace { +/* unless the tracker says otherwise, rescrape this frequently */ +auto constexpr DefaultScrapeIntervalSec = int{ 60 * 30 }; + +/* the value of the 'numwant' argument passed in tracker requests. */ +auto constexpr Numwant = int{ 80 }; + +/* how often to announce & scrape */ +auto constexpr MaxAnnouncesPerUpkeep = int{ 20 }; +auto constexpr MaxScrapesPerUpkeep = int{ 20 }; + +/* how many infohashes to remove when we get a scrape-too-long error */ +auto constexpr TrMultiscrapeStep = int{ 5 }; struct StopsCompare { @@ -643,22 +640,6 @@ private: } }; -static tr_tier* getTier(tr_announcer_impl* announcer, tr_sha1_digest_t const& info_hash, int tier_id) -{ - if (announcer == nullptr) - { - return nullptr; - } - - auto* const tor = announcer->session->torrents().get(info_hash); - if (tor == nullptr || tor->torrent_announcer == nullptr) - { - return nullptr; - } - - return tor->torrent_announcer->getTier(tier_id); -} - // --- PUBLISH namespace @@ -773,7 +754,11 @@ time_t tr_announcerNextManualAnnounce(tr_torrent const* tor) return ret; } -static void tr_logAddTrace_tier_announce_queue(tr_tier const* tier) +namespace +{ +namespace announce_helpers +{ +void tr_logAddTrace_tier_announce_queue(tr_tier const* tier) { if (!tr_logLevelIsActive(TR_LOG_TRACE) || std::empty(tier->announce_events)) { @@ -792,7 +777,7 @@ static void tr_logAddTrace_tier_announce_queue(tr_tier const* tier) } // higher priorities go to the front of the announce queue -static void tier_update_announce_priority(tr_tier* tier) +void tier_update_announce_priority(tr_tier* tier) { int priority = -1; @@ -804,7 +789,7 @@ static void tier_update_announce_priority(tr_tier* tier) tier->announce_event_priority = priority; } -static void tier_announce_remove_trailing(tr_tier* tier, tr_announce_event e) +void tier_announce_remove_trailing(tr_tier* tier, tr_announce_event e) { while (!std::empty(tier->announce_events) && tier->announce_events.back() == e) { @@ -814,7 +799,7 @@ static void tier_announce_remove_trailing(tr_tier* tier, tr_announce_event e) tier_update_announce_priority(tier); } -static void tier_announce_event_push(tr_tier* tier, tr_announce_event e, time_t announce_at) +void tier_announce_event_push(tr_tier* tier, tr_announce_event e, time_t announce_at) { TR_ASSERT(tier != nullptr); @@ -852,7 +837,7 @@ static void tier_announce_event_push(tr_tier* tier, tr_announce_event e, time_t tr_logAddTraceTier(tier, fmt::format("announcing in {} seconds", difftime(announce_at, tr_time()))); } -static auto tier_announce_event_pull(tr_tier* tier) +auto tier_announce_event_pull(tr_tier* tier) { auto const e = tier->announce_events.front(); tier->announce_events.pop_front(); @@ -860,104 +845,7 @@ static auto tier_announce_event_pull(tr_tier* tier) return e; } -static void torrentAddAnnounce(tr_torrent* tor, tr_announce_event e, time_t announce_at) -{ - // tell each tier to announce - for (auto& tier : tor->torrent_announcer->tiers) - { - tier_announce_event_push(&tier, e, announce_at); - } -} - -void tr_announcer_impl::startTorrent(tr_torrent* tor) -{ - torrentAddAnnounce(tor, TR_ANNOUNCE_EVENT_STARTED, tr_time()); -} - -void tr_announcerManualAnnounce(tr_torrent* tor) -{ - torrentAddAnnounce(tor, TR_ANNOUNCE_EVENT_NONE, tr_time()); -} - -void tr_announcer_impl::stopTorrent(tr_torrent* tor) -{ - torrentAddAnnounce(tor, TR_ANNOUNCE_EVENT_STOPPED, tr_time()); -} - -void tr_announcerTorrentCompleted(tr_torrent* tor) -{ - torrentAddAnnounce(tor, TR_ANNOUNCE_EVENT_COMPLETED, tr_time()); -} - -void tr_announcerChangeMyPort(tr_torrent* tor) -{ - torrentAddAnnounce(tor, TR_ANNOUNCE_EVENT_STARTED, tr_time()); -} - -// --- - -void tr_announcerAddBytes(tr_torrent* tor, int type, uint32_t n_bytes) -{ - TR_ASSERT(tr_isTorrent(tor)); - TR_ASSERT(type == TR_ANN_UP || type == TR_ANN_DOWN || type == TR_ANN_CORRUPT); - - for (auto& tier : tor->torrent_announcer->tiers) - { - tier.byteCounts[type] += n_bytes; - } -} - -// --- - -[[nodiscard]] static tr_announce_request create_announce_request( - tr_announcer_impl const* const announcer, - tr_torrent* const tor, - tr_tier const* const tier, - tr_announce_event const event) -{ - auto const* const current_tracker = tier->currentTracker(); - TR_ASSERT(current_tracker != nullptr); - - auto req = tr_announce_request{}; - req.port = announcer->session->advertisedPeerPort(); - req.announce_url = current_tracker->announce_url; - req.tracker_id = current_tracker->tracker_id; - req.info_hash = tor->infoHash(); - req.peer_id = tr_torrentGetPeerId(tor); - req.up = tier->byteCounts[TR_ANN_UP]; - req.down = tier->byteCounts[TR_ANN_DOWN]; - req.corrupt = tier->byteCounts[TR_ANN_CORRUPT]; - req.leftUntilComplete = tor->hasMetainfo() ? tor->totalSize() - tor->hasTotal() : INT64_MAX; - req.event = event; - req.numwant = event == TR_ANNOUNCE_EVENT_STOPPED ? 0 : Numwant; - req.key = tor->announce_key(); - req.partial_seed = tor->isPartialSeed(); - tier->buildLogName(req.log_name, sizeof(req.log_name)); - return req; -} - -void tr_announcer_impl::removeTorrent(tr_torrent* tor) -{ - // FIXME(ckerr) - auto* const ta = tor->torrent_announcer; - if (ta == nullptr) - { - return; - } - - for (auto const& tier : ta->tiers) - { - if (tier.isRunning && tier.lastAnnounceSucceeded) - { - stops_.emplace(create_announce_request(this, tor, &tier, TR_ANNOUNCE_EVENT_STOPPED)); - } - } - - tor->torrent_announcer = nullptr; - delete ta; -} - -static bool isUnregistered(char const* errmsg) +bool isUnregistered(char const* errmsg) { auto const lower = tr_strlower(errmsg != nullptr ? errmsg : ""); @@ -966,8 +854,10 @@ static bool isUnregistered(char const* errmsg) return std::any_of(std::begin(Keys), std::end(Keys), [&lower](auto const& key) { return tr_strvContains(lower, key); }); } -static void on_announce_error(tr_tier* tier, char const* err, tr_announce_event e) +void on_announce_error(tr_tier* tier, char const* err, tr_announce_event e) { + using namespace announce_helpers; + /* increment the error count */ auto* current_tracker = tier->currentTracker(); if (current_tracker != nullptr) @@ -1002,12 +892,69 @@ static void on_announce_error(tr_tier* tier, char const* err, tr_announce_event } } +[[nodiscard]] tr_announce_request create_announce_request( + tr_announcer_impl const* const announcer, + tr_torrent* const tor, + tr_tier const* const tier, + tr_announce_event const event) +{ + auto const* const current_tracker = tier->currentTracker(); + TR_ASSERT(current_tracker != nullptr); + + auto req = tr_announce_request{}; + req.port = announcer->session->advertisedPeerPort(); + req.announce_url = current_tracker->announce_url; + req.tracker_id = current_tracker->tracker_id; + req.info_hash = tor->infoHash(); + req.peer_id = tr_torrentGetPeerId(tor); + req.up = tier->byteCounts[TR_ANN_UP]; + req.down = tier->byteCounts[TR_ANN_DOWN]; + req.corrupt = tier->byteCounts[TR_ANN_CORRUPT]; + req.leftUntilComplete = tor->hasMetainfo() ? tor->totalSize() - tor->hasTotal() : INT64_MAX; + req.event = event; + req.numwant = event == TR_ANNOUNCE_EVENT_STOPPED ? 0 : Numwant; + req.key = tor->announce_key(); + req.partial_seed = tor->isPartialSeed(); + tier->buildLogName(req.log_name, sizeof(req.log_name)); + return req; +} + +[[nodiscard]] tr_tier* getTier(tr_announcer_impl* announcer, tr_sha1_digest_t const& info_hash, int tier_id) +{ + if (announcer == nullptr) + { + return nullptr; + } + + auto* const tor = announcer->session->torrents().get(info_hash); + if (tor == nullptr || tor->torrent_announcer == nullptr) + { + return nullptr; + } + + return tor->torrent_announcer->getTier(tier_id); +} +} // namespace announce_helpers + +void torrentAddAnnounce(tr_torrent* tor, tr_announce_event e, time_t announce_at) +{ + using namespace announce_helpers; + + // tell each tier to announce + for (auto& tier : tor->torrent_announcer->tiers) + { + tier_announce_event_push(&tier, e, announce_at); + } +} +} // namespace + void tr_announcer_impl::onAnnounceDone( int tier_id, tr_announce_event event, bool is_running_on_success, tr_announce_response const& response) { + using namespace announce_helpers; using namespace publish_helpers; auto* const tier = getTier(this, response.info_hash, tier_id); @@ -1193,54 +1140,92 @@ void tr_announcer_impl::onAnnounceDone( } } -static void tierAnnounce(tr_announcer_impl* announcer, tr_tier* tier) +void tr_announcer_impl::startTorrent(tr_torrent* tor) { - TR_ASSERT(!tier->isAnnouncing); - TR_ASSERT(!std::empty(tier->announce_events)); - TR_ASSERT(tier->currentTracker() != nullptr); + torrentAddAnnounce(tor, TR_ANNOUNCE_EVENT_STARTED, tr_time()); +} - auto const now = tr_time(); +void tr_announcerManualAnnounce(tr_torrent* tor) +{ + torrentAddAnnounce(tor, TR_ANNOUNCE_EVENT_NONE, tr_time()); +} - tr_torrent* tor = tier->tor; - auto const event = tier_announce_event_pull(tier); - auto const req = create_announce_request(announcer, tor, tier, event); +void tr_announcer_impl::stopTorrent(tr_torrent* tor) +{ + torrentAddAnnounce(tor, TR_ANNOUNCE_EVENT_STOPPED, tr_time()); +} - tier->isAnnouncing = true; - tier->lastAnnounceStartTime = now; +void tr_announcerTorrentCompleted(tr_torrent* tor) +{ + torrentAddAnnounce(tor, TR_ANNOUNCE_EVENT_COMPLETED, tr_time()); +} - auto tier_id = tier->id; - auto is_running_on_success = tor->isRunning; +void tr_announcerChangeMyPort(tr_torrent* tor) +{ + torrentAddAnnounce(tor, TR_ANNOUNCE_EVENT_STARTED, tr_time()); +} - announcer->announce( - req, - [session = announcer->session, announcer, tier_id, event, is_running_on_success](tr_announce_response const& response) +// --- + +void tr_announcerAddBytes(tr_torrent* tor, int type, uint32_t n_bytes) +{ + TR_ASSERT(tr_isTorrent(tor)); + TR_ASSERT(type == TR_ANN_UP || type == TR_ANN_DOWN || type == TR_ANN_CORRUPT); + + for (auto& tier : tor->torrent_announcer->tiers) + { + tier.byteCounts[type] += n_bytes; + } +} + +// --- + +void tr_announcer_impl::removeTorrent(tr_torrent* tor) +{ + using namespace announce_helpers; + + // FIXME(ckerr) + auto* const ta = tor->torrent_announcer; + if (ta == nullptr) + { + return; + } + + for (auto const& tier : ta->tiers) + { + if (tier.isRunning && tier.lastAnnounceSucceeded) { - if (session->announcer_) - { - announcer->onAnnounceDone(tier_id, event, is_running_on_success, response); - } - }); + stops_.emplace(create_announce_request(this, tor, &tier, TR_ANNOUNCE_EVENT_STOPPED)); + } + } + + tor->torrent_announcer = nullptr; + delete ta; } // --- SCRAPE -static bool multiscrape_too_big(std::string_view errmsg) +namespace +{ +namespace on_scrape_done_helpers +{ +[[nodiscard]] TR_CONSTEXPR20 bool multiscrape_too_big(std::string_view errmsg) { /* Found a tracker that returns some bespoke string for this case? Add your patch here and open a PR */ - auto constexpr TooLongErrors = std::array{ + auto too_long_errors = std::array{ "Bad Request", "GET string too long", "Request-URI Too Long", }; return std::any_of( - std::begin(TooLongErrors), - std::end(TooLongErrors), + std::begin(too_long_errors), + std::end(too_long_errors), [&errmsg](auto const& substr) { return tr_strvContains(errmsg, substr); }); } -static void on_scrape_error(tr_session const* /*session*/, tr_tier* tier, char const* errmsg) +void on_scrape_error(tr_session const* /*session*/, tr_tier* tier, char const* errmsg) { // increment the error count auto* current_tracker = tier->currentTracker(); @@ -1265,7 +1250,7 @@ static void on_scrape_error(tr_session const* /*session*/, tr_tier* tier, char c tier->scheduleNextScrape(interval); } -static void checkMultiscrapeMax(tr_announcer_impl* announcer, tr_scrape_response const& response) +void checkMultiscrapeMax(tr_announcer_impl* announcer, tr_scrape_response const& response) { if (!multiscrape_too_big(response.errmsg)) { @@ -1303,9 +1288,12 @@ static void checkMultiscrapeMax(tr_announcer_impl* announcer, tr_scrape_response multiscrape_max = n; } } +} // namespace on_scrape_done_helpers +} // namespace void tr_announcer_impl::onScrapeDone(tr_scrape_response const& response) { + using namespace on_scrape_done_helpers; using namespace publish_helpers; auto const now = tr_time(); @@ -1404,7 +1392,9 @@ void tr_announcer_impl::onScrapeDone(tr_scrape_response const& response) checkMultiscrapeMax(this, response); } -static void multiscrape(tr_announcer_impl* announcer, std::vector const& tiers) +namespace +{ +void multiscrape(tr_announcer_impl* announcer, std::vector const& tiers) { auto const now = tr_time(); auto requests = std::array{}; @@ -1471,7 +1461,9 @@ static void multiscrape(tr_announcer_impl* announcer, std::vector cons } } -static int compareAnnounceTiers(tr_tier const* a, tr_tier const* b) +namespace upkeep_helpers +{ +int compareAnnounceTiers(tr_tier const* a, tr_tier const* b) { /* prefer higher-priority events */ if (auto const priority_a = a->announce_event_priority, priority_b = b->announce_event_priority; priority_a != priority_b) @@ -1510,9 +1502,40 @@ static int compareAnnounceTiers(tr_tier const* a, tr_tier const* b) return a < b ? -1 : 1; } -static void scrapeAndAnnounceMore(tr_announcer_impl* announcer) +void tierAnnounce(tr_announcer_impl* announcer, tr_tier* tier) { - time_t const now = tr_time(); + using namespace announce_helpers; + + TR_ASSERT(!tier->isAnnouncing); + TR_ASSERT(!std::empty(tier->announce_events)); + TR_ASSERT(tier->currentTracker() != nullptr); + + auto const now = tr_time(); + + tr_torrent* tor = tier->tor; + auto const event = tier_announce_event_pull(tier); + auto const req = create_announce_request(announcer, tor, tier, event); + + tier->isAnnouncing = true; + tier->lastAnnounceStartTime = now; + + auto tier_id = tier->id; + auto is_running_on_success = tor->isRunning; + + announcer->announce( + req, + [session = announcer->session, announcer, tier_id, event, is_running_on_success](tr_announce_response const& response) + { + if (session->announcer_) + { + announcer->onAnnounceDone(tier_id, event, is_running_on_success, response); + } + }); +} + +void scrapeAndAnnounceMore(tr_announcer_impl* announcer) +{ + auto const now = tr_time(); /* build a list of tiers that need to be announced */ auto announce_me = std::vector{}; @@ -1557,9 +1580,13 @@ static void scrapeAndAnnounceMore(tr_announcer_impl* announcer) tierAnnounce(announcer, tier); } } +} // namespace upkeep_helpers +} // namespace void tr_announcer_impl::upkeep() { + using namespace upkeep_helpers; + auto const lock = session->unique_lock(); // maybe send out some "stopped" messages for closed torrents @@ -1576,7 +1603,11 @@ void tr_announcer_impl::upkeep() // --- -static tr_tracker_view trackerView(tr_torrent const& tor, size_t tier_index, tr_tier const& tier, tr_tracker const& tracker) +namespace +{ +namespace tracker_view_helpers +{ +[[nodiscard]] auto trackerView(tr_torrent const& tor, size_t tier_index, tr_tier const& tier, tr_tracker const& tracker) { auto const now = tr_time(); auto view = tr_tracker_view{}; @@ -1666,6 +1697,8 @@ static tr_tracker_view trackerView(tr_torrent const& tor, size_t tier_index, tr_ return view; } +} // namespace tracker_view_helpers +} // namespace size_t tr_announcerTrackerCount(tr_torrent const* tor) { @@ -1682,6 +1715,8 @@ size_t tr_announcerTrackerCount(tr_torrent const* tor) tr_tracker_view tr_announcerTracker(tr_torrent const* tor, size_t nth) { + using namespace tracker_view_helpers; + TR_ASSERT(tr_isTorrent(tor)); TR_ASSERT(tor->torrent_announcer != nullptr); @@ -1709,6 +1744,8 @@ tr_tracker_view tr_announcerTracker(tr_torrent const* tor, size_t nth) // so announcer needs to update the tr_tier / tr_trackers to match void tr_announcer_impl::resetTorrent(tr_torrent* tor) { + using namespace announce_helpers; + // make a new tr_announcer_tier auto* const older = tor->torrent_announcer; tor->torrent_announcer = new tr_torrent_announcer{ this, tor }; diff --git a/libtransmission/crypto-utils-openssl.cc b/libtransmission/crypto-utils-openssl.cc index 35d3401c6..1cd28ab3e 100644 --- a/libtransmission/crypto-utils-openssl.cc +++ b/libtransmission/crypto-utils-openssl.cc @@ -27,11 +27,9 @@ #include "tr-assert.h" #include "utils.h" -/*** -**** -***/ - -static void log_openssl_error(char const* file, int line) +namespace +{ +void log_openssl_error(char const* file, int line) { unsigned long const error_code = ERR_get_error(); @@ -64,7 +62,7 @@ static void log_openssl_error(char const* file, int line) #define log_error() log_openssl_error(__FILE__, __LINE__) -static bool check_openssl_result(int result, int expected_result, bool expected_equal, char const* file, int line) +bool check_openssl_result(int result, int expected_result, bool expected_equal, char const* file, int line) { bool const ret = (result == expected_result) == expected_equal; @@ -77,13 +75,8 @@ static bool check_openssl_result(int result, int expected_result, bool expected_ } #define check_result(result) check_openssl_result((result), 1, true, __FILE__, __LINE__) -#define check_result_neq(result, x_result) check_openssl_result((result), (x_result), false, __FILE__, __LINE__) -/*** -**** -***/ - -namespace +namespace sha_helpers { class ShaHelper @@ -196,55 +189,26 @@ private: ShaHelper helper_{ EVP_sha256 }; }; +} // namespace sha_helpers } // namespace +// --- sha + std::unique_ptr tr_sha1::create() { + using namespace sha_helpers; + return std::make_unique(); } std::unique_ptr tr_sha256::create() { + using namespace sha_helpers; + return std::make_unique(); } -/*** -**** -***/ - -#if OPENSSL_VERSION_NUMBER < 0x0090802fL - -static EVP_CIPHER_CTX* openssl_evp_cipher_context_new() -{ - auto* const handle = new EVP_CIPHER_CTX{}; - - if (handle != nullptr) - { - EVP_CIPHER_CTX_init(handle); - } - - return handle; -} - -static void openssl_evp_cipher_context_free(EVP_CIPHER_CTX* handle) -{ - if (handle == nullptr) - { - return; - } - - EVP_CIPHER_CTX_cleanup(handle); - delete handle; -} - -#define EVP_CIPHER_CTX_new() openssl_evp_cipher_context_new() -#define EVP_CIPHER_CTX_free(x) openssl_evp_cipher_context_free((x)) - -#endif - -/*** -**** -***/ +// --- x509 tr_x509_store_t tr_ssl_get_x509_store(tr_ssl_ctx_t handle) { @@ -288,9 +252,7 @@ void tr_x509_cert_free(tr_x509_cert_t handle) X509_free(static_cast(handle)); } -/*** -**** -***/ +// --- rand bool tr_rand_buffer_crypto(void* buffer, size_t length) { diff --git a/libtransmission/error.cc b/libtransmission/error.cc index c471c5093..5548cfcba 100644 --- a/libtransmission/error.cc +++ b/libtransmission/error.cc @@ -15,7 +15,9 @@ #include "tr-macros.h" #include "utils.h" -static char* tr_strvDup(std::string_view in) +namespace +{ +[[nodiscard]] char* tr_strvdup(std::string_view in) { auto const n = std::size(in); auto* const ret = new char[n + 1]; @@ -23,6 +25,7 @@ static char* tr_strvDup(std::string_view in) ret[n] = '\0'; return ret; } +} // namespace void tr_error_free(tr_error* error) { @@ -43,7 +46,7 @@ void tr_error_set(tr_error** error, int code, std::string_view message) } TR_ASSERT(*error == nullptr); - *error = new tr_error{ code, tr_strvDup(message) }; + *error = new tr_error{ code, tr_strvdup(message) }; } void tr_error_propagate(tr_error** new_error, tr_error** old_error) @@ -86,7 +89,7 @@ void tr_error_prefix(tr_error** error, char const* prefix) } auto* err = *error; - auto* const new_message = tr_strvDup(fmt::format(FMT_STRING("{:s}{:s}"), prefix, err->message)); + auto* const new_message = tr_strvdup(fmt::format(FMT_STRING("{:s}{:s}"), prefix, err->message)); delete[] err->message; err->message = new_message; } diff --git a/libtransmission/makelog b/libtransmission/makelog new file mode 100644 index 000000000..cf407e178 --- /dev/null +++ b/libtransmission/makelog @@ -0,0 +1 @@ +ninja: error: loading 'build.ninja': No such file or directory diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index 443724e35..f3f4d1112 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -467,6 +467,16 @@ public: pool_is_all_seeds_.reset(); } + [[nodiscard]] peer_atom* get_existing_atom(tr_address const& addr) + { + auto const test = [&addr](auto const& atom) + { + return atom.addr == addr; + }; + auto const it = std::find_if(std::begin(pool), std::end(pool), test); + return it != std::end(pool) ? &*it : nullptr; + } + Handshakes outgoing_handshakes; uint16_t interested_count = 0; @@ -559,6 +569,12 @@ struct tr_peerMgr void refillUpkeep() const; void makeNewPeerConnections(size_t max); + [[nodiscard]] tr_swarm* get_existing_swarm(tr_sha1_digest_t const& hash) const + { + auto* const tor = session->torrents().get(hash); + return tor == nullptr ? nullptr : tor->swarm; + } + tr_session* const session; Handshakes incoming_handshakes; @@ -623,24 +639,6 @@ tr_peer::~tr_peer() *** **/ -static tr_swarm* getExistingSwarm(tr_peerMgr* manager, tr_sha1_digest_t const& hash) -{ - auto* const tor = manager->session->torrents().get(hash); - - return tor == nullptr ? nullptr : tor->swarm; -} - -static struct peer_atom* getExistingAtom(tr_swarm const* cswarm, tr_address const& addr) -{ - auto* swarm = const_cast(cswarm); - auto const test = [&addr](auto const& atom) - { - return atom.addr == addr; - }; - auto const it = std::find_if(std::begin(swarm->pool), std::end(swarm->pool), test); - return it != std::end(swarm->pool) ? &*it : nullptr; -} - static bool peerIsInUse(tr_swarm const* swarm, struct peer_atom const* atom) { return atom->is_connected || swarm->outgoing_handshakes.count(atom->addr) != 0U || @@ -701,7 +699,7 @@ static void atomSetSeed(tr_swarm* swarm, peer_atom& atom) static bool tr_peerMgrPeerIsSeed(tr_torrent const* tor, tr_address const& addr) { - if (auto const* atom = getExistingAtom(tor->swarm, addr); atom != nullptr) + if (auto const* atom = tor->swarm->get_existing_atom(addr); atom != nullptr) { return atom->isSeed(); } @@ -711,7 +709,7 @@ static bool tr_peerMgrPeerIsSeed(tr_torrent const* tor, tr_address const& addr) void tr_peerMgrSetUtpSupported(tr_torrent* tor, tr_address const& addr) { - if (auto* const atom = getExistingAtom(tor->swarm, addr); atom != nullptr) + if (auto* const atom = tor->swarm->get_existing_atom(addr); atom != nullptr) { atom->flags |= ADDED_F_UTP_FLAGS; } @@ -719,7 +717,7 @@ void tr_peerMgrSetUtpSupported(tr_torrent* tor, tr_address const& addr) void tr_peerMgrSetUtpFailed(tr_torrent* tor, tr_address const& addr, bool failed) { - if (auto* const atom = getExistingAtom(tor->swarm, addr); atom != nullptr) + if (auto* const atom = tor->swarm->get_existing_atom(addr); atom != nullptr) { atom->utp_failed = failed; } @@ -1046,7 +1044,7 @@ static struct peer_atom* ensureAtomExists( TR_ASSERT(addr.is_valid()); TR_ASSERT(from < TR_PEER_FROM__MAX); - struct peer_atom* a = getExistingAtom(s, addr); + struct peer_atom* a = s->get_existing_atom(addr); if (a == nullptr) { @@ -1097,7 +1095,7 @@ static bool on_handshake_done(tr_peerMgr* manager, tr_handshake::Result const& r bool const ok = result.is_connected; bool success = false; - tr_swarm* const s = getExistingSwarm(manager, result.io->torrent_hash()); + auto* const s = manager->get_existing_swarm(result.io->torrent_hash()); auto const [addr, port] = result.io->socket_address(); @@ -1116,7 +1114,7 @@ static bool on_handshake_done(tr_peerMgr* manager, tr_handshake::Result const& r { if (s != nullptr) { - struct peer_atom* atom = getExistingAtom(s, addr); + struct peer_atom* atom = s->get_existing_atom(addr); if (atom != nullptr) { diff --git a/libtransmission/platform-quota.cc b/libtransmission/platform-quota.cc index 2a554ca6d..3fa48650c 100644 --- a/libtransmission/platform-quota.cc +++ b/libtransmission/platform-quota.cc @@ -67,13 +67,11 @@ #include "utils.h" #include "platform-quota.h" -/*** -**** -***/ - +namespace +{ #ifndef _WIN32 -static char const* getdev(std::string_view path) +[[nodiscard]] char const* getdev(std::string_view path) { #ifdef HAVE_GETMNTENT @@ -141,7 +139,7 @@ static char const* getdev(std::string_view path) #endif } -static char const* getfstype(std::string_view device) +[[nodiscard]] char const* getfstype(std::string_view device) { #ifdef HAVE_GETMNTENT @@ -209,7 +207,7 @@ static char const* getfstype(std::string_view device) #endif } -static std::string getblkdev(std::string_view path) +std::string getblkdev(std::string_view path) { for (;;) { @@ -235,7 +233,7 @@ extern "C" #include } -struct tr_disk_space getquota(char const* device) +[[nodiscard]] tr_disk_space getquota(char const* device) { struct quotahandle* qh; struct quotakey qk; @@ -285,7 +283,7 @@ struct tr_disk_space getquota(char const* device) #else -static struct tr_disk_space getquota(char const* device) +[[nodiscard]] tr_disk_space getquota(char const* device) { #if defined(__DragonFly__) struct ufs_dqblk dq = {}; @@ -371,7 +369,7 @@ static struct tr_disk_space getquota(char const* device) #ifdef HAVE_XQM -static struct tr_disk_space getxfsquota(char const* device) +[[nodiscard]] tr_disk_space getxfsquota(char const* device) { struct tr_disk_space disk_space = { -1, -1 }; struct fs_disk_quota dq; @@ -410,7 +408,7 @@ static struct tr_disk_space getxfsquota(char const* device) #endif /* _WIN32 */ -static tr_disk_space getQuotaSpace([[maybe_unused]] tr_device_info const& info) +[[nodiscard]] tr_disk_space getQuotaSpace([[maybe_unused]] tr_device_info const& info) { struct tr_disk_space ret = { -1, -1 }; @@ -432,7 +430,7 @@ static tr_disk_space getQuotaSpace([[maybe_unused]] tr_device_info const& info) return ret; } -static struct tr_disk_space getDiskSpace(char const* path) +[[nodiscard]] tr_disk_space getDiskSpace(char const* path) { #ifdef _WIN32 @@ -468,6 +466,8 @@ static struct tr_disk_space getDiskSpace(char const* path) #endif } +} // namespace + tr_device_info tr_device_info_create(std::string_view path) { auto out = tr_device_info{}; diff --git a/libtransmission/platform.cc b/libtransmission/platform.cc index dc7b017d9..633da6767 100644 --- a/libtransmission/platform.cc +++ b/libtransmission/platform.cc @@ -45,13 +45,10 @@ using namespace std::literals; -/*** -**** PATHS -***/ - +namespace +{ #ifdef _WIN32 - -static std::string win32_get_known_folder_ex(REFKNOWNFOLDERID folder_id, DWORD flags) +std::string win32_get_known_folder_ex(REFKNOWNFOLDERID folder_id, DWORD flags) { if (PWSTR path; SHGetKnownFolderPath(folder_id, flags | KF_FLAG_DONT_UNEXPAND, nullptr, &path) == S_OK) { @@ -63,14 +60,13 @@ static std::string win32_get_known_folder_ex(REFKNOWNFOLDERID folder_id, DWORD f return {}; } -static auto win32_get_known_folder(REFKNOWNFOLDERID folder_id) +auto win32_get_known_folder(REFKNOWNFOLDERID folder_id) { return win32_get_known_folder_ex(folder_id, KF_FLAG_DONT_VERIFY); } - #endif -static std::string getHomeDir() +std::string getHomeDir() { if (auto dir = tr_env_get_string("HOME"sv); !std::empty(dir)) { @@ -100,7 +96,7 @@ static std::string getHomeDir() return {}; } -static std::string xdgConfigHome() +std::string xdgConfigHome() { if (auto dir = tr_env_get_string("XDG_CONFIG_HOME"sv); !std::empty(dir)) { @@ -110,6 +106,51 @@ static std::string xdgConfigHome() return fmt::format("{:s}/.config"sv, getHomeDir()); } +std::string getXdgEntryFromUserDirs(std::string_view key) +{ + auto content = std::vector{}; + if (auto const filename = fmt::format("{:s}/{:s}"sv, xdgConfigHome(), "user-dirs.dirs"sv); + !tr_sys_path_exists(filename) || !tr_loadFile(filename, content) || std::empty(content)) + { + return {}; + } + + // search for key="val" and extract val + auto const search = fmt::format(FMT_STRING("{:s}=\""), key); + auto begin = std::search(std::begin(content), std::end(content), std::begin(search), std::end(search)); + if (begin == std::end(content)) + { + return {}; + } + std::advance(begin, std::size(search)); + auto const end = std::find(begin, std::end(content), '"'); + if (end == std::end(content)) + { + return {}; + } + auto val = std::string{ begin, end }; + + // if val contains "$HOME", replace that with getHomeDir() + auto constexpr Home = "$HOME"sv; + if (auto const it = std::search(std::begin(val), std::end(val), std::begin(Home), std::end(Home)); it != std::end(val)) + { + val.replace(it, it + std::size(Home), getHomeDir()); + } + + return val; +} + +[[nodiscard]] bool isWebClientDir(std::string_view path) +{ + auto const filename = tr_pathbuf{ path, '/', "index.html"sv }; + bool const found = tr_sys_path_exists(filename); + tr_logAddTrace(fmt::format(FMT_STRING("Searching for web interface file '{:s}'"), filename)); + return found; +} +} // namespace + +// --- + std::string tr_getDefaultConfigDir(std::string_view appname) { if (std::empty(appname)) @@ -149,40 +190,6 @@ size_t tr_getDefaultConfigDirToBuf(char const* appname, char* buf, size_t buflen return tr_strvToBuf(tr_getDefaultConfigDir(appname != nullptr ? appname : ""), buf, buflen); } -static std::string getXdgEntryFromUserDirs(std::string_view key) -{ - auto content = std::vector{}; - if (auto const filename = fmt::format("{:s}/{:s}"sv, xdgConfigHome(), "user-dirs.dirs"sv); - !tr_sys_path_exists(filename) || !tr_loadFile(filename, content) || std::empty(content)) - { - return {}; - } - - // search for key="val" and extract val - auto const search = fmt::format(FMT_STRING("{:s}=\""), key); - auto begin = std::search(std::begin(content), std::end(content), std::begin(search), std::end(search)); - if (begin == std::end(content)) - { - return {}; - } - std::advance(begin, std::size(search)); - auto const end = std::find(begin, std::end(content), '"'); - if (end == std::end(content)) - { - return {}; - } - auto val = std::string{ begin, end }; - - // if val contains "$HOME", replace that with getHomeDir() - auto constexpr Home = "$HOME"sv; - if (auto const it = std::search(std::begin(val), std::end(val), std::begin(Home), std::end(Home)); it != std::end(val)) - { - val.replace(it, it + std::size(Home), getHomeDir()); - } - - return val; -} - std::string tr_getDefaultDownloadDir() { if (auto dir = getXdgEntryFromUserDirs("XDG_DOWNLOAD_DIR"sv); !std::empty(dir)) @@ -209,17 +216,7 @@ size_t tr_getDefaultDownloadDirToBuf(char* buf, size_t buflen) return tr_strvToBuf(tr_getDefaultDownloadDir(), buf, buflen); } -/*** -**** -***/ - -static bool isWebClientDir(std::string_view path) -{ - auto const filename = tr_pathbuf{ path, '/', "index.html"sv }; - bool const found = tr_sys_path_exists(filename); - tr_logAddTrace(fmt::format(FMT_STRING("Searching for web interface file '{:s}'"), filename)); - return found; -} +// --- std::string tr_getWebClientDir([[maybe_unused]] tr_session const* session) { diff --git a/libtransmission/port-forwarding-upnp.cc b/libtransmission/port-forwarding-upnp.cc index f41a0b882..62f243b39 100644 --- a/libtransmission/port-forwarding-upnp.cc +++ b/libtransmission/port-forwarding-upnp.cc @@ -35,7 +35,6 @@ namespace { - enum class UpnpState { Idle, @@ -45,29 +44,6 @@ enum class UpnpState WillMap, // next action is UPNP_AddPortMapping() WillUnmap // next action is UPNP_DeletePortMapping() }; - -constexpr auto portFwdState(UpnpState upnp_state, bool is_mapped) -{ - switch (upnp_state) - { - case UpnpState::WillDiscover: - case UpnpState::Discovering: - return TR_PORT_UNMAPPED; - - case UpnpState::WillMap: - return TR_PORT_MAPPING; - - case UpnpState::WillUnmap: - return TR_PORT_UNMAPPING; - - case UpnpState::Idle: - return is_mapped ? TR_PORT_MAPPED : TR_PORT_UNMAPPED; - - default: // UpnpState::FAILED: - return TR_PORT_ERROR; - } -} - } // namespace struct tr_upnp @@ -102,25 +78,31 @@ struct tr_upnp std::optional> discover_future; }; -/** -*** -**/ - -tr_upnp* tr_upnpInit() +namespace { - return new tr_upnp(); +constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped) +{ + switch (upnp_state) + { + case UpnpState::WillDiscover: + case UpnpState::Discovering: + return TR_PORT_UNMAPPED; + + case UpnpState::WillMap: + return TR_PORT_MAPPING; + + case UpnpState::WillUnmap: + return TR_PORT_UNMAPPING; + + case UpnpState::Idle: + return is_mapped ? TR_PORT_MAPPED : TR_PORT_UNMAPPED; + + default: // UpnpState::FAILED: + return TR_PORT_ERROR; + } } -void tr_upnpClose(tr_upnp* handle) -{ - delete handle; -} - -/** -*** Wrappers for miniupnpc functions -**/ - -static struct UPNPDev* tr_upnpDiscover(int msec, char const* bindaddr) +[[nodiscard]] UPNPDev* upnp_discover(int msec, char const* bindaddr) { UPNPDev* ret = nullptr; auto have_err = bool{}; @@ -148,7 +130,7 @@ static struct UPNPDev* tr_upnpDiscover(int msec, char const* bindaddr) return ret; } -static int tr_upnpGetSpecificPortMappingEntry(tr_upnp const* handle, char const* proto) +[[nodiscard]] int get_specific_port_mapping_entry(tr_upnp const* handle, char const* proto) { auto int_client = std::array{}; auto int_port = std::array{}; @@ -191,7 +173,7 @@ static int tr_upnpGetSpecificPortMappingEntry(tr_upnp const* handle, char const* return err; } -static int tr_upnpAddPortMapping(tr_upnp const* handle, char const* proto, tr_port port, char const* desc) +[[nodiscard]] int upnp_add_port_mapping(tr_upnp const* handle, char const* proto, tr_port port, char const* desc) { int const old_errno = errno; errno = 0; @@ -230,17 +212,13 @@ static int tr_upnpAddPortMapping(tr_upnp const* handle, char const* proto, tr_po return err; } -static void tr_upnpDeletePortMapping(tr_upnp const* handle, char const* proto, tr_port port) +void tr_upnpDeletePortMapping(tr_upnp const* handle, char const* proto, tr_port port) { auto const port_str = fmt::format(FMT_STRING("{:d}"), port.host()); UPNP_DeletePortMapping(handle->urls.controlURL, handle->data.first.servicetype, port_str.c_str(), proto, nullptr); } -/** -*** -**/ - enum { UPNP_IGD_NONE = 0, @@ -249,19 +227,32 @@ enum UPNP_IGD_INVALID = 3 }; -static auto* discoverThreadfunc(std::string bindaddr) // NOLINT performance-unnecessary-value-param +auto* discover_thread_func(std::string bindaddr) // NOLINT performance-unnecessary-value-param { // If multicastif is not NULL, it will be used instead of the default // multicast interface for sending SSDP discover packets. char const* multicastif = std::empty(bindaddr) ? nullptr : bindaddr.c_str(); - return tr_upnpDiscover(2000, multicastif); + return upnp_discover(2000, multicastif); } template -static bool isFutureReady(std::future const& future) +bool is_future_ready(std::future const& future) { return future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } +} // namespace + +// --- + +tr_upnp* tr_upnpInit() +{ + return new tr_upnp(); +} + +void tr_upnpClose(tr_upnp* handle) +{ + delete handle; +} tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_enabled, bool do_port_check, std::string bindaddr) { @@ -269,7 +260,7 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena { TR_ASSERT(!handle->discover_future); - auto task = std::packaged_task{ discoverThreadfunc }; + auto task = std::packaged_task{ discover_thread_func }; handle->discover_future = task.get_future(); handle->state = UpnpState::Discovering; @@ -277,7 +268,7 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena } if (is_enabled && handle->state == UpnpState::Discovering && handle->discover_future && - isFutureReady(*handle->discover_future)) + is_future_ready(*handle->discover_future)) { auto* const devlist = handle->discover_future->get(); handle->discover_future.reset(); @@ -309,8 +300,8 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena } if (is_enabled && handle->isMapped && do_port_check && - ((tr_upnpGetSpecificPortMappingEntry(handle, "TCP") != UPNPCOMMAND_SUCCESS) || - (tr_upnpGetSpecificPortMappingEntry(handle, "UDP") != UPNPCOMMAND_SUCCESS))) + ((get_specific_port_mapping_entry(handle, "TCP") != UPNPCOMMAND_SUCCESS) || + (get_specific_port_mapping_entry(handle, "UDP") != UPNPCOMMAND_SUCCESS))) { tr_logAddInfo(fmt::format(_("Port {port} is not forwarded"), fmt::arg("port", handle->port.host()))); handle->isMapped = false; @@ -347,8 +338,8 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena else { auto const desc = fmt::format(FMT_STRING("Transmission at {:d}"), port.host()); - int const err_tcp = tr_upnpAddPortMapping(handle, "TCP", port, desc.c_str()); - int const err_udp = tr_upnpAddPortMapping(handle, "UDP", port, desc.c_str()); + int const err_tcp = upnp_add_port_mapping(handle, "TCP", port, desc.c_str()); + int const err_udp = upnp_add_port_mapping(handle, "UDP", port, desc.c_str()); handle->isMapped = err_tcp == 0 || err_udp == 0; } @@ -374,5 +365,5 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena } } - return portFwdState(handle->state, handle->isMapped); + return port_fwd_state(handle->state, handle->isMapped); } diff --git a/libtransmission/subprocess-posix.cc b/libtransmission/subprocess-posix.cc index 056f0a4b1..8f446a047 100644 --- a/libtransmission/subprocess-posix.cc +++ b/libtransmission/subprocess-posix.cc @@ -27,7 +27,9 @@ using namespace std::literals; -static void handle_sigchld(int /*i*/) +namespace +{ +void handle_sigchld(int /*i*/) { int rc = 0; @@ -40,7 +42,7 @@ static void handle_sigchld(int /*i*/) /* FIXME: Call old handler, if any */ } -static void set_system_error(tr_error** error, int code, std::string_view what) +void set_system_error(tr_error** error, int code, std::string_view what) { if (error == nullptr) { @@ -50,7 +52,7 @@ static void set_system_error(tr_error** error, int code, std::string_view what) tr_error_set(error, code, fmt::format(FMT_STRING("{:s} failed: {:s} ({:d})"), what, tr_strerror(code), code)); } -static bool tr_spawn_async_in_child( +[[nodiscard]] bool tr_spawn_async_in_child( char const* const* cmd, std::map const& env, std::string_view work_dir) @@ -82,7 +84,7 @@ static bool tr_spawn_async_in_child( return true; } -static bool tr_spawn_async_in_parent(int pipe_fd, tr_error** error) +[[nodiscard]] bool tr_spawn_async_in_parent(int pipe_fd, tr_error** error) { int child_errno = 0; ssize_t count = 0; @@ -114,6 +116,7 @@ static bool tr_spawn_async_in_parent(int pipe_fd, tr_error** error) return true; } +} // namespace bool tr_spawn_async( char const* const* cmd, diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index 6b11cd8db..4c034edf3 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -54,9 +54,7 @@ using namespace std::literals; -/*** -**** -***/ +// --- char const* tr_torrentName(tr_torrent const* tor) { @@ -133,22 +131,93 @@ bool tr_torrentSetMetainfoFromFile(tr_torrent* tor, tr_torrent_metainfo const* m return true; } -/*** -**** -***/ +// --- -static constexpr void tr_torrentUnsetPeerId(tr_torrent* tor) +namespace +{ +void torrentSetQueued(tr_torrent* tor, bool queued) +{ + TR_ASSERT(tr_isTorrent(tor)); + + if (tor->isQueued() != queued) + { + tor->is_queued = queued; + tor->markChanged(); + tor->setDirty(); + } +} + +bool setLocalErrorIfFilesDisappeared(tr_torrent* tor, std::optional has_local_data = {}) +{ + auto const has = has_local_data ? *has_local_data : tor->hasAnyLocalData(); + bool const files_disappeared = tor->hasTotal() > 0 && !has; + + if (files_disappeared) + { + tr_logAddTraceTor(tor, "[LAZY] uh oh, the files disappeared"); + tor->setLocalError(_( + "No data found! Ensure your drives are connected or use \"Set Location\". To re-download, remove the torrent and re-add it.")); + } + + return files_disappeared; +} + +void tr_torrentClearError(tr_torrent* tor) +{ + tor->error = TR_STAT_OK; + tor->error_announce_url.clear(); + tor->error_string.clear(); +} + +constexpr void tr_torrentUnsetPeerId(tr_torrent* tor) { // triggers a rebuild next time tr_torrentGetPeerId() is called tor->peer_id_ = {}; } -static int peerIdTTL(tr_torrent const* tor) +int peerIdTTL(tr_torrent const* tor) { auto const ctime = tor->peer_id_creation_time_; return ctime == 0 ? 0 : (int)difftime(ctime + tor->session->peerIdTTLHours() * 3600, tr_time()); } +/* returns true if the seed ratio applies -- + * it applies if the torrent's a seed AND it has a seed ratio set */ +bool tr_torrentGetSeedRatioBytes(tr_torrent const* tor, uint64_t* setme_left, uint64_t* setme_goal) +{ + bool seed_ratio_applies = false; + + TR_ASSERT(tr_isTorrent(tor)); + + if (auto seed_ratio = double{}; tr_torrentGetSeedRatio(tor, &seed_ratio)) + { + auto const uploaded = tor->uploadedCur + tor->uploadedPrev; + auto const baseline = tor->sizeWhenDone(); + auto const goal = baseline * seed_ratio; + + if (setme_left != nullptr) + { + *setme_left = goal > uploaded ? goal - uploaded : 0; + } + + if (setme_goal != nullptr) + { + *setme_goal = goal; + } + + seed_ratio_applies = tor->isDone(); + } + + return seed_ratio_applies; +} + +bool tr_torrentIsSeedRatioDone(tr_torrent const* tor) +{ + auto bytes_left = uint64_t{}; + return tr_torrentGetSeedRatioBytes(tor, &bytes_left, nullptr) && bytes_left == 0; +} +} // namespace + tr_peer_id_t const& tr_torrentGetPeerId(tr_torrent* tor) { bool const needs_new_peer_id = tor->peer_id_[0] == '\0' || // doesn't have one @@ -163,9 +232,7 @@ tr_peer_id_t const& tr_torrentGetPeerId(tr_torrent* tor) return tor->peer_id_; } -/*** -**** PER-TORRENT UL / DL SPEEDS -***/ +// --- PER-TORRENT UL / DL SPEEDS void tr_torrentSetSpeedLimit_KBps(tr_torrent* tor, tr_direction dir, tr_kilobytes_per_second_t kilo_per_second) { @@ -212,9 +279,7 @@ bool tr_torrentUsesSessionLimits(tr_torrent const* tor) return tor->usesSessionLimits(); } -/*** -**** -***/ +// --- Download Ratio void tr_torrentSetRatioMode(tr_torrent* tor, tr_ratiolimit mode) { @@ -286,45 +351,7 @@ bool tr_torrentGetSeedRatio(tr_torrent const* tor, double* ratio) return is_limited; } -/* returns true if the seed ratio applies -- - * it applies if the torrent's a seed AND it has a seed ratio set */ -static bool tr_torrentGetSeedRatioBytes(tr_torrent const* tor, uint64_t* setme_left, uint64_t* setme_goal) -{ - bool seed_ratio_applies = false; - - TR_ASSERT(tr_isTorrent(tor)); - - if (auto seed_ratio = double{}; tr_torrentGetSeedRatio(tor, &seed_ratio)) - { - auto const uploaded = tor->uploadedCur + tor->uploadedPrev; - auto const baseline = tor->sizeWhenDone(); - auto const goal = baseline * seed_ratio; - - if (setme_left != nullptr) - { - *setme_left = goal > uploaded ? goal - uploaded : 0; - } - - if (setme_goal != nullptr) - { - *setme_goal = goal; - } - - seed_ratio_applies = tor->isDone(); - } - - return seed_ratio_applies; -} - -static bool tr_torrentIsSeedRatioDone(tr_torrent const* tor) -{ - auto bytes_left = uint64_t{}; - return tr_torrentGetSeedRatioBytes(tor, &bytes_left, nullptr) && bytes_left == 0; -} - -/*** -**** -***/ +// --- void tr_torrentSetIdleMode(tr_torrent* tor, tr_idlelimit mode) { @@ -394,13 +421,6 @@ bool tr_torrentGetSeedIdle(tr_torrent const* tor, uint16_t* idle_minutes) return is_limited; } -static bool tr_torrentIsSeedIdleLimitDone(tr_torrent const* tor) -{ - auto idle_minutes = uint16_t{}; - return tr_torrentGetSeedIdle(tor, &idle_minutes) && - difftime(tr_time(), std::max(tor->startDate, tor->activityDate)) >= idle_minutes * 60U; -} - namespace { namespace script_helpers @@ -502,8 +522,23 @@ void callScriptIfEnabled(tr_torrent const* tor, TrScript type) // --- +namespace +{ +namespace seed_limit_helpers +{ +bool tr_torrentIsSeedIdleLimitDone(tr_torrent const* tor) +{ + auto idle_minutes = uint16_t{}; + return tr_torrentGetSeedIdle(tor, &idle_minutes) && + difftime(tr_time(), std::max(tor->startDate, tor->activityDate)) >= idle_minutes * 60U; +} +} // namespace seed_limit_helpers +} // namespace + void tr_torrentCheckSeedLimit(tr_torrent* tor) { + using namespace seed_limit_helpers; + TR_ASSERT(tr_isTorrent(tor)); if (!tor->isRunning || tor->isStopping || !tor->isDone()) @@ -534,18 +569,463 @@ void tr_torrentCheckSeedLimit(tr_torrent* tor) } } -/*** -**** -***/ +// --- Queue -static void tr_torrentClearError(tr_torrent* tor) +namespace { - tor->error = TR_STAT_OK; - tor->error_announce_url.clear(); - tor->error_string.clear(); +namespace queue_helpers +{ +struct CompareTorrentByQueuePosition +{ + constexpr bool operator()(tr_torrent const* a, tr_torrent const* b) const noexcept + { + return a->queuePosition < b->queuePosition; + } +}; + +#ifdef TR_ENABLE_ASSERTS +bool queueIsSequenced(tr_session const* session) +{ + auto torrents = session->getAllTorrents(); + std::sort( + std::begin(torrents), + std::end(torrents), + [](auto const* a, auto const* b) { return a->queuePosition < b->queuePosition; }); + + /* test them */ + bool is_sequenced = true; + + for (size_t i = 0, n = std::size(torrents); is_sequenced && i < n; ++i) + { + is_sequenced = torrents[i]->queuePosition == i; + } + + return is_sequenced; +} +#endif +} // namespace queue_helpers +} // namespace + +size_t tr_torrentGetQueuePosition(tr_torrent const* tor) +{ + return tor->queuePosition; } -static void onTrackerResponse(tr_torrent* tor, tr_tracker_event const* event, void* /*user_data*/) +void tr_torrentSetQueuePosition(tr_torrent* tor, size_t queue_position) +{ + using namespace queue_helpers; + + size_t current = 0; + auto const old_pos = tor->queuePosition; + + tor->queuePosition = static_cast(-1); + + for (auto* const walk : tor->session->torrents()) + { + if ((old_pos < queue_position) && (old_pos <= walk->queuePosition) && (walk->queuePosition <= queue_position)) + { + walk->queuePosition--; + walk->markChanged(); + } + + if ((old_pos > queue_position) && (queue_position <= walk->queuePosition) && (walk->queuePosition < old_pos)) + { + walk->queuePosition++; + walk->markChanged(); + } + + if (current < walk->queuePosition + 1) + { + current = walk->queuePosition + 1; + } + } + + tor->queuePosition = std::min(queue_position, current); + tor->markChanged(); + + TR_ASSERT(queueIsSequenced(tor->session)); +} + +void tr_torrentsQueueMoveTop(tr_torrent* const* torrents_in, size_t torrent_count) +{ + using namespace queue_helpers; + + auto torrents = std::vector(torrents_in, torrents_in + torrent_count); + std::sort(std::rbegin(torrents), std::rend(torrents), CompareTorrentByQueuePosition{}); + for (auto* tor : torrents) + { + tr_torrentSetQueuePosition(tor, 0); + } +} + +void tr_torrentsQueueMoveUp(tr_torrent* const* torrents_in, size_t torrent_count) +{ + using namespace queue_helpers; + + auto torrents = std::vector(torrents_in, torrents_in + torrent_count); + std::sort(std::begin(torrents), std::end(torrents), CompareTorrentByQueuePosition{}); + for (auto* tor : torrents) + { + if (tor->queuePosition > 0) + { + tr_torrentSetQueuePosition(tor, tor->queuePosition - 1); + } + } +} + +void tr_torrentsQueueMoveDown(tr_torrent* const* torrents_in, size_t torrent_count) +{ + using namespace queue_helpers; + + auto torrents = std::vector(torrents_in, torrents_in + torrent_count); + std::sort(std::rbegin(torrents), std::rend(torrents), CompareTorrentByQueuePosition{}); + for (auto* tor : torrents) + { + if (tor->queuePosition < UINT_MAX) + { + tr_torrentSetQueuePosition(tor, tor->queuePosition + 1); + } + } +} + +void tr_torrentsQueueMoveBottom(tr_torrent* const* torrents_in, size_t torrent_count) +{ + using namespace queue_helpers; + + auto torrents = std::vector(torrents_in, torrents_in + torrent_count); + std::sort(std::begin(torrents), std::end(torrents), CompareTorrentByQueuePosition{}); + for (auto* tor : torrents) + { + tr_torrentSetQueuePosition(tor, UINT_MAX); + } +} + +// --- Start, Stop + +namespace +{ +namespace start_stop_helpers +{ +bool torrentShouldQueue(tr_torrent const* const tor) +{ + tr_direction const dir = tor->queueDirection(); + + return tor->session->countQueueFreeSlots(dir) == 0; +} + +void torrentResetTransferStats(tr_torrent* tor) +{ + auto const lock = tor->unique_lock(); + + tor->downloadedPrev += tor->downloadedCur; + tor->downloadedCur = 0; + tor->uploadedPrev += tor->uploadedCur; + tor->uploadedCur = 0; + tor->corruptPrev += tor->corruptCur; + tor->corruptCur = 0; + + tor->setDirty(); +} + +void torrentStartImpl(tr_torrent* const tor) +{ + auto const lock = tor->unique_lock(); + + TR_ASSERT(tr_isTorrent(tor)); + + tor->recheckCompleteness(); + torrentSetQueued(tor, false); + + time_t const now = tr_time(); + + tor->isRunning = true; + tor->completeness = tor->completion.status(); + tor->startDate = now; + tor->markChanged(); + tr_torrentClearError(tor); + tor->finishedSeedingByIdle = false; + + torrentResetTransferStats(tor); + tor->session->announcer_->startTorrent(tor); + tor->lpdAnnounceAt = now; + tr_peerMgrStartTorrent(tor); +} + +bool removeTorrentFile(char const* filename, void* /*user_data*/, tr_error** error) +{ + return tr_sys_path_remove(filename, error); +} + +void removeTorrentInSessionThread(tr_torrent* tor, bool delete_flag, tr_fileFunc delete_func, void* user_data) +{ + auto const lock = tor->unique_lock(); + + if (delete_flag && tor->hasMetainfo()) + { + // ensure the files are all closed and idle before moving + tor->session->closeTorrentFiles(tor); + tor->session->verifyRemove(tor); + + if (delete_func == nullptr) + { + delete_func = removeTorrentFile; + } + + auto const delete_func_wrapper = [&delete_func, user_data](char const* filename) + { + delete_func(filename, user_data, nullptr); + }; + tor->metainfo_.files().remove(tor->currentDir(), tor->name(), delete_func_wrapper); + } + + tr_torrentFreeInSessionThread(tor); +} + +void freeTorrent(tr_torrent* tor) +{ + using namespace queue_helpers; + + auto const lock = tor->unique_lock(); + + TR_ASSERT(!tor->isRunning); + + tr_session* session = tor->session; + + tr_peerMgrRemoveTorrent(tor); + + session->announcer_->removeTorrent(tor); + + session->torrents().remove(tor, tr_time()); + + if (!session->isClosing()) + { + // "so you die, captain, and we all move up in rank." + // resequence the queue positions + for (auto* t : session->torrents()) + { + if (t->queuePosition > tor->queuePosition) + { + t->queuePosition--; + t->markChanged(); + } + } + + TR_ASSERT(queueIsSequenced(session)); + } + + delete tor; +} +} // namespace start_stop_helpers + +struct torrent_start_opts +{ + bool bypass_queue = false; + + // true or false if we know whether or not local data exists, + // or unset if we don't know and need to check for ourselves + std::optional has_local_data; +}; + +void torrentStart(tr_torrent* tor, torrent_start_opts opts) +{ + using namespace start_stop_helpers; + + switch (tor->activity()) + { + case TR_STATUS_SEED: + case TR_STATUS_DOWNLOAD: + return; /* already started */ + + case TR_STATUS_SEED_WAIT: + case TR_STATUS_DOWNLOAD_WAIT: + if (!opts.bypass_queue) + { + return; /* already queued */ + } + + break; + + case TR_STATUS_CHECK: + case TR_STATUS_CHECK_WAIT: + /* verifying right now... wait until that's done so + * we'll know what completeness to use/announce */ + tor->startAfterVerify = true; + return; + + case TR_STATUS_STOPPED: + if (!opts.bypass_queue && torrentShouldQueue(tor)) + { + torrentSetQueued(tor, true); + return; + } + + break; + } + + /* don't allow the torrent to be started if the files disappeared */ + if (setLocalErrorIfFilesDisappeared(tor, opts.has_local_data)) + { + return; + } + + /* otherwise, start it now... */ + auto const lock = tor->unique_lock(); + + /* allow finished torrents to be resumed */ + if (tr_torrentIsSeedRatioDone(tor)) + { + tr_logAddInfoTor(tor, _("Restarted manually -- disabling its seed ratio")); + tor->setRatioMode(TR_RATIOLIMIT_UNLIMITED); + } + + /* corresponds to the peer_id sent as a tracker request parameter. + * one tracker admin says: "When the same torrent is opened and + * closed and opened again without quitting Transmission ... + * change the peerid. It would help sometimes if a stopped event + * was missed to ensure that we didn't think someone was cheating. */ + tr_torrentUnsetPeerId(tor); + tor->isRunning = true; + tor->setDirty(); + tor->session->runInSessionThread(torrentStartImpl, tor); +} + +void torrentStop(tr_torrent* const tor) +{ + TR_ASSERT(tr_isTorrent(tor)); + TR_ASSERT(tor->session->amInSessionThread()); + auto const lock = tor->unique_lock(); + + if (!tor->session->isClosing()) + { + tr_logAddInfoTor(tor, _("Pausing torrent")); + } + + tor->session->verifyRemove(tor); + + tr_peerMgrStopTorrent(tor); + tor->session->announcer_->stopTorrent(tor); + + tor->session->closeTorrentFiles(tor); + + if (!tor->isDeleting) + { + tr_torrentSave(tor); + } + + torrentSetQueued(tor, false); + + if (tor->magnetVerify) + { + tor->magnetVerify = false; + tr_logAddTraceTor(tor, "Magnet Verify"); + tor->refreshCurrentDir(); + tr_torrentVerify(tor); + + callScriptIfEnabled(tor, TR_SCRIPT_ON_TORRENT_ADDED); + } +} +} // namespace + +void tr_torrentStop(tr_torrent* tor) +{ + if (!tr_isTorrent(tor)) + { + return; + } + + auto const lock = tor->unique_lock(); + + tor->isRunning = false; + tor->isStopping = false; + tor->setDirty(); + tor->session->runInSessionThread(torrentStop, tor); +} + +void tr_torrentRemove(tr_torrent* tor, bool delete_flag, tr_fileFunc delete_func, void* user_data) +{ + using namespace start_stop_helpers; + + TR_ASSERT(tr_isTorrent(tor)); + + tor->isDeleting = true; + + tor->session->runInSessionThread(removeTorrentInSessionThread, tor, delete_flag, delete_func, user_data); +} + +void tr_torrentFreeInSessionThread(tr_torrent* tor) +{ + using namespace start_stop_helpers; + + TR_ASSERT(tr_isTorrent(tor)); + TR_ASSERT(tor->session != nullptr); + TR_ASSERT(tor->session->amInSessionThread()); + + if (!tor->session->isClosing()) + { + tr_logAddInfoTor(tor, _("Removing torrent")); + } + + tor->magnetVerify = false; + torrentStop(tor); + + if (tor->isDeleting) + { + tr_torrent_metainfo::removeFile(tor->session->torrentDir(), tor->name(), tor->infoHashString(), ".torrent"sv); + tr_torrent_metainfo::removeFile(tor->session->torrentDir(), tor->name(), tor->infoHashString(), ".magnet"sv); + tr_torrent_metainfo::removeFile(tor->session->resumeDir(), tor->name(), tor->infoHashString(), ".resume"sv); + } + + tor->isRunning = false; + freeTorrent(tor); +} + +// --- + +namespace +{ +namespace torrent_init_helpers +{ +// Sniff out newly-added seeds so that they can skip the verify step +bool isNewTorrentASeed(tr_torrent* tor) +{ + if (!tor->hasMetainfo()) + { + return false; + } + + for (tr_file_index_t i = 0, n = tor->fileCount(); i < n; ++i) + { + // it's not a new seed if a file is missing + auto const found = tor->findFile(i); + if (!found) + { + return false; + } + + // it's not a new seed if a file is partial + if (tr_strvEndsWith(found->filename(), tr_torrent_files::PartialFileSuffix)) + { + return false; + } + + // it's not a new seed if a file size is wrong + if (found->size != tor->fileSize(i)) + { + return false; + } + + // it's not a new seed if it was modified after it was added + if (found->last_modified_at >= tor->addedDate) + { + return false; + } + } + + // check the first piece + return tor->ensurePieceIsChecked(0); +} + +void onTrackerResponse(tr_torrent* tor, tr_tracker_event const* event, void* /*user_data*/) { switch (event->type) { @@ -585,24 +1065,7 @@ static void onTrackerResponse(tr_torrent* tor, tr_tracker_event const* event, vo } } -/*** -**** -**** TORRENT INSTANTIATION -**** -***/ - -struct torrent_start_opts -{ - bool bypass_queue = false; - - // true or false if we know whether or not local data exists, - // or unset if we don't know and need to check for ourselves - std::optional has_local_data; -}; - -static void torrentStart(tr_torrent* tor, torrent_start_opts opts); - -static void torrentInitFromInfoDict(tr_torrent* tor) +void torrentInitFromInfoDict(tr_torrent* tor) { tor->completion = tr_completion{ tor, &tor->blockInfo() }; tor->obfuscated_hash = tr_sha1::digest("req2"sv, tor->infoHash()); @@ -613,105 +1076,7 @@ static void torrentInitFromInfoDict(tr_torrent* tor) tor->checked_pieces_ = tr_bitfield{ size_t(tor->pieceCount()) }; } -void tr_torrent::setMetainfo(tr_torrent_metainfo const& tm) -{ - metainfo_ = tm; - - torrentInitFromInfoDict(this); - tr_peerMgrOnTorrentGotMetainfo(this); - session->onMetadataCompleted(this); - this->setDirty(); -} - -static size_t buildSearchPathArray(tr_torrent const* tor, std::string_view* paths) -{ - auto* walk = paths; - - if (auto const& path = tor->downloadDir(); !std::empty(path)) - { - *walk++ = path.sv(); - } - - if (auto const& path = tor->incompleteDir(); !std::empty(path)) - { - *walk++ = path.sv(); - } - - return walk - paths; -} - -std::optional tr_torrent::findFile(tr_file_index_t file_index) const -{ - auto paths = std::array{}; - auto const n_paths = buildSearchPathArray(this, std::data(paths)); - return metainfo_.files().find(file_index, std::data(paths), n_paths); -} - -bool tr_torrent::hasAnyLocalData() const -{ - auto paths = std::array{}; - auto const n_paths = buildSearchPathArray(this, std::data(paths)); - return metainfo_.files().hasAnyLocalData(std::data(paths), n_paths); -} - -static bool setLocalErrorIfFilesDisappeared(tr_torrent* tor, std::optional has_local_data = {}) -{ - auto const has = has_local_data ? *has_local_data : tor->hasAnyLocalData(); - bool const files_disappeared = tor->hasTotal() > 0 && !has; - - if (files_disappeared) - { - tr_logAddTraceTor(tor, "[LAZY] uh oh, the files disappeared"); - tor->setLocalError(_( - "No data found! Ensure your drives are connected or use \"Set Location\". To re-download, remove the torrent and re-add it.")); - } - - return files_disappeared; -} - -/** - * Sniff out newly-added seeds so that they can skip the verify step - */ -static bool isNewTorrentASeed(tr_torrent* tor) -{ - if (!tor->hasMetainfo()) - { - return false; - } - - for (tr_file_index_t i = 0, n = tor->fileCount(); i < n; ++i) - { - // it's not a new seed if a file is missing - auto const found = tor->findFile(i); - if (!found) - { - return false; - } - - // it's not a new seed if a file is partial - if (tr_strvEndsWith(found->filename(), tr_torrent_files::PartialFileSuffix)) - { - return false; - } - - // it's not a new seed if a file size is wrong - if (found->size != tor->fileSize(i)) - { - return false; - } - - // it's not a new seed if it was modified after it was added - if (found->last_modified_at >= tor->addedDate) - { - return false; - } - } - - // check the first piece - return tor->ensurePieceIsChecked(0); -} - -static void torrentInit(tr_torrent* tor, tr_ctor const* ctor) +void torrentInit(tr_torrent* tor, tr_ctor const* ctor) { tr_session* session = tr_ctorGetSession(ctor); TR_ASSERT(session != nullptr); @@ -888,9 +1253,25 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor) setLocalErrorIfFilesDisappeared(tor, has_local_data); } } +} // namespace torrent_init_helpers +} // namespace + +void tr_torrent::setMetainfo(tr_torrent_metainfo const& tm) +{ + using namespace torrent_init_helpers; + + metainfo_ = tm; + + torrentInitFromInfoDict(this); + tr_peerMgrOnTorrentGotMetainfo(this); + session->onMetadataCompleted(this); + this->setDirty(); +} tr_torrent* tr_torrentNew(tr_ctor* ctor, tr_torrent** setme_duplicate_of) { + using namespace torrent_init_helpers; + TR_ASSERT(ctor != nullptr); auto* const session = tr_ctorGetSession(ctor); TR_ASSERT(session != nullptr); @@ -918,9 +1299,137 @@ tr_torrent* tr_torrentNew(tr_ctor* ctor, tr_torrent** setme_duplicate_of) return tor; } -/** -*** -**/ +// --- Location + +namespace +{ +namespace location_helpers +{ +void setLocationInSessionThread( + tr_torrent* tor, + std::string const& path, + bool move_from_old_path, + double volatile* setme_progress, + int volatile* setme_state) +{ + TR_ASSERT(tr_isTorrent(tor)); + TR_ASSERT(tor->session->amInSessionThread()); + + auto ok = bool{ true }; + if (move_from_old_path) + { + if (setme_state != nullptr) + { + *setme_state = TR_LOC_MOVING; + } + + // ensure the files are all closed and idle before moving + tor->session->closeTorrentFiles(tor); + tor->session->verifyRemove(tor); + + tr_error* error = nullptr; + ok = tor->metainfo_.files().move(tor->currentDir(), path, setme_progress, tor->name(), &error); + if (error != nullptr) + { + tor->setLocalError(fmt::format( + _("Couldn't move '{old_path}' to '{path}': {error} ({error_code})"), + fmt::arg("old_path", tor->currentDir()), + fmt::arg("path", path), + fmt::arg("error", error->message), + fmt::arg("error_code", error->code))); + tr_torrentStop(tor); + tr_error_clear(&error); + } + } + + // tell the torrent where the files are + if (ok) + { + tor->setDownloadDir(path); + + if (move_from_old_path) + { + tor->incomplete_dir.clear(); + tor->current_dir = tor->downloadDir(); + } + } + + if (setme_state != nullptr) + { + *setme_state = ok ? TR_LOC_DONE : TR_LOC_ERROR; + } +} +size_t buildSearchPathArray(tr_torrent const* tor, std::string_view* paths) +{ + auto* walk = paths; + + if (auto const& path = tor->downloadDir(); !std::empty(path)) + { + *walk++ = path.sv(); + } + + if (auto const& path = tor->incompleteDir(); !std::empty(path)) + { + *walk++ = path.sv(); + } + + return walk - paths; +} +} // namespace location_helpers +} // namespace + +void tr_torrent::setLocation( + std::string_view location, + bool move_from_old_path, + double volatile* setme_progress, + int volatile* setme_state) +{ + using namespace location_helpers; + + if (setme_state != nullptr) + { + *setme_state = TR_LOC_MOVING; + } + + this->session->runInSessionThread( + setLocationInSessionThread, + this, + std::string{ location }, + move_from_old_path, + setme_progress, + setme_state); +} + +void tr_torrentSetLocation( + tr_torrent* tor, + char const* location, + bool move_from_old_path, + double volatile* setme_progress, + int volatile* setme_state) +{ + TR_ASSERT(tr_isTorrent(tor)); + TR_ASSERT(!tr_str_is_empty(location)); + + tor->setLocation(location, move_from_old_path, setme_progress, setme_state); +} + +std::optional tr_torrent::findFile(tr_file_index_t file_index) const +{ + using namespace location_helpers; + + auto paths = std::array{}; + auto const n_paths = buildSearchPathArray(this, std::data(paths)); + return metainfo_.files().find(file_index, std::data(paths), n_paths); +} + +bool tr_torrent::hasAnyLocalData() const +{ + using namespace location_helpers; + + auto paths = std::array{}; + auto const n_paths = buildSearchPathArray(this, std::data(paths)); + return metainfo_.files().hasAnyLocalData(std::data(paths), n_paths); +} void tr_torrentSetDownloadDir(tr_torrent* tor, char const* path) { @@ -956,7 +1465,13 @@ void tr_torrentChangeMyPort(tr_torrent* tor) } } -static inline void tr_torrentManualUpdateImpl(tr_torrent* const tor) +// --- + +namespace +{ +namespace manual_update_helpers +{ +void torrentManualUpdateImpl(tr_torrent* const tor) { TR_ASSERT(tr_isTorrent(tor)); @@ -965,12 +1480,16 @@ static inline void tr_torrentManualUpdateImpl(tr_torrent* const tor) tr_announcerManualAnnounce(tor); } } +} // namespace manual_update_helpers +} // namespace void tr_torrentManualUpdate(tr_torrent* tor) { + using namespace manual_update_helpers; + TR_ASSERT(tr_isTorrent(tor)); - tor->session->runInSessionThread(tr_torrentManualUpdateImpl, tor); + tor->session->runInSessionThread(torrentManualUpdateImpl, tor); } bool tr_torrentCanManualUpdate(tr_torrent const* tor) @@ -978,36 +1497,30 @@ bool tr_torrentCanManualUpdate(tr_torrent const* tor) return tr_isTorrent(tor) && tor->isRunning && tr_announcerCanManualAnnounce(tor); } -tr_stat const* tr_torrentStatCached(tr_torrent* tor) +// --- + +namespace { - time_t const now = tr_time(); - - return (tr_isTorrent(tor) && now == tor->lastStatTime) ? &tor->stats : tr_torrentStat(tor); -} - -void tr_torrent::setVerifyState(tr_verify_state state) +namespace stat_helpers { - TR_ASSERT(state == TR_VERIFY_NONE || state == TR_VERIFY_WAIT || state == TR_VERIFY_NOW); - - this->verify_state_ = state; - this->verify_progress_ = {}; - this->markChanged(); -} - -static time_t torrentGetIdleSecs(tr_torrent const* tor, tr_torrent_activity activity) +[[nodiscard]] time_t torrentGetIdleSecs(tr_torrent const* tor, tr_torrent_activity activity) { return ((activity == TR_STATUS_DOWNLOAD || activity == TR_STATUS_SEED) && tor->startDate != 0) ? (time_t)difftime(tr_time(), std::max(tor->startDate, tor->activityDate)) : -1; } -static inline bool tr_torrentIsStalled(tr_torrent const* tor, size_t idle_secs) +[[nodiscard]] constexpr bool tr_torrentIsStalled(tr_torrent const* tor, size_t idle_secs) { return tor->session->queueStalledEnabled() && idle_secs > tor->session->queueStalledMinutes() * 60U; } +} // namespace stat_helpers +} // namespace tr_stat const* tr_torrentStat(tr_torrent* tor) { + using namespace stat_helpers; + TR_ASSERT(tr_isTorrent(tor)); auto const now = tr_time_msec(); @@ -1172,9 +1685,14 @@ tr_stat const* tr_torrentStat(tr_torrent* tor) return s; } -/*** -**** -***/ +tr_stat const* tr_torrentStatCached(tr_torrent* tor) +{ + time_t const now = tr_time(); + + return (tr_isTorrent(tor) && now == tor->lastStatTime) ? &tor->stats : tr_torrentStat(tor); +} + +// --- tr_file_view tr_torrentFile(tr_torrent const* tor, tr_file_index_t file) { @@ -1253,9 +1771,7 @@ size_t tr_torrentFilenameToBuf(tr_torrent const* tor, char* buf, size_t buflen) return tr_strvToBuf(tr_torrentFilename(tor), buf, buflen); } -/*** -**** -***/ +// --- tr_peer_stat* tr_torrentPeers(tr_torrent const* tor, size_t* peer_count) { @@ -1284,158 +1800,7 @@ void tr_torrentAmountFinished(tr_torrent const* tor, float* tabs, int n_tabs) return tor->amountDoneBins(tabs, n_tabs); } -static void tr_torrentResetTransferStats(tr_torrent* tor) -{ - auto const lock = tor->unique_lock(); - - tor->downloadedPrev += tor->downloadedCur; - tor->downloadedCur = 0; - tor->uploadedPrev += tor->uploadedCur; - tor->uploadedCur = 0; - tor->corruptPrev += tor->corruptCur; - tor->corruptCur = 0; - - tor->setDirty(); -} - -/*** -**** -***/ - -#ifdef TR_ENABLE_ASSERTS -static bool queueIsSequenced(tr_session const* /*session*/); -#endif - -static void freeTorrent(tr_torrent* tor) -{ - auto const lock = tor->unique_lock(); - - TR_ASSERT(!tor->isRunning); - - tr_session* session = tor->session; - - tr_peerMgrRemoveTorrent(tor); - - session->announcer_->removeTorrent(tor); - - session->torrents().remove(tor, tr_time()); - - if (!session->isClosing()) - { - // "so you die, captain, and we all move up in rank." - // resequence the queue positions - for (auto* t : session->torrents()) - { - if (t->queuePosition > tor->queuePosition) - { - t->queuePosition--; - t->markChanged(); - } - } - - TR_ASSERT(queueIsSequenced(session)); - } - - delete tor; -} - -/** -*** Start/Stop Callback -**/ - -static void torrentSetQueued(tr_torrent* tor, bool queued); - -static void torrentStartImpl(tr_torrent* const tor) -{ - auto const lock = tor->unique_lock(); - - TR_ASSERT(tr_isTorrent(tor)); - - tor->recheckCompleteness(); - torrentSetQueued(tor, false); - - time_t const now = tr_time(); - - tor->isRunning = true; - tor->completeness = tor->completion.status(); - tor->startDate = now; - tor->markChanged(); - tr_torrentClearError(tor); - tor->finishedSeedingByIdle = false; - - tr_torrentResetTransferStats(tor); - tor->session->announcer_->startTorrent(tor); - tor->lpdAnnounceAt = now; - tr_peerMgrStartTorrent(tor); -} - -static bool torrentShouldQueue(tr_torrent const* const tor) -{ - tr_direction const dir = tor->queueDirection(); - - return tor->session->countQueueFreeSlots(dir) == 0; -} - -static void torrentStart(tr_torrent* tor, torrent_start_opts opts) -{ - switch (tor->activity()) - { - case TR_STATUS_SEED: - case TR_STATUS_DOWNLOAD: - return; /* already started */ - - case TR_STATUS_SEED_WAIT: - case TR_STATUS_DOWNLOAD_WAIT: - if (!opts.bypass_queue) - { - return; /* already queued */ - } - - break; - - case TR_STATUS_CHECK: - case TR_STATUS_CHECK_WAIT: - /* verifying right now... wait until that's done so - * we'll know what completeness to use/announce */ - tor->startAfterVerify = true; - return; - - case TR_STATUS_STOPPED: - if (!opts.bypass_queue && torrentShouldQueue(tor)) - { - torrentSetQueued(tor, true); - return; - } - - break; - } - - /* don't allow the torrent to be started if the files disappeared */ - if (setLocalErrorIfFilesDisappeared(tor, opts.has_local_data)) - { - return; - } - - /* otherwise, start it now... */ - auto const lock = tor->unique_lock(); - - /* allow finished torrents to be resumed */ - if (tr_torrentIsSeedRatioDone(tor)) - { - tr_logAddInfoTor(tor, _("Restarted manually -- disabling its seed ratio")); - tor->setRatioMode(TR_RATIOLIMIT_UNLIMITED); - } - - /* corresponds to the peer_id sent as a tracker request parameter. - * one tracker admin says: "When the same torrent is opened and - * closed and opened again without quitting Transmission ... - * change the peerid. It would help sometimes if a stopped event - * was missed to ensure that we didn't think someone was cheating. */ - tr_torrentUnsetPeerId(tor); - tor->isRunning = true; - tor->setDirty(); - tor->session->runInSessionThread(torrentStartImpl, tor); -} +// --- Start/Stop Callback void tr_torrentStart(tr_torrent* tor) { @@ -1456,7 +1821,13 @@ void tr_torrentStartNow(tr_torrent* tor) } } -static void onVerifyDoneThreadFunc(tr_torrent* const tor) +// --- + +namespace +{ +namespace verify_helpers +{ +void onVerifyDoneThreadFunc(tr_torrent* const tor) { TR_ASSERT(tor->session->amInSessionThread()); @@ -1477,17 +1848,7 @@ static void onVerifyDoneThreadFunc(tr_torrent* const tor) } } -void tr_torrentOnVerifyDone(tr_torrent* tor, bool aborted) -{ - if (aborted || tor->isDeleting) - { - return; - } - - tor->session->runInSessionThread(onVerifyDoneThreadFunc, tor); -} - -static void verifyTorrent(tr_torrent* const tor) +void verifyTorrent(tr_torrent* const tor) { TR_ASSERT(tor->session->amInSessionThread()); auto const lock = tor->unique_lock(); @@ -1517,12 +1878,39 @@ static void verifyTorrent(tr_torrent* const tor) tor->session->verifyAdd(tor); } } +} // namespace verify_helpers +} // namespace + +void tr_torrentOnVerifyDone(tr_torrent* tor, bool aborted) +{ + using namespace verify_helpers; + + if (aborted || tor->isDeleting) + { + return; + } + + tor->session->runInSessionThread(onVerifyDoneThreadFunc, tor); +} void tr_torrentVerify(tr_torrent* tor) { + using namespace verify_helpers; + tor->session->runInSessionThread(verifyTorrent, tor); } +void tr_torrent::setVerifyState(tr_verify_state state) +{ + TR_ASSERT(state == TR_VERIFY_NONE || state == TR_VERIFY_WAIT || state == TR_VERIFY_NOW); + + this->verify_state_ = state; + this->verify_progress_ = {}; + this->markChanged(); +} + +// --- + void tr_torrentSave(tr_torrent* tor) { TR_ASSERT(tr_isTorrent(tor)); @@ -1534,124 +1922,7 @@ void tr_torrentSave(tr_torrent* tor) } } -static void stopTorrent(tr_torrent* const tor) -{ - TR_ASSERT(tr_isTorrent(tor)); - TR_ASSERT(tor->session->amInSessionThread()); - auto const lock = tor->unique_lock(); - - if (!tor->session->isClosing()) - { - tr_logAddInfoTor(tor, _("Pausing torrent")); - } - - tor->session->verifyRemove(tor); - - tr_peerMgrStopTorrent(tor); - tor->session->announcer_->stopTorrent(tor); - - tor->session->closeTorrentFiles(tor); - - if (!tor->isDeleting) - { - tr_torrentSave(tor); - } - - torrentSetQueued(tor, false); - - if (tor->magnetVerify) - { - tor->magnetVerify = false; - tr_logAddTraceTor(tor, "Magnet Verify"); - tor->refreshCurrentDir(); - tr_torrentVerify(tor); - - callScriptIfEnabled(tor, TR_SCRIPT_ON_TORRENT_ADDED); - } -} - -void tr_torrentStop(tr_torrent* tor) -{ - if (!tr_isTorrent(tor)) - { - return; - } - - auto const lock = tor->unique_lock(); - - tor->isRunning = false; - tor->isStopping = false; - tor->setDirty(); - tor->session->runInSessionThread(stopTorrent, tor); -} - -void tr_torrentFreeInSessionThread(tr_torrent* tor) -{ - TR_ASSERT(tr_isTorrent(tor)); - TR_ASSERT(tor->session != nullptr); - TR_ASSERT(tor->session->amInSessionThread()); - - if (!tor->session->isClosing()) - { - tr_logAddInfoTor(tor, _("Removing torrent")); - } - - tor->magnetVerify = false; - stopTorrent(tor); - - if (tor->isDeleting) - { - tr_torrent_metainfo::removeFile(tor->session->torrentDir(), tor->name(), tor->infoHashString(), ".torrent"sv); - tr_torrent_metainfo::removeFile(tor->session->torrentDir(), tor->name(), tor->infoHashString(), ".magnet"sv); - tr_torrent_metainfo::removeFile(tor->session->resumeDir(), tor->name(), tor->infoHashString(), ".resume"sv); - } - - tor->isRunning = false; - freeTorrent(tor); -} - -static bool removeTorrentFile(char const* filename, void* /*user_data*/, tr_error** error) -{ - return tr_sys_path_remove(filename, error); -} - -static void removeTorrentInSessionThread(tr_torrent* tor, bool delete_flag, tr_fileFunc delete_func, void* user_data) -{ - auto const lock = tor->unique_lock(); - - if (delete_flag && tor->hasMetainfo()) - { - // ensure the files are all closed and idle before moving - tor->session->closeTorrentFiles(tor); - tor->session->verifyRemove(tor); - - if (delete_func == nullptr) - { - delete_func = removeTorrentFile; - } - - auto const delete_func_wrapper = [&delete_func, user_data](char const* filename) - { - delete_func(filename, user_data, nullptr); - }; - tor->metainfo_.files().remove(tor->currentDir(), tor->name(), delete_func_wrapper); - } - - tr_torrentFreeInSessionThread(tor); -} - -void tr_torrentRemove(tr_torrent* tor, bool delete_flag, tr_fileFunc delete_func, void* user_data) -{ - TR_ASSERT(tr_isTorrent(tor)); - - tor->isDeleting = true; - - tor->session->runInSessionThread(removeTorrentInSessionThread, tor, delete_flag, delete_func, user_data); -} - -/** -*** Completeness -**/ +// --- Completeness namespace { @@ -1748,9 +2019,7 @@ void tr_torrent::recheckCompleteness() } } -/** -*** File DND -**/ +// --- File DND void tr_torrentSetFileDLs(tr_torrent* tor, tr_file_index_t const* files, tr_file_index_t n_files, bool wanted) { @@ -1759,9 +2028,7 @@ void tr_torrentSetFileDLs(tr_torrent* tor, tr_file_index_t const* files, tr_file tor->setFilesWanted(files, n_files, wanted); } -/*** -**** -***/ +// --- void tr_torrent::setLabels(std::vector const& new_labels) { @@ -1779,9 +2046,7 @@ void tr_torrent::setLabels(std::vector const& new_labels) this->setDirty(); } -/*** -**** -***/ +// --- void tr_torrent::setBandwidthGroup(std::string_view group_name) noexcept { @@ -1803,9 +2068,7 @@ void tr_torrent::setBandwidthGroup(std::string_view group_name) noexcept this->setDirty(); } -/*** -**** -***/ +// --- tr_priority_t tr_torrentGetPriority(tr_torrent const* tor) { @@ -1827,9 +2090,7 @@ void tr_torrentSetPriority(tr_torrent* tor, tr_priority_t priority) } } -/*** -**** -***/ +// --- void tr_torrentSetPeerLimit(tr_torrent* tor, uint16_t max_connected_peers) { @@ -1850,9 +2111,7 @@ uint16_t tr_torrentGetPeerLimit(tr_torrent const* tor) return tor->peerLimit(); } -/*** -**** -***/ +// --- bool tr_torrentReqIsValid(tr_torrent const* tor, tr_piece_index_t index, uint32_t offset, uint32_t length) { @@ -1905,9 +2164,7 @@ tr_block_span_t tr_torGetFileBlockSpan(tr_torrent const* tor, tr_file_index_t fi return { begin_block, end_block }; } -/*** -**** -***/ +// --- // TODO: should be const after tr_ioTestPiece() is const bool tr_torrent::checkPiece(tr_piece_index_t piece) @@ -1917,9 +2174,7 @@ bool tr_torrent::checkPiece(tr_piece_index_t piece) return pass; } -/*** -**** -***/ +// --- bool tr_torrent::setTrackerList(std::string_view text) { @@ -1994,9 +2249,7 @@ size_t tr_torrentGetTrackerListToBuf(tr_torrent const* tor, char* buf, size_t bu return tr_strvToBuf(tr_torrentGetTrackerList(tor), buf, buflen); } -/** -*** -**/ +// --- uint64_t tr_torrentGetBytesLeftToAllocate(tr_torrent const* tor) { @@ -2026,96 +2279,6 @@ uint64_t tr_torrentGetBytesLeftToAllocate(tr_torrent const* tor) // --- -static void setLocationInSessionThread( - tr_torrent* tor, - std::string const& path, - bool move_from_old_path, - double volatile* setme_progress, - int volatile* setme_state) -{ - TR_ASSERT(tr_isTorrent(tor)); - TR_ASSERT(tor->session->amInSessionThread()); - - auto ok = bool{ true }; - if (move_from_old_path) - { - if (setme_state != nullptr) - { - *setme_state = TR_LOC_MOVING; - } - - // ensure the files are all closed and idle before moving - tor->session->closeTorrentFiles(tor); - tor->session->verifyRemove(tor); - - tr_error* error = nullptr; - ok = tor->metainfo_.files().move(tor->currentDir(), path, setme_progress, tor->name(), &error); - if (error != nullptr) - { - tor->setLocalError(fmt::format( - _("Couldn't move '{old_path}' to '{path}': {error} ({error_code})"), - fmt::arg("old_path", tor->currentDir()), - fmt::arg("path", path), - fmt::arg("error", error->message), - fmt::arg("error_code", error->code))); - tr_torrentStop(tor); - tr_error_clear(&error); - } - } - - // tell the torrent where the files are - if (ok) - { - tor->setDownloadDir(path); - - if (move_from_old_path) - { - tor->incomplete_dir.clear(); - tor->current_dir = tor->downloadDir(); - } - } - - if (setme_state != nullptr) - { - *setme_state = ok ? TR_LOC_DONE : TR_LOC_ERROR; - } -} - -void tr_torrent::setLocation( - std::string_view location, - bool move_from_old_path, - double volatile* setme_progress, - int volatile* setme_state) -{ - if (setme_state != nullptr) - { - *setme_state = TR_LOC_MOVING; - } - - this->session->runInSessionThread( - setLocationInSessionThread, - this, - std::string{ location }, - move_from_old_path, - setme_progress, - setme_state); -} - -void tr_torrentSetLocation( - tr_torrent* tor, - char const* location, - bool move_from_old_path, - double volatile* setme_progress, - int volatile* setme_state) -{ - TR_ASSERT(tr_isTorrent(tor)); - TR_ASSERT(!tr_str_is_empty(location)); - - tor->setLocation(location, move_from_old_path, setme_progress, setme_state); -} - -// --- - std::string_view tr_torrent::primaryMimeType() const { // count up how many bytes there are for each mime-type in the torrent @@ -2144,11 +2307,13 @@ std::string_view tr_torrent::primaryMimeType() const return it->first; } -/*** -**** -***/ +// --- -static void tr_torrentFileCompleted(tr_torrent* tor, tr_file_index_t i) +namespace +{ +namespace got_block_helpers +{ +void tr_torrentFileCompleted(tr_torrent* tor, tr_file_index_t i) { /* close the file so that we can reopen in read-only mode as needed */ tor->session->closeTorrentFile(tor, i); @@ -2184,7 +2349,7 @@ static void tr_torrentFileCompleted(tr_torrent* tor, tr_file_index_t i) } } -static void tr_torrentPieceCompleted(tr_torrent* tor, tr_piece_index_t piece_index) +void tr_torrentPieceCompleted(tr_torrent* tor, tr_piece_index_t piece_index) { tr_peerMgrPieceCompleted(tor, piece_index); @@ -2198,9 +2363,13 @@ static void tr_torrentPieceCompleted(tr_torrent* tor, tr_piece_index_t piece_ind } } } +} // namespace got_block_helpers +} // namespace void tr_torrentGotBlock(tr_torrent* tor, tr_block_index_t block) { + using namespace got_block_helpers; + TR_ASSERT(tr_isTorrent(tor)); TR_ASSERT(tor->session->amInSessionThread()); @@ -2237,9 +2406,7 @@ void tr_torrentGotBlock(tr_torrent* tor, tr_block_index_t block) } } -/*** -**** -***/ +// --- std::string tr_torrentFindFile(tr_torrent const* tor, tr_file_index_t file_num) { @@ -2277,137 +2444,6 @@ void tr_torrent::refreshCurrentDir() current_dir = dir; } -/*** -**** -***/ - -#ifdef TR_ENABLE_ASSERTS - -static bool queueIsSequenced(tr_session const* session) -{ - auto torrents = session->getAllTorrents(); - std::sort( - std::begin(torrents), - std::end(torrents), - [](auto const* a, auto const* b) { return a->queuePosition < b->queuePosition; }); - - /* test them */ - bool is_sequenced = true; - - for (size_t i = 0, n = std::size(torrents); is_sequenced && i < n; ++i) - { - is_sequenced = torrents[i]->queuePosition == i; - } - - return is_sequenced; -} - -#endif - -size_t tr_torrentGetQueuePosition(tr_torrent const* tor) -{ - return tor->queuePosition; -} - -void tr_torrentSetQueuePosition(tr_torrent* tor, size_t queue_position) -{ - size_t current = 0; - auto const old_pos = tor->queuePosition; - - tor->queuePosition = static_cast(-1); - - for (auto* const walk : tor->session->torrents()) - { - if ((old_pos < queue_position) && (old_pos <= walk->queuePosition) && (walk->queuePosition <= queue_position)) - { - walk->queuePosition--; - walk->markChanged(); - } - - if ((old_pos > queue_position) && (queue_position <= walk->queuePosition) && (walk->queuePosition < old_pos)) - { - walk->queuePosition++; - walk->markChanged(); - } - - if (current < walk->queuePosition + 1) - { - current = walk->queuePosition + 1; - } - } - - tor->queuePosition = std::min(queue_position, current); - tor->markChanged(); - - TR_ASSERT(queueIsSequenced(tor->session)); -} - -struct CompareTorrentByQueuePosition -{ - constexpr bool operator()(tr_torrent const* a, tr_torrent const* b) const noexcept - { - return a->queuePosition < b->queuePosition; - } -}; - -void tr_torrentsQueueMoveTop(tr_torrent* const* torrents_in, size_t torrent_count) -{ - auto torrents = std::vector(torrents_in, torrents_in + torrent_count); - std::sort(std::rbegin(torrents), std::rend(torrents), CompareTorrentByQueuePosition{}); - for (auto* tor : torrents) - { - tr_torrentSetQueuePosition(tor, 0); - } -} - -void tr_torrentsQueueMoveUp(tr_torrent* const* torrents_in, size_t torrent_count) -{ - auto torrents = std::vector(torrents_in, torrents_in + torrent_count); - std::sort(std::begin(torrents), std::end(torrents), CompareTorrentByQueuePosition{}); - for (auto* tor : torrents) - { - if (tor->queuePosition > 0) - { - tr_torrentSetQueuePosition(tor, tor->queuePosition - 1); - } - } -} - -void tr_torrentsQueueMoveDown(tr_torrent* const* torrents_in, size_t torrent_count) -{ - auto torrents = std::vector(torrents_in, torrents_in + torrent_count); - std::sort(std::rbegin(torrents), std::rend(torrents), CompareTorrentByQueuePosition{}); - for (auto* tor : torrents) - { - if (tor->queuePosition < UINT_MAX) - { - tr_torrentSetQueuePosition(tor, tor->queuePosition + 1); - } - } -} - -void tr_torrentsQueueMoveBottom(tr_torrent* const* torrents_in, size_t torrent_count) -{ - auto torrents = std::vector(torrents_in, torrents_in + torrent_count); - std::sort(std::begin(torrents), std::end(torrents), CompareTorrentByQueuePosition{}); - for (auto* tor : torrents) - { - tr_torrentSetQueuePosition(tor, UINT_MAX); - } -} - -static void torrentSetQueued(tr_torrent* tor, bool queued) -{ - TR_ASSERT(tr_isTorrent(tor)); - - if (tor->isQueued() != queued) - { - tor->is_queued = queued; - tor->markChanged(); - tor->setDirty(); - } -} - // --- RENAME namespace diff --git a/libtransmission/tr-utp.cc b/libtransmission/tr-utp.cc index dca91e9be..7414974d1 100644 --- a/libtransmission/tr-utp.cc +++ b/libtransmission/tr-utp.cc @@ -73,10 +73,12 @@ void tr_utpClose(tr_session* /*session*/) #else +namespace +{ /* Greg says 50ms works for them. */ -static auto constexpr UtpInterval = 50ms; +auto constexpr UtpInterval = 50ms; -static void utp_on_accept(tr_session* const session, UTPSocket* const utp_sock) +void utp_on_accept(tr_session* const session, UTPSocket* const utp_sock) { auto from_storage = sockaddr_storage{}; auto* const from = (struct sockaddr*)&from_storage; @@ -102,7 +104,7 @@ static void utp_on_accept(tr_session* const session, UTPSocket* const utp_sock) } } -static void utp_send_to( +void utp_send_to( tr_session const* const ss, uint8_t const* const buf, size_t const buflen, @@ -112,7 +114,7 @@ static void utp_send_to( ss->udp_core_->sendto(buf, buflen, to, tolen); } -static uint64 utp_callback(utp_callback_arguments* args) +uint64 utp_callback(utp_callback_arguments* args) { auto* const session = static_cast(utp_context_get_userdata(args->context)); @@ -139,7 +141,7 @@ static uint64 utp_callback(utp_callback_arguments* args) return 0; } -static void reset_timer(tr_session* session) +void reset_timer(tr_session* session) { auto interval = std::chrono::milliseconds{}; auto const random_percent = tr_rand_int(1000U) / 1000.0; @@ -167,7 +169,7 @@ static void reset_timer(tr_session* session) session->utp_timer->startSingleShot(interval); } -static void timer_callback(void* vsession) +void timer_callback(void* vsession) { auto* session = static_cast(vsession); @@ -177,6 +179,7 @@ static void timer_callback(void* vsession) utp_check_timeouts(session->utp_context); reset_timer(session); } +} // namespace void tr_utpInit(tr_session* session) {