diff --git a/.clang-format b/.clang-format index b30567097..cf9fa7365 100644 --- a/.clang-format +++ b/.clang-format @@ -55,7 +55,7 @@ ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 FixNamespaceComments: true IndentGotoLabels: false -KeepEmptyLinesAtTheStartOfBlocks: true +KeepEmptyLinesAtTheStartOfBlocks: false QualifierAlignment: Right SortUsingDeclarations: true SpaceAfterTemplateKeyword: false diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 9383c14da..832f24501 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -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 diff --git a/.gitmodules b/.gitmodules index 07c4836a7..03f5af33e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/AUTHORS b/AUTHORS index 78a8e93a5..46a41cf77 100644 --- a/AUTHORS +++ b/AUTHORS @@ -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) diff --git a/CMakeLists.txt b/CMakeLists.txt index fda16eeac..ac786853f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 143c4ebc4..514ca7bf5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. `` over `` - 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: diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index f0503bcbf..93142f2a5 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -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 = ""; }; C841A28029197724009F18E8 /* NSKeyedUnarchiverAdditions.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NSKeyedUnarchiverAdditions.mm; sourceTree = ""; }; C843FC8329C51B9400491854 /* utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = utils.mm; sourceTree = ""; }; + C843FC8529C8B40800491854 /* VersionComparator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VersionComparator.h; sourceTree = ""; }; + C843FC8629C8B40800491854 /* VersionComparator.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VersionComparator.mm; sourceTree = ""; }; C86BCD9828228A9600F45599 /* SparkleProxy.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SparkleProxy.mm; sourceTree = ""; }; 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 = ""; }; + C8ED0FB0281C10F100B44472 /* addr_is_reserved.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = addr_is_reserved.h; sourceTree = ""; }; CAB35C62252F6F5E00552A55 /* mime-types.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "mime-types.h"; sourceTree = ""; }; CCEBA596277340F6DF9F4481 /* session-alt-speeds.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "session-alt-speeds.cc"; sourceTree = ""; }; CCEBA596277340F6DF9F4483 /* session-alt-speeds.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = "session-alt-speeds.h"; sourceTree = ""; }; @@ -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 = ""; }; ED8A163D2735A8AA000D61F9 /* peer-mgr-wishlist.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "peer-mgr-wishlist.h"; sourceTree = ""; }; ED8A163E2735A8AA000D61F9 /* peer-mgr-wishlist.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "peer-mgr-wishlist.cc"; sourceTree = ""; }; + ED9862952B979AA2002F3035 /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utils.h; sourceTree = ""; }; + ED9862962B979AA2002F3035 /* Utils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Utils.mm; sourceTree = ""; }; EDBAAC8B29E486BC00D9495F /* global-ip-cache.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "global-ip-cache.h"; sourceTree = ""; }; EDBAAC8D29E486C200D9495F /* global-ip-cache.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "global-ip-cache.cc"; sourceTree = ""; }; EDBDFA9D25AFCCA60093D9C1 /* evutil_time.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = evutil_time.c; sourceTree = ""; }; @@ -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 = ""; }; BE75C3570C72A0D600DBEFE0 /* libevent */ = { @@ -2274,6 +2261,51 @@ name = Compatibility; sourceTree = ""; }; + 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 = ""; + }; + 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 = ""; + }; 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", diff --git a/appveyor.yml b/appveyor.yml index 77086e149..cdacf14b7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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 diff --git a/daemon/daemon.cc b/daemon/daemon.cc index 8c82e09ff..6de4f584f 100644 --- a/daemon/daemon.cc +++ b/daemon/daemon.cc @@ -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{}; 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); } diff --git a/daemon/daemon.h b/daemon/daemon.h index a25eea529..b679cc6dd 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -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_ = {}; diff --git a/docs/Editing-Configuration-Files.md b/docs/Editing-Configuration-Files.md index bb6d4a7ed..37c6962fb 100644 --- a/docs/Editing-Configuration-Files.md +++ b/docs/Editing-Configuration-Files.md @@ -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) diff --git a/docs/Scripts.md b/docs/Scripts.md index d6c8adaa2..865958e54 100644 --- a/docs/Scripts.md +++ b/docs/Scripts.md @@ -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. diff --git a/docs/Why-is-my-port-closed.md b/docs/Why-is-my-port-closed.md index 66daa6a9e..6b1a7c085 100644 --- a/docs/Why-is-my-port-closed.md +++ b/docs/Why-is-my-port-closed.md @@ -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 → Netgear router → 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. diff --git a/docs/rpc-spec.md b/docs/rpc-spec.md index b224badca..964f3091e 100644 --- a/docs/rpc-spec.md +++ b/docs/rpc-spec.md @@ -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: diff --git a/gtk/OptionsDialog.cc b/gtk/OptionsDialog.cc index 31b72f3bf..63463f014 100644 --- a/gtk/OptionsDialog.cc +++ b/gtk/OptionsDialog.cc @@ -386,7 +386,6 @@ TorrentFileChooserDialog::TorrentFileChooserDialog(Gtk::Window& parent, Glib::Re void TorrentUrlChooserDialog::onOpenURLResponse(int response, Gtk::Entry const& entry, Glib::RefPtr const& core) { - if (response == TR_GTK_RESPONSE_TYPE(CANCEL)) { close(); diff --git a/gtk/transmission-gtk.metainfo.xml.in b/gtk/transmission-gtk.metainfo.xml.in index 9b8d75a38..599fe08ac 100644 --- a/gtk/transmission-gtk.metainfo.xml.in +++ b/gtk/transmission-gtk.metainfo.xml.in @@ -61,4 +61,5 @@ Copyright 2017 Endless Mobile, Inc. transmission-gtk + transmission-gtk.desktop diff --git a/libtransmission/announcer-http.cc b/libtransmission/announcer-http.cc index 8c0ce7c40..dafd6053f 100644 --- a/libtransmission/announcer-http.cc +++ b/libtransmission/announcer-http.cc @@ -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_ = {}; diff --git a/libtransmission/block-info.h b/libtransmission/block-info.h index 18a4c412f..cd06c67b4 100644 --- a/libtransmission/block-info.h +++ b/libtransmission/block-info.h @@ -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 { diff --git a/libtransmission/blocklist.cc b/libtransmission/blocklist.cc index 336d6f0bc..9ae103efe 100644 --- a/libtransmission/blocklist.cc +++ b/libtransmission/blocklist.cc @@ -204,10 +204,12 @@ std::optional 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(&tmp)); + tmp = htonl(ip_u | (~mask)); + std::tie(addrpair.second, std::ignore) = tr_address::from_compact_ipv4(reinterpret_cast(&tmp)); return addrpair; } diff --git a/libtransmission/completion.cc b/libtransmission/completion.cc index d71369711..87afecf8a 100644 --- a/libtransmission/completion.cc +++ b/libtransmission/completion.cc @@ -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); } diff --git a/libtransmission/completion.h b/libtransmission/completion.h index 9897f3005..b2dfded19 100644 --- a/libtransmission/completion.h +++ b/libtransmission/completion.h @@ -10,8 +10,9 @@ #endif #include -#include #include // size_t +#include +#include #include #include @@ -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; - 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 }; diff --git a/libtransmission/favicon-cache.h b/libtransmission/favicon-cache.h index 9e65f9fcd..c15ea1b63 100644 --- a/libtransmission/favicon-cache.h +++ b/libtransmission/favicon-cache.h @@ -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 */ }) { diff --git a/libtransmission/handshake.cc b/libtransmission/handshake.cc index 0753136c6..63297198e 100644 --- a/libtransmission/handshake.cc +++ b/libtransmission/handshake.cc @@ -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(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(); } // --- diff --git a/libtransmission/magnet-metainfo.cc b/libtransmission/magnet-metainfo.cc index 3cb783aae..7086f7f23 100644 --- a/libtransmission/magnet-metainfo.cc +++ b/libtransmission/magnet-metainfo.cc @@ -134,7 +134,7 @@ std::optional parseBase32Hash(std::string_view sv) std::optional 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 parseHash(std::string_view sv) std::optional 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) diff --git a/libtransmission/net.cc b/libtransmission/net.cc index b248f4b02..865ad4d9b 100644 --- a/libtransmission/net.cc +++ b/libtransmission/net.cc @@ -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(&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(&optval), sizeof(optval)); - (void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&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(&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(&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{}; + 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(&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(&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::from_string(std::string_view address_sv) { auto const address_sz = tr_strbuf{ 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(&ss), &sslen) != 0) { + return {}; + } + + auto addr = tr_address{}; + switch (ss.ss_family) + { + case AF_INET: + addr.addr.addr4 = reinterpret_cast(&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(&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::from_sockaddr(struct sockaddr const* from) diff --git a/libtransmission/net.h b/libtransmission/net.h index fc23685f4..2bc4aa71a 100644 --- a/libtransmission/net.h +++ b/libtransmission/net.h @@ -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 +struct std::hash { public: std::size_t operator()(tr_socket_address const& socket_address) const noexcept diff --git a/libtransmission/peer-common.h b/libtransmission/peer-common.h index 55ba2a754..d53e2d835 100644 --- a/libtransmission/peer-common.h +++ b/libtransmission/peer-common.h @@ -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; diff --git a/libtransmission/peer-io.cc b/libtransmission/peer-io.cc index 556f81673..d57acb62e 100644 --- a/libtransmission/peer-io.cc +++ b/libtransmission/peer-io.cc @@ -129,11 +129,6 @@ std::shared_ptr 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, 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)); diff --git a/libtransmission/peer-mgr-active-requests.cc b/libtransmission/peer-mgr-active-requests.cc index 0a7d87ea4..c66a60bf3 100644 --- a/libtransmission/peer-mgr-active-requests.cc +++ b/libtransmission/peer-mgr-active-requests.cc @@ -21,7 +21,7 @@ #include "libtransmission/peer-mgr-wishlist.h" #include "libtransmission/tr-assert.h" -class tr_peer; +struct tr_peer; class ActiveRequests::Impl { diff --git a/libtransmission/peer-mgr-active-requests.h b/libtransmission/peer-mgr-active-requests.h index 325bd5796..1116e44f6 100644 --- a/libtransmission/peer-mgr-active-requests.h +++ b/libtransmission/peer-mgr-active-requests.h @@ -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 -- diff --git a/libtransmission/peer-mgr.cc b/libtransmission/peer-mgr.cc index fc0550960..3f19c4a92 100644 --- a/libtransmission/peer-mgr.cc +++ b/libtransmission/peer-mgr.cc @@ -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> webseeds; + std::vector> 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(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_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 io, tr_peer_info* peer_info, tr_interned_string client) +void create_bit_torrent_peer(tr_torrent& tor, std::shared_ptr 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 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{}; + 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(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; } diff --git a/libtransmission/peer-mgr.h b/libtransmission/peer-mgr.h index c2b2262cf..6835a5e9a 100644 --- a/libtransmission/peer-mgr.h +++ b/libtransmission/peer-mgr.h @@ -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; diff --git a/libtransmission/peer-msgs.cc b/libtransmission/peer-msgs.cc index c308abb5a..b521404c9 100644 --- a/libtransmission/peer-msgs.cc +++ b/libtransmission/peer-msgs.cc @@ -10,8 +10,8 @@ #include #include // uint8_t, uint32_t, int64_t #include +#include #include -#include #include // std::unique_ptr #include #include @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -58,19 +59,17 @@ struct tr_error; using namespace std::literals; +namespace +{ // initial capacity is big enough to hold a BtPeerMsgs::Piece message using MessageBuffer = libtransmission::StackBuffer>; using MessageReader = libtransmission::BufferReader; using MessageWriter = libtransmission::BufferWriter; -namespace -{ - // these values are hardcoded by various BEPs as noted namespace BtPeerMsgs { - -// http://bittorrent.org/beps/bep_0003.html#peer-messages +// https://www.bittorrent.org/beps/bep_0003.html#peer-messages auto constexpr Choke = uint8_t{ 0 }; auto constexpr Unchoke = uint8_t{ 1 }; auto constexpr Interested = uint8_t{ 2 }; @@ -81,7 +80,7 @@ auto constexpr Request = uint8_t{ 6 }; auto constexpr Piece = uint8_t{ 7 }; auto constexpr Cancel = uint8_t{ 8 }; -// http://bittorrent.org/beps/bep_0005.html +// https://www.bittorrent.org/beps/bep_0005.html#bittorrent-protocol-extension auto constexpr Port = uint8_t{ 9 }; // https://www.bittorrent.org/beps/bep_0006.html @@ -91,7 +90,7 @@ auto constexpr FextHaveNone = uint8_t{ 15 }; auto constexpr FextReject = uint8_t{ 16 }; auto constexpr FextAllowedFast = uint8_t{ 17 }; -// http://bittorrent.org/beps/bep_0010.html +// https://www.bittorrent.org/beps/bep_0010.html // see also LtepMessageIds below auto constexpr Ltep = uint8_t{ 20 }; @@ -141,12 +140,12 @@ auto constexpr Ltep = uint8_t{ 20 }; namespace LtepMessages { -// http://bittorrent.org/beps/bep_0010.html +// https://www.bittorrent.org/beps/bep_0010.html auto constexpr Handshake = uint8_t{ 0 }; } // namespace LtepMessages -// http://bittorrent.org/beps/bep_0010.html +// https://www.bittorrent.org/beps/bep_0010.html // Client-defined extension message IDs that we tell peers about // in the LTEP handshake and will respond to when sent in an LTEP // message. @@ -162,7 +161,7 @@ enum LtepMessageIds : uint8_t UT_METADATA_ID = 3, }; -// http://bittorrent.org/beps/bep_0009.html +// https://www.bittorrent.org/beps/bep_0009.html namespace MetadataMsgType { @@ -172,25 +171,23 @@ auto constexpr Reject = 2; } // namespace MetadataMsgType -auto constexpr MinChokePeriodSec = 10; +auto constexpr MinChokePeriodSec = time_t{ 10 }; // idle seconds before we send a keepalive -auto constexpr KeepaliveIntervalSecs = 100; +auto constexpr KeepaliveIntervalSecs = time_t{ 100 }; -auto constexpr MetadataReqQ = 64; +auto constexpr MetadataReqQ = size_t{ 64U }; auto constexpr ReqQ = 512; -// used in lowering the outMessages queue period - // when we're making requests from another peer, // batch them together to send enough requests to // meet our bandwidth goals for the next N seconds -auto constexpr RequestBufSecs = 10; +auto constexpr RequestBufSecs = time_t{ 10 }; // --- -auto constexpr MaxPexPeerCount = size_t{ 50 }; +auto constexpr MaxPexPeerCount = size_t{ 50U }; // --- @@ -213,13 +210,13 @@ struct peer_request { return this->index == that.index && this->offset == that.offset && this->length == that.length; } -}; -peer_request blockToReq(tr_torrent const* tor, tr_block_index_t block) -{ - auto const loc = tor->block_loc(block); - return peer_request{ loc.piece, loc.piece_offset, tor->block_size(block) }; -} + [[nodiscard]] static auto from_block(tr_torrent const& tor, tr_block_index_t block) noexcept + { + auto const loc = tor.block_loc(block); + return peer_request{ loc.piece, loc.piece_offset, tor.block_size(block) }; + } +}; // --- @@ -263,62 +260,12 @@ struct tr_incoming private: std::bitset have_; - size_t const block_size_; + uint32_t const block_size_; }; - std::map blocks; + std::unordered_map blocks; }; -[[nodiscard]] bool is_valid_request(tr_torrent const& tor, tr_piece_index_t index, uint32_t offset, uint32_t length) -{ - int err = 0; - - if (index >= tor.piece_count()) - { - err = 1; - } - else if (length < 1) - { - err = 2; - } - else if (offset + length > tor.piece_size(index)) - { - err = 3; - } - else if (length > tr_block_info::BlockSize) - { - err = 4; - } - else if (tor.piece_loc(index, offset, length).byte > tor.total_size()) - { - err = 5; - } - - if (err != 0) - { - tr_logAddTraceTor(&tor, fmt::format("index {} offset {} length {} err {}", index, offset, length, err)); - } - - return err == 0; -} - -class tr_peerMsgsImpl; -// TODO: make these to be member functions -ReadState canRead(tr_peerIo* io, void* vmsgs, size_t* piece); -void cancelAllRequestsToClient(tr_peerMsgsImpl* msgs); -void didWrite(tr_peerIo* io, size_t bytes_written, bool was_piece_data, void* vmsgs); -void gotError(tr_peerIo* io, tr_error const& err, void* vmsgs); -void peerPulse(void* vmsgs); -size_t protocolSendCancel(tr_peerMsgsImpl* msgs, struct peer_request const& req); -size_t protocolSendChoke(tr_peerMsgsImpl* msgs, bool choke); -size_t protocolSendHave(tr_peerMsgsImpl* msgs, tr_piece_index_t index); -size_t protocolSendPort(tr_peerMsgsImpl* msgs, tr_port port); -size_t protocolSendRequest(tr_peerMsgsImpl* msgs, struct peer_request const& req); -void sendInterest(tr_peerMsgsImpl* msgs, bool b); -void sendLtepHandshake(tr_peerMsgsImpl* msgs); -void tellPeerWhatWeHave(tr_peerMsgsImpl* msgs); -void updateDesiredRequestCount(tr_peerMsgsImpl* msgs); - #define myLogMacro(msgs, level, text) \ do \ { \ @@ -328,8 +275,8 @@ void updateDesiredRequestCount(tr_peerMsgsImpl* msgs); __FILE__, \ __LINE__, \ (level), \ - fmt::format("{:s} [{:s}]: {:s}", (msgs)->io->display_name(), (msgs)->user_agent().sv(), text), \ - (msgs)->torrent->name()); \ + fmt::format("{:s} [{:s}]: {:s}", (msgs)->display_name(), (msgs)->user_agent().sv(), text), \ + (msgs)->tor_.name()); \ } \ } while (0) @@ -357,39 +304,39 @@ class tr_peerMsgsImpl final : public tr_peerMsgs { public: tr_peerMsgsImpl( - tr_torrent* torrent_in, + tr_torrent& torrent_in, tr_peer_info* const peer_info_in, std::shared_ptr io_in, tr_interned_string client, tr_peer_callback_bt callback, void* callback_data) : tr_peerMsgs{ torrent_in, peer_info_in, client, io_in->is_encrypted(), io_in->is_incoming(), io_in->is_utp() } - , torrent{ torrent_in } - , io{ std::move(io_in) } - , have_{ torrent_in->piece_count() } + , tor_{ torrent_in } + , io_{ std::move(io_in) } + , have_{ torrent_in.piece_count() } , callback_{ callback } , callback_data_{ callback_data } { - if (torrent->allows_pex()) + if (tor_.allows_pex()) { - pex_timer_ = session->timerMaker().create([this]() { sendPex(); }); + pex_timer_ = session->timerMaker().create([this]() { send_ut_pex(); }); pex_timer_->start_repeating(SendPexInterval); } - if (io->supports_ltep()) + if (io_->supports_ltep()) { - sendLtepHandshake(this); + send_ltep_handshake(); } - tellPeerWhatWeHave(this); + protocol_send_bitfield(); - if (session->allowsDHT() && io->supports_dht()) + if (session->allowsDHT() && io_->supports_dht()) { - protocolSendPort(this, session->udpPort()); + protocol_send_port(session->udpPort()); } - io->set_callbacks(canRead, didWrite, gotError, this); - updateDesiredRequestCount(this); + io_->set_callbacks(can_read, did_write, got_error, this); + update_desired_request_count(); update_active(); } @@ -404,23 +351,25 @@ public: set_active(TR_UP, false); set_active(TR_DOWN, false); - if (io) + if (io_) { - io->clear(); + io_->clear(); } } + // --- + [[nodiscard]] Speed get_piece_speed(uint64_t now, tr_direction dir) const override { - return io->get_piece_speed(now, dir); + return io_->get_piece_speed(now, dir); } - [[nodiscard]] size_t activeReqCount(tr_direction dir) const noexcept override + [[nodiscard]] size_t active_req_count(tr_direction dir) const noexcept override { switch (dir) { case TR_CLIENT_TO_PEER: // requests we sent - return tr_peerMgrCountActiveRequestsToPeer(torrent, this); + return tr_peerMgrCountActiveRequestsToPeer(&tor_, this); case TR_PEER_TO_CLIENT: // requests they sent return std::size(peer_requested_); @@ -433,7 +382,7 @@ public: [[nodiscard]] tr_socket_address socket_address() const override { - return io->socket_address(); + return io_->socket_address(); } [[nodiscard]] std::string display_name() const override @@ -446,58 +395,54 @@ public: return have_; } - void onTorrentGotMetainfo() noexcept override - { - invalidatePercentDone(); + // --- + void on_torrent_got_metainfo() noexcept override + { update_active(); } - void invalidatePercentDone() - { - updateInterest(); - } - void cancel_block_request(tr_block_index_t block) override { - protocolSendCancel(this, blockToReq(torrent, block)); + cancels_sent_to_peer.add(tr_time(), 1); + protocol_send_cancel(peer_request::from_block(tor_, block)); } void set_choke(bool peer_is_choked) override { - time_t const now = tr_time(); - time_t const fibrillation_time = now - MinChokePeriodSec; + auto const now = tr_time(); + auto const fibrillation_time = now - MinChokePeriodSec; - if (chokeChangedAt > fibrillation_time) + if (choke_changed_at_ > fibrillation_time) { - // TODO logtrace(msgs, "Not changing choke to %d to avoid fibrillation", peer_is_choked); + logtrace(this, fmt::format("Not changing choke to {} to avoid fibrillation", peer_is_choked)); } else if (this->peer_is_choked() != peer_is_choked) { set_peer_choked(peer_is_choked); + // https://www.bittorrent.org/beps/bep_0006.html#reject-request + // A peer SHOULD choke first and then reject requests so that + // the peer receiving the choke does not re-request the pieces. + protocol_send_choke(peer_is_choked); if (peer_is_choked) { - cancelAllRequestsToClient(this); + reject_all_requests(); } - protocolSendChoke(this, peer_is_choked); - chokeChangedAt = now; + choke_changed_at_ = now; update_active(TR_CLIENT_TO_PEER); } } - void pulse() override - { - peerPulse(this); - } + void pulse() override; void on_piece_completed(tr_piece_index_t piece) override { - protocolSendHave(this, piece); + protocol_send_have(piece); // since we have more pieces now, we might not be interested in this peer - updateInterest(); + update_interest(); } void set_interested(bool interested) override @@ -505,26 +450,16 @@ public: if (client_is_interested() != interested) { set_client_interested(interested); - sendInterest(this, interested); + protocol_send_interest(interested); update_active(TR_PEER_TO_CLIENT); } } - void updateInterest() - { - // TODO -- might need to poke the mgr on startup - } + // --- - // - - [[nodiscard]] bool isValidRequest(peer_request const& req) const + void request_blocks(tr_block_span_t const* block_spans, size_t n_spans) override { - return is_valid_request(*torrent, req.index, req.offset, req.length); - } - - void requestBlocks(tr_block_span_t const* block_spans, size_t n_spans) override - { - TR_ASSERT(torrent->client_can_download()); + TR_ASSERT(tor_.client_can_download()); TR_ASSERT(client_is_interested()); TR_ASSERT(!client_is_choked()); @@ -535,99 +470,24 @@ public: // Note that requests can't cross over a piece boundary. // So if a piece isn't evenly divisible by the block size, // we need to split our block request info per-piece chunks. - auto const byte_begin = torrent->block_loc(block).byte; - auto const block_size = torrent->block_size(block); + auto const byte_begin = tor_.block_loc(block).byte; + auto const block_size = tor_.block_size(block); auto const byte_end = byte_begin + block_size; for (auto offset = byte_begin; offset < byte_end;) { - auto const loc = torrent->byte_loc(offset); + auto const loc = tor_.byte_loc(offset); auto const left_in_block = block_size - loc.block_offset; - auto const left_in_piece = torrent->piece_size(loc.piece) - loc.piece_offset; + auto const left_in_piece = tor_.piece_size(loc.piece) - loc.piece_offset; auto const req_len = std::min(left_in_block, left_in_piece); - protocolSendRequest(this, { loc.piece, loc.piece_offset, req_len }); + protocol_send_request({ loc.piece, loc.piece_offset, req_len }); offset += req_len; } } - tr_peerMgrClientSentRequests(torrent, this, *span); + tr_peerMgrClientSentRequests(&tor_, this, *span); } } - // how many blocks could we request from this peer right now? - [[nodiscard]] RequestLimit canRequest() const noexcept override - { - auto const max_blocks = maxAvailableReqs(); - return RequestLimit{ max_blocks, max_blocks }; - } - - void sendPex(); - - void publish(tr_peer_event const& peer_event) - { - if (callback_ != nullptr) - { - (*callback_)(this, peer_event, callback_data_); - } - } - -private: - [[nodiscard]] size_t maxAvailableReqs() const - { - if (torrent->is_done() || !torrent->has_metainfo() || client_is_choked() || !client_is_interested()) - { - return 0; - } - - // Get the rate limit we should use. - // TODO: this needs to consider all the other peers as well... - uint64_t const now = tr_time_msec(); - auto rate = get_piece_speed(now, TR_PEER_TO_CLIENT); - if (torrent->uses_speed_limit(TR_PEER_TO_CLIENT)) - { - rate = std::min(rate, torrent->speed_limit(TR_PEER_TO_CLIENT)); - } - - // honor the session limits, if enabled - if (torrent->uses_session_limits()) - { - if (auto const limit = torrent->session->active_speed_limit(TR_PEER_TO_CLIENT); limit) - { - rate = std::min(rate, *limit); - } - } - - // use this desired rate to figure out how - // many requests we should send to this peer - size_t constexpr Floor = 32; - size_t constexpr Seconds = RequestBufSecs; - size_t const estimated_blocks_in_period = (rate.base_quantity() * Seconds) / tr_block_info::BlockSize; - size_t const ceil = reqq ? *reqq : 250; - - auto max_reqs = estimated_blocks_in_period; - max_reqs = std::min(max_reqs, ceil); - max_reqs = std::max(max_reqs, Floor); - return max_reqs; - } - - [[nodiscard]] bool calculate_active(tr_direction direction) const - { - if (direction == TR_CLIENT_TO_PEER) - { - return peer_is_interested() && !peer_is_choked(); - } - - // TR_PEER_TO_CLIENT - - if (!torrent->has_metainfo()) - { - return true; - } - - auto const active = client_is_interested() && !client_is_choked(); - TR_ASSERT(!active || !torrent->is_done()); - return active; - } - void update_active() { update_active(TR_UP); @@ -637,66 +497,227 @@ private: void update_active(tr_direction direction) { TR_ASSERT(tr_isDirection(direction)); - set_active(direction, calculate_active(direction)); } -public: - bool peerSupportsPex = false; - bool peerSupportsMetadataXfer = false; - bool clientSentLtepHandshake = false; + [[nodiscard]] bool calculate_active(tr_direction direction) const + { + if (direction == TR_CLIENT_TO_PEER) + { + return peer_is_interested() && !peer_is_choked(); + } - size_t desired_request_count = 0; + // TR_PEER_TO_CLIENT - uint8_t ut_pex_id = 0; - uint8_t ut_metadata_id = 0; + if (!tor_.has_metainfo()) + { + return true; + } - tr_port dht_port; + auto const active = client_is_interested() && !client_is_choked(); + TR_ASSERT(!active || !tor_.is_done()); + return active; + } - EncryptionPreference encryption_preference = EncryptionPreference::Unknown; +private: + // --- - tr_torrent* const torrent; + void update_interest() + { + // TODO(ckerr) -- might need to poke the mgr on startup - std::shared_ptr const io; + // additional note (tearfur) + // by "poke the mgr", Charles probably meant calling isPeerInteresting(), + // then pass the result to set_interesting() + } - std::vector peer_requested_; + // --- - std::array, NUM_TR_AF_INET_TYPES> pex; + [[nodiscard]] bool is_valid_request(peer_request const& req) const; - std::queue peerAskedForMetadata; + void reject_all_requests() + { + auto& queue = peer_requested_; - time_t clientSentAnythingAt = 0; + if (auto const must_send_rej = io_->supports_fext(); must_send_rej) + { + std::for_each(std::begin(queue), std::end(queue), [this](peer_request const& req) { protocol_send_reject(req); }); + } - time_t chokeChangedAt = 0; + queue.clear(); + } - /* when we started batching the outMessages */ - // time_t outMessagesBatchedAt = 0; + [[nodiscard]] bool can_add_request_from_peer(peer_request const& req); - struct tr_incoming incoming = {}; + void on_peer_made_request(peer_request const& req) + { + if (can_add_request_from_peer(req)) + { + peer_requested_.emplace_back(req); + } + else if (io_->supports_fext()) + { + protocol_send_reject(req); + } + } - /* if the peer supports the Extension Protocol in BEP 10 and - supplied a reqq argument, it's stored here. */ - std::optional reqq; + // how many blocks could we request from this peer right now? + [[nodiscard]] size_t max_available_reqs() const; + + void update_desired_request_count() + { + desired_request_count_ = max_available_reqs(); + } + + void update_block_requests(); + + // --- + + [[nodiscard]] std::optional pop_next_metadata_request() + { + auto& reqs = peer_requested_metadata_pieces_; + + if (std::empty(reqs)) + { + return {}; + } + + auto next = reqs.front(); + reqs.pop(); + return next; + } + + void update_metadata_requests(time_t now) const; + [[nodiscard]] size_t add_next_metadata_piece(); + [[nodiscard]] size_t add_next_block(time_t now_sec, uint64_t now_msec); + [[nodiscard]] size_t fill_output_buffer(time_t now_sec, uint64_t now_msec); + + // --- + + void send_ltep_handshake(); + void parse_ltep_handshake(MessageReader& payload); + void parse_ut_metadata(MessageReader& payload_in); + void parse_ut_pex(MessageReader& payload); + void parse_ltep(MessageReader& payload); + + void send_ut_pex(); + + int client_got_block(std::unique_ptr block_data, tr_block_index_t block); + ReadResult read_piece_data(MessageReader& payload); + ReadResult process_peer_message(uint8_t id, MessageReader& payload); + + // --- + + size_t protocol_send_keepalive() const; + + template + size_t protocol_send_message(uint8_t type, Args const&... args) const; + + size_t protocol_send_reject(peer_request const& req) const + { + TR_ASSERT(io_->supports_fext()); + return protocol_send_message(BtPeerMsgs::FextReject, req.index, req.offset, req.length); + } + + size_t protocol_send_cancel(peer_request const& req) const + { + return protocol_send_message(BtPeerMsgs::Cancel, req.index, req.offset, req.length); + } + + size_t protocol_send_request(peer_request const& req) const + { + TR_ASSERT(is_valid_request(req)); + return protocol_send_message(BtPeerMsgs::Request, req.index, req.offset, req.length); + } + + size_t protocol_send_port(tr_port const port) const + { + return protocol_send_message(BtPeerMsgs::Port, port.host()); + } + + size_t protocol_send_have(tr_piece_index_t const index) const + { + static_assert(sizeof(tr_piece_index_t) == sizeof(uint32_t)); + return protocol_send_message(BtPeerMsgs::Have, index); + } + + size_t protocol_send_choke(bool const choke) const + { + return protocol_send_message(choke ? BtPeerMsgs::Choke : BtPeerMsgs::Unchoke); + } + + void protocol_send_interest(bool const b) const + { + protocol_send_message(b ? BtPeerMsgs::Interested : BtPeerMsgs::NotInterested); + } + + void protocol_send_bitfield(); + + // --- + + void publish(tr_peer_event const& peer_event) + { + if (callback_ != nullptr) + { + (*callback_)(this, peer_event, callback_data_); + } + } + + // --- + + static void did_write(tr_peerIo* /*io*/, size_t bytes_written, bool was_piece_data, void* vmsgs); + static ReadState can_read(tr_peerIo* io, void* vmsgs, size_t* piece); + static void got_error(tr_peerIo* /*io*/, tr_error const& /*error*/, void* vmsgs); + + // --- + + bool peer_supports_pex_ = false; + bool peer_supports_metadata_xfer_ = false; + bool client_sent_ltep_handshake_ = false; + + size_t desired_request_count_ = 0; + + uint8_t ut_pex_id_ = 0; + uint8_t ut_metadata_id_ = 0; + + tr_port dht_port_; + + EncryptionPreference encryption_preference_ = EncryptionPreference::Unknown; + + tr_torrent& tor_; + + std::shared_ptr const io_; + + std::deque peer_requested_; + + std::array, NUM_TR_AF_INET_TYPES> pex_; + + std::queue peer_requested_metadata_pieces_; + + time_t client_sent_at_ = 0; + + time_t choke_changed_at_ = 0; + + tr_incoming incoming_ = {}; + + // if the peer supports the Extension Protocol in BEP 10 and + // supplied a reqq argument, it's stored here. + std::optional reqq_; std::unique_ptr pex_timer_; tr_bitfield have_; -private: - friend ReadResult process_peer_message(tr_peerMsgsImpl* msgs, uint8_t id, MessageReader& payload); - friend void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload); - friend void parseUtMetadata(tr_peerMsgsImpl* msgs, MessageReader& payload_in); - tr_peer_callback_bt const callback_; void* const callback_data_; - // seconds between periodic sendPex() calls + // seconds between periodic send_ut_pex() calls static auto constexpr SendPexInterval = 90s; }; // --- -[[nodiscard]] constexpr bool messageLengthIsCorrect(tr_torrent const* const tor, uint8_t id, uint32_t len) +[[nodiscard]] constexpr bool is_message_length_correct(tr_torrent const& tor, uint8_t id, uint32_t len) { switch (id) { @@ -714,7 +735,7 @@ private: return len == 5U; case BtPeerMsgs::Bitfield: - return !tor->has_metainfo() || len == 1 + ((tor->piece_count() + 7U) / 8U); + return !tor.has_metainfo() || len == 1 + ((tor.piece_count() + 7U) / 8U); case BtPeerMsgs::Request: case BtPeerMsgs::Cancel: @@ -738,9 +759,6 @@ private: namespace protocol_send_message_helpers { -namespace -{ - [[nodiscard]] constexpr auto get_param_length(uint8_t param) noexcept { return sizeof(param); @@ -815,388 +833,107 @@ template (text.append(log_param(args)), ...); return text; } -} // namespace template -void build_peer_message(tr_peerMsgsImpl const* const msgs, MessageWriter& out, uint8_t type, Args const&... args) +size_t build_peer_message(MessageWriter& out, uint8_t type, Args const&... args) { - logtrace(msgs, build_log_message(type, args...)); - auto msg_len = sizeof(type); ((msg_len += get_param_length(args)), ...); out.add_uint32(msg_len); out.add_uint8(type); (add_param(out, args), ...); - TR_ASSERT(messageLengthIsCorrect(msgs->torrent, type, msg_len)); + return msg_len; } } // namespace protocol_send_message_helpers template -size_t protocol_send_message(tr_peerMsgsImpl const* const msgs, uint8_t type, Args const&... args) +size_t tr_peerMsgsImpl::protocol_send_message(uint8_t type, Args const&... args) const { using namespace protocol_send_message_helpers; + logtrace(this, build_log_message(type, args...)); + auto out = MessageBuffer{}; - build_peer_message(msgs, out, type, args...); + [[maybe_unused]] auto const msg_len = build_peer_message(out, type, args...); + TR_ASSERT(is_message_length_correct(tor_, type, msg_len)); auto const n_bytes_added = std::size(out); - msgs->io->write(out, type == BtPeerMsgs::Piece); + io_->write(out, type == BtPeerMsgs::Piece); return n_bytes_added; } -size_t protocol_send_keepalive(tr_peerMsgsImpl* msgs) +void tr_peerMsgsImpl::protocol_send_bitfield() { - logtrace(msgs, "sending 'keepalive'"); + bool const fext = io_->supports_fext(); + + if (fext && tor_.has_all()) + { + protocol_send_message(BtPeerMsgs::FextHaveAll); + } + else if (fext && tor_.has_none()) + { + protocol_send_message(BtPeerMsgs::FextHaveNone); + } + else if (!tor_.has_none()) + { + // https://www.bittorrent.org/beps/bep_0003.html#peer-messages + // Downloaders which don't have anything yet may skip the 'bitfield' message. + protocol_send_message(BtPeerMsgs::Bitfield, tor_.create_piece_bitfield()); + } +} + +size_t tr_peerMsgsImpl::protocol_send_keepalive() const +{ + logtrace(this, "sending 'keepalive'"); auto out = MessageBuffer{}; out.add_uint32(0); auto const n_bytes_added = std::size(out); - msgs->io->write(out, false); + io_->write(out, false); return n_bytes_added; } -auto protocolSendReject(tr_peerMsgsImpl* const msgs, struct peer_request const* req) -{ - TR_ASSERT(msgs->io->supports_fext()); - return protocol_send_message(msgs, BtPeerMsgs::FextReject, req->index, req->offset, req->length); -} - -size_t protocolSendCancel(tr_peerMsgsImpl* const msgs, peer_request const& req) -{ - return protocol_send_message(msgs, BtPeerMsgs::Cancel, req.index, req.offset, req.length); -} - -size_t protocolSendRequest(tr_peerMsgsImpl* const msgs, struct peer_request const& req) -{ - TR_ASSERT(msgs->isValidRequest(req)); - return protocol_send_message(msgs, BtPeerMsgs::Request, req.index, req.offset, req.length); -} - -size_t protocolSendPort(tr_peerMsgsImpl* const msgs, tr_port port) -{ - return protocol_send_message(msgs, BtPeerMsgs::Port, port.host()); -} - -size_t protocolSendHave(tr_peerMsgsImpl* const msgs, tr_piece_index_t index) -{ - return protocol_send_message(msgs, BtPeerMsgs::Have, index); -} - -size_t protocolSendChoke(tr_peerMsgsImpl* const msgs, bool choke) -{ - return protocol_send_message(msgs, choke ? BtPeerMsgs::Choke : BtPeerMsgs::Unchoke); -} - -// --- INTEREST - -void sendInterest(tr_peerMsgsImpl* msgs, bool b) -{ - protocol_send_message(msgs, b ? BtPeerMsgs::Interested : BtPeerMsgs::NotInterested); -} - -std::optional popNextMetadataRequest(tr_peerMsgsImpl* msgs) -{ - auto& reqs = msgs->peerAskedForMetadata; - - if (std::empty(reqs)) - { - return {}; - } - - auto next = reqs.front(); - reqs.pop(); - return next; -} - -void cancelAllRequestsToClient(tr_peerMsgsImpl* msgs) -{ - auto& queue = msgs->peer_requested_; - - if (auto const must_send_rej = msgs->io->supports_fext(); must_send_rej) - { - for (auto const& req : queue) - { - protocolSendReject(msgs, &req); - } - } - - queue.clear(); -} - // --- -void sendLtepHandshake(tr_peerMsgsImpl* msgs) +void tr_peerMsgsImpl::parse_ltep(MessageReader& payload) { - if (msgs->clientSentLtepHandshake) + TR_ASSERT(!std::empty(payload)); + + auto const ltep_msgid = payload.to_uint8(); + + if (ltep_msgid == LtepMessages::Handshake) { - return; - } + logtrace(this, "got ltep handshake"); + parse_ltep_handshake(payload); - logtrace(msgs, "sending an ltep handshake"); - msgs->clientSentLtepHandshake = true; - - /* decide if we want to advertise metadata xfer support (BEP 9) */ - bool const allow_metadata_xfer = msgs->torrent->is_public(); - - /* decide if we want to advertise pex support */ - bool const allow_pex = msgs->torrent->allows_pex(); - - auto val = tr_variant{}; - tr_variantInitDict(&val, 8); - tr_variantDictAddBool(&val, TR_KEY_e, msgs->session->encryptionMode() != TR_CLEAR_PREFERRED); - - // If connecting to global peer, then use global address - // Otherwise we are connecting to local peer, use bind address directly - if (auto const addr = msgs->io->address().is_global_unicast_address() ? msgs->session->global_address(TR_AF_INET) : - msgs->session->bind_address(TR_AF_INET); - addr && !addr->is_any()) - { - TR_ASSERT(addr->is_ipv4()); - tr_variantDictAddRaw(&val, TR_KEY_ipv4, &addr->addr.addr4, sizeof(addr->addr.addr4)); - } - if (auto const addr = msgs->io->address().is_global_unicast_address() ? msgs->session->global_address(TR_AF_INET6) : - msgs->session->bind_address(TR_AF_INET6); - addr && !addr->is_any()) - { - TR_ASSERT(addr->is_ipv6()); - tr_variantDictAddRaw(&val, TR_KEY_ipv6, &addr->addr.addr6, sizeof(addr->addr.addr6)); - } - - // http://bittorrent.org/beps/bep_0009.html - // It also adds "metadata_size" to the handshake message (not the - // "m" dictionary) specifying an integer value of the number of - // bytes of the metadata. - if (auto const info_dict_size = msgs->torrent->info_dict_size(); - allow_metadata_xfer && msgs->torrent->has_metainfo() && info_dict_size > 0) - { - tr_variantDictAddInt(&val, TR_KEY_metadata_size, info_dict_size); - } - - // http://bittorrent.org/beps/bep_0010.html - // Local TCP listen port. Allows each side to learn about the TCP - // port number of the other side. Note that there is no need for the - // receiving side of the connection to send this extension message, - // since its port number is already known. - tr_variantDictAddInt(&val, TR_KEY_p, msgs->session->advertisedPeerPort().host()); - - // http://bittorrent.org/beps/bep_0010.html - // An integer, the number of outstanding request messages this - // client supports without dropping any. The default in in - // libtorrent is 250. - tr_variantDictAddInt(&val, TR_KEY_reqq, ReqQ); - - // https://www.bittorrent.org/beps/bep_0010.html - // A string containing the compact representation of the ip address this peer sees - // you as. i.e. this is the receiver's external ip address (no port is included). - // This may be either an IPv4 (4 bytes) or an IPv6 (16 bytes) address. - { - auto buf = std::array{}; - auto const begin = std::data(buf); - auto const end = msgs->io->address().to_compact(begin); - auto const len = end - begin; - TR_ASSERT(len == tr_address::CompactAddrBytes[0] || len == tr_address::CompactAddrBytes[1]); - tr_variantDictAddRaw(&val, TR_KEY_yourip, begin, len); - } - - // http://bittorrent.org/beps/bep_0010.html - // Client name and version (as a utf-8 string). This is a much more - // reliable way of identifying the client than relying on the - // peer id encoding. - tr_variantDictAddStrView(&val, TR_KEY_v, TR_NAME " " USERAGENT_PREFIX); - - // http://bittorrent.org/beps/bep_0021.html - // A peer that is a partial seed SHOULD include an extra header in - // the extension handshake 'upload_only'. Setting the value of this - // key to 1 indicates that this peer is not interested in downloading - // anything. - tr_variantDictAddBool(&val, TR_KEY_upload_only, msgs->torrent->is_done()); - - if (allow_metadata_xfer || allow_pex) - { - tr_variant* m = tr_variantDictAddDict(&val, TR_KEY_m, 2); - - if (allow_metadata_xfer) + if (io_->supports_ltep()) { - tr_variantDictAddInt(m, TR_KEY_ut_metadata, UT_METADATA_ID); - } - - if (allow_pex) - { - tr_variantDictAddInt(m, TR_KEY_ut_pex, UT_PEX_ID); + send_ltep_handshake(); + send_ut_pex(); } } - - protocol_send_message(msgs, BtPeerMsgs::Ltep, LtepMessages::Handshake, tr_variant_serde::benc().to_string(val)); -} - -void parseLtepHandshake(tr_peerMsgsImpl* msgs, MessageReader& payload) -{ - auto const handshake_sv = payload.to_string_view(); - - auto var = tr_variant_serde::benc().inplace().parse(handshake_sv); - if (!var || !var->holds_alternative()) + else if (ltep_msgid == UT_PEX_ID) { - logtrace(msgs, "GET extended-handshake, couldn't get dictionary"); - return; + logtrace(this, "got ut pex"); + peer_supports_pex_ = true; + parse_ut_pex(payload); } - - logtrace(msgs, fmt::format("here is the base64-encoded handshake: [{:s}]", tr_base64_encode(handshake_sv))); - - /* does the peer prefer encrypted connections? */ - auto pex = tr_pex{}; - auto& [addr, port] = pex.socket_address; - if (auto e = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_e, &e)) + else if (ltep_msgid == UT_METADATA_ID) { - msgs->encryption_preference = e != 0 ? EncryptionPreference::Yes : EncryptionPreference::No; - - if (msgs->encryption_preference == EncryptionPreference::Yes) - { - pex.flags |= ADDED_F_ENCRYPTION_FLAG; - } + logtrace(this, "got ut metadata"); + peer_supports_metadata_xfer_ = true; + parse_ut_metadata(payload); } - - /* check supported messages for utorrent pex */ - msgs->peerSupportsPex = false; - msgs->peerSupportsMetadataXfer = false; - - if (tr_variant* sub = nullptr; tr_variantDictFindDict(&*var, TR_KEY_m, &sub)) + else { - if (auto ut_pex = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_pex, &ut_pex)) - { - msgs->peerSupportsPex = ut_pex != 0; - msgs->ut_pex_id = static_cast(ut_pex); - logtrace(msgs, fmt::format("msgs->ut_pex is {:d}", static_cast(msgs->ut_pex_id))); - } - - if (auto ut_metadata = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_metadata, &ut_metadata)) - { - msgs->peerSupportsMetadataXfer = ut_metadata != 0; - msgs->ut_metadata_id = static_cast(ut_metadata); - logtrace(msgs, fmt::format("msgs->ut_metadata_id is {:d}", static_cast(msgs->ut_metadata_id))); - } - - if (auto ut_holepunch = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_holepunch, &ut_holepunch)) - { - // Transmission doesn't support this extension yet. - // But its presence does indicate µTP supports, - // which we do care about... - msgs->peer_info->set_utp_supported(true); - } - } - - /* look for metainfo size (BEP 9) */ - if (auto metadata_size = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_metadata_size, &metadata_size)) - { - if (!tr_metadata_download::is_valid_metadata_size(metadata_size)) - { - msgs->peerSupportsMetadataXfer = false; - } - else - { - msgs->torrent->maybe_start_metadata_transfer(metadata_size); - } - } - - /* look for upload_only (BEP 21) */ - if (auto upload_only = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_upload_only, &upload_only)) - { - pex.flags |= ADDED_F_SEED_FLAG; - } - - // http://bittorrent.org/beps/bep_0010.html - // Client name and version (as a utf-8 string). This is a much more - // reliable way of identifying the client than relying on the - // peer id encoding. - if (auto sv = std::string_view{}; tr_variantDictFindStrView(&*var, TR_KEY_v, &sv)) - { - msgs->set_user_agent(tr_interned_string{ sv }); - } - - /* get peer's listening port */ - if (auto p = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_p, &p) && p > 0) - { - port.set_host(p); - msgs->publish(tr_peer_event::GotPort(port)); - logtrace(msgs, fmt::format("peer's port is now {:d}", p)); - } - - std::byte const* addr_compact = nullptr; - auto addr_len = size_t{}; - if (msgs->io->is_incoming() && tr_variantDictFindRaw(&*var, TR_KEY_ipv4, &addr_compact, &addr_len) && - addr_len == tr_address::CompactAddrBytes[TR_AF_INET]) - { - std::tie(addr, std::ignore) = tr_address::from_compact_ipv4(addr_compact); - tr_peerMgrAddPex(msgs->torrent, TR_PEER_FROM_LTEP, &pex, 1); - } - - if (msgs->io->is_incoming() && tr_variantDictFindRaw(&*var, TR_KEY_ipv6, &addr_compact, &addr_len) && - addr_len == tr_address::CompactAddrBytes[TR_AF_INET6]) - { - std::tie(addr, std::ignore) = tr_address::from_compact_ipv6(addr_compact); - tr_peerMgrAddPex(msgs->torrent, TR_PEER_FROM_LTEP, &pex, 1); - } - - /* get peer's maximum request queue size */ - if (auto reqq = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_reqq, &reqq)) - { - msgs->reqq = reqq; + logtrace(this, fmt::format("skipping unknown ltep message ({:d})", static_cast(ltep_msgid))); } } -void parseUtMetadata(tr_peerMsgsImpl* msgs, MessageReader& payload_in) +void tr_peerMsgsImpl::parse_ut_pex(MessageReader& payload) { - int64_t msg_type = -1; - int64_t piece = -1; - int64_t total_size = 0; - - auto const tmp = payload_in.to_string_view(); - auto const* const msg_end = std::data(tmp) + std::size(tmp); - - auto serde = tr_variant_serde::benc(); - if (auto var = serde.inplace().parse(tmp); var) - { - (void)tr_variantDictFindInt(&*var, TR_KEY_msg_type, &msg_type); - (void)tr_variantDictFindInt(&*var, TR_KEY_piece, &piece); - (void)tr_variantDictFindInt(&*var, TR_KEY_total_size, &total_size); - } - - logtrace(msgs, fmt::format("got ut_metadata msg: type {:d}, piece {:d}, total_size {:d}", msg_type, piece, total_size)); - - if (msg_type == MetadataMsgType::Reject) - { - /* NOOP */ - } - - if (auto const piece_len = msg_end - serde.end(); - msg_type == MetadataMsgType::Data && piece * MetadataPieceSize + piece_len <= total_size) - { - msgs->torrent->set_metadata_piece(piece, serde.end(), piece_len); - } - - if (msg_type == MetadataMsgType::Request) - { - if (piece >= 0 && msgs->torrent->has_metainfo() && msgs->torrent->is_public() && - std::size(msgs->peerAskedForMetadata) < MetadataReqQ) - { - msgs->peerAskedForMetadata.push(piece); - } - else - { - /* send a rejection message */ - auto v = tr_variant{}; - tr_variantInitDict(&v, 2); - tr_variantDictAddInt(&v, TR_KEY_msg_type, MetadataMsgType::Reject); - tr_variantDictAddInt(&v, TR_KEY_piece, piece); - protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, serde.to_string(v)); - } - } -} - -void parseUtPex(tr_peerMsgsImpl* msgs, MessageReader& payload) -{ - auto* const tor = msgs->torrent; - if (!tor->allows_pex()) + if (!tor_.allows_pex()) { return; } @@ -1217,7 +954,7 @@ void parseUtPex(tr_peerMsgsImpl* msgs, MessageReader& payload) auto pex = tr_pex::from_compact_ipv4(added, added_len, added_f, added_f_len); pex.resize(std::min(MaxPexPeerCount, std::size(pex))); - tr_peerMgrAddPex(tor, TR_PEER_FROM_PEX, std::data(pex), std::size(pex)); + tr_peerMgrAddPex(&tor_, TR_PEER_FROM_PEX, std::data(pex), std::size(pex)); } if (tr_variantDictFindRaw(&*var, TR_KEY_added6, &added, &added_len)) @@ -1232,758 +969,24 @@ void parseUtPex(tr_peerMsgsImpl* msgs, MessageReader& payload) auto pex = tr_pex::from_compact_ipv6(added, added_len, added_f, added_f_len); pex.resize(std::min(MaxPexPeerCount, std::size(pex))); - tr_peerMgrAddPex(tor, TR_PEER_FROM_PEX, std::data(pex), std::size(pex)); + tr_peerMgrAddPex(&tor_, TR_PEER_FROM_PEX, std::data(pex), std::size(pex)); } } } -void parseLtep(tr_peerMsgsImpl* msgs, MessageReader& payload) -{ - TR_ASSERT(!std::empty(payload)); - - auto const ltep_msgid = payload.to_uint8(); - - if (ltep_msgid == LtepMessages::Handshake) - { - logtrace(msgs, "got ltep handshake"); - parseLtepHandshake(msgs, payload); - - if (msgs->io->supports_ltep()) - { - sendLtepHandshake(msgs); - msgs->sendPex(); - } - } - else if (ltep_msgid == UT_PEX_ID) - { - logtrace(msgs, "got ut pex"); - msgs->peerSupportsPex = true; - parseUtPex(msgs, payload); - } - else if (ltep_msgid == UT_METADATA_ID) - { - logtrace(msgs, "got ut metadata"); - msgs->peerSupportsMetadataXfer = true; - parseUtMetadata(msgs, payload); - } - else - { - logtrace(msgs, fmt::format("skipping unknown ltep message ({:d})", static_cast(ltep_msgid))); - } -} - -ReadResult process_peer_message(tr_peerMsgsImpl* msgs, uint8_t id, MessageReader& payload); - -[[nodiscard]] bool canAddRequestFromPeer(tr_peerMsgsImpl const* const msgs, struct peer_request const& req) -{ - if (msgs->peer_is_choked()) - { - logtrace(msgs, "rejecting request from choked peer"); - return false; - } - - if (std::size(msgs->peer_requested_) >= ReqQ) - { - logtrace(msgs, "rejecting request ... reqq is full"); - return false; - } - - if (!is_valid_request(*msgs->torrent, req.index, req.offset, req.length)) - { - logtrace(msgs, "rejecting an invalid request."); - return false; - } - - if (!msgs->torrent->has_piece(req.index)) - { - logtrace(msgs, "rejecting request for a piece we don't have."); - return false; - } - - return true; -} - -void peerMadeRequest(tr_peerMsgsImpl* msgs, struct peer_request const* req) -{ - if (canAddRequestFromPeer(msgs, *req)) - { - msgs->peer_requested_.emplace_back(*req); - } - else if (msgs->io->supports_fext()) - { - protocolSendReject(msgs, req); - } -} - -int clientGotBlock(tr_peerMsgsImpl* msgs, std::unique_ptr block_data, tr_block_index_t block); - -ReadResult read_piece_data(tr_peerMsgsImpl* msgs, MessageReader& payload) -{ - // - auto const piece = payload.to_uint32(); - auto const offset = payload.to_uint32(); - auto const len = std::size(payload); - - auto const loc = msgs->torrent->piece_loc(piece, offset); - auto const block = loc.block; - auto const block_size = msgs->torrent->block_size(block); - - logtrace(msgs, fmt::format("got {:d} bytes for req {:d}:{:d}->{:d}", len, piece, offset, len)); - - if (loc.block_offset + len > block_size) - { - logwarn(msgs, fmt::format("got unaligned piece {:d}:{:d}->{:d}", piece, offset, len)); - return { READ_ERR, len }; - } - - if (!tr_peerMgrDidPeerRequest(msgs->torrent, msgs, block)) - { - logwarn(msgs, fmt::format("got unrequested piece {:d}:{:d}->{:d}", piece, offset, len)); - return { READ_ERR, len }; - } - - msgs->publish(tr_peer_event::GotPieceData(len)); - - if (loc.block_offset == 0U && len == block_size) // simple case: one message has entire block - { - auto buf = std::make_unique(block_size); - payload.to_buf(std::data(*buf), len); - auto const ok = clientGotBlock(msgs, std::move(buf), block) == 0; - return { ok ? READ_NOW : READ_ERR, len }; - } - - auto& blocks = msgs->incoming.blocks; - auto& incoming_block = blocks.try_emplace(block, block_size).first->second; - payload.to_buf(std::data(*incoming_block.buf) + loc.block_offset, len); - - if (!incoming_block.add_span(loc.block_offset, loc.block_offset + len)) - { - return { READ_ERR, len }; // invalid span - } - - if (!incoming_block.has_all()) - { - return { READ_LATER, len }; // we don't have the full block yet - } - - auto block_buf = std::move(incoming_block.buf); - blocks.erase(block); // note: invalidates `incoming_block` local - auto const ok = clientGotBlock(msgs, std::move(block_buf), block) == 0; - return { ok ? READ_NOW : READ_ERR, len }; -} - -ReadResult process_peer_message(tr_peerMsgsImpl* msgs, uint8_t id, MessageReader& payload) -{ - bool const fext = msgs->io->supports_fext(); - - auto ui32 = uint32_t{}; - - logtrace( - msgs, - fmt::format( - "got peer msg '{:s}' ({:d}) with payload len {:d}", - BtPeerMsgs::debug_name(id), - static_cast(id), - std::size(payload))); - - if (!messageLengthIsCorrect(msgs->torrent, id, sizeof(id) + std::size(payload))) - { - logdbg( - msgs, - fmt::format( - "bad msg: '{:s}' ({:d}) with payload len {:d}", - BtPeerMsgs::debug_name(id), - static_cast(id), - std::size(payload))); - msgs->publish(tr_peer_event::GotError(EMSGSIZE)); - return { READ_ERR, {} }; - } - - switch (id) - { - case BtPeerMsgs::Choke: - logtrace(msgs, "got Choke"); - msgs->set_client_choked(true); - - if (!fext) - { - msgs->publish(tr_peer_event::GotChoke()); - } - - msgs->update_active(TR_PEER_TO_CLIENT); - break; - - case BtPeerMsgs::Unchoke: - logtrace(msgs, "got Unchoke"); - msgs->set_client_choked(false); - msgs->update_active(TR_PEER_TO_CLIENT); - updateDesiredRequestCount(msgs); - break; - - case BtPeerMsgs::Interested: - logtrace(msgs, "got Interested"); - msgs->set_peer_interested(true); - msgs->update_active(TR_CLIENT_TO_PEER); - break; - - case BtPeerMsgs::NotInterested: - logtrace(msgs, "got Not Interested"); - msgs->set_peer_interested(false); - msgs->update_active(TR_CLIENT_TO_PEER); - break; - - case BtPeerMsgs::Have: - ui32 = payload.to_uint32(); - logtrace(msgs, fmt::format("got Have: {:d}", ui32)); - - if (msgs->torrent->has_metainfo() && ui32 >= msgs->torrent->piece_count()) - { - msgs->publish(tr_peer_event::GotError(ERANGE)); - return { READ_ERR, {} }; - } - - /* a peer can send the same HAVE message twice... */ - if (!msgs->have_.test(ui32)) - { - msgs->have_.set(ui32); - msgs->publish(tr_peer_event::GotHave(ui32)); - } - - msgs->invalidatePercentDone(); - break; - - case BtPeerMsgs::Bitfield: - logtrace(msgs, "got a bitfield"); - msgs->have_ = tr_bitfield{ msgs->torrent->has_metainfo() ? msgs->torrent->piece_count() : std::size(payload) * 8 }; - msgs->have_.set_raw(reinterpret_cast(std::data(payload)), std::size(payload)); - msgs->publish(tr_peer_event::GotBitfield(&msgs->have_)); - msgs->invalidatePercentDone(); - break; - - case BtPeerMsgs::Request: - { - struct peer_request r; - r.index = payload.to_uint32(); - r.offset = payload.to_uint32(); - r.length = payload.to_uint32(); - logtrace(msgs, fmt::format("got Request: {:d}:{:d}->{:d}", r.index, r.offset, r.length)); - peerMadeRequest(msgs, &r); - break; - } - - case BtPeerMsgs::Cancel: - { - struct peer_request r; - r.index = payload.to_uint32(); - r.offset = payload.to_uint32(); - r.length = payload.to_uint32(); - msgs->cancels_sent_to_client.add(tr_time(), 1); - logtrace(msgs, fmt::format("got a Cancel {:d}:{:d}->{:d}", r.index, r.offset, r.length)); - - auto& requests = msgs->peer_requested_; - if (auto iter = std::find(std::begin(requests), std::end(requests), r); iter != std::end(requests)) - { - requests.erase(iter); - - // bep6: "Even when a request is cancelled, the peer - // receiving the cancel should respond with either the - // corresponding reject or the corresponding piece" - if (fext) - { - protocolSendReject(msgs, &r); - } - } - break; - } - - case BtPeerMsgs::Piece: - return read_piece_data(msgs, payload); - break; - - case BtPeerMsgs::Port: - // http://bittorrent.org/beps/bep_0005.html - // Peers supporting the DHT set the last bit of the 8-byte reserved flags - // exchanged in the BitTorrent protocol handshake. Peer receiving a handshake - // indicating the remote peer supports the DHT should send a PORT message. - // It begins with byte 0x09 and has a two byte payload containing the UDP - // port of the DHT node in network byte order. - { - logtrace(msgs, "Got a BtPeerMsgs::Port"); - - auto const hport = payload.to_uint16(); - if (auto const dht_port = tr_port::from_host(hport); !std::empty(dht_port)) - { - msgs->dht_port = dht_port; - msgs->session->maybe_add_dht_node(msgs->io->address(), msgs->dht_port); - } - } - break; - - case BtPeerMsgs::FextSuggest: - logtrace(msgs, "Got a BtPeerMsgs::FextSuggest"); - - if (fext) - { - auto const piece = payload.to_uint32(); - msgs->publish(tr_peer_event::GotSuggest(piece)); - } - else - { - msgs->publish(tr_peer_event::GotError(EMSGSIZE)); - return { READ_ERR, {} }; - } - - break; - - case BtPeerMsgs::FextAllowedFast: - logtrace(msgs, "Got a BtPeerMsgs::FextAllowedFast"); - - if (fext) - { - auto const piece = payload.to_uint32(); - msgs->publish(tr_peer_event::GotAllowedFast(piece)); - } - else - { - msgs->publish(tr_peer_event::GotError(EMSGSIZE)); - return { READ_ERR, {} }; - } - - break; - - case BtPeerMsgs::FextHaveAll: - logtrace(msgs, "Got a BtPeerMsgs::FextHaveAll"); - - if (fext) - { - msgs->have_.set_has_all(); - msgs->publish(tr_peer_event::GotHaveAll()); - msgs->invalidatePercentDone(); - } - else - { - msgs->publish(tr_peer_event::GotError(EMSGSIZE)); - return { READ_ERR, {} }; - } - - break; - - case BtPeerMsgs::FextHaveNone: - logtrace(msgs, "Got a BtPeerMsgs::FextHaveNone"); - - if (fext) - { - msgs->have_.set_has_none(); - msgs->publish(tr_peer_event::GotHaveNone()); - msgs->invalidatePercentDone(); - } - else - { - msgs->publish(tr_peer_event::GotError(EMSGSIZE)); - return { READ_ERR, {} }; - } - - break; - - case BtPeerMsgs::FextReject: - { - struct peer_request r; - r.index = payload.to_uint32(); - r.offset = payload.to_uint32(); - r.length = payload.to_uint32(); - - if (fext) - { - msgs->publish( - tr_peer_event::GotRejected(msgs->torrent->block_info(), msgs->torrent->piece_loc(r.index, r.offset).block)); - } - else - { - msgs->publish(tr_peer_event::GotError(EMSGSIZE)); - return { READ_ERR, {} }; - } - - break; - } - - case BtPeerMsgs::Ltep: - logtrace(msgs, "Got a BtPeerMsgs::Ltep"); - parseLtep(msgs, payload); - break; - - default: - logtrace(msgs, fmt::format("peer sent us an UNKNOWN: {:d}", static_cast(id))); - break; - } - - return { READ_NOW, {} }; -} - -/* returns 0 on success, or an errno on failure */ -int clientGotBlock(tr_peerMsgsImpl* msgs, std::unique_ptr block_data, tr_block_index_t const block) -{ - TR_ASSERT(msgs != nullptr); - - tr_torrent const* const tor = msgs->torrent; - auto const n_expected = msgs->torrent->block_size(block); - - if (!block_data) - { - logdbg(msgs, fmt::format("wrong block size: expected {:d}, got {:d}", n_expected, 0)); - return EMSGSIZE; - } - - if (std::size(*block_data) != msgs->torrent->block_size(block)) - { - logdbg(msgs, fmt::format("wrong block size: expected {:d}, got {:d}", n_expected, std::size(*block_data))); - return EMSGSIZE; - } - - logtrace(msgs, fmt::format("got block {:d}", block)); - - if (!tr_peerMgrDidPeerRequest(msgs->torrent, msgs, block)) - { - logdbg(msgs, "we didn't ask for this message..."); - return 0; - } - - auto const loc = msgs->torrent->block_loc(block); - if (msgs->torrent->has_piece(loc.piece)) - { - logtrace(msgs, "we did ask for this message, but the piece is already complete..."); - return 0; - } - - // NB: if writeBlock() fails the torrent may be paused. - // If this happens, `msgs` will be a dangling pointer and must no longer be used. - if (auto const err = msgs->session->cache->write_block(tor->id(), block, std::move(block_data)); err != 0) - { - return err; - } - - msgs->blame.set(loc.piece); - msgs->publish(tr_peer_event::GotBlock(tor->block_info(), block)); - - return 0; -} - -void didWrite(tr_peerIo* /*io*/, size_t bytes_written, bool was_piece_data, void* vmsgs) -{ - auto* const msgs = static_cast(vmsgs); - - if (was_piece_data) - { - msgs->publish(tr_peer_event::SentPieceData(bytes_written)); - } - - peerPulse(msgs); -} - -ReadState canRead(tr_peerIo* io, void* vmsgs, size_t* piece) -{ - auto* msgs = static_cast(vmsgs); - - // https://www.bittorrent.org/beps/bep_0003.html - // Next comes an alternating stream of length prefixes and messages. - // Messages of length zero are keepalives, and ignored. - // All non-keepalive messages start with a single byte which gives their type. - // - // https://wiki.theory.org/BitTorrentSpecification - // All of the remaining messages in the protocol take the form of - // . The length prefix is a four byte - // big-endian value. The message ID is a single decimal byte. - // The payload is message dependent. - - // read - auto& current_message_len = msgs->incoming.length; // the full message payload length. Includes the +1 for id length - if (!current_message_len) - { - auto message_len = uint32_t{}; - if (io->read_buffer_size() < sizeof(message_len)) - { - return READ_LATER; - } - - io->read_uint32(&message_len); - - // The keep-alive message is a message with zero bytes, - // specified with the length prefix set to zero. - // There is no message ID and no payload. - if (message_len == 0U) - { - logtrace(msgs, "got KeepAlive"); - return READ_NOW; - } - - current_message_len = message_len; - } - - // read - auto& current_message_type = msgs->incoming.id; - if (!current_message_type) - { - auto message_type = uint8_t{}; - if (io->read_buffer_size() < sizeof(message_type)) - { - return READ_LATER; - } - - io->read_uint8(&message_type); - current_message_type = message_type; - } - - // read - auto& current_payload = msgs->incoming.payload; - auto const full_payload_len = *current_message_len - sizeof(*current_message_type); - auto n_left = full_payload_len - std::size(current_payload); - auto const [buf, n_this_pass] = current_payload.reserve_space(std::min(n_left, io->read_buffer_size())); - io->read_bytes(buf, n_this_pass); - current_payload.commit_space(n_this_pass); - n_left -= n_this_pass; - logtrace(msgs, fmt::format("read {:d} payload bytes; {:d} left to go", n_this_pass, n_left)); - - if (n_left > 0U) - { - return READ_LATER; - } - - // The incoming message is now complete. After processing the message - // with `process_peer_message()`, reset the peerMsgs' incoming - // field so it's ready to receive the next message. - - auto const [read_state, n_piece_bytes_read] = process_peer_message(msgs, *current_message_type, current_payload); - *piece = n_piece_bytes_read; - - current_message_len.reset(); - current_message_type.reset(); - current_payload.clear(); - - return read_state; -} - -// --- - -void updateDesiredRequestCount(tr_peerMsgsImpl* msgs) -{ - msgs->desired_request_count = msgs->canRequest().max_blocks; -} - -void updateMetadataRequests(tr_peerMsgsImpl* msgs, time_t now) -{ - if (!msgs->peerSupportsMetadataXfer) - { - return; - } - - if (auto const piece = msgs->torrent->get_next_metadata_request(now); piece) - { - auto tmp = tr_variant{}; - tr_variantInitDict(&tmp, 3); - tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Request); - tr_variantDictAddInt(&tmp, TR_KEY_piece, *piece); - protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, tr_variant_serde::benc().to_string(tmp)); - } -} - -void updateBlockRequests(tr_peerMsgsImpl* msgs) -{ - auto* const tor = msgs->torrent; - - if (!tor->client_can_download()) - { - return; - } - - auto const n_active = tr_peerMgrCountActiveRequestsToPeer(tor, msgs); - if (n_active >= msgs->desired_request_count) - { - return; - } - - auto const n_wanted = msgs->desired_request_count - n_active; - if (n_wanted == 0) - { - return; - } - - TR_ASSERT(msgs->client_is_interested()); - TR_ASSERT(!msgs->client_is_choked()); - - if (auto const requests = tr_peerMgrGetNextRequests(tor, msgs, n_wanted); !std::empty(requests)) - { - msgs->requestBlocks(std::data(requests), std::size(requests)); - } -} - -namespace peer_pulse_helpers -{ -[[nodiscard]] size_t add_next_metadata_piece(tr_peerMsgsImpl* msgs) -{ - auto const piece = popNextMetadataRequest(msgs); - - if (!piece.has_value()) // no pending requests - { - return {}; - } - - auto data = msgs->torrent->get_metadata_piece(*piece); - if (!data) - { - // send a reject - auto tmp = tr_variant{}; - tr_variantInitDict(&tmp, 2); - tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Reject); - tr_variantDictAddInt(&tmp, TR_KEY_piece, *piece); - return protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, tr_variant_serde::benc().to_string(tmp)); - } - - // send the metadata - auto tmp = tr_variant{}; - tr_variantInitDict(&tmp, 3); - tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Data); - tr_variantDictAddInt(&tmp, TR_KEY_piece, *piece); - tr_variantDictAddInt(&tmp, TR_KEY_total_size, msgs->torrent->info_dict_size()); - return protocol_send_message(msgs, BtPeerMsgs::Ltep, msgs->ut_metadata_id, tr_variant_serde::benc().to_string(tmp), *data); -} - -[[nodiscard]] size_t add_next_piece(tr_peerMsgsImpl* msgs, uint64_t now) -{ - if (msgs->io->get_write_buffer_space(now) == 0U || std::empty(msgs->peer_requested_)) - { - return {}; - } - - auto const req = msgs->peer_requested_.front(); - msgs->peer_requested_.erase(std::begin(msgs->peer_requested_)); - - auto buf = std::array{}; - auto ok = msgs->isValidRequest(req) && msgs->torrent->has_piece(req.index); - - if (ok) - { - ok = msgs->torrent->ensure_piece_is_checked(req.index); - - if (!ok) - { - msgs->torrent->error().set_local_error(fmt::format("Please Verify Local Data! Piece #{:d} is corrupt.", req.index)); - } - } - - if (ok) - { - ok = msgs->session->cache - ->read_block(*msgs->torrent, msgs->torrent->piece_loc(req.index, req.offset), req.length, std::data(buf)) == 0; - } - - if (ok) - { - auto const piece_data = std::string_view{ reinterpret_cast(std::data(buf)), req.length }; - return protocol_send_message(msgs, BtPeerMsgs::Piece, req.index, req.offset, piece_data); - } - - if (msgs->io->supports_fext()) - { - return protocolSendReject(msgs, &req); - } - - return {}; -} - -[[nodiscard]] size_t fill_output_buffer(tr_peerMsgsImpl* msgs, time_t now_sec, uint64_t now_msec) -{ - auto n_bytes_written = size_t{}; - - // fulfuill metadata requests - for (;;) - { - auto const old_len = n_bytes_written; - n_bytes_written += add_next_metadata_piece(msgs); - if (old_len == n_bytes_written) - { - break; - } - } - - // fulfuill piece requests - for (;;) - { - auto const old_len = n_bytes_written; - n_bytes_written += add_next_piece(msgs, now_msec); - if (old_len == n_bytes_written) - { - break; - } - } - - if (msgs != nullptr && msgs->clientSentAnythingAt != 0 && now_sec - msgs->clientSentAnythingAt > KeepaliveIntervalSecs) - { - n_bytes_written += protocol_send_keepalive(msgs); - } - - return n_bytes_written; -} -} // namespace peer_pulse_helpers - -void peerPulse(void* vmsgs) -{ - using namespace peer_pulse_helpers; - - auto* msgs = static_cast(vmsgs); - auto const now_sec = tr_time(); - auto const now_msec = tr_time_msec(); - - updateDesiredRequestCount(msgs); - updateBlockRequests(msgs); - updateMetadataRequests(msgs, now_sec); - - for (;;) - { - if (fill_output_buffer(msgs, now_sec, now_msec) == 0U) - { - break; - } - } -} - -void gotError(tr_peerIo* /*io*/, tr_error const& /*error*/, void* vmsgs) -{ - static_cast(vmsgs)->publish(tr_peer_event::GotError(ENOTCONN)); -} - -void tellPeerWhatWeHave(tr_peerMsgsImpl* msgs) -{ - bool const fext = msgs->io->supports_fext(); - - if (fext && msgs->torrent->has_all()) - { - protocol_send_message(msgs, BtPeerMsgs::FextHaveAll); - } - else if (fext && msgs->torrent->has_none()) - { - protocol_send_message(msgs, BtPeerMsgs::FextHaveNone); - } - else if (!msgs->torrent->has_none()) - { - protocol_send_message(msgs, BtPeerMsgs::Bitfield, msgs->torrent->create_piece_bitfield()); - } -} - -void tr_peerMsgsImpl::sendPex() +void tr_peerMsgsImpl::send_ut_pex() { // only send pex if both the torrent and peer support it - if (!this->peerSupportsPex || !this->torrent->allows_pex()) + if (!peer_supports_pex_ || !tor_.allows_pex()) { return; } - static auto constexpr MaxPexAdded = size_t{ 50 }; - static auto constexpr MaxPexDropped = size_t{ 50 }; - - auto val = tr_variant{}; - tr_variantInitDict(&val, 3); /* ipv6 support: left as 3: speed vs. likelihood? */ - - auto tmpbuf = small::vector{}; + static auto constexpr MaxPexAdded = size_t{ 50U }; + static auto constexpr MaxPexDropped = size_t{ 50U }; + auto map = tr_variant::Map{ 4U }; + auto tmpbuf = small::vector{}; for (uint8_t i = 0; i < NUM_TR_AF_INET_TYPES; ++i) { static auto constexpr AddedMap = std::array{ TR_KEY_added, TR_KEY_added6 }; @@ -1991,8 +994,8 @@ void tr_peerMsgsImpl::sendPex() static auto constexpr DroppedMap = std::array{ TR_KEY_dropped, TR_KEY_dropped6 }; auto const ip_type = static_cast(i); - auto& old_pex = pex[i]; - auto new_pex = tr_peerMgrGetPeers(this->torrent, ip_type, TR_PEERS_CONNECTED, MaxPexPeerCount); + auto& old_pex = pex_[i]; + auto new_pex = tr_peerMgrGetPeers(&tor_, ip_type, TR_PEERS_CONNECTED, MaxPexPeerCount); auto added = std::vector{}; added.reserve(std::size(new_pex)); std::set_difference( @@ -2043,7 +1046,7 @@ void tr_peerMsgsImpl::sendPex() tmpbuf.reserve(std::size(added) * tr_socket_address::CompactSockAddrBytes[i]); tr_pex::to_compact(std::back_inserter(tmpbuf), std::data(added), std::size(added)); TR_ASSERT(std::size(tmpbuf) == std::size(added) * tr_socket_address::CompactSockAddrBytes[i]); - tr_variantDictAddRaw(&val, AddedMap[i], std::data(tmpbuf), std::size(tmpbuf)); + map.try_emplace(AddedMap[i], std::string_view{ reinterpret_cast(std::data(tmpbuf)), std::size(tmpbuf) }); // "added.f" tmpbuf.resize(std::size(added)); @@ -2054,8 +1057,9 @@ void tr_peerMsgsImpl::sendPex() *walk++ = std::byte{ p.flags }; } - TR_ASSERT(static_cast(walk - begin) == std::size(added)); - tr_variantDictAddRaw(&val, AddedFMap[i], begin, walk - begin); + auto const f_len = static_cast(walk - begin); + TR_ASSERT(f_len == std::size(added)); + map.try_emplace(AddedFMap[i], std::string_view{ reinterpret_cast(begin), f_len }); } if (!std::empty(dropped)) @@ -2065,17 +1069,991 @@ void tr_peerMsgsImpl::sendPex() tmpbuf.reserve(std::size(dropped) * tr_socket_address::CompactSockAddrBytes[i]); tr_pex::to_compact(std::back_inserter(tmpbuf), std::data(dropped), std::size(dropped)); TR_ASSERT(std::size(tmpbuf) == std::size(dropped) * tr_socket_address::CompactSockAddrBytes[i]); - tr_variantDictAddRaw(&val, DroppedMap[i], std::data(tmpbuf), std::size(tmpbuf)); + map.try_emplace(DroppedMap[i], std::string_view{ reinterpret_cast(std::data(tmpbuf)), std::size(tmpbuf) }); } } - protocol_send_message(this, BtPeerMsgs::Ltep, this->ut_pex_id, tr_variant_serde::benc().to_string(val)); + protocol_send_message(BtPeerMsgs::Ltep, ut_pex_id_, tr_variant_serde::benc().to_string(tr_variant{ std::move(map) })); +} + +void tr_peerMsgsImpl::send_ltep_handshake() +{ + if (client_sent_ltep_handshake_) + { + return; + } + + logtrace(this, "sending an ltep handshake"); + client_sent_ltep_handshake_ = true; + + /* decide if we want to advertise metadata xfer support (BEP 9) */ + bool const allow_metadata_xfer = tor_.is_public(); + + /* decide if we want to advertise pex support */ + bool const allow_pex = tor_.allows_pex(); + + auto val = tr_variant{}; + tr_variantInitDict(&val, 8); + tr_variantDictAddBool(&val, TR_KEY_e, session->encryptionMode() != TR_CLEAR_PREFERRED); + + // If connecting to global peer, then use global address + // Otherwise we are connecting to local peer, use bind address directly + if (auto const addr = io_->address().is_global_unicast_address() ? session->global_address(TR_AF_INET) : + session->bind_address(TR_AF_INET); + addr && !addr->is_any()) + { + TR_ASSERT(addr->is_ipv4()); + tr_variantDictAddRaw(&val, TR_KEY_ipv4, &addr->addr.addr4, sizeof(addr->addr.addr4)); + } + if (auto const addr = io_->address().is_global_unicast_address() ? session->global_address(TR_AF_INET6) : + session->bind_address(TR_AF_INET6); + addr && !addr->is_any()) + { + TR_ASSERT(addr->is_ipv6()); + tr_variantDictAddRaw(&val, TR_KEY_ipv6, &addr->addr.addr6, sizeof(addr->addr.addr6)); + } + + // https://www.bittorrent.org/beps/bep_0009.html + // It also adds "metadata_size" to the handshake message (not the + // "m" dictionary) specifying an integer value of the number of + // bytes of the metadata. + if (auto const info_dict_size = tor_.info_dict_size(); allow_metadata_xfer && tor_.has_metainfo() && info_dict_size > 0) + { + tr_variantDictAddInt(&val, TR_KEY_metadata_size, info_dict_size); + } + + // https://www.bittorrent.org/beps/bep_0010.html + // Local TCP listen port. Allows each side to learn about the TCP + // port number of the other side. Note that there is no need for the + // receiving side of the connection to send this extension message, + // since its port number is already known. + tr_variantDictAddInt(&val, TR_KEY_p, session->advertisedPeerPort().host()); + + // https://www.bittorrent.org/beps/bep_0010.html + // An integer, the number of outstanding request messages this + // client supports without dropping any. The default in in + // libtorrent is 250. + tr_variantDictAddInt(&val, TR_KEY_reqq, ReqQ); + + // https://www.bittorrent.org/beps/bep_0010.html + // A string containing the compact representation of the ip address this peer sees + // you as. i.e. this is the receiver's external ip address (no port is included). + // This may be either an IPv4 (4 bytes) or an IPv6 (16 bytes) address. + { + auto buf = std::array{}; + auto const begin = std::data(buf); + auto const end = io_->address().to_compact(begin); + auto const len = end - begin; + TR_ASSERT(len == tr_address::CompactAddrBytes[0] || len == tr_address::CompactAddrBytes[1]); + tr_variantDictAddRaw(&val, TR_KEY_yourip, begin, len); + } + + // https://www.bittorrent.org/beps/bep_0010.html + // Client name and version (as a utf-8 string). This is a much more + // reliable way of identifying the client than relying on the + // peer id encoding. + tr_variantDictAddStrView(&val, TR_KEY_v, TR_NAME " " USERAGENT_PREFIX); + + // https://www.bittorrent.org/beps/bep_0021.html + // A peer that is a partial seed SHOULD include an extra header in + // the extension handshake 'upload_only'. Setting the value of this + // key to 1 indicates that this peer is not interested in downloading + // anything. + tr_variantDictAddBool(&val, TR_KEY_upload_only, tor_.is_done()); + + if (allow_metadata_xfer || allow_pex) + { + tr_variant* m = tr_variantDictAddDict(&val, TR_KEY_m, 2); + + if (allow_metadata_xfer) + { + tr_variantDictAddInt(m, TR_KEY_ut_metadata, UT_METADATA_ID); + } + + if (allow_pex) + { + tr_variantDictAddInt(m, TR_KEY_ut_pex, UT_PEX_ID); + } + } + + protocol_send_message(BtPeerMsgs::Ltep, LtepMessages::Handshake, tr_variant_serde::benc().to_string(val)); +} + +void tr_peerMsgsImpl::parse_ltep_handshake(MessageReader& payload) +{ + auto const handshake_sv = payload.to_string_view(); + + auto var = tr_variant_serde::benc().inplace().parse(handshake_sv); + if (!var || !var->holds_alternative()) + { + logtrace(this, "GET extended-handshake, couldn't get dictionary"); + return; + } + + logtrace(this, fmt::format("here is the base64-encoded handshake: [{:s}]", tr_base64_encode(handshake_sv))); + + // does the peer prefer encrypted connections? + auto pex = tr_pex{}; + auto& [addr, port] = pex.socket_address; + if (auto e = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_e, &e)) + { + encryption_preference_ = e != 0 ? EncryptionPreference::Yes : EncryptionPreference::No; + + if (encryption_preference_ == EncryptionPreference::Yes) + { + pex.flags |= ADDED_F_ENCRYPTION_FLAG; + } + } + + // check supported messages for utorrent pex + peer_supports_pex_ = false; + peer_supports_metadata_xfer_ = false; + + if (tr_variant* sub = nullptr; tr_variantDictFindDict(&*var, TR_KEY_m, &sub)) + { + if (auto ut_pex = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_pex, &ut_pex)) + { + peer_supports_pex_ = ut_pex != 0; + ut_pex_id_ = static_cast(ut_pex); + logtrace(this, fmt::format("msgs->ut_pex is {:d}", ut_pex_id_)); + } + + if (auto ut_metadata = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_metadata, &ut_metadata)) + { + peer_supports_metadata_xfer_ = ut_metadata != 0; + ut_metadata_id_ = static_cast(ut_metadata); + logtrace(this, fmt::format("msgs->ut_metadata_id_ is {:d}", ut_metadata_id_)); + } + + if (auto ut_holepunch = int64_t{}; tr_variantDictFindInt(sub, TR_KEY_ut_holepunch, &ut_holepunch)) + { + // Transmission doesn't support this extension yet. + // But its presence does indicate µTP supports, + // which we do care about... + peer_info->set_utp_supported(true); + } + } + + // look for metainfo size (BEP 9) + if (auto metadata_size = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_metadata_size, &metadata_size)) + { + if (!tr_metadata_download::is_valid_metadata_size(metadata_size)) + { + peer_supports_metadata_xfer_ = false; + } + else + { + tor_.maybe_start_metadata_transfer(metadata_size); + } + } + + // look for upload_only (BEP 21) + if (auto upload_only = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_upload_only, &upload_only)) + { + pex.flags |= ADDED_F_SEED_FLAG; + } + + // https://www.bittorrent.org/beps/bep_0010.html + // Client name and version (as a utf-8 string). This is a much more + // reliable way of identifying the client than relying on the + // peer id encoding. + if (auto sv = std::string_view{}; tr_variantDictFindStrView(&*var, TR_KEY_v, &sv)) + { + set_user_agent(tr_interned_string{ sv }); + } + + /* get peer's listening port */ + if (auto p = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_p, &p) && p > 0) + { + port.set_host(p); + publish(tr_peer_event::GotPort(port)); + logtrace(this, fmt::format("peer's port is now {:d}", p)); + } + + std::byte const* addr_compact = nullptr; + auto addr_len = size_t{}; + if (io_->is_incoming() && tr_variantDictFindRaw(&*var, TR_KEY_ipv4, &addr_compact, &addr_len) && + addr_len == tr_address::CompactAddrBytes[TR_AF_INET]) + { + std::tie(addr, std::ignore) = tr_address::from_compact_ipv4(addr_compact); + tr_peerMgrAddPex(&tor_, TR_PEER_FROM_LTEP, &pex, 1); + } + + if (io_->is_incoming() && tr_variantDictFindRaw(&*var, TR_KEY_ipv6, &addr_compact, &addr_len) && + addr_len == tr_address::CompactAddrBytes[TR_AF_INET6]) + { + std::tie(addr, std::ignore) = tr_address::from_compact_ipv6(addr_compact); + tr_peerMgrAddPex(&tor_, TR_PEER_FROM_LTEP, &pex, 1); + } + + /* get peer's maximum request queue size */ + if (auto reqq_in = int64_t{}; tr_variantDictFindInt(&*var, TR_KEY_reqq, &reqq_in)) + { + reqq_ = reqq_in; + } +} + +void tr_peerMsgsImpl::parse_ut_metadata(MessageReader& payload_in) +{ + int64_t msg_type = -1; + int64_t piece = -1; + int64_t total_size = 0; + + auto const tmp = payload_in.to_string_view(); + auto const* const msg_end = std::data(tmp) + std::size(tmp); + + auto serde = tr_variant_serde::benc(); + if (auto var = serde.inplace().parse(tmp); var) + { + (void)tr_variantDictFindInt(&*var, TR_KEY_msg_type, &msg_type); + (void)tr_variantDictFindInt(&*var, TR_KEY_piece, &piece); + (void)tr_variantDictFindInt(&*var, TR_KEY_total_size, &total_size); + } + + logtrace(this, fmt::format("got ut_metadata msg: type {:d}, piece {:d}, total_size {:d}", msg_type, piece, total_size)); + + if (msg_type == MetadataMsgType::Reject) + { + // no-op + } + + if (auto const piece_len = msg_end - serde.end(); + msg_type == MetadataMsgType::Data && piece * MetadataPieceSize + piece_len <= total_size) + { + tor_.set_metadata_piece(piece, serde.end(), piece_len); + } + + if (msg_type == MetadataMsgType::Request) + { + if (piece >= 0 && tor_.has_metainfo() && tor_.is_public() && std::size(peer_requested_metadata_pieces_) < MetadataReqQ) + { + peer_requested_metadata_pieces_.push(piece); + } + else + { + /* send a rejection message */ + auto v = tr_variant{}; + tr_variantInitDict(&v, 2); + tr_variantDictAddInt(&v, TR_KEY_msg_type, MetadataMsgType::Reject); + tr_variantDictAddInt(&v, TR_KEY_piece, piece); + protocol_send_message(BtPeerMsgs::Ltep, ut_metadata_id_, serde.to_string(v)); + } + } +} + +// --- + +ReadResult tr_peerMsgsImpl::process_peer_message(uint8_t id, MessageReader& payload) +{ + bool const fext = io_->supports_fext(); + + auto ui32 = uint32_t{}; + + logtrace( + this, + fmt::format( + "got peer msg '{:s}' ({:d}) with payload len {:d}", + BtPeerMsgs::debug_name(id), + static_cast(id), + std::size(payload))); + + if (!is_message_length_correct(tor_, id, sizeof(id) + std::size(payload))) + { + logdbg( + this, + fmt::format( + "bad msg: '{:s}' ({:d}) with payload len {:d}", + BtPeerMsgs::debug_name(id), + static_cast(id), + std::size(payload))); + publish(tr_peer_event::GotError(EMSGSIZE)); + return { READ_ERR, {} }; + } + + switch (id) + { + case BtPeerMsgs::Choke: + logtrace(this, "got Choke"); + set_client_choked(true); + + if (!fext) + { + publish(tr_peer_event::GotChoke()); + } + + update_active(TR_PEER_TO_CLIENT); + break; + + case BtPeerMsgs::Unchoke: + logtrace(this, "got Unchoke"); + set_client_choked(false); + update_active(TR_PEER_TO_CLIENT); + update_desired_request_count(); + break; + + case BtPeerMsgs::Interested: + logtrace(this, "got Interested"); + set_peer_interested(true); + update_active(TR_CLIENT_TO_PEER); + break; + + case BtPeerMsgs::NotInterested: + logtrace(this, "got Not Interested"); + set_peer_interested(false); + update_active(TR_CLIENT_TO_PEER); + break; + + case BtPeerMsgs::Have: + ui32 = payload.to_uint32(); + logtrace(this, fmt::format("got Have: {:d}", ui32)); + + if (tor_.has_metainfo() && ui32 >= tor_.piece_count()) + { + publish(tr_peer_event::GotError(ERANGE)); + return { READ_ERR, {} }; + } + + /* a peer can send the same HAVE message twice... */ + if (!have_.test(ui32)) + { + have_.set(ui32); + publish(tr_peer_event::GotHave(ui32)); + } + + break; + + case BtPeerMsgs::Bitfield: + logtrace(this, "got a bitfield"); + have_ = tr_bitfield{ tor_.has_metainfo() ? tor_.piece_count() : std::size(payload) * 8 }; + have_.set_raw(reinterpret_cast(std::data(payload)), std::size(payload)); + publish(tr_peer_event::GotBitfield(&have_)); + break; + + case BtPeerMsgs::Request: + { + struct peer_request r; + r.index = payload.to_uint32(); + r.offset = payload.to_uint32(); + r.length = payload.to_uint32(); + logtrace(this, fmt::format("got Request: {:d}:{:d}->{:d}", r.index, r.offset, r.length)); + on_peer_made_request(r); + break; + } + + case BtPeerMsgs::Cancel: + { + struct peer_request r; + r.index = payload.to_uint32(); + r.offset = payload.to_uint32(); + r.length = payload.to_uint32(); + cancels_sent_to_client.add(tr_time(), 1); + logtrace(this, fmt::format("got a Cancel {:d}:{:d}->{:d}", r.index, r.offset, r.length)); + + auto& requests = peer_requested_; + if (auto iter = std::find(std::begin(requests), std::end(requests), r); iter != std::end(requests)) + { + requests.erase(iter); + + // bep6: "Even when a request is cancelled, the peer + // receiving the cancel should respond with either the + // corresponding reject or the corresponding piece" + if (fext) + { + protocol_send_reject(r); + } + } + break; + } + + case BtPeerMsgs::Piece: + return read_piece_data(payload); + + case BtPeerMsgs::Port: + // https://www.bittorrent.org/beps/bep_0005.html + // Peers supporting the DHT set the last bit of the 8-byte reserved flags + // exchanged in the BitTorrent protocol handshake. Peer receiving a handshake + // indicating the remote peer supports the DHT should send a PORT message. + // It begins with byte 0x09 and has a two byte payload containing the UDP + // port of the DHT node in network byte order. + { + logtrace(this, "Got a BtPeerMsgs::Port"); + + auto const hport = payload.to_uint16(); + if (auto const dht_port = tr_port::from_host(hport); !std::empty(dht_port)) + { + dht_port_ = dht_port; + session->maybe_add_dht_node(io_->address(), dht_port_); + } + } + break; + + case BtPeerMsgs::FextSuggest: + logtrace(this, "Got a BtPeerMsgs::FextSuggest"); + + if (fext) + { + auto const piece = payload.to_uint32(); + publish(tr_peer_event::GotSuggest(piece)); + } + else + { + publish(tr_peer_event::GotError(EMSGSIZE)); + return { READ_ERR, {} }; + } + + break; + + case BtPeerMsgs::FextAllowedFast: + logtrace(this, "Got a BtPeerMsgs::FextAllowedFast"); + + if (fext) + { + auto const piece = payload.to_uint32(); + publish(tr_peer_event::GotAllowedFast(piece)); + } + else + { + publish(tr_peer_event::GotError(EMSGSIZE)); + return { READ_ERR, {} }; + } + + break; + + case BtPeerMsgs::FextHaveAll: + logtrace(this, "Got a BtPeerMsgs::FextHaveAll"); + + if (fext) + { + have_.set_has_all(); + publish(tr_peer_event::GotHaveAll()); + } + else + { + publish(tr_peer_event::GotError(EMSGSIZE)); + return { READ_ERR, {} }; + } + + break; + + case BtPeerMsgs::FextHaveNone: + logtrace(this, "Got a BtPeerMsgs::FextHaveNone"); + + if (fext) + { + have_.set_has_none(); + publish(tr_peer_event::GotHaveNone()); + } + else + { + publish(tr_peer_event::GotError(EMSGSIZE)); + return { READ_ERR, {} }; + } + + break; + + case BtPeerMsgs::FextReject: + { + struct peer_request r; + r.index = payload.to_uint32(); + r.offset = payload.to_uint32(); + r.length = payload.to_uint32(); + + if (fext) + { + publish(tr_peer_event::GotRejected(tor_.block_info(), tor_.piece_loc(r.index, r.offset).block)); + } + else + { + publish(tr_peer_event::GotError(EMSGSIZE)); + return { READ_ERR, {} }; + } + + break; + } + + case BtPeerMsgs::Ltep: + logtrace(this, "Got a BtPeerMsgs::Ltep"); + parse_ltep(payload); + break; + + default: + logtrace(this, fmt::format("peer sent us an UNKNOWN: {:d}", static_cast(id))); + break; + } + + return { READ_NOW, {} }; +} + +ReadResult tr_peerMsgsImpl::read_piece_data(MessageReader& payload) +{ + // + auto const piece = payload.to_uint32(); + auto const offset = payload.to_uint32(); + auto const len = std::size(payload); + + auto const loc = tor_.piece_loc(piece, offset); + auto const block = loc.block; + auto const block_size = tor_.block_size(block); + + logtrace(this, fmt::format("got {:d} bytes for req {:d}:{:d}->{:d}", len, piece, offset, len)); + + if (loc.block_offset + len > block_size) + { + logwarn(this, fmt::format("got unaligned piece {:d}:{:d}->{:d}", piece, offset, len)); + return { READ_ERR, len }; + } + + if (!tr_peerMgrDidPeerRequest(&tor_, this, block)) + { + logwarn(this, fmt::format("got unrequested piece {:d}:{:d}->{:d}", piece, offset, len)); + return { READ_ERR, len }; + } + + publish(tr_peer_event::GotPieceData(len)); + + if (loc.block_offset == 0U && len == block_size) // simple case: one message has entire block + { + auto buf = std::make_unique(block_size); + payload.to_buf(std::data(*buf), len); + auto const ok = client_got_block(std::move(buf), block) == 0; + return { ok ? READ_NOW : READ_ERR, len }; + } + + auto& blocks = incoming_.blocks; + auto& incoming_block = blocks.try_emplace(block, block_size).first->second; + payload.to_buf(std::data(*incoming_block.buf) + loc.block_offset, len); + + if (!incoming_block.add_span(loc.block_offset, loc.block_offset + len)) + { + return { READ_ERR, len }; // invalid span + } + + if (!incoming_block.has_all()) + { + return { READ_LATER, len }; // we don't have the full block yet + } + + auto block_buf = std::move(incoming_block.buf); + blocks.erase(block); // note: invalidates `incoming_block` local + auto const ok = client_got_block(std::move(block_buf), block) == 0; + return { ok ? READ_NOW : READ_ERR, len }; +} + +// returns 0 on success, or an errno on failure +int tr_peerMsgsImpl::client_got_block(std::unique_ptr block_data, tr_block_index_t const block) +{ + auto const n_expected = tor_.block_size(block); + + if (!block_data) + { + logdbg(this, fmt::format("wrong block size: expected {:d}, got {:d}", n_expected, 0)); + return EMSGSIZE; + } + + if (std::size(*block_data) != tor_.block_size(block)) + { + logdbg(this, fmt::format("wrong block size: expected {:d}, got {:d}", n_expected, std::size(*block_data))); + return EMSGSIZE; + } + + logtrace(this, fmt::format("got block {:d}", block)); + + if (!tr_peerMgrDidPeerRequest(&tor_, this, block)) + { + logdbg(this, "we didn't ask for this message..."); + return 0; + } + + auto const loc = tor_.block_loc(block); + if (tor_.has_piece(loc.piece)) + { + logtrace(this, "we did ask for this message, but the piece is already complete..."); + return 0; + } + + // NB: if writeBlock() fails the torrent may be paused. + // If this happens, this object will be destructed and must no longer be used. + if (auto const err = session->cache->write_block(tor_.id(), block, std::move(block_data)); err != 0) + { + return err; + } + + blame.set(loc.piece); + publish(tr_peer_event::GotBlock(tor_.block_info(), block)); + + return 0; +} + +// --- + +void tr_peerMsgsImpl::did_write(tr_peerIo* /*io*/, size_t bytes_written, bool was_piece_data, void* vmsgs) +{ + auto* const msgs = static_cast(vmsgs); + + if (was_piece_data) + { + msgs->publish(tr_peer_event::SentPieceData(bytes_written)); + } + + msgs->pulse(); +} + +ReadState tr_peerMsgsImpl::can_read(tr_peerIo* io, void* vmsgs, size_t* piece) +{ + auto* const msgs = static_cast(vmsgs); + + // https://www.bittorrent.org/beps/bep_0003.html + // Next comes an alternating stream of length prefixes and messages. + // Messages of length zero are keepalives, and ignored. + // All non-keepalive messages start with a single byte which gives their type. + // + // https://wiki.theory.org/BitTorrentSpecification + // All of the remaining messages in the protocol take the form of + // . The length prefix is a four byte + // big-endian value. The message ID is a single decimal byte. + // The payload is message dependent. + + // read + auto& current_message_len = msgs->incoming_.length; // the full message payload length. Includes the +1 for id length + if (!current_message_len) + { + auto message_len = uint32_t{}; + if (io->read_buffer_size() < sizeof(message_len)) + { + return READ_LATER; + } + + io->read_uint32(&message_len); + + // The keep-alive message is a message with zero bytes, + // specified with the length prefix set to zero. + // There is no message ID and no payload. + if (message_len == 0U) + { + logtrace(msgs, "got KeepAlive"); + return READ_NOW; + } + + current_message_len = message_len; + } + + // read + auto& current_message_type = msgs->incoming_.id; + if (!current_message_type) + { + auto message_type = uint8_t{}; + if (io->read_buffer_size() < sizeof(message_type)) + { + return READ_LATER; + } + + io->read_uint8(&message_type); + current_message_type = message_type; + } + + // read + auto& current_payload = msgs->incoming_.payload; + auto const full_payload_len = *current_message_len - sizeof(*current_message_type); + auto n_left = full_payload_len - std::size(current_payload); + auto const [buf, n_this_pass] = current_payload.reserve_space(std::min(n_left, io->read_buffer_size())); + io->read_bytes(buf, n_this_pass); + current_payload.commit_space(n_this_pass); + n_left -= n_this_pass; + logtrace(msgs, fmt::format("read {:d} payload bytes; {:d} left to go", n_this_pass, n_left)); + + if (n_left > 0U) + { + return READ_LATER; + } + + // The incoming message is now complete. After processing the message + // with `process_peer_message()`, reset the peerMsgs' incoming + // field so it's ready to receive the next message. + + auto const [read_state, n_piece_bytes_read] = msgs->process_peer_message(*current_message_type, current_payload); + *piece = n_piece_bytes_read; + + current_message_len.reset(); + current_message_type.reset(); + current_payload.clear(); + + return read_state; +} + +void tr_peerMsgsImpl::got_error(tr_peerIo* /*io*/, tr_error const& /*error*/, void* vmsgs) +{ + static_cast(vmsgs)->publish(tr_peer_event::GotError(ENOTCONN)); +} + +// --- + +void tr_peerMsgsImpl::pulse() +{ + auto const now_sec = tr_time(); + auto const now_msec = tr_time_msec(); + + update_desired_request_count(); + update_block_requests(); + update_metadata_requests(now_sec); + + for (;;) + { + if (fill_output_buffer(now_sec, now_msec) == 0U) + { + break; + } + } +} + +void tr_peerMsgsImpl::update_metadata_requests(time_t now) const +{ + if (!peer_supports_metadata_xfer_) + { + return; + } + + if (auto const piece = tor_.get_next_metadata_request(now); piece) + { + auto tmp = tr_variant{}; + tr_variantInitDict(&tmp, 3); + tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Request); + tr_variantDictAddInt(&tmp, TR_KEY_piece, *piece); + protocol_send_message(BtPeerMsgs::Ltep, ut_metadata_id_, tr_variant_serde::benc().to_string(tmp)); + } +} + +void tr_peerMsgsImpl::update_block_requests() +{ + if (!tor_.client_can_download()) + { + return; + } + + auto const n_active = tr_peerMgrCountActiveRequestsToPeer(&tor_, this); + if (n_active >= desired_request_count_) + { + return; + } + + TR_ASSERT(client_is_interested()); + TR_ASSERT(!client_is_choked()); + + auto const n_wanted = desired_request_count_ - n_active; + if (auto const requests = tr_peerMgrGetNextRequests(&tor_, this, n_wanted); !std::empty(requests)) + { + request_blocks(std::data(requests), std::size(requests)); + } +} + +[[nodiscard]] size_t tr_peerMsgsImpl::fill_output_buffer(time_t now_sec, uint64_t now_msec) +{ + auto n_bytes_written = size_t{}; + + // fulfill metadata requests + for (;;) + { + auto const old_len = n_bytes_written; + n_bytes_written += add_next_metadata_piece(); + if (old_len == n_bytes_written) + { + break; + } + } + + // fulfill piece requests + for (;;) + { + auto const old_len = n_bytes_written; + n_bytes_written += add_next_block(now_sec, now_msec); + if (old_len == n_bytes_written) + { + break; + } + } + + if (client_sent_at_ != 0 && now_sec - client_sent_at_ > KeepaliveIntervalSecs) + { + n_bytes_written += protocol_send_keepalive(); + } + + return n_bytes_written; +} + +[[nodiscard]] size_t tr_peerMsgsImpl::add_next_metadata_piece() +{ + auto const piece = pop_next_metadata_request(); + + if (!piece.has_value()) // no pending requests + { + return {}; + } + + auto data = tor_.get_metadata_piece(*piece); + if (!data) + { + // send a reject + auto tmp = tr_variant{}; + tr_variantInitDict(&tmp, 2); + tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Reject); + tr_variantDictAddInt(&tmp, TR_KEY_piece, *piece); + return protocol_send_message(BtPeerMsgs::Ltep, ut_metadata_id_, tr_variant_serde::benc().to_string(tmp)); + } + + // send the metadata + auto tmp = tr_variant{}; + tr_variantInitDict(&tmp, 3); + tr_variantDictAddInt(&tmp, TR_KEY_msg_type, MetadataMsgType::Data); + tr_variantDictAddInt(&tmp, TR_KEY_piece, *piece); + tr_variantDictAddInt(&tmp, TR_KEY_total_size, tor_.info_dict_size()); + return protocol_send_message(BtPeerMsgs::Ltep, ut_metadata_id_, tr_variant_serde::benc().to_string(tmp), *data); +} + +[[nodiscard]] size_t tr_peerMsgsImpl::add_next_block(time_t now_sec, uint64_t now_msec) +{ + if (std::empty(peer_requested_) || io_->get_write_buffer_space(now_msec) == 0U) + { + return {}; + } + + auto const req = peer_requested_.front(); + peer_requested_.pop_front(); + + auto buf = std::array{}; + auto ok = is_valid_request(req) && tor_.has_piece(req.index); + + if (ok) + { + ok = tor_.ensure_piece_is_checked(req.index); + + if (!ok) + { + tor_.error().set_local_error(fmt::format("Please Verify Local Data! Piece #{:d} is corrupt.", req.index)); + } + } + + if (ok) + { + ok = session->cache->read_block(tor_, tor_.piece_loc(req.index, req.offset), req.length, std::data(buf)) == 0; + } + + if (ok) + { + blocks_sent_to_peer.add(now_sec, 1); + auto const piece_data = std::string_view{ reinterpret_cast(std::data(buf)), req.length }; + return protocol_send_message(BtPeerMsgs::Piece, req.index, req.offset, piece_data); + } + + if (io_->supports_fext()) + { + return protocol_send_reject(req); + } + + return {}; +} + +// --- + +bool tr_peerMsgsImpl::is_valid_request(peer_request const& req) const +{ + int err = 0; + + if (req.index >= tor_.piece_count()) + { + err = 1; + } + else if (req.length < 1) + { + err = 2; + } + else if (req.offset + req.length > tor_.piece_size(req.index)) + { + err = 3; + } + else if (req.length > tr_block_info::BlockSize) + { + err = 4; + } + else if (tor_.piece_loc(req.index, req.offset, req.length).byte > tor_.total_size()) + { + err = 5; + } + + if (err != 0) + { + tr_logAddTraceTor(&tor_, fmt::format("index {} offset {} length {} err {}", req.index, req.offset, req.length, err)); + } + + return err == 0; +} + +[[nodiscard]] bool tr_peerMsgsImpl::can_add_request_from_peer(peer_request const& req) +{ + if (peer_is_choked()) + { + logtrace(this, "rejecting request from choked peer"); + return false; + } + + if (std::size(peer_requested_) >= ReqQ) + { + logtrace(this, "rejecting request ... reqq is full"); + return false; + } + + if (!is_valid_request(req)) + { + logtrace(this, "rejecting an invalid request."); + return false; + } + + if (!tor_.has_piece(req.index)) + { + logtrace(this, "rejecting request for a piece we don't have."); + return false; + } + + return true; +} + +size_t tr_peerMsgsImpl::max_available_reqs() const +{ + if (tor_.is_done() || !tor_.has_metainfo() || client_is_choked() || !client_is_interested()) + { + return 0; + } + + // Get the rate limit we should use. + // TODO: this needs to consider all the other peers as well... + uint64_t const now = tr_time_msec(); + auto rate = get_piece_speed(now, TR_PEER_TO_CLIENT); + if (tor_.uses_speed_limit(TR_PEER_TO_CLIENT)) + { + rate = std::min(rate, tor_.speed_limit(TR_PEER_TO_CLIENT)); + } + + // honor the session limits, if enabled + if (tor_.uses_session_limits()) + { + if (auto const limit = session->active_speed_limit(TR_PEER_TO_CLIENT)) + { + rate = std::min(rate, *limit); + } + } + + // use this desired rate to figure out how + // many requests we should send to this peer + static auto constexpr Floor = size_t{ 32 }; + static size_t constexpr Seconds = RequestBufSecs; + size_t const estimated_blocks_in_period = (rate.base_quantity() * Seconds) / tr_block_info::BlockSize; + auto const ceil = reqq_.value_or(250); + + return std::clamp(estimated_blocks_in_period, Floor, ceil); } } // namespace tr_peerMsgs::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, @@ -2095,17 +2073,17 @@ tr_peerMsgs::tr_peerMsgs( tr_peerMsgs::~tr_peerMsgs() { peer_info->set_connected(tr_time(), false); - [[maybe_unused]] auto const n_prev = n_peers--; - TR_ASSERT(n_prev > 0U); + TR_ASSERT(n_peers > 0U); + --n_peers; } -tr_peerMsgs* tr_peerMsgsNew( - tr_torrent* const torrent, +tr_peerMsgs* tr_peerMsgs::create( + tr_torrent& torrent, tr_peer_info* const peer_info, std::shared_ptr io, tr_interned_string user_agent, tr_peer_callback_bt callback, void* callback_data) { - return new tr_peerMsgsImpl(torrent, peer_info, std::move(io), user_agent, callback, callback_data); + return new tr_peerMsgsImpl{ torrent, peer_info, std::move(io), user_agent, callback, callback_data }; } diff --git a/libtransmission/peer-msgs.h b/libtransmission/peer-msgs.h index 52f62e2f2..2017a9f89 100644 --- a/libtransmission/peer-msgs.h +++ b/libtransmission/peer-msgs.h @@ -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 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 io, - tr_interned_string user_agent, - tr_peer_callback_bt callback, - void* callback_data); - /* @} */ diff --git a/libtransmission/port-forwarding-upnp.cc b/libtransmission/port-forwarding-upnp.cc index 46fc5eb0e..26041b99d 100644 --- a/libtransmission/port-forwarding-upnp.cc +++ b/libtransmission/port-forwarding-upnp.cc @@ -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{}; - auto int_port = std::array{}; + auto int_port = std::array{}; - 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; } } diff --git a/libtransmission/port-forwarding-upnp.h b/libtransmission/port-forwarding-upnp.h index 11b82dcab..0d6d96e6a 100644 --- a/libtransmission/port-forwarding-upnp.h +++ b/libtransmission/port-forwarding-upnp.h @@ -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); /* @} */ diff --git a/libtransmission/port-forwarding.cc b/libtransmission/port-forwarding.cc index b7189db00..4161c0850 100644 --- a/libtransmission/port-forwarding.cc +++ b/libtransmission/port-forwarding.cc @@ -205,6 +205,7 @@ private: upnp_state_ = tr_upnpPulse( upnp_, + mediator_.advertised_peer_port(), mediator_.local_peer_port(), is_enabled, do_check, diff --git a/libtransmission/port-forwarding.h b/libtransmission/port-forwarding.h index 87ba4ff74..a3344ce76 100644 --- a/libtransmission/port-forwarding.h +++ b/libtransmission/port-forwarding.h @@ -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; diff --git a/libtransmission/quark.cc b/libtransmission/quark.cc index 44ffe72df..5c8468ffc 100644 --- a/libtransmission/quark.cc +++ b/libtransmission/quark.cc @@ -148,6 +148,7 @@ auto constexpr MyStatic = std::array{ "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{ "start-added-torrents"sv, "start-minimized"sv, "startDate"sv, + "start_paused"sv, "status"sv, "statusbar-stats"sv, "tag"sv, diff --git a/libtransmission/quark.h b/libtransmission/quark.h index 8375148ce..2ed31649c 100644 --- a/libtransmission/quark.h +++ b/libtransmission/quark.h @@ -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, diff --git a/libtransmission/resume.cc b/libtransmission/resume.cc index e6f094c7c..f0e2f1141 100644 --- a/libtransmission/resume.cc +++ b/libtransmission/resume.cc @@ -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); diff --git a/libtransmission/resume.h b/libtransmission/resume.h index a836e2e4e..c9449bdb6 100644 --- a/libtransmission/resume.h +++ b/libtransmission/resume.h @@ -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 }; diff --git a/libtransmission/rpc-server.cc b/libtransmission/rpc-server.cc index 2a2fad764..a3d2beaf9 100644 --- a/libtransmission/rpc-server.cc +++ b/libtransmission/rpc-server.cc @@ -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().sun_path) + std::size(TrUnixSocketPrefix) }; #endif diff --git a/libtransmission/rpcimpl.cc b/libtransmission/rpcimpl.cc index 861ad6788..7b61f2023 100644 --- a/libtransmission/rpcimpl.cc +++ b/libtransmission/rpcimpl.cc @@ -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(); val != nullptr) + if (auto const val = var.value_if()) { tor = torrents.get(*val); } - if (auto const* val = var.get_if(); val != nullptr) + if (auto const val = var.value_if()) { 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(); field_sv != nullptr) + if (auto const field_sv = field.value_if()) { 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(); value != nullptr) + if (auto const value = label_var.value_if()) { 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(); val != nullptr) + if (auto const val = file_var.value_if()) { if (auto const idx = static_cast(*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(); val != nullptr) + if (auto const val = url_var.value_if()) { 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(); - auto const* url = urls_vec[i + 1U].get_if(); + auto const id = urls_vec[i].value_if(); + auto const url = urls_vec[i + 1U].value_if(); - if (id != nullptr && url != nullptr) + if (id && url) { ann.replace(static_cast(*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(); val != nullptr) + if (auto const val = id_var.value_if()) { ann.remove(static_cast(*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(TR_KEY_bandwidthPriority); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_bandwidthPriority)) { if (auto const priority = static_cast(*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(TR_KEY_group); val != nullptr) + if (auto const val = args_in.value_if(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(TR_KEY_peer_limit); val != nullptr) + if (auto const val = args_in.value_if(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(TR_KEY_downloadLimit); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_downloadLimit)) { tr_torrentSetSpeedLimit_KBps(tor, TR_DOWN, *val); } - if (auto const* val = args_in.find_if(TR_KEY_sequentialDownload); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_sequentialDownload)) { tor->set_sequential_download(*val); } - if (auto const* val = args_in.find_if(TR_KEY_downloadLimited); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_downloadLimited)) { tor->use_speed_limit(TR_DOWN, *val); } - if (auto const* val = args_in.find_if(TR_KEY_honorsSessionLimits); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_honorsSessionLimits)) { tr_torrentUseSessionLimits(tor, *val); } - if (auto const* val = args_in.find_if(TR_KEY_uploadLimit); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_uploadLimit)) { tr_torrentSetSpeedLimit_KBps(tor, TR_UP, *val); } - if (auto const* val = args_in.find_if(TR_KEY_uploadLimited); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_uploadLimited)) { tor->use_speed_limit(TR_UP, *val); } - if (auto const* val = args_in.find_if(TR_KEY_seedIdleLimit); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_seedIdleLimit)) { tor->set_idle_limit_minutes(static_cast(*val)); } - if (auto const* val = args_in.find_if(TR_KEY_seedIdleMode); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_seedIdleMode)) { tor->set_idle_limit_mode(static_cast(*val)); } - if (auto const* val = args_in.find_if(TR_KEY_seedRatioLimit); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_seedRatioLimit)) { tor->set_seed_ratio(*val); } - if (auto const* val = args_in.find_if(TR_KEY_seedRatioMode); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_seedRatioMode)) { tor->set_seed_ratio_mode(static_cast(*val)); } - if (auto const* val = args_in.find_if(TR_KEY_queuePosition); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_queuePosition)) { tr_torrentSetQueuePosition(tor, static_cast(*val)); } - if (auto const* val = args_in.find_if(TR_KEY_trackerAdd); val != nullptr) + if (auto const* val = args_in.find_if(TR_KEY_trackerAdd)) { errmsg = add_tracker_urls(tor, *val); } - if (auto const* val = args_in.find_if(TR_KEY_trackerRemove); val != nullptr) + if (auto const* val = args_in.find_if(TR_KEY_trackerRemove)) { errmsg = remove_trackers(tor, *val); } - if (auto const* val = args_in.find_if(TR_KEY_trackerReplace); val != nullptr) + if (auto const* val = args_in.find_if(TR_KEY_trackerReplace)) { errmsg = replace_trackers(tor, *val); } - if (auto const* val = args_in.find_if(TR_KEY_trackerList); val != nullptr) + if (auto const val = args_in.value_if(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(TR_KEY_location); - if (location == nullptr) + auto const location = args_in.value_if(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(TR_KEY_ipProtocol); val != nullptr) + if (auto const val = args_in.value_if(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(); val != nullptr) + if (auto const val = idx_var.value_if()) { files.emplace_back(static_cast(*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(TR_KEY_paused); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_paused)) { ctor.set_paused(TR_FORCE, *val); } - if (auto const* val = args_in.find_if(TR_KEY_peer_limit); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_peer_limit)) { ctor.set_peer_limit(TR_FORCE, static_cast(*val)); } - if (auto const* val = args_in.find_if(TR_KEY_bandwidthPriority); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_bandwidthPriority)) { ctor.set_bandwidth_priority(static_cast(*val)); } - if (auto const* val = args_in.find_if(TR_KEY_files_unwanted); val != nullptr) + if (auto const val = args_in.find_if(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_KEY_files_wanted); val != nullptr) + if (auto const val = args_in.find_if(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_KEY_priority_low); val != nullptr) + if (auto const val = args_in.find_if(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_KEY_priority_normal); val != nullptr) + if (auto const* val = args_in.find_if(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_KEY_priority_high); val != nullptr) + if (auto const* val = args_in.find_if(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_KEY_labels); val != nullptr) + if (auto const* val = args_in.find_if(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& strings, tr_variant const& var) { - if (auto const* val = var.get_if(); val != nullptr) + if (auto const val = var.value_if()) { 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(TR_KEY_speed_limit_down_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_speed_limit_down_enabled)) { limits.down_limited = *val; } - if (auto const* const val = args_in.find_if(TR_KEY_speed_limit_up_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_speed_limit_up_enabled)) { limits.up_limited = *val; } - if (auto const* const val = args_in.find_if(TR_KEY_speed_limit_down); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_speed_limit_down)) { limits.down_limit = Speed{ *val, Speed::Units::KByps }; } - if (auto const* const val = args_in.find_if(TR_KEY_speed_limit_up); val != nullptr) + if (auto const val = args_in.value_if(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(TR_KEY_honorsSessionLimits); val != nullptr) + if (auto const val = args_in.value_if(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(TR_KEY_cache_size_mb); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_cache_size_mb)) { tr_sessionSetCacheLimit_MB(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_alt_speed_up); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_alt_speed_up)) { tr_sessionSetAltSpeed_KBps(session, TR_UP, *val); } - if (auto const* val = args_in.find_if(TR_KEY_alt_speed_down); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_alt_speed_down)) { tr_sessionSetAltSpeed_KBps(session, TR_DOWN, *val); } - if (auto const* val = args_in.find_if(TR_KEY_alt_speed_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_alt_speed_enabled)) { tr_sessionUseAltSpeed(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_alt_speed_time_begin); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_alt_speed_time_begin)) { tr_sessionSetAltSpeedBegin(session, static_cast(*val)); } - if (auto const* val = args_in.find_if(TR_KEY_alt_speed_time_end); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_alt_speed_time_end)) { tr_sessionSetAltSpeedEnd(session, static_cast(*val)); } - if (auto const* val = args_in.find_if(TR_KEY_alt_speed_time_day); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_alt_speed_time_day)) { tr_sessionSetAltSpeedDay(session, static_cast(*val)); } - if (auto const* val = args_in.find_if(TR_KEY_alt_speed_time_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_alt_speed_time_enabled)) { tr_sessionUseAltSpeedTime(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_blocklist_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_blocklist_enabled)) { session->set_blocklist_enabled(*val); } - if (auto const* val = args_in.find_if(TR_KEY_blocklist_url); val != nullptr) + if (auto const val = args_in.value_if(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(TR_KEY_queue_stalled_minutes); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_queue_stalled_minutes)) { tr_sessionSetQueueStalledMinutes(session, static_cast(*val)); } - if (auto const* val = args_in.find_if(TR_KEY_queue_stalled_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_queue_stalled_enabled)) { tr_sessionSetQueueStalledEnabled(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_default_trackers); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_default_trackers)) { session->setDefaultTrackers(*val); } - if (auto const* val = args_in.find_if(TR_KEY_download_queue_size); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_download_queue_size)) { tr_sessionSetQueueSize(session, TR_DOWN, *val); } - if (auto const* val = args_in.find_if(TR_KEY_download_queue_enabled); val != nullptr) + if (auto const val = args_in.value_if(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(TR_KEY_incomplete_dir_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_incomplete_dir_enabled)) { session->useIncompleteDir(*val); } - if (auto const* val = args_in.find_if(TR_KEY_peer_limit_global); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_peer_limit_global)) { tr_sessionSetPeerLimit(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_peer_limit_per_torrent); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_peer_limit_per_torrent)) { tr_sessionSetPeerLimitPerTorrent(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_pex_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_pex_enabled)) { tr_sessionSetPexEnabled(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_dht_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_dht_enabled)) { tr_sessionSetDHTEnabled(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_utp_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_utp_enabled)) { tr_sessionSetUTPEnabled(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_lpd_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_lpd_enabled)) { tr_sessionSetLPDEnabled(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_peer_port_random_on_start); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_peer_port_random_on_start)) { tr_sessionSetPeerPortRandomOnStart(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_peer_port); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_peer_port)) { tr_sessionSetPeerPort(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_port_forwarding_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_port_forwarding_enabled)) { tr_sessionSetPortForwardingEnabled(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_rename_partial_files); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_rename_partial_files)) { tr_sessionSetIncompleteFileNamingEnabled(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_seedRatioLimit); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_seedRatioLimit)) { tr_sessionSetRatioLimit(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_seedRatioLimited); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_seedRatioLimited)) { tr_sessionSetRatioLimited(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_idle_seeding_limit); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_idle_seeding_limit)) { tr_sessionSetIdleLimit(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_idle_seeding_limit_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_idle_seeding_limit_enabled)) { tr_sessionSetIdleLimited(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_start_added_torrents); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_start_added_torrents)) { tr_sessionSetPaused(session, !*val); } - if (auto const* val = args_in.find_if(TR_KEY_seed_queue_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_seed_queue_enabled)) { tr_sessionSetQueueEnabled(session, TR_UP, *val); } - if (auto const* val = args_in.find_if(TR_KEY_seed_queue_size); val != nullptr) + if (auto const val = args_in.value_if(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(enabled_key); val != nullptr) + if (auto const val = args_in.value_if(enabled_key)) { session->useScript(script, *val); } - if (auto const* val = args_in.find_if(script_key); val != nullptr) + if (auto const val = args_in.value_if(script_key)) { session->setScript(script, *val); } } - if (auto const* val = args_in.find_if(TR_KEY_trash_original_torrent_files); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_trash_original_torrent_files)) { tr_sessionSetDeleteSource(session, *val); } - if (auto const* val = args_in.find_if(TR_KEY_speed_limit_down); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_speed_limit_down)) { session->set_speed_limit(TR_DOWN, Speed{ *val, Speed::Units::KByps }); } - if (auto const* val = args_in.find_if(TR_KEY_speed_limit_down_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_speed_limit_down_enabled)) { tr_sessionLimitSpeed(session, TR_DOWN, *val); } - if (auto const* val = args_in.find_if(TR_KEY_speed_limit_up); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_speed_limit_up)) { session->set_speed_limit(TR_UP, Speed{ *val, Speed::Units::KByps }); } - if (auto const* val = args_in.find_if(TR_KEY_speed_limit_up_enabled); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_speed_limit_up_enabled)) { tr_sessionLimitSpeed(session, TR_UP, *val); } - if (auto const* val = args_in.find_if(TR_KEY_encryption); val != nullptr) + if (auto const val = args_in.value_if(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(TR_KEY_anti_brute_force_threshold); val != nullptr) + if (auto const val = args_in.value_if(TR_KEY_anti_brute_force_threshold)) { tr_sessionSetAntiBruteForceThreshold(session, static_cast(*val)); } - if (auto const* val = args_in.find_if(TR_KEY_anti_brute_force_enabled); val != nullptr) + if (auto const val = args_in.value_if(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(); field_name != nullptr) + if (auto const field_name = field_var.value_if()) { 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{}; if (request_map != nullptr) { // find the args - if (auto const* val = request_map->find_if(TR_KEY_arguments); val != nullptr) + if (auto const* val = request_map->find_if(TR_KEY_arguments)) { args_in = val; } // find the requested method - if (auto const* val = request_map->find_if(TR_KEY_method); val != nullptr) + if (auto const val = request_map->value_if(TR_KEY_method)) { method_name = *val; } - } - auto const tag = request_map->value_if(TR_KEY_tag); + tag = request_map->value_if(TR_KEY_tag); + } auto const test = [method_name](auto const& handler) { diff --git a/libtransmission/session-alt-speeds.h b/libtransmission/session-alt-speeds.h index 1671b8e74..084be65c7 100644 --- a/libtransmission/session-alt-speeds.h +++ b/libtransmission/session-alt-speeds.h @@ -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 } { } diff --git a/libtransmission/session.cc b/libtransmission/session.cc index f3d29378e..67f76b532 100644 --- a/libtransmission/session.cc +++ b/libtransmission/session.cc @@ -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(TR_KEY_uploadLimited); val != nullptr) + if (auto const val = group_map->value_if(TR_KEY_uploadLimited)) { limits.up_limited = *val; } - if (auto const* val = group_map->find_if(TR_KEY_downloadLimited); val != nullptr) + if (auto const val = group_map->value_if(TR_KEY_downloadLimited)) { limits.down_limited = *val; } - if (auto const* val = group_map->find_if(TR_KEY_uploadLimit); val != nullptr) + if (auto const val = group_map->value_if(TR_KEY_uploadLimit)) { limits.up_limit = Speed{ *val, Speed::Units::KByps }; } - if (auto const* val = group_map->find_if(TR_KEY_downloadLimit); val != nullptr) + if (auto const val = group_map->value_if(TR_KEY_downloadLimit)) { limits.down_limit = Speed{ *val, Speed::Units::KByps }; } group.set_limits(limits); - if (auto const* val = group_map->find_if(TR_KEY_honorsSessionLimits); val != nullptr) + if (auto const val = group_map->value_if(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(); settings_map != nullptr) { - if (auto const* val = settings_map->find_if(TR_KEY_message_level); val != nullptr) + if (auto const val = settings_map->value_if(TR_KEY_message_level)) { tr_logSetLevel(static_cast(*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* 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! diff --git a/libtransmission/session.h b/libtransmission/session.h index 5b0824b0b..477f102db 100644 --- a/libtransmission/session.h +++ b/libtransmission/session.h @@ -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(); diff --git a/libtransmission/settings.cc b/libtransmission/settings.cc index ea3b0775c..24f0a5034 100644 --- a/libtransmission/settings.cc +++ b/libtransmission/settings.cc @@ -4,13 +4,13 @@ // License text can be found in the licenses/ folder. #include +#include #include // size_t #include // int64_t, uint32_t #include #include #include #include -#include #include @@ -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 +using Lookup = std::array, N>; + +// --- + +bool load_bool(tr_variant const& src, bool* tgt) { -public: - template - static std::optional load(tr_variant const& src); - - template - static tr_variant save(T const& val); - -private: - template - using Lookup = std::array, N>; - - static auto constexpr EncryptionKeys = Lookup{ { - { "required", TR_ENCRYPTION_REQUIRED }, - { "preferred", TR_ENCRYPTION_PREFERRED }, - { "allowed", TR_CLEAR_PREFERRED }, - } }; - - static auto constexpr LogKeys = Lookup{ { - { "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{ { - { "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{ { - { "fast", TR_VERIFY_ADDED_FAST }, - { "full", TR_VERIFY_ADDED_FULL }, - } }; - - static auto constexpr PreferredTransportKeys = Lookup{ { - { "utp", TR_PREFER_UTP }, - { "tcp", TR_PREFER_TCP }, - } }; -}; - -template<> -std::optional VariantConverter::load(tr_variant const& src) -{ - if (auto val = src.get_if(); val != nullptr) + if (auto val = src.value_if()) { - 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 VariantConverter::load(tr_variant const& src) +bool load_double(tr_variant const& src, double* tgt) { - if (auto val = src.get_if(); val != nullptr) + if (auto val = src.value_if()) { - 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 VariantConverter::load(tr_variant const& src) -{ - if (auto val = src.get_if(); 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 VariantConverter::load(tr_variant const& src) -{ - static constexpr auto Keys = EncryptionKeys; +auto constexpr EncryptionKeys = Lookup{ { + { "required", TR_ENCRYPTION_REQUIRED }, + { "preferred", TR_ENCRYPTION_PREFERRED }, + { "allowed", TR_CLEAR_PREFERRED }, +} }; - if (auto const* val = src.get_if(); 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()) { auto const needle = tr_strlower(tr_strv_strip(*val)); @@ -148,39 +90,49 @@ std::optional VariantConverter::load(tr_variant const& src) { if (key == needle) { - return encryption; + *tgt = encryption; + return true; } } } - if (auto const* val = src.get_if(); val != nullptr) + if (auto const val = src.value_if()) { 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(val); } // --- -template<> -std::optional VariantConverter::load(tr_variant const& src) -{ - static constexpr auto Keys = LogKeys; +auto constexpr LogKeys = Lookup{ { + { "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(); 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()) { auto const needle = tr_strlower(tr_strv_strip(*val)); @@ -188,85 +140,110 @@ std::optional VariantConverter::load(tr_variant const& src) { if (needle == name) { - return log_level; + *tgt = log_level; + return true; } } } - if (auto const* val = src.get_if(); val != nullptr) + if (auto const val = src.value_if()) { 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(val); } // --- -template<> -std::optional 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(); val != nullptr) + if (auto const val = src.value_if()) { if (auto const mode = tr_num_parse(*val, nullptr, 8); mode) { - return static_cast(*mode); + *tgt = static_cast(*mode); + return true; } } - if (auto const* val = src.get_if(); val != nullptr) + if (auto const val = src.value_if()) { - return static_cast(*val); + *tgt = static_cast(*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 VariantConverter::load(tr_variant const& src) +bool load_msec(tr_variant const& src, std::chrono::milliseconds* tgt) { - if (auto const* val = src.get_if(); val != nullptr) + if (auto val = src.value_if()) { - 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()) + { + *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 VariantConverter::load(tr_variant const& src) -{ - static constexpr auto Keys = PreallocationKeys; +auto constexpr PreallocationKeys = Lookup{ { + { "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(); 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()) { auto const needle = tr_strlower(tr_strv_strip(*val)); @@ -274,39 +251,44 @@ std::optional VariantConverter::load(tr_variant co { if (name == needle) { - return value; + *tgt = value; + return true; } } } - if (auto const* val = src.get_if(); val != nullptr) + if (auto const val = src.value_if()) { for (auto const& [name, value] : Keys) { if (value == static_cast(*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(val); } // --- -template<> -std::optional VariantConverter::load(tr_variant const& src) -{ - static constexpr auto Keys = PreferredTransportKeys; +auto constexpr PreferredTransportKeys = Lookup{ { + { "utp", TR_PREFER_UTP }, + { "tcp", TR_PREFER_TCP }, +} }; - if (auto const* val = src.get_if(); 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()) { auto const needle = tr_strlower(tr_strv_strip(*val)); @@ -314,27 +296,28 @@ std::optional VariantConverter::load(tr_variant const& s { if (name == needle) { - return value; + *tgt = value; + return true; } } } - if (auto const* val = src.get_if(); val != nullptr) + if (auto const val = src.value_if()) { 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 VariantConverter::load(tr_variant const& src) +bool load_size_t(tr_variant const& src, size_t* tgt) { - if (auto const* val = src.get_if(); val != nullptr) + if (auto const val = src.value_if()) { - return static_cast(*val); + *tgt = static_cast(*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 VariantConverter::load(tr_variant const& src) +bool load_string(tr_variant const& src, std::string* tgt) { - if (auto const* val = src.get_if(); val != nullptr) + if (auto const val = src.value_if()) { - 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 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(); val != nullptr) + if (auto const val = src.value_if()) { - 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(); val != nullptr) + if (auto const val = src.value_if()) { - return tr_tos_t{ static_cast(*val) }; + *tgt = tr_tos_t{ static_cast(*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 VariantConverter::load(tr_variant const& src) +auto constexpr VerifyModeKeys = Lookup{ { + { "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(); val != nullptr) + if (auto const val = src.value_if()) { auto const needle = tr_strlower(tr_strv_strip(*val)); @@ -424,27 +414,28 @@ std::optional VariantConverter::load(tr_variant const& src { if (name == needle) { - return value; + *tgt = value; + return true; } } } - if (auto const* val = src.get_if(); val != nullptr) + if (auto const val = src.value_if()) { 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(val); } - -struct LoadVisitor -{ - explicit constexpr LoadVisitor(tr_variant const& src) - : src_{ src } - { - } - - template - void operator()(T* const tgt) - { - if (auto val = VariantConverter::load(src_)) - { - *tgt = *val; - } - } - -private: - tr_variant const& src_; -}; - -struct SaveVisitor -{ - constexpr SaveVisitor(tr_variant::Map& tgt, tr_quark key) - : tgt_{ tgt } - , key_{ key } - { - } - - template - 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(); @@ -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; diff --git a/libtransmission/settings.h b/libtransmission/settings.h index 92a80bc0f..168a4d5c1 100644 --- a/libtransmission/settings.h +++ b/libtransmission/settings.h @@ -5,19 +5,12 @@ #pragma once -#include -#include // for size_t -#include -#include -#include +#include +#include +#include +#include #include -#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; - using Fields = std::vector; + Settings(); - Settings() = default; + // convert from tr_variant to T + template + using Load = bool (*)(tr_variant const& src, T* tgt); + + // convert from T to tr_variant + template + using Save = tr_variant (*)(T const& src); + + template + void add_type_handler(Load load_handler, Save 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(tgt)); }); + save_.insert_or_assign(key, [save_handler](void const* src) { return save_handler(*static_cast(src)); }); + } + + struct Field + { + template + 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; [[nodiscard]] virtual Fields fields() = 0; + +private: + std::unordered_map> save_; + std::unordered_map> load_; }; } // namespace libtransmission diff --git a/libtransmission/torrent-metainfo.cc b/libtransmission/torrent-metainfo.cc index 9cdb8fbad..9ad4cf99e 100644 --- a/libtransmission/torrent-metainfo.cc +++ b/libtransmission/torrent-metainfo.cc @@ -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. // diff --git a/libtransmission/torrent-metainfo.h b/libtransmission/torrent-metainfo.h index f555b60e6..d878646e9 100644 --- a/libtransmission/torrent-metainfo.h +++ b/libtransmission/torrent-metainfo.h @@ -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; diff --git a/libtransmission/torrent.cc b/libtransmission/torrent.cc index e26a73988..1b4f23e3e 100644 --- a/libtransmission/torrent.cc +++ b/libtransmission/torrent.cc @@ -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{ { "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())); diff --git a/libtransmission/torrent.h b/libtransmission/torrent.h index d09ef9a21..8bb1af64c 100644 --- a/libtransmission/torrent.h +++ b/libtransmission/torrent.h @@ -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(); } } diff --git a/libtransmission/tr-dht.cc b/libtransmission/tr-dht.cc index 2303842b1..9c1cd9bc2 100644 --- a/libtransmission/tr-dht.cc +++ b/libtransmission/tr-dht.cc @@ -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 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_ = tr_rand_obj(); + 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 const periodic_timer_; Id id_ = {}; + int64_t id_timestamp_ = {}; Nodes bootstrap_queue_; size_t n_bootstrapped_ = 0; diff --git a/libtransmission/tr-udp.cc b/libtransmission/tr-udp.cc index 0c9c69f7c..a1d219033 100644 --- a/libtransmission/tr-udp.cc +++ b/libtransmission/tr-udp.cc @@ -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(&size), sizeof(size)); - - if (rc < 0) + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&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(&size), sizeof(size)); - - if (rc < 0) + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&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(&rbuf), &rbuf_len); - - if (rc < 0) + if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&rbuf), &rbuf_len) < 0) { rbuf = 0; } - rc = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&sbuf), &sbuf_len); - - if (rc < 0) + if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&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(&from); - - auto const rc = recvfrom(s, reinterpret_cast(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(vsession); - if (buf[0] == 'd') + auto got_utp_packet = false; + + for (;;) { - if (session->dht_) + auto const n_read = recvfrom(s, reinterpret_cast(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(&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(&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(&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(&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(&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(&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(&one), sizeof(one)); -#endif } } } diff --git a/libtransmission/tr-utp.cc b/libtransmission/tr-utp.cc index 745fd1898..fae16e9dc 100644 --- a/libtransmission/tr-utp.cc +++ b/libtransmission/tr-utp.cc @@ -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(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(); diff --git a/libtransmission/tr-utp.h b/libtransmission/tr-utp.h index 2cb658536..3b8b08393 100644 --- a/libtransmission/tr-utp.h +++ b/libtransmission/tr-utp.h @@ -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); diff --git a/libtransmission/variant.cc b/libtransmission/variant.cc index df8c42cd5..bf2daf69c 100644 --- a/libtransmission/variant.cc +++ b/libtransmission/variant.cc @@ -50,6 +50,24 @@ namespace return tr_variant::NoneIndex; } +template +[[nodiscard]] bool value_if(tr_variant const* const var, T* const setme) +{ + if (var != nullptr) + { + if (auto val = var->value_if()) + { + if (setme) + { + *setme = *val; + } + return true; + } + } + + return false; +} + template [[nodiscard]] tr_variant* dict_set(tr_variant* const var, tr_quark const key, T&& val) { @@ -82,6 +100,79 @@ template // --- +// 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 tr_variant::value_if() noexcept +{ + switch (index()) + { + case IntIndex: + return *get_if(); + + case BoolIndex: + return *get_if() ? 1 : 0; + + default: + return {}; + } +} + +template<> +[[nodiscard]] std::optional tr_variant::value_if() noexcept +{ + switch (index()) + { + case BoolIndex: + return *get_if(); + + case IntIndex: + if (auto const val = *get_if(); val == 0 || val == 1) + { + return val != 0; + } + break; + + case StringIndex: + if (auto const val = *get_if(); val == "true") + { + return true; + } + else if (val == "false") + { + return false; + } + break; + + default: + break; + } + + return {}; +} + +template<> +[[nodiscard]] std::optional tr_variant::value_if() noexcept +{ + switch (index()) + { + case DoubleIndex: + return *get_if(); + + case IntIndex: + return static_cast(*get_if()); + + case StringIndex: + return tr_num_parse(*get_if()); + + 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(); - } - return true; - - case tr_variant::BoolIndex: - if (setme != nullptr) - { - *setme = *var->get_if() ? 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(); - 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(); - return true; - - case tr_variant::IntIndex: - if (auto const val = *var->get_if(); val == 0 || val == 1) - { - *setme = val != 0; - return true; - } - break; - - case tr_variant::StringIndex: - if (auto const val = *var->get_if(); 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(); - return true; - - case tr_variant::IntIndex: - *setme = static_cast(*var->get_if()); - return true; - - case tr_variant::StringIndex: - if (auto const val = tr_num_parse(*var->get_if()); 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) diff --git a/libtransmission/variant.h b/libtransmission/variant.h index 5962384b4..dcb251052 100644 --- a/libtransmission/variant.h +++ b/libtransmission/variant.h @@ -158,11 +158,11 @@ public: } template - [[nodiscard]] TR_CONSTEXPR20 std::optional value_if(tr_quark const key) const noexcept + [[nodiscard]] std::optional value_if(tr_quark const key) const noexcept { - if (auto const* const value = find_if(key); value != nullptr) + if (auto it = find(key); it != end()) { - return std::optional{ *value }; + return it->second.value_if(); } return {}; @@ -180,9 +180,9 @@ public: tr_variant& operator=(tr_variant&& that) noexcept = default; template - tr_variant(Val value) + tr_variant(Val&& value) { - *this = std::move(value); + *this = std::forward(value); } [[nodiscard]] static auto make_map(size_t const n_reserve = 0U) noexcept @@ -310,6 +310,23 @@ public: return const_cast(this)->get_if(); } + template + [[nodiscard]] constexpr std::optional value_if() noexcept + { + if (auto const* const val = get_if()) + { + return *val; + } + + return {}; + } + + template + [[nodiscard]] std::optional value_if() const noexcept + { + return const_cast(this)->value_if(); + } + template [[nodiscard]] constexpr bool holds_alternative() const noexcept { @@ -371,6 +388,13 @@ private: std::variant val_; }; +template<> +[[nodiscard]] std::optional tr_variant::value_if() noexcept; +template<> +[[nodiscard]] std::optional tr_variant::value_if() noexcept; +template<> +[[nodiscard]] std::optional tr_variant::value_if() noexcept; + // --- Strings bool tr_variantGetStrView(tr_variant const* variant, std::string_view* setme); diff --git a/libtransmission/version.h.in b/libtransmission/version.h.in index 2448fb281..778deb2d1 100644 --- a/libtransmission/version.h.in +++ b/libtransmission/version.h.in @@ -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} diff --git a/libtransmission/web-utils.cc b/libtransmission/web-utils.cc index 1e5c545fc..25c233daa 100644 --- a/libtransmission/web-utils.cc +++ b/libtransmission/web-utils.cc @@ -340,8 +340,13 @@ std::optional 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_urlParse(std::string_view url) std::optional 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) diff --git a/libtransmission/webseed.cc b/libtransmission/webseed.cc index fbb04f17d..9c1a133de 100644 --- a/libtransmission/webseed.cc +++ b/libtransmission/webseed.cc @@ -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 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 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 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(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{ 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(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(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 -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::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(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(torrent, url, callback, callback_data); } diff --git a/libtransmission/webseed.h b/libtransmission/webseed.h index f064abd67..8fef98ed4 100644 --- a/libtransmission/webseed.h +++ b/libtransmission/webseed.h @@ -9,6 +9,7 @@ #error only libtransmission should #include this header. #endif +#include #include #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 create( + tr_torrent& torrent, + std::string_view, + tr_peer_callback_webseed callback, + void* callback_data); + + [[nodiscard]] virtual tr_webseed_view get_view() const = 0; +}; diff --git a/macosx/BadgeView.mm b/macosx/BadgeView.mm index 81b841c08..9841102c9 100644 --- a/macosx/BadgeView.mm +++ b/macosx/BadgeView.mm @@ -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; } diff --git a/macosx/Base.lproj/InfoGeneralView.xib b/macosx/Base.lproj/InfoGeneralView.xib index 343f528fd..d2ac95f22 100644 --- a/macosx/Base.lproj/InfoGeneralView.xib +++ b/macosx/Base.lproj/InfoGeneralView.xib @@ -1,8 +1,8 @@ - + - + @@ -13,6 +13,8 @@ + + @@ -111,36 +113,6 @@ - - - - - - - - - - - - - - - - - @@ -189,6 +161,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -201,9 +219,12 @@ + + + @@ -226,9 +247,12 @@ + + + diff --git a/macosx/Base.lproj/PrefsWindow.xib b/macosx/Base.lproj/PrefsWindow.xib index 6b99fd4ef..bfbb8e676 100644 --- a/macosx/Base.lproj/PrefsWindow.xib +++ b/macosx/Base.lproj/PrefsWindow.xib @@ -91,7 +91,6 @@ - diff --git a/macosx/CMakeLists.txt b/macosx/CMakeLists.txt index 9ef277835..3be8fe014 100644 --- a/macosx/CMakeLists.txt +++ b/macosx/CMakeLists.txt @@ -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) diff --git a/macosx/Controller.mm b/macosx/Controller.mm index 9f7b015b9..1f84de1e3 100644 --- a/macosx/Controller.mm +++ b/macosx/Controller.mm @@ -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 49152–65535 + // 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)versionComparatorForUpdater:(SUUpdater*)updater +{ + return [VersionComparator new]; +} + +@end diff --git a/macosx/Defaults.plist b/macosx/Defaults.plist index c3ed9bdb3..115825501 100644 --- a/macosx/Defaults.plist +++ b/macosx/Defaults.plist @@ -14,8 +14,6 @@ BadgeUploadRate - BindPort - 51413 BlocklistAutoUpdate BlocklistNew diff --git a/macosx/Info.plist.in b/macosx/Info.plist.in index ebfeeb3e7..cb3aff3b3 100644 --- a/macosx/Info.plist.in +++ b/macosx/Info.plist.in @@ -61,7 +61,7 @@ CFBundleVersion - 14714.@TR_VERSION_MAJOR@.@TR_VERSION_MINOR@.@TR_VERSION_PATCH@ + @CFBUNDLE_VERSION@ LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/macosx/InfoGeneralViewController.mm b/macosx/InfoGeneralViewController.mm index 95985ae60..dec78f267 100644 --- a/macosx/InfoGeneralViewController.mm +++ b/macosx/InfoGeneralViewController.mm @@ -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]; diff --git a/macosx/InfoOptionsViewController.mm b/macosx/InfoOptionsViewController.mm index 497f35b95..a043a2d6f 100644 --- a/macosx/InfoOptionsViewController.mm +++ b/macosx/InfoOptionsViewController.mm @@ -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; } diff --git a/macosx/InfoWindowController.mm b/macosx/InfoWindowController.mm index c6b102a76..bc056bf24 100644 --- a/macosx/InfoWindowController.mm +++ b/macosx/InfoWindowController.mm @@ -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; } } diff --git a/macosx/PortChecker.mm b/macosx/PortChecker.mm index 3184ccb9f..31e1ac6d5 100644 --- a/macosx/PortChecker.mm +++ b/macosx/PortChecker.mm @@ -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); diff --git a/macosx/PrefsController.mm b/macosx/PrefsController.mm index 5e0cee08d..635d07342 100644 --- a/macosx/PrefsController.mm +++ b/macosx/PrefsController.mm @@ -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]; diff --git a/macosx/StatusBarController.mm b/macosx/StatusBarController.mm index 9d16d66c2..2102e58a3 100644 --- a/macosx/StatusBarController.mm +++ b/macosx/StatusBarController.mm @@ -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; diff --git a/macosx/Torrent.h b/macosx/Torrent.h index ff3693881..777fbc183 100644 --- a/macosx/Torrent.h +++ b/macosx/Torrent.h @@ -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; diff --git a/macosx/Torrent.mm b/macosx/Torrent.mm index 18de4b28e..ac6fc52ba 100644 --- a/macosx/Torrent.mm +++ b/macosx/Torrent.mm @@ -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) diff --git a/macosx/TorrentTableView.mm b/macosx/TorrentTableView.mm index 54af6cc07..44cb140cb 100644 --- a/macosx/TorrentTableView.mm +++ b/macosx/TorrentTableView.mm @@ -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]) { diff --git a/macosx/TransmissionHelp/html/FAQ.html b/macosx/TransmissionHelp/html/FAQ.html index f65155a0c..f5fa1b632 100644 --- a/macosx/TransmissionHelp/html/FAQ.html +++ b/macosx/TransmissionHelp/html/FAQ.html @@ -16,7 +16,7 @@

