Compare commits

...

24 Commits

Author SHA1 Message Date
Chocobo1
a5b5ba2065 GHA CI: improve manageability for boost version
Adopted suggestion from: https://github.com/qbittorrent/qBittorrent/pull/23631#discussion_r2637707057

PR #23671.
2025-12-30 21:59:40 +08:00
Chocobo1
14d2db185a WebUI: simplify code for filtering nodes
Now it use object property to store visited information instead of an array.
Also prefix temporary properties with underscore.
There are no user visible changes.

PR #23670.
2025-12-30 21:50:09 +08:00
Chocobo1
351b065553 Migrate away from old libtorrent setting
The setting has been renamed from `peer_tos` to `peer_dscp` and the default value has changed
from `0x04` to `0x01`.
Note that in WebAPI and qbt config, the previous name is retained to avoid disruption to user.

Upstream PR:
https://github.com/arvidn/libtorrent/pull/6822
https://github.com/arvidn/libtorrent/pull/8072

PR #23669.
2025-12-30 21:17:37 +08:00
Vladimir Golovnev
52a6e7229b Refresh announce status if trackers were removed or replaced
PR #23668.
2025-12-29 11:50:58 +03:00
xavier2k6
be12be2b79 GHA CI: Bump pandoc version
https://github.com/jgm/pandoc/releases/

PR #23638.
2025-12-27 23:02:46 +08:00
Vladimir Golovnev
0bba3f342e Fix incorrect save path when torrent is added from watched folder
PR #23656.
Closes #23302.
2025-12-27 09:04:06 +03:00
Chocobo1
ae7cc7445b Enable strict type checking for Pyright
Doc: https://github.com/microsoft/pyright/blob/main/docs/configuration.md#type-check-diagnostics-settings

PR #23634.
2025-12-22 12:14:38 +08:00
Chocobo1
e79be5853e Enforce Python to run in UTF-8 mode
When running Python via QProcess on Windows, the output code page will be set to the system default instead of UTF-8.
This commit enforces that UTF-8 will be used unconditionally.

Supersedes #23629.
PR #23633.
2025-12-22 12:00:27 +08:00
Chocobo1
b718cc52fa GHA CI: separate Boost version for different libtorrent branches
PR #23631.
2025-12-22 11:20:33 +08:00
Vladimir Golovnev
57c99b54c8 Move torrents to parent category when category is removed
PR #23620.
2025-12-21 19:49:29 +03:00
Vladimir Golovnev
25224f6050 Remove indentation if no subcategory exists
PR #23619.
2025-12-21 19:49:02 +03:00
Vladimir Golovnev
b5d16dfeee Allow to set torrent share limits per category
PR #23577.
2025-12-21 19:48:31 +03:00
Tom Piccirello
1c231ce014 WebUI: keep preferences window open after unsuccessful save
This gives the user another opportunity to save their changes.

PR #23549.
2025-12-20 17:21:26 +08:00
Vladimir Golovnev
93470f2080 Use subcategories unconditionally
PR #23585.
2025-12-16 21:56:15 +03:00
Andrei Stepanov
260563d340 NSIS: Update Russian translation
PR #23602.
2025-12-15 16:40:17 +08:00
Burak Yavuz
b792ecede5 NSIS: Update Turkish translation
PR #23434.
2025-12-15 16:34:24 +08:00
John Veness
19ebf67c74 Use consistent text for "Do not download" priority
To match priority setting in menu.

PR #23593.
2025-12-15 16:28:19 +08:00
xavier2k6
56cd98e06e GHA CI: Bump Boost dependency in coverity-scan workflow
* Bumped Boost to `1.90.0`
https://www.boost.org/releases/1.90.0/

PR #23591.
2025-12-15 16:21:34 +08:00
Mark Yu
c25bd6aaea GHA CI: Include all image format plugins in Windows installer
Add support for WebP in the RSS viewer

Closes #23169.
Closes #23573.
PR #23590.
2025-12-15 16:14:49 +08:00
tehcneko
26e42abf32 WebUI: Clean up duplicated codes in dynamic table
Call class super methods.

PR #23576.
2025-12-15 16:07:35 +08:00
Chocobo1
5abf458e69 Calculate torrent pieces asynchronously
So the GUI won't hang when the calculation took a long time.
Note that it is not possible to cancel the calculation so it will always run until finish in the background.

Supersedes #23497.
PR #23584.
2025-12-13 14:22:20 +08:00
dependabot[bot]
7451552e6a GHA CI: Bump actions version
PR #23566.

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-13 14:13:34 +08:00
Vladimir Golovnev
96161627d6 Fix invalid index
PR #23586.
2025-12-10 16:26:39 +03:00
xavier2k6
0f80d3a74a GHA CI: Bump Qt version
* Bump `Qt` version to `6.10.1` for (Windows, macOS, coverity-scan)

PR #23563.
2025-12-07 18:45:10 +08:00
64 changed files with 819 additions and 809 deletions

