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
93 changed files with 1690 additions and 1130 deletions

View File

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

View File

@ -147,11 +147,6 @@ jobs:
libssl-dev \ libssl-dev \
ninja-build \ ninja-build \
npm 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 - name: Get Source
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
@ -226,7 +221,7 @@ jobs:
run: cmake -E chdir obj ctest -j $(nproc) --build-config Debug --output-on-failure run: cmake -E chdir obj ctest -j $(nproc) --build-config Debug --output-on-failure
clang-tidy-libtransmission: clang-tidy-libtransmission:
runs-on: ubuntu-22.04 runs-on: ubuntu-24.04
needs: [ what-to-make ] needs: [ what-to-make ]
if: ${{ needs.what-to-make.outputs.test-style == 'true' }} if: ${{ needs.what-to-make.outputs.test-style == 'true' }}
steps: steps:
@ -335,9 +330,9 @@ jobs:
name: binaries-${{ github.job }} name: binaries-${{ github.job }}
path: pfx/**/* path: pfx/**/*
# Only verify build support on old macOS # Only verify build support on older macOS and SDK
macos-11: macos-12:
runs-on: macos-11 runs-on: macos-12
needs: [ what-to-make ] needs: [ what-to-make ]
if: ${{ needs.what-to-make.outputs.make-mac == 'true' }} if: ${{ needs.what-to-make.outputs.make-mac == 'true' }}
steps: steps:
@ -354,6 +349,9 @@ jobs:
with: with:
path: src path: src
submodules: recursive submodules: recursive
- name: Set Xcode to 13.2.1
run: |
sudo xcode-select --switch /Applications/Xcode_13.2.1.app
- name: Configure - name: Configure
run: | run: |
cmake \ cmake \
@ -363,8 +361,9 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=pfx \ -DCMAKE_INSTALL_PREFIX=pfx \
-DCMAKE_OSX_ARCHITECTURES='x86_64' \ -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_MAC=${{ (needs.what-to-make.outputs.make-mac == 'true') && 'ON' || 'OFF' }} \
-DENABLE_QT=OFF \
-DENABLE_TESTS=OFF \ -DENABLE_TESTS=OFF \
-DENABLE_WERROR=ON \ -DENABLE_WERROR=ON \
-DRUN_CLANG_TIDY=OFF -DRUN_CLANG_TIDY=OFF
@ -613,7 +612,6 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=pfx \ -DCMAKE_INSTALL_PREFIX=pfx \
-DCMAKE_OSX_ARCHITECTURES='x86_64;arm64' \ -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_CLI=${{ (needs.what-to-make.outputs.make-cli == 'true') && 'ON' || 'OFF' }} \
-DENABLE_DAEMON=${{ (needs.what-to-make.outputs.make-daemon == 'true') && 'ON' || 'OFF' }} \ -DENABLE_DAEMON=${{ (needs.what-to-make.outputs.make-daemon == 'true') && 'ON' || 'OFF' }} \
-DENABLE_GTK=OFF \ -DENABLE_GTK=OFF \
@ -991,7 +989,7 @@ jobs:
run: | run: |
gradle build gradle build
ubuntu-24-04-from-tarball-cxx-23: ubuntu-24-04-cxx-23:
needs: [ make-source-tarball, what-to-make ] 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' }} 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 runs-on: ubuntu-24.04
@ -1028,11 +1026,10 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-qt == 'true' }} if: ${{ needs.what-to-make.outputs.make-qt == 'true' }}
run: sudo apt-get install -y --no-install-recommends qtbase5-dev libqt5svg5-dev qttools5-dev run: sudo apt-get install -y --no-install-recommends qtbase5-dev libqt5svg5-dev qttools5-dev
- name: Get Source - name: Get Source
uses: actions/download-artifact@v4 uses: actions/checkout@v4
with: with:
name: source-tarball submodules: recursive
- name: Extract Source path: src
run: mkdir src && tar xf transmission*.tar.* -C src --strip-components 1
- name: Configure - name: Configure
run: | run: |
cmake \ cmake \

View File

