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
PointerAlignment: Left
ReflowComments: false
SortIncludes: false
SortIncludes: Never
SpaceAfterCStyleCast: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false

View File

@ -147,11 +147,6 @@ jobs:
libssl-dev \
ninja-build \
npm
- name: Temporary workaround for sanitizer crashes
# https://bugs.launchpad.net/ubuntu/+source/llvm-toolchain-14/+bug/2048768
# https://github.com/actions/runner-images/issues/9491
# https://github.com/actions/runner-images/pull/9513
run: sudo sysctl vm.mmap_rnd_bits=28
- name: Get Source
uses: actions/checkout@v4
with:
@ -226,7 +221,7 @@ jobs:
run: cmake -E chdir obj ctest -j $(nproc) --build-config Debug --output-on-failure
clang-tidy-libtransmission:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
needs: [ what-to-make ]
if: ${{ needs.what-to-make.outputs.test-style == 'true' }}
steps:
@ -335,9 +330,9 @@ jobs:
name: binaries-${{ github.job }}
path: pfx/**/*
# Only verify build support on old macOS
macos-11:
runs-on: macos-11
# Only verify build support on older macOS and SDK
macos-12:
runs-on: macos-12
needs: [ what-to-make ]
if: ${{ needs.what-to-make.outputs.make-mac == 'true' }}
steps:
@ -354,6 +349,9 @@ jobs:
with:
path: src
submodules: recursive
- name: Set Xcode to 13.2.1
run: |
sudo xcode-select --switch /Applications/Xcode_13.2.1.app
- name: Configure
run: |
cmake \
@ -363,8 +361,9 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=pfx \
-DCMAKE_OSX_ARCHITECTURES='x86_64' \
-DCMAKE_PREFIX_PATH=`brew --prefix`/opt/qt \
-DENABLE_GTK=OFF \
-DENABLE_MAC=${{ (needs.what-to-make.outputs.make-mac == 'true') && 'ON' || 'OFF' }} \
-DENABLE_QT=OFF \
-DENABLE_TESTS=OFF \
-DENABLE_WERROR=ON \
-DRUN_CLANG_TIDY=OFF
@ -613,7 +612,6 @@ jobs:
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=pfx \
-DCMAKE_OSX_ARCHITECTURES='x86_64;arm64' \
-DCMAKE_PREFIX_PATH=`brew --prefix`/opt/qt \
-DENABLE_CLI=${{ (needs.what-to-make.outputs.make-cli == 'true') && 'ON' || 'OFF' }} \
-DENABLE_DAEMON=${{ (needs.what-to-make.outputs.make-daemon == 'true') && 'ON' || 'OFF' }} \
-DENABLE_GTK=OFF \
@ -991,7 +989,7 @@ jobs:
run: |
gradle build
ubuntu-24-04-from-tarball-cxx-23:
ubuntu-24-04-cxx-23:
needs: [ make-source-tarball, what-to-make ]
if: ${{ needs.what-to-make.outputs.make-cli == 'true' || needs.what-to-make.outputs.make-daemon == 'true' || needs.what-to-make.outputs.make-gtk == 'true' || needs.what-to-make.outputs.make-qt == 'true' || needs.what-to-make.outputs.make-tests == 'true' || needs.what-to-make.outputs.make-utils == 'true' }}
runs-on: ubuntu-24.04
@ -1028,11 +1026,10 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-qt == 'true' }}
run: sudo apt-get install -y --no-install-recommends qtbase5-dev libqt5svg5-dev qttools5-dev
- name: Get Source
uses: actions/download-artifact@v4
uses: actions/checkout@v4
with:
name: source-tarball
- name: Extract Source
run: mkdir src && tar xf transmission*.tar.* -C src --strip-components 1
submodules: recursive
path: src
- name: Configure
run: |
cmake \

View File

