mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2026-01-03 06:02:29 -06:00
Compare commits
57 Commits
e5026e4b19
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
576774bcae | ||
|
|
9d5a8270cf | ||
|
|
d55b924190 | ||
|
|
a5b5ba2065 | ||
|
|
14d2db185a | ||
|
|
351b065553 | ||
|
|
52a6e7229b | ||
|
|
be12be2b79 | ||
|
|
0bba3f342e | ||
|
|
ae7cc7445b | ||
|
|
e79be5853e | ||
|
|
b718cc52fa | ||
|
|
57c99b54c8 | ||
|
|
25224f6050 | ||
|
|
b5d16dfeee | ||
|
|
1c231ce014 | ||
|
|
93470f2080 | ||
|
|
260563d340 | ||
|
|
b792ecede5 | ||
|
|
19ebf67c74 | ||
|
|
56cd98e06e | ||
|
|
c25bd6aaea | ||
|
|
26e42abf32 | ||
|
|
5abf458e69 | ||
|
|
7451552e6a | ||
|
|
96161627d6 | ||
|
|
0f80d3a74a | ||
|
|
73c15e1f00 | ||
|
|
a59238f4ea | ||
|
|
d918c43aba | ||
|
|
c45dfb6662 | ||
|
|
f68bc3fef9 | ||
|
|
ed9a8687ad | ||
|
|
6a4b1b9727 | ||
|
|
f2f4676824 | ||
|
|
8b9064a33c | ||
|
|
296c90d688 | ||
|
|
564afc975f | ||
|
|
a77b17e6da | ||
|
|
4a3922d152 | ||
|
|
1b96a48266 | ||
|
|
1f6e7519a0 | ||
|
|
46cc3a358e | ||
|
|
9c654b8a73 | ||
|
|
e036781b36 | ||
|
|
ffbd01eb81 | ||
|
|
530c7d1bbd | ||
|
|
fec4e01aeb | ||
|
|
4541044c42 | ||
|
|
b819b9b7a6 | ||
|
|
6b3519f4eb | ||
|
|
33e7cff3b0 | ||
|
|
516f2ef6ec | ||
|
|
85f1c774f6 | ||
|
|
1be4e646e1 | ||
|
|
ee62dd3cda | ||
|
|
bc22e8929c |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -8,3 +8,6 @@ core.eol=lf
|
||||
|
||||
dist/windows/license.txt text eol=crlf
|
||||
test/testdata/crlf.txt text eol=crlf
|
||||
|
||||
# disable line endings conversions
|
||||
src/base/3rdparty/expected.hpp -text
|
||||
|
||||
7
.github/workflows/ci_file_health.yaml
vendored
7
.github/workflows/ci_file_health.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -31,12 +31,13 @@ jobs:
|
||||
- name: Check doc
|
||||
env:
|
||||
pandoc_path: "${{ github.workspace }}/../pandoc"
|
||||
pandoc_version: "3.8.3"
|
||||
run: |
|
||||
# install pandoc
|
||||
curl \
|
||||
-L \
|
||||
-o "${{ runner.temp }}/pandoc.tar.gz" \
|
||||
"https://github.com/jgm/pandoc/releases/download/3.7.0.2/pandoc-3.7.0.2-linux-amd64.tar.gz"
|
||||
"https://github.com/jgm/pandoc/releases/download/${{ env.pandoc_version }}/pandoc-${{ env.pandoc_version }}-linux-amd64.tar.gz"
|
||||
tar -xf "${{ runner.temp }}/pandoc.tar.gz" -C "${{ github.workspace }}/.."
|
||||
mv "${{ github.workspace }}/.."/pandoc-* "${{ env.pandoc_path }}"
|
||||
# run pandoc
|
||||
@@ -65,7 +66,7 @@ jobs:
|
||||
> "${{ runner.temp }}/zizmor_results.sarif"
|
||||
|
||||
- name: Upload zizmor results
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
uses: github/codeql-action/upload-sarif@v4
|
||||
with:
|
||||
category: zizmor
|
||||
sarif_file: "${{ runner.temp }}/zizmor_results.sarif"
|
||||
|
||||
36
.github/workflows/ci_macos.yaml
vendored
36
.github/workflows/ci_macos.yaml
vendored
@@ -18,9 +18,17 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
libt_version: ["2.0.11", "1.2.20"]
|
||||
libtorrent:
|
||||
- version: "2.0.11"
|
||||
boost_major_version: "1"
|
||||
boost_minor_version: "90"
|
||||
boost_patch_version: "0"
|
||||
- version: "1.2.20"
|
||||
boost_major_version: "1"
|
||||
boost_minor_version: "86"
|
||||
boost_patch_version: "0"
|
||||
qbt_gui: ["GUI=ON", "GUI=OFF"]
|
||||
qt_version: ["6.9.1"]
|
||||
qt_version: ["6.10.1"]
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
@@ -28,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -49,19 +57,15 @@ jobs:
|
||||
- name: Setup ccache
|
||||
uses: Chocobo1/setup-ccache-action@v1
|
||||
with:
|
||||
store_cache: ${{ github.ref == 'refs/heads/master' }}
|
||||
store_cache: ${{ (github.repository != 'qbittorrent/qBittorrent') || (github.ref == 'refs/heads/master') }}
|
||||
update_packager_index: false
|
||||
ccache_options: |
|
||||
max_size=1G
|
||||
|
||||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "86"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url="https://archives.boost.io/release/${{ matrix.libtorrent.boost_major_version }}.${{ matrix.libtorrent.boost_minor_version }}.${{ matrix.libtorrent.boost_patch_version }}/source/boost_${{ matrix.libtorrent.boost_major_version }}_${{ matrix.libtorrent.boost_minor_version }}_${{ matrix.libtorrent.boost_patch_version }}.tar.gz"
|
||||
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ matrix.libtorrent.boost_major_version }}.${{ matrix.libtorrent.boost_minor_version }}.${{ matrix.libtorrent.boost_patch_version }}/boost_${{ matrix.libtorrent.boost_major_version }}_${{ matrix.libtorrent.boost_minor_version }}_${{ matrix.libtorrent.boost_patch_version }}.tar.gz"
|
||||
set +e
|
||||
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
|
||||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
|
||||
@@ -79,13 +83,14 @@ jobs:
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
archives: qtbase qtdeclarative qtsvg qttools
|
||||
modules: qtimageformats
|
||||
# Not sure why Qt made a hard dependency on qtdeclarative, try removing it when Qt > 6.4.0
|
||||
cache: true
|
||||
|
||||
- name: Install libtorrent
|
||||
run: |
|
||||
git clone \
|
||||
--branch v${{ matrix.libt_version }} \
|
||||
--branch v${{ matrix.libtorrent.version }} \
|
||||
--depth 1 \
|
||||
--recurse-submodules \
|
||||
https://github.com/arvidn/libtorrent.git \
|
||||
@@ -119,6 +124,11 @@ jobs:
|
||||
cmake --build build --target qbt_update_translations
|
||||
cmake --build build
|
||||
cmake --build build --target check
|
||||
if [ "${{ matrix.qbt_gui }}" = "GUI=ON" ]; then
|
||||
build/qbittorrent.app/Contents/MacOS/qbittorrent -v
|
||||
else
|
||||
build/qbittorrent-nox.app/Contents/MacOS/qbittorrent-nox -v
|
||||
fi
|
||||
|
||||
- name: Prepare build artifacts
|
||||
run: |
|
||||
@@ -156,7 +166,7 @@ jobs:
|
||||
cp ${{ env.libtorrent_path }}/build/compile_commands.json upload/cmake/libtorrent
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: qBittorrent-CI_macOS_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
|
||||
name: qBittorrent-CI_macOS_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libtorrent.version }}_Qt-${{ matrix.qt_version }}
|
||||
path: upload
|
||||
|
||||
2
.github/workflows/ci_python.yaml
vendored
2
.github/workflows/ci_python.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
47
.github/workflows/ci_ubuntu.yaml
vendored
47
.github/workflows/ci_ubuntu.yaml
vendored
@@ -19,7 +19,15 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
libt_version: ["2.0.11", "1.2.20"]
|
||||
libtorrent:
|
||||
- version: "2.0.11"
|
||||
boost_major_version: "1"
|
||||
boost_minor_version: "77"
|
||||
boost_patch_version: "0"
|
||||
- version: "1.2.20"
|
||||
boost_major_version: "1"
|
||||
boost_minor_version: "77"
|
||||
boost_patch_version: "0"
|
||||
qbt_gui: ["GUI=ON", "GUI=OFF"]
|
||||
qt_version: ["6.6.3"]
|
||||
|
||||
@@ -30,7 +38,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -39,24 +47,20 @@ jobs:
|
||||
sudo apt update
|
||||
sudo apt install \
|
||||
build-essential cmake ninja-build \
|
||||
libssl-dev libxkbcommon-x11-dev libxcb-cursor-dev zlib1g-dev
|
||||
libssl-dev zlib1g-dev
|
||||
|
||||
- name: Setup ccache
|
||||
uses: Chocobo1/setup-ccache-action@v1
|
||||
with:
|
||||
store_cache: ${{ github.ref == 'refs/heads/master' }}
|
||||
store_cache: ${{ (github.repository != 'qbittorrent/qBittorrent') || (github.ref == 'refs/heads/master') }}
|
||||
update_packager_index: false
|
||||
ccache_options: |
|
||||
max_size=1G
|
||||
|
||||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "77"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url="https://archives.boost.io/release/${{ matrix.libtorrent.boost_major_version }}.${{ matrix.libtorrent.boost_minor_version }}.${{ matrix.libtorrent.boost_patch_version }}/source/boost_${{ matrix.libtorrent.boost_major_version }}_${{ matrix.libtorrent.boost_minor_version }}_${{ matrix.libtorrent.boost_patch_version }}.tar.gz"
|
||||
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ matrix.libtorrent.boost_major_version }}.${{ matrix.libtorrent.boost_minor_version }}.${{ matrix.libtorrent.boost_patch_version }}/boost_${{ matrix.libtorrent.boost_major_version }}_${{ matrix.libtorrent.boost_minor_version }}_${{ matrix.libtorrent.boost_patch_version }}.tar.gz"
|
||||
set +e
|
||||
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
|
||||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
|
||||
@@ -74,12 +78,13 @@ jobs:
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
archives: icu qtbase qtdeclarative qtsvg qttools
|
||||
modules: qtimageformats
|
||||
cache: true
|
||||
|
||||
- name: Install libtorrent
|
||||
run: |
|
||||
git clone \
|
||||
--branch v${{ matrix.libt_version }} \
|
||||
--branch v${{ matrix.libtorrent.version }} \
|
||||
--depth 1 \
|
||||
--recurse-submodules \
|
||||
https://github.com/arvidn/libtorrent.git \
|
||||
@@ -100,8 +105,8 @@ jobs:
|
||||
|
||||
# to avoid scanning 3rdparty codebases, initialize it just before building qbt
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
if: startsWith(matrix.libt_version, 2) && (matrix.qbt_gui == 'GUI=ON')
|
||||
uses: github/codeql-action/init@v4
|
||||
if: startsWith(matrix.libtorrent.version, 2) && (matrix.qbt_gui == 'GUI=ON')
|
||||
with:
|
||||
config-file: ./.github/workflows/helper/codeql/cpp.yaml
|
||||
languages: cpp
|
||||
@@ -123,11 +128,16 @@ jobs:
|
||||
cmake --build build --target qbt_update_translations
|
||||
cmake --build build
|
||||
cmake --build build --target check
|
||||
if [ "${{ matrix.qbt_gui }}" = "GUI=ON" ]; then
|
||||
QT_QPA_PLATFORM=offscreen build/qbittorrent -v
|
||||
else
|
||||
build/qbittorrent-nox -v
|
||||
fi
|
||||
DESTDIR="qbittorrent" cmake --install build
|
||||
|
||||
- name: Run CodeQL analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
if: startsWith(matrix.libt_version, 2) && (matrix.qbt_gui == 'GUI=ON')
|
||||
uses: github/codeql-action/analyze@v4
|
||||
if: startsWith(matrix.libtorrent.version, 2) && (matrix.qbt_gui == 'GUI=ON')
|
||||
with:
|
||||
category: ${{ github.base_ref || github.ref_name }}
|
||||
|
||||
@@ -164,14 +174,15 @@ jobs:
|
||||
run: |
|
||||
rm -f "${{ runner.workspace }}/Qt/${{ matrix.qt_version }}/gcc_64/plugins/sqldrivers/libqsqlmimer.so"
|
||||
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt
|
||||
rm qbittorrent/apprun-hooks/*
|
||||
mkdir -p qbittorrent/apprun-hooks
|
||||
rm -f qbittorrent/apprun-hooks/*
|
||||
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
|
||||
NO_APPSTREAM=1 \
|
||||
OUTPUT=upload/qbittorrent-CI_Ubuntu_x86_64.AppImage \
|
||||
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --output appimage
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: qBittorrent-CI_Ubuntu-x64_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
|
||||
name: qBittorrent-CI_Ubuntu-x64_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libtorrent.version }}_Qt-${{ matrix.qt_version }}
|
||||
path: upload
|
||||
|
||||
8
.github/workflows/ci_webui.yaml
vendored
8
.github/workflows/ci_webui.yaml
vendored
@@ -21,12 +21,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup nodejs
|
||||
uses: actions/setup-node@v5
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
git diff --exit-code
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
config-file: .github/workflows/helper/codeql/js.yaml
|
||||
@@ -60,4 +60,4 @@ jobs:
|
||||
|
||||
- name: Run CodeQL analysis
|
||||
if: ${{ !cancelled() }}
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
||||
45
.github/workflows/ci_windows.yaml
vendored
45
.github/workflows/ci_windows.yaml
vendored
@@ -10,7 +10,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: Build (${{ matrix.libt_version }}, ${{ matrix.config.arch }})
|
||||
name: Build (${{ matrix.libtorrent.version }}, ${{ matrix.config.arch }})
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
permissions:
|
||||
actions: write
|
||||
@@ -18,7 +18,15 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
libt_version: ["2.0.11", "1.2.20"]
|
||||
libtorrent:
|
||||
- version: "2.0.11"
|
||||
boost_major_version: "1"
|
||||
boost_minor_version: "90"
|
||||
boost_patch_version: "0"
|
||||
- version: "1.2.20"
|
||||
boost_major_version: "1"
|
||||
boost_minor_version: "86"
|
||||
boost_patch_version: "0"
|
||||
config:
|
||||
- os: windows-latest
|
||||
arch: x64
|
||||
@@ -31,7 +39,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -86,13 +94,9 @@ jobs:
|
||||
$packages
|
||||
|
||||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "86"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
$boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
$boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
$boost_url="https://archives.boost.io/release/${{ matrix.libtorrent.boost_major_version }}.${{ matrix.libtorrent.boost_minor_version }}.${{ matrix.libtorrent.boost_patch_version }}/source/boost_${{ matrix.libtorrent.boost_major_version }}_${{ matrix.libtorrent.boost_minor_version }}_${{ matrix.libtorrent.boost_patch_version }}.tar.gz"
|
||||
$boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ matrix.libtorrent.boost_major_version }}.${{ matrix.libtorrent.boost_minor_version }}.${{ matrix.libtorrent.boost_patch_version }}/boost_${{ matrix.libtorrent.boost_major_version }}_${{ matrix.libtorrent.boost_minor_version }}_${{ matrix.libtorrent.boost_patch_version }}.tar.gz"
|
||||
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
|
||||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."
|
||||
if ($LastExitCode -ne 0)
|
||||
@@ -112,15 +116,16 @@ jobs:
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: "6.9.1"
|
||||
version: "6.10.1"
|
||||
arch: ${{ matrix.config.qt_arch }}
|
||||
archives: qtbase qtsvg qttools
|
||||
modules: qtimageformats
|
||||
cache: true
|
||||
|
||||
- name: Install libtorrent
|
||||
run: |
|
||||
git clone `
|
||||
--branch v${{ matrix.libt_version }} `
|
||||
--branch v${{ matrix.libtorrent.version }} `
|
||||
--depth 1 `
|
||||
--recurse-submodules `
|
||||
https://github.com/arvidn/libtorrent.git `
|
||||
@@ -182,8 +187,16 @@ jobs:
|
||||
mkdir upload/qBittorrent/plugins/iconengines
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/iconengines/qsvgicon.dll" upload/qBittorrent/plugins/iconengines
|
||||
mkdir upload/qBittorrent/plugins/imageformats
|
||||
# include all imageformats dlls since CI (dev) build links dynamically
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qgif.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qicns.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qico.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qjpeg.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qsvg.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qtga.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qtiff.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qwbmp.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qwebp.dll" upload/qBittorrent/plugins/imageformats
|
||||
mkdir upload/qBittorrent/plugins/platforms
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/platforms/qwindows.dll" upload/qBittorrent/plugins/platforms
|
||||
mkdir upload/qBittorrent/plugins/sqldrivers
|
||||
@@ -200,13 +213,13 @@ jobs:
|
||||
copy ${{ env.libtorrent_path }}/build/compile_commands.json upload/cmake/libtorrent
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: qBittorrent-CI_Windows-${{ matrix.config.arch }}_libtorrent-${{ matrix.libt_version }}
|
||||
name: qBittorrent-CI_Windows-${{ matrix.config.arch }}_libtorrent-${{ matrix.libtorrent.version }}
|
||||
path: upload
|
||||
|
||||
- name: Install NSIS
|
||||
uses: repolevedavaj/install-nsis@265e893c16602d8ccfb0a9ca44173b084078917c # v1.0.3
|
||||
uses: repolevedavaj/install-nsis@a55ed92772254d1e51d880f85ce9b5719f907801 # v1.1.0
|
||||
with:
|
||||
nsis-version: '3.11'
|
||||
|
||||
@@ -216,7 +229,7 @@ jobs:
|
||||
makensis /DQBT_CPU_ARCH="${{ matrix.config.arch }}" /DQBT_DIST_DIR="../../upload/qBittorrent" /WX dist/windows/qbittorrent.nsi
|
||||
|
||||
- name: Upload installer
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: qBittorrent-CI_Windows-${{ matrix.config.arch }}_libtorrent-${{ matrix.libt_version }}-setup
|
||||
name: qBittorrent-CI_Windows-${{ matrix.config.arch }}_libtorrent-${{ matrix.libtorrent.version }}-setup
|
||||
path: dist/windows/qbittorrent_*_setup.exe
|
||||
|
||||
20
.github/workflows/coverity-scan.yaml
vendored
20
.github/workflows/coverity-scan.yaml
vendored
@@ -14,9 +14,13 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
libt_version: ["2.0.11"]
|
||||
libtorrent:
|
||||
- version: "2.0.11"
|
||||
boost_major_version: "1"
|
||||
boost_minor_version: "90"
|
||||
boost_patch_version: "0"
|
||||
qbt_gui: ["GUI=ON"]
|
||||
qt_version: ["6.9.1"]
|
||||
qt_version: ["6.10.1"]
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
@@ -25,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -37,13 +41,9 @@ jobs:
|
||||
libssl-dev libxkbcommon-x11-dev libxcb-cursor-dev zlib1g-dev
|
||||
|
||||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "88"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url="https://archives.boost.io/release/${{ matrix.libtorrent.boost_major_version }}.${{ matrix.libtorrent.boost_minor_version }}.${{ matrix.libtorrent.boost_patch_version }}/source/boost_${{ matrix.libtorrent.boost_major_version }}_${{ matrix.libtorrent.boost_minor_version }}_${{ matrix.libtorrent.boost_patch_version }}.tar.gz"
|
||||
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ matrix.libtorrent.boost_major_version }}.${{ matrix.libtorrent.boost_minor_version }}.${{ matrix.libtorrent.boost_patch_version }}/boost_${{ matrix.libtorrent.boost_major_version }}_${{ matrix.libtorrent.boost_minor_version }}_${{ matrix.libtorrent.boost_patch_version }}.tar.gz"
|
||||
set +e
|
||||
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
|
||||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
- name: Install libtorrent
|
||||
run: |
|
||||
git clone \
|
||||
--branch v${{ matrix.libt_version }} \
|
||||
--branch v${{ matrix.libtorrent.version }} \
|
||||
--depth 1 \
|
||||
--recurse-submodules \
|
||||
https://github.com/arvidn/libtorrent.git \
|
||||
|
||||
@@ -39,6 +39,7 @@ repos:
|
||||
args: ["--fix=lf"]
|
||||
exclude: |
|
||||
(?x)^(
|
||||
src/base/3rdparty/expected.hpp |
|
||||
src/webui/www/private/css/lib/.* |
|
||||
src/webui/www/private/scripts/lib/.* |
|
||||
dist/windows/license.txt |
|
||||
|
||||
@@ -20,7 +20,7 @@ target_compile_features(qbt_common_cfg INTERFACE
|
||||
)
|
||||
|
||||
target_compile_definitions(qbt_common_cfg INTERFACE
|
||||
QT_DISABLE_DEPRECATED_UP_TO=0x060600
|
||||
QT_DISABLE_DEPRECATED_UP_TO=0x060500
|
||||
QT_NO_CAST_FROM_ASCII
|
||||
QT_NO_CAST_TO_ASCII
|
||||
QT_NO_CAST_FROM_BYTEARRAY
|
||||
|
||||
@@ -161,8 +161,8 @@ Name[ta]=qBittorrent
|
||||
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
|
||||
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
|
||||
Name[te]=qBittorrent
|
||||
GenericName[th]=ไคลเอนต์บิททอร์เรนต์
|
||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่านบิตทอร์เรนต์
|
||||
GenericName[th]=ไคลเอนต์บิตทอร์เรนต์
|
||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่าน BitTorrent
|
||||
Name[th]=qBittorrent
|
||||
GenericName[tr]=BitTorrent istemcisi
|
||||
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
||||
|
||||
@@ -62,6 +62,6 @@
|
||||
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="5.2.0~alpha1" date="2025-02-11"/>
|
||||
<release version="5.2.0~beta1" date="2025-12-31"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
11
dist/windows/config.nsh
vendored
11
dist/windows/config.nsh
vendored
@@ -51,6 +51,8 @@
|
||||
|
||||
Unicode true
|
||||
ManifestDPIAware true
|
||||
; add an undocumented function in NSIS, PMv2 requires Win10 1703+
|
||||
ManifestDPIAwareness "PerMonitorV2,System"
|
||||
|
||||
!ifdef USE_UPX
|
||||
!packhdr "$%TEMP%\exehead.tmp" 'upx.exe -9 --best --ultra-brute "$%TEMP%\exehead.tmp"'
|
||||
@@ -59,7 +61,6 @@ ManifestDPIAware true
|
||||
;Setting the compression
|
||||
SetCompressor /SOLID LZMA
|
||||
SetCompressorDictSize 64
|
||||
XPStyle on
|
||||
|
||||
!include "MUI2.nsh"
|
||||
!include "UAC.nsh"
|
||||
@@ -73,8 +74,8 @@ XPStyle on
|
||||
!define SHCNF_IDLIST 0
|
||||
|
||||
;For special folder detection
|
||||
!define CSIDL_APPDATA '0x1A' ;Application Data path
|
||||
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
|
||||
!define FOLDERID_RoamingAppData {3EB685DB-65F9-4CF6-A03A-E3EF65729F3D} ; %APPDATA% (%USERPROFILE%\AppData\Roaming)
|
||||
!define FOLDERID_LocalAppData {F1B32785-6FBA-4FCF-9D55-7B8E7F157091} ; %LOCALAPPDATA% (%USERPROFILE%\AppData\Local)
|
||||
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
||||
@@ -156,16 +157,20 @@ ${Case} 0
|
||||
${IfThen} $3 <> 0 ${|} ${Break} ${|} ;we are admin, let the show go on
|
||||
${If} $1 = 3 ;RunAs completed successfully, but with a non-admin user
|
||||
MessageBox mb_YesNo|mb_IconExclamation|mb_TopMost|mb_SetForeground "This ${thing} requires admin privileges, try again" /SD IDNO IDYES uac_tryagain IDNO 0
|
||||
SetErrorLevel 1314 # WinError.h: `ERROR_PRIVILEGE_NOT_HELD`
|
||||
${EndIf}
|
||||
;fall-through and die
|
||||
${Case} 1223
|
||||
MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "This ${thing} requires admin privileges, aborting!"
|
||||
SetErrorLevel 1223 # WinError.h: `ERROR_CANCELLED`
|
||||
Quit
|
||||
${Case} 1062
|
||||
MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Logon service not running, aborting!"
|
||||
SetErrorLevel 1062 # WinError.h: `ERROR_SERVICE_NOT_ACTIVE`
|
||||
Quit
|
||||
${Default}
|
||||
MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Unable to elevate , error $0"
|
||||
SetErrorLevel 1603 # WinError.h: `ERROR_INSTALL_FAILURE`
|
||||
Quit
|
||||
${EndSwitch}
|
||||
|
||||
|
||||
54
dist/windows/installer-translations/kurdish.nsh
vendored
54
dist/windows/installer-translations/kurdish.nsh
vendored
@@ -1,37 +1,37 @@
|
||||
;Installer strings
|
||||
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_KURDISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_KURDISH} "کیووبیتتۆرێنت (پێویستە)"
|
||||
;LangString inst_desktop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_desktop ${LANG_KURDISH} "Create Desktop Shortcut"
|
||||
LangString inst_desktop ${LANG_KURDISH} "قەدبڕێک دروست بکە لەسەر دێسکتۆپ"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_KURDISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_KURDISH} "قەدبڕێک دروست بکە لەسەر پێڕستی دەتپێک"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_KURDISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_KURDISH} "لەگەڵ هەڵبوونی کۆمپیوتەرەکە کیووبیتتۆرێنت بکەرەوە"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_KURDISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_KURDISH} "پەڕگەکانی .torrent بە کیووبیتتۆرێنت بکەرەوە"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_KURDISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_KURDISH} "بەسەرە موگناتیسییەکان بە کیووبیتتۆرێنت بکەرەوە"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_KURDISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_KURDISH} "یاسای فایەروۆڵی ویندۆز زیاد بکە"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_KURDISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_KURDISH} "سنووری درێژیی ڕێڕەوی ویندۆز ناچالاک بکە (سنووری ٢٦٠ پیتی، پێویستی بە ویندۆزی ١٠ و دواتر هەیە)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_KURDISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_KURDISH} "یاسای فایەروۆڵی ویندۆز زیاد دەبێت"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_KURDISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_KURDISH} "کیووبیتتۆرێنت کارایە. تکایە بەرنامەکە دابخەرەوە پێش دامەزراندن."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_KURDISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_KURDISH} "ئەم وەشانەی ئێستا دەسڕدرێتەوە. ڕێکخستنەکانی بەکارهێنەر و تۆرێنتەکان وەک خۆیان دەمێننەوە."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_KURDISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_KURDISH} "وەشانی پێشوو دەسڕدرێتەوە."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_KURDISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_KURDISH} "کیووبیتتۆرێنت کارا بکە."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_KURDISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_KURDISH} "ئەم دامەزرێنەرە تەنیا لەسەر وەشانی ٦٤ بیتی ویندۆز کار دەکات."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_KURDISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_KURDISH} "ئەم دامەزرێنەرە لانیکەم پێویستی بە ویندۆزی ١٠ یان ویندۆز سێرڤەری ٢٠١٩ هەیە."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_KURDISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_KURDISH} "کیووبیتتۆرێنت بسڕەوە"
|
||||
;LangString inst_arch_mismatch_x64_on_arm64 ${LANG_ENGLISH} "Error: This x64 version of qBittorrent cannot run on ARM64 systems. Please download the ARM64 installer."
|
||||
LangString inst_arch_mismatch_x64_on_arm64 ${LANG_KURDISH} "This x64 version of qBittorrent cannot run on ARM64 systems. Please download the ARM64 installer."
|
||||
;LangString inst_arch_mismatch_arm64_on_x64 ${LANG_ENGLISH} "Error: This ARM64 version of qBittorrent cannot run on x64 systems. Please download the x64 installer."
|
||||
@@ -41,24 +41,24 @@ LangString inst_arch_mismatch_arm64_on_x64 ${LANG_KURDISH} "This ARM64 version o
|
||||
;Uninstaller strings
|
||||
|
||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||
LangString remove_files ${LANG_KURDISH} "Remove files"
|
||||
LangString remove_files ${LANG_KURDISH} "پەڕگەکان بسڕەوە"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_KURDISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_KURDISH} "قەدبڕەکان بسڕەوە"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_KURDISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_KURDISH} "پەیوەندیی پەڕگەکان بسڕەوە"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_KURDISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_KURDISH} "کلیلەکانی تۆمار بسڕەوە"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_KURDISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_KURDISH} "پەڕگەکانی شێوەپێدان بسڕەوە"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_KURDISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_KURDISH} "یاسای فایەروۆڵی ویندۆز بسڕەوە"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_KURDISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_KURDISH} "یاسای فایەروۆڵی ویندۆز دەسڕێتەوە"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_KURDISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_KURDISH} "تۆرێنت و داتای کاشکراو بسڕەوە"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_KURDISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_KURDISH} "کیووبیتتۆرێنت کارایە. تکایە بەرنامەکە دابخە پێش سڕینەوەی."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_KURDISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_KURDISH} "پەیوەندیی .torrent ناسڕێتەوە. پەیوەندیی هەیە بە:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_KURDISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_KURDISH} "پەیوەندیی موگناتیسی ناسڕێتەوە. پەیوەیندیی هەیە بە:"
|
||||
|
||||
@@ -33,9 +33,9 @@ LangString inst_requires_win10 ${LANG_RUSSIAN} "Для работы этого
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_RUSSIAN} "Удалить qBittorrent"
|
||||
;LangString inst_arch_mismatch_x64_on_arm64 ${LANG_ENGLISH} "Error: This x64 version of qBittorrent cannot run on ARM64 systems. Please download the ARM64 installer."
|
||||
LangString inst_arch_mismatch_x64_on_arm64 ${LANG_RUSSIAN} "This x64 version of qBittorrent cannot run on ARM64 systems. Please download the ARM64 installer."
|
||||
LangString inst_arch_mismatch_x64_on_arm64 ${LANG_RUSSIAN} "Эта версия qBittorrent для x64 не может работать на системах ARM64. Пожалуйста, скачайте установщик для ARM64."
|
||||
;LangString inst_arch_mismatch_arm64_on_x64 ${LANG_ENGLISH} "Error: This ARM64 version of qBittorrent cannot run on x64 systems. Please download the x64 installer."
|
||||
LangString inst_arch_mismatch_arm64_on_x64 ${LANG_RUSSIAN} "This ARM64 version of qBittorrent cannot run on x64 systems. Please download the x64 installer."
|
||||
LangString inst_arch_mismatch_arm64_on_x64 ${LANG_RUSSIAN} "Эта версия qBittorrent для ARM64 не может работать на системах x64. Пожалуйста, скачайте установщик для x64."
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
|
||||
@@ -21,7 +21,7 @@ LangString inst_firewallinfo ${LANG_TURKISH} "Windows Güvenlik Duvarı kuralı
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_TURKISH} "qBittorrent çalışıyor. Lütfen yüklemeden önce uygulamayı kapatın."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_TURKISH} "Şu anki sürüm kaldırılacaktır. Kullanıcı ayarları ve torrent'ler bozulmadan kalacaktır."
|
||||
LangString inst_uninstall_question ${LANG_TURKISH} "Şu anki sürüm kaldırılacak. Kullanıcı ayarları ve torrent'ler bozulmadan kalacaktır."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_TURKISH} "Önceki sürüm kaldırılıyor."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
@@ -33,9 +33,9 @@ LangString inst_requires_win10 ${LANG_TURKISH} "Bu yükleyici en az Windows 10
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_TURKISH} "qBittorrent'i kaldır"
|
||||
;LangString inst_arch_mismatch_x64_on_arm64 ${LANG_ENGLISH} "Error: This x64 version of qBittorrent cannot run on ARM64 systems. Please download the ARM64 installer."
|
||||
LangString inst_arch_mismatch_x64_on_arm64 ${LANG_TURKISH} "This x64 version of qBittorrent cannot run on ARM64 systems. Please download the ARM64 installer."
|
||||
LangString inst_arch_mismatch_x64_on_arm64 ${LANG_TURKISH} "qBittorrent'in bu x64 sürümü ARM64 sistemlerde çalışamaz. Lütfen ARM64 yükleyicisini indirin."
|
||||
;LangString inst_arch_mismatch_arm64_on_x64 ${LANG_ENGLISH} "Error: This ARM64 version of qBittorrent cannot run on x64 systems. Please download the x64 installer."
|
||||
LangString inst_arch_mismatch_arm64_on_x64 ${LANG_TURKISH} "This ARM64 version of qBittorrent cannot run on x64 systems. Please download the x64 installer."
|
||||
LangString inst_arch_mismatch_arm64_on_x64 ${LANG_TURKISH} "qBittorrent'in bu ARM64 sürümü x64 sistemlerde çalışamaz. Lütfen x64 yükleyicisini indirin."
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
|
||||
2
dist/windows/installer.nsh
vendored
2
dist/windows/installer.nsh
vendored
@@ -51,7 +51,7 @@ Section $(inst_qbt_req) ;"qBittorrent (required)"
|
||||
WriteRegStr HKLM "Software\Classes\magnet" "Content Type" "application/x-magnet"
|
||||
WriteRegStr HKLM "Software\Classes\magnet" "URL Protocol" ""
|
||||
|
||||
System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, p 0, p 0)'
|
||||
System::Call 'shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, p 0, p 0)'
|
||||
|
||||
; Write the uninstall keys for Windows
|
||||
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\qBittorrent" "DisplayName" "qBittorrent"
|
||||
|
||||
10
dist/windows/uninstaller.nsh
vendored
10
dist/windows/uninstaller.nsh
vendored
@@ -27,7 +27,7 @@ Section "un.$(remove_registry)" ;"un.Remove registry keys"
|
||||
; Remove ProgIDs
|
||||
DeleteRegKey HKLM "Software\Classes\qBittorrent.File.Torrent"
|
||||
DeleteRegKey HKLM "Software\Classes\qBittorrent.Url.Magnet"
|
||||
System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, p 0, p 0)'
|
||||
System::Call 'shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, p 0, p 0)'
|
||||
SectionEnd
|
||||
|
||||
Section "un.$(remove_firewall)" ;
|
||||
@@ -45,8 +45,9 @@ SectionEnd
|
||||
|
||||
Function un.remove_conf_user
|
||||
|
||||
System::Call 'shell32::SHGetSpecialFolderPath(i $HWNDPARENT, t .r1, i ${CSIDL_APPDATA}, i0)i.r0'
|
||||
System::Call 'shell32::SHGetKnownFolderPath(g "${FOLDERID_RoamingAppData}", i 0, p 0, *w . r1) i . r0'
|
||||
RMDir /r "$1\qBittorrent"
|
||||
System::Call 'ole32::CoTaskMemFree(p r1)'
|
||||
|
||||
FunctionEnd
|
||||
|
||||
@@ -58,8 +59,9 @@ SectionEnd
|
||||
|
||||
Function un.remove_cache_user
|
||||
|
||||
System::Call 'shell32::SHGetSpecialFolderPath(i $HWNDPARENT, t .r1, i ${CSIDL_LOCALAPPDATA}, i0)i.r0'
|
||||
RMDir /r "$1\qBittorrent\"
|
||||
System::Call 'shell32::SHGetKnownFolderPath(g "${FOLDERID_LocalAppData}", i 0, p 0, *w . r1) i . r0'
|
||||
RMDir /r "$1\qBittorrent"
|
||||
System::Call 'ole32::CoTaskMemFree(p r1)'
|
||||
|
||||
FunctionEnd
|
||||
|
||||
|
||||
@@ -776,8 +776,9 @@ void Application::allTorrentsFinished()
|
||||
bool isShutdown = pref->shutdownWhenDownloadsComplete();
|
||||
bool isSuspend = pref->suspendWhenDownloadsComplete();
|
||||
bool isHibernate = pref->hibernateWhenDownloadsComplete();
|
||||
bool isReboot = pref->rebootWhenDownloadsComplete();
|
||||
|
||||
bool haveAction = isExit || isShutdown || isSuspend || isHibernate;
|
||||
const bool haveAction = isExit || isShutdown || isSuspend || isHibernate || isReboot;
|
||||
if (!haveAction) return;
|
||||
|
||||
ShutdownDialogAction action = ShutdownDialogAction::Exit;
|
||||
@@ -787,6 +788,8 @@ void Application::allTorrentsFinished()
|
||||
action = ShutdownDialogAction::Hibernate;
|
||||
else if (isShutdown)
|
||||
action = ShutdownDialogAction::Shutdown;
|
||||
else if (isReboot)
|
||||
action = ShutdownDialogAction::Reboot;
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
// ask confirm
|
||||
@@ -808,6 +811,7 @@ void Application::allTorrentsFinished()
|
||||
pref->setShutdownWhenDownloadsComplete(false);
|
||||
pref->setSuspendWhenDownloadsComplete(false);
|
||||
pref->setHibernateWhenDownloadsComplete(false);
|
||||
pref->setRebootWhenDownloadsComplete(false);
|
||||
// Make sure preferences are synced before exiting
|
||||
m_shutdownAct = action;
|
||||
}
|
||||
|
||||
@@ -127,10 +127,12 @@ namespace
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
|
||||
void displayVersion()
|
||||
{
|
||||
printf("%s %s\n", qUtf8Printable(qApp->applicationName()), QBT_VERSION);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
void showSplashScreen()
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QMetaEnum>
|
||||
|
||||
#include "base/bittorrent/sharelimitaction.h"
|
||||
#include "base/bittorrent/sharelimits.h"
|
||||
#include "base/bittorrent/torrentcontentlayout.h"
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
|
||||
7191
src/base/3rdparty/expected.hpp
vendored
7191
src/base/3rdparty/expected.hpp
vendored
File diff suppressed because it is too large
Load Diff
@@ -34,10 +34,11 @@ add_library(qbt_base STATIC
|
||||
bittorrent/session.h
|
||||
bittorrent/sessionimpl.h
|
||||
bittorrent/sessionstatus.h
|
||||
bittorrent/sharelimitaction.h
|
||||
bittorrent/sharelimits.h
|
||||
bittorrent/speedmonitor.h
|
||||
bittorrent/sslparameters.h
|
||||
bittorrent/torrent.h
|
||||
bittorrent/torrentannouncestatus.h
|
||||
bittorrent/torrentcontenthandler.h
|
||||
bittorrent/torrentcontentlayout.h
|
||||
bittorrent/torrentcontentremoveoption.h
|
||||
@@ -247,7 +248,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
${IOKit_LIBRARY}
|
||||
)
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
target_link_libraries(qbt_base PRIVATE Iphlpapi PowrProf)
|
||||
target_link_libraries(qbt_base PRIVATE iphlpapi powrprof)
|
||||
endif()
|
||||
|
||||
if (NOT GUI)
|
||||
|
||||
@@ -125,9 +125,9 @@ BitTorrent::AddTorrentParams BitTorrent::parseAddTorrentParams(const QJsonObject
|
||||
.useAutoTMM = getOptionalBool(jsonObj, PARAM_AUTOTMM),
|
||||
.uploadLimit = jsonObj.value(PARAM_UPLOADLIMIT).toInt(-1),
|
||||
.downloadLimit = jsonObj.value(PARAM_DOWNLOADLIMIT).toInt(-1),
|
||||
.seedingTimeLimit = jsonObj.value(PARAM_SEEDINGTIMELIMIT).toInt(Torrent::USE_GLOBAL_SEEDING_TIME),
|
||||
.inactiveSeedingTimeLimit = jsonObj.value(PARAM_INACTIVESEEDINGTIMELIMIT).toInt(Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME),
|
||||
.ratioLimit = jsonObj.value(PARAM_RATIOLIMIT).toDouble(Torrent::USE_GLOBAL_RATIO),
|
||||
.seedingTimeLimit = jsonObj.value(PARAM_SEEDINGTIMELIMIT).toInt(DEFAULT_SEEDING_TIME_LIMIT),
|
||||
.inactiveSeedingTimeLimit = jsonObj.value(PARAM_INACTIVESEEDINGTIMELIMIT).toInt(DEFAULT_SEEDING_TIME_LIMIT),
|
||||
.ratioLimit = jsonObj.value(PARAM_RATIOLIMIT).toDouble(DEFAULT_RATIO_LIMIT),
|
||||
.shareLimitAction = getEnum<ShareLimitAction>(jsonObj, PARAM_SHARELIMITACTION, ShareLimitAction::Default),
|
||||
.sslParameters =
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
#include "base/path.h"
|
||||
#include "base/tagset.h"
|
||||
#include "sharelimitaction.h"
|
||||
#include "sharelimits.h"
|
||||
#include "sslparameters.h"
|
||||
#include "torrent.h"
|
||||
#include "torrentcontentlayout.h"
|
||||
@@ -68,9 +68,9 @@ namespace BitTorrent
|
||||
std::optional<bool> useAutoTMM;
|
||||
int uploadLimit = -1;
|
||||
int downloadLimit = -1;
|
||||
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
|
||||
int inactiveSeedingTimeLimit = Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME;
|
||||
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
|
||||
int seedingTimeLimit = DEFAULT_SEEDING_TIME_LIMIT;
|
||||
int inactiveSeedingTimeLimit = DEFAULT_SEEDING_TIME_LIMIT;
|
||||
qreal ratioLimit = DEFAULT_RATIO_LIMIT;
|
||||
ShareLimitAction shareLimitAction = ShareLimitAction::Default;
|
||||
SSLParameters sslParameters;
|
||||
|
||||
|
||||
@@ -239,8 +239,8 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
|
||||
torrentParams.comment = fromLTString(resumeDataRoot.dict_find_string_value("qBt-comment"));
|
||||
torrentParams.hasFinishedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus");
|
||||
torrentParams.firstLastPiecePriority = resumeDataRoot.dict_find_int_value("qBt-firstLastPiecePriority");
|
||||
torrentParams.seedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME);
|
||||
torrentParams.inactiveSeedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-inactiveSeedingTimeLimit", Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME);
|
||||
torrentParams.seedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-seedingTimeLimit", DEFAULT_SEEDING_TIME_LIMIT);
|
||||
torrentParams.inactiveSeedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-inactiveSeedingTimeLimit", DEFAULT_SEEDING_TIME_LIMIT);
|
||||
torrentParams.shareLimitAction = Utils::String::toEnum(
|
||||
fromLTString(resumeDataRoot.dict_find_string_value("qBt-shareLimitAction")), ShareLimitAction::Default);
|
||||
|
||||
@@ -283,7 +283,7 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
|
||||
|
||||
const lt::string_view ratioLimitString = resumeDataRoot.dict_find_string_value("qBt-ratioLimit");
|
||||
if (ratioLimitString.empty())
|
||||
torrentParams.ratioLimit = resumeDataRoot.dict_find_int_value("qBt-ratioLimit", Torrent::USE_GLOBAL_RATIO * 1000) / 1000.0;
|
||||
torrentParams.ratioLimit = resumeDataRoot.dict_find_int_value("qBt-ratioLimit", DEFAULT_RATIO_LIMIT * 1000) / 1000.0;
|
||||
else
|
||||
torrentParams.ratioLimit = fromLTString(ratioLimitString).toDouble();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2021-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -32,10 +32,16 @@
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/utils/string.h"
|
||||
|
||||
const QString OPTION_SAVEPATH = u"save_path"_s;
|
||||
const QString OPTION_DOWNLOADPATH = u"download_path"_s;
|
||||
|
||||
const QString OPTION_RATIOLIMIT = u"ratio_limit"_s;
|
||||
const QString OPTION_SEEDINGTIMELIMIT = u"seeding_time_limit"_s;
|
||||
const QString OPTION_INACTIVESEEDINGTIMELIMIT = u"inactive_seeding_time_limit"_s;
|
||||
const QString OPTION_SHARELIMITACTION = u"share_limit_action"_s;
|
||||
|
||||
BitTorrent::CategoryOptions BitTorrent::CategoryOptions::fromJSON(const QJsonObject &jsonObj)
|
||||
{
|
||||
CategoryOptions options;
|
||||
@@ -47,6 +53,11 @@ BitTorrent::CategoryOptions BitTorrent::CategoryOptions::fromJSON(const QJsonObj
|
||||
else if (downloadPathValue.isString())
|
||||
options.downloadPath = {true, Path(downloadPathValue.toString())};
|
||||
|
||||
options.ratioLimit = jsonObj.value(OPTION_RATIOLIMIT).toDouble(DEFAULT_RATIO_LIMIT);
|
||||
options.seedingTimeLimit = jsonObj.value(OPTION_SEEDINGTIMELIMIT).toInt(DEFAULT_SEEDING_TIME_LIMIT);
|
||||
options.inactiveSeedingTimeLimit = jsonObj.value(OPTION_INACTIVESEEDINGTIMELIMIT).toInt(DEFAULT_SEEDING_TIME_LIMIT);
|
||||
options.shareLimitAction = Utils::String::toEnum<ShareLimitAction>(jsonObj.value(OPTION_SHARELIMITACTION).toString(), ShareLimitAction::Default);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -63,12 +74,10 @@ QJsonObject BitTorrent::CategoryOptions::toJSON() const
|
||||
|
||||
return {
|
||||
{OPTION_SAVEPATH, savePath.data()},
|
||||
{OPTION_DOWNLOADPATH, downloadPathValue}
|
||||
{OPTION_DOWNLOADPATH, downloadPathValue},
|
||||
{OPTION_RATIOLIMIT, ratioLimit},
|
||||
{OPTION_SEEDINGTIMELIMIT, seedingTimeLimit},
|
||||
{OPTION_INACTIVESEEDINGTIMELIMIT, inactiveSeedingTimeLimit},
|
||||
{OPTION_SHARELIMITACTION, Utils::String::fromEnum(shareLimitAction)}
|
||||
};
|
||||
}
|
||||
|
||||
bool BitTorrent::operator==(const BitTorrent::CategoryOptions &left, const BitTorrent::CategoryOptions &right)
|
||||
{
|
||||
return ((left.savePath == right.savePath)
|
||||
&& (left.downloadPath == right.downloadPath));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2021-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
#include "base/path.h"
|
||||
#include "downloadpathoption.h"
|
||||
#include "sharelimits.h"
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
@@ -44,9 +45,14 @@ namespace BitTorrent
|
||||
Path savePath;
|
||||
std::optional<DownloadPathOption> downloadPath;
|
||||
|
||||
qreal ratioLimit = DEFAULT_RATIO_LIMIT;
|
||||
int seedingTimeLimit = DEFAULT_SEEDING_TIME_LIMIT;
|
||||
int inactiveSeedingTimeLimit = DEFAULT_SEEDING_TIME_LIMIT;
|
||||
ShareLimitAction shareLimitAction = ShareLimitAction::Default;
|
||||
|
||||
static CategoryOptions fromJSON(const QJsonObject &jsonObj);
|
||||
QJsonObject toJSON() const;
|
||||
};
|
||||
|
||||
bool operator==(const CategoryOptions &left, const CategoryOptions &right);
|
||||
friend bool operator==(const CategoryOptions &, const CategoryOptions &) = default;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
#include "base/path.h"
|
||||
#include "base/tagset.h"
|
||||
#include "sharelimitaction.h"
|
||||
#include "sharelimits.h"
|
||||
#include "sslparameters.h"
|
||||
#include "torrent.h"
|
||||
#include "torrentcontentlayout.h"
|
||||
@@ -61,9 +61,9 @@ namespace BitTorrent
|
||||
|
||||
bool addToQueueTop = false; // only for new torrents
|
||||
|
||||
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
|
||||
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
|
||||
int inactiveSeedingTimeLimit = Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME;
|
||||
qreal ratioLimit = DEFAULT_RATIO_LIMIT;
|
||||
int seedingTimeLimit = DEFAULT_SEEDING_TIME_LIMIT;
|
||||
int inactiveSeedingTimeLimit = DEFAULT_SEEDING_TIME_LIMIT;
|
||||
ShareLimitAction shareLimitAction = ShareLimitAction::Default;
|
||||
|
||||
SSLParameters sslParameters;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#include "addtorrenterror.h"
|
||||
#include "addtorrentparams.h"
|
||||
#include "categoryoptions.h"
|
||||
#include "sharelimitaction.h"
|
||||
#include "sharelimits.h"
|
||||
#include "torrentcontentremoveoption.h"
|
||||
#include "trackerentry.h"
|
||||
#include "trackerentrystatus.h"
|
||||
@@ -158,15 +158,17 @@ namespace BitTorrent
|
||||
|
||||
virtual QStringList categories() const = 0;
|
||||
virtual CategoryOptions categoryOptions(const QString &categoryName) const = 0;
|
||||
virtual bool setCategoryOptions(const QString &categoryName, const CategoryOptions &options) = 0;
|
||||
virtual Path categorySavePath(const QString &categoryName) const = 0;
|
||||
virtual Path categorySavePath(const QString &categoryName, const CategoryOptions &options) const = 0;
|
||||
virtual Path categoryDownloadPath(const QString &categoryName) const = 0;
|
||||
virtual Path categoryDownloadPath(const QString &categoryName, const CategoryOptions &options) const = 0;
|
||||
virtual qreal categoryRatioLimit(const QString &categoryName) const = 0;
|
||||
virtual int categorySeedingTimeLimit(const QString &categoryName) const = 0;
|
||||
virtual int categoryInactiveSeedingTimeLimit(const QString &categoryName) const = 0;
|
||||
virtual ShareLimitAction categoryShareLimitAction(const QString &categoryName) const = 0;
|
||||
virtual bool addCategory(const QString &name, const CategoryOptions &options = {}) = 0;
|
||||
virtual bool editCategory(const QString &name, const CategoryOptions &options) = 0;
|
||||
virtual bool removeCategory(const QString &name) = 0;
|
||||
virtual bool isSubcategoriesEnabled() const = 0;
|
||||
virtual void setSubcategoriesEnabled(bool value) = 0;
|
||||
virtual bool useCategoryPathsInManualMode() const = 0;
|
||||
virtual void setUseCategoryPathsInManualMode(bool value) = 0;
|
||||
|
||||
@@ -385,8 +387,8 @@ namespace BitTorrent
|
||||
virtual void setOutgoingPortsMax(int max) = 0;
|
||||
virtual int UPnPLeaseDuration() const = 0;
|
||||
virtual void setUPnPLeaseDuration(int duration) = 0;
|
||||
virtual int peerToS() const = 0;
|
||||
virtual void setPeerToS(int value) = 0;
|
||||
virtual int peerDSCP() const = 0;
|
||||
virtual void setPeerDSCP(int value) = 0;
|
||||
virtual bool ignoreLimitsOnLAN() const = 0;
|
||||
virtual void setIgnoreLimitsOnLAN(bool ignore) = 0;
|
||||
virtual bool includeOverheadInLimits() const = 0;
|
||||
@@ -518,7 +520,7 @@ namespace BitTorrent
|
||||
void torrentTagRemoved(Torrent *torrent, const Tag &tag);
|
||||
void trackerError(Torrent *torrent, const QString &tracker);
|
||||
void trackersAdded(Torrent *torrent, const QList<TrackerEntry> &trackers);
|
||||
void trackersChanged(Torrent *torrent);
|
||||
void trackersReset(Torrent *torrent, const QList<TrackerEntryStatus> &oldEntries, const QList<TrackerEntry> &newEntries);
|
||||
void trackersRemoved(Torrent *torrent, const QStringList &trackers);
|
||||
void trackerSuccess(Torrent *torrent, const QString &tracker);
|
||||
void trackerWarning(Torrent *torrent, const QString &tracker);
|
||||
|
||||
@@ -484,7 +484,7 @@ SessionImpl::SessionImpl(QObject *parent)
|
||||
, m_outgoingPortsMin(BITTORRENT_SESSION_KEY(u"OutgoingPortsMin"_s), 0)
|
||||
, m_outgoingPortsMax(BITTORRENT_SESSION_KEY(u"OutgoingPortsMax"_s), 0)
|
||||
, m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY(u"UPnPLeaseDuration"_s), 0)
|
||||
, m_peerToS(BITTORRENT_SESSION_KEY(u"PeerToS"_s), 0x04)
|
||||
, m_peerDSCP(BITTORRENT_SESSION_KEY(u"PeerToS"_s), 0x01)
|
||||
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY(u"IgnoreLimitsOnLAN"_s), false)
|
||||
, m_includeOverheadInLimits(BITTORRENT_SESSION_KEY(u"IncludeOverheadInLimits"_s), false)
|
||||
, m_announceIP(BITTORRENT_SESSION_KEY(u"AnnounceIP"_s))
|
||||
@@ -513,9 +513,9 @@ SessionImpl::SessionImpl(QObject *parent)
|
||||
, m_additionalTrackersURL(BITTORRENT_SESSION_KEY(u"AdditionalTrackersURL"_s))
|
||||
, m_globalMaxRatio(BITTORRENT_SESSION_KEY(u"GlobalMaxRatio"_s), -1, [](qreal r) { return r < 0 ? -1. : r; })
|
||||
, m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_s)
|
||||
, Torrent::NO_SEEDING_TIME_LIMIT, lowerLimited(Torrent::NO_SEEDING_TIME_LIMIT))
|
||||
, NO_SEEDING_TIME_LIMIT, lowerLimited(NO_SEEDING_TIME_LIMIT))
|
||||
, m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s)
|
||||
, Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT, lowerLimited(Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT))
|
||||
, NO_SEEDING_TIME_LIMIT, lowerLimited(NO_SEEDING_TIME_LIMIT))
|
||||
, m_isAddTorrentToQueueTop(BITTORRENT_SESSION_KEY(u"AddTorrentToTopOfQueue"_s), false)
|
||||
, m_isAddTorrentStopped(BITTORRENT_SESSION_KEY(u"AddTorrentStopped"_s), false)
|
||||
, m_torrentStopCondition(BITTORRENT_SESSION_KEY(u"TorrentStopCondition"_s), Torrent::StopCondition::None)
|
||||
@@ -555,7 +555,6 @@ SessionImpl::SessionImpl(QObject *parent)
|
||||
, m_savePath(BITTORRENT_SESSION_KEY(u"DefaultSavePath"_s), specialFolderLocation(SpecialFolder::Downloads))
|
||||
, m_downloadPath(BITTORRENT_SESSION_KEY(u"TempPath"_s), (savePath() / Path(u"temp"_s)))
|
||||
, m_isDownloadPathEnabled(BITTORRENT_SESSION_KEY(u"TempPathEnabled"_s), false)
|
||||
, m_isSubcategoriesEnabled(BITTORRENT_SESSION_KEY(u"SubcategoriesEnabled"_s), false)
|
||||
, m_useCategoryPathsInManualMode(BITTORRENT_SESSION_KEY(u"UseCategoryPathsInManualMode"_s), false)
|
||||
, m_isAutoTMMDisabledByDefault(BITTORRENT_SESSION_KEY(u"DisableAutoTMMByDefault"_s), true)
|
||||
, m_isDisableAutoTMMWhenCategoryChanged(BITTORRENT_SESSION_KEY(u"DisableAutoTMMTriggers/CategoryChanged"_s), false)
|
||||
@@ -626,11 +625,6 @@ SessionImpl::SessionImpl(QObject *parent)
|
||||
enableBandwidthScheduler();
|
||||
|
||||
loadCategories();
|
||||
if (isSubcategoriesEnabled())
|
||||
{
|
||||
// if subcategories support changed manually
|
||||
m_categories = expandCategories(m_categories);
|
||||
}
|
||||
|
||||
const QStringList storedTags = m_storedTags.get();
|
||||
for (const QString &tagStr : storedTags)
|
||||
@@ -938,15 +932,8 @@ Path SessionImpl::categorySavePath(const QString &categoryName, const CategoryOp
|
||||
if (path.isEmpty())
|
||||
{
|
||||
// use implicit save path
|
||||
if (isSubcategoriesEnabled())
|
||||
{
|
||||
path = Utils::Fs::toValidPath(subcategoryName(categoryName));
|
||||
basePath = categorySavePath(parentCategoryName(categoryName));
|
||||
}
|
||||
else
|
||||
{
|
||||
path = Utils::Fs::toValidPath(categoryName);
|
||||
}
|
||||
path = Utils::Fs::toValidPath(subcategoryName(categoryName));
|
||||
basePath = categorySavePath(parentCategoryName(categoryName));
|
||||
}
|
||||
|
||||
return (path.isAbsolute() ? path : (basePath / path));
|
||||
@@ -966,8 +953,7 @@ Path SessionImpl::categoryDownloadPath(const QString &categoryName, const Catego
|
||||
if (categoryName.isEmpty())
|
||||
return downloadPath();
|
||||
|
||||
const bool useSubcategories = isSubcategoriesEnabled();
|
||||
const QString name = useSubcategories ? subcategoryName(categoryName) : categoryName;
|
||||
const QString name = subcategoryName(categoryName);
|
||||
const Path path = !downloadPathOption.path.isEmpty()
|
||||
? downloadPathOption.path
|
||||
: Utils::Fs::toValidPath(name); // use implicit download path
|
||||
@@ -975,7 +961,7 @@ Path SessionImpl::categoryDownloadPath(const QString &categoryName, const Catego
|
||||
if (path.isAbsolute())
|
||||
return path;
|
||||
|
||||
const QString parentName = useSubcategories ? parentCategoryName(categoryName) : QString();
|
||||
const QString parentName = parentCategoryName(categoryName);
|
||||
CategoryOptions parentOptions = categoryOptions(parentName);
|
||||
// Even if download path of parent category is disabled (directly or by inheritance)
|
||||
// we need to construct the one as if it would be enabled.
|
||||
@@ -986,6 +972,62 @@ Path SessionImpl::categoryDownloadPath(const QString &categoryName, const Catego
|
||||
return (basePath / path);
|
||||
}
|
||||
|
||||
qreal SessionImpl::categoryRatioLimit(const QString &categoryName) const
|
||||
{
|
||||
if (categoryName.isEmpty())
|
||||
return globalMaxRatio();
|
||||
|
||||
if (const auto ratioLimit = categoryOptions(categoryName).ratioLimit;
|
||||
ratioLimit != DEFAULT_RATIO_LIMIT)
|
||||
{
|
||||
return ratioLimit;
|
||||
}
|
||||
|
||||
return categoryRatioLimit(parentCategoryName(categoryName));
|
||||
}
|
||||
|
||||
int SessionImpl::categorySeedingTimeLimit(const QString &categoryName) const
|
||||
{
|
||||
if (categoryName.isEmpty())
|
||||
return globalMaxSeedingMinutes();
|
||||
|
||||
if (const auto seedingTimeLimit = categoryOptions(categoryName).seedingTimeLimit;
|
||||
seedingTimeLimit != DEFAULT_SEEDING_TIME_LIMIT)
|
||||
{
|
||||
return seedingTimeLimit;
|
||||
}
|
||||
|
||||
return categorySeedingTimeLimit(parentCategoryName(categoryName));
|
||||
}
|
||||
|
||||
int SessionImpl::categoryInactiveSeedingTimeLimit(const QString &categoryName) const
|
||||
{
|
||||
if (categoryName.isEmpty())
|
||||
return globalMaxInactiveSeedingMinutes();
|
||||
|
||||
if (const auto inactiveSeedingTimeLimit = categoryOptions(categoryName).inactiveSeedingTimeLimit;
|
||||
inactiveSeedingTimeLimit != DEFAULT_SEEDING_TIME_LIMIT)
|
||||
{
|
||||
return inactiveSeedingTimeLimit;
|
||||
}
|
||||
|
||||
return categoryInactiveSeedingTimeLimit(parentCategoryName(categoryName));
|
||||
}
|
||||
|
||||
ShareLimitAction SessionImpl::categoryShareLimitAction(const QString &categoryName) const
|
||||
{
|
||||
if (categoryName.isEmpty())
|
||||
return shareLimitAction();
|
||||
|
||||
if (const auto shareLimitAction = categoryOptions(categoryName).shareLimitAction;
|
||||
shareLimitAction != ShareLimitAction::Default)
|
||||
{
|
||||
return shareLimitAction;
|
||||
}
|
||||
|
||||
return categoryShareLimitAction(parentCategoryName(categoryName));
|
||||
}
|
||||
|
||||
DownloadPathOption SessionImpl::resolveCategoryDownloadPathOption(const QString &categoryName, const std::optional<DownloadPathOption> &option) const
|
||||
{
|
||||
if (categoryName.isEmpty())
|
||||
@@ -994,7 +1036,7 @@ DownloadPathOption SessionImpl::resolveCategoryDownloadPathOption(const QString
|
||||
if (option.has_value())
|
||||
return *option;
|
||||
|
||||
const QString parentName = isSubcategoriesEnabled() ? parentCategoryName(categoryName) : QString();
|
||||
const QString parentName = parentCategoryName(categoryName);
|
||||
return resolveCategoryDownloadPathOption(parentName, categoryOptions(parentName).downloadPath);
|
||||
}
|
||||
|
||||
@@ -1006,15 +1048,12 @@ bool SessionImpl::addCategory(const QString &name, const CategoryOptions &option
|
||||
if (!isValidCategoryName(name) || m_categories.contains(name))
|
||||
return false;
|
||||
|
||||
if (isSubcategoriesEnabled())
|
||||
for (const QString &parent : asConst(expandCategory(name)))
|
||||
{
|
||||
for (const QString &parent : asConst(expandCategory(name)))
|
||||
if ((parent != name) && !m_categories.contains(parent))
|
||||
{
|
||||
if ((parent != name) && !m_categories.contains(parent))
|
||||
{
|
||||
m_categories[parent] = {};
|
||||
emit categoryAdded(parent);
|
||||
}
|
||||
m_categories[parent] = {};
|
||||
emit categoryAdded(parent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1025,9 +1064,9 @@ bool SessionImpl::addCategory(const QString &name, const CategoryOptions &option
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionImpl::editCategory(const QString &name, const CategoryOptions &options)
|
||||
bool SessionImpl::setCategoryOptions(const QString &categoryName, const CategoryOptions &options)
|
||||
{
|
||||
const auto it = m_categories.find(name);
|
||||
const auto it = m_categories.find(categoryName);
|
||||
if (it == m_categories.end())
|
||||
return false;
|
||||
|
||||
@@ -1035,14 +1074,15 @@ bool SessionImpl::editCategory(const QString &name, const CategoryOptions &optio
|
||||
if (options == currentOptions)
|
||||
return false;
|
||||
|
||||
if (isDisableAutoTMMWhenCategorySavePathChanged())
|
||||
if (isDisableAutoTMMWhenCategorySavePathChanged()
|
||||
&& ((options.savePath != currentOptions.savePath) || (options.downloadPath != currentOptions.downloadPath)))
|
||||
{
|
||||
// This should be done before changing the category options
|
||||
// to prevent the torrent from being moved at the new save path.
|
||||
|
||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||
{
|
||||
if (torrent->category() == name)
|
||||
if (torrent->category() == categoryName)
|
||||
torrent->setAutoTMMEnabled(false);
|
||||
}
|
||||
}
|
||||
@@ -1052,39 +1092,37 @@ bool SessionImpl::editCategory(const QString &name, const CategoryOptions &optio
|
||||
|
||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||
{
|
||||
if (torrent->category() == name)
|
||||
if (torrent->category() == categoryName)
|
||||
torrent->handleCategoryOptionsChanged();
|
||||
}
|
||||
|
||||
emit categoryOptionsChanged(name);
|
||||
emit categoryOptionsChanged(categoryName);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionImpl::removeCategory(const QString &name)
|
||||
{
|
||||
const QString parentCategory = parentCategoryName(name);
|
||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||
{
|
||||
if (torrent->belongsToCategory(name))
|
||||
torrent->setCategory(u""_s);
|
||||
torrent->setCategory(parentCategory);
|
||||
}
|
||||
|
||||
// remove stored category and its subcategories if exist
|
||||
bool result = false;
|
||||
if (isSubcategoriesEnabled())
|
||||
// remove subcategories
|
||||
const QString test = name + u'/';
|
||||
Algorithm::removeIf(m_categories, [this, &test, &result](const QString &category, const CategoryOptions &)
|
||||
{
|
||||
// remove subcategories
|
||||
const QString test = name + u'/';
|
||||
Algorithm::removeIf(m_categories, [this, &test, &result](const QString &category, const CategoryOptions &)
|
||||
if (category.startsWith(test))
|
||||
{
|
||||
if (category.startsWith(test))
|
||||
{
|
||||
result = true;
|
||||
emit categoryRemoved(category);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
result = true;
|
||||
emit categoryRemoved(category);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
result = (m_categories.remove(name) > 0) || result;
|
||||
|
||||
@@ -1098,32 +1136,6 @@ bool SessionImpl::removeCategory(const QString &name)
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SessionImpl::isSubcategoriesEnabled() const
|
||||
{
|
||||
return m_isSubcategoriesEnabled;
|
||||
}
|
||||
|
||||
void SessionImpl::setSubcategoriesEnabled(const bool value)
|
||||
{
|
||||
if (isSubcategoriesEnabled() == value) return;
|
||||
|
||||
if (value)
|
||||
{
|
||||
// expand categories to include all parent categories
|
||||
m_categories = expandCategories(m_categories);
|
||||
// update stored categories
|
||||
storeCategories();
|
||||
}
|
||||
else
|
||||
{
|
||||
// reload categories
|
||||
loadCategories();
|
||||
}
|
||||
|
||||
m_isSubcategoriesEnabled = value;
|
||||
emit subcategoriesSupportChanged();
|
||||
}
|
||||
|
||||
bool SessionImpl::useCategoryPathsInManualMode() const
|
||||
{
|
||||
return m_useCategoryPathsInManualMode;
|
||||
@@ -1281,7 +1293,7 @@ qreal SessionImpl::globalMaxRatio() const
|
||||
void SessionImpl::setGlobalMaxRatio(qreal ratio)
|
||||
{
|
||||
if (ratio < 0)
|
||||
ratio = Torrent::NO_RATIO_LIMIT;
|
||||
ratio = NO_RATIO_LIMIT;
|
||||
|
||||
if (ratio != globalMaxRatio())
|
||||
{
|
||||
@@ -1297,7 +1309,7 @@ int SessionImpl::globalMaxSeedingMinutes() const
|
||||
|
||||
void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
|
||||
{
|
||||
minutes = std::max(minutes, Torrent::NO_SEEDING_TIME_LIMIT);
|
||||
minutes = std::max(minutes, NO_SEEDING_TIME_LIMIT);
|
||||
|
||||
if (minutes != globalMaxSeedingMinutes())
|
||||
{
|
||||
@@ -1313,7 +1325,7 @@ int SessionImpl::globalMaxInactiveSeedingMinutes() const
|
||||
|
||||
void SessionImpl::setGlobalMaxInactiveSeedingMinutes(int minutes)
|
||||
{
|
||||
minutes = std::max(minutes, Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT);
|
||||
minutes = std::max(minutes, NO_SEEDING_TIME_LIMIT);
|
||||
|
||||
if (minutes != globalMaxInactiveSeedingMinutes())
|
||||
{
|
||||
@@ -2057,7 +2069,7 @@ lt::settings_pack SessionImpl::loadLTSettings() const
|
||||
// UPnP lease duration
|
||||
settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
|
||||
// Type of service
|
||||
settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
|
||||
settingsPack.set_int(lt::settings_pack::peer_dscp, peerDSCP());
|
||||
// Include overhead in transfer limits
|
||||
settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
|
||||
// IP address to announce to trackers
|
||||
@@ -2356,14 +2368,9 @@ void SessionImpl::processTorrentShareLimits(TorrentImpl *torrent)
|
||||
if (!torrent->isFinished() || torrent->isForced())
|
||||
return;
|
||||
|
||||
const auto effectiveLimit = []<typename T>(const T limit, const T useGlobalLimit, const T globalLimit) -> T
|
||||
{
|
||||
return (limit == useGlobalLimit) ? globalLimit : limit;
|
||||
};
|
||||
|
||||
const qreal ratioLimit = effectiveLimit(torrent->ratioLimit(), Torrent::USE_GLOBAL_RATIO, globalMaxRatio());
|
||||
const int seedingTimeLimit = effectiveLimit(torrent->seedingTimeLimit(), Torrent::USE_GLOBAL_SEEDING_TIME, globalMaxSeedingMinutes());
|
||||
const int inactiveSeedingTimeLimit = effectiveLimit(torrent->inactiveSeedingTimeLimit(), Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME, globalMaxInactiveSeedingMinutes());
|
||||
const qreal ratioLimit = torrent->effectiveRatioLimit();
|
||||
const int seedingTimeLimit = torrent->effectiveSeedingTimeLimit();
|
||||
const int inactiveSeedingTimeLimit = torrent->effectiveInactiveSeedingTimeLimit();
|
||||
|
||||
bool reached = false;
|
||||
QString description;
|
||||
@@ -2390,7 +2397,7 @@ void SessionImpl::processTorrentShareLimits(TorrentImpl *torrent)
|
||||
if (reached)
|
||||
{
|
||||
const QString torrentName = tr("Torrent: \"%1\".").arg(torrent->name());
|
||||
const ShareLimitAction shareLimitAction = (torrent->shareLimitAction() == ShareLimitAction::Default) ? m_shareLimitAction : torrent->shareLimitAction();
|
||||
const ShareLimitAction shareLimitAction = torrent->effectiveShareLimitAction();
|
||||
|
||||
if (shareLimitAction == ShareLimitAction::Remove)
|
||||
{
|
||||
@@ -4882,17 +4889,17 @@ void SessionImpl::setUPnPLeaseDuration(const int duration)
|
||||
}
|
||||
}
|
||||
|
||||
int SessionImpl::peerToS() const
|
||||
int SessionImpl::peerDSCP() const
|
||||
{
|
||||
return m_peerToS;
|
||||
return m_peerDSCP;
|
||||
}
|
||||
|
||||
void SessionImpl::setPeerToS(const int value)
|
||||
void SessionImpl::setPeerDSCP(const int value)
|
||||
{
|
||||
if (value == m_peerToS)
|
||||
if (value == m_peerDSCP)
|
||||
return;
|
||||
|
||||
m_peerToS = value;
|
||||
m_peerDSCP = value;
|
||||
configureDeferred();
|
||||
}
|
||||
|
||||
@@ -5218,9 +5225,9 @@ bool SessionImpl::isKnownTorrent(const InfoHash &infoHash) const
|
||||
|
||||
void SessionImpl::updateSeedingLimitTimer()
|
||||
{
|
||||
if ((globalMaxRatio() == Torrent::NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit()
|
||||
&& (globalMaxSeedingMinutes() == Torrent::NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit()
|
||||
&& (globalMaxInactiveSeedingMinutes() == Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT) && !hasPerTorrentInactiveSeedingTimeLimit())
|
||||
if ((globalMaxRatio() == NO_RATIO_LIMIT) && !hasPerTorrentRatioLimit()
|
||||
&& (globalMaxSeedingMinutes() == NO_SEEDING_TIME_LIMIT) && !hasPerTorrentSeedingTimeLimit()
|
||||
&& (globalMaxInactiveSeedingMinutes() == NO_SEEDING_TIME_LIMIT) && !hasPerTorrentInactiveSeedingTimeLimit())
|
||||
{
|
||||
if (m_seedingLimitTimer->isActive())
|
||||
m_seedingLimitTimer->stop();
|
||||
@@ -5279,9 +5286,9 @@ void SessionImpl::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const
|
||||
emit trackersRemoved(torrent, deletedTrackers);
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentTrackersChanged(TorrentImpl *const torrent)
|
||||
void SessionImpl::handleTorrentTrackersReset(TorrentImpl *const torrent, const QList<TrackerEntryStatus> &oldEntries, const QList<TrackerEntry> &newEntries)
|
||||
{
|
||||
emit trackersChanged(torrent);
|
||||
emit trackersReset(torrent, oldEntries, newEntries);
|
||||
}
|
||||
|
||||
void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QList<QUrl> &newUrlSeeds)
|
||||
@@ -5584,6 +5591,8 @@ void SessionImpl::loadCategories()
|
||||
const auto categoryOptions = CategoryOptions::fromJSON(it.value().toObject());
|
||||
m_categories[categoryName] = categoryOptions;
|
||||
}
|
||||
|
||||
m_categories = expandCategories(m_categories);
|
||||
}
|
||||
|
||||
bool SessionImpl::hasPerTorrentRatioLimit() const
|
||||
|
||||
@@ -149,15 +149,17 @@ namespace BitTorrent
|
||||
|
||||
QStringList categories() const override;
|
||||
CategoryOptions categoryOptions(const QString &categoryName) const override;
|
||||
bool setCategoryOptions(const QString &categoryName, const CategoryOptions &options) override;
|
||||
Path categorySavePath(const QString &categoryName) const override;
|
||||
Path categorySavePath(const QString &categoryName, const CategoryOptions &options) const override;
|
||||
Path categoryDownloadPath(const QString &categoryName) const override;
|
||||
Path categoryDownloadPath(const QString &categoryName, const CategoryOptions &options) const override;
|
||||
qreal categoryRatioLimit(const QString &categoryName) const override;
|
||||
int categorySeedingTimeLimit(const QString &categoryName) const override;
|
||||
int categoryInactiveSeedingTimeLimit(const QString &categoryName) const override;
|
||||
ShareLimitAction categoryShareLimitAction(const QString &categoryName) const override;
|
||||
bool addCategory(const QString &name, const CategoryOptions &options = {}) override;
|
||||
bool editCategory(const QString &name, const CategoryOptions &options) override;
|
||||
bool removeCategory(const QString &name) override;
|
||||
bool isSubcategoriesEnabled() const override;
|
||||
void setSubcategoriesEnabled(bool value) override;
|
||||
bool useCategoryPathsInManualMode() const override;
|
||||
void setUseCategoryPathsInManualMode(bool value) override;
|
||||
|
||||
@@ -359,8 +361,8 @@ namespace BitTorrent
|
||||
void setOutgoingPortsMax(int max) override;
|
||||
int UPnPLeaseDuration() const override;
|
||||
void setUPnPLeaseDuration(int duration) override;
|
||||
int peerToS() const override;
|
||||
void setPeerToS(int value) override;
|
||||
int peerDSCP() const override;
|
||||
void setPeerDSCP(int value) override;
|
||||
bool ignoreLimitsOnLAN() const override;
|
||||
void setIgnoreLimitsOnLAN(bool ignore) override;
|
||||
bool includeOverheadInLimits() const override;
|
||||
@@ -474,7 +476,7 @@ namespace BitTorrent
|
||||
void handleTorrentFinished(TorrentImpl *torrent);
|
||||
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QList<TrackerEntry> &newTrackers);
|
||||
void handleTorrentTrackersRemoved(TorrentImpl *torrent, const QStringList &deletedTrackers);
|
||||
void handleTorrentTrackersChanged(TorrentImpl *torrent);
|
||||
void handleTorrentTrackersReset(TorrentImpl *torrent, const QList<TrackerEntryStatus> &oldEntries, const QList<TrackerEntry> &newEntries);
|
||||
void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QList<QUrl> &newUrlSeeds);
|
||||
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QList<QUrl> &urlSeeds);
|
||||
void handleTorrentResumeDataReady(TorrentImpl *torrent, LoadTorrentParams data);
|
||||
@@ -690,7 +692,7 @@ namespace BitTorrent
|
||||
CachedSettingValue<int> m_outgoingPortsMin;
|
||||
CachedSettingValue<int> m_outgoingPortsMax;
|
||||
CachedSettingValue<int> m_UPnPLeaseDuration;
|
||||
CachedSettingValue<int> m_peerToS;
|
||||
CachedSettingValue<int> m_peerDSCP;
|
||||
CachedSettingValue<bool> m_ignoreLimitsOnLAN;
|
||||
CachedSettingValue<bool> m_includeOverheadInLimits;
|
||||
CachedSettingValue<QString> m_announceIP;
|
||||
@@ -754,7 +756,6 @@ namespace BitTorrent
|
||||
CachedSettingValue<Path> m_savePath;
|
||||
CachedSettingValue<Path> m_downloadPath;
|
||||
CachedSettingValue<bool> m_isDownloadPathEnabled;
|
||||
CachedSettingValue<bool> m_isSubcategoriesEnabled;
|
||||
CachedSettingValue<bool> m_useCategoryPathsInManualMode;
|
||||
CachedSettingValue<bool> m_isAutoTMMDisabledByDefault;
|
||||
CachedSettingValue<bool> m_isDisableAutoTMMWhenCategoryChanged;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -33,6 +33,12 @@
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
inline const qreal DEFAULT_RATIO_LIMIT = -2;
|
||||
inline const qreal NO_RATIO_LIMIT = -1;
|
||||
|
||||
inline const int DEFAULT_SEEDING_TIME_LIMIT = -2;
|
||||
inline const int NO_SEEDING_TIME_LIMIT = -1;
|
||||
|
||||
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
|
||||
// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files.
|
||||
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -44,15 +44,6 @@ namespace BitTorrent
|
||||
|
||||
// Torrent
|
||||
|
||||
const qreal Torrent::USE_GLOBAL_RATIO = -2;
|
||||
const qreal Torrent::NO_RATIO_LIMIT = -1;
|
||||
|
||||
const int Torrent::USE_GLOBAL_SEEDING_TIME = -2;
|
||||
const int Torrent::NO_SEEDING_TIME_LIMIT = -1;
|
||||
|
||||
const int Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME = -2;
|
||||
const int Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT = -1;
|
||||
|
||||
const qreal Torrent::MAX_RATIO = std::numeric_limits<qreal>::infinity();
|
||||
|
||||
TorrentID Torrent::id() const
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
#include "base/3rdparty/expected.hpp"
|
||||
#include "base/pathfwd.h"
|
||||
#include "base/tagset.h"
|
||||
#include "sharelimitaction.h"
|
||||
#include "sharelimits.h"
|
||||
#include "torrentannouncestatus.h"
|
||||
#include "torrentcontenthandler.h"
|
||||
|
||||
class QBitArray;
|
||||
@@ -124,15 +125,6 @@ namespace BitTorrent
|
||||
};
|
||||
Q_ENUM(StopCondition)
|
||||
|
||||
static const qreal USE_GLOBAL_RATIO;
|
||||
static const qreal NO_RATIO_LIMIT;
|
||||
|
||||
static const int USE_GLOBAL_SEEDING_TIME;
|
||||
static const int NO_SEEDING_TIME_LIMIT;
|
||||
|
||||
static const int USE_GLOBAL_INACTIVE_SEEDING_TIME;
|
||||
static const int NO_INACTIVE_SEEDING_TIME_LIMIT;
|
||||
|
||||
static const qreal MAX_RATIO;
|
||||
|
||||
using TorrentContentHandler::TorrentContentHandler;
|
||||
@@ -235,6 +227,10 @@ namespace BitTorrent
|
||||
virtual void setInactiveSeedingTimeLimit(int limit) = 0;
|
||||
virtual ShareLimitAction shareLimitAction() const = 0;
|
||||
virtual void setShareLimitAction(ShareLimitAction action) = 0;
|
||||
virtual qreal effectiveRatioLimit() const = 0;
|
||||
virtual int effectiveSeedingTimeLimit() const = 0;
|
||||
virtual int effectiveInactiveSeedingTimeLimit() const = 0;
|
||||
virtual ShareLimitAction effectiveShareLimitAction() const = 0;
|
||||
|
||||
virtual PathList filePaths() const = 0;
|
||||
virtual PathList actualFilePaths() const = 0;
|
||||
@@ -278,9 +274,6 @@ namespace BitTorrent
|
||||
virtual bool isLSDDisabled() const = 0;
|
||||
virtual QBitArray pieces() const = 0;
|
||||
virtual qreal distributedCopies() const = 0;
|
||||
virtual qreal maxRatio() const = 0;
|
||||
virtual int maxSeedingTime() const = 0;
|
||||
virtual int maxInactiveSeedingTime() const = 0;
|
||||
virtual qreal realRatio() const = 0;
|
||||
virtual qreal popularity() const = 0;
|
||||
virtual int uploadPayloadRate() const = 0;
|
||||
@@ -290,6 +283,7 @@ namespace BitTorrent
|
||||
virtual int connectionsCount() const = 0;
|
||||
virtual int connectionsLimit() const = 0;
|
||||
virtual qlonglong nextAnnounce() const = 0;
|
||||
virtual TorrentAnnounceStatus announceStatus() const = 0;
|
||||
|
||||
virtual void setName(const QString &name) = 0;
|
||||
virtual void setSequentialDownload(bool enable) = 0;
|
||||
|
||||
47
src/base/bittorrent/torrentannouncestatus.h
Normal file
47
src/base/bittorrent/torrentannouncestatus.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QFlags>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class TorrentAnnounceStatusFlag
|
||||
{
|
||||
HasNoProblem = 0,
|
||||
|
||||
HasWarning = 1,
|
||||
HasTrackerError = 2,
|
||||
HasOtherError = 4
|
||||
};
|
||||
|
||||
Q_DECLARE_FLAGS(TorrentAnnounceStatus, TorrentAnnounceStatusFlag);
|
||||
}
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(BitTorrent::TorrentAnnounceStatus);
|
||||
@@ -700,6 +700,7 @@ void TorrentImpl::removeTrackers(const QStringList &trackers)
|
||||
if (!removedTrackers.isEmpty())
|
||||
{
|
||||
m_nativeHandle.replace_trackers(nativeTrackers);
|
||||
m_announceStatus.reset();
|
||||
|
||||
deferredRequestResumeData();
|
||||
m_session->handleTorrentTrackersRemoved(this, removedTrackers);
|
||||
@@ -718,15 +719,16 @@ void TorrentImpl::replaceTrackers(QList<TrackerEntry> trackers)
|
||||
|
||||
std::vector<lt::announce_entry> nativeTrackers;
|
||||
nativeTrackers.reserve(trackers.size());
|
||||
m_trackerEntryStatuses.clear();
|
||||
const auto oldEntries = std::exchange(m_trackerEntryStatuses, {});
|
||||
|
||||
for (const TrackerEntry &tracker : trackers)
|
||||
for (const TrackerEntry &tracker : asConst(trackers))
|
||||
{
|
||||
nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
|
||||
m_trackerEntryStatuses.append({tracker.url, tracker.tier});
|
||||
}
|
||||
|
||||
m_nativeHandle.replace_trackers(nativeTrackers);
|
||||
m_announceStatus.reset();
|
||||
|
||||
// Clear the peer list if it's a private torrent since
|
||||
// we do not want to keep connecting with peers from old tracker.
|
||||
@@ -734,7 +736,7 @@ void TorrentImpl::replaceTrackers(QList<TrackerEntry> trackers)
|
||||
clearPeers();
|
||||
|
||||
deferredRequestResumeData();
|
||||
m_session->handleTorrentTrackersChanged(this);
|
||||
m_session->handleTorrentTrackersReset(this, oldEntries, trackers);
|
||||
}
|
||||
|
||||
QList<QUrl> TorrentImpl::urlSeeds() const
|
||||
@@ -934,7 +936,7 @@ bool TorrentImpl::belongsToCategory(const QString &category) const
|
||||
if (m_category == category)
|
||||
return true;
|
||||
|
||||
return (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + u'/'));
|
||||
return m_category.startsWith(category + u'/');
|
||||
}
|
||||
|
||||
TagSet TorrentImpl::tags() const
|
||||
@@ -1186,25 +1188,7 @@ bool TorrentImpl::isCompleted() const
|
||||
|
||||
bool TorrentImpl::isActive() const
|
||||
{
|
||||
switch (m_state)
|
||||
{
|
||||
case TorrentState::StalledDownloading:
|
||||
return (uploadPayloadRate() > 0);
|
||||
|
||||
case TorrentState::DownloadingMetadata:
|
||||
case TorrentState::ForcedDownloadingMetadata:
|
||||
case TorrentState::Downloading:
|
||||
case TorrentState::ForcedDownloading:
|
||||
case TorrentState::Uploading:
|
||||
case TorrentState::ForcedUploading:
|
||||
case TorrentState::Moving:
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
return false;
|
||||
return ((uploadPayloadRate() > 0) || (downloadPayloadRate() > 0));
|
||||
}
|
||||
|
||||
bool TorrentImpl::isInactive() const
|
||||
@@ -1356,16 +1340,16 @@ qlonglong TorrentImpl::eta() const
|
||||
|
||||
if (isFinished())
|
||||
{
|
||||
const qreal maxRatioValue = maxRatio();
|
||||
const int maxSeedingTimeValue = maxSeedingTime();
|
||||
const int maxInactiveSeedingTimeValue = maxInactiveSeedingTime();
|
||||
if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0)) return MAX_ETA;
|
||||
const qreal maxRatioValue = effectiveRatioLimit();
|
||||
const int maxSeedingTimeValue = effectiveSeedingTimeLimit();
|
||||
const int maxInactiveSeedingTimeValue = effectiveInactiveSeedingTimeLimit();
|
||||
if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0))
|
||||
return MAX_ETA;
|
||||
|
||||
qlonglong ratioEta = MAX_ETA;
|
||||
|
||||
if ((speedAverage.upload > 0) && (maxRatioValue >= 0))
|
||||
{
|
||||
|
||||
qlonglong realDL = totalDownload();
|
||||
if (realDL <= 0)
|
||||
realDL = wantedSize();
|
||||
@@ -1497,30 +1481,38 @@ qreal TorrentImpl::distributedCopies() const
|
||||
return m_nativeStatus.distributed_copies;
|
||||
}
|
||||
|
||||
qreal TorrentImpl::maxRatio() const
|
||||
qreal TorrentImpl::effectiveRatioLimit() const
|
||||
{
|
||||
if (m_ratioLimit == USE_GLOBAL_RATIO)
|
||||
return m_session->globalMaxRatio();
|
||||
if (m_ratioLimit == DEFAULT_RATIO_LIMIT)
|
||||
return m_session->categoryRatioLimit(category());
|
||||
|
||||
return m_ratioLimit;
|
||||
}
|
||||
|
||||
int TorrentImpl::maxSeedingTime() const
|
||||
int TorrentImpl::effectiveSeedingTimeLimit() const
|
||||
{
|
||||
if (m_seedingTimeLimit == USE_GLOBAL_SEEDING_TIME)
|
||||
return m_session->globalMaxSeedingMinutes();
|
||||
if (m_seedingTimeLimit == DEFAULT_SEEDING_TIME_LIMIT)
|
||||
return m_session->categorySeedingTimeLimit(category());
|
||||
|
||||
return m_seedingTimeLimit;
|
||||
}
|
||||
|
||||
int TorrentImpl::maxInactiveSeedingTime() const
|
||||
int TorrentImpl::effectiveInactiveSeedingTimeLimit() const
|
||||
{
|
||||
if (m_inactiveSeedingTimeLimit == USE_GLOBAL_INACTIVE_SEEDING_TIME)
|
||||
return m_session->globalMaxInactiveSeedingMinutes();
|
||||
if (m_inactiveSeedingTimeLimit == DEFAULT_SEEDING_TIME_LIMIT)
|
||||
return m_session->categoryInactiveSeedingTimeLimit(category());
|
||||
|
||||
return m_inactiveSeedingTimeLimit;
|
||||
}
|
||||
|
||||
ShareLimitAction TorrentImpl::effectiveShareLimitAction() const
|
||||
{
|
||||
if (m_shareLimitAction == ShareLimitAction::Default)
|
||||
return m_session->categoryShareLimitAction(category());
|
||||
|
||||
return m_shareLimitAction;
|
||||
}
|
||||
|
||||
qreal TorrentImpl::realRatio() const
|
||||
{
|
||||
const int64_t upload = m_nativeStatus.all_time_upload;
|
||||
@@ -1575,6 +1567,46 @@ qlonglong TorrentImpl::nextAnnounce() const
|
||||
return lt::total_seconds(m_nativeStatus.next_announce);
|
||||
}
|
||||
|
||||
TorrentAnnounceStatus TorrentImpl::announceStatus() const
|
||||
{
|
||||
if (m_announceStatus)
|
||||
return *m_announceStatus;
|
||||
|
||||
TorrentAnnounceStatus announceStatus = TorrentAnnounceStatusFlag::HasNoProblem;
|
||||
for (const TrackerEntryStatus &trackerEntryStatus : asConst(m_trackerEntryStatuses))
|
||||
{
|
||||
switch (trackerEntryStatus.state)
|
||||
{
|
||||
case BitTorrent::TrackerEndpointState::Working:
|
||||
if (!announceStatus.testFlag(TorrentAnnounceStatusFlag::HasWarning))
|
||||
{
|
||||
const bool hasWarningMessage = std::ranges::any_of(trackerEntryStatus.endpoints
|
||||
, [](const TrackerEndpointStatus &endpointEntry)
|
||||
{
|
||||
return !endpointEntry.message.isEmpty() && (endpointEntry.state == BitTorrent::TrackerEndpointState::Working);
|
||||
});
|
||||
announceStatus.setFlag(TorrentAnnounceStatusFlag::HasWarning, hasWarningMessage);
|
||||
}
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::NotWorking:
|
||||
case BitTorrent::TrackerEndpointState::Unreachable:
|
||||
announceStatus.setFlag(TorrentAnnounceStatusFlag::HasOtherError);
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::TrackerError:
|
||||
announceStatus.setFlag(TorrentAnnounceStatusFlag::HasTrackerError);
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::NotContacted:
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
m_announceStatus = announceStatus;
|
||||
return *m_announceStatus;
|
||||
}
|
||||
|
||||
qreal TorrentImpl::popularity() const
|
||||
{
|
||||
// in order to produce floating-point numbers using `std::chrono::duration_cast`,
|
||||
@@ -1761,6 +1793,7 @@ TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entr
|
||||
#endif
|
||||
|
||||
::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo);
|
||||
m_announceStatus.reset();
|
||||
|
||||
return *it;
|
||||
}
|
||||
@@ -1776,6 +1809,8 @@ void TorrentImpl::resetTrackerEntryStatuses()
|
||||
status.url = tempUrl;
|
||||
status.tier = tempTier;
|
||||
}
|
||||
|
||||
m_announceStatus = TorrentAnnounceStatusFlag::HasNoProblem;
|
||||
}
|
||||
|
||||
std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
|
||||
@@ -2598,7 +2633,7 @@ void TorrentImpl::updateProgress()
|
||||
|
||||
void TorrentImpl::setRatioLimit(qreal limit)
|
||||
{
|
||||
if (limit < USE_GLOBAL_RATIO)
|
||||
if (limit < DEFAULT_RATIO_LIMIT)
|
||||
limit = NO_RATIO_LIMIT;
|
||||
|
||||
if (m_ratioLimit != limit)
|
||||
@@ -2611,7 +2646,7 @@ void TorrentImpl::setRatioLimit(qreal limit)
|
||||
|
||||
void TorrentImpl::setSeedingTimeLimit(int limit)
|
||||
{
|
||||
if (limit < USE_GLOBAL_SEEDING_TIME)
|
||||
if (limit < DEFAULT_SEEDING_TIME_LIMIT)
|
||||
limit = NO_SEEDING_TIME_LIMIT;
|
||||
|
||||
if (m_seedingTimeLimit != limit)
|
||||
@@ -2624,8 +2659,8 @@ void TorrentImpl::setSeedingTimeLimit(int limit)
|
||||
|
||||
void TorrentImpl::setInactiveSeedingTimeLimit(int limit)
|
||||
{
|
||||
if (limit < USE_GLOBAL_INACTIVE_SEEDING_TIME)
|
||||
limit = NO_INACTIVE_SEEDING_TIME_LIMIT;
|
||||
if (limit < DEFAULT_SEEDING_TIME_LIMIT)
|
||||
limit = NO_SEEDING_TIME_LIMIT;
|
||||
|
||||
if (m_inactiveSeedingTimeLimit != limit)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -156,6 +156,10 @@ namespace BitTorrent
|
||||
void setInactiveSeedingTimeLimit(int limit) override;
|
||||
ShareLimitAction shareLimitAction() const override;
|
||||
void setShareLimitAction(ShareLimitAction action) override;
|
||||
qreal effectiveRatioLimit() const override;
|
||||
int effectiveSeedingTimeLimit() const override;
|
||||
int effectiveInactiveSeedingTimeLimit() const override;
|
||||
ShareLimitAction effectiveShareLimitAction() const override;
|
||||
|
||||
Path filePath(int index) const override;
|
||||
Path actualFilePath(int index) const override;
|
||||
@@ -205,9 +209,6 @@ namespace BitTorrent
|
||||
bool isLSDDisabled() const override;
|
||||
QBitArray pieces() const override;
|
||||
qreal distributedCopies() const override;
|
||||
qreal maxRatio() const override;
|
||||
int maxSeedingTime() const override;
|
||||
int maxInactiveSeedingTime() const override;
|
||||
qreal realRatio() const override;
|
||||
qreal popularity() const override;
|
||||
int uploadPayloadRate() const override;
|
||||
@@ -217,6 +218,7 @@ namespace BitTorrent
|
||||
int connectionsCount() const override;
|
||||
int connectionsLimit() const override;
|
||||
qlonglong nextAnnounce() const override;
|
||||
TorrentAnnounceStatus announceStatus() const override;
|
||||
|
||||
void setName(const QString &name) override;
|
||||
void setSequentialDownload(bool enable) override;
|
||||
@@ -349,6 +351,7 @@ namespace BitTorrent
|
||||
MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
|
||||
|
||||
QList<TrackerEntryStatus> m_trackerEntryStatuses;
|
||||
mutable std::optional<TorrentAnnounceStatus> m_announceStatus;
|
||||
QList<QUrl> m_urlSeeds;
|
||||
FileErrorInfo m_lastFileError;
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace
|
||||
bool hasDriveLetter(const QStringView path)
|
||||
{
|
||||
const QRegularExpression driveLetterRegex {u"^[A-Za-z]:/"_s};
|
||||
return driveLetterRegex.match(path).hasMatch();
|
||||
return driveLetterRegex.matchView(path).hasMatch();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -104,7 +104,7 @@ bool Path::isValid() const
|
||||
|
||||
// \\37 is using base-8 number system
|
||||
const QRegularExpression regex {u"[\\0-\\37:?\"*<>|]"_s};
|
||||
return !regex.match(view).hasMatch();
|
||||
return !regex.matchView(view).hasMatch();
|
||||
#elif defined(Q_OS_MACOS)
|
||||
const QRegularExpression regex {u"[\\0:]"_s};
|
||||
#else
|
||||
|
||||
@@ -494,10 +494,17 @@ void Preferences::setWinStartup(const bool b)
|
||||
settings.remove(profileID);
|
||||
}
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
QString Preferences::getStyle() const
|
||||
{
|
||||
return value<QString>(u"Appearance/Style"_s);
|
||||
#ifdef Q_OS_WIN
|
||||
const QString defaultStyleName = u"Fusion"_s;
|
||||
#else
|
||||
const QString defaultStyleName = u"system"_s;
|
||||
#endif
|
||||
const auto styleName = value<QString>(u"Appearance/Style"_s);
|
||||
return styleName.isEmpty() ? defaultStyleName : styleName;
|
||||
}
|
||||
|
||||
void Preferences::setStyle(const QString &styleName)
|
||||
@@ -507,7 +514,6 @@ void Preferences::setStyle(const QString &styleName)
|
||||
|
||||
setValue(u"Appearance/Style"_s, styleName);
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
// Downloads
|
||||
Path Preferences::getScanDirsLastPath() const
|
||||
@@ -1291,6 +1297,19 @@ void Preferences::setShutdownWhenDownloadsComplete(const bool shutdown)
|
||||
setValue(u"Preferences/Downloads/AutoShutDownOnCompletion"_s, shutdown);
|
||||
}
|
||||
|
||||
bool Preferences::rebootWhenDownloadsComplete() const
|
||||
{
|
||||
return value(u"Preferences/Downloads/AutoRebootOnCompletion"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setRebootWhenDownloadsComplete(const bool reboot)
|
||||
{
|
||||
if (reboot == rebootWhenDownloadsComplete())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/Downloads/AutoRebootOnCompletion"_s, reboot);
|
||||
}
|
||||
|
||||
bool Preferences::suspendWhenDownloadsComplete() const
|
||||
{
|
||||
return value(u"Preferences/Downloads/AutoSuspendOnCompletion"_s, false);
|
||||
@@ -1885,6 +1904,32 @@ void Preferences::setTrackerFilterState(const bool checked)
|
||||
setValue(u"TransferListFilters/trackerFilterState"_s, checked);
|
||||
}
|
||||
|
||||
bool Preferences::getTrackerStatusFilterState() const
|
||||
{
|
||||
return value(u"TransferListFilters/TrackerStatusFilterState"_s, true);
|
||||
}
|
||||
|
||||
void Preferences::setTrackerStatusFilterState(const bool checked)
|
||||
{
|
||||
if (checked == getTrackerStatusFilterState())
|
||||
return;
|
||||
|
||||
setValue(u"TransferListFilters/TrackerStatusFilterState"_s, checked);
|
||||
}
|
||||
|
||||
bool Preferences::useSeparateTrackerStatusFilter() const
|
||||
{
|
||||
return value(u"TransferListFilters/SeparateTrackerStatusFilter"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setUseSeparateTrackerStatusFilter(const bool value)
|
||||
{
|
||||
if (value == useSeparateTrackerStatusFilter())
|
||||
return;
|
||||
|
||||
setValue(u"TransferListFilters/SeparateTrackerStatusFilter"_s, value);
|
||||
}
|
||||
|
||||
int Preferences::getTransSelFilter() const
|
||||
{
|
||||
return value<int>(u"TransferListFilters/selectedFilterIndex"_s, 0);
|
||||
|
||||
@@ -137,11 +137,11 @@ public:
|
||||
void setPreventFromSuspendWhenDownloading(bool b);
|
||||
bool preventFromSuspendWhenSeeding() const;
|
||||
void setPreventFromSuspendWhenSeeding(bool b);
|
||||
QString getStyle() const;
|
||||
void setStyle(const QString &styleName);
|
||||
#ifdef Q_OS_WIN
|
||||
bool WinStartup() const;
|
||||
void setWinStartup(bool b);
|
||||
QString getStyle() const;
|
||||
void setStyle(const QString &styleName);
|
||||
#endif
|
||||
|
||||
// Downloads
|
||||
@@ -287,6 +287,8 @@ public:
|
||||
|
||||
bool shutdownWhenDownloadsComplete() const;
|
||||
void setShutdownWhenDownloadsComplete(bool shutdown);
|
||||
bool rebootWhenDownloadsComplete() const;
|
||||
void setRebootWhenDownloadsComplete(bool reboot);
|
||||
bool suspendWhenDownloadsComplete() const;
|
||||
void setSuspendWhenDownloadsComplete(bool suspend);
|
||||
bool hibernateWhenDownloadsComplete() const;
|
||||
@@ -400,6 +402,9 @@ public:
|
||||
bool getCategoryFilterState() const;
|
||||
bool getTagFilterState() const;
|
||||
bool getTrackerFilterState() const;
|
||||
bool getTrackerStatusFilterState() const;
|
||||
bool useSeparateTrackerStatusFilter() const;
|
||||
void setUseSeparateTrackerStatusFilter(bool value);
|
||||
int getTransSelFilter() const;
|
||||
void setTransSelFilter(int index);
|
||||
bool getHideZeroStatusFilters() const;
|
||||
@@ -449,6 +454,7 @@ public slots:
|
||||
void setCategoryFilterState(bool checked);
|
||||
void setTagFilterState(bool checked);
|
||||
void setTrackerFilterState(bool checked);
|
||||
void setTrackerStatusFilterState(bool checked);
|
||||
|
||||
void apply();
|
||||
|
||||
|
||||
@@ -405,12 +405,16 @@ void Session::loadLegacy()
|
||||
const QString feedUrl = Item::relativeName(legacyPath);
|
||||
|
||||
for (const QString &folderPath : asConst(Item::expandPath(parentFolderPath)))
|
||||
addFolder(folderPath);
|
||||
{
|
||||
if (const auto result = addFolder(folderPath); !result)
|
||||
LogMsg(tr("Failed to add RSS folder item. Reason: \"%1\"").arg(result.error()), Log::WARNING);
|
||||
}
|
||||
|
||||
const QString feedPath = feedAliases[i].isEmpty()
|
||||
? legacyPath
|
||||
: Item::joinPath(parentFolderPath, feedAliases[i]);
|
||||
addFeed(feedUrl, feedPath);
|
||||
if (const auto result = addFeed(feedUrl, feedPath); !result)
|
||||
LogMsg(tr("Failed to add RSS feed item. Reason: \"%1\"").arg(result.error()), Log::WARNING);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QS
|
||||
const QStringList params
|
||||
{
|
||||
Utils::ForeignApps::PYTHON_ISOLATE_MODE_FLAG,
|
||||
Utils::ForeignApps::PYTHON_UTF8_MODE_FLAG,
|
||||
(SearchPluginManager::engineLocation() / Path(u"nova2dl.py"_s)).toString(),
|
||||
pluginName,
|
||||
url
|
||||
|
||||
@@ -102,6 +102,7 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
|
||||
const QStringList params
|
||||
{
|
||||
Utils::ForeignApps::PYTHON_ISOLATE_MODE_FLAG,
|
||||
Utils::ForeignApps::PYTHON_UTF8_MODE_FLAG,
|
||||
(SearchPluginManager::engineLocation() / Path(u"nova2.py"_s)).toString(),
|
||||
m_usedPlugins.join(u','),
|
||||
m_category
|
||||
|
||||
@@ -554,6 +554,7 @@ void SearchPluginManager::update()
|
||||
const QStringList params
|
||||
{
|
||||
Utils::ForeignApps::PYTHON_ISOLATE_MODE_FLAG,
|
||||
Utils::ForeignApps::PYTHON_UTF8_MODE_FLAG,
|
||||
(engineLocation() / Path(u"/nova2.py"_s)).toString(),
|
||||
u"--capabilities"_s
|
||||
};
|
||||
|
||||
@@ -434,9 +434,9 @@ void TorrentFilesWatcher::Worker::processFolder(const Path &path, const Path &wa
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_failedTorrents.value(path).contains(filePath))
|
||||
if (!m_failedTorrents.value(watchedFolderPath).contains(filePath))
|
||||
{
|
||||
m_failedTorrents[path][filePath] = 0;
|
||||
m_failedTorrents[watchedFolderPath][filePath] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -471,7 +471,7 @@ void TorrentFilesWatcher::Worker::processFailedTorrents()
|
||||
BitTorrent::AddTorrentParams addTorrentParams = options.addTorrentParams;
|
||||
if (torrentPath != watchedFolderPath)
|
||||
{
|
||||
const Path subdirPath = watchedFolderPath.relativePathOf(torrentPath);
|
||||
const Path subdirPath = watchedFolderPath.relativePathOf(torrentPath).parentPath();
|
||||
const bool useAutoTMM = addTorrentParams.useAutoTMM.value_or(!BitTorrent::Session::instance()->isAutoTMMDisabledByDefault());
|
||||
if (useAutoTMM)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -28,94 +28,57 @@
|
||||
|
||||
#include "torrentfilter.h"
|
||||
|
||||
#include "bittorrent/infohash.h"
|
||||
#include "bittorrent/torrent.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/bittorrent/infohash.h"
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "base/bittorrent/trackerentrystatus.h"
|
||||
#include "base/global.h"
|
||||
|
||||
using namespace BitTorrent;
|
||||
|
||||
const std::optional<QString> TorrentFilter::AnyCategory;
|
||||
const std::optional<TorrentIDSet> TorrentFilter::AnyID;
|
||||
const std::optional<QString> TorrentFilter::AnyCategory;
|
||||
const std::optional<Tag> TorrentFilter::AnyTag;
|
||||
const std::optional<QString> TorrentFilter::AnyTrackerHost;
|
||||
const std::optional<TorrentAnnounceStatus> TorrentFilter::AnyAnnounceStatus;
|
||||
|
||||
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
|
||||
const TorrentFilter TorrentFilter::SeedingTorrent(TorrentFilter::Seeding);
|
||||
const TorrentFilter TorrentFilter::CompletedTorrent(TorrentFilter::Completed);
|
||||
const TorrentFilter TorrentFilter::StoppedTorrent(TorrentFilter::Stopped);
|
||||
const TorrentFilter TorrentFilter::RunningTorrent(TorrentFilter::Running);
|
||||
const TorrentFilter TorrentFilter::ActiveTorrent(TorrentFilter::Active);
|
||||
const TorrentFilter TorrentFilter::InactiveTorrent(TorrentFilter::Inactive);
|
||||
const TorrentFilter TorrentFilter::StalledTorrent(TorrentFilter::Stalled);
|
||||
const TorrentFilter TorrentFilter::StalledUploadingTorrent(TorrentFilter::StalledUploading);
|
||||
const TorrentFilter TorrentFilter::StalledDownloadingTorrent(TorrentFilter::StalledDownloading);
|
||||
const TorrentFilter TorrentFilter::CheckingTorrent(TorrentFilter::Checking);
|
||||
const TorrentFilter TorrentFilter::MovingTorrent(TorrentFilter::Moving);
|
||||
const TorrentFilter TorrentFilter::ErroredTorrent(TorrentFilter::Errored);
|
||||
QString getTrackerHost(const QString &url)
|
||||
{
|
||||
// We want the hostname.
|
||||
if (const QString host = QUrl(url).host(); !host.isEmpty())
|
||||
return host;
|
||||
|
||||
using BitTorrent::Torrent;
|
||||
// If failed to parse the domain, original input should be returned
|
||||
return url;
|
||||
}
|
||||
|
||||
TorrentFilter::TorrentFilter(const Type type, const std::optional<TorrentIDSet> &idSet
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag, const std::optional<bool> isPrivate)
|
||||
: m_type {type}
|
||||
TorrentFilter::TorrentFilter(const Status status, const std::optional<TorrentIDSet> &idSet, const std::optional<QString> &category
|
||||
, const std::optional<Tag> &tag, const std::optional<bool> &isPrivate, const std::optional<QString> &trackerHost
|
||||
, const std::optional<TorrentAnnounceStatus> &announceStatus)
|
||||
: m_status {status}
|
||||
, m_category {category}
|
||||
, m_tag {tag}
|
||||
, m_idSet {idSet}
|
||||
, m_private {isPrivate}
|
||||
, m_trackerHost {trackerHost}
|
||||
, m_announceStatus {announceStatus}
|
||||
{
|
||||
}
|
||||
|
||||
TorrentFilter::TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag, const std::optional<bool> isPrivate)
|
||||
: m_category {category}
|
||||
, m_tag {tag}
|
||||
, m_idSet {idSet}
|
||||
, m_private {isPrivate}
|
||||
bool TorrentFilter::setStatus(const Status status)
|
||||
{
|
||||
setTypeByName(filter);
|
||||
}
|
||||
|
||||
bool TorrentFilter::setType(Type type)
|
||||
{
|
||||
if (m_type != type)
|
||||
if (m_status != status)
|
||||
{
|
||||
m_type = type;
|
||||
m_status = status;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::setTypeByName(const QString &filter)
|
||||
{
|
||||
Type type = All;
|
||||
|
||||
if (filter == u"downloading")
|
||||
type = Downloading;
|
||||
else if (filter == u"seeding")
|
||||
type = Seeding;
|
||||
else if (filter == u"completed")
|
||||
type = Completed;
|
||||
else if (filter == u"stopped")
|
||||
type = Stopped;
|
||||
else if (filter == u"running")
|
||||
type = Running;
|
||||
else if (filter == u"active")
|
||||
type = Active;
|
||||
else if (filter == u"inactive")
|
||||
type = Inactive;
|
||||
else if (filter == u"stalled")
|
||||
type = Stalled;
|
||||
else if (filter == u"stalled_uploading")
|
||||
type = StalledUploading;
|
||||
else if (filter == u"stalled_downloading")
|
||||
type = StalledDownloading;
|
||||
else if (filter == u"checking")
|
||||
type = Checking;
|
||||
else if (filter == u"moving")
|
||||
type = Moving;
|
||||
else if (filter == u"errored")
|
||||
type = Errored;
|
||||
|
||||
return setType(type);
|
||||
}
|
||||
|
||||
bool TorrentFilter::setTorrentIDSet(const std::optional<TorrentIDSet> &idSet)
|
||||
{
|
||||
if (m_idSet != idSet)
|
||||
@@ -160,18 +123,43 @@ bool TorrentFilter::setPrivate(const std::optional<bool> isPrivate)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::match(const Torrent *const torrent) const
|
||||
bool TorrentFilter::setTrackerHost(const std::optional<QString> &trackerHost)
|
||||
{
|
||||
if (!torrent) return false;
|
||||
if (m_trackerHost != trackerHost)
|
||||
{
|
||||
m_trackerHost = trackerHost;
|
||||
return true;
|
||||
}
|
||||
|
||||
return (matchState(torrent) && matchHash(torrent) && matchCategory(torrent) && matchTag(torrent) && matchPrivate(torrent));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
||||
bool TorrentFilter::setAnnounceStatus(const std::optional<TorrentAnnounceStatus> &announceStatus)
|
||||
{
|
||||
const BitTorrent::TorrentState state = torrent->state();
|
||||
if (m_announceStatus != announceStatus)
|
||||
{
|
||||
m_announceStatus = announceStatus;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (m_type)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::match(const Torrent *const torrent) const
|
||||
{
|
||||
Q_ASSERT(torrent);
|
||||
if (!torrent) [[unlikely]]
|
||||
return false;
|
||||
|
||||
return (matchStatus(torrent) && matchHash(torrent) && matchCategory(torrent)
|
||||
&& matchTag(torrent) && matchPrivate(torrent) && matchTracker(torrent));
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchStatus(const Torrent *const torrent) const
|
||||
{
|
||||
const TorrentState state = torrent->state();
|
||||
|
||||
switch (m_status)
|
||||
{
|
||||
case All:
|
||||
return true;
|
||||
@@ -190,16 +178,16 @@ bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
||||
case Inactive:
|
||||
return torrent->isInactive();
|
||||
case Stalled:
|
||||
return (state == BitTorrent::TorrentState::StalledUploading)
|
||||
|| (state == BitTorrent::TorrentState::StalledDownloading);
|
||||
return (state == TorrentState::StalledUploading)
|
||||
|| (state == TorrentState::StalledDownloading);
|
||||
case StalledUploading:
|
||||
return state == BitTorrent::TorrentState::StalledUploading;
|
||||
return state == TorrentState::StalledUploading;
|
||||
case StalledDownloading:
|
||||
return state == BitTorrent::TorrentState::StalledDownloading;
|
||||
return state == TorrentState::StalledDownloading;
|
||||
case Checking:
|
||||
return (state == BitTorrent::TorrentState::CheckingUploading)
|
||||
|| (state == BitTorrent::TorrentState::CheckingDownloading)
|
||||
|| (state == BitTorrent::TorrentState::CheckingResumeData);
|
||||
return (state == TorrentState::CheckingUploading)
|
||||
|| (state == TorrentState::CheckingDownloading)
|
||||
|| (state == TorrentState::CheckingResumeData);
|
||||
case Moving:
|
||||
return torrent->isMoving();
|
||||
case Errored:
|
||||
@@ -212,7 +200,7 @@ bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchHash(const BitTorrent::Torrent *const torrent) const
|
||||
bool TorrentFilter::matchHash(const Torrent *const torrent) const
|
||||
{
|
||||
if (!m_idSet)
|
||||
return true;
|
||||
@@ -220,7 +208,7 @@ bool TorrentFilter::matchHash(const BitTorrent::Torrent *const torrent) const
|
||||
return m_idSet->contains(torrent->id());
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchCategory(const BitTorrent::Torrent *const torrent) const
|
||||
bool TorrentFilter::matchCategory(const Torrent *const torrent) const
|
||||
{
|
||||
if (!m_category)
|
||||
return true;
|
||||
@@ -228,7 +216,7 @@ bool TorrentFilter::matchCategory(const BitTorrent::Torrent *const torrent) cons
|
||||
return (torrent->belongsToCategory(*m_category));
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchTag(const BitTorrent::Torrent *const torrent) const
|
||||
bool TorrentFilter::matchTag(const Torrent *const torrent) const
|
||||
{
|
||||
if (!m_tag)
|
||||
return true;
|
||||
@@ -240,10 +228,65 @@ bool TorrentFilter::matchTag(const BitTorrent::Torrent *const torrent) const
|
||||
return torrent->hasTag(*m_tag);
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchPrivate(const BitTorrent::Torrent *const torrent) const
|
||||
bool TorrentFilter::matchPrivate(const Torrent *const torrent) const
|
||||
{
|
||||
if (!m_private)
|
||||
return true;
|
||||
|
||||
return m_private == torrent->isPrivate();
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchTracker(const Torrent *torrent) const
|
||||
{
|
||||
if (!m_trackerHost)
|
||||
{
|
||||
if (!m_announceStatus)
|
||||
return true;
|
||||
|
||||
const TorrentAnnounceStatus announceStatus = torrent->announceStatus();
|
||||
const TorrentAnnounceStatus &testAnnounceStatus = *m_announceStatus;
|
||||
if (!testAnnounceStatus)
|
||||
return !announceStatus;
|
||||
|
||||
return announceStatus.testAnyFlags(testAnnounceStatus);
|
||||
}
|
||||
|
||||
// Trackerless torrent
|
||||
if (m_trackerHost->isEmpty())
|
||||
return torrent->trackers().isEmpty() && !m_announceStatus;
|
||||
|
||||
return std::ranges::any_of(asConst(torrent->trackers())
|
||||
, [trackerHost = m_trackerHost, announceStatus = m_announceStatus](const TrackerEntryStatus &trackerEntryStatus)
|
||||
{
|
||||
if (getTrackerHost(trackerEntryStatus.url) != trackerHost)
|
||||
return false;
|
||||
|
||||
if (!announceStatus)
|
||||
return true;
|
||||
|
||||
switch (trackerEntryStatus.state)
|
||||
{
|
||||
case TrackerEndpointState::Working:
|
||||
{
|
||||
const bool hasWarningMessage = std::ranges::any_of(trackerEntryStatus.endpoints
|
||||
, [](const TrackerEndpointStatus &endpointEntry)
|
||||
{
|
||||
return !endpointEntry.message.isEmpty() && (endpointEntry.state == TrackerEndpointState::Working);
|
||||
});
|
||||
return hasWarningMessage ? announceStatus->testFlag(TorrentAnnounceStatusFlag::HasWarning) : !*announceStatus;
|
||||
}
|
||||
|
||||
case TrackerEndpointState::NotWorking:
|
||||
case TrackerEndpointState::Unreachable:
|
||||
return announceStatus->testFlag(TorrentAnnounceStatusFlag::HasOtherError);
|
||||
|
||||
case TrackerEndpointState::TrackerError:
|
||||
return announceStatus->testFlag(TorrentAnnounceStatusFlag::HasTrackerError);
|
||||
|
||||
case TrackerEndpointState::NotContacted:
|
||||
return false;
|
||||
};
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <QString>
|
||||
|
||||
#include "base/bittorrent/infohash.h"
|
||||
#include "base/bittorrent/torrentannouncestatus.h"
|
||||
#include "base/tag.h"
|
||||
|
||||
namespace BitTorrent
|
||||
@@ -46,7 +47,7 @@ using TorrentIDSet = QSet<BitTorrent::TorrentID>;
|
||||
class TorrentFilter
|
||||
{
|
||||
public:
|
||||
enum Type
|
||||
enum Status
|
||||
{
|
||||
All,
|
||||
Downloading,
|
||||
@@ -67,57 +68,47 @@ public:
|
||||
};
|
||||
|
||||
// These mean any permutation, including no category / tag.
|
||||
static const std::optional<QString> AnyCategory;
|
||||
static const std::optional<TorrentIDSet> AnyID;
|
||||
static const std::optional<QString> AnyCategory;
|
||||
static const std::optional<Tag> AnyTag;
|
||||
|
||||
static const TorrentFilter DownloadingTorrent;
|
||||
static const TorrentFilter SeedingTorrent;
|
||||
static const TorrentFilter CompletedTorrent;
|
||||
static const TorrentFilter StoppedTorrent;
|
||||
static const TorrentFilter RunningTorrent;
|
||||
static const TorrentFilter ActiveTorrent;
|
||||
static const TorrentFilter InactiveTorrent;
|
||||
static const TorrentFilter StalledTorrent;
|
||||
static const TorrentFilter StalledUploadingTorrent;
|
||||
static const TorrentFilter StalledDownloadingTorrent;
|
||||
static const TorrentFilter CheckingTorrent;
|
||||
static const TorrentFilter MovingTorrent;
|
||||
static const TorrentFilter ErroredTorrent;
|
||||
static const std::optional<QString> AnyTrackerHost;
|
||||
static const std::optional<BitTorrent::TorrentAnnounceStatus> AnyAnnounceStatus;
|
||||
|
||||
TorrentFilter() = default;
|
||||
// category & tags: pass empty string for uncategorized / untagged torrents.
|
||||
TorrentFilter(Type type
|
||||
TorrentFilter(Status status
|
||||
, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory
|
||||
, const std::optional<Tag> &tag = AnyTag
|
||||
, std::optional<bool> isPrivate = {});
|
||||
TorrentFilter(const QString &filter
|
||||
, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory
|
||||
, const std::optional<Tag> &tags = AnyTag
|
||||
, std::optional<bool> isPrivate = {});
|
||||
, const std::optional<bool> &isPrivate = {}
|
||||
, const std::optional<QString> &trackerHost = AnyTrackerHost
|
||||
, const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus = AnyAnnounceStatus);
|
||||
|
||||
|
||||
bool setType(Type type);
|
||||
bool setTypeByName(const QString &filter);
|
||||
bool setStatus(Status status);
|
||||
bool setTorrentIDSet(const std::optional<TorrentIDSet> &idSet);
|
||||
bool setCategory(const std::optional<QString> &category);
|
||||
bool setTag(const std::optional<Tag> &tag);
|
||||
bool setPrivate(std::optional<bool> isPrivate);
|
||||
bool setTrackerHost(const std::optional<QString> &trackerHost);
|
||||
bool setAnnounceStatus(const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus);
|
||||
|
||||
bool match(const BitTorrent::Torrent *torrent) const;
|
||||
|
||||
private:
|
||||
bool matchState(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchStatus(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchHash(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchCategory(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchTag(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchPrivate(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchTracker(const BitTorrent::Torrent *torrent) const;
|
||||
|
||||
Type m_type {All};
|
||||
Status m_status {All};
|
||||
std::optional<QString> m_category;
|
||||
std::optional<Tag> m_tag;
|
||||
std::optional<TorrentIDSet> m_idSet;
|
||||
std::optional<bool> m_private;
|
||||
std::optional<QString> m_trackerHost;
|
||||
std::optional<BitTorrent::TorrentAnnounceStatus> m_announceStatus;
|
||||
};
|
||||
|
||||
QString getTrackerHost(const QString &url);
|
||||
|
||||
@@ -37,7 +37,8 @@ enum class ShutdownDialogAction
|
||||
Exit,
|
||||
Shutdown,
|
||||
Suspend,
|
||||
Hibernate
|
||||
Hibernate,
|
||||
Reboot
|
||||
};
|
||||
|
||||
using QStringMap = QMap<QString, QString>;
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
namespace Utils::ForeignApps
|
||||
{
|
||||
inline const QString PYTHON_ISOLATE_MODE_FLAG = u"-I"_s;
|
||||
inline const QString PYTHON_UTF8_MODE_FLAG = u"-Xutf8=1"_s;
|
||||
|
||||
struct PythonInfo
|
||||
{
|
||||
|
||||
@@ -87,22 +87,30 @@ void Utils::OS::shutdownComputer([[maybe_unused]] const ShutdownDialogAction &ac
|
||||
{
|
||||
::SetSuspendState(TRUE, FALSE, FALSE);
|
||||
}
|
||||
else
|
||||
else if (action == ShutdownDialogAction::Shutdown)
|
||||
{
|
||||
std::wstring msg = QCoreApplication::translate("misc"
|
||||
, "qBittorrent will shutdown the computer now because all downloads are complete.").toStdWString();
|
||||
::InitiateSystemShutdownW(nullptr, msg.data(), 10, TRUE, FALSE);
|
||||
}
|
||||
else if (action == ShutdownDialogAction::Reboot)
|
||||
{
|
||||
std::wstring msg = QCoreApplication::translate("misc"
|
||||
, "qBittorrent will reboot the computer now because all downloads are complete.").toStdWString();
|
||||
::InitiateSystemShutdownW(nullptr, msg.data(), 10, TRUE, TRUE);
|
||||
}
|
||||
|
||||
// Disable shutdown privilege.
|
||||
tkp.Privileges[0].Attributes = 0;
|
||||
::AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0);
|
||||
|
||||
#elif defined(Q_OS_MACOS)
|
||||
AEEventID EventToSend;
|
||||
if (action != ShutdownDialogAction::Shutdown)
|
||||
AEEventID EventToSend {};
|
||||
if (action == ShutdownDialogAction::Suspend)
|
||||
EventToSend = kAESleep;
|
||||
else
|
||||
else if (action == ShutdownDialogAction::Reboot)
|
||||
EventToSend = kAERestart;
|
||||
else if (action == ShutdownDialogAction::Shutdown)
|
||||
EventToSend = kAEShutDown;
|
||||
AEAddressDesc targetDesc;
|
||||
const ProcessSerialNumber kPSNOfSystemProcess = {0, kSystemProcess};
|
||||
@@ -133,7 +141,7 @@ void Utils::OS::shutdownComputer([[maybe_unused]] const ShutdownDialogAction &ac
|
||||
|
||||
#elif defined(QBT_USES_DBUS)
|
||||
// Use dbus to power off / suspend the system
|
||||
if (action != ShutdownDialogAction::Shutdown)
|
||||
if ((action == ShutdownDialogAction::Suspend) || (action == ShutdownDialogAction::Hibernate))
|
||||
{
|
||||
// Some recent systems use systemd's logind
|
||||
QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s,
|
||||
@@ -166,7 +174,7 @@ void Utils::OS::shutdownComputer([[maybe_unused]] const ShutdownDialogAction &ac
|
||||
else
|
||||
halIface.call(u"Hibernate"_s);
|
||||
}
|
||||
else
|
||||
else if (action == ShutdownDialogAction::Shutdown)
|
||||
{
|
||||
// Some recent systems use systemd's logind
|
||||
QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s,
|
||||
@@ -190,6 +198,30 @@ void Utils::OS::shutdownComputer([[maybe_unused]] const ShutdownDialogAction &ac
|
||||
QDBusConnection::systemBus());
|
||||
halIface.call(u"Shutdown"_s);
|
||||
}
|
||||
else if (action == ShutdownDialogAction::Reboot)
|
||||
{
|
||||
// Some recent systems use systemd's logind
|
||||
QDBusInterface login1Iface(u"org.freedesktop.login1"_s, u"/org/freedesktop/login1"_s,
|
||||
u"org.freedesktop.login1.Manager"_s, QDBusConnection::systemBus());
|
||||
if (login1Iface.isValid())
|
||||
{
|
||||
login1Iface.call(u"Reboot"_s, false);
|
||||
return;
|
||||
}
|
||||
// Else, other recent systems use ConsoleKit
|
||||
QDBusInterface consolekitIface(u"org.freedesktop.ConsoleKit"_s, u"/org/freedesktop/ConsoleKit/Manager"_s,
|
||||
u"org.freedesktop.ConsoleKit.Manager"_s, QDBusConnection::systemBus());
|
||||
if (consolekitIface.isValid())
|
||||
{
|
||||
consolekitIface.call(u"Restart"_s);
|
||||
return;
|
||||
}
|
||||
// HAL (older systems)
|
||||
QDBusInterface halIface(u"org.freedesktop.Hal"_s, u"/org/freedesktop/Hal/devices/computer"_s,
|
||||
u"org.freedesktop.Hal.Device.SystemPowerManagement"_s,
|
||||
QDBusConnection::systemBus());
|
||||
halIface.call(u"Reboot"_s);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -275,11 +307,11 @@ bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
|
||||
const QByteArray zoneID = QByteArrayLiteral("[ZoneTransfer]\r\nZoneId=3\r\n")
|
||||
+ u"HostUrl=%1\r\n"_s.arg(hostURL).toUtf8();
|
||||
|
||||
if (LARGE_INTEGER streamSize = {0};
|
||||
if (LARGE_INTEGER streamSize {};
|
||||
::GetFileSizeEx(handle, &streamSize) && (streamSize.QuadPart > 0))
|
||||
{
|
||||
const DWORD expectedReadSize = std::min<LONGLONG>(streamSize.QuadPart, 1024);
|
||||
QByteArray buf {expectedReadSize, '\0'};
|
||||
QByteArray buf {static_cast<qsizetype>(expectedReadSize), '\0'};
|
||||
|
||||
if (DWORD actualReadSize = 0;
|
||||
::ReadFile(handle, buf.data(), expectedReadSize, &actualReadSize, nullptr) && (actualReadSize == expectedReadSize))
|
||||
@@ -289,14 +321,14 @@ bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
|
||||
}
|
||||
}
|
||||
|
||||
if (!::SetFilePointerEx(handle, {0}, nullptr, FILE_BEGIN))
|
||||
if (!::SetFilePointerEx(handle, {}, nullptr, FILE_BEGIN))
|
||||
return false;
|
||||
if (!::SetEndOfFile(handle))
|
||||
return false;
|
||||
|
||||
DWORD written = 0;
|
||||
const BOOL writeResult = ::WriteFile(handle, zoneID.constData(), zoneID.size(), &written, nullptr);
|
||||
return writeResult && (written == zoneID.size());
|
||||
return writeResult && (static_cast<qsizetype>(written) == zoneID.size());
|
||||
#endif
|
||||
}
|
||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#define QBT_VERSION_MINOR 2
|
||||
#define QBT_VERSION_BUGFIX 0
|
||||
#define QBT_VERSION_BUILD 0
|
||||
#define QBT_VERSION_STATUS "alpha1" // Should be empty for stable releases!
|
||||
#define QBT_VERSION_STATUS "beta1" // Should be empty for stable releases!
|
||||
|
||||
#define QBT__STRINGIFY(x) #x
|
||||
#define QBT_STRINGIFY(x) QBT__STRINGIFY(x)
|
||||
|
||||
@@ -129,7 +129,9 @@ add_library(qbt_gui STATIC
|
||||
transferlistfilters/tagfilterproxymodel.h
|
||||
transferlistfilters/tagfilterwidget.h
|
||||
transferlistfilters/trackersfilterwidget.h
|
||||
transferlistfilters/trackerstatusfilterwidget.h
|
||||
transferlistfilterswidget.h
|
||||
transferlistfilterswidgetitem.h
|
||||
transferlistmodel.h
|
||||
transferlistsortmodel.h
|
||||
transferlistwidget.h
|
||||
@@ -230,7 +232,9 @@ add_library(qbt_gui STATIC
|
||||
transferlistfilters/tagfilterproxymodel.cpp
|
||||
transferlistfilters/tagfilterwidget.cpp
|
||||
transferlistfilters/trackersfilterwidget.cpp
|
||||
transferlistfilters/trackerstatusfilterwidget.cpp
|
||||
transferlistfilterswidget.cpp
|
||||
transferlistfilterswidgetitem.cpp
|
||||
transferlistmodel.cpp
|
||||
transferlistsortmodel.cpp
|
||||
transferlistwidget.cpp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -190,6 +190,7 @@ void AddTorrentParamsWidget::populate()
|
||||
}
|
||||
|
||||
populateDefaultPaths();
|
||||
resetShareLimitsWidgetDefaults();
|
||||
});
|
||||
|
||||
m_ui->savePathEdit->disconnect(this);
|
||||
@@ -241,7 +242,7 @@ void AddTorrentParamsWidget::populate()
|
||||
|
||||
m_ui->startTorrentComboBox->disconnect(this);
|
||||
m_ui->startTorrentComboBox->setCurrentIndex(m_addTorrentParams.addStopped
|
||||
? m_ui->startTorrentComboBox->findData(!*m_addTorrentParams.addStopped) : 0);
|
||||
? m_ui->startTorrentComboBox->findData(!*m_addTorrentParams.addStopped) : 0);
|
||||
connect(m_ui->startTorrentComboBox, &QComboBox::currentIndexChanged, this, [this]
|
||||
{
|
||||
const QVariant data = m_ui->startTorrentComboBox->currentData();
|
||||
@@ -270,6 +271,7 @@ void AddTorrentParamsWidget::populate()
|
||||
m_addTorrentParams.addToQueueTop = data.toBool();
|
||||
});
|
||||
|
||||
resetShareLimitsWidgetDefaults();
|
||||
m_ui->torrentShareLimitsWidget->setRatioLimit(m_addTorrentParams.ratioLimit);
|
||||
m_ui->torrentShareLimitsWidget->setSeedingTimeLimit(m_addTorrentParams.seedingTimeLimit);
|
||||
m_ui->torrentShareLimitsWidget->setInactiveSeedingTimeLimit(m_addTorrentParams.inactiveSeedingTimeLimit);
|
||||
@@ -418,3 +420,11 @@ void AddTorrentParamsWidget::populateSavePathOptions()
|
||||
|
||||
populateDefaultPaths();
|
||||
}
|
||||
|
||||
void AddTorrentParamsWidget::resetShareLimitsWidgetDefaults()
|
||||
{
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
m_ui->torrentShareLimitsWidget->setDefaults((m_addTorrentParams.category.isEmpty() ? TorrentShareLimitsWidget::UsedDefaults::Global : TorrentShareLimitsWidget::UsedDefaults::Category)
|
||||
, btSession->categoryRatioLimit(m_addTorrentParams.category), btSession->categorySeedingTimeLimit(m_addTorrentParams.category)
|
||||
, btSession->categoryInactiveSeedingTimeLimit(m_addTorrentParams.category), btSession->categoryShareLimitAction(m_addTorrentParams.category));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -57,7 +57,7 @@ private:
|
||||
void populateDefaultPaths();
|
||||
void populateDefaultDownloadPath();
|
||||
void populateSavePathOptions();
|
||||
|
||||
void resetShareLimitsWidgetDefaults();
|
||||
|
||||
Ui::AddTorrentParamsWidget *m_ui;
|
||||
BitTorrent::AddTorrentParams m_addTorrentParams;
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace
|
||||
OUTGOING_PORT_MIN,
|
||||
OUTGOING_PORT_MAX,
|
||||
UPNP_LEASE_DURATION,
|
||||
PEER_TOS,
|
||||
PEER_DSCP,
|
||||
UTP_MIX_MODE,
|
||||
HOSTNAME_CACHE_TTL,
|
||||
IDN_SUPPORT,
|
||||
@@ -276,7 +276,7 @@ void AdvancedSettings::saveAdvancedSettings() const
|
||||
// UPnP lease duration
|
||||
session->setUPnPLeaseDuration(m_spinBoxUPnPLeaseDuration.value());
|
||||
// Type of service
|
||||
session->setPeerToS(m_spinBoxPeerToS.value());
|
||||
session->setPeerDSCP(m_spinBoxPeerDSCP.value());
|
||||
// uTP-TCP mixed mode
|
||||
session->setUtpMixedMode(m_comboBoxUtpMixedMode.currentData().value<BitTorrent::MixedModeAlgorithm>());
|
||||
// Hostname resolver cache TTL
|
||||
@@ -723,11 +723,11 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
addRow(UPNP_LEASE_DURATION, (tr("UPnP lease duration [0: permanent lease]") + u' ' + makeLink(u"https://www.libtorrent.org/reference-Settings.html#upnp_lease_duration", u"(?)"))
|
||||
, &m_spinBoxUPnPLeaseDuration);
|
||||
// Type of service
|
||||
m_spinBoxPeerToS.setMinimum(0);
|
||||
m_spinBoxPeerToS.setMaximum(255);
|
||||
m_spinBoxPeerToS.setValue(session->peerToS());
|
||||
addRow(PEER_TOS, (tr("Type of service (ToS) for connections to peers") + u' ' + makeLink(u"https://www.libtorrent.org/reference-Settings.html#peer_tos", u"(?)"))
|
||||
, &m_spinBoxPeerToS);
|
||||
m_spinBoxPeerDSCP.setMinimum(0);
|
||||
m_spinBoxPeerDSCP.setMaximum(255);
|
||||
m_spinBoxPeerDSCP.setValue(session->peerDSCP());
|
||||
addRow(PEER_DSCP, (tr("Differentiated Services Code Point (DSCP) for connections to peers") + u' ' + makeLink(u"https://www.libtorrent.org/reference-Settings.html#peer_dscp", u"(?)"))
|
||||
, &m_spinBoxPeerDSCP);
|
||||
// uTP-TCP mixed mode
|
||||
m_comboBoxUtpMixedMode.addItem(tr("Prefer TCP"), QVariant::fromValue(BitTorrent::MixedModeAlgorithm::TCP));
|
||||
m_comboBoxUtpMixedMode.addItem(tr("Peer proportional (throttles TCP)"), QVariant::fromValue(BitTorrent::MixedModeAlgorithm::Proportional));
|
||||
|
||||
@@ -70,7 +70,7 @@ private:
|
||||
|
||||
QSpinBox m_spinBoxSaveResumeDataInterval, m_spinBoxSaveStatisticsInterval, m_spinBoxTorrentFileSizeLimit, m_spinBoxBdecodeDepthLimit, m_spinBoxBdecodeTokenLimit,
|
||||
m_spinBoxAsyncIOThreads, m_spinBoxFilePoolSize, m_spinBoxCheckingMemUsage, m_spinBoxDiskQueueSize,
|
||||
m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration, m_spinBoxPeerToS, m_spinBoxHostnameCacheTTL,
|
||||
m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration, m_spinBoxPeerDSCP, m_spinBoxHostnameCacheTTL,
|
||||
m_spinBoxListRefresh, m_spinBoxTrackerPort, m_spinBoxSendBufferWatermark, m_spinBoxSendBufferLowWatermark,
|
||||
m_spinBoxSendBufferWatermarkFactor, m_spinBoxConnectionSpeed, m_spinBoxSocketSendBufferSize, m_spinBoxSocketReceiveBufferSize, m_spinBoxSocketBacklogSize,
|
||||
m_spinBoxAnnouncePort, m_spinBoxMaxConcurrentHTTPAnnounces, m_spinBoxStopTrackerTimeout, m_spinBoxSessionShutdownTimeout,
|
||||
|
||||
@@ -388,10 +388,16 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con
|
||||
autoShutdownGroup->addAction(m_ui->actionAutoShutdown);
|
||||
autoShutdownGroup->addAction(m_ui->actionAutoSuspend);
|
||||
autoShutdownGroup->addAction(m_ui->actionAutoHibernate);
|
||||
autoShutdownGroup->addAction(m_ui->actionAutoReboot);
|
||||
#if (!defined(Q_OS_UNIX) || defined(Q_OS_MACOS)) || defined(QBT_USES_DBUS)
|
||||
m_ui->actionAutoShutdown->setChecked(pref->shutdownWhenDownloadsComplete());
|
||||
m_ui->actionAutoReboot->setChecked(pref->rebootWhenDownloadsComplete());
|
||||
m_ui->actionAutoSuspend->setChecked(pref->suspendWhenDownloadsComplete());
|
||||
m_ui->actionAutoHibernate->setChecked(pref->hibernateWhenDownloadsComplete());
|
||||
#ifdef Q_OS_MACOS
|
||||
// macOS doesn't support Hibernate via Apple Events API
|
||||
m_ui->actionAutoHibernate->setDisabled(true);
|
||||
#endif
|
||||
#else
|
||||
m_ui->actionAutoShutdown->setDisabled(true);
|
||||
m_ui->actionAutoSuspend->setDisabled(true);
|
||||
@@ -486,7 +492,7 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con
|
||||
m_transferListWidget->applyStatusFilter(pref->getTransSelFilter());
|
||||
m_transferListWidget->applyCategoryFilter(QString());
|
||||
m_transferListWidget->applyTagFilter(std::nullopt);
|
||||
m_transferListWidget->applyTrackerFilterAll();
|
||||
m_transferListWidget->applyTrackerFilter({});
|
||||
}
|
||||
|
||||
// Start watching the executable for updates
|
||||
@@ -1355,11 +1361,6 @@ void MainWindow::showFiltersSidebar(const bool show)
|
||||
if (show && !m_transferListFiltersWidget)
|
||||
{
|
||||
m_transferListFiltersWidget = new TransferListFiltersWidget(m_splitter, m_transferListWidget, isDownloadTrackerFavicon());
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersAdded, m_transferListFiltersWidget, &TransferListFiltersWidget::addTrackers);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersRemoved, m_transferListFiltersWidget, &TransferListFiltersWidget::removeTrackers);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::refreshTrackers);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerEntryStatusesUpdated, m_transferListFiltersWidget, &TransferListFiltersWidget::trackerEntryStatusesUpdated);
|
||||
|
||||
m_splitter->insertWidget(0, m_transferListFiltersWidget);
|
||||
m_splitter->setCollapsible(0, true);
|
||||
// From https://doc.qt.io/qt-5/qsplitter.html#setSizes:
|
||||
@@ -1790,30 +1791,31 @@ void MainWindow::on_actionCriticalMessages_triggered(const bool checked)
|
||||
setExecutionLogMsgTypes(flags);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionAutoExit_toggled(bool enabled)
|
||||
void MainWindow::on_actionAutoExit_toggled(const bool enabled)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << enabled;
|
||||
Preferences::instance()->setShutdownqBTWhenDownloadsComplete(enabled);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionAutoSuspend_toggled(bool enabled)
|
||||
void MainWindow::on_actionAutoSuspend_toggled(const bool enabled)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << enabled;
|
||||
Preferences::instance()->setSuspendWhenDownloadsComplete(enabled);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionAutoHibernate_toggled(bool enabled)
|
||||
void MainWindow::on_actionAutoHibernate_toggled(const bool enabled)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << enabled;
|
||||
Preferences::instance()->setHibernateWhenDownloadsComplete(enabled);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionAutoShutdown_toggled(bool enabled)
|
||||
void MainWindow::on_actionAutoShutdown_toggled(const bool enabled)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << enabled;
|
||||
Preferences::instance()->setShutdownWhenDownloadsComplete(enabled);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionAutoReboot_toggled(const bool enabled)
|
||||
{
|
||||
Preferences::instance()->setRebootWhenDownloadsComplete(enabled);
|
||||
}
|
||||
|
||||
void MainWindow::updatePowerManagementState() const
|
||||
{
|
||||
const auto *pref = Preferences::instance();
|
||||
|
||||
@@ -158,6 +158,7 @@ private slots:
|
||||
void on_actionAutoSuspend_toggled(bool);
|
||||
void on_actionAutoHibernate_toggled(bool);
|
||||
void on_actionAutoShutdown_toggled(bool);
|
||||
void on_actionAutoReboot_toggled(bool);
|
||||
void on_actionAbout_triggered();
|
||||
void on_actionStatistics_triggered();
|
||||
void on_actionCreateTorrent_triggered();
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
<addaction name="actionAutoExit"/>
|
||||
<addaction name="actionAutoSuspend"/>
|
||||
<addaction name="actionAutoHibernate"/>
|
||||
<addaction name="actionAutoReboot"/>
|
||||
<addaction name="actionAutoShutdown"/>
|
||||
</widget>
|
||||
<addaction name="actionCreateTorrent"/>
|
||||
@@ -397,6 +398,14 @@
|
||||
<string>Sh&utdown System</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAutoReboot">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&Reboot System</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionAutoShutdownDisabled">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -43,15 +43,12 @@
|
||||
#include <QEvent>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QStyleFactory>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QTranslator>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QStyleFactory>
|
||||
#endif
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/sharelimitaction.h"
|
||||
#include "base/bittorrent/sharelimits.h"
|
||||
#include "base/exceptions.h"
|
||||
#include "base/global.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
@@ -274,7 +271,7 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
m_ui->checkAltRowColors->setChecked(pref->useAlternatingRowColors());
|
||||
m_ui->checkUseTorrentStatesColors->setChecked(pref->useTorrentStatesColors());
|
||||
m_ui->checkProgressBarFollowsTextColor->setChecked(pref->getProgressBarFollowsTextColor());
|
||||
m_ui->checkProgressBarFollowsTextColor->setEnabled(m_ui->checkProgressBarFollowsTextColor->isChecked());
|
||||
m_ui->checkProgressBarFollowsTextColor->setEnabled(m_ui->checkUseTorrentStatesColors->isChecked());
|
||||
m_ui->checkHideZero->setChecked(pref->getHideZeroValues());
|
||||
m_ui->comboHideZero->setCurrentIndex(pref->getHideZeroComboValues());
|
||||
m_ui->comboHideZero->setEnabled(m_ui->checkHideZero->isChecked());
|
||||
@@ -300,6 +297,7 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
m_ui->actionTorrentFnOnDblClBox->setCurrentIndex(m_ui->actionTorrentFnOnDblClBox->findData(actionSeeding));
|
||||
|
||||
m_ui->checkBoxHideZeroStatusFilters->setChecked(pref->getHideZeroStatusFilters());
|
||||
m_ui->checkBoxUseSeparateTrackerStatusFilter->setChecked(pref->useSeparateTrackerStatusFilter());
|
||||
|
||||
m_ui->checkTorrentContentDrag->setChecked(pref->isTorrentContentDragEnabled());
|
||||
|
||||
@@ -376,11 +374,7 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
m_ui->checkBoxPerformanceWarning->setChecked(session->isPerformanceWarningEnabled());
|
||||
|
||||
connect(m_ui->comboLanguage, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
connect(m_ui->comboStyle, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
#endif
|
||||
|
||||
#ifdef QBT_HAS_COLORSCHEME_OPTION
|
||||
connect(m_ui->comboColorScheme, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
#endif
|
||||
@@ -414,6 +408,7 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
connect(m_ui->actionTorrentDlOnDblClBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->actionTorrentFnOnDblClBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkBoxHideZeroStatusFilters, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkBoxUseSeparateTrackerStatusFilter, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
|
||||
connect(m_ui->checkTorrentContentDrag, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
|
||||
@@ -488,9 +483,7 @@ void OptionsDialog::saveBehaviorTabOptions() const
|
||||
}
|
||||
pref->setLocale(locale);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
pref->setStyle(m_ui->comboStyle->currentData().toString());
|
||||
#endif
|
||||
|
||||
#ifdef QBT_HAS_COLORSCHEME_OPTION
|
||||
UIThemeManager::instance()->setColorScheme(m_ui->comboColorScheme->currentData().value<ColorScheme>());
|
||||
@@ -513,6 +506,7 @@ void OptionsDialog::saveBehaviorTabOptions() const
|
||||
pref->setActionOnDblClOnTorrentFn(m_ui->actionTorrentFnOnDblClBox->currentData().toInt());
|
||||
|
||||
pref->setHideZeroStatusFilters(m_ui->checkBoxHideZeroStatusFilters->isChecked());
|
||||
pref->setUseSeparateTrackerStatusFilter(m_ui->checkBoxUseSeparateTrackerStatusFilter->isChecked());
|
||||
|
||||
pref->setTorrentContentDragEnabled(m_ui->checkTorrentContentDrag->isChecked());
|
||||
|
||||
@@ -631,7 +625,6 @@ void OptionsDialog::loadDownloadsTabOptions()
|
||||
m_ui->comboCategoryChanged->setCurrentIndex(session->isDisableAutoTMMWhenCategorySavePathChanged());
|
||||
m_ui->comboCategoryDefaultPathChanged->setCurrentIndex(session->isDisableAutoTMMWhenDefaultSavePathChanged());
|
||||
|
||||
m_ui->checkUseSubcategories->setChecked(session->isSubcategoriesEnabled());
|
||||
m_ui->checkUseCategoryPaths->setChecked(session->useCategoryPathsInManualMode());
|
||||
|
||||
m_ui->textSavePath->setDialogCaption(tr("Choose a save directory"));
|
||||
@@ -736,7 +729,6 @@ void OptionsDialog::loadDownloadsTabOptions()
|
||||
connect(m_ui->comboCategoryChanged, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->comboCategoryDefaultPathChanged, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
|
||||
connect(m_ui->checkUseSubcategories, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkUseCategoryPaths, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
|
||||
connect(m_ui->textSavePath, &FileSystemPathEdit::selectedPathChanged, this, &ThisType::enableApplyButton);
|
||||
@@ -808,7 +800,6 @@ void OptionsDialog::saveDownloadsTabOptions() const
|
||||
session->setDisableAutoTMMWhenCategorySavePathChanged(m_ui->comboCategoryChanged->currentIndex() == 1);
|
||||
session->setDisableAutoTMMWhenDefaultSavePathChanged(m_ui->comboCategoryDefaultPathChanged->currentIndex() == 1);
|
||||
|
||||
session->setSubcategoriesEnabled(m_ui->checkUseSubcategories->isChecked());
|
||||
session->setUseCategoryPathsInManualMode(m_ui->checkUseCategoryPaths->isChecked());
|
||||
|
||||
session->setSavePath(Path(m_ui->textSavePath->selectedPath()));
|
||||
@@ -1126,6 +1117,8 @@ void OptionsDialog::loadBittorrentTabOptions()
|
||||
m_ui->spinUploadRateForSlowTorrents->setValue(session->uploadRateForSlowTorrents());
|
||||
m_ui->spinSlowTorrentsInactivityTimer->setValue(session->slowTorrentsInactivityTimer());
|
||||
|
||||
m_ui->spinMaxRatio->setMaximum(std::numeric_limits<int>::max());
|
||||
|
||||
if (session->globalMaxRatio() >= 0.)
|
||||
{
|
||||
// Enable
|
||||
@@ -1170,7 +1163,7 @@ void OptionsDialog::loadBittorrentTabOptions()
|
||||
|
||||
const QHash<BitTorrent::ShareLimitAction, int> actIndex =
|
||||
{
|
||||
{BitTorrent::ShareLimitAction::Stop, 0},
|
||||
{BitTorrent::ShareLimitAction::Stop, 0},
|
||||
{BitTorrent::ShareLimitAction::Remove, 1},
|
||||
{BitTorrent::ShareLimitAction::RemoveWithContent, 2},
|
||||
{BitTorrent::ShareLimitAction::EnableSuperSeeding, 3}
|
||||
@@ -1846,6 +1839,11 @@ void OptionsDialog::initializeStyleCombo()
|
||||
#ifdef Q_OS_WIN
|
||||
m_ui->labelStyleHint->setText(tr("%1 is recommended for best compatibility with Windows dark mode"
|
||||
, "Fusion is recommended for best compatibility with Windows dark mode").arg(u"Fusion"_s));
|
||||
#else
|
||||
m_ui->labelStyleHint->hide();
|
||||
m_ui->layoutStyle->removeWidget(m_ui->labelStyleHint);
|
||||
#endif
|
||||
|
||||
m_ui->comboStyle->addItem(tr("System", "System default Qt style"), u"system"_s);
|
||||
m_ui->comboStyle->setItemData(0, tr("Let Qt decide the style for this system"), Qt::ToolTipRole);
|
||||
m_ui->comboStyle->insertSeparator(1);
|
||||
@@ -1859,14 +1857,6 @@ void OptionsDialog::initializeStyleCombo()
|
||||
const QString selectedStyleName = prefStyleName.isEmpty() ? QApplication::style()->name() : prefStyleName;
|
||||
const int styleIndex = m_ui->comboStyle->findData(selectedStyleName, Qt::UserRole, Qt::MatchFixedString);
|
||||
m_ui->comboStyle->setCurrentIndex(std::max(0, styleIndex));
|
||||
#else
|
||||
m_ui->labelStyle->hide();
|
||||
m_ui->comboStyle->hide();
|
||||
m_ui->labelStyleHint->hide();
|
||||
m_ui->layoutStyle->removeWidget(m_ui->labelStyle);
|
||||
m_ui->layoutStyle->removeWidget(m_ui->comboStyle);
|
||||
m_ui->layoutStyle->removeWidget(m_ui->labelStyleHint);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OptionsDialog::initializeColorSchemeOptions()
|
||||
|
||||
@@ -456,6 +456,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxUseSeparateTrackerStatusFilter">
|
||||
<property name="toolTip">
|
||||
<string>Use separate "Tracker status" filter. Otherwise it gets merged with "Trackers" filter.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use separate "Tracker status" filter</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -1371,13 +1381,6 @@ Manual: Various torrent properties (e.g. save path) must be assigned manually</s
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkUseSubcategories">
|
||||
<property name="text">
|
||||
<string>Use Subcategories</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkUseCategoryPaths">
|
||||
<property name="toolTip">
|
||||
@@ -1908,7 +1911,7 @@ readme[0-9].txt: filter 'readme1.txt', 'readme2.txt' but not 'readme10.txt'.</st
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2000</number>
|
||||
<number>2147483647</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>500</number>
|
||||
@@ -1944,7 +1947,7 @@ readme[0-9].txt: filter 'readme1.txt', 'readme2.txt' but not 'readme10.txt'.</st
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2000</number>
|
||||
<number>2147483647</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
@@ -1961,7 +1964,7 @@ readme[0-9].txt: filter 'readme1.txt', 'readme2.txt' but not 'readme10.txt'.</st
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="spinMaxUploads">
|
||||
<property name="maximum">
|
||||
<number>2000</number>
|
||||
<number>2147483647</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>8</number>
|
||||
@@ -1978,7 +1981,7 @@ readme[0-9].txt: filter 'readme1.txt', 'readme2.txt' but not 'readme10.txt'.</st
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="spinMaxUploadsPerTorrent">
|
||||
<property name="maximum">
|
||||
<number>500</number>
|
||||
<number>2147483647</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>4</number>
|
||||
@@ -3789,9 +3792,6 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
||||
<layout class="QHBoxLayout" name="horizontalLayoutAPIKey">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="textWebUIAPIKey">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
@@ -3805,89 +3805,35 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Copy API key</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/edit-copy.svg</normaloff>:/icons/edit-copy.svg</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnWebUIAPIKeyRotate">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Generate API key</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/view-refresh.svg</normaloff>:/icons/view-refresh.svg</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnWebUIAPIKeyDelete">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Delete API key</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../icons.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/list-remove.svg</normaloff>:/icons/list-remove.svg</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -85,6 +85,8 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
using Utils::Misc::SizeUnit;
|
||||
|
||||
SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidget *parent)
|
||||
: GUIApplicationComponent(app, parent)
|
||||
, m_nameFilteringMode {u"Search/FilteringMode"_s}
|
||||
@@ -99,6 +101,8 @@ SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidge
|
||||
header()->setStretchLastSection(false);
|
||||
header()->setTextElideMode(Qt::ElideRight);
|
||||
|
||||
fillFilterComboBoxes();
|
||||
|
||||
// Set Search results list model
|
||||
m_searchListModel = new QStandardItemModel(0, SearchSortModel::NB_SEARCH_COLUMNS, this);
|
||||
m_searchListModel->setHeaderData(SearchSortModel::NAME, Qt::Horizontal, tr("Name", "i.e: file name"));
|
||||
@@ -116,6 +120,13 @@ SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidge
|
||||
m_proxyModel = new SearchSortModel(this);
|
||||
m_proxyModel->setDynamicSortFilter(true);
|
||||
m_proxyModel->setSourceModel(m_searchListModel);
|
||||
m_proxyModel->enableNameFilter(m_nameFilteringMode.get(NameFilteringMode::OnlyNames) == NameFilteringMode::OnlyNames);
|
||||
m_proxyModel->setSeedsFilter(m_ui->minSeeds->value(), m_ui->maxSeeds->value());
|
||||
m_proxyModel->setSizeFilter(sizeInBytes(m_ui->minSize->value(), static_cast<SizeUnit>(m_ui->minSizeUnit->currentIndex()))
|
||||
, sizeInBytes(m_ui->maxSize->value(), static_cast<SizeUnit>(m_ui->maxSizeUnit->currentIndex())));
|
||||
|
||||
updateResultsCount();
|
||||
|
||||
m_ui->resultsBrowser->setModel(m_proxyModel);
|
||||
|
||||
m_ui->resultsBrowser->hideColumn(SearchSortModel::DL_LINK); // Hide url column
|
||||
@@ -154,8 +165,6 @@ SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidge
|
||||
connect(header(), &QHeaderView::sectionMoved, this, &SearchJobWidget::saveSettings);
|
||||
connect(header(), &QHeaderView::sortIndicatorChanged, this, &SearchJobWidget::saveSettings);
|
||||
|
||||
fillFilterComboBoxes();
|
||||
|
||||
m_lineEditSearchResultsFilter = new LineEdit(this);
|
||||
m_lineEditSearchResultsFilter->setPlaceholderText(tr("Filter search results..."));
|
||||
m_lineEditSearchResultsFilter->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
@@ -163,24 +172,17 @@ SearchJobWidget::SearchJobWidget(const QString &id, IGUIApplication *app, QWidge
|
||||
connect(m_lineEditSearchResultsFilter, &LineEdit::textChanged, this, &SearchJobWidget::filterSearchResults);
|
||||
m_ui->horizontalLayout->insertWidget(0, m_lineEditSearchResultsFilter);
|
||||
|
||||
connect(m_ui->filterMode, qOverload<int>(&QComboBox::currentIndexChanged)
|
||||
, this, &SearchJobWidget::updateFilter);
|
||||
connect(m_ui->minSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
|
||||
connect(m_ui->minSeeds, qOverload<int>(&QSpinBox::valueChanged)
|
||||
, this, &SearchJobWidget::updateFilter);
|
||||
connect(m_ui->maxSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
|
||||
connect(m_ui->maxSeeds, qOverload<int>(&QSpinBox::valueChanged)
|
||||
, this, &SearchJobWidget::updateFilter);
|
||||
connect(m_ui->minSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
|
||||
connect(m_ui->minSize, qOverload<double>(&QDoubleSpinBox::valueChanged)
|
||||
, this, &SearchJobWidget::updateFilter);
|
||||
connect(m_ui->maxSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateFilter);
|
||||
connect(m_ui->maxSize, qOverload<double>(&QDoubleSpinBox::valueChanged)
|
||||
, this, &SearchJobWidget::updateFilter);
|
||||
connect(m_ui->minSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged)
|
||||
, this, &SearchJobWidget::updateFilter);
|
||||
connect(m_ui->maxSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged)
|
||||
, this, &SearchJobWidget::updateFilter);
|
||||
connect(m_ui->filterMode, qOverload<int>(&QComboBox::currentIndexChanged), this, &SearchJobWidget::updateNameFilter);
|
||||
connect(m_ui->minSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateSeedsFilter);
|
||||
connect(m_ui->minSeeds, qOverload<int>(&QSpinBox::valueChanged), this, &SearchJobWidget::updateSeedsFilter);
|
||||
connect(m_ui->maxSeeds, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateSeedsFilter);
|
||||
connect(m_ui->maxSeeds, qOverload<int>(&QSpinBox::valueChanged), this, &SearchJobWidget::updateSeedsFilter);
|
||||
connect(m_ui->minSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateSizeFilter);
|
||||
connect(m_ui->minSize, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &SearchJobWidget::updateSizeFilter);
|
||||
connect(m_ui->maxSize, &QAbstractSpinBox::editingFinished, this, &SearchJobWidget::updateSizeFilter);
|
||||
connect(m_ui->maxSize, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &SearchJobWidget::updateSizeFilter);
|
||||
connect(m_ui->minSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged), this, &SearchJobWidget::updateSizeFilter);
|
||||
connect(m_ui->maxSizeUnit, qOverload<int>(&QComboBox::currentIndexChanged), this, &SearchJobWidget::updateSizeFilter);
|
||||
|
||||
connect(m_ui->resultsBrowser, &QAbstractItemView::doubleClicked, this, &SearchJobWidget::onItemDoubleClicked);
|
||||
|
||||
@@ -193,7 +195,6 @@ SearchJobWidget::SearchJobWidget(const QString &id, const QString &searchPattern
|
||||
{
|
||||
m_searchPattern = searchPattern;
|
||||
m_proxyModel->setNameFilter(m_searchPattern);
|
||||
updateFilter();
|
||||
|
||||
appendSearchResults(searchResults);
|
||||
}
|
||||
@@ -301,8 +302,8 @@ void SearchJobWidget::assignSearchHandler(SearchHandler *searchHandler)
|
||||
m_searchPattern = m_searchHandler->pattern();
|
||||
|
||||
m_proxyModel->setNameFilter(m_searchPattern);
|
||||
updateFilter();
|
||||
|
||||
updateResultsCount();
|
||||
setStatus(Status::Ongoing);
|
||||
}
|
||||
|
||||
@@ -454,26 +455,34 @@ void SearchJobWidget::updateResultsCount()
|
||||
emit resultsCountUpdated();
|
||||
}
|
||||
|
||||
void SearchJobWidget::updateFilter()
|
||||
void SearchJobWidget::updateNameFilter()
|
||||
{
|
||||
using Utils::Misc::SizeUnit;
|
||||
const auto filteringMode = static_cast<NameFilteringMode>(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toInt());
|
||||
m_proxyModel->enableNameFilter(filteringMode == NameFilteringMode::OnlyNames);
|
||||
m_nameFilteringMode = filteringMode;
|
||||
|
||||
m_proxyModel->enableNameFilter(filteringMode() == NameFilteringMode::OnlyNames);
|
||||
updateResultsCount();
|
||||
}
|
||||
|
||||
void SearchJobWidget::updateSeedsFilter()
|
||||
{
|
||||
// we update size and seeds filter parameters in the model even if they are disabled
|
||||
m_proxyModel->setSeedsFilter(m_ui->minSeeds->value(), m_ui->maxSeeds->value());
|
||||
m_proxyModel->setSizeFilter(
|
||||
sizeInBytes(m_ui->minSize->value(), static_cast<SizeUnit>(m_ui->minSizeUnit->currentIndex())),
|
||||
sizeInBytes(m_ui->maxSize->value(), static_cast<SizeUnit>(m_ui->maxSizeUnit->currentIndex())));
|
||||
|
||||
m_nameFilteringMode = filteringMode();
|
||||
updateResultsCount();
|
||||
}
|
||||
|
||||
void SearchJobWidget::updateSizeFilter()
|
||||
{
|
||||
// we update size and seeds filter parameters in the model even if they are disabled
|
||||
m_proxyModel->setSizeFilter(sizeInBytes(m_ui->minSize->value(), static_cast<SizeUnit>(m_ui->minSizeUnit->currentIndex()))
|
||||
, sizeInBytes(m_ui->maxSize->value(), static_cast<SizeUnit>(m_ui->maxSizeUnit->currentIndex())));
|
||||
|
||||
m_proxyModel->invalidate();
|
||||
updateResultsCount();
|
||||
}
|
||||
|
||||
void SearchJobWidget::fillFilterComboBoxes()
|
||||
{
|
||||
using Utils::Misc::SizeUnit;
|
||||
using Utils::Misc::unitString;
|
||||
|
||||
QStringList unitStrings;
|
||||
@@ -500,16 +509,15 @@ void SearchJobWidget::fillFilterComboBoxes()
|
||||
|
||||
m_ui->filterMode->addItem(tr("Torrent names only"), static_cast<int>(NameFilteringMode::OnlyNames));
|
||||
m_ui->filterMode->addItem(tr("Everywhere"), static_cast<int>(NameFilteringMode::Everywhere));
|
||||
|
||||
const QVariant selectedMode = static_cast<int>(m_nameFilteringMode.get(NameFilteringMode::OnlyNames));
|
||||
const int index = m_ui->filterMode->findData(selectedMode);
|
||||
const auto selectedFilteringMode = static_cast<int>(m_nameFilteringMode.get(NameFilteringMode::OnlyNames));
|
||||
const int index = m_ui->filterMode->findData(selectedFilteringMode);
|
||||
m_ui->filterMode->setCurrentIndex((index == -1) ? 0 : index);
|
||||
}
|
||||
|
||||
void SearchJobWidget::filterSearchResults(const QString &name)
|
||||
{
|
||||
const QString pattern = (Preferences::instance()->getRegexAsFilteringPatternForSearchJob()
|
||||
? name : Utils::String::wildcardToRegexPattern(name));
|
||||
? name : Utils::String::wildcardToRegexPattern(name));
|
||||
m_proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
||||
updateResultsCount();
|
||||
}
|
||||
@@ -557,11 +565,6 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
|
||||
menu->popup(event->globalPos());
|
||||
}
|
||||
|
||||
SearchJobWidget::NameFilteringMode SearchJobWidget::filteringMode() const
|
||||
{
|
||||
return static_cast<NameFilteringMode>(m_ui->filterMode->itemData(m_ui->filterMode->currentIndex()).toInt());
|
||||
}
|
||||
|
||||
void SearchJobWidget::loadSettings()
|
||||
{
|
||||
header()->restoreState(Preferences::instance()->getSearchTabHeaderState());
|
||||
|
||||
@@ -106,7 +106,9 @@ private:
|
||||
|
||||
void loadSettings();
|
||||
void saveSettings() const;
|
||||
void updateFilter();
|
||||
void updateNameFilter();
|
||||
void updateSeedsFilter();
|
||||
void updateSizeFilter();
|
||||
void filterSearchResults(const QString &name);
|
||||
void showFilterContextMenu();
|
||||
void contextMenuEvent(QContextMenuEvent *event) override;
|
||||
@@ -119,7 +121,6 @@ private:
|
||||
void downloadTorrent(const QModelIndex &rowIndex, AddTorrentOption option = AddTorrentOption::Default);
|
||||
void addTorrentToSession(const QString &source, AddTorrentOption option = AddTorrentOption::Default);
|
||||
void fillFilterComboBoxes();
|
||||
NameFilteringMode filteringMode() const;
|
||||
QHeaderView *header() const;
|
||||
int visibleColumnsCount() const;
|
||||
void setRowColor(int row, const QColor &color);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2013 sledgehammer999 <hammered999@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -39,34 +40,103 @@ SearchSortModel::SearchSortModel(QObject *parent)
|
||||
|
||||
void SearchSortModel::enableNameFilter(const bool enabled)
|
||||
{
|
||||
if (m_isNameFilterEnabled == enabled)
|
||||
return;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
m_isNameFilterEnabled = enabled;
|
||||
endFilterChange(Direction::Rows);
|
||||
#else
|
||||
m_isNameFilterEnabled = enabled;
|
||||
invalidateRowsFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
void SearchSortModel::setNameFilter(const QString &searchTerm)
|
||||
{
|
||||
if (m_searchTerm == searchTerm)
|
||||
return;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
|
||||
m_searchTerm = searchTerm;
|
||||
if ((searchTerm.length() > 2) && searchTerm.startsWith(u'"') && searchTerm.endsWith(u'"'))
|
||||
m_searchTermWords = QStringList(m_searchTerm.sliced(1, (m_searchTerm.length() - 2)));
|
||||
else
|
||||
m_searchTermWords = searchTerm.split(u' ', Qt::SkipEmptyParts);
|
||||
|
||||
endFilterChange(Direction::Rows);
|
||||
#else
|
||||
m_searchTerm = searchTerm;
|
||||
if ((searchTerm.length() > 2) && searchTerm.startsWith(u'"') && searchTerm.endsWith(u'"'))
|
||||
m_searchTermWords = QStringList(m_searchTerm.sliced(1, (m_searchTerm.length() - 2)));
|
||||
else
|
||||
m_searchTermWords = searchTerm.split(u' ', Qt::SkipEmptyParts);
|
||||
|
||||
invalidateRowsFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
void SearchSortModel::setSizeFilter(const qint64 minSize, const qint64 maxSize)
|
||||
void SearchSortModel::setSizeFilter(qint64 minSize, qint64 maxSize)
|
||||
{
|
||||
m_minSize = std::max(static_cast<qint64>(0), minSize);
|
||||
m_maxSize = std::max(static_cast<qint64>(-1), maxSize);
|
||||
minSize = std::max<qint64>(0, minSize);
|
||||
maxSize = std::max<qint64>(-1, maxSize);
|
||||
|
||||
if ((m_minSize == minSize) && (m_maxSize == maxSize))
|
||||
return;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
m_minSize = minSize;
|
||||
m_maxSize = maxSize;
|
||||
endFilterChange(Direction::Rows);
|
||||
#else
|
||||
m_minSize = minSize;
|
||||
m_maxSize = maxSize;
|
||||
invalidateRowsFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
void SearchSortModel::setSeedsFilter(const int minSeeds, const int maxSeeds)
|
||||
void SearchSortModel::setSeedsFilter(int minSeeds, int maxSeeds)
|
||||
{
|
||||
m_minSeeds = std::max(0, minSeeds);
|
||||
m_maxSeeds = std::max(-1, maxSeeds);
|
||||
minSeeds = std::max(0, minSeeds);
|
||||
maxSeeds = std::max(-1, maxSeeds);
|
||||
|
||||
if ((m_minSeeds == minSeeds) && (m_maxSeeds == maxSeeds))
|
||||
return;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
m_minSeeds = minSeeds;
|
||||
m_maxSeeds = maxSeeds;
|
||||
endFilterChange(Direction::Rows);
|
||||
#else
|
||||
m_minSeeds = minSeeds;
|
||||
m_maxSeeds = maxSeeds;
|
||||
invalidateRowsFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
void SearchSortModel::setLeechesFilter(const int minLeeches, const int maxLeeches)
|
||||
void SearchSortModel::setLeechesFilter(int minLeeches, int maxLeeches)
|
||||
{
|
||||
m_minLeeches = std::max(0, minLeeches);
|
||||
m_maxLeeches = std::max(-1, maxLeeches);
|
||||
minLeeches = std::max(0, minLeeches);
|
||||
maxLeeches = std::max(-1, maxLeeches);
|
||||
|
||||
if ((m_minLeeches == minLeeches) && (m_maxLeeches == maxLeeches))
|
||||
return;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
m_minLeeches = minLeeches;
|
||||
m_maxLeeches = maxLeeches;
|
||||
endFilterChange(Direction::Rows);
|
||||
#else
|
||||
m_minLeeches = minLeeches;
|
||||
m_maxLeeches = maxLeeches;
|
||||
invalidateRowsFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SearchSortModel::isNameFilterEnabled() const
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2013 sledgehammer999 <hammered999@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
||||
@@ -134,6 +134,11 @@ void ShutdownConfirmDialog::initText()
|
||||
okButton->setText(tr("&Hibernate Now"));
|
||||
setWindowTitle(tr("Hibernate confirmation"));
|
||||
break;
|
||||
case ShutdownDialogAction::Reboot:
|
||||
m_msg = tr("The computer is going to reboot.");
|
||||
okButton->setText(tr("&Reboot Now"));
|
||||
setWindowTitle(tr("Reboot confirmation"));
|
||||
break;
|
||||
}
|
||||
|
||||
m_msg += u'\n';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2017, 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2017-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -32,11 +32,12 @@
|
||||
#include <QPushButton>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "torrentsharelimitswidget.h"
|
||||
#include "ui_torrentcategorydialog.h"
|
||||
|
||||
TorrentCategoryDialog::TorrentCategoryDialog(QWidget *parent)
|
||||
: QDialog {parent}
|
||||
: QDialog(parent)
|
||||
, m_ui {new Ui::TorrentCategoryDialog}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
@@ -56,6 +57,15 @@ TorrentCategoryDialog::TorrentCategoryDialog(QWidget *parent)
|
||||
|
||||
connect(m_ui->textCategoryName, &QLineEdit::textChanged, this, &TorrentCategoryDialog::categoryNameChanged);
|
||||
connect(m_ui->comboUseDownloadPath, &QComboBox::currentIndexChanged, this, &TorrentCategoryDialog::useDownloadPathChanged);
|
||||
|
||||
resetShareLimitsWidgetDefaults();
|
||||
}
|
||||
|
||||
TorrentCategoryDialog::TorrentCategoryDialog(QWidget *parent, const QString &categoryName, const BitTorrent::CategoryOptions &categoryOptions)
|
||||
: TorrentCategoryDialog(parent)
|
||||
{
|
||||
setCategoryName(categoryName);
|
||||
setCategoryOptions(categoryOptions);
|
||||
}
|
||||
|
||||
TorrentCategoryDialog::~TorrentCategoryDialog()
|
||||
@@ -73,8 +83,7 @@ QString TorrentCategoryDialog::createCategory(QWidget *parent, const QString &pa
|
||||
newCategoryName += u'/';
|
||||
newCategoryName += tr("New Category");
|
||||
|
||||
TorrentCategoryDialog dialog {parent};
|
||||
dialog.setCategoryName(newCategoryName);
|
||||
TorrentCategoryDialog dialog {parent, newCategoryName, {}};
|
||||
while (dialog.exec() == TorrentCategoryDialog::Accepted)
|
||||
{
|
||||
newCategoryName = dialog.categoryName();
|
||||
@@ -110,14 +119,12 @@ void TorrentCategoryDialog::editCategory(QWidget *parent, const QString &categor
|
||||
|
||||
Q_ASSERT(Session::instance()->categories().contains(categoryName));
|
||||
|
||||
auto *dialog = new TorrentCategoryDialog(parent);
|
||||
auto *dialog = new TorrentCategoryDialog(parent, categoryName, Session::instance()->categoryOptions(categoryName));
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dialog->setCategoryNameEditable(false);
|
||||
dialog->setCategoryName(categoryName);
|
||||
dialog->setCategoryOptions(Session::instance()->categoryOptions(categoryName));
|
||||
connect(dialog, &TorrentCategoryDialog::accepted, parent, [dialog, categoryName]()
|
||||
{
|
||||
Session::instance()->editCategory(categoryName, dialog->categoryOptions());
|
||||
Session::instance()->setCategoryOptions(categoryName, dialog->categoryOptions());
|
||||
});
|
||||
dialog->open();
|
||||
}
|
||||
@@ -149,6 +156,11 @@ BitTorrent::CategoryOptions TorrentCategoryDialog::categoryOptions() const
|
||||
else if (m_ui->comboUseDownloadPath->currentIndex() == 2)
|
||||
categoryOptions.downloadPath = {false, {}};
|
||||
|
||||
categoryOptions.ratioLimit = m_ui->torrentShareLimitsWidget->ratioLimit().value_or(BitTorrent::DEFAULT_RATIO_LIMIT);
|
||||
categoryOptions.seedingTimeLimit = m_ui->torrentShareLimitsWidget->seedingTimeLimit().value_or(BitTorrent::DEFAULT_SEEDING_TIME_LIMIT);
|
||||
categoryOptions.inactiveSeedingTimeLimit = m_ui->torrentShareLimitsWidget->inactiveSeedingTimeLimit().value_or(BitTorrent::DEFAULT_SEEDING_TIME_LIMIT);
|
||||
categoryOptions.shareLimitAction = m_ui->torrentShareLimitsWidget->shareLimitAction().value_or(BitTorrent::ShareLimitAction::Default);
|
||||
|
||||
return categoryOptions;
|
||||
}
|
||||
|
||||
@@ -165,6 +177,11 @@ void TorrentCategoryDialog::setCategoryOptions(const BitTorrent::CategoryOptions
|
||||
m_ui->comboUseDownloadPath->setCurrentIndex(0);
|
||||
m_ui->comboDownloadPath->setSelectedPath({});
|
||||
}
|
||||
|
||||
m_ui->torrentShareLimitsWidget->setRatioLimit(categoryOptions.ratioLimit);
|
||||
m_ui->torrentShareLimitsWidget->setSeedingTimeLimit(categoryOptions.seedingTimeLimit);
|
||||
m_ui->torrentShareLimitsWidget->setInactiveSeedingTimeLimit(categoryOptions.inactiveSeedingTimeLimit);
|
||||
m_ui->torrentShareLimitsWidget->setShareLimitAction(categoryOptions.shareLimitAction);
|
||||
}
|
||||
|
||||
void TorrentCategoryDialog::categoryNameChanged(const QString &categoryName)
|
||||
@@ -177,6 +194,13 @@ void TorrentCategoryDialog::categoryNameChanged(const QString &categoryName)
|
||||
if (useDownloadPath)
|
||||
m_ui->comboDownloadPath->setPlaceholder(btSession->categoryDownloadPath(categoryName, categoryOptions()));
|
||||
|
||||
const QString parentCategoryName = BitTorrent::Session::parentCategoryName(categoryName);
|
||||
if (m_parentCategoryName != parentCategoryName)
|
||||
{
|
||||
m_parentCategoryName = parentCategoryName;
|
||||
resetShareLimitsWidgetDefaults();
|
||||
}
|
||||
|
||||
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!categoryName.isEmpty());
|
||||
}
|
||||
|
||||
@@ -195,3 +219,11 @@ void TorrentCategoryDialog::useDownloadPathChanged(const int index)
|
||||
const Path categoryPath = btSession->categoryDownloadPath(categoryName, categoryOptions());
|
||||
m_ui->comboDownloadPath->setPlaceholder(useDownloadPath ? categoryPath : Path());
|
||||
}
|
||||
|
||||
void TorrentCategoryDialog::resetShareLimitsWidgetDefaults()
|
||||
{
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
m_ui->torrentShareLimitsWidget->setDefaults((m_parentCategoryName.isEmpty() ? TorrentShareLimitsWidget::UsedDefaults::Global : TorrentShareLimitsWidget::UsedDefaults::Category)
|
||||
, btSession->categoryRatioLimit(m_parentCategoryName), btSession->categorySeedingTimeLimit(m_parentCategoryName)
|
||||
, btSession->categoryInactiveSeedingTimeLimit(m_parentCategoryName), btSession->categoryShareLimitAction(m_parentCategoryName));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2017, 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2017-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
static void editCategory(QWidget *parent, const QString &categoryName);
|
||||
|
||||
explicit TorrentCategoryDialog(QWidget *parent = nullptr);
|
||||
TorrentCategoryDialog(QWidget *parent, const QString &categoryName, const BitTorrent::CategoryOptions &categoryOptions);
|
||||
~TorrentCategoryDialog() override;
|
||||
|
||||
void setCategoryNameEditable(bool editable);
|
||||
@@ -65,6 +66,9 @@ private slots:
|
||||
void useDownloadPathChanged(int index);
|
||||
|
||||
private:
|
||||
void resetShareLimitsWidgetDefaults();
|
||||
|
||||
Ui::TorrentCategoryDialog *m_ui = nullptr;
|
||||
Path m_lastEnteredDownloadPath;
|
||||
QString m_parentCategoryName;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>493</width>
|
||||
<height>208</height>
|
||||
<height>268</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -140,6 +140,18 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="torrentShareLimitsBox">
|
||||
<property name="title">
|
||||
<string>Torrent share limits</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="torrentShareLimitsBoxLayout">
|
||||
<item>
|
||||
<widget class="TorrentShareLimitsWidget" name="torrentShareLimitsWidget" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
@@ -172,6 +184,12 @@
|
||||
<header>gui/fspathedit.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>TorrentShareLimitsWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/torrentsharelimitswidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
||||
@@ -112,7 +112,7 @@ QString TorrentContentModelItem::displayData(const int column) const
|
||||
case BitTorrent::DownloadPriority::Mixed:
|
||||
return tr("Mixed", "Mixed (priorities");
|
||||
case BitTorrent::DownloadPriority::Ignored:
|
||||
return tr("Not downloaded");
|
||||
return tr("Do not download", "Do not download (priority)");
|
||||
case BitTorrent::DownloadPriority::High:
|
||||
return tr("High", "High (priority)");
|
||||
case BitTorrent::DownloadPriority::Maximum:
|
||||
|
||||
@@ -492,14 +492,14 @@ void TorrentContentWidget::openItem(const QModelIndex &index) const
|
||||
Utils::Gui::openPath(getFullPath(index));
|
||||
}
|
||||
|
||||
void TorrentContentWidget::openParentFolder(const QModelIndex &index) const
|
||||
void TorrentContentWidget::openParentFolder(const QModelIndex &index)
|
||||
{
|
||||
const Path path = getFullPath(index);
|
||||
m_model->contentHandler()->flushCache(); // Flush data
|
||||
#ifdef Q_OS_MACOS
|
||||
MacUtils::openFiles({path});
|
||||
#else
|
||||
Utils::Gui::openFolderSelect(path);
|
||||
Utils::Gui::openFolderSelect(path, this);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ private:
|
||||
void displayColumnHeaderMenu();
|
||||
void displayContextMenu();
|
||||
void openItem(const QModelIndex &index) const;
|
||||
void openParentFolder(const QModelIndex &index) const;
|
||||
void openParentFolder(const QModelIndex &index);
|
||||
void openSelectedFile();
|
||||
void renameSelectedFile();
|
||||
void applyPriorities(BitTorrent::DownloadPriority priority);
|
||||
|
||||
@@ -31,13 +31,17 @@
|
||||
|
||||
#include "torrentcreatordialog.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QCloseEvent>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QMimeData>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrentcreator.h"
|
||||
#include "base/bittorrent/torrentdescriptor.h"
|
||||
#include "base/global.h"
|
||||
#include "base/utils/fs.h"
|
||||
@@ -57,6 +61,37 @@ namespace
|
||||
#else
|
||||
const QFileDialog::Options FILE_DIALOG_OPTIONS {};
|
||||
#endif
|
||||
|
||||
class PieceCalculationThread final : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(PieceCalculationThread)
|
||||
|
||||
public:
|
||||
using CalcFunc = std::function<int ()>;
|
||||
|
||||
explicit PieceCalculationThread(CalcFunc calc, QObject *parent = nullptr)
|
||||
: QThread(parent)
|
||||
, m_calc {std::move(calc)}
|
||||
{
|
||||
}
|
||||
|
||||
~PieceCalculationThread() override
|
||||
{
|
||||
wait();
|
||||
}
|
||||
|
||||
signals:
|
||||
void resultReady(int pieces);
|
||||
|
||||
private:
|
||||
void run() override
|
||||
{
|
||||
emit resultReady(m_calc());
|
||||
}
|
||||
|
||||
CalcFunc m_calc;
|
||||
};
|
||||
}
|
||||
|
||||
TorrentCreatorDialog::TorrentCreatorDialog(QWidget *parent, const Path &defaultPath)
|
||||
@@ -98,7 +133,7 @@ TorrentCreatorDialog::TorrentCreatorDialog(QWidget *parent, const Path &defaultP
|
||||
connect(m_ui->addFolderButton, &QPushButton::clicked, this, &TorrentCreatorDialog::onAddFolderButtonClicked);
|
||||
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &TorrentCreatorDialog::onCreateButtonClicked);
|
||||
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(m_ui->buttonCalcTotalPieces, &QPushButton::clicked, this, &TorrentCreatorDialog::updatePiecesCount);
|
||||
connect(m_ui->buttonCalcTotalPieces, &QPushButton::clicked, this, &TorrentCreatorDialog::onCalculatePiecesButtonClicked);
|
||||
connect(m_ui->checkStartSeeding, &QCheckBox::clicked, m_ui->checkIgnoreShareLimits, &QWidget::setEnabled);
|
||||
|
||||
loadSettings();
|
||||
@@ -192,6 +227,41 @@ void TorrentCreatorDialog::dragEnterEvent(QDragEnterEvent *event)
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
||||
void TorrentCreatorDialog::onCalculatePiecesButtonClicked()
|
||||
{
|
||||
m_ui->buttonCalcTotalPieces->setEnabled(false);
|
||||
m_ui->labelTotalPieces->setText(tr("Calculating..."));
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
PieceCalculationThread::CalcFunc calc = [path = m_ui->textInputPath->selectedPath()
|
||||
, pieceSize = getPieceSize()
|
||||
, torrentFormat = getTorrentFormat()]() -> int
|
||||
{
|
||||
return BitTorrent::TorrentCreator::calculateTotalPieces(path, pieceSize, torrentFormat);
|
||||
};
|
||||
#else
|
||||
PieceCalculationThread::CalcFunc calc = [path = m_ui->textInputPath->selectedPath()
|
||||
, pieceSize = getPieceSize()
|
||||
, isAlignmentOptimized = m_ui->checkOptimizeAlignment->isChecked()
|
||||
, paddedFileSizeLimit = getPaddedFileSizeLimit()]() -> int
|
||||
{
|
||||
return BitTorrent::TorrentCreator::calculateTotalPieces(path, pieceSize, isAlignmentOptimized, paddedFileSizeLimit);
|
||||
};
|
||||
#endif
|
||||
|
||||
// since the calculation (in libtorrent) cannot be interrupted, always let it run to completion and
|
||||
// not managed by `parent`
|
||||
auto *thread = new PieceCalculationThread(std::move(calc), nullptr);
|
||||
thread->setObjectName("PieceCalculationThread thread");
|
||||
connect(thread, &PieceCalculationThread::finished, thread, &QObject::deleteLater);
|
||||
connect(thread, &PieceCalculationThread::resultReady, this, [this](const int pieces)
|
||||
{
|
||||
m_ui->labelTotalPieces->setText(QString::number(pieces));
|
||||
m_ui->buttonCalcTotalPieces->setEnabled(true);
|
||||
});
|
||||
thread->start();
|
||||
}
|
||||
|
||||
// Main function that create a .torrent file
|
||||
void TorrentCreatorDialog::onCreateButtonClicked()
|
||||
{
|
||||
@@ -277,9 +347,9 @@ void TorrentCreatorDialog::handleCreationSuccess(const BitTorrent::TorrentCreato
|
||||
params.skipChecking = true;
|
||||
if (m_ui->checkIgnoreShareLimits->isChecked())
|
||||
{
|
||||
params.ratioLimit = BitTorrent::Torrent::NO_RATIO_LIMIT;
|
||||
params.seedingTimeLimit = BitTorrent::Torrent::NO_SEEDING_TIME_LIMIT;
|
||||
params.inactiveSeedingTimeLimit = BitTorrent::Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT;
|
||||
params.ratioLimit = BitTorrent::NO_RATIO_LIMIT;
|
||||
params.seedingTimeLimit = BitTorrent::NO_SEEDING_TIME_LIMIT;
|
||||
params.inactiveSeedingTimeLimit = BitTorrent::NO_SEEDING_TIME_LIMIT;
|
||||
}
|
||||
params.useAutoTMM = false; // otherwise if it is on by default, it will overwrite `savePath` to the default save path
|
||||
|
||||
@@ -298,20 +368,6 @@ void TorrentCreatorDialog::updateProgressBar(int progress)
|
||||
m_ui->progressBar->setValue(progress);
|
||||
}
|
||||
|
||||
void TorrentCreatorDialog::updatePiecesCount()
|
||||
{
|
||||
const Path path = m_ui->textInputPath->selectedPath();
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const int count = BitTorrent::TorrentCreator::calculateTotalPieces(
|
||||
path, getPieceSize(), getTorrentFormat());
|
||||
#else
|
||||
const bool isAlignmentOptimized = m_ui->checkOptimizeAlignment->isChecked();
|
||||
const int count = BitTorrent::TorrentCreator::calculateTotalPieces(path
|
||||
, getPieceSize(), isAlignmentOptimized, getPaddedFileSizeLimit());
|
||||
#endif
|
||||
m_ui->labelTotalPieces->setText(QString::number(count));
|
||||
}
|
||||
|
||||
void TorrentCreatorDialog::setInteractionEnabled(const bool enabled) const
|
||||
{
|
||||
m_ui->textInputPath->setEnabled(enabled);
|
||||
@@ -382,3 +438,5 @@ void TorrentCreatorDialog::loadSettings()
|
||||
if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid())
|
||||
resize(dialogSize);
|
||||
}
|
||||
|
||||
#include "torrentcreatordialog.moc"
|
||||
|
||||
@@ -33,10 +33,15 @@
|
||||
#include <QDialog>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include "base/bittorrent/torrentcreator.h"
|
||||
#include "base/path.h"
|
||||
#include "base/settingvalue.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class TorrentFormat;
|
||||
struct TorrentCreatorResult;
|
||||
}
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class TorrentCreatorDialog;
|
||||
@@ -54,7 +59,7 @@ public:
|
||||
|
||||
private slots:
|
||||
void updateProgressBar(int progress);
|
||||
void updatePiecesCount();
|
||||
void onCalculatePiecesButtonClicked();
|
||||
void onCreateButtonClicked();
|
||||
void onAddFileButtonClicked();
|
||||
void onAddFolderButtonClicked();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2024-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2020 thalieht
|
||||
* Copyright (C) 2011 Christian Kandeler
|
||||
* Copyright (C) 2011 Christophe Dumez <chris@qbittorrent.org>
|
||||
@@ -289,7 +289,12 @@ TorrentOptionsDialog::TorrentOptionsDialog(QWidget *parent, const QList<BitTorre
|
||||
, this, &TorrentOptionsDialog::handleDownSpeedLimitChanged);
|
||||
}
|
||||
|
||||
m_ui->torrentShareLimitsWidget->setDefaultLimits(session->globalMaxRatio(), session->globalMaxSeedingMinutes(), session->globalMaxInactiveSeedingMinutes());
|
||||
if (m_allSameCategory)
|
||||
{
|
||||
m_ui->torrentShareLimitsWidget->setDefaults((firstTorrentCategory.isEmpty() ? TorrentShareLimitsWidget::UsedDefaults::Global : TorrentShareLimitsWidget::UsedDefaults::Category)
|
||||
, session->categoryRatioLimit(firstTorrentCategory), session->categorySeedingTimeLimit(firstTorrentCategory)
|
||||
, session->categoryInactiveSeedingTimeLimit(firstTorrentCategory), session->categoryShareLimitAction(firstTorrentCategory));
|
||||
}
|
||||
if (allSameRatio)
|
||||
m_ui->torrentShareLimitsWidget->setRatioLimit(firstTorrentRatio);
|
||||
if (allSameSeedingTime)
|
||||
@@ -477,30 +482,37 @@ void TorrentOptionsDialog::accept()
|
||||
|
||||
void TorrentOptionsDialog::handleCategoryChanged([[maybe_unused]] const int index)
|
||||
{
|
||||
if (m_ui->checkAutoTMM->checkState() == Qt::Checked)
|
||||
{
|
||||
if (!m_allSameCategory && (m_ui->comboCategory->currentIndex() == 0))
|
||||
{
|
||||
m_ui->savePath->setSelectedPath({});
|
||||
}
|
||||
else
|
||||
{
|
||||
const Path savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->comboCategory->currentText());
|
||||
m_ui->savePath->setSelectedPath(savePath);
|
||||
const Path downloadPath = BitTorrent::Session::instance()->categoryDownloadPath(m_ui->comboCategory->currentText());
|
||||
m_ui->downloadPath->setSelectedPath(downloadPath);
|
||||
m_ui->checkUseDownloadPath->setChecked(!downloadPath.isEmpty());
|
||||
}
|
||||
}
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
|
||||
if (!m_allSameCategory && (m_ui->comboCategory->currentIndex() == 0))
|
||||
{
|
||||
if (m_ui->checkAutoTMM->checkState() == Qt::Checked)
|
||||
m_ui->savePath->setSelectedPath({});
|
||||
|
||||
m_ui->comboCategory->clearEditText();
|
||||
m_ui->comboCategory->lineEdit()->setPlaceholderText(m_currentCategoriesString);
|
||||
|
||||
m_ui->torrentShareLimitsWidget->setDefaults(TorrentShareLimitsWidget::UsedDefaults::Global
|
||||
, BitTorrent::DEFAULT_RATIO_LIMIT, BitTorrent::DEFAULT_SEEDING_TIME_LIMIT
|
||||
, BitTorrent::DEFAULT_SEEDING_TIME_LIMIT, BitTorrent::ShareLimitAction::Default);
|
||||
}
|
||||
else
|
||||
{
|
||||
const QString categoryName = m_ui->comboCategory->currentText();
|
||||
|
||||
if (m_ui->checkAutoTMM->checkState() == Qt::Checked)
|
||||
{
|
||||
const Path savePath = btSession->categorySavePath(categoryName);
|
||||
m_ui->savePath->setSelectedPath(savePath);
|
||||
const Path downloadPath = btSession->categoryDownloadPath(categoryName);
|
||||
m_ui->downloadPath->setSelectedPath(downloadPath);
|
||||
m_ui->checkUseDownloadPath->setChecked(!downloadPath.isEmpty());
|
||||
}
|
||||
|
||||
m_ui->comboCategory->lineEdit()->setPlaceholderText(QString());
|
||||
m_ui->torrentShareLimitsWidget->setDefaults((categoryName.isEmpty() ? TorrentShareLimitsWidget::UsedDefaults::Global : TorrentShareLimitsWidget::UsedDefaults::Category)
|
||||
, btSession->categoryRatioLimit(categoryName), btSession->categorySeedingTimeLimit(categoryName)
|
||||
, btSession->categoryInactiveSeedingTimeLimit(categoryName), btSession->categoryShareLimitAction(categoryName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2024-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2020 thalieht
|
||||
* Copyright (C) 2011 Christian Kandeler
|
||||
* Copyright (C) 2011 Christophe Dumez <chris@qbittorrent.org>
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "base/bittorrent/sharelimitaction.h"
|
||||
#include "base/bittorrent/sharelimits.h"
|
||||
#include "base/path.h"
|
||||
#include "base/settingvalue.h"
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
#include "torrentsharelimitswidget.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "ui_torrentsharelimitswidget.h"
|
||||
|
||||
@@ -50,6 +52,29 @@ namespace
|
||||
RemoveWithContentActionIndex,
|
||||
SuperSeedingActionIndex
|
||||
};
|
||||
|
||||
QString shareLimitActionName(const BitTorrent::ShareLimitAction shareLimitAction)
|
||||
{
|
||||
switch (shareLimitAction)
|
||||
{
|
||||
case BitTorrent::ShareLimitAction::Stop:
|
||||
return TorrentShareLimitsWidget::tr("Stop torrent");
|
||||
|
||||
case BitTorrent::ShareLimitAction::Remove:
|
||||
return TorrentShareLimitsWidget::tr("Remove torrent");
|
||||
|
||||
case BitTorrent::ShareLimitAction::RemoveWithContent:
|
||||
return TorrentShareLimitsWidget::tr("Remove torrent and its content");
|
||||
|
||||
case BitTorrent::ShareLimitAction::EnableSuperSeeding:
|
||||
return TorrentShareLimitsWidget::tr("Enable super seeding for torrent");
|
||||
|
||||
case BitTorrent::ShareLimitAction::Default:
|
||||
return TorrentShareLimitsWidget::tr("Default");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
TorrentShareLimitsWidget::TorrentShareLimitsWidget(QWidget *parent)
|
||||
@@ -58,7 +83,32 @@ TorrentShareLimitsWidget::TorrentShareLimitsWidget(QWidget *parent)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
m_ui->comboBoxRatioMode->addItem({});
|
||||
m_ui->comboBoxRatioMode->addItem(tr("Unlimited"));
|
||||
m_ui->comboBoxRatioMode->addItem(tr("Set to"));
|
||||
m_ui->comboBoxRatioMode->setCurrentIndex(UninitializedModeIndex);
|
||||
|
||||
m_ui->comboBoxSeedingTimeMode->addItem({});
|
||||
m_ui->comboBoxSeedingTimeMode->addItem(tr("Unlimited"));
|
||||
m_ui->comboBoxSeedingTimeMode->addItem(tr("Set to"));
|
||||
m_ui->comboBoxSeedingTimeMode->setCurrentIndex(UninitializedModeIndex);
|
||||
|
||||
m_ui->comboBoxInactiveSeedingTimeMode->addItem({});
|
||||
m_ui->comboBoxInactiveSeedingTimeMode->addItem(tr("Unlimited"));
|
||||
m_ui->comboBoxInactiveSeedingTimeMode->addItem(tr("Set to"));
|
||||
m_ui->comboBoxInactiveSeedingTimeMode->setCurrentIndex(UninitializedModeIndex);
|
||||
|
||||
m_ui->comboBoxAction->addItem({});
|
||||
m_ui->comboBoxAction->addItem(shareLimitActionName(BitTorrent::ShareLimitAction::Stop));
|
||||
m_ui->comboBoxAction->addItem(shareLimitActionName(BitTorrent::ShareLimitAction::Remove));
|
||||
m_ui->comboBoxAction->addItem(shareLimitActionName(BitTorrent::ShareLimitAction::RemoveWithContent));
|
||||
m_ui->comboBoxAction->addItem(shareLimitActionName(BitTorrent::ShareLimitAction::EnableSuperSeeding));
|
||||
m_ui->comboBoxAction->setCurrentIndex(UninitializedActionIndex);
|
||||
|
||||
resetDefaultItemsText();
|
||||
|
||||
m_ui->spinBoxRatioValue->setEnabled(false);
|
||||
m_ui->spinBoxRatioValue->setMaximum(std::numeric_limits<int>::max());
|
||||
m_ui->spinBoxRatioValue->setSuffix({});
|
||||
m_ui->spinBoxRatioValue->clear();
|
||||
m_ui->spinBoxSeedingTimeValue->setEnabled(false);
|
||||
@@ -68,9 +118,25 @@ TorrentShareLimitsWidget::TorrentShareLimitsWidget(QWidget *parent)
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->setSuffix({});
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->clear();
|
||||
|
||||
connect(m_ui->comboBoxRatioMode, &QComboBox::currentIndexChanged, this, &TorrentShareLimitsWidget::refreshRatioLimitControls);
|
||||
connect(m_ui->comboBoxSeedingTimeMode, &QComboBox::currentIndexChanged, this, &TorrentShareLimitsWidget::refreshSeedingTimeLimitControls);
|
||||
connect(m_ui->comboBoxInactiveSeedingTimeMode, &QComboBox::currentIndexChanged, this, &TorrentShareLimitsWidget::refreshInactiveSeedingTimeLimitControls);
|
||||
int prevIndex = UninitializedModeIndex;
|
||||
connect(m_ui->comboBoxRatioMode, &QComboBox::currentIndexChanged, this
|
||||
, [this, prevIndex](const int currentIndex) mutable
|
||||
{
|
||||
onRatioLimitModeChanged(currentIndex, prevIndex);
|
||||
prevIndex = currentIndex;
|
||||
});
|
||||
connect(m_ui->comboBoxSeedingTimeMode, &QComboBox::currentIndexChanged, this
|
||||
, [this, prevIndex](const int currentIndex) mutable
|
||||
{
|
||||
onSeedingTimeLimitModeChanged(currentIndex, prevIndex);
|
||||
prevIndex = currentIndex;
|
||||
});
|
||||
connect(m_ui->comboBoxInactiveSeedingTimeMode, &QComboBox::currentIndexChanged, this
|
||||
, [this, prevIndex](const int currentIndex) mutable
|
||||
{
|
||||
onInactiveSeedingTimeLimitModeChanged(currentIndex, prevIndex);
|
||||
prevIndex = currentIndex;
|
||||
});
|
||||
}
|
||||
|
||||
TorrentShareLimitsWidget::~TorrentShareLimitsWidget()
|
||||
@@ -80,11 +146,11 @@ TorrentShareLimitsWidget::~TorrentShareLimitsWidget()
|
||||
|
||||
void TorrentShareLimitsWidget::setRatioLimit(const qreal ratioLimit)
|
||||
{
|
||||
if (ratioLimit == BitTorrent::Torrent::USE_GLOBAL_RATIO)
|
||||
if (ratioLimit == BitTorrent::DEFAULT_RATIO_LIMIT)
|
||||
{
|
||||
m_ui->comboBoxRatioMode->setCurrentIndex(DefaultModeIndex);
|
||||
}
|
||||
else if (ratioLimit == BitTorrent::Torrent::NO_RATIO_LIMIT)
|
||||
else if (ratioLimit == BitTorrent::NO_RATIO_LIMIT)
|
||||
{
|
||||
m_ui->comboBoxRatioMode->setCurrentIndex(UnlimitedModeIndex);
|
||||
}
|
||||
@@ -97,11 +163,11 @@ void TorrentShareLimitsWidget::setRatioLimit(const qreal ratioLimit)
|
||||
|
||||
void TorrentShareLimitsWidget::setSeedingTimeLimit(const int seedingTimeLimit)
|
||||
{
|
||||
if (seedingTimeLimit == BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME)
|
||||
if (seedingTimeLimit == BitTorrent::DEFAULT_SEEDING_TIME_LIMIT)
|
||||
{
|
||||
m_ui->comboBoxSeedingTimeMode->setCurrentIndex(DefaultModeIndex);
|
||||
}
|
||||
else if (seedingTimeLimit == BitTorrent::Torrent::NO_SEEDING_TIME_LIMIT)
|
||||
else if (seedingTimeLimit == BitTorrent::NO_SEEDING_TIME_LIMIT)
|
||||
{
|
||||
m_ui->comboBoxSeedingTimeMode->setCurrentIndex(UnlimitedModeIndex);
|
||||
}
|
||||
@@ -114,11 +180,11 @@ void TorrentShareLimitsWidget::setSeedingTimeLimit(const int seedingTimeLimit)
|
||||
|
||||
void TorrentShareLimitsWidget::setInactiveSeedingTimeLimit(const int inactiveSeedingTimeLimit)
|
||||
{
|
||||
if (inactiveSeedingTimeLimit == BitTorrent::Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME)
|
||||
if (inactiveSeedingTimeLimit == BitTorrent::DEFAULT_SEEDING_TIME_LIMIT)
|
||||
{
|
||||
m_ui->comboBoxInactiveSeedingTimeMode->setCurrentIndex(DefaultModeIndex);
|
||||
}
|
||||
else if (inactiveSeedingTimeLimit == BitTorrent::Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT)
|
||||
else if (inactiveSeedingTimeLimit == BitTorrent::NO_SEEDING_TIME_LIMIT)
|
||||
{
|
||||
m_ui->comboBoxInactiveSeedingTimeMode->setCurrentIndex(UnlimitedModeIndex);
|
||||
}
|
||||
@@ -152,25 +218,56 @@ void TorrentShareLimitsWidget::setShareLimitAction(const BitTorrent::ShareLimitA
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentShareLimitsWidget::setDefaultLimits(const qreal ratioLimit, const int seedingTimeLimit, const int inactiveSeedingTimeLimit)
|
||||
void TorrentShareLimitsWidget::setDefaults(UsedDefaults usedDefaults
|
||||
, const qreal ratioLimit, const int seedingTimeLimit
|
||||
, const int inactiveSeedingTimeLimit, const BitTorrent::ShareLimitAction action)
|
||||
{
|
||||
if (m_defaultRatioLimit != ratioLimit)
|
||||
m_defaultRatioLimit = ratioLimit;
|
||||
if (m_ui->comboBoxRatioMode->currentIndex() == DefaultModeIndex)
|
||||
{
|
||||
m_defaultRatioLimit = ratioLimit;
|
||||
refreshRatioLimitControls();
|
||||
if (m_defaultRatioLimit >= 0)
|
||||
{
|
||||
m_ui->spinBoxRatioValue->setValue(m_defaultRatioLimit);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui->spinBoxRatioValue->clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_defaultSeedingTimeLimit != seedingTimeLimit)
|
||||
m_defaultSeedingTimeLimit = seedingTimeLimit;
|
||||
if (m_ui->comboBoxSeedingTimeMode->currentIndex() == DefaultModeIndex)
|
||||
{
|
||||
m_defaultSeedingTimeLimit = seedingTimeLimit;
|
||||
refreshSeedingTimeLimitControls();
|
||||
if (m_defaultSeedingTimeLimit >= 0)
|
||||
{
|
||||
m_ui->spinBoxSeedingTimeValue->setValue(m_defaultSeedingTimeLimit);
|
||||
m_ui->spinBoxSeedingTimeValue->setSuffix(tr(" min"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui->spinBoxSeedingTimeValue->setSuffix({});
|
||||
m_ui->spinBoxSeedingTimeValue->clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_defaultInactiveSeedingTimeLimit != inactiveSeedingTimeLimit)
|
||||
m_defaultInactiveSeedingTimeLimit = inactiveSeedingTimeLimit;
|
||||
if (m_ui->comboBoxInactiveSeedingTimeMode->currentIndex() == DefaultModeIndex)
|
||||
{
|
||||
m_defaultInactiveSeedingTimeLimit = inactiveSeedingTimeLimit;
|
||||
refreshInactiveSeedingTimeLimitControls();
|
||||
if (m_defaultInactiveSeedingTimeLimit >= 0)
|
||||
{
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->setValue(m_defaultInactiveSeedingTimeLimit);
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->setSuffix(tr(" min"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->setSuffix({});
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->clear();
|
||||
}
|
||||
}
|
||||
|
||||
m_defaultShareLimitAction = action;
|
||||
m_usedDefaults = usedDefaults;
|
||||
resetDefaultItemsText();
|
||||
}
|
||||
|
||||
std::optional<qreal> TorrentShareLimitsWidget::ratioLimit() const
|
||||
@@ -178,9 +275,9 @@ std::optional<qreal> TorrentShareLimitsWidget::ratioLimit() const
|
||||
switch (m_ui->comboBoxRatioMode->currentIndex())
|
||||
{
|
||||
case DefaultModeIndex:
|
||||
return BitTorrent::Torrent::USE_GLOBAL_RATIO;
|
||||
return BitTorrent::DEFAULT_RATIO_LIMIT;
|
||||
case UnlimitedModeIndex:
|
||||
return BitTorrent::Torrent::NO_RATIO_LIMIT;
|
||||
return BitTorrent::NO_RATIO_LIMIT;
|
||||
case AssignedModeIndex:
|
||||
return m_ui->spinBoxRatioValue->value();
|
||||
default:
|
||||
@@ -193,9 +290,9 @@ std::optional<int> TorrentShareLimitsWidget::seedingTimeLimit() const
|
||||
switch (m_ui->comboBoxSeedingTimeMode->currentIndex())
|
||||
{
|
||||
case DefaultModeIndex:
|
||||
return BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME;
|
||||
return BitTorrent::DEFAULT_SEEDING_TIME_LIMIT;
|
||||
case UnlimitedModeIndex:
|
||||
return BitTorrent::Torrent::NO_SEEDING_TIME_LIMIT;
|
||||
return BitTorrent::NO_SEEDING_TIME_LIMIT;
|
||||
case AssignedModeIndex:
|
||||
return m_ui->spinBoxSeedingTimeValue->value();
|
||||
default:
|
||||
@@ -208,9 +305,9 @@ std::optional<int> TorrentShareLimitsWidget::inactiveSeedingTimeLimit() const
|
||||
switch (m_ui->comboBoxInactiveSeedingTimeMode->currentIndex())
|
||||
{
|
||||
case DefaultModeIndex:
|
||||
return BitTorrent::Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME;
|
||||
return BitTorrent::DEFAULT_SEEDING_TIME_LIMIT;
|
||||
case UnlimitedModeIndex:
|
||||
return BitTorrent::Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT;
|
||||
return BitTorrent::NO_SEEDING_TIME_LIMIT;
|
||||
case AssignedModeIndex:
|
||||
return m_ui->spinBoxInactiveSeedingTimeValue->value();
|
||||
default:
|
||||
@@ -237,68 +334,97 @@ std::optional<BitTorrent::ShareLimitAction> TorrentShareLimitsWidget::shareLimit
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentShareLimitsWidget::refreshRatioLimitControls()
|
||||
void TorrentShareLimitsWidget::onRatioLimitModeChanged(const int currentIndex, const int previousIndex)
|
||||
{
|
||||
const auto index = m_ui->comboBoxRatioMode->currentIndex();
|
||||
m_ui->spinBoxRatioValue->setEnabled(currentIndex == AssignedModeIndex);
|
||||
|
||||
m_ui->spinBoxRatioValue->setEnabled(index == AssignedModeIndex);
|
||||
if (index == AssignedModeIndex)
|
||||
if (previousIndex == AssignedModeIndex)
|
||||
m_ratioLimit = m_ui->spinBoxRatioValue->value();
|
||||
|
||||
if (currentIndex == AssignedModeIndex)
|
||||
{
|
||||
m_ui->spinBoxRatioValue->setValue(m_ratioLimit);
|
||||
}
|
||||
else if ((index == DefaultModeIndex) && (m_defaultRatioLimit >= 0))
|
||||
else if ((currentIndex == DefaultModeIndex) && (m_defaultRatioLimit >= 0))
|
||||
{
|
||||
m_ui->spinBoxRatioValue->setValue(m_defaultRatioLimit);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ratioLimit = m_ui->spinBoxRatioValue->value();
|
||||
m_ui->spinBoxRatioValue->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentShareLimitsWidget::refreshSeedingTimeLimitControls()
|
||||
void TorrentShareLimitsWidget::onSeedingTimeLimitModeChanged(const int currentIndex, const int previousIndex)
|
||||
{
|
||||
const auto index = m_ui->comboBoxSeedingTimeMode->currentIndex();
|
||||
m_ui->spinBoxSeedingTimeValue->setEnabled(currentIndex == AssignedModeIndex);
|
||||
|
||||
m_ui->spinBoxSeedingTimeValue->setEnabled(index == AssignedModeIndex);
|
||||
if (index == AssignedModeIndex)
|
||||
if (previousIndex == AssignedModeIndex)
|
||||
m_seedingTimeLimit = m_ui->spinBoxSeedingTimeValue->value();
|
||||
|
||||
if (currentIndex == AssignedModeIndex)
|
||||
{
|
||||
m_ui->spinBoxSeedingTimeValue->setValue(m_seedingTimeLimit);
|
||||
m_ui->spinBoxSeedingTimeValue->setSuffix(tr(" min"));
|
||||
}
|
||||
else if ((index == DefaultModeIndex) && (m_defaultSeedingTimeLimit >= 0))
|
||||
else if ((currentIndex == DefaultModeIndex) && (m_defaultSeedingTimeLimit >= 0))
|
||||
{
|
||||
m_ui->spinBoxSeedingTimeValue->setValue(m_defaultSeedingTimeLimit);
|
||||
m_ui->spinBoxSeedingTimeValue->setSuffix(tr(" min"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_seedingTimeLimit = m_ui->spinBoxSeedingTimeValue->value();
|
||||
m_ui->spinBoxSeedingTimeValue->setSuffix({});
|
||||
m_ui->spinBoxSeedingTimeValue->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentShareLimitsWidget::refreshInactiveSeedingTimeLimitControls()
|
||||
void TorrentShareLimitsWidget::onInactiveSeedingTimeLimitModeChanged(const int currentIndex, const int previousIndex)
|
||||
{
|
||||
const auto index = m_ui->comboBoxInactiveSeedingTimeMode->currentIndex();
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->setEnabled(currentIndex == AssignedModeIndex);
|
||||
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->setEnabled(index == AssignedModeIndex);
|
||||
if (index == AssignedModeIndex)
|
||||
if (previousIndex == AssignedModeIndex)
|
||||
m_inactiveSeedingTimeLimit = m_ui->spinBoxInactiveSeedingTimeValue->value();
|
||||
|
||||
if (currentIndex == AssignedModeIndex)
|
||||
{
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->setValue(m_inactiveSeedingTimeLimit);
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->setSuffix(tr(" min"));
|
||||
}
|
||||
else if ((index == DefaultModeIndex) && (m_defaultInactiveSeedingTimeLimit >= 0))
|
||||
else if ((currentIndex == DefaultModeIndex) && (m_defaultInactiveSeedingTimeLimit >= 0))
|
||||
{
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->setValue(m_defaultInactiveSeedingTimeLimit);
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->setSuffix(tr(" min"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_inactiveSeedingTimeLimit = m_ui->spinBoxInactiveSeedingTimeValue->value();
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->setSuffix({});
|
||||
m_ui->spinBoxInactiveSeedingTimeValue->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentShareLimitsWidget::resetDefaultItemsText()
|
||||
{
|
||||
if (m_usedDefaults == UsedDefaults::Global)
|
||||
{
|
||||
m_ui->comboBoxRatioMode->setItemText(DefaultModeIndex, tr("Default"));
|
||||
m_ui->comboBoxSeedingTimeMode->setItemText(DefaultModeIndex, tr("Default"));
|
||||
m_ui->comboBoxInactiveSeedingTimeMode->setItemText(DefaultModeIndex, tr("Default"));
|
||||
|
||||
m_ui->comboBoxAction->setItemText(DefaultActionIndex
|
||||
, (m_defaultShareLimitAction == BitTorrent::ShareLimitAction::Default)
|
||||
? tr("Default")
|
||||
: tr("Default (%1)", "Default (share limit action)").arg(shareLimitActionName(m_defaultShareLimitAction)));
|
||||
}
|
||||
else // TorrentOptions
|
||||
{
|
||||
m_ui->comboBoxRatioMode->setItemText(DefaultModeIndex, tr("From category"));
|
||||
m_ui->comboBoxSeedingTimeMode->setItemText(DefaultModeIndex, tr("From category"));
|
||||
m_ui->comboBoxInactiveSeedingTimeMode->setItemText(DefaultModeIndex, tr("From category"));
|
||||
|
||||
m_ui->comboBoxAction->setItemText(DefaultActionIndex
|
||||
, (m_defaultShareLimitAction == BitTorrent::ShareLimitAction::Default)
|
||||
? tr("From category")
|
||||
: tr("From category (%1)", "From category (share limit action)").arg(shareLimitActionName(m_defaultShareLimitAction)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "base/bittorrent/sharelimitaction.h"
|
||||
#include "base/bittorrent/sharelimits.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
@@ -45,6 +45,12 @@ class TorrentShareLimitsWidget final : public QWidget
|
||||
Q_DISABLE_COPY_MOVE(TorrentShareLimitsWidget)
|
||||
|
||||
public:
|
||||
enum class UsedDefaults
|
||||
{
|
||||
Global,
|
||||
Category
|
||||
};
|
||||
|
||||
explicit TorrentShareLimitsWidget(QWidget *parent = nullptr);
|
||||
~TorrentShareLimitsWidget() override;
|
||||
|
||||
@@ -53,7 +59,8 @@ public:
|
||||
void setInactiveSeedingTimeLimit(int inactiveSeedingTimeLimit);
|
||||
void setShareLimitAction(BitTorrent::ShareLimitAction action);
|
||||
|
||||
void setDefaultLimits(qreal ratioLimit, int seedingTimeLimit, int inactiveSeedingTimeLimit);
|
||||
void setDefaults(UsedDefaults usedDefaults, qreal ratioLimit, int seedingTimeLimit
|
||||
, int inactiveSeedingTimeLimit, BitTorrent::ShareLimitAction action);
|
||||
|
||||
std::optional<qreal> ratioLimit() const;
|
||||
std::optional<int> seedingTimeLimit() const;
|
||||
@@ -61,9 +68,10 @@ public:
|
||||
std::optional<BitTorrent::ShareLimitAction> shareLimitAction() const;
|
||||
|
||||
private:
|
||||
void refreshRatioLimitControls();
|
||||
void refreshSeedingTimeLimitControls();
|
||||
void refreshInactiveSeedingTimeLimitControls();
|
||||
void onRatioLimitModeChanged(int currentIndex, int previousIndex);
|
||||
void onSeedingTimeLimitModeChanged(int currentIndex, int previousIndex);
|
||||
void onInactiveSeedingTimeLimitModeChanged(int currentIndex, int previousIndex);
|
||||
void resetDefaultItemsText();
|
||||
|
||||
Ui::TorrentShareLimitsWidget *m_ui = nullptr;
|
||||
|
||||
@@ -74,4 +82,7 @@ private:
|
||||
int m_defaultSeedingTimeLimit = -1;
|
||||
int m_defaultInactiveSeedingTimeLimit = -1;
|
||||
qreal m_defaultRatioLimit = -1;
|
||||
BitTorrent::ShareLimitAction m_defaultShareLimitAction = BitTorrent::ShareLimitAction::Default;
|
||||
|
||||
UsedDefaults m_usedDefaults = UsedDefaults::Global;
|
||||
};
|
||||
|
||||
@@ -21,26 +21,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="comboBoxRatioMode">
|
||||
<property name="currentIndex">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Default</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Unlimited</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Set to</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="comboBoxRatioMode"/>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QDoubleSpinBox" name="spinBoxRatioValue">
|
||||
@@ -63,26 +44,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="comboBoxSeedingTimeMode">
|
||||
<property name="currentIndex">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Default</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Unlimited</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Set to</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="comboBoxSeedingTimeMode"/>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QSpinBox" name="spinBoxSeedingTimeValue">
|
||||
@@ -108,26 +70,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="comboBoxInactiveSeedingTimeMode">
|
||||
<property name="currentIndex">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Default</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Unlimited</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Set to</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="comboBoxInactiveSeedingTimeMode"/>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QSpinBox" name="spinBoxInactiveSeedingTimeValue">
|
||||
@@ -157,36 +100,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboBoxAction">
|
||||
<property name="currentIndex">
|
||||
<number>-1</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Default</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Stop torrent</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Remove torrent</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Remove torrent and its content</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Enable super seeding for torrent</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="comboBoxAction"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="actionLayoutSpacer">
|
||||
|
||||
@@ -267,7 +267,7 @@ TrackerListModel::TrackerListModel(BitTorrent::Session *btSession, QObject *pare
|
||||
if (torrent == m_torrent)
|
||||
onTrackersRemoved(deletedTrackers);
|
||||
});
|
||||
connect(m_btSession, &BitTorrent::Session::trackersChanged, this
|
||||
connect(m_btSession, &BitTorrent::Session::trackersReset, this
|
||||
, [this](BitTorrent::Torrent *torrent)
|
||||
{
|
||||
if (torrent == m_torrent)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2016-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -324,17 +324,17 @@ void CategoryFilterModel::categoryAdded(const QString &categoryName)
|
||||
{
|
||||
CategoryModelItem *parent = m_rootItem;
|
||||
|
||||
if (m_isSubcategoriesEnabled)
|
||||
{
|
||||
QStringList expanded = BitTorrent::Session::expandCategory(categoryName);
|
||||
if (expanded.count() > 1)
|
||||
parent = findItem(expanded[expanded.count() - 2]);
|
||||
}
|
||||
const QStringList expanded = BitTorrent::Session::expandCategory(categoryName);
|
||||
if (expanded.count() > 1)
|
||||
parent = findItem(expanded[expanded.count() - 2]);
|
||||
|
||||
Q_ASSERT(parent);
|
||||
if (!parent) [[unlikely]]
|
||||
return;
|
||||
|
||||
const int row = parent->childCount();
|
||||
beginInsertRows(index(parent), row, row);
|
||||
new CategoryModelItem(
|
||||
parent, m_isSubcategoriesEnabled ? shortName(categoryName) : categoryName);
|
||||
new CategoryModelItem(parent, shortName(categoryName));
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
@@ -426,7 +426,6 @@ void CategoryFilterModel::populate()
|
||||
|
||||
const auto *session = BitTorrent::Session::instance();
|
||||
const auto torrents = session->torrents();
|
||||
m_isSubcategoriesEnabled = session->isSubcategoriesEnabled();
|
||||
|
||||
// All torrents
|
||||
m_rootItem->addChild(CategoryModelItem::UID_ALL
|
||||
@@ -440,31 +439,19 @@ void CategoryFilterModel::populate()
|
||||
, new CategoryModelItem(nullptr, tr("Uncategorized"), torrentsCount));
|
||||
|
||||
using BitTorrent::Torrent;
|
||||
if (m_isSubcategoriesEnabled)
|
||||
for (const QString &categoryName : asConst(session->categories()))
|
||||
{
|
||||
for (const QString &categoryName : asConst(session->categories()))
|
||||
CategoryModelItem *parent = m_rootItem;
|
||||
for (const QString &subcat : asConst(BitTorrent::Session::expandCategory(categoryName)))
|
||||
{
|
||||
CategoryModelItem *parent = m_rootItem;
|
||||
for (const QString &subcat : asConst(BitTorrent::Session::expandCategory(categoryName)))
|
||||
const QString subcatName = shortName(subcat);
|
||||
if (!parent->hasChild(subcatName))
|
||||
{
|
||||
const QString subcatName = shortName(subcat);
|
||||
if (!parent->hasChild(subcatName))
|
||||
{
|
||||
const int torrentsCount = std::ranges::count_if(torrents
|
||||
, [&subcat](const Torrent *torrent) { return torrent->category() == subcat; });
|
||||
new CategoryModelItem(parent, subcatName, torrentsCount);
|
||||
}
|
||||
parent = parent->child(subcatName);
|
||||
const int torrentsCount = std::ranges::count_if(torrents
|
||||
, [&subcat](const Torrent *torrent) { return torrent->category() == subcat; });
|
||||
new CategoryModelItem(parent, subcatName, torrentsCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const QString &categoryName : asConst(session->categories()))
|
||||
{
|
||||
const int torrentsCount = std::ranges::count_if(torrents
|
||||
, [&categoryName](const Torrent *torrent) { return torrent->belongsToCategory(categoryName); });
|
||||
new CategoryModelItem(m_rootItem, categoryName, torrentsCount);
|
||||
parent = parent->child(subcatName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -474,9 +461,6 @@ CategoryModelItem *CategoryFilterModel::findItem(const QString &fullName) const
|
||||
if (fullName.isEmpty())
|
||||
return m_rootItem->childAt(1); // "Uncategorized" item
|
||||
|
||||
if (!m_isSubcategoriesEnabled)
|
||||
return m_rootItem->child(fullName);
|
||||
|
||||
CategoryModelItem *item = m_rootItem;
|
||||
for (const QString &subcat : asConst(BitTorrent::Session::expandCategory(fullName)))
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2016-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -72,6 +72,5 @@ private:
|
||||
QModelIndex index(CategoryModelItem *item) const;
|
||||
CategoryModelItem *findItem(const QString &fullName) const;
|
||||
|
||||
bool m_isSubcategoriesEnabled = false;
|
||||
CategoryModelItem *m_rootItem = nullptr;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2016-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -72,9 +72,6 @@ CategoryFilterWidget::CategoryFilterWidget(QWidget *parent)
|
||||
#ifdef Q_OS_MACOS
|
||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
#endif
|
||||
m_defaultIndentation = indentation();
|
||||
if (!BitTorrent::Session::instance()->isSubcategoriesEnabled())
|
||||
setIndentation(0);
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
sortByColumn(0, Qt::AscendingOrder);
|
||||
setCurrentIndex(model()->index(0, 0));
|
||||
@@ -84,7 +81,18 @@ CategoryFilterWidget::CategoryFilterWidget(QWidget *parent)
|
||||
connect(this, &QWidget::customContextMenuRequested, this, &CategoryFilterWidget::showMenu);
|
||||
connect(selectionModel(), &QItemSelectionModel::currentRowChanged
|
||||
, this, &CategoryFilterWidget::onCurrentRowChanged);
|
||||
connect(model(), &QAbstractItemModel::modelReset, this, &CategoryFilterWidget::callUpdateGeometry);
|
||||
connect(model(), &QAbstractItemModel::rowsRemoved, this, [this]
|
||||
{
|
||||
adjustIndentation();
|
||||
updateGeometry();
|
||||
});
|
||||
connect(model(), &QAbstractItemModel::modelReset, this, [this]
|
||||
{
|
||||
adjustIndentation();
|
||||
updateGeometry();
|
||||
});
|
||||
|
||||
adjustIndentation();
|
||||
}
|
||||
|
||||
QString CategoryFilterWidget::currentCategory() const
|
||||
@@ -113,12 +121,8 @@ void CategoryFilterWidget::showMenu()
|
||||
const auto selectedRows = selectionModel()->selectedRows();
|
||||
if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first()))
|
||||
{
|
||||
if (BitTorrent::Session::instance()->isSubcategoriesEnabled())
|
||||
{
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"list-add"_s), tr("Add subcategory...")
|
||||
, this, &CategoryFilterWidget::addSubcategory);
|
||||
}
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"list-add"_s), tr("Add subcategory...")
|
||||
, this, &CategoryFilterWidget::addSubcategory);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_s, u"document-edit"_s), tr("Edit category...")
|
||||
, this, &CategoryFilterWidget::editCategory);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_s, u"list-remove"_s), tr("Remove category")
|
||||
@@ -140,11 +144,6 @@ void CategoryFilterWidget::showMenu()
|
||||
|
||||
void CategoryFilterWidget::callUpdateGeometry()
|
||||
{
|
||||
if (!BitTorrent::Session::instance()->isSubcategoriesEnabled())
|
||||
setIndentation(0);
|
||||
else
|
||||
setIndentation(m_defaultIndentation);
|
||||
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
@@ -168,10 +167,13 @@ QSize CategoryFilterWidget::minimumSizeHint() const
|
||||
return size;
|
||||
}
|
||||
|
||||
void CategoryFilterWidget::rowsInserted(const QModelIndex &parent, int start, int end)
|
||||
void CategoryFilterWidget::rowsInserted(const QModelIndex &parent, const int start, const int end)
|
||||
{
|
||||
QTreeView::rowsInserted(parent, start, end);
|
||||
|
||||
if (parent.isValid())
|
||||
adjustIndentation();
|
||||
|
||||
// Expand all parents if the parent(s) of the node are not expanded.
|
||||
QModelIndex p = parent;
|
||||
while (p.isValid())
|
||||
@@ -184,6 +186,26 @@ void CategoryFilterWidget::rowsInserted(const QModelIndex &parent, int start, in
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
bool CategoryFilterWidget::hasAnySubcategory() const
|
||||
{
|
||||
const int rowCount = model()->rowCount();
|
||||
for (int row = 0; row < rowCount; ++row)
|
||||
{
|
||||
if (model()->hasChildren(model()->index(row, 0)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CategoryFilterWidget::adjustIndentation()
|
||||
{
|
||||
if (hasAnySubcategory())
|
||||
resetIndentation();
|
||||
else
|
||||
setIndentation(0);
|
||||
}
|
||||
|
||||
void CategoryFilterWidget::addCategory()
|
||||
{
|
||||
TorrentCategoryDialog::createCategory(this);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2016-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -60,6 +60,6 @@ private:
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
void rowsInserted(const QModelIndex &parent, int start, int end) override;
|
||||
|
||||
int m_defaultIndentation;
|
||||
bool hasAnySubcategory() const;
|
||||
void adjustIndentation();
|
||||
};
|
||||
|
||||
@@ -99,8 +99,6 @@ StatusFilterWidget::StatusFilterWidget(QWidget *parent, TransferListWidget *tran
|
||||
setCurrentRow(TorrentFilter::All, QItemSelectionModel::SelectCurrent);
|
||||
else
|
||||
setCurrentRow(storedRow, QItemSelectionModel::SelectCurrent);
|
||||
|
||||
toggleFilter(pref->getStatusFilterState());
|
||||
}
|
||||
|
||||
StatusFilterWidget::~StatusFilterWidget()
|
||||
@@ -128,7 +126,7 @@ void StatusFilterWidget::updateTorrentStatus(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
TorrentFilterBitset &torrentStatus = m_torrentsStatus[torrent];
|
||||
|
||||
const auto update = [torrent, &torrentStatus](const TorrentFilter::Type status, int &counter)
|
||||
const auto update = [torrent, &torrentStatus](const TorrentFilter::Status status, int &counter)
|
||||
{
|
||||
const bool hasStatus = torrentStatus[status];
|
||||
const bool needStatus = TorrentFilter(status).match(torrent);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -36,13 +36,13 @@
|
||||
#include <QMessageBox>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/trackerentry.h"
|
||||
#include "base/bittorrent/trackerentrystatus.h"
|
||||
#include "base/global.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/torrentfilter.h"
|
||||
#include "base/utils/compare.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "gui/transferlistwidget.h"
|
||||
@@ -69,16 +69,35 @@ namespace
|
||||
return !scheme.isEmpty() ? scheme : u"http"_s;
|
||||
}
|
||||
|
||||
QString getHost(const QString &url)
|
||||
template <typename T>
|
||||
concept HasUrlMember = requires (T t) { { t.url } -> std::convertible_to<QString>; };
|
||||
|
||||
template <HasUrlMember T>
|
||||
QString getTrackerHost(const T &t)
|
||||
{
|
||||
// We want the hostname.
|
||||
// If failed to parse the domain, original input should be returned
|
||||
return getTrackerHost(t.url);
|
||||
}
|
||||
|
||||
const QString host = QUrl(url).host();
|
||||
if (host.isEmpty())
|
||||
return url;
|
||||
template <typename T>
|
||||
QSet<QString> extractTrackerHosts(const T &trackerEntries)
|
||||
{
|
||||
QSet<QString> trackerHosts;
|
||||
trackerHosts.reserve(trackerEntries.size());
|
||||
for (const auto &trackerEntry : trackerEntries)
|
||||
trackerHosts.insert(getTrackerHost(trackerEntry));
|
||||
|
||||
return host;
|
||||
return trackerHosts;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QSet<QString> extractTrackerURLs(const T &trackerEntries)
|
||||
{
|
||||
QSet<QString> trackerURLs;
|
||||
trackerURLs.reserve(trackerEntries.size());
|
||||
for (const auto &trackerEntry : trackerEntries)
|
||||
trackerURLs.insert(trackerEntry.url);
|
||||
|
||||
return trackerURLs;
|
||||
}
|
||||
|
||||
QString getFaviconHost(const QString &trackerHost)
|
||||
@@ -123,7 +142,7 @@ namespace
|
||||
|
||||
TrackersFilterWidget::TrackersFilterWidget(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon)
|
||||
: BaseFilterWidget(parent, transferList)
|
||||
, m_downloadTrackerFavicon(downloadFavicon)
|
||||
, m_downloadTrackerFavicon {downloadFavicon}
|
||||
{
|
||||
auto *allTrackersItem = new QListWidgetItem(this);
|
||||
allTrackersItem->setData(Qt::DisplayRole, formatItemText(ALL_ROW, 0));
|
||||
@@ -131,22 +150,34 @@ TrackersFilterWidget::TrackersFilterWidget(QWidget *parent, TransferListWidget *
|
||||
auto *trackerlessItem = new QListWidgetItem(this);
|
||||
trackerlessItem->setData(Qt::DisplayRole, formatItemText(TRACKERLESS_ROW, 0));
|
||||
trackerlessItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackerless"_s, u"network-server"_s));
|
||||
auto *trackerErrorItem = new QListWidgetItem(this);
|
||||
trackerErrorItem->setData(Qt::DisplayRole, formatItemText(TRACKERERROR_ROW, 0));
|
||||
trackerErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
auto *otherErrorItem = new QListWidgetItem(this);
|
||||
otherErrorItem->setData(Qt::DisplayRole, formatItemText(OTHERERROR_ROW, 0));
|
||||
otherErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
auto *warningItem = new QListWidgetItem(this);
|
||||
warningItem->setData(Qt::DisplayRole, formatItemText(WARNING_ROW, 0));
|
||||
warningItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_s, u"dialog-warning"_s));
|
||||
|
||||
m_trackers[NULL_HOST] = {{}, trackerlessItem};
|
||||
m_trackers[NULL_HOST] = {0, trackerlessItem};
|
||||
|
||||
handleTorrentsLoaded(BitTorrent::Session::instance()->torrents());
|
||||
const auto *pref = Preferences::instance();
|
||||
const bool useSeparateTrackerStatusFilter = pref->useSeparateTrackerStatusFilter();
|
||||
if (useSeparateTrackerStatusFilter == m_handleTrackerStatuses)
|
||||
enableTrackerStatusItems(!useSeparateTrackerStatusFilter);
|
||||
connect(pref, &Preferences::changed, this, [this, pref]
|
||||
{
|
||||
const bool useSeparateTrackerStatusFilter = pref->useSeparateTrackerStatusFilter();
|
||||
if (useSeparateTrackerStatusFilter == m_handleTrackerStatuses)
|
||||
{
|
||||
enableTrackerStatusItems(!useSeparateTrackerStatusFilter);
|
||||
updateGeometry();
|
||||
if (m_handleTrackerStatuses)
|
||||
applyFilter(currentRow());
|
||||
}
|
||||
});
|
||||
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
handleTorrentsLoaded(btSession->torrents());
|
||||
|
||||
connect(btSession, &BitTorrent::Session::trackersAdded, this, &TrackersFilterWidget::handleTorrentTrackersAdded);
|
||||
connect(btSession, &BitTorrent::Session::trackersRemoved, this, &TrackersFilterWidget::handleTorrentTrackersRemoved);
|
||||
connect(btSession, &BitTorrent::Session::trackersReset, this, &TrackersFilterWidget::handleTorrentTrackersReset);
|
||||
connect(btSession, &BitTorrent::Session::trackerEntryStatusesUpdated, this, &TrackersFilterWidget::handleTorrentTrackerStatusesUpdated);
|
||||
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
toggleFilter(Preferences::instance()->getTrackerFilterState());
|
||||
}
|
||||
|
||||
TrackersFilterWidget::~TrackersFilterWidget()
|
||||
@@ -155,83 +186,64 @@ TrackersFilterWidget::~TrackersFilterWidget()
|
||||
Utils::Fs::removeFile(iconPath);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::addTrackers(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers)
|
||||
void TrackersFilterWidget::handleTorrentTrackersAdded(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QSet<QString> prevTrackerURLs = extractTrackerURLs(torrent->trackers()).subtract(extractTrackerURLs(trackers));
|
||||
const QSet<QString> addedTrackerHosts = extractTrackerHosts(trackers).subtract(extractTrackerHosts(prevTrackerURLs));
|
||||
|
||||
for (const BitTorrent::TrackerEntry &tracker : trackers)
|
||||
addItems(tracker.url, {torrentID});
|
||||
for (const QString &trackerHost : addedTrackerHosts)
|
||||
increaseTorrentsCount(trackerHost, 1);
|
||||
|
||||
removeItem(NULL_HOST, torrentID);
|
||||
if (prevTrackerURLs.isEmpty())
|
||||
decreaseTorrentsCount(NULL_HOST); // torrent was trackerless previously
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers)
|
||||
void TrackersFilterWidget::handleTorrentTrackersRemoved(const BitTorrent::Torrent *torrent, const QStringList &trackers)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QList<BitTorrent::TrackerEntryStatus> currentTrackerEntries = torrent->trackers();
|
||||
const QSet<QString> removedTrackerHosts = extractTrackerHosts(trackers).subtract(extractTrackerHosts(currentTrackerEntries));
|
||||
for (const QString &trackerHost : removedTrackerHosts)
|
||||
decreaseTorrentsCount(trackerHost);
|
||||
|
||||
for (const QString &tracker : trackers)
|
||||
removeItem(tracker, torrentID);
|
||||
if (currentTrackerEntries.isEmpty())
|
||||
increaseTorrentsCount(NULL_HOST, 1);
|
||||
|
||||
if (torrent->trackers().isEmpty())
|
||||
addItems(NULL_HOST, {torrentID});
|
||||
if (m_handleTrackerStatuses)
|
||||
refreshStatusItems(torrent);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::refreshTrackers(const BitTorrent::Torrent *torrent)
|
||||
void TrackersFilterWidget::handleTorrentTrackersReset(const BitTorrent::Torrent *torrent
|
||||
, const QList<BitTorrent::TrackerEntryStatus> &oldEntries, const QList<BitTorrent::TrackerEntry> &newEntries)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
|
||||
m_errors.remove(torrentID);
|
||||
m_trackerErrors.remove(torrentID);
|
||||
m_warnings.remove(torrentID);
|
||||
|
||||
Algorithm::removeIf(m_trackers, [this, &torrentID](const QString &host, TrackerData &trackerData)
|
||||
if (oldEntries.isEmpty())
|
||||
{
|
||||
QSet<BitTorrent::TorrentID> &torrentIDs = trackerData.torrents;
|
||||
if (!torrentIDs.remove(torrentID))
|
||||
return false;
|
||||
|
||||
QListWidgetItem *trackerItem = trackerData.item;
|
||||
|
||||
if (!host.isEmpty() && torrentIDs.isEmpty())
|
||||
{
|
||||
if (currentItem() == trackerItem)
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
delete trackerItem;
|
||||
return true;
|
||||
}
|
||||
|
||||
trackerItem->setText(formatItemText(host, torrentIDs.size()));
|
||||
return false;
|
||||
});
|
||||
|
||||
const QList<BitTorrent::TrackerEntryStatus> trackers = torrent->trackers();
|
||||
if (trackers.isEmpty())
|
||||
{
|
||||
addItems(NULL_HOST, {torrentID});
|
||||
decreaseTorrentsCount(NULL_HOST);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const BitTorrent::TrackerEntryStatus &status : trackers)
|
||||
addItems(status.url, {torrentID});
|
||||
for (const QString &trackerHost : asConst(extractTrackerHosts(oldEntries)))
|
||||
decreaseTorrentsCount(trackerHost);
|
||||
}
|
||||
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
|
||||
if (const int row = currentRow(); (row == OTHERERROR_ROW)
|
||||
|| (row == TRACKERERROR_ROW) || (row == WARNING_ROW))
|
||||
if (newEntries.isEmpty())
|
||||
{
|
||||
applyFilter(row);
|
||||
increaseTorrentsCount(NULL_HOST, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const QString &trackerHost : asConst(extractTrackerHosts(newEntries)))
|
||||
increaseTorrentsCount(trackerHost, 1);
|
||||
}
|
||||
|
||||
if (m_handleTrackerStatuses)
|
||||
refreshStatusItems(torrent);
|
||||
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::addItems(const QString &trackerURL, const QList<BitTorrent::TorrentID> &torrents)
|
||||
void TrackersFilterWidget::increaseTorrentsCount(const QString &trackerHost, const qsizetype torrentsCount)
|
||||
{
|
||||
const QString host = getHost(trackerURL);
|
||||
auto trackersIt = m_trackers.find(host);
|
||||
auto trackersIt = m_trackers.find(trackerHost);
|
||||
const bool exists = (trackersIt != m_trackers.end());
|
||||
QListWidgetItem *trackerItem = nullptr;
|
||||
|
||||
@@ -244,33 +256,27 @@ void TrackersFilterWidget::addItems(const QString &trackerURL, const QList<BitTo
|
||||
trackerItem = new QListWidgetItem();
|
||||
trackerItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_s, u"network-server"_s));
|
||||
|
||||
const TrackerData trackerData {{}, trackerItem};
|
||||
trackersIt = m_trackers.insert(host, trackerData);
|
||||
const TrackerData trackerData {0, trackerItem};
|
||||
trackersIt = m_trackers.insert(trackerHost, trackerData);
|
||||
|
||||
const QString scheme = getScheme(trackerURL);
|
||||
downloadFavicon(host, u"%1://%2/favicon.ico"_s.arg((scheme.startsWith(u"http") ? scheme : u"http"_s), getFaviconHost(host)));
|
||||
const QString scheme = getScheme(trackerHost);
|
||||
downloadFavicon(trackerHost, u"%1://%2/favicon.ico"_s.arg((scheme.startsWith(u"http") ? scheme : u"http"_s), getFaviconHost(trackerHost)));
|
||||
}
|
||||
|
||||
Q_ASSERT(trackerItem);
|
||||
|
||||
QSet<BitTorrent::TorrentID> &torrentIDs = trackersIt->torrents;
|
||||
for (const BitTorrent::TorrentID &torrentID : torrents)
|
||||
torrentIDs.insert(torrentID);
|
||||
trackersIt->torrentsCount += torrentsCount;
|
||||
|
||||
trackerItem->setText(formatItemText(host, torrentIDs.size()));
|
||||
trackerItem->setText(formatItemText(trackerHost, trackersIt->torrentsCount));
|
||||
if (exists)
|
||||
{
|
||||
if (item(currentRow()) == trackerItem)
|
||||
applyFilter(currentRow());
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(count() >= NUM_SPECIAL_ROWS);
|
||||
Q_ASSERT(count() >= numSpecialRows());
|
||||
const Utils::Compare::NaturalLessThan<Qt::CaseSensitive> naturalLessThan {};
|
||||
int insPos = count();
|
||||
for (int i = NUM_SPECIAL_ROWS; i < count(); ++i)
|
||||
for (int i = numSpecialRows(); i < count(); ++i)
|
||||
{
|
||||
if (naturalLessThan(host, item(i)->text()))
|
||||
if (naturalLessThan(trackerHost, item(i)->text()))
|
||||
{
|
||||
insPos = i;
|
||||
break;
|
||||
@@ -280,88 +286,59 @@ void TrackersFilterWidget::addItems(const QString &trackerURL, const QList<BitTo
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id)
|
||||
void TrackersFilterWidget::decreaseTorrentsCount(const QString &trackerHost)
|
||||
{
|
||||
const QString host = getHost(trackerURL);
|
||||
const auto iter = m_trackers.find(trackerHost);
|
||||
Q_ASSERT(iter != m_trackers.end());
|
||||
if (iter == m_trackers.end()) [[unlikely]]
|
||||
return;
|
||||
|
||||
QSet<BitTorrent::TorrentID> torrentIDs = m_trackers.value(host).torrents;
|
||||
torrentIDs.remove(id);
|
||||
TrackerData &trackerData = iter.value();
|
||||
Q_ASSERT(trackerData.torrentsCount > 0);
|
||||
if (trackerData.torrentsCount <= 0) [[unlikely]]
|
||||
return;
|
||||
|
||||
QListWidgetItem *trackerItem = nullptr;
|
||||
--trackerData.torrentsCount;
|
||||
|
||||
if (!host.isEmpty())
|
||||
if ((trackerData.torrentsCount == 0) && (trackerHost != NULL_HOST))
|
||||
{
|
||||
// Remove from 'Error', 'Tracker error' and 'Warning' view
|
||||
if (const auto errorHashesIt = m_errors.find(id)
|
||||
; errorHashesIt != m_errors.end())
|
||||
{
|
||||
QSet<QString> &errored = *errorHashesIt;
|
||||
errored.remove(trackerURL);
|
||||
if (errored.isEmpty())
|
||||
{
|
||||
m_errors.erase(errorHashesIt);
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
if (currentRow() == OTHERERROR_ROW)
|
||||
applyFilter(OTHERERROR_ROW);
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto trackerErrorHashesIt = m_trackerErrors.find(id)
|
||||
; trackerErrorHashesIt != m_trackerErrors.end())
|
||||
{
|
||||
QSet<QString> &errored = *trackerErrorHashesIt;
|
||||
errored.remove(trackerURL);
|
||||
if (errored.isEmpty())
|
||||
{
|
||||
m_trackerErrors.erase(trackerErrorHashesIt);
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
if (currentRow() == TRACKERERROR_ROW)
|
||||
applyFilter(TRACKERERROR_ROW);
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto warningHashesIt = m_warnings.find(id)
|
||||
; warningHashesIt != m_warnings.end())
|
||||
{
|
||||
QSet<QString> &warned = *warningHashesIt;
|
||||
warned.remove(trackerURL);
|
||||
if (warned.isEmpty())
|
||||
{
|
||||
m_warnings.erase(warningHashesIt);
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
if (currentRow() == WARNING_ROW)
|
||||
applyFilter(WARNING_ROW);
|
||||
}
|
||||
}
|
||||
|
||||
trackerItem = m_trackers.value(host).item;
|
||||
|
||||
if (torrentIDs.isEmpty())
|
||||
{
|
||||
if (currentItem() == trackerItem)
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
delete trackerItem;
|
||||
m_trackers.remove(host);
|
||||
updateGeometry();
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackerItem)
|
||||
trackerItem->setText(u"%1 (%2)"_s.arg(host, QString::number(torrentIDs.size())));
|
||||
if (currentItem() == trackerData.item)
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
delete trackerData.item;
|
||||
m_trackers.erase(iter);
|
||||
updateGeometry();
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerItem = item(TRACKERLESS_ROW);
|
||||
trackerItem->setText(formatItemText(TRACKERLESS_ROW, torrentIDs.size()));
|
||||
trackerData.item->setText(formatItemText(trackerHost, trackerData.torrentsCount));
|
||||
}
|
||||
|
||||
m_trackers.insert(host, {torrentIDs, trackerItem});
|
||||
|
||||
if (currentItem() == trackerItem)
|
||||
applyFilter(currentRow());
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::setDownloadTrackerFavicon(bool value)
|
||||
void TrackersFilterWidget::refreshStatusItems(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
const BitTorrent::TorrentAnnounceStatus announceStatus = torrent->announceStatus();
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasWarning))
|
||||
m_warnings.insert(torrent);
|
||||
else
|
||||
m_warnings.remove(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError))
|
||||
m_trackerErrors.insert(torrent);
|
||||
else
|
||||
m_trackerErrors.remove(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError))
|
||||
m_errors.insert(torrent);
|
||||
else
|
||||
m_errors.remove(torrent);
|
||||
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::setDownloadTrackerFavicon(const bool value)
|
||||
{
|
||||
if (value == m_downloadTrackerFavicon) return;
|
||||
m_downloadTrackerFavicon = value;
|
||||
@@ -381,107 +358,11 @@ void TrackersFilterWidget::setDownloadTrackerFavicon(bool value)
|
||||
}
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::handleTrackerStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers)
|
||||
void TrackersFilterWidget::handleTorrentTrackerStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, [[maybe_unused]] const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers)
|
||||
{
|
||||
const BitTorrent::TorrentID id = torrent->id();
|
||||
|
||||
auto errorHashesIt = m_errors.find(id);
|
||||
auto trackerErrorHashesIt = m_trackerErrors.find(id);
|
||||
auto warningHashesIt = m_warnings.find(id);
|
||||
|
||||
for (const BitTorrent::TrackerEntryStatus &trackerEntryStatus : updatedTrackers)
|
||||
{
|
||||
switch (trackerEntryStatus.state)
|
||||
{
|
||||
case BitTorrent::TrackerEndpointState::Working:
|
||||
{
|
||||
// remove tracker from "error" and "tracker error" categories
|
||||
if (errorHashesIt != m_errors.end())
|
||||
errorHashesIt->remove(trackerEntryStatus.url);
|
||||
if (trackerErrorHashesIt != m_trackerErrors.end())
|
||||
trackerErrorHashesIt->remove(trackerEntryStatus.url);
|
||||
|
||||
const bool hasNoWarningMessages = std::ranges::all_of(trackerEntryStatus.endpoints
|
||||
, [](const BitTorrent::TrackerEndpointStatus &endpointEntry)
|
||||
{
|
||||
return endpointEntry.message.isEmpty() || (endpointEntry.state != BitTorrent::TrackerEndpointState::Working);
|
||||
});
|
||||
if (hasNoWarningMessages)
|
||||
{
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
{
|
||||
warningHashesIt->remove(trackerEntryStatus.url);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (warningHashesIt == m_warnings.end())
|
||||
warningHashesIt = m_warnings.insert(id, {});
|
||||
warningHashesIt->insert(trackerEntryStatus.url);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::NotWorking:
|
||||
case BitTorrent::TrackerEndpointState::Unreachable:
|
||||
{
|
||||
// remove tracker from "tracker error" and "warning" categories
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
warningHashesIt->remove(trackerEntryStatus.url);
|
||||
if (trackerErrorHashesIt != m_trackerErrors.end())
|
||||
trackerErrorHashesIt->remove(trackerEntryStatus.url);
|
||||
|
||||
if (errorHashesIt == m_errors.end())
|
||||
errorHashesIt = m_errors.insert(id, {});
|
||||
errorHashesIt->insert(trackerEntryStatus.url);
|
||||
}
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::TrackerError:
|
||||
{
|
||||
// remove tracker from "error" and "warning" categories
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
warningHashesIt->remove(trackerEntryStatus.url);
|
||||
if (errorHashesIt != m_errors.end())
|
||||
errorHashesIt->remove(trackerEntryStatus.url);
|
||||
|
||||
if (trackerErrorHashesIt == m_trackerErrors.end())
|
||||
trackerErrorHashesIt = m_trackerErrors.insert(id, {});
|
||||
trackerErrorHashesIt->insert(trackerEntryStatus.url);
|
||||
}
|
||||
break;
|
||||
|
||||
case BitTorrent::TrackerEndpointState::NotContacted:
|
||||
{
|
||||
// remove tracker from "error", "tracker error" and "warning" categories
|
||||
if (warningHashesIt != m_warnings.end())
|
||||
warningHashesIt->remove(trackerEntryStatus.url);
|
||||
if (errorHashesIt != m_errors.end())
|
||||
errorHashesIt->remove(trackerEntryStatus.url);
|
||||
if (trackerErrorHashesIt != m_trackerErrors.end())
|
||||
trackerErrorHashesIt->remove(trackerEntryStatus.url);
|
||||
}
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
if ((errorHashesIt != m_errors.end()) && errorHashesIt->isEmpty())
|
||||
m_errors.erase(errorHashesIt);
|
||||
if ((trackerErrorHashesIt != m_trackerErrors.end()) && trackerErrorHashesIt->isEmpty())
|
||||
m_trackerErrors.erase(trackerErrorHashesIt);
|
||||
if ((warningHashesIt != m_warnings.end()) && warningHashesIt->isEmpty())
|
||||
m_warnings.erase(warningHashesIt);
|
||||
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
|
||||
if (const int row = currentRow(); (row == OTHERERROR_ROW)
|
||||
|| (row == TRACKERERROR_ROW) || (row == WARNING_ROW))
|
||||
{
|
||||
applyFilter(row);
|
||||
}
|
||||
if (m_handleTrackerStatuses)
|
||||
refreshStatusItems(torrent);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::downloadFavicon(const QString &trackerHost, const QString &faviconURL)
|
||||
@@ -500,19 +381,14 @@ void TrackersFilterWidget::downloadFavicon(const QString &trackerHost, const QSt
|
||||
downloadingFaviconNode.insert(trackerHost);
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::removeTracker(const QString &tracker)
|
||||
void TrackersFilterWidget::removeTracker(const QString &trackerHost)
|
||||
{
|
||||
for (const BitTorrent::TorrentID &torrentID : asConst(m_trackers.value(tracker).torrents))
|
||||
for (BitTorrent::Torrent *torrent : asConst(BitTorrent::Session::instance()->torrents()))
|
||||
{
|
||||
auto *torrent = BitTorrent::Session::instance()->getTorrent(torrentID);
|
||||
Q_ASSERT(torrent);
|
||||
if (!torrent) [[unlikely]]
|
||||
continue;
|
||||
|
||||
QStringList trackersToRemove;
|
||||
for (const BitTorrent::TrackerEntryStatus &trackerEntryStatus : asConst(torrent->trackers()))
|
||||
{
|
||||
if ((trackerEntryStatus.url == tracker) || (QUrl(trackerEntryStatus.url).host() == tracker))
|
||||
if (getTrackerHost(trackerEntryStatus) == trackerHost)
|
||||
trackersToRemove.append(trackerEntryStatus.url);
|
||||
}
|
||||
|
||||
@@ -522,6 +398,72 @@ void TrackersFilterWidget::removeTracker(const QString &tracker)
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::enableTrackerStatusItems(const bool value)
|
||||
{
|
||||
m_handleTrackerStatuses = value;
|
||||
if (m_handleTrackerStatuses)
|
||||
{
|
||||
auto *trackerErrorItem = new QListWidgetItem;
|
||||
trackerErrorItem->setData(Qt::DisplayRole, formatItemText(TRACKERERROR_ROW, 0));
|
||||
trackerErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
insertItem(TRACKERERROR_ROW, trackerErrorItem);
|
||||
|
||||
auto *otherErrorItem = new QListWidgetItem;
|
||||
otherErrorItem->setData(Qt::DisplayRole, formatItemText(OTHERERROR_ROW, 0));
|
||||
otherErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
insertItem(OTHERERROR_ROW, otherErrorItem);
|
||||
|
||||
auto *warningItem = new QListWidgetItem;
|
||||
warningItem->setData(Qt::DisplayRole, formatItemText(WARNING_ROW, 0));
|
||||
warningItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_s, u"dialog-warning"_s));
|
||||
insertItem(WARNING_ROW, warningItem);
|
||||
|
||||
const QList<BitTorrent::Torrent *> torrents = BitTorrent::Session::instance()->torrents();
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
{
|
||||
const BitTorrent::TorrentAnnounceStatus announceStatus = torrent->announceStatus();
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasWarning))
|
||||
m_warnings.insert(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError))
|
||||
m_trackerErrors.insert(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError))
|
||||
m_errors.insert(torrent);
|
||||
}
|
||||
|
||||
warningItem->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
trackerErrorItem->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
otherErrorItem->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (const int row = currentRow();
|
||||
(row == WARNING_ROW) || (row == TRACKERERROR_ROW) || (row == OTHERERROR_ROW))
|
||||
{
|
||||
setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
|
||||
// Need to be removed in reversed order
|
||||
takeItem(WARNING_ROW);
|
||||
takeItem(OTHERERROR_ROW);
|
||||
takeItem(TRACKERERROR_ROW);
|
||||
|
||||
m_warnings.clear();
|
||||
m_trackerErrors.clear();
|
||||
m_errors.clear();
|
||||
}
|
||||
}
|
||||
|
||||
qsizetype TrackersFilterWidget::numSpecialRows() const
|
||||
{
|
||||
if (m_handleTrackerStatuses)
|
||||
return NUM_SPECIAL_ROWS;
|
||||
|
||||
return NUM_SPECIAL_ROWS - 3;
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::handleFavicoDownloadFinished(const Net::DownloadResult &result)
|
||||
{
|
||||
const QSet<QString> trackerHosts = m_downloadingFavicons.take(result.url);
|
||||
@@ -590,7 +532,7 @@ void TrackersFilterWidget::showMenu()
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
if (currentRow() >= NUM_SPECIAL_ROWS)
|
||||
if (currentRow() >= numSpecialRows())
|
||||
{
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_s, u"list-remove"_s), tr("Remove tracker")
|
||||
, this, &TrackersFilterWidget::onRemoveTrackerTriggered);
|
||||
@@ -609,30 +551,80 @@ void TrackersFilterWidget::showMenu()
|
||||
|
||||
void TrackersFilterWidget::applyFilter(const int row)
|
||||
{
|
||||
if (row == ALL_ROW)
|
||||
transferList()->applyTrackerFilterAll();
|
||||
else if (isVisible())
|
||||
transferList()->applyTrackerFilter(getTorrentIDs(row));
|
||||
if (m_handleTrackerStatuses)
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case ALL_ROW:
|
||||
transferList()->applyTrackerFilter(std::nullopt);
|
||||
transferList()->applyAnnounceStatusFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case TRACKERLESS_ROW:
|
||||
transferList()->applyTrackerFilter(NULL_HOST);
|
||||
transferList()->applyAnnounceStatusFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case OTHERERROR_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError);
|
||||
transferList()->applyTrackerFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case TRACKERERROR_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError);
|
||||
transferList()->applyTrackerFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case WARNING_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasWarning);
|
||||
transferList()->applyTrackerFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
default:
|
||||
transferList()->applyTrackerFilter(trackerFromRow(row));
|
||||
transferList()->applyAnnounceStatusFilter(std::nullopt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case ALL_ROW:
|
||||
transferList()->applyTrackerFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case TRACKERLESS_ROW:
|
||||
transferList()->applyTrackerFilter(NULL_HOST);
|
||||
break;
|
||||
|
||||
default:
|
||||
transferList()->applyTrackerFilter(trackerFromRow(row));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::handleTorrentsLoaded(const QList<BitTorrent::Torrent *> &torrents)
|
||||
{
|
||||
QHash<QString, QList<BitTorrent::TorrentID>> torrentsPerTracker;
|
||||
QHash<QString, qsizetype> torrentsPerTrackerHost;
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QList<BitTorrent::TrackerEntryStatus> trackers = torrent->trackers();
|
||||
for (const BitTorrent::TrackerEntryStatus &tracker : trackers)
|
||||
torrentsPerTracker[tracker.url].append(torrentID);
|
||||
|
||||
// Check for trackerless torrent
|
||||
if (trackers.isEmpty())
|
||||
torrentsPerTracker[NULL_HOST].append(torrentID);
|
||||
if (const QList<BitTorrent::TrackerEntryStatus> trackers = torrent->trackers(); trackers.isEmpty())
|
||||
{
|
||||
++torrentsPerTrackerHost[NULL_HOST];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const QString &trackerHost : asConst(extractTrackerHosts(trackers)))
|
||||
++torrentsPerTrackerHost[trackerHost];
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[trackerURL, torrents] : asConst(torrentsPerTracker).asKeyValueRange())
|
||||
for (const auto &[trackerHost, torrentsCount] : asConst(torrentsPerTrackerHost).asKeyValueRange())
|
||||
{
|
||||
addItems(trackerURL, torrents);
|
||||
increaseTorrentsCount(trackerHost, torrentsCount);
|
||||
}
|
||||
|
||||
m_totalTorrents += torrents.count();
|
||||
@@ -641,22 +633,35 @@ void TrackersFilterWidget::handleTorrentsLoaded(const QList<BitTorrent::Torrent
|
||||
|
||||
void TrackersFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
const BitTorrent::TorrentID torrentID = torrent->id();
|
||||
const QList<BitTorrent::TrackerEntryStatus> trackers = torrent->trackers();
|
||||
for (const BitTorrent::TrackerEntryStatus &tracker : trackers)
|
||||
removeItem(tracker.url, torrentID);
|
||||
|
||||
// Check for trackerless torrent
|
||||
if (trackers.isEmpty())
|
||||
removeItem(NULL_HOST, torrentID);
|
||||
if (const QList<BitTorrent::TrackerEntryStatus> trackers = torrent->trackers(); trackers.isEmpty())
|
||||
{
|
||||
decreaseTorrentsCount(NULL_HOST);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const QString &trackerHost : asConst(extractTrackerHosts(trackers)))
|
||||
decreaseTorrentsCount(trackerHost);
|
||||
}
|
||||
|
||||
item(ALL_ROW)->setText(formatItemText(ALL_ROW, --m_totalTorrents));
|
||||
|
||||
if (m_handleTrackerStatuses)
|
||||
{
|
||||
m_warnings.remove(torrent);
|
||||
m_trackerErrors.remove(torrent);
|
||||
m_errors.remove(torrent);
|
||||
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
}
|
||||
}
|
||||
|
||||
void TrackersFilterWidget::onRemoveTrackerTriggered()
|
||||
{
|
||||
const int row = currentRow();
|
||||
if (row < NUM_SPECIAL_ROWS)
|
||||
if (row < numSpecialRows())
|
||||
return;
|
||||
|
||||
const QString &tracker = trackerFromRow(row);
|
||||
@@ -694,27 +699,10 @@ QString TrackersFilterWidget::trackerFromRow(int row) const
|
||||
int TrackersFilterWidget::rowFromTracker(const QString &tracker) const
|
||||
{
|
||||
Q_ASSERT(!tracker.isEmpty());
|
||||
for (int i = NUM_SPECIAL_ROWS; i < count(); ++i)
|
||||
for (int i = numSpecialRows(); i < count(); ++i)
|
||||
{
|
||||
if (tracker == trackerFromRow(i))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
QSet<BitTorrent::TorrentID> TrackersFilterWidget::getTorrentIDs(const int row) const
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case TRACKERLESS_ROW:
|
||||
return m_trackers.value(NULL_HOST).torrents;
|
||||
case OTHERERROR_ROW:
|
||||
return {m_errors.keyBegin(), m_errors.keyEnd()};
|
||||
case TRACKERERROR_ROW:
|
||||
return {m_trackerErrors.keyBegin(), m_trackerErrors.keyEnd()};
|
||||
case WARNING_ROW:
|
||||
return {m_warnings.keyBegin(), m_warnings.keyEnd()};
|
||||
default:
|
||||
return m_trackers.value(trackerFromRow(row)).torrents;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -57,11 +57,6 @@ public:
|
||||
TrackersFilterWidget(QWidget *parent, TransferListWidget *transferList, bool downloadFavicon);
|
||||
~TrackersFilterWidget() override;
|
||||
|
||||
void addTrackers(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers);
|
||||
void removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers);
|
||||
void refreshTrackers(const BitTorrent::Torrent *torrent);
|
||||
void handleTrackerStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers);
|
||||
void setDownloadTrackerFavicon(bool value);
|
||||
|
||||
private slots:
|
||||
@@ -75,28 +70,40 @@ private:
|
||||
void handleTorrentsLoaded(const QList<BitTorrent::Torrent *> &torrents) override;
|
||||
void torrentAboutToBeDeleted(BitTorrent::Torrent *torrent) override;
|
||||
|
||||
void handleTorrentTrackersAdded(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers);
|
||||
void handleTorrentTrackersRemoved(const BitTorrent::Torrent *torrent, const QStringList &trackers);
|
||||
void handleTorrentTrackersReset(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntryStatus> &oldEntries
|
||||
, const QList<BitTorrent::TrackerEntry> &newEntries);
|
||||
void handleTorrentTrackerStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers);
|
||||
|
||||
void onRemoveTrackerTriggered();
|
||||
|
||||
void addItems(const QString &trackerURL, const QList<BitTorrent::TorrentID> &torrents);
|
||||
void removeItem(const QString &trackerURL, const BitTorrent::TorrentID &id);
|
||||
void increaseTorrentsCount(const QString &trackerHost, qsizetype torrentsCount);
|
||||
void decreaseTorrentsCount(const QString &trackerHost);
|
||||
void refreshStatusItems(const BitTorrent::Torrent *torrent);
|
||||
QString trackerFromRow(int row) const;
|
||||
int rowFromTracker(const QString &tracker) const;
|
||||
QSet<BitTorrent::TorrentID> getTorrentIDs(int row) const;
|
||||
void downloadFavicon(const QString &trackerHost, const QString &faviconURL);
|
||||
void removeTracker(const QString &tracker);
|
||||
void removeTracker(const QString &trackerHost);
|
||||
|
||||
void enableTrackerStatusItems(bool value);
|
||||
|
||||
qsizetype numSpecialRows() const;
|
||||
|
||||
struct TrackerData
|
||||
{
|
||||
QSet<BitTorrent::TorrentID> torrents;
|
||||
qsizetype torrentsCount = 0;
|
||||
QListWidgetItem *item = nullptr;
|
||||
};
|
||||
|
||||
QHash<QString, TrackerData> m_trackers; // <tracker host, tracker data>
|
||||
QHash<BitTorrent::TorrentID, QSet<QString>> m_errors; // <torrent ID, tracker hosts>
|
||||
QHash<BitTorrent::TorrentID, QSet<QString>> m_trackerErrors; // <torrent ID, tracker hosts>
|
||||
QHash<BitTorrent::TorrentID, QSet<QString>> m_warnings; // <torrent ID, tracker hosts>
|
||||
QSet<const BitTorrent::Torrent *> m_errors;
|
||||
QSet<const BitTorrent::Torrent *> m_trackerErrors;
|
||||
QSet<const BitTorrent::Torrent *> m_warnings;
|
||||
PathList m_iconPaths;
|
||||
int m_totalTorrents = 0;
|
||||
bool m_downloadTrackerFavicon = false;
|
||||
bool m_handleTrackerStatuses = false;
|
||||
QHash<QString, QSet<QString>> m_downloadingFavicons; // <favicon URL, tracker hosts>
|
||||
};
|
||||
|
||||
218
src/gui/transferlistfilters/trackerstatusfilterwidget.cpp
Normal file
218
src/gui/transferlistfilters/trackerstatusfilterwidget.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "trackerstatusfilterwidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QIcon>
|
||||
#include <QListWidgetItem>
|
||||
#include <QMenu>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "base/preferences.h"
|
||||
#include "gui/transferlistwidget.h"
|
||||
#include "gui/uithememanager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
enum TRACKERSTATUS_FILTER_ROW
|
||||
{
|
||||
ANY_ROW,
|
||||
WARNING_ROW,
|
||||
TRACKERERROR_ROW,
|
||||
OTHERERROR_ROW,
|
||||
|
||||
NUM_SPECIAL_ROWS
|
||||
};
|
||||
|
||||
QString getFormatStringForRow(const int row)
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case ANY_ROW:
|
||||
return TrackerStatusFilterWidget::tr("All (%1)", "this is for the tracker filter");
|
||||
case WARNING_ROW:
|
||||
return TrackerStatusFilterWidget::tr("Warning (%1)");
|
||||
case TRACKERERROR_ROW:
|
||||
return TrackerStatusFilterWidget::tr("Tracker error (%1)");
|
||||
case OTHERERROR_ROW:
|
||||
return TrackerStatusFilterWidget::tr("Other error (%1)");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QString formatItemText(const int row, const int torrentsCount)
|
||||
{
|
||||
return getFormatStringForRow(row).arg(torrentsCount);
|
||||
}
|
||||
}
|
||||
|
||||
TrackerStatusFilterWidget::TrackerStatusFilterWidget(QWidget *parent, TransferListWidget *transferList)
|
||||
: BaseFilterWidget(parent, transferList)
|
||||
{
|
||||
auto *anyStatusItem = new QListWidgetItem(this);
|
||||
anyStatusItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"trackers"_s, u"network-server"_s));
|
||||
|
||||
auto *warningItem = new QListWidgetItem(this);
|
||||
warningItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-warning"_s, u"dialog-warning"_s));
|
||||
|
||||
auto *trackerErrorItem = new QListWidgetItem(this);
|
||||
trackerErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
|
||||
auto *otherErrorItem = new QListWidgetItem(this);
|
||||
otherErrorItem->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"tracker-error"_s, u"dialog-error"_s));
|
||||
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
|
||||
const QList<BitTorrent::Torrent *> torrents = btSession->torrents();
|
||||
m_totalTorrents += torrents.count();
|
||||
|
||||
for (const BitTorrent::Torrent *torrent : torrents)
|
||||
{
|
||||
const BitTorrent::TorrentAnnounceStatus announceStatus = torrent->announceStatus();
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasWarning))
|
||||
m_warnings.insert(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError))
|
||||
m_trackerErrors.insert(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError))
|
||||
m_errors.insert(torrent);
|
||||
}
|
||||
|
||||
connect(btSession, &BitTorrent::Session::trackersRemoved, this, &TrackerStatusFilterWidget::handleTorrentTrackersRemoved);
|
||||
connect(btSession, &BitTorrent::Session::trackersReset, this, &TrackerStatusFilterWidget::handleTorrentTrackersReset);
|
||||
connect(btSession, &BitTorrent::Session::trackerEntryStatusesUpdated, this, &TrackerStatusFilterWidget::handleTorrentTrackerStatusesUpdated);
|
||||
|
||||
anyStatusItem->setText(formatItemText(ANY_ROW, m_totalTorrents));
|
||||
warningItem->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
trackerErrorItem->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
otherErrorItem->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
|
||||
setCurrentRow(0, QItemSelectionModel::SelectCurrent);
|
||||
setVisible(Preferences::instance()->getTrackerStatusFilterState());
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::handleTorrentTrackersRemoved(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
refreshItems(torrent);
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::handleTorrentTrackersReset(const BitTorrent::Torrent *torrent
|
||||
, [[maybe_unused]] const QList<BitTorrent::TrackerEntryStatus> &oldEntries, [[maybe_unused]] const QList<BitTorrent::TrackerEntry> &newEntries)
|
||||
{
|
||||
refreshItems(torrent);
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::handleTorrentTrackerStatusesUpdated(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
refreshItems(torrent);
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::showMenu()
|
||||
{
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-start"_s, u"media-playback-start"_s), tr("Start torrents")
|
||||
, transferList(), &TransferListWidget::startVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"torrent-stop"_s, u"media-playback-pause"_s), tr("Stop torrents")
|
||||
, transferList(), &TransferListWidget::stopVisibleTorrents);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon(u"list-remove"_s), tr("Remove torrents")
|
||||
, transferList(), &TransferListWidget::deleteVisibleTorrents);
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::applyFilter(const int row)
|
||||
{
|
||||
switch (row)
|
||||
{
|
||||
case ANY_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(std::nullopt);
|
||||
break;
|
||||
|
||||
case WARNING_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasWarning);
|
||||
break;
|
||||
|
||||
case TRACKERERROR_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError);
|
||||
break;
|
||||
|
||||
case OTHERERROR_ROW:
|
||||
transferList()->applyAnnounceStatusFilter(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::handleTorrentsLoaded(const QList<BitTorrent::Torrent *> &torrents)
|
||||
{
|
||||
m_totalTorrents += torrents.count();
|
||||
item(ANY_ROW)->setText(formatItemText(ANY_ROW, m_totalTorrents));
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::refreshItems(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
const BitTorrent::TorrentAnnounceStatus announceStatus = torrent->announceStatus();
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasWarning))
|
||||
m_warnings.insert(torrent);
|
||||
else
|
||||
m_warnings.remove(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasTrackerError))
|
||||
m_trackerErrors.insert(torrent);
|
||||
else
|
||||
m_trackerErrors.remove(torrent);
|
||||
|
||||
if (announceStatus.testFlag(BitTorrent::TorrentAnnounceStatusFlag::HasOtherError))
|
||||
m_errors.insert(torrent);
|
||||
else
|
||||
m_errors.remove(torrent);
|
||||
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
}
|
||||
|
||||
void TrackerStatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
m_warnings.remove(torrent);
|
||||
m_trackerErrors.remove(torrent);
|
||||
m_errors.remove(torrent);
|
||||
|
||||
item(ANY_ROW)->setText(formatItemText(ANY_ROW, --m_totalTorrents));
|
||||
item(WARNING_ROW)->setText(formatItemText(WARNING_ROW, m_warnings.size()));
|
||||
item(TRACKERERROR_ROW)->setText(formatItemText(TRACKERERROR_ROW, m_trackerErrors.size()));
|
||||
item(OTHERERROR_ROW)->setText(formatItemText(OTHERERROR_ROW, m_errors.size()));
|
||||
}
|
||||
66
src/gui/transferlistfilters/trackerstatusfilterwidget.h
Normal file
66
src/gui/transferlistfilters/trackerstatusfilterwidget.h
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QSet>
|
||||
|
||||
#include "basefilterwidget.h"
|
||||
|
||||
class TransferListWidget;
|
||||
|
||||
class TrackerStatusFilterWidget final : public BaseFilterWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(TrackerStatusFilterWidget)
|
||||
|
||||
public:
|
||||
TrackerStatusFilterWidget(QWidget *parent, TransferListWidget *transferList);
|
||||
|
||||
private:
|
||||
// These 4 methods are virtual slots in the base class.
|
||||
// No need to redeclare them here as slots.
|
||||
void showMenu() override;
|
||||
void applyFilter(int row) override;
|
||||
void handleTorrentsLoaded(const QList<BitTorrent::Torrent *> &torrents) override;
|
||||
void torrentAboutToBeDeleted(BitTorrent::Torrent *torrent) override;
|
||||
|
||||
void handleTorrentTrackersRemoved(const BitTorrent::Torrent *torrent);
|
||||
void handleTorrentTrackersReset(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntryStatus> &oldEntries
|
||||
, const QList<BitTorrent::TrackerEntry> &newEntries);
|
||||
void handleTorrentTrackerStatusesUpdated(const BitTorrent::Torrent *torrent);
|
||||
|
||||
void refreshItems(const BitTorrent::Torrent *torrent);
|
||||
|
||||
QSet<const BitTorrent::Torrent *> m_errors;
|
||||
QSet<const BitTorrent::Torrent *> m_trackerErrors;
|
||||
QSet<const BitTorrent::Torrent *> m_warnings;
|
||||
int m_totalTorrents = 0;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -29,61 +29,26 @@
|
||||
|
||||
#include "transferlistfilterswidget.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QIcon>
|
||||
#include <QListWidgetItem>
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
#include <QScrollArea>
|
||||
#include <QStyleOptionButton>
|
||||
#include <QUrl>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "base/bittorrent/trackerentrystatus.h"
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/torrentfilter.h"
|
||||
#include "base/utils/compare.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "transferlistfilters/categoryfilterwidget.h"
|
||||
#include "transferlistfilters/statusfilterwidget.h"
|
||||
#include "transferlistfilters/tagfilterwidget.h"
|
||||
#include "transferlistfilters/trackersfilterwidget.h"
|
||||
#include "transferlistfilters/trackerstatusfilterwidget.h"
|
||||
#include "transferlistfilterswidgetitem.h"
|
||||
#include "transferlistwidget.h"
|
||||
#include "uithememanager.h"
|
||||
#include "utils.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
class ArrowCheckBox final : public QCheckBox
|
||||
{
|
||||
public:
|
||||
using QCheckBox::QCheckBox;
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *) override
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
QStyleOptionViewItem indicatorOption;
|
||||
indicatorOption.initFrom(this);
|
||||
indicatorOption.rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &indicatorOption, this);
|
||||
indicatorOption.state |= (QStyle::State_Children
|
||||
| (isChecked() ? QStyle::State_Open : QStyle::State_None));
|
||||
style()->drawPrimitive(QStyle::PE_IndicatorBranch, &indicatorOption, &painter, this);
|
||||
|
||||
QStyleOptionButton labelOption;
|
||||
initStyleOption(&labelOption);
|
||||
labelOption.rect = style()->subElementRect(QStyle::SE_CheckBoxContents, &labelOption, this);
|
||||
style()->drawControl(QStyle::CE_CheckBoxLabel, &labelOption, &painter, this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon)
|
||||
: QWidget(parent)
|
||||
, m_transferList {transferList}
|
||||
@@ -99,66 +64,65 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
|
||||
mainWidgetLayout->setSpacing(2);
|
||||
mainWidgetLayout->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
|
||||
QFont font;
|
||||
font.setBold(true);
|
||||
font.setCapitalization(QFont::AllUppercase);
|
||||
{
|
||||
auto *item = new TransferListFiltersWidgetItem(tr("Status"), new StatusFilterWidget(this, transferList), this);
|
||||
item->setChecked(pref->getStatusFilterState());
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setStatusFilterState);
|
||||
mainWidgetLayout->addWidget(item);
|
||||
}
|
||||
|
||||
QCheckBox *statusLabel = new ArrowCheckBox(tr("Status"), this);
|
||||
statusLabel->setChecked(pref->getStatusFilterState());
|
||||
statusLabel->setFont(font);
|
||||
connect(statusLabel, &QCheckBox::toggled, pref, &Preferences::setStatusFilterState);
|
||||
mainWidgetLayout->addWidget(statusLabel);
|
||||
{
|
||||
auto *categoryFilterWidget = new CategoryFilterWidget(this);
|
||||
connect(categoryFilterWidget, &CategoryFilterWidget::actionDeleteTorrentsTriggered
|
||||
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||
connect(categoryFilterWidget, &CategoryFilterWidget::actionStopTorrentsTriggered
|
||||
, transferList, &TransferListWidget::stopVisibleTorrents);
|
||||
connect(categoryFilterWidget, &CategoryFilterWidget::actionStartTorrentsTriggered
|
||||
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||
connect(categoryFilterWidget, &CategoryFilterWidget::categoryChanged
|
||||
, transferList, &TransferListWidget::applyCategoryFilter);
|
||||
|
||||
auto *statusFilters = new StatusFilterWidget(this, transferList);
|
||||
connect(statusLabel, &QCheckBox::toggled, statusFilters, &StatusFilterWidget::toggleFilter);
|
||||
mainWidgetLayout->addWidget(statusFilters);
|
||||
auto *item = new TransferListFiltersWidgetItem(tr("Categories"), categoryFilterWidget, this);
|
||||
item->setChecked(pref->getCategoryFilterState());
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, this, [this, categoryFilterWidget](const bool enabled)
|
||||
{
|
||||
m_transferList->applyCategoryFilter(enabled ? categoryFilterWidget->currentCategory() : QString());
|
||||
});
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setCategoryFilterState);
|
||||
mainWidgetLayout->addWidget(item);
|
||||
}
|
||||
|
||||
QCheckBox *categoryLabel = new ArrowCheckBox(tr("Categories"), this);
|
||||
categoryLabel->setChecked(pref->getCategoryFilterState());
|
||||
categoryLabel->setFont(font);
|
||||
connect(categoryLabel, &QCheckBox::toggled, this
|
||||
, &TransferListFiltersWidget::onCategoryFilterStateChanged);
|
||||
mainWidgetLayout->addWidget(categoryLabel);
|
||||
{
|
||||
auto *tagFilterWidget = new TagFilterWidget(this);
|
||||
connect(tagFilterWidget, &TagFilterWidget::actionDeleteTorrentsTriggered
|
||||
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||
connect(tagFilterWidget, &TagFilterWidget::actionStopTorrentsTriggered
|
||||
, transferList, &TransferListWidget::stopVisibleTorrents);
|
||||
connect(tagFilterWidget, &TagFilterWidget::actionStartTorrentsTriggered
|
||||
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||
connect(tagFilterWidget, &TagFilterWidget::tagChanged
|
||||
, transferList, &TransferListWidget::applyTagFilter);
|
||||
|
||||
m_categoryFilterWidget = new CategoryFilterWidget(this);
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionDeleteTorrentsTriggered
|
||||
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionStopTorrentsTriggered
|
||||
, transferList, &TransferListWidget::stopVisibleTorrents);
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::actionStartTorrentsTriggered
|
||||
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||
connect(m_categoryFilterWidget, &CategoryFilterWidget::categoryChanged
|
||||
, transferList, &TransferListWidget::applyCategoryFilter);
|
||||
toggleCategoryFilter(pref->getCategoryFilterState());
|
||||
mainWidgetLayout->addWidget(m_categoryFilterWidget);
|
||||
auto *item = new TransferListFiltersWidgetItem(tr("Tags"), tagFilterWidget, this);
|
||||
item->setChecked(pref->getTagFilterState());
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, this, [this, tagFilterWidget](const bool enabled)
|
||||
{
|
||||
m_transferList->applyTagFilter(enabled ? tagFilterWidget->currentTag() : std::nullopt);
|
||||
});
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setTagFilterState);
|
||||
mainWidgetLayout->addWidget(item);
|
||||
}
|
||||
|
||||
QCheckBox *tagsLabel = new ArrowCheckBox(tr("Tags"), this);
|
||||
tagsLabel->setChecked(pref->getTagFilterState());
|
||||
tagsLabel->setFont(font);
|
||||
connect(tagsLabel, &QCheckBox::toggled, this, &TransferListFiltersWidget::onTagFilterStateChanged);
|
||||
mainWidgetLayout->addWidget(tagsLabel);
|
||||
const int trackerStatusItemPos = mainWidgetLayout->count();
|
||||
|
||||
m_tagFilterWidget = new TagFilterWidget(this);
|
||||
connect(m_tagFilterWidget, &TagFilterWidget::actionDeleteTorrentsTriggered
|
||||
, transferList, &TransferListWidget::deleteVisibleTorrents);
|
||||
connect(m_tagFilterWidget, &TagFilterWidget::actionStopTorrentsTriggered
|
||||
, transferList, &TransferListWidget::stopVisibleTorrents);
|
||||
connect(m_tagFilterWidget, &TagFilterWidget::actionStartTorrentsTriggered
|
||||
, transferList, &TransferListWidget::startVisibleTorrents);
|
||||
connect(m_tagFilterWidget, &TagFilterWidget::tagChanged
|
||||
, transferList, &TransferListWidget::applyTagFilter);
|
||||
toggleTagFilter(pref->getTagFilterState());
|
||||
mainWidgetLayout->addWidget(m_tagFilterWidget);
|
||||
{
|
||||
m_trackersFilterWidget = new TrackersFilterWidget(this, transferList, downloadFavicon);
|
||||
|
||||
QCheckBox *trackerLabel = new ArrowCheckBox(tr("Trackers"), this);
|
||||
trackerLabel->setChecked(pref->getTrackerFilterState());
|
||||
trackerLabel->setFont(font);
|
||||
connect(trackerLabel, &QCheckBox::toggled, pref, &Preferences::setTrackerFilterState);
|
||||
mainWidgetLayout->addWidget(trackerLabel);
|
||||
|
||||
m_trackersFilterWidget = new TrackersFilterWidget(this, transferList, downloadFavicon);
|
||||
connect(trackerLabel, &QCheckBox::toggled, m_trackersFilterWidget, &TrackersFilterWidget::toggleFilter);
|
||||
mainWidgetLayout->addWidget(m_trackersFilterWidget);
|
||||
auto *item = new TransferListFiltersWidgetItem(tr("Trackers"), m_trackersFilterWidget, this);
|
||||
item->setChecked(pref->getTrackerFilterState());
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setTrackerFilterState);
|
||||
mainWidgetLayout->addWidget(item);
|
||||
}
|
||||
|
||||
auto *scroll = new QScrollArea(this);
|
||||
scroll->setWidgetResizable(true);
|
||||
@@ -169,54 +133,40 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
|
||||
auto *vLayout = new QVBoxLayout(this);
|
||||
vLayout->setContentsMargins(0, 0, 0, 0);
|
||||
vLayout->addWidget(scroll);
|
||||
|
||||
const auto createTrackerStatusItem = [this, mainWidgetLayout, trackerStatusItemPos, pref]
|
||||
{
|
||||
auto *item = new TransferListFiltersWidgetItem(tr("Tracker status"), new TrackerStatusFilterWidget(this, m_transferList), this);
|
||||
item->setChecked(pref->getTrackerStatusFilterState());
|
||||
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setTrackerStatusFilterState);
|
||||
mainWidgetLayout->insertWidget(trackerStatusItemPos, item);
|
||||
};
|
||||
|
||||
const auto removeTrackerStatusItem = [mainWidgetLayout, trackerStatusItemPos]
|
||||
{
|
||||
QLayoutItem *layoutItem = mainWidgetLayout->takeAt(trackerStatusItemPos);
|
||||
delete layoutItem->widget();
|
||||
delete layoutItem;
|
||||
};
|
||||
|
||||
m_useSeparateTrackerStatusFilter = pref->useSeparateTrackerStatusFilter();
|
||||
if (m_useSeparateTrackerStatusFilter)
|
||||
createTrackerStatusItem();
|
||||
|
||||
connect(pref, &Preferences::changed, this, [this, pref, createTrackerStatusItem, removeTrackerStatusItem]
|
||||
{
|
||||
if (m_useSeparateTrackerStatusFilter == pref->useSeparateTrackerStatusFilter())
|
||||
return;
|
||||
|
||||
m_useSeparateTrackerStatusFilter = !m_useSeparateTrackerStatusFilter;
|
||||
if (m_useSeparateTrackerStatusFilter)
|
||||
createTrackerStatusItem();
|
||||
else
|
||||
removeTrackerStatusItem();
|
||||
});
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::setDownloadTrackerFavicon(bool value)
|
||||
{
|
||||
m_trackersFilterWidget->setDownloadTrackerFavicon(value);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::addTrackers(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers)
|
||||
{
|
||||
m_trackersFilterWidget->addTrackers(torrent, trackers);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers)
|
||||
{
|
||||
m_trackersFilterWidget->removeTrackers(torrent, trackers);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::refreshTrackers(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
m_trackersFilterWidget->refreshTrackers(torrent);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::trackerEntryStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers)
|
||||
{
|
||||
m_trackersFilterWidget->handleTrackerStatusesUpdated(torrent, updatedTrackers);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::onCategoryFilterStateChanged(bool enabled)
|
||||
{
|
||||
toggleCategoryFilter(enabled);
|
||||
Preferences::instance()->setCategoryFilterState(enabled);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::toggleCategoryFilter(bool enabled)
|
||||
{
|
||||
m_categoryFilterWidget->setVisible(enabled);
|
||||
m_transferList->applyCategoryFilter(enabled ? m_categoryFilterWidget->currentCategory() : QString());
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::onTagFilterStateChanged(bool enabled)
|
||||
{
|
||||
toggleTagFilter(enabled);
|
||||
Preferences::instance()->setTagFilterState(enabled);
|
||||
}
|
||||
|
||||
void TransferListFiltersWidget::toggleTagFilter(bool enabled)
|
||||
{
|
||||
m_tagFilterWidget->setVisible(enabled);
|
||||
m_transferList->applyTagFilter(enabled ? m_tagFilterWidget->currentTag() : std::nullopt);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -32,11 +32,6 @@
|
||||
#include <QtContainerFwd>
|
||||
#include <QWidget>
|
||||
|
||||
#include "base/bittorrent/trackerentry.h"
|
||||
|
||||
class CategoryFilterWidget;
|
||||
class StatusFilterWidget;
|
||||
class TagFilterWidget;
|
||||
class TrackersFilterWidget;
|
||||
class TransferListWidget;
|
||||
|
||||
@@ -55,23 +50,8 @@ public:
|
||||
TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList, bool downloadFavicon);
|
||||
void setDownloadTrackerFavicon(bool value);
|
||||
|
||||
public slots:
|
||||
void addTrackers(const BitTorrent::Torrent *torrent, const QList<BitTorrent::TrackerEntry> &trackers);
|
||||
void removeTrackers(const BitTorrent::Torrent *torrent, const QStringList &trackers);
|
||||
void refreshTrackers(const BitTorrent::Torrent *torrent);
|
||||
void trackerEntryStatusesUpdated(const BitTorrent::Torrent *torrent
|
||||
, const QHash<QString, BitTorrent::TrackerEntryStatus> &updatedTrackers);
|
||||
|
||||
private slots:
|
||||
void onCategoryFilterStateChanged(bool enabled);
|
||||
void onTagFilterStateChanged(bool enabled);
|
||||
|
||||
private:
|
||||
void toggleCategoryFilter(bool enabled);
|
||||
void toggleTagFilter(bool enabled);
|
||||
|
||||
TransferListWidget *m_transferList = nullptr;
|
||||
TrackersFilterWidget *m_trackersFilterWidget = nullptr;
|
||||
CategoryFilterWidget *m_categoryFilterWidget = nullptr;
|
||||
TagFilterWidget *m_tagFilterWidget = nullptr;
|
||||
bool m_useSeparateTrackerStatusFilter = false;
|
||||
};
|
||||
|
||||
96
src/gui/transferlistfilterswidgetitem.cpp
Normal file
96
src/gui/transferlistfilterswidgetitem.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "transferlistfilterswidgetitem.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QFont>
|
||||
#include <QPainter>
|
||||
#include <QString>
|
||||
#include <QStyleOptionViewItem>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace
|
||||
{
|
||||
class ArrowCheckBox final : public QCheckBox
|
||||
{
|
||||
public:
|
||||
using QCheckBox::QCheckBox;
|
||||
|
||||
private:
|
||||
void paintEvent(QPaintEvent *) override
|
||||
{
|
||||
QPainter painter {this};
|
||||
|
||||
QStyleOptionViewItem indicatorOption;
|
||||
indicatorOption.initFrom(this);
|
||||
indicatorOption.rect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &indicatorOption, this);
|
||||
indicatorOption.state |= (QStyle::State_Children
|
||||
| (isChecked() ? QStyle::State_Open : QStyle::State_None));
|
||||
style()->drawPrimitive(QStyle::PE_IndicatorBranch, &indicatorOption, &painter, this);
|
||||
|
||||
QStyleOptionButton labelOption;
|
||||
initStyleOption(&labelOption);
|
||||
labelOption.rect = style()->subElementRect(QStyle::SE_CheckBoxContents, &labelOption, this);
|
||||
style()->drawControl(QStyle::CE_CheckBoxLabel, &labelOption, &painter, this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TransferListFiltersWidgetItem::TransferListFiltersWidgetItem(const QString &caption, QWidget *filterWidget, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_caption {new ArrowCheckBox(caption, this)}
|
||||
, m_filterWidget {filterWidget}
|
||||
{
|
||||
QFont font;
|
||||
font.setBold(true);
|
||||
font.setCapitalization(QFont::AllUppercase);
|
||||
m_caption->setFont(font);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 2, 0, 0);
|
||||
layout->setSpacing(2);
|
||||
layout->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
layout->addWidget(m_caption);
|
||||
layout->addWidget(m_filterWidget);
|
||||
|
||||
m_filterWidget->setVisible(m_caption->isChecked());
|
||||
|
||||
connect(m_caption, &QCheckBox::toggled, m_filterWidget, &QWidget::setVisible);
|
||||
connect(m_caption, &QCheckBox::toggled, this, &TransferListFiltersWidgetItem::toggled);
|
||||
}
|
||||
|
||||
bool TransferListFiltersWidgetItem::isChecked() const
|
||||
{
|
||||
return m_caption->isChecked();
|
||||
}
|
||||
|
||||
void TransferListFiltersWidgetItem::setChecked(const bool value)
|
||||
{
|
||||
m_caption->setChecked(value);
|
||||
}
|
||||
53
src/gui/transferlistfilterswidgetitem.h
Normal file
53
src/gui/transferlistfilterswidgetitem.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QCheckBox;
|
||||
class QString;
|
||||
|
||||
class TransferListFiltersWidgetItem final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(TransferListFiltersWidgetItem)
|
||||
|
||||
public:
|
||||
TransferListFiltersWidgetItem(const QString &caption, QWidget *filterWidget, QWidget *parent = nullptr);
|
||||
|
||||
bool isChecked() const;
|
||||
void setChecked(bool value);
|
||||
|
||||
signals:
|
||||
void toggled(bool checked);
|
||||
|
||||
private:
|
||||
QCheckBox *m_caption = nullptr;
|
||||
QWidget *m_filterWidget = nullptr;
|
||||
};
|
||||
@@ -91,27 +91,25 @@ namespace
|
||||
|
||||
TransferListModel::TransferListModel(QObject *parent)
|
||||
: QAbstractListModel {parent}
|
||||
, m_statusStrings
|
||||
{
|
||||
{BitTorrent::TorrentState::Downloading, tr("Downloading")},
|
||||
{BitTorrent::TorrentState::StalledDownloading, tr("Stalled", "Torrent is waiting for download to begin")},
|
||||
{BitTorrent::TorrentState::DownloadingMetadata, tr("Downloading metadata", "Used when loading a magnet link")},
|
||||
{BitTorrent::TorrentState::ForcedDownloadingMetadata, tr("[F] Downloading metadata", "Used when forced to load a magnet link. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::ForcedDownloading, tr("[F] Downloading", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::Uploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
|
||||
{BitTorrent::TorrentState::StalledUploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
|
||||
{BitTorrent::TorrentState::ForcedUploading, tr("[F] Seeding", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::QueuedDownloading, tr("Queued", "Torrent is queued")},
|
||||
{BitTorrent::TorrentState::QueuedUploading, tr("Queued", "Torrent is queued")},
|
||||
{BitTorrent::TorrentState::CheckingDownloading, tr("Checking", "Torrent local data is being checked")},
|
||||
{BitTorrent::TorrentState::CheckingUploading, tr("Checking", "Torrent local data is being checked")},
|
||||
{BitTorrent::TorrentState::CheckingResumeData, tr("Checking resume data", "Used when loading the torrents from disk after qbt is launched. It checks the correctness of the .fastresume file. Normally it is completed in a fraction of a second, unless loading many many torrents.")},
|
||||
{BitTorrent::TorrentState::StoppedDownloading, tr("Stopped")},
|
||||
{BitTorrent::TorrentState::StoppedUploading, tr("Completed")},
|
||||
{BitTorrent::TorrentState::Moving, tr("Moving", "Torrent local data are being moved/relocated")},
|
||||
{BitTorrent::TorrentState::MissingFiles, tr("Missing Files")},
|
||||
{BitTorrent::TorrentState::Error, tr("Errored", "Torrent status, the torrent has an error")}
|
||||
}
|
||||
, m_statusStrings {
|
||||
{BitTorrent::TorrentState::Downloading, tr("Downloading")},
|
||||
{BitTorrent::TorrentState::StalledDownloading, tr("Stalled", "Torrent is waiting for download to begin")},
|
||||
{BitTorrent::TorrentState::DownloadingMetadata, tr("Downloading metadata", "Used when loading a magnet link")},
|
||||
{BitTorrent::TorrentState::ForcedDownloadingMetadata, tr("[F] Downloading metadata", "Used when forced to load a magnet link. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::ForcedDownloading, tr("[F] Downloading", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::Uploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
|
||||
{BitTorrent::TorrentState::StalledUploading, tr("Seeding", "Torrent is complete and in upload-only mode")},
|
||||
{BitTorrent::TorrentState::ForcedUploading, tr("[F] Seeding", "Used when the torrent is forced started. You probably shouldn't translate the F.")},
|
||||
{BitTorrent::TorrentState::QueuedDownloading, tr("Queued", "Torrent is queued")},
|
||||
{BitTorrent::TorrentState::QueuedUploading, tr("Queued", "Torrent is queued")},
|
||||
{BitTorrent::TorrentState::CheckingDownloading, tr("Checking", "Torrent local data is being checked")},
|
||||
{BitTorrent::TorrentState::CheckingUploading, tr("Checking", "Torrent local data is being checked")},
|
||||
{BitTorrent::TorrentState::CheckingResumeData, tr("Checking resume data", "Used when loading the torrents from disk after qbt is launched. It checks the correctness of the .fastresume file. Normally it is completed in a fraction of a second, unless loading many many torrents.")},
|
||||
{BitTorrent::TorrentState::StoppedDownloading, tr("Stopped")},
|
||||
{BitTorrent::TorrentState::StoppedUploading, tr("Completed")},
|
||||
{BitTorrent::TorrentState::Moving, tr("Moving", "Torrent local data are being moved/relocated")},
|
||||
{BitTorrent::TorrentState::MissingFiles, tr("Missing Files")},
|
||||
{BitTorrent::TorrentState::Error, tr("Errored", "Torrent status, the torrent has an error")}}
|
||||
{
|
||||
configure();
|
||||
connect(Preferences::instance(), &Preferences::changed, this, &TransferListModel::configure);
|
||||
@@ -137,6 +135,8 @@ TransferListModel::TransferListModel(QObject *parent)
|
||||
connect(Session::instance(), &Session::torrentStarted, this, &TransferListModel::handleTorrentStatusUpdated);
|
||||
connect(Session::instance(), &Session::torrentStopped, this, &TransferListModel::handleTorrentStatusUpdated);
|
||||
connect(Session::instance(), &Session::torrentFinishedChecking, this, &TransferListModel::handleTorrentStatusUpdated);
|
||||
|
||||
connect(Session::instance(), &Session::trackerEntryStatusesUpdated, this, &TransferListModel::handleTorrentStatusUpdated);
|
||||
}
|
||||
|
||||
int TransferListModel::rowCount(const QModelIndex &) const
|
||||
@@ -392,7 +392,7 @@ QString TransferListModel::displayValue(const BitTorrent::Torrent *torrent, cons
|
||||
case TR_RATIO:
|
||||
return ratioString(torrent->realRatio());
|
||||
case TR_RATIO_LIMIT:
|
||||
return ratioString(torrent->maxRatio());
|
||||
return ratioString(torrent->effectiveRatioLimit());
|
||||
case TR_POPULARITY:
|
||||
return ratioString(torrent->popularity());
|
||||
case TR_CATEGORY:
|
||||
@@ -509,7 +509,7 @@ QVariant TransferListModel::internalValue(const BitTorrent::Torrent *torrent, co
|
||||
case TR_COMPLETED:
|
||||
return torrent->completedSize();
|
||||
case TR_RATIO_LIMIT:
|
||||
return torrent->maxRatio();
|
||||
return torrent->effectiveRatioLimit();
|
||||
case TR_SEEN_COMPLETE_DATE:
|
||||
return torrent->lastSeenComplete();
|
||||
case TR_LAST_ACTIVITY:
|
||||
|
||||
@@ -124,14 +124,14 @@ void TransferListSortModel::sort(const int column, const Qt::SortOrder order)
|
||||
QSortFilterProxyModel::sort(column, order);
|
||||
}
|
||||
|
||||
void TransferListSortModel::setStatusFilter(const TorrentFilter::Type filter)
|
||||
void TransferListSortModel::setStatusFilter(const TorrentFilter::Status status)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
m_filter.setType(filter);
|
||||
m_filter.setStatus(status);
|
||||
endFilterChange(Direction::Rows);
|
||||
#else
|
||||
if (m_filter.setType(filter))
|
||||
if (m_filter.setStatus(status))
|
||||
invalidateRowsFilter();
|
||||
#endif
|
||||
}
|
||||
@@ -184,26 +184,26 @@ void TransferListSortModel::disableTagFilter()
|
||||
#endif
|
||||
}
|
||||
|
||||
void TransferListSortModel::setTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs)
|
||||
void TransferListSortModel::setTrackerFilter(const std::optional<QString> &trackerHost)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
m_filter.setTorrentIDSet(torrentIDs);
|
||||
m_filter.setTrackerHost(trackerHost);
|
||||
endFilterChange(Direction::Rows);
|
||||
#else
|
||||
if (m_filter.setTorrentIDSet(torrentIDs))
|
||||
if (m_filter.setTrackerHost(trackerHost))
|
||||
invalidateRowsFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
void TransferListSortModel::disableTrackerFilter()
|
||||
void TransferListSortModel::setAnnounceStatusFilter(const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0)
|
||||
beginFilterChange();
|
||||
m_filter.setTorrentIDSet(TorrentFilter::AnyID);
|
||||
m_filter.setAnnounceStatus(announceStatus);
|
||||
endFilterChange(Direction::Rows);
|
||||
#else
|
||||
if (m_filter.setTorrentIDSet(TorrentFilter::AnyID))
|
||||
if (m_filter.setAnnounceStatus(announceStatus))
|
||||
invalidateRowsFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -49,13 +49,13 @@ public:
|
||||
|
||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
|
||||
|
||||
void setStatusFilter(TorrentFilter::Type filter);
|
||||
void setStatusFilter(TorrentFilter::Status status);
|
||||
void setCategoryFilter(const QString &category);
|
||||
void disableCategoryFilter();
|
||||
void setTagFilter(const Tag &tag);
|
||||
void disableTagFilter();
|
||||
void setTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs);
|
||||
void disableTrackerFilter();
|
||||
void setTrackerFilter(const std::optional<QString> &trackerHost);
|
||||
void setAnnounceStatusFilter(const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus);
|
||||
|
||||
private:
|
||||
int compare(const QModelIndex &left, const QModelIndex &right) const;
|
||||
|
||||
@@ -104,20 +104,6 @@ namespace
|
||||
return false;
|
||||
}
|
||||
|
||||
void openDestinationFolder(const BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
const Path contentPath = torrent->contentPath();
|
||||
const Path openedPath = (!contentPath.isEmpty() ? contentPath : torrent->savePath());
|
||||
#ifdef Q_OS_MACOS
|
||||
MacUtils::openFiles({openedPath});
|
||||
#else
|
||||
if (torrent->filesCount() == 1)
|
||||
Utils::Gui::openFolderSelect(openedPath);
|
||||
else
|
||||
Utils::Gui::openPath(openedPath);
|
||||
#endif
|
||||
}
|
||||
|
||||
void removeTorrents(const QList<BitTorrent::Torrent *> &torrents, const bool isDeleteFileSelected)
|
||||
{
|
||||
auto *session = BitTorrent::Session::instance();
|
||||
@@ -592,7 +578,7 @@ void TransferListWidget::hideQueuePosColumn(bool hide)
|
||||
resizeColumnToContents(TransferListModel::TR_QUEUE_POSITION);
|
||||
}
|
||||
|
||||
void TransferListWidget::openSelectedTorrentsFolder() const
|
||||
void TransferListWidget::openSelectedTorrentsFolder()
|
||||
{
|
||||
QSet<Path> paths;
|
||||
#ifdef Q_OS_MACOS
|
||||
@@ -612,7 +598,7 @@ void TransferListWidget::openSelectedTorrentsFolder() const
|
||||
if (!paths.contains(openedPath))
|
||||
{
|
||||
if (torrent->filesCount() == 1)
|
||||
Utils::Gui::openFolderSelect(openedPath);
|
||||
Utils::Gui::openFolderSelect(openedPath, this);
|
||||
else
|
||||
Utils::Gui::openPath(openedPath);
|
||||
}
|
||||
@@ -621,6 +607,20 @@ void TransferListWidget::openSelectedTorrentsFolder() const
|
||||
#endif // Q_OS_MACOS
|
||||
}
|
||||
|
||||
void TransferListWidget::openDestinationFolder(const BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
const Path contentPath = torrent->contentPath();
|
||||
const Path openedPath = (!contentPath.isEmpty() ? contentPath : torrent->savePath());
|
||||
#ifdef Q_OS_MACOS
|
||||
MacUtils::openFiles({openedPath});
|
||||
#else
|
||||
if (torrent->filesCount() == 1)
|
||||
Utils::Gui::openFolderSelect(openedPath, this);
|
||||
else
|
||||
Utils::Gui::openPath(openedPath);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TransferListWidget::previewSelectedTorrents()
|
||||
{
|
||||
for (const BitTorrent::Torrent *torrent : asConst(getSelectedTorrents()))
|
||||
@@ -1342,14 +1342,14 @@ void TransferListWidget::applyTagFilter(const std::optional<Tag> &tag)
|
||||
m_sortFilterModel->setTagFilter(*tag);
|
||||
}
|
||||
|
||||
void TransferListWidget::applyTrackerFilterAll()
|
||||
void TransferListWidget::applyTrackerFilter(const std::optional<QString> &trackerHost)
|
||||
{
|
||||
m_sortFilterModel->disableTrackerFilter();
|
||||
m_sortFilterModel->setTrackerFilter(trackerHost);
|
||||
}
|
||||
|
||||
void TransferListWidget::applyTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs)
|
||||
void TransferListWidget::applyAnnounceStatusFilter(const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus)
|
||||
{
|
||||
m_sortFilterModel->setTrackerFilter(torrentIDs);
|
||||
m_sortFilterModel->setAnnounceStatusFilter(announceStatus);
|
||||
}
|
||||
|
||||
void TransferListWidget::applyFilter(const QString &name, const TransferListModel::Column &type)
|
||||
@@ -1362,7 +1362,7 @@ void TransferListWidget::applyFilter(const QString &name, const TransferListMode
|
||||
|
||||
void TransferListWidget::applyStatusFilter(const int filterIndex)
|
||||
{
|
||||
const auto filterType = static_cast<TorrentFilter::Type>(filterIndex);
|
||||
const auto filterType = static_cast<TorrentFilter::Status>(filterIndex);
|
||||
m_sortFilterModel->setStatusFilter(((filterType >= TorrentFilter::All) && (filterType < TorrentFilter::_Count)) ? filterType : TorrentFilter::All);
|
||||
// Select first item if nothing is selected
|
||||
if (selectionModel()->selectedRows(0).empty() && (m_sortFilterModel->rowCount() > 0))
|
||||
|
||||
@@ -89,7 +89,8 @@ public slots:
|
||||
void copySelectedInfohashes(CopyInfohashPolicy policy) const;
|
||||
void copySelectedIDs() const;
|
||||
void copySelectedComments() const;
|
||||
void openSelectedTorrentsFolder() const;
|
||||
void openSelectedTorrentsFolder();
|
||||
void openDestinationFolder(const BitTorrent::Torrent *torrent);
|
||||
void recheckSelectedTorrents();
|
||||
void reannounceSelectedTorrents();
|
||||
void setTorrentOptions();
|
||||
@@ -99,8 +100,8 @@ public slots:
|
||||
void applyStatusFilter(int filterIndex);
|
||||
void applyCategoryFilter(const QString &category);
|
||||
void applyTagFilter(const std::optional<Tag> &tag);
|
||||
void applyTrackerFilterAll();
|
||||
void applyTrackerFilter(const QSet<BitTorrent::TorrentID> &torrentIDs);
|
||||
void applyTrackerFilter(const std::optional<QString> &trackerHost);
|
||||
void applyAnnounceStatusFilter(const std::optional<BitTorrent::TorrentAnnounceStatus> &announceStatus);
|
||||
void previewFile(const Path &filePath);
|
||||
void renameSelectedTorrent();
|
||||
|
||||
|
||||
@@ -76,13 +76,11 @@ UIThemeManager::UIThemeManager()
|
||||
, m_useSystemIcons {Preferences::instance()->useSystemIcons()}
|
||||
#endif
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
if (const QString styleName = Preferences::instance()->getStyle(); styleName.compare(u"system", Qt::CaseInsensitive) != 0)
|
||||
{
|
||||
if (!QApplication::setStyle(styleName.isEmpty() ? u"Fusion"_s : styleName))
|
||||
if (!QApplication::setStyle(styleName))
|
||||
LogMsg(tr("Set app style failed. Unknown style: \"%1\"").arg(styleName), Log::WARNING);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef QBT_HAS_COLORSCHEME_OPTION
|
||||
applyColorScheme();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user