@ -530,17 +530,9 @@ if(NOT USE_SYSTEM_MINIUPNPC)
target_compile_definitions(miniupnpc::libminiupnpc target_compile_definitions(miniupnpc::libminiupnpc
INTERFACE INTERFACE
MINIUPNP_STATICLIB) MINIUPNP_STATICLIB)
set(MINIUPNPC_VERSION 2.2)
set(MINIUPNPC_API_VERSION 17)
endif() endif()
unset(TR_MINIUPNPC_LIBNAME) 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) add_subdirectory(${TR_THIRD_PARTY_SOURCE_DIR}/wildmat)
tr_add_external_auto_library(DHT dht dht 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 */; }; A20162C913DE48BF00E15488 /* receivedata.c in Sources */ = {isa = PBXBuildFile; fileRef = A20162C713DE48BF00E15488 /* receivedata.c */; };
A20162CA13DE48BF00E15488 /* receivedata.h in Headers */ = {isa = PBXBuildFile; fileRef = A20162C813DE48BF00E15488 /* receivedata.h */; }; A20162CA13DE48BF00E15488 /* receivedata.h in Headers */ = {isa = PBXBuildFile; fileRef = A20162C813DE48BF00E15488 /* receivedata.h */; };
A20162CD13DE497000E15488 /* portlistingparse.c in Sources */ = {isa = PBXBuildFile; fileRef = A20162CB13DE497000E15488 /* portlistingparse.c */; }; A20162CD13DE497000E15488 /* portlistingparse.c in Sources */ = {isa = PBXBuildFile; fileRef = A20162CB13DE497000E15488 /* portlistingparse.c */; };
A20162CE13DE497000E15488 /* portlistingparse.h in Headers */ = {isa = PBXBuildFile; fileRef = A20162CC13DE497000E15488 /* portlistingparse.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 */; }; 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 */; }; A2074F4C12BEA8CE00F70985 /* buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = A2074F4B12BEA8CE00F70985 /* buffer.c */; };
A2074F5912BEA8E000F70985 /* bufferevent_filter.c in Sources */ = {isa = PBXBuildFile; fileRef = A2074F5012BEA8E000F70985 /* bufferevent_filter.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 */; }; 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 */; }; A2FB701C0D95CAEA0001F331 /* GroupsController.mm in Sources */ = {isa = PBXBuildFile; fileRef = A2FB701B0D95CAEA0001F331 /* GroupsController.mm */; };
A47A7C87B8B57BE50DF0D410 /* torrent-files.cc in Sources */ = {isa = PBXBuildFile; fileRef = A47A7C87B8B57BE50DF0D411 /* torrent-files.cc */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; BE11835C0CE160C50002D0F3 /* minisoap.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183520CE160C50002D0F3 /* minisoap.h */; };
BE11835D0CE160C50002D0F3 /* upnpreplyparse.h in Headers */ = {isa = PBXBuildFile; fileRef = BE1183530CE160C50002D0F3 /* upnpreplyparse.h */; }; 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 = (Public, ); }; }; 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 = (Public, ); }; }; 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 */; }; 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 */; }; 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 */; }; 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 */; }; C11DEA161FCD31C0009E22B9 /* subprocess-posix.cc in Sources */ = {isa = PBXBuildFile; fileRef = C11DEA141FCD31C0009E22B9 /* subprocess-posix.cc */; };
C11DEA171FCD31C0009E22B9 /* subprocess.h in Headers */ = {isa = PBXBuildFile; fileRef = C11DEA151FCD31C0009E22B9 /* subprocess.h */; }; C11DEA171FCD31C0009E22B9 /* subprocess.h in Headers */ = {isa = PBXBuildFile; fileRef = C11DEA151FCD31C0009E22B9 /* subprocess.h */; };
C12F19791E1AE3C30005E93F /* upnperrors.c in Sources */ = {isa = PBXBuildFile; fileRef = C12F19771E1AE3C30005E93F /* upnperrors.c */; }; 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 */; }; C1305EBE186A13B100F03351 /* file.cc in Sources */ = {isa = PBXBuildFile; fileRef = C1305EB8186A134000F03351 /* file.cc */; };
C1425B361EE9C605001DB85F /* tr-assert.h in Headers */ = {isa = PBXBuildFile; fileRef = C1425B331EE9C5EA001DB85F /* tr-assert.h */; }; 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 */; }; 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 */; }; C1846BA3294F7A6800A98F30 /* wildmat.h in Headers */ = {isa = PBXBuildFile; fileRef = C1846B87294F781800A98F30 /* wildmat.h */; };
C1846BA9294F7B5A00A98F30 /* libwildmat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C1846B9E294F7A3400A98F30 /* libwildmat.a */; }; C1846BA9294F7B5A00A98F30 /* libwildmat.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C1846B9E294F7A3400A98F30 /* libwildmat.a */; };
C1BF7BA81F2A3CB7008E88A7 /* upnpdev.c in Sources */ = {isa = PBXBuildFile; fileRef = C1BF7BA71F2A3CB7008E88A7 /* upnpdev.c */; }; 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 */; }; 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 */; }; 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 */; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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; isa = PBXNativeTarget;
buildConfigurationList = BE11834C0CE160A80002D0F3 /* Build configuration list for PBXNativeTarget "miniupnp" */; buildConfigurationList = BE11834C0CE160A80002D0F3 /* Build configuration list for PBXNativeTarget "miniupnp" */;
buildPhases = ( buildPhases = (
A2305097100C0293003FDB0C /* ShellScript */, A2305097100C0293003FDB0C /* updateminiupnpcstrings */,
C12F197C1E1AE55A0005E93F /* ShellScript */, C12F197C1E1AE55A0005E93F /* symlinks */,
BE1183440CE160960002D0F3 /* Headers */, BE1183440CE160960002D0F3 /* Headers */,
BE1183450CE160960002D0F3 /* Sources */, BE1183450CE160960002D0F3 /* Sources */,
BE1183460CE160960002D0F3 /* Frameworks */, BE1183460CE160960002D0F3 /* Frameworks */,
@ -2909,7 +2909,8 @@
29B97313FDCFA39411CA2CEA /* Project object */ = { 29B97313FDCFA39411CA2CEA /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 1420; BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1530;
ORGANIZATIONNAME = "The Transmission Project"; ORGANIZATIONNAME = "The Transmission Project";
TargetAttributes = { TargetAttributes = {
8D1107260486CEB800E47090 = { 8D1107260486CEB800E47090 = {
@ -3040,16 +3041,19 @@
files = ( files = (
); );
inputPaths = ( inputPaths = (
"update-version-h.sh",
CMakeLists.txt,
); );
name = "Generate version file"; name = "Generate version file";
outputPaths = ( outputPaths = (
"$(SRCROOT)/libtransmission/version.h", "$(SRCROOT)/libtransmission/version.h",
"$(SRCROOT)/libtransmission/version.h.new",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "sh update-version-h.sh\n"; shellScript = "sh update-version-h.sh\n";
}; };
A2305097100C0293003FDB0C /* ShellScript */ = { A2305097100C0293003FDB0C /* updateminiupnpcstrings */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
@ -3059,6 +3063,7 @@
"third-party/miniupnp/miniupnpc/miniupnpcstrings.h.in", "third-party/miniupnp/miniupnpc/miniupnpcstrings.h.in",
"third-party/miniupnp/miniupnpc/updateminiupnpcstrings.sh", "third-party/miniupnp/miniupnpc/updateminiupnpcstrings.sh",
); );
name = updateminiupnpcstrings;
outputPaths = ( outputPaths = (
"third-party/miniupnp/miniupnpc/miniupnpcstrings.h", "third-party/miniupnp/miniupnpc/miniupnpcstrings.h",
); );
@ -3077,26 +3082,27 @@
); );
name = "Copy libevent headers"; name = "Copy libevent headers";
outputPaths = ( 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", "$(SRCROOT)/third-party/libevent/include/event2/event-config.h",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/bash; 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"; 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; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputPaths = ( inputPaths = (
); );
name = symlinks;
outputPaths = ( outputPaths = (
"third-party/miniupnpc/miniupnp", "third-party/miniupnp/miniupnpc/include/miniupnpc",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; 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 */ = { C12F197E1E1AE6D50005E93F /* ShellScript */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
@ -3952,7 +3958,6 @@
0053D3D30C86774200545606 /* Debug */ = { 0053D3D30C86774200545606 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO; CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GCC_WARN_UNUSED_FUNCTION = NO; GCC_WARN_UNUSED_FUNCTION = NO;
@ -3967,6 +3972,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -4005,7 +4011,7 @@
"third-party/libnatpmp/*.h", "third-party/libnatpmp/*.h",
"third-party/libpsl/include", "third-party/libpsl/include",
"third-party/libutp/include", "third-party/libutp/include",
"third-party/miniupnpc/*.h", "third-party/miniupnp/miniupnpc/include",
"third-party/utfcpp/source", "third-party/utfcpp/source",
"third-party/wide-integer", "third-party/wide-integer",
"third-party/wildmat", "third-party/wildmat",
@ -4022,9 +4028,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = macosx/Transmission.entitlements; CODE_SIGN_ENTITLEMENTS = macosx/Transmission.entitlements;
CODE_SIGN_IDENTITY = "-";
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = macosx; FRAMEWORK_SEARCH_PATHS = macosx;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
@ -4051,8 +4055,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -4071,8 +4073,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -4095,8 +4095,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -4143,6 +4141,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = c11; GCC_C_LANGUAGE_STANDARD = c11;
GCC_DYNAMIC_NO_PIC = YES; GCC_DYNAMIC_NO_PIC = YES;
GCC_ENABLE_PASCAL_STRINGS = NO; GCC_ENABLE_PASCAL_STRINGS = NO;
@ -4181,14 +4180,12 @@
OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)";
PRODUCT_BUNDLE_IDENTIFIER = org.m0k.transmission; PRODUCT_BUNDLE_IDENTIFIER = org.m0k.transmission;
SDKROOT = macosx; SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO;
}; };
name = Debug; name = Debug;
}; };
3C7A118E0D0B2EB800B5701F /* Release */ = { 3C7A118E0D0B2EB800B5701F /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
OTHER_CFLAGS = "-DENABLE_STRNATPMPERR"; OTHER_CFLAGS = "-DENABLE_STRNATPMPERR";
@ -4199,7 +4196,6 @@
3C7A118F0D0B2EB800B5701F /* Release - Debug */ = { 3C7A118F0D0B2EB800B5701F /* Release - Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
OTHER_CFLAGS = "-DENABLE_STRNATPMPERR"; OTHER_CFLAGS = "-DENABLE_STRNATPMPERR";
@ -4210,7 +4206,6 @@
3C7A11900D0B2EB800B5701F /* Debug */ = { 3C7A11900D0B2EB800B5701F /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
OTHER_CFLAGS = "-DENABLE_STRNATPMPERR"; OTHER_CFLAGS = "-DENABLE_STRNATPMPERR";
@ -4222,6 +4217,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -4260,7 +4256,7 @@
"third-party/libnatpmp/*.h", "third-party/libnatpmp/*.h",
"third-party/libpsl/include", "third-party/libpsl/include",
"third-party/libutp/include", "third-party/libutp/include",
"third-party/miniupnpc/*.h", "third-party/miniupnp/miniupnpc/include",
"third-party/utfcpp/source", "third-party/utfcpp/source",
"third-party/wide-integer", "third-party/wide-integer",
"third-party/wildmat", "third-party/wildmat",
@ -4276,8 +4272,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -4297,9 +4291,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = macosx/Transmission.entitlements; CODE_SIGN_ENTITLEMENTS = macosx/Transmission.entitlements;
CODE_SIGN_IDENTITY = "-";
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = macosx; FRAMEWORK_SEARCH_PATHS = macosx;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
@ -4353,6 +4345,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEPLOYMENT_POSTPROCESSING = YES; DEPLOYMENT_POSTPROCESSING = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = c11; GCC_C_LANGUAGE_STANDARD = c11;
GCC_DYNAMIC_NO_PIC = YES; GCC_DYNAMIC_NO_PIC = YES;
GCC_ENABLE_PASCAL_STRINGS = NO; GCC_ENABLE_PASCAL_STRINGS = NO;
@ -4397,7 +4390,6 @@
A22CFCBB0FC24F720009BD3E /* Debug */ = { A22CFCBB0FC24F720009BD3E /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO; CLANG_WARN_STRICT_PROTOTYPES = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
@ -4408,7 +4400,6 @@
A22CFCBC0FC24F720009BD3E /* Release - Debug */ = { A22CFCBC0FC24F720009BD3E /* Release - Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO; CLANG_WARN_STRICT_PROTOTYPES = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
@ -4419,7 +4410,6 @@
A22CFCBD0FC24F720009BD3E /* Release */ = { A22CFCBD0FC24F720009BD3E /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO; CLANG_WARN_STRICT_PROTOTYPES = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
@ -4460,6 +4450,7 @@
DEPLOYMENT_POSTPROCESSING = YES; DEPLOYMENT_POSTPROCESSING = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = c11; GCC_C_LANGUAGE_STANDARD = c11;
GCC_DYNAMIC_NO_PIC = YES; GCC_DYNAMIC_NO_PIC = YES;
GCC_ENABLE_PASCAL_STRINGS = NO; GCC_ENABLE_PASCAL_STRINGS = NO;
@ -4497,7 +4488,6 @@
OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)";
PRODUCT_BUNDLE_IDENTIFIER = org.m0k.transmission; PRODUCT_BUNDLE_IDENTIFIER = org.m0k.transmission;
SDKROOT = macosx; SDKROOT = macosx;
STRIP_INSTALLED_PRODUCT = NO;
}; };
name = "Release - Debug"; name = "Release - Debug";
}; };
@ -4506,9 +4496,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = macosx/Transmission.entitlements; CODE_SIGN_ENTITLEMENTS = macosx/Transmission.entitlements;
CODE_SIGN_IDENTITY = "-";
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = macosx; FRAMEWORK_SEARCH_PATHS = macosx;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
@ -4535,8 +4523,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -4555,6 +4541,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -4593,7 +4580,7 @@
"third-party/libnatpmp/*.h", "third-party/libnatpmp/*.h",
"third-party/libpsl/include", "third-party/libpsl/include",
"third-party/libutp/include", "third-party/libutp/include",
"third-party/miniupnpc/*.h", "third-party/miniupnp/miniupnpc/include",
"third-party/utfcpp/source", "third-party/utfcpp/source",
"third-party/wide-integer", "third-party/wide-integer",
"third-party/wildmat", "third-party/wildmat",
@ -4609,8 +4596,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -4633,8 +4618,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -4652,7 +4635,6 @@
A250CFF20CDA19680068B4B6 /* Release - Debug */ = { A250CFF20CDA19680068B4B6 /* Release - Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO; CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GCC_WARN_UNUSED_FUNCTION = NO; GCC_WARN_UNUSED_FUNCTION = NO;
@ -4666,7 +4648,6 @@
A2E384CF130DFB1D001F501B /* Debug */ = { A2E384CF130DFB1D001F501B /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_PREPROCESSOR_DEFINITIONS = POSIX; GCC_PREPROCESSOR_DEFINITIONS = POSIX;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
@ -4677,7 +4658,6 @@
A2E384D0130DFB1D001F501B /* Release - Debug */ = { A2E384D0130DFB1D001F501B /* Release - Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_PREPROCESSOR_DEFINITIONS = POSIX; GCC_PREPROCESSOR_DEFINITIONS = POSIX;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
@ -4688,7 +4668,6 @@
A2E384D1130DFB1D001F501B /* Release */ = { A2E384D1130DFB1D001F501B /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
POSIX, POSIX,
NS_BLOCK_ASSERTIONS, NS_BLOCK_ASSERTIONS,
@ -4703,7 +4682,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
GCC_PREFIX_HEADER = "macosx/QuickLookPlugin/QuickLookPlugin-Prefix.pch"; GCC_PREFIX_HEADER = "macosx/QuickLookPlugin/QuickLookPlugin-Prefix.pch";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -4731,7 +4709,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
GCC_PREFIX_HEADER = "macosx/QuickLookPlugin/QuickLookPlugin-Prefix.pch"; GCC_PREFIX_HEADER = "macosx/QuickLookPlugin/QuickLookPlugin-Prefix.pch";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -4759,7 +4736,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = YES;
GCC_PREFIX_HEADER = "macosx/QuickLookPlugin/QuickLookPlugin-Prefix.pch"; GCC_PREFIX_HEADER = "macosx/QuickLookPlugin/QuickLookPlugin-Prefix.pch";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -4786,7 +4762,6 @@
BE1183490CE160960002D0F3 /* Release */ = { BE1183490CE160960002D0F3 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = miniupnp; PRODUCT_NAME = miniupnp;
@ -4796,7 +4771,6 @@
BE11834A0CE160960002D0F3 /* Release - Debug */ = { BE11834A0CE160960002D0F3 /* Release - Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = miniupnp; PRODUCT_NAME = miniupnp;
@ -4806,7 +4780,6 @@
BE11834B0CE160960002D0F3 /* Debug */ = { BE11834B0CE160960002D0F3 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = miniupnp; PRODUCT_NAME = miniupnp;
@ -4816,7 +4789,6 @@
BE75C34B0C729EB600DBEFE0 /* Release */ = { BE75C34B0C729EB600DBEFE0 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO; CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GCC_WARN_UNUSED_FUNCTION = NO; GCC_WARN_UNUSED_FUNCTION = NO;
@ -4831,8 +4803,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -4855,8 +4825,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -4874,7 +4842,6 @@
C1639A701A55F4D600E42033 /* Debug */ = { C1639A701A55F4D600E42033 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -4884,7 +4851,6 @@
C1639A711A55F4D600E42033 /* Release - Debug */ = { C1639A711A55F4D600E42033 /* Release - Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -4894,7 +4860,6 @@
C1639A721A55F4D600E42033 /* Release */ = { C1639A721A55F4D600E42033 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -4904,7 +4869,6 @@
C1846B9B294F7A3400A98F30 /* Debug */ = { C1846B9B294F7A3400A98F30 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = wildmat; PRODUCT_NAME = wildmat;
@ -4914,7 +4878,6 @@
C1846B9C294F7A3400A98F30 /* Release - Debug */ = { C1846B9C294F7A3400A98F30 /* Release - Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = wildmat; PRODUCT_NAME = wildmat;
@ -4924,7 +4887,6 @@
C1846B9D294F7A3400A98F30 /* Release */ = { C1846B9D294F7A3400A98F30 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = wildmat; PRODUCT_NAME = wildmat;
@ -4934,7 +4896,6 @@
C3CEBBA627949CA000683BE0 /* Debug */ = { C3CEBBA627949CA000683BE0 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO; CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
@ -4946,7 +4907,6 @@
C3CEBBA727949CA000683BE0 /* Release - Debug */ = { C3CEBBA727949CA000683BE0 /* Release - Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO; CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
@ -4958,7 +4918,6 @@
C3CEBBA827949CA000683BE0 /* Release */ = { C3CEBBA827949CA000683BE0 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_UNREACHABLE_CODE = NO; CLANG_WARN_UNREACHABLE_CODE = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES; GENERATE_MASTER_OBJECT_FILE = YES;
@ -5016,8 +4975,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -5036,8 +4993,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -5056,8 +5011,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -5076,8 +5029,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -5096,8 +5047,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -5116,8 +5065,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -5136,8 +5083,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -5156,8 +5101,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,
@ -5176,8 +5119,6 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_OBJC_ARC = NO;
CODE_SIGN_IDENTITY = "-";
HEADER_SEARCH_PATHS = ( HEADER_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
., .,

View File

@ -167,8 +167,8 @@ void onTorrentFileDownloaded(tr_web::FetchResponse const& response)
{ {
if (c == 'g') if (c == 'g')
{ {
tr_optind = ind;
return my_optarg; 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 libminiupnpc
HINTS ${_MINIUPNPC_LIBDIR}) 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_INCLUDE_DIRS ${MINIUPNPC_INCLUDE_DIR})
set(MINIUPNPC_LIBRARIES ${MINIUPNPC_LIBRARY}) set(MINIUPNPC_LIBRARIES ${MINIUPNPC_LIBRARY})
@ -123,9 +29,7 @@ include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(MINIUPNPC find_package_handle_standard_args(MINIUPNPC
REQUIRED_VARS REQUIRED_VARS
MINIUPNPC_LIBRARY MINIUPNPC_LIBRARY
MINIUPNPC_INCLUDE_DIR MINIUPNPC_INCLUDE_DIR)
MINIUPNPC_API_VERSION
VERSION_VAR MINIUPNPC_VERSION)
mark_as_advanced(MINIUPNPC_INCLUDE_DIR MINIUPNPC_LIBRARY) 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 exitcode=1
fi 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 # format JS
# but only if js has changed # but only if js has changed
git diff --cached --quiet -- "web/**" && exit $exitcode git diff --cached --quiet -- "web/**" && exit $exitcode

View File

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

View File

@ -36,6 +36,16 @@ cmake --build build -t transmission-gtk
./build/gtk/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 ## ## On Unix ##
### Prerequisites ### ### 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-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 "::". * **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-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-global:** Number (default = 200)
* **peer-limit-per-torrent:** Number (default = 60) * **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-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 #### 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 * https://github.com/jaboto/Transmission-script - (cron)script set network limits according to the number of clients in the network
## Security with systemd ## 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

@ -35,7 +35,6 @@
#include <fmt/core.h> #include <fmt/core.h>
#include <algorithm> #include <algorithm>
#include <list>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <queue> #include <queue>

View File

@ -38,8 +38,8 @@
#include <algorithm> // std::transform() #include <algorithm> // std::transform()
#include <array> #include <array>
#include <map>
#include <memory> #include <memory>
#include <set>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>

View File

@ -55,12 +55,12 @@ namespace
{ {
#if !defined(TR_SYS_TRAY_IMPL_NONE) #if !defined(TR_SYS_TRAY_IMPL_NONE)
auto const TrayIconName = Glib::ustring("transmission-tray-icon"s); char const* const TrayIconName = "transmission-tray-icon";
auto const AppIconName = Glib::ustring("transmission"s); char const* const AppIconName = "transmission";
#endif #endif
#if defined(TR_SYS_TRAY_IMPL_APPINDICATOR) #if defined(TR_SYS_TRAY_IMPL_APPINDICATOR)
auto const AppName = Glib::ustring("transmission-gtk"s); char const* const AppName = "transmission-gtk";
#endif #endif
} // namespace } // namespace
@ -138,23 +138,11 @@ namespace
Glib::ustring getIconName() Glib::ustring getIconName()
{ {
Glib::ustring icon_name;
// if the tray's icon is a 48x48 file, use it. // if the tray's icon is a 48x48 file, use it.
// otherwise, use the fallback builtin icon. // 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; auto const icon = Gtk::IconTheme::get_default()->lookup_icon(TrayIconName, 48, Gtk::ICON_LOOKUP_USE_BUILTIN);
} return icon && !icon.get_filename().empty() ? TrayIconName : AppIconName;
return icon_name;
} }
#endif #endif
@ -199,7 +187,7 @@ SystemTrayIcon::Impl::Impl([[maybe_unused]] Gtk::Window& main_window, Glib::RefP
#endif #endif
#if defined(TR_SYS_TRAY_IMPL_APPINDICATOR) #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_status(indicator_, APP_INDICATOR_STATUS_ACTIVE);
app_indicator_set_menu(indicator_, Glib::unwrap(menu_)); app_indicator_set_menu(indicator_, Glib::unwrap(menu_));
app_indicator_set_title(indicator_, Glib::get_application_name().c_str()); app_indicator_set_title(indicator_, Glib::get_application_name().c_str());

View File

@ -1,3 +1,4 @@
include(CheckAtomic)
include(CheckLibraryExists) include(CheckLibraryExists)
include(CheckSymbolExists) include(CheckSymbolExists)
@ -222,7 +223,6 @@ target_compile_definitions(${TR_NAME}
$<$<BOOL:${WITH_INOTIFY}>:WITH_INOTIFY> $<$<BOOL:${WITH_INOTIFY}>:WITH_INOTIFY>
$<$<BOOL:${WITH_KQUEUE}>:WITH_KQUEUE> $<$<BOOL:${WITH_KQUEUE}>:WITH_KQUEUE>
$<$<BOOL:${ENABLE_UTP}>:WITH_UTP> $<$<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:${USE_SYSTEM_B64}>:USE_SYSTEM_B64>
$<$<BOOL:${HAVE_SO_REUSEPORT}>:HAVE_SO_REUSEPORT=1> $<$<BOOL:${HAVE_SO_REUSEPORT}>:HAVE_SO_REUSEPORT=1>
PUBLIC PUBLIC
@ -299,6 +299,7 @@ target_link_libraries(${TR_NAME}
$<$<BOOL:${WIN32}>:shlwapi> $<$<BOOL:${WIN32}>:shlwapi>
"$<$<BOOL:${APPLE}>:-framework Foundation>" "$<$<BOOL:${APPLE}>:-framework Foundation>"
"$<$<BOOL:${ANDROID}>:${log-lib}>" "$<$<BOOL:${ANDROID}>:${log-lib}>"
$<$<BOOL:${HAVE_LIBATOMIC}>:atomic>
PUBLIC PUBLIC
transmission::crypto_impl transmission::crypto_impl
fmt::fmt-header-only 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 * This is only an upper bound: if the tracker complains about
* length, announcer will incrementally lower the batch size. * 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 TrAnnounceTimeoutSec = std::chrono::seconds{ 45 };
auto inline constexpr TR_SCRAPE_TIMEOUT_SEC = std::chrono::seconds{ 30 }; auto inline constexpr TrScrapeTimeoutSec = std::chrono::seconds{ 30 };
struct tr_scrape_request struct tr_scrape_request
{ {
@ -171,7 +171,7 @@ struct tr_scrape_request
std::string log_name; std::string log_name;
/* info hashes of the torrents to scrape */ /* 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 */ /* how many hashes to use in the info_hash field */
int info_hash_count = 0; int info_hash_count = 0;
@ -209,7 +209,7 @@ struct tr_scrape_response
int row_count; int row_count;
/* the individual torrents' scrape results */ /* 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 */ /* the raw scrape url */
tr_interned_string scrape_url; tr_interned_string scrape_url;

View File

@ -265,7 +265,7 @@ void tr_tracker_http_announce(
auto url = tr_urlbuf{}; auto url = tr_urlbuf{};
announce_url_new(url, session, request); announce_url_new(url, session, request);
auto options = tr_web::FetchOptions{ url.sv(), onAnnounceDone, d }; auto options = tr_web::FetchOptions{ url.sv(), onAnnounceDone, d };
options.timeout_secs = TR_ANNOUNCE_TIMEOUT_SEC; options.timeout_secs = TrAnnounceTimeoutSec;
options.sndbuf = 4096; options.sndbuf = 4096;
options.rcvbuf = 4096; options.rcvbuf = 4096;
@ -542,7 +542,7 @@ void tr_tracker_http_scrape(tr_session const* session, tr_scrape_request const&
scrape_url_new(scrape_url, request); scrape_url_new(scrape_url, request);
tr_logAddTrace(fmt::format("Sending scrape to libcurl: '{}'", scrape_url), request.log_name); tr_logAddTrace(fmt::format("Sending scrape to libcurl: '{}'", scrape_url), request.log_name);
auto options = tr_web::FetchOptions{ scrape_url, onScrapeDone, d }; auto options = tr_web::FetchOptions{ scrape_url, onScrapeDone, d };
options.timeout_secs = TR_SCRAPE_TIMEOUT_SEC; options.timeout_secs = TrScrapeTimeoutSec;
options.sndbuf = 4096; options.sndbuf = 4096;
options.rcvbuf = 4096; options.rcvbuf = 4096;
session->fetch(std::move(options)); session->fetch(std::move(options));

View File

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

View File

@ -160,7 +160,7 @@ public:
return nullptr; 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; return &it->second;
} }

View File

@ -156,7 +156,7 @@ public:
// @brief process an incoming udp message if it's a tracker response. // @brief process an incoming udp message if it's a tracker response.
// @return true if msg was a tracker response; false otherwise // @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; [[nodiscard]] virtual bool is_idle() const noexcept = 0;
}; };

View File

@ -3,7 +3,6 @@
// or any future license endorsed by Mnemosyne LLC. // or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder. // License text can be found in the licenses/ folder.
#include <memory>
#include <mutex> #include <mutex>
#include <mbedtls/base64.h> #include <mbedtls/base64.h>
@ -118,7 +117,7 @@ void tr_sha1::clear()
{ {
mbedtls_sha1_init(&handle_); mbedtls_sha1_init(&handle_);
#if MBEDTLS_VERSION_NUMBER >= 0x02070000 #if MBEDTLS_VERSION_NUMBER < 0x03000000 && MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha1_starts_ret(&handle_); mbedtls_sha1_starts_ret(&handle_);
#else #else
mbedtls_sha1_starts(&handle_); mbedtls_sha1_starts(&handle_);
@ -132,7 +131,7 @@ void tr_sha1::add(void const* data, size_t data_length)
return; 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); mbedtls_sha1_update_ret(&handle_, static_cast<unsigned char const*>(data), data_length);
#else #else
mbedtls_sha1_update(&handle_, static_cast<unsigned char const*>(data), data_length); 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 digest = tr_sha1_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest)); 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); mbedtls_sha1_finish_ret(&handle_, digest_as_uchar);
#else #else
mbedtls_sha1_finish(&handle_, digest_as_uchar); mbedtls_sha1_finish(&handle_, digest_as_uchar);
@ -165,10 +164,10 @@ void tr_sha256::clear()
{ {
mbedtls_sha256_init(&handle_); mbedtls_sha256_init(&handle_);
#if MBEDTLS_VERSION_NUMBER >= 0x02070000 #if MBEDTLS_VERSION_NUMBER < 0x03000000 && MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha256_starts_ret(&handle_, 0); mbedtls_sha256_starts_ret(&handle_, 0);
#else #else
mbedtls_sha256_starts(&handle_); mbedtls_sha256_starts(&handle_, 0);
#endif #endif
} }
@ -179,7 +178,7 @@ void tr_sha256::add(void const* data, size_t data_length)
return; 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); mbedtls_sha256_update_ret(&handle_, static_cast<unsigned char const*>(data), data_length);
#else #else
mbedtls_sha256_update(&handle_, static_cast<unsigned char const*>(data), data_length); 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 digest = tr_sha256_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest)); 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); mbedtls_sha256_finish_ret(&handle_, digest_as_uchar);
#else #else
mbedtls_sha256_finish(&handle_, digest_as_uchar); mbedtls_sha256_finish(&handle_, digest_as_uchar);

View File

@ -10,7 +10,6 @@
#include <array> #include <array>
#include <cstddef> // size_t #include <cstddef> // size_t
#include <memory>
#include <openssl/crypto.h> #include <openssl/crypto.h>
#include <openssl/err.h> #include <openssl/err.h>

View File

@ -3,7 +3,6 @@
// or any future license endorsed by Mnemosyne LLC. // or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder. // License text can be found in the licenses/ folder.
#include <memory>
#include <mutex> #include <mutex>
#include <wolfssl/options.h> #include <wolfssl/options.h>

View File

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

View File

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

View File

@ -26,7 +26,7 @@ class tr_recentHistory
public: public:
/** /**
* @brief add a counter to the recent history object. * @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 * @param n how many items to add to the history's counter
*/ */
constexpr void add(time_t now, SizeType n) 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. * @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 now the current time in seconds, such as from tr_time()
* @param seconds how many seconds to count back through. * @param age_sec how many seconds to count back through.
*/ */
[[nodiscard]] constexpr SizeType count(time_t now, unsigned int age_sec) const [[nodiscard]] constexpr SizeType count(time_t now, unsigned int age_sec) const
{ {

View File

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

View File

@ -134,7 +134,7 @@ tr_metainfo_builder::tr_metainfo_builder(std::string_view single_file_or_parent_
: top_{ single_file_or_parent_directory } : top_{ single_file_or_parent_directory }
{ {
files_ = findFiles(tr_sys_path_dirname(top_), tr_sys_path_basename(top_)); 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 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; return false;
} }
block_info_ = tr_block_info{ files_.totalSize(), piece_size }; block_info_ = tr_block_info{ files_.total_size(), piece_size };
return true; return true;
} }

View File

@ -122,12 +122,12 @@ public:
[[nodiscard]] TR_CONSTEXPR20 auto file_count() const noexcept [[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 [[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 [[nodiscard]] constexpr auto is_private() const noexcept
@ -167,7 +167,7 @@ public:
[[nodiscard]] constexpr auto total_size() const noexcept [[nodiscard]] constexpr auto total_size() const noexcept
{ {
return files_.totalSize(); return files_.total_size();
} }
[[nodiscard]] constexpr auto const& webseeds() const noexcept [[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. // This file Copyright © Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), // 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. // or any future license endorsed by Mnemosyne LLC.

View File

@ -1,7 +1,10 @@
#!/usr/bin/env node #!/usr/bin/env node
const copyright = 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), // 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. // or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.`; // License text can be found in the licenses/ folder.`;

View File

@ -16,8 +16,12 @@
#include <utility> // std::pair #include <utility> // std::pair
#ifdef _WIN32 #ifdef _WIN32
#include <winsock2.h> // must come before iphlpapi.h
#include <iphlpapi.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#else #else
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/tcp.h> /* TCP_CONGESTION */ #include <netinet/tcp.h> /* TCP_CONGESTION */
#endif #endif
@ -430,16 +434,6 @@ namespace
namespace is_valid_for_peers_helpers 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, /* isMartianAddr was written by Juliusz Chroboczek,
and is covered under the same license as third-party/dht/dht.c. */ 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) [[nodiscard]] auto is_martian_addr(tr_address const& addr, tr_peer_from from)
@ -523,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 std::string_view tr_address::display_name(char* out, size_t outlen) const
{ {
TR_ASSERT(is_valid()); 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 [[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)) }; return std::string{ display_name(std::data(buf), std::size(buf)) };
} }
@ -557,6 +555,99 @@ std::pair<tr_address, std::byte const*> tr_address::from_compact_ipv6(std::byte
return { address, compact }; 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 // <=> int tr_address::compare(tr_address const& that) const noexcept // <=>
{ {
// IPv6 addresses are always "greater than" IPv4 // IPv6 addresses are always "greater than" IPv4
@ -715,10 +806,23 @@ bool tr_socket_address::is_valid_for_peers(tr_peer_from from) const noexcept
{ {
using namespace is_valid_for_peers_helpers; 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); !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) std::optional<tr_socket_address> tr_socket_address::from_sockaddr(struct sockaddr const* from)
{ {
if (from == nullptr) if (from == nullptr)

View File

@ -12,7 +12,6 @@
#include <array> #include <array>
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint16_t, uint32_t, uint8_t #include <cstdint> // uint16_t, uint32_t, uint8_t
#include <functional>
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
@ -146,9 +145,10 @@ private:
enum tr_address_type : uint8_t enum tr_address_type : uint8_t
{ {
TR_AF_INET, TR_AF_INET = 0,
TR_AF_INET6, 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); 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_ipv4(std::byte const* compact) noexcept;
[[nodiscard]] static std::pair<tr_address, std::byte const*> from_compact_ipv6(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; std::string_view display_name(char* out, size_t outlen) const;
[[nodiscard]] std::string display_name() const; [[nodiscard]] std::string display_name() const;
/// // ---
[[nodiscard]] constexpr auto is_ipv4() const noexcept [[nodiscard]] constexpr auto is_ipv4() const noexcept
{ {
@ -177,7 +177,7 @@ struct tr_address
return type == TR_AF_INET6; 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 // 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; [[nodiscard]] int compare(tr_address const& that) const noexcept;
@ -232,10 +236,20 @@ struct tr_address
return this->compare(that) > 0; return this->compare(that) > 0;
} }
// // ---
[[nodiscard]] bool is_global_unicast_address() const noexcept; [[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; tr_address_type type = NUM_TR_AF_INET_TYPES;
union union
{ {
@ -374,6 +388,7 @@ struct tr_socket_address
// --- sockaddr helpers // --- 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::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; [[nodiscard]] static std::pair<sockaddr_storage, socklen_t> to_sockaddr(tr_address const& addr, tr_port port) noexcept;

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); auto const current_speed = io->get_piece_speed(now, TR_UP);
return std::max(Floor, current_speed.base_quantity() * PeriodSecs); 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 } // 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); auto io = std::make_shared<tr_peerIo>(session, info_hash, is_incoming, is_seed, parent);
io->bandwidth().set_peer(io); io->bandwidth().set_peer(io);
tr_logAddTraceIo(io, fmt::format("bandwidth is {}; its parent is {}", fmt::ptr(&io->bandwidth()), fmt::ptr(parent)));
return io; 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); auto peer_io = tr_peerIo::create(session, parent, nullptr, true, false);
peer_io->set_socket(std::move(socket)); peer_io->set_socket(std::move(socket));
log_peer_io_bandwidth(*peer_io, parent);
return peer_io; return peer_io;
} }
@ -167,12 +174,14 @@ std::shared_ptr<tr_peerIo> tr_peerIo::new_outgoing(
if (func.at(preferred)()) if (func.at(preferred)())
{ {
log_peer_io_bandwidth(*peer_io, parent);
return peer_io; return peer_io;
} }
for (preferred_key_t i = 0U; i < TR_NUM_PREFERRED_TRANSPORT; ++i) for (preferred_key_t i = 0U; i < TR_NUM_PREFERRED_TRANSPORT; ++i)
{ {
if (i != preferred && func.at(i)()) if (i != preferred && func.at(i)())
{ {
log_peer_io_bandwidth(*peer_io, parent);
return peer_io; return peer_io;
} }
} }

View File

@ -11,15 +11,16 @@
#include <cstddef> // std::byte #include <cstddef> // std::byte
#include <cstdint> #include <cstdint>
#include <ctime> // time_t #include <ctime> // time_t
#include <functional>
#include <iterator> // std::back_inserter #include <iterator> // std::back_inserter
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <tuple> // std::tie #include <tuple> // std::tie
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <small/map.hpp>
#include <small/vector.hpp> #include <small/vector.hpp>
#include <fmt/core.h> #include <fmt/core.h>
@ -270,7 +271,9 @@ constexpr struct
return compare(a, b) < 0; 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; return compare(*a, *b) < 0;
} }
@ -283,7 +286,7 @@ class tr_swarm
{ {
public: public:
using Peers = std::vector<tr_peerMsgs*>; 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 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 [[nodiscard]] uint16_t count_active_webseeds(uint64_t now) const noexcept
{ {
if (!tor->is_running() || tor->is_done()) if (!tor->is_running() || tor->is_done())
@ -416,10 +401,8 @@ public:
peer_disconnect.emit(tor, peer->has()); peer_disconnect.emit(tor, peer->has());
auto* const peer_info = peer->peer_info; auto const& peer_info = peer->peer_info;
auto const socket_address = peer->socket_address(); TR_ASSERT(peer_info);
[[maybe_unused]] auto const is_incoming = peer->is_incoming_connection();
TR_ASSERT(peer_info != nullptr);
--stats.peer_count; --stats.peer_count;
--stats.peer_from_count[peer_info->from_first()]; --stats.peer_from_count[peer_info->from_first()];
@ -431,17 +414,6 @@ public:
} }
delete peer; 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() void remove_all_peers()
@ -475,37 +447,41 @@ public:
pool_is_all_seeds_ = std::all_of( pool_is_all_seeds_ = std::all_of(
std::begin(connectable_pool), std::begin(connectable_pool),
std::end(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_; 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); if (auto it = connectable_pool.find(socket_address); it != std::end(connectable_pool))
return it != connectable_pool.end() ? &it->second : nullptr; {
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, tr_socket_address const& socket_address,
uint8_t const flags, uint8_t const flags,
tr_peer_from const from, tr_peer_from const from)
bool is_connectable)
{ {
TR_ASSERT(socket_address.is_valid()); TR_ASSERT(socket_address.is_valid());
TR_ASSERT(from < TR_PEER_FROM__MAX); TR_ASSERT(from < TR_PEER_FROM__MAX);
auto&& [it, is_new] = is_connectable ? connectable_pool.try_emplace(socket_address, socket_address, flags, from) : auto peer_info = get_existing_peer_info(socket_address);
incoming_pool.try_emplace(socket_address, socket_address.address(), flags, from); if (peer_info)
auto& peer_info = it->second;
if (!is_new)
{ {
peer_info.found_at(from); peer_info->found_at(from);
peer_info.set_pex_flags(flags); 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]; ++stats.known_peer_from_count[from];
} }
@ -568,19 +544,13 @@ public:
break; break;
case tr_peer_event::Type::ClientGotPort: 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 s->on_got_port(msgs, event);
}
// 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);
} }
break; break;
@ -622,9 +592,6 @@ public:
std::unique_ptr<Wishlist> wishlist; 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; Pool connectable_pool;
tr_peerMsgs* optimistic = nullptr; /* the optimistic peer, or nullptr if none */ tr_peerMsgs* optimistic = nullptr; /* the optimistic peer, or nullptr if none */
@ -672,9 +639,10 @@ private:
is_running = false; is_running = false;
remove_all_peers(); remove_all_peers();
wishlist.reset();
for (auto& [sockaddr, peer_info] : connectable_pool) 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(); 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(); mark_all_seeds_flag_dirty();
@ -862,89 +830,75 @@ private:
tor->session->add_downloaded(sent_length); 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; auto info_this = msgs->peer_info;
TR_ASSERT(info_this.is_connected()); TR_ASSERT(info_this->is_connected());
TR_ASSERT(was_connectable != std::empty(info_this.listen_port())); TR_ASSERT(info_this->listen_port() != event.port);
// If we already know about this peer, merge the info objects without invalidating references // we already know about this peer
if (auto it_that = connectable_pool.find({ info_this.listen_address(), event.port }); if (auto it_that = connectable_pool.find({ info_this->listen_address(), event.port });
it_that != std::end(connectable_pool)) it_that != std::end(connectable_pool))
{ {
auto& info_that = it_that->second; auto const info_that = it_that->second;
TR_ASSERT(it_that->first == info_that.listen_socket_address()); 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.address() == info_this->listen_address());
TR_ASSERT(it_that->first.port() != info_this.listen_port()); TR_ASSERT(it_that->first.port() != info_this->listen_port());
// If there is an existing connection to this peer, keep the better one // 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 (info_that->is_connected() && on_got_port_duplicate_connection(msgs, info_that))
{ {
return; goto EXIT; // NOLINT cppcoreguidelines-avoid-goto
} }
info_this.merge(info_that); // merge the peer info objects
auto from = info_that.from_first(); info_this->merge(*info_that);
stats.known_peer_from_count[from] -= connectable_pool.erase(info_that.listen_socket_address());
// 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()) : // erase the old peer info entry
incoming_pool.extract(msgs->socket_address()); stats.known_peer_from_count[info_this->from_first()] -= connectable_pool.erase(info_this->listen_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);
// 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(); 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 const info_this = msgs->peer_info;
auto& info_that = it_that->second;
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::begin(peers),
std::end(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)); TR_ASSERT(it != std::end(peers));
(*it)->do_purge = true; (*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; return false;
} }
info_that.merge(info_this); info_that->merge(*info_this);
msgs->do_purge = true; 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; return true;
} }
@ -958,10 +912,6 @@ private:
std::array<libtransmission::ObserverTag, 8> const tags_; 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_; mutable std::optional<bool> pool_is_all_seeds_;
bool is_endgame_ = false; bool is_endgame_ = false;
@ -1075,6 +1025,7 @@ struct tr_peerMgr
{ {
private: private:
static auto constexpr BandwidthTimerPeriod = 500ms; static auto constexpr BandwidthTimerPeriod = 500ms;
static auto constexpr PeerInfoPeriod = 1min;
static auto constexpr RechokePeriod = 10s; static auto constexpr RechokePeriod = 10s;
static auto constexpr RefillUpkeepPeriod = 10s; static auto constexpr RefillUpkeepPeriod = 10s;
@ -1109,11 +1060,13 @@ public:
, blocklists_{ blocklist } , blocklists_{ blocklist }
, handshake_mediator_{ *session, timer_maker, torrents } , handshake_mediator_{ *session, timer_maker, torrents }
, bandwidth_timer_{ timer_maker.create([this]() { bandwidth_pulse(); }) } , 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(); }) } , rechoke_timer_{ timer_maker.create([this]() { rechoke_pulse_marshall(); }) }
, refill_upkeep_timer_{ timer_maker.create([this]() { refill_upkeep(); }) } , refill_upkeep_timer_{ timer_maker.create([this]() { refill_upkeep(); }) }
, blocklists_tag_{ blocklist.observe_changes([this]() { on_blocklists_changed(); }) } , blocklists_tag_{ blocklist.observe_changes([this]() { on_blocklists_changed(); }) }
{ {
bandwidth_timer_->start_repeating(BandwidthTimerPeriod); bandwidth_timer_->start_repeating(BandwidthTimerPeriod);
peer_info_timer_->start_repeating(PeerInfoPeriod);
rechoke_timer_->start_repeating(RechokePeriod); rechoke_timer_->start_repeating(RechokePeriod);
refill_upkeep_timer_->start_repeating(RefillUpkeepPeriod); refill_upkeep_timer_->start_repeating(RefillUpkeepPeriod);
} }
@ -1155,6 +1108,7 @@ public:
private: private:
void bandwidth_pulse(); void bandwidth_pulse();
void make_new_peer_connections(); void make_new_peer_connections();
void peer_info_pulse();
void rechoke_pulse() const; void rechoke_pulse() const;
void reconnect_pulse(); void reconnect_pulse();
void refill_upkeep() const; void refill_upkeep() const;
@ -1171,12 +1125,14 @@ private:
since the blocklist has changed, erase that cached value */ since the blocklist has changed, erase that cached value */
for (auto* const tor : torrents_) 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()) peer_info->set_blocklisted_dirty();
{ }
atom.set_blocklisted_dirty();
} for (auto* const peer : tor->swarm->peers)
{
peer->peer_info->set_blocklisted_dirty();
} }
} }
} }
@ -1184,6 +1140,7 @@ private:
OutboundCandidates outbound_candidates_; OutboundCandidates outbound_candidates_;
std::unique_ptr<libtransmission::Timer> const bandwidth_timer_; 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 rechoke_timer_;
std::unique_ptr<libtransmission::Timer> const refill_upkeep_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_) for (auto* const tor : torrents_)
{ {
tor->swarm->cancel_old_requests(); tor->swarm->cancel_old_requests();
tor->swarm->remove_inactive_peer_info();
} }
} }
@ -1291,22 +1247,26 @@ namespace
{ {
namespace handshake_helpers 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_ASSERT(tor.swarm != nullptr);
tr_swarm* swarm = tor.swarm; tr_swarm* swarm = tor.swarm;
auto* peer = tr_peerMsgs::create(tor, peer_info, std::move(io), client, &tr_swarm::peer_callback_bt, swarm); 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(peer); swarm->peers.push_back(msgs);
++swarm->stats.peer_count; ++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_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. */ /* 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); TR_ASSERT(result.io != nullptr);
auto const& socket_address = result.io->socket_address(); auto const& socket_address = result.io->socket_address();
auto* const swarm = manager->get_existing_swarm(result.io->torrent_hash()); 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()) if (result.io->is_incoming())
{ {
manager->incoming_handshakes.erase(socket_address); manager->incoming_handshakes.erase(socket_address);
} }
else if (info != nullptr) else if (info)
{ {
info->destroy_handshake(); info->destroy_handshake();
} }
if (!result.is_connected || swarm == nullptr || !swarm->is_running) if (!result.is_connected || swarm == nullptr || !swarm->is_running)
{ {
if (info != nullptr && !info->is_connected()) if (info && !info->is_connected())
{ {
info->on_connection_failed(); 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()) 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; 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()); 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; 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) // 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 // 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; ++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) 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)); infos.reserve(std::size(pool));
for (auto const& [socket_address, peer_info] : pool) for (auto const& [socket_address, peer_info] : pool)
{ {
TR_ASSERT(socket_address == peer_info.listen_socket_address()); TR_ASSERT(socket_address == peer_info->listen_socket_address());
if (socket_address.address().type == address_type && is_peer_interesting(tor, peer_info)) 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* 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 */ /* disconnect if we're both seeds and enough time has passed for PEX */
if (tor->is_done() && peer->is_seed()) if (tor->is_done() && peer->is_seed())
@ -2195,7 +2155,7 @@ void close_peer(tr_peerMsgs* peer)
constexpr struct 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) 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); 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; return compare(a, b) < 0;
} }
@ -2331,6 +2291,93 @@ void tr_peerMgr::reconnect_pulse()
make_new_peer_connections(); 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 // --- Bandwidth Allocation
namespace namespace
@ -2419,22 +2466,6 @@ namespace connect_helpers
return true; 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) [[nodiscard]] constexpr uint64_t addValToKey(uint64_t value, unsigned int width, uint64_t addme)
{ {
value <<= width; 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) 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(); setme.clear();
auto const now = tr_time(); auto const now = tr_time();
@ -2551,24 +2598,24 @@ void get_peer_candidates(size_t global_peer_limit, tr_torrents& torrents, tr_pee
continue; 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 // 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::partial_sort(
std::begin(candidates), std::begin(candidates),
std::begin(candidates) + max, std::begin(candidates) + Max,
std::end(candidates), std::end(candidates),
[](auto const& a, auto const& b) { return a.score < b.score; }); [](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 // 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 // initiate connections to the last N candidates
auto const n_this_pass = std::min(std::size(candidates), MaxConnectionsPerPulse); 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), end = std::crbegin(candidates) + n_this_pass; it != end; ++it)
for (auto it = std::crbegin(candidates); it != it_end; ++it)
{ {
auto const& [tor_id, sock_addr] = *it; auto const& [tor_id, sock_addr] = *it;
if (auto* const tor = torrents_.get(tor_id); tor != nullptr) 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); 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 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); 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 // the minimum we'll wait before attempting to reconnect to a peer
static auto constexpr MinimumReconnectIntervalSecs = time_t{ 5U }; 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{}; static auto inline n_known_connectable = size_t{};

View File

@ -305,12 +305,12 @@ class tr_peerMsgsImpl final : public tr_peerMsgs
public: public:
tr_peerMsgsImpl( tr_peerMsgsImpl(
tr_torrent& torrent_in, 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, std::shared_ptr<tr_peerIo> io_in,
tr_interned_string client, tr_interned_string client,
tr_peer_callback_bt callback, tr_peer_callback_bt callback,
void* callback_data) 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 } , tor_{ torrent_in }
, io_{ std::move(io_in) } , io_{ std::move(io_in) }
, have_{ torrent_in.piece_count() } , have_{ torrent_in.piece_count() }
@ -2054,13 +2054,13 @@ size_t tr_peerMsgsImpl::max_available_reqs() const
tr_peerMsgs::tr_peerMsgs( tr_peerMsgs::tr_peerMsgs(
tr_torrent const& tor, tr_torrent const& tor,
tr_peer_info* peer_info_in, std::shared_ptr<tr_peer_info> peer_info_in,
tr_interned_string user_agent, tr_interned_string user_agent,
bool connection_is_encrypted, bool connection_is_encrypted,
bool connection_is_incoming, bool connection_is_incoming,
bool connection_is_utp) bool connection_is_utp)
: tr_peer{ tor } : tr_peer{ tor }
, peer_info{ peer_info_in } , peer_info{ std::move(peer_info_in) }
, user_agent_{ user_agent } , user_agent_{ user_agent }
, connection_is_encrypted_{ connection_is_encrypted } , connection_is_encrypted_{ connection_is_encrypted }
, connection_is_incoming_{ connection_is_incoming } , connection_is_incoming_{ connection_is_incoming }
@ -2079,11 +2079,11 @@ tr_peerMsgs::~tr_peerMsgs()
tr_peerMsgs* tr_peerMsgs::create( tr_peerMsgs* tr_peerMsgs::create(
tr_torrent& torrent, tr_torrent& torrent,
tr_peer_info* const peer_info, std::shared_ptr<tr_peer_info> peer_info,
std::shared_ptr<tr_peerIo> io, std::shared_ptr<tr_peerIo> io,
tr_interned_string user_agent, tr_interned_string user_agent,
tr_peer_callback_bt callback, tr_peer_callback_bt callback,
void* callback_data) 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: public:
tr_peerMsgs( tr_peerMsgs(
tr_torrent const& tor, tr_torrent const& tor,
tr_peer_info* peer_info_in, std::shared_ptr<tr_peer_info> peer_info_in,
tr_interned_string user_agent, tr_interned_string user_agent,
bool connection_is_encrypted, bool connection_is_encrypted,
bool connection_is_incoming, bool connection_is_incoming,
@ -108,7 +108,7 @@ public:
static tr_peerMsgs* create( static tr_peerMsgs* create(
tr_torrent& torrent, tr_torrent& torrent,
tr_peer_info* peer_info, std::shared_ptr<tr_peer_info> peer_info,
std::shared_ptr<tr_peerIo> io, std::shared_ptr<tr_peerIo> io,
tr_interned_string user_agent, tr_interned_string user_agent,
tr_peer_callback_bt callback, tr_peer_callback_bt callback,
@ -146,8 +146,7 @@ protected:
} }
public: public:
// TODO(tearfur): change this to reference std::shared_ptr<tr_peer_info> const peer_info;
tr_peer_info* const peer_info;
private: private:
static inline auto n_peers = std::atomic<size_t>{}; static inline auto n_peers = std::atomic<size_t>{};

View File

@ -142,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;
{
state_ = State::SendMap;
}
else if (is_mapped_ && tr_time() >= renew_time_)
{
state_ = State::SendMap;
}
} }
if (state_ == State::SendMap && canSendCommand()) if (state_ == State::SendMap && canSendCommand())

View File

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

View File

@ -8,7 +8,6 @@
#include <cstddef> #include <cstddef>
#include <iterator> // for std::distance() #include <iterator> // for std::distance()
#include <optional> #include <optional>
#include <string>
#include <string_view> #include <string_view>
#include <vector> #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; tr_variant* list = nullptr;
if (!tr_variantDictFindList(dict, TR_KEY_labels, &list)) if (!tr_variantDictFindList(dict, TR_KEY_labels, &list))
{ {
return tr_resume::fields_t{}; return {};
} }
auto const n = tr_variantListSize(list); 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()); 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)) 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::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; 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 wanted = std::vector<tr_file_index_t>{};
auto unwanted = 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(unwanted), std::size(unwanted), false);
tor->init_files_wanted(std::data(wanted), std::size(wanted), true); tor->init_files_wanted(std::data(wanted), std::size(wanted), true);
ret = tr_resume::Dnd; return 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 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(); auto const n = tor->file_count();
tr_variant* list = nullptr; tr_variant* list = nullptr;
if (tr_variantDictFindList(dict, TR_KEY_priority, &list) && tr_variantListSize(list) == n) 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; 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 (tr_variant* d = nullptr; tr_variantDictFindDict(dict, TR_KEY_ratio_limit, &d))
{ {
if (auto dratio = double{}; tr_variantDictFindReal(d, TR_KEY_ratio_limit, &dratio)) 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)); 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 (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)) 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)); 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()); 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{}; auto name = std::string_view{};
if (!tr_variantDictFindStrView(dict, TR_KEY_name, &name)) if (!tr_variantDictFindStrView(dict, TR_KEY_name, &name))
{ {
return ret; return {};
} }
name = tr_strv_strip(name); name = tr_strv_strip(name);
if (std::empty(name)) if (std::empty(name))
{ {
return ret; return {};
} }
tor->set_name(name); 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; tr_variant* list = nullptr;
if (!tr_variantDictFindList(dict, TR_KEY_files, &list)) if (!tr_variantDictFindList(dict, TR_KEY_files, &list))
{ {
return ret; return {};
} }
auto const n_files = tor->file_count(); auto const n_files = tor->file_count();
@ -412,8 +398,7 @@ auto loadFilenames(tr_variant* dict, tr_torrent* tor)
} }
} }
ret |= tr_resume::Filenames; return tr_resume::Filenames;
return ret;
} }
// --- // ---
@ -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); 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 // add the 'checked pieces' bitfield
bitfieldToRaw(helper.checked_pieces(), tr_variantDictAdd(prog, TR_KEY_pieces)); bitfieldToRaw(helper.checked_pieces(), tr_variantDictAdd(prog, TR_KEY_pieces));
/* add the progress */ // add the blocks bitfield
if (tor->is_seed())
{
tr_variantDictAddStrView(prog, TR_KEY_have, "all"sv);
}
/* add the blocks bitfield */
bitfieldToRaw(helper.blocks(), tr_variantDictAdd(prog, TR_KEY_blocks)); 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. * pieces cleared from the bitset.
* *
* Second approach (2.20 - 3.00): the 'progress' dict had a * 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 * Each item was either a list of per-piece timestamps, or a
* single timestamp if either all or none of the pieces had been * single timestamp if either all or none of the pieces had been
* tested more recently than the file's mtime. * 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 * First approach (pre-2.20) had an "mtimes" list identical to
* 3.10, but not the 'pieces' bitfield. * 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)) 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); 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)) else if (tr_variantDictFindRaw(prog, TR_KEY_bitfield, &raw, &rawlen))
{ {
blocks.set_raw(raw, rawlen); blocks.set_raw(raw, rawlen);
} }
else else
{ {
err = "Couldn't find 'pieces' or 'have' or 'bitfield'"; err = "Couldn't find 'blocks' or 'bitfield'";
} }
if (err != nullptr) 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::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); saveFilePriorities(&top, tor);
saveDND(&top, tor); saveDND(&top, tor);
saveProgress(&top, tor, helper); saveProgress(&top, helper);
} }
saveSpeedLimits(&top, tor); saveSpeedLimits(&top, tor);