@ -530,17 +530,9 @@ if(NOT USE_SYSTEM_MINIUPNPC)
target_compile_definitions(miniupnpc::libminiupnpc
INTERFACE
MINIUPNP_STATICLIB)
set(MINIUPNPC_VERSION 2.2)
set(MINIUPNPC_API_VERSION 17)
endif()
unset(TR_MINIUPNPC_LIBNAME)
target_compile_definitions(miniupnpc::libminiupnpc
INTERFACE
SYSTEM_MINIUPNP
$<$<VERSION_LESS:${MINIUPNPC_VERSION},1.7>:MINIUPNPC_API_VERSION=${MINIUPNPC_API_VERSION}>) # API version macro was only added in 1.7
add_subdirectory(${TR_THIRD_PARTY_SOURCE_DIR}/wildmat)
tr_add_external_auto_library(DHT dht dht

View File

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

View File

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

43
cmake/CheckAtomic.cmake Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -160,7 +160,7 @@ public:
return nullptr;
}
auto const [it, is_new] = scrape_info_.try_emplace(url, url, TR_MULTISCRAPE_MAX);
auto const [it, is_new] = scrape_info_.try_emplace(url, url, TrMultiscrapeMax);
return &it->second;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -134,7 +134,7 @@ tr_metainfo_builder::tr_metainfo_builder(std::string_view single_file_or_parent_
: top_{ single_file_or_parent_directory }
{
files_ = findFiles(tr_sys_path_dirname(top_), tr_sys_path_basename(top_));
block_info_ = tr_block_info{ files_.totalSize(), default_piece_size(files_.totalSize()) };
block_info_ = tr_block_info{ files_.total_size(), default_piece_size(files_.total_size()) };
}
bool tr_metainfo_builder::set_piece_size(uint32_t piece_size) noexcept
@ -144,7 +144,7 @@ bool tr_metainfo_builder::set_piece_size(uint32_t piece_size) noexcept
return false;
}
block_info_ = tr_block_info{ files_.totalSize(), piece_size };
block_info_ = tr_block_info{ files_.total_size(), piece_size };
return true;
}

View File

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

View File

@ -1,3 +1,6 @@
// This file was generated with libtransmission/mime-types.js
// DO NOT EDIT MANUALLY
// This file Copyright © Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
// or any future license endorsed by Mnemosyne LLC.

View File

@ -1,7 +1,10 @@
#!/usr/bin/env node
const copyright =
`// This file Copyright © 2021-${new Date().getFullYear()} Mnemosyne LLC.
`// This file was generated with libtransmission/mime-types.js
// DO NOT EDIT MANUALLY
// This file Copyright © Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.`;

View File

