mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-07-06 13:11:25 -07:00
Compare commits
164 commits
master
...
release-5.
Author | SHA1 | Date | |
---|---|---|---|
|
4b794a9930 | ||
|
96b27a313e | ||
|
c39849bd3b | ||
|
daf7af0d7b | ||
|
a6809efbbb | ||
|
ddd8fbd34e | ||
|
13fcd3d635 | ||
|
3c6ff0097f | ||
|
03dc089148 | ||
|
100ee5dbe0 | ||
|
310a9d8e1a | ||
|
677cabcbdf | ||
|
b86079974c | ||
|
ca6a89e238 | ||
|
0132b17af6 | ||
|
505c1e1c0a | ||
|
ecde201ec5 | ||
|
730bf957a4 | ||
|
069cd029eb | ||
|
375e6800e9 | ||
|
09fb92466a | ||
|
69321f0e94 | ||
|
f39e066672 | ||
|
6a5ea93c92 | ||
|
35dce07c63 | ||
|
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 |
751 changed files with 137589 additions and 118150 deletions
7
.github/workflows/ci_macos.yaml
vendored
7
.github/workflows/ci_macos.yaml
vendored
|
@ -23,7 +23,6 @@ jobs:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
boost_path: "${{ github.workspace }}/../boost"
|
boost_path: "${{ github.workspace }}/../boost"
|
||||||
openssl_root: "$(brew --prefix openssl@3)"
|
|
||||||
libtorrent_path: "${{ github.workspace }}/../libtorrent"
|
libtorrent_path: "${{ github.workspace }}/../libtorrent"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -70,7 +69,7 @@ jobs:
|
||||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||||
|
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.qt_version }}
|
version: ${{ matrix.qt_version }}
|
||||||
archives: qtbase qtdeclarative qtsvg qttools
|
archives: qtbase qtdeclarative qtsvg qttools
|
||||||
|
@ -94,8 +93,7 @@ jobs:
|
||||||
-DCMAKE_CXX_STANDARD=17 \
|
-DCMAKE_CXX_STANDARD=17 \
|
||||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||||
-Ddeprecated-functions=OFF \
|
-Ddeprecated-functions=OFF
|
||||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}"
|
|
||||||
cmake --build build
|
cmake --build build
|
||||||
sudo cmake --install build
|
sudo cmake --install build
|
||||||
|
|
||||||
|
@ -109,7 +107,6 @@ jobs:
|
||||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
|
|
||||||
-DTESTING=ON \
|
-DTESTING=ON \
|
||||||
-DVERBOSE_CONFIGURE=ON \
|
-DVERBOSE_CONFIGURE=ON \
|
||||||
-D${{ matrix.qbt_gui }}
|
-D${{ matrix.qbt_gui }}
|
||||||
|
|
12
.github/workflows/ci_python.yaml
vendored
12
.github/workflows/ci_python.yaml
vendored
|
@ -53,7 +53,7 @@ jobs:
|
||||||
python-version: '3.7'
|
python-version: '3.7'
|
||||||
|
|
||||||
- name: Install tools (search engine)
|
- name: Install tools (search engine)
|
||||||
run: pip install bandit pycodestyle pyflakes
|
run: pip install bandit mypy pycodestyle pyflakes pyright
|
||||||
|
|
||||||
- name: Gather files (search engine)
|
- name: Gather files (search engine)
|
||||||
run: |
|
run: |
|
||||||
|
@ -61,6 +61,16 @@ jobs:
|
||||||
echo $PY_FILES
|
echo $PY_FILES
|
||||||
echo "PY_FILES=$PY_FILES" >> "$GITHUB_ENV"
|
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)
|
- name: Lint code (search engine)
|
||||||
run: |
|
run: |
|
||||||
pyflakes $PY_FILES
|
pyflakes $PY_FILES
|
||||||
|
|
3
.github/workflows/ci_ubuntu.yaml
vendored
3
.github/workflows/ci_ubuntu.yaml
vendored
|
@ -64,7 +64,7 @@ jobs:
|
||||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||||
|
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.qt_version }}
|
version: ${{ matrix.qt_version }}
|
||||||
archives: icu qtbase qtdeclarative qtsvg qttools
|
archives: icu qtbase qtdeclarative qtsvg qttools
|
||||||
|
@ -134,7 +134,6 @@ jobs:
|
||||||
|
|
||||||
- name: Install AppImage
|
- name: Install AppImage
|
||||||
run: |
|
run: |
|
||||||
sudo apt install libfuse2
|
|
||||||
curl \
|
curl \
|
||||||
-L \
|
-L \
|
||||||
-Z \
|
-Z \
|
||||||
|
|
32
.github/workflows/ci_windows.yaml
vendored
32
.github/workflows/ci_windows.yaml
vendored
|
@ -93,9 +93,9 @@ jobs:
|
||||||
move "${{ github.workspace }}/../boost_*" "${{ env.boost_path }}"
|
move "${{ github.workspace }}/../boost_*" "${{ env.boost_path }}"
|
||||||
|
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: "6.7.0"
|
version: "6.7.3"
|
||||||
archives: qtbase qtsvg qttools
|
archives: qtbase qtsvg qttools
|
||||||
cache: true
|
cache: true
|
||||||
|
|
||||||
|
@ -153,26 +153,26 @@ jobs:
|
||||||
copy build/qbittorrent.pdb upload/qBittorrent
|
copy build/qbittorrent.pdb upload/qBittorrent
|
||||||
copy dist/windows/qt.conf upload/qBittorrent
|
copy dist/windows/qt.conf upload/qBittorrent
|
||||||
# runtimes
|
# runtimes
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Core.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Core.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Gui.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Gui.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Network.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Network.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Sql.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Sql.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Svg.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Svg.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Widgets.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Widgets.dll" upload/qBittorrent
|
||||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Xml.dll" upload/qBittorrent
|
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Xml.dll" upload/qBittorrent
|
||||||
mkdir upload/qBittorrent/plugins/iconengines
|
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
|
mkdir upload/qBittorrent/plugins/imageformats
|
||||||
copy "${{ env.Qt6_DIR }}/plugins/imageformats/qico.dll" upload/qBittorrent/plugins/imageformats
|
copy "${{ env.Qt_ROOT_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/qsvg.dll" upload/qBittorrent/plugins/imageformats
|
||||||
mkdir upload/qBittorrent/plugins/platforms
|
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
|
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
|
mkdir upload/qBittorrent/plugins/styles
|
||||||
copy "${{ env.Qt6_DIR }}/plugins/styles/qmodernwindowsstyle.dll" upload/qBittorrent/plugins/styles
|
copy "${{ env.Qt_ROOT_DIR }}/plugins/styles/qmodernwindowsstyle.dll" upload/qBittorrent/plugins/styles
|
||||||
mkdir upload/qBittorrent/plugins/tls
|
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
|
# cmake additionals
|
||||||
mkdir upload/cmake
|
mkdir upload/cmake
|
||||||
copy build/compile_commands.json upload/cmake
|
copy build/compile_commands.json upload/cmake
|
||||||
|
|
2
.github/workflows/coverity-scan.yaml
vendored
2
.github/workflows/coverity-scan.yaml
vendored
|
@ -52,7 +52,7 @@ jobs:
|
||||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||||
|
|
||||||
- name: Install Qt
|
- name: Install Qt
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v4
|
||||||
with:
|
with:
|
||||||
version: ${{ matrix.qt_version }}
|
version: ${{ matrix.qt_version }}
|
||||||
archives: icu qtbase qtdeclarative qtsvg qttools
|
archives: icu qtbase qtdeclarative qtsvg qttools
|
||||||
|
|
|
@ -78,11 +78,7 @@ repos:
|
||||||
m4/.* |
|
m4/.* |
|
||||||
src/base/3rdparty/.* |
|
src/base/3rdparty/.* |
|
||||||
src/searchengine/nova3/socks.py |
|
src/searchengine/nova3/socks.py |
|
||||||
src/webui/www/private/lang/.* |
|
src/webui/www/private/scripts/lib/.*
|
||||||
src/webui/www/private/scripts/lib/.* |
|
|
||||||
src/webui/www/public/lang/.* |
|
|
||||||
src/webui/www/public/scripts/lib/.* |
|
|
||||||
src/webui/www/transifex/.*
|
|
||||||
)$
|
)$
|
||||||
exclude_types:
|
exclude_types:
|
||||||
- ts
|
- ts
|
||||||
|
@ -106,11 +102,7 @@ repos:
|
||||||
m4/.* |
|
m4/.* |
|
||||||
src/base/3rdparty/.* |
|
src/base/3rdparty/.* |
|
||||||
src/searchengine/nova3/socks.py |
|
src/searchengine/nova3/socks.py |
|
||||||
src/webui/www/private/lang/.* |
|
src/webui/www/private/scripts/lib/.*
|
||||||
src/webui/www/private/scripts/lib/.* |
|
|
||||||
src/webui/www/public/lang/.* |
|
|
||||||
src/webui/www/public/scripts/lib/.* |
|
|
||||||
src/webui/www/transifex/.*
|
|
||||||
)$
|
)$
|
||||||
exclude_types:
|
exclude_types:
|
||||||
- svg
|
- svg
|
||||||
|
|
12
.tx/config
12
.tx/config
|
@ -1,7 +1,7 @@
|
||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
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
|
file_filter = src/lang/qbittorrent_<lang>.ts
|
||||||
source_file = src/lang/qbittorrent_en.ts
|
source_file = src/lang/qbittorrent_en.ts
|
||||||
source_lang = en
|
source_lang = en
|
||||||
|
@ -9,7 +9,7 @@ type = QT
|
||||||
minimum_perc = 23
|
minimum_perc = 23
|
||||||
lang_map = pt: pt_PT, zh: zh_CN
|
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
|
file_filter = src/webui/www/translations/webui_<lang>.ts
|
||||||
source_file = src/webui/www/translations/webui_en.ts
|
source_file = src/webui/www/translations/webui_en.ts
|
||||||
source_lang = en
|
source_lang = en
|
||||||
|
@ -17,14 +17,6 @@ type = QT
|
||||||
minimum_perc = 23
|
minimum_perc = 23
|
||||||
lang_map = pt: pt_PT, zh: zh_CN
|
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]
|
[o:sledgehammer999:p:qbittorrent:r:qbittorrentdesktop_master]
|
||||||
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
|
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
|
||||||
source_lang = en
|
source_lang = en
|
||||||
|
|
106
Changelog
106
Changelog
|
@ -1,4 +1,67 @@
|
||||||
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
Sun Apr 13th 2025 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.5
|
||||||
|
- FEATURE: Add an advanced setting for setting the "Add New Torrent" dialog as modal (glassez)
|
||||||
|
- BUGFIX: Improve command line parameters serialization (glassez)
|
||||||
|
- BUGFIX: Declare missing color IDs for theming (glassez)
|
||||||
|
- WINDOWS: NSIS: Update Swedish translation (Daniel Nylander)
|
||||||
|
|
||||||
|
Tue Feb 18th 2025 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.4
|
||||||
|
- BUGFIX: Fix cannot remove trackers via WebAPI (Chocobo1)
|
||||||
|
- BUGFIX: Fix torrent content checkbox state under certain conditions (thalieht)
|
||||||
|
- BUGFIX: Hide zero and infinity values in peer list only when that setting is set to `Always` (thalieht)
|
||||||
|
- BUGFIX: Remove stopped torrent from "error" tracker filter (glassez)
|
||||||
|
- WEBUI: Fix memory leak in context menus (skomerko)
|
||||||
|
- WEBAPI: Don't trim string parameters (glassez)
|
||||||
|
- WINDOWS: Handle Qt style options uniformly (glassez)
|
||||||
|
- WINDOWS: NSIS: Update Portuguese translation (Hugo Carvalho)
|
||||||
|
- MACOS: Avoid memory leak (Chocobo1)
|
||||||
|
|
||||||
|
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: Support creating .torrent with larger piece size (Chocobo1)
|
||||||
- FEATURE: Improve tracker entries handling (glassez)
|
- FEATURE: Improve tracker entries handling (glassez)
|
||||||
- FEATURE: Add separate filter item for tracker errors (glassez)
|
- FEATURE: Add separate filter item for tracker errors (glassez)
|
||||||
|
@ -12,14 +75,30 @@ Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
||||||
- FEATURE: Enable Ctrl+F hotkey for more inputs (thalieht)
|
- FEATURE: Enable Ctrl+F hotkey for more inputs (thalieht)
|
||||||
- FEATURE: Add seeding limits to RSS and Watched folders options UI (glassez)
|
- FEATURE: Add seeding limits to RSS and Watched folders options UI (glassez)
|
||||||
- FEATURE: Subcategories implicitly follow the parent category options (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 option to name each qbittorrent instance (Chocobo1)
|
||||||
- FEATURE: Add button for sending test email (Thomas Piccirello)
|
- FEATURE: Add button for sending test email (Thomas Piccirello)
|
||||||
- FEATURE: Allow torrents to override default share limit action (glassez)
|
- 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: Display error message when unrecoverable error occurred (glassez)
|
||||||
- BUGFIX: Update size of selected files when selection is changed (glassez)
|
- BUGFIX: Update size of selected files when selection is changed (glassez)
|
||||||
- BUGFIX: Normalize tags by trimming leading/trailing whitespace (glassez)
|
- BUGFIX: Normalize tags by trimming leading/trailing whitespace (glassez)
|
||||||
- BUGFIX: Correctly handle share limits in torrent options dialog (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: Improve WebUI responsiveness (Chocobo1)
|
||||||
- WEBUI: Do not exit the app when WebUI has failed to start (Hanabishi)
|
- WEBUI: Do not exit the app when WebUI has failed to start (Hanabishi)
|
||||||
- WEBUI: Add `Moving` filter to side panel (xavier2k6)
|
- WEBUI: Add `Moving` filter to side panel (xavier2k6)
|
||||||
|
@ -28,14 +107,37 @@ Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
||||||
- WEBUI: Leave the fields empty when value is invalid (Chocobo1)
|
- WEBUI: Leave the fields empty when value is invalid (Chocobo1)
|
||||||
- WEBUI: Use natural sorting (Chocobo1)
|
- WEBUI: Use natural sorting (Chocobo1)
|
||||||
- WEBUI: Improve WebUI login behavior (JayRet)
|
- 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: Fix wrong timestamp values (Chocobo1)
|
||||||
- WEBAPI: Send binary data with filename and mime type specified (glassez)
|
- WEBAPI: Send binary data with filename and mime type specified (glassez)
|
||||||
- WEBAPI: Expose API for the torrent creator (glassez, Radu Carpa)
|
- 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: Show RSS feed title in HTML browser (Jay)
|
||||||
- RSS: Allow to set delay between requests to the same host (jNullj)
|
- RSS: Allow to set delay between requests to the same host (jNullj)
|
||||||
- SEARCH: Allow users to specify Python executable path (Chocobo1)
|
- 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: Use Fusion style on Windows 10+. It has better compatibility with dark mode (glassez)
|
||||||
- WINDOWS: Allow to set qBittorrent as default program (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 systemd power management (Chocobo1)
|
||||||
- LINUX: Add support for localized man pages (Victor Chernyakin)
|
- LINUX: Add support for localized man pages (Victor Chernyakin)
|
||||||
- LINUX: Specify a locale if none is set (Chocobo1)
|
- LINUX: Specify a locale if none is set (Chocobo1)
|
||||||
|
|
4
dist/mac/Info.plist
vendored
4
dist/mac/Info.plist
vendored
|
@ -55,7 +55,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>5.0.0</string>
|
<string>5.0.5</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>${EXECUTABLE_NAME}</string>
|
<string>${EXECUTABLE_NAME}</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
<key>NSAppleScriptEnabled</key>
|
<key>NSAppleScriptEnabled</key>
|
||||||
<string>YES</string>
|
<string>YES</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>Copyright © 2006-2024 The qBittorrent project</string>
|
<string>Copyright © 2006-2025 The qBittorrent project</string>
|
||||||
<key>UTExportedTypeDeclarations</key>
|
<key>UTExportedTypeDeclarations</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
|
|
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
|
SingleMainWindow=true
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
Comment[af]=Aflaai en deel lêers oor BitTorrent
|
|
||||||
GenericName[af]=BitTorrent kliënt
|
GenericName[af]=BitTorrent kliënt
|
||||||
|
Comment[af]=Aflaai en deel lêers oor BitTorrent
|
||||||
Name[af]=qBittorrent
|
Name[af]=qBittorrent
|
||||||
Comment[ar]=نزّل وشارك الملفات عبر كيوبتتورنت
|
|
||||||
GenericName[ar]=عميل بتتورنت
|
GenericName[ar]=عميل بتتورنت
|
||||||
|
Comment[ar]=نزّل وشارك الملفات عبر كيوبتتورنت
|
||||||
Name[ar]=qBittorrent
|
Name[ar]=qBittorrent
|
||||||
Comment[be]=Спампоўванне і раздача файлаў праз пратакол BitTorrent
|
|
||||||
GenericName[be]=Кліент BitTorrent
|
GenericName[be]=Кліент BitTorrent
|
||||||
|
Comment[be]=Спампоўванне і раздача файлаў праз пратакол BitTorrent
|
||||||
Name[be]=qBittorrent
|
Name[be]=qBittorrent
|
||||||
Comment[bg]=Сваляне и споделяне на файлове чрез BitTorrent
|
|
||||||
GenericName[bg]=BitTorrent клиент
|
GenericName[bg]=BitTorrent клиент
|
||||||
|
Comment[bg]=Сваляне и споделяне на файлове чрез BitTorrent
|
||||||
Name[bg]=qBittorrent
|
Name[bg]=qBittorrent
|
||||||
Comment[bn]=বিটটরেন্টে ফাইল ডাউনলোড এবং শেয়ার করুন
|
|
||||||
GenericName[bn]=বিটটরেন্ট ক্লায়েন্ট
|
GenericName[bn]=বিটটরেন্ট ক্লায়েন্ট
|
||||||
|
Comment[bn]=বিটটরেন্টে ফাইল ডাউনলোড এবং শেয়ার করুন
|
||||||
Name[bn]=qBittorrent
|
Name[bn]=qBittorrent
|
||||||
Comment[zh]=通过 BitTorrent 下载和分享文件
|
|
||||||
GenericName[zh]=BitTorrent 客户端
|
GenericName[zh]=BitTorrent 客户端
|
||||||
|
Comment[zh]=通过 BitTorrent 下载和分享文件
|
||||||
Name[zh]=qBittorrent
|
Name[zh]=qBittorrent
|
||||||
Comment[bs]=Preuzmi i dijeli datoteke preko BitTorrent-a
|
|
||||||
GenericName[bs]=BitTorrent klijent
|
GenericName[bs]=BitTorrent klijent
|
||||||
|
Comment[bs]=Preuzmi i dijeli datoteke preko BitTorrent-a
|
||||||
Name[bs]=qBittorrent
|
Name[bs]=qBittorrent
|
||||||
Comment[ca]=Baixeu i compartiu fitxers amb el BitTorrent
|
|
||||||
GenericName[ca]=Client de BitTorrent
|
GenericName[ca]=Client de BitTorrent
|
||||||
|
Comment[ca]=Baixeu i compartiu fitxers amb el BitTorrent
|
||||||
Name[ca]=qBittorrent
|
Name[ca]=qBittorrent
|
||||||
Comment[cs]=Stahování a sdílení souborů přes síť BitTorrent
|
|
||||||
GenericName[cs]=BitTorrent klient
|
GenericName[cs]=BitTorrent klient
|
||||||
|
Comment[cs]=Stahování a sdílení souborů přes síť BitTorrent
|
||||||
Name[cs]=qBittorrent
|
Name[cs]=qBittorrent
|
||||||
Comment[da]=Download og del filer over BitTorrent
|
|
||||||
GenericName[da]=BitTorrent-klient
|
GenericName[da]=BitTorrent-klient
|
||||||
|
Comment[da]=Download og del filer over BitTorrent
|
||||||
Name[da]=qBittorrent
|
Name[da]=qBittorrent
|
||||||
Comment[de]=Über BitTorrent Dateien herunterladen und teilen
|
|
||||||
GenericName[de]=BitTorrent Client
|
GenericName[de]=BitTorrent Client
|
||||||
|
Comment[de]=Über BitTorrent Dateien herunterladen und teilen
|
||||||
Name[de]=qBittorrent
|
Name[de]=qBittorrent
|
||||||
Comment[el]=Κάντε λήψη και μοιραστείτε αρχεία μέσω BitTorrent
|
|
||||||
GenericName[el]=BitTorrent client
|
GenericName[el]=BitTorrent client
|
||||||
|
Comment[el]=Κάντε λήψη και μοιραστείτε αρχεία μέσω BitTorrent
|
||||||
Name[el]=qBittorrent
|
Name[el]=qBittorrent
|
||||||
Comment[en_GB]=Download and share files over BitTorrent
|
|
||||||
GenericName[en_GB]=BitTorrent client
|
GenericName[en_GB]=BitTorrent client
|
||||||
|
Comment[en_GB]=Download and share files over BitTorrent
|
||||||
Name[en_GB]=qBittorrent
|
Name[en_GB]=qBittorrent
|
||||||
Comment[es]=Descargue y comparta archivos por BitTorrent
|
|
||||||
GenericName[es]=Cliente BitTorrent
|
GenericName[es]=Cliente BitTorrent
|
||||||
|
Comment[es]=Descargue y comparta archivos por BitTorrent
|
||||||
Name[es]=qBittorrent
|
Name[es]=qBittorrent
|
||||||
Comment[et]=Lae alla ja jaga faile üle BitTorrenti
|
|
||||||
GenericName[et]=BitTorrent klient
|
GenericName[et]=BitTorrent klient
|
||||||
|
Comment[et]=Lae alla ja jaga faile üle BitTorrenti
|
||||||
Name[et]=qBittorrent
|
Name[et]=qBittorrent
|
||||||
Comment[eu]=Jeitsi eta elkarbanatu agiriak BitTorrent bidez
|
|
||||||
GenericName[eu]=BitTorrent bezeroa
|
GenericName[eu]=BitTorrent bezeroa
|
||||||
|
Comment[eu]=Jeitsi eta elkarbanatu agiriak BitTorrent bidez
|
||||||
Name[eu]=qBittorrent
|
Name[eu]=qBittorrent
|
||||||
Comment[fa]=دانلود و به اشتراک گذاری فایل های بوسیله بیت تورنت
|
|
||||||
GenericName[fa]=بیت تورنت نسخه کلاینت
|
GenericName[fa]=بیت تورنت نسخه کلاینت
|
||||||
|
Comment[fa]=دانلود و به اشتراک گذاری فایل های بوسیله بیت تورنت
|
||||||
Name[fa]=qBittorrent
|
Name[fa]=qBittorrent
|
||||||
Comment[fi]=Lataa ja jaa tiedostoja BitTorrentia käyttäen
|
|
||||||
GenericName[fi]=BitTorrent-asiakasohjelma
|
GenericName[fi]=BitTorrent-asiakasohjelma
|
||||||
|
Comment[fi]=Lataa ja jaa tiedostoja BitTorrentia käyttäen
|
||||||
Name[fi]=qBittorrent
|
Name[fi]=qBittorrent
|
||||||
Comment[fr]=Télécharger et partager des fichiers sur BitTorrent
|
|
||||||
GenericName[fr]=Client BitTorrent
|
GenericName[fr]=Client BitTorrent
|
||||||
|
Comment[fr]=Télécharger et partager des fichiers sur BitTorrent
|
||||||
Name[fr]=qBittorrent
|
Name[fr]=qBittorrent
|
||||||
Comment[gl]=Descargar e compartir ficheiros co protocolo BitTorrent
|
|
||||||
GenericName[gl]=Cliente BitTorrent
|
GenericName[gl]=Cliente BitTorrent
|
||||||
|
Comment[gl]=Descargar e compartir ficheiros co protocolo BitTorrent
|
||||||
Name[gl]=qBittorrent
|
Name[gl]=qBittorrent
|
||||||
Comment[gu]=બિટ્ટોરેંટ પર ફાઈલો ડાઉનલોડ અને શેર કરો
|
|
||||||
GenericName[gu]=બિટ્ટોરેંટ ક્લાયન્ટ
|
GenericName[gu]=બિટ્ટોરેંટ ક્લાયન્ટ
|
||||||
|
Comment[gu]=બિટ્ટોરેંટ પર ફાઈલો ડાઉનલોડ અને શેર કરો
|
||||||
Name[gu]=qBittorrent
|
Name[gu]=qBittorrent
|
||||||
Comment[he]=הורד ושתף קבצים על גבי ביטורנט
|
|
||||||
GenericName[he]=לקוח ביטורנט
|
GenericName[he]=לקוח ביטורנט
|
||||||
|
Comment[he]=הורד ושתף קבצים על גבי ביטורנט
|
||||||
Name[he]=qBittorrent
|
Name[he]=qBittorrent
|
||||||
Comment[hr]=Preuzmite i dijelite datoteke putem BitTorrenta
|
|
||||||
GenericName[hr]=BitTorrent klijent
|
GenericName[hr]=BitTorrent klijent
|
||||||
|
Comment[hr]=Preuzmite i dijelite datoteke putem BitTorrenta
|
||||||
Name[hr]=qBittorrent
|
Name[hr]=qBittorrent
|
||||||
Comment[hu]=Fájlok letöltése és megosztása a BitTorrent hálózaton keresztül
|
|
||||||
GenericName[hu]=BitTorrent kliens
|
GenericName[hu]=BitTorrent kliens
|
||||||
|
Comment[hu]=Fájlok letöltése és megosztása a BitTorrent hálózaton keresztül
|
||||||
Name[hu]=qBittorrent
|
Name[hu]=qBittorrent
|
||||||
Comment[hy]=Նիշքերի փոխանցում BitTorrent-ի միջոցով
|
|
||||||
GenericName[hy]=BitTorrent սպասառու
|
GenericName[hy]=BitTorrent սպասառու
|
||||||
|
Comment[hy]=Նիշքերի փոխանցում BitTorrent-ի միջոցով
|
||||||
Name[hy]=qBittorrent
|
Name[hy]=qBittorrent
|
||||||
Comment[id]=Unduh dan berbagi berkas melalui BitTorrent
|
|
||||||
GenericName[id]=Klien BitTorrent
|
GenericName[id]=Klien BitTorrent
|
||||||
|
Comment[id]=Unduh dan berbagi berkas melalui BitTorrent
|
||||||
Name[id]=qBittorrent
|
Name[id]=qBittorrent
|
||||||
Comment[is]=Sækja og deila skrám yfir BitTorrent
|
|
||||||
GenericName[is]=BitTorrent biðlarar
|
GenericName[is]=BitTorrent biðlarar
|
||||||
|
Comment[is]=Sækja og deila skrám yfir BitTorrent
|
||||||
Name[is]=qBittorrent
|
Name[is]=qBittorrent
|
||||||
Comment[it]=Scarica e condividi file tramite BitTorrent
|
|
||||||
GenericName[it]=Client BitTorrent
|
GenericName[it]=Client BitTorrent
|
||||||
|
Comment[it]=Scarica e condividi file tramite BitTorrent
|
||||||
Name[it]=qBittorrent
|
Name[it]=qBittorrent
|
||||||
Comment[ja]=BitTorrentでファイルのダウンロードと共有
|
|
||||||
GenericName[ja]=BitTorrentクライアント
|
GenericName[ja]=BitTorrentクライアント
|
||||||
|
Comment[ja]=BitTorrentでファイルのダウンロードと共有
|
||||||
Name[ja]=qBittorrent
|
Name[ja]=qBittorrent
|
||||||
Comment[ka]=გადმოტვირთეთ და გააზიარეთ ფაილები BitTorrent-ის საშუალებით
|
|
||||||
GenericName[ka]=BitTorrent კლიენტი
|
GenericName[ka]=BitTorrent კლიენტი
|
||||||
|
Comment[ka]=გადმოტვირთეთ და გააზიარეთ ფაილები BitTorrent-ის საშუალებით
|
||||||
Name[ka]=qBittorrent
|
Name[ka]=qBittorrent
|
||||||
Comment[ko]=BitTorrent를 통한 파일 내려받기 및 공유
|
|
||||||
GenericName[ko]=BitTorrent 클라이언트
|
GenericName[ko]=BitTorrent 클라이언트
|
||||||
|
Comment[ko]=BitTorrent를 통해 파일 다운로드 및 공유
|
||||||
Name[ko]=qBittorrent
|
Name[ko]=qBittorrent
|
||||||
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle
|
|
||||||
GenericName[lt]=BitTorrent klientas
|
GenericName[lt]=BitTorrent klientas
|
||||||
|
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle
|
||||||
Name[lt]=qBittorrent
|
Name[lt]=qBittorrent
|
||||||
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
|
|
||||||
GenericName[mk]=BitTorrent клиент
|
GenericName[mk]=BitTorrent клиент
|
||||||
|
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
|
||||||
Name[mk]=qBittorrent
|
Name[mk]=qBittorrent
|
||||||
Comment[my]=တောရန့်ဖြင့်ဖိုင်များဒေါင်းလုဒ်ဆွဲရန်နှင့်မျှဝေရန်
|
|
||||||
GenericName[my]=တောရန့်စီမံခန့်ခွဲသည့်အရာ
|
GenericName[my]=တောရန့်စီမံခန့်ခွဲသည့်အရာ
|
||||||
|
Comment[my]=တောရန့်ဖြင့်ဖိုင်များဒေါင်းလုဒ်ဆွဲရန်နှင့်မျှဝေရန်
|
||||||
Name[my]=qBittorrent
|
Name[my]=qBittorrent
|
||||||
Comment[nb]=Last ned og del filer over BitTorrent
|
|
||||||
GenericName[nb]=BitTorrent-klient
|
GenericName[nb]=BitTorrent-klient
|
||||||
|
Comment[nb]=Last ned og del filer over BitTorrent
|
||||||
Name[nb]=qBittorrent
|
Name[nb]=qBittorrent
|
||||||
Comment[nl]=Bestanden downloaden en delen via BitTorrent
|
|
||||||
GenericName[nl]=BitTorrent-client
|
GenericName[nl]=BitTorrent-client
|
||||||
|
Comment[nl]=Bestanden downloaden en delen via BitTorrent
|
||||||
Name[nl]=qBittorrent
|
Name[nl]=qBittorrent
|
||||||
Comment[pl]=Pobieraj i dziel się plikami przez BitTorrent
|
|
||||||
GenericName[pl]=Klient BitTorrent
|
GenericName[pl]=Klient BitTorrent
|
||||||
|
Comment[pl]=Pobieraj i dziel się plikami przez BitTorrent
|
||||||
Name[pl]=qBittorrent
|
Name[pl]=qBittorrent
|
||||||
Comment[pt]=Transferir e partilhar ficheiros por BitTorrent
|
|
||||||
GenericName[pt]=Cliente BitTorrent
|
GenericName[pt]=Cliente BitTorrent
|
||||||
|
Comment[pt]=Transferir e partilhar ficheiros por BitTorrent
|
||||||
Name[pt]=qBittorrent
|
Name[pt]=qBittorrent
|
||||||
Comment[pt_BR]=Baixe e compartilhe arquivos pelo BitTorrent
|
|
||||||
GenericName[pt_BR]=Cliente BitTorrent
|
GenericName[pt_BR]=Cliente BitTorrent
|
||||||
|
Comment[pt_BR]=Baixe e compartilhe arquivos pelo BitTorrent
|
||||||
Name[pt_BR]=qBittorrent
|
Name[pt_BR]=qBittorrent
|
||||||
Comment[ro]=Descărcați și partajați fișiere prin BitTorrent
|
|
||||||
GenericName[ro]=Client BitTorrent
|
GenericName[ro]=Client BitTorrent
|
||||||
|
Comment[ro]=Descărcați și partajați fișiere prin BitTorrent
|
||||||
Name[ro]=qBittorrent
|
Name[ro]=qBittorrent
|
||||||
Comment[ru]=Обмен файлами по сети БитТоррент
|
|
||||||
GenericName[ru]=Клиент сети БитТоррент
|
GenericName[ru]=Клиент сети БитТоррент
|
||||||
|
Comment[ru]=Обмен файлами по сети БитТоррент
|
||||||
Name[ru]=qBittorrent
|
Name[ru]=qBittorrent
|
||||||
Comment[sk]=Sťahovanie a zdieľanie súborov prostredníctvom siete BitTorrent
|
|
||||||
GenericName[sk]=Klient siete BitTorrent
|
GenericName[sk]=Klient siete BitTorrent
|
||||||
|
Comment[sk]=Sťahovanie a zdieľanie súborov prostredníctvom siete BitTorrent
|
||||||
Name[sk]=qBittorrent
|
Name[sk]=qBittorrent
|
||||||
Comment[sl]=Prenesite in delite datoteke preko BitTorrenta
|
|
||||||
GenericName[sl]=BitTorrent odjemalec
|
GenericName[sl]=BitTorrent odjemalec
|
||||||
|
Comment[sl]=Prenesite in delite datoteke preko BitTorrenta
|
||||||
Name[sl]=qBittorrent
|
Name[sl]=qBittorrent
|
||||||
|
GenericName[sq]=Klienti BitTorrent
|
||||||
|
Comment[sq]=Shkarko dhe shpërndaj skedarë në BitTorrent
|
||||||
Name[sq]=qBittorrent
|
Name[sq]=qBittorrent
|
||||||
Comment[sr]=Преузимајте и делите фајлове преко BitTorrent протокола
|
GenericName[sr]=BitTorrent клијент
|
||||||
GenericName[sr]=BitTorrent-клијент
|
Comment[sr]=Преузимајте и делите фајлове преко BitTorrent-а
|
||||||
Name[sr]=qBittorrent
|
Name[sr]=qBittorrent
|
||||||
Comment[sr@latin]=Preuzimanje i deljenje fajlova preko BitTorrent-a
|
|
||||||
GenericName[sr@latin]=BitTorrent klijent
|
GenericName[sr@latin]=BitTorrent klijent
|
||||||
|
Comment[sr@latin]=Preuzimanje i deljenje fajlova preko BitTorrent-a
|
||||||
Name[sr@latin]=qBittorrent
|
Name[sr@latin]=qBittorrent
|
||||||
Comment[sv]=Hämta och dela filer över BitTorrent
|
|
||||||
GenericName[sv]=BitTorrent-klient
|
GenericName[sv]=BitTorrent-klient
|
||||||
|
Comment[sv]=Hämta och dela filer över BitTorrent
|
||||||
Name[sv]=qBittorrent
|
Name[sv]=qBittorrent
|
||||||
Comment[ta]=BitTorrent வழியாக கோப்புகளை பதிவிறக்க மற்றும் பகிர
|
|
||||||
GenericName[ta]=BitTorrent வாடிக்கையாளர்
|
GenericName[ta]=BitTorrent வாடிக்கையாளர்
|
||||||
|
Comment[ta]=BitTorrent வழியாக கோப்புகளை பதிவிறக்க மற்றும் பகிர
|
||||||
Name[ta]=qBittorrent
|
Name[ta]=qBittorrent
|
||||||
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
|
|
||||||
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
|
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
|
||||||
|
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
|
||||||
Name[te]=qBittorrent
|
Name[te]=qBittorrent
|
||||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่าน BitTorrent
|
GenericName[th]=ไคลเอนต์บิททอร์เรนต์
|
||||||
GenericName[th]=โปรแกรมบิททอเร้นท์
|
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่านบิตทอร์เรนต์
|
||||||
Name[th]=qBittorrent
|
Name[th]=qBittorrent
|
||||||
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
|
||||||
GenericName[tr]=BitTorrent istemcisi
|
GenericName[tr]=BitTorrent istemcisi
|
||||||
|
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
||||||
Name[tr]=qBittorrent
|
Name[tr]=qBittorrent
|
||||||
Comment[ur]=BitTorrent پر فائلوں کو ڈاؤن لوڈ کریں اور اشتراک کریں
|
|
||||||
GenericName[ur]=قیو بٹ ٹورنٹ کلائنٹ
|
GenericName[ur]=قیو بٹ ٹورنٹ کلائنٹ
|
||||||
|
Comment[ur]=BitTorrent پر فائلوں کو ڈاؤن لوڈ کریں اور اشتراک کریں
|
||||||
Name[ur]=qBittorrent
|
Name[ur]=qBittorrent
|
||||||
Comment[uk]=Завантажуйте та поширюйте файли через BitTorrent
|
|
||||||
GenericName[uk]=BitTorrent-клієнт
|
GenericName[uk]=BitTorrent-клієнт
|
||||||
|
Comment[uk]=Завантажуйте та поширюйте файли через BitTorrent
|
||||||
Name[uk]=qBittorrent
|
Name[uk]=qBittorrent
|
||||||
Comment[vi]=Tải xuống và chia sẻ tệp qua BitTorrent
|
|
||||||
GenericName[vi]=Máy khách BitTorrent
|
GenericName[vi]=Máy khách BitTorrent
|
||||||
|
Comment[vi]=Tải xuống và chia sẻ tệp qua BitTorrent
|
||||||
Name[vi]=qBittorrent
|
Name[vi]=qBittorrent
|
||||||
Comment[zh_HK]=經由BitTorrent下載並分享檔案
|
|
||||||
GenericName[zh_HK]=BitTorrent用戶端
|
GenericName[zh_HK]=BitTorrent用戶端
|
||||||
|
Comment[zh_HK]=經由BitTorrent下載並分享檔案
|
||||||
Name[zh_HK]=qBittorrent
|
Name[zh_HK]=qBittorrent
|
||||||
Comment[zh_TW]=經由 BitTorrent 下載並分享檔案
|
|
||||||
GenericName[zh_TW]=BitTorrent 用戶端
|
GenericName[zh_TW]=BitTorrent 用戶端
|
||||||
|
Comment[zh_TW]=使用 BitTorrent 下載並分享檔案
|
||||||
Name[zh_TW]=qBittorrent
|
Name[zh_TW]=qBittorrent
|
||||||
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
|
|
||||||
GenericName[eo]=BitTorrent-kliento
|
GenericName[eo]=BitTorrent-kliento
|
||||||
|
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
|
||||||
Name[eo]=qBittorrent
|
Name[eo]=qBittorrent
|
||||||
Comment[kk]=BitTorrent арқылы файл жүктеу және бөлісу
|
|
||||||
GenericName[kk]=BitTorrent клиенті
|
GenericName[kk]=BitTorrent клиенті
|
||||||
|
Comment[kk]=BitTorrent арқылы файл жүктеу және бөлісу
|
||||||
Name[kk]=qBittorrent
|
Name[kk]=qBittorrent
|
||||||
Comment[en_AU]=Download and share files over BitTorrent
|
|
||||||
GenericName[en_AU]=BitTorrent client
|
GenericName[en_AU]=BitTorrent client
|
||||||
|
Comment[en_AU]=Download and share files over BitTorrent
|
||||||
Name[en_AU]=qBittorrent
|
Name[en_AU]=qBittorrent
|
||||||
Name[rm]=qBittorrent
|
Name[rm]=qBittorrent
|
||||||
Name[jv]=qBittorrent
|
Name[jv]=qBittorrent
|
||||||
Comment[oc]=Telecargar e partejar de fichièrs amb BitTorrent
|
|
||||||
GenericName[oc]=Client BitTorrent
|
GenericName[oc]=Client BitTorrent
|
||||||
|
Comment[oc]=Telecargar e partejar de fichièrs amb BitTorrent
|
||||||
Name[oc]=qBittorrent
|
Name[oc]=qBittorrent
|
||||||
Name[ug]=qBittorrent
|
Name[ug]=qBittorrent
|
||||||
Name[yi]=qBittorrent
|
Name[yi]=qBittorrent
|
||||||
Comment[nqo]=ߞߐߕߐ߯ߘߐ ߟߎ߬ ߟߊߖߌ߰ ߞߊ߬ ߓߊ߲߫ ߞߵߊ߬ߟߎ߬ ߘߐߕߟߊ߫ ߓߌߙߏߙߍ߲ߕ ߞߊ߲߬
|
|
||||||
GenericName[nqo]=ߓߌߙߏߙߍ߲ߕ ߕߣߐ߬ߓߐ߬ߟߊ
|
GenericName[nqo]=ߓߌߙߏߙߍ߲ߕ ߕߣߐ߬ߓߐ߬ߟߊ
|
||||||
|
Comment[nqo]=ߞߐߕߐ߯ߘߐ ߟߎ߬ ߟߊߖߌ߰ ߞߊ߬ ߓߊ߲߫ ߞߵߊ߬ߟߎ߬ ߘߐߕߟߊ߫ ߓߌߙߏߙߍ߲ߕ ߞߊ߲߬
|
||||||
Name[nqo]=qBittorrent
|
Name[nqo]=qBittorrent
|
||||||
Comment[uz@Latn]=BitTorrent orqali fayllarni yuklab olish va baham ko‘rish
|
|
||||||
GenericName[uz@Latn]=BitTorrent mijozi
|
GenericName[uz@Latn]=BitTorrent mijozi
|
||||||
|
Comment[uz@Latn]=BitTorrent orqali fayllarni yuklab olish va baham ko‘rish
|
||||||
Name[uz@Latn]=qBittorrent
|
Name[uz@Latn]=qBittorrent
|
||||||
Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent
|
|
||||||
GenericName[ltg]=BitTorrent klients
|
GenericName[ltg]=BitTorrent klients
|
||||||
|
Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent
|
||||||
Name[ltg]=qBittorrent
|
Name[ltg]=qBittorrent
|
||||||
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
|
|
||||||
GenericName[hi_IN]=Bittorrent साधन
|
GenericName[hi_IN]=Bittorrent साधन
|
||||||
|
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
|
||||||
Name[hi_IN]=qBittorrent
|
Name[hi_IN]=qBittorrent
|
||||||
Comment[az@latin]=Faylları BitTorrent vasitəsilə endirin və paylaşın
|
|
||||||
GenericName[az@latin]=BitTorrent client
|
GenericName[az@latin]=BitTorrent client
|
||||||
|
Comment[az@latin]=Faylları BitTorrent vasitəsilə endirin və paylaşın
|
||||||
Name[az@latin]=qBittorrent
|
Name[az@latin]=qBittorrent
|
||||||
Comment[lv_LV]=Lejupielādēt un koplietot failus ar BitTorrent
|
|
||||||
GenericName[lv_LV]=BitTorrent klients
|
GenericName[lv_LV]=BitTorrent klients
|
||||||
|
Comment[lv_LV]=Lejupielādēt un koplietot failus ar BitTorrent
|
||||||
Name[lv_LV]=qBittorrent
|
Name[lv_LV]=qBittorrent
|
||||||
Comment[ms_MY]=Muat turun dan kongsi fail melalui BitTorrent
|
|
||||||
GenericName[ms_MY]=Klien BitTorrent
|
GenericName[ms_MY]=Klien BitTorrent
|
||||||
|
Comment[ms_MY]=Muat turun dan kongsi fail melalui BitTorrent
|
||||||
Name[ms_MY]=qBittorrent
|
Name[ms_MY]=qBittorrent
|
||||||
Comment[mn_MN]=BitTorrent-оор файлуудаа тат, түгээ
|
|
||||||
GenericName[mn_MN]=BitTorrent татагч
|
GenericName[mn_MN]=BitTorrent татагч
|
||||||
|
Comment[mn_MN]=BitTorrent-оор файлуудаа тат, түгээ
|
||||||
Name[mn_MN]=qBittorrent
|
Name[mn_MN]=qBittorrent
|
||||||
Comment[ne_NP]=फाइलहरू डाउनलोड गर्नुहोस् र BitTorrent मा साझा गर्नुहोस्
|
|
||||||
GenericName[ne_NP]=BitTorrent क्लाइन्ट
|
GenericName[ne_NP]=BitTorrent क्लाइन्ट
|
||||||
|
Comment[ne_NP]=फाइलहरू डाउनलोड गर्नुहोस् र BitTorrent मा साझा गर्नुहोस्
|
||||||
Name[ne_NP]=qBittorrent
|
Name[ne_NP]=qBittorrent
|
||||||
Comment[pt_PT]=Transferir e partilhar ficheiros por BitTorrent
|
|
||||||
GenericName[pt_PT]=Cliente BitTorrent
|
GenericName[pt_PT]=Cliente BitTorrent
|
||||||
|
Comment[pt_PT]=Transferir e partilhar ficheiros por BitTorrent
|
||||||
Name[pt_PT]=qBittorrent
|
Name[pt_PT]=qBittorrent
|
||||||
|
GenericName[si_LK]=BitTorrent සේවාදායකයා
|
||||||
|
Comment[si_LK]=BitTorrent හරහා ගොනු බාගත කර බෙදාගන්න.
|
||||||
Name[si_LK]=qBittorrent
|
Name[si_LK]=qBittorrent
|
||||||
|
|
|
@ -62,6 +62,6 @@
|
||||||
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
|
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
|
||||||
<content_rating type="oars-1.1"/>
|
<content_rating type="oars-1.1"/>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="5.0.0~beta1" date="2024-03-19"/>
|
<release version="5.0.5" date="2025-04-13"/>
|
||||||
</releases>
|
</releases>
|
||||||
</component>
|
</component>
|
||||||
|
|
4
dist/windows/config.nsh
vendored
4
dist/windows/config.nsh
vendored
|
@ -14,7 +14,7 @@
|
||||||
; 4.5.1.3 -> good
|
; 4.5.1.3 -> good
|
||||||
; 4.5.1.3.2 -> bad
|
; 4.5.1.3.2 -> bad
|
||||||
; 4.5.0beta -> bad
|
; 4.5.0beta -> bad
|
||||||
!define /ifndef QBT_VERSION "5.0.0"
|
!define /ifndef QBT_VERSION "5.0.5"
|
||||||
|
|
||||||
; Option that controls the installer's window name
|
; Option that controls the installer's window name
|
||||||
; If set, its value will be used like this:
|
; If set, its value will be used like this:
|
||||||
|
@ -86,7 +86,7 @@ OutFile "qbittorrent_${QBT_INSTALLER_FILENAME}_setup.exe"
|
||||||
;Installer Version Information
|
;Installer Version Information
|
||||||
VIAddVersionKey "ProductName" "qBittorrent"
|
VIAddVersionKey "ProductName" "qBittorrent"
|
||||||
VIAddVersionKey "CompanyName" "The qBittorrent project"
|
VIAddVersionKey "CompanyName" "The qBittorrent project"
|
||||||
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2024 The qBittorrent project"
|
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2025 The qBittorrent project"
|
||||||
VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client"
|
VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client"
|
||||||
VIAddVersionKey "FileVersion" "${QBT_VERSION}"
|
VIAddVersionKey "FileVersion" "${QBT_VERSION}"
|
||||||
|
|
||||||
|
|
|
@ -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_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_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_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_ENGLISH} "Uninstall qBittorrent"
|
||||||
LangString inst_uninstall_link_description ${LANG_ITALIAN} "Disinstalla 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_ENGLISH} "Add Windows Firewall rule"
|
||||||
LangString inst_firewall ${LANG_LUXEMBOURGISH} "Reegel an der Windows Firewall dobäisetzen"
|
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_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_ENGLISH} "Adding Windows Firewall rule"
|
||||||
LangString inst_firewallinfo ${LANG_LUXEMBOURGISH} "Reegel an der Windows Firewall dobäisetzen"
|
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."
|
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||||
|
|
|
@ -7,7 +7,7 @@ LangString inst_desktop ${LANG_PORTUGUESE} "Criar atalho no ambiente de trabalho
|
||||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||||
LangString inst_startmenu ${LANG_PORTUGUESE} "Criar atalho no menu Iniciar"
|
LangString inst_startmenu ${LANG_PORTUGUESE} "Criar atalho no menu Iniciar"
|
||||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||||
LangString inst_startup ${LANG_PORTUGUESE} "Iniciar o qBittorrent na inicialização do Windows"
|
LangString inst_startup ${LANG_PORTUGUESE} "Iniciar o qBittorrent no arranque do Windows"
|
||||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||||
LangString inst_torrent ${LANG_PORTUGUESE} "Abrir ficheiros .torrent com o qBittorrent"
|
LangString inst_torrent ${LANG_PORTUGUESE} "Abrir ficheiros .torrent com o qBittorrent"
|
||||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||||
|
@ -29,7 +29,7 @@ LangString launch_qbt ${LANG_PORTUGUESE} "Iniciar qBittorrent."
|
||||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||||
LangString inst_requires_64bit ${LANG_PORTUGUESE} "Este instalador funciona apenas em versões Windows de 64 bits."
|
LangString inst_requires_64bit ${LANG_PORTUGUESE} "Este instalador funciona apenas em versões Windows de 64 bits."
|
||||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||||
LangString inst_requires_win10 ${LANG_PORTUGUESE} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
LangString inst_requires_win10 ${LANG_PORTUGUESE} "Este instalador requer, pelo menos, o Windows 10 (1809) / Windows Server 2019."
|
||||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||||
LangString inst_uninstall_link_description ${LANG_PORTUGUESE} "Desinstalar qBittorrent"
|
LangString inst_uninstall_link_description ${LANG_PORTUGUESE} "Desinstalar qBittorrent"
|
||||||
|
|
||||||
|
|
|
@ -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_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||||
LangString inst_uninstall_question ${LANG_SIMPCHINESE} "当前版本会被卸载。 用户设置和种子会被完整保留。"
|
LangString inst_uninstall_question ${LANG_SIMPCHINESE} "当前版本会被卸载。 用户设置和种子会被完整保留。"
|
||||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
;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_ENGLISH} "Launch qBittorrent."
|
||||||
LangString launch_qbt ${LANG_SIMPCHINESE} "启动 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_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||||
LangString inst_requires_64bit ${LANG_SIMPCHINESE} "此安装程序仅支持 64 位 Windows 系统。"
|
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_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_ENGLISH} "Uninstall qBittorrent"
|
||||||
LangString inst_uninstall_link_description ${LANG_SIMPCHINESE} "卸载 qBittorrent"
|
LangString inst_uninstall_link_description ${LANG_SIMPCHINESE} "卸载 qBittorrent"
|
||||||
|
|
||||||
|
|
12
dist/windows/installer-translations/swedish.nsh
vendored
12
dist/windows/installer-translations/swedish.nsh
vendored
|
@ -7,21 +7,21 @@ LangString inst_desktop ${LANG_SWEDISH} "Skapa skrivbordsgenväg"
|
||||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||||
LangString inst_startmenu ${LANG_SWEDISH} "Skapa startmenygenväg"
|
LangString inst_startmenu ${LANG_SWEDISH} "Skapa startmenygenväg"
|
||||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||||
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows start"
|
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows-uppstart"
|
||||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||||
LangString inst_torrent ${LANG_SWEDISH} "Öppna .torrent-filer med qBittorrent"
|
LangString inst_torrent ${LANG_SWEDISH} "Öppna .torrent-filer med qBittorrent"
|
||||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||||
LangString inst_magnet ${LANG_SWEDISH} "Öppna magnetlänkar med qBittorrent"
|
LangString inst_magnet ${LANG_SWEDISH} "Öppna magnetlänkar med qBittorrent"
|
||||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||||
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggregel"
|
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggsregel"
|
||||||
;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_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||||
LangString inst_pathlimit ${LANG_SWEDISH} "Inaktivera gränsen för Windows-sökvägslängd (260 tecken MAX_PATH-begränsning, kräver Windows 10 1607 eller senare)"
|
LangString inst_pathlimit ${LANG_SWEDISH} "Inaktivera gränsen för Windows-sökvägslängd (260 tecken MAX_PATH-begränsning, kräver Windows 10 1607 eller senare)"
|
||||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||||
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggregel"
|
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggsregel"
|
||||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||||
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du installerar."
|
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du installerar."
|
||||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||||
LangString inst_uninstall_question ${LANG_SWEDISH} "Nuvarande version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
|
LangString inst_uninstall_question ${LANG_SWEDISH} "Aktuell version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
|
||||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||||
LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
|
LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
|
||||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||||
|
@ -53,7 +53,7 @@ LangString remove_firewallinfo ${LANG_SWEDISH} "Tar bort Windows-brandväggsrege
|
||||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||||
LangString remove_cache ${LANG_SWEDISH} "Ta bort torrenter och cachade data"
|
LangString remove_cache ${LANG_SWEDISH} "Ta bort torrenter och cachade data"
|
||||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||||
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du avinstallerar."
|
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du avinstallerar."
|
||||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||||
LangString uninst_tor_warn ${LANG_SWEDISH} "Tar inte bort .torrent-association. Den är associerad med:"
|
LangString uninst_tor_warn ${LANG_SWEDISH} "Tar inte bort .torrent-association. Den är associerad med:"
|
||||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||||
|
|
|
@ -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_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||||
LangString inst_requires_64bit ${LANG_TRADCHINESE} "此安裝程式僅支援 64 位元版本的 Windows。"
|
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_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_ENGLISH} "Uninstall qBittorrent"
|
||||||
LangString inst_uninstall_link_description ${LANG_TRADCHINESE} "移除 qBittorrent"
|
LangString inst_uninstall_link_description ${LANG_TRADCHINESE} "移除 qBittorrent"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez
|
* Copyright (C) 2006 Christophe Dumez
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -124,6 +124,28 @@ namespace
|
||||||
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
|
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
const QString PARAM_ADDSTOPPED = u"@addStopped"_s;
|
||||||
|
const QString PARAM_CATEGORY = u"@category"_s;
|
||||||
|
const QString PARAM_FIRSTLASTPIECEPRIORITY = u"@firstLastPiecePriority"_s;
|
||||||
|
const QString PARAM_SAVEPATH = u"@savePath"_s;
|
||||||
|
const QString PARAM_SEQUENTIAL = u"@sequential"_s;
|
||||||
|
const QString PARAM_SKIPCHECKING = u"@skipChecking"_s;
|
||||||
|
const QString PARAM_SKIPDIALOG = u"@skipDialog"_s;
|
||||||
|
|
||||||
|
QString bindParamValue(const QStringView paramName, const QStringView paramValue)
|
||||||
|
{
|
||||||
|
return paramName + u'=' + paramValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<QStringView, QStringView> parseParam(const QStringView param)
|
||||||
|
{
|
||||||
|
const qsizetype sepIndex = param.indexOf(u'=');
|
||||||
|
if (sepIndex >= 0)
|
||||||
|
return {param.first(sepIndex), param.sliced(sepIndex + 1)};
|
||||||
|
|
||||||
|
return {param, {}};
|
||||||
|
}
|
||||||
|
|
||||||
QString serializeParams(const QBtCommandLineParameters ¶ms)
|
QString serializeParams(const QBtCommandLineParameters ¶ms)
|
||||||
{
|
{
|
||||||
QStringList result;
|
QStringList result;
|
||||||
|
@ -138,85 +160,86 @@ namespace
|
||||||
const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
|
const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
|
||||||
|
|
||||||
if (!addTorrentParams.savePath.isEmpty())
|
if (!addTorrentParams.savePath.isEmpty())
|
||||||
result.append(u"@savePath=" + addTorrentParams.savePath.data());
|
result.append(bindParamValue(PARAM_SAVEPATH, addTorrentParams.savePath.data()));
|
||||||
|
|
||||||
if (addTorrentParams.addStopped.has_value())
|
if (addTorrentParams.addStopped.has_value())
|
||||||
result.append(*addTorrentParams.addStopped ? u"@addStopped=1"_s : u"@addStopped=0"_s);
|
result.append(bindParamValue(PARAM_ADDSTOPPED, (*addTorrentParams.addStopped ? u"1" : u"0")));
|
||||||
|
|
||||||
if (addTorrentParams.skipChecking)
|
if (addTorrentParams.skipChecking)
|
||||||
result.append(u"@skipChecking"_s);
|
result.append(PARAM_SKIPCHECKING);
|
||||||
|
|
||||||
if (!addTorrentParams.category.isEmpty())
|
if (!addTorrentParams.category.isEmpty())
|
||||||
result.append(u"@category=" + addTorrentParams.category);
|
result.append(bindParamValue(PARAM_CATEGORY, addTorrentParams.category));
|
||||||
|
|
||||||
if (addTorrentParams.sequential)
|
if (addTorrentParams.sequential)
|
||||||
result.append(u"@sequential"_s);
|
result.append(PARAM_SEQUENTIAL);
|
||||||
|
|
||||||
if (addTorrentParams.firstLastPiecePriority)
|
if (addTorrentParams.firstLastPiecePriority)
|
||||||
result.append(u"@firstLastPiecePriority"_s);
|
result.append(PARAM_FIRSTLASTPIECEPRIORITY);
|
||||||
|
|
||||||
if (params.skipDialog.has_value())
|
if (params.skipDialog.has_value())
|
||||||
result.append(*params.skipDialog ? u"@skipDialog=1"_s : u"@skipDialog=0"_s);
|
result.append(bindParamValue(PARAM_SKIPDIALOG, (*params.skipDialog ? u"1" : u"0")));
|
||||||
|
|
||||||
result += params.torrentSources;
|
result += params.torrentSources;
|
||||||
|
|
||||||
return result.join(PARAMS_SEPARATOR);
|
return result.join(PARAMS_SEPARATOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
QBtCommandLineParameters parseParams(const QString &str)
|
QBtCommandLineParameters parseParams(const QStringView str)
|
||||||
{
|
{
|
||||||
QBtCommandLineParameters parsedParams;
|
QBtCommandLineParameters parsedParams;
|
||||||
BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
|
BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
|
||||||
|
|
||||||
for (QString param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
|
for (QStringView param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
|
||||||
{
|
{
|
||||||
param = param.trimmed();
|
param = param.trimmed();
|
||||||
|
const auto [paramName, paramValue] = parseParam(param);
|
||||||
|
|
||||||
// Process strings indicating options specified by the user.
|
// Process strings indicating options specified by the user.
|
||||||
|
|
||||||
if (param.startsWith(u"@savePath="))
|
if (paramName == PARAM_SAVEPATH)
|
||||||
{
|
{
|
||||||
addTorrentParams.savePath = Path(param.mid(10));
|
addTorrentParams.savePath = Path(paramValue.toString());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param.startsWith(u"@addStopped="))
|
if (paramName == PARAM_ADDSTOPPED)
|
||||||
{
|
{
|
||||||
addTorrentParams.addStopped = (QStringView(param).mid(11).toInt() != 0);
|
addTorrentParams.addStopped = (paramValue.toInt() != 0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param == u"@skipChecking")
|
if (paramName == PARAM_SKIPCHECKING)
|
||||||
{
|
{
|
||||||
addTorrentParams.skipChecking = true;
|
addTorrentParams.skipChecking = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param.startsWith(u"@category="))
|
if (paramName == PARAM_CATEGORY)
|
||||||
{
|
{
|
||||||
addTorrentParams.category = param.mid(10);
|
addTorrentParams.category = paramValue.toString();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param == u"@sequential")
|
if (paramName == PARAM_SEQUENTIAL)
|
||||||
{
|
{
|
||||||
addTorrentParams.sequential = true;
|
addTorrentParams.sequential = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param == u"@firstLastPiecePriority")
|
if (paramName == PARAM_FIRSTLASTPIECEPRIORITY)
|
||||||
{
|
{
|
||||||
addTorrentParams.firstLastPiecePriority = true;
|
addTorrentParams.firstLastPiecePriority = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param.startsWith(u"@skipDialog="))
|
if (paramName == PARAM_SKIPDIALOG)
|
||||||
{
|
{
|
||||||
parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0);
|
parsedParams.skipDialog = (paramValue.toInt() != 0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedParams.torrentSources.append(param);
|
parsedParams.torrentSources.append(param.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
return parsedParams;
|
return parsedParams;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -58,10 +58,6 @@
|
||||||
#include <QSplashScreen>
|
#include <QSplashScreen>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
|
||||||
#include <QOperatingSystemVersion>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef QBT_STATIC_QT
|
#ifdef QBT_STATIC_QT
|
||||||
#include <QtPlugin>
|
#include <QtPlugin>
|
||||||
Q_IMPORT_PLUGIN(QICOPlugin)
|
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
|
// We must save it here because QApplication constructor may change it
|
||||||
const bool isOneArg = (argc == 2);
|
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
|
// `app` must be declared out of try block to allow display message box in case of exception
|
||||||
std::unique_ptr<Application> app;
|
std::unique_ptr<Application> app;
|
||||||
try
|
try
|
||||||
|
|
|
@ -38,6 +38,8 @@ add_library(qbt_base STATIC
|
||||||
bittorrent/torrent.h
|
bittorrent/torrent.h
|
||||||
bittorrent/torrentcontenthandler.h
|
bittorrent/torrentcontenthandler.h
|
||||||
bittorrent/torrentcontentlayout.h
|
bittorrent/torrentcontentlayout.h
|
||||||
|
bittorrent/torrentcontentremoveoption.h
|
||||||
|
bittorrent/torrentcontentremover.h
|
||||||
bittorrent/torrentcreationmanager.h
|
bittorrent/torrentcreationmanager.h
|
||||||
bittorrent/torrentcreationtask.h
|
bittorrent/torrentcreationtask.h
|
||||||
bittorrent/torrentcreator.h
|
bittorrent/torrentcreator.h
|
||||||
|
@ -145,6 +147,7 @@ add_library(qbt_base STATIC
|
||||||
bittorrent/sslparameters.cpp
|
bittorrent/sslparameters.cpp
|
||||||
bittorrent/torrent.cpp
|
bittorrent/torrent.cpp
|
||||||
bittorrent/torrentcontenthandler.cpp
|
bittorrent/torrentcontenthandler.cpp
|
||||||
|
bittorrent/torrentcontentremover.cpp
|
||||||
bittorrent/torrentcreationmanager.cpp
|
bittorrent/torrentcreationmanager.cpp
|
||||||
bittorrent/torrentcreationtask.cpp
|
bittorrent/torrentcreationtask.cpp
|
||||||
bittorrent/torrentcreator.cpp
|
bittorrent/torrentcreator.cpp
|
||||||
|
|
|
@ -157,10 +157,36 @@ void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QStr
|
||||||
emit addTorrentFailed(source, reason);
|
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")
|
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);
|
emit addTorrentFailed(source, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,11 +195,9 @@ void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_p
|
||||||
m_guardedTorrentFiles.emplace(source, std::move(torrentFileGuard));
|
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);
|
return m_guardedTorrentFiles.take(source);
|
||||||
if (torrentFileGuard)
|
|
||||||
torrentFileGuard->setAutoRemove(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
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))
|
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
||||||
{
|
{
|
||||||
// a duplicate torrent is being added
|
// a duplicate torrent is being added
|
||||||
|
handleDuplicateTorrent(source, torrentDescr, torrent);
|
||||||
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"));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,9 +72,9 @@ protected:
|
||||||
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||||
, const BitTorrent::AddTorrentParams &addTorrentParams);
|
, const BitTorrent::AddTorrentParams &addTorrentParams);
|
||||||
void handleAddTorrentFailed(const QString &source, const QString &reason);
|
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 setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard);
|
||||||
void releaseTorrentFileGuard(const QString &source);
|
std::shared_ptr<TorrentFileGuard> releaseTorrentFileGuard(const QString &source);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onDownloadFinished(const Net::DownloadResult &result);
|
void onDownloadFinished(const Net::DownloadResult &result);
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
#include "base/algorithm.h"
|
|
||||||
#include "base/exceptions.h"
|
#include "base/exceptions.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/logger.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)
|
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)
|
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}
|
, m_savePath {params.path}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ namespace
|
||||||
{
|
{
|
||||||
const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_s;
|
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_META = u"meta"_s;
|
||||||
const QString DB_TABLE_TORRENTS = u"torrents"_s;
|
const QString DB_TABLE_TORRENTS = u"torrents"_s;
|
||||||
|
@ -630,6 +630,30 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
|
||||||
if (fromVersion <= 6)
|
if (fromVersion <= 6)
|
||||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SHARE_LIMIT_ACTION, "TEXT NOT 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});
|
const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE});
|
||||||
if (!query.prepare(updateMetaVersionQuery))
|
if (!query.prepare(updateMetaVersionQuery))
|
||||||
throw RuntimeError(query.lastError().text());
|
throw RuntimeError(query.lastError().text());
|
||||||
|
|
|
@ -367,6 +367,10 @@ void PeerInfo::determineFlags()
|
||||||
if (useUTPSocket())
|
if (useUTPSocket())
|
||||||
updateFlags(u'P', C_UTP);
|
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_flags.chop(1);
|
||||||
m_flagsDescription.chop(1);
|
m_flagsDescription.chop(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,17 +37,12 @@
|
||||||
#include "addtorrentparams.h"
|
#include "addtorrentparams.h"
|
||||||
#include "categoryoptions.h"
|
#include "categoryoptions.h"
|
||||||
#include "sharelimitaction.h"
|
#include "sharelimitaction.h"
|
||||||
|
#include "torrentcontentremoveoption.h"
|
||||||
#include "trackerentry.h"
|
#include "trackerentry.h"
|
||||||
#include "trackerentrystatus.h"
|
#include "trackerentrystatus.h"
|
||||||
|
|
||||||
class QString;
|
class QString;
|
||||||
|
|
||||||
enum DeleteOption
|
|
||||||
{
|
|
||||||
DeleteTorrent,
|
|
||||||
DeleteTorrentAndFiles
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
class InfoHash;
|
class InfoHash;
|
||||||
|
@ -58,6 +53,12 @@ namespace BitTorrent
|
||||||
struct CacheStatus;
|
struct CacheStatus;
|
||||||
struct SessionStatus;
|
struct SessionStatus;
|
||||||
|
|
||||||
|
enum class TorrentRemoveOption
|
||||||
|
{
|
||||||
|
KeepContent,
|
||||||
|
RemoveContent
|
||||||
|
};
|
||||||
|
|
||||||
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
|
// 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.
|
// 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
|
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
|
||||||
|
@ -91,7 +92,8 @@ namespace BitTorrent
|
||||||
{
|
{
|
||||||
Default = 0,
|
Default = 0,
|
||||||
MMap = 1,
|
MMap = 1,
|
||||||
Posix = 2
|
Posix = 2,
|
||||||
|
SimplePreadPwrite = 3
|
||||||
};
|
};
|
||||||
Q_ENUM_NS(DiskIOType)
|
Q_ENUM_NS(DiskIOType)
|
||||||
|
|
||||||
|
@ -425,7 +427,7 @@ namespace BitTorrent
|
||||||
virtual void setExcludedFileNamesEnabled(bool enabled) = 0;
|
virtual void setExcludedFileNamesEnabled(bool enabled) = 0;
|
||||||
virtual QStringList excludedFileNames() const = 0;
|
virtual QStringList excludedFileNames() const = 0;
|
||||||
virtual void setExcludedFileNames(const QStringList &newList) = 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 QStringList bannedIPs() const = 0;
|
||||||
virtual void setBannedIPs(const QStringList &newList) = 0;
|
virtual void setBannedIPs(const QStringList &newList) = 0;
|
||||||
virtual ResumeDataStorageType resumeDataStorageType() const = 0;
|
virtual ResumeDataStorageType resumeDataStorageType() const = 0;
|
||||||
|
@ -434,6 +436,8 @@ namespace BitTorrent
|
||||||
virtual void setMergeTrackersEnabled(bool enabled) = 0;
|
virtual void setMergeTrackersEnabled(bool enabled) = 0;
|
||||||
virtual bool isStartPaused() const = 0;
|
virtual bool isStartPaused() const = 0;
|
||||||
virtual void setStartPaused(bool value) = 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 isRestored() const = 0;
|
||||||
|
|
||||||
|
@ -453,7 +457,7 @@ namespace BitTorrent
|
||||||
|
|
||||||
virtual bool isKnownTorrent(const InfoHash &infoHash) const = 0;
|
virtual bool isKnownTorrent(const InfoHash &infoHash) const = 0;
|
||||||
virtual bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) = 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 downloadMetadata(const TorrentDescriptor &torrentDescr) = 0;
|
||||||
virtual bool cancelDownloadMetadata(const TorrentID &id) = 0;
|
virtual bool cancelDownloadMetadata(const TorrentID &id) = 0;
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,7 @@
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonValue>
|
#include <QJsonValue>
|
||||||
|
#include <QMutexLocker>
|
||||||
#include <QNetworkAddressEntry>
|
#include <QNetworkAddressEntry>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
|
@ -101,6 +102,7 @@
|
||||||
#include "nativesessionextension.h"
|
#include "nativesessionextension.h"
|
||||||
#include "portforwarderimpl.h"
|
#include "portforwarderimpl.h"
|
||||||
#include "resumedatastorage.h"
|
#include "resumedatastorage.h"
|
||||||
|
#include "torrentcontentremover.h"
|
||||||
#include "torrentdescriptor.h"
|
#include "torrentdescriptor.h"
|
||||||
#include "torrentimpl.h"
|
#include "torrentimpl.h"
|
||||||
#include "tracker.h"
|
#include "tracker.h"
|
||||||
|
@ -525,6 +527,7 @@ SessionImpl::SessionImpl(QObject *parent)
|
||||||
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
|
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_s), 3}
|
||||||
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
|
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_s), 3}
|
||||||
, m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_s), 3}
|
, m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_s), 3}
|
||||||
|
, m_torrentContentRemoveOption {BITTORRENT_SESSION_KEY(u"TorrentContentRemoveOption"_s), TorrentContentRemoveOption::Delete}
|
||||||
, m_startPaused {BITTORRENT_SESSION_KEY(u"StartPaused"_s)}
|
, m_startPaused {BITTORRENT_SESSION_KEY(u"StartPaused"_s)}
|
||||||
, m_seedingLimitTimer {new QTimer(this)}
|
, m_seedingLimitTimer {new QTimer(this)}
|
||||||
, m_resumeDataTimer {new QTimer(this)}
|
, m_resumeDataTimer {new QTimer(this)}
|
||||||
|
@ -550,7 +553,14 @@ SessionImpl::SessionImpl(QObject *parent)
|
||||||
, this, [this]() { m_recentErroredTorrents.clear(); });
|
, this, [this]() { m_recentErroredTorrents.clear(); });
|
||||||
|
|
||||||
m_seedingLimitTimer->setInterval(10s);
|
m_seedingLimitTimer->setInterval(10s);
|
||||||
connect(m_seedingLimitTimer, &QTimer::timeout, this, &SessionImpl::processShareLimits);
|
connect(m_seedingLimitTimer, &QTimer::timeout, this, [this]
|
||||||
|
{
|
||||||
|
// We shouldn't iterate over `m_torrents` in the loop below
|
||||||
|
// since `deleteTorrent()` modifies it indirectly
|
||||||
|
const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
|
||||||
|
for (TorrentImpl *torrent : torrents)
|
||||||
|
processTorrentShareLimits(torrent);
|
||||||
|
});
|
||||||
|
|
||||||
initializeNativeSession();
|
initializeNativeSession();
|
||||||
configureComponents();
|
configureComponents();
|
||||||
|
@ -586,6 +596,11 @@ SessionImpl::SessionImpl(QObject *parent)
|
||||||
connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
|
connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
|
||||||
connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
|
connect(m_fileSearcher, &FileSearcher::searchFinished, this, &SessionImpl::fileSearchFinished);
|
||||||
|
|
||||||
|
m_torrentContentRemover = new TorrentContentRemover;
|
||||||
|
m_torrentContentRemover->moveToThread(m_ioThread.get());
|
||||||
|
connect(m_ioThread.get(), &QThread::finished, m_torrentContentRemover, &QObject::deleteLater);
|
||||||
|
connect(m_torrentContentRemover, &TorrentContentRemover::jobFinished, this, &SessionImpl::torrentContentRemovingFinished);
|
||||||
|
|
||||||
m_ioThread->start();
|
m_ioThread->start();
|
||||||
|
|
||||||
initMetrics();
|
initMetrics();
|
||||||
|
@ -604,7 +619,7 @@ SessionImpl::~SessionImpl()
|
||||||
{
|
{
|
||||||
m_nativeSession->pause();
|
m_nativeSession->pause();
|
||||||
|
|
||||||
const qint64 timeout = (m_shutdownTimeout >= 0) ? (m_shutdownTimeout * 1000) : -1;
|
const auto timeout = (m_shutdownTimeout >= 0) ? (static_cast<qint64>(m_shutdownTimeout) * 1000) : -1;
|
||||||
const QDeadlineTimer shutdownDeadlineTimer {timeout};
|
const QDeadlineTimer shutdownDeadlineTimer {timeout};
|
||||||
|
|
||||||
if (m_torrentsQueueChanged)
|
if (m_torrentsQueueChanged)
|
||||||
|
@ -1592,7 +1607,7 @@ void SessionImpl::endStartup(ResumeSessionContext *context)
|
||||||
reannounceToAllTrackers();
|
reannounceToAllTrackers();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_wakeupCheckTimestamp = QDateTime::currentDateTime();
|
m_wakeupCheckTimestamp = now;
|
||||||
});
|
});
|
||||||
m_wakeupCheckTimestamp = QDateTime::currentDateTime();
|
m_wakeupCheckTimestamp = QDateTime::currentDateTime();
|
||||||
m_wakeupCheckTimer->start(30s);
|
m_wakeupCheckTimer->start(30s);
|
||||||
|
@ -1624,6 +1639,13 @@ void SessionImpl::initializeNativeSession()
|
||||||
#ifdef QBT_USES_LIBTORRENT2
|
#ifdef QBT_USES_LIBTORRENT2
|
||||||
// preserve the same behavior as in earlier libtorrent versions
|
// preserve the same behavior as in earlier libtorrent versions
|
||||||
pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
|
pack.set_bool(lt::settings_pack::enable_set_file_valid_data, true);
|
||||||
|
|
||||||
|
// This is a special case. We use MMap disk IO but tweak it to always fallback to pread/pwrite.
|
||||||
|
if (diskIOType() == DiskIOType::SimplePreadPwrite)
|
||||||
|
{
|
||||||
|
pack.set_int(lt::settings_pack::mmap_file_size_cutoff, std::numeric_limits<int>::max());
|
||||||
|
pack.set_int(lt::settings_pack::disk_write_mode, lt::settings_pack::mmap_write_mode_t::always_pwrite);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
lt::session_params sessionParams {std::move(pack), {}};
|
lt::session_params sessionParams {std::move(pack), {}};
|
||||||
|
@ -1634,6 +1656,7 @@ void SessionImpl::initializeNativeSession()
|
||||||
sessionParams.disk_io_constructor = customPosixDiskIOConstructor;
|
sessionParams.disk_io_constructor = customPosixDiskIOConstructor;
|
||||||
break;
|
break;
|
||||||
case DiskIOType::MMap:
|
case DiskIOType::MMap:
|
||||||
|
case DiskIOType::SimplePreadPwrite:
|
||||||
sessionParams.disk_io_constructor = customMMapDiskIOConstructor;
|
sessionParams.disk_io_constructor = customMMapDiskIOConstructor;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -2236,21 +2259,16 @@ void SessionImpl::populateAdditionalTrackers()
|
||||||
m_additionalTrackerEntries = parseTrackerEntries(additionalTrackers());
|
m_additionalTrackerEntries = parseTrackerEntries(additionalTrackers());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::processShareLimits()
|
void SessionImpl::processTorrentShareLimits(TorrentImpl *torrent)
|
||||||
{
|
{
|
||||||
|
if (!torrent->isFinished() || torrent->isForced())
|
||||||
|
return;
|
||||||
|
|
||||||
const auto effectiveLimit = []<typename T>(const T limit, const T useGlobalLimit, const T globalLimit) -> T
|
const auto effectiveLimit = []<typename T>(const T limit, const T useGlobalLimit, const T globalLimit) -> T
|
||||||
{
|
{
|
||||||
return (limit == useGlobalLimit) ? globalLimit : limit;
|
return (limit == useGlobalLimit) ? globalLimit : limit;
|
||||||
};
|
};
|
||||||
|
|
||||||
// We shouldn't iterate over `m_torrents` in the loop below
|
|
||||||
// since `deleteTorrent()` modifies it indirectly
|
|
||||||
const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
|
|
||||||
for (const auto &[torrentID, torrent] : torrents.asKeyValueRange())
|
|
||||||
{
|
|
||||||
if (!torrent->isFinished() || torrent->isForced())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const qreal ratioLimit = effectiveLimit(torrent->ratioLimit(), Torrent::USE_GLOBAL_RATIO, globalMaxRatio());
|
const qreal ratioLimit = effectiveLimit(torrent->ratioLimit(), Torrent::USE_GLOBAL_RATIO, globalMaxRatio());
|
||||||
const int seedingTimeLimit = effectiveLimit(torrent->seedingTimeLimit(), Torrent::USE_GLOBAL_SEEDING_TIME, globalMaxSeedingMinutes());
|
const int seedingTimeLimit = effectiveLimit(torrent->seedingTimeLimit(), Torrent::USE_GLOBAL_SEEDING_TIME, globalMaxSeedingMinutes());
|
||||||
const int inactiveSeedingTimeLimit = effectiveLimit(torrent->inactiveSeedingTimeLimit(), Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME, globalMaxInactiveSeedingMinutes());
|
const int inactiveSeedingTimeLimit = effectiveLimit(torrent->inactiveSeedingTimeLimit(), Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME, globalMaxInactiveSeedingMinutes());
|
||||||
|
@ -2285,12 +2303,12 @@ void SessionImpl::processShareLimits()
|
||||||
if (shareLimitAction == ShareLimitAction::Remove)
|
if (shareLimitAction == ShareLimitAction::Remove)
|
||||||
{
|
{
|
||||||
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent."), torrentName));
|
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent."), torrentName));
|
||||||
deleteTorrent(torrentID);
|
removeTorrent(torrent->id(), TorrentRemoveOption::KeepContent);
|
||||||
}
|
}
|
||||||
else if (shareLimitAction == ShareLimitAction::RemoveWithContent)
|
else if (shareLimitAction == ShareLimitAction::RemoveWithContent)
|
||||||
{
|
{
|
||||||
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent and deleting its content."), torrentName));
|
LogMsg(u"%1 %2 %3"_s.arg(description, tr("Removing torrent and deleting its content."), torrentName));
|
||||||
deleteTorrent(torrentID, DeleteTorrentAndFiles);
|
removeTorrent(torrent->id(), TorrentRemoveOption::RemoveContent);
|
||||||
}
|
}
|
||||||
else if ((shareLimitAction == ShareLimitAction::Stop) && !torrent->isStopped())
|
else if ((shareLimitAction == ShareLimitAction::Stop) && !torrent->isStopped())
|
||||||
{
|
{
|
||||||
|
@ -2304,7 +2322,6 @@ void SessionImpl::processShareLimits()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
|
void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames)
|
||||||
{
|
{
|
||||||
|
@ -2331,6 +2348,19 @@ void SessionImpl::fileSearchFinished(const TorrentID &id, const Path &savePath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SessionImpl::torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage)
|
||||||
|
{
|
||||||
|
if (errorMessage.isEmpty())
|
||||||
|
{
|
||||||
|
LogMsg(tr("Torrent content removed. Torrent: \"%1\"").arg(torrentName));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogMsg(tr("Failed to remove torrent content. Torrent: \"%1\". Error: \"%2\"")
|
||||||
|
.arg(torrentName, errorMessage), Log::WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Torrent *SessionImpl::getTorrent(const TorrentID &id) const
|
Torrent *SessionImpl::getTorrent(const TorrentID &id) const
|
||||||
{
|
{
|
||||||
return m_torrents.value(id);
|
return m_torrents.value(id);
|
||||||
|
@ -2377,22 +2407,25 @@ void SessionImpl::banIP(const QString &ip)
|
||||||
|
|
||||||
// Delete a torrent from the session, given its hash
|
// Delete a torrent from the session, given its hash
|
||||||
// and from the disk, if the corresponding deleteOption is chosen
|
// and from the disk, if the corresponding deleteOption is chosen
|
||||||
bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
|
bool SessionImpl::removeTorrent(const TorrentID &id, const TorrentRemoveOption deleteOption)
|
||||||
{
|
{
|
||||||
TorrentImpl *const torrent = m_torrents.take(id);
|
TorrentImpl *const torrent = m_torrents.take(id);
|
||||||
if (!torrent)
|
if (!torrent)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
|
const TorrentID torrentID = torrent->id();
|
||||||
|
const QString torrentName = torrent->name();
|
||||||
|
|
||||||
|
qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrentID.toString()));
|
||||||
emit torrentAboutToBeRemoved(torrent);
|
emit torrentAboutToBeRemoved(torrent);
|
||||||
|
|
||||||
if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
|
if (const InfoHash infoHash = torrent->infoHash(); infoHash.isHybrid())
|
||||||
m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
|
m_hybridTorrentsByAltID.remove(TorrentID::fromSHA1Hash(infoHash.v1()));
|
||||||
|
|
||||||
// Remove it from session
|
// Remove it from session
|
||||||
if (deleteOption == DeleteTorrent)
|
if (deleteOption == TorrentRemoveOption::KeepContent)
|
||||||
{
|
{
|
||||||
m_removingTorrents[torrent->id()] = {torrent->name(), {}, deleteOption};
|
m_removingTorrents[torrentID] = {torrentName, torrent->actualStorageLocation(), {}, deleteOption};
|
||||||
|
|
||||||
const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
|
const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
|
||||||
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
|
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
|
||||||
|
@ -2414,7 +2447,7 @@ bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOp
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_removingTorrents[torrent->id()] = {torrent->name(), torrent->rootPath(), deleteOption};
|
m_removingTorrents[torrentID] = {torrentName, torrent->actualStorageLocation(), torrent->actualFilePaths(), deleteOption};
|
||||||
|
|
||||||
if (m_moveStorageQueue.size() > 1)
|
if (m_moveStorageQueue.size() > 1)
|
||||||
{
|
{
|
||||||
|
@ -2429,12 +2462,13 @@ bool SessionImpl::deleteTorrent(const TorrentID &id, const DeleteOption deleteOp
|
||||||
m_moveStorageQueue.erase(iter);
|
m_moveStorageQueue.erase(iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_files);
|
m_nativeSession->remove_torrent(torrent->nativeHandle(), lt::session::delete_partfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove it from torrent resume directory
|
// Remove it from torrent resume directory
|
||||||
m_resumeDataStorage->remove(torrent->id());
|
m_resumeDataStorage->remove(torrentID);
|
||||||
|
|
||||||
|
LogMsg(tr("Torrent removed. Torrent: \"%1\"").arg(torrentName));
|
||||||
delete torrent;
|
delete torrent;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -2462,7 +2496,7 @@ bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_files);
|
m_nativeSession->remove_torrent(nativeHandle);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2686,8 +2720,39 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
||||||
if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
|
if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (findTorrent(infoHash))
|
if (Torrent *torrent = findTorrent(infoHash))
|
||||||
|
{
|
||||||
|
// a duplicate torrent is being added
|
||||||
|
|
||||||
|
if (hasMetadata)
|
||||||
|
{
|
||||||
|
// Trying to set metadata to existing torrent in case if it has none
|
||||||
|
torrent->setMetadata(*source.info());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMergeTrackersEnabled())
|
||||||
|
{
|
||||||
|
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
|
||||||
|
.arg(torrent->name(), tr("Merging of trackers is disabled")));
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool isPrivate = torrent->isPrivate() || (hasMetadata && source.info()->isPrivate());
|
||||||
|
if (isPrivate)
|
||||||
|
{
|
||||||
|
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
|
||||||
|
.arg(torrent->name(), tr("Trackers cannot be merged because it is a private torrent")));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge trackers and web seeds
|
||||||
|
torrent->addTrackers(source.trackers());
|
||||||
|
torrent->addUrlSeeds(source.urlSeeds());
|
||||||
|
|
||||||
|
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
|
||||||
|
.arg(torrent->name(), tr("Trackers are merged from new source")));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// It looks illogical that we don't just use an existing handle,
|
// It looks illogical that we don't just use an existing handle,
|
||||||
// but as previous experience has shown, it actually creates unnecessary
|
// but as previous experience has shown, it actually creates unnecessary
|
||||||
|
@ -2751,6 +2816,19 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
||||||
loadTorrentParams.name = contentName;
|
loadTorrentParams.name = contentName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
||||||
|
|
||||||
|
Q_ASSERT(p.file_priorities.empty());
|
||||||
|
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
|
||||||
|
QList<DownloadPriority> filePriorities = addTorrentParams.filePriorities;
|
||||||
|
|
||||||
|
// Filename filter should be applied before `findIncompleteFiles()` is called.
|
||||||
|
if (filePriorities.isEmpty() && isExcludedFileNamesEnabled())
|
||||||
|
{
|
||||||
|
// Check file name blacklist when priorities are not explicitly set
|
||||||
|
applyFilenameFilter(filePaths, filePriorities);
|
||||||
|
}
|
||||||
|
|
||||||
if (!loadTorrentParams.hasFinishedStatus)
|
if (!loadTorrentParams.hasFinishedStatus)
|
||||||
{
|
{
|
||||||
const Path actualDownloadPath = useAutoTMM
|
const Path actualDownloadPath = useAutoTMM
|
||||||
|
@ -2759,36 +2837,20 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
|
||||||
isFindingIncompleteFiles = true;
|
isFindingIncompleteFiles = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
|
||||||
if (!isFindingIncompleteFiles)
|
if (!isFindingIncompleteFiles)
|
||||||
{
|
{
|
||||||
for (int index = 0; index < filePaths.size(); ++index)
|
for (int index = 0; index < filePaths.size(); ++index)
|
||||||
p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
|
p.renamed_files[nativeIndexes[index]] = filePaths.at(index).toString().toStdString();
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(p.file_priorities.empty());
|
|
||||||
Q_ASSERT(addTorrentParams.filePriorities.isEmpty() || (addTorrentParams.filePriorities.size() == nativeIndexes.size()));
|
|
||||||
|
|
||||||
const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
|
const int internalFilesCount = torrentInfo.nativeInfo()->files().num_files(); // including .pad files
|
||||||
// Use qBittorrent default priority rather than libtorrent's (4)
|
// Use qBittorrent default priority rather than libtorrent's (4)
|
||||||
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
|
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
|
||||||
|
|
||||||
if (addTorrentParams.filePriorities.isEmpty())
|
if (!filePriorities.isEmpty())
|
||||||
{
|
{
|
||||||
if (isExcludedFileNamesEnabled())
|
for (int i = 0; i < filePriorities.size(); ++i)
|
||||||
{
|
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(filePriorities[i]);
|
||||||
// Check file name blacklist when priorities are not explicitly set
|
|
||||||
for (int i = 0; i < filePaths.size(); ++i)
|
|
||||||
{
|
|
||||||
if (isFilenameExcluded(filePaths.at(i).filename()))
|
|
||||||
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = lt::dont_download;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int i = 0; i < addTorrentParams.filePriorities.size(); ++i)
|
|
||||||
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(addTorrentParams.filePriorities[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_ASSERT(p.ti);
|
Q_ASSERT(p.ti);
|
||||||
|
@ -3874,21 +3936,41 @@ void SessionImpl::populateExcludedFileNamesRegExpList()
|
||||||
|
|
||||||
for (const QString &str : excludedNames)
|
for (const QString &str : excludedNames)
|
||||||
{
|
{
|
||||||
const QString pattern = QRegularExpression::anchoredPattern(QRegularExpression::wildcardToRegularExpression(str));
|
const QString pattern = QRegularExpression::wildcardToRegularExpression(str);
|
||||||
const QRegularExpression re {pattern, QRegularExpression::CaseInsensitiveOption};
|
const QRegularExpression re {pattern, QRegularExpression::CaseInsensitiveOption};
|
||||||
m_excludedFileNamesRegExpList.append(re);
|
m_excludedFileNamesRegExpList.append(re);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SessionImpl::isFilenameExcluded(const QString &fileName) const
|
void SessionImpl::applyFilenameFilter(const PathList &files, QList<DownloadPriority> &priorities)
|
||||||
{
|
{
|
||||||
if (!isExcludedFileNamesEnabled())
|
if (!isExcludedFileNamesEnabled())
|
||||||
return false;
|
return;
|
||||||
|
|
||||||
return std::any_of(m_excludedFileNamesRegExpList.begin(), m_excludedFileNamesRegExpList.end(), [&fileName](const QRegularExpression &re)
|
const auto isFilenameExcluded = [patterns = m_excludedFileNamesRegExpList](const Path &fileName)
|
||||||
{
|
{
|
||||||
return re.match(fileName).hasMatch();
|
return std::any_of(patterns.begin(), patterns.end(), [&fileName](const QRegularExpression &re)
|
||||||
|
{
|
||||||
|
Path path = fileName;
|
||||||
|
while (!re.match(path.filename()).hasMatch())
|
||||||
|
{
|
||||||
|
path = path.parentPath();
|
||||||
|
if (path.isEmpty())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
priorities.resize(files.count(), DownloadPriority::Normal);
|
||||||
|
for (int i = 0; i < priorities.size(); ++i)
|
||||||
|
{
|
||||||
|
if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (isFilenameExcluded(files.at(i)))
|
||||||
|
priorities[i] = BitTorrent::DownloadPriority::Ignored;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::setBannedIPs(const QStringList &newList)
|
void SessionImpl::setBannedIPs(const QStringList &newList)
|
||||||
|
@ -3957,6 +4039,16 @@ void SessionImpl::setStartPaused(const bool value)
|
||||||
m_startPaused = value;
|
m_startPaused = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TorrentContentRemoveOption SessionImpl::torrentContentRemoveOption() const
|
||||||
|
{
|
||||||
|
return m_torrentContentRemoveOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionImpl::setTorrentContentRemoveOption(const TorrentContentRemoveOption option)
|
||||||
|
{
|
||||||
|
m_torrentContentRemoveOption = option;
|
||||||
|
}
|
||||||
|
|
||||||
QStringList SessionImpl::bannedIPs() const
|
QStringList SessionImpl::bannedIPs() const
|
||||||
{
|
{
|
||||||
return m_bannedIPs;
|
return m_bannedIPs;
|
||||||
|
@ -3974,15 +4066,30 @@ bool SessionImpl::isPaused() const
|
||||||
|
|
||||||
void SessionImpl::pause()
|
void SessionImpl::pause()
|
||||||
{
|
{
|
||||||
if (!m_isPaused)
|
if (m_isPaused)
|
||||||
{
|
return;
|
||||||
|
|
||||||
if (isRestored())
|
if (isRestored())
|
||||||
|
{
|
||||||
m_nativeSession->pause();
|
m_nativeSession->pause();
|
||||||
|
|
||||||
|
for (TorrentImpl *torrent : asConst(m_torrents))
|
||||||
|
{
|
||||||
|
torrent->resetTrackerEntryStatuses();
|
||||||
|
|
||||||
|
const QList<TrackerEntryStatus> trackers = torrent->trackers();
|
||||||
|
QHash<QString, TrackerEntryStatus> updatedTrackers;
|
||||||
|
updatedTrackers.reserve(trackers.size());
|
||||||
|
|
||||||
|
for (const TrackerEntryStatus &status : trackers)
|
||||||
|
updatedTrackers.emplace(status.url, status);
|
||||||
|
emit trackerEntryStatusesUpdated(torrent, updatedTrackers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_isPaused = true;
|
m_isPaused = true;
|
||||||
emit paused();
|
emit paused();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void SessionImpl::resume()
|
void SessionImpl::resume()
|
||||||
{
|
{
|
||||||
|
@ -5002,18 +5109,7 @@ void SessionImpl::handleTorrentChecked(TorrentImpl *const torrent)
|
||||||
|
|
||||||
void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
|
void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
|
||||||
{
|
{
|
||||||
LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
|
m_pendingFinishedTorrents.append(torrent);
|
||||||
emit torrentFinished(torrent);
|
|
||||||
|
|
||||||
if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
|
|
||||||
exportTorrentFile(torrent, exportPath);
|
|
||||||
|
|
||||||
const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
|
|
||||||
{
|
|
||||||
return !(torrent->isFinished() || torrent->isStopped() || torrent->isErrored());
|
|
||||||
});
|
|
||||||
if (!hasUnfinishedTorrents)
|
|
||||||
emit allTorrentsFinished();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
|
void SessionImpl::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
|
||||||
|
@ -5141,11 +5237,37 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
|
||||||
// Last job is completed for torrent that being removing, so actually remove it
|
// Last job is completed for torrent that being removing, so actually remove it
|
||||||
const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
|
const lt::torrent_handle nativeHandle {finishedJob.torrentHandle};
|
||||||
const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
|
const RemovingTorrentData &removingTorrentData = m_removingTorrents[nativeHandle.info_hash()];
|
||||||
if (removingTorrentData.deleteOption == DeleteTorrent)
|
if (removingTorrentData.removeOption == TorrentRemoveOption::KeepContent)
|
||||||
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
|
m_nativeSession->remove_torrent(nativeHandle, lt::session::delete_partfile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SessionImpl::processPendingFinishedTorrents()
|
||||||
|
{
|
||||||
|
if (m_pendingFinishedTorrents.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (TorrentImpl *torrent : asConst(m_pendingFinishedTorrents))
|
||||||
|
{
|
||||||
|
LogMsg(tr("Torrent download finished. Torrent: \"%1\"").arg(torrent->name()));
|
||||||
|
emit torrentFinished(torrent);
|
||||||
|
|
||||||
|
if (const Path exportPath = finishedTorrentExportDirectory(); !exportPath.isEmpty())
|
||||||
|
exportTorrentFile(torrent, exportPath);
|
||||||
|
|
||||||
|
processTorrentShareLimits(torrent);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pendingFinishedTorrents.clear();
|
||||||
|
|
||||||
|
const bool hasUnfinishedTorrents = std::any_of(m_torrents.cbegin(), m_torrents.cend(), [](const TorrentImpl *torrent)
|
||||||
|
{
|
||||||
|
return !(torrent->isFinished() || torrent->isStopped() || torrent->isErrored());
|
||||||
|
});
|
||||||
|
if (!hasUnfinishedTorrents)
|
||||||
|
emit allTorrentsFinished();
|
||||||
|
}
|
||||||
|
|
||||||
void SessionImpl::storeCategories() const
|
void SessionImpl::storeCategories() const
|
||||||
{
|
{
|
||||||
QJsonObject jsonObj;
|
QJsonObject jsonObj;
|
||||||
|
@ -5372,6 +5494,11 @@ void SessionImpl::setTorrentContentLayout(const TorrentContentLayout value)
|
||||||
// Read alerts sent by libtorrent session
|
// Read alerts sent by libtorrent session
|
||||||
void SessionImpl::readAlerts()
|
void SessionImpl::readAlerts()
|
||||||
{
|
{
|
||||||
|
// cache current datetime of Qt and libtorrent clocks in order
|
||||||
|
// to optimize conversion of time points from lt to Qt clocks
|
||||||
|
m_ltNow = lt::clock_type::now();
|
||||||
|
m_qNow = QDateTime::currentDateTime();
|
||||||
|
|
||||||
const std::vector<lt::alert *> alerts = getPendingAlerts();
|
const std::vector<lt::alert *> alerts = getPendingAlerts();
|
||||||
|
|
||||||
Q_ASSERT(m_loadedTorrents.isEmpty());
|
Q_ASSERT(m_loadedTorrents.isEmpty());
|
||||||
|
@ -5398,7 +5525,8 @@ void SessionImpl::readAlerts()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processTrackerStatuses();
|
// Some torrents may become "finished" after different alerts handling.
|
||||||
|
processPendingFinishedTorrents();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
|
void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
|
||||||
|
@ -5660,74 +5788,32 @@ TorrentImpl *SessionImpl::createTorrent(const lt::torrent_handle &nativeHandle,
|
||||||
return torrent;
|
return torrent;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert *alert)
|
void SessionImpl::handleTorrentRemovedAlert(const lt::torrent_removed_alert */*alert*/)
|
||||||
{
|
{
|
||||||
#ifdef QBT_USES_LIBTORRENT2
|
// We cannot consider `torrent_removed_alert` as a starting point for removing content,
|
||||||
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
|
// because it has an inconsistent posting time between different versions of libtorrent,
|
||||||
#else
|
// so files may still be in use in some cases.
|
||||||
const auto id = TorrentID::fromInfoHash(alert->info_hash);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
|
||||||
if (removingTorrentDataIter != m_removingTorrents.end())
|
|
||||||
{
|
|
||||||
if (removingTorrentDataIter->deleteOption == DeleteTorrent)
|
|
||||||
{
|
|
||||||
LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
|
|
||||||
m_removingTorrents.erase(removingTorrentDataIter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *alert)
|
void SessionImpl::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *alert)
|
||||||
{
|
{
|
||||||
#ifdef QBT_USES_LIBTORRENT2
|
#ifdef QBT_USES_LIBTORRENT2
|
||||||
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
|
const auto torrentID = TorrentID::fromInfoHash(alert->info_hashes);
|
||||||
#else
|
#else
|
||||||
const auto id = TorrentID::fromInfoHash(alert->info_hash);
|
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
|
||||||
#endif
|
#endif
|
||||||
|
handleRemovedTorrent(torrentID);
|
||||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
|
||||||
if (removingTorrentDataIter == m_removingTorrents.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// torrent_deleted_alert can also be posted due to deletion of partfile. Ignore it in such a case.
|
|
||||||
if (removingTorrentDataIter->deleteOption == DeleteTorrent)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
|
|
||||||
LogMsg(tr("Removed torrent and deleted its content. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
|
|
||||||
m_removingTorrents.erase(removingTorrentDataIter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *alert)
|
void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *alert)
|
||||||
{
|
{
|
||||||
#ifdef QBT_USES_LIBTORRENT2
|
#ifdef QBT_USES_LIBTORRENT2
|
||||||
const auto id = TorrentID::fromInfoHash(alert->info_hashes);
|
const auto torrentID = TorrentID::fromInfoHash(alert->info_hashes);
|
||||||
#else
|
#else
|
||||||
const auto id = TorrentID::fromInfoHash(alert->info_hash);
|
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
|
||||||
#endif
|
#endif
|
||||||
|
const auto errorMessage = alert->error ? QString::fromLocal8Bit(alert->error.message().c_str()) : QString();
|
||||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
handleRemovedTorrent(torrentID, errorMessage);
|
||||||
if (removingTorrentDataIter == m_removingTorrents.end())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (alert->error)
|
|
||||||
{
|
|
||||||
// libtorrent won't delete the directory if it contains files not listed in the torrent,
|
|
||||||
// so we remove the directory ourselves
|
|
||||||
Utils::Fs::smartRemoveEmptyFolderTree(removingTorrentDataIter->pathToRemove);
|
|
||||||
|
|
||||||
LogMsg(tr("Removed torrent but failed to delete its content and/or partfile. Torrent: \"%1\". Error: \"%2\"")
|
|
||||||
.arg(removingTorrentDataIter->name, QString::fromLocal8Bit(alert->error.message().c_str()))
|
|
||||||
, Log::WARNING);
|
|
||||||
}
|
|
||||||
else // torrent without metadata, hence no files on disk
|
|
||||||
{
|
|
||||||
LogMsg(tr("Removed torrent. Torrent: \"%1\"").arg(removingTorrentDataIter->name));
|
|
||||||
}
|
|
||||||
|
|
||||||
m_removingTorrents.erase(removingTorrentDataIter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *alert)
|
void SessionImpl::handleTorrentNeedCertAlert(const lt::torrent_need_cert_alert *alert)
|
||||||
|
@ -6116,7 +6202,12 @@ void SessionImpl::handleTrackerAlert(const lt::tracker_alert *alert)
|
||||||
if (!torrent)
|
if (!torrent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
[[maybe_unused]] const QMutexLocker updatedTrackerStatusesLocker {&m_updatedTrackerStatusesMutex};
|
||||||
|
|
||||||
|
const auto prevSize = m_updatedTrackerStatuses.size();
|
||||||
QMap<int, int> &updateInfo = m_updatedTrackerStatuses[torrent->nativeHandle()][std::string(alert->tracker_url())][alert->local_endpoint];
|
QMap<int, int> &updateInfo = m_updatedTrackerStatuses[torrent->nativeHandle()][std::string(alert->tracker_url())][alert->local_endpoint];
|
||||||
|
if (prevSize < m_updatedTrackerStatuses.size())
|
||||||
|
updateTrackerEntryStatuses(torrent->nativeHandle());
|
||||||
|
|
||||||
if (alert->type() == lt::tracker_reply_alert::alert_type)
|
if (alert->type() == lt::tracker_reply_alert::alert_type)
|
||||||
{
|
{
|
||||||
|
@ -6140,7 +6231,7 @@ void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *a
|
||||||
if (torrent2)
|
if (torrent2)
|
||||||
{
|
{
|
||||||
if (torrent1)
|
if (torrent1)
|
||||||
deleteTorrent(torrentIDv1);
|
removeTorrent(torrentIDv1);
|
||||||
else
|
else
|
||||||
cancelDownloadMetadata(torrentIDv1);
|
cancelDownloadMetadata(torrentIDv1);
|
||||||
|
|
||||||
|
@ -6178,17 +6269,6 @@ void SessionImpl::handleTorrentConflictAlert(const lt::torrent_conflict_alert *a
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void SessionImpl::processTrackerStatuses()
|
|
||||||
{
|
|
||||||
if (m_updatedTrackerStatuses.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (auto it = m_updatedTrackerStatuses.cbegin(); it != m_updatedTrackerStatuses.cend(); ++it)
|
|
||||||
updateTrackerEntryStatuses(it.key(), it.value());
|
|
||||||
|
|
||||||
m_updatedTrackerStatuses.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SessionImpl::saveStatistics() const
|
void SessionImpl::saveStatistics() const
|
||||||
{
|
{
|
||||||
if (!m_isStatisticsDirty)
|
if (!m_isStatisticsDirty)
|
||||||
|
@ -6213,15 +6293,19 @@ void SessionImpl::loadStatistics()
|
||||||
m_previouslyUploaded = value[u"AlltimeUL"_s].toLongLong();
|
m_previouslyUploaded = value[u"AlltimeUL"_s].toLongLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers)
|
void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle)
|
||||||
{
|
{
|
||||||
invokeAsync([this, torrentHandle = std::move(torrentHandle), updatedTrackers = std::move(updatedTrackers)]() mutable
|
invokeAsync([this, torrentHandle = std::move(torrentHandle)]() mutable
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::vector<lt::announce_entry> nativeTrackers = torrentHandle.trackers();
|
std::vector<lt::announce_entry> nativeTrackers = torrentHandle.trackers();
|
||||||
invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers)
|
|
||||||
, updatedTrackers = std::move(updatedTrackers)]
|
QMutexLocker updatedTrackerStatusesLocker {&m_updatedTrackerStatusesMutex};
|
||||||
|
QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers = m_updatedTrackerStatuses.take(torrentHandle);
|
||||||
|
updatedTrackerStatusesLocker.unlock();
|
||||||
|
|
||||||
|
invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers), updatedTrackers = std::move(updatedTrackers)]
|
||||||
{
|
{
|
||||||
TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash());
|
TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash());
|
||||||
if (!torrent || torrent->isStopped())
|
if (!torrent || torrent->isStopped())
|
||||||
|
@ -6249,3 +6333,35 @@ void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, Q
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SessionImpl::handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError)
|
||||||
|
{
|
||||||
|
const auto removingTorrentDataIter = m_removingTorrents.find(torrentID);
|
||||||
|
if (removingTorrentDataIter == m_removingTorrents.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!partfileRemoveError.isEmpty())
|
||||||
|
{
|
||||||
|
LogMsg(tr("Failed to remove partfile. Torrent: \"%1\". Reason: \"%2\".")
|
||||||
|
.arg(removingTorrentDataIter->name, partfileRemoveError)
|
||||||
|
, Log::WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((removingTorrentDataIter->removeOption == TorrentRemoveOption::RemoveContent)
|
||||||
|
&& !removingTorrentDataIter->contentStoragePath.isEmpty())
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(m_torrentContentRemover, [this, jobData = *removingTorrentDataIter]
|
||||||
|
{
|
||||||
|
m_torrentContentRemover->performJob(jobData.name, jobData.contentStoragePath
|
||||||
|
, jobData.fileNames, m_torrentContentRemoveOption);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_removingTorrents.erase(removingTorrentDataIter);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime SessionImpl::fromLTTimePoint32(const lt::time_point32 &timePoint) const
|
||||||
|
{
|
||||||
|
const auto secsSinceNow = lt::duration_cast<lt::seconds>(timePoint - m_ltNow + lt::milliseconds(500)).count();
|
||||||
|
return m_qNow.addSecs(secsSinceNow);
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
#include <QMutex>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
@ -75,6 +76,7 @@ namespace BitTorrent
|
||||||
class InfoHash;
|
class InfoHash;
|
||||||
class ResumeDataStorage;
|
class ResumeDataStorage;
|
||||||
class Torrent;
|
class Torrent;
|
||||||
|
class TorrentContentRemover;
|
||||||
class TorrentDescriptor;
|
class TorrentDescriptor;
|
||||||
class TorrentImpl;
|
class TorrentImpl;
|
||||||
class Tracker;
|
class Tracker;
|
||||||
|
@ -402,7 +404,7 @@ namespace BitTorrent
|
||||||
void setExcludedFileNamesEnabled(bool enabled) override;
|
void setExcludedFileNamesEnabled(bool enabled) override;
|
||||||
QStringList excludedFileNames() const override;
|
QStringList excludedFileNames() const override;
|
||||||
void setExcludedFileNames(const QStringList &excludedFileNames) 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;
|
QStringList bannedIPs() const override;
|
||||||
void setBannedIPs(const QStringList &newList) override;
|
void setBannedIPs(const QStringList &newList) override;
|
||||||
ResumeDataStorageType resumeDataStorageType() const override;
|
ResumeDataStorageType resumeDataStorageType() const override;
|
||||||
|
@ -411,6 +413,8 @@ namespace BitTorrent
|
||||||
void setMergeTrackersEnabled(bool enabled) override;
|
void setMergeTrackersEnabled(bool enabled) override;
|
||||||
bool isStartPaused() const override;
|
bool isStartPaused() const override;
|
||||||
void setStartPaused(bool value) override;
|
void setStartPaused(bool value) override;
|
||||||
|
TorrentContentRemoveOption torrentContentRemoveOption() const override;
|
||||||
|
void setTorrentContentRemoveOption(TorrentContentRemoveOption option) override;
|
||||||
|
|
||||||
bool isRestored() const override;
|
bool isRestored() const override;
|
||||||
|
|
||||||
|
@ -430,7 +434,7 @@ namespace BitTorrent
|
||||||
|
|
||||||
bool isKnownTorrent(const InfoHash &infoHash) const override;
|
bool isKnownTorrent(const InfoHash &infoHash) const override;
|
||||||
bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) 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 downloadMetadata(const TorrentDescriptor &torrentDescr) override;
|
||||||
bool cancelDownloadMetadata(const TorrentID &id) override;
|
bool cancelDownloadMetadata(const TorrentID &id) override;
|
||||||
|
|
||||||
|
@ -472,6 +476,8 @@ namespace BitTorrent
|
||||||
void addMappedPorts(const QSet<quint16> &ports);
|
void addMappedPorts(const QSet<quint16> &ports);
|
||||||
void removeMappedPorts(const QSet<quint16> &ports);
|
void removeMappedPorts(const QSet<quint16> &ports);
|
||||||
|
|
||||||
|
QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint) const;
|
||||||
|
|
||||||
template <typename Func>
|
template <typename Func>
|
||||||
void invoke(Func &&func)
|
void invoke(Func &&func)
|
||||||
{
|
{
|
||||||
|
@ -487,11 +493,11 @@ namespace BitTorrent
|
||||||
void configureDeferred();
|
void configureDeferred();
|
||||||
void readAlerts();
|
void readAlerts();
|
||||||
void enqueueRefresh();
|
void enqueueRefresh();
|
||||||
void processShareLimits();
|
|
||||||
void generateResumeData();
|
void generateResumeData();
|
||||||
void handleIPFilterParsed(int ruleCount);
|
void handleIPFilterParsed(int ruleCount);
|
||||||
void handleIPFilterError();
|
void handleIPFilterError();
|
||||||
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
|
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
|
||||||
|
void torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ResumeSessionContext;
|
struct ResumeSessionContext;
|
||||||
|
@ -507,8 +513,9 @@ namespace BitTorrent
|
||||||
struct RemovingTorrentData
|
struct RemovingTorrentData
|
||||||
{
|
{
|
||||||
QString name;
|
QString name;
|
||||||
Path pathToRemove;
|
Path contentStoragePath;
|
||||||
DeleteOption deleteOption {};
|
PathList fileNames;
|
||||||
|
TorrentRemoveOption removeOption {};
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit SessionImpl(QObject *parent = nullptr);
|
explicit SessionImpl(QObject *parent = nullptr);
|
||||||
|
@ -535,7 +542,7 @@ namespace BitTorrent
|
||||||
void populateAdditionalTrackers();
|
void populateAdditionalTrackers();
|
||||||
void enableIPFilter();
|
void enableIPFilter();
|
||||||
void disableIPFilter();
|
void disableIPFilter();
|
||||||
void processTrackerStatuses();
|
void processTorrentShareLimits(TorrentImpl *torrent);
|
||||||
void populateExcludedFileNamesRegExpList();
|
void populateExcludedFileNamesRegExpList();
|
||||||
void prepareStartup();
|
void prepareStartup();
|
||||||
void handleLoadedResumeData(ResumeSessionContext *context);
|
void handleLoadedResumeData(ResumeSessionContext *context);
|
||||||
|
@ -588,6 +595,7 @@ namespace BitTorrent
|
||||||
|
|
||||||
void moveTorrentStorage(const MoveStorageJob &job) const;
|
void moveTorrentStorage(const MoveStorageJob &job) const;
|
||||||
void handleMoveTorrentStorageJobFinished(const Path &newPath);
|
void handleMoveTorrentStorageJobFinished(const Path &newPath);
|
||||||
|
void processPendingFinishedTorrents();
|
||||||
|
|
||||||
void loadCategories();
|
void loadCategories();
|
||||||
void storeCategories() const;
|
void storeCategories() const;
|
||||||
|
@ -597,15 +605,9 @@ namespace BitTorrent
|
||||||
void saveStatistics() const;
|
void saveStatistics() const;
|
||||||
void loadStatistics();
|
void loadStatistics();
|
||||||
|
|
||||||
void updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers);
|
void updateTrackerEntryStatuses(lt::torrent_handle torrentHandle);
|
||||||
|
|
||||||
// BitTorrent
|
void handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError = {});
|
||||||
lt::session *m_nativeSession = nullptr;
|
|
||||||
NativeSessionExtension *m_nativeSessionExtension = nullptr;
|
|
||||||
|
|
||||||
bool m_deferredConfigureScheduled = false;
|
|
||||||
bool m_IPFilteringConfigured = false;
|
|
||||||
mutable bool m_listenInterfaceConfigured = false;
|
|
||||||
|
|
||||||
CachedSettingValue<QString> m_DHTBootstrapNodes;
|
CachedSettingValue<QString> m_DHTBootstrapNodes;
|
||||||
CachedSettingValue<bool> m_isDHTEnabled;
|
CachedSettingValue<bool> m_isDHTEnabled;
|
||||||
|
@ -731,8 +733,16 @@ namespace BitTorrent
|
||||||
CachedSettingValue<int> m_I2POutboundQuantity;
|
CachedSettingValue<int> m_I2POutboundQuantity;
|
||||||
CachedSettingValue<int> m_I2PInboundLength;
|
CachedSettingValue<int> m_I2PInboundLength;
|
||||||
CachedSettingValue<int> m_I2POutboundLength;
|
CachedSettingValue<int> m_I2POutboundLength;
|
||||||
|
CachedSettingValue<TorrentContentRemoveOption> m_torrentContentRemoveOption;
|
||||||
SettingValue<bool> m_startPaused;
|
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_isRestored = false;
|
||||||
bool m_isPaused = isStartPaused();
|
bool m_isPaused = isStartPaused();
|
||||||
|
|
||||||
|
@ -766,6 +776,7 @@ namespace BitTorrent
|
||||||
QThreadPool *m_asyncWorker = nullptr;
|
QThreadPool *m_asyncWorker = nullptr;
|
||||||
ResumeDataStorage *m_resumeDataStorage = nullptr;
|
ResumeDataStorage *m_resumeDataStorage = nullptr;
|
||||||
FileSearcher *m_fileSearcher = nullptr;
|
FileSearcher *m_fileSearcher = nullptr;
|
||||||
|
TorrentContentRemover *m_torrentContentRemover = nullptr;
|
||||||
|
|
||||||
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
|
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
|
||||||
|
|
||||||
|
@ -783,6 +794,7 @@ namespace BitTorrent
|
||||||
// This field holds amounts of peers reported by trackers in their responses to announces
|
// This field holds amounts of peers reported by trackers in their responses to announces
|
||||||
// (torrent.tracker_name.tracker_local_endpoint.protocol_version.num_peers)
|
// (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_updatedTrackerStatuses;
|
QHash<lt::torrent_handle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>>> m_updatedTrackerStatuses;
|
||||||
|
QMutex m_updatedTrackerStatusesMutex;
|
||||||
|
|
||||||
// I/O errored torrents
|
// I/O errored torrents
|
||||||
QSet<TorrentID> m_recentErroredTorrents;
|
QSet<TorrentID> m_recentErroredTorrents;
|
||||||
|
@ -809,6 +821,11 @@ namespace BitTorrent
|
||||||
QTimer *m_wakeupCheckTimer = nullptr;
|
QTimer *m_wakeupCheckTimer = nullptr;
|
||||||
QDateTime m_wakeupCheckTimestamp;
|
QDateTime m_wakeupCheckTimestamp;
|
||||||
|
|
||||||
|
QList<TorrentImpl *> m_pendingFinishedTorrents;
|
||||||
|
|
||||||
|
QDateTime m_qNow;
|
||||||
|
lt::clock_type::time_point m_ltNow;
|
||||||
|
|
||||||
friend void Session::initInstance();
|
friend void Session::initInstance();
|
||||||
friend void Session::freeInstance();
|
friend void Session::freeInstance();
|
||||||
friend Session *Session::instance();
|
friend Session *Session::instance();
|
||||||
|
|
|
@ -215,7 +215,15 @@ namespace BitTorrent
|
||||||
virtual int piecesCount() const = 0;
|
virtual int piecesCount() const = 0;
|
||||||
virtual int piecesHave() const = 0;
|
virtual int piecesHave() const = 0;
|
||||||
virtual qreal progress() const = 0;
|
virtual qreal progress() const = 0;
|
||||||
|
|
||||||
virtual QDateTime addedTime() 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
|
// Share limits
|
||||||
virtual qreal ratioLimit() const = 0;
|
virtual qreal ratioLimit() const = 0;
|
||||||
|
@ -228,6 +236,7 @@ namespace BitTorrent
|
||||||
virtual void setShareLimitAction(ShareLimitAction action) = 0;
|
virtual void setShareLimitAction(ShareLimitAction action) = 0;
|
||||||
|
|
||||||
virtual PathList filePaths() const = 0;
|
virtual PathList filePaths() const = 0;
|
||||||
|
virtual PathList actualFilePaths() const = 0;
|
||||||
|
|
||||||
virtual TorrentInfo info() const = 0;
|
virtual TorrentInfo info() const = 0;
|
||||||
virtual bool isFinished() const = 0;
|
virtual bool isFinished() const = 0;
|
||||||
|
@ -253,8 +262,6 @@ namespace BitTorrent
|
||||||
virtual QString error() const = 0;
|
virtual QString error() const = 0;
|
||||||
virtual qlonglong totalDownload() const = 0;
|
virtual qlonglong totalDownload() const = 0;
|
||||||
virtual qlonglong totalUpload() const = 0;
|
virtual qlonglong totalUpload() const = 0;
|
||||||
virtual qlonglong activeTime() const = 0;
|
|
||||||
virtual qlonglong finishedTime() const = 0;
|
|
||||||
virtual qlonglong eta() const = 0;
|
virtual qlonglong eta() const = 0;
|
||||||
virtual int seedsCount() const = 0;
|
virtual int seedsCount() const = 0;
|
||||||
virtual int peersCount() const = 0;
|
virtual int peersCount() const = 0;
|
||||||
|
@ -262,11 +269,6 @@ namespace BitTorrent
|
||||||
virtual int totalSeedsCount() const = 0;
|
virtual int totalSeedsCount() const = 0;
|
||||||
virtual int totalPeersCount() const = 0;
|
virtual int totalPeersCount() const = 0;
|
||||||
virtual int totalLeechersCount() 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 downloadLimit() const = 0;
|
||||||
virtual int uploadLimit() const = 0;
|
virtual int uploadLimit() const = 0;
|
||||||
virtual bool superSeeding() const = 0;
|
virtual bool superSeeding() const = 0;
|
||||||
|
|
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/file_storage.hpp>
|
||||||
#include <libtorrent/torrent_info.hpp>
|
#include <libtorrent/torrent_info.hpp>
|
||||||
|
|
||||||
|
#include <QtSystemDetection>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
@ -123,7 +124,14 @@ void TorrentCreator::run()
|
||||||
// need to sort the file names by natural sort order
|
// need to sort the file names by natural sort order
|
||||||
QStringList dirs = {m_params.sourcePath.data()};
|
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())
|
while (dirIter.hasNext())
|
||||||
{
|
{
|
||||||
const QString filePath = dirIter.next();
|
const QString filePath = dirIter.next();
|
||||||
|
@ -138,7 +146,12 @@ void TorrentCreator::run()
|
||||||
{
|
{
|
||||||
QStringList tmpNames; // natural sort files within each dir
|
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())
|
while (fileIter.hasNext())
|
||||||
{
|
{
|
||||||
const QFileInfo fileInfo = fileIter.nextFileInfo();
|
const QFileInfo fileInfo = fileIter.nextFileInfo();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
|
@ -35,9 +35,7 @@
|
||||||
#include <libtorrent/write_resume_data.hpp>
|
#include <libtorrent/write_resume_data.hpp>
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDateTime>
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
|
@ -147,7 +145,13 @@ BitTorrent::TorrentDescriptor::TorrentDescriptor(lt::add_torrent_params ltAddTor
|
||||||
: m_ltAddTorrentParams {std::move(ltAddTorrentParams)}
|
: m_ltAddTorrentParams {std::move(ltAddTorrentParams)}
|
||||||
{
|
{
|
||||||
if (m_ltAddTorrentParams.ti && m_ltAddTorrentParams.ti->is_valid())
|
if (m_ltAddTorrentParams.ti && m_ltAddTorrentParams.ti->is_valid())
|
||||||
|
{
|
||||||
m_info.emplace(*m_ltAddTorrentParams.ti);
|
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
|
BitTorrent::InfoHash BitTorrent::TorrentDescriptor::infoHash() const
|
||||||
|
@ -166,18 +170,17 @@ QString BitTorrent::TorrentDescriptor::name() const
|
||||||
|
|
||||||
QDateTime BitTorrent::TorrentDescriptor::creationDate() const
|
QDateTime BitTorrent::TorrentDescriptor::creationDate() const
|
||||||
{
|
{
|
||||||
return ((m_ltAddTorrentParams.ti->creation_date() != 0)
|
return m_creationDate;
|
||||||
? QDateTime::fromSecsSinceEpoch(m_ltAddTorrentParams.ti->creation_date()) : QDateTime());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString BitTorrent::TorrentDescriptor::creator() const
|
QString BitTorrent::TorrentDescriptor::creator() const
|
||||||
{
|
{
|
||||||
return QString::fromStdString(m_ltAddTorrentParams.ti->creator());
|
return m_creator;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString BitTorrent::TorrentDescriptor::comment() const
|
QString BitTorrent::TorrentDescriptor::comment() const
|
||||||
{
|
{
|
||||||
return QString::fromStdString(m_ltAddTorrentParams.ti->comment());
|
return m_comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::optional<BitTorrent::TorrentInfo> &BitTorrent::TorrentDescriptor::info() const
|
const std::optional<BitTorrent::TorrentInfo> &BitTorrent::TorrentDescriptor::info() const
|
||||||
|
|
|
@ -33,7 +33,9 @@
|
||||||
#include <libtorrent/add_torrent_params.hpp>
|
#include <libtorrent/add_torrent_params.hpp>
|
||||||
|
|
||||||
#include <QtContainerFwd>
|
#include <QtContainerFwd>
|
||||||
|
#include <QDateTime>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
#include "base/3rdparty/expected.hpp"
|
#include "base/3rdparty/expected.hpp"
|
||||||
#include "base/path.h"
|
#include "base/path.h"
|
||||||
|
@ -41,8 +43,6 @@
|
||||||
#include "torrentinfo.h"
|
#include "torrentinfo.h"
|
||||||
|
|
||||||
class QByteArray;
|
class QByteArray;
|
||||||
class QDateTime;
|
|
||||||
class QString;
|
|
||||||
class QUrl;
|
class QUrl;
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
|
@ -78,6 +78,9 @@ namespace BitTorrent
|
||||||
|
|
||||||
lt::add_torrent_params m_ltAddTorrentParams;
|
lt::add_torrent_params m_ltAddTorrentParams;
|
||||||
std::optional<TorrentInfo> m_info;
|
std::optional<TorrentInfo> m_info;
|
||||||
|
QDateTime m_creationDate;
|
||||||
|
QString m_creator;
|
||||||
|
QString m_comment;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
|
|
||||||
#include <QtSystemDetection>
|
#include <QtSystemDetection>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
|
#include <QCache>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
@ -77,6 +78,10 @@
|
||||||
#include "base/utils/os.h"
|
#include "base/utils/os.h"
|
||||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||||
|
|
||||||
|
#ifndef QBT_USES_LIBTORRENT2
|
||||||
|
#include "customstorage.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace BitTorrent;
|
using namespace BitTorrent;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -88,37 +93,28 @@ namespace
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint)
|
|
||||||
{
|
|
||||||
const auto ltNow = lt::clock_type::now();
|
|
||||||
const auto qNow = QDateTime::currentDateTime();
|
|
||||||
const auto secsSinceNow = lt::duration_cast<lt::seconds>(timePoint - ltNow + lt::milliseconds(500)).count();
|
|
||||||
|
|
||||||
return qNow.addSecs(secsSinceNow);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString toString(const lt::tcp::endpoint <TCPEndpoint)
|
QString toString(const lt::tcp::endpoint <TCPEndpoint)
|
||||||
{
|
{
|
||||||
return QString::fromStdString((std::stringstream() << ltTCPEndpoint).str());
|
static QCache<lt::tcp::endpoint, QString> cache;
|
||||||
|
|
||||||
|
if (const QString *endpointName = cache.object(ltTCPEndpoint))
|
||||||
|
return *endpointName;
|
||||||
|
|
||||||
|
const std::string tmp = (std::ostringstream() << ltTCPEndpoint).str();
|
||||||
|
const auto endpointName = QString::fromLatin1(tmp.c_str(), tmp.size());
|
||||||
|
cache.insert(ltTCPEndpoint, new QString(endpointName));
|
||||||
|
return endpointName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename FromLTTimePoint32Func>
|
||||||
void updateTrackerEntryStatus(TrackerEntryStatus &trackerEntryStatus, const lt::announce_entry &nativeEntry
|
void updateTrackerEntryStatus(TrackerEntryStatus &trackerEntryStatus, const lt::announce_entry &nativeEntry
|
||||||
, const QSet<int> &btProtocols, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
|
, const QSet<int> &btProtocols, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo
|
||||||
|
, const FromLTTimePoint32Func &fromLTTimePoint32)
|
||||||
{
|
{
|
||||||
Q_ASSERT(trackerEntryStatus.url == QString::fromStdString(nativeEntry.url));
|
Q_ASSERT(trackerEntryStatus.url == QString::fromStdString(nativeEntry.url));
|
||||||
|
|
||||||
trackerEntryStatus.tier = nativeEntry.tier;
|
trackerEntryStatus.tier = nativeEntry.tier;
|
||||||
|
|
||||||
// remove outdated endpoints
|
|
||||||
trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash<std::pair<QString, int>, TrackerEndpointStatus>::iterator &iter)
|
|
||||||
{
|
|
||||||
return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend()
|
|
||||||
, [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint)
|
|
||||||
{
|
|
||||||
return (endpointName == toString(existingEndpoint.local_endpoint));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * btProtocols.size();
|
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * btProtocols.size();
|
||||||
|
|
||||||
int numUpdating = 0;
|
int numUpdating = 0;
|
||||||
|
@ -201,6 +197,19 @@ namespace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trackerEntryStatus.endpoints.size() > numEndpoints)
|
||||||
|
{
|
||||||
|
// remove outdated endpoints
|
||||||
|
trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash<std::pair<QString, int>, TrackerEndpointStatus>::iterator &iter)
|
||||||
|
{
|
||||||
|
return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend()
|
||||||
|
, [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint)
|
||||||
|
{
|
||||||
|
return (endpointName == toString(existingEndpoint.local_endpoint));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (numEndpoints > 0)
|
if (numEndpoints > 0)
|
||||||
{
|
{
|
||||||
if (numUpdating > 0)
|
if (numUpdating > 0)
|
||||||
|
@ -313,6 +322,11 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||||
{
|
{
|
||||||
if (m_ltAddTorrentParams.ti)
|
if (m_ltAddTorrentParams.ti)
|
||||||
{
|
{
|
||||||
|
if (const std::time_t creationDate = m_ltAddTorrentParams.ti->creation_date(); creationDate > 0)
|
||||||
|
m_creationDate = QDateTime::fromSecsSinceEpoch(creationDate);
|
||||||
|
m_creator = QString::fromStdString(m_ltAddTorrentParams.ti->creator());
|
||||||
|
m_comment = QString::fromStdString(m_ltAddTorrentParams.ti->comment());
|
||||||
|
|
||||||
// Initialize it only if torrent is added with metadata.
|
// Initialize it only if torrent is added with metadata.
|
||||||
// Otherwise it should be initialized in "Metadata received" handler.
|
// Otherwise it should be initialized in "Metadata received" handler.
|
||||||
m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
|
m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
|
||||||
|
@ -356,6 +370,12 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||||
m_urlSeeds.append(QString::fromStdString(urlSeed));
|
m_urlSeeds.append(QString::fromStdString(urlSeed));
|
||||||
m_nativeStatus = extensionData->status;
|
m_nativeStatus = extensionData->status;
|
||||||
|
|
||||||
|
m_addedTime = QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
|
||||||
|
if (m_nativeStatus.completed_time > 0)
|
||||||
|
m_completedTime = QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
|
||||||
|
if (m_nativeStatus.last_seen_complete > 0)
|
||||||
|
m_lastSeenComplete = QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
|
||||||
|
|
||||||
if (hasMetadata())
|
if (hasMetadata())
|
||||||
updateProgress();
|
updateProgress();
|
||||||
|
|
||||||
|
@ -399,17 +419,17 @@ QString TorrentImpl::name() const
|
||||||
|
|
||||||
QDateTime TorrentImpl::creationDate() const
|
QDateTime TorrentImpl::creationDate() const
|
||||||
{
|
{
|
||||||
return m_torrentInfo.creationDate();
|
return m_creationDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TorrentImpl::creator() const
|
QString TorrentImpl::creator() const
|
||||||
{
|
{
|
||||||
return m_torrentInfo.creator();
|
return m_creator;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TorrentImpl::comment() const
|
QString TorrentImpl::comment() const
|
||||||
{
|
{
|
||||||
return m_torrentInfo.comment();
|
return m_comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TorrentImpl::isPrivate() const
|
bool TorrentImpl::isPrivate() const
|
||||||
|
@ -456,6 +476,8 @@ Path TorrentImpl::savePath() const
|
||||||
void TorrentImpl::setSavePath(const Path &path)
|
void TorrentImpl::setSavePath(const Path &path)
|
||||||
{
|
{
|
||||||
Q_ASSERT(!isAutoTMMEnabled());
|
Q_ASSERT(!isAutoTMMEnabled());
|
||||||
|
if (isAutoTMMEnabled()) [[unlikely]]
|
||||||
|
return;
|
||||||
|
|
||||||
const Path basePath = m_session->useCategoryPathsInManualMode()
|
const Path basePath = m_session->useCategoryPathsInManualMode()
|
||||||
? m_session->categorySavePath(category()) : m_session->savePath();
|
? m_session->categorySavePath(category()) : m_session->savePath();
|
||||||
|
@ -483,6 +505,8 @@ Path TorrentImpl::downloadPath() const
|
||||||
void TorrentImpl::setDownloadPath(const Path &path)
|
void TorrentImpl::setDownloadPath(const Path &path)
|
||||||
{
|
{
|
||||||
Q_ASSERT(!isAutoTMMEnabled());
|
Q_ASSERT(!isAutoTMMEnabled());
|
||||||
|
if (isAutoTMMEnabled()) [[unlikely]]
|
||||||
|
return;
|
||||||
|
|
||||||
const Path basePath = m_session->useCategoryPathsInManualMode()
|
const Path basePath = m_session->useCategoryPathsInManualMode()
|
||||||
? m_session->categoryDownloadPath(category()) : m_session->downloadPath();
|
? m_session->categoryDownloadPath(category()) : m_session->downloadPath();
|
||||||
|
@ -934,7 +958,52 @@ void TorrentImpl::removeAllTags()
|
||||||
|
|
||||||
QDateTime TorrentImpl::addedTime() const
|
QDateTime TorrentImpl::addedTime() const
|
||||||
{
|
{
|
||||||
return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
|
return m_addedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime TorrentImpl::completedTime() const
|
||||||
|
{
|
||||||
|
return m_completedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime TorrentImpl::lastSeenComplete() const
|
||||||
|
{
|
||||||
|
return m_lastSeenComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
qlonglong TorrentImpl::activeTime() const
|
||||||
|
{
|
||||||
|
return lt::total_seconds(m_nativeStatus.active_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
qlonglong TorrentImpl::finishedTime() const
|
||||||
|
{
|
||||||
|
return lt::total_seconds(m_nativeStatus.finished_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
qlonglong TorrentImpl::timeSinceUpload() const
|
||||||
|
{
|
||||||
|
if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
|
||||||
|
}
|
||||||
|
|
||||||
|
qlonglong TorrentImpl::timeSinceDownload() const
|
||||||
|
{
|
||||||
|
if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
|
||||||
|
}
|
||||||
|
|
||||||
|
qlonglong TorrentImpl::timeSinceActivity() const
|
||||||
|
{
|
||||||
|
const qlonglong upTime = timeSinceUpload();
|
||||||
|
const qlonglong downTime = timeSinceDownload();
|
||||||
|
return ((upTime < 0) != (downTime < 0))
|
||||||
|
? std::max(upTime, downTime)
|
||||||
|
: std::min(upTime, downTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
qreal TorrentImpl::ratioLimit() const
|
qreal TorrentImpl::ratioLimit() const
|
||||||
|
@ -982,6 +1051,21 @@ PathList TorrentImpl::filePaths() const
|
||||||
return m_filePaths;
|
return m_filePaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PathList TorrentImpl::actualFilePaths() const
|
||||||
|
{
|
||||||
|
if (!hasMetadata())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
PathList paths;
|
||||||
|
paths.reserve(filesCount());
|
||||||
|
|
||||||
|
const lt::file_storage files = nativeTorrentInfo()->files();
|
||||||
|
for (const lt::file_index_t &nativeIndex : asConst(m_torrentInfo.nativeIndexes()))
|
||||||
|
paths.emplaceBack(files.file_path(nativeIndex));
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
QVector<DownloadPriority> TorrentImpl::filePriorities() const
|
QVector<DownloadPriority> TorrentImpl::filePriorities() const
|
||||||
{
|
{
|
||||||
return m_filePriorities;
|
return m_filePriorities;
|
||||||
|
@ -1238,16 +1322,6 @@ qlonglong TorrentImpl::totalUpload() const
|
||||||
return m_nativeStatus.all_time_upload;
|
return m_nativeStatus.all_time_upload;
|
||||||
}
|
}
|
||||||
|
|
||||||
qlonglong TorrentImpl::activeTime() const
|
|
||||||
{
|
|
||||||
return lt::total_seconds(m_nativeStatus.active_duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
qlonglong TorrentImpl::finishedTime() const
|
|
||||||
{
|
|
||||||
return lt::total_seconds(m_nativeStatus.finished_duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
qlonglong TorrentImpl::eta() const
|
qlonglong TorrentImpl::eta() const
|
||||||
{
|
{
|
||||||
if (isStopped()) return MAX_ETA;
|
if (isStopped()) return MAX_ETA;
|
||||||
|
@ -1357,45 +1431,6 @@ int TorrentImpl::totalLeechersCount() const
|
||||||
return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
|
return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime TorrentImpl::lastSeenComplete() const
|
|
||||||
{
|
|
||||||
if (m_nativeStatus.last_seen_complete > 0)
|
|
||||||
return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
|
|
||||||
else
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QDateTime TorrentImpl::completedTime() const
|
|
||||||
{
|
|
||||||
if (m_nativeStatus.completed_time > 0)
|
|
||||||
return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
|
|
||||||
else
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
qlonglong TorrentImpl::timeSinceUpload() const
|
|
||||||
{
|
|
||||||
if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
|
|
||||||
return -1;
|
|
||||||
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
|
|
||||||
}
|
|
||||||
|
|
||||||
qlonglong TorrentImpl::timeSinceDownload() const
|
|
||||||
{
|
|
||||||
if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
|
|
||||||
return -1;
|
|
||||||
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
|
|
||||||
}
|
|
||||||
|
|
||||||
qlonglong TorrentImpl::timeSinceActivity() const
|
|
||||||
{
|
|
||||||
const qlonglong upTime = timeSinceUpload();
|
|
||||||
const qlonglong downTime = timeSinceDownload();
|
|
||||||
return ((upTime < 0) != (downTime < 0))
|
|
||||||
? std::max(upTime, downTime)
|
|
||||||
: std::min(upTime, downTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
int TorrentImpl::downloadLimit() const
|
int TorrentImpl::downloadLimit() const
|
||||||
{
|
{
|
||||||
return m_downloadLimit;;
|
return m_downloadLimit;;
|
||||||
|
@ -1447,11 +1482,13 @@ QBitArray TorrentImpl::pieces() const
|
||||||
|
|
||||||
QBitArray TorrentImpl::downloadingPieces() const
|
QBitArray TorrentImpl::downloadingPieces() const
|
||||||
{
|
{
|
||||||
QBitArray result(piecesCount());
|
if (!hasMetadata())
|
||||||
|
return {};
|
||||||
|
|
||||||
std::vector<lt::partial_piece_info> queue;
|
std::vector<lt::partial_piece_info> queue;
|
||||||
m_nativeHandle.get_download_queue(queue);
|
m_nativeHandle.get_download_queue(queue);
|
||||||
|
|
||||||
|
QBitArray result {piecesCount()};
|
||||||
for (const lt::partial_piece_info &info : queue)
|
for (const lt::partial_piece_info &info : queue)
|
||||||
result.setBit(LT::toUnderlyingType(info.piece_index));
|
result.setBit(LT::toUnderlyingType(info.piece_index));
|
||||||
|
|
||||||
|
@ -1607,9 +1644,19 @@ void TorrentImpl::forceRecheck()
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_nativeHandle.force_recheck();
|
m_nativeHandle.force_recheck();
|
||||||
|
|
||||||
// We have to force update the cached state, otherwise someone will be able to get
|
// We have to force update the cached state, otherwise someone will be able to get
|
||||||
// an incorrect one during the interval until the cached state is updated in a regular way.
|
// an incorrect one during the interval until the cached state is updated in a regular way.
|
||||||
m_nativeStatus.state = lt::torrent_status::checking_resume_data;
|
m_nativeStatus.state = lt::torrent_status::checking_resume_data;
|
||||||
|
m_nativeStatus.pieces.clear_all();
|
||||||
|
m_nativeStatus.num_pieces = 0;
|
||||||
|
m_ltAddTorrentParams.have_pieces.clear();
|
||||||
|
m_ltAddTorrentParams.verified_pieces.clear();
|
||||||
|
m_ltAddTorrentParams.unfinished_pieces.clear();
|
||||||
|
m_completedFiles.fill(false);
|
||||||
|
m_filesProgress.fill(0);
|
||||||
|
m_pieces.fill(false);
|
||||||
|
m_unchecked = false;
|
||||||
|
|
||||||
if (m_hasMissingFiles)
|
if (m_hasMissingFiles)
|
||||||
{
|
{
|
||||||
|
@ -1622,14 +1669,6 @@ void TorrentImpl::forceRecheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_unchecked = false;
|
|
||||||
|
|
||||||
m_completedFiles.fill(false);
|
|
||||||
m_filesProgress.fill(0);
|
|
||||||
m_pieces.fill(false);
|
|
||||||
m_nativeStatus.pieces.clear_all();
|
|
||||||
m_nativeStatus.num_pieces = 0;
|
|
||||||
|
|
||||||
if (isStopped())
|
if (isStopped())
|
||||||
{
|
{
|
||||||
// When "force recheck" is applied on Stopped torrent, we start them to perform checking
|
// When "force recheck" is applied on Stopped torrent, we start them to perform checking
|
||||||
|
@ -1734,7 +1773,13 @@ TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entr
|
||||||
#else
|
#else
|
||||||
const QSet<int> btProtocols {1};
|
const QSet<int> btProtocols {1};
|
||||||
#endif
|
#endif
|
||||||
::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo);
|
|
||||||
|
const auto fromLTTimePoint32 = [this](const lt::time_point32 &timePoint)
|
||||||
|
{
|
||||||
|
return m_session->fromLTTimePoint32(timePoint);
|
||||||
|
};
|
||||||
|
::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo, fromLTTimePoint32);
|
||||||
|
|
||||||
return *it;
|
return *it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1791,12 +1836,13 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
|
||||||
const Path filePath = actualFilePath.removedExtension(QB_EXT);
|
const Path filePath = actualFilePath.removedExtension(QB_EXT);
|
||||||
m_filePaths.append(filePath);
|
m_filePaths.append(filePath);
|
||||||
|
|
||||||
lt::download_priority_t &nativePriority = p.file_priorities[LT::toUnderlyingType(nativeIndex)];
|
m_filePriorities.append(LT::fromNative(p.file_priorities[LT::toUnderlyingType(nativeIndex)]));
|
||||||
if ((nativePriority != lt::dont_download) && m_session->isFilenameExcluded(filePath.filename()))
|
|
||||||
nativePriority = lt::dont_download;
|
|
||||||
const auto priority = LT::fromNative(nativePriority);
|
|
||||||
m_filePriorities.append(priority);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_session->applyFilenameFilter(m_filePaths, m_filePriorities);
|
||||||
|
for (int i = 0; i < m_filePriorities.size(); ++i)
|
||||||
|
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(m_filePriorities[i]);
|
||||||
|
|
||||||
p.save_path = savePath.toString().toStdString();
|
p.save_path = savePath.toString().toStdString();
|
||||||
p.ti = metadata;
|
p.ti = metadata;
|
||||||
|
|
||||||
|
@ -1859,6 +1905,9 @@ void TorrentImpl::reload()
|
||||||
|
|
||||||
auto *const extensionData = new ExtensionData;
|
auto *const extensionData = new ExtensionData;
|
||||||
p.userdata = LTClientData(extensionData);
|
p.userdata = LTClientData(extensionData);
|
||||||
|
#ifndef QBT_USES_LIBTORRENT2
|
||||||
|
p.storage = customStorageConstructor;
|
||||||
|
#endif
|
||||||
m_nativeHandle = m_nativeSession->add_torrent(p);
|
m_nativeHandle = m_nativeSession->add_torrent(p);
|
||||||
|
|
||||||
m_nativeStatus = extensionData->status;
|
m_nativeStatus = extensionData->status;
|
||||||
|
@ -1932,9 +1981,18 @@ void TorrentImpl::start(const TorrentOperatingMode mode)
|
||||||
void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
|
void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
|
||||||
{
|
{
|
||||||
if (!hasMetadata())
|
if (!hasMetadata())
|
||||||
|
{
|
||||||
|
if (context == MoveStorageContext::ChangeSavePath)
|
||||||
{
|
{
|
||||||
m_savePath = newPath;
|
m_savePath = newPath;
|
||||||
m_session->handleTorrentSavePathChanged(this);
|
m_session->handleTorrentSavePathChanged(this);
|
||||||
|
}
|
||||||
|
else if (context == MoveStorageContext::ChangeDownloadPath)
|
||||||
|
{
|
||||||
|
m_downloadPath = newPath;
|
||||||
|
m_session->handleTorrentSavePathChanged(this);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2104,6 +2162,7 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
||||||
|
|
||||||
m_ltAddTorrentParams.have_pieces.clear();
|
m_ltAddTorrentParams.have_pieces.clear();
|
||||||
m_ltAddTorrentParams.verified_pieces.clear();
|
m_ltAddTorrentParams.verified_pieces.clear();
|
||||||
|
m_ltAddTorrentParams.unfinished_pieces.clear();
|
||||||
|
|
||||||
m_nativeStatus.torrent_file = m_ltAddTorrentParams.ti;
|
m_nativeStatus.torrent_file = m_ltAddTorrentParams.ti;
|
||||||
|
|
||||||
|
@ -2146,23 +2205,37 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
||||||
|
|
||||||
void TorrentImpl::prepareResumeData(const lt::add_torrent_params ¶ms)
|
void TorrentImpl::prepareResumeData(const lt::add_torrent_params ¶ms)
|
||||||
{
|
{
|
||||||
if (m_hasMissingFiles)
|
|
||||||
{
|
{
|
||||||
const auto havePieces = m_ltAddTorrentParams.have_pieces;
|
decltype(params.have_pieces) havePieces;
|
||||||
const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
|
decltype(params.unfinished_pieces) unfinishedPieces;
|
||||||
const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
|
decltype(params.verified_pieces) verifiedPieces;
|
||||||
|
|
||||||
// Update recent resume data but preserve existing progress
|
// The resume data obtained from libtorrent contains an empty "progress" in the following cases:
|
||||||
m_ltAddTorrentParams = params;
|
// 1. when it was requested at a time when the initial resume data has not yet been checked,
|
||||||
m_ltAddTorrentParams.have_pieces = havePieces;
|
// 2. when initial resume data was rejected
|
||||||
m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
|
// We should preserve the initial "progress" in such cases.
|
||||||
m_ltAddTorrentParams.verified_pieces = verifiedPieces;
|
const bool needPreserveProgress = m_hasMissingFiles
|
||||||
}
|
|| (!m_ltAddTorrentParams.have_pieces.empty() && params.have_pieces.empty());
|
||||||
else
|
const bool preserveSeedMode = !m_hasMissingFiles && !hasMetadata()
|
||||||
|
&& (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode);
|
||||||
|
|
||||||
|
if (needPreserveProgress)
|
||||||
{
|
{
|
||||||
const bool preserveSeedMode = (!hasMetadata() && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode));
|
havePieces = std::move(m_ltAddTorrentParams.have_pieces);
|
||||||
|
unfinishedPieces = std::move(m_ltAddTorrentParams.unfinished_pieces);
|
||||||
|
verifiedPieces = std::move(m_ltAddTorrentParams.verified_pieces);
|
||||||
|
}
|
||||||
|
|
||||||
// Update recent resume data
|
// Update recent resume data
|
||||||
m_ltAddTorrentParams = params;
|
m_ltAddTorrentParams = params;
|
||||||
|
|
||||||
|
if (needPreserveProgress)
|
||||||
|
{
|
||||||
|
m_ltAddTorrentParams.have_pieces = std::move(havePieces);
|
||||||
|
m_ltAddTorrentParams.unfinished_pieces = std::move(unfinishedPieces);
|
||||||
|
m_ltAddTorrentParams.verified_pieces = std::move(verifiedPieces);
|
||||||
|
}
|
||||||
|
|
||||||
if (preserveSeedMode)
|
if (preserveSeedMode)
|
||||||
m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
|
m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
|
||||||
}
|
}
|
||||||
|
@ -2312,7 +2385,8 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||||
// only apply Mark-of-the-Web to new download files
|
// only apply Mark-of-the-Web to new download files
|
||||||
if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
|
if (Preferences::instance()->isMarkOfTheWebEnabled()
|
||||||
|
&& (m_nativeStatus.state == lt::torrent_status::downloading))
|
||||||
{
|
{
|
||||||
const Path fullpath = actualStorageLocation() / actualPath;
|
const Path fullpath = actualStorageLocation() / actualPath;
|
||||||
Utils::OS::applyMarkOfTheWeb(fullpath);
|
Utils::OS::applyMarkOfTheWeb(fullpath);
|
||||||
|
@ -2558,11 +2632,23 @@ bool TorrentImpl::isMoveInProgress() const
|
||||||
|
|
||||||
void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
|
void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
|
||||||
{
|
{
|
||||||
|
// Since libtorrent alerts are handled asynchronously there can be obsolete
|
||||||
|
// "state update" event reached here after torrent was reloaded in libtorrent.
|
||||||
|
// Just discard such events.
|
||||||
|
if (nativeStatus.handle != m_nativeHandle) [[unlikely]]
|
||||||
|
return;
|
||||||
|
|
||||||
const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
|
const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
|
||||||
|
|
||||||
if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
|
if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
|
||||||
updateProgress();
|
updateProgress();
|
||||||
|
|
||||||
|
if (m_nativeStatus.completed_time != oldStatus.completed_time)
|
||||||
|
m_completedTime = (m_nativeStatus.completed_time > 0) ? QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time) : QDateTime();
|
||||||
|
|
||||||
|
if (m_nativeStatus.last_seen_complete != oldStatus.last_seen_complete)
|
||||||
|
m_lastSeenComplete = QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
|
||||||
|
|
||||||
updateState();
|
updateState();
|
||||||
|
|
||||||
m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
|
m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
|
||||||
|
|
|
@ -138,7 +138,15 @@ namespace BitTorrent
|
||||||
int piecesCount() const override;
|
int piecesCount() const override;
|
||||||
int piecesHave() const override;
|
int piecesHave() const override;
|
||||||
qreal progress() const override;
|
qreal progress() const override;
|
||||||
|
|
||||||
QDateTime addedTime() 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;
|
qreal ratioLimit() const override;
|
||||||
void setRatioLimit(qreal limit) override;
|
void setRatioLimit(qreal limit) override;
|
||||||
|
@ -153,6 +161,7 @@ namespace BitTorrent
|
||||||
Path actualFilePath(int index) const override;
|
Path actualFilePath(int index) const override;
|
||||||
qlonglong fileSize(int index) const override;
|
qlonglong fileSize(int index) const override;
|
||||||
PathList filePaths() const override;
|
PathList filePaths() const override;
|
||||||
|
PathList actualFilePaths() const override;
|
||||||
QVector<DownloadPriority> filePriorities() const override;
|
QVector<DownloadPriority> filePriorities() const override;
|
||||||
|
|
||||||
TorrentInfo info() const override;
|
TorrentInfo info() const override;
|
||||||
|
@ -180,8 +189,6 @@ namespace BitTorrent
|
||||||
QString error() const override;
|
QString error() const override;
|
||||||
qlonglong totalDownload() const override;
|
qlonglong totalDownload() const override;
|
||||||
qlonglong totalUpload() const override;
|
qlonglong totalUpload() const override;
|
||||||
qlonglong activeTime() const override;
|
|
||||||
qlonglong finishedTime() const override;
|
|
||||||
qlonglong eta() const override;
|
qlonglong eta() const override;
|
||||||
QVector<qreal> filesProgress() const override;
|
QVector<qreal> filesProgress() const override;
|
||||||
int seedsCount() const override;
|
int seedsCount() const override;
|
||||||
|
@ -190,11 +197,6 @@ namespace BitTorrent
|
||||||
int totalSeedsCount() const override;
|
int totalSeedsCount() const override;
|
||||||
int totalPeersCount() const override;
|
int totalPeersCount() const override;
|
||||||
int totalLeechersCount() 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 downloadLimit() const override;
|
||||||
int uploadLimit() const override;
|
int uploadLimit() const override;
|
||||||
bool superSeeding() const override;
|
bool superSeeding() const override;
|
||||||
|
@ -341,6 +343,14 @@ namespace BitTorrent
|
||||||
|
|
||||||
InfoHash m_infoHash;
|
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:
|
// m_moveFinishedTriggers is activated only when the following conditions are met:
|
||||||
// all file rename jobs complete, all file move jobs complete
|
// all file rename jobs complete, all file move jobs complete
|
||||||
QQueue<EventTrigger> m_moveFinishedTriggers;
|
QQueue<EventTrigger> m_moveFinishedTriggers;
|
||||||
|
|
|
@ -44,6 +44,7 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
||||||
, m_requestHandler(requestHandler)
|
, m_requestHandler(requestHandler)
|
||||||
{
|
{
|
||||||
m_socket->setParent(this);
|
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
|
// reserve common size for requests, don't use the max allowed size which is too big for
|
||||||
// memory constrained platforms
|
// memory constrained platforms
|
||||||
|
@ -62,11 +63,6 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Connection::~Connection()
|
|
||||||
{
|
|
||||||
m_socket->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Connection::read()
|
void Connection::read()
|
||||||
{
|
{
|
||||||
// reuse existing buffer and avoid unnecessary memory allocation/relocation
|
// reuse existing buffer and avoid unnecessary memory allocation/relocation
|
||||||
|
@ -182,11 +178,6 @@ bool Connection::hasExpired(const qint64 timeout) const
|
||||||
&& m_idleTimer.hasExpired(timeout);
|
&& m_idleTimer.hasExpired(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Connection::isClosed() const
|
|
||||||
{
|
|
||||||
return (m_socket->state() == QAbstractSocket::UnconnectedState);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Connection::acceptsGzipEncoding(QString codings)
|
bool Connection::acceptsGzipEncoding(QString codings)
|
||||||
{
|
{
|
||||||
// [rfc7231] 5.3.4. Accept-Encoding
|
// [rfc7231] 5.3.4. Accept-Encoding
|
||||||
|
|
|
@ -47,10 +47,11 @@ namespace Http
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = nullptr);
|
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = nullptr);
|
||||||
~Connection();
|
|
||||||
|
|
||||||
bool hasExpired(qint64 timeout) const;
|
bool hasExpired(qint64 timeout) const;
|
||||||
bool isClosed() const;
|
|
||||||
|
signals:
|
||||||
|
void closed();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool acceptsGzipEncoding(QString codings);
|
static bool acceptsGzipEncoding(QString codings);
|
||||||
|
|
|
@ -32,7 +32,10 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <memory>
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
#include <QtLogging>
|
||||||
#include <QNetworkProxy>
|
#include <QNetworkProxy>
|
||||||
#include <QSslCipher>
|
#include <QSslCipher>
|
||||||
#include <QSslConfiguration>
|
#include <QSslConfiguration>
|
||||||
|
@ -40,7 +43,6 @@
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "base/algorithm.h"
|
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/utils/net.h"
|
#include "base/utils/net.h"
|
||||||
#include "base/utils/sslkey.h"
|
#include "base/utils/sslkey.h"
|
||||||
|
@ -113,32 +115,38 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
||||||
|
|
||||||
void Server::incomingConnection(const qintptr socketDescriptor)
|
void Server::incomingConnection(const qintptr socketDescriptor)
|
||||||
{
|
{
|
||||||
if (m_connections.size() >= CONNECTIONS_LIMIT) return;
|
std::unique_ptr<QTcpSocket> serverSocket = m_https ? std::make_unique<QSslSocket>(this) : std::make_unique<QTcpSocket>(this);
|
||||||
|
|
||||||
QTcpSocket *serverSocket = nullptr;
|
|
||||||
if (m_https)
|
|
||||||
serverSocket = new QSslSocket(this);
|
|
||||||
else
|
|
||||||
serverSocket = new QTcpSocket(this);
|
|
||||||
|
|
||||||
if (!serverSocket->setSocketDescriptor(socketDescriptor))
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
if (m_https)
|
if (m_https)
|
||||||
{
|
{
|
||||||
static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols);
|
auto *sslSocket = static_cast<QSslSocket *>(serverSocket.get());
|
||||||
static_cast<QSslSocket *>(serverSocket)->setPrivateKey(m_key);
|
sslSocket->setProtocol(QSsl::SecureProtocols);
|
||||||
static_cast<QSslSocket *>(serverSocket)->setLocalCertificateChain(m_certificates);
|
sslSocket->setPrivateKey(m_key);
|
||||||
static_cast<QSslSocket *>(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone);
|
sslSocket->setLocalCertificateChain(m_certificates);
|
||||||
static_cast<QSslSocket *>(serverSocket)->startServerEncryption();
|
sslSocket->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||||
|
sslSocket->startServerEncryption();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *c = new Connection(serverSocket, m_requestHandler, this);
|
auto *connection = new Connection(serverSocket.release(), m_requestHandler, this);
|
||||||
m_connections.insert(c);
|
m_connections.insert(connection);
|
||||||
connect(serverSocket, &QAbstractSocket::disconnected, this, [c, this]() { removeConnection(c); });
|
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)
|
void Server::removeConnection(Connection *connection)
|
||||||
|
|
|
@ -29,7 +29,10 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QHash>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
|
#include <QMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
|
|
|
@ -148,10 +148,20 @@ Net::DownloadManager::DownloadManager(QObject *parent)
|
||||||
QStringList errorList;
|
QStringList errorList;
|
||||||
for (const QSslError &error : errors)
|
for (const QSslError &error : errors)
|
||||||
errorList += error.errorString();
|
errorList += error.errorString();
|
||||||
LogMsg(tr("Ignoring SSL error, URL: \"%1\", errors: \"%2\"").arg(reply->url().toString(), errorList.join(u". ")), Log::WARNING);
|
|
||||||
|
|
||||||
|
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
|
// Ignore all SSL errors
|
||||||
reply->ignoreSslErrors();
|
reply->ignoreSslErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
LogMsg(errorMsg.arg(reply->url().toString(), errorList.join(u". ")), Log::WARNING);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(ProxyConfigurationManager::instance(), &ProxyConfigurationManager::proxyConfigurationChanged
|
connect(ProxyConfigurationManager::instance(), &ProxyConfigurationManager::proxyConfigurationChanged
|
||||||
|
|
|
@ -43,7 +43,6 @@ class QSslSocket;
|
||||||
#else
|
#else
|
||||||
class QTcpSocket;
|
class QTcpSocket;
|
||||||
#endif
|
#endif
|
||||||
class QTextCodec;
|
|
||||||
|
|
||||||
namespace Net
|
namespace Net
|
||||||
{
|
{
|
||||||
|
|
|
@ -134,17 +134,17 @@ void Preferences::setCustomUIThemePath(const Path &path)
|
||||||
setValue(u"Preferences/General/CustomUIThemePath"_s, path);
|
setValue(u"Preferences/General/CustomUIThemePath"_s, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Preferences::deleteTorrentFilesAsDefault() const
|
bool Preferences::removeTorrentContent() const
|
||||||
{
|
{
|
||||||
return value(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, false);
|
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;
|
return;
|
||||||
|
|
||||||
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, del);
|
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Preferences::confirmOnExit() const
|
bool Preferences::confirmOnExit() const
|
||||||
|
@ -429,6 +429,19 @@ void Preferences::setWinStartup(const bool b)
|
||||||
settings.remove(profileID);
|
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
|
#endif // Q_OS_WIN
|
||||||
|
|
||||||
// Downloads
|
// Downloads
|
||||||
|
@ -1330,6 +1343,19 @@ void Preferences::setMarkOfTheWebEnabled(const bool enabled)
|
||||||
setValue(u"Preferences/Advanced/markOfTheWeb"_s, 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
|
Path Preferences::getPythonExecutablePath() const
|
||||||
{
|
{
|
||||||
return value(u"Preferences/Search/pythonExecutablePath"_s, Path());
|
return value(u"Preferences/Search/pythonExecutablePath"_s, Path());
|
||||||
|
@ -1974,6 +2000,19 @@ void Preferences::setAddNewTorrentDialogSavePathHistoryLength(const int value)
|
||||||
setValue(u"AddNewTorrentDialog/SavePathHistoryLength"_s, clampedValue);
|
setValue(u"AddNewTorrentDialog/SavePathHistoryLength"_s, clampedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Preferences::isAddNewTorrentDialogAttached() const
|
||||||
|
{
|
||||||
|
return value(u"AddNewTorrentDialog/Attached"_s, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setAddNewTorrentDialogAttached(const bool attached)
|
||||||
|
{
|
||||||
|
if (attached == isAddNewTorrentDialogAttached())
|
||||||
|
return;
|
||||||
|
|
||||||
|
setValue(u"AddNewTorrentDialog/Attached"_s, attached);
|
||||||
|
}
|
||||||
|
|
||||||
void Preferences::apply()
|
void Preferences::apply()
|
||||||
{
|
{
|
||||||
if (SettingsStorage::instance()->save())
|
if (SettingsStorage::instance()->save())
|
||||||
|
|
|
@ -105,8 +105,8 @@ public:
|
||||||
void setUseCustomUITheme(bool use);
|
void setUseCustomUITheme(bool use);
|
||||||
Path customUIThemePath() const;
|
Path customUIThemePath() const;
|
||||||
void setCustomUIThemePath(const Path &path);
|
void setCustomUIThemePath(const Path &path);
|
||||||
bool deleteTorrentFilesAsDefault() const;
|
bool removeTorrentContent() const;
|
||||||
void setDeleteTorrentFilesAsDefault(bool del);
|
void setRemoveTorrentContent(bool remove);
|
||||||
bool confirmOnExit() const;
|
bool confirmOnExit() const;
|
||||||
void setConfirmOnExit(bool confirm);
|
void setConfirmOnExit(bool confirm);
|
||||||
bool speedInTitleBar() const;
|
bool speedInTitleBar() const;
|
||||||
|
@ -130,6 +130,8 @@ public:
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
bool WinStartup() const;
|
bool WinStartup() const;
|
||||||
void setWinStartup(bool b);
|
void setWinStartup(bool b);
|
||||||
|
QString getStyle() const;
|
||||||
|
void setStyle(const QString &styleName);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Downloads
|
// Downloads
|
||||||
|
@ -293,6 +295,8 @@ public:
|
||||||
void setTrackerPortForwardingEnabled(bool enabled);
|
void setTrackerPortForwardingEnabled(bool enabled);
|
||||||
bool isMarkOfTheWebEnabled() const;
|
bool isMarkOfTheWebEnabled() const;
|
||||||
void setMarkOfTheWebEnabled(bool enabled);
|
void setMarkOfTheWebEnabled(bool enabled);
|
||||||
|
bool isIgnoreSSLErrors() const;
|
||||||
|
void setIgnoreSSLErrors(bool enabled);
|
||||||
Path getPythonExecutablePath() const;
|
Path getPythonExecutablePath() const;
|
||||||
void setPythonExecutablePath(const Path &path);
|
void setPythonExecutablePath(const Path &path);
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||||
|
@ -419,6 +423,8 @@ public:
|
||||||
void setAddNewTorrentDialogTopLevel(bool value);
|
void setAddNewTorrentDialogTopLevel(bool value);
|
||||||
int addNewTorrentDialogSavePathHistoryLength() const;
|
int addNewTorrentDialogSavePathHistoryLength() const;
|
||||||
void setAddNewTorrentDialogSavePathHistoryLength(int value);
|
void setAddNewTorrentDialogSavePathHistoryLength(int value);
|
||||||
|
bool isAddNewTorrentDialogAttached() const;
|
||||||
|
void setAddNewTorrentDialogAttached(bool attached);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setStatusFilterState(bool checked);
|
void setStatusFilterState(bool checked);
|
||||||
|
|
|
@ -396,6 +396,8 @@ bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString &articleTitle) co
|
||||||
m_dataPtr->lastComputedEpisodes.append(episodeStr + u"-REPACK");
|
m_dataPtr->lastComputedEpisodes.append(episodeStr + u"-REPACK");
|
||||||
m_dataPtr->lastComputedEpisodes.append(episodeStr + u"-PROPER");
|
m_dataPtr->lastComputedEpisodes.append(episodeStr + u"-PROPER");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_dataPtr->lastComputedEpisodes.append(episodeStr);
|
m_dataPtr->lastComputedEpisodes.append(episodeStr);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -29,11 +29,8 @@
|
||||||
|
|
||||||
#include "rss_parser.h"
|
#include "rss_parser.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QGlobalStatic>
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QMetaObject>
|
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
@ -359,7 +356,7 @@ namespace
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ported to Qt from KDElibs4
|
// Ported to Qt from KDElibs4
|
||||||
QDateTime parseDate(const QString &string)
|
QDateTime parseDate(const QString &string, const QDateTime &fallbackDate)
|
||||||
{
|
{
|
||||||
const char16_t shortDay[][4] =
|
const char16_t shortDay[][4] =
|
||||||
{
|
{
|
||||||
|
@ -382,7 +379,7 @@ namespace
|
||||||
|
|
||||||
const QString str = string.trimmed();
|
const QString str = string.trimmed();
|
||||||
if (str.isEmpty())
|
if (str.isEmpty())
|
||||||
return QDateTime::currentDateTime();
|
return fallbackDate;
|
||||||
|
|
||||||
int nyear = 6; // indexes within string to values
|
int nyear = 6; // indexes within string to values
|
||||||
int nmonth = 4;
|
int nmonth = 4;
|
||||||
|
@ -402,14 +399,14 @@ namespace
|
||||||
const bool h1 = (parts[3] == u"-");
|
const bool h1 = (parts[3] == u"-");
|
||||||
const bool h2 = (parts[5] == u"-");
|
const bool h2 = (parts[5] == u"-");
|
||||||
if (h1 != h2)
|
if (h1 != h2)
|
||||||
return QDateTime::currentDateTime();
|
return fallbackDate;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
|
// 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};
|
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)
|
if (str.indexOf(rx, 0, &rxMatch) != 0)
|
||||||
return QDateTime::currentDateTime();
|
return fallbackDate;
|
||||||
|
|
||||||
nyear = 7;
|
nyear = 7;
|
||||||
nmonth = 2;
|
nmonth = 2;
|
||||||
|
@ -427,14 +424,14 @@ namespace
|
||||||
const int hour = parts[nhour].toInt(&ok[2]);
|
const int hour = parts[nhour].toInt(&ok[2]);
|
||||||
const int minute = parts[nmin].toInt(&ok[3]);
|
const int minute = parts[nmin].toInt(&ok[3]);
|
||||||
if (!ok[0] || !ok[1] || !ok[2] || !ok[3])
|
if (!ok[0] || !ok[1] || !ok[2] || !ok[3])
|
||||||
return QDateTime::currentDateTime();
|
return fallbackDate;
|
||||||
|
|
||||||
int second = 0;
|
int second = 0;
|
||||||
if (!parts[nsec].isEmpty())
|
if (!parts[nsec].isEmpty())
|
||||||
{
|
{
|
||||||
second = parts[nsec].toInt(&ok[0]);
|
second = parts[nsec].toInt(&ok[0]);
|
||||||
if (!ok[0])
|
if (!ok[0])
|
||||||
return QDateTime::currentDateTime();
|
return fallbackDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool leapSecond = (second == 60);
|
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
|
const QDate qDate(year, month + 1, day); // convert date, and check for out-of-range
|
||||||
if (!qDate.isValid())
|
if (!qDate.isValid())
|
||||||
return QDateTime::currentDateTime();
|
return fallbackDate;
|
||||||
|
|
||||||
const QTime qTime(hour, minute, second);
|
const QTime qTime(hour, minute, second);
|
||||||
QDateTime result(qDate, qTime, Qt::UTC);
|
QDateTime result(qDate, qTime, Qt::UTC);
|
||||||
if (offset)
|
if (offset)
|
||||||
result = result.addSecs(-offset);
|
result = result.addSecs(-offset);
|
||||||
if (!result.isValid())
|
if (!result.isValid())
|
||||||
return QDateTime::currentDateTime(); // invalid date/time
|
return fallbackDate; // invalid date/time
|
||||||
|
|
||||||
if (leapSecond)
|
if (leapSecond)
|
||||||
{
|
{
|
||||||
// Validate a leap second time. Leap seconds are inserted after 23:59:59 UTC.
|
// 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.
|
// 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)
|
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;
|
return result;
|
||||||
|
@ -550,6 +547,7 @@ RSS::Private::Parser::Parser(const QString &lastBuildDate)
|
||||||
void RSS::Private::Parser::parse(const QByteArray &feedData)
|
void RSS::Private::Parser::parse(const QByteArray &feedData)
|
||||||
{
|
{
|
||||||
QXmlStreamReader xml {feedData};
|
QXmlStreamReader xml {feedData};
|
||||||
|
m_fallbackDate = QDateTime::currentDateTime();
|
||||||
XmlStreamEntityResolver resolver;
|
XmlStreamEntityResolver resolver;
|
||||||
xml.setEntityResolver(&resolver);
|
xml.setEntityResolver(&resolver);
|
||||||
bool foundChannel = false;
|
bool foundChannel = false;
|
||||||
|
@ -641,7 +639,7 @@ void RSS::Private::Parser::parseRssArticle(QXmlStreamReader &xml)
|
||||||
}
|
}
|
||||||
else if (name == u"pubDate")
|
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")
|
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
|
// ATOM uses standard compliant date, don't do fancy stuff
|
||||||
const QDateTime articleDate = QDateTime::fromString(xml.readElementText().trimmed(), Qt::ISODate);
|
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")
|
else if (name == u"author")
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -29,6 +29,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDateTime>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
@ -66,6 +67,7 @@ namespace RSS::Private
|
||||||
void parseAtomChannel(QXmlStreamReader &xml);
|
void parseAtomChannel(QXmlStreamReader &xml);
|
||||||
void addArticle(QVariantHash article);
|
void addArticle(QVariantHash article);
|
||||||
|
|
||||||
|
QDateTime m_fallbackDate;
|
||||||
QString m_baseUrl;
|
QString m_baseUrl;
|
||||||
ParsingResult m_result;
|
ParsingResult m_result;
|
||||||
QSet<QString> m_articleIDs;
|
QSet<QString> m_articleIDs;
|
||||||
|
|
|
@ -52,19 +52,21 @@ const TorrentFilter TorrentFilter::ErroredTorrent(TorrentFilter::Errored);
|
||||||
using BitTorrent::Torrent;
|
using BitTorrent::Torrent;
|
||||||
|
|
||||||
TorrentFilter::TorrentFilter(const Type type, const std::optional<TorrentIDSet> &idSet
|
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_type {type}
|
||||||
, m_category {category}
|
, m_category {category}
|
||||||
, m_tag {tag}
|
, m_tag {tag}
|
||||||
, m_idSet {idSet}
|
, m_idSet {idSet}
|
||||||
|
, m_private {isPrivate}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TorrentFilter::TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet
|
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_category {category}
|
||||||
, m_tag {tag}
|
, m_tag {tag}
|
||||||
, m_idSet {idSet}
|
, m_idSet {idSet}
|
||||||
|
, m_private {isPrivate}
|
||||||
{
|
{
|
||||||
setTypeByName(filter);
|
setTypeByName(filter);
|
||||||
}
|
}
|
||||||
|
@ -147,11 +149,22 @@ bool TorrentFilter::setTag(const std::optional<Tag> &tag)
|
||||||
return false;
|
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
|
bool TorrentFilter::match(const Torrent *const torrent) const
|
||||||
{
|
{
|
||||||
if (!torrent) return false;
|
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
|
bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
||||||
|
@ -224,3 +237,11 @@ bool TorrentFilter::matchTag(const BitTorrent::Torrent *const torrent) const
|
||||||
|
|
||||||
return torrent->hasTag(*m_tag);
|
return torrent->hasTag(*m_tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TorrentFilter::matchPrivate(const BitTorrent::Torrent *const torrent) const
|
||||||
|
{
|
||||||
|
if (!m_private)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return m_private == torrent->isPrivate();
|
||||||
|
}
|
||||||
|
|
|
@ -87,16 +87,24 @@ public:
|
||||||
|
|
||||||
TorrentFilter() = default;
|
TorrentFilter() = default;
|
||||||
// category & tags: pass empty string for uncategorized / untagged torrents.
|
// category & tags: pass empty string for uncategorized / untagged torrents.
|
||||||
TorrentFilter(Type type, const std::optional<TorrentIDSet> &idSet = AnyID
|
TorrentFilter(Type type
|
||||||
, const std::optional<QString> &category = AnyCategory, const std::optional<Tag> &tag = AnyTag);
|
, const std::optional<TorrentIDSet> &idSet = AnyID
|
||||||
TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet = AnyID
|
, const std::optional<QString> &category = AnyCategory
|
||||||
, const std::optional<QString> &category = AnyCategory, const std::optional<Tag> &tags = AnyTag);
|
, 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 setType(Type type);
|
||||||
bool setTypeByName(const QString &filter);
|
bool setTypeByName(const QString &filter);
|
||||||
bool setTorrentIDSet(const std::optional<TorrentIDSet> &idSet);
|
bool setTorrentIDSet(const std::optional<TorrentIDSet> &idSet);
|
||||||
bool setCategory(const std::optional<QString> &category);
|
bool setCategory(const std::optional<QString> &category);
|
||||||
bool setTag(const std::optional<Tag> &tag);
|
bool setTag(const std::optional<Tag> &tag);
|
||||||
|
bool setPrivate(std::optional<bool> isPrivate);
|
||||||
|
|
||||||
bool match(const BitTorrent::Torrent *torrent) const;
|
bool match(const BitTorrent::Torrent *torrent) const;
|
||||||
|
|
||||||
|
@ -105,9 +113,11 @@ private:
|
||||||
bool matchHash(const BitTorrent::Torrent *torrent) const;
|
bool matchHash(const BitTorrent::Torrent *torrent) const;
|
||||||
bool matchCategory(const BitTorrent::Torrent *torrent) const;
|
bool matchCategory(const BitTorrent::Torrent *torrent) const;
|
||||||
bool matchTag(const BitTorrent::Torrent *torrent) const;
|
bool matchTag(const BitTorrent::Torrent *torrent) const;
|
||||||
|
bool matchPrivate(const BitTorrent::Torrent *torrent) const;
|
||||||
|
|
||||||
Type m_type {All};
|
Type m_type {All};
|
||||||
std::optional<QString> m_category;
|
std::optional<QString> m_category;
|
||||||
std::optional<Tag> m_tag;
|
std::optional<Tag> m_tag;
|
||||||
std::optional<TorrentIDSet> m_idSet;
|
std::optional<TorrentIDSet> m_idSet;
|
||||||
|
std::optional<bool> m_private;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -29,8 +29,6 @@
|
||||||
|
|
||||||
#include "fs.h"
|
#include "fs.h"
|
||||||
|
|
||||||
#include <cerrno>
|
|
||||||
#include <cstring>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
|
@ -52,6 +50,7 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#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.
|
* 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()};
|
QFile file {path.data()};
|
||||||
|
if (file.remove())
|
||||||
|
return {};
|
||||||
|
|
||||||
if (!file.exists())
|
if (!file.exists())
|
||||||
return true;
|
return {};
|
||||||
|
|
||||||
// Make sure we have read/write permissions
|
// Make sure we have read/write permissions
|
||||||
file.setPermissions(file.permissions() | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser);
|
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)
|
bool Utils::Fs::isReadable(const Path &path)
|
||||||
{
|
{
|
||||||
return QFileInfo(path.data()).isReadable();
|
return QFileInfo(path.data()).isReadable();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
|
#include "base/3rdparty/expected.hpp"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/pathfwd.h"
|
#include "base/pathfwd.h"
|
||||||
|
|
||||||
|
@ -60,7 +61,8 @@ namespace Utils::Fs
|
||||||
|
|
||||||
bool copyFile(const Path &from, const Path &to);
|
bool copyFile(const Path &from, const Path &to);
|
||||||
bool renameFile(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 mkdir(const Path &dirPath);
|
||||||
bool mkpath(const Path &dirPath);
|
bool mkpath(const Path &dirPath);
|
||||||
bool rmdir(const Path &dirPath);
|
bool rmdir(const Path &dirPath);
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
#include "os.h"
|
#include "os.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <powrprof.h>
|
#include <powrprof.h>
|
||||||
#include <shlobj.h>
|
#include <shlobj.h>
|
||||||
|
@ -42,6 +44,8 @@
|
||||||
#include <CoreServices/CoreServices.h>
|
#include <CoreServices/CoreServices.h>
|
||||||
#endif // Q_OS_MACOS
|
#endif // Q_OS_MACOS
|
||||||
|
|
||||||
|
#include <QScopeGuard>
|
||||||
|
|
||||||
#ifdef QBT_USES_DBUS
|
#ifdef QBT_USES_DBUS
|
||||||
#include <QDBusInterface>
|
#include <QDBusInterface>
|
||||||
#endif // QBT_USES_DBUS
|
#endif // QBT_USES_DBUS
|
||||||
|
@ -271,6 +275,11 @@ Path Utils::OS::windowsSystemPath()
|
||||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||||
bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
|
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:"));
|
Q_ASSERT(url.isEmpty() || url.startsWith(u"http:") || url.startsWith(u"https:"));
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
|
@ -278,34 +287,53 @@ bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
|
||||||
// https://searchfox.org/mozilla-central/rev/ffdc4971dc18e1141cb2a90c2b0b776365650270/xpcom/io/CocoaFileUtils.mm#230
|
// https://searchfox.org/mozilla-central/rev/ffdc4971dc18e1141cb2a90c2b0b776365650270/xpcom/io/CocoaFileUtils.mm#230
|
||||||
// https://github.com/transmission/transmission/blob/f62f7427edb1fd5c430e0ef6956bbaa4f03ae597/macosx/Torrent.mm#L1945-L1955
|
// 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
|
CFMutableDictionaryRef properties = ::CFDictionaryCreateMutable(kCFAllocatorDefault, 0
|
||||||
, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||||
if (properties == NULL)
|
if (!properties)
|
||||||
return false;
|
return false;
|
||||||
|
[[maybe_unused]] const auto propertiesGuard = qScopeGuard([&properties] { ::CFRelease(properties); });
|
||||||
|
|
||||||
::CFDictionarySetValue(properties, kLSQuarantineTypeKey, kLSQuarantineTypeOtherDownload);
|
::CFDictionarySetValue(properties, kLSQuarantineTypeKey, kLSQuarantineTypeOtherDownload);
|
||||||
if (!url.isEmpty())
|
if (!url.isEmpty())
|
||||||
::CFDictionarySetValue(properties, kLSQuarantineDataURLKey, url.toCFString());
|
{
|
||||||
|
const CFStringRef urlCFString = url.toCFString();
|
||||||
const CFStringRef fileString = file.toString().toCFString();
|
[[maybe_unused]] const auto urlStringGuard = qScopeGuard([&urlCFString] { ::CFRelease(urlCFString); });
|
||||||
const CFURLRef fileURL = ::CFURLCreateWithFileSystemPath(kCFAllocatorDefault
|
::CFDictionarySetValue(properties, kLSQuarantineDataURLKey, urlCFString);
|
||||||
, fileString, kCFURLPOSIXPathStyle, false);
|
}
|
||||||
|
|
||||||
const Boolean success = ::CFURLSetResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey
|
const Boolean success = ::CFURLSetResourcePropertyForKey(fileURL, kCFURLQuarantinePropertiesKey
|
||||||
, properties, NULL);
|
, properties, NULL);
|
||||||
|
|
||||||
::CFRelease(fileURL);
|
|
||||||
::CFRelease(fileString);
|
|
||||||
::CFRelease(properties);
|
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
#elif defined(Q_OS_WIN)
|
#elif defined(Q_OS_WIN)
|
||||||
const QString zoneIDStream = file.toString() + u":Zone.Identifier";
|
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)
|
, (FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE)
|
||||||
, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
if (handle == INVALID_HANDLE_VALUE)
|
if (handle == INVALID_HANDLE_VALUE)
|
||||||
return false;
|
return false;
|
||||||
|
[[maybe_unused]] const auto handleGuard = qScopeGuard([&handle] { ::CloseHandle(handle); });
|
||||||
|
|
||||||
// 5.6.1 Zone.Identifier Stream Name
|
// 5.6.1 Zone.Identifier Stream Name
|
||||||
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8
|
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/6e3f7352-d11c-4d76-8c39-2516a9df36e8
|
||||||
|
@ -313,10 +341,27 @@ bool Utils::OS::applyMarkOfTheWeb(const Path &file, const QString &url)
|
||||||
const QByteArray zoneID = QByteArrayLiteral("[ZoneTransfer]\r\nZoneId=3\r\n")
|
const QByteArray zoneID = QByteArrayLiteral("[ZoneTransfer]\r\nZoneId=3\r\n")
|
||||||
+ u"HostUrl=%1\r\n"_s.arg(hostURL).toUtf8();
|
+ 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;
|
DWORD written = 0;
|
||||||
const BOOL writeResult = ::WriteFile(handle, zoneID.constData(), zoneID.size(), &written, nullptr);
|
const BOOL writeResult = ::WriteFile(handle, zoneID.constData(), zoneID.size(), &written, nullptr);
|
||||||
::CloseHandle(handle);
|
|
||||||
|
|
||||||
return writeResult && (written == zoneID.size());
|
return writeResult && (written == zoneID.size());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,9 +30,9 @@
|
||||||
|
|
||||||
#define QBT_VERSION_MAJOR 5
|
#define QBT_VERSION_MAJOR 5
|
||||||
#define QBT_VERSION_MINOR 0
|
#define QBT_VERSION_MINOR 0
|
||||||
#define QBT_VERSION_BUGFIX 0
|
#define QBT_VERSION_BUGFIX 5
|
||||||
#define QBT_VERSION_BUILD 0
|
#define QBT_VERSION_BUILD 0
|
||||||
#define QBT_VERSION_STATUS "beta1" // Should be empty for stable releases!
|
#define QBT_VERSION_STATUS "" // Should be empty for stable releases!
|
||||||
|
|
||||||
#define QBT__STRINGIFY(x) #x
|
#define QBT__STRINGIFY(x) #x
|
||||||
#define QBT_STRINGIFY(x) QBT__STRINGIFY(x)
|
#define QBT_STRINGIFY(x) QBT__STRINGIFY(x)
|
||||||
|
|
|
@ -52,6 +52,8 @@ add_library(qbt_gui STATIC
|
||||||
desktopintegration.h
|
desktopintegration.h
|
||||||
downloadfromurldialog.h
|
downloadfromurldialog.h
|
||||||
executionlogwidget.h
|
executionlogwidget.h
|
||||||
|
filterpatternformat.h
|
||||||
|
filterpatternformatmenu.h
|
||||||
flowlayout.h
|
flowlayout.h
|
||||||
fspathedit.h
|
fspathedit.h
|
||||||
fspathedit_p.h
|
fspathedit_p.h
|
||||||
|
@ -151,6 +153,7 @@ add_library(qbt_gui STATIC
|
||||||
desktopintegration.cpp
|
desktopintegration.cpp
|
||||||
downloadfromurldialog.cpp
|
downloadfromurldialog.cpp
|
||||||
executionlogwidget.cpp
|
executionlogwidget.cpp
|
||||||
|
filterpatternformatmenu.cpp
|
||||||
flowlayout.cpp
|
flowlayout.cpp
|
||||||
fspathedit.cpp
|
fspathedit.cpp
|
||||||
fspathedit_p.cpp
|
fspathedit_p.cpp
|
||||||
|
|
|
@ -67,7 +67,7 @@ AboutDialog::AboutDialog(QWidget *parent)
|
||||||
u"</p>"_s
|
u"</p>"_s
|
||||||
.arg(tr("An advanced BitTorrent client programmed in C++, based on Qt toolkit and libtorrent-rasterbar.")
|
.arg(tr("An advanced BitTorrent client programmed in C++, based on Qt toolkit and libtorrent-rasterbar.")
|
||||||
.replace(u"C++"_s, u"C\u2060+\u2060+"_s) // make C++ non-breaking
|
.replace(u"C++"_s, u"C\u2060+\u2060+"_s) // make C++ non-breaking
|
||||||
, tr("Copyright %1 2006-2024 The qBittorrent project").arg(C_COPYRIGHT)
|
, tr("Copyright %1 2006-2025 The qBittorrent project").arg(C_COPYRIGHT)
|
||||||
, tr("Home Page:")
|
, tr("Home Page:")
|
||||||
, tr("Forum:")
|
, tr("Forum:")
|
||||||
, tr("Bug Tracker:"));
|
, tr("Bug Tracker:"));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -64,6 +64,7 @@
|
||||||
#include "base/utils/fs.h"
|
#include "base/utils/fs.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
|
#include "filterpatternformatmenu.h"
|
||||||
#include "lineedit.h"
|
#include "lineedit.h"
|
||||||
#include "torrenttagsdialog.h"
|
#include "torrenttagsdialog.h"
|
||||||
|
|
||||||
|
@ -181,6 +182,11 @@ public:
|
||||||
return (m_filePaths.isEmpty() ? m_torrentInfo.filePath(index) : m_filePaths.at(index));
|
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
|
void renameFile(const int index, const Path &newFilePath) override
|
||||||
{
|
{
|
||||||
Q_ASSERT((index >= 0) && (index < filesCount()));
|
Q_ASSERT((index >= 0) && (index < filesCount()));
|
||||||
|
@ -290,6 +296,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
||||||
, m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_s)}
|
, m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_s)}
|
||||||
, m_storeTreeHeaderState {u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)}
|
, m_storeTreeHeaderState {u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)}
|
||||||
, m_storeSplitterState {u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)}
|
, m_storeSplitterState {u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)}
|
||||||
|
, m_storeFilterPatternFormat {u"GUI/" SETTINGS_KEY(u"FilterPatternFormat"_s)}
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
|
@ -316,6 +323,8 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
||||||
// Torrent content filtering
|
// Torrent content filtering
|
||||||
m_filterLine->setPlaceholderText(tr("Filter files..."));
|
m_filterLine->setPlaceholderText(tr("Filter files..."));
|
||||||
m_filterLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
m_filterLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||||
|
m_filterLine->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
connect(m_filterLine, &QWidget::customContextMenuRequested, this, &AddNewTorrentDialog::showContentFilterContextMenu);
|
||||||
m_ui->contentFilterLayout->insertWidget(3, m_filterLine);
|
m_ui->contentFilterLayout->insertWidget(3, m_filterLine);
|
||||||
const auto *focusSearchHotkey = new QShortcut(QKeySequence::Find, this);
|
const auto *focusSearchHotkey = new QShortcut(QKeySequence::Find, this);
|
||||||
connect(focusSearchHotkey, &QShortcut::activated, this, [this]()
|
connect(focusSearchHotkey, &QShortcut::activated, this, [this]()
|
||||||
|
@ -360,7 +369,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &to
|
||||||
});
|
});
|
||||||
dlg->open();
|
dlg->open();
|
||||||
});
|
});
|
||||||
connect(m_filterLine, &LineEdit::textChanged, m_ui->contentTreeView, &TorrentContentWidget::setFilterPattern);
|
connect(m_filterLine, &LineEdit::textChanged, this, &AddNewTorrentDialog::setContentFilterPattern);
|
||||||
connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
|
connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
|
||||||
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
|
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
|
||||||
connect(Preferences::instance(), &Preferences::changed, []
|
connect(Preferences::instance(), &Preferences::changed, []
|
||||||
|
@ -691,6 +700,28 @@ void AddNewTorrentDialog::saveTorrentFile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddNewTorrentDialog::showContentFilterContextMenu()
|
||||||
|
{
|
||||||
|
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()
|
void AddNewTorrentDialog::populateSavePaths()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_currentContext);
|
Q_ASSERT(m_currentContext);
|
||||||
|
@ -790,6 +821,8 @@ void AddNewTorrentDialog::reject()
|
||||||
if (!m_currentContext) [[unlikely]]
|
if (!m_currentContext) [[unlikely]]
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
emit torrentRejected(m_currentContext->torrentDescr);
|
||||||
|
|
||||||
const BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr;
|
const BitTorrent::TorrentDescriptor &torrentDescr = m_currentContext->torrentDescr;
|
||||||
const bool hasMetadata = torrentDescr.info().has_value();
|
const bool hasMetadata = torrentDescr.info().has_value();
|
||||||
if (!hasMetadata)
|
if (!hasMetadata)
|
||||||
|
@ -886,15 +919,7 @@ void AddNewTorrentDialog::setupTreeview()
|
||||||
{
|
{
|
||||||
// Check file name blacklist for torrents that are manually added
|
// Check file name blacklist for torrents that are manually added
|
||||||
QVector<BitTorrent::DownloadPriority> priorities = m_contentAdaptor->filePriorities();
|
QVector<BitTorrent::DownloadPriority> priorities = m_contentAdaptor->filePriorities();
|
||||||
for (int i = 0; i < priorities.size(); ++i)
|
BitTorrent::Session::instance()->applyFilenameFilter(m_contentAdaptor->filePaths(), priorities);
|
||||||
{
|
|
||||||
if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (BitTorrent::Session::instance()->isFilenameExcluded(torrentInfo.filePath(i).filename()))
|
|
||||||
priorities[i] = BitTorrent::DownloadPriority::Ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_contentAdaptor->prioritizeFiles(priorities);
|
m_contentAdaptor->prioritizeFiles(priorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
#include "base/path.h"
|
#include "base/path.h"
|
||||||
#include "base/settingvalue.h"
|
#include "base/settingvalue.h"
|
||||||
|
#include "filterpatternformat.h"
|
||||||
|
|
||||||
class LineEdit;
|
class LineEdit;
|
||||||
|
|
||||||
|
@ -65,6 +66,7 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void torrentAccepted(const BitTorrent::TorrentDescriptor &torrentDescriptor, const BitTorrent::AddTorrentParams &addTorrentParams);
|
void torrentAccepted(const BitTorrent::TorrentDescriptor &torrentDescriptor, const BitTorrent::AddTorrentParams &addTorrentParams);
|
||||||
|
void torrentRejected(const BitTorrent::TorrentDescriptor &torrentDescriptor);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateDiskSpaceLabel();
|
void updateDiskSpaceLabel();
|
||||||
|
@ -92,6 +94,8 @@ private:
|
||||||
void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = {});
|
void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = {});
|
||||||
void setupTreeview();
|
void setupTreeview();
|
||||||
void saveTorrentFile();
|
void saveTorrentFile();
|
||||||
|
void showContentFilterContextMenu();
|
||||||
|
void setContentFilterPattern();
|
||||||
|
|
||||||
Ui::AddNewTorrentDialog *m_ui = nullptr;
|
Ui::AddNewTorrentDialog *m_ui = nullptr;
|
||||||
std::unique_ptr<TorrentContentAdaptor> m_contentAdaptor;
|
std::unique_ptr<TorrentContentAdaptor> m_contentAdaptor;
|
||||||
|
@ -107,4 +111,5 @@ private:
|
||||||
SettingValue<bool> m_storeRememberLastSavePath;
|
SettingValue<bool> m_storeRememberLastSavePath;
|
||||||
SettingValue<QByteArray> m_storeTreeHeaderState;
|
SettingValue<QByteArray> m_storeTreeHeaderState;
|
||||||
SettingValue<QByteArray> m_storeSplitterState;
|
SettingValue<QByteArray> m_storeSplitterState;
|
||||||
|
SettingValue<FilterPatternFormat> m_storeFilterPatternFormat;
|
||||||
};
|
};
|
||||||
|
|
|
@ -63,6 +63,7 @@ namespace
|
||||||
// qBittorrent section
|
// qBittorrent section
|
||||||
QBITTORRENT_HEADER,
|
QBITTORRENT_HEADER,
|
||||||
RESUME_DATA_STORAGE,
|
RESUME_DATA_STORAGE,
|
||||||
|
TORRENT_CONTENT_REMOVE_OPTION,
|
||||||
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||||
MEMORY_WORKING_SET_LIMIT,
|
MEMORY_WORKING_SET_LIMIT,
|
||||||
#endif
|
#endif
|
||||||
|
@ -96,6 +97,7 @@ namespace
|
||||||
ENABLE_SPEED_WIDGET,
|
ENABLE_SPEED_WIDGET,
|
||||||
#ifndef Q_OS_MACOS
|
#ifndef Q_OS_MACOS
|
||||||
ENABLE_ICONS_IN_MENUS,
|
ENABLE_ICONS_IN_MENUS,
|
||||||
|
USE_ATTACHED_ADD_NEW_TORRENT_DIALOG,
|
||||||
#endif
|
#endif
|
||||||
// embedded tracker
|
// embedded tracker
|
||||||
TRACKER_STATUS,
|
TRACKER_STATUS,
|
||||||
|
@ -104,6 +106,7 @@ namespace
|
||||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||||
ENABLE_MARK_OF_THE_WEB,
|
ENABLE_MARK_OF_THE_WEB,
|
||||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||||
|
IGNORE_SSL_ERRORS,
|
||||||
PYTHON_EXECUTABLE_PATH,
|
PYTHON_EXECUTABLE_PATH,
|
||||||
START_SESSION_PAUSED,
|
START_SESSION_PAUSED,
|
||||||
SESSION_SHUTDOWN_TIMEOUT,
|
SESSION_SHUTDOWN_TIMEOUT,
|
||||||
|
@ -321,6 +324,7 @@ void AdvancedSettings::saveAdvancedSettings() const
|
||||||
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
|
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
|
||||||
#ifndef Q_OS_MACOS
|
#ifndef Q_OS_MACOS
|
||||||
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
|
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
|
||||||
|
pref->setAddNewTorrentDialogAttached(m_checkBoxAttachedAddNewTorrentDialog.isChecked());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Tracker
|
// Tracker
|
||||||
|
@ -331,6 +335,8 @@ void AdvancedSettings::saveAdvancedSettings() const
|
||||||
// Mark-of-the-Web
|
// Mark-of-the-Web
|
||||||
pref->setMarkOfTheWebEnabled(m_checkBoxMarkOfTheWeb.isChecked());
|
pref->setMarkOfTheWebEnabled(m_checkBoxMarkOfTheWeb.isChecked());
|
||||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||||
|
// Ignore SSL errors
|
||||||
|
pref->setIgnoreSSLErrors(m_checkBoxIgnoreSSLErrors.isChecked());
|
||||||
// Python executable path
|
// Python executable path
|
||||||
pref->setPythonExecutablePath(Path(m_pythonExecutablePath.text().trimmed()));
|
pref->setPythonExecutablePath(Path(m_pythonExecutablePath.text().trimmed()));
|
||||||
// Start session paused
|
// Start session paused
|
||||||
|
@ -364,6 +370,8 @@ void AdvancedSettings::saveAdvancedSettings() const
|
||||||
session->setI2PInboundLength(m_spinBoxI2PInboundLength.value());
|
session->setI2PInboundLength(m_spinBoxI2PInboundLength.value());
|
||||||
session->setI2POutboundLength(m_spinBoxI2POutboundLength.value());
|
session->setI2POutboundLength(m_spinBoxI2POutboundLength.value());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
session->setTorrentContentRemoveOption(m_comboBoxTorrentContentRemoveOption.currentData().value<BitTorrent::TorrentContentRemoveOption>());
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef QBT_USES_LIBTORRENT2
|
#ifndef QBT_USES_LIBTORRENT2
|
||||||
|
@ -472,6 +480,11 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||||
m_comboBoxResumeDataStorage.setCurrentIndex(m_comboBoxResumeDataStorage.findData(QVariant::fromValue(session->resumeDataStorageType())));
|
m_comboBoxResumeDataStorage.setCurrentIndex(m_comboBoxResumeDataStorage.findData(QVariant::fromValue(session->resumeDataStorageType())));
|
||||||
addRow(RESUME_DATA_STORAGE, tr("Resume data storage type (requires restart)"), &m_comboBoxResumeDataStorage);
|
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)
|
#if defined(QBT_USES_LIBTORRENT2) && !defined(Q_OS_MACOS)
|
||||||
// Physical memory (RAM) usage limit
|
// Physical memory (RAM) usage limit
|
||||||
m_spinBoxMemoryWorkingSetLimit.setMinimum(1);
|
m_spinBoxMemoryWorkingSetLimit.setMinimum(1);
|
||||||
|
@ -577,6 +590,7 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||||
m_comboBoxDiskIOType.addItem(tr("Default"), QVariant::fromValue(BitTorrent::DiskIOType::Default));
|
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("Memory mapped files"), QVariant::fromValue(BitTorrent::DiskIOType::MMap));
|
||||||
m_comboBoxDiskIOType.addItem(tr("POSIX-compliant"), QVariant::fromValue(BitTorrent::DiskIOType::Posix));
|
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())));
|
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"(?)")
|
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);
|
, &m_comboBoxDiskIOType);
|
||||||
|
@ -823,6 +837,9 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||||
// Enable icons in menus
|
// Enable icons in menus
|
||||||
m_checkBoxIconsInMenusEnabled.setChecked(pref->iconsInMenusEnabled());
|
m_checkBoxIconsInMenusEnabled.setChecked(pref->iconsInMenusEnabled());
|
||||||
addRow(ENABLE_ICONS_IN_MENUS, tr("Enable icons in menus"), &m_checkBoxIconsInMenusEnabled);
|
addRow(ENABLE_ICONS_IN_MENUS, tr("Enable icons in menus"), &m_checkBoxIconsInMenusEnabled);
|
||||||
|
|
||||||
|
m_checkBoxAttachedAddNewTorrentDialog.setChecked(pref->isAddNewTorrentDialogAttached());
|
||||||
|
addRow(USE_ATTACHED_ADD_NEW_TORRENT_DIALOG, tr("Attach \"Add new torrent\" dialog to main window"), &m_checkBoxAttachedAddNewTorrentDialog);
|
||||||
#endif
|
#endif
|
||||||
// Tracker State
|
// Tracker State
|
||||||
m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
|
m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
|
||||||
|
@ -845,6 +862,10 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||||
m_checkBoxMarkOfTheWeb.setChecked(pref->isMarkOfTheWebEnabled());
|
m_checkBoxMarkOfTheWeb.setChecked(pref->isMarkOfTheWebEnabled());
|
||||||
addRow(ENABLE_MARK_OF_THE_WEB, motwLabel, &m_checkBoxMarkOfTheWeb);
|
addRow(ENABLE_MARK_OF_THE_WEB, motwLabel, &m_checkBoxMarkOfTheWeb);
|
||||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
#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
|
// Python executable path
|
||||||
m_pythonExecutablePath.setPlaceholderText(tr("(Auto detect if empty)"));
|
m_pythonExecutablePath.setPlaceholderText(tr("(Auto detect if empty)"));
|
||||||
m_pythonExecutablePath.setText(pref->getPythonExecutablePath().toString());
|
m_pythonExecutablePath.setText(pref->getPythonExecutablePath().toString());
|
||||||
|
|
|
@ -77,11 +77,12 @@ private:
|
||||||
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval, m_spinBoxRequestQueueSize;
|
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval, m_spinBoxRequestQueueSize;
|
||||||
QCheckBox m_checkBoxOsCache, m_checkBoxRecheckCompleted, m_checkBoxResolveCountries, m_checkBoxResolveHosts,
|
QCheckBox m_checkBoxOsCache, m_checkBoxRecheckCompleted, m_checkBoxResolveCountries, m_checkBoxResolveHosts,
|
||||||
m_checkBoxProgramNotifications, m_checkBoxTorrentAddedNotifications, m_checkBoxReannounceWhenAddressChanged, m_checkBoxTrackerFavicon, m_checkBoxTrackerStatus,
|
m_checkBoxProgramNotifications, m_checkBoxTorrentAddedNotifications, m_checkBoxReannounceWhenAddressChanged, m_checkBoxTrackerFavicon, m_checkBoxTrackerStatus,
|
||||||
m_checkBoxTrackerPortForwarding, m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers, m_checkBoxAnnounceAllTiers,
|
m_checkBoxTrackerPortForwarding, m_checkBoxIgnoreSSLErrors, m_checkBoxConfirmTorrentRecheck, m_checkBoxConfirmRemoveAllTags, m_checkBoxAnnounceAllTrackers,
|
||||||
m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts, m_checkBoxPieceExtentAffinity,
|
m_checkBoxAnnounceAllTiers, m_checkBoxMultiConnectionsPerIp, m_checkBoxValidateHTTPSTrackerCertificate, m_checkBoxSSRFMitigation, m_checkBoxBlockPeersOnPrivilegedPorts,
|
||||||
m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport, m_checkBoxConfirmRemoveTrackerFromAllTorrents, m_checkBoxStartSessionPaused;
|
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,
|
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;
|
QLineEdit m_lineEditAppInstanceName, m_pythonExecutablePath, m_lineEditAnnounceIP, m_lineEditDHTBootstrapNodes;
|
||||||
|
|
||||||
#ifndef QBT_USES_LIBTORRENT2
|
#ifndef QBT_USES_LIBTORRENT2
|
||||||
|
@ -107,6 +108,7 @@ private:
|
||||||
|
|
||||||
#ifndef Q_OS_MACOS
|
#ifndef Q_OS_MACOS
|
||||||
QCheckBox m_checkBoxIconsInMenusEnabled;
|
QCheckBox m_checkBoxIconsInMenusEnabled;
|
||||||
|
QCheckBox m_checkBoxAttachedAddNewTorrentDialog;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
|
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
||||||
|
#include "base/bittorrent/session.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
#include "uithememanager.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->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_s));
|
||||||
m_ui->rememberBtn->setIconSize(Utils::Gui::mediumIconSize());
|
m_ui->rememberBtn->setIconSize(Utils::Gui::mediumIconSize());
|
||||||
|
|
||||||
m_ui->checkPermDelete->setChecked(defaultDeleteFiles || Preferences::instance()->deleteTorrentFilesAsDefault());
|
m_ui->checkRemoveContent->setChecked(defaultDeleteFiles || Preferences::instance()->removeTorrentContent());
|
||||||
connect(m_ui->checkPermDelete, &QCheckBox::clicked, this, &DeletionConfirmationDialog::updateRememberButtonState);
|
connect(m_ui->checkRemoveContent, &QCheckBox::clicked, this, &DeletionConfirmationDialog::updateRememberButtonState);
|
||||||
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Remove"));
|
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Remove"));
|
||||||
m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setFocus();
|
m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setFocus();
|
||||||
|
|
||||||
|
@ -67,18 +69,18 @@ DeletionConfirmationDialog::~DeletionConfirmationDialog()
|
||||||
delete m_ui;
|
delete m_ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeletionConfirmationDialog::isDeleteFileSelected() const
|
bool DeletionConfirmationDialog::isRemoveContentSelected() const
|
||||||
{
|
{
|
||||||
return m_ui->checkPermDelete->isChecked();
|
return m_ui->checkRemoveContent->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeletionConfirmationDialog::updateRememberButtonState()
|
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()
|
void DeletionConfirmationDialog::on_rememberBtn_clicked()
|
||||||
{
|
{
|
||||||
Preferences::instance()->setDeleteTorrentFilesAsDefault(m_ui->checkPermDelete->isChecked());
|
Preferences::instance()->setRemoveTorrentContent(m_ui->checkRemoveContent->isChecked());
|
||||||
m_ui->rememberBtn->setEnabled(false);
|
m_ui->rememberBtn->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -37,16 +38,16 @@ namespace Ui
|
||||||
class DeletionConfirmationDialog;
|
class DeletionConfirmationDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DeletionConfirmationDialog : public QDialog
|
class DeletionConfirmationDialog final : public QDialog
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY_MOVE(DeletionConfirmationDialog)
|
Q_DISABLE_COPY_MOVE(DeletionConfirmationDialog)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
DeletionConfirmationDialog(QWidget *parent, int size, const QString &name, bool defaultDeleteFiles);
|
DeletionConfirmationDialog(QWidget *parent, int size, const QString &name, bool defaultDeleteFiles);
|
||||||
~DeletionConfirmationDialog();
|
~DeletionConfirmationDialog() override;
|
||||||
|
|
||||||
bool isDeleteFileSelected() const;
|
bool isRemoveContentSelected() const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void updateRememberButtonState();
|
void updateRememberButtonState();
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="checkPermDelete">
|
<widget class="QCheckBox" name="checkRemoveContent">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
</font>
|
</font>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Also permanently delete the files</string>
|
<string>Also remove the content files</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
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);
|
||||||
|
};
|
|
@ -175,13 +175,16 @@ 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 bool hasMetadata = torrentDescr.info().has_value();
|
||||||
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
|
const BitTorrent::InfoHash infoHash = torrentDescr.infoHash();
|
||||||
|
|
||||||
// Prevent showing the dialog if download is already present
|
// Prevent showing the dialog if download is already present
|
||||||
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
||||||
|
{
|
||||||
|
if (Preferences::instance()->confirmMergeTrackers())
|
||||||
{
|
{
|
||||||
if (hasMetadata)
|
if (hasMetadata)
|
||||||
{
|
{
|
||||||
|
@ -189,27 +192,32 @@ bool GUIAddTorrentManager::processTorrent(const QString &source, const BitTorren
|
||||||
torrent->setMetadata(*torrentDescr.info());
|
torrent->setMetadata(*torrentDescr.info());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate()))
|
const bool isPrivate = torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
||||||
|
const QString dialogCaption = tr("Torrent is already present");
|
||||||
|
if (isPrivate)
|
||||||
{
|
{
|
||||||
handleDuplicateTorrent(source, torrent, tr("Trackers cannot be merged because it is a private torrent"));
|
// 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
|
else
|
||||||
{
|
{
|
||||||
bool mergeTrackers = btSession()->isMergeTrackersEnabled();
|
const bool mergeTrackers = btSession()->isMergeTrackersEnabled();
|
||||||
if (Preferences::instance()->confirmMergeTrackers())
|
const QMessageBox::StandardButton btn = RaisedMessageBox::question(app()->mainWindow(), dialogCaption
|
||||||
{
|
|
||||||
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())
|
, 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);
|
, (QMessageBox::Yes | QMessageBox::No), (mergeTrackers ? QMessageBox::Yes : QMessageBox::No));
|
||||||
mergeTrackers = (btn == QMessageBox::Yes);
|
if (btn == QMessageBox::Yes)
|
||||||
}
|
|
||||||
|
|
||||||
if (mergeTrackers)
|
|
||||||
{
|
{
|
||||||
torrent->addTrackers(torrentDescr.trackers());
|
torrent->addTrackers(torrentDescr.trackers());
|
||||||
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handleDuplicateTorrent(source, torrentDescr, torrent);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -217,25 +225,39 @@ bool GUIAddTorrentManager::processTorrent(const QString &source, const BitTorren
|
||||||
if (!hasMetadata)
|
if (!hasMetadata)
|
||||||
btSession()->downloadMetadata(torrentDescr);
|
btSession()->downloadMetadata(torrentDescr);
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
const bool attached = false;
|
||||||
|
#else
|
||||||
|
const bool attached = Preferences::instance()->isAddNewTorrentDialogAttached();
|
||||||
|
#endif
|
||||||
|
|
||||||
// By not setting a parent to the "AddNewTorrentDialog", all those dialogs
|
// By not setting a parent to the "AddNewTorrentDialog", all those dialogs
|
||||||
// will be displayed on top and will not overlap with the main window.
|
// will be displayed on top and will not overlap with the main window.
|
||||||
auto *dlg = new AddNewTorrentDialog(torrentDescr, params, nullptr);
|
auto *dlg = new AddNewTorrentDialog(torrentDescr, params, (attached ? app()->mainWindow() : nullptr));
|
||||||
// Qt::Window is required to avoid showing only two dialog on top (see #12852).
|
// Qt::Window is required to avoid showing only two dialog on top (see #12852).
|
||||||
// Also improves the general convenience of adding multiple torrents.
|
// Also improves the general convenience of adding multiple torrents.
|
||||||
|
if (!attached)
|
||||||
dlg->setWindowFlags(Qt::Window);
|
dlg->setWindowFlags(Qt::Window);
|
||||||
|
|
||||||
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
dlg->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
m_dialogs[infoHash] = dlg;
|
m_dialogs[infoHash] = dlg;
|
||||||
connect(dlg, &AddNewTorrentDialog::torrentAccepted, this
|
connect(dlg, &AddNewTorrentDialog::torrentAccepted, this
|
||||||
, [this, source](const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &addTorrentParams)
|
, [this, source, dlg](const BitTorrent::TorrentDescriptor &torrentDescr, const BitTorrent::AddTorrentParams &addTorrentParams)
|
||||||
{
|
|
||||||
addTorrentToSession(source, torrentDescr, addTorrentParams);
|
|
||||||
});
|
|
||||||
connect(dlg, &QDialog::finished, this, [this, source, infoHash, dlg]
|
|
||||||
{
|
{
|
||||||
if (dlg->isDoNotDeleteTorrentChecked())
|
if (dlg->isDoNotDeleteTorrentChecked())
|
||||||
releaseTorrentFileGuard(source);
|
{
|
||||||
|
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);
|
m_dialogs.remove(infoHash);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2023-2024 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2024 Jonathan Ketchker
|
* Copyright (C) 2024 Jonathan Ketchker
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
|
@ -30,6 +30,7 @@
|
||||||
|
|
||||||
#include "optionsdialog.h"
|
#include "optionsdialog.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
@ -44,6 +45,10 @@
|
||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <QStyleFactory>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "base/bittorrent/session.h"
|
#include "base/bittorrent/session.h"
|
||||||
#include "base/bittorrent/sharelimitaction.h"
|
#include "base/bittorrent/sharelimitaction.h"
|
||||||
#include "base/exceptions.h"
|
#include "base/exceptions.h"
|
||||||
|
@ -56,6 +61,7 @@
|
||||||
#include "base/rss/rss_session.h"
|
#include "base/rss/rss_session.h"
|
||||||
#include "base/torrentfileguard.h"
|
#include "base/torrentfileguard.h"
|
||||||
#include "base/torrentfileswatcher.h"
|
#include "base/torrentfileswatcher.h"
|
||||||
|
#include "base/utils/compare.h"
|
||||||
#include "base/utils/io.h"
|
#include "base/utils/io.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/net.h"
|
#include "base/utils/net.h"
|
||||||
|
@ -236,6 +242,8 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||||
initializeLanguageCombo();
|
initializeLanguageCombo();
|
||||||
setLocale(pref->getLocale());
|
setLocale(pref->getLocale());
|
||||||
|
|
||||||
|
initializeStyleCombo();
|
||||||
|
|
||||||
m_ui->checkUseCustomTheme->setChecked(Preferences::instance()->useCustomUITheme());
|
m_ui->checkUseCustomTheme->setChecked(Preferences::instance()->useCustomUITheme());
|
||||||
m_ui->customThemeFilePath->setSelectedPath(Preferences::instance()->customUIThemePath());
|
m_ui->customThemeFilePath->setSelectedPath(Preferences::instance()->customUIThemePath());
|
||||||
m_ui->customThemeFilePath->setMode(FileSystemPathEdit::Mode::FileOpen);
|
m_ui->customThemeFilePath->setMode(FileSystemPathEdit::Mode::FileOpen);
|
||||||
|
@ -345,7 +353,11 @@ void OptionsDialog::loadBehaviorTabOptions()
|
||||||
|
|
||||||
m_ui->checkBoxPerformanceWarning->setChecked(session->isPerformanceWarningEnabled());
|
m_ui->checkBoxPerformanceWarning->setChecked(session->isPerformanceWarningEnabled());
|
||||||
|
|
||||||
connect(m_ui->comboI18n, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
connect(m_ui->comboLanguage, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
connect(m_ui->comboStyle, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
||||||
connect(m_ui->checkUseSystemIcon, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
connect(m_ui->checkUseSystemIcon, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
|
||||||
|
@ -443,6 +455,10 @@ void OptionsDialog::saveBehaviorTabOptions() const
|
||||||
}
|
}
|
||||||
pref->setLocale(locale);
|
pref->setLocale(locale);
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
pref->setStyle(m_ui->comboStyle->currentData().toString());
|
||||||
|
#endif
|
||||||
|
|
||||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS))
|
||||||
pref->useSystemIcons(m_ui->checkUseSystemIcon->isChecked());
|
pref->useSystemIcons(m_ui->checkUseSystemIcon->isChecked());
|
||||||
#endif
|
#endif
|
||||||
|
@ -1387,7 +1403,7 @@ void OptionsDialog::initializeLanguageCombo()
|
||||||
for (const QString &langFile : langFiles)
|
for (const QString &langFile : langFiles)
|
||||||
{
|
{
|
||||||
const QString langCode = QStringView(langFile).sliced(12).chopped(3).toString(); // remove "qbittorrent_" and ".qm"
|
const QString langCode = QStringView(langFile).sliced(12).chopped(3).toString(); // remove "qbittorrent_" and ".qm"
|
||||||
m_ui->comboI18n->addItem(Utils::Misc::languageToLocalizedString(langCode), langCode);
|
m_ui->comboLanguage->addItem(Utils::Misc::languageToLocalizedString(langCode), langCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1672,6 +1688,34 @@ bool OptionsDialog::isSplashScreenDisabled() const
|
||||||
return !m_ui->checkShowSplash->isChecked();
|
return !m_ui->checkShowSplash->isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OptionsDialog::initializeStyleCombo()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
m_ui->labelStyleHint->setText(tr("%1 is recommended for best compatibility with Windows dark mode"
|
||||||
|
, "Fusion is recommended for best compatibility with Windows dark mode").arg(u"Fusion"_s));
|
||||||
|
m_ui->comboStyle->addItem(tr("System", "System default Qt style"), u"system"_s);
|
||||||
|
m_ui->comboStyle->setItemData(0, tr("Let Qt decide the style for this system"), Qt::ToolTipRole);
|
||||||
|
m_ui->comboStyle->insertSeparator(1);
|
||||||
|
|
||||||
|
QStringList styleNames = QStyleFactory::keys();
|
||||||
|
std::sort(styleNames.begin(), styleNames.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
|
||||||
|
for (const QString &styleName : asConst(styleNames))
|
||||||
|
m_ui->comboStyle->addItem(styleName, styleName);
|
||||||
|
|
||||||
|
const QString prefStyleName = Preferences::instance()->getStyle();
|
||||||
|
const QString selectedStyleName = prefStyleName.isEmpty() ? QApplication::style()->name() : prefStyleName;
|
||||||
|
const int styleIndex = m_ui->comboStyle->findData(selectedStyleName, Qt::UserRole, Qt::MatchFixedString);
|
||||||
|
m_ui->comboStyle->setCurrentIndex(std::max(0, styleIndex));
|
||||||
|
#else
|
||||||
|
m_ui->labelStyle->hide();
|
||||||
|
m_ui->comboStyle->hide();
|
||||||
|
m_ui->labelStyleHint->hide();
|
||||||
|
m_ui->UISettingsBoxLayout->removeWidget(m_ui->labelStyle);
|
||||||
|
m_ui->UISettingsBoxLayout->removeWidget(m_ui->comboStyle);
|
||||||
|
m_ui->UISettingsBoxLayout->removeWidget(m_ui->labelStyleHint);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
bool OptionsDialog::WinStartup() const
|
bool OptionsDialog::WinStartup() const
|
||||||
{
|
{
|
||||||
|
@ -1721,7 +1765,7 @@ QString OptionsDialog::getProxyPassword() const
|
||||||
// Locale Settings
|
// Locale Settings
|
||||||
QString OptionsDialog::getLocale() const
|
QString OptionsDialog::getLocale() const
|
||||||
{
|
{
|
||||||
return m_ui->comboI18n->itemData(m_ui->comboI18n->currentIndex(), Qt::UserRole).toString();
|
return m_ui->comboLanguage->itemData(m_ui->comboLanguage->currentIndex(), Qt::UserRole).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OptionsDialog::setLocale(const QString &localeStr)
|
void OptionsDialog::setLocale(const QString &localeStr)
|
||||||
|
@ -1746,7 +1790,7 @@ void OptionsDialog::setLocale(const QString &localeStr)
|
||||||
name = locale.name();
|
name = locale.name();
|
||||||
}
|
}
|
||||||
// Attempt to find exact match
|
// Attempt to find exact match
|
||||||
int index = m_ui->comboI18n->findData(name, Qt::UserRole);
|
int index = m_ui->comboLanguage->findData(name, Qt::UserRole);
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
//Attempt to find a language match without a country
|
//Attempt to find a language match without a country
|
||||||
|
@ -1754,16 +1798,16 @@ void OptionsDialog::setLocale(const QString &localeStr)
|
||||||
if (pos > -1)
|
if (pos > -1)
|
||||||
{
|
{
|
||||||
QString lang = name.left(pos);
|
QString lang = name.left(pos);
|
||||||
index = m_ui->comboI18n->findData(lang, Qt::UserRole);
|
index = m_ui->comboLanguage->findData(lang, Qt::UserRole);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
{
|
{
|
||||||
// Unrecognized, use US English
|
// Unrecognized, use US English
|
||||||
index = m_ui->comboI18n->findData(u"en"_s, Qt::UserRole);
|
index = m_ui->comboLanguage->findData(u"en"_s, Qt::UserRole);
|
||||||
Q_ASSERT(index >= 0);
|
Q_ASSERT(index >= 0);
|
||||||
}
|
}
|
||||||
m_ui->comboI18n->setCurrentIndex(index);
|
m_ui->comboLanguage->setCurrentIndex(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
Path OptionsDialog::getTorrentExportDir() const
|
Path OptionsDialog::getTorrentExportDir() const
|
||||||
|
@ -1869,7 +1913,7 @@ Path OptionsDialog::getFilter() const
|
||||||
void OptionsDialog::webUIHttpsCertChanged(const Path &path)
|
void OptionsDialog::webUIHttpsCertChanged(const Path &path)
|
||||||
{
|
{
|
||||||
const auto readResult = Utils::IO::readFile(path, Utils::Net::MAX_SSL_FILE_SIZE);
|
const auto readResult = Utils::IO::readFile(path, Utils::Net::MAX_SSL_FILE_SIZE);
|
||||||
const bool isCertValid = !Utils::SSLKey::load(readResult.value_or(QByteArray())).isNull();
|
const bool isCertValid = Utils::Net::isSSLCertificatesValid(readResult.value_or(QByteArray()));
|
||||||
|
|
||||||
m_ui->textWebUIHttpsCert->setSelectedPath(path);
|
m_ui->textWebUIHttpsCert->setSelectedPath(path);
|
||||||
m_ui->lblSslCertStatus->setPixmap(UIThemeManager::instance()->getScaledPixmap(
|
m_ui->lblSslCertStatus->setPixmap(UIThemeManager::instance()->getScaledPixmap(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2023-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -143,6 +143,7 @@ private:
|
||||||
|
|
||||||
// General options
|
// General options
|
||||||
void initializeLanguageCombo();
|
void initializeLanguageCombo();
|
||||||
|
void initializeStyleCombo();
|
||||||
QString getLocale() const;
|
QString getLocale() const;
|
||||||
bool isSplashScreenDisabled() const;
|
bool isSplashScreenDisabled() const;
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
|
|
@ -132,9 +132,9 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Interface</string>
|
<string>Interface</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_81">
|
<layout class="QGridLayout" name="UISettingsBoxLayout">
|
||||||
<item row="0" column="0" colspan="3">
|
<item row="0" column="0" colspan="3">
|
||||||
<widget class="QLabel" name="label_15">
|
<widget class="QLabel" name="labelRestartRequired">
|
||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<italic>true</italic>
|
<italic>true</italic>
|
||||||
|
@ -146,17 +146,17 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_9">
|
<widget class="QLabel" name="labelLanguage">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Language:</string>
|
<string>Language:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QComboBox" name="comboI18n"/>
|
<widget class="QComboBox" name="comboLanguage"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="2">
|
<item row="1" column="2">
|
||||||
<spacer name="horizontalSpacer_111">
|
<spacer name="spacerLanguage">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
<enum>Qt::Horizontal</enum>
|
<enum>Qt::Horizontal</enum>
|
||||||
</property>
|
</property>
|
||||||
|
@ -168,7 +168,26 @@
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0" colspan="3">
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="labelStyle">
|
||||||
|
<property name="text">
|
||||||
|
<string>Style:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QComboBox" name="comboStyle"/>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="2">
|
||||||
|
<widget class="QLabel" name="labelStyleHint">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<italic>true</italic>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0" colspan="3">
|
||||||
<widget class="QGroupBox" name="checkUseCustomTheme">
|
<widget class="QGroupBox" name="checkUseCustomTheme">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Use custom UI Theme</string>
|
<string>Use custom UI Theme</string>
|
||||||
|
@ -190,14 +209,14 @@
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0" colspan="2">
|
<item row="4" column="0" colspan="2">
|
||||||
<widget class="QCheckBox" name="checkUseSystemIcon">
|
<widget class="QCheckBox" name="checkUseSystemIcon">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Use icons from system theme</string>
|
<string>Use icons from system theme</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0" colspan="2">
|
<item row="5" column="0" colspan="2">
|
||||||
<widget class="QPushButton" name="buttonCustomizeUITheme">
|
<widget class="QPushButton" name="buttonCustomizeUITheme">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Customize UI Theme...</string>
|
<string>Customize UI Theme...</string>
|
||||||
|
@ -3881,7 +3900,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<tabstops>
|
<tabstops>
|
||||||
<tabstop>tabOption</tabstop>
|
<tabstop>tabOption</tabstop>
|
||||||
<tabstop>comboI18n</tabstop>
|
<tabstop>comboLanguage</tabstop>
|
||||||
<tabstop>checkUseCustomTheme</tabstop>
|
<tabstop>checkUseCustomTheme</tabstop>
|
||||||
<tabstop>customThemeFilePath</tabstop>
|
<tabstop>customThemeFilePath</tabstop>
|
||||||
<tabstop>checkAddStopped</tabstop>
|
<tabstop>checkAddStopped</tabstop>
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
#include <IOKit/pwr_mgt/IOPMLib.h>
|
#include <IOKit/pwr_mgt/IOPMLib.h>
|
||||||
|
#include <QScopeGuard>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
@ -74,8 +75,10 @@ void PowerManagement::setBusy()
|
||||||
#elif defined(QBT_USES_DBUS)
|
#elif defined(QBT_USES_DBUS)
|
||||||
m_inhibitor->requestBusy();
|
m_inhibitor->requestBusy();
|
||||||
#elif defined(Q_OS_MACOS)
|
#elif defined(Q_OS_MACOS)
|
||||||
|
const CFStringRef assertName = tr("qBittorrent is active").toCFString();
|
||||||
|
[[maybe_unused]] const auto assertNameGuard = qScopeGuard([&assertName] { ::CFRelease(assertName); });
|
||||||
const IOReturn success = ::IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn
|
const IOReturn success = ::IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn
|
||||||
, tr("qBittorrent is active").toCFString(), &m_assertionID);
|
, assertName, &m_assertionID);
|
||||||
if (success != kIOReturnSuccess)
|
if (success != kIOReturnSuccess)
|
||||||
m_busy = false;
|
m_busy = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -29,6 +29,9 @@
|
||||||
|
|
||||||
#include "programupdater.h"
|
#include "programupdater.h"
|
||||||
|
|
||||||
|
#include <libtorrent/version.hpp>
|
||||||
|
|
||||||
|
#include <QtCore/qconfig.h>
|
||||||
#include <QtSystemDetection>
|
#include <QtSystemDetection>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
|
@ -61,6 +64,20 @@ namespace
|
||||||
}
|
}
|
||||||
return (newVersion > currentVersion);
|
return (newVersion > currentVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString buildVariant()
|
||||||
|
{
|
||||||
|
#if defined(Q_OS_MACOS)
|
||||||
|
const auto BASE_OS = u"Mac OS X"_s;
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
const auto BASE_OS = u"Windows x64"_s;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if constexpr ((QT_VERSION_MAJOR == 6) && (LIBTORRENT_VERSION_MAJOR == 1))
|
||||||
|
return BASE_OS;
|
||||||
|
|
||||||
|
return u"%1 (qt%2 lt%3%4)"_s.arg(BASE_OS, QString::number(QT_VERSION_MAJOR), QString::number(LIBTORRENT_VERSION_MAJOR), QString::number(LIBTORRENT_VERSION_MINOR));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgramUpdater::checkForUpdates() const
|
void ProgramUpdater::checkForUpdates() const
|
||||||
|
@ -97,12 +114,7 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
||||||
: QString {};
|
: QString {};
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
const QString variant = buildVariant();
|
||||||
const QString OS_TYPE = u"Mac OS X"_s;
|
|
||||||
#elif defined(Q_OS_WIN)
|
|
||||||
const QString OS_TYPE = u"Windows x64"_s;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool inItem = false;
|
bool inItem = false;
|
||||||
QString version;
|
QString version;
|
||||||
QString updateLink;
|
QString updateLink;
|
||||||
|
@ -128,7 +140,7 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
||||||
{
|
{
|
||||||
if (inItem && (xml.name() == u"item"))
|
if (inItem && (xml.name() == u"item"))
|
||||||
{
|
{
|
||||||
if (type.compare(OS_TYPE, Qt::CaseInsensitive) == 0)
|
if (type.compare(variant, Qt::CaseInsensitive) == 0)
|
||||||
{
|
{
|
||||||
qDebug("The last update available is %s", qUtf8Printable(version));
|
qDebug("The last update available is %s", qUtf8Printable(version));
|
||||||
if (!version.isEmpty())
|
if (!version.isEmpty())
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -46,9 +47,9 @@ namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadedPiecesBar::DownloadedPiecesBar(QWidget *parent)
|
DownloadedPiecesBar::DownloadedPiecesBar(QWidget *parent)
|
||||||
: base {parent}
|
: base(parent)
|
||||||
, m_dlPieceColor {dlPieceColor(pieceColor())}
|
|
||||||
{
|
{
|
||||||
|
updateColorsImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<float> DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin, int reqSize)
|
QVector<float> DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin, int reqSize)
|
||||||
|
@ -128,25 +129,24 @@ QVector<float> DownloadedPiecesBar::bitfieldToFloatVector(const QBitArray &vecin
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DownloadedPiecesBar::updateImage(QImage &image)
|
QImage DownloadedPiecesBar::renderImage()
|
||||||
{
|
{
|
||||||
// qDebug() << "updateImage";
|
// qDebug() << "updateImage";
|
||||||
QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888);
|
QImage image {width() - 2 * borderWidth, 1, QImage::Format_RGB888};
|
||||||
if (image2.isNull())
|
if (image.isNull())
|
||||||
{
|
{
|
||||||
qDebug() << "QImage image2() allocation failed, width():" << width();
|
qDebug() << "QImage allocation failed, width():" << width();
|
||||||
return false;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_pieces.isEmpty())
|
if (m_pieces.isEmpty())
|
||||||
{
|
{
|
||||||
image2.fill(backgroundColor());
|
image.fill(backgroundColor());
|
||||||
image = image2;
|
return image;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<float> scaledPieces = bitfieldToFloatVector(m_pieces, image2.width());
|
QVector<float> scaledPieces = bitfieldToFloatVector(m_pieces, image.width());
|
||||||
QVector<float> scaledPiecesDl = bitfieldToFloatVector(m_downloadedPieces, image2.width());
|
QVector<float> scaledPiecesDl = bitfieldToFloatVector(m_downloadedPieces, image.width());
|
||||||
|
|
||||||
// filling image
|
// filling image
|
||||||
for (int x = 0; x < scaledPieces.size(); ++x)
|
for (int x = 0; x < scaledPieces.size(); ++x)
|
||||||
|
@ -161,15 +161,15 @@ bool DownloadedPiecesBar::updateImage(QImage &image)
|
||||||
QRgb mixedColor = mixTwoColors(pieceColor().rgb(), m_dlPieceColor.rgb(), ratio);
|
QRgb mixedColor = mixTwoColors(pieceColor().rgb(), m_dlPieceColor.rgb(), ratio);
|
||||||
mixedColor = mixTwoColors(backgroundColor().rgb(), mixedColor, fillRatio);
|
mixedColor = mixTwoColors(backgroundColor().rgb(), mixedColor, fillRatio);
|
||||||
|
|
||||||
image2.setPixel(x, 0, mixedColor);
|
image.setPixel(x, 0, mixedColor);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
|
image.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
image = image2;
|
|
||||||
return true;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DownloadedPiecesBar::setProgress(const QBitArray &pieces, const QBitArray &downloadedPieces)
|
void DownloadedPiecesBar::setProgress(const QBitArray &pieces, const QBitArray &downloadedPieces)
|
||||||
|
@ -177,7 +177,7 @@ void DownloadedPiecesBar::setProgress(const QBitArray &pieces, const QBitArray &
|
||||||
m_pieces = pieces;
|
m_pieces = pieces;
|
||||||
m_downloadedPieces = downloadedPieces;
|
m_downloadedPieces = downloadedPieces;
|
||||||
|
|
||||||
requestImageUpdate();
|
redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DownloadedPiecesBar::clear()
|
void DownloadedPiecesBar::clear()
|
||||||
|
@ -198,3 +198,14 @@ QString DownloadedPiecesBar::simpleToolTipText() const
|
||||||
+ u"</table>";
|
+ u"</table>";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DownloadedPiecesBar::updateColors()
|
||||||
|
{
|
||||||
|
PiecesBar::updateColors();
|
||||||
|
updateColorsImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DownloadedPiecesBar::updateColorsImpl()
|
||||||
|
{
|
||||||
|
m_dlPieceColor = dlPieceColor(pieceColor());
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -52,11 +53,13 @@ public:
|
||||||
private:
|
private:
|
||||||
// scale bitfield vector to float vector
|
// scale bitfield vector to float vector
|
||||||
QVector<float> bitfieldToFloatVector(const QBitArray &vecin, int reqSize);
|
QVector<float> bitfieldToFloatVector(const QBitArray &vecin, int reqSize);
|
||||||
bool updateImage(QImage &image) override;
|
QImage renderImage() override;
|
||||||
QString simpleToolTipText() const override;
|
QString simpleToolTipText() const override;
|
||||||
|
void updateColors() override;
|
||||||
|
void updateColorsImpl();
|
||||||
|
|
||||||
// incomplete piece color
|
// incomplete piece color
|
||||||
const QColor m_dlPieceColor;
|
QColor m_dlPieceColor;
|
||||||
// last used bitfields, uses to better resize redraw
|
// last used bitfields, uses to better resize redraw
|
||||||
// TODO: make a diff pieces to new pieces and update only changed pixels, speedup when update > 20x faster
|
// TODO: make a diff pieces to new pieces and update only changed pixels, speedup when update > 20x faster
|
||||||
QBitArray m_pieces;
|
QBitArray m_pieces;
|
||||||
|
|
|
@ -411,7 +411,7 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Remove I2P peers since they will be completely reloaded.
|
// Remove I2P peers since they will be completely reloaded.
|
||||||
for (QStandardItem *item : asConst(m_I2PPeerItems))
|
for (const QStandardItem *item : asConst(m_I2PPeerItems))
|
||||||
m_listModel->removeRow(item->row());
|
m_listModel->removeRow(item->row());
|
||||||
m_I2PPeerItems.clear();
|
m_I2PPeerItems.clear();
|
||||||
|
|
||||||
|
@ -420,7 +420,8 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
|
||||||
for (auto i = m_peerItems.cbegin(); i != m_peerItems.cend(); ++i)
|
for (auto i = m_peerItems.cbegin(); i != m_peerItems.cend(); ++i)
|
||||||
existingPeers.insert(i.key());
|
existingPeers.insert(i.key());
|
||||||
|
|
||||||
const bool hideZeroValues = Preferences::instance()->getHideZeroValues();
|
const Preferences *pref = Preferences::instance();
|
||||||
|
const bool hideZeroValues = (pref->getHideZeroValues() && (pref->getHideZeroComboValues() == 0));
|
||||||
for (const BitTorrent::PeerInfo &peer : peers)
|
for (const BitTorrent::PeerInfo &peer : peers)
|
||||||
{
|
{
|
||||||
const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()};
|
const PeerEndpoint peerEndpoint {peer.address(), peer.connectionType()};
|
||||||
|
@ -466,10 +467,14 @@ void PeerListWidget::loadPeers(const BitTorrent::Torrent *torrent)
|
||||||
{
|
{
|
||||||
QStandardItem *item = m_peerItems.take(peerEndpoint);
|
QStandardItem *item = m_peerItems.take(peerEndpoint);
|
||||||
|
|
||||||
QSet<QStandardItem *> &items = m_itemsByIP[peerEndpoint.address.ip];
|
const auto items = m_itemsByIP.find(peerEndpoint.address.ip);
|
||||||
items.remove(item);
|
Q_ASSERT(items != m_itemsByIP.end());
|
||||||
if (items.isEmpty())
|
if (items == m_itemsByIP.end()) [[unlikely]]
|
||||||
m_itemsByIP.remove(peerEndpoint.address.ip);
|
continue;
|
||||||
|
|
||||||
|
items->remove(item);
|
||||||
|
if (items->isEmpty())
|
||||||
|
m_itemsByIP.erase(items);
|
||||||
|
|
||||||
m_listModel->removeRow(item->row());
|
m_listModel->removeRow(item->row());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -126,39 +127,38 @@ QVector<float> PieceAvailabilityBar::intToFloatVector(const QVector<int> &vecin,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PieceAvailabilityBar::updateImage(QImage &image)
|
QImage PieceAvailabilityBar::renderImage()
|
||||||
{
|
{
|
||||||
QImage image2(width() - 2 * borderWidth, 1, QImage::Format_RGB888);
|
QImage image {width() - 2 * borderWidth, 1, QImage::Format_RGB888};
|
||||||
if (image2.isNull())
|
if (image.isNull())
|
||||||
{
|
{
|
||||||
qDebug() << "QImage image2() allocation failed, width():" << width();
|
qDebug() << "QImage allocation failed, width():" << width();
|
||||||
return false;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_pieces.empty())
|
if (m_pieces.empty())
|
||||||
{
|
{
|
||||||
image2.fill(backgroundColor());
|
image.fill(backgroundColor());
|
||||||
image = image2;
|
return image;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<float> scaledPieces = intToFloatVector(m_pieces, image2.width());
|
QVector<float> scaledPieces = intToFloatVector(m_pieces, image.width());
|
||||||
|
|
||||||
// filling image
|
// filling image
|
||||||
for (int x = 0; x < scaledPieces.size(); ++x)
|
for (int x = 0; x < scaledPieces.size(); ++x)
|
||||||
{
|
{
|
||||||
float piecesToValue = scaledPieces.at(x);
|
float piecesToValue = scaledPieces.at(x);
|
||||||
image2.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
|
image.setPixel(x, 0, pieceColors()[piecesToValue * 255]);
|
||||||
}
|
}
|
||||||
image = image2;
|
|
||||||
return true;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PieceAvailabilityBar::setAvailability(const QVector<int> &avail)
|
void PieceAvailabilityBar::setAvailability(const QVector<int> &avail)
|
||||||
{
|
{
|
||||||
m_pieces = avail;
|
m_pieces = avail;
|
||||||
|
|
||||||
requestImageUpdate();
|
redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PieceAvailabilityBar::clear()
|
void PieceAvailabilityBar::clear()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -46,7 +47,7 @@ public:
|
||||||
void clear() override;
|
void clear() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool updateImage(QImage &image) override;
|
QImage renderImage() override;
|
||||||
QString simpleToolTipText() const override;
|
QString simpleToolTipText() const override;
|
||||||
|
|
||||||
// last used int vector, uses to better resize redraw
|
// last used int vector, uses to better resize redraw
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2016 Eugene Shalygin
|
* Copyright (C) 2016 Eugene Shalygin
|
||||||
* Copyright (C) 2006 Christophe Dumez
|
* Copyright (C) 2006 Christophe Dumez
|
||||||
*
|
*
|
||||||
|
@ -114,10 +115,10 @@ namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
PiecesBar::PiecesBar(QWidget *parent)
|
PiecesBar::PiecesBar(QWidget *parent)
|
||||||
: QWidget {parent}
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
updatePieceColors();
|
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
|
updateColorsImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiecesBar::setTorrent(const BitTorrent::Torrent *torrent)
|
void PiecesBar::setTorrent(const BitTorrent::Torrent *torrent)
|
||||||
|
@ -135,12 +136,19 @@ void PiecesBar::clear()
|
||||||
|
|
||||||
bool PiecesBar::event(QEvent *e)
|
bool PiecesBar::event(QEvent *e)
|
||||||
{
|
{
|
||||||
if (e->type() == QEvent::ToolTip)
|
const QEvent::Type eventType = e->type();
|
||||||
|
if (eventType == QEvent::ToolTip)
|
||||||
{
|
{
|
||||||
showToolTip(static_cast<QHelpEvent *>(e));
|
showToolTip(static_cast<QHelpEvent *>(e));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventType == QEvent::PaletteChange)
|
||||||
|
{
|
||||||
|
updateColors();
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
return base::event(e);
|
return base::event(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +162,7 @@ void PiecesBar::leaveEvent(QEvent *e)
|
||||||
{
|
{
|
||||||
m_hovered = false;
|
m_hovered = false;
|
||||||
m_highlightedRegion = {};
|
m_highlightedRegion = {};
|
||||||
requestImageUpdate();
|
redraw();
|
||||||
base::leaveEvent(e);
|
base::leaveEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,16 +186,17 @@ void PiecesBar::paintEvent(QPaintEvent *)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (m_image.width() != imageRect.width())
|
if (m_image.width() != imageRect.width())
|
||||||
updateImage(m_image);
|
{
|
||||||
|
if (const QImage image = renderImage(); !image.isNull())
|
||||||
|
m_image = image;
|
||||||
|
}
|
||||||
painter.drawImage(imageRect, m_image);
|
painter.drawImage(imageRect, m_image);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_highlightedRegion.isNull())
|
if (!m_highlightedRegion.isNull())
|
||||||
{
|
{
|
||||||
QColor highlightColor {this->palette().color(QPalette::Active, QPalette::Highlight)};
|
|
||||||
highlightColor.setAlphaF(0.35f);
|
|
||||||
QRect targetHighlightRect {m_highlightedRegion.adjusted(borderWidth, borderWidth, borderWidth, height() - 2 * borderWidth)};
|
QRect targetHighlightRect {m_highlightedRegion.adjusted(borderWidth, borderWidth, borderWidth, height() - 2 * borderWidth)};
|
||||||
painter.fillRect(targetHighlightRect, highlightColor);
|
painter.fillRect(targetHighlightRect, highlightedPieceColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
QPainterPath border;
|
QPainterPath border;
|
||||||
|
@ -196,30 +205,40 @@ void PiecesBar::paintEvent(QPaintEvent *)
|
||||||
painter.drawPath(border);
|
painter.drawPath(border);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiecesBar::requestImageUpdate()
|
void PiecesBar::redraw()
|
||||||
{
|
{
|
||||||
if (updateImage(m_image))
|
if (const QImage image = renderImage(); !image.isNull())
|
||||||
|
{
|
||||||
|
m_image = image;
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QColor PiecesBar::backgroundColor() const
|
QColor PiecesBar::backgroundColor() const
|
||||||
{
|
{
|
||||||
return palette().color(QPalette::Base);
|
return palette().color(QPalette::Active, QPalette::Base);
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor PiecesBar::borderColor() const
|
QColor PiecesBar::borderColor() const
|
||||||
{
|
{
|
||||||
return palette().color(QPalette::Dark);
|
return palette().color(QPalette::Active, QPalette::Dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor PiecesBar::pieceColor() const
|
QColor PiecesBar::pieceColor() const
|
||||||
{
|
{
|
||||||
return palette().color(QPalette::Highlight);
|
return palette().color(QPalette::Active, QPalette::Highlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor PiecesBar::highlightedPieceColor() const
|
||||||
|
{
|
||||||
|
QColor col = palette().color(QPalette::Highlight).darker();
|
||||||
|
col.setAlphaF(0.35);
|
||||||
|
return col;
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor PiecesBar::colorBoxBorderColor() const
|
QColor PiecesBar::colorBoxBorderColor() const
|
||||||
{
|
{
|
||||||
return palette().color(QPalette::ToolTipText);
|
return palette().color(QPalette::Active, QPalette::ToolTipText);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QVector<QRgb> &PiecesBar::pieceColors() const
|
const QVector<QRgb> &PiecesBar::pieceColors() const
|
||||||
|
@ -325,12 +344,17 @@ void PiecesBar::highlightFile(int imagePos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PiecesBar::updatePieceColors()
|
void PiecesBar::updateColors()
|
||||||
|
{
|
||||||
|
updateColorsImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PiecesBar::updateColorsImpl()
|
||||||
{
|
{
|
||||||
m_pieceColors = QVector<QRgb>(256);
|
m_pieceColors = QVector<QRgb>(256);
|
||||||
for (int i = 0; i < 256; ++i)
|
for (int i = 0; i < 256; ++i)
|
||||||
{
|
{
|
||||||
float ratio = (i / 255.0);
|
const float ratio = (i / 255.0);
|
||||||
m_pieceColors[i] = mixTwoColors(backgroundColor().rgb(), pieceColor().rgb(), ratio);
|
m_pieceColors[i] = mixTwoColors(backgroundColor().rgb(), pieceColor().rgb(), ratio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2016 Eugene Shalygin
|
* Copyright (C) 2016 Eugene Shalygin
|
||||||
* Copyright (C) 2006 Christophe Dumez
|
* Copyright (C) 2006 Christophe Dumez
|
||||||
*
|
*
|
||||||
|
@ -54,22 +55,22 @@ public:
|
||||||
|
|
||||||
virtual void clear();
|
virtual void clear();
|
||||||
|
|
||||||
// QObject interface
|
|
||||||
bool event(QEvent *e) override;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// QWidget interface
|
bool event(QEvent *e) override;
|
||||||
void enterEvent(QEnterEvent *e) override;
|
void enterEvent(QEnterEvent *e) override;
|
||||||
void leaveEvent(QEvent *e) override;
|
void leaveEvent(QEvent *e) override;
|
||||||
void mouseMoveEvent(QMouseEvent *e) override;
|
void mouseMoveEvent(QMouseEvent *e) override;
|
||||||
|
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent *e) override;
|
||||||
void requestImageUpdate();
|
|
||||||
|
virtual void updateColors();
|
||||||
|
void redraw();
|
||||||
|
|
||||||
QColor backgroundColor() const;
|
QColor backgroundColor() const;
|
||||||
QColor borderColor() const;
|
QColor borderColor() const;
|
||||||
QColor pieceColor() const;
|
QColor pieceColor() const;
|
||||||
|
QColor highlightedPieceColor() const;
|
||||||
QColor colorBoxBorderColor() const;
|
QColor colorBoxBorderColor() const;
|
||||||
|
|
||||||
const QVector<QRgb> &pieceColors() const;
|
const QVector<QRgb> &pieceColors() const;
|
||||||
|
|
||||||
// mix two colors by light model, ratio <0, 1>
|
// mix two colors by light model, ratio <0, 1>
|
||||||
|
@ -82,11 +83,9 @@ private:
|
||||||
void highlightFile(int imagePos);
|
void highlightFile(int imagePos);
|
||||||
|
|
||||||
virtual QString simpleToolTipText() const = 0;
|
virtual QString simpleToolTipText() const = 0;
|
||||||
|
virtual QImage renderImage() = 0;
|
||||||
|
|
||||||
// draw new image to replace the actual image
|
void updateColorsImpl();
|
||||||
// returns true if image was successfully updated
|
|
||||||
virtual bool updateImage(QImage &image) = 0;
|
|
||||||
void updatePieceColors();
|
|
||||||
|
|
||||||
const BitTorrent::Torrent *m_torrent = nullptr;
|
const BitTorrent::Torrent *m_torrent = nullptr;
|
||||||
QImage m_image;
|
QImage m_image;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -52,6 +52,7 @@
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
#include "gui/autoexpandabledialog.h"
|
#include "gui/autoexpandabledialog.h"
|
||||||
|
#include "gui/filterpatternformatmenu.h"
|
||||||
#include "gui/lineedit.h"
|
#include "gui/lineedit.h"
|
||||||
#include "gui/trackerlist/trackerlistwidget.h"
|
#include "gui/trackerlist/trackerlistwidget.h"
|
||||||
#include "gui/uithememanager.h"
|
#include "gui/uithememanager.h"
|
||||||
|
@ -66,6 +67,7 @@
|
||||||
PropertiesWidget::PropertiesWidget(QWidget *parent)
|
PropertiesWidget::PropertiesWidget(QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_ui {new Ui::PropertiesWidget}
|
, m_ui {new Ui::PropertiesWidget}
|
||||||
|
, m_storeFilterPatternFormat {u"GUI/PropertiesWidget/FilterPatternFormat"_s}
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
#ifndef Q_OS_MACOS
|
#ifndef Q_OS_MACOS
|
||||||
|
@ -78,7 +80,9 @@ PropertiesWidget::PropertiesWidget(QWidget *parent)
|
||||||
m_contentFilterLine = new LineEdit(this);
|
m_contentFilterLine = new LineEdit(this);
|
||||||
m_contentFilterLine->setPlaceholderText(tr("Filter files..."));
|
m_contentFilterLine->setPlaceholderText(tr("Filter files..."));
|
||||||
m_contentFilterLine->setFixedWidth(300);
|
m_contentFilterLine->setFixedWidth(300);
|
||||||
connect(m_contentFilterLine, &LineEdit::textChanged, m_ui->filesList, &TorrentContentWidget::setFilterPattern);
|
m_contentFilterLine->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
connect(m_contentFilterLine, &QWidget::customContextMenuRequested, this, &PropertiesWidget::showContentFilterContextMenu);
|
||||||
|
connect(m_contentFilterLine, &LineEdit::textChanged, this, &PropertiesWidget::setContentFilterPattern);
|
||||||
m_ui->contentFilterLayout->insertWidget(3, m_contentFilterLine);
|
m_ui->contentFilterLayout->insertWidget(3, m_contentFilterLine);
|
||||||
|
|
||||||
m_ui->filesList->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Open);
|
m_ui->filesList->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Open);
|
||||||
|
@ -206,6 +210,7 @@ void PropertiesWidget::clear()
|
||||||
m_ui->labelSavePathVal->clear();
|
m_ui->labelSavePathVal->clear();
|
||||||
m_ui->labelCreatedOnVal->clear();
|
m_ui->labelCreatedOnVal->clear();
|
||||||
m_ui->labelTotalPiecesVal->clear();
|
m_ui->labelTotalPiecesVal->clear();
|
||||||
|
m_ui->labelPrivateVal->clear();
|
||||||
m_ui->labelInfohash1Val->clear();
|
m_ui->labelInfohash1Val->clear();
|
||||||
m_ui->labelInfohash2Val->clear();
|
m_ui->labelInfohash2Val->clear();
|
||||||
m_ui->labelCommentVal->clear();
|
m_ui->labelCommentVal->clear();
|
||||||
|
@ -274,6 +279,28 @@ void PropertiesWidget::updateSavePath(BitTorrent::Torrent *const torrent)
|
||||||
m_ui->labelSavePathVal->setText(m_torrent->savePath().toString());
|
m_ui->labelSavePathVal->setText(m_torrent->savePath().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PropertiesWidget::showContentFilterContextMenu()
|
||||||
|
{
|
||||||
|
QMenu *menu = m_contentFilterLine->createStandardContextMenu();
|
||||||
|
|
||||||
|
auto *formatMenu = new FilterPatternFormatMenu(m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards), menu);
|
||||||
|
connect(formatMenu, &FilterPatternFormatMenu::patternFormatChanged, this, [this](const FilterPatternFormat format)
|
||||||
|
{
|
||||||
|
m_storeFilterPatternFormat = format;
|
||||||
|
setContentFilterPattern();
|
||||||
|
});
|
||||||
|
|
||||||
|
menu->addSeparator();
|
||||||
|
menu->addMenu(formatMenu);
|
||||||
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
menu->popup(QCursor::pos());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PropertiesWidget::setContentFilterPattern()
|
||||||
|
{
|
||||||
|
m_ui->filesList->setFilterPattern(m_contentFilterLine->text(), m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards));
|
||||||
|
}
|
||||||
|
|
||||||
void PropertiesWidget::updateTorrentInfos(BitTorrent::Torrent *const torrent)
|
void PropertiesWidget::updateTorrentInfos(BitTorrent::Torrent *const torrent)
|
||||||
{
|
{
|
||||||
if (torrent == m_torrent)
|
if (torrent == m_torrent)
|
||||||
|
@ -309,7 +336,14 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent)
|
||||||
m_ui->labelCommentVal->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment().toHtmlEscaped()));
|
m_ui->labelCommentVal->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment().toHtmlEscaped()));
|
||||||
|
|
||||||
m_ui->labelCreatedByVal->setText(m_torrent->creator());
|
m_ui->labelCreatedByVal->setText(m_torrent->creator());
|
||||||
|
|
||||||
|
m_ui->labelPrivateVal->setText(m_torrent->isPrivate() ? tr("Yes") : tr("No"));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_ui->labelPrivateVal->setText(tr("N/A"));
|
||||||
|
}
|
||||||
|
|
||||||
// Load dynamic data
|
// Load dynamic data
|
||||||
loadDynamicData();
|
loadDynamicData();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -32,7 +32,8 @@
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "base/pathfwd.h"
|
#include "base/settingvalue.h"
|
||||||
|
#include "gui/filterpatternformat.h"
|
||||||
|
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
class QTreeView;
|
class QTreeView;
|
||||||
|
@ -102,6 +103,8 @@ private slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPushButton *getButtonFromIndex(int index);
|
QPushButton *getButtonFromIndex(int index);
|
||||||
|
void showContentFilterContextMenu();
|
||||||
|
void setContentFilterPattern();
|
||||||
|
|
||||||
Ui::PropertiesWidget *m_ui = nullptr;
|
Ui::PropertiesWidget *m_ui = nullptr;
|
||||||
BitTorrent::Torrent *m_torrent = nullptr;
|
BitTorrent::Torrent *m_torrent = nullptr;
|
||||||
|
@ -115,4 +118,6 @@ private:
|
||||||
PropTabBar *m_tabBar = nullptr;
|
PropTabBar *m_tabBar = nullptr;
|
||||||
LineEdit *m_contentFilterLine = nullptr;
|
LineEdit *m_contentFilterLine = nullptr;
|
||||||
int m_handleWidth = -1;
|
int m_handleWidth = -1;
|
||||||
|
|
||||||
|
SettingValue<FilterPatternFormat> m_storeFilterPatternFormat;
|
||||||
};
|
};
|
||||||
|
|
|
@ -823,6 +823,38 @@
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="0">
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="labelPrivate">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Private:</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1" colspan="5">
|
||||||
|
<widget class="QLabel" name="labelPrivateVal">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::PlainText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="labelInfohash1">
|
<widget class="QLabel" name="labelInfohash1">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||||
|
@ -838,71 +870,7 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
|
||||||
<widget class="QLabel" name="labelInfohash2">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Info Hash v2:</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="3" column="1" colspan="5">
|
<item row="3" column="1" colspan="5">
|
||||||
<widget class="QLabel" name="labelInfohash2Val">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="textFormat">
|
|
||||||
<enum>Qt::PlainText</enum>
|
|
||||||
</property>
|
|
||||||
<property name="textInteractionFlags">
|
|
||||||
<set>Qt::TextSelectableByMouse</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="4" column="0">
|
|
||||||
<widget class="QLabel" name="labelSavePath">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Save Path:</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="5" column="0">
|
|
||||||
<widget class="QLabel" name="labelComment">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Comment:</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="1" colspan="5">
|
|
||||||
<widget class="QLabel" name="labelInfohash1Val">
|
<widget class="QLabel" name="labelInfohash1Val">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
|
@ -918,7 +886,55 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="labelInfohash2">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Info Hash v2:</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item row="4" column="1" colspan="5">
|
<item row="4" column="1" colspan="5">
|
||||||
|
<widget class="QLabel" name="labelInfohash2Val">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="textFormat">
|
||||||
|
<enum>Qt::PlainText</enum>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="labelSavePath">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Save Path:</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="1" colspan="5">
|
||||||
<widget class="QLabel" name="labelSavePathVal">
|
<widget class="QLabel" name="labelSavePathVal">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
|
@ -937,7 +953,23 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="5" column="1" colspan="5">
|
<item row="6" column="0">
|
||||||
|
<widget class="QLabel" name="labelComment">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Comment:</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="1" colspan="5">
|
||||||
<widget class="QLabel" name="labelCommentVal">
|
<widget class="QLabel" name="labelCommentVal">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||||
|
|
|
@ -106,10 +106,12 @@ void HtmlBrowser::resourceLoaded(QNetworkReply *reply)
|
||||||
atts[QNetworkRequest::HttpStatusCodeAttribute] = 200;
|
atts[QNetworkRequest::HttpStatusCodeAttribute] = 200;
|
||||||
atts[QNetworkRequest::HttpReasonPhraseAttribute] = u"Ok"_s;
|
atts[QNetworkRequest::HttpReasonPhraseAttribute] = u"Ok"_s;
|
||||||
metaData.setAttributes(atts);
|
metaData.setAttributes(atts);
|
||||||
metaData.setLastModified(QDateTime::currentDateTime());
|
const auto currentDateTime = QDateTime::currentDateTime();
|
||||||
metaData.setExpirationDate(QDateTime::currentDateTime().addDays(1));
|
metaData.setLastModified(currentDateTime);
|
||||||
|
metaData.setExpirationDate(currentDateTime.addDays(1));
|
||||||
QIODevice *dev = m_diskCache->prepare(metaData);
|
QIODevice *dev = m_diskCache->prepare(metaData);
|
||||||
if (!dev) return;
|
if (!dev)
|
||||||
|
return;
|
||||||
|
|
||||||
QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(32, 32).save(dev, "PNG");
|
QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(32, 32).save(dev, "PNG");
|
||||||
m_diskCache->insert(dev);
|
m_diskCache->insert(dev);
|
||||||
|
|
|
@ -126,6 +126,8 @@ RSSWidget::RSSWidget(IGUIApplication *app, QWidget *parent)
|
||||||
, this, &RSSWidget::handleSessionProcessingStateChanged);
|
, this, &RSSWidget::handleSessionProcessingStateChanged);
|
||||||
connect(RSS::Session::instance()->rootFolder(), &RSS::Folder::unreadCountChanged
|
connect(RSS::Session::instance()->rootFolder(), &RSS::Folder::unreadCountChanged
|
||||||
, this, &RSSWidget::handleUnreadCountChanged);
|
, this, &RSSWidget::handleUnreadCountChanged);
|
||||||
|
|
||||||
|
m_ui->textBrowser->installEventFilter(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
RSSWidget::~RSSWidget()
|
RSSWidget::~RSSWidget()
|
||||||
|
@ -494,14 +496,76 @@ void RSSWidget::handleCurrentArticleItemChanged(QListWidgetItem *currentItem, QL
|
||||||
article->markAsRead();
|
article->markAsRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!currentItem) return;
|
if (!currentItem)
|
||||||
|
return;
|
||||||
|
|
||||||
auto *article = m_articleListWidget->getRSSArticle(currentItem);
|
auto *article = m_articleListWidget->getRSSArticle(currentItem);
|
||||||
|
renderArticle(article);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RSSWidget::saveSlidersPosition()
|
||||||
|
{
|
||||||
|
// Remember sliders positions
|
||||||
|
Preferences *const pref = Preferences::instance();
|
||||||
|
pref->setRssSideSplitterState(m_ui->splitterSide->saveState());
|
||||||
|
pref->setRssMainSplitterState(m_ui->splitterMain->saveState());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RSSWidget::restoreSlidersPosition()
|
||||||
|
{
|
||||||
|
const Preferences *const pref = Preferences::instance();
|
||||||
|
const QByteArray stateSide = pref->getRssSideSplitterState();
|
||||||
|
if (!stateSide.isEmpty())
|
||||||
|
m_ui->splitterSide->restoreState(stateSide);
|
||||||
|
const QByteArray stateMain = pref->getRssMainSplitterState();
|
||||||
|
if (!stateMain.isEmpty())
|
||||||
|
m_ui->splitterMain->restoreState(stateMain);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RSSWidget::updateRefreshInterval(int val) const
|
||||||
|
{
|
||||||
|
RSS::Session::instance()->setRefreshInterval(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RSSWidget::on_rssDownloaderBtn_clicked()
|
||||||
|
{
|
||||||
|
auto *downloader = new AutomatedRssDownloader(this);
|
||||||
|
downloader->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
downloader->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RSSWidget::handleSessionProcessingStateChanged(bool enabled)
|
||||||
|
{
|
||||||
|
m_ui->labelWarn->setVisible(!enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RSSWidget::handleUnreadCountChanged()
|
||||||
|
{
|
||||||
|
emit unreadCountUpdated(RSS::Session::instance()->rootFolder()->unreadCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RSSWidget::eventFilter(QObject *obj, QEvent *event)
|
||||||
|
{
|
||||||
|
if ((obj == m_ui->textBrowser) && (event->type() == QEvent::PaletteChange))
|
||||||
|
{
|
||||||
|
QListWidgetItem *currentItem = m_articleListWidget->currentItem();
|
||||||
|
if (currentItem)
|
||||||
|
{
|
||||||
|
const RSS::Article *article = m_articleListWidget->getRSSArticle(currentItem);
|
||||||
|
renderArticle(article);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RSSWidget::renderArticle(const RSS::Article *article) const
|
||||||
|
{
|
||||||
Q_ASSERT(article);
|
Q_ASSERT(article);
|
||||||
|
|
||||||
const QString highlightedBaseColor = m_ui->textBrowser->palette().color(QPalette::Highlight).name();
|
const QString highlightedBaseColor = m_ui->textBrowser->palette().color(QPalette::Active, QPalette::Highlight).name();
|
||||||
const QString highlightedBaseTextColor = m_ui->textBrowser->palette().color(QPalette::HighlightedText).name();
|
const QString highlightedBaseTextColor = m_ui->textBrowser->palette().color(QPalette::Active, QPalette::HighlightedText).name();
|
||||||
const QString alternateBaseColor = m_ui->textBrowser->palette().color(QPalette::AlternateBase).name();
|
const QString alternateBaseColor = m_ui->textBrowser->palette().color(QPalette::Active, QPalette::AlternateBase).name();
|
||||||
|
|
||||||
QString html =
|
QString html =
|
||||||
u"<div style='border: 2px solid red; margin-left: 5px; margin-right: 5px; margin-bottom: 5px;'>" +
|
u"<div style='border: 2px solid red; margin-left: 5px; margin-right: 5px; margin-bottom: 5px;'>" +
|
||||||
|
@ -549,44 +613,3 @@ void RSSWidget::handleCurrentArticleItemChanged(QListWidgetItem *currentItem, QL
|
||||||
html += u"</div>";
|
html += u"</div>";
|
||||||
m_ui->textBrowser->setHtml(html);
|
m_ui->textBrowser->setHtml(html);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RSSWidget::saveSlidersPosition()
|
|
||||||
{
|
|
||||||
// Remember sliders positions
|
|
||||||
Preferences *const pref = Preferences::instance();
|
|
||||||
pref->setRssSideSplitterState(m_ui->splitterSide->saveState());
|
|
||||||
pref->setRssMainSplitterState(m_ui->splitterMain->saveState());
|
|
||||||
}
|
|
||||||
|
|
||||||
void RSSWidget::restoreSlidersPosition()
|
|
||||||
{
|
|
||||||
const Preferences *const pref = Preferences::instance();
|
|
||||||
const QByteArray stateSide = pref->getRssSideSplitterState();
|
|
||||||
if (!stateSide.isEmpty())
|
|
||||||
m_ui->splitterSide->restoreState(stateSide);
|
|
||||||
const QByteArray stateMain = pref->getRssMainSplitterState();
|
|
||||||
if (!stateMain.isEmpty())
|
|
||||||
m_ui->splitterMain->restoreState(stateMain);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RSSWidget::updateRefreshInterval(int val) const
|
|
||||||
{
|
|
||||||
RSS::Session::instance()->setRefreshInterval(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RSSWidget::on_rssDownloaderBtn_clicked()
|
|
||||||
{
|
|
||||||
auto *downloader = new AutomatedRssDownloader(this);
|
|
||||||
downloader->setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
downloader->open();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RSSWidget::handleSessionProcessingStateChanged(bool enabled)
|
|
||||||
{
|
|
||||||
m_ui->labelWarn->setVisible(!enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RSSWidget::handleUnreadCountChanged()
|
|
||||||
{
|
|
||||||
emit unreadCountUpdated(RSS::Session::instance()->rootFolder()->unreadCount());
|
|
||||||
}
|
|
||||||
|
|
|
@ -40,6 +40,11 @@ class QTreeWidgetItem;
|
||||||
class ArticleListWidget;
|
class ArticleListWidget;
|
||||||
class FeedListWidget;
|
class FeedListWidget;
|
||||||
|
|
||||||
|
namespace RSS
|
||||||
|
{
|
||||||
|
class Article;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
class RSSWidget;
|
class RSSWidget;
|
||||||
|
@ -85,6 +90,9 @@ private slots:
|
||||||
void handleUnreadCountChanged();
|
void handleUnreadCountChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
|
void renderArticle(const RSS::Article *article) const;
|
||||||
|
|
||||||
Ui::RSSWidget *m_ui = nullptr;
|
Ui::RSSWidget *m_ui = nullptr;
|
||||||
ArticleListWidget *m_articleListWidget = nullptr;
|
ArticleListWidget *m_articleListWidget = nullptr;
|
||||||
FeedListWidget *m_feedListWidget = nullptr;
|
FeedListWidget *m_feedListWidget = nullptr;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2018-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -50,6 +50,19 @@
|
||||||
#include "searchsortmodel.h"
|
#include "searchsortmodel.h"
|
||||||
#include "ui_searchjobwidget.h"
|
#include "ui_searchjobwidget.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
enum DataRole
|
||||||
|
{
|
||||||
|
LinkVisitedRole = Qt::UserRole + 100
|
||||||
|
};
|
||||||
|
|
||||||
|
QColor visitedRowColor()
|
||||||
|
{
|
||||||
|
return QApplication::palette().color(QPalette::Disabled, QPalette::WindowText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent)
|
SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *app, QWidget *parent)
|
||||||
: GUIApplicationComponent(app, parent)
|
: GUIApplicationComponent(app, parent)
|
||||||
, m_ui {new Ui::SearchJobWidget}
|
, m_ui {new Ui::SearchJobWidget}
|
||||||
|
@ -158,6 +171,8 @@ SearchJobWidget::SearchJobWidget(SearchHandler *searchHandler, IGUIApplication *
|
||||||
connect(this, &QObject::destroyed, searchHandler, &QObject::deleteLater);
|
connect(this, &QObject::destroyed, searchHandler, &QObject::deleteLater);
|
||||||
|
|
||||||
setStatusTip(statusText(m_status));
|
setStatusTip(statusText(m_status));
|
||||||
|
|
||||||
|
connect(UIThemeManager::instance(), &UIThemeManager::themeChanged, this, &SearchJobWidget::onUIThemeChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchJobWidget::~SearchJobWidget()
|
SearchJobWidget::~SearchJobWidget()
|
||||||
|
@ -179,9 +194,31 @@ QHeaderView *SearchJobWidget::header() const
|
||||||
// Set the color of a row in data model
|
// Set the color of a row in data model
|
||||||
void SearchJobWidget::setRowColor(int row, const QColor &color)
|
void SearchJobWidget::setRowColor(int row, const QColor &color)
|
||||||
{
|
{
|
||||||
m_proxyModel->setDynamicSortFilter(false);
|
|
||||||
for (int i = 0; i < m_proxyModel->columnCount(); ++i)
|
for (int i = 0; i < m_proxyModel->columnCount(); ++i)
|
||||||
m_proxyModel->setData(m_proxyModel->index(row, i), color, Qt::ForegroundRole);
|
m_proxyModel->setData(m_proxyModel->index(row, i), color, Qt::ForegroundRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchJobWidget::setRowVisited(const int row)
|
||||||
|
{
|
||||||
|
m_proxyModel->setDynamicSortFilter(false);
|
||||||
|
|
||||||
|
m_proxyModel->setData(m_proxyModel->index(row, 0), true, LinkVisitedRole);
|
||||||
|
setRowColor(row, visitedRowColor());
|
||||||
|
|
||||||
|
m_proxyModel->setDynamicSortFilter(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchJobWidget::onUIThemeChanged()
|
||||||
|
{
|
||||||
|
m_proxyModel->setDynamicSortFilter(false);
|
||||||
|
|
||||||
|
for (int row = 0; row < m_proxyModel->rowCount(); ++row)
|
||||||
|
{
|
||||||
|
const QVariant userData = m_proxyModel->data(m_proxyModel->index(row, 0), LinkVisitedRole);
|
||||||
|
const bool isVisited = userData.toBool();
|
||||||
|
if (isVisited)
|
||||||
|
setRowColor(row, visitedRowColor());
|
||||||
|
}
|
||||||
|
|
||||||
m_proxyModel->setDynamicSortFilter(true);
|
m_proxyModel->setDynamicSortFilter(true);
|
||||||
}
|
}
|
||||||
|
@ -284,7 +321,8 @@ void SearchJobWidget::downloadTorrent(const QModelIndex &rowIndex, const AddTorr
|
||||||
, this, [this, option](const QString &source) { addTorrentToSession(source, option); });
|
, this, [this, option](const QString &source) { addTorrentToSession(source, option); });
|
||||||
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
|
connect(downloadHandler, &SearchDownloadHandler::downloadFinished, downloadHandler, &SearchDownloadHandler::deleteLater);
|
||||||
}
|
}
|
||||||
setRowColor(rowIndex.row(), QApplication::palette().color(QPalette::LinkVisited));
|
|
||||||
|
setRowVisited(rowIndex.row());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchJobWidget::addTorrentToSession(const QString &source, const AddTorrentOption option)
|
void SearchJobWidget::addTorrentToSession(const QString &source, const AddTorrentOption option)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2018 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2018-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -113,8 +113,10 @@ private:
|
||||||
void fillFilterComboBoxes();
|
void fillFilterComboBoxes();
|
||||||
NameFilteringMode filteringMode() const;
|
NameFilteringMode filteringMode() const;
|
||||||
QHeaderView *header() const;
|
QHeaderView *header() const;
|
||||||
void setRowColor(int row, const QColor &color);
|
|
||||||
int visibleColumnsCount() const;
|
int visibleColumnsCount() const;
|
||||||
|
void setRowColor(int row, const QColor &color);
|
||||||
|
void setRowVisited(int row);
|
||||||
|
void onUIThemeChanged();
|
||||||
|
|
||||||
void downloadTorrents(AddTorrentOption option = AddTorrentOption::Default);
|
void downloadTorrents(AddTorrentOption option = AddTorrentOption::Default);
|
||||||
void openTorrentPages() const;
|
void openTorrentPages() const;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2020, Will Da Silva <will@willdasilva.xyz>
|
* Copyright (C) 2020, Will Da Silva <will@willdasilva.xyz>
|
||||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -120,6 +120,7 @@ SearchWidget::SearchWidget(IGUIApplication *app, MainWindow *mainWindow)
|
||||||
#endif
|
#endif
|
||||||
connect(m_ui->tabWidget, &QTabWidget::tabCloseRequested, this, &SearchWidget::closeTab);
|
connect(m_ui->tabWidget, &QTabWidget::tabCloseRequested, this, &SearchWidget::closeTab);
|
||||||
connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &SearchWidget::tabChanged);
|
connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, &SearchWidget::tabChanged);
|
||||||
|
connect(m_ui->tabWidget->tabBar(), &QTabBar::tabMoved, this, &SearchWidget::tabMoved);
|
||||||
|
|
||||||
const auto *searchManager = SearchPluginManager::instance();
|
const auto *searchManager = SearchPluginManager::instance();
|
||||||
const auto onPluginChanged = [this]()
|
const auto onPluginChanged = [this]()
|
||||||
|
@ -262,6 +263,11 @@ void SearchWidget::tabChanged(const int index)
|
||||||
m_currentSearchTab = ((index < 0) ? nullptr : m_allTabs.at(m_ui->tabWidget->currentIndex()));
|
m_currentSearchTab = ((index < 0) ? nullptr : m_allTabs.at(m_ui->tabWidget->currentIndex()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SearchWidget::tabMoved(const int from, const int to)
|
||||||
|
{
|
||||||
|
m_allTabs.move(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
void SearchWidget::selectMultipleBox([[maybe_unused]] const int index)
|
void SearchWidget::selectMultipleBox([[maybe_unused]] const int index)
|
||||||
{
|
{
|
||||||
if (selectedPlugin() == u"multi")
|
if (selectedPlugin() == u"multi")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
* Copyright (C) 2020, Will Da Silva <will@willdasilva.xyz>
|
* Copyright (C) 2020, Will Da Silva <will@willdasilva.xyz>
|
||||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
|
||||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -66,6 +66,7 @@ private slots:
|
||||||
private:
|
private:
|
||||||
bool eventFilter(QObject *object, QEvent *event) override;
|
bool eventFilter(QObject *object, QEvent *event) override;
|
||||||
void tabChanged(int index);
|
void tabChanged(int index);
|
||||||
|
void tabMoved(int from, int to);
|
||||||
void closeTab(int index);
|
void closeTab(int index);
|
||||||
void closeAllTabs();
|
void closeAllTabs();
|
||||||
void tabStatusChanged(QWidget *tab);
|
void tabStatusChanged(QWidget *tab);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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-2012 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -37,17 +37,12 @@
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QScopeGuard>
|
#include <QScopeGuard>
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_MACOS)
|
||||||
#include <windows.h>
|
|
||||||
#include <shellapi.h>
|
|
||||||
#else
|
|
||||||
#include <QMimeDatabase>
|
|
||||||
#include <QMimeType>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined Q_OS_WIN || defined Q_OS_MACOS
|
|
||||||
#define QBT_PIXMAP_CACHE_FOR_FILE_ICONS
|
#define QBT_PIXMAP_CACHE_FOR_FILE_ICONS
|
||||||
#include <QPixmapCache>
|
#include <QPixmapCache>
|
||||||
|
#elif !defined(Q_OS_WIN)
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
#include <QMimeType>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "base/bittorrent/downloadpriority.h"
|
#include "base/bittorrent/downloadpriority.h"
|
||||||
|
@ -116,27 +111,8 @@ namespace
|
||||||
};
|
};
|
||||||
#endif // QBT_PIXMAP_CACHE_FOR_FILE_ICONS
|
#endif // QBT_PIXMAP_CACHE_FOR_FILE_ICONS
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_MACOS)
|
||||||
// See QTBUG-25319 for explanation why this is required
|
// There is a bug on macOS, to be reported to Qt
|
||||||
class WinShellFileIconProvider final : public CachingFileIconProvider
|
|
||||||
{
|
|
||||||
QPixmap pixmapForExtension(const QString &ext) const override
|
|
||||||
{
|
|
||||||
const std::wstring extWStr = QString(u'.' + ext).toStdWString();
|
|
||||||
|
|
||||||
SHFILEINFOW sfi {};
|
|
||||||
const HRESULT hr = ::SHGetFileInfoW(extWStr.c_str(),
|
|
||||||
FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(sfi), (SHGFI_ICON | SHGFI_USEFILEATTRIBUTES));
|
|
||||||
if (FAILED(hr))
|
|
||||||
return {};
|
|
||||||
|
|
||||||
const auto iconPixmap = QPixmap::fromImage(QImage::fromHICON(sfi.hIcon));
|
|
||||||
::DestroyIcon(sfi.hIcon);
|
|
||||||
return iconPixmap;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
#elif defined(Q_OS_MACOS)
|
|
||||||
// There is a similar bug on macOS, to be reported to Qt
|
|
||||||
// https://github.com/qbittorrent/qBittorrent/pull/6156#issuecomment-316302615
|
// https://github.com/qbittorrent/qBittorrent/pull/6156#issuecomment-316302615
|
||||||
class MacFileIconProvider final : public CachingFileIconProvider
|
class MacFileIconProvider final : public CachingFileIconProvider
|
||||||
{
|
{
|
||||||
|
@ -145,7 +121,7 @@ namespace
|
||||||
return MacUtils::pixmapForExtension(ext, QSize(32, 32));
|
return MacUtils::pixmapForExtension(ext, QSize(32, 32));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#else
|
#elif !defined(Q_OS_WIN)
|
||||||
/**
|
/**
|
||||||
* @brief Tests whether QFileIconProvider actually works
|
* @brief Tests whether QFileIconProvider actually works
|
||||||
*
|
*
|
||||||
|
@ -189,7 +165,7 @@ TorrentContentModel::TorrentContentModel(QObject *parent)
|
||||||
: QAbstractItemModel(parent)
|
: QAbstractItemModel(parent)
|
||||||
, m_rootItem(new TorrentContentModelFolder(QVector<QString>({ tr("Name"), tr("Total Size"), tr("Progress"), tr("Download Priority"), tr("Remaining"), tr("Availability") })))
|
, m_rootItem(new TorrentContentModelFolder(QVector<QString>({ tr("Name"), tr("Total Size"), tr("Progress"), tr("Download Priority"), tr("Remaining"), tr("Availability") })))
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
, m_fileIconProvider {new WinShellFileIconProvider}
|
, m_fileIconProvider {new QFileIconProvider}
|
||||||
#elif defined(Q_OS_MACOS)
|
#elif defined(Q_OS_MACOS)
|
||||||
, m_fileIconProvider {new MacFileIconProvider}
|
, m_fileIconProvider {new MacFileIconProvider}
|
||||||
#else
|
#else
|
||||||
|
@ -422,7 +398,9 @@ QVariant TorrentContentModel::data(const QModelIndex &index, const int role) con
|
||||||
const bool hasIgnored = std::any_of(childItems.cbegin(), childItems.cend()
|
const bool hasIgnored = std::any_of(childItems.cbegin(), childItems.cend()
|
||||||
, [](const TorrentContentModelItem *childItem)
|
, [](const TorrentContentModelItem *childItem)
|
||||||
{
|
{
|
||||||
return (childItem->priority() == BitTorrent::DownloadPriority::Ignored);
|
const auto prio = childItem->priority();
|
||||||
|
return ((prio == BitTorrent::DownloadPriority::Ignored)
|
||||||
|
|| (prio == BitTorrent::DownloadPriority::Mixed));
|
||||||
});
|
});
|
||||||
|
|
||||||
return hasIgnored ? Qt::PartiallyChecked : Qt::Checked;
|
return hasIgnored ? Qt::PartiallyChecked : Qt::Checked;
|
||||||
|
|
|
@ -147,10 +147,19 @@ void TorrentContentModelFolder::recalculateProgress()
|
||||||
tRemaining += child->remaining();
|
tRemaining += child->remaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isRootItem() && (tSize > 0))
|
if (!isRootItem())
|
||||||
|
{
|
||||||
|
if (tSize > 0)
|
||||||
{
|
{
|
||||||
m_progress = tProgress / tSize;
|
m_progress = tProgress / tSize;
|
||||||
m_remaining = tRemaining;
|
m_remaining = tRemaining;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_progress = 1.0;
|
||||||
|
m_remaining = 0;
|
||||||
|
}
|
||||||
|
|
||||||
Q_ASSERT(m_progress <= 1.);
|
Q_ASSERT(m_progress <= 1.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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) 2014 Ivan Sorokin <vanyacpp@gmail.com>
|
* Copyright (C) 2014 Ivan Sorokin <vanyacpp@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -56,6 +56,19 @@
|
||||||
#include "gui/macutilities.h"
|
#include "gui/macutilities.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
QList<QPersistentModelIndex> toPersistentIndexes(const QModelIndexList &indexes)
|
||||||
|
{
|
||||||
|
QList<QPersistentModelIndex> persistentIndexes;
|
||||||
|
persistentIndexes.reserve(indexes.size());
|
||||||
|
for (const QModelIndex &index : indexes)
|
||||||
|
persistentIndexes.emplaceBack(index);
|
||||||
|
|
||||||
|
return persistentIndexes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TorrentContentWidget::TorrentContentWidget(QWidget *parent)
|
TorrentContentWidget::TorrentContentWidget(QWidget *parent)
|
||||||
: QTreeView(parent)
|
: QTreeView(parent)
|
||||||
{
|
{
|
||||||
|
@ -173,10 +186,20 @@ Path TorrentContentWidget::getItemPath(const QModelIndex &index) const
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentContentWidget::setFilterPattern(const QString &patternText)
|
void TorrentContentWidget::setFilterPattern(const QString &patternText, const FilterPatternFormat format)
|
||||||
{
|
{
|
||||||
const QString pattern = Utils::String::wildcardToRegexPattern(patternText);
|
if (format == FilterPatternFormat::PlainText)
|
||||||
|
{
|
||||||
|
m_filterModel->setFilterFixedString(patternText);
|
||||||
|
m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const QString pattern = ((format == FilterPatternFormat::Regex)
|
||||||
|
? patternText : Utils::String::wildcardToRegexPattern(patternText));
|
||||||
m_filterModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
m_filterModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
||||||
|
}
|
||||||
|
|
||||||
if (patternText.isEmpty())
|
if (patternText.isEmpty())
|
||||||
{
|
{
|
||||||
collapseAll();
|
collapseAll();
|
||||||
|
@ -219,9 +242,9 @@ void TorrentContentWidget::keyPressEvent(QKeyEvent *event)
|
||||||
|
|
||||||
const Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked)
|
const Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked)
|
||||||
? Qt::Unchecked : Qt::Checked;
|
? Qt::Unchecked : Qt::Checked;
|
||||||
const QModelIndexList selection = selectionModel()->selectedRows(TorrentContentModelItem::COL_NAME);
|
const QList<QPersistentModelIndex> selection = toPersistentIndexes(selectionModel()->selectedRows(TorrentContentModelItem::COL_NAME));
|
||||||
|
|
||||||
for (const QModelIndex &index : selection)
|
for (const QPersistentModelIndex &index : selection)
|
||||||
model()->setData(index, state, Qt::CheckStateRole);
|
model()->setData(index, state, Qt::CheckStateRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,10 +271,10 @@ void TorrentContentWidget::renameSelectedFile()
|
||||||
|
|
||||||
void TorrentContentWidget::applyPriorities(const BitTorrent::DownloadPriority priority)
|
void TorrentContentWidget::applyPriorities(const BitTorrent::DownloadPriority priority)
|
||||||
{
|
{
|
||||||
const QModelIndexList selectedRows = selectionModel()->selectedRows(0);
|
const QList<QPersistentModelIndex> selectedRows = toPersistentIndexes(selectionModel()->selectedRows(Priority));
|
||||||
for (const QModelIndex &index : selectedRows)
|
for (const QPersistentModelIndex &index : selectedRows)
|
||||||
{
|
{
|
||||||
model()->setData(index.sibling(index.row(), Priority), static_cast<int>(priority));
|
model()->setData(index, static_cast<int>(priority));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +284,7 @@ void TorrentContentWidget::applyPrioritiesByOrder()
|
||||||
// a download priority that will apply to each item. The number of groups depends on how
|
// a download priority that will apply to each item. The number of groups depends on how
|
||||||
// many "download priority" are available to be assigned
|
// many "download priority" are available to be assigned
|
||||||
|
|
||||||
const QModelIndexList selectedRows = selectionModel()->selectedRows(0);
|
const QList<QPersistentModelIndex> selectedRows = toPersistentIndexes(selectionModel()->selectedRows(Priority));
|
||||||
|
|
||||||
const qsizetype priorityGroups = 3;
|
const qsizetype priorityGroups = 3;
|
||||||
const auto priorityGroupSize = std::max<qsizetype>((selectedRows.length() / priorityGroups), 1);
|
const auto priorityGroupSize = std::max<qsizetype>((selectedRows.length() / priorityGroups), 1);
|
||||||
|
@ -283,8 +306,8 @@ void TorrentContentWidget::applyPrioritiesByOrder()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QModelIndex &index = selectedRows[i];
|
const QPersistentModelIndex &index = selectedRows[i];
|
||||||
model()->setData(index.sibling(index.row(), Priority), static_cast<int>(priority));
|
model()->setData(index, static_cast<int>(priority));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* 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) 2014 Ivan Sorokin <vanyacpp@gmail.com>
|
* Copyright (C) 2014 Ivan Sorokin <vanyacpp@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
|
@ -33,6 +33,7 @@
|
||||||
|
|
||||||
#include "base/bittorrent/downloadpriority.h"
|
#include "base/bittorrent/downloadpriority.h"
|
||||||
#include "base/pathfwd.h"
|
#include "base/pathfwd.h"
|
||||||
|
#include "filterpatternformat.h"
|
||||||
|
|
||||||
class QShortcut;
|
class QShortcut;
|
||||||
|
|
||||||
|
@ -92,7 +93,7 @@ public:
|
||||||
int getFileIndex(const QModelIndex &index) const;
|
int getFileIndex(const QModelIndex &index) const;
|
||||||
Path getItemPath(const QModelIndex &index) const;
|
Path getItemPath(const QModelIndex &index) const;
|
||||||
|
|
||||||
void setFilterPattern(const QString &patternText);
|
void setFilterPattern(const QString &patternText, FilterPatternFormat format = FilterPatternFormat::Wildcards);
|
||||||
|
|
||||||
void checkAll();
|
void checkAll();
|
||||||
void checkNone();
|
void checkNone();
|
||||||
|
|
|
@ -84,11 +84,7 @@ TorrentCreatorDialog::TorrentCreatorDialog(QWidget *parent, const Path &defaultP
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
|
|
||||||
m_ui->comboPieceSize->addItem(tr("Auto"), 0);
|
m_ui->comboPieceSize->addItem(tr("Auto"), 0);
|
||||||
#ifdef QBT_USES_LIBTORRENT2
|
|
||||||
for (int i = 4; i <= 18; ++i)
|
|
||||||
#else
|
|
||||||
for (int i = 4; i <= 17; ++i)
|
for (int i = 4; i <= 17; ++i)
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
const int size = 1024 << i;
|
const int size = 1024 << i;
|
||||||
const QString displaySize = Utils::Misc::friendlyUnit(size, false, 0);
|
const QString displaySize = Utils::Misc::friendlyUnit(size, false, 0);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2023-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
|
@ -37,6 +37,7 @@
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "autoexpandabledialog.h"
|
#include "autoexpandabledialog.h"
|
||||||
#include "flowlayout.h"
|
#include "flowlayout.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
#include "ui_torrenttagsdialog.h"
|
#include "ui_torrenttagsdialog.h"
|
||||||
|
|
||||||
|
@ -52,10 +53,10 @@ TorrentTagsDialog::TorrentTagsDialog(const TagSet &initialTags, QWidget *parent)
|
||||||
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
|
||||||
auto *tagsLayout = new FlowLayout(m_ui->scrollArea);
|
auto *tagsLayout = new FlowLayout(m_ui->scrollArea->widget());
|
||||||
for (const Tag &tag : asConst(initialTags.united(BitTorrent::Session::instance()->tags())))
|
for (const Tag &tag : asConst(initialTags.united(BitTorrent::Session::instance()->tags())))
|
||||||
{
|
{
|
||||||
auto *tagWidget = new QCheckBox(tag.toString());
|
auto *tagWidget = new QCheckBox(Utils::Gui::tagToWidgetText(tag));
|
||||||
if (initialTags.contains(tag))
|
if (initialTags.contains(tag))
|
||||||
tagWidget->setChecked(true);
|
tagWidget->setChecked(true);
|
||||||
tagsLayout->addWidget(tagWidget);
|
tagsLayout->addWidget(tagWidget);
|
||||||
|
@ -78,12 +79,12 @@ TorrentTagsDialog::~TorrentTagsDialog()
|
||||||
TagSet TorrentTagsDialog::tags() const
|
TagSet TorrentTagsDialog::tags() const
|
||||||
{
|
{
|
||||||
TagSet tags;
|
TagSet tags;
|
||||||
auto *layout = m_ui->scrollArea->layout();
|
auto *layout = m_ui->scrollArea->widget()->layout();
|
||||||
for (int i = 0; i < (layout->count() - 1); ++i)
|
for (int i = 0; i < (layout->count() - 1); ++i)
|
||||||
{
|
{
|
||||||
const auto *tagWidget = static_cast<QCheckBox *>(layout->itemAt(i)->widget());
|
const auto *tagWidget = static_cast<QCheckBox *>(layout->itemAt(i)->widget());
|
||||||
if (tagWidget->isChecked())
|
if (tagWidget->isChecked())
|
||||||
tags.insert(Tag(tagWidget->text()));
|
tags.insert(Utils::Gui::widgetTextToTag(tagWidget->text()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return tags;
|
return tags;
|
||||||
|
@ -111,9 +112,9 @@ void TorrentTagsDialog::addNewTag()
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto *layout = m_ui->scrollArea->layout();
|
auto *layout = m_ui->scrollArea->widget()->layout();
|
||||||
auto *btn = layout->takeAt(layout->count() - 1);
|
auto *btn = layout->takeAt(layout->count() - 1);
|
||||||
auto *tagWidget = new QCheckBox(tag.toString());
|
auto *tagWidget = new QCheckBox(Utils::Gui::tagToWidgetText(tag));
|
||||||
tagWidget->setChecked(true);
|
tagWidget->setChecked(true);
|
||||||
layout->addWidget(tagWidget);
|
layout->addWidget(tagWidget);
|
||||||
layout->addItem(btn);
|
layout->addItem(btn);
|
||||||
|
|
|
@ -488,11 +488,11 @@ QVariant TrackerListModel::headerData(const int section, const Qt::Orientation o
|
||||||
switch (section)
|
switch (section)
|
||||||
{
|
{
|
||||||
case COL_URL:
|
case COL_URL:
|
||||||
return tr("URL/Announce endpoint");
|
return tr("URL/Announce Endpoint");
|
||||||
case COL_TIER:
|
case COL_TIER:
|
||||||
return tr("Tier");
|
return tr("Tier");
|
||||||
case COL_PROTOCOL:
|
case COL_PROTOCOL:
|
||||||
return tr("Protocol");
|
return tr("BT Protocol");
|
||||||
case COL_STATUS:
|
case COL_STATUS:
|
||||||
return tr("Status");
|
return tr("Status");
|
||||||
case COL_PEERS:
|
case COL_PEERS:
|
||||||
|
@ -506,9 +506,9 @@ QVariant TrackerListModel::headerData(const int section, const Qt::Orientation o
|
||||||
case COL_MSG:
|
case COL_MSG:
|
||||||
return tr("Message");
|
return tr("Message");
|
||||||
case COL_NEXT_ANNOUNCE:
|
case COL_NEXT_ANNOUNCE:
|
||||||
return tr("Next announce");
|
return tr("Next Announce");
|
||||||
case COL_MIN_ANNOUNCE:
|
case COL_MIN_ANNOUNCE:
|
||||||
return tr("Min announce");
|
return tr("Min Announce");
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -585,7 +585,7 @@ QVariant TrackerListModel::data(const QModelIndex &index, const int role) const
|
||||||
case COL_TIER:
|
case COL_TIER:
|
||||||
return (isEndpoint || (index.row() < STICKY_ROW_COUNT)) ? QString() : QString::number(itemPtr->tier);
|
return (isEndpoint || (index.row() < STICKY_ROW_COUNT)) ? QString() : QString::number(itemPtr->tier);
|
||||||
case COL_PROTOCOL:
|
case COL_PROTOCOL:
|
||||||
return isEndpoint ? tr("v%1").arg(itemPtr->btVersion) : QString();
|
return isEndpoint ? (u'v' + QString::number(itemPtr->btVersion)) : QString();
|
||||||
case COL_STATUS:
|
case COL_STATUS:
|
||||||
if (isEndpoint)
|
if (isEndpoint)
|
||||||
return toString(itemPtr->status);
|
return toString(itemPtr->status);
|
||||||
|
|
|
@ -235,10 +235,7 @@ void StatusFilterWidget::applyFilter(int row)
|
||||||
|
|
||||||
void StatusFilterWidget::handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents)
|
void StatusFilterWidget::handleTorrentsLoaded(const QVector<BitTorrent::Torrent *> &torrents)
|
||||||
{
|
{
|
||||||
for (const BitTorrent::Torrent *torrent : torrents)
|
update(torrents);
|
||||||
updateTorrentStatus(torrent);
|
|
||||||
|
|
||||||
updateTexts();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
|
void StatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torrent)
|
||||||
|
@ -273,6 +270,12 @@ void StatusFilterWidget::torrentAboutToBeDeleted(BitTorrent::Torrent *const torr
|
||||||
m_nbStalled = m_nbStalledUploading + m_nbStalledDownloading;
|
m_nbStalled = m_nbStalledUploading + m_nbStalledDownloading;
|
||||||
|
|
||||||
updateTexts();
|
updateTexts();
|
||||||
|
|
||||||
|
if (Preferences::instance()->getHideZeroStatusFilters())
|
||||||
|
{
|
||||||
|
hideZeroItems();
|
||||||
|
updateGeometry();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatusFilterWidget::configure()
|
void StatusFilterWidget::configure()
|
||||||
|
|
|
@ -394,15 +394,11 @@ void TrackersFilterWidget::handleTrackerStatusesUpdated(const BitTorrent::Torren
|
||||||
{
|
{
|
||||||
if (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::Working)
|
if (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::Working)
|
||||||
{
|
{
|
||||||
|
// remove tracker from "error" and "tracker error" categories
|
||||||
if (errorHashesIt != m_errors.end())
|
if (errorHashesIt != m_errors.end())
|
||||||
{
|
|
||||||
errorHashesIt->remove(trackerEntryStatus.url);
|
errorHashesIt->remove(trackerEntryStatus.url);
|
||||||
}
|
|
||||||
|
|
||||||
if (trackerErrorHashesIt != m_trackerErrors.end())
|
if (trackerErrorHashesIt != m_trackerErrors.end())
|
||||||
{
|
|
||||||
trackerErrorHashesIt->remove(trackerEntryStatus.url);
|
trackerErrorHashesIt->remove(trackerEntryStatus.url);
|
||||||
}
|
|
||||||
|
|
||||||
const bool hasNoWarningMessages = std::all_of(trackerEntryStatus.endpoints.cbegin(), trackerEntryStatus.endpoints.cend()
|
const bool hasNoWarningMessages = std::all_of(trackerEntryStatus.endpoints.cbegin(), trackerEntryStatus.endpoints.cend()
|
||||||
, [](const BitTorrent::TrackerEndpointStatus &endpointEntry)
|
, [](const BitTorrent::TrackerEndpointStatus &endpointEntry)
|
||||||
|
@ -426,16 +422,38 @@ void TrackersFilterWidget::handleTrackerStatusesUpdated(const BitTorrent::Torren
|
||||||
else if ((trackerEntryStatus.state == BitTorrent::TrackerEndpointState::NotWorking)
|
else if ((trackerEntryStatus.state == BitTorrent::TrackerEndpointState::NotWorking)
|
||||||
|| (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::Unreachable))
|
|| (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::Unreachable))
|
||||||
{
|
{
|
||||||
|
// remove tracker from "tracker error" and "warning" categories
|
||||||
|
if (warningHashesIt != m_warnings.end())
|
||||||
|
warningHashesIt->remove(trackerEntryStatus.url);
|
||||||
|
if (trackerErrorHashesIt != m_trackerErrors.end())
|
||||||
|
trackerErrorHashesIt->remove(trackerEntryStatus.url);
|
||||||
|
|
||||||
if (errorHashesIt == m_errors.end())
|
if (errorHashesIt == m_errors.end())
|
||||||
errorHashesIt = m_errors.insert(id, {});
|
errorHashesIt = m_errors.insert(id, {});
|
||||||
errorHashesIt->insert(trackerEntryStatus.url);
|
errorHashesIt->insert(trackerEntryStatus.url);
|
||||||
}
|
}
|
||||||
else if (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::TrackerError)
|
else if (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::TrackerError)
|
||||||
{
|
{
|
||||||
|
// remove tracker from "error" and "warning" categories
|
||||||
|
if (warningHashesIt != m_warnings.end())
|
||||||
|
warningHashesIt->remove(trackerEntryStatus.url);
|
||||||
|
if (errorHashesIt != m_errors.end())
|
||||||
|
errorHashesIt->remove(trackerEntryStatus.url);
|
||||||
|
|
||||||
if (trackerErrorHashesIt == m_trackerErrors.end())
|
if (trackerErrorHashesIt == m_trackerErrors.end())
|
||||||
trackerErrorHashesIt = m_trackerErrors.insert(id, {});
|
trackerErrorHashesIt = m_trackerErrors.insert(id, {});
|
||||||
trackerErrorHashesIt->insert(trackerEntryStatus.url);
|
trackerErrorHashesIt->insert(trackerEntryStatus.url);
|
||||||
}
|
}
|
||||||
|
else if (trackerEntryStatus.state == BitTorrent::TrackerEndpointState::NotContacted)
|
||||||
|
{
|
||||||
|
// remove tracker from "error", "tracker error" and "warning" categories
|
||||||
|
if (warningHashesIt != m_warnings.end())
|
||||||
|
warningHashesIt->remove(trackerEntryStatus.url);
|
||||||
|
if (errorHashesIt != m_errors.end())
|
||||||
|
errorHashesIt->remove(trackerEntryStatus.url);
|
||||||
|
if (trackerErrorHashesIt != m_trackerErrors.end())
|
||||||
|
trackerErrorHashesIt->remove(trackerEntryStatus.url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((errorHashesIt != m_errors.end()) && errorHashesIt->isEmpty())
|
if ((errorHashesIt != m_errors.end()) && errorHashesIt->isEmpty())
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue