Compare commits

...

53 Commits

Author SHA1 Message Date
Dzmitry Neviadomski
c1ee2b4f12
fix: add job to check if transmission compiles with C++23
Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-06-29 02:20:56 +03:00
Dzmitry Neviadomski
ded1b96137
fix: allow to override C and CXX standard via cmdline or env
Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-06-29 02:16:39 +03:00
Dzmitry Neviadomski
2c51f53b49
fix: explicitly define Application dtor to fix unique ptr issues in qt
Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-06-29 02:16:39 +03:00
Dzmitry Neviadomski
4151b0eed9
fix: wrap runtime format strings with fmt::runtime for GTK client
Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-06-29 02:16:39 +03:00
Dzmitry Neviadomski
3cc0cc42d7
fix: wrap remaining runtime format strings in library, daemon and cli
Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-06-29 02:16:39 +03:00
Dzmitry Neviadomski
358139bc13
fix: wrap runtime format strings with fmt::runtime
fmt::format_string ctor is consteval with C++20
See https://github.com/fmtlib/fmt/issues/2438

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-06-29 02:13:46 +03:00
Dzmitry Neviadomski
77ca06029d
fix: explicitly specify Blocklist::size() return type as size_t
Fixes building with C++20/C++23
error: no matching function for call to 'size'
function 'size' with deduced return type cannot be used before it is defined

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-06-29 02:13:46 +03:00
Dzmitry Neviadomski
5b22db3fd2
fix: operator== should return bool in tr_strbuf
Fixes build error with C++20/C++23