@ -16,8 +16,12 @@
#include <utility> // std::pair
#ifdef _WIN32
#include <winsock2.h> // must come before iphlpapi.h
#include <iphlpapi.h>
#include <ws2tcpip.h>
#else
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/tcp.h> /* TCP_CONGESTION */
#endif
@ -430,16 +434,6 @@ namespace
namespace is_valid_for_peers_helpers
{
[[nodiscard]] constexpr auto is_ipv4_mapped_address(tr_address const& addr)
{
return addr.is_ipv6() && IN6_IS_ADDR_V4MAPPED(&addr.addr.addr6);
}
[[nodiscard]] constexpr auto is_ipv6_link_local_address(tr_address const& addr)
{
return addr.is_ipv6() && IN6_IS_ADDR_LINKLOCAL(&addr.addr.addr6);
}
/* isMartianAddr was written by Juliusz Chroboczek,
and is covered under the same license as third-party/dht/dht.c. */
[[nodiscard]] auto is_martian_addr(tr_address const& addr, tr_peer_from from)
@ -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
{
TR_ASSERT(is_valid());
return evutil_inet_ntop(tr_ip_protocol_to_af(type), &addr, out, outlen);
if (auto* name = evutil_inet_ntop(tr_ip_protocol_to_af(type), &addr, out, outlen))
{
return name;
}
return "Invalid address"sv;
}
[[nodiscard]] std::string tr_address::display_name() const
{
auto buf = std::array<char, INET6_ADDRSTRLEN>{};
auto buf = std::array<char, std::max(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)>{};
return std::string{ display_name(std::data(buf), std::size(buf)) };
}
@ -557,6 +555,99 @@ std::pair<tr_address, std::byte const*> tr_address::from_compact_ipv6(std::byte
return { address, compact };
}
std::optional<unsigned> tr_address::to_interface_index() const noexcept
{
if (!is_valid())
{
tr_logAddDebug("Invalid target address to find interface index");
return {};
}
tr_logAddDebug(fmt::format("Find interface index for {}", display_name()));
#ifdef _WIN32
auto p_addresses = std::unique_ptr<void, void (*)(void*)>{ nullptr, operator delete };
// The recommended method of calling the GetAdaptersAddresses function is to
// pre-allocate a 15KB working buffer pointed to by the AdapterAddresses parameter.
// On typical computers, this dramatically reduces the chances that the
// GetAdaptersAddresses function returns ERROR_BUFFER_OVERFLOW, which would require
// calling GetAdaptersAddresses function multiple times.
// https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses
for (auto p_addresses_size = ULONG{ 15000 } /* 15KB */;;)
{
p_addresses.reset(operator new(p_addresses_size, std::nothrow));
if (!p_addresses)
{
tr_logAddDebug("Could not allocate memory for interface list");
return {};
}
if (auto ret = GetAdaptersAddresses(
AF_UNSPEC,
GAA_FLAG_SKIP_FRIENDLY_NAME,
nullptr,
reinterpret_cast<PIP_ADAPTER_ADDRESSES>(p_addresses.get()),
&p_addresses_size);
ret != ERROR_BUFFER_OVERFLOW)
{
if (ret != ERROR_SUCCESS)
{
tr_logAddDebug(fmt::format("Failed to retrieve interface list: {} ({})", ret, tr_win32_format_message(ret)));
return {};
}
break;
}
}
for (auto const* cur = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(p_addresses.get()); cur != nullptr; cur = cur->Next)
{
if (cur->OperStatus != IfOperStatusUp)
{
continue;
}
for (auto const* sa_p = cur->FirstUnicastAddress; sa_p != nullptr; sa_p = sa_p->Next)
{
if (auto if_addr = tr_socket_address::from_sockaddr(sa_p->Address.lpSockaddr);
if_addr && if_addr->address() == *this)
{
auto const ret = type == TR_AF_INET ? cur->IfIndex : cur->Ipv6IfIndex;
tr_logAddDebug(fmt::format("Found interface index for {}: {}", display_name(), ret));
return ret;
}
}
}
#else
struct ifaddrs* ifa = nullptr;
if (getifaddrs(&ifa) != 0)
{
auto err = errno;
tr_logAddDebug(fmt::format("Failed to retrieve interface list: {} ({})", err, tr_strerror(err)));
return {};
}
auto const ifa_uniq = std::unique_ptr<ifaddrs, void (*)(struct ifaddrs*)>{ ifa, freeifaddrs };
for (; ifa != nullptr; ifa = ifa->ifa_next)
{
if (ifa->ifa_addr == nullptr || (ifa->ifa_flags & IFF_UP) == 0U)
{
continue;
}
if (auto if_addr = tr_socket_address::from_sockaddr(ifa->ifa_addr); if_addr && if_addr->address() == *this)
{
auto const ret = if_nametoindex(ifa->ifa_name);
tr_logAddDebug(fmt::format("Found interface index for {}: {}", display_name(), ret));
return ret;
}
}
#endif
tr_logAddDebug(fmt::format("Could not find interface index for {}", display_name()));
return {};
}
int tr_address::compare(tr_address const& that) const noexcept // <=>
{
// IPv6 addresses are always "greater than" IPv4
@ -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;
return is_valid() && !std::empty(port_) && !is_ipv6_link_local_address(address_) && !is_ipv4_mapped_address(address_) &&
return is_valid() && !std::empty(port_) && !address_.is_ipv6_link_local_address() && !address_.is_ipv4_mapped_address() &&
!is_martian_addr(address_, from);
}
std::optional<tr_socket_address> tr_socket_address::from_string(std::string_view sockaddr_sv)
{
auto ss = sockaddr_storage{};
auto sslen = int{ sizeof(ss) };
if (evutil_parse_sockaddr_port(tr_strbuf<char, TR_ADDRSTRLEN>{ sockaddr_sv }, reinterpret_cast<sockaddr*>(&ss), &sslen) !=
0)
{
return {};
}
return from_sockaddr(reinterpret_cast<struct sockaddr const*>(&ss));
}
std::optional<tr_socket_address> tr_socket_address::from_sockaddr(struct sockaddr const* from)
{
if (from == nullptr)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -126,7 +126,7 @@ void event_callback(evutil_socket_t s, [[maybe_unused]] short type, void* vsessi
}
else if (n_read >= 8 && buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] <= 3)
{
if (!session->announcer_udp_->handle_message(std::data(buf), n_read))
if (!session->announcer_udp_->handle_message(std::data(buf), n_read, from_sa, fromlen))
{
tr_logAddTrace("Couldn't parse UDP tracker packet.");
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -251,7 +251,7 @@ bool trashDataFile(char const* filename, void* /*user_data*/, tr_error* error)
if (wasTransmitting != self.transmitting)
{
//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
coalesceMask:NSNotificationCoalescingOnName
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
{
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.
<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>
</li>
<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.
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">
<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>
</li>
</ol>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@
#include <optional>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
@ -45,6 +46,9 @@
using namespace std::literals;
using tau_connection_t = uint64_t;
using tau_transaction_t = uint32_t;
using MessageBuffer = libtransmission::StackBuffer<4096, std::byte>;
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);
EXPECT_EQ(ProtocolId, buf.to_uint64());
@ -169,7 +173,7 @@ protected:
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);
EXPECT_EQ(expected_connection_id, buf.to_uint64());
@ -187,13 +191,19 @@ protected:
[[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_);
mediator.sent_.pop_back();
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{};
buf.add_uint32(ErrorAction);
@ -204,21 +214,25 @@ protected:
auto arr = std::array<uint8_t, 256>{};
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{};
buf.add_uint32(ConnectAction);
buf.add_uint32(transaction_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);
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;
}
@ -227,7 +241,7 @@ protected:
{
uint64_t connection_id = 0;
uint32_t action = 0; // 1: announce
uint32_t transaction_id = 0;
tau_transaction_t transaction_id = 0;
tr_sha1_digest_t info_hash = {};
tr_peer_id_t peer_id = {};
uint64_t downloaded = 0;
@ -307,6 +321,16 @@ protected:
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_;
// https://www.bittorrent.org/beps/bep_0015.html
@ -337,12 +361,16 @@ TEST_F(AnnouncerUdpTest, canScrape)
auto response = std::optional<tr_scrape_response>{};
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.
// Inspect that request for validity.
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator));
// 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.
// Inspect that request for validity.
@ -359,7 +387,7 @@ TEST_F(AnnouncerUdpTest, canScrape)
auto response_size = std::size(buf);
auto arr = std::array<uint8_t, 256>{};
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());
@ -413,13 +441,17 @@ TEST_F(AnnouncerUdpTest, canMultiScrape)
expected_response.scrape_url = DefaultScrapeUrl;
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 response = std::optional<tr_scrape_response>{};
announcer->scrape(request, [&response](tr_scrape_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);
auto const connection_id = sendConnectionResponse(*announcer, connect_transaction_id, from_ptr, fromlen);
// The announcer should have sent a UDP scrape request.
// Inspect that request for validity.
@ -439,7 +471,7 @@ TEST_F(AnnouncerUdpTest, canMultiScrape)
auto response_size = std::size(buf);
auto arr = std::array<uint8_t, 256>{};
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());
@ -463,6 +495,10 @@ TEST_F(AnnouncerUdpTest, canHandleScrapeError)
expected_response.min_request_interval = 0;
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
auto request = buildScrapeRequestFromResponse(expected_response);
@ -480,7 +516,7 @@ TEST_F(AnnouncerUdpTest, canHandleScrapeError)
auto connect_transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator));
// 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.
// Inspect that request for validity.
@ -489,7 +525,7 @@ TEST_F(AnnouncerUdpTest, canHandleScrapeError)
connection_id);
// 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
EXPECT_TRUE(response.has_value());
@ -513,6 +549,10 @@ TEST_F(AnnouncerUdpTest, canHandleConnectError)
expected_response.min_request_interval = 0;
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
auto mediator = MockMediator{};
auto announcer = tr_announcer_udp::create(mediator);
@ -529,7 +569,7 @@ TEST_F(AnnouncerUdpTest, canHandleConnectError)
auto transaction_id = parseConnectionRequest(waitForAnnouncerToSendMessage(mediator));
// 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
EXPECT_TRUE(response.has_value());
@ -545,6 +585,10 @@ TEST_F(AnnouncerUdpTest, handleMessageReturnsFalseOnInvalidMessage)
request.info_hash_count = 1;
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
auto mediator = MockMediator{};
auto announcer = tr_announcer_udp::create(mediator);
@ -566,7 +610,7 @@ TEST_F(AnnouncerUdpTest, handleMessageReturnsFalseOnInvalidMessage)
auto response_size = std::size(buf);
auto arr = std::array<uint8_t, 256>{};
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
buf.clear();
@ -575,15 +619,15 @@ TEST_F(AnnouncerUdpTest, handleMessageReturnsFalseOnInvalidMessage)
buf.add_uint64(tr_rand_obj<uint64_t>());
response_size = std::size(buf);
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,
// 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);
}
TEST_F(AnnouncerUdpTest, canAnnounce)
TEST_F(AnnouncerUdpTest, canAnnounceIPv4)
{
static auto constexpr Interval = uint32_t{ 3600 };
static auto constexpr Leechers = uint32_t{ 10 };
@ -608,6 +652,10 @@ TEST_F(AnnouncerUdpTest, canAnnounce)
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;
@ -634,7 +682,7 @@ TEST_F(AnnouncerUdpTest, canAnnounce)
// 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);
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.
@ -658,7 +706,96 @@ TEST_F(AnnouncerUdpTest, canAnnounce)
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));
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
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

