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
FixNamespaceComments: true
IndentGotoLabels: false
KeepEmptyLinesAtTheStartOfBlocks: true
KeepEmptyLinesAtTheStartOfBlocks: false
QualifierAlignment: Right
SortUsingDeclarations: true
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}/ ."
exit 1
# sanitizer-tests-ubuntu:
# runs-on: ubuntu-22.04
# needs: [ what-to-make ]
# if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
# env:
# NODE_PATH: /usr/lib/nodejs:/usr/share/nodejs
# steps:
# - name: Show Configuration
# run: |
# echo '${{ toJSON(needs) }}'
# echo '${{ toJSON(runner) }}'
# cat /etc/os-release
# - name: Get Dependencies
# run: |
# set -ex
# sudo apt-get update
# sudo apt-get install -y --no-install-recommends \
# ca-certificates \
# clang \
# cmake \
# gettext \
# libcurl4-openssl-dev \
# libdeflate-dev \
# libevent-dev \
# libfmt-dev \
# libminiupnpc-dev \
# libnatpmp-dev \
# libpsl-dev \
# libssl-dev \
# ninja-build \
# npm
# - name: Get Source
# uses: actions/checkout@v4
# with:
# submodules: recursive
# path: src
# - name: Configure
# run: |
# cmake \
# -S src \
# -B obj \
# -G Ninja \
# -DCMAKE_BUILD_TYPE=Debug \
# -DCMAKE_CXX_COMPILER='clang++' \
# -DCMAKE_CXX_FLAGS='-gdwarf-4 -fno-omit-frame-pointer -fsanitize=address,leak,undefined' \
# -DCMAKE_C_COMPILER='clang' \
# -DCMAKE_C_FLAGS='-gdwarf-4 -fno-omit-frame-pointer -fsanitize=address,leak,undefined' \
# -DCMAKE_INSTALL_PREFIX=pfx \
# -DENABLE_CLI=OFF \
# -DENABLE_DAEMON=OFF \
# -DENABLE_GTK=OFF \
# -DENABLE_MAC=OFF \
# -DENABLE_QT=OFF \
# -DENABLE_TESTS=ON \
# -DENABLE_UTILS=ON \
# -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-ubuntu:
runs-on: ubuntu-22.04
needs: [ what-to-make ]
if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
env:
NODE_PATH: /usr/lib/nodejs:/usr/share/nodejs
steps:
- name: Show Configuration
run: |
echo '${{ toJSON(needs) }}'
echo '${{ toJSON(runner) }}'
cat /etc/os-release
- name: Get Dependencies
run: |
set -ex
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
ca-certificates \
clang \
cmake \
gettext \
libcurl4-openssl-dev \
libdeflate-dev \
libevent-dev \
libfmt-dev \
libminiupnpc-dev \
libnatpmp-dev \
libpsl-dev \
libssl-dev \
ninja-build \
npm
- name: Temporary workaround for sanitizer crashes
# https://bugs.launchpad.net/ubuntu/+source/llvm-toolchain-14/+bug/2048768
# https://github.com/actions/runner-images/issues/9491
# https://github.com/actions/runner-images/pull/9513
run: sudo sysctl vm.mmap_rnd_bits=28
- name: Get Source
uses: actions/checkout@v4
with:
submodules: recursive
path: src
- name: Configure
run: |
cmake \
-S src \
-B obj \
-G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_COMPILER='clang++' \
-DCMAKE_CXX_FLAGS='-gdwarf-4 -fno-omit-frame-pointer -fsanitize=address,leak,undefined' \
-DCMAKE_C_COMPILER='clang' \
-DCMAKE_C_FLAGS='-gdwarf-4 -fno-omit-frame-pointer -fsanitize=address,leak,undefined' \
-DCMAKE_INSTALL_PREFIX=pfx \
-DENABLE_CLI=OFF \
-DENABLE_DAEMON=OFF \
-DENABLE_GTK=OFF \
-DENABLE_MAC=OFF \
-DENABLE_QT=OFF \
-DENABLE_TESTS=ON \
-DENABLE_UTILS=ON \
-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:
runs-on: macos-14
@ -272,8 +277,8 @@ jobs:
run: |
if grep 'warning:' makelog; then exit 1; fi
macos-12:
runs-on: macos-12
macos-14-arm64:
runs-on: macos-14
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' }}
steps:
@ -304,14 +309,14 @@ jobs:
-G Ninja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=pfx \
-DCMAKE_OSX_ARCHITECTURES='x86_64' \
-DCMAKE_OSX_ARCHITECTURES='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=${{ (needs.what-to-make.outputs.make-gtk == '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_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' }} \
-DREBUILD_WEB=${{ (needs.what-to-make.outputs.make-web == 'true') && 'ON' || 'OFF' }} \
-DENABLE_WERROR=ON \
@ -330,6 +335,42 @@ jobs:
name: binaries-${{ github.job }}
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:
needs: [ what-to-make ]
runs-on: ubuntu-22.04
@ -478,7 +519,7 @@ jobs:
-DENABLE_GTK=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_TESTS=ON `
-DENABLE_TESTS=${{ (needs.what-to-make.outputs.make-tests == 'true') && 'ON' || 'OFF' }} `
-DENABLE_UTILS=ON `
-DREBUILD_WEB=${{ (needs.what-to-make.outputs.make-web == 'true') && 'ON' || 'OFF' }} `
-DENABLE_WERROR=ON `
@ -544,7 +585,68 @@ jobs:
name: source-tarball
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 ]
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

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 new-style headers, e.g. `<cstring>` over `<string.h>`
- 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:

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_force_generic, false);
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);
}
@ -428,7 +429,6 @@ bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_setting
int c;
char const* optstr;
paused_ = false;
*dump_settings = false;
*foreground = false;
@ -562,7 +562,7 @@ bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_setting
break;
case 800:
paused_ = true;
tr_variantDictAddBool(&settings_, TR_KEY_start_paused, true);
break;
case 910:
@ -768,7 +768,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
auto watchdir = std::unique_ptr<Watchdir>{};
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);
auto dir = std::string_view{};
@ -792,7 +792,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
{
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);
}

View File

@ -46,7 +46,6 @@ private:
#ifdef HAVE_SYS_SIGNALFD_H
int sigfd_ = -1;
#endif /* signalfd API */
bool paused_ = false;
bool seen_hup_ = false;
std::string config_dir_;
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).
* **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).
* **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)
* **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-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-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.
* **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.
* **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)

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)
{
if (response == TR_GTK_RESPONSE_TYPE(CANCEL))
{
close();

View File

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

View File

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

View File

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

View File

@ -16,6 +16,12 @@
#include "libtransmission/block-info.h"
#include "libtransmission/completion.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
{
@ -55,7 +61,7 @@ uint64_t tr_completion::compute_size_when_done() const
auto size = uint64_t{ 0 };
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);
}

View File

@ -10,8 +10,9 @@
#endif
#include <algorithm>
#include <cstdint>
#include <cstddef> // size_t
#include <cstdint>
#include <functional>
#include <optional>
#include <vector>
@ -26,21 +27,18 @@
*/
struct tr_completion
{
struct torrent_view
{
virtual bool piece_is_wanted(tr_piece_index_t piece) const = 0;
using PieceIsWantedFunc = std::function<bool(tr_piece_index_t piece)>;
virtual ~torrent_view() = default;
};
explicit tr_completion(torrent_view const* tor, tr_block_info const* block_info)
: tor_{ tor }
tr_completion(PieceIsWantedFunc&& piece_is_wanted, tr_block_info const* block_info)
: piece_is_wanted_{ std::move(piece_is_wanted) }
, block_info_{ block_info }
, blocks_{ block_info_->block_count() }
{
blocks_.set_has_none();
}
tr_completion(tr_torrent const* tor, tr_block_info const* block_info);
[[nodiscard]] constexpr tr_bitfield const& blocks() const noexcept
{
return blocks_;
@ -175,7 +173,7 @@ private:
void remove_block(tr_block_index_t block);
torrent_view const* tor_;
PieceIsWantedFunc piece_is_wanted_;
tr_block_info const* block_info_;
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 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))
{
// 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())
{
handshake->send_handshake(io);
handshake->set_state(State::AwaitingHandshake);
retry();
return;
}
fail();
return;
}
/* 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())
{
tr_logAddTraceHand(handshake, "handshake failed, trying plaintext...");
handshake->send_handshake(io);
handshake->set_state(State::AwaitingHandshake);
retry();
return;
}
tr_logAddTraceHand(handshake, fmt::format("handshake socket err: {:s} ({:d})", error.message(), error.code()));
handshake->done(false);
fail();
}
// ---

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(!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 {};
}
@ -320,21 +320,16 @@ tr_socket_t tr_netBindTCPImpl(tr_address const& addr, tr_port port, bool suppres
int optval = 1;
(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() &&
(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
if (addr.is_ipv6() && evutil_make_listen_socket_ipv6only(fd) != 0 &&
sockerrno != ENOPROTOOPT) // if the kernel doesn't support it, ignore it
{
*err_out = sockerrno;
tr_net_close_socket(fd);
return TR_BAD_SOCKET;
}
#endif
auto const [sock, addrlen] = tr_socket_address::to_sockaddr(addr, port);
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,
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>{};
auto const loopback_allowed = from == TR_PEER_FROM_INCOMING || from == TR_PEER_FROM_LPD || from == TR_PEER_FROM_RESUME;
switch (addr.type)
{
case TR_AF_INET:
{
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:
{
auto const* const address = reinterpret_cast<unsigned char const*>(&addr.addr.addr6);
return address[0] == 0xFF ||
(memcmp(address, std::data(Zeroes), 15) == 0 && (address[15] == 0 || address[15] == 1));
return address[0] == 0xFF || // multicast address
(std::memcmp(address, std::data(Zeroes), 15) == 0 &&
(address[15] == 0 || // ::
(!loopback_allowed && address[15] == 1)) // ::1
);
}
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 addr = tr_address{};
addr.addr.addr4 = {};
if (evutil_inet_pton(AF_INET, address_sz, &addr.addr.addr4) == 1)
auto ss = sockaddr_storage{};
auto sslen = int{ sizeof(ss) };
if (evutil_parse_sockaddr_port(address_sz, reinterpret_cast<sockaddr*>(&ss), &sslen) != 0)
{
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;
return addr;
}
addr.addr.addr6 = {};
if (evutil_inet_pton(AF_INET6, address_sz, &addr.addr.addr6) == 1)
{
case AF_INET6:
addr.addr.addr6 = reinterpret_cast<sockaddr_in6*>(&ss)->sin6_addr;
addr.type = TR_AF_INET6;
return addr;
}
return {};
default:
return {};
}
}
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
{
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;
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)

View File

@ -60,6 +60,8 @@ using tr_socket_t = int;
#define sockerrno errno
#endif
#include "libtransmission/transmission.h" // tr_peer_from
#include "libtransmission/tr-assert.h"
#include "libtransmission/utils.h" // for tr_compare_3way()
@ -234,7 +236,7 @@ struct tr_address
[[nodiscard]] bool is_global_unicast_address() const noexcept;
tr_address_type type;
tr_address_type type = NUM_TR_AF_INET_TYPES;
union
{
struct in6_addr addr6;
@ -306,7 +308,7 @@ struct tr_socket_address
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
{
@ -402,7 +404,7 @@ struct tr_socket_address
};
template<>
class std::hash<tr_socket_address>
struct std::hash<tr_socket_address>
{
public:
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(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 const func = small::max_size_map<preferred_key_t, std::function<bool()>, TR_NUM_PREFERRED_TRANSPORT>{
{ TR_PREFER_UTP,
@ -250,12 +245,12 @@ bool tr_peerIo::reconnect()
return false;
}
socket_ = tr_netOpenPeerSocket(session_, socket_address(), is_seed());
if (!socket_.is_tcp())
auto sock = tr_netOpenPeerSocket(session_, socket_address(), is_seed());
if (!sock.is_tcp())
{
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_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(); }),
} }
{
rebuild_webseeds();
}
@ -616,7 +615,7 @@ public:
ActiveRequests 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
Peers peers;
@ -646,7 +645,7 @@ private:
webseeds.reserve(n);
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();
@ -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)
{
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))
{
// 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);
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
@ -1829,8 +1828,6 @@ namespace peer_stat_helpers
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(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::end(peers),
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_future.wait();

View File

@ -517,9 +517,14 @@ struct tr_pex
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;

View File

@ -65,10 +65,10 @@ struct tr_upnp
FreeUPNPUrls(&urls);
}
bool hasDiscovered = false;
UPNPUrls urls = {};
IGDdatas data = {};
tr_port port;
tr_port advertised_port;
tr_port local_port;
std::string lanaddr;
bool isMapped = false;
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)
{
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 */
int const err = UPNP_GetSpecificPortMappingEntry(
@ -174,19 +174,25 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
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;
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)
int const err = UPNP_AddPortMapping(
handle->urls.controlURL,
handle->data.first.servicetype,
port_str.c_str(),
port_str.c_str(),
advertised_port_str.c_str(),
local_port_str.c_str(),
handle->lanaddr.c_str(),
desc,
proto,
@ -196,8 +202,8 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
int const err = UPNP_AddPortMapping(
handle->urls.controlURL,
handle->data.first.servicetype,
port_str.c_str(),
port_str.c_str(),
advertised_port_str.c_str(),
local_port_str.c_str(),
handle->lanaddr.c_str(),
desc,
proto,
@ -213,9 +219,9 @@ constexpr auto port_fwd_state(UpnpState upnp_state, bool is_mapped)
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);
}
@ -255,7 +261,13 @@ void tr_upnpClose(tr_upnp* 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)
{
@ -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(_("Local Address is '{address}'"), fmt::arg("address", lanaddr.data())));
handle->state = UpnpState::Idle;
handle->hasDiscovered = true;
handle->lanaddr = std::data(lanaddr);
}
else
@ -295,7 +306,8 @@ tr_port_forwarding_state tr_upnpPulse(tr_upnp* handle, tr_port port, bool is_ena
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;
}
@ -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, "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;
}
if (handle->state == UpnpState::WillUnmap)
{
tr_upnpDeletePortMapping(handle, "TCP", handle->port);
tr_upnpDeletePortMapping(handle, "UDP", handle->port);
tr_upnpDeletePortMapping(handle, "TCP", handle->advertised_port);
tr_upnpDeletePortMapping(handle, "UDP", handle->advertised_port);
tr_logAddInfo(fmt::format(
_("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->state = UpnpState::Idle;
handle->port = {};
handle->advertised_port = {};
handle->local_port = {};
}
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
{
auto const desc = fmt::format("Transmission at {:d}", port.host());
int const err_tcp = upnp_add_port_mapping(handle, "TCP", port, desc.c_str());
int const err_udp = upnp_add_port_mapping(handle, "UDP", port, desc.c_str());
auto const desc = fmt::format("Transmission at {:d}", local_port.host());
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", advertised_port, local_port, desc.c_str());
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("type", handle->data.first.servicetype),
fmt::arg("address", handle->lanaddr),
fmt::arg("port", port.host())));
fmt::arg("port", local_port.host())));
if (handle->isMapped)
{
tr_logAddInfo(fmt::format(_("Port {port} is forwarded"), fmt::arg("port", port.host())));
handle->port = port;
tr_logAddInfo(fmt::format(
_("Forwarded local port {local_port} to {advertised_port}"),
fmt::arg("local_port", local_port.host()),
fmt::arg("advertised_port", advertised_port.host())));
handle->advertised_port = advertised_port;
handle->local_port = local_port;
handle->state = UpnpState::Idle;
}
else
{
tr_logAddInfo(_("If your router supports UPnP, please make sure UPnP is enabled!"));
handle->port = {};
handle->advertised_port = {};
handle->local_port = {};
handle->state = UpnpState::Failed;
}
}

View File

@ -25,6 +25,12 @@ tr_upnp* tr_upnpInit();
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_,
mediator_.advertised_peer_port(),
mediator_.local_peer_port(),
is_enabled,
do_check,

View File

@ -28,6 +28,7 @@ public:
public:
virtual ~Mediator() = default;
[[nodiscard]] virtual tr_port advertised_peer_port() const = 0;
[[nodiscard]] virtual tr_port local_peer_port() const = 0;
[[nodiscard]] virtual tr_address incoming_peer_address() const = 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-minimized"sv,
"startDate"sv,
"start_paused"sv,
"status"sv,
"statusbar-stats"sv,
"tag"sv,

View File

@ -368,6 +368,7 @@ enum
TR_KEY_start_added_torrents,
TR_KEY_start_minimized,
TR_KEY_startDate,
TR_KEY_start_paused,
TR_KEY_status,
TR_KEY_statusbar_stats,
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;
}
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)
{
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 Labels = fields_t{ 1 << 22 };
auto inline constexpr Group = fields_t{ 1 << 23 };
auto inline constexpr SequentialDownload = fields_t{ 1 << 24 };
auto inline constexpr All = ~fields_t{ 0 };

View File

@ -74,7 +74,7 @@ auto constexpr TrUnixSocketPrefix = "unix:"sv;
#ifdef _WIN32
auto inline constexpr TrUnixAddrStrLen = size_t{ INET6_ADDRSTRLEN };
#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) };
#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;
if (auto const* val = var.get_if<int64_t>(); val != nullptr)
if (auto const val = var.value_if<int64_t>())
{
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)
{
@ -741,7 +741,7 @@ char const* torrentGet(tr_session* session, tr_variant::Map const& args_in, tr_v
keys.reserve(n_fields);
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))
{
@ -788,7 +788,7 @@ char const* torrentGet(tr_session* session, tr_variant::Map const& args_in, tr_v
labels.reserve(n_labels);
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);
@ -840,7 +840,7 @@ char const* set_labels(tr_torrent* tor, tr_variant::Vector const& list)
{
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)
{
@ -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)
{
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);
}
@ -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)
{
auto const* id = urls_vec[i].get_if<int64_t>();
auto const* url = urls_vec[i + 1U].get_if<std::string_view>();
auto const id = urls_vec[i].value_if<int64_t>();
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);
}
@ -934,7 +934,7 @@ char const* remove_trackers(tr_torrent* tor, tr_variant::Vector const& 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));
}
@ -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))
{
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))
{
@ -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);
}
@ -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);
}
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);
}
@ -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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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));
}
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));
}
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);
}
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));
}
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));
}
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);
}
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);
}
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);
}
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))
{
@ -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*/)
{
auto const* const location = args_in.find_if<std::string_view>(TR_KEY_location);
if (location == nullptr)
auto const location = args_in.value_if<std::string_view>(TR_KEY_location);
if (!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 };
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)
{
@ -1359,7 +1359,7 @@ bool isCurlURL(std::string_view url)
files.reserve(n_files);
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));
}
@ -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);
}
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);
}
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));
}
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));
}
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);
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);
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);
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);
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);
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);
@ -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)
{
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);
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 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;
}
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;
}
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 };
}
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 };
}
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_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";
}
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);
}
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);
}
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);
}
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);
}
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));
}
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));
}
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));
}
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);
}
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);
}
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);
}
@ -1650,27 +1650,27 @@ char const* sessionSet(tr_session* session, tr_variant::Map const& args_in, tr_v
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));
}
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);
}
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);
}
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);
}
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);
}
@ -1680,135 +1680,135 @@ char const* sessionSet(tr_session* session, tr_variant::Map const& args_in, tr_v
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
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);
}
}
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);
}
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 });
}
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);
}
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 });
}
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);
}
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)
{
@ -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));
}
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);
}
@ -1994,7 +1994,7 @@ namespace session_get_helpers
{
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)
{
@ -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* args_in = &empty_args;
auto method_name = std::string_view{};
auto tag = std::optional<int64_t>{};
if (request_map != nullptr)
{
// 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;
}
// 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;
}
}
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)
{

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 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;
}
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;
}
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 };
}
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 };
}
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_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 (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));
}
@ -736,7 +736,7 @@ void tr_session::initImpl(init_data& data)
setSettings(settings, true);
tr_utpInit(this);
tr_utp_init(this);
/* cleanup */
data.done_cv.notify_one();
@ -1406,7 +1406,7 @@ void tr_session::closeImplPart2(std::promise<void>* closed_promise, std::chrono:
stats().save();
peer_mgr_.reset();
openFiles().close_all();
tr_utpClose(this);
tr_utp_close(this);
this->udp_core_.reset();
// tada we are done!

View File

@ -203,6 +203,11 @@ private:
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
{
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)
{
if (auto val = src.get_if<bool>(); val != nullptr)
if (auto val = src.value_if<bool>())
{
*tgt = *val;
return true;
@ -56,7 +56,7 @@ tr_variant save_bool(bool const& val)
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;
return true;
@ -82,7 +82,7 @@ bool load_encryption_mode(tr_variant const& src, tr_encryption_mode* tgt)
{
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));
@ -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)
{
@ -132,7 +132,7 @@ bool load_log_level(tr_variant const& src, tr_log_level* tgt)
{
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));
@ -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)
{
@ -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)
{
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)
{
@ -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);
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)
{
if (auto val = src.get_if<int64_t>(); val != nullptr)
if (auto val = src.value_if<int64_t>())
{
*tgt = std::chrono::milliseconds(*val);
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)
{
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);
return true;
@ -243,7 +243,7 @@ bool load_preallocation_mode(tr_variant const& src, tr_open_files::Preallocation
{
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));
@ -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)
{
@ -288,7 +288,7 @@ bool load_preferred_transport(tr_variant const& src, tr_preferred_transport* tgt
{
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));
@ -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)
{
@ -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)
{
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);
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)
{
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 };
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)
{
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)
{
@ -381,7 +381,7 @@ bool load_tos_t(tr_variant const& src, tr_tos_t* tgt)
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) };
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;
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));
@ -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)
{

View File

@ -36,14 +36,16 @@ protected:
using Save = tr_variant (*)(T const& src);
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*));
// 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
load_.insert_or_assign(key, [load](tr_variant const& src, void* tgt) { return load(src, static_cast<T*>(tgt)); });
save_.insert_or_assign(key, [save](void const* src) { return save(*static_cast<T const*>(src)); });
load_.insert_or_assign(
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

View File

@ -498,15 +498,18 @@ void tr_torrent::set_unique_queue_position(size_t const new_pos)
{
using namespace queue_helpers;
auto current = size_t{};
auto max_pos = size_t{};
auto const old_pos = queue_position_;
queue_position_ = MaxQueuePosition;
auto& torrents = session->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->mark_changed();
@ -518,10 +521,10 @@ void tr_torrent::set_unique_queue_position(size_t const new_pos)
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();
TR_ASSERT(torrents_are_sorted_by_queue_position(torrents.get_all()));

View File

@ -63,7 +63,7 @@ class RenameTest_singleFilenameTorrent_Test;
} // namespace libtransmission::test
/** @brief Torrent object */
struct tr_torrent final : public tr_completion::torrent_view
struct tr_torrent
{
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)
: 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
[[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);
}
@ -729,6 +729,7 @@ struct tr_torrent final : public tr_completion::torrent_view
{
sequential_download_ = 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:
extern "C"
{
// This function should return true when a node is blacklisted.
// We don't support using a blacklist with the DHT in Transmission,
// since massive (ab)use of this feature could harm the DHT. However,

View File

@ -27,8 +27,9 @@
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)
{
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);
int size = large ? RecvBufferSize : SmallBufferSize;
int rc = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char const*>(&size), sizeof(size));
if (rc < 0)
if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char const*>(&size), sizeof(size)) < 0)
{
tr_logAddDebug(fmt::format("Couldn't set receive buffer: {}", tr_net_strerror(sockerrno)));
}
size = large ? SendBufferSize : SmallBufferSize;
rc = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char const*>(&size), sizeof(size));
if (rc < 0)
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char const*>(&size), sizeof(size)) < 0)
{
tr_logAddDebug(fmt::format("Couldn't set send buffer: {}", tr_net_strerror(sockerrno)));
}
if (large)
{
rc = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char*>(&rbuf), &rbuf_len);
if (rc < 0)
if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<char*>(&rbuf), &rbuf_len) < 0)
{
rbuf = 0;
}
rc = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&sbuf), &sbuf_len);
if (rc < 0)
if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&sbuf), &sbuf_len) < 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 fromlen = socklen_t{ sizeof(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);
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
session->dht_->handle_message(std::data(buf), rc, from_sa, fromlen);
if (got_utp_packet)
{
// 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)
{
if (!session->announcer_udp_->handle_message(std::data(buf), rc))
// 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' (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 (session->allowsUTP() && (session->utp_context != nullptr))
{
if (!tr_utpPacket(std::data(buf), rc, from_sa, fromlen, session))
else if (n_read >= 8 && buf[0] == 0 && buf[1] == 0 && buf[2] == 0 && buf[3] <= 3)
{
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)
{
auto optval = 1;
(void)setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char const*>(&optval), sizeof(optval));
(void)evutil_make_listen_socket_reuseable(sock);
auto const addr = session_.bind_address(TR_AF_INET);
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;
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)
{
auto optval = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char const*>(&optval), sizeof(optval));
(void)evutil_make_listen_socket_reuseable(sock);
(void)evutil_make_listen_socket_ipv6only(sock);
auto const addr = session_.bind_address(TR_AF_INET6);
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;
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_event_.reset(event_new(session_.event_base(), udp6_socket_, EV_READ | EV_PERSIST, event_callback, &session_));
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
{
/* If somebody has disabled µTP, then we still want to run
utp_check_timeouts, in order to let closed sockets finish
gracefully and so on. However, since we're not particularly
interested in that happening in a timely manner, we might as
well use a large timeout. */
// If somebody has disabled µTP, then we still want to run
// utp_check_timeouts, in order to let closed sockets finish
// gracefully and so on. However, since we're not particularly
// interested in that happening in a timely manner, we might as
// well use a large timeout.
static auto constexpr MinInterval = 2s;
static auto constexpr MaxInterval = 3s;
auto const target = MinInterval + random_percent * (MaxInterval - MinInterval);
@ -173,15 +173,12 @@ void timer_callback(void* 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);
restart_timer(session);
}
} // namespace
void tr_utpInit(tr_session* session)
void tr_utp_init(tr_session* session)
{
if (session->utp_context != nullptr)
{
@ -212,17 +209,19 @@ void tr_utpInit(tr_session* 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);
/* 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;
}
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();

View File

@ -18,8 +18,10 @@
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;
}
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>
[[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
: 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)
{
switch (variant_index(var))
{
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;
}
return value_if(var, setme);
}
bool tr_variantGetStrView(tr_variant const* const var, std::string_view* setme)
{
switch (variant_index(var))
{
case tr_variant::StringIndex:
*setme = *var->get_if<tr_variant::StringIndex>();
return true;
default:
return false;
}
return value_if(var, setme);
}
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)
{
switch (variant_index(var))
{
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;
return value_if(var, setme);
}
bool tr_variantGetReal(tr_variant const* const var, double* setme)
{
switch (variant_index(var))
{
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;
}
return value_if(var, setme);
}
bool tr_variantDictFindInt(tr_variant* const var, tr_quark key, int64_t* setme)

View File

@ -158,11 +158,11 @@ public:
}
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 {};
@ -310,6 +310,23 @@ public:
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>
[[nodiscard]] constexpr bool holds_alternative() const noexcept
{
@ -371,6 +388,13 @@ private:
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
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;
if (tr_strv_starts_with(remain, '['))
{
remain.remove_prefix(1); // '['
parsed.host = tr_strv_sep(&remain, ']');
pos = remain.find(']');
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, ':'))
{
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)
{
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)

View File

@ -45,40 +45,45 @@ using namespace libtransmission::Values;
namespace
{
class tr_webseed;
void on_idle(tr_webseed* w);
class tr_webseed_impl;
class tr_webseed_task
{
private:
libtransmission::evhelpers::evbuffer_unique_ptr const content_{ evbuffer_new() };
public:
tr_webseed_task(tr_torrent* tor, tr_webseed* webseed_in, tr_block_span_t blocks_in)
: webseed{ webseed_in }
, session{ tor->session }
, blocks{ blocks_in }
, end_byte{ tor->block_loc(blocks.end - 1).byte + tor->block_size(blocks.end - 1) }
, loc{ tor->block_loc(blocks.begin) }
tr_webseed_task(tr_torrent const& tor, tr_webseed_impl* webseed_in, tr_block_span_t blocks_in)
: blocks{ blocks_in }
, webseed_{ webseed_in }
, session_{ tor.session }
, end_byte_{ tor.block_loc(blocks.end - 1).byte + tor.block_size(blocks.end - 1) }
, loc_{ tor.block_loc(blocks.begin) }
{
evbuffer_add_cb(content_.get(), on_buffer_got_data, this);
}
tr_webseed* const webseed;
[[nodiscard]] auto* content() const
{
return content_.get();
}
tr_session* const session;
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;
void request_next_chunk();
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
{
public:
constexpr void taskStarted() noexcept
constexpr void task_started() noexcept
{
++n_tasks;
}
void taskFinished(bool success)
void task_finished(bool success)
{
if (!success)
{
taskFailed();
task_failed();
}
TR_ASSERT(n_tasks > 0);
--n_tasks;
}
constexpr void gotData() noexcept
constexpr void got_data() noexcept
{
TR_ASSERT(n_tasks > 0);
n_consecutive_failures = 0;
paused_until = 0;
}
[[nodiscard]] size_t slotsAvailable() const noexcept
[[nodiscard]] size_t slots_available() const noexcept
{
if (isPaused())
if (is_paused())
{
return 0;
}
auto const max = maxConnections();
auto const max = max_connections();
if (n_tasks >= max)
{
return 0;
@ -133,17 +138,17 @@ public:
}
private:
[[nodiscard]] bool isPaused() const noexcept
[[nodiscard]] bool is_paused() const noexcept
{
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;
}
void taskFailed()
void task_failed()
{
TR_ASSERT(n_tasks > 0);
@ -153,19 +158,16 @@ private:
}
}
static time_t constexpr TimeoutIntervalSecs = 120;
static size_t constexpr MaxConnections = 4;
static size_t constexpr MaxConsecutiveFailures = MaxConnections;
static auto constexpr TimeoutIntervalSecs = time_t{ 120 };
static auto constexpr MaxConnections = size_t{ 4 };
static auto constexpr MaxConsecutiveFailures = MaxConnections;
size_t n_tasks = 0;
size_t n_consecutive_failures = 0;
time_t paused_until = 0;
};
void task_request_next_chunk(tr_webseed_task* task);
void onBufferGotData(evbuffer* /*buf*/, evbuffer_cb_info const* info, void* vtask);
class tr_webseed final : public tr_peer
class tr_webseed_impl final : public tr_webseed
{
public:
struct RequestLimit
@ -178,42 +180,44 @@ public:
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_peer{ tor }
, torrent_id{ tor.id() }
tr_webseed_impl(tr_torrent& tor_in, std::string_view url, tr_peer_callback_webseed callback_in, void* callback_data_in)
: tr_webseed{ tor_in }
, tor{ tor_in }
, base_url{ url }
, callback{ callback_in }
, callback_data{ callback_data_in }
, idle_timer_{ session->timerMaker().create([this]() { on_idle(this); }) }
, have_{ tor.piece_count() }
, bandwidth_{ &tor.bandwidth() }
, idle_timer_{ session->timerMaker().create([this]() { on_idle(); }) }
, have_{ tor_in.piece_count() }
, bandwidth_{ &tor_in.bandwidth() }
, callback_{ callback_in }
, callback_data_{ callback_data_in }
{
have_.set_has_all();
idle_timer_->start_repeating(IdleTimerInterval);
}
tr_webseed(tr_webseed&&) = delete;
tr_webseed(tr_webseed const&) = delete;
tr_webseed& operator=(tr_webseed&&) = delete;
tr_webseed& operator=(tr_webseed const&) = delete;
tr_webseed_impl(tr_webseed_impl&&) = delete;
tr_webseed_impl(tr_webseed_impl const&) = delete;
tr_webseed_impl& operator=(tr_webseed_impl&&) = delete;
tr_webseed_impl& operator=(tr_webseed_impl const&) = delete;
~tr_webseed() override
~tr_webseed_impl() override
{
// flag all the pending tasks as dead
std::for_each(std::begin(tasks), std::end(tasks), [](auto* task) { task->dead = true; });
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
{
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
{
if (dir == TR_CLIENT_TO_PEER) // blocks we've requested
@ -231,7 +235,7 @@ public:
[[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);
}
@ -244,26 +248,24 @@ public:
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());
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)
{
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
{
auto* const tor = getTorrent();
if (tor == nullptr || !tor->is_running() || tor->is_done())
if (!tor.is_running() || tor.is_done())
{
return;
}
@ -271,23 +273,41 @@ public:
for (auto const *span = block_spans, *end = span + n_spans; span != end; ++span)
{
auto* const task = new tr_webseed_task{ tor, this, *span };
evbuffer_add_cb(task->content(), onBufferGotData, 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
{
auto const n_slots = connection_limiter.slotsAvailable();
auto const n_slots = connection_limiter.slots_available();
if (n_slots == 0)
{
return {};
}
if (auto const* const tor = getTorrent(); tor == nullptr || !tor->is_running() || tor->is_done())
if (!tor.is_running() || tor.is_done())
{
return {};
}
@ -301,16 +321,14 @@ public:
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;
tr_peer_callback_webseed const callback;
void* const callback_data;
ConnectionLimiter connection_limiter;
std::set<tr_webseed_task*> tasks;
@ -323,92 +341,57 @@ private:
tr_bitfield have_;
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:
libtransmission::evhelpers::evbuffer_unique_ptr const content_{ evbuffer_new() };
auto const lock = session_->unique_lock();
public:
write_block_data(
tr_session* session,
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& tor = webseed_->tor;
for (auto* const buf = content();;)
{
}
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);
auto const block_size = tor.block_size(loc_.block);
if (evbuffer_get_length(buf) < block_size)
{
break;
}
if (tor->has_block(task->loc.block))
if (tor.has_block(loc_.block))
{
evbuffer_drain(buf, block_size);
}
else
{
auto block_buf = std::make_unique<Cache::BlockData>(block_size);
evbuffer_remove(task->content(), 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(&write_block_data::write_block_func, data);
auto block_buf = new Cache::BlockData(block_size);
evbuffer_remove(buf, std::data(*block_buf), std::size(*block_buf));
session_->run_in_session_thread(
[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(task->loc.byte == task->end_byte || task->loc.block_offset == 0);
TR_ASSERT(loc_.byte <= end_byte_);
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;
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;
}
auto const lock = task->session->unique_lock();
task->webseed->gotPieceData(n_added);
auto const lock = task->session_->unique_lock();
task->webseed_->got_piece_data(n_added);
}
void on_idle(tr_webseed* webseed)
{
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)
void tr_webseed_task::on_partial_data_fetched(tr_web::FetchResponse const& 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);
@ -453,43 +417,38 @@ void onPartialDataFetched(tr_web::FetchResponse const& web_response)
return;
}
auto* const webseed = task->webseed;
webseed->connection_limiter.taskFinished(success);
if (auto const* const tor = webseed->getTorrent(); tor == nullptr)
{
return;
}
auto* const webseed = task->webseed_;
webseed->connection_limiter.task_finished(success);
if (!success)
{
webseed->publishRejection({ task->loc.block, task->blocks.end });
webseed->publish_rejection({ task->loc_.block, task->blocks.end });
webseed->tasks.erase(task);
delete task;
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.
// That means we've reached the end of a file and need to request
// the next one
task_request_next_chunk(task);
task->request_next_chunk();
return;
}
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);
delete task;
on_idle(webseed);
webseed->on_idle();
}
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;
@ -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* const tor = webseed->getTorrent();
if (tor == nullptr)
{
return;
}
auto const& tor = webseed_->tor;
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 left_in_file = tor->file_size(file_index) - file_offset;
auto const left_in_task = task->end_byte - loc.byte;
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_task = end_byte_ - downloaded_loc.byte;
auto const this_chunk = std::min(left_in_file, left_in_task);
TR_ASSERT(this_chunk > 0U);
webseed->connection_limiter.taskStarted();
webseed_->connection_limiter.task_started();
auto url = tr_urlbuf{};
makeUrl(webseed, tor->file_subpath(file_index), std::back_inserter(url));
auto options = tr_web::FetchOptions{ url.sv(), onPartialDataFetched, task };
makeUrl(webseed_, tor.file_subpath(file_index), std::back_inserter(url));
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.speed_limit_tag = tor->id();
options.buffer = task->content();
tor->session->fetch(std::move(options));
options.speed_limit_tag = tor.id();
options.buffer = content();
tor.session->fetch(std::move(options));
}
} // 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 };
}
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() };
return std::make_unique<tr_webseed_impl>(torrent, url, callback, callback_data);
}

View File

@ -9,6 +9,7 @@
#error only libtransmission should #include this header.
#endif
#include <memory>
#include <string_view>
#include "libtransmission/transmission.h"
@ -17,6 +18,20 @@
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"?>
<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>
<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"/>
</dependencies>
<objects>
@ -13,6 +13,8 @@
<outlet property="fDataLocationField" destination="14" id="48"/>
<outlet property="fDateCreatedField" destination="8" id="47"/>
<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="fRevealDataButton" destination="12" id="49"/>
<outlet property="fSecureField" destination="16" id="44"/>
@ -111,36 +113,6 @@
<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"/>
<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">
<rect key="frame" x="82" y="154" width="264" height="14"/>
<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"/>
</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="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>
<constraints>
<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 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 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="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 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="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"/>
@ -226,9 +247,12 @@
<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="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="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="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="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"/>

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_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"];
tr_variantDictAddBool(&settings, TR_KEY_peer_port_random_on_start, randomPort);
if (!randomPort)

View File

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

View File

@ -16,6 +16,8 @@
@property(nonatomic) IBOutlet NSTextField* fHashField;
@property(nonatomic) IBOutlet NSTextField* fSecureField;
@property(nonatomic) IBOutlet NSTextField* fDataLocationField;
@property(nonatomic) IBOutlet NSTextField* fLastDataLocationField;
@property(nonatomic) IBOutlet NSTextField* fLastDataLabel;
@property(nonatomic) IBOutlet NSTextField* fCreatorField;
@property(nonatomic) IBOutlet NSTextField* fDateCreatedField;
@ -60,10 +62,17 @@
Torrent* torrent = self.fTorrents[0];
NSString* location = torrent.dataLocation;
NSString* lastKnownDataLocation = torrent.lastKnownDataLocation;
self.fDataLocationField.stringValue = location ? location.stringByAbbreviatingWithTildeInPath : @"";
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
@ -83,6 +92,9 @@
- (void)setupInfo
{
self.fLastDataLabel.hidden = YES;
self.fLastDataLocationField.hidden = YES;
if (self.fTorrents.count == 1)
{
Torrent* torrent = self.fTorrents[0];

View File

@ -111,6 +111,7 @@ extern NSString* const kTorrentDidChangeGroupNotification;
@property(nonatomic, readonly) NSString* torrentLocation;
@property(nonatomic, readonly) NSString* dataLocation;
@property(nonatomic, readonly) NSString* lastKnownDataLocation;
- (NSString*)fileLocation:(FileListNode*)node;
- (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
{
if (node.isFolder)

View File

@ -46,7 +46,6 @@
// - Callbacks are only on the main thread.
// - Unmaintained as a standalone project.
#warning Adopt an alternative to VDKQueue (UKFSEventsWatcher, EonilFSEvents, FileWatcher, DTFolderMonitor or SFSMonitor)
// ALTERNATIVES (from archaic to modern)
//
// - 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
NSLog(@"watcherThread started.");

View File

@ -1,3 +1,4 @@
/* Localized versions of Info.plist keys */
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();
}
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())
{
@ -609,7 +609,7 @@ std::vector<std::string_view> const& Session::getKeyNames(TorrentProperties prop
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)
@ -642,10 +642,6 @@ std::vector<std::string_view> const& Session::getKeyNames(TorrentProperties prop
// must be in every torrent req
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;

View File

@ -9,6 +9,7 @@
#include <cstdint> // int64_t
#include <map>
#include <optional>
#include <set>
#include <string_view>
#include <vector>
@ -133,6 +134,26 @@ public:
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:
void addTorrent(AddData add_me);
void launchWebInterface() const;
@ -173,7 +194,7 @@ private:
void pumpRequests();
void sendTorrentRequest(std::string_view request, torrent_ids_t const& torrent_ids);
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);
@ -182,7 +203,7 @@ private:
QString const config_dir_;
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;
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
{
int val = 0;
@ -84,37 +104,30 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
switch (prefs_.get<SortMode>(Prefs::SORT_MODE).mode())
{
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;
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;
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;
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;
case SortMode::SORT_BY_ETA:
val = a->compareETA(*b);
[[fallthrough]];
case SortMode::SORT_BY_ACTIVITY:
if (val == 0)
{
@ -124,8 +137,8 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
if (val == 0)
{
val = tr_compare_3way(
a->peersWeAreUploadingTo() + a->webseedsWeAreDownloadingFrom(),
b->peersWeAreUploadingTo() + b->webseedsWeAreDownloadingFrom());
a->peersWeAreUploadingTo() + a->peersWeAreDownloadingFrom() + a->webseedsWeAreDownloadingFrom(),
b->peersWeAreUploadingTo() + b->peersWeAreDownloadingFrom() + b->webseedsWeAreDownloadingFrom());
}
[[fallthrough]];
@ -133,22 +146,7 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
case SortMode::SORT_BY_STATE:
if (val == 0)
{
val = -tr_compare_3way(a->isPaused(), b->isPaused());
}
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());
val = compareState(a, b);
}
[[fallthrough]];
@ -161,12 +159,12 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
if (val == 0)
{
val = tr_compare_3way(a->percentComplete(), b->percentComplete());
val = a->compareSeedProgress(*b);
}
if (val == 0)
{
val = a->compareSeedProgress(*b);
val = tr_compare_3way(a->getActivity(), b->getActivity());
}
if (val == 0)
@ -174,9 +172,10 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
val = -tr_compare_3way(a->queuePosition(), b->queuePosition());
}
[[fallthrough]];
break;
case SortMode::SORT_BY_RATIO:
val = -tr_compare_3way(!a->hasMetadata(), !b->hasMetadata());
if (val == 0)
{
val = a->compareRatio(*b);
@ -184,14 +183,12 @@ bool TorrentFilter::lessThan(QModelIndex const& left, QModelIndex const& right)
break;
case SortMode::SORT_BY_ETA:
if (val == 0)
{
val = a->compareETA(*b);
}
case SortMode::SORT_BY_NAME:
// nothing to do: sorting by name is done after the switch
break;
// TODO(coeur): SORT_BY_TRACKER
default:
break;
}

View File

@ -84,7 +84,7 @@ TEST_F(AnnouncerTest, parseHttpAnnounceResponsePexCompact)
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)
{
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
{
struct TestTorrent : public tr_completion::torrent_view
struct TestTorrent
{
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 block_info = tr_block_info{};
auto completion = tr_completion(&torrent, &block_info);
auto completion = torrent.makeCompletion(block_info);
EXPECT_FALSE(completion.has_all());
EXPECT_TRUE(completion.has_none());
@ -64,7 +64,7 @@ TEST_F(CompletionTest, setBlocks)
auto torrent = TestTorrent{};
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.has_all());
EXPECT_EQ(0, completion.has_total());
@ -85,7 +85,7 @@ TEST_F(CompletionTest, hasBlock)
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 };
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
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(1));
@ -106,7 +106,7 @@ TEST_F(CompletionTest, hasBlocks)
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
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, 2 }));
@ -122,7 +122,7 @@ TEST_F(CompletionTest, hasNone)
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
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());
completion.add_block(0);
@ -137,7 +137,7 @@ TEST_F(CompletionTest, hasPiece)
auto const block_info = tr_block_info{ TotalSize, PieceSize };
// 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(1));
EXPECT_EQ(0, completion.has_valid());
@ -174,7 +174,7 @@ TEST_F(CompletionTest, percentCompleteAndDone)
auto const block_info = tr_block_info{ TotalSize, PieceSize };
// 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_done());
@ -215,7 +215,7 @@ TEST_F(CompletionTest, hasTotalAndValid)
auto const block_info = tr_block_info{ TotalSize, PieceSize };
// 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(completion.has_valid(), completion.has_total());
@ -253,7 +253,7 @@ TEST_F(CompletionTest, leftUntilDone)
auto const block_info = tr_block_info{ TotalSize, PieceSize };
// 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());
// 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 };
// 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());
completion.add_block(0);
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 };
// 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>>();
ASSERT_EQ(std::size(buf), block_info.piece_count());
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 PieceSize = uint64_t{ BlockSize * 64 };
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));
completion.add_block(0);
@ -391,7 +391,7 @@ TEST_F(CompletionTest, amountDone)
auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1;
auto constexpr PieceSize = uint64_t{ BlockSize * 64 };
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
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 PieceSize = uint64_t{ BlockSize * 64 };
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
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 PieceSize = uint64_t{ BlockSize * 64 };
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
completion.add_block(0);

View File

@ -112,6 +112,7 @@ TEST_F(LpdTest, HelloWorld)
EXPECT_EQ(0U, std::size(mediator.found_));
}
// TODO(anyone): flaky test should be fixed instead of disabled
TEST_F(LpdTest, DISABLED_CanAnnounceAndRead)
{
auto mediator_a = MyMediator{ *session_ };
@ -134,6 +135,7 @@ TEST_F(LpdTest, DISABLED_CanAnnounceAndRead)
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)
{
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)
{
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, "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)
{

View File

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

View File

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

View File

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

View File

@ -226,6 +226,7 @@ TEST_P(WatchDirTest, watch)
EXPECT_TRUE(std::empty(names));
}
// TODO(ckerr): flaky test should be fixed instead of disabled
TEST_P(WatchDirTest, DISABLED_retry)
{
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;
parsed = tr_urlParse(url);
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->host);
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("/announce"sv, parsed->path);
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;
parsed = tr_urlParse(url);
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->host);
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("/announce"sv, parsed->path);
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
Mark file(s) for download.
.Ar all
marks all all of the torrent's files for downloading,
marks all of the torrent's files for downloading,
.Ar file-index
adds a single file to the download list, and
.Ar files