View File

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

View File

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

View File

@ -20,6 +20,12 @@ namespace libtransmission
class Settings class Settings
{ {
public: 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); void load(tr_variant const& src);
[[nodiscard]] tr_variant save() const; [[nodiscard]] tr_variant save() const;

View File

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

View File

@ -35,17 +35,17 @@ public:
return std::empty(files_); 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_); 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_; return files_.at(file_index).size_;
} }
[[nodiscard]] constexpr auto totalSize() const noexcept [[nodiscard]] constexpr auto total_size() const noexcept
{ {
return total_size_; return total_size_;
} }
@ -55,12 +55,12 @@ public:
return files_.at(file_index).path_; 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, '/' }; auto const buf = tr_pathbuf{ path, '/' };
@ -76,7 +76,7 @@ public:
files_.reserve(n_files); files_.reserve(n_files);
} }
void shrinkToFit() void shrink_to_fit()
{ {
files_.shrink_to_fit(); files_.shrink_to_fit();
} }
@ -87,7 +87,7 @@ public:
total_size_ = uint64_t{}; 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*/>>{}; auto ret = std::vector<std::pair<std::string /*path*/, uint64_t /*size*/>>{};
ret.reserve(std::size(files_)); 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]] 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{}; auto tmp = tr_pathbuf{};
makeSubpathPortable(path, tmp); sanitize_subpath(path, tmp, os_specific);
return std::string{ tmp.sv() }; 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"; static constexpr std::string_view PartialFileSuffix = ".part";
@ -175,7 +175,7 @@ private:
struct file_t struct file_t
{ {
public: public:
void setPath(std::string_view subpath) void set_path(std::string_view subpath)
{ {
if (path_ != 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); 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 void tr_torrent::maybe_start_metadata_transfer(int64_t const size) noexcept
{ {
if (has_metainfo() || metadata_download_) if (has_metainfo() || metadata_download_)
@ -262,20 +271,20 @@ void tr_torrent::on_have_all_metainfo()
m.reset(); 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); TR_ASSERT(data != nullptr);
// sanity test: is `piece` in range? // sanity test: is `piece` in range?
if (piece < 0 || piece >= piece_count_) if (piece < 0 || piece >= piece_count_)
{ {
return false; return;
} }
// sanity test: is `len` the right size? // sanity test: is `len` the right size?
if (get_piece_length(piece) != len) if (get_piece_length(piece) != len)
{ {
return false; return;
} }
// do we need this piece? // 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; }); [piece](auto const& item) { return item.piece == piece; });
if (iter == std::end(needed)) if (iter == std::end(needed))
{ {
return false; return;
} }
auto const offset = piece * MetadataPieceSize; 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); needed.erase(iter);
tr_logAddDebugMagnet(this, fmt::format("saving metainfo piece {}... {} remain", piece, std::size(needed))); 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) 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)); 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!")); m->set_metadata_piece(piece, data, len);
on_have_all_metainfo();
} }
} }