View File

@ -84,7 +84,7 @@ protected:
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)
{
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.name(), metainfo.name());

View File

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

View File

@ -176,7 +176,7 @@ protected:
{
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) };
createFileWithContents(walk, std::data(Content), std::size(Content));
@ -332,7 +332,7 @@ TEST_F(RemoveTest, PreservesDirectoryHierarchyIfPossible)
// after remove, the subtree should be:
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) });
}

View File

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

View File

@ -32,13 +32,13 @@ TEST_F(TorrentFilesTest, add)
auto constexpr Size = size_t{ 1024 };
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));
auto const file_index = files.add(Path, Size);
EXPECT_EQ(tr_file_index_t{ 0U }, file_index);
EXPECT_EQ(size_t{ 1U }, files.fileCount());
EXPECT_EQ(Size, files.fileSize(file_index));
EXPECT_EQ(size_t{ 1U }, files.file_count());
EXPECT_EQ(Size, files.file_size(file_index));
EXPECT_EQ(Path, files.path(file_index));
EXPECT_FALSE(std::empty(files));
}
@ -52,11 +52,11 @@ TEST_F(TorrentFilesTest, setPath)
auto files = tr_torrent_files{};
auto const file_index = files.add(Path1, Size);
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(Size, files.fileSize(file_index));
EXPECT_EQ(Size, files.file_size(file_index));
}
TEST_F(TorrentFilesTest, clear)
@ -67,13 +67,13 @@ TEST_F(TorrentFilesTest, clear)
auto files = tr_torrent_files{};
files.add(Path1, Size);
EXPECT_EQ(size_t{ 1U }, files.fileCount());
EXPECT_EQ(size_t{ 1U }, files.file_count());
files.add(Path2, Size);
EXPECT_EQ(size_t{ 2U }, files.fileCount());
EXPECT_EQ(size_t{ 2U }, files.file_count());
files.clear();
EXPECT_TRUE(std::empty(files));
EXPECT_EQ(size_t{ 0U }, files.fileCount());
EXPECT_EQ(size_t{ 0U }, files.file_count());
}
TEST_F(TorrentFilesTest, find)
@ -135,10 +135,10 @@ TEST_F(TorrentFilesTest, hasAnyLocalData)
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() };
EXPECT_TRUE(files.hasAnyLocalData(std::data(search_path), 2U));
EXPECT_TRUE(files.hasAnyLocalData(std::data(search_path), 1U));
EXPECT_FALSE(files.hasAnyLocalData(std::data(search_path) + 1, 1U));
EXPECT_FALSE(files.hasAnyLocalData(std::data(search_path), 0U));
EXPECT_TRUE(files.has_any_local_data(std::data(search_path), 2U));
EXPECT_TRUE(files.has_any_local_data(std::data(search_path), 1U));
EXPECT_FALSE(files.has_any_local_data(std::data(search_path) + 1, 1U));
EXPECT_FALSE(files.has_any_local_data(std::data(search_path), 0U));
}
TEST_F(TorrentFilesTest, isSubpathPortable)
@ -179,6 +179,6 @@ TEST_F(TorrentFilesTest, isSubpathPortable)
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->set_metadata_piece(0, std::data(metainfo_benc), metainfo_size);
tor->do_idle_work();
EXPECT_TRUE(tor->has_metainfo());
EXPECT_EQ(tor->info_dict_size(), metainfo_size);
EXPECT_EQ(tor->get_metadata_percent(), 1.0);

