Squashed commit of the following:

commit 4b9b9920bf
Author: Charles Kerr <charles@charleskerr.com>
Date:   Mon Jun 19 16:51:34 2023 -0500

    refactor: prefer std::map over QMap in transmission-qt (#5641)

    * refactor: use std::map instead of QMap in PrefsDialog.cc

    * refactor: use std::map instead of QMap in DetailsDialog.cc

    * refactor: use std::map instead of QMap in OptionsDialog.cc

    * refactor: use std::map instead of QMap in FileTreeModel.cc

commit 040bc8a1ce
Author: Dmitry Antipov <dmantipov@yandex.ru>
Date:   Tue Jun 20 00:22:54 2023 +0300

    fix: Qt 6.5 deprecation warning (#5552)

commit 23a52fa1c5
Author: LaserEyess <16581533+LaserEyess@users.noreply.github.com>
Date:   Mon Jun 19 16:51:20 2023 -0400

    fixup: dedup tr_rpc_address with tr_address (#5523)

    * fixup: dedup tr_rpc_address with tr_address

    tr_rpc_address was duplicating a lot of work that tr_address was doing a
    lot better. Fall back to using tr_address for ipv4/ipv6 connections and
    make a tr_unix_addr for handling unix sockets.

    * remove unnecessary functions

commit ddac05954b
Author: Василий Чай <basilefff@gmail.com>
Date:   Mon Jun 19 09:30:55 2023 +0400

    fix: return error when renaming into existing file (#5563)

commit b8ff35c4ce
Author: tearfur <46261767+tearfur@users.noreply.github.com>
Date:   Mon Jun 19 08:06:31 2023 +0800

    handle IPv6 NAT during LTEP handshake (#5565)

    * fix: peer handshake reported wrong ipv6 address

    https://github.com/transmission/transmission/issues/5542#issuecomment-1556710922

commit fd583ac878
Author: Charles Kerr <charles@charleskerr.com>
Date:   Sun Jun 18 17:36:39 2023 -0500

    deps: bump libfmt to v10.0.0 (#5635)

    seems to be semver/minor safe for our API use

    Fixes #5511.

    Possibly fixes #5627.

commit 1664088ba5
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Sun Jun 18 15:55:43 2023 -0500

    chore: update generated transmission-web files (#5588)

commit 0fd7989b18
Author: Gary Elshaw <69029666+GaryElshaw@users.noreply.github.com>
Date:   Mon Jun 19 07:11:56 2023 +1200

    fix: restore png files that were corrupted in recent "compression" script

commit bd9d110d45
Author: Cœur <coeur@gmx.fr>
Date:   Sun Jun 18 20:32:34 2023 +0200

    fix 'setNeedsDisplay' is deprecated: first deprecated in macOS 10.14 (#5633)

commit 76166d8fa7
Author: Cœur <coeur@gmx.fr>
Date:   Mon Jun 12 18:03:22 2023 +0200

    refactor: replace NSMutableDictionary with constant attributes (#5221)

commit c379cd727f
Author: Robin Seth Ekman <robin.seth.ekman@gmail.com>
Date:   Mon Jun 12 03:19:54 2023 +0200

    fix: transmission-remote only list every other label (fixes #5571) (#5572)

commit 87f254ae90
Author: Charles Kerr <charles@charleskerr.com>
Date:   Sun Jun 11 18:52:45 2023 -0500

    chore: bump fast_float snaapshot to 5.2.0 (#5605)

commit 802619e174
Author: tearfur <46261767+tearfur@users.noreply.github.com>
Date:   Mon Jun 12 07:27:01 2023 +0800

    fix: fixes and improvements to global IP query (#5510)

commit c8e84f870b
Author: Charles Kerr <charles@charleskerr.com>
Date:   Sun Jun 11 16:28:43 2023 -0500

    ci: remove "brew update" step (#5606)

commit 60c68afdde
Author: Charles Kerr <charles@charleskerr.com>
Date:   Sun Jun 11 11:25:35 2023 -0500

    Revert "fix: some labels not displayed in transmission-remote (#5600)"

    This reverts commit ebd5080a93.

commit ebd5080a93
Author: Charles Kerr <charles@charleskerr.com>
Date:   Sat Jun 10 19:01:32 2023 -0500

    fix: some labels not displayed in transmission-remote (#5600)

    Fixes #5571.

commit 8ca02b8f28
Author: Mike Gelfand <mikedld@users.noreply.github.com>
Date:   Sun Jun 11 02:09:18 2023 +0400

    Avoid locale use in `tr_truncd()` (#5587)

    `tr_parseNum<>()` is implemented with `fast_float::from_chars()`, and
    the latter is documented as "expecting a locale-indepent format
    equivalent to what is used by std::strtod in the default ("C") locale".
    To accomodate locale independent number parsing, switch back to
    locale-independent number formatting in `tr_truncd()` by both removing a
    `L` format specifier from `fmt::format_to_n()` call and using a fixed
    "." decimal separator when truncating the value.

commit 0ef58c2a20
Author: Charles Kerr <charles@charleskerr.com>
Date:   Mon Jun 5 17:03:11 2023 -0500

    chore: improve lossless compression of png files (#5586)

commit c1c27f3da0
Author: Daniel Kamil Kozar <dkk089@gmail.com>
Date:   Mon Jun 5 20:15:32 2023 +0200

    Expose files' begin and end pieces via RPC (#5578)

    * Expose files' begin and end pieces via RPC

    This adds two arguments, `beginPiece` and `endPiece`, for each of the entries
    in the `files` array of the RPC's `torrent-`get` method.

    The point is to allow RPC clients to display a file's completion progress as
    piece-based in addition to byte-based.

commit 61679e1adc
Author: Cœur <coeur@gmx.fr>
Date:   Mon May 29 15:43:43 2023 +0200

    Support localized punctuation for "Port:" (#4452)

    * Support localized punctuation for "Port:"

    * Code review: adopting "Port: %@"

    * updating other locales for "Port" -> "Port: %@"
This commit is contained in:
Charles Kerr
2023-06-19 18:48:08 -05:00
parent a91372ef7d
commit 3519acfc0b
157 changed files with 731 additions and 518 deletions

View File

@@ -239,7 +239,6 @@ jobs:
sw_vers
- name: Get Dependencies
run: |
brew update
brew install cmake gettext libdeflate libevent libpsl miniupnpc ninja node pkg-config
- name: Get Dependencies (GTK)
if: ${{ needs.what-to-make.outputs.make-gtk == 'true' }}
@@ -513,7 +512,6 @@ jobs:
sw_vers
- name: Get Dependencies
run: |
brew update
brew install cmake gettext ninja node pkg-config
- name: Get Dependencies (GTK)
if: ${{ needs.what-to-make.outputs.make-gtk == 'true' }}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -294,6 +294,8 @@ The 'source' column here corresponds to the data structure there.
| `bytesCompleted` | number | tr_file_view
| `length` | number | tr_file_view
| `name` | string | tr_file_view
| `beginPiece` | number | tr_file_view
| `endPiece` | number | tr_file_view
`fileStats`: a file's non-constant properties. An array of `tr_info.filecount` objects, each containing:
@@ -1009,3 +1011,5 @@ Transmission 4.1.0 (`rpc-version-semver` 5.4.0, `rpc-version`: 18)
|:---|:---
| `torrent-get` | new arg `sequentialDownload`
| `torrent-set` | new arg `sequentialDownload`
| `torrent-get` | new arg `files.beginPiece`
| `torrent-get` | new arg `files.endPiece`

View File

@@ -395,7 +395,7 @@ void register_magnet_link_handler()
_("Couldn't register Transmission as a {content_type} handler: {error} ({error_code})"),
fmt::arg("content_type", content_type),
fmt::arg("error", e.what()),
fmt::arg("error_code", e.code())));
fmt::arg("error_code", static_cast<int>(e.code()))));
}
}

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -556,7 +556,7 @@ tr_sys_file_t tr_sys_file_get_std(tr_std_sys_file_t std_file, tr_error** error)
break;
default:
TR_ASSERT_MSG(false, fmt::format(FMT_STRING("unknown standard file {:d}"), std_file));
TR_ASSERT_MSG(false, fmt::format(FMT_STRING("unknown standard file {:d}"), static_cast<int>(std_file)));
tr_error_set_from_errno(error, EINVAL);
}

View File

@@ -28,7 +28,25 @@ namespace
using namespace std::literals;
auto constexpr IPQueryServices = std::array{ "https://icanhazip.com"sv, "https://api64.ipify.org"sv };
static_assert(TR_AF_INET == 0);
static_assert(TR_AF_INET6 == 1);
auto constexpr protocol_str(tr_address_type type) noexcept
{
/* TODO: very slight performance nit
* - If upgrading to C++20, change Map to a consteval lambda:
* auto map = []() consteval { return std::array{ "IPv4"sv, "IPv6"sv }; };
* - If upgrading to C++23, change Map to static constexpr
*
* Ref: https://wg21.link/p2647r1
*/
auto constexpr Map = std::array{ "IPv4"sv, "IPv6"sv };
return Map[type];
}
auto constexpr IPQueryServices = std::array{ std::array{ "https://ip4.transmissionbt.com/"sv },
std::array{ "https://ip6.transmissionbt.com/"sv } };
auto constexpr UpkeepInterval = 30min;
auto constexpr RetryUpkeepInterval = 30s;
@@ -36,11 +54,11 @@ auto constexpr RetryUpkeepInterval = 30s;
namespace
{
// Functions contained in external_source_ip_helpers are modified from code
// Functions contained in global_source_ip_helpers are modified from code
// by Juliusz Chroboczek and is covered under the same license as dht.cc.
// Please feel free to copy them into your software if it can help
// unbreaking the double-stack Internet.
namespace external_source_ip_helpers
namespace global_source_ip_helpers
{
// Get the source address used for a given destination address.
@@ -109,12 +127,12 @@ namespace external_source_ip_helpers
return {};
}
} // namespace external_source_ip_helpers
} // namespace global_source_ip_helpers
} // namespace
tr_global_ip_cache::tr_global_ip_cache(tr_web& web_in, libtransmission::TimerMaker& timer_maker_in)
: web_{ web_in }
, upkeep_timers_{ timer_maker_in.create(), timer_maker_in.create() }
tr_global_ip_cache::tr_global_ip_cache(Mediator& mediator_in)
: mediator_{ mediator_in }
, upkeep_timers_{ mediator_in.timer_maker().create(), mediator_in.timer_maker().create() }
{
static_assert(TR_AF_INET == 0);
static_assert(TR_AF_INET6 == 1);
@@ -132,6 +150,11 @@ tr_global_ip_cache::tr_global_ip_cache(tr_web& web_in, libtransmission::TimerMak
}
}
std::unique_ptr<tr_global_ip_cache> tr_global_ip_cache::create(tr_global_ip_cache::Mediator& mediator_in)
{
return std::unique_ptr<tr_global_ip_cache>(new tr_global_ip_cache(mediator_in));
}
tr_global_ip_cache::~tr_global_ip_cache()
{
// Destroying mutex while someone owns it is undefined behaviour, so we acquire it first
@@ -164,37 +187,21 @@ bool tr_global_ip_cache::try_shutdown() noexcept
return true;
}
void tr_global_ip_cache::set_settings_bind_addr(tr_address_type type, std::string_view bind_address) noexcept
{
settings_bind_addr_[type] = tr_address::from_string(bind_address);
if (settings_bind_addr_[type] && type != settings_bind_addr_[type]->type)
{
settings_bind_addr_[type].reset();
}
update_addr(type);
}
tr_address tr_global_ip_cache::bind_addr(tr_address_type type) const noexcept
{
if (type == TR_AF_INET || type == TR_AF_INET6)
{
return settings_bind_addr_[type].value_or(type == TR_AF_INET ? tr_address::any_ipv4() : tr_address::any_ipv6());
if (auto const addr = tr_address::from_string(mediator_.settings_bind_addr(type)); addr && type == addr->type)
{
return *addr;
}
return type == TR_AF_INET ? tr_address::any_ipv4() : tr_address::any_ipv6();
}
TR_ASSERT_MSG(false, "invalid type");
return {};
}
void tr_global_ip_cache::update_addr(tr_address_type type) noexcept
{
update_source_addr(type);
/* TODO: Temporarily disable because there is currently no way for this to work without using third party services */
// if (global_source_addr(type))
// {
// update_global_addr(type);
// }
}
bool tr_global_ip_cache::set_global_addr(tr_address_type type, tr_address const& addr) noexcept
{
if (type == addr.type && addr.is_global_unicast_address())
@@ -207,11 +214,125 @@ bool tr_global_ip_cache::set_global_addr(tr_address_type type, tr_address const&
return false;
}
void tr_global_ip_cache::update_addr(tr_address_type type) noexcept
{
update_source_addr(type);
if (global_source_addr(type))
{
update_global_addr(type);
}
}
void tr_global_ip_cache::update_global_addr(tr_address_type type) noexcept
{
TR_ASSERT(has_ip_protocol_[type]);
TR_ASSERT(ix_service_[type] < std::size(IPQueryServices[type]));
if (ix_service_[type] == 0U && !set_is_updating(type))
{
return;
}
TR_ASSERT(is_updating_[type] == is_updating_t::YES);
// Update global address
auto options = tr_web::FetchOptions{ IPQueryServices[type][ix_service_[type]],
[this, type](tr_web::FetchResponse const& response)
{ this->on_response_ip_query(type, response); },
nullptr };
options.ip_proto = type == TR_AF_INET ? tr_web::FetchOptions::IPProtocol::V4 : tr_web::FetchOptions::IPProtocol::V6;
options.sndbuf = 4096;
options.rcvbuf = 4096;
mediator_.fetch(std::move(options));
}
void tr_global_ip_cache::update_source_addr(tr_address_type type) noexcept
{
using namespace global_source_ip_helpers;
TR_ASSERT(has_ip_protocol_[type]);
if (!set_is_updating(type))
{
return;
}
TR_ASSERT(is_updating_[type] == is_updating_t::YES);
auto const protocol = protocol_str(type);
auto err = int{ 0 };
auto const& source_addr = get_global_source_address(bind_addr(type), err);
if (source_addr)
{
set_source_addr(*source_addr);
tr_logAddInfo(fmt::format(
_("Successfully updated source {protocol} address to {ip}"),
fmt::arg("protocol", protocol),
fmt::arg("ip", source_addr->display_name())));
}
else
{
// Stop the update process since we have no public internet connectivity
unset_addr(type);
upkeep_timers_[type]->set_interval(RetryUpkeepInterval);
tr_logAddDebug(fmt::format("Couldn't obtain source {} address", protocol));
if (err == EAFNOSUPPORT)
{
stop_timer(type); // No point in retrying
has_ip_protocol_[type] = false;
tr_logAddInfo(fmt::format(_("Your machine does not support {protocol}"), fmt::arg("protocol", protocol)));
}
}
unset_is_updating(type);
}
void tr_global_ip_cache::on_response_ip_query(tr_address_type type, tr_web::FetchResponse const& response) noexcept
{
TR_ASSERT(is_updating_[type] == is_updating_t::YES);
TR_ASSERT(ix_service_[type] < std::size(IPQueryServices[type]));
auto const protocol = protocol_str(type);
auto success = false;
if (response.status == 200 /* HTTP_OK */)
{
// Update member
if (auto addr = tr_address::from_string(tr_strvStrip(response.body)); addr && set_global_addr(type, *addr))
{
success = true;
upkeep_timers_[type]->set_interval(UpkeepInterval);
tr_logAddInfo(fmt::format(
_("Successfully updated global {type} address to {ip} using {url}"),
fmt::arg("type", protocol),
fmt::arg("ip", addr->display_name()),
fmt::arg("url", IPQueryServices[type][ix_service_[type]])));
}
}
// Try next IP query URL
if (!success && ++ix_service_[type] < std::size(IPQueryServices[type]))
{
update_global_addr(type);
return;
}
if (!success)
{
tr_logAddDebug(fmt::format("Couldn't obtain global {} address", protocol));
unset_global_addr(type);
upkeep_timers_[type]->set_interval(RetryUpkeepInterval);
}
ix_service_[type] = 0U;
unset_is_updating(type);
}
void tr_global_ip_cache::unset_global_addr(tr_address_type type) noexcept
{
auto const lock = std::lock_guard{ global_addr_mutex_[type] };
global_addr_[type].reset();
tr_logAddTrace(fmt::format("Unset {} global address cache", type == TR_AF_INET ? "IPv4"sv : "IPv6"sv));
tr_logAddTrace(fmt::format("Unset {} global address cache", protocol_str(type)));
}
void tr_global_ip_cache::set_source_addr(tr_address const& addr) noexcept
@@ -225,7 +346,7 @@ void tr_global_ip_cache::unset_addr(tr_address_type type) noexcept
{
auto const lock = std::lock_guard{ source_addr_mutex_[type] };
source_addr_[type].reset();
tr_logAddTrace(fmt::format("Unset {} source address cache", type == TR_AF_INET ? "IPv4"sv : "IPv6"sv));
tr_logAddTrace(fmt::format("Unset {} source address cache", protocol_str(type)));
// No public internet connectivity means no global IP address
unset_global_addr(type);
@@ -255,109 +376,3 @@ void tr_global_ip_cache::unset_is_updating(tr_address_type type) noexcept
lock.unlock();
is_updating_cv_[type].notify_one();
}
void tr_global_ip_cache::update_global_addr(tr_address_type type) noexcept
{
TR_ASSERT(has_ip_protocol_[type]);
TR_ASSERT(global_source_addr(type));
TR_ASSERT(ix_service_[type] < std::size(IPQueryServices));
if (ix_service_[type] == 0U && !set_is_updating(type))
{
return;
}
TR_ASSERT(is_updating_[type] == is_updating_t::YES);
// Update global address
auto options = tr_web::FetchOptions{ IPQueryServices[ix_service_[type]],
[this, type](tr_web::FetchResponse const& response)
{ this->on_response_ip_query(type, response); },
nullptr };
options.ip_proto = type == TR_AF_INET ? tr_web::FetchOptions::IPProtocol::V4 : tr_web::FetchOptions::IPProtocol::V6;
options.sndbuf = 4096;
options.rcvbuf = 4096;
web_.fetch(std::move(options));
}
void tr_global_ip_cache::update_source_addr(tr_address_type type) noexcept
{
using namespace external_source_ip_helpers;
TR_ASSERT(has_ip_protocol_[type]);
if (!set_is_updating(type))
{
return;
}
TR_ASSERT(is_updating_[type] == is_updating_t::YES);
auto const protocol = type == TR_AF_INET ? "IPv4"sv : "IPv6"sv;
auto err = int{};
auto const& source_addr = get_global_source_address(bind_addr(type), err);
if (source_addr)
{
set_source_addr(*source_addr);
tr_logAddInfo(fmt::format(
_("Successfully updated source {protocol} address to {ip}"),
fmt::arg("protocol", protocol),
fmt::arg("ip", source_addr->display_name())));
}
else
{
// Stop the update process since we have no public internet connectivity
unset_addr(type);
upkeep_timers_[type]->set_interval(RetryUpkeepInterval);
tr_logAddDebug(fmt::format(_("Couldn't obtain source {protocol} address"), fmt::arg("protocol", protocol)));
if (err == EAFNOSUPPORT)
{
stop_timer(type); // No point in retrying
has_ip_protocol_[type] = false;
tr_logAddInfo(fmt::format(_("Your machine does not support {protocol}"), fmt::arg("protocol", protocol)));
}
}
unset_is_updating(type);
}
void tr_global_ip_cache::on_response_ip_query(tr_address_type type, tr_web::FetchResponse const& response) noexcept
{
TR_ASSERT(is_updating_[type] == is_updating_t::YES);
TR_ASSERT(ix_service_[type] < std::size(IPQueryServices));
auto const protocol = type == TR_AF_INET ? "IPv4"sv : "IPv6"sv;
auto success = bool{ false };
if (response.status == 200 /* HTTP_OK */)
{
// Update member
if (auto addr = tr_address::from_string(tr_strvStrip(response.body)); addr && set_global_addr(type, *addr))
{
success = true;
upkeep_timers_[type]->set_interval(UpkeepInterval);
tr_logAddInfo(fmt::format(
_("Successfully updated global {type} address to {ip} using {url}"),
fmt::arg("type", protocol),
fmt::arg("ip", addr->display_name()),
fmt::arg("url", IPQueryServices[ix_service_[type]])));
}
}
// Try next IP query URL
if (!success && ++ix_service_[type] < std::size(IPQueryServices))
{
update_global_addr(type);
return;
}
if (!success)
{
tr_logAddDebug(fmt::format("Couldn't obtain global {} address", protocol));
unset_global_addr(type);
upkeep_timers_[type]->set_interval(RetryUpkeepInterval);
}
ix_service_[type] = 0U;
unset_is_updating(type);
}

View File

@@ -11,7 +11,6 @@
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <string>
#include <string_view>
#include "libtransmission/net.h"
@@ -34,13 +33,34 @@
*
* The idea is, if this class successfully cached a source address, that means
* you have connectivity to the public internet. And if the global address is
* the same with the source address, then you are not behind an NAT.
* the same as the source address, then you are not behind a NAT.
*
*/
class tr_global_ip_cache
{
public:
tr_global_ip_cache(tr_web& web_in, libtransmission::TimerMaker& timer_maker_in);
struct Mediator
{
virtual ~Mediator() = default;
virtual void fetch(tr_web::FetchOptions&& /* options */)
{
}
[[nodiscard]] virtual std::string_view settings_bind_addr(tr_address_type /* type */)
{
return {};
}
[[nodiscard]] virtual libtransmission::TimerMaker& timer_maker() = 0;
};
private:
explicit tr_global_ip_cache(Mediator& mediator_in);
public:
[[nodiscard]] static std::unique_ptr<tr_global_ip_cache> create(Mediator& mediator_in);
tr_global_ip_cache() = delete;
~tr_global_ip_cache();
tr_global_ip_cache(tr_global_ip_cache const&) = delete;
@@ -62,12 +82,17 @@ public:
return source_addr_[type];
}
void set_settings_bind_addr(tr_address_type type, std::string_view bind_address) noexcept;
[[nodiscard]] tr_address bind_addr(tr_address_type type) const noexcept;
void update_addr(tr_address_type type) noexcept;
bool set_global_addr(tr_address_type type, tr_address const& addr) noexcept;
void update_addr(tr_address_type type) noexcept;
void update_global_addr(tr_address_type type) noexcept;
void update_source_addr(tr_address_type type) noexcept;
// Only use as a callback for web_->fetch()
void on_response_ip_query(tr_address_type type, tr_web::FetchResponse const& response) noexcept;
[[nodiscard]] constexpr auto has_ip_protocol(tr_address_type type) const noexcept
{
return has_ip_protocol_[type];
@@ -94,19 +119,11 @@ private:
[[nodiscard]] bool set_is_updating(tr_address_type type) noexcept;
void unset_is_updating(tr_address_type type) noexcept;
void update_global_addr(tr_address_type type) noexcept;
void update_source_addr(tr_address_type type) noexcept;
// Only use as a callback for web_->fetch()
void on_response_ip_query(tr_address_type type, tr_web::FetchResponse const& response) noexcept;
tr_web& web_;
array_ip_t<std::optional<tr_address>> settings_bind_addr_;
Mediator& mediator_;
enum class is_updating_t
{
NO,
NO = 0,
YES,
ABORT
};

View File

@@ -961,10 +961,14 @@ void sendLtepHandshake(tr_peerMsgsImpl* msgs)
tr_variantInitDict(&val, 8);
tr_variantDictAddBool(&val, TR_KEY_e, msgs->session->encryptionMode() != TR_CLEAR_PREFERRED);
if (auto const addr = msgs->session->publicAddress(TR_AF_INET6); !addr.is_any())
// If connecting to global peer, then use global address
// Otherwise we are connecting to local peer, use bind address directly
if (auto const addr = msgs->io->address().is_global_unicast_address() ? msgs->session->global_address(TR_AF_INET6) :
msgs->session->publicAddress(TR_AF_INET6);
addr && !addr->is_any())
{
TR_ASSERT(addr.is_ipv6());
tr_variantDictAddRaw(&val, TR_KEY_ipv6, &addr.addr.addr6, sizeof(addr.addr.addr6));
TR_ASSERT(addr->is_ipv6());
tr_variantDictAddRaw(&val, TR_KEY_ipv6, &addr->addr.addr6, sizeof(addr->addr.addr6));
}
// http://bittorrent.org/beps/bep_0009.html

View File

@@ -45,6 +45,10 @@
using namespace std::literals;
// FIXME(ckerr) do not merge these three lines.
// This comment is to make CI think libtransmission has
// changed so that it will run the libtransmission CI tests
namespace
{
#ifdef _WIN32

View File

@@ -18,7 +18,7 @@ using namespace std::literals;
namespace
{
auto constexpr MyStatic = std::array<std::string_view, 402>{ ""sv,
auto constexpr MyStatic = std::array<std::string_view, 404>{ ""sv,
"activeTorrentCount"sv,
"activity-date"sv,
"activityDate"sv,
@@ -47,6 +47,7 @@ auto constexpr MyStatic = std::array<std::string_view, 402>{ ""sv,
"availability"sv,
"bandwidth-priority"sv,
"bandwidthPriority"sv,
"beginPiece"sv,
"bind-address-ipv4"sv,
"bind-address-ipv6"sv,
"bitfield"sv,
@@ -108,6 +109,7 @@ auto constexpr MyStatic = std::array<std::string_view, 402>{ ""sv,
"editDate"sv,
"encoding"sv,
"encryption"sv,
"endPiece"sv,
"error"sv,
"errorString"sv,
"eta"sv,

View File

@@ -50,6 +50,7 @@ enum
TR_KEY_availability, // rpc
TR_KEY_bandwidth_priority,
TR_KEY_bandwidthPriority,
TR_KEY_beginPiece,
TR_KEY_bind_address_ipv4,
TR_KEY_bind_address_ipv6,
TR_KEY_bitfield,
@@ -111,6 +112,7 @@ enum
TR_KEY_editDate,
TR_KEY_encoding,
TR_KEY_encryption,
TR_KEY_endPiece,
TR_KEY_error,
TR_KEY_errorString,
TR_KEY_eta,

View File

@@ -77,40 +77,102 @@ auto inline constexpr TrUnixAddrStrLen = size_t{ sizeof(((struct sockaddr_un*)nu
enum tr_rpc_address_type
{
TR_RPC_AF_INET,
TR_RPC_AF_INET6,
TR_RPC_AF_UNIX
TR_RPC_INET_ADDR,
TR_RPC_UNIX_ADDR
};
class tr_unix_addr
{
public:
[[nodiscard]] std::string to_string() const
{
return std::empty(unix_socket_path_) ? unix_socket_path_ : std::string(TrUnixSocketPrefix);
}
[[nodiscard]] bool from_string(std::string_view src)
{
if (!tr_strvStartsWith(TrUnixSocketPrefix, src))
{
return false;
}
if (std::size(src) >= TrUnixAddrStrLen)
{
tr_logAddError(fmt::format(
_("Unix socket path must be fewer than {count} characters (including '{prefix}' prefix)"),
fmt::arg("count", TrUnixAddrStrLen - 1),
fmt::arg("prefix", TrUnixSocketPrefix)));
return false;
}
unix_socket_path_ = src;
return true;
}
private:
std::string unix_socket_path_ = {};
};
} // namespace
struct tr_rpc_address
class tr_rpc_address
{
tr_rpc_address_type type;
union
public:
tr_rpc_address()
: inet_addr_(tr_address::any_ipv4())
{
struct in_addr addr4;
struct in6_addr addr6;
std::array<char, TrUnixAddrStrLen> unixSocketPath;
} addr;
void set_inaddr_any()
{
type = TR_RPC_AF_INET;
addr.addr4 = { INADDR_ANY };
}
[[nodiscard]] constexpr auto is_unix_addr() const noexcept
{
return type_ == TR_RPC_UNIX_ADDR;
}
[[nodiscard]] constexpr auto is_inet_addr() const noexcept
{
return type_ == TR_RPC_INET_ADDR;
}
bool from_string(std::string_view src)
{
if (auto address = tr_address::from_string(src); address.has_value())
{
type_ = TR_RPC_INET_ADDR;
inet_addr_ = address.value();
return true;
}
if (unix_addr_.from_string(src))
{
type_ = TR_RPC_UNIX_ADDR;
return true;
}
return false;
}
[[nodiscard]] std::string to_string(tr_port port = {}) const
{
if (type_ == TR_RPC_UNIX_ADDR)
{
return unix_addr_.to_string();
}
if (port.empty())
{
return { inet_addr_.display_name() };
}
return { inet_addr_.display_name(port) };
}
private:
tr_rpc_address_type type_ = TR_RPC_INET_ADDR;
struct tr_address inet_addr_;
class tr_unix_addr unix_addr_;
};
namespace
{
int constexpr DeflateLevel = 6; // medium / default
#ifdef TR_ENABLE_ASSERTS
bool constexpr tr_rpc_address_is_valid(tr_rpc_address const& a)
{
return a.type == TR_RPC_AF_INET || a.type == TR_RPC_AF_INET6 || a.type == TR_RPC_AF_UNIX;
}
#endif
// ---
void send_simple_response(struct evhttp_request* req, int code, char const* text = nullptr)
@@ -534,73 +596,6 @@ auto constexpr ServerStartRetryCount = int{ 10 };
auto constexpr ServerStartRetryDelayIncrement = 5s;
auto constexpr ServerStartRetryMaxDelay = 60s;
char const* tr_rpc_address_to_string(tr_rpc_address const& addr, char* buf, size_t buflen)
{
TR_ASSERT(tr_rpc_address_is_valid(addr));
switch (addr.type)
{
case TR_RPC_AF_INET:
return evutil_inet_ntop(AF_INET, &addr.addr, buf, buflen);
case TR_RPC_AF_INET6:
return evutil_inet_ntop(AF_INET6, &addr.addr, buf, buflen);
case TR_RPC_AF_UNIX:
tr_strlcpy(buf, std::data(addr.addr.unixSocketPath), buflen);
return buf;
default:
return nullptr;
}
}
std::string tr_rpc_address_with_port(tr_rpc_server const* server)
{
auto addr_buf = std::array<char, TrUnixAddrStrLen>{};
tr_rpc_address_to_string(*server->bind_address_, std::data(addr_buf), std::size(addr_buf));
std::string addr_port_str = std::data(addr_buf);
if (server->bind_address_->type != TR_RPC_AF_UNIX)
{
addr_port_str.append(":" + std::to_string(server->port().host()));
}
return addr_port_str;
}
bool tr_rpc_address_from_string(tr_rpc_address& dst, std::string_view src)
{
if (tr_strvStartsWith(src, TrUnixSocketPrefix))
{
if (std::size(src) >= TrUnixAddrStrLen)
{
tr_logAddError(fmt::format(
_("Unix socket path must be fewer than {count} characters (including '{prefix}' prefix)"),
fmt::arg("count", TrUnixAddrStrLen - 1),
fmt::arg("prefix", TrUnixSocketPrefix)));
return false;
}
dst.type = TR_RPC_AF_UNIX;
tr_strlcpy(std::data(dst.addr.unixSocketPath), std::string{ src }.c_str(), std::size(dst.addr.unixSocketPath));
return true;
}
if (evutil_inet_pton(AF_INET, std::string{ src }.c_str(), &dst.addr) == 1)
{
dst.type = TR_RPC_AF_INET;
return true;
}
if (evutil_inet_pton(AF_INET6, std::string{ src }.c_str(), &dst.addr) == 1)
{
dst.type = TR_RPC_AF_INET6;
return true;
}
return false;
}
bool bindUnixSocket(
[[maybe_unused]] struct event_base* base,
[[maybe_unused]] struct evhttp* httpd,
@@ -679,11 +674,11 @@ void start_server(tr_rpc_server* server)
auto const address = server->get_bind_address();
auto const port = server->port();
bool const success = server->bind_address_->type == TR_RPC_AF_UNIX ?
bool const success = server->bind_address_->is_unix_addr() ?
bindUnixSocket(base, httpd, address.c_str(), server->socket_mode_) :
(evhttp_bind_socket(httpd, address.c_str(), port.host()) != -1);
auto const addr_port_str = tr_rpc_address_with_port(server);
auto const addr_port_str = server->bind_address_->to_string(port);
if (!success)
{
@@ -732,14 +727,14 @@ void stop_server(tr_rpc_server* server)
httpd.reset();
if (server->bind_address_->type == TR_RPC_AF_UNIX)
if (server->bind_address_->is_unix_addr())
{
unlink(address.c_str() + std::size(TrUnixSocketPrefix));
}
tr_logAddInfo(fmt::format(
_("Stopped listening for RPC and Web requests on '{address}'"),
fmt::arg("address", tr_rpc_address_with_port(server))));
fmt::arg("address", server->bind_address_->to_string(server->port()))));
}
void restart_server(tr_rpc_server* const server)
@@ -838,8 +833,7 @@ void tr_rpc_server::set_password_enabled(bool enabled)
std::string tr_rpc_server::get_bind_address() const
{
auto buf = std::array<char, TrUnixAddrStrLen>{};
return tr_rpc_address_to_string(*this->bind_address_, std::data(buf), std::size(buf));
return bind_address_->to_string();
}
void tr_rpc_server::set_anti_brute_force_enabled(bool enabled) noexcept
@@ -857,7 +851,7 @@ void tr_rpc_server::set_anti_brute_force_enabled(bool enabled) noexcept
tr_rpc_server::tr_rpc_server(tr_session* session_in, tr_variant* settings)
: compressor{ libdeflate_alloc_compressor(DeflateLevel), libdeflate_free_compressor }
, web_client_dir_{ tr_getWebClientDir(session_in) }
, bind_address_(std::make_unique<struct tr_rpc_address>())
, bind_address_(std::make_unique<class tr_rpc_address>())
, session{ session_in }
{
load(settings);
@@ -887,23 +881,23 @@ void tr_rpc_server::load(tr_variant* src)
this->set_username(username_);
this->set_password(salted_password_);
if (!tr_rpc_address_from_string(*bind_address_, bind_address_str_))
if (!bind_address_->from_string(bind_address_str_))
{
// NOTE: bind_address_ is default initialized to INADDR_ANY
tr_logAddWarn(fmt::format(
_("The '{key}' setting is '{value}' but must be an IPv4 or IPv6 address or a Unix socket path. Using default value '0.0.0.0'"),
fmt::format("key", tr_quark_get_string_view(TR_KEY_rpc_bind_address)),
fmt::format("value", bind_address_str_)));
bind_address_->set_inaddr_any();
}
if (bind_address_->type == TR_RPC_AF_UNIX)
if (bind_address_->is_unix_addr())
{
this->set_whitelist_enabled(false);
this->is_host_whitelist_enabled_ = false;
}
if (this->is_enabled())
{
auto const rpc_uri = tr_rpc_address_with_port(this) + this->url_;
auto const rpc_uri = bind_address_->to_string(this->port()) + this->url_;
tr_logAddInfo(fmt::format(_("Serving RPC and Web requests on {address}"), fmt::arg("address", rpc_uri)));
session->runInSessionThread(start_server, this);

View File

@@ -21,7 +21,7 @@
struct evhttp;
struct tr_variant;
struct tr_rpc_address;
class tr_rpc_address;
struct libdeflate_compressor;
namespace libtransmission
@@ -153,7 +153,7 @@ public:
std::vector<std::string> whitelist_;
std::string const web_client_dir_;
std::unique_ptr<struct tr_rpc_address> bind_address_;
std::unique_ptr<class tr_rpc_address> bind_address_;
std::unique_ptr<libtransmission::Timer> start_retry_timer;
libtransmission::evhelpers::evhttp_unique_ptr httpd;

View File

@@ -326,8 +326,10 @@ void addFiles(tr_torrent const* tor, tr_variant* list)
for (tr_file_index_t i = 0, n = tor->file_count(); i < n; ++i)
{
auto const file = tr_torrentFile(tor, i);
tr_variant* d = tr_variantListAddDict(list, 3);
tr_variant* d = tr_variantListAddDict(list, 5);
tr_variantDictAddInt(d, TR_KEY_beginPiece, file.beginPiece);
tr_variantDictAddInt(d, TR_KEY_bytesCompleted, file.have);
tr_variantDictAddInt(d, TR_KEY_endPiece, file.endPiece);
tr_variantDictAddInt(d, TR_KEY_length, file.length);
tr_variantDictAddStr(d, TR_KEY_name, file.name);
}

View File

@@ -432,7 +432,9 @@ tr_address tr_session::publicAddress(tr_address_type type) const noexcept
// otherwise, if we can determine which one to use via global_source_address(ipv6) magic, use it.
// otherwise, use any_ipv6 (::).
auto const source_addr = global_source_address(type);
return source_addr && source_addr->is_global_unicast_address() ? *source_addr : global_ip_cache_->bind_addr(type);
auto const default_addr = source_addr && source_addr->is_global_unicast_address() ? *source_addr :
tr_address::any_ipv6();
return tr_address::from_string(settings_.bind_address_ipv6).value_or(default_addr);
}
TR_ASSERT_MSG(false, "invalid type");
@@ -639,8 +641,6 @@ void tr_session::initImpl(init_data& data)
this->blocklists_ = libtransmission::Blocklist::loadBlocklists(blocklist_dir_, useBlocklist());
this->global_ip_cache_ = std::make_unique<tr_global_ip_cache>(*web_, timerMaker());
tr_logAddInfo(fmt::format(_("Transmission version {version} starting"), fmt::arg("version", LONG_VERSION_STRING)));
setSettings(client_settings, true);
@@ -701,11 +701,11 @@ void tr_session::setSettings(tr_session_settings&& settings_in, bool force)
if (auto const& val = new_settings.bind_address_ipv4; force || val != old_settings.bind_address_ipv4)
{
global_ip_cache_->set_settings_bind_addr(TR_AF_INET, new_settings.bind_address_ipv4);
global_ip_cache_->update_addr(TR_AF_INET);
}
if (auto const& val = new_settings.bind_address_ipv6; force || val != old_settings.bind_address_ipv6)
{
global_ip_cache_->set_settings_bind_addr(TR_AF_INET6, new_settings.bind_address_ipv6);
global_ip_cache_->update_addr(TR_AF_INET6);
}
if (auto const& val = new_settings.default_trackers_str; force || val != old_settings.default_trackers_str)

View File

@@ -275,6 +275,42 @@ private:
tr_session& session_;
};
class GlobalIPCacheMediator final : public tr_global_ip_cache::Mediator
{
public:
explicit GlobalIPCacheMediator(tr_session& session) noexcept
: session_(session)
{
}
void fetch(tr_web::FetchOptions&& options) override
{
session_.fetch(std::move(options));
}
[[nodiscard]] std::string_view settings_bind_addr(tr_address_type type) override
{
switch (type)
{
case TR_AF_INET:
return session_.settings_.bind_address_ipv4;
case TR_AF_INET6:
return session_.settings_.bind_address_ipv6;
default:
TR_ASSERT_MSG(false, "Invalid type");
return {};
}
}
[[nodiscard]] libtransmission::TimerMaker& timer_maker() override
{
return session_.timerMaker();
}
private:
tr_session& session_;
};
// UDP connectivity used for the DHT and µTP
class tr_udp_core
{
@@ -1112,7 +1148,8 @@ private:
tr_torrents torrents_;
// depends-on: settings_, session_thread_, timer_maker_, web_
std::unique_ptr<tr_global_ip_cache> global_ip_cache_;
GlobalIPCacheMediator global_ip_cache_mediator_{ *this };
std::unique_ptr<tr_global_ip_cache> global_ip_cache_ = tr_global_ip_cache::create(global_ip_cache_mediator_);
// depends-on: settings_, session_thread_, torrents_, global_ip_cache (via tr_session::publicAddress())
WebMediator web_mediator_{ this };

View File

@@ -1615,14 +1615,15 @@ tr_file_view tr_torrentFile(tr_torrent const* tor, tr_file_index_t file)
auto const priority = tor->file_priorities_.file_priority(file);
auto const wanted = tor->files_wanted_.file_wanted(file);
auto const length = tor->file_size(file);
auto const [begin, end] = tor->pieces_in_file(file);
if (tor->completeness == TR_SEED || length == 0)
{
return { subpath.c_str(), length, length, 1.0, priority, wanted };
return { subpath.c_str(), length, length, 1.0, begin, end, priority, wanted };
}
auto const have = tor->completion.count_has_bytes_in_span(tor->fpm_.byte_span(file));
return { subpath.c_str(), have, length, have >= length ? 1.0 : have / double(length), priority, wanted };
return { subpath.c_str(), have, length, have >= length ? 1.0 : have / double(length), begin, end, priority, wanted };
}
size_t tr_torrentFileCount(tr_torrent const* torrent)
@@ -2446,10 +2447,36 @@ namespace
{
namespace rename_helpers
{
bool renameArgsAreValid(std::string_view oldpath, std::string_view newname)
bool renameArgsAreValid(tr_torrent const* tor, std::string_view oldpath, std::string_view newname)
{
return !std::empty(oldpath) && !std::empty(newname) && newname != "."sv && newname != ".."sv &&
!tr_strvContains(newname, TR_PATH_DELIMITER);
if (std::empty(oldpath) || std::empty(newname) || newname == "."sv || newname == ".."sv ||
tr_strvContains(newname, TR_PATH_DELIMITER))
{
return false;
}
auto const newpath = tr_strvContains(oldpath, TR_PATH_DELIMITER) ?
tr_pathbuf{ tr_sys_path_dirname(oldpath), '/', newname } :
tr_pathbuf{ newname };
if (newpath == oldpath)
{
return true;
}
auto const newpath_as_dir = tr_pathbuf{ newpath, '/' };
auto const n_files = tor->file_count();
for (tr_file_index_t i = 0; i < n_files; ++i)
{
auto const& name = tor->file_subpath(i);
if (newpath == name || tr_strvStartsWith(name, newpath_as_dir))
{
return false;
}
}
return true;
}
auto renameFindAffectedFiles(tr_torrent const* tor, std::string_view oldpath)
@@ -2566,7 +2593,7 @@ void torrentRenamePath(
int error = 0;
if (!renameArgsAreValid(oldpath, newname))
if (!renameArgsAreValid(tor, oldpath, newname))
{
error = EINVAL;
}

View File

@@ -1334,6 +1334,8 @@ struct tr_file_view
uint64_t have; // the current size of the file, i.e. how much we've downloaded
uint64_t length; // the total size of the file
double progress; // have / length
tr_piece_index_t beginPiece; // piece index where this file starts
tr_piece_index_t endPiece; // piece index where this file ends (exclusive)
tr_priority_t priority; // the file's priority
bool wanted; // do we want to download this file?
};

View File

@@ -9,7 +9,6 @@
#include <cerrno>
#include <cfloat> // DBL_DIG
#include <chrono>
#include <clocale> // localeconv()
#include <cstdint> // SIZE_MAX
#include <cstdlib> // getenv()
#include <cstring> /* strerror() */
@@ -514,10 +513,10 @@ std::vector<int> tr_parseNumberRange(std::string_view str)
double tr_truncd(double x, int decimal_places)
{
auto buf = std::array<char, 128>{};
auto const [out, len] = fmt::format_to_n(std::data(buf), std::size(buf) - 1, "{:.{}Lf}", x, DBL_DIG);
auto const [out, len] = fmt::format_to_n(std::data(buf), std::size(buf) - 1, "{:.{}f}", x, DBL_DIG);
*out = '\0';
if (auto* const pt = strstr(std::data(buf), localeconv()->decimal_point); pt != nullptr)
if (auto* const pt = strchr(std::data(buf), '.'); pt != nullptr)
{
pt[decimal_places != 0 ? decimal_places + 1 : 0] = '\0';
}

View File

@@ -99,7 +99,7 @@ void error_handler(jsonsl_t jsn, jsonsl_error_t error, jsonsl_state_st* /*state*
fmt::arg("position", jsn->pos),
fmt::arg("text", std::string_view{ buf, std::min(size_t{ 16U }, data->size - jsn->pos) }),
fmt::arg("error", jsonsl_strerror(error)),
fmt::arg("error_code", error)));
fmt::arg("error_code", static_cast<int>(error))));
}
int error_callback(jsonsl_t jsn, jsonsl_error_t error, struct jsonsl_state_st* state, jsonsl_char_t* at)

View File

@@ -4,6 +4,12 @@
#import <AppKit/AppKit.h>
typedef NS_ENUM(NSInteger, AttributesStyle) {
AttributesStyleNormal,
AttributesStyleEmphasized,
AttributesStyleDisabled,
};
@interface FileNameCell : NSActionCell
- (NSRect)imageRectForBounds:(NSRect)bounds;

View File

@@ -20,46 +20,52 @@ static CGFloat const kPaddingBelowStatusFile = 2.0;
static CGFloat const kPaddingBetweenNameAndFolderStatus = 4.0;
static CGFloat const kPaddingExpansionFrame = 2.0;
@interface FileNameCell ()
static NSMutableParagraphStyle* sParagraphStyle()
{
NSMutableParagraphStyle* paragraphStyle = [NSParagraphStyle.defaultParagraphStyle mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingMiddle;
return paragraphStyle;
}
static NSMutableParagraphStyle* sStatusParagraphStyle()
{
NSMutableParagraphStyle* paragraphStyle = [NSParagraphStyle.defaultParagraphStyle mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
return paragraphStyle;
}
@property(nonatomic, readonly) NSAttributedString* attributedTitle;
@property(nonatomic, readonly) NSAttributedString* attributedStatus;
@property(nonatomic, readonly) NSMutableDictionary* fTitleAttributes;
@property(nonatomic, readonly) NSMutableDictionary* fStatusAttributes;
@end
static NSDictionary<NSAttributedStringKey, id>* const kTitleAttributes = @{
NSFontAttributeName : [NSFont messageFontOfSize:12.0],
NSParagraphStyleAttributeName : sParagraphStyle(),
NSForegroundColorAttributeName : NSColor.controlTextColor
};
static NSDictionary<NSAttributedStringKey, id>* const kStatusAttributes = @{
NSFontAttributeName : [NSFont messageFontOfSize:9.0],
NSParagraphStyleAttributeName : sStatusParagraphStyle(),
NSForegroundColorAttributeName : NSColor.secondaryLabelColor
};
static NSDictionary<NSAttributedStringKey, id>* const kTitleEmphasizedAttributes = @{
NSFontAttributeName : [NSFont messageFontOfSize:12.0],
NSParagraphStyleAttributeName : sParagraphStyle(),
NSForegroundColorAttributeName : NSColor.whiteColor
};
static NSDictionary<NSAttributedStringKey, id>* const kStatusEmphasizedAttributes = @{
NSFontAttributeName : [NSFont messageFontOfSize:9.0],
NSParagraphStyleAttributeName : sStatusParagraphStyle(),
NSForegroundColorAttributeName : NSColor.whiteColor
};
static NSDictionary<NSAttributedStringKey, id>* const kTitleDisabledAttributes = @{
NSFontAttributeName : [NSFont messageFontOfSize:12.0],
NSParagraphStyleAttributeName : sParagraphStyle(),
NSForegroundColorAttributeName : NSColor.disabledControlTextColor
};
static NSDictionary<NSAttributedStringKey, id>* const kStatusDisabledAttributes = @{
NSFontAttributeName : [NSFont messageFontOfSize:9.0],
NSParagraphStyleAttributeName : sStatusParagraphStyle(),
NSForegroundColorAttributeName : NSColor.disabledControlTextColor
};
@implementation FileNameCell
- (instancetype)init
{
if ((self = [super init]))
{
NSMutableParagraphStyle* paragraphStyle = [NSParagraphStyle.defaultParagraphStyle mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingMiddle;
_fTitleAttributes = [[NSMutableDictionary alloc]
initWithObjectsAndKeys:[NSFont messageFontOfSize:12.0], NSFontAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
NSMutableParagraphStyle* statusParagraphStyle = [NSParagraphStyle.defaultParagraphStyle mutableCopy];
statusParagraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
_fStatusAttributes = [[NSMutableDictionary alloc]
initWithObjectsAndKeys:[NSFont messageFontOfSize:9.0], NSFontAttributeName, statusParagraphStyle, NSParagraphStyleAttributeName, nil];
}
return self;
}
- (id)copyWithZone:(NSZone*)zone
{
FileNameCell* copy = [super copyWithZone:zone];
copy->_fTitleAttributes = _fTitleAttributes;
copy->_fStatusAttributes = _fStatusAttributes;
return copy;
}
- (NSImage*)image
{
FileListNode* node = (FileListNode*)self.objectValue;
@@ -87,40 +93,36 @@ static CGFloat const kPaddingExpansionFrame = 2.0;
respectFlipped:YES
hints:nil];
NSColor *titleColor, *statusColor;
FileListNode* node = self.objectValue;
AttributesStyle style;
if (self.backgroundStyle == NSBackgroundStyleEmphasized)
{
titleColor = statusColor = NSColor.whiteColor;
style = AttributesStyleEmphasized;
}
else if ([node.torrent checkForFiles:node.indexes] == NSControlStateValueOff)
{
titleColor = statusColor = NSColor.disabledControlTextColor;
style = AttributesStyleDisabled;
}
else
{
titleColor = NSColor.controlTextColor;
statusColor = NSColor.secondaryLabelColor;
style = AttributesStyleNormal;
}
self.fTitleAttributes[NSForegroundColorAttributeName] = titleColor;
self.fStatusAttributes[NSForegroundColorAttributeName] = statusColor;
//title
NSAttributedString* titleString = self.attributedTitle;
NSRect titleRect = [self rectForTitleWithString:titleString inBounds:cellFrame];
NSAttributedString* titleString = [self attributedTitleWithStyle:style];
NSRect titleRect = [self rectForTitleWithStringSize:[titleString size] inBounds:cellFrame];
[titleString drawInRect:titleRect];
//status
NSAttributedString* statusString = self.attributedStatus;
NSAttributedString* statusString = [self attributedStatusWithStyle:style];
NSRect statusRect = [self rectForStatusWithString:statusString withTitleRect:titleRect inBounds:cellFrame];
[statusString drawInRect:statusRect];
}
- (NSRect)expansionFrameWithFrame:(NSRect)cellFrame inView:(NSView*)view
{
NSAttributedString* titleString = self.attributedTitle;
NSRect realRect = [self rectForTitleWithString:titleString inBounds:cellFrame];
NSAttributedString* titleString = [self attributedTitleWithStyle:AttributesStyleNormal];
NSRect realRect = [self rectForTitleWithStringSize:[titleString size] inBounds:cellFrame];
if ([titleString size].width > NSWidth(realRect) &&
NSMouseInRect([view convertPoint:view.window.mouseLocationOutsideOfEventStream fromView:nil], realRect, view.flipped))
@@ -137,16 +139,15 @@ static CGFloat const kPaddingExpansionFrame = 2.0;
cellFrame.origin.x += kPaddingExpansionFrame;
cellFrame.origin.y += kPaddingExpansionFrame;
self.fTitleAttributes[NSForegroundColorAttributeName] = NSColor.controlTextColor;
NSAttributedString* titleString = self.attributedTitle;
NSAttributedString* titleString = [self attributedTitleWithStyle:AttributesStyleNormal];
[titleString drawInRect:cellFrame];
}
#pragma mark - Private
- (NSRect)rectForTitleWithString:(NSAttributedString*)string inBounds:(NSRect)bounds
- (NSRect)rectForTitleWithStringSize:(NSSize)stringSize inBounds:(NSRect)bounds
{
NSSize const titleSize = [string size];
NSSize const titleSize = stringSize;
//no right padding, so that there's not too much space between this and the priority image
NSRect result;
@@ -189,13 +190,15 @@ static CGFloat const kPaddingExpansionFrame = 2.0;
return result;
}
- (NSAttributedString*)attributedTitle
- (NSAttributedString*)attributedTitleWithStyle:(AttributesStyle)style
{
NSString* title = ((FileListNode*)self.objectValue).name;
return [[NSAttributedString alloc] initWithString:title attributes:self.fTitleAttributes];
return [[NSAttributedString alloc] initWithString:title attributes:style == AttributesStyleEmphasized ? kTitleEmphasizedAttributes :
style == AttributesStyleDisabled ? kTitleDisabledAttributes :
kTitleAttributes];
}
- (NSAttributedString*)attributedStatus
- (NSAttributedString*)attributedStatusWithStyle:(AttributesStyle)style
{
FileListNode* node = (FileListNode*)self.objectValue;
Torrent* torrent = node.torrent;
@@ -207,7 +210,9 @@ static CGFloat const kPaddingExpansionFrame = 2.0;
percentString,
[NSString stringForFileSize:node.size]];
return [[NSAttributedString alloc] initWithString:status attributes:self.fStatusAttributes];
return [[NSAttributedString alloc] initWithString:status attributes:style == AttributesStyleEmphasized ? kStatusEmphasizedAttributes :
style == AttributesStyleDisabled ? kStatusDisabledAttributes :
kStatusAttributes];
}
@end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 897 B

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 KiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 B

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 966 B

After

Width:  |  Height:  |  Size: 723 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 B

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 616 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 177 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 B

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 B

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 B

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 B

After

Width:  |  Height:  |  Size: 132 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

After

Width:  |  Height:  |  Size: 244 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 B

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 479 B

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 B

After

Width:  |  Height:  |  Size: 76 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 B

After

Width:  |  Height:  |  Size: 87 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 B

After

Width:  |  Height:  |  Size: 299 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 616 B

View File

@@ -428,8 +428,7 @@ static NSString* const kWebSeedAnimationId = @"webSeed";
{
portString = NSLocalizedString(@"N/A", "Inspector -> Peers tab -> table row tooltip");
}
[components addObject:[NSString stringWithFormat:@"%@: %@",
NSLocalizedString(@"Port", "Inspector -> Peers tab -> table row tooltip"),
[components addObject:[NSString stringWithFormat:NSLocalizedString(@"Port: %@", "Inspector -> Peers tab -> table row tooltip"),
portString]];
NSInteger const peerFrom = [peer[@"From"] integerValue];

View File

@@ -129,7 +129,7 @@ typedef struct PieceInfo
self.image = [[NSImage alloc] initWithSize:self.bounds.size];
[self clearView];
[self setNeedsDisplay];
self.needsDisplay = YES;
}
- (void)clearView
@@ -196,7 +196,7 @@ typedef struct PieceInfo
NSRectFillListWithColors(cFillRects, cFillColors, numCells);
return YES;
}];
[self setNeedsDisplay];
self.needsDisplay = YES;
}
// save the current state so we can compare it later

View File

@@ -3,6 +3,7 @@
// License text can be found in the licenses/ folder.
#import "TorrentCell.h"
#import "FileNameCell.h"
#import "GroupsController.h"
#import "NSImageAdditions.h"
#import "NSStringAdditions.h"
@@ -48,13 +49,35 @@ static CGFloat const kPiecesTotalPercent = 0.6;
static NSInteger const kMaxPieces = 18 * 18;
static NSMutableParagraphStyle* sParagraphStyle()
{
NSMutableParagraphStyle* paragraphStyle = [NSParagraphStyle.defaultParagraphStyle mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingMiddle;
return paragraphStyle;
}
static NSDictionary<NSAttributedStringKey, id>* const kTitleAttributes = @{
NSFontAttributeName : [NSFont messageFontOfSize:12.0],
NSParagraphStyleAttributeName : sParagraphStyle(),
NSForegroundColorAttributeName : NSColor.labelColor
};
static NSDictionary<NSAttributedStringKey, id>* const kStatusAttributes = @{
NSFontAttributeName : [NSFont messageFontOfSize:10.0],
NSParagraphStyleAttributeName : sParagraphStyle(),
NSForegroundColorAttributeName : NSColor.secondaryLabelColor
};
static NSDictionary<NSAttributedStringKey, id>* const kTitleEmphasizedAttributes = @{
NSFontAttributeName : [NSFont messageFontOfSize:12.0],
NSParagraphStyleAttributeName : sParagraphStyle(),
NSForegroundColorAttributeName : NSColor.whiteColor
};
static NSDictionary<NSAttributedStringKey, id>* const kStatusEmphasizedAttributes = @{
NSFontAttributeName : [NSFont messageFontOfSize:10.0],
NSParagraphStyleAttributeName : sParagraphStyle(),
NSForegroundColorAttributeName : NSColor.whiteColor
};
@interface TorrentCell ()
@property(nonatomic, readonly) NSUserDefaults* fDefaults;
@property(nonatomic, readonly) NSMutableDictionary* fTitleAttributes;
@property(nonatomic, readonly) NSMutableDictionary* fStatusAttributes;
@property(nonatomic) BOOL fTracking;
@property(nonatomic) BOOL fMouseDownControlButton;
@property(nonatomic) BOOL fMouseDownRevealButton;
@@ -63,13 +86,6 @@ static NSInteger const kMaxPieces = 18 * 18;
@property(nonatomic, readonly) NSColor* fBluePieceColor;
@property(nonatomic, readonly) NSColor* fBarMinimalBorderColor;
@property(nonatomic, readonly) NSAttributedString* attributedTitle;
- (NSAttributedString*)attributedStatusString:(NSString*)string;
@property(nonatomic, readonly) NSString* buttonString;
@property(nonatomic, readonly) NSString* statusString;
@property(nonatomic, readonly) NSString* minimalStatusString;
@end
@implementation TorrentCell
@@ -79,17 +95,6 @@ static NSInteger const kMaxPieces = 18 * 18;
{
if ((self = [super init]))
{
NSMutableParagraphStyle* paragraphStyle = [NSParagraphStyle.defaultParagraphStyle mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByTruncatingMiddle;
_fTitleAttributes = [[NSMutableDictionary alloc] initWithCapacity:3];
_fTitleAttributes[NSFontAttributeName] = [NSFont messageFontOfSize:12.0];
_fTitleAttributes[NSParagraphStyleAttributeName] = paragraphStyle;
_fStatusAttributes = [[NSMutableDictionary alloc] initWithCapacity:3];
_fStatusAttributes[NSFontAttributeName] = [NSFont messageFontOfSize:10.0];
_fStatusAttributes[NSParagraphStyleAttributeName] = paragraphStyle;
_fBluePieceColor = [NSColor colorWithCalibratedRed:0.0 green:0.4 blue:0.8 alpha:1.0];
_fBarBorderColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.2];
_fBarMinimalBorderColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.015];
@@ -100,8 +105,6 @@ static NSInteger const kMaxPieces = 18 * 18;
- (id)copyWithZone:(NSZone*)zone
{
TorrentCell* copy = [super copyWithZone:zone];
copy->_fTitleAttributes = [_fTitleAttributes mutableCopyWithZone:zone];
copy->_fStatusAttributes = [_fStatusAttributes mutableCopyWithZone:zone];
copy->_fBluePieceColor = _fBluePieceColor;
copy->_fBarBorderColor = _fBarBorderColor;
copy->_fBarMinimalBorderColor = _fBarMinimalBorderColor;
@@ -333,27 +336,14 @@ static NSInteger const kMaxPieces = 18 * 18;
hints:nil];
}
//text color
NSColor *titleColor, *statusColor;
if (self.backgroundStyle == NSBackgroundStyleEmphasized)
{
titleColor = statusColor = NSColor.whiteColor;
}
else
{
titleColor = NSColor.labelColor;
statusColor = NSColor.secondaryLabelColor;
}
self.fTitleAttributes[NSForegroundColorAttributeName] = titleColor;
self.fStatusAttributes[NSForegroundColorAttributeName] = statusColor;
AttributesStyle style = self.backgroundStyle == NSBackgroundStyleEmphasized ? AttributesStyleEmphasized : AttributesStyleNormal;
CGFloat titleRightBound;
//minimal status
if (minimal)
{
NSAttributedString* minimalString = [self attributedStatusString:self.minimalStatusString];
NSRect minimalStatusRect = [self rectForMinimalStatusWithString:minimalString inBounds:cellFrame];
NSAttributedString* minimalString = [self attributedStatusString:self.minimalStatusString style:style];
NSRect minimalStatusRect = [self rectForMinimalStatusWithStringSize:[minimalString size] inBounds:cellFrame];
if (!self.hover)
{
@@ -365,7 +355,7 @@ static NSInteger const kMaxPieces = 18 * 18;
//progress
else
{
NSAttributedString* progressString = [self attributedStatusString:torrent.progressString];
NSAttributedString* progressString = [self attributedStatusString:torrent.progressString style:style];
NSRect progressRect = [self rectForProgressWithStringInBounds:cellFrame];
[progressString drawInRect:progressRect];
@@ -455,8 +445,9 @@ static NSInteger const kMaxPieces = 18 * 18;
}
//title
NSAttributedString* titleString = self.attributedTitle;
NSRect titleRect = [self rectForTitleWithString:titleString withRightBound:titleRightBound inBounds:cellFrame minimal:minimal];
NSAttributedString* titleString = [self attributedTitleWithStyle:style];
NSRect titleRect = [self rectForTitleWithStringSize:[titleString size] withRightBound:titleRightBound inBounds:cellFrame
minimal:minimal];
[titleString drawInRect:titleRect];
//priority icon
@@ -480,7 +471,7 @@ static NSInteger const kMaxPieces = 18 * 18;
//status
if (!minimal)
{
NSAttributedString* statusString = [self attributedStatusString:self.statusString];
NSAttributedString* statusString = [self attributedStatusString:self.statusString style:style];
[statusString drawInRect:[self rectForStatusWithStringInBounds:cellFrame]];
}
}
@@ -493,8 +484,8 @@ static NSInteger const kMaxPieces = 18 * 18;
CGFloat titleRightBound;
if (minimal)
{
NSAttributedString* minimalString = [self attributedStatusString:self.minimalStatusString];
NSRect minimalStatusRect = [self rectForMinimalStatusWithString:minimalString inBounds:cellFrame];
NSAttributedString* minimalString = [self attributedStatusString:self.minimalStatusString style:AttributesStyleNormal];
NSRect minimalStatusRect = [self rectForMinimalStatusWithStringSize:[minimalString size] inBounds:cellFrame];
titleRightBound = NSMinX(minimalStatusRect);
@@ -509,15 +500,17 @@ static NSInteger const kMaxPieces = 18 * 18;
titleRightBound = NSMaxX(cellFrame);
}
NSAttributedString* titleString = self.attributedTitle;
NSRect realRect = [self rectForTitleWithString:titleString withRightBound:titleRightBound inBounds:cellFrame minimal:minimal];
NSAttributedString* titleString = [self attributedTitleWithStyle:AttributesStyleNormal];
NSSize titleStringSize = [titleString size];
NSRect realRect = [self rectForTitleWithStringSize:titleStringSize withRightBound:titleRightBound inBounds:cellFrame
minimal:minimal];
NSAssert([titleString size].width >= NSWidth(realRect), @"Full rect width should not be less than the used title rect width!");
NSAssert(titleStringSize.width >= NSWidth(realRect), @"Full rect width should not be less than the used title rect width!");
if ([titleString size].width > NSWidth(realRect) &&
if (titleStringSize.width > NSWidth(realRect) &&
NSMouseInRect([view convertPoint:view.window.mouseLocationOutsideOfEventStream fromView:nil], realRect, view.flipped))
{
realRect.size.width = [titleString size].width;
realRect.size.width = titleStringSize.width;
return NSInsetRect(realRect, -kPaddingExpansionFrame, -kPaddingExpansionFrame);
}
@@ -529,8 +522,7 @@ static NSInteger const kMaxPieces = 18 * 18;
cellFrame.origin.x += kPaddingExpansionFrame;
cellFrame.origin.y += kPaddingExpansionFrame;
self.fTitleAttributes[NSForegroundColorAttributeName] = NSColor.labelColor;
NSAttributedString* titleString = self.attributedTitle;
NSAttributedString* titleString = [self attributedTitleWithStyle:AttributesStyleNormal];
[titleString drawInRect:cellFrame];
}
@@ -711,10 +703,10 @@ static NSInteger const kMaxPieces = 18 * 18;
hints:nil];
}
- (NSRect)rectForMinimalStatusWithString:(NSAttributedString*)string inBounds:(NSRect)bounds
- (NSRect)rectForMinimalStatusWithStringSize:(NSSize)stringSize inBounds:(NSRect)bounds
{
NSRect result;
result.size = [string size];
result.size = stringSize;
result.origin.x = NSMaxX(bounds) - (kPaddingHorizontal + NSWidth(result) + kPaddingEdgeMax);
result.origin.y = ceil(NSMidY(bounds) - NSHeight(result) * 0.5);
@@ -722,10 +714,10 @@ static NSInteger const kMaxPieces = 18 * 18;
return result;
}
- (NSRect)rectForTitleWithString:(NSAttributedString*)string
withRightBound:(CGFloat)rightBound
inBounds:(NSRect)bounds
minimal:(BOOL)minimal
- (NSRect)rectForTitleWithStringSize:(NSSize)stringSize
withRightBound:(CGFloat)rightBound
inBounds:(NSRect)bounds
minimal:(BOOL)minimal
{
NSRect result;
result.origin.x = NSMinX(bounds) + kPaddingHorizontal + (minimal ? kImageSizeMin : kImageSizeRegular) + kPaddingBetweenImageAndTitle;
@@ -748,7 +740,7 @@ static NSInteger const kMaxPieces = 18 * 18;
{
result.size.width -= kPriorityIconSize + kPaddingBetweenTitleAndPriority;
}
result.size.width = MIN(NSWidth(result), [string size].width);
result.size.width = MIN(NSWidth(result), stringSize.width);
return result;
}
@@ -857,15 +849,17 @@ static NSInteger const kMaxPieces = 18 * 18;
return NSMakeRect(NSMinX(bounds) - padding * 0.5, NSMidY(bounds) - imageSize * 0.5, imageSize, imageSize);
}
- (NSAttributedString*)attributedTitle
- (NSAttributedString*)attributedTitleWithStyle:(AttributesStyle)style
{
NSString* title = ((Torrent*)self.representedObject).name;
return [[NSAttributedString alloc] initWithString:title attributes:self.fTitleAttributes];
return [[NSAttributedString alloc] initWithString:title
attributes:style == AttributesStyleEmphasized ? kTitleEmphasizedAttributes : kTitleAttributes];
}
- (NSAttributedString*)attributedStatusString:(NSString*)string
- (NSAttributedString*)attributedStatusString:(NSString*)string style:(AttributesStyle)style
{
return [[NSAttributedString alloc] initWithString:string attributes:self.fStatusAttributes];
return [[NSAttributedString alloc] initWithString:string
attributes:style == AttributesStyleEmphasized ? kStatusEmphasizedAttributes : kStatusAttributes];
}
- (NSString*)buttonString

View File

@@ -126,7 +126,13 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175;
//disable highlight color and set manually in drawRow
[self setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleNone];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(setNeedsDisplay) name:@"RefreshTorrentTable" object:nil];
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(refreshTorrentTable) name:@"RefreshTorrentTable"
object:nil];
}
- (void)refreshTorrentTable
{
self.needsDisplay = YES;
}
- (BOOL)isGroupCollapsed:(NSInteger)value

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -674,7 +674,7 @@
"Peers" = "Modparter";
/* Inspector -> Peers tab -> table row tooltip */
"Port" = "Port";
"Port: %@" = "Port: %@";
/* Preferences -> Network -> port status */
"Port check site is down" = "Port checksite er nede";

View File

@@ -674,7 +674,7 @@
"Peers" = "Teilnehmer";
/* Inspector -> Peers tab -> table row tooltip */
"Port" = "Port";
"Port: %@" = "Port: %@";
/* Preferences -> Network -> port status */
"Port check site is down" = "Site zum Überprüfen des Ports ist nicht erreichbar.";

View File

@@ -674,7 +674,7 @@
"Peers" = "Peers";
/* Inspector -> Peers tab -> table row tooltip */
"Port" = "Port";
"Port: %@" = "Port: %@";
/* Preferences -> Network -> port status */
"Port check site is down" = "Port check site is down";

View File

@@ -674,7 +674,7 @@
"Peers" = "Clientes";
/* Inspector -> Peers tab -> table row tooltip */
"Port" = "Puerto";
"Port: %@" = "Puerto: %@";
/* Preferences -> Network -> port status */
"Port check site is down" = "Sitio de comprobación de puertos caido";

Some files were not shown because too many files have changed in this diff Show More