mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-18 06:28:03 -06:00
Compare commits
207 Commits
release-5.
...
release-5.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0188e11dd7 | ||
|
|
1dc348539b | ||
|
|
241a0e91bf | ||
|
|
68f7295500 | ||
|
|
53adb7bfa8 | ||
|
|
6128f6eecc | ||
|
|
d156a44f8d | ||
|
|
c3c7f28bad | ||
|
|
9ac14cdf9f | ||
|
|
b899ea8c40 | ||
|
|
0d7c367332 | ||
|
|
22826499d5 | ||
|
|
dbfd830b56 | ||
|
|
ad3348b95f | ||
|
|
44b08fcb74 | ||
|
|
71b752baf3 | ||
|
|
15b6091261 | ||
|
|
abe457389d | ||
|
|
abce4cd1bc | ||
|
|
2bfb336905 | ||
|
|
2dee65fa52 | ||
|
|
423b3ed9bf | ||
|
|
3454f064f0 | ||
|
|
ac9ca4f452 | ||
|
|
09899a7d0d | ||
|
|
9ab3c573dc | ||
|
|
993eb25323 | ||
|
|
1e27e6504e | ||
|
|
330dce6aa2 | ||
|
|
39b965af48 | ||
|
|
5e105b0348 | ||
|
|
f2b2a2b034 | ||
|
|
10499dffe9 | ||
|
|
eea01b94a3 | ||
|
|
374951f6f2 | ||
|
|
6d6f9bc619 | ||
|
|
84ee620fdc | ||
|
|
6079b25419 | ||
|
|
fe24bc825b | ||
|
|
94136262a8 | ||
|
|
f52947e27e | ||
|
|
315e88aee9 | ||
|
|
565c6d843a | ||
|
|
9104351c89 | ||
|
|
e58b0a65d2 | ||
|
|
878d829904 | ||
|
|
063f77bc6c | ||
|
|
2a4077414f | ||
|
|
2a44253802 | ||
|
|
4712eba0dc | ||
|
|
983b7814aa | ||
|
|
e082a21751 | ||
|
|
7dd1d1bac8 | ||
|
|
49f57b1049 | ||
|
|
fbf68a0649 | ||
|
|
39229dc06a | ||
|
|
bb314e1555 | ||
|
|
a3a8b15828 | ||
|
|
b579afe1aa | ||
|
|
93096dba56 | ||
|
|
6379c33964 | ||
|
|
84372de675 | ||
|
|
403b7c7c35 | ||
|
|
b2fab43865 | ||
|
|
387821267f | ||
|
|
dd7ef8e934 | ||
|
|
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.3"
|
||||
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)
|
||||
|
||||
89
Changelog
89
Changelog
@@ -1,4 +1,50 @@
|
||||
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
||||
Mon Dec 16th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.3
|
||||
- BUGFIX: Discard obsolete "state update" events after torrent is reloaded (glassez)
|
||||
- BUGFIX: Fix incorrect SQL column definition (glassez)
|
||||
- BUGFIX: Avoid redundant requests of announce entries from libtorrent (glassez)
|
||||
- WEBUI: Fix removing tracker URL with '|' character (Thomas Piccirello)
|
||||
- WEBUI: Fix reloading page after login (Evgenii Ryshkov)
|
||||
- WEBAPI: Fix incorrect key in torrent creator (Bartu Özen)
|
||||
- RSS: Don't add duplicate episodes to previously matched (wavygecko)
|
||||
- RSS: Use cached current time when parsing RSS feed (glassez)
|
||||
- WINDOWS: Don't follow symlink when creating torrents on Windows (Chocobo1)
|
||||
- WINDOWS: NSIS: Update Italian translation (Giacomo411)
|
||||
|
||||
Sun Nov 17th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.2
|
||||
- BUGFIX: Remove trackers from previous category when moved to new one (glassez)
|
||||
- BUGFIX: Fix `.torrent` file could not be deleted when torrent is canceled (glassez)
|
||||
- BUGFIX: Reset tracker entries when pausing the session (glassez)
|
||||
- BUGFIX: Check real palette darkness to detect "dark theme" (glassez)
|
||||
- BUGFIX: Correctly handle "torrent finished" events (glassez)
|
||||
- BUGFIX: Preserve initial torrent progress while checking resume data (glassez)
|
||||
- BUGFIX: Avoid reapplying Mark-of-the-Web when it already exists (Chocobo1)
|
||||
- BUGFIX: Don't apply Mark-of-the-Web on existing files (Chocobo1)
|
||||
- WEBUI: Add color scheme switcher (sledgehammer999)
|
||||
- SEARCH: Correctly delete the moved search tab (glassez)
|
||||
- WINDOWS: Correctly save and restore Qt style setting (glassez)
|
||||
- WINDOWS: NSIS: update Luxembourgish, Simplified Chinese and Traditional Chinese translations (Ikko Eltociear Ashimine, 3gf8jv4dv)
|
||||
|
||||
Mon Oct 28th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.1
|
||||
- FEATURE: Add "Simple pread/pwrite" disk IO type (Hanabishi)
|
||||
- BUGFIX: Don't ignore SSL errors (sledgehammer999)
|
||||
- BUGFIX: Don't try to apply Mark-of-the-Web to nonexistent files (glassez)
|
||||
- BUGFIX: Disable "Move to trash" option by default (glassez)
|
||||
- BUGFIX: Disable the ability to create torrents with a piece size of 256MiB (stalkerok)
|
||||
- BUGFIX: Allow to choose Qt style (glassez)
|
||||
- BUGFIX: Always notify user about duplicate torrent (glassez)
|
||||
- BUGFIX: Correctly handle "torrent finished after move" event (glassez)
|
||||
- BUGFIX: Correctly apply filename filter when `!qB` extension is enabled (glassez)
|
||||
- BUGFIX: Improve color scheme change detection (glassez)
|
||||
- BUGFIX: Fix button state for SSL certificate check (Chocobo1)
|
||||
- WEBUI: Fix CSS that results in hidden torrent list in some browsers (skomerko)
|
||||
- WEBUI: Use proper text color to highlight items in all filter lists (skomerko)
|
||||
- WEBUI: Fix 'rename files' dialog cannot be opened more than once (Chocobo1)
|
||||
- WEBUI: Fix UI of Advanced Settings to show all settings (glassez)
|
||||
- WEBUI: Free resources allocated by web session once it is destructed (dyseg)
|
||||
- SEARCH: Import correct libraries (Chocobo1)
|
||||
- OTHER: Sync flag icons with upstream (xavier2k6)
|
||||
|
||||
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 +58,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 +90,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/mac/Info.plist
vendored
2
dist/mac/Info.plist
vendored
@@ -55,7 +55,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.0.0</string>
|
||||
<string>5.0.3</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
146
dist/unix/org.qbittorrent.qBittorrent.desktop
vendored
146
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]=โปรแกรมบิททอเร้นท์
|
||||
GenericName[th]=ไคลเอนต์บิททอร์เรนต์
|
||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่านบิตทอร์เรนต์
|
||||
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.3" date="2024-12-16"/>
|
||||
</releases>
|
||||
</component>
|
||||
2
dist/windows/config.nsh
vendored
2
dist/windows/config.nsh
vendored
@@ -14,7 +14,7 @@
|
||||
; 4.5.1.3 -> good
|
||||
; 4.5.1.3.2 -> bad
|
||||
; 4.5.0beta -> bad
|
||||
!define /ifndef QBT_VERSION "5.0.0"
|
||||
!define /ifndef QBT_VERSION "5.0.3"
|
||||
|
||||
; Option that controls the installer's window name
|
||||
; If set, its value will be used like this:
|
||||
|
||||
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())
|
||||
|
||||
@@ -29,7 +29,7 @@ LangString launch_qbt ${LANG_ITALIAN} "Esegui qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_ITALIAN} "Questo installer funziona solo con versioni di Windows a 64bit."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_ITALIAN} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_ITALIAN} "Questo installer richiede almeno Windows 10 (1809) / Windows Server 2019."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_ITALIAN} "Disinstalla qBittorrent"
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ LangString inst_magnet ${LANG_LUXEMBOURGISH} "Magnet-Linken mat qBittorrent opma
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_LUXEMBOURGISH} "Reegel an der Windows Firewall dobäisetzen"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_LUXEMBOURGISH} "D'Windows path lenght (Padlängtbeschränkung) desaktivéieren (260 Zeechen MAX_PATH Beschränkung, erfuerdert min. Windows 10 1607)"
|
||||
LangString inst_pathlimit ${LANG_LUXEMBOURGISH} "D'Windows path length (Padlängtbeschränkung) desaktivéieren (260 Zeechen MAX_PATH Beschränkung, erfuerdert min. Windows 10 1607)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_LUXEMBOURGISH} "Reegel an der Windows Firewall dobäisetzen"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
|
||||
@@ -23,13 +23,13 @@ LangString inst_warning ${LANG_SIMPCHINESE} "qBittorrent 正在运行。 安装
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_SIMPCHINESE} "当前版本会被卸载。 用户设置和种子会被完整保留。"
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_SIMPCHINESE} "卸载以前的版本。"
|
||||
LangString inst_unist ${LANG_SIMPCHINESE} "正在卸载以前的版本。"
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_SIMPCHINESE} "启动 qBittorrent。"
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_SIMPCHINESE} "此安装程序仅支持 64 位 Windows 系统。"
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_SIMPCHINESE} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_SIMPCHINESE} "此安装程序仅支持 Windows 10 (1809) / Windows Server 2019 或更新的系统。"
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_SIMPCHINESE} "卸载 qBittorrent"
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ LangString launch_qbt ${LANG_TRADCHINESE} "啟動 qBittorrent"
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_TRADCHINESE} "此安裝程式僅支援 64 位元版本的 Windows。"
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_TRADCHINESE} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_TRADCHINESE} "此安裝程式僅支援 Windows 10 (1809) / Windows Server 2019 以上的系統。"
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_TRADCHINESE} "移除 qBittorrent"
|
||||
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014-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
|
||||
@@ -58,10 +58,6 @@
|
||||
#include <QSplashScreen>
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QOperatingSystemVersion>
|
||||
#endif
|
||||
|
||||
#ifdef QBT_STATIC_QT
|
||||
#include <QtPlugin>
|
||||
Q_IMPORT_PLUGIN(QICOPlugin)
|
||||
@@ -189,11 +185,6 @@ int main(int argc, char *argv[])
|
||||
// We must save it here because QApplication constructor may change it
|
||||
const bool isOneArg = (argc == 2);
|
||||
|
||||
#if !defined(DISABLE_GUI) && defined(Q_OS_WIN)
|
||||
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10)
|
||||
QApplication::setStyle(u"Fusion"_s);
|
||||
#endif
|
||||
|
||||
// `app` must be declared out of try block to allow display message box in case of exception
|
||||
std::unique_ptr<Application> app;
|
||||
try
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -157,10 +157,36 @@ void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QStr
|
||||
emit addTorrentFailed(source, reason);
|
||||
}
|
||||
|
||||
void AddTorrentManager::handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message)
|
||||
void AddTorrentManager::handleDuplicateTorrent(const QString &source
|
||||
, const BitTorrent::TorrentDescriptor &torrentDescr, BitTorrent::Torrent *existingTorrent)
|
||||
{
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
if (hasMetadata)
|
||||
{
|
||||
// Trying to set metadata to existing torrent in case if it has none
|
||||
existingTorrent->setMetadata(*torrentDescr.info());
|
||||
}
|
||||
|
||||
const bool isPrivate = existingTorrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
||||
QString message;
|
||||
if (!btSession()->isMergeTrackersEnabled())
|
||||
{
|
||||
message = tr("Merging of trackers is disabled");
|
||||
}
|
||||
else if (isPrivate)
|
||||
{
|
||||
message = tr("Trackers cannot be merged because it is a private torrent");
|
||||
}
|
||||
else
|
||||
{
|
||||
// merge trackers and web seeds
|
||||
existingTorrent->addTrackers(torrentDescr.trackers());
|
||||
existingTorrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||
message = tr("Trackers are merged from new source");
|
||||
}
|
||||
|
||||
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
|
||||
.arg(source, torrent->name(), message));
|
||||
.arg(source, existingTorrent->name(), message));
|
||||
emit addTorrentFailed(source, message);
|
||||
}
|
||||
|
||||
@@ -169,11 +195,9 @@ void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_p
|
||||
m_guardedTorrentFiles.emplace(source, std::move(torrentFileGuard));
|
||||
}
|
||||
|
||||
void AddTorrentManager::releaseTorrentFileGuard(const QString &source)
|
||||
std::shared_ptr<TorrentFileGuard> AddTorrentManager::releaseTorrentFileGuard(const QString &source)
|
||||
{
|
||||
auto torrentFileGuard = m_guardedTorrentFiles.take(source);
|
||||
if (torrentFileGuard)
|
||||
torrentFileGuard->setAutoRemove(false);
|
||||
return m_guardedTorrentFiles.take(source);
|
||||
}
|
||||
|
||||
bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||
@@ -184,32 +208,7 @@ bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::
|
||||
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
||||
{
|
||||
// a duplicate torrent is being added
|
||||
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
if (hasMetadata)
|
||||
{
|
||||
// Trying to set metadata to existing torrent in case if it has none
|
||||
torrent->setMetadata(*torrentDescr.info());
|
||||
}
|
||||
|
||||
if (!btSession()->isMergeTrackersEnabled())
|
||||
{
|
||||
handleDuplicateTorrent(source, torrent, tr("Merging of trackers is disabled"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool isPrivate = torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
||||
if (isPrivate)
|
||||
{
|
||||
handleDuplicateTorrent(source, torrent, tr("Trackers cannot be merged because it is a private torrent"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// merge trackers and web seeds
|
||||
torrent->addTrackers(torrentDescr.trackers());
|
||||
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||
|
||||
handleDuplicateTorrent(source, torrent, tr("Trackers are merged from new source"));
|
||||
handleDuplicateTorrent(source, torrentDescr, torrent);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,9 +72,9 @@ protected:
|
||||
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||
, const BitTorrent::AddTorrentParams &addTorrentParams);
|
||||
void handleAddTorrentFailed(const QString &source, const QString &reason);
|
||||
void handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message);
|
||||
void handleDuplicateTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr, BitTorrent::Torrent *existingTorrent);
|
||||
void setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard);
|
||||
void releaseTorrentFileGuard(const QString &source);
|
||||
std::shared_ptr<TorrentFileGuard> releaseTorrentFileGuard(const QString &source);
|
||||
|
||||
private:
|
||||
void onDownloadFinished(const Net::DownloadResult &result);
|
||||
|
||||
@@ -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}
|
||||
{
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace
|
||||
{
|
||||
const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_s;
|
||||
|
||||
const int DB_VERSION = 7;
|
||||
const int DB_VERSION = 8;
|
||||
|
||||
const QString DB_TABLE_META = u"meta"_s;
|
||||
const QString DB_TABLE_TORRENTS = u"torrents"_s;
|
||||
@@ -628,7 +628,31 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
|
||||
}
|
||||
|
||||
if (fromVersion <= 6)
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SHARE_LIMIT_ACTION, "TEXTNOT NULL DEFAULT `Default`");
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SHARE_LIMIT_ACTION, "TEXT NOT NULL DEFAULT `Default`");
|
||||
|
||||
if (fromVersion == 7)
|
||||
{
|
||||
const QString TEMP_COLUMN_NAME = DB_COLUMN_SHARE_LIMIT_ACTION.name + u"_temp";
|
||||
|
||||
auto queryStr = u"ALTER TABLE %1 ADD %2 %3"_s
|
||||
.arg(quoted(DB_TABLE_TORRENTS), TEMP_COLUMN_NAME, u"TEXT NOT NULL DEFAULT `Default`");
|
||||
if (!query.exec(queryStr))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
queryStr = u"UPDATE %1 SET %2 = %3"_s
|
||||
.arg(quoted(DB_TABLE_TORRENTS), quoted(TEMP_COLUMN_NAME), quoted(DB_COLUMN_SHARE_LIMIT_ACTION.name));
|
||||
if (!query.exec(queryStr))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
queryStr = u"ALTER TABLE %1 DROP %2"_s.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_SHARE_LIMIT_ACTION.name));
|
||||
if (!query.exec(queryStr))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
queryStr = u"ALTER TABLE %1 RENAME %2 TO %3"_s
|
||||
.arg(quoted(DB_TABLE_TORRENTS), quoted(TEMP_COLUMN_NAME), quoted(DB_COLUMN_SHARE_LIMIT_ACTION.name));
|
||||
if (!query.exec(queryStr))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
|
||||
const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE});
|
||||
if (!query.prepare(updateMetaVersionQuery))
|
||||
|
||||
@@ -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
|
||||
@@ -90,7 +92,8 @@ namespace BitTorrent
|
||||
{
|
||||
Default = 0,
|
||||
MMap = 1,
|
||||
Posix = 2
|
||||
Posix = 2,
|
||||
SimplePreadPwrite = 3
|
||||
};
|
||||
Q_ENUM_NS(DiskIOType)
|
||||
|
||||
@@ -213,8 +216,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 +258,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 +427,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 +457,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 +478,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 +491,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 +505,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
@@ -41,6 +41,7 @@
|
||||
#include <QElapsedTimer>
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
@@ -54,7 +55,7 @@
|
||||
#include "session.h"
|
||||
#include "sessionstatus.h"
|
||||
#include "torrentinfo.h"
|
||||
#include "trackerentry.h"
|
||||
#include "trackerentrystatus.h"
|
||||
|
||||
class QString;
|
||||
class QThread;
|
||||
@@ -69,16 +70,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 +193,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 +235,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 +404,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 +434,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 +453,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);
|
||||
@@ -462,6 +476,8 @@ namespace BitTorrent
|
||||
void addMappedPorts(const QSet<quint16> &ports);
|
||||
void removeMappedPorts(const QSet<quint16> &ports);
|
||||
|
||||
QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint) const;
|
||||
|
||||
template <typename Func>
|
||||
void invoke(Func &&func)
|
||||
{
|
||||
@@ -477,11 +493,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 +513,9 @@ namespace BitTorrent
|
||||
struct RemovingTorrentData
|
||||
{
|
||||
QString name;
|
||||
Path pathToRemove;
|
||||
DeleteOption deleteOption {};
|
||||
Path contentStoragePath;
|
||||
PathList fileNames;
|
||||
TorrentRemoveOption removeOption {};
|
||||
};
|
||||
|
||||
explicit SessionImpl(QObject *parent = nullptr);
|
||||
@@ -525,7 +542,7 @@ namespace BitTorrent
|
||||
void populateAdditionalTrackers();
|
||||
void enableIPFilter();
|
||||
void disableIPFilter();
|
||||
void processTrackerStatuses();
|
||||
void processTorrentShareLimits(TorrentImpl *torrent);
|
||||
void populateExcludedFileNamesRegExpList();
|
||||
void prepareStartup();
|
||||
void handleLoadedResumeData(ResumeSessionContext *context);
|
||||
@@ -538,34 +555,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);
|
||||
@@ -578,6 +595,7 @@ namespace BitTorrent
|
||||
|
||||
void moveTorrentStorage(const MoveStorageJob &job) const;
|
||||
void handleMoveTorrentStorageJobFinished(const Path &newPath);
|
||||
void processPendingFinishedTorrents();
|
||||
|
||||
void loadCategories();
|
||||
void storeCategories() const;
|
||||
@@ -587,15 +605,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);
|
||||
|
||||
// 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 +675,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 +692,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 +733,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 +752,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 +776,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 +788,13 @@ 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;
|
||||
QMutex m_updatedTrackerStatusesMutex;
|
||||
|
||||
// I/O errored torrents
|
||||
QSet<TorrentID> m_recentErroredTorrents;
|
||||
@@ -793,6 +821,11 @@ namespace BitTorrent
|
||||
QTimer *m_wakeupCheckTimer = nullptr;
|
||||
QDateTime m_wakeupCheckTimestamp;
|
||||
|
||||
QList<TorrentImpl *> m_pendingFinishedTorrents;
|
||||
|
||||
QDateTime m_qNow;
|
||||
lt::clock_type::time_point m_ltNow;
|
||||
|
||||
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,
|
||||
|
||||
@@ -212,7 +215,15 @@ namespace BitTorrent
|
||||
virtual int piecesCount() const = 0;
|
||||
virtual int piecesHave() const = 0;
|
||||
virtual qreal progress() const = 0;
|
||||
|
||||
virtual QDateTime addedTime() const = 0;
|
||||
virtual QDateTime completedTime() const = 0;
|
||||
virtual QDateTime lastSeenComplete() const = 0;
|
||||
virtual qlonglong activeTime() const = 0;
|
||||
virtual qlonglong finishedTime() const = 0;
|
||||
virtual qlonglong timeSinceUpload() const = 0;
|
||||
virtual qlonglong timeSinceDownload() const = 0;
|
||||
virtual qlonglong timeSinceActivity() const = 0;
|
||||
|
||||
// Share limits
|
||||
virtual qreal ratioLimit() const = 0;
|
||||
@@ -225,10 +236,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,13 +257,11 @@ 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;
|
||||
virtual qlonglong totalUpload() const = 0;
|
||||
virtual qlonglong activeTime() const = 0;
|
||||
virtual qlonglong finishedTime() const = 0;
|
||||
virtual qlonglong eta() const = 0;
|
||||
virtual int seedsCount() const = 0;
|
||||
virtual int peersCount() const = 0;
|
||||
@@ -259,11 +269,6 @@ namespace BitTorrent
|
||||
virtual int totalSeedsCount() const = 0;
|
||||
virtual int totalPeersCount() const = 0;
|
||||
virtual int totalLeechersCount() const = 0;
|
||||
virtual QDateTime lastSeenComplete() const = 0;
|
||||
virtual QDateTime completedTime() const = 0;
|
||||
virtual qlonglong timeSinceUpload() const = 0;
|
||||
virtual qlonglong timeSinceDownload() const = 0;
|
||||
virtual qlonglong timeSinceActivity() const = 0;
|
||||
virtual int downloadLimit() const = 0;
|
||||
virtual int uploadLimit() const = 0;
|
||||
virtual bool superSeeding() const = 0;
|
||||
@@ -279,6 +284,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 +296,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 +331,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);
|
||||
};
|
||||
}
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <libtorrent/file_storage.hpp>
|
||||
#include <libtorrent/torrent_info.hpp>
|
||||
|
||||
#include <QtSystemDetection>
|
||||
#include <QDirIterator>
|
||||
#include <QFileInfo>
|
||||
#include <QHash>
|
||||
@@ -123,7 +124,14 @@ void TorrentCreator::run()
|
||||
// need to sort the file names by natural sort order
|
||||
QStringList dirs = {m_params.sourcePath.data()};
|
||||
|
||||
QDirIterator dirIter {m_params.sourcePath.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
|
||||
#ifdef Q_OS_WIN
|
||||
// libtorrent couldn't handle .lnk files on Windows
|
||||
// Also, Windows users do not expect torrent creator to traverse into .lnk files so skip over them
|
||||
const QDir::Filters dirFilters {QDir::AllDirs | QDir::NoDotAndDotDot | QDir::NoSymLinks};
|
||||
#else
|
||||
const QDir::Filters dirFilters {QDir::AllDirs | QDir::NoDotAndDotDot};
|
||||
#endif
|
||||
QDirIterator dirIter {m_params.sourcePath.data(), dirFilters, QDirIterator::Subdirectories};
|
||||
while (dirIter.hasNext())
|
||||
{
|
||||
const QString filePath = dirIter.next();
|
||||
@@ -138,7 +146,12 @@ void TorrentCreator::run()
|
||||
{
|
||||
QStringList tmpNames; // natural sort files within each dir
|
||||
|
||||
QDirIterator fileIter {dir, QDir::Files};
|
||||
#ifdef Q_OS_WIN
|
||||
const QDir::Filters fileFilters {QDir::Files | QDir::NoSymLinks};
|
||||
#else
|
||||
const QDir::Filters fileFilters {QDir::Files};
|
||||
#endif
|
||||
QDirIterator fileIter {dir, fileFilters};
|
||||
while (fileIter.hasNext())
|
||||
{
|
||||
const QFileInfo fileInfo = fileIter.nextFileInfo();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-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
|
||||
@@ -35,9 +35,7 @@
|
||||
#include <libtorrent/write_resume_data.hpp>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/global.h"
|
||||
@@ -147,7 +145,13 @@ BitTorrent::TorrentDescriptor::TorrentDescriptor(lt::add_torrent_params ltAddTor
|
||||
: m_ltAddTorrentParams {std::move(ltAddTorrentParams)}
|
||||
{
|
||||
if (m_ltAddTorrentParams.ti && m_ltAddTorrentParams.ti->is_valid())
|
||||
{
|
||||
m_info.emplace(*m_ltAddTorrentParams.ti);
|
||||
if (m_ltAddTorrentParams.ti->creation_date() > 0)
|
||||
m_creationDate = QDateTime::fromSecsSinceEpoch(m_ltAddTorrentParams.ti->creation_date());
|
||||
m_creator = QString::fromStdString(m_ltAddTorrentParams.ti->creator());
|
||||
m_comment = QString::fromStdString(m_ltAddTorrentParams.ti->comment());
|
||||
}
|
||||
}
|
||||
|
||||
BitTorrent::InfoHash BitTorrent::TorrentDescriptor::infoHash() const
|
||||
@@ -166,18 +170,17 @@ QString BitTorrent::TorrentDescriptor::name() const
|
||||
|
||||
QDateTime BitTorrent::TorrentDescriptor::creationDate() const
|
||||
{
|
||||
return ((m_ltAddTorrentParams.ti->creation_date() != 0)
|
||||
? QDateTime::fromSecsSinceEpoch(m_ltAddTorrentParams.ti->creation_date()) : QDateTime());
|
||||
return m_creationDate;
|
||||
}
|
||||
|
||||
QString BitTorrent::TorrentDescriptor::creator() const
|
||||
{
|
||||
return QString::fromStdString(m_ltAddTorrentParams.ti->creator());
|
||||
return m_creator;
|
||||
}
|
||||
|
||||
QString BitTorrent::TorrentDescriptor::comment() const
|
||||
{
|
||||
return QString::fromStdString(m_ltAddTorrentParams.ti->comment());
|
||||
return m_comment;
|
||||
}
|
||||
|
||||
const std::optional<BitTorrent::TorrentInfo> &BitTorrent::TorrentDescriptor::info() const
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
#include <libtorrent/add_torrent_params.hpp>
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QDateTime>
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
|
||||
#include "base/3rdparty/expected.hpp"
|
||||
#include "base/path.h"
|
||||
@@ -41,8 +43,6 @@
|
||||
#include "torrentinfo.h"
|
||||
|
||||
class QByteArray;
|
||||
class QDateTime;
|
||||
class QString;
|
||||
class QUrl;
|
||||
|
||||
namespace BitTorrent
|
||||
@@ -78,6 +78,9 @@ namespace BitTorrent
|
||||
|
||||
lt::add_torrent_params m_ltAddTorrentParams;
|
||||
std::optional<TorrentInfo> m_info;
|
||||
QDateTime m_creationDate;
|
||||
QString m_creator;
|
||||
QString m_comment;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -55,7 +55,7 @@
|
||||
#include "torrent.h"
|
||||
#include "torrentcontentlayout.h"
|
||||
#include "torrentinfo.h"
|
||||
#include "trackerentry.h"
|
||||
#include "trackerentrystatus.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
@@ -138,7 +138,15 @@ namespace BitTorrent
|
||||
int piecesCount() const override;
|
||||
int piecesHave() const override;
|
||||
qreal progress() const override;
|
||||
|
||||
QDateTime addedTime() const override;
|
||||
QDateTime completedTime() const override;
|
||||
QDateTime lastSeenComplete() const override;
|
||||
qlonglong activeTime() const override;
|
||||
qlonglong finishedTime() const override;
|
||||
qlonglong timeSinceUpload() const override;
|
||||
qlonglong timeSinceDownload() const override;
|
||||
qlonglong timeSinceActivity() const override;
|
||||
|
||||
qreal ratioLimit() const override;
|
||||
void setRatioLimit(qreal limit) override;
|
||||
@@ -153,11 +161,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,13 +184,11 @@ 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;
|
||||
qlonglong totalUpload() const override;
|
||||
qlonglong activeTime() const override;
|
||||
qlonglong finishedTime() const override;
|
||||
qlonglong eta() const override;
|
||||
QVector<qreal> filesProgress() const override;
|
||||
int seedsCount() const override;
|
||||
@@ -190,11 +197,6 @@ namespace BitTorrent
|
||||
int totalSeedsCount() const override;
|
||||
int totalPeersCount() const override;
|
||||
int totalLeechersCount() const override;
|
||||
QDateTime lastSeenComplete() const override;
|
||||
QDateTime completedTime() const override;
|
||||
qlonglong timeSinceUpload() const override;
|
||||
qlonglong timeSinceDownload() const override;
|
||||
qlonglong timeSinceActivity() const override;
|
||||
int downloadLimit() const override;
|
||||
int uploadLimit() const override;
|
||||
bool superSeeding() const override;
|
||||
@@ -210,6 +212,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 +225,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 +271,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 +279,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 ()>;
|
||||
@@ -339,6 +343,14 @@ namespace BitTorrent
|
||||
|
||||
InfoHash m_infoHash;
|
||||
|
||||
QDateTime m_creationDate;
|
||||
QString m_creator;
|
||||
QString m_comment;
|
||||
|
||||
QDateTime m_addedTime;
|
||||
QDateTime m_completedTime;
|
||||
QDateTime m_lastSeenComplete;
|
||||
|
||||
// m_moveFinishedTriggers is activated only when the following conditions are met:
|
||||
// all file rename jobs complete, all file move jobs complete
|
||||
QQueue<EventTrigger> m_moveFinishedTriggers;
|
||||
@@ -349,7 +361,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
|
||||
@@ -128,10 +148,20 @@ Net::DownloadManager::DownloadManager(QObject *parent)
|
||||
QStringList errorList;
|
||||
for (const QSslError &error : errors)
|
||||
errorList += error.errorString();
|
||||
LogMsg(tr("Ignoring SSL error, URL: \"%1\", errors: \"%2\"").arg(reply->url().toString(), errorList.join(u". ")), Log::WARNING);
|
||||
|
||||
// Ignore all SSL errors
|
||||
reply->ignoreSslErrors();
|
||||
QString errorMsg;
|
||||
if (!Preferences::instance()->isIgnoreSSLErrors())
|
||||
{
|
||||
errorMsg = tr("SSL error, URL: \"%1\", errors: \"%2\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMsg = tr("Ignoring SSL error, URL: \"%1\", errors: \"%2\"");
|
||||
// Ignore all SSL errors
|
||||
reply->ignoreSslErrors();
|
||||
}
|
||||
|
||||
LogMsg(errorMsg.arg(reply->url().toString(), errorList.join(u". ")), Log::WARNING);
|
||||
});
|
||||
|
||||
connect(ProxyConfigurationManager::instance(), &ProxyConfigurationManager::proxyConfigurationChanged
|
||||
@@ -292,7 +322,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
|
||||
@@ -429,6 +429,19 @@ void Preferences::setWinStartup(const bool b)
|
||||
settings.remove(profileID);
|
||||
}
|
||||
}
|
||||
|
||||
QString Preferences::getStyle() const
|
||||
{
|
||||
return value<QString>(u"Appearance/Style"_s);
|
||||
}
|
||||
|
||||
void Preferences::setStyle(const QString &styleName)
|
||||
{
|
||||
if (styleName == getStyle())
|
||||
return;
|
||||
|
||||
setValue(u"Appearance/Style"_s, styleName);
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
// Downloads
|
||||
@@ -1330,6 +1343,19 @@ void Preferences::setMarkOfTheWebEnabled(const bool enabled)
|
||||
setValue(u"Preferences/Advanced/markOfTheWeb"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isIgnoreSSLErrors() const
|
||||
{
|
||||
return value(u"Preferences/Advanced/IgnoreSSLErrors"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setIgnoreSSLErrors(const bool enabled)
|
||||
{
|
||||
if (enabled == isIgnoreSSLErrors())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/Advanced/IgnoreSSLErrors"_s, enabled);
|
||||
}
|
||||
|
||||
Path Preferences::getPythonExecutablePath() const
|
||||
{
|
||||
return value(u"Preferences/Search/pythonExecutablePath"_s, Path());
|
||||
@@ -1397,19 +1423,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;
|
||||
@@ -130,6 +130,8 @@ public:
|
||||
#ifdef Q_OS_WIN
|
||||
bool WinStartup() const;
|
||||
void setWinStartup(bool b);
|
||||
QString getStyle() const;
|
||||
void setStyle(const QString &styleName);
|
||||
#endif
|
||||
|
||||
// Downloads
|
||||
@@ -293,6 +295,8 @@ public:
|
||||
void setTrackerPortForwardingEnabled(bool enabled);
|
||||
bool isMarkOfTheWebEnabled() const;
|
||||
void setMarkOfTheWebEnabled(bool enabled);
|
||||
bool isIgnoreSSLErrors() const;
|
||||
void setIgnoreSSLErrors(bool enabled);
|
||||
Path getPythonExecutablePath() const;
|
||||
void setPythonExecutablePath(const Path &path);
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
@@ -305,8 +309,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;
|
||||
|
||||
@@ -396,6 +396,8 @@ bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString &articleTitle) co
|
||||
m_dataPtr->lastComputedEpisodes.append(episodeStr + u"-REPACK");
|
||||
m_dataPtr->lastComputedEpisodes.append(episodeStr + u"-PROPER");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
m_dataPtr->lastComputedEpisodes.append(episodeStr);
|
||||
@@ -468,7 +470,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 +527,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 +570,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 +581,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) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-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,11 +29,8 @@
|
||||
|
||||
#include "rss_parser.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QGlobalStatic>
|
||||
#include <QHash>
|
||||
#include <QMetaObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
@@ -359,7 +356,7 @@ namespace
|
||||
};
|
||||
|
||||
// Ported to Qt from KDElibs4
|
||||
QDateTime parseDate(const QString &string)
|
||||
QDateTime parseDate(const QString &string, const QDateTime &fallbackDate)
|
||||
{
|
||||
const char16_t shortDay[][4] =
|
||||
{
|
||||
@@ -382,7 +379,7 @@ namespace
|
||||
|
||||
const QString str = string.trimmed();
|
||||
if (str.isEmpty())
|
||||
return QDateTime::currentDateTime();
|
||||
return fallbackDate;
|
||||
|
||||
int nyear = 6; // indexes within string to values
|
||||
int nmonth = 4;
|
||||
@@ -402,14 +399,14 @@ namespace
|
||||
const bool h1 = (parts[3] == u"-");
|
||||
const bool h2 = (parts[5] == u"-");
|
||||
if (h1 != h2)
|
||||
return QDateTime::currentDateTime();
|
||||
return fallbackDate;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
|
||||
rx = QRegularExpression {u"^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$"_s};
|
||||
if (str.indexOf(rx, 0, &rxMatch) != 0)
|
||||
return QDateTime::currentDateTime();
|
||||
return fallbackDate;
|
||||
|
||||
nyear = 7;
|
||||
nmonth = 2;
|
||||
@@ -427,14 +424,14 @@ namespace
|
||||
const int hour = parts[nhour].toInt(&ok[2]);
|
||||
const int minute = parts[nmin].toInt(&ok[3]);
|
||||
if (!ok[0] || !ok[1] || !ok[2] || !ok[3])
|
||||
return QDateTime::currentDateTime();
|
||||
return fallbackDate;
|
||||
|
||||
int second = 0;
|
||||
if (!parts[nsec].isEmpty())
|
||||
{
|
||||
second = parts[nsec].toInt(&ok[0]);
|
||||
if (!ok[0])
|
||||
return QDateTime::currentDateTime();
|
||||
return fallbackDate;
|
||||
}
|
||||
|
||||
const bool leapSecond = (second == 60);
|
||||
@@ -518,21 +515,21 @@ namespace
|
||||
|
||||
const QDate qDate(year, month + 1, day); // convert date, and check for out-of-range
|
||||
if (!qDate.isValid())
|
||||
return QDateTime::currentDateTime();
|
||||
return fallbackDate;
|
||||
|
||||
const QTime qTime(hour, minute, second);
|
||||
QDateTime result(qDate, qTime, Qt::UTC);
|
||||
if (offset)
|
||||
result = result.addSecs(-offset);
|
||||
if (!result.isValid())
|
||||
return QDateTime::currentDateTime(); // invalid date/time
|
||||
return fallbackDate; // invalid date/time
|
||||
|
||||
if (leapSecond)
|
||||
{
|
||||
// Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
|
||||
// Convert the time to UTC and check that it is 00:00:00.
|
||||
if ((hour*3600 + minute*60 + 60 - offset + 86400*5) % 86400) // (max abs(offset) is 100 hours)
|
||||
return QDateTime::currentDateTime(); // the time isn't the last second of the day
|
||||
return fallbackDate; // the time isn't the last second of the day
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -550,6 +547,7 @@ RSS::Private::Parser::Parser(const QString &lastBuildDate)
|
||||
void RSS::Private::Parser::parse(const QByteArray &feedData)
|
||||
{
|
||||
QXmlStreamReader xml {feedData};
|
||||
m_fallbackDate = QDateTime::currentDateTime();
|
||||
XmlStreamEntityResolver resolver;
|
||||
xml.setEntityResolver(&resolver);
|
||||
bool foundChannel = false;
|
||||
@@ -641,7 +639,7 @@ void RSS::Private::Parser::parseRssArticle(QXmlStreamReader &xml)
|
||||
}
|
||||
else if (name == u"pubDate")
|
||||
{
|
||||
article[Article::KeyDate] = parseDate(xml.readElementText().trimmed());
|
||||
article[Article::KeyDate] = parseDate(xml.readElementText().trimmed(), m_fallbackDate);
|
||||
}
|
||||
else if (name == u"author")
|
||||
{
|
||||
@@ -755,7 +753,7 @@ void RSS::Private::Parser::parseAtomArticle(QXmlStreamReader &xml)
|
||||
{
|
||||
// ATOM uses standard compliant date, don't do fancy stuff
|
||||
const QDateTime articleDate = QDateTime::fromString(xml.readElementText().trimmed(), Qt::ISODate);
|
||||
article[Article::KeyDate] = (articleDate.isValid() ? articleDate : QDateTime::currentDateTime());
|
||||
article[Article::KeyDate] = (articleDate.isValid() ? articleDate : m_fallbackDate);
|
||||
}
|
||||
else if (name == u"author")
|
||||
{
|
||||
|
||||
@@ -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) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
@@ -66,6 +67,7 @@ namespace RSS::Private
|
||||
void parseAtomChannel(QXmlStreamReader &xml);
|
||||
void addArticle(QVariantHash article);
|
||||
|
||||
QDateTime m_fallbackDate;
|
||||
QString m_baseUrl;
|
||||
ParsingResult m_result;
|
||||
QSet<QString> m_articleIDs;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -31,6 +31,8 @@
|
||||
#include "os.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <algorithm>
|
||||
|
||||
#include <windows.h>
|
||||
#include <powrprof.h>
|
||||
#include <shlobj.h>
|
||||
@@ -42,6 +44,8 @@
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#endif // Q_OS_MACOS
|
||||
|
||||
#include <QScopeGuard>
|
||||
|
||||
#ifdef QBT_USES_DBUS
|
||||
#include <QDBusInterface>
|
||||
#endif // QBT_USES_DBUS
|
||||
@@ -271,6 +275,11 @@ Path Utils::OS::windowsSystemPath()
|
||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||
bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
|
||||
{
|
||||
// Trying to apply this to a non-existent file is unacceptable,
|
||||
// as it may unexpectedly create such a file.
|
||||
if (!file.exists())
|
||||
return false;
|
||||
|
||||
Q_ASSERT(url.isEmpty() || url.startsWith(u"http:") || url.startsWith(u"https:"));
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
@@ -278,34 +287,49 @@ bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
|
||||
// https://searchfox.org/mozilla-central/rev/ffdc4971dc18e1141cb2a90c2b0b776365650270/xpcom/io/CocoaFileUtils.mm#230
|
||||
// https://github.com/transmission/transmission/blob/f62f7427edb1fd5c430e0ef6956bbaa4f03ae597/macosx/Torrent.mm#L1945-L1955
|
||||
|
||||
const CFStringRef fileString = file.toString().toCFString();
|
||||
[[maybe_unused]] const auto fileStringGuard = qScopeGuard([&fileString] { ::CFRelease(fileString); });
|
||||
const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault
|
||||
, fileString, kCFURLPOSIXPathStyle, false);
|
||||
[[maybe_unused]] const auto fileURLGuard = qScopeGuard([&fileURL] { ::CFRelease(fileURL); });
|
||||
|
||||
if (CFDictionaryRef currentProperties = nullptr;
|
||||
::CFURLCopyResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey, ¤tProperties, NULL)
|
||||
&& currentProperties)
|
||||
{
|
||||
[[maybe_unused]] const auto currentPropertiesGuard = qScopeGuard([¤tProperties] { ::CFRelease(currentProperties); });
|
||||
|
||||
if (CFStringRef quarantineType = nullptr;
|
||||
::CFDictionaryGetValueIfPresent(currentProperties, kLSQuarantineTypeKey, reinterpret_cast<const void **>(&quarantineType))
|
||||
&& quarantineType)
|
||||
{
|
||||
if (::CFStringCompare(quarantineType, kLSQuarantineTypeOtherDownload, 0) == kCFCompareEqualTo)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
CFMutableDictionaryRef properties = ::CFDictionaryCreateMutable(kCFAllocatorDefault, 0
|
||||
, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
if (properties == NULL)
|
||||
if (!properties)
|
||||
return false;
|
||||
[[maybe_unused]] const auto propertiesGuard = qScopeGuard([&properties] { ::CFRelease(properties); });
|
||||
|
||||
::CFDictionarySetValue(properties, kLSQuarantineTypeKey, kLSQuarantineTypeOtherDownload);
|
||||
if (!url.isEmpty())
|
||||
::CFDictionarySetValue(properties, kLSQuarantineDataURLKey, url.toCFString());
|
||||
|
||||
const CFStringRef fileString = file.toString().toCFString();
|
||||
const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault
|
||||
, fileString, kCFURLPOSIXPathStyle, false);
|
||||
|
||||
const Boolean success = ::CFURLSetResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey
|
||||
, properties, NULL);
|
||||
|
||||
::CFRelease(fileURL);
|
||||
::CFRelease(fileString);
|
||||
::CFRelease(properties);
|
||||
|
||||
return success;
|
||||
#elif defined(Q_OS_WIN)
|
||||
const QString zoneIDStream = file.toString() + u":Zone.Identifier";
|
||||
HANDLE handle = ::CreateFileW(zoneIDStream.toStdWString().c_str(), GENERIC_WRITE
|
||||
|
||||
HANDLE handle = ::CreateFileW(zoneIDStream.toStdWString().c_str(), (GENERIC_READ | GENERIC_WRITE)
|
||||
, (FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE)
|
||||
, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
[[maybe_unused]] const auto handleGuard = qScopeGuard([&handle] { ::CloseHandle(handle); });
|
||||
|
||||
// 5.6.1 Zone.Identifier Stream Name
|
||||
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8
|
||||
@@ -313,10 +337,27 @@ bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
|
||||
const QByteArray zoneID = QByteArrayLiteral("[ZoneTransfer]\r\nZoneId=3\r\n")
|
||||
+ u"HostUrl=%1\r\n"_s.arg(hostURL).toUtf8();
|
||||
|
||||
if (LARGE_INTEGER streamSize = {0};
|
||||
::GetFileSizeEx(handle, &streamSize) && (streamSize.QuadPart > 0))
|
||||
{
|
||||
const DWORD expectedReadSize = std::min<LONGLONG>(streamSize.QuadPart, 1024);
|
||||
QByteArray buf {expectedReadSize, '\0'};
|
||||
|
||||
if (DWORD actualReadSize = 0;
|
||||
::ReadFile(handle, buf.data(), expectedReadSize, &actualReadSize, nullptr) && (actualReadSize == expectedReadSize))
|
||||
{
|
||||
if (buf.startsWith("[ZoneTransfer]\r\n") && buf.contains("\r\nZoneId=3\r\n") && buf.contains("\r\nHostUrl="))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!::SetFilePointerEx(handle, {0}, nullptr, FILE_BEGIN))
|
||||
return false;
|
||||
if (!::SetEndOfFile(handle))
|
||||
return false;
|
||||
|
||||
DWORD written = 0;
|
||||
const BOOL writeResult = ::WriteFile(handle, zoneID.constData(), zoneID.size(), &written, nullptr);
|
||||
::CloseHandle(handle);
|
||||
|
||||
return writeResult && (written == zoneID.size());
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
|
||||
#define QBT_VERSION_MAJOR 5
|
||||
#define QBT_VERSION_MINOR 0
|
||||
#define QBT_VERSION_BUGFIX 0
|
||||
#define QBT_VERSION_BUGFIX 3
|
||||
#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,32 @@ 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;
|
||||
|
||||
emit torrentRejected(m_currentContext->torrentDescr);
|
||||
|
||||
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 +836,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 +868,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 +885,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 +919,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,13 @@ 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);
|
||||
void torrentRejected(const BitTorrent::TorrentDescriptor &torrentDescriptor);
|
||||
|
||||
private slots:
|
||||
void updateDiskSpaceLabel();
|
||||
void onSavePathChanged(const Path &newPath);
|
||||
@@ -75,29 +82,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
|
||||
@@ -104,7 +105,10 @@ namespace
|
||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||
ENABLE_MARK_OF_THE_WEB,
|
||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||
IGNORE_SSL_ERRORS,
|
||||
PYTHON_EXECUTABLE_PATH,
|
||||
START_SESSION_PAUSED,
|
||||
SESSION_SHUTDOWN_TIMEOUT,
|
||||
|
||||
// libtorrent section
|
||||
LIBTORRENT_HEADER,
|
||||
@@ -329,8 +333,14 @@ void AdvancedSettings::saveAdvancedSettings() const
|
||||
// Mark-of-the-Web
|
||||
pref->setMarkOfTheWebEnabled(m_checkBoxMarkOfTheWeb.isChecked());
|
||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||
// Ignore SSL errors
|
||||
pref->setIgnoreSSLErrors(m_checkBoxIgnoreSSLErrors.isChecked());
|
||||
// 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 +368,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 +478,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);
|
||||
@@ -571,6 +588,7 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
m_comboBoxDiskIOType.addItem(tr("Default"), QVariant::fromValue(BitTorrent::DiskIOType::Default));
|
||||
m_comboBoxDiskIOType.addItem(tr("Memory mapped files"), QVariant::fromValue(BitTorrent::DiskIOType::MMap));
|
||||
m_comboBoxDiskIOType.addItem(tr("POSIX-compliant"), QVariant::fromValue(BitTorrent::DiskIOType::Posix));
|
||||
m_comboBoxDiskIOType.addItem(tr("Simple pread/pwrite"), QVariant::fromValue(BitTorrent::DiskIOType::SimplePreadPwrite));
|
||||
m_comboBoxDiskIOType.setCurrentIndex(m_comboBoxDiskIOType.findData(QVariant::fromValue(session->diskIOType())));
|
||||
addRow(DISK_IO_TYPE, tr("Disk IO type (requires restart)") + u' ' + makeLink(u"https://www.libtorrent.org/single-page-ref.html#default-disk-io-constructor", u"(?)")
|
||||
, &m_comboBoxDiskIOType);
|
||||
@@ -839,10 +857,26 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
m_checkBoxMarkOfTheWeb.setChecked(pref->isMarkOfTheWebEnabled());
|
||||
addRow(ENABLE_MARK_OF_THE_WEB, motwLabel, &m_checkBoxMarkOfTheWeb);
|
||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||
// Ignore SSL errors
|
||||
m_checkBoxIgnoreSSLErrors.setChecked(pref->isIgnoreSSLErrors());
|
||||
m_checkBoxIgnoreSSLErrors.setToolTip(tr("Affects certificate validation and non-torrent protocol activities (e.g. RSS feeds, program updates, torrent files, geoip db, etc)"));
|
||||
addRow(IGNORE_SSL_ERRORS, tr("Ignore SSL errors"), &m_checkBoxIgnoreSSLErrors);
|
||||
// Python executable path
|
||||
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 +974,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,16 @@ 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_checkBoxTrackerPortForwarding, m_checkBoxIgnoreSSLErrors, 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_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)
|
||||
@@ -174,7 +175,8 @@ void GUIAddTorrentManager::onMetadataDownloaded(const BitTorrent::TorrentInfo &m
|
||||
}
|
||||
}
|
||||
|
||||
bool GUIAddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams ¶ms)
|
||||
bool GUIAddTorrentManager::processTorrent(const QString &source
|
||||
, const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams ¶ms)
|
||||
{
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
|
||||
@@ -182,32 +184,39 @@ bool GUIAddTorrentManager::processTorrent(const QString &source, const BitTorren
|
||||
// Prevent showing the dialog if download is already present
|
||||
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
||||
{
|
||||
if (hasMetadata)
|
||||
if (Preferences::instance()->confirmMergeTrackers())
|
||||
{
|
||||
// Trying to set metadata to existing torrent in case if it has none
|
||||
torrent->setMetadata(*torrentDescr.info());
|
||||
}
|
||||
if (hasMetadata)
|
||||
{
|
||||
// Trying to set metadata to existing torrent in case if it has none
|
||||
torrent->setMetadata(*torrentDescr.info());
|
||||
}
|
||||
|
||||
if (torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate()))
|
||||
{
|
||||
handleDuplicateTorrent(source, torrent, tr("Trackers cannot be merged because it is a private torrent"));
|
||||
const bool isPrivate = torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
||||
const QString dialogCaption = tr("Torrent is already present");
|
||||
if (isPrivate)
|
||||
{
|
||||
// We cannot merge trackers for private torrent but we still notify user
|
||||
// about duplicate torrent if confirmation dialog is enabled.
|
||||
RaisedMessageBox::warning(app()->mainWindow(), dialogCaption
|
||||
, tr("Trackers cannot be merged because it is a private torrent."));
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool mergeTrackers = btSession()->isMergeTrackersEnabled();
|
||||
const QMessageBox::StandardButton btn = RaisedMessageBox::question(app()->mainWindow(), dialogCaption
|
||||
, tr("Torrent '%1' is already in the transfer list. Do you want to merge trackers from new source?").arg(torrent->name())
|
||||
, (QMessageBox::Yes | QMessageBox::No), (mergeTrackers ? QMessageBox::Yes : QMessageBox::No));
|
||||
if (btn == QMessageBox::Yes)
|
||||
{
|
||||
torrent->addTrackers(torrentDescr.trackers());
|
||||
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool mergeTrackers = btSession()->isMergeTrackersEnabled();
|
||||
if (Preferences::instance()->confirmMergeTrackers())
|
||||
{
|
||||
const QMessageBox::StandardButton btn = RaisedMessageBox::question(app()->mainWindow(), tr("Torrent is already present")
|
||||
, tr("Torrent '%1' is already in the transfer list. Do you want to merge trackers from new source?").arg(torrent->name())
|
||||
, (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
|
||||
mergeTrackers = (btn == QMessageBox::Yes);
|
||||
}
|
||||
|
||||
if (mergeTrackers)
|
||||
{
|
||||
torrent->addTrackers(torrentDescr.trackers());
|
||||
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||
}
|
||||
handleDuplicateTorrent(source, torrentDescr, torrent);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -225,14 +234,23 @@ 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, dlg](const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &addTorrentParams)
|
||||
{
|
||||
if (dlg->isDoNotDeleteTorrentChecked())
|
||||
releaseTorrentFileGuard(source);
|
||||
|
||||
if (result == QDialog::Accepted)
|
||||
addTorrentToSession(source, dlg->torrentDescriptor(), dlg->addTorrentParams());
|
||||
{
|
||||
if (auto torrentFileGuard = releaseTorrentFileGuard(source))
|
||||
torrentFileGuard->setAutoRemove(false);
|
||||
}
|
||||
|
||||
addTorrentToSession(source, torrentDescr, addTorrentParams);
|
||||
});
|
||||
connect(dlg, &AddNewTorrentDialog::torrentRejected, this, [this, source]
|
||||
{
|
||||
releaseTorrentFileGuard(source);
|
||||
});
|
||||
connect(dlg, &QDialog::finished, this, [this, source, infoHash]
|
||||
{
|
||||
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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user