Merge branch 'main' into failed_removal_error

This commit is contained in:
Lucas Clemente Vella 2024-05-10 23:31:49 +01:00
commit 299305e128
128 changed files with 3152 additions and 2540 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

@ -12,19 +12,19 @@ jobs:
what-to-make:
runs-on: ubuntu-22.04
outputs:
make-android: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.android-changed == '1' }}
make-cli: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.cli-changed == '1' }}
make-daemon: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.daemon-changed == '1' }}
make-dist: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.dist-changed == '1' }}
make-android: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.android-changed == '1' || steps.check-diffs.outputs.ci-actions-changed == '1' }}
make-cli: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.cli-changed == '1' || steps.check-diffs.outputs.ci-actions-changed == '1' }}
make-daemon: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.daemon-changed == '1' || steps.check-diffs.outputs.ci-actions-changed == '1' }}
make-dist: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.dist-changed == '1' || steps.check-diffs.outputs.ci-actions-changed == '1' }}
make-docs: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.docs-changed == '1' }}
make-gtk: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.gtk-changed == '1' }}
make-mac: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.mac-changed == '1' }}
make-qt: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.qt-changed == '1' }}
make-source-tarball: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.any-code-changed == '1' }}
make-tests: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.tests-changed == '1' }}
make-utils: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.utils-changed == '1' || steps.check-diffs.outputs.tests-changed == '1' }}
make-gtk: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.gtk-changed == '1' || steps.check-diffs.outputs.ci-actions-changed == '1' }}
make-mac: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.mac-changed == '1' || steps.check-diffs.outputs.ci-actions-changed == '1' }}
make-qt: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.qt-changed == '1' || steps.check-diffs.outputs.ci-actions-changed == '1' }}
make-source-tarball: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.any-code-changed == '1' || steps.check-diffs.outputs.ci-actions-changed == '1' }}
make-tests: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.tests-changed == '1' || steps.check-diffs.outputs.ci-actions-changed == '1' }}
make-utils: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.utils-changed == '1' || steps.check-diffs.outputs.tests-changed == '1' || steps.check-diffs.outputs.ci-actions-changed == '1' }}
make-web: 'false' # this is handled in the webapp workflow
test-style: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.our-code-changed == '1' }}
test-style: ${{ steps.check-main-push.outputs.is-main-push == '1' || steps.check-diffs.outputs.our-code-changed == '1' || steps.check-diffs.outputs.ci-actions-changed == '1' }}
steps:
- name: Check Push to Main Branch
id: check-main-push
@ -54,19 +54,20 @@ jobs:
git diff --exit-code "$MERGE_BASE" -- "$@"
echo "$name-changed=$?" >> "$GITHUB_OUTPUT"
}
get_changes android CMakeLists.txt cmake third-party libtransmission android
get_changes cli CMakeLists.txt cmake Transmission.xcodeproj third-party libtransmission cli
get_changes any-code CMakeLists.txt cmake Transmission.xcodeproj libtransmission cli daemon gtk macosx qt utils tests web third-party
get_changes our-code CMakeLists.txt cmake Transmission.xcodeproj libtransmission cli daemon gtk macosx qt utils tests web
get_changes daemon CMakeLists.txt cmake Transmission.xcodeproj third-party libtransmission daemon
get_changes dist dist release
get_changes docs docs
get_changes gtk CMakeLists.txt cmake third-party libtransmission gtk
get_changes mac CMakeLists.txt cmake Transmission.xcodeproj third-party libtransmission macosx
get_changes qt CMakeLists.txt cmake third-party libtransmission qt
get_changes tests CMakeLists.txt cmake third-party libtransmission utils tests
get_changes utils CMakeLists.txt cmake third-party libtransmission utils
get_changes web CMakeLists.txt cmake third-party libtransmission web
get_changes android CMakeLists.txt cmake third-party libtransmission android
get_changes cli CMakeLists.txt cmake Transmission.xcodeproj third-party libtransmission cli
get_changes any-code CMakeLists.txt cmake Transmission.xcodeproj libtransmission cli daemon gtk macosx qt utils tests web third-party
get_changes our-code CMakeLists.txt cmake Transmission.xcodeproj libtransmission cli daemon gtk macosx qt utils tests web
get_changes daemon CMakeLists.txt cmake Transmission.xcodeproj third-party libtransmission daemon
get_changes dist dist release
get_changes docs docs
get_changes gtk CMakeLists.txt cmake third-party libtransmission gtk
get_changes mac CMakeLists.txt cmake Transmission.xcodeproj third-party libtransmission macosx
get_changes qt CMakeLists.txt cmake third-party libtransmission qt
get_changes tests CMakeLists.txt cmake third-party libtransmission utils tests
get_changes utils CMakeLists.txt cmake third-party libtransmission utils
get_changes web CMakeLists.txt cmake third-party libtransmission web
get_changes ci-actions .github/workflows/actions.yml
cat "$GITHUB_OUTPUT"
code-style:
@ -115,7 +116,7 @@ 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:
sanitizer-tests-ubuntu:
runs-on: ubuntu-22.04
needs: [ what-to-make ]
if: ${{ needs.what-to-make.outputs.make-tests == 'true' }}
@ -146,6 +147,11 @@ jobs:
libssl-dev \
ninja-build \
npm
- name: Temporary workaround for sanitizer crashes
# https://bugs.launchpad.net/ubuntu/+source/llvm-toolchain-14/+bug/2048768
# https://github.com/actions/runner-images/issues/9491
# https://github.com/actions/runner-images/pull/9513
run: sudo sysctl vm.mmap_rnd_bits=28
- name: Get Source
uses: actions/checkout@v4
with:
@ -177,6 +183,48 @@ jobs:
- 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
needs: [ what-to-make ]
if: ${{ needs.what-to-make.outputs.make-tests == '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:
submodules: recursive
path: src
- name: Configure
run: |
cmake \
-S src \
-B obj \
-G Ninja \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS='-gdwarf-4 -fno-omit-frame-pointer -fsanitize=address,undefined' \
-DCMAKE_C_FLAGS='-gdwarf-4 -fno-omit-frame-pointer -fsanitize=address,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
clang-tidy-libtransmission:
runs-on: ubuntu-22.04
needs: [ what-to-make ]
@ -229,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:
@ -261,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=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 \
@ -287,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
@ -387,7 +471,7 @@ jobs:
nodejs
& "C:\Program Files\OpenSSL\unins000.exe" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- | Out-Host
(Join-Path $Env:ProgramFiles NASM) | Out-File $Env:GITHUB_PATH -Append
(Join-Path ${Env:ProgramFiles(x86)} 'WiX Toolset v3.11' bin) | Out-File $Env:GITHUB_PATH -Append
(Join-Path (Get-Item -Path "${Env:ProgramFiles(x86)}\WiX Toolset v3.*")[0].FullName bin) | Out-File $Env:GITHUB_PATH -Append
Install-Module -Name Pscx -RequiredVersion 4.0.0-beta4 -AllowPrerelease -Force
- name: Get Source
@ -435,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 `
@ -501,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

37
.gitmodules vendored
View File

@ -1,59 +1,64 @@
[submodule "third-party/dht"]
path = third-party/dht
url = https://github.com/transmission/dht
url = https://github.com/transmission/dht.git
branch = post-0.27-transmission
[submodule "third-party/libb64"]
path = third-party/libb64
url = https://github.com/transmission/libb64
url = https://github.com/transmission/libb64.git
branch = post-1.2.1-transmission
[submodule "third-party/libevent"]
path = third-party/libevent
url = https://github.com/transmission/libevent
url = https://github.com/transmission/libevent.git
branch = post-2.0.22-transmission
[submodule "third-party/libnatpmp"]
path = third-party/libnatpmp
url = https://github.com/transmission/libnatpmp
url = https://github.com/transmission/libnatpmp.git
branch = post-20151025-transmission
[submodule "third-party/libutp"]
path = third-party/libutp
url = https://github.com/transmission/libutp
url = https://github.com/transmission/libutp.git
branch = post-3.4-transmission
[submodule "third-party/miniupnpc"]
path = third-party/miniupnpc
url = https://github.com/transmission/miniupnpc
branch = post-2.0.20170509-transmission
[submodule "third-party/miniupnp"]
path = third-party/miniupnp
# synced with https://github.com/miniupnp/miniupnp.git
url = https://github.com/transmission/miniupnp.git
[submodule "third-party/googletest"]
path = third-party/googletest
url = https://github.com/google/googletest.git
[submodule "third-party/utfcpp"]
branch = post-3.2.1-transmission
path = third-party/utfcpp
url = https://github.com/transmission/utfcpp
# synced with https://github.com/nemtrif/utfcpp.git
url = https://github.com/transmission/utfcpp.git
[submodule "third-party/libdeflate"]
path = third-party/libdeflate
url = https://github.com/transmission/libdeflate
branch = v1.17.x
# synced with https://github.com/ebiggers/libdeflate.git
url = https://github.com/transmission/libdeflate.git
[submodule "third-party/libpsl"]
path = third-party/libpsl
url = https://github.com/transmission/libpsl.git
branch = post-3.0.0-transmission
[submodule "third-party/fmt"]
path = third-party/fmt
# synced with https://github.com/fmtlib/fmt.git
url = https://github.com/transmission/fmt.git
branch = 9-x-y
[submodule "third-party/fast_float"]
path = third-party/fast_float
url = https://github.com/transmission/fast_float
# synced with https://github.com/fastfloat/fast_float.git
url = https://github.com/transmission/fast_float.git
[submodule "third-party/wide-integer"]
path = third-party/wide-integer
url = https://github.com/transmission/wide-integer
# synced with https://github.com/ckormanyos/wide-integer.git
url = https://github.com/transmission/wide-integer.git
[submodule "third-party/small"]
path = third-party/small
# synced with https://github.com/alandefreitas/small.git
url = https://github.com/transmission/small.git
[submodule "third-party/rapidjson"]
path = third-party/rapidjson
# synced with https://github.com/Tencent/rapidjson.git
url = https://github.com/transmission/rapidjson.git
fetchRecurseSubmodules = false
[submodule "third-party/rpavlik-cmake-modules"]
path = third-party/rpavlik-cmake-modules
# synced with https://github.com/rpavlik/cmake-modules.git
url = https://github.com/transmission/rpavlik-cmake-modules.git

View File

@ -11,7 +11,6 @@ Contributor Team
Dmitry Serov (@DevilDimon) (macOS client)
Dzmitry Neviadomski (@nevack) (macOS client)
FX Coudert (@fxcoudert) (macOS client)
Gary Elshaw (@GaryElshaw)
John Clay (@JohnClay)
SweetP Pro (@sweetppro) (macOS client)
Yat Ho (@tearfur) (libtransmission)

View File

@ -109,6 +109,19 @@ else()
set(TR_STABLE_RELEASE 1)
endif()
# derived from above: CFBundleVersion
# note: CFBundleVersion only honors 3 numbers, so third number has to hold both patch and beta info.
# 5.0.1-dev -> 14719.0.100
# 5.0.1-beta.1 -> 14719.0.101
# 5.0.1 -> 14719.0.199
math(EXPR CFBUNDLE_1 "${TR_VERSION_MAJOR} + 14714")
math(EXPR CFBUNDLE_2 "${TR_VERSION_MINOR}")
math(EXPR CFBUNDLE_3 "${TR_VERSION_PATCH} * 100 + 0${TR_STABLE_RELEASE} * 99 + 0${TR_VERSION_BETA_NUMBER}")
set(CFBUNDLE_VERSION "${CFBUNDLE_1}.${CFBUNDLE_2}.${CFBUNDLE_3}")
unset(CFBUNDLE_1)
unset(CFBUNDLE_2)
unset(CFBUNDLE_3)
# derived from above: semver version string. https://semver.org/
# '4.0.0-beta.1'
# '4.0.0-beta.1.dev' (a dev release between beta 1 and 2)
@ -497,7 +510,13 @@ if(NOT USE_SYSTEM_NATPMP)
NATPMP_STATICLIB)
endif()
tr_add_external_auto_library(MINIUPNPC miniupnpc miniupnpc
if(WIN32)
# https://github.com/miniupnp/miniupnp/pull/304
set(TR_MINIUPNPC_LIBNAME libminiupnpc)
else()
set(TR_MINIUPNPC_LIBNAME miniupnpc)
endif()
tr_add_external_auto_library(MINIUPNPC miniupnp/miniupnpc ${TR_MINIUPNPC_LIBNAME}
TARGET miniupnpc::libminiupnpc
CMAKE_ARGS
-DUPNPC_BUILD_STATIC=ON
@ -508,9 +527,10 @@ if(NOT USE_SYSTEM_MINIUPNPC)
INTERFACE
MINIUPNP_STATICLIB)
set(MINIUPNPC_VERSION 1.9)
set(MINIUPNPC_API_VERSION 12)
set(MINIUPNPC_VERSION 2.2)
set(MINIUPNPC_API_VERSION 17)
endif()
unset(TR_MINIUPNPC_LIBNAME)
target_compile_definitions(miniupnpc::libminiupnpc
INTERFACE
@ -630,7 +650,6 @@ else()
-Wextra
-Wcast-align
-Wduplicated-cond
-Wexit-time-destructors
-Wextra-semi
-Wextra-semi-stmt
-Wextra-tokens

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

@ -403,6 +403,7 @@
C83B17212B7341BC00B2EAE4 /* tr-assert.cc in Sources */ = {isa = PBXBuildFile; fileRef = C1425B321EE9C5EA001DB85F /* tr-assert.cc */; };
C841A28129197724009F18E8 /* NSKeyedUnarchiverAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = C841A28029197724009F18E8 /* NSKeyedUnarchiverAdditions.mm */; };
C843FC8429C51B9400491854 /* utils.mm in Sources */ = {isa = PBXBuildFile; fileRef = C843FC8329C51B9400491854 /* utils.mm */; };
C843FC8729C8B40800491854 /* VersionComparator.mm in Sources */ = {isa = PBXBuildFile; fileRef = C843FC8629C8B40800491854 /* VersionComparator.mm */; };
C86BCD9928228A9600F45599 /* SparkleProxy.mm in Sources */ = {isa = PBXBuildFile; fileRef = C86BCD9828228A9600F45599 /* SparkleProxy.mm */; };
C87369652809984200573C90 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C87369642809984200573C90 /* UserNotifications.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
C8748D8A29891EA100D9E979 /* suffixes_dafsa.h in Headers */ = {isa = PBXBuildFile; fileRef = C8748D8929891EA100D9E979 /* suffixes_dafsa.h */; };
@ -434,6 +435,8 @@
C8B27BA328153F6300A22B5D /* create.cc in Sources */ = {isa = PBXBuildFile; fileRef = C887BEC02807FCE900867D3C /* create.cc */; };
C8B27BA428153F6600A22B5D /* edit.cc in Sources */ = {isa = PBXBuildFile; fileRef = C887BEC22807FCE900867D3C /* edit.cc */; };
C8B27BA528153F6900A22B5D /* show.cc in Sources */ = {isa = PBXBuildFile; fileRef = C887BEC32807FCE900867D3C /* show.cc */; };
C8ED0FB1281C10F100B44472 /* addr_is_reserved.c in Sources */ = {isa = PBXBuildFile; fileRef = C8ED0FAF281C10F100B44472 /* addr_is_reserved.c */; };
C8ED0FB2281C10F100B44472 /* addr_is_reserved.h in Headers */ = {isa = PBXBuildFile; fileRef = C8ED0FB0281C10F100B44472 /* addr_is_reserved.h */; };
CAB35C64252F6F5E00552A55 /* mime-types.h in Headers */ = {isa = PBXBuildFile; fileRef = CAB35C62252F6F5E00552A55 /* mime-types.h */; };
CCEBA596277340F6DF9F4480 /* session-alt-speeds.cc in Sources */ = {isa = PBXBuildFile; fileRef = CCEBA596277340F6DF9F4481 /* session-alt-speeds.cc */; };
CCEBA596277340F6DF9F4482 /* session-alt-speeds.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEBA596277340F6DF9F4483 /* session-alt-speeds.h */; };
@ -452,6 +455,7 @@
ED8A16402735A8AA000D61F9 /* peer-mgr-active-requests.cc in Sources */ = {isa = PBXBuildFile; fileRef = ED8A163C2735A8AA000D61F9 /* peer-mgr-active-requests.cc */; };
ED8A16412735A8AA000D61F9 /* peer-mgr-wishlist.h in Headers */ = {isa = PBXBuildFile; fileRef = ED8A163D2735A8AA000D61F9 /* peer-mgr-wishlist.h */; };
ED8A16422735A8AA000D61F9 /* peer-mgr-wishlist.cc in Sources */ = {isa = PBXBuildFile; fileRef = ED8A163E2735A8AA000D61F9 /* peer-mgr-wishlist.cc */; };
ED9862972B979AA2002F3035 /* Utils.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED9862962B979AA2002F3035 /* Utils.mm */; };
EDBAAC8C29E486BC00D9495F /* global-ip-cache.h in Headers */ = {isa = PBXBuildFile; fileRef = EDBAAC8B29E486BC00D9495F /* global-ip-cache.h */; };
EDBAAC8E29E486C200D9495F /* global-ip-cache.cc in Sources */ = {isa = PBXBuildFile; fileRef = EDBAAC8D29E486C200D9495F /* global-ip-cache.cc */; };
EDBDFA9E25AFCCA60093D9C1 /* evutil_time.c in Sources */ = {isa = PBXBuildFile; fileRef = EDBDFA9D25AFCCA60093D9C1 /* evutil_time.c */; };
@ -1335,6 +1339,8 @@
C841A27F29197724009F18E8 /* NSKeyedUnarchiverAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSKeyedUnarchiverAdditions.h; sourceTree = "<group>"; };
C841A28029197724009F18E8 /* NSKeyedUnarchiverAdditions.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NSKeyedUnarchiverAdditions.mm; sourceTree = "<group>"; };
C843FC8329C51B9400491854 /* utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = utils.mm; sourceTree = "<group>"; };
C843FC8529C8B40800491854 /* VersionComparator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VersionComparator.h; sourceTree = "<group>"; };
C843FC8629C8B40800491854 /* VersionComparator.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VersionComparator.mm; sourceTree = "<group>"; };
C86BCD9828228A9600F45599 /* SparkleProxy.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SparkleProxy.mm; sourceTree = "<group>"; };
C87369642809984200573C90 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };
C8748D8929891EA100D9E979 /* suffixes_dafsa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = suffixes_dafsa.h; path = "third-party/suffixes_dafsa.h"; sourceTree = SOURCE_ROOT; };
@ -1346,6 +1352,8 @@
C8B27B7F28153F2B00A22B5D /* transmission-create */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "transmission-create"; sourceTree = BUILT_PRODUCTS_DIR; };
C8B27B9028153F3100A22B5D /* transmission-edit */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "transmission-edit"; sourceTree = BUILT_PRODUCTS_DIR; };
C8B27BA128153F3400A22B5D /* transmission-show */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "transmission-show"; sourceTree = BUILT_PRODUCTS_DIR; };
C8ED0FAF281C10F100B44472 /* addr_is_reserved.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = addr_is_reserved.c; sourceTree = "<group>"; };
C8ED0FB0281C10F100B44472 /* addr_is_reserved.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = addr_is_reserved.h; sourceTree = "<group>"; };
CAB35C62252F6F5E00552A55 /* mime-types.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "mime-types.h"; sourceTree = "<group>"; };
CCEBA596277340F6DF9F4481 /* session-alt-speeds.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "session-alt-speeds.cc"; sourceTree = "<group>"; };
CCEBA596277340F6DF9F4483 /* session-alt-speeds.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = "session-alt-speeds.h"; sourceTree = "<group>"; };
@ -1366,6 +1374,8 @@
ED8A163C2735A8AA000D61F9 /* peer-mgr-active-requests.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "peer-mgr-active-requests.cc"; sourceTree = "<group>"; };
ED8A163D2735A8AA000D61F9 /* peer-mgr-wishlist.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "peer-mgr-wishlist.h"; sourceTree = "<group>"; };
ED8A163E2735A8AA000D61F9 /* peer-mgr-wishlist.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "peer-mgr-wishlist.cc"; sourceTree = "<group>"; };
ED9862952B979AA2002F3035 /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utils.h; sourceTree = "<group>"; };
ED9862962B979AA2002F3035 /* Utils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Utils.mm; sourceTree = "<group>"; };
EDBAAC8B29E486BC00D9495F /* global-ip-cache.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "global-ip-cache.h"; sourceTree = "<group>"; };
EDBAAC8D29E486C200D9495F /* global-ip-cache.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "global-ip-cache.cc"; sourceTree = "<group>"; };
EDBDFA9D25AFCCA60093D9C1 /* evutil_time.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = evutil_time.c; sourceTree = "<group>"; };
@ -1566,6 +1576,8 @@
C86BCD9828228A9600F45599 /* SparkleProxy.mm */,
4DF0C5AA0899190500DD8943 /* Controller.h */,
4DF0C5A90899190500DD8943 /* Controller.mm */,
C843FC8529C8B40800491854 /* VersionComparator.h */,
C843FC8629C8B40800491854 /* VersionComparator.mm */,
4DFBC2DD09C0970D00D5C571 /* Torrent.h */,
4DFBC2DE09C0970D00D5C571 /* Torrent.mm */,
A27F0F310E19AD9800B2DB97 /* TorrentGroup.h */,
@ -1664,6 +1676,8 @@
A222EA7A0E6C32C4009FB003 /* BlocklistScheduler.mm */,
ED86936D2ADAE34D00342B1A /* DefaultAppHelper.h */,
ED86936E2ADAE34D00342B1A /* DefaultAppHelper.mm */,
ED9862952B979AA2002F3035 /* Utils.h */,
ED9862962B979AA2002F3035 /* Utils.mm */,
A2AB883916A399A6008FAD50 /* VDKQueue */,
);
name = Sources;
@ -2087,39 +2101,12 @@
BE1183410CE15DF00002D0F3 /* libminiupnp */ = {
isa = PBXGroup;
children = (
A22B00AE116A9E90003315FC /* connecthostport.c */,
BE1183610CE160D50002D0F3 /* igd_desc_parse.c */,
BE1183620CE160D50002D0F3 /* minixml.c */,
BE1183630CE160D50002D0F3 /* miniwget.c */,
BE1183640CE160D50002D0F3 /* minissdpc.c */,
BE1183650CE160D50002D0F3 /* minisoap.c */,
BE1183660CE160D50002D0F3 /* upnpreplyparse.c */,
BE1183670CE160D50002D0F3 /* upnpcommands.c */,
BE1183680CE160D50002D0F3 /* miniupnpc.c */,
A20162CB13DE497000E15488 /* portlistingparse.c */,
A20162C713DE48BF00E15488 /* receivedata.c */,
C1BF7BA71F2A3CB7008E88A7 /* upnpdev.c */,
C12F19771E1AE3C30005E93F /* upnperrors.c */,
A22B00AF116A9E90003315FC /* connecthostport.h */,
BE11834E0CE160C50002D0F3 /* miniupnpc_declspec.h */,
BE11834F0CE160C50002D0F3 /* igd_desc_parse.h */,
BE1183500CE160C50002D0F3 /* minixml.h */,
BE1183510CE160C50002D0F3 /* miniwget.h */,
BE1183520CE160C50002D0F3 /* minisoap.h */,
A2F8CD420F3D0F4A00DB356A /* miniupnpcstrings.h */,
A20162CF13DE49E500E15488 /* miniupnpctypes.h */,
BE1183530CE160C50002D0F3 /* upnpreplyparse.h */,
BE1183540CE160C50002D0F3 /* upnpcommands.h */,
BE1183550CE160C50002D0F3 /* miniupnpc.h */,
BE1183560CE160C50002D0F3 /* minissdpc.h */,
A25485390EB66CBB004539DA /* codelength.h */,
A20162CC13DE497000E15488 /* portlistingparse.h */,
A20162C813DE48BF00E15488 /* receivedata.h */,
C1BF7BA91F2A3CCE008E88A7 /* upnpdev.h */,
C12F197A1E1AE4460005E93F /* upnperrors.h */,
C891A007281C02F3002E745F /* include */,
C8734FB02B9EA39F00EF2AD9 /* src */,
);
name = libminiupnp;
path = "third-party/miniupnpc";
path = "third-party/miniupnp/miniupnpc";
sourceTree = "<group>";
};
BE75C3570C72A0D600DBEFE0 /* libevent */ = {
@ -2274,6 +2261,51 @@
name = Compatibility;
sourceTree = "<group>";
};
C8734FB02B9EA39F00EF2AD9 /* src */ = {
isa = PBXGroup;
children = (
C8ED0FAF281C10F100B44472 /* addr_is_reserved.c */,
A22B00AE116A9E90003315FC /* connecthostport.c */,
BE1183610CE160D50002D0F3 /* igd_desc_parse.c */,
BE1183650CE160D50002D0F3 /* minisoap.c */,
BE1183640CE160D50002D0F3 /* minissdpc.c */,
BE1183680CE160D50002D0F3 /* miniupnpc.c */,
BE1183630CE160D50002D0F3 /* miniwget.c */,
BE1183620CE160D50002D0F3 /* minixml.c */,
A20162CB13DE497000E15488 /* portlistingparse.c */,
A20162C713DE48BF00E15488 /* receivedata.c */,
BE1183670CE160D50002D0F3 /* upnpcommands.c */,
C1BF7BA71F2A3CB7008E88A7 /* upnpdev.c */,
C12F19771E1AE3C30005E93F /* upnperrors.c */,
BE1183660CE160D50002D0F3 /* upnpreplyparse.c */,
C8ED0FB0281C10F100B44472 /* addr_is_reserved.h */,
A25485390EB66CBB004539DA /* codelength.h */,
A22B00AF116A9E90003315FC /* connecthostport.h */,
BE1183520CE160C50002D0F3 /* minisoap.h */,
BE1183560CE160C50002D0F3 /* minissdpc.h */,
BE1183500CE160C50002D0F3 /* minixml.h */,
A20162C813DE48BF00E15488 /* receivedata.h */,
);
path = src;
sourceTree = "<group>";
};
C891A007281C02F3002E745F /* include */ = {
isa = PBXGroup;
children = (
BE11834F0CE160C50002D0F3 /* igd_desc_parse.h */,
BE11834E0CE160C50002D0F3 /* miniupnpc_declspec.h */,
BE1183550CE160C50002D0F3 /* miniupnpc.h */,
A20162CF13DE49E500E15488 /* miniupnpctypes.h */,
BE1183510CE160C50002D0F3 /* miniwget.h */,
A20162CC13DE497000E15488 /* portlistingparse.h */,
BE1183540CE160C50002D0F3 /* upnpcommands.h */,
C1BF7BA91F2A3CCE008E88A7 /* upnpdev.h */,
C12F197A1E1AE4460005E93F /* upnperrors.h */,
BE1183530CE160C50002D0F3 /* upnpreplyparse.h */,
);
path = include;
sourceTree = "<group>";
};
E1B6FBF80C0D719B0015FE4D /* Info Window */ = {
isa = PBXGroup;
children = (
@ -2465,6 +2497,7 @@
BE11835D0CE160C50002D0F3 /* upnpreplyparse.h in Headers */,
C1BF7BAA1F2A3CCE008E88A7 /* upnpdev.h in Headers */,
BE1183600CE160C50002D0F3 /* minissdpc.h in Headers */,
C8ED0FB2281C10F100B44472 /* addr_is_reserved.h in Headers */,
A254853C0EB66CD4004539DA /* codelength.h in Headers */,
A2F8CD430F3D0F4A00DB356A /* miniupnpcstrings.h in Headers */,
A22B00B2116A9E9F003315FC /* connecthostport.h in Headers */,
@ -3022,15 +3055,16 @@
files = (
);
inputPaths = (
"third-party/miniupnpc/VERSION",
"third-party/miniupnpc/miniupnpcstrings.h.in",
"third-party/miniupnp/miniupnpc/VERSION",
"third-party/miniupnp/miniupnpc/miniupnpcstrings.h.in",
"third-party/miniupnp/miniupnpc/updateminiupnpcstrings.sh",
);
outputPaths = (
"third-party/miniupnpc/miniupnpcstrings.h",
"third-party/miniupnp/miniupnpc/miniupnpcstrings.h",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd third-party/miniupnpc\nsh updateminiupnpcstrings.sh \"$SCRIPT_INPUT_FILE_0\" \"$SCRIPT_INPUT_FILE_1\" \"$SCRIPT_OUTPUT_FILE_0\"\n";
shellScript = "cd third-party/miniupnp/miniupnpc\nsh updateminiupnpcstrings.sh\n";
};
BE75C3510C729EE100DBEFE0 /* Copy libevent headers */ = {
isa = PBXShellScriptBuildPhase;
@ -3062,7 +3096,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd third-party/miniupnpc && rm -f miniupnp && ln -s . miniupnp\n";
shellScript = "cd third-party/miniupnp && rm -f miniupnp && ln -s . miniupnp\n";
};
C12F197E1E1AE6D50005E93F /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
@ -3274,6 +3308,7 @@
A209EB9D1142E59A002B02D1 /* InfoPeersViewController.mm in Sources */,
A209EBCE1142F2B4002B02D1 /* InfoFileViewController.mm in Sources */,
A209EBF91142FEEE002B02D1 /* InfoOptionsViewController.mm in Sources */,
ED9862972B979AA2002F3035 /* Utils.mm in Sources */,
A21F15AC11729A8B00CF5A9C /* AddMagnetWindowController.mm in Sources */,
A2661D6112D0E8D9004F69D5 /* FilterBarView.mm in Sources */,
A2F7CF5F13035FFD0016FF10 /* URLSheetWindowController.mm in Sources */,
@ -3282,6 +3317,7 @@
A2B5B4E91880665E0071A66A /* ShareTorrentFileHelper.mm in Sources */,
A22BAE281388040500FB022F /* NSMutableArrayAdditions.mm in Sources */,
A2966E8713DAF74C007B52DF /* GlobalOptionsPopoverViewController.mm in Sources */,
C843FC8729C8B40800491854 /* VersionComparator.mm in Sources */,
A234EA541453563B000F3E97 /* NSImageAdditions.mm in Sources */,
A2AB883E16A399A6008FAD50 /* VDKQueue.mm in Sources */,
4534164229B0EA8600F544C9 /* SmallTorrentCell.mm in Sources */,
@ -3334,6 +3370,7 @@
C12F19791E1AE3C30005E93F /* upnperrors.c in Sources */,
BE11836E0CE160D50002D0F3 /* upnpreplyparse.c in Sources */,
C1BF7BA81F2A3CB7008E88A7 /* upnpdev.c in Sources */,
C8ED0FB1281C10F100B44472 /* addr_is_reserved.c in Sources */,
BE11836F0CE160D50002D0F3 /* upnpcommands.c in Sources */,
BE1183700CE160D50002D0F3 /* miniupnpc.c in Sources */,
A22B00B3116A9EA4003315FC /* connecthostport.c in Sources */,
@ -3930,6 +3967,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"third-party/dht",
@ -4184,6 +4222,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"third-party/dht",
@ -4359,6 +4398,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = dht;
@ -4369,6 +4409,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = dht;
@ -4379,6 +4420,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_STRICT_PROTOTYPES = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
GENERATE_MASTER_OBJECT_FILE = YES;
PRODUCT_NAME = dht;
@ -4513,6 +4555,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_CXX_LIBRARY = "libc++";
GCC_WARN_64_TO_32_BIT_CONVERSION = NO;
HEADER_SEARCH_PATHS = (
"$(inherited)",
"third-party/dht",

View File

@ -61,7 +61,7 @@ for:
choco install python3 --pre
choco install nasm
choco install jom
choco install wixtoolset --version 3.11.2
choco install wixtoolset --version 3.14.0
choco install ccache --version 4.8.3
Remove-Item -Path (Join-Path $Env:SystemDrive OpenSSL-Win32) -Recurse
@ -74,7 +74,7 @@ for:
$Env:PATH = @(
(Join-Path $Env:SystemDrive Strawberry perl bin)
(Join-Path $Env:ProgramFiles NASM)
(Join-Path ${Env:ProgramFiles(x86)} 'WiX Toolset v3.11' bin)
(Join-Path (Get-Item -Path "${Env:ProgramFiles(x86)}\WiX Toolset v3.*")[0].FullName bin)
$Env:PATH
) -join [System.IO.Path]::PathSeparator

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

@ -27,6 +27,7 @@ Transmission can be set to invoke a script when downloads complete. The environm
* `TR_TORRENT_ID`
* `TR_TORRENT_LABELS` - A comma-delimited list of the torrent's labels
* `TR_TORRENT_NAME` - Name of torrent (not filename)
* `TR_TORRENT_PRIORITY` - The priority of the torrent (Low is "-1", Normal is "0", High is "1")
* `TR_TORRENT_TRACKERS` - A comma-delimited list of the torrent's trackers' announce URLs
[Here is an example script](https://trac.transmissionbt.com/browser/trunk/extras/send-email-when-torrent-done.sh) that sends an email when a torrent finishes.

View File

@ -22,7 +22,7 @@ Note: NAT-PMP is only for Apple Airport routers.
### Double NAT
Another possible reason your port remains closed could be because your router is not the only device on the network which needs to be configured.
For example, your network might resemble the following: ADSL modem/router &rarr; Netgear router &rarr; laptop.
For example, your network might resemble the following: ADSL modem/router → Netgear router → laptop.
If you have multiple routers in your home network (such as in the example above), you have two options. The easiest way is to turn one of the routers into 'Bridge mode' which means you then only have to configure one device rather than all of them. So, in our above example, we would set the Netgear router to 'Bridge'. See your router's help documentation for instructions.

View File

@ -303,7 +303,7 @@ The 'source' column here corresponds to the data structure there.
| Key | Value Type | transmission.h source
|:--|:--|:--
| `bytesCompleted` | number | tr_file_view
| `wanted` | number | tr_file_view (**Note:** For backwards compatibility, this is serialized as an array of `0` or `1` that should be treated as booleans)
| `wanted` | boolean | tr_file_view (**Note:** Not to be confused with `torrent-get.wanted`, which is an array of 0/1 instead of boolean)
| `priority` | number | tr_file_view
`peers`: an array of objects, each containing:
@ -400,8 +400,10 @@ The 'source' column here corresponds to the data structure there.
| `tier` | number | tr_tracker_view
`wanted`: An array of `tr_torrentFileCount()` Booleans true if the corresponding file is to be downloaded. (Source: `tr_file_view`)
`wanted`: An array of `tr_torrentFileCount()` 0/1, 1 (true) if the corresponding file is to be downloaded. (Source: `tr_file_view`)
**Note:** For backwards compatibility, in `4.x.x`, `wanted` is serialized as an array of `0` or `1` that should be treated as booleans.
This will be fixed in `5.0.0` to return an array of booleans.
Example:

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

@ -14,7 +14,9 @@ struct tr_block_info
public:
static auto constexpr BlockSize = uint32_t{ 1024U * 16U };
tr_block_info() noexcept = default;
tr_block_info() noexcept
{
}
tr_block_info(uint64_t const total_size_in, uint32_t const piece_size_in) noexcept
{

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

@ -44,7 +44,7 @@ public:
return iter != std::end(icons_) ? &iter->second : nullptr;
}
void load(
void load( //
std::string_view url_in,
IconFunc callback = [](Icon const&) { /*default callback is a no-op */ })
{

View File

@ -561,7 +561,7 @@ ReadState tr_handshake::can_read(tr_peerIo* peer_io, void* vhandshake, size_t* p
/* no piece data in handshake */
*piece = 0;
tr_logAddTraceHand(handshake, fmt::format("handling canRead; state is [{}]", handshake->state_string()));
tr_logAddTraceHand(handshake, fmt::format("handling can_read; state is [{}]", handshake->state_string()));
ReadState ret = READ_NOW;
while (ret == READ_NOW)
@ -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

@ -134,7 +134,7 @@ std::optional<tr_sha1_digest_t> parseBase32Hash(std::string_view sv)
std::optional<tr_sha1_digest_t> parseHash(std::string_view sv)
{
// http://bittorrent.org/beps/bep_0009.html
// https://www.bittorrent.org/beps/bep_0009.html
// Is the info-hash hex encoded, for a total of 40 characters.
// For compatibility with existing links in the wild, clients
// should also support the 32 character base32 encoded info-hash.
@ -153,7 +153,7 @@ std::optional<tr_sha1_digest_t> parseHash(std::string_view sv)
std::optional<tr_sha256_digest_t> parseHash2(std::string_view sv)
{
// http://bittorrent.org/beps/bep_0009.html
// https://www.bittorrent.org/beps/bep_0009.html
// Is the info-hash v2 hex encoded and tag removed, for a total of 64 characters.
if (auto const hash = tr_sha256_from_string(sv); hash)

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 {};
}
@ -271,7 +271,6 @@ tr_peer_socket tr_netOpenPeerSocket(tr_session* session, tr_socket_address const
return {};
}
auto ret = tr_peer_socket{};
if (connect(s, reinterpret_cast<sockaddr const*>(&sock), addrlen) == -1 &&
#ifdef _WIN32
sockerrno != WSAEWOULDBLOCK &&
@ -291,15 +290,12 @@ tr_peer_socket tr_netOpenPeerSocket(tr_session* session, tr_socket_address const
}
tr_net_close_socket(s);
}
else
{
ret = tr_peer_socket{ session, socket_address, s };
return {};
}
tr_logAddTrace(fmt::format("New OUTGOING connection {} ({})", s, socket_address.display_name()));
return ret;
return { session, socket_address, s };
}
namespace
@ -324,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)
@ -450,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:
@ -497,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
@ -704,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;
@ -242,7 +244,7 @@ struct tr_address
} addr;
static auto constexpr CompactAddrBytes = std::array{ 4U, 16U };
static auto constexpr CompactAddrMaxBytes = 16U;
static auto constexpr CompactAddrMaxBytes = *std::max_element(std::begin(CompactAddrBytes), std::end(CompactAddrBytes));
static_assert(std::size(CompactAddrBytes) == NUM_TR_AF_INET_TYPES);
[[nodiscard]] static auto any(tr_address_type type) noexcept
@ -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
{
@ -397,11 +399,12 @@ struct tr_socket_address
static auto constexpr CompactSockAddrBytes = std::array{ tr_address::CompactAddrBytes[0] + tr_port::CompactPortBytes,
tr_address::CompactAddrBytes[1] + tr_port::CompactPortBytes };
static auto constexpr CompactSockAddrMaxBytes = tr_address::CompactAddrMaxBytes + tr_port::CompactPortBytes;
static_assert(std::size(CompactSockAddrBytes) == NUM_TR_AF_INET_TYPES);
};
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

@ -26,9 +26,9 @@
* @{
*/
class tr_peer;
class tr_swarm;
struct tr_bandwidth;
struct tr_peer;
// --- Peer Publish / Subscribe
@ -178,27 +178,26 @@ using tr_peer_callback_generic = void (*)(tr_peer* peer, tr_peer_event const& ev
* @see tr_peer_info
* @see tr_peerMsgs
*/
class tr_peer
struct tr_peer
{
public:
using Speed = libtransmission::Values::Speed;
explicit tr_peer(tr_torrent const* tor);
explicit tr_peer(tr_torrent const& tor);
virtual ~tr_peer();
[[nodiscard]] virtual Speed get_piece_speed(uint64_t now, tr_direction direction) const = 0;
[[nodiscard]] bool hasPiece(tr_piece_index_t piece) const noexcept
[[nodiscard]] bool has_piece(tr_piece_index_t piece) const noexcept
{
return has().test(piece);
}
[[nodiscard]] float percentDone() const noexcept
[[nodiscard]] float percent_done() const noexcept
{
return has().percent();
}
[[nodiscard]] bool isSeed() const noexcept
[[nodiscard]] bool is_seed() const noexcept
{
return has().has_all();
}
@ -208,22 +207,13 @@ public:
[[nodiscard]] virtual tr_bitfield const& has() const noexcept = 0;
// requests that have been made but haven't been fulfilled yet
[[nodiscard]] virtual size_t activeReqCount(tr_direction) const noexcept = 0;
[[nodiscard]] virtual size_t active_req_count(tr_direction) const noexcept = 0;
virtual void requestBlocks(tr_block_span_t const* block_spans, size_t n_spans) = 0;
virtual void request_blocks(tr_block_span_t const* block_spans, size_t n_spans) = 0;
struct RequestLimit
virtual void cancel_block_request(tr_block_index_t /*block*/)
{
// How many blocks we could request.
size_t max_spans = 0;
// How many spans those blocks could be in.
// This is for webseeds, which make parallel requests.
size_t max_blocks = 0;
};
// how many blocks could we request from this peer right now?
[[nodiscard]] virtual RequestLimit canRequest() const noexcept = 0;
}
tr_session* const session;

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

@ -21,7 +21,7 @@
#include "libtransmission/peer-mgr-wishlist.h"
#include "libtransmission/tr-assert.h"
class tr_peer;
struct tr_peer;
class ActiveRequests::Impl
{

View File

@ -17,7 +17,7 @@
#include "libtransmission/transmission.h" // tr_block_index_t
class tr_peer;
struct tr_peer;
/**
* Bookkeeping for the active requests we have --

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();
@ -681,11 +680,9 @@ private:
static void maybe_send_cancel_request(tr_peer* peer, tr_block_index_t block, tr_peer const* muted)
{
auto* msgs = dynamic_cast<tr_peerMsgs*>(peer);
if (msgs != nullptr && msgs != muted)
if (peer != nullptr && peer != muted)
{
peer->cancels_sent_to_peer.add(tr_time(), 1);
msgs->cancel_block_request(block);
peer->cancel_block_request(block);
}
}
@ -780,9 +777,9 @@ private:
// didn't have the metadata before now... so refresh them all...
for (auto* peer : peers)
{
peer->onTorrentGotMetainfo();
peer->on_torrent_got_metainfo();
if (peer->isSeed())
if (peer->is_seed())
{
mark_peer_as_seed(*peer->peer_info);
}
@ -1006,7 +1003,7 @@ size_t tr_swarm::WishlistMediator::count_piece_replication(tr_piece_index_t piec
std::begin(swarm_.peers),
std::end(swarm_.peers),
size_t{},
[piece](size_t acc, tr_peer* peer) { return acc + (peer->hasPiece(piece) ? 1U : 0U); });
[piece](size_t acc, tr_peer* peer) { return acc + (peer->has_piece(piece) ? 1U : 0U); });
}
tr_block_span_t tr_swarm::WishlistMediator::block_span(tr_piece_index_t piece) const
@ -1195,10 +1192,10 @@ private:
// --- tr_peer virtual functions
tr_peer::tr_peer(tr_torrent const* tor)
: session{ tor->session }
, swarm{ tor->swarm }
, blame{ tor->block_count() }
tr_peer::tr_peer(tr_torrent const& tor)
: session{ tor.session }
, swarm{ tor.swarm }
, blame{ tor.block_count() }
{
}
@ -1263,7 +1260,7 @@ std::vector<tr_block_span_t> tr_peerMgrGetNextRequests(tr_torrent* torrent, tr_p
swarm.update_endgame();
return swarm.wishlist->next(
numwant,
[peer](tr_piece_index_t p) { return peer->hasPiece(p); },
[peer](tr_piece_index_t p) { return peer->has_piece(p); },
[peer, &swarm](tr_block_index_t b) { return swarm.active_requests.has(b, peer); });
}
@ -1294,15 +1291,14 @@ namespace
{
namespace handshake_helpers
{
void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr<tr_peerIo> io, tr_peer_info* peer_info, tr_interned_string client)
void create_bit_torrent_peer(tr_torrent& tor, std::shared_ptr<tr_peerIo> io, tr_peer_info* peer_info, tr_interned_string client)
{
TR_ASSERT(peer_info != nullptr);
TR_ASSERT(tr_isTorrent(tor));
TR_ASSERT(tor->swarm != nullptr);
TR_ASSERT(tor.swarm != nullptr);
tr_swarm* swarm = tor->swarm;
tr_swarm* swarm = tor.swarm;
auto* peer = tr_peerMsgsNew(tor, peer_info, std::move(io), client, &tr_swarm::peer_callback_bt, swarm);
auto* peer = tr_peerMsgs::create(tor, peer_info, std::move(io), client, &tr_swarm::peer_callback_bt, swarm);
swarm->peers.push_back(peer);
@ -1399,7 +1395,7 @@ void create_bit_torrent_peer(tr_torrent* tor, std::shared_ptr<tr_peerIo> io, tr_
}
result.io->set_bandwidth(&swarm->tor->bandwidth());
create_bit_torrent_peer(swarm->tor, result.io, info, client);
create_bit_torrent_peer(*swarm->tor, result.io, info, client);
return true;
}
@ -1443,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)
@ -1635,7 +1631,7 @@ int8_t tr_peerMgrPieceAvailability(tr_torrent const* tor, tr_piece_index_t piece
}
auto const& peers = tor->swarm->peers;
return std::count_if(std::begin(peers), std::end(peers), [piece](auto const* peer) { return peer->hasPiece(piece); });
return std::count_if(std::begin(peers), std::end(peers), [piece](auto const* peer) { return peer->has_piece(piece); });
}
void tr_peerMgrTorrentAvailability(tr_torrent const* tor, int8_t* tab, unsigned int n_tabs)
@ -1723,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
@ -1741,7 +1737,7 @@ namespace peer_stat_helpers
stats.client = peer->user_agent().c_str();
stats.port = port.host();
stats.from = peer->peer_info->from_first();
stats.progress = peer->percentDone();
stats.progress = peer->percent_done();
stats.isUTP = peer->is_utp_connection();
stats.isEncrypted = peer->is_encrypted();
stats.rateToPeer_KBps = peer->get_piece_speed(now_msec, TR_CLIENT_TO_PEER).count(Speed::Units::KByps);
@ -1753,15 +1749,15 @@ namespace peer_stat_helpers
stats.isIncoming = peer->is_incoming_connection();
stats.isDownloadingFrom = peer->is_active(TR_PEER_TO_CLIENT);
stats.isUploadingTo = peer->is_active(TR_CLIENT_TO_PEER);
stats.isSeed = peer->isSeed();
stats.isSeed = peer->is_seed();
stats.blocksToPeer = peer->blocks_sent_to_peer.count(now, CancelHistorySec);
stats.blocksToClient = peer->blocks_sent_to_client.count(now, CancelHistorySec);
stats.cancelsToPeer = peer->cancels_sent_to_peer.count(now, CancelHistorySec);
stats.cancelsToClient = peer->cancels_sent_to_client.count(now, CancelHistorySec);
stats.activeReqsToPeer = peer->activeReqCount(TR_CLIENT_TO_PEER);
stats.activeReqsToClient = peer->activeReqCount(TR_PEER_TO_CLIENT);
stats.activeReqsToPeer = peer->active_req_count(TR_CLIENT_TO_PEER);
stats.activeReqsToClient = peer->active_req_count(TR_PEER_TO_CLIENT);
char* pch = stats.flagStr;
@ -1832,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);
@ -1841,13 +1835,22 @@ tr_peer_stat* tr_peerMgrPeerStats(tr_torrent const* tor, size_t* setme_count)
auto const n = std::size(peers);
auto* const ret = new tr_peer_stat[n];
auto const now = tr_time();
auto const now_msec = tr_time_msec();
std::transform(
std::begin(peers),
std::end(peers),
ret,
[&now, &now_msec](auto const* peer) { return get_peer_stats(peer, now, now_msec); });
// TODO: re-implement as a callback solution (similar to tr_sessionSetCompletenessCallback) in case present call to run_in_session_thread is causing hangs when the peers info window is displayed.
auto done_promise = std::promise<void>{};
auto done_future = done_promise.get_future();
tor->session->run_in_session_thread(
[&peers, &ret, &done_promise]()
{
auto const now = tr_time();
auto const now_msec = tr_time_msec();
std::transform(
std::begin(peers),
std::end(peers),
ret,
[&now, &now_msec](auto const* peer) { return peer_stat_helpers::get_peer_stats(peer, now, now_msec); });
done_promise.set_value();
});
done_future.wait();
*setme_count = n;
return ret;
@ -1867,14 +1870,14 @@ namespace update_interest_helpers
TR_ASSERT(!tor->is_done());
TR_ASSERT(tor->client_can_download());
if (peer->isSeed())
if (peer->is_seed())
{
return true;
}
for (tr_piece_index_t i = 0; i < tor->piece_count(); ++i)
{
if (piece_is_interesting[i] && peer->hasPiece(i))
if (piece_is_interesting[i] && peer->has_piece(i))
{
return true;
}
@ -1895,11 +1898,11 @@ void updateInterest(tr_swarm* swarm)
if (auto const& peers = swarm->peers; !std::empty(peers))
{
int const n = tor->piece_count();
auto const n = tor->piece_count();
// build a bitfield of interesting pieces...
auto piece_is_interesting = std::vector<bool>(n);
for (int i = 0; i < n; ++i)
for (tr_piece_index_t i = 0U; i < n; ++i)
{
piece_is_interesting[i] = tor->piece_is_wanted(i) && !tor->has_piece(i);
}
@ -2010,7 +2013,7 @@ void rechokeUploads(tr_swarm* s, uint64_t const now)
auto salter = tr_salt_shaker{};
for (auto* const peer : peers)
{
if (peer->isSeed())
if (peer->is_seed())
{
/* choke seeds and partial seeds */
peer->set_choke(true);
@ -2151,7 +2154,7 @@ auto constexpr MaxUploadIdleSecs = time_t{ 60 * 5 };
auto const* const info = peer->peer_info;
/* disconnect if we're both seeds and enough time has passed for PEX */
if (tor->is_done() && peer->isSeed())
if (tor->is_done() && peer->is_seed())
{
return !tor->allows_pex() || info->idle_secs(now).value_or(0U) >= 30U;
}

View File

@ -30,8 +30,8 @@
* @{
*/
class tr_peer;
class tr_peer_socket;
struct tr_peer;
struct tr_peerMgr;
struct tr_peer_stat;
struct tr_session;
@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -36,7 +36,7 @@ class tr_peerMsgs : public tr_peer
{
public:
tr_peerMsgs(
tr_torrent const* tor,
tr_torrent const& tor,
tr_peer_info* peer_info_in,
tr_interned_string user_agent,
bool connection_is_encrypted,
@ -97,17 +97,23 @@ public:
[[nodiscard]] virtual tr_socket_address socket_address() const = 0;
virtual void cancel_block_request(tr_block_index_t block) = 0;
virtual void set_choke(bool peer_is_choked) = 0;
virtual void set_interested(bool client_is_interested) = 0;
virtual void pulse() = 0;
virtual void onTorrentGotMetainfo() = 0;
virtual void on_torrent_got_metainfo() noexcept = 0;
virtual void on_piece_completed(tr_piece_index_t) = 0;
static tr_peerMsgs* create(
tr_torrent& torrent,
tr_peer_info* peer_info,
std::shared_ptr<tr_peerIo> io,
tr_interned_string user_agent,
tr_peer_callback_bt callback,
void* callback_data);
protected:
constexpr void set_client_choked(bool val) noexcept
{
@ -169,12 +175,4 @@ private:
bool peer_is_interested_ = false;
};
tr_peerMsgs* tr_peerMsgsNew(
tr_torrent* torrent,
tr_peer_info* peer_info,
std::shared_ptr<tr_peerIo> io,
tr_interned_string user_agent,
tr_peer_callback_bt callback,
void* callback_data);
/* @} */

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

@ -148,6 +148,7 @@ auto constexpr MyStatic = std::array<std::string_view, TR_N_KEYS>{
"honorsSessionLimits"sv,
"host"sv,
"id"sv,
"id_timestamp"sv,
"idle-limit"sv,
"idle-mode"sv,
"idle-seeding-limit"sv,
@ -366,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

@ -149,6 +149,7 @@ enum
TR_KEY_honorsSessionLimits,
TR_KEY_host,
TR_KEY_id,
TR_KEY_id_timestamp,
TR_KEY_idle_limit,
TR_KEY_idle_mode,
TR_KEY_idle_seeding_limit,
@ -367,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

@ -80,7 +80,7 @@ public:
[[nodiscard]] virtual time_t time() = 0;
};
constexpr explicit tr_session_alt_speeds(Mediator& mediator) noexcept
explicit tr_session_alt_speeds(Mediator& mediator) noexcept
: mediator_{ mediator }
{
}

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

@ -4,13 +4,13 @@
// License text can be found in the licenses/ folder.
#include <array>
#include <chrono>
#include <cstddef> // size_t
#include <cstdint> // int64_t, uint32_t
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <fmt/core.h>
@ -23,6 +23,7 @@
#include "libtransmission/settings.h"
#include "libtransmission/utils.h" // for tr_strv_strip(), tr_strlower()
#include "libtransmission/variant.h"
#include "libtransmission/tr-assert.h"
using namespace std::literals;
@ -30,117 +31,58 @@ namespace libtransmission
{
namespace
{
struct VariantConverter
template<typename T, size_t N>
using Lookup = std::array<std::pair<std::string_view, T>, N>;
// ---
bool load_bool(tr_variant const& src, bool* tgt)
{
public:
template<typename T>
static std::optional<T> load(tr_variant const& src);
template<typename T>
static tr_variant save(T const& val);
private:
template<typename T, size_t N>
using Lookup = std::array<std::pair<std::string_view, T>, N>;
static auto constexpr EncryptionKeys = Lookup<tr_encryption_mode, 3U>{ {
{ "required", TR_ENCRYPTION_REQUIRED },
{ "preferred", TR_ENCRYPTION_PREFERRED },
{ "allowed", TR_CLEAR_PREFERRED },
} };
static auto constexpr LogKeys = Lookup<tr_log_level, 7U>{ {
{ "critical", TR_LOG_CRITICAL },
{ "debug", TR_LOG_DEBUG },
{ "error", TR_LOG_ERROR },
{ "info", TR_LOG_INFO },
{ "off", TR_LOG_OFF },
{ "trace", TR_LOG_TRACE },
{ "warn", TR_LOG_WARN },
} };
static auto constexpr PreallocationKeys = Lookup<tr_open_files::Preallocation, 5U>{ {
{ "off", tr_open_files::Preallocation::None },
{ "none", tr_open_files::Preallocation::None },
{ "fast", tr_open_files::Preallocation::Sparse },
{ "sparse", tr_open_files::Preallocation::Sparse },
{ "full", tr_open_files::Preallocation::Full },
} };
static auto constexpr VerifyModeKeys = Lookup<tr_verify_added_mode, 2U>{ {
{ "fast", TR_VERIFY_ADDED_FAST },
{ "full", TR_VERIFY_ADDED_FULL },
} };
static auto constexpr PreferredTransportKeys = Lookup<tr_preferred_transport, TR_NUM_PREFERRED_TRANSPORT>{ {
{ "utp", TR_PREFER_UTP },
{ "tcp", TR_PREFER_TCP },
} };
};
template<>
std::optional<bool> VariantConverter::load(tr_variant const& src)
{
if (auto val = src.get_if<bool>(); val != nullptr)
if (auto val = src.value_if<bool>())
{
return *val;
*tgt = *val;
return true;
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(bool const& val)
tr_variant save_bool(bool const& val)
{
return val;
}
// ---
template<>
std::optional<std::chrono::milliseconds> VariantConverter::load(tr_variant const& src)
bool load_double(tr_variant const& src, double* tgt)
{
if (auto val = src.get_if<int64_t>(); val != nullptr)
if (auto val = src.value_if<double>())
{
return std::chrono::milliseconds(*val);
*tgt = *val;
return true;
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(std::chrono::milliseconds const& val)
{
return val.count();
}
// ---
template<>
std::optional<double> VariantConverter::load(tr_variant const& src)
{
if (auto val = src.get_if<double>(); val != nullptr)
{
return *val;
}
return {};
}
template<>
tr_variant VariantConverter::save(double const& val)
tr_variant save_double(double const& val)
{
return val;
}
// ---
template<>
std::optional<tr_encryption_mode> VariantConverter::load(tr_variant const& src)
{
static constexpr auto Keys = EncryptionKeys;
auto constexpr EncryptionKeys = Lookup<tr_encryption_mode, 3U>{ {
{ "required", TR_ENCRYPTION_REQUIRED },
{ "preferred", TR_ENCRYPTION_PREFERRED },
{ "allowed", TR_CLEAR_PREFERRED },
} };
if (auto const* val = src.get_if<std::string_view>(); val != nullptr)
bool load_encryption_mode(tr_variant const& src, tr_encryption_mode* tgt)
{
static constexpr auto& Keys = EncryptionKeys;
if (auto const val = src.value_if<std::string_view>())
{
auto const needle = tr_strlower(tr_strv_strip(*val));
@ -148,39 +90,49 @@ std::optional<tr_encryption_mode> VariantConverter::load(tr_variant const& src)
{
if (key == needle)
{
return encryption;
*tgt = encryption;
return true;
}
}
}
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)
{
if (encryption == *val)
{
return encryption;
*tgt = encryption;
return true;
}
}
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(tr_encryption_mode const& val)
tr_variant save_encryption_mode(tr_encryption_mode const& val)
{
return static_cast<int64_t>(val);
}
// ---
template<>
std::optional<tr_log_level> VariantConverter::load(tr_variant const& src)
{
static constexpr auto Keys = LogKeys;
auto constexpr LogKeys = Lookup<tr_log_level, 7U>{ {
{ "critical", TR_LOG_CRITICAL },
{ "debug", TR_LOG_DEBUG },
{ "error", TR_LOG_ERROR },
{ "info", TR_LOG_INFO },
{ "off", TR_LOG_OFF },
{ "trace", TR_LOG_TRACE },
{ "warn", TR_LOG_WARN },
} };
if (auto const* val = src.get_if<std::string_view>(); val != nullptr)
bool load_log_level(tr_variant const& src, tr_log_level* tgt)
{
static constexpr auto& Keys = LogKeys;
if (auto const val = src.value_if<std::string_view>())
{
auto const needle = tr_strlower(tr_strv_strip(*val));
@ -188,85 +140,110 @@ std::optional<tr_log_level> VariantConverter::load(tr_variant const& src)
{
if (needle == name)
{
return log_level;
*tgt = log_level;
return true;
}
}
}
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)
{
if (log_level == *val)
{
return log_level;
*tgt = log_level;
return true;
}
}
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(tr_log_level const& val)
tr_variant save_log_level(tr_log_level const& val)
{
return static_cast<int64_t>(val);
}
// ---
template<>
std::optional<tr_mode_t> VariantConverter::load(tr_variant const& src)
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)
{
return static_cast<tr_mode_t>(*mode);
*tgt = static_cast<tr_mode_t>(*mode);
return true;
}
}
if (auto const* val = src.get_if<int64_t>(); val != nullptr)
if (auto const val = src.value_if<int64_t>())
{
return static_cast<tr_mode_t>(*val);
*tgt = static_cast<tr_mode_t>(*val);
return true;
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(tr_mode_t const& val)
tr_variant save_mode_t(tr_mode_t const& val)
{
return fmt::format("{:#03o}", val);
}
// ---
template<>
std::optional<tr_port> VariantConverter::load(tr_variant const& src)
bool load_msec(tr_variant const& src, std::chrono::milliseconds* tgt)
{
if (auto const* val = src.get_if<int64_t>(); val != nullptr)
if (auto val = src.value_if<int64_t>())
{
return tr_port::from_host(*val);
*tgt = std::chrono::milliseconds(*val);
return true;
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(tr_port const& val)
tr_variant save_msec(std::chrono::milliseconds const& src)
{
return src.count();
}
// ---
bool load_port(tr_variant const& src, tr_port* tgt)
{
if (auto const val = src.value_if<int64_t>())
{
*tgt = tr_port::from_host(*val);
return true;
}
return false;
}
tr_variant save_port(tr_port const& val)
{
return int64_t{ val.host() };
}
// ---
template<>
std::optional<tr_open_files::Preallocation> VariantConverter::load(tr_variant const& src)
{
static constexpr auto Keys = PreallocationKeys;
auto constexpr PreallocationKeys = Lookup<tr_open_files::Preallocation, 5U>{ {
{ "off", tr_open_files::Preallocation::None },
{ "none", tr_open_files::Preallocation::None },
{ "fast", tr_open_files::Preallocation::Sparse },
{ "sparse", tr_open_files::Preallocation::Sparse },
{ "full", tr_open_files::Preallocation::Full },
} };
if (auto const* val = src.get_if<std::string_view>(); val != nullptr)
bool load_preallocation_mode(tr_variant const& src, tr_open_files::Preallocation* tgt)
{
static constexpr auto& Keys = PreallocationKeys;
if (auto const val = src.value_if<std::string_view>())
{
auto const needle = tr_strlower(tr_strv_strip(*val));
@ -274,39 +251,44 @@ std::optional<tr_open_files::Preallocation> VariantConverter::load(tr_variant co
{
if (name == needle)
{
return value;
*tgt = value;
return true;
}
}
}
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)
{
if (value == static_cast<tr_open_files::Preallocation>(*val))
{
return value;
*tgt = value;
return true;
}
}
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(tr_open_files::Preallocation const& val)
tr_variant save_preallocation_mode(tr_open_files::Preallocation const& val)
{
return static_cast<int64_t>(val);
}
// ---
template<>
std::optional<tr_preferred_transport> VariantConverter::load(tr_variant const& src)
{
static constexpr auto Keys = PreferredTransportKeys;
auto constexpr PreferredTransportKeys = Lookup<tr_preferred_transport, TR_NUM_PREFERRED_TRANSPORT>{ {
{ "utp", TR_PREFER_UTP },
{ "tcp", TR_PREFER_TCP },
} };
if (auto const* val = src.get_if<std::string_view>(); val != nullptr)
bool load_preferred_transport(tr_variant const& src, tr_preferred_transport* tgt)
{
static constexpr auto& Keys = PreferredTransportKeys;
if (auto const val = src.value_if<std::string_view>())
{
auto const needle = tr_strlower(tr_strv_strip(*val));
@ -314,27 +296,28 @@ std::optional<tr_preferred_transport> VariantConverter::load(tr_variant const& s
{
if (name == needle)
{
return value;
*tgt = value;
return true;
}
}
}
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)
{
if (value == *val)
{
return value;
*tgt = value;
return true;
}
}
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(tr_preferred_transport const& val)
tr_variant save_preferred_transport(tr_preferred_transport const& val)
{
for (auto const& [key, value] : PreferredTransportKeys)
{
@ -349,74 +332,81 @@ tr_variant VariantConverter::save(tr_preferred_transport const& val)
// ---
template<>
std::optional<size_t> VariantConverter::load(tr_variant const& src)
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>())
{
return static_cast<size_t>(*val);
*tgt = static_cast<size_t>(*val);
return true;
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(size_t const& val)
tr_variant save_size_t(size_t const& val)
{
return uint64_t{ val };
}
// ---
template<>
std::optional<std::string> VariantConverter::load(tr_variant const& src)
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>())
{
return std::string{ *val };
*tgt = std::string{ *val };
return true;
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(std::string const& val)
tr_variant save_string(std::string const& val)
{
return val;
}
// ---
template<>
std::optional<tr_tos_t> VariantConverter::load(tr_variant const& src)
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>())
{
return tr_tos_t::from_string(*val);
if (auto const tos = tr_tos_t::from_string(*val); tos)
{
*tgt = *tos;
return true;
}
return false;
}
if (auto const* val = src.get_if<int64_t>(); val != nullptr)
if (auto const val = src.value_if<int64_t>())
{
return tr_tos_t{ static_cast<int>(*val) };
*tgt = tr_tos_t{ static_cast<int>(*val) };
return true;
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(tr_tos_t const& val)
tr_variant save_tos_t(tr_tos_t const& val)
{
return val.toString();
}
// ---
template<>
std::optional<tr_verify_added_mode> VariantConverter::load(tr_variant const& src)
auto constexpr VerifyModeKeys = Lookup<tr_verify_added_mode, 2U>{ {
{ "fast", TR_VERIFY_ADDED_FAST },
{ "full", TR_VERIFY_ADDED_FULL },
} };
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));
@ -424,27 +414,28 @@ std::optional<tr_verify_added_mode> VariantConverter::load(tr_variant const& src
{
if (name == needle)
{
return value;
*tgt = value;
return true;
}
}
}
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)
{
if (value == *val)
{
return value;
*tgt = value;
return true;
}
}
}
return {};
return false;
}
template<>
tr_variant VariantConverter::save(tr_verify_added_mode const& val)
tr_variant save_verify_added_mode(tr_verify_added_mode const& val)
{
for (auto const& [key, value] : VerifyModeKeys)
{
@ -456,47 +447,25 @@ tr_variant VariantConverter::save(tr_verify_added_mode const& val)
return static_cast<int64_t>(val);
}
struct LoadVisitor
{
explicit constexpr LoadVisitor(tr_variant const& src)
: src_{ src }
{
}
template<typename T>
void operator()(T* const tgt)
{
if (auto val = VariantConverter::load<T>(src_))
{
*tgt = *val;
}
}
private:
tr_variant const& src_;
};
struct SaveVisitor
{
constexpr SaveVisitor(tr_variant::Map& tgt, tr_quark key)
: tgt_{ tgt }
, key_{ key }
{
}
template<typename T>
void operator()(T const* const src)
{
tgt_.try_emplace(key_, VariantConverter::save(*src));
}
private:
tr_variant::Map& tgt_;
tr_quark key_;
};
} // unnamed namespace
Settings::Settings()
{
add_type_handler(load_bool, save_bool);
add_type_handler(load_double, save_double);
add_type_handler(load_encryption_mode, save_encryption_mode);
add_type_handler(load_log_level, save_log_level);
add_type_handler(load_mode_t, save_mode_t);
add_type_handler(load_msec, save_msec);
add_type_handler(load_port, save_port);
add_type_handler(load_preallocation_mode, save_preallocation_mode);
add_type_handler(load_preferred_transport, save_preferred_transport);
add_type_handler(load_size_t, save_size_t);
add_type_handler(load_string, save_string);
add_type_handler(load_tos_t, save_tos_t);
add_type_handler(load_verify_added_mode, save_verify_added_mode);
}
void Settings::load(tr_variant const& src)
{
auto const* map = src.get_if<tr_variant::Map>();
@ -505,11 +474,13 @@ void Settings::load(tr_variant const& src)
return;
}
for (auto& [key, prop_vptr] : fields())
for (auto& field : fields())
{
if (auto const iter = map->find(key); iter != std::end(*map))
if (auto const iter = map->find(field.key); iter != std::end(*map))
{
std::visit(LoadVisitor{ iter->second }, prop_vptr);
auto const type_index = std::type_index{ field.type };
TR_ASSERT(load_.count(type_index) == 1U);
load_.at(type_index)(iter->second, field.ptr);
}
}
}
@ -520,9 +491,11 @@ tr_variant Settings::save() const
auto map = tr_variant::Map{ std::size(fields) };
for (auto const& [key, prop_vptr] : fields)
for (auto const& field : fields)
{
std::visit(SaveVisitor{ map, key }, prop_vptr);
auto const type_index = std::type_index{ field.type };
TR_ASSERT(save_.count(type_index) == 1U);
map.try_emplace(field.key, save_.at(type_index)(field.ptr));
}
return map;

View File

@ -5,19 +5,12 @@
#pragma once
#include <chrono>
#include <cstddef> // for size_t
#include <string>
#include <utility>
#include <variant>
#include <functional>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
#include <vector>
#include "libtransmission/transmission.h"
#include "libtransmission/log.h" // for tr_log_level
#include "libtransmission/net.h" // for tr_port, tr_tos_t
#include "libtransmission/open-files.h" // for tr_open_files::Preallocation
#include "libtransmission/peer-io.h" // tr_preferred_transport
#include "libtransmission/quark.h"
#include "libtransmission/variant.h"
@ -32,26 +25,50 @@ public:
[[nodiscard]] tr_variant save() const;
protected:
using field_key_type = tr_quark;
using field_mapped_type = std::variant<
bool*,
double*,
size_t*,
std::chrono::milliseconds*,
std::string*,
tr_encryption_mode*,
tr_log_level*,
tr_mode_t*,
tr_open_files::Preallocation*,
tr_port*,
tr_preferred_transport*,
tr_tos_t*,
tr_verify_added_mode*>;
using field_value_type = std::pair<field_key_type const, field_mapped_type>;
using Fields = std::vector<field_value_type>;
Settings();
Settings() = default;
// convert from tr_variant to T
template<typename T>
using Load = bool (*)(tr_variant const& src, T* tgt);
// convert from T to tr_variant
template<typename T>
using Save = tr_variant (*)(T const& src);
template<typename T>
void add_type_handler(Load<T> load_handler, Save<T> save_handler)
{
auto const key = std::type_index(typeid(T*));
// 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_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
{
template<typename T>
Field(tr_quark key_in, T* ptr_in)
: key{ key_in }
, type{ typeid(T*) }
, ptr{ ptr_in }
{
}
tr_quark key;
std::type_info const& type;
void* ptr;
};
using Fields = std::vector<Field>;
[[nodiscard]] virtual Fields fields() = 0;
private:
std::unordered_map<std::type_index, std::function<tr_variant(void const* src)>> save_;
std::unordered_map<std::type_index, std::function<bool(tr_variant const& src, void* tgt)>> load_;
};
} // namespace libtransmission

View File

@ -32,7 +32,7 @@ using namespace std::literals;
/**
* @brief Ensure that the URLs for multfile torrents end in a slash.
*
* See http://bittorrent.org/beps/bep_0019.html#metadata-extension
* See https://www.bittorrent.org/beps/bep_0019.html#metadata-extension
* for background on how the trailing slash is used for "url-list"
* fields.
*
@ -513,7 +513,7 @@ private:
bool finish(Context const& context)
{
// bittorrent 1.0 spec
// http://bittorrent.org/beps/bep_0003.html
// https://www.bittorrent.org/beps/bep_0003.html
//
// "There is also a key length or a key files, but not both or neither.
//

View File

@ -231,7 +231,7 @@ private:
// Offset + size of the bencoded info dict subset of the bencoded data.
// Used when loading pieces of it to sent to magnet peers.
// See http://bittorrent.org/beps/bep_0009.html
// See https://www.bittorrent.org/beps/bep_0009.html
uint64_t info_dict_size_ = 0;
uint64_t info_dict_offset_ = 0;

View File

@ -384,6 +384,7 @@ void torrentCallScript(tr_torrent const* tor, std::string const& script)
auto const trackers_str = buildTrackersString(tor);
auto const bytes_downloaded_str = std::to_string(tor->bytes_downloaded_.ever());
auto const localtime_str = fmt::format("{:%a %b %d %T %Y%n}", fmt::localtime(tr_time()));
auto const priority_str = std::to_string(tor->get_priority());
auto const env = std::map<std::string_view, std::string_view>{
{ "TR_APP_VERSION"sv, SHORT_VERSION_STRING },
@ -394,6 +395,7 @@ void torrentCallScript(tr_torrent const* tor, std::string const& script)
{ "TR_TORRENT_ID"sv, id_str },
{ "TR_TORRENT_LABELS"sv, labels_str },
{ "TR_TORRENT_NAME"sv, tor->name() },
{ "TR_TORRENT_PRIORITY"sv, priority_str },
{ "TR_TORRENT_TRACKERS"sv, trackers_str },
};
@ -496,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();
@ -516,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,
@ -140,7 +139,7 @@ public:
tr_logAddDebug(fmt::format("Starting DHT on port {port}", fmt::arg("port", peer_port.host())));
// init state from scratch, or load from state file if it exists
std::tie(id_, bootstrap_queue_) = init_state(state_filename_);
init_state(state_filename_);
get_nodes_from_bootstrap_file(tr_pathbuf{ mediator_.config_dir(), "/dht.bootstrap"sv }, bootstrap_queue_);
get_nodes_from_name("dht.transmissionbt.com", tr_port::from_host(6881), bootstrap_queue_);
@ -426,8 +425,9 @@ private:
tr_logAddTrace(fmt::format("Saving {} ({} + {}) nodes", n, num4, num6));
tr_variant benc;
tr_variantInitDict(&benc, 3);
tr_variantInitDict(&benc, 4);
tr_variantDictAddRaw(&benc, TR_KEY_id, std::data(id_), std::size(id_));
tr_variantDictAddInt(&benc, TR_KEY_id_timestamp, id_timestamp_);
if (num4 > 0)
{
@ -462,32 +462,38 @@ private:
tr_variant_serde::benc().to_file(benc, state_filename_);
}
[[nodiscard]] static std::pair<Id, Nodes> init_state(std::string_view filename)
void init_state(std::string_view filename)
{
// Note that DHT ids need to be distributed uniformly,
// so it should be something truly random
auto id = tr_rand_obj<Id>();
id_ = tr_rand_obj<Id>();
id_timestamp_ = tr_time();
if (!tr_sys_path_exists(std::data(filename)))
{
return { id, {} };
return;
}
auto otop = tr_variant_serde::benc().parse_file(filename);
if (!otop)
{
return { id, {} };
return;
}
static auto constexpr CompactLen = tr_socket_address::CompactSockAddrBytes[TR_AF_INET];
static auto constexpr Compact6Len = tr_socket_address::CompactSockAddrBytes[TR_AF_INET6];
static auto constexpr IdTtl = time_t{ 30 * 24 * 60 * 60 }; // 30 days
auto& top = *otop;
auto nodes = Nodes{};
if (auto sv = std::string_view{}; tr_variantDictFindStrView(&top, TR_KEY_id, &sv) && std::size(sv) == std::size(id))
if (auto t = int64_t{}; tr_variantDictFindInt(&top, TR_KEY_id_timestamp, &t) && t + IdTtl > id_timestamp_)
{
std::copy(std::begin(sv), std::end(sv), std::begin(id));
if (auto sv = std::string_view{};
tr_variantDictFindStrView(&top, TR_KEY_id, &sv) && std::size(sv) == std::size(id_))
{
id_timestamp_ = t;
std::copy(std::begin(sv), std::end(sv), std::begin(id_));
}
}
size_t raw_len = 0U;
@ -502,7 +508,7 @@ private:
auto port = tr_port{};
std::tie(addr, walk) = tr_address::from_compact_ipv4(walk);
std::tie(port, walk) = tr_port::from_compact(walk);
nodes.emplace_back(addr, port);
bootstrap_queue_.emplace_back(addr, port);
}
}
@ -516,11 +522,9 @@ private:
auto port = tr_port{};
std::tie(addr, walk) = tr_address::from_compact_ipv6(walk);
std::tie(port, walk) = tr_port::from_compact(walk);
nodes.emplace_back(addr, port);
bootstrap_queue_.emplace_back(addr, port);
}
}
return { id, std::move(nodes) };
}
///
@ -601,6 +605,7 @@ private:
std::unique_ptr<libtransmission::Timer> const periodic_timer_;
Id id_ = {};
int64_t id_timestamp_ = {};
Nodes bootstrap_queue_;
size_t n_bootstrapped_ = 0;

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,24 @@ 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>())
{
if (setme)
{
*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 +100,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 +297,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 +331,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 {};
@ -180,9 +180,9 @@ public:
tr_variant& operator=(tr_variant&& that) noexcept = default;
template<typename Val>
tr_variant(Val value)
tr_variant(Val&& value)
{
*this = std::move(value);
*this = std::forward<Val>(value);
}
[[nodiscard]] static auto make_map(size_t const n_reserve = 0U) noexcept
@ -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

@ -7,7 +7,7 @@
#define SHORT_VERSION_STRING "${TR_USER_AGENT_PREFIX}"
#define LONG_VERSION_STRING "${TR_USER_AGENT_PREFIX} (${TR_VCS_REVISION})"
#define VERSION_STRING_INFOPLIST ${TR_USER_AGENT_PREFIX}
#define BUILD_STRING_INFOPLIST 14714.${TR_VERSION_MAJOR}.${TR_VERSION_MINOR}.${TR_VERSION_PATCH}
#define BUILD_STRING_INFOPLIST ${CFBUNDLE_VERSION}
#define MAJOR_VERSION ${TR_VERSION_MAJOR}
#define MINOR_VERSION ${TR_VERSION_MINOR}
#define PATCH_VERSION ${TR_VERSION_PATCH}

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,58 +158,67 @@ 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:
tr_webseed(struct tr_torrent* tor, std::string_view url, tr_peer_callback_webseed callback_in, void* callback_data_in)
: tr_peer{ tor }
, torrent_id{ tr_torrentId(tor) }
struct RequestLimit
{
// How many spans those blocks could be in.
// This is for webseeds, which make parallel requests.
size_t max_spans = 0;
// How many blocks we could request.
size_t max_blocks = 0;
};
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_CONSTEXPR20 size_t activeReqCount(tr_direction dir) const noexcept override
[[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
{
@ -221,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);
}
@ -234,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 requestBlocks(tr_block_span_t const* block_spans, size_t n_spans) override
void request_blocks(tr_block_span_t const* block_spans, size_t n_spans) override
{
auto* const tor = getTorrent();
if (tor == nullptr || !tor->is_running() || tor->is_done())
if (!tor.is_running() || tor.is_done())
{
return;
}
@ -261,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);
}
}
[[nodiscard]] RequestLimit canRequest() const noexcept override
void on_idle()
{
auto const n_slots = connection_limiter.slotsAvailable();
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.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 {};
}
@ -285,22 +315,20 @@ public:
// Prefer to request large, contiguous chunks from webseeds.
// The actual value of '64' is arbitrary here;
// we could probably be smarter about this.
auto constexpr PreferredBlocksPerTask = size_t{ 64 };
static auto constexpr PreferredBlocksPerTask = size_t{ 64 };
return { n_slots, n_slots * PreferredBlocksPerTask };
}
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;
@ -313,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);
@ -407,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->canRequest();
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->requestBlocks(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);
@ -443,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;
@ -491,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(struct 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

@ -5,6 +5,7 @@
#import "BadgeView.h"
#import "NSStringAdditions.h"
#import "NSImageAdditions.h"
#import "Utils.h"
static CGFloat const kBetweenPadding = 2.0;
static NSImage* kWhiteUpArrow = [[NSImage imageNamed:@"UpArrowTemplate"] imageWithColor:NSColor.whiteColor];
@ -61,7 +62,7 @@ typedef NS_ENUM(NSInteger, ArrowDirection) {
- (BOOL)setRatesWithDownload:(CGFloat)downloadRate upload:(CGFloat)uploadRate
{
//only needs update if the badges were displayed or are displayed now
if (self.fDownloadRate == downloadRate && self.fUploadRate == uploadRate)
if (isSpeedEqual(self.fDownloadRate, downloadRate) && isSpeedEqual(self.fUploadRate, uploadRate))
{
return NO;
}

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

@ -91,7 +91,6 @@
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="setAutoUpdateToBeta:" target="-2" id="1900"/>
<binding destination="365" name="value" keyPath="values.AutoUpdateBeta" id="1897"/>
</connections>
</button>

View File

@ -191,6 +191,10 @@ target_sources(${TR_NAME}-mac
TrackerTableView.mm
URLSheetWindowController.h
URLSheetWindowController.mm
Utils.h
Utils.mm
VersionComparator.h
VersionComparator.mm
WebSeedTableView.h
WebSeedTableView.mm)

View File

@ -55,6 +55,7 @@
#import "NSStringAdditions.h"
#import "ExpandedPathToPathTransformer.h"
#import "ExpandedPathToIconTransformer.h"
#import "VersionComparator.h"
typedef NSString* ToolbarItemIdentifier NS_TYPED_EXTENSIBLE_ENUM;
@ -509,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)
@ -5428,11 +5440,6 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool
[NSWorkspace.sharedWorkspace openURL:[NSURL URLWithString:kDonateURL]];
}
- (void)updaterWillRelaunchApplication:(SUUpdater*)updater
{
self.fQuitRequested = YES;
}
- (void)rpcCallback:(tr_rpc_callback_type)type forTorrentStruct:(struct tr_torrent*)torrentStruct
{
@autoreleasepool
@ -5585,3 +5592,17 @@ void onTorrentCompletenessChanged(tr_torrent* tor, tr_completeness status, bool
}
@end
@implementation Controller (SUUpdaterDelegate)
- (void)updaterWillRelaunchApplication:(SUUpdater*)updater
{
self.fQuitRequested = YES;
}
- (nullable id<SUVersionComparison>)versionComparatorForUpdater:(SUUpdater*)updater
{
return [VersionComparator new];
}
@end

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

@ -61,7 +61,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>14714.@TR_VERSION_MAJOR@.@TR_VERSION_MINOR@.@TR_VERSION_PATCH@</string>
<string>@CFBUNDLE_VERSION@</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</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

@ -5,6 +5,7 @@
#import "InfoOptionsViewController.h"
#import "NSStringAdditions.h"
#import "Torrent.h"
#import "Utils.h"
typedef NS_ENUM(NSInteger, OptionPopupType) {
OptionPopupTypeGlobal = 0,
@ -295,7 +296,7 @@ static CGFloat const kStackViewSpacing = 8.0;
checkRatio = kInvalidValue;
}
if (!multipleRatioLimits && ratioLimit != torrent.ratioLimit)
if (!multipleRatioLimits && !isRatioEqual(ratioLimit, torrent.ratioLimit))
{
multipleRatioLimits = YES;
}

View File

@ -351,9 +351,9 @@ typedef NS_ENUM(NSUInteger, TabTag) {
viewRect = [self.fOptionsViewController viewRect];
}
CGFloat const difference = NSHeight(viewRect) - oldHeight;
windowRect.origin.y -= difference;
windowRect.size.height += difference;
CGFloat const viewHeightDifference = NSHeight(viewRect) - oldHeight;
windowRect.origin.y -= viewHeightDifference;
windowRect.size.height += viewHeightDifference;
windowRect.size.width = MAX(NSWidth(windowRect), minWindowWidth);
if ([self.fViewController respondsToSelector:@selector(saveViewSize)]) //a little bit hacky, but avoids requiring an extra method
@ -363,11 +363,11 @@ typedef NS_ENUM(NSUInteger, TabTag) {
CGFloat const screenHeight = NSHeight(window.screen.visibleFrame);
if (NSHeight(windowRect) > screenHeight)
{
CGFloat const difference = screenHeight - NSHeight(windowRect);
windowRect.origin.y -= difference;
windowRect.size.height += difference;
CGFloat const windowHeightDifference = screenHeight - NSHeight(windowRect);
windowRect.origin.y -= windowHeightDifference;
windowRect.size.height += windowHeightDifference;
viewRect.size.height += difference;
viewRect.size.height += windowHeightDifference;
}
}

View File

@ -72,7 +72,7 @@ static NSTimeInterval const kCheckFireInterval = 3.0;
timeoutInterval:15.0];
_fTask = [_fSession dataTaskWithRequest:portProbeRequest
completionHandler:^(NSData* _Nullable data, NSURLResponse* _Nullable response, NSError* _Nullable error) {
completionHandler:^(NSData* _Nullable data, NSURLResponse* _Nullable, NSError* _Nullable error) {
if (error)
{
NSLog(@"Unable to get port status: connection failed (%@)", error.localizedDescription);

View File

@ -178,8 +178,6 @@ static NSString* const kWebUIURLFormat = @"http://localhost:%ld/";
[_fDefaults removeObjectForKey:@"CheckForUpdates"];
}
[self setAutoUpdateToBeta:nil];
_fDefaultAppHelper = [[DefaultAppHelper alloc] init];
}
@ -409,17 +407,6 @@ static NSString* const kWebUIURLFormat = @"http://localhost:%ld/";
[self.window setFrame:windowRect display:YES animate:NO];
}
//for a beta release, always use the beta appcast
#if defined(TR_BETA_RELEASE)
#define SPARKLE_TAG YES
#else
#define SPARKLE_TAG [fDefaults boolForKey:@"AutoUpdateBeta"]
#endif
- (void)setAutoUpdateToBeta:(id)sender
{
// TODO: Support beta releases (if/when necessary)
}
- (void)setPort:(id)sender
{
uint16_t const port = [sender intValue];

View File

@ -4,6 +4,7 @@
#import "StatusBarController.h"
#import "NSStringAdditions.h"
#import "Utils.h"
typedef NSString* StatusRatioType NS_TYPED_EXTENSIBLE_ENUM;
@ -77,13 +78,13 @@ typedef NS_ENUM(NSUInteger, StatusTag) {
- (void)updateWithDownload:(CGFloat)dlRate upload:(CGFloat)ulRate
{
//set rates
if (dlRate != self.fPreviousDownloadRate)
if (!isSpeedEqual(self.fPreviousDownloadRate, dlRate))
{
self.fTotalDLField.stringValue = [NSString stringForSpeed:dlRate];
self.fPreviousDownloadRate = dlRate;
}
if (ulRate != self.fPreviousUploadRate)
if (!isSpeedEqual(self.fPreviousUploadRate, ulRate))
{
self.fTotalULField.stringValue = [NSString stringForSpeed:ulRate];
self.fPreviousUploadRate = ulRate;

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

@ -125,7 +125,7 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175;
NSIndexSet* fullIndexSet = [NSIndexSet indexSetWithIndexesInRange:fullRange];
NSMutableIndexSet* visibleIndexSet = [[NSMutableIndexSet alloc] init];
[fullIndexSet enumerateIndexesUsingBlock:^(NSUInteger row, BOOL* stop) {
[fullIndexSet enumerateIndexesUsingBlock:^(NSUInteger row, BOOL*) {
id rowView = [self rowViewAtRow:row makeIfNecessary:NO];
if ([rowView isGroupRowStyle])
{
@ -151,7 +151,7 @@ static NSTimeInterval const kToggleProgressSeconds = 0.175;
//redraw fControlButton
BOOL minimal = [self.fDefaults boolForKey:@"SmallView"];
[rowIndexes enumerateIndexesUsingBlock:^(NSUInteger row, BOOL* stop) {
[rowIndexes enumerateIndexesUsingBlock:^(NSUInteger row, BOOL*) {
id rowView = [self rowViewAtRow:row makeIfNecessary:NO];
if (![rowView isGroupRowStyle])
{

View File

@ -16,7 +16,7 @@
</div>
<p>Read these tips for <a href="speed.html">maximizing your download speed</a>.
<p>Some Internet Service Providers (ISPs) throttle peer-to-peer traffic, and even block it completely on well known peer-to-peer ports. If your ISP is listed on <a href="http://wiki.vuze.com/w/Bad_ISPs">this page</a>, it is likely you will experience these issues.</p>
<p>Transmission's encryption feature may overcome some ISP throttling. Checking the 'Ignore unencrypted peers' box (Preferences &rarr; Peers) also may improve your speed further, at the expense of losing some potential peers in the swarm. Changing the port Transmission uses may help if the ISP targets particular ports.</p>
<p>Transmission's encryption feature may overcome some ISP throttling. Checking the 'Ignore unencrypted peers' box (Preferences Peers) also may improve your speed further, at the expense of losing some potential peers in the swarm. Changing the port Transmission uses may help if the ISP targets particular ports.</p>
<p>Ultimately, the speed you get depends on the quality of the peers you are downloading from. If they have dial up connections, you are only going to be able to download at dial up speeds. Furthermore, if there are few seeds and many peers, more people will be fighting for the same scarce pieces which will slow things down. Best results are achieved when the torrent has more seeds than peers.</p>
@ -24,7 +24,7 @@
<h1>Why isn't my torrent downloading at all?</h1>
</div>
<p>Often this is because the tracker is down, and thus Transmission is unable to interact with other peers. If this is the case, enabling DHT (trackerless torrents) (Preferences &rarr; Peers) might help for public torrents.</p>
<p>Often this is because the tracker is down, and thus Transmission is unable to interact with other peers. If this is the case, enabling DHT (trackerless torrents) (Preferences Peers) might help for public torrents.</p>
<p>If there are no seeders in the swarm, and all the other peers have sent you what they have, you (and everyone else) will not be able to complete the download, and your speed will drop to zer.</p>
<p>Torrents take a while to get going and so may not download much (if at all) initially. Most torrents are downloading at some rate after 15 or so minutes.</p>
<div class="pagetitle">

View File

@ -20,7 +20,7 @@
<div class="taskbox">
<ol>
<li>Select the relevant torrent.</li>
<li>Go to the Transfers menu &rarr; Verify Local Data.</li>
<li>Go to the Transfers menu Verify Local Data.</li>
</ol>
</div>

View File

@ -83,14 +83,14 @@
<div class="question">Can I schedule my transfers?</div>
<div class="answer-thumbnail"><img alt="scheduling" src="../gfx/scheduler.png"/></div>
<div class="answer-text">
<p>Yes, by using 'Speed Limit Mode'. Simply go to Preferences &rarr; Bandwidth, and then set both the speed you would like Transmission to be limited to, as well as the period of time you would like the limits applied.</p>
<p>Yes, by using 'Speed Limit Mode'. Simply go to Preferences Bandwidth, and then set both the speed you would like Transmission to be limited to, as well as the period of time you would like the limits applied.</p>
<p>When Speed Limit Mode is enabled, the turtle will be illuminated in blue.</p>
</div>
<div class="question">Can I queue my transfers?</div>
<div class="answer-thumbnail"><img alt="queue" src="../gfx/queue.png"/></div>
<div class="answer-text">
<p>Yes, you can queue seeding and/or downloading transfers via Preferences &rarr; Transfers &rarr; Management.</p>
<p>Yes, you can queue seeding and/or downloading transfers via Preferences → Transfers → Management.</p>
<p>The queue system is very simple: You start and pause transfers as usual, but if you're over the queue limit starting a transfer will instead make it "Waiting to download..."</p>
<p>You can force a transfer to start by holding down option and clicking the orange resume button, or by using the Transfers menu item "Resume Selected Without Wait".</p>
</div>
@ -100,8 +100,8 @@
<div class="answer-text">
<p>Transmission allows you to sort your torrents by various criteria. Choose "Sort Transfers By" in the View menu, as well as the Action menu.</p>
<p>You can also filter your torrents by their activity state. Simply enable the Filter bar in the View menu.</p>
<p>Transmission allows you to group torrents by color labels. Groups can be assigned upon adding a transfer to the list by establishing rules in Preferences &rarr; Groups. Groups can be manually changed with Transfers &rarr; Group and by dragging transfers to different groups in the main window (when "Use Groups" is enabled in the View menu).</p>
<p>These groups can be used as sorting and filtering criteria. Add, remove, and modify groups in Preferences &rarr; Groups. Groups can also be used for choosing the data location when adding transfers - this location is also set in the Groups preference window tab.</p>
<p>Transmission allows you to group torrents by color labels. Groups can be assigned upon adding a transfer to the list by establishing rules in Preferences → Groups. Groups can be manually changed with Transfers → Group and by dragging transfers to different groups in the main window (when "Use Groups" is enabled in the View menu).</p>
<p>These groups can be used as sorting and filtering criteria. Add, remove, and modify groups in Preferences Groups. Groups can also be used for choosing the data location when adding transfers - this location is also set in the Groups preference window tab.</p>
</div>
<div class="question">Where can I find more detailed information on my torrents?</div>
@ -122,7 +122,7 @@
<p>Yes, either upon opening a torrent, or once it has started. When you open a multi-file torrent, a detailed Open window will appear, allowing you to select specific files.</p>
<p>For transfers which are already running, double click them to open the Inspector, and then click the 'Files' tab. Simply check the boxes next to the files you want to download (the default is all of them).</p>
<p>You can even set a priority (high, normal, or low) to each file, if you want some to finish faster than others. To do so, use the selector that appears next to the checkboxes.</p>
<p>If the window doesn't appear when opening a torrent, ensure that "Display options window" is checked in Preferences &rarr; Transfers &rarr; Adding.</p>
<p>If the window doesn't appear when opening a torrent, ensure that "Display options window" is checked in Preferences → Transfers → Adding.</p>
</div>
</div><!-- questions-and-answers -->

View File

@ -23,13 +23,13 @@
<div class="pagetitle">
<h1>What is encryption?</h1>
</div>
<p>Transmission encrypts the connections it makes with other peers when necessary, using the RC4 cipher. The implementation is compatible with other clients such as Vuze and µTorrent. It is always enabled, however you can set Transmission (Preferences &rarr; Peers) to prefer encrypted peers or to ignore unencrypted peers completely.</p>
<p>Transmission encrypts the connections it makes with other peers when necessary, using the RC4 cipher. The implementation is compatible with other clients such as Vuze and µTorrent. It is always enabled, however you can set Transmission (Preferences Peers) to prefer encrypted peers or to ignore unencrypted peers completely.</p>
<p>Note that the latter option may make Transmission unconnectable in some swarms. The encryption feature does not mean your session is secure or anonymous, it is merely a way to avoid the traffic shaping measures some ISPs have implemented.</p>
<p>
<div class="pagetitle">
<h1>What are 'connections'?</h1>
</div>
<p><em>Global maximum connections</em> (Preferences &rarr; Peers) is the total number of peers that Transmission will connect to across all of your transfers. Connections per torrent can also be adjusted here, as well as in the Inspector.</p>
<p><em>Global maximum connections</em> (Preferences Peers) is the total number of peers that Transmission will connect to across all of your transfers. Connections per torrent can also be adjusted here, as well as in the Inspector.</p>
<p>It is recommended that these values are left at their default setting, as allowing too many connections will severely hinder web browsing and other online activities, as well as possibly crashing your router. Increase this value at your own risk!</p>
<div class="taskbox">
@ -43,7 +43,7 @@
<div class="pagetitle">
<h1>What is a blocklist?</h1>
</div>
<p>Transmission can block specific peers by utilizing a blocklist. An internet address for a blocklist file containing a list of IP addresses can be entered (Preferences &rarr; Peers) and configured to auto-update weekly. Blocklists can also be manually added into ~/Library/Application Support/Transmission.</p>
<p>Transmission can block specific peers by utilizing a blocklist. An internet address for a blocklist file containing a list of IP addresses can be entered (Preferences Peers) and configured to auto-update weekly. Blocklists can also be manually added into ~/Library/Application Support/Transmission.</p>
<p>The internet address may be to a text file or compressed file. Most standard compression formats are supported, including ZIP.</p>
</div>
</body>

View File

@ -19,7 +19,7 @@
<p>If this doesn't happen, you can add Transmission to the firewall manually:</p>
<div class="taskbox">
<ol>
<li>Open System Prefs &rarr; Security &rarr; Firewall. Make sure "Set access for specific services and applications" is selected.</li>
<li>Open System Prefs → Security → Firewall. Make sure "Set access for specific services and applications" is selected.</li>
<li>Click the "+" button and select Transmission from your Applications folder.</li>
<li>Make sure the pull down menu is set to "Allow incoming connections".
</ol>

View File

@ -14,13 +14,13 @@
<div class="pagetitle">
<h1>Port Forwarding a Router</h1>
</div>
<p>If you are using a router, it is probably OK to disable the macOS firewall, as you are already being protected by the router. To disable the firewall, open System Prefs &rarr; Security &rarr; Firewall. Click Stop.
<p>If you are using a router, it is probably OK to disable the macOS firewall, as you are already being protected by the router. To disable the firewall, open System Prefs → Security → Firewall. Click Stop.
<p>To forward a port in your router manually:
<div class="taskbox">
<ol>
<li>Find out what your IP address is. You can find your computer's IP address by going to System Prefs &rarr; Network, double-clicking on your connection (for instance, Built-in Ethernet), and clicking the TCP/IP tab. It's probably something like 192.168.1.100, or 10.0.1.2.</li>
<li>Find out what your IP address is. You can find your computer's IP address by going to System Prefs Network, double-clicking on your connection (for instance, Built-in Ethernet), and clicking the TCP/IP tab. It's probably something like 192.168.1.100, or 10.0.1.2.</li>
<li>Open Transmission, go to preferences, and enter a number for the port. It is recommended you pick a random number between 49152 and 65535. Let's use 50001 for now. Then quit Transmission.</li>
<li>Go into your router configuration screen. Normally this is done via your web browser using the address<a href="http://192.168.0.1">192.168.0.1</a> etc.
<br>Note: Apple's AirPort uses an application called 'AirPort Utility' to configure it.</li>
@ -52,7 +52,7 @@
</div>
<div class="taskbox">
<ol>
<li>Go to System Prefs &rarr; Network, double-click on your connection (for instance, Built-in Ethernet), and click the TCP/IP tab.
<li>Go to System Prefs Network, double-click on your connection (for instance, Built-in Ethernet), and click the TCP/IP tab.
<li>Write down the IP, Subnet Mask and Router addresses.
<li>Go to your router 'status' page via your web browser (AirPort Admin Utility if you are using an AirPort BS), and write down the DNS Server addresses. Alternatively, you can enter your router's internal IP (e.g. 192.168.0.1). This is sometimes quicker, as it refers to the router instead of the server.
<li>Then, return to the TCP/IP page in System Prefs.

View File

@ -19,7 +19,7 @@
<div class="taskbox">
<ol>
<li>Open Transmission.</li>
<li>Go to Preferences &rarr; Network, and check 'Automatically map port'.</li>
<li>Go to Preferences Network, and check 'Automatically map port'.</li>
<li>If you get a green dot and 'Port is Open' then you have successfully port forwarded!</li>
<li>If you get a red dot and the message 'Port is closed', <a href="troubleshoot.html">click here</a>.</li>
</ol>

View File

@ -24,7 +24,7 @@
<li>In the address bar, enter "http://localip:port/transmission/web/", where:</li>
<ul>
<li><em>localip</em> is the IP address of the computer Transmission is running on.</li>
<li><em>port</em> is the port specified in Preferences &rarr; Remote.</li>
<li><em>port</em> is the port specified in Preferences Remote.</li>
</ul>
</ol>
</div>

View File

@ -17,17 +17,17 @@
<ol>
<li>Make sure Transmission's <a href="portforward.html">port is forwarded</a>. Port forwarding makes it easier for others to connect to you, which therefore increases your speed.
<div class="taskbox">
<p>If your router supports NAT-PMP, UPnP, or you have Apple AirPort, Transmission can do this automatically; just tick the checkbox in Preferences &rarr; Network.
<p>If your router supports NAT-PMP, UPnP, or you have Apple AirPort, Transmission can do this automatically; just tick the checkbox in Preferences Network.
</div>
</li>
<li>Make sure you cap your upload speed, so that it isn't flooded. A good rule of thumb is about 60-70% of your maximum upload bandwidth. This can be adjusted in Preferences &rarr; Bandwidth, or in real time using the Action menu.
<li>Make sure you cap your upload speed, so that it isn't flooded. A good rule of thumb is about 60-70% of your maximum upload bandwidth. This can be adjusted in Preferences Bandwidth, or in real time using the Action menu.
<div class="taskbox">
<p>eg. If your upload connection is 256 Kilobits/sec, then you should cap it at 21 KB/sec ((<strong>256</strong> / 8) * 0.66 = <strong>21</strong>).
</div>
</li>
<li><a href="gettingstarted.html#queue">Queue</a> your transfers. Transmission's queue preferences are located in Transfers &rarr; Management.
<li><a href="gettingstarted.html#queue">Queue</a> your transfers. Transmission's queue preferences are located in Transfers Management.
<p>Remember, your download speed is proportional to how fast you upload. If there are many transfers running, then each transfer will only receive a small proportion of your upload bandwidth, reducing their respective download speeds.
To avoid spreading your upload too thinly, a good rule of thumb is to have at least 128 KBit/sec of upload bandwidth for every torrent you wish to run simultaneously.

View File

@ -14,7 +14,7 @@
<div class="pagetitle">
<h1>Can I add and remove trackers in my torrents?</h1>
</div>
<p>Yes. To add trackers to a currently running torrent, go to Inspector &rarr; Tracker, and click the plus button (+). To remove them, click minus button (-). Multiple trackers can also be added to torrent files you create. Each newly-added tracker will be placed in a new tier.
<p>Yes. To add trackers to a currently running torrent, go to Inspector Tracker, and click the plus button (+). To remove them, click minus button (-). Multiple trackers can also be added to torrent files you create. Each newly-added tracker will be placed in a new tier.
<p>
<div class="pagetitle">
<h1>What is 'Tier 1', 'Tier 2', etc?</h1>
@ -24,7 +24,7 @@
<div class="pagetitle">
<h1>What does 'announce' mean?</h1>
</div>
<p>When Transmission <em>announces</em>, it is updating its presence to the tracker and asking for more peers. This happens periodically, at the discretion of the tracker, however can be manually invoked via Transfers menu &rarr; Update Tracker.
<p>When Transmission <em>announces</em>, it is updating its presence to the tracker and asking for more peers. This happens periodically, at the discretion of the tracker, however can be manually invoked via Transfers menu Update Tracker.
<p>
<div class="pagetitle">
<h1>What does 'scrape' mean?</h1>

View File

@ -17,14 +17,14 @@
<ul>
<li>Torrent files contain information about the actual file you want to download, and connect you to the swarm of peers sharing it.</li>
<li>Transmission can watch a certain folder (e.g. your Safari download folder) for torrent files and then open them automatically via Preferences &rarr; Transfers &rarr; General.</li>
<li>Transmission can watch a certain folder (e.g. your Safari download folder) for torrent files and then open them automatically via Preferences → Transfers → General.</li>
<li>By default, Transmission deletes the original torrent file upon opening. If you remove a transfer, in order to resume it you will need to reopen the original torrent file in Transmission. Simply choose 'Save Torrent File As…' from the File menu before deletion to avoid having to download the torrent file again.</li>
<li>Once your download is complete, you can set a default ratio to automatically seed to, and then pause. This can be adjusted in Preferences &rarr; Transfers &rarr; Management, or in real time using the Action menu.</li>
<li>Once your download is complete, you can set a default ratio to automatically seed to, and then pause. This can be adjusted in Preferences → Transfers → Management, or in real time using the Action menu.</li>
<li>Both seeding and downloading transfers can be queued, and Transmission can skip over stalled transfers, in order to maximise queuing efficiency.
Queuing can be configured in Preferences &rarr; Transfers &rarr; Management.</li>
Queuing can be configured in Preferences → Transfers → Management.</li>
</ul>

10
macosx/Utils.h Normal file
View File

@ -0,0 +1,10 @@
// This file Copyright © Transmission authors and contributors.
// It may be used under the MIT (SPDX: MIT) license.
// License text can be found in the licenses/ folder.
#import <Foundation/Foundation.h>
// 0.1 precision
bool isSpeedEqual(CGFloat old_speed, CGFloat new_speed);
// 0.01 precision
bool isRatioEqual(CGFloat old_ratio, CGFloat new_ratio);

19
macosx/Utils.mm Normal file
View File

@ -0,0 +1,19 @@
// This file Copyright © Transmission authors and contributors.
// It may be used under the MIT (SPDX: MIT) license.
// License text can be found in the licenses/ folder.
#include <cmath>
#import "Utils.h"
bool isSpeedEqual(CGFloat old_speed, CGFloat new_speed)
{
static CGFloat constexpr kSpeedCompareEps = 0.1 / 2;
return std::abs(new_speed - old_speed) < kSpeedCompareEps;
}
bool isRatioEqual(CGFloat old_ratio, CGFloat new_ratio)
{
static CGFloat constexpr kRatioCompareEps = 0.01 / 2;
return std::abs(new_ratio - old_ratio) < kRatioCompareEps;
}

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

@ -0,0 +1,15 @@
// This file Copyright © 2023 Transmission authors and contributors.
// It may be used under the MIT (SPDX: MIT) license.
// License text can be found in the licenses/ folder.
#import <Foundation/Foundation.h>
#import <Sparkle/SUVersionComparisonProtocol.h>
NS_ASSUME_NONNULL_BEGIN
@interface VersionComparator : NSObject<SUVersionComparison>
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,39 @@
// This file Copyright © 2023 Transmission authors and contributors.
// It may be used under the MIT (SPDX: MIT) license.
// License text can be found in the licenses/ folder.
#import "VersionComparator.h"
@implementation VersionComparator
- (NSComparisonResult)compareVersion:(NSString*)versionA toVersion:(NSString*)versionB
{
// Transmission version format follows:
// 14714+major.minor.patch&beta
// 5.0.1-dev -> 14719.0.100
// 5.0.1-beta.1 -> 14719.0.101
// 5.0.1 -> 14719.0.199
NSArray<NSString*>* versionBComponents = [versionB componentsSeparatedByString:@"."];
if (versionBComponents.count > 2 && versionBComponents[2].integerValue % 100 != 99 &&
![NSUserDefaults.standardUserDefaults boolForKey:@"AutoUpdateBeta"] &&
![[[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString*)kCFBundleVersionKey] isEqualToString:versionB])
{
// pre-releases are ignored
return NSOrderedDescending;
}
NSArray<NSString*>* versionAComponents = [versionA componentsSeparatedByString:@"."];
for (NSUInteger idx = 0; versionAComponents.count > idx || versionBComponents.count > idx; idx++)
{
NSInteger vA = versionAComponents.count > idx ? versionAComponents[idx].integerValue : 0;
NSInteger vB = versionBComponents.count > idx ? versionBComponents[idx].integerValue : 0;
if (vA < vB)
{
return NSOrderedAscending;
}
if (vA > vB)
{
return NSOrderedDescending;
}
}
return NSOrderedSame;
}
@end

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

@ -130,8 +130,7 @@ void RpcClient::sendNetworkRequest(TrVariantPtr req, QFutureInterface<RpcRespons
if (verbose_)
{
qInfo() << "sending"
<< "POST" << qPrintable(url_.path());
qInfo() << "sending POST " << qPrintable(url_.path());
for (QByteArray const& b : request_->rawHeaderList())
{

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_ = {};

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