Read these tips for maximizing your download speed.

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 this page, it is likely you will experience these issues.

-

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.

+

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.

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.

@@ -24,7 +24,7 @@

Why isn't my torrent downloading at all?

-

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.

+

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.

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.

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.

diff --git a/macosx/TransmissionHelp/html/check.html b/macosx/TransmissionHelp/html/check.html index 47d4824b4..267a0ee56 100644 --- a/macosx/TransmissionHelp/html/check.html +++ b/macosx/TransmissionHelp/html/check.html @@ -20,7 +20,7 @@
  1. Select the relevant torrent.
  2. -
  3. Go to the Transfers menu → Verify Local Data.
  4. +
  5. Go to the Transfers menu → Verify Local Data.
diff --git a/macosx/TransmissionHelp/html/gettingstarted.html b/macosx/TransmissionHelp/html/gettingstarted.html index 825fdc188..ab652dc79 100644 --- a/macosx/TransmissionHelp/html/gettingstarted.html +++ b/macosx/TransmissionHelp/html/gettingstarted.html @@ -83,14 +83,14 @@
Can I schedule my transfers?
scheduling
-

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.

+

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.

When Speed Limit Mode is enabled, the turtle will be illuminated in blue.

Can I queue my transfers?
queue
-

Yes, you can queue seeding and/or downloading transfers via Preferences → Transfers → Management.