View File

@ -14,7 +14,6 @@
#include <ctime> // time_t #include <ctime> // time_t
#include <deque> #include <deque>
#include <limits> #include <limits>
#include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
@ -39,7 +38,12 @@ public:
return size > 0 && size <= std::numeric_limits<int>::max(); 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; [[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_ += '/'; file_subpath_ += '/';
} }
tr_torrent_files::makeSubpathPortable(currentKey(), file_subpath_); tr_torrent_files::sanitize_subpath(currentKey(), file_subpath_);
} }
else if (pathIs(InfoKey)) else if (pathIs(InfoKey))
{ {
@ -298,7 +298,7 @@ struct MetainfoHandler final : public transmission::benc::BasicHandler<MaxBencDe
{ {
file_subpath_ += '/'; file_subpath_ += '/';
} }
tr_torrent_files::makeSubpathPortable(value, file_subpath_); tr_torrent_files::sanitize_subpath(value, file_subpath_);
} }
else if (current_key == AttrKey) else if (current_key == AttrKey)
{ {
@ -488,10 +488,10 @@ private:
} }
auto root = tr_pathbuf{}; auto root = tr_pathbuf{};
tr_torrent_files::makeSubpathPortable(tm_.name_, root); tr_torrent_files::sanitize_subpath(tm_.name_, root);
if (!std::empty(root)) if (!std::empty(root))
{ {
tm_.files_.insertSubpathPrefix(root); tm_.files_.insert_subpath_prefix(root);
} }
TR_ASSERT(info_dict_begin_[0] == 'd'); 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. // 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_)) 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) if (auto const has_metainfo = tm_.info_dict_size() != 0U; has_metainfo)
@ -546,7 +546,7 @@ private:
return false; 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; return true;
} }

View File

