feat: add tr_torrentSetTrackerList(), tr_torrentGetTrackerList() (#2642)
Add a getter/setter for torrent announce URLs as text that can be copied and pasted: one URL per line, with a blank line between tiers. C API: tr_torrentGetTrackerList() and tr_torrentSetTrackerList() RPC APi: `trackerList` in `torrent-get` and `torrent-set`. This deprecates `trackerAdd`, `trackerRemove`, and `trackerEdit` from the RPC API.
This commit is contained in:
parent
17cb155451
commit
02b6cc76d1
|
@ -142,8 +142,8 @@ Request arguments:
|
|||
| `bandwidthPriority` | number | this torrent's bandwidth tr_priority_t
|
||||
| `downloadLimit` | number | maximum download speed (KBps)
|
||||
| `downloadLimited` | boolean | true if `downloadLimit` is honored
|
||||
| `files-wanted` | array | indices of file(s) to download
|
||||
| `files-unwanted` | array | indices of file(s) to not download
|
||||
| `files-wanted` | array | indices of file(s) to download
|
||||
| `honorsSessionLimits` | boolean | true if session upload limits are honored
|
||||
| `ids` | array | torrent list, as described in 3.1
|
||||
| `labels` | array | array of string labels
|
||||
|
@ -157,9 +157,10 @@ Request arguments:
|
|||
| `seedIdleMode` | number | which seeding inactivity to use. See tr_idlelimit
|
||||
| `seedRatioLimit` | double | torrent-level seeding ratio
|
||||
| `seedRatioMode` | number | which ratio to use. See tr_ratiolimit
|
||||
| `trackerAdd` | array | strings of announce URLs to add
|
||||
| `trackerRemove` | array | ids of trackers to remove
|
||||
| `trackerReplace` | array | pairs of <trackerId/new announce URLs>
|
||||
| `trackerAdd` | array | **DEPRECATED** use trackerList instead
|
||||
| `trackerList` | string | string of announce URLs, one per line, with a blank line between tiers
|
||||
| `trackerRemove` | array | **DEPRECATED** use trackerList instead
|
||||
| `trackerReplace` | array | **DEPRECATED** use trackerList instead
|
||||
| `uploadLimit` | number | maximum upload speed (KBps)
|
||||
| `uploadLimited` | boolean | true if `uploadLimit` is honored
|
||||
|
||||
|
@ -269,6 +270,7 @@ The 'source' column here corresponds to the data structure there.
|
|||
| `startDate`| number| tr_stat
|
||||
| `status`| number (see below)| tr_stat
|
||||
| `trackers`| array (see below)| n/a
|
||||
' `trackerList` | string | string of announce URLs, one per line, with a blank line between tiers
|
||||
| `trackerStats`| array (see below)| n/a
|
||||
| `totalSize`| number| tr_torrent_view
|
||||
| `torrentFile`| string| tr_info
|
||||
|
@ -531,6 +533,7 @@ Response arguments: `path`, `name`, and `id`, holding the torrent ID integer
|
|||
| `config-dir` | string | location of transmission's configuration directory
|
||||
| `dht-enabled` | boolean | true means allow dht in public torrents
|
||||
| `download-dir` | string | default path to download torrents
|
||||
| `download-dir-free-space` | number | **DEPRECATED** Use the `free-space` method instead.
|
||||
| `download-queue-enabled` | boolean | if true, limit how many torrents can be downloaded at once
|
||||
| `download-queue-size` | number | max number of torrents to download at once (see download-queue-enabled)
|
||||
| `encryption` | string | `required`, `preferred`, `tolerated`
|
||||
|
@ -935,6 +938,7 @@ Transmission 4.0.0 (`rpc-version-semver` 5.3.0, `rpc-version`: 17)
|
|||
| Method | Description
|
||||
|:---|:---
|
||||
| `/upload` | :warning: undocumented `/upload` endpoint removed
|
||||
| `session-get` | **DEPRECATED** `download-dir-free-space`. Use `free-space` instead.
|
||||
| `free-space` | new return arg `total-capacity`
|
||||
| `session-get` | new arg `rpc-version-semver`
|
||||
| `session-get` | new arg `script-torrent-added-enabled`
|
||||
|
@ -947,16 +951,9 @@ Transmission 4.0.0 (`rpc-version-semver` 5.3.0, `rpc-version`: 17)
|
|||
| `torrent-get` | new arg `primary-mime-type`
|
||||
| `torrent-get` | new arg `tracker.sitename`
|
||||
| `torrent-get` | new arg `trackerStats.sitename`
|
||||
| `torrent-get` | new arg `trackerList`
|
||||
| `torrent-set` | new arg `trackerList`
|
||||
| `torrent-set` | **DEPRECATED** `trackerAdd`. Use `trackerList` instead.
|
||||
| `torrent-set` | **DEPRECATED** `trackerRemove`. Use `trackerList` instead.
|
||||
| `torrent-set` | **DEPRECATED** `trackerReplace`. Use `trackerList` instead.
|
||||
|
||||
|
||||
### 5.1. Upcoming Breakage
|
||||
|
||||
These features are deprecated:
|
||||
|
||||
1. session-get's 'download-dir-free-space' argument will be removed.
|
||||
Its functionality has been superceded by the 'free-space' method.
|
||||
|
||||
2. HTTP POSTs to http://server:port/transmission/upload will fail.
|
||||
This was an undocumented hack to allow web clients to add files without
|
||||
client-side access to the file. This functionality is superceded by
|
||||
using HTML5's FileReader object + the documented 'torrent-add' method.
|
||||
|
|
|
@ -2211,39 +2211,10 @@ void DetailsDialog::Impl::on_edit_trackers_response(int response, std::shared_pt
|
|||
{
|
||||
auto const torrent_id = GPOINTER_TO_INT(dialog->get_data(TORRENT_ID_KEY));
|
||||
auto* const text_buffer = static_cast<Gtk::TextBuffer*>(dialog->get_data(TEXT_BUFFER_KEY));
|
||||
tr_torrent* const tor = core_->find_torrent(torrent_id);
|
||||
|
||||
if (tor != nullptr)
|
||||
if (auto* const tor = core_->find_torrent(torrent_id); tor != nullptr)
|
||||
{
|
||||
/* build the array of trackers */
|
||||
auto const tracker_text = text_buffer->get_text(false);
|
||||
std::istringstream tracker_strings(tracker_text);
|
||||
|
||||
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))
|
||||
{
|
||||
if (str.empty())
|
||||
{
|
||||
++tier;
|
||||
}
|
||||
else
|
||||
{
|
||||
announce_url_strings.push_back(str);
|
||||
tiers.push_back(tier);
|
||||
}
|
||||
}
|
||||
|
||||
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)))
|
||||
if (tr_torrentSetTrackerList(tor, text_buffer->get_text(false).c_str()))
|
||||
{
|
||||
refresh();
|
||||
}
|
||||
|
|
|
@ -248,3 +248,62 @@ bool tr_announce_list::save(std::string const& torrent_file, tr_error** error) c
|
|||
// save it
|
||||
return tr_saveFile(torrent_file, contents, error);
|
||||
}
|
||||
|
||||
bool tr_announce_list::parse(std::string_view text)
|
||||
{
|
||||
auto scratch = tr_announce_list{};
|
||||
|
||||
auto current_tier = tr_tracker_tier_t{ 0 };
|
||||
auto current_tier_size = size_t{ 0 };
|
||||
auto line = std::string_view{};
|
||||
while (tr_strvSep(&text, &line, '\n'))
|
||||
{
|
||||
if (tr_strvEndsWith(line, '\r'))
|
||||
{
|
||||
line = line.substr(0, std::size(line) - 1);
|
||||
}
|
||||
|
||||
line = tr_strvStrip(line);
|
||||
|
||||
if (std::empty(line))
|
||||
{
|
||||
if (current_tier_size > 0)
|
||||
{
|
||||
++current_tier;
|
||||
current_tier_size = 0;
|
||||
}
|
||||
}
|
||||
else if (scratch.add(line, current_tier))
|
||||
{
|
||||
++current_tier_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*this = scratch;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string tr_announce_list::toString() const
|
||||
{
|
||||
auto text = std::string{};
|
||||
auto current_tier = std::optional<tr_tracker_tier_t>{};
|
||||
|
||||
for (auto const& tracker : *this)
|
||||
{
|
||||
if (current_tier && *current_tier != tracker.tier)
|
||||
{
|
||||
text += '\n';
|
||||
}
|
||||
|
||||
text += tracker.announce.full;
|
||||
text += '\n';
|
||||
|
||||
current_tier = tracker.tier;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
|
@ -32,7 +33,7 @@ public:
|
|||
tr_tracker_tier_t tier = 0;
|
||||
tr_tracker_id_t id = 0;
|
||||
|
||||
int compare(tracker_info const& that) const // <=>
|
||||
[[nodiscard]] int compare(tracker_info const& that) const // <=>
|
||||
{
|
||||
if (this->tier != that.tier)
|
||||
{
|
||||
|
@ -47,12 +48,12 @@ public:
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool operator<(tracker_info const& that) const
|
||||
[[nodiscard]] bool operator<(tracker_info const& that) const
|
||||
{
|
||||
return compare(that) < 0;
|
||||
}
|
||||
|
||||
bool operator==(tracker_info const& that) const
|
||||
[[nodiscard]] bool operator==(tracker_info const& that) const
|
||||
{
|
||||
return compare(that) == 0;
|
||||
}
|
||||
|
@ -62,29 +63,34 @@ private:
|
|||
using trackers_t = std::vector<tracker_info>;
|
||||
|
||||
public:
|
||||
auto begin() const
|
||||
[[nodiscard]] auto begin() const
|
||||
{
|
||||
return std::begin(trackers_);
|
||||
}
|
||||
auto end() const
|
||||
|
||||
[[nodiscard]] auto end() const
|
||||
{
|
||||
return std::end(trackers_);
|
||||
}
|
||||
bool empty() const
|
||||
|
||||
[[nodiscard]] bool empty() const
|
||||
{
|
||||
return std::empty(trackers_);
|
||||
}
|
||||
size_t size() const
|
||||
|
||||
[[nodiscard]] size_t size() const
|
||||
{
|
||||
return std::size(trackers_);
|
||||
}
|
||||
tracker_info const& at(size_t i) const
|
||||
|
||||
[[nodiscard]] 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;
|
||||
[[nodiscard]] std::set<tr_tracker_tier_t> tiers() const;
|
||||
|
||||
[[nodiscard]] tr_tracker_tier_t nextTier() const;
|
||||
|
||||
bool add(std::string_view announce_url_sv)
|
||||
{
|
||||
|
@ -101,13 +107,21 @@ public:
|
|||
return trackers_.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the announce list from a text string.
|
||||
* - One announce URL per line
|
||||
* - Blank line denotes a new tier
|
||||
*/
|
||||
bool parse(std::string_view text);
|
||||
[[nodiscard]] std::string toString() const;
|
||||
|
||||
bool save(std::string 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;
|
||||
[[nodiscard]] 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);
|
||||
static tr_tracker_id_t nextUniqueId();
|
||||
|
|
|
@ -18,7 +18,7 @@ using namespace std::literals;
|
|||
namespace
|
||||
{
|
||||
|
||||
auto constexpr my_static = std::array<std::string_view, 387>{ ""sv,
|
||||
auto constexpr my_static = std::array<std::string_view, 388>{ ""sv,
|
||||
"activeTorrentCount"sv,
|
||||
"activity-date"sv,
|
||||
"activityDate"sv,
|
||||
|
@ -370,6 +370,7 @@ auto constexpr my_static = std::array<std::string_view, 387>{ ""sv,
|
|||
"totalSize"sv,
|
||||
"total_size"sv,
|
||||
"trackerAdd"sv,
|
||||
"trackerList"sv,
|
||||
"trackerRemove"sv,
|
||||
"trackerReplace"sv,
|
||||
"trackerStats"sv,
|
||||
|
|
|
@ -373,6 +373,7 @@ enum
|
|||
TR_KEY_totalSize,
|
||||
TR_KEY_total_size,
|
||||
TR_KEY_trackerAdd,
|
||||
TR_KEY_trackerList,
|
||||
TR_KEY_trackerRemove,
|
||||
TR_KEY_trackerReplace,
|
||||
TR_KEY_trackerStats,
|
||||
|
|
|
@ -777,6 +777,10 @@ static void initField(tr_torrent const* const tor, tr_stat const* const st, tr_v
|
|||
addTrackers(tor, initme);
|
||||
break;
|
||||
|
||||
case TR_KEY_trackerList:
|
||||
tr_variantInitStr(initme, tor->trackerList());
|
||||
break;
|
||||
|
||||
case TR_KEY_trackerStats:
|
||||
{
|
||||
auto const n = tr_torrentTrackerCount(tor);
|
||||
|
@ -1258,6 +1262,14 @@ static char const* torrentSet(
|
|||
errmsg = replaceTrackers(tor, tmp_variant);
|
||||
}
|
||||
|
||||
if (std::string_view txt; errmsg == nullptr && tr_variantDictFindStrView(args_in, TR_KEY_trackerList, &txt))
|
||||
{
|
||||
if (!tor->setTrackerList(txt))
|
||||
{
|
||||
errmsg = "Invalid tracker list";
|
||||
}
|
||||
}
|
||||
|
||||
notify(session, TR_RPC_TORRENT_CHANGED, tor);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
|
||||
#include <libutp/utp.h>
|
||||
|
||||
// #define TR_SHOW_DEPRECATED
|
||||
#include "transmission.h"
|
||||
|
||||
#include "announcer.h"
|
||||
|
|
|
@ -2028,42 +2028,51 @@ bool tr_torrent::checkPiece(tr_piece_index_t piece)
|
|||
****
|
||||
***/
|
||||
|
||||
bool tr_torrentSetAnnounceList(tr_torrent* tor, char const* const* announce_urls, tr_tracker_tier_t const* tiers, size_t n)
|
||||
bool tr_torrent::setTrackerList(std::string_view text)
|
||||
{
|
||||
TR_ASSERT(tr_isTorrent(tor));
|
||||
auto const lock = tor->unique_lock();
|
||||
auto const lock = this->unique_lock();
|
||||
|
||||
auto announce_list = tr_announce_list();
|
||||
if ((announce_list.set(announce_urls, tiers, n) == 0U) || !announce_list.save(tor->torrentFile()))
|
||||
if (!announce_list.parse(text) || !announce_list.save(this->torrentFile()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
tor->metainfo_.announceList() = announce_list;
|
||||
tor->markEdited();
|
||||
this->metainfo_.announceList() = announce_list;
|
||||
this->markEdited();
|
||||
|
||||
/* 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)
|
||||
if (this->error == TR_STAT_TRACKER_WARNING || this->error == TR_STAT_TRACKER_ERROR)
|
||||
{
|
||||
auto const error_url = tor->error_announce_url;
|
||||
auto const error_url = this->error_announce_url;
|
||||
|
||||
if (std::any_of(
|
||||
std::begin(tor->announceList()),
|
||||
std::end(tor->announceList()),
|
||||
std::begin(this->announceList()),
|
||||
std::end(this->announceList()),
|
||||
[error_url](auto const& tracker) { return tracker.announce_str == error_url; }))
|
||||
{
|
||||
tr_torrentClearError(tor);
|
||||
tr_torrentClearError(this);
|
||||
}
|
||||
}
|
||||
|
||||
/* tell the announcer to reload this torrent's tracker list */
|
||||
tr_announcerResetTorrent(tor->session->announcer, tor);
|
||||
tr_announcerResetTorrent(this->session->announcer, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tr_torrentSetTrackerList(tr_torrent* tor, char const* text)
|
||||
{
|
||||
return text != nullptr && tor->setTrackerList(text);
|
||||
}
|
||||
|
||||
char* tr_torrentGetTrackerList(tr_torrent const* tor)
|
||||
{
|
||||
return tr_strvDup(tor->trackerList());
|
||||
}
|
||||
|
||||
/**
|
||||
***
|
||||
**/
|
||||
|
|
|
@ -426,6 +426,13 @@ public:
|
|||
return this->announceList().tiers();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto trackerList() const
|
||||
{
|
||||
return this->announceList().toString();
|
||||
}
|
||||
|
||||
bool setTrackerList(std::string_view text);
|
||||
|
||||
/// METAINFO - WEBSEEDS
|
||||
|
||||
[[nodiscard]] auto webseedCount() const
|
||||
|
|
|
@ -1186,17 +1186,19 @@ char* tr_torrentGetMagnetLink(tr_torrent const* tor);
|
|||
**/
|
||||
|
||||
/**
|
||||
* @brief Modify a torrent's tracker list.
|
||||
* Returns a newly-allocated string listing its tracker's announce URLs.
|
||||
* One URL per line, with a blank line between tiers
|
||||
*/
|
||||
char* tr_torrentGetTrackerList(tr_torrent const* tor);
|
||||
|
||||
/**
|
||||
* Sets a torrent's tracker list from a list of announce URLs with one
|
||||
* URL per line and a blank line between tiers.
|
||||
*
|
||||
* This updates both the `torrent' object's tracker list
|
||||
* and the metainfo file in tr_sessionGetConfigDir()'s torrent subdirectory.
|
||||
*
|
||||
* @param torrent The torrent whose tracker list is to be modified
|
||||
* @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, char const* const* announce_urls, tr_tracker_tier_t const* tiers, size_t n);
|
||||
bool tr_torrentSetTrackerList(tr_torrent* tor, char const* text);
|
||||
|
||||
/**
|
||||
***
|
||||
|
@ -1599,8 +1601,8 @@ struct tr_stat
|
|||
This ONLY counts piece data. */
|
||||
float pieceDownloadSpeed_KBps;
|
||||
|
||||
#define TR_ETA_NOT_AVAIL -1
|
||||
#define TR_ETA_UNKNOWN -2
|
||||
#define TR_ETA_NOT_AVAIL (-1)
|
||||
#define TR_ETA_UNKNOWN (-2)
|
||||
/** If downloading, estimated number of seconds left until the torrent is done.
|
||||
If seeding, estimated number of seconds left until seed ratio is reached. */
|
||||
int eta;
|
||||
|
|
|
@ -114,7 +114,7 @@ void tr_timerAdd(struct event* timer, int seconds, int microseconds) TR_GNUC_NON
|
|||
void tr_timerAddMsec(struct event* timer, int milliseconds) TR_GNUC_NONNULL(1);
|
||||
|
||||
/** @brief return the current date in milliseconds */
|
||||
uint64_t tr_time_msec(void);
|
||||
uint64_t tr_time_msec();
|
||||
|
||||
/** @brief sleep the specified number of milliseconds */
|
||||
void tr_wait_msec(long int delay_milliseconds);
|
||||
|
@ -360,13 +360,19 @@ constexpr std::string_view tr_strvSep(std::string_view* sv, char delim)
|
|||
{
|
||||
auto pos = sv->find(delim);
|
||||
auto const ret = sv->substr(0, pos);
|
||||
sv->remove_prefix(pos != sv->npos ? pos + 1 : std::size(*sv));
|
||||
sv->remove_prefix(pos != std::string_view::npos ? pos + 1 : std::size(*sv));
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr bool tr_strvSep(std::string_view* sv, std::string_view* token, char delim)
|
||||
{
|
||||
return !std::empty((*token = tr_strvSep(sv, delim)));
|
||||
if (std::empty(*sv))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*token = tr_strvSep(sv, delim);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string_view tr_strvStrip(std::string_view sv);
|
||||
|
@ -453,7 +459,7 @@ extern time_t __tr_current_time;
|
|||
* to always be accurate. However, it is *much* faster when 100% accuracy
|
||||
* isn't needed
|
||||
*/
|
||||
static inline time_t tr_time(void)
|
||||
static inline time_t tr_time()
|
||||
{
|
||||
return __tr_current_time;
|
||||
}
|
||||
|
@ -542,4 +548,4 @@ char* tr_env_get_string(char const* key, char const* default_value);
|
|||
****
|
||||
***/
|
||||
|
||||
void tr_net_init(void);
|
||||
void tr_net_init();
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#include <libtransmission/error.h>
|
||||
#include <libtransmission/log.h>
|
||||
#include <libtransmission/utils.h> // tr_new()
|
||||
#include <libtransmission/utils.h> // tr_free(), tr_strvJoin()
|
||||
|
||||
#import "Torrent.h"
|
||||
#import "GroupsController.h"
|
||||
|
@ -746,28 +746,18 @@ bool trashDataFile(char const* filename, tr_error** error)
|
|||
new_tracker = [@"http://" stringByAppendingString:new_tracker];
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
auto const tracker = tr_torrentTracker(fHandle, i);
|
||||
urls.push_back(tracker.announce);
|
||||
tiers.push_back(tracker.tier);
|
||||
}
|
||||
|
||||
urls.push_back(new_tracker.UTF8String);
|
||||
tiers.push_back(std::empty(tiers) ? 0 : tiers.back() + 1);
|
||||
|
||||
BOOL const success = tr_torrentSetAnnounceList(fHandle, std::data(urls), std::data(tiers), std::size(urls));
|
||||
char* old_list = tr_torrentGetTrackerList(fHandle);
|
||||
auto new_list = tr_strvJoin(old_list, "\n\n", new_tracker.UTF8String);
|
||||
BOOL const success = tr_torrentSetTrackerList(fHandle, new_list.c_str());
|
||||
tr_free(old_list);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
- (void)removeTrackers:(NSSet*)trackers
|
||||
{
|
||||
auto urls = std::vector<char const*>{};
|
||||
auto tiers = std::vector<tr_tracker_tier_t>{};
|
||||
auto new_list = std::string{};
|
||||
auto current_tier = std::optional<tr_tracker_tier_t>{};
|
||||
|
||||
for (size_t i = 0, n = tr_torrentTrackerCount(fHandle); i < n; ++i)
|
||||
{
|
||||
|
@ -778,11 +768,18 @@ bool trashDataFile(char const* filename, tr_error** error)
|
|||
continue;
|
||||
}
|
||||
|
||||
urls.push_back(tracker.announce);
|
||||
tiers.push_back(tracker.tier);
|
||||
if (current_tier && *current_tier != tracker.tier)
|
||||
{
|
||||
new_list += '\n';
|
||||
}
|
||||
|
||||
new_list += tracker.announce;
|
||||
new_list += '\n';
|
||||
|
||||
current_tier = tracker.tier;
|
||||
}
|
||||
|
||||
BOOL const success = tr_torrentSetAnnounceList(fHandle, std::data(urls), std::data(tiers), std::size(urls));
|
||||
BOOL const success = tr_torrentSetTrackerList(fHandle, new_list.c_str());
|
||||
NSAssert(success, @"Removing tracker addresses failed");
|
||||
}
|
||||
|
||||
|
|
|
@ -132,6 +132,7 @@ tr_qt_wrap_ui(${PROJECT_NAME}_UI_SOURCES
|
|||
RelocateDialog.ui
|
||||
SessionDialog.ui
|
||||
StatsDialog.ui
|
||||
TrackersDialog.ui
|
||||
)
|
||||
|
||||
set(${PROJECT_NAME}_QRC_FILES application.qrc)
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <libtransmission/transmission.h>
|
||||
#include <libtransmission/utils.h> // tr_getRatio()
|
||||
|
||||
#include "BaseDialog.h"
|
||||
#include "ColumnResizer.h"
|
||||
#include "DetailsDialog.h"
|
||||
#include "Formatter.h"
|
||||
|
@ -43,6 +44,8 @@
|
|||
#include "TrackerModelFilter.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "ui_TrackersDialog.h"
|
||||
|
||||
class Prefs;
|
||||
class Session;
|
||||
|
||||
|
@ -53,6 +56,43 @@ class Session;
|
|||
namespace
|
||||
{
|
||||
|
||||
class TrackersDialog : public BaseDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TrackersDialog(QString tracker_list, QWidget* parent = nullptr)
|
||||
: BaseDialog{ parent }
|
||||
{
|
||||
ui_.setupUi(this);
|
||||
ui_.trackerList->setPlainText(tracker_list);
|
||||
connect(ui_.dialogButtons, &QDialogButtonBox::clicked, this, &TrackersDialog::onButtonBoxClicked);
|
||||
}
|
||||
|
||||
signals:
|
||||
void trackerListEdited(QString trackerList);
|
||||
|
||||
private slots:
|
||||
void onButtonBoxClicked(QAbstractButton* button)
|
||||
{
|
||||
if (ui_.dialogButtons->standardButton(button) == QDialogButtonBox::Ok)
|
||||
{
|
||||
emit trackerListEdited(ui_.trackerList->toPlainText());
|
||||
}
|
||||
|
||||
close();
|
||||
}
|
||||
|
||||
private:
|
||||
Ui::TrackersDialog ui_{};
|
||||
QTimer timer_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
int constexpr DebounceIntervalMSec = 100;
|
||||
int constexpr RefreshIntervalMSec = 4000;
|
||||
|
||||
|
@ -1014,6 +1054,7 @@ void DetailsDialog::refreshUI()
|
|||
///
|
||||
|
||||
tracker_model_->refresh(model_, ids_);
|
||||
ui_.editTrackersButton->setEnabled(std::size(ids_) == 1);
|
||||
|
||||
///
|
||||
/// Peers tab
|
||||
|
@ -1256,7 +1297,6 @@ void DetailsDialog::onBandwidthPriorityChanged(int index)
|
|||
void DetailsDialog::onTrackerSelectionChanged()
|
||||
{
|
||||
int const selection_count = ui_.trackersView->selectionModel()->selectedRows().size();
|
||||
ui_.editTrackerButton->setEnabled(selection_count == 1);
|
||||
ui_.removeTrackerButton->setEnabled(selection_count > 0);
|
||||
}
|
||||
|
||||
|
@ -1303,39 +1343,28 @@ void DetailsDialog::onAddTrackerClicked()
|
|||
}
|
||||
}
|
||||
|
||||
void DetailsDialog::onEditTrackerClicked()
|
||||
void DetailsDialog::onTrackerListEdited(QString tracker_list)
|
||||
{
|
||||
QItemSelectionModel* selection_model = ui_.trackersView->selectionModel();
|
||||
QModelIndexList selected_rows = selection_model->selectedRows();
|
||||
assert(selected_rows.size() == 1);
|
||||
QModelIndex i = selection_model->currentIndex();
|
||||
auto const tracker_info = ui_.trackersView->model()->data(i, TrackerModel::TrackerRole).value<TrackerInfo>();
|
||||
torrentSet(TR_KEY_trackerList, tracker_list);
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
QString const newval = QInputDialog::getText(
|
||||
this,
|
||||
tr("Edit URL "),
|
||||
tr("Edit tracker announce URL:"),
|
||||
QLineEdit::Normal,
|
||||
tracker_info.st.announce,
|
||||
&ok);
|
||||
|
||||
if (!ok)
|
||||
void DetailsDialog::onEditTrackersClicked()
|
||||
{
|
||||
if (std::size(ids_) != 1)
|
||||
{
|
||||
// user pressed "cancel" -- noop
|
||||
return;
|
||||
}
|
||||
else if (!QUrl(newval).isValid())
|
||||
{
|
||||
QMessageBox::warning(this, tr("Error"), tr("Invalid URL \"%1\"").arg(newval));
|
||||
}
|
||||
else
|
||||
{
|
||||
torrent_ids_t ids{ tracker_info.torrent_id };
|
||||
|
||||
QPair<int, QString> const id_url = qMakePair(tracker_info.st.id, newval);
|
||||
|
||||
torrentSet(ids, TR_KEY_trackerReplace, id_url);
|
||||
auto const* const tor = model_.getTorrentFromId(*std::begin(ids_));
|
||||
if (tor == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto* dialog = new TrackersDialog(tor->trackerList(), this);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(dialog, &TrackersDialog::trackerListEdited, this, &DetailsDialog::onTrackerListEdited);
|
||||
dialog->open();
|
||||
}
|
||||
|
||||
void DetailsDialog::onRemoveTrackerClicked()
|
||||
|
@ -1433,14 +1462,14 @@ void DetailsDialog::initTrackerTab()
|
|||
|
||||
auto& icons = IconCache::get();
|
||||
ui_.addTrackerButton->setIcon(icons.getThemeIcon(QStringLiteral("list-add"), QStyle::SP_DialogOpenButton));
|
||||
ui_.editTrackerButton->setIcon(icons.getThemeIcon(QStringLiteral("document-properties"), QStyle::SP_DesktopIcon));
|
||||
ui_.editTrackersButton->setIcon(icons.getThemeIcon(QStringLiteral("document-properties"), QStyle::SP_DesktopIcon));
|
||||
ui_.removeTrackerButton->setIcon(icons.getThemeIcon(QStringLiteral("list-remove"), QStyle::SP_TrashIcon));
|
||||
|
||||
ui_.showTrackerScrapesCheck->setChecked(prefs_.getBool(Prefs::SHOW_TRACKER_SCRAPES));
|
||||
ui_.showBackupTrackersCheck->setChecked(prefs_.getBool(Prefs::SHOW_BACKUP_TRACKERS));
|
||||
|
||||
connect(ui_.addTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onAddTrackerClicked);
|
||||
connect(ui_.editTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onEditTrackerClicked);
|
||||
connect(ui_.editTrackersButton, &QAbstractButton::clicked, this, &DetailsDialog::onEditTrackersClicked);
|
||||
connect(ui_.removeTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onRemoveTrackerClicked);
|
||||
connect(ui_.showBackupTrackersCheck, &QAbstractButton::clicked, this, &DetailsDialog::onShowBackupTrackersToggled);
|
||||
connect(ui_.showTrackerScrapesCheck, &QAbstractButton::clicked, this, &DetailsDialog::onShowTrackerScrapesToggled);
|
||||
|
@ -1545,3 +1574,5 @@ void DetailsDialog::onOpenRequested(QString const& path) const
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "DetailsDialog.moc"
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "Typedefs.h"
|
||||
|
||||
#include "ui_DetailsDialog.h"
|
||||
#include "ui_TrackersDialog.h"
|
||||
|
||||
class QTreeWidgetItem;
|
||||
|
||||
|
@ -68,10 +69,11 @@ private slots:
|
|||
// Tracker tab
|
||||
void onTrackerSelectionChanged();
|
||||
void onAddTrackerClicked();
|
||||
void onEditTrackerClicked();
|
||||
void onEditTrackersClicked();
|
||||
void onRemoveTrackerClicked();
|
||||
void onShowTrackerScrapesToggled(bool);
|
||||
void onShowBackupTrackersToggled(bool);
|
||||
void onTrackerListEdited(QString);
|
||||
|
||||
// Files tab
|
||||
void onFilePriorityChanged(QSet<int> const& file_indices, int);
|
||||
|
@ -124,6 +126,7 @@ private:
|
|||
TorrentModel const& model_;
|
||||
|
||||
Ui::DetailsDialog ui_ = {};
|
||||
Ui::TrackersDialog trackers_ui_ = {};
|
||||
|
||||
torrent_ids_t ids_;
|
||||
QTimer model_timer_;
|
||||
|
|
|
@ -527,9 +527,9 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QToolButton" name="editTrackerButton">
|
||||
<widget class="QToolButton" name="editTrackersButton">
|
||||
<property name="toolTip">
|
||||
<string>Edit Tracker</string>
|
||||
<string>Edit Trackers</string>
|
||||
</property>
|
||||
<property name="toolButtonStyle">
|
||||
<enum>Qt::ToolButtonIconOnly</enum>
|
||||
|
|
|
@ -436,6 +436,15 @@ Session::Tag Session::torrentSet(torrent_ids_t const& ids, tr_quark const key, b
|
|||
return torrentSetImpl(&args);
|
||||
}
|
||||
|
||||
Session::Tag Session::torrentSet(torrent_ids_t const& ids, tr_quark const key, QString const& value)
|
||||
{
|
||||
tr_variant args;
|
||||
tr_variantInitDict(&args, 2);
|
||||
addOptionalIds(&args, ids);
|
||||
dictAdd(&args, key, value);
|
||||
return torrentSetImpl(&args);
|
||||
}
|
||||
|
||||
Session::Tag Session::torrentSet(torrent_ids_t const& ids, tr_quark const key, QStringList const& value)
|
||||
{
|
||||
tr_variant args;
|
||||
|
@ -561,7 +570,7 @@ std::vector<std::string_view> const& Session::getKeyNames(TorrentProperties prop
|
|||
};
|
||||
|
||||
// unchanging fields needed by the details dialog
|
||||
static auto constexpr DetailInfoKeys = std::array<tr_quark, 8>{
|
||||
static auto constexpr DetailInfoKeys = std::array<tr_quark, 9>{
|
||||
TR_KEY_comment, //
|
||||
TR_KEY_creator, //
|
||||
TR_KEY_dateCreated, //
|
||||
|
@ -569,6 +578,7 @@ std::vector<std::string_view> const& Session::getKeyNames(TorrentProperties prop
|
|||
TR_KEY_isPrivate, //
|
||||
TR_KEY_pieceCount, //
|
||||
TR_KEY_pieceSize, //
|
||||
TR_KEY_trackerList, //
|
||||
TR_KEY_trackers, //
|
||||
};
|
||||
|
||||
|
|
|
@ -86,6 +86,7 @@ public:
|
|||
Tag torrentSet(torrent_ids_t const& ids, tr_quark const key, bool val);
|
||||
Tag torrentSet(torrent_ids_t const& ids, tr_quark const key, int val);
|
||||
Tag torrentSet(torrent_ids_t const& ids, tr_quark const key, double val);
|
||||
Tag torrentSet(torrent_ids_t const& ids, tr_quark const key, QString const& val);
|
||||
Tag torrentSet(torrent_ids_t const& ids, tr_quark const key, QList<int> const& val);
|
||||
Tag torrentSet(torrent_ids_t const& ids, tr_quark const key, QStringList const& val);
|
||||
Tag torrentSet(torrent_ids_t const& ids, tr_quark const key, QPair<int, QString> const& val);
|
||||
|
|
|
@ -224,6 +224,7 @@ Torrent::fields_t Torrent::update(tr_quark const* keys, tr_variant const* const*
|
|||
HANDLE_KEY(startDate, start_date, START_DATE)
|
||||
HANDLE_KEY(status, status, STATUS)
|
||||
HANDLE_KEY(totalSize, total_size, TOTAL_SIZE)
|
||||
HANDLE_KEY(trackerList, tracker_list, TRACKER_LIST)
|
||||
HANDLE_KEY(trackerStats, tracker_stats, TRACKER_STATS)
|
||||
HANDLE_KEY(trackers, tracker_stats, TRACKER_STATS)
|
||||
HANDLE_KEY(uploadLimit, upload_limit, UPLOAD_LIMIT) // KB/s
|
||||
|
|
|
@ -201,6 +201,11 @@ public:
|
|||
|
||||
QString getError() const;
|
||||
|
||||
QString trackerList() const
|
||||
{
|
||||
return tracker_list_;
|
||||
}
|
||||
|
||||
TorrentHash const& hash() const
|
||||
{
|
||||
return hash_;
|
||||
|
@ -606,6 +611,7 @@ public:
|
|||
STATUS,
|
||||
TOTAL_SIZE,
|
||||
TRACKER_STATS,
|
||||
TRACKER_LIST,
|
||||
UPLOADED_EVER,
|
||||
UPLOAD_LIMIT,
|
||||
UPLOAD_LIMITED,
|
||||
|
@ -669,12 +675,13 @@ private:
|
|||
double recheck_progress_ = {};
|
||||
double seed_ratio_limit_ = {};
|
||||
|
||||
QString primary_mime_type_;
|
||||
QString comment_;
|
||||
QString creator_;
|
||||
QString download_dir_;
|
||||
QString error_string_;
|
||||
QString name_;
|
||||
QString primary_mime_type_;
|
||||
QString tracker_list_;
|
||||
|
||||
// mutable because it's a lazy lookup
|
||||
mutable QIcon icon_ = IconCache::get().fileIcon();
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TrackersDialog</class>
|
||||
<widget class="QDialog" name="TrackersDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>450</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="acceptDrops">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Edit Trackers</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="dialogLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="filesSectionLabel">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">font-weight:bold</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Tracker Announce URLs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="filesSectionLayout" columnstretch="0">
|
||||
<property name="leftMargin">
|
||||
<number>18</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>To add another primary URL, add it after a blank line.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPlainTextEdit" name="trackerList"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="destinationLabel">
|
||||
<property name="text">
|
||||
<string>To add a backup URL, add it on the line after the primary URL.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="dialogButtons">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -388,3 +388,226 @@ TEST_F(AnnounceListTest, save)
|
|||
// cleanup
|
||||
std::remove(test_file.c_str());
|
||||
}
|
||||
|
||||
TEST_F(AnnounceListTest, SingleAnnounce)
|
||||
{
|
||||
auto announce_list = tr_announce_list{};
|
||||
|
||||
auto constexpr Text = "https://www.example.com/a/announce";
|
||||
EXPECT_TRUE(announce_list.parse(Text));
|
||||
EXPECT_EQ(1, std::size(announce_list));
|
||||
EXPECT_EQ("https://www.example.com/a/announce", announce_list.at(0).announce.full);
|
||||
}
|
||||
|
||||
TEST_F(AnnounceListTest, parseThreeTier)
|
||||
{
|
||||
auto announce_list = tr_announce_list{};
|
||||
|
||||
auto constexpr Text =
|
||||
"https://www.example.com/a/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/b/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/c/announce"sv;
|
||||
|
||||
EXPECT_TRUE(announce_list.parse(Text));
|
||||
EXPECT_EQ(3, std::size(announce_list));
|
||||
EXPECT_EQ("https://www.example.com/a/announce", announce_list.at(0).announce.full);
|
||||
EXPECT_EQ(0, announce_list.at(0).tier);
|
||||
EXPECT_EQ("https://www.example.com/b/announce", announce_list.at(1).announce.full);
|
||||
EXPECT_EQ(1, announce_list.at(1).tier);
|
||||
EXPECT_EQ("https://www.example.com/c/announce", announce_list.at(2).announce.full);
|
||||
EXPECT_EQ(2, announce_list.at(2).tier);
|
||||
EXPECT_EQ(tr_strvJoin(Text, "\n"sv), announce_list.toString());
|
||||
}
|
||||
|
||||
TEST_F(AnnounceListTest, parseThreeTierWithTrailingLf)
|
||||
{
|
||||
auto announce_list = tr_announce_list{};
|
||||
|
||||
auto constexpr Text =
|
||||
"https://www.example.com/a/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/b/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/c/announce\n"sv;
|
||||
|
||||
EXPECT_TRUE(announce_list.parse(Text));
|
||||
EXPECT_EQ(3, std::size(announce_list));
|
||||
EXPECT_EQ("https://www.example.com/a/announce", announce_list.at(0).announce.full);
|
||||
EXPECT_EQ(0, announce_list.at(0).tier);
|
||||
EXPECT_EQ("https://www.example.com/b/announce", announce_list.at(1).announce.full);
|
||||
EXPECT_EQ(1, announce_list.at(1).tier);
|
||||
EXPECT_EQ("https://www.example.com/c/announce", announce_list.at(2).announce.full);
|
||||
EXPECT_EQ(2, announce_list.at(2).tier);
|
||||
EXPECT_EQ(Text, announce_list.toString());
|
||||
}
|
||||
|
||||
TEST_F(AnnounceListTest, parseThreeTierWithExcessLf)
|
||||
{
|
||||
auto announce_list = tr_announce_list{};
|
||||
|
||||
auto constexpr Text =
|
||||
"https://www.example.com/a/announce\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"https://www.example.com/b/announce\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"https://www.example.com/c/announce\n"sv;
|
||||
|
||||
EXPECT_TRUE(announce_list.parse(Text));
|
||||
EXPECT_EQ(3, std::size(announce_list));
|
||||
EXPECT_EQ("https://www.example.com/a/announce", announce_list.at(0).announce.full);
|
||||
EXPECT_EQ(0, announce_list.at(0).tier);
|
||||
EXPECT_EQ("https://www.example.com/b/announce", announce_list.at(1).announce.full);
|
||||
EXPECT_EQ(1, announce_list.at(1).tier);
|
||||
EXPECT_EQ("https://www.example.com/c/announce", announce_list.at(2).announce.full);
|
||||
EXPECT_EQ(2, announce_list.at(2).tier);
|
||||
|
||||
auto constexpr ExpectedText =
|
||||
"https://www.example.com/a/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/b/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/c/announce\n"sv;
|
||||
EXPECT_EQ(ExpectedText, announce_list.toString());
|
||||
}
|
||||
|
||||
TEST_F(AnnounceListTest, parseThreeTierWithWhitespace)
|
||||
{
|
||||
auto announce_list = tr_announce_list{};
|
||||
|
||||
auto constexpr Text =
|
||||
"https://www.example.com/a/announce \n"
|
||||
"\n"
|
||||
" \n"
|
||||
"\n"
|
||||
"https://www.example.com/b/announce\n"
|
||||
"\n"
|
||||
"\n"
|
||||
"\n"
|
||||
" https://www.example.com/c/announce\n"sv;
|
||||
|
||||
EXPECT_TRUE(announce_list.parse(Text));
|
||||
EXPECT_EQ(3, std::size(announce_list));
|
||||
EXPECT_EQ("https://www.example.com/a/announce", announce_list.at(0).announce.full);
|
||||
EXPECT_EQ(0, announce_list.at(0).tier);
|
||||
EXPECT_EQ("https://www.example.com/b/announce", announce_list.at(1).announce.full);
|
||||
EXPECT_EQ(1, announce_list.at(1).tier);
|
||||
EXPECT_EQ("https://www.example.com/c/announce", announce_list.at(2).announce.full);
|
||||
EXPECT_EQ(2, announce_list.at(2).tier);
|
||||
|
||||
auto constexpr ExpectedText =
|
||||
"https://www.example.com/a/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/b/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/c/announce\n"sv;
|
||||
EXPECT_EQ(ExpectedText, announce_list.toString());
|
||||
}
|
||||
|
||||
TEST_F(AnnounceListTest, parseThreeTierCrLf)
|
||||
{
|
||||
auto announce_list = tr_announce_list{};
|
||||
|
||||
auto constexpr Text =
|
||||
"https://www.example.com/a/announce\r\n"
|
||||
"\r\n"
|
||||
"https://www.example.com/b/announce\r\n"
|
||||
"\r\n"
|
||||
"https://www.example.com/c/announce\r\n"sv;
|
||||
|
||||
EXPECT_TRUE(announce_list.parse(Text));
|
||||
EXPECT_EQ(3, std::size(announce_list));
|
||||
EXPECT_EQ("https://www.example.com/a/announce", announce_list.at(0).announce.full);
|
||||
EXPECT_EQ(0, announce_list.at(0).tier);
|
||||
EXPECT_EQ("https://www.example.com/b/announce", announce_list.at(1).announce.full);
|
||||
EXPECT_EQ(1, announce_list.at(1).tier);
|
||||
EXPECT_EQ("https://www.example.com/c/announce", announce_list.at(2).announce.full);
|
||||
EXPECT_EQ(2, announce_list.at(2).tier);
|
||||
|
||||
auto constexpr ExpectedText =
|
||||
"https://www.example.com/a/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/b/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/c/announce\n"sv;
|
||||
EXPECT_EQ(ExpectedText, announce_list.toString());
|
||||
}
|
||||
|
||||
TEST_F(AnnounceListTest, parseMultiTrackerInTier)
|
||||
{
|
||||
auto announce_list = tr_announce_list{};
|
||||
|
||||
auto constexpr Text =
|
||||
"https://www.example.com/a/announce\n"
|
||||
"https://www.example.com/b/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/c/announce\n"
|
||||
"https://www.example.com/d/announce\n"
|
||||
"https://www.example.com/e/announce\n"
|
||||
"https://www.example.com/f/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/g/announce\n"
|
||||
"https://www.example.com/h/announce\n"
|
||||
"https://www.example.com/i/announce\n"sv;
|
||||
|
||||
EXPECT_TRUE(announce_list.parse(Text));
|
||||
EXPECT_EQ(9, std::size(announce_list));
|
||||
|
||||
EXPECT_EQ("https://www.example.com/a/announce", announce_list.at(0).announce.full);
|
||||
EXPECT_EQ(0, announce_list.at(0).tier);
|
||||
EXPECT_EQ("https://www.example.com/b/announce", announce_list.at(1).announce.full);
|
||||
EXPECT_EQ(0, announce_list.at(1).tier);
|
||||
|
||||
EXPECT_EQ("https://www.example.com/c/announce", announce_list.at(2).announce.full);
|
||||
EXPECT_EQ(1, announce_list.at(2).tier);
|
||||
EXPECT_EQ("https://www.example.com/d/announce", announce_list.at(3).announce.full);
|
||||
EXPECT_EQ(1, announce_list.at(3).tier);
|
||||
EXPECT_EQ("https://www.example.com/e/announce", announce_list.at(4).announce.full);
|
||||
EXPECT_EQ(1, announce_list.at(4).tier);
|
||||
EXPECT_EQ("https://www.example.com/f/announce", announce_list.at(5).announce.full);
|
||||
EXPECT_EQ(1, announce_list.at(5).tier);
|
||||
|
||||
EXPECT_EQ("https://www.example.com/g/announce", announce_list.at(6).announce.full);
|
||||
EXPECT_EQ(2, announce_list.at(6).tier);
|
||||
EXPECT_EQ("https://www.example.com/h/announce", announce_list.at(7).announce.full);
|
||||
EXPECT_EQ(2, announce_list.at(7).tier);
|
||||
EXPECT_EQ("https://www.example.com/i/announce", announce_list.at(8).announce.full);
|
||||
EXPECT_EQ(2, announce_list.at(8).tier);
|
||||
|
||||
EXPECT_EQ(Text, announce_list.toString());
|
||||
}
|
||||
|
||||
TEST_F(AnnounceListTest, parseInvalidUrl)
|
||||
{
|
||||
auto announce_list = tr_announce_list{};
|
||||
|
||||
auto constexpr Text =
|
||||
"siojfaiojf"
|
||||
"\n"
|
||||
"https://www.example.com/b/announce\n"
|
||||
"\n"
|
||||
"https://www.example.com/c/announce\n"sv;
|
||||
EXPECT_FALSE(announce_list.parse(Text));
|
||||
}
|
||||
|
||||
TEST_F(AnnounceListTest, parseDuplicateUrl)
|
||||
{
|
||||
auto announce_list = tr_announce_list{};
|
||||
|
||||
auto constexpr Text =
|
||||
"https://www.example.com/a/announce\n"
|
||||
"\r\n"
|
||||
"https://www.example.com/a/announce"sv;
|
||||
|
||||
EXPECT_FALSE(announce_list.parse(Text));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue