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
|
| `bandwidthPriority` | number | this torrent's bandwidth tr_priority_t
|
||||||
| `downloadLimit` | number | maximum download speed (KBps)
|
| `downloadLimit` | number | maximum download speed (KBps)
|
||||||
| `downloadLimited` | boolean | true if `downloadLimit` is honored
|
| `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-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
|
| `honorsSessionLimits` | boolean | true if session upload limits are honored
|
||||||
| `ids` | array | torrent list, as described in 3.1
|
| `ids` | array | torrent list, as described in 3.1
|
||||||
| `labels` | array | array of string labels
|
| `labels` | array | array of string labels
|
||||||
|
@ -157,9 +157,10 @@ Request arguments:
|
||||||
| `seedIdleMode` | number | which seeding inactivity to use. See tr_idlelimit
|
| `seedIdleMode` | number | which seeding inactivity to use. See tr_idlelimit
|
||||||
| `seedRatioLimit` | double | torrent-level seeding ratio
|
| `seedRatioLimit` | double | torrent-level seeding ratio
|
||||||
| `seedRatioMode` | number | which ratio to use. See tr_ratiolimit
|
| `seedRatioMode` | number | which ratio to use. See tr_ratiolimit
|
||||||
| `trackerAdd` | array | strings of announce URLs to add
|
| `trackerAdd` | array | **DEPRECATED** use trackerList instead
|
||||||
| `trackerRemove` | array | ids of trackers to remove
|
| `trackerList` | string | string of announce URLs, one per line, with a blank line between tiers
|
||||||
| `trackerReplace` | array | pairs of <trackerId/new announce URLs>
|
| `trackerRemove` | array | **DEPRECATED** use trackerList instead
|
||||||
|
| `trackerReplace` | array | **DEPRECATED** use trackerList instead
|
||||||
| `uploadLimit` | number | maximum upload speed (KBps)
|
| `uploadLimit` | number | maximum upload speed (KBps)
|
||||||
| `uploadLimited` | boolean | true if `uploadLimit` is honored
|
| `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
|
| `startDate`| number| tr_stat
|
||||||
| `status`| number (see below)| tr_stat
|
| `status`| number (see below)| tr_stat
|
||||||
| `trackers`| array (see below)| n/a
|
| `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
|
| `trackerStats`| array (see below)| n/a
|
||||||
| `totalSize`| number| tr_torrent_view
|
| `totalSize`| number| tr_torrent_view
|
||||||
| `torrentFile`| string| tr_info
|
| `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
|
| `config-dir` | string | location of transmission's configuration directory
|
||||||
| `dht-enabled` | boolean | true means allow dht in public torrents
|
| `dht-enabled` | boolean | true means allow dht in public torrents
|
||||||
| `download-dir` | string | default path to download 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-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)
|
| `download-queue-size` | number | max number of torrents to download at once (see download-queue-enabled)
|
||||||
| `encryption` | string | `required`, `preferred`, `tolerated`
|
| `encryption` | string | `required`, `preferred`, `tolerated`
|
||||||
|
@ -935,6 +938,7 @@ Transmission 4.0.0 (`rpc-version-semver` 5.3.0, `rpc-version`: 17)
|
||||||
| Method | Description
|
| Method | Description
|
||||||
|:---|:---
|
|:---|:---
|
||||||
| `/upload` | :warning: undocumented `/upload` endpoint removed
|
| `/upload` | :warning: undocumented `/upload` endpoint removed
|
||||||
|
| `session-get` | **DEPRECATED** `download-dir-free-space`. Use `free-space` instead.
|
||||||
| `free-space` | new return arg `total-capacity`
|
| `free-space` | new return arg `total-capacity`
|
||||||
| `session-get` | new arg `rpc-version-semver`
|
| `session-get` | new arg `rpc-version-semver`
|
||||||
| `session-get` | new arg `script-torrent-added-enabled`
|
| `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 `primary-mime-type`
|
||||||
| `torrent-get` | new arg `tracker.sitename`
|
| `torrent-get` | new arg `tracker.sitename`
|
||||||
| `torrent-get` | new arg `trackerStats.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 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));
|
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 */
|
if (tr_torrentSetTrackerList(tor, text_buffer->get_text(false).c_str()))
|
||||||
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)))
|
|
||||||
{
|
{
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,3 +248,62 @@ bool tr_announce_list::save(std::string const& torrent_file, tr_error** error) c
|
||||||
// save it
|
// save it
|
||||||
return tr_saveFile(torrent_file, contents, error);
|
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 <cstddef>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ public:
|
||||||
tr_tracker_tier_t tier = 0;
|
tr_tracker_tier_t tier = 0;
|
||||||
tr_tracker_id_t id = 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)
|
if (this->tier != that.tier)
|
||||||
{
|
{
|
||||||
|
@ -47,12 +48,12 @@ public:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator<(tracker_info const& that) const
|
[[nodiscard]] bool operator<(tracker_info const& that) const
|
||||||
{
|
{
|
||||||
return compare(that) < 0;
|
return compare(that) < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(tracker_info const& that) const
|
[[nodiscard]] bool operator==(tracker_info const& that) const
|
||||||
{
|
{
|
||||||
return compare(that) == 0;
|
return compare(that) == 0;
|
||||||
}
|
}
|
||||||
|
@ -62,29 +63,34 @@ private:
|
||||||
using trackers_t = std::vector<tracker_info>;
|
using trackers_t = std::vector<tracker_info>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto begin() const
|
[[nodiscard]] auto begin() const
|
||||||
{
|
{
|
||||||
return std::begin(trackers_);
|
return std::begin(trackers_);
|
||||||
}
|
}
|
||||||
auto end() const
|
|
||||||
|
[[nodiscard]] auto end() const
|
||||||
{
|
{
|
||||||
return std::end(trackers_);
|
return std::end(trackers_);
|
||||||
}
|
}
|
||||||
bool empty() const
|
|
||||||
|
[[nodiscard]] bool empty() const
|
||||||
{
|
{
|
||||||
return std::empty(trackers_);
|
return std::empty(trackers_);
|
||||||
}
|
}
|
||||||
size_t size() const
|
|
||||||
|
[[nodiscard]] size_t size() const
|
||||||
{
|
{
|
||||||
return std::size(trackers_);
|
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);
|
return trackers_.at(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<tr_tracker_tier_t> tiers() const;
|
[[nodiscard]] std::set<tr_tracker_tier_t> tiers() const;
|
||||||
tr_tracker_tier_t nextTier() const;
|
|
||||||
|
[[nodiscard]] tr_tracker_tier_t nextTier() const;
|
||||||
|
|
||||||
bool add(std::string_view announce_url_sv)
|
bool add(std::string_view announce_url_sv)
|
||||||
{
|
{
|
||||||
|
@ -101,13 +107,21 @@ public:
|
||||||
return trackers_.clear();
|
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;
|
bool save(std::string const& torrent_file, tr_error** error = nullptr) const;
|
||||||
|
|
||||||
static std::optional<std::string> announceToScrape(std::string_view announce);
|
static std::optional<std::string> announceToScrape(std::string_view announce);
|
||||||
static tr_quark announceToScrape(tr_quark announce);
|
static tr_quark announceToScrape(tr_quark announce);
|
||||||
|
|
||||||
private:
|
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);
|
bool canAdd(tr_url_parsed_t const& announce);
|
||||||
static tr_tracker_id_t nextUniqueId();
|
static tr_tracker_id_t nextUniqueId();
|
||||||
|
|
|
@ -18,7 +18,7 @@ using namespace std::literals;
|
||||||
namespace
|
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,
|
"activeTorrentCount"sv,
|
||||||
"activity-date"sv,
|
"activity-date"sv,
|
||||||
"activityDate"sv,
|
"activityDate"sv,
|
||||||
|
@ -370,6 +370,7 @@ auto constexpr my_static = std::array<std::string_view, 387>{ ""sv,
|
||||||
"totalSize"sv,
|
"totalSize"sv,
|
||||||
"total_size"sv,
|
"total_size"sv,
|
||||||
"trackerAdd"sv,
|
"trackerAdd"sv,
|
||||||
|
"trackerList"sv,
|
||||||
"trackerRemove"sv,
|
"trackerRemove"sv,
|
||||||
"trackerReplace"sv,
|
"trackerReplace"sv,
|
||||||
"trackerStats"sv,
|
"trackerStats"sv,
|
||||||
|
|
|
@ -373,6 +373,7 @@ enum
|
||||||
TR_KEY_totalSize,
|
TR_KEY_totalSize,
|
||||||
TR_KEY_total_size,
|
TR_KEY_total_size,
|
||||||
TR_KEY_trackerAdd,
|
TR_KEY_trackerAdd,
|
||||||
|
TR_KEY_trackerList,
|
||||||
TR_KEY_trackerRemove,
|
TR_KEY_trackerRemove,
|
||||||
TR_KEY_trackerReplace,
|
TR_KEY_trackerReplace,
|
||||||
TR_KEY_trackerStats,
|
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);
|
addTrackers(tor, initme);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case TR_KEY_trackerList:
|
||||||
|
tr_variantInitStr(initme, tor->trackerList());
|
||||||
|
break;
|
||||||
|
|
||||||
case TR_KEY_trackerStats:
|
case TR_KEY_trackerStats:
|
||||||
{
|
{
|
||||||
auto const n = tr_torrentTrackerCount(tor);
|
auto const n = tr_torrentTrackerCount(tor);
|
||||||
|
@ -1258,6 +1262,14 @@ static char const* torrentSet(
|
||||||
errmsg = replaceTrackers(tor, tmp_variant);
|
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);
|
notify(session, TR_RPC_TORRENT_CHANGED, tor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
|
|
||||||
#include <libutp/utp.h>
|
#include <libutp/utp.h>
|
||||||
|
|
||||||
// #define TR_SHOW_DEPRECATED
|
|
||||||
#include "transmission.h"
|
#include "transmission.h"
|
||||||
|
|
||||||
#include "announcer.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 = this->unique_lock();
|
||||||
auto const lock = tor->unique_lock();
|
|
||||||
|
|
||||||
auto announce_list = tr_announce_list();
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
tor->metainfo_.announceList() = announce_list;
|
this->metainfo_.announceList() = announce_list;
|
||||||
tor->markEdited();
|
this->markEdited();
|
||||||
|
|
||||||
/* if we had a tracker-related error on this torrent,
|
/* if we had a tracker-related error on this torrent,
|
||||||
* and that tracker's been removed,
|
* and that tracker's been removed,
|
||||||
* then clear the error */
|
* 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(
|
if (std::any_of(
|
||||||
std::begin(tor->announceList()),
|
std::begin(this->announceList()),
|
||||||
std::end(tor->announceList()),
|
std::end(this->announceList()),
|
||||||
[error_url](auto const& tracker) { return tracker.announce_str == error_url; }))
|
[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 */
|
/* tell the announcer to reload this torrent's tracker list */
|
||||||
tr_announcerResetTorrent(tor->session->announcer, tor);
|
tr_announcerResetTorrent(this->session->announcer, this);
|
||||||
|
|
||||||
return true;
|
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();
|
return this->announceList().tiers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto trackerList() const
|
||||||
|
{
|
||||||
|
return this->announceList().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setTrackerList(std::string_view text);
|
||||||
|
|
||||||
/// METAINFO - WEBSEEDS
|
/// METAINFO - WEBSEEDS
|
||||||
|
|
||||||
[[nodiscard]] auto webseedCount() const
|
[[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
|
* This updates both the `torrent' object's tracker list
|
||||||
* and the metainfo file in tr_sessionGetConfigDir()'s torrent subdirectory.
|
* 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. */
|
This ONLY counts piece data. */
|
||||||
float pieceDownloadSpeed_KBps;
|
float pieceDownloadSpeed_KBps;
|
||||||
|
|
||||||
#define TR_ETA_NOT_AVAIL -1
|
#define TR_ETA_NOT_AVAIL (-1)
|
||||||
#define TR_ETA_UNKNOWN -2
|
#define TR_ETA_UNKNOWN (-2)
|
||||||
/** If downloading, estimated number of seconds left until the torrent is done.
|
/** If downloading, estimated number of seconds left until the torrent is done.
|
||||||
If seeding, estimated number of seconds left until seed ratio is reached. */
|
If seeding, estimated number of seconds left until seed ratio is reached. */
|
||||||
int eta;
|
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);
|
void tr_timerAddMsec(struct event* timer, int milliseconds) TR_GNUC_NONNULL(1);
|
||||||
|
|
||||||
/** @brief return the current date in milliseconds */
|
/** @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 */
|
/** @brief sleep the specified number of milliseconds */
|
||||||
void tr_wait_msec(long int delay_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 pos = sv->find(delim);
|
||||||
auto const ret = sv->substr(0, pos);
|
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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool tr_strvSep(std::string_view* sv, std::string_view* token, char delim)
|
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);
|
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
|
* to always be accurate. However, it is *much* faster when 100% accuracy
|
||||||
* isn't needed
|
* isn't needed
|
||||||
*/
|
*/
|
||||||
static inline time_t tr_time(void)
|
static inline time_t tr_time()
|
||||||
{
|
{
|
||||||
return __tr_current_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/error.h>
|
||||||
#include <libtransmission/log.h>
|
#include <libtransmission/log.h>
|
||||||
#include <libtransmission/utils.h> // tr_new()
|
#include <libtransmission/utils.h> // tr_free(), tr_strvJoin()
|
||||||
|
|
||||||
#import "Torrent.h"
|
#import "Torrent.h"
|
||||||
#import "GroupsController.h"
|
#import "GroupsController.h"
|
||||||
|
@ -746,28 +746,18 @@ bool trashDataFile(char const* filename, tr_error** error)
|
||||||
new_tracker = [@"http://" stringByAppendingString:new_tracker];
|
new_tracker = [@"http://" stringByAppendingString:new_tracker];
|
||||||
}
|
}
|
||||||
|
|
||||||
auto urls = std::vector<char const*>{};
|
char* old_list = tr_torrentGetTrackerList(fHandle);
|
||||||
auto tiers = std::vector<tr_tracker_tier_t>{};
|
auto new_list = tr_strvJoin(old_list, "\n\n", new_tracker.UTF8String);
|
||||||
|
BOOL const success = tr_torrentSetTrackerList(fHandle, new_list.c_str());
|
||||||
for (size_t i = 0, n = tr_torrentTrackerCount(fHandle); i < n; ++i)
|
tr_free(old_list);
|
||||||
{
|
|
||||||
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));
|
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)removeTrackers:(NSSet*)trackers
|
- (void)removeTrackers:(NSSet*)trackers
|
||||||
{
|
{
|
||||||
auto urls = std::vector<char const*>{};
|
auto new_list = std::string{};
|
||||||
auto tiers = std::vector<tr_tracker_tier_t>{};
|
auto current_tier = std::optional<tr_tracker_tier_t>{};
|
||||||
|
|
||||||
for (size_t i = 0, n = tr_torrentTrackerCount(fHandle); i < n; ++i)
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
urls.push_back(tracker.announce);
|
if (current_tier && *current_tier != tracker.tier)
|
||||||
tiers.push_back(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");
|
NSAssert(success, @"Removing tracker addresses failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,6 +132,7 @@ tr_qt_wrap_ui(${PROJECT_NAME}_UI_SOURCES
|
||||||
RelocateDialog.ui
|
RelocateDialog.ui
|
||||||
SessionDialog.ui
|
SessionDialog.ui
|
||||||
StatsDialog.ui
|
StatsDialog.ui
|
||||||
|
TrackersDialog.ui
|
||||||
)
|
)
|
||||||
|
|
||||||
set(${PROJECT_NAME}_QRC_FILES application.qrc)
|
set(${PROJECT_NAME}_QRC_FILES application.qrc)
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include <libtransmission/transmission.h>
|
#include <libtransmission/transmission.h>
|
||||||
#include <libtransmission/utils.h> // tr_getRatio()
|
#include <libtransmission/utils.h> // tr_getRatio()
|
||||||
|
|
||||||
|
#include "BaseDialog.h"
|
||||||
#include "ColumnResizer.h"
|
#include "ColumnResizer.h"
|
||||||
#include "DetailsDialog.h"
|
#include "DetailsDialog.h"
|
||||||
#include "Formatter.h"
|
#include "Formatter.h"
|
||||||
|
@ -43,6 +44,8 @@
|
||||||
#include "TrackerModelFilter.h"
|
#include "TrackerModelFilter.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
|
#include "ui_TrackersDialog.h"
|
||||||
|
|
||||||
class Prefs;
|
class Prefs;
|
||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
|
@ -53,6 +56,43 @@ class Session;
|
||||||
namespace
|
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 DebounceIntervalMSec = 100;
|
||||||
int constexpr RefreshIntervalMSec = 4000;
|
int constexpr RefreshIntervalMSec = 4000;
|
||||||
|
|
||||||
|
@ -1014,6 +1054,7 @@ void DetailsDialog::refreshUI()
|
||||||
///
|
///
|
||||||
|
|
||||||
tracker_model_->refresh(model_, ids_);
|
tracker_model_->refresh(model_, ids_);
|
||||||
|
ui_.editTrackersButton->setEnabled(std::size(ids_) == 1);
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Peers tab
|
/// Peers tab
|
||||||
|
@ -1256,7 +1297,6 @@ void DetailsDialog::onBandwidthPriorityChanged(int index)
|
||||||
void DetailsDialog::onTrackerSelectionChanged()
|
void DetailsDialog::onTrackerSelectionChanged()
|
||||||
{
|
{
|
||||||
int const selection_count = ui_.trackersView->selectionModel()->selectedRows().size();
|
int const selection_count = ui_.trackersView->selectionModel()->selectedRows().size();
|
||||||
ui_.editTrackerButton->setEnabled(selection_count == 1);
|
|
||||||
ui_.removeTrackerButton->setEnabled(selection_count > 0);
|
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();
|
torrentSet(TR_KEY_trackerList, tracker_list);
|
||||||
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>();
|
|
||||||
|
|
||||||
bool ok = false;
|
void DetailsDialog::onEditTrackersClicked()
|
||||||
QString const newval = QInputDialog::getText(
|
{
|
||||||
this,
|
if (std::size(ids_) != 1)
|
||||||
tr("Edit URL "),
|
|
||||||
tr("Edit tracker announce URL:"),
|
|
||||||
QLineEdit::Normal,
|
|
||||||
tracker_info.st.announce,
|
|
||||||
&ok);
|
|
||||||
|
|
||||||
if (!ok)
|
|
||||||
{
|
{
|
||||||
// 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);
|
auto const* const tor = model_.getTorrentFromId(*std::begin(ids_));
|
||||||
|
if (tor == nullptr)
|
||||||
torrentSet(ids, TR_KEY_trackerReplace, id_url);
|
{
|
||||||
|
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()
|
void DetailsDialog::onRemoveTrackerClicked()
|
||||||
|
@ -1433,14 +1462,14 @@ void DetailsDialog::initTrackerTab()
|
||||||
|
|
||||||
auto& icons = IconCache::get();
|
auto& icons = IconCache::get();
|
||||||
ui_.addTrackerButton->setIcon(icons.getThemeIcon(QStringLiteral("list-add"), QStyle::SP_DialogOpenButton));
|
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_.removeTrackerButton->setIcon(icons.getThemeIcon(QStringLiteral("list-remove"), QStyle::SP_TrashIcon));
|
||||||
|
|
||||||
ui_.showTrackerScrapesCheck->setChecked(prefs_.getBool(Prefs::SHOW_TRACKER_SCRAPES));
|
ui_.showTrackerScrapesCheck->setChecked(prefs_.getBool(Prefs::SHOW_TRACKER_SCRAPES));
|
||||||
ui_.showBackupTrackersCheck->setChecked(prefs_.getBool(Prefs::SHOW_BACKUP_TRACKERS));
|
ui_.showBackupTrackersCheck->setChecked(prefs_.getBool(Prefs::SHOW_BACKUP_TRACKERS));
|
||||||
|
|
||||||
connect(ui_.addTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onAddTrackerClicked);
|
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_.removeTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onRemoveTrackerClicked);
|
||||||
connect(ui_.showBackupTrackersCheck, &QAbstractButton::clicked, this, &DetailsDialog::onShowBackupTrackersToggled);
|
connect(ui_.showBackupTrackersCheck, &QAbstractButton::clicked, this, &DetailsDialog::onShowBackupTrackersToggled);
|
||||||
connect(ui_.showTrackerScrapesCheck, &QAbstractButton::clicked, this, &DetailsDialog::onShowTrackerScrapesToggled);
|
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 "Typedefs.h"
|
||||||
|
|
||||||
#include "ui_DetailsDialog.h"
|
#include "ui_DetailsDialog.h"
|
||||||
|
#include "ui_TrackersDialog.h"
|
||||||
|
|
||||||
class QTreeWidgetItem;
|
class QTreeWidgetItem;
|
||||||
|
|
||||||
|
@ -68,10 +69,11 @@ private slots:
|
||||||
// Tracker tab
|
// Tracker tab
|
||||||
void onTrackerSelectionChanged();
|
void onTrackerSelectionChanged();
|
||||||
void onAddTrackerClicked();
|
void onAddTrackerClicked();
|
||||||
void onEditTrackerClicked();
|
void onEditTrackersClicked();
|
||||||
void onRemoveTrackerClicked();
|
void onRemoveTrackerClicked();
|
||||||
void onShowTrackerScrapesToggled(bool);
|
void onShowTrackerScrapesToggled(bool);
|
||||||
void onShowBackupTrackersToggled(bool);
|
void onShowBackupTrackersToggled(bool);
|
||||||
|
void onTrackerListEdited(QString);
|
||||||
|
|
||||||
// Files tab
|
// Files tab
|
||||||
void onFilePriorityChanged(QSet<int> const& file_indices, int);
|
void onFilePriorityChanged(QSet<int> const& file_indices, int);
|
||||||
|
@ -124,6 +126,7 @@ private:
|
||||||
TorrentModel const& model_;
|
TorrentModel const& model_;
|
||||||
|
|
||||||
Ui::DetailsDialog ui_ = {};
|
Ui::DetailsDialog ui_ = {};
|
||||||
|
Ui::TrackersDialog trackers_ui_ = {};
|
||||||
|
|
||||||
torrent_ids_t ids_;
|
torrent_ids_t ids_;
|
||||||
QTimer model_timer_;
|
QTimer model_timer_;
|
||||||
|
|
|
@ -527,9 +527,9 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QToolButton" name="editTrackerButton">
|
<widget class="QToolButton" name="editTrackersButton">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Edit Tracker</string>
|
<string>Edit Trackers</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolButtonStyle">
|
<property name="toolButtonStyle">
|
||||||
<enum>Qt::ToolButtonIconOnly</enum>
|
<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);
|
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)
|
Session::Tag Session::torrentSet(torrent_ids_t const& ids, tr_quark const key, QStringList const& value)
|
||||||
{
|
{
|
||||||
tr_variant args;
|
tr_variant args;
|
||||||
|
@ -561,7 +570,7 @@ std::vector<std::string_view> const& Session::getKeyNames(TorrentProperties prop
|
||||||
};
|
};
|
||||||
|
|
||||||
// unchanging fields needed by the details dialog
|
// 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_comment, //
|
||||||
TR_KEY_creator, //
|
TR_KEY_creator, //
|
||||||
TR_KEY_dateCreated, //
|
TR_KEY_dateCreated, //
|
||||||
|
@ -569,6 +578,7 @@ std::vector<std::string_view> const& Session::getKeyNames(TorrentProperties prop
|
||||||
TR_KEY_isPrivate, //
|
TR_KEY_isPrivate, //
|
||||||
TR_KEY_pieceCount, //
|
TR_KEY_pieceCount, //
|
||||||
TR_KEY_pieceSize, //
|
TR_KEY_pieceSize, //
|
||||||
|
TR_KEY_trackerList, //
|
||||||
TR_KEY_trackers, //
|
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, 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, 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, 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, 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, QStringList const& val);
|
||||||
Tag torrentSet(torrent_ids_t const& ids, tr_quark const key, QPair<int, QString> 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(startDate, start_date, START_DATE)
|
||||||
HANDLE_KEY(status, status, STATUS)
|
HANDLE_KEY(status, status, STATUS)
|
||||||
HANDLE_KEY(totalSize, total_size, TOTAL_SIZE)
|
HANDLE_KEY(totalSize, total_size, TOTAL_SIZE)
|
||||||
|
HANDLE_KEY(trackerList, tracker_list, TRACKER_LIST)
|
||||||
HANDLE_KEY(trackerStats, tracker_stats, TRACKER_STATS)
|
HANDLE_KEY(trackerStats, tracker_stats, TRACKER_STATS)
|
||||||
HANDLE_KEY(trackers, tracker_stats, TRACKER_STATS)
|
HANDLE_KEY(trackers, tracker_stats, TRACKER_STATS)
|
||||||
HANDLE_KEY(uploadLimit, upload_limit, UPLOAD_LIMIT) // KB/s
|
HANDLE_KEY(uploadLimit, upload_limit, UPLOAD_LIMIT) // KB/s
|
||||||
|
|
|
@ -201,6 +201,11 @@ public:
|
||||||
|
|
||||||
QString getError() const;
|
QString getError() const;
|
||||||
|
|
||||||
|
QString trackerList() const
|
||||||
|
{
|
||||||
|
return tracker_list_;
|
||||||
|
}
|
||||||
|
|
||||||
TorrentHash const& hash() const
|
TorrentHash const& hash() const
|
||||||
{
|
{
|
||||||
return hash_;
|
return hash_;
|
||||||
|
@ -606,6 +611,7 @@ public:
|
||||||
STATUS,
|
STATUS,
|
||||||
TOTAL_SIZE,
|
TOTAL_SIZE,
|
||||||
TRACKER_STATS,
|
TRACKER_STATS,
|
||||||
|
TRACKER_LIST,
|
||||||
UPLOADED_EVER,
|
UPLOADED_EVER,
|
||||||
UPLOAD_LIMIT,
|
UPLOAD_LIMIT,
|
||||||
UPLOAD_LIMITED,
|
UPLOAD_LIMITED,
|
||||||
|
@ -669,12 +675,13 @@ private:
|
||||||
double recheck_progress_ = {};
|
double recheck_progress_ = {};
|
||||||
double seed_ratio_limit_ = {};
|
double seed_ratio_limit_ = {};
|
||||||
|
|
||||||
QString primary_mime_type_;
|
|
||||||
QString comment_;
|
QString comment_;
|
||||||
QString creator_;
|
QString creator_;
|
||||||
QString download_dir_;
|
QString download_dir_;
|
||||||
QString error_string_;
|
QString error_string_;
|
||||||
QString name_;
|
QString name_;
|
||||||
|
QString primary_mime_type_;
|
||||||
|
QString tracker_list_;
|
||||||
|
|
||||||
// mutable because it's a lazy lookup
|
// mutable because it's a lazy lookup
|
||||||
mutable QIcon icon_ = IconCache::get().fileIcon();
|
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
|
// cleanup
|
||||||
std::remove(test_file.c_str());
|
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