+

Yes, you can queue seeding and/or downloading transfers via Preferences → Transfers → Management.

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..."

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".

@@ -100,8 +100,8 @@

Transmission allows you to sort your torrents by various criteria. Choose "Sort Transfers By" in the View menu, as well as the Action menu.

You can also filter your torrents by their activity state. Simply enable the Filter bar in the View menu.

-

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).

-

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.

+

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).

+

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.

Where can I find more detailed information on my torrents?
@@ -122,7 +122,7 @@

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.

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).

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.

-

If the window doesn't appear when opening a torrent, ensure that "Display options window" is checked in Preferences → Transfers → Adding.

+

If the window doesn't appear when opening a torrent, ensure that "Display options window" is checked in Preferences → Transfers → Adding.

diff --git a/macosx/TransmissionHelp/html/peers.html b/macosx/TransmissionHelp/html/peers.html index 1fd821322..31c283564 100644 --- a/macosx/TransmissionHelp/html/peers.html +++ b/macosx/TransmissionHelp/html/peers.html @@ -23,13 +23,13 @@

What is encryption?

-

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.

+

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.

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.

What are 'connections'?

-

Global maximum connections (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.

+

Global maximum connections (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.

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!

@@ -43,7 +43,7 @@

What is a blocklist?

-

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.

+

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.

The internet address may be to a text file or compressed file. Most standard compression formats are supported, including ZIP.

diff --git a/macosx/TransmissionHelp/html/pffirewall.html b/macosx/TransmissionHelp/html/pffirewall.html index 20f990b6e..f4a1b8c06 100644 --- a/macosx/TransmissionHelp/html/pffirewall.html +++ b/macosx/TransmissionHelp/html/pffirewall.html @@ -19,7 +19,7 @@

If this doesn't happen, you can add Transmission to the firewall manually:

    -
  1. Open System Prefs → Security → Firewall. Make sure "Set access for specific services and applications" is selected.
  2. +
  3. Open System Prefs → Security → Firewall. Make sure "Set access for specific services and applications" is selected.
  4. Click the "+" button and select Transmission from your Applications folder.
  5. Make sure the pull down menu is set to "Allow incoming connections".
diff --git a/macosx/TransmissionHelp/html/pfrouter.html b/macosx/TransmissionHelp/html/pfrouter.html index 3004e96c9..663db6bba 100644 --- a/macosx/TransmissionHelp/html/pfrouter.html +++ b/macosx/TransmissionHelp/html/pfrouter.html @@ -14,13 +14,13 @@

Port Forwarding a Router

-

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. +

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.

To forward a port in your router manually:

    -
  1. 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.
  2. +
  3. 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.
  4. 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.
  5. Go into your router configuration screen. Normally this is done via your web browser using the address192.168.0.1 etc.
    Note: Apple's AirPort uses an application called 'AirPort Utility' to configure it.
  6. @@ -52,7 +52,7 @@
    -
  1. Go to System Prefs → Network, double-click on your connection (for instance, Built-in Ethernet), and click the TCP/IP tab. +
  2. Go to System Prefs → Network, double-click on your connection (for instance, Built-in Ethernet), and click the TCP/IP tab.
  3. Write down the IP, Subnet Mask and Router addresses.
  4. 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.
  5. Then, return to the TCP/IP page in System Prefs. diff --git a/macosx/TransmissionHelp/html/portforward.html b/macosx/TransmissionHelp/html/portforward.html index 9630dfdfe..a50cf1bf1 100644 --- a/macosx/TransmissionHelp/html/portforward.html +++ b/macosx/TransmissionHelp/html/portforward.html @@ -19,7 +19,7 @@
    1. Open Transmission.
    2. -
    3. Go to Preferences → Network, and check 'Automatically map port'.
    4. +
    5. Go to Preferences → Network, and check 'Automatically map port'.
    6. If you get a green dot and 'Port is Open' then you have successfully port forwarded!
    7. If you get a red dot and the message 'Port is closed', click here.
    diff --git a/macosx/TransmissionHelp/html/remote.html b/macosx/TransmissionHelp/html/remote.html index 95412f32f..a6f0c27cb 100644 --- a/macosx/TransmissionHelp/html/remote.html +++ b/macosx/TransmissionHelp/html/remote.html @@ -24,7 +24,7 @@
  6. In the address bar, enter "http://localip:port/transmission/web/", where:
    • localip is the IP address of the computer Transmission is running on.
    • -
    • port is the port specified in Preferences → Remote.
    • +
    • port is the port specified in Preferences → Remote.
diff --git a/macosx/TransmissionHelp/html/speed.html b/macosx/TransmissionHelp/html/speed.html index 70cd02c19..354321d10 100644 --- a/macosx/TransmissionHelp/html/speed.html +++ b/macosx/TransmissionHelp/html/speed.html @@ -17,17 +17,17 @@
  1. Make sure Transmission's port is forwarded. Port forwarding makes it easier for others to connect to you, which therefore increases your speed.
    -

    If your router supports NAT-PMP, UPnP, or you have Apple AirPort, Transmission can do this automatically; just tick the checkbox in Preferences → Network. +

    If your router supports NAT-PMP, UPnP, or you have Apple AirPort, Transmission can do this automatically; just tick the checkbox in Preferences → Network.

  2. -
  3. 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. +
  4. 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.

    eg. If your upload connection is 256 Kilobits/sec, then you should cap it at 21 KB/sec ((256 / 8) * 0.66 = 21).

  5. -
  6. Queue your transfers. Transmission's queue preferences are located in Transfers → Management. +
  7. Queue your transfers. Transmission's queue preferences are located in Transfers → Management.

    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. diff --git a/macosx/TransmissionHelp/html/tracker.html b/macosx/TransmissionHelp/html/tracker.html index 889c26e0a..38baa19ec 100644 --- a/macosx/TransmissionHelp/html/tracker.html +++ b/macosx/TransmissionHelp/html/tracker.html @@ -14,7 +14,7 @@

    Can I add and remove trackers in my torrents?

    -

    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. +

    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.

    What is 'Tier 1', 'Tier 2', etc?

    @@ -24,7 +24,7 @@

    What does 'announce' mean?

    -

    When Transmission announces, 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. +

    When Transmission announces, 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.

    What does 'scrape' mean?

    diff --git a/macosx/TransmissionHelp/html/usingt.html b/macosx/TransmissionHelp/html/usingt.html index 646be6da5..3fa5c72dd 100644 --- a/macosx/TransmissionHelp/html/usingt.html +++ b/macosx/TransmissionHelp/html/usingt.html @@ -17,14 +17,14 @@
    • Torrent files contain information about the actual file you want to download, and connect you to the swarm of peers sharing it.
    • -
    • Transmission can watch a certain folder (e.g. your Safari download folder) for torrent files and then open them automatically via Preferences → Transfers → General.
    • +
    • Transmission can watch a certain folder (e.g. your Safari download folder) for torrent files and then open them automatically via Preferences → Transfers → General.
    • 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.
    • -
    • 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.
    • +
    • 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.
    • 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 → Transfers → Management.
    • + Queuing can be configured in Preferences → Transfers → Management.
    diff --git a/macosx/Utils.h b/macosx/Utils.h new file mode 100644 index 000000000..51df39f7e --- /dev/null +++ b/macosx/Utils.h @@ -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 + +// 0.1 precision +bool isSpeedEqual(CGFloat old_speed, CGFloat new_speed); +// 0.01 precision +bool isRatioEqual(CGFloat old_ratio, CGFloat new_ratio); diff --git a/macosx/Utils.mm b/macosx/Utils.mm new file mode 100644 index 000000000..b7130ca48 --- /dev/null +++ b/macosx/Utils.mm @@ -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 + +#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; +} diff --git a/macosx/VDKQueue/VDKQueue.h b/macosx/VDKQueue/VDKQueue.h index ebfc912b2..f53d4953c 100644 --- a/macosx/VDKQueue/VDKQueue.h +++ b/macosx/VDKQueue/VDKQueue.h @@ -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) diff --git a/macosx/VDKQueue/VDKQueue.mm b/macosx/VDKQueue/VDKQueue.mm index fe86122bc..c95223b44 100644 --- a/macosx/VDKQueue/VDKQueue.mm +++ b/macosx/VDKQueue/VDKQueue.mm @@ -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."); diff --git a/macosx/VersionComparator.h b/macosx/VersionComparator.h new file mode 100644 index 000000000..f31ec88ad --- /dev/null +++ b/macosx/VersionComparator.h @@ -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 + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface VersionComparator : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/macosx/VersionComparator.mm b/macosx/VersionComparator.mm new file mode 100644 index 000000000..1020481d2 --- /dev/null +++ b/macosx/VersionComparator.mm @@ -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* 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* 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 diff --git a/macosx/en.lproj/InfoPlist.strings b/macosx/en.lproj/InfoPlist.strings index 5b9ded493..e851cf6c5 100644 --- a/macosx/en.lproj/InfoPlist.strings +++ b/macosx/en.lproj/InfoPlist.strings @@ -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"; diff --git a/qt/RpcClient.cc b/qt/RpcClient.cc index 46f8d87d2..bb0be68db 100644 --- a/qt/RpcClient.cc +++ b/qt/RpcClient.cc @@ -130,8 +130,7 @@ void RpcClient::sendNetworkRequest(TrVariantPtr req, QFutureInterfacerawHeaderList()) { diff --git a/qt/Session.cc b/qt/Session.cc index 8646013da..f7992995c 100644 --- a/qt/Session.cc +++ b/qt/Session.cc @@ -516,9 +516,9 @@ void Session::torrentRenamePath(torrent_ids_t const& torrent_ids, QString const& q->run(); } -std::vector const& Session::getKeyNames(TorrentProperties props) +std::set const& Session::getKeyNames(TorrentProperties props) { - std::vector& names = names_[props]; + std::set& names = names_[props]; if (names.empty()) { @@ -609,7 +609,7 @@ std::vector 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 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; diff --git a/qt/Session.h b/qt/Session.h index 7ac6982e9..e00993db5 100644 --- a/qt/Session.h +++ b/qt/Session.h @@ -9,6 +9,7 @@ #include // int64_t #include #include +#include #include #include @@ -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 const& getKeyNames(TorrentProperties props); + std::set 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> names_; + std::map> names_; int64_t blocklist_size_ = -1; std::array port_test_pending_ = {}; diff --git a/tests/libtransmission/announce-list-test.cc b/tests/libtransmission/announce-list-test.cc index 104d5bd17..ce3f1f605 100644 --- a/tests/libtransmission/announce-list-test.cc +++ b/tests/libtransmission/announce-list-test.cc @@ -363,7 +363,8 @@ TEST_F(AnnounceListTest, save) // first, set up a scratch torrent auto constexpr* const OriginalFile = LIBTRANSMISSION_TEST_ASSETS_DIR "/Android-x86 8.1 r6 iso.torrent"; auto original_content = std::vector{}; - auto const test_file = tr_pathbuf{ ::testing::TempDir(), "transmission-announce-list-test.torrent"sv }; + auto const sandbox = libtransmission::test::Sandbox::create_sandbox(::testing::TempDir(), "transmission-test-XXXXXX"); + auto const test_file = tr_pathbuf{ sandbox, "transmission-announce-list-test.torrent"sv }; auto error = tr_error{}; EXPECT_TRUE(tr_file_read(OriginalFile, original_content, &error)); EXPECT_FALSE(error) << error; diff --git a/tests/libtransmission/announcer-test.cc b/tests/libtransmission/announcer-test.cc index 0984ff15e..e22bf4948 100644 --- a/tests/libtransmission/announcer-test.cc +++ b/tests/libtransmission/announcer-test.cc @@ -84,7 +84,7 @@ TEST_F(AnnouncerTest, parseHttpAnnounceResponsePexCompact) if (std::size(response.pex) == 1) { - EXPECT_EQ("[127.0.0.1]:64551"sv, response.pex[0].display_name()); + EXPECT_EQ("127.0.0.1:64551"sv, response.pex[0].display_name()); } } @@ -123,7 +123,7 @@ TEST_F(AnnouncerTest, parseHttpAnnounceResponsePexList) if (std::size(response.pex) == 1) { - EXPECT_EQ("[8.8.4.4]:53"sv, response.pex[0].display_name()); + EXPECT_EQ("8.8.4.4:53"sv, response.pex[0].display_name()); } } diff --git a/tests/libtransmission/completion-test.cc b/tests/libtransmission/completion-test.cc index 25f0a826c..2f53bee80 100644 --- a/tests/libtransmission/completion-test.cc +++ b/tests/libtransmission/completion-test.cc @@ -23,13 +23,13 @@ using CompletionTest = ::testing::Test; namespace { -struct TestTorrent : public tr_completion::torrent_view +struct TestTorrent { std::set dnd_pieces; - [[nodiscard]] bool piece_is_wanted(tr_piece_index_t piece) const final + [[nodiscard]] tr_completion makeCompletion(tr_block_info const& block_info) const { - return dnd_pieces.count(piece) == 0; + return { [this](tr_piece_index_t const piece) { return dnd_pieces.count(piece) == 0; }, &block_info }; } }; @@ -41,7 +41,7 @@ TEST_F(CompletionTest, MagnetLink) { auto torrent = TestTorrent{}; auto block_info = tr_block_info{}; - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); EXPECT_FALSE(completion.has_all()); EXPECT_TRUE(completion.has_none()); @@ -64,7 +64,7 @@ TEST_F(CompletionTest, setBlocks) auto torrent = TestTorrent{}; auto const block_info = tr_block_info{ TotalSize, PieceSize }; - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); EXPECT_FALSE(completion.blocks().has_all()); EXPECT_FALSE(completion.has_all()); EXPECT_EQ(0, completion.has_total()); @@ -85,7 +85,7 @@ TEST_F(CompletionTest, hasBlock) auto constexpr TotalSize = uint64_t{ BlockSize * 4096 }; auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto const block_info = tr_block_info{ TotalSize, PieceSize }; - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); EXPECT_FALSE(completion.has_block(0)); EXPECT_FALSE(completion.has_block(1)); @@ -106,7 +106,7 @@ TEST_F(CompletionTest, hasBlocks) auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto const block_info = tr_block_info{ TotalSize, PieceSize }; - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); EXPECT_FALSE(completion.has_blocks({ 0, 1 })); EXPECT_FALSE(completion.has_blocks({ 0, 2 })); @@ -122,7 +122,7 @@ TEST_F(CompletionTest, hasNone) auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto const block_info = tr_block_info{ TotalSize, PieceSize }; - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); EXPECT_TRUE(completion.has_none()); completion.add_block(0); @@ -137,7 +137,7 @@ TEST_F(CompletionTest, hasPiece) auto const block_info = tr_block_info{ TotalSize, PieceSize }; // check that the initial state does not have it - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); EXPECT_FALSE(completion.has_piece(0)); EXPECT_FALSE(completion.has_piece(1)); EXPECT_EQ(0, completion.has_valid()); @@ -174,7 +174,7 @@ TEST_F(CompletionTest, percentCompleteAndDone) auto const block_info = tr_block_info{ TotalSize, PieceSize }; // check that in blank-slate initial state, isDone() is false - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); EXPECT_DOUBLE_EQ(0.0, completion.percent_complete()); EXPECT_DOUBLE_EQ(0.0, completion.percent_done()); @@ -187,7 +187,7 @@ TEST_F(CompletionTest, percentCompleteAndDone) EXPECT_DOUBLE_EQ(0.5, completion.percent_done()); // but marking some of the pieces we have as unwanted - // should not change percentDone + // should not change percent_done for (size_t i = 0; i < 16; ++i) { torrent.dnd_pieces.insert(i); @@ -197,7 +197,7 @@ TEST_F(CompletionTest, percentCompleteAndDone) EXPECT_DOUBLE_EQ(0.5, completion.percent_done()); // but marking some of the pieces we DON'T have as unwanted - // SHOULD change percentDone + // SHOULD change percent_done for (size_t i = 32; i < 48; ++i) { torrent.dnd_pieces.insert(i); @@ -215,7 +215,7 @@ TEST_F(CompletionTest, hasTotalAndValid) auto const block_info = tr_block_info{ TotalSize, PieceSize }; // check that the initial blank-slate state has nothing - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); EXPECT_EQ(0, completion.has_total()); EXPECT_EQ(completion.has_valid(), completion.has_total()); @@ -253,7 +253,7 @@ TEST_F(CompletionTest, leftUntilDone) auto const block_info = tr_block_info{ TotalSize, PieceSize }; // check that the initial blank-slate state has nothing - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); EXPECT_EQ(block_info.total_size(), completion.left_until_done()); // check that adding the final piece adjusts by block_info.final_piece_size @@ -301,7 +301,7 @@ TEST_F(CompletionTest, sizeWhenDone) auto const block_info = tr_block_info{ TotalSize, PieceSize }; // check that adding or removing blocks or pieces does not affect sizeWhenDone - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); EXPECT_EQ(block_info.total_size(), completion.size_when_done()); completion.add_block(0); EXPECT_EQ(block_info.total_size(), completion.size_when_done()); @@ -336,7 +336,7 @@ TEST_F(CompletionTest, createPieceBitfield) auto const block_info = tr_block_info{ TotalSize, PieceSize }; // make a completion object that has a random assortment of pieces - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); auto buf = tr_rand_obj>(); ASSERT_EQ(std::size(buf), block_info.piece_count()); for (uint64_t i = 0; i < block_info.piece_count(); ++i) @@ -368,7 +368,7 @@ TEST_F(CompletionTest, countMissingBytesInPiece) auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1; auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto const block_info = tr_block_info{ TotalSize, PieceSize }; - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); EXPECT_EQ(block_info.piece_size(0), completion.count_missing_bytes_in_piece(0)); completion.add_block(0); @@ -391,7 +391,7 @@ TEST_F(CompletionTest, amountDone) auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1; auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto const block_info = tr_block_info{ TotalSize, PieceSize }; - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); // make bins s.t. each bin is a single piece auto bins = std::array{}; @@ -435,7 +435,7 @@ TEST_F(CompletionTest, countHasBytesInSpan) auto constexpr TotalSize = uint64_t{ BlockSize * 4096 } + 1; auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto const block_info = tr_block_info{ TotalSize, PieceSize }; - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); // torrent is complete auto blocks = tr_bitfield{ block_info.block_count() }; @@ -479,7 +479,7 @@ TEST_F(CompletionTest, wantNone) auto constexpr TotalSize = uint64_t{ BlockSize * 4096 }; auto constexpr PieceSize = uint64_t{ BlockSize * 64 }; auto const block_info = tr_block_info{ TotalSize, PieceSize }; - auto completion = tr_completion(&torrent, &block_info); + auto completion = torrent.makeCompletion(block_info); // we have some data completion.add_block(0); diff --git a/tests/libtransmission/dht-test.cc b/tests/libtransmission/dht-test.cc index 7b4c452c6..40123acd0 100644 --- a/tests/libtransmission/dht-test.cc +++ b/tests/libtransmission/dht-test.cc @@ -63,7 +63,7 @@ namespace libtransmission::test bool waitFor(struct event_base* event_base, std::chrono::milliseconds msec) { - return waitFor( + return waitFor( // event_base, []() { return false; }, msec); @@ -85,6 +85,7 @@ protected: // Fake data to be written to the test state file std::array const id_ = tr_rand_obj>(); + int64_t id_timestamp_ = std::time(nullptr); std::vector ipv4_nodes_ = { { *tr_address::from_string("10.10.10.1"), tr_port::from_host(128) }, { *tr_address::from_string("10.10.10.2"), tr_port::from_host(129) }, @@ -128,6 +129,7 @@ protected: auto dict = tr_variant{}; tr_variantInitDict(&dict, 3U); tr_variantDictAddRaw(&dict, TR_KEY_id, std::data(id_), std::size(id_)); + tr_variantDictAddInt(&dict, TR_KEY_id_timestamp, id_timestamp_); auto compact = std::vector{}; for (auto const& socket_address : ipv4_nodes_) { @@ -262,6 +264,7 @@ protected: std::vector pinged_; std::vector searched_; std::array id_ = {}; + int64_t id_timestamp_ = {}; tr_socket_t dht_socket_ = TR_BAD_SOCKET; tr_socket_t dht_socket6_ = TR_BAD_SOCKET; }; @@ -476,6 +479,8 @@ TEST_F(DhtTest, loadsStateFromStateFile) auto const state_file = MockStateFile{}; state_file.save(sandboxDir()); + tr_timeUpdate(time(nullptr)); + // Make the DHT auto mediator = MockMediator{ event_base_ }; mediator.config_dir_ = sandboxDir(); @@ -501,6 +506,41 @@ TEST_F(DhtTest, loadsStateFromStateFile) EXPECT_EQ(state_file.nodesString(), actual_nodes_str); } +TEST_F(DhtTest, loadsStateFromStateFileExpiredId) +{ + auto state_file = MockStateFile{}; + state_file.id_timestamp_ = 0; + state_file.save(sandboxDir()); + + tr_timeUpdate(time(nullptr)); + + // Make the DHT + auto mediator = MockMediator{ event_base_ }; + mediator.config_dir_ = sandboxDir(); + auto dht = tr_dht::create(mediator, ArbitraryPeerPort, ArbitrarySock4, ArbitrarySock6); + + // Wait for all the state nodes to be pinged + auto& pinged = mediator.mock_dht_.pinged_; + auto const n_expected_nodes = std::size(state_file.ipv4_nodes_) + std::size(state_file.ipv6_nodes_); + waitFor(event_base_, [&pinged, n_expected_nodes]() { return std::size(pinged) >= n_expected_nodes; }); + auto actual_nodes_str = std::string{}; + for (auto const& [addrport, timestamp] : pinged) + { + actual_nodes_str += addrport.display_name(); + actual_nodes_str += ','; + } + + /// Confirm that the state was loaded + + // dht_init() should have been called with the state file's id + // N.B. There is a minuscule chance for this to fail, this is + // normal because id generation is random + EXPECT_NE(state_file.id_, mediator.mock_dht_.id_); + + // dht_ping_nodedht_init() should have been called with state file's nodes + EXPECT_EQ(state_file.nodesString(), actual_nodes_str); +} + TEST_F(DhtTest, stopsBootstrappingWhenSwarmHealthIsGoodEnough) { auto const state_file = MockStateFile{}; @@ -596,7 +636,7 @@ TEST_F(DhtTest, usesBootstrapFile) // Confirm that BootstrapNodeName gets pinged first. auto const expected = getSockaddr(BootstrapNodeName, BootstrapNodePort); auto& pinged = mediator.mock_dht_.pinged_; - waitFor( + waitFor( // event_base_, [&pinged]() { return !std::empty(pinged); }, 5s); diff --git a/tests/libtransmission/lpd-test.cc b/tests/libtransmission/lpd-test.cc index 77b01201d..315dd6e05 100644 --- a/tests/libtransmission/lpd-test.cc +++ b/tests/libtransmission/lpd-test.cc @@ -112,6 +112,7 @@ TEST_F(LpdTest, HelloWorld) EXPECT_EQ(0U, std::size(mediator.found_)); } +// TODO(anyone): flaky test should be fixed instead of disabled TEST_F(LpdTest, DISABLED_CanAnnounceAndRead) { auto mediator_a = MyMediator{ *session_ }; @@ -134,6 +135,7 @@ TEST_F(LpdTest, DISABLED_CanAnnounceAndRead) EXPECT_EQ(0U, mediator_b.found_.count(info_hash_str)); } +// TODO(anyone): flaky test should be fixed instead of disabled TEST_F(LpdTest, DISABLED_canMultiAnnounce) { auto mediator_a = MyMediator{ *session_ }; @@ -170,6 +172,7 @@ TEST_F(LpdTest, DISABLED_canMultiAnnounce) } } +// TODO(anyone): flaky test should be fixed instead of disabled TEST_F(LpdTest, DISABLED_DoesNotReannounceTooSoon) { auto mediator_a = MyMediator{ *session_ }; diff --git a/tests/libtransmission/net-test.cc b/tests/libtransmission/net-test.cc index 4e208a4fe..0cd8f3f4f 100644 --- a/tests/libtransmission/net-test.cc +++ b/tests/libtransmission/net-test.cc @@ -218,7 +218,9 @@ TEST_F(NetTest, ipCompare) std::tuple{ "8.8.8.8"sv, "8.8.8.8"sv, 0 }, std::tuple{ "8.8.8.8"sv, "2001:0:0eab:dead::a0:abcd:4e"sv, -1 }, std::tuple{ "2001:1890:1112:1::20"sv, "2001:0:0eab:dead::a0:abcd:4e"sv, 1 }, - std::tuple{ "2001:1890:1112:1::20"sv, "2001:1890:1112:1::20"sv, 0 } }; + std::tuple{ "2001:1890:1112:1::20"sv, "[2001:0:0eab:dead::a0:abcd:4e]"sv, 1 }, + std::tuple{ "2001:1890:1112:1::20"sv, "2001:1890:1112:1::20"sv, 0 }, + std::tuple{ "2001:1890:1112:1::20"sv, "[2001:1890:1112:1::20]"sv, 0 } }; for (auto const& [sv1, sv2, res] : IpPairs) { diff --git a/tests/libtransmission/peer-mgr-active-requests-test.cc b/tests/libtransmission/peer-mgr-active-requests-test.cc index 88dbbdf7e..044ed9483 100644 --- a/tests/libtransmission/peer-mgr-active-requests-test.cc +++ b/tests/libtransmission/peer-mgr-active-requests-test.cc @@ -15,7 +15,7 @@ #include "gtest/gtest.h" -class tr_peer; +struct tr_peer; class PeerMgrActiveRequestsTest : public ::testing::Test { diff --git a/tests/libtransmission/subprocess-test-program.cc b/tests/libtransmission/subprocess-test-program.cc index c69ad65f0..04b477e8e 100644 --- a/tests/libtransmission/subprocess-test-program.cc +++ b/tests/libtransmission/subprocess-test-program.cc @@ -7,8 +7,9 @@ #include // tr_env_get_string() #include +#include -#include +#include #include int main(int argc, char** argv) @@ -22,8 +23,8 @@ int main(int argc, char** argv) auto const test_action = std::string{ argv[2] }; auto const tmp_result_path = result_path + ".tmp"; - FILE* out = std::fopen(tmp_result_path.c_str(), "w+"); - if (out == nullptr) + auto out = std::ofstream(tmp_result_path.c_str(), std::ios::out | std::ios::trunc | std::ios::binary); + if (!out) { return 1; } @@ -55,12 +56,12 @@ int main(int argc, char** argv) } else { - (void)std::fclose(out); + out.close(); (void)std::remove(tmp_result_path.c_str()); return 1; } - (void)std::fclose(out); + out.close(); tr_sys_path_rename(tmp_result_path.c_str(), result_path.c_str()); return 0; } diff --git a/tests/libtransmission/test-fixtures.h b/tests/libtransmission/test-fixtures.h index a9c0fecdf..d16143ab4 100644 --- a/tests/libtransmission/test-fixtures.h +++ b/tests/libtransmission/test-fixtures.h @@ -140,6 +140,14 @@ public: return sandbox_dir_; } + static std::string create_sandbox(std::string const& parent_dir, std::string const& tmpl) + { + auto path = fmt::format(FMT_STRING("{:s}/{:s}"sv), tr_sys_path_resolve(parent_dir), tmpl); + tr_sys_dir_create_temp(std::data(path)); + tr_sys_path_native_separators(std::data(path)); + return path; + } + protected: static std::string get_default_parent_dir() { @@ -152,14 +160,6 @@ protected: return tr_sys_dir_get_current(&error); } - static std::string create_sandbox(std::string const& parent_dir, std::string const& tmpl) - { - auto path = fmt::format(FMT_STRING("{:s}/{:s}"sv), tr_sys_path_resolve(parent_dir), tmpl); - tr_sys_dir_create_temp(std::data(path)); - tr_sys_path_native_separators(std::data(path)); - return path; - } - static void rimraf(std::string const& path, bool verbose = false) { auto remove = [verbose](char const* filename) diff --git a/tests/libtransmission/timer-test.cc b/tests/libtransmission/timer-test.cc index 914a5e813..7a81ec93e 100644 --- a/tests/libtransmission/timer-test.cc +++ b/tests/libtransmission/timer-test.cc @@ -42,7 +42,7 @@ protected: void sleepMsec(std::chrono::milliseconds msec) { - EXPECT_FALSE(waitFor( + EXPECT_FALSE(waitFor( // evbase_.get(), []() { return false; }, msec)); @@ -177,7 +177,8 @@ TEST_F(TimerTest, repeatingHonorsInterval) EXPECT_EQ(DesiredLoops, n_calls); } -TEST_F(TimerTest, restartWithDifferentInterval) +// TODO: flaky test should be fixed instead of disabled +TEST_F(TimerTest, DISABLED_restartWithDifferentInterval) { auto timer_maker = EvTimerMaker{ evbase_.get() }; auto timer = timer_maker.create(); @@ -206,6 +207,7 @@ TEST_F(TimerTest, restartWithDifferentInterval) test(200ms); } +// TODO: flaky test should be fixed instead of disabled TEST_F(TimerTest, DISABLED_restartWithSameInterval) { auto timer_maker = EvTimerMaker{ evbase_.get() }; @@ -235,6 +237,7 @@ TEST_F(TimerTest, DISABLED_restartWithSameInterval) test(timer->interval()); } +// TODO: flaky test should be fixed instead of disabled TEST_F(TimerTest, DISABLED_repeatingThenSingleShot) { auto timer_maker = EvTimerMaker{ evbase_.get() }; @@ -277,6 +280,7 @@ TEST_F(TimerTest, DISABLED_repeatingThenSingleShot) EXPECT_EQ(baseline + 1, n_calls); } +// TODO: flaky test should be fixed instead of disabled TEST_F(TimerTest, DISABLED_singleShotStop) { auto timer_maker = EvTimerMaker{ evbase_.get() }; diff --git a/tests/libtransmission/torrent-metainfo-test.cc b/tests/libtransmission/torrent-metainfo-test.cc index 641c51700..cc3547003 100644 --- a/tests/libtransmission/torrent-metainfo-test.cc +++ b/tests/libtransmission/torrent-metainfo-test.cc @@ -134,8 +134,9 @@ TEST_F(TorrentMetainfoTest, AndroidTorrent) TEST_F(TorrentMetainfoTest, ctorSaveContents) { + auto const sandbox = libtransmission::test::Sandbox::create_sandbox(::testing::TempDir(), "transmission-test-XXXXXX"); auto const src_filename = tr_pathbuf{ LIBTRANSMISSION_TEST_ASSETS_DIR, "/Android-x86 8.1 r6 iso.torrent"sv }; - auto const tgt_filename = tr_pathbuf{ ::testing::TempDir(), "save-contents-test.torrent" }; + auto const tgt_filename = tr_pathbuf{ sandbox, "save-contents-test.torrent" }; // try saving without passing any metainfo. auto* ctor = tr_ctorNew(session_); diff --git a/tests/libtransmission/utils-test.cc b/tests/libtransmission/utils-test.cc index 04fe1f8ab..2e4c7b020 100644 --- a/tests/libtransmission/utils-test.cc +++ b/tests/libtransmission/utils-test.cc @@ -262,7 +262,8 @@ TEST_F(UtilsTest, saveFile) auto filename = tr_pathbuf{}; // save a file to GoogleTest's temp dir - filename.assign(::testing::TempDir(), "filename.txt"sv); + auto const sandbox = libtransmission::test::Sandbox::create_sandbox(::testing::TempDir(), "transmission-test-XXXXXX"); + filename.assign(sandbox, "filename.txt"sv); auto contents = "these are the contents"sv; auto error = tr_error{}; EXPECT_TRUE(tr_file_save(filename.sv(), contents, &error)); diff --git a/tests/libtransmission/watchdir-test.cc b/tests/libtransmission/watchdir-test.cc index c969ba63d..139fee9c8 100644 --- a/tests/libtransmission/watchdir-test.cc +++ b/tests/libtransmission/watchdir-test.cc @@ -226,6 +226,7 @@ TEST_P(WatchDirTest, watch) EXPECT_TRUE(std::empty(names)); } +// TODO(ckerr): flaky test should be fixed instead of disabled TEST_P(WatchDirTest, DISABLED_retry) { auto const path = sandboxDir(); diff --git a/tests/libtransmission/web-utils-test.cc b/tests/libtransmission/web-utils-test.cc index f84848cf9..e163207b8 100644 --- a/tests/libtransmission/web-utils-test.cc +++ b/tests/libtransmission/web-utils-test.cc @@ -126,8 +126,8 @@ TEST_F(WebUtilsTest, urlParse) url = "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]:8080/announce"sv; parsed = tr_urlParse(url); EXPECT_EQ("http"sv, parsed->scheme); - EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->sitename); - EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->host); + EXPECT_EQ("[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]"sv, parsed->sitename); + EXPECT_EQ("[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]"sv, parsed->host); EXPECT_EQ("/announce"sv, parsed->path); EXPECT_EQ(8080, parsed->port); @@ -135,8 +135,8 @@ TEST_F(WebUtilsTest, urlParse) url = "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]/announce"sv; parsed = tr_urlParse(url); EXPECT_EQ("http"sv, parsed->scheme); - EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->sitename); - EXPECT_EQ("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"sv, parsed->host); + EXPECT_EQ("[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]"sv, parsed->sitename); + EXPECT_EQ("[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]"sv, parsed->host); EXPECT_EQ("/announce"sv, parsed->path); EXPECT_EQ(80, parsed->port); diff --git a/third-party/fast_float b/third-party/fast_float index 8378916ed..1fc3ac393 160000 --- a/third-party/fast_float +++ b/third-party/fast_float @@ -1 +1 @@ -Subproject commit 8378916ed814bebc4bcbf519c66f7c38c8374594 +Subproject commit 1fc3ac3932220b5effaca7203bb1bb771528d256 diff --git a/third-party/googletest b/third-party/googletest index af29db7ec..f8d7d77c0 160000 --- a/third-party/googletest +++ b/third-party/googletest @@ -1 +1 @@ -Subproject commit af29db7ec28d6df1c7f0f745186884091e602e07 +Subproject commit f8d7d77c06936315286eb55f8de22cd23c188571 diff --git a/third-party/libdeflate b/third-party/libdeflate index 495fee110..275aa5141 160000 --- a/third-party/libdeflate +++ b/third-party/libdeflate @@ -1 +1 @@ -Subproject commit 495fee110ebb48a5eb63b75fd67e42b2955871e2 +Subproject commit 275aa5141db6eda3587214e0f1d3a134768f557d diff --git a/third-party/miniupnp b/third-party/miniupnp new file mode 160000 index 000000000..7f189988a --- /dev/null +++ b/third-party/miniupnp @@ -0,0 +1 @@ +Subproject commit 7f189988a0decca0ab7da89000051ab91751f70d diff --git a/third-party/miniupnpc b/third-party/miniupnpc deleted file mode 160000 index e37cde82e..000000000 --- a/third-party/miniupnpc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e37cde82e8673a5e71bae43086828704fd5eeac1 diff --git a/third-party/small b/third-party/small index 589f1d166..c63eef66a 160000 --- a/third-party/small +++ b/third-party/small @@ -1 +1 @@ -Subproject commit 589f1d166919b6490cade75465cf22f9b2ea65ee +Subproject commit c63eef66aebf6e09cee151e6d67f91b432577447 diff --git a/third-party/utfcpp b/third-party/utfcpp index b85efd66a..6be08bbea 160000 --- a/third-party/utfcpp +++ b/third-party/utfcpp @@ -1 +1 @@ -Subproject commit b85efd66a76caccbe0c186b00cab34df1e4281fa +Subproject commit 6be08bbea14ffa0a5c594257fb6285a054395cd7 diff --git a/update-version-h.sh b/update-version-h.sh index 477e45509..af6ff4451 100755 --- a/update-version-h.sh +++ b/update-version-h.sh @@ -21,6 +21,9 @@ if grep -q 'set[(]TR_VERSION_DEV TRUE)' CMakeLists.txt; then else is_dev=false fi +if [ "$is_dev" != true ] && [ -z "${beta_number}" ]; then + release_number=1 +fi # derived from above: semver version string. https://semver.org/ # '4.0.0-beta.1' @@ -90,7 +93,7 @@ cat > libtransmission/version.h.new << EOF #define SHORT_VERSION_STRING "${user_agent_prefix}" #define LONG_VERSION_STRING "${user_agent_prefix} (${vcs_revision})" #define VERSION_STRING_INFOPLIST ${user_agent_prefix} -#define BUILD_STRING_INFOPLIST 14714.${major_version}.${minor_version}.${patch_version} +#define BUILD_STRING_INFOPLIST $((major_version + 14714)).${minor_version}.$((patch_version * 100 + release_number * 99 + beta_number)) #define MAJOR_VERSION ${major_version} #define MINOR_VERSION ${minor_version} #define PATCH_VERSION ${patch_version} diff --git a/utils/transmission-remote.1 b/utils/transmission-remote.1 index 54e584ee5..af4564235 100644 --- a/utils/transmission-remote.1 +++ b/utils/transmission-remote.1 @@ -150,7 +150,7 @@ Get a file list for the current torrent(s) .It Fl g Fl -get Ar all | file-index | files Mark file(s) for download. .Ar all -marks all all of the torrent's files for downloading, +marks all of the torrent's files for downloading, .Ar file-index adds a single file to the download list, and .Ar files diff --git a/web/assets/img/pause-circle-active.svg b/web/assets/img/pause-circle-active.svg index 2f2cb114a..7493ad671 100644 --- a/web/assets/img/pause-circle-active.svg +++ b/web/assets/img/pause-circle-active.svg @@ -1 +1,39 @@ - + + + + + + + + + + diff --git a/web/assets/img/pause-circle-idle.svg b/web/assets/img/pause-circle-idle.svg index 1c2f127ba..00ed842e3 100644 --- a/web/assets/img/pause-circle-idle.svg +++ b/web/assets/img/pause-circle-idle.svg @@ -1 +1,11 @@ - + + + + + <?xmlversion="1.0"encoding="UTF-8"?><!--Generator:AppleNativeCoreSVG232.5--><!DOCTYPEsvgPUBLIC"-//W3C//DTDSVG1.1//EN""http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svgversion="1.1"xmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"width="60"height="59.7949"><g><rectheight="59.7949"opacity="0"width="60"x="0"y="0"/><pathd="M29.882859.7656C46.230559.765659.765646.201259.765629.8828C59.765613.535246.2012029.85350C13.53520013.5352029.8828C046.201213.564559.765629.882859.7656Z"fill="#808080"/><pathd="M21.357441.6602C19.863341.660219.189540.839819.189539.668L19.189520.0391C19.189518.896519.863318.076221.357418.0762L24.316418.0762C25.839818.076226.513718.896526.513720.0391L26.513739.668C26.513740.839825.839841.660224.316441.6602ZM35.507841.6602C33.984441.660233.310540.839833.310539.668L33.310520.0391C33.310518.896533.984418.076235.507818.0762L38.466818.0762C39.931618.076240.605518.896540.605520.0391L40.605539.668C40.605540.839839.931641.660238.466841.6602Z"fill="#ffffff"/></g></svg> + + + + + + diff --git a/web/assets/img/play-circle-active.svg b/web/assets/img/play-circle-active.svg index b8e97e4f8..998a49a5e 100644 --- a/web/assets/img/play-circle-active.svg +++ b/web/assets/img/play-circle-active.svg @@ -1 +1,39 @@ - + + + + + + + + + + diff --git a/web/assets/img/play-circle-idle.svg b/web/assets/img/play-circle-idle.svg index 816b68731..83bcf27c2 100644 --- a/web/assets/img/play-circle-idle.svg +++ b/web/assets/img/play-circle-idle.svg @@ -1 +1,11 @@ - + + + + + <?xmlversion="1.0"encoding="UTF-8"?><!--Generator:AppleNativeCoreSVG232.5--><!DOCTYPEsvgPUBLIC"-//W3C//DTDSVG1.1//EN""http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svgversion="1.1"xmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"width="60"height="59.7949"><g><rectheight="59.7949"opacity="0"width="60"x="0"y="0"/><pathd="M29.882859.7656C46.230559.765659.765646.201259.765629.8828C59.765613.535246.2012029.85350C13.53520013.5352029.8828C046.201213.564559.765629.882859.7656Z"fill="#808080"/><pathd="M24.316441.6895C22.910242.539121.298841.865221.298840.4004L21.298819.3945C21.298817.95923.027317.343824.316418.1055L41.513728.3008C42.744129.033242.773430.79141.513731.5527Z"fill="#ffffff"/></g></svg> + + + + + + diff --git a/web/src/remote.js b/web/src/remote.js index fb7aaba79..1c7b5b7db 100644 --- a/web/src/remote.js +++ b/web/src/remote.js @@ -57,7 +57,10 @@ export class Remote { callback.call(context, payload, response_argument); } - this._connection_alert = null; + if (this._connection_alert) { + this._connection_alert.close(); + this._connection_alert = null; + } }) .catch((error) => { if (error.message === Remote._SessionHeader) { @@ -74,8 +77,6 @@ export class Remote { message: 'Could not connect to the server. You may need to reload the page to reconnect.', }); - }) - .finally(() => { this._controller.setCurrentPopup(this._connection_alert); }); }