@ -44,11 +44,11 @@ public:
} }
[[nodiscard]] TR_CONSTEXPR20 auto file_count() const noexcept [[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 [[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 [[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) void set_file_subpath(tr_file_index_t i, std::string_view subpath)
{ {
files_.setPath(i, subpath); files_.set_path(i, subpath);
} }
/// BLOCK INFO /// BLOCK INFO

View File

@ -446,7 +446,7 @@ void tr_torrent::stop_if_seed_limit_reached()
session->onRatioLimitHit(this); session->onRatioLimitHit(this);
} }
/* if we're seeding and reach our inactivity limit, stop the torrent */ /* 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")); 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()); TR_ASSERT(session->am_in_session_thread());
auto const lock = unique_lock(); 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_running_ = false;
is_stopping_ = false; is_stopping_ = false;
mark_changed(); mark_changed();
@ -895,7 +899,7 @@ void tr_torrent::on_metainfo_completed()
// Potentially, we are in `tr_torrent::init`, // Potentially, we are in `tr_torrent::init`,
// and we don't want any file created before `tr_torrent::start` // and we don't want any file created before `tr_torrent::start`
// so we Verify but we don't Create files. // so we Verify but we don't Create files.
session->queue_session_thread(tr_torrentVerify, this); tr_torrentVerify(this);
} }
else else
{ {
@ -1208,7 +1212,7 @@ bool tr_torrent::has_any_local_data() const
auto paths = std::array<std::string_view, 4>{}; auto paths = std::array<std::string_view, 4>{};
auto const n_paths = buildSearchPathArray(this, std::data(paths)); 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) 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 {}; 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() void tr_torrent::VerifyMediator::on_verify_queued()
{ {
tr_logAddTraceTor(tor_, "Queued for verification"); 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); 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_has_piece(piece, has_piece);
tor_->set_dirty(); tor_->set_dirty();
@ -1660,6 +1697,11 @@ void tr_torrent::VerifyMediator::on_verify_done(bool const aborted)
return; 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(); tor->recheck_completeness();
if (tor->verify_done_callback_) if (tor->verify_done_callback_)
@ -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 /* 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 * metadata -- for example, if it had the ".part" suffix appended to
* it until now -- then rename it to match the one in the metadata */ * it until now -- then rename it to match the one in the metadata */
if (auto found = find_file(file); found) update_file_path(file, true);
{
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(
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::on_piece_completed(tr_piece_index_t const piece) 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 // 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) 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); on_file_completed(file);
} }
@ -2544,7 +2566,7 @@ tr_bitfield const& tr_torrent::ResumeHelper::checked_pieces() const noexcept
return tor_.checked_pieces_; 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()); TR_ASSERT(std::size(checked) == tor_.piece_count());
tor_.checked_pieces_ = checked; tor_.checked_pieces_ = checked;

View File

@ -70,7 +70,7 @@ struct tr_torrent
class ResumeHelper class ResumeHelper
{ {
public: 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_blocks(tr_bitfield blocks);
void load_date_added(time_t when) noexcept; void load_date_added(time_t when) noexcept;
void load_date_done(time_t when) noexcept; void load_date_done(time_t when) noexcept;
@ -325,6 +325,11 @@ struct tr_torrent
return completion_.has_none(); 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 [[nodiscard]] auto has_piece(tr_piece_index_t piece) const
{ {
return completion_.has_piece(piece); return completion_.has_piece(piece);
@ -372,7 +377,7 @@ struct tr_torrent
void amount_done_bins(float* tab, int n_tabs) const 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 /// FILE <-> PIECE
@ -827,7 +832,7 @@ struct tr_torrent
if (auto const latest = std::max(date_started_, date_active_); latest != 0) if (auto const latest = std::max(date_started_, date_active_); latest != 0)
{ {
TR_ASSERT(now >= latest); 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() void do_idle_work()
{ {
do_magnet_idle_work();
if (needs_completeness_check_) if (needs_completeness_check_)
{ {
needs_completeness_check_ = false; needs_completeness_check_ = false;
@ -1244,8 +1251,11 @@ private:
void create_empty_files() const; void create_empty_files() const;
void recheck_completeness(); void recheck_completeness();
void do_magnet_idle_work();
[[nodiscard]] bool use_new_metainfo(tr_error* error); [[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 set_location_in_session_thread(std::string_view path, bool move_from_old_path, int volatile* setme_state);
void rename_path_in_session_thread( void rename_path_in_session_thread(

View File

@ -9,18 +9,18 @@
#include <cstdint> // uint16_t #include <cstdint> // uint16_t
#include <cstring> #include <cstring>
#include <ctime> // time_t #include <ctime> // time_t
#include <functional>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <type_traits>
#include <vector> #include <vector>
#ifdef _WIN32 #ifdef _WIN32
#include <ws2tcpip.h> #include <ws2tcpip.h>
#else #else
#include <sys/socket.h> /* socket(), bind() */
#include <netinet/in.h> /* sockaddr_in */ #include <netinet/in.h> /* sockaddr_in */
#include <sys/socket.h> /* socket(), bind() */
#endif #endif
#include <event2/event.h> #include <event2/event.h>
@ -47,6 +47,8 @@ using namespace std::literals;
namespace namespace
{ {
using ipp_t = std::underlying_type_t<tr_address_type>;
// opaque value, allowing the sending client to filter out its // opaque value, allowing the sending client to filter out its
// own announces if it receives them via multicast loopback // own announces if it receives them via multicast loopback
auto makeCookie() auto makeCookie()
@ -62,8 +64,8 @@ auto makeCookie()
return std::string{ std::data(buf), std::size(buf) }; return std::string{ std::data(buf), std::size(buf) };
} }
constexpr char const* const McastGroup = "239.192.152.143"; /**<LPD multicast group */ auto constexpr McastSockAddr = std::array{ "239.192.152.143:6771"sv, "[ff15::efc0:988f]:6771"sv };
auto constexpr McastPort = tr_port::from_host(6771); /**<LPD source and destination UPD port */ static_assert(std::size(McastSockAddr) == NUM_TR_AF_INET_TYPES);
/* /*
* A LSD announce is formatted as follows: * 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 * multiple infohashes the packet length should not exceed 1400
* bytes to avoid MTU/fragmentation problems. * 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( auto ret = fmt::format(
"BT-SEARCH * HTTP/1.1\r\n" "BT-SEARCH * HTTP/1.1\r\n"
"Host: {:s}:{:d}\r\n" "Host: {:s}\r\n"
"Port: {:d}\r\n", "Port: {:d}\r\n",
McastGroup, McastSockAddr[ip_protocol],
McastPort.host(),
port.host()); port.host());
for (auto const& info_hash : info_hash_strings) for (auto const& info_hash : info_hash_strings)
@ -230,11 +241,17 @@ public:
~tr_lpd_impl() override ~tr_lpd_impl() override
{ {
event_.reset(); for (auto& event : events_)
if (mcast_socket_ != TR_BAD_SOCKET)
{ {
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"); tr_logAddTrace("Done uninitialising Local Peer Discovery");
@ -243,19 +260,34 @@ public:
private: private:
bool init(struct event_base* event_base) 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; if (!initImpl<TR_AF_INET6>(event_base))
tr_net_close_socket(mcast_socket_); {
mcast_socket_ = TR_BAD_SOCKET; auto const err = sockerrno;
tr_logAddWarn(fmt::format( tr_net_close_socket(mcast_sockets_[TR_AF_INET6]);
fmt::runtime(_("Couldn't initialize LPD: {error} ({error_code})")), mcast_sockets_[TR_AF_INET6] = TR_BAD_SOCKET;
fmt::arg("error", tr_strerror(err)), tr_logAddWarn(fmt::format(
fmt::arg("error_code", err))); fmt::runtime(_("Couldn't initialize {ip_protocol} LPD: {error} ({error_code})")),
return false; 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 * For the most part, this means setting up an appropriately configured multicast socket
* and event-based message handling. * 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) bool initImpl(struct event_base* event_base)
{ {
auto const opt_on = 1; auto const opt_on = 1;
auto& sock = mcast_sockets_[ip_protocol];
static_assert(AnnounceScope > 0); 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) if (evutil_make_socket_nonblocking(sock) == -1)
{ {
return false; return false;
} }
if (evutil_make_socket_nonblocking(mcast_socket_) == -1) if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) == -1)
{ {
return false; return false;
} }
if (setsockopt(mcast_socket_, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char const*>(&opt_on), sizeof(opt_on)) ==
-1)
{
return false;
}
#if HAVE_SO_REUSEPORT #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) -1)
{ {
return false; 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( if (setsockopt(
mcast_socket_, sock,
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_,
IPPROTO_IP, IPPROTO_IP,
IP_MULTICAST_TTL, IP_MULTICAST_TTL,
reinterpret_cast<char const*>(&AnnounceScope), reinterpret_cast<char const*>(&AnnounceScope),
@ -340,12 +376,71 @@ private:
{ {
return false; 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)); events_[ip_protocol].reset(event_new(event_base, sock, EV_READ | EV_PERSIST, event_callback<ip_protocol>, this));
event_add(event_.get(), nullptr); 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; return true;
} }
@ -354,27 +449,34 @@ private:
* @brief Processing of timeout notifications and incoming data on the socket * @brief Processing of timeout notifications and incoming data on the socket
* @note maximum rate of read events is limited according to @a lpd_maxAnnounceCap * @note maximum rate of read events is limited according to @a lpd_maxAnnounceCap
* @see DoS */ * @see DoS */
template<tr_address_type ip_protocol>
static void event_callback(evutil_socket_t /*s*/, short type, void* vself) static void event_callback(evutil_socket_t /*s*/, short type, void* vself)
{ {
if ((type & EV_READ) != 0) 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()) if (!mediator_.allowsLPD())
{ {
return; return;
} }
// process announcement from foreign peer // process announcement from foreign peer
struct sockaddr_in foreign_addr = {}; struct sockaddr_storage foreign_addr = {};
auto addr_len = socklen_t{ sizeof(foreign_addr) }; auto addr_len = socklen_t{ sizeof(foreign_addr) };
auto foreign_msg = std::array<char, MaxDatagramLength>{}; auto foreign_msg = std::array<char, MaxDatagramLength>{};
auto const res = recvfrom( auto const res = recvfrom(
mcast_socket_, mcast_sockets_[ip_protocol],
std::data(foreign_msg), std::data(foreign_msg),
MaxDatagramLength, MaxDatagramLength,
0, 0,
@ -386,6 +488,7 @@ private:
{ {
return; 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 // 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) }; auto const msg = std::string_view{ std::data(foreign_msg), static_cast<size_t>(res) };
@ -410,10 +513,14 @@ private:
return; 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) 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)); tr_logAddDebug(fmt::format("Cannot serve torrent #{:s}", hash_string));
} }
@ -462,19 +569,30 @@ private:
std::sort(std::begin(torrents), std::end(torrents), TorrentComparator); std::sort(std::begin(torrents), std::end(torrents), TorrentComparator);
// cram in as many as will fit in a message // cram in as many as will fit in a message
auto const baseline_size = std::size(makeAnnounceMsg(cookie_, mediator_.port(), {})); auto baseline_size = size_t{};
auto const size_with_one = std::size(makeAnnounceMsg(cookie_, mediator_.port(), { torrents.front().info_hash_str })); for (ipp_t ipp = 0; ipp < NUM_TR_AF_INET_TYPES; ++ipp)
auto const size_per_hash = size_with_one - baseline_size; {
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 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>{}; 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::transform(
std::begin(torrents), std::begin(torrents),
std::begin(torrents) + std::size(info_hash_strings), std::begin(torrents) + torrents_this_announce,
std::begin(info_hash_strings), std::back_inserter(info_hash_strings),
[](auto const& tor) { return tor.info_hash_str; }); [](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; return;
} }
@ -508,28 +626,40 @@ private:
* matter). A listening client on the same network might react by adding us to his * matter). A listening client on the same network might react by adding us to his
* peer pool for torrent t. * 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); TR_ASSERT(std::size(announce) <= MaxDatagramLength);
auto const res = sendto( auto const res = sendto(
mcast_socket_, mcast_sockets_[ip_protocol],
std::data(announce), std::data(announce),
std::size(announce), std::size(announce),
0, 0,
reinterpret_cast<sockaddr const*>(&mcast_addr_), ip_protocol == TR_AF_INET ? reinterpret_cast<sockaddr const*>(&mcast_addr_) :
sizeof(mcast_addr_)); reinterpret_cast<sockaddr const*>(&mcast6_addr_),
auto const sent = res == static_cast<int>(std::size(announce)); ip_protocol == TR_AF_INET ? sizeof(mcast_addr_) : sizeof(mcast6_addr_));
return sent; return res == static_cast<int>(std::size(announce));
} }
std::string const cookie_ = makeCookie(); std::string const cookie_ = makeCookie();
Mediator& mediator_; Mediator& mediator_;
tr_socket_t mcast_socket_ = TR_BAD_SOCKET; /**multicast socket */ std::array<tr_socket_t, NUM_TR_AF_INET_TYPES> mcast_sockets_ = { TR_BAD_SOCKET, TR_BAD_SOCKET }; // multicast sockets
libtransmission::evhelpers::event_unique_ptr event_; std::array<libtransmission::evhelpers::event_unique_ptr, NUM_TR_AF_INET_TYPES> events_;
static auto constexpr MaxDatagramLength = size_t{ 1400 }; 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 // BEP14: "To avoid causing multicast storms on large networks a
// client should send no more than 1 announce per minute." // client should send no more than 1 announce per minute."
@ -546,12 +676,11 @@ private:
static auto constexpr MaxIncomingPerSecond = 10; static auto constexpr MaxIncomingPerSecond = 10;
static auto constexpr MaxIncomingPerUpkeep = std::chrono::duration_cast<std::chrono::seconds>(DosInterval).count() * static auto constexpr MaxIncomingPerUpkeep = std::chrono::duration_cast<std::chrono::seconds>(DosInterval).count() *
MaxIncomingPerSecond; MaxIncomingPerSecond;
// @brief throw away messages after this number exceeds MaxIncomingPerUpkeep size_t messages_received_since_upkeep_ = 0U; // throw away messages after this number exceeds MaxIncomingPerUpkeep
size_t messages_received_since_upkeep_ = 0U;
static auto constexpr TorrentAnnounceIntervalSec = time_t{ 240U }; // how frequently to reannounce the same torrent static auto constexpr TorrentAnnounceIntervalSec = time_t{ 240U }; // how frequently to reannounce the same torrent
static auto constexpr TtlSameSubnet = 1; 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) std::unique_ptr<tr_lpd> tr_lpd::create(Mediator& mediator, struct event_base* event_base)

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) 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."); tr_logAddTrace("Couldn't parse UDP tracker packet.");
} }

View File

@ -18,7 +18,6 @@
#include <locale> #include <locale>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <set>
#include <stdexcept> // std::runtime_error #include <stdexcept> // std::runtime_error
#include <string> #include <string>
#include <string_view> #include <string_view>

View File

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

View File

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

View File

@ -291,6 +291,19 @@ std::string_view getSiteName(std::string_view host)
return 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 } // namespace
std::optional<tr_url_parsed_t> tr_urlParse(std::string_view url) 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 = tr_strv_sep(&remain, ':');
} }
parsed.host_wo_brackets = getHostWoBrackets(parsed.host);
parsed.sitename = getSiteName(parsed.host); parsed.sitename = getSiteName(parsed.host);
parsed.port = parsePort(!std::empty(remain) ? remain : getPortForScheme(parsed.scheme)); 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 scheme; // "http"
std::string_view authority; // "example.com:80" std::string_view authority; // "example.com:80"
std::string_view host; // "example.com" std::string_view host; // "example.com"
std::string_view host_wo_brackets; // "example.com" ("[::1]" -> "::1")
std::string_view sitename; // "example" std::string_view sitename; // "example"
std::string_view path; // /"over/there" std::string_view path; // /"over/there"
std::string_view query; // "name=ferret" std::string_view query; // "name=ferret"

View File

@ -381,14 +381,14 @@ static void removeKeRangerRansomware()
[NSValueTransformer setValueTransformer:iconTransformer forName:@"ExpandedPathToIconTransformer"]; [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(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
auto* const torrent = [controller torrentForHash:hashstr]; //posting asynchronously with coalescing to prevent stack overflow on lots of torrents changing state at the same time
[torrent startQueue]; [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]; [nc addObserver:self.fWindow selector:@selector(makeKeyWindow) name:@"MakeWindowKey" object:nil];
#warning rename [nc addObserver:self selector:@selector(fullUpdateUI) name:@"UpdateTorrentsState" object:nil];
[nc addObserver:self selector:@selector(fullUpdateUI) name:@"UpdateQueue" object:nil];
[nc addObserver:self selector:@selector(applyFilter) name:@"ApplyFilter" 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) { [FileRenameSheetController presentSheetForTorrent:torrent modalForWindow:self.fOutline.window completionHandler:^(BOOL didRename) {
if (didRename) if (didRename)
{ {
[NSNotificationCenter.defaultCenter postNotificationName:@"UpdateQueue" object:self]; [NSNotificationCenter.defaultCenter postNotificationName:@"UpdateTorrentsState" object:nil];
[NSNotificationCenter.defaultCenter postNotificationName:@"ResetInspector" object:self [NSNotificationCenter.defaultCenter postNotificationName:@"ResetInspector" object:self
userInfo:@{ @"Torrent" : torrent }]; 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 - (CGFloat)tableView:(NSTableView*)tableView heightOfRow:(NSInteger)row
{ {
NSString* message = self.fDisplayedMessages[row][@"Message"]; 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"]); tr_sessionSetQueueEnabled(self.fHandle, TR_UP, [self.fDefaults boolForKey:@"QueueSeed"]);
//handle if any transfers switch from queued to paused //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 - (void)setQueueNumber:(id)sender

View File

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

View File

@ -42,7 +42,6 @@ extern NSString* const kTorrentDidChangeGroupNotification;
- (void)startTransfer; - (void)startTransfer;
- (void)startMagnetTransferAfterMetaDownload; - (void)startMagnetTransferAfterMetaDownload;
- (void)stopTransfer; - (void)stopTransfer;
- (void)startQueue;
- (void)sleep; - (void)sleep;
- (void)wakeUp; - (void)wakeUp;
- (void)idleLimitHit; - (void)idleLimitHit;

View File

@ -251,7 +251,7 @@ bool trashDataFile(char const* filename, void* /*user_data*/, tr_error* error)
if (wasTransmitting != self.transmitting) if (wasTransmitting != self.transmitting)
{ {
//posting asynchronously with coalescing to prevent stack overflow on lots of torrents changing state at the same time //posting asynchronously with coalescing to prevent stack overflow on lots of torrents changing state at the same time
[NSNotificationQueue.defaultQueue enqueueNotification:[NSNotification notificationWithName:@"UpdateQueue" object:self] [NSNotificationQueue.defaultQueue enqueueNotification:[NSNotification notificationWithName:@"UpdateTorrentsState" object:nil]
postingStyle:NSPostASAP postingStyle:NSPostASAP
coalesceMask:NSNotificationCoalescingOnName coalesceMask:NSNotificationCoalescingOnName
forModes:nil]; forModes:nil];
@ -1982,11 +1982,6 @@ bool trashDataFile(char const* filename, void* /*user_data*/, tr_error* error)
}]; }];
} }
- (void)startQueue
{
[NSNotificationCenter.defaultCenter postNotificationName:@"UpdateQueue" object:self];
}
- (void)completenessChange:(tr_completeness)status wasRunning:(BOOL)wasRunning - (void)completenessChange:(tr_completeness)status wasRunning:(BOOL)wasRunning
{ {
self.fStat = tr_torrentStat(self.fHandle); //don't call update yet to avoid auto-stop self.fStat = tr_torrentStat(self.fHandle); //don't call update yet to avoid auto-stop

View File

@ -23,16 +23,16 @@
<li>Make sure you cap your upload speed, so that it isn't flooded. A good rule of thumb is about 60-70% of your maximum upload bandwidth. This can be adjusted in Preferences → Bandwidth, or in real time using the Action menu. <li>Make sure you cap your upload speed, so that it isn't flooded. A good rule of thumb is about 60-70% of your maximum upload bandwidth. This can be adjusted in Preferences → Bandwidth, or in real time using the Action menu.
<div class="taskbox"> <div class="taskbox">
<p>eg. If your upload connection is 256 Kilobits/sec, then you should cap it at 21 KB/sec ((<strong>256</strong> / 8) * 0.66 = <strong>21</strong>). <p>eg. If your upload connection is 256 Kilobits/second, then you should cap it at 21 KB/s ((<strong>256</strong> / 8) * 0.66 = <strong>21</strong>).
</div> </div>
</li> </li>
<li><a href="gettingstarted.html#queue">Queue</a> your transfers. Transmission's queue preferences are located in Transfers → Management. <li><a href="gettingstarted.html#queue">Queue</a> your transfers. Transmission's queue preferences are located in Transfers → Management.
<p>Remember, your download speed is proportional to how fast you upload. If there are many transfers running, then each transfer will only receive a small proportion of your upload bandwidth, reducing their respective download speeds. <p>Remember, your download speed is proportional to how fast you upload. If there are many transfers running, then each transfer will only receive a small proportion of your upload bandwidth, reducing their respective download speeds.
To avoid spreading your upload too thinly, a good rule of thumb is to have at least 128 KBit/sec of upload bandwidth for every torrent you wish to run simultaneously. To avoid spreading your upload too thinly, a good rule of thumb is to have at least 128 Kb/s of upload bandwidth for every torrent you wish to run simultaneously.
<div class="taskbox"> <div class="taskbox">
<p>eg. If your upload bandwidth is 256 KBit/sec, then you should only have two (<strong>256</strong>/128 = <strong>2</strong>) downloading transfers in the queue. <p>eg. If your upload bandwidth is 256 Kb/s, then you should only have two (<strong>256</strong>/128 = <strong>2</strong>) downloading transfers in the queue.
</div> </div>
</li> </li>
</ol> </ol>

View File

@ -6,7 +6,6 @@
#include "FilterBar.h" #include "FilterBar.h"
#include <cstdint> // uint64_t #include <cstdint> // uint64_t
#include <map>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>

View File

@ -5,7 +5,6 @@
#pragma once #pragma once
#include <map>
#include <optional> #include <optional>
#include <vector> #include <vector>

View File

@ -6,7 +6,6 @@
#pragma once #pragma once
#include <array> #include <array>
#include <set>
#include <QObject> #include <QObject>
#include <QString> #include <QString>

View File

@ -6,7 +6,6 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cassert> #include <cassert>
#include <optional>
#include <string_view> #include <string_view>
#include <utility> #include <utility>

View File

@ -8,7 +8,6 @@
#include <cstddef> #include <cstddef>
#include <ctime> #include <ctime>
#include <iterator> // for std::back_inserter #include <iterator> // for std::back_inserter
#include <set>
#include <string_view> #include <string_view>
#include <vector> #include <vector>

View File

@ -15,6 +15,7 @@
#include <optional> #include <optional>
#include <string_view> #include <string_view>
#include <tuple> #include <tuple>
#include <type_traits>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -45,6 +46,9 @@
using namespace std::literals; using namespace std::literals;
using tau_connection_t = uint64_t;
using tau_transaction_t = uint32_t;
using MessageBuffer = libtransmission::StackBuffer<4096, std::byte>; using MessageBuffer = libtransmission::StackBuffer<4096, std::byte>;
class AnnouncerUdpTest : public ::testing::Test class AnnouncerUdpTest : public ::testing::Test
@ -132,7 +136,7 @@ protected:
} }
} }
[[nodiscard]] static uint32_t parseConnectionRequest(std::vector<char> const& data) [[nodiscard]] static tau_transaction_t parseConnectionRequest(std::vector<char> const& data)
{ {
auto buf = MessageBuffer(data); auto buf = MessageBuffer(data);
EXPECT_EQ(ProtocolId, buf.to_uint64()); EXPECT_EQ(ProtocolId, buf.to_uint64());
@ -169,7 +173,7 @@ protected:
return std::make_pair(buildScrapeRequestFromResponse(response), response); return std::make_pair(buildScrapeRequestFromResponse(response), response);
} }
[[nodiscard]] static auto parseScrapeRequest(std::vector<char> const& data, uint64_t expected_connection_id) [[nodiscard]] static auto parseScrapeRequest(std::vector<char> const& data, tau_connection_t expected_connection_id)
{ {
auto buf = MessageBuffer(data); auto buf = MessageBuffer(data);
EXPECT_EQ(expected_connection_id, buf.to_uint64()); EXPECT_EQ(expected_connection_id, buf.to_uint64());
@ -187,13 +191,19 @@ protected:
[[nodiscard]] static auto waitForAnnouncerToSendMessage(MockMediator& mediator) [[nodiscard]] static auto waitForAnnouncerToSendMessage(MockMediator& mediator)
{ {
libtransmission::test::waitFor(mediator.eventBase(), [&mediator]() { return !std::empty(mediator.sent_); }); EXPECT_TRUE(
libtransmission::test::waitFor(mediator.eventBase(), [&mediator]() { return !std::empty(mediator.sent_); }));
auto buf = std::move(mediator.sent_.back().buf_); auto buf = std::move(mediator.sent_.back().buf_);
mediator.sent_.pop_back(); mediator.sent_.pop_back();
return buf; return buf;
} }
[[nodiscard]] static bool sendError(tr_announcer_udp& announcer, uint32_t transaction_id, std::string_view errmsg) [[nodiscard]] static bool sendError(
tr_announcer_udp& announcer,
tau_transaction_t transaction_id,
std::string_view errmsg,
struct sockaddr const* from,
socklen_t fromlen)
{ {
auto buf = MessageBuffer{}; auto buf = MessageBuffer{};
buf.add_uint32(ErrorAction); buf.add_uint32(ErrorAction);
@ -204,21 +214,25 @@ protected:
auto arr = std::array<uint8_t, 256>{}; auto arr = std::array<uint8_t, 256>{};
buf.to_buf(std::data(arr), response_size); buf.to_buf(std::data(arr), response_size);
return announcer.handle_message(std::data(arr), response_size); return announcer.handle_message(std::data(arr), response_size, from, fromlen);
} }
[[nodiscard]] static auto sendConnectionResponse(tr_announcer_udp& announcer, uint32_t transaction_id) [[nodiscard]] static auto sendConnectionResponse(
tr_announcer_udp& announcer,
tau_transaction_t transaction_id,
struct sockaddr const* from,
socklen_t fromlen)
{ {
auto const connection_id = tr_rand_obj<uint64_t>(); auto const connection_id = tr_rand_obj<tau_connection_t>();
auto buf = MessageBuffer{}; auto buf = MessageBuffer{};
buf.add_uint32(ConnectAction); buf.add_uint32(ConnectAction);
buf.add_uint32(transaction_id); buf.add_uint32(transaction_id);
buf.add_uint64(connection_id); buf.add_uint64(connection_id);
auto arr = std::array<uint8_t, 128>{}; auto arr = std::array<uint8_t, 16>{};
auto response_size = std::size(buf); auto response_size = std::size(buf);
buf.to_buf(std::data(arr), response_size); buf.to_buf(std::data(arr), response_size);
EXPECT_TRUE(announcer.handle_message(std::data(arr), response_size)); EXPECT_TRUE(announcer.handle_message(std::data(arr), response_size, from, fromlen));
return connection_id; return connection_id;
} }
@ -227,7 +241,7 @@ protected:
{ {
uint64_t connection_id = 0; uint64_t connection_id = 0;
uint32_t action = 0; // 1: announce uint32_t action = 0; // 1: announce
uint32_t transaction_id = 0; tau_transaction_t transaction_id = 0;
tr_sha1_digest_t info_hash = {}; tr_sha1_digest_t info_hash = {};
tr_peer_id_t peer_id = {}; tr_peer_id_t peer_id = {};
uint64_t downloaded = 0; uint64_t downloaded = 0;
@ -307,6 +321,16 @@ protected:
return timer; return timer;
} }
static auto sockaddrFromUrl(std::string_view tracker_url)
{
auto parsed_url = tr_urlParse(tracker_url);
EXPECT_TRUE(parsed_url);
auto addr = tr_address::from_string(parsed_url->host);
EXPECT_TRUE(addr);
return tr_socket_address{ *addr, tr_port::from_host(parsed_url->port) }.to_sockaddr();
}
std::unique_ptr<tr_net_init_mgr> init_mgr_; std::unique_ptr<tr_net_init_mgr> init_mgr_;
// https://www.bittorrent.org/beps/bep_0015.html // https://www.bittorrent.org/beps/bep_0015.html
@ -337,12 +361,16 @@ TEST_F(AnnouncerUdpTest, canScrape)
auto response = std::optional<tr_scrape_response>{}; auto response = std::optional<tr_scrape_response>{};
announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; }); announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; });
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(request.scrape_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
// The announcer should have sent a UDP connection request. // The announcer should have sent a UDP connection request.
// Inspect that request for validity. // Inspect that request for validity.
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator));
// Have the tracker respond to the request // Have the tracker respond to the request
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id); auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
// The announcer should have sent a UDP scrape request. // The announcer should have sent a UDP scrape request.
// Inspect that request for validity. // Inspect that request for validity.
@ -359,7 +387,7 @@ TEST_F(AnnouncerUdpTest, canScrape)
auto response_size = std::size(buf); auto response_size = std::size(buf);
auto arr = std::array<uint8_t, 256>{}; auto arr = std::array<uint8_t, 256>{};
buf.to_buf(std::data(arr), response_size); buf.to_buf(std::data(arr), response_size);
EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size)); EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen));
// confirm that announcer processed the response // confirm that announcer processed the response
EXPECT_TRUE(response.has_value()); EXPECT_TRUE(response.has_value());
@ -413,13 +441,17 @@ TEST_F(AnnouncerUdpTest, canMultiScrape)
expected_response.scrape_url = DefaultScrapeUrl; expected_response.scrape_url = DefaultScrapeUrl;
expected_response.min_request_interval = 0; expected_response.min_request_interval = 0;
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(expected_response.scrape_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto request = buildScrapeRequestFromResponse(expected_response); auto request = buildScrapeRequestFromResponse(expected_response);
auto response = std::optional<tr_scrape_response>{}; auto response = std::optional<tr_scrape_response>{};
announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; }); announcer->scrape(request, [&response](tr_scrape_response const& resp) { response = resp; });
// Announcer will request a connection. Verify and grant the request // Announcer will request a connection. Verify and grant the request
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator));
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id); auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
// The announcer should have sent a UDP scrape request. // The announcer should have sent a UDP scrape request.
// Inspect that request for validity. // Inspect that request for validity.
@ -439,7 +471,7 @@ TEST_F(AnnouncerUdpTest, canMultiScrape)
auto response_size = std::size(buf); auto response_size = std::size(buf);
auto arr = std::array<uint8_t, 256>{}; auto arr = std::array<uint8_t, 256>{};
buf.to_buf(std::data(arr), response_size); buf.to_buf(std::data(arr), response_size);
EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size)); EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen));
// Confirm that announcer processed the response // Confirm that announcer processed the response
EXPECT_TRUE(response.has_value()); EXPECT_TRUE(response.has_value());
@ -463,6 +495,10 @@ TEST_F(AnnouncerUdpTest, canHandleScrapeError)
expected_response.min_request_interval = 0; expected_response.min_request_interval = 0;
expected_response.errmsg = "Unrecognized info-hash"; expected_response.errmsg = "Unrecognized info-hash";
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(expected_response.scrape_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
// build the request // build the request
auto request = buildScrapeRequestFromResponse(expected_response); auto request = buildScrapeRequestFromResponse(expected_response);
@ -480,7 +516,7 @@ TEST_F(AnnouncerUdpTest, canHandleScrapeError)
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator));
// Have the tracker respond to the request // Have the tracker respond to the request
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id); auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
// The announcer should have sent a UDP scrape request. // The announcer should have sent a UDP scrape request.
// Inspect that request for validity. // Inspect that request for validity.
@ -489,7 +525,7 @@ TEST_F(AnnouncerUdpTest, canHandleScrapeError)
connection_id); connection_id);
// Have the tracker respond to the request with an "unable to scrape" error // Have the tracker respond to the request with an "unable to scrape" error
EXPECT_TRUE(sendError(*announcer, scrape_transaction_id, expected_response.errmsg)); EXPECT_TRUE(sendError(*announcer, scrape_transaction_id, expected_response.errmsg, from_ptr, fromlen));
// confirm that announcer processed the response // confirm that announcer processed the response
EXPECT_TRUE(response.has_value()); EXPECT_TRUE(response.has_value());
@ -513,6 +549,10 @@ TEST_F(AnnouncerUdpTest, canHandleConnectError)
expected_response.min_request_interval = 0; expected_response.min_request_interval = 0;
expected_response.errmsg = "Unable to Connect"; expected_response.errmsg = "Unable to Connect";
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(expected_response.scrape_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
// build the announcer // build the announcer
auto mediator = MockMediator{}; auto mediator = MockMediator{};
auto announcer = tr_announcer_udp::create(mediator); auto announcer = tr_announcer_udp::create(mediator);
@ -529,7 +569,7 @@ TEST_F(AnnouncerUdpTest, canHandleConnectError)
auto transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator));
// Have the tracker respond to the request with an "unable to connect" error // Have the tracker respond to the request with an "unable to connect" error
EXPECT_TRUE(sendError(*announcer, transaction_id, expected_response.errmsg)); EXPECT_TRUE(sendError(*announcer, transaction_id, expected_response.errmsg, from_ptr, fromlen));
// Confirm that announcer processed the response // Confirm that announcer processed the response
EXPECT_TRUE(response.has_value()); EXPECT_TRUE(response.has_value());
@ -545,6 +585,10 @@ TEST_F(AnnouncerUdpTest, handleMessageReturnsFalseOnInvalidMessage)
request.info_hash_count = 1; request.info_hash_count = 1;
request.info_hash[0] = tr_rand_obj<tr_sha1_digest_t>(); request.info_hash[0] = tr_rand_obj<tr_sha1_digest_t>();
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(request.scrape_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
// build the announcer // build the announcer
auto mediator = MockMediator{}; auto mediator = MockMediator{};
auto announcer = tr_announcer_udp::create(mediator); auto announcer = tr_announcer_udp::create(mediator);
@ -566,7 +610,7 @@ TEST_F(AnnouncerUdpTest, handleMessageReturnsFalseOnInvalidMessage)
auto response_size = std::size(buf); auto response_size = std::size(buf);
auto arr = std::array<uint8_t, 256>{}; auto arr = std::array<uint8_t, 256>{};
buf.to_buf(std::data(arr), response_size); buf.to_buf(std::data(arr), response_size);
EXPECT_FALSE(announcer->handle_message(std::data(arr), response_size)); EXPECT_FALSE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen));
// send a connection response but with an *invalid* action // send a connection response but with an *invalid* action
buf.clear(); buf.clear();
@ -575,15 +619,15 @@ TEST_F(AnnouncerUdpTest, handleMessageReturnsFalseOnInvalidMessage)
buf.add_uint64(tr_rand_obj<uint64_t>()); buf.add_uint64(tr_rand_obj<uint64_t>());
response_size = std::size(buf); response_size = std::size(buf);
buf.to_buf(std::data(arr), response_size); buf.to_buf(std::data(arr), response_size);
EXPECT_FALSE(announcer->handle_message(std::data(arr), response_size)); EXPECT_FALSE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen));
// but after discarding invalid messages, // but after discarding invalid messages,
// a valid connection response should still work // a valid connection response should still work
auto const connection_id = sendConnectionResponse(*announcer, transaction_id); auto const connection_id = sendConnectionResponse(*announcer, transaction_id, from_ptr, fromlen);
EXPECT_NE(0, connection_id); EXPECT_NE(0, connection_id);
} }
TEST_F(AnnouncerUdpTest, canAnnounce) TEST_F(AnnouncerUdpTest, canAnnounceIPv4)
{ {
static auto constexpr Interval = uint32_t{ 3600 }; static auto constexpr Interval = uint32_t{ 3600 };
static auto constexpr Leechers = uint32_t{ 10 }; static auto constexpr Leechers = uint32_t{ 10 };
@ -608,6 +652,10 @@ TEST_F(AnnouncerUdpTest, canAnnounce)
request.peer_id = tr_peerIdInit(); request.peer_id = tr_peerIdInit();
request.info_hash = tr_rand_obj<tr_sha1_digest_t>(); request.info_hash = tr_rand_obj<tr_sha1_digest_t>();
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(request.announce_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto expected_response = tr_announce_response{}; auto expected_response = tr_announce_response{};
expected_response.info_hash = request.info_hash; expected_response.info_hash = request.info_hash;
expected_response.did_connect = true; expected_response.did_connect = true;
@ -634,7 +682,7 @@ TEST_F(AnnouncerUdpTest, canAnnounce)
// Announcer will request a connection. Verify and grant the request // Announcer will request a connection. Verify and grant the request
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator)); auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator));
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id); auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
// The announcer should have sent a UDP announce request. // The announcer should have sent a UDP announce request.
// Inspect that request for validity. // Inspect that request for validity.
@ -658,7 +706,96 @@ TEST_F(AnnouncerUdpTest, canAnnounce)
auto response_size = std::size(buf); auto response_size = std::size(buf);
auto arr = std::array<uint8_t, 512>{}; auto arr = std::array<uint8_t, 512>{};
buf.to_buf(std::data(arr), response_size); buf.to_buf(std::data(arr), response_size);
EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size));
EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen));
// Confirm that announcer processed the response
EXPECT_TRUE(response.has_value());
assert(response.has_value());
expectEqual(expected_response, *response);
}
TEST_F(AnnouncerUdpTest, canAnnounceIPv6)
{
static auto constexpr Interval = uint32_t{ 3600 };
static auto constexpr Leechers = uint32_t{ 10 };
static auto constexpr Seeders = uint32_t{ 20 };
auto const addresses = std::array<tr_socket_address, 3>{ {
{ tr_address::from_string("fd12:3456:789a:1::1").value_or(tr_address{}), tr_port::from_host(128) },
{ tr_address::from_string("fd12:3456:789a:1::2").value_or(tr_address{}), tr_port::from_host(2021) },
{ tr_address::from_string("fd12:3456:789a:1::3").value_or(tr_address{}), tr_port::from_host(2022) },
} };
auto request = tr_announce_request{};
request.event = TR_ANNOUNCE_EVENT_STARTED;
request.port = tr_port::from_host(80);
request.key = 0xCAFE;
request.numwant = 20;
request.up = 1;
request.down = 2;
request.corrupt = 3;
request.leftUntilComplete = 100;
request.announce_url = "https://[::1]/announce";
request.tracker_id = "fnord";
request.peer_id = tr_peerIdInit();
request.info_hash = tr_rand_obj<tr_sha1_digest_t>();
// Obtain the source socket address from tracker url
auto [from, fromlen] = sockaddrFromUrl(request.announce_url);
auto const* const from_ptr = reinterpret_cast<struct sockaddr*>(&from);
auto expected_response = tr_announce_response{};
expected_response.info_hash = request.info_hash;
expected_response.did_connect = true;
expected_response.did_timeout = false;
expected_response.interval = Interval;
expected_response.min_interval = 0; // not specified in UDP announce
expected_response.seeders = Seeders;
expected_response.leechers = Leechers;
expected_response.downloads = std::nullopt; // not specified in UDP announce
expected_response.pex = {};
expected_response.pex6 = std::vector<tr_pex>{ tr_pex{ addresses[0] }, tr_pex{ addresses[1] }, tr_pex{ addresses[2] } };
expected_response.errmsg = {};
expected_response.warning = {};
expected_response.tracker_id = {}; // not specified in UDP announce
expected_response.external_ip = {};
// build the announcer
auto mediator = MockMediator{};
auto announcer = tr_announcer_udp::create(mediator);
auto upkeep_timer = createUpkeepTimer(mediator, announcer);
auto response = std::optional<tr_announce_response>{};
announcer->announce(request, [&response](tr_announce_response const& resp) { response = resp; });
// Announcer will request a connection. Verify and grant the request
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator));
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
// The announcer should have sent a UDP announce request.
// Inspect that request for validity.
auto udp_ann_req = parseAnnounceRequest(waitForAnnouncerToSendMessage(mediator), connection_id);
expectEqual(request, udp_ann_req);
// Have the tracker respond to the request
auto buf = MessageBuffer{};
buf.add_uint32(AnnounceAction);
buf.add_uint32(udp_ann_req.transaction_id);
buf.add_uint32(expected_response.interval);
buf.add_uint32(expected_response.leechers.value_or(-1));
buf.add_uint32(expected_response.seeders.value_or(-1));
for (auto const& [addr, port] : addresses)
{
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access)
buf.add(&addr.addr.addr6.s6_addr, sizeof(addr.addr.addr6.s6_addr));
buf.add_uint16(port.host());
}
auto response_size = std::size(buf);
auto arr = std::array<uint8_t, 512>{};
buf.to_buf(std::data(arr), response_size);
EXPECT_TRUE(announcer->handle_message(std::data(arr), response_size, from_ptr, fromlen));
// Confirm that announcer processed the response // Confirm that announcer processed the response
EXPECT_TRUE(response.has_value()); EXPECT_TRUE(response.has_value());