View File

@@ -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

View File

@@ -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
@@ -55,13 +63,9 @@ jobs:
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 \
@@ -163,5 +168,5 @@ jobs:
- name: Upload build artifacts
uses: actions/upload-artifact@v5
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

View File

@@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
persist-credentials: false

View File

@@ -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
@@ -50,13 +58,9 @@ jobs:
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 \
@@ -101,7 +106,7 @@ jobs:
# to avoid scanning 3rdparty codebases, initialize it just before building qbt
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
if: startsWith(matrix.libt_version, 2) && (matrix.qbt_gui == 'GUI=ON')
if: startsWith(matrix.libtorrent.version, 2) && (matrix.qbt_gui == 'GUI=ON')
with:
config-file: ./.github/workflows/helper/codeql/cpp.yaml
languages: cpp
@@ -132,7 +137,7 @@ jobs:
- name: Run CodeQL analysis
uses: github/codeql-action/analyze@v4
if: startsWith(matrix.libt_version, 2) && (matrix.qbt_gui == 'GUI=ON')
if: startsWith(matrix.libtorrent.version, 2) && (matrix.qbt_gui == 'GUI=ON')
with:
category: ${{ github.base_ref || github.ref_name }}
@@ -179,5 +184,5 @@ jobs:
- name: Upload build artifacts
uses: actions/upload-artifact@v5
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

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
persist-credentials: false

View File

@@ -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
@@ -202,7 +215,7 @@ jobs:
- name: Upload build artifacts
uses: actions/upload-artifact@v5
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
@@ -218,5 +231,5 @@ jobs:
- name: Upload installer
uses: actions/upload-artifact@v5
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

View File

@@ -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 \

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -34,7 +34,7 @@ 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

View File

@@ -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 =
{

View File

@@ -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;

View File

@@ -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();

View File

@@ -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));
}

View File

@@ -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;
};
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();
@@ -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

View File

@@ -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;
@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -37,7 +37,7 @@
#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"
@@ -125,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;
@@ -236,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;
@@ -279,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;

View File

@@ -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);
@@ -727,6 +728,7 @@ void TorrentImpl::replaceTrackers(QList<TrackerEntry> trackers)
}
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.
@@ -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
@@ -1338,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();
@@ -1479,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;
@@ -2623,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)
@@ -2636,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)
@@ -2649,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)
{

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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
};

View File

@@ -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)
{

View File

@@ -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
{

View File

@@ -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));
}

View File

@@ -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;

View File

@@ -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));

View File

@@ -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,

View File

@@ -48,7 +48,7 @@
#include <QTranslator>
#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"
@@ -625,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"));
@@ -730,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);
@@ -802,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()));

View File

@@ -1381,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">

View File

@@ -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));
}

View File

@@ -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;
};

View File

@@ -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/>

View File

@@ -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:

View File

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

View File

@@ -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();

View File

@@ -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));
}
}

View File

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

View File

@@ -52,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)
@@ -60,6 +83,30 @@ 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({});
@@ -71,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()
@@ -83,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);
}
@@ -100,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);
}
@@ -117,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);
}
@@ -155,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
@@ -181,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:
@@ -196,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:
@@ -211,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:
@@ -240,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)));
}
}

View File

@@ -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;
};

View File

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

View File

@@ -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)))
{

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -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();
};

View File

@@ -49,18 +49,6 @@
#include "transferlistwidget.h"
#include "utils.h"
namespace
{
enum ItemPos
{
StatusItemPos,
CategoryItemPos,
TagItemPos,
TrackerStatusItemPos,
TrackersItemPos
};
}
TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferListWidget *transferList, const bool downloadFavicon)
: QWidget(parent)
, m_transferList {transferList}
@@ -80,7 +68,7 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
auto *item = new TransferListFiltersWidgetItem(tr("Status"), new StatusFilterWidget(this, transferList), this);
item->setChecked(pref->getStatusFilterState());
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setStatusFilterState);
mainWidgetLayout->insertWidget(StatusItemPos, item);
mainWidgetLayout->addWidget(item);
}
{
@@ -101,7 +89,7 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
m_transferList->applyCategoryFilter(enabled ? categoryFilterWidget->currentCategory() : QString());
});
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setCategoryFilterState);
mainWidgetLayout->insertWidget(CategoryItemPos, item);
mainWidgetLayout->addWidget(item);
}
{
@@ -122,16 +110,18 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
m_transferList->applyTagFilter(enabled ? tagFilterWidget->currentTag() : std::nullopt);
});
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setTagFilterState);
mainWidgetLayout->insertWidget(TagItemPos, item);
mainWidgetLayout->addWidget(item);
}
const int trackerStatusItemPos = mainWidgetLayout->count();
{
m_trackersFilterWidget = new TrackersFilterWidget(this, transferList, downloadFavicon);
auto *item = new TransferListFiltersWidgetItem(tr("Trackers"), m_trackersFilterWidget, this);
item->setChecked(pref->getTrackerFilterState());
connect(item, &TransferListFiltersWidgetItem::toggled, pref, &Preferences::setTrackerFilterState);
mainWidgetLayout->insertWidget(TrackersItemPos, item);
mainWidgetLayout->addWidget(item);
}
auto *scroll = new QScrollArea(this);
@@ -144,17 +134,17 @@ TransferListFiltersWidget::TransferListFiltersWidget(QWidget *parent, TransferLi
vLayout->setContentsMargins(0, 0, 0, 0);
vLayout->addWidget(scroll);
const auto createTrackerStatusItem = [this, mainWidgetLayout, pref]
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);
mainWidgetLayout->insertWidget(trackerStatusItemPos, item);
};
const auto removeTrackerStatusItem = [mainWidgetLayout]
const auto removeTrackerStatusItem = [mainWidgetLayout, trackerStatusItemPos]
{
QLayoutItem *layoutItem = mainWidgetLayout->takeAt(TrackerStatusItemPos);
QLayoutItem *layoutItem = mainWidgetLayout->takeAt(trackerStatusItemPos);
delete layoutItem->widget();
delete layoutItem;
};

