Compare commits

...

36 Commits

Author SHA1 Message Date
Cœur e9bc4bea4d code review: keeping SORT_BY_QUEUE by queue only 2024-04-27 01:55:47 +08:00
Cœur 5a7dd36f12 code review: code style 2024-04-26 22:30:28 +08:00
Christian Muehlhaeuser db713ee6fa Qt: Refactor sorting
As discussed in #234 and with @mikedld, the sorting code in the Qt frontend
needed a refactor.

This change proposes handling magnet transfers as a separate sorting category.
This means that magnets will always appear at the bottom (or top with reversed
sort order) of the list.

Sorting by Name, Size, Age, ID, Queue, ETA & Ratio remains straight forward.

Sorting by Progress sorts in the following order: download percentage, ratio,
queue position.

Sorting by Activity will first sort by activity, transfer speeds & peers, then
fallback to sorting by Progress.

Sorting by State now behaves the same as sorting by Activity. I'd like to
propose dropping this sorting mode, as it's now implicitly handled for sorting by
either Progress or Activity:

Transfers with errors come first, followed by finished transfers, active
transfers, paused items and eventually magnets.
2024-04-26 22:30:28 +08:00
Pooyan Khanjankhani 821a6816ef
doc: fix typo (#6790) 2024-04-21 18:21:17 -05:00
Dzmitry Neviadomski ef18816b7f
Fix code style script path in CONTRIBUTING.md (#6787)
Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-04-21 07:36:13 -05:00
Dzmitry Neviadomski 0e25584e78
Make std::hash specialization for tr_socket_address a struct (#6788)
To be in line with std::hash declaration

See https://en.cppreference.com/w/cpp/utility/hash

Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-04-20 21:01:47 -05:00
Dzmitry Neviadomski bd0b74fccb
Use std::declval instead of nullptr cast trick (#6785)
Signed-off-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-04-15 15:18:43 -05:00
Yat Ho 92519281b0
refactor: convert `tr_webseed` to C++ interface (#6708)
* chore: housekeeping

* refactor: convert `on_idle()` to class method

* refactor: convert `task_request_next_chunk()` to class method

* refactor: convert `onPartialDataFetched()` to class method

* refactor: convert `onBufferGotData()` to class method

* refactor: convert `useFetchedBlocks()` to class method

* refactor: hide some `tr_webseed_task` fields and methods

* refactor: convert `tr_webseed_task` methods to snake_case

* refactor: remove `write_block_data`

* refactor: store reference to `tr_torrent` directly

* refactor: convert `ConnectionLimiter` methods to snake_case

* refactor: convert `tr_webseed` methods to snake_case

* refactor: hide `tr_webseed` callback member variables

* refactor: convert `tr_webseed` to C++ interface
2024-04-02 09:31:20 -05:00
Will Thompson de11cbdf85
chore: add Add <launchable> tag to GTK client metainfo (#6720) 2024-04-01 17:29:36 -05:00
Cœur c0c00d0d19
Replace mac app default BindPort with a random port (#5102) 2024-04-01 11:53:04 -05:00
Cœur 5e5ec143b4
bump fast-float to 6.1.1 and miniupnpc to 2.2.7 and libdeflate to 1.2.0 (#6721)
* bump fast-float to 6.1.1

* bump miniupnpc to 2.2.7

* bump libdeflate to 1.2.0
2024-04-01 11:19:49 -05:00
Cœur cdbc8574a7
feat(macos): added last known location (#6610)
* feat(macos): added last known location

* fix data file label and reveal data button positions

* code review: code style

---------

Co-authored-by: BogdanArdelean <bogdan.ardelean@ymail.com>
2024-04-01 09:53:48 -05:00
Nick fb79a2d399
qt: add dynamic RPC keys (#6599)
* add dynamic main stat keys

* make dynamic rpc calls more generic
2024-04-01 09:02:17 -05:00
Yat Ho 87862e506d
feat: support different internal and external port for UPnP (#6672)
* feat: support different internal and external port in upnp

* chore: housekeeping

* code review: better log wording
2024-03-31 18:49:19 -05:00
Cœur 7264e2dc90
feat: support "torrent" in Spotlight indexation (#6578)
* feat: support "torrent" in Spotlight indexation

* removing "btih" from spotlight keywords
2024-03-31 17:05:06 -05:00
Cœur e0fdd4e9c5
doc: no need to adopt an alternative to VDKQueue (#6600) 2024-03-31 16:39:18 -05:00
Cœur ded869974c
ci: support macos-14 universal builds (#6626)
* support macos-14 universal builds

* Apply suggestions from code review

Co-authored-by: Dzmitry Neviadomski <nevack.d@gmail.com>

* add the arch to the job's name

* revert support macos-14 universal builds

---------

Co-authored-by: Dzmitry Neviadomski <nevack.d@gmail.com>
2024-03-31 16:31:30 -05:00
Cœur 764f2ad85a
ci: re-enable ubuntu tests (#6715)
* re-enabling ubuntu tests

* official GitHub workaround
2024-03-31 15:32:58 -05:00
Yat Ho e619718a1e
fix: save sequential download across sessions (#6746) 2024-03-31 15:01:05 -05:00
Cœur 0ee64695ca
test: disable restartWithDifferentInterval for all platforms (#6745) 2024-03-31 14:07:12 -05:00
Yat Ho e1c9fbde60
fix: restore loose data type parsing in RPC and `settings.json` (#6723)
* refactor: incorporate lenient parsing from `tr_variantGet*()` functions into `tr_variant`

* fix: restore loose data types in RPC and settings.json

* fix: workaround GCC defect

* fix: MSVC build
2024-03-31 14:06:36 -05:00
Cœur 460ce7c302
Add start_paused to settings file and daemon (#6728)
* Add daemon-startPaused to settings file

* code review: naming

* "Predefined quarks must be sorted by their string value"

* code review: replacing paused_ with tr_variantDictAddBool/tr_variantDictFindBool

* code review: doc

* Update docs/Editing-Configuration-Files.md

Co-authored-by: Yat Ho <lagoho7@gmail.com>

---------

Co-authored-by: Eugen Beck <beck@cs.rwth-aachen.de>
Co-authored-by: Yat Ho <lagoho7@gmail.com>
2024-03-30 16:39:44 -05:00
Yat Ho 6384abeb2b
fix: invalid socket address in `tr_peerIo::reconnect()` (#6750)
* fix: don't discard socket if reconnect failed

* fix: don't try to reconnect more than once
2024-03-30 15:26:55 -05:00
Charles Kerr d935d364ed
refactor: remove torrent_view virtual class (#6738)
* refactor: remove torrent_view virtual class

* chore: workaround for google-readability-todo warnings

* chore: fix completion tests
2024-03-30 14:45:00 -05:00
Yat Ho 8944e587f9
fix: assert failure when moving torrents to bottom of the queue (#6751) 2024-03-30 13:49:03 -05:00
Cœur bc7d447949
fix shadowed declaration warnings [-Wshadow] (#6759) 2024-03-30 12:56:48 -05:00
Cœur 465b878e8a
Win32 compatibility for get_peer_stats (#6743) 2024-03-28 09:40:08 +00:00
Yat Ho 2ff3ae07d1
fix: more misc `net.cc` fixes (#6735)
* feat: accept ipv6 string in square brackets for `tr_address::from_string()`

* test: add test case for ipv6 string in square brackets

* fix: include square brackets in host component

According to RFC3986:
  host       = IP-literal / IPv4address / reg-name
  IP-literal = "[" ( IPv6address / IPvFuture  ) "]"

* fix: set ipv6-only socket before binding UDP socket

Will return EINVAL on Linux otherwise

* refactor: simplify code using `evutil` when binding TCP socket

* fix: do not set SO_REUSEADDR for listening sockets on Windows systems

Reason: https://stackoverflow.com/a/14388707/11390656

* fix: do not enclose ipv4 address string in square brackets
2024-03-25 21:10:06 -05:00
Cœur 2917374159
add macos-11 to actions.yml (#6617) 2024-03-25 20:46:11 -05:00
Yat Ho 1568659f95
fix: mismatched `class` forward declaration for `struct tr_peer` (#6725) 2024-03-25 12:00:26 -05:00
Yat Ho c223c70644
fix: potential nullptr deference in rpc (#6734) 2024-03-25 09:55:45 -05:00
Charles Kerr b318bf8d9d
refactor: use fstream in subprocess tests (#6733) 2024-03-25 09:13:09 -05:00
Cœur 4ce8a02916
Disable a long-lived and recurrent flaky test (#6722) 2024-03-25 00:32:29 -05:00
Yat Ho 3e958cfbaf
feat: µTP delayed ack (#6586)
* chore: rename tr utp functions to snake_case

* refactor: make udp sockets non-blocking

* feat: rudimentary uTP delayed ACK

* chore: housekeeping

* chore: correct comment about µTP packet format
2024-03-24 20:48:23 -05:00
Yat Ho 20aef2f79d
fix: misc net.cc and blocklist.cc fixes (#6717)
* fix: `tr_address` should be invalid by default

* fix: allow loopback address for LPD and incoming connections

* fix: `parseCidrline()` should set address type

* code review: keep `memcmp`
2024-03-24 17:09:51 -05:00
Cœur 9ddf609d50
KeepEmptyLinesAtTheStartOfBlocks: false (#6726) 2024-03-24 11:25:00 -05:00
68 changed files with 1089 additions and 802 deletions

View File

@ -55,7 +55,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4 ConstructorInitializerIndentWidth: 4
FixNamespaceComments: true FixNamespaceComments: true
IndentGotoLabels: false IndentGotoLabels: false
KeepEmptyLinesAtTheStartOfBlocks: true KeepEmptyLinesAtTheStartOfBlocks: false
QualifierAlignment: Right QualifierAlignment: Right
SortUsingDeclarations: true SortUsingDeclarations: true
SpaceAfterTemplateKeyword: false SpaceAfterTemplateKeyword: false

View File

@ -116,67 +116,72 @@ jobs:
echo "When CI is done, the above patch will be uploaded as 'code-style.diff' to https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/ ." echo "When CI is done, the above patch will be uploaded as 'code-style.diff' to https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/ ."
exit 1 exit 1
# sanitizer-tests-ubuntu: sanitizer-tests-ubuntu:
# runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
# needs: [ what-to-make ] needs: [ what-to-make ]
# if: ${{ needs.what-to-make.outputs.make-tests == 'true' }} if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
# env: env:
# NODE_PATH: /usr/lib/nodejs:/usr/share/nodejs NODE_PATH: /usr/lib/nodejs:/usr/share/nodejs
# steps: steps:
# - name: Show Configuration - name: Show Configuration
# run: | run: |
# echo '${{ toJSON(needs) }}' echo '${{ toJSON(needs) }}'
# echo '${{ toJSON(runner) }}' echo '${{ toJSON(runner) }}'
# cat /etc/os-release cat /etc/os-release
# - name: Get Dependencies - name: Get Dependencies
# run: | run: |
# set -ex set -ex
# sudo apt-get update sudo apt-get update
# sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
# ca-certificates \ ca-certificates \
# clang \ clang \
# cmake \ cmake \
# gettext \ gettext \
# libcurl4-openssl-dev \ libcurl4-openssl-dev \
# libdeflate-dev \ libdeflate-dev \
# libevent-dev \ libevent-dev \
# libfmt-dev \ libfmt-dev \
# libminiupnpc-dev \ libminiupnpc-dev \
# libnatpmp-dev \ libnatpmp-dev \
# libpsl-dev \ libpsl-dev \
# libssl-dev \ libssl-dev \
# ninja-build \ ninja-build \
# npm npm
# - name: Get Source - name: Temporary workaround for sanitizer crashes
# uses: actions/checkout@v4 # https://bugs.launchpad.net/ubuntu/+source/llvm-toolchain-14/+bug/2048768
# with: # https://github.com/actions/runner-images/issues/9491
# submodules: recursive # https://github.com/actions/runner-images/pull/9513
# path: src run: sudo sysctl vm.mmap_rnd_bits=28
# - name: Configure - name: Get Source
# run: | uses: actions/checkout@v4
# cmake \ with:
# -S src \ submodules: recursive
# -B obj \ path: src
# -G Ninja \ - name: Configure
# -DCMAKE_BUILD_TYPE=Debug \ run: |
# -DCMAKE_CXX_COMPILER='clang++' \ cmake \
# -DCMAKE_CXX_FLAGS='-gdwarf-4 -fno-omit-frame-pointer -fsanitize=address,leak,undefined' \ -S src \
# -DCMAKE_C_COMPILER='clang' \ -B obj \
# -DCMAKE_C_FLAGS='-gdwarf-4 -fno-omit-frame-pointer -fsanitize=address,leak,undefined' \ -G Ninja \
# -DCMAKE_INSTALL_PREFIX=pfx \ -DCMAKE_BUILD_TYPE=Debug \
# -DENABLE_CLI=OFF \ -DCMAKE_CXX_COMPILER='clang++' \
# -DENABLE_DAEMON=OFF \ -DCMAKE_CXX_FLAGS='-gdwarf-4 -fno-omit-frame-pointer -fsanitize=address,leak,undefined' \
# -DENABLE_GTK=OFF \ -DCMAKE_C_COMPILER='clang' \
# -DENABLE_MAC=OFF \ -DCMAKE_C_FLAGS='-gdwarf-4 -fno-omit-frame-pointer -fsanitize=address,leak,undefined' \
# -DENABLE_QT=OFF \ -DCMAKE_INSTALL_PREFIX=pfx \
# -DENABLE_TESTS=ON \ -DENABLE_CLI=OFF \
# -DENABLE_UTILS=ON \ -DENABLE_DAEMON=OFF \
# -DREBUILD_WEB=OFF \ -DENABLE_GTK=OFF \
# -DRUN_CLANG_TIDY=OFF -DENABLE_MAC=OFF \
# - name: Make -DENABLE_QT=OFF \
# run: cmake --build obj --config Debug --target libtransmission-test transmission-show -DENABLE_TESTS=ON \
# - name: Test with sanitizers -DENABLE_UTILS=ON \
# run: cmake -E chdir obj ctest -j $(nproc) --build-config Debug --output-on-failure -DREBUILD_WEB=OFF \
-DRUN_CLANG_TIDY=OFF
- name: Make
run: cmake --build obj --config Debug --target libtransmission-test transmission-show
- name: Test with sanitizers
run: cmake -E chdir obj ctest -j $(nproc) --build-config Debug --output-on-failure
sanitizer-tests-macos: sanitizer-tests-macos:
runs-on: macos-14 runs-on: macos-14
@ -272,8 +277,8 @@ jobs:
run: | run: |
if grep 'warning:' makelog; then exit 1; fi if grep 'warning:' makelog; then exit 1; fi
macos-12: macos-14-arm64:
runs-on: macos-12 runs-on: macos-14
needs: [ what-to-make ] needs: [ 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-mac == 'true' || needs.what-to-make.outputs.make-qt == 'true' || needs.what-to-make.outputs.make-tests == 'true' || needs.what-to-make.outputs.make-utils == 'true' }} if: ${{ needs.what-to-make.outputs.make-cli == 'true' || needs.what-to-make.outputs.make-daemon == 'true' || needs.what-to-make.outputs.make-gtk == 'true' || needs.what-to-make.outputs.make-mac == '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' }}
steps: steps:
@ -304,14 +309,14 @@ jobs:
-G Ninja \ -G Ninja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=pfx \ -DCMAKE_INSTALL_PREFIX=pfx \
-DCMAKE_OSX_ARCHITECTURES='x86_64' \ -DCMAKE_OSX_ARCHITECTURES='arm64' \
-DCMAKE_PREFIX_PATH=`brew --prefix`/opt/qt \ -DCMAKE_PREFIX_PATH=`brew --prefix`/opt/qt \
-DENABLE_CLI=${{ (needs.what-to-make.outputs.make-cli == 'true') && 'ON' || 'OFF' }} \ -DENABLE_CLI=${{ (needs.what-to-make.outputs.make-cli == 'true') && 'ON' || 'OFF' }} \
-DENABLE_DAEMON=${{ (needs.what-to-make.outputs.make-daemon == 'true') && 'ON' || 'OFF' }} \ -DENABLE_DAEMON=${{ (needs.what-to-make.outputs.make-daemon == 'true') && 'ON' || 'OFF' }} \
-DENABLE_GTK=${{ (needs.what-to-make.outputs.make-gtk == 'true') && 'ON' || 'OFF' }} \ -DENABLE_GTK=${{ (needs.what-to-make.outputs.make-gtk == 'true') && 'ON' || 'OFF' }} \
-DENABLE_MAC=${{ (needs.what-to-make.outputs.make-mac == 'true') && 'ON' || 'OFF' }} \ -DENABLE_MAC=${{ (needs.what-to-make.outputs.make-mac == 'true') && 'ON' || 'OFF' }} \
-DENABLE_QT=${{ (needs.what-to-make.outputs.make-qt == 'true') && 'ON' || 'OFF' }} \ -DENABLE_QT=${{ (needs.what-to-make.outputs.make-qt == 'true') && 'ON' || 'OFF' }} \
-DENABLE_TESTS=ON \ -DENABLE_TESTS=${{ (needs.what-to-make.outputs.make-tests == 'true') && 'ON' || 'OFF' }} \
-DENABLE_UTILS=${{ (needs.what-to-make.outputs.make-utils == 'true') && 'ON' || 'OFF' }} \ -DENABLE_UTILS=${{ (needs.what-to-make.outputs.make-utils == 'true') && 'ON' || 'OFF' }} \
-DREBUILD_WEB=${{ (needs.what-to-make.outputs.make-web == 'true') && 'ON' || 'OFF' }} \ -DREBUILD_WEB=${{ (needs.what-to-make.outputs.make-web == 'true') && 'ON' || 'OFF' }} \
-DENABLE_WERROR=ON \ -DENABLE_WERROR=ON \
@ -330,6 +335,42 @@ jobs:
name: binaries-${{ github.job }} name: binaries-${{ github.job }}
path: pfx/**/* path: pfx/**/*
# Only verify build support on old macOS
macos-11:
runs-on: macos-11
needs: [ what-to-make ]
if: ${{ needs.what-to-make.outputs.make-mac == 'true' }}
steps:
- name: Show Configuration
run: |
echo '${{ toJSON(needs) }}'
echo '${{ toJSON(runner) }}'
sw_vers
- name: Get Dependencies
run: |
brew install cmake gettext libdeflate libevent libpsl miniupnpc ninja node pkg-config
- name: Get Source
uses: actions/checkout@v4
with:
path: src
submodules: recursive
- name: Configure
run: |
cmake \
-S src \
-B obj \
-G Ninja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=pfx \
-DCMAKE_OSX_ARCHITECTURES='x86_64' \
-DCMAKE_PREFIX_PATH=`brew --prefix`/opt/qt \
-DENABLE_MAC=${{ (needs.what-to-make.outputs.make-mac == 'true') && 'ON' || 'OFF' }} \
-DENABLE_TESTS=OFF \
-DENABLE_WERROR=ON \
-DRUN_CLANG_TIDY=OFF
- name: Make
run: cmake --build obj --config RelWithDebInfo
alpine-musl: alpine-musl:
needs: [ what-to-make ] needs: [ what-to-make ]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
@ -478,7 +519,7 @@ jobs:
-DENABLE_GTK=OFF ` -DENABLE_GTK=OFF `
-DENABLE_MAC=OFF ` -DENABLE_MAC=OFF `
-DENABLE_QT=${{ (needs.what-to-make.outputs.make-dist == 'true' || needs.what-to-make.outputs.make-qt == 'true') && 'ON' || 'OFF' }} ` -DENABLE_QT=${{ (needs.what-to-make.outputs.make-dist == 'true' || needs.what-to-make.outputs.make-qt == 'true') && 'ON' || 'OFF' }} `
-DENABLE_TESTS=ON ` -DENABLE_TESTS=${{ (needs.what-to-make.outputs.make-tests == 'true') && 'ON' || 'OFF' }} `
-DENABLE_UTILS=ON ` -DENABLE_UTILS=ON `
-DREBUILD_WEB=${{ (needs.what-to-make.outputs.make-web == 'true') && 'ON' || 'OFF' }} ` -DREBUILD_WEB=${{ (needs.what-to-make.outputs.make-web == 'true') && 'ON' || 'OFF' }} `
-DENABLE_WERROR=ON ` -DENABLE_WERROR=ON `
@ -544,7 +585,68 @@ jobs:
name: source-tarball name: source-tarball
path: obj/transmission*.tar.* path: obj/transmission*.tar.*
macos-12-from-tarball: macos-14-universal-from-tarball:
needs: [ make-source-tarball, what-to-make ]
if: ${{ needs.what-to-make.outputs.make-mac == 'true' }}
runs-on: macos-14
steps:
- name: Show Configuration
run: |
echo '${{ toJSON(needs) }}'
echo '${{ toJSON(runner) }}'
sw_vers
- name: Get Dependencies
run: |
brew install cmake gettext ninja node pkg-config
- name: Get Source
uses: actions/download-artifact@v4
with:
name: source-tarball
- name: Extract Source
run: mkdir src && tar xf transmission*.tar.* -C src --strip-components 1
- name: Configure
run: |
cmake \
-S src \
-B obj \
-G Ninja \
-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 \
-DENABLE_MAC=${{ (needs.what-to-make.outputs.make-mac == 'true') && 'ON' || 'OFF' }} \
-DENABLE_QT=OFF \
-DENABLE_TESTS=${{ (needs.what-to-make.outputs.make-tests == 'true') && 'ON' || 'OFF' }} \
-DENABLE_UTILS=${{ (needs.what-to-make.outputs.make-utils == 'true') && 'ON' || 'OFF' }} \
-DREBUILD_WEB=${{ (needs.what-to-make.outputs.make-web == 'true') && 'ON' || 'OFF' }} \
-DENABLE_WERROR=ON \
-DRUN_CLANG_TIDY=OFF \
-DUSE_SYSTEM_EVENT2=OFF \
-DUSE_SYSTEM_DEFLATE=OFF \
-DUSE_SYSTEM_DHT=OFF \
-DUSE_SYSTEM_MINIUPNPC=OFF \
-DUSE_SYSTEM_NATPMP=OFF \
-DUSE_SYSTEM_UTP=OFF \
-DUSE_SYSTEM_B64=OFF \
-DUSE_SYSTEM_PSL=OFF
- name: Make
run: cmake --build obj --config RelWithDebInfo
- name: Test
if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
env:
TMPDIR: /private/tmp
run: cmake -E chdir obj ctest -j $(sysctl -n hw.logicalcpu) --build-config RelWithDebInfo --output-on-failure
- name: Install
run: cmake --build obj --config RelWithDebInfo --target install/strip
- uses: actions/upload-artifact@v4
with:
name: binaries-${{ github.job }}
path: pfx/**/*
macos-12-x86_64-from-tarball:
needs: [ make-source-tarball, what-to-make ] needs: [ make-source-tarball, what-to-make ]
if: ${{ needs.what-to-make.outputs.make-cli == 'true' || needs.what-to-make.outputs.make-daemon == 'true' || needs.what-to-make.outputs.make-gtk == 'true' || needs.what-to-make.outputs.make-mac == 'true' || needs.what-to-make.outputs.make-qt == 'true' || needs.what-to-make.outputs.make-tests == 'true' || needs.what-to-make.outputs.make-utils == 'true' }} if: ${{ needs.what-to-make.outputs.make-cli == 'true' || needs.what-to-make.outputs.make-daemon == 'true' || needs.what-to-make.outputs.make-gtk == 'true' || needs.what-to-make.outputs.make-mac == '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: macos-12 runs-on: macos-12

View File

@ -41,7 +41,7 @@ On macOS, Transmission is usually built with Xcode. Everywhere else, it's CMake
- Prefer `enum class` over `enum` - Prefer `enum class` over `enum`
- Prefer new-style headers, e.g. `<cstring>` over `<string.h>` - Prefer new-style headers, e.g. `<cstring>` over `<string.h>`
- Fix any warnings in new code before merging - Fix any warnings in new code before merging
- Run `./code-style.sh` on your code to ensure the whole codebase has consistent indentation. - Run `./code_style.sh` on your code to ensure the whole codebase has consistent indentation.
Note that Transmission existed in C for over a decade and those idioms don't change overnight. "Follow the C++ core guidelines" can be difficult when working with older code, and the maintainers will understand that when reviewing your PRs. :smiley: Note that Transmission existed in C for over a decade and those idioms don't change overnight. "Follow the C++ core guidelines" can be difficult when working with older code, and the maintainers will understand that when reviewing your PRs. :smiley:

View File

@ -370,6 +370,7 @@ tr_variant load_settings(char const* config_dir)
tr_variantDictAddBool(&app_defaults, TR_KEY_watch_dir_enabled, false); tr_variantDictAddBool(&app_defaults, TR_KEY_watch_dir_enabled, false);
tr_variantDictAddBool(&app_defaults, TR_KEY_watch_dir_force_generic, false); tr_variantDictAddBool(&app_defaults, TR_KEY_watch_dir_force_generic, false);
tr_variantDictAddBool(&app_defaults, TR_KEY_rpc_enabled, true); tr_variantDictAddBool(&app_defaults, TR_KEY_rpc_enabled, true);
tr_variantDictAddBool(&app_defaults, TR_KEY_start_paused, false);
return tr_sessionLoadSettings(&app_defaults, config_dir, MyName); return tr_sessionLoadSettings(&app_defaults, config_dir, MyName);
} }
@ -428,7 +429,6 @@ bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_setting
int c; int c;
char const* optstr; char const* optstr;
paused_ = false;
*dump_settings = false; *dump_settings = false;
*foreground = false; *foreground = false;
@ -562,7 +562,7 @@ bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_setting
break; break;
case 800: case 800:
paused_ = true; tr_variantDictAddBool(&settings_, TR_KEY_start_paused, true);
break; break;
case 910: case 910:
@ -768,7 +768,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
auto watchdir = std::unique_ptr<Watchdir>{}; auto watchdir = std::unique_ptr<Watchdir>{};
if (auto tmp_bool = false; tr_variantDictFindBool(&settings_, TR_KEY_watch_dir_enabled, &tmp_bool) && tmp_bool) if (auto tmp_bool = false; tr_variantDictFindBool(&settings_, TR_KEY_watch_dir_enabled, &tmp_bool) && tmp_bool)
{ {
auto force_generic = bool{ false }; auto force_generic = false;
(void)tr_variantDictFindBool(&settings_, TR_KEY_watch_dir_force_generic, &force_generic); (void)tr_variantDictFindBool(&settings_, TR_KEY_watch_dir_force_generic, &force_generic);
auto dir = std::string_view{}; auto dir = std::string_view{};
@ -792,7 +792,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
{ {
tr_ctor* ctor = tr_ctorNew(my_session_); tr_ctor* ctor = tr_ctorNew(my_session_);
if (paused_) if (auto paused = false; tr_variantDictFindBool(&settings_, TR_KEY_start_paused, &paused) && paused)
{ {
tr_ctorSetPaused(ctor, TR_FORCE, true); tr_ctorSetPaused(ctor, TR_FORCE, true);
} }

View File

@ -46,7 +46,6 @@ private:
#ifdef HAVE_SYS_SIGNALFD_H #ifdef HAVE_SYS_SIGNALFD_H
int sigfd_ = -1; int sigfd_ = -1;
#endif /* signalfd API */ #endif /* signalfd API */
bool paused_ = false;
bool seen_hup_ = false; bool seen_hup_ = false;
std::string config_dir_; std::string config_dir_;
tr_variant settings_ = {}; tr_variant settings_ = {};

View File

@ -79,14 +79,15 @@ Here is a sample of the three basic types: respectively Boolean, Number and Stri
* **lpd-enabled:** Boolean (default = false) Enable [Local Peer Discovery (LPD)](https://en.wikipedia.org/wiki/Local_Peer_Discovery). * **lpd-enabled:** Boolean (default = false) Enable [Local Peer Discovery (LPD)](https://en.wikipedia.org/wiki/Local_Peer_Discovery).
* **message-level:** Number (0 = None, 1 = Critical, 2 = Error, 3 = Warn, 4 = Info, 5 = Debug, 6 = Trace; default = 2) Set verbosity of Transmission's log messages. * **message-level:** Number (0 = None, 1 = Critical, 2 = Error, 3 = Warn, 4 = Info, 5 = Debug, 6 = Trace; default = 2) Set verbosity of Transmission's log messages.
* **pex-enabled:** Boolean (default = true) Enable [Peer Exchange (PEX)](https://en.wikipedia.org/wiki/Peer_exchange). * **pex-enabled:** Boolean (default = true) Enable [Peer Exchange (PEX)](https://en.wikipedia.org/wiki/Peer_exchange).
* **pidfile:** String Path to file in which daemon PID will be stored (transmission-daemon only) * **pidfile:** String Path to file in which daemon PID will be stored (_transmission-daemon only_)
* **scrape-paused-torrents-enabled:** Boolean (default = true) * **scrape-paused-torrents-enabled:** Boolean (default = true)
* **script-torrent-added-enabled:** Boolean (default = false) Run a script when a torrent is added to Transmission. Environmental variables are passed in as detailed on the [Scripts](./Scripts.md) page * **script-torrent-added-enabled:** Boolean (default = false) Run a script when a torrent is added to Transmission. Environmental variables are passed in as detailed on the [Scripts](./Scripts.md) page.
* **script-torrent-added-filename:** String (default = "") Path to script. * **script-torrent-added-filename:** String (default = "") Path to script.
* **script-torrent-done-enabled:** Boolean (default = false) Run a script when a torrent is done downloading. Environmental variables are passed in as detailed on the [Scripts](./Scripts.md) page * **script-torrent-done-enabled:** Boolean (default = false) Run a script when a torrent is done downloading. Environmental variables are passed in as detailed on the [Scripts](./Scripts.md) page.
* **script-torrent-done-filename:** String (default = "") Path to script. * **script-torrent-done-filename:** String (default = "") Path to script.
* **script-torrent-done-seeding-enabled:** Boolean (default = false) Run a script when a torrent is done seeding. Environmental variables are passed in as detailed on the [Scripts](./Scripts.md) page * **script-torrent-done-seeding-enabled:** Boolean (default = false) Run a script when a torrent is done seeding. Environmental variables are passed in as detailed on the [Scripts](./Scripts.md) page.
* **script-torrent-done-seeding-filename:** String (default = "") Path to script. * **script-torrent-done-seeding-filename:** String (default = "") Path to script.
* **start_paused**: Boolean (default = false) Pause the torrents when daemon starts. _Note: transmission-daemon only._
* **tcp-enabled:** Boolean (default = true) Optionally disable TCP connection to other peers. Never disable TCP when you also disable µTP, because then your client would not be able to communicate. Disabling TCP might also break webseeds. Unless you have a good reason, you should not set this to false. * **tcp-enabled:** Boolean (default = true) Optionally disable TCP connection to other peers. Never disable TCP when you also disable µTP, because then your client would not be able to communicate. Disabling TCP might also break webseeds. Unless you have a good reason, you should not set this to false.
* **torrent-added-verify-mode:** String ("fast", "full", default: "fast") Whether newly-added torrents' local data should be fully verified when added, or wait and verify them on-demand later. See [#2626](https://github.com/transmission/transmission/pull/2626) for more discussion. * **torrent-added-verify-mode:** String ("fast", "full", default: "fast") Whether newly-added torrents' local data should be fully verified when added, or wait and verify them on-demand later. See [#2626](https://github.com/transmission/transmission/pull/2626) for more discussion.
* **utp-enabled:** Boolean (default = true) Enable [Micro Transport Protocol (µTP)](https://en.wikipedia.org/wiki/Micro_Transport_Protocol) * **utp-enabled:** Boolean (default = true) Enable [Micro Transport Protocol (µTP)](https://en.wikipedia.org/wiki/Micro_Transport_Protocol)

View File

@ -386,7 +386,6 @@ TorrentFileChooserDialog::TorrentFileChooserDialog(Gtk::Window& parent, Glib::Re
void TorrentUrlChooserDialog::onOpenURLResponse(int response, Gtk::Entry const& entry, Glib::RefPtr<Session> const& core) void TorrentUrlChooserDialog::onOpenURLResponse(int response, Gtk::Entry const& entry, Glib::RefPtr<Session> const& core)
{ {
if (response == TR_GTK_RESPONSE_TYPE(CANCEL)) if (response == TR_GTK_RESPONSE_TYPE(CANCEL))
{ {
close(); close();

View File

@ -61,4 +61,5 @@ Copyright 2017 Endless Mobile, Inc.
<release date="2020-05-03" version="3.00" /> <release date="2020-05-03" version="3.00" />
</releases> </releases>
<translation type="gettext">transmission-gtk</translation> <translation type="gettext">transmission-gtk</translation>
<launchable type="desktop-id">transmission-gtk.desktop</launchable>
</component> </component>

View File

@ -339,7 +339,7 @@ void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::
{ {
BasicHandler::EndDict(context); BasicHandler::EndDict(context);
if (pex_.is_valid_for_peers()) if (pex_.is_valid())
{ {
response_.pex.push_back(pex_); response_.pex.push_back(pex_);
pex_ = {}; pex_ = {};

View File

@ -204,10 +204,12 @@ std::optional<address_range_t> parseCidrLine(std::string_view line)
return {}; return {};
} }
auto const mask = uint32_t{ 0xFFFFFFFF } << (32 - *pflen); auto const mask = ~(~uint32_t{ 0 } >> *pflen);
auto const ip_u = htonl(addrpair.first.addr.addr4.s_addr); auto const ip_u = ntohl(addrpair.first.addr.addr4.s_addr);
addrpair.first.addr.addr4.s_addr = ntohl(ip_u & mask); auto tmp = htonl(ip_u & mask);
addrpair.second.addr.addr4.s_addr = ntohl(ip_u | (~mask)); std::tie(addrpair.first, std::ignore) = tr_address::from_compact_ipv4(reinterpret_cast<std::byte*>(&tmp));
tmp = htonl(ip_u | (~mask));
std::tie(addrpair.second, std::ignore) = tr_address::from_compact_ipv4(reinterpret_cast<std::byte*>(&tmp));
return addrpair; return addrpair;
} }

View File

@ -16,6 +16,12 @@
#include "libtransmission/block-info.h" #include "libtransmission/block-info.h"
#include "libtransmission/completion.h" #include "libtransmission/completion.h"
#include "libtransmission/tr-assert.h" #include "libtransmission/tr-assert.h"
#include "libtransmission/torrent.h"
tr_completion::tr_completion(tr_torrent const* tor, tr_block_info const* block_info)
: tr_completion{ [tor](tr_piece_index_t const piece) { return tor->piece_is_wanted(piece); }, block_info }
{
}
uint64_t tr_completion::compute_has_valid() const uint64_t tr_completion::compute_has_valid() const
{ {
@ -55,7 +61,7 @@ uint64_t tr_completion::compute_size_when_done() const
auto size = uint64_t{ 0 }; auto size = uint64_t{ 0 };
for (tr_piece_index_t piece = 0, n_pieces = block_info_->piece_count(); piece < n_pieces; ++piece) for (tr_piece_index_t piece = 0, n_pieces = block_info_->piece_count(); piece < n_pieces; ++piece)
{ {
if (tor_->piece_is_wanted(piece)) if (piece_is_wanted_(piece))
{ {
size += block_info_->piece_size(piece); size += block_info_->piece_size(piece);
} }

View File

@ -10,8 +10,9 @@
#endif #endif
#include <algorithm> #include <algorithm>
#include <cstdint>
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint>
#include <functional>
#include <optional> #include <optional>
#include <vector> #include <vector>
@ -26,21 +27,18 @@
*/ */
struct tr_completion struct tr_completion
{ {
struct torrent_view using PieceIsWantedFunc = std::function<bool(tr_piece_index_t piece)>;
{
virtual bool piece_is_wanted(tr_piece_index_t piece) const = 0;
virtual ~torrent_view() = default; tr_completion(PieceIsWantedFunc&& piece_is_wanted, tr_block_info const* block_info)
}; : piece_is_wanted_{ std::move(piece_is_wanted) }
explicit tr_completion(torrent_view const* tor, tr_block_info const* block_info)
: tor_{ tor }
, block_info_{ block_info } , block_info_{ block_info }
, blocks_{ block_info_->block_count() } , blocks_{ block_info_->block_count() }
{ {
blocks_.set_has_none(); blocks_.set_has_none();
} }
tr_completion(tr_torrent const* tor, tr_block_info const* block_info);
[[nodiscard]] constexpr tr_bitfield const& blocks() const noexcept [[nodiscard]] constexpr tr_bitfield const& blocks() const noexcept
{ {
return blocks_; return blocks_;
@ -175,7 +173,7 @@ private:
void remove_block(tr_block_index_t block); void remove_block(tr_block_index_t block);
torrent_view const* tor_; PieceIsWantedFunc piece_is_wanted_;
tr_block_info const* block_info_; tr_block_info const* block_info_;
tr_bitfield blocks_{ 0 }; tr_bitfield blocks_{ 0 };

View File

@ -626,6 +626,17 @@ void tr_handshake::on_error(tr_peerIo* io, tr_error const& error, void* vhandsha
{ {
auto* handshake = static_cast<tr_handshake*>(vhandshake); auto* handshake = static_cast<tr_handshake*>(vhandshake);
auto const retry = [&]()
{
handshake->send_handshake(io);
handshake->set_state(State::AwaitingHandshake);
};
auto const fail = [&]()
{
tr_logAddTraceHand(handshake, fmt::format("handshake socket err: {:s} ({:d})", error.message(), error.code()));
handshake->done(false);
};
if (io->is_utp() && !io->is_incoming() && handshake->is_state(State::AwaitingYb)) if (io->is_utp() && !io->is_incoming() && handshake->is_state(State::AwaitingYb))
{ {
// the peer probably doesn't speak µTP. // the peer probably doesn't speak µTP.
@ -641,10 +652,12 @@ void tr_handshake::on_error(tr_peerIo* io, tr_error const& error, void* vhandsha
if (handshake->mediator_->allows_tcp() && io->reconnect()) if (handshake->mediator_->allows_tcp() && io->reconnect())
{ {
handshake->send_handshake(io); retry();
handshake->set_state(State::AwaitingHandshake);
return; return;
} }
fail();
return;
} }
/* if the error happened while we were sending a public key, we might /* if the error happened while we were sending a public key, we might
@ -654,13 +667,11 @@ void tr_handshake::on_error(tr_peerIo* io, tr_error const& error, void* vhandsha
handshake->encryption_mode_ != TR_ENCRYPTION_REQUIRED && handshake->mediator_->allows_tcp() && io->reconnect()) handshake->encryption_mode_ != TR_ENCRYPTION_REQUIRED && handshake->mediator_->allows_tcp() && io->reconnect())
{ {
tr_logAddTraceHand(handshake, "handshake failed, trying plaintext..."); tr_logAddTraceHand(handshake, "handshake failed, trying plaintext...");
handshake->send_handshake(io); retry();
handshake->set_state(State::AwaitingHandshake);
return; return;
} }
tr_logAddTraceHand(handshake, fmt::format("handshake socket err: {:s} ({:d})", error.message(), error.code())); fail();
handshake->done(false);
} }
// --- // ---

View File

@ -231,7 +231,7 @@ tr_peer_socket tr_netOpenPeerSocket(tr_session* session, tr_socket_address const
TR_ASSERT(addr.is_valid()); TR_ASSERT(addr.is_valid());
TR_ASSERT(!tr_peer_socket::limit_reached(session)); TR_ASSERT(!tr_peer_socket::limit_reached(session));
if (tr_peer_socket::limit_reached(session) || !session->allowsTCP() || !socket_address.is_valid_for_peers()) if (tr_peer_socket::limit_reached(session) || !session->allowsTCP() || !socket_address.is_valid())
{ {
return {}; return {};
} }
@ -320,21 +320,16 @@ tr_socket_t tr_netBindTCPImpl(tr_address const& addr, tr_port port, bool suppres
int optval = 1; int optval = 1;
(void)setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<char const*>(&optval), sizeof(optval)); (void)setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<char const*>(&optval), sizeof(optval));
(void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char const*>(&optval), sizeof(optval)); (void)evutil_make_listen_socket_reuseable(fd);
#ifdef IPV6_V6ONLY if (addr.is_ipv6() && evutil_make_listen_socket_ipv6only(fd) != 0 &&
sockerrno != ENOPROTOOPT) // if the kernel doesn't support it, ignore it
if (addr.is_ipv6() &&
(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char const*>(&optval), sizeof(optval)) == -1) &&
(sockerrno != ENOPROTOOPT)) // if the kernel doesn't support it, ignore it
{ {
*err_out = sockerrno; *err_out = sockerrno;
tr_net_close_socket(fd); tr_net_close_socket(fd);
return TR_BAD_SOCKET; return TR_BAD_SOCKET;
} }
#endif
auto const [sock, addrlen] = tr_socket_address::to_sockaddr(addr, port); auto const [sock, addrlen] = tr_socket_address::to_sockaddr(addr, port);
if (bind(fd, reinterpret_cast<sockaddr const*>(&sock), addrlen) == -1) if (bind(fd, reinterpret_cast<sockaddr const*>(&sock), addrlen) == -1)
@ -446,23 +441,29 @@ namespace is_valid_for_peers_helpers
/* isMartianAddr was written by Juliusz Chroboczek, /* isMartianAddr was written by Juliusz Chroboczek,
and is covered under the same license as third-party/dht/dht.c. */ and is covered under the same license as third-party/dht/dht.c. */
[[nodiscard]] auto is_martian_addr(tr_address const& addr) [[nodiscard]] auto is_martian_addr(tr_address const& addr, tr_peer_from from)
{ {
static auto constexpr Zeroes = std::array<unsigned char, 16>{}; static auto constexpr Zeroes = std::array<unsigned char, 16>{};
auto const loopback_allowed = from == TR_PEER_FROM_INCOMING || from == TR_PEER_FROM_LPD || from == TR_PEER_FROM_RESUME;
switch (addr.type) switch (addr.type)
{ {
case TR_AF_INET: case TR_AF_INET:
{ {
auto const* const address = reinterpret_cast<unsigned char const*>(&addr.addr.addr4); auto const* const address = reinterpret_cast<unsigned char const*>(&addr.addr.addr4);
return address[0] == 0 || address[0] == 127 || (address[0] & 0xE0) == 0xE0; return address[0] == 0 || // 0.x.x.x
(!loopback_allowed && address[0] == 127) || // 127.x.x.x
(address[0] & 0xE0) == 0xE0; // multicast address
} }
case TR_AF_INET6: case TR_AF_INET6:
{ {
auto const* const address = reinterpret_cast<unsigned char const*>(&addr.addr.addr6); auto const* const address = reinterpret_cast<unsigned char const*>(&addr.addr.addr6);
return address[0] == 0xFF || return address[0] == 0xFF || // multicast address
(memcmp(address, std::data(Zeroes), 15) == 0 && (address[15] == 0 || address[15] == 1)); (std::memcmp(address, std::data(Zeroes), 15) == 0 &&
(address[15] == 0 || // ::
(!loopback_allowed && address[15] == 1)) // ::1
);
} }
default: default:
@ -493,23 +494,29 @@ std::optional<tr_address> tr_address::from_string(std::string_view address_sv)
{ {
auto const address_sz = tr_strbuf<char, TR_ADDRSTRLEN>{ address_sv }; auto const address_sz = tr_strbuf<char, TR_ADDRSTRLEN>{ address_sv };
auto addr = tr_address{}; auto ss = sockaddr_storage{};
auto sslen = int{ sizeof(ss) };
addr.addr.addr4 = {}; if (evutil_parse_sockaddr_port(address_sz, reinterpret_cast<sockaddr*>(&ss), &sslen) != 0)
if (evutil_inet_pton(AF_INET, address_sz, &addr.addr.addr4) == 1)
{ {
return {};
}
auto addr = tr_address{};
switch (ss.ss_family)
{
case AF_INET:
addr.addr.addr4 = reinterpret_cast<sockaddr_in*>(&ss)->sin_addr;
addr.type = TR_AF_INET; addr.type = TR_AF_INET;
return addr; return addr;
}
addr.addr.addr6 = {}; case AF_INET6:
if (evutil_inet_pton(AF_INET6, address_sz, &addr.addr.addr6) == 1) addr.addr.addr6 = reinterpret_cast<sockaddr_in6*>(&ss)->sin6_addr;
{
addr.type = TR_AF_INET6; addr.type = TR_AF_INET6;
return addr; return addr;
}
return {}; default:
return {};
}
} }
std::string_view tr_address::display_name(char* out, size_t outlen) const std::string_view tr_address::display_name(char* out, size_t outlen) const
@ -700,15 +707,15 @@ int tr_address::compare(tr_address const& that) const noexcept // <=>
std::string tr_socket_address::display_name(tr_address const& address, tr_port port) noexcept std::string tr_socket_address::display_name(tr_address const& address, tr_port port) noexcept
{ {
return fmt::format("[{:s}]:{:d}", address.display_name(), port.host()); return fmt::format(address.is_ipv6() ? "[{:s}]:{:d}" : "{:s}:{:d}", address.display_name(), port.host());
} }
bool tr_socket_address::is_valid_for_peers() const noexcept bool tr_socket_address::is_valid_for_peers(tr_peer_from from) const noexcept
{ {
using namespace is_valid_for_peers_helpers; using namespace is_valid_for_peers_helpers;
return is_valid() && !std::empty(port_) && !is_ipv6_link_local_address(address_) && !is_ipv4_mapped_address(address_) && return is_valid() && !std::empty(port_) && !is_ipv6_link_local_address(address_) && !is_ipv4_mapped_address(address_) &&
!is_martian_addr(address_); !is_martian_addr(address_, from);
} }
std::optional<tr_socket_address> tr_socket_address::from_sockaddr(struct sockaddr const* from) std::optional<tr_socket_address> tr_socket_address::from_sockaddr(struct sockaddr const* from)

View File

@ -60,6 +60,8 @@ using tr_socket_t = int;
#define sockerrno errno #define sockerrno errno
#endif #endif
#include "libtransmission/transmission.h" // tr_peer_from
#include "libtransmission/tr-assert.h" #include "libtransmission/tr-assert.h"
#include "libtransmission/utils.h" // for tr_compare_3way() #include "libtransmission/utils.h" // for tr_compare_3way()
@ -234,7 +236,7 @@ struct tr_address
[[nodiscard]] bool is_global_unicast_address() const noexcept; [[nodiscard]] bool is_global_unicast_address() const noexcept;
tr_address_type type; tr_address_type type = NUM_TR_AF_INET_TYPES;
union union
{ {
struct in6_addr addr6; struct in6_addr addr6;
@ -306,7 +308,7 @@ struct tr_socket_address
return address_.is_valid(); return address_.is_valid();
} }
[[nodiscard]] bool is_valid_for_peers() const noexcept; [[nodiscard]] bool is_valid_for_peers(tr_peer_from from) const noexcept;
[[nodiscard]] int compare(tr_socket_address const& that) const noexcept [[nodiscard]] int compare(tr_socket_address const& that) const noexcept
{ {
@ -402,7 +404,7 @@ struct tr_socket_address
}; };
template<> template<>
class std::hash<tr_socket_address> struct std::hash<tr_socket_address>
{ {
public: public:
std::size_t operator()(tr_socket_address const& socket_address) const noexcept std::size_t operator()(tr_socket_address const& socket_address) const noexcept

View File

@ -129,11 +129,6 @@ std::shared_ptr<tr_peerIo> tr_peerIo::new_outgoing(
TR_ASSERT(socket_address.is_valid()); TR_ASSERT(socket_address.is_valid());
TR_ASSERT(utp || session->allowsTCP()); TR_ASSERT(utp || session->allowsTCP());
if (!socket_address.is_valid_for_peers())
{
return {};
}
auto peer_io = tr_peerIo::create(session, parent, &info_hash, false, is_seed); auto peer_io = tr_peerIo::create(session, parent, &info_hash, false, is_seed);
auto const func = small::max_size_map<preferred_key_t, std::function<bool()>, TR_NUM_PREFERRED_TRANSPORT>{ auto const func = small::max_size_map<preferred_key_t, std::function<bool()>, TR_NUM_PREFERRED_TRANSPORT>{
{ TR_PREFER_UTP, { TR_PREFER_UTP,
@ -250,12 +245,12 @@ bool tr_peerIo::reconnect()
return false; return false;
} }
socket_ = tr_netOpenPeerSocket(session_, socket_address(), is_seed()); auto sock = tr_netOpenPeerSocket(session_, socket_address(), is_seed());
if (!sock.is_tcp())
if (!socket_.is_tcp())
{ {
return false; return false;
} }
socket_ = std::move(sock);
this->event_read_.reset(event_new(session_->event_base(), socket_.handle.tcp, EV_READ, event_read_cb, this)); this->event_read_.reset(event_new(session_->event_base(), socket_.handle.tcp, EV_READ, event_read_cb, this));
this->event_write_.reset(event_new(session_->event_base(), socket_.handle.tcp, EV_WRITE, event_write_cb, this)); this->event_write_.reset(event_new(session_->event_base(), socket_.handle.tcp, EV_WRITE, event_write_cb, this));

View File

@ -347,7 +347,6 @@ public:
tor_in->swarm_is_all_seeds_.observe([this](tr_torrent* /*tor*/) { on_swarm_is_all_seeds(); }), tor_in->swarm_is_all_seeds_.observe([this](tr_torrent* /*tor*/) { on_swarm_is_all_seeds(); }),
} } } }
{ {
rebuild_webseeds(); rebuild_webseeds();
} }
@ -616,7 +615,7 @@ public:
ActiveRequests active_requests; ActiveRequests active_requests;
// depends-on: active_requests // depends-on: active_requests
std::vector<std::unique_ptr<tr_peer>> webseeds; std::vector<std::unique_ptr<tr_webseed>> webseeds;
// depends-on: active_requests // depends-on: active_requests
Peers peers; Peers peers;
@ -646,7 +645,7 @@ private:
webseeds.reserve(n); webseeds.reserve(n);
for (size_t i = 0; i < n; ++i) for (size_t i = 0; i < n; ++i)
{ {
webseeds.emplace_back(tr_webseedNew(*tor, tor->webseed(i), &tr_swarm::peer_callback_webseed, this)); webseeds.emplace_back(tr_webseed::create(*tor, tor->webseed(i), &tr_swarm::peer_callback_webseed, this));
} }
webseeds.shrink_to_fit(); webseeds.shrink_to_fit();
@ -1440,7 +1439,7 @@ size_t tr_peerMgrAddPex(tr_torrent* tor, tr_peer_from from, tr_pex const* pex, s
for (tr_pex const* const end = pex + n_pex; pex != end; ++pex) for (tr_pex const* const end = pex + n_pex; pex != end; ++pex)
{ {
if (tr_isPex(pex) && /* safeguard against corrupt data */ if (tr_isPex(pex) && /* safeguard against corrupt data */
!s->manager->blocklists_.contains(pex->socket_address.address()) && pex->is_valid_for_peers() && !s->manager->blocklists_.contains(pex->socket_address.address()) && pex->is_valid_for_peers(from) &&
from != TR_PEER_FROM_INCOMING && (from != TR_PEER_FROM_PEX || (pex->flags & ADDED_F_CONNECTABLE) != 0)) from != TR_PEER_FROM_INCOMING && (from != TR_PEER_FROM_PEX || (pex->flags & ADDED_F_CONNECTABLE) != 0))
{ {
// we store this peer since it is supposedly connectable (socket address should be the peer's listening address) // we store this peer since it is supposedly connectable (socket address should be the peer's listening address)
@ -1720,7 +1719,7 @@ tr_webseed_view tr_peerMgrWebseed(tr_torrent const* tor, size_t i)
size_t const n = std::size(tor->swarm->webseeds); size_t const n = std::size(tor->swarm->webseeds);
TR_ASSERT(i < n); TR_ASSERT(i < n);
return i >= n ? tr_webseed_view{} : tr_webseedView(tor->swarm->webseeds[i].get()); return i >= n ? tr_webseed_view{} : tor->swarm->webseeds[i]->get_view();
} }
namespace namespace
@ -1829,8 +1828,6 @@ namespace peer_stat_helpers
tr_peer_stat* tr_peerMgrPeerStats(tr_torrent const* tor, size_t* setme_count) tr_peer_stat* tr_peerMgrPeerStats(tr_torrent const* tor, size_t* setme_count)
{ {
using namespace peer_stat_helpers;
TR_ASSERT(tr_isTorrent(tor)); TR_ASSERT(tr_isTorrent(tor));
TR_ASSERT(tor->swarm->manager != nullptr); TR_ASSERT(tor->swarm->manager != nullptr);
@ -1850,7 +1847,7 @@ tr_peer_stat* tr_peerMgrPeerStats(tr_torrent const* tor, size_t* setme_count)
std::begin(peers), std::begin(peers),
std::end(peers), std::end(peers),
ret, ret,
[&now, &now_msec](auto const* peer) { return get_peer_stats(peer, now, now_msec); }); [&now, &now_msec](auto const* peer) { return peer_stat_helpers::get_peer_stats(peer, now, now_msec); });
done_promise.set_value(); done_promise.set_value();
}); });
done_future.wait(); done_future.wait();

View File

@ -517,9 +517,14 @@ struct tr_pex
return compare(that) < 0; return compare(that) < 0;
} }
[[nodiscard]] bool is_valid_for_peers() const noexcept [[nodiscard]] bool is_valid() const noexcept
{ {
return socket_address.is_valid_for_peers(); return socket_address.is_valid();
}
[[nodiscard]] bool is_valid_for_peers(tr_peer_from from) const noexcept
{
return socket_address.is_valid_for_peers(from);
} }
tr_socket_address socket_address; tr_socket_address socket_address;

View File

@ -65,10 +65,10 @@ struct tr_upnp
FreeUPNPUrls(&urls); FreeUPNPUrls(&urls);
} }
bool hasDiscovered = false;
UPNPUrls urls = {}; UPNPUrls urls = {};
IGDdatas data = {}; IGDdatas data = {};
tr_port port; tr_port advertised_port;
tr_port local_port;
std::string lanaddr; std::string lanaddr;
bool isMapped = false; bool isMapped = false;
UpnpState state = UpnpState::WillDiscover; UpnpState state = UpnpState::WillDiscover;
@ -134,9 +134,9 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
[[nodiscard]] int get_specific_port_mapping_entry(tr_upnp const* handle, char const* proto) [[nodiscard]] int get_specific_port_mapping_entry(tr_upnp const* handle, char const* proto)
{ {
auto int_client = std::array<char, 16>{}; auto int_client = std::array<char, 16>{};
auto int_port = std::array<char, 16>{}; auto int_port = std::array<char, 6>{};
auto const port_str = std::to_string(handle->port.host()); auto const port_str = std::to_string(handle->advertised_port.host());
#if (MINIUPNPC_API_VERSION >= 10) /* adds remoteHost arg */ #if (MINIUPNPC_API_VERSION >= 10) /* adds remoteHost arg */
int const err = UPNP_GetSpecificPortMappingEntry( int const err = UPNP_GetSpecificPortMappingEntry(
@ -174,19 +174,25 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
return err; return err;
} }
[[nodiscard]] int upnp_add_port_mapping(tr_upnp const* handle, char const* proto, tr_port port, char const* desc) [[nodiscard]] int upnp_add_port_mapping(
tr_upnp const* handle,
char const* proto,
tr_port advertised_port,
tr_port local_port,
char const* desc)
{ {
int const old_errno = errno; int const old_errno = errno;
errno = 0; errno = 0;
auto const port_str = std::to_string(port.host()); 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) #if (MINIUPNPC_API_VERSION >= 8)
int const err = UPNP_AddPortMapping( int const err = UPNP_AddPortMapping(
handle->urls.controlURL, handle->urls.controlURL,
handle->data.first.servicetype, handle->data.first.servicetype,
port_str.c_str(), advertised_port_str.c_str(),
port_str.c_str(), local_port_str.c_str(),
handle->lanaddr.c_str(), handle->lanaddr.c_str(),
desc, desc,
proto, proto,
@ -196,8 +202,8 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
int const err = UPNP_AddPortMapping( int const err = UPNP_AddPortMapping(
handle->urls.controlURL, handle->urls.controlURL,
handle->data.first.servicetype, handle->data.first.servicetype,
port_str.c_str(), advertised_port_str.c_str(),
port_str.c_str(), local_port_str.c_str(),
handle->lanaddr.c_str(), handle->lanaddr.c_str(),
desc, desc,
proto, proto,
@ -213,9 +219,9 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
return err; return err;
} }
void tr_upnpDeletePortMapping(tr_upnp const* handle, char const* proto, tr_port port) void tr_upnpDeletePortMapping(tr_upnp const* handle, char const* proto, tr_port advertised_port)
{ {
auto const port_str = std::to_string(port.host()); auto const port_str = std::to_string(advertised_port.host());
UPNP_DeletePortMapping(handle->urls.controlURL, handle->data.first.servicetype, port_str.c_str(), proto, nullptr); UPNP_DeletePortMapping(handle->urls.controlURL, handle->data.first.servicetype, port_str.c_str(), proto, nullptr);
} }
@ -255,7 +261,13 @@ void tr_upnpClose(tr_upnp* handle)
delete handle; delete handle;
} }
tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_enabled, bool do_port_check, std::string bindaddr) tr_port_forwarding_state tr_upnpPulse(
tr_upnp* handle,
tr_port advertised_port,
tr_port local_port,
bool is_enabled,
bool do_port_check,
std::string bindaddr)
{ {
if (is_enabled && handle->state == UpnpState::WillDiscover) if (is_enabled && handle->state == UpnpState::WillDiscover)
{ {
@ -282,7 +294,6 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena
tr_logAddInfo(fmt::format(_("Found Internet Gateway Device '{url}'"), fmt::arg("url", handle->urls.controlURL))); tr_logAddInfo(fmt::format(_("Found Internet Gateway Device '{url}'"), fmt::arg("url", handle->urls.controlURL)));
tr_logAddInfo(fmt::format(_("Local Address is '{address}'"), fmt::arg("address", lanaddr.data()))); tr_logAddInfo(fmt::format(_("Local Address is '{address}'"), fmt::arg("address", lanaddr.data())));
handle->state = UpnpState::Idle; handle->state = UpnpState::Idle;
handle->hasDiscovered = true;
handle->lanaddr = std::data(lanaddr); handle->lanaddr = std::data(lanaddr);
} }
else else
@ -295,7 +306,8 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena
freeUPNPDevlist(devlist); freeUPNPDevlist(devlist);
} }
if ((handle->state == UpnpState::Idle) && (handle->isMapped) && (!is_enabled || handle->port != port)) if ((handle->state == UpnpState::Idle) && (handle->isMapped) &&
(!is_enabled || handle->advertised_port != advertised_port || handle->local_port != local_port))
{ {
handle->state = UpnpState::WillUnmap; handle->state = UpnpState::WillUnmap;
} }
@ -304,14 +316,17 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena
((get_specific_port_mapping_entry(handle, "TCP") != UPNPCOMMAND_SUCCESS) || ((get_specific_port_mapping_entry(handle, "TCP") != UPNPCOMMAND_SUCCESS) ||
(get_specific_port_mapping_entry(handle, "UDP") != UPNPCOMMAND_SUCCESS))) (get_specific_port_mapping_entry(handle, "UDP") != UPNPCOMMAND_SUCCESS)))
{ {
tr_logAddInfo(fmt::format(_("Port {port} is not forwarded"), fmt::arg("port", handle->port.host()))); tr_logAddInfo(fmt::format(
_("Local port {local_port} is not forwarded to {advertised_port}"),
fmt::arg("local_port", handle->local_port.host()),
fmt::arg("advertised_port", handle->advertised_port.host())));
handle->isMapped = false; handle->isMapped = false;
} }
if (handle->state == UpnpState::WillUnmap) if (handle->state == UpnpState::WillUnmap)
{ {
tr_upnpDeletePortMapping(handle, "TCP", handle->port); tr_upnpDeletePortMapping(handle, "TCP", handle->advertised_port);
tr_upnpDeletePortMapping(handle, "UDP", handle->port); tr_upnpDeletePortMapping(handle, "UDP", handle->advertised_port);
tr_logAddInfo(fmt::format( tr_logAddInfo(fmt::format(
_("Stopping port forwarding through '{url}', service '{type}'"), _("Stopping port forwarding through '{url}', service '{type}'"),
@ -320,7 +335,8 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena
handle->isMapped = false; handle->isMapped = false;
handle->state = UpnpState::Idle; handle->state = UpnpState::Idle;
handle->port = {}; handle->advertised_port = {};
handle->local_port = {};
} }
if ((handle->state == UpnpState::Idle) && is_enabled && !handle->isMapped) if ((handle->state == UpnpState::Idle) && is_enabled && !handle->isMapped)
@ -338,9 +354,9 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena
} }
else else
{ {
auto const desc = fmt::format("Transmission at {:d}", port.host()); auto const desc = fmt::format("Transmission at {:d}", local_port.host());
int const err_tcp = upnp_add_port_mapping(handle, "TCP", port, desc.c_str()); int const err_tcp = upnp_add_port_mapping(handle, "TCP", advertised_port, local_port, desc.c_str());
int const err_udp = upnp_add_port_mapping(handle, "UDP", port, desc.c_str()); int const err_udp = upnp_add_port_mapping(handle, "UDP", advertised_port, local_port, desc.c_str());
handle->isMapped = err_tcp == 0 || err_udp == 0; handle->isMapped = err_tcp == 0 || err_udp == 0;
} }
@ -350,18 +366,23 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena
fmt::arg("url", handle->urls.controlURL), fmt::arg("url", handle->urls.controlURL),
fmt::arg("type", handle->data.first.servicetype), fmt::arg("type", handle->data.first.servicetype),
fmt::arg("address", handle->lanaddr), fmt::arg("address", handle->lanaddr),
fmt::arg("port", port.host()))); fmt::arg("port", local_port.host())));
if (handle->isMapped) if (handle->isMapped)
{ {
tr_logAddInfo(fmt::format(_("Port {port} is forwarded"), fmt::arg("port", port.host()))); tr_logAddInfo(fmt::format(
handle->port = port; _("Forwarded local port {local_port} to {advertised_port}"),
fmt::arg("local_port", local_port.host()),
fmt::arg("advertised_port", advertised_port.host())));
handle->advertised_port = advertised_port;
handle->local_port = local_port;
handle->state = UpnpState::Idle; handle->state = UpnpState::Idle;
} }
else else
{ {
tr_logAddInfo(_("If your router supports UPnP, please make sure UPnP is enabled!")); tr_logAddInfo(_("If your router supports UPnP, please make sure UPnP is enabled!"));
handle->port = {}; handle->advertised_port = {};
handle->local_port = {};
handle->state = UpnpState::Failed; handle->state = UpnpState::Failed;
} }
} }

View File

@ -25,6 +25,12 @@ tr_upnp* tr_upnpInit();
void tr_upnpClose(tr_upnp* handle); void tr_upnpClose(tr_upnp* handle);
tr_port_forwarding_state tr_upnpPulse(tr_upnp*, tr_port port, bool is_enabled, bool do_port_check, std::string bindaddr); tr_port_forwarding_state tr_upnpPulse(
tr_upnp*,
tr_port advertised_port,
tr_port local_port,
bool is_enabled,
bool do_port_check,
std::string bindaddr);
/* @} */ /* @} */

View File

@ -205,6 +205,7 @@ private:
upnp_state_ = tr_upnpPulse( upnp_state_ = tr_upnpPulse(
upnp_, upnp_,
mediator_.advertised_peer_port(),
mediator_.local_peer_port(), mediator_.local_peer_port(),
is_enabled, is_enabled,
do_check, do_check,

View File

@ -28,6 +28,7 @@ public:
public: public:
virtual ~Mediator() = default; virtual ~Mediator() = default;
[[nodiscard]] virtual tr_port advertised_peer_port() const = 0;
[[nodiscard]] virtual tr_port local_peer_port() const = 0; [[nodiscard]] virtual tr_port local_peer_port() const = 0;
[[nodiscard]] virtual tr_address incoming_peer_address() const = 0; [[nodiscard]] virtual tr_address incoming_peer_address() const = 0;
[[nodiscard]] virtual libtransmission::TimerMaker& timer_maker() = 0; [[nodiscard]] virtual libtransmission::TimerMaker& timer_maker() = 0;

View File

@ -367,6 +367,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
"start-added-torrents"sv, "start-added-torrents"sv,
"start-minimized"sv, "start-minimized"sv,
"startDate"sv, "startDate"sv,
"start_paused"sv,
"status"sv, "status"sv,
"statusbar-stats"sv, "statusbar-stats"sv,
"tag"sv, "tag"sv,

View File

@ -368,6 +368,7 @@ enum
TR_KEY_start_added_torrents, TR_KEY_start_added_torrents,
TR_KEY_start_minimized, TR_KEY_start_minimized,
TR_KEY_startDate, TR_KEY_startDate,
TR_KEY_start_paused,
TR_KEY_status, TR_KEY_status,
TR_KEY_statusbar_stats, TR_KEY_statusbar_stats,
TR_KEY_tag, TR_KEY_tag,

View File

@ -735,6 +735,13 @@ tr_resume::fields_t load_from_file(tr_torrent* tor, tr_torrent::ResumeHelper& he
fields_loaded |= tr_resume::BandwidthPriority; fields_loaded |= tr_resume::BandwidthPriority;
} }
if (auto val = bool{};
(fields_to_load & tr_resume::SequentialDownload) != 0 && tr_variantDictFindBool(&top, TR_KEY_sequentialDownload, &val))
{
tor->set_sequential_download(val);
fields_loaded |= tr_resume::SequentialDownload;
}
if ((fields_to_load & tr_resume::Peers) != 0) if ((fields_to_load & tr_resume::Peers) != 0)
{ {
fields_loaded |= loadPeers(&top, tor); fields_loaded |= loadPeers(&top, tor);

View File

@ -44,6 +44,7 @@ auto inline constexpr Filenames = fields_t{ 1 << 20 };
auto inline constexpr Name = fields_t{ 1 << 21 }; auto inline constexpr Name = fields_t{ 1 << 21 };
auto inline constexpr Labels = fields_t{ 1 << 22 }; auto inline constexpr Labels = fields_t{ 1 << 22 };
auto inline constexpr Group = fields_t{ 1 << 23 }; auto inline constexpr Group = fields_t{ 1 << 23 };
auto inline constexpr SequentialDownload = fields_t{ 1 << 24 };
auto inline constexpr All = ~fields_t{ 0 }; auto inline constexpr All = ~fields_t{ 0 };

View File

@ -74,7 +74,7 @@ auto constexpr TrUnixSocketPrefix = "unix:"sv;
#ifdef _WIN32 #ifdef _WIN32
auto inline constexpr TrUnixAddrStrLen = size_t{ INET6_ADDRSTRLEN }; auto inline constexpr TrUnixAddrStrLen = size_t{ INET6_ADDRSTRLEN };
#else #else
auto inline constexpr TrUnixAddrStrLen = size_t{ sizeof(((struct sockaddr_un*)nullptr)->sun_path) + auto inline constexpr TrUnixAddrStrLen = size_t{ sizeof(std::declval<struct sockaddr_un>().sun_path) +
std::size(TrUnixSocketPrefix) }; std::size(TrUnixSocketPrefix) };
#endif #endif

View File

@ -105,12 +105,12 @@ void tr_idle_function_done(struct tr_rpc_idle_data* data, std::string_view resul
{ {
tr_torrent* tor = nullptr; tr_torrent* tor = nullptr;
if (auto const* val = var.get_if<int64_t>(); val != nullptr) if (auto const val = var.value_if<int64_t>())
{ {
tor = torrents.get(*val); tor = torrents.get(*val);
} }
if (auto const* val = var.get_if<std::string_view>(); val != nullptr) if (auto const val = var.value_if<std::string_view>())
{ {
if (*val == "recently-active"sv) if (*val == "recently-active"sv)
{ {
@ -741,7 +741,7 @@ char const* torrentGet(tr_session* session, tr_variant::Map const& args_in, tr_v
keys.reserve(n_fields); keys.reserve(n_fields);
for (auto const& field : *fields_vec) for (auto const& field : *fields_vec)
{ {
if (auto const* field_sv = field.get_if<std::string_view>(); field_sv != nullptr) if (auto const field_sv = field.value_if<std::string_view>())
{ {
if (auto const key = tr_quark_lookup(*field_sv); key && isSupportedTorrentGetField(*key)) if (auto const key = tr_quark_lookup(*field_sv); key && isSupportedTorrentGetField(*key))
{ {
@ -788,7 +788,7 @@ char const* torrentGet(tr_session* session, tr_variant::Map const& args_in, tr_v
labels.reserve(n_labels); labels.reserve(n_labels);
for (auto const& label_var : labels_vec) for (auto const& label_var : labels_vec)
{ {
if (auto const* value = label_var.get_if<std::string_view>(); value != nullptr) if (auto const value = label_var.value_if<std::string_view>())
{ {
auto const label = tr_strv_strip(*value); auto const label = tr_strv_strip(*value);
@ -840,7 +840,7 @@ char const* set_labels(tr_torrent* tor, tr_variant::Vector const& list)
{ {
for (auto const& file_var : files_vec) for (auto const& file_var : files_vec)
{ {
if (auto const* val = file_var.get_if<int64_t>(); val != nullptr) if (auto const val = file_var.value_if<int64_t>())
{ {
if (auto const idx = static_cast<tr_file_index_t>(*val); idx < n_files) if (auto const idx = static_cast<tr_file_index_t>(*val); idx < n_files)
{ {
@ -887,7 +887,7 @@ char const* add_tracker_urls(tr_torrent* tor, tr_variant::Vector const& urls_vec
for (auto const& url_var : urls_vec) for (auto const& url_var : urls_vec)
{ {
if (auto const* val = url_var.get_if<std::string_view>(); val != nullptr) if (auto const val = url_var.value_if<std::string_view>())
{ {
ann.add(*val); ann.add(*val);
} }
@ -909,10 +909,10 @@ char const* replace_trackers(tr_torrent* tor, tr_variant::Vector const& urls_vec
for (size_t i = 0, vec_size = std::size(urls_vec); i + 1 < vec_size; i += 2U) for (size_t i = 0, vec_size = std::size(urls_vec); i + 1 < vec_size; i += 2U)
{ {
auto const* id = urls_vec[i].get_if<int64_t>(); auto const id = urls_vec[i].value_if<int64_t>();
auto const* url = urls_vec[i + 1U].get_if<std::string_view>(); auto const url = urls_vec[i + 1U].value_if<std::string_view>();
if (id != nullptr && url != nullptr) if (id && url)
{ {
ann.replace(static_cast<tr_tracker_id_t>(*id), *url); ann.replace(static_cast<tr_tracker_id_t>(*id), *url);
} }
@ -934,7 +934,7 @@ char const* remove_trackers(tr_torrent* tor, tr_variant::Vector const& ids_vec)
for (auto const& id_var : ids_vec) for (auto const& id_var : ids_vec)
{ {
if (auto const* val = id_var.get_if<int64_t>(); val != nullptr) if (auto const val = id_var.value_if<int64_t>())
{ {
ann.remove(static_cast<tr_tracker_id_t>(*val)); ann.remove(static_cast<tr_tracker_id_t>(*val));
} }
@ -955,7 +955,7 @@ char const* torrentSet(tr_session* session, tr_variant::Map const& args_in, tr_v
for (auto* tor : getTorrents(session, args_in)) for (auto* tor : getTorrents(session, args_in))
{ {
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_bandwidthPriority); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_bandwidthPriority))
{ {
if (auto const priority = static_cast<tr_priority_t>(*val); tr_isPriority(priority)) if (auto const priority = static_cast<tr_priority_t>(*val); tr_isPriority(priority))
{ {
@ -963,7 +963,7 @@ char const* torrentSet(tr_session* session, tr_variant::Map const& args_in, tr_v
} }
} }
if (auto const* val = args_in.find_if<std::string_view>(TR_KEY_group); val != nullptr) if (auto const val = args_in.value_if<std::string_view>(TR_KEY_group))
{ {
tor->set_bandwidth_group(*val); tor->set_bandwidth_group(*val);
} }
@ -983,7 +983,7 @@ char const* torrentSet(tr_session* session, tr_variant::Map const& args_in, tr_v
errmsg = set_file_dls(tor, true, *val); errmsg = set_file_dls(tor, true, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_peer_limit); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_peer_limit))
{ {
tr_torrentSetPeerLimit(tor, *val); tr_torrentSetPeerLimit(tor, *val);
} }
@ -1003,77 +1003,77 @@ char const* torrentSet(tr_session* session, tr_variant::Map const& args_in, tr_v
errmsg = set_file_priorities(tor, TR_PRI_NORMAL, *val); errmsg = set_file_priorities(tor, TR_PRI_NORMAL, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_downloadLimit); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_downloadLimit))
{ {
tr_torrentSetSpeedLimit_KBps(tor, TR_DOWN, *val); tr_torrentSetSpeedLimit_KBps(tor, TR_DOWN, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_sequentialDownload); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_sequentialDownload))
{ {
tor->set_sequential_download(*val); tor->set_sequential_download(*val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_downloadLimited); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_downloadLimited))
{ {
tor->use_speed_limit(TR_DOWN, *val); tor->use_speed_limit(TR_DOWN, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_honorsSessionLimits); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_honorsSessionLimits))
{ {
tr_torrentUseSessionLimits(tor, *val); tr_torrentUseSessionLimits(tor, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_uploadLimit); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_uploadLimit))
{ {
tr_torrentSetSpeedLimit_KBps(tor, TR_UP, *val); tr_torrentSetSpeedLimit_KBps(tor, TR_UP, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_uploadLimited); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_uploadLimited))
{ {
tor->use_speed_limit(TR_UP, *val); tor->use_speed_limit(TR_UP, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_seedIdleLimit); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_seedIdleLimit))
{ {
tor->set_idle_limit_minutes(static_cast<uint16_t>(*val)); tor->set_idle_limit_minutes(static_cast<uint16_t>(*val));
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_seedIdleMode); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_seedIdleMode))
{ {
tor->set_idle_limit_mode(static_cast<tr_idlelimit>(*val)); tor->set_idle_limit_mode(static_cast<tr_idlelimit>(*val));
} }
if (auto const* val = args_in.find_if<double>(TR_KEY_seedRatioLimit); val != nullptr) if (auto const val = args_in.value_if<double>(TR_KEY_seedRatioLimit))
{ {
tor->set_seed_ratio(*val); tor->set_seed_ratio(*val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_seedRatioMode); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_seedRatioMode))
{ {
tor->set_seed_ratio_mode(static_cast<tr_ratiolimit>(*val)); tor->set_seed_ratio_mode(static_cast<tr_ratiolimit>(*val));
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_queuePosition); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_queuePosition))
{ {
tr_torrentSetQueuePosition(tor, static_cast<size_t>(*val)); tr_torrentSetQueuePosition(tor, static_cast<size_t>(*val));
} }
if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_trackerAdd); val != nullptr) if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_trackerAdd))
{ {
errmsg = add_tracker_urls(tor, *val); errmsg = add_tracker_urls(tor, *val);
} }
if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_trackerRemove); val != nullptr) if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_trackerRemove))
{ {
errmsg = remove_trackers(tor, *val); errmsg = remove_trackers(tor, *val);
} }
if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_trackerReplace); val != nullptr) if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_trackerReplace))
{ {
errmsg = replace_trackers(tor, *val); errmsg = replace_trackers(tor, *val);
} }
if (auto const* val = args_in.find_if<std::string_view>(TR_KEY_trackerList); val != nullptr) if (auto const val = args_in.value_if<std::string_view>(TR_KEY_trackerList))
{ {
if (!tor->set_announce_list(*val)) if (!tor->set_announce_list(*val))
{ {
@ -1089,8 +1089,8 @@ char const* torrentSet(tr_session* session, tr_variant::Map const& args_in, tr_v
char const* torrentSetLocation(tr_session* session, tr_variant::Map const& args_in, tr_variant::Map& /*args_out*/) char const* torrentSetLocation(tr_session* session, tr_variant::Map const& args_in, tr_variant::Map& /*args_out*/)
{ {
auto const* const location = args_in.find_if<std::string_view>(TR_KEY_location); auto const location = args_in.value_if<std::string_view>(TR_KEY_location);
if (location == nullptr) if (!location)
{ {
return "no location"; return "no location";
} }
@ -1174,7 +1174,7 @@ char const* portTest(tr_session* session, tr_variant::Map const& args_in, struct
auto options = tr_web::FetchOptions{ url, onPortTested, idle_data }; auto options = tr_web::FetchOptions{ url, onPortTested, idle_data };
options.timeout_secs = TimeoutSecs; options.timeout_secs = TimeoutSecs;
if (auto const* val = args_in.find_if<std::string_view>(TR_KEY_ipProtocol); val != nullptr) if (auto const val = args_in.value_if<std::string_view>(TR_KEY_ipProtocol))
{ {
if (*val == "ipv4"sv) if (*val == "ipv4"sv)
{ {
@ -1359,7 +1359,7 @@ bool isCurlURL(std::string_view url)
files.reserve(n_files); files.reserve(n_files);
for (auto const& idx_var : idx_vec) for (auto const& idx_var : idx_vec)
{ {
if (auto const* val = idx_var.get_if<int64_t>(); val != nullptr) if (auto const val = idx_var.value_if<int64_t>())
{ {
files.emplace_back(static_cast<tr_file_index_t>(*val)); files.emplace_back(static_cast<tr_file_index_t>(*val));
} }
@ -1395,52 +1395,52 @@ char const* torrentAdd(tr_session* session, tr_variant::Map const& args_in, tr_r
ctor.set_download_dir(TR_FORCE, *download_dir); ctor.set_download_dir(TR_FORCE, *download_dir);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_paused); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_paused))
{ {
ctor.set_paused(TR_FORCE, *val); ctor.set_paused(TR_FORCE, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_peer_limit); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_peer_limit))
{ {
ctor.set_peer_limit(TR_FORCE, static_cast<uint16_t>(*val)); ctor.set_peer_limit(TR_FORCE, static_cast<uint16_t>(*val));
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_bandwidthPriority); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_bandwidthPriority))
{ {
ctor.set_bandwidth_priority(static_cast<tr_priority_t>(*val)); ctor.set_bandwidth_priority(static_cast<tr_priority_t>(*val));
} }
if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_files_unwanted); val != nullptr) if (auto const val = args_in.find_if<tr_variant::Vector>(TR_KEY_files_unwanted))
{ {
auto const files = file_list_from_list(*val); auto const files = file_list_from_list(*val);
ctor.set_files_wanted(std::data(files), std::size(files), false); ctor.set_files_wanted(std::data(files), std::size(files), false);
} }
if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_files_wanted); val != nullptr) if (auto const val = args_in.find_if<tr_variant::Vector>(TR_KEY_files_wanted))
{ {
auto const files = file_list_from_list(*val); auto const files = file_list_from_list(*val);
ctor.set_files_wanted(std::data(files), std::size(files), true); ctor.set_files_wanted(std::data(files), std::size(files), true);
} }
if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_priority_low); val != nullptr) if (auto const val = args_in.find_if<tr_variant::Vector>(TR_KEY_priority_low))
{ {
auto const files = file_list_from_list(*val); auto const files = file_list_from_list(*val);
ctor.set_file_priorities(std::data(files), std::size(files), TR_PRI_LOW); ctor.set_file_priorities(std::data(files), std::size(files), TR_PRI_LOW);
} }
if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_priority_normal); val != nullptr) if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_priority_normal))
{ {
auto const files = file_list_from_list(*val); auto const files = file_list_from_list(*val);
ctor.set_file_priorities(std::data(files), std::size(files), TR_PRI_NORMAL); ctor.set_file_priorities(std::data(files), std::size(files), TR_PRI_NORMAL);
} }
if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_priority_high); val != nullptr) if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_priority_high))
{ {
auto const files = file_list_from_list(*val); auto const files = file_list_from_list(*val);
ctor.set_file_priorities(std::data(files), std::size(files), TR_PRI_HIGH); ctor.set_file_priorities(std::data(files), std::size(files), TR_PRI_HIGH);
} }
if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_labels); val != nullptr) if (auto const* val = args_in.find_if<tr_variant::Vector>(TR_KEY_labels))
{ {
auto [labels, errmsg] = make_labels(*val); auto [labels, errmsg] = make_labels(*val);
@ -1493,7 +1493,7 @@ char const* torrentAdd(tr_session* session, tr_variant::Map const& args_in, tr_r
void add_strings_from_var(std::set<std::string_view>& strings, tr_variant const& var) void add_strings_from_var(std::set<std::string_view>& strings, tr_variant const& var)
{ {
if (auto const* val = var.get_if<std::string_view>(); val != nullptr) if (auto const val = var.value_if<std::string_view>())
{ {
strings.insert(*val); strings.insert(*val);
return; return;
@ -1548,29 +1548,29 @@ char const* groupSet(tr_session* session, tr_variant::Map const& args_in, tr_var
auto& group = session->getBandwidthGroup(name); auto& group = session->getBandwidthGroup(name);
auto limits = group.get_limits(); auto limits = group.get_limits();
if (auto const* const val = args_in.find_if<bool>(TR_KEY_speed_limit_down_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_speed_limit_down_enabled))
{ {
limits.down_limited = *val; limits.down_limited = *val;
} }
if (auto const* const val = args_in.find_if<bool>(TR_KEY_speed_limit_up_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_speed_limit_up_enabled))
{ {
limits.up_limited = *val; limits.up_limited = *val;
} }
if (auto const* const val = args_in.find_if<int64_t>(TR_KEY_speed_limit_down); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_speed_limit_down))
{ {
limits.down_limit = Speed{ *val, Speed::Units::KByps }; limits.down_limit = Speed{ *val, Speed::Units::KByps };
} }
if (auto const* const val = args_in.find_if<int64_t>(TR_KEY_speed_limit_up); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_speed_limit_up))
{ {
limits.up_limit = Speed{ *val, Speed::Units::KByps }; limits.up_limit = Speed{ *val, Speed::Units::KByps };
} }
group.set_limits(limits); group.set_limits(limits);
if (auto const* const val = args_in.find_if<bool>(TR_KEY_honorsSessionLimits); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_honorsSessionLimits))
{ {
group.honor_parent_limits(TR_UP, *val); group.honor_parent_limits(TR_UP, *val);
group.honor_parent_limits(TR_DOWN, *val); group.honor_parent_limits(TR_DOWN, *val);
@ -1595,52 +1595,52 @@ char const* sessionSet(tr_session* session, tr_variant::Map const& args_in, tr_v
return "incomplete torrents directory path is not absolute"; return "incomplete torrents directory path is not absolute";
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_cache_size_mb); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_cache_size_mb))
{ {
tr_sessionSetCacheLimit_MB(session, *val); tr_sessionSetCacheLimit_MB(session, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_alt_speed_up); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_alt_speed_up))
{ {
tr_sessionSetAltSpeed_KBps(session, TR_UP, *val); tr_sessionSetAltSpeed_KBps(session, TR_UP, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_alt_speed_down); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_alt_speed_down))
{ {
tr_sessionSetAltSpeed_KBps(session, TR_DOWN, *val); tr_sessionSetAltSpeed_KBps(session, TR_DOWN, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_alt_speed_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_alt_speed_enabled))
{ {
tr_sessionUseAltSpeed(session, *val); tr_sessionUseAltSpeed(session, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_alt_speed_time_begin); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_alt_speed_time_begin))
{ {
tr_sessionSetAltSpeedBegin(session, static_cast<size_t>(*val)); tr_sessionSetAltSpeedBegin(session, static_cast<size_t>(*val));
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_alt_speed_time_end); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_alt_speed_time_end))
{ {
tr_sessionSetAltSpeedEnd(session, static_cast<size_t>(*val)); tr_sessionSetAltSpeedEnd(session, static_cast<size_t>(*val));
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_alt_speed_time_day); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_alt_speed_time_day))
{ {
tr_sessionSetAltSpeedDay(session, static_cast<tr_sched_day>(*val)); tr_sessionSetAltSpeedDay(session, static_cast<tr_sched_day>(*val));
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_alt_speed_time_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_alt_speed_time_enabled))
{ {
tr_sessionUseAltSpeedTime(session, *val); tr_sessionUseAltSpeedTime(session, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_blocklist_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_blocklist_enabled))
{ {
session->set_blocklist_enabled(*val); session->set_blocklist_enabled(*val);
} }
if (auto const* val = args_in.find_if<std::string_view>(TR_KEY_blocklist_url); val != nullptr) if (auto const val = args_in.value_if<std::string_view>(TR_KEY_blocklist_url))
{ {
session->setBlocklistUrl(*val); session->setBlocklistUrl(*val);
} }
@ -1650,27 +1650,27 @@ char const* sessionSet(tr_session* session, tr_variant::Map const& args_in, tr_v
session->setDownloadDir(*download_dir); session->setDownloadDir(*download_dir);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_queue_stalled_minutes); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_queue_stalled_minutes))
{ {
tr_sessionSetQueueStalledMinutes(session, static_cast<int>(*val)); tr_sessionSetQueueStalledMinutes(session, static_cast<int>(*val));
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_queue_stalled_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_queue_stalled_enabled))
{ {
tr_sessionSetQueueStalledEnabled(session, *val); tr_sessionSetQueueStalledEnabled(session, *val);
} }
if (auto const* val = args_in.find_if<std::string_view>(TR_KEY_default_trackers); val != nullptr) if (auto const val = args_in.value_if<std::string_view>(TR_KEY_default_trackers))
{ {
session->setDefaultTrackers(*val); session->setDefaultTrackers(*val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_download_queue_size); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_download_queue_size))
{ {
tr_sessionSetQueueSize(session, TR_DOWN, *val); tr_sessionSetQueueSize(session, TR_DOWN, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_download_queue_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_download_queue_enabled))
{ {
tr_sessionSetQueueEnabled(session, TR_DOWN, *val); tr_sessionSetQueueEnabled(session, TR_DOWN, *val);
} }
@ -1680,135 +1680,135 @@ char const* sessionSet(tr_session* session, tr_variant::Map const& args_in, tr_v
session->setIncompleteDir(*incomplete_dir); session->setIncompleteDir(*incomplete_dir);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_incomplete_dir_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_incomplete_dir_enabled))
{ {
session->useIncompleteDir(*val); session->useIncompleteDir(*val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_peer_limit_global); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_peer_limit_global))
{ {
tr_sessionSetPeerLimit(session, *val); tr_sessionSetPeerLimit(session, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_peer_limit_per_torrent); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_peer_limit_per_torrent))
{ {
tr_sessionSetPeerLimitPerTorrent(session, *val); tr_sessionSetPeerLimitPerTorrent(session, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_pex_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_pex_enabled))
{ {
tr_sessionSetPexEnabled(session, *val); tr_sessionSetPexEnabled(session, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_dht_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_dht_enabled))
{ {
tr_sessionSetDHTEnabled(session, *val); tr_sessionSetDHTEnabled(session, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_utp_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_utp_enabled))
{ {
tr_sessionSetUTPEnabled(session, *val); tr_sessionSetUTPEnabled(session, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_lpd_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_lpd_enabled))
{ {
tr_sessionSetLPDEnabled(session, *val); tr_sessionSetLPDEnabled(session, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_peer_port_random_on_start); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_peer_port_random_on_start))
{ {
tr_sessionSetPeerPortRandomOnStart(session, *val); tr_sessionSetPeerPortRandomOnStart(session, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_peer_port); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_peer_port))
{ {
tr_sessionSetPeerPort(session, *val); tr_sessionSetPeerPort(session, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_port_forwarding_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_port_forwarding_enabled))
{ {
tr_sessionSetPortForwardingEnabled(session, *val); tr_sessionSetPortForwardingEnabled(session, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_rename_partial_files); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_rename_partial_files))
{ {
tr_sessionSetIncompleteFileNamingEnabled(session, *val); tr_sessionSetIncompleteFileNamingEnabled(session, *val);
} }
if (auto const* val = args_in.find_if<double>(TR_KEY_seedRatioLimit); val != nullptr) if (auto const val = args_in.value_if<double>(TR_KEY_seedRatioLimit))
{ {
tr_sessionSetRatioLimit(session, *val); tr_sessionSetRatioLimit(session, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_seedRatioLimited); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_seedRatioLimited))
{ {
tr_sessionSetRatioLimited(session, *val); tr_sessionSetRatioLimited(session, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_idle_seeding_limit); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_idle_seeding_limit))
{ {
tr_sessionSetIdleLimit(session, *val); tr_sessionSetIdleLimit(session, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_idle_seeding_limit_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_idle_seeding_limit_enabled))
{ {
tr_sessionSetIdleLimited(session, *val); tr_sessionSetIdleLimited(session, *val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_start_added_torrents); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_start_added_torrents))
{ {
tr_sessionSetPaused(session, !*val); tr_sessionSetPaused(session, !*val);
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_seed_queue_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_seed_queue_enabled))
{ {
tr_sessionSetQueueEnabled(session, TR_UP, *val); tr_sessionSetQueueEnabled(session, TR_UP, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_seed_queue_size); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_seed_queue_size))
{ {
tr_sessionSetQueueSize(session, TR_UP, *val); tr_sessionSetQueueSize(session, TR_UP, *val);
} }
for (auto const& [enabled_key, script_key, script] : tr_session::Scripts) for (auto const& [enabled_key, script_key, script] : tr_session::Scripts)
{ {
if (auto const* val = args_in.find_if<bool>(enabled_key); val != nullptr) if (auto const val = args_in.value_if<bool>(enabled_key))
{ {
session->useScript(script, *val); session->useScript(script, *val);
} }
if (auto const* val = args_in.find_if<std::string_view>(script_key); val != nullptr) if (auto const val = args_in.value_if<std::string_view>(script_key))
{ {
session->setScript(script, *val); session->setScript(script, *val);
} }
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_trash_original_torrent_files); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_trash_original_torrent_files))
{ {
tr_sessionSetDeleteSource(session, *val); tr_sessionSetDeleteSource(session, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_speed_limit_down); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_speed_limit_down))
{ {
session->set_speed_limit(TR_DOWN, Speed{ *val, Speed::Units::KByps }); session->set_speed_limit(TR_DOWN, Speed{ *val, Speed::Units::KByps });
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_speed_limit_down_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_speed_limit_down_enabled))
{ {
tr_sessionLimitSpeed(session, TR_DOWN, *val); tr_sessionLimitSpeed(session, TR_DOWN, *val);
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_speed_limit_up); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_speed_limit_up))
{ {
session->set_speed_limit(TR_UP, Speed{ *val, Speed::Units::KByps }); session->set_speed_limit(TR_UP, Speed{ *val, Speed::Units::KByps });
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_speed_limit_up_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_speed_limit_up_enabled))
{ {
tr_sessionLimitSpeed(session, TR_UP, *val); tr_sessionLimitSpeed(session, TR_UP, *val);
} }
if (auto const* val = args_in.find_if<std::string_view>(TR_KEY_encryption); val != nullptr) if (auto const val = args_in.value_if<std::string_view>(TR_KEY_encryption))
{ {
if (*val == "required"sv) if (*val == "required"sv)
{ {
@ -1824,12 +1824,12 @@ char const* sessionSet(tr_session* session, tr_variant::Map const& args_in, tr_v
} }
} }
if (auto const* val = args_in.find_if<int64_t>(TR_KEY_anti_brute_force_threshold); val != nullptr) if (auto const val = args_in.value_if<int64_t>(TR_KEY_anti_brute_force_threshold))
{ {
tr_sessionSetAntiBruteForceThreshold(session, static_cast<int>(*val)); tr_sessionSetAntiBruteForceThreshold(session, static_cast<int>(*val));
} }
if (auto const* val = args_in.find_if<bool>(TR_KEY_anti_brute_force_enabled); val != nullptr) if (auto const val = args_in.value_if<bool>(TR_KEY_anti_brute_force_enabled))
{ {
tr_sessionSetAntiBruteForceEnabled(session, *val); tr_sessionSetAntiBruteForceEnabled(session, *val);
} }
@ -1994,7 +1994,7 @@ namespace session_get_helpers
{ {
for (auto const& field_var : *fields_vec) for (auto const& field_var : *fields_vec)
{ {
if (auto const* field_name = field_var.get_if<std::string_view>(); field_name != nullptr) if (auto const field_name = field_var.value_if<std::string_view>())
{ {
if (auto const field_id = tr_quark_lookup(*field_name); field_id) if (auto const field_id = tr_quark_lookup(*field_name); field_id)
{ {
@ -2122,22 +2122,23 @@ void tr_rpc_request_exec(tr_session* session, tr_variant const& request, tr_rpc_
auto const empty_args = tr_variant::Map{}; auto const empty_args = tr_variant::Map{};
auto const* args_in = &empty_args; auto const* args_in = &empty_args;
auto method_name = std::string_view{}; auto method_name = std::string_view{};
auto tag = std::optional<int64_t>{};
if (request_map != nullptr) if (request_map != nullptr)
{ {
// find the args // find the args
if (auto const* val = request_map->find_if<tr_variant::Map>(TR_KEY_arguments); val != nullptr) if (auto const* val = request_map->find_if<tr_variant::Map>(TR_KEY_arguments))
{ {
args_in = val; args_in = val;
} }
// find the requested method // find the requested method
if (auto const* val = request_map->find_if<std::string_view>(TR_KEY_method); val != nullptr) if (auto const val = request_map->value_if<std::string_view>(TR_KEY_method))
{ {
method_name = *val; method_name = *val;
} }
}
auto const tag = request_map->value_if<int64_t>(TR_KEY_tag); tag = request_map->value_if<int64_t>(TR_KEY_tag);
}
auto const test = [method_name](auto const& handler) auto const test = [method_name](auto const& handler)
{ {

View File

@ -101,29 +101,29 @@ void bandwidthGroupRead(tr_session* session, std::string_view config_dir)
auto& group = session->getBandwidthGroup(tr_interned_string{ key }); auto& group = session->getBandwidthGroup(tr_interned_string{ key });
auto limits = tr_bandwidth_limits{}; auto limits = tr_bandwidth_limits{};
if (auto const* val = group_map->find_if<bool>(TR_KEY_uploadLimited); val != nullptr) if (auto const val = group_map->value_if<bool>(TR_KEY_uploadLimited))
{ {
limits.up_limited = *val; limits.up_limited = *val;
} }
if (auto const* val = group_map->find_if<bool>(TR_KEY_downloadLimited); val != nullptr) if (auto const val = group_map->value_if<bool>(TR_KEY_downloadLimited))
{ {
limits.down_limited = *val; limits.down_limited = *val;
} }
if (auto const* val = group_map->find_if<int64_t>(TR_KEY_uploadLimit); val != nullptr) if (auto const val = group_map->value_if<int64_t>(TR_KEY_uploadLimit))
{ {
limits.up_limit = Speed{ *val, Speed::Units::KByps }; limits.up_limit = Speed{ *val, Speed::Units::KByps };
} }
if (auto const* val = group_map->find_if<int64_t>(TR_KEY_downloadLimit); val != nullptr) if (auto const val = group_map->value_if<int64_t>(TR_KEY_downloadLimit))
{ {
limits.down_limit = Speed{ *val, Speed::Units::KByps }; limits.down_limit = Speed{ *val, Speed::Units::KByps };
} }
group.set_limits(limits); group.set_limits(limits);
if (auto const* val = group_map->find_if<bool>(TR_KEY_honorsSessionLimits); val != nullptr) if (auto const val = group_map->value_if<bool>(TR_KEY_honorsSessionLimits))
{ {
group.honor_parent_limits(TR_UP, *val); group.honor_parent_limits(TR_UP, *val);
group.honor_parent_limits(TR_DOWN, *val); group.honor_parent_limits(TR_DOWN, *val);
@ -565,7 +565,7 @@ tr_session* tr_sessionInit(char const* config_dir, bool message_queueing_enabled
// if logging is desired, start it now before doing more work // if logging is desired, start it now before doing more work
if (auto const* settings_map = settings.get_if<tr_variant::Map>(); settings_map != nullptr) if (auto const* settings_map = settings.get_if<tr_variant::Map>(); settings_map != nullptr)
{ {
if (auto const* val = settings_map->find_if<bool>(TR_KEY_message_level); val != nullptr) if (auto const val = settings_map->value_if<bool>(TR_KEY_message_level))
{ {
tr_logSetLevel(static_cast<tr_log_level>(*val)); tr_logSetLevel(static_cast<tr_log_level>(*val));
} }
@ -736,7 +736,7 @@ void tr_session::initImpl(init_data& data)
setSettings(settings, true); setSettings(settings, true);
tr_utpInit(this); tr_utp_init(this);
/* cleanup */ /* cleanup */
data.done_cv.notify_one(); data.done_cv.notify_one();
@ -1406,7 +1406,7 @@ void tr_session::closeImplPart2(std::promise<void>* closed_promise, std::chrono:
stats().save(); stats().save();
peer_mgr_.reset(); peer_mgr_.reset();
openFiles().close_all(); openFiles().close_all();
tr_utpClose(this); tr_utp_close(this);
this->udp_core_.reset(); this->udp_core_.reset();
// tada we are done! // tada we are done!

View File

@ -203,6 +203,11 @@ private:
return session_.bind_address(TR_AF_INET); return session_.bind_address(TR_AF_INET);
} }
[[nodiscard]] tr_port advertised_peer_port() const override
{
return session_.advertisedPeerPort();
}
[[nodiscard]] tr_port local_peer_port() const override [[nodiscard]] tr_port local_peer_port() const override
{ {
return session_.localPeerPort(); return session_.localPeerPort();

View File

@ -38,7 +38,7 @@ using Lookup = std::array<std::pair<std::string_view, T>, N>;
bool load_bool(tr_variant const& src, bool* tgt) bool load_bool(tr_variant const& src, bool* tgt)
{ {
if (auto val = src.get_if<bool>(); val != nullptr) if (auto val = src.value_if<bool>())
{ {
*tgt = *val; *tgt = *val;
return true; return true;
@ -56,7 +56,7 @@ tr_variant save_bool(bool const& val)
bool load_double(tr_variant const& src, double* tgt) bool load_double(tr_variant const& src, double* tgt)
{ {
if (auto val = src.get_if<double>(); val != nullptr) if (auto val = src.value_if<double>())
{ {
*tgt = *val; *tgt = *val;
return true; return true;
@ -82,7 +82,7 @@ bool load_encryption_mode(tr_variant const& src, tr_encryption_mode* tgt)
{ {
static constexpr auto& Keys = EncryptionKeys; static constexpr auto& Keys = EncryptionKeys;
if (auto const* val = src.get_if<std::string_view>(); val != nullptr) if (auto const val = src.value_if<std::string_view>())
{ {
auto const needle = tr_strlower(tr_strv_strip(*val)); auto const needle = tr_strlower(tr_strv_strip(*val));
@ -96,7 +96,7 @@ bool load_encryption_mode(tr_variant const& src, tr_encryption_mode* tgt)
} }
} }
if (auto const* val = src.get_if<int64_t>(); val != nullptr) if (auto const val = src.value_if<int64_t>())
{ {
for (auto const& [key, encryption] : Keys) for (auto const& [key, encryption] : Keys)
{ {
@ -132,7 +132,7 @@ bool load_log_level(tr_variant const& src, tr_log_level* tgt)
{ {
static constexpr auto& Keys = LogKeys; static constexpr auto& Keys = LogKeys;
if (auto const* val = src.get_if<std::string_view>(); val != nullptr) if (auto const val = src.value_if<std::string_view>())
{ {
auto const needle = tr_strlower(tr_strv_strip(*val)); auto const needle = tr_strlower(tr_strv_strip(*val));
@ -146,7 +146,7 @@ bool load_log_level(tr_variant const& src, tr_log_level* tgt)
} }
} }
if (auto const* val = src.get_if<int64_t>(); val != nullptr) if (auto const val = src.value_if<int64_t>())
{ {
for (auto const& [name, log_level] : Keys) for (auto const& [name, log_level] : Keys)
{ {
@ -170,7 +170,7 @@ tr_variant save_log_level(tr_log_level const& val)
bool load_mode_t(tr_variant const& src, tr_mode_t* tgt) bool load_mode_t(tr_variant const& src, tr_mode_t* tgt)
{ {
if (auto const* val = src.get_if<std::string_view>(); val != nullptr) if (auto const val = src.value_if<std::string_view>())
{ {
if (auto const mode = tr_num_parse<uint32_t>(*val, nullptr, 8); mode) if (auto const mode = tr_num_parse<uint32_t>(*val, nullptr, 8); mode)
{ {
@ -179,7 +179,7 @@ bool load_mode_t(tr_variant const& src, tr_mode_t* tgt)
} }
} }
if (auto const* val = src.get_if<int64_t>(); val != nullptr) if (auto const val = src.value_if<int64_t>())
{ {
*tgt = static_cast<tr_mode_t>(*val); *tgt = static_cast<tr_mode_t>(*val);
return true; return true;
@ -197,7 +197,7 @@ tr_variant save_mode_t(tr_mode_t const& val)
bool load_msec(tr_variant const& src, std::chrono::milliseconds* tgt) bool load_msec(tr_variant const& src, std::chrono::milliseconds* tgt)
{ {
if (auto val = src.get_if<int64_t>(); val != nullptr) if (auto val = src.value_if<int64_t>())
{ {
*tgt = std::chrono::milliseconds(*val); *tgt = std::chrono::milliseconds(*val);
return true; return true;
@ -215,7 +215,7 @@ tr_variant save_msec(std::chrono::milliseconds const& src)
bool load_port(tr_variant const& src, tr_port* tgt) bool load_port(tr_variant const& src, tr_port* tgt)
{ {
if (auto const* val = src.get_if<int64_t>(); val != nullptr) if (auto const val = src.value_if<int64_t>())
{ {
*tgt = tr_port::from_host(*val); *tgt = tr_port::from_host(*val);
return true; return true;
@ -243,7 +243,7 @@ bool load_preallocation_mode(tr_variant const& src, tr_open_files::Preallocation
{ {
static constexpr auto& Keys = PreallocationKeys; static constexpr auto& Keys = PreallocationKeys;
if (auto const* val = src.get_if<std::string_view>(); val != nullptr) if (auto const val = src.value_if<std::string_view>())
{ {
auto const needle = tr_strlower(tr_strv_strip(*val)); auto const needle = tr_strlower(tr_strv_strip(*val));
@ -257,7 +257,7 @@ bool load_preallocation_mode(tr_variant const& src, tr_open_files::Preallocation
} }
} }
if (auto const* val = src.get_if<int64_t>(); val != nullptr) if (auto const val = src.value_if<int64_t>())
{ {
for (auto const& [name, value] : Keys) for (auto const& [name, value] : Keys)
{ {
@ -288,7 +288,7 @@ bool load_preferred_transport(tr_variant const& src, tr_preferred_transport* tgt
{ {
static constexpr auto& Keys = PreferredTransportKeys; static constexpr auto& Keys = PreferredTransportKeys;
if (auto const* val = src.get_if<std::string_view>(); val != nullptr) if (auto const val = src.value_if<std::string_view>())
{ {
auto const needle = tr_strlower(tr_strv_strip(*val)); auto const needle = tr_strlower(tr_strv_strip(*val));
@ -302,7 +302,7 @@ bool load_preferred_transport(tr_variant const& src, tr_preferred_transport* tgt
} }
} }
if (auto const* val = src.get_if<int64_t>(); val != nullptr) if (auto const val = src.value_if<int64_t>())
{ {
for (auto const& [name, value] : Keys) for (auto const& [name, value] : Keys)
{ {
@ -334,7 +334,7 @@ tr_variant save_preferred_transport(tr_preferred_transport const& val)
bool load_size_t(tr_variant const& src, size_t* tgt) bool load_size_t(tr_variant const& src, size_t* tgt)
{ {
if (auto const* val = src.get_if<int64_t>(); val != nullptr) if (auto const val = src.value_if<int64_t>())
{ {
*tgt = static_cast<size_t>(*val); *tgt = static_cast<size_t>(*val);
return true; return true;
@ -352,7 +352,7 @@ tr_variant save_size_t(size_t const& val)
bool load_string(tr_variant const& src, std::string* tgt) bool load_string(tr_variant const& src, std::string* tgt)
{ {
if (auto const* val = src.get_if<std::string_view>(); val != nullptr) if (auto const val = src.value_if<std::string_view>())
{ {
*tgt = std::string{ *val }; *tgt = std::string{ *val };
return true; return true;
@ -370,7 +370,7 @@ tr_variant save_string(std::string const& val)
bool load_tos_t(tr_variant const& src, tr_tos_t* tgt) bool load_tos_t(tr_variant const& src, tr_tos_t* tgt)
{ {
if (auto const* val = src.get_if<std::string_view>(); val != nullptr) if (auto const val = src.value_if<std::string_view>())
{ {
if (auto const tos = tr_tos_t::from_string(*val); tos) if (auto const tos = tr_tos_t::from_string(*val); tos)
{ {
@ -381,7 +381,7 @@ bool load_tos_t(tr_variant const& src, tr_tos_t* tgt)
return false; return false;
} }
if (auto const* val = src.get_if<int64_t>(); val != nullptr) if (auto const val = src.value_if<int64_t>())
{ {
*tgt = tr_tos_t{ static_cast<int>(*val) }; *tgt = tr_tos_t{ static_cast<int>(*val) };
return true; return true;
@ -406,7 +406,7 @@ bool load_verify_added_mode(tr_variant const& src, tr_verify_added_mode* tgt)
{ {
static constexpr auto& Keys = VerifyModeKeys; static constexpr auto& Keys = VerifyModeKeys;
if (auto const* val = src.get_if<std::string_view>(); val != nullptr) if (auto const val = src.value_if<std::string_view>())
{ {
auto const needle = tr_strlower(tr_strv_strip(*val)); auto const needle = tr_strlower(tr_strv_strip(*val));
@ -420,7 +420,7 @@ bool load_verify_added_mode(tr_variant const& src, tr_verify_added_mode* tgt)
} }
} }
if (auto const* val = src.get_if<int64_t>(); val != nullptr) if (auto const val = src.value_if<int64_t>())
{ {
for (auto const& [name, value] : Keys) for (auto const& [name, value] : Keys)
{ {

View File

@ -36,14 +36,16 @@ protected:
using Save = tr_variant (*)(T const& src); using Save = tr_variant (*)(T const& src);
template<typename T> template<typename T>
void add_type_handler(Load<T> load, Save<T> save) void add_type_handler(Load<T> load_handler, Save<T> save_handler)
{ {
auto const key = std::type_index(typeid(T*)); auto const key = std::type_index(typeid(T*));
// wrap load + save with void* wrappers so that // wrap load_handler + save_handler with void* wrappers so that
// they can be stored in the save_ and load_ maps // they can be stored in the save_ and load_ maps
load_.insert_or_assign(key, [load](tr_variant const& src, void* tgt) { return load(src, static_cast<T*>(tgt)); }); load_.insert_or_assign(
save_.insert_or_assign(key, [save](void const* src) { return save(*static_cast<T const*>(src)); }); key,
[load_handler](tr_variant const& src, void* tgt) { return load_handler(src, static_cast<T*>(tgt)); });
save_.insert_or_assign(key, [save_handler](void const* src) { return save_handler(*static_cast<T const*>(src)); });
} }
struct Field struct Field

View File

@ -498,15 +498,18 @@ void tr_torrent::set_unique_queue_position(size_t const new_pos)
{ {
using namespace queue_helpers; using namespace queue_helpers;
auto current = size_t{}; auto max_pos = size_t{};
auto const old_pos = queue_position_; auto const old_pos = queue_position_;
queue_position_ = MaxQueuePosition;
auto& torrents = session->torrents(); auto& torrents = session->torrents();
for (auto* const walk : torrents) for (auto* const walk : torrents)
{ {
if ((old_pos < new_pos) && (old_pos <= walk->queue_position_) && (walk->queue_position_ <= new_pos)) if (walk == this)
{
continue;
}
if ((old_pos < new_pos) && (old_pos < walk->queue_position_) && (walk->queue_position_ <= new_pos))
{ {
--walk->queue_position_; --walk->queue_position_;
walk->mark_changed(); walk->mark_changed();
@ -518,10 +521,10 @@ void tr_torrent::set_unique_queue_position(size_t const new_pos)
walk->mark_changed(); walk->mark_changed();
} }
current = std::max(current, walk->queue_position_ + 1U); max_pos = std::max(max_pos, walk->queue_position_);
} }
queue_position_ = std::min(new_pos, current); queue_position_ = std::min(new_pos, max_pos + 1);
mark_changed(); mark_changed();
TR_ASSERT(torrents_are_sorted_by_queue_position(torrents.get_all())); TR_ASSERT(torrents_are_sorted_by_queue_position(torrents.get_all()));

View File

@ -63,7 +63,7 @@ class RenameTest_singleFilenameTorrent_Test;
} // namespace libtransmission::test } // namespace libtransmission::test
/** @brief Torrent object */ /** @brief Torrent object */
struct tr_torrent final : public tr_completion::torrent_view struct tr_torrent
{ {
using Speed = libtransmission::Values::Speed; using Speed = libtransmission::Values::Speed;
@ -174,7 +174,7 @@ struct tr_torrent final : public tr_completion::torrent_view
explicit tr_torrent(tr_torrent_metainfo&& tm) explicit tr_torrent(tr_torrent_metainfo&& tm)
: metainfo_{ std::move(tm) } : metainfo_{ std::move(tm) }
, completion_{ this, &this->metainfo_.block_info() } , completion_{ this, &metainfo_.block_info() }
{ {
} }
@ -389,7 +389,7 @@ struct tr_torrent final : public tr_completion::torrent_view
/// WANTED /// WANTED
[[nodiscard]] bool piece_is_wanted(tr_piece_index_t piece) const final [[nodiscard]] bool piece_is_wanted(tr_piece_index_t piece) const
{ {
return files_wanted_.piece_wanted(piece); return files_wanted_.piece_wanted(piece);
} }
@ -729,6 +729,7 @@ struct tr_torrent final : public tr_completion::torrent_view
{ {
sequential_download_ = is_sequential; sequential_download_ = is_sequential;
sequential_download_changed_.emit(this, is_sequential); sequential_download_changed_.emit(this, is_sequential);
set_dirty();
} }
} }

View File

@ -52,7 +52,6 @@ using namespace std::literals;
// the dht library needs us to implement these: // the dht library needs us to implement these:
extern "C" extern "C"
{ {
// This function should return true when a node is blacklisted. // This function should return true when a node is blacklisted.
// We don't support using a blacklist with the DHT in Transmission, // We don't support using a blacklist with the DHT in Transmission,
// since massive (ab)use of this feature could harm the DHT. However, // since massive (ab)use of this feature could harm the DHT. However,

View File

@ -27,8 +27,9 @@
namespace namespace
{ {
/* Since we use a single UDP socket in order to implement multiple
µTP sockets, try to set up huge buffers. */ // Since we use a single UDP socket in order to implement multiple
// µTP sockets, try to set up huge buffers.
void set_socket_buffers(tr_socket_t fd, bool large) void set_socket_buffers(tr_socket_t fd, bool large)
{ {
static auto constexpr RecvBufferSize = 4 * 1024 * 1024; static auto constexpr RecvBufferSize = 4 * 1024 * 1024;
@ -41,33 +42,25 @@ void set_socket_buffers(tr_socket_t fd, bool large)
socklen_t sbuf_len = sizeof(sbuf); socklen_t sbuf_len = sizeof(sbuf);
int size = large ? RecvBufferSize : SmallBufferSize; int size = large ? RecvBufferSize : SmallBufferSize;
int rc = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char const*>(&size), sizeof(size)); if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char const*>(&size), sizeof(size)) < 0)
if (rc < 0)
{ {
tr_logAddDebug(fmt::format("Couldn't set receive buffer: {}", tr_net_strerror(sockerrno))); tr_logAddDebug(fmt::format("Couldn't set receive buffer: {}", tr_net_strerror(sockerrno)));
} }
size = large ? SendBufferSize : SmallBufferSize; size = large ? SendBufferSize : SmallBufferSize;
rc = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char const*>(&size), sizeof(size)); if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char const*>(&size), sizeof(size)) < 0)
if (rc < 0)
{ {
tr_logAddDebug(fmt::format("Couldn't set send buffer: {}", tr_net_strerror(sockerrno))); tr_logAddDebug(fmt::format("Couldn't set send buffer: {}", tr_net_strerror(sockerrno)));
} }
if (large) if (large)
{ {
rc = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char*>(&rbuf), &rbuf_len); if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char*>(&rbuf), &rbuf_len) < 0)
if (rc < 0)
{ {
rbuf = 0; rbuf = 0;
} }
rc = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&sbuf), &sbuf_len); if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&sbuf), &sbuf_len) < 0)
if (rc < 0)
{ {
sbuf = 0; sbuf = 0;
} }
@ -99,41 +92,55 @@ void event_callback(evutil_socket_t s, [[maybe_unused]] short type, void* vsessi
auto from = sockaddr_storage{}; auto from = sockaddr_storage{};
auto fromlen = socklen_t{ sizeof(from) }; auto fromlen = socklen_t{ sizeof(from) };
auto* const from_sa = reinterpret_cast<sockaddr*>(&from); auto* const from_sa = reinterpret_cast<sockaddr*>(&from);
auto const rc = recvfrom(s, reinterpret_cast<char*>(std::data(buf)), std::size(buf) - 1, 0, from_sa, &fromlen);
if (rc <= 0)
{
return;
}
/* Since most packets we receive here are µTP, make quick inline
checks for the other protocols. The logic is as follows:
- all DHT packets start with 'd'
- all UDP tracker packets start with a 32-bit (!) "action", which
is between 0 and 3
- the above cannot be µTP packets, since these start with a 4-bit
version number (1). */
auto* const session = static_cast<tr_session*>(vsession); auto* const session = static_cast<tr_session*>(vsession);
if (buf[0] == 'd') auto got_utp_packet = false;
for (;;)
{ {
if (session->dht_) auto const n_read = recvfrom(s, reinterpret_cast<char*>(std::data(buf)), std::size(buf) - 1, 0, from_sa, &fromlen);
if (n_read <= 0)
{ {
buf[rc] = '\0'; // libdht requires zero-terminated messages if (got_utp_packet)
session->dht_->handle_message(std::data(buf), rc, from_sa, fromlen); {
// To reduce protocol overhead, we wait until we've read all UDP packets
// we can, then send one ACK for each µTP socket that received packet(s).
tr_utp_issue_deferred_acks(session);
}
return;
} }
}
else if (rc >= 8 && buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] <= 3) // Since most packets we receive here are µTP, make quick inline
{ // checks for the other protocols. The logic is as follows:
if (!session->announcer_udp_->handle_message(std::data(buf), rc)) // - all DHT packets start with 'd' (100)
// - all UDP tracker packets start with a 32-bit (!) "action", which
// is between 0 and 3
// - the above cannot be µTP packets, since these start with a 4-bit
// "type" between 0 and 4, followed by a 4-bit version number (1)
if (buf[0] == 'd')
{ {
tr_logAddTrace("Couldn't parse UDP tracker packet."); if (session->dht_)
{
buf[n_read] = '\0'; // libdht requires zero-terminated messages
session->dht_->handle_message(std::data(buf), n_read, from_sa, fromlen);
}
} }
} else if (n_read >= 8 && buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] <= 3)
else if (session->allowsUTP() && (session->utp_context != nullptr))
{
if (!tr_utpPacket(std::data(buf), rc, from_sa, fromlen, session))
{ {
tr_logAddTrace("Unexpected UDP packet"); if (!session->announcer_udp_->handle_message(std::data(buf), n_read))
{
tr_logAddTrace("Couldn't parse UDP tracker packet.");
}
}
else if (session->allowsUTP() && session->utp_context != nullptr)
{
if (tr_utp_packet(std::data(buf), n_read, from_sa, fromlen, session))
{
got_utp_packet = true;
}
else
{
tr_logAddTrace("Unexpected UDP packet");
}
} }
} }
} }
@ -152,13 +159,23 @@ tr_session::tr_udp_core::tr_udp_core(tr_session& session, tr_port udp_port)
if (auto sock = socket(PF_INET, SOCK_DGRAM, 0); sock != TR_BAD_SOCKET) if (auto sock = socket(PF_INET, SOCK_DGRAM, 0); sock != TR_BAD_SOCKET)
{ {
auto optval = 1; (void)evutil_make_listen_socket_reuseable(sock);
(void)setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char const*>(&optval), sizeof(optval));
auto const addr = session_.bind_address(TR_AF_INET); auto const addr = session_.bind_address(TR_AF_INET);
auto const [ss, sslen] = tr_socket_address::to_sockaddr(addr, udp_port_); auto const [ss, sslen] = tr_socket_address::to_sockaddr(addr, udp_port_);
if (bind(sock, reinterpret_cast<sockaddr const*>(&ss), sslen) != 0) if (evutil_make_socket_nonblocking(sock) != 0)
{
auto const error_code = errno;
tr_logAddWarn(fmt::format(
_("Couldn't make IPv4 socket non-blocking {address}: {error} ({error_code})"),
fmt::arg("address", tr_socket_address::display_name(addr, udp_port_)),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
tr_net_close_socket(sock);
}
else if (bind(sock, reinterpret_cast<sockaddr const*>(&ss), sslen) != 0)
{ {
auto const error_code = errno; auto const error_code = errno;
tr_logAddWarn(fmt::format( tr_logAddWarn(fmt::format(
@ -186,13 +203,24 @@ tr_session::tr_udp_core::tr_udp_core(tr_session& session, tr_port udp_port)
} }
else if (auto sock = socket(PF_INET6, SOCK_DGRAM, 0); sock != TR_BAD_SOCKET) else if (auto sock = socket(PF_INET6, SOCK_DGRAM, 0); sock != TR_BAD_SOCKET)
{ {
auto optval = 1; (void)evutil_make_listen_socket_reuseable(sock);
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char const*>(&optval), sizeof(optval)); (void)evutil_make_listen_socket_ipv6only(sock);
auto const addr = session_.bind_address(TR_AF_INET6); auto const addr = session_.bind_address(TR_AF_INET6);
auto const [ss, sslen] = tr_socket_address::to_sockaddr(addr, udp_port_); auto const [ss, sslen] = tr_socket_address::to_sockaddr(addr, udp_port_);
if (bind(sock, reinterpret_cast<sockaddr const*>(&ss), sslen) != 0) if (evutil_make_socket_nonblocking(sock) != 0)
{
auto const error_code = errno;
tr_logAddWarn(fmt::format(
_("Couldn't make IPv6 socket non-blocking {address}: {error} ({error_code})"),
fmt::arg("address", tr_socket_address::display_name(addr, udp_port_)),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code)));
tr_net_close_socket(sock);
}
else if (bind(sock, reinterpret_cast<sockaddr const*>(&ss), sslen) != 0)
{ {
auto const error_code = errno; auto const error_code = errno;
tr_logAddWarn(fmt::format( tr_logAddWarn(fmt::format(
@ -211,13 +239,6 @@ tr_session::tr_udp_core::tr_udp_core(tr_session& session, tr_port udp_port)
udp6_socket_ = sock; udp6_socket_ = sock;
udp6_event_.reset(event_new(session_.event_base(), udp6_socket_, EV_READ | EV_PERSIST, event_callback, &session_)); udp6_event_.reset(event_new(session_.event_base(), udp6_socket_, EV_READ | EV_PERSIST, event_callback, &session_));
event_add(udp6_event_.get(), nullptr); event_add(udp6_event_.get(), nullptr);
#ifdef IPV6_V6ONLY
// Since we always open an IPv4 socket on the same port,
// this shouldn't matter. But I'm superstitious.
int one = 1;
(void)setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<char const*>(&one), sizeof(one));
#endif
} }
} }
} }

View File

@ -155,11 +155,11 @@ void restart_timer(tr_session* session)
} }
else else
{ {
/* If somebody has disabled µTP, then we still want to run // If somebody has disabled µTP, then we still want to run
utp_check_timeouts, in order to let closed sockets finish // utp_check_timeouts, in order to let closed sockets finish
gracefully and so on. However, since we're not particularly // gracefully and so on. However, since we're not particularly
interested in that happening in a timely manner, we might as // interested in that happening in a timely manner, we might as
well use a large timeout. */ // well use a large timeout.
static auto constexpr MinInterval = 2s; static auto constexpr MinInterval = 2s;
static auto constexpr MaxInterval = 3s; static auto constexpr MaxInterval = 3s;
auto const target = MinInterval + random_percent * (MaxInterval - MinInterval); auto const target = MinInterval + random_percent * (MaxInterval - MinInterval);
@ -173,15 +173,12 @@ void timer_callback(void* vsession)
{ {
auto* session = static_cast<tr_session*>(vsession); auto* session = static_cast<tr_session*>(vsession);
/* utp_internal.cpp says "Should be called each time the UDP socket is drained" but it's tricky with libevent */
utp_issue_deferred_acks(session->utp_context);
utp_check_timeouts(session->utp_context); utp_check_timeouts(session->utp_context);
restart_timer(session); restart_timer(session);
} }
} // namespace } // namespace
void tr_utpInit(tr_session* session) void tr_utp_init(tr_session* session)
{ {
if (session->utp_context != nullptr) if (session->utp_context != nullptr)
{ {
@ -212,17 +209,19 @@ void tr_utpInit(tr_session* session)
restart_timer(session); restart_timer(session);
} }
bool tr_utpPacket(unsigned char const* buf, size_t buflen, struct sockaddr const* from, socklen_t fromlen, tr_session* ss) bool tr_utp_packet(unsigned char const* buf, size_t buflen, struct sockaddr const* from, socklen_t fromlen, tr_session* ss)
{ {
auto const ret = utp_process_udp(ss->utp_context, buf, buflen, from, fromlen); auto const ret = utp_process_udp(ss->utp_context, buf, buflen, from, fromlen);
/* utp_internal.cpp says "Should be called each time the UDP socket is drained" but it's tricky with libevent */
utp_issue_deferred_acks(ss->utp_context);
return ret != 0; return ret != 0;
} }
void tr_utpClose(tr_session* session) void tr_utp_issue_deferred_acks(tr_session* ss)
{
utp_issue_deferred_acks(ss->utp_context);
}
void tr_utp_close(tr_session* session)
{ {
session->utp_timer.reset(); session->utp_timer.reset();

View File

@ -18,8 +18,10 @@
struct tr_session; struct tr_session;
void tr_utpInit(tr_session* session); void tr_utp_init(tr_session* session);
bool tr_utpPacket(unsigned char const* buf, size_t buflen, struct sockaddr const* from, socklen_t fromlen, tr_session* ss); bool tr_utp_packet(unsigned char const* buf, size_t buflen, struct sockaddr const* from, socklen_t fromlen, tr_session* ss);
void tr_utpClose(tr_session*); void tr_utp_issue_deferred_acks(tr_session* ss);
void tr_utp_close(tr_session* session);

View File

@ -50,6 +50,21 @@ namespace
return tr_variant::NoneIndex; return tr_variant::NoneIndex;
} }
template<typename T>
[[nodiscard]] bool value_if(tr_variant const* const var, T* const setme)
{
if (var != nullptr)
{
if (auto val = var->value_if<T>())
{
*setme = *val;
return true;
}
}
return false;
}
template<typename T> template<typename T>
[[nodiscard]] tr_variant* dict_set(tr_variant* const var, tr_quark const key, T&& val) [[nodiscard]] tr_variant* dict_set(tr_variant* const var, tr_quark const key, T&& val)
{ {
@ -82,6 +97,79 @@ template<typename T>
// --- // ---
// Specialisations for int64_t and bool could have been inline and constexpr,
// but aren't because https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282
template<>
[[nodiscard]] std::optional<int64_t> tr_variant::value_if() noexcept
{
switch (index())
{
case IntIndex:
return *get_if<IntIndex>();
case BoolIndex:
return *get_if<BoolIndex>() ? 1 : 0;
default:
return {};
}
}
template<>
[[nodiscard]] std::optional<bool> tr_variant::value_if() noexcept
{
switch (index())
{
case BoolIndex:
return *get_if<BoolIndex>();
case IntIndex:
if (auto const val = *get_if<IntIndex>(); val == 0 || val == 1)
{
return val != 0;
}
break;
case StringIndex:
if (auto const val = *get_if<StringIndex>(); val == "true")
{
return true;
}
else if (val == "false")
{
return false;
}
break;
default:
break;
}
return {};
}
template<>
[[nodiscard]] std::optional<double> tr_variant::value_if() noexcept
{
switch (index())
{
case DoubleIndex:
return *get_if<DoubleIndex>();
case IntIndex:
return static_cast<double>(*get_if<IntIndex>());
case StringIndex:
return tr_num_parse<double>(*get_if<StringIndex>());
default:
return {};
}
}
// ---
tr_variant::StringHolder::StringHolder(std::string&& str) noexcept tr_variant::StringHolder::StringHolder(std::string&& str) noexcept
: str_{ std::move(str) } : str_{ std::move(str) }
{ {
@ -206,38 +294,12 @@ bool tr_variantListRemove(tr_variant* const var, size_t pos)
bool tr_variantGetInt(tr_variant const* const var, int64_t* setme) bool tr_variantGetInt(tr_variant const* const var, int64_t* setme)
{ {
switch (variant_index(var)) return value_if(var, setme);
{
case tr_variant::IntIndex:
if (setme != nullptr)
{
*setme = *var->get_if<tr_variant::IntIndex>();
}
return true;
case tr_variant::BoolIndex:
if (setme != nullptr)
{
*setme = *var->get_if<tr_variant::BoolIndex>() ? 1 : 0;
}
return true;
default:
return false;
}
} }
bool tr_variantGetStrView(tr_variant const* const var, std::string_view* setme) bool tr_variantGetStrView(tr_variant const* const var, std::string_view* setme)
{ {
switch (variant_index(var)) return value_if(var, setme);
{
case tr_variant::StringIndex:
*setme = *var->get_if<tr_variant::StringIndex>();
return true;
default:
return false;
}
} }
bool tr_variantGetRaw(tr_variant const* v, std::byte const** setme_raw, size_t* setme_len) bool tr_variantGetRaw(tr_variant const* v, std::byte const** setme_raw, size_t* setme_len)
@ -266,63 +328,12 @@ bool tr_variantGetRaw(tr_variant const* v, uint8_t const** setme_raw, size_t* se
bool tr_variantGetBool(tr_variant const* const var, bool* setme) bool tr_variantGetBool(tr_variant const* const var, bool* setme)
{ {
switch (variant_index(var)) return value_if(var, setme);
{
case tr_variant::BoolIndex:
*setme = *var->get_if<tr_variant::BoolIndex>();
return true;
case tr_variant::IntIndex:
if (auto const val = *var->get_if<tr_variant::IntIndex>(); val == 0 || val == 1)
{
*setme = val != 0;
return true;
}
break;
case tr_variant::StringIndex:
if (auto const val = *var->get_if<tr_variant::StringIndex>(); val == "true"sv)
{
*setme = true;
return true;
}
else if (val == "false"sv)
{
*setme = false;
return true;
}
break;
default:
break;
}
return false;
} }
bool tr_variantGetReal(tr_variant const* const var, double* setme) bool tr_variantGetReal(tr_variant const* const var, double* setme)
{ {
switch (variant_index(var)) return value_if(var, setme);
{
case tr_variant::DoubleIndex:
*setme = *var->get_if<tr_variant::DoubleIndex>();
return true;
case tr_variant::IntIndex:
*setme = static_cast<double>(*var->get_if<tr_variant::IntIndex>());
return true;
case tr_variant::StringIndex:
if (auto const val = tr_num_parse<double>(*var->get_if<tr_variant::StringIndex>()); val)
{
*setme = *val;
return true;
}
[[fallthrough]];
default:
return false;
}
} }
bool tr_variantDictFindInt(tr_variant* const var, tr_quark key, int64_t* setme) bool tr_variantDictFindInt(tr_variant* const var, tr_quark key, int64_t* setme)

View File

@ -158,11 +158,11 @@ public:
} }
template<typename Type> template<typename Type>
[[nodiscard]] TR_CONSTEXPR20 std::optional<Type> value_if(tr_quark const key) const noexcept [[nodiscard]] std::optional<Type> value_if(tr_quark const key) const noexcept
{ {
if (auto const* const value = find_if<Type>(key); value != nullptr) if (auto it = find(key); it != end())
{ {
return std::optional<Type>{ *value }; return it->second.value_if<Type>();
} }
return {}; return {};
@ -310,6 +310,23 @@ public:
return const_cast<tr_variant*>(this)->get_if<Index>(); return const_cast<tr_variant*>(this)->get_if<Index>();
} }
template<typename Val>
[[nodiscard]] constexpr std::optional<Val> value_if() noexcept
{
if (auto const* const val = get_if<Val>())
{
return *val;
}
return {};
}
template<typename Val>
[[nodiscard]] std::optional<Val> value_if() const noexcept
{
return const_cast<tr_variant*>(this)->value_if<Val>();
}
template<typename Val> template<typename Val>
[[nodiscard]] constexpr bool holds_alternative() const noexcept [[nodiscard]] constexpr bool holds_alternative() const noexcept
{ {
@ -371,6 +388,13 @@ private:
std::variant<std::monostate, bool, int64_t, double, StringHolder, Vector, Map> val_; std::variant<std::monostate, bool, int64_t, double, StringHolder, Vector, Map> val_;
}; };
template<>
[[nodiscard]] std::optional<int64_t> tr_variant::value_if() noexcept;
template<>
[[nodiscard]] std::optional<bool> tr_variant::value_if() noexcept;
template<>
[[nodiscard]] std::optional<double> tr_variant::value_if() noexcept;
// --- Strings // --- Strings
bool tr_variantGetStrView(tr_variant const* variant, std::string_view* setme); bool tr_variantGetStrView(tr_variant const* variant, std::string_view* setme);

View File

@ -340,8 +340,13 @@ std::optional<tr_url_parsed_t> tr_urlParse(std::string_view url)
auto remain = parsed.authority; auto remain = parsed.authority;
if (tr_strv_starts_with(remain, '[')) if (tr_strv_starts_with(remain, '['))
{ {
remain.remove_prefix(1); // '[' pos = remain.find(']');
parsed.host = tr_strv_sep(&remain, ']'); if (pos == std::string_view::npos)
{
return std::nullopt;
}
parsed.host = remain.substr(0, pos + 1);
remain.remove_prefix(pos + 1);
if (tr_strv_starts_with(remain, ':')) if (tr_strv_starts_with(remain, ':'))
{ {
remain.remove_prefix(1); remain.remove_prefix(1);
@ -389,7 +394,7 @@ std::optional<tr_url_parsed_t> tr_urlParse(std::string_view url)
std::optional<tr_url_parsed_t> tr_urlParseTracker(std::string_view url) std::optional<tr_url_parsed_t> tr_urlParseTracker(std::string_view url)
{ {
auto const parsed = tr_urlParse(url); auto const parsed = tr_urlParse(url);
return parsed && tr_isValidTrackerScheme(parsed->scheme) ? std::make_optional(*parsed) : std::nullopt; return parsed && tr_isValidTrackerScheme(parsed->scheme) ? parsed : std::nullopt;
} }
bool tr_urlIsValidTracker(std::string_view url) bool tr_urlIsValidTracker(std::string_view url)

View File

@ -45,40 +45,45 @@ using namespace libtransmission::Values;
namespace namespace
{ {
class tr_webseed; class tr_webseed_impl;
void on_idle(tr_webseed* w);
class tr_webseed_task class tr_webseed_task
{ {
private:
libtransmission::evhelpers::evbuffer_unique_ptr const content_{ evbuffer_new() };
public: public:
tr_webseed_task(tr_torrent* tor, tr_webseed* webseed_in, tr_block_span_t blocks_in) tr_webseed_task(tr_torrent const& tor, tr_webseed_impl* webseed_in, tr_block_span_t blocks_in)
: webseed{ webseed_in } : blocks{ blocks_in }
, session{ tor->session } , webseed_{ webseed_in }
, blocks{ blocks_in } , session_{ tor.session }
, end_byte{ tor->block_loc(blocks.end - 1).byte + tor->block_size(blocks.end - 1) } , end_byte_{ tor.block_loc(blocks.end - 1).byte + tor.block_size(blocks.end - 1) }
, loc{ tor->block_loc(blocks.begin) } , loc_{ tor.block_loc(blocks.begin) }
{ {
evbuffer_add_cb(content_.get(), on_buffer_got_data, this);
} }
tr_webseed* const webseed;
[[nodiscard]] auto* content() const [[nodiscard]] auto* content() const
{ {
return content_.get(); return content_.get();
} }
tr_session* const session; void request_next_chunk();
tr_block_span_t const blocks;
uint64_t const end_byte;
// the current position in the task; i.e., the next block to save
tr_block_info::Location loc;
bool dead = false; bool dead = false;
tr_block_span_t const blocks;
private:
void use_fetched_blocks();
static void on_partial_data_fetched(tr_web::FetchResponse const& web_response);
static void on_buffer_got_data(evbuffer* /*buf*/, evbuffer_cb_info const* info, void* vtask);
tr_webseed_impl* const webseed_;
tr_session* const session_;
uint64_t const end_byte_;
// the current position in the task; i.e., the next block to save
tr_block_info::Location loc_;
libtransmission::evhelpers::evbuffer_unique_ptr const content_{ evbuffer_new() };
}; };
/** /**
@ -93,37 +98,37 @@ public:
class ConnectionLimiter class ConnectionLimiter
{ {
public: public:
constexpr void taskStarted() noexcept constexpr void task_started() noexcept
{ {
++n_tasks; ++n_tasks;
} }
void taskFinished(bool success) void task_finished(bool success)
{ {
if (!success) if (!success)
{ {
taskFailed(); task_failed();
} }
TR_ASSERT(n_tasks > 0); TR_ASSERT(n_tasks > 0);
--n_tasks; --n_tasks;
} }
constexpr void gotData() noexcept constexpr void got_data() noexcept
{ {
TR_ASSERT(n_tasks > 0); TR_ASSERT(n_tasks > 0);
n_consecutive_failures = 0; n_consecutive_failures = 0;
paused_until = 0; paused_until = 0;
} }
[[nodiscard]] size_t slotsAvailable() const noexcept [[nodiscard]] size_t slots_available() const noexcept
{ {
if (isPaused()) if (is_paused())
{ {
return 0; return 0;
} }
auto const max = maxConnections(); auto const max = max_connections();
if (n_tasks >= max) if (n_tasks >= max)
{ {
return 0; return 0;
@ -133,17 +138,17 @@ public:
} }
private: private:
[[nodiscard]] bool isPaused() const noexcept [[nodiscard]] bool is_paused() const noexcept
{ {
return paused_until > tr_time(); return paused_until > tr_time();
} }
[[nodiscard]] constexpr size_t maxConnections() const noexcept [[nodiscard]] constexpr size_t max_connections() const noexcept
{ {
return n_consecutive_failures > 0 ? 1 : MaxConnections; return n_consecutive_failures > 0 ? 1 : MaxConnections;
} }
void taskFailed() void task_failed()
{ {
TR_ASSERT(n_tasks > 0); TR_ASSERT(n_tasks > 0);
@ -153,19 +158,16 @@ private:
} }
} }
static time_t constexpr TimeoutIntervalSecs = 120; static auto constexpr TimeoutIntervalSecs = time_t{ 120 };
static size_t constexpr MaxConnections = 4; static auto constexpr MaxConnections = size_t{ 4 };
static size_t constexpr MaxConsecutiveFailures = MaxConnections; static auto constexpr MaxConsecutiveFailures = MaxConnections;
size_t n_tasks = 0; size_t n_tasks = 0;
size_t n_consecutive_failures = 0; size_t n_consecutive_failures = 0;
time_t paused_until = 0; time_t paused_until = 0;
}; };
void task_request_next_chunk(tr_webseed_task* task); class tr_webseed_impl final : public tr_webseed
void onBufferGotData(evbuffer* /*buf*/, evbuffer_cb_info const* info, void* vtask);
class tr_webseed final : public tr_peer
{ {
public: public:
struct RequestLimit struct RequestLimit
@ -178,42 +180,44 @@ public:
size_t max_blocks = 0; size_t max_blocks = 0;
}; };
tr_webseed(tr_torrent& tor, std::string_view url, tr_peer_callback_webseed callback_in, void* callback_data_in) tr_webseed_impl(tr_torrent& tor_in, std::string_view url, tr_peer_callback_webseed callback_in, void* callback_data_in)
: tr_peer{ tor } : tr_webseed{ tor_in }
, torrent_id{ tor.id() } , tor{ tor_in }
, base_url{ url } , base_url{ url }
, callback{ callback_in } , idle_timer_{ session->timerMaker().create([this]() { on_idle(); }) }
, callback_data{ callback_data_in } , have_{ tor_in.piece_count() }
, idle_timer_{ session->timerMaker().create([this]() { on_idle(this); }) } , bandwidth_{ &tor_in.bandwidth() }
, have_{ tor.piece_count() } , callback_{ callback_in }
, bandwidth_{ &tor.bandwidth() } , callback_data_{ callback_data_in }
{ {
have_.set_has_all(); have_.set_has_all();
idle_timer_->start_repeating(IdleTimerInterval); idle_timer_->start_repeating(IdleTimerInterval);
} }
tr_webseed(tr_webseed&&) = delete; tr_webseed_impl(tr_webseed_impl&&) = delete;
tr_webseed(tr_webseed const&) = delete; tr_webseed_impl(tr_webseed_impl const&) = delete;
tr_webseed& operator=(tr_webseed&&) = delete; tr_webseed_impl& operator=(tr_webseed_impl&&) = delete;
tr_webseed& operator=(tr_webseed const&) = delete; tr_webseed_impl& operator=(tr_webseed_impl const&) = delete;
~tr_webseed() override ~tr_webseed_impl() override
{ {
// flag all the pending tasks as dead // flag all the pending tasks as dead
std::for_each(std::begin(tasks), std::end(tasks), [](auto* task) { task->dead = true; }); std::for_each(std::begin(tasks), std::end(tasks), [](auto* task) { task->dead = true; });
tasks.clear(); tasks.clear();
} }
[[nodiscard]] tr_torrent* getTorrent() const
{
return tr_torrentFindFromId(session, torrent_id);
}
[[nodiscard]] Speed get_piece_speed(uint64_t now, tr_direction dir) const override [[nodiscard]] Speed get_piece_speed(uint64_t now, tr_direction dir) const override
{ {
return dir == TR_DOWN ? bandwidth_.get_piece_speed(now, dir) : Speed{}; return dir == TR_DOWN ? bandwidth_.get_piece_speed(now, dir) : Speed{};
} }
[[nodiscard]] tr_webseed_view get_view() const override
{
auto const is_downloading = !std::empty(tasks);
auto const speed = get_piece_speed(tr_time_msec(), TR_DOWN);
return { base_url.c_str(), is_downloading, speed.base_quantity() };
}
[[nodiscard]] TR_CONSTEXPR20 size_t active_req_count(tr_direction dir) const noexcept override [[nodiscard]] TR_CONSTEXPR20 size_t active_req_count(tr_direction dir) const noexcept override
{ {
if (dir == TR_CLIENT_TO_PEER) // blocks we've requested if (dir == TR_CLIENT_TO_PEER) // blocks we've requested
@ -231,7 +235,7 @@ public:
[[nodiscard]] std::string display_name() const override [[nodiscard]] std::string display_name() const override
{ {
if (auto const parsed = tr_urlParse(base_url); parsed) if (auto const parsed = tr_urlParse(base_url))
{ {
return fmt::format("{:s}:{:d}", parsed->host, parsed->port); return fmt::format("{:s}:{:d}", parsed->host, parsed->port);
} }
@ -244,26 +248,24 @@ public:
return have_; return have_;
} }
void gotPieceData(uint32_t n_bytes) void got_piece_data(uint32_t n_bytes)
{ {
bandwidth_.notify_bandwidth_consumed(TR_DOWN, n_bytes, true, tr_time_msec()); bandwidth_.notify_bandwidth_consumed(TR_DOWN, n_bytes, true, tr_time_msec());
publish(tr_peer_event::GotPieceData(n_bytes)); publish(tr_peer_event::GotPieceData(n_bytes));
connection_limiter.gotData(); connection_limiter.got_data();
} }
void publishRejection(tr_block_span_t block_span) void publish_rejection(tr_block_span_t block_span)
{ {
auto const* const tor = getTorrent();
for (auto block = block_span.begin; block < block_span.end; ++block) for (auto block = block_span.begin; block < block_span.end; ++block)
{ {
publish(tr_peer_event::GotRejected(tor->block_info(), block)); publish(tr_peer_event::GotRejected(tor.block_info(), block));
} }
} }
void request_blocks(tr_block_span_t const* block_spans, size_t n_spans) override void request_blocks(tr_block_span_t const* block_spans, size_t n_spans) override
{ {
auto* const tor = getTorrent(); if (!tor.is_running() || tor.is_done())
if (tor == nullptr || !tor->is_running() || tor->is_done())
{ {
return; return;
} }
@ -271,23 +273,41 @@ public:
for (auto const *span = block_spans, *end = span + n_spans; span != end; ++span) for (auto const *span = block_spans, *end = span + n_spans; span != end; ++span)
{ {
auto* const task = new tr_webseed_task{ tor, this, *span }; auto* const task = new tr_webseed_task{ tor, this, *span };
evbuffer_add_cb(task->content(), onBufferGotData, task);
tasks.insert(task); tasks.insert(task);
task_request_next_chunk(task); task->request_next_chunk();
tr_peerMgrClientSentRequests(tor, this, *span); tr_peerMgrClientSentRequests(&tor, this, *span);
} }
} }
void on_idle()
{
auto const [max_spans, max_blocks] = max_available_reqs();
if (max_spans == 0 || max_blocks == 0)
{
return;
}
// Prefer to request large, contiguous chunks from webseeds.
// The actual value of '64' is arbitrary here; we could probably
// be smarter about this.
auto spans = tr_peerMgrGetNextRequests(&tor, this, max_blocks);
if (std::size(spans) > max_spans)
{
spans.resize(max_spans);
}
request_blocks(std::data(spans), std::size(spans));
}
[[nodiscard]] RequestLimit max_available_reqs() const noexcept [[nodiscard]] RequestLimit max_available_reqs() const noexcept
{ {
auto const n_slots = connection_limiter.slotsAvailable(); auto const n_slots = connection_limiter.slots_available();
if (n_slots == 0) if (n_slots == 0)
{ {
return {}; return {};
} }
if (auto const* const tor = getTorrent(); tor == nullptr || !tor->is_running() || tor->is_done()) if (!tor.is_running() || tor.is_done())
{ {
return {}; return {};
} }
@ -301,16 +321,14 @@ public:
void publish(tr_peer_event const& peer_event) void publish(tr_peer_event const& peer_event)
{ {
if (callback != nullptr) if (callback_ != nullptr)
{ {
(*callback)(this, peer_event, callback_data); (*callback_)(this, peer_event, callback_data_);
} }
} }
tr_torrent_id_t const torrent_id; tr_torrent& tor;
std::string const base_url; std::string const base_url;
tr_peer_callback_webseed const callback;
void* const callback_data;
ConnectionLimiter connection_limiter; ConnectionLimiter connection_limiter;
std::set<tr_webseed_task*> tasks; std::set<tr_webseed_task*> tasks;
@ -323,92 +341,57 @@ private:
tr_bitfield have_; tr_bitfield have_;
tr_bandwidth bandwidth_; tr_bandwidth bandwidth_;
tr_peer_callback_webseed const callback_;
void* const callback_data_;
}; };
// --- // ---
struct write_block_data void tr_webseed_task::use_fetched_blocks()
{ {
private: auto const lock = session_->unique_lock();
libtransmission::evhelpers::evbuffer_unique_ptr const content_{ evbuffer_new() };
public: auto const& tor = webseed_->tor;
write_block_data(
tr_session* session, for (auto* const buf = content();;)
tr_torrent_id_t tor_id,
tr_block_index_t block,
std::unique_ptr<Cache::BlockData> data,
tr_webseed* webseed)
: session_{ session }
, tor_id_{ tor_id }
, block_{ block }
, data_{ std::move(data) }
, webseed_{ webseed }
{ {
} auto const block_size = tor.block_size(loc_.block);
void write_block_func()
{
if (auto const* const tor = tr_torrentFindFromId(session_, tor_id_); tor != nullptr)
{
session_->cache->write_block(tor_id_, block_, std::move(data_));
webseed_->publish(tr_peer_event::GotBlock(tor->block_info(), block_));
}
delete this;
}
private:
tr_session* const session_;
tr_torrent_id_t const tor_id_;
tr_block_index_t const block_;
std::unique_ptr<Cache::BlockData> data_;
tr_webseed* const webseed_;
};
void useFetchedBlocks(tr_webseed_task* task)
{
auto* const session = task->session;
auto const lock = session->unique_lock();
auto* const webseed = task->webseed;
auto const* const tor = webseed->getTorrent();
if (tor == nullptr)
{
return;
}
auto* const buf = task->content();
for (;;)
{
auto const block_size = tor->block_size(task->loc.block);
if (evbuffer_get_length(buf) < block_size) if (evbuffer_get_length(buf) < block_size)
{ {
break; break;
} }
if (tor->has_block(task->loc.block)) if (tor.has_block(loc_.block))
{ {
evbuffer_drain(buf, block_size); evbuffer_drain(buf, block_size);
} }
else else
{ {
auto block_buf = std::make_unique<Cache::BlockData>(block_size); auto block_buf = new Cache::BlockData(block_size);
evbuffer_remove(task->content(), std::data(*block_buf), std::size(*block_buf)); evbuffer_remove(buf, std::data(*block_buf), std::size(*block_buf));
auto* const data = new write_block_data{ session, tor->id(), task->loc.block, std::move(block_buf), webseed }; session_->run_in_session_thread(
session->run_in_session_thread(&write_block_data::write_block_func, data); [session = session_, tor_id = tor.id(), block = loc_.block, block_buf, webseed = webseed_]()
{
auto data = std::unique_ptr<Cache::BlockData>{ block_buf };
if (auto const* const torrent = tr_torrentFindFromId(session, tor_id); torrent != nullptr)
{
session->cache->write_block(tor_id, block, std::move(data));
webseed->publish(tr_peer_event::GotBlock(torrent->block_info(), block));
}
});
} }
task->loc = tor->byte_loc(task->loc.byte + block_size); loc_ = tor.byte_loc(loc_.byte + block_size);
TR_ASSERT(task->loc.byte <= task->end_byte); TR_ASSERT(loc_.byte <= end_byte_);
TR_ASSERT(task->loc.byte == task->end_byte || task->loc.block_offset == 0); TR_ASSERT(loc_.byte == end_byte_ || loc_.block_offset == 0);
} }
} }
// --- // ---
void onBufferGotData(evbuffer* /*buf*/, evbuffer_cb_info const* info, void* vtask) void tr_webseed_task::on_buffer_got_data(evbuffer* /*buf*/, evbuffer_cb_info const* info, void* vtask)
{ {
size_t const n_added = info->n_added; size_t const n_added = info->n_added;
auto* const task = static_cast<tr_webseed_task*>(vtask); auto* const task = static_cast<tr_webseed_task*>(vtask);
@ -417,33 +400,14 @@ void onBufferGotData(evbuffer* /*buf*/, evbuffer_cb_info const* info, void* vtas
return; return;
} }
auto const lock = task->session->unique_lock(); auto const lock = task->session_->unique_lock();
task->webseed->gotPieceData(n_added); task->webseed_->got_piece_data(n_added);
} }
void on_idle(tr_webseed* webseed) void tr_webseed_task::on_partial_data_fetched(tr_web::FetchResponse const& web_response)
{
auto const [max_spans, max_blocks] = webseed->max_available_reqs();
if (max_spans == 0 || max_blocks == 0)
{
return;
}
// Prefer to request large, contiguous chunks from webseeds.
// The actual value of '64' is arbitrary here; we could probably
// be smarter about this.
auto spans = tr_peerMgrGetNextRequests(webseed->getTorrent(), webseed, max_blocks);
if (std::size(spans) > max_spans)
{
spans.resize(max_spans);
}
webseed->request_blocks(std::data(spans), std::size(spans));
}
void onPartialDataFetched(tr_web::FetchResponse const& web_response)
{ {
auto const& [status, body, primary_ip, did_connect, did_timeout, vtask] = web_response; auto const& [status, body, primary_ip, did_connect, did_timeout, vtask] = web_response;
bool const success = status == 206; auto const success = status == 206;
auto* const task = static_cast<tr_webseed_task*>(vtask); auto* const task = static_cast<tr_webseed_task*>(vtask);
@ -453,43 +417,38 @@ void onPartialDataFetched(tr_web::FetchResponse const& web_response)
return; return;
} }
auto* const webseed = task->webseed; auto* const webseed = task->webseed_;
webseed->connection_limiter.taskFinished(success); webseed->connection_limiter.task_finished(success);
if (auto const* const tor = webseed->getTorrent(); tor == nullptr)
{
return;
}
if (!success) if (!success)
{ {
webseed->publishRejection({ task->loc.block, task->blocks.end }); webseed->publish_rejection({ task->loc_.block, task->blocks.end });
webseed->tasks.erase(task); webseed->tasks.erase(task);
delete task; delete task;
return; return;
} }
useFetchedBlocks(task); task->use_fetched_blocks();
if (task->loc.byte < task->end_byte) if (task->loc_.byte < task->end_byte_)
{ {
// Request finished successfully but there's still data missing. // Request finished successfully but there's still data missing.
// That means we've reached the end of a file and need to request // That means we've reached the end of a file and need to request
// the next one // the next one
task_request_next_chunk(task); task->request_next_chunk();
return; return;
} }
TR_ASSERT(evbuffer_get_length(task->content()) == 0); TR_ASSERT(evbuffer_get_length(task->content()) == 0);
TR_ASSERT(task->loc.byte == task->end_byte); TR_ASSERT(task->loc_.byte == task->end_byte_);
webseed->tasks.erase(task); webseed->tasks.erase(task);
delete task; delete task;
on_idle(webseed); webseed->on_idle();
} }
template<typename OutputIt> template<typename OutputIt>
void makeUrl(tr_webseed const* const webseed, std::string_view name, OutputIt out) void makeUrl(tr_webseed_impl const* const webseed, std::string_view name, OutputIt out)
{ {
auto const& url = webseed->base_url; auto const& url = webseed->base_url;
@ -501,52 +460,38 @@ void makeUrl(tr_webseed const* const webseed, std::string_view name, OutputIt ou
} }
} }
void task_request_next_chunk(tr_webseed_task* task) void tr_webseed_task::request_next_chunk()
{ {
auto* const webseed = task->webseed; auto const& tor = webseed_->tor;
auto const* const tor = webseed->getTorrent();
if (tor == nullptr)
{
return;
}
auto const loc = tor->byte_loc(task->loc.byte + evbuffer_get_length(task->content())); auto const downloaded_loc = tor.byte_loc(loc_.byte + evbuffer_get_length(content()));
auto const [file_index, file_offset] = tor->file_offset(loc); auto const [file_index, file_offset] = tor.file_offset(downloaded_loc);
auto const left_in_file = tor->file_size(file_index) - file_offset; auto const left_in_file = tor.file_size(file_index) - file_offset;
auto const left_in_task = task->end_byte - loc.byte; auto const left_in_task = end_byte_ - downloaded_loc.byte;
auto const this_chunk = std::min(left_in_file, left_in_task); auto const this_chunk = std::min(left_in_file, left_in_task);
TR_ASSERT(this_chunk > 0U); TR_ASSERT(this_chunk > 0U);
webseed->connection_limiter.taskStarted(); webseed_->connection_limiter.task_started();
auto url = tr_urlbuf{}; auto url = tr_urlbuf{};
makeUrl(webseed, tor->file_subpath(file_index), std::back_inserter(url)); makeUrl(webseed_, tor.file_subpath(file_index), std::back_inserter(url));
auto options = tr_web::FetchOptions{ url.sv(), onPartialDataFetched, task }; auto options = tr_web::FetchOptions{ url.sv(), on_partial_data_fetched, this };
options.range = fmt::format("{:d}-{:d}", file_offset, file_offset + this_chunk - 1); options.range = fmt::format("{:d}-{:d}", file_offset, file_offset + this_chunk - 1);
options.speed_limit_tag = tor->id(); options.speed_limit_tag = tor.id();
options.buffer = task->content(); options.buffer = content();
tor->session->fetch(std::move(options)); tor.session->fetch(std::move(options));
} }
} // namespace } // namespace
// --- // ---
tr_peer* tr_webseedNew(tr_torrent& torrent, std::string_view url, tr_peer_callback_webseed callback, void* callback_data) std::unique_ptr<tr_webseed> tr_webseed::create(
tr_torrent& torrent,
std::string_view url,
tr_peer_callback_webseed callback,
void* callback_data)
{ {
return new tr_webseed{ torrent, url, callback, callback_data }; return std::make_unique<tr_webseed_impl>(torrent, url, callback, callback_data);
}
tr_webseed_view tr_webseedView(tr_peer const* peer)
{
auto const* const webseed = dynamic_cast<tr_webseed const*>(peer);
if (webseed == nullptr)
{
return {};
}
auto const is_downloading = !std::empty(webseed->tasks);
auto const speed = peer->get_piece_speed(tr_time_msec(), TR_DOWN);
return { webseed->base_url.c_str(), is_downloading, speed.base_quantity() };
} }

View File

@ -9,6 +9,7 @@
#error only libtransmission should #include this header. #error only libtransmission should #include this header.
#endif #endif
#include <memory>
#include <string_view> #include <string_view>
#include "libtransmission/transmission.h" #include "libtransmission/transmission.h"
@ -17,6 +18,20 @@
using tr_peer_callback_webseed = tr_peer_callback_generic; using tr_peer_callback_webseed = tr_peer_callback_generic;
tr_peer* tr_webseedNew(tr_torrent& torrent, std::string_view, tr_peer_callback_webseed callback, void* callback_data); class tr_webseed : public tr_peer
{
protected:
explicit tr_webseed(tr_torrent& tor_in)
: tr_peer{ tor_in }
{
}
tr_webseed_view tr_webseedView(tr_peer const* peer); public:
[[nodiscard]] static std::unique_ptr<tr_webseed> create(
tr_torrent& torrent,
std::string_view,
tr_peer_callback_webseed callback,
void* callback_data);
[[nodiscard]] virtual tr_webseed_view get_view() const = 0;
};

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>
@ -13,6 +13,8 @@
<outlet property="fDataLocationField" destination="14" id="48"/> <outlet property="fDataLocationField" destination="14" id="48"/>
<outlet property="fDateCreatedField" destination="8" id="47"/> <outlet property="fDateCreatedField" destination="8" id="47"/>
<outlet property="fHashField" destination="5" id="43"/> <outlet property="fHashField" destination="5" id="43"/>
<outlet property="fLastDataLabel" destination="52" id="53"/>
<outlet property="fLastDataLocationField" destination="54" id="55"/>
<outlet property="fPiecesField" destination="19" id="42"/> <outlet property="fPiecesField" destination="19" id="42"/>
<outlet property="fRevealDataButton" destination="12" id="49"/> <outlet property="fRevealDataButton" destination="12" id="49"/>
<outlet property="fSecureField" destination="16" id="44"/> <outlet property="fSecureField" destination="16" id="44"/>
@ -111,36 +113,6 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="12">
<rect key="frame" x="111" y="12" width="14" height="14"/>
<constraints>
<constraint firstAttribute="height" constant="14" id="50q-rm-IPl"/>
<constraint firstAttribute="width" constant="14" id="Zd6-df-Ynq"/>
</constraints>
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="RevealOff" imagePosition="only" alignment="center" alternateImage="RevealOn" imageScaling="proportionallyDown" inset="2" id="29">
<behavior key="behavior" lightByContents="YES"/>
<font key="font" metaFont="label"/>
</buttonCell>
<connections>
<action selector="revealDataFile:" target="-2" id="50"/>
</connections>
</button>
<textField horizontalHuggingPriority="249" verticalHuggingPriority="750" horizontalCompressionResistancePriority="249" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="14" customClass="InfoTextField">
<rect key="frame" x="82" y="12" width="23" height="14"/>
<textFieldCell key="cell" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" placeholderString="N/A" id="27">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="249" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="15">
<rect key="frame" x="10" y="12" width="68" height="14"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="right" title="Data File:" id="26">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="249" verticalHuggingPriority="750" horizontalCompressionResistancePriority="249" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="16" customClass="InfoTextField"> <textField horizontalHuggingPriority="249" verticalHuggingPriority="750" horizontalCompressionResistancePriority="249" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="16" customClass="InfoTextField">
<rect key="frame" x="82" y="154" width="264" height="14"/> <rect key="frame" x="82" y="154" width="264" height="14"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" placeholderString="N/A" id="25"> <textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" placeholderString="N/A" id="25">
@ -189,6 +161,52 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="249" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="15">
<rect key="frame" x="10" y="12" width="68" height="14"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="right" title="Data File:" id="26">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="249" verticalHuggingPriority="750" horizontalCompressionResistancePriority="249" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="14" customClass="InfoTextField">
<rect key="frame" x="82" y="12" width="23" height="14"/>
<textFieldCell key="cell" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" placeholderString="N/A" id="27">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="249" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="52">
<rect key="frame" x="109" y="12" width="30" height="14"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="right" title="Was:" id="uaW-NM-PTU">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField focusRingType="none" horizontalHuggingPriority="249" verticalHuggingPriority="750" horizontalCompressionResistancePriority="249" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="0.0" translatesAutoresizingMaskIntoConstraints="NO" id="54" customClass="InfoTextField">
<rect key="frame" x="143" y="12" width="23" height="14"/>
<textFieldCell key="cell" lineBreakMode="truncatingMiddle" selectable="YES" sendsActionOnEndEditing="YES" placeholderString="N/A" id="7YU-bS-UkI">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="12">
<rect key="frame" x="111" y="12" width="14" height="14"/>
<buttonCell key="cell" type="square" bezelStyle="shadowlessSquare" image="RevealOff" imagePosition="only" alignment="center" alternateImage="RevealOn" imageScaling="proportionallyDown" inset="2" id="29">
<behavior key="behavior" lightByContents="YES"/>
<font key="font" metaFont="label"/>
</buttonCell>
<constraints>
<constraint firstAttribute="height" constant="14" id="50q-rm-IPl"/>
<constraint firstAttribute="width" constant="14" id="Zd6-df-Ynq"/>
</constraints>
<connections>
<action selector="revealDataFile:" target="-2" id="50"/>
</connections>
</button>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="5" firstAttribute="leading" secondItem="6" secondAttribute="trailing" constant="8" symbolic="YES" id="5Ne-pq-sRS"/> <constraint firstItem="5" firstAttribute="leading" secondItem="6" secondAttribute="trailing" constant="8" symbolic="YES" id="5Ne-pq-sRS"/>
@ -201,9 +219,12 @@
<constraint firstItem="5" firstAttribute="baseline" secondItem="6" secondAttribute="baseline" id="ECi-VE-QMb"/> <constraint firstItem="5" firstAttribute="baseline" secondItem="6" secondAttribute="baseline" id="ECi-VE-QMb"/>
<constraint firstAttribute="trailing" secondItem="19" secondAttribute="trailing" constant="12" id="Gz7-KD-B5M"/> <constraint firstAttribute="trailing" secondItem="19" secondAttribute="trailing" constant="12" id="Gz7-KD-B5M"/>
<constraint firstItem="6" firstAttribute="leading" secondItem="20" secondAttribute="leading" id="HYF-CL-zWb"/> <constraint firstItem="6" firstAttribute="leading" secondItem="20" secondAttribute="leading" id="HYF-CL-zWb"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="54" secondAttribute="trailing" priority="249" constant="12" id="HoE-d3-pZC"/>
<constraint firstItem="54" firstAttribute="centerY" secondItem="12" secondAttribute="centerY" id="HuR-fz-Sgm"/>
<constraint firstItem="15" firstAttribute="top" secondItem="13" secondAttribute="bottom" constant="2" id="ISV-i7-Dw4"/> <constraint firstItem="15" firstAttribute="top" secondItem="13" secondAttribute="bottom" constant="2" id="ISV-i7-Dw4"/>
<constraint firstItem="11" firstAttribute="width" secondItem="17" secondAttribute="width" id="IvL-6F-3c5"/> <constraint firstItem="11" firstAttribute="width" secondItem="17" secondAttribute="width" id="IvL-6F-3c5"/>
<constraint firstItem="8" firstAttribute="leading" secondItem="9" secondAttribute="trailing" constant="8" symbolic="YES" id="JVu-ov-gM4"/> <constraint firstItem="8" firstAttribute="leading" secondItem="9" secondAttribute="trailing" constant="8" symbolic="YES" id="JVu-ov-gM4"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="13" secondAttribute="trailing" constant="12" id="K5p-t1-MGg"/>
<constraint firstItem="8" firstAttribute="baseline" secondItem="9" secondAttribute="baseline" id="MtY-dy-1hK"/> <constraint firstItem="8" firstAttribute="baseline" secondItem="9" secondAttribute="baseline" id="MtY-dy-1hK"/>
<constraint firstItem="13" firstAttribute="leading" secondItem="3" secondAttribute="leading" id="Mzp-Ok-3pG"/> <constraint firstItem="13" firstAttribute="leading" secondItem="3" secondAttribute="leading" id="Mzp-Ok-3pG"/>
<constraint firstItem="10" firstAttribute="leading" secondItem="11" secondAttribute="trailing" constant="8" symbolic="YES" id="Nn5-Qe-jAB"/> <constraint firstItem="10" firstAttribute="leading" secondItem="11" secondAttribute="trailing" constant="8" symbolic="YES" id="Nn5-Qe-jAB"/>
@ -226,9 +247,12 @@
<constraint firstAttribute="bottom" secondItem="15" secondAttribute="bottom" constant="12" id="gv8-oj-Jkx"/> <constraint firstAttribute="bottom" secondItem="15" secondAttribute="bottom" constant="12" id="gv8-oj-Jkx"/>
<constraint firstItem="14" firstAttribute="leading" secondItem="15" secondAttribute="trailing" constant="8" symbolic="YES" id="iqM-1Z-Swp"/> <constraint firstItem="14" firstAttribute="leading" secondItem="15" secondAttribute="trailing" constant="8" symbolic="YES" id="iqM-1Z-Swp"/>
<constraint firstItem="17" firstAttribute="leading" secondItem="6" secondAttribute="leading" id="jYk-4k-PC2"/> <constraint firstItem="17" firstAttribute="leading" secondItem="6" secondAttribute="leading" id="jYk-4k-PC2"/>
<constraint firstItem="52" firstAttribute="leading" secondItem="14" secondAttribute="trailing" constant="8" symbolic="YES" id="jc0-0Z-u36"/>
<constraint firstItem="9" firstAttribute="width" secondItem="11" secondAttribute="width" id="jrd-BL-IQN"/> <constraint firstItem="9" firstAttribute="width" secondItem="11" secondAttribute="width" id="jrd-BL-IQN"/>
<constraint firstItem="54" firstAttribute="baseline" secondItem="52" secondAttribute="baseline" id="lhn-fM-eSh"/>
<constraint firstItem="12" firstAttribute="leading" secondItem="14" secondAttribute="trailing" constant="8" symbolic="YES" id="m7k-6t-cbD"/> <constraint firstItem="12" firstAttribute="leading" secondItem="14" secondAttribute="trailing" constant="8" symbolic="YES" id="m7k-6t-cbD"/>
<constraint firstItem="11" firstAttribute="leading" secondItem="17" secondAttribute="leading" id="mMq-1j-8xZ"/> <constraint firstItem="11" firstAttribute="leading" secondItem="17" secondAttribute="leading" id="mMq-1j-8xZ"/>
<constraint firstItem="54" firstAttribute="leading" secondItem="52" secondAttribute="trailing" constant="8" symbolic="YES" id="nOT-Fw-u31"/>
<constraint firstItem="13" firstAttribute="top" secondItem="4" secondAttribute="bottom" constant="20" id="pL5-Vg-kGN"/> <constraint firstItem="13" firstAttribute="top" secondItem="4" secondAttribute="bottom" constant="20" id="pL5-Vg-kGN"/>
<constraint firstItem="7" firstAttribute="leading" secondItem="9" secondAttribute="leading" id="rBJ-74-Kzj"/> <constraint firstItem="7" firstAttribute="leading" secondItem="9" secondAttribute="leading" id="rBJ-74-Kzj"/>
<constraint firstItem="20" firstAttribute="top" secondItem="3" secondAttribute="bottom" constant="2" id="rl9-wM-6CX"/> <constraint firstItem="20" firstAttribute="top" secondItem="3" secondAttribute="bottom" constant="2" id="rl9-wM-6CX"/>

View File

@ -510,6 +510,17 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool
tr_variantDictAddInt(&settings, TR_KEY_peer_limit_global, [_fDefaults integerForKey:@"PeersTotal"]); tr_variantDictAddInt(&settings, TR_KEY_peer_limit_global, [_fDefaults integerForKey:@"PeersTotal"]);
tr_variantDictAddInt(&settings, TR_KEY_peer_limit_per_torrent, [_fDefaults integerForKey:@"PeersTorrent"]); tr_variantDictAddInt(&settings, TR_KEY_peer_limit_per_torrent, [_fDefaults integerForKey:@"PeersTorrent"]);
NSInteger bindPort = [_fDefaults integerForKey:@"BindPort"];
if (bindPort <= 0 || bindPort > 65535)
{
// First launch, we avoid a default port to be less likely blocked on such port and to have more chances of success when connecting to swarms.
// Ideally, we should be setting port 0, then reading the port number assigned by the system and save that value. But that would be best handled by libtransmission itself.
// For now, we randomize the port as a Dynamic/Private/Ephemeral Port from 4915265535
// https://datatracker.ietf.org/doc/html/rfc6335#section-6
uint16_t defaultPort = 49152 + arc4random_uniform(65536 - 49152);
[_fDefaults setInteger:defaultPort forKey:@"BindPort"];
}
BOOL const randomPort = [_fDefaults boolForKey:@"RandomPort"]; BOOL const randomPort = [_fDefaults boolForKey:@"RandomPort"];
tr_variantDictAddBool(&settings, TR_KEY_peer_port_random_on_start, randomPort); tr_variantDictAddBool(&settings, TR_KEY_peer_port_random_on_start, randomPort);
if (!randomPort) if (!randomPort)

View File

@ -14,8 +14,6 @@
<true/> <true/>
<key>BadgeUploadRate</key> <key>BadgeUploadRate</key>
<true/> <true/>
<key>BindPort</key>
<integer>51413</integer>
<key>BlocklistAutoUpdate</key> <key>BlocklistAutoUpdate</key>
<false/> <false/>
<key>BlocklistNew</key> <key>BlocklistNew</key>

View File

@ -16,6 +16,8 @@
@property(nonatomic) IBOutlet NSTextField* fHashField; @property(nonatomic) IBOutlet NSTextField* fHashField;
@property(nonatomic) IBOutlet NSTextField* fSecureField; @property(nonatomic) IBOutlet NSTextField* fSecureField;
@property(nonatomic) IBOutlet NSTextField* fDataLocationField; @property(nonatomic) IBOutlet NSTextField* fDataLocationField;
@property(nonatomic) IBOutlet NSTextField* fLastDataLocationField;
@property(nonatomic) IBOutlet NSTextField* fLastDataLabel;
@property(nonatomic) IBOutlet NSTextField* fCreatorField; @property(nonatomic) IBOutlet NSTextField* fCreatorField;
@property(nonatomic) IBOutlet NSTextField* fDateCreatedField; @property(nonatomic) IBOutlet NSTextField* fDateCreatedField;
@ -60,10 +62,17 @@
Torrent* torrent = self.fTorrents[0]; Torrent* torrent = self.fTorrents[0];
NSString* location = torrent.dataLocation; NSString* location = torrent.dataLocation;
NSString* lastKnownDataLocation = torrent.lastKnownDataLocation;
self.fDataLocationField.stringValue = location ? location.stringByAbbreviatingWithTildeInPath : @""; self.fDataLocationField.stringValue = location ? location.stringByAbbreviatingWithTildeInPath : @"";
self.fDataLocationField.toolTip = location ? location : @""; self.fDataLocationField.toolTip = location ? location : @"";
self.fRevealDataButton.hidden = !location; self.fLastDataLabel.hidden = location ? YES : NO;
self.fLastDataLocationField.hidden = location ? YES : NO;
self.fLastDataLocationField.stringValue = location ? @"" : lastKnownDataLocation.stringByAbbreviatingWithTildeInPath;
self.fLastDataLocationField.toolTip = location ? @"" : lastKnownDataLocation;
self.fRevealDataButton.hidden = location ? NO : YES;
} }
- (void)revealDataFile:(id)sender - (void)revealDataFile:(id)sender
@ -83,6 +92,9 @@
- (void)setupInfo - (void)setupInfo
{ {
self.fLastDataLabel.hidden = YES;
self.fLastDataLocationField.hidden = YES;
if (self.fTorrents.count == 1) if (self.fTorrents.count == 1)
{ {
Torrent* torrent = self.fTorrents[0]; Torrent* torrent = self.fTorrents[0];

View File

@ -111,6 +111,7 @@ extern NSString* const kTorrentDidChangeGroupNotification;
@property(nonatomic, readonly) NSString* torrentLocation; @property(nonatomic, readonly) NSString* torrentLocation;
@property(nonatomic, readonly) NSString* dataLocation; @property(nonatomic, readonly) NSString* dataLocation;
@property(nonatomic, readonly) NSString* lastKnownDataLocation;
- (NSString*)fileLocation:(FileListNode*)node; - (NSString*)fileLocation:(FileListNode*)node;
- (void)renameTorrent:(NSString*)newName completionHandler:(void (^)(BOOL didRename))completionHandler; - (void)renameTorrent:(NSString*)newName completionHandler:(void (^)(BOOL didRename))completionHandler;

View File

@ -773,6 +773,25 @@ bool trashDataFile(char const* filename, void* /*user_data*/, tr_error* error)
} }
} }
- (NSString*)lastKnownDataLocation
{
if (self.magnet)
{
return nil;
}
if (self.folder)
{
NSString* lastDataLocation = [self.currentDirectory stringByAppendingPathComponent:self.name];
return lastDataLocation;
}
else
{
auto const lastFileName = @(tr_torrentFile(self.fHandle, 0).name);
return [self.currentDirectory stringByAppendingPathComponent:lastFileName];
}
}
- (NSString*)fileLocation:(FileListNode*)node - (NSString*)fileLocation:(FileListNode*)node
{ {
if (node.isFolder) if (node.isFolder)

View File

@ -46,7 +46,6 @@
// - Callbacks are only on the main thread. // - Callbacks are only on the main thread.
// - Unmaintained as a standalone project. // - Unmaintained as a standalone project.
#warning Adopt an alternative to VDKQueue (UKFSEventsWatcher, EonilFSEvents, FileWatcher, DTFolderMonitor or SFSMonitor)
// ALTERNATIVES (from archaic to modern) // ALTERNATIVES (from archaic to modern)
// //
// - FreeBSD 4.1: Kernel Queue API (kevent and kqueue) // - FreeBSD 4.1: Kernel Queue API (kevent and kqueue)

View File

@ -165,7 +165,7 @@ NSString const* VDKQueueAccessRevocationNotification = @"VDKQueueAccessWasRevoke
} }
} }
- (void)watcherThread:(id)sender - (void)watcherThread:(id)__unused sender
{ {
#if DEBUG_LOG_THREAD_LIFETIME #if DEBUG_LOG_THREAD_LIFETIME
NSLog(@"watcherThread started."); NSLog(@"watcherThread started.");

View File

@ -1,3 +1,4 @@
/* Localized versions of Info.plist keys */ /* Localized versions of Info.plist keys */
NSHumanReadableCopyright = "Copyright © 2005-2024 The Transmission Project"; NSHumanReadableCopyright = "Copyright © 2005-2024 The Transmission Project";
MDItemKeywords = "torrent,torrents,magnet,magnets,tor,bt,bit";

View File

@ -516,9 +516,9 @@ void Session::torrentRenamePath(torrent_ids_t const& torrent_ids, QString const&
q->run(); q->run();
} }
std::vector<std::string_view> const& Session::getKeyNames(TorrentProperties props) std::set<std::string_view> const& Session::getKeyNames(TorrentProperties props)
{ {
std::vector<std::string_view>& names = names_[props]; std::set<std::string_view>& names = names_[props];
if (names.empty()) if (names.empty())
{ {
@ -609,7 +609,7 @@ std::vector<std::string_view> const& Session::getKeyNames(TorrentProperties prop
auto const append = [&names](tr_quark key) auto const append = [&names](tr_quark key)
{ {
names.emplace_back(tr_quark_get_string_view(key)); names.emplace(tr_quark_get_string_view(key));
}; };
switch (props) switch (props)
@ -642,10 +642,6 @@ std::vector<std::string_view> const& Session::getKeyNames(TorrentProperties prop
// must be in every torrent req // must be in every torrent req
append(TR_KEY_id); append(TR_KEY_id);
// sort and remove dupes
std::sort(names.begin(), names.end());
names.erase(std::unique(names.begin(), names.end()), names.end());
} }
return names; return names;

View File

@ -9,6 +9,7 @@
#include <cstdint> // int64_t #include <cstdint> // int64_t
#include <map> #include <map>
#include <optional> #include <optional>
#include <set>
#include <string_view> #include <string_view>
#include <vector> #include <vector>
@ -133,6 +134,26 @@ public:
Rename Rename
}; };
void addKeyName(TorrentProperties props, tr_quark const key)
{
// populate names cache with default values
if (names_[props].empty())
{
getKeyNames(props);
}
names_[props].emplace(tr_quark_get_string_view(key));
}
void removeKeyName(TorrentProperties props, tr_quark const key)
{
// do not remove id because it must be in every torrent req
if (key != TR_KEY_id)
{
names_[props].erase(tr_quark_get_string_view(key));
}
}
public slots: public slots:
void addTorrent(AddData add_me); void addTorrent(AddData add_me);
void launchWebInterface() const; void launchWebInterface() const;
@ -173,7 +194,7 @@ private:
void pumpRequests(); void pumpRequests();
void sendTorrentRequest(std::string_view request, torrent_ids_t const& torrent_ids); void sendTorrentRequest(std::string_view request, torrent_ids_t const& torrent_ids);
void refreshTorrents(torrent_ids_t const& ids, TorrentProperties props); void refreshTorrents(torrent_ids_t const& ids, TorrentProperties props);
std::vector<std::string_view> const& getKeyNames(TorrentProperties props); std::set<std::string_view> const& getKeyNames(TorrentProperties props);
static void updateStats(tr_variant* args_dict, tr_session_stats* stats); static void updateStats(tr_variant* args_dict, tr_session_stats* stats);
@ -182,7 +203,7 @@ private:
QString const config_dir_; QString const config_dir_;
Prefs& prefs_; Prefs& prefs_;
std::map<TorrentProperties, std::vector<std::string_view>> names_; std::map<TorrentProperties, std::set<std::string_view>> names_;
int64_t blocklist_size_ = -1; int64_t blocklist_size_ = -1;
std::array<bool, NUM_PORT_TEST_IP_PROTOCOL> port_test_pending_ = {}; std::array<bool, NUM_PORT_TEST_IP_PROTOCOL> port_test_pending_ = {};

View File

@ -75,6 +75,26 @@ void TorrentFilter::refilter()
**** ****
***/ ***/
namespace
{
int compareState(Torrent const* left, Torrent const* right)
{
if (auto const val = tr_compare_3way(left->hasError(), right->hasError()); val != 0)
{
return val;
}
if (auto const val = tr_compare_3way(left->isFinished(), right->isFinished()); val != 0)
{
return val;
}
if (auto const val = -tr_compare_3way(left->isPaused(), right->isPaused()); val != 0)
{
return val;
}
return -tr_compare_3way(!left->hasMetadata(), !right->hasMetadata());
}
} // namespace
bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right) const bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right) const
{ {
int val = 0; int val = 0;
@ -84,37 +104,30 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
switch (prefs_.get<SortMode>(Prefs::SORT_MODE).mode()) switch (prefs_.get<SortMode>(Prefs::SORT_MODE).mode())
{ {
case SortMode::SORT_BY_QUEUE: case SortMode::SORT_BY_QUEUE:
if (val == 0) val = -tr_compare_3way(a->queuePosition(), b->queuePosition());
{
val = -tr_compare_3way(a->queuePosition(), b->queuePosition());
}
break; break;
case SortMode::SORT_BY_SIZE: case SortMode::SORT_BY_SIZE:
if (val == 0) val = tr_compare_3way(a->sizeWhenDone(), b->sizeWhenDone());
{
val = tr_compare_3way(a->sizeWhenDone(), b->sizeWhenDone());
}
break; break;
case SortMode::SORT_BY_AGE: case SortMode::SORT_BY_AGE:
if (val == 0) val = tr_compare_3way(a->dateAdded(), b->dateAdded());
{
val = tr_compare_3way(a->dateAdded(), b->dateAdded());
}
break; break;
case SortMode::SORT_BY_ID: case SortMode::SORT_BY_ID:
if (val == 0) val = tr_compare_3way(a->id(), b->id());
{
val = tr_compare_3way(a->id(), b->id());
}
break; break;
case SortMode::SORT_BY_ETA:
val = a->compareETA(*b);
[[fallthrough]];
case SortMode::SORT_BY_ACTIVITY: case SortMode::SORT_BY_ACTIVITY:
if (val == 0) if (val == 0)
{ {
@ -124,8 +137,8 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
if (val == 0) if (val == 0)
{ {
val = tr_compare_3way( val = tr_compare_3way(
a->peersWeAreUploadingTo() + a->webseedsWeAreDownloadingFrom(), a->peersWeAreUploadingTo() + a->peersWeAreDownloadingFrom() + a->webseedsWeAreDownloadingFrom(),
b->peersWeAreUploadingTo() + b->webseedsWeAreDownloadingFrom()); b->peersWeAreUploadingTo() + b->peersWeAreDownloadingFrom() + b->webseedsWeAreDownloadingFrom());
} }
[[fallthrough]]; [[fallthrough]];
@ -133,22 +146,7 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
case SortMode::SORT_BY_STATE: case SortMode::SORT_BY_STATE:
if (val == 0) if (val == 0)
{ {
val = -tr_compare_3way(a->isPaused(), b->isPaused()); val = compareState(a, b);
}
if (val == 0)
{
val = tr_compare_3way(a->getActivity(), b->getActivity());
}
if (val == 0)
{
val = -tr_compare_3way(a->queuePosition(), b->queuePosition());
}
if (val == 0)
{
val = tr_compare_3way(a->hasError(), b->hasError());
} }
[[fallthrough]]; [[fallthrough]];
@ -161,12 +159,12 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
if (val == 0) if (val == 0)
{ {
val = tr_compare_3way(a->percentComplete(), b->percentComplete()); val = a->compareSeedProgress(*b);
} }
if (val == 0) if (val == 0)
{ {
val = a->compareSeedProgress(*b); val = tr_compare_3way(a->getActivity(), b->getActivity());
} }
if (val == 0) if (val == 0)
@ -174,9 +172,10 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
val = -tr_compare_3way(a->queuePosition(), b->queuePosition()); val = -tr_compare_3way(a->queuePosition(), b->queuePosition());
} }
[[fallthrough]]; break;
case SortMode::SORT_BY_RATIO: case SortMode::SORT_BY_RATIO:
val = -tr_compare_3way(!a->hasMetadata(), !b->hasMetadata());
if (val == 0) if (val == 0)
{ {
val = a->compareRatio(*b); val = a->compareRatio(*b);
@ -184,14 +183,12 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
break; break;
case SortMode::SORT_BY_ETA: case SortMode::SORT_BY_NAME:
if (val == 0) // nothing to do: sorting by name is done after the switch
{
val = a->compareETA(*b);
}
break; break;
// TODO(coeur): SORT_BY_TRACKER
default: default:
break; break;
} }

View File

@ -84,7 +84,7 @@ TEST_F(AnnouncerTest, parseHttpAnnounceResponsePexCompact)
if (std::size(response.pex) == 1) if (std::size(response.pex) == 1)
{ {
EXPECT_EQ("[127.0.0.1]:64551"sv, response.pex[0].display_name()); EXPECT_EQ("127.0.0.1:64551"sv, response.pex[0].display_name());
} }
} }
@ -123,7 +123,7 @@ TEST_F(AnnouncerTest, parseHttpAnnounceResponsePexList)
if (std::size(response.pex) == 1) if (std::size(response.pex) == 1)
{ {
EXPECT_EQ("[8.8.4.4]:53"sv, response.pex[0].display_name()); EXPECT_EQ("8.8.4.4:53"sv, response.pex[0].display_name());
} }
} }

View File

@ -23,13 +23,13 @@ using CompletionTest = ::testing::Test;
namespace namespace
{ {
struct TestTorrent : public tr_completion::torrent_view struct TestTorrent
{ {
std::set<tr_piece_index_t> dnd_pieces; std::set<tr_piece_index_t> dnd_pieces;
[[nodiscard]] bool piece_is_wanted(tr_piece_index_t piece) const final [[nodiscard]] tr_completion makeCompletion(tr_block_info const& block_info) const
{ {
return dnd_pieces.count(piece) == 0; return { [this](tr_piece_index_t const piece) { return dnd_pieces.count(piece) == 0; }, &block_info };
} }
}; };
@ -41,7 +41,7 @@ TEST_F(CompletionTest, MagnetLink)
{ {
auto torrent = TestTorrent{}; auto torrent = TestTorrent{};
auto block_info = tr_block_info{}; auto block_info = tr_block_info{};
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
EXPECT_FALSE(completion.has_all()); EXPECT_FALSE(completion.has_all());
EXPECT_TRUE(completion.has_none()); EXPECT_TRUE(completion.has_none());
@ -64,7 +64,7 @@ TEST_F(CompletionTest, setBlocks)
auto torrent = TestTorrent{}; auto torrent = TestTorrent{};
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
EXPECT_FALSE(completion.blocks().has_all()); EXPECT_FALSE(completion.blocks().has_all());
EXPECT_FALSE(completion.has_all()); EXPECT_FALSE(completion.has_all());
EXPECT_EQ(0, completion.has_total()); EXPECT_EQ(0, completion.has_total());
@ -85,7 +85,7 @@ TEST_F(CompletionTest, hasBlock)
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 }; auto constexpr TotalSize = uint64_t{ BlockSize * 4096 };
auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
EXPECT_FALSE(completion.has_block(0)); EXPECT_FALSE(completion.has_block(0));
EXPECT_FALSE(completion.has_block(1)); EXPECT_FALSE(completion.has_block(1));
@ -106,7 +106,7 @@ TEST_F(CompletionTest, hasBlocks)
auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
EXPECT_FALSE(completion.has_blocks({ 0, 1 })); EXPECT_FALSE(completion.has_blocks({ 0, 1 }));
EXPECT_FALSE(completion.has_blocks({ 0, 2 })); EXPECT_FALSE(completion.has_blocks({ 0, 2 }));
@ -122,7 +122,7 @@ TEST_F(CompletionTest, hasNone)
auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
EXPECT_TRUE(completion.has_none()); EXPECT_TRUE(completion.has_none());
completion.add_block(0); completion.add_block(0);
@ -137,7 +137,7 @@ TEST_F(CompletionTest, hasPiece)
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
// check that the initial state does not have it // check that the initial state does not have it
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
EXPECT_FALSE(completion.has_piece(0)); EXPECT_FALSE(completion.has_piece(0));
EXPECT_FALSE(completion.has_piece(1)); EXPECT_FALSE(completion.has_piece(1));
EXPECT_EQ(0, completion.has_valid()); EXPECT_EQ(0, completion.has_valid());
@ -174,7 +174,7 @@ TEST_F(CompletionTest, percentCompleteAndDone)
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
// check that in blank-slate initial state, isDone() is false // check that in blank-slate initial state, isDone() is false
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
EXPECT_DOUBLE_EQ(0.0, completion.percent_complete()); EXPECT_DOUBLE_EQ(0.0, completion.percent_complete());
EXPECT_DOUBLE_EQ(0.0, completion.percent_done()); EXPECT_DOUBLE_EQ(0.0, completion.percent_done());
@ -215,7 +215,7 @@ TEST_F(CompletionTest, hasTotalAndValid)
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
// check that the initial blank-slate state has nothing // check that the initial blank-slate state has nothing
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
EXPECT_EQ(0, completion.has_total()); EXPECT_EQ(0, completion.has_total());
EXPECT_EQ(completion.has_valid(), completion.has_total()); EXPECT_EQ(completion.has_valid(), completion.has_total());
@ -253,7 +253,7 @@ TEST_F(CompletionTest, leftUntilDone)
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
// check that the initial blank-slate state has nothing // check that the initial blank-slate state has nothing
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
EXPECT_EQ(block_info.total_size(), completion.left_until_done()); EXPECT_EQ(block_info.total_size(), completion.left_until_done());
// check that adding the final piece adjusts by block_info.final_piece_size // check that adding the final piece adjusts by block_info.final_piece_size
@ -301,7 +301,7 @@ TEST_F(CompletionTest, sizeWhenDone)
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
// check that adding or removing blocks or pieces does not affect sizeWhenDone // check that adding or removing blocks or pieces does not affect sizeWhenDone
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
EXPECT_EQ(block_info.total_size(), completion.size_when_done()); EXPECT_EQ(block_info.total_size(), completion.size_when_done());
completion.add_block(0); completion.add_block(0);
EXPECT_EQ(block_info.total_size(), completion.size_when_done()); EXPECT_EQ(block_info.total_size(), completion.size_when_done());
@ -336,7 +336,7 @@ TEST_F(CompletionTest, createPieceBitfield)
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
// make a completion object that has a random assortment of pieces // make a completion object that has a random assortment of pieces
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
auto buf = tr_rand_obj<std::array<char, 65>>(); auto buf = tr_rand_obj<std::array<char, 65>>();
ASSERT_EQ(std::size(buf), block_info.piece_count()); ASSERT_EQ(std::size(buf), block_info.piece_count());
for (uint64_t i = 0; i < block_info.piece_count(); ++i) for (uint64_t i = 0; i < block_info.piece_count(); ++i)
@ -368,7 +368,7 @@ TEST_F(CompletionTest, countMissingBytesInPiece)
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1; auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1;
auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
EXPECT_EQ(block_info.piece_size(0), completion.count_missing_bytes_in_piece(0)); EXPECT_EQ(block_info.piece_size(0), completion.count_missing_bytes_in_piece(0));
completion.add_block(0); completion.add_block(0);
@ -391,7 +391,7 @@ TEST_F(CompletionTest, amountDone)
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1; auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1;
auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
// make bins s.t. each bin is a single piece // make bins s.t. each bin is a single piece
auto bins = std::array<float, TotalSize / PieceSize>{}; auto bins = std::array<float, TotalSize / PieceSize>{};
@ -435,7 +435,7 @@ TEST_F(CompletionTest, countHasBytesInSpan)
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1; auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1;
auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
// torrent is complete // torrent is complete
auto blocks = tr_bitfield{ block_info.block_count() }; auto blocks = tr_bitfield{ block_info.block_count() };
@ -479,7 +479,7 @@ TEST_F(CompletionTest, wantNone)
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 }; auto constexpr TotalSize = uint64_t{ BlockSize * 4096 };
auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
auto const block_info = tr_block_info{ TotalSize, PieceSize }; auto const block_info = tr_block_info{ TotalSize, PieceSize };
auto completion = tr_completion(&torrent, &block_info); auto completion = torrent.makeCompletion(block_info);
// we have some data // we have some data
completion.add_block(0); completion.add_block(0);

View File

@ -112,6 +112,7 @@ TEST_F(LpdTest, HelloWorld)
EXPECT_EQ(0U, std::size(mediator.found_)); EXPECT_EQ(0U, std::size(mediator.found_));
} }
// TODO(anyone): flaky test should be fixed instead of disabled
TEST_F(LpdTest, DISABLED_CanAnnounceAndRead) TEST_F(LpdTest, DISABLED_CanAnnounceAndRead)
{ {
auto mediator_a = MyMediator{ *session_ }; auto mediator_a = MyMediator{ *session_ };
@ -134,6 +135,7 @@ TEST_F(LpdTest, DISABLED_CanAnnounceAndRead)
EXPECT_EQ(0U, mediator_b.found_.count(info_hash_str)); EXPECT_EQ(0U, mediator_b.found_.count(info_hash_str));
} }
// TODO(anyone): flaky test should be fixed instead of disabled
TEST_F(LpdTest, DISABLED_canMultiAnnounce) TEST_F(LpdTest, DISABLED_canMultiAnnounce)
{ {
auto mediator_a = MyMediator{ *session_ }; auto mediator_a = MyMediator{ *session_ };
@ -170,6 +172,7 @@ TEST_F(LpdTest, DISABLED_canMultiAnnounce)
} }
} }
// TODO(anyone): flaky test should be fixed instead of disabled
TEST_F(LpdTest, DISABLED_DoesNotReannounceTooSoon) TEST_F(LpdTest, DISABLED_DoesNotReannounceTooSoon)
{ {
auto mediator_a = MyMediator{ *session_ }; auto mediator_a = MyMediator{ *session_ };

View File

@ -218,7 +218,9 @@ TEST_F(NetTest, ipCompare)
std::tuple{ "8.8.8.8"sv, "8.8.8.8"sv, 0 }, std::tuple{ "8.8.8.8"sv, "8.8.8.8"sv, 0 },
std::tuple{ "8.8.8.8"sv, "2001:0:0eab:dead::a0:abcd:4e"sv, -1 }, std::tuple{ "8.8.8.8"sv, "2001:0:0eab:dead::a0:abcd:4e"sv, -1 },
std::tuple{ "2001:1890:1112:1::20"sv, "2001:0:0eab:dead::a0:abcd:4e"sv, 1 }, std::tuple{ "2001:1890:1112:1::20"sv, "2001:0:0eab:dead::a0:abcd:4e"sv, 1 },
std::tuple{ "2001:1890:1112:1::20"sv, "2001:1890:1112:1::20"sv, 0 } }; std::tuple{ "2001:1890:1112:1::20"sv, "[2001:0:0eab:dead::a0:abcd:4e]"sv, 1 },
std::tuple{ "2001:1890:1112:1::20"sv, "2001:1890:1112:1::20"sv, 0 },
std::tuple{ "2001:1890:1112:1::20"sv, "[2001:1890:1112:1::20]"sv, 0 } };
for (auto const& [sv1, sv2, res] : IpPairs) for (auto const& [sv1, sv2, res] : IpPairs)
{ {

View File

@ -15,7 +15,7 @@
#include "gtest/gtest.h" #include "gtest/gtest.h"
class tr_peer; struct tr_peer;
class PeerMgrActiveRequestsTest : public ::testing::Test class PeerMgrActiveRequestsTest : public ::testing::Test
{ {

View File

@ -7,8 +7,9 @@
#include <libtransmission/utils.h> // tr_env_get_string() #include <libtransmission/utils.h> // tr_env_get_string()
#include <fmt/core.h> #include <fmt/core.h>
#include <fmt/ostream.h>
#include <cstdio> #include <fstream>
#include <string> #include <string>
int main(int argc, char** argv) int main(int argc, char** argv)
@ -22,8 +23,8 @@ int main(int argc, char** argv)
auto const test_action = std::string{ argv[2] }; auto const test_action = std::string{ argv[2] };
auto const tmp_result_path = result_path + ".tmp"; auto const tmp_result_path = result_path + ".tmp";
FILE* out = std::fopen(tmp_result_path.c_str(), "w+"); auto out = std::ofstream(tmp_result_path.c_str(), std::ios::out | std::ios::trunc | std::ios::binary);
if (out == nullptr) if (!out)
{ {
return 1; return 1;
} }
@ -55,12 +56,12 @@ int main(int argc, char** argv)
} }
else else
{ {
(void)std::fclose(out); out.close();
(void)std::remove(tmp_result_path.c_str()); (void)std::remove(tmp_result_path.c_str());
return 1; return 1;
} }
(void)std::fclose(out); out.close();
tr_sys_path_rename(tmp_result_path.c_str(), result_path.c_str()); tr_sys_path_rename(tmp_result_path.c_str(), result_path.c_str());
return 0; return 0;
} }

View File

@ -177,7 +177,8 @@ TEST_F(TimerTest, repeatingHonorsInterval)
EXPECT_EQ(DesiredLoops, n_calls); EXPECT_EQ(DesiredLoops, n_calls);
} }
TEST_F(TimerTest, restartWithDifferentInterval) // TODO: flaky test should be fixed instead of disabled
TEST_F(TimerTest, DISABLED_restartWithDifferentInterval)
{ {
auto timer_maker = EvTimerMaker{ evbase_.get() }; auto timer_maker = EvTimerMaker{ evbase_.get() };
auto timer = timer_maker.create(); auto timer = timer_maker.create();
@ -206,6 +207,7 @@ TEST_F(TimerTest, restartWithDifferentInterval)
test(200ms); test(200ms);
} }
// TODO: flaky test should be fixed instead of disabled
TEST_F(TimerTest, DISABLED_restartWithSameInterval) TEST_F(TimerTest, DISABLED_restartWithSameInterval)
{ {
auto timer_maker = EvTimerMaker{ evbase_.get() }; auto timer_maker = EvTimerMaker{ evbase_.get() };
@ -235,6 +237,7 @@ TEST_F(TimerTest, DISABLED_restartWithSameInterval)
test(timer->interval()); test(timer->interval());
} }
// TODO: flaky test should be fixed instead of disabled
TEST_F(TimerTest, DISABLED_repeatingThenSingleShot) TEST_F(TimerTest, DISABLED_repeatingThenSingleShot)
{ {
auto timer_maker = EvTimerMaker{ evbase_.get() }; auto timer_maker = EvTimerMaker{ evbase_.get() };
@ -277,6 +280,7 @@ TEST_F(TimerTest, DISABLED_repeatingThenSingleShot)
EXPECT_EQ(baseline + 1, n_calls); EXPECT_EQ(baseline + 1, n_calls);
} }
// TODO: flaky test should be fixed instead of disabled
TEST_F(TimerTest, DISABLED_singleShotStop) TEST_F(TimerTest, DISABLED_singleShotStop)
{ {
auto timer_maker = EvTimerMaker{ evbase_.get() }; auto timer_maker = EvTimerMaker{ evbase_.get() };

View File

@ -226,6 +226,7 @@ TEST_P(WatchDirTest, watch)
EXPECT_TRUE(std::empty(names)); EXPECT_TRUE(std::empty(names));
} }
// TODO(ckerr): flaky test should be fixed instead of disabled
TEST_P(WatchDirTest, DISABLED_retry) TEST_P(WatchDirTest, DISABLED_retry)
{ {
auto const path = sandboxDir(); auto const path = sandboxDir();

View File

@ -126,8 +126,8 @@ TEST_F(WebUtilsTest, urlParse)
url = "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]:8080/announce"sv; url = "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]:8080/announce"sv;
parsed = tr_urlParse(url); parsed = tr_urlParse(url);
EXPECT_EQ("http"sv, parsed->scheme); EXPECT_EQ("http"sv, parsed->scheme);
EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->sitename); EXPECT_EQ("[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]"sv, parsed->sitename);
EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->host); EXPECT_EQ("[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]"sv, parsed->host);
EXPECT_EQ("/announce"sv, parsed->path); EXPECT_EQ("/announce"sv, parsed->path);
EXPECT_EQ(8080, parsed->port); EXPECT_EQ(8080, parsed->port);
@ -135,8 +135,8 @@ TEST_F(WebUtilsTest, urlParse)
url = "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]/announce"sv; url = "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]/announce"sv;
parsed = tr_urlParse(url); parsed = tr_urlParse(url);
EXPECT_EQ("http"sv, parsed->scheme); EXPECT_EQ("http"sv, parsed->scheme);
EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->sitename); EXPECT_EQ("[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]"sv, parsed->sitename);
EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->host); EXPECT_EQ("[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]"sv, parsed->host);
EXPECT_EQ("/announce"sv, parsed->path); EXPECT_EQ("/announce"sv, parsed->path);
EXPECT_EQ(80, parsed->port); EXPECT_EQ(80, parsed->port);

@ -1 +1 @@
Subproject commit 8378916ed814bebc4bcbf519c66f7c38c8374594 Subproject commit 1fc3ac3932220b5effaca7203bb1bb771528d256

@ -1 +1 @@
Subproject commit dd12ff2b36d603dbb7fa8838fe7e7176fcbd4f6f Subproject commit 275aa5141db6eda3587214e0f1d3a134768f557d

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

View File

@ -150,7 +150,7 @@ Get a file list for the current torrent(s)
.It Fl g Fl -get Ar all | file-index | files .It Fl g Fl -get Ar all | file-index | files
Mark file(s) for download. Mark file(s) for download.
.Ar all .Ar all
marks all all of the torrent's files for downloading, marks all of the torrent's files for downloading,
.Ar file-index .Ar file-index
adds a single file to the download list, and adds a single file to the download list, and
.Ar files .Ar files