View File

@ -41,9 +41,11 @@ public:
{ {
} }
[[nodiscard]] tr_address bind_address(tr_address_type /* type */) const override [[nodiscard]] tr_address bind_address(tr_address_type type) const override
{ {
return {}; auto ret = tr_address{};
ret.type = type;
return ret;
} }
[[nodiscard]] tr_port port() const override [[nodiscard]] tr_port port() const override

View File

@ -84,7 +84,7 @@ protected:
EXPECT_EQ(builder.total_size(), metainfo.total_size()); EXPECT_EQ(builder.total_size(), metainfo.total_size());
for (size_t i = 0, n = std::min(builder.file_count(), metainfo.file_count()); i < n; ++i) for (size_t i = 0, n = std::min(builder.file_count(), metainfo.file_count()); i < n; ++i)
{ {
EXPECT_EQ(builder.file_size(i), metainfo.files().fileSize(i)); EXPECT_EQ(builder.file_size(i), metainfo.files().file_size(i));
EXPECT_EQ(builder.path(i), metainfo.files().path(i)); EXPECT_EQ(builder.path(i), metainfo.files().path(i));
} }
EXPECT_EQ(builder.name(), metainfo.name()); EXPECT_EQ(builder.name(), metainfo.name());

View File

@ -56,6 +56,7 @@ TEST_P(IncompleteDirTest, incompleteDir)
// init an incomplete torrent. // init an incomplete torrent.
// the test zero_torrent will be missing its first piece. // the test zero_torrent will be missing its first piece.
tr_sessionSetIncompleteFileNamingEnabled(session_, true);
auto* const tor = zeroTorrentInit(ZeroTorrentState::Partial); auto* const tor = zeroTorrentInit(ZeroTorrentState::Partial);
auto path = tr_pathbuf{}; auto path = tr_pathbuf{};

