mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-17 06:01:33 -06:00
Compare commits
141 Commits
release-5.
...
release-5.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cce295faeb | ||
|
|
db5479ea01 | ||
|
|
e1216c4c9a | ||
|
|
f4a0868426 | ||
|
|
59a5fcf7d0 | ||
|
|
f9a2b02a8d | ||
|
|
04f6a565f3 | ||
|
|
3e96048ee4 | ||
|
|
d4ccf3001c | ||
|
|
64506f16bd | ||
|
|
24a7a835af | ||
|
|
93b9bf9552 | ||
|
|
f4125601de | ||
|
|
2d67729617 | ||
|
|
878ebbed41 | ||
|
|
c61c3d7cd8 | ||
|
|
978fbbdc0d | ||
|
|
63689cf763 | ||
|
|
cebc72d3cf | ||
|
|
a67bd271c6 | ||
|
|
a8cffbb205 | ||
|
|
7dfb0110d4 | ||
|
|
3ad8fcbdd2 | ||
|
|
195eae5f3d | ||
|
|
920ae26f7b | ||
|
|
09ed0d6b66 | ||
|
|
4f0cc8aa11 | ||
|
|
4d490c84e7 | ||
|
|
96607ce874 | ||
|
|
418edc7471 | ||
|
|
bd01b7c4df | ||
|
|
b0ac763048 | ||
|
|
127d2d6f0b | ||
|
|
4149609e78 | ||
|
|
78c549f83e | ||
|
|
a3a53e2e0e | ||
|
|
5aaa43e01d | ||
|
|
86745d7b07 | ||
|
|
210650a5ee | ||
|
|
fe93b6d0d8 | ||
|
|
e8b585acd8 | ||
|
|
cea20141a9 | ||
|
|
0f5a27ed50 | ||
|
|
c2cf898ccd | ||
|
|
5e5aa8a563 | ||
|
|
12a4c3fda2 | ||
|
|
5f50b701d2 | ||
|
|
9f20d9c3aa | ||
|
|
05e3130baa | ||
|
|
683492648f | ||
|
|
2f2e158877 | ||
|
|
e60e96cb0e | ||
|
|
5f31208bf1 | ||
|
|
fa58e58e70 | ||
|
|
671943a9a6 | ||
|
|
8bad80bcdd | ||
|
|
c44e300507 | ||
|
|
318a677e8f | ||
|
|
0246df790a | ||
|
|
782fbc1425 | ||
|
|
7deccd5592 | ||
|
|
4a36fe7278 | ||
|
|
1c5af96ad8 | ||
|
|
3bb47a5410 | ||
|
|
d7abeb4bf0 | ||
|
|
a19d623ead | ||
|
|
1ef21bc2b7 | ||
|
|
4687b4e8e4 | ||
|
|
d2e5163861 | ||
|
|
8a15ea8026 | ||
|
|
2b99554813 | ||
|
|
e6638f9c19 | ||
|
|
ec6eac2ba1 | ||
|
|
a126a7b493 | ||
|
|
b8a774f1fb | ||
|
|
e09a871ca3 | ||
|
|
04154ebb76 | ||
|
|
fb796ec595 | ||
|
|
00ca209ab9 | ||
|
|
4d8713ce11 | ||
|
|
2c47f09d7a | ||
|
|
a19ef58400 | ||
|
|
21a4ab6bac | ||
|
|
2b728b3bc0 | ||
|
|
6231208ddf | ||
|
|
e2d6cd31b2 | ||
|
|
79eb7b8e38 | ||
|
|
8ef7d3ec9a | ||
|
|
05416458db | ||
|
|
cd3982cf3c | ||
|
|
a1af077889 | ||
|
|
42b87963fd | ||
|
|
775b38079f | ||
|
|
d65d8558d6 | ||
|
|
b1175b60e1 | ||
|
|
d3315f7cc7 | ||
|
|
321d7e5b17 | ||
|
|
4ac586c896 | ||
|
|
ca71c186e0 | ||
|
|
ddb0ff29e2 | ||
|
|
6c57fad0cd | ||
|
|
1c7ecb7371 | ||
|
|
4945ed576a | ||
|
|
c6f4e95b7d | ||
|
|
fc3953dbaa | ||
|
|
75e2ae2fa0 | ||
|
|
7310eec74e | ||
|
|
3e0fd01604 | ||
|
|
ace5286402 | ||
|
|
d7cded54e4 | ||
|
|
6c82d5e305 | ||
|
|
c036313adf | ||
|
|
29f0adf215 | ||
|
|
e697d40382 | ||
|
|
01cc4ea90b | ||
|
|
d407e954d1 | ||
|
|
f085f8c076 | ||
|
|
92ce507151 | ||
|
|
67dfce7437 | ||
|
|
e4aad461c7 | ||
|
|
f37d0c486c | ||
|
|
8e6515be2c | ||
|
|
1d221c22e4 | ||
|
|
2fe91a6c8f | ||
|
|
90383567b2 | ||
|
|
4967f977c5 | ||
|
|
eb9e98a4b3 | ||
|
|
f5cac13979 | ||
|
|
f20467889d | ||
|
|
5e8b9df859 | ||
|
|
489bacd766 | ||
|
|
5d1c249606 | ||
|
|
f2d6129db3 | ||
|
|
5c67c5a77d | ||
|
|
ce013f132f | ||
|
|
abcf1e076e | ||
|
|
47c38e8d91 | ||
|
|
34d19e5714 | ||
|
|
25b7972f88 | ||
|
|
845f9a821e | ||
|
|
b489262f51 |
29
.github/workflows/ci_macos.yaml
vendored
29
.github/workflows/ci_macos.yaml
vendored
@@ -19,11 +19,10 @@ jobs:
|
||||
matrix:
|
||||
libt_version: ["2.0.10", "1.2.19"]
|
||||
qbt_gui: ["GUI=ON", "GUI=OFF"]
|
||||
qt_version: ["6.5.2"]
|
||||
qt_version: ["6.7.0"]
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
openssl_root: /usr/local/opt/openssl@3
|
||||
libtorrent_path: "${{ github.workspace }}/../libtorrent"
|
||||
|
||||
steps:
|
||||
@@ -31,7 +30,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
uses: Wandalen/wretry.action@v1
|
||||
uses: Wandalen/wretry.action@v3
|
||||
env:
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
|
||||
HOMEBREW_NO_INSTALL_CLEANUP: 1
|
||||
@@ -47,13 +46,15 @@ jobs:
|
||||
- name: Setup ccache
|
||||
uses: Chocobo1/setup-ccache-action@v1
|
||||
with:
|
||||
store_cache: ${{ startsWith(github.ref, 'refs/heads/') }}
|
||||
store_cache: ${{ github.ref == 'refs/heads/master' }}
|
||||
update_packager_index: false
|
||||
ccache_options: |
|
||||
max_size=2G
|
||||
|
||||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "84"
|
||||
BOOST_MINOR_VERSION: "85"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
boost_url="https://boostorg.jfrog.io/artifactory/main/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"
|
||||
@@ -68,7 +69,7 @@ jobs:
|
||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
archives: qtbase qtdeclarative qtsvg qttools
|
||||
@@ -92,8 +93,7 @@ jobs:
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-Ddeprecated-functions=OFF \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}"
|
||||
-Ddeprecated-functions=OFF
|
||||
cmake --build build
|
||||
sudo cmake --install build
|
||||
|
||||
@@ -107,7 +107,6 @@ jobs:
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
|
||||
-DTESTING=ON \
|
||||
-DVERBOSE_CONFIGURE=ON \
|
||||
-D${{ matrix.qbt_gui }}
|
||||
@@ -122,8 +121,18 @@ jobs:
|
||||
if [ "${{ matrix.qbt_gui }}" = "GUI=OFF" ]; then
|
||||
appName="qbittorrent-nox"
|
||||
fi
|
||||
# package
|
||||
pushd build
|
||||
macdeployqt "$appName.app" -dmg -no-strip
|
||||
PACKAGE_RETRY=0
|
||||
while [ "$PACKAGE_RETRY" -lt "3" ]; do
|
||||
macdeployqt "$appName.app" -dmg -no-strip
|
||||
if [ -f "$appName.dmg" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
PACKAGE_RETRY=$((PACKAGE_RETRY + 1))
|
||||
echo "Retry $PACKAGE_RETRY..."
|
||||
done
|
||||
popd
|
||||
# prepare upload folder
|
||||
mkdir upload
|
||||
|
||||
89
.github/workflows/ci_python.yaml
vendored
Normal file
89
.github/workflows/ci_python.yaml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
name: CI - Python
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.head_ref != '' }}
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup python (auxiliary scripts)
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3' # use default version
|
||||
|
||||
- name: Install tools (auxiliary scripts)
|
||||
run: pip install bandit pycodestyle pyflakes
|
||||
|
||||
- name: Gather files (auxiliary scripts)
|
||||
run: |
|
||||
export "PY_FILES=$(find . -type f -name '*.py' ! -path '*searchengine*' -printf '%p ')"
|
||||
echo $PY_FILES
|
||||
echo "PY_FILES=$PY_FILES" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Lint code (auxiliary scripts)
|
||||
run: |
|
||||
pyflakes $PY_FILES
|
||||
bandit --skip B314,B405 $PY_FILES
|
||||
|
||||
- name: Format code (auxiliary scripts)
|
||||
run: |
|
||||
pycodestyle \
|
||||
--max-line-length=1000 \
|
||||
--statistics \
|
||||
$PY_FILES
|
||||
|
||||
- name: Build code (auxiliary scripts)
|
||||
run: |
|
||||
python -m compileall $PY_FILES
|
||||
|
||||
- name: Setup python (search engine)
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.7'
|
||||
|
||||
- name: Install tools (search engine)
|
||||
run: pip install bandit mypy pycodestyle pyflakes pyright
|
||||
|
||||
- name: Gather files (search engine)
|
||||
run: |
|
||||
export "PY_FILES=$(find . -type f -name '*.py' -path '*searchengine*' ! -name 'socks.py' -printf '%p ')"
|
||||
echo $PY_FILES
|
||||
echo "PY_FILES=$PY_FILES" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Check typings (search engine)
|
||||
run: |
|
||||
MYPYPATH="src/searchengine/nova3" \
|
||||
mypy \
|
||||
--follow-imports skip \
|
||||
--strict \
|
||||
$PY_FILES
|
||||
pyright \
|
||||
$PY_FILES
|
||||
|
||||
- name: Lint code (search engine)
|
||||
run: |
|
||||
pyflakes $PY_FILES
|
||||
bandit --skip B110,B310,B314,B405 $PY_FILES
|
||||
|
||||
- name: Format code (search engine)
|
||||
run: |
|
||||
pycodestyle \
|
||||
--ignore=E265,E402 \
|
||||
--max-line-length=1000 \
|
||||
--statistics \
|
||||
$PY_FILES
|
||||
|
||||
- name: Build code (search engine)
|
||||
run: |
|
||||
python -m compileall $PY_FILES
|
||||
16
.github/workflows/ci_ubuntu.yaml
vendored
16
.github/workflows/ci_ubuntu.yaml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
- name: Setup ccache
|
||||
uses: Chocobo1/setup-ccache-action@v1
|
||||
with:
|
||||
store_cache: ${{ startsWith(github.ref, 'refs/heads/') }}
|
||||
store_cache: ${{ github.ref == 'refs/heads/master' }}
|
||||
update_packager_index: false
|
||||
ccache_options: |
|
||||
max_size=2G
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
archives: icu qtbase qtdeclarative qtsvg qttools
|
||||
@@ -138,12 +138,12 @@ jobs:
|
||||
curl \
|
||||
-L \
|
||||
-Z \
|
||||
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage \
|
||||
-O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage \
|
||||
-O https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-static-x86_64.AppImage \
|
||||
-O https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-static-x86_64.AppImage \
|
||||
-O https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage
|
||||
chmod +x \
|
||||
linuxdeploy-x86_64.AppImage \
|
||||
linuxdeploy-plugin-qt-x86_64.AppImage \
|
||||
linuxdeploy-static-x86_64.AppImage \
|
||||
linuxdeploy-plugin-qt-static-x86_64.AppImage \
|
||||
linuxdeploy-plugin-appimage-x86_64.AppImage
|
||||
|
||||
- name: Prepare files for AppImage
|
||||
@@ -156,12 +156,12 @@ jobs:
|
||||
|
||||
- name: Package AppImage
|
||||
run: |
|
||||
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --plugin qt
|
||||
./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --plugin qt
|
||||
rm qbittorrent/apprun-hooks/*
|
||||
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
|
||||
NO_APPSTREAM=1 \
|
||||
OUTPUT=upload/qbittorrent-CI_Ubuntu_x86_64.AppImage \
|
||||
./linuxdeploy-x86_64.AppImage --appdir qbittorrent --output appimage
|
||||
./linuxdeploy-static-x86_64.AppImage --appdir qbittorrent --output appimage
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
2
.github/workflows/ci_webui.yaml
vendored
2
.github/workflows/ci_webui.yaml
vendored
@@ -41,7 +41,7 @@ jobs:
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
config-file: ./.github/workflows/helper/codeql/js.yaml
|
||||
config-file: .github/workflows/helper/codeql/js.yaml
|
||||
languages: javascript
|
||||
|
||||
- name: Run CodeQL analysis
|
||||
|
||||
34
.github/workflows/ci_windows.yaml
vendored
34
.github/workflows/ci_windows.yaml
vendored
@@ -78,7 +78,7 @@ jobs:
|
||||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "84"
|
||||
BOOST_MINOR_VERSION: "85"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
$boost_url="https://boostorg.jfrog.io/artifactory/main/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"
|
||||
@@ -93,9 +93,9 @@ jobs:
|
||||
move "${{ github.workspace }}/../boost_*" "${{ env.boost_path }}"
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: "6.5.2"
|
||||
version: "6.7.0"
|
||||
archives: qtbase qtsvg qttools
|
||||
cache: true
|
||||
|
||||
@@ -153,26 +153,26 @@ jobs:
|
||||
copy build/qbittorrent.pdb upload/qBittorrent
|
||||
copy dist/windows/qt.conf upload/qBittorrent
|
||||
# runtimes
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Core.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Gui.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Network.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Sql.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Svg.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Widgets.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Xml.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Core.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Gui.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Network.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Sql.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Svg.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Widgets.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Xml.dll" upload/qBittorrent
|
||||
mkdir upload/qBittorrent/plugins/iconengines
|
||||
copy "${{ env.Qt6_DIR }}/plugins/iconengines/qsvgicon.dll" upload/qBittorrent/plugins/iconengines
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/iconengines/qsvgicon.dll" upload/qBittorrent/plugins/iconengines
|
||||
mkdir upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt6_DIR }}/plugins/imageformats/qico.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt6_DIR }}/plugins/imageformats/qsvg.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/qsvg.dll" upload/qBittorrent/plugins/imageformats
|
||||
mkdir upload/qBittorrent/plugins/platforms
|
||||
copy "${{ env.Qt6_DIR }}/plugins/platforms/qwindows.dll" upload/qBittorrent/plugins/platforms
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/platforms/qwindows.dll" upload/qBittorrent/plugins/platforms
|
||||
mkdir upload/qBittorrent/plugins/sqldrivers
|
||||
copy "${{ env.Qt6_DIR }}/plugins/sqldrivers/qsqlite.dll" upload/qBittorrent/plugins/sqldrivers
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/sqldrivers/qsqlite.dll" upload/qBittorrent/plugins/sqldrivers
|
||||
mkdir upload/qBittorrent/plugins/styles
|
||||
copy "${{ env.Qt6_DIR }}/plugins/styles/qwindowsvistastyle.dll" upload/qBittorrent/plugins/styles
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/styles/qmodernwindowsstyle.dll" upload/qBittorrent/plugins/styles
|
||||
mkdir upload/qBittorrent/plugins/tls
|
||||
copy "${{ env.Qt6_DIR }}/plugins/tls/qschannelbackend.dll" upload/qBittorrent/plugins/tls
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/tls/qschannelbackend.dll" upload/qBittorrent/plugins/tls
|
||||
# cmake additionals
|
||||
mkdir upload/cmake
|
||||
copy build/compile_commands.json upload/cmake
|
||||
|
||||
4
.github/workflows/coverity-scan.yaml
vendored
4
.github/workflows/coverity-scan.yaml
vendored
@@ -37,7 +37,7 @@ jobs:
|
||||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "84"
|
||||
BOOST_MINOR_VERSION: "85"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
boost_url="https://boostorg.jfrog.io/artifactory/main/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"
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
archives: icu qtbase qtdeclarative qtsvg qttools
|
||||
|
||||
@@ -30,6 +30,7 @@ from typing import Optional, Sequence
|
||||
import argparse
|
||||
import re
|
||||
|
||||
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
||||
@@ -47,12 +48,12 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
for line in file:
|
||||
if (match := regex.match(line)) is not None:
|
||||
error_buffer += str(f"Defect file: \"{filename}\"\n"
|
||||
f"Line: {line_counter}\n"
|
||||
f"Column span: {match.span()}\n"
|
||||
f"Part: \"{match.group()}\"\n\n")
|
||||
f"Line: {line_counter}\n"
|
||||
f"Column span: {match.span()}\n"
|
||||
f"Part: \"{match.group()}\"\n\n")
|
||||
line_counter += 1
|
||||
|
||||
except UnicodeDecodeError as error:
|
||||
except UnicodeDecodeError:
|
||||
# not a text file, skip
|
||||
continue
|
||||
|
||||
@@ -64,5 +65,6 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
|
||||
@@ -67,7 +67,7 @@ repos:
|
||||
hooks:
|
||||
- id: codespell
|
||||
name: Check spelling (codespell)
|
||||
args: ["--ignore-words-list", "additionals,curren,fo,ist,ket,superseeding,te,ths"]
|
||||
args: ["--ignore-words-list", "additionals,curren,fo,ist,ket,searchin,superseeding,te,ths"]
|
||||
exclude: |
|
||||
(?x)^(
|
||||
.*\.desktop |
|
||||
@@ -78,11 +78,7 @@ repos:
|
||||
m4/.* |
|
||||
src/base/3rdparty/.* |
|
||||
src/searchengine/nova3/socks.py |
|
||||
src/webui/www/private/lang/.* |
|
||||
src/webui/www/private/scripts/lib/.* |
|
||||
src/webui/www/public/lang/.* |
|
||||
src/webui/www/public/scripts/lib/.* |
|
||||
src/webui/www/transifex/.*
|
||||
src/webui/www/private/scripts/lib/.*
|
||||
)$
|
||||
exclude_types:
|
||||
- ts
|
||||
@@ -106,11 +102,7 @@ repos:
|
||||
m4/.* |
|
||||
src/base/3rdparty/.* |
|
||||
src/searchengine/nova3/socks.py |
|
||||
src/webui/www/private/lang/.* |
|
||||
src/webui/www/private/scripts/lib/.* |
|
||||
src/webui/www/public/lang/.* |
|
||||
src/webui/www/public/scripts/lib/.* |
|
||||
src/webui/www/transifex/.*
|
||||
src/webui/www/private/scripts/lib/.*
|
||||
)$
|
||||
exclude_types:
|
||||
- svg
|
||||
|
||||
12
.tx/config
12
.tx/config
@@ -1,7 +1,7 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_master]
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_v50x]
|
||||
file_filter = src/lang/qbittorrent_<lang>.ts
|
||||
source_file = src/lang/qbittorrent_en.ts
|
||||
source_lang = en
|
||||
@@ -9,7 +9,7 @@ type = QT
|
||||
minimum_perc = 23
|
||||
lang_map = pt: pt_PT, zh: zh_CN
|
||||
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui]
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui_v50x]
|
||||
file_filter = src/webui/www/translations/webui_<lang>.ts
|
||||
source_file = src/webui/www/translations/webui_en.ts
|
||||
source_lang = en
|
||||
@@ -17,14 +17,6 @@ type = QT
|
||||
minimum_perc = 23
|
||||
lang_map = pt: pt_PT, zh: zh_CN
|
||||
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui_json]
|
||||
file_filter = src/webui/www/transifex/<lang>.json
|
||||
source_file = src/webui/www/transifex/en.json
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
minimum_perc = 23
|
||||
lang_map = pt: pt_PT, zh: zh_CN
|
||||
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrentdesktop_master]
|
||||
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
|
||||
source_lang = en
|
||||
|
||||
@@ -11,7 +11,7 @@ set(minBoostVersion 1.76)
|
||||
set(minQt6Version 6.5.0)
|
||||
set(minOpenSSLVersion 3.0.2)
|
||||
set(minLibtorrent1Version 1.2.19)
|
||||
set(minLibtorrentVersion 2.0.9)
|
||||
set(minLibtorrentVersion 2.0.10)
|
||||
set(minZlibVersion 1.2.11)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
43
Changelog
43
Changelog
@@ -1,4 +1,4 @@
|
||||
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
||||
Sun Sep 29th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
||||
- FEATURE: Support creating .torrent with larger piece size (Chocobo1)
|
||||
- FEATURE: Improve tracker entries handling (glassez)
|
||||
- FEATURE: Add separate filter item for tracker errors (glassez)
|
||||
@@ -12,14 +12,30 @@ Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
||||
- FEATURE: Enable Ctrl+F hotkey for more inputs (thalieht)
|
||||
- FEATURE: Add seeding limits to RSS and Watched folders options UI (glassez)
|
||||
- FEATURE: Subcategories implicitly follow the parent category options (glassez)
|
||||
- FEATURE: Add support for SSL torrents (Chocobo1, Radu Carpa)
|
||||
- FEATURE: Add option to name each qbittorrent instance (Chocobo1)
|
||||
- FEATURE: Add button for sending test email (Thomas Piccirello)
|
||||
- FEATURE: Allow torrents to override default share limit action (glassez)
|
||||
- FEATURE: Use Start/Stop instead of Resume/Pause (thalieht)
|
||||
- FEATURE: Add the Popularity metric (Aliaksei Urbanski)
|
||||
- FEATURE: Focus on Download button if torrent link retrieved from the clipboard (glassez)
|
||||
- FEATURE: Add ability to pause/resume entire BitTorrent session (glassez)
|
||||
- FEATURE: Add an option to set BitTorrent session shutdown timeout (glassez)
|
||||
- FEATURE: Apply "Excluded file names" to folder names as well (glassez)
|
||||
- FEATURE: Allow to use regular expression to filter torrent content (glassez)
|
||||
- FEATURE: Allow to move content files to Trash instead of deleting them (glassez)
|
||||
- FEATURE: Add ability to display torrent "privateness" in UI (ManiMatter)
|
||||
- FEATURE: Add a flag in `Peers` tab denoting a connection using NAT hole punching (stalkerok)
|
||||
- BUGFIX: Display error message when unrecoverable error occurred (glassez)
|
||||
- BUGFIX: Update size of selected files when selection is changed (glassez)
|
||||
- BUGFIX: Normalize tags by trimming leading/trailing whitespace (glassez)
|
||||
- BUGFIX: Correctly handle share limits in torrent options dialog (glassez)
|
||||
- BUGFIX: Adjust tracker tier when adding additional trackers (Chocobo1)
|
||||
- BUGFIX: Fix inconsistent naming between `Done/Progress` column (luzpaz)
|
||||
- BUGFIX: Sanitize peer client names (Hanabishi)
|
||||
- BUGFIX: Apply share limits immediately when torrent downloading is finished (glassez)
|
||||
- BUGFIX: Show download progress for folders with zero byte size as 100 instead of 0 (vikas_c)
|
||||
- BUGFIX: Fix highlighted piece color (Prince Gupta)
|
||||
- BUGFIX: Apply "merge trackers" logic regardless of way the torrent is added (glassez)
|
||||
- WEBUI: Improve WebUI responsiveness (Chocobo1)
|
||||
- WEBUI: Do not exit the app when WebUI has failed to start (Hanabishi)
|
||||
- WEBUI: Add `Moving` filter to side panel (xavier2k6)
|
||||
@@ -28,14 +44,37 @@ Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
||||
- WEBUI: Leave the fields empty when value is invalid (Chocobo1)
|
||||
- WEBUI: Use natural sorting (Chocobo1)
|
||||
- WEBUI: Improve WebUI login behavior (JayRet)
|
||||
- WEBUI: Conditionally show filters sidebar (Thomas Piccirello)
|
||||
- WEBUI: Add support for running concurrent searches (Thomas Piccirello)
|
||||
- WEBUI: Improve accuracy of trackers list (Thomas Piccirello)
|
||||
- WEBUI: Fix error when category doesn't exist (Thomas Piccirello)
|
||||
- WEBUI: Improve table scrolling and selection on mobile (Thomas Piccirello)
|
||||
- WEBUI: Restore search tabs on load (Thomas Piccirello)
|
||||
- WEBUI: Restore previously used tab on load (Thomas Piccirello)
|
||||
- WEBUI: Increase default height of `Share ratio limit` dialog (thalieht)
|
||||
- WEBUI: Use enabled search plugins by default (Thomas Piccirello)
|
||||
- WEBUI: Add columns `Incomplete Save Path`, `Info Hash v1`, `Info Hash v2` (thalieht)
|
||||
- WEBUI: Always create generic filter items (skomerko)
|
||||
- WEBUI: Provide `Use Category paths in Manual Mode` option (skomerko)
|
||||
- WEBUI: Provide `Merge trackers to existing torrent` option (skomerko)
|
||||
- WEBAPI: Fix wrong timestamp values (Chocobo1)
|
||||
- WEBAPI: Send binary data with filename and mime type specified (glassez)
|
||||
- WEBAPI: Expose API for the torrent creator (glassez, Radu Carpa)
|
||||
- WEBAPI: Add support for SSL torrents (Chocobo1, Radu Carpa)
|
||||
- WEBAPI: Provide endpoint for listing directory content (Paweł Kotiuk)
|
||||
- WEBAPI: Provide "private" flag via "torrents/info" endpoint (ManiMatter)
|
||||
- WEBAPI: Add a way to download .torrent file using search plugin (glassez)
|
||||
- WEBAPI: Add "private" filter for "torrents/info" endpoint (ManiMatter)
|
||||
- WEBAPI: Add root_path to "torrents/info" result (David Newhall)
|
||||
- RSS: Show RSS feed title in HTML browser (Jay)
|
||||
- RSS: Allow to set delay between requests to the same host (jNullj)
|
||||
- SEARCH: Allow users to specify Python executable path (Chocobo1)
|
||||
- SEARCH: Lazy load search plugins (milahu)
|
||||
- SEARCH: Add date column to the built-in search engine (ducalex)
|
||||
- SEARCH: Allow to rearrange search tabs (glassez)
|
||||
- WINDOWS: Use Fusion style on Windows 10+. It has better compatibility with dark mode (glassez)
|
||||
- WINDOWS: Allow to set qBittorrent as default program (glassez)
|
||||
- WINDOWS: Don't access "Favorites" folder unexpectedly (glassez)
|
||||
- LINUX: Add support for systemd power management (Chocobo1)
|
||||
- LINUX: Add support for localized man pages (Victor Chernyakin)
|
||||
- LINUX: Specify a locale if none is set (Chocobo1)
|
||||
|
||||
2
INSTALL
2
INSTALL
@@ -5,7 +5,7 @@ qBittorrent - A BitTorrent client in C++ / Qt
|
||||
|
||||
- Boost >= 1.76
|
||||
|
||||
- libtorrent-rasterbar 1.2.19 - 1.2.x || 2.0.9 - 2.0.x
|
||||
- libtorrent-rasterbar 1.2.19 - 1.2.x || 2.0.10 - 2.0.x
|
||||
* By Arvid Norberg, https://www.libtorrent.org/
|
||||
* Be careful: another library (the one used by rTorrent) uses a similar name
|
||||
|
||||
|
||||
2
dist/unix/CMakeLists.txt
vendored
2
dist/unix/CMakeLists.txt
vendored
@@ -38,7 +38,7 @@ if (GUI)
|
||||
COMPONENT data
|
||||
)
|
||||
|
||||
install(FILES org.qbittorrent.qBittorrent.appdata.xml
|
||||
install(FILES org.qbittorrent.qBittorrent.metainfo.xml
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo/
|
||||
COMPONENT data
|
||||
)
|
||||
|
||||
144
dist/unix/org.qbittorrent.qBittorrent.desktop
vendored
144
dist/unix/org.qbittorrent.qBittorrent.desktop
vendored
@@ -14,216 +14,220 @@ Keywords=bittorrent;torrent;magnet;download;p2p;
|
||||
SingleMainWindow=true
|
||||
|
||||
# Translations
|
||||
Comment[af]=Aflaai en deel lêers oor BitTorrent
|
||||
GenericName[af]=BitTorrent kliënt
|
||||
Comment[af]=Aflaai en deel lêers oor BitTorrent
|
||||
Name[af]=qBittorrent
|
||||
Comment[ar]=نزّل وشارك الملفات عبر كيوبتتورنت
|
||||
GenericName[ar]=عميل بتتورنت
|
||||
Comment[ar]=نزّل وشارك الملفات عبر كيوبتتورنت
|
||||
Name[ar]=qBittorrent
|
||||
Comment[be]=Спампоўванне і раздача файлаў праз пратакол BitTorrent
|
||||
GenericName[be]=Кліент BitTorrent
|
||||
Comment[be]=Спампоўванне і раздача файлаў праз пратакол BitTorrent
|
||||
Name[be]=qBittorrent
|
||||
Comment[bg]=Сваляне и споделяне на файлове чрез BitTorrent
|
||||
GenericName[bg]=BitTorrent клиент
|
||||
Comment[bg]=Сваляне и споделяне на файлове чрез BitTorrent
|
||||
Name[bg]=qBittorrent
|
||||
Comment[bn]=বিটটরেন্টে ফাইল ডাউনলোড এবং শেয়ার করুন
|
||||
GenericName[bn]=বিটটরেন্ট ক্লায়েন্ট
|
||||
Comment[bn]=বিটটরেন্টে ফাইল ডাউনলোড এবং শেয়ার করুন
|
||||
Name[bn]=qBittorrent
|
||||
Comment[zh]=通过 BitTorrent 下载和分享文件
|
||||
GenericName[zh]=BitTorrent 客户端
|
||||
Comment[zh]=通过 BitTorrent 下载和分享文件
|
||||
Name[zh]=qBittorrent
|
||||
Comment[bs]=Preuzmi i dijeli datoteke preko BitTorrent-a
|
||||
GenericName[bs]=BitTorrent klijent
|
||||
Comment[bs]=Preuzmi i dijeli datoteke preko BitTorrent-a
|
||||
Name[bs]=qBittorrent
|
||||
Comment[ca]=Baixeu i compartiu fitxers amb el BitTorrent
|
||||
GenericName[ca]=Client de BitTorrent
|
||||
Comment[ca]=Baixeu i compartiu fitxers amb el BitTorrent
|
||||
Name[ca]=qBittorrent
|
||||
Comment[cs]=Stahování a sdílení souborů přes síť BitTorrent
|
||||
GenericName[cs]=BitTorrent klient
|
||||
Comment[cs]=Stahování a sdílení souborů přes síť BitTorrent
|
||||
Name[cs]=qBittorrent
|
||||
Comment[da]=Download og del filer over BitTorrent
|
||||
GenericName[da]=BitTorrent-klient
|
||||
Comment[da]=Download og del filer over BitTorrent
|
||||
Name[da]=qBittorrent
|
||||
Comment[de]=Über BitTorrent Dateien herunterladen und teilen
|
||||
GenericName[de]=BitTorrent Client
|
||||
Comment[de]=Über BitTorrent Dateien herunterladen und teilen
|
||||
Name[de]=qBittorrent
|
||||
Comment[el]=Κάντε λήψη και μοιραστείτε αρχεία μέσω BitTorrent
|
||||
GenericName[el]=BitTorrent client
|
||||
Comment[el]=Κάντε λήψη και μοιραστείτε αρχεία μέσω BitTorrent
|
||||
Name[el]=qBittorrent
|
||||
Comment[en_GB]=Download and share files over BitTorrent
|
||||
GenericName[en_GB]=BitTorrent client
|
||||
Comment[en_GB]=Download and share files over BitTorrent
|
||||
Name[en_GB]=qBittorrent
|
||||
Comment[es]=Descargue y comparta archivos por BitTorrent
|
||||
GenericName[es]=Cliente BitTorrent
|
||||
Comment[es]=Descargue y comparta archivos por BitTorrent
|
||||
Name[es]=qBittorrent
|
||||
Comment[et]=Lae alla ja jaga faile üle BitTorrenti
|
||||
GenericName[et]=BitTorrent klient
|
||||
Comment[et]=Lae alla ja jaga faile üle BitTorrenti
|
||||
Name[et]=qBittorrent
|
||||
Comment[eu]=Jeitsi eta elkarbanatu agiriak BitTorrent bidez
|
||||
GenericName[eu]=BitTorrent bezeroa
|
||||
Comment[eu]=Jeitsi eta elkarbanatu agiriak BitTorrent bidez
|
||||
Name[eu]=qBittorrent
|
||||
Comment[fa]=دانلود و به اشتراک گذاری فایل های بوسیله بیت تورنت
|
||||
GenericName[fa]=بیت تورنت نسخه کلاینت
|
||||
Comment[fa]=دانلود و به اشتراک گذاری فایل های بوسیله بیت تورنت
|
||||
Name[fa]=qBittorrent
|
||||
Comment[fi]=Lataa ja jaa tiedostoja BitTorrentia käyttäen
|
||||
GenericName[fi]=BitTorrent-asiakasohjelma
|
||||
Comment[fi]=Lataa ja jaa tiedostoja BitTorrentia käyttäen
|
||||
Name[fi]=qBittorrent
|
||||
Comment[fr]=Télécharger et partager des fichiers sur BitTorrent
|
||||
GenericName[fr]=Client BitTorrent
|
||||
Comment[fr]=Télécharger et partager des fichiers sur BitTorrent
|
||||
Name[fr]=qBittorrent
|
||||
Comment[gl]=Descargar e compartir ficheiros co protocolo BitTorrent
|
||||
GenericName[gl]=Cliente BitTorrent
|
||||
Comment[gl]=Descargar e compartir ficheiros co protocolo BitTorrent
|
||||
Name[gl]=qBittorrent
|
||||
Comment[gu]=બિટ્ટોરેંટ પર ફાઈલો ડાઉનલોડ અને શેર કરો
|
||||
GenericName[gu]=બિટ્ટોરેંટ ક્લાયન્ટ
|
||||
Comment[gu]=બિટ્ટોરેંટ પર ફાઈલો ડાઉનલોડ અને શેર કરો
|
||||
Name[gu]=qBittorrent
|
||||
Comment[he]=הורד ושתף קבצים על גבי ביטורנט
|
||||
GenericName[he]=לקוח ביטורנט
|
||||
Comment[he]=הורד ושתף קבצים על גבי ביטורנט
|
||||
Name[he]=qBittorrent
|
||||
Comment[hr]=Preuzmite i dijelite datoteke putem BitTorrenta
|
||||
GenericName[hr]=BitTorrent klijent
|
||||
Comment[hr]=Preuzmite i dijelite datoteke putem BitTorrenta
|
||||
Name[hr]=qBittorrent
|
||||
Comment[hu]=Fájlok letöltése és megosztása a BitTorrent hálózaton keresztül
|
||||
GenericName[hu]=BitTorrent kliens
|
||||
Comment[hu]=Fájlok letöltése és megosztása a BitTorrent hálózaton keresztül
|
||||
Name[hu]=qBittorrent
|
||||
Comment[hy]=Նիշքերի փոխանցում BitTorrent-ի միջոցով
|
||||
GenericName[hy]=BitTorrent սպասառու
|
||||
Comment[hy]=Նիշքերի փոխանցում BitTorrent-ի միջոցով
|
||||
Name[hy]=qBittorrent
|
||||
Comment[id]=Unduh dan berbagi berkas melalui BitTorrent
|
||||
GenericName[id]=Klien BitTorrent
|
||||
Comment[id]=Unduh dan berbagi berkas melalui BitTorrent
|
||||
Name[id]=qBittorrent
|
||||
Comment[is]=Sækja og deila skrám yfir BitTorrent
|
||||
GenericName[is]=BitTorrent biðlarar
|
||||
Comment[is]=Sækja og deila skrám yfir BitTorrent
|
||||
Name[is]=qBittorrent
|
||||
Comment[it]=Scarica e condividi file tramite BitTorrent
|
||||
GenericName[it]=Client BitTorrent
|
||||
Comment[it]=Scarica e condividi file tramite BitTorrent
|
||||
Name[it]=qBittorrent
|
||||
Comment[ja]=BitTorrentでファイルのダウンロードと共有
|
||||
GenericName[ja]=BitTorrentクライアント
|
||||
Comment[ja]=BitTorrentでファイルのダウンロードと共有
|
||||
Name[ja]=qBittorrent
|
||||
Comment[ka]=გადმოტვირთეთ და გააზიარეთ ფაილები BitTorrent-ის საშუალებით
|
||||
GenericName[ka]=BitTorrent კლიენტი
|
||||
Comment[ka]=გადმოტვირთეთ და გააზიარეთ ფაილები BitTorrent-ის საშუალებით
|
||||
Name[ka]=qBittorrent
|
||||
Comment[ko]=BitTorrent를 통한 파일 내려받기 및 공유
|
||||
GenericName[ko]=BitTorrent 클라이언트
|
||||
Comment[ko]=BitTorrent를 통한 파일 다운로드 및 공유
|
||||
Name[ko]=qBittorrent
|
||||
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle
|
||||
GenericName[lt]=BitTorrent klientas
|
||||
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle
|
||||
Name[lt]=qBittorrent
|
||||
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
|
||||
GenericName[mk]=BitTorrent клиент
|
||||
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
|
||||
Name[mk]=qBittorrent
|
||||
Comment[my]=တောရန့်ဖြင့်ဖိုင်များဒေါင်းလုဒ်ဆွဲရန်နှင့်မျှဝေရန်
|
||||
GenericName[my]=တောရန့်စီမံခန့်ခွဲသည့်အရာ
|
||||
Comment[my]=တောရန့်ဖြင့်ဖိုင်များဒေါင်းလုဒ်ဆွဲရန်နှင့်မျှဝေရန်
|
||||
Name[my]=qBittorrent
|
||||
Comment[nb]=Last ned og del filer over BitTorrent
|
||||
GenericName[nb]=BitTorrent-klient
|
||||
Comment[nb]=Last ned og del filer over BitTorrent
|
||||
Name[nb]=qBittorrent
|
||||
Comment[nl]=Bestanden downloaden en delen via BitTorrent
|
||||
GenericName[nl]=BitTorrent-client
|
||||
Comment[nl]=Bestanden downloaden en delen via BitTorrent
|
||||
Name[nl]=qBittorrent
|
||||
Comment[pl]=Pobieraj i dziel się plikami przez BitTorrent
|
||||
GenericName[pl]=Klient BitTorrent
|
||||
Comment[pl]=Pobieraj i dziel się plikami przez BitTorrent
|
||||
Name[pl]=qBittorrent
|
||||
Comment[pt]=Transferir e partilhar ficheiros por BitTorrent
|
||||
GenericName[pt]=Cliente BitTorrent
|
||||
Comment[pt]=Transferir e partilhar ficheiros por BitTorrent
|
||||
Name[pt]=qBittorrent
|
||||
Comment[pt_BR]=Baixe e compartilhe arquivos pelo BitTorrent
|
||||
GenericName[pt_BR]=Cliente BitTorrent
|
||||
Comment[pt_BR]=Baixe e compartilhe arquivos pelo BitTorrent
|
||||
Name[pt_BR]=qBittorrent
|
||||
Comment[ro]=Descărcați și partajați fișiere prin BitTorrent
|
||||
GenericName[ro]=Client BitTorrent
|
||||
Comment[ro]=Descărcați și partajați fișiere prin BitTorrent
|
||||
Name[ro]=qBittorrent
|
||||
Comment[ru]=Обмен файлами по сети БитТоррент
|
||||
GenericName[ru]=Клиент сети БитТоррент
|
||||
Comment[ru]=Обмен файлами по сети БитТоррент
|
||||
Name[ru]=qBittorrent
|
||||
Comment[sk]=Sťahovanie a zdieľanie súborov prostredníctvom siete BitTorrent
|
||||
GenericName[sk]=Klient siete BitTorrent
|
||||
Comment[sk]=Sťahovanie a zdieľanie súborov prostredníctvom siete BitTorrent
|
||||
Name[sk]=qBittorrent
|
||||
Comment[sl]=Prenesite in delite datoteke preko BitTorrenta
|
||||
GenericName[sl]=BitTorrent odjemalec
|
||||
Comment[sl]=Prenesite in delite datoteke preko BitTorrenta
|
||||
Name[sl]=qBittorrent
|
||||
GenericName[sq]=Klienti BitTorrent
|
||||
Comment[sq]=Shkarko dhe shpërndaj skedarë në BitTorrent
|
||||
Name[sq]=qBittorrent
|
||||
Comment[sr]=Преузимајте и делите фајлове преко BitTorrent протокола
|
||||
GenericName[sr]=BitTorrent-клијент
|
||||
GenericName[sr]=BitTorrent клијент
|
||||
Comment[sr]=Преузимајте и делите фајлове преко BitTorrent-а
|
||||
Name[sr]=qBittorrent
|
||||
Comment[sr@latin]=Preuzimanje i deljenje fajlova preko BitTorrent-a
|
||||
GenericName[sr@latin]=BitTorrent klijent
|
||||
Comment[sr@latin]=Preuzimanje i deljenje fajlova preko BitTorrent-a
|
||||
Name[sr@latin]=qBittorrent
|
||||
Comment[sv]=Hämta och dela filer över BitTorrent
|
||||
GenericName[sv]=BitTorrent-klient
|
||||
Comment[sv]=Hämta och dela filer över BitTorrent
|
||||
Name[sv]=qBittorrent
|
||||
Comment[ta]=BitTorrent வழியாக கோப்புகளை பதிவிறக்க மற்றும் பகிர
|
||||
GenericName[ta]=BitTorrent வாடிக்கையாளர்
|
||||
Comment[ta]=BitTorrent வழியாக கோப்புகளை பதிவிறக்க மற்றும் பகிர
|
||||
Name[ta]=qBittorrent
|
||||
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
|
||||
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
|
||||
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
|
||||
Name[te]=qBittorrent
|
||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่าน BitTorrent
|
||||
GenericName[th]=โปรแกรมบิททอเร้นท์
|
||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่าน BitTorrent
|
||||
Name[th]=qBittorrent
|
||||
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
||||
GenericName[tr]=BitTorrent istemcisi
|
||||
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
||||
Name[tr]=qBittorrent
|
||||
Comment[ur]=BitTorrent پر فائلوں کو ڈاؤن لوڈ کریں اور اشتراک کریں
|
||||
GenericName[ur]=قیو بٹ ٹورنٹ کلائنٹ
|
||||
Comment[ur]=BitTorrent پر فائلوں کو ڈاؤن لوڈ کریں اور اشتراک کریں
|
||||
Name[ur]=qBittorrent
|
||||
Comment[uk]=Завантажуйте та поширюйте файли через BitTorrent
|
||||
GenericName[uk]=BitTorrent-клієнт
|
||||
Comment[uk]=Завантажуйте та поширюйте файли через BitTorrent
|
||||
Name[uk]=qBittorrent
|
||||
Comment[vi]=Tải xuống và chia sẻ tệp qua BitTorrent
|
||||
GenericName[vi]=Máy khách BitTorrent
|
||||
Comment[vi]=Tải xuống và chia sẻ tệp qua BitTorrent
|
||||
Name[vi]=qBittorrent
|
||||
Comment[zh_HK]=經由BitTorrent下載並分享檔案
|
||||
GenericName[zh_HK]=BitTorrent用戶端
|
||||
Comment[zh_HK]=經由BitTorrent下載並分享檔案
|
||||
Name[zh_HK]=qBittorrent
|
||||
Comment[zh_TW]=經由 BitTorrent 下載並分享檔案
|
||||
GenericName[zh_TW]=BitTorrent 用戶端
|
||||
Comment[zh_TW]=使用 BitTorrent 下載並分享檔案
|
||||
Name[zh_TW]=qBittorrent
|
||||
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
|
||||
GenericName[eo]=BitTorrent-kliento
|
||||
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
|
||||
Name[eo]=qBittorrent
|
||||
Comment[kk]=BitTorrent арқылы файл жүктеу және бөлісу
|
||||
GenericName[kk]=BitTorrent клиенті
|
||||
Comment[kk]=BitTorrent арқылы файл жүктеу және бөлісу
|
||||
Name[kk]=qBittorrent
|
||||
Comment[en_AU]=Download and share files over BitTorrent
|
||||
GenericName[en_AU]=BitTorrent client
|
||||
Comment[en_AU]=Download and share files over BitTorrent
|
||||
Name[en_AU]=qBittorrent
|
||||
Name[rm]=qBittorrent
|
||||
Name[jv]=qBittorrent
|
||||
Comment[oc]=Telecargar e partejar de fichièrs amb BitTorrent
|
||||
GenericName[oc]=Client BitTorrent
|
||||
Comment[oc]=Telecargar e partejar de fichièrs amb BitTorrent
|
||||
Name[oc]=qBittorrent
|
||||
Name[ug]=qBittorrent
|
||||
Name[yi]=qBittorrent
|
||||
Comment[nqo]=ߞߐߕߐ߯ߘߐ ߟߎ߬ ߟߊߖߌ߰ ߞߊ߬ ߓߊ߲߫ ߞߵߊ߬ߟߎ߬ ߘߐߕߟߊ߫ ߓߌߙߏߙߍ߲ߕ ߞߊ߲߬
|
||||
GenericName[nqo]=ߓߌߙߏߙߍ߲ߕ ߕߣߐ߬ߓߐ߬ߟߊ
|
||||
Comment[nqo]=ߞߐߕߐ߯ߘߐ ߟߎ߬ ߟߊߖߌ߰ ߞߊ߬ ߓߊ߲߫ ߞߵߊ߬ߟߎ߬ ߘߐߕߟߊ߫ ߓߌߙߏߙߍ߲ߕ ߞߊ߲߬
|
||||
Name[nqo]=qBittorrent
|
||||
Comment[uz@Latn]=BitTorrent orqali fayllarni yuklab olish va baham ko‘rish
|
||||
GenericName[uz@Latn]=BitTorrent mijozi
|
||||
Comment[uz@Latn]=BitTorrent orqali fayllarni yuklab olish va baham ko‘rish
|
||||
Name[uz@Latn]=qBittorrent
|
||||
Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent
|
||||
GenericName[ltg]=BitTorrent klients
|
||||
Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent
|
||||
Name[ltg]=qBittorrent
|
||||
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
|
||||
GenericName[hi_IN]=Bittorrent साधन
|
||||
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
|
||||
Name[hi_IN]=qBittorrent
|
||||
Comment[az@latin]=Faylları BitTorrent vasitəsilə endirin və paylaşın
|
||||
GenericName[az@latin]=BitTorrent client
|
||||
Comment[az@latin]=Faylları BitTorrent vasitəsilə endirin və paylaşın
|
||||
Name[az@latin]=qBittorrent
|
||||
Comment[lv_LV]=Lejupielādēt un koplietot failus ar BitTorrent
|
||||
GenericName[lv_LV]=BitTorrent klients
|
||||
Comment[lv_LV]=Lejupielādēt un koplietot failus ar BitTorrent
|
||||
Name[lv_LV]=qBittorrent
|
||||
Comment[ms_MY]=Muat turun dan kongsi fail melalui BitTorrent
|
||||
GenericName[ms_MY]=Klien BitTorrent
|
||||
Comment[ms_MY]=Muat turun dan kongsi fail melalui BitTorrent
|
||||
Name[ms_MY]=qBittorrent
|
||||
Comment[mn_MN]=BitTorrent-оор файлуудаа тат, түгээ
|
||||
GenericName[mn_MN]=BitTorrent татагч
|
||||
Comment[mn_MN]=BitTorrent-оор файлуудаа тат, түгээ
|
||||
Name[mn_MN]=qBittorrent
|
||||
Comment[ne_NP]=फाइलहरू डाउनलोड गर्नुहोस् र BitTorrent मा साझा गर्नुहोस्
|
||||
GenericName[ne_NP]=BitTorrent क्लाइन्ट
|
||||
Comment[ne_NP]=फाइलहरू डाउनलोड गर्नुहोस् र BitTorrent मा साझा गर्नुहोस्
|
||||
Name[ne_NP]=qBittorrent
|
||||
Comment[pt_PT]=Transferir e partilhar ficheiros por BitTorrent
|
||||
GenericName[pt_PT]=Cliente BitTorrent
|
||||
Comment[pt_PT]=Transferir e partilhar ficheiros por BitTorrent
|
||||
Name[pt_PT]=qBittorrent
|
||||
GenericName[si_LK]=BitTorrent සේවාදායකයා
|
||||
Comment[si_LK]=BitTorrent හරහා ගොනු බාගත කර බෙදාගන්න.
|
||||
Name[si_LK]=qBittorrent
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2014 sledgehammer999 <sledgehammer999@qbittorrent.org> -->
|
||||
<component type="desktop">
|
||||
<component type="desktop-application">
|
||||
<id>org.qbittorrent.qBittorrent</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0-or-later and OpenSSL</project_license>
|
||||
@@ -14,33 +14,12 @@
|
||||
</p>
|
||||
<ul>
|
||||
<li>Polished µTorrent-like User Interface</li>
|
||||
<li>
|
||||
Well-integrated and extensible Search Engine
|
||||
<ul>
|
||||
<li>Simultaneous search in many Torrent search sites</li>
|
||||
<li>Category-specific search requests (e.g. Books, Music, Software)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Well-integrated and extensible Search Engine</li>
|
||||
<li>RSS feed support with advanced download filters (incl. regex)</li>
|
||||
<li>
|
||||
Many Bittorrent extensions supported:
|
||||
<ul>
|
||||
<li>Magnet links</li>
|
||||
<li>Distributed hash table (DHT), peer exchange protocol (PEX), local peer discovery (LSD)</li>
|
||||
<li>Private torrents</li>
|
||||
<li>Encrypted connections</li>
|
||||
<li>and many more...</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Many Bittorrent extensions supported</li>
|
||||
<li>Remote control through Web user interface, written with AJAX</li>
|
||||
<li>Sequential downloading (Download in order)</li>
|
||||
<li>
|
||||
Advanced control over torrents, trackers and peers
|
||||
<ul>
|
||||
<li>Torrents queueing and prioritizing</li>
|
||||
<li>Torrent content selection and prioritizing</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Advanced control over torrents, trackers and peers</li>
|
||||
<li>Bandwidth scheduler</li>
|
||||
<li>Torrent creation tool</li>
|
||||
<li>IP Filtering (eMule & PeerGuardian format compatible)</li>
|
||||
@@ -53,27 +32,36 @@
|
||||
<launchable type="desktop-id">org.qbittorrent.qBittorrent.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_01.png</image>
|
||||
<caption>Main window (General tab collapsed)</caption>
|
||||
<image>https://raw.githubusercontent.com/qbittorrent/qBittorrent-website/2741f2a90854604e268c6bba9e6859aad0103583/src/img/screenshots/linux/1.webp</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_02.png</image>
|
||||
<caption>Main window (General tab expanded)</caption>
|
||||
<image>https://raw.githubusercontent.com/qbittorrent/qBittorrent-website/2741f2a90854604e268c6bba9e6859aad0103583/src/img/screenshots/linux/2.webp</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_03.png</image>
|
||||
<caption>Options dialog</caption>
|
||||
<image>https://raw.githubusercontent.com/qbittorrent/qBittorrent-website/2741f2a90854604e268c6bba9e6859aad0103583/src/img/screenshots/linux/3.webp</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_04.png</image>
|
||||
<caption>Search engine</caption>
|
||||
<image>https://raw.githubusercontent.com/qbittorrent/qBittorrent-website/2741f2a90854604e268c6bba9e6859aad0103583/src/img/screenshots/linux/4.webp</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<update_contact>sledgehammer999@qbittorrent.org</update_contact>
|
||||
<developer_name>The qBittorrent Project</developer_name>
|
||||
<developer id="org.qbittorrent">
|
||||
<name>The qBittorrent Project</name>
|
||||
</developer>
|
||||
<url type="homepage">https://www.qbittorrent.org/</url>
|
||||
<url type="bugtracker">https://bugs.qbittorrent.org/</url>
|
||||
<url type="donation">https://www.qbittorrent.org/donate</url>
|
||||
<url type="faq">https://wiki.qbittorrent.org/Frequently-Asked-Questions</url>
|
||||
<url type="help">https://forum.qbittorrent.org/</url>
|
||||
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
|
||||
<url type="donation">https://www.qbittorrent.org/donate</url>
|
||||
<url type="translate">https://wiki.qbittorrent.org/How-to-translate-qBittorrent</url>
|
||||
<url type="vcs-browser">https://github.com/qbittorrent/qBittorrent</url>
|
||||
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="5.0.0~beta1" date="2024-03-19"/>
|
||||
<release version="5.0.0" date="2024-09-29"/>
|
||||
</releases>
|
||||
</component>
|
||||
3
dist/windows/gather_qt_translations.py
vendored
3
dist/windows/gather_qt_translations.py
vendored
@@ -7,9 +7,11 @@ import shutil
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
|
||||
def isNotStub(path: str) -> bool:
|
||||
return (os.path.getsize(path) >= (10 * 1024))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description='Gather valid Qt translations for NSIS packaging.')
|
||||
parser.add_argument("qt_translations_folder", help="Qt's translations folder")
|
||||
@@ -27,5 +29,6 @@ def main() -> int:
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -140,8 +140,8 @@ namespace
|
||||
if (!addTorrentParams.savePath.isEmpty())
|
||||
result.append(u"@savePath=" + addTorrentParams.savePath.data());
|
||||
|
||||
if (addTorrentParams.addPaused.has_value())
|
||||
result.append(*addTorrentParams.addPaused ? u"@addPaused=1"_s : u"@addPaused=0"_s);
|
||||
if (addTorrentParams.addStopped.has_value())
|
||||
result.append(*addTorrentParams.addStopped ? u"@addStopped=1"_s : u"@addStopped=0"_s);
|
||||
|
||||
if (addTorrentParams.skipChecking)
|
||||
result.append(u"@skipChecking"_s);
|
||||
@@ -180,9 +180,9 @@ namespace
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(u"@addPaused="))
|
||||
if (param.startsWith(u"@addStopped="))
|
||||
{
|
||||
addTorrentParams.addPaused = (QStringView(param).mid(11).toInt() != 0);
|
||||
addTorrentParams.addStopped = (QStringView(param).mid(11).toInt() != 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -251,6 +251,7 @@ Application::Application(int &argc, char **argv)
|
||||
#if !defined(DISABLE_GUI)
|
||||
setDesktopFileName(u"org.qbittorrent.qBittorrent"_s);
|
||||
setQuitOnLastWindowClosed(false);
|
||||
setQuitLockEnabled(false);
|
||||
QPixmapCache::setCacheLimit(PIXMAP_CACHE_SIZE);
|
||||
#endif
|
||||
|
||||
@@ -832,7 +833,7 @@ int Application::exec()
|
||||
m_desktopIntegration = new DesktopIntegration;
|
||||
m_desktopIntegration->setToolTip(tr("Loading torrents..."));
|
||||
#ifndef Q_OS_MACOS
|
||||
auto *desktopIntegrationMenu = new QMenu;
|
||||
auto *desktopIntegrationMenu = m_desktopIntegration->menu();
|
||||
auto *actionExit = new QAction(tr("E&xit"), desktopIntegrationMenu);
|
||||
actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_s));
|
||||
actionExit->setMenuRole(QAction::QuitRole);
|
||||
@@ -843,8 +844,6 @@ int Application::exec()
|
||||
});
|
||||
desktopIntegrationMenu->addAction(actionExit);
|
||||
|
||||
m_desktopIntegration->setMenu(desktopIntegrationMenu);
|
||||
|
||||
const bool isHidden = m_desktopIntegration->isActive() && (startUpWindowState() == WindowState::Hidden);
|
||||
#else
|
||||
const bool isHidden = false;
|
||||
|
||||
@@ -263,8 +263,8 @@ namespace
|
||||
}
|
||||
|
||||
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
|
||||
"e.g. Parameter '--add-paused' must follow syntax "
|
||||
"'--add-paused=<true|false>'")
|
||||
"e.g. Parameter '--add-stopped' must follow syntax "
|
||||
"'--add-stopped=<true|false>'")
|
||||
.arg(fullParameter(), u"<true|false>"_s));
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ namespace
|
||||
constexpr const StringOption CONFIGURATION_OPTION {"configuration"};
|
||||
constexpr const BoolOption RELATIVE_FASTRESUME {"relative-fastresume"};
|
||||
constexpr const StringOption SAVE_PATH_OPTION {"save-path"};
|
||||
constexpr const TriStateBoolOption PAUSED_OPTION {"add-paused", true};
|
||||
constexpr const TriStateBoolOption STOPPED_OPTION {"add-stopped", true};
|
||||
constexpr const BoolOption SKIP_HASH_CHECK_OPTION {"skip-hash-check"};
|
||||
constexpr const StringOption CATEGORY_OPTION {"category"};
|
||||
constexpr const BoolOption SEQUENTIAL_OPTION {"sequential"};
|
||||
@@ -345,7 +345,7 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en
|
||||
addTorrentParams.skipChecking = SKIP_HASH_CHECK_OPTION.value(env);
|
||||
addTorrentParams.sequential = SEQUENTIAL_OPTION.value(env);
|
||||
addTorrentParams.firstLastPiecePriority = FIRST_AND_LAST_OPTION.value(env);
|
||||
addTorrentParams.addPaused = PAUSED_OPTION.value(env);
|
||||
addTorrentParams.addStopped = STOPPED_OPTION.value(env);
|
||||
}
|
||||
|
||||
QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
||||
@@ -417,9 +417,9 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
||||
{
|
||||
result.addTorrentParams.savePath = Path(SAVE_PATH_OPTION.value(arg));
|
||||
}
|
||||
else if (arg == PAUSED_OPTION)
|
||||
else if (arg == STOPPED_OPTION)
|
||||
{
|
||||
result.addTorrentParams.addPaused = PAUSED_OPTION.value(arg);
|
||||
result.addTorrentParams.addStopped = STOPPED_OPTION.value(arg);
|
||||
}
|
||||
else if (arg == SKIP_HASH_CHECK_OPTION)
|
||||
{
|
||||
@@ -523,7 +523,7 @@ QString makeUsage(const QString &prgName)
|
||||
|
||||
+ wrapText(QCoreApplication::translate("CMD Options", "Options when adding new torrents:"), 0) + u'\n'
|
||||
+ SAVE_PATH_OPTION.usage(QCoreApplication::translate("CMD Options", "path")) + wrapText(QCoreApplication::translate("CMD Options", "Torrent save path")) + u'\n'
|
||||
+ PAUSED_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Add torrents as started or paused")) + u'\n'
|
||||
+ STOPPED_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Add torrents as running or stopped")) + u'\n'
|
||||
+ SKIP_HASH_CHECK_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Skip hash check")) + u'\n'
|
||||
+ CATEGORY_OPTION.usage(QCoreApplication::translate("CMD Options", "name"))
|
||||
+ wrapText(QCoreApplication::translate("CMD Options", "Assign torrents to category. If the category doesn't exist, it will be "
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
const int MIGRATION_VERSION = 7;
|
||||
const int MIGRATION_VERSION = 8;
|
||||
const QString MIGRATION_VERSION_KEY = u"Meta/MigrationVersion"_s;
|
||||
|
||||
void exportWebUIHttpsFiles()
|
||||
@@ -468,6 +468,16 @@ namespace
|
||||
|
||||
settingsStorage->removeValue(oldKey);
|
||||
}
|
||||
|
||||
void migrateAddPausedSetting()
|
||||
{
|
||||
auto *settingsStorage = SettingsStorage::instance();
|
||||
const auto oldKey = u"BitTorrent/Session/AddTorrentPaused"_s;
|
||||
const auto newKey = u"BitTorrent/Session/AddTorrentStopped"_s;
|
||||
|
||||
settingsStorage->storeValue(newKey, settingsStorage->loadValue<bool>(oldKey));
|
||||
settingsStorage->removeValue(oldKey);
|
||||
}
|
||||
}
|
||||
|
||||
bool upgrade()
|
||||
@@ -509,6 +519,9 @@ bool upgrade()
|
||||
if (version < 7)
|
||||
migrateShareLimitActionSettings();
|
||||
|
||||
if (version < 8)
|
||||
migrateAddPausedSetting();
|
||||
|
||||
version = MIGRATION_VERSION;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ add_library(qbt_base STATIC
|
||||
bittorrent/torrent.h
|
||||
bittorrent/torrentcontenthandler.h
|
||||
bittorrent/torrentcontentlayout.h
|
||||
bittorrent/torrentcontentremoveoption.h
|
||||
bittorrent/torrentcontentremover.h
|
||||
bittorrent/torrentcreationmanager.h
|
||||
bittorrent/torrentcreationtask.h
|
||||
bittorrent/torrentcreator.h
|
||||
@@ -46,6 +48,7 @@ add_library(qbt_base STATIC
|
||||
bittorrent/torrentinfo.h
|
||||
bittorrent/tracker.h
|
||||
bittorrent/trackerentry.h
|
||||
bittorrent/trackerentrystatus.h
|
||||
concepts/explicitlyconvertibleto.h
|
||||
concepts/stringable.h
|
||||
digest32.h
|
||||
@@ -106,6 +109,7 @@ add_library(qbt_base STATIC
|
||||
utils/io.h
|
||||
utils/misc.h
|
||||
utils/net.h
|
||||
utils/number.h
|
||||
utils/os.h
|
||||
utils/password.h
|
||||
utils/random.h
|
||||
@@ -143,6 +147,7 @@ add_library(qbt_base STATIC
|
||||
bittorrent/sslparameters.cpp
|
||||
bittorrent/torrent.cpp
|
||||
bittorrent/torrentcontenthandler.cpp
|
||||
bittorrent/torrentcontentremover.cpp
|
||||
bittorrent/torrentcreationmanager.cpp
|
||||
bittorrent/torrentcreationtask.cpp
|
||||
bittorrent/torrentcreator.cpp
|
||||
@@ -151,6 +156,7 @@ add_library(qbt_base STATIC
|
||||
bittorrent/torrentinfo.cpp
|
||||
bittorrent/tracker.cpp
|
||||
bittorrent/trackerentry.cpp
|
||||
bittorrent/trackerentrystatus.cpp
|
||||
exceptions.cpp
|
||||
http/connection.cpp
|
||||
http/httperror.cpp
|
||||
@@ -199,6 +205,7 @@ add_library(qbt_base STATIC
|
||||
utils/io.cpp
|
||||
utils/misc.cpp
|
||||
utils/net.cpp
|
||||
utils/number.cpp
|
||||
utils/os.cpp
|
||||
utils/password.cpp
|
||||
utils/random.cpp
|
||||
|
||||
@@ -116,7 +116,7 @@ BitTorrent::AddTorrentParams BitTorrent::parseAddTorrentParams(const QJsonObject
|
||||
.downloadPath = Path(jsonObj.value(PARAM_DOWNLOADPATH).toString()),
|
||||
.addForced = (getEnum<TorrentOperatingMode>(jsonObj, PARAM_OPERATINGMODE) == TorrentOperatingMode::Forced),
|
||||
.addToQueueTop = getOptionalBool(jsonObj, PARAM_QUEUETOP),
|
||||
.addPaused = getOptionalBool(jsonObj, PARAM_STOPPED),
|
||||
.addStopped = getOptionalBool(jsonObj, PARAM_STOPPED),
|
||||
.stopCondition = getOptionalEnum<Torrent::StopCondition>(jsonObj, PARAM_STOPCONDITION),
|
||||
.filePaths = {},
|
||||
.filePriorities = {},
|
||||
@@ -163,8 +163,8 @@ QJsonObject BitTorrent::serializeAddTorrentParams(const AddTorrentParams ¶ms
|
||||
|
||||
if (params.addToQueueTop)
|
||||
jsonObj[PARAM_QUEUETOP] = *params.addToQueueTop;
|
||||
if (params.addPaused)
|
||||
jsonObj[PARAM_STOPPED] = *params.addPaused;
|
||||
if (params.addStopped)
|
||||
jsonObj[PARAM_STOPPED] = *params.addStopped;
|
||||
if (params.stopCondition)
|
||||
jsonObj[PARAM_STOPCONDITION] = Utils::String::fromEnum(*params.stopCondition);
|
||||
if (params.contentLayout)
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace BitTorrent
|
||||
bool firstLastPiecePriority = false;
|
||||
bool addForced = false;
|
||||
std::optional<bool> addToQueueTop;
|
||||
std::optional<bool> addPaused;
|
||||
std::optional<bool> addStopped;
|
||||
std::optional<Torrent::StopCondition> stopCondition;
|
||||
PathList filePaths; // used if TorrentInfo is set
|
||||
QVector<DownloadPriority> filePriorities; // used if TorrentInfo is set
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QThread>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/exceptions.h"
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
|
||||
@@ -240,11 +240,11 @@ void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const
|
||||
|
||||
lt::storage_interface *customStorageConstructor(const lt::storage_params ¶ms, lt::file_pool &pool)
|
||||
{
|
||||
return new CustomStorage {params, pool};
|
||||
return new CustomStorage(params, pool);
|
||||
}
|
||||
|
||||
CustomStorage::CustomStorage(const lt::storage_params ¶ms, lt::file_pool &filePool)
|
||||
: lt::default_storage {params, filePool}
|
||||
: lt::default_storage(params, filePool)
|
||||
, m_savePath {params.path}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -198,7 +198,12 @@ QString PeerInfo::I2PAddress() const
|
||||
|
||||
QString PeerInfo::client() const
|
||||
{
|
||||
return QString::fromStdString(m_nativeInfo.client);
|
||||
auto client = QString::fromStdString(m_nativeInfo.client).simplified();
|
||||
|
||||
// remove non-printable characters
|
||||
erase_if(client, [](const QChar &c) { return !c.isPrint(); });
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
QString PeerInfo::peerIdClient() const
|
||||
@@ -362,6 +367,10 @@ void PeerInfo::determineFlags()
|
||||
if (useUTPSocket())
|
||||
updateFlags(u'P', C_UTP);
|
||||
|
||||
// h = Peer is using NAT hole punching
|
||||
if (isHolepunched())
|
||||
updateFlags(u'h', tr("Peer is using NAT hole punching"));
|
||||
|
||||
m_flags.chop(1);
|
||||
m_flagsDescription.chop(1);
|
||||
}
|
||||
|
||||
@@ -37,16 +37,12 @@
|
||||
#include "addtorrentparams.h"
|
||||
#include "categoryoptions.h"
|
||||
#include "sharelimitaction.h"
|
||||
#include "torrentcontentremoveoption.h"
|
||||
#include "trackerentry.h"
|
||||
#include "trackerentrystatus.h"
|
||||
|
||||
class QString;
|
||||
|
||||
enum DeleteOption
|
||||
{
|
||||
DeleteTorrent,
|
||||
DeleteTorrentAndFiles
|
||||
};
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class InfoHash;
|
||||
@@ -57,6 +53,12 @@ namespace BitTorrent
|
||||
struct CacheStatus;
|
||||
struct SessionStatus;
|
||||
|
||||
enum class TorrentRemoveOption
|
||||
{
|
||||
KeepContent,
|
||||
RemoveContent
|
||||
};
|
||||
|
||||
// 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
|
||||
@@ -213,8 +215,8 @@ namespace BitTorrent
|
||||
virtual void setPeXEnabled(bool enabled) = 0;
|
||||
virtual bool isAddTorrentToQueueTop() const = 0;
|
||||
virtual void setAddTorrentToQueueTop(bool value) = 0;
|
||||
virtual bool isAddTorrentPaused() const = 0;
|
||||
virtual void setAddTorrentPaused(bool value) = 0;
|
||||
virtual bool isAddTorrentStopped() const = 0;
|
||||
virtual void setAddTorrentStopped(bool value) = 0;
|
||||
virtual Torrent::StopCondition torrentStopCondition() const = 0;
|
||||
virtual void setTorrentStopCondition(Torrent::StopCondition stopCondition) = 0;
|
||||
virtual TorrentContentLayout torrentContentLayout() const = 0;
|
||||
@@ -255,6 +257,8 @@ namespace BitTorrent
|
||||
virtual void setPerformanceWarningEnabled(bool enable) = 0;
|
||||
virtual int saveResumeDataInterval() const = 0;
|
||||
virtual void setSaveResumeDataInterval(int value) = 0;
|
||||
virtual int shutdownTimeout() const = 0;
|
||||
virtual void setShutdownTimeout(int value) = 0;
|
||||
virtual int port() const = 0;
|
||||
virtual void setPort(int port) = 0;
|
||||
virtual bool isSSLEnabled() const = 0;
|
||||
@@ -422,16 +426,24 @@ namespace BitTorrent
|
||||
virtual void setExcludedFileNamesEnabled(bool enabled) = 0;
|
||||
virtual QStringList excludedFileNames() const = 0;
|
||||
virtual void setExcludedFileNames(const QStringList &newList) = 0;
|
||||
virtual bool isFilenameExcluded(const QString &fileName) const = 0;
|
||||
virtual void applyFilenameFilter(const PathList &files, QList<BitTorrent::DownloadPriority> &priorities) = 0;
|
||||
virtual QStringList bannedIPs() const = 0;
|
||||
virtual void setBannedIPs(const QStringList &newList) = 0;
|
||||
virtual ResumeDataStorageType resumeDataStorageType() const = 0;
|
||||
virtual void setResumeDataStorageType(ResumeDataStorageType type) = 0;
|
||||
virtual bool isMergeTrackersEnabled() const = 0;
|
||||
virtual void setMergeTrackersEnabled(bool enabled) = 0;
|
||||
virtual bool isStartPaused() const = 0;
|
||||
virtual void setStartPaused(bool value) = 0;
|
||||
virtual TorrentContentRemoveOption torrentContentRemoveOption() const = 0;
|
||||
virtual void setTorrentContentRemoveOption(TorrentContentRemoveOption option) = 0;
|
||||
|
||||
virtual bool isRestored() const = 0;
|
||||
|
||||
virtual bool isPaused() const = 0;
|
||||
virtual void pause() = 0;
|
||||
virtual void resume() = 0;
|
||||
|
||||
virtual Torrent *getTorrent(const TorrentID &id) const = 0;
|
||||
virtual Torrent *findTorrent(const InfoHash &infoHash) const = 0;
|
||||
virtual QVector<Torrent *> torrents() const = 0;
|
||||
@@ -444,7 +456,7 @@ namespace BitTorrent
|
||||
|
||||
virtual bool isKnownTorrent(const InfoHash &infoHash) const = 0;
|
||||
virtual bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) = 0;
|
||||
virtual bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteOption::DeleteTorrent) = 0;
|
||||
virtual bool removeTorrent(const TorrentID &id, TorrentRemoveOption deleteOption = TorrentRemoveOption::KeepContent) = 0;
|
||||
virtual bool downloadMetadata(const TorrentDescriptor &torrentDescr) = 0;
|
||||
virtual bool cancelDownloadMetadata(const TorrentID &id) = 0;
|
||||
|
||||
@@ -465,6 +477,8 @@ namespace BitTorrent
|
||||
void loadTorrentFailed(const QString &error);
|
||||
void metadataDownloaded(const TorrentInfo &info);
|
||||
void restored();
|
||||
void paused();
|
||||
void resumed();
|
||||
void speedLimitModeChanged(bool alternative);
|
||||
void statsUpdated();
|
||||
void subcategoriesSupportChanged();
|
||||
@@ -476,8 +490,8 @@ namespace BitTorrent
|
||||
void torrentFinished(Torrent *torrent);
|
||||
void torrentFinishedChecking(Torrent *torrent);
|
||||
void torrentMetadataReceived(Torrent *torrent);
|
||||
void torrentPaused(Torrent *torrent);
|
||||
void torrentResumed(Torrent *torrent);
|
||||
void torrentStopped(Torrent *torrent);
|
||||
void torrentStarted(Torrent *torrent);
|
||||
void torrentSavePathChanged(Torrent *torrent);
|
||||
void torrentSavingModeChanged(Torrent *torrent);
|
||||
void torrentsLoaded(const QVector<Torrent *> &torrents);
|
||||
@@ -490,6 +504,6 @@ namespace BitTorrent
|
||||
void trackersRemoved(Torrent *torrent, const QStringList &trackers);
|
||||
void trackerSuccess(Torrent *torrent, const QString &tracker);
|
||||
void trackerWarning(Torrent *torrent, const QString &tracker);
|
||||
void trackerEntriesUpdated(Torrent *torrent, const QHash<QString, TrackerEntry> &updatedTrackerEntries);
|
||||
void trackerEntryStatusesUpdated(Torrent *torrent, const QHash<QString, TrackerEntryStatus> &updatedTrackers);
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,7 +54,7 @@
|
||||
#include "session.h"
|
||||
#include "sessionstatus.h"
|
||||
#include "torrentinfo.h"
|
||||
#include "trackerentry.h"
|
||||
#include "trackerentrystatus.h"
|
||||
|
||||
class QString;
|
||||
class QThread;
|
||||
@@ -69,16 +69,19 @@ class NativeSessionExtension;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class MoveStorageMode;
|
||||
enum class MoveStorageContext;
|
||||
|
||||
class InfoHash;
|
||||
class ResumeDataStorage;
|
||||
class Torrent;
|
||||
class TorrentContentRemover;
|
||||
class TorrentDescriptor;
|
||||
class TorrentImpl;
|
||||
class Tracker;
|
||||
struct LoadTorrentParams;
|
||||
|
||||
enum class MoveStorageMode;
|
||||
enum class MoveStorageContext;
|
||||
struct LoadTorrentParams;
|
||||
struct TrackerEntry;
|
||||
|
||||
struct SessionMetricIndices
|
||||
{
|
||||
@@ -189,8 +192,8 @@ namespace BitTorrent
|
||||
void setPeXEnabled(bool enabled) override;
|
||||
bool isAddTorrentToQueueTop() const override;
|
||||
void setAddTorrentToQueueTop(bool value) override;
|
||||
bool isAddTorrentPaused() const override;
|
||||
void setAddTorrentPaused(bool value) override;
|
||||
bool isAddTorrentStopped() const override;
|
||||
void setAddTorrentStopped(bool value) override;
|
||||
Torrent::StopCondition torrentStopCondition() const override;
|
||||
void setTorrentStopCondition(Torrent::StopCondition stopCondition) override;
|
||||
TorrentContentLayout torrentContentLayout() const override;
|
||||
@@ -231,6 +234,8 @@ namespace BitTorrent
|
||||
void setPerformanceWarningEnabled(bool enable) override;
|
||||
int saveResumeDataInterval() const override;
|
||||
void setSaveResumeDataInterval(int value) override;
|
||||
int shutdownTimeout() const override;
|
||||
void setShutdownTimeout(int value) override;
|
||||
int port() const override;
|
||||
void setPort(int port) override;
|
||||
bool isSSLEnabled() const override;
|
||||
@@ -398,16 +403,24 @@ namespace BitTorrent
|
||||
void setExcludedFileNamesEnabled(bool enabled) override;
|
||||
QStringList excludedFileNames() const override;
|
||||
void setExcludedFileNames(const QStringList &excludedFileNames) override;
|
||||
bool isFilenameExcluded(const QString &fileName) const override;
|
||||
void applyFilenameFilter(const PathList &files, QList<BitTorrent::DownloadPriority> &priorities) override;
|
||||
QStringList bannedIPs() const override;
|
||||
void setBannedIPs(const QStringList &newList) override;
|
||||
ResumeDataStorageType resumeDataStorageType() const override;
|
||||
void setResumeDataStorageType(ResumeDataStorageType type) override;
|
||||
bool isMergeTrackersEnabled() const override;
|
||||
void setMergeTrackersEnabled(bool enabled) override;
|
||||
bool isStartPaused() const override;
|
||||
void setStartPaused(bool value) override;
|
||||
TorrentContentRemoveOption torrentContentRemoveOption() const override;
|
||||
void setTorrentContentRemoveOption(TorrentContentRemoveOption option) override;
|
||||
|
||||
bool isRestored() const override;
|
||||
|
||||
bool isPaused() const override;
|
||||
void pause() override;
|
||||
void resume() override;
|
||||
|
||||
Torrent *getTorrent(const TorrentID &id) const override;
|
||||
Torrent *findTorrent(const InfoHash &infoHash) const override;
|
||||
QVector<Torrent *> torrents() const override;
|
||||
@@ -420,7 +433,7 @@ namespace BitTorrent
|
||||
|
||||
bool isKnownTorrent(const InfoHash &infoHash) const override;
|
||||
bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) override;
|
||||
bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteTorrent) override;
|
||||
bool removeTorrent(const TorrentID &id, TorrentRemoveOption deleteOption = TorrentRemoveOption::KeepContent) override;
|
||||
bool downloadMetadata(const TorrentDescriptor &torrentDescr) override;
|
||||
bool cancelDownloadMetadata(const TorrentID &id) override;
|
||||
|
||||
@@ -439,8 +452,8 @@ namespace BitTorrent
|
||||
void handleTorrentTagRemoved(TorrentImpl *torrent, const Tag &tag);
|
||||
void handleTorrentSavingModeChanged(TorrentImpl *torrent);
|
||||
void handleTorrentMetadataReceived(TorrentImpl *torrent);
|
||||
void handleTorrentPaused(TorrentImpl *torrent);
|
||||
void handleTorrentResumed(TorrentImpl *torrent);
|
||||
void handleTorrentStopped(TorrentImpl *torrent);
|
||||
void handleTorrentStarted(TorrentImpl *torrent);
|
||||
void handleTorrentChecked(TorrentImpl *torrent);
|
||||
void handleTorrentFinished(TorrentImpl *torrent);
|
||||
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QVector<TrackerEntry> &newTrackers);
|
||||
@@ -477,11 +490,11 @@ namespace BitTorrent
|
||||
void configureDeferred();
|
||||
void readAlerts();
|
||||
void enqueueRefresh();
|
||||
void processShareLimits();
|
||||
void generateResumeData();
|
||||
void handleIPFilterParsed(int ruleCount);
|
||||
void handleIPFilterError();
|
||||
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
|
||||
void torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage);
|
||||
|
||||
private:
|
||||
struct ResumeSessionContext;
|
||||
@@ -497,8 +510,9 @@ namespace BitTorrent
|
||||
struct RemovingTorrentData
|
||||
{
|
||||
QString name;
|
||||
Path pathToRemove;
|
||||
DeleteOption deleteOption {};
|
||||
Path contentStoragePath;
|
||||
PathList fileNames;
|
||||
TorrentRemoveOption removeOption {};
|
||||
};
|
||||
|
||||
explicit SessionImpl(QObject *parent = nullptr);
|
||||
@@ -526,6 +540,7 @@ namespace BitTorrent
|
||||
void enableIPFilter();
|
||||
void disableIPFilter();
|
||||
void processTrackerStatuses();
|
||||
void processTorrentShareLimits(TorrentImpl *torrent);
|
||||
void populateExcludedFileNamesRegExpList();
|
||||
void prepareStartup();
|
||||
void handleLoadedResumeData(ResumeSessionContext *context);
|
||||
@@ -538,34 +553,34 @@ namespace BitTorrent
|
||||
void updateSeedingLimitTimer();
|
||||
void exportTorrentFile(const Torrent *torrent, const Path &folderPath);
|
||||
|
||||
void handleAlert(const lt::alert *a);
|
||||
void handleAddTorrentAlerts(const std::vector<lt::alert *> &alerts);
|
||||
void dispatchTorrentAlert(const lt::torrent_alert *a);
|
||||
void handleStateUpdateAlert(const lt::state_update_alert *p);
|
||||
void handleMetadataReceivedAlert(const lt::metadata_received_alert *p);
|
||||
void handleFileErrorAlert(const lt::file_error_alert *p);
|
||||
void handleTorrentRemovedAlert(const lt::torrent_removed_alert *p);
|
||||
void handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p);
|
||||
void handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p);
|
||||
void handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *a);
|
||||
void handlePortmapWarningAlert(const lt::portmap_error_alert *p);
|
||||
void handlePortmapAlert(const lt::portmap_alert *p);
|
||||
void handlePeerBlockedAlert(const lt::peer_blocked_alert *p);
|
||||
void handlePeerBanAlert(const lt::peer_ban_alert *p);
|
||||
void handleUrlSeedAlert(const lt::url_seed_alert *p);
|
||||
void handleListenSucceededAlert(const lt::listen_succeeded_alert *p);
|
||||
void handleListenFailedAlert(const lt::listen_failed_alert *p);
|
||||
void handleExternalIPAlert(const lt::external_ip_alert *p);
|
||||
void handleSessionErrorAlert(const lt::session_error_alert *p) const;
|
||||
void handleSessionStatsAlert(const lt::session_stats_alert *p);
|
||||
void handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const;
|
||||
void handleStorageMovedAlert(const lt::storage_moved_alert *p);
|
||||
void handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p);
|
||||
void handleSocks5Alert(const lt::socks5_alert *p) const;
|
||||
void handleI2PAlert(const lt::i2p_alert *p) const;
|
||||
void handleTrackerAlert(const lt::tracker_alert *a);
|
||||
void handleAlert(const lt::alert *alert);
|
||||
void dispatchTorrentAlert(const lt::torrent_alert *alert);
|
||||
void handleAddTorrentAlert(const lt::add_torrent_alert *alert);
|
||||
void handleStateUpdateAlert(const lt::state_update_alert *alert);
|
||||
void handleMetadataReceivedAlert(const lt::metadata_received_alert *alert);
|
||||
void handleFileErrorAlert(const lt::file_error_alert *alert);
|
||||
void handleTorrentRemovedAlert(const lt::torrent_removed_alert *alert);
|
||||
void handleTorrentDeletedAlert(const lt::torrent_deleted_alert *alert);
|
||||
void handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *alert);
|
||||
void handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *alert);
|
||||
void handlePortmapWarningAlert(const lt::portmap_error_alert *alert);
|
||||
void handlePortmapAlert(const lt::portmap_alert *alert);
|
||||
void handlePeerBlockedAlert(const lt::peer_blocked_alert *alert);
|
||||
void handlePeerBanAlert(const lt::peer_ban_alert *alert);
|
||||
void handleUrlSeedAlert(const lt::url_seed_alert *alert);
|
||||
void handleListenSucceededAlert(const lt::listen_succeeded_alert *alert);
|
||||
void handleListenFailedAlert(const lt::listen_failed_alert *alert);
|
||||
void handleExternalIPAlert(const lt::external_ip_alert *alert);
|
||||
void handleSessionErrorAlert(const lt::session_error_alert *alert) const;
|
||||
void handleSessionStatsAlert(const lt::session_stats_alert *alert);
|
||||
void handleAlertsDroppedAlert(const lt::alerts_dropped_alert *alert) const;
|
||||
void handleStorageMovedAlert(const lt::storage_moved_alert *alert);
|
||||
void handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *alert);
|
||||
void handleSocks5Alert(const lt::socks5_alert *alert) const;
|
||||
void handleI2PAlert(const lt::i2p_alert *alert) const;
|
||||
void handleTrackerAlert(const lt::tracker_alert *alert);
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
void handleTorrentConflictAlert(const lt::torrent_conflict_alert *a);
|
||||
void handleTorrentConflictAlert(const lt::torrent_conflict_alert *alert);
|
||||
#endif
|
||||
|
||||
TorrentImpl *createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms);
|
||||
@@ -587,15 +602,9 @@ namespace BitTorrent
|
||||
void saveStatistics() const;
|
||||
void loadStatistics();
|
||||
|
||||
void updateTrackerEntries(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers);
|
||||
void updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers);
|
||||
|
||||
// BitTorrent
|
||||
lt::session *m_nativeSession = nullptr;
|
||||
NativeSessionExtension *m_nativeSessionExtension = nullptr;
|
||||
|
||||
bool m_deferredConfigureScheduled = false;
|
||||
bool m_IPFilteringConfigured = false;
|
||||
mutable bool m_listenInterfaceConfigured = false;
|
||||
void handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError = {});
|
||||
|
||||
CachedSettingValue<QString> m_DHTBootstrapNodes;
|
||||
CachedSettingValue<bool> m_isDHTEnabled;
|
||||
@@ -663,7 +672,7 @@ namespace BitTorrent
|
||||
CachedSettingValue<int> m_globalMaxSeedingMinutes;
|
||||
CachedSettingValue<int> m_globalMaxInactiveSeedingMinutes;
|
||||
CachedSettingValue<bool> m_isAddTorrentToQueueTop;
|
||||
CachedSettingValue<bool> m_isAddTorrentPaused;
|
||||
CachedSettingValue<bool> m_isAddTorrentStopped;
|
||||
CachedSettingValue<Torrent::StopCondition> m_torrentStopCondition;
|
||||
CachedSettingValue<TorrentContentLayout> m_torrentContentLayout;
|
||||
CachedSettingValue<bool> m_isAppendExtensionEnabled;
|
||||
@@ -680,6 +689,7 @@ namespace BitTorrent
|
||||
CachedSettingValue<bool> m_isBandwidthSchedulerEnabled;
|
||||
CachedSettingValue<bool> m_isPerformanceWarningEnabled;
|
||||
CachedSettingValue<int> m_saveResumeDataInterval;
|
||||
CachedSettingValue<int> m_shutdownTimeout;
|
||||
CachedSettingValue<int> m_port;
|
||||
CachedSettingValue<bool> m_sslEnabled;
|
||||
CachedSettingValue<int> m_sslPort;
|
||||
@@ -720,8 +730,18 @@ namespace BitTorrent
|
||||
CachedSettingValue<int> m_I2POutboundQuantity;
|
||||
CachedSettingValue<int> m_I2PInboundLength;
|
||||
CachedSettingValue<int> m_I2POutboundLength;
|
||||
CachedSettingValue<TorrentContentRemoveOption> m_torrentContentRemoveOption;
|
||||
SettingValue<bool> m_startPaused;
|
||||
|
||||
lt::session *m_nativeSession = nullptr;
|
||||
NativeSessionExtension *m_nativeSessionExtension = nullptr;
|
||||
|
||||
bool m_deferredConfigureScheduled = false;
|
||||
bool m_IPFilteringConfigured = false;
|
||||
mutable bool m_listenInterfaceConfigured = false;
|
||||
|
||||
bool m_isRestored = false;
|
||||
bool m_isPaused = isStartPaused();
|
||||
|
||||
// Order is important. This needs to be declared after its CachedSettingsValue
|
||||
// counterpart, because it uses it for initialization in the constructor
|
||||
@@ -729,7 +749,7 @@ namespace BitTorrent
|
||||
const bool m_wasPexEnabled = m_isPeXEnabled;
|
||||
|
||||
int m_numResumeData = 0;
|
||||
QVector<TrackerEntry> m_additionalTrackerList;
|
||||
QVector<TrackerEntry> m_additionalTrackerEntries;
|
||||
QVector<QRegularExpression> m_excludedFileNamesRegExpList;
|
||||
|
||||
// Statistics
|
||||
@@ -753,6 +773,7 @@ namespace BitTorrent
|
||||
QThreadPool *m_asyncWorker = nullptr;
|
||||
ResumeDataStorage *m_resumeDataStorage = nullptr;
|
||||
FileSearcher *m_fileSearcher = nullptr;
|
||||
TorrentContentRemover *m_torrentContentRemover = nullptr;
|
||||
|
||||
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
|
||||
|
||||
@@ -764,9 +785,12 @@ namespace BitTorrent
|
||||
QMap<QString, CategoryOptions> m_categories;
|
||||
TagSet m_tags;
|
||||
|
||||
qsizetype m_receivedAddTorrentAlertsCount = 0;
|
||||
QList<Torrent *> m_loadedTorrents;
|
||||
|
||||
// This field holds amounts of peers reported by trackers in their responses to announces
|
||||
// (torrent.tracker_name.tracker_local_endpoint.protocol_version.num_peers)
|
||||
QHash<lt::torrent_handle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>>> m_updatedTrackerEntries;
|
||||
QHash<lt::torrent_handle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>>> m_updatedTrackerStatuses;
|
||||
|
||||
// I/O errored torrents
|
||||
QSet<TorrentID> m_recentErroredTorrents;
|
||||
@@ -793,6 +817,8 @@ namespace BitTorrent
|
||||
QTimer *m_wakeupCheckTimer = nullptr;
|
||||
QDateTime m_wakeupCheckTimestamp;
|
||||
|
||||
QList<TorrentImpl *> m_pendingFinishedTorrents;
|
||||
|
||||
friend void Session::initInstance();
|
||||
friend void Session::freeInstance();
|
||||
friend Session *Session::instance();
|
||||
|
||||
@@ -60,9 +60,9 @@ namespace BitTorrent
|
||||
return infoHash().toTorrentID();
|
||||
}
|
||||
|
||||
bool Torrent::isResumed() const
|
||||
bool Torrent::isRunning() const
|
||||
{
|
||||
return !isPaused();
|
||||
return !isStopped();
|
||||
}
|
||||
|
||||
qlonglong Torrent::remainingSize() const
|
||||
|
||||
@@ -48,14 +48,17 @@ class QUrl;
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class DownloadPriority;
|
||||
|
||||
class InfoHash;
|
||||
class PeerInfo;
|
||||
class Session;
|
||||
class TorrentID;
|
||||
class TorrentInfo;
|
||||
|
||||
struct PeerAddress;
|
||||
struct SSLParameters;
|
||||
struct TrackerEntry;
|
||||
struct TrackerEntryStatus;
|
||||
|
||||
// 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.
|
||||
@@ -94,8 +97,8 @@ namespace BitTorrent
|
||||
CheckingUploading,
|
||||
CheckingDownloading,
|
||||
|
||||
PausedDownloading,
|
||||
PausedUploading,
|
||||
StoppedDownloading,
|
||||
StoppedUploading,
|
||||
|
||||
Moving,
|
||||
|
||||
@@ -225,10 +228,11 @@ namespace BitTorrent
|
||||
virtual void setShareLimitAction(ShareLimitAction action) = 0;
|
||||
|
||||
virtual PathList filePaths() const = 0;
|
||||
virtual PathList actualFilePaths() const = 0;
|
||||
|
||||
virtual TorrentInfo info() const = 0;
|
||||
virtual bool isFinished() const = 0;
|
||||
virtual bool isPaused() const = 0;
|
||||
virtual bool isStopped() const = 0;
|
||||
virtual bool isQueued() const = 0;
|
||||
virtual bool isForced() const = 0;
|
||||
virtual bool isChecking() const = 0;
|
||||
@@ -245,7 +249,7 @@ namespace BitTorrent
|
||||
virtual bool hasMissingFiles() const = 0;
|
||||
virtual bool hasError() const = 0;
|
||||
virtual int queuePosition() const = 0;
|
||||
virtual QVector<TrackerEntry> trackers() const = 0;
|
||||
virtual QVector<TrackerEntryStatus> trackers() const = 0;
|
||||
virtual QVector<QUrl> urlSeeds() const = 0;
|
||||
virtual QString error() const = 0;
|
||||
virtual qlonglong totalDownload() const = 0;
|
||||
@@ -279,6 +283,7 @@ namespace BitTorrent
|
||||
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;
|
||||
virtual int downloadPayloadRate() const = 0;
|
||||
virtual qlonglong totalPayloadUpload() const = 0;
|
||||
@@ -290,8 +295,8 @@ namespace BitTorrent
|
||||
virtual void setName(const QString &name) = 0;
|
||||
virtual void setSequentialDownload(bool enable) = 0;
|
||||
virtual void setFirstLastPiecePriority(bool enabled) = 0;
|
||||
virtual void pause() = 0;
|
||||
virtual void resume(TorrentOperatingMode mode = TorrentOperatingMode::AutoManaged) = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void start(TorrentOperatingMode mode = TorrentOperatingMode::AutoManaged) = 0;
|
||||
virtual void forceReannounce(int index = -1) = 0;
|
||||
virtual void forceDHTAnnounce() = 0;
|
||||
virtual void forceRecheck() = 0;
|
||||
@@ -325,7 +330,7 @@ namespace BitTorrent
|
||||
virtual void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const = 0;
|
||||
|
||||
TorrentID id() const;
|
||||
bool isResumed() const;
|
||||
bool isRunning() const;
|
||||
qlonglong remainingSize() const;
|
||||
|
||||
void toggleSequentialDownload();
|
||||
|
||||
50
src/base/bittorrent/torrentcontentremoveoption.h
Normal file
50
src/base/bittorrent/torrentcontentremoveoption.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMetaEnum>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
// 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
|
||||
inline namespace TorrentContentRemoveOptionNS
|
||||
{
|
||||
Q_NAMESPACE
|
||||
|
||||
enum class TorrentContentRemoveOption
|
||||
{
|
||||
Delete,
|
||||
MoveToTrash
|
||||
};
|
||||
|
||||
Q_ENUM_NS(TorrentContentRemoveOption)
|
||||
}
|
||||
}
|
||||
61
src/base/bittorrent/torrentcontentremover.cpp
Normal file
61
src/base/bittorrent/torrentcontentremover.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "torrentcontentremover.h"
|
||||
|
||||
#include "base/utils/fs.h"
|
||||
|
||||
void BitTorrent::TorrentContentRemover::performJob(const QString &torrentName, const Path &basePath
|
||||
, const PathList &fileNames, const TorrentContentRemoveOption option)
|
||||
{
|
||||
QString errorMessage;
|
||||
|
||||
if (!fileNames.isEmpty())
|
||||
{
|
||||
const auto removeFileFn = [&option](const Path &filePath)
|
||||
{
|
||||
return ((option == TorrentContentRemoveOption::MoveToTrash)
|
||||
? Utils::Fs::moveFileToTrash : Utils::Fs::removeFile)(filePath);
|
||||
};
|
||||
|
||||
for (const Path &fileName : fileNames)
|
||||
{
|
||||
if (const auto result = removeFileFn(basePath / fileName)
|
||||
; !result && errorMessage.isEmpty())
|
||||
{
|
||||
errorMessage = result.error();
|
||||
}
|
||||
}
|
||||
|
||||
const Path rootPath = Path::findRootFolder(fileNames);
|
||||
if (!rootPath.isEmpty())
|
||||
Utils::Fs::smartRemoveEmptyFolderTree(basePath / rootPath);
|
||||
}
|
||||
|
||||
emit jobFinished(torrentName, errorMessage);
|
||||
}
|
||||
53
src/base/bittorrent/torrentcontentremover.h
Normal file
53
src/base/bittorrent/torrentcontentremover.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "base/path.h"
|
||||
#include "torrentcontentremoveoption.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentContentRemover final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(TorrentContentRemover)
|
||||
|
||||
public:
|
||||
using QObject::QObject;
|
||||
|
||||
public slots:
|
||||
void performJob(const QString &torrentName, const Path &basePath
|
||||
, const PathList &fileNames, TorrentContentRemoveOption option);
|
||||
|
||||
signals:
|
||||
void jobFinished(const QString &torrentName, const QString &errorMessage);
|
||||
};
|
||||
}
|
||||
@@ -71,11 +71,16 @@
|
||||
#include "peeraddress.h"
|
||||
#include "peerinfo.h"
|
||||
#include "sessionimpl.h"
|
||||
#include "trackerentry.h"
|
||||
|
||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||
#include "base/utils/os.h"
|
||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
#include "customstorage.h"
|
||||
#endif
|
||||
|
||||
using namespace BitTorrent;
|
||||
|
||||
namespace
|
||||
@@ -101,15 +106,15 @@ namespace
|
||||
return QString::fromStdString((std::stringstream() << ltTCPEndpoint).str());
|
||||
}
|
||||
|
||||
void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
|
||||
void updateTrackerEntryStatus(TrackerEntryStatus &trackerEntryStatus, const lt::announce_entry &nativeEntry
|
||||
, const QSet<int> &btProtocols, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
|
||||
{
|
||||
Q_ASSERT(trackerEntry.url == QString::fromStdString(nativeEntry.url));
|
||||
Q_ASSERT(trackerEntryStatus.url == QString::fromStdString(nativeEntry.url));
|
||||
|
||||
trackerEntry.tier = nativeEntry.tier;
|
||||
trackerEntryStatus.tier = nativeEntry.tier;
|
||||
|
||||
// remove outdated endpoints
|
||||
trackerEntry.endpointEntries.removeIf([&nativeEntry](const QHash<std::pair<QString, int>, TrackerEndpointEntry>::iterator &iter)
|
||||
trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash<std::pair<QString, int>, TrackerEndpointStatus>::iterator &iter)
|
||||
{
|
||||
return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend()
|
||||
, [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint)
|
||||
@@ -141,61 +146,61 @@ namespace
|
||||
const lt::announce_endpoint <AnnounceInfo = ltAnnounceEndpoint;
|
||||
#endif
|
||||
const QMap<int, int> &endpointUpdateInfo = updateInfo[ltAnnounceEndpoint.local_endpoint];
|
||||
TrackerEndpointEntry &trackerEndpointEntry = trackerEntry.endpointEntries[std::make_pair(endpointName, protocolVersion)];
|
||||
TrackerEndpointStatus &trackerEndpointStatus = trackerEntryStatus.endpoints[std::make_pair(endpointName, protocolVersion)];
|
||||
|
||||
trackerEndpointEntry.name = endpointName;
|
||||
trackerEndpointEntry.btVersion = protocolVersion;
|
||||
trackerEndpointEntry.numPeers = endpointUpdateInfo.value(protocolVersion, trackerEndpointEntry.numPeers);
|
||||
trackerEndpointEntry.numSeeds = ltAnnounceInfo.scrape_complete;
|
||||
trackerEndpointEntry.numLeeches = ltAnnounceInfo.scrape_incomplete;
|
||||
trackerEndpointEntry.numDownloaded = ltAnnounceInfo.scrape_downloaded;
|
||||
trackerEndpointEntry.nextAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.next_announce);
|
||||
trackerEndpointEntry.minAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.min_announce);
|
||||
trackerEndpointStatus.name = endpointName;
|
||||
trackerEndpointStatus.btVersion = protocolVersion;
|
||||
trackerEndpointStatus.numPeers = endpointUpdateInfo.value(protocolVersion, trackerEndpointStatus.numPeers);
|
||||
trackerEndpointStatus.numSeeds = ltAnnounceInfo.scrape_complete;
|
||||
trackerEndpointStatus.numLeeches = ltAnnounceInfo.scrape_incomplete;
|
||||
trackerEndpointStatus.numDownloaded = ltAnnounceInfo.scrape_downloaded;
|
||||
trackerEndpointStatus.nextAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.next_announce);
|
||||
trackerEndpointStatus.minAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.min_announce);
|
||||
|
||||
if (ltAnnounceInfo.updating)
|
||||
{
|
||||
trackerEndpointEntry.status = TrackerEntryStatus::Updating;
|
||||
trackerEndpointStatus.state = TrackerEndpointState::Updating;
|
||||
++numUpdating;
|
||||
}
|
||||
else if (ltAnnounceInfo.fails > 0)
|
||||
{
|
||||
if (ltAnnounceInfo.last_error == lt::errors::tracker_failure)
|
||||
{
|
||||
trackerEndpointEntry.status = TrackerEntryStatus::TrackerError;
|
||||
trackerEndpointStatus.state = TrackerEndpointState::TrackerError;
|
||||
++numTrackerError;
|
||||
}
|
||||
else if (ltAnnounceInfo.last_error == lt::errors::announce_skipped)
|
||||
{
|
||||
trackerEndpointEntry.status = TrackerEntryStatus::Unreachable;
|
||||
trackerEndpointStatus.state = TrackerEndpointState::Unreachable;
|
||||
++numUnreachable;
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerEndpointEntry.status = TrackerEntryStatus::NotWorking;
|
||||
trackerEndpointStatus.state = TrackerEndpointState::NotWorking;
|
||||
++numNotWorking;
|
||||
}
|
||||
}
|
||||
else if (nativeEntry.verified)
|
||||
{
|
||||
trackerEndpointEntry.status = TrackerEntryStatus::Working;
|
||||
trackerEndpointStatus.state = TrackerEndpointState::Working;
|
||||
++numWorking;
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerEndpointEntry.status = TrackerEntryStatus::NotContacted;
|
||||
trackerEndpointStatus.state = TrackerEndpointState::NotContacted;
|
||||
}
|
||||
|
||||
if (!ltAnnounceInfo.message.empty())
|
||||
{
|
||||
trackerEndpointEntry.message = QString::fromStdString(ltAnnounceInfo.message);
|
||||
trackerEndpointStatus.message = QString::fromStdString(ltAnnounceInfo.message);
|
||||
}
|
||||
else if (ltAnnounceInfo.last_error)
|
||||
{
|
||||
trackerEndpointEntry.message = QString::fromLocal8Bit(ltAnnounceInfo.last_error.message());
|
||||
trackerEndpointStatus.message = QString::fromLocal8Bit(ltAnnounceInfo.last_error.message());
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerEndpointEntry.message.clear();
|
||||
trackerEndpointStatus.message.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,58 +209,58 @@ namespace
|
||||
{
|
||||
if (numUpdating > 0)
|
||||
{
|
||||
trackerEntry.status = TrackerEntryStatus::Updating;
|
||||
trackerEntryStatus.state = TrackerEndpointState::Updating;
|
||||
}
|
||||
else if (numWorking > 0)
|
||||
{
|
||||
trackerEntry.status = TrackerEntryStatus::Working;
|
||||
trackerEntryStatus.state = TrackerEndpointState::Working;
|
||||
}
|
||||
else if (numTrackerError > 0)
|
||||
{
|
||||
trackerEntry.status = TrackerEntryStatus::TrackerError;
|
||||
trackerEntryStatus.state = TrackerEndpointState::TrackerError;
|
||||
}
|
||||
else if (numUnreachable == numEndpoints)
|
||||
{
|
||||
trackerEntry.status = TrackerEntryStatus::Unreachable;
|
||||
trackerEntryStatus.state = TrackerEndpointState::Unreachable;
|
||||
}
|
||||
else if ((numUnreachable + numNotWorking) == numEndpoints)
|
||||
{
|
||||
trackerEntry.status = TrackerEntryStatus::NotWorking;
|
||||
trackerEntryStatus.state = TrackerEndpointState::NotWorking;
|
||||
}
|
||||
}
|
||||
|
||||
trackerEntry.numPeers = -1;
|
||||
trackerEntry.numSeeds = -1;
|
||||
trackerEntry.numLeeches = -1;
|
||||
trackerEntry.numDownloaded = -1;
|
||||
trackerEntry.nextAnnounceTime = QDateTime();
|
||||
trackerEntry.minAnnounceTime = QDateTime();
|
||||
trackerEntry.message.clear();
|
||||
trackerEntryStatus.numPeers = -1;
|
||||
trackerEntryStatus.numSeeds = -1;
|
||||
trackerEntryStatus.numLeeches = -1;
|
||||
trackerEntryStatus.numDownloaded = -1;
|
||||
trackerEntryStatus.nextAnnounceTime = QDateTime();
|
||||
trackerEntryStatus.minAnnounceTime = QDateTime();
|
||||
trackerEntryStatus.message.clear();
|
||||
|
||||
for (const TrackerEndpointEntry &endpointEntry : asConst(trackerEntry.endpointEntries))
|
||||
for (const TrackerEndpointStatus &endpointStatus : asConst(trackerEntryStatus.endpoints))
|
||||
{
|
||||
trackerEntry.numPeers = std::max(trackerEntry.numPeers, endpointEntry.numPeers);
|
||||
trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, endpointEntry.numSeeds);
|
||||
trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, endpointEntry.numLeeches);
|
||||
trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, endpointEntry.numDownloaded);
|
||||
trackerEntryStatus.numPeers = std::max(trackerEntryStatus.numPeers, endpointStatus.numPeers);
|
||||
trackerEntryStatus.numSeeds = std::max(trackerEntryStatus.numSeeds, endpointStatus.numSeeds);
|
||||
trackerEntryStatus.numLeeches = std::max(trackerEntryStatus.numLeeches, endpointStatus.numLeeches);
|
||||
trackerEntryStatus.numDownloaded = std::max(trackerEntryStatus.numDownloaded, endpointStatus.numDownloaded);
|
||||
|
||||
if (endpointEntry.status == trackerEntry.status)
|
||||
if (endpointStatus.state == trackerEntryStatus.state)
|
||||
{
|
||||
if (!trackerEntry.nextAnnounceTime.isValid() || (trackerEntry.nextAnnounceTime > endpointEntry.nextAnnounceTime))
|
||||
if (!trackerEntryStatus.nextAnnounceTime.isValid() || (trackerEntryStatus.nextAnnounceTime > endpointStatus.nextAnnounceTime))
|
||||
{
|
||||
trackerEntry.nextAnnounceTime = endpointEntry.nextAnnounceTime;
|
||||
trackerEntry.minAnnounceTime = endpointEntry.minAnnounceTime;
|
||||
if ((endpointEntry.status != TrackerEntryStatus::Working)
|
||||
|| !endpointEntry.message.isEmpty())
|
||||
trackerEntryStatus.nextAnnounceTime = endpointStatus.nextAnnounceTime;
|
||||
trackerEntryStatus.minAnnounceTime = endpointStatus.minAnnounceTime;
|
||||
if ((endpointStatus.state != TrackerEndpointState::Working)
|
||||
|| !endpointStatus.message.isEmpty())
|
||||
{
|
||||
trackerEntry.message = endpointEntry.message;
|
||||
trackerEntryStatus.message = endpointStatus.message;
|
||||
}
|
||||
}
|
||||
|
||||
if (endpointEntry.status == TrackerEntryStatus::Working)
|
||||
if (endpointStatus.state == TrackerEndpointState::Working)
|
||||
{
|
||||
if (trackerEntry.message.isEmpty())
|
||||
trackerEntry.message = endpointEntry.message;
|
||||
if (trackerEntryStatus.message.isEmpty())
|
||||
trackerEntryStatus.message = endpointStatus.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,9 +352,9 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||
setStopCondition(params.stopCondition);
|
||||
|
||||
const auto *extensionData = static_cast<ExtensionData *>(m_ltAddTorrentParams.userdata);
|
||||
m_trackerEntries.reserve(static_cast<decltype(m_trackerEntries)::size_type>(extensionData->trackers.size()));
|
||||
m_trackerEntryStatuses.reserve(static_cast<decltype(m_trackerEntryStatuses)::size_type>(extensionData->trackers.size()));
|
||||
for (const lt::announce_entry &announceEntry : extensionData->trackers)
|
||||
m_trackerEntries.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
|
||||
m_trackerEntryStatuses.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
|
||||
m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(extensionData->urlSeeds.size()));
|
||||
for (const std::string &urlSeed : extensionData->urlSeeds)
|
||||
m_urlSeeds.append(QString::fromStdString(urlSeed));
|
||||
@@ -455,6 +460,8 @@ Path TorrentImpl::savePath() const
|
||||
void TorrentImpl::setSavePath(const Path &path)
|
||||
{
|
||||
Q_ASSERT(!isAutoTMMEnabled());
|
||||
if (isAutoTMMEnabled()) [[unlikely]]
|
||||
return;
|
||||
|
||||
const Path basePath = m_session->useCategoryPathsInManualMode()
|
||||
? m_session->categorySavePath(category()) : m_session->savePath();
|
||||
@@ -482,6 +489,8 @@ Path TorrentImpl::downloadPath() const
|
||||
void TorrentImpl::setDownloadPath(const Path &path)
|
||||
{
|
||||
Q_ASSERT(!isAutoTMMEnabled());
|
||||
if (isAutoTMMEnabled()) [[unlikely]]
|
||||
return;
|
||||
|
||||
const Path basePath = m_session->useCategoryPathsInManualMode()
|
||||
? m_session->categoryDownloadPath(category()) : m_session->downloadPath();
|
||||
@@ -601,27 +610,32 @@ Path TorrentImpl::makeUserPath(const Path &path) const
|
||||
return userPath;
|
||||
}
|
||||
|
||||
QVector<TrackerEntry> TorrentImpl::trackers() const
|
||||
QVector<TrackerEntryStatus> TorrentImpl::trackers() const
|
||||
{
|
||||
return m_trackerEntries;
|
||||
return m_trackerEntryStatuses;
|
||||
}
|
||||
|
||||
void TorrentImpl::addTrackers(QVector<TrackerEntry> trackers)
|
||||
{
|
||||
trackers.removeIf([](const TrackerEntry &entry) { return entry.url.isEmpty(); });
|
||||
trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
|
||||
|
||||
const auto newTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend())
|
||||
- QSet<TrackerEntry>(m_trackerEntries.cbegin(), m_trackerEntries.cend());
|
||||
if (newTrackers.isEmpty())
|
||||
QSet<TrackerEntry> currentTrackerSet;
|
||||
currentTrackerSet.reserve(m_trackerEntryStatuses.size());
|
||||
for (const TrackerEntryStatus &status : asConst(m_trackerEntryStatuses))
|
||||
currentTrackerSet.insert({.url = status.url, .tier = status.tier});
|
||||
|
||||
const auto newTrackerSet = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend()) - currentTrackerSet;
|
||||
if (newTrackerSet.isEmpty())
|
||||
return;
|
||||
|
||||
trackers = QVector<TrackerEntry>(newTrackers.cbegin(), newTrackers.cend());
|
||||
for (const TrackerEntry &tracker : trackers)
|
||||
trackers = QVector<TrackerEntry>(newTrackerSet.cbegin(), newTrackerSet.cend());
|
||||
for (const TrackerEntry &tracker : asConst(trackers))
|
||||
{
|
||||
m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier));
|
||||
|
||||
m_trackerEntries.append(trackers);
|
||||
std::sort(m_trackerEntries.begin(), m_trackerEntries.end()
|
||||
, [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; });
|
||||
m_trackerEntryStatuses.append({tracker.url, tracker.tier});
|
||||
}
|
||||
std::sort(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
|
||||
, [](const TrackerEntryStatus &left, const TrackerEntryStatus &right) { return left.tier < right.tier; });
|
||||
|
||||
deferredRequestResumeData();
|
||||
m_session->handleTorrentTrackersAdded(this, trackers);
|
||||
@@ -632,13 +646,13 @@ void TorrentImpl::removeTrackers(const QStringList &trackers)
|
||||
QStringList removedTrackers = trackers;
|
||||
for (const QString &tracker : trackers)
|
||||
{
|
||||
if (!m_trackerEntries.removeOne({tracker}))
|
||||
if (!m_trackerEntryStatuses.removeOne({tracker}))
|
||||
removedTrackers.removeOne(tracker);
|
||||
}
|
||||
|
||||
std::vector<lt::announce_entry> nativeTrackers;
|
||||
nativeTrackers.reserve(m_trackerEntries.size());
|
||||
for (const TrackerEntry &tracker : asConst(m_trackerEntries))
|
||||
nativeTrackers.reserve(m_trackerEntryStatuses.size());
|
||||
for (const TrackerEntryStatus &tracker : asConst(m_trackerEntryStatuses))
|
||||
nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
|
||||
|
||||
if (!removedTrackers.isEmpty())
|
||||
@@ -652,20 +666,25 @@ void TorrentImpl::removeTrackers(const QStringList &trackers)
|
||||
|
||||
void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
|
||||
{
|
||||
trackers.removeIf([](const TrackerEntry &entry) { return entry.url.isEmpty(); });
|
||||
trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
|
||||
|
||||
// Filter out duplicate trackers
|
||||
const auto uniqueTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend());
|
||||
trackers = QVector<TrackerEntry>(uniqueTrackers.cbegin(), uniqueTrackers.cend());
|
||||
std::sort(trackers.begin(), trackers.end()
|
||||
, [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; });
|
||||
, [](const TrackerEntry &left, const TrackerEntry &right) { return left.tier < right.tier; });
|
||||
|
||||
std::vector<lt::announce_entry> nativeTrackers;
|
||||
nativeTrackers.reserve(trackers.size());
|
||||
m_trackerEntryStatuses.clear();
|
||||
|
||||
for (const TrackerEntry &tracker : trackers)
|
||||
{
|
||||
nativeTrackers.emplace_back(makeNativeAnnounceEntry(tracker.url, tracker.tier));
|
||||
m_trackerEntryStatuses.append({tracker.url, tracker.tier});
|
||||
}
|
||||
|
||||
m_nativeHandle.replace_trackers(nativeTrackers);
|
||||
m_trackerEntries = trackers;
|
||||
|
||||
// Clear the peer list if it's a private torrent since
|
||||
// we do not want to keep connecting with peers from old tracker.
|
||||
@@ -971,6 +990,21 @@ PathList TorrentImpl::filePaths() const
|
||||
return m_filePaths;
|
||||
}
|
||||
|
||||
PathList TorrentImpl::actualFilePaths() const
|
||||
{
|
||||
if (!hasMetadata())
|
||||
return {};
|
||||
|
||||
PathList paths;
|
||||
paths.reserve(filesCount());
|
||||
|
||||
const lt::file_storage files = nativeTorrentInfo()->files();
|
||||
for (const lt::file_index_t &nativeIndex : asConst(m_torrentInfo.nativeIndexes()))
|
||||
paths.emplaceBack(files.file_path(nativeIndex));
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
QVector<DownloadPriority> TorrentImpl::filePriorities() const
|
||||
{
|
||||
return m_filePriorities;
|
||||
@@ -981,15 +1015,18 @@ TorrentInfo TorrentImpl::info() const
|
||||
return m_torrentInfo;
|
||||
}
|
||||
|
||||
bool TorrentImpl::isPaused() const
|
||||
bool TorrentImpl::isStopped() const
|
||||
{
|
||||
return m_isStopped;
|
||||
}
|
||||
|
||||
bool TorrentImpl::isQueued() const
|
||||
{
|
||||
// Torrent is Queued if it isn't in Paused state but paused internally
|
||||
return (!isPaused()
|
||||
if (!m_session->isQueueingSystemEnabled())
|
||||
return false;
|
||||
|
||||
// Torrent is Queued if it isn't in Stopped state but paused internally
|
||||
return (!isStopped()
|
||||
&& (m_nativeStatus.flags & lt::torrent_flags::auto_managed)
|
||||
&& (m_nativeStatus.flags & lt::torrent_flags::paused));
|
||||
}
|
||||
@@ -1009,7 +1046,7 @@ bool TorrentImpl::isDownloading() const
|
||||
case TorrentState::ForcedDownloadingMetadata:
|
||||
case TorrentState::StalledDownloading:
|
||||
case TorrentState::CheckingDownloading:
|
||||
case TorrentState::PausedDownloading:
|
||||
case TorrentState::StoppedDownloading:
|
||||
case TorrentState::QueuedDownloading:
|
||||
case TorrentState::ForcedDownloading:
|
||||
return true;
|
||||
@@ -1049,7 +1086,7 @@ bool TorrentImpl::isCompleted() const
|
||||
case TorrentState::Uploading:
|
||||
case TorrentState::StalledUploading:
|
||||
case TorrentState::CheckingUploading:
|
||||
case TorrentState::PausedUploading:
|
||||
case TorrentState::StoppedUploading:
|
||||
case TorrentState::QueuedUploading:
|
||||
case TorrentState::ForcedUploading:
|
||||
return true;
|
||||
@@ -1102,7 +1139,7 @@ bool TorrentImpl::isFinished() const
|
||||
|
||||
bool TorrentImpl::isForced() const
|
||||
{
|
||||
return (!isPaused() && (m_operatingMode == TorrentOperatingMode::Forced));
|
||||
return (!isStopped() && (m_operatingMode == TorrentOperatingMode::Forced));
|
||||
}
|
||||
|
||||
bool TorrentImpl::isSequentialDownload() const
|
||||
@@ -1140,23 +1177,23 @@ void TorrentImpl::updateState()
|
||||
}
|
||||
else if (!hasMetadata())
|
||||
{
|
||||
if (isPaused())
|
||||
m_state = TorrentState::PausedDownloading;
|
||||
else if (m_session->isQueueingSystemEnabled() && isQueued())
|
||||
if (isStopped())
|
||||
m_state = TorrentState::StoppedDownloading;
|
||||
else if (isQueued())
|
||||
m_state = TorrentState::QueuedDownloading;
|
||||
else
|
||||
m_state = isForced() ? TorrentState::ForcedDownloadingMetadata : TorrentState::DownloadingMetadata;
|
||||
}
|
||||
else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isPaused())
|
||||
else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isStopped())
|
||||
{
|
||||
// If the torrent is not just in the "checking" state, but is being actually checked
|
||||
m_state = m_hasFinishedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
|
||||
}
|
||||
else if (isFinished())
|
||||
{
|
||||
if (isPaused())
|
||||
m_state = TorrentState::PausedUploading;
|
||||
else if (m_session->isQueueingSystemEnabled() && isQueued())
|
||||
if (isStopped())
|
||||
m_state = TorrentState::StoppedUploading;
|
||||
else if (isQueued())
|
||||
m_state = TorrentState::QueuedUploading;
|
||||
else if (isForced())
|
||||
m_state = TorrentState::ForcedUploading;
|
||||
@@ -1167,9 +1204,9 @@ void TorrentImpl::updateState()
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isPaused())
|
||||
m_state = TorrentState::PausedDownloading;
|
||||
else if (m_session->isQueueingSystemEnabled() && isQueued())
|
||||
if (isStopped())
|
||||
m_state = TorrentState::StoppedDownloading;
|
||||
else if (isQueued())
|
||||
m_state = TorrentState::QueuedDownloading;
|
||||
else if (isForced())
|
||||
m_state = TorrentState::ForcedDownloading;
|
||||
@@ -1236,7 +1273,7 @@ qlonglong TorrentImpl::finishedTime() const
|
||||
|
||||
qlonglong TorrentImpl::eta() const
|
||||
{
|
||||
if (isPaused()) return MAX_ETA;
|
||||
if (isStopped()) return MAX_ETA;
|
||||
|
||||
const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
|
||||
|
||||
@@ -1433,11 +1470,13 @@ QBitArray TorrentImpl::pieces() const
|
||||
|
||||
QBitArray TorrentImpl::downloadingPieces() const
|
||||
{
|
||||
QBitArray result(piecesCount());
|
||||
if (!hasMetadata())
|
||||
return {};
|
||||
|
||||
std::vector<lt::partial_piece_info> queue;
|
||||
m_nativeHandle.get_download_queue(queue);
|
||||
|
||||
QBitArray result {piecesCount()};
|
||||
for (const lt::partial_piece_info &info : queue)
|
||||
result.setBit(LT::toUnderlyingType(info.piece_index));
|
||||
|
||||
@@ -1499,14 +1538,14 @@ qreal TorrentImpl::realRatio() const
|
||||
|
||||
int TorrentImpl::uploadPayloadRate() const
|
||||
{
|
||||
// workaround: suppress the speed for paused state
|
||||
return isPaused() ? 0 : m_nativeStatus.upload_payload_rate;
|
||||
// workaround: suppress the speed for Stopped state
|
||||
return isStopped() ? 0 : m_nativeStatus.upload_payload_rate;
|
||||
}
|
||||
|
||||
int TorrentImpl::downloadPayloadRate() const
|
||||
{
|
||||
// workaround: suppress the speed for paused state
|
||||
return isPaused() ? 0 : m_nativeStatus.download_payload_rate;
|
||||
// workaround: suppress the speed for Stopped state
|
||||
return isStopped() ? 0 : m_nativeStatus.download_payload_rate;
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::totalPayloadUpload() const
|
||||
@@ -1534,6 +1573,15 @@ qlonglong TorrentImpl::nextAnnounce() const
|
||||
return lt::total_seconds(m_nativeStatus.next_announce);
|
||||
}
|
||||
|
||||
qreal TorrentImpl::popularity() const
|
||||
{
|
||||
// in order to produce floating-point numbers using `std::chrono::duration_cast`,
|
||||
// we should use `qreal` as `Rep` to define the `months` duration
|
||||
using months = std::chrono::duration<qreal, std::chrono::months::period>;
|
||||
const auto activeMonths = std::chrono::duration_cast<months>(m_nativeStatus.active_duration).count();
|
||||
return (activeMonths > 0) ? (realRatio() / activeMonths) : 0;
|
||||
}
|
||||
|
||||
void TorrentImpl::setName(const QString &name)
|
||||
{
|
||||
if (m_name != name)
|
||||
@@ -1588,7 +1636,17 @@ void TorrentImpl::forceRecheck()
|
||||
// an incorrect one during the interval until the cached state is updated in a regular way.
|
||||
m_nativeStatus.state = lt::torrent_status::checking_resume_data;
|
||||
|
||||
m_hasMissingFiles = false;
|
||||
if (m_hasMissingFiles)
|
||||
{
|
||||
m_hasMissingFiles = false;
|
||||
if (!isStopped())
|
||||
{
|
||||
setAutoManaged(m_operatingMode == TorrentOperatingMode::AutoManaged);
|
||||
if (m_operatingMode == TorrentOperatingMode::Forced)
|
||||
m_nativeHandle.resume();
|
||||
}
|
||||
}
|
||||
|
||||
m_unchecked = false;
|
||||
|
||||
m_completedFiles.fill(false);
|
||||
@@ -1597,10 +1655,10 @@ void TorrentImpl::forceRecheck()
|
||||
m_nativeStatus.pieces.clear_all();
|
||||
m_nativeStatus.num_pieces = 0;
|
||||
|
||||
if (isPaused())
|
||||
if (isStopped())
|
||||
{
|
||||
// When "force recheck" is applied on paused torrent, we temporarily resume it
|
||||
resume();
|
||||
// When "force recheck" is applied on Stopped torrent, we start them to perform checking
|
||||
start();
|
||||
m_stopCondition = StopCondition::FilesChecked;
|
||||
}
|
||||
}
|
||||
@@ -1679,16 +1737,16 @@ void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileN
|
||||
endReceivedMetadataHandling(savePath, fileNames);
|
||||
}
|
||||
|
||||
TrackerEntry TorrentImpl::updateTrackerEntry(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
|
||||
TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
|
||||
{
|
||||
const auto it = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end()
|
||||
, [&announceEntry](const TrackerEntry &trackerEntry)
|
||||
const auto it = std::find_if(m_trackerEntryStatuses.begin(), m_trackerEntryStatuses.end()
|
||||
, [&announceEntry](const TrackerEntryStatus &trackerEntryStatus)
|
||||
{
|
||||
return (trackerEntry.url == QString::fromStdString(announceEntry.url));
|
||||
return (trackerEntryStatus.url == QString::fromStdString(announceEntry.url));
|
||||
});
|
||||
|
||||
Q_ASSERT(it != m_trackerEntries.end());
|
||||
if (it == m_trackerEntries.end()) [[unlikely]]
|
||||
Q_ASSERT(it != m_trackerEntryStatuses.end());
|
||||
if (it == m_trackerEntryStatuses.end()) [[unlikely]]
|
||||
return {};
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
@@ -1701,14 +1759,21 @@ TrackerEntry TorrentImpl::updateTrackerEntry(const lt::announce_entry &announceE
|
||||
#else
|
||||
const QSet<int> btProtocols {1};
|
||||
#endif
|
||||
::updateTrackerEntry(*it, announceEntry, btProtocols, updateInfo);
|
||||
::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo);
|
||||
return *it;
|
||||
}
|
||||
|
||||
void TorrentImpl::resetTrackerEntries()
|
||||
void TorrentImpl::resetTrackerEntryStatuses()
|
||||
{
|
||||
for (auto &trackerEntry : m_trackerEntries)
|
||||
trackerEntry = {trackerEntry.url, trackerEntry.tier};
|
||||
for (TrackerEntryStatus &status : m_trackerEntryStatuses)
|
||||
{
|
||||
const QString tempUrl = status.url;
|
||||
const int tempTier = status.tier;
|
||||
|
||||
status.clear();
|
||||
status.url = tempUrl;
|
||||
status.tier = tempTier;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
|
||||
@@ -1751,12 +1816,13 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
|
||||
const Path filePath = actualFilePath.removedExtension(QB_EXT);
|
||||
m_filePaths.append(filePath);
|
||||
|
||||
lt::download_priority_t &nativePriority = p.file_priorities[LT::toUnderlyingType(nativeIndex)];
|
||||
if ((nativePriority != lt::dont_download) && m_session->isFilenameExcluded(filePath.filename()))
|
||||
nativePriority = lt::dont_download;
|
||||
const auto priority = LT::fromNative(nativePriority);
|
||||
m_filePriorities.append(priority);
|
||||
m_filePriorities.append(LT::fromNative(p.file_priorities[LT::toUnderlyingType(nativeIndex)]));
|
||||
}
|
||||
|
||||
m_session->applyFilenameFilter(fileNames, m_filePriorities);
|
||||
for (int i = 0; i < m_filePriorities.size(); ++i)
|
||||
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(m_filePriorities[i]);
|
||||
|
||||
p.save_path = savePath.toString().toStdString();
|
||||
p.ti = metadata;
|
||||
|
||||
@@ -1768,7 +1834,7 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
|
||||
p.flags |= lt::torrent_flags::paused;
|
||||
p.flags &= ~lt::torrent_flags::auto_managed;
|
||||
|
||||
m_session->handleTorrentPaused(this);
|
||||
m_session->handleTorrentStopped(this);
|
||||
}
|
||||
|
||||
reload();
|
||||
@@ -1819,6 +1885,9 @@ void TorrentImpl::reload()
|
||||
|
||||
auto *const extensionData = new ExtensionData;
|
||||
p.userdata = LTClientData(extensionData);
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
p.storage = customStorageConstructor;
|
||||
#endif
|
||||
m_nativeHandle = m_nativeSession->add_torrent(p);
|
||||
|
||||
m_nativeStatus = extensionData->status;
|
||||
@@ -1836,14 +1905,14 @@ void TorrentImpl::reload()
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::pause()
|
||||
void TorrentImpl::stop()
|
||||
{
|
||||
if (!m_isStopped)
|
||||
{
|
||||
m_stopCondition = StopCondition::None;
|
||||
m_isStopped = true;
|
||||
deferredRequestResumeData();
|
||||
m_session->handleTorrentPaused(this);
|
||||
m_session->handleTorrentStopped(this);
|
||||
}
|
||||
|
||||
if (m_maintenanceJob == MaintenanceJob::None)
|
||||
@@ -1855,7 +1924,7 @@ void TorrentImpl::pause()
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::resume(const TorrentOperatingMode mode)
|
||||
void TorrentImpl::start(const TorrentOperatingMode mode)
|
||||
{
|
||||
if (hasError())
|
||||
{
|
||||
@@ -1878,7 +1947,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
|
||||
{
|
||||
m_isStopped = false;
|
||||
deferredRequestResumeData();
|
||||
m_session->handleTorrentResumed(this);
|
||||
m_session->handleTorrentStarted(this);
|
||||
}
|
||||
|
||||
if (m_maintenanceJob == MaintenanceJob::None)
|
||||
@@ -1893,8 +1962,17 @@ void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext cont
|
||||
{
|
||||
if (!hasMetadata())
|
||||
{
|
||||
m_savePath = newPath;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
if (context == MoveStorageContext::ChangeSavePath)
|
||||
{
|
||||
m_savePath = newPath;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
}
|
||||
else if (context == MoveStorageContext::ChangeDownloadPath)
|
||||
{
|
||||
m_downloadPath = newPath;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1926,6 +2004,11 @@ void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
|
||||
updateStatus(nativeStatus);
|
||||
}
|
||||
|
||||
void TorrentImpl::handleQueueingModeChanged()
|
||||
{
|
||||
updateState();
|
||||
}
|
||||
|
||||
void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStorageContext context, const bool hasOutstandingJob)
|
||||
{
|
||||
if (context == MoveStorageContext::ChangeSavePath)
|
||||
@@ -1967,7 +2050,7 @@ void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused]] const lt::torrent_c
|
||||
}
|
||||
|
||||
if (stopCondition() == StopCondition::FilesChecked)
|
||||
pause();
|
||||
stop();
|
||||
|
||||
m_statusUpdatedTriggers.enqueue([this]()
|
||||
{
|
||||
@@ -1983,7 +2066,7 @@ void TorrentImpl::handleTorrentCheckedAlert([[maybe_unused]] const lt::torrent_c
|
||||
adjustStorageLocation();
|
||||
manageActualFilePaths();
|
||||
|
||||
if (!isPaused())
|
||||
if (!isStopped())
|
||||
{
|
||||
// torrent is internally paused using NativeTorrentExtension after files checked
|
||||
// so we need to resume it if there is no corresponding "stop condition" set
|
||||
@@ -2468,7 +2551,7 @@ void TorrentImpl::setStopCondition(const StopCondition stopCondition)
|
||||
if (stopCondition == m_stopCondition)
|
||||
return;
|
||||
|
||||
if (isPaused())
|
||||
if (isStopped())
|
||||
return;
|
||||
|
||||
if ((stopCondition == StopCondition::MetadataReceived) && hasMetadata())
|
||||
@@ -2738,7 +2821,7 @@ QString TorrentImpl::createMagnetURI() const
|
||||
ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
|
||||
}
|
||||
|
||||
for (const TrackerEntry &tracker : asConst(trackers()))
|
||||
for (const TrackerEntryStatus &tracker : asConst(trackers()))
|
||||
{
|
||||
ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
|
||||
}
|
||||
@@ -2766,8 +2849,8 @@ nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
|
||||
#endif
|
||||
lt::create_torrent creator {*torrentInfo};
|
||||
|
||||
for (const TrackerEntry &entry : asConst(trackers()))
|
||||
creator.add_tracker(entry.url.toStdString(), entry.tier);
|
||||
for (const TrackerEntryStatus &status : asConst(trackers()))
|
||||
creator.add_tracker(status.url.toStdString(), status.tier);
|
||||
|
||||
return creator.generate();
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
#include "torrent.h"
|
||||
#include "torrentcontentlayout.h"
|
||||
#include "torrentinfo.h"
|
||||
#include "trackerentry.h"
|
||||
#include "trackerentrystatus.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
@@ -153,11 +153,12 @@ namespace BitTorrent
|
||||
Path actualFilePath(int index) const override;
|
||||
qlonglong fileSize(int index) const override;
|
||||
PathList filePaths() const override;
|
||||
PathList actualFilePaths() const override;
|
||||
QVector<DownloadPriority> filePriorities() const override;
|
||||
|
||||
TorrentInfo info() const override;
|
||||
bool isFinished() const override;
|
||||
bool isPaused() const override;
|
||||
bool isStopped() const override;
|
||||
bool isQueued() const override;
|
||||
bool isForced() const override;
|
||||
bool isChecking() const override;
|
||||
@@ -175,7 +176,7 @@ namespace BitTorrent
|
||||
bool hasMissingFiles() const override;
|
||||
bool hasError() const override;
|
||||
int queuePosition() const override;
|
||||
QVector<TrackerEntry> trackers() const override;
|
||||
QVector<TrackerEntryStatus> trackers() const override;
|
||||
QVector<QUrl> urlSeeds() const override;
|
||||
QString error() const override;
|
||||
qlonglong totalDownload() const override;
|
||||
@@ -210,6 +211,7 @@ namespace BitTorrent
|
||||
int maxSeedingTime() const override;
|
||||
int maxInactiveSeedingTime() const override;
|
||||
qreal realRatio() const override;
|
||||
qreal popularity() const override;
|
||||
int uploadPayloadRate() const override;
|
||||
int downloadPayloadRate() const override;
|
||||
qlonglong totalPayloadUpload() const override;
|
||||
@@ -222,8 +224,8 @@ namespace BitTorrent
|
||||
void setName(const QString &name) override;
|
||||
void setSequentialDownload(bool enable) override;
|
||||
void setFirstLastPiecePriority(bool enabled) override;
|
||||
void pause() override;
|
||||
void resume(TorrentOperatingMode mode = TorrentOperatingMode::AutoManaged) override;
|
||||
void stop() override;
|
||||
void start(TorrentOperatingMode mode = TorrentOperatingMode::AutoManaged) override;
|
||||
void forceReannounce(int index = -1) override;
|
||||
void forceDHTAnnounce() override;
|
||||
void forceRecheck() override;
|
||||
@@ -268,6 +270,7 @@ namespace BitTorrent
|
||||
|
||||
void handleAlert(const lt::alert *a);
|
||||
void handleStateUpdate(const lt::torrent_status &nativeStatus);
|
||||
void handleQueueingModeChanged();
|
||||
void handleCategoryOptionsChanged();
|
||||
void handleAppendExtensionToggled();
|
||||
void handleUnwantedFolderToggled();
|
||||
@@ -275,8 +278,8 @@ namespace BitTorrent
|
||||
void deferredRequestResumeData();
|
||||
void handleMoveStorageJobFinished(const Path &path, MoveStorageContext context, bool hasOutstandingJob);
|
||||
void fileSearchFinished(const Path &savePath, const PathList &fileNames);
|
||||
TrackerEntry updateTrackerEntry(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo);
|
||||
void resetTrackerEntries();
|
||||
TrackerEntryStatus updateTrackerEntryStatus(const lt::announce_entry &announceEntry, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo);
|
||||
void resetTrackerEntryStatuses();
|
||||
|
||||
private:
|
||||
using EventTrigger = std::function<void ()>;
|
||||
@@ -349,7 +352,7 @@ namespace BitTorrent
|
||||
|
||||
MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
|
||||
|
||||
QVector<TrackerEntry> m_trackerEntries;
|
||||
QVector<TrackerEntryStatus> m_trackerEntryStatuses;
|
||||
QVector<QUrl> m_urlSeeds;
|
||||
FileErrorInfo m_lastFileError;
|
||||
|
||||
|
||||
@@ -279,7 +279,7 @@ QVector<TrackerEntry> TorrentInfo::trackers() const
|
||||
QVector<TrackerEntry> ret;
|
||||
ret.reserve(static_cast<decltype(ret)::size_type>(trackers.size()));
|
||||
for (const lt::announce_entry &tracker : trackers)
|
||||
ret.append({QString::fromStdString(tracker.url), tracker.tier});
|
||||
ret.append({.url = QString::fromStdString(tracker.url), .tier = tracker.tier});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -28,7 +29,9 @@
|
||||
|
||||
#include "trackerentry.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QStringView>
|
||||
|
||||
QList<BitTorrent::TrackerEntry> BitTorrent::parseTrackerEntries(const QStringView str)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -29,56 +30,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QStringView>
|
||||
|
||||
class QStringView;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class TrackerEntryStatus
|
||||
{
|
||||
NotContacted = 1,
|
||||
Working = 2,
|
||||
Updating = 3,
|
||||
NotWorking = 4,
|
||||
TrackerError = 5,
|
||||
Unreachable = 6
|
||||
};
|
||||
struct TrackerEndpointEntry
|
||||
{
|
||||
QString name {};
|
||||
int btVersion = 1;
|
||||
|
||||
TrackerEntryStatus status = TrackerEntryStatus::NotContacted;
|
||||
QString message {};
|
||||
|
||||
int numPeers = -1;
|
||||
int numSeeds = -1;
|
||||
int numLeeches = -1;
|
||||
int numDownloaded = -1;
|
||||
|
||||
QDateTime nextAnnounceTime {};
|
||||
QDateTime minAnnounceTime {};
|
||||
};
|
||||
|
||||
struct TrackerEntry
|
||||
{
|
||||
QString url {};
|
||||
int tier = 0;
|
||||
|
||||
TrackerEntryStatus status = TrackerEntryStatus::NotContacted;
|
||||
QString message {};
|
||||
|
||||
int numPeers = -1;
|
||||
int numSeeds = -1;
|
||||
int numLeeches = -1;
|
||||
int numDownloaded = -1;
|
||||
|
||||
QDateTime nextAnnounceTime {};
|
||||
QDateTime minAnnounceTime {};
|
||||
|
||||
QHash<std::pair<QString, int>, TrackerEndpointEntry> endpointEntries {};
|
||||
};
|
||||
|
||||
QList<TrackerEntry> parseTrackerEntries(QStringView str);
|
||||
|
||||
54
src/base/bittorrent/trackerentrystatus.cpp
Normal file
54
src/base/bittorrent/trackerentrystatus.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "trackerentrystatus.h"
|
||||
|
||||
void BitTorrent::TrackerEntryStatus::clear()
|
||||
{
|
||||
url.clear();
|
||||
tier = 0;
|
||||
state = TrackerEndpointState::NotContacted;
|
||||
message.clear();
|
||||
numPeers = -1;
|
||||
numSeeds = -1;
|
||||
numLeeches = -1;
|
||||
numDownloaded = -1;
|
||||
nextAnnounceTime = {};
|
||||
minAnnounceTime = {};
|
||||
endpoints.clear();
|
||||
}
|
||||
|
||||
bool BitTorrent::operator==(const TrackerEntryStatus &left, const TrackerEntryStatus &right)
|
||||
{
|
||||
return (left.url == right.url);
|
||||
}
|
||||
|
||||
std::size_t BitTorrent::qHash(const TrackerEntryStatus &key, const std::size_t seed)
|
||||
{
|
||||
return ::qHash(key.url, seed);
|
||||
}
|
||||
89
src/base/bittorrent/trackerentrystatus.h
Normal file
89
src/base/bittorrent/trackerentrystatus.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
class QStringView;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class TrackerEndpointState
|
||||
{
|
||||
NotContacted = 1,
|
||||
Working = 2,
|
||||
Updating = 3,
|
||||
NotWorking = 4,
|
||||
TrackerError = 5,
|
||||
Unreachable = 6
|
||||
};
|
||||
|
||||
struct TrackerEndpointStatus
|
||||
{
|
||||
QString name {};
|
||||
int btVersion = 1;
|
||||
|
||||
TrackerEndpointState state = TrackerEndpointState::NotContacted;
|
||||
QString message {};
|
||||
|
||||
int numPeers = -1;
|
||||
int numSeeds = -1;
|
||||
int numLeeches = -1;
|
||||
int numDownloaded = -1;
|
||||
|
||||
QDateTime nextAnnounceTime {};
|
||||
QDateTime minAnnounceTime {};
|
||||
};
|
||||
|
||||
struct TrackerEntryStatus
|
||||
{
|
||||
QString url {};
|
||||
int tier = 0;
|
||||
|
||||
TrackerEndpointState state = TrackerEndpointState::NotContacted;
|
||||
QString message {};
|
||||
|
||||
int numPeers = -1;
|
||||
int numSeeds = -1;
|
||||
int numLeeches = -1;
|
||||
int numDownloaded = -1;
|
||||
|
||||
QDateTime nextAnnounceTime {};
|
||||
QDateTime minAnnounceTime {};
|
||||
|
||||
QHash<std::pair<QString, int>, TrackerEndpointStatus> endpoints {};
|
||||
|
||||
void clear();
|
||||
};
|
||||
|
||||
bool operator==(const TrackerEntryStatus &left, const TrackerEntryStatus &right);
|
||||
std::size_t qHash(const TrackerEntryStatus &key, std::size_t seed = 0);
|
||||
}
|
||||
@@ -44,6 +44,7 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
||||
, m_requestHandler(requestHandler)
|
||||
{
|
||||
m_socket->setParent(this);
|
||||
connect(m_socket, &QAbstractSocket::disconnected, this, &Connection::closed);
|
||||
|
||||
// reserve common size for requests, don't use the max allowed size which is too big for
|
||||
// memory constrained platforms
|
||||
@@ -62,11 +63,6 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
||||
});
|
||||
}
|
||||
|
||||
Connection::~Connection()
|
||||
{
|
||||
m_socket->close();
|
||||
}
|
||||
|
||||
void Connection::read()
|
||||
{
|
||||
// reuse existing buffer and avoid unnecessary memory allocation/relocation
|
||||
@@ -182,11 +178,6 @@ bool Connection::hasExpired(const qint64 timeout) const
|
||||
&& m_idleTimer.hasExpired(timeout);
|
||||
}
|
||||
|
||||
bool Connection::isClosed() const
|
||||
{
|
||||
return (m_socket->state() == QAbstractSocket::UnconnectedState);
|
||||
}
|
||||
|
||||
bool Connection::acceptsGzipEncoding(QString codings)
|
||||
{
|
||||
// [rfc7231] 5.3.4. Accept-Encoding
|
||||
|
||||
@@ -47,10 +47,11 @@ namespace Http
|
||||
|
||||
public:
|
||||
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = nullptr);
|
||||
~Connection();
|
||||
|
||||
bool hasExpired(qint64 timeout) const;
|
||||
bool isClosed() const;
|
||||
|
||||
signals:
|
||||
void closed();
|
||||
|
||||
private:
|
||||
static bool acceptsGzipEncoding(QString codings);
|
||||
|
||||
@@ -32,7 +32,10 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
|
||||
#include <QtLogging>
|
||||
#include <QNetworkProxy>
|
||||
#include <QSslCipher>
|
||||
#include <QSslConfiguration>
|
||||
@@ -40,7 +43,6 @@
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/global.h"
|
||||
#include "base/utils/net.h"
|
||||
#include "base/utils/sslkey.h"
|
||||
@@ -113,32 +115,38 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
||||
|
||||
void Server::incomingConnection(const qintptr socketDescriptor)
|
||||
{
|
||||
if (m_connections.size() >= CONNECTIONS_LIMIT) return;
|
||||
|
||||
QTcpSocket *serverSocket = nullptr;
|
||||
if (m_https)
|
||||
serverSocket = new QSslSocket(this);
|
||||
else
|
||||
serverSocket = new QTcpSocket(this);
|
||||
|
||||
std::unique_ptr<QTcpSocket> serverSocket = m_https ? std::make_unique<QSslSocket>(this) : std::make_unique<QTcpSocket>(this);
|
||||
if (!serverSocket->setSocketDescriptor(socketDescriptor))
|
||||
return;
|
||||
|
||||
if (m_connections.size() >= CONNECTIONS_LIMIT)
|
||||
{
|
||||
delete serverSocket;
|
||||
qWarning("Too many connections. Exceeded CONNECTIONS_LIMIT (%d). Connection closed.", CONNECTIONS_LIMIT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_https)
|
||||
try
|
||||
{
|
||||
static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols);
|
||||
static_cast<QSslSocket *>(serverSocket)->setPrivateKey(m_key);
|
||||
static_cast<QSslSocket *>(serverSocket)->setLocalCertificateChain(m_certificates);
|
||||
static_cast<QSslSocket *>(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
static_cast<QSslSocket *>(serverSocket)->startServerEncryption();
|
||||
}
|
||||
if (m_https)
|
||||
{
|
||||
auto *sslSocket = static_cast<QSslSocket *>(serverSocket.get());
|
||||
sslSocket->setProtocol(QSsl::SecureProtocols);
|
||||
sslSocket->setPrivateKey(m_key);
|
||||
sslSocket->setLocalCertificateChain(m_certificates);
|
||||
sslSocket->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
sslSocket->startServerEncryption();
|
||||
}
|
||||
|
||||
auto *c = new Connection(serverSocket, m_requestHandler, this);
|
||||
m_connections.insert(c);
|
||||
connect(serverSocket, &QAbstractSocket::disconnected, this, [c, this]() { removeConnection(c); });
|
||||
auto *connection = new Connection(serverSocket.release(), m_requestHandler, this);
|
||||
m_connections.insert(connection);
|
||||
connect(connection, &Connection::closed, this, [this, connection] { removeConnection(connection); });
|
||||
}
|
||||
catch (const std::bad_alloc &exception)
|
||||
{
|
||||
// drop the connection instead of throwing exception and crash
|
||||
qWarning("Failed to allocate memory for HTTP connection. Connection closed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Server::removeConnection(Connection *connection)
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QNetworkAccessManager>
|
||||
@@ -54,8 +55,27 @@ using namespace std::chrono_literals;
|
||||
|
||||
namespace
|
||||
{
|
||||
// Disguise as Firefox to avoid web server banning
|
||||
const char DEFAULT_USER_AGENT[] = "Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0";
|
||||
// Disguise as browser to circumvent website blocking
|
||||
QByteArray getBrowserUserAgent()
|
||||
{
|
||||
// Firefox release calendar
|
||||
// https://whattrainisitnow.com/calendar/
|
||||
// https://wiki.mozilla.org/index.php?title=Release_Management/Calendar&redirect=no
|
||||
|
||||
static QByteArray ret;
|
||||
if (ret.isEmpty())
|
||||
{
|
||||
const std::chrono::time_point baseDate = std::chrono::sys_days(2024y / 04 / 16);
|
||||
const int baseVersion = 125;
|
||||
|
||||
const std::chrono::time_point nowDate = std::chrono::system_clock::now();
|
||||
const int nowVersion = baseVersion + std::chrono::duration_cast<std::chrono::months>(nowDate - baseDate).count();
|
||||
|
||||
QByteArray userAgentTemplate = QByteArrayLiteral("Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:%1.0) Gecko/20100101 Firefox/%1.0");
|
||||
ret = userAgentTemplate.replace("%1", QByteArray::number(nowVersion));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
class Net::DownloadManager::NetworkCookieJar final : public QNetworkCookieJar
|
||||
@@ -292,7 +312,7 @@ void Net::DownloadManager::processRequest(DownloadHandlerImpl *downloadHandler)
|
||||
QNetworkRequest request {downloadRequest.url()};
|
||||
|
||||
if (downloadRequest.userAgent().isEmpty())
|
||||
request.setRawHeader("User-Agent", DEFAULT_USER_AGENT);
|
||||
request.setRawHeader("User-Agent", getBrowserUserAgent());
|
||||
else
|
||||
request.setRawHeader("User-Agent", downloadRequest.userAgent().toUtf8());
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ class QSslSocket;
|
||||
#else
|
||||
class QTcpSocket;
|
||||
#endif
|
||||
class QTextCodec;
|
||||
|
||||
namespace Net
|
||||
{
|
||||
|
||||
@@ -134,17 +134,17 @@ void Preferences::setCustomUIThemePath(const Path &path)
|
||||
setValue(u"Preferences/General/CustomUIThemePath"_s, path);
|
||||
}
|
||||
|
||||
bool Preferences::deleteTorrentFilesAsDefault() const
|
||||
bool Preferences::removeTorrentContent() const
|
||||
{
|
||||
return value(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setDeleteTorrentFilesAsDefault(const bool del)
|
||||
void Preferences::setRemoveTorrentContent(const bool remove)
|
||||
{
|
||||
if (del == deleteTorrentFilesAsDefault())
|
||||
if (remove == removeTorrentContent())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, del);
|
||||
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, remove);
|
||||
}
|
||||
|
||||
bool Preferences::confirmOnExit() const
|
||||
@@ -1397,19 +1397,6 @@ void Preferences::setConfirmRemoveAllTags(const bool enabled)
|
||||
setValue(u"Preferences/Advanced/confirmRemoveAllTags"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::confirmPauseAndResumeAll() const
|
||||
{
|
||||
return value(u"GUI/ConfirmActions/PauseAndResumeAllTorrents"_s, true);
|
||||
}
|
||||
|
||||
void Preferences::setConfirmPauseAndResumeAll(const bool enabled)
|
||||
{
|
||||
if (enabled == confirmPauseAndResumeAll())
|
||||
return;
|
||||
|
||||
setValue(u"GUI/ConfirmActions/PauseAndResumeAllTorrents"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::confirmMergeTrackers() const
|
||||
{
|
||||
return value(u"GUI/ConfirmActions/MergeTrackers"_s, true);
|
||||
|
||||
@@ -105,8 +105,8 @@ public:
|
||||
void setUseCustomUITheme(bool use);
|
||||
Path customUIThemePath() const;
|
||||
void setCustomUIThemePath(const Path &path);
|
||||
bool deleteTorrentFilesAsDefault() const;
|
||||
void setDeleteTorrentFilesAsDefault(bool del);
|
||||
bool removeTorrentContent() const;
|
||||
void setRemoveTorrentContent(bool remove);
|
||||
bool confirmOnExit() const;
|
||||
void setConfirmOnExit(bool confirm);
|
||||
bool speedInTitleBar() const;
|
||||
@@ -305,8 +305,6 @@ public:
|
||||
void setConfirmTorrentRecheck(bool enabled);
|
||||
bool confirmRemoveAllTags() const;
|
||||
void setConfirmRemoveAllTags(bool enabled);
|
||||
bool confirmPauseAndResumeAll() const;
|
||||
void setConfirmPauseAndResumeAll(bool enabled);
|
||||
bool confirmMergeTrackers() const;
|
||||
void setConfirmMergeTrackers(bool enabled);
|
||||
bool confirmRemoveTrackerFromAllTorrents() const;
|
||||
|
||||
@@ -468,7 +468,7 @@ QJsonObject AutoDownloadRule::toJsonObject() const
|
||||
|
||||
// TODO: The following code is deprecated. Replace with the commented one after several releases in 4.6.x.
|
||||
// === BEGIN DEPRECATED CODE === //
|
||||
, {S_ADD_PAUSED, toJsonValue(addTorrentParams.addPaused)}
|
||||
, {S_ADD_PAUSED, toJsonValue(addTorrentParams.addStopped)}
|
||||
, {S_CONTENT_LAYOUT, contentLayoutToJsonValue(addTorrentParams.contentLayout)}
|
||||
, {S_SAVE_PATH, addTorrentParams.savePath.toString()}
|
||||
, {S_ASSIGNED_CATEGORY, addTorrentParams.category}
|
||||
@@ -525,7 +525,7 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
|
||||
{
|
||||
addTorrentParams.savePath = Path(jsonObj.value(S_SAVE_PATH).toString());
|
||||
addTorrentParams.category = jsonObj.value(S_ASSIGNED_CATEGORY).toString();
|
||||
addTorrentParams.addPaused = toOptionalBool(jsonObj.value(S_ADD_PAUSED));
|
||||
addTorrentParams.addStopped = toOptionalBool(jsonObj.value(S_ADD_PAUSED));
|
||||
if (!addTorrentParams.savePath.isEmpty())
|
||||
addTorrentParams.useAutoTMM = false;
|
||||
|
||||
@@ -568,7 +568,7 @@ QVariantHash AutoDownloadRule::toLegacyDict() const
|
||||
{u"enabled"_s, isEnabled()},
|
||||
{u"category_assigned"_s, addTorrentParams.category},
|
||||
{u"use_regex"_s, useRegex()},
|
||||
{u"add_paused"_s, toAddPausedLegacy(addTorrentParams.addPaused)},
|
||||
{u"add_paused"_s, toAddPausedLegacy(addTorrentParams.addStopped)},
|
||||
{u"episode_filter"_s, episodeFilter()},
|
||||
{u"last_match"_s, lastMatch()},
|
||||
{u"ignore_days"_s, ignoreDays()}};
|
||||
@@ -579,7 +579,7 @@ AutoDownloadRule AutoDownloadRule::fromLegacyDict(const QVariantHash &dict)
|
||||
BitTorrent::AddTorrentParams addTorrentParams;
|
||||
addTorrentParams.savePath = Path(dict.value(u"save_path"_s).toString());
|
||||
addTorrentParams.category = dict.value(u"category_assigned"_s).toString();
|
||||
addTorrentParams.addPaused = addPausedLegacyToOptionalBool(dict.value(u"add_paused"_s).toInt());
|
||||
addTorrentParams.addStopped = addPausedLegacyToOptionalBool(dict.value(u"add_paused"_s).toInt());
|
||||
if (!addTorrentParams.savePath.isEmpty())
|
||||
addTorrentParams.useAutoTMM = false;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018-2024 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
|
||||
@@ -36,10 +36,10 @@
|
||||
#include "base/utils/fs.h"
|
||||
#include "searchpluginmanager.h"
|
||||
|
||||
SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QString &url, SearchPluginManager *manager)
|
||||
: QObject {manager}
|
||||
SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QString &url, SearchPluginManager *manager)
|
||||
: QObject(manager)
|
||||
, m_manager {manager}
|
||||
, m_downloadProcess {new QProcess {this}}
|
||||
, m_downloadProcess {new QProcess(this)}
|
||||
{
|
||||
m_downloadProcess->setEnvironment(QProcess::systemEnvironment());
|
||||
connect(m_downloadProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished)
|
||||
@@ -48,7 +48,7 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QStri
|
||||
{
|
||||
Utils::ForeignApps::PYTHON_ISOLATE_MODE_FLAG,
|
||||
(SearchPluginManager::engineLocation() / Path(u"nova2dl.py"_s)).toString(),
|
||||
siteUrl,
|
||||
pluginName,
|
||||
url
|
||||
};
|
||||
// Launch search
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018-2024 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
|
||||
@@ -41,7 +41,7 @@ class SearchDownloadHandler : public QObject
|
||||
|
||||
friend class SearchPluginManager;
|
||||
|
||||
SearchDownloadHandler(const QString &siteUrl, const QString &url, SearchPluginManager *manager);
|
||||
SearchDownloadHandler(const QString &pluginName, const QString &url, SearchPluginManager *manager);
|
||||
|
||||
signals:
|
||||
void downloadFinished(const QString &path);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -55,18 +55,19 @@ namespace
|
||||
PL_LEECHS,
|
||||
PL_ENGINE_URL,
|
||||
PL_DESC_LINK,
|
||||
PL_PUB_DATE,
|
||||
NB_PLUGIN_COLUMNS
|
||||
};
|
||||
}
|
||||
|
||||
SearchHandler::SearchHandler(const QString &pattern, const QString &category, const QStringList &usedPlugins, SearchPluginManager *manager)
|
||||
: QObject {manager}
|
||||
: QObject(manager)
|
||||
, m_pattern {pattern}
|
||||
, m_category {category}
|
||||
, m_usedPlugins {usedPlugins}
|
||||
, m_manager {manager}
|
||||
, m_searchProcess {new QProcess {this}}
|
||||
, m_searchTimeout {new QTimer {this}}
|
||||
, m_searchProcess {new QProcess(this)}
|
||||
, m_searchTimeout {new QTimer(this)}
|
||||
{
|
||||
// Load environment variables (proxy)
|
||||
m_searchProcess->setEnvironment(QProcess::systemEnvironment());
|
||||
@@ -176,7 +177,8 @@ bool SearchHandler::parseSearchResult(const QStringView line, SearchResult &sear
|
||||
const QList<QStringView> parts = line.split(u'|');
|
||||
const int nbFields = parts.size();
|
||||
|
||||
if (nbFields < (NB_PLUGIN_COLUMNS - 1)) return false; // -1 because desc_link is optional
|
||||
if (nbFields <= PL_ENGINE_URL)
|
||||
return false; // Anything after ENGINE_URL is optional
|
||||
|
||||
searchResult = SearchResult();
|
||||
searchResult.fileUrl = parts.at(PL_DL_LINK).trimmed().toString(); // download URL
|
||||
@@ -193,10 +195,19 @@ bool SearchHandler::parseSearchResult(const QStringView line, SearchResult &sear
|
||||
if (!ok || (searchResult.nbLeechers < 0))
|
||||
searchResult.nbLeechers = -1;
|
||||
|
||||
searchResult.siteUrl = parts.at(PL_ENGINE_URL).trimmed().toString(); // Search site URL
|
||||
if (nbFields == NB_PLUGIN_COLUMNS)
|
||||
searchResult.siteUrl = parts.at(PL_ENGINE_URL).trimmed().toString(); // Search engine site URL
|
||||
searchResult.engineName = m_manager->pluginNameBySiteURL(searchResult.siteUrl); // Search engine name
|
||||
|
||||
if (nbFields > PL_DESC_LINK)
|
||||
searchResult.descrLink = parts.at(PL_DESC_LINK).trimmed().toString(); // Description Link
|
||||
|
||||
if (nbFields > PL_PUB_DATE)
|
||||
{
|
||||
const qint64 secs = parts.at(PL_PUB_DATE).trimmed().toLongLong(&ok);
|
||||
if (ok && (secs > 0))
|
||||
searchResult.pubDate = QDateTime::fromSecsSinceEpoch(secs); // Date
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -30,6 +30,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
@@ -45,8 +46,10 @@ struct SearchResult
|
||||
qlonglong fileSize = 0;
|
||||
qlonglong nbSeeders = 0;
|
||||
qlonglong nbLeechers = 0;
|
||||
QString engineName;
|
||||
QString siteUrl;
|
||||
QString descrLink;
|
||||
QDateTime pubDate;
|
||||
};
|
||||
|
||||
class SearchPluginManager;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -181,6 +181,17 @@ PluginInfo *SearchPluginManager::pluginInfo(const QString &name) const
|
||||
return m_plugins.value(name);
|
||||
}
|
||||
|
||||
QString SearchPluginManager::pluginNameBySiteURL(const QString &siteURL) const
|
||||
{
|
||||
for (const PluginInfo *plugin : asConst(m_plugins))
|
||||
{
|
||||
if (plugin->url == siteURL)
|
||||
return plugin->name;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void SearchPluginManager::enablePlugin(const QString &name, const bool enabled)
|
||||
{
|
||||
PluginInfo *plugin = m_plugins.value(name, nullptr);
|
||||
@@ -338,9 +349,9 @@ void SearchPluginManager::checkForUpdates()
|
||||
, this, &SearchPluginManager::versionInfoDownloadFinished);
|
||||
}
|
||||
|
||||
SearchDownloadHandler *SearchPluginManager::downloadTorrent(const QString &siteUrl, const QString &url)
|
||||
SearchDownloadHandler *SearchPluginManager::downloadTorrent(const QString &pluginName, const QString &url)
|
||||
{
|
||||
return new SearchDownloadHandler {siteUrl, url, this};
|
||||
return new SearchDownloadHandler(pluginName, url, this);
|
||||
}
|
||||
|
||||
SearchHandler *SearchPluginManager::startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -75,6 +75,7 @@ public:
|
||||
QStringList supportedCategories() const;
|
||||
QStringList getPluginCategories(const QString &pluginName) const;
|
||||
PluginInfo *pluginInfo(const QString &name) const;
|
||||
QString pluginNameBySiteURL(const QString &siteURL) const;
|
||||
|
||||
void enablePlugin(const QString &name, bool enabled = true);
|
||||
void updatePlugin(const QString &name);
|
||||
@@ -84,7 +85,7 @@ public:
|
||||
void checkForUpdates();
|
||||
|
||||
SearchHandler *startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins);
|
||||
SearchDownloadHandler *downloadTorrent(const QString &siteUrl, const QString &url);
|
||||
SearchDownloadHandler *downloadTorrent(const QString &pluginName, const QString &url);
|
||||
|
||||
static PluginVersion getPluginVersion(const Path &filePath);
|
||||
static QString categoryFullName(const QString &categoryName);
|
||||
|
||||
@@ -38,8 +38,8 @@ const std::optional<Tag> TorrentFilter::AnyTag;
|
||||
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
|
||||
const TorrentFilter TorrentFilter::SeedingTorrent(TorrentFilter::Seeding);
|
||||
const TorrentFilter TorrentFilter::CompletedTorrent(TorrentFilter::Completed);
|
||||
const TorrentFilter TorrentFilter::PausedTorrent(TorrentFilter::Paused);
|
||||
const TorrentFilter TorrentFilter::ResumedTorrent(TorrentFilter::Resumed);
|
||||
const TorrentFilter TorrentFilter::StoppedTorrent(TorrentFilter::Stopped);
|
||||
const TorrentFilter TorrentFilter::RunningTorrent(TorrentFilter::Running);
|
||||
const TorrentFilter TorrentFilter::ActiveTorrent(TorrentFilter::Active);
|
||||
const TorrentFilter TorrentFilter::InactiveTorrent(TorrentFilter::Inactive);
|
||||
const TorrentFilter TorrentFilter::StalledTorrent(TorrentFilter::Stalled);
|
||||
@@ -52,19 +52,21 @@ const TorrentFilter TorrentFilter::ErroredTorrent(TorrentFilter::Errored);
|
||||
using BitTorrent::Torrent;
|
||||
|
||||
TorrentFilter::TorrentFilter(const Type type, const std::optional<TorrentIDSet> &idSet
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag)
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag, const std::optional<bool> isPrivate)
|
||||
: m_type {type}
|
||||
, m_category {category}
|
||||
, m_tag {tag}
|
||||
, m_idSet {idSet}
|
||||
, m_private {isPrivate}
|
||||
{
|
||||
}
|
||||
|
||||
TorrentFilter::TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag)
|
||||
, const std::optional<QString> &category, const std::optional<Tag> &tag, const std::optional<bool> isPrivate)
|
||||
: m_category {category}
|
||||
, m_tag {tag}
|
||||
, m_idSet {idSet}
|
||||
, m_private {isPrivate}
|
||||
{
|
||||
setTypeByName(filter);
|
||||
}
|
||||
@@ -90,10 +92,10 @@ bool TorrentFilter::setTypeByName(const QString &filter)
|
||||
type = Seeding;
|
||||
else if (filter == u"completed")
|
||||
type = Completed;
|
||||
else if (filter == u"paused")
|
||||
type = Paused;
|
||||
else if (filter == u"resumed")
|
||||
type = Resumed;
|
||||
else if (filter == u"stopped")
|
||||
type = Stopped;
|
||||
else if (filter == u"running")
|
||||
type = Running;
|
||||
else if (filter == u"active")
|
||||
type = Active;
|
||||
else if (filter == u"inactive")
|
||||
@@ -147,19 +149,31 @@ bool TorrentFilter::setTag(const std::optional<Tag> &tag)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::setPrivate(const std::optional<bool> isPrivate)
|
||||
{
|
||||
if (m_private != isPrivate)
|
||||
{
|
||||
m_private = isPrivate;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::match(const Torrent *const torrent) const
|
||||
{
|
||||
if (!torrent) return false;
|
||||
|
||||
return (matchState(torrent) && matchHash(torrent) && matchCategory(torrent) && matchTag(torrent));
|
||||
return (matchState(torrent) && matchHash(torrent) && matchCategory(torrent) && matchTag(torrent) && matchPrivate(torrent));
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
||||
{
|
||||
const BitTorrent::TorrentState state = torrent->state();
|
||||
|
||||
switch (m_type)
|
||||
{
|
||||
case All:
|
||||
default:
|
||||
return true;
|
||||
case Downloading:
|
||||
return torrent->isDownloading();
|
||||
@@ -167,29 +181,32 @@ bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
||||
return torrent->isUploading();
|
||||
case Completed:
|
||||
return torrent->isCompleted();
|
||||
case Paused:
|
||||
return torrent->isPaused();
|
||||
case Resumed:
|
||||
return torrent->isResumed();
|
||||
case Stopped:
|
||||
return torrent->isStopped();
|
||||
case Running:
|
||||
return torrent->isRunning();
|
||||
case Active:
|
||||
return torrent->isActive();
|
||||
case Inactive:
|
||||
return torrent->isInactive();
|
||||
case Stalled:
|
||||
return (torrent->state() == BitTorrent::TorrentState::StalledUploading)
|
||||
|| (torrent->state() == BitTorrent::TorrentState::StalledDownloading);
|
||||
return (state == BitTorrent::TorrentState::StalledUploading)
|
||||
|| (state == BitTorrent::TorrentState::StalledDownloading);
|
||||
case StalledUploading:
|
||||
return torrent->state() == BitTorrent::TorrentState::StalledUploading;
|
||||
return state == BitTorrent::TorrentState::StalledUploading;
|
||||
case StalledDownloading:
|
||||
return torrent->state() == BitTorrent::TorrentState::StalledDownloading;
|
||||
return state == BitTorrent::TorrentState::StalledDownloading;
|
||||
case Checking:
|
||||
return (torrent->state() == BitTorrent::TorrentState::CheckingUploading)
|
||||
|| (torrent->state() == BitTorrent::TorrentState::CheckingDownloading)
|
||||
|| (torrent->state() == BitTorrent::TorrentState::CheckingResumeData);
|
||||
return (state == BitTorrent::TorrentState::CheckingUploading)
|
||||
|| (state == BitTorrent::TorrentState::CheckingDownloading)
|
||||
|| (state == BitTorrent::TorrentState::CheckingResumeData);
|
||||
case Moving:
|
||||
return torrent->isMoving();
|
||||
case Errored:
|
||||
return torrent->isErrored();
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,3 +237,11 @@ bool TorrentFilter::matchTag(const BitTorrent::Torrent *const torrent) const
|
||||
|
||||
return torrent->hasTag(*m_tag);
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchPrivate(const BitTorrent::Torrent *const torrent) const
|
||||
{
|
||||
if (!m_private)
|
||||
return true;
|
||||
|
||||
return m_private == torrent->isPrivate();
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ public:
|
||||
Downloading,
|
||||
Seeding,
|
||||
Completed,
|
||||
Resumed,
|
||||
Paused,
|
||||
Running,
|
||||
Stopped,
|
||||
Active,
|
||||
Inactive,
|
||||
Stalled,
|
||||
@@ -61,7 +61,9 @@ public:
|
||||
StalledDownloading,
|
||||
Checking,
|
||||
Moving,
|
||||
Errored
|
||||
Errored,
|
||||
|
||||
_Count
|
||||
};
|
||||
|
||||
// These mean any permutation, including no category / tag.
|
||||
@@ -72,8 +74,8 @@ public:
|
||||
static const TorrentFilter DownloadingTorrent;
|
||||
static const TorrentFilter SeedingTorrent;
|
||||
static const TorrentFilter CompletedTorrent;
|
||||
static const TorrentFilter PausedTorrent;
|
||||
static const TorrentFilter ResumedTorrent;
|
||||
static const TorrentFilter StoppedTorrent;
|
||||
static const TorrentFilter RunningTorrent;
|
||||
static const TorrentFilter ActiveTorrent;
|
||||
static const TorrentFilter InactiveTorrent;
|
||||
static const TorrentFilter StalledTorrent;
|
||||
@@ -85,16 +87,24 @@ public:
|
||||
|
||||
TorrentFilter() = default;
|
||||
// category & tags: pass empty string for uncategorized / untagged torrents.
|
||||
TorrentFilter(Type type, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory, const std::optional<Tag> &tag = AnyTag);
|
||||
TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory, const std::optional<Tag> &tags = AnyTag);
|
||||
TorrentFilter(Type type
|
||||
, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory
|
||||
, const std::optional<Tag> &tag = AnyTag
|
||||
, std::optional<bool> isPrivate = {});
|
||||
TorrentFilter(const QString &filter
|
||||
, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||
, const std::optional<QString> &category = AnyCategory
|
||||
, const std::optional<Tag> &tags = AnyTag
|
||||
, std::optional<bool> isPrivate = {});
|
||||
|
||||
|
||||
bool setType(Type type);
|
||||
bool setTypeByName(const QString &filter);
|
||||
bool setTorrentIDSet(const std::optional<TorrentIDSet> &idSet);
|
||||
bool setCategory(const std::optional<QString> &category);
|
||||
bool setTag(const std::optional<Tag> &tag);
|
||||
bool setPrivate(std::optional<bool> isPrivate);
|
||||
|
||||
bool match(const BitTorrent::Torrent *torrent) const;
|
||||
|
||||
@@ -103,9 +113,11 @@ private:
|
||||
bool matchHash(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchCategory(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchTag(const BitTorrent::Torrent *torrent) const;
|
||||
bool matchPrivate(const BitTorrent::Torrent *torrent) const;
|
||||
|
||||
Type m_type {All};
|
||||
std::optional<QString> m_category;
|
||||
std::optional<Tag> m_tag;
|
||||
std::optional<TorrentIDSet> m_idSet;
|
||||
std::optional<bool> m_private;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -29,8 +29,6 @@
|
||||
|
||||
#include "fs.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
@@ -52,6 +50,7 @@
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
@@ -311,20 +310,42 @@ bool Utils::Fs::renameFile(const Path &from, const Path &to)
|
||||
*
|
||||
* This function will try to fix the file permissions before removing it.
|
||||
*/
|
||||
bool Utils::Fs::removeFile(const Path &path)
|
||||
nonstd::expected<void, QString> Utils::Fs::removeFile(const Path &path)
|
||||
{
|
||||
if (QFile::remove(path.data()))
|
||||
return true;
|
||||
|
||||
QFile file {path.data()};
|
||||
if (file.remove())
|
||||
return {};
|
||||
|
||||
if (!file.exists())
|
||||
return true;
|
||||
return {};
|
||||
|
||||
// Make sure we have read/write permissions
|
||||
file.setPermissions(file.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
|
||||
return file.remove();
|
||||
if (file.remove())
|
||||
return {};
|
||||
|
||||
return nonstd::make_unexpected(file.errorString());
|
||||
}
|
||||
|
||||
nonstd::expected<void, QString> Utils::Fs::moveFileToTrash(const Path &path)
|
||||
{
|
||||
QFile file {path.data()};
|
||||
if (file.moveToTrash())
|
||||
return {};
|
||||
|
||||
if (!file.exists())
|
||||
return {};
|
||||
|
||||
// Make sure we have read/write permissions
|
||||
file.setPermissions(file.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
|
||||
if (file.moveToTrash())
|
||||
return {};
|
||||
|
||||
const QString errorMessage = file.errorString();
|
||||
return nonstd::make_unexpected(!errorMessage.isEmpty() ? errorMessage : QCoreApplication::translate("fs", "Unknown error"));
|
||||
}
|
||||
|
||||
|
||||
bool Utils::Fs::isReadable(const Path &path)
|
||||
{
|
||||
return QFileInfo(path.data()).isReadable();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "base/3rdparty/expected.hpp"
|
||||
#include "base/global.h"
|
||||
#include "base/pathfwd.h"
|
||||
|
||||
@@ -60,7 +61,8 @@ namespace Utils::Fs
|
||||
|
||||
bool copyFile(const Path &from, const Path &to);
|
||||
bool renameFile(const Path &from, const Path &to);
|
||||
bool removeFile(const Path &path);
|
||||
nonstd::expected<void, QString> removeFile(const Path &path);
|
||||
nonstd::expected<void, QString> moveFileToTrash(const Path &path);
|
||||
bool mkdir(const Path &dirPath);
|
||||
bool mkpath(const Path &dirPath);
|
||||
bool rmdir(const Path &dirPath);
|
||||
|
||||
44
src/base/utils/number.cpp
Normal file
44
src/base/utils/number.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Mike Tzou (Chocobo1)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "number.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
int Utils::Number::clampingAdd(const int num1, const int num2)
|
||||
{
|
||||
static_assert(sizeof(int64_t) > sizeof(int));
|
||||
|
||||
const int64_t intMin = std::numeric_limits<int>::min();
|
||||
const int64_t intMax = std::numeric_limits<int>::max();
|
||||
const int64_t sumResult = static_cast<int64_t>(num1) + num2;
|
||||
const int64_t clampedValue = std::clamp(sumResult, intMin, intMax);
|
||||
return static_cast<int>(clampedValue);
|
||||
}
|
||||
35
src/base/utils/number.h
Normal file
35
src/base/utils/number.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Mike Tzou (Chocobo1)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Utils::Number
|
||||
{
|
||||
// math addition that the result will never overflow/underflow
|
||||
int clampingAdd(int num1, int num2);
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
#define QBT_VERSION_MINOR 0
|
||||
#define QBT_VERSION_BUGFIX 0
|
||||
#define QBT_VERSION_BUILD 0
|
||||
#define QBT_VERSION_STATUS "beta1" // Should be empty for stable releases!
|
||||
#define QBT_VERSION_STATUS "" // Should be empty for stable releases!
|
||||
|
||||
#define QBT__STRINGIFY(x) #x
|
||||
#define QBT_STRINGIFY(x) QBT__STRINGIFY(x)
|
||||
|
||||
@@ -52,6 +52,8 @@ add_library(qbt_gui STATIC
|
||||
desktopintegration.h
|
||||
downloadfromurldialog.h
|
||||
executionlogwidget.h
|
||||
filterpatternformat.h
|
||||
filterpatternformatmenu.h
|
||||
flowlayout.h
|
||||
fspathedit.h
|
||||
fspathedit_p.h
|
||||
@@ -151,6 +153,7 @@ add_library(qbt_gui STATIC
|
||||
desktopintegration.cpp
|
||||
downloadfromurldialog.cpp
|
||||
executionlogwidget.cpp
|
||||
filterpatternformatmenu.cpp
|
||||
flowlayout.cpp
|
||||
fspathedit.cpp
|
||||
fspathedit_p.cpp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -42,17 +42,20 @@
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QShortcut>
|
||||
#include <QSignalBlocker>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/bittorrent/addtorrentparams.h"
|
||||
#include "base/bittorrent/downloadpriority.h"
|
||||
#include "base/bittorrent/infohash.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "base/bittorrent/torrentcontenthandler.h"
|
||||
#include "base/bittorrent/torrentcontentlayout.h"
|
||||
#include "base/bittorrent/torrentdescriptor.h"
|
||||
#include "base/global.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/settingsstorage.h"
|
||||
@@ -61,6 +64,7 @@
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "filterpatternformatmenu.h"
|
||||
#include "lineedit.h"
|
||||
#include "torrenttagsdialog.h"
|
||||
|
||||
@@ -178,6 +182,11 @@ public:
|
||||
return (m_filePaths.isEmpty() ? m_torrentInfo.filePath(index) : m_filePaths.at(index));
|
||||
}
|
||||
|
||||
PathList filePaths() const
|
||||
{
|
||||
return (m_filePaths.isEmpty() ? m_torrentInfo.filePaths() : m_filePaths);
|
||||
}
|
||||
|
||||
void renameFile(const int index, const Path &newFilePath) override
|
||||
{
|
||||
Q_ASSERT((index >= 0) && (index < filesCount()));
|
||||
@@ -271,128 +280,51 @@ private:
|
||||
BitTorrent::TorrentContentLayout m_currentContentLayout;
|
||||
};
|
||||
|
||||
struct AddNewTorrentDialog::Context
|
||||
{
|
||||
BitTorrent::TorrentDescriptor torrentDescr;
|
||||
BitTorrent::AddTorrentParams torrentParams;
|
||||
};
|
||||
|
||||
AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &torrentDescr
|
||||
, const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, m_ui {new Ui::AddNewTorrentDialog}
|
||||
, m_torrentDescr {torrentDescr}
|
||||
, m_torrentParams {inParams}
|
||||
, m_filterLine {new LineEdit(this)}
|
||||
, m_storeDialogSize {SETTINGS_KEY(u"DialogSize"_s)}
|
||||
, m_storeDefaultCategory {SETTINGS_KEY(u"DefaultCategory"_s)}
|
||||
, m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_s)}
|
||||
, m_storeTreeHeaderState {u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)}
|
||||
, m_storeSplitterState {u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)}
|
||||
, m_storeFilterPatternFormat {u"GUI/" SETTINGS_KEY(u"FilterPatternFormat"_s)}
|
||||
{
|
||||
// TODO: set dialog file properties using m_torrentParams.filePriorities
|
||||
m_ui->setupUi(this);
|
||||
|
||||
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
m_ui->lblMetaLoading->setVisible(false);
|
||||
m_ui->progMetaLoading->setVisible(false);
|
||||
m_ui->buttonSave->setVisible(false);
|
||||
connect(m_ui->buttonSave, &QPushButton::clicked, this, &AddNewTorrentDialog::saveTorrentFile);
|
||||
|
||||
m_ui->savePath->setMode(FileSystemPathEdit::Mode::DirectorySave);
|
||||
m_ui->savePath->setDialogCaption(tr("Choose save path"));
|
||||
m_ui->savePath->setMaxVisibleItems(20);
|
||||
|
||||
const auto *session = BitTorrent::Session::instance();
|
||||
|
||||
m_ui->downloadPath->setMode(FileSystemPathEdit::Mode::DirectorySave);
|
||||
m_ui->downloadPath->setDialogCaption(tr("Choose save path"));
|
||||
m_ui->downloadPath->setMaxVisibleItems(20);
|
||||
|
||||
m_ui->addToQueueTopCheckBox->setChecked(m_torrentParams.addToQueueTop.value_or(session->isAddTorrentToQueueTop()));
|
||||
|
||||
m_ui->stopConditionComboBox->setToolTip(
|
||||
u"<html><body><p><b>" + tr("None") + u"</b> - " + tr("No stop condition is set.") + u"</p><p><b>" +
|
||||
tr("Metadata received") + u"</b> - " + tr("Torrent will stop after metadata is received.") +
|
||||
u" <em>" + tr("Torrents that have metadata initially will be added as stopped.") + u"</em></p><p><b>" +
|
||||
tr("Files checked") + u"</b> - " + tr("Torrent will stop after files are initially checked.") +
|
||||
u" <em>" + tr("This will also download metadata if it wasn't there initially.") + u"</em></p></body></html>");
|
||||
u"<html><body><p><b>" + tr("None") + u"</b> - " + tr("No stop condition is set.") + u"</p><p><b>"
|
||||
+ tr("Metadata received") + u"</b> - " + tr("Torrent will stop after metadata is received.")
|
||||
+ u" <em>" + tr("Torrents that have metadata initially will be added as stopped.") + u"</em></p><p><b>"
|
||||
+ tr("Files checked") + u"</b> - " + tr("Torrent will stop after files are initially checked.")
|
||||
+ u" <em>" + tr("This will also download metadata if it wasn't there initially.") + u"</em></p></body></html>");
|
||||
m_ui->stopConditionComboBox->addItem(tr("None"), QVariant::fromValue(BitTorrent::Torrent::StopCondition::None));
|
||||
if (!hasMetadata())
|
||||
m_ui->stopConditionComboBox->addItem(tr("Metadata received"), QVariant::fromValue(BitTorrent::Torrent::StopCondition::MetadataReceived));
|
||||
m_ui->stopConditionComboBox->addItem(tr("Files checked"), QVariant::fromValue(BitTorrent::Torrent::StopCondition::FilesChecked));
|
||||
const auto stopCondition = m_torrentParams.stopCondition.value_or(session->torrentStopCondition());
|
||||
if (hasMetadata() && (stopCondition == BitTorrent::Torrent::StopCondition::MetadataReceived))
|
||||
{
|
||||
m_ui->startTorrentCheckBox->setChecked(false);
|
||||
m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData(QVariant::fromValue(BitTorrent::Torrent::StopCondition::None)));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui->startTorrentCheckBox->setChecked(!m_torrentParams.addPaused.value_or(session->isAddTorrentPaused()));
|
||||
m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData(QVariant::fromValue(stopCondition)));
|
||||
}
|
||||
m_ui->stopConditionLabel->setEnabled(m_ui->startTorrentCheckBox->isChecked());
|
||||
m_ui->stopConditionComboBox->setEnabled(m_ui->startTorrentCheckBox->isChecked());
|
||||
connect(m_ui->startTorrentCheckBox, &QCheckBox::toggled, this, [this](const bool checked)
|
||||
{
|
||||
m_ui->stopConditionLabel->setEnabled(checked);
|
||||
m_ui->stopConditionComboBox->setEnabled(checked);
|
||||
});
|
||||
|
||||
m_ui->comboTTM->blockSignals(true); // the TreeView size isn't correct if the slot does its job at this point
|
||||
m_ui->comboTTM->setCurrentIndex(session->isAutoTMMDisabledByDefault() ? 0 : 1);
|
||||
m_ui->comboTTM->blockSignals(false);
|
||||
connect(m_ui->comboTTM, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::TMMChanged);
|
||||
|
||||
connect(m_ui->savePath, &FileSystemPathEdit::selectedPathChanged, this, &AddNewTorrentDialog::onSavePathChanged);
|
||||
connect(m_ui->downloadPath, &FileSystemPathEdit::selectedPathChanged, this, &AddNewTorrentDialog::onDownloadPathChanged);
|
||||
connect(m_ui->groupBoxDownloadPath, &QGroupBox::toggled, this, &AddNewTorrentDialog::onUseDownloadPathChanged);
|
||||
|
||||
m_ui->checkBoxRememberLastSavePath->setChecked(m_storeRememberLastSavePath);
|
||||
|
||||
m_ui->contentLayoutComboBox->setCurrentIndex(
|
||||
static_cast<int>(m_torrentParams.contentLayout.value_or(session->torrentContentLayout())));
|
||||
connect(m_ui->contentLayoutComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::contentLayoutChanged);
|
||||
|
||||
m_ui->sequentialCheckBox->setChecked(m_torrentParams.sequential);
|
||||
m_ui->firstLastCheckBox->setChecked(m_torrentParams.firstLastPiecePriority);
|
||||
|
||||
m_ui->skipCheckingCheckBox->setChecked(m_torrentParams.skipChecking);
|
||||
m_ui->doNotDeleteTorrentCheckBox->setVisible(TorrentFileGuard::autoDeleteMode() != TorrentFileGuard::Never);
|
||||
|
||||
// Load categories
|
||||
QStringList categories = session->categories();
|
||||
std::sort(categories.begin(), categories.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
|
||||
const QString defaultCategory = m_storeDefaultCategory;
|
||||
|
||||
if (!m_torrentParams.category.isEmpty())
|
||||
m_ui->categoryComboBox->addItem(m_torrentParams.category);
|
||||
if (!defaultCategory.isEmpty())
|
||||
m_ui->categoryComboBox->addItem(defaultCategory);
|
||||
m_ui->categoryComboBox->addItem(u""_s);
|
||||
|
||||
for (const QString &category : asConst(categories))
|
||||
{
|
||||
if ((category != defaultCategory) && (category != m_torrentParams.category))
|
||||
m_ui->categoryComboBox->addItem(category);
|
||||
}
|
||||
|
||||
connect(m_ui->categoryComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::categoryChanged);
|
||||
|
||||
m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_torrentParams.tags, u", "_s));
|
||||
connect(m_ui->tagsEditButton, &QAbstractButton::clicked, this, [this]
|
||||
{
|
||||
auto *dlg = new TorrentTagsDialog(m_torrentParams.tags, this);
|
||||
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg]
|
||||
{
|
||||
m_torrentParams.tags = dlg->tags();
|
||||
m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_torrentParams.tags, u", "_s));
|
||||
});
|
||||
dlg->open();
|
||||
});
|
||||
|
||||
// Torrent content filtering
|
||||
m_filterLine->setPlaceholderText(tr("Filter files..."));
|
||||
m_filterLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
connect(m_filterLine, &LineEdit::textChanged, m_ui->contentTreeView, &TorrentContentWidget::setFilterPattern);
|
||||
m_filterLine->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_filterLine, &QWidget::customContextMenuRequested, this, &AddNewTorrentDialog::showContentFilterContextMenu);
|
||||
m_ui->contentFilterLayout->insertWidget(3, m_filterLine);
|
||||
const auto *focusSearchHotkey = new QShortcut(QKeySequence::Find, this);
|
||||
connect(focusSearchHotkey, &QShortcut::activated, this, [this]()
|
||||
@@ -403,9 +335,6 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
||||
|
||||
loadState();
|
||||
|
||||
connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
|
||||
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
|
||||
|
||||
if (const QByteArray state = m_storeTreeHeaderState; !state.isEmpty())
|
||||
m_ui->contentTreeView->header()->restoreState(state);
|
||||
// Hide useless columns after loading the header state
|
||||
@@ -415,17 +344,34 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
||||
m_ui->contentTreeView->setColumnsVisibilityMode(TorrentContentWidget::ColumnsVisibilityMode::Locked);
|
||||
m_ui->contentTreeView->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Rename);
|
||||
|
||||
m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
|
||||
m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
|
||||
|
||||
m_filterLine->blockSignals(true);
|
||||
|
||||
// Default focus
|
||||
if (m_ui->comboTTM->currentIndex() == 0) // 0 is Manual mode
|
||||
m_ui->savePath->setFocus();
|
||||
else
|
||||
m_ui->categoryComboBox->setFocus();
|
||||
|
||||
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(m_ui->buttonSave, &QPushButton::clicked, this, &AddNewTorrentDialog::saveTorrentFile);
|
||||
connect(m_ui->savePath, &FileSystemPathEdit::selectedPathChanged, this, &AddNewTorrentDialog::onSavePathChanged);
|
||||
connect(m_ui->downloadPath, &FileSystemPathEdit::selectedPathChanged, this, &AddNewTorrentDialog::onDownloadPathChanged);
|
||||
connect(m_ui->groupBoxDownloadPath, &QGroupBox::toggled, this, &AddNewTorrentDialog::onUseDownloadPathChanged);
|
||||
connect(m_ui->comboTMM, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::TMMChanged);
|
||||
connect(m_ui->startTorrentCheckBox, &QCheckBox::toggled, this, [this](const bool checked)
|
||||
{
|
||||
m_ui->stopConditionLabel->setEnabled(checked);
|
||||
m_ui->stopConditionComboBox->setEnabled(checked);
|
||||
});
|
||||
connect(m_ui->contentLayoutComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::contentLayoutChanged);
|
||||
connect(m_ui->categoryComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::categoryChanged);
|
||||
connect(m_ui->tagsEditButton, &QAbstractButton::clicked, this, [this]
|
||||
{
|
||||
auto *dlg = new TorrentTagsDialog(m_currentContext->torrentParams.tags, this);
|
||||
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg]
|
||||
{
|
||||
m_currentContext->torrentParams.tags = dlg->tags();
|
||||
m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_currentContext->torrentParams.tags, u", "_s));
|
||||
});
|
||||
dlg->open();
|
||||
});
|
||||
connect(m_filterLine, &LineEdit::textChanged, this, &AddNewTorrentDialog::setContentFilterPattern);
|
||||
connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
|
||||
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
|
||||
connect(Preferences::instance(), &Preferences::changed, []
|
||||
{
|
||||
const int length = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
|
||||
@@ -433,25 +379,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
||||
, QStringList(settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY).mid(0, length)));
|
||||
});
|
||||
|
||||
const BitTorrent::InfoHash infoHash = m_torrentDescr.infoHash();
|
||||
|
||||
m_ui->labelInfohash1Data->setText(infoHash.v1().isValid() ? infoHash.v1().toString() : tr("N/A"));
|
||||
m_ui->labelInfohash2Data->setText(infoHash.v2().isValid() ? infoHash.v2().toString() : tr("N/A"));
|
||||
|
||||
if (hasMetadata())
|
||||
{
|
||||
setupTreeview();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set dialog title
|
||||
const QString torrentName = m_torrentDescr.name();
|
||||
setWindowTitle(torrentName.isEmpty() ? tr("Magnet link") : torrentName);
|
||||
updateDiskSpaceLabel();
|
||||
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
||||
}
|
||||
|
||||
TMMChanged(m_ui->comboTTM->currentIndex());
|
||||
setCurrentContext(std::make_shared<Context>(Context {torrentDescr, inParams}));
|
||||
}
|
||||
|
||||
AddNewTorrentDialog::~AddNewTorrentDialog()
|
||||
@@ -460,16 +388,6 @@ AddNewTorrentDialog::~AddNewTorrentDialog()
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
BitTorrent::TorrentDescriptor AddNewTorrentDialog::torrentDescriptor() const
|
||||
{
|
||||
return m_torrentDescr;
|
||||
}
|
||||
|
||||
BitTorrent::AddTorrentParams AddNewTorrentDialog::addTorrentParams() const
|
||||
{
|
||||
return m_torrentParams;
|
||||
}
|
||||
|
||||
bool AddNewTorrentDialog::isDoNotDeleteTorrentChecked() const
|
||||
{
|
||||
return m_ui->doNotDeleteTorrentCheckBox->isChecked();
|
||||
@@ -485,9 +403,16 @@ void AddNewTorrentDialog::loadState()
|
||||
|
||||
void AddNewTorrentDialog::saveState()
|
||||
{
|
||||
Q_ASSERT(m_currentContext);
|
||||
if (!m_currentContext) [[unlikely]]
|
||||
return;
|
||||
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr;
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
|
||||
m_storeDialogSize = size();
|
||||
m_storeSplitterState = m_ui->splitter->saveState();
|
||||
if (hasMetadata())
|
||||
if (hasMetadata)
|
||||
m_storeTreeHeaderState = m_ui->contentTreeView->header()->saveState();
|
||||
}
|
||||
|
||||
@@ -501,14 +426,174 @@ void AddNewTorrentDialog::showEvent(QShowEvent *event)
|
||||
raise();
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::setCurrentContext(const std::shared_ptr<Context> context)
|
||||
{
|
||||
Q_ASSERT(context);
|
||||
if (!context) [[unlikely]]
|
||||
return;
|
||||
|
||||
m_currentContext = context;
|
||||
|
||||
const QSignalBlocker comboTMMSignalBlocker {m_ui->comboTMM};
|
||||
const QSignalBlocker startTorrentCheckBoxSignalBlocker {m_ui->startTorrentCheckBox};
|
||||
const QSignalBlocker contentLayoutComboBoxSignalBlocker {m_ui->contentLayoutComboBox};
|
||||
const QSignalBlocker categoryComboBoxSignalBlocker {m_ui->categoryComboBox};
|
||||
|
||||
const BitTorrent::AddTorrentParams &addTorrentParams = m_currentContext->torrentParams;
|
||||
const auto *session = BitTorrent::Session::instance();
|
||||
|
||||
// TODO: set dialog file properties using m_torrentParams.filePriorities
|
||||
|
||||
m_ui->comboTMM->setCurrentIndex(addTorrentParams.useAutoTMM.value_or(!session->isAutoTMMDisabledByDefault()) ? 1 : 0);
|
||||
m_ui->addToQueueTopCheckBox->setChecked(addTorrentParams.addToQueueTop.value_or(session->isAddTorrentToQueueTop()));
|
||||
m_ui->startTorrentCheckBox->setChecked(!addTorrentParams.addStopped.value_or(session->isAddTorrentStopped()));
|
||||
m_ui->stopConditionLabel->setEnabled(m_ui->startTorrentCheckBox->isChecked());
|
||||
m_ui->stopConditionComboBox->setEnabled(m_ui->startTorrentCheckBox->isChecked());
|
||||
m_ui->contentLayoutComboBox->setCurrentIndex(
|
||||
static_cast<int>(addTorrentParams.contentLayout.value_or(session->torrentContentLayout())));
|
||||
m_ui->sequentialCheckBox->setChecked(addTorrentParams.sequential);
|
||||
m_ui->firstLastCheckBox->setChecked(addTorrentParams.firstLastPiecePriority);
|
||||
m_ui->skipCheckingCheckBox->setChecked(addTorrentParams.skipChecking);
|
||||
m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(addTorrentParams.tags, u", "_s));
|
||||
|
||||
// Load categories
|
||||
QStringList categories = session->categories();
|
||||
std::sort(categories.begin(), categories.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
|
||||
const QString defaultCategory = m_storeDefaultCategory;
|
||||
|
||||
if (!addTorrentParams.category.isEmpty())
|
||||
m_ui->categoryComboBox->addItem(addTorrentParams.category);
|
||||
if (!defaultCategory.isEmpty())
|
||||
m_ui->categoryComboBox->addItem(defaultCategory);
|
||||
m_ui->categoryComboBox->addItem(u""_s);
|
||||
|
||||
for (const QString &category : asConst(categories))
|
||||
{
|
||||
if ((category != defaultCategory) && (category != addTorrentParams.category))
|
||||
m_ui->categoryComboBox->addItem(category);
|
||||
}
|
||||
|
||||
m_filterLine->blockSignals(true);
|
||||
m_filterLine->clear();
|
||||
|
||||
// Default focus
|
||||
if (m_ui->comboTMM->currentIndex() == 0) // 0 is Manual mode
|
||||
m_ui->savePath->setFocus();
|
||||
else
|
||||
m_ui->categoryComboBox->setFocus();
|
||||
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr;
|
||||
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
|
||||
if (hasMetadata)
|
||||
m_ui->stopConditionComboBox->removeItem(m_ui->stopConditionComboBox->findData(QVariant::fromValue(BitTorrent::Torrent::StopCondition::MetadataReceived)));
|
||||
else
|
||||
m_ui->stopConditionComboBox->insertItem(1, tr("Metadata received"), QVariant::fromValue(BitTorrent::Torrent::StopCondition::MetadataReceived));
|
||||
const auto stopCondition = addTorrentParams.stopCondition.value_or(session->torrentStopCondition());
|
||||
if (hasMetadata && (stopCondition == BitTorrent::Torrent::StopCondition::MetadataReceived))
|
||||
{
|
||||
m_ui->startTorrentCheckBox->setChecked(false);
|
||||
m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData(QVariant::fromValue(BitTorrent::Torrent::StopCondition::None)));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui->startTorrentCheckBox->setChecked(!addTorrentParams.addStopped.value_or(session->isAddTorrentStopped()));
|
||||
m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData(QVariant::fromValue(stopCondition)));
|
||||
}
|
||||
|
||||
m_ui->labelInfohash1Data->setText(infoHash.v1().isValid() ? infoHash.v1().toString() : tr("N/A"));
|
||||
m_ui->labelInfohash2Data->setText(infoHash.v2().isValid() ? infoHash.v2().toString() : tr("N/A"));
|
||||
|
||||
if (hasMetadata)
|
||||
{
|
||||
m_ui->lblMetaLoading->setVisible(false);
|
||||
m_ui->progMetaLoading->setVisible(false);
|
||||
m_ui->buttonSave->setVisible(false);
|
||||
setupTreeview();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set dialog title
|
||||
const QString torrentName = torrentDescr.name();
|
||||
setWindowTitle(torrentName.isEmpty() ? tr("Magnet link") : torrentName);
|
||||
m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
|
||||
m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
|
||||
updateDiskSpaceLabel();
|
||||
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
||||
}
|
||||
|
||||
TMMChanged(m_ui->comboTMM->currentIndex());
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::updateCurrentContext()
|
||||
{
|
||||
Q_ASSERT(m_currentContext);
|
||||
if (!m_currentContext) [[unlikely]]
|
||||
return;
|
||||
|
||||
BitTorrent::AddTorrentParams &addTorrentParams = m_currentContext->torrentParams;
|
||||
|
||||
addTorrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked();
|
||||
|
||||
// Category
|
||||
addTorrentParams.category = m_ui->categoryComboBox->currentText();
|
||||
if (m_ui->defaultCategoryCheckbox->isChecked())
|
||||
m_storeDefaultCategory = addTorrentParams.category;
|
||||
|
||||
m_storeRememberLastSavePath = m_ui->checkBoxRememberLastSavePath->isChecked();
|
||||
|
||||
addTorrentParams.addToQueueTop = m_ui->addToQueueTopCheckBox->isChecked();
|
||||
addTorrentParams.addStopped = !m_ui->startTorrentCheckBox->isChecked();
|
||||
addTorrentParams.stopCondition = m_ui->stopConditionComboBox->currentData().value<BitTorrent::Torrent::StopCondition>();
|
||||
addTorrentParams.contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
|
||||
|
||||
addTorrentParams.sequential = m_ui->sequentialCheckBox->isChecked();
|
||||
addTorrentParams.firstLastPiecePriority = m_ui->firstLastCheckBox->isChecked();
|
||||
|
||||
const bool useAutoTMM = (m_ui->comboTMM->currentIndex() == 1); // 1 is Automatic mode. Handle all non 1 values as manual mode.
|
||||
addTorrentParams.useAutoTMM = useAutoTMM;
|
||||
if (!useAutoTMM)
|
||||
{
|
||||
const int savePathHistoryLength = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
|
||||
const Path savePath = m_ui->savePath->selectedPath();
|
||||
addTorrentParams.savePath = savePath;
|
||||
updatePathHistory(KEY_SAVEPATHHISTORY, savePath, savePathHistoryLength);
|
||||
|
||||
addTorrentParams.useDownloadPath = m_ui->groupBoxDownloadPath->isChecked();
|
||||
if (addTorrentParams.useDownloadPath)
|
||||
{
|
||||
const Path downloadPath = m_ui->downloadPath->selectedPath();
|
||||
addTorrentParams.downloadPath = downloadPath;
|
||||
updatePathHistory(KEY_DOWNLOADPATHHISTORY, downloadPath, savePathHistoryLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
addTorrentParams.downloadPath = Path();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
addTorrentParams.savePath = Path();
|
||||
addTorrentParams.downloadPath = Path();
|
||||
addTorrentParams.useDownloadPath = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::updateDiskSpaceLabel()
|
||||
{
|
||||
Q_ASSERT(m_currentContext);
|
||||
if (!m_currentContext) [[unlikely]]
|
||||
return;
|
||||
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr;
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
|
||||
// Determine torrent size
|
||||
qlonglong torrentSize = 0;
|
||||
|
||||
if (hasMetadata())
|
||||
if (hasMetadata)
|
||||
{
|
||||
const auto torrentInfo = *m_torrentDescr.info();
|
||||
const auto torrentInfo = *torrentDescr.info();
|
||||
const QVector<BitTorrent::DownloadPriority> &priorities = m_contentAdaptor->filePriorities();
|
||||
Q_ASSERT(priorities.size() == torrentInfo.filesCount());
|
||||
for (int i = 0; i < priorities.size(); ++i)
|
||||
@@ -548,7 +633,7 @@ void AddNewTorrentDialog::onUseDownloadPathChanged(const bool checked)
|
||||
|
||||
void AddNewTorrentDialog::categoryChanged([[maybe_unused]] const int index)
|
||||
{
|
||||
if (m_ui->comboTTM->currentIndex() == 1)
|
||||
if (m_ui->comboTMM->currentIndex() == 1)
|
||||
{
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
const QString categoryName = m_ui->categoryComboBox->currentText();
|
||||
@@ -567,7 +652,14 @@ void AddNewTorrentDialog::categoryChanged([[maybe_unused]] const int index)
|
||||
|
||||
void AddNewTorrentDialog::contentLayoutChanged()
|
||||
{
|
||||
if (!hasMetadata())
|
||||
Q_ASSERT(m_currentContext);
|
||||
if (!m_currentContext) [[unlikely]]
|
||||
return;
|
||||
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr;
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
|
||||
if (!hasMetadata)
|
||||
return;
|
||||
|
||||
const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
|
||||
@@ -577,37 +669,66 @@ void AddNewTorrentDialog::contentLayoutChanged()
|
||||
|
||||
void AddNewTorrentDialog::saveTorrentFile()
|
||||
{
|
||||
Q_ASSERT(hasMetadata());
|
||||
if (!hasMetadata()) [[unlikely]]
|
||||
Q_ASSERT(m_currentContext);
|
||||
if (!m_currentContext) [[unlikely]]
|
||||
return;
|
||||
|
||||
const auto torrentInfo = *m_torrentDescr.info();
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr;
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
|
||||
Q_ASSERT(hasMetadata);
|
||||
if (!hasMetadata) [[unlikely]]
|
||||
return;
|
||||
|
||||
const auto torrentInfo = *torrentDescr.info();
|
||||
|
||||
const QString filter {tr("Torrent file (*%1)").arg(TORRENT_FILE_EXTENSION)};
|
||||
|
||||
Path path {QFileDialog::getSaveFileName(this, tr("Save as torrent file")
|
||||
, QDir::home().absoluteFilePath(torrentInfo.name() + TORRENT_FILE_EXTENSION)
|
||||
, filter)};
|
||||
if (path.isEmpty()) return;
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
|
||||
if (!path.hasExtension(TORRENT_FILE_EXTENSION))
|
||||
path += TORRENT_FILE_EXTENSION;
|
||||
|
||||
const auto result = m_torrentDescr.saveToFile(path);
|
||||
if (!result)
|
||||
if (const auto result = torrentDescr.saveToFile(path); !result)
|
||||
{
|
||||
QMessageBox::critical(this, tr("I/O Error")
|
||||
, tr("Couldn't export torrent metadata file '%1'. Reason: %2.").arg(path.toString(), result.error()));
|
||||
}
|
||||
}
|
||||
|
||||
bool AddNewTorrentDialog::hasMetadata() const
|
||||
void AddNewTorrentDialog::showContentFilterContextMenu()
|
||||
{
|
||||
return m_torrentDescr.info().has_value();
|
||||
QMenu *menu = m_filterLine->createStandardContextMenu();
|
||||
|
||||
auto *formatMenu = new FilterPatternFormatMenu(m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards), menu);
|
||||
connect(formatMenu, &FilterPatternFormatMenu::patternFormatChanged, this, [this](const FilterPatternFormat format)
|
||||
{
|
||||
m_storeFilterPatternFormat = format;
|
||||
setContentFilterPattern();
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
menu->addMenu(formatMenu);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::setContentFilterPattern()
|
||||
{
|
||||
m_ui->contentTreeView->setFilterPattern(m_filterLine->text(), m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards));
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::populateSavePaths()
|
||||
{
|
||||
Q_ASSERT(m_currentContext);
|
||||
if (!m_currentContext) [[unlikely]]
|
||||
return;
|
||||
|
||||
const BitTorrent::AddTorrentParams &addTorrentParams = m_currentContext->torrentParams;
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
|
||||
m_ui->savePath->blockSignals(true);
|
||||
@@ -629,8 +750,8 @@ void AddNewTorrentDialog::populateSavePaths()
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_torrentParams.savePath.isEmpty())
|
||||
setPath(m_ui->savePath, m_torrentParams.savePath);
|
||||
if (!addTorrentParams.savePath.isEmpty())
|
||||
setPath(m_ui->savePath, addTorrentParams.savePath);
|
||||
else if (!m_storeRememberLastSavePath)
|
||||
setPath(m_ui->savePath, btSession->savePath());
|
||||
else
|
||||
@@ -661,11 +782,11 @@ void AddNewTorrentDialog::populateSavePaths()
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool useDownloadPath = m_torrentParams.useDownloadPath.value_or(btSession->isDownloadPathEnabled());
|
||||
const bool useDownloadPath = addTorrentParams.useDownloadPath.value_or(btSession->isDownloadPathEnabled());
|
||||
m_ui->groupBoxDownloadPath->setChecked(useDownloadPath);
|
||||
|
||||
if (!m_torrentParams.downloadPath.isEmpty())
|
||||
setPath(m_ui->downloadPath, m_torrentParams.downloadPath);
|
||||
if (!addTorrentParams.downloadPath.isEmpty())
|
||||
setPath(m_ui->downloadPath, addTorrentParams.downloadPath);
|
||||
else if (!m_storeRememberLastSavePath)
|
||||
setPath(m_ui->downloadPath, btSession->downloadPath());
|
||||
else
|
||||
@@ -682,63 +803,30 @@ void AddNewTorrentDialog::populateSavePaths()
|
||||
|
||||
void AddNewTorrentDialog::accept()
|
||||
{
|
||||
// TODO: Check if destination actually exists
|
||||
m_torrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked();
|
||||
Q_ASSERT(m_currentContext);
|
||||
if (!m_currentContext) [[unlikely]]
|
||||
return;
|
||||
|
||||
// Category
|
||||
m_torrentParams.category = m_ui->categoryComboBox->currentText();
|
||||
if (m_ui->defaultCategoryCheckbox->isChecked())
|
||||
m_storeDefaultCategory = m_torrentParams.category;
|
||||
updateCurrentContext();
|
||||
emit torrentAccepted(m_currentContext->torrentDescr, m_currentContext->torrentParams);
|
||||
|
||||
m_storeRememberLastSavePath = m_ui->checkBoxRememberLastSavePath->isChecked();
|
||||
|
||||
m_torrentParams.addToQueueTop = m_ui->addToQueueTopCheckBox->isChecked();
|
||||
m_torrentParams.addPaused = !m_ui->startTorrentCheckBox->isChecked();
|
||||
m_torrentParams.stopCondition = m_ui->stopConditionComboBox->currentData().value<BitTorrent::Torrent::StopCondition>();
|
||||
m_torrentParams.contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
|
||||
|
||||
m_torrentParams.sequential = m_ui->sequentialCheckBox->isChecked();
|
||||
m_torrentParams.firstLastPiecePriority = m_ui->firstLastCheckBox->isChecked();
|
||||
|
||||
const bool useAutoTMM = (m_ui->comboTTM->currentIndex() == 1); // 1 is Automatic mode. Handle all non 1 values as manual mode.
|
||||
m_torrentParams.useAutoTMM = useAutoTMM;
|
||||
if (!useAutoTMM)
|
||||
{
|
||||
const int savePathHistoryLength = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
|
||||
const Path savePath = m_ui->savePath->selectedPath();
|
||||
m_torrentParams.savePath = savePath;
|
||||
updatePathHistory(KEY_SAVEPATHHISTORY, savePath, savePathHistoryLength);
|
||||
|
||||
m_torrentParams.useDownloadPath = m_ui->groupBoxDownloadPath->isChecked();
|
||||
if (m_torrentParams.useDownloadPath)
|
||||
{
|
||||
const Path downloadPath = m_ui->downloadPath->selectedPath();
|
||||
m_torrentParams.downloadPath = downloadPath;
|
||||
updatePathHistory(KEY_DOWNLOADPATHHISTORY, downloadPath, savePathHistoryLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_torrentParams.downloadPath = Path();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_torrentParams.savePath = Path();
|
||||
m_torrentParams.downloadPath = Path();
|
||||
m_torrentParams.useDownloadPath = std::nullopt;
|
||||
}
|
||||
|
||||
setEnabled(!m_ui->checkBoxNeverShow->isChecked());
|
||||
Preferences::instance()->setAddNewTorrentDialogEnabled(!m_ui->checkBoxNeverShow->isChecked());
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::reject()
|
||||
{
|
||||
if (!hasMetadata())
|
||||
Q_ASSERT(m_currentContext);
|
||||
if (!m_currentContext) [[unlikely]]
|
||||
return;
|
||||
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr;
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
if (!hasMetadata)
|
||||
{
|
||||
setMetadataProgressIndicator(false);
|
||||
BitTorrent::Session::instance()->cancelDownloadMetadata(m_torrentDescr.infoHash().toTorrentID());
|
||||
BitTorrent::Session::instance()->cancelDownloadMetadata(torrentDescr.infoHash().toTorrentID());
|
||||
}
|
||||
|
||||
QDialog::reject();
|
||||
@@ -746,15 +834,20 @@ void AddNewTorrentDialog::reject()
|
||||
|
||||
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
|
||||
{
|
||||
Q_ASSERT(m_currentContext);
|
||||
if (!m_currentContext) [[unlikely]]
|
||||
return;
|
||||
|
||||
Q_ASSERT(metadata.isValid());
|
||||
if (!metadata.isValid()) [[unlikely]]
|
||||
return;
|
||||
|
||||
Q_ASSERT(metadata.matchesInfoHash(m_torrentDescr.infoHash()));
|
||||
if (!metadata.matchesInfoHash(m_torrentDescr.infoHash())) [[unlikely]]
|
||||
BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr;
|
||||
Q_ASSERT(metadata.matchesInfoHash(torrentDescr.infoHash()));
|
||||
if (!metadata.matchesInfoHash(torrentDescr.infoHash())) [[unlikely]]
|
||||
return;
|
||||
|
||||
m_torrentDescr.setTorrentInfo(metadata);
|
||||
torrentDescr.setTorrentInfo(metadata);
|
||||
setMetadataProgressIndicator(true, tr("Parsing metadata..."));
|
||||
|
||||
// Update UI
|
||||
@@ -773,7 +866,7 @@ void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata
|
||||
}
|
||||
|
||||
m_ui->buttonSave->setVisible(true);
|
||||
if (m_torrentDescr.infoHash().v2().isValid())
|
||||
if (torrentDescr.infoHash().v2().isValid())
|
||||
{
|
||||
m_ui->buttonSave->setEnabled(false);
|
||||
m_ui->buttonSave->setToolTip(tr("Cannot create v2 torrent until its data is fully downloaded."));
|
||||
@@ -790,24 +883,32 @@ void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, co
|
||||
|
||||
void AddNewTorrentDialog::setupTreeview()
|
||||
{
|
||||
Q_ASSERT(hasMetadata());
|
||||
if (!hasMetadata()) [[unlikely]]
|
||||
Q_ASSERT(m_currentContext);
|
||||
if (!m_currentContext) [[unlikely]]
|
||||
return;
|
||||
|
||||
const BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr;
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
|
||||
Q_ASSERT(hasMetadata);
|
||||
if (!hasMetadata) [[unlikely]]
|
||||
return;
|
||||
|
||||
// Set dialog title
|
||||
setWindowTitle(m_torrentDescr.name());
|
||||
setWindowTitle(torrentDescr.name());
|
||||
|
||||
const auto &torrentInfo = *m_torrentDescr.info();
|
||||
const auto &torrentInfo = *torrentDescr.info();
|
||||
|
||||
// Set torrent information
|
||||
m_ui->labelCommentData->setText(Utils::Misc::parseHtmlLinks(torrentInfo.comment().toHtmlEscaped()));
|
||||
m_ui->labelDateData->setText(!torrentInfo.creationDate().isNull() ? QLocale().toString(torrentInfo.creationDate(), QLocale::ShortFormat) : tr("Not available"));
|
||||
|
||||
if (m_torrentParams.filePaths.isEmpty())
|
||||
m_torrentParams.filePaths = torrentInfo.filePaths();
|
||||
BitTorrent::AddTorrentParams &addTorrentParams = m_currentContext->torrentParams;
|
||||
if (addTorrentParams.filePaths.isEmpty())
|
||||
addTorrentParams.filePaths = torrentInfo.filePaths();
|
||||
|
||||
m_contentAdaptor = std::make_unique<TorrentContentAdaptor>(torrentInfo, m_torrentParams.filePaths
|
||||
, m_torrentParams.filePriorities, [this] { updateDiskSpaceLabel(); });
|
||||
m_contentAdaptor = std::make_unique<TorrentContentAdaptor>(torrentInfo, addTorrentParams.filePaths
|
||||
, addTorrentParams.filePriorities, [this] { updateDiskSpaceLabel(); });
|
||||
|
||||
const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
|
||||
m_contentAdaptor->applyContentLayout(contentLayout);
|
||||
@@ -816,15 +917,7 @@ void AddNewTorrentDialog::setupTreeview()
|
||||
{
|
||||
// Check file name blacklist for torrents that are manually added
|
||||
QVector<BitTorrent::DownloadPriority> priorities = m_contentAdaptor->filePriorities();
|
||||
for (int i = 0; i < priorities.size(); ++i)
|
||||
{
|
||||
if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
|
||||
continue;
|
||||
|
||||
if (BitTorrent::Session::instance()->isFilenameExcluded(torrentInfo.filePath(i).filename()))
|
||||
priorities[i] = BitTorrent::DownloadPriority::Ignored;
|
||||
}
|
||||
|
||||
BitTorrent::Session::instance()->applyFilenameFilter(m_contentAdaptor->filePaths(), priorities);
|
||||
m_contentAdaptor->prioritizeFiles(priorities);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -33,13 +33,19 @@
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "base/bittorrent/addtorrentparams.h"
|
||||
#include "base/bittorrent/torrentdescriptor.h"
|
||||
#include "base/path.h"
|
||||
#include "base/settingvalue.h"
|
||||
#include "filterpatternformat.h"
|
||||
|
||||
class LineEdit;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentDescriptor;
|
||||
class TorrentInfo;
|
||||
struct AddTorrentParams;
|
||||
}
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class AddNewTorrentDialog;
|
||||
@@ -55,12 +61,12 @@ public:
|
||||
, const BitTorrent::AddTorrentParams &inParams, QWidget *parent);
|
||||
~AddNewTorrentDialog() override;
|
||||
|
||||
BitTorrent::TorrentDescriptor torrentDescriptor() const;
|
||||
BitTorrent::AddTorrentParams addTorrentParams() const;
|
||||
bool isDoNotDeleteTorrentChecked() const;
|
||||
|
||||
void updateMetadata(const BitTorrent::TorrentInfo &metadata);
|
||||
|
||||
signals:
|
||||
void torrentAccepted(const BitTorrent::TorrentDescriptor &torrentDescriptor, const BitTorrent::AddTorrentParams &addTorrentParams);
|
||||
|
||||
private slots:
|
||||
void updateDiskSpaceLabel();
|
||||
void onSavePathChanged(const Path &newPath);
|
||||
@@ -75,29 +81,34 @@ private slots:
|
||||
|
||||
private:
|
||||
class TorrentContentAdaptor;
|
||||
struct Context;
|
||||
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
void setCurrentContext(std::shared_ptr<Context> context);
|
||||
void updateCurrentContext();
|
||||
void populateSavePaths();
|
||||
void loadState();
|
||||
void saveState();
|
||||
void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = {});
|
||||
void setupTreeview();
|
||||
void saveTorrentFile();
|
||||
bool hasMetadata() const;
|
||||
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void showContentFilterContextMenu();
|
||||
void setContentFilterPattern();
|
||||
|
||||
Ui::AddNewTorrentDialog *m_ui = nullptr;
|
||||
std::unique_ptr<TorrentContentAdaptor> m_contentAdaptor;
|
||||
BitTorrent::TorrentDescriptor m_torrentDescr;
|
||||
BitTorrent::AddTorrentParams m_torrentParams;
|
||||
int m_savePathIndex = -1;
|
||||
int m_downloadPathIndex = -1;
|
||||
bool m_useDownloadPath = false;
|
||||
LineEdit *m_filterLine = nullptr;
|
||||
|
||||
std::shared_ptr<Context> m_currentContext;
|
||||
|
||||
SettingValue<QSize> m_storeDialogSize;
|
||||
SettingValue<QString> m_storeDefaultCategory;
|
||||
SettingValue<bool> m_storeRememberLastSavePath;
|
||||
SettingValue<QByteArray> m_storeTreeHeaderState;
|
||||
SettingValue<QByteArray> m_storeSplitterState;
|
||||
SettingValue<FilterPatternFormat> m_storeFilterPatternFormat;
|
||||
};
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboTTM">
|
||||
<widget class="QComboBox" name="comboTMM">
|
||||
<property name="toolTip">
|
||||
<string>Automatic mode means that various torrent properties(eg save path) will be decided by the associated category</string>
|
||||
</property>
|
||||
|
||||
@@ -240,15 +240,15 @@ void AddTorrentParamsWidget::populate()
|
||||
m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_addTorrentParams.tags, u", "_s));
|
||||
|
||||
m_ui->startTorrentComboBox->disconnect(this);
|
||||
m_ui->startTorrentComboBox->setCurrentIndex(m_addTorrentParams.addPaused
|
||||
? m_ui->startTorrentComboBox->findData(!*m_addTorrentParams.addPaused) : 0);
|
||||
m_ui->startTorrentComboBox->setCurrentIndex(m_addTorrentParams.addStopped
|
||||
? m_ui->startTorrentComboBox->findData(!*m_addTorrentParams.addStopped) : 0);
|
||||
connect(m_ui->startTorrentComboBox, &QComboBox::currentIndexChanged, this, [this]
|
||||
{
|
||||
const QVariant data = m_ui->startTorrentComboBox->currentData();
|
||||
if (!data.isValid())
|
||||
m_addTorrentParams.addPaused = std::nullopt;
|
||||
m_addTorrentParams.addStopped = std::nullopt;
|
||||
else
|
||||
m_addTorrentParams.addPaused = !data.toBool();
|
||||
m_addTorrentParams.addStopped = !data.toBool();
|
||||
});
|
||||
|
||||
m_ui->skipCheckingCheckBox->disconnect(this);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 qBittorrent project
|
||||
* Copyright (C) 2016-2024 qBittorrent project
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -63,6 +63,7 @@ namespace
|
||||
// qBittorrent section
|
||||
QBITTORRENT_HEADER,
|
||||
RESUME_DATA_STORAGE,
|
||||
TORRENT_CONTENT_REMOVE_OPTION,
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
MEMORY_WORKING_SET_LIMIT,
|
||||
#endif
|
||||
@@ -105,6 +106,8 @@ namespace
|
||||
ENABLE_MARK_OF_THE_WEB,
|
||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||
PYTHON_EXECUTABLE_PATH,
|
||||
START_SESSION_PAUSED,
|
||||
SESSION_SHUTDOWN_TIMEOUT,
|
||||
|
||||
// libtorrent section
|
||||
LIBTORRENT_HEADER,
|
||||
@@ -331,6 +334,10 @@ void AdvancedSettings::saveAdvancedSettings() const
|
||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||
// Python executable path
|
||||
pref->setPythonExecutablePath(Path(m_pythonExecutablePath.text().trimmed()));
|
||||
// Start session paused
|
||||
session->setStartPaused(m_checkBoxStartSessionPaused.isChecked());
|
||||
// Session shutdown timeout
|
||||
session->setShutdownTimeout(m_spinBoxSessionShutdownTimeout.value());
|
||||
// Choking algorithm
|
||||
session->setChokingAlgorithm(m_comboBoxChokingAlgorithm.currentData().value<BitTorrent::ChokingAlgorithm>());
|
||||
// Seed choking algorithm
|
||||
@@ -358,6 +365,8 @@ void AdvancedSettings::saveAdvancedSettings() const
|
||||
session->setI2PInboundLength(m_spinBoxI2PInboundLength.value());
|
||||
session->setI2POutboundLength(m_spinBoxI2POutboundLength.value());
|
||||
#endif
|
||||
|
||||
session->setTorrentContentRemoveOption(m_comboBoxTorrentContentRemoveOption.currentData().value<BitTorrent::TorrentContentRemoveOption>());
|
||||
}
|
||||
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
@@ -466,6 +475,11 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
m_comboBoxResumeDataStorage.setCurrentIndex(m_comboBoxResumeDataStorage.findData(QVariant::fromValue(session->resumeDataStorageType())));
|
||||
addRow(RESUME_DATA_STORAGE, tr("Resume data storage type (requires restart)"), &m_comboBoxResumeDataStorage);
|
||||
|
||||
m_comboBoxTorrentContentRemoveOption.addItem(tr("Delete files permanently"), QVariant::fromValue(BitTorrent::TorrentContentRemoveOption::Delete));
|
||||
m_comboBoxTorrentContentRemoveOption.addItem(tr("Move files to trash (if possible)"), QVariant::fromValue(BitTorrent::TorrentContentRemoveOption::MoveToTrash));
|
||||
m_comboBoxTorrentContentRemoveOption.setCurrentIndex(m_comboBoxTorrentContentRemoveOption.findData(QVariant::fromValue(session->torrentContentRemoveOption())));
|
||||
addRow(TORRENT_CONTENT_REMOVE_OPTION, tr("Torrent content removing mode"), &m_comboBoxTorrentContentRemoveOption);
|
||||
|
||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||
// Physical memory (RAM) usage limit
|
||||
m_spinBoxMemoryWorkingSetLimit.setMinimum(1);
|
||||
@@ -843,6 +857,18 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
m_pythonExecutablePath.setPlaceholderText(tr("(Auto detect if empty)"));
|
||||
m_pythonExecutablePath.setText(pref->getPythonExecutablePath().toString());
|
||||
addRow(PYTHON_EXECUTABLE_PATH, tr("Python executable path (may require restart)"), &m_pythonExecutablePath);
|
||||
// Start session paused
|
||||
m_checkBoxStartSessionPaused.setChecked(session->isStartPaused());
|
||||
addRow(START_SESSION_PAUSED, tr("Start BitTorrent session in paused state"), &m_checkBoxStartSessionPaused);
|
||||
// Session shutdown timeout
|
||||
m_spinBoxSessionShutdownTimeout.setMinimum(-1);
|
||||
m_spinBoxSessionShutdownTimeout.setMaximum(std::numeric_limits<int>::max());
|
||||
m_spinBoxSessionShutdownTimeout.setValue(session->shutdownTimeout());
|
||||
m_spinBoxSessionShutdownTimeout.setSuffix(tr(" sec", " seconds"));
|
||||
m_spinBoxSessionShutdownTimeout.setSpecialValueText(tr("-1 (unlimited)"));
|
||||
m_spinBoxSessionShutdownTimeout.setToolTip(u"Sets the timeout for the session to be shut down gracefully, at which point it will be forcibly terminated.<br>Note that this does not apply to the saving resume data time."_s);
|
||||
addRow(SESSION_SHUTDOWN_TIMEOUT, tr("BitTorrent session shutdown timeout [-1: unlimited]"), &m_spinBoxSessionShutdownTimeout);
|
||||
|
||||
// Choking algorithm
|
||||
m_comboBoxChokingAlgorithm.addItem(tr("Fixed slots"), QVariant::fromValue(BitTorrent::ChokingAlgorithm::FixedSlots));
|
||||
m_comboBoxChokingAlgorithm.addItem(tr("Upload rate based"), QVariant::fromValue(BitTorrent::ChokingAlgorithm::RateBased));
|
||||
@@ -940,6 +966,7 @@ void AdvancedSettings::addRow(const int row, const QString &text, T *widget)
|
||||
{
|
||||
auto *label = new QLabel(text);
|
||||
label->setOpenExternalLinks(true);
|
||||
label->setToolTip(widget->toolTip());
|
||||
|
||||
setCellWidget(row, PROPERTY, label);
|
||||
setCellWidget(row, VALUE, widget);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 qBittorrent project
|
||||
* Copyright (C) 2015-2024 qBittorrent project
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -73,15 +73,15 @@ private:
|
||||
m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration, m_spinBoxPeerToS,
|
||||
m_spinBoxListRefresh, m_spinBoxTrackerPort, m_spinBoxSendBufferWatermark, m_spinBoxSendBufferLowWatermark,
|
||||
m_spinBoxSendBufferWatermarkFactor, m_spinBoxConnectionSpeed, m_spinBoxSocketSendBufferSize, m_spinBoxSocketReceiveBufferSize, m_spinBoxSocketBacklogSize,
|
||||
m_spinBoxMaxConcurrentHTTPAnnounces, m_spinBoxStopTrackerTimeout,
|
||||
m_spinBoxMaxConcurrentHTTPAnnounces, m_spinBoxStopTrackerTimeout, m_spinBoxSessionShutdownTimeout,
|
||||
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval, m_spinBoxRequestQueueSize;
|
||||
QCheckBox m_checkBoxOsCache, m_checkBoxRecheckCompleted, m_checkBoxResolveCountries, m_checkBoxResolveHosts,
|
||||
m_checkBoxProgramNotifications, m_checkBoxTorrentAddedNotifications, m_checkBoxReannounceWhenAddressChanged, m_checkBoxTrackerFavicon, m_checkBoxTrackerStatus,
|
||||
m_checkBoxTrackerPortForwarding, m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers,
|
||||
m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity,
|
||||
m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport, m_checkBoxConfirmRemoveTrackerFromAllTorrents;
|
||||
m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport, m_checkBoxConfirmRemoveTrackerFromAllTorrents, m_checkBoxStartSessionPaused;
|
||||
QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxDiskIOReadMode, m_comboBoxDiskIOWriteMode, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm,
|
||||
m_comboBoxSeedChokingAlgorithm, m_comboBoxResumeDataStorage;
|
||||
m_comboBoxSeedChokingAlgorithm, m_comboBoxResumeDataStorage, m_comboBoxTorrentContentRemoveOption;
|
||||
QLineEdit m_lineEditAppInstanceName, m_pythonExecutablePath, m_lineEditAnnounceIP, m_lineEditDHTBootstrapNodes;
|
||||
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -30,6 +31,7 @@
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "base/preferences.h"
|
||||
#include "uithememanager.h"
|
||||
@@ -53,8 +55,8 @@ DeletionConfirmationDialog::DeletionConfirmationDialog(QWidget *parent, const in
|
||||
m_ui->rememberBtn->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_s));
|
||||
m_ui->rememberBtn->setIconSize(Utils::Gui::mediumIconSize());
|
||||
|
||||
m_ui->checkPermDelete->setChecked(defaultDeleteFiles || Preferences::instance()->deleteTorrentFilesAsDefault());
|
||||
connect(m_ui->checkPermDelete, &QCheckBox::clicked, this, &DeletionConfirmationDialog::updateRememberButtonState);
|
||||
m_ui->checkRemoveContent->setChecked(defaultDeleteFiles || Preferences::instance()->removeTorrentContent());
|
||||
connect(m_ui->checkRemoveContent, &QCheckBox::clicked, this, &DeletionConfirmationDialog::updateRememberButtonState);
|
||||
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Remove"));
|
||||
m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setFocus();
|
||||
|
||||
@@ -67,18 +69,18 @@ DeletionConfirmationDialog::~DeletionConfirmationDialog()
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
bool DeletionConfirmationDialog::isDeleteFileSelected() const
|
||||
bool DeletionConfirmationDialog::isRemoveContentSelected() const
|
||||
{
|
||||
return m_ui->checkPermDelete->isChecked();
|
||||
return m_ui->checkRemoveContent->isChecked();
|
||||
}
|
||||
|
||||
void DeletionConfirmationDialog::updateRememberButtonState()
|
||||
{
|
||||
m_ui->rememberBtn->setEnabled(m_ui->checkPermDelete->isChecked() != Preferences::instance()->deleteTorrentFilesAsDefault());
|
||||
m_ui->rememberBtn->setEnabled(m_ui->checkRemoveContent->isChecked() != Preferences::instance()->removeTorrentContent());
|
||||
}
|
||||
|
||||
void DeletionConfirmationDialog::on_rememberBtn_clicked()
|
||||
{
|
||||
Preferences::instance()->setDeleteTorrentFilesAsDefault(m_ui->checkPermDelete->isChecked());
|
||||
Preferences::instance()->setRemoveTorrentContent(m_ui->checkRemoveContent->isChecked());
|
||||
m_ui->rememberBtn->setEnabled(false);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -37,16 +38,16 @@ namespace Ui
|
||||
class DeletionConfirmationDialog;
|
||||
}
|
||||
|
||||
class DeletionConfirmationDialog : public QDialog
|
||||
class DeletionConfirmationDialog final : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(DeletionConfirmationDialog)
|
||||
|
||||
public:
|
||||
DeletionConfirmationDialog(QWidget *parent, int size, const QString &name, bool defaultDeleteFiles);
|
||||
~DeletionConfirmationDialog();
|
||||
~DeletionConfirmationDialog() override;
|
||||
|
||||
bool isDeleteFileSelected() const;
|
||||
bool isRemoveContentSelected() const;
|
||||
|
||||
private slots:
|
||||
void updateRememberButtonState();
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkPermDelete">
|
||||
<widget class="QCheckBox" name="checkRemoveContent">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -88,7 +88,7 @@
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Also permanently delete the files</string>
|
||||
<string>Also remove the content files</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -73,6 +73,7 @@ using namespace std::chrono_literals;
|
||||
DesktopIntegration::DesktopIntegration(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_storeNotificationEnabled {NOTIFICATIONS_SETTINGS_KEY(u"Enabled"_s), true}
|
||||
, m_menu {new QMenu}
|
||||
#ifdef QBT_USES_DBUS
|
||||
, m_storeNotificationTimeOut {NOTIFICATIONS_SETTINGS_KEY(u"Timeout"_s), -1}
|
||||
#endif
|
||||
@@ -80,6 +81,7 @@ DesktopIntegration::DesktopIntegration(QObject *parent)
|
||||
#ifdef Q_OS_MACOS
|
||||
desktopIntegrationInstance = this;
|
||||
MacUtils::overrideDockClickHandler(handleDockClicked);
|
||||
m_menu->setAsDockMenu();
|
||||
#else
|
||||
if (Preferences::instance()->systemTrayEnabled())
|
||||
createTrayIcon();
|
||||
@@ -132,46 +134,6 @@ QMenu *DesktopIntegration::menu() const
|
||||
return m_menu;
|
||||
}
|
||||
|
||||
void DesktopIntegration::setMenu(QMenu *menu)
|
||||
{
|
||||
if (menu == m_menu)
|
||||
return;
|
||||
|
||||
#if defined Q_OS_MACOS
|
||||
if (m_menu)
|
||||
delete m_menu;
|
||||
|
||||
m_menu = menu;
|
||||
|
||||
if (m_menu)
|
||||
m_menu->setAsDockMenu();
|
||||
#elif defined Q_OS_UNIX
|
||||
const bool systemTrayEnabled = m_systrayIcon;
|
||||
if (m_menu)
|
||||
{
|
||||
if (m_systrayIcon)
|
||||
{
|
||||
delete m_systrayIcon;
|
||||
m_systrayIcon = nullptr;
|
||||
}
|
||||
delete m_menu;
|
||||
}
|
||||
|
||||
m_menu = menu;
|
||||
|
||||
if (systemTrayEnabled && !m_systrayIcon)
|
||||
createTrayIcon();
|
||||
#else
|
||||
if (m_menu)
|
||||
delete m_menu;
|
||||
|
||||
m_menu = menu;
|
||||
|
||||
if (m_systrayIcon)
|
||||
m_systrayIcon->setContextMenu(m_menu);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DesktopIntegration::isNotificationsEnabled() const
|
||||
{
|
||||
return m_storeNotificationEnabled;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -56,7 +56,6 @@ public:
|
||||
void setToolTip(const QString &toolTip);
|
||||
|
||||
QMenu *menu() const;
|
||||
void setMenu(QMenu *menu);
|
||||
|
||||
bool isNotificationsEnabled() const;
|
||||
void setNotificationsEnabled(bool value);
|
||||
|
||||
@@ -90,11 +90,12 @@ DownloadFromURLDialog::DownloadFromURLDialog(QWidget *parent)
|
||||
urls << urlString;
|
||||
}
|
||||
|
||||
const QString text = urls.join(u'\n')
|
||||
+ (!urls.isEmpty() ? u"\n" : u"");
|
||||
|
||||
m_ui->textUrls->setText(text);
|
||||
m_ui->textUrls->moveCursor(QTextCursor::End);
|
||||
if (!urls.isEmpty())
|
||||
{
|
||||
m_ui->textUrls->setText(urls.join(u'\n') + u"\n");
|
||||
m_ui->textUrls->moveCursor(QTextCursor::End);
|
||||
m_ui->buttonBox->setFocus();
|
||||
}
|
||||
|
||||
if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid())
|
||||
resize(dialogSize);
|
||||
|
||||
48
src/gui/filterpatternformat.h
Normal file
48
src/gui/filterpatternformat.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMetaEnum>
|
||||
|
||||
// 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
|
||||
inline namespace FilterPatternFormatNS
|
||||
{
|
||||
Q_NAMESPACE
|
||||
|
||||
enum class FilterPatternFormat
|
||||
{
|
||||
PlainText,
|
||||
Wildcards,
|
||||
Regex
|
||||
};
|
||||
|
||||
Q_ENUM_NS(FilterPatternFormat)
|
||||
}
|
||||
82
src/gui/filterpatternformatmenu.cpp
Normal file
82
src/gui/filterpatternformatmenu.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "filterpatternformatmenu.h"
|
||||
|
||||
#include <QActionGroup>
|
||||
|
||||
FilterPatternFormatMenu::FilterPatternFormatMenu(const FilterPatternFormat format, QWidget *parent)
|
||||
: QMenu(parent)
|
||||
{
|
||||
setTitle(tr("Pattern Format"));
|
||||
|
||||
auto *patternFormatGroup = new QActionGroup(this);
|
||||
patternFormatGroup->setExclusive(true);
|
||||
|
||||
QAction *plainTextAction = addAction(tr("Plain text"));
|
||||
plainTextAction->setCheckable(true);
|
||||
patternFormatGroup->addAction(plainTextAction);
|
||||
|
||||
QAction *wildcardsAction = addAction(tr("Wildcards"));
|
||||
wildcardsAction->setCheckable(true);
|
||||
patternFormatGroup->addAction(wildcardsAction);
|
||||
|
||||
QAction *regexAction = addAction(tr("Regular expression"));
|
||||
regexAction->setCheckable(true);
|
||||
patternFormatGroup->addAction(regexAction);
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case FilterPatternFormat::Wildcards:
|
||||
default:
|
||||
wildcardsAction->setChecked(true);
|
||||
break;
|
||||
case FilterPatternFormat::PlainText:
|
||||
plainTextAction->setChecked(true);
|
||||
break;
|
||||
case FilterPatternFormat::Regex:
|
||||
regexAction->setChecked(true);
|
||||
break;
|
||||
}
|
||||
|
||||
connect(plainTextAction, &QAction::toggled, this, [this](const bool checked)
|
||||
{
|
||||
if (checked)
|
||||
emit patternFormatChanged(FilterPatternFormat::PlainText);
|
||||
});
|
||||
connect(wildcardsAction, &QAction::toggled, this, [this](const bool checked)
|
||||
{
|
||||
if (checked)
|
||||
emit patternFormatChanged(FilterPatternFormat::Wildcards);
|
||||
});
|
||||
connect(regexAction, &QAction::toggled, this, [this](const bool checked)
|
||||
{
|
||||
if (checked)
|
||||
emit patternFormatChanged(FilterPatternFormat::Regex);
|
||||
});
|
||||
}
|
||||
45
src/gui/filterpatternformatmenu.h
Normal file
45
src/gui/filterpatternformatmenu.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
#include "filterpatternformat.h"
|
||||
|
||||
class FilterPatternFormatMenu final : public QMenu
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(FilterPatternFormatMenu)
|
||||
|
||||
public:
|
||||
explicit FilterPatternFormatMenu(FilterPatternFormat format, QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void patternFormatChanged(FilterPatternFormat format);
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2016 Eugene Shalygin
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2016 Eugene Shalygin
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -32,6 +33,7 @@
|
||||
#include <QCompleter>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QDir>
|
||||
#include <QFileIconProvider>
|
||||
#include <QFileInfo>
|
||||
#include <QFileSystemModel>
|
||||
#include <QMenu>
|
||||
@@ -159,36 +161,34 @@ QValidator::State Private::FileSystemPathValidator::validate(QString &input, [[m
|
||||
}
|
||||
|
||||
Private::FileLineEdit::FileLineEdit(QWidget *parent)
|
||||
: QLineEdit {parent}
|
||||
, m_completerModel {new QFileSystemModel(this)}
|
||||
, m_completer {new QCompleter(this)}
|
||||
: QLineEdit(parent)
|
||||
{
|
||||
m_iconProvider.setOptions(QFileIconProvider::DontUseCustomDirectoryIcons);
|
||||
|
||||
m_completerModel->setIconProvider(&m_iconProvider);
|
||||
m_completerModel->setOptions(QFileSystemModel::DontWatchForChanges);
|
||||
|
||||
m_completer->setModel(m_completerModel);
|
||||
setCompleter(m_completer);
|
||||
|
||||
setCompleter(new QCompleter(this));
|
||||
connect(this, &QLineEdit::textChanged, this, &FileLineEdit::validateText);
|
||||
}
|
||||
|
||||
Private::FileLineEdit::~FileLineEdit()
|
||||
{
|
||||
delete m_completerModel; // has to be deleted before deleting the m_iconProvider object
|
||||
delete m_iconProvider;
|
||||
}
|
||||
|
||||
void Private::FileLineEdit::completeDirectoriesOnly(const bool completeDirsOnly)
|
||||
{
|
||||
const QDir::Filters filters = QDir::NoDotAndDotDot
|
||||
| (completeDirsOnly ? QDir::Dirs : QDir::AllEntries);
|
||||
m_completerModel->setFilter(filters);
|
||||
m_completeDirectoriesOnly = completeDirsOnly;
|
||||
if (m_completerModel)
|
||||
{
|
||||
const QDir::Filters filters = QDir::NoDotAndDotDot
|
||||
| (completeDirsOnly ? QDir::Dirs : QDir::AllEntries);
|
||||
m_completerModel->setFilter(filters);
|
||||
}
|
||||
}
|
||||
|
||||
void Private::FileLineEdit::setFilenameFilters(const QStringList &filters)
|
||||
{
|
||||
m_completerModel->setNameFilters(filters);
|
||||
m_filenameFilters = filters;
|
||||
if (m_completerModel)
|
||||
m_completerModel->setNameFilters(m_filenameFilters);
|
||||
}
|
||||
|
||||
void Private::FileLineEdit::setBrowseAction(QAction *action)
|
||||
@@ -222,6 +222,22 @@ void Private::FileLineEdit::keyPressEvent(QKeyEvent *e)
|
||||
|
||||
if ((e->key() == Qt::Key_Space) && (e->modifiers() == Qt::CTRL))
|
||||
{
|
||||
if (!m_completerModel)
|
||||
{
|
||||
m_iconProvider = new QFileIconProvider;
|
||||
m_iconProvider->setOptions(QFileIconProvider::DontUseCustomDirectoryIcons);
|
||||
|
||||
m_completerModel = new QFileSystemModel(this);
|
||||
m_completerModel->setIconProvider(m_iconProvider);
|
||||
m_completerModel->setOptions(QFileSystemModel::DontWatchForChanges);
|
||||
m_completerModel->setNameFilters(m_filenameFilters);
|
||||
const QDir::Filters filters = QDir::NoDotAndDotDot
|
||||
| (m_completeDirectoriesOnly ? QDir::Dirs : QDir::AllEntries);
|
||||
m_completerModel->setFilter(filters);
|
||||
|
||||
completer()->setModel(m_completerModel);
|
||||
}
|
||||
|
||||
m_completerModel->setRootPath(Path(text()).data());
|
||||
showCompletionPopup();
|
||||
}
|
||||
@@ -243,8 +259,8 @@ void Private::FileLineEdit::contextMenuEvent(QContextMenuEvent *event)
|
||||
|
||||
void Private::FileLineEdit::showCompletionPopup()
|
||||
{
|
||||
m_completer->setCompletionPrefix(text());
|
||||
m_completer->complete();
|
||||
completer()->setCompletionPrefix(text());
|
||||
completer()->complete();
|
||||
}
|
||||
|
||||
void Private::FileLineEdit::validateText()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 Eugene Shalygin
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2016 Eugene Shalygin
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -29,7 +30,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QFileIconProvider>
|
||||
#include <QLineEdit>
|
||||
#include <QtContainerFwd>
|
||||
#include <QValidator>
|
||||
@@ -37,8 +37,8 @@
|
||||
#include "base/pathfwd.h"
|
||||
|
||||
class QAction;
|
||||
class QCompleter;
|
||||
class QContextMenuEvent;
|
||||
class QFileIconProvider;
|
||||
class QFileSystemModel;
|
||||
class QKeyEvent;
|
||||
|
||||
@@ -140,10 +140,11 @@ namespace Private
|
||||
static QString warningText(FileSystemPathValidator::TestResult result);
|
||||
|
||||
QFileSystemModel *m_completerModel = nullptr;
|
||||
QCompleter *m_completer = nullptr;
|
||||
QAction *m_browseAction = nullptr;
|
||||
QAction *m_warningAction = nullptr;
|
||||
QFileIconProvider m_iconProvider;
|
||||
QFileIconProvider *m_iconProvider = nullptr;
|
||||
bool m_completeDirectoriesOnly = false;
|
||||
QStringList m_filenameFilters;
|
||||
};
|
||||
|
||||
class FileComboEdit final : public QComboBox, public IFileEditorWithCompletion
|
||||
|
||||
@@ -64,7 +64,8 @@ namespace
|
||||
delta.setY(0);
|
||||
dialogGeometry.translate(delta);
|
||||
|
||||
delta = screenGeometry.topLeft() - dialogGeometry.topLeft();
|
||||
const QPoint frameOffset {10, 40};
|
||||
delta = screenGeometry.topLeft() - dialogGeometry.topLeft() + frameOffset;
|
||||
if (delta.x() < 0)
|
||||
delta.setX(0);
|
||||
if (delta.y() < 0)
|
||||
@@ -225,14 +226,16 @@ bool GUIAddTorrentManager::processTorrent(const QString &source, const BitTorren
|
||||
|
||||
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
||||
m_dialogs[infoHash] = dlg;
|
||||
connect(dlg, &QDialog::finished, this, [this, source, infoHash, dlg](int result)
|
||||
connect(dlg, &AddNewTorrentDialog::torrentAccepted, this
|
||||
, [this, source](const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &addTorrentParams)
|
||||
{
|
||||
addTorrentToSession(source, torrentDescr, addTorrentParams);
|
||||
});
|
||||
connect(dlg, &QDialog::finished, this, [this, source, infoHash, dlg]
|
||||
{
|
||||
if (dlg->isDoNotDeleteTorrentChecked())
|
||||
releaseTorrentFileGuard(source);
|
||||
|
||||
if (result == QDialog::Accepted)
|
||||
addTorrentToSession(source, dlg->torrentDescriptor(), dlg->addTorrentParams());
|
||||
|
||||
m_dialogs.remove(infoHash);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2020 Prince Gupta <jagannatharjun11@gmail.com>
|
||||
* Copyright (C) 2019 sledgehammer999 <hammered999@gmail.com>
|
||||
*
|
||||
@@ -36,65 +37,26 @@
|
||||
#include "base/global.h"
|
||||
#include "gui/uithememanager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const int MAX_VISIBLE_MESSAGES = 20000;
|
||||
const int MAX_VISIBLE_MESSAGES = 20000;
|
||||
|
||||
QColor getTimestampColor()
|
||||
{
|
||||
return UIThemeManager::instance()->getColor(u"Log.TimeStamp"_s);
|
||||
}
|
||||
|
||||
QColor getLogNormalColor()
|
||||
{
|
||||
return UIThemeManager::instance()->getColor(u"Log.Normal"_s);
|
||||
}
|
||||
|
||||
QColor getLogInfoColor()
|
||||
{
|
||||
return UIThemeManager::instance()->getColor(u"Log.Info"_s);
|
||||
}
|
||||
|
||||
QColor getLogWarningColor()
|
||||
{
|
||||
return UIThemeManager::instance()->getColor(u"Log.Warning"_s);
|
||||
}
|
||||
|
||||
QColor getLogCriticalColor()
|
||||
{
|
||||
return UIThemeManager::instance()->getColor(u"Log.Critical"_s);
|
||||
}
|
||||
|
||||
QColor getPeerBannedColor()
|
||||
{
|
||||
return UIThemeManager::instance()->getColor(u"Log.BannedPeer"_s);
|
||||
}
|
||||
}
|
||||
|
||||
BaseLogModel::Message::Message(const QString &time, const QString &message, const QColor &foreground, const Log::MsgType type)
|
||||
: m_time(time)
|
||||
, m_message(message)
|
||||
, m_foreground(foreground)
|
||||
, m_type(type)
|
||||
BaseLogModel::Message::Message(const QString &time, const QString &message, const Log::MsgType type)
|
||||
: m_time {time}
|
||||
, m_message {message}
|
||||
, m_type {type}
|
||||
{
|
||||
}
|
||||
|
||||
QVariant BaseLogModel::Message::time() const
|
||||
QString BaseLogModel::Message::time() const
|
||||
{
|
||||
return m_time;
|
||||
}
|
||||
|
||||
QVariant BaseLogModel::Message::message() const
|
||||
QString BaseLogModel::Message::message() const
|
||||
{
|
||||
return m_message;
|
||||
}
|
||||
|
||||
QVariant BaseLogModel::Message::foreground() const
|
||||
{
|
||||
return m_foreground;
|
||||
}
|
||||
|
||||
QVariant BaseLogModel::Message::type() const
|
||||
Log::MsgType BaseLogModel::Message::type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
@@ -102,8 +64,9 @@ QVariant BaseLogModel::Message::type() const
|
||||
BaseLogModel::BaseLogModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_messages(MAX_VISIBLE_MESSAGES)
|
||||
, m_timeForeground(getTimestampColor())
|
||||
{
|
||||
loadColors();
|
||||
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, &BaseLogModel::onUIThemeChanged);
|
||||
}
|
||||
|
||||
int BaseLogModel::rowCount(const QModelIndex &) const
|
||||
@@ -135,7 +98,7 @@ QVariant BaseLogModel::data(const QModelIndex &index, const int role) const
|
||||
case TimeForegroundRole:
|
||||
return m_timeForeground;
|
||||
case MessageForegroundRole:
|
||||
return message.foreground();
|
||||
return messageForeground(message);
|
||||
case TypeRole:
|
||||
return message.type();
|
||||
default:
|
||||
@@ -160,6 +123,17 @@ void BaseLogModel::addNewMessage(const BaseLogModel::Message &message)
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void BaseLogModel::onUIThemeChanged()
|
||||
{
|
||||
loadColors();
|
||||
emit dataChanged(index(0, 0), index((rowCount() - 1), (columnCount() - 1)), {TimeForegroundRole, MessageForegroundRole});
|
||||
}
|
||||
|
||||
void BaseLogModel::loadColors()
|
||||
{
|
||||
m_timeForeground = UIThemeManager::instance()->getColor(u"Log.TimeStamp"_s);
|
||||
}
|
||||
|
||||
void BaseLogModel::reset()
|
||||
{
|
||||
beginResetModel();
|
||||
@@ -169,14 +143,9 @@ void BaseLogModel::reset()
|
||||
|
||||
LogMessageModel::LogMessageModel(QObject *parent)
|
||||
: BaseLogModel(parent)
|
||||
, m_foregroundForMessageTypes
|
||||
{
|
||||
{Log::NORMAL, getLogNormalColor()},
|
||||
{Log::INFO, getLogInfoColor()},
|
||||
{Log::WARNING, getLogWarningColor()},
|
||||
{Log::CRITICAL, getLogCriticalColor()}
|
||||
}
|
||||
{
|
||||
loadColors();
|
||||
|
||||
for (const Log::Msg &msg : asConst(Logger::instance()->getMessages()))
|
||||
handleNewMessage(msg);
|
||||
connect(Logger::instance(), &Logger::newLogMessage, this, &LogMessageModel::handleNewMessage);
|
||||
@@ -185,16 +154,38 @@ LogMessageModel::LogMessageModel(QObject *parent)
|
||||
void LogMessageModel::handleNewMessage(const Log::Msg &message)
|
||||
{
|
||||
const QString time = QLocale::system().toString(QDateTime::fromSecsSinceEpoch(message.timestamp), QLocale::ShortFormat);
|
||||
const QString messageText = message.message;
|
||||
const QColor foreground = m_foregroundForMessageTypes[message.type];
|
||||
addNewMessage({time, message.message, message.type});
|
||||
}
|
||||
|
||||
addNewMessage({time, messageText, foreground, message.type});
|
||||
QColor LogMessageModel::messageForeground(const Message &message) const
|
||||
{
|
||||
return m_foregroundForMessageTypes.value(message.type());
|
||||
}
|
||||
|
||||
void LogMessageModel::onUIThemeChanged()
|
||||
{
|
||||
loadColors();
|
||||
BaseLogModel::onUIThemeChanged();
|
||||
}
|
||||
|
||||
void LogMessageModel::loadColors()
|
||||
{
|
||||
const auto *themeManager = UIThemeManager::instance();
|
||||
const QColor normalColor = themeManager->getColor(u"Log.Normal"_s);
|
||||
m_foregroundForMessageTypes =
|
||||
{
|
||||
{Log::NORMAL, normalColor.isValid() ? normalColor : QApplication::palette().color(QPalette::Active, QPalette::WindowText)},
|
||||
{Log::INFO, themeManager->getColor(u"Log.Info"_s)},
|
||||
{Log::WARNING, themeManager->getColor(u"Log.Warning"_s)},
|
||||
{Log::CRITICAL, themeManager->getColor(u"Log.Critical"_s)}
|
||||
};
|
||||
}
|
||||
|
||||
LogPeerModel::LogPeerModel(QObject *parent)
|
||||
: BaseLogModel(parent)
|
||||
, m_bannedPeerForeground(getPeerBannedColor())
|
||||
{
|
||||
loadColors();
|
||||
|
||||
for (const Log::Peer &peer : asConst(Logger::instance()->getPeers()))
|
||||
handleNewMessage(peer);
|
||||
connect(Logger::instance(), &Logger::newLogPeer, this, &LogPeerModel::handleNewMessage);
|
||||
@@ -207,5 +198,21 @@ void LogPeerModel::handleNewMessage(const Log::Peer &peer)
|
||||
? tr("%1 was blocked. Reason: %2.", "0.0.0.0 was blocked. Reason: reason for blocking.").arg(peer.ip, peer.reason)
|
||||
: tr("%1 was banned", "0.0.0.0 was banned").arg(peer.ip);
|
||||
|
||||
addNewMessage({time, message, m_bannedPeerForeground, Log::NORMAL});
|
||||
addNewMessage({time, message, Log::NORMAL});
|
||||
}
|
||||
|
||||
QColor LogPeerModel::messageForeground([[maybe_unused]] const Message &message) const
|
||||
{
|
||||
return m_bannedPeerForeground;
|
||||
}
|
||||
|
||||
void LogPeerModel::onUIThemeChanged()
|
||||
{
|
||||
loadColors();
|
||||
BaseLogModel::onUIThemeChanged();
|
||||
}
|
||||
|
||||
void LogPeerModel::loadColors()
|
||||
{
|
||||
m_bannedPeerForeground = UIThemeManager::instance()->getColor(u"Log.BannedPeer"_s);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2020 Prince Gupta <jagannatharjun11@gmail.com>
|
||||
* Copyright (C) 2019 sledgehammer999 <hammered999@gmail.com>
|
||||
*
|
||||
@@ -62,25 +63,27 @@ protected:
|
||||
class Message
|
||||
{
|
||||
public:
|
||||
Message(const QString &time, const QString &message, const QColor &foreground, Log::MsgType type);
|
||||
Message(const QString &time, const QString &message, Log::MsgType type);
|
||||
|
||||
QVariant time() const;
|
||||
QVariant message() const;
|
||||
QVariant foreground() const;
|
||||
QVariant type() const;
|
||||
QString time() const;
|
||||
QString message() const;
|
||||
Log::MsgType type() const;
|
||||
|
||||
private:
|
||||
QVariant m_time;
|
||||
QVariant m_message;
|
||||
QVariant m_foreground;
|
||||
QVariant m_type;
|
||||
QString m_time;
|
||||
QString m_message;
|
||||
Log::MsgType m_type;
|
||||
};
|
||||
|
||||
void addNewMessage(const Message &message);
|
||||
virtual QColor messageForeground(const Message &message) const = 0;
|
||||
virtual void onUIThemeChanged();
|
||||
|
||||
private:
|
||||
void loadColors();
|
||||
|
||||
boost::circular_buffer_space_optimized<Message> m_messages;
|
||||
const QColor m_timeForeground;
|
||||
QColor m_timeForeground;
|
||||
};
|
||||
|
||||
class LogMessageModel : public BaseLogModel
|
||||
@@ -95,7 +98,11 @@ private slots:
|
||||
void handleNewMessage(const Log::Msg &message);
|
||||
|
||||
private:
|
||||
const QHash<int, QColor> m_foregroundForMessageTypes;
|
||||
QColor messageForeground(const Message &message) const override;
|
||||
void onUIThemeChanged() override;
|
||||
void loadColors();
|
||||
|
||||
QHash<int, QColor> m_foregroundForMessageTypes;
|
||||
};
|
||||
|
||||
class LogPeerModel : public BaseLogModel
|
||||
@@ -110,5 +117,9 @@ private slots:
|
||||
void handleNewMessage(const Log::Peer &peer);
|
||||
|
||||
private:
|
||||
const QColor m_bannedPeerForeground;
|
||||
QColor messageForeground(const Message &message) const override;
|
||||
void onUIThemeChanged() override;
|
||||
void loadColors();
|
||||
|
||||
QColor m_bannedPeerForeground;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -125,18 +125,18 @@ namespace
|
||||
|
||||
MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, const QString &titleSuffix)
|
||||
: GUIApplicationComponent(app)
|
||||
, m_ui(new Ui::MainWindow)
|
||||
, m_storeExecutionLogEnabled(EXECUTIONLOG_SETTINGS_KEY(u"Enabled"_s))
|
||||
, m_storeDownloadTrackerFavicon(SETTINGS_KEY(u"DownloadTrackerFavicon"_s))
|
||||
, m_storeExecutionLogTypes(EXECUTIONLOG_SETTINGS_KEY(u"Types"_s), Log::MsgType::ALL)
|
||||
, m_ui {new Ui::MainWindow}
|
||||
, m_downloadRate {Utils::Misc::friendlyUnit(0, true)}
|
||||
, m_uploadRate {Utils::Misc::friendlyUnit(0, true)}
|
||||
, m_storeExecutionLogEnabled {EXECUTIONLOG_SETTINGS_KEY(u"Enabled"_s)}
|
||||
, m_storeDownloadTrackerFavicon {SETTINGS_KEY(u"DownloadTrackerFavicon"_s)}
|
||||
, m_storeExecutionLogTypes {EXECUTIONLOG_SETTINGS_KEY(u"Types"_s), Log::MsgType::ALL}
|
||||
#ifdef Q_OS_MACOS
|
||||
, m_badger(std::make_unique<MacUtils::Badger>())
|
||||
, m_badger {std::make_unique<MacUtils::Badger>()}
|
||||
#endif // Q_OS_MACOS
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
setTitleSuffix(titleSuffix);
|
||||
|
||||
Preferences *const pref = Preferences::instance();
|
||||
m_uiLocked = pref->isUILocked();
|
||||
m_displaySpeedInTitle = pref->speedInTitleBar();
|
||||
@@ -145,6 +145,8 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con
|
||||
setWindowIcon(UIThemeManager::instance()->getIcon(u"qbittorrent"_s));
|
||||
#endif // Q_OS_MACOS
|
||||
|
||||
setTitleSuffix(titleSuffix);
|
||||
|
||||
#if (defined(Q_OS_UNIX))
|
||||
m_ui->actionOptions->setText(tr("Preferences"));
|
||||
#endif
|
||||
@@ -167,21 +169,37 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con
|
||||
m_ui->actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_s));
|
||||
m_ui->actionLock->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_s));
|
||||
m_ui->actionOptions->setIcon(UIThemeManager::instance()->getIcon(u"configure"_s, u"preferences-system"_s));
|
||||
m_ui->actionPause->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_s, u"media-playback-pause"_s));
|
||||
m_ui->actionPauseAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_s, u"media-playback-pause"_s));
|
||||
m_ui->actionStart->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_s, u"media-playback-start"_s));
|
||||
m_ui->actionStartAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_s, u"media-playback-start"_s));
|
||||
m_ui->actionStop->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_s, u"media-playback-pause"_s));
|
||||
m_ui->actionPauseSession->setIcon(UIThemeManager::instance()->getIcon(u"pause-session"_s, u"media-playback-pause"_s));
|
||||
m_ui->actionResumeSession->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_s, u"media-playback-start"_s));
|
||||
m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_s, u"application-exit"_s));
|
||||
m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon(u"browser-cookies"_s, u"preferences-web-browser-cookies"_s));
|
||||
m_ui->menuLog->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_s));
|
||||
m_ui->actionCheckForUpdates->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_s));
|
||||
|
||||
m_ui->actionPauseSession->setVisible(!BitTorrent::Session::instance()->isPaused());
|
||||
m_ui->actionResumeSession->setVisible(BitTorrent::Session::instance()->isPaused());
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::paused, this, [this]
|
||||
{
|
||||
m_ui->actionPauseSession->setVisible(false);
|
||||
m_ui->actionResumeSession->setVisible(true);
|
||||
refreshWindowTitle();
|
||||
refreshTrayIconTooltip();
|
||||
});
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::resumed, this, [this]
|
||||
{
|
||||
m_ui->actionPauseSession->setVisible(true);
|
||||
m_ui->actionResumeSession->setVisible(false);
|
||||
refreshWindowTitle();
|
||||
refreshTrayIconTooltip();
|
||||
});
|
||||
|
||||
auto *lockMenu = new QMenu(m_ui->menuView);
|
||||
lockMenu->addAction(tr("&Set Password"), this, &MainWindow::defineUILockPassword);
|
||||
lockMenu->addAction(tr("&Clear Password"), this, &MainWindow::clearUILockPassword);
|
||||
m_ui->actionLock->setMenu(lockMenu);
|
||||
|
||||
// Creating Bittorrent session
|
||||
updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled());
|
||||
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::speedLimitModeChanged, this, &MainWindow::updateAltSpeedsBtn);
|
||||
@@ -285,17 +303,14 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con
|
||||
|
||||
// Transfer list slots
|
||||
connect(m_ui->actionStart, &QAction::triggered, m_transferListWidget, &TransferListWidget::startSelectedTorrents);
|
||||
connect(m_ui->actionStartAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::resumeAllTorrents);
|
||||
connect(m_ui->actionPause, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseSelectedTorrents);
|
||||
connect(m_ui->actionPauseAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseAllTorrents);
|
||||
connect(m_ui->actionStop, &QAction::triggered, m_transferListWidget, &TransferListWidget::stopSelectedTorrents);
|
||||
connect(m_ui->actionPauseSession, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseSession);
|
||||
connect(m_ui->actionResumeSession, &QAction::triggered, m_transferListWidget, &TransferListWidget::resumeSession);
|
||||
connect(m_ui->actionDelete, &QAction::triggered, m_transferListWidget, &TransferListWidget::softDeleteSelectedTorrents);
|
||||
connect(m_ui->actionTopQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::topQueuePosSelectedTorrents);
|
||||
connect(m_ui->actionIncreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::increaseQueuePosSelectedTorrents);
|
||||
connect(m_ui->actionDecreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::decreaseQueuePosSelectedTorrents);
|
||||
connect(m_ui->actionBottomQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::bottomQueuePosSelectedTorrents);
|
||||
#ifndef Q_OS_MACOS
|
||||
connect(m_ui->actionToggleVisibility, &QAction::triggered, this, &MainWindow::toggleVisibility);
|
||||
#endif
|
||||
connect(m_ui->actionMinimize, &QAction::triggered, this, &MainWindow::minimizeWindow);
|
||||
connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
|
||||
|
||||
@@ -329,7 +344,7 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con
|
||||
// Configure BT session according to options
|
||||
loadPreferences();
|
||||
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::statsUpdated, this, &MainWindow::reloadSessionStats);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::statsUpdated, this, &MainWindow::loadSessionStats);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated, this, &MainWindow::reloadTorrentStats);
|
||||
|
||||
// Accept drag 'n drops
|
||||
@@ -387,7 +402,7 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con
|
||||
// Load Window state and sizes
|
||||
loadSettings();
|
||||
|
||||
app->desktopIntegration()->setMenu(createDesktopIntegrationMenu());
|
||||
populateDesktopIntegrationMenu();
|
||||
#ifndef Q_OS_MACOS
|
||||
m_ui->actionLock->setVisible(app->desktopIntegration()->isActive());
|
||||
connect(app->desktopIntegration(), &DesktopIntegration::stateChanged, this, [this, app]()
|
||||
@@ -533,7 +548,7 @@ void MainWindow::setTitleSuffix(const QString &suffix)
|
||||
m_windowTitle = QStringLiteral("qBittorrent " QBT_VERSION)
|
||||
+ (!suffix.isEmpty() ? (separator + suffix) : QString());
|
||||
|
||||
setWindowTitle(m_windowTitle);
|
||||
refreshWindowTitle();
|
||||
}
|
||||
|
||||
void MainWindow::addToolbarContextMenu()
|
||||
@@ -799,8 +814,11 @@ void MainWindow::saveSplitterSettings() const
|
||||
|
||||
void MainWindow::cleanup()
|
||||
{
|
||||
saveSettings();
|
||||
saveSplitterSettings();
|
||||
if (!m_neverShown)
|
||||
{
|
||||
saveSettings();
|
||||
saveSplitterSettings();
|
||||
}
|
||||
|
||||
// delete RSSWidget explicitly to avoid crash in
|
||||
// handleRSSUnreadCountUpdated() at application shutdown
|
||||
@@ -881,9 +899,9 @@ void MainWindow::createKeyboardShortcuts()
|
||||
m_ui->actionOptions->setShortcut(Qt::ALT | Qt::Key_O);
|
||||
m_ui->actionStatistics->setShortcut(Qt::CTRL | Qt::Key_I);
|
||||
m_ui->actionStart->setShortcut(Qt::CTRL | Qt::Key_S);
|
||||
m_ui->actionStartAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
|
||||
m_ui->actionPause->setShortcut(Qt::CTRL | Qt::Key_P);
|
||||
m_ui->actionPauseAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
|
||||
m_ui->actionStop->setShortcut(Qt::CTRL | Qt::Key_P);
|
||||
m_ui->actionPauseSession->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
|
||||
m_ui->actionResumeSession->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
|
||||
m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus);
|
||||
m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Minus);
|
||||
m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Plus);
|
||||
@@ -1326,12 +1344,6 @@ void MainWindow::dragEnterEvent(QDragEnterEvent *event)
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
|
||||
/*****************************************************
|
||||
* *
|
||||
* Torrent *
|
||||
* *
|
||||
*****************************************************/
|
||||
|
||||
// Display a dialog to allow user to add
|
||||
// torrents to download list
|
||||
void MainWindow::on_actionOpen_triggered()
|
||||
@@ -1395,7 +1407,7 @@ void MainWindow::showFiltersSidebar(const bool show)
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersAdded, m_transferListFiltersWidget, &TransferListFiltersWidget::addTrackers);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersRemoved, m_transferListFiltersWidget, &TransferListFiltersWidget::removeTrackers);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::refreshTrackers);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerEntriesUpdated, m_transferListFiltersWidget, &TransferListFiltersWidget::trackerEntriesUpdated);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerEntryStatusesUpdated, m_transferListFiltersWidget, &TransferListFiltersWidget::trackerEntryStatusesUpdated);
|
||||
|
||||
m_splitter->insertWidget(0, m_transferListFiltersWidget);
|
||||
m_splitter->setCollapsible(0, true);
|
||||
@@ -1494,28 +1506,21 @@ void MainWindow::loadPreferences()
|
||||
qDebug("GUI settings loaded");
|
||||
}
|
||||
|
||||
void MainWindow::reloadSessionStats()
|
||||
void MainWindow::loadSessionStats()
|
||||
{
|
||||
const BitTorrent::SessionStatus &status = BitTorrent::Session::instance()->status();
|
||||
const QString downloadRate = Utils::Misc::friendlyUnit(status.payloadDownloadRate, true);
|
||||
const QString uploadRate = Utils::Misc::friendlyUnit(status.payloadUploadRate, true);
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
const BitTorrent::SessionStatus &status = btSession->status();
|
||||
m_downloadRate = Utils::Misc::friendlyUnit(status.payloadDownloadRate, true);
|
||||
m_uploadRate = Utils::Misc::friendlyUnit(status.payloadUploadRate, true);
|
||||
|
||||
// update global information
|
||||
#ifdef Q_OS_MACOS
|
||||
m_badger->updateSpeed(status.payloadDownloadRate, status.payloadUploadRate);
|
||||
#else
|
||||
const auto toolTip = u"%1\n%2"_s.arg(
|
||||
tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(downloadRate)
|
||||
, tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(uploadRate));
|
||||
app()->desktopIntegration()->setToolTip(toolTip); // tray icon
|
||||
refreshTrayIconTooltip();
|
||||
#endif // Q_OS_MACOS
|
||||
|
||||
if (m_displaySpeedInTitle)
|
||||
{
|
||||
const QString title = tr("[D: %1, U: %2] %3", "D = Download; U = Upload; %3 is the rest of the window title")
|
||||
.arg(downloadRate, uploadRate, m_windowTitle);
|
||||
setWindowTitle(title);
|
||||
}
|
||||
refreshWindowTitle();
|
||||
}
|
||||
|
||||
void MainWindow::reloadTorrentStats(const QVector<BitTorrent::Torrent *> &torrents)
|
||||
@@ -1527,33 +1532,23 @@ void MainWindow::reloadTorrentStats(const QVector<BitTorrent::Torrent *> &torren
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************
|
||||
* *
|
||||
* Utils *
|
||||
* *
|
||||
*****************************************************/
|
||||
|
||||
void MainWindow::downloadFromURLList(const QStringList &urlList)
|
||||
{
|
||||
for (const QString &url : urlList)
|
||||
app()->addTorrentManager()->addTorrent(url);
|
||||
}
|
||||
|
||||
/*****************************************************
|
||||
* *
|
||||
* Options *
|
||||
* *
|
||||
*****************************************************/
|
||||
|
||||
QMenu *MainWindow::createDesktopIntegrationMenu()
|
||||
void MainWindow::populateDesktopIntegrationMenu()
|
||||
{
|
||||
auto *menu = new QMenu;
|
||||
auto *menu = app()->desktopIntegration()->menu();
|
||||
menu->clear();
|
||||
|
||||
#ifndef Q_OS_MACOS
|
||||
connect(menu, &QMenu::aboutToShow, this, [this]()
|
||||
{
|
||||
m_ui->actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show"));
|
||||
});
|
||||
connect(m_ui->actionToggleVisibility, &QAction::triggered, this, &MainWindow::toggleVisibility);
|
||||
|
||||
menu->addAction(m_ui->actionToggleVisibility);
|
||||
menu->addSeparator();
|
||||
@@ -1567,8 +1562,8 @@ QMenu *MainWindow::createDesktopIntegrationMenu()
|
||||
menu->addAction(m_ui->actionSetGlobalSpeedLimits);
|
||||
menu->addSeparator();
|
||||
|
||||
menu->addAction(m_ui->actionStartAll);
|
||||
menu->addAction(m_ui->actionPauseAll);
|
||||
menu->addAction(m_ui->actionResumeSession);
|
||||
menu->addAction(m_ui->actionPauseSession);
|
||||
|
||||
#ifndef Q_OS_MACOS
|
||||
menu->addSeparator();
|
||||
@@ -1577,8 +1572,6 @@ QMenu *MainWindow::createDesktopIntegrationMenu()
|
||||
|
||||
if (m_uiLocked)
|
||||
menu->setEnabled(false);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
void MainWindow::updateAltSpeedsBtn(const bool alternative)
|
||||
@@ -1631,10 +1624,7 @@ void MainWindow::on_actionSpeedInTitleBar_triggered()
|
||||
{
|
||||
m_displaySpeedInTitle = static_cast<QAction *>(sender())->isChecked();
|
||||
Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
|
||||
if (m_displaySpeedInTitle)
|
||||
reloadSessionStats();
|
||||
else
|
||||
setWindowTitle(m_windowTitle);
|
||||
refreshWindowTitle();
|
||||
}
|
||||
|
||||
void MainWindow::on_actionRSSReader_triggered()
|
||||
@@ -1696,12 +1686,6 @@ void MainWindow::on_actionSearchWidget_triggered()
|
||||
displaySearchTab(m_ui->actionSearchWidget->isChecked());
|
||||
}
|
||||
|
||||
/*****************************************************
|
||||
* *
|
||||
* HTTP Downloader *
|
||||
* *
|
||||
*****************************************************/
|
||||
|
||||
// Display an input dialog to prompt user for
|
||||
// an url
|
||||
void MainWindow::on_actionDownloadFromURL_triggered()
|
||||
@@ -1887,10 +1871,10 @@ void MainWindow::updatePowerManagementState() const
|
||||
const QVector<BitTorrent::Torrent *> allTorrents = BitTorrent::Session::instance()->torrents();
|
||||
const bool inhibitSuspend = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [&](const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
if (preventFromSuspendWhenDownloading && (!torrent->isFinished() && !torrent->isPaused() && !torrent->isErrored() && torrent->hasMetadata()))
|
||||
if (preventFromSuspendWhenDownloading && (!torrent->isFinished() && !torrent->isStopped() && !torrent->isErrored() && torrent->hasMetadata()))
|
||||
return true;
|
||||
|
||||
if (preventFromSuspendWhenSeeding && (torrent->isFinished() && !torrent->isPaused()))
|
||||
if (preventFromSuspendWhenSeeding && (torrent->isFinished() && !torrent->isStopped()))
|
||||
return true;
|
||||
|
||||
return torrent->isMoving();
|
||||
@@ -1905,6 +1889,45 @@ void MainWindow::applyTransferListFilter()
|
||||
m_transferListWidget->applyFilter(m_columnFilterEdit->text(), m_columnFilterComboBox->currentData().value<TransferListModel::Column>());
|
||||
}
|
||||
|
||||
void MainWindow::refreshWindowTitle()
|
||||
{
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
if (btSession->isPaused())
|
||||
{
|
||||
const QString title = tr("[PAUSED] %1", "%1 is the rest of the window title").arg(m_windowTitle);
|
||||
setWindowTitle(title);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_displaySpeedInTitle)
|
||||
{
|
||||
const QString title = tr("[D: %1, U: %2] %3", "D = Download; U = Upload; %3 is the rest of the window title")
|
||||
.arg(m_downloadRate, m_uploadRate, m_windowTitle);
|
||||
setWindowTitle(title);
|
||||
}
|
||||
else
|
||||
{
|
||||
setWindowTitle(m_windowTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::refreshTrayIconTooltip()
|
||||
{
|
||||
const auto *btSession = BitTorrent::Session::instance();
|
||||
if (!btSession->isPaused())
|
||||
{
|
||||
const auto toolTip = u"%1\n%2"_s.arg(
|
||||
tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(m_downloadRate)
|
||||
, tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(m_uploadRate));
|
||||
app()->desktopIntegration()->setToolTip(toolTip);
|
||||
}
|
||||
else
|
||||
{
|
||||
app()->desktopIntegration()->setToolTip(tr("Paused"));
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
void MainWindow::checkProgramUpdate(const bool invokedByUser)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -127,7 +127,7 @@ private slots:
|
||||
void displayRSSTab();
|
||||
void displayExecutionLogTab();
|
||||
void toggleFocusBetweenLineEdits();
|
||||
void reloadSessionStats();
|
||||
void loadSessionStats();
|
||||
void reloadTorrentStats(const QVector<BitTorrent::Torrent *> &torrents);
|
||||
void loadPreferences();
|
||||
void optionsSaved();
|
||||
@@ -170,7 +170,7 @@ private slots:
|
||||
void on_actionDownloadFromURL_triggered();
|
||||
void on_actionExit_triggered();
|
||||
void on_actionLock_triggered();
|
||||
// Check for unpaused downloading or seeding torrents and prevent system suspend/sleep according to preferences
|
||||
// Check for non-stopped downloading or seeding torrents and prevent system suspend/sleep according to preferences
|
||||
void updatePowerManagementState() const;
|
||||
|
||||
void toolbarMenuRequested();
|
||||
@@ -186,7 +186,7 @@ private slots:
|
||||
#endif
|
||||
|
||||
private:
|
||||
QMenu *createDesktopIntegrationMenu();
|
||||
void populateDesktopIntegrationMenu();
|
||||
#ifdef Q_OS_WIN
|
||||
void installPython();
|
||||
#endif
|
||||
@@ -203,14 +203,19 @@ private:
|
||||
void showStatusBar(bool show);
|
||||
void showFiltersSidebar(bool show);
|
||||
void applyTransferListFilter();
|
||||
void refreshWindowTitle();
|
||||
void refreshTrayIconTooltip();
|
||||
|
||||
Ui::MainWindow *m_ui = nullptr;
|
||||
|
||||
QFileSystemWatcher *m_executableWatcher = nullptr;
|
||||
// GUI related
|
||||
QString m_windowTitle;
|
||||
QString m_downloadRate;
|
||||
QString m_uploadRate;
|
||||
bool m_posInitialized = false;
|
||||
bool m_neverShown = true;
|
||||
|
||||
QFileSystemWatcher *m_executableWatcher = nullptr;
|
||||
// GUI related
|
||||
QPointer<QTabWidget> m_tabs;
|
||||
QPointer<StatusBar> m_statusBar;
|
||||
QPointer<OptionsDialog> m_options;
|
||||
|
||||
@@ -43,15 +43,16 @@
|
||||
<string>&Edit</string>
|
||||
</property>
|
||||
<addaction name="actionStart"/>
|
||||
<addaction name="actionPause"/>
|
||||
<addaction name="actionStartAll"/>
|
||||
<addaction name="actionPauseAll"/>
|
||||
<addaction name="actionStop"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionDelete"/>
|
||||
<addaction name="actionTopQueuePos"/>
|
||||
<addaction name="actionIncreaseQueuePos"/>
|
||||
<addaction name="actionDecreaseQueuePos"/>
|
||||
<addaction name="actionBottomQueuePos"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionPauseSession"/>
|
||||
<addaction name="actionResumeSession"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
@@ -153,7 +154,7 @@
|
||||
<addaction name="actionDelete"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionStart"/>
|
||||
<addaction name="actionPause"/>
|
||||
<addaction name="actionStop"/>
|
||||
<addaction name="actionTopQueuePos"/>
|
||||
<addaction name="actionIncreaseQueuePos"/>
|
||||
<addaction name="actionDecreaseQueuePos"/>
|
||||
@@ -188,22 +189,22 @@
|
||||
</action>
|
||||
<action name="actionStart">
|
||||
<property name="text">
|
||||
<string>&Resume</string>
|
||||
<string>Sta&rt</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPause">
|
||||
<action name="actionStop">
|
||||
<property name="text">
|
||||
<string>&Pause</string>
|
||||
<string>Sto&p</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionStartAll">
|
||||
<action name="actionResumeSession">
|
||||
<property name="text">
|
||||
<string>R&esume All</string>
|
||||
<string>&Resume Session</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionPauseAll">
|
||||
<action name="actionPauseSession">
|
||||
<property name="text">
|
||||
<string>P&ause All</string>
|
||||
<string>Pau&se Session</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDelete">
|
||||
|
||||
@@ -253,17 +253,17 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
m_ui->comboHideZero->setCurrentIndex(pref->getHideZeroComboValues());
|
||||
m_ui->comboHideZero->setEnabled(m_ui->checkHideZero->isChecked());
|
||||
|
||||
m_ui->actionTorrentDlOnDblClBox->setItemData(0, TOGGLE_PAUSE);
|
||||
m_ui->actionTorrentDlOnDblClBox->setItemData(0, TOGGLE_STOP);
|
||||
m_ui->actionTorrentDlOnDblClBox->setItemData(1, OPEN_DEST);
|
||||
m_ui->actionTorrentDlOnDblClBox->setItemData(2, PREVIEW_FILE);
|
||||
m_ui->actionTorrentDlOnDblClBox->setItemData(3, SHOW_OPTIONS);
|
||||
m_ui->actionTorrentDlOnDblClBox->setItemData(4, NO_ACTION);
|
||||
int actionDownloading = pref->getActionOnDblClOnTorrentDl();
|
||||
if ((actionDownloading < 0) || (actionDownloading >= m_ui->actionTorrentDlOnDblClBox->count()))
|
||||
actionDownloading = TOGGLE_PAUSE;
|
||||
actionDownloading = TOGGLE_STOP;
|
||||
m_ui->actionTorrentDlOnDblClBox->setCurrentIndex(m_ui->actionTorrentDlOnDblClBox->findData(actionDownloading));
|
||||
|
||||
m_ui->actionTorrentFnOnDblClBox->setItemData(0, TOGGLE_PAUSE);
|
||||
m_ui->actionTorrentFnOnDblClBox->setItemData(0, TOGGLE_STOP);
|
||||
m_ui->actionTorrentFnOnDblClBox->setItemData(1, OPEN_DEST);
|
||||
m_ui->actionTorrentFnOnDblClBox->setItemData(2, PREVIEW_FILE);
|
||||
m_ui->actionTorrentFnOnDblClBox->setItemData(3, SHOW_OPTIONS);
|
||||
@@ -281,7 +281,6 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
m_ui->checkShowSplash->setChecked(!pref->isSplashScreenDisabled());
|
||||
m_ui->checkProgramExitConfirm->setChecked(pref->confirmOnExit());
|
||||
m_ui->checkProgramAutoExitConfirm->setChecked(!pref->dontConfirmAutoExit());
|
||||
m_ui->checkConfirmPauseAndResumeAll->setChecked(pref->confirmPauseAndResumeAll());
|
||||
|
||||
m_ui->windowStateComboBox->addItem(tr("Normal"), QVariant::fromValue(WindowState::Normal));
|
||||
m_ui->windowStateComboBox->addItem(tr("Minimized"), QVariant::fromValue(WindowState::Minimized));
|
||||
@@ -381,7 +380,6 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||
connect(m_ui->checkShowSplash, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkProgramExitConfirm, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkProgramAutoExitConfirm, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkConfirmPauseAndResumeAll, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkShowSystray, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkMinimizeToSysTray, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkCloseToSystray, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
@@ -464,7 +462,6 @@ void OptionsDialog::saveBehaviorTabOptions() const
|
||||
pref->setSplashScreenDisabled(isSplashScreenDisabled());
|
||||
pref->setConfirmOnExit(m_ui->checkProgramExitConfirm->isChecked());
|
||||
pref->setDontConfirmAutoExit(!m_ui->checkProgramAutoExitConfirm->isChecked());
|
||||
pref->setConfirmPauseAndResumeAll(m_ui->checkConfirmPauseAndResumeAll->isChecked());
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
pref->setWinStartup(WinStartup());
|
||||
@@ -522,7 +519,7 @@ void OptionsDialog::loadDownloadsTabOptions()
|
||||
|
||||
m_ui->contentLayoutComboBox->setCurrentIndex(static_cast<int>(session->torrentContentLayout()));
|
||||
m_ui->checkAddToQueueTop->setChecked(session->isAddTorrentToQueueTop());
|
||||
m_ui->checkStartPaused->setChecked(session->isAddTorrentPaused());
|
||||
m_ui->checkAddStopped->setChecked(session->isAddTorrentStopped());
|
||||
|
||||
m_ui->stopConditionComboBox->setToolTip(
|
||||
u"<html><body><p><b>" + tr("None") + u"</b> - " + tr("No stop condition is set.") + u"</p><p><b>" +
|
||||
@@ -534,8 +531,8 @@ void OptionsDialog::loadDownloadsTabOptions()
|
||||
m_ui->stopConditionComboBox->setItemData(1, QVariant::fromValue(BitTorrent::Torrent::StopCondition::MetadataReceived));
|
||||
m_ui->stopConditionComboBox->setItemData(2, QVariant::fromValue(BitTorrent::Torrent::StopCondition::FilesChecked));
|
||||
m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData(QVariant::fromValue(session->torrentStopCondition())));
|
||||
m_ui->stopConditionLabel->setEnabled(!m_ui->checkStartPaused->isChecked());
|
||||
m_ui->stopConditionComboBox->setEnabled(!m_ui->checkStartPaused->isChecked());
|
||||
m_ui->stopConditionLabel->setEnabled(!m_ui->checkAddStopped->isChecked());
|
||||
m_ui->stopConditionComboBox->setEnabled(!m_ui->checkAddStopped->isChecked());
|
||||
|
||||
m_ui->checkMergeTrackers->setChecked(session->isMergeTrackersEnabled());
|
||||
m_ui->checkConfirmMergeTrackers->setEnabled(m_ui->checkAdditionDialog->isChecked());
|
||||
@@ -655,8 +652,8 @@ void OptionsDialog::loadDownloadsTabOptions()
|
||||
connect(m_ui->contentLayoutComboBox, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||
|
||||
connect(m_ui->checkAddToQueueTop, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkStartPaused, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkStartPaused, &QAbstractButton::toggled, this, [this](const bool checked)
|
||||
connect(m_ui->checkAddStopped, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->checkAddStopped, &QAbstractButton::toggled, this, [this](const bool checked)
|
||||
{
|
||||
m_ui->stopConditionLabel->setEnabled(!checked);
|
||||
m_ui->stopConditionComboBox->setEnabled(!checked);
|
||||
@@ -732,7 +729,7 @@ void OptionsDialog::saveDownloadsTabOptions() const
|
||||
session->setTorrentContentLayout(static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex()));
|
||||
|
||||
session->setAddTorrentToQueueTop(m_ui->checkAddToQueueTop->isChecked());
|
||||
session->setAddTorrentPaused(addTorrentsInPause());
|
||||
session->setAddTorrentStopped(addTorrentsStopped());
|
||||
session->setTorrentStopCondition(m_ui->stopConditionComboBox->currentData().value<BitTorrent::Torrent::StopCondition>());
|
||||
TorrentFileGuard::setAutoDeleteMode(!m_ui->deleteTorrentBox->isChecked() ? TorrentFileGuard::Never
|
||||
: !m_ui->deleteCancelledTorrentBox->isChecked() ? TorrentFileGuard::IfAdded
|
||||
@@ -1687,9 +1684,9 @@ bool OptionsDialog::preAllocateAllFiles() const
|
||||
return m_ui->checkPreallocateAll->isChecked();
|
||||
}
|
||||
|
||||
bool OptionsDialog::addTorrentsInPause() const
|
||||
bool OptionsDialog::addTorrentsStopped() const
|
||||
{
|
||||
return m_ui->checkStartPaused->isChecked();
|
||||
return m_ui->checkAddStopped->isChecked();
|
||||
}
|
||||
|
||||
// Proxy settings
|
||||
|
||||
@@ -42,7 +42,7 @@ class AdvancedSettings;
|
||||
// actions on double-click on torrents
|
||||
enum DoubleClickAction
|
||||
{
|
||||
TOGGLE_PAUSE = 0,
|
||||
TOGGLE_STOP = 0,
|
||||
OPEN_DEST = 1,
|
||||
PREVIEW_FILE = 2,
|
||||
NO_ACTION = 3,
|
||||
@@ -151,7 +151,7 @@ private:
|
||||
// Downloads
|
||||
bool preAllocateAllFiles() const;
|
||||
bool useAdditionDialog() const;
|
||||
bool addTorrentsInPause() const;
|
||||
bool addTorrentsStopped() const;
|
||||
Path getTorrentExportDir() const;
|
||||
Path getFinishedTorrentExportDir() const;
|
||||
// Connection options
|
||||
|
||||
@@ -226,19 +226,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkConfirmPauseAndResumeAll">
|
||||
<property name="toolTip">
|
||||
<string>Shows a confirmation dialog upon pausing/resuming all the torrents</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Confirm "Pause/Resume all" actions</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkAltRowColors">
|
||||
<property name="text">
|
||||
@@ -267,7 +254,7 @@
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Paused torrents only</string>
|
||||
<string>Stopped torrents only</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
@@ -925,12 +912,12 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkStartPaused">
|
||||
<widget class="QCheckBox" name="checkAddStopped">
|
||||
<property name="toolTip">
|
||||
<string>The torrent will be added to download list in a paused state</string>
|
||||
<string>The torrent will be added to download list in a stopped state</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string extracomment="The torrent will be added to download list in a paused state">Do not start the download automatically</string>
|
||||
<string extracomment="The torrent will be added to download list in a stopped state">Do not start the download automatically</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -3043,7 +3030,7 @@ Disable encryption: Only connect to peers without protocol encryption</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Pause torrent</string>
|
||||
<string>Stop torrent</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
@@ -3069,7 +3056,7 @@ Disable encryption: Only connect to peers without protocol encryption</string>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="checkEnableAddTrackers">
|
||||
<property name="title">
|
||||
<string>A&utomatically add these trackers to new downloads:</string>
|
||||
<string>A&utomatically append these trackers to new downloads:</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
@@ -3897,7 +3884,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
|
||||
<tabstop>comboI18n</tabstop>
|
||||
<tabstop>checkUseCustomTheme</tabstop>
|
||||
<tabstop>customThemeFilePath</tabstop>
|
||||
<tabstop>checkStartPaused</tabstop>
|
||||
<tabstop>checkAddStopped</tabstop>
|
||||
<tabstop>stopConditionComboBox</tabstop>
|
||||
<tabstop>spinPort</tabstop>
|
||||
<tabstop>checkUPnP</tabstop>
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
|
||||
#include "programupdater.h"
|
||||
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
#include <QtCore/qconfig.h>
|
||||
#include <QtSystemDetection>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
@@ -61,6 +64,20 @@ namespace
|
||||
}
|
||||
return (newVersion > currentVersion);
|
||||
}
|
||||
|
||||
QString buildVariant()
|
||||
{
|
||||
#if defined(Q_OS_MACOS)
|
||||
const auto BASE_OS = u"Mac OS X"_s;
|
||||
#elif defined(Q_OS_WIN)
|
||||
const auto BASE_OS = u"Windows x64"_s;
|
||||
#endif
|
||||
|
||||
if constexpr ((QT_VERSION_MAJOR == 6) && (LIBTORRENT_VERSION_MAJOR == 1))
|
||||
return BASE_OS;
|
||||
|
||||
return u"%1 (qt%2 lt%3%4)"_s.arg(BASE_OS, QString::number(QT_VERSION_MAJOR), QString::number(LIBTORRENT_VERSION_MAJOR), QString::number(LIBTORRENT_VERSION_MINOR));
|
||||
}
|
||||
}
|
||||
|
||||
void ProgramUpdater::checkForUpdates() const
|
||||
@@ -97,12 +114,7 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
||||
: QString {};
|
||||
};
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
const QString OS_TYPE = u"Mac OS X"_s;
|
||||
#elif defined(Q_OS_WIN)
|
||||
const QString OS_TYPE = u"Windows x64"_s;
|
||||
#endif
|
||||
|
||||
const QString variant = buildVariant();
|
||||
bool inItem = false;
|
||||
QString version;
|
||||
QString updateLink;
|
||||
@@ -128,7 +140,7 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
||||
{
|
||||
if (inItem && (xml.name() == u"item"))
|
||||
{
|
||||
if (type.compare(OS_TYPE, Qt::CaseInsensitive) == 0)
|
||||
if (type.compare(variant, Qt::CaseInsensitive) == 0)
|
||||
{
|
||||
qDebug("The last update available is %s", qUtf8Printable(version));
|
||||
if (!version.isEmpty())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -46,9 +47,9 @@ namespace
|
||||
}
|
||||
|
||||
DownloadedPiecesBar::DownloadedPiecesBar(QWidget *parent)
|
||||
: base {parent}
|
||||
, m_dlPieceColor {dlPieceColor(pieceColor())}
|
||||
: base(parent)
|
||||
{
|
||||
updateColorsImpl();
|
||||
}
|
||||
|
||||
QVector<float> DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin, int reqSize)
|
||||
@@ -128,25 +129,24 @@ QVector<float> DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DownloadedPiecesBar::updateImage(QImage &image)
|
||||
QImage DownloadedPiecesBar::renderImage()
|
||||
{
|
||||
// qDebug() << "updateImage";
|
||||
QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888);
|
||||
if (image2.isNull())
|
||||
QImage image {width() - 2 * borderWidth, 1, QImage::Format_RGB888};
|
||||
if (image.isNull())
|
||||
{
|
||||
qDebug() << "QImage image2() allocation failed, width():" << width();
|
||||
return false;
|
||||
qDebug() << "QImage allocation failed, width():" << width();
|
||||
return image;
|
||||
}
|
||||
|
||||
if (m_pieces.isEmpty())
|
||||
{
|
||||
image2.fill(backgroundColor());
|
||||
image = image2;
|
||||
return true;
|
||||
image.fill(backgroundColor());
|
||||
return image;
|
||||
}
|
||||
|
||||
QVector<float> scaledPieces = bitfieldToFloatVector(m_pieces, image2.width());
|
||||
QVector<float> scaledPiecesDl = bitfieldToFloatVector(m_downloadedPieces, image2.width());
|
||||
QVector<float> scaledPieces = bitfieldToFloatVector(m_pieces, image.width());
|
||||
QVector<float> scaledPiecesDl = bitfieldToFloatVector(m_downloadedPieces, image.width());
|
||||
|
||||
// filling image
|
||||
for (int x = 0; x < scaledPieces.size(); ++x)
|
||||
@@ -161,15 +161,15 @@ bool DownloadedPiecesBar::updateImage(QImage &image)
|
||||
QRgb mixedColor = mixTwoColors(pieceColor().rgb(), m_dlPieceColor.rgb(), ratio);
|
||||
mixedColor = mixTwoColors(backgroundColor().rgb(), mixedColor, fillRatio);
|
||||
|
||||
image2.setPixel(x, 0, mixedColor);
|
||||
image.setPixel(x, 0, mixedColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
|
||||
image.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
|
||||
}
|
||||
}
|
||||
image = image2;
|
||||
return true;
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
void DownloadedPiecesBar::setProgress(const QBitArray &pieces, const QBitArray &downloadedPieces)
|
||||
@@ -177,7 +177,7 @@ void DownloadedPiecesBar::setProgress(const QBitArray &pieces, const QBitArray &
|
||||
m_pieces = pieces;
|
||||
m_downloadedPieces = downloadedPieces;
|
||||
|
||||
requestImageUpdate();
|
||||
redraw();
|
||||
}
|
||||
|
||||
void DownloadedPiecesBar::clear()
|
||||
@@ -198,3 +198,14 @@ QString DownloadedPiecesBar::simpleToolTipText() const
|
||||
+ u"</table>";
|
||||
|
||||
}
|
||||
|
||||
void DownloadedPiecesBar::updateColors()
|
||||
{
|
||||
PiecesBar::updateColors();
|
||||
updateColorsImpl();
|
||||
}
|
||||
|
||||
void DownloadedPiecesBar::updateColorsImpl()
|
||||
{
|
||||
m_dlPieceColor = dlPieceColor(pieceColor());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -52,11 +53,13 @@ public:
|
||||
private:
|
||||
// scale bitfield vector to float vector
|
||||
QVector<float> bitfieldToFloatVector(const QBitArray &vecin, int reqSize);
|
||||
bool updateImage(QImage &image) override;
|
||||
QImage renderImage() override;
|
||||
QString simpleToolTipText() const override;
|
||||
void updateColors() override;
|
||||
void updateColorsImpl();
|
||||
|
||||
// incomplete piece color
|
||||
const QColor m_dlPieceColor;
|
||||
QColor m_dlPieceColor;
|
||||
// last used bitfields, uses to better resize redraw
|
||||
// TODO: make a diff pieces to new pieces and update only changed pixels, speedup when update > 20x faster
|
||||
QBitArray m_pieces;
|
||||
|
||||
@@ -411,7 +411,7 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
|
||||
return;
|
||||
|
||||
// Remove I2P peers since they will be completely reloaded.
|
||||
for (QStandardItem *item : asConst(m_I2PPeerItems))
|
||||
for (const QStandardItem *item : asConst(m_I2PPeerItems))
|
||||
m_listModel->removeRow(item->row());
|
||||
m_I2PPeerItems.clear();
|
||||
|
||||
@@ -466,10 +466,14 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
QStandardItem *item = m_peerItems.take(peerEndpoint);
|
||||
|
||||
QSet<QStandardItem *> &items = m_itemsByIP[peerEndpoint.address.ip];
|
||||
items.remove(item);
|
||||
if (items.isEmpty())
|
||||
m_itemsByIP.remove(peerEndpoint.address.ip);
|
||||
const auto items = m_itemsByIP.find(peerEndpoint.address.ip);
|
||||
Q_ASSERT(items != m_itemsByIP.end());
|
||||
if (items == m_itemsByIP.end()) [[unlikely]]
|
||||
continue;
|
||||
|
||||
items->remove(item);
|
||||
if (items->isEmpty())
|
||||
m_itemsByIP.erase(items);
|
||||
|
||||
m_listModel->removeRow(item->row());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -126,39 +127,38 @@ QVector<float> PieceAvailabilityBar::intToFloatVector(const QVector<int> &vecin,
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PieceAvailabilityBar::updateImage(QImage &image)
|
||||
QImage PieceAvailabilityBar::renderImage()
|
||||
{
|
||||
QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888);
|
||||
if (image2.isNull())
|
||||
QImage image {width() - 2 * borderWidth, 1, QImage::Format_RGB888};
|
||||
if (image.isNull())
|
||||
{
|
||||
qDebug() << "QImage image2() allocation failed, width():" << width();
|
||||
return false;
|
||||
qDebug() << "QImage allocation failed, width():" << width();
|
||||
return image;
|
||||
}
|
||||
|
||||
if (m_pieces.empty())
|
||||
{
|
||||
image2.fill(backgroundColor());
|
||||
image = image2;
|
||||
return true;
|
||||
image.fill(backgroundColor());
|
||||
return image;
|
||||
}
|
||||
|
||||
QVector<float> scaledPieces = intToFloatVector(m_pieces, image2.width());
|
||||
QVector<float> scaledPieces = intToFloatVector(m_pieces, image.width());
|
||||
|
||||
// filling image
|
||||
for (int x = 0; x < scaledPieces.size(); ++x)
|
||||
{
|
||||
float piecesToValue = scaledPieces.at(x);
|
||||
image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
|
||||
image.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
|
||||
}
|
||||
image = image2;
|
||||
return true;
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
void PieceAvailabilityBar::setAvailability(const QVector<int> &avail)
|
||||
{
|
||||
m_pieces = avail;
|
||||
|
||||
requestImageUpdate();
|
||||
redraw();
|
||||
}
|
||||
|
||||
void PieceAvailabilityBar::clear()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -46,7 +47,7 @@ public:
|
||||
void clear() override;
|
||||
|
||||
private:
|
||||
bool updateImage(QImage &image) override;
|
||||
QImage renderImage() override;
|
||||
QString simpleToolTipText() const override;
|
||||
|
||||
// last used int vector, uses to better resize redraw
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2016 Eugene Shalygin
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
@@ -41,6 +42,7 @@
|
||||
#include "base/indexrange.h"
|
||||
#include "base/path.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "gui/uithememanager.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -114,10 +116,16 @@ namespace
|
||||
}
|
||||
|
||||
PiecesBar::PiecesBar(QWidget *parent)
|
||||
: QWidget {parent}
|
||||
: QWidget(parent)
|
||||
{
|
||||
updatePieceColors();
|
||||
setMouseTracking(true);
|
||||
|
||||
updateColorsImpl();
|
||||
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, [this]
|
||||
{
|
||||
updateColors();
|
||||
redraw();
|
||||
});
|
||||
}
|
||||
|
||||
void PiecesBar::setTorrent(const BitTorrent::Torrent *torrent)
|
||||
@@ -154,7 +162,7 @@ void PiecesBar::leaveEvent(QEvent *e)
|
||||
{
|
||||
m_hovered = false;
|
||||
m_highlightedRegion = {};
|
||||
requestImageUpdate();
|
||||
redraw();
|
||||
base::leaveEvent(e);
|
||||
}
|
||||
|
||||
@@ -178,16 +186,17 @@ void PiecesBar::paintEvent(QPaintEvent *)
|
||||
else
|
||||
{
|
||||
if (m_image.width() != imageRect.width())
|
||||
updateImage(m_image);
|
||||
{
|
||||
if (const QImage image = renderImage(); !image.isNull())
|
||||
m_image = image;
|
||||
}
|
||||
painter.drawImage(imageRect, m_image);
|
||||
}
|
||||
|
||||
if (!m_highlightedRegion.isNull())
|
||||
{
|
||||
QColor highlightColor {this->palette().color(QPalette::Active, QPalette::Highlight)};
|
||||
highlightColor.setAlphaF(0.35f);
|
||||
QRect targetHighlightRect {m_highlightedRegion.adjusted(borderWidth, borderWidth, borderWidth, height() - 2 * borderWidth)};
|
||||
painter.fillRect(targetHighlightRect, highlightColor);
|
||||
painter.fillRect(targetHighlightRect, highlightedPieceColor());
|
||||
}
|
||||
|
||||
QPainterPath border;
|
||||
@@ -196,30 +205,40 @@ void PiecesBar::paintEvent(QPaintEvent *)
|
||||
painter.drawPath(border);
|
||||
}
|
||||
|
||||
void PiecesBar::requestImageUpdate()
|
||||
void PiecesBar::redraw()
|
||||
{
|
||||
if (updateImage(m_image))
|
||||
if (const QImage image = renderImage(); !image.isNull())
|
||||
{
|
||||
m_image = image;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
QColor PiecesBar::backgroundColor() const
|
||||
{
|
||||
return palette().color(QPalette::Base);
|
||||
return palette().color(QPalette::Active, QPalette::Base);
|
||||
}
|
||||
|
||||
QColor PiecesBar::borderColor() const
|
||||
{
|
||||
return palette().color(QPalette::Dark);
|
||||
return palette().color(QPalette::Active, QPalette::Dark);
|
||||
}
|
||||
|
||||
QColor PiecesBar::pieceColor() const
|
||||
{
|
||||
return palette().color(QPalette::Highlight);
|
||||
return palette().color(QPalette::Active, QPalette::Highlight);
|
||||
}
|
||||
|
||||
QColor PiecesBar::highlightedPieceColor() const
|
||||
{
|
||||
QColor col = palette().color(QPalette::Highlight).darker();
|
||||
col.setAlphaF(0.35);
|
||||
return col;
|
||||
}
|
||||
|
||||
QColor PiecesBar::colorBoxBorderColor() const
|
||||
{
|
||||
return palette().color(QPalette::ToolTipText);
|
||||
return palette().color(QPalette::Active, QPalette::ToolTipText);
|
||||
}
|
||||
|
||||
const QVector<QRgb> &PiecesBar::pieceColors() const
|
||||
@@ -325,12 +344,17 @@ void PiecesBar::highlightFile(int imagePos)
|
||||
}
|
||||
}
|
||||
|
||||
void PiecesBar::updatePieceColors()
|
||||
void PiecesBar::updateColors()
|
||||
{
|
||||
updateColorsImpl();
|
||||
}
|
||||
|
||||
void PiecesBar::updateColorsImpl()
|
||||
{
|
||||
m_pieceColors = QVector<QRgb>(256);
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
float ratio = (i / 255.0);
|
||||
const float ratio = (i / 255.0);
|
||||
m_pieceColors[i] = mixTwoColors(backgroundColor().rgb(), pieceColor().rgb(), ratio);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2016 Eugene Shalygin
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
@@ -54,22 +55,22 @@ public:
|
||||
|
||||
virtual void clear();
|
||||
|
||||
// QObject interface
|
||||
bool event(QEvent *e) override;
|
||||
|
||||
protected:
|
||||
// QWidget interface
|
||||
bool event(QEvent *e) override;
|
||||
void enterEvent(QEnterEvent *e) override;
|
||||
void leaveEvent(QEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void requestImageUpdate();
|
||||
|
||||
virtual void updateColors();
|
||||
void redraw();
|
||||
|
||||
QColor backgroundColor() const;
|
||||
QColor borderColor() const;
|
||||
QColor pieceColor() const;
|
||||
QColor highlightedPieceColor() const;
|
||||
QColor colorBoxBorderColor() const;
|
||||
|
||||
const QVector<QRgb> &pieceColors() const;
|
||||
|
||||
// mix two colors by light model, ratio <0, 1>
|
||||
@@ -82,11 +83,9 @@ private:
|
||||
void highlightFile(int imagePos);
|
||||
|
||||
virtual QString simpleToolTipText() const = 0;
|
||||
virtual QImage renderImage() = 0;
|
||||
|
||||
// draw new image to replace the actual image
|
||||
// returns true if image was successfully updated
|
||||
virtual bool updateImage(QImage &image) = 0;
|
||||
void updatePieceColors();
|
||||
void updateColorsImpl();
|
||||
|
||||
const BitTorrent::Torrent *m_torrent = nullptr;
|
||||
QImage m_image;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -52,6 +52,7 @@
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "gui/autoexpandabledialog.h"
|
||||
#include "gui/filterpatternformatmenu.h"
|
||||
#include "gui/lineedit.h"
|
||||
#include "gui/trackerlist/trackerlistwidget.h"
|
||||
#include "gui/uithememanager.h"
|
||||
@@ -66,6 +67,7 @@
|
||||
PropertiesWidget::PropertiesWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_ui {new Ui::PropertiesWidget}
|
||||
, m_storeFilterPatternFormat {u"GUI/PropertiesWidget/FilterPatternFormat"_s}
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
#ifndef Q_OS_MACOS
|
||||
@@ -78,7 +80,9 @@ PropertiesWidget::PropertiesWidget(QWidget *parent)
|
||||
m_contentFilterLine = new LineEdit(this);
|
||||
m_contentFilterLine->setPlaceholderText(tr("Filter files..."));
|
||||
m_contentFilterLine->setFixedWidth(300);
|
||||
connect(m_contentFilterLine, &LineEdit::textChanged, m_ui->filesList, &TorrentContentWidget::setFilterPattern);
|
||||
m_contentFilterLine->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_contentFilterLine, &QWidget::customContextMenuRequested, this, &PropertiesWidget::showContentFilterContextMenu);
|
||||
connect(m_contentFilterLine, &LineEdit::textChanged, this, &PropertiesWidget::setContentFilterPattern);
|
||||
m_ui->contentFilterLayout->insertWidget(3, m_contentFilterLine);
|
||||
|
||||
m_ui->filesList->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Open);
|
||||
@@ -206,6 +210,7 @@ void PropertiesWidget::clear()
|
||||
m_ui->labelSavePathVal->clear();
|
||||
m_ui->labelCreatedOnVal->clear();
|
||||
m_ui->labelTotalPiecesVal->clear();
|
||||
m_ui->labelPrivateVal->clear();
|
||||
m_ui->labelInfohash1Val->clear();
|
||||
m_ui->labelInfohash2Val->clear();
|
||||
m_ui->labelCommentVal->clear();
|
||||
@@ -220,6 +225,7 @@ void PropertiesWidget::clear()
|
||||
m_ui->labelConnectionsVal->clear();
|
||||
m_ui->labelReannounceInVal->clear();
|
||||
m_ui->labelShareRatioVal->clear();
|
||||
m_ui->labelPopularityVal->clear();
|
||||
m_ui->listWebSeeds->clear();
|
||||
m_ui->labelETAVal->clear();
|
||||
m_ui->labelSeedsVal->clear();
|
||||
@@ -273,6 +279,28 @@ void PropertiesWidget::updateSavePath(BitTorrent::Torrent *const torrent)
|
||||
m_ui->labelSavePathVal->setText(m_torrent->savePath().toString());
|
||||
}
|
||||
|
||||
void PropertiesWidget::showContentFilterContextMenu()
|
||||
{
|
||||
QMenu *menu = m_contentFilterLine->createStandardContextMenu();
|
||||
|
||||
auto *formatMenu = new FilterPatternFormatMenu(m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards), menu);
|
||||
connect(formatMenu, &FilterPatternFormatMenu::patternFormatChanged, this, [this](const FilterPatternFormat format)
|
||||
{
|
||||
m_storeFilterPatternFormat = format;
|
||||
setContentFilterPattern();
|
||||
});
|
||||
|
||||
menu->addSeparator();
|
||||
menu->addMenu(formatMenu);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void PropertiesWidget::setContentFilterPattern()
|
||||
{
|
||||
m_ui->filesList->setFilterPattern(m_contentFilterLine->text(), m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards));
|
||||
}
|
||||
|
||||
void PropertiesWidget::updateTorrentInfos(BitTorrent::Torrent *const torrent)
|
||||
{
|
||||
if (torrent == m_torrent)
|
||||
@@ -308,7 +336,14 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent)
|
||||
m_ui->labelCommentVal->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment().toHtmlEscaped()));
|
||||
|
||||
m_ui->labelCreatedByVal->setText(m_torrent->creator());
|
||||
|
||||
m_ui->labelPrivateVal->setText(m_torrent->isPrivate() ? tr("Yes") : tr("No"));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui->labelPrivateVal->setText(tr("N/A"));
|
||||
}
|
||||
|
||||
// Load dynamic data
|
||||
loadDynamicData();
|
||||
}
|
||||
@@ -407,6 +442,9 @@ void PropertiesWidget::loadDynamicData()
|
||||
const qreal ratio = m_torrent->realRatio();
|
||||
m_ui->labelShareRatioVal->setText(ratio > BitTorrent::Torrent::MAX_RATIO ? C_INFINITY : Utils::String::fromDouble(ratio, 2));
|
||||
|
||||
const qreal popularity = m_torrent->popularity();
|
||||
m_ui->labelPopularityVal->setText(popularity > BitTorrent::Torrent::MAX_RATIO ? C_INFINITY : Utils::String::fromDouble(popularity, 2));
|
||||
|
||||
m_ui->labelSeedsVal->setText(tr("%1 (%2 total)", "%1 and %2 are numbers, e.g. 3 (10 total)")
|
||||
.arg(QString::number(m_torrent->seedsCount())
|
||||
, QString::number(m_torrent->totalSeedsCount())));
|
||||
@@ -437,7 +475,7 @@ void PropertiesWidget::loadDynamicData()
|
||||
|
||||
m_ui->labelTotalPiecesVal->setText(tr("%1 x %2 (have %3)", "(torrent pieces) eg 152 x 4MB (have 25)").arg(m_torrent->piecesCount()).arg(Utils::Misc::friendlyUnit(m_torrent->pieceLength())).arg(m_torrent->piecesHave()));
|
||||
|
||||
if (!m_torrent->isFinished() && !m_torrent->isPaused() && !m_torrent->isQueued() && !m_torrent->isChecking())
|
||||
if (!m_torrent->isFinished() && !m_torrent->isStopped() && !m_torrent->isQueued() && !m_torrent->isChecking())
|
||||
{
|
||||
// Pieces availability
|
||||
showPiecesAvailability(true);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -32,7 +32,8 @@
|
||||
#include <QList>
|
||||
#include <QWidget>
|
||||
|
||||
#include "base/pathfwd.h"
|
||||
#include "base/settingvalue.h"
|
||||
#include "gui/filterpatternformat.h"
|
||||
|
||||
class QPushButton;
|
||||
class QTreeView;
|
||||
@@ -102,6 +103,8 @@ private slots:
|
||||
|
||||
private:
|
||||
QPushButton *getButtonFromIndex(int index);
|
||||
void showContentFilterContextMenu();
|
||||
void setContentFilterPattern();
|
||||
|
||||
Ui::PropertiesWidget *m_ui = nullptr;
|
||||
BitTorrent::Torrent *m_torrent = nullptr;
|
||||
@@ -115,4 +118,6 @@ private:
|
||||
PropTabBar *m_tabBar = nullptr;
|
||||
LineEdit *m_contentFilterLine = nullptr;
|
||||
int m_handleWidth = -1;
|
||||
|
||||
SettingValue<FilterPatternFormat> m_storeFilterPatternFormat;
|
||||
};
|
||||
|
||||
@@ -295,6 +295,25 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="labelPopularity">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Ratio / Time Active (in months), indicates how popular the torrent is</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Popularity:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="5">
|
||||
<widget class="QLabel" name="labelConnectionsVal">
|
||||
<property name="sizePolicy">
|
||||
@@ -495,6 +514,22 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLabel" name="labelPopularityVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Ratio / Time Active (in months), indicates how popular the torrent is</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="labelUploaded">
|
||||
<property name="sizePolicy">
|
||||
@@ -546,7 +581,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string extracomment="Time (duration) the torrent is active (not paused)">Time Active:</string>
|
||||
<string extracomment="Time (duration) the torrent is active (not stopped)">Time Active:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
@@ -788,6 +823,38 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelPrivate">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Private:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelPrivateVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelInfohash1">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
@@ -803,71 +870,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelInfohash2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Info Hash v2:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelInfohash2Val">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="labelSavePath">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Path:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="labelComment">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Comment:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelInfohash1Val">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
@@ -883,7 +886,55 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="labelInfohash2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Info Hash v2:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelInfohash2Val">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="labelSavePath">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Path:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelSavePathVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
@@ -902,7 +953,23 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1" colspan="5">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="labelComment">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Comment:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1" colspan="5">
|
||||
<widget class="QLabel" name="labelCommentVal">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2017-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "articlelistwidget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QListWidgetItem>
|
||||
|
||||
#include "base/global.h"
|
||||
@@ -42,6 +43,12 @@ ArticleListWidget::ArticleListWidget(QWidget *parent)
|
||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
|
||||
checkInvariant();
|
||||
|
||||
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, [this]
|
||||
{
|
||||
for (int row = 0; row < count(); ++row)
|
||||
applyUITheme(item(row));
|
||||
});
|
||||
}
|
||||
|
||||
RSS::Article *ArticleListWidget::getRSSArticle(QListWidgetItem *item) const
|
||||
@@ -100,11 +107,10 @@ void ArticleListWidget::handleArticleAdded(RSS::Article *rssArticle)
|
||||
void ArticleListWidget::handleArticleRead(RSS::Article *rssArticle)
|
||||
{
|
||||
auto *item = mapRSSArticle(rssArticle);
|
||||
if (!item) return;
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.ReadArticle"_s)};
|
||||
item->setData(Qt::ForegroundRole, foregroundBrush);
|
||||
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_s, u"sphere"_s));
|
||||
applyUITheme(item);
|
||||
|
||||
checkInvariant();
|
||||
}
|
||||
@@ -127,18 +133,25 @@ QListWidgetItem *ArticleListWidget::createItem(RSS::Article *article) const
|
||||
|
||||
item->setData(Qt::DisplayRole, article->title());
|
||||
item->setData(Qt::UserRole, QVariant::fromValue(article));
|
||||
if (article->isRead())
|
||||
{
|
||||
const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.ReadArticle"_s)};
|
||||
item->setData(Qt::ForegroundRole, foregroundBrush);
|
||||
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_s, u"sphere"_s));
|
||||
}
|
||||
else
|
||||
{
|
||||
const QBrush foregroundBrush {UIThemeManager::instance()->getColor(u"RSS.UnreadArticle"_s)};
|
||||
item->setData(Qt::ForegroundRole, foregroundBrush);
|
||||
item->setData(Qt::DecorationRole, UIThemeManager::instance()->getIcon(u"loading"_s, u"sphere"_s));
|
||||
}
|
||||
applyUITheme(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void ArticleListWidget::applyUITheme(QListWidgetItem *item) const
|
||||
{
|
||||
const bool isRead = getRSSArticle(item)->isRead();
|
||||
const auto *themeManager = UIThemeManager::instance();
|
||||
if (isRead)
|
||||
{
|
||||
const QColor color = themeManager->getColor(u"RSS.ReadArticle"_s);
|
||||
item->setData(Qt::ForegroundRole, (color.isValid() ? color : QApplication::palette().color(QPalette::Inactive, QPalette::WindowText)));
|
||||
item->setData(Qt::DecorationRole, themeManager->getIcon(u"rss_read_article"_s, u"sphere"_s));
|
||||
}
|
||||
else
|
||||
{
|
||||
const QColor color = themeManager->getColor(u"RSS.UnreadArticle"_s);
|
||||
item->setData(Qt::ForegroundRole, (color.isValid() ? color : QApplication::palette().color(QPalette::Active, QPalette::Link)));
|
||||
item->setData(Qt::DecorationRole, themeManager->getIcon(u"rss_unread_article"_s, u"sphere"_s));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user