2023-02-11 20:49:42 +00:00
|
|
|
// This file Copyright © 2022-2023 Mnemosyne LLC.
|
2022-12-21 21:26:25 +00:00
|
|
|
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
|
|
|
// or any future license endorsed by Mnemosyne LLC.
|
|
|
|
// License text can be found in the licenses/ folder.
|
|
|
|
|
2022-12-26 21:13:21 +00:00
|
|
|
#include "Torrent.h"
|
2022-12-21 21:26:25 +00:00
|
|
|
|
2022-12-26 21:13:21 +00:00
|
|
|
#include "IconCache.h"
|
2023-02-18 01:14:01 +00:00
|
|
|
#include "Percents.h"
|
2022-12-26 21:13:21 +00:00
|
|
|
#include "Utils.h"
|
2022-12-21 21:26:25 +00:00
|
|
|
|
|
|
|
#include <libtransmission/transmission.h>
|
|
|
|
#include <libtransmission/utils.h>
|
|
|
|
|
2022-12-26 21:13:21 +00:00
|
|
|
#include <glibmm/i18n.h>
|
2022-12-27 01:43:20 +00:00
|
|
|
#include <glibmm/value.h>
|
2022-12-26 21:13:21 +00:00
|
|
|
|
|
|
|
#include <fmt/core.h>
|
|
|
|
|
|
|
|
#include <array>
|
|
|
|
#include <functional>
|
2023-02-18 01:14:01 +00:00
|
|
|
#include <utility>
|
2022-12-21 21:26:25 +00:00
|
|
|
|
|
|
|
using namespace std::string_view_literals;
|
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
|
|
|
|
|
|
|
template<typename T>
|
2022-12-28 14:47:53 +00:00
|
|
|
Glib::Value<T>& column_value_cast(Glib::ValueBase& value, Gtk::TreeModelColumn<T> const& /*column*/)
|
2022-12-21 21:26:25 +00:00
|
|
|
{
|
|
|
|
return static_cast<Glib::Value<T>&>(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T, typename U, typename = std::enable_if_t<!std::is_floating_point_v<T>>>
|
|
|
|
void update_cache_value(T& value, U&& new_value, Torrent::ChangeFlags& changes, Torrent::ChangeFlag flag)
|
|
|
|
{
|
2023-02-18 01:14:01 +00:00
|
|
|
using std::rel_ops::operator!=;
|
|
|
|
|
2022-12-21 21:26:25 +00:00
|
|
|
if (value != new_value)
|
|
|
|
{
|
|
|
|
value = std::forward<U>(new_value);
|
|
|
|
changes.set(flag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename T, typename U, typename = std::enable_if_t<std::is_floating_point_v<T>>>
|
|
|
|
void update_cache_value(T& value, U new_value, T epsilon, Torrent::ChangeFlags& changes, Torrent::ChangeFlag flag)
|
|
|
|
{
|
|
|
|
if (std::fabs(value - new_value) >= epsilon)
|
|
|
|
{
|
|
|
|
value = new_value;
|
|
|
|
changes.set(flag);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int build_torrent_trackers_hash(tr_torrent const& torrent)
|
|
|
|
{
|
|
|
|
auto hash = uint64_t(0);
|
|
|
|
|
|
|
|
for (auto i = size_t(0), n = tr_torrentTrackerCount(&torrent); i < n; ++i)
|
|
|
|
{
|
|
|
|
for (auto const ch : std::string_view{ tr_torrentTracker(&torrent, i).announce })
|
|
|
|
{
|
2023-02-03 16:12:48 +00:00
|
|
|
hash = (hash << 4U) ^ (hash >> 28U) ^ static_cast<unsigned char>(ch);
|
2022-12-21 21:26:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string_view get_mime_type(tr_torrent const& torrent)
|
|
|
|
{
|
|
|
|
auto const n_files = tr_torrentFileCount(&torrent);
|
|
|
|
|
|
|
|
if (n_files == 0)
|
|
|
|
{
|
|
|
|
return UnknownMimeType;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (n_files > 1)
|
|
|
|
{
|
|
|
|
return DirectoryMimeType;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto const name = std::string_view(tr_torrentFile(&torrent, 0).name);
|
|
|
|
|
|
|
|
return name.find('/') != std::string_view::npos ? DirectoryMimeType : tr_get_mime_type_for_filename(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
Torrent::Columns::Columns()
|
|
|
|
{
|
|
|
|
add(self);
|
|
|
|
add(name_collated);
|
|
|
|
}
|
|
|
|
|
|
|
|
class Torrent::Impl
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
struct Cache
|
|
|
|
{
|
|
|
|
Glib::ustring error_message;
|
|
|
|
Glib::ustring name;
|
|
|
|
Glib::ustring name_collated;
|
|
|
|
|
|
|
|
std::string_view mime_type;
|
|
|
|
|
|
|
|
uint64_t have_unchecked = {};
|
|
|
|
uint64_t have_valid = {};
|
|
|
|
uint64_t left_until_done = {};
|
|
|
|
uint64_t size_when_done = {};
|
|
|
|
uint64_t total_size = {};
|
|
|
|
uint64_t uploaded_ever = {};
|
|
|
|
|
|
|
|
size_t queue_position = {};
|
|
|
|
|
|
|
|
time_t added_date = {};
|
|
|
|
time_t eta = {};
|
|
|
|
|
|
|
|
tr_torrent_activity activity = {};
|
|
|
|
|
|
|
|
unsigned int trackers = {};
|
|
|
|
int active_peer_count = {};
|
|
|
|
int active_peers_down = {};
|
|
|
|
int active_peers_up = {};
|
|
|
|
int error_code = {};
|
|
|
|
|
2023-02-18 01:14:01 +00:00
|
|
|
Percents activity_percent_done = {};
|
|
|
|
Percents metadata_percent_complete = {};
|
|
|
|
Percents percent_complete = {};
|
|
|
|
Percents percent_done = {};
|
|
|
|
Percents recheck_progress = {};
|
|
|
|
Percents seed_ratio_percent_done = {};
|
|
|
|
|
2022-12-21 21:26:25 +00:00
|
|
|
uint16_t peers_connected = {};
|
|
|
|
uint16_t peers_getting_from_us = {};
|
|
|
|
uint16_t peers_sending_to_us = {};
|
|
|
|
uint16_t webseeds_sending_to_us = {};
|
|
|
|
|
|
|
|
float ratio = {};
|
|
|
|
float seed_ratio = {};
|
|
|
|
float speed_down = {};
|
|
|
|
float speed_up = {};
|
|
|
|
|
|
|
|
tr_priority_t priority = {};
|
|
|
|
|
|
|
|
bool active = {};
|
|
|
|
bool finished = {};
|
|
|
|
bool has_metadata = {};
|
|
|
|
bool has_seed_ratio = {};
|
|
|
|
bool stalled = {};
|
|
|
|
};
|
|
|
|
|
|
|
|
public:
|
|
|
|
Impl(Torrent& torrent, tr_torrent* raw_torrent);
|
|
|
|
|
|
|
|
tr_torrent* get_raw_torrent()
|
|
|
|
{
|
|
|
|
return raw_torrent_;
|
|
|
|
}
|
|
|
|
|
|
|
|
Cache& get_cache()
|
|
|
|
{
|
|
|
|
return cache_;
|
|
|
|
}
|
|
|
|
|
|
|
|
ChangeFlags update_cache();
|
|
|
|
|
|
|
|
void notify_property_changes(ChangeFlags changes) const;
|
|
|
|
|
|
|
|
void get_value(int column, Glib::ValueBase& value) const;
|
|
|
|
|
2022-12-28 14:47:53 +00:00
|
|
|
[[nodiscard]] Glib::RefPtr<Gio::Icon> get_icon() const;
|
|
|
|
[[nodiscard]] Glib::ustring get_short_status_text() const;
|
|
|
|
[[nodiscard]] Glib::ustring get_long_progress_text() const;
|
|
|
|
[[nodiscard]] Glib::ustring get_long_status_text() const;
|
2022-12-21 21:26:25 +00:00
|
|
|
|
|
|
|
private:
|
2022-12-28 14:47:53 +00:00
|
|
|
[[nodiscard]] Glib::ustring get_short_transfer_text() const;
|
|
|
|
[[nodiscard]] Glib::ustring get_error_text() const;
|
|
|
|
[[nodiscard]] Glib::ustring get_activity_text() const;
|
2022-12-21 21:26:25 +00:00
|
|
|
|
|
|
|
private:
|
|
|
|
Torrent& torrent_;
|
|
|
|
tr_torrent* const raw_torrent_;
|
|
|
|
|
|
|
|
Cache cache_;
|
|
|
|
};
|
|
|
|
|
|
|
|
Torrent::Impl::Impl(Torrent& torrent, tr_torrent* raw_torrent)
|
|
|
|
: torrent_(torrent)
|
|
|
|
, raw_torrent_(raw_torrent)
|
|
|
|
{
|
|
|
|
if (raw_torrent_ != nullptr)
|
|
|
|
{
|
|
|
|
update_cache();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Torrent::ChangeFlags Torrent::Impl::update_cache()
|
|
|
|
{
|
|
|
|
auto result = ChangeFlags();
|
|
|
|
|
|
|
|
auto const* const stats = tr_torrentStat(raw_torrent_);
|
|
|
|
g_return_val_if_fail(stats != nullptr, Torrent::ChangeFlags());
|
|
|
|
|
|
|
|
auto seed_ratio = 0.0;
|
|
|
|
auto const has_seed_ratio = tr_torrentGetSeedRatio(raw_torrent_, &seed_ratio);
|
|
|
|
|
|
|
|
update_cache_value(cache_.name, tr_torrentName(raw_torrent_), result, ChangeFlag::NAME);
|
|
|
|
update_cache_value(cache_.speed_up, stats->pieceUploadSpeed_KBps, 0.01F, result, ChangeFlag::SPEED_UP);
|
|
|
|
update_cache_value(cache_.speed_down, stats->pieceDownloadSpeed_KBps, 0.01F, result, ChangeFlag::SPEED_DOWN);
|
|
|
|
update_cache_value(cache_.active_peers_up, stats->peersGettingFromUs, result, ChangeFlag::ACTIVE_PEERS_UP);
|
|
|
|
update_cache_value(
|
|
|
|
cache_.active_peers_down,
|
|
|
|
stats->peersSendingToUs + stats->webseedsSendingToUs,
|
|
|
|
result,
|
|
|
|
ChangeFlag::ACTIVE_PEERS_DOWN);
|
2023-02-18 01:14:01 +00:00
|
|
|
update_cache_value(cache_.recheck_progress, Percents(stats->recheckProgress), result, ChangeFlag::RECHECK_PROGRESS);
|
2022-12-21 21:26:25 +00:00
|
|
|
update_cache_value(
|
|
|
|
cache_.active,
|
|
|
|
stats->peersSendingToUs > 0 || stats->peersGettingFromUs > 0 || stats->activity == TR_STATUS_CHECK,
|
|
|
|
result,
|
|
|
|
ChangeFlag::ACTIVE);
|
|
|
|
update_cache_value(cache_.activity, stats->activity, result, ChangeFlag::ACTIVITY);
|
|
|
|
update_cache_value(
|
|
|
|
cache_.activity_percent_done,
|
2023-02-18 01:14:01 +00:00
|
|
|
Percents(std::clamp(
|
2022-12-21 21:26:25 +00:00
|
|
|
stats->activity == TR_STATUS_SEED && has_seed_ratio ? stats->seedRatioPercentDone : stats->percentDone,
|
|
|
|
0.0F,
|
2023-02-18 01:14:01 +00:00
|
|
|
1.0F)),
|
2022-12-21 21:26:25 +00:00
|
|
|
result,
|
|
|
|
ChangeFlag::PERCENT_DONE);
|
|
|
|
update_cache_value(cache_.finished, stats->finished, result, ChangeFlag::FINISHED);
|
|
|
|
update_cache_value(cache_.priority, tr_torrentGetPriority(raw_torrent_), result, ChangeFlag::PRIORITY);
|
|
|
|
update_cache_value(cache_.queue_position, stats->queuePosition, result, ChangeFlag::QUEUE_POSITION);
|
|
|
|
update_cache_value(cache_.trackers, build_torrent_trackers_hash(*raw_torrent_), result, ChangeFlag::TRACKERS);
|
|
|
|
update_cache_value(cache_.error_code, stats->error, result, ChangeFlag::ERROR_CODE);
|
|
|
|
update_cache_value(cache_.error_message, stats->errorString, result, ChangeFlag::ERROR_MESSAGE);
|
|
|
|
update_cache_value(
|
|
|
|
cache_.active_peer_count,
|
|
|
|
stats->peersSendingToUs + stats->peersGettingFromUs + stats->webseedsSendingToUs,
|
|
|
|
result,
|
|
|
|
ChangeFlag::ACTIVE_PEER_COUNT);
|
|
|
|
update_cache_value(cache_.mime_type, get_mime_type(*raw_torrent_), result, ChangeFlag::MIME_TYPE);
|
|
|
|
update_cache_value(cache_.has_metadata, tr_torrentHasMetadata(raw_torrent_), result, ChangeFlag::HAS_METADATA);
|
|
|
|
update_cache_value(cache_.stalled, stats->isStalled, result, ChangeFlag::STALLED);
|
|
|
|
update_cache_value(cache_.ratio, stats->ratio, 0.01F, result, ChangeFlag::RATIO);
|
|
|
|
|
|
|
|
update_cache_value(cache_.added_date, stats->addedDate, result, ChangeFlag::ADDED_DATE);
|
|
|
|
update_cache_value(cache_.eta, stats->eta, result, ChangeFlag::ETA);
|
2023-02-18 01:14:01 +00:00
|
|
|
update_cache_value(cache_.percent_complete, Percents(stats->percentComplete), result, ChangeFlag::PERCENT_COMPLETE);
|
2022-12-21 21:26:25 +00:00
|
|
|
update_cache_value(
|
|
|
|
cache_.seed_ratio_percent_done,
|
2023-02-18 01:14:01 +00:00
|
|
|
Percents(stats->seedRatioPercentDone),
|
2022-12-21 21:26:25 +00:00
|
|
|
result,
|
|
|
|
ChangeFlag::SEED_RATIO_PERCENT_DONE);
|
|
|
|
update_cache_value(cache_.total_size, tr_torrentTotalSize(raw_torrent_), result, ChangeFlag::TOTAL_SIZE);
|
|
|
|
|
|
|
|
update_cache_value(cache_.has_seed_ratio, has_seed_ratio, result, ChangeFlag::LONG_PROGRESS);
|
|
|
|
update_cache_value(cache_.have_unchecked, stats->haveUnchecked, result, ChangeFlag::LONG_PROGRESS);
|
|
|
|
update_cache_value(cache_.have_valid, stats->haveValid, result, ChangeFlag::LONG_PROGRESS);
|
|
|
|
update_cache_value(cache_.left_until_done, stats->leftUntilDone, result, ChangeFlag::LONG_PROGRESS);
|
2023-02-18 01:14:01 +00:00
|
|
|
update_cache_value(cache_.percent_done, Percents(stats->percentDone), result, ChangeFlag::LONG_PROGRESS);
|
2022-12-21 21:26:25 +00:00
|
|
|
update_cache_value(cache_.seed_ratio, static_cast<float>(seed_ratio), 0.01F, result, ChangeFlag::LONG_PROGRESS);
|
|
|
|
update_cache_value(cache_.size_when_done, stats->sizeWhenDone, result, ChangeFlag::LONG_PROGRESS);
|
|
|
|
update_cache_value(cache_.uploaded_ever, stats->uploadedEver, result, ChangeFlag::LONG_PROGRESS);
|
|
|
|
|
|
|
|
update_cache_value(
|
|
|
|
cache_.metadata_percent_complete,
|
2023-02-18 01:14:01 +00:00
|
|
|
Percents(stats->metadataPercentComplete),
|
2022-12-21 21:26:25 +00:00
|
|
|
result,
|
|
|
|
ChangeFlag::LONG_STATUS);
|
|
|
|
update_cache_value(cache_.peers_connected, stats->peersConnected, result, ChangeFlag::LONG_STATUS);
|
|
|
|
update_cache_value(cache_.peers_getting_from_us, stats->peersGettingFromUs, result, ChangeFlag::LONG_STATUS);
|
|
|
|
update_cache_value(cache_.peers_sending_to_us, stats->peersSendingToUs, result, ChangeFlag::LONG_STATUS);
|
|
|
|
update_cache_value(cache_.webseeds_sending_to_us, stats->webseedsSendingToUs, result, ChangeFlag::LONG_STATUS);
|
|
|
|
|
|
|
|
if (result.test(ChangeFlag::NAME))
|
|
|
|
{
|
|
|
|
cache_.name_collated = fmt::format("{}\t{}", cache_.name.lowercase(), tr_torrentView(raw_torrent_).hash_string);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Torrent::Impl::notify_property_changes(ChangeFlags changes) const
|
|
|
|
{
|
|
|
|
// Updating the model triggers off resort/refresh, so don't notify unless something's actually changed
|
|
|
|
if (changes.none())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
gtr_object_notify_emit(torrent_);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Torrent::Impl::get_value(int column, Glib::ValueBase& value) const
|
|
|
|
{
|
|
|
|
static auto const& columns = get_columns();
|
|
|
|
|
|
|
|
if (column == columns.self.index())
|
|
|
|
{
|
|
|
|
column_value_cast(value, columns.self).set(&torrent_);
|
|
|
|
}
|
|
|
|
else if (column == columns.name_collated.index())
|
|
|
|
{
|
|
|
|
column_value_cast(value, columns.name_collated).set(cache_.name_collated);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::RefPtr<Gio::Icon> Torrent::Impl::get_icon() const
|
|
|
|
{
|
|
|
|
return gtr_get_mime_type_icon(cache_.mime_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring Torrent::Impl::get_short_status_text() const
|
|
|
|
{
|
|
|
|
switch (cache_.activity)
|
|
|
|
{
|
|
|
|
case TR_STATUS_STOPPED:
|
|
|
|
return cache_.finished ? _("Finished") : _("Paused");
|
|
|
|
|
|
|
|
case TR_STATUS_CHECK_WAIT:
|
|
|
|
return _("Queued for verification");
|
|
|
|
|
|
|
|
case TR_STATUS_DOWNLOAD_WAIT:
|
|
|
|
return _("Queued for download");
|
|
|
|
|
|
|
|
case TR_STATUS_SEED_WAIT:
|
|
|
|
return _("Queued for seeding");
|
|
|
|
|
|
|
|
case TR_STATUS_CHECK:
|
|
|
|
return fmt::format(
|
|
|
|
// xgettext:no-c-format
|
|
|
|
_("Verifying local data ({percent_done}% tested)"),
|
2023-02-18 01:14:01 +00:00
|
|
|
fmt::arg("percent_done", cache_.recheck_progress.to_string()));
|
2022-12-21 21:26:25 +00:00
|
|
|
|
|
|
|
case TR_STATUS_DOWNLOAD:
|
|
|
|
case TR_STATUS_SEED:
|
|
|
|
return fmt::format(
|
|
|
|
FMT_STRING("{:s} {:s}"),
|
|
|
|
get_short_transfer_text(),
|
|
|
|
fmt::format(_("Ratio: {ratio}"), fmt::arg("ratio", tr_strlratio(cache_.ratio))));
|
|
|
|
|
|
|
|
default:
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring Torrent::Impl::get_long_progress_text() const
|
|
|
|
{
|
|
|
|
Glib::ustring gstr;
|
|
|
|
|
|
|
|
bool const isDone = cache_.left_until_done == 0;
|
|
|
|
auto const haveTotal = cache_.have_unchecked + cache_.have_valid;
|
|
|
|
bool const isSeed = cache_.have_valid >= cache_.total_size;
|
|
|
|
|
|
|
|
if (!isDone) // downloading
|
|
|
|
{
|
|
|
|
// 50 MB of 200 MB (25%)
|
|
|
|
gstr += fmt::format(
|
|
|
|
_("{current_size} of {complete_size} ({percent_done}%)"),
|
|
|
|
fmt::arg("current_size", tr_strlsize(haveTotal)),
|
|
|
|
fmt::arg("complete_size", tr_strlsize(cache_.size_when_done)),
|
2023-02-18 01:14:01 +00:00
|
|
|
fmt::arg("percent_done", cache_.percent_done.to_string()));
|
2022-12-21 21:26:25 +00:00
|
|
|
}
|
|
|
|
else if (!isSeed && cache_.has_seed_ratio) // partial seed, seed ratio
|
|
|
|
{
|
|
|
|
// 50 MB of 200 MB (25%), uploaded 30 MB (Ratio: X%, Goal: Y%)
|
|
|
|
gstr += fmt::format(
|
|
|
|
// xgettext:no-c-format
|
|
|
|
_("{current_size} of {complete_size} ({percent_complete}%), uploaded {uploaded_size} (Ratio: {ratio}, Goal: {seed_ratio})"),
|
|
|
|
fmt::arg("current_size", tr_strlsize(haveTotal)),
|
|
|
|
fmt::arg("complete_size", tr_strlsize(cache_.total_size)),
|
2023-02-18 01:14:01 +00:00
|
|
|
fmt::arg("percent_complete", cache_.percent_complete.to_string()),
|
2022-12-21 21:26:25 +00:00
|
|
|
fmt::arg("uploaded_size", tr_strlsize(cache_.uploaded_ever)),
|
|
|
|
fmt::arg("ratio", tr_strlratio(cache_.ratio)),
|
|
|
|
fmt::arg("seed_ratio", tr_strlratio(cache_.seed_ratio)));
|
|
|
|
}
|
|
|
|
else if (!isSeed) // partial seed, no seed ratio
|
|
|
|
{
|
|
|
|
gstr += fmt::format(
|
|
|
|
// xgettext:no-c-format
|
|
|
|
_("{current_size} of {complete_size} ({percent_complete}%), uploaded {uploaded_size} (Ratio: {ratio})"),
|
|
|
|
fmt::arg("current_size", tr_strlsize(haveTotal)),
|
|
|
|
fmt::arg("complete_size", tr_strlsize(cache_.total_size)),
|
2023-02-18 01:14:01 +00:00
|
|
|
fmt::arg("percent_complete", cache_.percent_complete.to_string()),
|
2022-12-21 21:26:25 +00:00
|
|
|
fmt::arg("uploaded_size", tr_strlsize(cache_.uploaded_ever)),
|
|
|
|
fmt::arg("ratio", tr_strlratio(cache_.ratio)));
|
|
|
|
}
|
|
|
|
else if (cache_.has_seed_ratio) // seed, seed ratio
|
|
|
|
{
|
|
|
|
gstr += fmt::format(
|
|
|
|
_("{complete_size}, uploaded {uploaded_size} (Ratio: {ratio}, Goal: {seed_ratio})"),
|
|
|
|
fmt::arg("complete_size", tr_strlsize(cache_.total_size)),
|
|
|
|
fmt::arg("uploaded_size", tr_strlsize(cache_.uploaded_ever)),
|
|
|
|
fmt::arg("ratio", tr_strlratio(cache_.ratio)),
|
|
|
|
fmt::arg("seed_ratio", tr_strlratio(cache_.seed_ratio)));
|
|
|
|
}
|
|
|
|
else // seed, no seed ratio
|
|
|
|
{
|
|
|
|
gstr += fmt::format(
|
|
|
|
_("{complete_size}, uploaded {uploaded_size} (Ratio: {ratio})"),
|
|
|
|
fmt::arg("complete_size", tr_strlsize(cache_.total_size)),
|
|
|
|
fmt::arg("uploaded_size", tr_strlsize(cache_.uploaded_ever)),
|
|
|
|
fmt::arg("ratio", tr_strlratio(cache_.ratio)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// add time remaining when applicable
|
|
|
|
if (cache_.activity == TR_STATUS_DOWNLOAD || (cache_.has_seed_ratio && cache_.activity == TR_STATUS_SEED))
|
|
|
|
{
|
|
|
|
gstr += " - ";
|
|
|
|
|
|
|
|
if (cache_.eta < 0)
|
|
|
|
{
|
|
|
|
gstr += _("Remaining time unknown");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gstr += tr_format_time_left(cache_.eta);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return gstr;
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring Torrent::Impl::get_long_status_text() const
|
|
|
|
{
|
|
|
|
auto status_str = get_error_text();
|
|
|
|
if (status_str.empty())
|
|
|
|
{
|
|
|
|
status_str = get_activity_text();
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (cache_.activity)
|
|
|
|
{
|
|
|
|
case TR_STATUS_CHECK_WAIT:
|
|
|
|
case TR_STATUS_CHECK:
|
|
|
|
case TR_STATUS_DOWNLOAD_WAIT:
|
|
|
|
case TR_STATUS_SEED_WAIT:
|
|
|
|
case TR_STATUS_STOPPED:
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
if (auto const buf = get_short_transfer_text(); !std::empty(buf))
|
|
|
|
{
|
|
|
|
status_str += fmt::format(FMT_STRING(" - {:s}"), buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return status_str;
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring Torrent::Impl::get_short_transfer_text() const
|
|
|
|
{
|
|
|
|
if (cache_.has_metadata && cache_.active_peers_down > 0)
|
|
|
|
{
|
|
|
|
return fmt::format(
|
|
|
|
_("{download_speed} ▼ {upload_speed} ▲"),
|
|
|
|
fmt::arg("upload_speed", tr_formatter_speed_KBps(cache_.speed_up)),
|
|
|
|
fmt::arg("download_speed", tr_formatter_speed_KBps(cache_.speed_down)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cache_.has_metadata && cache_.active_peers_up > 0)
|
|
|
|
{
|
|
|
|
return fmt::format(_("{upload_speed} ▲"), fmt::arg("upload_speed", tr_formatter_speed_KBps(cache_.speed_up)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cache_.stalled)
|
|
|
|
{
|
|
|
|
return _("Stalled");
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring Torrent::Impl::get_error_text() const
|
|
|
|
{
|
|
|
|
switch (cache_.error_code)
|
|
|
|
{
|
|
|
|
case TR_STAT_TRACKER_WARNING:
|
|
|
|
return fmt::format(_("Tracker warning: '{warning}'"), fmt::arg("warning", cache_.error_message));
|
|
|
|
|
|
|
|
case TR_STAT_TRACKER_ERROR:
|
|
|
|
return fmt::format(_("Tracker Error: '{error}'"), fmt::arg("error", cache_.error_message));
|
|
|
|
|
|
|
|
case TR_STAT_LOCAL_ERROR:
|
|
|
|
return fmt::format(_("Local error: '{error}'"), fmt::arg("error", cache_.error_message));
|
|
|
|
|
|
|
|
default:
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring Torrent::Impl::get_activity_text() const
|
|
|
|
{
|
|
|
|
switch (cache_.activity)
|
|
|
|
{
|
|
|
|
case TR_STATUS_STOPPED:
|
|
|
|
case TR_STATUS_CHECK_WAIT:
|
|
|
|
case TR_STATUS_CHECK:
|
|
|
|
case TR_STATUS_DOWNLOAD_WAIT:
|
|
|
|
case TR_STATUS_SEED_WAIT:
|
|
|
|
return get_short_status_text();
|
|
|
|
|
|
|
|
case TR_STATUS_DOWNLOAD:
|
|
|
|
if (!cache_.has_metadata)
|
|
|
|
{
|
|
|
|
return fmt::format(
|
|
|
|
ngettext(
|
|
|
|
// xgettext:no-c-format
|
|
|
|
"Downloading metadata from {active_count} connected peer ({percent_done}% done)",
|
|
|
|
"Downloading metadata from {active_count} connected peers ({percent_done}% done)",
|
|
|
|
cache_.peers_connected),
|
|
|
|
fmt::arg("active_count", cache_.peers_connected),
|
2023-02-18 01:14:01 +00:00
|
|
|
fmt::arg("percent_done", cache_.metadata_percent_complete.to_string()));
|
2022-12-21 21:26:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (cache_.peers_sending_to_us != 0 && cache_.webseeds_sending_to_us != 0)
|
|
|
|
{
|
|
|
|
return fmt::format(
|
|
|
|
ngettext(
|
|
|
|
"Downloading from {active_count} of {connected_count} connected peer and webseed",
|
|
|
|
"Downloading from {active_count} of {connected_count} connected peers and webseeds",
|
|
|
|
cache_.peers_connected + cache_.webseeds_sending_to_us),
|
|
|
|
fmt::arg("active_count", cache_.peers_sending_to_us + cache_.webseeds_sending_to_us),
|
|
|
|
fmt::arg("connected_count", cache_.peers_connected + cache_.webseeds_sending_to_us));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cache_.webseeds_sending_to_us != 0)
|
|
|
|
{
|
|
|
|
return fmt::format(
|
|
|
|
ngettext(
|
|
|
|
"Downloading from {active_count} webseed",
|
|
|
|
"Downloading from {active_count} webseeds",
|
|
|
|
cache_.webseeds_sending_to_us),
|
|
|
|
fmt::arg("active_count", cache_.webseeds_sending_to_us));
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt::format(
|
|
|
|
ngettext(
|
|
|
|
"Downloading from {active_count} of {connected_count} connected peer",
|
|
|
|
"Downloading from {active_count} of {connected_count} connected peers",
|
|
|
|
cache_.peers_connected),
|
|
|
|
fmt::arg("active_count", cache_.peers_sending_to_us),
|
|
|
|
fmt::arg("connected_count", cache_.peers_connected));
|
|
|
|
|
|
|
|
case TR_STATUS_SEED:
|
|
|
|
return fmt::format(
|
|
|
|
ngettext(
|
|
|
|
"Seeding to {active_count} of {connected_count} connected peer",
|
|
|
|
"Seeding to {active_count} of {connected_count} connected peers",
|
|
|
|
cache_.peers_connected),
|
|
|
|
fmt::arg("active_count", cache_.peers_getting_from_us),
|
|
|
|
fmt::arg("connected_count", cache_.peers_connected));
|
|
|
|
|
|
|
|
default:
|
|
|
|
g_assert_not_reached();
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Torrent::Torrent()
|
|
|
|
: Glib::ObjectBase(typeid(Torrent))
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Torrent::Torrent(tr_torrent* torrent)
|
|
|
|
: Glib::ObjectBase(typeid(Torrent))
|
|
|
|
, impl_(std::make_unique<Impl>(*this, torrent))
|
|
|
|
{
|
|
|
|
g_assert(torrent != nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring const& Torrent::get_name_collated() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().name_collated;
|
|
|
|
}
|
|
|
|
|
|
|
|
tr_torrent_id_t Torrent::get_id() const
|
|
|
|
{
|
|
|
|
return tr_torrentId(impl_->get_raw_torrent());
|
|
|
|
}
|
|
|
|
|
|
|
|
tr_torrent& Torrent::get_underlying() const
|
|
|
|
{
|
|
|
|
return *impl_->get_raw_torrent();
|
|
|
|
}
|
|
|
|
|
|
|
|
float Torrent::get_speed_up() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().speed_up;
|
|
|
|
}
|
|
|
|
|
|
|
|
float Torrent::get_speed_down() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().speed_down;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Torrent::get_active_peers_up() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().active_peers_up;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Torrent::get_active_peers_down() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().active_peers_down;
|
|
|
|
}
|
|
|
|
|
2023-02-18 01:14:01 +00:00
|
|
|
Percents Torrent::get_recheck_progress() const
|
2022-12-21 21:26:25 +00:00
|
|
|
{
|
|
|
|
return impl_->get_cache().recheck_progress;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Torrent::get_active() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().active;
|
|
|
|
}
|
|
|
|
|
|
|
|
tr_torrent_activity Torrent::get_activity() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().activity;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Torrent::get_finished() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().finished;
|
|
|
|
}
|
|
|
|
|
|
|
|
tr_priority_t Torrent::get_priority() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().priority;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t Torrent::get_queue_position() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().queue_position;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int Torrent::get_trackers() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().trackers;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Torrent::get_error_code() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().error_code;
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring const& Torrent::get_error_message() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().error_message;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Torrent::get_active_peer_count() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().active_peer_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t Torrent::get_total_size() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().total_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
float Torrent::get_ratio() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().ratio;
|
|
|
|
}
|
|
|
|
|
|
|
|
time_t Torrent::get_eta() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().eta;
|
|
|
|
}
|
|
|
|
|
|
|
|
time_t Torrent::get_added_date() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().added_date;
|
|
|
|
}
|
|
|
|
|
2023-02-18 01:14:01 +00:00
|
|
|
Percents Torrent::get_percent_complete() const
|
2022-12-21 21:26:25 +00:00
|
|
|
{
|
|
|
|
return impl_->get_cache().percent_complete;
|
|
|
|
}
|
|
|
|
|
2023-02-18 01:14:01 +00:00
|
|
|
Percents Torrent::get_seed_ratio_percent_done() const
|
2022-12-21 21:26:25 +00:00
|
|
|
{
|
|
|
|
return impl_->get_cache().seed_ratio_percent_done;
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::RefPtr<Gio::Icon> Torrent::get_icon() const
|
|
|
|
{
|
|
|
|
return impl_->get_icon();
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring Torrent::get_name() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().name;
|
|
|
|
}
|
|
|
|
|
2023-02-18 01:14:01 +00:00
|
|
|
Percents Torrent::get_percent_done() const
|
2022-12-21 21:26:25 +00:00
|
|
|
{
|
|
|
|
return impl_->get_cache().activity_percent_done;
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring Torrent::get_short_status_text() const
|
|
|
|
{
|
|
|
|
return impl_->get_short_status_text();
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring Torrent::get_long_progress_text() const
|
|
|
|
{
|
|
|
|
return impl_->get_long_progress_text();
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::ustring Torrent::get_long_status_text() const
|
|
|
|
{
|
|
|
|
return impl_->get_long_status_text();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Torrent::get_sensitive() const
|
|
|
|
{
|
|
|
|
return impl_->get_cache().activity != TR_STATUS_STOPPED;
|
|
|
|
}
|
|
|
|
|
|
|
|
Torrent::ChangeFlags Torrent::update()
|
|
|
|
{
|
|
|
|
auto result = impl_->update_cache();
|
|
|
|
impl_->notify_property_changes(result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
Glib::RefPtr<Torrent> Torrent::create(tr_torrent* torrent)
|
|
|
|
{
|
2022-12-28 14:47:53 +00:00
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
|
2022-12-21 21:26:25 +00:00
|
|
|
return Glib::make_refptr_for_instance(new Torrent(torrent));
|
|
|
|
}
|
|
|
|
|
|
|
|
Torrent::Columns const& Torrent::get_columns()
|
|
|
|
{
|
|
|
|
static Columns const columns;
|
|
|
|
return columns;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Torrent::get_item_id(Glib::RefPtr<Glib::ObjectBase const> const& item)
|
|
|
|
{
|
|
|
|
if (auto const torrent = gtr_ptr_dynamic_cast<Torrent const>(item); torrent != nullptr)
|
|
|
|
{
|
|
|
|
return torrent->get_id();
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Torrent::get_item_value(Glib::RefPtr<Glib::ObjectBase const> const& item, int column, Glib::ValueBase& value)
|
|
|
|
{
|
|
|
|
if (auto const torrent = gtr_ptr_dynamic_cast<Torrent const>(item); torrent != nullptr)
|
|
|
|
{
|
|
|
|
torrent->impl_->get_value(column, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int Torrent::compare_by_id(Glib::RefPtr<Torrent const> const& lhs, Glib::RefPtr<Torrent const> const& rhs)
|
|
|
|
{
|
|
|
|
return gtr_compare_generic(lhs->get_id(), rhs->get_id());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Torrent::less_by_id(Glib::RefPtr<Torrent const> const& lhs, Glib::RefPtr<Torrent const> const& rhs)
|
|
|
|
{
|
|
|
|
return lhs->get_id() < rhs->get_id();
|
|
|
|
}
|