View File

@ -176,7 +176,7 @@ protected:
{ {
auto paths = std::set<std::string>{}; auto paths = std::set<std::string>{};
for (tr_file_index_t i = 0, n = files.fileCount(); i < n; ++i) for (tr_file_index_t i = 0, n = files.file_count(); i < n; ++i)
{ {
auto walk = tr_pathbuf{ parent, '/', files.path(i) }; auto walk = tr_pathbuf{ parent, '/', files.path(i) };
createFileWithContents(walk, std::data(Content), std::size(Content)); createFileWithContents(walk, std::data(Content), std::size(Content));
@ -332,7 +332,7 @@ TEST_F(RemoveTest, PreservesDirectoryHierarchyIfPossible)
// after remove, the subtree should be: // after remove, the subtree should be:
expected_tree = { parent, recycle_bin.c_str() }; expected_tree = { parent, recycle_bin.c_str() };
for (tr_file_index_t i = 0, n = files.fileCount(); i < n; ++i) for (tr_file_index_t i = 0, n = files.file_count(); i < n; ++i)
{ {
expected_tree.emplace(tr_pathbuf{ recycle_bin, '/', files.path(i) }); expected_tree.emplace(tr_pathbuf{ recycle_bin, '/', files.path(i) });
} }

View File

@ -449,6 +449,8 @@ TEST_F(RenameTest, partialFile)
**** create our test torrent with an incomplete .part file **** create our test torrent with an incomplete .part file
***/ ***/
tr_sessionSetIncompleteFileNamingEnabled(session_, true);
auto* tor = zeroTorrentInit(ZeroTorrentState::Partial); auto* tor = zeroTorrentInit(ZeroTorrentState::Partial);
EXPECT_EQ(TotalSize, tor->total_size()); EXPECT_EQ(TotalSize, tor->total_size());
EXPECT_EQ(PieceSize, tor->piece_size()); EXPECT_EQ(PieceSize, tor->piece_size());

View File

@ -32,13 +32,13 @@ TEST_F(TorrentFilesTest, add)
auto constexpr Size = size_t{ 1024 }; auto constexpr Size = size_t{ 1024 };
auto files = tr_torrent_files{}; auto files = tr_torrent_files{};
EXPECT_EQ(size_t{ 0U }, files.fileCount()); EXPECT_EQ(size_t{ 0U }, files.file_count());
EXPECT_TRUE(std::empty(files)); EXPECT_TRUE(std::empty(files));
auto const file_index = files.add(Path, Size); auto const file_index = files.add(Path, Size);
EXPECT_EQ(tr_file_index_t{ 0U }, file_index); EXPECT_EQ(tr_file_index_t{ 0U }, file_index);
EXPECT_EQ(size_t{ 1U }, files.fileCount()); EXPECT_EQ(size_t{ 1U }, files.file_count());
EXPECT_EQ(Size, files.fileSize(file_index)); EXPECT_EQ(Size, files.file_size(file_index));
EXPECT_EQ(Path, files.path(file_index)); EXPECT_EQ(Path, files.path(file_index));
EXPECT_FALSE(std::empty(files)); EXPECT_FALSE(std::empty(files));
} }
@ -52,11 +52,11 @@ TEST_F(TorrentFilesTest, setPath)
auto files = tr_torrent_files{}; auto files = tr_torrent_files{};
auto const file_index = files.add(Path1, Size); auto const file_index = files.add(Path1, Size);
EXPECT_EQ(Path1, files.path(file_index)); EXPECT_EQ(Path1, files.path(file_index));
EXPECT_EQ(Size, files.fileSize(file_index)); EXPECT_EQ(Size, files.file_size(file_index));
files.setPath(file_index, Path2); files.set_path(file_index, Path2);
EXPECT_EQ(Path2, files.path(file_index)); EXPECT_EQ(Path2, files.path(file_index));
EXPECT_EQ(Size, files.fileSize(file_index)); EXPECT_EQ(Size, files.file_size(file_index));
} }
TEST_F(TorrentFilesTest, clear) TEST_F(TorrentFilesTest, clear)
@ -67,13 +67,13 @@ TEST_F(TorrentFilesTest, clear)
auto files = tr_torrent_files{}; auto files = tr_torrent_files{};
files.add(Path1, Size); files.add(Path1, Size);
EXPECT_EQ(size_t{ 1U }, files.fileCount()); EXPECT_EQ(size_t{ 1U }, files.file_count());
files.add(Path2, Size); files.add(Path2, Size);
EXPECT_EQ(size_t{ 2U }, files.fileCount()); EXPECT_EQ(size_t{ 2U }, files.file_count());
files.clear(); files.clear();
EXPECT_TRUE(std::empty(files)); EXPECT_TRUE(std::empty(files));
EXPECT_EQ(size_t{ 0U }, files.fileCount()); EXPECT_EQ(size_t{ 0U }, files.file_count());
} }
TEST_F(TorrentFilesTest, find) TEST_F(TorrentFilesTest, find)
@ -135,10 +135,10 @@ TEST_F(TorrentFilesTest, hasAnyLocalData)
auto const search_path_2 = tr_pathbuf{ "/tmp"sv }; auto const search_path_2 = tr_pathbuf{ "/tmp"sv };
auto search_path = std::vector<std::string_view>{ search_path_1.sv(), search_path_2.sv() }; auto search_path = std::vector<std::string_view>{ search_path_1.sv(), search_path_2.sv() };
EXPECT_TRUE(files.hasAnyLocalData(std::data(search_path), 2U)); EXPECT_TRUE(files.has_any_local_data(std::data(search_path), 2U));
EXPECT_TRUE(files.hasAnyLocalData(std::data(search_path), 1U)); EXPECT_TRUE(files.has_any_local_data(std::data(search_path), 1U));
EXPECT_FALSE(files.hasAnyLocalData(std::data(search_path) + 1, 1U)); EXPECT_FALSE(files.has_any_local_data(std::data(search_path) + 1, 1U));
EXPECT_FALSE(files.hasAnyLocalData(std::data(search_path), 0U)); EXPECT_FALSE(files.has_any_local_data(std::data(search_path), 0U));
} }
TEST_F(TorrentFilesTest, isSubpathPortable) TEST_F(TorrentFilesTest, isSubpathPortable)
@ -179,6 +179,6 @@ TEST_F(TorrentFilesTest, isSubpathPortable)
for (auto const& [subpath, expected] : Tests) for (auto const& [subpath, expected] : Tests)
{ {
EXPECT_EQ(expected, tr_torrent_files::isSubpathPortable(subpath)) << " subpath " << subpath; EXPECT_EQ(expected, tr_torrent_files::is_subpath_sanitized(subpath)) << " subpath " << subpath;
} }
} }

View File

@ -85,6 +85,7 @@ TEST_F(TorrentMagnetTest, setMetadataPiece)
tor->maybe_start_metadata_transfer(metainfo_size); tor->maybe_start_metadata_transfer(metainfo_size);
tor->set_metadata_piece(0, std::data(metainfo_benc), metainfo_size); tor->set_metadata_piece(0, std::data(metainfo_benc), metainfo_size);
tor->do_idle_work();
EXPECT_TRUE(tor->has_metainfo()); EXPECT_TRUE(tor->has_metainfo());
EXPECT_EQ(tor->info_dict_size(), metainfo_size); EXPECT_EQ(tor->info_dict_size(), metainfo_size);
EXPECT_EQ(tor->get_metadata_percent(), 1.0); EXPECT_EQ(tor->get_metadata_percent(), 1.0);

View File

@ -1,3 +1,3 @@
--- ---
DisableFormat: true DisableFormat: true
SortIncludes: false SortIncludes: Never

@ -1 +1 @@
Subproject commit 7f189988a0decca0ab7da89000051ab91751f70d Subproject commit b55145ec095652289a59c33603f3abafee898273

View File

@ -217,11 +217,11 @@ int tr_main(int argc, char* argv[])
for (tr_file_index_t i = 0; i < n_files; ++i) for (tr_file_index_t i = 0; i < n_files; ++i)
{ {
auto const& path = builder.path(i); auto const& path = builder.path(i);
if (!tr_torrent_files::isSubpathPortable(path)) if (!tr_torrent_files::is_subpath_sanitized(path, false))
{ {
fmt::print(stderr, "WARNING\n"); fmt::print(stderr, "WARNING\n");
fmt::print(stderr, "filename \"{:s}\" may not be portable on all systems.\n", path); fmt::print(stderr, "filename \"{:s}\" may not be portable on all systems.\n", path);
fmt::print(stderr, "consider \"{:s}\" instead.\n", tr_torrent_files::makeSubpathPortable(path)); fmt::print(stderr, "consider \"{:s}\" instead.\n", tr_torrent_files::sanitize_subpath(path, false));
} }
} }

View File