View File

@@ -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:

View File

@@ -1,4 +1,4 @@
# VERSION: 1.50
# VERSION: 1.51
# Author:
# Fabien Devaux <fab AT gnux DOT info>
@@ -61,7 +61,7 @@ THREADED: bool = True
try:
MAX_THREADS: int = cpu_count()
except NotImplementedError:
MAX_THREADS = 1
MAX_THREADS = 1 # pyright: ignore[reportConstantRedefinition]
Category = Enum('Category', ['all', 'anime', 'books', 'games', 'movies', 'music', 'pictures', 'software', 'tv'])
@@ -106,7 +106,7 @@ def list_engines() -> list[EngineModuleName]:
Return list of all engines' module name
"""
names = []
names: list[EngineModuleName] = []
for engine_path in glob(path.join(path.dirname(__file__), 'engines', '*.py')):
engine_module_name = path.basename(engine_path).split('.')[0].strip()

View File

@@ -21,5 +21,8 @@ dev = [
explicit_package_bases = true
strict = true
[tool.pyright]
typeCheckingMode = "strict"
[tool.setuptools.packages.find]
where = ["./"]

View File

@@ -174,7 +174,6 @@ void AppController::preferencesAction()
data[u"torrent_changed_tmm_enabled"_s] = !session->isDisableAutoTMMWhenCategoryChanged();
data[u"save_path_changed_tmm_enabled"_s] = !session->isDisableAutoTMMWhenDefaultSavePathChanged();
data[u"category_changed_tmm_enabled"_s] = !session->isDisableAutoTMMWhenCategorySavePathChanged();
data[u"use_subcategories"] = session->isSubcategoriesEnabled();
data[u"save_path"_s] = session->savePath().toString();
data[u"temp_path_enabled"_s] = session->isDownloadPathEnabled();
data[u"temp_path"_s] = session->downloadPath().toString();
@@ -462,7 +461,7 @@ void AppController::preferencesAction()
// UPnP lease duration
data[u"upnp_lease_duration"_s] = session->UPnPLeaseDuration();
// Type of service
data[u"peer_tos"_s] = session->peerToS();
data[u"peer_tos"_s] = session->peerDSCP();
// uTP-TCP mixed mode
data[u"utp_tcp_mixed_mode"_s] = static_cast<int>(session->utpMixedMode());
// Hostname resolver cache TTL
@@ -593,8 +592,6 @@ void AppController::setPreferencesAction()
session->setDisableAutoTMMWhenDefaultSavePathChanged(!it.value().toBool());
if (hasKey(u"category_changed_tmm_enabled"_s))
session->setDisableAutoTMMWhenCategorySavePathChanged(!it.value().toBool());
if (hasKey(u"use_subcategories"_s))
session->setSubcategoriesEnabled(it.value().toBool());
if (hasKey(u"save_path"_s))
session->setSavePath(Path(it.value().toString()));
if (hasKey(u"temp_path_enabled"_s))
@@ -1117,7 +1114,7 @@ void AppController::setPreferencesAction()
session->setUPnPLeaseDuration(it.value().toInt());
// Type of service
if (hasKey(u"peer_tos"_s))
session->setPeerToS(it.value().toInt());
session->setPeerDSCP(it.value().toInt());
// uTP-TCP mixed mode
if (hasKey(u"utp_tcp_mixed_mode"_s))
session->setUtpMixedMode(static_cast<BitTorrent::MixedModeAlgorithm>(it.value().toInt()));

View File

@@ -163,9 +163,9 @@ QVariantMap serialize(const BitTorrent::Torrent &torrent)
{KEY_TORRENT_AMOUNT_COMPLETED, torrent.completedSize()},
{KEY_TORRENT_CONNECTIONS_COUNT, torrent.connectionsCount()},
{KEY_TORRENT_CONNECTIONS_LIMIT, torrent.connectionsLimit()},
{KEY_TORRENT_MAX_RATIO, torrent.maxRatio()},
{KEY_TORRENT_MAX_SEEDING_TIME, torrent.maxSeedingTime()},
{KEY_TORRENT_MAX_INACTIVE_SEEDING_TIME, torrent.maxInactiveSeedingTime()},
{KEY_TORRENT_MAX_RATIO, torrent.effectiveRatioLimit()},
{KEY_TORRENT_MAX_SEEDING_TIME, torrent.effectiveSeedingTimeLimit()},
{KEY_TORRENT_MAX_INACTIVE_SEEDING_TIME, torrent.effectiveInactiveSeedingTimeLimit()},
{KEY_TORRENT_RATIO, adjustRatio(torrent.realRatio())},
{KEY_TORRENT_RATIO_LIMIT, torrent.ratioLimit()},
{KEY_TORRENT_POPULARITY, torrent.popularity()},

View File

@@ -56,7 +56,6 @@ namespace
const QString KEY_SYNC_MAINDATA_QUEUEING = u"queueing"_s;
const QString KEY_SYNC_MAINDATA_REFRESH_INTERVAL = u"refresh_interval"_s;
const QString KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS = u"use_alt_speed_limits"_s;
const QString KEY_SYNC_MAINDATA_USE_SUBCATEGORIES = u"use_subcategories"_s;
// Sync torrent peers keys
const QString KEY_SYNC_TORRENT_PEERS_SHOW_FLAGS = u"show_flags"_s;
@@ -617,7 +616,6 @@ void SyncController::makeMaindataSnapshot()
m_maindataSnapshot.serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled();
m_maindataSnapshot.serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled();
m_maindataSnapshot.serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval();
m_maindataSnapshot.serverState[KEY_SYNC_MAINDATA_USE_SUBCATEGORIES] = session->isSubcategoriesEnabled();
}
QJsonObject SyncController::generateMaindataSyncData(const int id, const bool fullUpdate)
@@ -771,7 +769,6 @@ QJsonObject SyncController::generateMaindataSyncData(const int id, const bool fu
serverState[KEY_SYNC_MAINDATA_QUEUEING] = session->isQueueingSystemEnabled();
serverState[KEY_SYNC_MAINDATA_USE_ALT_SPEED_LIMITS] = session->isAltGlobalSpeedLimitEnabled();
serverState[KEY_SYNC_MAINDATA_REFRESH_INTERVAL] = session->refreshInterval();
serverState[KEY_SYNC_MAINDATA_USE_SUBCATEGORIES] = session->isSubcategoriesEnabled();
if (const QVariantMap syncData = processMap(m_maindataSnapshot.serverState, serverState); !syncData.isEmpty())
{
m_maindataSyncBuf.serverState = syncData;

View File

@@ -1066,9 +1066,9 @@ void TorrentsController::addAction()
const QString torrentName = params()[u"rename"_s].trimmed();
const int upLimit = parseInt(params()[u"upLimit"_s]).value_or(-1);
const int dlLimit = parseInt(params()[u"dlLimit"_s]).value_or(-1);
const double ratioLimit = parseDouble(params()[u"ratioLimit"_s]).value_or(BitTorrent::Torrent::USE_GLOBAL_RATIO);
const int seedingTimeLimit = parseInt(params()[u"seedingTimeLimit"_s]).value_or(BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME);
const int inactiveSeedingTimeLimit = parseInt(params()[u"inactiveSeedingTimeLimit"_s]).value_or(BitTorrent::Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME);
const double ratioLimit = parseDouble(params()[u"ratioLimit"_s]).value_or(BitTorrent::DEFAULT_RATIO_LIMIT);
const int seedingTimeLimit = parseInt(params()[u"seedingTimeLimit"_s]).value_or(BitTorrent::DEFAULT_SEEDING_TIME_LIMIT);
const int inactiveSeedingTimeLimit = parseInt(params()[u"inactiveSeedingTimeLimit"_s]).value_or(BitTorrent::DEFAULT_SEEDING_TIME_LIMIT);
const BitTorrent::ShareLimitAction shareLimitAction = Utils::String::toEnum(params()[u"shareLimitAction"_s], BitTorrent::ShareLimitAction::Default);
const std::optional<bool> autoTMM = parseBool(params()[u"autoTMM"_s]);
@@ -1897,7 +1897,7 @@ void TorrentsController::editCategoryAction()
categoryOptions.downloadPath = {useDownloadPath.value(), downloadPath};
}
if (!BitTorrent::Session::instance()->editCategory(category, categoryOptions))
if (!BitTorrent::Session::instance()->setCategoryOptions(category, categoryOptions))
throw APIError(APIErrorType::Conflict, tr("Unable to edit category"));
setResult(QString());

View File

@@ -57,7 +57,7 @@
using namespace std::chrono_literals;
inline const Utils::Version<3, 2> API_VERSION {2, 14, 1};
inline const Utils::Version<3, 2> API_VERSION {2, 15, 0};
class APIController;
class AuthController;

View File

@@ -222,7 +222,6 @@ let alternativeSpeedLimits = false;
let queueing_enabled = true;
let serverSyncMainDataInterval = 1500;
let customSyncMainDataInterval = null;
let useSubcategories = true;
const useAutoHideZeroStatusFilters = localPreferences.get("hide_zero_status_filters", "false") === "true";
const displayFullURLTrackerColumn = localPreferences.get("full_url_tracker_column", "false") === "true";
@@ -634,7 +633,7 @@ window.addEventListener("DOMContentLoaded", (event) => {
categoryName: category,
categoryCount: categoryData.torrents.size,
nameSegments: category.split("/"),
...(useSubcategories && {
...({
children: [],
isRoot: true,
forceExpand: localPreferences.get(`category_${category}_collapsed`) === null
@@ -659,32 +658,25 @@ window.addEventListener("DOMContentLoaded", (event) => {
categoriesFragment.appendChild(createLink(CATEGORIES_ALL, "QBT_TR(All)QBT_TR[CONTEXT=CategoryFilterModel]", torrentsTable.getRowSize()));
categoriesFragment.appendChild(createLink(CATEGORIES_UNCATEGORIZED, "QBT_TR(Uncategorized)QBT_TR[CONTEXT=CategoryFilterModel]", uncategorized));
if (useSubcategories) {
categoryList.classList.add("subcategories");
for (let i = 0; i < sortedCategories.length; ++i) {
const category = sortedCategories[i];
for (let j = (i + 1);
((j < sortedCategories.length) && sortedCategories[j].categoryName.startsWith(`${category.categoryName}/`)); ++j) {
const subcategory = sortedCategories[j];
category.categoryCount += subcategory.categoryCount;
category.forceExpand ||= subcategory.forceExpand;
categoryList.classList.add("subcategories");
for (let i = 0; i < sortedCategories.length; ++i) {
const category = sortedCategories[i];
for (let j = (i + 1);
((j < sortedCategories.length) && sortedCategories[j].categoryName.startsWith(`${category.categoryName}/`)); ++j) {
const subcategory = sortedCategories[j];
category.categoryCount += subcategory.categoryCount;
category.forceExpand ||= subcategory.forceExpand;
const isDirectSubcategory = (subcategory.nameSegments.length - category.nameSegments.length) === 1;
if (isDirectSubcategory) {
subcategory.isRoot = false;
category.children.push(subcategory);
}
const isDirectSubcategory = (subcategory.nameSegments.length - category.nameSegments.length) === 1;
if (isDirectSubcategory) {
subcategory.isRoot = false;
category.children.push(subcategory);
}
}
for (const category of sortedCategories) {
if (category.isRoot)
createCategoryTree(category);
}
}
else {
categoryList.classList.remove("subcategories");
for (const { categoryName, categoryCount } of sortedCategories)
categoriesFragment.appendChild(createLink(categoryName, categoryName, categoryCount));
for (const category of sortedCategories) {
if (category.isRoot)
createCategoryTree(category);
}
categoryList.appendChild(categoriesFragment);
@@ -1178,11 +1170,6 @@ window.addEventListener("DOMContentLoaded", (event) => {
updateAltSpeedIcon(alternativeSpeedLimits);
}
if (useSubcategories !== serverState.use_subcategories) {
useSubcategories = serverState.use_subcategories;
updateCategoryList();
}
serverSyncMainDataInterval = Math.max(serverState.refresh_interval, 500);
};

View File

@@ -585,10 +585,7 @@ window.qBittorrent.ContextMenu ??= (() => {
if ((id !== CATEGORIES_ALL) && (id !== CATEGORIES_UNCATEGORIZED)) {
this.showItem("editCategory");
this.showItem("deleteCategory");
if (useSubcategories)
this.showItem("createSubcategory");
else
this.hideItem("createSubcategory");
this.showItem("createSubcategory");
}
else {
this.hideItem("editCategory");

View File

@@ -564,7 +564,8 @@ window.qBittorrent.DynamicTable ??= (() => {
column["force_hide"] = false;
column["caption"] = caption;
column["style"] = style;
column["width"] = localPreferences.get(`column_${name}_width_${this.dynamicTableDivId}`, defaultWidth);
if (defaultWidth !== -1)
column["width"] = localPreferences.get(`column_${name}_width_${this.dynamicTableDivId}`, defaultWidth);
column["dataProperties"] = [name];
column["getRowValue"] = function(row, pos) {
if (pos === undefined)
@@ -1620,21 +1621,16 @@ window.qBittorrent.DynamicTable ??= (() => {
return false;
break; // do nothing
default:
if (!useSubcategories) {
if (category !== row["full_data"].category)
default: {
const selectedCategory = window.qBittorrent.Client.categoryMap.get(category);
if (selectedCategory !== undefined) {
const selectedCategoryName = `${category}/`;
const torrentCategoryName = `${row["full_data"].category}/`;
if (!torrentCategoryName.startsWith(selectedCategoryName))
return false;
}
else {
const selectedCategory = window.qBittorrent.Client.categoryMap.get(category);
if (selectedCategory !== undefined) {
const selectedCategoryName = `${category}/`;
const torrentCategoryName = `${row["full_data"].category}/`;
if (!torrentCategoryName.startsWith(selectedCategoryName))
return false;
}
}
break;
}
}
switch (tag) {
@@ -2693,32 +2689,30 @@ window.qBittorrent.DynamicTable ??= (() => {
#filterNodes(root, filterTerms) {
const ret = [];
const stack = [root];
const visited = [];
while (stack.length > 0) {
const node = stack.at(-1);
if (node.isFolder && (!this.useVirtualList || !this.isCollapsed(node.rowId))) {
const lastVisited = visited.at(-1);
if ((visited.length <= 0) || (lastVisited !== node)) {
visited.push(node);
if (node._visited === undefined) {
node._visited = true;
stack.push(...node.children);
continue;
}
// has children added or itself matches
if (lastVisited.has_children_added || window.qBittorrent.Misc.containsAllTerms(node.name, filterTerms)) {
// ready to check results from children at this point
if (node._hasChildrenAdded || window.qBittorrent.Misc.containsAllTerms(node.name, filterTerms)) {
ret.push(this.getRow(node));
delete node.has_children_added;
delete node._hasChildrenAdded;
// propagate up
const parent = node.root;
if (parent !== undefined)
parent.has_children_added = true;
parent._hasChildrenAdded = true;
}
visited.pop();
delete node._visited;
}
else {
if (window.qBittorrent.Misc.containsAllTerms(node[this.fileNameColumn], filterTerms)) {
@@ -2726,7 +2720,7 @@ window.qBittorrent.DynamicTable ??= (() => {
const parent = node.root;
if (parent !== undefined)
parent.has_children_added = true;
parent._hasChildrenAdded = true;
}
}
@@ -3098,9 +3092,7 @@ window.qBittorrent.DynamicTable ??= (() => {
return [...this.getRowValues()];
}
selectRow(rowId) {
this.selectedRows.push(rowId);
this.setRowClass();
this.onSelectedRowChanged();
super.selectRow(rowId);
let path = "";
for (const row of this.getRowValues()) {
@@ -3177,42 +3169,6 @@ window.qBittorrent.DynamicTable ??= (() => {
}
}
}
newColumn(name, style, caption, defaultWidth, defaultVisible) {
const column = {};
column["name"] = name;
column["title"] = name;
column["visible"] = defaultVisible;
column["force_hide"] = false;
column["caption"] = caption;
column["style"] = style;
if (defaultWidth !== -1)
column["width"] = defaultWidth;
column["dataProperties"] = [name];
column["getRowValue"] = function(row, pos) {
if (pos === undefined)
pos = 0;
return row["full_data"][this.dataProperties[pos]];
};
column["compareRows"] = function(row1, row2) {
const value1 = this.getRowValue(row1);
const value2 = this.getRowValue(row2);
if ((typeof(value1) === "number") && (typeof(value2) === "number"))
return compareNumbers(value1, value2);
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
};
column["updateTd"] = function(td, row) {
const value = this.getRowValue(row);
td.textContent = value;
td.title = value;
};
column["onResize"] = null;
this.columns.push(column);
this.columns[name] = column;
this.hiddenTableHeader.append(document.createElement("th"));
this.fixedTableHeader.append(document.createElement("th"));
}
}
class RssArticleTable extends DynamicTable {
@@ -3225,9 +3181,7 @@ window.qBittorrent.DynamicTable ??= (() => {
return [...this.getRowValues()];
}
selectRow(rowId) {
this.selectedRows.push(rowId);
this.setRowClass();
this.onSelectedRowChanged();
super.selectRow(rowId);
let articleId = "";
let feedUid = "";
@@ -3259,42 +3213,6 @@ window.qBittorrent.DynamicTable ??= (() => {
return super.updateRow(tr, fullUpdate);
}
newColumn(name, style, caption, defaultWidth, defaultVisible) {
const column = {};
column["name"] = name;
column["title"] = name;
column["visible"] = defaultVisible;
column["force_hide"] = false;
column["caption"] = caption;
column["style"] = style;
if (defaultWidth !== -1)
column["width"] = defaultWidth;
column["dataProperties"] = [name];
column["getRowValue"] = function(row, pos) {
if (pos === undefined)
pos = 0;
return row["full_data"][this.dataProperties[pos]];
};
column["compareRows"] = function(row1, row2) {
const value1 = this.getRowValue(row1);
const value2 = this.getRowValue(row2);
if ((typeof(value1) === "number") && (typeof(value2) === "number"))
return compareNumbers(value1, value2);
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
};
column["updateTd"] = function(td, row) {
const value = this.getRowValue(row);
td.textContent = value;
td.title = value;
};
column["onResize"] = null;
this.columns.push(column);
this.columns[name] = column;
this.hiddenTableHeader.append(document.createElement("th"));
this.fixedTableHeader.append(document.createElement("th"));
}
}
class RssDownloaderRulesTable extends DynamicTable {
@@ -3342,46 +3260,8 @@ window.qBittorrent.DynamicTable ??= (() => {
window.qBittorrent.RssDownloader.renameRule(this.getRow(tr.rowId).full_data.name);
});
}
newColumn(name, style, caption, defaultWidth, defaultVisible) {
const column = {};
column["name"] = name;
column["title"] = name;
column["visible"] = defaultVisible;
column["force_hide"] = false;
column["caption"] = caption;
column["style"] = style;
if (defaultWidth !== -1)
column["width"] = defaultWidth;
column["dataProperties"] = [name];
column["getRowValue"] = function(row, pos) {
if (pos === undefined)
pos = 0;
return row["full_data"][this.dataProperties[pos]];
};
column["compareRows"] = function(row1, row2) {
const value1 = this.getRowValue(row1);
const value2 = this.getRowValue(row2);
if ((typeof(value1) === "number") && (typeof(value2) === "number"))
return compareNumbers(value1, value2);
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
};
column["updateTd"] = function(td, row) {
const value = this.getRowValue(row);
td.textContent = value;
td.title = value;
};
column["onResize"] = null;
this.columns.push(column);
this.columns[name] = column;
this.hiddenTableHeader.append(document.createElement("th"));
this.fixedTableHeader.append(document.createElement("th"));
}
selectRow(rowId) {
this.selectedRows.push(rowId);
this.setRowClass();
this.onSelectedRowChanged();
super.selectRow(rowId);
let name = "";
for (const row of this.getRowValues()) {
@@ -3427,42 +3307,6 @@ window.qBittorrent.DynamicTable ??= (() => {
getFilteredAndSortedRows() {
return [...this.getRowValues()];
}
newColumn(name, style, caption, defaultWidth, defaultVisible) {
const column = {};
column["name"] = name;
column["title"] = name;
column["visible"] = defaultVisible;
column["force_hide"] = false;
column["caption"] = caption;
column["style"] = style;
if (defaultWidth !== -1)
column["width"] = defaultWidth;
column["dataProperties"] = [name];
column["getRowValue"] = function(row, pos) {
if (pos === undefined)
pos = 0;
return row["full_data"][this.dataProperties[pos]];
};
column["compareRows"] = function(row1, row2) {
const value1 = this.getRowValue(row1);
const value2 = this.getRowValue(row2);
if ((typeof(value1) === "number") && (typeof(value2) === "number"))
return compareNumbers(value1, value2);
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
};
column["updateTd"] = function(td, row) {
const value = this.getRowValue(row);
td.textContent = value;
td.title = value;
};
column["onResize"] = null;
this.columns.push(column);
this.columns[name] = column;
this.hiddenTableHeader.append(document.createElement("th"));
this.fixedTableHeader.append(document.createElement("th"));
}
selectRow() {}
}
@@ -3475,42 +3319,6 @@ window.qBittorrent.DynamicTable ??= (() => {
getFilteredAndSortedRows() {
return [...this.getRowValues()];
}
newColumn(name, style, caption, defaultWidth, defaultVisible) {
const column = {};
column["name"] = name;
column["title"] = name;
column["visible"] = defaultVisible;
column["force_hide"] = false;
column["caption"] = caption;
column["style"] = style;
if (defaultWidth !== -1)
column["width"] = defaultWidth;
column["dataProperties"] = [name];
column["getRowValue"] = function(row, pos) {
if (pos === undefined)
pos = 0;
return row["full_data"][this.dataProperties[pos]];
};
column["compareRows"] = function(row1, row2) {
const value1 = this.getRowValue(row1);
const value2 = this.getRowValue(row2);
if ((typeof(value1) === "number") && (typeof(value2) === "number"))
return compareNumbers(value1, value2);
return window.qBittorrent.Misc.naturalSortCollator.compare(value1, value2);
};
column["updateTd"] = function(td, row) {
const value = this.getRowValue(row);
td.textContent = value;
td.title = value;
};
column["onResize"] = null;
this.columns.push(column);
this.columns[name] = column;
this.hiddenTableHeader.append(document.createElement("th"));
this.fixedTableHeader.append(document.createElement("th"));
}
selectRow() {}
updateRow(tr, fullUpdate) {
const row = this.rows.get(tr.rowId);

View File

@@ -229,10 +229,6 @@
</tr>
</tbody>
</table>
<div class="formRow">
<input type="checkbox" id="use_subcategories_checkbox">
<label for="use_subcategories_checkbox">QBT_TR(Use Subcategories)QBT_TR[CONTEXT=OptionsDialog]</label>
</div>
<div class="formRow">
<input type="checkbox" id="categoryPathsManualModeCheckbox" title="QBT_TR(Resolve relative Save Path against appropriate Category path instead of Default one)QBT_TR[CONTEXT=OptionsDialog]">
<label for="categoryPathsManualModeCheckbox">QBT_TR(Use Category paths in Manual Mode)QBT_TR[CONTEXT=OptionsDialog]</label>
@@ -1565,10 +1561,10 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
</tr>
<tr>
<td>
<label for="peerToS">QBT_TR(Type of service (ToS) for connections to peers)QBT_TR[CONTEXT=OptionsDialog]&nbsp;<a href="https://www.libtorrent.org/reference-Settings.html#peer_tos" target="_blank">(?)</a></label>
<label for="peerDSCP">QBT_TR(Differentiated Services Code Point (DSCP) for connections to peers)QBT_TR[CONTEXT=OptionsDialog]&nbsp;<a href="https://www.libtorrent.org/reference-Settings.html#peer_dscp" target="_blank">(?)</a></label>
</td>
<td>
<input type="text" id="peerToS" style="width: 15em;">
<input type="text" id="peerDSCP" style="width: 15em;">
</td>
</tr>
<tr>
@@ -2332,7 +2328,6 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
document.getElementById("torrent_changed_tmm_combobox").value = pref.torrent_changed_tmm_enabled;
document.getElementById("save_path_changed_tmm_combobox").value = pref.save_path_changed_tmm_enabled;
document.getElementById("category_changed_tmm_combobox").value = pref.category_changed_tmm_enabled;
document.getElementById("use_subcategories_checkbox").checked = pref.use_subcategories;
document.getElementById("categoryPathsManualModeCheckbox").checked = pref.use_category_paths_in_manual_mode;
document.getElementById("savepath_text").value = pref.save_path;
document.getElementById("temppath_checkbox").checked = pref.temp_path_enabled;
@@ -2673,7 +2668,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
document.getElementById("outgoingPortsMin").value = pref.outgoing_ports_min;
document.getElementById("outgoingPortsMax").value = pref.outgoing_ports_max;
document.getElementById("UPnPLeaseDuration").value = pref.upnp_lease_duration;
document.getElementById("peerToS").value = pref.peer_tos;
document.getElementById("peerDSCP").value = pref.peer_tos;
document.getElementById("utpTCPMixedModeAlgorithm").value = pref.utp_tcp_mixed_mode;
document.getElementById("hostnameCacheTTL").value = pref.hostname_cache_ttl;
document.getElementById("IDNSupportCheckbox").checked = pref.idn_support_enabled;
@@ -2754,7 +2749,6 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
settings["torrent_changed_tmm_enabled"] = (document.getElementById("torrent_changed_tmm_combobox").value === "true");
settings["save_path_changed_tmm_enabled"] = (document.getElementById("save_path_changed_tmm_combobox").value === "true");
settings["category_changed_tmm_enabled"] = (document.getElementById("category_changed_tmm_combobox").value === "true");
settings["use_subcategories"] = document.getElementById("use_subcategories_checkbox").checked;
settings["use_category_paths_in_manual_mode"] = document.getElementById("categoryPathsManualModeCheckbox").checked;
settings["save_path"] = document.getElementById("savepath_text").value;
settings["temp_path_enabled"] = document.getElementById("temppath_checkbox").checked;
@@ -3162,12 +3156,12 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
settings["outgoing_ports_max"] = Number(document.getElementById("outgoingPortsMax").value);
settings["upnp_lease_duration"] = Number(document.getElementById("UPnPLeaseDuration").value);
const peerToS = Number(document.getElementById("peerToS").value);
if (Number.isNaN(peerToS) || (peerToS < 0) || (peerToS > 255)) {
alert("QBT_TR(Peer ToS must be between 0 and 255.)QBT_TR[CONTEXT=HttpServer]");
const peerDSCP = Number(document.getElementById("peerDSCP").value);
if (Number.isNaN(peerDSCP) || (peerDSCP < 0) || (peerDSCP > 255)) {
alert("QBT_TR(Peer DSCP must be between 0 and 255.)QBT_TR[CONTEXT=HttpServer]");
return;
}
settings["peer_tos"] = peerToS;
settings["peer_tos"] = peerDSCP;
settings["utp_tcp_mixed_mode"] = Number(document.getElementById("utpTCPMixedModeAlgorithm").value);
settings["hostname_cache_ttl"] = Number(document.getElementById("hostnameCacheTTL").value);
@@ -3225,8 +3219,8 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD
window.parent.location.reload();
window.parent.qBittorrent.Client.closeWindow(document.getElementById("preferencesPage"));
}).catch((error) => {
// keep window open so user can reattempt saving
alert("QBT_TR(Unable to save program preferences, qBittorrent is probably unreachable.)QBT_TR[CONTEXT=HttpServer]");
window.parent.qBittorrent.Client.closeWindow(document.getElementById("preferencesPage"));
});
};