View File

@ -1,3 +1,3 @@
---
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)
{
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, "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
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 },
{ 970, "alt-speed", "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",
false,
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>" },
{ 712, "tracker-remove", "Remove a tracker from a torrent", "tr", true, "<trackerId>" },
{ 's', "start", "Start the current torrent(s)", "s", false, nullptr },
@ -436,6 +461,8 @@ enum
case 912: /* encryption-tolerated */
case 953: /* 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 991: /* no-start-paused */
case 992: /* trash-torrent */
@ -446,6 +473,9 @@ enum
case 950: /* seedratio */
case 951: /* seedratio-default */
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 985: /* no-honor-session */
return MODE_TORRENT_SET;
@ -704,7 +734,7 @@ auto constexpr FilesKeys = std::array<tr_quark, 4>{
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_addedDate,
TR_KEY_bandwidthPriority,
@ -747,6 +777,8 @@ auto constexpr DetailsKeys = std::array<tr_quark, 53>{
TR_KEY_seedRatioMode,
TR_KEY_seedRatioLimit,
TR_KEY_sequentialDownload,
TR_KEY_seedIdleMode,
TR_KEY_seedIdleLimit,
TR_KEY_sizeWhenDone,
TR_KEY_source,
TR_KEY_startDate,
@ -988,12 +1020,12 @@ void printDetails(tr_variant* top)
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))
{
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))
@ -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))
{
fmt::print(" Honors Session Limits: {:s}\n", boolVal ? "Yes" : "No");
@ -1792,6 +1849,7 @@ void printSession(tr_variant* top)
bool upEnabled;
bool downEnabled;
bool seedRatioLimited;
bool seedIdleLimited;
int64_t altDown;
int64_t altUp;
int64_t altBegin;
@ -1800,6 +1858,7 @@ void printSession(tr_variant* top)
int64_t upLimit;
int64_t downLimit;
int64_t peerLimit;
int64_t seedIdleLimit;
double seedRatioLimit;
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_variantDictFindBool(args, TR_KEY_speed_limit_up_enabled, &upEnabled) &&
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(" Peer limit: {:d}\n", peerLimit);
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;
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);
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:
tr_variantDictAddBool(args, TR_KEY_start_added_torrents, false);
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);
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:
tr_variantDictAddBool(args, TR_KEY_honorsSessionLimits, true);
break;