@ -212,7 +212,7 @@ enum
// --- Command-Line Arguments // --- Command-Line Arguments
auto constexpr Options = std::array<tr_option, 98>{ static auto constexpr Options = std::array<tr_option, 108>{
{ { 'a', "add", "Add torrent files by filename or URL", "a", false, nullptr }, { { 'a', "add", "Add torrent files by filename or URL", "a", false, nullptr },
{ 970, "alt-speed", "Use the alternate Limits", "as", false, nullptr }, { 970, "alt-speed", "Use the alternate Limits", "as", false, nullptr },
{ 971, "no-alt-speed", "Don't use the alternate Limits", "AS", false, nullptr }, { 971, "no-alt-speed", "Don't use the alternate Limits", "AS", false, nullptr },
@ -307,6 +307,31 @@ auto constexpr Options = std::array<tr_option, 98>{
"GSR", "GSR",
false, false,
nullptr }, nullptr },
{ 955,
"idle-seeding-limit",
"Let the current torrent(s) seed until a specific amount idle time",
"isl",
true,
"<minutes>" },
{ 956,
"default-idle-seeding-limit",
"Let the current torrent(s) use the default idle seeding settings",
"isld",
false,
nullptr },
{ 957, "no-idle-seeding-limit", "Let the current torrent(s) seed regardless of idle time", "ISL", false, nullptr },
{ 958,
"global-idle-seeding-limit",
"All torrents, unless overridden by a per-torrent setting, should seed until a specific amount of idle time",
"gisl",
true,
"<minutes>" },
{ 959,
"no-global-idle-seeding-limit",
"All torrents, unless overridden by a per-torrent setting, should seed regardless of idle time",
"GISL",
false,
nullptr },
{ 710, "tracker-add", "Add a tracker to a torrent", "td", true, "<tracker>" }, { 710, "tracker-add", "Add a tracker to a torrent", "td", true, "<tracker>" },
{ 712, "tracker-remove", "Remove a tracker from a torrent", "tr", true, "<trackerId>" }, { 712, "tracker-remove", "Remove a tracker from a torrent", "tr", true, "<trackerId>" },
{ 's', "start", "Start the current torrent(s)", "s", false, nullptr }, { 's', "start", "Start the current torrent(s)", "s", false, nullptr },
@ -436,6 +461,8 @@ enum
case 912: /* encryption-tolerated */ case 912: /* encryption-tolerated */
case 953: /* global-seedratio */ case 953: /* global-seedratio */
case 954: /* no-global-seedratio */ case 954: /* no-global-seedratio */
case 958: /* global-idle-seeding-limit */
case 959: /* no-global-idle-seeding-limit */
case 990: /* start-paused */ case 990: /* start-paused */
case 991: /* no-start-paused */ case 991: /* no-start-paused */
case 992: /* trash-torrent */ case 992: /* trash-torrent */
@ -446,6 +473,9 @@ enum
case 950: /* seedratio */ case 950: /* seedratio */
case 951: /* seedratio-default */ case 951: /* seedratio-default */
case 952: /* no-seedratio */ case 952: /* no-seedratio */
case 955: /* idle-seeding-limit */
case 956: /* default-idle-seeding-limit */
case 957: /* no-idle-seeding-limit*/
case 984: /* honor-session */ case 984: /* honor-session */
case 985: /* no-honor-session */ case 985: /* no-honor-session */
return MODE_TORRENT_SET; return MODE_TORRENT_SET;
@ -704,7 +734,7 @@ auto constexpr FilesKeys = std::array<tr_quark, 4>{
TR_KEY_wanted, TR_KEY_wanted,
}; };
auto constexpr DetailsKeys = std::array<tr_quark, 53>{ static auto constexpr DetailsKeys = std::array<tr_quark, 56>{
TR_KEY_activityDate, TR_KEY_activityDate,
TR_KEY_addedDate, TR_KEY_addedDate,
TR_KEY_bandwidthPriority, TR_KEY_bandwidthPriority,
@ -747,6 +777,8 @@ auto constexpr DetailsKeys = std::array<tr_quark, 53>{
TR_KEY_seedRatioMode, TR_KEY_seedRatioMode,
TR_KEY_seedRatioLimit, TR_KEY_seedRatioLimit,
TR_KEY_sequentialDownload, TR_KEY_sequentialDownload,
TR_KEY_seedIdleMode,
TR_KEY_seedIdleLimit,
TR_KEY_sizeWhenDone, TR_KEY_sizeWhenDone,
TR_KEY_source, TR_KEY_source,
TR_KEY_startDate, TR_KEY_startDate,
@ -988,12 +1020,12 @@ void printDetails(tr_variant* top)
if (tr_variantDictFindInt(t, TR_KEY_rateDownload, &i)) if (tr_variantDictFindInt(t, TR_KEY_rateDownload, &i))
{ {
fmt::print(" Download Speed: {:s}\n", Speed{ i, Speed::Units::KByps }.to_string()); fmt::print(" Download Speed: {:s}\n", Speed{ i, Speed::Units::Byps }.to_string());
} }
if (tr_variantDictFindInt(t, TR_KEY_rateUpload, &i)) if (tr_variantDictFindInt(t, TR_KEY_rateUpload, &i))
{ {
fmt::print(" Upload Speed: {:s}\n", Speed{ i, Speed::Units::KByps }.to_string()); fmt::print(" Upload Speed: {:s}\n", Speed{ i, Speed::Units::Byps }.to_string());
} }
if (tr_variantDictFindInt(t, TR_KEY_haveUnchecked, &i) && tr_variantDictFindInt(t, TR_KEY_haveValid, &j)) if (tr_variantDictFindInt(t, TR_KEY_haveUnchecked, &i) && tr_variantDictFindInt(t, TR_KEY_haveValid, &j))
@ -1212,6 +1244,31 @@ void printDetails(tr_variant* top)
} }
} }
if (tr_variantDictFindInt(t, TR_KEY_seedIdleMode, &i))
{
switch (i)
{
case TR_IDLELIMIT_GLOBAL:
fmt::print(" Idle Limit: Default\n");
break;
case TR_IDLELIMIT_SINGLE:
if (tr_variantDictFindInt(t, TR_KEY_seedIdleLimit, &j))
{
fmt::print(" Idle Limit: {} minutes\n", j);
}
break;
case TR_IDLELIMIT_UNLIMITED:
fmt::print(" Idle Limit: Unlimited\n");
break;
default:
break;
}
}
if (tr_variantDictFindBool(t, TR_KEY_honorsSessionLimits, &boolVal)) if (tr_variantDictFindBool(t, TR_KEY_honorsSessionLimits, &boolVal))
{ {
fmt::print(" Honors Session Limits: {:s}\n", boolVal ? "Yes" : "No"); fmt::print(" Honors Session Limits: {:s}\n", boolVal ? "Yes" : "No");
@ -1792,6 +1849,7 @@ void printSession(tr_variant* top)
bool upEnabled; bool upEnabled;
bool downEnabled; bool downEnabled;
bool seedRatioLimited; bool seedRatioLimited;
bool seedIdleLimited;
int64_t altDown; int64_t altDown;
int64_t altUp; int64_t altUp;
int64_t altBegin; int64_t altBegin;
@ -1800,6 +1858,7 @@ void printSession(tr_variant* top)
int64_t upLimit; int64_t upLimit;
int64_t downLimit; int64_t downLimit;
int64_t peerLimit; int64_t peerLimit;
int64_t seedIdleLimit;
double seedRatioLimit; double seedRatioLimit;
if (tr_variantDictFindInt(args, TR_KEY_alt_speed_down, &altDown) && if (tr_variantDictFindInt(args, TR_KEY_alt_speed_down, &altDown) &&
@ -1815,13 +1874,19 @@ void printSession(tr_variant* top)
tr_variantDictFindInt(args, TR_KEY_speed_limit_up, &upLimit) && tr_variantDictFindInt(args, TR_KEY_speed_limit_up, &upLimit) &&
tr_variantDictFindBool(args, TR_KEY_speed_limit_up_enabled, &upEnabled) && tr_variantDictFindBool(args, TR_KEY_speed_limit_up_enabled, &upEnabled) &&
tr_variantDictFindReal(args, TR_KEY_seedRatioLimit, &seedRatioLimit) && tr_variantDictFindReal(args, TR_KEY_seedRatioLimit, &seedRatioLimit) &&
tr_variantDictFindBool(args, TR_KEY_seedRatioLimited, &seedRatioLimited)) tr_variantDictFindBool(args, TR_KEY_seedRatioLimited, &seedRatioLimited) &&
tr_variantDictFindInt(args, TR_KEY_idle_seeding_limit, &seedIdleLimit) &&
tr_variantDictFindBool(args, TR_KEY_idle_seeding_limit_enabled, &seedIdleLimited))
{ {
fmt::print("LIMITS\n"); fmt::print("LIMITS\n");
fmt::print(" Peer limit: {:d}\n", peerLimit); fmt::print(" Peer limit: {:d}\n", peerLimit);
fmt::print(" Default seed ratio limit: {:s}\n", seedRatioLimited ? strlratio2(seedRatioLimit) : "Unlimited"); fmt::print(" Default seed ratio limit: {:s}\n", seedRatioLimited ? strlratio2(seedRatioLimit) : "Unlimited");
fmt::print(
" Default idle seeding time limit: {:s}\n",
seedIdleLimited ? (std::to_string(seedIdleLimit) + " minutes").c_str() : "Unlimited");
std::string effective_up_limit; std::string effective_up_limit;
if (altEnabled) if (altEnabled)
@ -2767,6 +2832,15 @@ int processArgs(char const* rpcurl, int argc, char const* const* argv, RemoteCon
tr_variantDictAddBool(args, TR_KEY_seedRatioLimited, false); tr_variantDictAddBool(args, TR_KEY_seedRatioLimited, false);
break; break;
case 958:
tr_variantDictAddInt(args, TR_KEY_idle_seeding_limit, atoi(optarg));
tr_variantDictAddBool(args, TR_KEY_idle_seeding_limit_enabled, true);
break;
case 959:
tr_variantDictAddBool(args, TR_KEY_idle_seeding_limit_enabled, false);
break;
case 990: case 990:
tr_variantDictAddBool(args, TR_KEY_start_added_torrents, false); tr_variantDictAddBool(args, TR_KEY_start_added_torrents, false);
break; break;
@ -2903,6 +2977,19 @@ int processArgs(char const* rpcurl, int argc, char const* const* argv, RemoteCon
tr_variantDictAddInt(args, TR_KEY_seedRatioMode, TR_RATIOLIMIT_UNLIMITED); tr_variantDictAddInt(args, TR_KEY_seedRatioMode, TR_RATIOLIMIT_UNLIMITED);
break; break;
case 955:
tr_variantDictAddInt(args, TR_KEY_seedIdleLimit, atoi(optarg));
tr_variantDictAddInt(args, TR_KEY_seedIdleMode, TR_IDLELIMIT_SINGLE);
break;
case 956:
tr_variantDictAddInt(args, TR_KEY_seedIdleMode, TR_IDLELIMIT_GLOBAL);
break;
case 957:
tr_variantDictAddInt(args, TR_KEY_seedIdleMode, TR_IDLELIMIT_UNLIMITED);
break;
case 984: case 984:
tr_variantDictAddBool(args, TR_KEY_honorsSessionLimits, true); tr_variantDictAddBool(args, TR_KEY_honorsSessionLimits, true);
break; break;

View File

@ -28,6 +28,8 @@ and
.Op Fl F Ar filter .Op Fl F Ar filter
.Op Fl g Ar files .Op Fl g Ar files
.Op Fl G Ar files .Op Fl G Ar files
.Op Fl gisl Ar number
.Op Fl GISL Ar number
.Op Fl gsr Ar ratio .Op Fl gsr Ar ratio
.Op Fl GSR .Op Fl GSR
.Op Fl h .Op Fl h
@ -36,6 +38,9 @@ and
.Op Fl ids .Op Fl ids
.Op Fl if .Op Fl if
.Op Fl ip .Op Fl ip
.Op Fl isl Ar number
.Op Fl ISL Ar number
.Op Fl isld
.Op Fl it .Op Fl it
.Op Fl j .Op Fl j
.Op Fl l .Op Fl l
@ -176,6 +181,17 @@ All torrents, unless overridden by a per-torrent setting, should seed until a sp
.Ar ratio .Ar ratio
.It Fl GSR Fl -no-global-seedratio .It Fl GSR Fl -no-global-seedratio
All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio
.It Fl isl Fl -idle-seeding-limit Ar minutes
Let the selected torrent(s) seed until a specific amount of idle time
.It Fl isld Fl -default-idle-seeding-limit
Use the default idle-seeding-limit for the selected torrent
.It Fl ISL Fl -no-idle-seeding-limit
Let the selected torrent(s) seed regardless of idle time
.It Fl gisl Fl -global-idle-seeding-limit Ar minutes
All torrents, unless overridden by a per-torrent, should stop seeding after spending the specified time idling
.It Fl GISL Fl -no-global-idle-seeding-limit
All torrents, unless overridden by a per-torrent, should seed regardless of the time spent idle
Let the selected torrent(s) seed regardless of idle time
.It Fl h Fl -help .It Fl h Fl -help
Print command-line option descriptions. Print command-line option descriptions.
.It Fl i Fl -info .It Fl i Fl -info
@ -371,12 +387,12 @@ Rename torrent root folder from "test1/examplefile.txt" to "test2/examplefile.tx
.Bd -literal -offset indent .Bd -literal -offset indent
$ transmission-remote -t1 --path test1 --rename test2 $ transmission-remote -t1 --path test1 --rename test2
.Ed .Ed
Set download and upload limits to 400 kB/sec and 60 kB/sec: Set download and upload limits to 400 kB/s and 60 kB/s:
.Bd -literal -offset indent .Bd -literal -offset indent
$ transmission-remote \-d400 \-u60 $ transmission-remote \-d400 \-u60
$ transmission-remote \-\-downlimit=400 \-\-uplimit=60 $ transmission-remote \-\-downlimit=400 \-\-uplimit=60
.Ed .Ed
Set alternate download and upload limits to 100 kB/sec and 20 kB/sec: Set alternate download and upload limits to 100 kB/s and 20 kB/s:
.Bd -literal -offset indent .Bd -literal -offset indent
$ transmission-remote \-asd100 \-asu20 $ transmission-remote \-asd100 \-asu20
$ transmission-remote \-\-alt-speed-downlimit=100 \-\-alt-speed-uplimit=20 $ transmission-remote \-\-alt-speed-downlimit=100 \-\-alt-speed-uplimit=20

View File

@ -383,20 +383,30 @@ a {
> * { > * {
margin-right: 5px; margin-right: 5px;
} }
}
#speed-up-icon, .speed-container {
#speed-dn-icon { display: inherit;
fill: var(--color-fg-primary); align-items: inherit;
flex-direction: inherit;
svg { &:not(:nth-child(1 of #mainwin-statusbar .speed-container)) {
width: 20px; width: 100px;
}
} }
}
#speed-dn-label, #speed-up-icon,
#speed-up-label { #speed-dn-icon {
text-align: right; fill: var(--color-fg-primary);
svg {
width: 20px;
}
}
#speed-dn-label,
#speed-up-label {
text-align: right;
}
} }
/// TORRENT CONTAINER /// TORRENT CONTAINER

View File

@ -203,40 +203,45 @@
<input type="search" id="torrent-search" placeholder="Filter" /> <input type="search" id="torrent-search" placeholder="Filter" />
<span id="filter-count">&nbsp;</span> <span id="filter-count">&nbsp;</span>
<span class="flexible-space"></span> <span class="flexible-space"></span>
<div id="speed-dn-icon"> <div class="speed-container">
<svg <div id="speed-dn-label"></div>
xmlns="http://www.w3.org/2000/svg" <div id="speed-dn-icon">
width="32" <svg
height="32" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="32"
fill="none" height="32"
stroke="currentColor" viewBox="0 0 24 24"
stroke-width="2" fill="none"
stroke-linecap="round" stroke="currentColor"
stroke-linejoin="round" stroke-width="2"
class="feather feather-chevron-down" stroke-linecap="round"
> stroke-linejoin="round"
<polyline points="6 9 12 15 18 9"></polyline> class="feather feather-chevron-down"
</svg> >
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
</div> </div>
<div id="speed-dn-label"></div> <div class="speed-container">
<div id="speed-up-icon"> <span class="flexible-space"></span>
<svg <div id="speed-up-label"></div>
xmlns="http://www.w3.org/2000/svg" <div id="speed-up-icon">
width="32" <svg
height="32" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" width="32"
fill="none" height="32"
stroke="currentColor" viewBox="0 0 24 24"
stroke-width="2" fill="none"
stroke-linecap="round" stroke="currentColor"
stroke-linejoin="round" stroke-width="2"
class="feather feather-chevron-up" stroke-linecap="round"
> stroke-linejoin="round"
<polyline points="18 15 12 9 6 15"></polyline> class="feather feather-chevron-up"
</svg> >
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</div>
</div> </div>
<div id="speed-up-label"></div>
</header> </header>
<main id="mainwin-workarea"> <main id="mainwin-workarea">

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -125,7 +125,9 @@ export class Transmission extends EventTarget {
this.setCurrentPopup(new AboutDialog(this.version_info)); this.setCurrentPopup(new AboutDialog(this.version_info));
break; break;
case 'show-inspector': case 'show-inspector':
if (!this.popup || this.popup.name !== 'inspector') { if (this.popup instanceof Inspector) {
this.setCurrentPopup(null);
} else {
this.setCurrentPopup(new Inspector(this)); this.setCurrentPopup(new Inspector(this));
} }
break; break;