error: return type 'auto' of selected 'operator==' function for rewritten '!=' comparison is not 'bool'

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-06-29 02:13:45 +03:00
Torbjörn Lönnemark
ec5296c8dc
fix: restore tr_optind in all getConfigDir branches (#6920)
When the --config-dir/-g option was passed, this bug caused all options
preceeding it on the command line to be ignored.

Also remove the superfluous break statement.

Regression introduced by e49747ab51.
2024-06-15 21:57:46 +01:00
Yat Ho
0f1aaf11e0
docs: fix default value in docs (#6919) 2024-06-15 21:57:22 +01:00
Yat Ho
1f10c50979
ci: bump clang-tidy from 14 to 18 (#6923)
* ci: bump `clang-tidy` from 14 to 18

`clang-tidy-14` has been crashing when being run on `peer-mgr.cc` since 96de1706af.

According to https://github.com/llvm/llvm-project/issues/95631, upgrading to `clang-tidy-18` fixes this.

* chore: workaround clang-tidy false positives

* fix: limit nolint comment scope

* code review: try avoiding false positive without nolint
2024-06-15 21:06:37 +01:00
Cœur
febfe49ca3
bump miniupnpc to 2.2.8 (#6907)
* bump miniupnpc to 2.2.8

* Avoid build error "ln: include/miniupnpc/.: Operation not permitted"
2024-06-15 00:24:06 +01:00
Cœur
ec6112e0b1
fix compile error: no matching function for call to ‘flock::flock‘ (#6908)
* fix compile error with gcc 8.2: no matching function for call to ‘flock::flock(tr_sys_file_t&, const int&)

* #error temp checking which pipelines have XFS

* code review: removing duplicate include

---------

Co-authored-by: yunhai <haihai107@126.com>
2024-06-12 02:34:47 +01:00
niol
d42d0f3f3f
build with -latomic on platforms that need it (#6774) 2024-06-04 21:59:13 +01:00
Dzmitry Neviadomski
b565e076a9
chore: update older macos build verification workflow to macos-12 (#6883)
* chore: update older macos build verification workflow to macos-12

Manually set Xcode to the version was used in macos-11 runner.

See:
https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md#xcode
https://github.com/actions/runner-images/blob/main/images/macos/macos-11-Readme.md#xcode

* chore: re-enable macos-12 build verification action

* chore: disable gtk and qt clients explicitly with macos build verification

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-06-03 21:47:51 +01:00
Cœur
489de60222
Remove warning "don't cut off end" (#6890) 2024-06-03 21:46:15 +01:00
Yat Ho
2c2011d40f
fix: update partial file suffix after verifying torrent (#6871)
* fix: only update piece completion if different

* fix: remove return statement from void function

* refactor: add `tr_torrent::has_file()`

* fix: update file suffixes after verifying torrent

* fix: tests
2024-06-01 19:44:01 -05:00
Cœur
acee39e15c
refactor: harden idle_seconds (#6834)
* Hardening idle_seconds

* code review: revert "size_t to time_t" because time_t isn't guaranted to be signed in the C spec
2024-06-01 17:41:44 -05:00
Yat Ho
78027a8e5b
refactor: cleanup build for miniupnp (#6665)
* fix: remove redundant/outdated miniupnp cmake definitions

* refactor: simplify miniupnpc includes

* fix path to miniupnp

* fix: Xcode project

* fixup! fix: Xcode project

* code review: Xcode changes from mikedld

* refactor: drop miniupnpc support below `1.7`
2024-06-01 20:10:52 +01:00
Yat Ho
efec65050e
fix: don't call tr_logAddTraceIo before tr_peerIo::set_socket() (#6881)
* fix: add fallback for invalid address display name

* fix: only call tr_logAddTraceIo after `tr_peerIo::set_socket()` is called

* chore: housekeeping

* code review: handle `nullptr` from `inet_ntop()` instead

* code review: remove unclear comment

* code review: dedupe peerIo bandwidth log
2024-06-01 16:23:46 +01:00
Laura Kirsch
f3f887c93e
libtransmission: fix copyright header generation (#6874)
This is related to #4850 since libtransmission/mime-types.js generates
libtransmission/mime-types.h and puts it there.
Also add a note that the file is automatically generated for good measure.
2024-06-01 15:39:33 +01:00
Charles Kerr
2b75869c80
fix: cert-err58 warning in declaration of TrayIconName, AppIconName, AppName (#6861) 2024-06-01 10:50:51 +01:00
Yat Ho
a18fca5950
fix: reset wishlist when stopping torrent (#6869) 2024-05-29 14:35:07 -05:00
github-actions[bot]
d467fa1f7f
chore: update generated transmission-web files (#6865)
Co-authored-by: ckerr <70381+ckerr@users.noreply.github.com>
2024-05-27 20:16:23 -05:00
niol
63e74f4df8
webui: enable click to hide inspector (#6863) 2024-05-27 20:12:43 -05:00
Charles Kerr
6132706565
chore: iwyu (#6864)
* chore: do not include <set> unless we use it

* chore: do not include <map> unless we use it

* chore: do not include <string> unless we use it

* chore: do not include <list> unless we use it

* chore: do not include <memory> unless we use it

* chore: do not include <optional> unless we use it

* chore: do not include <functional> unless we use it
2024-05-27 17:36:02 -05:00
Luukas Pörtfors
88d280be8f
feat(remote): implement idle seeding limits (#2947) 2024-05-27 15:08:33 -05:00
Yat Ho
96de1706af
perf: restore 3.00 peer info (atom) pool pruning (#6712)
* refactor: remove inactive peer info housekeeping

* refactor: restore atom pulse (now peer info pulse)

* refactor: don't hard cap peer info limit
2024-05-26 16:34:26 -05:00
Yat Ho
4657d210ba
feat: dual stack udp tracker support (#6687)
* chore: housekeeping

* refactor: reduce copying when building payloads

* feat: dual-stack udp tracker support

* refactor: convert function names to snake_case

* fix: `readability-identifier-naming` warning

* fix: account for dual-stack in tests

* code review: add prefix to global names

* fix: don't resolve to IPv4-mapped address

* refactor: use `tr_address` method to check ip protocol

* fix: workaround MSVC x86 build failure

* fix: handle host components that has square brackets

* Partial Revert: "fix: account for dual-stack in tests"

Not needed anymore

* fix: store ipv6 peers in pex6

---------

Co-authored-by: Charles Kerr <charles@charleskerr.com>
2024-05-26 15:43:55 -05:00
Yat Ho
3677e7a591
chore: resume file remove redundant have key and other cleanup (#6747)
* fix: don't save blocks bitfield to resume when we are seed

* chore: housekeeping

* code review: replace `auto` with `tr_resume::fields_t`

* code review: remove the `have` key in resume

* fix: `blocks` error message
2024-05-26 13:02:42 -05:00
Yat Ho
d6f5e60a35
feat: ipv6 lpd (#6700)
* feat: ipv6 lpd

* feat: find interface index from ip address

* refactor: use `tr_socket_address::from_string()`

* fix: enable multicast loop

* chore: housekeeping

* refactor: dedupe `if_nametoindex()` call

* refactor: rename `mcast_socket_` to `mcast_sockets_`

* code review: fix variable name typo

* code review: unify comment styles

* fixup! code review: unify comment styles

* code review: explain 15KB in Win32 interface index code
2024-05-26 00:04:50 -05:00
Yat Ho
5f091fac18
refactor: store peer info objects in shared pointers (#6614)
* chore: housekeeping

* refactor: store peer info objects in shared_ptr

* refactor: minimise insert/erase operations to the peer info pool

* refactor: unify `on_got_port()` exit point to simplify cleanup

* fix: use `std::unordered_map` as a stand-in for `small::map`

* refactor: use small maps but with `std::vector` as base

* fix: suppress goto warning

* refactor: use `small::map`

* fix: remove constexpr
2024-05-25 19:13:15 -05:00
Cœur
92478ec849
refactor: rename "UpdateQueue" to "UpdateTorrentsState" (#6613) 2024-05-25 18:43:08 -05:00
Cœur
bf0119dd3f
Recommended Project Settings (#6591)
* Recommended Project Settings

* adding script inputs and outputs

* fix path to evconfig-private.h

* disabling sandboxing for retrieving the git commit hash

* Also Enable Parallelization in Command Line Builds Using '-target'.

* remove superfluous "per-target" settings

* fix settings.h explicit file type.

* fix path to miniupnp

* ensuring objectVersion = 51 in code_style.
2024-05-25 17:42:15 -05:00
Yat Ho
17c6ec755c
feat: allow port forwarding state to recover from error (#6718)
* feat: allow upnp to recover from errors

* feat: allow natpmp to recover from errors

* chore: housekeeping

* code review: explicitly list all states to start discovering from

* fix: recover from failed UPnP discovery

* refactor: remove `UpnpState::Failed`
2024-05-25 17:08:16 -05:00
Yat Ho
edddf9d80e
fix: torrent details speed info unit (#6845) 2024-05-25 16:43:38 -05:00
Charles Kerr
c465575dab
build: temporarily disable macos-11 sanity checks (#6858)
re-enable when brew is fixed
2024-05-25 15:59:52 -05:00
Dzmitry Neviadomski
acd0c22a3d
refactor: add virtual destructor to the polymorphic Settings class (#6786)
Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-05-25 15:20:40 -05:00
github-actions[bot]
c2e12cbf52
chore: update generated transmission-web files (#6857)
Co-authored-by: ckerr <70381+ckerr@users.noreply.github.com>
2024-05-25 14:37:21 -05:00
Yat Ho
381c17e0bb
webui: fixed width for speed info (#6739)
* webui: fixed width for speed info

* fix: match download icon-text gap with upload

* webui: move speed arrow to the right of the text
2024-05-25 13:32:01 -05:00
orangepizza
8bb49e3fdf
fix: building with mbedtls 3.X (#6822)
uses renamed funtion name for mbedtls 3.x

Signed-off-by: Seo Suchan <tjtncks@gmail.com>
2024-05-25 12:41:03 -05:00
niol
be67b33f42
systemd service documentation key (#6781)
Co-authored-by: Barak A. Pearlmutter <barak+git@pearlmutter.net>
2024-05-25 11:51:51 -05:00
Yat Ho
9748f42c5a
fix: restore portable file path check (#6853)
* chore: change to snake_case naming

* fix: restore portable file path check

* fix: macosx build
2024-05-25 10:08:53 -05:00
niol
adc405e5be
fir scripts, document usage of systemd overrides rather than changing /lib files (#6800) 2024-05-24 15:52:10 -05:00
Dzmitry Neviadomski
e8a72d9c5d
Default initialize sleep callback duration in tr_verify_worker (#6789)
std::chrono::duration is just a wrapper, underlying numerical value member will be default initialized to zero as expected.

See https://en.cppreference.com/w/cpp/chrono/duration

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-05-24 15:51:16 -05:00
Yat Ho
09b67c84b1
fix: possible heap-use-after-free with magnet links (#6815)
* fix: queue torrent verification as soon as metadata complete

* fix: avoid heap-use-after-free in `tr_peerMsgsImpl::process_peer_message()`

Details: https://github.com/transmission/transmission/pull/6383#discussion_r1429202253

* code review: move `tr_torrent::do_magnet_idle_work()` to private

* code review: `std::deque::empty()` is not `constexpr`

* fix: test
2024-05-24 15:50:01 -05:00
Cœur
740ce3b904
Avoiding "sec" as abbreviation (#6828)
* Avoiding "sec" as abbreviation

* code review: KBit -> Kb
2024-05-24 10:34:05 -05:00
Cœur
0a299bbbe8
docs: add notes on how to build Qt app with CMake (#6814) 2024-05-24 10:33:24 -05:00
Dzmitry Neviadomski
78c82b4526
Update outdated Doxygen params refs for tr_recentHistory (#6791)
Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-05-24 10:31:50 -05:00
Dzmitry Neviadomski
f24582ea2b
Fix incorrect value for SortIncludes in .clang-format (#6784)
See https://clang.llvm.org/docs/ClangFormatStyleOptions.html#sortincludes

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-05-24 10:31:21 -05:00
Cœur
1be53ac139
removing temporary workaround for sanitizer crashes (#6802) 2024-05-24 10:16:46 -05:00
Yat Ho
b6363fcf47
fix: store seconds downloading/seeding when stopping torrent (#6844)
* fix: save seconds downloading when stopping torrent

* fix: save seconds seeding when stopping torrent
2024-05-24 10:12:30 -05:00
Yat Ho
459c8d3791
fix: escape single file torrent name (#6846) 2024-05-24 10:08:33 -05:00
130 changed files with 2056 additions and 1358 deletions

View File

@ -32,7 +32,7 @@ PenaltyBreakBeforeFirstCallParameter: 0
PenaltyReturnTypeOnItsOwnLine: 1000
PointerAlignment: Left
ReflowComments: false
SortIncludes: false
SortIncludes: Never
SpaceAfterCStyleCast: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false

View File

@ -147,11 +147,6 @@ jobs:
libssl-dev \
ninja-build \
npm
- name: Temporary workaround for sanitizer crashes
# https://bugs.launchpad.net/ubuntu/+source/llvm-toolchain-14/+bug/2048768
# https://github.com/actions/runner-images/issues/9491
# https://github.com/actions/runner-images/pull/9513
run: sudo sysctl vm.mmap_rnd_bits=28
- name: Get Source
uses: actions/checkout@v4
with:
@ -226,7 +221,7 @@ jobs:
run: cmake -E chdir obj ctest -j $(nproc) --build-config Debug --output-on-failure
clang-tidy-libtransmission:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs: [ what-to-make ]
if: ${{ needs.what-to-make.outputs.test-style == 'true' }}
steps:
@ -335,9 +330,9 @@ jobs:
name: binaries-${{ github.job }}
path: pfx/**/*
# Only verify build support on old macOS
macos-11:
runs-on: macos-11
# Only verify build support on older macOS and SDK
macos-12:
runs-on: macos-12
needs: [ what-to-make ]
if: ${{ needs.what-to-make.outputs.make-mac == 'true' }}
steps:
@ -354,6 +349,9 @@ jobs:
with:
path: src
submodules: recursive
- name: Set Xcode to 13.2.1
run: |
sudo xcode-select --switch /Applications/Xcode_13.2.1.app
- name: Configure
run: |
cmake \
@ -363,8 +361,9 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=pfx \
-DCMAKE_OSX_ARCHITECTURES='x86_64' \
-DCMAKE_PREFIX_PATH=`brew --prefix`/opt/qt \
-DENABLE_GTK=OFF \
-DENABLE_MAC=${{ (needs.what-to-make.outputs.make-mac == 'true') && 'ON' || 'OFF' }} \
-DENABLE_QT=OFF \
-DENABLE_TESTS=OFF \
-DENABLE_WERROR=ON \
-DRUN_CLANG_TIDY=OFF
@ -613,7 +612,6 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=pfx \
-DCMAKE_OSX_ARCHITECTURES='x86_64;arm64' \
-DCMAKE_PREFIX_PATH=`brew --prefix`/opt/qt \
-DENABLE_CLI=${{ (needs.what-to-make.outputs.make-cli == 'true') && 'ON' || 'OFF' }} \
-DENABLE_DAEMON=${{ (needs.what-to-make.outputs.make-daemon == 'true') && 'ON' || 'OFF' }} \
-DENABLE_GTK=OFF \
@ -990,3 +988,79 @@ jobs:
working-directory: ./android
run: |
gradle build
ubuntu-24-04-cxx-23:
needs: [ make-source-tarball, what-to-make ]
if: ${{ needs.what-to-make.outputs.make-cli == 'true' || needs.what-to-make.outputs.make-daemon == 'true' || needs.what-to-make.outputs.make-gtk == 'true' || needs.what-to-make.outputs.make-qt == 'true' || needs.what-to-make.outputs.make-tests == 'true' || needs.what-to-make.outputs.make-utils == 'true' }}
runs-on: ubuntu-24.04
steps:
- name: Show Configuration
run: |
echo '${{ toJSON(needs) }}'
echo '${{ toJSON(runner) }}'
cat /etc/os-release
- name: Get Dependencies
run: |
set -ex
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
appstream \
ca-certificates \
clang \
cmake \
gettext \
libcurl4-openssl-dev \
libdeflate-dev \
libevent-dev \
libfmt-dev \
libminiupnpc-dev \
libnatpmp-dev \
libpsl-dev \
libssl-dev \
ninja-build \
npm
- name: Get Dependencies (GTK)
if: ${{ needs.what-to-make.outputs.make-gtk == 'true' }}
run: sudo apt-get install -y --no-install-recommends libglibmm-2.4-dev libgtkmm-3.0-dev
- name: Get Dependencies (Qt)
if: ${{ needs.what-to-make.outputs.make-qt == 'true' }}
run: sudo apt-get install -y --no-install-recommends qtbase5-dev libqt5svg5-dev qttools5-dev
- name: Get Source
uses: actions/checkout@v4
with:
submodules: recursive
path: src
- name: Configure
run: |
cmake \
-S src \
-B obj \
-G Ninja \
-DCMAKE_CXX_STANDARD=23 \
-DCMAKE_CXX_COMPILER='clang++' \
-DCMAKE_C_COMPILER='clang' \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=pfx \
-DENABLE_CLI=${{ (needs.what-to-make.outputs.make-cli == 'true') && 'ON' || 'OFF' }} \
-DENABLE_DAEMON=${{ (needs.what-to-make.outputs.make-daemon == 'true') && 'ON' || 'OFF' }} \
-DENABLE_GTK=${{ (needs.what-to-make.outputs.make-gtk == 'true') && 'ON' || 'OFF' }} \
-DENABLE_MAC=OFF \
-DENABLE_QT=${{ (needs.what-to-make.outputs.make-qt == 'true') && 'ON' || 'OFF' }} \
-DENABLE_TESTS=OFF \
-DENABLE_UTILS=${{ (needs.what-to-make.outputs.make-utils == 'true') && 'ON' || 'OFF' }} \
-DREBUILD_WEB=${{ (needs.what-to-make.outputs.make-web == 'true') && 'ON' || 'OFF' }} \
-DENABLE_WERROR=ON \
-DRUN_CLANG_TIDY=OFF
- name: Make
run: cmake --build obj --config RelWithDebInfo -- "-k 0"
- name: Test
if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
env:
TMPDIR: /private/tmp
run: cmake -E chdir obj ctest -j $(nproc) --build-config RelWithDebInfo --output-on-failure
- name: Install
run: cmake --build obj --config RelWithDebInfo --target install/strip
- uses: actions/upload-artifact@v4
with:
name: binaries-${{ github.job }}
path: pfx/**/*

View File

@ -169,9 +169,13 @@ set(TR_VCS_REVISION_FILE "${PROJECT_SOURCE_DIR}/REVISION")
## Compiler standard version
set(CMAKE_C_STANDARD 11)
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 11)
endif()
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(EXISTS ${PROJECT_SOURCE_DIR}/.git)
@ -526,17 +530,9 @@ if(NOT USE_SYSTEM_MINIUPNPC)
target_compile_definitions(miniupnpc::libminiupnpc
INTERFACE
MINIUPNP_STATICLIB)
set(MINIUPNPC_VERSION 2.2)
set(MINIUPNPC_API_VERSION 17)
endif()
unset(TR_MINIUPNPC_LIBNAME)
target_compile_definitions(miniupnpc::libminiupnpc
INTERFACE
SYSTEM_MINIUPNP
$<$<VERSION_LESS:${MINIUPNPC_VERSION},1.7>:MINIUPNPC_API_VERSION=${MINIUPNPC_API_VERSION}>) # API version macro was only added in 1.7
add_subdirectory(${TR_THIRD_PARTY_SOURCE_DIR}/wildmat)
tr_add_external_auto_library(DHT dht dht

View File

@ -75,8 +75,8 @@
A20162C913DE48BF00E15488 /* receivedata.c in Sources */ = {isa = PBXBuildFile; fileRef = A20162C713DE48BF00E15488 /* receivedata.c */; };
A20162CA13DE48BF00E15488 /* receivedata.h in Headers */ = {isa = PBXBuildFile; fileRef = A20162C813DE48BF00E15488 /* receivedata.h */; };
A20162CD13DE497000E15488 /* portlistingparse.c in Sources */ = {isa = PBXBuildFile; fileRef = A20162CB13DE497000E15488 /* portlistingparse.c */; };
A20162CE13DE497000E15488 /* portlistingparse.h in Headers */ = {isa = PBXBuildFile; fileRef = A20162CC13DE497000E15488 /* portlistingparse.h */; };
A20162D013DE49E500E15488 /* miniupnpctypes.h in Headers */ = {isa = PBXBuildFile; fileRef = A20162CF13DE49E500E15488 /* miniupnpctypes.h */; };
A20162CE13DE497000E15488 /* portlistingparse.h in Headers */ = {isa = PBXBuildFile; fileRef = A20162CC13DE497000E15488 /* portlistingparse.h */; settings = {ATTRIBUTES = (Private, ); }; };
A20162D013DE49E500E15488 /* miniupnpctypes.h in Headers */ = {isa = PBXBuildFile; fileRef = A20162CF13DE49E500E15488 /* miniupnpctypes.h */; settings = {ATTRIBUTES = (Private, ); }; };
A2074F4C12BEA8CE00F70985 /* buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = A2074F4B12BEA8CE00F70985 /* buffer.c */; };
A2074F5912BEA8E000F70985 /* bufferevent_filter.c in Sources */ = {isa = PBXBuildFile; fileRef = A2074F5012BEA8E000F70985 /* bufferevent_filter.c */; };
A2074F5B12BEA8E000F70985 /* bufferevent_pair.c in Sources */ = {isa = PBXBuildFile; fileRef = A2074F5212BEA8E000F70985 /* bufferevent_pair.c */; };
@ -264,14 +264,14 @@
A2FB701C0D95CAEA0001F331 /* GroupsController.mm in Sources */ = {isa = PBXBuildFile; fileRef = A2FB701B0D95CAEA0001F331 /* GroupsController.mm */; };
A47A7C87B8B57BE50DF0D410 /* torrent-files.cc in Sources */ = {isa = PBXBuildFile; fileRef = A47A7C87B8B57BE50DF0D411 /* torrent-files.cc */; };
A47A7C87B8B57BE50DF0D412 /* torrent-files.h in Headers */ = {isa = PBXBuildFile; fileRef = A47A7C87B8B57BE50DF0D413 /* torrent-files.h */; };
BE1183580CE160C50002D0F3 /* miniupnpc_declspec.h in Headers */ = {isa = PBXBuildFile; fileRef = BE11834E0CE160C50002D0F3 /* miniupnpc_declspec.h */; };
BE1183590CE160C50002D0F3 /* igd_desc_parse.h in Headers */ = {isa = PBXBuildFile; fileRef = BE11834F0CE160C50002D0F3 /* igd_desc_parse.h */; };
BE1183580CE160C50002D0F3 /* miniupnpc_declspec.h in Headers */ = {isa = PBXBuildFile; fileRef = BE11834E0CE160C50002D0F3 /* miniupnpc_declspec.h */; settings = {ATTRIBUTES = (Private, ); }; };
BE1183590CE160C50002D0F3 /* igd_desc_parse.h in Headers */ = {isa = PBXBuildFile; fileRef = BE11834F0CE160C50002D0F3 /* igd_desc_parse.h */; settings = {ATTRIBUTES = (Private, ); }; };
BE11835A0CE160C50002D0F3 /* minixml.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183500CE160C50002D0F3 /* minixml.h */; };
BE11835B0CE160C50002D0F3 /* miniwget.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183510CE160C50002D0F3 /* miniwget.h */; };
BE11835B0CE160C50002D0F3 /* miniwget.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183510CE160C50002D0F3 /* miniwget.h */; settings = {ATTRIBUTES = (Private, ); }; };
BE11835C0CE160C50002D0F3 /* minisoap.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183520CE160C50002D0F3 /* minisoap.h */; };
BE11835D0CE160C50002D0F3 /* upnpreplyparse.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183530CE160C50002D0F3 /* upnpreplyparse.h */; };
BE11835E0CE160C50002D0F3 /* upnpcommands.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183540CE160C50002D0F3 /* upnpcommands.h */; settings = {ATTRIBUTES = (Public, ); }; };
BE11835F0CE160C50002D0F3 /* miniupnpc.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183550CE160C50002D0F3 /* miniupnpc.h */; settings = {ATTRIBUTES = (Public, ); }; };
BE11835D0CE160C50002D0F3 /* upnpreplyparse.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183530CE160C50002D0F3 /* upnpreplyparse.h */; settings = {ATTRIBUTES = (Private, ); }; };
BE11835E0CE160C50002D0F3 /* upnpcommands.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183540CE160C50002D0F3 /* upnpcommands.h */; settings = {ATTRIBUTES = (Private, ); }; };
BE11835F0CE160C50002D0F3 /* miniupnpc.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183550CE160C50002D0F3 /* miniupnpc.h */; settings = {ATTRIBUTES = (Private, ); }; };
BE1183600CE160C50002D0F3 /* minissdpc.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183560CE160C50002D0F3 /* minissdpc.h */; };
BE1183690CE160D50002D0F3 /* igd_desc_parse.c in Sources */ = {isa = PBXBuildFile; fileRef = BE1183610CE160D50002D0F3 /* igd_desc_parse.c */; };
BE11836A0CE160D50002D0F3 /* minixml.c in Sources */ = {isa = PBXBuildFile; fileRef = BE1183620CE160D50002D0F3 /* minixml.c */; };
@ -325,7 +325,7 @@
C11DEA161FCD31C0009E22B9 /* subprocess-posix.cc in Sources */ = {isa = PBXBuildFile; fileRef = C11DEA141FCD31C0009E22B9 /* subprocess-posix.cc */; };
C11DEA171FCD31C0009E22B9 /* subprocess.h in Headers */ = {isa = PBXBuildFile; fileRef = C11DEA151FCD31C0009E22B9 /* subprocess.h */; };
C12F19791E1AE3C30005E93F /* upnperrors.c in Sources */ = {isa = PBXBuildFile; fileRef = C12F19771E1AE3C30005E93F /* upnperrors.c */; };
C12F197B1E1AE4460005E93F /* upnperrors.h in Headers */ = {isa = PBXBuildFile; fileRef = C12F197A1E1AE4460005E93F /* upnperrors.h */; };
C12F197B1E1AE4460005E93F /* upnperrors.h in Headers */ = {isa = PBXBuildFile; fileRef = C12F197A1E1AE4460005E93F /* upnperrors.h */; settings = {ATTRIBUTES = (Private, ); }; };
C1305EBE186A13B100F03351 /* file.cc in Sources */ = {isa = PBXBuildFile; fileRef = C1305EB8186A134000F03351 /* file.cc */; };
C1425B361EE9C605001DB85F /* tr-assert.h in Headers */ = {isa = PBXBuildFile; fileRef = C1425B331EE9C5EA001DB85F /* tr-assert.h */; };
C1425B371EE9C705001DB85F /* tr-macros.h in Headers */ = {isa = PBXBuildFile; fileRef = C1425B341EE9C5EA001DB85F /* tr-macros.h */; };
@ -353,7 +353,7 @@
C1846BA3294F7A6800A98F30 /* wildmat.h in Headers */ = {isa = PBXBuildFile; fileRef = C1846B87294F781800A98F30 /* wildmat.h */; };
C1846BA9294F7B5A00A98F30 /* libwildmat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C1846B9E294F7A3400A98F30 /* libwildmat.a */; };
C1BF7BA81F2A3CB7008E88A7 /* upnpdev.c in Sources */ = {isa = PBXBuildFile; fileRef = C1BF7BA71F2A3CB7008E88A7 /* upnpdev.c */; };
C1BF7BAA1F2A3CCE008E88A7 /* upnpdev.h in Headers */ = {isa = PBXBuildFile; fileRef = C1BF7BA91F2A3CCE008E88A7 /* upnpdev.h */; };
C1BF7BAA1F2A3CCE008E88A7 /* upnpdev.h in Headers */ = {isa = PBXBuildFile; fileRef = C1BF7BA91F2A3CCE008E88A7 /* upnpdev.h */; settings = {ATTRIBUTES = (Private, ); }; };
C1F690FD1AD0627500D95CF0 /* daemon-posix.cc in Sources */ = {isa = PBXBuildFile; fileRef = C1F690FC1AD0627500D95CF0 /* daemon-posix.cc */; };
C1FEE5781C3223CC00D62832 /* watchdir-generic.cc in Sources */ = {isa = PBXBuildFile; fileRef = C1FEE5731C3223CC00D62832 /* watchdir-generic.cc */; };
C1FEE5791C3223CC00D62832 /* watchdir-kqueue.cc in Sources */ = {isa = PBXBuildFile; fileRef = C1FEE5741C3223CC00D62832 /* watchdir-kqueue.cc */; };
@ -1367,7 +1367,7 @@
ED20B87D285892C5005FA6BE /* crc32_multipliers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = crc32_multipliers.h; path = lib/crc32_multipliers.h; sourceTree = "<group>"; };
ED20B87E285892C5005FA6BE /* crc32_tables.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = crc32_tables.h; path = lib/crc32_tables.h; sourceTree = "<group>"; };
ED67FB402B70FCE400D8A037 /* settings.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = settings.cc; sourceTree = "<group>"; };
ED67FB412B70FCE400D8A037 /* settings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = settings.h; sourceTree = "<group>"; };
ED67FB412B70FCE400D8A037 /* settings.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = settings.h; sourceTree = "<group>"; };
ED86936D2ADAE34D00342B1A /* DefaultAppHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DefaultAppHelper.h; sourceTree = "<group>"; };
ED86936E2ADAE34D00342B1A /* DefaultAppHelper.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DefaultAppHelper.mm; sourceTree = "<group>"; };
ED8A163B2735A8AA000D61F9 /* peer-mgr-active-requests.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "peer-mgr-active-requests.h"; sourceTree = "<group>"; };
@ -2716,8 +2716,8 @@
isa = PBXNativeTarget;
buildConfigurationList = BE11834C0CE160A80002D0F3 /* Build configuration list for PBXNativeTarget "miniupnp" */;
buildPhases = (
A2305097100C0293003FDB0C /* ShellScript */,
C12F197C1E1AE55A0005E93F /* ShellScript */,
A2305097100C0293003FDB0C /* updateminiupnpcstrings */,
C12F197C1E1AE55A0005E93F /* symlinks */,
BE1183440CE160960002D0F3 /* Headers */,
BE1183450CE160960002D0F3 /* Sources */,
BE1183460CE160960002D0F3 /* Frameworks */,
@ -2909,7 +2909,8 @@
29B97313FDCFA39411CA2CEA /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1420;
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1530;
ORGANIZATIONNAME = "The Transmission Project";
TargetAttributes = {
8D1107260486CEB800E47090 = {
@ -3040,16 +3041,19 @@
files = (
);
inputPaths = (
"update-version-h.sh",
CMakeLists.txt,
);
name = "Generate version file";
outputPaths = (
"$(SRCROOT)/libtransmission/version.h",
"$(SRCROOT)/libtransmission/version.h.new",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "sh update-version-h.sh\n";
};
A2305097100C0293003FDB0C /* ShellScript */ = {
A2305097100C0293003FDB0C /* updateminiupnpcstrings */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -3059,6 +3063,7 @@
"third-party/miniupnp/miniupnpc/miniupnpcstrings.h.in",
"third-party/miniupnp/miniupnpc/updateminiupnpcstrings.sh",
);
name = updateminiupnpcstrings;
outputPaths = (
"third-party/miniupnp/miniupnpc/miniupnpcstrings.h",
);
@ -3077,26 +3082,27 @@
);
name = "Copy libevent headers";
outputPaths = (
"$(SRCROOT)/third-party/libevent/evconfig-private.h",
"$(SRCROOT)/third-party/libevent/include/evconfig-private.h",
"$(SRCROOT)/third-party/libevent/include/event2/event-config.h",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/bash;
shellScript = "cd third-party/libevent/include/event2\n\nif [ ! -e event-config.h -a ! ../../../macosx-libevent-event-config.h -ef event-config.h ]; then\n ln -s ../../../macosx-libevent-event-config.h event-config.h;\nfi\n\nif [ ! -e ../evconfig-private.h -a ! ../../macosx-libevent-evconfig-private.h -ef ../evconfig-private.h ]; then\n ln -s ../../macosx-libevent-evconfig-private.h ../evconfig-private.h;\nfi\n";
};
C12F197C1E1AE55A0005E93F /* ShellScript */ = {
C12F197C1E1AE55A0005E93F /* symlinks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = symlinks;
outputPaths = (
"third-party/miniupnpc/miniupnp",
"third-party/miniupnp/miniupnpc/include/miniupnpc",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd third-party/miniupnp && rm -f miniupnp && ln -s . miniupnp\n";
shellScript = "cd third-party/miniupnp/miniupnpc\nln -snf . include/miniupnpc\n";
};
C12F197E1E1AE6D50005E93F /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
@ -3952,7 +3958,6 @@
0053D3D30C86774200545606 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GCC_WARN_UNUSED_FUNCTION = NO;
@ -3967,6 +3972,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
@ -4005,7 +4011,7 @@
"third-party/libnatpmp/*.h",
"third-party/libpsl/include",
"third-party/libutp/include",
"third-party/miniupnpc/*.h",
"third-party/miniupnp/miniupnpc/include",
"third-party/utfcpp/source",
"third-party/wide-integer",
"third-party/wildmat",
@ -4022,9 +4028,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = macosx/Transmission.entitlements;
CODE_SIGN_IDENTITY = "-";
ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = macosx;
HEADER_SEARCH_PATHS = (
@ -4051,8 +4055,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -4071,8 +4073,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -4095,8 +4095,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -4143,6 +4141,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = c11;
GCC_DYNAMIC_NO_PIC = YES;
GCC_ENABLE_PASCAL_STRINGS = NO;
@ -4181,14 +4180,12 @@
OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)";
PRODUCT_BUNDLE_IDENTIFIER = org.m0k.transmission;
SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO;
};
name = Debug;
};
3C7A118E0D0B2EB800B5701F /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
OTHER_CFLAGS = "-DENABLE_STRNATPMPERR";
@ -4199,7 +4196,6 @@
3C7A118F0D0B2EB800B5701F /* Release - Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
OTHER_CFLAGS = "-DENABLE_STRNATPMPERR";
@ -4210,7 +4206,6 @@
3C7A11900D0B2EB800B5701F /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
OTHER_CFLAGS = "-DENABLE_STRNATPMPERR";
@ -4222,6 +4217,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
@ -4260,7 +4256,7 @@
"third-party/libnatpmp/*.h",
"third-party/libpsl/include",
"third-party/libutp/include",
"third-party/miniupnpc/*.h",
"third-party/miniupnp/miniupnpc/include",
"third-party/utfcpp/source",
"third-party/wide-integer",
"third-party/wildmat",
@ -4276,8 +4272,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -4297,9 +4291,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = macosx/Transmission.entitlements;
CODE_SIGN_IDENTITY = "-";
ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = macosx;
HEADER_SEARCH_PATHS = (
@ -4353,6 +4345,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEPLOYMENT_POSTPROCESSING = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = c11;
GCC_DYNAMIC_NO_PIC = YES;
GCC_ENABLE_PASCAL_STRINGS = NO;
@ -4397,7 +4390,6 @@
A22CFCBB0FC24F720009BD3E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
@ -4408,7 +4400,6 @@
A22CFCBC0FC24F720009BD3E /* Release - Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
@ -4419,7 +4410,6 @@
A22CFCBD0FC24F720009BD3E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
@ -4460,6 +4450,7 @@
DEPLOYMENT_POSTPROCESSING = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = c11;
GCC_DYNAMIC_NO_PIC = YES;
GCC_ENABLE_PASCAL_STRINGS = NO;
@ -4497,7 +4488,6 @@
OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)";
PRODUCT_BUNDLE_IDENTIFIER = org.m0k.transmission;
SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO;
};
name = "Release - Debug";
};
@ -4506,9 +4496,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = macosx/Transmission.entitlements;
CODE_SIGN_IDENTITY = "-";
ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = macosx;
HEADER_SEARCH_PATHS = (
@ -4535,8 +4523,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -4555,6 +4541,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
@ -4593,7 +4580,7 @@
"third-party/libnatpmp/*.h",
"third-party/libpsl/include",
"third-party/libutp/include",
"third-party/miniupnpc/*.h",
"third-party/miniupnp/miniupnpc/include",
"third-party/utfcpp/source",
"third-party/wide-integer",
"third-party/wildmat",
@ -4609,8 +4596,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -4633,8 +4618,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -4652,7 +4635,6 @@
A250CFF20CDA19680068B4B6 /* Release - Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GCC_WARN_UNUSED_FUNCTION = NO;
@ -4666,7 +4648,6 @@
A2E384CF130DFB1D001F501B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_PREPROCESSOR_DEFINITIONS = POSIX;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
@ -4677,7 +4658,6 @@
A2E384D0130DFB1D001F501B /* Release - Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_PREPROCESSOR_DEFINITIONS = POSIX;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
@ -4688,7 +4668,6 @@
A2E384D1130DFB1D001F501B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_PREPROCESSOR_DEFINITIONS = (
POSIX,
NS_BLOCK_ASSERTIONS,
@ -4703,7 +4682,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
GCC_PREFIX_HEADER = "macosx/QuickLookPlugin/QuickLookPlugin-Prefix.pch";
HEADER_SEARCH_PATHS = (
"$(inherited)",
@ -4731,7 +4709,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
GCC_PREFIX_HEADER = "macosx/QuickLookPlugin/QuickLookPlugin-Prefix.pch";
HEADER_SEARCH_PATHS = (
"$(inherited)",
@ -4759,7 +4736,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
GCC_PREFIX_HEADER = "macosx/QuickLookPlugin/QuickLookPlugin-Prefix.pch";
HEADER_SEARCH_PATHS = (
"$(inherited)",
@ -4786,7 +4762,6 @@
BE1183490CE160960002D0F3 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = miniupnp;
@ -4796,7 +4771,6 @@
BE11834A0CE160960002D0F3 /* Release - Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = miniupnp;
@ -4806,7 +4780,6 @@
BE11834B0CE160960002D0F3 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = miniupnp;
@ -4816,7 +4789,6 @@
BE75C34B0C729EB600DBEFE0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GCC_WARN_UNUSED_FUNCTION = NO;
@ -4831,8 +4803,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -4855,8 +4825,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -4874,7 +4842,6 @@
C1639A701A55F4D600E42033 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -4884,7 +4851,6 @@
C1639A711A55F4D600E42033 /* Release - Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -4894,7 +4860,6 @@
C1639A721A55F4D600E42033 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
@ -4904,7 +4869,6 @@
C1846B9B294F7A3400A98F30 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = wildmat;
@ -4914,7 +4878,6 @@
C1846B9C294F7A3400A98F30 /* Release - Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = wildmat;
@ -4924,7 +4887,6 @@
C1846B9D294F7A3400A98F30 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = wildmat;
@ -4934,7 +4896,6 @@
C3CEBBA627949CA000683BE0 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
@ -4946,7 +4907,6 @@
C3CEBBA727949CA000683BE0 /* Release - Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
@ -4958,7 +4918,6 @@
C3CEBBA827949CA000683BE0 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
@ -5016,8 +4975,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -5036,8 +4993,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -5056,8 +5011,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -5076,8 +5029,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -5096,8 +5047,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -5116,8 +5065,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -5136,8 +5083,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -5156,8 +5101,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,
@ -5176,8 +5119,6 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = (
"$(inherited)",
.,

View File

@ -167,8 +167,8 @@ void onTorrentFileDownloaded(tr_web::FetchResponse const& response)
{
if (c == 'g')
{
tr_optind = ind;
return my_optarg;
break;
}
}

43
cmake/CheckAtomic.cmake Normal file
View File

@ -0,0 +1,43 @@
# - Try to find if 64-bits atomics need -latomic linking
# Once done this will define
# HAVE_CXX_ATOMICS_WITHOUT_LIB - Whether atomic types work without -latomic
include(CheckCXXSourceCompiles)
include(CheckLibraryExists)
# Sometimes linking against libatomic is required for atomic ops, if
# the platform doesn't support lock-free atomics.
function(check_working_cxx_atomics VARNAME)
check_cxx_source_compiles("
#include <atomic>
int main() {
std::atomic<long long> x;
return std::atomic_is_lock_free(&x);
}
" ${VARNAME})
endfunction()
# Check for atomic operations.
if(MSVC)
# This isn't necessary on MSVC.
set(HAVE_CXX_ATOMICS_WITHOUT_LIB TRUE)
else()
# First check if atomics work without the library.
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB)
endif()
# If not, check if the library exists, and atomics work with it.
if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
check_library_exists(atomic __atomic_load_8 "" HAVE_LIBATOMIC)
if(NOT HAVE_LIBATOMIC)
message(STATUS "Host compiler appears to require libatomic, but cannot locate it.")
endif()
list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB)
list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "atomic")
if(NOT HAVE_CXX_ATOMICS_WITH_LIB)
message(FATAL_ERROR "Host compiler must support std::atomic!")
endif()
endif()

View File

@ -21,100 +21,6 @@ find_library(MINIUPNPC_LIBRARY
libminiupnpc
HINTS ${_MINIUPNPC_LIBDIR})
if(MINIUPNPC_INCLUDE_DIR)
if(_MINIUPNPC_VERSION)
set(MINIUPNPC_VERSION ${_MINIUPNPC_VERSION})
else()
file(STRINGS "${MINIUPNPC_INCLUDE_DIR}/miniupnpc/miniupnpc.h" MINIUPNPC_VERSION_STR
REGEX "^#define[\t ]+MINIUPNPC_VERSION[\t ]+\"[^\"]+\"")
if(MINIUPNPC_VERSION_STR MATCHES "\"([^\"]+)\"")
set(MINIUPNPC_VERSION "${CMAKE_MATCH_1}")
endif()
# Let's hope it's 1.7 or higher, since it provides
# MINIUPNPC_API_VERSION and we won't have to figure
# it out on our own
file(STRINGS "${MINIUPNPC_INCLUDE_DIR}/miniupnpc/miniupnpc.h" MINIUPNPC_API_VERSION_STR
REGEX "^#define[\t ]+MINIUPNPC_API_VERSION[\t ]+[0-9]+")
if(MINIUPNPC_API_VERSION_STR MATCHES "^#define[\t ]+MINIUPNPC_API_VERSION[\t ]+([0-9]+)")
set(MINIUPNPC_API_VERSION "${CMAKE_MATCH_1}")
endif()
endif()
if(MINIUPNPC_LIBRARY)
# Or maybe it's miniupnp 1.6
if(NOT DEFINED MINIUPNPC_API_VERSION)
file(WRITE ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckMiniUPnPC_1.6.c
"#include <stdlib.h>
#include <errno.h>
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>
int main()
{
struct UPNPDev * devlist;
struct UPNPUrls urls;
struct IGDdatas data;
char lanaddr[16];
char portStr[8];
char intPort[8];
char intClient[16];
upnpDiscover( 2000, NULL, NULL, 0, 0, &errno );
UPNP_GetValidIGD( devlist, &urls, &data, lanaddr, sizeof( lanaddr ) );
UPNP_GetSpecificPortMappingEntry( urls.controlURL, data.first.servicetype,
portStr, \"TCP\", intClient, intPort, NULL, NULL, NULL );
return 0;
}")
try_compile(_MINIUPNPC_HAVE_VERSION_1_6
${CMAKE_BINARY_DIR}
${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckMiniUPnPC_1.6.c
COMPILE_DEFINITIONS -DINCLUDE_DIRECTORIES=${MINIUPNPC_INCLUDE_DIR}
LINK_LIBRARIES ${MINIUPNPC_LIBRARY}
OUTPUT_VARIABLE OUTPUT)
if(_MINIUPNPC_HAVE_VERSION_1_6)
if(NOT DEFINED MINIUPNPC_VERSION)
set(MINIUPNPC_VERSION 1.6)
endif()
set(MINIUPNPC_API_VERSION 8)
endif()
endif()
# Or maybe it's miniupnp 1.5
if(NOT DEFINED MINIUPNPC_API_VERSION)
file(WRITE ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckMiniUPnPC_1.5.c
"#include <stdlib.h>
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>
int main()
{
struct UPNPDev * devlist;
struct UPNPUrls urls;
struct IGDdatas data;
char lanaddr[16];
char portStr[8];
char intPort[8];
char intClient[16];
upnpDiscover( 2000, NULL, NULL, 0 );
UPNP_GetValidIGD( devlist, &urls, &data, lanaddr, sizeof( lanaddr ) );
UPNP_GetSpecificPortMappingEntry( urls.controlURL, data.first.servicetype,
portStr, \"TCP\", intClient, intPort );
return 0;
}")
try_compile(_MINIUPNPC_HAVE_VERSION_1_5
${CMAKE_BINARY_DIR}
${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/CheckMiniUPnPC_1.5.c
COMPILE_DEFINITIONS -DINCLUDE_DIRECTORIES=${MlINIUPNPC_INCLUDE_DIR}
LINK_LIBRARIES ${MINIUPNPC_LIBRARY}
OUTPUT_VARIABLE OUTPUT)
if(_MINIUPNPC_HAVE_VERSION_1_5)
if(NOT DEFINED MINIUPNPC_VERSION)
set(MINIUPNPC_VERSION 1.5)
endif()
set(MINIUPNPC_API_VERSION 5)
endif()
endif()
endif()
endif()
set(MINIUPNPC_INCLUDE_DIRS ${MINIUPNPC_INCLUDE_DIR})
set(MINIUPNPC_LIBRARIES ${MINIUPNPC_LIBRARY})
@ -123,9 +29,7 @@ include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MINIUPNPC
REQUIRED_VARS
MINIUPNPC_LIBRARY
MINIUPNPC_INCLUDE_DIR
MINIUPNPC_API_VERSION
VERSION_VAR MINIUPNPC_VERSION)
MINIUPNPC_INCLUDE_DIR)
mark_as_advanced(MINIUPNPC_INCLUDE_DIR MINIUPNPC_LIBRARY)

View File

@ -65,6 +65,16 @@ if ! find_cfiles -exec "${clang_format_exe}" $clang_format_args '{}' '+'; then
exitcode=1
fi
# format Xcodeproj
if ! grep -q 'objectVersion = 51' Transmission.xcodeproj/project.pbxproj; then
echo 'project.pbxproj needs objectVersion = 51 for compatibility with Xcode 11'
exitcode=1
fi
if ! grep -q 'BuildIndependentTargetsInParallel = YES' Transmission.xcodeproj/project.pbxproj; then
echo 'please keep BuildIndependentTargetsInParallel in project.pbxproj'
exitcode=1
fi
# format JS
# but only if js has changed
git diff --cached --quiet -- "web/**" && exit $exitcode

View File

@ -193,7 +193,7 @@ auto onFileAdded(tr_session* session, std::string_view dirname, std::string_view
if (!tr_file_read(filename, content, &error))
{
tr_logAddWarn(fmt::format(
_("Couldn't read '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't read '{path}': {error} ({error_code})")),
fmt::arg("path", basename),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -218,7 +218,7 @@ auto onFileAdded(tr_session* session, std::string_view dirname, std::string_view
if (tr_torrentNew(ctor, nullptr) == nullptr)
{
tr_logAddError(fmt::format(_("Couldn't add torrent file '{path}'"), fmt::arg("path", basename)));
tr_logAddError(fmt::format(fmt::runtime(_("Couldn't add torrent file '{path}'")), fmt::arg("path", basename)));
}
else
{
@ -227,12 +227,12 @@ auto onFileAdded(tr_session* session, std::string_view dirname, std::string_view
if (test && trash)
{
tr_logAddInfo(fmt::format(_("Removing torrent file '{path}'"), fmt::arg("path", basename)));
tr_logAddInfo(fmt::format(fmt::runtime(_("Removing torrent file '{path}'")), fmt::arg("path", basename)));
if (auto error = tr_error{}; !tr_sys_path_remove(filename, &error))
{
tr_logAddError(fmt::format(
_("Couldn't remove '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't remove '{path}': {error} ({error_code})")),
fmt::arg("path", basename),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -620,7 +620,8 @@ bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_setting
}
else
{
std::cerr << fmt::format(_("Couldn't parse log level '{level}'"), fmt::arg("level", optstr)) << std::endl;
std::cerr << fmt::format(fmt::runtime(_("Couldn't parse log level '{level}'")), fmt::arg("level", optstr))
<< std::endl;
}
break;
@ -681,7 +682,7 @@ void tr_daemon::reconfigure()
}
configDir = tr_sessionGetConfigDir(my_session_);
tr_logAddInfo(fmt::format(_("Reloading settings from '{path}'"), fmt::arg("path", configDir)));
tr_logAddInfo(fmt::format(fmt::runtime(_("Reloading settings from '{path}'")), fmt::arg("path", configDir)));
tr_sessionSet(my_session_, load_settings(configDir));
tr_sessionReloadBlocklists(my_session_);
@ -705,7 +706,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
{
auto const error_code = errno;
auto const errmsg = fmt::format(
_("Couldn't initialize daemon: {error} ({error_code})"),
fmt::runtime(_("Couldn't initialize daemon: {error} ({error_code})")),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code));
printMessage(log_stream_, TR_LOG_ERROR, MyName, errmsg, __FILE__, __LINE__);
@ -717,7 +718,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
auto const* const cdir = this->config_dir_.c_str();
auto* session = tr_sessionInit(cdir, true, settings_);
tr_sessionSetRPCCallback(session, on_rpc_callback, this);
tr_logAddInfo(fmt::format(_("Loading settings from '{path}'"), fmt::arg("path", cdir)));
tr_logAddInfo(fmt::format(fmt::runtime(_("Loading settings from '{path}'")), fmt::arg("path", cdir)));
tr_sessionSaveSettings(session, cdir, settings_);
auto sv = std::string_view{};
@ -738,13 +739,13 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
auto const out = std::to_string(getpid());
tr_sys_file_write(fp, std::data(out), std::size(out), nullptr);
tr_sys_file_close(fp);
tr_logAddInfo(fmt::format(_("Saved pidfile '{path}'"), fmt::arg("path", sz_pid_filename)));
tr_logAddInfo(fmt::format(fmt::runtime(_("Saved pidfile '{path}'")), fmt::arg("path", sz_pid_filename)));
pidfile_created = true;
}
else
{
tr_logAddError(fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't save '{path}': {error} ({error_code})")),
fmt::arg("path", sz_pid_filename),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -775,7 +776,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
(void)tr_variantDictFindStrView(&settings_, TR_KEY_watch_dir, &dir);
if (!std::empty(dir))
{
tr_logAddInfo(fmt::format(_("Watching '{path}' for new torrent files"), fmt::arg("path", dir)));
tr_logAddInfo(fmt::format(fmt::runtime(_("Watching '{path}' for new torrent files")), fmt::arg("path", dir)));
auto handler = [session](std::string_view dirname, std::string_view basename)
{
@ -820,7 +821,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
{
auto const error_code = errno;
tr_logAddError(fmt::format(
_("Couldn't create event: {error} ({error_code})"),
fmt::runtime(_("Couldn't create event: {error} ({error_code})")),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
goto CLEANUP;
@ -830,7 +831,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
{
auto const error_code = errno;
tr_logAddError(fmt::format(
_("Couldn't add event: {error} ({error_code})"),
fmt::runtime(_("Couldn't add event: {error} ({error_code})")),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
goto CLEANUP;
@ -844,7 +845,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
{
auto const error_code = errno;
tr_logAddError(fmt::format(
_("Couldn't launch daemon event loop: {error} ({error_code})"),
fmt::runtime(_("Couldn't launch daemon event loop: {error} ({error_code})")),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
goto CLEANUP;

View File

@ -2,6 +2,7 @@
Description=Transmission BitTorrent Daemon
Wants=network-online.target
After=network-online.target
Documentation=man:transmission-daemon(1)
[Service]
User=transmission

View File

@ -36,6 +36,16 @@ cmake --build build -t transmission-gtk
./build/gtk/transmission-gtk
```
### Building the QT app with CMake ###
Install QT and build the app:
```bash
brew install qt
brew services start dbus
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_QT=ON -DENABLE_MAC=OFF
cmake --build build -t transmission-qt
./build/qt/transmission-qt
```
## On Unix ##
### Prerequisites ###

View File

@ -98,8 +98,8 @@ Here is a sample of the three basic types: respectively Boolean, Number and Stri
* **bind-address-ipv4:** String (default = "") Where to listen for peer connections. When no valid IPv4 address is provided, Transmission will bind to "0.0.0.0".
* **bind-address-ipv6:** String (default = "") Where to listen for peer connections. When no valid IPv6 address is provided, Transmission will try to bind to your default global IPv6 address. If that didn't work, then Transmission will bind to "::".
* **peer-congestion-algorithm:** String. This is documented on https://www.pps.jussieu.fr/~jch/software/bittorrent/tcp-congestion-control.html.
* **peer-limit-global:** Number (default = 240)
* **peer-limit-per-torrent:** Number (default = 60)
* **peer-limit-global:** Number (default = 200)
* **peer-limit-per-torrent:** Number (default = 50)
* **peer-socket-tos:** String (default = "default") Set the [Type-Of-Service (TOS)](https://en.wikipedia.org/wiki/Type_of_Service) parameter for outgoing TCP packets. Possible values are "default", "lowcost", "throughput", "lowdelay" and "reliability". The value "lowcost" is recommended if you're using a smart router, and shouldn't harm in any case.
#### Peer Port

View File

@ -46,4 +46,17 @@ Scripts which have not yet been ported and may not work with the latest version:
* https://github.com/jaboto/Transmission-script - (cron)script set network limits according to the number of clients in the network
## Security with systemd
`transmission-daemon`'s packaging has many permissions disabled as a standard safety measure. If your script needs more permissions than are provided by the default, users have [reported](https://github.com/transmission/transmission/issues/1951) that it can be resolved by changing to `NoNewPrivileges=false` in `/lib/systemd/system/transmission-daemon.service`.
`transmission-daemon`'s packaging has many permissions disabled as a standard safety measure. If your script needs more permissions than are provided by the default, users have [reported](https://github.com/transmission/transmission/issues/1951) that it can be resolved by changing to `NoNewPrivileges=false` using a systemd unit override.
```
$ sudo systemctl edit transmission-daemon.service
```
and add the following content to the override:
```
[Service]
NoNewPrivileges=false
```
and that override will be kept untouched by package upgrades.

View File

@ -395,7 +395,7 @@ void register_magnet_link_handler()
catch (Gio::Error const& e)
{
gtr_warning(fmt::format(
_("Couldn't register Transmission as a {content_type} handler: {error} ({error_code})"),
fmt::runtime(_("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", static_cast<int>(e.code()))));

View File

@ -715,15 +715,18 @@ void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
}
else if (!empty_creator && !empty_date)
{
str = fmt::format(_("Created by {creator} on {date}"), fmt::arg("creator", creator), fmt::arg("date", datestr));
str = fmt::format(
fmt::runtime(_("Created by {creator} on {date}")),
fmt::arg("creator", creator),
fmt::arg("date", datestr));
}
else if (!empty_creator)
{
str = fmt::format(_("Created by {creator}"), fmt::arg("creator", creator));
str = fmt::format(fmt::runtime(_("Created by {creator}")), fmt::arg("creator", creator));
}
else if (!empty_date)
{
str = fmt::format(_("Created on {date}"), fmt::arg("date", datestr));
str = fmt::format(fmt::runtime(_("Created on {date}")), fmt::arg("date", datestr));
}
else
{
@ -874,7 +877,8 @@ void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
[](auto sum, auto const* tor) { return sum + tr_torrentFileCount(tor); });
str = fmt::format(
ngettext("{total_size} in {file_count:L} file", "{total_size} in {file_count:L} files", file_count),
fmt::runtime(
ngettext("{total_size} in {file_count:L} file", "{total_size} in {file_count:L} files", file_count)),
fmt::arg("total_size", tr_strlsize(total_size)),
fmt::arg("file_count", file_count));
@ -888,10 +892,10 @@ void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
{
str += ' ';
str += fmt::format(
ngettext(
fmt::runtime(ngettext(
"({piece_count} BitTorrent piece @ {piece_size})",
"({piece_count} BitTorrent pieces @ {piece_size})",
piece_count),
piece_count)),
fmt::arg("piece_count", piece_count),
fmt::arg("piece_size", Memory{ piece_size, Memory::Units::Bytes }.to_string()));
}
@ -933,7 +937,7 @@ void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
if (haveUnchecked == 0 && leftUntilDone == 0)
{
str = fmt::format(
_("{current_size} ({percent_done}%)"),
fmt::runtime(_("{current_size} ({percent_done}%)")),
fmt::arg("current_size", total),
fmt::arg("percent_done", buf2));
}
@ -941,7 +945,7 @@ void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
{
str = fmt::format(
// xgettext:no-c-format
_("{current_size} ({percent_done}% of {percent_available}% available)"),
fmt::runtime(_("{current_size} ({percent_done}% of {percent_available}% available)")),
fmt::arg("current_size", total),
fmt::arg("percent_done", buf2),
fmt::arg("percent_available", avail));
@ -950,7 +954,8 @@ void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
{
str = fmt::format(
// xgettext:no-c-format
_("{current_size} ({percent_done}% of {percent_available}% available; {unverified_size} unverified)"),
fmt::runtime(
_("{current_size} ({percent_done}% of {percent_available}% available; {unverified_size} unverified)")),
fmt::arg("current_size", total),
fmt::arg("percent_done", buf2),
fmt::arg("percent_available", avail),
@ -983,7 +988,7 @@ void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
if (failed != 0)
{
str = fmt::format(
_("{downloaded_size} (+{discarded_size} discarded after failed checksum)"),
fmt::runtime(_("{downloaded_size} (+{discarded_size} discarded after failed checksum)")),
fmt::arg("downloaded_size", downloaded_str),
fmt::arg("discarded_size", tr_strlsize(failed)));
}
@ -1013,7 +1018,7 @@ void DetailsDialog::Impl::refreshInfo(std::vector<tr_torrent*> const& torrents)
uint64_t{},
[](auto sum, auto const* st) { return sum + st->sizeWhenDone; });
str = fmt::format(
_("{uploaded_size} (Ratio: {ratio})"),
fmt::runtime(_("{uploaded_size} (Ratio: {ratio})")),
fmt::arg("uploaded_size", tr_strlsize(uploaded)),
fmt::arg("ratio", tr_strlratio(tr_getRatio(uploaded, denominator))));
}
@ -1809,10 +1814,10 @@ void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::T
{
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround the peer text
ngettext(
fmt::runtime(ngettext(
"Got a list of {markup_begin}{peer_count} peer{markup_end} {time_span_ago}",
"Got a list of {markup_begin}{peer_count} peers{markup_end} {time_span_ago}",
tracker.lastAnnouncePeerCount),
tracker.lastAnnouncePeerCount)),
fmt::arg("markup_begin", SuccessMarkupBegin),
fmt::arg("peer_count", tracker.lastAnnouncePeerCount),
fmt::arg("markup_end", SuccessMarkupEnd),
@ -1822,7 +1827,7 @@ void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::T
{
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround the time_span
_("Peer list request {markup_begin}timed out {time_span_ago}{markup_end}; will retry"),
fmt::runtime(_("Peer list request {markup_begin}timed out {time_span_ago}{markup_end}; will retry")),
fmt::arg("markup_begin", TimeoutMarkupBegin),
fmt::arg("time_span_ago", time_span_ago),
fmt::arg("markup_end", TimeoutMarkupEnd));
@ -1831,7 +1836,7 @@ void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::T
{
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround the error
_("Got an error '{markup_begin}{error}{markup_end}' {time_span_ago}"),
fmt::runtime(_("Got an error '{markup_begin}{error}{markup_end}' {time_span_ago}")),
fmt::arg("markup_begin", ErrMarkupBegin),
fmt::arg("error", Glib::Markup::escape_text(std::data(tracker.lastAnnounceResult))),
fmt::arg("markup_end", ErrMarkupEnd),
@ -1851,7 +1856,7 @@ void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::T
gstr << '\n';
gstr << dir_mark;
gstr << fmt::format(
_("Asking for more peers {time_span_from_now}"),
fmt::runtime(_("Asking for more peers {time_span_from_now}")),
fmt::arg("time_span_from_now", tr_format_time_relative(now, tracker.nextAnnounceTime)));
break;
@ -1866,7 +1871,7 @@ void appendAnnounceInfo(tr_tracker_view const& tracker, time_t const now, Gtk::T
gstr << dir_mark;
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround time_span_ago
_("Asked for more peers {markup_begin}{time_span_ago}{markup_end}"),
fmt::runtime(_("Asked for more peers {markup_begin}{time_span_ago}{markup_end}")),
fmt::arg("markup_begin", "<small>"),
fmt::arg("time_span_ago", tr_format_time_relative(now, tracker.lastAnnounceStartTime)),
fmt::arg("markup_end", "</small>"));
@ -1891,7 +1896,8 @@ void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::Tex
{
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround the seeder/leecher text
_("Tracker had {markup_begin}{seeder_count} {seeder_or_seeders} and {leecher_count} {leecher_or_leechers}{markup_end} {time_span_ago}"),
fmt::runtime(_(
"Tracker had {markup_begin}{seeder_count} {seeder_or_seeders} and {leecher_count} {leecher_or_leechers}{markup_end} {time_span_ago}")),
fmt::arg("seeder_count", tracker.seederCount),
fmt::arg("seeder_or_seeders", ngettext("seeder", "seeders", tracker.seederCount)),
fmt::arg("leecher_count", tracker.leecherCount),
@ -1904,7 +1910,7 @@ void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::Tex
{
gstr << fmt::format(
// {markup_begin} and {markup_end} should surround the error text
_("Got a scrape error '{markup_begin}{error}{markup_end}' {time_span_ago}"),
fmt::runtime(_("Got a scrape error '{markup_begin}{error}{markup_end}' {time_span_ago}")),
fmt::arg("error", Glib::Markup::escape_text(std::data(tracker.lastScrapeResult))),
fmt::arg("time_span_ago", time_span_ago),
fmt::arg("markup_begin", ErrMarkupBegin),
@ -1921,7 +1927,7 @@ void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::Tex
gstr << '\n';
gstr << dir_mark;
gstr << fmt::format(
_("Asking for peer counts in {time_span_from_now}"),
fmt::runtime(_("Asking for peer counts in {time_span_from_now}")),
fmt::arg("time_span_from_now", tr_format_time_relative(now, tracker.nextScrapeTime)));
break;
@ -1935,7 +1941,7 @@ void appendScrapeInfo(tr_tracker_view const& tracker, time_t const now, Gtk::Tex
gstr << '\n';
gstr << dir_mark;
gstr << fmt::format(
_("Asked for peer counts {markup_begin}{time_span_ago}{markup_end}"),
fmt::runtime(_("Asked for peer counts {markup_begin}{time_span_ago}{markup_end}")),
fmt::arg("markup_begin", "<small>"),
fmt::arg("time_span_ago", tr_format_time_relative(now, tracker.lastScrapeStartTime)),
fmt::arg("markup_end", "</small>"));
@ -2218,7 +2224,8 @@ EditTrackersDialog::EditTrackersDialog(
, torrent_id_(tr_torrentId(torrent))
, urls_view_(gtr_get_widget<Gtk::TextView>(builder, "urls_view"))
{
set_title(fmt::format(_("{torrent_name} - Edit Trackers"), fmt::arg("torrent_name", tr_torrentName(torrent))));
set_title(
fmt::format(fmt::runtime(_("{torrent_name} - Edit Trackers")), fmt::arg("torrent_name", tr_torrentName(torrent))));
set_transient_for(parent);
urls_view_->get_buffer()->set_text(tr_torrentGetTrackerList(torrent));
@ -2337,7 +2344,7 @@ AddTrackerDialog::AddTrackerDialog(
, torrent_id_(tr_torrentId(torrent))
, url_entry_(gtr_get_widget<Gtk::Entry>(builder, "url_entry"))
{
set_title(fmt::format(_("{torrent_name} - Add Tracker"), fmt::arg("torrent_name", tr_torrentName(torrent))));
set_title(fmt::format(fmt::runtime(_("{torrent_name} - Add Tracker")), fmt::arg("torrent_name", tr_torrentName(torrent))));
set_transient_for(parent);
gtr_paste_clipboard_url_into_entry(*url_entry_);
@ -2625,12 +2632,12 @@ void DetailsDialog::Impl::set_torrents(std::vector<tr_torrent_id_t> const& ids)
{
int const id = ids.front();
auto const* tor = core_->find_torrent(id);
title = fmt::format(_("{torrent_name} Properties"), fmt::arg("torrent_name", tr_torrentName(tor)));
title = fmt::format(fmt::runtime(_("{torrent_name} Properties")), fmt::arg("torrent_name", tr_torrentName(tor)));
}
else
{
title = fmt::format(
ngettext("Properties - {torrent_count:L} Torrent", "Properties - {torrent_count:L} Torrents", len),
fmt::runtime(ngettext("Properties - {torrent_count:L} Torrent", "Properties - {torrent_count:L} Torrents", len)),
fmt::arg("torrent_count", len));
}

View File

@ -53,9 +53,12 @@ void gtr_confirm_remove(
}
auto const primary_text = fmt::format(
!delete_files ?
ngettext("Remove torrent?", "Remove {count:L} torrents?", count) :
ngettext("Delete this torrent's downloaded files?", "Delete these {count:L} torrents' downloaded files?", count),
fmt::runtime(
!delete_files ? ngettext("Remove torrent?", "Remove {count:L} torrents?", count) :
ngettext(
"Delete this torrent's downloaded files?",
"Delete these {count:L} torrents' downloaded files?",
count)),
fmt::arg("count", count));
Glib::ustring secondary_text;

View File

@ -35,7 +35,6 @@
#include <fmt/core.h>
#include <algorithm>
#include <list>
#include <memory>
#include <optional>
#include <queue>
@ -853,7 +852,7 @@ void FileList::Impl::on_rename_done_idle(Glib::ustring const& path_string, Glib:
auto w = std::make_shared<Gtk::MessageDialog>(
gtr_widget_get_window(widget_),
fmt::format(
_("Couldn't rename '{old_path}' as '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't rename '{old_path}' as '{path}': {error} ({error_code})")),
fmt::arg("old_path", path_string),
fmt::arg("path", newname),
fmt::arg("error", tr_strerror(error)),

View File

@ -38,8 +38,8 @@
#include <algorithm> // std::transform()
#include <array>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
@ -583,7 +583,7 @@ bool FilterBar::Impl::update_count_label()
/* set the text */
if (auto const new_markup = visibleCount == std::min(activityCount, trackerCount) ?
_("_Show:") :
fmt::format(_("_Show {count:L} of:"), fmt::arg("count", visibleCount));
fmt::format(fmt::runtime(_("_Show {count:L} of:")), fmt::arg("count", visibleCount));
new_markup != show_lb_->get_label().raw())
{
show_lb_->set_markup_with_mnemonic(new_markup);

View File

@ -51,8 +51,9 @@ bool FreeSpaceLabel::Impl::on_freespace_timer()
}
auto const capacity = tr_sys_path_get_capacity(dir_);
auto const text = capacity ? fmt::format(_("{disk_space} free"), fmt::arg("disk_space", tr_strlsize(capacity->free))) :
_("Error");
auto const text = capacity ?
fmt::format(fmt::runtime(_("{disk_space} free")), fmt::arg("disk_space", tr_strlsize(capacity->free))) :
_("Error");
label_.set_markup(fmt::format("<i>{:s}</i>", text));
return true;

View File

@ -378,8 +378,9 @@ void MainWindow::Impl::syncAltSpeedButton()
bool const b = gtr_pref_flag_get(TR_KEY_alt_speed_enabled);
alt_speed_button_->set_active(b);
alt_speed_button_->set_tooltip_text(fmt::format(
b ? _("Click to disable Alternative Speed Limits\n ({download_speed} down, {upload_speed} up)") :
_("Click to enable Alternative Speed Limits\n ({download_speed} down, {upload_speed} up)"),
fmt::runtime(
b ? _("Click to disable Alternative Speed Limits\n ({download_speed} down, {upload_speed} up)") :
_("Click to enable Alternative Speed Limits\n ({download_speed} down, {upload_speed} up)")),
fmt::arg("download_speed", Speed{ gtr_pref_int_get(TR_KEY_alt_speed_down), Speed::Units::KByps }.to_string()),
fmt::arg("upload_speed", Speed{ gtr_pref_int_get(TR_KEY_alt_speed_up), Speed::Units::KByps }.to_string())));
}
@ -577,7 +578,9 @@ void MainWindow::Impl::onOptionsClicked()
update_menu(
ratio_menu_info_,
fmt::format(_("Stop at Ratio ({ratio})"), fmt::arg("ratio", tr_strlratio(gtr_pref_double_get(TR_KEY_ratio_limit)))),
fmt::format(
fmt::runtime(_("Stop at Ratio ({ratio})")),
fmt::arg("ratio", tr_strlratio(gtr_pref_double_get(TR_KEY_ratio_limit)))),
TR_KEY_ratio_limit_enabled);
}
@ -758,13 +761,13 @@ void MainWindow::Impl::updateStats()
if (auto const pch = gtr_pref_string_get(TR_KEY_statusbar_stats); pch == "session-ratio")
{
auto const stats = tr_sessionGetStats(session);
buf = fmt::format(_("Ratio: {ratio}"), fmt::arg("ratio", tr_strlratio(stats.ratio)));
buf = fmt::format(fmt::runtime(_("Ratio: {ratio}")), fmt::arg("ratio", tr_strlratio(stats.ratio)));
}
else if (pch == "session-transfer")
{
auto const stats = tr_sessionGetStats(session);
buf = fmt::format(
C_("current session totals", "Down: {downloaded_size}, Up: {uploaded_size}"),
fmt::runtime(C_("current session totals", "Down: {downloaded_size}, Up: {uploaded_size}")),
fmt::arg("downloaded_size", tr_strlsize(stats.downloadedBytes)),
fmt::arg("uploaded_size", tr_strlsize(stats.uploadedBytes)));
}
@ -772,14 +775,14 @@ void MainWindow::Impl::updateStats()
{
auto const stats = tr_sessionGetCumulativeStats(session);
buf = fmt::format(
C_("all-time totals", "Down: {downloaded_size}, Up: {uploaded_size}"),
fmt::runtime(C_("all-time totals", "Down: {downloaded_size}, Up: {uploaded_size}")),
fmt::arg("downloaded_size", tr_strlsize(stats.downloadedBytes)),
fmt::arg("uploaded_size", tr_strlsize(stats.uploadedBytes)));
}
else /* default is total-ratio */
{
auto const stats = tr_sessionGetCumulativeStats(session);
buf = fmt::format(_("Ratio: {ratio}"), fmt::arg("ratio", tr_strlratio(stats.ratio)));
buf = fmt::format(fmt::runtime(_("Ratio: {ratio}")), fmt::arg("ratio", tr_strlratio(stats.ratio)));
}
stats_lb_->set_text(buf);

View File

@ -187,7 +187,7 @@ bool MakeProgressDialog::onProgressDialogRefresh()
auto const base = Glib::path_get_basename(builder_.top());
if (!is_done)
{
str = fmt::format(_("Creating '{path}'"), fmt::arg("path", base));
str = fmt::format(fmt::runtime(_("Creating '{path}'")), fmt::arg("path", base));
}
else
{
@ -200,13 +200,13 @@ bool MakeProgressDialog::onProgressDialogRefresh()
if (!error)
{
str = fmt::format(_("Created '{path}'"), fmt::arg("path", base));
str = fmt::format(fmt::runtime(_("Created '{path}'")), fmt::arg("path", base));
success = true;
}
else
{
str = fmt::format(
_("Couldn't create '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't create '{path}': {error} ({error_code})")),
fmt::arg("path", base),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code()));
@ -224,7 +224,7 @@ bool MakeProgressDialog::onProgressDialogRefresh()
{
/* how much data we've scanned through to generate checksums */
str = fmt::format(
_("Scanned {file_size}"),
fmt::runtime(_("Scanned {file_size}")),
fmt::arg("file_size", tr_strlsize(static_cast<uint64_t>(piece_index) * builder_.piece_size())));
}
@ -385,15 +385,18 @@ void MakeDialog::Impl::updatePiecesLabel()
else
{
gstr += fmt::format(
ngettext("{total_size} in {file_count:L} file", "{total_size} in {file_count:L} files", builder_->file_count()),
fmt::runtime(ngettext(
"{total_size} in {file_count:L} file",
"{total_size} in {file_count:L} files",
builder_->file_count())),
fmt::arg("total_size", tr_strlsize(builder_->total_size())),
fmt::arg("file_count", builder_->file_count()));
gstr += ' ';
gstr += fmt::format(
ngettext(
fmt::runtime(ngettext(
"({piece_count} BitTorrent piece @ {piece_size})",
"({piece_count} BitTorrent pieces @ {piece_size})",
builder_->piece_count()),
builder_->piece_count())),
fmt::arg("piece_count", builder_->piece_count()),
fmt::arg("piece_size", Memory{ builder_->piece_size(), Memory::Units::Bytes }.to_string()));
}

View File

@ -237,7 +237,7 @@ void MessageLogWindow::Impl::doSave(std::string const& filename)
auto w = std::make_shared<Gtk::MessageDialog>(
window_,
fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't save '{path}': {error} ({error_code})")),
fmt::arg("path", Glib::filename_to_utf8(filename)),
fmt::arg("error", e.code().message()),
fmt::arg("error_code", e.code().value())),

View File

@ -145,7 +145,7 @@ void dbus_proxy_ready_callback(Glib::RefPtr<Gio::AsyncResult>& res)
catch (Glib::Error const& e)
{
gtr_warning(fmt::format(
_("Couldn't create proxy for '{bus}': {error} ({error_code})"),
fmt::runtime(_("Couldn't create proxy for '{bus}': {error} ({error_code})")),
fmt::arg("bus", NotificationsDbusName),
fmt::arg("error", TR_GLIB_EXCEPTION_WHAT(e)),
fmt::arg("error_code", e.code())));

View File

@ -126,7 +126,7 @@ public:
template<typename T, typename... ArgTs>
static void localize_label(T& widget, ArgTs&&... args)
{
widget.set_label(fmt::format(widget.get_label().raw(), std::forward<ArgTs>(args)...));
widget.set_label(fmt::format(fmt::runtime(widget.get_label().raw()), std::forward<ArgTs>(args)...));
}
private:
@ -552,7 +552,7 @@ void PrivacyPage::updateBlocklistText()
{
int const n = tr_blocklistGetRuleCount(core_->get_session());
auto const msg = fmt::format(
ngettext("Blocklist has {count:L} entry", "Blocklist has {count:L} entries", n),
fmt::runtime(ngettext("Blocklist has {count:L} entry", "Blocklist has {count:L} entries", n)),
fmt::arg("count", n));
label_->set_text(msg);
}
@ -954,9 +954,9 @@ void NetworkPage::updatePortStatusText()
portLabel_->set_markup(
portTestStatus_[Session::PORT_TEST_IPV4] == portTestStatus_[Session::PORT_TEST_IPV6] ?
fmt::format(_("Status: <b>{status}</b>"), fmt::arg("status", status_ipv4)) :
fmt::format(fmt::runtime(_("Status: <b>{status}</b>")), fmt::arg("status", status_ipv4)) :
fmt::format(
_("Status: <b>{status_ipv4}</b> (IPv4), <b>{status_ipv6}</b> (IPv6)"),
fmt::runtime(_("Status: <b>{status_ipv4}</b> (IPv4), <b>{status_ipv6}</b> (IPv6)")),
fmt::arg("status_ipv4", status_ipv4),
fmt::arg("status_ipv6", status_ipv6)));
}

View File

@ -81,7 +81,7 @@ void RelocateDialog::Impl::startMovingNextTorrent()
torrent_ids_.pop_back();
message_dialog_->set_message(
fmt::format(_("Moving '{torrent_name}'"), fmt::arg("torrent_name", tr_torrentName(tor))),
fmt::format(fmt::runtime(_("Moving '{torrent_name}'")), fmt::arg("torrent_name", tr_torrentName(tor))),
true);
}

View File

@ -313,7 +313,7 @@ void rename_torrent(Glib::RefPtr<Gio::File> const& file)
catch (Glib::Error const& e)
{
gtr_message(fmt::format(
_("Couldn't rename '{old_path}' as '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't rename '{old_path}' as '{path}': {error} ({error_code})")),
fmt::arg("old_path", old_name),
fmt::arg("path", new_name),
fmt::arg("error", e.what()),
@ -781,7 +781,7 @@ void Session::Impl::add_file_async_callback(
if (!file->load_contents_finish(result, contents, length))
{
gtr_message(fmt::format(_("Couldn't read '{path}'"), fmt::arg("path", file->get_parse_name())));
gtr_message(fmt::format(fmt::runtime(_("Couldn't read '{path}'")), fmt::arg("path", file->get_parse_name())));
}
else if (tr_ctorSetMetainfo(ctor, contents, length, nullptr))
{
@ -795,7 +795,7 @@ void Session::Impl::add_file_async_callback(
catch (Glib::Error const& e)
{
gtr_message(fmt::format(
_("Couldn't read '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't read '{path}': {error} ({error_code})")),
fmt::arg("path", file->get_parse_name()),
fmt::arg("error", e.what()),
fmt::arg("error_code", e.code())));
@ -846,7 +846,10 @@ bool Session::Impl::add_file(Glib::RefPtr<Gio::File> const& file, bool do_start,
else
{
tr_ctorFree(ctor);
std::cerr << fmt::format(_("Couldn't add torrent file '{path}'"), fmt::arg("path", file->get_parse_name())) << '\n';
std::cerr << fmt::format(
fmt::runtime(_("Couldn't add torrent file '{path}'")),
fmt::arg("path", file->get_parse_name()))
<< '\n';
}
return handled;
@ -1044,7 +1047,8 @@ bool gtr_inhibit_hibernation(guint32& cookie)
}
catch (Glib::Error const& e)
{
tr_logAddError(fmt::format(_("Couldn't inhibit desktop hibernation: {error}"), fmt::arg("error", e.what())));
tr_logAddError(
fmt::format(fmt::runtime(_("Couldn't inhibit desktop hibernation: {error}")), fmt::arg("error", e.what())));
}
return success;
@ -1069,7 +1073,8 @@ void gtr_uninhibit_hibernation(guint inhibit_cookie)
}
catch (Glib::Error const& e)
{
tr_logAddError(fmt::format(_("Couldn't inhibit desktop hibernation: {error}"), fmt::arg("error", e.what())));
tr_logAddError(
fmt::format(fmt::runtime(_("Couldn't inhibit desktop hibernation: {error}")), fmt::arg("error", e.what())));
}
}
@ -1183,7 +1188,7 @@ bool core_read_rpc_response_idle(tr_variant& response)
}
else
{
gtr_warning(fmt::format(_("Couldn't find pending RPC request for tag {tag}"), fmt::arg("tag", tag)));
gtr_warning(fmt::format(fmt::runtime(_("Couldn't find pending RPC request for tag {tag}")), fmt::arg("tag", tag)));
}
}

View File

@ -68,7 +68,7 @@ void setLabelFromRatio(Gtk::Label* l, double d)
auto startedTimesText(uint64_t n)
{
return fmt::format(ngettext("Started {count:L} time", "Started {count:L} times", n), fmt::arg("count", n));
return fmt::format(fmt::runtime(ngettext("Started {count:L} time", "Started {count:L} times", n)), fmt::arg("count", n));
}
} // namespace

View File

@ -55,12 +55,12 @@ namespace
{
#if !defined(TR_SYS_TRAY_IMPL_NONE)
auto const TrayIconName = Glib::ustring("transmission-tray-icon"s);
auto const AppIconName = Glib::ustring("transmission"s);
char const* const TrayIconName = "transmission-tray-icon";
char const* const AppIconName = "transmission";
#endif
#if defined(TR_SYS_TRAY_IMPL_APPINDICATOR)
auto const AppName = Glib::ustring("transmission-gtk"s);
char const* const AppName = "transmission-gtk";
#endif
} // namespace
@ -138,23 +138,11 @@ namespace
Glib::ustring getIconName()
{
Glib::ustring icon_name;
// if the tray's icon is a 48x48 file, use it.
// otherwise, use the fallback builtin icon.
if (auto theme = Gtk::IconTheme::get_default(); !theme->has_icon(TrayIconName))
{
icon_name = AppIconName;
}
else
{
auto const icon_info = theme->lookup_icon(TrayIconName, 48, Gtk::ICON_LOOKUP_USE_BUILTIN);
bool const icon_is_builtin = icon_info.get_filename().empty();
icon_name = icon_is_builtin ? AppIconName : TrayIconName;
}
return icon_name;
auto const icon = Gtk::IconTheme::get_default()->lookup_icon(TrayIconName, 48, Gtk::ICON_LOOKUP_USE_BUILTIN);
return icon && !icon.get_filename().empty() ? TrayIconName : AppIconName;
}
#endif
@ -199,7 +187,7 @@ SystemTrayIcon::Impl::Impl([[maybe_unused]] Gtk::Window& main_window, Glib::RefP
#endif
#if defined(TR_SYS_TRAY_IMPL_APPINDICATOR)
indicator_ = app_indicator_new(AppName.c_str(), icon_name.c_str(), APP_INDICATOR_CATEGORY_SYSTEM_SERVICES);
indicator_ = app_indicator_new(AppName, icon_name.c_str(), APP_INDICATOR_CATEGORY_SYSTEM_SERVICES);
app_indicator_set_status(indicator_, APP_INDICATOR_STATUS_ACTIVE);
app_indicator_set_menu(indicator_, Glib::unwrap(menu_));
app_indicator_set_title(indicator_, Glib::get_application_name().c_str());
@ -214,7 +202,7 @@ std::string SystemTrayIcon::Impl::make_tooltip_text() const
{
auto const* const session = core_->get_session();
return fmt::format(
_("{upload_speed} ▲ {download_speed} ▼"),
fmt::runtime(_("{upload_speed} ▲ {download_speed} ▼")),
fmt::arg("upload_speed", Speed{ tr_sessionGetRawSpeed_KBps(session, TR_UP), Speed::Units::KByps }.to_string()),
fmt::arg("download_speed", Speed{ tr_sessionGetRawSpeed_KBps(session, TR_DOWN), Speed::Units::KByps }.to_string()));
}

View File

@ -436,7 +436,7 @@ Glib::ustring Torrent::Impl::get_short_status_text() const
case TR_STATUS_CHECK:
return fmt::format(
// xgettext:no-c-format
_("Verifying local data ({percent_done}% tested)"),
fmt::runtime(_("Verifying local data ({percent_done}% tested)")),
fmt::arg("percent_done", cache_.recheck_progress.to_string()));
case TR_STATUS_DOWNLOAD:
@ -444,7 +444,7 @@ Glib::ustring Torrent::Impl::get_short_status_text() const
return fmt::format(
"{:s} {:s}",
get_short_transfer_text(),
fmt::format(_("Ratio: {ratio}"), fmt::arg("ratio", tr_strlratio(cache_.ratio))));
fmt::format(fmt::runtime(_("Ratio: {ratio}")), fmt::arg("ratio", tr_strlratio(cache_.ratio))));
default:
return {};
@ -463,7 +463,7 @@ Glib::ustring Torrent::Impl::get_long_progress_text() const
{
// 50 MB of 200 MB (25%)
gstr += fmt::format(
_("{current_size} of {complete_size} ({percent_done}%)"),
fmt::runtime(_("{current_size} of {complete_size} ({percent_done}%)")),
fmt::arg("current_size", tr_strlsize(haveTotal)),
fmt::arg("complete_size", tr_strlsize(cache_.size_when_done)),
fmt::arg("percent_done", cache_.percent_done.to_string()));
@ -473,7 +473,8 @@ Glib::ustring Torrent::Impl::get_long_progress_text() const
// 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::runtime(_(
"{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)),
fmt::arg("percent_complete", cache_.percent_complete.to_string()),
@ -485,7 +486,8 @@ Glib::ustring Torrent::Impl::get_long_progress_text() const
{
gstr += fmt::format(
// xgettext:no-c-format
_("{current_size} of {complete_size} ({percent_complete}%), uploaded {uploaded_size} (Ratio: {ratio})"),
fmt::runtime(
_("{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)),
fmt::arg("percent_complete", cache_.percent_complete.to_string()),
@ -495,7 +497,7 @@ Glib::ustring Torrent::Impl::get_long_progress_text() const
else if (cache_.has_seed_ratio) // seed, seed ratio
{
gstr += fmt::format(
_("{complete_size}, uploaded {uploaded_size} (Ratio: {ratio}, Goal: {seed_ratio})"),
fmt::runtime(_("{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)),
@ -504,7 +506,7 @@ Glib::ustring Torrent::Impl::get_long_progress_text() const
else // seed, no seed ratio
{
gstr += fmt::format(
_("{complete_size}, uploaded {uploaded_size} (Ratio: {ratio})"),
fmt::runtime(_("{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)));
@ -637,13 +639,13 @@ 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));
return fmt::format(fmt::runtime(_("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));
return fmt::format(fmt::runtime(_("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));
return fmt::format(fmt::runtime(_("Local error: '{error}'")), fmt::arg("error", cache_.error_message));
default:
return {};
@ -665,11 +667,11 @@ Glib::ustring Torrent::Impl::get_activity_text() const
if (!cache_.has_metadata)
{
return fmt::format(
ngettext(
fmt::runtime(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),
cache_.peers_connected)),
fmt::arg("active_count", cache_.peers_connected),
fmt::arg("percent_done", cache_.metadata_percent_complete.to_string()));
}
@ -677,10 +679,10 @@ Glib::ustring Torrent::Impl::get_activity_text() const
if (cache_.peers_sending_to_us != 0 && cache_.webseeds_sending_to_us != 0)
{
return fmt::format(
ngettext(
fmt::runtime(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),
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));
}
@ -688,27 +690,27 @@ Glib::ustring Torrent::Impl::get_activity_text() const
if (cache_.webseeds_sending_to_us != 0)
{
return fmt::format(
ngettext(
fmt::runtime(ngettext(
"Downloading from {active_count} webseed",
"Downloading from {active_count} webseeds",
cache_.webseeds_sending_to_us),
cache_.webseeds_sending_to_us)),
fmt::arg("active_count", cache_.webseeds_sending_to_us));
}
return fmt::format(
ngettext(
fmt::runtime(ngettext(
"Downloading from {active_count} of {connected_count} connected peer",
"Downloading from {active_count} of {connected_count} connected peers",
cache_.peers_connected),
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(
fmt::runtime(ngettext(
"Seeding to {active_count} of {connected_count} connected peer",
"Seeding to {active_count} of {connected_count} connected peers",
cache_.peers_connected),
cache_.peers_connected)),
fmt::arg("active_count", cache_.peers_getting_from_us),
fmt::arg("connected_count", cache_.peers_connected));

View File

@ -132,28 +132,30 @@ std::string tr_format_future_time(time_t seconds)
if (auto const days_from_now = seconds / 86400U; days_from_now > 0U)
{
return fmt::format(
ngettext("{days_from_now:L} day from now", "{days_from_now:L} days from now", days_from_now),
fmt::runtime(ngettext("{days_from_now:L} day from now", "{days_from_now:L} days from now", days_from_now)),
fmt::arg("days_from_now", days_from_now));
}
if (auto const hours_from_now = (seconds % 86400U) / 3600U; hours_from_now > 0U)
{
return fmt::format(
ngettext("{hours_from_now:L} hour from now", "{hours_from_now:L} hours from now", hours_from_now),
fmt::runtime(ngettext("{hours_from_now:L} hour from now", "{hours_from_now:L} hours from now", hours_from_now)),
fmt::arg("hours_from_now", hours_from_now));
}
if (auto const minutes_from_now = (seconds % 3600U) / 60U; minutes_from_now > 0U)
{
return fmt::format(
ngettext("{minutes_from_now:L} minute from now", "{minutes_from_now:L} minutes from now", minutes_from_now),
fmt::runtime(
ngettext("{minutes_from_now:L} minute from now", "{minutes_from_now:L} minutes from now", minutes_from_now)),
fmt::arg("minutes_from_now", minutes_from_now));
}
if (auto const seconds_from_now = seconds % 60U; seconds_from_now > 0U)
{
return fmt::format(
ngettext("{seconds_from_now:L} second from now", "{seconds_from_now:L} seconds from now", seconds_from_now),
fmt::runtime(
ngettext("{seconds_from_now:L} second from now", "{seconds_from_now:L} seconds from now", seconds_from_now)),
fmt::arg("seconds_from_now", seconds_from_now));
}
@ -164,27 +166,29 @@ std::string tr_format_past_time(time_t seconds)
{
if (auto const days_ago = seconds / 86400U; days_ago > 0U)
{
return fmt::format(ngettext("{days_ago:L} day ago", "{days_ago:L} days ago", days_ago), fmt::arg("days_ago", days_ago));
return fmt::format(
fmt::runtime(ngettext("{days_ago:L} day ago", "{days_ago:L} days ago", days_ago)),
fmt::arg("days_ago", days_ago));
}
if (auto const hours_ago = (seconds % 86400U) / 3600U; hours_ago > 0U)
{
return fmt::format(
ngettext("{hours_ago:L} hour ago", "{hours_ago:L} hours ago", hours_ago),
fmt::runtime(ngettext("{hours_ago:L} hour ago", "{hours_ago:L} hours ago", hours_ago)),
fmt::arg("hours_ago", hours_ago));
}
if (auto const minutes_ago = (seconds % 3600U) / 60U; minutes_ago > 0U)
{
return fmt::format(
ngettext("{minutes_ago:L} minute ago", "{minutes_ago:L} minutes ago", minutes_ago),
fmt::runtime(ngettext("{minutes_ago:L} minute ago", "{minutes_ago:L} minutes ago", minutes_ago)),
fmt::arg("minutes_ago", minutes_ago));
}
if (auto const seconds_ago = seconds % 60U; seconds_ago > 0U)
{
return fmt::format(
ngettext("{seconds_ago:L} second ago", "{seconds_ago:L} seconds ago", seconds_ago),
fmt::runtime(ngettext("{seconds_ago:L} second ago", "{seconds_ago:L} seconds ago", seconds_ago)),
fmt::arg("seconds_ago", seconds_ago));
}
@ -197,22 +201,26 @@ std::string tr_format_time(time_t timestamp)
{
if (auto const days = timestamp / 86400U; days > 0U)
{
return fmt::format(ngettext("{days:L} day", "{days:L} days", days), fmt::arg("days", days));
return fmt::format(fmt::runtime(ngettext("{days:L} day", "{days:L} days", days)), fmt::arg("days", days));
}
if (auto const hours = (timestamp % 86400U) / 3600U; hours > 0U)
{
return fmt::format(ngettext("{hours:L} hour", "{hours:L} hours", hours), fmt::arg("hours", hours));
return fmt::format(fmt::runtime(ngettext("{hours:L} hour", "{hours:L} hours", hours)), fmt::arg("hours", hours));
}
if (auto const minutes = (timestamp % 3600U) / 60U; minutes > 0U)
{
return fmt::format(ngettext("{minutes:L} minute", "{minutes:L} minutes", minutes), fmt::arg("minutes", minutes));
return fmt::format(
fmt::runtime(ngettext("{minutes:L} minute", "{minutes:L} minutes", minutes)),
fmt::arg("minutes", minutes));
}
if (auto const seconds = timestamp % 60U; seconds > 0U)
{
return fmt::format(ngettext("{seconds:L} second", "{seconds:L} seconds", seconds), fmt::arg("seconds", seconds));
return fmt::format(
fmt::runtime(ngettext("{seconds:L} second", "{seconds:L} seconds", seconds)),
fmt::arg("seconds", seconds));
}
return _("now");
@ -223,28 +231,28 @@ std::string tr_format_time_left(time_t timestamp)
if (auto const days_left = timestamp / 86400U; days_left > 0U)
{
return fmt::format(
ngettext("{days_left:L} day left", "{days_left:L} days left", days_left),
fmt::runtime(ngettext("{days_left:L} day left", "{days_left:L} days left", days_left)),
fmt::arg("days_left", days_left));
}
if (auto const hours_left = (timestamp % 86400U) / 3600U; hours_left > 0U)
{
return fmt::format(
ngettext("{hours_left:L} hour left", "{hours_left:L} hours left", hours_left),
fmt::runtime(ngettext("{hours_left:L} hour left", "{hours_left:L} hours left", hours_left)),
fmt::arg("hours_left", hours_left));
}
if (auto const minutes_left = (timestamp % 3600U) / 60U; minutes_left > 0U)
{
return fmt::format(
ngettext("{minutes_left:L} minute left", "{minutes_left:L} minutes left", minutes_left),
fmt::runtime(ngettext("{minutes_left:L} minute left", "{minutes_left:L} minutes left", minutes_left)),
fmt::arg("minutes_left", minutes_left));
}
if (auto const seconds_left = timestamp % 60U; seconds_left > 0U)
{
return fmt::format(
ngettext("{seconds_left:L} second left", "{seconds_left:L} seconds left", seconds_left),
fmt::runtime(ngettext("{seconds_left:L} second left", "{seconds_left:L} seconds left", seconds_left)),
fmt::arg("seconds_left", seconds_left));
}
@ -263,13 +271,13 @@ void gtr_add_torrent_error_dialog(Gtk::Widget& child, tr_torrent* duplicate_torr
if (duplicate_torrent != nullptr)
{
secondary = fmt::format(
_("The torrent file '{path}' is already in use by '{torrent_name}'."),
fmt::runtime(_("The torrent file '{path}' is already in use by '{torrent_name}'.")),
fmt::arg("path", filename),
fmt::arg("torrent_name", tr_torrentName(duplicate_torrent)));
}
else
{
secondary = fmt::format(_("Couldn't add torrent file '{path}'"), fmt::arg("path", filename));
secondary = fmt::format(fmt::runtime(_("Couldn't add torrent file '{path}'")), fmt::arg("path", filename));
}
auto w = std::make_shared<Gtk::MessageDialog>(
@ -534,7 +542,7 @@ bool gtr_file_trash_or_remove(std::string const& filename, tr_error* error)
{
error->set(e.code(), TR_GLIB_EXCEPTION_WHAT(e));
gtr_message(fmt::format(
_("Couldn't move '{path}' to trash: {error} ({error_code})"),
fmt::runtime(_("Couldn't move '{path}' to trash: {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", error->message()),
fmt::arg("error_code", error->code())));
@ -552,7 +560,7 @@ bool gtr_file_trash_or_remove(std::string const& filename, tr_error* error)
{
error->set(e.code(), TR_GLIB_EXCEPTION_WHAT(e));
gtr_message(fmt::format(
_("Couldn't remove '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't remove '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", error->message()),
fmt::arg("error_code", error->code())));
@ -641,7 +649,7 @@ void gtr_open_uri(Glib::ustring const& uri)
if (!opened)
{
gtr_message(fmt::format(_("Couldn't open '{url}'"), fmt::arg("url", uri)));
gtr_message(fmt::format(fmt::runtime(_("Couldn't open '{url}'")), fmt::arg("url", uri)));
}
}
}
@ -852,13 +860,13 @@ void gtr_unrecognized_url_dialog(Gtk::Widget& parent, Glib::ustring const& url)
auto w = std::make_shared<Gtk::MessageDialog>(
gtr_widget_get_window(parent),
fmt::format(_("Unsupported URL: '{url}'"), fmt::arg("url", url)),
fmt::format(fmt::runtime(_("Unsupported URL: '{url}'")), fmt::arg("url", url)),
false /*use markup*/,
TR_GTK_MESSAGE_TYPE(ERROR),
TR_GTK_BUTTONS_TYPE(CLOSE),
true /*modal*/);
gstr += fmt::format(_("Transmission doesn't know how to use '{url}'"), fmt::arg("url", url));
gstr += fmt::format(fmt::runtime(_("Transmission doesn't know how to use '{url}'")), fmt::arg("url", url));
if (tr_magnet_metainfo{}.parseMagnet(url.raw()))
{

View File

@ -103,7 +103,7 @@ int main(int argc, char** argv)
fmt::print(stderr, "{}\n", TR_GLIB_EXCEPTION_WHAT(e));
fmt::print(
stderr,
_("Run '{program} --help' to see a full list of available command line options.\n"),
fmt::runtime(_("Run '{program} --help' to see a full list of available command line options.\n")),
fmt::arg("program", *argv));
return 1;
}

View File

@ -1,3 +1,4 @@
include(CheckAtomic)
include(CheckLibraryExists)
include(CheckSymbolExists)
@ -222,7 +223,6 @@ target_compile_definitions(${TR_NAME}
$<$<BOOL:${WITH_INOTIFY}>:WITH_INOTIFY>
$<$<BOOL:${WITH_KQUEUE}>:WITH_KQUEUE>
$<$<BOOL:${ENABLE_UTP}>:WITH_UTP>
$<$<VERSION_LESS:${MINIUPNPC_VERSION},1.7>:MINIUPNPC_API_VERSION=${MINIUPNPC_API_VERSION}> # API version macro was only added in 1.7
$<$<BOOL:${USE_SYSTEM_B64}>:USE_SYSTEM_B64>
$<$<BOOL:${HAVE_SO_REUSEPORT}>:HAVE_SO_REUSEPORT=1>
PUBLIC
@ -299,6 +299,7 @@ target_link_libraries(${TR_NAME}
$<$<BOOL:${WIN32}>:shlwapi>
"$<$<BOOL:${APPLE}>:-framework Foundation>"
"$<$<BOOL:${ANDROID}>:${log-lib}>"
$<$<BOOL:${HAVE_LIBATOMIC}>:atomic>
PUBLIC
transmission::crypto_impl
fmt::fmt-header-only

View File

@ -157,10 +157,10 @@ struct tr_announce_response
* This is only an upper bound: if the tracker complains about
* length, announcer will incrementally lower the batch size.
*/
auto inline constexpr TR_MULTISCRAPE_MAX = 60;
auto inline constexpr TrMultiscrapeMax = 60;
auto inline constexpr TR_ANNOUNCE_TIMEOUT_SEC = std::chrono::seconds{ 45 };
auto inline constexpr TR_SCRAPE_TIMEOUT_SEC = std::chrono::seconds{ 30 };
auto inline constexpr TrAnnounceTimeoutSec = std::chrono::seconds{ 45 };
auto inline constexpr TrScrapeTimeoutSec = std::chrono::seconds{ 30 };
struct tr_scrape_request
{
@ -171,7 +171,7 @@ struct tr_scrape_request
std::string log_name;
/* info hashes of the torrents to scrape */
std::array<tr_sha1_digest_t, TR_MULTISCRAPE_MAX> info_hash;
std::array<tr_sha1_digest_t, TrMultiscrapeMax> info_hash;
/* how many hashes to use in the info_hash field */
int info_hash_count = 0;
@ -209,7 +209,7 @@ struct tr_scrape_response
int row_count;
/* the individual torrents' scrape results */
std::array<tr_scrape_response_row, TR_MULTISCRAPE_MAX> rows;
std::array<tr_scrape_response_row, TrMultiscrapeMax> rows;
/* the raw scrape url */
tr_interned_string scrape_url;

View File

@ -265,7 +265,7 @@ void tr_tracker_http_announce(
auto url = tr_urlbuf{};
announce_url_new(url, session, request);
auto options = tr_web::FetchOptions{ url.sv(), onAnnounceDone, d };
options.timeout_secs = TR_ANNOUNCE_TIMEOUT_SEC;
options.timeout_secs = TrAnnounceTimeoutSec;
options.sndbuf = 4096;
options.rcvbuf = 4096;
@ -437,7 +437,7 @@ void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::
{
tr_logAddWarn(
fmt::format(
_("Couldn't parse announce response: {error} ({error_code})"),
fmt::runtime(_("Couldn't parse announce response: {error} ({error_code})")),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())),
log_name);
@ -542,7 +542,7 @@ void tr_tracker_http_scrape(tr_session const* session, tr_scrape_request const&
scrape_url_new(scrape_url, request);
tr_logAddTrace(fmt::format("Sending scrape to libcurl: '{}'", scrape_url), request.log_name);
auto options = tr_web::FetchOptions{ scrape_url, onScrapeDone, d };
options.timeout_secs = TR_SCRAPE_TIMEOUT_SEC;
options.timeout_secs = TrScrapeTimeoutSec;
options.sndbuf = 4096;
options.rcvbuf = 4096;
session->fetch(std::move(options));
@ -644,7 +644,7 @@ void tr_announcerParseHttpScrapeResponse(tr_scrape_response& response, std::stri
{
tr_logAddWarn(
fmt::format(
_("Couldn't parse scrape response: {error} ({error_code})"),
fmt::runtime(_("Couldn't parse scrape response: {error} ({error_code})")),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())),
log_name);

View File

@ -6,7 +6,6 @@
#include <algorithm> // for std::find_if()
#include <array>
#include <chrono> // operator""ms, literals
#include <climits> // CHAR_BIT
#include <cstddef> // std::byte
#include <cstdint> // uint32_t, uint64_t
#include <cstring> // memcpy()
@ -17,8 +16,8 @@
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
#ifdef _WIN32
#include <ws2tcpip.h>
@ -55,13 +54,15 @@ namespace
{
using namespace std::literals;
// size defined by bep15
// size defined by https://www.bittorrent.org/beps/bep_0015.html
using tau_connection_t = uint64_t;
using tau_transaction_t = uint32_t;
using InBuf = libtransmission::BufferReader<std::byte>;
using PayloadBuffer = libtransmission::StackBuffer<4096, std::byte>;
using ipp_t = std::underlying_type_t<tr_address_type>;
constexpr auto TauConnectionTtlSecs = time_t{ 45 };
auto tau_transaction_new()
@ -69,7 +70,8 @@ auto tau_transaction_new()
return tr_rand_obj<tau_transaction_t>();
}
// used in the "action" field of a request. Values defined in bep 15.
// used in the "action" field of a request.
// Values defined in https://www.bittorrent.org/beps/bep_0015.html
enum tau_action_t : uint8_t
{
TAU_ACTION_CONNECT = 0,
@ -85,22 +87,20 @@ struct tau_scrape_request
tau_scrape_request(tr_scrape_request const& in, tr_scrape_response_func on_response)
: on_response_{ std::move(on_response) }
{
this->response.scrape_url = in.scrape_url;
this->response.row_count = in.info_hash_count;
for (int i = 0; i < this->response.row_count; ++i)
response.scrape_url = in.scrape_url;
response.row_count = in.info_hash_count;
for (int i = 0; i < response.row_count; ++i)
{
this->response.rows[i].info_hash = in.info_hash[i];
response.rows[i].info_hash = in.info_hash[i];
}
// build the payload
auto buf = PayloadBuffer{};
buf.add_uint32(TAU_ACTION_SCRAPE);
buf.add_uint32(transaction_id);
payload.add_uint32(TAU_ACTION_SCRAPE);
payload.add_uint32(transaction_id);
for (int i = 0; i < in.info_hash_count; ++i)
{
buf.add(in.info_hash[i]);
payload.add(in.info_hash[i]);
}
this->payload.insert(std::end(this->payload), std::begin(buf), std::end(buf));
}
[[nodiscard]] auto has_callback() const noexcept
@ -108,7 +108,7 @@ struct tau_scrape_request
return !!on_response_;
}
void requestFinished() const
void request_finished() const
{
if (on_response_)
{
@ -121,10 +121,10 @@ struct tau_scrape_request
response.did_connect = did_connect;
response.did_timeout = did_timeout;
response.errmsg = errmsg;
requestFinished();
request_finished();
}
void onResponse(tau_action_t action, InBuf& buf)
void on_response(tau_action_t action, InBuf& buf)
{
response.did_connect = true;
response.did_timeout = false;
@ -139,7 +139,7 @@ struct tau_scrape_request
row.leechers = buf.to_uint32();
}
requestFinished();
request_finished();
}
else
{
@ -148,18 +148,20 @@ struct tau_scrape_request
}
}
[[nodiscard]] constexpr auto expiresAt() const noexcept
[[nodiscard]] constexpr auto expires_at() const noexcept
{
return created_at_ + TR_SCRAPE_TIMEOUT_SEC.count();
return created_at_ + TrScrapeTimeoutSec.count();
}
std::vector<std::byte> payload;
PayloadBuffer payload;
time_t sent_at = 0;
tau_transaction_t const transaction_id = tau_transaction_new();
tr_scrape_response response = {};
static auto constexpr ip_protocol = TR_AF_UNSPEC; // NOLINT(readability-identifier-naming)
private:
time_t const created_at_ = tr_time();
@ -171,38 +173,39 @@ private:
struct tau_announce_request
{
tau_announce_request(
tr_address_type ip_protocol_in,
std::optional<tr_address> announce_ip,
tr_announce_request const& in,
tr_announce_response_func on_response)
: on_response_{ std::move(on_response) }
: ip_protocol{ ip_protocol_in }
, on_response_{ std::move(on_response) }
{
// https://www.bittorrent.org/beps/bep_0015.html sets key size at 32 bits
static_assert(sizeof(tr_announce_request::key) * CHAR_BIT == 32);
static_assert(sizeof(tr_announce_request::key) == sizeof(uint32_t));
response.info_hash = in.info_hash;
// build the payload
auto buf = PayloadBuffer{};
buf.add_uint32(TAU_ACTION_ANNOUNCE);
buf.add_uint32(transaction_id);
buf.add(in.info_hash);
buf.add(in.peer_id);
buf.add_uint64(in.down);
buf.add_uint64(in.leftUntilComplete);
buf.add_uint64(in.up);
buf.add_uint32(get_tau_announce_event(in.event));
payload.add_uint32(TAU_ACTION_ANNOUNCE);
payload.add_uint32(transaction_id);
payload.add(in.info_hash);
payload.add(in.peer_id);
payload.add_uint64(in.down);
payload.add_uint64(in.leftUntilComplete);
payload.add_uint64(in.up);
payload.add_uint32(get_tau_announce_event(in.event));
if (announce_ip && announce_ip->is_ipv4())
{
buf.add_address(*announce_ip);
// Since size of IP field is only 4 bytes long, we can only announce IPv4 addresses
payload.add_address(*announce_ip);
}
else
{
buf.add_uint32(0U);
payload.add_uint32(0U);
}
buf.add_uint32(in.key);
buf.add_uint32(in.numwant);
buf.add_port(in.port);
payload.insert(std::end(payload), std::begin(buf), std::end(buf));
payload.add_uint32(in.key);
payload.add_uint32(in.numwant);
payload.add_port(in.port);
}
[[nodiscard]] auto has_callback() const noexcept
@ -210,28 +213,28 @@ struct tau_announce_request
return !!on_response_;
}
void requestFinished() const
void request_finished() const
{
if (on_response_)
{
on_response_(this->response);
on_response_(response);
}
}
void fail(bool did_connect, bool did_timeout, std::string_view errmsg)
{
this->response.did_connect = did_connect;
this->response.did_timeout = did_timeout;
this->response.errmsg = errmsg;
this->requestFinished();
response.did_connect = did_connect;
response.did_timeout = did_timeout;
response.errmsg = errmsg;
request_finished();
}
void onResponse(tau_action_t action, InBuf& buf)
void on_response(tr_address_type ip_protocol_resp, tau_action_t action, InBuf& buf)
{
auto const buflen = std::size(buf);
this->response.did_connect = true;
this->response.did_timeout = false;
response.did_connect = true;
response.did_timeout = false;
if (action == TAU_ACTION_ANNOUNCE && buflen >= 3 * sizeof(uint32_t))
{
@ -239,8 +242,18 @@ struct tau_announce_request
response.leechers = buf.to_uint32();
response.seeders = buf.to_uint32();
response.pex = tr_pex::from_compact_ipv4(std::data(buf), std::size(buf), nullptr, 0);
requestFinished();
switch (ip_protocol_resp)
{
case TR_AF_INET:
response.pex = tr_pex::from_compact_ipv4(std::data(buf), std::size(buf), nullptr, 0);
break;
case TR_AF_INET6:
response.pex6 = tr_pex::from_compact_ipv6(std::data(buf), std::size(buf), nullptr, 0);
break;
default:
break;
}
request_finished();
}
else
{
@ -249,23 +262,24 @@ struct tau_announce_request
}
}
[[nodiscard]] constexpr auto expiresAt() const noexcept
[[nodiscard]] constexpr auto expires_at() const noexcept
{
return created_at_ + TR_ANNOUNCE_TIMEOUT_SEC.count();
return created_at_ + TrAnnounceTimeoutSec.count();
}
enum tau_announce_event : uint8_t
{
// https://www.bittorrent.org/beps/bep_0015.html
// Used in the "event" field of an announce request.
// These values come from BEP 15
TAU_ANNOUNCE_EVENT_NONE = 0,
TAU_ANNOUNCE_EVENT_COMPLETED = 1,
TAU_ANNOUNCE_EVENT_STARTED = 2,
TAU_ANNOUNCE_EVENT_STOPPED = 3
};
std::vector<std::byte> payload;
PayloadBuffer payload;
tr_address_type const ip_protocol;
time_t sent_at = 0;
tau_transaction_t const transaction_id = tau_transaction_new();
@ -303,97 +317,145 @@ struct tau_tracker
tau_tracker(
Mediator& mediator,
std::string_view const interned_authority,
std::string_view const interned_host,
std::string_view const authority_in,
std::string_view const host_in,
std::string_view const host_lookup_in,
tr_port const port_in)
: authority{ interned_authority }
, host{ interned_host }
: authority{ authority_in }
, host{ host_in }
, host_lookup{ host_lookup_in }
, port{ port_in }
, mediator_{ mediator }
{
}
void sendto(std::byte const* buf, size_t buflen)
void sendto(tr_address_type ip_protocol, std::byte const* buf, size_t buflen)
{
TR_ASSERT(addr_);
if (!addr_)
TR_ASSERT(tr_address::is_valid(ip_protocol));
if (!tr_address::is_valid(ip_protocol))
{
return;
}
auto const& [ss, sslen] = *addr_;
auto const& addr = addr_[ip_protocol];
TR_ASSERT(addr);
if (!addr)
{
return;
}
auto const& [ss, sslen] = *addr;
mediator_.sendto(buf, buflen, reinterpret_cast<sockaddr const*>(&ss), sslen);
}
void on_connection_response(tau_action_t action, InBuf& buf)
void on_connection_response(tr_address_type ip_protocol, tau_action_t action, InBuf& buf)
{
this->connecting_at = 0;
this->connection_transaction_id = 0;
TR_ASSERT(tr_address::is_valid(ip_protocol));
if (!tr_address::is_valid(ip_protocol))
{
return;
}
connecting_at[ip_protocol] = 0;
connection_transaction_id[ip_protocol] = 0;
if (action == TAU_ACTION_CONNECT)
{
this->connection_id = buf.to_uint64();
this->connection_expiration_time = tr_time() + TauConnectionTtlSecs;
logdbg(log_name(), fmt::format("Got a new connection ID from tracker: {}", this->connection_id));
connection_id[ip_protocol] = buf.to_uint64();
connection_expiration_time[ip_protocol] = tr_time() + TauConnectionTtlSecs;
logdbg(
log_name(),
fmt::format(
"Got a new {} connection ID from tracker: {}",
tr_ip_protocol_to_sv(ip_protocol),
connection_id[ip_protocol]));
}
else if (action == TAU_ACTION_ERROR)
{
std::string errmsg = !std::empty(buf) ? buf.to_string() : _("Connection failed");
this->failAll(true, false, errmsg);
std::string errmsg = !std::empty(buf) ?
buf.to_string() :
fmt::format(_("{ip_protocol} connection failed"), fmt::arg("ip_protocol", tr_ip_protocol_to_sv(ip_protocol)));
fail_all(true, false, errmsg);
logdbg(log_name(), std::move(errmsg));
}
this->upkeep();
upkeep();
}
void upkeep(bool timeout_reqs = true)
{
time_t const now = tr_time();
// do we have a DNS request that's ready?
if (addr_pending_dns_ && addr_pending_dns_->wait_for(0ms) == std::future_status::ready)
for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
{
addr_ = addr_pending_dns_->get();
addr_pending_dns_.reset();
addr_expires_at_ = now + DnsRetryIntervalSecs;
// do we have a DNS request that's ready?
if (auto& dns = addr_pending_dns_[ipp]; dns && dns->wait_for(0ms) == std::future_status::ready)
{
addr_[ipp] = dns->get();
dns.reset();
addr_expires_at_[ipp] = now + DnsRetryIntervalSecs;
}
}
// are there any requests pending?
if (this->is_idle())
// are there any tracker requests pending?
if (is_idle())
{
return;
}
// update the addr if our lookup is past its shelf date
if (!addr_pending_dns_ && addr_expires_at_ <= now)
for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
{
// update the addr if our lookup is past its shelf date
if (!addr_pending_dns_[ipp] && addr_expires_at_[ipp] <= now)
{
addr_[ipp].reset();
addr_pending_dns_[ipp] = std::async(
std::launch::async,
[this](tr_address_type ip_protocol) { return lookup(ip_protocol); },
static_cast<tr_address_type>(ipp));
}
}
// are there any dns requests pending?
if (is_dns_pending())
{
addr_.reset();
addr_pending_dns_ = std::async(std::launch::async, lookup, this->log_name(), this->host, this->port);
return;
}
logtrace(
log_name(),
fmt::format(
"connected {} ({} {}) -- connecting_at {}",
is_connected(now),
this->connection_expiration_time,
now,
this->connecting_at));
/* also need a valid connection ID... */
if (addr_ && !is_connected(now) && this->connecting_at == 0)
for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
{
this->connecting_at = now;
this->connection_transaction_id = tau_transaction_new();
logtrace(log_name(), fmt::format("Trying to connect. Transaction ID is {}", this->connection_transaction_id));
auto const ipp_enum = static_cast<tr_address_type>(ipp);
logtrace(
log_name(),
fmt::format(
"{} connected {} ({} {}) -- connecting_at {}",
tr_ip_protocol_to_sv(ipp_enum),
is_connected(ipp_enum, now),
connection_expiration_time[ipp],
now,
connecting_at[ipp]));
auto buf = PayloadBuffer{};
buf.add_uint64(0x41727101980LL);
buf.add_uint32(TAU_ACTION_CONNECT);
buf.add_uint32(this->connection_transaction_id);
// also need a valid connection ID...
if (auto const& addr = addr_[ipp]; addr && !is_connected(ipp_enum, now) && connecting_at[ipp] == 0)
{
TR_ASSERT(addr->first.ss_family == tr_ip_protocol_to_af(ipp_enum));
this->sendto(std::data(buf), std::size(buf));
connecting_at[ipp] = now;
connection_transaction_id[ipp] = tau_transaction_new();
logtrace(
log_name(),
fmt::format(
"Trying to connect {}. Transaction ID is {}",
tr_ip_protocol_to_sv(ipp_enum),
connection_transaction_id[ipp]));
auto buf = PayloadBuffer{};
buf.add_uint64(0x41727101980LL);
buf.add_uint32(TAU_ACTION_CONNECT);
buf.add_uint32(connection_transaction_id[ipp]);
sendto(ipp_enum, std::data(buf), std::size(buf));
}
}
if (timeout_reqs)
@ -401,91 +463,113 @@ struct tau_tracker
timeout_requests(now);
}
if (addr_ && is_connected(now))
{
send_requests();
}
maybe_send_requests(now);
}
[[nodiscard]] bool is_idle() const noexcept
[[nodiscard]] constexpr bool is_idle() const noexcept
{
return std::empty(announces) && std::empty(scrapes) && !addr_pending_dns_;
return std::empty(announces) && std::empty(scrapes);
}
private:
using Sockaddr = std::pair<sockaddr_storage, socklen_t>;
using MaybeSockaddr = std::optional<Sockaddr>;
[[nodiscard]] constexpr bool is_connected(time_t now) const noexcept
[[nodiscard]] constexpr bool is_connected(tr_address_type ip_protocol, time_t now) const noexcept
{
return connection_id != tau_connection_t{} && now < connection_expiration_time;
return connection_id[ip_protocol] != tau_connection_t{} && now < connection_expiration_time[ip_protocol];
}
[[nodiscard]] static MaybeSockaddr lookup(
std::string_view const interned_log_name,
std::string_view const interned_host,
tr_port const port)
[[nodiscard]] TR_CONSTEXPR20 bool is_dns_pending() const noexcept
{
return std::any_of(std::begin(addr_pending_dns_), std::end(addr_pending_dns_), [](auto const& o) { return !!o; });
}
[[nodiscard]] TR_CONSTEXPR20 bool has_addr() const noexcept
{
return std::any_of(std::begin(addr_), std::end(addr_), [](auto const& o) { return !!o; });
}
[[nodiscard]] MaybeSockaddr lookup(tr_address_type ip_protocol)
{
auto szport = std::array<char, 16>{};
*fmt::format_to(std::data(szport), "{:d}", port.host()) = '\0';
auto hints = addrinfo{};
hints.ai_family = AF_INET; // https://github.com/transmission/transmission/issues/4719
hints.ai_family = tr_ip_protocol_to_af(ip_protocol);
hints.ai_protocol = IPPROTO_UDP;
hints.ai_socktype = SOCK_DGRAM;
addrinfo* info = nullptr;
auto const szhost = tr_pathbuf{ interned_host };
auto const szhost = tr_urlbuf{ host_lookup };
if (int const rc = getaddrinfo(szhost.c_str(), std::data(szport), &hints, &info); rc != 0)
{
logwarn(
interned_log_name,
log_name(),
fmt::format(
_("Couldn't look up '{address}:{port}': {error} ({error_code})"),
fmt::arg("address", interned_host),
fmt::runtime(_("Couldn't look up '{address}:{port}' in {ip_protocol}: {error} ({error_code})")),
fmt::arg("address", host),
fmt::arg("port", port.host()),
fmt::arg("ip_protocol", tr_ip_protocol_to_sv(ip_protocol)),
fmt::arg("error", gai_strerror(rc)),
fmt::arg("error_code", static_cast<int>(rc))));
return {};
}
auto const info_uniq = std::unique_ptr<addrinfo, void (*)(addrinfo*)>{ info,
[](addrinfo* p) // MSVC forced my hands
{
freeaddrinfo(p);
} };
auto ss = sockaddr_storage{};
auto const len = info->ai_addrlen;
memcpy(&ss, info->ai_addr, len);
freeaddrinfo(info);
// N.B. getaddrinfo() will return IPv4-mapped addresses by default on macOS
auto socket_address = tr_socket_address::from_sockaddr(info->ai_addr);
if (!socket_address || socket_address->address().is_ipv4_mapped_address())
{
logdbg(
log_name(),
fmt::format(
"Couldn't look up '{address}:{port}' in {ip_protocol}: got invalid address",
fmt::arg("address", host),
fmt::arg("port", port.host()),
fmt::arg("ip_protocol", tr_ip_protocol_to_sv(ip_protocol))));
return {};
}
logdbg(interned_log_name, "DNS lookup succeeded");
return std::make_pair(ss, len);
logdbg(log_name(), fmt::format("{} DNS lookup succeeded", tr_ip_protocol_to_sv(ip_protocol)));
return socket_address->to_sockaddr();
}
void failAll(bool did_connect, bool did_timeout, std::string_view errmsg)
void fail_all(bool did_connect, bool did_timeout, std::string_view errmsg)
{
for (auto& req : this->scrapes)
for (auto& req : scrapes)
{
req.fail(did_connect, did_timeout, errmsg);
}
for (auto& req : this->announces)
for (auto& req : announces)
{
req.fail(did_connect, did_timeout, errmsg);
}
this->scrapes.clear();
this->announces.clear();
scrapes.clear();
announces.clear();
}
///
// ---
void timeout_requests(time_t now)
{
if (this->connecting_at != 0 && this->connecting_at + ConnectionRequestTtl < now)
for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
{
auto empty_buf = PayloadBuffer{};
on_connection_response(TAU_ACTION_ERROR, empty_buf);
if (connecting_at[ipp] != 0 && connecting_at[ipp] + ConnectionRequestTtl < now)
{
auto empty_buf = PayloadBuffer{};
on_connection_response(static_cast<tr_address_type>(ipp), TAU_ACTION_ERROR, empty_buf);
}
}
timeout_requests(this->announces, now, "announce");
timeout_requests(this->scrapes, now, "scrape");
timeout_requests(announces, now, "announce"sv);
timeout_requests(scrapes, now, "scrape"sv);
}
template<typename T>
@ -493,7 +577,7 @@ private:
{
for (auto it = std::begin(requests); it != std::end(requests);)
{
if (auto& req = *it; req.expiresAt() <= now)
if (auto& req = *it; req.expires_at() <= now)
{
logtrace(log_name(), fmt::format("timeout {} req {}", name, fmt::ptr(&req)));
req.fail(false, true, "");
@ -506,37 +590,35 @@ private:
}
}
///
// ---
void send_requests()
void maybe_send_requests(time_t now)
{
TR_ASSERT(!addr_pending_dns_);
TR_ASSERT(addr_);
TR_ASSERT(this->connecting_at == 0);
TR_ASSERT(this->connection_expiration_time > tr_time());
TR_ASSERT(!is_dns_pending());
if (is_dns_pending() || !has_addr())
{
return;
}
send_requests(this->announces);
send_requests(this->scrapes);
maybe_send_requests(announces, now);
maybe_send_requests(scrapes, now);
}
template<typename T>
void send_requests(std::list<T>& reqs)
void maybe_send_requests(std::list<T>& reqs, time_t now)
{
auto const now = tr_time();
for (auto it = std::begin(reqs); it != std::end(reqs);)
{
auto& req = *it;
if (req.sent_at != 0) // it's already been sent; we're awaiting a response
if (req.sent_at != 0 || // it's already been sent; we're awaiting a response
!maybe_send_request(req.ip_protocol, std::data(req.payload), std::size(req.payload), now))
{
++it;
continue;
}
logdbg(log_name(), fmt::format("sending req {}", fmt::ptr(&req)));
logdbg(log_name(), fmt::format("sent req {}", fmt::ptr(&req)));
req.sent_at = now;
send_request(std::data(req.payload), std::size(req.payload));
if (req.has_callback())
{
@ -549,15 +631,24 @@ private:
}
}
void send_request(std::byte const* payload, size_t payload_len)
bool maybe_send_request(tr_address_type ip_protocol, std::byte const* payload, size_t payload_len, time_t now)
{
logdbg(log_name(), fmt::format("sending request w/connection id {}", this->connection_id));
for (uint8_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
{
auto const ipp_enum = static_cast<tr_address_type>(ipp);
if (addr_[ipp] && (ip_protocol == TR_AF_UNSPEC || ipp == ip_protocol) && is_connected(ipp_enum, now))
{
logdbg(log_name(), fmt::format("sending request w/connection id {}", connection_id[ipp]));
auto buf = PayloadBuffer{};
buf.add_uint64(this->connection_id);
buf.add(payload, payload_len);
auto buf = PayloadBuffer{};
buf.add_uint64(connection_id[ipp]);
buf.add(payload, payload_len);
this->sendto(std::data(buf), std::size(buf));
sendto(ipp_enum, std::data(buf), std::size(buf));
return true;
}
}
return false;
}
public:
@ -566,14 +657,15 @@ public:
return authority;
}
std::string_view const authority; // interned
std::string_view const host; // interned
std::string_view const authority;
std::string_view const host;
std::string_view const host_lookup;
tr_port const port;
time_t connecting_at = 0;
time_t connection_expiration_time = 0;
tau_connection_t connection_id = {};
tau_transaction_t connection_transaction_id = {};
std::array<time_t, NUM_TR_AF_INET_TYPES> connecting_at = {};
std::array<time_t, NUM_TR_AF_INET_TYPES> connection_expiration_time = {};
std::array<tau_connection_t, NUM_TR_AF_INET_TYPES> connection_id = {};
std::array<tau_transaction_t, NUM_TR_AF_INET_TYPES> connection_transaction_id = {};
std::list<tau_announce_request> announces;
std::list<tau_scrape_request> scrapes;
@ -581,13 +673,13 @@ public:
private:
Mediator& mediator_;
std::optional<std::future<MaybeSockaddr>> addr_pending_dns_;
std::array<std::optional<std::future<MaybeSockaddr>>, NUM_TR_AF_INET_TYPES> addr_pending_dns_;
MaybeSockaddr addr_;
time_t addr_expires_at_ = 0;
std::array<MaybeSockaddr, NUM_TR_AF_INET_TYPES> addr_ = {};
std::array<time_t, NUM_TR_AF_INET_TYPES> addr_expires_at_ = {};
static constexpr auto DnsRetryIntervalSecs = time_t{ 3600 };
static constexpr auto ConnectionRequestTtl = 30;
static constexpr auto ConnectionRequestTtl = time_t{ 30 };
};
// --- SESSION
@ -602,20 +694,22 @@ public:
void announce(tr_announce_request const& request, tr_announce_response_func on_response) override
{
auto* const tracker = getTrackerFromUrl(request.announce_url);
auto* const tracker = get_tracker_from_url(request.announce_url);
if (tracker == nullptr)
{
return;
}
// Since size of IP field is only 4 bytes long, we can only announce IPv4 addresses
tracker->announces.emplace_back(mediator_.announce_ip(), request, std::move(on_response));
for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
{
tracker->announces.emplace_back(static_cast<tr_address_type>(ipp), mediator_.announce_ip(), request, on_response);
}
tracker->upkeep(false);
}
void scrape(tr_scrape_request const& request, tr_scrape_response_func on_response) override
{
auto* const tracker = getTrackerFromUrl(request.scrape_url);
auto* const tracker = get_tracker_from_url(request.scrape_url);
if (tracker == nullptr)
{
return;
@ -635,7 +729,7 @@ public:
// @brief process an incoming udp message if it's a tracker response.
// @return true if msg was a tracker response; false otherwise
bool handle_message(uint8_t const* msg, size_t msglen) override
bool handle_message(uint8_t const* msg, size_t msglen, struct sockaddr const* from, socklen_t /*fromlen*/) override
{
if (msglen < sizeof(uint32_t) * 2)
{
@ -647,21 +741,24 @@ public:
buf.add(msg, msglen);
auto const action_id = static_cast<tau_action_t>(buf.to_uint32());
if (!isResponseMessage(action_id, msglen))
if (!is_response_message(action_id, msglen))
{
return false;
}
/* extract the transaction_id and look for a match */
// extract the transaction_id and look for a match
tau_transaction_t const transaction_id = buf.to_uint32();
auto const socket_address = tr_socket_address::from_sockaddr(from);
auto const ip_protocol = socket_address ? socket_address->address().type : NUM_TR_AF_INET_TYPES;
for (auto& tracker : trackers_)
{
// is it a connection response?
if (tracker.connecting_at != 0 && transaction_id == tracker.connection_transaction_id)
if (tr_address::is_valid(ip_protocol) && tracker.connecting_at[ip_protocol] != 0 &&
transaction_id == tracker.connection_transaction_id[ip_protocol])
{
logtrace(tracker.log_name(), fmt::format("{} is my connection request!", transaction_id));
tracker.on_connection_response(action_id, buf);
tracker.on_connection_response(ip_protocol, action_id, buf);
return true;
}
@ -675,9 +772,8 @@ public:
it != std::end(reqs))
{
logtrace(tracker.log_name(), fmt::format("{} is an announce request!", transaction_id));
auto req = *it;
it = reqs.erase(it);
req.onResponse(action_id, buf);
it->on_response(ip_protocol, action_id, buf);
reqs.erase(it);
return true;
}
}
@ -692,15 +788,14 @@ public:
it != std::end(reqs))
{
logtrace(tracker.log_name(), fmt::format("{} is a scrape request!", transaction_id));
auto req = *it;
it = reqs.erase(it);
req.onResponse(action_id, buf);
it->on_response(action_id, buf);
reqs.erase(it);
return true;
}
}
}
/* no match... */
// no match...
return false;
}
@ -712,7 +807,7 @@ public:
private:
// Finds the tau_tracker struct that corresponds to this url.
// If it doesn't exist yet, create one.
tau_tracker* getTrackerFromUrl(tr_interned_string const announce_url)
tau_tracker* get_tracker_from_url(tr_interned_string const announce_url)
{
// build a lookup key for this tracker
auto const parsed = tr_urlParseTracker(announce_url);
@ -733,12 +828,17 @@ private:
}
// we don't have it -- build a new one
auto& tracker = trackers_.emplace_back(mediator_, authority, parsed->host, tr_port::from_host(parsed->port));
auto& tracker = trackers_.emplace_back(
mediator_,
authority,
parsed->host,
parsed->host_wo_brackets,
tr_port::from_host(parsed->port));
logtrace(tracker.log_name(), "New tau_tracker created");
return &tracker;
}
[[nodiscard]] static constexpr bool isResponseMessage(tau_action_t action, size_t msglen) noexcept
[[nodiscard]] static constexpr bool is_response_message(tau_action_t action, size_t msglen) noexcept
{
if (action == TAU_ACTION_CONNECT)
{

View File

@ -160,7 +160,7 @@ public:
return nullptr;
}
auto const [it, is_new] = scrape_info_.try_emplace(url, url, TR_MULTISCRAPE_MAX);
auto const [it, is_new] = scrape_info_.try_emplace(url, url, TrMultiscrapeMax);
return &it->second;
}
@ -179,7 +179,7 @@ public:
}
else
{
tr_logAddError(fmt::format(_("Unsupported URL: '{url}'"), fmt::arg("url", scrape_sv)));
tr_logAddError(fmt::format(fmt::runtime(_("Unsupported URL: '{url}'")), fmt::arg("url", scrape_sv)));
}
}
@ -198,7 +198,7 @@ public:
}
else
{
tr_logAddWarn(fmt::format(_("Unsupported URL: '{url}'"), fmt::arg("url", announce_sv)));
tr_logAddWarn(fmt::format(fmt::runtime(_("Unsupported URL: '{url}'")), fmt::arg("url", announce_sv)));
}
}
@ -881,7 +881,10 @@ void on_announce_error(tr_tier* tier, char const* err, tr_announce_event e)
{
tr_logAddErrorTier(
tier,
fmt::format(_("Announce error: {error} ({url})"), fmt::arg("error", err), fmt::arg("url", announce_url)));
fmt::format(
fmt::runtime(_("Announce error: {error} ({url})")),
fmt::arg("error", err),
fmt::arg("url", announce_url)));
}
else
{
@ -890,10 +893,10 @@ void on_announce_error(tr_tier* tier, char const* err, tr_announce_event e)
tr_logAddWarnTier(
tier,
fmt::format(
tr_ngettext(
fmt::runtime(tr_ngettext(
"Announce error: {error} (Retrying in {count} second) ({url})",
"Announce error: {error} (Retrying in {count} seconds) ({url})",
interval),
interval)),
fmt::arg("error", err),
fmt::arg("count", interval),
fmt::arg("url", announce_url)));

View File

@ -156,7 +156,7 @@ public:
// @brief process an incoming udp message if it's a tracker response.
// @return true if msg was a tracker response; false otherwise
virtual bool handle_message(uint8_t const* msg, size_t msglen) = 0;
virtual bool handle_message(uint8_t const* msg, size_t msglen, struct sockaddr const* from, socklen_t fromlen) = 0;
[[nodiscard]] virtual bool is_idle() const noexcept = 0;
};

View File

@ -59,7 +59,7 @@ void save(std::string_view filename, address_range_t const* ranges, size_t n_ran
if (!out.is_open())
{
tr_logAddWarn(fmt::format(
_("Couldn't read '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't read '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", tr_strerror(errno)),
fmt::arg("error_code", errno)));
@ -70,7 +70,7 @@ void save(std::string_view filename, address_range_t const* ranges, size_t n_ran
!out.write(reinterpret_cast<char const*>(ranges), n_ranges * sizeof(*ranges)))
{
tr_logAddWarn(fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't save '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", tr_strerror(errno)),
fmt::arg("error_code", errno)));
@ -78,7 +78,8 @@ void save(std::string_view filename, address_range_t const* ranges, size_t n_ran
else
{
tr_logAddInfo(fmt::format(
tr_ngettext("Blocklist '{path}' has {count} entry", "Blocklist '{path}' has {count} entries", n_ranges),
fmt::runtime(
tr_ngettext("Blocklist '{path}' has {count} entry", "Blocklist '{path}' has {count} entries", n_ranges)),
fmt::arg("path", tr_sys_path_basename(filename)),
fmt::arg("count", n_ranges)));
}
@ -237,7 +238,7 @@ auto parseFile(std::string_view filename)
if (!in.is_open())
{
tr_logAddWarn(fmt::format(
_("Couldn't read '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't read '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", tr_strerror(errno)),
fmt::arg("error_code", errno)));
@ -256,7 +257,7 @@ auto parseFile(std::string_view filename)
else
{
// don't try to display the actual lines - it causes issues
tr_logAddWarn(fmt::format(_("Couldn't parse line: '{line}'"), fmt::arg("line", line_number)));
tr_logAddWarn(fmt::format(fmt::runtime(_("Couldn't parse line: '{line}'")), fmt::arg("line", line_number)));
}
}
in.close();
@ -335,7 +336,7 @@ void Blocklists::Blocklist::ensureLoaded() const
if (error)
{
tr_logAddWarn(fmt::format(
_("Couldn't read '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't read '{path}': {error} ({error_code})")),
fmt::arg("path", bin_file_),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -350,7 +351,7 @@ void Blocklists::Blocklist::ensureLoaded() const
if (!in)
{
tr_logAddWarn(fmt::format(
_("Couldn't read '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't read '{path}': {error} ({error_code})")),
fmt::arg("path", bin_file_),
fmt::arg("error", tr_strerror(errno)),
fmt::arg("error_code", errno)));
@ -400,7 +401,8 @@ void Blocklists::Blocklist::ensureLoaded() const
}
tr_logAddInfo(fmt::format(
tr_ngettext("Blocklist '{path}' has {count} entry", "Blocklist '{path}' has {count} entries", std::size(rules_)),
fmt::runtime(
tr_ngettext("Blocklist '{path}' has {count} entry", "Blocklist '{path}' has {count} entries", std::size(rules_))),
fmt::arg("path", tr_sys_path_basename(bin_file_)),
fmt::arg("count", std::size(rules_))));
}
@ -470,7 +472,7 @@ std::optional<Blocklists::Blocklist> Blocklists::Blocklist::saveNew(
if (error)
{
tr_logAddWarn(fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't save '{path}': {error} ({error_code})")),
fmt::arg("path", src_file),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));

View File

@ -78,7 +78,7 @@ private:
[[nodiscard]] bool contains(tr_address const& addr) const;
[[nodiscard]] auto size() const
[[nodiscard]] size_t size() const
{
ensureLoaded();

View File

@ -70,7 +70,7 @@ void log_ccrypto_error(CCCryptorStatus error_code, char const* file, long line)
line,
TR_LOG_ERROR,
fmt::format(
_("{crypto_library} error: {error} ({error_code})"),
fmt::runtime(_("{crypto_library} error: {error} ({error_code})")),
fmt::arg("crypto_library", "CCrypto"),
fmt::arg("error", ccrypto_error_to_str(error_code)),
fmt::arg("error_code", error_code)));

View File

@ -3,7 +3,6 @@
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include <memory>
#include <mutex>
#include <mbedtls/base64.h>
@ -43,7 +42,7 @@ void log_mbedtls_error(int error_code, char const* file, int line)
line,
TR_LOG_ERROR,
fmt::format(
_("{crypto_library} error: {error} ({error_code})"),
fmt::runtime(_("{crypto_library} error: {error} ({error_code})")),
fmt::arg("crypto_library", "MbedTLS"),
fmt::arg("error", error_message),
fmt::arg("error_code", error_code)));
@ -118,7 +117,7 @@ void tr_sha1::clear()
{
mbedtls_sha1_init(&handle_);
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
#if MBEDTLS_VERSION_NUMBER < 0x03000000 && MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha1_starts_ret(&handle_);
#else
mbedtls_sha1_starts(&handle_);
@ -132,7 +131,7 @@ void tr_sha1::add(void const* data, size_t data_length)
return;
}
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
#if MBEDTLS_VERSION_NUMBER < 0x03000000 && MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha1_update_ret(&handle_, static_cast<unsigned char const*>(data), data_length);
#else
mbedtls_sha1_update(&handle_, static_cast<unsigned char const*>(data), data_length);
@ -143,7 +142,7 @@ tr_sha1_digest_t tr_sha1::finish()
{
auto digest = tr_sha1_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
#if MBEDTLS_VERSION_NUMBER < 0x03000000 && MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha1_finish_ret(&handle_, digest_as_uchar);
#else
mbedtls_sha1_finish(&handle_, digest_as_uchar);
@ -165,10 +164,10 @@ void tr_sha256::clear()
{
mbedtls_sha256_init(&handle_);
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
#if MBEDTLS_VERSION_NUMBER < 0x03000000 && MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha256_starts_ret(&handle_, 0);
#else
mbedtls_sha256_starts(&handle_);
mbedtls_sha256_starts(&handle_, 0);
#endif
}
@ -179,7 +178,7 @@ void tr_sha256::add(void const* data, size_t data_length)
return;
}
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
#if MBEDTLS_VERSION_NUMBER < 0x03000000 && MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha256_update_ret(&handle_, static_cast<unsigned char const*>(data), data_length);
#else
mbedtls_sha256_update(&handle_, static_cast<unsigned char const*>(data), data_length);
@ -190,7 +189,7 @@ tr_sha256_digest_t tr_sha256::finish()
{
auto digest = tr_sha256_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
#if MBEDTLS_VERSION_NUMBER < 0x03000000 && MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha256_finish_ret(&handle_, digest_as_uchar);
#else
mbedtls_sha256_finish(&handle_, digest_as_uchar);

View File

@ -10,7 +10,6 @@
#include <array>
#include <cstddef> // size_t
#include <memory>
#include <openssl/crypto.h>
#include <openssl/err.h>
@ -62,7 +61,7 @@ void log_openssl_error(char const* file, int line)
line,
TR_LOG_ERROR,
fmt::format(
_("{crypto_library} error: {error} ({error_code})"),
fmt::runtime(_("{crypto_library} error: {error} ({error_code})")),
fmt::arg("crypto_library", "OpenSSL"),
fmt::arg("error", std::data(buf)),
fmt::arg("error_code", error_code)));

View File

@ -3,7 +3,6 @@
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include <memory>
#include <mutex>
#include <wolfssl/options.h>
@ -46,7 +45,7 @@ void log_wolfssl_error(int error_code, char const* file, int line)
line,
TR_LOG_ERROR,
fmt::format(
_("{crypto_library} error: {error} ({error_code})"),
fmt::runtime(_("{crypto_library} error: {error} ({error_code})")),
fmt::arg("crypto_library", "WolfSSL"),
fmt::arg("error", wc_GetErrorString(error_code)),
fmt::arg("error_code", error_code)));

View File

@ -10,7 +10,6 @@
#include <cstddef> // size_t
#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
#include <random> // for std::uniform_int_distribution<T>
#include <string>

View File

@ -23,8 +23,11 @@
#include <sys/stat.h>
#include <unistd.h> /* lseek(), write(), ftruncate(), pread(), pwrite(), pathconf(), etc */
#ifdef HAVE_XFS_XFS_H
#ifdef HAVE_FLOCK
#include <sys/file.h> /* flock() */
#endif
#ifdef HAVE_XFS_XFS_H
#include <xfs/xfs.h>
#endif

View File

@ -274,7 +274,7 @@ void tr_global_ip_cache::update_source_addr(tr_address_type type) noexcept
{
set_source_addr(*source_addr);
tr_logAddDebug(fmt::format(
_("Successfully updated source {protocol} address to {ip}"),
fmt::runtime(_("Successfully updated source {protocol} address to {ip}")),
fmt::arg("protocol", protocol),
fmt::arg("ip", source_addr->display_name())));
}
@ -288,7 +288,8 @@ void tr_global_ip_cache::update_source_addr(tr_address_type type) noexcept
{
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)));
tr_logAddInfo(
fmt::format(fmt::runtime(_("Your machine does not support {protocol}")), fmt::arg("protocol", protocol)));
}
}
@ -312,7 +313,7 @@ void tr_global_ip_cache::on_response_ip_query(tr_address_type type, tr_web::Fetc
upkeep_timers_[type]->set_interval(UpkeepInterval);
tr_logAddDebug(fmt::format(
_("Successfully updated global {type} address to {ip} using {url}"),
fmt::runtime(_("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]])));

View File

@ -26,7 +26,7 @@ class tr_recentHistory
public:
/**
* @brief add a counter to the recent history object.
* @param when the current time in sec, such as from tr_time()
* @param now the current time in seconds, such as from tr_time()
* @param n how many items to add to the history's counter
*/
constexpr void add(time_t now, SizeType n)
@ -43,8 +43,8 @@ public:
/**
* @brief count how many events have occurred in the last N seconds.
* @param when the current time in sec, such as from tr_time()
* @param seconds how many seconds to count back through.
* @param now the current time in seconds, such as from tr_time()
* @param age_sec how many seconds to count back through.
*/
[[nodiscard]] constexpr SizeType count(time_t now, unsigned int age_sec) const
{

View File

@ -115,7 +115,7 @@ bool write_entire_buf(tr_sys_file_t const fd, uint64_t file_offset, uint8_t cons
error.set(
err,
fmt::format(
_("Couldn't get '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't get '{path}': {error} ({error_code})")),
fmt::arg("path", tor.file_subpath(file_index)),
fmt::arg("error", tr_strerror(err)),
fmt::arg("error_code", err)));

View File

@ -8,7 +8,6 @@
#include <chrono>
#include <cstddef> // size_t
#include <iterator> // back_insert_iterator, empty
#include <memory>
#include <mutex>
#include <optional>
#include <string>

View File

@ -77,7 +77,7 @@ void walkTree(std::string_view const top, std::string_view const subpath, std::s
if (error)
{
tr_logAddWarn(fmt::format(
_("Skipping '{path}': {error} ({error_code})"),
fmt::runtime(_("Skipping '{path}': {error} ({error_code})")),
fmt::arg("path", path),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -134,7 +134,7 @@ tr_metainfo_builder::tr_metainfo_builder(std::string_view single_file_or_parent_
: top_{ single_file_or_parent_directory }
{
files_ = findFiles(tr_sys_path_dirname(top_), tr_sys_path_basename(top_));
block_info_ = tr_block_info{ files_.totalSize(), default_piece_size(files_.totalSize()) };
block_info_ = tr_block_info{ files_.total_size(), default_piece_size(files_.total_size()) };
}
bool tr_metainfo_builder::set_piece_size(uint32_t piece_size) noexcept
@ -144,7 +144,7 @@ bool tr_metainfo_builder::set_piece_size(uint32_t piece_size) noexcept
return false;
}
block_info_ = tr_block_info{ files_.totalSize(), piece_size };
block_info_ = tr_block_info{ files_.total_size(), piece_size };
return true;
}

View File

@ -122,12 +122,12 @@ public:
[[nodiscard]] TR_CONSTEXPR20 auto file_count() const noexcept
{
return files_.fileCount();
return files_.file_count();
}
[[nodiscard]] TR_CONSTEXPR20 auto file_size(tr_file_index_t i) const noexcept
{
return files_.fileSize(i);
return files_.file_size(i);
}
[[nodiscard]] constexpr auto is_private() const noexcept
@ -167,7 +167,7 @@ public:
[[nodiscard]] constexpr auto total_size() const noexcept
{
return files_.totalSize();
return files_.total_size();
}
[[nodiscard]] constexpr auto const& webseeds() const noexcept

View File

@ -1,3 +1,6 @@
// This file was generated with libtransmission/mime-types.js
// DO NOT EDIT MANUALLY
// This file Copyright © Mnemosyne LLC.
// 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.

View File

@ -1,7 +1,10 @@
#!/usr/bin/env node
const copyright =
`// This file Copyright © 2021-${new Date().getFullYear()} Mnemosyne LLC.
`// This file was generated with libtransmission/mime-types.js
// DO NOT EDIT MANUALLY
// This file Copyright © Mnemosyne LLC.
// 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.`;

View File

@ -16,8 +16,12 @@
#include <utility> // std::pair
#ifdef _WIN32
#include <winsock2.h> // must come before iphlpapi.h
#include <iphlpapi.h>
#include <ws2tcpip.h>
#else
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/tcp.h> /* TCP_CONGESTION */
#endif
@ -185,7 +189,7 @@ tr_socket_t createSocket(int domain, int type)
if (sockerrno != EAFNOSUPPORT)
{
tr_logAddWarn(fmt::format(
_("Couldn't create socket: {error} ({error_code})"),
fmt::runtime(_("Couldn't create socket: {error} ({error_code})")),
fmt::arg("error", tr_net_strerror(sockerrno)),
fmt::arg("error_code", sockerrno)));
}
@ -262,7 +266,7 @@ tr_peer_socket tr_netOpenPeerSocket(tr_session* session, tr_socket_address const
if (bind(s, reinterpret_cast<sockaddr const*>(&source_sock), sourcelen) == -1)
{
tr_logAddWarn(fmt::format(
_("Couldn't set source address {address} on {socket}: {error} ({error_code})"),
fmt::runtime(_("Couldn't set source address {address} on {socket}: {error} ({error_code})")),
fmt::arg("address", source_addr.display_name()),
fmt::arg("socket", s),
fmt::arg("error", tr_net_strerror(sockerrno)),
@ -281,7 +285,7 @@ tr_peer_socket tr_netOpenPeerSocket(tr_session* session, tr_socket_address const
(tmperrno != ECONNREFUSED && tmperrno != ENETUNREACH && tmperrno != EHOSTUNREACH) || addr.is_ipv4())
{
tr_logAddWarn(fmt::format(
_("Couldn't connect socket {socket} to {address}:{port}: {error} ({error_code})"),
fmt::runtime(_("Couldn't connect socket {socket} to {address}:{port}: {error} ({error_code})")),
fmt::arg("socket", s),
fmt::arg("address", addr.display_name()),
fmt::arg("port", port.host()),
@ -339,9 +343,10 @@ tr_socket_t tr_netBindTCPImpl(tr_address const& addr, tr_port port, bool suppres
if (!suppress_msgs)
{
tr_logAddError(fmt::format(
err == EADDRINUSE ?
_("Couldn't bind port {port} on {address}: {error} ({error_code}) -- Is another copy of Transmission already running?") :
_("Couldn't bind port {port} on {address}: {error} ({error_code})"),
fmt::runtime(
err == EADDRINUSE ?
_("Couldn't bind port {port} on {address}: {error} ({error_code}) -- Is another copy of Transmission already running?") :
_("Couldn't bind port {port} on {address}: {error} ({error_code})")),
fmt::arg("address", addr.display_name()),
fmt::arg("port", port.host()),
fmt::arg("error", tr_net_strerror(err)),
@ -429,16 +434,6 @@ namespace
namespace is_valid_for_peers_helpers
{
[[nodiscard]] constexpr auto is_ipv4_mapped_address(tr_address const& addr)
{
return addr.is_ipv6() && IN6_IS_ADDR_V4MAPPED(&addr.addr.addr6);
}
[[nodiscard]] constexpr auto is_ipv6_link_local_address(tr_address const& addr)
{
return addr.is_ipv6() && IN6_IS_ADDR_LINKLOCAL(&addr.addr.addr6);
}
/* isMartianAddr was written by Juliusz Chroboczek,
and is covered under the same license as third-party/dht/dht.c. */
[[nodiscard]] auto is_martian_addr(tr_address const& addr, tr_peer_from from)
@ -522,12 +517,16 @@ std::optional<tr_address> tr_address::from_string(std::string_view address_sv)
std::string_view tr_address::display_name(char* out, size_t outlen) const
{
TR_ASSERT(is_valid());
return evutil_inet_ntop(tr_ip_protocol_to_af(type), &addr, out, outlen);
if (auto* name = evutil_inet_ntop(tr_ip_protocol_to_af(type), &addr, out, outlen))
{
return name;
}
return "Invalid address"sv;
}
[[nodiscard]] std::string tr_address::display_name() const
{
auto buf = std::array<char, INET6_ADDRSTRLEN>{};
auto buf = std::array<char, std::max(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)>{};
return std::string{ display_name(std::data(buf), std::size(buf)) };
}
@ -556,6 +555,99 @@ std::pair<tr_address, std::byte const*> tr_address::from_compact_ipv6(std::byte
return { address, compact };
}
std::optional<unsigned> tr_address::to_interface_index() const noexcept
{
if (!is_valid())
{
tr_logAddDebug("Invalid target address to find interface index");
return {};
}
tr_logAddDebug(fmt::format("Find interface index for {}", display_name()));
#ifdef _WIN32
auto p_addresses = std::unique_ptr<void, void (*)(void*)>{ nullptr, operator delete };
// The recommended method of calling the GetAdaptersAddresses function is to
// pre-allocate a 15KB working buffer pointed to by the AdapterAddresses parameter.
// On typical computers, this dramatically reduces the chances that the
// GetAdaptersAddresses function returns ERROR_BUFFER_OVERFLOW, which would require
// calling GetAdaptersAddresses function multiple times.
// https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses
for (auto p_addresses_size = ULONG{ 15000 } /* 15KB */;;)
{
p_addresses.reset(operator new(p_addresses_size, std::nothrow));
if (!p_addresses)
{
tr_logAddDebug("Could not allocate memory for interface list");
return {};
}
if (auto ret = GetAdaptersAddresses(
AF_UNSPEC,
GAA_FLAG_SKIP_FRIENDLY_NAME,
nullptr,
reinterpret_cast<PIP_ADAPTER_ADDRESSES>(p_addresses.get()),
&p_addresses_size);
ret != ERROR_BUFFER_OVERFLOW)
{
if (ret != ERROR_SUCCESS)
{
tr_logAddDebug(fmt::format("Failed to retrieve interface list: {} ({})", ret, tr_win32_format_message(ret)));
return {};
}
break;
}
}
for (auto const* cur = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(p_addresses.get()); cur != nullptr; cur = cur->Next)
{
if (cur->OperStatus != IfOperStatusUp)
{
continue;
}
for (auto const* sa_p = cur->FirstUnicastAddress; sa_p != nullptr; sa_p = sa_p->Next)
{
if (auto if_addr = tr_socket_address::from_sockaddr(sa_p->Address.lpSockaddr);
if_addr && if_addr->address() == *this)
{
auto const ret = type == TR_AF_INET ? cur->IfIndex : cur->Ipv6IfIndex;
tr_logAddDebug(fmt::format("Found interface index for {}: {}", display_name(), ret));
return ret;
}
}
}
#else
struct ifaddrs* ifa = nullptr;
if (getifaddrs(&ifa) != 0)
{
auto err = errno;
tr_logAddDebug(fmt::format("Failed to retrieve interface list: {} ({})", err, tr_strerror(err)));
return {};
}
auto const ifa_uniq = std::unique_ptr<ifaddrs, void (*)(struct ifaddrs*)>{ ifa, freeifaddrs };
for (; ifa != nullptr; ifa = ifa->ifa_next)
{
if (ifa->ifa_addr == nullptr || (ifa->ifa_flags & IFF_UP) == 0U)
{
continue;
}
if (auto if_addr = tr_socket_address::from_sockaddr(ifa->ifa_addr); if_addr && if_addr->address() == *this)
{
auto const ret = if_nametoindex(ifa->ifa_name);
tr_logAddDebug(fmt::format("Found interface index for {}: {}", display_name(), ret));
return ret;
}
}
#endif
tr_logAddDebug(fmt::format("Could not find interface index for {}", display_name()));
return {};
}
int tr_address::compare(tr_address const& that) const noexcept // <=>
{
// IPv6 addresses are always "greater than" IPv4
@ -707,17 +799,30 @@ int tr_address::compare(tr_address const& that) const noexcept // <=>
std::string tr_socket_address::display_name(tr_address const& address, tr_port port) noexcept
{
return fmt::format(address.is_ipv6() ? "[{:s}]:{:d}" : "{:s}:{:d}", address.display_name(), port.host());
return fmt::format(fmt::runtime(address.is_ipv6() ? "[{:s}]:{:d}" : "{:s}:{:d}"), address.display_name(), port.host());
}
bool tr_socket_address::is_valid_for_peers(tr_peer_from from) const noexcept
{
using namespace is_valid_for_peers_helpers;
return is_valid() && !std::empty(port_) && !is_ipv6_link_local_address(address_) && !is_ipv4_mapped_address(address_) &&
return is_valid() && !std::empty(port_) && !address_.is_ipv6_link_local_address() && !address_.is_ipv4_mapped_address() &&
!is_martian_addr(address_, from);
}
std::optional<tr_socket_address> tr_socket_address::from_string(std::string_view sockaddr_sv)
{
auto ss = sockaddr_storage{};
auto sslen = int{ sizeof(ss) };
if (evutil_parse_sockaddr_port(tr_strbuf<char, TR_ADDRSTRLEN>{ sockaddr_sv }, reinterpret_cast<sockaddr*>(&ss), &sslen) !=
0)
{
return {};
}
return from_sockaddr(reinterpret_cast<struct sockaddr const*>(&ss));
}
std::optional<tr_socket_address> tr_socket_address::from_sockaddr(struct sockaddr const* from)
{
if (from == nullptr)

View File

@ -12,7 +12,6 @@
#include <array>
#include <cstddef> // size_t
#include <cstdint> // uint16_t, uint32_t, uint8_t
#include <functional>
#include <optional>
#include <string>
#include <string_view>
@ -146,9 +145,10 @@ private:
enum tr_address_type : uint8_t
{
TR_AF_INET,
TR_AF_INET = 0,
TR_AF_INET6,
NUM_TR_AF_INET_TYPES
NUM_TR_AF_INET_TYPES,
TR_AF_UNSPEC = NUM_TR_AF_INET_TYPES
};
std::string_view tr_ip_protocol_to_sv(tr_address_type type);
@ -161,11 +161,11 @@ struct tr_address
[[nodiscard]] static std::pair<tr_address, std::byte const*> from_compact_ipv4(std::byte const* compact) noexcept;
[[nodiscard]] static std::pair<tr_address, std::byte const*> from_compact_ipv6(std::byte const* compact) noexcept;
// write the text form of the address, e.g. inet_ntop()
// --- write the text form of the address, e.g. inet_ntop()
std::string_view display_name(char* out, size_t outlen) const;
[[nodiscard]] std::string display_name() const;
///
// ---
[[nodiscard]] constexpr auto is_ipv4() const noexcept
{
@ -177,7 +177,7 @@ struct tr_address
return type == TR_AF_INET6;
}
/// bt protocol compact form
// --- bt protocol compact form
// compact addr only -- used e.g. as `yourip` value in extension protocol handshake
@ -208,7 +208,11 @@ struct tr_address
}
}
// comparisons
// ---
[[nodiscard]] std::optional<unsigned> to_interface_index() const noexcept;
// --- comparisons
[[nodiscard]] int compare(tr_address const& that) const noexcept;
@ -232,10 +236,20 @@ struct tr_address
return this->compare(that) > 0;
}
//
// ---
[[nodiscard]] bool is_global_unicast_address() const noexcept;
[[nodiscard]] constexpr bool is_ipv4_mapped_address() const noexcept
{
return is_ipv6() && IN6_IS_ADDR_V4MAPPED(&addr.addr6);
}
[[nodiscard]] constexpr bool is_ipv6_link_local_address() const noexcept
{
return is_ipv6() && IN6_IS_ADDR_LINKLOCAL(&addr.addr6);
}
tr_address_type type = NUM_TR_AF_INET_TYPES;
union
{
@ -374,6 +388,7 @@ struct tr_socket_address
// --- sockaddr helpers
[[nodiscard]] static std::optional<tr_socket_address> from_string(std::string_view sockaddr_sv);
[[nodiscard]] static std::optional<tr_socket_address> from_sockaddr(sockaddr const*);
[[nodiscard]] static std::pair<sockaddr_storage, socklen_t> to_sockaddr(tr_address const& addr, tr_port port) noexcept;

View File

@ -167,7 +167,7 @@ std::optional<tr_sys_file_t> tr_open_files::get(
if (!tr_sys_dir_create(dir, TR_SYS_DIR_CREATE_PARENTS, 0777, &error))
{
tr_logAddError(fmt::format(
_("Couldn't create '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't create '{path}': {error} ({error_code})")),
fmt::arg("path", dir),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -189,7 +189,7 @@ std::optional<tr_sys_file_t> tr_open_files::get(
if (!is_open(fd))
{
tr_logAddError(fmt::format(
_("Couldn't open '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't open '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -217,7 +217,7 @@ std::optional<tr_sys_file_t> tr_open_files::get(
if (!success)
{
tr_logAddError(fmt::format(
_("Couldn't preallocate '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't preallocate '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -236,7 +236,7 @@ std::optional<tr_sys_file_t> tr_open_files::get(
if (resize_needed && !tr_sys_file_truncate(fd, file_size, &error))
{
tr_logAddWarn(fmt::format(
_("Couldn't truncate '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't truncate '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));

View File

@ -70,6 +70,13 @@ size_t get_desired_output_buffer_size(tr_peerIo const* io, uint64_t now)
auto const current_speed = io->get_piece_speed(now, TR_UP);
return std::max(Floor, current_speed.base_quantity() * PeriodSecs);
}
void log_peer_io_bandwidth(tr_peerIo const& peer_io, tr_bandwidth* const parent)
{
tr_logAddTraceIo(
&peer_io,
fmt::format("bandwidth is {}; its parent is {}", fmt::ptr(&peer_io.bandwidth()), fmt::ptr(parent)));
}
} // namespace
// ---
@ -100,7 +107,6 @@ std::shared_ptr<tr_peerIo> tr_peerIo::create(
auto io = std::make_shared<tr_peerIo>(session, info_hash, is_incoming, is_seed, parent);
io->bandwidth().set_peer(io);
tr_logAddTraceIo(io, fmt::format("bandwidth is {}; its parent is {}", fmt::ptr(&io->bandwidth()), fmt::ptr(parent)));
return io;
}
@ -110,6 +116,7 @@ std::shared_ptr<tr_peerIo> tr_peerIo::new_incoming(tr_session* session, tr_bandw
auto peer_io = tr_peerIo::create(session, parent, nullptr, true, false);
peer_io->set_socket(std::move(socket));
log_peer_io_bandwidth(*peer_io, parent);
return peer_io;
}
@ -167,12 +174,14 @@ std::shared_ptr<tr_peerIo> tr_peerIo::new_outgoing(
if (func.at(preferred)())
{
log_peer_io_bandwidth(*peer_io, parent);
return peer_io;
}
for (preferred_key_t i = 0U; i < TR_NUM_PREFERRED_TRANSPORT; ++i)
{
if (i != preferred && func.at(i)())
{
log_peer_io_bandwidth(*peer_io, parent);
return peer_io;
}
}
@ -670,7 +679,7 @@ void tr_peerIo::on_utp_state_change(int state)
}
else
{
tr_logAddErrorIo(this, fmt::format(_("Unknown state: {state}"), fmt::arg("state", state)));
tr_logAddErrorIo(this, fmt::format(fmt::runtime(_("Unknown state: {state}")), fmt::arg("state", state)));
}
}

View File

@ -11,15 +11,16 @@
#include <cstddef> // std::byte
#include <cstdint>
#include <ctime> // time_t
#include <functional>
#include <iterator> // std::back_inserter
#include <memory>
#include <optional>
#include <tuple> // std::tie
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#include <small/map.hpp>
#include <small/vector.hpp>
#include <fmt/core.h>
@ -270,7 +271,9 @@ constexpr struct
return compare(a, b) < 0;
}
[[nodiscard]] constexpr bool operator()(tr_peer_info const* a, tr_peer_info const* b) const noexcept
template<typename T>
[[nodiscard]] constexpr std::enable_if_t<std::is_same_v<std::decay_t<decltype(*std::declval<T>())>, tr_peer_info>, bool>
operator()(T const& a, T const& b) const noexcept
{
return compare(*a, *b) < 0;
}
@ -283,7 +286,7 @@ class tr_swarm
{
public:
using Peers = std::vector<tr_peerMsgs*>;
using Pool = std::unordered_map<tr_socket_address, tr_peer_info>;
using Pool = small::map<tr_socket_address, std::shared_ptr<tr_peer_info>>;
class WishlistMediator final : public Wishlist::Mediator
{
@ -374,24 +377,6 @@ public:
}
}
void remove_inactive_peer_info() noexcept
{
auto const now = tr_time();
for (auto iter = std::begin(connectable_pool), end = std::end(connectable_pool); iter != end;)
{
auto& [socket_address, peer_info] = *iter;
if (peer_info.is_inactive(now))
{
--stats.known_peer_from_count[peer_info.from_first()];
iter = connectable_pool.erase(iter);
}
else
{
++iter;
}
}
}
[[nodiscard]] uint16_t count_active_webseeds(uint64_t now) const noexcept
{
if (!tor->is_running() || tor->is_done())
@ -416,10 +401,8 @@ public:
peer_disconnect.emit(tor, peer->has());
auto* const peer_info = peer->peer_info;
auto const socket_address = peer->socket_address();
[[maybe_unused]] auto const is_incoming = peer->is_incoming_connection();
TR_ASSERT(peer_info != nullptr);
auto const& peer_info = peer->peer_info;
TR_ASSERT(peer_info);
--stats.peer_count;
--stats.peer_from_count[peer_info->from_first()];
@ -431,17 +414,6 @@ public:
}
delete peer;
if (std::empty(peer_info->listen_port())) // is not connectable
{
TR_ASSERT(is_incoming);
[[maybe_unused]] auto const count = incoming_pool.erase(socket_address);
TR_ASSERT(count != 0U);
}
else
{
graveyard_pool.erase(peer_info->listen_socket_address());
}
}
void remove_all_peers()
@ -475,37 +447,41 @@ public:
pool_is_all_seeds_ = std::all_of(
std::begin(connectable_pool),
std::end(connectable_pool),
[](auto const& key_val) { return key_val.second.is_seed(); });
[](auto const& key_val) { return key_val.second->is_seed(); });
}
return *pool_is_all_seeds_;
}
[[nodiscard]] tr_peer_info* get_existing_peer_info(tr_socket_address const& socket_address) noexcept
[[nodiscard]] std::shared_ptr<tr_peer_info> get_existing_peer_info(tr_socket_address const& socket_address) const noexcept
{
auto&& it = connectable_pool.find(socket_address);
return it != connectable_pool.end() ? &it->second : nullptr;
if (auto it = connectable_pool.find(socket_address); it != std::end(connectable_pool))
{
return it->second;
}
return {};
}
tr_peer_info& ensure_info_exists(
std::shared_ptr<tr_peer_info> ensure_info_exists(
tr_socket_address const& socket_address,
uint8_t const flags,
tr_peer_from const from,
bool is_connectable)
tr_peer_from const from)
{
TR_ASSERT(socket_address.is_valid());
TR_ASSERT(from < TR_PEER_FROM__MAX);
auto&& [it, is_new] = is_connectable ? connectable_pool.try_emplace(socket_address, socket_address, flags, from) :
incoming_pool.try_emplace(socket_address, socket_address.address(), flags, from);
auto& peer_info = it->second;
if (!is_new)
auto peer_info = get_existing_peer_info(socket_address);
if (peer_info)
{
peer_info.found_at(from);
peer_info.set_pex_flags(flags);
peer_info->found_at(from);
peer_info->set_pex_flags(flags);
}
else if (is_connectable)
else
{
peer_info = connectable_pool
.try_emplace(socket_address, std::make_shared<tr_peer_info>(socket_address, flags, from))
.first->second;
++stats.known_peer_from_count[from];
}
@ -568,19 +544,13 @@ public:
break;
case tr_peer_event::Type::ClientGotPort:
if (std::empty(event.port))
// We have 2 cases:
// 1. We don't know the listening port of this peer (i.e. incoming connection and first time ClientGotPort)
// 2. We got a new listening port from a known peer
if (auto const& info = msgs->peer_info;
!std::empty(event.port) && info && (std::empty(info->listen_port()) || info->listen_port() != event.port))
{
// Do nothing
}
// If we don't know the listening port of this peer (i.e. incoming connection and first time ClientGotPort)
else if (auto const& info = *msgs->peer_info; std::empty(info.listen_port()))
{
s->on_got_port(msgs, event, false);
}
// If we got a new listening port from a known connectable peer
else if (info.listen_port() != event.port)
{
s->on_got_port(msgs, event, true);
s->on_got_port(msgs, event);
}
break;
@ -622,9 +592,6 @@ public:
std::unique_ptr<Wishlist> wishlist;
// tr_peerMsgs hold pointers to the items in these containers,
// therefore references to elements within cannot invalidate
Pool incoming_pool;
Pool connectable_pool;
tr_peerMsgs* optimistic = nullptr; /* the optimistic peer, or nullptr if none */
@ -672,9 +639,10 @@ private:
is_running = false;
remove_all_peers();
wishlist.reset();
for (auto& [sockaddr, peer_info] : connectable_pool)
{
peer_info.destroy_handshake();
peer_info->destroy_handshake();
}
}
@ -717,9 +685,9 @@ private:
{
auto const lock = unique_lock();
for (auto& [socket_address, atom] : connectable_pool)
for (auto const& [socket_address, peer_info] : connectable_pool)
{
mark_peer_as_seed(atom);
mark_peer_as_seed(*peer_info);
}
mark_all_seeds_flag_dirty();
@ -862,89 +830,75 @@ private:
tor->session->add_downloaded(sent_length);
}
void on_got_port(tr_peerMsgs* const msgs, tr_peer_event const& event, bool was_connectable)
void on_got_port(tr_peerMsgs* const msgs, tr_peer_event const& event)
{
auto& info_this = *msgs->peer_info;
TR_ASSERT(info_this.is_connected());
TR_ASSERT(was_connectable != std::empty(info_this.listen_port()));
auto info_this = msgs->peer_info;
TR_ASSERT(info_this->is_connected());
TR_ASSERT(info_this->listen_port() != event.port);
// If we already know about this peer, merge the info objects without invalidating references
if (auto it_that = connectable_pool.find({ info_this.listen_address(), event.port });
// we already know about this peer
if (auto it_that = connectable_pool.find({ info_this->listen_address(), event.port });
it_that != std::end(connectable_pool))
{
auto& info_that = it_that->second;
TR_ASSERT(it_that->first == info_that.listen_socket_address());
TR_ASSERT(it_that->first.address() == info_this.listen_address());
TR_ASSERT(it_that->first.port() != info_this.listen_port());
auto const info_that = it_that->second;
TR_ASSERT(it_that->first == info_that->listen_socket_address());
TR_ASSERT(it_that->first.address() == info_this->listen_address());
TR_ASSERT(it_that->first.port() != info_this->listen_port());
// If there is an existing connection to this peer, keep the better one
if (info_that.is_connected() && on_got_port_duplicate_connection(msgs, it_that, was_connectable))
// if there is an existing connection to this peer, keep the better one
if (info_that->is_connected() && on_got_port_duplicate_connection(msgs, info_that))
{
return;
goto EXIT; // NOLINT cppcoreguidelines-avoid-goto
}
info_this.merge(info_that);
auto from = info_that.from_first();
stats.known_peer_from_count[from] -= connectable_pool.erase(info_that.listen_socket_address());
// merge the peer info objects
info_this->merge(*info_that);
// info_that will be replaced by info_this later, so decrement stat
--stats.known_peer_from_count[info_that->from_first()];
}
else if (!was_connectable)
// we are going to insert a brand-new peer info object to the pool
else if (std::empty(info_this->listen_port()))
{
info_this.set_connectable();
info_this->set_connectable();
}
auto nh = was_connectable ? connectable_pool.extract(info_this.listen_socket_address()) :
incoming_pool.extract(msgs->socket_address());
TR_ASSERT(!std::empty(nh));
if (was_connectable)
{
TR_ASSERT(nh.key() == nh.mapped().listen_socket_address());
}
else
{
++stats.known_peer_from_count[nh.mapped().from_first()];
TR_ASSERT(nh.key().address() == nh.mapped().listen_address());
}
nh.key().port_ = event.port;
[[maybe_unused]] auto const inserted = connectable_pool.insert(std::move(nh)).inserted;
TR_ASSERT(inserted);
info_this.set_listen_port(event.port);
// erase the old peer info entry
stats.known_peer_from_count[info_this->from_first()] -= connectable_pool.erase(info_this->listen_socket_address());
// set new listen port
info_this->set_listen_port(event.port);
// insert or replace the peer info ptr at the target location
++stats.known_peer_from_count[info_this->from_first()];
connectable_pool.insert_or_assign(info_this->listen_socket_address(), std::move(info_this));
EXIT:
mark_all_seeds_flag_dirty();
}
bool on_got_port_duplicate_connection(tr_peerMsgs* const msgs, Pool::iterator& it_that, bool was_connectable)
bool on_got_port_duplicate_connection(tr_peerMsgs* const msgs, std::shared_ptr<tr_peer_info> info_that)
{
auto& info_this = *msgs->peer_info;
auto& info_that = it_that->second;
auto const info_this = msgs->peer_info;
TR_ASSERT(info_that.is_connected());
TR_ASSERT(info_that->is_connected());
if (CompareAtomsByUsefulness(info_this, info_that))
if (CompareAtomsByUsefulness(*info_this, *info_that))
{
auto it = std::find_if(
auto const it = std::find_if(
std::begin(peers),
std::end(peers),
[&info_that](tr_peerMsgs const* const peer) { return peer->peer_info == &info_that; });
[&info_that](tr_peerMsgs const* const peer) { return peer->peer_info == info_that; });
TR_ASSERT(it != std::end(peers));
(*it)->do_purge = true;
--stats.known_peer_from_count[info_that.from_first()];
// Note that it_that is invalid after this point
graveyard_pool.insert(connectable_pool.extract(it_that));
return false;
}
info_that.merge(info_this);
info_that->merge(*info_this);
msgs->do_purge = true;
stats.known_peer_from_count[info_this->from_first()] -= connectable_pool.erase(info_this->listen_socket_address());
if (was_connectable)
{
--stats.known_peer_from_count[info_this.from_first()];
graveyard_pool.insert(connectable_pool.extract(info_this.listen_socket_address()));
}
mark_all_seeds_flag_dirty();
return true;
}
@ -958,10 +912,6 @@ private:
std::array<libtransmission::ObserverTag, 8> const tags_;
// tr_peerMsgs hold pointers to the items in these containers,
// therefore references to elements within cannot invalidate
Pool graveyard_pool;
mutable std::optional<bool> pool_is_all_seeds_;
bool is_endgame_ = false;
@ -1075,6 +1025,7 @@ struct tr_peerMgr
{
private:
static auto constexpr BandwidthTimerPeriod = 500ms;
static auto constexpr PeerInfoPeriod = 1min;
static auto constexpr RechokePeriod = 10s;
static auto constexpr RefillUpkeepPeriod = 10s;
@ -1109,11 +1060,13 @@ public:
, blocklists_{ blocklist }
, handshake_mediator_{ *session, timer_maker, torrents }
, bandwidth_timer_{ timer_maker.create([this]() { bandwidth_pulse(); }) }
, peer_info_timer_{ timer_maker.create([this]() { peer_info_pulse(); }) }
, rechoke_timer_{ timer_maker.create([this]() { rechoke_pulse_marshall(); }) }
, refill_upkeep_timer_{ timer_maker.create([this]() { refill_upkeep(); }) }
, blocklists_tag_{ blocklist.observe_changes([this]() { on_blocklists_changed(); }) }
{
bandwidth_timer_->start_repeating(BandwidthTimerPeriod);
peer_info_timer_->start_repeating(PeerInfoPeriod);
rechoke_timer_->start_repeating(RechokePeriod);
refill_upkeep_timer_->start_repeating(RefillUpkeepPeriod);
}
@ -1155,6 +1108,7 @@ public:
private:
void bandwidth_pulse();
void make_new_peer_connections();
void peer_info_pulse();
void rechoke_pulse() const;
void reconnect_pulse();
void refill_upkeep() const;
@ -1171,12 +1125,14 @@ private:
since the blocklist has changed, erase that cached value */
for (auto* const tor : torrents_)
{
for (auto& pool : { std::ref(tor->swarm->connectable_pool), std::ref(tor->swarm->incoming_pool) })
for (auto const& [socket_address, peer_info] : tor->swarm->connectable_pool)
{
for (auto& [socket_address, atom] : pool.get())
{
atom.set_blocklisted_dirty();
}
peer_info->set_blocklisted_dirty();
}
for (auto* const peer : tor->swarm->peers)
{
peer->peer_info->set_blocklisted_dirty();
}
}
}
@ -1184,6 +1140,7 @@ private:
OutboundCandidates outbound_candidates_;
std::unique_ptr<libtransmission::Timer> const bandwidth_timer_;
std::unique_ptr<libtransmission::Timer> const peer_info_timer_;
std::unique_ptr<libtransmission::Timer> const rechoke_timer_;
std::unique_ptr<libtransmission::Timer> const refill_upkeep_timer_;
@ -1283,7 +1240,6 @@ void tr_peerMgr::refill_upkeep() const
for (auto* const tor : torrents_)
{
tor->swarm->cancel_old_requests();
tor->swarm->remove_inactive_peer_info();
}
}
@ -1291,22 +1247,26 @@ namespace
{
namespace handshake_helpers
{
void create_bit_torrent_peer(tr_torrent& tor, std::shared_ptr<tr_peerIo> io, tr_peer_info* peer_info, tr_interned_string client)
void create_bit_torrent_peer(
tr_torrent& tor,
std::shared_ptr<tr_peerIo> io,
std::shared_ptr<tr_peer_info> peer_info,
tr_interned_string client)
{
TR_ASSERT(peer_info != nullptr);
TR_ASSERT(peer_info);
TR_ASSERT(tor.swarm != nullptr);
tr_swarm* swarm = tor.swarm;
auto* peer = tr_peerMsgs::create(tor, peer_info, std::move(io), client, &tr_swarm::peer_callback_bt, swarm);
swarm->peers.push_back(peer);
auto* const
msgs = tr_peerMsgs::create(tor, std::move(peer_info), std::move(io), client, &tr_swarm::peer_callback_bt, swarm);
swarm->peers.push_back(msgs);
++swarm->stats.peer_count;
++swarm->stats.peer_from_count[peer_info->from_first()];
++swarm->stats.peer_from_count[msgs->peer_info->from_first()];
TR_ASSERT(swarm->stats.peer_count == swarm->peerCount());
TR_ASSERT(swarm->stats.peer_from_count[peer_info->from_first()] <= swarm->stats.peer_count);
TR_ASSERT(swarm->stats.peer_from_count[msgs->peer_info->from_first()] <= swarm->stats.peer_count);
}
/* FIXME: this is kind of a mess. */
@ -1317,20 +1277,20 @@ void create_bit_torrent_peer(tr_torrent& tor, std::shared_ptr<tr_peerIo> io, tr_
TR_ASSERT(result.io != nullptr);
auto const& socket_address = result.io->socket_address();
auto* const swarm = manager->get_existing_swarm(result.io->torrent_hash());
auto* info = swarm != nullptr ? swarm->get_existing_peer_info(socket_address) : nullptr;
auto info = swarm != nullptr ? swarm->get_existing_peer_info(socket_address) : std::shared_ptr<tr_peer_info>{};
if (result.io->is_incoming())
{
manager->incoming_handshakes.erase(socket_address);
}
else if (info != nullptr)
else if (info)
{
info->destroy_handshake();
}
if (!result.is_connected || swarm == nullptr || !swarm->is_running)
{
if (info != nullptr && !info->is_connected())
if (info && !info->is_connected())
{
info->on_connection_failed();
@ -1351,10 +1311,10 @@ void create_bit_torrent_peer(tr_torrent& tor, std::shared_ptr<tr_peerIo> io, tr_
if (result.io->is_incoming())
{
info = &swarm->ensure_info_exists(socket_address, 0U, TR_PEER_FROM_INCOMING, false);
info = std::make_shared<tr_peer_info>(socket_address.address(), 0U, TR_PEER_FROM_INCOMING);
}
if (info == nullptr)
if (!info)
{
return false;
}
@ -1395,7 +1355,7 @@ void create_bit_torrent_peer(tr_torrent& tor, std::shared_ptr<tr_peerIo> io, tr_
}
result.io->set_bandwidth(&swarm->tor->bandwidth());
create_bit_torrent_peer(*swarm->tor, result.io, info, client);
create_bit_torrent_peer(*swarm->tor, result.io, std::move(info), client);
return true;
}
@ -1444,7 +1404,7 @@ size_t tr_peerMgrAddPex(tr_torrent* tor, tr_peer_from from, tr_pex const* pex, s
{
// we store this peer since it is supposedly connectable (socket address should be the peer's listening address)
// don't care about non-connectable peers that we are not connected to
s->ensure_info_exists(pex->socket_address, pex->flags, from, true);
s->ensure_info_exists(pex->socket_address, pex->flags, from);
++n_used;
}
}
@ -1556,7 +1516,7 @@ std::vector<tr_pex> tr_peerMgrGetPeers(tr_torrent const* tor, uint8_t address_ty
{
if (peer->socket_address().address().type == address_type)
{
infos.emplace_back(peer->peer_info);
infos.emplace_back(peer->peer_info.get());
}
}
}
@ -1566,10 +1526,10 @@ std::vector<tr_pex> tr_peerMgrGetPeers(tr_torrent const* tor, uint8_t address_ty
infos.reserve(std::size(pool));
for (auto const& [socket_address, peer_info] : pool)
{
TR_ASSERT(socket_address == peer_info.listen_socket_address());
if (socket_address.address().type == address_type && is_peer_interesting(tor, peer_info))
TR_ASSERT(socket_address == peer_info->listen_socket_address());
if (socket_address.address().type == address_type && is_peer_interesting(tor, *peer_info))
{
infos.emplace_back(&peer_info);
infos.emplace_back(peer_info.get());
}
}
}
@ -2151,7 +2111,7 @@ auto constexpr MaxUploadIdleSecs = time_t{ 60 * 5 };
}
auto const* tor = s->tor;
auto const* const info = peer->peer_info;
auto const& info = peer->peer_info;
/* disconnect if we're both seeds and enough time has passed for PEX */
if (tor->is_done() && peer->is_seed())
@ -2195,7 +2155,7 @@ void close_peer(tr_peerMsgs* peer)
constexpr struct
{
[[nodiscard]] constexpr static int compare(tr_peerMsgs const* a, tr_peerMsgs const* b) // <=>
[[nodiscard]] static int compare(tr_peerMsgs const* a, tr_peerMsgs const* b) // <=>
{
if (a->do_purge != b->do_purge)
{
@ -2205,7 +2165,7 @@ constexpr struct
return -a->peer_info->compare_by_piece_data_time(*b->peer_info);
}
[[nodiscard]] constexpr bool operator()(tr_peerMsgs const* a, tr_peerMsgs const* b) const // less than
[[nodiscard]] bool operator()(tr_peerMsgs const* a, tr_peerMsgs const* b) const // less than
{
return compare(a, b) < 0;
}
@ -2331,6 +2291,93 @@ void tr_peerMgr::reconnect_pulse()
make_new_peer_connections();
}
// --- Peer Pool Size
namespace
{
namespace peer_info_pulse_helpers
{
auto get_max_peer_info_count(tr_torrent const& tor)
{
return tor.is_done() ? tor.peer_limit() : tor.peer_limit() * 3U;
}
struct ComparePeerInfo
{
[[nodiscard]] int compare(tr_peer_info const& a, tr_peer_info const& b) const noexcept
{
auto const is_a_inactive = a.is_inactive(now_);
auto const is_b_inactive = b.is_inactive(now_);
if (is_a_inactive != is_b_inactive)
{
return is_a_inactive ? 1 : -1;
}
return CompareAtomsByUsefulness.compare(a, b);
}
template<typename T>
[[nodiscard]] std::enable_if_t<std::is_same_v<std::decay_t<decltype(*std::declval<T>())>, tr_peer_info>, bool> operator()(
T const& a,
T const& b) const noexcept
{
return compare(*a, *b) < 0;
}
time_t const now_ = tr_time();
};
} // namespace peer_info_pulse_helpers
} // namespace
void tr_peerMgr::peer_info_pulse()
{
using namespace peer_info_pulse_helpers;
auto const lock = unique_lock();
for (auto const* tor : torrents_)
{
auto& pool = tor->swarm->connectable_pool;
auto const max = get_max_peer_info_count(*tor);
auto const pool_size = std::size(pool);
if (pool_size <= max)
{
continue;
}
auto infos = std::vector<std::shared_ptr<tr_peer_info>>{};
infos.reserve(pool_size);
std::transform(
std::begin(pool),
std::end(pool),
std::back_inserter(infos),
[](auto const& keyval) { return keyval.second; });
pool.clear();
// Keep all peer info objects before test_begin unconditionally
auto const test_begin = std::partition(
std::begin(infos),
std::end(infos),
[](auto const& info) { return info->is_in_use(); });
auto const iter_max = std::begin(infos) + max;
if (iter_max > test_begin)
{
std::partial_sort(test_begin, iter_max, std::end(infos), ComparePeerInfo{});
}
infos.erase(std::max(test_begin, iter_max), std::end(infos));
pool.reserve(std::size(infos));
for (auto& info : infos)
{
pool.try_emplace(info->listen_socket_address(), std::move(info));
}
tr_logAddTraceSwarm(
tor->swarm,
fmt::format("max peer info count is {}... pruned from {} to {}", max, pool_size, std::size(pool)));
}
}
// --- Bandwidth Allocation
namespace
@ -2419,22 +2466,6 @@ namespace connect_helpers
return true;
}
struct peer_candidate
{
peer_candidate() = default;
peer_candidate(uint64_t score_in, tr_torrent const* const tor_in, tr_peer_info const* const peer_info_in)
: score{ score_in }
, tor{ tor_in }
, peer_info{ peer_info_in }
{
}
uint64_t score;
tr_torrent const* tor;
tr_peer_info const* peer_info;
};
[[nodiscard]] constexpr uint64_t addValToKey(uint64_t value, unsigned int width, uint64_t addme)
{
value <<= width;
@ -2506,6 +2537,22 @@ struct peer_candidate
void get_peer_candidates(size_t global_peer_limit, tr_torrents& torrents, tr_peerMgr::OutboundCandidates& setme)
{
struct peer_candidate
{
peer_candidate() = default;
peer_candidate(uint64_t score_in, tr_torrent const* const tor_in, tr_peer_info const* const peer_info_in)
: score{ score_in }
, tor{ tor_in }
, peer_info{ peer_info_in }
{
}
uint64_t score;
tr_torrent const* tor;
tr_peer_info const* peer_info;
};
setme.clear();
auto const now = tr_time();
@ -2551,24 +2598,24 @@ void get_peer_candidates(size_t global_peer_limit, tr_torrents& torrents, tr_pee
continue;
}
for (auto const& [socket_address, atom] : swarm->connectable_pool)
for (auto const& [socket_address, peer_info] : swarm->connectable_pool)
{
if (is_peer_candidate(tor, atom, now))
if (is_peer_candidate(tor, *peer_info, now))
{
candidates.emplace_back(getPeerCandidateScore(tor, atom, salter()), tor, &atom);
candidates.emplace_back(getPeerCandidateScore(tor, *peer_info, salter()), tor, peer_info.get());
}
}
}
// only keep the best `max` candidates
if (auto const max = tr_peerMgr::OutboundCandidates::requested_inline_size; max < std::size(candidates))
if (static auto constexpr Max = tr_peerMgr::OutboundCandidates::requested_inline_size; Max < std::size(candidates))
{
std::partial_sort(
std::begin(candidates),
std::begin(candidates) + max,
std::begin(candidates) + Max,
std::end(candidates),
[](auto const& a, auto const& b) { return a.score < b.score; });
candidates.resize(max);
candidates.resize(Max);
}
// put the best candidates at the end of the list
@ -2638,14 +2685,13 @@ void tr_peerMgr::make_new_peer_connections()
// initiate connections to the last N candidates
auto const n_this_pass = std::min(std::size(candidates), MaxConnectionsPerPulse);
auto const it_end = std::crbegin(candidates) + n_this_pass;
for (auto it = std::crbegin(candidates); it != it_end; ++it)
for (auto it = std::crbegin(candidates), end = std::crbegin(candidates) + n_this_pass; it != end; ++it)
{
auto const& [tor_id, sock_addr] = *it;
if (auto* const tor = torrents_.get(tor_id); tor != nullptr)
{
if (auto* const peer_info = tor->swarm->get_existing_peer_info(sock_addr); peer_info != nullptr)
if (auto const& peer_info = tor->swarm->get_existing_peer_info(sock_addr))
{
initiate_connection(this, tor->swarm, *peer_info);
}
@ -2660,7 +2706,7 @@ void HandshakeMediator::set_utp_failed(tr_sha1_digest_t const& info_hash, tr_soc
{
if (auto* const tor = torrents_.get(info_hash); tor != nullptr)
{
if (auto* const peer_info = tor->swarm->get_existing_peer_info(socket_address); peer_info != nullptr)
if (auto const& peer_info = tor->swarm->get_existing_peer_info(socket_address))
{
peer_info->set_utp_supported(false);
}

View File

@ -431,7 +431,7 @@ private:
// the minimum we'll wait before attempting to reconnect to a peer
static auto constexpr MinimumReconnectIntervalSecs = time_t{ 5U };
static auto constexpr InactiveThresSecs = time_t{ 24 * 60 * 60 };
static auto constexpr InactiveThresSecs = time_t{ 60 * 60 };
static auto inline n_known_connectable = size_t{};

View File

@ -305,12 +305,12 @@ class tr_peerMsgsImpl final : public tr_peerMsgs
public:
tr_peerMsgsImpl(
tr_torrent& torrent_in,
tr_peer_info* const peer_info_in,
std::shared_ptr<tr_peer_info> peer_info_in,
std::shared_ptr<tr_peerIo> io_in,
tr_interned_string client,
tr_peer_callback_bt callback,
void* callback_data)
: tr_peerMsgs{ torrent_in, peer_info_in, client, io_in->is_encrypted(), io_in->is_incoming(), io_in->is_utp() }
: tr_peerMsgs{ torrent_in, std::move(peer_info_in), client, io_in->is_encrypted(), io_in->is_incoming(), io_in->is_utp() }
, tor_{ torrent_in }
, io_{ std::move(io_in) }
, have_{ torrent_in.piece_count() }
@ -2054,13 +2054,13 @@ size_t tr_peerMsgsImpl::max_available_reqs() const
tr_peerMsgs::tr_peerMsgs(
tr_torrent const& tor,
tr_peer_info* peer_info_in,
std::shared_ptr<tr_peer_info> peer_info_in,
tr_interned_string user_agent,
bool connection_is_encrypted,
bool connection_is_incoming,
bool connection_is_utp)
: tr_peer{ tor }
, peer_info{ peer_info_in }
, peer_info{ std::move(peer_info_in) }
, user_agent_{ user_agent }
, connection_is_encrypted_{ connection_is_encrypted }
, connection_is_incoming_{ connection_is_incoming }
@ -2079,11 +2079,11 @@ tr_peerMsgs::~tr_peerMsgs()
tr_peerMsgs* tr_peerMsgs::create(
tr_torrent& torrent,
tr_peer_info* const peer_info,
std::shared_ptr<tr_peer_info> peer_info,
std::shared_ptr<tr_peerIo> io,
tr_interned_string user_agent,
tr_peer_callback_bt callback,
void* callback_data)
{
return new tr_peerMsgsImpl{ torrent, peer_info, std::move(io), user_agent, callback, callback_data };
return new tr_peerMsgsImpl{ torrent, std::move(peer_info), std::move(io), user_agent, callback, callback_data };
}

View File

@ -37,7 +37,7 @@ class tr_peerMsgs : public tr_peer
public:
tr_peerMsgs(
tr_torrent const& tor,
tr_peer_info* peer_info_in,
std::shared_ptr<tr_peer_info> peer_info_in,
tr_interned_string user_agent,
bool connection_is_encrypted,
bool connection_is_incoming,
@ -108,7 +108,7 @@ public:
static tr_peerMsgs* create(
tr_torrent& torrent,
tr_peer_info* peer_info,
std::shared_ptr<tr_peer_info> peer_info,
std::shared_ptr<tr_peerIo> io,
tr_interned_string user_agent,
tr_peer_callback_bt callback,
@ -146,8 +146,7 @@ protected:
}
public:
// TODO(tearfur): change this to reference
tr_peer_info* const peer_info;
std::shared_ptr<tr_peer_info> const peer_info;
private:
static inline auto n_peers = std::atomic<size_t>{};

View File

@ -87,7 +87,8 @@ tr_natpmp::PulseResult tr_natpmp::pulse(tr_port local_port, bool is_enabled)
{
auto str = std::array<char, 128>{};
evutil_inet_ntop(AF_INET, &response.pnu.publicaddress.addr, std::data(str), std::size(str));
tr_logAddInfo(fmt::format(_("Found public address '{address}'"), fmt::arg("address", std::data(str))));
tr_logAddInfo(
fmt::format(fmt::runtime(_("Found public address '{address}'")), fmt::arg("address", std::data(str))));
state_ = State::Idle;
}
else if (val != NATPMP_TRYAGAIN)
@ -124,7 +125,8 @@ tr_natpmp::PulseResult tr_natpmp::pulse(tr_port local_port, bool is_enabled)
{
auto const unmapped_port = tr_port::from_host(resp.pnu.newportmapping.privateport);
tr_logAddInfo(fmt::format(_("Port {port} is no longer forwarded"), fmt::arg("port", unmapped_port.host())));
tr_logAddInfo(
fmt::format(fmt::runtime(_("Port {port} is no longer forwarded")), fmt::arg("port", unmapped_port.host())));
if (local_port_ == unmapped_port)
{
@ -140,16 +142,10 @@ tr_natpmp::PulseResult tr_natpmp::pulse(tr_port local_port, bool is_enabled)
}
}
if (state_ == State::Idle)
if ((state_ == State::Idle || state_ == State::Err) &&
(is_mapped_ ? tr_time() >= renew_time_ : is_enabled && has_discovered_))
{
if (is_enabled && !is_mapped_ && has_discovered_)
{
state_ = State::SendMap;
}
else if (is_mapped_ && tr_time() >= renew_time_)
{
state_ = State::SendMap;
}
state_ = State::SendMap;
}
if (state_ == State::SendMap && canSendCommand())
@ -178,7 +174,8 @@ tr_natpmp::PulseResult tr_natpmp::pulse(tr_port local_port, bool is_enabled)
renew_time_ = tr_time() + (resp.pnu.newportmapping.lifetime / 2);
local_port_ = tr_port::from_host(resp.pnu.newportmapping.privateport);
advertised_port_ = tr_port::from_host(resp.pnu.newportmapping.mappedpublicport);
tr_logAddInfo(fmt::format(_("Port {port} forwarded successfully"), fmt::arg("port", local_port_.host())));
tr_logAddInfo(
fmt::format(fmt::runtime(_("Port {port} forwarded successfully")), fmt::arg("port", local_port_.host())));
}
else if (val != NATPMP_TRYAGAIN)
{

View File

@ -15,13 +15,8 @@
#include <fmt/core.h>
#ifdef SYSTEM_MINIUPNP
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/upnpcommands.h>
#else
#include <miniupnp/miniupnpc.h>
#include <miniupnp/upnpcommands.h>
#endif
#define LIBTRANSMISSION_PORT_FORWARDING_MODULE
@ -34,12 +29,15 @@
#include "libtransmission/tr-macros.h" // TR_ADDRSTRLEN
#include "libtransmission/utils.h" // for _(), tr_strerror()
#ifndef MINIUPNPC_API_VERSION
#error miniupnpc >= 1.7 is required
#endif
namespace
{
enum class UpnpState : uint8_t
{
Idle,
Failed,
WillDiscover, // next action is upnpDiscover()
Discovering, // currently making blocking upnpDiscover() call in a worker thread
WillMap, // next action is UPNP_AddPortMapping()
@ -58,9 +56,7 @@ struct tr_upnp
~tr_upnp()
{
TR_ASSERT(!isMapped);
TR_ASSERT(
state == UpnpState::Idle || state == UpnpState::Failed || state == UpnpState::WillDiscover ||
state == UpnpState::Discovering);
TR_ASSERT(state == UpnpState::Idle || state == UpnpState::WillDiscover || state == UpnpState::Discovering);
FreeUPNPUrls(&urls);
}
@ -108,20 +104,16 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
UPNPDev* ret = nullptr;
auto have_err = bool{};
#if (MINIUPNPC_API_VERSION >= 8) /* adds ipv6 and error args */
// MINIUPNPC_API_VERSION >= 8 (adds ipv6 and error args)
int err = UPNPDISCOVER_SUCCESS;
#if (MINIUPNPC_API_VERSION >= 14) /* adds ttl */
#if (MINIUPNPC_API_VERSION >= 14) // adds ttl
ret = upnpDiscover(msec, bindaddr, nullptr, 0, 0, 2, &err);
#else
ret = upnpDiscover(msec, bindaddr, nullptr, 0, 0, &err);
#endif
have_err = err != UPNPDISCOVER_SUCCESS;
#else
ret = upnpDiscover(msec, bindaddr, nullptr, 0);
have_err = ret == nullptr;
#endif
if (have_err)
{
@ -150,7 +142,7 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
nullptr /*desc*/,
nullptr /*enabled*/,
nullptr /*duration*/);
#elif (MINIUPNPC_API_VERSION >= 8) /* adds desc, enabled and leaseDuration args */
#else // MINIUPNPC_API_VERSION >= 8 (adds desc, enabled and leaseDuration args)
int const err = UPNP_GetSpecificPortMappingEntry(
handle->urls.controlURL,
handle->data.first.servicetype,
@ -161,14 +153,6 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
nullptr /*desc*/,
nullptr /*enabled*/,
nullptr /*duration*/);
#else
int const err = UPNP_GetSpecificPortMappingEntry(
handle->urls.controlURL,
handle->data.first.servicetype,
port_str.c_str(),
proto,
std::data(int_client),
std::data(int_port));
#endif
return err;
@ -187,7 +171,7 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
auto const advertised_port_str = std::to_string(advertised_port.host());
auto const local_port_str = std::to_string(local_port.host());
#if (MINIUPNPC_API_VERSION >= 8)
// MINIUPNPC_API_VERSION >= 8
int const err = UPNP_AddPortMapping(
handle->urls.controlURL,
handle->data.first.servicetype,
@ -198,17 +182,6 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
proto,
nullptr,
nullptr);
#else
int const err = UPNP_AddPortMapping(
handle->urls.controlURL,
handle->data.first.servicetype,
advertised_port_str.c_str(),
local_port_str.c_str(),
handle->lanaddr.c_str(),
desc,
proto,
nullptr);
#endif
if (err != 0)
{
@ -288,17 +261,24 @@ tr_port_forwarding_state tr_upnpPulse(
FreeUPNPUrls(&handle->urls);
auto lanaddr = std::array<char, TR_ADDRSTRLEN>{};
if (UPNP_GetValidIGD(devlist, &handle->urls, &handle->data, std::data(lanaddr), std::size(lanaddr) - 1) ==
UPNP_IGD_VALID_CONNECTED)
if (
#if (MINIUPNPC_API_VERSION >= 18)
UPNP_GetValidIGD(devlist, &handle->urls, &handle->data, std::data(lanaddr), std::size(lanaddr) - 1, nullptr, 0)
#else
UPNP_GetValidIGD(devlist, &handle->urls, &handle->data, std::data(lanaddr), std::size(lanaddr) - 1)
#endif
== UPNP_IGD_VALID_CONNECTED)
{
tr_logAddInfo(fmt::format(_("Found Internet Gateway Device '{url}'"), fmt::arg("url", handle->urls.controlURL)));
tr_logAddInfo(fmt::format(_("Local Address is '{address}'"), fmt::arg("address", lanaddr.data())));
tr_logAddInfo(fmt::format(
fmt::runtime(_("Found Internet Gateway Device '{url}'")),
fmt::arg("url", handle->urls.controlURL)));
tr_logAddInfo(fmt::format(fmt::runtime(_("Local Address is '{address}'")), fmt::arg("address", lanaddr.data())));
handle->state = UpnpState::Idle;
handle->lanaddr = std::data(lanaddr);
}
else
{
handle->state = UpnpState::Failed;
handle->state = UpnpState::WillDiscover;
tr_logAddDebug(fmt::format("UPNP_GetValidIGD failed: {} ({})", tr_strerror(errno), errno));
tr_logAddDebug("If your router supports UPnP, please make sure UPnP is enabled!");
}
@ -306,18 +286,18 @@ tr_port_forwarding_state tr_upnpPulse(
freeUPNPDevlist(devlist);
}
if ((handle->state == UpnpState::Idle) && (handle->isMapped) &&
if (handle->state == UpnpState::Idle && handle->isMapped &&
(!is_enabled || handle->advertised_port != advertised_port || handle->local_port != local_port))
{
handle->state = UpnpState::WillUnmap;
}
if (is_enabled && handle->isMapped && do_port_check &&
((get_specific_port_mapping_entry(handle, "TCP") != UPNPCOMMAND_SUCCESS) ||
(get_specific_port_mapping_entry(handle, "UDP") != UPNPCOMMAND_SUCCESS)))
(get_specific_port_mapping_entry(handle, "TCP") != UPNPCOMMAND_SUCCESS ||
get_specific_port_mapping_entry(handle, "UDP") != UPNPCOMMAND_SUCCESS))
{
tr_logAddInfo(fmt::format(
_("Local port {local_port} is not forwarded to {advertised_port}"),
fmt::runtime(_("Local port {local_port} is not forwarded to {advertised_port}")),
fmt::arg("local_port", handle->local_port.host()),
fmt::arg("advertised_port", handle->advertised_port.host())));
handle->isMapped = false;
@ -329,7 +309,7 @@ tr_port_forwarding_state tr_upnpPulse(
tr_upnpDeletePortMapping(handle, "UDP", handle->advertised_port);
tr_logAddInfo(fmt::format(
_("Stopping port forwarding through '{url}', service '{type}'"),
fmt::runtime(_("Stopping port forwarding through '{url}', service '{type}'")),
fmt::arg("url", handle->urls.controlURL),
fmt::arg("type", handle->data.first.servicetype)));
@ -339,7 +319,7 @@ tr_port_forwarding_state tr_upnpPulse(
handle->local_port = {};
}
if ((handle->state == UpnpState::Idle) && is_enabled && !handle->isMapped)
if (handle->state == UpnpState::Idle && is_enabled && !handle->isMapped)
{
handle->state = UpnpState::WillMap;
}
@ -362,7 +342,7 @@ tr_port_forwarding_state tr_upnpPulse(
}
tr_logAddDebug(fmt::format(
_("Port forwarding through '{url}', service '{type}'. (local address: {address}:{port})"),
fmt::runtime(_("Port forwarding through '{url}', service '{type}'. (local address: {address}:{port})")),
fmt::arg("url", handle->urls.controlURL),
fmt::arg("type", handle->data.first.servicetype),
fmt::arg("address", handle->lanaddr),
@ -371,20 +351,19 @@ tr_port_forwarding_state tr_upnpPulse(
if (handle->isMapped)
{
tr_logAddInfo(fmt::format(
_("Forwarded local port {local_port} to {advertised_port}"),
fmt::runtime(_("Forwarded local port {local_port} to {advertised_port}")),
fmt::arg("local_port", local_port.host()),
fmt::arg("advertised_port", advertised_port.host())));
handle->advertised_port = advertised_port;
handle->local_port = local_port;
handle->state = UpnpState::Idle;
}
else
{
tr_logAddInfo(_("If your router supports UPnP, please make sure UPnP is enabled!"));
handle->advertised_port = {};
handle->local_port = {};
handle->state = UpnpState::Failed;
}
handle->state = UpnpState::Idle;
}
return port_fwd_state(handle->state, handle->isMapped);

View File

@ -198,7 +198,7 @@ private:
{
mediator_.on_port_forwarded(result.advertised_port);
tr_logAddInfo(fmt::format(
_("Mapped private port {private_port} to public port {public_port}"),
fmt::runtime(_("Mapped private port {private_port} to public port {public_port}")),
fmt::arg("private_port", result.local_port.host()),
fmt::arg("public_port", result.advertised_port.host())));
}
@ -214,7 +214,7 @@ private:
if (auto const new_state = state(); new_state != old_state)
{
tr_logAddInfo(fmt::format(
_("State changed from '{old_state}' to '{state}'"),
fmt::runtime(_("State changed from '{old_state}' to '{state}'")),
fmt::arg("old_state", getNatStateStr(old_state)),
fmt::arg("state", getNatStateStr(new_state))));
}

View File

@ -8,7 +8,6 @@
#include <cstddef>
#include <iterator> // for std::distance()
#include <optional>
#include <string>
#include <string_view>
#include <vector>

View File

@ -100,12 +100,12 @@ void saveLabels(tr_variant* dict, tr_torrent const* tor)
}
}
auto loadLabels(tr_variant* dict, tr_torrent* tor)
tr_resume::fields_t loadLabels(tr_variant* dict, tr_torrent* tor)
{
tr_variant* list = nullptr;
if (!tr_variantDictFindList(dict, TR_KEY_labels, &list))
{
return tr_resume::fields_t{};
return {};
}
auto const n = tr_variantListSize(list);
@ -131,7 +131,7 @@ void saveGroup(tr_variant* dict, tr_torrent const* tor)
tr_variantDictAddStrView(dict, TR_KEY_group, tor->bandwidth_group());
}
auto loadGroup(tr_variant* dict, tr_torrent* tor)
tr_resume::fields_t loadGroup(tr_variant* dict, tr_torrent* tor)
{
if (std::string_view group_name; tr_variantDictFindStrView(dict, TR_KEY_group, &group_name) && !std::empty(group_name))
{
@ -139,7 +139,7 @@ auto loadGroup(tr_variant* dict, tr_torrent* tor)
return tr_resume::Group;
}
return tr_resume::fields_t{};
return {};
}
// ---
@ -155,12 +155,12 @@ void saveDND(tr_variant* dict, tr_torrent const* tor)
}
}
auto loadDND(tr_variant* dict, tr_torrent* tor)
tr_resume::fields_t loadDND(tr_variant* dict, tr_torrent* tor)
{
auto ret = tr_resume::fields_t{};
tr_variant* list = nullptr;
auto const n = tor->file_count();
if (auto const n = tor->file_count(); tr_variantDictFindList(dict, TR_KEY_dnd, &list) && tr_variantListSize(list) == n)
if (tr_variantDictFindList(dict, TR_KEY_dnd, &list) && tr_variantListSize(list) == n)
{
auto wanted = std::vector<tr_file_index_t>{};
auto unwanted = std::vector<tr_file_index_t>{};
@ -183,20 +183,17 @@ auto loadDND(tr_variant* dict, tr_torrent* tor)
tor->init_files_wanted(std::data(unwanted), std::size(unwanted), false);
tor->init_files_wanted(std::data(wanted), std::size(wanted), true);
ret = tr_resume::Dnd;
}
else
{
tr_logAddDebugTor(
tor,
fmt::format(
"Couldn't load DND flags. DND list {} has {} children; torrent has {} files",
fmt::ptr(list),
tr_variantListSize(list),
n));
return tr_resume::Dnd;
}
return ret;
tr_logAddDebugTor(
tor,
fmt::format(
"Couldn't load DND flags. DND list {} has {} children; torrent has {} files",
fmt::ptr(list),
tr_variantListSize(list),
n));
return {};
}
// ---
@ -212,10 +209,8 @@ void saveFilePriorities(tr_variant* dict, tr_torrent const* tor)
}
}
auto loadFilePriorities(tr_variant* dict, tr_torrent* tor)
tr_resume::fields_t loadFilePriorities(tr_variant* dict, tr_torrent* tor)
{
auto ret = tr_resume::fields_t{};
auto const n = tor->file_count();
tr_variant* list = nullptr;
if (tr_variantDictFindList(dict, TR_KEY_priority, &list) && tr_variantListSize(list) == n)
@ -229,10 +224,10 @@ auto loadFilePriorities(tr_variant* dict, tr_torrent* tor)
}
}
ret = tr_resume::FilePriorities;
return tr_resume::FilePriorities;
}
return ret;
return {};
}
// ---
@ -306,10 +301,8 @@ auto loadSpeedLimits(tr_variant* dict, tr_torrent* tor)
return ret;
}
auto loadRatioLimits(tr_variant* dict, tr_torrent* tor)
tr_resume::fields_t loadRatioLimits(tr_variant* dict, tr_torrent* tor)
{
auto ret = tr_resume::fields_t{};
if (tr_variant* d = nullptr; tr_variantDictFindDict(dict, TR_KEY_ratio_limit, &d))
{
if (auto dratio = double{}; tr_variantDictFindReal(d, TR_KEY_ratio_limit, &dratio))
@ -322,16 +315,14 @@ auto loadRatioLimits(tr_variant* dict, tr_torrent* tor)
tor->set_seed_ratio_mode(static_cast<tr_ratiolimit>(i));
}
ret = tr_resume::Ratiolimit;
return tr_resume::Ratiolimit;
}
return ret;
return {};
}
auto loadIdleLimits(tr_variant* dict, tr_torrent* tor)
tr_resume::fields_t loadIdleLimits(tr_variant* dict, tr_torrent* tor)
{
auto ret = tr_resume::fields_t{};
if (tr_variant* d = nullptr; tr_variantDictFindDict(dict, TR_KEY_idle_limit, &d))
{
if (auto imin = int64_t{}; tr_variantDictFindInt(d, TR_KEY_idle_limit, &imin))
@ -344,10 +335,10 @@ auto loadIdleLimits(tr_variant* dict, tr_torrent* tor)
tor->set_idle_limit_mode(static_cast<tr_idlelimit>(i));
}
ret = tr_resume::Idlelimit;
return tr_resume::Idlelimit;
}
return ret;
return {};
}
// ---
@ -357,26 +348,23 @@ void saveName(tr_variant* dict, tr_torrent const* tor)
tr_variantDictAddStrView(dict, TR_KEY_name, tor->name());
}
auto loadName(tr_variant* dict, tr_torrent* tor)
tr_resume::fields_t loadName(tr_variant* dict, tr_torrent* tor)
{
auto ret = tr_resume::fields_t{};
auto name = std::string_view{};
if (!tr_variantDictFindStrView(dict, TR_KEY_name, &name))
{
return ret;
return {};
}
name = tr_strv_strip(name);
if (std::empty(name))
{
return ret;
return {};
}
tor->set_name(name);
ret |= tr_resume::Name;
return ret;
return tr_resume::Name;
}
// ---
@ -391,14 +379,12 @@ void saveFilenames(tr_variant* dict, tr_torrent const* tor)
}
}
auto loadFilenames(tr_variant* dict, tr_torrent* tor)
tr_resume::fields_t loadFilenames(tr_variant* dict, tr_torrent* tor)
{
auto ret = tr_resume::fields_t{};
tr_variant* list = nullptr;
if (!tr_variantDictFindList(dict, TR_KEY_files, &list))
{
return ret;
return {};
}
auto const n_files = tor->file_count();
@ -412,8 +398,7 @@ auto loadFilenames(tr_variant* dict, tr_torrent* tor)
}
}
ret |= tr_resume::Filenames;
return ret;
return tr_resume::Filenames;
}
// ---
@ -451,7 +436,7 @@ void rawToBitfield(tr_bitfield& bitfield, uint8_t const* raw, size_t rawlen)
}
}
void saveProgress(tr_variant* dict, tr_torrent const* tor, tr_torrent::ResumeHelper const& helper)
void saveProgress(tr_variant* dict, tr_torrent::ResumeHelper const& helper)
{
tr_variant* const prog = tr_variantDictAddDict(dict, TR_KEY_progress, 4);
@ -467,13 +452,7 @@ void saveProgress(tr_variant* dict, tr_torrent const* tor, tr_torrent::ResumeHel
// add the 'checked pieces' bitfield
bitfieldToRaw(helper.checked_pieces(), tr_variantDictAdd(prog, TR_KEY_pieces));
/* add the progress */
if (tor->is_seed())
{
tr_variantDictAddStrView(prog, TR_KEY_have, "all"sv);
}
/* add the blocks bitfield */
// add the blocks bitfield
bitfieldToRaw(helper.blocks(), tr_variantDictAdd(prog, TR_KEY_blocks));
}
@ -489,7 +468,7 @@ void saveProgress(tr_variant* dict, tr_torrent const* tor, tr_torrent::ResumeHel
* pieces cleared from the bitset.
*
* Second approach (2.20 - 3.00): the 'progress' dict had a
* 'time_checked' entry which was a list with fileCount items.
* 'time_checked' entry which was a list with file_count items.
* Each item was either a list of per-piece timestamps, or a
* single timestamp if either all or none of the pieces had been
* tested more recently than the file's mtime.
@ -497,7 +476,7 @@ void saveProgress(tr_variant* dict, tr_torrent const* tor, tr_torrent::ResumeHel
* First approach (pre-2.20) had an "mtimes" list identical to
* 3.10, but not the 'pieces' bitfield.
*/
auto loadProgress(tr_variant* dict, tr_torrent* tor, tr_torrent::ResumeHelper& helper)
tr_resume::fields_t loadProgress(tr_variant* dict, tr_torrent* tor, tr_torrent::ResumeHelper& helper)
{
if (tr_variant* prog = nullptr; tr_variantDictFindDict(dict, TR_KEY_progress, &prog))
{
@ -590,24 +569,13 @@ auto loadProgress(tr_variant* dict, tr_torrent* tor, tr_torrent::ResumeHelper& h
rawToBitfield(blocks, buf, buflen);
}
}
else if (auto sv = std::string_view{}; tr_variantDictFindStrView(prog, TR_KEY_have, &sv))
{
if (sv == "all"sv)
{
blocks.set_has_all();
}
else
{
err = "Invalid value for HAVE";
}
}
else if (tr_variantDictFindRaw(prog, TR_KEY_bitfield, &raw, &rawlen))
{
blocks.set_raw(raw, rawlen);
}
else
{
err = "Couldn't find 'pieces' or 'have' or 'bitfield'";
err = "Couldn't find 'blocks' or 'bitfield'";
}
if (err != nullptr)
@ -622,7 +590,7 @@ auto loadProgress(tr_variant* dict, tr_torrent* tor, tr_torrent::ResumeHelper& h
return tr_resume::Progress;
}
return tr_resume::fields_t{};
return {};
}
// ---
@ -914,7 +882,7 @@ void save(tr_torrent* const tor, tr_torrent::ResumeHelper const& helper)
{
saveFilePriorities(&top, tor);
saveDND(&top, tor);
saveProgress(&top, tor, helper);
saveProgress(&top, helper);
}
saveSpeedLimits(&top, tor);

View File

@ -102,7 +102,7 @@ public:
if (std::size(src) >= TrUnixAddrStrLen)
{
tr_logAddError(fmt::format(
_("Unix socket path must be fewer than {count} characters (including '{prefix}' prefix)"),
fmt::runtime(_("Unix socket path must be fewer than {count} characters (including '{prefix}' prefix)")),
fmt::arg("count", TrUnixAddrStrLen - 1),
fmt::arg("prefix", TrUnixSocketPrefix)));
return false;
@ -614,8 +614,9 @@ bool bindUnixSocket(
if (chmod(addr.sun_path, socket_mode) != 0)
{
tr_logAddWarn(
fmt::format(_("Couldn't set RPC socket mode to {mode:#o}, defaulting to 0755"), fmt::arg("mode", socket_mode)));
tr_logAddWarn(fmt::format(
fmt::runtime(_("Couldn't set RPC socket mode to {mode:#o}, defaulting to 0755")),
fmt::arg("mode", socket_mode)));
}
return evhttp_bind_listener(httpd, lev) != nullptr;
@ -677,10 +678,10 @@ void start_server(tr_rpc_server* server)
}
tr_logAddError(fmt::format(
tr_ngettext(
fmt::runtime(tr_ngettext(
"Couldn't bind to {address} after {count} attempt, giving up",
"Couldn't bind to {address} after {count} attempts, giving up",
ServerStartRetryCount),
ServerStartRetryCount)),
fmt::arg("address", addr_port_str),
fmt::arg("count", ServerStartRetryCount)));
}
@ -689,7 +690,9 @@ void start_server(tr_rpc_server* server)
evhttp_set_gencb(httpd, handle_request, server);
server->httpd.reset(httpd);
tr_logAddInfo(fmt::format(_("Listening for RPC and Web requests on '{address}'"), fmt::arg("address", addr_port_str)));
tr_logAddInfo(fmt::format(
fmt::runtime(_("Listening for RPC and Web requests on '{address}'")),
fmt::arg("address", addr_port_str)));
}
rpc_server_start_retry_cancel(server);
@ -717,7 +720,7 @@ void stop_server(tr_rpc_server* server)
}
tr_logAddInfo(fmt::format(
_("Stopped listening for RPC and Web requests on '{address}'"),
fmt::runtime(_("Stopped listening for RPC and Web requests on '{address}'")),
fmt::arg("address", server->bind_address_->to_string(server->port()))));
}
@ -739,7 +742,7 @@ auto parse_whitelist(std::string_view whitelist)
auto const pos = whitelist.find_first_of(" ,;"sv);
auto const token = tr_strv_strip(whitelist.substr(0, pos));
list.emplace_back(token);
tr_logAddInfo(fmt::format(_("Added '{entry}' to host whitelist"), fmt::arg("entry", token)));
tr_logAddInfo(fmt::format(fmt::runtime(_("Added '{entry}' to host whitelist")), fmt::arg("entry", token)));
whitelist = pos == std::string_view::npos ? ""sv : whitelist.substr(pos + 1);
}
@ -859,7 +862,8 @@ void tr_rpc_server::load(Settings&& settings)
{
// 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::runtime(_(
"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::arg("key", tr_quark_get_string_view(TR_KEY_rpc_bind_address)),
fmt::arg("value", settings_.bind_address_str)));
}
@ -872,7 +876,7 @@ void tr_rpc_server::load(Settings&& settings)
if (this->is_enabled())
{
auto const rpc_uri = bind_address_->to_string(port()) + settings_.url;
tr_logAddInfo(fmt::format(_("Serving RPC and Web requests on {address}"), fmt::arg("address", rpc_uri)));
tr_logAddInfo(fmt::format(fmt::runtime(_("Serving RPC and Web requests on {address}")), fmt::arg("address", rpc_uri)));
session->run_in_session_thread(start_server, this);
if (this->is_whitelist_enabled())
@ -888,7 +892,8 @@ void tr_rpc_server::load(Settings&& settings)
if (!std::empty(web_client_dir_))
{
tr_logAddInfo(fmt::format(_("Serving RPC and Web requests from '{path}'"), fmt::arg("path", web_client_dir_)));
tr_logAddInfo(
fmt::format(fmt::runtime(_("Serving RPC and Web requests from '{path}'")), fmt::arg("path", web_client_dir_)));
}
}

View File

@ -1155,7 +1155,7 @@ void onPortTested(tr_web::FetchResponse const& web_response)
tr_idle_function_done(
data,
fmt::format(
_("Couldn't test port: {error} ({error_code})"),
fmt::runtime(_("Couldn't test port: {error} ({error_code})")),
fmt::arg("error", tr_webGetResponseStr(status)),
fmt::arg("error_code", status)));
return;
@ -1210,7 +1210,7 @@ void onBlocklistFetched(tr_web::FetchResponse const& web_response)
tr_idle_function_done(
data,
fmt::format(
_("Couldn't fetch blocklist: {error} ({error_code})"),
fmt::runtime(_("Couldn't fetch blocklist: {error} ({error_code})")),
fmt::arg("error", tr_webGetResponseStr(status)),
fmt::arg("error_code", status)));
return;
@ -1255,7 +1255,7 @@ void onBlocklistFetched(tr_web::FetchResponse const& web_response)
tr_idle_function_done(
data,
fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't save '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -1337,7 +1337,7 @@ void onMetadataFetched(tr_web::FetchResponse const& web_response)
tr_idle_function_done(
data->data,
fmt::format(
_("Couldn't fetch torrent: {error} ({error_code})"),
fmt::runtime(_("Couldn't fetch torrent: {error} ({error_code})")),
fmt::arg("error", tr_webGetResponseStr(status)),
fmt::arg("error_code", status)));
}

View File

@ -65,7 +65,7 @@ tr_sys_file_t create_lockfile(std::string_view session_id)
if (error)
{
tr_logAddWarn(fmt::format(
_("Couldn't create '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't create '{path}': {error} ({error_code})")),
fmt::arg("path", lockfile_path),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -147,7 +147,7 @@ bool tr_session_id::is_local(std::string_view session_id) noexcept
if (error)
{
tr_logAddWarn(fmt::format(
_("Couldn't open session lock file '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't open session lock file '{path}': {error} ({error_code})")),
fmt::arg("path", lockfile_path),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));

View File

@ -414,7 +414,7 @@ tr_session::BoundSocket::BoundSocket(
}
tr_logAddInfo(fmt::format(
_("Listening to incoming peer connections on {hostport}"),
fmt::runtime(_("Listening to incoming peer connections on {hostport}")),
fmt::arg("hostport", tr_socket_address::display_name(addr, port))));
event_add(ev_.get(), nullptr);
}
@ -732,7 +732,8 @@ void tr_session::initImpl(init_data& data)
blocklists_.load(blocklist_dir_, blocklist_enabled());
tr_logAddInfo(fmt::format(_("Transmission version {version} starting"), fmt::arg("version", LONG_VERSION_STRING)));
tr_logAddInfo(
fmt::format(fmt::runtime(_("Transmission version {version} starting")), fmt::arg("version", LONG_VERSION_STRING)));
setSettings(settings, true);
@ -1418,7 +1419,8 @@ void tr_sessionClose(tr_session* session, size_t timeout_secs)
TR_ASSERT(session != nullptr);
TR_ASSERT(!session->am_in_session_thread());
tr_logAddInfo(fmt::format(_("Transmission version {version} shutting down"), fmt::arg("version", LONG_VERSION_STRING)));
tr_logAddInfo(
fmt::format(fmt::runtime(_("Transmission version {version} shutting down")), fmt::arg("version", LONG_VERSION_STRING)));
auto closed_promise = std::promise<void>{};
auto closed_future = closed_promise.get_future();
@ -1465,7 +1467,7 @@ void session_load_torrents(tr_session* session, tr_ctor* ctor, std::promise<size
if (n_torrents != 0U)
{
tr_logAddInfo(fmt::format(
tr_ngettext("Loaded {count} torrent", "Loaded {count} torrents", n_torrents),
fmt::runtime(tr_ngettext("Loaded {count} torrent", "Loaded {count} torrents", n_torrents)),
fmt::arg("count", n_torrents)));
}

View File

@ -17,7 +17,6 @@
#include <cstddef> // size_t
#include <cstdint> // uintX_t
#include <ctime> // time_t
#include <functional>
#include <future>
#include <memory>
#include <mutex>

View File

@ -7,7 +7,6 @@
#include <chrono>
#include <cstddef> // size_t
#include <cstdint> // int64_t, uint32_t
#include <optional>
#include <string>
#include <string_view>
#include <utility>

View File

@ -20,6 +20,12 @@ namespace libtransmission
class Settings
{
public:
virtual ~Settings() = default;
Settings(Settings const& settings) = default;
Settings& operator=(Settings const& other) = default;
Settings(Settings&& settings) noexcept = default;
Settings& operator=(Settings&& other) noexcept = default;
void load(tr_variant const& src);
[[nodiscard]] tr_variant save() const;

View File

@ -32,15 +32,15 @@ namespace
using file_func_t = std::function<void(char const* filename)>;
bool isFolder(std::string_view path)
bool is_folder(std::string_view path)
{
auto const info = tr_sys_path_get_info(path);
return info && info->isFolder();
}
bool isEmptyFolder(char const* path)
bool is_empty_folder(char const* path)
{
if (!isFolder(path))
if (!is_folder(path))
{
return false;
}
@ -63,9 +63,9 @@ bool isEmptyFolder(char const* path)
return true;
}
void depthFirstWalk(char const* path, file_func_t const& func, std::optional<int> max_depth = {})
void depth_first_walk(char const* path, file_func_t const& func, std::optional<int> max_depth = {})
{
if (isFolder(path) && (!max_depth || *max_depth > 0))
if (is_folder(path) && (!max_depth || *max_depth > 0))
{
if (auto const odir = tr_sys_dir_open(path); odir != TR_BAD_SYS_DIR)
{
@ -78,7 +78,7 @@ void depthFirstWalk(char const* path, file_func_t const& func, std::optional<int
continue;
}
depthFirstWalk(tr_pathbuf{ path, '/', name }.c_str(), func, max_depth ? *max_depth - 1 : max_depth);
depth_first_walk(tr_pathbuf{ path, '/', name }.c_str(), func, max_depth ? *max_depth - 1 : max_depth);
}
tr_sys_dir_close(odir);
@ -88,7 +88,7 @@ void depthFirstWalk(char const* path, file_func_t const& func, std::optional<int
func(path);
}
bool isJunkFile(std::string_view filename)
bool is_junk_file(std::string_view filename)
{
auto const base = tr_sys_path_basename(filename);
@ -141,9 +141,9 @@ std::optional<tr_torrent_files::FoundFile> tr_torrent_files::find(
return {};
}
bool tr_torrent_files::hasAnyLocalData(std::string_view const* paths, size_t n_paths) const
bool tr_torrent_files::has_any_local_data(std::string_view const* paths, size_t n_paths) const
{
for (tr_file_index_t i = 0, n = fileCount(); i < n; ++i)
for (tr_file_index_t i = 0, n = file_count(); i < n; ++i)
{
if (find(i, paths, n_paths))
{
@ -180,7 +180,7 @@ bool tr_torrent_files::move(
auto err = bool{};
for (tr_file_index_t i = 0, n = fileCount(); i < n; ++i)
for (tr_file_index_t i = 0, n = file_count(); i < n; ++i)
{
auto const found = find(i, std::data(paths), std::size(paths));
if (!found)
@ -210,7 +210,7 @@ bool tr_torrent_files::move(
{
auto const remove_empty_directories = [](char const* filename)
{
if (isEmptyFolder(filename))
if (is_empty_folder(filename))
{
tr_sys_path_remove(filename, nullptr);
}
@ -249,7 +249,7 @@ void tr_torrent_files::remove(std::string_view parent_in, std::string_view tmpdi
// move the local data to the tmpdir
auto const paths = std::array<std::string_view, 1>{ parent.sv() };
for (tr_file_index_t idx = 0, n_files = fileCount(); idx < n_files; ++idx)
for (tr_file_index_t idx = 0, n_files = file_count(); idx < n_files; ++idx)
{
if (auto const found = find(idx, std::data(paths), std::size(paths)); found)
{
@ -261,7 +261,7 @@ void tr_torrent_files::remove(std::string_view parent_in, std::string_view tmpdi
// because we'll need it below in the 'remove junk' phase
auto const path = tr_pathbuf{ parent, '/', tmpdir_prefix };
auto top_files = std::set<std::string>{ std::string{ path } };
depthFirstWalk(
depth_first_walk(
tmpdir,
[&parent, &tmpdir, &top_files](char const* filename)
{
@ -285,8 +285,8 @@ void tr_torrent_files::remove(std::string_view parent_in, std::string_view tmpdi
// the folder hierarchy by removing top-level files & folders first.
// But that can fail -- e.g. `func` might refuse to remove nonempty
// directories -- so plan B is to remove everything bottom-up.
depthFirstWalk(tmpdir, func_wrapper, 1);
depthFirstWalk(tmpdir, func_wrapper);
depth_first_walk(tmpdir, func_wrapper, 1);
depth_first_walk(tmpdir, func_wrapper);
tr_sys_path_remove(tmpdir);
// OK we've removed the local data.
@ -294,23 +294,23 @@ void tr_torrent_files::remove(std::string_view parent_in, std::string_view tmpdi
// Remove the first two categories and leave the third alone.
auto const remove_junk = [](char const* filename)
{
if (isEmptyFolder(filename) || isJunkFile(filename))
if (is_empty_folder(filename) || is_junk_file(filename))
{
tr_sys_path_remove(filename);
}
};
for (auto const& filename : top_files)
{
depthFirstWalk(filename.c_str(), remove_junk);
depth_first_walk(filename.c_str(), remove_junk);
}
}
namespace
{
// `isUnixReservedFile` and `isWin32ReservedFile` kept as `maybe_unused`
// `is_unix_reserved_file` and `is_win32_reserved_file` kept as `maybe_unused`
// for potential support of different filesystems on the same OS
[[nodiscard, maybe_unused]] bool isUnixReservedFile(std::string_view in) noexcept
[[nodiscard, maybe_unused]] bool is_unix_reserved_file(std::string_view in) noexcept
{
static auto constexpr ReservedNames = std::array<std::string_view, 2>{
"."sv,
@ -325,7 +325,7 @@ namespace
// COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.
// Also avoid these names followed immediately by an extension;
// for example, NUL.txt is not recommended.
[[nodiscard, maybe_unused]] bool isWin32ReservedFile(std::string_view in) noexcept
[[nodiscard, maybe_unused]] bool is_win32_reserved_file(std::string_view in) noexcept
{
if (std::empty(in))
{
@ -365,18 +365,22 @@ namespace
[in_upper_sv](auto const& prefix) { return tr_strv_starts_with(in_upper_sv, prefix); });
}
[[nodiscard]] bool isReservedFile(std::string_view in) noexcept
[[nodiscard]] bool is_reserved_file(std::string_view in, bool os_specific) noexcept
{
if (!os_specific)
{
return is_unix_reserved_file(in) || is_win32_reserved_file(in);
}
#ifdef _WIN32
return isWin32ReservedFile(in);
return is_win32_reserved_file(in);
#else
return isUnixReservedFile(in);
return is_unix_reserved_file(in);
#endif
}
// `isUnixReservedChar` and `isWin32ReservedChar` kept as `maybe_unused`
// `is_unix_reserved_char` and `is_win32_reserved_char` kept as `maybe_unused`
// for potential support of different filesystems on the same OS
[[nodiscard, maybe_unused]] auto constexpr isUnixReservedChar(unsigned char ch) noexcept
[[nodiscard, maybe_unused]] auto constexpr is_unix_reserved_char(unsigned char ch) noexcept
{
return ch == '/';
}
@ -385,7 +389,7 @@ namespace
// Use any character in the current code page for a name, including Unicode
// characters and characters in the extended character set (128255),
// except for the following:
[[nodiscard, maybe_unused]] auto constexpr isWin32ReservedChar(unsigned char ch) noexcept
[[nodiscard, maybe_unused]] auto constexpr is_win32_reserved_char(unsigned char ch) noexcept
{
switch (ch)
{
@ -404,17 +408,21 @@ namespace
}
}
[[nodiscard]] auto constexpr isReservedChar(unsigned char ch) noexcept
[[nodiscard]] auto constexpr is_reserved_char(unsigned char ch, bool os_specific) noexcept
{
if (!os_specific)
{
return is_unix_reserved_char(ch) || is_win32_reserved_char(ch);
}
#ifdef _WIN32
return isWin32ReservedChar(ch);
return is_win32_reserved_char(ch);
#else
return isUnixReservedChar(ch);
return is_unix_reserved_char(ch);
#endif
}
// https://en.wikipedia.org/wiki/Filename#Comparison_of_filename_limitations
void appendSanitizedComponent(std::string_view in, tr_pathbuf& out)
void append_sanitized_component(std::string_view in, tr_pathbuf& out, bool os_specific)
{
#ifdef _WIN32
// remove leading and trailing spaces
@ -428,27 +436,27 @@ void appendSanitizedComponent(std::string_view in, tr_pathbuf& out)
#endif
// replace reserved filenames with an underscore
if (isReservedFile(in))
if (is_reserved_file(in, os_specific))
{
out.append('_');
}
// replace reserved characters with an underscore
static auto constexpr AddChar = [](auto ch)
auto const add_char = [os_specific](auto ch)
{
return isReservedChar(ch) ? '_' : ch;
return is_reserved_char(ch, os_specific) ? '_' : ch;
};
std::transform(std::begin(in), std::end(in), std::back_inserter(out), AddChar);
std::transform(std::begin(in), std::end(in), std::back_inserter(out), add_char);
}
} // namespace
void tr_torrent_files::makeSubpathPortable(std::string_view path, tr_pathbuf& append_me)
void tr_torrent_files::sanitize_subpath(std::string_view path, tr_pathbuf& append_me, bool os_specific)
{
auto segment = std::string_view{};
while (tr_strv_sep(&path, &segment, '/'))
{
appendSanitizedComponent(segment, append_me);
append_sanitized_component(segment, append_me, os_specific);
append_me.append('/');
}

View File

@ -35,17 +35,17 @@ public:
return std::empty(files_);
}
[[nodiscard]] TR_CONSTEXPR20 size_t fileCount() const noexcept
[[nodiscard]] TR_CONSTEXPR20 size_t file_count() const noexcept
{
return std::size(files_);
}
[[nodiscard]] TR_CONSTEXPR20 uint64_t fileSize(tr_file_index_t file_index) const
[[nodiscard]] TR_CONSTEXPR20 uint64_t file_size(tr_file_index_t file_index) const
{
return files_.at(file_index).size_;
}
[[nodiscard]] constexpr auto totalSize() const noexcept
[[nodiscard]] constexpr auto total_size() const noexcept
{
return total_size_;
}
@ -55,12 +55,12 @@ public:
return files_.at(file_index).path_;
}
void setPath(tr_file_index_t file_index, std::string_view path)
void set_path(tr_file_index_t file_index, std::string_view path)
{
files_.at(file_index).setPath(path);
files_.at(file_index).set_path(path);
}
void insertSubpathPrefix(std::string_view path)
void insert_subpath_prefix(std::string_view path)
{
auto const buf = tr_pathbuf{ path, '/' };
@ -76,7 +76,7 @@ public:
files_.reserve(n_files);
}
void shrinkToFit()
void shrink_to_fit()
{
files_.shrink_to_fit();
}
@ -87,7 +87,7 @@ public:
total_size_ = uint64_t{};
}
[[nodiscard]] auto sortedByPath() const
[[nodiscard]] auto sorted_by_path() const
{
auto ret = std::vector<std::pair<std::string /*path*/, uint64_t /*size*/>>{};
ret.reserve(std::size(files_));
@ -153,20 +153,20 @@ public:
};
[[nodiscard]] std::optional<FoundFile> find(tr_file_index_t file, std::string_view const* paths, size_t n_paths) const;
[[nodiscard]] bool hasAnyLocalData(std::string_view const* paths, size_t n_paths) const;
[[nodiscard]] bool has_any_local_data(std::string_view const* paths, size_t n_paths) const;
static void makeSubpathPortable(std::string_view path, tr_pathbuf& append_me);
static void sanitize_subpath(std::string_view path, tr_pathbuf& append_me, bool os_specific = true);
[[nodiscard]] static auto makeSubpathPortable(std::string_view path)
[[nodiscard]] static auto sanitize_subpath(std::string_view path, bool os_specific = true)
{
auto tmp = tr_pathbuf{};
makeSubpathPortable(path, tmp);
sanitize_subpath(path, tmp, os_specific);
return std::string{ tmp.sv() };
}
[[nodiscard]] static bool isSubpathPortable(std::string_view path)
[[nodiscard]] static bool is_subpath_sanitized(std::string_view path, bool os_specific = true)
{
return makeSubpathPortable(path) == path;
return sanitize_subpath(path, os_specific) == path;
}
static constexpr std::string_view PartialFileSuffix = ".part";
@ -175,7 +175,7 @@ private:
struct file_t
{
public:
void setPath(std::string_view subpath)
void set_path(std::string_view subpath)
{
if (path_ != subpath)
{

View File

@ -68,6 +68,15 @@ tr_metadata_download::tr_metadata_download(std::string_view log_name, int64_t co
create_all_needed(n);
}
void tr_torrent::do_magnet_idle_work()
{
if (auto& m = metadata_download_; m && m->is_complete())
{
tr_logAddDebugTor(this, fmt::format("we now have all the metainfo!"));
on_have_all_metainfo();
}
}
void tr_torrent::maybe_start_metadata_transfer(int64_t const size) noexcept
{
if (has_metainfo() || metadata_download_)
@ -262,20 +271,20 @@ void tr_torrent::on_have_all_metainfo()
m.reset();
}
bool tr_metadata_download::set_metadata_piece(int64_t const piece, void const* const data, size_t const len)
void tr_metadata_download::set_metadata_piece(int64_t const piece, void const* const data, size_t const len)
{
TR_ASSERT(data != nullptr);
// sanity test: is `piece` in range?
if (piece < 0 || piece >= piece_count_)
{
return false;
return;
}
// sanity test: is `len` the right size?
if (get_piece_length(piece) != len)
{
return false;
return;
}
// do we need this piece?
@ -286,7 +295,7 @@ bool tr_metadata_download::set_metadata_piece(int64_t const piece, void const* c
[piece](auto const& item) { return item.piece == piece; });
if (iter == std::end(needed))
{
return false;
return;
}
auto const offset = piece * MetadataPieceSize;
@ -294,8 +303,6 @@ bool tr_metadata_download::set_metadata_piece(int64_t const piece, void const* c
needed.erase(iter);
tr_logAddDebugMagnet(this, fmt::format("saving metainfo piece {}... {} remain", piece, std::size(needed)));
return std::empty(needed);
}
void tr_torrent::set_metadata_piece(int64_t const piece, void const* const data, size_t const len)
@ -304,10 +311,9 @@ void tr_torrent::set_metadata_piece(int64_t const piece, void const* const data,
tr_logAddDebugTor(this, fmt::format("got metadata piece {} of {} bytes", piece, len));
if (auto& m = metadata_download_; m && m->set_metadata_piece(piece, data, len))
if (auto& m = metadata_download_)
{
tr_logAddDebugTor(this, fmt::format("we now have all the metainfo!"));
on_have_all_metainfo();
m->set_metadata_piece(piece, data, len);
}
}

View File

@ -14,7 +14,6 @@
#include <ctime> // time_t
#include <deque>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
@ -39,7 +38,12 @@ public:
return size > 0 && size <= std::numeric_limits<int>::max();
}
bool set_metadata_piece(int64_t piece, void const* data, size_t len);
[[nodiscard]] auto is_complete() const noexcept
{
return std::empty(pieces_needed_);
}
void set_metadata_piece(int64_t piece, void const* data, size_t len);
[[nodiscard]] std::optional<int64_t> get_next_metadata_request(time_t now) noexcept;

View File

@ -99,7 +99,7 @@ struct MetainfoHandler final : public transmission::benc::BasicHandler<MaxBencDe
{
file_subpath_ += '/';
}
tr_torrent_files::makeSubpathPortable(currentKey(), file_subpath_);
tr_torrent_files::sanitize_subpath(currentKey(), file_subpath_);
}
else if (pathIs(InfoKey))
{
@ -298,7 +298,7 @@ struct MetainfoHandler final : public transmission::benc::BasicHandler<MaxBencDe
{
file_subpath_ += '/';
}
tr_torrent_files::makeSubpathPortable(value, file_subpath_);
tr_torrent_files::sanitize_subpath(value, file_subpath_);
}
else if (current_key == AttrKey)
{
@ -488,10 +488,10 @@ private:
}
auto root = tr_pathbuf{};
tr_torrent_files::makeSubpathPortable(tm_.name_, root);
tr_torrent_files::sanitize_subpath(tm_.name_, root);
if (!std::empty(root))
{
tm_.files_.insertSubpathPrefix(root);
tm_.files_.insert_subpath_prefix(root);
}
TR_ASSERT(info_dict_begin_[0] == 'd');
@ -522,7 +522,7 @@ private:
// In the single file case, length maps to the length of the file in bytes.
if (tm_.file_count() == 0 && length_ != 0 && !std::empty(tm_.name_))
{
tm_.files_.add(tm_.name_, length_);
tm_.files_.add(tr_torrent_files::sanitize_subpath(tm_.name_), length_);
}
if (auto const has_metainfo = tm_.info_dict_size() != 0U; has_metainfo)
@ -546,7 +546,7 @@ private:
return false;
}
tm_.block_info_ = tr_block_info{ tm_.files_.totalSize(), piece_size_ };
tm_.block_info_ = tr_block_info{ tm_.files_.total_size(), piece_size_ };
return true;
}
@ -710,7 +710,7 @@ bool tr_torrent_metainfo::migrate_file(
{
tr_logAddError(
fmt::format(
_("Migrated torrent file from '{old_path}' to '{path}'"),
fmt::runtime(_("Migrated torrent file from '{old_path}' to '{path}'")),
fmt::arg("old_path", old_filename),
fmt::arg("path", new_filename)),
name);

View File

@ -44,11 +44,11 @@ public:
}
[[nodiscard]] TR_CONSTEXPR20 auto file_count() const noexcept
{
return files().fileCount();
return files().file_count();
}
[[nodiscard]] TR_CONSTEXPR20 auto file_size(tr_file_index_t i) const
{
return files().fileSize(i);
return files().file_size(i);
}
[[nodiscard]] TR_CONSTEXPR20 auto const& file_subpath(tr_file_index_t i) const
{
@ -57,7 +57,7 @@ public:
void set_file_subpath(tr_file_index_t i, std::string_view subpath)
{
files_.setPath(i, subpath);
files_.set_path(i, subpath);
}
/// BLOCK INFO

View File

@ -135,7 +135,7 @@ bool tr_torrentSetMetainfoFromFile(tr_torrent* tor, tr_torrent_metainfo const* m
if (error)
{
tor->error().set_local_error(fmt::format(
_("Couldn't use metainfo from '{path}' for '{magnet}': {error} ({error_code})"),
fmt::runtime(_("Couldn't use metainfo from '{path}' for '{magnet}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("magnet", tor->magnet()),
fmt::arg("error", error.message()),
@ -399,7 +399,7 @@ void torrentCallScript(tr_torrent const* tor, std::string const& script)
{ "TR_TORRENT_TRACKERS"sv, trackers_str },
};
tr_logAddInfoTor(tor, fmt::format(_("Calling script '{path}'"), fmt::arg("path", script)));
tr_logAddInfoTor(tor, fmt::format(fmt::runtime(_("Calling script '{path}'")), fmt::arg("path", script)));
auto error = tr_error{};
if (!tr_spawn_async(std::data(cmd), env, TR_IF_WIN32("\\", "/"), &error))
@ -407,7 +407,7 @@ void torrentCallScript(tr_torrent const* tor, std::string const& script)
tr_logAddWarnTor(
tor,
fmt::format(
_("Couldn't call script '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't call script '{path}': {error} ({error_code})")),
fmt::arg("path", script),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -446,7 +446,7 @@ void tr_torrent::stop_if_seed_limit_reached()
session->onRatioLimitHit(this);
}
/* if we're seeding and reach our inactivity limit, stop the torrent */
else if (auto const secs_left = idle_seconds_left(tr_time()); secs_left && *secs_left == 0U)
else if (auto const secs_left = idle_seconds_left(tr_time()); secs_left && *secs_left <= 0U)
{
tr_logAddInfoTor(this, _("Seeding idle limit reached; pausing torrent"));
@ -756,6 +756,10 @@ void tr_torrent::stop_now()
TR_ASSERT(session->am_in_session_thread());
auto const lock = unique_lock();
auto const now = tr_time();
seconds_downloading_before_current_start_ = seconds_downloading(now);
seconds_seeding_before_current_start_ = seconds_seeding(now);
is_running_ = false;
is_stopping_ = false;
mark_changed();
@ -895,7 +899,7 @@ void tr_torrent::on_metainfo_completed()
// Potentially, we are in `tr_torrent::init`,
// and we don't want any file created before `tr_torrent::start`
// so we Verify but we don't Create files.
session->queue_session_thread(tr_torrentVerify, this);
tr_torrentVerify(this);
}
else
{
@ -1031,7 +1035,7 @@ void tr_torrent::init(tr_ctor const& ctor)
if (error)
{
this->error().set_local_error(fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't save '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -1123,7 +1127,7 @@ void tr_torrent::set_location_in_session_thread(std::string_view const path, boo
if (error)
{
this->error().set_local_error(fmt::format(
_("Couldn't move '{old_path}' to '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't move '{old_path}' to '{path}': {error} ({error_code})")),
fmt::arg("old_path", current_dir()),
fmt::arg("path", path),
fmt::arg("error", error.message()),
@ -1208,7 +1212,7 @@ bool tr_torrent::has_any_local_data() const
auto paths = std::array<std::string_view, 4>{};
auto const n_paths = buildSearchPathArray(this, std::data(paths));
return files().hasAnyLocalData(std::data(paths), n_paths);
return files().has_any_local_data(std::data(paths), n_paths);
}
void tr_torrentSetDownloadDir(tr_torrent* tor, char const* path)
@ -1604,6 +1608,39 @@ std::optional<std::string> tr_torrent::VerifyMediator::find_file(tr_file_index_t
return {};
}
void tr_torrent::update_file_path(tr_file_index_t file, std::optional<bool> has_file) const
{
auto const found = find_file(file);
if (!found)
{
return;
}
auto const has = has_file ? *has_file : this->has_file(file);
auto const needs_suffix = session->isIncompleteFileNamingEnabled() && !has;
auto const oldpath = found->filename();
auto const newpath = needs_suffix ?
tr_pathbuf{ found->base(), '/', file_subpath(file), tr_torrent_files::PartialFileSuffix } :
tr_pathbuf{ found->base(), '/', file_subpath(file) };
if (tr_sys_path_is_same(oldpath, newpath))
{
return;
}
if (auto error = tr_error{}; !tr_sys_path_rename(oldpath, newpath, &error))
{
tr_logAddErrorTor(
this,
fmt::format(
fmt::runtime(_("Couldn't move '{old_path}' to '{path}': {error} ({error_code})")),
fmt::arg("old_path", oldpath),
fmt::arg("path", newpath),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
}
}
void tr_torrent::VerifyMediator::on_verify_queued()
{
tr_logAddTraceTor(tor_, "Queued for verification");
@ -1621,7 +1658,7 @@ void tr_torrent::VerifyMediator::on_piece_checked(tr_piece_index_t const piece,
{
auto const had_piece = tor_->has_piece(piece);
if (has_piece || had_piece)
if (has_piece != had_piece)
{
tor_->set_has_piece(piece, has_piece);
tor_->set_dirty();
@ -1660,6 +1697,11 @@ void tr_torrent::VerifyMediator::on_verify_done(bool const aborted)
return;
}
for (tr_file_index_t file = 0, n_files = tor->file_count(); file < n_files; ++file)
{
tor->update_file_path(file, {});
}
tor->recheck_completeness();
if (tor->verify_done_callback_)
@ -1972,7 +2014,7 @@ bool tr_torrent::set_announce_list(tr_announce_list announce_list)
if (save_error.has_value())
{
error().set_local_error(fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't save '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", save_error.message()),
fmt::arg("error_code", save_error.code())));
@ -2025,7 +2067,7 @@ void tr_torrent::on_tracker_response(tr_tracker_event const* event)
tr_logAddWarnTor(
this,
fmt::format(
_("Tracker warning: '{warning}' ({url})"),
fmt::runtime(_("Tracker warning: '{warning}' ({url})")),
fmt::arg("warning", event->text),
fmt::arg("url", tr_urlTrackerLogName(event->announce_url))));
error_.set_tracker_warning(event->announce_url, event->text);
@ -2128,27 +2170,7 @@ void tr_torrent::on_file_completed(tr_file_index_t const file)
/* if the torrent's current filename isn't the same as the one in the
* metadata -- for example, if it had the ".part" suffix appended to
* it until now -- then rename it to match the one in the metadata */
if (auto found = find_file(file); found)
{
if (auto const& file_subpath = this->file_subpath(file); file_subpath != found->subpath())
{
auto const& oldpath = found->filename();
auto const newpath = tr_pathbuf{ found->base(), '/', file_subpath };
auto error = tr_error{};
if (!tr_sys_path_rename(oldpath, newpath, &error))
{
tr_logAddErrorTor(
this,
fmt::format(
_("Couldn't move '{old_path}' to '{path}': {error} ({error_code})"),
fmt::arg("old_path", oldpath),
fmt::arg("path", newpath),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
}
}
}
update_file_path(file, true);
}
void tr_torrent::on_piece_completed(tr_piece_index_t const piece)
@ -2161,7 +2183,7 @@ void tr_torrent::on_piece_completed(tr_piece_index_t const piece)
// if this piece completes any file, invoke the fileCompleted func for it
for (auto [file, file_end] = fpm_.file_span_for_piece(piece); file < file_end; ++file)
{
if (completion_.has_blocks(block_span_for_file(file)))
if (has_file(file))
{
on_file_completed(file);
}
@ -2544,7 +2566,7 @@ tr_bitfield const& tr_torrent::ResumeHelper::checked_pieces() const noexcept
return tor_.checked_pieces_;
}
void tr_torrent::ResumeHelper::load_checked_pieces(tr_bitfield const& checked, time_t const* mtimes /*fileCount()*/)
void tr_torrent::ResumeHelper::load_checked_pieces(tr_bitfield const& checked, time_t const* mtimes /*file_count()*/)
{
TR_ASSERT(std::size(checked) == tor_.piece_count());
tor_.checked_pieces_ = checked;

View File

@ -70,7 +70,7 @@ struct tr_torrent
class ResumeHelper
{
public:
void load_checked_pieces(tr_bitfield const& checked, time_t const* mtimes /*fileCount()*/);
void load_checked_pieces(tr_bitfield const& checked, time_t const* mtimes /*file_count()*/);
void load_blocks(tr_bitfield blocks);
void load_date_added(time_t when) noexcept;
void load_date_done(time_t when) noexcept;
@ -325,6 +325,11 @@ struct tr_torrent
return completion_.has_none();
}
[[nodiscard]] auto has_file(tr_file_index_t file) const
{
return completion_.has_blocks(block_span_for_file(file));
}
[[nodiscard]] auto has_piece(tr_piece_index_t piece) const
{
return completion_.has_piece(piece);
@ -372,7 +377,7 @@ struct tr_torrent
void amount_done_bins(float* tab, int n_tabs) const
{
return completion_.amount_done(tab, n_tabs);
completion_.amount_done(tab, n_tabs);
}
/// FILE <-> PIECE
@ -827,7 +832,7 @@ struct tr_torrent
if (auto const latest = std::max(date_started_, date_active_); latest != 0)
{
TR_ASSERT(now >= latest);
return now - latest;
return static_cast<size_t>(std::max(now - latest, time_t{ 0 }));
}
}
@ -887,6 +892,8 @@ struct tr_torrent
void do_idle_work()
{
do_magnet_idle_work();
if (needs_completeness_check_)
{
needs_completeness_check_ = false;
@ -1244,8 +1251,11 @@ private:
void create_empty_files() const;
void recheck_completeness();
void do_magnet_idle_work();
[[nodiscard]] bool use_new_metainfo(tr_error* error);
void update_file_path(tr_file_index_t file, std::optional<bool> has_file) const;
void set_location_in_session_thread(std::string_view path, bool move_from_old_path, int volatile* setme_state);
void rename_path_in_session_thread(

View File

@ -549,7 +549,7 @@ private:
if (line_stream.bad() || std::empty(addrstr))
{
tr_logAddWarn(fmt::format(
_("Couldn't parse '{filename}' line: '{line}'"),
fmt::runtime(_("Couldn't parse '{filename}' line: '{line}'")),
fmt::arg("filename", filename),
fmt::arg("line", line)));
}
@ -573,7 +573,7 @@ private:
if (int const rc = getaddrinfo(name, port_str.c_str(), &hints, &info); rc != 0)
{
tr_logAddWarn(fmt::format(
_("Couldn't look up '{address}:{port}': {error} ({error_code})"),
fmt::runtime(_("Couldn't look up '{address}:{port}': {error} ({error_code})")),
fmt::arg("address", name),
fmt::arg("port", port_in.host()),
fmt::arg("error", gai_strerror(rc)),

View File

@ -9,18 +9,18 @@
#include <cstdint> // uint16_t
#include <cstring>
#include <ctime> // time_t
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#ifdef _WIN32
#include <ws2tcpip.h>
#else
#include <sys/socket.h> /* socket(), bind() */
#include <netinet/in.h> /* sockaddr_in */
#include <sys/socket.h> /* socket(), bind() */
#endif
#include <event2/event.h>
@ -47,6 +47,8 @@ using namespace std::literals;
namespace
{
using ipp_t = std::underlying_type_t<tr_address_type>;
// opaque value, allowing the sending client to filter out its
// own announces if it receives them via multicast loopback
auto makeCookie()
@ -62,8 +64,8 @@ auto makeCookie()
return std::string{ std::data(buf), std::size(buf) };
}
constexpr char const* const McastGroup = "239.192.152.143"; /**<LPD multicast group */
auto constexpr McastPort = tr_port::from_host(6771); /**<LPD source and destination UPD port */
auto constexpr McastSockAddr = std::array{ "239.192.152.143:6771"sv, "[ff15::efc0:988f]:6771"sv };
static_assert(std::size(McastSockAddr) == NUM_TR_AF_INET_TYPES);
/*
* A LSD announce is formatted as follows:
@ -84,14 +86,23 @@ auto constexpr McastPort = tr_port::from_host(6771); /**<LPD source and destinat
* multiple infohashes the packet length should not exceed 1400
* bytes to avoid MTU/fragmentation problems.
*/
auto makeAnnounceMsg(std::string_view cookie, tr_port port, std::vector<std::string_view> const& info_hash_strings)
std::string makeAnnounceMsg(
tr_address_type ip_protocol,
std::string_view cookie,
tr_port port,
std::vector<std::string_view> const& info_hash_strings)
{
TR_ASSERT(tr_address::is_valid(ip_protocol));
if (!tr_address::is_valid(ip_protocol))
{
return {};
}
auto ret = fmt::format(
"BT-SEARCH * HTTP/1.1\r\n"
"Host: {:s}:{:d}\r\n"
"Host: {:s}\r\n"
"Port: {:d}\r\n",
McastGroup,
McastPort.host(),
McastSockAddr[ip_protocol],
port.host());
for (auto const& info_hash : info_hash_strings)
@ -230,11 +241,17 @@ public:
~tr_lpd_impl() override
{
event_.reset();
if (mcast_socket_ != TR_BAD_SOCKET)
for (auto& event : events_)
{
tr_net_close_socket(mcast_socket_);
event.reset();
}
for (auto const sock : mcast_sockets_)
{
if (sock != TR_BAD_SOCKET)
{
tr_net_close_socket(sock);
}
}
tr_logAddTrace("Done uninitialising Local Peer Discovery");
@ -243,19 +260,34 @@ public:
private:
bool init(struct event_base* event_base)
{
if (initImpl(event_base))
ipp_t n_success = NUM_TR_AF_INET_TYPES;
if (!initImpl<TR_AF_INET>(event_base))
{
return true;
auto const err = sockerrno;
tr_net_close_socket(mcast_sockets_[TR_AF_INET]);
mcast_sockets_[TR_AF_INET] = TR_BAD_SOCKET;
tr_logAddWarn(fmt::format(
_("Couldn't initialize {ip_protocol} LPD: {error} ({error_code})"),
fmt::arg("ip_protocol", tr_ip_protocol_to_sv(TR_AF_INET)),
fmt::arg("error", tr_strerror(err)),
fmt::arg("error_code", err)));
--n_success;
}
auto const err = sockerrno;
tr_net_close_socket(mcast_socket_);
mcast_socket_ = TR_BAD_SOCKET;
tr_logAddWarn(fmt::format(
_("Couldn't initialize LPD: {error} ({error_code})"),
fmt::arg("error", tr_strerror(err)),
fmt::arg("error_code", err)));
return false;
if (!initImpl<TR_AF_INET6>(event_base))
{
auto const err = sockerrno;
tr_net_close_socket(mcast_sockets_[TR_AF_INET6]);
mcast_sockets_[TR_AF_INET6] = TR_BAD_SOCKET;
tr_logAddWarn(fmt::format(
fmt::runtime(_("Couldn't initialize {ip_protocol} LPD: {error} ({error_code})")),
fmt::arg("ip_protocol", tr_ip_protocol_to_sv(TR_AF_INET6)),
fmt::arg("error", tr_strerror(err)),
fmt::arg("error_code", err)));
--n_success;
}
return n_success != 0U;
}
/**
@ -263,76 +295,80 @@ private:
*
* For the most part, this means setting up an appropriately configured multicast socket
* and event-based message handling.
*
* @remark Since the LPD service does not use another protocol family yet, this code is
* IPv4 only for the time being.
*/
template<tr_address_type ip_protocol>
bool initImpl(struct event_base* event_base)
{
auto const opt_on = 1;
auto& sock = mcast_sockets_[ip_protocol];
static_assert(AnnounceScope > 0);
static_assert(tr_address::is_valid(ip_protocol));
tr_logAddDebug("Initialising Local Peer Discovery");
tr_logAddDebug(fmt::format("Initialising {} Local Peer Discovery", tr_ip_protocol_to_sv(ip_protocol)));
/* setup datagram socket */
// setup datagram socket
sock = socket(tr_ip_protocol_to_af(ip_protocol), SOCK_DGRAM, 0);
if (sock == TR_BAD_SOCKET)
{
mcast_socket_ = socket(PF_INET, SOCK_DGRAM, 0);
return false;
}
if (mcast_socket_ == TR_BAD_SOCKET)
{
return false;
}
if (evutil_make_socket_nonblocking(sock) == -1)
{
return false;
}
if (evutil_make_socket_nonblocking(mcast_socket_) == -1)
{
return false;
}
if (setsockopt(mcast_socket_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) ==
-1)
{
return false;
}
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) == -1)
{
return false;
}
#if HAVE_SO_REUSEPORT
if (setsockopt(mcast_socket_, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) ==
if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) == -1)
{
return false;
}
#endif
if constexpr (ip_protocol == TR_AF_INET6)
{
// must be done before binding on Linux
if (evutil_make_listen_socket_ipv6only(sock) == -1)
{
return false;
}
}
auto const mcast_sockaddr = tr_socket_address::from_string(McastSockAddr[ip_protocol]);
TR_ASSERT(mcast_sockaddr);
auto const [mcast_ss, mcast_sslen] = mcast_sockaddr->to_sockaddr();
auto const [bind_ss, bind_sslen] = tr_socket_address::to_sockaddr(tr_address::any(ip_protocol), mcast_sockaddr->port());
if (bind(sock, reinterpret_cast<sockaddr const*>(&bind_ss), bind_sslen) == -1)
{
return false;
}
if constexpr (ip_protocol == TR_AF_INET)
{
std::memcpy(&mcast_addr_, &mcast_ss, mcast_sslen);
// we want to join that LPD multicast group
struct ip_mreq mcast_req = {};
mcast_req.imr_multiaddr = mcast_addr_.sin_addr;
mcast_req.imr_interface = mediator_.bind_address(ip_protocol).addr.addr4;
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast<char const*>(&mcast_req), sizeof(mcast_req)) ==
-1)
{
return false;
}
#endif
auto const [bind_ss, bind_sslen] = tr_socket_address::to_sockaddr(mediator_.bind_address(TR_AF_INET), McastPort);
if (bind(mcast_socket_, reinterpret_cast<sockaddr const*>(&bind_ss), bind_sslen) == -1)
{
return false;
}
auto const mcast_addr = tr_address::from_string(McastGroup);
TR_ASSERT(mcast_addr);
auto const [mcast_ss, mcast_sslen] = tr_socket_address::to_sockaddr(*mcast_addr, McastPort);
std::memcpy(&mcast_addr_, &mcast_ss, mcast_sslen);
/* we want to join that LPD multicast group */
ip_mreq mcast_req = {};
mcast_req.imr_multiaddr = mcast_addr_.sin_addr;
mcast_req.imr_interface = reinterpret_cast<sockaddr_in const*>(&bind_ss)->sin_addr;
// configure outbound multicast TTL
if (setsockopt(
mcast_socket_,
IPPROTO_IP,
IP_ADD_MEMBERSHIP,
reinterpret_cast<char const*>(&mcast_req),
sizeof(mcast_req)) == -1)
{
return false;
}
/* configure outbound multicast TTL */
if (setsockopt(
mcast_socket_,
sock,
IPPROTO_IP,
IP_MULTICAST_TTL,
reinterpret_cast<char const*>(&AnnounceScope),
@ -340,12 +376,71 @@ private:
{
return false;
}
if (setsockopt(
sock,
IPPROTO_IP,
IP_MULTICAST_IF,
reinterpret_cast<char const*>(&mcast_req.imr_interface),
sizeof(mcast_req.imr_interface)) == -1)
{
return false;
}
// needed to announce to BT clients on the same interface
if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) == -1)
{
return false;
}
}
else // TR_AF_INET6
{
std::memcpy(&mcast6_addr_, &mcast_ss, mcast_sslen);
// we want to join that LPD multicast group
struct ipv6_mreq mcast_req = {};
mcast_req.ipv6mr_multiaddr = mcast6_addr_.sin6_addr;
mcast_req.ipv6mr_interface = mediator_.bind_address(ip_protocol).to_interface_index().value_or(0);
if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, reinterpret_cast<char const*>(&mcast_req), sizeof(mcast_req)) ==
-1)
{
return false;
}
// configure outbound multicast TTL
if (setsockopt(
sock,
IPPROTO_IPV6,
IPV6_MULTICAST_HOPS,
reinterpret_cast<char const*>(&AnnounceScope),
sizeof(AnnounceScope)) == -1)
{
return false;
}
if (setsockopt(
sock,
IPPROTO_IPV6,
IPV6_MULTICAST_IF,
reinterpret_cast<char const*>(&mcast_req.ipv6mr_interface),
sizeof(mcast_req.ipv6mr_interface)) == -1)
{
return false;
}
// needed to announce to BT clients on the same interface
if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) ==
-1)
{
return false;
}
}
event_.reset(event_new(event_base, mcast_socket_, EV_READ | EV_PERSIST, event_callback, this));
event_add(event_.get(), nullptr);
events_[ip_protocol].reset(event_new(event_base, sock, EV_READ | EV_PERSIST, event_callback<ip_protocol>, this));
event_add(events_[ip_protocol].get(), nullptr);
tr_logAddDebug("Local Peer Discovery initialised");
tr_logAddDebug(fmt::format("{} Local Peer Discovery initialised", tr_ip_protocol_to_sv(ip_protocol)));
return true;
}
@ -354,27 +449,34 @@ private:
* @brief Processing of timeout notifications and incoming data on the socket
* @note maximum rate of read events is limited according to @a lpd_maxAnnounceCap
* @see DoS */
template<tr_address_type ip_protocol>
static void event_callback(evutil_socket_t /*s*/, short type, void* vself)
{
if ((type & EV_READ) != 0)
{
static_cast<tr_lpd_impl*>(vself)->onCanRead();
static_cast<tr_lpd_impl*>(vself)->onCanRead(ip_protocol);
}
}
void onCanRead()
void onCanRead(tr_address_type ip_protocol)
{
TR_ASSERT(tr_address::is_valid(ip_protocol));
if (!tr_address::is_valid(ip_protocol))
{
return;
}
if (!mediator_.allowsLPD())
{
return;
}
// process announcement from foreign peer
struct sockaddr_in foreign_addr = {};
struct sockaddr_storage foreign_addr = {};
auto addr_len = socklen_t{ sizeof(foreign_addr) };
auto foreign_msg = std::array<char, MaxDatagramLength>{};
auto const res = recvfrom(
mcast_socket_,
mcast_sockets_[ip_protocol],
std::data(foreign_msg),
MaxDatagramLength,
0,
@ -386,6 +488,7 @@ private:
{
return;
}
TR_ASSERT(tr_af_to_ip_protocol(foreign_addr.ss_family) == ip_protocol);
// If it doesn't look like a BEP14 message, discard it
auto const msg = std::string_view{ std::data(foreign_msg), static_cast<size_t>(res) };
@ -410,10 +513,14 @@ private:
return;
}
auto [peer_addr, compact] = tr_address::from_compact_ipv4(reinterpret_cast<std::byte*>(&foreign_addr.sin_addr));
auto peer_sockaddr = tr_socket_address::from_sockaddr(reinterpret_cast<sockaddr*>(&foreign_addr));
if (!peer_sockaddr)
{
return;
}
for (auto const& hash_string : parsed->info_hash_strings)
{
if (!mediator_.onPeerFound(hash_string, peer_addr, parsed->port))
if (!mediator_.onPeerFound(hash_string, peer_sockaddr->address(), parsed->port))
{
tr_logAddDebug(fmt::format("Cannot serve torrent #{:s}", hash_string));
}
@ -462,19 +569,30 @@ private:
std::sort(std::begin(torrents), std::end(torrents), TorrentComparator);
// cram in as many as will fit in a message
auto const baseline_size = std::size(makeAnnounceMsg(cookie_, mediator_.port(), {}));
auto const size_with_one = std::size(makeAnnounceMsg(cookie_, mediator_.port(), { torrents.front().info_hash_str }));
auto const size_per_hash = size_with_one - baseline_size;
auto baseline_size = size_t{};
for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
{
baseline_size = std::max(
baseline_size,
std::size(makeAnnounceMsg(static_cast<tr_address_type>(ipp), cookie_, mediator_.port(), {})));
}
auto const size_per_hash = std::size(torrents.front().info_hash_str);
auto const max_torrents_per_announce = (MaxDatagramLength - baseline_size) / size_per_hash;
auto const torrents_this_announce = std::min(std::size(torrents), max_torrents_per_announce);
auto info_hash_strings = std::vector<std::string_view>{};
info_hash_strings.resize(std::min(std::size(torrents), max_torrents_per_announce));
info_hash_strings.reserve(torrents_this_announce);
std::transform(
std::begin(torrents),
std::begin(torrents) + std::size(info_hash_strings),
std::begin(info_hash_strings),
std::begin(torrents) + torrents_this_announce,
std::back_inserter(info_hash_strings),
[](auto const& tor) { return tor.info_hash_str; });
if (!sendAnnounce(info_hash_strings))
auto success = false;
for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
{
success |= sendAnnounce(static_cast<tr_address_type>(ipp), info_hash_strings);
}
if (!success)
{
return;
}
@ -508,28 +626,40 @@ private:
* matter). A listening client on the same network might react by adding us to his
* peer pool for torrent t.
*/
bool sendAnnounce(std::vector<std::string_view> const& info_hash_strings)
bool sendAnnounce(tr_address_type ip_protocol, std::vector<std::string_view> const& info_hash_strings)
{
auto const announce = makeAnnounceMsg(cookie_, mediator_.port(), info_hash_strings);
TR_ASSERT(tr_address::is_valid(ip_protocol));
if (!tr_address::is_valid(ip_protocol))
{
return false;
}
if (mcast_sockets_[ip_protocol] == TR_BAD_SOCKET)
{
return true;
}
auto const announce = makeAnnounceMsg(ip_protocol, cookie_, mediator_.port(), info_hash_strings);
TR_ASSERT(std::size(announce) <= MaxDatagramLength);
auto const res = sendto(
mcast_socket_,
mcast_sockets_[ip_protocol],
std::data(announce),
std::size(announce),
0,
reinterpret_cast<sockaddr const*>(&mcast_addr_),
sizeof(mcast_addr_));
auto const sent = res == static_cast<int>(std::size(announce));
return sent;
ip_protocol == TR_AF_INET ? reinterpret_cast<sockaddr const*>(&mcast_addr_) :
reinterpret_cast<sockaddr const*>(&mcast6_addr_),
ip_protocol == TR_AF_INET ? sizeof(mcast_addr_) : sizeof(mcast6_addr_));
return res == static_cast<int>(std::size(announce));
}
std::string const cookie_ = makeCookie();
Mediator& mediator_;
tr_socket_t mcast_socket_ = TR_BAD_SOCKET; /**multicast socket */
libtransmission::evhelpers::event_unique_ptr event_;
std::array<tr_socket_t, NUM_TR_AF_INET_TYPES> mcast_sockets_ = { TR_BAD_SOCKET, TR_BAD_SOCKET }; // multicast sockets
std::array<libtransmission::evhelpers::event_unique_ptr, NUM_TR_AF_INET_TYPES> events_;
static auto constexpr MaxDatagramLength = size_t{ 1400 };
sockaddr_in mcast_addr_ = {}; /**<initialized from the above constants in init() */
sockaddr_in mcast_addr_ = {}; // initialized from the above constants in init()
sockaddr_in6 mcast6_addr_ = {}; // initialized from the above constants in init()
// BEP14: "To avoid causing multicast storms on large networks a
// client should send no more than 1 announce per minute."
@ -546,12 +676,11 @@ private:
static auto constexpr MaxIncomingPerSecond = 10;
static auto constexpr MaxIncomingPerUpkeep = std::chrono::duration_cast<std::chrono::seconds>(DosInterval).count() *
MaxIncomingPerSecond;
// @brief throw away messages after this number exceeds MaxIncomingPerUpkeep
size_t messages_received_since_upkeep_ = 0U;
size_t messages_received_since_upkeep_ = 0U; // throw away messages after this number exceeds MaxIncomingPerUpkeep
static auto constexpr TorrentAnnounceIntervalSec = time_t{ 240U }; // how frequently to reannounce the same torrent
static auto constexpr TtlSameSubnet = 1;
static auto constexpr AnnounceScope = int{ TtlSameSubnet }; /**<the maximum scope for LPD datagrams */
static auto constexpr AnnounceScope = int{ TtlSameSubnet }; // the maximum scope for LPD datagrams
};
std::unique_ptr<tr_lpd> tr_lpd::create(Mediator& mediator, struct event_base* event_base)

View File

@ -124,7 +124,7 @@ public:
}
template<typename ContiguousRange>
[[nodiscard]] constexpr auto operator==(ContiguousRange const& x) const noexcept
[[nodiscard]] constexpr bool operator==(ContiguousRange const& x) const noexcept
{
return sv() == x;
}

View File

@ -126,7 +126,7 @@ void event_callback(evutil_socket_t s, [[maybe_unused]] short type, void* vsessi
}
else if (n_read >= 8 && buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] <= 3)
{
if (!session->announcer_udp_->handle_message(std::data(buf), n_read))
if (!session->announcer_udp_->handle_message(std::data(buf), n_read, from_sa, fromlen))
{
tr_logAddTrace("Couldn't parse UDP tracker packet.");
}
@ -168,7 +168,7 @@ tr_session::tr_udp_core::tr_udp_core(tr_session& session, tr_port udp_port)
{
auto const error_code = errno;
tr_logAddWarn(fmt::format(
_("Couldn't make IPv4 socket non-blocking {address}: {error} ({error_code})"),
fmt::runtime(_("Couldn't make IPv4 socket non-blocking {address}: {error} ({error_code})")),
fmt::arg("address", tr_socket_address::display_name(addr, udp_port_)),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
@ -179,7 +179,7 @@ tr_session::tr_udp_core::tr_udp_core(tr_session& session, tr_port udp_port)
{
auto const error_code = errno;
tr_logAddWarn(fmt::format(
_("Couldn't bind IPv4 socket {address}: {error} ({error_code})"),
fmt::runtime(_("Couldn't bind IPv4 socket {address}: {error} ({error_code})")),
fmt::arg("address", tr_socket_address::display_name(addr, udp_port_)),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
@ -213,7 +213,7 @@ tr_session::tr_udp_core::tr_udp_core(tr_session& session, tr_port udp_port)
{
auto const error_code = errno;
tr_logAddWarn(fmt::format(
_("Couldn't make IPv6 socket non-blocking {address}: {error} ({error_code})"),
fmt::runtime(_("Couldn't make IPv6 socket non-blocking {address}: {error} ({error_code})")),
fmt::arg("address", tr_socket_address::display_name(addr, udp_port_)),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
@ -224,7 +224,7 @@ tr_session::tr_udp_core::tr_udp_core(tr_session& session, tr_port udp_port)
{
auto const error_code = errno;
tr_logAddWarn(fmt::format(
_("Couldn't bind IPv6 socket {address}: {error} ({error_code})"),
fmt::runtime(_("Couldn't bind IPv6 socket {address}: {error} ({error_code})")),
fmt::arg("address", tr_socket_address::display_name(addr, udp_port_)),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));

View File

@ -18,7 +18,6 @@
#include <locale>
#include <memory>
#include <optional>
#include <set>
#include <stdexcept> // std::runtime_error
#include <string>
#include <string_view>
@ -122,7 +121,7 @@ bool tr_file_read(std::string_view filename, std::vector<char>& contents, tr_err
if (*error)
{
tr_logAddError(fmt::format(
_("Couldn't read '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't read '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", error->message()),
fmt::arg("error_code", error->code())));
@ -131,7 +130,7 @@ bool tr_file_read(std::string_view filename, std::vector<char>& contents, tr_err
if (!info || !info->isFile())
{
tr_logAddError(fmt::format(_("Couldn't read '{path}': Not a regular file"), fmt::arg("path", filename)));
tr_logAddError(fmt::format(fmt::runtime(_("Couldn't read '{path}': Not a regular file")), fmt::arg("path", filename)));
error->set(TR_ERROR_EISDIR, "Not a regular file"sv);
return false;
}
@ -141,7 +140,7 @@ bool tr_file_read(std::string_view filename, std::vector<char>& contents, tr_err
if (fd == TR_BAD_SYS_FILE)
{
tr_logAddError(fmt::format(
_("Couldn't read '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't read '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", error->message()),
fmt::arg("error_code", error->code())));
@ -152,7 +151,7 @@ bool tr_file_read(std::string_view filename, std::vector<char>& contents, tr_err
if (!tr_sys_file_read(fd, std::data(contents), info->size, nullptr, error))
{
tr_logAddError(fmt::format(
_("Couldn't read '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't read '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", error->message()),
fmt::arg("error_code", error->code())));
@ -606,7 +605,7 @@ bool tr_file_move(std::string_view oldpath_in, std::string_view newpath_in, tr_e
if (auto log_error = tr_error{}; !tr_sys_path_remove(oldpath, &log_error))
{
tr_logAddError(fmt::format(
_("Couldn't remove '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't remove '{path}': {error} ({error_code})")),
fmt::arg("path", oldpath),
fmt::arg("error", log_error.message()),
fmt::arg("error_code", log_error.code())));

View File

@ -233,7 +233,7 @@ std::optional<tr_variant> tr_variant_serde::parse_json(std::string_view input)
error_.set(
EILSEQ,
fmt::format(
_("Couldn't parse JSON at position {position} '{text}': {error} ({error_code})"),
fmt::runtime(_("Couldn't parse JSON at position {position} '{text}': {error} ({error_code})")),
fmt::arg("position", err_offset),
fmt::arg("text", std::string_view{ begin + err_offset, std::min(size_t{ 16U }, size - err_offset) }),
fmt::arg("error", rapidjson::GetParseError_En(err_code)),

View File

@ -866,7 +866,7 @@ bool tr_variant_serde::to_file(tr_variant const& var, std::string_view filename)
if (error_)
{
tr_logAddError(fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't save '{path}': {error} ({error_code})")),
fmt::arg("path", filename),
fmt::arg("error", error_.message()),
fmt::arg("error_code", error_.code())));

View File

@ -96,5 +96,5 @@ private:
std::atomic<bool> stop_current_ = false;
std::condition_variable stop_current_cv_;
std::chrono::milliseconds sleep_per_seconds_during_verify_;
std::chrono::milliseconds sleep_per_seconds_during_verify_ = {};
};

View File

@ -83,7 +83,7 @@ private:
{
auto const error_code = errno;
tr_logAddError(fmt::format(
_("Couldn't watch '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't watch '{path}': {error} ({error_code})")),
fmt::arg("path", dirname()),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
@ -95,7 +95,7 @@ private:
{
auto const error_code = errno;
tr_logAddError(fmt::format(
_("Couldn't watch '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't watch '{path}': {error} ({error_code})")),
fmt::arg("path", dirname()),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
@ -107,7 +107,7 @@ private:
{
auto const error_code = errno;
tr_logAddError(fmt::format(
_("Couldn't watch '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't watch '{path}': {error} ({error_code})")),
fmt::arg("path", dirname()),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
@ -139,7 +139,7 @@ private:
{
auto const error_code = errno;
tr_logAddError(fmt::format(
_("Couldn't read event: {error} ({error_code})"),
fmt::runtime(_("Couldn't read event: {error} ({error_code})")),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
break;
@ -148,7 +148,7 @@ private:
if (nread != sizeof(ev))
{
tr_logAddError(fmt::format(
_("Couldn't read event: expected {expected_size}, got {actual_size}"),
fmt::runtime(_("Couldn't read event: expected {expected_size}, got {actual_size}")),
fmt::arg("expected_size", sizeof(ev)),
fmt::arg("actual_size", nread)));
break;
@ -165,7 +165,7 @@ private:
{
auto const error_code = errno;
tr_logAddError(fmt::format(
_("Couldn't read filename: {error} ({error_code})"),
fmt::runtime(_("Couldn't read filename: {error} ({error_code})")),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
break;
@ -174,7 +174,7 @@ private:
if (nread != ev.len)
{
tr_logAddError(fmt::format(
_("Couldn't read filename: expected {expected_size}, got {actual_size}"),
fmt::runtime(_("Couldn't read filename: expected {expected_size}, got {actual_size}")),
fmt::arg("expected_size", sizeof(ev)),
fmt::arg("actual_size", nread)));
break;

View File

@ -5,7 +5,6 @@
#include <cerrno> // for errno
#include <memory>
#include <string>
#include <utility>
#include <fcntl.h> // for open()

View File

@ -49,7 +49,7 @@ namespace
if (error && !TR_ERROR_IS_ENOENT(error.code()))
{
tr_logAddWarn(fmt::format(
_("Skipping '{path}': {error} ({error_code})"),
fmt::runtime(_("Skipping '{path}': {error} ({error_code})")),
fmt::arg("path", path),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));
@ -88,7 +88,7 @@ void BaseWatchdir::processFile(std::string_view basename)
if (now - info.first_kick_at > timeoutDuration())
{
tr_logAddWarn(fmt::format(_("Couldn't add torrent file '{path}'"), fmt::arg("path", basename)));
tr_logAddWarn(fmt::format(fmt::runtime(_("Couldn't add torrent file '{path}'")), fmt::arg("path", basename)));
pending_.erase(iter);
}
else
@ -115,7 +115,7 @@ void BaseWatchdir::scan()
if (error)
{
tr_logAddWarn(fmt::format(
_("Couldn't read '{path}': {error} ({error_code})"),
fmt::runtime(_("Couldn't read '{path}': {error} ({error_code})")),
fmt::arg("path", dirname()),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code())));

View File

@ -291,6 +291,19 @@ std::string_view getSiteName(std::string_view host)
return host;
}
// Not part of the RFC3986 standard, but included for convenience
// when using the result with API that does not accept IPv6 address
// strings that are wrapped in square brackets (e.g. inet_pton())
std::string_view getHostWoBrackets(std::string_view host)
{
if (tr_strv_starts_with(host, '['))
{
host.remove_prefix(1);
host.remove_suffix(1);
}
return host;
}
} // namespace
std::optional<tr_url_parsed_t> tr_urlParse(std::string_view url)
@ -363,6 +376,7 @@ std::optional<tr_url_parsed_t> tr_urlParse(std::string_view url)
{
parsed.host = tr_strv_sep(&remain, ':');
}
parsed.host_wo_brackets = getHostWoBrackets(parsed.host);
parsed.sitename = getSiteName(parsed.host);
parsed.port = parsePort(!std::empty(remain) ? remain : getPortForScheme(parsed.scheme));
}

View File

@ -31,6 +31,7 @@ struct tr_url_parsed_t
std::string_view scheme; // "http"
std::string_view authority; // "example.com:80"
std::string_view host; // "example.com"
std::string_view host_wo_brackets; // "example.com" ("[::1]" -> "::1")
std::string_view sitename; // "example"
std::string_view path; // /"over/there"
std::string_view query; // "name=ferret"

View File

@ -181,8 +181,9 @@ public:
if (curl_ssl_verify)
{
auto const* bundle = std::empty(curl_ca_bundle) ? "none" : curl_ca_bundle.c_str();
tr_logAddInfo(
fmt::format(_("Will verify tracker certs using envvar CURL_CA_BUNDLE: {bundle}"), fmt::arg("bundle", bundle)));
tr_logAddInfo(fmt::format(
fmt::runtime(_("Will verify tracker certs using envvar CURL_CA_BUNDLE: {bundle}")),
fmt::arg("bundle", bundle)));
tr_logAddInfo(_("NB: this only works if you built against libcurl with openssl or gnutls, NOT nss"));
tr_logAddInfo(_("NB: Invalid certs will appear as 'Could not connect to tracker' like many other errors"));
}
@ -460,7 +461,7 @@ public:
if (code != NoResponseCode && code != PartialContentResponseCode)
{
tr_logAddWarn(fmt::format(
_("Couldn't fetch '{url}': expected HTTP response code {expected_code}, got {actual_code}"),
fmt::runtime(_("Couldn't fetch '{url}': expected HTTP response code {expected_code}, got {actual_code}")),
fmt::arg("url", task->url()),
fmt::arg("expected_code", PartialContentResponseCode),
fmt::arg("actual_code", code)));

View File

@ -381,14 +381,14 @@ static void removeKeRangerRansomware()
[NSValueTransformer setValueTransformer:iconTransformer forName:@"ExpandedPathToIconTransformer"];
}
void onStartQueue(tr_session* /*session*/, tr_torrent* tor, void* vself)
void onStartQueue(tr_session* /*session*/, tr_torrent* /*tor*/, void* /*vself*/)
{
auto* controller = (__bridge Controller*)(vself);
auto const hashstr = @(tr_torrentView(tor).hash_string);
dispatch_async(dispatch_get_main_queue(), ^{
auto* const torrent = [controller torrentForHash:hashstr];
[torrent startQueue];
//posting asynchronously with coalescing to prevent stack overflow on lots of torrents changing state at the same time
[NSNotificationQueue.defaultQueue enqueueNotification:[NSNotification notificationWithName:@"UpdateTorrentsState" object:nil]
postingStyle:NSPostASAP
coalesceMask:NSNotificationCoalescingOnName
forModes:nil];
});
}
@ -812,8 +812,7 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool
[nc addObserver:self.fWindow selector:@selector(makeKeyWindow) name:@"MakeWindowKey" object:nil];
#warning rename
[nc addObserver:self selector:@selector(fullUpdateUI) name:@"UpdateQueue" object:nil];
[nc addObserver:self selector:@selector(fullUpdateUI) name:@"UpdateTorrentsState" object:nil];
[nc addObserver:self selector:@selector(applyFilter) name:@"ApplyFilter" object:nil];

View File

@ -463,7 +463,7 @@ typedef NS_ENUM(NSUInteger, FilePriorityMenuTag) { //
[FileRenameSheetController presentSheetForTorrent:torrent modalForWindow:self.fOutline.window completionHandler:^(BOOL didRename) {
if (didRename)
{
[NSNotificationCenter.defaultCenter postNotificationName:@"UpdateQueue" object:self];
[NSNotificationCenter.defaultCenter postNotificationName:@"UpdateTorrentsState" object:nil];
[NSNotificationCenter.defaultCenter postNotificationName:@"ResetInspector" object:self
userInfo:@{ @"Torrent" : torrent }];
}

View File

@ -323,7 +323,6 @@ static NSTimeInterval const kUpdateSeconds = 0.75;
}
}
#warning don't cut off end
- (CGFloat)tableView:(NSTableView*)tableView heightOfRow:(NSInteger)row
{
NSString* message = self.fDisplayedMessages[row][@"Message"];

View File

@ -879,7 +879,7 @@ static NSString* const kWebUIURLFormat = @"http://localhost:%ld/";
tr_sessionSetQueueEnabled(self.fHandle, TR_UP, [self.fDefaults boolForKey:@"QueueSeed"]);
//handle if any transfers switch from queued to paused
[NSNotificationCenter.defaultCenter postNotificationName:@"UpdateQueue" object:self];
[NSNotificationCenter.defaultCenter postNotificationName:@"UpdateTorrentsState" object:nil];
}
- (void)setQueueNumber:(id)sender

View File

@ -219,7 +219,7 @@ OSStatus GeneratePreviewForURL(void* /*thisInterface*/, QLPreviewRequestRef prev
FileTreeNode root{};
for (auto const& [path, size] : metainfo.files().sortedByPath())
for (auto const& [path, size] : metainfo.files().sorted_by_path())
{
FileTreeNode* curNode = &root;
size_t level = 0;

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