View File

@ -28,6 +28,8 @@ and
.Op Fl F Ar filter
.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
.Op Fl h
@ -36,6 +38,9 @@ and
.Op Fl ids
.Op Fl if
.Op Fl ip
.Op Fl isl Ar number
.Op Fl ISL Ar number
.Op Fl isld
.Op Fl it
.Op Fl j
.Op Fl l
@ -176,6 +181,17 @@ All torrents, unless overridden by a per-torrent setting, should seed until a sp
.Ar ratio
.It Fl GSR Fl -no-global-seedratio
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
Print command-line option descriptions.
.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
$ transmission-remote -t1 --path test1 --rename test2
.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
$ transmission-remote \-d400 \-u60
$ transmission-remote \-\-downlimit=400 \-\-uplimit=60
.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
$ transmission-remote \-asd100 \-asu20
$ transmission-remote \-\-alt-speed-downlimit=100 \-\-alt-speed-uplimit=20

View File

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

View File

@ -203,40 +203,45 @@
<input type="search" id="torrent-search" placeholder="Filter" />
<span id="filter-count">&nbsp;</span>
<span class="flexible-space"></span>
<div id="speed-dn-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-chevron-down"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
<div class="speed-container">
<div id="speed-dn-label"></div>
<div id="speed-dn-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-chevron-down"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
</div>
<div id="speed-dn-label"></div>
<div id="speed-up-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-chevron-up"
>
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
<div class="speed-container">
<span class="flexible-space"></span>
<div id="speed-up-label"></div>
<div id="speed-up-icon">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-chevron-up"
>
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</div>
</div>
<div id="speed-up-label"></div>
</header>
<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));
break;
case 'show-inspector':
if (!this.popup || this.popup.name !== 'inspector') {
if (this.popup instanceof Inspector) {
this.setCurrentPopup(null);
} else {
this.setCurrentPopup(new Inspector(this));
}
break;