Merge branch 'main' into patch-1

This commit is contained in:
Runbing 2024-05-01 19:32:50 +08:00 committed by GitHub
commit 5f4f19a54c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
661 changed files with 85570 additions and 57511 deletions

View File

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

View File

@ -7,6 +7,9 @@ cmake-build-*/*
libtransmission/version.h
web/node_modules/*
# android
android/*
# third-party maintained projects
third-party/*

View File

@ -12,18 +12,19 @@ jobs:
what-to-make:
runs-on: ubuntu-22.04
outputs:
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
@ -36,7 +37,7 @@ jobs:
fi
- name: Get Source
id: get-source
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
path: src
@ -53,18 +54,20 @@ jobs:
git diff --exit-code "$MERGE_BASE" -- "$@"
echo "$name-changed=$?" >> "$GITHUB_OUTPUT"
}
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 Transmission.xcodeproj
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:
@ -80,15 +83,15 @@ jobs:
echo '${{ toJSON(runner) }}'
cat /etc/os-release
- name: Get Source
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
- name: Get Dependencies
run: |
set -ex
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main"
sudo apt-get install -y clang-format-15 npm
sudo add-apt-repository "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main"
sudo apt-get install -y clang-format-17 npm
- name: Check for style diffs
id: check-for-diffs
working-directory: .
@ -100,7 +103,7 @@ jobs:
cat style.diff
set -e
- name: Upload Diffs
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
if: ${{ steps.check-for-diffs.outputs.differs == '1' }}
with:
name: code-style.diff
@ -113,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' }}
@ -144,8 +147,13 @@ 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@v3
uses: actions/checkout@v4
with:
submodules: recursive
path: src
@ -175,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 ]
@ -206,7 +256,7 @@ jobs:
ninja-build \
npm
- name: Get Source
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
path: src
@ -227,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:
@ -247,7 +297,7 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-qt == 'true' }}
run: brew install qt
- name: Get Source
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
path: src
submodules: recursive
@ -259,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 \
@ -280,11 +330,47 @@ jobs:
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@v3
- uses: actions/upload-artifact@v4
with:
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
@ -324,7 +410,7 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-qt == 'true' }}
run: apk add --upgrade qt5-qtbase-dev qt5-qtsvg-dev qt5-qttools-dev
- name: Get Source
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
path: src
submodules: recursive
@ -355,7 +441,7 @@ jobs:
run: cmake -E chdir obj ctest -j $(nproc) --build-config RelWithDebInfo --output-on-failure
- name: Install
run: cmake --build obj --config RelWithDebInfo --target install/strip
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: binaries-${{ github.job }}
path: pfx/**/*
@ -385,11 +471,11 @@ 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
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
path: src
submodules: recursive
@ -404,7 +490,7 @@ jobs:
exit 1
}
- name: Get Cache
uses: actions/cache@v3
uses: actions/cache@v4
id: cache
with:
path: ${{ env.DEPS_PREFIX }}
@ -427,13 +513,13 @@ jobs:
-G Ninja `
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
-DCMAKE_INSTALL_PREFIX=pfx `
-DCMAKE_PREFIX_PATH="${Env:DepsPrefix}" `
-DCMAKE_PREFIX_PATH="${Env:DEPS_PREFIX}" `
-DENABLE_CLI=${{ (needs.what-to-make.outputs.make-cli == 'true') && 'ON' || 'OFF' }} `
-DENABLE_DAEMON=${{ (needs.what-to-make.outputs.make-daemon == 'true' || needs.what-to-make.outputs.make-dist == 'true') && 'ON' || 'OFF' }} `
-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 `
@ -452,11 +538,11 @@ jobs:
run: |
Import-VisualStudioVars -VisualStudioVersion 2022 -Architecture ${{ matrix.arch }}
cmake --build obj --config RelWithDebInfo --target pack-msi
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: binaries-${{ github.job }}-${{ matrix.arch }}
path: pfx/**/*
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: binaries-${{ github.job }}-${{ matrix.arch }}-msi
path: obj/dist/msi/*.msi
@ -482,7 +568,7 @@ jobs:
libssl-dev \
ninja-build
- name: Get Source
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
path: src
submodules: recursive
@ -494,12 +580,73 @@ jobs:
-G Ninja
- name: Create source tarball
run: cmake --build obj --target package_source
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
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
@ -519,7 +666,7 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-qt == 'true' }}
run: brew install qt
- name: Get Source
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: source-tarball
- name: Extract Source
@ -561,7 +708,7 @@ jobs:
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@v3
- uses: actions/upload-artifact@v4
with:
name: binaries-${{ github.job }}
path: pfx/**/*
@ -608,7 +755,7 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-qt == 'true' }}
run: apt-get install -y --no-install-recommends qtbase5-dev libqt5svg5-dev qttools5-dev
- name: Get Source
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: source-tarball
- name: Extract Source
@ -638,7 +785,7 @@ jobs:
run: cmake -E chdir obj ctest -j $(nproc) --build-config RelWithDebInfo --output-on-failure
- name: Install
run: cmake --build obj --config RelWithDebInfo --target install/strip
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: binaries-${{ github.job }}
path: pfx/**/*
@ -684,7 +831,7 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-qt == 'true' }}
run: dnf install -y qt6-qtbase-devel qt6-qtsvg-devel qt6-qttools-devel
- name: Get Source
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: source-tarball
- name: Extract Source
@ -715,7 +862,7 @@ jobs:
run: cmake -E chdir obj ctest -j $(nproc) --build-config RelWithDebInfo --output-on-failure
- name: Install
run: cmake --build obj --config RelWithDebInfo --target install/strip
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: binaries-${{ github.job }}
path: pfx/**/*
@ -757,7 +904,7 @@ jobs:
if: ${{ needs.what-to-make.outputs.make-qt == 'true' }}
run: sudo apt-get install -y --no-install-recommends qtbase5-dev libqt5svg5-dev qttools5-dev
- name: Get Source
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: source-tarball
- name: Extract Source
@ -789,7 +936,57 @@ jobs:
run: cmake -E chdir obj ctest -j $(nproc) --build-config RelWithDebInfo --output-on-failure
- name: Install
run: cmake --build obj --config RelWithDebInfo --target install/strip
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: binaries-${{ github.job }}
path: pfx/**/*
android:
needs: [ what-to-make ]
if: ${{ needs.what-to-make.outputs.make-android == 'true' }}
runs-on: ubuntu-22.04
env:
VCPKG_DEFAULT_TRIPLET: arm64-android
steps:
- name: Get Dependencies
run: |
set -ex
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
ninja-build
- name: Get Source
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/gradle-build-action@v3
with:
gradle-version: 7.6
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Install NDK
run: sdkmanager "ndk;26.1.10909125"
- name: Setup vcpkg
uses: lukka/run-vcpkg@v11
with:
vcpkgGitCommitId: 53bef8994c541b6561884a8395ea35715ece75db # 2024.01.12
- name: Install vcpkg packages
run: |
vcpkg install openssl curl
- name: Build Transmission
working-directory: ./android
run: |
gradle build

View File

@ -30,7 +30,7 @@ jobs:
steps:
- name: Checkout repository and submodules
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
@ -77,14 +77,14 @@ jobs:
- name: Initialize CodeQL
if: ${{ matrix.language == 'javascript' }}
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/codeql-config-js.yml
- name: Initialize CodeQL
if: ${{ matrix.language == 'cpp' }}
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
@ -94,6 +94,6 @@ jobs:
ninja -C _build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View File

@ -21,7 +21,7 @@ jobs:
update-generated-files: ${{ steps.check-diffs.outputs.web-changed == '1' && steps.check-main-push.outputs.is-main-push == '1'}}
steps:
- name: Get source
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 2 # >1 needed for merge base
- name: Check push-to-main
@ -58,7 +58,7 @@ jobs:
if: ${{ needs.decide-what-jobs-to-run.outputs.test-style == 'true' }}
steps:
- name: Get source
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Get dependencies
run: |
set -e # abort if any command fails
@ -79,7 +79,7 @@ jobs:
echo ===
set -e # undo set +e
- name: Upload diffs
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: ${{ steps.check-for-diffs.outputs.differs == '1' }}
with:
name: code-style.diff
@ -98,7 +98,7 @@ jobs:
if: ${{ needs.decide-what-jobs-to-run.outputs.test-generated-files == 'true' }}
steps:
- name: Get source
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 2 # >1 needed for merge base
- name: Check for changes to generated files
@ -111,9 +111,12 @@ jobs:
echo
echo Please undo your changes to these files:
git diff --exit-code --name-only --merge-base "origin/$GITHUB_BASE_REF" -- \
web/public_html/transmission-app.css \
web/public_html/transmission-app.css.LEGAL.txt \
web/public_html/transmission-app.css.map \
web/public_html/transmission-app.js \
web/public_html/transmission-app.js.map \
web/public_html/transmission-app.js.LICENSE.txt
web/public_html/transmission-app.js.LEGAL.txt \
web/public_html/transmission-app.js.map
update-generated-files:
runs-on: ubuntu-latest
@ -128,7 +131,7 @@ jobs:
set -e # abort if any command fails
sudo apt-get install -y npm
- name: Get source
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 2 # >1 needed for merge base
- name: Generate webapp files
@ -140,7 +143,7 @@ jobs:
git diff --name-only
git add --update web
- name: Create pull request
uses: peter-evans/create-pull-request@v4
uses: peter-evans/create-pull-request@v6
with:
branch: 'chore/update-webapp-files'
commit-message: 'chore: update generated transmission-web files'

6
.gitignore vendored
View File

@ -23,9 +23,15 @@ node_modules/
/third-party/miniupnp/miniupnpcstrings.h
/third-party/suffixes_dafsa.h
/web/public_html/transmission-app.js.map
/android/.cxx
/android/.gradle
/android/build
# clangd compile commands
compile_commands.json
# CLion IDE build directory
/cmake-build-*/
# CMake user presets
CMakeUserPresets.json

37
.gitmodules vendored
View File

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

View File

@ -12,72 +12,84 @@ file_filter = macosx/<lang>.lproj/Localizable.strings
source_file = macosx/en.lproj/Localizable.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:mac-AddMagnetWindow]
file_filter = macosx/<lang>.lproj/AddMagnetWindow.strings
source_file = macosx/en.lproj/AddMagnetWindow.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:mac-AddWindow]
file_filter = macosx/<lang>.lproj/AddWindow.strings
source_file = macosx/en.lproj/AddWindow.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:mac-Creator]
file_filter = macosx/<lang>.lproj/Creator.strings
source_file = macosx/en.lproj/Creator.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:mac-GlobalOptionsPopover]
file_filter = macosx/<lang>.lproj/GlobalOptionsPopover.strings
source_file = macosx/en.lproj/GlobalOptionsPopover.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:mac-GroupRules]
file_filter = macosx/<lang>.lproj/GroupRules.strings
source_file = macosx/en.lproj/GroupRules.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:mac-InfoActivityView]
file_filter = macosx/<lang>.lproj/InfoActivityView.strings
source_file = macosx/en.lproj/InfoActivityView.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:mac-InfoGeneralView]
file_filter = macosx/<lang>.lproj/InfoGeneralView.strings
source_file = macosx/en.lproj/InfoGeneralView.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:mac-InfoOptionsView]
file_filter = macosx/<lang>.lproj/InfoOptionsView.strings
source_file = macosx/en.lproj/InfoOptionsView.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:mac-MainMenu]
file_filter = macosx/<lang>.lproj/MainMenu.strings
source_file = macosx/en.lproj/MainMenu.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:mac-PrefsWindow]
file_filter = macosx/<lang>.lproj/PrefsWindow.strings
source_file = macosx/en.lproj/PrefsWindow.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:mac-ql]
file_filter = macosx/QuickLookPlugin/<lang>.lproj/Localizable.strings
source_file = macosx/QuickLookPlugin/en.lproj/Localizable.strings
source_lang = en
type = STRINGS
lang_map = pt_BR: pt-BR, pt_PT: pt-PT, zh_CN: zh-CN, zh_TW: zh-TW
[o:transmissionbt:p:transmissionbt:r:qt]
file_filter = qt/translations/transmission_<lang>.ts

View File

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

View File

@ -18,6 +18,10 @@ if(NOT CMAKE_OSX_DEPLOYMENT_TARGET)
FORCE)
endif()
if(VCPKG_TARGET_ANDROID)
include(cmake/VcpkgAndroid.cmake)
endif()
project(transmission)
set(TR_THIRD_PARTY_DIR_NAME third-party)
@ -105,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)
@ -493,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
@ -504,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
@ -626,7 +650,6 @@ else()
-Wextra
-Wcast-align
-Wduplicated-cond
-Wexit-time-destructors
-Wextra-semi
-Wextra-semi-stmt
-Wextra-tokens

View File

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

View File

@ -1,4 +1,4 @@
Copyright 2005-2023. All code is copyrighted by the respective authors.
Copyright 2005-2024. All code is copyrighted by the respective authors.
Transmission can be redistributed and/or modified under the terms of
the GNU GPLv2 (http://www.gnu.org/licenses/license-list.html#GPLv2),

View File

@ -400,8 +400,10 @@
C3D9062F27B7F7E200EF2386 /* libpsl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C3D9062127B7E3C900EF2386 /* libpsl.a */; };
C809AEE7291ECFD000BFDBE1 /* NSDataAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = C809AEE6291ECFD000BFDBE1 /* NSDataAdditions.mm */; };
C82B30312953337B0001BD6E /* NSDataAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = C809AEE6291ECFD000BFDBE1 /* NSDataAdditions.mm */; };
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 */; };
@ -433,26 +435,27 @@
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 */; };
D5C306568A7346FFFB8EFAD0 /* session-settings.cc in Sources */ = {isa = PBXBuildFile; fileRef = D5C306568A7346FFFB8EFAD1 /* session-settings.cc */; };
D5C306568A7346FFFB8EFAD2 /* session-settings.h in Headers */ = {isa = PBXBuildFile; fileRef = D5C306568A7346FFFB8EFAD3 /* session-settings.h */; };
D9057D68C13B75636539B680 /* variant-converters.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9057D68C13B75636539B681 /* variant-converters.cc */; };
E138A9780C04D88F00C5426C /* ProgressGradients.mm in Sources */ = {isa = PBXBuildFile; fileRef = E138A9760C04D88F00C5426C /* ProgressGradients.mm */; };
E23B55A5FC3B557F7746D510 /* interned-string.h in Headers */ = {isa = PBXBuildFile; fileRef = E23B55A5FC3B557F7746D511 /* interned-string.h */; settings = {ATTRIBUTES = (Project, ); }; };
E71A5565279C2DD600EBFA1E /* tr-assert.mm in Sources */ = {isa = PBXBuildFile; fileRef = E71A5564279C2DD600EBFA1E /* tr-assert.mm */; };
E975121263DD973CAF4AEBA0 /* timer.h in Headers */ = {isa = PBXBuildFile; fileRef = E975121263DD973CAF4AEBA1 /* timer.h */; };
E975121263DD973CAF4AEBA2 /* timer-ev.h in Headers */ = {isa = PBXBuildFile; fileRef = E975121263DD973CAF4AEBA3 /* timer-ev.h */; };
E975121263DD973CAF4AEBA4 /* timer-ev.cc in Sources */ = {isa = PBXBuildFile; fileRef = E975121263DD973CAF4AEBA5 /* timer-ev.cc */; };
ED20B87C28589274005FA6BE /* common_defs.h in Headers */ = {isa = PBXBuildFile; fileRef = ED20B87B28589274005FA6BE /* common_defs.h */; };
ED20B87F285892C5005FA6BE /* crc32_multipliers.h in Headers */ = {isa = PBXBuildFile; fileRef = ED20B87D285892C5005FA6BE /* crc32_multipliers.h */; };
ED20B880285892C5005FA6BE /* crc32_tables.h in Headers */ = {isa = PBXBuildFile; fileRef = ED20B87E285892C5005FA6BE /* crc32_tables.h */; };
ED67FB422B70FCE400D8A037 /* settings.cc in Sources */ = {isa = PBXBuildFile; fileRef = ED67FB402B70FCE400D8A037 /* settings.cc */; };
ED67FB432B70FCE400D8A037 /* settings.h in Headers */ = {isa = PBXBuildFile; fileRef = ED67FB412B70FCE400D8A037 /* settings.h */; };
ED86936F2ADAE34D00342B1A /* DefaultAppHelper.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED86936E2ADAE34D00342B1A /* DefaultAppHelper.mm */; };
ED8A163F2735A8AA000D61F9 /* peer-mgr-active-requests.h in Headers */ = {isa = PBXBuildFile; fileRef = ED8A163B2735A8AA000D61F9 /* peer-mgr-active-requests.h */; };
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 */; };
@ -654,7 +657,7 @@
455C093B2877672C0003A078 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
455C093C2877672E0003A078 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
455C093D287767300003A078 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
455C093E287767320003A078 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = pt_PT.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
455C093E287767320003A078 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/PrefsWindow.strings"; sourceTree = "<group>"; };
455C093F287767350003A078 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
455C0940287767380003A078 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
455C09412877673A0003A078 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
@ -665,7 +668,7 @@
457DC1E32873930500ED04C4 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/MainMenu.strings; sourceTree = "<group>"; };
457DC1E42873930900ED04C4 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/MainMenu.strings; sourceTree = "<group>"; };
457DC1E52873930D00ED04C4 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/MainMenu.strings; sourceTree = "<group>"; };
457DC1E62873931100ED04C4 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = pt_PT.lproj/MainMenu.strings; sourceTree = "<group>"; };
457DC1E62873931100ED04C4 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/MainMenu.strings"; sourceTree = "<group>"; };
457DC1E72873931500ED04C4 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/MainMenu.strings; sourceTree = "<group>"; };
457DC1E82873931900ED04C4 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/MainMenu.strings; sourceTree = "<group>"; };
457DC1E92873931E00ED04C4 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/MainMenu.strings; sourceTree = "<group>"; };
@ -678,14 +681,14 @@
45C688C92875AC8100A2EB25 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
45C688CA2875AC8300A2EB25 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
45C688CB2875AC8700A2EB25 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
45C688CC2875AC8900A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = pt_PT.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
45C688CC2875AC8900A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/AddMagnetWindow.strings"; sourceTree = "<group>"; };
45C688CD2875AC8D00A2EB25 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
45C688CE2875AC8F00A2EB25 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
45C688CF2875AC9200A2EB25 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
45C688D02875ADF000A2EB25 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/GroupRules.strings; sourceTree = "<group>"; };
45C688D12875ADF500A2EB25 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/GroupRules.strings; sourceTree = "<group>"; };
45C688D22875ADF800A2EB25 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/GroupRules.strings; sourceTree = "<group>"; };
45C688D32875ADFB00A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = pt_PT.lproj/GroupRules.strings; sourceTree = "<group>"; };
45C688D32875ADFB00A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/GroupRules.strings"; sourceTree = "<group>"; };
45C688D42875ADFE00A2EB25 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/GroupRules.strings; sourceTree = "<group>"; };
45C688D52875AE0100A2EB25 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/GroupRules.strings; sourceTree = "<group>"; };
45C688D62875AE0500A2EB25 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/GroupRules.strings; sourceTree = "<group>"; };
@ -696,7 +699,7 @@
45C688DB2875B06200A2EB25 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
45C688DC2875B06400A2EB25 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
45C688DD2875B06700A2EB25 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
45C688DE2875B06900A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = pt_PT.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
45C688DE2875B06900A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/InfoGeneralView.strings"; sourceTree = "<group>"; };
45C688DF2875B06D00A2EB25 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
45C688E02875B06F00A2EB25 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
45C688E12875B07300A2EB25 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
@ -705,7 +708,7 @@
45C688E42875B17F00A2EB25 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
45C688E52875B18200A2EB25 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
45C688E62875B18700A2EB25 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
45C688E72875B18A00A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = pt_PT.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
45C688E72875B18A00A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/InfoActivityView.strings"; sourceTree = "<group>"; };
45C688E82875B19000A2EB25 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
45C688E92875B19200A2EB25 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
45C688EA2875B19500A2EB25 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
@ -714,7 +717,7 @@
45C688ED2875B2C000A2EB25 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
45C688EE2875B2C200A2EB25 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
45C688EF2875B2C400A2EB25 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
45C688F02875B2C700A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = pt_PT.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
45C688F02875B2C700A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/InfoOptionsView.strings"; sourceTree = "<group>"; };
45C688F12875B2CA00A2EB25 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
45C688F22875B2CD00A2EB25 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
45C688F32875B2CF00A2EB25 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
@ -723,7 +726,7 @@
45C688F62875B3F600A2EB25 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
45C688F72875B3F800A2EB25 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
45C688F82875B3FB00A2EB25 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
45C688F92875B3FF00A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = pt_PT.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
45C688F92875B3FF00A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/GlobalOptionsPopover.strings"; sourceTree = "<group>"; };
45C688FA2875B40300A2EB25 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
45C688FB2875B40600A2EB25 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
45C688FC2875B40900A2EB25 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
@ -732,7 +735,7 @@
45C688FF28762FEC00A2EB25 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/AddWindow.strings; sourceTree = "<group>"; };
45C6890028762FEF00A2EB25 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/AddWindow.strings; sourceTree = "<group>"; };
45C6890128762FF200A2EB25 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/AddWindow.strings; sourceTree = "<group>"; };
45C6890228762FF800A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = pt_PT.lproj/AddWindow.strings; sourceTree = "<group>"; };
45C6890228762FF800A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/AddWindow.strings"; sourceTree = "<group>"; };
45C6890328762FFD00A2EB25 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/AddWindow.strings; sourceTree = "<group>"; };
45C6890428762FFF00A2EB25 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/AddWindow.strings; sourceTree = "<group>"; };
45C689052876300200A2EB25 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/AddWindow.strings; sourceTree = "<group>"; };
@ -741,7 +744,7 @@
45C6890828763C3200A2EB25 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Creator.strings; sourceTree = "<group>"; };
45C6890928763C3600A2EB25 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Creator.strings; sourceTree = "<group>"; };
45C6890A28763C3900A2EB25 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Creator.strings; sourceTree = "<group>"; };
45C6890B28763C3C00A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = pt_PT.lproj/Creator.strings; sourceTree = "<group>"; };
45C6890B28763C3C00A2EB25 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Creator.strings"; sourceTree = "<group>"; };
45C6890C28763C3F00A2EB25 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Creator.strings; sourceTree = "<group>"; };
45C6890D28763C4300A2EB25 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Creator.strings; sourceTree = "<group>"; };
45C6890E28763C4600A2EB25 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Creator.strings; sourceTree = "<group>"; };
@ -927,8 +930,8 @@
A25E74450AF5089E006F11AE /* ExpandedPathToPathTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExpandedPathToPathTransformer.h; sourceTree = "<group>"; };
A25E74460AF5089E006F11AE /* ExpandedPathToIconTransformer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ExpandedPathToIconTransformer.mm; sourceTree = "<group>"; };
A25E74470AF5089E006F11AE /* ExpandedPathToIconTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ExpandedPathToIconTransformer.h; sourceTree = "<group>"; };
A2613F9811B3383200472893 /* pt_PT */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pt_PT; path = pt_PT.lproj/InfoPlist.strings; sourceTree = "<group>"; };
A2613F9911B3383200472893 /* pt_PT */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pt_PT; path = pt_PT.lproj/Localizable.strings; sourceTree = "<group>"; };
A2613F9811B3383200472893 /* pt-PT */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
A2613F9911B3383200472893 /* pt-PT */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
A263C5661560A3CF0082A3D1 /* da */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = "<group>"; };
A263C5671560A4210082A3D1 /* da */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = "<group>"; };
A263C6B1F6718E2486DB20E1 /* tr-buffer.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = "tr-buffer.h"; sourceTree = "<group>"; };
@ -959,7 +962,7 @@
A28393FD10D54A79005C0240 /* de */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = "<group>"; };
A28393FF10D54A96005C0240 /* de */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
A284214212DA663E00FBDDBB /* tr-udp.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "tr-udp.cc"; sourceTree = "<group>"; };
A28B3A2D160E1BC900D4A2BC /* pt_PT */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pt_PT; path = pt_PT.lproj/Localizable.strings; sourceTree = "<group>"; };
A28B3A2D160E1BC900D4A2BC /* pt-PT */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Localizable.strings"; sourceTree = "<group>"; };
A28E1DDF0CFFD8EC00E16385 /* ButtonToolbarItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ButtonToolbarItem.h; sourceTree = "<group>"; };
A28F4F750E085BDC003A3882 /* ColorTextField.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ColorTextField.h; sourceTree = "<group>"; };
A28F4F760E085BDC003A3882 /* ColorTextField.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ColorTextField.mm; sourceTree = "<group>"; };
@ -1153,6 +1156,136 @@
C1846B87294F781800A98F30 /* wildmat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wildmat.h; sourceTree = "<group>"; };
C1846B88294F781800A98F30 /* wildmat.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = wildmat.c; sourceTree = "<group>"; };
C1846B9E294F7A3400A98F30 /* libwildmat.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libwildmat.a; sourceTree = BUILT_PRODUCTS_DIR; };
C1A6BB3C2B3E69CB00C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BB3D2B3E6A0700C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BB3E2B3E6A0F00C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BB3F2B3E6A1800C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BB402B3E6A2200C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BB412B3E6A2900C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
C1A6BB422B3E6A3000C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BB432B3E6A3A00C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BB442B3E6A3F00C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
C1A6BB452B3E6A4C00C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
C1A6BB462B3E6ACE00C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C1A6BB472B3E6ADA00C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C1A6BB482B3E6ADF00C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C1A6BB492B3E6AE800C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C1A6BB4A2B3E6AEE00C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C1A6BB4B2B3E6AF500C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
C1A6BB4C2B3E6AFC00C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C1A6BB4D2B3E6B0200C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C1A6BB4E2B3E6B0800C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
C1A6BB4F2B3E6B0D00C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
C1A6BB532B3E6C6600C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/MainMenu.strings; sourceTree = "<group>"; };
C1A6BB542B3E6C7900C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/MainMenu.strings; sourceTree = "<group>"; };
C1A6BB552B3E6C8700C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/MainMenu.strings; sourceTree = "<group>"; };
C1A6BB562B3E6C8D00C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/MainMenu.strings; sourceTree = "<group>"; };
C1A6BB572B3E6C9000C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/MainMenu.strings; sourceTree = "<group>"; };
C1A6BB582B3E6C9300C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/MainMenu.strings"; sourceTree = "<group>"; };
C1A6BB592B3E6C9B00C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/MainMenu.strings; sourceTree = "<group>"; };
C1A6BB5A2B3E6C9C00C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/MainMenu.strings; sourceTree = "<group>"; };
C1A6BB5B2B3E6CA100C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/MainMenu.strings"; sourceTree = "<group>"; };
C1A6BB5C2B3E6CA300C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/MainMenu.strings"; sourceTree = "<group>"; };
C1A6BB5D2B3E6CD600C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
C1A6BB5E2B3E6CD800C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
C1A6BB5F2B3E6CDA00C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
C1A6BB602B3E6CDB00C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
C1A6BB612B3E6CDD00C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
C1A6BB622B3E6CDF00C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/PrefsWindow.strings"; sourceTree = "<group>"; };
C1A6BB632B3E6CE000C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
C1A6BB642B3E6CE200C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/PrefsWindow.strings; sourceTree = "<group>"; };
C1A6BB652B3E6CE400C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/PrefsWindow.strings"; sourceTree = "<group>"; };
C1A6BB662B3E6CE500C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/PrefsWindow.strings"; sourceTree = "<group>"; };
C1A6BB672B3E6CF000C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Creator.strings; sourceTree = "<group>"; };
C1A6BB682B3E6CF200C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Creator.strings; sourceTree = "<group>"; };
C1A6BB692B3E6CF300C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Creator.strings; sourceTree = "<group>"; };
C1A6BB6A2B3E6CF500C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Creator.strings; sourceTree = "<group>"; };
C1A6BB6B2B3E6CF600C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Creator.strings; sourceTree = "<group>"; };
C1A6BB6C2B3E6CF800C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Creator.strings"; sourceTree = "<group>"; };
C1A6BB6D2B3E6CFA00C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Creator.strings; sourceTree = "<group>"; };
C1A6BB6E2B3E6CFC00C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Creator.strings; sourceTree = "<group>"; };
C1A6BB6F2B3E6D0200C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Creator.strings"; sourceTree = "<group>"; };
C1A6BB702B3E6D0300C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Creator.strings"; sourceTree = "<group>"; };
C1A6BB712B3E6D1000C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/AddWindow.strings; sourceTree = "<group>"; };
C1A6BB722B3E6D1200C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/AddWindow.strings; sourceTree = "<group>"; };
C1A6BB732B3E6D1400C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/AddWindow.strings; sourceTree = "<group>"; };
C1A6BB742B3E6D1500C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/AddWindow.strings; sourceTree = "<group>"; };
C1A6BB752B3E6D1600C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/AddWindow.strings; sourceTree = "<group>"; };
C1A6BB762B3E6D1800C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/AddWindow.strings"; sourceTree = "<group>"; };
C1A6BB772B3E6D1900C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/AddWindow.strings; sourceTree = "<group>"; };
C1A6BB782B3E6D1B00C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/AddWindow.strings; sourceTree = "<group>"; };
C1A6BB792B3E6D1E00C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/AddWindow.strings"; sourceTree = "<group>"; };
C1A6BB7A2B3E6D2000C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/AddWindow.strings"; sourceTree = "<group>"; };
C1A6BB7B2B3E6D6200C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
C1A6BB7C2B3E6D6400C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
C1A6BB7D2B3E6D6500C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
C1A6BB7E2B3E6D6700C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
C1A6BB7F2B3E6D6800C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
C1A6BB802B3E6D6A00C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/AddMagnetWindow.strings"; sourceTree = "<group>"; };
C1A6BB812B3E6D6B00C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
C1A6BB822B3E6D6D00C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/AddMagnetWindow.strings; sourceTree = "<group>"; };
C1A6BB832B3E6D6F00C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/AddMagnetWindow.strings"; sourceTree = "<group>"; };
C1A6BB842B3E6D7100C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/AddMagnetWindow.strings"; sourceTree = "<group>"; };
C1A6BB852B3E6D8100C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/GroupRules.strings; sourceTree = "<group>"; };
C1A6BB862B3E6D8300C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/GroupRules.strings; sourceTree = "<group>"; };
C1A6BB872B3E6D8400C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/GroupRules.strings; sourceTree = "<group>"; };
C1A6BB882B3E6D8600C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/GroupRules.strings; sourceTree = "<group>"; };
C1A6BB892B3E6D8700C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/GroupRules.strings; sourceTree = "<group>"; };
C1A6BB8A2B3E6D8900C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/GroupRules.strings"; sourceTree = "<group>"; };
C1A6BB8B2B3E6D8A00C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/GroupRules.strings; sourceTree = "<group>"; };
C1A6BB8C2B3E6D8B00C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/GroupRules.strings; sourceTree = "<group>"; };
C1A6BB8D2B3E6D8D00C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/GroupRules.strings"; sourceTree = "<group>"; };
C1A6BB8E2B3E6D8F00C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/GroupRules.strings"; sourceTree = "<group>"; };
C1A6BB8F2B3E6DA200C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
C1A6BB902B3E6DA500C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
C1A6BB912B3E6DA600C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
C1A6BB922B3E6DA700C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
C1A6BB932B3E6DA900C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
C1A6BB942B3E6DAA00C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoGeneralView.strings"; sourceTree = "<group>"; };
C1A6BB952B3E6DAB00C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
C1A6BB962B3E6DAD00C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoGeneralView.strings; sourceTree = "<group>"; };
C1A6BB972B3E6DAF00C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoGeneralView.strings"; sourceTree = "<group>"; };
C1A6BB982B3E6DB000C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/InfoGeneralView.strings"; sourceTree = "<group>"; };
C1A6BB992B3E6DBE00C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
C1A6BB9A2B3E6DC000C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
C1A6BB9B2B3E6DC100C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
C1A6BB9C2B3E6DC300C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
C1A6BB9D2B3E6DC400C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
C1A6BB9E2B3E6DC600C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoActivityView.strings"; sourceTree = "<group>"; };
C1A6BB9F2B3E6DC700C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
C1A6BBA02B3E6DC900C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoActivityView.strings; sourceTree = "<group>"; };
C1A6BBA12B3E6DCA00C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoActivityView.strings"; sourceTree = "<group>"; };
C1A6BBA22B3E6DCC00C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/InfoActivityView.strings"; sourceTree = "<group>"; };
C1A6BBA32B3E6DDC00C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
C1A6BBA42B3E6DDE00C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
C1A6BBA52B3E6DDF00C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
C1A6BBA62B3E6DE000C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
C1A6BBA72B3E6DE200C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
C1A6BBA82B3E6DE300C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoOptionsView.strings"; sourceTree = "<group>"; };
C1A6BBA92B3E6DE500C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
C1A6BBAA2B3E6DE600C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoOptionsView.strings; sourceTree = "<group>"; };
C1A6BBAB2B3E6DE800C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/InfoOptionsView.strings"; sourceTree = "<group>"; };
C1A6BBAC2B3E6DEA00C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/InfoOptionsView.strings"; sourceTree = "<group>"; };
C1A6BBAD2B3E6E0900C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
C1A6BBAE2B3E6E0B00C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
C1A6BBAF2B3E6E0C00C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
C1A6BBB02B3E6E0E00C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
C1A6BBB12B3E6E0F00C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
C1A6BBB22B3E6E1000C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/GlobalOptionsPopover.strings"; sourceTree = "<group>"; };
C1A6BBB32B3E6E1200C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
C1A6BBB42B3E6E1300C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/GlobalOptionsPopover.strings; sourceTree = "<group>"; };
C1A6BBB52B3E6E1600C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/GlobalOptionsPopover.strings"; sourceTree = "<group>"; };
C1A6BBB62B3E6E1A00C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/GlobalOptionsPopover.strings"; sourceTree = "<group>"; };
C1A6BBB72B3E6E2900C4A151 /* eu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BBB82B3E6E2B00C4A151 /* he */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BBB92B3E6E2C00C4A151 /* hu */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BBBA2B3E6E2E00C4A151 /* ja */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BBBB2B3E6E2F00C4A151 /* pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BBBC2B3E6E3100C4A151 /* pt-BR */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
C1A6BBBD2B3E6E3200C4A151 /* sv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BBBE2B3E6E3300C4A151 /* uk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = "<group>"; };
C1A6BBBF2B3E6E3600C4A151 /* zh-CN */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
C1A6BBC02B3E6E3700C4A151 /* zh-TW */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
C1BF7BA71F2A3CB7008E88A7 /* upnpdev.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = upnpdev.c; sourceTree = "<group>"; };
C1BF7BA91F2A3CCE008E88A7 /* upnpdev.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = upnpdev.h; sourceTree = "<group>"; };
C1F690FC1AD0627500D95CF0 /* daemon-posix.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "daemon-posix.cc"; sourceTree = "<group>"; };
@ -1206,6 +1339,8 @@
C841A27F29197724009F18E8 /* NSKeyedUnarchiverAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NSKeyedUnarchiverAdditions.h; sourceTree = "<group>"; };
C841A28029197724009F18E8 /* NSKeyedUnarchiverAdditions.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NSKeyedUnarchiverAdditions.mm; sourceTree = "<group>"; };
C843FC8329C51B9400491854 /* utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = utils.mm; sourceTree = "<group>"; };
C843FC8529C8B40800491854 /* VersionComparator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VersionComparator.h; sourceTree = "<group>"; };
C843FC8629C8B40800491854 /* VersionComparator.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VersionComparator.mm; sourceTree = "<group>"; };
C86BCD9828228A9600F45599 /* SparkleProxy.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SparkleProxy.mm; sourceTree = "<group>"; };
C87369642809984200573C90 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };
C8748D8929891EA100D9E979 /* suffixes_dafsa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = suffixes_dafsa.h; path = "third-party/suffixes_dafsa.h"; sourceTree = SOURCE_ROOT; };
@ -1217,28 +1352,30 @@
C8B27B7F28153F2B00A22B5D /* transmission-create */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "transmission-create"; sourceTree = BUILT_PRODUCTS_DIR; };
C8B27B9028153F3100A22B5D /* transmission-edit */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "transmission-edit"; sourceTree = BUILT_PRODUCTS_DIR; };
C8B27BA128153F3400A22B5D /* transmission-show */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "transmission-show"; sourceTree = BUILT_PRODUCTS_DIR; };
C8ED0FAF281C10F100B44472 /* addr_is_reserved.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = addr_is_reserved.c; sourceTree = "<group>"; };
C8ED0FB0281C10F100B44472 /* addr_is_reserved.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = addr_is_reserved.h; sourceTree = "<group>"; };
CAB35C62252F6F5E00552A55 /* mime-types.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "mime-types.h"; sourceTree = "<group>"; };
CCEBA596277340F6DF9F4481 /* session-alt-speeds.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "session-alt-speeds.cc"; sourceTree = "<group>"; };
CCEBA596277340F6DF9F4483 /* session-alt-speeds.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = "session-alt-speeds.h"; sourceTree = "<group>"; };
D5C306568A7346FFFB8EFAD1 /* session-settings.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "session-settings.cc"; sourceTree = "<group>"; };
D5C306568A7346FFFB8EFAD3 /* session-settings.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = "session-settings.h"; sourceTree = "<group>"; };
D9057D68C13B75636539B681 /* variant-converters.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "variant-converters.cc"; sourceTree = "<group>"; };
E138A9750C04D88F00C5426C /* ProgressGradients.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProgressGradients.h; sourceTree = "<group>"; };
E138A9760C04D88F00C5426C /* ProgressGradients.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ProgressGradients.mm; sourceTree = "<group>"; };
E23B55A5FC3B557F7746D511 /* interned-string.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = "interned-string.h"; sourceTree = "<group>"; };
E71A5564279C2DD600EBFA1E /* tr-assert.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "tr-assert.mm"; sourceTree = "<group>"; };
E975121263DD973CAF4AEBA1 /* timer.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = timer.h; sourceTree = "<group>"; };
E975121263DD973CAF4AEBA3 /* timer-ev.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "timer-ev.h"; sourceTree = "<group>"; };
E975121263DD973CAF4AEBA5 /* timer-ev.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "timer-ev.cc"; sourceTree = "<group>"; };
ED20B87B28589274005FA6BE /* common_defs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = common_defs.h; sourceTree = "<group>"; };
ED20B87D285892C5005FA6BE /* crc32_multipliers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = crc32_multipliers.h; path = lib/crc32_multipliers.h; sourceTree = "<group>"; };
ED20B87E285892C5005FA6BE /* crc32_tables.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = crc32_tables.h; path = lib/crc32_tables.h; sourceTree = "<group>"; };
ED67FB402B70FCE400D8A037 /* settings.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = settings.cc; sourceTree = "<group>"; };
ED67FB412B70FCE400D8A037 /* settings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = settings.h; sourceTree = "<group>"; };
ED86936D2ADAE34D00342B1A /* DefaultAppHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DefaultAppHelper.h; sourceTree = "<group>"; };
ED86936E2ADAE34D00342B1A /* DefaultAppHelper.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DefaultAppHelper.mm; sourceTree = "<group>"; };
ED8A163B2735A8AA000D61F9 /* peer-mgr-active-requests.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "peer-mgr-active-requests.h"; sourceTree = "<group>"; };
ED8A163C2735A8AA000D61F9 /* peer-mgr-active-requests.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "peer-mgr-active-requests.cc"; sourceTree = "<group>"; };
ED8A163D2735A8AA000D61F9 /* peer-mgr-wishlist.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "peer-mgr-wishlist.h"; sourceTree = "<group>"; };
ED8A163E2735A8AA000D61F9 /* peer-mgr-wishlist.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "peer-mgr-wishlist.cc"; sourceTree = "<group>"; };
ED9862952B979AA2002F3035 /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Utils.h; sourceTree = "<group>"; };
ED9862962B979AA2002F3035 /* Utils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Utils.mm; sourceTree = "<group>"; };
EDBAAC8B29E486BC00D9495F /* global-ip-cache.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "global-ip-cache.h"; sourceTree = "<group>"; };
EDBAAC8D29E486C200D9495F /* global-ip-cache.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "global-ip-cache.cc"; sourceTree = "<group>"; };
EDBDFA9D25AFCCA60093D9C1 /* evutil_time.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = evutil_time.c; sourceTree = "<group>"; };
@ -1439,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 */,
@ -1537,6 +1676,8 @@
A222EA7A0E6C32C4009FB003 /* BlocklistScheduler.mm */,
ED86936D2ADAE34D00342B1A /* DefaultAppHelper.h */,
ED86936E2ADAE34D00342B1A /* DefaultAppHelper.mm */,
ED9862952B979AA2002F3035 /* Utils.h */,
ED9862962B979AA2002F3035 /* Utils.mm */,
A2AB883916A399A6008FAD50 /* VDKQueue */,
);
name = Sources;
@ -1752,9 +1893,8 @@
BEFC1E140C07861A00B0BB3C /* session.h */,
CCEBA596277340F6DF9F4481 /* session-alt-speeds.cc */,
CCEBA596277340F6DF9F4483 /* session-alt-speeds.h */,
D5C306568A7346FFFB8EFAD1 /* session-settings.cc */,
D5C306568A7346FFFB8EFAD3 /* session-settings.h */,
D9057D68C13B75636539B681 /* variant-converters.cc */,
ED67FB402B70FCE400D8A037 /* settings.cc */,
ED67FB412B70FCE400D8A037 /* settings.h */,
A25D2CBB0CF4C7190096A262 /* stats.cc */,
A25D2CBA0CF4C7190096A262 /* stats.h */,
C11DEA141FCD31C0009E22B9 /* subprocess-posix.cc */,
@ -1775,7 +1915,6 @@
2B9BA6C508B488FE586A0AB3 /* torrents.h */,
C1425B321EE9C5EA001DB85F /* tr-assert.cc */,
C1425B331EE9C5EA001DB85F /* tr-assert.h */,
E71A5564279C2DD600EBFA1E /* tr-assert.mm */,
A263C6B1F6718E2486DB20E1 /* tr-buffer.h */,
A22CFCA60FC24ED80009BD3E /* tr-dht.cc */,
A22CFCA70FC24ED80009BD3E /* tr-dht.h */,
@ -1962,39 +2101,12 @@
BE1183410CE15DF00002D0F3 /* libminiupnp */ = {
isa = PBXGroup;
children = (
A22B00AE116A9E90003315FC /* connecthostport.c */,
BE1183610CE160D50002D0F3 /* igd_desc_parse.c */,
BE1183620CE160D50002D0F3 /* minixml.c */,
BE1183630CE160D50002D0F3 /* miniwget.c */,
BE1183640CE160D50002D0F3 /* minissdpc.c */,
BE1183650CE160D50002D0F3 /* minisoap.c */,
BE1183660CE160D50002D0F3 /* upnpreplyparse.c */,
BE1183670CE160D50002D0F3 /* upnpcommands.c */,
BE1183680CE160D50002D0F3 /* miniupnpc.c */,
A20162CB13DE497000E15488 /* portlistingparse.c */,
A20162C713DE48BF00E15488 /* receivedata.c */,
C1BF7BA71F2A3CB7008E88A7 /* upnpdev.c */,
C12F19771E1AE3C30005E93F /* upnperrors.c */,
A22B00AF116A9E90003315FC /* connecthostport.h */,
BE11834E0CE160C50002D0F3 /* miniupnpc_declspec.h */,
BE11834F0CE160C50002D0F3 /* igd_desc_parse.h */,
BE1183500CE160C50002D0F3 /* minixml.h */,
BE1183510CE160C50002D0F3 /* miniwget.h */,
BE1183520CE160C50002D0F3 /* minisoap.h */,
A2F8CD420F3D0F4A00DB356A /* miniupnpcstrings.h */,
A20162CF13DE49E500E15488 /* miniupnpctypes.h */,
BE1183530CE160C50002D0F3 /* upnpreplyparse.h */,
BE1183540CE160C50002D0F3 /* upnpcommands.h */,
BE1183550CE160C50002D0F3 /* miniupnpc.h */,
BE1183560CE160C50002D0F3 /* minissdpc.h */,
A25485390EB66CBB004539DA /* codelength.h */,
A20162CC13DE497000E15488 /* portlistingparse.h */,
A20162C813DE48BF00E15488 /* receivedata.h */,
C1BF7BA91F2A3CCE008E88A7 /* upnpdev.h */,
C12F197A1E1AE4460005E93F /* upnperrors.h */,
C891A007281C02F3002E745F /* include */,
C8734FB02B9EA39F00EF2AD9 /* src */,
);
name = libminiupnp;
path = "third-party/miniupnpc";
path = "third-party/miniupnp/miniupnpc";
sourceTree = "<group>";
};
BE75C3570C72A0D600DBEFE0 /* libevent */ = {
@ -2149,6 +2261,51 @@
name = Compatibility;
sourceTree = "<group>";
};
C8734FB02B9EA39F00EF2AD9 /* src */ = {
isa = PBXGroup;
children = (
C8ED0FAF281C10F100B44472 /* addr_is_reserved.c */,
A22B00AE116A9E90003315FC /* connecthostport.c */,
BE1183610CE160D50002D0F3 /* igd_desc_parse.c */,
BE1183650CE160D50002D0F3 /* minisoap.c */,
BE1183640CE160D50002D0F3 /* minissdpc.c */,
BE1183680CE160D50002D0F3 /* miniupnpc.c */,
BE1183630CE160D50002D0F3 /* miniwget.c */,
BE1183620CE160D50002D0F3 /* minixml.c */,
A20162CB13DE497000E15488 /* portlistingparse.c */,
A20162C713DE48BF00E15488 /* receivedata.c */,
BE1183670CE160D50002D0F3 /* upnpcommands.c */,
C1BF7BA71F2A3CB7008E88A7 /* upnpdev.c */,
C12F19771E1AE3C30005E93F /* upnperrors.c */,
BE1183660CE160D50002D0F3 /* upnpreplyparse.c */,
C8ED0FB0281C10F100B44472 /* addr_is_reserved.h */,
A25485390EB66CBB004539DA /* codelength.h */,
A22B00AF116A9E90003315FC /* connecthostport.h */,
BE1183520CE160C50002D0F3 /* minisoap.h */,
BE1183560CE160C50002D0F3 /* minissdpc.h */,
BE1183500CE160C50002D0F3 /* minixml.h */,
A20162C813DE48BF00E15488 /* receivedata.h */,
);
path = src;
sourceTree = "<group>";
};
C891A007281C02F3002E745F /* include */ = {
isa = PBXGroup;
children = (
BE11834F0CE160C50002D0F3 /* igd_desc_parse.h */,
BE11834E0CE160C50002D0F3 /* miniupnpc_declspec.h */,
BE1183550CE160C50002D0F3 /* miniupnpc.h */,
A20162CF13DE49E500E15488 /* miniupnpctypes.h */,
BE1183510CE160C50002D0F3 /* miniwget.h */,
A20162CC13DE497000E15488 /* portlistingparse.h */,
BE1183540CE160C50002D0F3 /* upnpcommands.h */,
C1BF7BA91F2A3CCE008E88A7 /* upnpdev.h */,
C12F197A1E1AE4460005E93F /* upnperrors.h */,
BE1183530CE160C50002D0F3 /* upnpreplyparse.h */,
);
path = include;
sourceTree = "<group>";
};
E1B6FBF80C0D719B0015FE4D /* Info Window */ = {
isa = PBXGroup;
children = (
@ -2243,7 +2400,6 @@
BEFC1E450C07861A00B0BB3C /* net.h in Headers */,
BEFC1E4D0C07861A00B0BB3C /* session.h in Headers */,
CCEBA596277340F6DF9F4482 /* session-alt-speeds.h in Headers */,
D5C306568A7346FFFB8EFAD2 /* session-settings.h in Headers */,
BEFC1E4E0C07861A00B0BB3C /* inout.h in Headers */,
BEFC1E520C07861A00B0BB3C /* open-files.h in Headers */,
ED8A163F2735A8AA000D61F9 /* peer-mgr-active-requests.h in Headers */,
@ -2268,6 +2424,7 @@
A29DF8BE0DB2545F00D04E5A /* verify.h in Headers */,
C1FEE57B1C3223CC00D62832 /* watchdir.h in Headers */,
A2AAB6650DE0D08B00E04DDA /* blocklist.h in Headers */,
ED67FB432B70FCE400D8A037 /* settings.h in Headers */,
A2A4E9210DE0F7E9000CE197 /* web.h in Headers */,
A25E03E20E4015380086C225 /* tr-getopt.h in Headers */,
A21FBBAB0EDA78C300BC3C51 /* bandwidth.h in Headers */,
@ -2340,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 */,
@ -2784,8 +2942,17 @@
de,
da,
"pt-PT",
pt_PT,
Base,
eu,
he,
hu,
ja,
pl,
"pt-BR",
sv,
uk,
"zh-CN",
"zh-TW",
);
mainGroup = 29B97314FDCFA39411CA2CEA /* Transmission */;
projectDirPath = "";
@ -2888,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;
@ -2928,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;
@ -2990,8 +3158,6 @@
ED8A16402735A8AA000D61F9 /* peer-mgr-active-requests.cc in Sources */,
BEFC1E2F0C07861A00B0BB3C /* session.cc in Sources */,
CCEBA596277340F6DF9F4480 /* session-alt-speeds.cc in Sources */,
D5C306568A7346FFFB8EFAD0 /* session-settings.cc in Sources */,
D9057D68C13B75636539B680 /* variant-converters.cc in Sources */,
BEFC1E320C07861A00B0BB3C /* torrent.cc in Sources */,
2B9BA6C508B488FE586A0AB0 /* torrents.cc in Sources */,
BE7AA337F6752914B0C416B2 /* utils-ev.cc in Sources */,
@ -3019,6 +3185,7 @@
4D36BA770CA2F00800A63CA5 /* peer-mgr.cc in Sources */,
C1077A50183EB29600634C22 /* file-posix.cc in Sources */,
ED8A16422735A8AA000D61F9 /* peer-mgr-wishlist.cc in Sources */,
C83B17212B7341BC00B2EAE4 /* tr-assert.cc in Sources */,
4D36BA790CA2F00800A63CA5 /* peer-msgs.cc in Sources */,
A25D2CBD0CF4C73E0096A262 /* stats.cc in Sources */,
A201527E0D1C270F0081714F /* torrent-ctor.cc in Sources */,
@ -3028,12 +3195,12 @@
A2A4E9220DE0F7EB000CE197 /* web.cc in Sources */,
A292A6E80DFB45FC004B9C0A /* webseed.cc in Sources */,
A25E03E30E4015380086C225 /* tr-getopt.cc in Sources */,
E71A5565279C2DD600EBFA1E /* tr-assert.mm in Sources */,
C1305EBE186A13B100F03351 /* file.cc in Sources */,
A21FBBAC0EDA78C300BC3C51 /* bandwidth.cc in Sources */,
C1033E081A3279B800EF44D8 /* crypto-utils-ccrypto.cc in Sources */,
A22CFCA80FC24ED80009BD3E /* tr-dht.cc in Sources */,
0A6169A70FE5C9A200C66CE6 /* bitfield.cc in Sources */,
ED67FB422B70FCE400D8A037 /* settings.cc in Sources */,
1BB44E07B1B52E28291B4E32 /* file-piece-map.cc in Sources */,
A25964A6106D73A800453B31 /* announcer.cc in Sources */,
66F977825E65AD498C028BB0 /* announce-list.cc in Sources */,
@ -3141,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 */,
@ -3149,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 */,
@ -3201,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 */,
@ -3427,9 +3597,19 @@
A26AF1050D2855FC00FF7140 /* ru */,
A202FF5D0DDA9275009938FF /* it */,
A28393FD10D54A79005C0240 /* de */,
A2613F9811B3383200472893 /* pt_PT */,
A2613F9811B3383200472893 /* pt-PT */,
A263C5671560A4210082A3D1 /* da */,
A2A9D11A187DD75200C52A1F /* tr */,
C1A6BB462B3E6ACE00C4A151 /* eu */,
C1A6BB472B3E6ADA00C4A151 /* he */,
C1A6BB482B3E6ADF00C4A151 /* hu */,
C1A6BB492B3E6AE800C4A151 /* ja */,
C1A6BB4A2B3E6AEE00C4A151 /* pl */,
C1A6BB4B2B3E6AF500C4A151 /* pt-BR */,
C1A6BB4C2B3E6AFC00C4A151 /* sv */,
C1A6BB4D2B3E6B0200C4A151 /* uk */,
C1A6BB4E2B3E6B0800C4A151 /* zh-CN */,
C1A6BB4F2B3E6B0D00C4A151 /* zh-TW */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
@ -3447,6 +3627,16 @@
45C688E82875B19000A2EB25 /* ru */,
45C688E92875B19200A2EB25 /* es */,
45C688EA2875B19500A2EB25 /* tr */,
C1A6BB992B3E6DBE00C4A151 /* eu */,
C1A6BB9A2B3E6DC000C4A151 /* he */,
C1A6BB9B2B3E6DC100C4A151 /* hu */,
C1A6BB9C2B3E6DC300C4A151 /* ja */,
C1A6BB9D2B3E6DC400C4A151 /* pl */,
C1A6BB9E2B3E6DC600C4A151 /* pt-BR */,
C1A6BB9F2B3E6DC700C4A151 /* sv */,
C1A6BBA02B3E6DC900C4A151 /* uk */,
C1A6BBA12B3E6DCA00C4A151 /* zh-CN */,
C1A6BBA22B3E6DCC00C4A151 /* zh-TW */,
);
name = InfoActivityView.xib;
sourceTree = "<group>";
@ -3464,6 +3654,16 @@
45C688DF2875B06D00A2EB25 /* ru */,
45C688E02875B06F00A2EB25 /* es */,
45C688E12875B07300A2EB25 /* tr */,
C1A6BB8F2B3E6DA200C4A151 /* eu */,
C1A6BB902B3E6DA500C4A151 /* he */,
C1A6BB912B3E6DA600C4A151 /* hu */,
C1A6BB922B3E6DA700C4A151 /* ja */,
C1A6BB932B3E6DA900C4A151 /* pl */,
C1A6BB942B3E6DAA00C4A151 /* pt-BR */,
C1A6BB952B3E6DAB00C4A151 /* sv */,
C1A6BB962B3E6DAD00C4A151 /* uk */,
C1A6BB972B3E6DAF00C4A151 /* zh-CN */,
C1A6BB982B3E6DB000C4A151 /* zh-TW */,
);
name = InfoGeneralView.xib;
sourceTree = "<group>";
@ -3481,6 +3681,16 @@
45C688F12875B2CA00A2EB25 /* ru */,
45C688F22875B2CD00A2EB25 /* es */,
45C688F32875B2CF00A2EB25 /* tr */,
C1A6BBA32B3E6DDC00C4A151 /* eu */,
C1A6BBA42B3E6DDE00C4A151 /* he */,
C1A6BBA52B3E6DDF00C4A151 /* hu */,
C1A6BBA62B3E6DE000C4A151 /* ja */,
C1A6BBA72B3E6DE200C4A151 /* pl */,
C1A6BBA82B3E6DE300C4A151 /* pt-BR */,
C1A6BBA92B3E6DE500C4A151 /* sv */,
C1A6BBAA2B3E6DE600C4A151 /* uk */,
C1A6BBAB2B3E6DE800C4A151 /* zh-CN */,
C1A6BBAC2B3E6DEA00C4A151 /* zh-TW */,
);
name = InfoOptionsView.xib;
sourceTree = "<group>";
@ -3498,6 +3708,16 @@
45C688D62875AE0500A2EB25 /* tr */,
45C688D72875AE0800A2EB25 /* fr */,
45C688D82875AE0C00A2EB25 /* nl */,
C1A6BB852B3E6D8100C4A151 /* eu */,
C1A6BB862B3E6D8300C4A151 /* he */,
C1A6BB872B3E6D8400C4A151 /* hu */,
C1A6BB882B3E6D8600C4A151 /* ja */,
C1A6BB892B3E6D8700C4A151 /* pl */,
C1A6BB8A2B3E6D8900C4A151 /* pt-BR */,
C1A6BB8B2B3E6D8A00C4A151 /* sv */,
C1A6BB8C2B3E6D8B00C4A151 /* uk */,
C1A6BB8D2B3E6D8D00C4A151 /* zh-CN */,
C1A6BB8E2B3E6D8F00C4A151 /* zh-TW */,
);
name = GroupRules.xib;
sourceTree = "<group>";
@ -3515,6 +3735,16 @@
45C688CD2875AC8D00A2EB25 /* ru */,
45C688CE2875AC8F00A2EB25 /* es */,
45C688CF2875AC9200A2EB25 /* tr */,
C1A6BB7B2B3E6D6200C4A151 /* eu */,
C1A6BB7C2B3E6D6400C4A151 /* he */,
C1A6BB7D2B3E6D6500C4A151 /* hu */,
C1A6BB7E2B3E6D6700C4A151 /* ja */,
C1A6BB7F2B3E6D6800C4A151 /* pl */,
C1A6BB802B3E6D6A00C4A151 /* pt-BR */,
C1A6BB812B3E6D6B00C4A151 /* sv */,
C1A6BB822B3E6D6D00C4A151 /* uk */,
C1A6BB832B3E6D6F00C4A151 /* zh-CN */,
C1A6BB842B3E6D7100C4A151 /* zh-TW */,
);
name = AddMagnetWindow.xib;
sourceTree = "<group>";
@ -3532,6 +3762,16 @@
45C688FA2875B40300A2EB25 /* ru */,
45C688FB2875B40600A2EB25 /* es */,
45C688FC2875B40900A2EB25 /* tr */,
C1A6BBAD2B3E6E0900C4A151 /* eu */,
C1A6BBAE2B3E6E0B00C4A151 /* he */,
C1A6BBAF2B3E6E0C00C4A151 /* hu */,
C1A6BBB02B3E6E0E00C4A151 /* ja */,
C1A6BBB12B3E6E0F00C4A151 /* pl */,
C1A6BBB22B3E6E1000C4A151 /* pt-BR */,
C1A6BBB32B3E6E1200C4A151 /* sv */,
C1A6BBB42B3E6E1300C4A151 /* uk */,
C1A6BBB52B3E6E1600C4A151 /* zh-CN */,
C1A6BBB62B3E6E1A00C4A151 /* zh-TW */,
);
name = GlobalOptionsPopover.xib;
sourceTree = "<group>";
@ -3546,9 +3786,19 @@
A202FF5F0DDA9275009938FF /* it */,
A291477F0E195A0C00F60CB2 /* en */,
A28393FF10D54A96005C0240 /* de */,
A2613F9911B3383200472893 /* pt_PT */,
A2613F9911B3383200472893 /* pt-PT */,
A263C5661560A3CF0082A3D1 /* da */,
A2A9D119187DD75100C52A1F /* tr */,
C1A6BB3C2B3E69CB00C4A151 /* eu */,
C1A6BB3D2B3E6A0700C4A151 /* he */,
C1A6BB3E2B3E6A0F00C4A151 /* hu */,
C1A6BB3F2B3E6A1800C4A151 /* ja */,
C1A6BB402B3E6A2200C4A151 /* pl */,
C1A6BB412B3E6A2900C4A151 /* pt-BR */,
C1A6BB422B3E6A3000C4A151 /* sv */,
C1A6BB432B3E6A3A00C4A151 /* uk */,
C1A6BB442B3E6A3F00C4A151 /* zh-CN */,
C1A6BB452B3E6A4C00C4A151 /* zh-TW */,
);
name = Localizable.strings;
sourceTree = "<group>";
@ -3566,6 +3816,16 @@
455C093F287767350003A078 /* ru */,
455C0940287767380003A078 /* es */,
455C09412877673A0003A078 /* tr */,
C1A6BB5D2B3E6CD600C4A151 /* eu */,
C1A6BB5E2B3E6CD800C4A151 /* he */,
C1A6BB5F2B3E6CDA00C4A151 /* hu */,
C1A6BB602B3E6CDB00C4A151 /* ja */,
C1A6BB612B3E6CDD00C4A151 /* pl */,
C1A6BB622B3E6CDF00C4A151 /* pt-BR */,
C1A6BB632B3E6CE000C4A151 /* sv */,
C1A6BB642B3E6CE200C4A151 /* uk */,
C1A6BB652B3E6CE400C4A151 /* zh-CN */,
C1A6BB662B3E6CE500C4A151 /* zh-TW */,
);
name = PrefsWindow.xib;
sourceTree = "<group>";
@ -3583,6 +3843,16 @@
457DC1E72873931500ED04C4 /* ru */,
457DC1E82873931900ED04C4 /* es */,
457DC1E92873931E00ED04C4 /* tr */,
C1A6BB532B3E6C6600C4A151 /* eu */,
C1A6BB542B3E6C7900C4A151 /* he */,
C1A6BB552B3E6C8700C4A151 /* hu */,
C1A6BB562B3E6C8D00C4A151 /* ja */,
C1A6BB572B3E6C9000C4A151 /* pl */,
C1A6BB582B3E6C9300C4A151 /* pt-BR */,
C1A6BB592B3E6C9B00C4A151 /* sv */,
C1A6BB5A2B3E6C9C00C4A151 /* uk */,
C1A6BB5B2B3E6CA100C4A151 /* zh-CN */,
C1A6BB5C2B3E6CA300C4A151 /* zh-TW */,
);
name = MainMenu.xib;
sourceTree = "<group>";
@ -3598,8 +3868,18 @@
A2FB07F115F8208300933543 /* nl */,
A2D8CFBF15FA177A0056E93D /* ru */,
A27F4483160B4AF50048CD4C /* fr */,
A28B3A2D160E1BC900D4A2BC /* pt_PT */,
A28B3A2D160E1BC900D4A2BC /* pt-PT */,
A2CA772B187F063A00154956 /* tr */,
C1A6BBB72B3E6E2900C4A151 /* eu */,
C1A6BBB82B3E6E2B00C4A151 /* he */,
C1A6BBB92B3E6E2C00C4A151 /* hu */,
C1A6BBBA2B3E6E2E00C4A151 /* ja */,
C1A6BBBB2B3E6E2F00C4A151 /* pl */,
C1A6BBBC2B3E6E3100C4A151 /* pt-BR */,
C1A6BBBD2B3E6E3200C4A151 /* sv */,
C1A6BBBE2B3E6E3300C4A151 /* uk */,
C1A6BBBF2B3E6E3600C4A151 /* zh-CN */,
C1A6BBC02B3E6E3700C4A151 /* zh-TW */,
);
name = Localizable.strings;
sourceTree = "<group>";
@ -3617,6 +3897,16 @@
45C6890328762FFD00A2EB25 /* ru */,
45C6890428762FFF00A2EB25 /* es */,
45C689052876300200A2EB25 /* tr */,
C1A6BB712B3E6D1000C4A151 /* eu */,
C1A6BB722B3E6D1200C4A151 /* he */,
C1A6BB732B3E6D1400C4A151 /* hu */,
C1A6BB742B3E6D1500C4A151 /* ja */,
C1A6BB752B3E6D1600C4A151 /* pl */,
C1A6BB762B3E6D1800C4A151 /* pt-BR */,
C1A6BB772B3E6D1900C4A151 /* sv */,
C1A6BB782B3E6D1B00C4A151 /* uk */,
C1A6BB792B3E6D1E00C4A151 /* zh-CN */,
C1A6BB7A2B3E6D2000C4A151 /* zh-TW */,
);
name = AddWindow.xib;
sourceTree = "<group>";
@ -3634,6 +3924,16 @@
45C6890C28763C3F00A2EB25 /* ru */,
45C6890D28763C4300A2EB25 /* es */,
45C6890E28763C4600A2EB25 /* tr */,
C1A6BB672B3E6CF000C4A151 /* eu */,
C1A6BB682B3E6CF200C4A151 /* he */,
C1A6BB692B3E6CF300C4A151 /* hu */,
C1A6BB6A2B3E6CF500C4A151 /* ja */,
C1A6BB6B2B3E6CF600C4A151 /* pl */,
C1A6BB6C2B3E6CF800C4A151 /* pt-BR */,
C1A6BB6D2B3E6CFA00C4A151 /* sv */,
C1A6BB6E2B3E6CFC00C4A151 /* uk */,
C1A6BB6F2B3E6D0200C4A151 /* zh-CN */,
C1A6BB702B3E6D0300C4A151 /* zh-TW */,
);
name = Creator.xib;
sourceTree = "<group>";
@ -3667,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",
@ -3688,7 +3989,7 @@
"-DWIDE_INTEGER_DISABLE_IOSTREAM",
"-DRAPIDJSON_HAS_STDSTRING=1",
"-DHAVE_FLOCK",
"-DHAVE_STRLCPY",
"-DWITH_CCRYPTO",
);
PRODUCT_NAME = transmission;
SYSTEM_HEADER_SEARCH_PATHS = (
@ -3875,6 +4176,7 @@
OTHER_CFLAGS = (
"$(inherited)",
"-DFMT_HEADER_ONLY",
"-DWITH_CCRYPTO",
);
OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)";
PRODUCT_BUNDLE_IDENTIFIER = org.m0k.transmission;
@ -3920,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",
@ -3941,7 +4244,7 @@
"-DWIDE_INTEGER_DISABLE_IOSTREAM",
"-DRAPIDJSON_HAS_STDSTRING=1",
"-DHAVE_FLOCK",
"-DHAVE_STRLCPY",
"-DWITH_CCRYPTO",
);
PRODUCT_NAME = transmission;
SYSTEM_HEADER_SEARCH_PATHS = (
@ -4082,6 +4385,7 @@
OTHER_CFLAGS = (
"$(inherited)",
"-DFMT_HEADER_ONLY",
"-DWITH_CCRYPTO",
"-DNDEBUG",
);
OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)";
@ -4094,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;
@ -4104,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;
@ -4114,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;
@ -4185,6 +4492,7 @@
OTHER_CFLAGS = (
"$(inherited)",
"-DFMT_HEADER_ONLY",
"-DWITH_CCRYPTO",
);
OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)";
PRODUCT_BUNDLE_IDENTIFIER = org.m0k.transmission;
@ -4247,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",
@ -4268,7 +4577,7 @@
"-DWIDE_INTEGER_DISABLE_IOSTREAM",
"-DRAPIDJSON_HAS_STDSTRING=1",
"-DHAVE_FLOCK",
"-DHAVE_STRLCPY",
"-DWITH_CCRYPTO",
);
PRODUCT_NAME = transmission;
SYSTEM_HEADER_SEARCH_PATHS = (

69
android/build.gradle Normal file
View File

@ -0,0 +1,69 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.2.1"
}
}
apply plugin: "com.android.library"
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["Transmission_" + name]
}
def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Transmission_" + name]).toInteger()
}
android {
ndkVersion getExtOrDefault("ndkVersion")
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
defaultConfig {
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
externalNativeBuild {
cmake {
abiFilters "arm64-v8a"
arguments "-DVCPKG_TARGET_ANDROID=ON", "-DWITH_CRYPTO=openssl"
}
}
}
externalNativeBuild {
cmake {
version "3.22.1"
path "../CMakeLists.txt"
}
}
buildTypes {
release {
minifyEnabled false
}
}
lintOptions {
disable "GradleCompatible"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
prefab true
}
}
repositories {
mavenCentral()
google()
}

View File

@ -0,0 +1,6 @@
Transmission_kotlinVersion=1.7.0
# quotactl is defined from >= 26
Transmission_minSdkVersion=26
Transmission_targetSdkVersion=31
Transmission_compileSdkVersion=31
Transmission_ndkVersion=26.1.10909125

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.transmissionbt.transmission">
<application android:label="Transmission">
</application>
</manifest>

View File

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

View File

@ -30,23 +30,25 @@ using namespace libtransmission::Values;
#define SPEED_K_STR "kB/s"
static auto constexpr LineWidth = int{ 80 };
namespace
{
auto constexpr LineWidth = int{ 80 };
static char constexpr MyConfigName[] = "transmission";
static char constexpr MyReadableName[] = "transmission-cli";
static char constexpr Usage
char constexpr MyConfigName[] = "transmission";
char constexpr MyReadableName[] = "transmission-cli";
char constexpr Usage
[] = "A fast and easy BitTorrent client\n"
"\n"
"Usage: transmission-cli [options] <file|url|magnet>";
static bool showVersion = false;
static bool verify = false;
static sig_atomic_t gotsig = false;
static sig_atomic_t manualUpdate = false;
bool showVersion = false;
bool verify = false;
sig_atomic_t gotsig = false;
sig_atomic_t manualUpdate = false;
static char const* torrentPath = nullptr;
char const* torrentPath = nullptr;
static auto constexpr Options = std::array<tr_option, 20>{
auto constexpr Options = std::array<tr_option, 20>{
{ { 'b', "blocklist", "Enable peer blocklists", "b", false, nullptr },
{ 'B', "no-blocklist", "Disable peer blocklists", "B", false, nullptr },
{ 'd', "downlimit", "Set max download speed in " SPEED_K_STR, "d", true, "<speed>" },
@ -76,11 +78,11 @@ static auto constexpr Options = std::array<tr_option, 20>{
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
};
static int parseCommandLine(tr_variant*, int argc, char const** argv);
int parseCommandLine(tr_variant*, int argc, char const** argv);
static void sigHandler(int signal);
void sigHandler(int signal);
static std::string tr_strlratio(double ratio)
[[nodiscard]] std::string tr_strlratio(double ratio)
{
if (static_cast<int>(ratio) == TR_RATIO_NA)
{
@ -94,27 +96,27 @@ static std::string tr_strlratio(double ratio)
if (ratio < 10.0)
{
return fmt::format(FMT_STRING("{:.2f}"), ratio);
return fmt::format("{:.2f}", ratio);
}
if (ratio < 100.0)
{
return fmt::format(FMT_STRING("{:.1f}"), ratio);
return fmt::format("{:.1f}", ratio);
}
return fmt::format(FMT_STRING("{:.0f}"), ratio);
return fmt::format("{:.0f}", ratio);
}
static bool waitingOnWeb;
bool waitingOnWeb;
static void onTorrentFileDownloaded(tr_web::FetchResponse const& response)
void onTorrentFileDownloaded(tr_web::FetchResponse const& response)
{
auto* ctor = static_cast<tr_ctor*>(response.user_data);
tr_ctorSetMetainfo(ctor, std::data(response.body), std::size(response.body), nullptr);
waitingOnWeb = false;
}
static std::string getStatusStr(tr_stat const* st)
[[nodiscard]] std::string getStatusStr(tr_stat const* st)
{
if (st->activity == TR_STATUS_CHECK_WAIT)
{
@ -124,7 +126,7 @@ static std::string getStatusStr(tr_stat const* st)
if (st->activity == TR_STATUS_CHECK)
{
return fmt::format(
FMT_STRING("Verifying local files ({:.2f}%, {:.2f}% valid)"),
"Verifying local files ({:.2f}%, {:.2f}% valid)",
tr_truncd(100 * st->recheckProgress, 2),
tr_truncd(100 * st->percentDone, 2));
}
@ -132,7 +134,7 @@ static std::string getStatusStr(tr_stat const* st)
if (st->activity == TR_STATUS_DOWNLOAD)
{
return fmt::format(
FMT_STRING("Progress: {:.1f}%, dl from {:d} of {:d} peers ({:s}), ul to {:d} ({:s}) [{:s}]"),
"Progress: {:.1f}%, dl from {:d} of {:d} peers ({:s}), ul to {:d} ({:s}) [{:s}]",
tr_truncd(100 * st->percentDone, 1),
st->peersSendingToUs,
st->peersConnected,
@ -145,7 +147,7 @@ static std::string getStatusStr(tr_stat const* st)
if (st->activity == TR_STATUS_SEED)
{
return fmt::format(
FMT_STRING("Seeding, uploading to {:d} of {:d} peer(s), {:s} [{:s}]"),
"Seeding, uploading to {:d} of {:d} peer(s), {:s} [{:s}]",
st->peersGettingFromUs,
st->peersConnected,
Speed{ st->pieceUploadSpeed_KBps, Speed::Units::KByps }.to_string(),
@ -155,7 +157,7 @@ static std::string getStatusStr(tr_stat const* st)
return "";
}
static std::string getConfigDir(int argc, char const** argv)
[[nodiscard]] std::string getConfigDir(int argc, char const** argv)
{
int c;
char const* my_optarg;
@ -175,6 +177,133 @@ static std::string getConfigDir(int argc, char const** argv)
return tr_getDefaultConfigDir(MyConfigName);
}
// ---
int parseCommandLine(tr_variant* d, int argc, char const** argv)
{
int c;
char const* my_optarg;
while ((c = tr_getopt(Usage, argc, argv, std::data(Options), &my_optarg)) != TR_OPT_DONE)
{
switch (c)
{
case 'b':
tr_variantDictAddBool(d, TR_KEY_blocklist_enabled, true);
break;
case 'B':
tr_variantDictAddBool(d, TR_KEY_blocklist_enabled, false);
break;
case 'd':
tr_variantDictAddInt(d, TR_KEY_speed_limit_down, atoi(my_optarg));
tr_variantDictAddBool(d, TR_KEY_speed_limit_down_enabled, true);
break;
case 'D':
tr_variantDictAddBool(d, TR_KEY_speed_limit_down_enabled, false);
break;
case 'f':
tr_variantDictAddStr(d, TR_KEY_script_torrent_done_filename, my_optarg);
tr_variantDictAddBool(d, TR_KEY_script_torrent_done_enabled, true);
break;
case 'g': /* handled above */
break;
case 'm':
tr_variantDictAddBool(d, TR_KEY_port_forwarding_enabled, true);
break;
case 'M':
tr_variantDictAddBool(d, TR_KEY_port_forwarding_enabled, false);
break;
case 'p':
tr_variantDictAddInt(d, TR_KEY_peer_port, atoi(my_optarg));
break;
case 't':
tr_variantDictAddStr(d, TR_KEY_peer_socket_tos, my_optarg);
break;
case 'u':
tr_variantDictAddInt(d, TR_KEY_speed_limit_up, atoi(my_optarg));
tr_variantDictAddBool(d, TR_KEY_speed_limit_up_enabled, true);
break;
case 'U':
tr_variantDictAddBool(d, TR_KEY_speed_limit_up_enabled, false);
break;
case 'v':
verify = true;
break;
case 'V':
showVersion = true;
break;
case 'w':
tr_variantDictAddStr(d, TR_KEY_download_dir, my_optarg);
break;
case 910:
tr_variantDictAddInt(d, TR_KEY_encryption, TR_ENCRYPTION_REQUIRED);
break;
case 911:
tr_variantDictAddInt(d, TR_KEY_encryption, TR_ENCRYPTION_PREFERRED);
break;
case 912:
tr_variantDictAddInt(d, TR_KEY_encryption, TR_CLEAR_PREFERRED);
break;
case 500:
tr_variantDictAddBool(d, TR_KEY_sequentialDownload, true);
break;
case TR_OPT_UNK:
if (torrentPath == nullptr)
{
torrentPath = my_optarg;
}
break;
default:
return 1;
}
}
return 0;
}
void sigHandler(int signal)
{
switch (signal)
{
case SIGINT:
gotsig = true;
break;
#ifndef _WIN32
case SIGHUP:
manualUpdate = true;
break;
#endif
default:
break;
}
}
} // namespace
int tr_main(int argc, char* argv[])
{
auto const init_mgr = tr_lib_init();
@ -192,7 +321,7 @@ int tr_main(int argc, char* argv[])
/* load the defaults from config file + libtransmission defaults */
auto const config_dir = getConfigDir(argc, (char const**)argv);
auto settings = tr_sessionLoadSettings(config_dir.c_str(), MyConfigName);
auto settings = tr_sessionLoadSettings(nullptr, config_dir.c_str(), MyConfigName);
/* the command line overrides defaults */
if (parseCommandLine(&settings, argc, (char const**)argv) != 0)
@ -335,132 +464,3 @@ int tr_main(int argc, char* argv[])
tr_sessionClose(h);
return EXIT_SUCCESS;
}
/***
****
****
***/
static int parseCommandLine(tr_variant* d, int argc, char const** argv)
{
int c;
char const* my_optarg;
while ((c = tr_getopt(Usage, argc, argv, std::data(Options), &my_optarg)) != TR_OPT_DONE)
{
switch (c)
{
case 'b':
tr_variantDictAddBool(d, TR_KEY_blocklist_enabled, true);
break;
case 'B':
tr_variantDictAddBool(d, TR_KEY_blocklist_enabled, false);
break;
case 'd':
tr_variantDictAddInt(d, TR_KEY_speed_limit_down, atoi(my_optarg));
tr_variantDictAddBool(d, TR_KEY_speed_limit_down_enabled, true);
break;
case 'D':
tr_variantDictAddBool(d, TR_KEY_speed_limit_down_enabled, false);
break;
case 'f':
tr_variantDictAddStr(d, TR_KEY_script_torrent_done_filename, my_optarg);
tr_variantDictAddBool(d, TR_KEY_script_torrent_done_enabled, true);
break;
case 'g': /* handled above */
break;
case 'm':
tr_variantDictAddBool(d, TR_KEY_port_forwarding_enabled, true);
break;
case 'M':
tr_variantDictAddBool(d, TR_KEY_port_forwarding_enabled, false);
break;
case 'p':
tr_variantDictAddInt(d, TR_KEY_peer_port, atoi(my_optarg));
break;
case 't':
tr_variantDictAddStr(d, TR_KEY_peer_socket_tos, my_optarg);
break;
case 'u':
tr_variantDictAddInt(d, TR_KEY_speed_limit_up, atoi(my_optarg));
tr_variantDictAddBool(d, TR_KEY_speed_limit_up_enabled, true);
break;
case 'U':
tr_variantDictAddBool(d, TR_KEY_speed_limit_up_enabled, false);
break;
case 'v':
verify = true;
break;
case 'V':
showVersion = true;
break;
case 'w':
tr_variantDictAddStr(d, TR_KEY_download_dir, my_optarg);
break;
case 910:
tr_variantDictAddInt(d, TR_KEY_encryption, TR_ENCRYPTION_REQUIRED);
break;
case 911:
tr_variantDictAddInt(d, TR_KEY_encryption, TR_ENCRYPTION_PREFERRED);
break;
case 912:
tr_variantDictAddInt(d, TR_KEY_encryption, TR_CLEAR_PREFERRED);
break;
case 500:
tr_variantDictAddBool(d, TR_KEY_sequentialDownload, true);
break;
case TR_OPT_UNK:
if (torrentPath == nullptr)
{
torrentPath = my_optarg;
}
break;
default:
return 1;
}
}
return 0;
}
static void sigHandler(int signal)
{
switch (signal)
{
case SIGINT:
gotsig = true;
break;
#ifndef _WIN32
case SIGHUP:
manualUpdate = true;
break;
#endif
default:
break;
}
}

View File

@ -159,6 +159,7 @@ macro(tr_add_external_auto_library ID DIRNAME LIBNAME)
set(${ID}_LIBRARIES ${${ID}_LIBRARY})
set(${ID}_EXT_PROJ_CMAKE_ARGS)
if(APPLE)
string(REPLACE ";" "$<SEMICOLON>" ${ID}_CMAKE_OSX_ARCHITECTURES "${CMAKE_OSX_ARCHITECTURES}")
list(APPEND ${ID}_EXT_PROJ_CMAKE_ARGS
@ -167,6 +168,21 @@ macro(tr_add_external_auto_library ID DIRNAME LIBNAME)
"-DCMAKE_OSX_SYSROOT:PATH=${CMAKE_OSX_SYSROOT}")
endif()
if(ANDROID)
list(APPEND ${ID}_EXT_PROJ_CMAKE_ARGS
"-DANDROID_PLATFORM=${ANDROID_PLATFORM}"
"-DANDROID_NDK=${ANDROID_NDK}"
"-DANDROID_ABI=${ANDROID_ABI}"
"-DANDROID_STL=${ANDROID_STL}"
"-DCMAKE_ANDROID_NDK=${CMAKE_ANDROID_NDK}"
"-DCMAKE_ANDROID_ARCH_ABI=${CMAKE_ANDROID_ARCH_ABI}")
endif()
if(VCPKG_CHAINLOAD_TOOLCHAIN_FILE)
list(APPEND ${ID}_EXT_PROJ_CMAKE_ARGS
"-DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=${VCPKG_CHAINLOAD_TOOLCHAIN_FILE}")
endif()
ExternalProject_Add(
${${ID}_UPSTREAM_TARGET}
PREFIX "${TR_THIRD_PARTY_BINARY_DIR}/${DIRNAME}.bld"

View File

@ -32,7 +32,7 @@ BEGIN
VALUE "FileDescription", "${TR_FILE_DESCRIPTION}"
VALUE "FileVersion", LONG_VERSION_STRING
VALUE "InternalName", "${TR_INTERNAL_NAME}"
VALUE "LegalCopyright", "2005-2023 Transmission Project"
VALUE "LegalCopyright", "2005-2024 Transmission Project"
VALUE "OriginalFilename", "${TR_ORIGINAL_FILENAME}"
VALUE "ProductName", "Transmission"
VALUE "ProductVersion", LONG_VERSION_STRING

100
cmake/VcpkgAndroid.cmake Normal file
View File

@ -0,0 +1,100 @@
# Copied from https://github.com/microsoft/vcpkg-docs/blob/main/vcpkg/examples/vcpkg_android_example_cmake_script/cmake/vcpkg_android.cmake
#
# vcpkg_android.cmake
#
# Helper script when using vcpkg with cmake. It should be triggered via the variable VCPKG_TARGET_ANDROID
#
# For example:
# if (VCPKG_TARGET_ANDROID)
# include("cmake/vcpkg_android.cmake")
# endif()
#
# This script will:
# 1 & 2. check the presence of needed env variables: ANDROID_NDK_HOME and VCPKG_ROOT
# 3. set VCPKG_TARGET_TRIPLET according to ANDROID_ABI
# 4. Combine vcpkg and Android toolchains by setting CMAKE_TOOLCHAIN_FILE
# and VCPKG_CHAINLOAD_TOOLCHAIN_FILE
# Note: VCPKG_TARGET_ANDROID is not an official vcpkg variable.
# it is introduced for the need of this script
if (VCPKG_TARGET_ANDROID)
#
# 1. Check the presence of environment variable ANDROID_NDK_HOME
#
if (NOT DEFINED ENV{ANDROID_NDK_HOME})
message(FATAL_ERROR "
Please set an environment variable ANDROID_NDK_HOME
For example:
export ANDROID_NDK_HOME=/home/your-account/Android/Sdk/ndk-bundle
Or:
export ANDROID_NDK_HOME=/home/your-account/Android/android-ndk-r21b
")
endif()
#
# 2. Check the presence of environment variable VCPKG_ROOT
#
if (NOT DEFINED ENV{VCPKG_ROOT})
message(FATAL_ERROR "
Please set an environment variable VCPKG_ROOT
For example:
export VCPKG_ROOT=/path/to/vcpkg
")
endif()
#
# 3. Set VCPKG_TARGET_TRIPLET according to ANDROID_ABI
#
# There are four different Android ABI, each of which maps to
# a vcpkg triplet. The following table outlines the mapping from vcpkg architectures to android architectures
#
# |VCPKG_TARGET_TRIPLET | ANDROID_ABI |
# |---------------------------|----------------------|
# |arm64-android | arm64-v8a |
# |arm-android | armeabi-v7a |
# |x64-android | x86_64 |
# |x86-android | x86 |
#
# The variable must be stored in the cache in order to successfully the two toolchains.
#
if (ANDROID_ABI MATCHES "arm64-v8a")
set(VCPKG_TARGET_TRIPLET "arm64-android" CACHE STRING "" FORCE)
elseif(ANDROID_ABI MATCHES "armeabi-v7a")
set(VCPKG_TARGET_TRIPLET "arm-android" CACHE STRING "" FORCE)
elseif(ANDROID_ABI MATCHES "x86_64")
set(VCPKG_TARGET_TRIPLET "x64-android" CACHE STRING "" FORCE)
elseif(ANDROID_ABI MATCHES "x86")
set(VCPKG_TARGET_TRIPLET "x86-android" CACHE STRING "" FORCE)
else()
message(FATAL_ERROR "
Please specify ANDROID_ABI
For example
cmake ... -DANDROID_ABI=armeabi-v7a
Possible ABIs are: arm64-v8a, armeabi-v7a, x64-android, x86-android
")
endif()
message("vcpkg_android.cmake: VCPKG_TARGET_TRIPLET was set to ${VCPKG_TARGET_TRIPLET}")
#
# 4. Combine vcpkg and Android toolchains
#
# vcpkg and android both provide dedicated toolchains:
#
# vcpkg_toolchain_file=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
# android_toolchain_file=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake
#
# When using vcpkg, the vcpkg toolchain shall be specified first.
# However, vcpkg provides a way to preload and additional toolchain,
# with the VCPKG_CHAINLOAD_TOOLCHAIN_FILE option.
set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE $ENV{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake)
set(CMAKE_TOOLCHAIN_FILE $ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
message("vcpkg_android.cmake: CMAKE_TOOLCHAIN_FILE was set to ${CMAKE_TOOLCHAIN_FILE}")
message("vcpkg_android.cmake: VCPKG_CHAINLOAD_TOOLCHAIN_FILE was set to ${VCPKG_CHAINLOAD_TOOLCHAIN_FILE}")
endif(VCPKG_TARGET_ANDROID)

View File

@ -40,10 +40,10 @@ find_cfiles() {
! \( $(get_find_path_args $(trim_comments .clang-format-ignore)) \) "$@"
}
# We're targeting clang-format version 15 and other versions give slightly
# different results, so prefer `clang-format-15` if it's installed.
# We're targeting clang-format version 17 and other versions give slightly
# different results, so prefer `clang-format-17` if it's installed.
clang_format_exe_names=(
'clang-format-15'
'clang-format-17'
'clang-format'
)
for name in ${clang_format_exe_names[@]}; do

View File

@ -52,4 +52,10 @@ foreach(P daemon)
FILES ${TR_NAME}-${P}.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
endif()
if (WITH_SYSTEMD)
install(
FILES ${TR_NAME}-${P}.service
DESTINATION ${CMAKE_INSTALL_LIBDIR}/systemd/system)
endif()
endforeach()

View File

@ -26,7 +26,7 @@
static void set_system_error(tr_error& error, int code, std::string_view message)
{
error.set(code, fmt::format(FMT_STRING("{:s}: {:s} ({:d}"), message, tr_strerror(code), code));
error.set(code, fmt::format("{:s}: {:s} ({:d}", message, tr_strerror(code), code));
}
#ifdef HAVE_SYS_SIGNALFD_H

View File

@ -23,27 +23,29 @@
#define SERVICE_CONTROL_PRESHUTDOWN 0x0000000F
#endif
static LPCWSTR const service_name = L"TransmissionDaemon";
namespace
{
LPCWSTR constexpr service_name = L"TransmissionDaemon";
// If we can get rid of this global variable...
static tr_daemon* daemon;
// ...these becomes a good candidates for being converted to 'class tr_daemon' members.
static SERVICE_STATUS_HANDLE status_handle = nullptr;
static DWORD current_state = SERVICE_STOPPED;
static HANDLE service_thread = nullptr;
static HANDLE service_stop_thread = nullptr;
SERVICE_STATUS_HANDLE status_handle = nullptr;
DWORD current_state = SERVICE_STOPPED;
HANDLE service_thread = nullptr;
HANDLE service_stop_thread = nullptr;
static void set_system_error(tr_error& error, DWORD code, char const* message)
void set_system_error(tr_error& error, DWORD code, char const* message)
{
auto const system_message = tr_win32_format_message(code);
error.set(code, fmt::format(FMT_STRING("{:s} ({:#08x}): {:s})"), message, code, system_message));
error.set(code, fmt::format("{:s} ({:#08x}): {:s})", message, code, system_message));
}
static void do_log_system_error(char const* file, int line, tr_log_level level, DWORD code, char const* message)
void do_log_system_error(char const* file, int line, tr_log_level level, DWORD code, char const* message)
{
auto const system_message = tr_win32_format_message(code);
tr_logAddMessage(file, line, level, "tr_daemon", fmt::format("[tr_daemon] {} ({:#x}): {}", message, code, system_message));
tr_logAddMessage(file, line, level, fmt::format("{} ({:#x}): {}", message, code, system_message), "tr_daemon");
}
#define log_system_error(level, code, message) \
@ -57,13 +59,13 @@ static void do_log_system_error(char const* file, int line, tr_log_level level,
} \
} while (0)
static BOOL WINAPI handle_console_ctrl(DWORD /*control_type*/)
BOOL WINAPI handle_console_ctrl(DWORD /*control_type*/)
{
daemon->stop();
return TRUE;
}
static void update_service_status(
void update_service_status(
DWORD new_state,
DWORD win32_exit_code,
DWORD service_specific_exit_code,
@ -91,7 +93,7 @@ static void update_service_status(
}
}
static unsigned int __stdcall service_stop_thread_main(void* param)
unsigned int __stdcall service_stop_thread_main(void* param)
{
daemon->stop();
@ -107,7 +109,7 @@ static unsigned int __stdcall service_stop_thread_main(void* param)
return 0;
}
static void stop_service(void)
void stop_service(void)
{
if (service_stop_thread != nullptr)
{
@ -128,7 +130,7 @@ static void stop_service(void)
}
}
static DWORD WINAPI handle_service_ctrl(DWORD control_code, DWORD /*event_type*/, LPVOID /*event_data*/, LPVOID /*context*/)
DWORD WINAPI handle_service_ctrl(DWORD control_code, DWORD /*event_type*/, LPVOID /*event_data*/, LPVOID /*context*/)
{
switch (control_code)
{
@ -150,12 +152,12 @@ static DWORD WINAPI handle_service_ctrl(DWORD control_code, DWORD /*event_type*/
return ERROR_CALL_NOT_IMPLEMENTED;
}
static unsigned int __stdcall service_thread_main(void* /*context*/)
unsigned int __stdcall service_thread_main(void* /*context*/)
{
return daemon->start(false);
}
static VOID WINAPI service_main(DWORD /*argc*/, LPWSTR* /*argv*/)
VOID WINAPI service_main(DWORD /*argc*/, LPWSTR* /*argv*/)
{
status_handle = RegisterServiceCtrlHandlerExW(service_name, &handle_service_ctrl, nullptr);
@ -199,6 +201,7 @@ static VOID WINAPI service_main(DWORD /*argc*/, LPWSTR* /*argv*/)
update_service_status(SERVICE_STOPPED, NO_ERROR, exit_code, 0, 0);
}
} // namespace
bool tr_daemon::setup_signals([[maybe_unused]] struct event*& sig_ev)
{

View File

@ -6,7 +6,6 @@
#include <array>
#include <cerrno>
#include <cstdio> /* printf */
#include <cstdlib> /* atoi */
#include <iostream>
#include <iterator> /* std::back_inserter */
#include <memory>
@ -63,21 +62,21 @@ struct tr_torrent;
using namespace std::literals;
using libtransmission::Watchdir;
static char constexpr MyName[] = "transmission-daemon";
static char constexpr Usage[] = "Transmission " LONG_VERSION_STRING
" https://transmissionbt.com/\n"
"A fast and easy BitTorrent client\n"
"\n"
"transmission-daemon is a headless Transmission session that can be\n"
"controlled via transmission-qt, transmission-remote, or its web interface.\n"
"\n"
"Usage: transmission-daemon [options]";
namespace
{
char constexpr MyName[] = "transmission-daemon";
char constexpr Usage[] = "Transmission " LONG_VERSION_STRING
" https://transmissionbt.com/\n"
"A fast and easy BitTorrent client\n"
"\n"
"transmission-daemon is a headless Transmission session that can be\n"
"controlled via transmission-qt, transmission-remote, or its web interface.\n"
"\n"
"Usage: transmission-daemon [options]";
/***
**** Config File
***/
// --- Config File
static auto constexpr Options = std::array<tr_option, 45>{
auto constexpr Options = std::array<tr_option, 45>{
{ { 'a', "allowed", "Allowed IP addresses. (Default: " TR_DEFAULT_RPC_WHITELIST ")", "a", true, "<list>" },
{ 'b', "blocklist", "Enable peer blocklists", "b", false, nullptr },
{ 'B', "no-blocklist", "Disable peer blocklists", "B", false, nullptr },
@ -145,39 +144,7 @@ static auto constexpr Options = std::array<tr_option, 45>{
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
};
bool tr_daemon::reopen_log_file(char const* filename)
{
auto error = tr_error{};
tr_sys_file_t const old_log_file = logfile_;
tr_sys_file_t const new_log_file = tr_sys_file_open(
filename,
TR_SYS_FILE_WRITE | TR_SYS_FILE_CREATE | TR_SYS_FILE_APPEND,
0666,
&error);
if (new_log_file == TR_BAD_SYS_FILE)
{
auto const errmsg = fmt::format(
"Couldn't open '{path}': {error} ({error_code})",
fmt::arg("path", filename),
fmt::arg("error", error.message()),
fmt::arg("error_code", error.code()));
fmt::print(stderr, "{:s}\n", errmsg);
return false;
}
logfile_ = new_log_file;
logfile_flush_ = tr_sys_file_flush_possible(logfile_);
if (old_log_file != TR_BAD_SYS_FILE)
{
tr_sys_file_close(old_log_file);
}
return true;
}
static std::string getConfigDir(int argc, char const* const* argv)
[[nodiscard]] std::string getConfigDir(int argc, char const* const* argv)
{
int c;
char const* optstr;
@ -196,7 +163,7 @@ static std::string getConfigDir(int argc, char const* const* argv)
return tr_getDefaultConfigDir(MyName);
}
static auto onFileAdded(tr_session* session, std::string_view dirname, std::string_view basename)
auto onFileAdded(tr_session* session, std::string_view dirname, std::string_view basename)
{
auto const lowercase = tr_strlower(basename);
auto const is_torrent = tr_strv_ends_with(lowercase, ".torrent"sv);
@ -281,7 +248,7 @@ static auto onFileAdded(tr_session* session, std::string_view dirname, std::stri
return Watchdir::Action::Done;
}
static char const* levelName(tr_log_level level)
[[nodiscard]] constexpr char const* levelName(tr_log_level level)
{
switch (level)
{
@ -300,8 +267,8 @@ static char const* levelName(tr_log_level level)
}
}
static void printMessage(
tr_sys_file_t file,
void printMessage(
FILE* ostream,
tr_log_level level,
std::string_view name,
std::string_view message,
@ -319,11 +286,11 @@ static void printMessage(
fmt::format_to(std::back_inserter(out), "{:s} {:s} ({:s}:{:d})", name, message, filename, line);
}
if (file != TR_BAD_SYS_FILE)
if (ostream != nullptr)
{
auto timestr = std::array<char, 64>{};
tr_logGetTimeStr(std::data(timestr), std::size(timestr));
tr_sys_file_write_line(file, fmt::format("[{:s}] {:s} {:s}", std::data(timestr), levelName(level), std::data(out)));
fmt::print(ostream, "[{:s}] {:s} {:s}\n", std::data(timestr), levelName(level), out.c_str());
}
#ifdef HAVE_SYSLOG
@ -362,24 +329,81 @@ static void printMessage(
#endif
}
static void pumpLogMessages(tr_sys_file_t file, bool flush)
void pumpLogMessages(FILE* log_stream)
{
tr_log_message* list = tr_logGetQueue();
for (tr_log_message const* l = list; l != nullptr; l = l->next)
{
printMessage(file, l->level, l->name, l->message, l->file, l->line);
printMessage(log_stream, l->level, l->name, l->message, l->file, l->line);
}
if (flush && file != TR_BAD_SYS_FILE)
// two reasons to not flush stderr:
// 1. it's usually redundant, since stderr flushes itself
// 2. when running as a systemd unit, it's redirected to a socket
if (log_stream != stderr)
{
tr_sys_file_flush(file);
fflush(log_stream);
}
tr_logFreeQueue(list);
}
void tr_daemon::report_status(void)
void periodic_update(evutil_socket_t /*fd*/, short /*what*/, void* arg)
{
static_cast<tr_daemon*>(arg)->periodic_update();
}
tr_rpc_callback_status on_rpc_callback(tr_session* /*session*/, tr_rpc_callback_type type, tr_torrent* /*tor*/, void* arg)
{
if (type == TR_RPC_SESSION_CLOSE)
{
static_cast<tr_daemon*>(arg)->stop();
}
return TR_RPC_OK;
}
tr_variant load_settings(char const* config_dir)
{
auto app_defaults = tr_variant::make_map();
tr_variantDictAddStr(&app_defaults, TR_KEY_watch_dir, ""sv);
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);
}
} // namespace
bool tr_daemon::reopen_log_file(char const* filename)
{
auto* const old_stream = log_stream_;
auto* new_stream = std::fopen(filename, "a");
if (new_stream == nullptr)
{
auto const err = errno;
auto const errmsg = fmt::format(
"Couldn't open '{path}': {error} ({error_code})",
fmt::arg("path", filename),
fmt::arg("error", tr_strerror(err)),
fmt::arg("error_code", err));
fmt::print(stderr, "{:s}\n", errmsg);
return false;
}
log_stream_ = new_stream;
if (old_stream != nullptr && old_stream != stderr)
{
fclose(old_stream);
}
return true;
}
void tr_daemon::report_status()
{
double const up = tr_sessionGetRawSpeed_KBps(my_session_, TR_UP);
double const dn = tr_sessionGetRawSpeed_KBps(my_session_, TR_DOWN);
@ -394,36 +418,17 @@ void tr_daemon::report_status(void)
}
}
void tr_daemon::periodic_update(void)
void tr_daemon::periodic_update()
{
pumpLogMessages(logfile_, logfile_flush_);
pumpLogMessages(log_stream_);
report_status();
}
static void periodic_update(evutil_socket_t /*fd*/, short /*what*/, void* arg)
{
static_cast<tr_daemon*>(arg)->periodic_update();
}
static tr_rpc_callback_status on_rpc_callback(
tr_session* /*session*/,
tr_rpc_callback_type type,
tr_torrent* /*tor*/,
void* arg)
{
if (type == TR_RPC_SESSION_CLOSE)
{
static_cast<tr_daemon*>(arg)->stop();
}
return TR_RPC_OK;
}
bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_settings, bool* foreground, int* exit_code)
{
int c;
char const* optstr;
paused_ = false;
*dump_settings = false;
*foreground = false;
@ -501,7 +506,10 @@ bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_setting
break;
case 'p':
tr_variantDictAddInt(&settings_, TR_KEY_rpc_port, atoi(optstr));
if (auto const rpc_port = tr_num_parse<uint16_t>(optstr); rpc_port)
{
tr_variantDictAddInt(&settings_, TR_KEY_rpc_port, *rpc_port);
}
break;
case 't':
@ -525,7 +533,10 @@ bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_setting
break;
case 'P':
tr_variantDictAddInt(&settings_, TR_KEY_peer_port, atoi(optstr));
if (auto const peer_port = tr_num_parse<uint16_t>(optstr); peer_port)
{
tr_variantDictAddInt(&settings_, TR_KEY_peer_port, *peer_port);
}
break;
case 'm':
@ -537,15 +548,21 @@ bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_setting
break;
case 'L':
tr_variantDictAddInt(&settings_, TR_KEY_peer_limit_global, atoi(optstr));
if (auto const peer_limit_global = tr_num_parse<int64_t>(optstr); peer_limit_global && *peer_limit_global >= 0)
{
tr_variantDictAddInt(&settings_, TR_KEY_peer_limit_global, *peer_limit_global);
}
break;
case 'l':
tr_variantDictAddInt(&settings_, TR_KEY_peer_limit_per_torrent, atoi(optstr));
if (auto const peer_limit_tor = tr_num_parse<int64_t>(optstr); peer_limit_tor && *peer_limit_tor >= 0)
{
tr_variantDictAddInt(&settings_, TR_KEY_peer_limit_per_torrent, *peer_limit_tor);
}
break;
case 800:
paused_ = true;
tr_variantDictAddBool(&settings_, TR_KEY_start_paused, true);
break;
case 910:
@ -573,7 +590,10 @@ bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_setting
break;
case 953:
tr_variantDictAddReal(&settings_, TR_KEY_ratio_limit, atof(optstr));
if (auto const ratio_limit = tr_num_parse<double>(optstr); optstr)
{
tr_variantDictAddReal(&settings_, TR_KEY_ratio_limit, *ratio_limit);
}
tr_variantDictAddBool(&settings_, TR_KEY_ratio_limit_enabled, true);
break;
@ -582,7 +602,7 @@ bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_setting
break;
case 'x':
tr_variantDictAddStr(&settings_, key_pidfile_, optstr);
tr_variantDictAddStr(&settings_, TR_KEY_pidfile, optstr);
break;
case 'y':
@ -643,7 +663,7 @@ bool tr_daemon::parse_args(int argc, char const* const* argv, bool* dump_setting
return true;
}
void tr_daemon::reconfigure(void)
void tr_daemon::reconfigure()
{
if (my_session_ == nullptr)
{
@ -663,35 +683,24 @@ void tr_daemon::reconfigure(void)
configDir = tr_sessionGetConfigDir(my_session_);
tr_logAddInfo(fmt::format(_("Reloading settings from '{path}'"), fmt::arg("path", configDir)));
auto newsettings = tr_variant::make_map();
tr_variantDictAddBool(&newsettings, TR_KEY_rpc_enabled, true);
newsettings.merge(tr_sessionLoadSettings(configDir, MyName));
tr_sessionSet(my_session_, newsettings);
tr_sessionSet(my_session_, load_settings(configDir));
tr_sessionReloadBlocklists(my_session_);
}
}
void tr_daemon::stop(void)
void tr_daemon::stop()
{
event_base_loopexit(ev_base_, nullptr);
}
int tr_daemon::start([[maybe_unused]] bool foreground)
{
bool boolVal;
bool pidfile_created = false;
tr_session* session = nullptr;
struct event* status_ev = nullptr;
struct event* sig_ev = nullptr;
auto watchdir = std::unique_ptr<Watchdir>{};
char const* const cdir = this->config_dir_.c_str();
sd_notifyf(0, "MAINPID=%d\n", (int)getpid());
/* setup event state */
ev_base_ = event_base_new();
event* sig_ev = nullptr;
if (ev_base_ == nullptr || !setup_signals(sig_ev))
{
auto const error_code = errno;
@ -699,20 +708,22 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
_("Couldn't initialize daemon: {error} ({error_code})"),
fmt::arg("error", tr_strerror(error_code)),
fmt::arg("error_code", error_code));
printMessage(logfile_, TR_LOG_ERROR, MyName, errmsg, __FILE__, __LINE__);
printMessage(log_stream_, TR_LOG_ERROR, MyName, errmsg, __FILE__, __LINE__);
cleanup_signals(sig_ev);
return 1;
}
/* start the session */
session = tr_sessionInit(cdir, true, settings_);
auto const* const cdir = this->config_dir_.c_str();
auto* session = tr_sessionInit(cdir, true, settings_);
tr_sessionSetRPCCallback(session, on_rpc_callback, this);
tr_logAddInfo(fmt::format(_("Loading settings from '{path}'"), fmt::arg("path", cdir)));
tr_sessionSaveSettings(session, cdir, settings_);
auto sv = std::string_view{};
(void)tr_variantDictFindStrView(&settings_, key_pidfile_, &sv);
(void)tr_variantDictFindStrView(&settings_, TR_KEY_pidfile, &sv);
auto const sz_pid_filename = std::string{ sv };
auto pidfile_created = false;
if (!std::empty(sz_pid_filename))
{
auto error = tr_error{};
@ -740,7 +751,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
}
}
if (tr_variantDictFindBool(&settings_, TR_KEY_rpc_authentication_required, &boolVal) && boolVal)
if (auto tmp_bool = false; tr_variantDictFindBool(&settings_, TR_KEY_rpc_authentication_required, &tmp_bool) && tmp_bool)
{
tr_logAddInfo(_("Requiring authentication"));
}
@ -754,10 +765,11 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
}
/* maybe add a watchdir */
if (tr_variantDictFindBool(&settings_, TR_KEY_watch_dir_enabled, &boolVal) && boolVal)
auto watchdir = std::unique_ptr<Watchdir>{};
if (auto tmp_bool = false; tr_variantDictFindBool(&settings_, TR_KEY_watch_dir_enabled, &tmp_bool) && tmp_bool)
{
auto force_generic = bool{ false };
(void)tr_variantDictFindBool(&settings_, key_watch_dir_force_generic_, &force_generic);
auto force_generic = false;
(void)tr_variantDictFindBool(&settings_, TR_KEY_watch_dir_force_generic, &force_generic);
auto dir = std::string_view{};
(void)tr_variantDictFindStrView(&settings_, TR_KEY_watch_dir, &dir);
@ -780,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);
}
@ -799,6 +811,7 @@ int tr_daemon::start([[maybe_unused]] bool foreground)
#endif
/* Create new timer event to report daemon status */
event* status_ev;
{
constexpr auto one_sec = timeval{ 1, 0 }; // 1 second
status_ev = event_new(ev_base_, -1, EV_PERSIST, &::periodic_update, this);
@ -855,7 +868,7 @@ CLEANUP:
tr_sessionSaveSettings(my_session_, cdir, settings_);
tr_sessionClose(my_session_);
pumpLogMessages(logfile_, logfile_flush_);
pumpLogMessages(log_stream_);
printf(" done.\n");
/* shutdown */
@ -885,9 +898,7 @@ bool tr_daemon::init(int argc, char const* const argv[], bool* foreground, int*
config_dir_ = getConfigDir(argc, argv);
/* load settings from defaults + config file */
settings_ = tr_variant::make_map();
tr_variantDictAddBool(&settings_, TR_KEY_rpc_enabled, true);
settings_.merge(tr_sessionLoadSettings(config_dir_.c_str(), MyName));
settings_ = load_settings(config_dir_.c_str());
bool dumpSettings;
@ -896,31 +907,27 @@ bool tr_daemon::init(int argc, char const* const argv[], bool* foreground, int*
/* overwrite settings from the command line */
if (!parse_args(argc, argv, &dumpSettings, foreground, ret))
{
goto EXIT_EARLY;
return false;
}
if (*foreground && logfile_ == TR_BAD_SYS_FILE)
if (*foreground && log_stream_ == nullptr)
{
logfile_ = tr_sys_file_get_std(TR_STD_SYS_FILE_ERR);
logfile_flush_ = tr_sys_file_flush_possible(logfile_);
log_stream_ = stderr;
}
if (dumpSettings)
{
fmt::print("{:s}\n", tr_variant_serde::json().to_string(settings_));
goto EXIT_EARLY;
return false;
}
return true;
EXIT_EARLY:
return false;
}
void tr_daemon::handle_error(tr_error const& error) const
{
auto const errmsg = fmt::format("Couldn't daemonize: {:s} ({:d})", error.message(), error.code());
printMessage(logfile_, TR_LOG_ERROR, MyName, errmsg, __FILE__, __LINE__);
printMessage(log_stream_, TR_LOG_ERROR, MyName, errmsg, __FILE__, __LINE__);
}
int tr_main(int argc, char* argv[])

View File

@ -5,6 +5,7 @@
#pragma once
#include <cstdio>
#include <string>
#ifdef HAVE_SYS_SIGNALFD_H
@ -13,8 +14,8 @@
#include <libtransmission/variant.h>
#include <libtransmission/quark.h>
#include <libtransmission/file.h>
struct event_base;
struct tr_error;
struct tr_session;
@ -45,17 +46,13 @@ 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_ = {};
bool logfile_flush_ = false;
tr_session* my_session_ = nullptr;
char const* log_file_name_ = nullptr;
struct event_base* ev_base_ = nullptr;
tr_sys_file_t logfile_ = TR_BAD_SYS_FILE;
tr_quark key_pidfile_ = tr_quark_new("pidfile");
tr_quark key_watch_dir_force_generic_ = tr_quark_new("watch-dir-force-generic");
FILE* log_stream_ = nullptr;
bool parse_args(int argc, char const* const* argv, bool* dump_settings, bool* foreground, int* exit_code);
bool reopen_log_file(char const* filename);

View File

@ -8,10 +8,31 @@ User=transmission
Type=notify
ExecStart=/usr/bin/transmission-daemon -f --log-level=error
ExecReload=/bin/kill -s HUP $MAINPID
# Hardening
CapabilityBoundingSet=
DevicePolicy=closed
KeyringMode=private
LockPersonality=true
NoNewPrivileges=true
MemoryDenyWriteExecute=true
ProtectSystem=true
PrivateTmp=true
PrivateDevices=true
ProtectClock=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectKernelModules=true
ProtectSystem=true
ProtectHostname=true
ProtectKernelTunables=true
ProtectProc=invisible
RestrictNamespaces=true
RestrictSUIDSGID=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictRealtime=true
SystemCallFilter=@system-service
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
[Install]
WantedBy=multi-user.target

View File

@ -7,7 +7,7 @@ Software prerequisites:
* Xcode 11.3.1 or newer
Building the project on Mac requires the source to be retrieved from GitHub. Pre-packaged source code will not compile.
```console
```bash
git clone --recurse-submodules https://github.com/transmission/transmission Transmission
```
@ -21,7 +21,7 @@ Transmission has an Xcode project file for building in Xcode.
### Building the native app with CMake ###
Build the app:
```console
```bash
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo
cmake --build build -t transmission-mac
open ./build/macosx/Transmission.app
@ -29,7 +29,7 @@ open ./build/macosx/Transmission.app
### Building the GTK app with CMake ###
Install GTK and build the app:
```console
```bash
brew install gtk4 gtkmm4
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_GTK=ON -DENABLE_MAC=OFF
cmake --build build -t transmission-gtk
@ -39,20 +39,52 @@ cmake --build build -t transmission-gtk
## On Unix ##
### Prerequisites ###
#### Debian 11 and Newer ####
#### Debian 12 / Bookworm ####
On Debian, you can build transmission with a few dependencies on top of a base installation.
For building transmission-daemon you will need basic dependencies:
```bash
$ sudo apt install build-essential cmake git libcurl4-openssl-dev libssl-dev
```
These packages are not mandatory for a working binary. Transmission brings its own libraries if they aren't installed, except for `libsystemd-dev`.
```bash
$ sudo apt install libb64-dev libdeflate-dev libevent-dev libminiupnpc-dev libnatpmp-dev libpsl-dev libsystemd-dev
```
You likely want to install transmission as a native GUI application.
There are two options, GTK and Qt.
GTK 3 client:
```bash
$ sudo apt install gettext libgtkmm-3.0-dev
```
Qt5 client:
```bash
$ sudo apt install libqt5svg5-dev qttools5-dev
```
Qt6 client:
```bash
$ sudo apt install qt6-svg-dev qt6-tools-dev
```
Then you can begin [building.](#building-transmission-from-git-first-time)
#### Debian 11 / Bullseye ####
On Debian, you can build transmission with a few dependencies on top of a base installation.
For building transmission-daemon you will need basic dependencies
```bash
$ sudo apt install git build-essential cmake libcurl4-openssl-dev libssl-dev python3
```
You likely want to install transmission as a native GUI application. There are two options, GTK and QT.
You likely want to install transmission as a native GUI application. There are two options, GTK and Qt.
For GTK 3 client, two additional packages are required
```bash
$ sudo apt install libgtkmm-3.0-dev gettext
```
For QT client, one additional package is needed on top of basic dependencies
For Qt client, one additional package is needed on top of basic dependencies
```bash
$ sudo apt install qttools5-dev
```

View File

@ -54,7 +54,7 @@ Here is a sample of the three basic types: respectively Boolean, Number and Stri
* **blocklist-url:** String (default = https://www.example.com/blocklist)
* **blocklist-enabled:** Boolean (default = false)
#### [Files and Locations](./ConfigFiles.md)
#### [Files and Locations](./Configuration-Files.md)
* **download-dir:** String (default = [default locations](Configuration-Files.md#Locations))
* **incomplete-dir:** String (default = [default locations](Configuration-Files.md#Locations)) Directory to keep files in until torrent is complete.
@ -76,27 +76,27 @@ Here is a sample of the three basic types: respectively Boolean, Number and Stri
* **default-trackers:** String (default = "") A list of double-newline separated tracker announce URLs. These are used for all torrents in addition to the per torrent trackers specified in the torrent file. If a tracker is only meant to be a backup, it should be separated from its main tracker by a single newline character. If a tracker should be used additionally to another tracker it should be separated by two newlines. (e.g. "udp://tracker.example.invalid:1337/announce\n\nudp://tracker.another-example.invalid:6969/announce\nhttps://backup-tracker.another-example.invalid:443/announce\n\nudp://tracker.yet-another-example.invalid:1337/announce", in this case tracker.example.invalid, tracker.another-example.invalid and tracker.yet-another-example.invalid would be used as trackers and backup-tracker.another-example.invalid as backup in case tracker.another-example.invalid is unreachable.
* **dht-enabled:** Boolean (default = true) Enable [Distributed Hash Table (DHT)](https://wiki.theory.org/BitTorrentSpecification#Distributed_Hash_Table).
* **encryption:** Number (0 = Prefer unencrypted connections, 1 = Prefer encrypted connections, 2 = Require encrypted connections; default = 1) [Encryption](https://wiki.vuze.com/w/Message_Stream_Encryption) preference. Encryption may help get around some ISP filtering, but at the cost of slightly higher CPU use.
* **lazy-bitfield-enabled:** Boolean (default = true) May help get around some ISP filtering. [Vuze specification](https://wiki.vuze.com/w/Commandline_options#Network_Options).
* **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)
* **prefetch-enabled:** Boolean (default = true). When enabled, Transmission will hint to the OS which piece data it's about to read from disk in order to satisfy requests from peers. On Linux, this is done by passing `POSIX_FADV_WILLNEED` to [posix_fadvise()](https://www.kernel.org/doc/man-pages/online/pages/man2/posix_fadvise.2.html). On macOS, this is done by passing `F_RDADVISE` to [fcntl()](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fcntl.2.html).
* **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)
* **preferred-transport:** String ("utp" = Prefer µTP, "tcp" = Prefer TCP; default = "utp") Choose your preferred transport protocol (has no effect if one of them is disabled).
* **sleep-per-seconds-during-verify:** Number (default = 100) Controls the duration in milliseconds for which the verification process will pause to reduce disk I/O pressure.
#### Peers
* **bind-address-ipv4:** String (default = "0.0.0.0") Where to listen for peer connections. When no valid IPv4 address is provided, Transmission will bind to "0.0.0.0".
* **bind-address-ipv6:** String (default = "::") Where to listen for peer connections. When no valid IPv6 address is provided, Transmission will try to bind to your default global IPv6 address. If that didn't work, then Transmission will bind to "::".
* **bind-address-ipv4:** String (default = "") Where to listen for peer connections. When no valid IPv4 address is provided, Transmission will bind to "0.0.0.0".
* **bind-address-ipv6:** String (default = "") Where to listen for peer connections. When no valid IPv6 address is provided, Transmission will try to bind to your default global IPv6 address. If that didn't work, then Transmission will bind to "::".
* **peer-congestion-algorithm:** String. This is documented on https://www.pps.jussieu.fr/~jch/software/bittorrent/tcp-congestion-control.html.
* **peer-limit-global:** Number (default = 240)
* **peer-limit-per-torrent:** Number (default = 60)
@ -122,7 +122,7 @@ Here is a sample of the three basic types: respectively Boolean, Number and Stri
* **anti-brute-force-threshold:**: Number (default = 100) After this amount of failed authentication attempts is surpassed, the RPC server will deny any further authentication attempts until it is restarted. This is not tracked per IP but in total.
* **rpc-authentication-required:** Boolean (default = false)
* **rpc-bind-address:** String (default = "0.0.0.0") Where to listen for RPC connections
* **rpc-enabled:** Boolean (default = true)
* **rpc-enabled:** Boolean (default = true \[transmission-daemon\], false \[others\])
* **rpc-host-whitelist:** String (Comma-delimited list of domain names. Wildcards allowed using '\*'. Example: "*.foo.org,example.com", Default: "", Always allowed: "localhost", "localhost.", all the IP addresses. Added in v2.93)
* **rpc-host-whitelist-enabled:** Boolean (default = true. Added in v2.93)
* **rpc-password:** String. You can enter this in as plaintext when Transmission is not running, and then Transmission will salt the value on startup and re-save the salted version as a security measure. **Note:** Transmission treats passwords starting with the character `{` as salted, so when you first create your password, the plaintext password you enter must not begin with `{`.

View File

@ -1,10 +1,13 @@
**We strongly recommend that you use the [current version](https://github.com/transmission/transmission/releases/latest) of Transmission unless you have a legacy operating system version shown below.**
If you have a legacy operating system version, this table shows the best Transmission version to use:
* **Mac OS X 10.7 (Lion), 10.8 (Mountain Lion), 10.9 (Mavericks)**
* **OS X 10.10 (Yosemite), OS X 10.11 (El Capitan), macOS 10.12 (Sierra)**
Latest Version: [Transmission 3.00](https://github.com/transmission/transmission-releases/raw/master/Transmission-3.00.dmg)
SHA256 hash: [`f9984b6ba51a02bb8f880c538b28e2c7d6a3b7a22257a166cc3e1d55a133ab34`](https://www.virustotal.com/gui/file/f9984b6ba51a02bb8f880c538b28e2c7d6a3b7a22257a166cc3e1d55a133ab34/detection)
* **Mac OS X 10.7 (Lion), OS X 10.8 (Mountain Lion), OS X 10.9 (Mavericks)**
Latest version: [Transmission 2.94](https://github.com/transmission/transmission-releases/raw/master/Transmission-2.94.dmg)
SHA256 hash: [`2cae915ae0e37fc5983406ca7fbd53a054a7153d3bfd7a6cef117a8a28d8a78a`](https://www.virustotal.com/gui/file/2cae915ae0e37fc5983406ca7fbd53a054a7153d3bfd7a6cef117a8a28d8a78a/detection)
* **Mac OS X 10.6 Intel (Snow Leopard)**
Latest version: [Transmission 2.84](https://github.com/transmission/transmission-releases/raw/master/Transmission-2.84.dmg)
SHA256 hash: [`53d08a55a5ca55010d409acb10f0285a649b8879085cad83f2cbcb7faa489ad5`](https://www.virustotal.com/en/file/53d08a55a5ca55010d409acb10f0285a649b8879085cad83f2cbcb7faa489ad5/analysis/)

View File

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

View File

@ -37,6 +37,8 @@ The file contains the following **per-torrent** properties:
<tr><td><tt>bitfield</tt></td><td></td></tr>
</table>
The file format is bencoding, as described in [bep_0003](https://www.bittorrent.org/beps/bep_0003.html).
## Constants
<table>
<tr><td>Maximum number of remembered peers</td><td><tt>MAX_REMEMBERED_PEERS</tt></td><td>200</td></tr>

View File

@ -259,8 +259,8 @@ The 'source' column here corresponds to the data structure there.
| `priorities`| array (see below)| n/a
| `primary-mime-type`| string| tr_torrent
| `queuePosition`| number| tr_stat
| `rateDownload (B/s)`| number| tr_stat
| `rateUpload (B/s)`| number| tr_stat
| `rateDownload` (B/s)| number| tr_stat
| `rateUpload` (B/s)| number| tr_stat
| `recheckProgress`| double| tr_stat
| `secondsDownloading`| number| tr_stat
| `secondsSeeding`| number| tr_stat
@ -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:
@ -673,7 +675,7 @@ Response arguments:
| Key | Value Type | Description
| :-- | :-- | :--
| `port-is-open` | boolean | true if port is open, false if port is closed
| `ipProtocol` | string | `ipv4` if the test was carried out on IPv4, `ipv6` if the test was carried out on IPv6, unset if an error occured
| `ipProtocol` | string | `ipv4` if the test was carried out on IPv4, `ipv6` if the test was carried out on IPv6, unset if it cannot be determined
### 4.5 Session shutdown
This method tells the transmission session to shut down.

View File

@ -11,7 +11,7 @@ IncludeCategories:
SortPriority: 4
- Regex: '^<(cairo|gdk|gio|glib|gtk|pango)mm([-./]|config)'
Priority: 5
- Regex: '^<(fmt)/'
- Regex: '^<(fmt|small)/'
Priority: 6
- Regex: '^<(cairo|gdk|gio|glib|gtk|pango)[-./]'
Priority: 8

View File

@ -7,11 +7,13 @@ Checks: >
-bugprone-narrowing-conversions,
cert-*,
cppcoreguidelines-*,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-narrowing-conversions,
hicpp-*,
misc-*,
-misc-include-cleaner,
modernize-*,
-modernize-use-trailing-return-type,
performance-*,
@ -19,8 +21,10 @@ Checks: >
-readability-function-cognitive-complexity,
-readability-identifier-length,
-readability-magic-numbers,
-readability-qualified-auto,
-readability-redundant-access-specifiers
CheckOptions:
misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: true
modernize-pass-by-value.ValuesOnly: true
- { key: cppcoreguidelines-avoid-do-while.IgnoreMacros, value: true }
- { key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic, value: true }
- { key: modernize-pass-by-value.ValuesOnly, value: true }

View File

@ -10,24 +10,21 @@
#include "Session.h"
#include "Utils.h"
#include <libtransmission/transmission.h>
#include <libtransmission/quark.h>
#include <giomm/liststore.h>
#include <giomm/menuattributeiter.h>
#include <giomm/menulinkiter.h>
#include <giomm/simpleaction.h>
#include <glibmm/i18n.h>
#include <glibmm/variant.h>
#include <array>
#include <stack>
#include <string>
#include <string_view>
#include <unordered_map>
#if GTKMM_CHECK_VERSION(4, 0, 0)
#include <giomm/liststore.h>
#include <giomm/menuattributeiter.h>
#include <giomm/menulinkiter.h>
#include <gtkmm/shortcut.h>
#include <gtkmm/shortcutaction.h>
#include <gtkmm/shortcuttrigger.h>

View File

@ -52,6 +52,8 @@
#include <gtkmm/stylecontext.h>
#include <gtkmm/window.h>
#include <small/set.hpp>
#if GTKMM_CHECK_VERSION(4, 0, 0)
#include <gtkmm/droptarget.h>
#include <gtkmm/eventcontrollerfocus.h>
@ -70,11 +72,9 @@
#include <iterator> // std::back_inserter
#include <map>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <thread>
#include <tuple>
#include <utility>
#include <vector>
@ -101,7 +101,7 @@ namespace
auto const AppIconName = "transmission"sv; // TODO(C++20): Use ""s
char const* const LICENSE =
"Copyright 2005-2023. All code is copyrighted by the respective authors.\n"
"Copyright 2005-2024. All code is copyrighted by the respective authors.\n"
"\n"
"Transmission can be redistributed and/or modified under the terms of the "
"GNU GPL, versions 2 or 3, or by any future license endorsed by Mnemosyne LLC."
@ -479,11 +479,13 @@ bool Application::Impl::on_rpc_changed_idle(tr_rpc_callback_type type, tr_torren
auto const newvals = tr_sessionGetSettings(session);
// determine which settings changed
auto changed_keys = std::set<tr_quark>{};
auto changed_keys = small::set<tr_quark>{};
auto& oldvals = gtr_pref_get_all();
auto const serde = tr_variant_serde::benc();
if (auto const* const newvals_map = newvals.get_if<tr_variant::Map>(); newvals_map != nullptr)
{
changed_keys.reserve(std::size(*newvals_map));
for (auto const& [key, newval] : *newvals_map)
{
bool changed = true;
@ -1013,7 +1015,7 @@ void Application::Impl::on_app_exit()
p->attach(*icon, 0, 0, 1, 2);
auto* top_label = Gtk::make_managed<Gtk::Label>();
top_label->set_markup(fmt::format(FMT_STRING("<b>{:s}</b>"), _("Closing Connections…")));
top_label->set_markup(fmt::format("<b>{:s}</b>", _("Closing Connections…")));
top_label->set_halign(TR_GTK_ALIGN(START));
top_label->set_valign(TR_GTK_ALIGN(CENTER));
p->attach(*top_label, 1, 0, 1, 1);
@ -1451,7 +1453,7 @@ bool Application::Impl::call_rpc_for_selected_torrents(std::string const& method
if (tr_variantListSize(ids) != 0)
{
tr_rpc_request_exec_json(session, &top, nullptr, nullptr);
tr_rpc_request_exec(session, top, {});
invoked = true;
}
@ -1473,7 +1475,7 @@ void Application::Impl::start_all_torrents()
tr_variantInitDict(&request, 1);
tr_variantDictAddStrView(&request, TR_KEY_method, "torrent-start"sv);
tr_rpc_request_exec_json(session, &request, nullptr, nullptr);
tr_rpc_request_exec(session, request, {});
}
void Application::Impl::pause_all_torrents()
@ -1483,7 +1485,7 @@ void Application::Impl::pause_all_torrents()
tr_variantInitDict(&request, 1);
tr_variantDictAddStrView(&request, TR_KEY_method, "torrent-stop"sv);
tr_rpc_request_exec_json(session, &request, nullptr, nullptr);
tr_rpc_request_exec(session, request, {});
}
void Application::Impl::copy_magnet_link_to_clipboard(Glib::RefPtr<Torrent> const& torrent) const
@ -1517,7 +1519,7 @@ void Application::Impl::actions_handler(Glib::ustring const& action_name)
else if (action_name == "open-torrent")
{
auto w = std::shared_ptr<TorrentFileChooserDialog>(TorrentFileChooserDialog::create(*wind_, core_));
gtr_window_on_close(*w, [w]() mutable { w.reset(); });
w->signal_response().connect([w](int /*response*/) mutable { w.reset(); });
w->show();
}
else if (action_name == "show-stats")

View File

@ -53,6 +53,7 @@
#include <array>
#include <cstddef>
#include <cstdlib> // abort()
#include <iterator>
#include <limits>
#include <memory>
#include <numeric>
@ -446,7 +447,7 @@ void DetailsDialog::Impl::torrent_set_bool(tr_quark key, bool value)
tr_variantListAddInt(ids, id);
}
core_->exec(&top);
core_->exec(top);
}
void DetailsDialog::Impl::torrent_set_int(tr_quark key, int value)
@ -464,7 +465,7 @@ void DetailsDialog::Impl::torrent_set_int(tr_quark key, int value)
tr_variantListAddInt(ids, id);
}
core_->exec(&top);
core_->exec(top);
}
void DetailsDialog::Impl::torrent_set_real(tr_quark key, double value)
@ -482,7 +483,7 @@ void DetailsDialog::Impl::torrent_set_real(tr_quark key, double value)
tr_variantListAddInt(ids, id);
}
core_->exec(&top);
core_->exec(top);
}
void DetailsDialog::Impl::options_page_init(Glib::RefPtr<Gtk::Builder> const& /*builder*/)
@ -607,12 +608,12 @@ void gtr_text_buffer_set_text(Glib::RefPtr<Gtk::TextBuffer> const& b, Glib::ustr
[[nodiscard]] std::string get_date_string(time_t t)
{
return t == 0 ? _("N/A") : fmt::format(FMT_STRING("{:%x}"), fmt::localtime(t));
return t == 0 ? _("N/A") : fmt::format("{:%x}", fmt::localtime(t));
}
[[nodiscard]] std::string get_date_time_string(time_t t)
{
return t == 0 ? _("N/A") : fmt::format(FMT_STRING("{:%c}"), fmt::localtime(t));
return t == 0 ? _("N/A") : fmt::format("{:%c}", fmt::localtime(t));
}
} // namespace
@ -1326,7 +1327,7 @@ void DetailsDialog::Impl::refreshPeerList(std::vector<tr_torrent*> const& torren
auto make_key = [](tr_torrent const* tor, tr_peer_stat const* ps)
{
return fmt::format(FMT_STRING("{:d}.{:s}"), tr_torrentId(tor), ps->addr);
return fmt::format("{:d}.{:s}", tr_torrentId(tor), ps->addr);
};
/* step 3: add any new peers */
@ -1388,13 +1389,13 @@ void DetailsDialog::Impl::refreshPeerList(std::vector<tr_torrent*> const& torren
void DetailsDialog::Impl::refreshWebseedList(std::vector<tr_torrent*> const& torrents)
{
auto has_any_webseeds = bool{ false };
auto has_any_webseeds = false;
auto& hash = webseed_hash_;
auto const& store = webseed_store_;
auto make_key = [](tr_torrent const* tor, char const* url)
{
return fmt::format(FMT_STRING("{:d}.{:s}"), tr_torrentId(tor), url);
return fmt::format("{:d}.{:s}", tr_torrentId(tor), url);
};
/* step 1: mark all webseeds as not-updated */
@ -1956,7 +1957,7 @@ void buildTrackerSummary(
gstr << text_dir_mark.at(static_cast<int>(direction));
gstr << (tracker.isBackup ? "<i>" : "<b>");
gstr << Glib::Markup::escape_text(
!key.empty() ? fmt::format(FMT_STRING("{:s} - {:s}"), tracker.host_and_port, key) : tracker.host_and_port);
!key.empty() ? fmt::format("{:s} - {:s}", tracker.host_and_port, key) : tracker.host_and_port);
gstr << (tracker.isBackup ? "</i>" : "</b>");
if (!tracker.isBackup)
@ -2373,7 +2374,7 @@ void AddTrackerDialog::on_response(int response)
auto* const trackers = tr_variantDictAddList(args, TR_KEY_trackerAdd, 1);
tr_variantListAddStr(trackers, url.raw());
core_->exec(&top);
core_->exec(top);
parent_.refresh();
}
else
@ -2420,7 +2421,7 @@ void DetailsDialog::Impl::on_tracker_list_remove_button_clicked()
auto* const trackers = tr_variantDictAddList(args, TR_KEY_trackerRemove, 1);
tr_variantListAddInt(trackers, tracker_id);
core_->exec(&top);
core_->exec(top);
refresh();
}
}

View File

@ -80,7 +80,7 @@ public:
}
private:
PropertyInfo const& get_property(PropertyType index) const noexcept
[[nodiscard]] PropertyInfo const& get_property(PropertyType index) const noexcept
{
auto const id = static_cast<PropertyIdType>(index);
g_assert(id > 0);

View File

@ -35,9 +35,9 @@
#include <fmt/core.h>
#include <algorithm>
#include <cstddef>
#include <list>
#include <memory>
#include <optional>
#include <queue>
#include <stack>
#include <string>
@ -50,7 +50,7 @@ using namespace std::literals;
namespace
{
enum
enum : uint16_t
{
/* these two fields could be any number at all so long as they're not
* TR_PRI_LOW, TR_PRI_NORMAL, TR_PRI_HIGH, true, or false */
@ -285,7 +285,7 @@ bool refreshFilesForeach(
if (new_progress != old_progress)
{
(*iter)[file_cols.prog] = new_progress;
(*iter)[file_cols.prog_str] = fmt::format(FMT_STRING("{:d}%"), new_progress);
(*iter)[file_cols.prog_str] = fmt::format("{:d}%", new_progress);
}
return false; /* keep walking */
@ -659,7 +659,7 @@ void renderPriority(Gtk::CellRenderer* renderer, Gtk::TreeModel::const_iterator
}
/* build a filename from tr_torrentGetCurrentDir() + the model's FC_LABELs */
std::string buildFilename(tr_torrent const* tor, Gtk::TreeModel::iterator const& iter)
std::string build_filename(tr_torrent const* tor, Gtk::TreeModel::iterator const& iter)
{
std::vector<std::string> tokens;
for (auto child = iter; child; child = child->parent())
@ -672,37 +672,52 @@ std::string buildFilename(tr_torrent const* tor, Gtk::TreeModel::iterator const&
return Glib::build_filename(tokens);
}
std::optional<std::string> get_filename_to_open(tr_torrent const* tor, Gtk::TreeModel::iterator const& iter)
{
auto file = Gio::File::create_for_path(build_filename(tor, iter));
// if the selected file is complete, use it
if (iter->get_value(file_cols.prog) == 100 && file->query_exists())
{
return file->get_path();
}
// use nearest existing ancestor instead
for (;;)
{
file = file->get_parent();
if (!file)
{
return {};
}
if (file->query_exists())
{
return file->get_path();
}
}
}
} // namespace
void FileList::Impl::onRowActivated(Gtk::TreeModel::Path const& path, Gtk::TreeViewColumn* /*col*/)
{
bool handled = false;
if (auto const* tor = core_->find_torrent(torrent_id_); tor != nullptr)
auto const* const tor = core_->find_torrent(torrent_id_);
if (tor == nullptr)
{
if (auto const iter = store_->get_iter(path); iter)
{
auto filename = buildFilename(tor, iter);
auto const prog = iter->get_value(file_cols.prog);
/* if the file's not done, walk up the directory tree until we find
* an ancestor that exists, and open that instead */
if (!filename.empty() && (prog < 100 || !Glib::file_test(filename, TR_GLIB_FILE_TEST(EXISTS))))
{
do
{
filename = Glib::path_get_dirname(filename);
} while (!filename.empty() && !Glib::file_test(filename, TR_GLIB_FILE_TEST(EXISTS)));
}
if (handled = !filename.empty(); handled)
{
gtr_open_file(filename);
}
}
return;
}
// return handled;
auto const iter = store_->get_iter(path);
if (!iter)
{
return;
}
if (auto const filename = get_filename_to_open(tor, iter); filename)
{
gtr_open_file(*filename);
}
}
bool FileList::Impl::onViewPathToggled(Gtk::TreeViewColumn* col, Gtk::TreeModel::Path const& path)
@ -725,24 +740,25 @@ bool FileList::Impl::onViewPathToggled(Gtk::TreeViewColumn* col, Gtk::TreeModel:
if (cid == file_cols.priority.index())
{
auto priority = iter->get_value(file_cols.priority);
auto const old_priority = iter->get_value(file_cols.priority);
auto new_priority = TR_PRI_NORMAL;
switch (priority)
switch (old_priority)
{
case TR_PRI_NORMAL:
priority = TR_PRI_HIGH;
new_priority = TR_PRI_HIGH;
break;
case TR_PRI_HIGH:
priority = TR_PRI_LOW;
new_priority = TR_PRI_LOW;
break;
default:
priority = TR_PRI_NORMAL;
new_priority = TR_PRI_NORMAL;
break;
}
tr_torrentSetFilePriorities(tor, indexBuf.data(), indexBuf.size(), priority);
tr_torrentSetFilePriorities(tor, indexBuf.data(), indexBuf.size(), new_priority);
}
else
{

View File

@ -32,8 +32,6 @@
#if GTKMM_CHECK_VERSION(4, 0, 0)
#include <gtkmm/filterlistmodel.h>
#else
#include <gtkmm/treemodelfilter.h>
#endif
#include <fmt/core.h>
@ -45,13 +43,19 @@
#include <string>
#include <unordered_map>
namespace
{
using ActivityType = TorrentFilter::Activity;
using TrackerType = TorrentFilter::Tracker;
constexpr auto ActivitySeparator = static_cast<ActivityType>(-1);
constexpr auto TrackerSeparator = static_cast<TrackerType>(-1);
} // namespace
class FilterBar::Impl
{
using FilterModel = IF_GTKMM4(Gtk::FilterListModel, Gtk::TreeModelFilter);
using TrackerType = TorrentFilter::Tracker;
using ActivityType = TorrentFilter::Activity;
public:
Impl(FilterBar& widget, Glib::RefPtr<Session> const& core);
~Impl();
@ -118,15 +122,10 @@ private:
sigc::connection update_filter_models_on_change_tag_;
};
/***
****
**** TRACKERS
****
***/
// --- TRACKERS
namespace
{
class TrackerFilterModelColumns : public Gtk::TreeModelColumnRecord
{
public:
@ -141,7 +140,7 @@ public:
Gtk::TreeModelColumn<Glib::ustring> displayname; /* human-readable name; ie, Legaltorrents */
Gtk::TreeModelColumn<int> count; /* how many matches there are */
Gtk::TreeModelColumn<int> type;
Gtk::TreeModelColumn<TrackerType> type;
Gtk::TreeModelColumn<Glib::ustring> sitename; // pattern-matching text; see tr_parsed_url.sitename
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> pixbuf;
};
@ -202,7 +201,7 @@ bool FilterBar::Impl::tracker_filter_model_update()
/* Walk through all the torrents, tallying how many matches there are
* for the various categories. Also make a sorted list of all tracker
* hosts s.t. we can merge it with the existing list */
auto n_torrents = int{ 0 };
auto n_torrents = 0;
auto site_infos = std::unordered_map<std::string /*site*/, site_info>{};
for (auto i = 0U, count = torrents_model->get_n_items(); i < count; ++i)
{
@ -298,7 +297,7 @@ bool FilterBar::Impl::tracker_filter_model_update()
add->set_value(tracker_filter_cols.sitename, Glib::ustring{ site.sitename });
add->set_value(tracker_filter_cols.displayname, get_name_from_host(site.sitename));
add->set_value(tracker_filter_cols.count, site.count);
add->set_value(tracker_filter_cols.type, static_cast<int>(TrackerType::HOST));
add->set_value(tracker_filter_cols.type, TrackerType::HOST);
auto path = tracker_model_->get_path(add);
core_->favicon_cache().load(
site.announce_url,
@ -322,17 +321,17 @@ Glib::RefPtr<Gtk::TreeStore> FilterBar::Impl::tracker_filter_model_new()
auto iter = store->append();
iter->set_value(tracker_filter_cols.displayname, Glib::ustring(_("All")));
iter->set_value(tracker_filter_cols.type, static_cast<int>(TrackerType::ALL));
iter->set_value(tracker_filter_cols.type, TrackerType::ALL);
iter = store->append();
iter->set_value(tracker_filter_cols.type, -1);
iter->set_value(tracker_filter_cols.type, TrackerSeparator);
return store;
}
bool FilterBar::Impl::is_it_a_separator(Gtk::TreeModel::const_iterator const& iter)
{
return iter->get_value(tracker_filter_cols.type) == -1;
return iter->get_value(tracker_filter_cols.type) == TrackerSeparator;
}
void FilterBar::Impl::render_pixbuf_func(Gtk::CellRendererPixbuf& cell_renderer, Gtk::TreeModel::const_iterator const& iter)
@ -406,7 +405,7 @@ public:
Gtk::TreeModelColumn<Glib::ustring> name;
Gtk::TreeModelColumn<int> count;
Gtk::TreeModelColumn<int> type;
Gtk::TreeModelColumn<ActivityType> type;
Gtk::TreeModelColumn<Glib::ustring> icon_name;
};
@ -416,7 +415,7 @@ ActivityFilterModelColumns const activity_filter_cols;
bool FilterBar::Impl::activity_is_it_a_separator(Gtk::TreeModel::const_iterator const& iter)
{
return iter->get_value(activity_filter_cols.type) == -1;
return iter->get_value(activity_filter_cols.type) == ActivitySeparator;
}
void FilterBar::Impl::status_model_update_count(Gtk::TreeModel::iterator const& iter, int n)
@ -434,7 +433,7 @@ bool FilterBar::Impl::activity_filter_model_update()
for (auto& row : activity_model_->children())
{
auto const type = row.get_value(activity_filter_cols.type);
if (type == -1)
if (type == ActivitySeparator)
{
continue;
}
@ -487,7 +486,7 @@ Glib::RefPtr<Gtk::ListStore> FilterBar::Impl::activity_filter_model_new()
Glib::ustring();
auto const iter = store->append();
iter->set_value(activity_filter_cols.name, name);
iter->set_value(activity_filter_cols.type, static_cast<int>(type.type));
iter->set_value(activity_filter_cols.type, type.type);
iter->set_value(activity_filter_cols.icon_name, Glib::ustring(type.icon_name != nullptr ? type.icon_name : ""));
}

View File

@ -15,12 +15,14 @@
#include <glibmm/object.h>
#endif
#include <cstdint>
template<typename T>
class FilterBase : public IF_GTKMM4(Gtk::Filter, Glib::Object)
{
public:
#if !GTKMM_CHECK_VERSION(4, 0, 0)
enum class Change{
enum class Change : uint8_t{
DIFFERENT,
LESS_STRICT,
MORE_STRICT,

View File

@ -30,6 +30,13 @@ FilterListModel<ItemT>::FilterListModel(Glib::RefPtr<Gtk::TreeModel> const& mode
: Gtk::TreeModelFilter(model)
, matches_all_(filter->matches_all())
, matches_none_(filter->matches_none())
, signal_changed_tag_{ filter->signal_changed().connect(
[this, filter](auto /*changes*/)
{
matches_all_ = filter->matches_all();
matches_none_ = filter->matches_none();
refilter();
}) }
{
static auto const& self_col = ItemT::get_columns().self;
@ -53,14 +60,6 @@ FilterListModel<ItemT>::FilterListModel(Glib::RefPtr<Gtk::TreeModel> const& mode
set_visible_func(filter_func);
signal_changed_tag_ = filter->signal_changed().connect(
[this, filter](auto /*changes*/)
{
matches_all_ = filter->matches_all();
matches_none_ = filter->matches_none();
refilter();
});
signal_row_inserted().connect([this](auto const& path, auto const& /*iter*/)
{ signal_items_changed_.emit(path.front(), 0, 1); });
signal_row_deleted().connect([this](auto const& path) { signal_items_changed_.emit(path.front(), 1, 0); });

View File

@ -53,7 +53,7 @@ bool FreeSpaceLabel::Impl::on_freespace_timer()
auto const capacity = tr_sys_path_get_capacity(dir_);
auto const text = capacity ? fmt::format(_("{disk_space} free"), fmt::arg("disk_space", tr_strlsize(capacity->free))) :
_("Error");
label_.set_markup(fmt::format(FMT_STRING("<i>{:s}</i>"), text));
label_.set_markup(fmt::format("<i>{:s}</i>", text));
return true;
}

View File

@ -5,7 +5,7 @@
#pragma once
auto inline constexpr GUI_PAD_SMALL = int{ 3 };
auto inline constexpr GUI_PAD = int{ 6 };
auto inline constexpr GUI_PAD_BIG = int{ 12 };
auto inline constexpr GUI_PAD_LARGE = int{ 12 };
auto inline constexpr GUI_PAD_SMALL = 3;
auto inline constexpr GUI_PAD = 6;
auto inline constexpr GUI_PAD_BIG = 12;
auto inline constexpr GUI_PAD_LARGE = 12;

View File

@ -14,6 +14,7 @@
#include <gtkmm/treemodel.h>
#include <gtkmm/treemodelcolumn.h>
#include <cstdint>
#include <optional>
#include <unordered_map>
#include <vector>
@ -25,7 +26,7 @@ class ListModelAdapter
using IdGetter = std::function<int(Glib::RefPtr<Glib::ObjectBase const> const&)>;
using ValueGetter = std::function<void(Glib::RefPtr<Glib::ObjectBase const> const&, int, Glib::ValueBase&)>;
enum class PositionAdjustment
enum class PositionAdjustment : int8_t
{
DECREMENT = -1,
INCREMENT = 1,

View File

@ -542,7 +542,7 @@ MakeDialog::Impl::Impl(MakeDialog& dialog, Glib::RefPtr<Gtk::Builder> const& bui
file_radio_->signal_toggled().connect([this]() { onSourceToggled(file_radio_, file_chooser_); });
file_chooser_->signal_selection_changed().connect([this]() { onChooserChosen(file_chooser_); });
pieces_lb_->set_markup(fmt::format(FMT_STRING("<i>{:s}</i>"), _("No source selected")));
pieces_lb_->set_markup(fmt::format("<i>{:s}</i>", _("No source selected")));
piece_size_scale_->set_visible(false);
piece_size_scale_->signal_value_changed().connect([this]() { onPieceSizeUpdated(); });

View File

@ -12,7 +12,6 @@
#include "Session.h"
#include "Utils.h"
#include <libtransmission/transmission.h>
#include <libtransmission/log.h>
#include <giomm/simpleaction.h>
@ -25,7 +24,7 @@
#include <glibmm/variant.h>
#include <gtkmm/cellrenderertext.h>
#include <gtkmm/combobox.h>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/filechoosernative.h>
#include <gtkmm/liststore.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/treemodel.h>
@ -37,9 +36,10 @@
#include <fmt/core.h>
#include <fmt/ostream.h>
#include <array>
#include <fstream>
#include <map>
#include <memory>
#include <utility>
class MessageLogColumnsModel : public Gtk::TreeModelColumnRecord
{
@ -72,15 +72,15 @@ private:
bool onRefresh();
void onSaveRequest();
void onSaveDialogResponse(std::shared_ptr<Gtk::FileChooserDialog>& d, int response);
void doSave(Gtk::Window& parent, Glib::ustring const& filename);
void onSaveDialogResponse(Glib::RefPtr<Gtk::FileChooserNative>& d, int response);
void doSave(std::string const& filename);
void onClearRequest();
void onPauseToggled(Gio::SimpleAction& action);
void scroll_to_bottom();
void level_combo_changed_cb(Gtk::ComboBox* combo_box);
void level_combo_init(Gtk::ComboBox* level_combo) const;
static void level_combo_init(Gtk::ComboBox* level_combo);
[[nodiscard]] bool is_pinned_to_new() const;
[[nodiscard]] bool isRowVisible(Gtk::TreeModel::const_iterator const& iter) const;
@ -96,7 +96,14 @@ private:
tr_log_level maxLevel_ = TR_LOG_INFO;
bool isPaused_ = false;
sigc::connection refresh_tag_;
std::map<tr_log_level, char const*> const level_names_;
static auto inline const level_names_ = std::array<std::pair<tr_log_level, char const*>, 5U>{ {
{ TR_LOG_CRITICAL, C_("Logging level", "Critical") },
{ TR_LOG_ERROR, C_("Logging level", "Error") },
{ TR_LOG_WARN, C_("Logging level", "Warning") },
{ TR_LOG_INFO, C_("Logging level", "Information") },
{ TR_LOG_DEBUG, C_("Logging level", "Debug") },
} };
};
namespace
@ -157,13 +164,15 @@ void MessageLogWindow::Impl::scroll_to_bottom()
*****
****/
void MessageLogWindow::Impl::level_combo_init(Gtk::ComboBox* level_combo) const
// static
void MessageLogWindow::Impl::level_combo_init(Gtk::ComboBox* level_combo)
{
auto const pref_level = static_cast<tr_log_level>(gtr_pref_int_get(TR_KEY_message_level));
auto const default_level = TR_LOG_INFO;
auto has_pref_level = false;
auto items = std::vector<std::pair<Glib::ustring, int>>{};
items.reserve(std::size(level_names_));
for (auto const& [level, name] : level_names_)
{
items.emplace_back(name, level);
@ -201,21 +210,24 @@ Glib::ustring gtr_asctime(time_t t)
} // namespace
void MessageLogWindow::Impl::doSave(Gtk::Window& parent, Glib::ustring const& filename)
void MessageLogWindow::Impl::doSave(std::string const& filename)
{
try
{
auto stream = std::ofstream();
stream.exceptions(std::ios_base::failbit | std::ios_base::badbit);
stream.open(Glib::locale_from_utf8(filename), std::ios_base::trunc);
stream.open(filename, std::ios_base::trunc);
for (auto const& row : store_->children())
{
auto const* const node = row.get_value(message_log_cols.tr_msg);
auto const date = gtr_asctime(node->when);
auto const it = level_names_.find(node->level);
auto const* const level_str = it != std::end(level_names_) ? it->second : "???";
auto const iter = std::find_if(
std::begin(level_names_),
std::end(level_names_),
[key = node->level](auto const& item) { return item.first == key; });
auto const* const level_str = iter != std::end(level_names_) ? iter->second : "???";
fmt::print(stream, "{}\t{}\t{}\t{}\n", date, level_str, node->name, node->message);
}
@ -223,10 +235,10 @@ void MessageLogWindow::Impl::doSave(Gtk::Window& parent, Glib::ustring const& fi
catch (std::ios_base::failure const& e)
{
auto w = std::make_shared<Gtk::MessageDialog>(
parent,
window_,
fmt::format(
_("Couldn't save '{path}': {error} ({error_code})"),
fmt::arg("path", filename),
fmt::arg("path", Glib::filename_to_utf8(filename)),
fmt::arg("error", e.code().message()),
fmt::arg("error_code", e.code().value())),
false,
@ -238,22 +250,21 @@ void MessageLogWindow::Impl::doSave(Gtk::Window& parent, Glib::ustring const& fi
}
}
void MessageLogWindow::Impl::onSaveDialogResponse(std::shared_ptr<Gtk::FileChooserDialog>& d, int response)
void MessageLogWindow::Impl::onSaveDialogResponse(Glib::RefPtr<Gtk::FileChooserNative>& d, int response)
{
if (response == TR_GTK_RESPONSE_TYPE(ACCEPT))
{
doSave(*d, d->get_file()->get_path());
}
auto const filename = response == TR_GTK_RESPONSE_TYPE(ACCEPT) ? d->get_file()->get_path() : std::string();
d.reset();
if (!filename.empty())
{
doSave(filename);
}
}
void MessageLogWindow::Impl::onSaveRequest()
{
auto d = std::make_shared<Gtk::FileChooserDialog>(window_, _("Save Log"), TR_GTK_FILE_CHOOSER_ACTION(SAVE));
d->add_button(_("_Cancel"), TR_GTK_RESPONSE_TYPE(CANCEL));
d->add_button(_("_Save"), TR_GTK_RESPONSE_TYPE(ACCEPT));
auto d = Gtk::FileChooserNative::create(_("Save Log"), window_, TR_GTK_FILE_CHOOSER_ACTION(SAVE), _("_Save"), _("_Cancel"));
d->signal_response().connect([this, d](int response) mutable { onSaveDialogResponse(d, response); });
d->show();
}
@ -479,13 +490,6 @@ MessageLogWindow::Impl::Impl(
, refresh_tag_(Glib::signal_timeout().connect_seconds(
sigc::mem_fun(*this, &Impl::onRefresh),
SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS))
, level_names_{ {
{ TR_LOG_CRITICAL, C_("Logging level", "Critical") },
{ TR_LOG_ERROR, C_("Logging level", "Error") },
{ TR_LOG_WARN, C_("Logging level", "Warning") },
{ TR_LOG_INFO, C_("Logging level", "Information") },
{ TR_LOG_DEBUG, C_("Logging level", "Debug") },
} }
{
/**
*** toolbar

View File

@ -118,7 +118,7 @@ void OptionsDialog::Impl::addResponseCB(int response)
{
if (response == TR_GTK_RESPONSE_TYPE(ACCEPT))
{
tr_torrentSetPriority(tor_, gtr_combo_box_get_active_enum(*priority_combo_));
tr_torrentSetPriority(tor_, static_cast<tr_priority_t>(gtr_combo_box_get_active_enum(*priority_combo_)));
if (run_check_->get_active())
{
@ -353,8 +353,6 @@ void TorrentFileChooserDialog::onOpenDialogResponse(int response, Glib::RefPtr<S
core->add_files(files, do_start, do_prompt, do_notify);
}
close();
}
std::unique_ptr<TorrentFileChooserDialog> TorrentFileChooserDialog::create(
@ -365,13 +363,10 @@ std::unique_ptr<TorrentFileChooserDialog> TorrentFileChooserDialog::create(
}
TorrentFileChooserDialog::TorrentFileChooserDialog(Gtk::Window& parent, Glib::RefPtr<Session> const& core)
: Gtk::FileChooserDialog(parent, _("Open a Torrent"), TR_GTK_FILE_CHOOSER_ACTION(OPEN))
: Gtk::FileChooserNative(_("Open a Torrent"), parent, TR_GTK_FILE_CHOOSER_ACTION(OPEN), _("_Open"), _("_Cancel"))
{
set_modal(true);
add_button(_("_Cancel"), TR_GTK_RESPONSE_TYPE(CANCEL));
add_button(_("_Open"), TR_GTK_RESPONSE_TYPE(ACCEPT));
set_select_multiple(true);
addTorrentFilters(this);
signal_response().connect([this, core](int response) { onOpenDialogResponse(response, core); });
@ -391,7 +386,6 @@ TorrentFileChooserDialog::TorrentFileChooserDialog(Gtk::Window& parent, Glib::Re
void TorrentUrlChooserDialog::onOpenURLResponse(int response, Gtk::Entry const& entry, Glib::RefPtr<Session> const& core)
{
if (response == TR_GTK_RESPONSE_TYPE(CANCEL))
{
close();

View File

@ -11,7 +11,7 @@
#include <gtkmm/builder.h>
#include <gtkmm/dialog.h>
#include <gtkmm/entry.h>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/filechoosernative.h>
#include <gtkmm/window.h>
#include <memory>
@ -38,7 +38,7 @@ private:
void onOpenURLResponse(int response, Gtk::Entry const& entry, Glib::RefPtr<Session> const& core);
};
class TorrentFileChooserDialog : public Gtk::FileChooserDialog
class TorrentFileChooserDialog : public Gtk::FileChooserNative
{
public:
~TorrentFileChooserDialog() override = default;

View File

@ -5,17 +5,21 @@
#include "PathButton.h"
#include "GtkCompat.h"
#include "Utils.h"
#include <giomm/file.h>
#include <glibmm/error.h>
#include <glibmm/i18n.h>
#include <glibmm/property.h>
#include <gtkmm/box.h>
#include <gtkmm/filechooserdialog.h>
#if GTKMM_CHECK_VERSION(4, 0, 0)
#include <glibmm/error.h>
#include <glibmm/property.h>
#include <gtkmm/dialog.h>
#include <gtkmm/filechoosernative.h>
#include <gtkmm/image.h>
#include <gtkmm/label.h>
#include <gtkmm/separator.h>
#endif
#include <vector>
@ -142,10 +146,12 @@ void PathButton::Impl::show_dialog()
{
auto const title = title_.get_value();
auto dialog = std::make_shared<Gtk::FileChooserDialog>(!title.empty() ? title : _("Select a File"), action_.get_value());
auto dialog = Gtk::FileChooserNative::create(
!title.empty() ? title : _("Select a File"),
action_.get_value(),
_("_Open"),
_("_Cancel"));
dialog->set_transient_for(gtr_widget_get_window(widget_));
dialog->add_button(_("_Cancel"), Gtk::ResponseType::CANCEL);
dialog->add_button(_("_Open"), Gtk::ResponseType::ACCEPT);
dialog->set_modal(true);
if (!current_file_.empty())
@ -167,7 +173,7 @@ void PathButton::Impl::show_dialog()
dialog->signal_response().connect(
[this, dialog](int response) mutable
{
if (response == Gtk::ResponseType::ACCEPT)
if (response == TR_GTK_RESPONSE_TYPE(ACCEPT))
{
set_filename(dialog->get_file()->get_path());
selection_changed_.emit();

View File

@ -1,6 +1,5 @@
#include "Percents.h"
#include <libtransmission/transmission.h>
#include <libtransmission/utils.h>
std::string Percents::to_string() const

View File

@ -25,13 +25,9 @@ void gtr_pref_init(std::string_view config_dir)
gl_confdir = config_dir;
}
/***
****
**** Preferences
****
***/
[[nodiscard]] static std::string get_default_download_dir()
namespace
{
[[nodiscard]] std::string get_default_download_dir()
{
if (auto dir = Glib::get_user_special_dir(TR_GLIB_USER_DIRECTORY(DOWNLOAD)); !std::empty(dir))
{
@ -50,7 +46,7 @@ void gtr_pref_init(std::string_view config_dir)
* This is where we initialize the preferences file with the default values.
* If you add a new preferences key, you /must/ add a default value here.
*/
[[nodiscard]] static tr_variant get_default_app_settings()
[[nodiscard]] tr_variant get_default_app_settings()
{
auto const dir = get_default_download_dir();
@ -88,7 +84,7 @@ void gtr_pref_init(std::string_view config_dir)
return tr_variant{ std::move(map) };
}
static void ensure_sound_cmd_is_a_list(tr_variant* dict)
void ensure_sound_cmd_is_a_list(tr_variant* dict)
{
tr_quark const key = TR_KEY_torrent_complete_sound_command;
tr_variant* list = nullptr;
@ -106,23 +102,20 @@ static void ensure_sound_cmd_is_a_list(tr_variant* dict)
tr_variantListAddStr(list, "transmission torrent downloaded"sv);
}
static tr_variant& getPrefs()
tr_variant& getPrefs()
{
static auto settings = tr_variant{};
if (!settings.has_value())
{
settings = get_default_app_settings();
settings.merge(tr_sessionLoadSettings(gl_confdir.c_str(), nullptr));
auto const app_defaults = get_default_app_settings();
settings.merge(tr_sessionLoadSettings(&app_defaults, gl_confdir.c_str(), nullptr));
ensure_sound_cmd_is_a_list(&settings);
}
return settings;
}
/***
****
***/
} // namespace
tr_variant& gtr_pref_get_all()
{
@ -153,9 +146,7 @@ void gtr_pref_double_set(tr_quark const key, double value)
tr_variantDictAddReal(&getPrefs(), key, value);
}
/***
****
***/
// ---
bool gtr_pref_flag_get(tr_quark const key)
{
@ -169,9 +160,7 @@ void gtr_pref_flag_set(tr_quark const key, bool value)
tr_variantDictAddBool(&getPrefs(), key, value);
}
/***
****
***/
// ---
std::vector<std::string> gtr_pref_strv_get(tr_quark const key)
{
@ -207,9 +196,7 @@ void gtr_pref_string_set(tr_quark const key, std::string_view value)
tr_variantDictAddStr(&getPrefs(), key, value);
}
/***
****
***/
// ---
void gtr_pref_save(tr_session* session)
{

View File

@ -14,7 +14,6 @@
#include "Utils.h"
#include <libtransmission/transmission.h>
#include <libtransmission/version.h>
#include <libtransmission/web-utils.h>
#include <glibmm/date.h>
@ -43,11 +42,14 @@
#include <fmt/core.h>
#include <array>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <string_view>
using namespace libtransmission::Values;
@ -879,10 +881,23 @@ public:
TR_DISABLE_COPY_MOVE(NetworkPage)
private:
enum PortTestStatus : uint8_t
{
PORT_TEST_UNKNOWN = 0U,
PORT_TEST_CHECKING,
PORT_TEST_OPEN,
PORT_TEST_CLOSED,
PORT_TEST_ERROR
};
void portTestSetSensitive();
void updatePortStatusText();
void onCorePrefsChanged(tr_quark key);
void onPortTested(bool isOpen);
void onPortTested(std::optional<bool> result, Session::PortTestIpProtocol ip_protocol);
void onPortTest();
static std::string_view getPortStatusText(PortTestStatus status) noexcept;
private:
Glib::RefPtr<Session> core_;
@ -892,15 +907,18 @@ private:
sigc::connection portTag_;
sigc::connection prefsTag_;
std::array<PortTestStatus, Session::NUM_PORT_TEST_IP_PROTOCOL> portTestStatus_ = {};
};
void NetworkPage::onCorePrefsChanged(tr_quark const key)
{
if (key == TR_KEY_peer_port)
{
gtr_label_set_text(*portLabel_, _("Status unknown"));
portButton_->set_sensitive(true);
portSpin_->set_sensitive(true);
portTestStatus_[Session::PORT_TEST_IPV4] = PORT_TEST_UNKNOWN;
portTestStatus_[Session::PORT_TEST_IPV6] = PORT_TEST_UNKNOWN;
updatePortStatusText();
portTestSetSensitive();
}
}
@ -910,28 +928,92 @@ NetworkPage::~NetworkPage()
portTag_.disconnect();
}
void NetworkPage::onPortTested(bool isOpen)
std::string_view NetworkPage::getPortStatusText(PortTestStatus const status) noexcept
{
portLabel_->set_markup(fmt::format(
isOpen ? _("Port is {markup_begin}open{markup_end}") : _("Port is {markup_begin}closed{markup_end}"),
fmt::arg("markup_begin", "<b>"),
fmt::arg("markup_end", "</b>")));
portButton_->set_sensitive(true);
portSpin_->set_sensitive(true);
switch (status)
{
case PORT_TEST_UNKNOWN:
return C_("Port test status", "unknown");
case PORT_TEST_CHECKING:
return C_("Port test status", "checking…");
case PORT_TEST_OPEN:
return C_("Port test status", "open");
case PORT_TEST_CLOSED:
return C_("Port test status", "closed");
case PORT_TEST_ERROR:
return C_("Port test status", "error");
default:
return {};
}
}
void NetworkPage::updatePortStatusText()
{
auto const status_ipv4 = getPortStatusText(portTestStatus_[Session::PORT_TEST_IPV4]);
auto const status_ipv6 = getPortStatusText(portTestStatus_[Session::PORT_TEST_IPV6]);
portLabel_->set_markup(
portTestStatus_[Session::PORT_TEST_IPV4] == portTestStatus_[Session::PORT_TEST_IPV6] ?
fmt::format(_("Status: <b>{status}</b>"), fmt::arg("status", status_ipv4)) :
fmt::format(
_("Status: <b>{status_ipv4}</b> (IPv4), <b>{status_ipv6}</b> (IPv6)"),
fmt::arg("status_ipv4", status_ipv4),
fmt::arg("status_ipv6", status_ipv6)));
}
void NetworkPage::portTestSetSensitive()
{
// Depend on the RPC call status instead of the UI status, so that the widgets
// won't be enabled even if the port peer port changed while we have port-test
// RPC call(s) in-flight.
auto const sensitive = !core_->port_test_pending(Session::PORT_TEST_IPV4) &&
!core_->port_test_pending(Session::PORT_TEST_IPV6);
portButton_->set_sensitive(sensitive);
portSpin_->set_sensitive(sensitive);
}
void NetworkPage::onPortTested(std::optional<bool> const result, Session::PortTestIpProtocol const ip_protocol)
{
auto constexpr ResultToStatus = [](std::optional<bool> const res)
{
if (!res)
{
return PORT_TEST_ERROR;
}
if (!*res)
{
return PORT_TEST_CLOSED;
}
return PORT_TEST_OPEN;
};
// Only update the UI if the current status is "checking", so that
// we won't show the port test results for the old peer port if it
// changed while we have port-test RPC call(s) in-flight.
if (auto& status = portTestStatus_[ip_protocol]; status == PORT_TEST_CHECKING)
{
status = ResultToStatus(result);
updatePortStatusText();
}
portTestSetSensitive();
}
void NetworkPage::onPortTest()
{
portButton_->set_sensitive(false);
portSpin_->set_sensitive(false);
portLabel_->set_text(_("Testing TCP port…"));
portTestStatus_[Session::PORT_TEST_IPV4] = PORT_TEST_CHECKING;
portTestStatus_[Session::PORT_TEST_IPV6] = PORT_TEST_CHECKING;
updatePortStatusText();
if (!portTag_.connected())
{
portTag_ = core_->signal_port_tested().connect([this](bool is_open) { onPortTested(is_open); });
portTag_ = core_->signal_port_tested().connect(
[this](std::optional<bool> status, Session::PortTestIpProtocol ip_protocol) { onPortTested(status, ip_protocol); });
}
core_->port_test();
core_->port_test(Session::PORT_TEST_IPV4);
core_->port_test(Session::PORT_TEST_IPV6);
portTestSetSensitive();
}
NetworkPage::NetworkPage(
@ -945,6 +1027,7 @@ NetworkPage::NetworkPage(
, portSpin_(init_spin_button("listening_port_spin", TR_KEY_peer_port, 1, std::numeric_limits<uint16_t>::max(), 1))
{
portButton_->signal_clicked().connect([this]() { onPortTest(); });
updatePortStatusText();
prefsTag_ = core_->signal_prefs_changed().connect([this](auto key) { onCorePrefsChanged(key); });

View File

@ -35,5 +35,5 @@ private:
std::unique_ptr<Impl> const impl_;
};
auto inline constexpr MAIN_WINDOW_REFRESH_INTERVAL_SECONDS = int{ 2 };
auto inline constexpr SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS = int{ 2 };
auto inline constexpr MAIN_WINDOW_REFRESH_INTERVAL_SECONDS = 2;
auto inline constexpr SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS = 2;

View File

@ -18,7 +18,6 @@
#include <libtransmission/log.h>
#include <libtransmission/rpcimpl.h>
#include <libtransmission/torrent-metainfo.h>
#include <libtransmission/tr-assert.h>
#include <libtransmission/utils.h> // tr_time()
#include <libtransmission/variant.h>
#include <libtransmission/web-utils.h> // tr_urlIsValid()
@ -45,12 +44,14 @@
#include <fmt/core.h>
#include <algorithm>
#include <array>
#include <cinttypes> // PRId64
#include <cstring> // strstr
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
@ -75,15 +76,18 @@ public:
size_t get_active_torrent_count() const;
bool get_port_test_pending(PortTestIpProtocol ip_protocol);
void set_port_test_pending(bool pending, PortTestIpProtocol ip_protocol);
void update();
void torrents_added();
void add_files(std::vector<Glib::RefPtr<Gio::File>> const& files, bool do_start, bool do_prompt, bool do_notify);
int add_ctor(tr_ctor* ctor, bool do_prompt, bool do_notify);
void add_ctor(tr_ctor* ctor, bool do_prompt, bool do_notify);
void add_torrent(Glib::RefPtr<Torrent> const& torrent, bool do_notify);
bool add_from_url(Glib::ustring const& url);
void send_rpc_request(tr_variant const* request, int64_t tag, std::function<void(tr_variant&)> const& response_func);
void send_rpc_request(tr_variant const& request, int64_t tag, std::function<void(tr_variant&)> const& response_func);
void commit_prefs_change(tr_quark key);
@ -170,7 +174,7 @@ private:
sigc::signal<void(bool)> signal_blocklist_updated_;
sigc::signal<void(bool)> signal_busy_;
sigc::signal<void(tr_quark)> signal_prefs_changed_;
sigc::signal<void(bool)> signal_port_tested_;
sigc::signal<void(std::optional<bool>, PortTestIpProtocol)> signal_port_tested_;
sigc::signal<void(std::unordered_set<tr_torrent_id_t> const&, Torrent::ChangeFlags)> signal_torrents_changed_;
Glib::RefPtr<Gio::FileMonitor> monitor_;
@ -183,6 +187,7 @@ private:
bool inhibit_allowed_ = false;
bool have_inhibit_cookie_ = false;
bool dbus_error_ = false;
std::array<bool, NUM_PORT_TEST_IP_PROTOCOL> port_test_pending_ = {};
guint inhibit_cookie_ = 0;
gint busy_count_ = 0;
Glib::RefPtr<Gio::ListStore<Torrent>> raw_model_;
@ -690,12 +695,12 @@ Glib::RefPtr<Torrent> Session::Impl::create_new_torrent(tr_ctor* ctor)
return Torrent::create(tor);
}
int Session::Impl::add_ctor(tr_ctor* ctor, bool do_prompt, bool do_notify)
void Session::Impl::add_ctor(tr_ctor* ctor, bool do_prompt, bool do_notify)
{
auto const* metainfo = tr_ctorGetMetainfo(ctor);
if (metainfo == nullptr)
{
return TR_PARSE_ERR;
return;
}
if (tr_torrentFindFromMetainfo(get_session(), metainfo) != nullptr)
@ -709,18 +714,17 @@ int Session::Impl::add_ctor(tr_ctor* ctor, bool do_prompt, bool do_notify)
}
tr_ctorFree(ctor);
return TR_PARSE_DUPLICATE;
return;
}
if (!do_prompt)
{
add_torrent(create_new_torrent(ctor), do_notify);
tr_ctorFree(ctor);
return 0;
return;
}
signal_add_prompt_.emit(ctor);
return 0;
}
namespace
@ -842,8 +846,7 @@ bool Session::Impl::add_file(Glib::RefPtr<Gio::File> const& file, bool do_start,
else
{
tr_ctorFree(ctor);
std::cerr << fmt::format(_("Couldn't add torrent file '{path}'"), fmt::arg("path", file->get_parse_name()))
<< std::endl;
std::cerr << fmt::format(_("Couldn't add torrent file '{path}'"), fmt::arg("path", file->get_parse_name())) << '\n';
}
return handled;
@ -968,7 +971,7 @@ void Session::start_now(tr_torrent_id_t id)
auto* args = tr_variantDictAddDict(&top, TR_KEY_arguments, 1);
auto* ids = tr_variantDictAddList(args, TR_KEY_ids, 1);
tr_variantListAddInt(ids, id);
exec(&top);
exec(top);
}
void Session::Impl::update()
@ -1187,19 +1190,16 @@ bool core_read_rpc_response_idle(tr_variant& response)
return false;
}
void core_read_rpc_response(tr_session* /*session*/, tr_variant* response, gpointer /*user_data*/)
void core_read_rpc_response(tr_session* /*session*/, tr_variant&& response)
{
auto owned_response = std::make_shared<tr_variant>();
*owned_response.get() = false;
std::swap(*owned_response, *response);
auto owned_response = std::make_shared<tr_variant>(std::move(response));
Glib::signal_idle().connect([owned_response]() mutable { return core_read_rpc_response_idle(*owned_response); });
}
} // namespace
void Session::Impl::send_rpc_request(
tr_variant const* request,
tr_variant const& request,
int64_t tag,
std::function<void(tr_variant&)> const& response_func)
{
@ -1217,7 +1217,7 @@ void Session::Impl::send_rpc_request(
gtr_message(fmt::format("request: [{}]", tr_variantToStr(request, TR_VARIANT_FMT_JSON_LEAN)));
#endif
tr_rpc_request_exec_json(session_, request, core_read_rpc_response, nullptr);
tr_rpc_request_exec(session_, request, core_read_rpc_response);
}
}
@ -1225,33 +1225,67 @@ void Session::Impl::send_rpc_request(
**** Sending a test-port request via RPC
***/
void Session::port_test()
void Session::port_test(PortTestIpProtocol const ip_protocol)
{
auto const tag = nextTag;
++nextTag;
static auto constexpr IpStr = std::array{ "ipv4"sv, "ipv6"sv };
if (port_test_pending(ip_protocol))
{
return;
}
impl_->set_port_test_pending(true, ip_protocol);
auto const tag = nextTag++;
auto arguments_map = tr_variant::Map{ 1U };
arguments_map.try_emplace(TR_KEY_ipProtocol, tr_variant::unmanaged_string(IpStr[ip_protocol]));
auto request_map = tr_variant::Map{ 3U };
request_map.try_emplace(TR_KEY_method, tr_variant::unmanaged_string("port-test"sv));
request_map.try_emplace(TR_KEY_tag, tag);
request_map.try_emplace(TR_KEY_arguments, std::move(arguments_map));
tr_variant request;
tr_variantInitDict(&request, 2);
tr_variantDictAddStrView(&request, TR_KEY_method, "port-test");
tr_variantDictAddInt(&request, TR_KEY_tag, tag);
impl_->send_rpc_request(
&request,
tr_variant{ std::move(request_map) },
tag,
[this](auto& response)
[this, ip_protocol](tr_variant& response)
{
tr_variant* args = nullptr;
bool is_open = false;
impl_->set_port_test_pending(false, ip_protocol);
if (!tr_variantDictFindDict(&response, TR_KEY_arguments, &args) ||
!tr_variantDictFindBool(args, TR_KEY_port_is_open, &is_open))
auto status = std::optional<bool>{};
if (tr_variant* args = nullptr; tr_variantDictFindDict(&response, TR_KEY_arguments, &args))
{
is_open = false;
if (auto result = bool{}; tr_variantDictFindBool(args, TR_KEY_port_is_open, &result))
{
status = result;
}
}
impl_->signal_port_tested().emit(is_open);
// If for whatever reason the status optional is empty here,
// then something must have gone wrong with the port test,
// so the UI should show the "error" state
impl_->signal_port_tested().emit(status, ip_protocol);
});
}
bool Session::port_test_pending(Session::PortTestIpProtocol ip_protocol) const noexcept
{
return impl_->get_port_test_pending(ip_protocol);
}
bool Session::Impl::get_port_test_pending(Session::PortTestIpProtocol ip_protocol)
{
return ip_protocol < NUM_PORT_TEST_IP_PROTOCOL && port_test_pending_[ip_protocol];
}
void Session::Impl::set_port_test_pending(bool pending, Session::PortTestIpProtocol ip_protocol)
{
if (ip_protocol < NUM_PORT_TEST_IP_PROTOCOL)
{
port_test_pending_[ip_protocol] = pending;
}
}
/***
**** Updating a blocklist via RPC
***/
@ -1266,7 +1300,7 @@ void Session::blocklist_update()
tr_variantDictAddStrView(&request, TR_KEY_method, "blocklist-update");
tr_variantDictAddInt(&request, TR_KEY_tag, tag);
impl_->send_rpc_request(
&request,
request,
tag,
[this](auto& response)
{
@ -1292,7 +1326,7 @@ void Session::blocklist_update()
****
***/
void Session::exec(tr_variant const* request)
void Session::exec(tr_variant const& request)
{
auto const tag = nextTag;
++nextTag;
@ -1391,7 +1425,7 @@ sigc::signal<void(tr_quark)>& Session::signal_prefs_changed()
return impl_->signal_prefs_changed();
}
sigc::signal<void(bool)>& Session::signal_port_tested()
sigc::signal<void(std::optional<bool>, Session::PortTestIpProtocol)>& Session::signal_port_tested()
{
return impl_->signal_port_tested();
}

View File

@ -21,7 +21,9 @@
#include <gtkmm/treemodel.h>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <unordered_set>
#include <vector>
@ -29,13 +31,20 @@
class Session : public Glib::Object
{
public:
enum ErrorCode
enum ErrorCode : uint16_t
{
ERR_ADD_TORRENT_ERR = TR_PARSE_ERR,
ERR_ADD_TORRENT_DUP = TR_PARSE_DUPLICATE,
ERR_ADD_TORRENT_ERR = 1,
ERR_ADD_TORRENT_DUP = 2,
ERR_NO_MORE_TORRENTS = 1000 /* finished adding a batch */
};
enum PortTestIpProtocol : uint8_t
{
PORT_TEST_IPV4,
PORT_TEST_IPV6,
NUM_PORT_TEST_IP_PROTOCOL // Must always be the last value
};
using Model = IF_GTKMM4(Gio::ListModel, Gtk::TreeModel);
public:
@ -127,11 +136,12 @@ public:
***
**/
void port_test();
void port_test(PortTestIpProtocol ip_protocol);
bool port_test_pending(PortTestIpProtocol ip_protocol) const noexcept;
void blocklist_update();
void exec(tr_variant const* request);
void exec(tr_variant const& request);
void open_folder(tr_torrent_id_t torrent_id) const;
@ -140,7 +150,7 @@ public:
sigc::signal<void(bool)>& signal_blocklist_updated();
sigc::signal<void(bool)>& signal_busy();
sigc::signal<void(tr_quark)>& signal_prefs_changed();
sigc::signal<void(bool)>& signal_port_tested();
sigc::signal<void(std::optional<bool>, PortTestIpProtocol)>& signal_port_tested();
sigc::signal<void(std::unordered_set<tr_torrent_id_t> const&, Torrent::ChangeFlags)>& signal_torrents_changed();
protected:

View File

@ -20,7 +20,7 @@ class SorterBase : public IF_GTKMM4(Gtk::Sorter, Glib::Object)
{
public:
#if !GTKMM_CHECK_VERSION(4, 0, 0)
enum class Change{
enum class Change : uint8_t{
DIFFERENT,
INVERTED,
LESS_STRICT,

View File

@ -20,7 +20,7 @@
#include <memory>
static auto constexpr TR_RESPONSE_RESET = int{ 1 };
static auto constexpr TR_RESPONSE_RESET = 1;
class StatsDialog::Impl
{

View File

@ -140,15 +140,15 @@ public:
std::string_view mime_type;
Storage have_unchecked = {};
Storage have_valid = {};
Storage left_until_done = {};
Storage size_when_done = {};
Storage total_size = {};
Storage uploaded_ever = {};
Storage have_unchecked;
Storage have_valid;
Storage left_until_done;
Storage size_when_done;
Storage total_size;
Storage uploaded_ever;
Speed speed_down = {};
Speed speed_up = {};
Speed speed_down;
Speed speed_up;
size_t queue_position = {};
@ -163,12 +163,12 @@ public:
int active_peers_up = {};
int error_code = {};
Percents activity_percent_done = {};
Percents metadata_percent_complete = {};
Percents percent_complete = {};
Percents percent_done = {};
Percents recheck_progress = {};
Percents seed_ratio_percent_done = {};
Percents activity_percent_done;
Percents metadata_percent_complete;
Percents percent_complete;
Percents percent_done;
Percents recheck_progress;
Percents seed_ratio_percent_done;
uint16_t peers_connected = {};
uint16_t peers_getting_from_us = {};
@ -442,7 +442,7 @@ Glib::ustring Torrent::Impl::get_short_status_text() const
case TR_STATUS_DOWNLOAD:
case TR_STATUS_SEED:
return fmt::format(
FMT_STRING("{:s} {:s}"),
"{:s} {:s}",
get_short_transfer_text(),
fmt::format(_("Ratio: {ratio}"), fmt::arg("ratio", tr_strlratio(cache_.ratio))));
@ -548,7 +548,7 @@ Glib::ustring Torrent::Impl::get_long_status_text() const
default:
if (auto const buf = get_short_transfer_text(); !std::empty(buf))
{
status_str += fmt::format(FMT_STRING(" - {:s}"), buf);
status_str += fmt::format(" - {:s}", buf);
}
}

View File

@ -19,6 +19,7 @@
#include <algorithm>
#include <bitset>
#include <cstdint>
#include <initializer_list>
#include <memory>
@ -38,7 +39,7 @@ public:
Gtk::TreeModelColumn<Glib::ustring> name_collated;
};
enum class ChangeFlag
enum class ChangeFlag : uint8_t
{
ACTIVE_PEER_COUNT,
ACTIVE_PEERS_DOWN,

View File

@ -36,16 +36,10 @@
#include <cstring> // strchr()
#include <memory>
#include <optional>
#include <string>
#include <string_view>
/* #define TEST_RTL */
using namespace std::string_literals;
/***
****
***/
// ---
namespace
{
@ -240,7 +234,7 @@ void set_error_color(
Gtk::Widget& widget,
Gtk::CellRendererState flags)
{
static auto const error_color_name = Glib::ustring("tr_error_color"s);
static auto const error_color_name = Glib::ustring{ "tr_error_color" };
auto color = Gdk::RGBA();
if (torrent.get_error_code() != 0 && (flags & TR_GTK_CELL_RENDERER_STATE(SELECTED)) == Gtk::CellRendererState{} &&
@ -256,9 +250,9 @@ void set_error_color(
std::optional<Gdk::RGBA> get_progress_bar_color(Torrent const& torrent, Gtk::Widget const& widget)
{
static auto const down_color_name = Glib::ustring("tr_transfer_down_color"s);
static auto const up_color_name = Glib::ustring("tr_transfer_up_color"s);
static auto const idle_color_name = Glib::ustring("tr_transfer_idle_color"s);
static auto const down_color_name = Glib::ustring{ "tr_transfer_down_color" };
static auto const up_color_name = Glib::ustring{ "tr_transfer_up_color" };
static auto const idle_color_name = Glib::ustring{ "tr_transfer_idle_color" };
auto const* color_name = &idle_color_name;
switch (torrent.get_activity())
@ -420,7 +414,7 @@ void TorrentCellRenderer::Impl::render_compact(
icon_renderer_->render(context, widget, icon_area, icon_area, flags);
progress_renderer_->property_value() = percent_done;
progress_renderer_->property_text() = fmt::format(FMT_STRING("{:d}%"), percent_done);
progress_renderer_->property_text() = fmt::format("{:d}%", percent_done);
progress_renderer_->property_sensitive() = sensitive;
render_progress_bar(context, widget, prog_area, flags, progress_color);

View File

@ -10,6 +10,9 @@
#include <libtransmission/transmission.h>
#include <array>
#include <utility>
TorrentFilter::TorrentFilter()
: Glib::ObjectBase(typeid(TorrentFilter))
{
@ -127,7 +130,7 @@ void TorrentFilter::update(Torrent::ChangeFlags changes)
if (activity_type_ != Activity::ALL)
{
static auto const activity_flags = std::map<Activity, Torrent::ChangeFlags>({
static constexpr auto ActivityFlags = std::array<std::pair<Activity, Torrent::ChangeFlags>, 7U>{ {
{ Activity::DOWNLOADING, Flag::ACTIVITY },
{ Activity::SEEDING, Flag::ACTIVITY },
{ Activity::ACTIVE, Flag::ACTIVE_PEER_COUNT | Flag::ACTIVITY },
@ -135,10 +138,13 @@ void TorrentFilter::update(Torrent::ChangeFlags changes)
{ Activity::FINISHED, Flag::FINISHED },
{ Activity::VERIFYING, Flag::ACTIVITY },
{ Activity::ERROR, Flag::ERROR_CODE },
});
} };
auto const activity_flags_it = activity_flags.find(activity_type_);
refilter_needed = activity_flags_it != activity_flags.end() && changes.test(activity_flags_it->second);
auto const iter = std::find_if(
std::begin(ActivityFlags),
std::end(ActivityFlags),
[key = activity_type_](auto const& row) { return row.first == key; });
refilter_needed = iter != std::end(ActivityFlags) && changes.test(iter->second);
}
if (!refilter_needed)

View File

@ -11,10 +11,12 @@
#include <glibmm/refptr.h>
#include <glibmm/ustring.h>
#include <cstdint>
class TorrentFilter : public FilterBase<Torrent>
{
public:
enum class Activity
enum class Activity : int8_t
{
ALL,
DOWNLOADING,
@ -26,7 +28,7 @@ public:
ERROR,
};
enum class Tracker
enum class Tracker : int8_t
{
ALL,
HOST,

View File

@ -13,6 +13,8 @@
#include <libtransmission/utils.h>
#include <algorithm>
#include <array>
#include <utility>
using namespace std::string_view_literals;
@ -178,7 +180,8 @@ TorrentSorter::TorrentSorter()
void TorrentSorter::set_mode(std::string_view mode)
{
static auto const compare_funcs = std::map<std::string_view, CompareFunc>({
static auto constexpr DefaultCompareFunc = &compare_by_name;
static auto constexpr CompareFuncs = std::array<std::pair<std::string_view, CompareFunc>, 9U>{ {
{ "sort-by-activity"sv, &compare_by_activity },
{ "sort-by-age"sv, &compare_by_age },
{ "sort-by-name"sv, &compare_by_name },
@ -188,14 +191,13 @@ void TorrentSorter::set_mode(std::string_view mode)
{ "sort-by-size"sv, &compare_by_size },
{ "sort-by-state"sv, &compare_by_state },
{ "sort-by-time-left"sv, &compare_by_eta },
});
auto compare_func = &compare_by_name;
if (auto const compare_func_it = compare_funcs.find(mode); compare_func_it != compare_funcs.end())
{
compare_func = compare_func_it->second;
}
} };
auto const iter = std::find_if(
std::begin(CompareFuncs),
std::end(CompareFuncs),
[key = mode](auto const& row) { return row.first == key; });
auto const compare_func = iter != std::end(CompareFuncs) ? iter->second : DefaultCompareFunc;
if (compare_func_ == compare_func)
{
return;
@ -224,8 +226,7 @@ int TorrentSorter::compare(Torrent const& lhs, Torrent const& rhs) const
void TorrentSorter::update(Torrent::ChangeFlags changes)
{
using Flag = Torrent::ChangeFlag;
static auto const compare_flags = std::map<CompareFunc, Torrent::ChangeFlags>({
static auto constexpr CompareFlags = std::array<std::pair<CompareFunc, Torrent::ChangeFlags>, 9U>{ {
{ &compare_by_activity, Flag::ACTIVE_PEER_COUNT | Flag::QUEUE_POSITION | Flag::SPEED_DOWN | Flag::SPEED_UP },
{ &compare_by_age, Flag::ADDED_DATE | Flag::NAME },
{ &compare_by_eta, Flag::ETA | Flag::NAME },
@ -235,10 +236,13 @@ void TorrentSorter::update(Torrent::ChangeFlags changes)
{ &compare_by_ratio, Flag::QUEUE_POSITION | Flag::RATIO },
{ &compare_by_size, Flag::NAME | Flag::TOTAL_SIZE },
{ &compare_by_state, Flag::ACTIVITY | Flag::QUEUE_POSITION },
});
} };
if (auto const compare_flags_it = compare_flags.find(compare_func_);
compare_flags_it != compare_flags.end() && changes.test(compare_flags_it->second))
if (auto const iter = std::find_if(
std::begin(CompareFlags),
std::end(CompareFlags),
[key = compare_func_](auto const& row) { return row.first == key; });
iter != std::end(CompareFlags) && changes.test(iter->second))
{
changed(Change::DIFFERENT);
}

View File

@ -26,8 +26,6 @@
#include <glibmm/quark.h>
#include <glibmm/spawn.h>
#include <gtkmm/cellrenderertext.h>
#include <gtkmm/eventcontroller.h>
#include <gtkmm/gesture.h>
#include <gtkmm/liststore.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/treemodel.h>
@ -35,6 +33,8 @@
#if GTKMM_CHECK_VERSION(4, 0, 0)
#include <gdkmm/clipboard.h>
#include <gtkmm/eventcontroller.h>
#include <gtkmm/gesture.h>
#include <gtkmm/gestureclick.h>
#else
#include <gdkmm/window.h>
@ -45,7 +45,6 @@
#include <functional>
#include <memory>
#include <optional>
#include <stack>
#include <stdexcept>
#include <utility>
@ -54,6 +53,8 @@
#include <gtk/gtk.h>
#if GTK_CHECK_VERSION(4, 0, 0) && defined(GDK_WINDOWING_X11)
#include <optional>
#include <gdk/x11/gdkx.h>
#endif
@ -645,9 +646,7 @@ void gtr_open_uri(Glib::ustring const& uri)
}
}
/***
****
***/
// ---
namespace
{
@ -735,9 +734,7 @@ void gtr_priority_combo_init(Gtk::ComboBox& combo)
});
}
/***
****
***/
// ---
void gtr_widget_set_visible(Gtk::Widget& widget, bool is_visible)
{
@ -847,9 +844,7 @@ void gtr_window_raise([[maybe_unused]] Gtk::Window& window)
#endif
}
/***
****
***/
// ---
void gtr_unrecognized_url_dialog(Gtk::Widget& parent, Glib::ustring const& url)
{

View File

@ -30,6 +30,7 @@
#include <fmt/core.h>
#include <cstddef>
#include <cstdint>
#include <ctime>
#include <functional>
#include <list>
@ -52,7 +53,7 @@ void gtr_error(std::string const& message);
****
***/
enum class GtrUnicode
enum class GtrUnicode : uint8_t
{
Up,
Down,
@ -260,7 +261,7 @@ T* gtr_get_widget_derived(Glib::RefPtr<Gtk::Builder> const& builder, Glib::ustri
template<typename F>
void gtr_window_on_close(Gtk::Window& widget, F&& callback)
{
auto bool_callback = [callback = std::move(callback)]() mutable -> bool
auto bool_callback = [callback = std::forward<F>(callback)]() mutable -> bool
{
if constexpr (std::is_same_v<void, std::invoke_result_t<decltype(callback)>>)
{

View File

@ -9,6 +9,10 @@ Copyright 2017 Endless Mobile, Inc.
<project_license>GPL-2.0 OR GPL-3.0</project_license>
<name>Transmission</name>
<developer_name>The Transmission Project</developer_name>
<developer id="transmissionbt.com">
<name>The Transmission Project</name>
</developer>
<summary>Download and share files over BitTorrent</summary>
<description>
@ -40,6 +44,7 @@ Copyright 2017 Endless Mobile, Inc.
<screenshots>
<screenshot type="default">
<image type="source">https://raw.githubusercontent.com/transmission/transmission/main/gtk/screenshots/a.png</image>
<caption>Main window, showing an ongoing download of Fedora Workstation</caption>
</screenshot>
</screenshots>
<update_contact>info_AT_transmissionbt.com</update_contact>
@ -47,6 +52,8 @@ Copyright 2017 Endless Mobile, Inc.
<content_attribute id="social-info">mild</content_attribute>
</content_rating>
<releases>
<release date="2023-12-06" version="4.0.5" />
<release date="2023-08-23" version="4.0.4" />
<release date="2023-04-14" version="4.0.3" />
<release date="2023-03-16" version="4.0.2" />
<release date="2023-02-23" version="4.0.1" />
@ -54,4 +61,5 @@ Copyright 2017 Endless Mobile, Inc.
<release date="2020-05-03" version="3.00" />
</releases>
<translation type="gettext">transmission-gtk</translation>
<launchable type="desktop-id">transmission-gtk.desktop</launchable>
</component>

View File

@ -1245,14 +1245,18 @@
</packing>
</child>
<child>
<object class="GtkSpinButton" id="listening_port_spin">
<object class="GtkLabel" id="listening_port_status_label">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="can-focus">False</property>
<property name="label">...</property>
<property name="xalign">0</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
@ -1262,14 +1266,10 @@
<property name="hexpand">True</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="listening_port_status_label">
<object class="GtkSpinButton" id="listening_port_spin">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Status unknown</property>
<property name="xalign">0</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
</object>
<packing>
<property name="expand">True</property>
@ -1295,7 +1295,7 @@
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
</object>

View File

@ -868,12 +868,16 @@
</object>
</child>
<child>
<object class="GtkSpinButton" id="listening_port_spin">
<property name="focusable">1</property>
<object class="GtkLabel" id="listening_port_status_label">
<property name="hexpand">1</property>
<property name="label">...</property>
<property name="xalign">0</property>
<attributes>
<attribute name="style" value="italic"/>
</attributes>
<layout>
<property name="column">1</property>
<property name="row">0</property>
<property name="row">1</property>
</layout>
</object>
</child>
@ -882,13 +886,13 @@
<property name="hexpand">1</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="listening_port_status_label">
<object class="GtkSpinButton" id="listening_port_spin">
<property name="focusable">1</property>
<property name="hexpand">1</property>
<property name="label" translatable="1">Status unknown</property>
<property name="xalign">0</property>
<attributes>
<attribute name="style" value="italic"></attribute>
</attributes>
<layout>
<property name="column">1</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child>
@ -901,7 +905,7 @@
</child>
<layout>
<property name="column">1</property>
<property name="row">1</property>
<property name="row">0</property>
</layout>
</object>
</child>

View File

@ -9,6 +9,7 @@ Checks: >
cert-*,
-cert-err58-cpp,
clang-analyzer-*,
-clang-analyzer-optin.core.EnumCastOutOfRange,
cppcoreguidelines-avoid-do-while,
cppcoreguidelines-avoid-goto,
cppcoreguidelines-avoid-reference-coroutine-parameters,
@ -23,11 +24,13 @@ Checks: >
cppcoreguidelines-virtual-class-destructor,
google-explicit-constructor,
misc-*,
-misc-include-cleaner,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
modernize-*,
-modernize-use-trailing-return-type,
performance-*,
-performance-move-const-arg,
portability-*,
readability-*,
-readability-function-cognitive-complexity,

View File

@ -12,7 +12,7 @@ check_symbol_exists(SO_REUSEPORT "sys/types.h;sys/socket.h" HAVE_SO_REUSEPORT)
add_compile_options(
# equivalent of XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES for this directory
$<$<AND:$<BOOL:${APPLE}>,$<CXX_COMPILER_ID:AppleClang,Clang>,$<COMPILE_LANGUAGE:C,CXX>>:-fobjc-arc>)
$<$<AND:$<BOOL:${APPLE}>,$<OR:$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:Clang>>,$<OR:$<COMPILE_LANGUAGE:C>,$<COMPILE_LANGUAGE:CXX>>>:-fobjc-arc>)
add_library(${TR_NAME} STATIC)
@ -113,11 +113,12 @@ target_sources(${TR_NAME}
session-alt-speeds.h
session-id.cc
session-id.h
session-settings.cc
session-thread.cc
session-thread.h
session.cc
session.h
settings.cc
settings.h
stats.cc
stats.h
subprocess-posix.cc
@ -140,7 +141,6 @@ target_sources(${TR_NAME}
torrents.h
tr-assert.cc
tr-assert.h
tr-assert.mm
tr-buffer.h
tr-dht.cc
tr-dht.h
@ -160,7 +160,6 @@ target_sources(${TR_NAME}
utils.h
utils.mm
variant-benc.cc
variant-converters.cc
variant-json.cc
variant.cc
variant.h
@ -205,10 +204,7 @@ tr_allow_compile_if(
[=[[WITH_KQUEUE]]=]
watchdir-kqueue.cc
[=[[APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang"]]=]
tr-assert.mm
utils.mm
[=[[NOT (APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")]]=]
tr-assert.cc
[=[[WIN32]]=]
file-win32.cc
subprocess-win32.cc
@ -230,6 +226,10 @@ target_compile_definitions(${TR_NAME}
$<$<BOOL:${USE_SYSTEM_B64}>:USE_SYSTEM_B64>
$<$<BOOL:${HAVE_SO_REUSEPORT}>:HAVE_SO_REUSEPORT=1>
PUBLIC
$<$<STREQUAL:${CRYPTO_PKG},ccrypto>:WITH_CCRYPTO>
$<$<STREQUAL:${CRYPTO_PKG},mbedtls>:WITH_MBEDTLS>
$<$<STREQUAL:${CRYPTO_PKG},openssl>:WITH_OPENSSL>
$<$<STREQUAL:${CRYPTO_PKG},wolfssl>:WITH_WOLFSSL>
$<$<NOT:$<BOOL:${ENABLE_NLS}>>:DISABLE_GETTEXT>)
tr_target_compile_definitions_for_headers(${TR_NAME}
@ -271,11 +271,14 @@ target_include_directories(${TR_NAME}
${CMAKE_CURRENT_SOURCE_DIR}/..
${CMAKE_CURRENT_BINARY_DIR}/..)
if(ANDROID)
find_library(log-lib log)
endif()
target_link_libraries(${TR_NAME}
PRIVATE
Threads::Threads
deflate::deflate
transmission::crypto_impl
CURL::libcurl
FastFloat::fast_float
psl::psl
@ -295,7 +298,9 @@ target_link_libraries(${TR_NAME}
$<$<BOOL:${WIN32}>:crypt32>
$<$<BOOL:${WIN32}>:shlwapi>
"$<$<BOOL:${APPLE}>:-framework Foundation>"
"$<$<BOOL:${ANDROID}>:${log-lib}>"
PUBLIC
transmission::crypto_impl
fmt::fmt-header-only
small::small
libevent::event)

View File

@ -8,8 +8,6 @@
#include <string_view>
#include <utility>
#include <fmt/core.h>
#include <small/map.hpp>
#include "libtransmission/transmission.h"
@ -78,23 +76,31 @@ bool tr_announce_list::replace(tr_tracker_id_t id, std::string_view announce_url
return add(announce_url_sv, tier);
}
bool tr_announce_list::add(std::string_view announce_url_sv, tr_tracker_tier_t tier)
bool tr_announce_list::add(std::string_view announce_url, tr_tracker_tier_t tier)
{
auto const announce = tr_urlParseTracker(announce_url_sv);
if (!announce || !can_add(*announce))
// Make sure the announce URL is usable before we intern it.
if (auto const announce = tr_urlParseTracker(announce_url); !announce || !can_add(*announce))
{
return false;
}
// Parse again with the interned string so that `parsed` fields all
// point to the interned addresses. This second call should never
// fail, but check anyway to make the linter happy.
auto const announce_interned = tr_interned_string{ announce_url };
auto const parsed = tr_urlParseTracker(announce_interned.sv());
if (!parsed)
{
return false;
}
auto tracker = tracker_info{};
tracker.announce = announce_url_sv;
tracker.tier = get_tier(tier, *announce);
tracker.announce = announce_interned;
tracker.announce_parsed = *parsed;
tracker.tier = get_tier(tier, *parsed);
tracker.id = next_unique_id();
tracker.host_and_port = fmt::format("{:s}:{:d}", announce->host, announce->port);
tracker.sitename = announce->sitename;
tracker.query = announce->query;
if (auto const scrape_str = announce_to_scrape(announce_url_sv); scrape_str)
if (auto const scrape_str = announce_to_scrape(tracker.announce.sv()); scrape_str)
{
tracker.scrape = *scrape_str;
}

View File

@ -14,8 +14,9 @@
#include "libtransmission/transmission.h"
#include "libtransmission/interned-string.h"
#include "libtransmission/tr-macros.h"
#include "libtransmission/tr-macros.h" // TR_CONSTEXPR20
#include "libtransmission/variant.h"
#include "libtransmission/web-utils.h"
struct tr_error;
struct tr_url_parsed_t;
@ -27,9 +28,7 @@ public:
{
tr_interned_string announce;
tr_interned_string scrape;
tr_interned_string host_and_port; // 'example.org:80'
tr_interned_string sitename; // 'example'
tr_interned_string query; // 'name=ferret'
tr_url_parsed_t announce_parsed;
tr_tracker_tier_t tier = 0;
tr_tracker_id_t id = 0;
@ -102,12 +101,12 @@ public:
void add_to_map(tr_variant::Map& setme) const;
bool add(std::string_view announce_url_sv)
bool add(std::string_view announce_url)
{
return add(announce_url_sv, this->nextTier());
return add(announce_url, this->nextTier());
}
bool add(std::string_view announce_url_sv, tr_tracker_tier_t tier);
bool add(std::string_view announce_url, tr_tracker_tier_t tier);
void add(tr_announce_list const& src);
bool remove(std::string_view announce_url);
bool remove(tr_tracker_id_t id);

View File

@ -23,6 +23,7 @@
#include "libtransmission/interned-string.h"
#include "libtransmission/net.h"
#include "libtransmission/peer-mgr.h" // tr_pex
#include "libtransmission/tr-macros.h" // tr_peer_id_t
struct tr_url_parsed_t;
@ -34,8 +35,6 @@ void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::
void tr_announcerParseHttpScrapeResponse(tr_scrape_response& response, std::string_view benc, std::string_view log_name);
tr_interned_string tr_announcerGetKey(tr_url_parsed_t const& parsed);
[[nodiscard]] constexpr std::string_view tr_announce_event_get_string(tr_announce_event e)
{
switch (e)

View File

@ -46,14 +46,13 @@ namespace
{
void verboseLog(std::string_view description, tr_direction direction, std::string_view message)
{
auto& out = std::cerr;
static bool const verbose = tr_env_key_exists("TR_CURL_VERBOSE");
if (!verbose)
if (static bool const verbose = tr_env_key_exists("TR_CURL_VERBOSE"); !verbose)
{
return;
}
auto const direction_sv = direction == TR_DOWN ? "<< "sv : ">> "sv;
auto& out = std::cerr;
out << description << '\n' << "[raw]"sv << direction_sv;
for (unsigned char const ch : message)
{
@ -319,7 +318,7 @@ void tr_announcerParseHttpAnnounceResponse(tr_announce_response& response, std::
tr_announce_response& response_;
std::string_view const log_name_;
std::optional<size_t> row_;
tr_pex pex_ = {};
tr_pex pex_;
explicit AnnounceHandler(tr_announce_response& response, std::string_view log_name)
: response_{ response }
@ -340,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_ = {};
@ -480,7 +479,7 @@ public:
private:
tr_scrape_response response_ = {};
tr_scrape_response_func response_func_ = {};
tr_scrape_response_func response_func_;
std::string log_name_;
};

View File

@ -43,12 +43,13 @@
#include "libtransmission/peer-mgr.h" // for tr_pex::fromCompact4()
#include "libtransmission/tr-assert.h"
#include "libtransmission/tr-buffer.h"
#include "libtransmission/tr-strbuf.h"
#include "libtransmission/utils.h"
#include "libtransmission/web-utils.h"
#define logwarn(interned, msg) tr_logAddWarn(msg, (interned).sv())
#define logdbg(interned, msg) tr_logAddDebug(msg, (interned).sv())
#define logtrace(interned, msg) tr_logAddTrace(msg, (interned).sv())
#define logwarn(name, msg) tr_logAddWarn(msg, name)
#define logdbg(name, msg) tr_logAddDebug(msg, name)
#define logtrace(name, msg) tr_logAddTrace(msg, name)
namespace
{
@ -69,7 +70,7 @@ auto tau_transaction_new()
}
// used in the "action" field of a request. Values defined in bep 15.
enum tau_action_t
enum tau_action_t : uint8_t
{
TAU_ACTION_CONNECT = 0,
TAU_ACTION_ANNOUNCE = 1,
@ -253,7 +254,7 @@ struct tau_announce_request
return created_at_ + TR_ANNOUNCE_TIMEOUT_SEC.count();
}
enum tau_announce_event
enum tau_announce_event : uint8_t
{
// Used in the "event" field of an announce request.
// These values come from BEP 15
@ -300,9 +301,13 @@ struct tau_tracker
{
using Mediator = tr_announcer_udp::Mediator;
tau_tracker(Mediator& mediator, tr_interned_string key_in, tr_interned_string host_in, tr_port port_in)
: key{ key_in }
, host{ host_in }
tau_tracker(
Mediator& mediator,
std::string_view const interned_authority,
std::string_view const interned_host,
tr_port const port_in)
: authority{ interned_authority }
, host{ interned_host }
, port{ port_in }
, mediator_{ mediator }
{
@ -329,13 +334,13 @@ struct tau_tracker
{
this->connection_id = buf.to_uint64();
this->connection_expiration_time = tr_time() + TauConnectionTtlSecs;
logdbg(this->key, fmt::format("Got a new connection ID from tracker: {}", this->connection_id));
logdbg(log_name(), fmt::format("Got a new connection ID from tracker: {}", this->connection_id));
}
else if (action == TAU_ACTION_ERROR)
{
std::string errmsg = !std::empty(buf) ? buf.to_string() : _("Connection failed");
this->failAll(true, false, errmsg);
logdbg(this->key, std::move(errmsg));
logdbg(log_name(), std::move(errmsg));
}
this->upkeep();
@ -363,12 +368,12 @@ struct tau_tracker
if (!addr_pending_dns_ && addr_expires_at_ <= now)
{
addr_.reset();
addr_pending_dns_ = std::async(std::launch::async, lookup, this->host, this->port, this->key);
addr_pending_dns_ = std::async(std::launch::async, lookup, this->log_name(), this->host, this->port);
return;
}
logtrace(
this->key,
log_name(),
fmt::format(
"connected {} ({} {}) -- connecting_at {}",
is_connected(now),
@ -381,7 +386,7 @@ struct tau_tracker
{
this->connecting_at = now;
this->connection_transaction_id = tau_transaction_new();
logtrace(this->key, fmt::format("Trying to connect. Transaction ID is {}", this->connection_transaction_id));
logtrace(log_name(), fmt::format("Trying to connect. Transaction ID is {}", this->connection_transaction_id));
auto buf = PayloadBuffer{};
buf.add_uint64(0x41727101980LL);
@ -416,7 +421,10 @@ private:
return connection_id != tau_connection_t{} && now < connection_expiration_time;
}
[[nodiscard]] static MaybeSockaddr lookup(tr_interned_string host, tr_port port, tr_interned_string logname)
[[nodiscard]] static MaybeSockaddr lookup(
std::string_view const interned_log_name,
std::string_view const interned_host,
tr_port const port)
{
auto szport = std::array<char, 16>{};
*fmt::format_to(std::data(szport), "{:d}", port.host()) = '\0';
@ -427,13 +435,14 @@ private:
hints.ai_socktype = SOCK_DGRAM;
addrinfo* info = nullptr;
if (int const rc = getaddrinfo(host.c_str(), std::data(szport), &hints, &info); rc != 0)
auto const szhost = tr_pathbuf{ interned_host };
if (int const rc = getaddrinfo(szhost.c_str(), std::data(szport), &hints, &info); rc != 0)
{
logwarn(
logname,
interned_log_name,
fmt::format(
_("Couldn't look up '{address}:{port}': {error} ({error_code})"),
fmt::arg("address", host.sv()),
fmt::arg("address", interned_host),
fmt::arg("port", port.host()),
fmt::arg("error", gai_strerror(rc)),
fmt::arg("error_code", static_cast<int>(rc))));
@ -445,7 +454,7 @@ private:
memcpy(&ss, info->ai_addr, len);
freeaddrinfo(info);
logdbg(logname, "DNS lookup succeeded");
logdbg(interned_log_name, "DNS lookup succeeded");
return std::make_pair(ss, len);
}
@ -486,7 +495,7 @@ private:
{
if (auto& req = *it; req.expiresAt() <= now)
{
logtrace(this->key, fmt::format("timeout {} req {}", name, fmt::ptr(&req)));
logtrace(log_name(), fmt::format("timeout {} req {}", name, fmt::ptr(&req)));
req.fail(false, true, "");
it = requests.erase(it);
}
@ -525,7 +534,7 @@ private:
continue;
}
logdbg(this->key, fmt::format("sending req {}", fmt::ptr(&req)));
logdbg(log_name(), fmt::format("sending req {}", fmt::ptr(&req)));
req.sent_at = now;
send_request(std::data(req.payload), std::size(req.payload));
@ -542,7 +551,7 @@ private:
void send_request(std::byte const* payload, size_t payload_len)
{
logdbg(this->key, fmt::format("sending request w/connection id {}", this->connection_id));
logdbg(log_name(), fmt::format("sending request w/connection id {}", this->connection_id));
auto buf = PayloadBuffer{};
buf.add_uint64(this->connection_id);
@ -552,8 +561,13 @@ private:
}
public:
tr_interned_string const key;
tr_interned_string const host;
[[nodiscard]] constexpr std::string_view log_name() const noexcept
{
return authority;
}
std::string_view const authority; // interned
std::string_view const host; // interned
tr_port const port;
time_t connecting_at = 0;
@ -567,13 +581,13 @@ public:
private:
Mediator& mediator_;
std::optional<std::future<MaybeSockaddr>> addr_pending_dns_ = {};
std::optional<std::future<MaybeSockaddr>> addr_pending_dns_;
MaybeSockaddr addr_ = {};
MaybeSockaddr addr_;
time_t addr_expires_at_ = 0;
static inline constexpr auto DnsRetryIntervalSecs = time_t{ 3600 };
static inline constexpr auto ConnectionRequestTtl = int{ 30 };
static constexpr auto DnsRetryIntervalSecs = time_t{ 3600 };
static constexpr auto ConnectionRequestTtl = 30;
};
// --- SESSION
@ -646,7 +660,7 @@ public:
// is it a connection response?
if (tracker.connecting_at != 0 && transaction_id == tracker.connection_transaction_id)
{
logtrace(tracker.key, fmt::format("{} is my connection request!", transaction_id));
logtrace(tracker.log_name(), fmt::format("{} is my connection request!", transaction_id));
tracker.on_connection_response(action_id, buf);
return true;
}
@ -660,7 +674,7 @@ public:
[&transaction_id](auto const& req) { return req.transaction_id == transaction_id; });
it != std::end(reqs))
{
logtrace(tracker.key, fmt::format("{} is an announce request!", transaction_id));
logtrace(tracker.log_name(), fmt::format("{} is an announce request!", transaction_id));
auto req = *it;
it = reqs.erase(it);
req.onResponse(action_id, buf);
@ -677,7 +691,7 @@ public:
[&transaction_id](auto const& req) { return req.transaction_id == transaction_id; });
it != std::end(reqs))
{
logtrace(tracker.key, fmt::format("{} is a scrape request!", transaction_id));
logtrace(tracker.log_name(), fmt::format("{} is a scrape request!", transaction_id));
auto req = *it;
it = reqs.erase(it);
req.onResponse(action_id, buf);
@ -698,7 +712,7 @@ public:
private:
// Finds the tau_tracker struct that corresponds to this url.
// If it doesn't exist yet, create one.
tau_tracker* getTrackerFromUrl(tr_interned_string announce_url)
tau_tracker* getTrackerFromUrl(tr_interned_string const announce_url)
{
// build a lookup key for this tracker
auto const parsed = tr_urlParseTracker(announce_url);
@ -709,20 +723,19 @@ private:
}
// see if we already have it
auto const key = tr_announcerGetKey(*parsed);
auto const authority = parsed->authority;
for (auto& tracker : trackers_)
{
if (tracker.key == key)
if (tracker.authority == authority)
{
return &tracker;
}
}
// we don't have it -- build a new one
trackers_.emplace_back(mediator_, key, tr_interned_string(parsed->host), tr_port::from_host(parsed->port));
auto* const tracker = &trackers_.back();
logtrace(tracker->key, "New tau_tracker created");
return tracker;
auto& tracker = trackers_.emplace_back(mediator_, authority, parsed->host, tr_port::from_host(parsed->port));
logtrace(tracker.log_name(), "New tau_tracker created");
return &tracker;
}
[[nodiscard]] static constexpr bool isResponseMessage(tau_action_t action, size_t msglen) noexcept

View File

@ -51,17 +51,17 @@ using namespace std::literals;
namespace
{
/* unless the tracker says otherwise, rescrape this frequently */
auto constexpr DefaultScrapeIntervalSec = int{ 60 * 30 };
auto constexpr DefaultScrapeIntervalSec = 60 * 30;
/* the value of the 'numwant' argument passed in tracker requests. */
auto constexpr Numwant = int{ 80 };
auto constexpr Numwant = 80;
/* how often to announce & scrape */
auto constexpr MaxAnnouncesPerUpkeep = int{ 20 };
auto constexpr MaxScrapesPerUpkeep = int{ 20 };
auto constexpr MaxAnnouncesPerUpkeep = 20;
auto constexpr MaxScrapesPerUpkeep = 20;
/* how many infohashes to remove when we get a scrape-too-long error */
auto constexpr TrMultiscrapeStep = int{ 5 };
auto constexpr TrMultiscrapeStep = 5;
struct StopsCompare
{
@ -240,9 +240,8 @@ std::unique_ptr<tr_announcer> tr_announcer::create(tr_session* session, tr_annou
struct tr_tracker
{
explicit tr_tracker(tr_announcer_impl* announcer, tr_announce_list::tracker_info const& info)
: host_and_port{ info.host_and_port }
, announce_url{ info.announce }
, sitename{ info.sitename }
: announce_url{ info.announce }
, announce_parsed{ info.announce_parsed }
, scrape_info{ std::empty(info.scrape) ? nullptr : announcer->scrape_info(info.scrape) }
, id{ info.id }
{
@ -335,9 +334,8 @@ struct tr_tracker
return false;
}
tr_interned_string const host_and_port;
tr_interned_string const announce_url;
std::string_view const sitename;
tr_url_parsed_t const announce_parsed;
tr_scrape_info* const scrape_info;
std::string tracker_id;
@ -353,17 +351,6 @@ private:
std::optional<int64_t> downloader_count_;
};
// format: `${host}:${port}`
tr_interned_string tr_announcerGetKey(tr_url_parsed_t const& parsed)
{
auto buf = std::array<char, 1024>{};
auto* const begin = std::data(buf);
auto const* const end = fmt::format_to_n(begin, std::size(buf), "{:s}:{:d}", parsed.host, parsed.port).out;
auto const sv = std::string_view{ begin, static_cast<size_t>(end - begin) };
return tr_interned_string{ sv };
}
// ---
/** @brief A group of trackers in a single tier, as per the multitracker spec */
@ -463,11 +450,12 @@ struct tr_tier
return std::nullopt;
}
[[nodiscard]] std::string buildLogName() const
[[nodiscard]] auto buildLogName() const
{
auto const* const current_tracker = currentTracker();
auto const host_and_port_sv = current_tracker == nullptr ? "?"sv : current_tracker->host_and_port.sv();
return fmt::format("{:s} at {:s}", tor->name(), host_and_port_sv);
auto const* const tracker = currentTracker();
return tracker != nullptr ?
fmt::format("{:s} at {:s}:{:d}", tor->name(), tracker->announce_parsed.host, tracker->announce_parsed.port) :
fmt::format("{:s} at ?", tor->name());
}
[[nodiscard]] bool canManualAnnounce() const
@ -536,10 +524,10 @@ struct tr_tier
private:
// unless the tracker says otherwise, this is the announce interval
static auto constexpr DefaultAnnounceIntervalSec = int{ 60 * 10 };
static auto constexpr DefaultAnnounceIntervalSec = 60 * 10;
// unless the tracker says otherwise, this is the announce min_interval
static auto constexpr DefaultAnnounceMinIntervalSec = int{ 60 * 2 };
static auto constexpr DefaultAnnounceMinIntervalSec = 60 * 2;
[[nodiscard]] static time_t getNextScrapeTime(tr_session const* session, tr_tier const* tier, time_t interval_secs)
{
@ -1243,27 +1231,29 @@ namespace on_scrape_done_helpers
void on_scrape_error(tr_session const* /*session*/, tr_tier* tier, char const* errmsg)
{
// increment the error count
auto* current_tracker = tier->currentTracker();
if (current_tracker != nullptr)
if (auto* const current_tracker = tier->currentTracker(); current_tracker != nullptr)
{
++current_tracker->consecutive_failures;
tr_logAddDebugTier(
tier,
fmt::format(
"Tracker '{}' scrape error: {} (Can retry in {} seconds)",
current_tracker->announce_parsed.authority,
errmsg,
current_tracker->getRetryInterval()));
}
// set the error message
tier->last_scrape_str = errmsg != nullptr ? errmsg : "";
tier->lastScrapeSucceeded = false;
// switch to the next tracker
current_tracker = tier->useNextTracker();
// schedule a rescrape
auto const interval = current_tracker->getRetryInterval();
auto const* const host_and_port_cstr = current_tracker->host_and_port.c_str();
tr_logAddDebugTier(
tier,
fmt::format("Tracker '{}' scrape error: {} (Retrying in {} seconds)", host_and_port_cstr, errmsg, interval));
tier->lastScrapeSucceeded = false;
tier->scheduleNextScrape(interval);
if (auto* const current_tracker = tier->useNextTracker(); current_tracker != nullptr)
{
// schedule a rescrape
tier->scheduleNextScrape(current_tracker->getRetryInterval());
}
}
void checkMultiscrapeMax(tr_announcer_impl* announcer, tr_scrape_response const& response)
@ -1293,7 +1283,7 @@ void checkMultiscrapeMax(tr_announcer_impl* announcer, tr_scrape_response const&
if (multiscrape_max != n)
{
// don't log the full URL, since that might have a personal announce id
tr_logAddDebug(fmt::format(FMT_STRING("Reducing multiscrape max to {:d}"), n), tr_urlTrackerLogName(url));
tr_logAddDebug(fmt::format("Reducing multiscrape max to {:d}", n), tr_urlTrackerLogName(url));
multiscrape_max = n;
}
@ -1485,9 +1475,9 @@ int compareAnnounceTiers(tr_tier const* a, tr_tier const* b)
}
/* prefer swarms where we might download */
if (auto const is_done_a = a->tor->is_done(), is_done_b = b->tor->is_done(); is_done_a != is_done_b)
if (auto const val = tr_compare_3way(a->tor->is_done(), b->tor->is_done()); val != 0)
{
return is_done_a ? 1 : -1;
return val;
}
/* prefer larger stats, to help ensure stats get recorded when stopping on shutdown */
@ -1617,17 +1607,20 @@ namespace tracker_view_helpers
{
[[nodiscard]] auto trackerView(tr_torrent const& tor, size_t tier_index, tr_tier const& tier, tr_tracker const& tracker)
{
auto const& announce = tracker.announce_parsed;
auto const now = tr_time();
auto view = tr_tracker_view{};
view.host_and_port = tracker.host_and_port.c_str();
*fmt::format_to_n(
std::data(view.host_and_port),
std::size(view.host_and_port) - 1U,
"{:s}:{:d}",
announce.host,
announce.port)
.out = '\0';
*fmt::format_to_n(std::data(view.sitename), std::size(view.sitename) - 1U, "{:s}", announce.sitename).out = '\0';
view.announce = tracker.announce_url.c_str();
view.scrape = tracker.scrape_info == nullptr ? "" : tracker.scrape_info->scrape_url.c_str();
*std::copy_n(
std::begin(tracker.sitename),
std::min(std::size(tracker.sitename), sizeof(view.sitename) - 1),
view.sitename) = '\0';
view.id = tracker.id;
view.tier = tier_index;
view.isBackup = &tracker != tier.currentTracker();
@ -1651,7 +1644,8 @@ namespace tracker_view_helpers
view.lastScrapeTime = tier.lastScrapeTime;
view.lastScrapeSucceeded = tier.lastScrapeSucceeded;
view.lastScrapeTimedOut = tier.lastScrapeTimedOut;
tr_strlcpy(view.lastScrapeResult, tier.last_scrape_str.c_str(), sizeof(view.lastScrapeResult));
auto& buf = view.lastScrapeResult;
*fmt::format_to_n(buf, sizeof(buf) - 1, "{:s}", tier.last_scrape_str).out = '\0';
}
if (tier.isScraping)
@ -1681,7 +1675,8 @@ namespace tracker_view_helpers
view.lastAnnounceSucceeded = tier.lastAnnounceSucceeded;
view.lastAnnounceTimedOut = tier.lastAnnounceTimedOut;
view.lastAnnouncePeerCount = tier.lastAnnouncePeerCount;
tr_strlcpy(view.lastAnnounceResult, tier.last_announce_str.c_str(), sizeof(view.lastAnnounceResult));
auto& buf = view.lastAnnounceResult;
*fmt::format_to_n(buf, sizeof(buf) - 1, "{:s}", tier.last_announce_str).out = '\0';
}
if (tier.isAnnouncing)

View File

@ -8,6 +8,7 @@
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <limits>
#include <memory>
#include <utility> // for std::swap()
#include <vector>
@ -28,14 +29,14 @@ using namespace libtransmission::Values;
Speed tr_bandwidth::get_speed(RateControl& r, unsigned int interval_msec, uint64_t now)
{
if (now == 0)
if (now == 0U)
{
now = tr_time_msec();
}
if (now != r.cache_time_)
{
uint64_t bytes = 0;
uint64_t bytes = 0U;
uint64_t const cutoff = now - interval_msec;
for (int i = r.newest_; r.date_[i] > cutoff;)
@ -78,14 +79,15 @@ void tr_bandwidth::notify_bandwidth_consumed_bytes(uint64_t const now, RateContr
}
/* invalidate cache_val*/
r.cache_time_ = 0;
r.cache_time_ = 0U;
}
// ---
tr_bandwidth::tr_bandwidth(tr_bandwidth* new_parent)
tr_bandwidth::tr_bandwidth(tr_bandwidth* parent, bool is_group)
: priority_(is_group ? std::numeric_limits<tr_priority_t>::max() : TR_PRI_NORMAL)
{
this->set_parent(new_parent);
set_parent(parent);
}
// ---
@ -135,7 +137,7 @@ void tr_bandwidth::set_parent(tr_bandwidth* new_parent)
#endif
new_parent->children_.push_back(this);
this->parent_ = new_parent;
parent_ = new_parent;
}
}
@ -146,7 +148,7 @@ void tr_bandwidth::allocate_bandwidth(
uint64_t period_msec,
std::vector<std::shared_ptr<tr_peerIo>>& peer_pool)
{
auto const priority = std::max(parent_priority, this->priority_);
auto const priority = std::min(parent_priority, priority_);
// set the available bandwidth
for (auto const dir : { TR_UP, TR_DOWN })
@ -159,14 +161,15 @@ void tr_bandwidth::allocate_bandwidth(
}
// add this bandwidth's peer, if any, to the peer pool
if (auto shared = this->peer_.lock(); shared)
if (auto shared = peer_.lock(); shared)
{
TR_ASSERT(tr_isPriority(priority));
shared->set_priority(priority);
peer_pool.push_back(std::move(shared));
}
// traverse & repeat for the subtree
for (auto* child : this->children_)
for (auto* child : children_)
{
child->allocate_bandwidth(priority, period_msec, peer_pool);
}
@ -186,7 +189,7 @@ void tr_bandwidth::phase_one(std::vector<tr_peerIo*>& peers, tr_direction dir)
// process until we run out of bandwidth and/or peers that can use it.
for (size_t n_unfinished = std::size(peers); n_unfinished > 0U;)
{
for (size_t i = 0; i < n_unfinished;)
for (size_t i = 0U; i < n_unfinished;)
{
// Value of 3000 bytes chosen so that when using µTP we'll send a full-size
// frame right away and leave enough buffered data for the next frame to go
@ -223,7 +226,7 @@ void tr_bandwidth::allocate(uint64_t period_msec)
// allocateBandwidth () is a helper function with two purposes:
// 1. allocate bandwidth to b and its subtree
// 2. accumulate an array of all the peerIos from b and its subtree.
this->allocate_bandwidth(TR_PRI_LOW, period_msec, refs);
allocate_bandwidth(std::numeric_limits<tr_priority_t>::max(), period_msec, refs);
for (auto const& io : refs)
{
@ -239,8 +242,13 @@ void tr_bandwidth::allocate(uint64_t period_msec)
normal.push_back(io.get());
[[fallthrough]];
default:
case TR_PRI_LOW:
low.push_back(io.get());
break;
default:
TR_ASSERT_MSG(false, "invalid priority");
break;
}
}
@ -271,14 +279,14 @@ size_t tr_bandwidth::clamp(tr_direction const dir, size_t byte_count) const noex
{
TR_ASSERT(tr_isDirection(dir));
if (this->band_[dir].is_limited_)
if (band_[dir].is_limited_)
{
byte_count = std::min(byte_count, this->band_[dir].bytes_left_);
byte_count = std::min(byte_count, band_[dir].bytes_left_);
}
if (this->parent_ != nullptr && this->band_[dir].honor_parent_limits_ && byte_count > 0)
if (parent_ != nullptr && band_[dir].honor_parent_limits_ && byte_count > 0U)
{
byte_count = this->parent_->clamp(dir, byte_count);
byte_count = parent_->clamp(dir, byte_count);
}
return byte_count;
@ -288,39 +296,23 @@ void tr_bandwidth::notify_bandwidth_consumed(tr_direction dir, size_t byte_count
{
TR_ASSERT(tr_isDirection(dir));
Band* band = &this->band_[dir];
auto& band = band_[dir];
if (band->is_limited_ && is_piece_data)
if (band.is_limited_ && is_piece_data)
{
band->bytes_left_ -= std::min(band->bytes_left_, byte_count);
band.bytes_left_ -= std::min(band.bytes_left_, byte_count);
}
#ifdef DEBUG_DIRECTION
if (dir == DEBUG_DIRECTION && band_->isLimited)
{
fprintf(
stderr,
"%p consumed %5zu bytes of %5s data... was %6zu, now %6zu left\n",
this,
byte_count,
is_piece_data ? "piece" : "raw",
oldBytesLeft,
band_->bytesLeft);
}
#endif
notify_bandwidth_consumed_bytes(now, band->raw_, byte_count);
notify_bandwidth_consumed_bytes(now, band.raw_, byte_count);
if (is_piece_data)
{
notify_bandwidth_consumed_bytes(now, band->piece_, byte_count);
notify_bandwidth_consumed_bytes(now, band.piece_, byte_count);
}
if (this->parent_ != nullptr)
if (parent_ != nullptr)
{
this->parent_->notify_bandwidth_consumed(dir, byte_count, is_piece_data, now);
parent_->notify_bandwidth_consumed(dir, byte_count, is_piece_data, now);
}
}
@ -329,17 +321,17 @@ void tr_bandwidth::notify_bandwidth_consumed(tr_direction dir, size_t byte_count
tr_bandwidth_limits tr_bandwidth::get_limits() const
{
auto limits = tr_bandwidth_limits{};
limits.up_limit = this->get_desired_speed(TR_UP);
limits.down_limit = this->get_desired_speed(TR_DOWN);
limits.up_limited = this->is_limited(TR_UP);
limits.down_limited = this->is_limited(TR_DOWN);
limits.up_limit = get_desired_speed(TR_UP);
limits.down_limit = get_desired_speed(TR_DOWN);
limits.up_limited = is_limited(TR_UP);
limits.down_limited = is_limited(TR_DOWN);
return limits;
}
void tr_bandwidth::set_limits(tr_bandwidth_limits const& limits)
{
this->set_desired_speed(TR_UP, limits.up_limit);
this->set_desired_speed(TR_DOWN, limits.down_limit);
this->set_limited(TR_UP, limits.up_limited);
this->set_limited(TR_DOWN, limits.down_limited);
set_desired_speed(TR_UP, limits.up_limit);
set_desired_speed(TR_DOWN, limits.down_limit);
set_limited(TR_UP, limits.up_limited);
set_limited(TR_DOWN, limits.down_limited);
}

View File

@ -80,15 +80,15 @@ struct tr_bandwidth
private:
using Speed = libtransmission::Values::Speed;
static constexpr size_t HistoryMSec = 2000U;
static constexpr size_t GranularityMSec = 250U;
static constexpr size_t HistorySize = HistoryMSec / GranularityMSec;
static constexpr auto HistoryMSec = 2000U;
static constexpr auto GranularityMSec = 250U;
static constexpr auto HistorySize = HistoryMSec / GranularityMSec;
public:
explicit tr_bandwidth(tr_bandwidth* new_parent);
explicit tr_bandwidth(tr_bandwidth* parent, bool is_group = false);
tr_bandwidth()
: tr_bandwidth{ nullptr }
explicit tr_bandwidth(bool is_group = false)
: tr_bandwidth{ nullptr, is_group }
{
}
@ -107,7 +107,7 @@ public:
*/
void set_peer(std::weak_ptr<tr_peerIo> peer) noexcept
{
this->peer_ = std::move(peer);
peer_ = std::move(peer);
}
/**
@ -123,14 +123,14 @@ public:
void set_parent(tr_bandwidth* new_parent);
[[nodiscard]] constexpr tr_priority_t get_priority() const noexcept
[[nodiscard]] constexpr auto get_priority() const noexcept
{
return this->priority_;
return priority_;
}
constexpr void set_priority(tr_priority_t prio) noexcept
{
this->priority_ = prio;
priority_ = prio;
}
/**
@ -143,7 +143,7 @@ public:
{
TR_ASSERT(tr_isDirection(dir));
return get_speed(this->band_[dir].raw_, HistoryMSec, now);
return get_speed(band_[dir].raw_, HistoryMSec, now);
}
/** @brief Get the number of piece data bytes read or sent by this bandwidth subtree. */
@ -151,7 +151,7 @@ public:
{
TR_ASSERT(tr_isDirection(dir));
return get_speed(this->band_[dir].piece_, HistoryMSec, now);
return get_speed(band_[dir].piece_, HistoryMSec, now);
}
/**
@ -161,8 +161,8 @@ public:
*/
constexpr bool set_desired_speed(tr_direction dir, Speed desired_speed)
{
auto& value = this->band_[dir].desired_speed_;
bool const did_change = desired_speed != value;
auto& value = band_[dir].desired_speed_;
auto const did_change = desired_speed != value;
value = desired_speed;
return did_change;
}
@ -173,7 +173,7 @@ public:
*/
[[nodiscard]] constexpr auto get_desired_speed(tr_direction dir) const
{
return this->band_[dir].desired_speed_;
return band_[dir].desired_speed_;
}
[[nodiscard]] bool is_maxed_out(tr_direction dir, uint64_t now_msec) const noexcept
@ -193,8 +193,8 @@ public:
*/
constexpr bool set_limited(tr_direction dir, bool is_limited)
{
bool& value = this->band_[dir].is_limited_;
bool const did_change = is_limited != value;
auto& value = band_[dir].is_limited_;
auto const did_change = is_limited != value;
value = is_limited;
return did_change;
}
@ -204,7 +204,7 @@ public:
*/
[[nodiscard]] constexpr bool is_limited(tr_direction dir) const noexcept
{
return this->band_[dir].is_limited_;
return band_[dir].is_limited_;
}
/**
@ -215,17 +215,15 @@ public:
*/
constexpr bool honor_parent_limits(tr_direction direction, bool is_enabled)
{
bool& value = this->band_[direction].honor_parent_limits_;
bool const did_change = is_enabled != value;
auto& value = band_[direction].honor_parent_limits_;
auto const did_change = is_enabled != value;
value = is_enabled;
return did_change;
}
[[nodiscard]] constexpr bool are_parent_limits_honored(tr_direction direction) const
{
TR_ASSERT(tr_isDirection(direction));
return this->band_[direction].honor_parent_limits_;
return band_[direction].honor_parent_limits_;
}
[[nodiscard]] tr_bandwidth_limits get_limits() const;
@ -274,7 +272,12 @@ private:
std::vector<tr_bandwidth*> children_;
tr_bandwidth* parent_ = nullptr;
std::weak_ptr<tr_peerIo> peer_;
tr_priority_t priority_ = 0;
tr_priority_t priority_;
};
/* @} */
constexpr auto tr_isPriority(tr_priority_t p)
{
return p == TR_PRI_LOW || p == TR_PRI_NORMAL || p == TR_PRI_HIGH;
}

View File

@ -8,7 +8,7 @@
#include "libtransmission/block-info.h"
#include "libtransmission/tr-assert.h" // TR_ASSERT
void tr_block_info::init_sizes(uint64_t total_size_in, uint32_t piece_size_in) noexcept
void tr_block_info::init_sizes(uint64_t const total_size_in, uint32_t const piece_size_in) noexcept
{
TR_ASSERT(piece_size_in == 0 || piece_size_in >= BlockSize);
if (piece_size_in == 0)
@ -19,8 +19,8 @@ void tr_block_info::init_sizes(uint64_t total_size_in, uint32_t piece_size_in) n
total_size_ = total_size_in;
piece_size_ = piece_size_in;
n_pieces_ = (total_size_ + piece_size_ - 1) / piece_size_;
n_blocks_ = (total_size_ + BlockSize - 1) / BlockSize;
n_pieces_ = static_cast<tr_piece_index_t>((total_size_ + piece_size_ - 1) / piece_size_);
n_blocks_ = static_cast<tr_block_index_t>((total_size_ + BlockSize - 1) / BlockSize);
uint32_t remainder = total_size_ % piece_size_;
final_piece_size_ = remainder != 0U ? remainder : piece_size_;

View File

@ -11,37 +11,26 @@
struct tr_block_info
{
private:
uint64_t total_size_ = 0;
uint32_t piece_size_ = 0;
tr_piece_index_t n_pieces_ = 0;
tr_block_index_t n_blocks_ = 0;
// should be same type as BlockSize
uint32_t final_block_size_ = 0;
// should be same type as piece_size
uint32_t final_piece_size_ = 0;
public:
static auto constexpr BlockSize = uint32_t{ 1024 * 16 };
static auto constexpr BlockSize = uint32_t{ 1024U * 16U };
tr_block_info() noexcept = default;
tr_block_info() noexcept
{
}
tr_block_info(uint64_t total_size_in, uint32_t piece_size_in) noexcept
tr_block_info(uint64_t const total_size_in, uint32_t const piece_size_in) noexcept
{
init_sizes(total_size_in, piece_size_in);
}
void init_sizes(uint64_t total_size_in, uint32_t piece_size_in) noexcept;
[[nodiscard]] constexpr auto block_count() const noexcept
{
return n_blocks_;
}
[[nodiscard]] constexpr auto block_size(tr_block_index_t block) const noexcept
[[nodiscard]] constexpr auto block_size(tr_block_index_t const block) const noexcept
{
return block + 1 == n_blocks_ ? final_block_size_ : BlockSize;
return block + 1U == n_blocks_ ? final_block_size_ : BlockSize;
}
[[nodiscard]] constexpr auto piece_count() const noexcept
@ -54,9 +43,9 @@ public:
return piece_size_;
}
[[nodiscard]] constexpr auto piece_size(tr_piece_index_t piece) const noexcept
[[nodiscard]] constexpr auto piece_size(tr_piece_index_t const piece) const noexcept
{
return piece + 1 == n_pieces_ ? final_piece_size_ : piece_size();
return piece + 1U == n_pieces_ ? final_piece_size_ : piece_size();
}
[[nodiscard]] constexpr auto total_size() const noexcept
@ -66,14 +55,6 @@ public:
struct Location
{
uint64_t byte = 0;
tr_piece_index_t piece = 0;
uint32_t piece_offset = 0;
tr_block_index_t block = 0;
uint32_t block_offset = 0;
[[nodiscard]] constexpr bool operator==(Location const& that) const noexcept
{
return this->byte == that.byte;
@ -83,10 +64,18 @@ public:
{
return this->byte < that.byte;
}
uint64_t byte = {};
tr_piece_index_t piece = {};
uint32_t piece_offset = {};
tr_block_index_t block = {};
uint32_t block_offset = {};
};
// Location of the torrent's nth byte
[[nodiscard]] constexpr auto byte_loc(uint64_t byte_idx) const noexcept
[[nodiscard]] constexpr auto byte_loc(uint64_t const byte_idx) const noexcept
{
auto loc = Location{};
@ -96,13 +85,13 @@ public:
if (byte_idx == total_size()) // handle 0-byte files at the end of a torrent
{
loc.block = block_count() - 1;
loc.piece = piece_count() - 1;
loc.block = block_count() - 1U;
loc.piece = piece_count() - 1U;
}
else
{
loc.block = byte_idx / BlockSize;
loc.piece = byte_idx / piece_size();
loc.block = static_cast<tr_block_index_t>(byte_idx / BlockSize);
loc.piece = static_cast<tr_piece_index_t>(byte_idx / piece_size());
}
loc.block_offset = static_cast<uint32_t>(loc.byte - (uint64_t{ loc.block } * BlockSize));
@ -113,28 +102,28 @@ public:
}
// Location of the first byte in `block`.
[[nodiscard]] constexpr auto block_loc(tr_block_index_t block) const noexcept
[[nodiscard]] constexpr auto block_loc(tr_block_index_t const block) const noexcept
{
return byte_loc(uint64_t{ block } * BlockSize);
}
// Location of the first byte (+ optional offset and length) in `piece`
[[nodiscard]] constexpr auto piece_loc(tr_piece_index_t piece, uint32_t offset = 0, uint32_t length = 0) const noexcept
[[nodiscard]] constexpr auto piece_loc(tr_piece_index_t piece, uint32_t offset = {}, uint32_t length = {}) const noexcept
{
return byte_loc(uint64_t{ piece } * piece_size() + offset + length);
}
[[nodiscard]] constexpr tr_block_span_t block_span_for_piece(tr_piece_index_t piece) const noexcept
[[nodiscard]] constexpr tr_block_span_t block_span_for_piece(tr_piece_index_t const piece) const noexcept
{
if (!is_initialized())
{
return { 0U, 0U };
}
return { piece_loc(piece).block, piece_last_loc(piece).block + 1 };
return { piece_loc(piece).block, piece_last_loc(piece).block + 1U };
}
[[nodiscard]] constexpr tr_byte_span_t byte_span_for_piece(tr_piece_index_t piece) const noexcept
[[nodiscard]] constexpr tr_byte_span_t byte_span_for_piece(tr_piece_index_t const piece) const noexcept
{
if (!is_initialized())
{
@ -146,14 +135,26 @@ public:
}
private:
void init_sizes(uint64_t total_size_in, uint32_t piece_size_in) noexcept;
// Location of the last byte in `piece`.
[[nodiscard]] constexpr Location piece_last_loc(tr_piece_index_t piece) const noexcept
[[nodiscard]] constexpr Location piece_last_loc(tr_piece_index_t const piece) const noexcept
{
return byte_loc(static_cast<uint64_t>(piece) * piece_size() + piece_size(piece) - 1);
}
[[nodiscard]] constexpr bool is_initialized() const noexcept
{
return piece_size_ != 0;
return piece_size_ != 0U;
}
uint64_t total_size_ = {};
tr_block_index_t n_blocks_ = {};
tr_piece_index_t n_pieces_ = {};
uint32_t final_block_size_ = {};
uint32_t piece_size_ = {};
uint32_t final_piece_size_ = {};
};

View File

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

View File

@ -9,7 +9,6 @@
#include <cstdint> // uint8_t
#include <iterator> // std::distance(), std::next(), std::prev()
#include <memory>
#include <mutex>
#include <numeric> // std::accumulate()
#include <utility> // std::make_pair()
#include <vector>
@ -25,9 +24,9 @@
#include "libtransmission/torrents.h"
#include "libtransmission/tr-assert.h"
Cache::Key Cache::make_key(tr_torrent const* torrent, tr_block_info::Location loc) noexcept
Cache::Key Cache::make_key(tr_torrent const& tor, tr_block_info::Location const loc) noexcept
{
return std::make_pair(torrent->id(), loc.block);
return std::make_pair(tor.id(), loc.block);
}
Cache::CIter Cache::find_span_end(CIter span_begin, CIter end) noexcept
@ -104,7 +103,7 @@ int Cache::write_contiguous(CIter const begin, CIter const end) const
auto const loc = tor->block_loc(block);
if (auto const err = tr_ioWrite(tor, loc, outlen, out); err != 0)
if (auto const err = tr_ioWrite(*tor, loc, outlen, out); err != 0)
{
return err;
}
@ -116,8 +115,6 @@ int Cache::write_contiguous(CIter const begin, CIter const end) const
int Cache::set_limit(Memory const max_size)
{
auto const lock = std::lock_guard{ mutex_ };
max_blocks_ = get_max_blocks(max_size);
tr_logAddDebug(fmt::format("Maximum cache size set to {} ({} blocks)", max_size.to_string(), max_blocks_));
@ -132,7 +129,7 @@ Cache::Cache(tr_torrents const& torrents, Memory const max_size)
// ---
int Cache::write_block(tr_torrent_id_t tor_id, tr_block_index_t block, std::unique_ptr<BlockData> writeme)
int Cache::write_block(tr_torrent_id_t const tor_id, tr_block_index_t const block, std::unique_ptr<BlockData> writeme)
{
if (max_blocks_ == 0U)
{
@ -142,11 +139,9 @@ int Cache::write_block(tr_torrent_id_t tor_id, tr_block_index_t block, std::uniq
// already has a cache layer for the very purpose of this cache
// https://github.com/transmission/transmission/pull/5668
auto* const tor = torrents_.get(tor_id);
return tr_ioWrite(tor, tor->block_loc(block), std::size(*writeme), std::data(*writeme));
return tor == nullptr ? EINVAL : tr_ioWrite(*tor, tor->block_loc(block), std::size(*writeme), std::data(*writeme));
}
auto const lock = std::lock_guard{ mutex_ };
auto const key = Key{ tor_id, block };
auto iter = std::lower_bound(std::begin(blocks_), std::end(blocks_), key, CompareCacheBlockByKey);
if (iter == std::end(blocks_) || iter->key != key)
@ -163,12 +158,12 @@ int Cache::write_block(tr_torrent_id_t tor_id, tr_block_index_t block, std::uniq
return cache_trim();
}
Cache::CIter Cache::get_block(tr_torrent const* torrent, tr_block_info::Location const& loc) noexcept
Cache::CIter Cache::get_block(tr_torrent const& tor, tr_block_info::Location const& loc) noexcept
{
if (auto const [begin, end] = std::equal_range(
std::begin(blocks_),
std::end(blocks_),
make_key(torrent, loc),
make_key(tor, loc),
CompareCacheBlockByKey);
begin < end)
{
@ -178,29 +173,15 @@ Cache::CIter Cache::get_block(tr_torrent const* torrent, tr_block_info::Location
return std::end(blocks_);
}
int Cache::read_block(tr_torrent* torrent, tr_block_info::Location const& loc, size_t len, uint8_t* setme)
int Cache::read_block(tr_torrent const& tor, tr_block_info::Location const& loc, size_t len, uint8_t* setme)
{
auto lock = std::unique_lock{ mutex_ };
if (auto const iter = get_block(torrent, loc); iter != std::end(blocks_))
if (auto const iter = get_block(tor, loc); iter != std::end(blocks_))
{
std::copy_n(std::begin(*iter->buf), len, setme);
return {};
}
lock.unlock();
return tr_ioRead(torrent, loc, len, setme);
}
int Cache::prefetch_block(tr_torrent* torrent, tr_block_info::Location const& loc, size_t len)
{
auto lock = std::unique_lock{ mutex_ };
if (auto const iter = get_block(torrent, loc); iter != std::end(blocks_))
{
return {}; // already have it
}
lock.unlock();
return tr_ioPrefetch(torrent, loc, len);
return tr_ioRead(tor, loc, len, setme);
}
// ---
@ -223,22 +204,18 @@ int Cache::flush_span(CIter const begin, CIter const end)
return {};
}
int Cache::flush_file(tr_torrent const* const torrent, tr_file_index_t const file)
int Cache::flush_file(tr_torrent const& tor, tr_file_index_t const file)
{
auto const tor_id = torrent->id();
auto const [block_begin, block_end] = torrent->block_span_for_file(file);
auto const tor_id = tor.id();
auto const [block_begin, block_end] = tor.block_span_for_file(file);
auto const lock = std::lock_guard{ mutex_ };
return flush_span(
std::lower_bound(std::begin(blocks_), std::end(blocks_), std::make_pair(tor_id, block_begin), CompareCacheBlockByKey),
std::lower_bound(std::begin(blocks_), std::end(blocks_), std::make_pair(tor_id, block_end), CompareCacheBlockByKey));
}
int Cache::flush_torrent(tr_torrent const* torrent)
int Cache::flush_torrent(tr_torrent_id_t const tor_id)
{
auto const tor_id = torrent->id();
auto const lock = std::lock_guard{ mutex_ };
return flush_span(
std::lower_bound(std::begin(blocks_), std::end(blocks_), std::make_pair(tor_id, 0), CompareCacheBlockByKey),
std::lower_bound(std::begin(blocks_), std::end(blocks_), std::make_pair(tor_id + 1, 0), CompareCacheBlockByKey));

View File

@ -12,7 +12,6 @@
#include <cstddef> // for size_t
#include <cstdint> // for intX_t, uintX_t
#include <memory> // for std::unique_ptr
#include <mutex>
#include <utility> // for std::pair
#include <vector>
@ -39,10 +38,9 @@ public:
// @return any error code from cacheTrim()
int write_block(tr_torrent_id_t tor, tr_block_index_t block, std::unique_ptr<BlockData> writeme);
int read_block(tr_torrent* torrent, tr_block_info::Location const& loc, size_t len, uint8_t* setme);
int prefetch_block(tr_torrent* torrent, tr_block_info::Location const& loc, size_t len);
int flush_torrent(tr_torrent const* torrent);
int flush_file(tr_torrent const* torrent, tr_file_index_t file);
int read_block(tr_torrent const& tor, tr_block_info::Location const& loc, size_t len, uint8_t* setme);
int flush_torrent(tr_torrent_id_t tor_id);
int flush_file(tr_torrent const& tor, tr_file_index_t file);
private:
using Key = std::pair<tr_torrent_id_t, tr_block_index_t>;
@ -56,7 +54,7 @@ private:
using Blocks = std::vector<CacheBlock>;
using CIter = Blocks::const_iterator;
[[nodiscard]] static Key make_key(tr_torrent const* torrent, tr_block_info::Location loc) noexcept;
[[nodiscard]] static Key make_key(tr_torrent const& tor, tr_block_info::Location loc) noexcept;
[[nodiscard]] static std::pair<CIter, CIter> find_biggest_span(CIter begin, CIter end) noexcept;
@ -79,7 +77,7 @@ private:
return max_size.base_quantity() / tr_block_info::BlockSize;
}
[[nodiscard]] CIter get_block(tr_torrent const* torrent, tr_block_info::Location const& loc) noexcept;
[[nodiscard]] CIter get_block(tr_torrent const& tor, tr_block_info::Location const& loc) noexcept;
tr_torrents const& torrents_;
@ -91,15 +89,13 @@ private:
mutable size_t cache_writes_ = 0;
mutable size_t cache_write_bytes_ = 0;
mutable std::mutex mutex_;
static constexpr struct
{
[[nodiscard]] constexpr bool operator()(Key const& key, CacheBlock const& block)
[[nodiscard]] constexpr bool operator()(Key const& key, CacheBlock const& block) const
{
return key < block.key;
}
[[nodiscard]] constexpr bool operator()(CacheBlock const& block, Key const& key)
[[nodiscard]] constexpr bool operator()(CacheBlock const& block, Key const& key) const
{
return block.key < key;
}

View File

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

View File

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

View File

@ -93,101 +93,72 @@ bool check_ccrypto_result(CCCryptorStatus result, char const* file, long line)
} // namespace
// ---
// --- sha1
namespace
tr_sha1::tr_sha1()
{
class Sha1Impl final : public tr_sha1
{
public:
Sha1Impl()
{
clear();
}
~Sha1Impl() override = default;
void clear() override
{
CC_SHA1_Init(&handle_);
}
void add(void const* data, size_t data_length) override
{
static auto constexpr Max = static_cast<size_t>(std::numeric_limits<CC_LONG>::max());
auto const* sha_data = static_cast<uint8_t const*>(data);
while (data_length > 0)
{
auto const n_bytes = static_cast<CC_LONG>(std::min(data_length, Max));
CC_SHA1_Update(&handle_, sha_data, n_bytes);
data_length -= n_bytes;
sha_data += n_bytes;
}
}
[[nodiscard]] tr_sha1_digest_t finish() override
{
auto digest = tr_sha1_digest_t{};
CC_SHA1_Final(reinterpret_cast<unsigned char*>(std::data(digest)), &handle_);
clear();
return digest;
}
private:
CC_SHA1_CTX handle_ = {};
};
class Sha256Impl final : public tr_sha256
{
public:
Sha256Impl()
{
clear();
}
~Sha256Impl() override = default;
void clear() override
{
CC_SHA256_Init(&handle_);
}
void add(void const* data, size_t data_length) override
{
static auto constexpr Max = static_cast<size_t>(std::numeric_limits<CC_LONG>::max());
auto const* sha_data = static_cast<uint8_t const*>(data);
while (data_length > 0)
{
auto const n_bytes = static_cast<CC_LONG>(std::min(data_length, Max));
CC_SHA256_Update(&handle_, sha_data, n_bytes);
data_length -= n_bytes;
sha_data += n_bytes;
}
}
[[nodiscard]] tr_sha256_digest_t finish() override
{
auto digest = tr_sha256_digest_t{};
CC_SHA256_Final(reinterpret_cast<unsigned char*>(std::data(digest)), &handle_);
clear();
return digest;
}
private:
CC_SHA256_CTX handle_;
};
} // namespace
std::unique_ptr<tr_sha1> tr_sha1::create()
{
return std::make_unique<Sha1Impl>();
clear();
}
std::unique_ptr<tr_sha256> tr_sha256::create()
tr_sha1::~tr_sha1()
{
return std::make_unique<Sha256Impl>();
}
void tr_sha1::clear()
{
CC_SHA1_Init(&handle_);
}
void tr_sha1::add(void const* data, size_t data_length)
{
if (data_length == 0U)
{
return;
}
CC_SHA1_Update(&handle_, data, data_length);
}
tr_sha1_digest_t tr_sha1::finish()
{
auto digest = tr_sha1_digest_t{};
CC_SHA1_Final(reinterpret_cast<unsigned char*>(std::data(digest)), &handle_);
clear();
return digest;
}
// --- sha256
tr_sha256::tr_sha256()
{
clear();
}
tr_sha256::~tr_sha256()
{
}
void tr_sha256::clear()
{
CC_SHA256_Init(&handle_);
}
void tr_sha256::add(void const* data, size_t data_length)
{
if (data_length == 0U)
{
return;
}
CC_SHA256_Update(&handle_, data, data_length);
}
tr_sha256_digest_t tr_sha256::finish()
{
auto digest = tr_sha256_digest_t{};
CC_SHA256_Final(reinterpret_cast<unsigned char*>(std::data(digest)), &handle_);
clear();
return digest;
}
// ---

View File

@ -25,9 +25,13 @@
#define TR_CRYPTO_X509_FALLBACK
#include "libtransmission/crypto-utils-fallback.cc" // NOLINT(bugprone-suspicious-include)
// ---
#if !defined(WITH_MBEDTLS)
#error mbedtls module
#endif
static void log_mbedtls_error(int error_code, char const* file, int line)
namespace
{
void log_mbedtls_error(int error_code, char const* file, int line)
{
if (tr_logLevelIsActive(TR_LOG_ERROR))
{
@ -48,7 +52,7 @@ static void log_mbedtls_error(int error_code, char const* file, int line)
#define log_error(error_code) log_mbedtls_error((error_code), __FILE__, __LINE__)
static bool check_mbedtls_result(int result, int expected_result, char const* file, int line)
bool check_mbedtls_result(int result, int expected_result, char const* file, int line)
{
bool const ret = result == expected_result;
@ -65,14 +69,14 @@ static bool check_mbedtls_result(int result, int expected_result, char const* fi
// ---
static int my_rand(void* /*context*/, unsigned char* buffer, size_t buffer_size)
int my_rand(void* /*context*/, unsigned char* buffer, size_t buffer_size)
{
// since we're initializing tr_rand_buffer()'s rng, we can't use tr_rand_buffer() here
tr_rand_buffer_std(buffer, buffer_size);
return 0;
}
static mbedtls_ctr_drbg_context* get_rng()
mbedtls_ctr_drbg_context* get_rng()
{
static mbedtls_ctr_drbg_context rng;
static bool rng_initialized = false;
@ -97,125 +101,102 @@ static mbedtls_ctr_drbg_context* get_rng()
return &rng;
}
static std::recursive_mutex rng_mutex_;
// ---
namespace
{
class Sha1Impl final : public tr_sha1
{
public:
Sha1Impl()
{
clear();
}
~Sha1Impl() override = default;
void clear() override
{
mbedtls_sha1_init(&handle_);
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha1_starts_ret(&handle_);
#else
mbedtls_sha1_starts(&handle_);
#endif
}
void add(void const* data, size_t data_length) override
{
if (data_length > 0U)
{
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha1_update_ret(&handle_, static_cast<unsigned char const*>(data), data_length);
#else
mbedtls_sha1_update(&handle_, static_cast<unsigned char const*>(data), data_length);
#endif
}
}
[[nodiscard]] tr_sha1_digest_t finish() override
{
auto digest = tr_sha1_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha1_finish_ret(&handle_, digest_as_uchar);
#else
mbedtls_sha1_finish(&handle_, digest_as_uchar);
#endif
mbedtls_sha1_free(&handle_);
return digest;
}
private:
mbedtls_sha1_context handle_ = {};
};
class Sha256Impl final : public tr_sha256
{
public:
Sha256Impl()
{
clear();
}
~Sha256Impl() override = default;
void clear() override
{
mbedtls_sha256_init(&handle_);
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha256_starts_ret(&handle_, 0);
#else
mbedtls_sha256_starts(&handle_);
#endif
}
void add(void const* data, size_t data_length) override
{
if (data_length > 0U)
{
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha256_update_ret(&handle_, static_cast<unsigned char const*>(data), data_length);
#else
mbedtls_sha256_update(&handle_, static_cast<unsigned char const*>(data), data_length);
#endif
}
}
[[nodiscard]] tr_sha256_digest_t finish() override
{
auto digest = tr_sha256_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha256_finish_ret(&handle_, digest_as_uchar);
#else
mbedtls_sha256_finish(&handle_, digest_as_uchar);
#endif
mbedtls_sha256_free(&handle_);
return digest;
}
private:
mbedtls_sha256_context handle_ = {};
};
std::recursive_mutex rng_mutex_;
} // namespace
std::unique_ptr<tr_sha1> tr_sha1::create()
// --- sha1
tr_sha1::tr_sha1()
{
return std::make_unique<Sha1Impl>();
clear();
}
std::unique_ptr<tr_sha256> tr_sha256::create()
tr_sha1::~tr_sha1() = default;
void tr_sha1::clear()
{
return std::make_unique<Sha256Impl>();
mbedtls_sha1_init(&handle_);
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha1_starts_ret(&handle_);
#else
mbedtls_sha1_starts(&handle_);
#endif
}
void tr_sha1::add(void const* data, size_t data_length)
{
if (data_length == 0U)
{
return;
}
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha1_update_ret(&handle_, static_cast<unsigned char const*>(data), data_length);
#else
mbedtls_sha1_update(&handle_, static_cast<unsigned char const*>(data), data_length);
#endif
}
tr_sha1_digest_t tr_sha1::finish()
{
auto digest = tr_sha1_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha1_finish_ret(&handle_, digest_as_uchar);
#else
mbedtls_sha1_finish(&handle_, digest_as_uchar);
#endif
clear();
return digest;
}
// --- sha256
tr_sha256::tr_sha256()
{
clear();
}
tr_sha256::~tr_sha256() = default;
void tr_sha256::clear()
{
mbedtls_sha256_init(&handle_);
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha256_starts_ret(&handle_, 0);
#else
mbedtls_sha256_starts(&handle_);
#endif
}
void tr_sha256::add(void const* data, size_t data_length)
{
if (data_length == 0U)
{
return;
}
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha256_update_ret(&handle_, static_cast<unsigned char const*>(data), data_length);
#else
mbedtls_sha256_update(&handle_, static_cast<unsigned char const*>(data), data_length);
#endif
}
tr_sha256_digest_t tr_sha256::finish()
{
auto digest = tr_sha256_digest_t{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
#if MBEDTLS_VERSION_NUMBER >= 0x02070000
mbedtls_sha256_finish_ret(&handle_, digest_as_uchar);
#else
mbedtls_sha256_finish(&handle_, digest_as_uchar);
#endif
clear();
return digest;
}
// ---
@ -229,6 +210,21 @@ bool tr_rand_buffer_crypto(void* buffer, size_t length)
TR_ASSERT(buffer != nullptr);
auto const lock = std::lock_guard(rng_mutex_);
return check_result(mbedtls_ctr_drbg_random(get_rng(), static_cast<unsigned char*>(buffer), length));
auto constexpr ChunkSize = size_t{ MBEDTLS_CTR_DRBG_MAX_REQUEST };
static_assert(ChunkSize > 0U);
auto const lock = std::scoped_lock{ rng_mutex_ };
for (auto offset = size_t{ 0 }; offset < length; offset += ChunkSize)
{
if (!check_result(mbedtls_ctr_drbg_random(
get_rng(),
static_cast<unsigned char*>(buffer) + offset,
std::min(ChunkSize, length - offset))))
{
return false;
}
}
return true;
}

View File

@ -29,37 +29,43 @@
#include "libtransmission/tr-macros.h" // tr_sha1_digest_t, tr_sha25...
#include "libtransmission/utils.h"
#if !defined(WITH_OPENSSL)
#error OPENSSL module
#endif
namespace
{
void log_openssl_error(char const* file, int line)
{
unsigned long const error_code = ERR_get_error();
if (tr_logLevelIsActive(TR_LOG_ERROR))
if (!tr_logLevelIsActive(TR_LOG_ERROR))
{
return;
}
auto const error_code = ERR_get_error();
if (static bool strings_loaded = false; !strings_loaded)
{
if (static bool strings_loaded = false; !strings_loaded)
{
#if OPENSSL_VERSION_NUMBER < 0x10100000 || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000)
ERR_load_crypto_strings();
ERR_load_crypto_strings();
#else
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
#endif
strings_loaded = true;
}
auto buf = std::array<char, 512>{};
ERR_error_string_n(error_code, std::data(buf), std::size(buf));
tr_logAddMessage(
file,
line,
TR_LOG_ERROR,
fmt::format(
_("{crypto_library} error: {error} ({error_code})"),
fmt::arg("crypto_library", "OpenSSL"),
fmt::arg("error", std::data(buf)),
fmt::arg("error_code", error_code)));
strings_loaded = true;
}
auto buf = std::array<char, 512>{};
ERR_error_string_n(error_code, std::data(buf), std::size(buf));
tr_logAddMessage(
file,
line,
TR_LOG_ERROR,
fmt::format(
_("{crypto_library} error: {error} ({error_code})"),
fmt::arg("crypto_library", "OpenSSL"),
fmt::arg("error", std::data(buf)),
fmt::arg("error_code", error_code)));
}
#define log_error() log_openssl_error(__FILE__, __LINE__)
@ -78,136 +84,84 @@ bool check_openssl_result(int result, int expected_result, bool expected_equal,
#define check_result(result) check_openssl_result((result), 1, true, __FILE__, __LINE__)
namespace sha_helpers
void digest_add_bytes(EVP_MD_CTX* ctx, void const* data, size_t data_length)
{
class ShaHelper
{
public:
using EvpFunc = decltype((EVP_sha1));
explicit ShaHelper(EvpFunc evp_func)
: evp_func_{ evp_func }
if (data_length != 0U)
{
clear();
EVP_DigestUpdate(ctx, data, data_length);
}
void clear() const
{
EVP_DigestInit_ex(handle_.get(), evp_func_(), nullptr);
}
void update(void const* data, size_t data_length) const
{
if (data_length != 0U)
{
EVP_DigestUpdate(handle_.get(), data, data_length);
}
}
template<typename DigestType>
[[nodiscard]] DigestType digest()
{
TR_ASSERT(handle_ != nullptr);
unsigned int hash_length = 0;
auto digest = DigestType{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
[[maybe_unused]] bool const ok = check_result(EVP_DigestFinal_ex(handle_.get(), digest_as_uchar, &hash_length));
TR_ASSERT(!ok || hash_length == std::size(digest));
clear();
return digest;
}
private:
struct MessageDigestDeleter
{
void operator()(EVP_MD_CTX* ctx) const noexcept
{
EVP_MD_CTX_destroy(ctx);
}
};
EvpFunc evp_func_;
std::unique_ptr<EVP_MD_CTX, MessageDigestDeleter> const handle_{ EVP_MD_CTX_create() };
};
class Sha1Impl final : public tr_sha1
{
public:
Sha1Impl() = default;
Sha1Impl(Sha1Impl&&) = delete;
Sha1Impl(Sha1Impl const&) = delete;
~Sha1Impl() override = default;
Sha1Impl& operator=(Sha1Impl&&) = delete;
Sha1Impl& operator=(Sha1Impl const&) = delete;
void clear() override
{
helper_.clear();
}
void add(void const* data, size_t data_length) override
{
helper_.update(data, data_length);
}
[[nodiscard]] tr_sha1_digest_t finish() override
{
return helper_.digest<tr_sha1_digest_t>();
}
private:
ShaHelper helper_{ EVP_sha1 };
};
class Sha256Impl final : public tr_sha256
{
public:
Sha256Impl() = default;
Sha256Impl(Sha256Impl&&) = delete;
Sha256Impl(Sha256Impl const&) = delete;
~Sha256Impl() override = default;
Sha256Impl& operator=(Sha256Impl&&) = delete;
Sha256Impl& operator=(Sha256Impl const&) = delete;
void clear() override
{
helper_.clear();
}
void add(void const* data, size_t data_length) override
{
helper_.update(data, data_length);
}
[[nodiscard]] tr_sha256_digest_t finish() override
{
return helper_.digest<tr_sha256_digest_t>();
}
private:
ShaHelper helper_{ EVP_sha256 };
};
} // namespace sha_helpers
} // namespace
// --- sha
std::unique_ptr<tr_sha1> tr_sha1::create()
{
using namespace sha_helpers;
return std::make_unique<Sha1Impl>();
}
std::unique_ptr<tr_sha256> tr_sha256::create()
template<typename DigestType>
DigestType digest_finish(EVP_MD_CTX* ctx)
{
using namespace sha_helpers;
unsigned int hash_length = 0;
auto digest = DigestType{};
auto* const digest_as_uchar = reinterpret_cast<unsigned char*>(std::data(digest));
[[maybe_unused]] bool const ok = check_result(EVP_DigestFinal_ex(ctx, digest_as_uchar, &hash_length));
TR_ASSERT(!ok || hash_length == std::size(digest));
return digest;
}
} // namespace
return std::make_unique<Sha256Impl>();
// --- sha1
tr_sha1::tr_sha1()
: handle_{ EVP_MD_CTX_create() }
{
clear();
}
tr_sha1::~tr_sha1()
{
EVP_MD_CTX_destroy(handle_);
}
void tr_sha1::clear()
{
EVP_DigestInit_ex(handle_, EVP_sha1(), nullptr);
}
void tr_sha1::add(void const* data, size_t data_length)
{
digest_add_bytes(handle_, data, data_length);
}
tr_sha1_digest_t tr_sha1::finish()
{
auto digest = digest_finish<tr_sha1_digest_t>(handle_);
clear();
return digest;
}
// --- sha256
tr_sha256::tr_sha256()
: handle_{ EVP_MD_CTX_create() }
{
clear();
}
tr_sha256::~tr_sha256()
{
EVP_MD_CTX_destroy(handle_);
}
void tr_sha256::clear()
{
EVP_DigestInit_ex(handle_, EVP_sha256(), nullptr);
}
void tr_sha256::add(void const* data, size_t data_length)
{
digest_add_bytes(handle_, data, data_length);
}
tr_sha256_digest_t tr_sha256::finish()
{
auto digest = digest_finish<tr_sha256_digest_t>(handle_);
clear();
return digest;
}
// --- x509

View File

@ -22,6 +22,10 @@
#include "libtransmission/tr-assert.h"
#include "libtransmission/utils.h"
#ifndef WITH_WOLFSSL
#error wolfssl module
#endif
#if LIBWOLFSSL_VERSION_HEX >= 0x04000000 // 4.0.0
using TR_WC_RNG = WC_RNG;
#else
@ -31,9 +35,9 @@ using TR_WC_RNG = RNG;
#define TR_CRYPTO_X509_FALLBACK
#include "crypto-utils-fallback.cc" // NOLINT(bugprone-suspicious-include)
// ---
static void log_wolfssl_error(int error_code, char const* file, int line)
namespace
{
void log_wolfssl_error(int error_code, char const* file, int line)
{
if (tr_logLevelIsActive(TR_LOG_ERROR))
{
@ -49,7 +53,7 @@ static void log_wolfssl_error(int error_code, char const* file, int line)
}
}
static bool check_wolfssl_result(int result, char const* file, int line)
bool check_wolfssl_result(int result, char const* file, int line)
{
bool const ret = result == 0;
@ -65,7 +69,7 @@ static bool check_wolfssl_result(int result, char const* file, int line)
// ---
static TR_WC_RNG* get_rng()
TR_WC_RNG* get_rng()
{
static TR_WC_RNG rng;
static bool rng_initialized = false;
@ -83,93 +87,68 @@ static TR_WC_RNG* get_rng()
return &rng;
}
static std::mutex rng_mutex_;
// ---
namespace
{
class Sha1Impl final : public tr_sha1
{
public:
Sha1Impl()
{
clear();
}
~Sha1Impl() override = default;
void clear() override
{
wc_InitSha(&handle_);
}
void add(void const* data, size_t data_length) override
{
if (data_length > 0U)
{
wc_ShaUpdate(&handle_, static_cast<byte const*>(data), data_length);
}
}
[[nodiscard]] tr_sha1_digest_t finish() override
{
auto digest = tr_sha1_digest_t{};
wc_ShaFinal(&handle_, reinterpret_cast<byte*>(std::data(digest)));
clear();
return digest;
}
private:
wc_Sha handle_ = {};
};
class Sha256Impl final : public tr_sha256
{
public:
Sha256Impl()
{
clear();
}
~Sha256Impl() override = default;
void clear() override
{
wc_InitSha256(&handle_);
}
void add(void const* data, size_t data_length) override
{
if (data_length > 0U)
{
wc_Sha256Update(&handle_, static_cast<byte const*>(data), data_length);
}
}
[[nodiscard]] tr_sha256_digest_t finish() override
{
auto digest = tr_sha256_digest_t{};
wc_Sha256Final(&handle_, reinterpret_cast<byte*>(std::data(digest)));
clear();
return digest;
}
private:
wc_Sha256 handle_ = {};
};
std::mutex rng_mutex_;
} // namespace
std::unique_ptr<tr_sha1> tr_sha1::create()
// --- sha1
tr_sha1::tr_sha1()
{
return std::make_unique<Sha1Impl>();
clear();
}
std::unique_ptr<tr_sha256> tr_sha256::create()
tr_sha1::~tr_sha1() = default;
void tr_sha1::clear()
{
return std::make_unique<Sha256Impl>();
wc_InitSha(&handle_);
}
void tr_sha1::add(void const* data, size_t data_length)
{
if (data_length > 0U)
{
wc_ShaUpdate(&handle_, static_cast<byte const*>(data), data_length);
}
}
tr_sha1_digest_t tr_sha1::finish()
{
auto digest = tr_sha1_digest_t{};
wc_ShaFinal(&handle_, reinterpret_cast<byte*>(std::data(digest)));
clear();
return digest;
}
// --- sha256
tr_sha256::tr_sha256()
{
clear();
}
tr_sha256::~tr_sha256() = default;
void tr_sha256::clear()
{
wc_InitSha256(&handle_);
}
void tr_sha256::add(void const* data, size_t data_length)
{
if (data_length > 0U)
{
wc_Sha256Update(&handle_, static_cast<byte const*>(data), data_length);
}
}
tr_sha256_digest_t tr_sha256::finish()
{
auto digest = tr_sha256_digest_t{};
wc_Sha256Final(&handle_, reinterpret_cast<byte*>(std::data(digest)));
clear();
return digest;
}
// ---
@ -183,6 +162,6 @@ bool tr_rand_buffer_crypto(void* buffer, size_t length)
TR_ASSERT(buffer != nullptr);
auto const lock = std::lock_guard(rng_mutex_);
auto const lock = std::lock_guard{ rng_mutex_ };
return check_result(wc_RNG_GenerateBlock(get_rng(), static_cast<byte*>(buffer), length));
}

View File

@ -19,6 +19,28 @@
#include "libtransmission/tr-macros.h" // tr_sha1_digest_t, tr_sha256_d...
#include "libtransmission/tr-strbuf.h"
#if defined(WITH_CCRYPTO)
#include <CommonCrypto/CommonDigest.h>
using tr_sha1_context_t = CC_SHA1_CTX;
using tr_sha256_context_t = CC_SHA256_CTX;
#elif defined(WITH_MBEDTLS)
#include <mbedtls/sha1.h>
#include <mbedtls/sha256.h>
using tr_sha1_context_t = mbedtls_sha1_context;
using tr_sha256_context_t = mbedtls_sha256_context;
#elif defined(WITH_OPENSSL)
#include <openssl/evp.h>
using tr_sha1_context_t = EVP_MD_CTX*;
using tr_sha256_context_t = EVP_MD_CTX*;
#elif defined(WITH_WOLFSSL)
#include <wolfssl/wolfcrypt/sha.h>
#include <wolfssl/wolfcrypt/sha256.h>
using tr_sha1_context_t = wc_Sha;
using tr_sha256_context_t = wc_Sha256;
#else
#error no crypto library specified
#endif
/**
* @addtogroup utils Utilities
* @{
@ -27,39 +49,53 @@
class tr_sha1
{
public:
static std::unique_ptr<tr_sha1> create();
virtual ~tr_sha1() = default;
tr_sha1();
tr_sha1(tr_sha1&&) = delete;
tr_sha1(tr_sha1 const&) = delete;
tr_sha1& operator=(tr_sha1&&) = delete;
tr_sha1& operator=(tr_sha1 const&) = delete;
~tr_sha1();
virtual void clear() = 0;
virtual void add(void const* data, size_t data_length) = 0;
[[nodiscard]] virtual tr_sha1_digest_t finish() = 0;
void add(void const* data, size_t data_length);
[[nodiscard]] tr_sha1_digest_t finish();
void clear();
template<typename... T>
[[nodiscard]] static tr_sha1_digest_t digest(T const&... args)
[[nodiscard]] static auto digest(T const&... args)
{
auto context = tr_sha1::create();
(context->add(std::data(args), std::size(args)), ...);
return context->finish();
auto context = tr_sha1{};
(context.add(std::data(args), std::size(args)), ...);
return context.finish();
}
private:
tr_sha1_context_t handle_;
};
class tr_sha256
{
public:
static std::unique_ptr<tr_sha256> create();
virtual ~tr_sha256() = default;
tr_sha256();
tr_sha256(tr_sha256&&) = delete;
tr_sha256(tr_sha256 const&) = delete;
tr_sha256& operator=(tr_sha256&&) = delete;
tr_sha256& operator=(tr_sha256 const&) = delete;
~tr_sha256();
virtual void clear() = 0;
virtual void add(void const* data, size_t data_length) = 0;
[[nodiscard]] virtual tr_sha256_digest_t finish() = 0;
void add(void const* data, size_t data_length);
[[nodiscard]] tr_sha256_digest_t finish();
void clear();
template<typename... T>
[[nodiscard]] static tr_sha256_digest_t digest(T const&... args)
[[nodiscard]] static auto digest(T const&... args)
{
auto context = tr_sha256::create();
(context->add(std::data(args), std::size(args)), ...);
return context->finish();
auto context = tr_sha256{};
(context.add(std::data(args), std::size(args)), ...);
return context.finish();
}
private:
tr_sha256_context_t handle_;
};
/** @brief Opaque SSL context type. */

View File

@ -42,12 +42,23 @@ public:
return has_value();
}
void set(int code, std::string&& message)
{
code_ = code;
message_ = std::move(message);
}
void set(int code, std::string_view message)
{
code_ = code;
message_.assign(message);
}
void set(int code, char const* const message)
{
set(code, std::string_view{ message != nullptr ? message : "" });
}
void prefix_message(std::string_view prefix)
{
message_.insert(std::begin(message_), std::begin(prefix), std::end(prefix));

View File

@ -21,7 +21,7 @@
#include <fmt/core.h>
#include <libtransmission/file.h>
#include <libtransmission/utils.h>
#include <libtransmission/utils.h> // for tr_file_save()
#include <libtransmission/web-utils.h>
#include <libtransmission/web.h>
@ -44,9 +44,9 @@ public:
return iter != std::end(icons_) ? &iter->second : nullptr;
}
void load(
void load( //
std::string_view url_in,
IconFunc callback = [](Icon const&) {})
IconFunc callback = [](Icon const&) { /*default callback is a no-op */ })
{
std::call_once(scan_once_flag_, &FaviconCache::scan_file_cache, this);
@ -99,8 +99,8 @@ public:
}
}
static inline constexpr auto Width = 16;
static inline constexpr auto Height = 16;
static constexpr auto Width = 16;
static constexpr auto Height = 16;
private:
class InFlightData
@ -112,6 +112,9 @@ private:
{
}
InFlightData(InFlightData const&) = delete;
InFlightData& operator=(InFlightData const&) = delete;
[[nodiscard]] constexpr auto const& sitename() const noexcept
{
return sitename_;
@ -133,7 +136,7 @@ private:
[[nodiscard]] auto get_responses()
{
auto lock = std::lock_guard{ responses_mutex_ };
auto lock = std::scoped_lock{ responses_mutex_ };
auto tmp = decltype(responses_){};
std::swap(tmp, responses_);
@ -142,7 +145,7 @@ private:
void add_response(std::string contents, long code)
{
auto lock = std::lock_guard{ responses_mutex_ };
auto lock = std::scoped_lock{ responses_mutex_ };
responses_.emplace_back(std::move(contents), code);
}
@ -201,7 +204,7 @@ private:
}
}
void mark_site_as_scraped(std::string_view sitename)
void mark_site_as_scraped(std::string_view sitename) const
{
if (auto ofs = std::ofstream{ scraped_sitenames_filename_, std::ios_base::out | std::ios_base::app }; ofs.is_open())
{

View File

@ -30,6 +30,9 @@
#else
#include <sys/quota.h> /* quotactl() */
#endif
#if !defined(btodb) && defined(QIF_DQBLKSIZE_BITS)
#define btodb(num) ((num) >> QIF_DQBLKSIZE_BITS)
#endif
#ifdef HAVE_GETMNTENT
#ifdef __sun
#include <fcntl.h>

View File

@ -19,7 +19,17 @@
#include "libtransmission/torrent-metainfo.h"
#include "libtransmission/tr-assert.h"
void tr_file_piece_map::reset(tr_block_info const& block_info, uint64_t const* file_sizes, size_t n_files)
tr_file_piece_map::tr_file_piece_map(tr_torrent_metainfo const& tm)
{
reset(tm);
}
tr_file_piece_map::tr_file_piece_map(tr_block_info const& block_info, uint64_t const* const file_sizes, size_t const n_files)
{
reset(block_info, file_sizes, n_files);
}
void tr_file_piece_map::reset(tr_block_info const& block_info, uint64_t const* const file_sizes, size_t const n_files)
{
file_bytes_.resize(n_files);
file_bytes_.shrink_to_fit();
@ -75,19 +85,58 @@ void tr_file_piece_map::reset(tr_torrent_metainfo const& tm)
reset({ tm.total_size(), tm.piece_size() }, std::data(file_sizes), std::size(file_sizes));
}
tr_file_piece_map::file_span_t tr_file_piece_map::file_span(tr_piece_index_t piece) const
namespace
{
static constexpr auto Compare = CompareToSpan<tr_piece_index_t>{ false };
template<typename T>
struct CompareToSpan
{
using span_t = tr_file_piece_map::index_span_t<T>;
[[nodiscard]] constexpr int compare(T item, span_t span) const // <=>
{
if (item < span.begin)
{
return -1;
}
if (item >= span.end)
{
return 1;
}
return 0;
}
[[nodiscard]] constexpr bool operator()(T const item, span_t const span) const // <
{
return compare(item, span) < 0;
}
[[nodiscard]] constexpr int compare(span_t const span, T const item) const // <=>
{
return -compare(item, span);
}
[[nodiscard]] constexpr bool operator()(span_t const span, T const item) const // <
{
return compare(span, item) < 0;
}
};
} // namespace
tr_file_piece_map::file_span_t tr_file_piece_map::file_span_for_piece(tr_piece_index_t const piece) const
{
static constexpr auto Compare = CompareToSpan<tr_piece_index_t>{};
auto const begin = std::begin(file_pieces_);
auto const& [equal_begin, equal_end] = std::equal_range(begin, std::end(file_pieces_), piece, Compare);
auto const [equal_begin, equal_end] = std::equal_range(begin, std::end(file_pieces_), piece, Compare);
return { static_cast<tr_file_index_t>(equal_begin - begin), static_cast<tr_file_index_t>(equal_end - begin) };
}
tr_file_piece_map::file_offset_t tr_file_piece_map::file_offset(uint64_t offset, bool include_empty_files) const
tr_file_piece_map::file_offset_t tr_file_piece_map::file_offset(uint64_t const offset) const
{
auto const compare = CompareToSpan<uint64_t>{ include_empty_files };
static constexpr auto Compare = CompareToSpan<uint64_t>{};
auto const begin = std::begin(file_bytes_);
auto const it = std::lower_bound(begin, std::end(file_bytes_), offset, compare);
auto const it = std::lower_bound(begin, std::end(file_bytes_), offset, Compare);
tr_file_index_t const file_index = std::distance(begin, it);
auto const file_offset = offset - it->begin;
return file_offset_t{ file_index, file_offset };
@ -95,13 +144,7 @@ tr_file_piece_map::file_offset_t tr_file_piece_map::file_offset(uint64_t offset,
// ---
void tr_file_priorities::reset(tr_file_piece_map const* fpm)
{
fpm_ = fpm;
priorities_ = {};
}
void tr_file_priorities::set(tr_file_index_t file, tr_priority_t new_priority)
void tr_file_priorities::set(tr_file_index_t const file, tr_priority_t const new_priority)
{
if (std::empty(priorities_))
{
@ -110,14 +153,14 @@ void tr_file_priorities::set(tr_file_index_t file, tr_priority_t new_priority)
return;
}
priorities_.assign(std::size(*fpm_), TR_PRI_NORMAL);
priorities_.assign(fpm_->file_count(), TR_PRI_NORMAL);
priorities_.shrink_to_fit();
}
priorities_[file] = new_priority;
}
void tr_file_priorities::set(tr_file_index_t const* files, size_t n, tr_priority_t new_priority)
void tr_file_priorities::set(tr_file_index_t const* const files, size_t const n, tr_priority_t const new_priority)
{
for (size_t i = 0U; i < n; ++i)
{
@ -125,9 +168,9 @@ void tr_file_priorities::set(tr_file_index_t const* files, size_t n, tr_priority
}
}
tr_priority_t tr_file_priorities::file_priority(tr_file_index_t file) const
tr_priority_t tr_file_priorities::file_priority(tr_file_index_t const file) const
{
TR_ASSERT(file < std::size(*fpm_));
TR_ASSERT(file < fpm_->file_count());
if (std::empty(priorities_))
{
@ -137,7 +180,7 @@ tr_priority_t tr_file_priorities::file_priority(tr_file_index_t file) const
return priorities_[file];
}
tr_priority_t tr_file_priorities::piece_priority(tr_piece_index_t piece) const
tr_priority_t tr_file_priorities::piece_priority(tr_piece_index_t const piece) const
{
// increase priority if a file begins or ends in this piece
// because that makes life easier for code/users using at incomplete files.
@ -148,7 +191,7 @@ tr_priority_t tr_file_priorities::piece_priority(tr_piece_index_t piece) const
}
// check the priorities of the files that touch this piece
if (auto const [begin_file, end_file] = fpm_->file_span(piece); end_file <= std::size(priorities_))
if (auto const [begin_file, end_file] = fpm_->file_span_for_piece(piece); end_file <= std::size(priorities_))
{
auto const begin = std::begin(priorities_) + begin_file;
auto const end = std::begin(priorities_) + end_file;
@ -163,33 +206,33 @@ tr_priority_t tr_file_priorities::piece_priority(tr_piece_index_t piece) const
// ---
void tr_files_wanted::reset(tr_file_piece_map const* fpm)
tr_files_wanted::tr_files_wanted(tr_file_piece_map const* const fpm)
: fpm_{ fpm }
, wanted_{ fpm->file_count() }
{
fpm_ = fpm;
wanted_ = tr_bitfield{ std::size(*fpm) };
wanted_.set_has_all(); // by default we want all files
}
void tr_files_wanted::set(tr_file_index_t file, bool wanted)
void tr_files_wanted::set(tr_file_index_t const file, bool const wanted)
{
wanted_.set(file, wanted);
}
void tr_files_wanted::set(tr_file_index_t const* files, size_t n, bool wanted)
void tr_files_wanted::set(tr_file_index_t const* const files, size_t const n_files, bool const wanted)
{
for (size_t i = 0U; i < n; ++i)
for (size_t idx = 0U; idx < n_files; ++idx)
{
set(files[i], wanted);
set(files[idx], wanted);
}
}
bool tr_files_wanted::piece_wanted(tr_piece_index_t piece) const
bool tr_files_wanted::piece_wanted(tr_piece_index_t const piece) const
{
if (wanted_.has_all())
{
return true;
}
auto const [begin, end] = fpm_->file_span(piece);
auto const [begin, end] = fpm_->file_span_for_piece(piece);
return wanted_.count(begin, end) != 0U;
}

View File

@ -18,7 +18,6 @@
#include "libtransmission/bitfield.h"
#include "libtransmission/tr-macros.h" // TR_CONSTEXPR20
#include "libtransmission/utils.h"
struct tr_block_info;
struct tr_torrent_metainfo;
@ -43,102 +42,43 @@ public:
};
using file_offset_t = offset_t<tr_file_index_t>;
explicit tr_file_piece_map(tr_torrent_metainfo const& tm);
tr_file_piece_map(tr_block_info const& block_info, uint64_t const* file_sizes, size_t n_files);
explicit tr_file_piece_map(tr_torrent_metainfo const& tm)
{
reset(tm);
}
tr_file_piece_map(tr_block_info const& block_info, uint64_t const* file_sizes, size_t n_files)
{
reset(block_info, file_sizes, n_files);
}
void reset(tr_torrent_metainfo const& tm);
[[nodiscard]] TR_CONSTEXPR20 piece_span_t piece_span(tr_file_index_t file) const noexcept
[[nodiscard]] TR_CONSTEXPR20 piece_span_t piece_span_for_file(tr_file_index_t const file) const noexcept
{
return file_pieces_[file];
}
[[nodiscard]] file_span_t file_span(tr_piece_index_t piece) const;
[[nodiscard]] file_span_t file_span_for_piece(tr_piece_index_t piece) const;
[[nodiscard]] file_offset_t file_offset(uint64_t offset, bool include_empty_files) const;
[[nodiscard]] file_offset_t file_offset(uint64_t offset) const;
[[nodiscard]] TR_CONSTEXPR20 size_t size() const
[[nodiscard]] TR_CONSTEXPR20 size_t file_count() const
{
return std::size(file_pieces_);
}
[[nodiscard]] TR_CONSTEXPR20 bool empty() const noexcept
{
return std::empty(file_pieces_);
}
// TODO(ckerr) minor wart here, two identical span types
[[nodiscard]] TR_CONSTEXPR20 tr_byte_span_t byte_span(tr_file_index_t file) const
[[nodiscard]] TR_CONSTEXPR20 auto byte_span_for_file(tr_file_index_t const file) const
{
auto const& span = file_bytes_[file];
return tr_byte_span_t{ span.begin, span.end };
}
[[nodiscard]] TR_CONSTEXPR20 bool is_edge_piece(tr_piece_index_t piece) const
[[nodiscard]] TR_CONSTEXPR20 bool is_edge_piece(tr_piece_index_t const piece) const
{
return std::binary_search(std::begin(edge_pieces_), std::end(edge_pieces_), piece);
}
private:
using byte_span_t = index_span_t<uint64_t>;
void reset(tr_torrent_metainfo const& tm);
void reset(tr_block_info const& block_info, uint64_t const* file_sizes, size_t n_files);
using byte_span_t = index_span_t<uint64_t>;
std::vector<byte_span_t> file_bytes_;
std::vector<piece_span_t> file_pieces_;
std::vector<tr_piece_index_t> edge_pieces_;
template<typename T>
struct CompareToSpan
{
using span_t = index_span_t<T>;
[[nodiscard]] constexpr int compare(T item, span_t span) const // <=>
{
if (include_empty_span && span.begin == span.end)
{
return tr_compare_3way(item, span.begin);
}
if (item < span.begin)
{
return -1;
}
if (item >= span.end)
{
return 1;
}
return 0;
}
[[nodiscard]] constexpr bool operator()(T item, span_t span) const // <
{
return compare(item, span) < 0;
}
[[nodiscard]] constexpr int compare(span_t span, T item) const // <=>
{
return -compare(item, span);
}
[[nodiscard]] constexpr bool operator()(span_t span, T item) const // <
{
return compare(span, item) < 0;
}
bool const include_empty_span;
};
};
class tr_file_priorities
@ -149,7 +89,6 @@ public:
{
}
void reset(tr_file_piece_map const*);
void set(tr_file_index_t file, tr_priority_t priority);
void set(tr_file_index_t const* files, size_t n, tr_priority_t priority);
@ -164,12 +103,7 @@ private:
class tr_files_wanted
{
public:
explicit tr_files_wanted(tr_file_piece_map const* fpm)
: wanted_{ std::size(*fpm) }
{
reset(fpm);
}
void reset(tr_file_piece_map const* fpm);
explicit tr_files_wanted(tr_file_piece_map const* fpm);
void set(tr_file_index_t file, bool wanted);
void set(tr_file_index_t const* files, size_t n, bool wanted);

View File

@ -273,7 +273,7 @@ std::string_view tr_sys_path_dirname(std::string_view path)
auto const has_root = path[0] == '/';
auto end = std::string_view::npos;
auto matched_slash = bool{ true };
auto matched_slash = true;
for (auto i = len - 1; i >= 1U; --i)
{
@ -545,37 +545,6 @@ char* tr_sys_path_native_separators(char* path)
return path;
}
tr_sys_file_t tr_sys_file_get_std(tr_std_sys_file_t std_file, tr_error* error)
{
tr_sys_file_t ret = TR_BAD_SYS_FILE;
switch (std_file)
{
case TR_STD_SYS_FILE_IN:
ret = STDIN_FILENO;
break;
case TR_STD_SYS_FILE_OUT:
ret = STDOUT_FILENO;
break;
case TR_STD_SYS_FILE_ERR:
ret = STDERR_FILENO;
break;
default:
TR_ASSERT_MSG(false, fmt::format(FMT_STRING("unknown standard file {:d}"), static_cast<int>(std_file)));
if (error != nullptr)
{
error->set_from_errno(EINVAL);
}
break;
}
return ret;
}
tr_sys_file_t tr_sys_file_open(char const* path, int flags, int permissions, tr_error* error)
{
TR_ASSERT(path != nullptr);
@ -797,37 +766,6 @@ bool tr_sys_file_write_at(
return ret;
}
bool tr_sys_file_flush(tr_sys_file_t handle, tr_error* error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
bool const ret = (fsync(handle) != -1);
if (error != nullptr && !ret)
{
error->set_from_errno(errno);
}
return ret;
}
bool tr_sys_file_flush_possible(tr_sys_file_t handle, tr_error* error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
if (struct stat statbuf = {}; fstat(handle, &statbuf) == 0)
{
return S_ISREG(statbuf.st_mode);
}
if (error != nullptr)
{
error->set_from_errno(errno);
}
return false;
}
bool tr_sys_file_truncate(tr_sys_file_t handle, uint64_t size, tr_error* error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
@ -842,58 +780,6 @@ bool tr_sys_file_truncate(tr_sys_file_t handle, uint64_t size, tr_error* error)
return ret;
}
bool tr_sys_file_advise(
[[maybe_unused]] tr_sys_file_t handle,
[[maybe_unused]] uint64_t offset,
[[maybe_unused]] uint64_t size,
[[maybe_unused]] tr_sys_file_advice_t advice,
[[maybe_unused]] tr_error* error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
TR_ASSERT(size > 0);
TR_ASSERT(advice == TR_SYS_FILE_ADVICE_WILL_NEED || advice == TR_SYS_FILE_ADVICE_DONT_NEED);
bool ret = true;
#if defined(HAVE_POSIX_FADVISE)
int const native_advice = advice == TR_SYS_FILE_ADVICE_WILL_NEED ?
POSIX_FADV_WILLNEED :
(advice == TR_SYS_FILE_ADVICE_DONT_NEED ? POSIX_FADV_DONTNEED : POSIX_FADV_NORMAL);
TR_ASSERT(native_advice != POSIX_FADV_NORMAL);
if (int const code = posix_fadvise(handle, offset, size, native_advice); code != 0)
{
if (error != nullptr)
{
error->set_from_errno(errno);
}
ret = false;
}
#elif defined(__APPLE__)
if (advice == TR_SYS_FILE_ADVICE_WILL_NEED)
{
auto radv = radvisory{};
radv.ra_offset = offset;
radv.ra_count = size;
ret = fcntl(handle, F_RDADVISE, &radv) != -1;
if (error != nullptr && !ret)
{
error->set_from_errno(errno);
}
}
#endif
return ret;
}
namespace
{
namespace preallocate_helpers
@ -1029,8 +915,6 @@ bool tr_sys_file_lock([[maybe_unused]] tr_sys_file_t handle, [[maybe_unused]] in
TR_ASSERT(
!!(operation & TR_SYS_FILE_LOCK_SH) + !!(operation & TR_SYS_FILE_LOCK_EX) + !!(operation & TR_SYS_FILE_LOCK_UN) == 1);
bool ret = false;
#if defined(F_OFD_SETLK)
struct flock fl = {};
@ -1048,62 +932,68 @@ bool tr_sys_file_lock([[maybe_unused]] tr_sys_file_t handle, [[maybe_unused]] in
case TR_SYS_FILE_LOCK_UN:
fl.l_type = F_UNLCK;
break;
default:
errno = EINVAL;
break;
}
fl.l_whence = SEEK_SET;
do
{
ret = fcntl(handle, (operation & TR_SYS_FILE_LOCK_NB) != 0 ? F_OFD_SETLK : F_OFD_SETLKW, &fl) != -1;
} while (!ret && errno == EINTR);
int const native_operation = (operation & TR_SYS_FILE_LOCK_NB) != 0 ? F_OFD_SETLK : F_OFD_SETLKW;
if (!ret && errno == EAGAIN)
auto result = std::optional<bool>{};
while (!result)
{
errno = EWOULDBLOCK;
if (fcntl(handle, native_operation, &fl) != -1)
{
result = true;
}
else if (errno != EINTR)
{
result = false;
}
}
#elif defined(HAVE_FLOCK)
int native_operation = 0;
int const native_operation = //
(((operation & TR_SYS_FILE_LOCK_SH) != 0) ? LOCK_SH : 0) | //
(((operation & TR_SYS_FILE_LOCK_EX) != 0) ? LOCK_EX : 0) | //
(((operation & TR_SYS_FILE_LOCK_NB) != 0) ? LOCK_NB : 0) | //
(((operation & TR_SYS_FILE_LOCK_UN) != 0) ? LOCK_UN : 0);
if ((operation & TR_SYS_FILE_LOCK_SH) != 0)
auto result = std::optional<bool>{};
while (!result)
{
native_operation |= LOCK_SH;
if (flock(handle, native_operation) != -1)
{
result = true;
}
else if (errno != EINTR)
{
result = false;
}
}
if ((operation & TR_SYS_FILE_LOCK_EX) != 0)
{
native_operation |= LOCK_EX;
}
if ((operation & TR_SYS_FILE_LOCK_NB) != 0)
{
native_operation |= LOCK_NB;
}
if ((operation & TR_SYS_FILE_LOCK_UN) != 0)
{
native_operation |= LOCK_UN;
}
do
{
ret = flock(handle, native_operation) != -1;
} while (!ret && errno == EINTR);
#else
errno = ENOSYS;
ret = false;
auto const result = std::optional<bool>{ false };
#endif
if (error != nullptr && !ret)
if (!*result && errno == EAGAIN)
{
errno = EWOULDBLOCK;
}
if (error != nullptr && !*result)
{
error->set_from_errno(errno);
}
return ret;
return *result;
}
std::string tr_sys_dir_get_current(tr_error* error)
@ -1150,7 +1040,7 @@ namespace
{
if (error != nullptr)
{
error->set(ENOTDIR, fmt::format(FMT_STRING("File is in the way: {:s}"), path));
error->set(ENOTDIR, fmt::format("File is in the way: {:s}", path));
}
return false;
@ -1180,7 +1070,7 @@ bool tr_sys_dir_create(char const* path, int flags, int permissions, tr_error* e
{
TR_ASSERT(path != nullptr);
auto ret = bool{ false };
auto ret = false;
auto local_error = tr_error{};
if ((flags & TR_SYS_DIR_CREATE_PARENTS) != 0)

View File

@ -43,19 +43,21 @@ struct tr_sys_dir_win32
std::string utf8_name;
};
static auto constexpr NativeLocalPathPrefix = L"\\\\?\\"sv;
static auto constexpr NativeUncPathPrefix = L"\\\\?\\UNC\\"sv;
namespace
{
auto constexpr NativeLocalPathPrefix = L"\\\\?\\"sv;
auto constexpr NativeUncPathPrefix = L"\\\\?\\UNC\\"sv;
static void set_system_error(tr_error* error, DWORD code)
void set_system_error(tr_error* error, DWORD code)
{
if (error != nullptr)
{
auto const message = tr_win32_format_message(code);
error->set(code, !std::empty(message) ? message : fmt::format(FMT_STRING("Unknown error: {:#08x}"), code));
error->set(code, !std::empty(message) ? message : fmt::format("Unknown error: {:#08x}", code));
}
}
static void set_system_error_if_file_found(tr_error* error, DWORD code)
void set_system_error_if_file_found(tr_error* error, DWORD code)
{
if (code != ERROR_FILE_NOT_FOUND && code != ERROR_PATH_NOT_FOUND && code != ERROR_NO_MORE_FILES)
{
@ -63,7 +65,7 @@ static void set_system_error_if_file_found(tr_error* error, DWORD code)
}
}
static constexpr time_t filetime_to_unix_time(FILETIME const& t)
constexpr time_t filetime_to_unix_time(FILETIME const& t)
{
uint64_t tmp = 0;
tmp |= t.dwHighDateTime;
@ -75,7 +77,7 @@ static constexpr time_t filetime_to_unix_time(FILETIME const& t)
return tmp / 1000000UL;
}
static constexpr auto stat_to_sys_path_info(DWORD attributes, DWORD size_low, DWORD size_high, FILETIME const& mtime)
constexpr auto stat_to_sys_path_info(DWORD attributes, DWORD size_low, DWORD size_high, FILETIME const& mtime)
{
auto info = tr_sys_path_info{};
@ -101,19 +103,19 @@ static constexpr auto stat_to_sys_path_info(DWORD attributes, DWORD size_low, DW
return info;
}
static auto constexpr Slashes = "\\/"sv;
auto constexpr Slashes = "\\/"sv;
static constexpr bool is_slash(char c)
constexpr bool is_slash(char c)
{
return tr_strv_contains(Slashes, c);
}
static constexpr bool is_unc_path(std::string_view path)
constexpr bool is_unc_path(std::string_view path)
{
return std::size(path) >= 2 && is_slash(path[0]) && path[1] == path[0];
}
static bool is_valid_path(std::string_view path)
bool is_valid_path(std::string_view path)
{
if (is_unc_path(path))
{
@ -140,11 +142,6 @@ static bool is_valid_path(std::string_view path)
return path.find_first_of("<>:\"|?*"sv) == path.npos;
}
namespace
{
namespace path_to_native_path_helpers
{
auto path_to_fixed_native_path(std::string_view path)
{
auto wide_path = tr_win32_utf8_to_native(path);
@ -168,15 +165,10 @@ auto path_to_fixed_native_path(std::string_view path)
return wide_path;
}
} // namespace path_to_native_path_helpers
} // namespace
/* Extending maximum path length limit up to ~32K. See "Naming Files, Paths, and Namespaces"
https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx for more info */
static auto path_to_native_path(std::string_view path)
auto path_to_native_path(std::string_view path)
{
using namespace path_to_native_path_helpers;
if (is_unc_path(path))
{
// UNC path: "\\server\share" -> "\\?\UNC\server\share"
@ -197,7 +189,7 @@ static auto path_to_native_path(std::string_view path)
return path_to_fixed_native_path(path);
}
static std::string native_path_to_path(std::wstring_view wide_path)
std::string native_path_to_path(std::wstring_view wide_path)
{
if (std::empty(wide_path))
{
@ -221,7 +213,7 @@ static std::string native_path_to_path(std::wstring_view wide_path)
return tr_win32_native_to_utf8(wide_path);
}
static tr_sys_file_t open_file(std::string_view path, DWORD access, DWORD disposition, DWORD flags, tr_error* error)
tr_sys_file_t open_file(std::string_view path, DWORD access, DWORD disposition, DWORD flags, tr_error* error)
{
tr_sys_file_t ret = TR_BAD_SYS_FILE;
@ -245,7 +237,7 @@ static tr_sys_file_t open_file(std::string_view path, DWORD access, DWORD dispos
return ret;
}
static bool create_dir(std::string_view path, int flags, int /*permissions*/, bool okay_if_exists, tr_error* error)
bool create_dir(std::string_view path, int flags, int /*permissions*/, bool okay_if_exists, tr_error* error)
{
bool ret;
DWORD error_code = ERROR_SUCCESS;
@ -290,7 +282,7 @@ static bool create_dir(std::string_view path, int flags, int /*permissions*/, bo
return ret;
}
static void create_temp_path(
void create_temp_path(
char* path_template,
void (*callback)(char const* path, void* param, tr_error* error),
void* callback_param,
@ -339,6 +331,72 @@ static void create_temp_path(
}
}
std::optional<tr_sys_path_info> tr_sys_file_get_info_(tr_sys_file_t handle, tr_error* error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
auto attributes = BY_HANDLE_FILE_INFORMATION{};
if (GetFileInformationByHandle(handle, &attributes))
{
return stat_to_sys_path_info(
attributes.dwFileAttributes,
attributes.nFileSizeLow,
attributes.nFileSizeHigh,
attributes.ftLastWriteTime);
}
set_system_error(error, GetLastError());
return {};
}
std::optional<BY_HANDLE_FILE_INFORMATION> get_file_info(char const* path, tr_error* error)
{
auto const wpath = path_to_native_path(path);
if (std::empty(wpath))
{
set_system_error_if_file_found(error, GetLastError());
return {};
}
auto const handle = CreateFileW(wpath.c_str(), 0, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (handle == INVALID_HANDLE_VALUE)
{
set_system_error_if_file_found(error, GetLastError());
return {};
}
// TODO: Use GetFileInformationByHandleEx on >= Server 2012
auto info = BY_HANDLE_FILE_INFORMATION{};
if (!GetFileInformationByHandle(handle, &info))
{
set_system_error_if_file_found(error, GetLastError());
CloseHandle(handle);
return {};
}
CloseHandle(handle);
return info;
}
void file_open_temp_callback(char const* path, void* param, tr_error* error)
{
auto* const result = static_cast<tr_sys_file_t*>(param);
TR_ASSERT(result != nullptr);
*result = open_file(path, GENERIC_READ | GENERIC_WRITE, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, error);
}
void dir_create_temp_callback(char const* path, void* param, tr_error* error)
{
auto* const result = static_cast<bool*>(param);
TR_ASSERT(result != nullptr);
*result = create_dir(path, 0, 0, false, error);
}
} // namespace
bool tr_sys_path_exists(char const* path, tr_error* error)
{
TR_ASSERT(path != nullptr);
@ -377,24 +435,6 @@ bool tr_sys_path_exists(char const* path, tr_error* error)
return ret;
}
static std::optional<tr_sys_path_info> tr_sys_file_get_info_(tr_sys_file_t handle, tr_error* error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
auto attributes = BY_HANDLE_FILE_INFORMATION{};
if (GetFileInformationByHandle(handle, &attributes))
{
return stat_to_sys_path_info(
attributes.dwFileAttributes,
attributes.nFileSizeLow,
attributes.nFileSizeHigh,
attributes.ftLastWriteTime);
}
set_system_error(error, GetLastError());
return {};
}
std::optional<tr_sys_path_info> tr_sys_path_get_info(std::string_view path, int flags, tr_error* error)
{
if (auto const wide_path = path_to_native_path(path); std::empty(wide_path))
@ -449,35 +489,6 @@ bool tr_sys_path_is_relative(std::string_view path)
return true;
}
static std::optional<BY_HANDLE_FILE_INFORMATION> get_file_info(char const* path, tr_error* error)
{
auto const wpath = path_to_native_path(path);
if (std::empty(wpath))
{
set_system_error_if_file_found(error, GetLastError());
return {};
}
auto const handle = CreateFileW(wpath.c_str(), 0, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (handle == INVALID_HANDLE_VALUE)
{
set_system_error_if_file_found(error, GetLastError());
return {};
}
// TODO: Use GetFileInformationByHandleEx on >= Server 2012
auto info = BY_HANDLE_FILE_INFORMATION{};
if (!GetFileInformationByHandle(handle, &info))
{
set_system_error_if_file_found(error, GetLastError());
CloseHandle(handle);
return {};
}
CloseHandle(handle);
return info;
}
bool tr_sys_path_is_same(char const* path1, char const* path2, tr_error* error)
{
TR_ASSERT(path1 != nullptr);
@ -808,42 +819,6 @@ char* tr_sys_path_native_separators(char* path)
return path;
}
tr_sys_file_t tr_sys_file_get_std(tr_std_sys_file_t std_file, tr_error* error)
{
tr_sys_file_t ret = TR_BAD_SYS_FILE;
switch (std_file)
{
case TR_STD_SYS_FILE_IN:
ret = GetStdHandle(STD_INPUT_HANDLE);
break;
case TR_STD_SYS_FILE_OUT:
ret = GetStdHandle(STD_OUTPUT_HANDLE);
break;
case TR_STD_SYS_FILE_ERR:
ret = GetStdHandle(STD_ERROR_HANDLE);
break;
default:
TR_ASSERT_MSG(false, fmt::format(FMT_STRING("unknown standard file {:d}"), std_file));
set_system_error(error, ERROR_INVALID_PARAMETER);
return TR_BAD_SYS_FILE;
}
if (ret == TR_BAD_SYS_FILE)
{
set_system_error(error, GetLastError());
}
else if (ret == nullptr)
{
ret = TR_BAD_SYS_FILE;
}
return ret;
}
tr_sys_file_t tr_sys_file_open(char const* path, int flags, int /*permissions*/, tr_error* error)
{
TR_ASSERT(path != nullptr);
@ -899,15 +874,6 @@ tr_sys_file_t tr_sys_file_open(char const* path, int flags, int /*permissions*/,
return ret;
}
static void file_open_temp_callback(char const* path, void* param, tr_error* error)
{
auto* const result = static_cast<tr_sys_file_t*>(param);
TR_ASSERT(result != nullptr);
*result = open_file(path, GENERIC_READ | GENERIC_WRITE, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, error);
}
tr_sys_file_t tr_sys_file_open_temp(char* path_template, tr_error* error)
{
TR_ASSERT(path_template != nullptr);
@ -1081,35 +1047,6 @@ bool tr_sys_file_write_at(
return ret;
}
bool tr_sys_file_flush(tr_sys_file_t handle, tr_error* error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
bool ret = FlushFileBuffers(handle);
if (!ret)
{
set_system_error(error, GetLastError());
}
return ret;
}
bool tr_sys_file_flush_possible(tr_sys_file_t handle, tr_error* error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
DWORD type = GetFileType(handle);
if (type == FILE_TYPE_UNKNOWN)
{
set_system_error(error, GetLastError());
return false;
}
return type == FILE_TYPE_DISK;
}
bool tr_sys_file_truncate(tr_sys_file_t handle, uint64_t size, tr_error* error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
@ -1127,24 +1064,6 @@ bool tr_sys_file_truncate(tr_sys_file_t handle, uint64_t size, tr_error* error)
return ret;
}
bool tr_sys_file_advise(
[[maybe_unused]] tr_sys_file_t handle,
uint64_t /*offset*/,
[[maybe_unused]] uint64_t size,
[[maybe_unused]] tr_sys_file_advice_t advice,
tr_error* /*error*/)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
TR_ASSERT(size > 0);
TR_ASSERT(advice == TR_SYS_FILE_ADVICE_WILL_NEED || advice == TR_SYS_FILE_ADVICE_DONT_NEED);
bool ret = true;
/* ??? */
return ret;
}
bool tr_sys_file_preallocate(tr_sys_file_t handle, uint64_t size, int flags, tr_error* error)
{
TR_ASSERT(handle != TR_BAD_SYS_FILE);
@ -1225,15 +1144,6 @@ bool tr_sys_dir_create(char const* path, int flags, int permissions, tr_error* e
return create_dir(path, flags, permissions, true, error);
}
static void dir_create_temp_callback(char const* path, void* param, tr_error* error)
{
auto* const result = static_cast<bool*>(param);
TR_ASSERT(result != nullptr);
*result = create_dir(path, 0, 0, false, error);
}
bool tr_sys_dir_create_temp(char* path_template, tr_error* error)
{
TR_ASSERT(path_template != nullptr);

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