refactor: add tr_announce_list (#2308)

* refactor: add tr_announce_list (#2308)
This commit is contained in:
Charles Kerr 2021-12-14 14:59:40 -06:00 committed by GitHub
parent 3e072f9bd4
commit 656df477f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1141 additions and 734 deletions

View File

@ -155,6 +155,8 @@
A257C1820CAD3003004E121C /* PeerTableView.mm in Sources */ = {isa = PBXBuildFile; fileRef = A257C1800CAD3003004E121C /* PeerTableView.mm */; };
A25892640CF1F7E800CCCDDF /* StatsWindowController.mm in Sources */ = {isa = PBXBuildFile; fileRef = A25892630CF1F7E800CCCDDF /* StatsWindowController.mm */; };
A259317E0A73B2CC002F4FE7 /* TransmissionHelp in Resources */ = {isa = PBXBuildFile; fileRef = A259316A0A73B2CC002F4FE7 /* TransmissionHelp */; };
66F977825E65AD498C028BB0 /* announce-list.cc in Sources */ = {isa = PBXBuildFile; fileRef = 66F977825E65AD498C028BB1 /* announcer-list.cc */; };
66F977825E65AD498C028BB2 /* announce-list.h in Headers */ = {isa = PBXBuildFile; fileRef = 66F977825E65AD498C028BB3 /* announce-list.h */; };
A25964A6106D73A800453B31 /* announcer.cc in Sources */ = {isa = PBXBuildFile; fileRef = A25964A4106D73A800453B31 /* announcer.cc */; };
A25964A7106D73A800453B31 /* announcer.h in Headers */ = {isa = PBXBuildFile; fileRef = A25964A5106D73A800453B31 /* announcer.h */; };
A25BFD69167BED3B0039D1AA /* variant-benc.cc in Sources */ = {isa = PBXBuildFile; fileRef = A25BFD63167BED3B0039D1AA /* variant-benc.cc */; };
@ -702,6 +704,8 @@
A25892620CF1F7E800CCCDDF /* StatsWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StatsWindowController.h; sourceTree = "<group>"; };
A25892630CF1F7E800CCCDDF /* StatsWindowController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = StatsWindowController.mm; sourceTree = "<group>"; };
A259316A0A73B2CC002F4FE7 /* TransmissionHelp */ = {isa = PBXFileReference; lastKnownFileType = folder; path = TransmissionHelp; sourceTree = "<group>"; };
66F977825E65AD498C028BB1 /* announce-list.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = announce-list.cc; sourceTree = "<group>"; };
66F977825E65AD498C028BB3 /* announce-list.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = announce-list.h; sourceTree = "<group>"; };
A25964A4106D73A800453B31 /* announcer.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = announcer.cc; sourceTree = "<group>"; };
A25964A5106D73A800453B31 /* announcer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = announcer.h; sourceTree = "<group>"; };
A25BFD63167BED3B0039D1AA /* variant-benc.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "variant-benc.cc"; sourceTree = "<group>"; };
@ -1447,6 +1451,8 @@
C10C644C1D9AF328003C1B4C /* session-id.h */,
A20152790D1C26EB0081714F /* torrent-ctor.cc */,
A23F299F132A447400E9A83B /* announcer-common.h */,
66F977825E65AD498C028BB1 /* announce-list.cc */,
66F977825E65AD498C028BB3 /* announce-list.h */,
A25964A4106D73A800453B31 /* announcer.cc */,
A25964A5106D73A800453B31 /* announcer.h */,
A23F29A0132A447400E9A83B /* announcer-http.cc */,
@ -1869,6 +1875,7 @@
0A6169A80FE5C9A200C66CE6 /* bitfield.h in Headers */,
1BB44E07B1B52E28291B4E33 /* file-piece-map.h in Headers */,
A25964A7106D73A800453B31 /* announcer.h in Headers */,
66F977825E65AD498C028BB2 /* announce-list.h in Headers */,
ED8A16412735A8AA000D61F9 /* peer-mgr-wishlist.h in Headers */,
4D8017EB10BBC073008A4AF2 /* torrent-magnet.h in Headers */,
4D80185A10BBC0B0008A4AF2 /* magnet-metainfo.h in Headers */,
@ -2465,6 +2472,7 @@
0A6169A70FE5C9A200C66CE6 /* bitfield.cc in Sources */,
1BB44E07B1B52E28291B4E32 /* file-piece-map.cc in Sources */,
A25964A6106D73A800453B31 /* announcer.cc in Sources */,
66F977825E65AD498C028BB0 /* announce-list.cc in Sources */,
4D8017EA10BBC073008A4AF2 /* torrent-magnet.cc in Sources */,
4D80185910BBC0B0008A4AF2 /* magnet-metainfo.cc in Sources */,
A220EC5B118C8A060022B4BE /* tr-lpd.cc in Sources */,

View File

@ -2219,9 +2219,10 @@ void DetailsDialog::Impl::on_edit_trackers_response(int response, std::shared_pt
auto const tracker_text = text_buffer->get_text(false);
std::istringstream tracker_strings(tracker_text);
std::vector<tr_tracker_info> trackers;
std::list<std::string> announce_urls;
int tier = 0;
auto announce_url_strings = std::vector<std::string>{};
auto announce_urls = std::vector<char const*>{};
auto tiers = std::vector<tr_tracker_tier_t>{};
auto tier = tr_tracker_tier_t{ 0 };
std::string str;
while (std::getline(tracker_strings, str))
@ -2232,13 +2233,17 @@ void DetailsDialog::Impl::on_edit_trackers_response(int response, std::shared_pt
}
else
{
announce_urls.push_front(str);
trackers.push_back(tr_tracker_info{ tier, announce_urls.front().data(), nullptr, 0 });
announce_url_strings.push_back(str);
tiers.push_back(tier);
}
}
/* update the torrent */
if (tr_torrentSetAnnounceList(tor, trackers.data(), trackers.size()))
std::transform(
std::begin(announce_url_strings),
std::end(announce_url_strings),
std::back_inserter(announce_urls),
[](auto const& url) { return url.c_str(); });
if (tr_torrentSetAnnounceList(tor, std::data(announce_urls), std::data(tiers), std::size(announce_urls)))
{
refresh();
}

View File

@ -6,6 +6,7 @@ configure_file(
)
set(PROJECT_FILES
announce-list.cc
announcer-http.cc
announcer-udp.cc
announcer.cc
@ -143,6 +144,7 @@ set(${PROJECT_NAME}_PUBLIC_HEADERS
)
set(${PROJECT_NAME}_PRIVATE_HEADERS
announce-list.h
announcer-common.h
announcer.h
bandwidth.h

View File

@ -0,0 +1,259 @@
/*
* This file Copyright (C) Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#include <algorithm>
#include <set>
#include <string>
#include <string_view>
#include "transmission.h"
#include "announce-list.h"
#include "metainfo.h"
#include "utils.h"
#include "variant.h"
size_t tr_announce_list::set(char const* const* announce_urls, tr_tracker_tier_t const* tiers, size_t n)
{
trackers_.clear();
for (size_t i = 0; i < n; ++i)
{
add(tiers[i], announce_urls[i]);
}
return size();
}
bool tr_announce_list::remove(std::string_view announce_url)
{
auto it = find(announce_url);
if (it == std::end(trackers_))
{
return false;
}
trackers_.erase(it);
return true;
}
bool tr_announce_list::remove(tr_tracker_id_t id)
{
auto it = find(id);
if (it == std::end(trackers_))
{
return false;
}
trackers_.erase(it);
return true;
}
bool tr_announce_list::replace(tr_tracker_id_t id, std::string_view announce_url_sv)
{
auto const announce = tr_urlParseTracker(announce_url_sv);
if (!announce || !canAdd(*announce))
{
return false;
}
auto it = find(id);
if (it == std::end(trackers_))
{
return false;
}
auto const tier = it->tier;
trackers_.erase(it);
return add(tier, announce_url_sv);
}
bool tr_announce_list::add(tr_tracker_tier_t tier, std::string_view announce_url_sv)
{
auto const announce = tr_urlParseTracker(announce_url_sv);
if (!announce || !canAdd(*announce))
{
return false;
}
auto tracker = tracker_info{};
tracker.announce_interned = tr_quark_new(announce_url_sv);
tracker.announce = *tr_urlParseTracker(tr_quark_get_string_view(tracker.announce_interned));
tracker.tier = getTier(tier, *announce);
tracker.id = nextUniqueId();
auto host = std::string{ tracker.announce.host };
host += ':';
host += tracker.announce.portstr;
tracker.host = tr_quark_new(host);
auto const scrape_str = announceToScrape(announce_url_sv);
if (scrape_str)
{
tracker.scrape_interned = tr_quark_new(*scrape_str);
tracker.scrape = *tr_urlParseTracker(tr_quark_get_string_view(tracker.scrape_interned));
}
auto const it = std::lower_bound(std::begin(trackers_), std::end(trackers_), tracker);
trackers_.insert(it, tracker);
return true;
}
std::optional<std::string> tr_announce_list::announceToScrape(std::string_view announce)
{
// To derive the scrape URL use the following steps:
// Begin with the announce URL. Find the last '/' in it.
// If the text immediately following that '/' isn't 'announce'
// it will be taken as a sign that that tracker doesn't support
// the scrape convention. If it does, substitute 'scrape' for
// 'announce' to find the scrape page.
auto constexpr oldval = std::string_view{ "/announce" };
if (auto pos = announce.rfind(oldval.front()); pos != std::string_view::npos && announce.find(oldval, pos) == pos)
{
auto const prefix = announce.substr(0, pos);
auto const suffix = announce.substr(pos + std::size(oldval));
return tr_strvJoin(prefix, std::string_view{ "/scrape" }, suffix);
}
// some torrents with UDP announce URLs don't have /announce
if (tr_strvStartsWith(announce, std::string_view{ "udp:" }))
{
return std::string{ announce };
}
return {};
}
tr_quark tr_announce_list::announceToScrape(tr_quark announce)
{
auto const scrape_str = announceToScrape(tr_quark_get_string_view(announce));
if (scrape_str)
{
return tr_quark_new(*scrape_str);
}
return TR_KEY_NONE;
}
std::set<tr_tracker_tier_t> tr_announce_list::tiers() const
{
auto tiers = std::set<tr_tracker_tier_t>{};
for (auto const& tracker : trackers_)
{
tiers.insert(tracker.tier);
}
return tiers;
}
tr_tracker_tier_t tr_announce_list::nextTier() const
{
return std::empty(trackers_) ? 0 : trackers_.back().tier + 1;
}
tr_tracker_id_t tr_announce_list::nextUniqueId()
{
static tr_tracker_id_t id = 0;
return id++;
}
tr_announce_list::trackers_t::iterator tr_announce_list::find(tr_tracker_id_t id)
{
auto const test = [&id](auto const& tracker)
{
return tracker.id == id;
};
return std::find_if(std::begin(trackers_), std::end(trackers_), test);
}
tr_announce_list::trackers_t::iterator tr_announce_list::find(std::string_view announce)
{
auto const test = [&announce](auto const& tracker)
{
return announce == tracker.announce.full;
};
return std::find_if(std::begin(trackers_), std::end(trackers_), test);
}
// if two announce URLs differ only by scheme, put them in the same tier.
// (note: this can leave gaps in the `tier' values, but since the calling
// function doesn't care, there's no point in removing the gaps...)
tr_tracker_tier_t tr_announce_list::getTier(tr_tracker_tier_t tier, tr_url_parsed_t const& announce) const
{
auto const is_sibling = [&announce](tracker_info const& tracker)
{
return tracker.announce.host == announce.host && tracker.announce.path == announce.path;
};
auto const it = std::find_if(std::begin(trackers_), std::end(trackers_), is_sibling);
return it != std::end(trackers_) ? it->tier : tier;
}
bool tr_announce_list::canAdd(tr_url_parsed_t const& announce)
{
// looking at components instead of the full original URL lets
// us weed out implicit-vs-explicit port duplicates e.g.
// "http://tracker/announce" + "http://tracker:80/announce"
auto const is_same = [&announce](auto const& tracker)
{
return tracker.announce.scheme == announce.scheme && tracker.announce.host == announce.host &&
tracker.announce.port == announce.port && tracker.announce.path == announce.path;
};
return std::none_of(std::begin(trackers_), std::end(trackers_), is_same);
}
bool tr_announce_list::save(char const* torrent_file, tr_error** error) const
{
// load the .torrent file
auto metainfo = tr_variant{};
if (!tr_variantFromFile(&metainfo, TR_VARIANT_PARSE_BENC, torrent_file, error))
{
return false;
}
// remove the old fields
tr_variantDictRemove(&metainfo, TR_KEY_announce);
tr_variantDictRemove(&metainfo, TR_KEY_announce_list);
// add the new fields
if (this->size() == 1)
{
tr_variantDictAddQuark(&metainfo, TR_KEY_announce, at(0).announce_interned);
}
else if (this->size() > 1)
{
tr_variant* tier_list = tr_variantDictAddList(&metainfo, TR_KEY_announce_list, 0);
auto current_tier = std::optional<tr_tracker_tier_t>{};
tr_variant* tracker_list = nullptr;
for (auto const& tracker : *this)
{
if (tracker_list == nullptr || !current_tier || current_tier != tracker.tier)
{
tracker_list = tr_variantListAddList(tier_list, 1);
current_tier = tracker.tier;
}
tr_variantListAddQuark(tracker_list, tracker.announce_interned);
}
}
// confirm that it's good by parsing it back again
if (!tr_metainfoParse(nullptr, &metainfo, error))
{
tr_variantFree(&metainfo);
return false;
}
// save it
auto contents_len = size_t{};
auto* const contents = tr_variantToStr(&metainfo, TR_VARIANT_FMT_BENC, &contents_len);
tr_variantFree(&metainfo);
auto const success = tr_saveFile(torrent_file, { contents, contents_len }, error);
tr_free(contents);
return success;
}

View File

@ -0,0 +1,123 @@
/*
* This file Copyright (C) Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#pragma once
#if 0 // TODO(ckerr): re-enable this after tr_info is made private
#ifndef __TRANSMISSION__
#error only libtransmission should #include this header.
#endif
#endif
#include <cstddef>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <vector>
#include "transmission.h"
#include "error.h"
#include "quark.h"
#include "tr-assert.h"
#include "utils.h"
#include "web-utils.h"
class tr_announce_list
{
public:
struct tracker_info
{
tr_quark host;
tr_url_parsed_t announce;
tr_url_parsed_t scrape;
tr_quark announce_interned = TR_KEY_NONE;
tr_quark scrape_interned = TR_KEY_NONE;
tr_tracker_tier_t tier = 0;
tr_tracker_id_t id = 0;
int compare(tracker_info const& that) const // <=>
{
if (this->tier != that.tier)
{
return this->tier < that.tier ? -1 : 1;
}
if (this->announce.full != that.announce.full)
{
return this->announce.full < that.announce.full ? -1 : 1;
}
return 0;
}
bool operator<(tracker_info const& that) const
{
return compare(that) < 0;
}
bool operator==(tracker_info const& that) const
{
return compare(that) == 0;
}
};
private:
using trackers_t = std::vector<tracker_info>;
public:
auto begin() const
{
return std::begin(trackers_);
}
auto end() const
{
return std::end(trackers_);
}
bool empty() const
{
return std::empty(trackers_);
}
size_t size() const
{
return std::size(trackers_);
}
tracker_info const& at(size_t i) const
{
return trackers_.at(i);
}
std::set<tr_tracker_tier_t> tiers() const;
tr_tracker_tier_t nextTier() const;
bool add(tr_tracker_tier_t tier, std::string_view announce_url_sv);
bool remove(std::string_view announce_url);
bool remove(tr_tracker_id_t id);
bool replace(tr_tracker_id_t id, std::string_view announce_url_sv);
size_t set(char const* const* announce_urls, tr_tracker_tier_t const* tiers, size_t n);
void clear()
{
return trackers_.clear();
}
bool save(char const* torrent_file, tr_error** error = nullptr) const;
static std::optional<std::string> announceToScrape(std::string_view announce);
static tr_quark announceToScrape(tr_quark announce);
private:
tr_tracker_tier_t getTier(tr_tracker_tier_t tier, tr_url_parsed_t const& announce) const;
bool canAdd(tr_url_parsed_t const& announce);
tr_tracker_id_t nextUniqueId();
trackers_t::iterator find(std::string_view announce);
trackers_t::iterator find(tr_tracker_id_t id);
trackers_t trackers_;
};

View File

@ -23,8 +23,10 @@
#define LIBTRANSMISSION_ANNOUNCER_MODULE
#include "transmission.h"
#include "announcer.h"
#include "announce-list.h"
#include "announcer-common.h"
#include "announcer.h"
#include "crypto-utils.h" /* tr_rand_int(), tr_rand_int_weak() */
#include "log.h"
#include "peer-mgr.h" /* tr_peerMgrCompactToPex() */
@ -249,13 +251,14 @@ tr_quark tr_announcerGetKey(tr_url_parsed_t const& parsed)
return tr_quark_new(buf);
}
static void trackerConstruct(tr_announcer* announcer, tr_tracker* tracker, tr_tracker_info const* inf)
static void trackerConstruct(tr_announcer* announcer, tr_tracker* tracker, tr_announce_list::tracker_info const& info)
{
memset(tracker, 0, sizeof(tr_tracker));
tracker->key = tr_announcerGetKey(inf->announce);
tracker->announce_url = tr_quark_new(tr_strvStrip(inf->announce));
tracker->scrape_info = inf->scrape == nullptr ? nullptr : tr_announcerGetScrapeInfo(announcer, tr_quark_new(inf->scrape));
tracker->id = inf->id;
tracker->key = info.host;
tracker->announce_url = info.announce_interned;
tracker->scrape_info = info.scrape_interned == TR_KEY_NONE ? nullptr :
tr_announcerGetScrapeInfo(announcer, info.scrape_interned);
tracker->id = info.id;
tracker->seederCount = -1;
tracker->leecherCount = -1;
tracker->downloadCount = -1;
@ -270,7 +273,7 @@ static void trackerDestruct(tr_tracker* tracker)
****
***/
struct tr_torrent_tiers;
struct tr_announcer_tiers;
/** @brief A group of trackers in a single tier, as per the multitracker spec */
struct tr_tier
@ -398,7 +401,7 @@ static void tierIncrementTracker(tr_tier* tier)
*
* this opaque data structure can be found in tr_torrent.tiers
*/
struct tr_torrent_tiers
struct tr_announcer_tiers
{
tr_tier* tiers;
int tier_count;
@ -410,12 +413,12 @@ struct tr_torrent_tiers
void* callbackData;
};
static tr_torrent_tiers* tiersNew(void)
static tr_announcer_tiers* tiersNew(void)
{
return tr_new0(tr_torrent_tiers, 1);
return tr_new0(tr_announcer_tiers, 1);
}
static void tiersDestruct(tr_torrent_tiers* tt)
static void tiersDestruct(tr_announcer_tiers* tt)
{
for (int i = 0; i < tt->tracker_count; ++i)
{
@ -432,7 +435,7 @@ static void tiersDestruct(tr_torrent_tiers* tt)
tr_free(tt->tiers);
}
static void tiersFree(tr_torrent_tiers* tt)
static void tiersFree(tr_announcer_tiers* tt)
{
tiersDestruct(tt);
tr_free(tt);
@ -447,9 +450,9 @@ static tr_tier* getTier(tr_announcer* announcer, tr_sha1_digest_t const& info_ha
tr_session* session = announcer->session;
tr_torrent* tor = tr_torrentFindFromHash(session, info_hash);
if (tor != nullptr && tor->tiers != nullptr)
if (tor != nullptr && tor->announcer_tiers != nullptr)
{
tr_torrent_tiers* tt = tor->tiers;
tr_announcer_tiers* tt = tor->announcer_tiers;
for (int i = 0; tier == nullptr && i < tt->tier_count; ++i)
{
@ -470,9 +473,10 @@ static tr_tier* getTier(tr_announcer* announcer, tr_sha1_digest_t const& info_ha
static void publishMessage(tr_tier* tier, char const* msg, TrackerEventType type)
{
if (tier != nullptr && tier->tor != nullptr && tier->tor->tiers != nullptr && tier->tor->tiers->callback != nullptr)
if (tier != nullptr && tier->tor != nullptr && tier->tor->announcer_tiers != nullptr &&
tier->tor->announcer_tiers->callback != nullptr)
{
tr_torrent_tiers* tiers = tier->tor->tiers;
tr_announcer_tiers* tiers = tier->tor->announcer_tiers;
auto event = tr_tracker_event{};
event.messageType = type;
event.text = msg;
@ -503,7 +507,7 @@ static void publishError(tr_tier* tier, char const* msg)
static void publishPeerCounts(tr_tier* tier, int seeders, int leechers)
{
if (tier->tor->tiers->callback != nullptr)
if (tier->tor->announcer_tiers->callback != nullptr)
{
auto e = tr_tracker_event{};
e.messageType = TR_TRACKER_COUNTS;
@ -511,13 +515,13 @@ static void publishPeerCounts(tr_tier* tier, int seeders, int leechers)
e.leechers = leechers;
dbgmsg(tier, "peer counts: %d seeders, %d leechers.", seeders, leechers);
(*tier->tor->tiers->callback)(tier->tor, &e, nullptr);
(*tier->tor->announcer_tiers->callback)(tier->tor, &e, nullptr);
}
}
static void publishPeersPex(tr_tier* tier, int seeders, int leechers, tr_pex const* pex, int n)
{
if (tier->tor->tiers->callback != nullptr)
if (tier->tor->announcer_tiers->callback != nullptr)
{
auto e = tr_tracker_event{};
e.messageType = TR_TRACKER_PEERS;
@ -527,7 +531,7 @@ static void publishPeersPex(tr_tier* tier, int seeders, int leechers, tr_pex con
e.pexCount = n;
dbgmsg(tier, "tracker knows of %d seeders and %d leechers and gave a list of %d peers.", seeders, leechers, n);
(*tier->tor->tiers->callback)(tier->tor, &e, nullptr);
(*tier->tor->announcer_tiers->callback)(tier->tor, &e, nullptr);
}
}
@ -535,152 +539,46 @@ static void publishPeersPex(tr_tier* tier, int seeders, int leechers, tr_pex con
****
***/
struct AnnTrackerInfo
static void addTorrentToTier(tr_announcer_tiers* tt, tr_torrent* tor)
{
AnnTrackerInfo(tr_tracker_info const& info_in, tr_url_parsed_t const& url_in)
: info{ info_in }
, url{ url_in }
{
}
auto const n = tor->trackerCount();
auto const tiers = tor->tiers();
tr_tracker_info info;
tr_url_parsed_t url;
/* primary key: tier
* secondary key: udp comes before http */
int compare(AnnTrackerInfo const& that) const // <=>
{
if (this->info.tier != that.info.tier)
{
return this->info.tier - that.info.tier;
}
return -this->url.scheme.compare(that.url.scheme);
}
bool operator<(AnnTrackerInfo const& that) const // less than
{
return this->compare(that) < 0;
}
};
/**
* Massages the incoming list of trackers into something we can use.
*/
static tr_tracker_info* filter_trackers(tr_tracker_info const* input, int input_count, int* setme_count)
{
auto tmp = std::vector<AnnTrackerInfo>{};
tmp.reserve(input_count);
// build a list of valid trackers
for (auto const *walk = input, *const end = walk + input_count; walk != end; ++walk)
{
auto const parsed = tr_urlParseTracker(walk->announce);
if (!parsed)
{
continue;
}
// weed out implicit-vs-explicit port duplicates e.g.
// "http://tracker/announce" + "http://tracker:80/announce"
auto const is_same = [&parsed](auto const& item)
{
return item.url.scheme == parsed->scheme && item.url.host == parsed->host && item.url.port == parsed->port &&
item.url.path == parsed->path;
};
if (std::any_of(std::begin(tmp), std::end(tmp), is_same))
{
continue;
}
tmp.emplace_back(*walk, *parsed);
}
// if two announce URLs differ only by scheme, put them in the same tier.
// (note: this can leave gaps in the `tier' values, but since the calling
// function doesn't care, there's no point in removing the gaps...)
for (size_t i = 0, n = std::size(tmp); i < n; ++i)
{
for (size_t j = i + 1; j < n; ++j)
{
auto const& a = tmp[i];
auto& b = tmp[j];
if ((a.info.tier != b.info.tier) && (a.url.port == b.url.port) && (a.url.host == b.url.host) &&
(a.url.path == b.url.path))
{
b.info.tier = a.info.tier;
}
}
}
// sort them, for two reasons:
// 1. unjumble the tiers from the previous step
// 2. move the UDP trackers to the front of each tier
std::sort(std::begin(tmp), std::end(tmp));
// build the output
auto const n = std::size(tmp);
*setme_count = n;
auto* const ret = tr_new0(tr_tracker_info, n);
std::transform(std::begin(tmp), std::end(tmp), ret, [](auto const& item) { return item.info; });
return ret;
}
static void addTorrentToTier(tr_torrent_tiers* tt, tr_torrent* tor)
{
auto n = int{};
tr_tracker_info* infos = filter_trackers(tor->info.trackers, tor->info.trackerCount, &n);
/* build the array of trackers */
// build the tracker and tier arrays
tt->trackers = tr_new0(tr_tracker, n);
tt->tracker_count = n;
tt->tiers = tr_new0(tr_tier, std::size(tiers));
for (int i = 0; i < n; ++i)
{
trackerConstruct(tor->session->announcer, &tt->trackers[i], &infos[i]);
}
/* count how many tiers there are */
auto tier_count = int{};
for (int i = 0; i < n; ++i)
{
if (i == 0 || infos[i].tier != infos[i - 1].tier)
{
++tier_count;
}
}
/* build the array of tiers */
auto prev_tier = std::optional<tr_tracker_tier_t>{};
tr_tier* tier = nullptr;
tt->tiers = tr_new0(tr_tier, tier_count);
tt->tier_count = 0;
for (int i = 0; i < n; ++i)
for (size_t i = 0; i < n; ++i)
{
if (i != 0 && infos[i].tier == infos[i - 1].tier)
{
++tier->tracker_count;
}
else
auto const info = tor->tracker(i);
trackerConstruct(tor->session->announcer, &tt->trackers[i], info);
if (!prev_tier || *prev_tier != info.tier)
{
tier = &tt->tiers[tt->tier_count++];
tierConstruct(tier, tor);
tier->trackers = &tt->trackers[i];
tier->tracker_count = 1;
tierIncrementTracker(tier);
prev_tier = info.tier;
}
else
{
++tier->tracker_count;
}
}
/* cleanup */
tr_free(infos);
}
tr_torrent_tiers* tr_announcerAddTorrent(tr_torrent* tor, tr_tracker_callback callback, void* callbackData)
tr_announcer_tiers* tr_announcerAddTorrent(tr_torrent* tor, tr_tracker_callback callback, void* callbackData)
{
TR_ASSERT(tr_isTorrent(tor));
tr_torrent_tiers* tiers = tiersNew();
tr_announcer_tiers* tiers = tiersNew();
tiers->callback = callback;
tiers->callbackData = callbackData;
@ -701,13 +599,13 @@ static bool tierCanManualAnnounce(tr_tier const* tier)
bool tr_announcerCanManualAnnounce(tr_torrent const* tor)
{
TR_ASSERT(tr_isTorrent(tor));
TR_ASSERT(tor->tiers != nullptr);
TR_ASSERT(tor->announcer_tiers != nullptr);
struct tr_torrent_tiers const* tt = nullptr;
struct tr_announcer_tiers const* tt = nullptr;
if (tor->isRunning)
{
tt = tor->tiers;
tt = tor->announcer_tiers;
}
/* return true if any tier can manual announce */
@ -725,7 +623,7 @@ bool tr_announcerCanManualAnnounce(tr_torrent const* tor)
time_t tr_announcerNextManualAnnounce(tr_torrent const* tor)
{
time_t ret = ~(time_t)0;
struct tr_torrent_tiers const* tt = tor->tiers;
struct tr_announcer_tiers const* tt = tor->announcer_tiers;
/* find the earliest manual announce time from all peers */
for (int i = 0; tt != nullptr && i < tt->tier_count; ++i)
@ -850,7 +748,7 @@ static tr_announce_event tier_announce_event_pull(tr_tier* tier)
static void torrentAddAnnounce(tr_torrent* tor, tr_announce_event e, time_t announceAt)
{
struct tr_torrent_tiers* tt = tor->tiers;
struct tr_announcer_tiers* tt = tor->announcer_tiers;
/* walk through each tier and tell them to announce */
for (int i = 0; i < tt->tier_count; ++i)
@ -893,7 +791,7 @@ void tr_announcerAddBytes(tr_torrent* tor, int type, uint32_t byteCount)
TR_ASSERT(tr_isTorrent(tor));
TR_ASSERT(type == TR_ANN_UP || type == TR_ANN_DOWN || type == TR_ANN_CORRUPT);
struct tr_torrent_tiers* tt = tor->tiers;
struct tr_announcer_tiers* tt = tor->announcer_tiers;
for (int i = 0; i < tt->tier_count; ++i)
{
@ -933,7 +831,7 @@ static void announce_request_free(tr_announce_request* req);
void tr_announcerRemoveTorrent(tr_announcer* announcer, tr_torrent* tor)
{
struct tr_torrent_tiers const* tt = tor->tiers;
struct tr_announcer_tiers const* tt = tor->announcer_tiers;
if (tt != nullptr)
{
@ -957,8 +855,8 @@ void tr_announcerRemoveTorrent(tr_announcer* announcer, tr_torrent* tor)
}
}
tiersFree(tor->tiers);
tor->tiers = nullptr;
tiersFree(tor->announcer_tiers);
tor->announcer_tiers = nullptr;
}
}
@ -1080,7 +978,7 @@ static void on_announce_done(tr_announce_response const* response, void* vdata)
Don't bother publishing if there are other trackers -- it's
all too common for people to load up dozens of dead trackers
in a torrent's metainfo... */
if (tier->tor->info.trackerCount < 2)
if (tier->tor->trackerCount() < 2)
{
publishError(tier, response->errmsg);
}
@ -1323,7 +1221,7 @@ static void on_scrape_error(tr_session const* session, tr_tier* tier, char const
static tr_tier* find_tier(tr_torrent* tor, tr_quark scrape_url)
{
struct tr_torrent_tiers* tt = tor->tiers;
struct tr_announcer_tiers* tt = tor->announcer_tiers;
for (int i = 0; tt != nullptr && i < tt->tier_count; ++i)
{
@ -1637,7 +1535,7 @@ static void scrapeAndAnnounceMore(tr_announcer* announcer)
auto scrape_me = std::vector<tr_tier*>{};
for (auto* tor : announcer->session->torrents)
{
struct tr_torrent_tiers* tt = tor->tiers;
struct tr_announcer_tiers* tt = tor->announcer_tiers;
for (int i = 0; tt != nullptr && i < tt->tier_count; ++i)
{
@ -1798,25 +1696,25 @@ static tr_tracker_view trackerView(tr_torrent const& tor, int tier_index, tr_tie
}
TR_ASSERT(0 <= view.tier);
TR_ASSERT(view.tier < tor.tiers->tier_count);
TR_ASSERT(view.tier < tor.announcer_tiers->tier_count);
return view;
}
size_t tr_announcerTrackerCount(tr_torrent const* tor)
{
TR_ASSERT(tr_isTorrent(tor));
TR_ASSERT(tor->tiers != nullptr);
TR_ASSERT(tor->announcer_tiers != nullptr);
return tor->tiers->tracker_count;
return tor->announcer_tiers->tracker_count;
}
tr_tracker_view tr_announcerTracker(tr_torrent const* tor, size_t nth)
{
TR_ASSERT(tr_isTorrent(tor));
TR_ASSERT(tor->tiers != nullptr);
TR_ASSERT(tor->announcer_tiers != nullptr);
// find the nth tracker
struct tr_torrent_tiers const* const tt = tor->tiers;
struct tr_announcer_tiers const* const tt = tor->announcer_tiers;
if (nth >= size_t(tt->tracker_count))
{
return {};
@ -1869,7 +1767,7 @@ static void copy_tier_attributes_impl(struct tr_tier* tgt, int trackerIndex, tr_
tgt->currentTracker->downloaderCount = src->currentTracker->downloaderCount;
}
static void copy_tier_attributes(struct tr_torrent_tiers* tt, tr_tier const* src)
static void copy_tier_attributes(struct tr_announcer_tiers* tt, tr_tier const* src)
{
bool found = false;
@ -1889,12 +1787,12 @@ static void copy_tier_attributes(struct tr_torrent_tiers* tt, tr_tier const* src
void tr_announcerResetTorrent(tr_announcer* /*announcer*/, tr_torrent* tor)
{
TR_ASSERT(tor->tiers != nullptr);
TR_ASSERT(tor->announcer_tiers != nullptr);
time_t const now = tr_time();
struct tr_torrent_tiers* tt = tor->tiers;
tr_torrent_tiers old = *tt;
tr_announcer_tiers* tt = tor->announcer_tiers;
tr_announcer_tiers old = *tt;
/* remove the old tiers / trackers */
tt->tiers = nullptr;

View File

@ -16,7 +16,7 @@
#include "quark.h"
struct tr_announcer;
struct tr_torrent_tiers;
struct tr_announcer_tiers;
/**
* *** Tracker Publish / Subscribe
@ -66,7 +66,7 @@ void tr_announcerClose(tr_session*);
*** For torrent customers
**/
struct tr_torrent_tiers* tr_announcerAddTorrent(tr_torrent* torrent, tr_tracker_callback cb, void* cbdata);
struct tr_announcer_tiers* tr_announcerAddTorrent(tr_torrent* torrent, tr_tracker_callback cb, void* cbdata);
void tr_announcerResetTorrent(struct tr_announcer*, tr_torrent*);

View File

@ -123,10 +123,10 @@ std::string tr_magnet_metainfo::magnet() const
tr_http_escape(s, name, true);
}
for (auto const& [tier, tracker] : trackers)
for (auto const& tracker : this->announce_list)
{
s += "&tr="sv;
tr_http_escape(s, tr_quark_get_string_view(tracker.announce_url), true);
tr_http_escape(s, tracker.announce.full, true);
}
for (auto const& webseed : webseed_urls)
@ -138,33 +138,6 @@ std::string tr_magnet_metainfo::magnet() const
return s;
}
static tr_quark announceToScrape(std::string_view announce)
{
auto buf = std::string{};
if (!tr_magnet_metainfo::convertAnnounceToScrape(buf, announce))
{
return TR_KEY_NONE;
}
return tr_quark_new(buf);
}
bool tr_magnet_metainfo::addTracker(tr_tracker_tier_t tier, std::string_view announce_sv)
{
announce_sv = tr_strvStrip(announce_sv);
if (!tr_urlIsValidTracker(announce_sv))
{
return false;
}
auto const announce_url = tr_quark_new(announce_sv);
auto const scrape_url = announceToScrape(announce_sv);
this->trackers.insert({ tier, { announce_url, scrape_url, tier } });
return true;
}
bool tr_magnet_metainfo::parseMagnet(std::string_view magnet_link, tr_error** error)
{
auto const parsed = tr_urlParse(magnet_link);
@ -175,7 +148,6 @@ bool tr_magnet_metainfo::parseMagnet(std::string_view magnet_link, tr_error** er
}
bool got_checksum = false;
auto tier = tr_tracker_tier_t{ 0 };
for (auto const& [key, value] : tr_url_query_view{ parsed->query })
{
if (key == "dn"sv)
@ -185,8 +157,7 @@ bool tr_magnet_metainfo::parseMagnet(std::string_view magnet_link, tr_error** er
else if (key == "tr"sv || tr_strvStartsWith(key, "tr."sv))
{
// "tr." explanation @ https://trac.transmissionbt.com/ticket/3341
addTracker(tier, tr_urlPercentDecode(value));
++tier;
this->announce_list.add(this->announce_list.nextTier(), tr_urlPercentDecode(value));
}
else if (key == "ws"sv)
{
@ -228,49 +199,31 @@ bool tr_magnet_metainfo::parseMagnet(std::string_view magnet_link, tr_error** er
return got_checksum;
}
bool tr_magnet_metainfo::convertAnnounceToScrape(std::string& out, std::string_view in)
{
// To derive the scrape URL use the following steps:
// Begin with the announce URL. Find the last '/' in it.
// If the text immediately following that '/' isn't 'announce'
// it will be taken as a sign that that tracker doesn't support
// the scrape convention. If it does, substitute 'scrape' for
// 'announce' to find the scrape page.
auto constexpr oldval = "/announce"sv;
if (auto pos = in.rfind(oldval.front()); pos != in.npos && in.find(oldval, pos) == pos)
{
auto const prefix = in.substr(0, pos);
auto const suffix = in.substr(pos + std::size(oldval));
tr_buildBuf(out, prefix, "/scrape"sv, suffix);
return true;
}
// some torrents with UDP announce URLs don't have /announce
if (tr_strvStartsWith(in, "udp:"sv))
{
out = in;
return true;
}
return false;
}
void tr_magnet_metainfo::toVariant(tr_variant* top) const
{
tr_variantInitDict(top, 4);
// announce list
auto n = std::size(this->trackers);
auto n = std::size(this->announce_list);
if (n == 1)
{
tr_variantDictAddStr(top, TR_KEY_announce, tr_quark_get_string_view(std::begin(this->trackers)->second.announce_url));
tr_variantDictAddQuark(top, TR_KEY_announce, this->announce_list.at(0).announce_interned);
}
else
{
auto* list = tr_variantDictAddList(top, TR_KEY_announce_list, n);
for (auto const& [tier, tracker] : this->trackers)
auto current_tier = tr_tracker_tier_t{};
tr_variant* tracker_list = nullptr;
auto* tier_list = tr_variantDictAddList(top, TR_KEY_announce_list, n);
for (auto const& tracker : this->announce_list)
{
tr_variantListAddStr(tr_variantListAddList(list, 1), tr_quark_get_string_view(tracker.announce_url));
if (tracker_list == nullptr || current_tier != tracker.tier)
{
tracker_list = tr_variantListAddList(tier_list, 1);
current_tier = tracker.tier;
}
tr_variantListAddQuark(tracker_list, tracker.announce_interned);
}
}

View File

@ -19,6 +19,7 @@
#include "transmission.h"
#include "announce-list.h"
#include "error.h"
#include "quark.h"
@ -32,43 +33,19 @@ struct tr_magnet_metainfo
void toVariant(tr_variant*) const;
static bool convertAnnounceToScrape(std::string& setme, std::string_view announce_url);
std::string_view infoHashString() const
{
// trim one byte off the end because of zero termination
return std::string_view{ std::data(info_hash_chars), std::size(info_hash_chars) - 1 };
}
struct tracker_t
{
tr_quark announce_url;
tr_quark scrape_url;
tr_tracker_tier_t tier;
tracker_t(tr_quark announce_in, tr_quark scrape_in, tr_tracker_tier_t tier_in)
: announce_url{ announce_in }
, scrape_url{ scrape_in }
, tier{ tier_in }
{
}
bool operator<(tracker_t const& that) const
{
return announce_url < that.announce_url;
}
};
tr_announce_list announce_list;
std::vector<std::string> webseed_urls;
std::string name;
std::multimap<tr_tracker_tier_t, tracker_t> trackers;
tr_sha1_digest_string_t info_hash_chars;
tr_sha1_digest_t info_hash;
protected:
bool addTracker(tr_tracker_tier_t tier, std::string_view announce_url);
};

View File

@ -26,6 +26,14 @@ enum tr_metainfo_builder_err
TR_MAKEMETA_IO_WRITE /* see builder.errfile, builder.my_errno */
};
struct tr_tracker_info
{
int tier;
char* announce;
char* scrape;
uint32_t id; /* unique identifier used to match to a tr_tracker_stat */
};
struct tr_metainfo_builder
{
/**

View File

@ -236,122 +236,44 @@ static char const* parseFiles(tr_info* inf, tr_variant* files, tr_variant const*
return errstr;
}
static char* tr_convertAnnounceToScrape(std::string_view url)
{
char* scrape = nullptr;
/* To derive the scrape URL use the following steps:
* Begin with the announce URL. Find the last '/' in it.
* If the text immediately following that '/' isn't 'announce'
* it will be taken as a sign that that tracker doesn't support
* the scrape convention. If it does, substitute 'scrape' for
* 'announce' to find the scrape page. */
auto constexpr oldval = "/announce"sv;
auto pos = url.rfind(oldval.front());
if (pos != url.npos && url.find(oldval, pos) == pos)
{
auto constexpr newval = "/scrape"sv;
auto const prefix = url.substr(0, pos);
auto const suffix = url.substr(pos + std::size(oldval));
auto const n = std::size(prefix) + std::size(newval) + std::size(suffix);
scrape = tr_new(char, n + 1);
auto* walk = scrape;
walk = std::copy(std::begin(prefix), std::end(prefix), walk);
walk = std::copy(std::begin(newval), std::end(newval), walk);
walk = std::copy(std::begin(suffix), std::end(suffix), walk);
*walk = '\0';
TR_ASSERT(scrape + n == walk);
}
// some torrents with UDP announce URLs don't have /announce
else if (url.find("udp:"sv) == 0)
{
scrape = tr_strvDup(url);
}
return scrape;
}
static char const* getannounce(tr_info* inf, tr_variant* meta)
{
tr_tracker_info* trackers = nullptr;
int trackerCount = 0;
inf->announce_list = std::make_shared<tr_announce_list>();
// tr_tracker_info* trackers = nullptr;
// int trackerCount = 0;
auto url = std::string_view{};
/* Announce-list */
tr_variant* tiers = nullptr;
if (tr_variantDictFindList(meta, TR_KEY_announce_list, &tiers))
{
int const numTiers = tr_variantListSize(tiers);
int n = 0;
for (int i = 0; i < numTiers; i++)
for (size_t i = 0, in = tr_variantListSize(tiers); i < in; ++i)
{
n += tr_variantListSize(tr_variantListChild(tiers, i));
}
trackers = tr_new0(tr_tracker_info, n);
int validTiers = 0;
for (int i = 0; i < numTiers; ++i)
{
tr_variant* tier = tr_variantListChild(tiers, i);
int const tierSize = tr_variantListSize(tier);
bool anyAdded = false;
for (int j = 0; j < tierSize; j++)
tr_variant* tier_list = tr_variantListChild(tiers, i);
if (tier_list == nullptr)
{
if (tr_variantGetStrView(tr_variantListChild(tier, j), &url))
continue;
}
for (size_t j = 0, jn = tr_variantListSize(tier_list); j < jn; ++j)
{
if (!tr_variantGetStrView(tr_variantListChild(tier_list, j), &url))
{
url = tr_strvStrip(url);
if (tr_urlIsValidTracker(url))
{
tr_tracker_info* t = trackers + trackerCount;
t->tier = validTiers;
t->announce = tr_strvDup(url);
t->scrape = tr_convertAnnounceToScrape(url);
t->id = trackerCount;
anyAdded = true;
++trackerCount;
}
continue;
}
}
if (anyAdded)
{
++validTiers;
inf->announce_list->add(i, url);
}
}
/* did we use any of the tiers? */
if (trackerCount == 0)
{
tr_free(trackers);
trackers = nullptr;
}
}
/* Regular announce value */
if (trackerCount == 0 && tr_variantDictFindStrView(meta, TR_KEY_announce, &url))
if (std::empty(*inf->announce_list) && tr_variantDictFindStrView(meta, TR_KEY_announce, &url))
{
url = tr_strvStrip(url);
if (tr_urlIsValidTracker(url))
{
trackers = tr_new0(tr_tracker_info, 1);
trackers[trackerCount].tier = 0;
trackers[trackerCount].announce = tr_strvDup(url);
trackers[trackerCount].scrape = tr_convertAnnounceToScrape(url);
trackers[trackerCount].id = 0;
trackerCount++;
}
inf->announce_list->add(0, url);
}
inf->trackers = trackers;
inf->trackerCount = trackerCount;
return nullptr;
}
@ -630,33 +552,39 @@ std::optional<tr_metainfo_parsed> tr_metainfoParse(tr_session const* session, tr
void tr_metainfoFree(tr_info* inf)
{
for (unsigned int i = 0; i < inf->webseedCount; i++)
if (inf->webseeds != nullptr)
{
tr_free(inf->webseeds[i]);
for (unsigned int i = 0; i < inf->webseedCount; i++)
{
tr_free(inf->webseeds[i]);
}
}
for (tr_file_index_t ff = 0; ff < inf->fileCount; ff++)
if (inf->files != nullptr)
{
tr_free(inf->files[ff].name);
for (tr_file_index_t ff = 0; ff < inf->fileCount; ff++)
{
tr_free(inf->files[ff].name);
}
}
tr_free(inf->webseeds);
tr_free(inf->files);
tr_free(inf->comment);
tr_free(inf->creator);
tr_free(inf->files);
tr_free(inf->name);
tr_free(inf->source);
tr_free(inf->torrent);
tr_free(inf->name);
tr_free(inf->webseeds);
for (unsigned int i = 0; i < inf->trackerCount; i++)
{
tr_free(inf->trackers[i].announce);
tr_free(inf->trackers[i].scrape);
}
inf->comment = nullptr;
inf->creator = nullptr;
inf->files = nullptr;
inf->name = nullptr;
inf->source = nullptr;
inf->torrent = nullptr;
inf->webseeds = nullptr;
tr_free(inf->trackers);
memset(inf, '\0', sizeof(tr_info));
inf->announce_list.reset();
}
void tr_metainfoRemoveSaved(tr_session const* session, tr_info const* inf)

View File

@ -413,16 +413,16 @@ static void addWebseeds(tr_info const* info, tr_variant* webseeds)
}
}
static void addTrackers(tr_info const* info, tr_variant* trackers)
static void addTrackers(tr_torrent const* tor, tr_variant* trackers)
{
for (unsigned int i = 0; i < info->trackerCount; ++i)
for (size_t i = 0, n = tor->trackerCount(); i < n; ++i)
{
tr_tracker_info const* t = &info->trackers[i];
auto const info = tor->tracker(i);
tr_variant* d = tr_variantListAddDict(trackers, 4);
tr_variantDictAddStrView(d, TR_KEY_announce, t->announce);
tr_variantDictAddInt(d, TR_KEY_id, t->id);
tr_variantDictAddStrView(d, TR_KEY_scrape, t->scrape != nullptr ? t->scrape : ""sv);
tr_variantDictAddInt(d, TR_KEY_tier, t->tier);
tr_variantDictAddQuark(d, TR_KEY_announce, info.announce_interned);
tr_variantDictAddInt(d, TR_KEY_id, info.id);
tr_variantDictAddQuark(d, TR_KEY_scrape, info.scrape_interned);
tr_variantDictAddInt(d, TR_KEY_tier, info.tier);
}
}
@ -781,8 +781,8 @@ static void initField(
break;
case TR_KEY_trackers:
tr_variantInitList(initme, inf->trackerCount);
addTrackers(inf, initme);
tr_variantInitList(initme, tor->trackerCount());
addTrackers(tor, initme);
break;
case TR_KEY_trackerStats:
@ -1056,175 +1056,79 @@ static char const* setFileDLs(tr_torrent* tor, bool wanted, tr_variant* list)
return errmsg;
}
static bool hasAnnounceUrl(tr_tracker_info const* t, int n, std::string_view url)
{
return std::any_of(t, t + n, [&url](auto const& row) { return row.announce == url; });
}
static int copyTrackers(tr_tracker_info* tgt, tr_tracker_info const* src, int n)
{
int maxTier = -1;
for (int i = 0; i < n; ++i)
{
tgt[i].tier = src[i].tier;
tgt[i].announce = tr_strdup(src[i].announce);
maxTier = std::max(maxTier, src[i].tier);
}
return maxTier;
}
static void freeTrackers(tr_tracker_info* trackers, int n)
{
for (int i = 0; i < n; ++i)
{
tr_free(trackers[i].announce);
}
tr_free(trackers);
}
static char const* addTrackerUrls(tr_torrent* tor, tr_variant* urls)
{
char const* errmsg = nullptr;
auto const old_size = tor->trackerCount();
/* make a working copy of the existing announce list */
auto const* const inf = tr_torrentInfo(tor);
int n = inf->trackerCount;
auto* const trackers = tr_new0(tr_tracker_info, n + tr_variantListSize(urls));
int tier = copyTrackers(trackers, inf->trackers, n);
/* and add the new ones */
auto i = int{ 0 };
auto changed = bool{ false };
tr_variant const* val = nullptr;
while ((val = tr_variantListChild(urls, i)) != nullptr)
for (size_t i = 0, n = tr_variantListSize(urls); i < n; ++i)
{
auto announce = std::string_view{};
if (tr_variantGetStrView(val, &announce) && tr_urlIsValidTracker(announce) && !hasAnnounceUrl(trackers, n, announce))
{
trackers[n].tier = ++tier; /* add a new tier */
trackers[n].announce = tr_strvDup(announce);
++n;
changed = true;
}
++i;
}
if (!changed)
{
errmsg = "invalid argument";
}
else if (!tr_torrentSetAnnounceList(tor, trackers, n))
{
errmsg = "error setting announce list";
}
freeTrackers(trackers, n);
return errmsg;
}
static char const* replaceTrackers(tr_torrent* tor, tr_variant* urls)
{
char const* errmsg = nullptr;
/* make a working copy of the existing announce list */
auto const* const inf = tr_torrentInfo(tor);
int const n = inf->trackerCount;
auto* const trackers = tr_new0(tr_tracker_info, n);
copyTrackers(trackers, inf->trackers, n);
/* make the substitutions... */
bool changed = false;
for (size_t i = 0, url_count = tr_variantListSize(urls); i + 1 < url_count; i += 2)
{
auto pos = int64_t{};
auto newval = std::string_view{};
if (tr_variantGetInt(tr_variantListChild(urls, i), &pos) &&
tr_variantGetStrView(tr_variantListChild(urls, i + 1), &newval) && tr_urlIsValidTracker(newval) && pos < n &&
pos >= 0)
{
tr_free(trackers[pos].announce);
trackers[pos].announce = tr_strvDup(newval);
changed = true;
}
}
if (!changed)
{
errmsg = "invalid argument";
}
else if (!tr_torrentSetAnnounceList(tor, trackers, n))
{
errmsg = "error setting announce list";
}
freeTrackers(trackers, n);
return errmsg;
}
static char const* removeTrackers(tr_torrent* tor, tr_variant* ids)
{
/* make a working copy of the existing announce list */
tr_info const* inf = tr_torrentInfo(tor);
int n = inf->trackerCount;
int* tids = tr_new0(int, n);
tr_tracker_info* trackers = tr_new0(tr_tracker_info, n);
copyTrackers(trackers, inf->trackers, n);
/* remove the ones specified in the urls list */
int i = 0;
int t = 0;
tr_variant const* val = nullptr;
while ((val = tr_variantListChild(ids, i)) != nullptr)
{
auto pos = int64_t{};
if (tr_variantGetInt(val, &pos) && 0 <= pos && pos < n)
{
tids[t++] = (int)pos;
}
++i;
}
/* sort trackerIds and remove from largest to smallest so there is no need to recalculate array indicies */
std::sort(tids, tids + t);
bool changed = false;
int dup = -1;
while (t-- != 0)
{
/* check for duplicates */
if (tids[t] == dup)
auto announce = std::string_view();
auto* const val = tr_variantListChild(urls, i);
if (val == nullptr || !tr_variantGetStrView(val, &announce))
{
continue;
}
tr_removeElementFromArray(trackers, tids[t], sizeof(tr_tracker_info), n);
--n;
dup = tids[t];
changed = true;
tor->info.announce_list->add(tor->info.announce_list->nextTier(), announce);
}
if (tor->trackerCount() == old_size)
{
return "error setting announce list";
}
tor->info.announce_list->save(tor->info.torrent);
return nullptr;
}
static char const* replaceTrackers(tr_torrent* tor, tr_variant* urls)
{
auto changed = bool{ false };
for (size_t i = 0, url_count = tr_variantListSize(urls); i + 1 < url_count; i += 2)
{
auto id = int64_t{};
auto newval = std::string_view{};
if (tr_variantGetInt(tr_variantListChild(urls, i), &id) &&
tr_variantGetStrView(tr_variantListChild(urls, i + 1), &newval))
{
changed |= tor->info.announce_list->replace(id, newval);
}
}
char const* errmsg = nullptr;
if (!changed)
{
errmsg = "invalid argument";
}
else if (!tr_torrentSetAnnounceList(tor, trackers, n))
{
errmsg = "error setting announce list";
return "error setting announce list";
}
freeTrackers(trackers, n);
tr_free(tids);
return errmsg;
tor->info.announce_list->save(tor->info.torrent);
return nullptr;
}
static char const* removeTrackers(tr_torrent* tor, tr_variant* ids)
{
auto const old_size = tor->trackerCount();
for (size_t i = 0, n = tr_variantListSize(ids); i < n; ++i)
{
auto id = int64_t{};
auto* const val = tr_variantListChild(ids, i);
if (val == nullptr || !tr_variantGetInt(val, &id))
{
continue;
}
tor->info.announce_list->remove(id);
}
if (tor->trackerCount() == old_size)
{
return "error setting announce list";
}
tor->info.announce_list->save(tor->info.torrent);
return nullptr;
}
static char const* torrentSet(

View File

@ -399,10 +399,10 @@ char* tr_torrentInfoGetMagnetLink(tr_info const* inf)
tr_http_escape(s, name, true);
}
for (unsigned int i = 0; i < inf->trackerCount; ++i)
for (size_t i = 0, n = std::size(*inf->announce_list); i < n; ++i)
{
evbuffer_add_printf(s, "%s", "&tr=");
tr_http_escape(s, inf->trackers[i].announce, true);
tr_http_escape(s, inf->announce_list->at(i).announce.full, true);
}
for (unsigned int i = 0; i < inf->webseedCount; i++)

View File

@ -764,7 +764,7 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
tr_error_clear(&error);
}
tor->tiers = tr_announcerAddTorrent(tor, onTrackerResponse, nullptr);
tor->announcer_tiers = tr_announcerAddTorrent(tor, onTrackerResponse, nullptr);
if (isNewTorrent)
{
@ -2138,114 +2138,40 @@ bool tr_torrent::checkPiece(tr_piece_index_t piece)
****
***/
static int compareTrackerByTier(void const* va, void const* vb)
{
auto const* const a = static_cast<tr_tracker_info const*>(va);
auto const* const b = static_cast<tr_tracker_info const*>(vb);
/* sort by tier */
if (a->tier != b->tier)
{
return a->tier - b->tier;
}
/* get the effects of a stable sort by comparing the two elements' addresses */
return a - b;
}
bool tr_torrentSetAnnounceList(tr_torrent* tor, tr_tracker_info const* trackers_in, int trackerCount)
bool tr_torrentSetAnnounceList(tr_torrent* tor, char const* const* announce_urls, tr_tracker_tier_t const* tiers, size_t n)
{
TR_ASSERT(tr_isTorrent(tor));
auto const lock = tor->unique_lock();
auto metainfo = tr_variant{};
auto ok = bool{ true };
/* ensure the trackers' tiers are in ascending order */
auto* trackers = static_cast<tr_tracker_info*>(tr_memdup(trackers_in, sizeof(tr_tracker_info) * trackerCount));
qsort(trackers, trackerCount, sizeof(tr_tracker_info), compareTrackerByTier);
/* look for bad URLs */
for (int i = 0; ok && i < trackerCount; ++i)
auto announce_list = tr_announce_list();
if (!announce_list.set(announce_urls, tiers, n) || !announce_list.save(tor->info.torrent))
{
if (!tr_urlIsValidTracker(trackers[i].announce))
return false;
}
std::swap(*tor->info.announce_list, announce_list);
tr_torrentMarkEdited(tor);
/* if we had a tracker-related error on this torrent,
* and that tracker's been removed,
* then clear the error */
if (tor->error == TR_STAT_TRACKER_WARNING || tor->error == TR_STAT_TRACKER_ERROR)
{
auto const error_url = tor->error_announce_url;
if (std::any_of(
std::begin(*tor->info.announce_list),
std::end(*tor->info.announce_list),
[error_url](auto const& tracker) { return tracker.announce_interned == error_url; }))
{
ok = false;
tr_torrentClearError(tor);
}
}
/* save to the .torrent file */
if (ok && tr_variantFromFile(&metainfo, TR_VARIANT_PARSE_BENC, tor->info.torrent, nullptr))
{
/* remove the old fields */
tr_variantDictRemove(&metainfo, TR_KEY_announce);
tr_variantDictRemove(&metainfo, TR_KEY_announce_list);
/* tell the announcer to reload this torrent's tracker list */
tr_announcerResetTorrent(tor->session->announcer, tor);
/* add the new fields */
if (trackerCount > 0)
{
tr_variantDictAddStr(&metainfo, TR_KEY_announce, trackers[0].announce);
}
if (trackerCount > 1)
{
int prevTier = -1;
tr_variant* tier = nullptr;
tr_variant* announceList = tr_variantDictAddList(&metainfo, TR_KEY_announce_list, 0);
for (int i = 0; i < trackerCount; ++i)
{
if (prevTier != trackers[i].tier)
{
prevTier = trackers[i].tier;
tier = tr_variantListAddList(announceList, 0);
}
tr_variantListAddStr(tier, trackers[i].announce);
}
}
/* try to parse it back again, to make sure it's good */
auto parsed = tr_metainfoParse(tor->session, &metainfo, nullptr);
if (parsed)
{
/* it's good, so keep these new trackers and free the old ones */
std::swap(tor->info.trackers, parsed->info.trackers);
std::swap(tor->info.trackerCount, parsed->info.trackerCount);
tr_torrentMarkEdited(tor);
tr_variantToFile(&metainfo, TR_VARIANT_FMT_BENC, tor->info.torrent);
}
/* cleanup */
tr_variantFree(&metainfo);
/* if we had a tracker-related error on this torrent,
* and that tracker's been removed,
* then clear the error */
if (tor->error == TR_STAT_TRACKER_WARNING || tor->error == TR_STAT_TRACKER_ERROR)
{
bool clear = true;
for (int i = 0; clear && i < trackerCount; ++i)
{
if (strcmp(trackers[i].announce, tr_quark_get_string(tor->error_announce_url)) == 0)
{
clear = false;
}
}
if (clear)
{
tr_torrentClearError(tor);
}
}
/* tell the announcer to reload this torrent's tracker list */
tr_announcerResetTorrent(tor->session->announcer, tor);
}
tr_free(trackers);
return ok;
return true;
}
/**

View File

@ -20,6 +20,7 @@
#include "transmission.h"
#include "announce-list.h"
#include "bandwidth.h"
#include "bitfield.h"
#include "block-info.h"
@ -36,7 +37,7 @@ struct tr_magnet_info;
struct tr_metainfo_parsed;
struct tr_session;
struct tr_torrent;
struct tr_torrent_tiers;
struct tr_announcer_tiers;
/**
*** Package-visible ctor API
@ -327,6 +328,23 @@ public:
std::optional<tr_found_file_t> findFile(std::string& filename, tr_file_index_t i) const;
/// TRACKERS
size_t trackerCount() const
{
return std::size(*info.announce_list);
}
auto const& tracker(size_t i) const
{
return info.announce_list->at(i);
}
auto tiers() const
{
return info.announce_list->tiers();
}
/// WEBSEEDS
auto webseedCount() const
@ -398,7 +416,7 @@ public:
tr_session* session = nullptr;
struct tr_torrent_tiers* tiers = nullptr;
tr_announcer_tiers* announcer_tiers = nullptr;
// Changed to non-owning pointer temporarily till tr_torrent becomes C++-constructible and destructible
// TODO: change tr_bandwidth* to owning pointer to the bandwidth, or remove * and own the value

View File

@ -20,6 +20,7 @@
****
***/
#include <memory>
#include <stdbool.h> /* bool */
#include <stddef.h> /* size_t */
#include <stdint.h> /* uintN_t */
@ -34,6 +35,9 @@ using tr_piece_index_t = uint32_t;
using tr_block_index_t = uint32_t;
using tr_port = uint16_t;
using tr_tracker_tier_t = uint32_t;
using tr_tracker_id_t = uint32_t;
#include "announce-list.h"
struct tr_block_span_t
{
@ -1215,15 +1219,6 @@ static inline char* tr_torrentGetMagnetLink(tr_torrent const* tor)
***
**/
/** @brief a part of tr_info that represents a single tracker */
struct tr_tracker_info
{
int tier;
char* announce;
char* scrape;
uint32_t id; /* unique identifier used to match to a tr_tracker_stat */
};
/**
* @brief Modify a torrent's tracker list.
*
@ -1231,13 +1226,11 @@ struct tr_tracker_info
* and the metainfo file in tr_sessionGetConfigDir()'s torrent subdirectory.
*
* @param torrent The torrent whose tracker list is to be modified
* @param trackers An array of trackers, sorted by tier from first to last.
* NOTE: only the `tier' and `announce' fields are used.
* libtransmission derives `scrape' from `announce'
* and reassigns 'id'.
* @param trackerCount size of the `trackers' array
* @param urls Array of n announce url strings
* @param tiers Array of n tier numbers for grouping 'urls' into tiers
* @param n the number of urls/tiers
*/
bool tr_torrentSetAnnounceList(tr_torrent* torrent, tr_tracker_info const* trackers, int trackerCount);
bool tr_torrentSetAnnounceList(tr_torrent* torrent, char const* const* announce_urls, tr_tracker_tier_t const* tiers, size_t n);
/**
***
@ -1522,6 +1515,7 @@ struct tr_file_priv
time_t mtime;
bool is_renamed; // true if we're using a different path from the one in the metainfo; ie, if the user has renamed it */
};
/** @brief a part of tr_info that represents a single file of the torrent's content */
struct tr_file
{
@ -1557,13 +1551,12 @@ struct tr_info
// Use tr_torrentFile() and tr_torrentFileCount() instead.
tr_file* files;
/* these trackers are sorted by tier */
tr_tracker_info* trackers;
// TODO(ckerr) aggregate this directly, rather than using a shared_ptr, when tr_info is private
std::shared_ptr<tr_announce_list> announce_list;
/* Torrent info */
time_t dateCreated;
unsigned int trackerCount;
unsigned int webseedCount;
tr_file_index_t fileCount;
uint32_t pieceSize;

View File

@ -119,7 +119,6 @@ enum tr_variant_parse_opts
TR_VARIANT_PARSE_INPLACE = (1 << 2)
};
/* TR_VARIANT_FMT_JSON_LEAN and TR_VARIANT_FMT_JSON are equivalent here. */
bool tr_variantFromFile(tr_variant* setme, tr_variant_parse_opts opts, char const* filename, struct tr_error** error = nullptr);
bool tr_variantFromBuf(

View File

@ -156,21 +156,22 @@ OSStatus GeneratePreviewForURL(void* thisInterface, QLPreviewRequestRef preview,
[lists addObject:listSection];
}
if (inf.trackerCount > 0)
if (!std::empty(*inf.announce_list))
{
NSMutableString* listSection = [NSMutableString string];
[listSection appendString:@"<table>"];
NSString* headerTitleString = inf.trackerCount == 1 ?
auto const n = std::size(*inf.announce_list);
NSString* headerTitleString = n == 1 ?
NSLocalizedStringFromTableInBundle(@"1 Tracker", nil, bundle, "quicklook tracker header") :
[NSString stringWithFormat:NSLocalizedStringFromTableInBundle(@"%@ Trackers", nil, bundle, "quicklook tracker header"),
[NSString formattedUInteger:inf.trackerCount]];
[NSString formattedUInteger:n]];
[listSection appendFormat:@"<tr><th>%@</th></tr>", headerTitleString];
#warning handle tiers?
for (int i = 0; i < inf.trackerCount; ++i)
for (auto const tracker : *inf.announce_list)
{
[listSection appendFormat:@"<tr><td>%s<td></tr>", inf.trackers[i].announce];
[listSection appendFormat:@"<tr><td>%s<td></tr>", tr_quark_get_string(tracker.announce_interned)];
}
[listSection appendString:@"</table>"];

View File

@ -763,61 +763,63 @@ bool trashDataFile(char const* filename, tr_error** error)
- (NSArray*)allTrackersFlat
{
NSMutableArray* allTrackers = [NSMutableArray arrayWithCapacity:fInfo->trackerCount];
auto const n = tr_torrentTrackerCount(fHandle);
NSMutableArray* allTrackers = [NSMutableArray arrayWithCapacity:n];
for (NSInteger i = 0; i < fInfo->trackerCount; i++)
for (size_t i = 0; i < n; ++i)
{
[allTrackers addObject:@(fInfo->trackers[i].announce)];
[allTrackers addObject:@(tr_torrentTracker(fHandle, i).announce)];
}
return allTrackers;
}
- (BOOL)addTrackerToNewTier:(NSString*)tracker
- (BOOL)addTrackerToNewTier:(NSString*)new_tracker
{
tracker = [tracker stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
if ([tracker rangeOfString:@"://"].location == NSNotFound)
new_tracker = [new_tracker stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
if ([new_tracker rangeOfString:@"://"].location == NSNotFound)
{
tracker = [@"http://" stringByAppendingString:tracker];
new_tracker = [@"http://" stringByAppendingString:new_tracker];
}
//recreate the tracker structure
int const oldTrackerCount = fInfo->trackerCount;
tr_tracker_info* trackerStructs = tr_new(tr_tracker_info, oldTrackerCount + 1);
for (int i = 0; i < oldTrackerCount; ++i)
auto urls = std::vector<char const*>{};
auto tiers = std::vector<tr_tracker_tier_t>{};
for (size_t i = 0, n = tr_torrentTrackerCount(fHandle); i < n; ++i)
{
trackerStructs[i] = fInfo->trackers[i];
auto const tracker = tr_torrentTracker(fHandle, i);
urls.push_back(tracker.announce);
tiers.push_back(tracker.tier);
}
trackerStructs[oldTrackerCount].announce = (char*)tracker.UTF8String;
trackerStructs[oldTrackerCount].tier = trackerStructs[oldTrackerCount - 1].tier + 1;
trackerStructs[oldTrackerCount].id = oldTrackerCount;
urls.push_back(new_tracker.UTF8String);
tiers.push_back(std::empty(tiers) ? 0 : tiers.back() + 1);
BOOL const success = tr_torrentSetAnnounceList(fHandle, trackerStructs, oldTrackerCount + 1);
tr_free(trackerStructs);
BOOL const success = tr_torrentSetAnnounceList(fHandle, std::data(urls), std::data(tiers), std::size(urls));
return success;
}
- (void)removeTrackers:(NSSet*)trackers
{
//recreate the tracker structure
tr_tracker_info* trackerStructs = tr_new(tr_tracker_info, fInfo->trackerCount);
auto urls = std::vector<char const*>{};
auto tiers = std::vector<tr_tracker_tier_t>{};
NSUInteger newCount = 0;
for (NSUInteger i = 0; i < fInfo->trackerCount; i++)
for (size_t i = 0, n = tr_torrentTrackerCount(fHandle); i < n; ++i)
{
if (![trackers containsObject:@(fInfo->trackers[i].announce)])
auto const tracker = tr_torrentTracker(fHandle, i);
if ([trackers containsObject:@(tracker.announce)])
{
trackerStructs[newCount++] = fInfo->trackers[i];
continue;
}
urls.push_back(tracker.announce);
tiers.push_back(tracker.tier);
}
BOOL const success = tr_torrentSetAnnounceList(fHandle, trackerStructs, newCount);
BOOL const success = tr_torrentSetAnnounceList(fHandle, std::data(urls), std::data(tiers), std::size(urls));
NSAssert(success, @"Removing tracker addresses failed");
tr_free(trackerStructs);
}
- (NSString*)comment

View File

@ -1,4 +1,5 @@
add_executable(libtransmission-test
announce-list-test.cc
bitfield-test.cc
block-info-test.cc
blocklist-test.cc

View File

@ -0,0 +1,404 @@
/*
* This file Copyright (C) Mnemosyne LLC
*
* It may be used under the GNU GPL versions 2 or 3
* or any future license endorsed by Mnemosyne LLC.
*
*/
#include <array>
#include <cstdlib>
#include <set>
#include <string_view>
#include <vector>
#include "transmission.h"
#include "announce-list.h"
#include "metainfo.h"
#include "utils.h"
#include "variant.h"
#include "gtest/gtest.h"
using AnnounceListTest = ::testing::Test;
using namespace std::literals;
TEST_F(AnnounceListTest, canAdd)
{
auto constexpr Tier = tr_tracker_tier_t{ 2 };
auto constexpr Announce = "https://example.org/announce"sv;
auto announce_list = tr_announce_list{};
EXPECT_EQ(1, announce_list.add(Tier, Announce));
auto const tracker = announce_list.at(0);
EXPECT_EQ(Announce, tracker.announce.full);
EXPECT_EQ("https://example.org/scrape"sv, tracker.scrape.full);
EXPECT_EQ(Tier, tracker.tier);
EXPECT_EQ("example.org:443"sv, tr_quark_get_string_view(tracker.host));
}
TEST_F(AnnounceListTest, groupsSiblingsIntoSameTier)
{
auto constexpr Tier1 = tr_tracker_tier_t{ 1 };
auto constexpr Tier2 = tr_tracker_tier_t{ 2 };
auto constexpr Tier3 = tr_tracker_tier_t{ 3 };
auto constexpr Announce1 = "https://example.org/announce"sv;
auto constexpr Announce2 = "http://example.org/announce"sv;
auto constexpr Announce3 = "udp://example.org:999/announce"sv;
auto announce_list = tr_announce_list{};
EXPECT_TRUE(announce_list.add(Tier1, Announce1));
EXPECT_TRUE(announce_list.add(Tier2, Announce2));
EXPECT_TRUE(announce_list.add(Tier3, Announce3));
EXPECT_EQ(3, std::size(announce_list));
EXPECT_EQ(Tier1, announce_list.at(0).tier);
EXPECT_EQ(Tier1, announce_list.at(1).tier);
EXPECT_EQ(Tier1, announce_list.at(2).tier);
EXPECT_EQ(Announce1, announce_list.at(1).announce.full);
EXPECT_EQ(Announce2, announce_list.at(0).announce.full);
EXPECT_EQ(Announce3, announce_list.at(2).announce.full);
EXPECT_EQ("example.org:443"sv, tr_quark_get_string_view(announce_list.at(1).host));
EXPECT_EQ("example.org:80"sv, tr_quark_get_string_view(announce_list.at(0).host));
EXPECT_EQ("example.org:999"sv, tr_quark_get_string_view(announce_list.at(2).host));
}
TEST_F(AnnounceListTest, canAddWithoutScrape)
{
auto constexpr Tier = tr_tracker_tier_t{ 2 };
auto constexpr Announce = "https://example.org/foo"sv;
auto announce_list = tr_announce_list{};
EXPECT_TRUE(announce_list.add(Tier, Announce));
auto const tracker = announce_list.at(0);
EXPECT_EQ(Announce, tracker.announce.full);
EXPECT_EQ(TR_KEY_NONE, tracker.scrape_interned);
EXPECT_EQ(Tier, tracker.tier);
}
TEST_F(AnnounceListTest, canAddUdp)
{
auto constexpr Tier = tr_tracker_tier_t{ 2 };
auto constexpr Announce = "udp://example.org/"sv;
auto announce_list = tr_announce_list{};
EXPECT_TRUE(announce_list.add(Tier, Announce));
auto const tracker = announce_list.at(0);
EXPECT_EQ(Announce, tracker.announce.full);
EXPECT_EQ("udp://example.org/"sv, tracker.scrape.full);
EXPECT_EQ(Tier, tracker.tier);
}
TEST_F(AnnounceListTest, canNotAddDuplicateAnnounce)
{
auto constexpr Tier = tr_tracker_tier_t{ 2 };
auto constexpr Announce = "https://example.org/announce"sv;
auto announce_list = tr_announce_list{};
EXPECT_TRUE(announce_list.add(Tier, Announce));
EXPECT_EQ(1, announce_list.size());
EXPECT_FALSE(announce_list.add(Tier, Announce));
EXPECT_EQ(1, announce_list.size());
auto constexpr Announce2 = "https://example.org:443/announce"sv;
EXPECT_FALSE(announce_list.add(Tier, Announce2));
EXPECT_EQ(1, announce_list.size());
}
TEST_F(AnnounceListTest, canNotAddInvalidUrl)
{
auto constexpr Tier = tr_tracker_tier_t{ 2 };
auto constexpr Announce = "telnet://example.org/announce"sv;
auto announce_list = tr_announce_list{};
EXPECT_FALSE(announce_list.add(Tier, Announce));
EXPECT_EQ(0, announce_list.size());
}
TEST_F(AnnounceListTest, canSet)
{
auto constexpr Urls = std::array<char const*, 3>{
"https://www.example.com/a/announce",
"https://www.example.com/b/announce",
"https://www.example.com/c/announce",
};
auto constexpr Tiers = std::array<tr_tracker_tier_t, 3>{ 1, 2, 3 };
auto announce_list = tr_announce_list{};
EXPECT_EQ(3, announce_list.set(std::data(Urls), std::data(Tiers), 3));
EXPECT_EQ(3, announce_list.size());
EXPECT_EQ(Tiers[0], announce_list.at(0).tier);
EXPECT_EQ(Tiers[1], announce_list.at(1).tier);
EXPECT_EQ(Tiers[2], announce_list.at(2).tier);
EXPECT_EQ(Urls[0], announce_list.at(0).announce.full);
EXPECT_EQ(Urls[1], announce_list.at(1).announce.full);
EXPECT_EQ(Urls[2], announce_list.at(2).announce.full);
auto const expected_tiers = std::set<tr_tracker_tier_t>(std::begin(Tiers), std::end(Tiers));
auto const tiers = announce_list.tiers();
EXPECT_EQ(expected_tiers, tiers);
}
TEST_F(AnnounceListTest, canSetUnsortedWithBackupsInTiers)
{
auto constexpr Urls = std::array<char const*, 6>{
"https://www.backup-a.com/announce", "https://www.backup-b.com/announce", "https://www.backup-c.com/announce",
"https://www.primary-a.com/announce", "https://www.primary-b.com/announce", "https://www.primary-c.com/announce",
};
auto constexpr Tiers = std::array<tr_tracker_tier_t, 6>{ 0, 1, 2, 0, 1, 2 };
auto announce_list = tr_announce_list{};
EXPECT_EQ(6, announce_list.set(std::data(Urls), std::data(Tiers), 6));
EXPECT_EQ(6, announce_list.size());
EXPECT_EQ(0, announce_list.at(0).tier);
EXPECT_EQ(0, announce_list.at(1).tier);
EXPECT_EQ(1, announce_list.at(2).tier);
EXPECT_EQ(1, announce_list.at(3).tier);
EXPECT_EQ(2, announce_list.at(4).tier);
EXPECT_EQ(2, announce_list.at(5).tier);
EXPECT_EQ(Urls[0], announce_list.at(0).announce.full);
EXPECT_EQ(Urls[3], announce_list.at(1).announce.full);
EXPECT_EQ(Urls[1], announce_list.at(2).announce.full);
EXPECT_EQ(Urls[4], announce_list.at(3).announce.full);
EXPECT_EQ(Urls[2], announce_list.at(4).announce.full);
EXPECT_EQ(Urls[5], announce_list.at(5).announce.full);
// confirm that each has a unique id
auto ids = std::set<tr_tracker_id_t>{};
for (size_t i = 0, n = std::size(announce_list); i < n; ++i)
{
ids.insert(announce_list.at(i).id);
}
EXPECT_EQ(std::size(announce_list), std::size(ids));
}
TEST_F(AnnounceListTest, canSetExceptDuplicate)
{
auto constexpr Urls = std::array<char const*, 3>{
"https://www.example.com/a/announce",
"https://www.example.com/b/announce",
"https://www.example.com/b/announce",
};
auto constexpr Tiers = std::array<tr_tracker_tier_t, 3>{ 3, 2, 1 };
auto announce_list = tr_announce_list{};
EXPECT_EQ(2, announce_list.set(std::data(Urls), std::data(Tiers), 3));
EXPECT_EQ(2, announce_list.size());
EXPECT_EQ(Tiers[0], announce_list.at(1).tier);
EXPECT_EQ(Tiers[1], announce_list.at(0).tier);
EXPECT_EQ(Urls[0], announce_list.at(1).announce.full);
EXPECT_EQ(Urls[1], announce_list.at(0).announce.full);
}
TEST_F(AnnounceListTest, canSetExceptInvalid)
{
auto constexpr Urls = std::array<char const*, 3>{
"https://www.example.com/a/announce",
"telnet://www.example.com/b/announce",
"https://www.example.com/c/announce",
};
auto constexpr Tiers = std::array<tr_tracker_tier_t, 3>{ 1, 2, 3 };
auto announce_list = tr_announce_list{};
EXPECT_EQ(2, announce_list.set(std::data(Urls), std::data(Tiers), 3));
EXPECT_EQ(2, announce_list.size());
EXPECT_EQ(Tiers[0], announce_list.at(0).tier);
EXPECT_EQ(Tiers[2], announce_list.at(1).tier);
EXPECT_EQ(Urls[0], announce_list.at(0).announce.full);
EXPECT_EQ(Urls[2], announce_list.at(1).announce.full);
}
TEST_F(AnnounceListTest, canRemoveById)
{
auto constexpr Announce = "https://www.example.com/announce"sv;
auto constexpr Tier = tr_tracker_tier_t{ 1 };
auto announce_list = tr_announce_list{};
announce_list.add(Tier, Announce);
EXPECT_EQ(1, std::size(announce_list));
auto const id = announce_list.at(0).id;
EXPECT_TRUE(announce_list.remove(id));
EXPECT_EQ(0, std::size(announce_list));
}
TEST_F(AnnounceListTest, canNotRemoveByInvalidId)
{
auto constexpr Announce = "https://www.example.com/announce"sv;
auto constexpr Tier = tr_tracker_tier_t{ 1 };
auto announce_list = tr_announce_list{};
announce_list.add(Tier, Announce);
EXPECT_EQ(1, std::size(announce_list));
auto const id = announce_list.at(0).id;
EXPECT_FALSE(announce_list.remove(id + 1));
EXPECT_EQ(1, std::size(announce_list));
EXPECT_EQ(Announce, announce_list.at(0).announce.full);
}
TEST_F(AnnounceListTest, canRemoveByAnnounce)
{
auto constexpr Announce = "https://www.example.com/announce"sv;
auto constexpr Tier = tr_tracker_tier_t{ 1 };
auto announce_list = tr_announce_list{};
announce_list.add(Tier, Announce);
EXPECT_EQ(1, std::size(announce_list));
EXPECT_TRUE(announce_list.remove(Announce));
EXPECT_EQ(0, std::size(announce_list));
}
TEST_F(AnnounceListTest, canNotRemoveByInvalidAnnounce)
{
auto constexpr Announce = "https://www.example.com/announce"sv;
auto constexpr Tier = tr_tracker_tier_t{ 1 };
auto announce_list = tr_announce_list{};
announce_list.add(Tier, Announce);
EXPECT_EQ(1, std::size(announce_list));
EXPECT_FALSE(announce_list.remove("https://www.not-example.com/announce"sv));
EXPECT_EQ(1, std::size(announce_list));
}
TEST_F(AnnounceListTest, canReplace)
{
auto constexpr Tier = tr_tracker_tier_t{ 1 };
auto constexpr Announce1 = "https://www.example.com/1/announce"sv;
auto constexpr Announce2 = "https://www.example.com/2/announce"sv;
auto announce_list = tr_announce_list{};
EXPECT_TRUE(announce_list.add(Tier, Announce1));
EXPECT_TRUE(announce_list.replace(announce_list.at(0).id, Announce2));
EXPECT_EQ(Announce2, announce_list.at(0).announce.full);
}
TEST_F(AnnounceListTest, canNotReplaceInvalidId)
{
auto constexpr Tier = tr_tracker_tier_t{ 1 };
auto constexpr Announce1 = "https://www.example.com/1/announce"sv;
auto constexpr Announce2 = "https://www.example.com/2/announce"sv;
auto announce_list = tr_announce_list{};
EXPECT_TRUE(announce_list.add(Tier, Announce1));
EXPECT_FALSE(announce_list.replace(announce_list.at(0).id + 1, Announce2));
EXPECT_EQ(Announce1, announce_list.at(0).announce.full);
}
TEST_F(AnnounceListTest, canNotReplaceWithInvalidAnnounce)
{
auto constexpr Tier = tr_tracker_tier_t{ 1 };
auto constexpr Announce1 = "https://www.example.com/1/announce"sv;
auto constexpr Announce2 = "telnet://www.example.com/2/announce"sv;
auto announce_list = tr_announce_list{};
EXPECT_TRUE(announce_list.add(Tier, Announce1));
EXPECT_FALSE(announce_list.replace(announce_list.at(0).id, Announce2));
EXPECT_EQ(Announce1, announce_list.at(0).announce.full);
}
TEST_F(AnnounceListTest, canNotReplaceWithDuplicate)
{
auto constexpr Tier = tr_tracker_tier_t{ 1 };
auto constexpr Announce = "https://www.example.com/announce"sv;
auto announce_list = tr_announce_list{};
EXPECT_TRUE(announce_list.add(Tier, Announce));
EXPECT_FALSE(announce_list.replace(announce_list.at(0).id, Announce));
EXPECT_EQ(Announce, announce_list.at(0).announce.full);
}
TEST_F(AnnounceListTest, announceToScrape)
{
struct ScrapeTest
{
std::string_view announce;
std::string_view expected_scrape;
};
auto constexpr Tests = std::array<ScrapeTest, 3>{ {
{ "https://www.example.com/announce"sv, "https://www.example.com/scrape"sv },
{ "https://www.example.com/foo"sv, ""sv },
{ "udp://www.example.com:999/"sv, "udp://www.example.com:999/"sv },
} };
for (auto const test : Tests)
{
auto const scrape = tr_announce_list::announceToScrape(tr_quark_new(test.announce));
EXPECT_EQ(tr_quark_new(test.expected_scrape), scrape);
}
}
TEST_F(AnnounceListTest, save)
{
auto constexpr Urls = std::array<char const*, 3>{
"https://www.example.com/a/announce",
"https://www.example.com/b/announce",
"https://www.example.com/c/announce",
};
auto constexpr Tiers = std::array<tr_tracker_tier_t, 3>{ 0, 1, 2 };
// first, set up a scratch .torrent
auto constexpr* const OriginalFile = LIBTRANSMISSION_TEST_ASSETS_DIR "/Android-x86 8.1 r6 iso.torrent";
auto original_content = std::vector<char>{};
auto const test_file = tr_strvJoin(::testing::TempDir(), "transmission-announce-list-test.torrent"sv);
tr_error* error = nullptr;
EXPECT_TRUE(tr_loadFile(original_content, OriginalFile, &error));
EXPECT_EQ(nullptr, error);
EXPECT_TRUE(tr_saveFile(test_file.c_str(), { std::data(original_content), std::size(original_content) }, &error));
EXPECT_EQ(nullptr, error);
// make an announce_list for it
auto announce_list = tr_announce_list();
EXPECT_TRUE(announce_list.add(Tiers[0], Urls[0]));
EXPECT_TRUE(announce_list.add(Tiers[1], Urls[1]));
EXPECT_TRUE(announce_list.add(Tiers[2], Urls[2]));
// try saving to a nonexistent .torrent file
EXPECT_FALSE(announce_list.save("/this/path/does/not/exist", &error));
EXPECT_NE(nullptr, error);
EXPECT_NE(0, error->code);
tr_error_clear(&error);
// now save to a real .torrent fi le
EXPECT_TRUE(announce_list.save(test_file.c_str(), &error));
EXPECT_EQ(nullptr, error);
// load the original
auto metainfo = tr_variant{};
EXPECT_TRUE(tr_variantFromBuf(
&metainfo,
TR_VARIANT_PARSE_BENC | TR_VARIANT_PARSE_INPLACE,
{ std::data(original_content), std::size(original_content) },
nullptr,
nullptr));
auto original = tr_metainfoParse(nullptr, &metainfo, nullptr);
EXPECT_TRUE(original);
tr_variantFree(&metainfo);
// load the scratch that we saved to
metainfo = tr_variant{};
EXPECT_TRUE(tr_variantFromFile(&metainfo, TR_VARIANT_PARSE_BENC, test_file.c_str(), nullptr));
auto saved = tr_metainfoParse(nullptr, &metainfo, nullptr);
EXPECT_TRUE(saved);
tr_variantFree(&metainfo);
// test that non-announce parts of the metainfo are the same
EXPECT_STREQ(original->info.name, saved->info.name);
EXPECT_EQ(original->info.fileCount, saved->info.fileCount);
EXPECT_EQ(original->info.dateCreated, saved->info.dateCreated);
EXPECT_EQ(original->pieces, saved->pieces);
// test that the saved version has the updated announce list
EXPECT_EQ(std::size(announce_list), std::size(*saved->info.announce_list));
EXPECT_TRUE(std::equal(
std::begin(announce_list),
std::end(announce_list),
std::begin(*saved->info.announce_list),
std::end(*saved->info.announce_list)));
// cleanup
std::remove(test_file.c_str());
}

View File

@ -46,15 +46,15 @@ TEST(MagnetMetainfo, magnetParse)
auto mm = tr_magnet_metainfo{};
EXPECT_TRUE(mm.parseMagnet(uri));
EXPECT_EQ(2, std::size(mm.trackers));
auto it = std::begin(mm.trackers);
EXPECT_EQ(0, it->first);
EXPECT_EQ("http://tracker.openbittorrent.com/announce"sv, tr_quark_get_string_view(it->second.announce_url));
EXPECT_EQ("http://tracker.openbittorrent.com/scrape"sv, tr_quark_get_string_view(it->second.scrape_url));
EXPECT_EQ(2, std::size(mm.announce_list));
auto it = std::begin(mm.announce_list);
EXPECT_EQ(0, it->tier);
EXPECT_EQ("http://tracker.openbittorrent.com/announce"sv, it->announce.full);
EXPECT_EQ("http://tracker.openbittorrent.com/scrape"sv, it->scrape.full);
++it;
EXPECT_EQ(1, it->first);
EXPECT_EQ("http://tracker.opentracker.org/announce", tr_quark_get_string_view(it->second.announce_url));
EXPECT_EQ("http://tracker.opentracker.org/scrape", tr_quark_get_string_view(it->second.scrape_url));
EXPECT_EQ(1, it->tier);
EXPECT_EQ("http://tracker.opentracker.org/announce", it->announce.full);
EXPECT_EQ("http://tracker.opentracker.org/scrape", it->scrape.full);
EXPECT_EQ(1, std::size(mm.webseed_urls));
EXPECT_EQ("http://server.webseed.org/path/to/file"sv, mm.webseed_urls.front());
EXPECT_EQ("Display Name"sv, mm.name);

View File

@ -82,7 +82,7 @@ protected:
EXPECT_EQ(tr_file_index_t{ 1 }, inf.fileCount);
EXPECT_EQ(isPrivate, inf.isPrivate);
EXPECT_FALSE(inf.isFolder);
EXPECT_EQ(trackerCount, inf.trackerCount);
EXPECT_EQ(trackerCount, std::size(*inf.announce_list));
// cleanup
tr_ctorFree(ctor);
@ -168,7 +168,7 @@ protected:
EXPECT_EQ(payload_count, inf.fileCount);
EXPECT_EQ(is_private, inf.isPrivate);
EXPECT_EQ(builder->isFolder, inf.isFolder);
EXPECT_EQ(tracker_count, inf.trackerCount);
EXPECT_EQ(tracker_count, std::size(*inf.announce_list));
// cleanup
tr_ctorFree(ctor);

View File

@ -39,14 +39,15 @@ TEST(Metainfo, magnetLink)
auto const parse_result = tr_torrentParse(ctor, &inf);
EXPECT_EQ(TR_PARSE_OK, parse_result);
EXPECT_EQ(0, inf.fileCount); // because it's a magnet link
EXPECT_EQ(2, inf.trackerCount);
if (inf.trackerCount >= 1)
auto const n = std::size(*inf.announce_list);
EXPECT_EQ(2, n);
if (n >= 1)
{
EXPECT_STREQ("http://tracker.publicbt.com/announce", inf.trackers[0].announce);
EXPECT_EQ("http://tracker.publicbt.com/announce"sv, inf.announce_list->at(0).announce.full);
}
if (inf.trackerCount >= 2)
if (n >= 2)
{
EXPECT_STREQ("udp://tracker.publicbt.com:80", inf.trackers[1].announce);
EXPECT_EQ("udp://tracker.publicbt.com:80"sv, inf.announce_list->at(1).announce.full);
}
EXPECT_EQ(1, inf.webseedCount);
if (inf.webseedCount >= 1)

View File

@ -27,6 +27,8 @@
#define MY_NAME "transmission-show"
#define TIMEOUT_SECS 30
using namespace std::literals;
static tr_option options[] = {
{ 'm', "magnet", "Give a magnet link for the specified torrent", "m", false, nullptr },
{ 's', "scrape", "Ask the torrent's trackers how many peers are in the torrent's swarm", "s", false, nullptr },
@ -115,7 +117,6 @@ static void showInfo(tr_info const* inf)
{
char buf[128];
tr_file** files;
int prevTier = -1;
/**
*** General Info
@ -132,7 +133,7 @@ static void showInfo(tr_info const* inf)
printf(" Comment: %s\n", inf->comment);
}
printf(" Piece Count: %d\n", inf->pieceCount);
printf(" Piece Count: %zu\n", size_t(inf->pieceCount));
printf(" Piece Size: %s\n", tr_formatter_mem_B(buf, inf->pieceSize, sizeof(buf)));
printf(" Total Size: %s\n", tr_formatter_size_B(buf, inf->totalSize, sizeof(buf)));
printf(" Privacy: %s\n", inf->isPrivate ? "Private torrent" : "Public torrent");
@ -142,16 +143,18 @@ static void showInfo(tr_info const* inf)
**/
printf("\nTRACKERS\n");
for (unsigned int i = 0; i < inf->trackerCount; ++i)
auto current_tier = std::optional<tr_tracker_tier_t>{};
auto print_tier = size_t{ 1 };
for (auto const& tracker : *inf->announce_list)
{
if (prevTier != inf->trackers[i].tier)
if (!current_tier || current_tier != tracker.tier)
{
prevTier = inf->trackers[i].tier;
printf("\n Tier #%d\n", prevTier + 1);
current_tier = tracker.tier;
printf("\n Tier #%zu\n", print_tier);
++print_tier;
}
printf(" %s\n", inf->trackers[i].announce);
printf(" %" TR_PRIsv "\n", TR_PRIsv_ARG(tracker.announce.full));
}
/**
@ -215,33 +218,28 @@ static CURL* tr_curl_easy_init(struct evbuffer* writebuf)
static void doScrape(tr_info const* inf)
{
for (unsigned int i = 0; i < inf->trackerCount; ++i)
for (auto const& tracker : *inf->announce_list)
{
CURL* curl;
CURLcode res;
struct evbuffer* buf;
char const* scrape = inf->trackers[i].scrape;
char* url;
char escaped[SHA_DIGEST_LENGTH * 3 + 1];
if (scrape == nullptr)
if (tracker.scrape_interned == TR_KEY_NONE)
{
continue;
}
char escaped[SHA_DIGEST_LENGTH * 3 + 1];
tr_http_escape_sha1(escaped, inf->hash);
auto const scrape = tracker.scrape.full;
auto const url = tr_strvJoin(scrape, (tr_strvContains(scrape, '?') ? "&"sv : "?"sv), "info_hash="sv, escaped);
url = tr_strdup_printf("%s%cinfo_hash=%s", scrape, strchr(scrape, '?') != nullptr ? '&' : '?', escaped);
printf("%s ... ", url);
printf("%" TR_PRIsv " ... ", TR_PRIsv_ARG(url));
fflush(stdout);
buf = evbuffer_new();
curl = tr_curl_easy_init(buf);
curl_easy_setopt(curl, CURLOPT_URL, url);
auto* const buf = evbuffer_new();
auto* const curl = tr_curl_easy_init(buf);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_TIMEOUT, TIMEOUT_SECS);
if ((res = curl_easy_perform(curl)) != CURLE_OK)
auto const res = curl_easy_perform(curl);
if (res != CURLE_OK)
{
printf("error: %s\n", curl_easy_strerror(res));
}
@ -305,7 +303,6 @@ static void doScrape(tr_info const* inf)
curl_easy_cleanup(curl);
evbuffer_free(buf);
tr_free(url);
}
}
@ -327,7 +324,7 @@ int tr_main(int argc, char* argv[])
if (showVersion)
{
fprintf(stderr, MY_NAME " " LONG_VERSION_STRING "\n");
fprintf(stderr, "%s %s\n", MY_NAME, LONG_VERSION_STRING);
return EXIT_SUCCESS;
}