Compare commits

...

93 Commits

Author SHA1 Message Date
sledgehammer999
f7e6b96493 Bump to 4.3.4 2021-03-23 23:14:15 +02:00
sledgehammer999
88bf6f11c7 Update Changelog 2021-03-23 23:04:12 +02:00
sledgehammer999
90e2236990 Sync translations from Transifex and run lupdate 2021-03-23 23:02:22 +02:00
treysis
6ad7cadc4b Fix bad IPv6 address format for outgoingInterfaces
Fixes https://github.com/qbittorrent/qBittorrent/issues/12892#issuecomment-792292336
2021-03-23 22:26:59 +02:00
brvphoenix
0499111156 WebUI: Avoid decoding strings repeatedly
Fix #14553
2021-03-23 22:26:57 +02:00
Vladimir Golovnev (Glassez)
ae44e59c9a Wrap "resume data" in LoadTorrentParams 2021-03-23 22:26:55 +02:00
Vladimir Golovnev (Glassez)
1de52f9bcf Drop deprecated code 2021-03-23 22:26:54 +02:00
Vladimir Golovnev (Glassez)
448e55031e Save resume data when torrent has done checking 2021-03-23 22:26:52 +02:00
Vladimir Golovnev (Glassez)
3b748178c2 Use QRegularExpression instead of deprecated QRegExp
Now it follows closely the definition of wildcard for glob patterns.
The backslash (\) character is not an escape char in this context.
In order to match one of the special characters, place it in square
brackets (for example, [?]).
2021-03-23 22:26:50 +02:00
thalieht
a4a54ce712 Allow >100 days in WebUI function "friendlyDuration"
Because it's not only used for ETA.
2021-03-23 22:26:48 +02:00
thalieht
d19b524d2d Fix incorrect seeding time string in WebUI General tab 2021-03-23 22:26:47 +02:00
thalieht
1e2bf50e66 Add seeding time to the active time column in WebUI
Closes #14526
2021-03-23 22:26:46 +02:00
Vladimir Golovnev (Glassez)
e7f3409053 Don't use deprecated operators 2021-03-23 22:26:44 +02:00
Vladimir Golovnev (Glassez)
9758633eeb Use correct return statement 2021-03-23 22:26:42 +02:00
Vladimir Golovnev (Glassez)
3def5e40c4 Include missing header 2021-03-23 22:26:41 +02:00
Vladimir Golovnev (Glassez)
ca923ed02c Include QDesktopWidget header only when needed 2021-03-23 22:26:38 +02:00
Chocobo1
e4c3bad93a Fix library requirements 2021-03-23 22:26:37 +02:00
Chocobo1
3b52c5ce97 Draw progress bar in disabled style 2021-03-23 22:26:36 +02:00
Vladimir Golovnev (Glassez)
44b94803a4 Improve "save resume data" handling 2021-03-23 22:26:35 +02:00
jagannatharjun
5d4644c4fc Remember sub sort column of transfer list 2021-03-23 22:26:34 +02:00
Chocobo1
a2ef115c66 Simplify progress bar painting 2021-03-23 22:26:31 +02:00
Vladimir Golovnev (Glassez)
1356f200b8 Don't use deprecated QTextCodec 2021-03-23 22:26:30 +02:00
Vladimir Golovnev
3c68896b1d CI: Don't compile on Ubuntu 18.04 2021-03-23 22:26:29 +02:00
Vladimir Golovnev (Glassez)
265da50791 Don't use deprecated features 2021-03-23 22:26:28 +02:00
Vladimir Golovnev (Glassez)
4037143f4e Raise minimum supported Qt version to 5.12 2021-03-23 22:26:26 +02:00
Chocobo1
8cae8ad5c5 Replace parameters in one step
This would avoid the unwanted effect of replacing parameter coming from
another parameter.
2021-03-23 22:26:22 +02:00
Vladimir Golovnev (Glassez)
50bd845682 Initialize torrent status from add torrent params 2021-03-23 22:26:20 +02:00
Vladimir Golovnev (Glassez)
ed5aa07526 CI: Disable libtorrent2 deprecated functions on Travis 2021-03-23 22:26:19 +02:00
Vladimir Golovnev (Glassez)
437b51b3a5 Improve "info hash" handling
Define "torrent ID" concept, which is either a SHA1 hash for torrents of version 1,
or a SHA256 hash (truncated to SHA1 hash length) for torrents of version 2.
Add support for native libtorrent2 info hashes.
2021-03-23 22:26:18 +02:00
Vladimir Golovnev (Glassez)
c2ccc9dfa4 Properly show tracker status for "paused" torrents 2021-03-23 22:26:16 +02:00
Vladimir Golovnev (Glassez)
b2c7d8211f Improve tracker entries handling 2021-03-23 22:26:14 +02:00
Vladimir Golovnev (Glassez)
726455ac3e Don't allow speed plot buffer to overflow 2021-03-23 22:26:13 +02:00
Vladimir Golovnev (Glassez)
ae2bb4efeb Accept "share limits" when adding torrent using WebAPI 2021-03-23 22:26:12 +02:00
Vladimir Golovnev (Glassez)
9971329121 Look for qbittorrent.pdb in installation directory
Pass application directory as PDB search path in SymInitialize.
Otherwise it searches in application working directory so when you
run qBittorrent with working directory other than its installation
one it can't find qbittorent.pdb file and produces broken stacktrace.
2021-03-23 22:26:10 +02:00
Chocobo1
d0ec1c4a86 Expose ToS setting from libtorrent
Closes #14420.
2021-03-23 22:26:08 +02:00
Chocobo1
9c55600d81 Add missing semicolon 2021-03-23 22:26:01 +02:00
Vladimir Golovnev (Glassez)
b45fb74e01 Define template for classes that represent SHA hashes 2021-03-23 21:05:41 +02:00
Vladimir Golovnev (Glassez)
f16c585a77 Drop implicit conversions between InfoHash and QString 2021-03-23 21:05:40 +02:00
Chocobo1
9c664d04ae Remove unused lambda capture 2021-03-23 21:05:38 +02:00
Chocobo1
3d0ca83474 Specify Qt version in TravisCI build script
In homebrew `qt` package is referring to Qt6 instead of Qt5.
2021-03-23 21:05:37 +02:00
Chocobo1
e713ffb064 Properly stop torrent creation if aborted
Closes #11346.
2021-03-23 21:05:37 +02:00
Chocobo1
cf1e61bcf5 Correctly draw the background of progress bar
Closes #12271.
2021-03-23 21:05:36 +02:00
Vladimir Golovnev (Glassez)
42b22d6645 CI: Use custom vcpkg libtorrent port 2021-03-23 21:05:35 +02:00
Vladimir Golovnev (Glassez)
2d607f8c1a Raise minimum libtorrent version to 1.2.12 2021-03-23 21:05:34 +02:00
jagannatharjun
69256905c2 Support sub-sorting in Transferlist 2021-03-23 21:05:32 +02:00
brvphoenix
305316b1fc WebUI: Properly decode strings 2021-03-23 21:05:32 +02:00
Chocobo1
27e222455b Improve detection of filename extension of audio/video files 2021-03-23 21:05:31 +02:00
Michał Kopeć
2b18318e0c Add an option to disable icons in menus 2021-03-23 21:05:30 +02:00
Chocobo1
49cadce253 Enable sponsor button on Github 2021-03-23 21:05:29 +02:00
Juraj Oršulić
f1b908b95b Systemd: wait for mounting of local filesystems 2021-03-23 21:05:28 +02:00
jagannatharjun
4acfcef8da Add a 3-Hour graph 2021-03-23 21:05:27 +02:00
jagannatharjun
69f2196a22 Make SpeedPlotView averager time aware
Previously SpeedPlotView assumed speed is updated per second but the
default value was 1500ms and that can be further changed by the
user, this caused a lot of duplicate data in the calculation of the
graph points. Now Averager averages based on the target duration, resolution
and also takes into account when actually data has arrived.

Also improved resolution of 6-hour graph, previously it was same as 12-hour graph
2021-03-23 21:05:26 +02:00
Chocobo1
b20a3c5b8e Use std::optional to return results 2021-03-23 21:05:25 +02:00
Chocobo1
2c5271b3b2 Fix potential out-of-bounds access 2021-03-23 21:05:24 +02:00
Si Yong Kim
7696895a88 Refactor apply button logics on options dialog 2021-03-23 21:05:23 +02:00
Si Yong Kim
c1ae5d2572 Add empty name error handling on new category dialog 2021-03-23 21:05:23 +02:00
Si Yong Kim
0e635c7fdd Add category button on AutomatedRSSDownloader on GUI
Closes #7629
2021-03-23 21:05:22 +02:00
Chocobo1
58345e5bbf Revise getter function for torrrent queue position
This addresses https://github.com/qbittorrent/qBittorrent/pull/14335#issuecomment-774667836

The WebAPI is not affected as a workaround is added.
2021-03-23 21:05:21 +02:00
Chocobo1
89382d4ec2 Apply "Hide infinity values" to ETA column 2021-03-23 21:05:20 +02:00
Chocobo1
372f5af36b Apply "Hide infinity values" to "Down/Up Limit" columns 2021-03-23 21:05:19 +02:00
Chocobo1
f38736729d Apply "Hide zero values" to "Time Active" column 2021-03-23 21:05:18 +02:00
Chocobo1
bf67ef21c6 Clean up coding style 2021-03-23 21:05:18 +02:00
Chocobo1
cfd40adcb5 Show proper string when torrent availability is not available 2021-03-23 21:05:16 +02:00
Vladimir Golovnev (Glassez)
8210f9841e Restart "missing files" torrents after changing location 2021-03-23 21:05:16 +02:00
Vladimir Golovnev (Glassez)
ae3d17ec01 Allow "missing files" torrents to save more resume data 2021-03-23 21:05:15 +02:00
Vladimir Golovnev (Glassez)
349e958be3 Allow change-case-only file renaming on Windows 2021-03-23 21:05:14 +02:00
Chocobo1
42acc75394 Use stable sorting in transfer list 2021-03-23 21:05:12 +02:00
Chocobo1
8b91dcedb0 Use built-in function for configuring file contents 2021-03-23 21:05:11 +02:00
dyumin
a454a0303d Treat errored torrents as finished 2021-03-23 21:05:11 +02:00
Chocobo1
789c6de2e8 Simplify CI script directives 2021-03-23 21:05:10 +02:00
Chocobo1
c2fb51159f Don't trigger Github Actions CI builds after editing a PR's opening post
After dropping "edited" keyword, it is the same as the default.
2021-03-23 21:05:09 +02:00
PriitUring
bfb0afe3cf NSIS: Add Estonian translation
This file was previously not translated.
PR #14331.
2021-03-23 21:05:08 +02:00
Chocobo1
26a2d4f24d Reuse existing code for sorting
This makes the behavior of sorting by TR_SEED_DATE consistent.
2021-03-23 21:05:08 +02:00
Si Yong Kim
f6e88c8c55 Add hyperlink to Transifex on translator list
Closes #12609
2021-03-23 21:05:07 +02:00
Si Yong Kim
51033c212a Remove Hungarian translator email 2021-03-23 21:05:06 +02:00
Chocobo1
16c858cf61 Prolong checking interval for program updates 2021-03-23 21:05:05 +02:00
Chocobo1
0496543fce Improve behavior when using ProgramUpdater class
This is mainly to avoid involving of `sender()` function.
2021-03-23 21:05:04 +02:00
Chocobo1
746e8a7be1 Revise version comparison 2021-03-23 21:05:04 +02:00
Chocobo1
6d301ccf55 Clean up coding style 2021-03-23 21:05:03 +02:00
jagannatharjun
d441b18da0 Disable expand on double click in TorrentContentTreeView
We hook our own actions on double click. Fixes #14269
2021-03-23 21:05:01 +02:00
Vladimir Golovnev (Glassez)
13023ba70a Bump WebAPI version 2021-03-23 21:05:01 +02:00
Alex
ecb7c02d4c Update Portuguese BR NSIS translation (#12376) 2021-03-23 21:05:00 +02:00
slrslr
fd1ac43157 Translating new phrases (#12318)
* Update Czech NSIS translation

Co-authored-by: slrslr <czautohits@gmail.com>
2021-03-23 21:04:59 +02:00
Chocobo1
c6d4a1f7d4 Enlarge "speed limit" icon slightly 2021-03-23 21:04:58 +02:00
Chocobo1
01110690da Don't let "program update" dialog steal focus
And also avoid creating an unnecessary event loop.
Closes #14250.
2021-03-23 21:04:57 +02:00
Chocobo1
c998c7d38d Disable translation of program name 2021-03-23 21:04:56 +02:00
an0n666
230f98da4a Validate HTTPS Tracker Certificate by default 2021-03-23 21:04:56 +02:00
xavier2k6
c86db0004f Change qBittorrent Updater window title 2021-03-23 21:04:54 +02:00
Christoph Rackwitz
e645514c8f Allow tab to escape the text box in "Edit trackers" dialog 2021-03-23 21:04:53 +02:00
Chocobo1
f3c9dbd512 Remove redundant variable declarations 2021-03-23 21:04:52 +02:00
Chocobo1
ef650293e3 Add ability to prioritize selected items by shown file order
Closes #2834.
2021-03-23 21:04:51 +02:00
Chocobo1
05e217537c Move menu actions out of .ui files
This is to move related code together.
2021-03-23 21:04:50 +02:00
Vladimir Golovnev (Glassez)
13cb3b5ca1 Drop extension from generated content folder name
Try to detect whether generated content folder name contains extension
and drop it to avoid possible conflicts between file/folder names.
2021-03-23 21:04:38 +02:00
257 changed files with 48578 additions and 54452 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
custom: "https://www.qbittorrent.org/donate.php"

View File

@@ -8,16 +8,13 @@ on:
push: push:
branches: [ master ] branches: [ master ]
pull_request: pull_request:
types: [edited, opened, reopened, synchronize]
branches: [ master ] branches: [ master ]
env: env:
# Qt: 5.15.1 VCPKG_COMMIT: e4ce66eecfd3e5cca5eac06c971921bf8e37cf88
# libtorrent: RC_1_2 HEAD, 1.2.11
VCPKG_COMMIT: 133051b793486ef14e67e9d1f48c9cfe64dc127e
VCPKG_DEST_MACOS: /Users/runner/qbt_tools/vcpkg VCPKG_DEST_MACOS: /Users/runner/qbt_tools/vcpkg
VCPKG_DEST_WIN: C:\qbt_tools\vcpkg VCPKG_DEST_WIN: C:\qbt_tools\vcpkg
LIBTORRENT_VERSION_TAG: v1.2.11 LIBTORRENT_VERSION_TAG: v1.2.12
jobs: jobs:
@@ -26,7 +23,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, ubuntu-18.04] os: [ubuntu-20.04]
qbt_gui: ["GUI=ON", "GUI=OFF"] qbt_gui: ["GUI=ON", "GUI=OFF"]
fail-fast: false fail-fast: false
@@ -125,10 +122,15 @@ jobs:
"qt5-svg:x64-windows-static-release", "qt5-svg:x64-windows-static-release",
"qt5-tools:x64-windows-static-release", "qt5-tools:x64-windows-static-release",
"qt5-winextras:x64-windows-static-release" "qt5-winextras:x64-windows-static-release"
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg.exe upgrade `
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
--overlay-ports=${{ github.workspace }}/vcpkg `
--no-dry-run
foreach($package in $packages) foreach($package in $packages)
{ {
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg.exe install $package ` ${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg.exe install $package `
--overlay-triplets=${{ github.workspace }}/triplets_overlay ` --overlay-triplets=${{ github.workspace }}/triplets_overlay `
--overlay-ports=${{ github.workspace }}/vcpkg `
--clean-after-build --clean-after-build
} }
@@ -199,13 +201,6 @@ jobs:
Add-Content ${{ github.workspace }}/triplets_overlay/x64-osx-release.cmake ` Add-Content ${{ github.workspace }}/triplets_overlay/x64-osx-release.cmake `
-Value "set(VCPKG_BUILD_TYPE release)","set(VCPKG_OSX_DEPLOYMENT_TARGET 10.15)" -Value "set(VCPKG_BUILD_TYPE release)","set(VCPKG_OSX_DEPLOYMENT_TARGET 10.15)"
# NOTE: Avoids a libtorrent ABI issue. See https://github.com/arvidn/libtorrent/issues/4965
- name: force AppleClang to compile libtorrent with C++17
run: |
(Get-Content -path ${{ env.RUNVCPKG_VCPKG_ROOT }}/ports/libtorrent/portfile.cmake).Replace( `
'${FEATURE_OPTIONS}', '${FEATURE_OPTIONS} -DCMAKE_CXX_STANDARD=17') `
| Set-Content -Path ${{ env.RUNVCPKG_VCPKG_ROOT }}/ports/libtorrent/portfile.cmake
- name: install dependencies via vcpkg - name: install dependencies via vcpkg
run: | run: |
$packages = ` $packages = `
@@ -215,10 +210,15 @@ jobs:
"qt5-svg:x64-osx-release", "qt5-svg:x64-osx-release",
"qt5-tools:x64-osx-release", "qt5-tools:x64-osx-release",
"qt5-macextras:x64-osx-release" "qt5-macextras:x64-osx-release"
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg upgrade `
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
--overlay-ports=${{ github.workspace }}/vcpkg `
--no-dry-run
foreach($package in $packages) foreach($package in $packages)
{ {
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg install $package ` ${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg install $package `
--overlay-triplets=${{ github.workspace }}/triplets_overlay ` --overlay-triplets=${{ github.workspace }}/triplets_overlay `
--overlay-ports=${{ github.workspace }}/vcpkg `
--clean-after-build --clean-after-build
} }

View File

@@ -1,11 +1,6 @@
name: GitHub Actions file health check name: GitHub Actions file health check
on: on: [pull_request, push]
push:
branches: [ '**' ]
pull_request:
types: [edited, opened, reopened, synchronize]
branches: [ '**' ]
jobs: jobs:
check_file_health: check_file_health:

View File

@@ -106,8 +106,8 @@ install:
brew update > /dev/null brew update > /dev/null
brew upgrade cmake brew upgrade cmake
brew install ccache colormake boost openssl qt zlib brew install ccache colormake boost openssl qt@5 zlib
brew link --force qt zlib brew link --force qt@5 zlib
if [ "$build_system" = "cmake" ]; then if [ "$build_system" = "cmake" ]; then
sudo ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs sudo ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs
@@ -125,7 +125,7 @@ install:
pushd "$HOME" pushd "$HOME"
git clone --single-branch --branch RC_1_2 https://github.com/arvidn/libtorrent.git git clone --single-branch --branch RC_1_2 https://github.com/arvidn/libtorrent.git
cd libtorrent cd libtorrent
git checkout tags/v1.2.11 git checkout tags/v1.2.12
cmake \ cmake \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
@@ -140,13 +140,13 @@ install:
pushd "$HOME" pushd "$HOME"
git clone --single-branch --branch RC_2_0 https://github.com/arvidn/libtorrent.git git clone --single-branch --branch RC_2_0 https://github.com/arvidn/libtorrent.git
cd libtorrent cd libtorrent
git checkout tags/v2.0.1 git checkout tags/v2.0.2
git submodule update --init --recursive git submodule update --init --recursive
cmake \ cmake \
-DCMAKE_BUILD_TYPE=Release \ -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_STANDARD=17 \ -DCMAKE_CXX_STANDARD=17 \
-Ddeprecated-functions=ON \ -Ddeprecated-functions=OFF \
-DOPENSSL_ROOT_DIR="$openssl_root_path" \ -DOPENSSL_ROOT_DIR="$openssl_root_path" \
./ ./
make make

View File

@@ -119,7 +119,7 @@ Translations authors:
- German: Niels Hoffmann (zentralmaschine@users.sourceforge.net) - German: Niels Hoffmann (zentralmaschine@users.sourceforge.net)
- Greek: Tsvetan Bankov (emerge_life@users.sourceforge.net), Stephanos Antaris (santaris@csd.auth.gr), sledgehammer999(hammered999@gmail.com) and Γιάννης Ανθυμίδης Evropi(Transifex) - Greek: Tsvetan Bankov (emerge_life@users.sourceforge.net), Stephanos Antaris (santaris@csd.auth.gr), sledgehammer999(hammered999@gmail.com) and Γιάννης Ανθυμίδης Evropi(Transifex)
- Hebrew: David Deutsch (d.deffo@gmail.com) - Hebrew: David Deutsch (d.deffo@gmail.com)
- Hungarian: Majoros Péter (majoros.j.p@t-online.hu) - Hungarian: Majoros Péter
- Italian: bovirus (bovirus@live.it) and Matteo Sechi (bu17714@gmail.com) - Italian: bovirus (bovirus@live.it) and Matteo Sechi (bu17714@gmail.com)
- Japanese: Masato Hashimoto (cabezon.hashimoto@gmail.com) - Japanese: Masato Hashimoto (cabezon.hashimoto@gmail.com)
- Korean: Jin Woo Sin (jin828sin@users.sourceforge.net) - Korean: Jin Woo Sin (jin828sin@users.sourceforge.net)

View File

@@ -12,9 +12,9 @@ project(qBittorrent
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
# version requirements - older vesions may work, but you are on your own # version requirements - older vesions may work, but you are on your own
set(minBoostVersion 1.65) set(minBoostVersion 1.65)
set(minQtVersion 5.9.5) set(minQtVersion 5.12)
set(minOpenSSLVersion 1.1.1) set(minOpenSSLVersion 1.1.1)
set(minLibtorrentVersion 1.2.11) set(minLibtorrentVersion 1.2.12)
set(minZlibVersion 1.2.11) set(minZlibVersion 1.2.11)
# features (some are platform-specific) # features (some are platform-specific)
@@ -61,5 +61,4 @@ else()
endif() endif()
# Generate version header # Generate version header
file(READ "src/base/version.h.in" versionHeader) configure_file("src/base/version.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/base/version.h" @ONLY)
file(WRITE "src/base/version.h" "${versionHeader}")

View File

@@ -1,3 +1,47 @@
Tue Mar 23 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.4
- FEATURE: Add ability to prioritize selected items by shown file order (Chocobo1)
- FEATURE: Allow tab to escape the text box in "Edit trackers" dialog (Christoph Rackwitz)
- FEATURE: Support sub-sorting in Transferlist (jagannatharjun)
- FEATURE: Expose ToS setting from libtorrent (Chocobo1)
- FEATURE: Improve tracker entries handling (glassez)
- BUGFIX: Drop extension from generated content folder name (glassez)
- BUGFIX: Change qBittorrent Updater window title (xavier2k6)
- BUGFIX: Validate HTTPS Tracker Certificate by default (an0n666)
- BUGFIX: Don't let "program update" dialog steal focus (Chocobo1)
- BUGFIX: Disable expand on double click in TorrentContentTreeView (jagannatharjun)
- BUGFIX: Add hyperlink to Transifex on translator list (Si Yong Kim)
- BUGFIX: Enlarge "speed limit" icon slightly (Chocobo1)
- BUGFIX: Don't prevent system sleep due to errored torrents (dyumin)
- BUGFIX: Use stable sorting in transfer list (Chocobo1)
- BUGFIX: Allow "missing files" torrents to save more resume data (glassez)
- BUGFIX: Restart "missing files" torrents after changing location (glassez)
- BUGFIX: Show proper string when torrent availability is not available (Chocobo1)
- BUGFIX: Apply "Hide zero/infinity values" to "Time Active", "Down/Up Limit" and ETA columns (Chocobo1)
- BUGFIX: Fix potential out-of-bounds access (Chocobo1)
- BUGFIX: Make SpeedPlotView averager time aware (jagannatharjun)
- BUGFIX: Add a 3-Hour graph (jagannatharjun)
- BUGFIX: Add an option to disable icons in menus (always disabled on MacOS) (Michał Kopeć)
- BUGFIX: Improve detection of filename extension of audio/video files (Chocobo1)
- BUGFIX: Various drawing improvements of progress bar (Chocobo1)
- BUGFIX: Properly stop torrent creation if aborted (Chocobo1)
- BUGFIX: Replace external program parameters in one step (Chocobo1)
- BUGFIX: Improve "save resume data" handling (glassez)
- BUGFIX: Fix bad IPv6 address format for outgoingInterfaces (treysis)
- WEBUI: Properly decode strings (brvphoenix)
- WEBUI: Accept "share limits" when adding torrent using WebAPI (glassez)
- WEBUI: Add seeding time to the active time column (thalieht)
- WEBUI: Fix incorrect seeding time string in General tab (thalieht)
- WEBUI: Allow >100 days in WebUI function "friendlyDuration" (thalieht)
- WEBUI: Avoid decoding strings repeatedly (brvphoenix)
- RSS: Add category button on AutomatedRSSDownloader on GUI (Si Yong Kim)
- WINDOWS: NSIS: Update Czech translation (slrslr)
- WINDOWS: NSIS: Update Portuguese BR translation (Alex)
- WINDOWS: NSIS: Add Estonian translation (PriitUring)
- WINDOWS: Allow change-case-only file renaming (glassez)
- LINUX: Systemd: wait for mounting of local filesystems (Juraj Oršulić)
- OTHER: Raise minimum libtorrent version to 1.2.12 (glassez)
- OTHER: Raise minimum Qt version to 5.12 (glassez)
Tue Jan 19 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.3 Tue Jan 19 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.3
- FEATURE: New languages: Azerbaijani, Estonian - FEATURE: New languages: Azerbaijani, Estonian
- BUGFIX: Unify global speed dialogs for normal/alternative speeds (thalieht) - BUGFIX: Unify global speed dialogs for normal/alternative speeds (thalieht)

View File

@@ -5,13 +5,13 @@ qBittorrent - A BitTorrent client in C++ / Qt
- Boost >= 1.65 - Boost >= 1.65
- libtorrent-rasterbar >= 1.2.11 (by Arvid Norberg) - libtorrent-rasterbar >= 1.2.12 (by Arvid Norberg)
* https://www.libtorrent.org/ * https://www.libtorrent.org/
* Be careful: another library (the one used by rTorrent) uses a similar name * Be careful: another library (the one used by rTorrent) uses a similar name
- OpenSSL >= 1.1.1 - OpenSSL >= 1.1.1
- Qt >= 5.9.5 - Qt >= 5.12
- zlib >= 1.2.11 - zlib >= 1.2.11

80
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh #! /bin/sh
# Guess values for system-dependent variables and create Makefiles. # Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.70 for qbittorrent v4.3.3. # Generated by GNU Autoconf 2.70 for qbittorrent v4.3.4.
# #
# Report bugs to <bugs.qbittorrent.org>. # Report bugs to <bugs.qbittorrent.org>.
# #
@@ -610,8 +610,8 @@ MAKEFLAGS=
# Identity of this package. # Identity of this package.
PACKAGE_NAME='qbittorrent' PACKAGE_NAME='qbittorrent'
PACKAGE_TARNAME='qbittorrent' PACKAGE_TARNAME='qbittorrent'
PACKAGE_VERSION='v4.3.3' PACKAGE_VERSION='v4.3.4'
PACKAGE_STRING='qbittorrent v4.3.3' PACKAGE_STRING='qbittorrent v4.3.4'
PACKAGE_BUGREPORT='bugs.qbittorrent.org' PACKAGE_BUGREPORT='bugs.qbittorrent.org'
PACKAGE_URL='https://www.qbittorrent.org/' PACKAGE_URL='https://www.qbittorrent.org/'
@@ -1330,7 +1330,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing. # Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh. # This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF cat <<_ACEOF
\`configure' configures qbittorrent v4.3.3 to adapt to many kinds of systems. \`configure' configures qbittorrent v4.3.4 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]... Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1401,7 +1401,7 @@ fi
if test -n "$ac_init_help"; then if test -n "$ac_init_help"; then
case $ac_init_help in case $ac_init_help in
short | recursive ) echo "Configuration of qbittorrent v4.3.3:";; short | recursive ) echo "Configuration of qbittorrent v4.3.4:";;
esac esac
cat <<\_ACEOF cat <<\_ACEOF
@@ -1455,7 +1455,7 @@ Some influential environment variables:
directories to add to pkg-config's search path directories to add to pkg-config's search path
PKG_CONFIG_LIBDIR PKG_CONFIG_LIBDIR
path overriding pkg-config's built-in search path path overriding pkg-config's built-in search path
QT_QMAKE value of host_bins for Qt5Core >= 5.9.5, overriding pkg-config QT_QMAKE value of host_bins for Qt5Core >= 5.12, overriding pkg-config
Qt5Svg_CFLAGS Qt5Svg_CFLAGS
C compiler flags for Qt5Svg, overriding pkg-config C compiler flags for Qt5Svg, overriding pkg-config
Qt5Svg_LIBS linker flags for Qt5Svg, overriding pkg-config Qt5Svg_LIBS linker flags for Qt5Svg, overriding pkg-config
@@ -1538,7 +1538,7 @@ fi
test -n "$ac_init_help" && exit $ac_status test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then if $ac_init_version; then
cat <<\_ACEOF cat <<\_ACEOF
qbittorrent configure v4.3.3 qbittorrent configure v4.3.4
generated by GNU Autoconf 2.70 generated by GNU Autoconf 2.70
Copyright (C) 2020 Free Software Foundation, Inc. Copyright (C) 2020 Free Software Foundation, Inc.
@@ -1700,7 +1700,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake. running configure, to aid debugging if configure makes a mistake.
It was created by qbittorrent $as_me v4.3.3, which was It was created by qbittorrent $as_me v4.3.4, which was
generated by GNU Autoconf 2.70. Invocation command line was generated by GNU Autoconf 2.70. Invocation command line was
$ $0$ac_configure_args_raw $ $0$ac_configure_args_raw
@@ -4848,7 +4848,7 @@ fi
# Define the identity of the package. # Define the identity of the package.
PACKAGE='qbittorrent' PACKAGE='qbittorrent'
VERSION='v4.3.3' VERSION='v4.3.4'
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -5525,8 +5525,8 @@ printf "%s\n" "$enable_webui" >&6; }
esac esac
if test -n "$PKG_CONFIG" && \ if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.9.5\""; } >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.12\""; } >&5
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.9.5") 2>&5 ($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.12") 2>&5
ac_status=$? ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then test $ac_status = 0; }; then
@@ -5535,12 +5535,12 @@ if test -n "$QT_QMAKE"; then
pkg_cv_QT_QMAKE="$QT_QMAKE" pkg_cv_QT_QMAKE="$QT_QMAKE"
elif test -n "$PKG_CONFIG"; then elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \ if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.9.5\""; } >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.12\""; } >&5
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.9.5") 2>&5 ($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.12") 2>&5
ac_status=$? ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then test $ac_status = 0; }; then
pkg_cv_QT_QMAKE=`$PKG_CONFIG --variable="host_bins" "Qt5Core >= 5.9.5" 2>/dev/null` pkg_cv_QT_QMAKE=`$PKG_CONFIG --variable="host_bins" "Qt5Core >= 5.12" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes test "x$?" != "x0" && pkg_failed=yes
else else
pkg_failed=yes pkg_failed=yes
@@ -5570,8 +5570,8 @@ fi
fi fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5 qmake >= 5.9.5" >&5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5 qmake >= 5.12" >&5
printf %s "checking for Qt5 qmake >= 5.9.5... " >&6; } printf %s "checking for Qt5 qmake >= 5.12... " >&6; }
if test "x$QT_QMAKE" != "x" if test "x$QT_QMAKE" != "x"
then : then :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $QT_QMAKE" >&5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $QT_QMAKE" >&5
@@ -5598,12 +5598,12 @@ if test -n "$Qt5Svg_CFLAGS"; then
pkg_cv_Qt5Svg_CFLAGS="$Qt5Svg_CFLAGS" pkg_cv_Qt5Svg_CFLAGS="$Qt5Svg_CFLAGS"
elif test -n "$PKG_CONFIG"; then elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \ if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.5.1\""; } >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.12\""; } >&5
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.5.1") 2>&5 ($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.12") 2>&5
ac_status=$? ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then test $ac_status = 0; }; then
pkg_cv_Qt5Svg_CFLAGS=`$PKG_CONFIG --cflags "Qt5Svg >= 5.5.1" 2>/dev/null` pkg_cv_Qt5Svg_CFLAGS=`$PKG_CONFIG --cflags "Qt5Svg >= 5.12" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes test "x$?" != "x0" && pkg_failed=yes
else else
pkg_failed=yes pkg_failed=yes
@@ -5615,12 +5615,12 @@ if test -n "$Qt5Svg_LIBS"; then
pkg_cv_Qt5Svg_LIBS="$Qt5Svg_LIBS" pkg_cv_Qt5Svg_LIBS="$Qt5Svg_LIBS"
elif test -n "$PKG_CONFIG"; then elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \ if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.5.1\""; } >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.12\""; } >&5
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.5.1") 2>&5 ($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.12") 2>&5
ac_status=$? ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then test $ac_status = 0; }; then
pkg_cv_Qt5Svg_LIBS=`$PKG_CONFIG --libs "Qt5Svg >= 5.5.1" 2>/dev/null` pkg_cv_Qt5Svg_LIBS=`$PKG_CONFIG --libs "Qt5Svg >= 5.12" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes test "x$?" != "x0" && pkg_failed=yes
else else
pkg_failed=yes pkg_failed=yes
@@ -5641,14 +5641,14 @@ else
_pkg_short_errors_supported=no _pkg_short_errors_supported=no
fi fi
if test $_pkg_short_errors_supported = yes; then if test $_pkg_short_errors_supported = yes; then
Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "Qt5Svg >= 5.5.1" 2>&1` Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "Qt5Svg >= 5.12" 2>&1`
else else
Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "Qt5Svg >= 5.5.1" 2>&1` Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "Qt5Svg >= 5.12" 2>&1`
fi fi
# Put the nasty error message in config.log where it belongs # Put the nasty error message in config.log where it belongs
echo "$Qt5Svg_PKG_ERRORS" >&5 echo "$Qt5Svg_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (Qt5Svg >= 5.5.1) were not met: as_fn_error $? "Package requirements (Qt5Svg >= 5.12) were not met:
$Qt5Svg_PKG_ERRORS $Qt5Svg_PKG_ERRORS
@@ -5688,11 +5688,11 @@ case "x$enable_qt_dbus" in #(
"xyes") : "xyes") :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
printf "%s\n" "yes" >&6; } printf "%s\n" "yes" >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5DBus >= 5.9.5" >&5 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5DBus >= 5.12" >&5
printf %s "checking for Qt5DBus >= 5.9.5... " >&6; } printf %s "checking for Qt5DBus >= 5.12... " >&6; }
if test -n "$PKG_CONFIG" && \ if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5DBus >= 5.9.5\""; } >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5DBus >= 5.12\""; } >&5
($PKG_CONFIG --exists --print-errors "Qt5DBus >= 5.9.5") 2>&5 ($PKG_CONFIG --exists --print-errors "Qt5DBus >= 5.12") 2>&5
ac_status=$? ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then test $ac_status = 0; }; then
@@ -6354,12 +6354,12 @@ if test -n "$libtorrent_CFLAGS"; then
pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS" pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS"
elif test -n "$PKG_CONFIG"; then elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \ if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.11\""; } >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.12\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.11") 2>&5 ($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.12") 2>&5
ac_status=$? ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then test $ac_status = 0; }; then
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.11" 2>/dev/null` pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.12" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes test "x$?" != "x0" && pkg_failed=yes
else else
pkg_failed=yes pkg_failed=yes
@@ -6371,12 +6371,12 @@ if test -n "$libtorrent_LIBS"; then
pkg_cv_libtorrent_LIBS="$libtorrent_LIBS" pkg_cv_libtorrent_LIBS="$libtorrent_LIBS"
elif test -n "$PKG_CONFIG"; then elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \ if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.11\""; } >&5 { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.12\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.11") 2>&5 ($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.12") 2>&5
ac_status=$? ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then test $ac_status = 0; }; then
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.11" 2>/dev/null` pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.12" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes test "x$?" != "x0" && pkg_failed=yes
else else
pkg_failed=yes pkg_failed=yes
@@ -6397,14 +6397,14 @@ else
_pkg_short_errors_supported=no _pkg_short_errors_supported=no
fi fi
if test $_pkg_short_errors_supported = yes; then if test $_pkg_short_errors_supported = yes; then
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.11" 2>&1` libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.12" 2>&1`
else else
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.11" 2>&1` libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.12" 2>&1`
fi fi
# Put the nasty error message in config.log where it belongs # Put the nasty error message in config.log where it belongs
echo "$libtorrent_PKG_ERRORS" >&5 echo "$libtorrent_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.11) were not met: as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.12) were not met:
$libtorrent_PKG_ERRORS $libtorrent_PKG_ERRORS
@@ -7401,7 +7401,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their # report actual input values of CONFIG_FILES etc. instead of their
# values after options handling. # values after options handling.
ac_log=" ac_log="
This file was extended by qbittorrent $as_me v4.3.3, which was This file was extended by qbittorrent $as_me v4.3.4, which was
generated by GNU Autoconf 2.70. Invocation command line was generated by GNU Autoconf 2.70. Invocation command line was
CONFIG_FILES = $CONFIG_FILES CONFIG_FILES = $CONFIG_FILES
@@ -7461,7 +7461,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped' ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\ ac_cs_version="\\
qbittorrent config.status v4.3.3 qbittorrent config.status v4.3.4
configured by $0, generated by GNU Autoconf 2.70, configured by $0, generated by GNU Autoconf 2.70,
with options \\"\$ac_cs_config\\" with options \\"\$ac_cs_config\\"

View File

@@ -1,4 +1,4 @@
AC_INIT([qbittorrent], [v4.3.3], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/]) AC_INIT([qbittorrent], [v4.3.4], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])
: ${CFLAGS=""} : ${CFLAGS=""}
@@ -141,7 +141,7 @@ AS_IF([test "x$QT_QMAKE" = "x"],
[AC_MSG_ERROR([Could not find qmake]) [AC_MSG_ERROR([Could not find qmake])
]) ])
AS_IF([test "x$enable_gui" = "xyes"], AS_IF([test "x$enable_gui" = "xyes"],
[PKG_CHECK_MODULES(Qt5Svg, [Qt5Svg >= 5.5.1]) [PKG_CHECK_MODULES(Qt5Svg, [Qt5Svg >= 5.12])
]) ])
AC_MSG_CHECKING([whether QtDBus should be enabled]) AC_MSG_CHECKING([whether QtDBus should be enabled])
AS_CASE(["x$enable_qt_dbus"], AS_CASE(["x$enable_qt_dbus"],
@@ -180,7 +180,7 @@ AC_MSG_NOTICE([Boost.System LIB: "$BOOST_SYSTEM_LIB"])
LIBS="$BOOST_SYSTEM_LIB $LIBS" LIBS="$BOOST_SYSTEM_LIB $LIBS"
PKG_CHECK_MODULES(libtorrent, PKG_CHECK_MODULES(libtorrent,
[libtorrent-rasterbar >= 1.2.11], [libtorrent-rasterbar >= 1.2.12],
[CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS" [CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS"
LIBS="$libtorrent_LIBS $LIBS"]) LIBS="$libtorrent_LIBS $LIBS"])

2
dist/mac/Info.plist vendored
View File

@@ -55,7 +55,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>4.3.3</string> <string>4.3.4</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>@EXECUTABLE@</string> <string>@EXECUTABLE@</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

View File

@@ -74,6 +74,6 @@
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url> <url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
<content_rating type="oars-1.1"/> <content_rating type="oars-1.1"/>
<releases> <releases>
<release version="4.3.3" date="2021-01-19"/> <release version="4.3.4" date="2021-03-23"/>
</releases> </releases>
</component> </component>

View File

@@ -118,6 +118,9 @@ Name[lt]=qBittorrent
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
GenericName[mk]=BitTorrent клиент GenericName[mk]=BitTorrent клиент
Name[mk]=qBittorrent Name[mk]=qBittorrent
Comment[en_AU]=Download and share files over BitTorrent
GenericName[en_AU]=BitTorrent client
Name[en_AU]=qBittorrent
Comment[nb]=Last ned og del filer over BitTorrent Comment[nb]=Last ned og del filer over BitTorrent
GenericName[nb]=BitTorrent-klient GenericName[nb]=BitTorrent-klient
Name[nb]=qBittorrent Name[nb]=qBittorrent
@@ -162,9 +165,9 @@ Name[uz@Latn]=qBittorrent
Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent
GenericName[ltg]=BitTorrent klients GenericName[ltg]=BitTorrent klients
Name[ltg]=qBittorrent Name[ltg]=qBittorrent
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन Comment[hi_IN]=बिटटौरेंट द्वारा फाइल डाउनलोड व सहभाजन
GenericName[hi_IN]=BitTorrent साधन GenericName[hi_IN]=बिटटौरेंट साधन
Name[hi_IN]=qBittorrent Name[hi_IN]=क्यूबिटटौरेंट
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
GenericName[tr]=BitTorrent istemcisi GenericName[tr]=BitTorrent istemcisi
Name[tr]=qBittorrent Name[tr]=qBittorrent
@@ -207,9 +210,9 @@ Name[ne_NP]=qBittorrent
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్ GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
Name[te]=క్యు బిట్ టొరెంట్ Name[te]=క్యు బిట్ టొరెంట్
Comment[en_AU]=Download and share files over BitTorrent Comment[pt_PT]=Transferir e partilhar ficheiros por BitTorrent
GenericName[en_AU]=BitTorrent client GenericName[pt_PT]=Cliente BitTorrent
Name[en_AU]=qBittorrent Name[pt_PT]=qBittorrent
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ด้วยบิททอเร้น Comment[th]=ดาวน์โหลดและแชร์ไฟล์ด้วยบิททอเร้น
GenericName[th]=โปรแกรมบิททอเร้น GenericName[th]=โปรแกรมบิททอเร้น
Name[th]=qBittorrent Name[th]=qBittorrent

View File

@@ -2,7 +2,7 @@
Description=qBittorrent-nox service for user %I Description=qBittorrent-nox service for user %I
Documentation=man:qbittorrent-nox(1) Documentation=man:qbittorrent-nox(1)
Wants=network-online.target Wants=network-online.target
After=network-online.target nss-lookup.target After=local-fs.target network-online.target nss-lookup.target
[Service] [Service]
Type=simple Type=simple

View File

@@ -7,7 +7,7 @@ LangString inst_dekstop ${LANG_CZECH} "Vytvořit zástupce na ploše"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut" ;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_CZECH} "Vytvořit zástupce v nabídce Start" LangString inst_startmenu ${LANG_CZECH} "Vytvořit zástupce v nabídce Start"
;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_CZECH} "Spusťte aplikaci qBittorrent ve Windows" LangString inst_startup ${LANG_CZECH} "Spustit qBittorrent při startu 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_CZECH} "Otevírat .torrent soubory pomocí qBittorrent" LangString inst_torrent ${LANG_CZECH} "Otevírat .torrent soubory pomocí qBittorrent"
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent" ;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
@@ -15,9 +15,9 @@ LangString inst_magnet ${LANG_CZECH} "Otevírat odkazy Magnet pomocí qBittorren
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule" ;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_CZECH} "Vytvořit pravidlo ve Windows Firewall" LangString inst_firewall ${LANG_CZECH} "Vytvořit pravidlo ve Windows Firewall"
;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_CZECH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)" LangString inst_pathlimit ${LANG_CZECH} "Vypnout MAX_PATH limit 260 znaků pro cesty (vyžaduje Windows 10 1607 a novější)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule" ;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_CZECH} "Vytváření pravidla ve Windows Firewall" LangString inst_firewallinfo ${LANG_CZECH} "Přidávání pravidla do Windows Firewall"
;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_CZECH} "qBittorrent je spuštěn. Před instalací aplikaci ukončete, prosím." LangString inst_warning ${LANG_CZECH} "qBittorrent je spuštěn. Před instalací aplikaci ukončete, prosím."
;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."

View File

@@ -1,59 +1,59 @@
;Installer strings ;Installer strings
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)" ;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
LangString inst_qbt_req ${LANG_ESTONIAN} "qBittorrent (required)" LangString inst_qbt_req ${LANG_ESTONIAN} "qBittorrent (vajalik)"
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut" ;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
LangString inst_dekstop ${LANG_ESTONIAN} "Create Desktop Shortcut" LangString inst_dekstop ${LANG_ESTONIAN} "Loo Töölaua Otsetee"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut" ;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_ESTONIAN} "Create Start Menu Shortcut" LangString inst_startmenu ${LANG_ESTONIAN} "Loo Start Menüü Otsetee"
;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_ESTONIAN} "Start qBittorrent on Windows start up" LangString inst_startup ${LANG_ESTONIAN} "Käivita qBittorrent Windowsi käivitamisel"
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent" ;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_ESTONIAN} "Open .torrent files with qBittorrent" LangString inst_torrent ${LANG_ESTONIAN} "Ava .torrent failid qBittorrentiga"
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent" ;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_ESTONIAN} "Open magnet links with qBittorrent" LangString inst_magnet ${LANG_ESTONIAN} "Ava magnet lingid qBittorrentiga"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule" ;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_ESTONIAN} "Add Windows Firewall rule" LangString inst_firewall ${LANG_ESTONIAN} "Lisa reegel Windowsi Tulemüüri"
;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_ESTONIAN} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)" LangString inst_pathlimit ${LANG_ESTONIAN} "Keela Windowsi kaustade pikkuse limiit (260 karakterit MAX_PATH limiit, vajalik Windows 10 1607 või uuemat)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule" ;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_ESTONIAN} "Adding Windows Firewall rule" LangString inst_firewallinfo ${LANG_ESTONIAN} "Lisatakse Windowsi Tulemüüri reegel"
;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_ESTONIAN} "qBittorrent is running. Please close the application before installing." LangString inst_warning ${LANG_ESTONIAN} "qBittorrent töötab. Palun sulge see programm, enne installi."
;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_ESTONIAN} "Current version will be uninstalled. User settings and torrents will remain intact." LangString inst_uninstall_question ${LANG_ESTONIAN} "Praegune versioon uninstallitakse. Kasutaja sätted ja torrenti failid jäetakse alles."
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version." ;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_ESTONIAN} "Uninstalling previous version." LangString inst_unist ${LANG_ESTONIAN} "Uninstallitakse eelmist versiooni."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent." ;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
LangString launch_qbt ${LANG_ESTONIAN} "Launch qBittorrent." LangString launch_qbt ${LANG_ESTONIAN} "Käivita 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_ESTONIAN} "This installer works only in 64-bit Windows versions." LangString inst_requires_64bit ${LANG_ESTONIAN} "See installer töötab ainult 64-bit Windowsi versioonides."
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7." ;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_ESTONIAN} "This qBittorrent version requires at least Windows 7." LangString inst_requires_win7 ${LANG_ESTONIAN} "Selle qBittorrenti versiooni jaoks on vajalik vähemalt Windows 7."
;------------------------------------ ;------------------------------------
;Uninstaller strings ;Uninstaller strings
;LangString remove_files ${LANG_ENGLISH} "Remove files" ;LangString remove_files ${LANG_ENGLISH} "Remove files"
LangString remove_files ${LANG_ESTONIAN} "Remove files" LangString remove_files ${LANG_ESTONIAN} "Eemalda failid"
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts" ;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
LangString remove_shortcuts ${LANG_ESTONIAN} "Remove shortcuts" LangString remove_shortcuts ${LANG_ESTONIAN} "Eemalda otseteed"
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations" ;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
LangString remove_associations ${LANG_ESTONIAN} "Remove file associations" LangString remove_associations ${LANG_ESTONIAN} "Eemalda failide seotus"
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys" ;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
LangString remove_registry ${LANG_ESTONIAN} "Remove registry keys" LangString remove_registry ${LANG_ESTONIAN} "Eemalda registri võtmed"
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files" ;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
LangString remove_conf ${LANG_ESTONIAN} "Remove configuration files" LangString remove_conf ${LANG_ESTONIAN} "Eemalda seadistuste failid"
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule" ;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
LangString remove_firewall ${LANG_ESTONIAN} "Remove Windows Firewall rule" LangString remove_firewall ${LANG_ESTONIAN} "Eemalda Windowsi Tulemüüri reegel"
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule" ;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
LangString remove_firewallinfo ${LANG_ESTONIAN} "Removing Windows Firewall rule" LangString remove_firewallinfo ${LANG_ESTONIAN} "Eemaldatakse Windowsi Tulemüüri reegel"
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data" ;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_ESTONIAN} "Remove torrents and cached data" LangString remove_cache ${LANG_ESTONIAN} "Eemalda torrentid ja ajutised andmed"
;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_ESTONIAN} "qBittorrent is running. Please close the application before uninstalling." LangString uninst_warning ${LANG_ESTONIAN} "qBittorrent töötab. Palun sulge programm, enne uninstallimist."
;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_ESTONIAN} "Not removing .torrent association. It is associated with:" LangString uninst_tor_warn ${LANG_ESTONIAN} "Ei eemaldata .torrent seotust. Mis on seotud:"
;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:"
LangString uninst_mag_warn ${LANG_ESTONIAN} "Not removing magnet association. It is associated with:" LangString uninst_mag_warn ${LANG_ESTONIAN} "Ei eemaldata magnetitega seotust. Mis on seotud:"

View File

@@ -15,7 +15,7 @@ LangString inst_magnet ${LANG_PORTUGUESEBR} "Abrir links magnéticos com qBittor
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule" ;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_PORTUGUESEBR} "Adicionar regra no firewall do Windows" LangString inst_firewall ${LANG_PORTUGUESEBR} "Adicionar regra no firewall do Windows"
;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_PORTUGUESEBR} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)" LangString inst_pathlimit ${LANG_PORTUGUESEBR} "Desativar limite de caracteres em caminhos do Windows (limite MAX_PATH de 260 caracteres, requer Windows 10 1067 ou superior)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule" ;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_PORTUGUESEBR} "Adicionando regra no firewall do Windows" LangString inst_firewallinfo ${LANG_PORTUGUESEBR} "Adicionando regra no firewall do Windows"
;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."
@@ -27,9 +27,9 @@ LangString inst_unist ${LANG_PORTUGUESEBR} "Desinstalando versão anterior."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent." ;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
LangString launch_qbt ${LANG_PORTUGUESEBR} "Executar qBittorrent." LangString launch_qbt ${LANG_PORTUGUESEBR} "Executar 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_PORTUGUESEBR} "This installer works only in 64-bit Windows versions." LangString inst_requires_64bit ${LANG_PORTUGUESEBR} "Este instalador apenas funciona em versões 64-bit do Windows."
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7." ;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_PORTUGUESEBR} "This qBittorrent version requires at least Windows 7." LangString inst_requires_win7 ${LANG_PORTUGUESEBR} "Esta versão do qBittorrent requer no mínimo o Windows 7."
;------------------------------------ ;------------------------------------
@@ -42,13 +42,13 @@ LangString remove_shortcuts ${LANG_PORTUGUESEBR} "Remover atalhos"
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations" ;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
LangString remove_associations ${LANG_PORTUGUESEBR} "Remover associação de arquivos" LangString remove_associations ${LANG_PORTUGUESEBR} "Remover associação de arquivos"
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys" ;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
LangString remove_registry ${LANG_PORTUGUESEBR} "Remover registros" LangString remove_registry ${LANG_PORTUGUESEBR} "Remover chaves do registro"
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files" ;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
LangString remove_conf ${LANG_PORTUGUESEBR} "Remover configurações" LangString remove_conf ${LANG_PORTUGUESEBR} "Remover configurações"
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule" ;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
LangString remove_firewall ${LANG_PORTUGUESEBR} "Remover regras do firewall do Windows" LangString remove_firewall ${LANG_PORTUGUESEBR} "Remover regra do Firewall do Windows"
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule" ;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
LangString remove_firewallinfo ${LANG_PORTUGUESEBR} "Removendo regras do firewall do Windows" LangString remove_firewallinfo ${LANG_PORTUGUESEBR} "Removendo regra do Firewall do Windows"
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data" ;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_PORTUGUESEBR} "Remover torrents e dados em cache" LangString remove_cache ${LANG_PORTUGUESEBR} "Remover torrents e dados em cache"
;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."

View File

@@ -28,7 +28,7 @@ XPStyle on
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path !define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
; Program specific ; Program specific
!define PROG_VERSION "4.3.3" !define PROG_VERSION "4.3.4"
!define MUI_FINISHPAGE_RUN !define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun !define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun

View File

@@ -5,9 +5,9 @@
# Sets the QT_QMAKE variable to the path of Qt5 qmake if found. # Sets the QT_QMAKE variable to the path of Qt5 qmake if found.
# -------------------------------------- # --------------------------------------
AC_DEFUN([FIND_QT5], AC_DEFUN([FIND_QT5],
[PKG_CHECK_EXISTS([Qt5Core >= 5.9.5], [PKG_CHECK_EXISTS([Qt5Core >= 5.12],
[PKG_CHECK_VAR(QT_QMAKE, [PKG_CHECK_VAR(QT_QMAKE,
[Qt5Core >= 5.9.5], [Qt5Core >= 5.12],
[host_bins]) [host_bins])
]) ])
@@ -18,7 +18,7 @@ AS_IF([test -f "$QT_QMAKE/qmake"],
[QT_QMAKE=""]) [QT_QMAKE=""])
]) ])
AC_MSG_CHECKING([for Qt5 qmake >= 5.9.5]) AC_MSG_CHECKING([for Qt5 qmake >= 5.12])
AS_IF([test "x$QT_QMAKE" != "x"], AS_IF([test "x$QT_QMAKE" != "x"],
[AC_MSG_RESULT([$QT_QMAKE])], [AC_MSG_RESULT([$QT_QMAKE])],
[AC_MSG_RESULT([not found])] [AC_MSG_RESULT([not found])]
@@ -29,8 +29,8 @@ AS_IF([test "x$QT_QMAKE" != "x"],
# Sets the HAVE_QTDBUS variable to true or false. # Sets the HAVE_QTDBUS variable to true or false.
# -------------------------------------- # --------------------------------------
AC_DEFUN([FIND_QTDBUS], AC_DEFUN([FIND_QTDBUS],
[AC_MSG_CHECKING([for Qt5DBus >= 5.9.5]) [AC_MSG_CHECKING([for Qt5DBus >= 5.12])
PKG_CHECK_EXISTS([Qt5DBus >= 5.9.5], PKG_CHECK_EXISTS([Qt5DBus >= 5.12],
[AC_MSG_RESULT([found]) [AC_MSG_RESULT([found])
HAVE_QTDBUS=[true]], HAVE_QTDBUS=[true]],
[AC_MSG_RESULT([not found]) [AC_MSG_RESULT([not found])

View File

@@ -4,22 +4,14 @@
# Based on https://gist.github.com/giraldeau/546ba5512a74dfe9d8ea0862d66db412 # Based on https://gist.github.com/giraldeau/546ba5512a74dfe9d8ea0862d66db412
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts") file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts")
set_source_files_properties(${QBT_TS_FILES} PROPERTIES OUTPUT_LOCATION "${qBittorrent_BINARY_DIR}/src/lang") set_source_files_properties(${QBT_TS_FILES} PROPERTIES OUTPUT_LOCATION "${qBittorrent_BINARY_DIR}/src/lang")
if (Qt5_VERSION VERSION_LESS 5.12) qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES} OPTIONS -silent)
qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES})
else()
qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES} OPTIONS -silent)
endif()
configure_file("${qBittorrent_SOURCE_DIR}/src/lang/lang.qrc" "${qBittorrent_BINARY_DIR}/src/lang/lang.qrc" COPYONLY) configure_file("${qBittorrent_SOURCE_DIR}/src/lang/lang.qrc" "${qBittorrent_BINARY_DIR}/src/lang/lang.qrc" COPYONLY)
if (WEBUI) if (WEBUI)
file(GLOB QBT_WEBUI_TS_FILES "${qBittorrent_SOURCE_DIR}/src/webui/www/translations/*.ts") file(GLOB QBT_WEBUI_TS_FILES "${qBittorrent_SOURCE_DIR}/src/webui/www/translations/*.ts")
set_source_files_properties(${QBT_WEBUI_TS_FILES} set_source_files_properties(${QBT_WEBUI_TS_FILES}
PROPERTIES OUTPUT_LOCATION "${qBittorrent_BINARY_DIR}/src/webui/www/translations") PROPERTIES OUTPUT_LOCATION "${qBittorrent_BINARY_DIR}/src/webui/www/translations")
if (Qt5_VERSION VERSION_LESS 5.12) qt5_add_translation(QBT_WEBUI_QM_FILES ${QBT_WEBUI_TS_FILES} OPTIONS -silent)
qt5_add_translation(QBT_WEBUI_QM_FILES ${QBT_WEBUI_TS_FILES})
else()
qt5_add_translation(QBT_WEBUI_QM_FILES ${QBT_WEBUI_TS_FILES} OPTIONS -silent)
endif()
configure_file("${qBittorrent_SOURCE_DIR}/src/webui/www/translations/webui_translations.qrc" configure_file("${qBittorrent_SOURCE_DIR}/src/webui/www/translations/webui_translations.qrc"
"${qBittorrent_BINARY_DIR}/src/webui/www/translations/webui_translations.qrc" COPYONLY) "${qBittorrent_BINARY_DIR}/src/webui/www/translations/webui_translations.qrc" COPYONLY)
endif() endif()

View File

@@ -313,14 +313,6 @@ void Application::processMessage(const QString &message)
void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
{ {
QString program = Preferences::instance()->getAutoRunProgram().trimmed();
program.replace("%N", torrent->name());
program.replace("%L", torrent->category());
QStringList tags = torrent->tags().values();
std::sort(tags.begin(), tags.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
program.replace("%G", tags.join(','));
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
const auto chopPathSep = [](const QString &str) -> QString const auto chopPathSep = [](const QString &str) -> QString
{ {
@@ -328,21 +320,74 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
return str.mid(0, (str.length() -1)); return str.mid(0, (str.length() -1));
return str; return str;
}; };
program.replace("%F", chopPathSep(Utils::Fs::toNativePath(torrent->contentPath())));
program.replace("%R", chopPathSep(Utils::Fs::toNativePath(torrent->rootPath())));
program.replace("%D", chopPathSep(Utils::Fs::toNativePath(torrent->savePath())));
#else
program.replace("%F", Utils::Fs::toNativePath(torrent->contentPath()));
program.replace("%R", Utils::Fs::toNativePath(torrent->rootPath()));
program.replace("%D", Utils::Fs::toNativePath(torrent->savePath()));
#endif #endif
program.replace("%C", QString::number(torrent->filesCount()));
program.replace("%Z", QString::number(torrent->totalSize()));
program.replace("%T", torrent->currentTracker());
program.replace("%I", torrent->hash());
Logger *logger = Logger::instance(); QString program = Preferences::instance()->getAutoRunProgram().trimmed();
logger->addMessage(tr("Torrent: %1, running external program, command: %2").arg(torrent->name(), program));
for (int i = (program.length() - 2); i >= 0; --i)
{
if (program[i] != QLatin1Char('%'))
continue;
const ushort specifier = program[i + 1].unicode();
switch (specifier)
{
case u'C':
program.replace(i, 2, QString::number(torrent->filesCount()));
break;
case u'D':
#if defined(Q_OS_WIN)
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->savePath())));
#else
program.replace(i, 2, Utils::Fs::toNativePath(torrent->savePath()));
#endif
break;
case u'F':
#if defined(Q_OS_WIN)
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->contentPath())));
#else
program.replace(i, 2, Utils::Fs::toNativePath(torrent->contentPath()));
#endif
break;
case u'G':
{
QStringList tags = torrent->tags().values();
std::sort(tags.begin(), tags.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
program.replace(i, 2, tags.join(','));
}
break;
case u'I':
program.replace(i, 2, torrent->id().toString());
break;
case u'L':
program.replace(i, 2, torrent->category());
break;
case u'N':
program.replace(i, 2, torrent->name());
break;
case u'R':
#if defined(Q_OS_WIN)
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->rootPath())));
#else
program.replace(i, 2, Utils::Fs::toNativePath(torrent->rootPath()));
#endif
break;
case u'T':
program.replace(i, 2, torrent->currentTracker());
break;
case u'Z':
program.replace(i, 2, QString::number(torrent->totalSize()));
break;
default:
// do nothing
break;
}
// decrement `i` to avoid unwanted replacement, example pattern: "%%N"
--i;
}
LogMsg(tr("Torrent: %1, running external program, command: %2").arg(torrent->name(), program));
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
auto programWchar = std::make_unique<wchar_t[]>(program.length() + 1); auto programWchar = std::make_unique<wchar_t[]>(program.length() + 1);
@@ -358,7 +403,6 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
for (int i = 1; i < argCount; ++i) for (int i = 1; i < argCount; ++i)
argList += QString::fromWCharArray(args[i]); argList += QString::fromWCharArray(args[i]);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QProcess proc; QProcess proc;
proc.setProgram(QString::fromWCharArray(args[0])); proc.setProgram(QString::fromWCharArray(args[0]));
proc.setArguments(argList); proc.setArguments(argList);
@@ -384,9 +428,6 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
args->startupInfo->hStdError = nullptr; args->startupInfo->hStdError = nullptr;
}); });
proc.startDetached(); proc.startDetached();
#else
QProcess::startDetached(QString::fromWCharArray(args[0]), argList);
#endif // QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
#else // Q_OS_WIN #else // Q_OS_WIN
// Cannot give users shell environment by default, as doing so could // Cannot give users shell environment by default, as doing so could
// enable command injection via torrent name and other arguments // enable command injection via torrent name and other arguments

View File

@@ -235,7 +235,7 @@ int main(int argc, char *argv[])
// 3. https://bugreports.qt.io/browse/QTBUG-46015 // 3. https://bugreports.qt.io/browse/QTBUG-46015
qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1)); qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) && !defined(DISABLE_GUI) #if !defined(DISABLE_GUI)
// this is the default in Qt6 // this is the default in Qt6
app->setAttribute(Qt::AA_DisableWindowContextHelpButton); app->setAttribute(Qt::AA_DisableWindowContextHelpButton);
#endif #endif
@@ -251,6 +251,9 @@ int main(int argc, char *argv[])
// On OS X the standard is to not show icons in the menus // On OS X the standard is to not show icons in the menus
app->setAttribute(Qt::AA_DontShowIconsInMenus); app->setAttribute(Qt::AA_DontShowIconsInMenus);
#else
if (!Preferences::instance()->iconsInMenusEnabled())
app->setAttribute(Qt::AA_DontShowIconsInMenus);
#endif #endif
if (!firstTimeUser) if (!firstTimeUser)
@@ -378,11 +381,7 @@ void showSplashScreen()
const QString version = QBT_VERSION; const QString version = QBT_VERSION;
painter.setPen(QPen(Qt::white)); painter.setPen(QPen(Qt::white));
painter.setFont(QFont("Arial", 22, QFont::Black)); painter.setFont(QFont("Arial", 22, QFont::Black));
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
painter.drawText(224 - painter.fontMetrics().horizontalAdvance(version), 270, version); painter.drawText(224 - painter.fontMetrics().horizontalAdvance(version), 270, version);
#else
painter.drawText(224 - painter.fontMetrics().width(version), 270, version);
#endif
QSplashScreen *splash = new QSplashScreen(splashImg); QSplashScreen *splash = new QSplashScreen(splashImg);
splash->show(); splash->show();
QTimer::singleShot(1500, splash, &QObject::deleteLater); QTimer::singleShot(1500, splash, &QObject::deleteLater);

View File

@@ -79,6 +79,7 @@
#include <QDir> #include <QDir>
#include <QLocalServer> #include <QLocalServer>
#include <QLocalSocket> #include <QLocalSocket>
#include <QRegularExpression>
#include "base/utils/misc.h" #include "base/utils/misc.h"
@@ -108,7 +109,7 @@ QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
#endif #endif
prefix = id.section(QLatin1Char('/'), -1); prefix = id.section(QLatin1Char('/'), -1);
} }
prefix.remove(QRegExp("[^a-zA-Z]")); prefix.remove(QRegularExpression("[^a-zA-Z]"));
prefix.truncate(6); prefix.truncate(6);
QByteArray idc = id.toUtf8(); QByteArray idc = id.toUtf8();

View File

@@ -24,6 +24,7 @@
#include <dbghelp.h> #include <dbghelp.h>
#include <stdio.h> #include <stdio.h>
#include <QCoreApplication>
#include <QDir> #include <QDir>
#include <QTextStream> #include <QTextStream>
#ifdef __MINGW32__ #ifdef __MINGW32__
@@ -256,7 +257,7 @@ const QString straceWin::getBacktrace()
HANDLE hProcess = GetCurrentProcess(); HANDLE hProcess = GetCurrentProcess();
HANDLE hThread = GetCurrentThread(); HANDLE hThread = GetCurrentThread();
SymInitialize(hProcess, NULL, TRUE); SymInitializeW(hProcess, QCoreApplication::applicationDirPath().toStdWString().c_str(), TRUE);
DWORD64 dwDisplacement; DWORD64 dwDisplacement;

View File

@@ -32,6 +32,7 @@ add_library(qbt_base STATIC
bittorrent/torrentinfo.h bittorrent/torrentinfo.h
bittorrent/tracker.h bittorrent/tracker.h
bittorrent/trackerentry.h bittorrent/trackerentry.h
digest32.h
exceptions.h exceptions.h
filesystemwatcher.h filesystemwatcher.h
global.h global.h

View File

@@ -55,13 +55,8 @@ AsyncFileStorage::~AsyncFileStorage()
void AsyncFileStorage::store(const QString &fileName, const QByteArray &data) void AsyncFileStorage::store(const QString &fileName, const QByteArray &data)
{ {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(this, [this, data, fileName]() { store_impl(fileName, data); } QMetaObject::invokeMethod(this, [this, data, fileName]() { store_impl(fileName, data); }
, Qt::QueuedConnection); , Qt::QueuedConnection);
#else
QMetaObject::invokeMethod(this, "store_impl", Qt::QueuedConnection
, Q_ARG(QString, fileName), Q_ARG(QByteArray, data));
#endif
} }
QDir AsyncFileStorage::storageDir() const QDir AsyncFileStorage::storageDir() const

View File

@@ -31,6 +31,7 @@ HEADERS += \
$$PWD/bittorrent/torrentinfo.h \ $$PWD/bittorrent/torrentinfo.h \
$$PWD/bittorrent/tracker.h \ $$PWD/bittorrent/tracker.h \
$$PWD/bittorrent/trackerentry.h \ $$PWD/bittorrent/trackerentry.h \
$$PWD/digest32.h \
$$PWD/exceptions.h \ $$PWD/exceptions.h \
$$PWD/filesystemwatcher.h \ $$PWD/filesystemwatcher.h \
$$PWD/global.h \ $$PWD/global.h \

View File

@@ -78,8 +78,7 @@ void BitTorrent::AbstractFileStorage::renameFile(const QString &oldPath, const Q
if ((renamingFileIndex < 0) && areSameFileNames(path, oldFilePath)) if ((renamingFileIndex < 0) && areSameFileNames(path, oldFilePath))
renamingFileIndex = i; renamingFileIndex = i;
else if (areSameFileNames(path, newFilePath))
if (areSameFileNames(path, newFilePath))
throw RuntimeError {tr("The file already exists: '%1'.").arg(newFilePath)}; throw RuntimeError {tr("The file already exists: '%1'.").arg(newFilePath)};
} }
@@ -124,8 +123,7 @@ void BitTorrent::AbstractFileStorage::renameFolder(const QString &oldPath, const
if (path.startsWith(oldFolderPath, CASE_SENSITIVITY)) if (path.startsWith(oldFolderPath, CASE_SENSITIVITY))
renamingFileIndexes.append(i); renamingFileIndexes.append(i);
else if (path.startsWith(newFolderPath, CASE_SENSITIVITY))
if (path.startsWith(newFolderPath, CASE_SENSITIVITY))
throw RuntimeError {tr("The folder already exists: '%1'.").arg(newFolderPath)}; throw RuntimeError {tr("The folder already exists: '%1'.").arg(newFolderPath)};
} }

View File

@@ -33,7 +33,7 @@
#include "base/bittorrent/common.h" #include "base/bittorrent/common.h"
#include "base/bittorrent/infohash.h" #include "base/bittorrent/infohash.h"
void FileSearcher::search(const BitTorrent::InfoHash &id, const QStringList &originalFileNames void FileSearcher::search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames
, const QString &completeSavePath, const QString &incompleteSavePath) , const QString &completeSavePath, const QString &incompleteSavePath)
{ {
const auto findInDir = [](const QString &dirPath, QStringList &fileNames) -> bool const auto findInDir = [](const QString &dirPath, QStringList &fileNames) -> bool

View File

@@ -32,7 +32,7 @@
namespace BitTorrent namespace BitTorrent
{ {
class InfoHash; class TorrentID;
} }
class FileSearcher final : public QObject class FileSearcher final : public QObject
@@ -44,9 +44,9 @@ public:
FileSearcher() = default; FileSearcher() = default;
public slots: public slots:
void search(const BitTorrent::InfoHash &id, const QStringList &originalFileNames void search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames
, const QString &completeSavePath, const QString &incompleteSavePath); , const QString &completeSavePath, const QString &incompleteSavePath);
signals: signals:
void searchFinished(const BitTorrent::InfoHash &id, const QString &savePath, const QStringList &fileNames); void searchFinished(const BitTorrent::TorrentID &id, const QString &savePath, const QStringList &fileNames);
}; };

View File

@@ -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, 2021 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
@@ -28,68 +28,54 @@
#include "infohash.h" #include "infohash.h"
#include <QByteArray> const int TorrentIDTypeId = qRegisterMetaType<BitTorrent::TorrentID>();
#include <QHash>
using namespace BitTorrent; BitTorrent::InfoHash::InfoHash(const WrappedType &nativeHash)
: m_valid {true}
const int InfoHashTypeId = qRegisterMetaType<InfoHash>(); , m_nativeHash {nativeHash}
InfoHash::InfoHash(const lt::sha1_hash &nativeHash)
: m_valid(true)
, m_nativeHash(nativeHash)
{ {
const QByteArray raw = QByteArray::fromRawData(nativeHash.data(), length());
m_hashString = QString::fromLatin1(raw.toHex());
} }
InfoHash::InfoHash(const QString &hashString) bool BitTorrent::InfoHash::isValid() const
: m_valid(false)
{
if (hashString.size() != (length() * 2))
return;
const QByteArray raw = QByteArray::fromHex(hashString.toLatin1());
if (raw.size() != length()) // QByteArray::fromHex() will skip over invalid characters
return;
m_valid = true;
m_hashString = hashString;
m_nativeHash.assign(raw.constData());
}
bool InfoHash::isValid() const
{ {
return m_valid; return m_valid;
} }
InfoHash::operator lt::sha1_hash() const BitTorrent::TorrentID BitTorrent::InfoHash::toTorrentID() const
{
#if (LIBTORRENT_VERSION_NUM >= 20000)
return m_nativeHash.get_best();
#else
return {m_nativeHash};
#endif
}
BitTorrent::InfoHash::operator WrappedType() const
{ {
return m_nativeHash; return m_nativeHash;
} }
InfoHash::operator QString() const BitTorrent::TorrentID BitTorrent::TorrentID::fromString(const QString &hashString)
{ {
return m_hashString; return {BaseType::fromString(hashString)};
} }
bool BitTorrent::operator==(const InfoHash &left, const InfoHash &right) BitTorrent::TorrentID BitTorrent::TorrentID::fromInfoHash(const BitTorrent::InfoHash &infoHash)
{ {
return (static_cast<lt::sha1_hash>(left) return infoHash.toTorrentID();
== static_cast<lt::sha1_hash>(right));
} }
bool BitTorrent::operator!=(const InfoHash &left, const InfoHash &right) uint BitTorrent::qHash(const BitTorrent::TorrentID &key, const uint seed)
{
return ::qHash(std::hash<TorrentID::UnderlyingType>()(key), seed);
}
bool BitTorrent::operator==(const BitTorrent::InfoHash &left, const BitTorrent::InfoHash &right)
{
return (static_cast<InfoHash::WrappedType>(left) == static_cast<InfoHash::WrappedType>(right));
}
bool BitTorrent::operator!=(const BitTorrent::InfoHash &left, const BitTorrent::InfoHash &right)
{ {
return !(left == right); return !(left == right);
} }
bool BitTorrent::operator<(const InfoHash &left, const InfoHash &right)
{
return static_cast<lt::sha1_hash>(left) < static_cast<lt::sha1_hash>(right);
}
uint BitTorrent::qHash(const InfoHash &key, const uint seed)
{
return ::qHash((std::hash<lt::sha1_hash> {})(key), seed);
}

View File

@@ -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, 2021 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
@@ -28,41 +28,60 @@
#pragma once #pragma once
#include <libtorrent/sha1_hash.hpp> #include <libtorrent/version.hpp>
#if (LIBTORRENT_VERSION_NUM >= 20000)
#include <libtorrent/info_hash.hpp>
#endif
#include <QHash>
#include <QMetaType> #include <QMetaType>
#include <QString>
#include "base/digest32.h"
using SHA1Hash = Digest32<160>;
using SHA256Hash = Digest32<256>;
namespace BitTorrent namespace BitTorrent
{ {
class InfoHash;
class TorrentID : public Digest32<160>
{
public:
using BaseType = Digest32<160>;
using BaseType::BaseType;
static TorrentID fromString(const QString &hashString);
static TorrentID fromInfoHash(const InfoHash &infoHash);
};
class InfoHash class InfoHash
{ {
public: public:
InfoHash() = default; #if (LIBTORRENT_VERSION_NUM >= 20000)
InfoHash(const lt::sha1_hash &nativeHash); using WrappedType = lt::info_hash_t;
InfoHash(const QString &hashString); #else
InfoHash(const InfoHash &other) = default; using WrappedType = lt::sha1_hash;
#endif
static constexpr int length() InfoHash() = default;
{ InfoHash(const InfoHash &other) = default;
return lt::sha1_hash::size(); InfoHash(const WrappedType &nativeHash);
}
bool isValid() const; bool isValid() const;
TorrentID toTorrentID() const;
operator lt::sha1_hash() const; operator WrappedType() const;
operator QString() const;
private: private:
bool m_valid = false; bool m_valid = false;
lt::sha1_hash m_nativeHash; WrappedType m_nativeHash;
QString m_hashString;
}; };
uint qHash(const TorrentID &key, uint seed);
bool operator==(const InfoHash &left, const InfoHash &right); bool operator==(const InfoHash &left, const InfoHash &right);
bool operator!=(const InfoHash &left, const InfoHash &right); bool operator!=(const InfoHash &left, const InfoHash &right);
bool operator<(const InfoHash &left, const InfoHash &right);
uint qHash(const InfoHash &key, uint seed);
} }
Q_DECLARE_METATYPE(BitTorrent::InfoHash) Q_DECLARE_METATYPE(BitTorrent::TorrentID)

View File

@@ -40,15 +40,15 @@
namespace namespace
{ {
bool isBitTorrentInfoHash(const QString &string) bool isSHA1Hash(const QString &string)
{ {
// There are 2 representations for BitTorrent info hash: // There are 2 representations for BitTorrent info hash:
// 1. 40 chars hex-encoded string // 1. 40 chars hex-encoded string
// == 20 (SHA-1 length in bytes) * 2 (each byte maps to 2 hex characters) // == 20 (SHA-1 length in bytes) * 2 (each byte maps to 2 hex characters)
// 2. 32 chars Base32 encoded string // 2. 32 chars Base32 encoded string
// == 20 (SHA-1 length in bytes) * 1.6 (the efficiency of Base32 encoding) // == 20 (SHA-1 length in bytes) * 1.6 (the efficiency of Base32 encoding)
const int SHA1_HEX_SIZE = BitTorrent::InfoHash::length() * 2; const int SHA1_HEX_SIZE = SHA1Hash::length() * 2;
const int SHA1_BASE32_SIZE = BitTorrent::InfoHash::length() * 1.6; const int SHA1_BASE32_SIZE = SHA1Hash::length() * 1.6;
return ((((string.size() == SHA1_HEX_SIZE)) return ((((string.size() == SHA1_HEX_SIZE))
&& !string.contains(QRegularExpression(QLatin1String("[^0-9A-Fa-f]")))) && !string.contains(QRegularExpression(QLatin1String("[^0-9A-Fa-f]"))))
@@ -65,7 +65,7 @@ MagnetUri::MagnetUri(const QString &source)
{ {
if (source.isEmpty()) return; if (source.isEmpty()) return;
if (isBitTorrentInfoHash(source)) if (isSHA1Hash(source))
m_url = QLatin1String("magnet:?xt=urn:btih:") + source; m_url = QLatin1String("magnet:?xt=urn:btih:") + source;
lt::error_code ec; lt::error_code ec;
@@ -73,12 +73,18 @@ MagnetUri::MagnetUri(const QString &source)
if (ec) return; if (ec) return;
m_valid = true; m_valid = true;
m_hash = m_addTorrentParams.info_hash;
#if (LIBTORRENT_VERSION_NUM >= 20000)
m_infoHash = m_addTorrentParams.info_hashes;
#else
m_infoHash = m_addTorrentParams.info_hash;
#endif
m_name = QString::fromStdString(m_addTorrentParams.name); m_name = QString::fromStdString(m_addTorrentParams.name);
m_trackers.reserve(m_addTorrentParams.trackers.size()); m_trackers.reserve(m_addTorrentParams.trackers.size());
for (const std::string &tracker : m_addTorrentParams.trackers) for (const std::string &tracker : m_addTorrentParams.trackers)
m_trackers.append(lt::announce_entry {tracker}); m_trackers.append({QString::fromStdString(tracker)});
m_urlSeeds.reserve(m_addTorrentParams.url_seeds.size()); m_urlSeeds.reserve(m_addTorrentParams.url_seeds.size());
for (const std::string &urlSeed : m_addTorrentParams.url_seeds) for (const std::string &urlSeed : m_addTorrentParams.url_seeds)
@@ -90,9 +96,9 @@ bool MagnetUri::isValid() const
return m_valid; return m_valid;
} }
InfoHash MagnetUri::hash() const InfoHash MagnetUri::infoHash() const
{ {
return m_hash; return m_infoHash;
} }
QString MagnetUri::name() const QString MagnetUri::name() const

View File

@@ -46,7 +46,7 @@ namespace BitTorrent
explicit MagnetUri(const QString &source = {}); explicit MagnetUri(const QString &source = {});
bool isValid() const; bool isValid() const;
InfoHash hash() const; InfoHash infoHash() const;
QString name() const; QString name() const;
QVector<TrackerEntry> trackers() const; QVector<TrackerEntry> trackers() const;
QVector<QUrl> urlSeeds() const; QVector<QUrl> urlSeeds() const;
@@ -57,7 +57,7 @@ namespace BitTorrent
private: private:
bool m_valid; bool m_valid;
QString m_url; QString m_url;
InfoHash m_hash; InfoHash m_infoHash;
QString m_name; QString m_name;
QVector<TrackerEntry> m_trackers; QVector<TrackerEntry> m_trackers;
QVector<QUrl> m_urlSeeds; QVector<QUrl> m_urlSeeds;

View File

@@ -44,6 +44,7 @@
#include <libtorrent/alert_types.hpp> #include <libtorrent/alert_types.hpp>
#include <libtorrent/bdecode.hpp> #include <libtorrent/bdecode.hpp>
#include <libtorrent/bencode.hpp> #include <libtorrent/bencode.hpp>
#include <libtorrent/entry.hpp>
#include <libtorrent/error_code.hpp> #include <libtorrent/error_code.hpp>
#include <libtorrent/extensions/smart_ban.hpp> #include <libtorrent/extensions/smart_ban.hpp>
#include <libtorrent/extensions/ut_metadata.hpp> #include <libtorrent/extensions/ut_metadata.hpp>
@@ -55,6 +56,7 @@
#include <libtorrent/session_stats.hpp> #include <libtorrent/session_stats.hpp>
#include <libtorrent/session_status.hpp> #include <libtorrent/session_status.hpp>
#include <libtorrent/torrent_info.hpp> #include <libtorrent/torrent_info.hpp>
#include <libtorrent/write_resume_data.hpp>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
@@ -303,6 +305,17 @@ namespace
}; };
} }
using ListType = lt::entry::list_type;
ListType setToEntryList(const QSet<QString> &input)
{
ListType entryList;
entryList.reserve(input.size());
for (const QString &setValue : input)
entryList.emplace_back(setValue.toStdString());
return entryList;
}
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QString convertIfaceNameToGuid(const QString &name) QString convertIfaceNameToGuid(const QString &name)
{ {
@@ -372,6 +385,7 @@ Session::Session(QObject *parent)
, m_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0) , m_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0)
, m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0) , m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
, m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY("UPnPLeaseDuration"), 0) , m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY("UPnPLeaseDuration"), 0)
, m_peerToS(BITTORRENT_SESSION_KEY("PeerToS"), 0x20)
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), false) , m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), false)
, m_includeOverheadInLimits(BITTORRENT_SESSION_KEY("IncludeOverheadInLimits"), false) , m_includeOverheadInLimits(BITTORRENT_SESSION_KEY("IncludeOverheadInLimits"), false)
, m_announceIP(BITTORRENT_SESSION_KEY("AnnounceIP")) , m_announceIP(BITTORRENT_SESSION_KEY("AnnounceIP"))
@@ -388,7 +402,7 @@ Session::Session(QObject *parent)
, clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional)) , clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
, m_IDNSupportEnabled(BITTORRENT_SESSION_KEY("IDNSupportEnabled"), false) , m_IDNSupportEnabled(BITTORRENT_SESSION_KEY("IDNSupportEnabled"), false)
, m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY("MultiConnectionsPerIp"), false) , m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY("MultiConnectionsPerIp"), false)
, m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY("ValidateHTTPSTrackerCertificate"), false) , m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY("ValidateHTTPSTrackerCertificate"), true)
, m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY("BlockPeersOnPrivilegedPorts"), false) , m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY("BlockPeersOnPrivilegedPorts"), false)
, m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false) , m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false)
, m_additionalTrackers(BITTORRENT_SESSION_KEY("AdditionalTrackers")) , m_additionalTrackers(BITTORRENT_SESSION_KEY("AdditionalTrackers"))
@@ -1110,11 +1124,7 @@ void Session::initializeNativeSession()
m_nativeSession->set_alert_notify([this]() m_nativeSession->set_alert_notify([this]()
{ {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(this, &Session::readAlerts, Qt::QueuedConnection); QMetaObject::invokeMethod(this, &Session::readAlerts, Qt::QueuedConnection);
#else
QMetaObject::invokeMethod(this, "readAlerts", Qt::QueuedConnection);
#endif
}); });
// Enabling plugins // Enabling plugins
@@ -1335,9 +1345,10 @@ void Session::loadLTSettings(lt::settings_pack &settingsPack)
// Outgoing ports // Outgoing ports
settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin()); settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
settingsPack.set_int(lt::settings_pack::num_outgoing_ports, outgoingPortsMax() - outgoingPortsMin() + 1); settingsPack.set_int(lt::settings_pack::num_outgoing_ports, outgoingPortsMax() - outgoingPortsMin() + 1);
// UPnP lease duration
settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration()); settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
// Type of service
settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
// Include overhead in transfer limits // Include overhead in transfer limits
settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits()); settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
// IP address to announce to trackers // IP address to announce to trackers
@@ -1387,15 +1398,11 @@ void Session::loadLTSettings(lt::settings_pack &settingsPack)
break; break;
} }
#ifdef HAS_IDN_SUPPORT
settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled()); settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
#endif
settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled()); settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
#ifdef HAS_HTTPS_TRACKER_VALIDATION
settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate()); settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
#endif
settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts()); settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
@@ -1450,12 +1457,14 @@ void Session::configureNetworkInterfaces(lt::settings_pack &settingsPack)
const QHostAddress addr {ip}; const QHostAddress addr {ip};
if (!addr.isNull()) if (!addr.isNull())
{ {
const QString ip = ((addr.protocol() == QAbstractSocket::IPv6Protocol) const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
? ('[' + Utils::Net::canonicalIPv6Addr(addr).toString() + ']') const QString ip = isIPv6
: addr.toString()); ? Utils::Net::canonicalIPv6Addr(addr).toString()
endpoints << (ip + portString); : addr.toString();
if ((ip != "0.0.0.0") && (ip != "[::]")) endpoints << ((isIPv6 ? ('[' + ip + ']') : ip) + portString);
if ((ip != QLatin1String("0.0.0.0")) && (ip != QLatin1String("::")))
outgoingInterfaces << ip; outgoingInterfaces << ip;
} }
else else
@@ -1608,7 +1617,7 @@ void Session::populateAdditionalTrackers()
{ {
tracker = tracker.trimmed(); tracker = tracker.trimmed();
if (!tracker.isEmpty()) if (!tracker.isEmpty())
m_additionalTrackerList << tracker.toString(); m_additionalTrackerList.append({tracker.toString()});
} }
} }
@@ -1618,7 +1627,7 @@ void Session::processShareLimits()
// We shouldn't iterate over `m_torrents` in the loop below // We shouldn't iterate over `m_torrents` in the loop below
// since `deleteTorrent()` modifies it indirectly // since `deleteTorrent()` modifies it indirectly
const QHash<InfoHash, TorrentImpl *> torrents {m_torrents}; const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
for (TorrentImpl *const torrent : torrents) for (TorrentImpl *const torrent : torrents)
{ {
if (torrent->isSeed() && !torrent->isForced()) if (torrent->isSeed() && !torrent->isForced())
@@ -1640,12 +1649,12 @@ void Session::processShareLimits()
if (m_maxRatioAction == Remove) if (m_maxRatioAction == Remove)
{ {
LogMsg(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name())); LogMsg(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name()));
deleteTorrent(torrent->hash()); deleteTorrent(torrent->id());
} }
else if (m_maxRatioAction == DeleteFiles) else if (m_maxRatioAction == DeleteFiles)
{ {
LogMsg(tr("'%1' reached the maximum ratio you set. Removed torrent and its files.").arg(torrent->name())); LogMsg(tr("'%1' reached the maximum ratio you set. Removed torrent and its files.").arg(torrent->name()));
deleteTorrent(torrent->hash(), DeleteTorrentAndFiles); deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
} }
else if ((m_maxRatioAction == Pause) && !torrent->isPaused()) else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
{ {
@@ -1679,12 +1688,12 @@ void Session::processShareLimits()
if (m_maxRatioAction == Remove) if (m_maxRatioAction == Remove)
{ {
LogMsg(tr("'%1' reached the maximum seeding time you set. Removed.").arg(torrent->name())); LogMsg(tr("'%1' reached the maximum seeding time you set. Removed.").arg(torrent->name()));
deleteTorrent(torrent->hash()); deleteTorrent(torrent->id());
} }
else if (m_maxRatioAction == DeleteFiles) else if (m_maxRatioAction == DeleteFiles)
{ {
LogMsg(tr("'%1' reached the maximum seeding time you set. Removed torrent and its files.").arg(torrent->name())); LogMsg(tr("'%1' reached the maximum seeding time you set. Removed torrent and its files.").arg(torrent->name()));
deleteTorrent(torrent->hash(), DeleteTorrentAndFiles); deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
} }
else if ((m_maxRatioAction == Pause) && !torrent->isPaused()) else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
{ {
@@ -1721,7 +1730,7 @@ void Session::handleDownloadFinished(const Net::DownloadResult &result)
} }
} }
void Session::fileSearchFinished(const InfoHash &id, const QString &savePath, const QStringList &fileNames) void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames)
{ {
TorrentImpl *torrent = m_torrents.value(id); TorrentImpl *torrent = m_torrents.value(id);
if (torrent) if (torrent)
@@ -1747,9 +1756,9 @@ void Session::fileSearchFinished(const InfoHash &id, const QString &savePath, co
} }
// Return the torrent handle, given its hash // Return the torrent handle, given its hash
Torrent *Session::findTorrent(const InfoHash &hash) const Torrent *Session::findTorrent(const TorrentID &id) const
{ {
return m_torrents.value(hash); return m_torrents.value(id);
} }
bool Session::hasActiveTorrents() const bool Session::hasActiveTorrents() const
@@ -1764,7 +1773,7 @@ bool Session::hasUnfinishedTorrents() const
{ {
return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent) return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
{ {
return (!torrent->isSeed() && !torrent->isPaused()); return (!torrent->isSeed() && !torrent->isPaused() && !torrent->isErrored());
}); });
} }
@@ -1797,18 +1806,18 @@ void Session::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 Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOption) bool Session::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
{ {
TorrentImpl *const torrent = m_torrents.take(hash); TorrentImpl *const torrent = m_torrents.take(id);
if (!torrent) return false; if (!torrent) return false;
qDebug("Deleting torrent with hash: %s", qUtf8Printable(torrent->hash())); qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
emit torrentAboutToBeRemoved(torrent); emit torrentAboutToBeRemoved(torrent);
// Remove it from session // Remove it from session
if (deleteOption == DeleteTorrent) if (deleteOption == DeleteTorrent)
{ {
m_removingTorrents[torrent->hash()] = {torrent->name(), "", deleteOption}; m_removingTorrents[torrent->id()] = {torrent->name(), "", 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()
@@ -1837,7 +1846,7 @@ bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOptio
rootPath = torrent->actualStorageLocation(); rootPath = torrent->actualStorageLocation();
} }
m_removingTorrents[torrent->hash()] = {torrent->name(), rootPath, deleteOption}; m_removingTorrents[torrent->id()] = {torrent->name(), rootPath, deleteOption};
if (m_moveStorageQueue.size() > 1) if (m_moveStorageQueue.size() > 1)
{ {
@@ -1856,48 +1865,44 @@ bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOptio
} }
// Remove it from torrent resume directory // Remove it from torrent resume directory
const QString resumedataFile = QString::fromLatin1("%1.fastresume").arg(torrent->hash()); const QString resumedataFile = QString::fromLatin1("%1.fastresume").arg(torrent->id().toString());
const QString metadataFile = QString::fromLatin1("%1.torrent").arg(torrent->hash()); const QString metadataFile = QString::fromLatin1("%1.torrent").arg(torrent->id().toString());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(m_resumeDataSavingManager, [this, resumedataFile, metadataFile]() QMetaObject::invokeMethod(m_resumeDataSavingManager, [this, resumedataFile, metadataFile]()
{ {
m_resumeDataSavingManager->remove(resumedataFile); m_resumeDataSavingManager->remove(resumedataFile);
m_resumeDataSavingManager->remove(metadataFile); m_resumeDataSavingManager->remove(metadataFile);
}); });
#else
QMetaObject::invokeMethod(m_resumeDataSavingManager, "remove", Q_ARG(QString, resumedataFile));
QMetaObject::invokeMethod(m_resumeDataSavingManager, "remove", Q_ARG(QString, metadataFile));
#endif
delete torrent; delete torrent;
return true; return true;
} }
bool Session::cancelDownloadMetadata(const InfoHash &hash) bool Session::cancelDownloadMetadata(const TorrentID &id)
{ {
const auto downloadedMetadataIter = m_downloadedMetadata.find(hash); const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
if (downloadedMetadataIter == m_downloadedMetadata.end()) return false; if (downloadedMetadataIter == m_downloadedMetadata.end()) return false;
m_downloadedMetadata.erase(downloadedMetadataIter); m_downloadedMetadata.erase(downloadedMetadataIter);
--m_extraLimit; --m_extraLimit;
adjustLimits(); adjustLimits();
m_nativeSession->remove_torrent(m_nativeSession->find_torrent(hash), lt::session::delete_files); m_nativeSession->remove_torrent(m_nativeSession->find_torrent(id), lt::session::delete_files);
return true; return true;
} }
void Session::increaseTorrentsQueuePos(const QVector<InfoHash> &hashes) void Session::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
{ {
using ElementType = std::pair<int, TorrentImpl *>; using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType std::priority_queue<ElementType
, std::vector<ElementType> , std::vector<ElementType>
, std::greater<ElementType>> torrentQueue; , std::greater<ElementType>> torrentQueue;
// Sort torrents by queue position // Sort torrents by queue position
for (const InfoHash &infoHash : hashes) for (const TorrentID &id : ids)
{ {
TorrentImpl *const torrent = m_torrents.value(infoHash); const TorrentImpl *torrent = m_torrents.value(id);
if (torrent && !torrent->isSeed()) if (!torrent) continue;
torrentQueue.emplace(torrent->queuePosition(), torrent); if (const int position = torrent->queuePosition(); position >= 0)
torrentQueue.emplace(position, torrent);
} }
// Increase torrents queue position (starting with the one in the highest queue position) // Increase torrents queue position (starting with the one in the highest queue position)
@@ -1911,17 +1916,18 @@ void Session::increaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
saveTorrentsQueue(); saveTorrentsQueue();
} }
void Session::decreaseTorrentsQueuePos(const QVector<InfoHash> &hashes) void Session::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
{ {
using ElementType = std::pair<int, TorrentImpl *>; using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType> torrentQueue; std::priority_queue<ElementType> torrentQueue;
// Sort torrents by queue position // Sort torrents by queue position
for (const InfoHash &infoHash : hashes) for (const TorrentID &id : ids)
{ {
TorrentImpl *const torrent = m_torrents.value(infoHash); const TorrentImpl *torrent = m_torrents.value(id);
if (torrent && !torrent->isSeed()) if (!torrent) continue;
torrentQueue.emplace(torrent->queuePosition(), torrent); if (const int position = torrent->queuePosition(); position >= 0)
torrentQueue.emplace(position, torrent);
} }
// Decrease torrents queue position (starting with the one in the lowest queue position) // Decrease torrents queue position (starting with the one in the lowest queue position)
@@ -1938,17 +1944,18 @@ void Session::decreaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
saveTorrentsQueue(); saveTorrentsQueue();
} }
void Session::topTorrentsQueuePos(const QVector<InfoHash> &hashes) void Session::topTorrentsQueuePos(const QVector<TorrentID> &ids)
{ {
using ElementType = std::pair<int, TorrentImpl *>; using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType> torrentQueue; std::priority_queue<ElementType> torrentQueue;
// Sort torrents by queue position // Sort torrents by queue position
for (const InfoHash &infoHash : hashes) for (const TorrentID &id : ids)
{ {
TorrentImpl *const torrent = m_torrents.value(infoHash); const TorrentImpl *torrent = m_torrents.value(id);
if (torrent && !torrent->isSeed()) if (!torrent) continue;
torrentQueue.emplace(torrent->queuePosition(), torrent); if (const int position = torrent->queuePosition(); position >= 0)
torrentQueue.emplace(position, torrent);
} }
// Top torrents queue position (starting with the one in the lowest queue position) // Top torrents queue position (starting with the one in the lowest queue position)
@@ -1962,19 +1969,20 @@ void Session::topTorrentsQueuePos(const QVector<InfoHash> &hashes)
saveTorrentsQueue(); saveTorrentsQueue();
} }
void Session::bottomTorrentsQueuePos(const QVector<InfoHash> &hashes) void Session::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
{ {
using ElementType = std::pair<int, TorrentImpl *>; using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType std::priority_queue<ElementType
, std::vector<ElementType> , std::vector<ElementType>
, std::greater<ElementType>> torrentQueue; , std::greater<ElementType>> torrentQueue;
// Sort torrents by queue position // Sort torrents by queue position
for (const InfoHash &infoHash : hashes) for (const TorrentID &id : ids)
{ {
TorrentImpl *const torrent = m_torrents.value(infoHash); const TorrentImpl *torrent = m_torrents.value(id);
if (torrent && !torrent->isSeed()) if (!torrent) continue;
torrentQueue.emplace(torrent->queuePosition(), torrent); if (const int position = torrent->queuePosition(); position >= 0)
torrentQueue.emplace(position, torrent);
} }
// Bottom torrents queue position (starting with the one in the highest queue position) // Bottom torrents queue position (starting with the one in the highest queue position)
@@ -1991,6 +1999,25 @@ void Session::bottomTorrentsQueuePos(const QVector<InfoHash> &hashes)
saveTorrentsQueue(); saveTorrentsQueue();
} }
void Session::handleTorrentNeedSaveResumeData(const TorrentImpl *torrent)
{
if (m_needSaveResumeDataTorrents.empty())
{
QMetaObject::invokeMethod(this, [this]()
{
for (const TorrentID &torrentID : asConst(m_needSaveResumeDataTorrents))
{
TorrentImpl *torrent = m_torrents.value(torrentID);
if (torrent)
torrent->saveResumeData();
}
m_needSaveResumeDataTorrents.clear();
}, Qt::QueuedConnection);
}
m_needSaveResumeDataTorrents.insert(torrent->id());
}
void Session::handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent) void Session::handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent)
{ {
qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name())); qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
@@ -2086,20 +2113,20 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
const bool hasMetadata = std::holds_alternative<TorrentInfo>(source); const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
TorrentInfo metadata = (hasMetadata ? std::get<TorrentInfo>(source) : TorrentInfo {}); TorrentInfo metadata = (hasMetadata ? std::get<TorrentInfo>(source) : TorrentInfo {});
const MagnetUri &magnetUri = (hasMetadata ? MagnetUri {} : std::get<MagnetUri>(source)); const MagnetUri &magnetUri = (hasMetadata ? MagnetUri {} : std::get<MagnetUri>(source));
const InfoHash hash = (hasMetadata ? metadata.hash() : magnetUri.hash()); const auto id = TorrentID::fromInfoHash(hasMetadata ? metadata.infoHash() : magnetUri.infoHash());
// 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
// problems and unwanted behavior due to the fact that it was originally // problems and unwanted behavior due to the fact that it was originally
// added with parameters other than those provided by the user. // added with parameters other than those provided by the user.
cancelDownloadMetadata(hash); cancelDownloadMetadata(id);
// We should not add the torrent if it is already // We should not add the torrent if it is already
// processed or is pending to add to session // processed or is pending to add to session
if (m_loadingTorrents.contains(hash)) if (m_loadingTorrents.contains(id))
return false; return false;
TorrentImpl *const torrent = m_torrents.value(hash); TorrentImpl *const torrent = m_torrents.value(id);
if (torrent) if (torrent)
{ // a duplicate torrent is added { // a duplicate torrent is added
if (torrent->isPrivate() || (hasMetadata && metadata.isPrivate())) if (torrent->isPrivate() || (hasMetadata && metadata.isPrivate()))
@@ -2190,7 +2217,7 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
if (!isFindingIncompleteFiles) if (!isFindingIncompleteFiles)
return loadTorrent(loadTorrentParams); return loadTorrent(loadTorrentParams);
m_loadingTorrents.insert(hash, loadTorrentParams); m_loadingTorrents.insert(id, loadTorrentParams);
return true; return true;
} }
@@ -2207,8 +2234,12 @@ bool Session::loadTorrent(LoadTorrentParams params)
p.max_uploads = maxUploadsPerTorrent(); p.max_uploads = maxUploadsPerTorrent();
const bool hasMetadata = (p.ti && p.ti->is_valid()); const bool hasMetadata = (p.ti && p.ti->is_valid());
const InfoHash hash = (hasMetadata ? p.ti->info_hash() : p.info_hash); #if (LIBTORRENT_VERSION_NUM >= 20000)
m_loadingTorrents.insert(hash, params); const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hashes() : p.info_hashes);
#else
const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hash() : p.info_hash);
#endif
m_loadingTorrents.insert(id, params);
// Adding torrent to BitTorrent session // Adding torrent to BitTorrent session
m_nativeSession->async_add_torrent(p); m_nativeSession->async_add_torrent(p);
@@ -2218,20 +2249,14 @@ bool Session::loadTorrent(LoadTorrentParams params)
void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const
{ {
const InfoHash searchId = torrentInfo.hash(); const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
const QStringList originalFileNames = torrentInfo.filePaths(); const QStringList originalFileNames = torrentInfo.filePaths();
const QString completeSavePath = savePath; const QString completeSavePath = savePath;
const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {}); const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {});
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(m_fileSearcher, [=]() QMetaObject::invokeMethod(m_fileSearcher, [=]()
{ {
m_fileSearcher->search(searchId, originalFileNames, completeSavePath, incompleteSavePath); m_fileSearcher->search(searchId, originalFileNames, completeSavePath, incompleteSavePath);
}); });
#else
QMetaObject::invokeMethod(m_fileSearcher, "search"
, Q_ARG(BitTorrent::InfoHash, searchId), Q_ARG(QStringList, originalFileNames)
, Q_ARG(QString, completeSavePath), Q_ARG(QString, incompleteSavePath));
#endif
} }
// Add a torrent to libtorrent session in hidden mode // Add a torrent to libtorrent session in hidden mode
@@ -2240,17 +2265,17 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri)
{ {
if (!magnetUri.isValid()) return false; if (!magnetUri.isValid()) return false;
const InfoHash hash = magnetUri.hash(); const auto id = TorrentID::fromInfoHash(magnetUri.infoHash());
const QString name = magnetUri.name(); const QString name = magnetUri.name();
// We should not add torrent if it's already // We should not add torrent if it's already
// processed or adding to session // processed or adding to session
if (m_torrents.contains(hash)) return false; if (m_torrents.contains(id)) return false;
if (m_loadingTorrents.contains(hash)) return false; if (m_loadingTorrents.contains(id)) return false;
if (m_downloadedMetadata.contains(hash)) return false; if (m_downloadedMetadata.contains(id)) return false;
qDebug("Adding torrent to preload metadata..."); qDebug("Adding torrent to preload metadata...");
qDebug(" -> Hash: %s", qUtf8Printable(hash)); qDebug(" -> Torrent ID: %s", qUtf8Printable(id.toString()));
qDebug(" -> Name: %s", qUtf8Printable(name)); qDebug(" -> Name: %s", qUtf8Printable(name));
lt::add_torrent_params p = magnetUri.addTorrentParams(); lt::add_torrent_params p = magnetUri.addTorrentParams();
@@ -2266,7 +2291,7 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri)
p.max_connections = maxConnectionsPerTorrent(); p.max_connections = maxConnectionsPerTorrent();
p.max_uploads = maxUploadsPerTorrent(); p.max_uploads = maxUploadsPerTorrent();
const QString savePath = Utils::Fs::tempPath() + static_cast<QString>(hash); const QString savePath = Utils::Fs::tempPath() + id.toString();
p.save_path = Utils::Fs::toNativePath(savePath).toStdString(); p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
// Forced start // Forced start
@@ -2299,7 +2324,7 @@ void Session::exportTorrentFile(const Torrent *torrent, TorrentExportFolder fold
((folder == TorrentExportFolder::Finished) && !finishedTorrentExportDirectory().isEmpty())); ((folder == TorrentExportFolder::Finished) && !finishedTorrentExportDirectory().isEmpty()));
const QString validName = Utils::Fs::toValidFileSystemName(torrent->name()); const QString validName = Utils::Fs::toValidFileSystemName(torrent->name());
const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(torrent->hash()); const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(torrent->id().toString());
QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName); QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName);
const QString torrentPath = QDir(m_resumeFolderPath).absoluteFilePath(torrentFilename); const QString torrentPath = QDir(m_resumeFolderPath).absoluteFilePath(torrentFilename);
const QDir exportPath(folder == TorrentExportFolder::Regular ? torrentExportDirectory() : finishedTorrentExportDirectory()); const QDir exportPath(folder == TorrentExportFolder::Regular ? torrentExportDirectory() : finishedTorrentExportDirectory());
@@ -2326,7 +2351,10 @@ void Session::generateResumeData()
if (!torrent->isValid()) continue; if (!torrent->isValid()) continue;
if (torrent->needSaveResumeData()) if (torrent->needSaveResumeData())
{
torrent->saveResumeData(); torrent->saveResumeData();
m_needSaveResumeDataTorrents.remove(torrent->id());
}
} }
} }
@@ -2363,7 +2391,7 @@ void Session::saveResumeData()
} }
} }
void Session::saveTorrentsQueue() void Session::saveTorrentsQueue() const
{ {
// store hash in textual representation // store hash in textual representation
QMap<int, QString> queue; // Use QMap since it should be ordered by key QMap<int, QString> queue; // Use QMap since it should be ordered by key
@@ -2372,33 +2400,24 @@ void Session::saveTorrentsQueue()
// We require actual (non-cached) queue position here! // We require actual (non-cached) queue position here!
const int queuePos = static_cast<LTUnderlyingType<lt::queue_position_t>>(torrent->nativeHandle().queue_position()); const int queuePos = static_cast<LTUnderlyingType<lt::queue_position_t>>(torrent->nativeHandle().queue_position());
if (queuePos >= 0) if (queuePos >= 0)
queue[queuePos] = torrent->hash(); queue[queuePos] = torrent->id().toString();
} }
QByteArray data; QByteArray data;
data.reserve(((InfoHash::length() * 2) + 1) * queue.size()); data.reserve(((TorrentID::length() * 2) + 1) * queue.size());
for (const QString &hash : asConst(queue)) for (const QString &torrentID : asConst(queue))
data += (hash.toLatin1() + '\n'); data += (torrentID.toLatin1() + '\n');
const QString filename = QLatin1String {"queue"}; const QString filename = QLatin1String {"queue"};
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(m_resumeDataSavingManager QMetaObject::invokeMethod(m_resumeDataSavingManager
, [this, data, filename]() { m_resumeDataSavingManager->save(filename, data); }); , [this, data, filename]() { m_resumeDataSavingManager->save(filename, data); });
#else
QMetaObject::invokeMethod(m_resumeDataSavingManager, "save"
, Q_ARG(QString, filename), Q_ARG(QByteArray, data));
#endif
} }
void Session::removeTorrentsQueue() void Session::removeTorrentsQueue() const
{ {
const QString filename = QLatin1String {"queue"}; const QString filename = QLatin1String {"queue"};
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(m_resumeDataSavingManager QMetaObject::invokeMethod(m_resumeDataSavingManager
, [this, filename]() { m_resumeDataSavingManager->remove(filename); }); , [this, filename]() { m_resumeDataSavingManager->remove(filename); });
#else
QMetaObject::invokeMethod(m_resumeDataSavingManager, "remove", Q_ARG(QString, filename));
#endif
} }
void Session::setDefaultSavePath(QString path) void Session::setDefaultSavePath(QString path)
@@ -3516,6 +3535,20 @@ void Session::setUPnPLeaseDuration(const int duration)
} }
} }
int Session::peerToS() const
{
return m_peerToS;
}
void Session::setPeerToS(const int value)
{
if (value == m_peerToS)
return;
m_peerToS = value;
configureDeferred();
}
bool Session::ignoreLimitsOnLAN() const bool Session::ignoreLimitsOnLAN() const
{ {
return m_ignoreLimitsOnLAN; return m_ignoreLimitsOnLAN;
@@ -3742,11 +3775,11 @@ void Session::setMaxRatioAction(const MaxRatioAction act)
// If this functions returns true, we cannot add torrent to session, // If this functions returns true, we cannot add torrent to session,
// but it is still possible to merge trackers in some cases // but it is still possible to merge trackers in some cases
bool Session::isKnownTorrent(const InfoHash &hash) const bool Session::isKnownTorrent(const TorrentID &id) const
{ {
return (m_torrents.contains(hash) return (m_torrents.contains(id)
|| m_loadingTorrents.contains(hash) || m_loadingTorrents.contains(id)
|| m_downloadedMetadata.contains(hash)); || m_downloadedMetadata.contains(id));
} }
void Session::updateSeedingLimitTimer() void Session::updateSeedingLimitTimer()
@@ -3765,51 +3798,43 @@ void Session::updateSeedingLimitTimer()
void Session::handleTorrentShareLimitChanged(TorrentImpl *const torrent) void Session::handleTorrentShareLimitChanged(TorrentImpl *const torrent)
{ {
torrent->saveResumeData();
updateSeedingLimitTimer(); updateSeedingLimitTimer();
} }
void Session::handleTorrentNameChanged(TorrentImpl *const torrent) void Session::handleTorrentNameChanged(TorrentImpl *const torrent)
{ {
torrent->saveResumeData(); Q_UNUSED(torrent);
} }
void Session::handleTorrentSavePathChanged(TorrentImpl *const torrent) void Session::handleTorrentSavePathChanged(TorrentImpl *const torrent)
{ {
torrent->saveResumeData();
emit torrentSavePathChanged(torrent); emit torrentSavePathChanged(torrent);
} }
void Session::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory) void Session::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
{ {
torrent->saveResumeData();
emit torrentCategoryChanged(torrent, oldCategory); emit torrentCategoryChanged(torrent, oldCategory);
} }
void Session::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag) void Session::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
{ {
torrent->saveResumeData();
emit torrentTagAdded(torrent, tag); emit torrentTagAdded(torrent, tag);
} }
void Session::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag) void Session::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag)
{ {
torrent->saveResumeData();
emit torrentTagRemoved(torrent, tag); emit torrentTagRemoved(torrent, tag);
} }
void Session::handleTorrentSavingModeChanged(TorrentImpl *const torrent) void Session::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
{ {
torrent->saveResumeData();
emit torrentSavingModeChanged(torrent); emit torrentSavingModeChanged(torrent);
} }
void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers) void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
{ {
torrent->saveResumeData();
for (const TrackerEntry &newTracker : newTrackers) for (const TrackerEntry &newTracker : newTrackers)
LogMsg(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url(), torrent->name())); LogMsg(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url, torrent->name()));
emit trackersAdded(torrent, newTrackers); emit trackersAdded(torrent, newTrackers);
if (torrent->trackers().size() == newTrackers.size()) if (torrent->trackers().size() == newTrackers.size())
emit trackerlessStateChanged(torrent, false); emit trackerlessStateChanged(torrent, false);
@@ -3818,10 +3843,8 @@ void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVect
void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVector<TrackerEntry> &deletedTrackers) void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVector<TrackerEntry> &deletedTrackers)
{ {
torrent->saveResumeData();
for (const TrackerEntry &deletedTracker : deletedTrackers) for (const TrackerEntry &deletedTracker : deletedTrackers)
LogMsg(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url(), torrent->name())); LogMsg(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url, torrent->name()));
emit trackersRemoved(torrent, deletedTrackers); emit trackersRemoved(torrent, deletedTrackers);
if (torrent->trackers().empty()) if (torrent->trackers().empty())
emit trackerlessStateChanged(torrent, true); emit trackerlessStateChanged(torrent, true);
@@ -3830,20 +3853,17 @@ void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVe
void Session::handleTorrentTrackersChanged(TorrentImpl *const torrent) void Session::handleTorrentTrackersChanged(TorrentImpl *const torrent)
{ {
torrent->saveResumeData();
emit trackersChanged(torrent); emit trackersChanged(torrent);
} }
void Session::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds) void Session::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
{ {
torrent->saveResumeData();
for (const QUrl &newUrlSeed : newUrlSeeds) for (const QUrl &newUrlSeed : newUrlSeeds)
LogMsg(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name())); LogMsg(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name()));
} }
void Session::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds) void Session::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
{ {
torrent->saveResumeData();
for (const QUrl &urlSeed : urlSeeds) for (const QUrl &urlSeed : urlSeeds)
LogMsg(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name())); LogMsg(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name()));
} }
@@ -3852,7 +3872,7 @@ void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
{ {
// Save metadata // Save metadata
const QDir resumeDataDir {m_resumeFolderPath}; const QDir resumeDataDir {m_resumeFolderPath};
const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->hash())}; const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->id().toString())};
try try
{ {
torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName)); torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName));
@@ -3871,13 +3891,11 @@ void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
void Session::handleTorrentPaused(TorrentImpl *const torrent) void Session::handleTorrentPaused(TorrentImpl *const torrent)
{ {
torrent->saveResumeData();
emit torrentPaused(torrent); emit torrentPaused(torrent);
} }
void Session::handleTorrentResumed(TorrentImpl *const torrent) void Session::handleTorrentResumed(TorrentImpl *const torrent)
{ {
torrent->saveResumeData();
emit torrentResumed(torrent); emit torrentResumed(torrent);
} }
@@ -3888,8 +3906,6 @@ void Session::handleTorrentChecked(TorrentImpl *const torrent)
void Session::handleTorrentFinished(TorrentImpl *const torrent) void Session::handleTorrentFinished(TorrentImpl *const torrent)
{ {
if (!torrent->hasError() && !torrent->hasMissingFiles())
torrent->saveResumeData();
emit torrentFinished(torrent); emit torrentFinished(torrent);
qDebug("Checking if the torrent contains torrent files to download"); qDebug("Checking if the torrent contains torrent files to download");
@@ -3925,21 +3941,52 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent)
emit allTorrentsFinished(); emit allTorrentsFinished();
} }
void Session::handleTorrentResumeDataReady(TorrentImpl *const torrent, const std::shared_ptr<lt::entry> &data) void Session::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
{ {
--m_numResumeData; --m_numResumeData;
// We need to adjust native libtorrent resume data
lt::add_torrent_params p = data.ltAddTorrentParams;
p.save_path = Profile::instance()->toPortablePath(QString::fromStdString(p.save_path)).toStdString();
if (data.paused)
{
p.flags |= lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
}
else
{
// Torrent can be actually "running" but temporarily "paused" to perform some
// service jobs behind the scenes so we need to restore it as "running"
if (!data.forced)
{
p.flags |= lt::torrent_flags::auto_managed;
}
else
{
p.flags &= ~lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
}
}
// Separated thread is used for the blocking IO which results in slow processing of many torrents. // Separated thread is used for the blocking IO which results in slow processing of many torrents.
// Copying lt::entry objects around isn't cheap. // Copying lt::entry objects around isn't cheap.
const QString filename = QString::fromLatin1("%1.fastresume").arg(torrent->hash()); auto resumeDataPtr = std::make_shared<lt::entry>(lt::write_resume_data(p));
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) lt::entry &resumeData = *resumeDataPtr;
resumeData["qBt-savePath"] = Profile::instance()->toPortablePath(data.savePath).toStdString();
resumeData["qBt-ratioLimit"] = static_cast<int>(data.ratioLimit * 1000);
resumeData["qBt-seedingTimeLimit"] = data.seedingTimeLimit;
resumeData["qBt-category"] = data.category.toStdString();
resumeData["qBt-tags"] = setToEntryList(data.tags);
resumeData["qBt-name"] = data.name.toStdString();
resumeData["qBt-seedStatus"] = data.hasSeedStatus;
resumeData["qBt-contentLayout"] = Utils::String::fromEnum(data.contentLayout).toStdString();
resumeData["qBt-firstLastPiecePriority"] = data.firstLastPiecePriority;
const QString filename = QString::fromLatin1("%1.fastresume").arg(torrent->id().toString());
QMetaObject::invokeMethod(m_resumeDataSavingManager QMetaObject::invokeMethod(m_resumeDataSavingManager
, [this, filename, data]() { m_resumeDataSavingManager->save(filename, data); }); , [this, filename, resumeDataPtr]() { m_resumeDataSavingManager->save(filename, resumeDataPtr); });
#else
QMetaObject::invokeMethod(m_resumeDataSavingManager, "save"
, Q_ARG(QString, filename), Q_ARG(std::shared_ptr<lt::entry>, data));
#endif
} }
void Session::handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl) void Session::handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl)
@@ -4008,9 +4055,13 @@ bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newP
void Session::moveTorrentStorage(const MoveStorageJob &job) const void Session::moveTorrentStorage(const MoveStorageJob &job) const
{ {
const InfoHash infoHash = job.torrentHandle.info_hash(); #if (LIBTORRENT_VERSION_NUM >= 20000)
const TorrentImpl *torrent = m_torrents.value(infoHash); const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
const QString torrentName = (torrent ? torrent->name() : QString {infoHash}); #else
const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
#endif
const TorrentImpl *torrent = m_torrents.value(id);
const QString torrentName = (torrent ? torrent->name() : id.toString());
LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path)); LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path));
job.torrentHandle.move_storage(job.path.toUtf8().constData() job.torrentHandle.move_storage(job.path.toUtf8().constData()
@@ -4094,15 +4145,9 @@ void Session::configureDeferred()
{ {
if (m_deferredConfigureScheduled) if (m_deferredConfigureScheduled)
return; return;
m_deferredConfigureScheduled = true;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) m_deferredConfigureScheduled = true;
QMetaObject::invokeMethod(this QMetaObject::invokeMethod(this, qOverload<>(&Session::configure), Qt::QueuedConnection);
, qOverload<>(&Session::configure)
, Qt::QueuedConnection);
#else
QMetaObject::invokeMethod(this, "configure", Qt::QueuedConnection);
#endif
} }
// Enable IP Filtering // Enable IP Filtering
@@ -4141,9 +4186,9 @@ void Session::disableIPFilter()
m_nativeSession->set_ip_filter(filter); m_nativeSession->set_ip_filter(filter);
} }
void Session::recursiveTorrentDownload(const InfoHash &hash) void Session::recursiveTorrentDownload(const TorrentID &id)
{ {
TorrentImpl *const torrent = m_torrents.value(hash); TorrentImpl *const torrent = m_torrents.value(id);
if (!torrent) return; if (!torrent) return;
for (int i = 0; i < torrent->filesCount(); ++i) for (int i = 0; i < torrent->filesCount(); ++i)
@@ -4259,51 +4304,7 @@ bool Session::loadTorrentResumeData(const QByteArray &data, const TorrentInfo &m
const bool hasMetadata = (p.ti && p.ti->is_valid()); const bool hasMetadata = (p.ti && p.ti->is_valid());
if (!hasMetadata && !root.dict_find("info-hash")) if (!hasMetadata && !root.dict_find("info-hash"))
{ return false;
// TODO: The following code is deprecated. Remove after several releases in 4.3.x.
// === BEGIN DEPRECATED CODE === //
// Try to load from legacy data used in older versions for torrents w/o metadata
const lt::bdecode_node magnetURINode = root.dict_find("qBt-magnetUri");
if (magnetURINode.type() == lt::bdecode_node::string_t)
{
lt::parse_magnet_uri(magnetURINode.string_value(), p, ec);
if (isTempPathEnabled())
{
p.save_path = Utils::Fs::toNativePath(tempPath()).toStdString();
}
else
{
// If empty then Automatic mode, otherwise Manual mode
const QString savePath = torrentParams.savePath.isEmpty() ? categorySavePath(torrentParams.category) : torrentParams.savePath;
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
}
// Preallocation mode
p.storage_mode = (isPreallocationEnabled() ? lt::storage_mode_allocate : lt::storage_mode_sparse);
const lt::bdecode_node addedTimeNode = root.dict_find("qBt-addedTime");
if (addedTimeNode.type() == lt::bdecode_node::int_t)
p.added_time = addedTimeNode.int_value();
const lt::bdecode_node sequentialNode = root.dict_find("qBt-sequential");
if (sequentialNode.type() == lt::bdecode_node::int_t)
{
if (static_cast<bool>(sequentialNode.int_value()))
p.flags |= lt::torrent_flags::sequential_download;
else
p.flags &= ~lt::torrent_flags::sequential_download;
}
if (torrentParams.name.isEmpty() && !p.name.empty())
torrentParams.name = QString::fromStdString(p.name);
}
// === END DEPRECATED CODE === //
else
{
return false;
}
}
return true; return true;
} }
@@ -4568,7 +4569,7 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle)
const LoadTorrentParams params = m_loadingTorrents.take(nativeHandle.info_hash()); const LoadTorrentParams params = m_loadingTorrents.take(nativeHandle.info_hash());
auto *const torrent = new TorrentImpl {this, m_nativeSession, nativeHandle, params}; auto *const torrent = new TorrentImpl {this, m_nativeSession, nativeHandle, params};
m_torrents.insert(torrent->hash(), torrent); m_torrents.insert(torrent->id(), torrent);
const bool hasMetadata = torrent->hasMetadata(); const bool hasMetadata = torrent->hasMetadata();
@@ -4583,7 +4584,7 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle)
{ {
// Backup torrent file // Backup torrent file
const QDir resumeDataDir {m_resumeFolderPath}; const QDir resumeDataDir {m_resumeFolderPath};
const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->hash())}; const QString torrentFileName {QString::fromLatin1("%1.torrent").arg(torrent->id().toString())};
try try
{ {
torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName)); torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName));
@@ -4641,9 +4642,13 @@ void Session::handleAddTorrentAlert(const lt::add_torrent_alert *p)
void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p) void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
{ {
const InfoHash infoHash {p->info_hash}; #if (LIBTORRENT_VERSION_NUM >= 20000)
const auto id = TorrentID::fromInfoHash(p->info_hashes);
#else
const auto id = TorrentID::fromInfoHash(p->info_hash);
#endif
const auto removingTorrentDataIter = m_removingTorrents.find(infoHash); const auto removingTorrentDataIter = m_removingTorrents.find(id);
if (removingTorrentDataIter != m_removingTorrents.end()) if (removingTorrentDataIter != m_removingTorrents.end())
{ {
if (removingTorrentDataIter->deleteOption == DeleteTorrent) if (removingTorrentDataIter->deleteOption == DeleteTorrent)
@@ -4656,8 +4661,13 @@ void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p) void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
{ {
const InfoHash infoHash {p->info_hash}; #if (LIBTORRENT_VERSION_NUM >= 20000)
const auto removingTorrentDataIter = m_removingTorrents.find(infoHash); const auto id = TorrentID::fromInfoHash(p->info_hashes);
#else
const auto id = TorrentID::fromInfoHash(p->info_hash);
#endif
const auto removingTorrentDataIter = m_removingTorrents.find(id);
if (removingTorrentDataIter == m_removingTorrents.end()) if (removingTorrentDataIter == m_removingTorrents.end())
return; return;
@@ -4669,8 +4679,13 @@ void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p) void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
{ {
const InfoHash infoHash {p->info_hash}; #if (LIBTORRENT_VERSION_NUM >= 20000)
const auto removingTorrentDataIter = m_removingTorrents.find(infoHash); const auto id = TorrentID::fromInfoHash(p->info_hashes);
#else
const auto id = TorrentID::fromInfoHash(p->info_hash);
#endif
const auto removingTorrentDataIter = m_removingTorrents.find(id);
if (removingTorrentDataIter == m_removingTorrents.end()) if (removingTorrentDataIter == m_removingTorrents.end())
return; return;
@@ -4694,8 +4709,13 @@ void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_ale
void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p) void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
{ {
const InfoHash hash {p->handle.info_hash()}; #if (LIBTORRENT_VERSION_NUM >= 20000)
const auto downloadedMetadataIter = m_downloadedMetadata.find(hash); const auto id = TorrentID::fromInfoHash(p->handle.info_hashes());
#else
const auto id = TorrentID::fromInfoHash(p->handle.info_hash());
#endif
const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
if (downloadedMetadataIter != m_downloadedMetadata.end()) if (downloadedMetadataIter != m_downloadedMetadata.end())
{ {
@@ -4716,11 +4736,11 @@ void Session::handleFileErrorAlert(const lt::file_error_alert *p)
if (!torrent) if (!torrent)
return; return;
const InfoHash hash = torrent->hash(); const TorrentID id = torrent->id();
if (!m_recentErroredTorrents.contains(hash)) if (!m_recentErroredTorrents.contains(id))
{ {
m_recentErroredTorrents.insert(hash); m_recentErroredTorrents.insert(id);
const QString msg = QString::fromStdString(p->message()); const QString msg = QString::fromStdString(p->message());
LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: %3") LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: %3")
@@ -4919,9 +4939,14 @@ void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p)
const QString newPath {p->storage_path()}; const QString newPath {p->storage_path()};
Q_ASSERT(newPath == currentJob.path); Q_ASSERT(newPath == currentJob.path);
const InfoHash infoHash = currentJob.torrentHandle.info_hash(); #if (LIBTORRENT_VERSION_NUM >= 20000)
TorrentImpl *torrent = m_torrents.value(infoHash); const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
const QString torrentName = (torrent ? torrent->name() : QString {infoHash}); #else
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
#endif
TorrentImpl *torrent = m_torrents.value(id);
const QString torrentName = (torrent ? torrent->name() : id.toString());
LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath)); LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath));
handleMoveTorrentStorageJobFinished(); handleMoveTorrentStorageJobFinished();
@@ -4934,9 +4959,14 @@ void Session::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert
const MoveStorageJob &currentJob = m_moveStorageQueue.first(); const MoveStorageJob &currentJob = m_moveStorageQueue.first();
Q_ASSERT(currentJob.torrentHandle == p->handle); Q_ASSERT(currentJob.torrentHandle == p->handle);
const InfoHash infoHash = currentJob.torrentHandle.info_hash(); #if (LIBTORRENT_VERSION_NUM >= 20000)
TorrentImpl *torrent = m_torrents.value(infoHash); const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
const QString torrentName = (torrent ? torrent->name() : QString {infoHash}); #else
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
#endif
TorrentImpl *torrent = m_torrents.value(id);
const QString torrentName = (torrent ? torrent->name() : id.toString());
const QString currentLocation = QString::fromStdString(p->handle.status(lt::torrent_handle::query_save_path).save_path); const QString currentLocation = QString::fromStdString(p->handle.status(lt::torrent_handle::query_save_path).save_path);
const QString errorMessage = QString::fromStdString(p->message()); const QString errorMessage = QString::fromStdString(p->message());
LogMsg(tr("Failed to move \"%1\" from \"%2\" to \"%3\". Reason: %4.") LogMsg(tr("Failed to move \"%1\" from \"%2\" to \"%3\". Reason: %4.")
@@ -4952,8 +4982,12 @@ void Session::handleStateUpdateAlert(const lt::state_update_alert *p)
for (const lt::torrent_status &status : p->status) for (const lt::torrent_status &status : p->status)
{ {
TorrentImpl *const torrent = m_torrents.value(status.info_hash); #if (LIBTORRENT_VERSION_NUM >= 20000)
const auto id = TorrentID::fromInfoHash(status.info_hashes);
#else
const auto id = TorrentID::fromInfoHash(status.info_hash);
#endif
TorrentImpl *const torrent = m_torrents.value(id);
if (!torrent) if (!torrent)
continue; continue;

View File

@@ -51,14 +51,6 @@
#include "sessionstatus.h" #include "sessionstatus.h"
#include "torrentinfo.h" #include "torrentinfo.h"
#if !defined(Q_OS_WIN) || (LIBTORRENT_VERSION_NUM >= 10212)
#define HAS_HTTPS_TRACKER_VALIDATION
#endif
#if ((LIBTORRENT_VERSION_NUM >= 10212) && (LIBTORRENT_VERSION_NUM < 20000)) || (LIBTORRENT_VERSION_NUM >= 20002)
#define HAS_IDN_SUPPORT
#endif
class QFile; class QFile;
class QNetworkConfiguration; class QNetworkConfiguration;
class QNetworkConfigurationManager; class QNetworkConfigurationManager;
@@ -107,8 +99,8 @@ namespace BitTorrent
class Torrent; class Torrent;
class TorrentImpl; class TorrentImpl;
class Tracker; class Tracker;
class TrackerEntry;
struct LoadTorrentParams; struct LoadTorrentParams;
struct TrackerEntry;
enum class MoveStorageMode; enum class MoveStorageMode;
@@ -392,6 +384,8 @@ namespace BitTorrent
void setOutgoingPortsMax(int max); void setOutgoingPortsMax(int max);
int UPnPLeaseDuration() const; int UPnPLeaseDuration() const;
void setUPnPLeaseDuration(int duration); void setUPnPLeaseDuration(int duration);
int peerToS() const;
void setPeerToS(int value);
bool ignoreLimitsOnLAN() const; bool ignoreLimitsOnLAN() const;
void setIgnoreLimitsOnLAN(bool ignore); void setIgnoreLimitsOnLAN(bool ignore);
bool includeOverheadInLimits() const; bool includeOverheadInLimits() const;
@@ -440,7 +434,7 @@ namespace BitTorrent
#endif #endif
void startUpTorrents(); void startUpTorrents();
Torrent *findTorrent(const InfoHash &hash) const; Torrent *findTorrent(const TorrentID &id) const;
QVector<Torrent *> torrents() const; QVector<Torrent *> torrents() const;
bool hasActiveTorrents() const; bool hasActiveTorrents() const;
bool hasUnfinishedTorrents() const; bool hasUnfinishedTorrents() const;
@@ -456,21 +450,22 @@ namespace BitTorrent
void banIP(const QString &ip); void banIP(const QString &ip);
bool isKnownTorrent(const InfoHash &hash) const; bool isKnownTorrent(const TorrentID &id) const;
bool addTorrent(const QString &source, const AddTorrentParams &params = AddTorrentParams()); bool addTorrent(const QString &source, const AddTorrentParams &params = AddTorrentParams());
bool addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &params = AddTorrentParams()); bool addTorrent(const MagnetUri &magnetUri, const AddTorrentParams &params = AddTorrentParams());
bool addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams &params = AddTorrentParams()); bool addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams &params = AddTorrentParams());
bool deleteTorrent(const InfoHash &hash, DeleteOption deleteOption = DeleteTorrent); bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteTorrent);
bool downloadMetadata(const MagnetUri &magnetUri); bool downloadMetadata(const MagnetUri &magnetUri);
bool cancelDownloadMetadata(const InfoHash &hash); bool cancelDownloadMetadata(const TorrentID &id);
void recursiveTorrentDownload(const InfoHash &hash); void recursiveTorrentDownload(const TorrentID &id);
void increaseTorrentsQueuePos(const QVector<InfoHash> &hashes); void increaseTorrentsQueuePos(const QVector<TorrentID> &ids);
void decreaseTorrentsQueuePos(const QVector<InfoHash> &hashes); void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids);
void topTorrentsQueuePos(const QVector<InfoHash> &hashes); void topTorrentsQueuePos(const QVector<TorrentID> &ids);
void bottomTorrentsQueuePos(const QVector<InfoHash> &hashes); void bottomTorrentsQueuePos(const QVector<TorrentID> &ids);
// Torrent interface // Torrent interface
void handleTorrentNeedSaveResumeData(const TorrentImpl *torrent);
void handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent); void handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent);
void handleTorrentShareLimitChanged(TorrentImpl *const torrent); void handleTorrentShareLimitChanged(TorrentImpl *const torrent);
void handleTorrentNameChanged(TorrentImpl *const torrent); void handleTorrentNameChanged(TorrentImpl *const torrent);
@@ -489,7 +484,7 @@ namespace BitTorrent
void handleTorrentTrackersChanged(TorrentImpl *const torrent); void handleTorrentTrackersChanged(TorrentImpl *const torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds); void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds); void handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds);
void handleTorrentResumeDataReady(TorrentImpl *const torrent, const std::shared_ptr<lt::entry> &data); void handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data);
void handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl); void handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl);
void handleTorrentTrackerWarning(TorrentImpl *const torrent, const QString &trackerUrl); void handleTorrentTrackerWarning(TorrentImpl *const torrent, const QString &trackerUrl);
void handleTorrentTrackerError(TorrentImpl *const torrent, const QString &trackerUrl); void handleTorrentTrackerError(TorrentImpl *const torrent, const QString &trackerUrl);
@@ -545,7 +540,7 @@ namespace BitTorrent
void handleIPFilterParsed(int ruleCount); void handleIPFilterParsed(int ruleCount);
void handleIPFilterError(); void handleIPFilterError();
void handleDownloadFinished(const Net::DownloadResult &result); void handleDownloadFinished(const Net::DownloadResult &result);
void fileSearchFinished(const InfoHash &id, const QString &savePath, const QStringList &fileNames); void fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames);
// Session reconfiguration triggers // Session reconfiguration triggers
void networkOnlineStateChanged(bool online); void networkOnlineStateChanged(bool online);
@@ -632,8 +627,8 @@ namespace BitTorrent
void createTorrent(const lt::torrent_handle &nativeHandle); void createTorrent(const lt::torrent_handle &nativeHandle);
void saveResumeData(); void saveResumeData();
void saveTorrentsQueue(); void saveTorrentsQueue() const;
void removeTorrentsQueue(); void removeTorrentsQueue() const;
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const; std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
@@ -681,6 +676,7 @@ namespace BitTorrent
CachedSettingValue<int> m_outgoingPortsMin; CachedSettingValue<int> m_outgoingPortsMin;
CachedSettingValue<int> m_outgoingPortsMax; CachedSettingValue<int> m_outgoingPortsMax;
CachedSettingValue<int> m_UPnPLeaseDuration; CachedSettingValue<int> m_UPnPLeaseDuration;
CachedSettingValue<int> m_peerToS;
CachedSettingValue<bool> m_ignoreLimitsOnLAN; CachedSettingValue<bool> m_ignoreLimitsOnLAN;
CachedSettingValue<bool> m_includeOverheadInLimits; CachedSettingValue<bool> m_includeOverheadInLimits;
CachedSettingValue<QString> m_announceIP; CachedSettingValue<QString> m_announceIP;
@@ -769,17 +765,18 @@ namespace BitTorrent
ResumeDataSavingManager *m_resumeDataSavingManager = nullptr; ResumeDataSavingManager *m_resumeDataSavingManager = nullptr;
FileSearcher *m_fileSearcher = nullptr; FileSearcher *m_fileSearcher = nullptr;
QSet<InfoHash> m_downloadedMetadata; QSet<TorrentID> m_downloadedMetadata;
QHash<InfoHash, TorrentImpl *> m_torrents; QHash<TorrentID, TorrentImpl *> m_torrents;
QHash<InfoHash, LoadTorrentParams> m_loadingTorrents; QHash<TorrentID, LoadTorrentParams> m_loadingTorrents;
QHash<QString, AddTorrentParams> m_downloadedTorrents; QHash<QString, AddTorrentParams> m_downloadedTorrents;
QHash<InfoHash, RemovingTorrentData> m_removingTorrents; QHash<TorrentID, RemovingTorrentData> m_removingTorrents;
QSet<TorrentID> m_needSaveResumeDataTorrents;
QStringMap m_categories; QStringMap m_categories;
QSet<QString> m_tags; QSet<QString> m_tags;
// I/O errored torrents // I/O errored torrents
QSet<InfoHash> m_recentErroredTorrents; QSet<TorrentID> m_recentErroredTorrents;
QTimer *m_recentErroredTorrentsTimer = nullptr; QTimer *m_recentErroredTorrentsTimer = nullptr;
SessionMetricIndices m_metricIndices; SessionMetricIndices m_metricIndices;
@@ -795,8 +792,3 @@ namespace BitTorrent
static Session *m_instance; static Session *m_instance;
}; };
} }
#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
Q_DECLARE_METATYPE(std::shared_ptr<lt::entry>)
const int sharedPtrLtEntryTypeID = qRegisterMetaType<std::shared_ptr<lt::entry>>();
#endif

View File

@@ -29,10 +29,10 @@
#include "torrent.h" #include "torrent.h"
#include <type_traits>
#include <QHash> #include <QHash>
#include "infohash.h"
namespace BitTorrent namespace BitTorrent
{ {
uint qHash(const TorrentState key, const uint seed) uint qHash(const TorrentState key, const uint seed)
@@ -42,15 +42,20 @@ namespace BitTorrent
// Torrent // Torrent
const qreal Torrent::USE_GLOBAL_RATIO = -2.; const qreal Torrent::USE_GLOBAL_RATIO = -2;
const qreal Torrent::NO_RATIO_LIMIT = -1.; const qreal Torrent::NO_RATIO_LIMIT = -1;
const int Torrent::USE_GLOBAL_SEEDING_TIME = -2; const int Torrent::USE_GLOBAL_SEEDING_TIME = -2;
const int Torrent::NO_SEEDING_TIME_LIMIT = -1; const int Torrent::NO_SEEDING_TIME_LIMIT = -1;
const qreal Torrent::MAX_RATIO = 9999.; const qreal Torrent::MAX_RATIO = 9999;
const int Torrent::MAX_SEEDING_TIME = 525600; const int Torrent::MAX_SEEDING_TIME = 525600;
TorrentID Torrent::id() const
{
return infoHash().toTorrentID();
}
bool Torrent::isResumed() const bool Torrent::isResumed() const
{ {
return !isPaused(); return !isPaused();

View File

@@ -44,9 +44,10 @@ namespace BitTorrent
enum class DownloadPriority; enum class DownloadPriority;
class InfoHash; class InfoHash;
class PeerInfo; class PeerInfo;
class TorrentID;
class TorrentInfo; class TorrentInfo;
class TrackerEntry;
struct PeerAddress; struct PeerAddress;
struct TrackerEntry;
enum class TorrentOperatingMode enum class TorrentOperatingMode
{ {
@@ -105,7 +106,7 @@ namespace BitTorrent
virtual ~Torrent() = default; virtual ~Torrent() = default;
virtual InfoHash hash() const = 0; virtual InfoHash infoHash() const = 0;
virtual QString name() const = 0; virtual QString name() const = 0;
virtual QDateTime creationDate() const = 0; virtual QDateTime creationDate() const = 0;
virtual QString creator() const = 0; virtual QString creator() const = 0;
@@ -290,6 +291,7 @@ namespace BitTorrent
virtual QString createMagnetURI() const = 0; virtual QString createMagnetURI() const = 0;
TorrentID id() const;
bool isResumed() const; bool isResumed() const;
qlonglong remainingSize() const; qlonglong remainingSize() const;

View File

@@ -100,8 +100,6 @@ void TorrentCreatorThread::sendProgressSignal(int currentPieceIdx, int totalPiec
void TorrentCreatorThread::run() void TorrentCreatorThread::run()
{ {
const QString creatorStr("qBittorrent " QBT_VERSION);
emit updateProgress(0); emit updateProgress(0);
try try
@@ -178,17 +176,19 @@ void TorrentCreatorThread::run()
newTorrent.add_tracker(tracker.trimmed().toStdString(), tier); newTorrent.add_tracker(tracker.trimmed().toStdString(), tier);
} }
if (isInterruptionRequested()) return;
// calculate the hash for all pieces // calculate the hash for all pieces
lt::set_piece_hashes(newTorrent, Utils::Fs::toNativePath(parentPath).toStdString() lt::set_piece_hashes(newTorrent, Utils::Fs::toNativePath(parentPath).toStdString()
, [this, &newTorrent](const lt::piece_index_t n) , [this, &newTorrent](const lt::piece_index_t n)
{ {
if (isInterruptionRequested())
throw RuntimeError {tr("Create new torrent aborted.")};
sendProgressSignal(static_cast<LTUnderlyingType<lt::piece_index_t>>(n), newTorrent.num_pieces()); sendProgressSignal(static_cast<LTUnderlyingType<lt::piece_index_t>>(n), newTorrent.num_pieces());
}); });
// Set qBittorrent as creator and add user comment to // Set qBittorrent as creator and add user comment to
// torrent_info structure // torrent_info structure
newTorrent.set_creator(creatorStr.toUtf8().constData()); newTorrent.set_creator("qBittorrent " QBT_VERSION);
newTorrent.set_comment(m_params.comment.toUtf8().constData()); newTorrent.set_comment(m_params.comment.toUtf8().constData());
// Is private ? // Is private ?
newTorrent.set_priv(m_params.isPrivate); newTorrent.set_priv(m_params.isPrivate);
@@ -207,8 +207,7 @@ void TorrentCreatorThread::run()
QFile outfile {m_params.savePath}; QFile outfile {m_params.savePath};
if (!outfile.open(QIODevice::WriteOnly)) if (!outfile.open(QIODevice::WriteOnly))
{ {
throw RuntimeError throw RuntimeError {tr("Create new torrent file failed. Reason: %1")
{tr("Create new torrent file failed. Reason: %1")
.arg(outfile.errorString())}; .arg(outfile.errorString())};
} }
@@ -217,8 +216,7 @@ void TorrentCreatorThread::run()
lt::bencode(Utils::IO::FileDeviceOutputIterator {outfile}, entry); lt::bencode(Utils::IO::FileDeviceOutputIterator {outfile}, entry);
if (outfile.error() != QFileDevice::NoError) if (outfile.error() != QFileDevice::NoError)
{ {
throw RuntimeError throw RuntimeError {tr("Create new torrent file failed. Reason: %1")
{tr("Create new torrent file failed. Reason: %1")
.arg(outfile.errorString())}; .arg(outfile.errorString())};
} }
outfile.close(); outfile.close();

View File

@@ -39,13 +39,15 @@
#include <libtorrent/address.hpp> #include <libtorrent/address.hpp>
#include <libtorrent/alert_types.hpp> #include <libtorrent/alert_types.hpp>
#include <libtorrent/entry.hpp>
#include <libtorrent/magnet_uri.hpp> #include <libtorrent/magnet_uri.hpp>
#include <libtorrent/session.hpp> #include <libtorrent/session.hpp>
#include <libtorrent/storage_defs.hpp> #include <libtorrent/storage_defs.hpp>
#include <libtorrent/time.hpp> #include <libtorrent/time.hpp>
#include <libtorrent/version.hpp> #include <libtorrent/version.hpp>
#include <libtorrent/write_resume_data.hpp>
#if (LIBTORRENT_VERSION_NUM >= 20000)
#include <libtorrent/info_hash.hpp>
#endif
#include <QBitArray> #include <QBitArray>
#include <QDebug> #include <QDebug>
@@ -57,7 +59,6 @@
#include "base/global.h" #include "base/global.h"
#include "base/logger.h" #include "base/logger.h"
#include "base/preferences.h" #include "base/preferences.h"
#include "base/profile.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/utils/string.h" #include "base/utils/string.h"
#include "common.h" #include "common.h"
@@ -87,14 +88,135 @@ namespace
return out; return out;
} }
using ListType = lt::entry::list_type; lt::announce_entry makeNativeAnnouncerEntry(const QString &url, const int tier)
ListType setToEntryList(const QSet<QString> &input)
{ {
ListType entryList; lt::announce_entry entry {url.toStdString()};
for (const QString &setValue : input) entry.tier = tier;
entryList.emplace_back(setValue.toStdString()); return entry;
return entryList; }
#if (LIBTORRENT_VERSION_NUM >= 20000)
TrackerEntry fromNativeAnnouncerEntry(const lt::announce_entry &nativeEntry, const lt::info_hash_t &hashes)
#else
TrackerEntry fromNativeAnnouncerEntry(const lt::announce_entry &nativeEntry)
#endif
{
TrackerEntry trackerEntry {QString::fromStdString(nativeEntry.url), nativeEntry.tier};
int numUpdating = 0;
int numWorking = 0;
int numNotWorking = 0;
#if (LIBTORRENT_VERSION_NUM >= 20000)
const int numEndpoints = nativeEntry.endpoints.size() * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1);
trackerEntry.endpoints.reserve(numEndpoints);
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
{
for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2})
{
if (hashes.has(protocolVersion))
{
const lt::announce_infohash &infoHash = endpoint.info_hashes[protocolVersion];
TrackerEntry::EndpointStats trackerEndpoint;
trackerEndpoint.protocolVersion = (protocolVersion == lt::protocol_version::V1) ? 1 : 2;
trackerEndpoint.numSeeds = infoHash.scrape_complete;
trackerEndpoint.numLeeches = infoHash.scrape_incomplete;
trackerEndpoint.numDownloaded = infoHash.scrape_downloaded;
if (infoHash.updating)
{
trackerEndpoint.status = TrackerEntry::Updating;
++numUpdating;
}
else if (infoHash.fails > 0)
{
trackerEndpoint.status = TrackerEntry::NotWorking;
++numNotWorking;
}
else if (nativeEntry.verified)
{
trackerEndpoint.status = TrackerEntry::Working;
++numWorking;
}
else
{
trackerEndpoint.status = TrackerEntry::NotContacted;
}
trackerEntry.endpoints.append(trackerEndpoint);
trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, infoHash.scrape_complete);
trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, infoHash.scrape_incomplete);
trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, infoHash.scrape_downloaded);
}
}
}
#else
const int numEndpoints = nativeEntry.endpoints.size();
trackerEntry.endpoints.reserve(numEndpoints);
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
{
TrackerEntry::EndpointStats trackerEndpoint;
trackerEndpoint.numSeeds = endpoint.scrape_complete;
trackerEndpoint.numLeeches = endpoint.scrape_incomplete;
trackerEndpoint.numDownloaded = endpoint.scrape_downloaded;
if (endpoint.updating)
{
trackerEndpoint.status = TrackerEntry::Updating;
++numUpdating;
}
else if (endpoint.fails > 0)
{
trackerEndpoint.status = TrackerEntry::NotWorking;
++numNotWorking;
}
else if (nativeEntry.verified)
{
trackerEndpoint.status = TrackerEntry::Working;
++numWorking;
}
else
{
trackerEndpoint.status = TrackerEntry::NotContacted;
}
trackerEntry.endpoints.append(trackerEndpoint);
trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, endpoint.scrape_complete);
trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, endpoint.scrape_incomplete);
trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, endpoint.scrape_downloaded);
}
#endif
if (numEndpoints > 0)
{
if (numUpdating > 0)
trackerEntry.status = TrackerEntry::Updating;
else if (numWorking > 0)
trackerEntry.status = TrackerEntry::Working;
else if (numNotWorking == numEndpoints)
trackerEntry.status = TrackerEntry::NotWorking;
}
return trackerEntry;
}
void initializeStatus(lt::torrent_status &status, const lt::add_torrent_params &params)
{
status.flags = params.flags;
status.active_duration = lt::seconds {params.active_time};
status.finished_duration = lt::seconds {params.finished_time};
status.seeding_duration = lt::seconds {params.seeding_time};
status.num_complete = params.num_complete;
status.num_incomplete = params.num_incomplete;
status.all_time_download = params.total_downloaded;
status.all_time_upload = params.total_uploaded;
status.added_time = params.added_time;
status.last_seen_complete = params.last_seen_complete;
status.last_download = lt::time_point {lt::seconds {params.last_download}};
status.last_upload = lt::time_point {lt::seconds {params.last_upload}};
status.completed_time = params.completed_time;
status.save_path = params.save_path;
status.connections_limit = params.max_connections;
status.pieces = params.have_pieces;
status.verified_pieces = params.verified_pieces;
} }
} }
@@ -106,6 +228,11 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
, m_session(session) , m_session(session)
, m_nativeSession(nativeSession) , m_nativeSession(nativeSession)
, m_nativeHandle(nativeHandle) , m_nativeHandle(nativeHandle)
#if (LIBTORRENT_VERSION_NUM >= 20000)
, m_infoHash(m_nativeHandle.info_hashes())
#else
, m_infoHash(m_nativeHandle.info_hash())
#endif
, m_name(params.name) , m_name(params.name)
, m_savePath(Utils::Fs::toNativePath(params.savePath)) , m_savePath(Utils::Fs::toNativePath(params.savePath))
, m_category(params.category) , m_category(params.category)
@@ -123,7 +250,6 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
if (m_useAutoTMM) if (m_useAutoTMM)
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category)); m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
m_hash = InfoHash {m_nativeHandle.info_hash()};
if (m_ltAddTorrentParams.ti) if (m_ltAddTorrentParams.ti)
{ {
// Initialize it only if torrent is added with metadata. // Initialize it only if torrent is added with metadata.
@@ -131,7 +257,8 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()}; m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
} }
updateStatus(); initializeStatus(m_nativeStatus, m_ltAddTorrentParams);
updateState();
if (hasMetadata()) if (hasMetadata())
applyFirstLastPiecePriority(m_hasFirstLastPiecePriority); applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
@@ -168,9 +295,9 @@ bool TorrentImpl::isValid() const
return m_nativeHandle.is_valid(); return m_nativeHandle.is_valid();
} }
InfoHash TorrentImpl::hash() const InfoHash TorrentImpl::infoHash() const
{ {
return m_hash; return m_infoHash;
} }
QString TorrentImpl::name() const QString TorrentImpl::name() const
@@ -185,7 +312,7 @@ QString TorrentImpl::name() const
if (!name.isEmpty()) if (!name.isEmpty())
return name; return name;
return m_hash; return id().toString();
} }
QDateTime TorrentImpl::creationDate() const QDateTime TorrentImpl::creationDate() const
@@ -284,6 +411,7 @@ void TorrentImpl::setAutoTMMEnabled(bool enabled)
if (m_useAutoTMM == enabled) return; if (m_useAutoTMM == enabled) return;
m_useAutoTMM = enabled; m_useAutoTMM = enabled;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentSavingModeChanged(this); m_session->handleTorrentSavingModeChanged(this);
if (m_useAutoTMM) if (m_useAutoTMM)
@@ -311,7 +439,11 @@ QVector<TrackerEntry> TorrentImpl::trackers() const
entries.reserve(nativeTrackers.size()); entries.reserve(nativeTrackers.size());
for (const lt::announce_entry &tracker : nativeTrackers) for (const lt::announce_entry &tracker : nativeTrackers)
entries << tracker; #if (LIBTORRENT_VERSION_NUM >= 20000)
entries << fromNativeAnnouncerEntry(tracker, m_nativeHandle.info_hashes());
#else
entries << fromNativeAnnouncerEntry(tracker);
#endif
return entries; return entries;
} }
@@ -325,7 +457,7 @@ void TorrentImpl::addTrackers(const QVector<TrackerEntry> &trackers)
{ {
QSet<TrackerEntry> currentTrackers; QSet<TrackerEntry> currentTrackers;
for (const lt::announce_entry &entry : m_nativeHandle.trackers()) for (const lt::announce_entry &entry : m_nativeHandle.trackers())
currentTrackers << entry; currentTrackers.insert({QString::fromStdString(entry.url), entry.tier});
QVector<TrackerEntry> newTrackers; QVector<TrackerEntry> newTrackers;
newTrackers.reserve(trackers.size()); newTrackers.reserve(trackers.size());
@@ -334,13 +466,16 @@ void TorrentImpl::addTrackers(const QVector<TrackerEntry> &trackers)
{ {
if (!currentTrackers.contains(tracker)) if (!currentTrackers.contains(tracker))
{ {
m_nativeHandle.add_tracker(tracker.nativeEntry()); m_nativeHandle.add_tracker(makeNativeAnnouncerEntry(tracker.url, tracker.tier));
newTrackers << tracker; newTrackers << tracker;
} }
} }
if (!newTrackers.isEmpty()) if (!newTrackers.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentTrackersAdded(this, newTrackers); m_session->handleTorrentTrackersAdded(this, newTrackers);
}
} }
void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers) void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
@@ -355,7 +490,7 @@ void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
for (const TrackerEntry &tracker : trackers) for (const TrackerEntry &tracker : trackers)
{ {
nativeTrackers.emplace_back(tracker.nativeEntry()); nativeTrackers.emplace_back(makeNativeAnnouncerEntry(tracker.url, tracker.tier));
if (!currentTrackers.removeOne(tracker)) if (!currentTrackers.removeOne(tracker))
newTrackers << tracker; newTrackers << tracker;
@@ -363,6 +498,8 @@ void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
m_nativeHandle.replace_trackers(nativeTrackers); m_nativeHandle.replace_trackers(nativeTrackers);
m_session->handleTorrentNeedSaveResumeData(this);
if (newTrackers.isEmpty() && currentTrackers.isEmpty()) if (newTrackers.isEmpty() && currentTrackers.isEmpty())
{ {
// when existing tracker reorders // when existing tracker reorders
@@ -414,7 +551,10 @@ void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
} }
if (!addedUrlSeeds.isEmpty()) if (!addedUrlSeeds.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds); m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds);
}
} }
void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds) void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
@@ -435,7 +575,10 @@ void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
} }
if (!removedUrlSeeds.isEmpty()) if (!removedUrlSeeds.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds); m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds);
}
} }
void TorrentImpl::clearPeers() void TorrentImpl::clearPeers()
@@ -467,9 +610,7 @@ bool TorrentImpl::connectPeer(const PeerAddress &peerAddress)
bool TorrentImpl::needSaveResumeData() const bool TorrentImpl::needSaveResumeData() const
{ {
if (m_isStopped && !(m_nativeStatus.flags & lt::torrent_flags::auto_managed)) return m_nativeHandle.need_save_resume_data();
return false;
return m_nativeStatus.need_save_resume;
} }
void TorrentImpl::saveResumeData() void TorrentImpl::saveResumeData()
@@ -548,6 +689,7 @@ bool TorrentImpl::addTag(const QString &tag)
if (!m_session->addTag(tag)) if (!m_session->addTag(tag))
return false; return false;
m_tags.insert(tag); m_tags.insert(tag);
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentTagAdded(this, tag); m_session->handleTorrentTagAdded(this, tag);
return true; return true;
} }
@@ -558,6 +700,7 @@ bool TorrentImpl::removeTag(const QString &tag)
{ {
if (m_tags.remove(tag)) if (m_tags.remove(tag))
{ {
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentTagRemoved(this, tag); m_session->handleTorrentTagRemoved(this, tag);
return true; return true;
} }
@@ -818,9 +961,7 @@ bool TorrentImpl::hasFilteredPieces() const
int TorrentImpl::queuePosition() const int TorrentImpl::queuePosition() const
{ {
if (m_nativeStatus.queue_position < lt::queue_position_t {0}) return 0; return static_cast<int>(m_nativeStatus.queue_position);
return static_cast<int>(m_nativeStatus.queue_position) + 1;
} }
QString TorrentImpl::error() const QString TorrentImpl::error() const
@@ -1150,6 +1291,7 @@ void TorrentImpl::setName(const QString &name)
if (m_name != name) if (m_name != name)
{ {
m_name = name; m_name = name;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentNameChanged(this); m_session->handleTorrentNameChanged(this);
} }
} }
@@ -1163,6 +1305,7 @@ bool TorrentImpl::setCategory(const QString &category)
const QString oldCategory = m_category; const QString oldCategory = m_category;
m_category = category; m_category = category;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentCategoryChanged(this, oldCategory); m_session->handleTorrentCategoryChanged(this, oldCategory);
if (m_useAutoTMM) if (m_useAutoTMM)
@@ -1182,6 +1325,7 @@ void TorrentImpl::move(QString path)
if (m_useAutoTMM) if (m_useAutoTMM)
{ {
m_useAutoTMM = false; m_useAutoTMM = false;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentSavingModeChanged(this); m_session->handleTorrentSavingModeChanged(this);
} }
@@ -1197,8 +1341,8 @@ void TorrentImpl::move(QString path)
void TorrentImpl::move_impl(QString path, const MoveStorageMode mode) void TorrentImpl::move_impl(QString path, const MoveStorageMode mode)
{ {
if (path == savePath()) return; if (path == savePath()) return;
path = Utils::Fs::toNativePath(path);
path = Utils::Fs::toNativePath(path);
if (!useTempPath()) if (!useTempPath())
{ {
moveStorage(path, mode); moveStorage(path, mode);
@@ -1206,6 +1350,7 @@ void TorrentImpl::move_impl(QString path, const MoveStorageMode mode)
else else
{ {
m_savePath = path; m_savePath = path;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentSavePathChanged(this); m_session->handleTorrentSavePathChanged(this);
} }
} }
@@ -1249,7 +1394,7 @@ void TorrentImpl::setSequentialDownload(const bool enable)
m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
} }
saveResumeData(); m_session->handleTorrentNeedSaveResumeData(this);
} }
void TorrentImpl::setFirstLastPiecePriority(const bool enabled) void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
@@ -1264,7 +1409,7 @@ void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
LogMsg(tr("Download first and last piece first: %1, torrent: '%2'") LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
.arg((enabled ? tr("On") : tr("Off")), name())); .arg((enabled ? tr("On") : tr("Off")), name()));
saveResumeData(); m_session->handleTorrentNeedSaveResumeData(this);
} }
void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector<DownloadPriority> &updatedFilePrio) void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector<DownloadPriority> &updatedFilePrio)
@@ -1364,6 +1509,7 @@ void TorrentImpl::pause()
if (!m_isStopped) if (!m_isStopped)
{ {
m_isStopped = true; m_isStopped = true;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentPaused(this); m_session->handleTorrentPaused(this);
} }
@@ -1387,6 +1533,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
{ {
m_hasMissingFiles = false; m_hasMissingFiles = false;
m_isStopped = false; m_isStopped = false;
m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
reload(); reload();
updateStatus(); updateStatus();
return; return;
@@ -1399,6 +1546,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready); m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready);
m_isStopped = false; m_isStopped = false;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentResumed(this); m_session->handleTorrentResumed(this);
} }
@@ -1434,6 +1582,7 @@ void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob) void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
{ {
m_session->handleTorrentNeedSaveResumeData(this);
m_storageIsMoving = hasOutstandingJob; m_storageIsMoving = hasOutstandingJob;
updateStatus(); updateStatus();
@@ -1444,10 +1593,21 @@ void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
m_session->handleTorrentSavePathChanged(this); m_session->handleTorrentSavePathChanged(this);
} }
saveResumeData(); if (!m_storageIsMoving)
{
if (m_hasMissingFiles)
{
// it can be moved to the proper location
m_hasMissingFiles = false;
m_ltAddTorrentParams.save_path = m_nativeStatus.save_path;
m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
reload();
updateStatus();
}
while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()(); m_moveFinishedTriggers.takeFirst()();
}
} }
void TorrentImpl::handleTrackerReplyAlert(const lt::tracker_reply_alert *p) void TorrentImpl::handleTrackerReplyAlert(const lt::tracker_reply_alert *p)
@@ -1484,9 +1644,9 @@ void TorrentImpl::handleTrackerErrorAlert(const lt::tracker_error_alert *p)
const QVector<TrackerEntry> trackerList = trackers(); const QVector<TrackerEntry> trackerList = trackers();
const auto iter = std::find_if(trackerList.cbegin(), trackerList.cend(), [&trackerUrl](const TrackerEntry &entry) const auto iter = std::find_if(trackerList.cbegin(), trackerList.cend(), [&trackerUrl](const TrackerEntry &entry)
{ {
return (entry.url() == trackerUrl); return (entry.url == trackerUrl);
}); });
if ((iter != trackerList.cend()) && (iter->status() == TrackerEntry::NotWorking)) if ((iter != trackerList.cend()) && (iter->status == TrackerEntry::NotWorking))
m_session->handleTorrentTrackerError(this, trackerUrl); m_session->handleTorrentTrackerError(this, trackerUrl);
} }
@@ -1503,7 +1663,8 @@ void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
return; return;
} }
saveResumeData(); if (m_nativeHandle.need_save_resume_data())
m_session->handleTorrentNeedSaveResumeData(this);
if (m_fastresumeDataRejected && !m_hasMissingFiles) if (m_fastresumeDataRejected && !m_hasMissingFiles)
m_fastresumeDataRejected = false; m_fastresumeDataRejected = false;
@@ -1538,6 +1699,8 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
adjustActualSavePath(); adjustActualSavePath();
manageIncompleteFiles(); manageIncompleteFiles();
m_session->handleTorrentNeedSaveResumeData(this);
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion(); const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
if (isMoveInProgress() || (m_renameCount > 0)) if (isMoveInProgress() || (m_renameCount > 0))
{ {
@@ -1565,35 +1728,25 @@ void TorrentImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p)
void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p) void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
{ {
if (!m_hasMissingFiles) if (m_hasMissingFiles)
{
const auto havePieces = m_ltAddTorrentParams.have_pieces;
const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
// Update recent resume data but preserve existing progress
m_ltAddTorrentParams = p->params;
m_ltAddTorrentParams.have_pieces = havePieces;
m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
m_ltAddTorrentParams.verified_pieces = verifiedPieces;
}
else
{ {
// Update recent resume data // Update recent resume data
m_ltAddTorrentParams = p->params; m_ltAddTorrentParams = p->params;
} }
if (m_isStopped)
{
m_ltAddTorrentParams.flags |= lt::torrent_flags::paused;
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::auto_managed;
}
else
{
// Torrent can be actually "running" but temporarily "paused" to perform some
// service jobs behind the scenes so we need to restore it as "running"
if (m_operatingMode == TorrentOperatingMode::AutoManaged)
{
m_ltAddTorrentParams.flags |= lt::torrent_flags::auto_managed;
}
else
{
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::paused;
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::auto_managed;
}
}
m_ltAddTorrentParams.added_time = addedTime().toSecsSinceEpoch(); m_ltAddTorrentParams.added_time = addedTime().toSecsSinceEpoch();
m_ltAddTorrentParams.save_path = Profile::instance()->toPortablePath(
QString::fromStdString(m_ltAddTorrentParams.save_path)).toStdString();
if (m_maintenanceJob == MaintenanceJob::HandleMetadata) if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
{ {
@@ -1606,37 +1759,21 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
m_session->findIncompleteFiles(metadata, m_savePath); m_session->findIncompleteFiles(metadata, m_savePath);
} }
auto resumeDataPtr = std::make_shared<lt::entry>(lt::write_resume_data(m_ltAddTorrentParams)); LoadTorrentParams resumeData;
lt::entry &resumeData = *resumeDataPtr; resumeData.name = m_name;
resumeData.category = m_category;
resumeData.savePath = m_useAutoTMM ? "" : m_savePath;
resumeData.tags = m_tags;
resumeData.contentLayout = m_contentLayout;
resumeData.ratioLimit = m_ratioLimit;
resumeData.seedingTimeLimit = m_seedingTimeLimit;
resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
resumeData.hasSeedStatus = m_hasSeedStatus;
resumeData.paused = m_isStopped;
resumeData.forced = (m_operatingMode == TorrentOperatingMode::Forced);
resumeData.ltAddTorrentParams = m_ltAddTorrentParams;
// TODO: The following code is deprecated. Remove after several releases in 4.3.x. m_session->handleTorrentResumeDataReady(this, resumeData);
// === BEGIN DEPRECATED CODE === //
const bool useDummyResumeData = !hasMetadata();
if (useDummyResumeData)
{
updateStatus();
resumeData["qBt-magnetUri"] = createMagnetURI().toStdString();
// sequentialDownload needs to be stored in the
// resume data if there is no metadata, otherwise they won't be
// restored if qBittorrent quits before the metadata are retrieved:
resumeData["qBt-sequential"] = isSequentialDownload();
resumeData["qBt-addedTime"] = addedTime().toSecsSinceEpoch();
}
// === END DEPRECATED CODE === //
resumeData["qBt-savePath"] = m_useAutoTMM ? "" : Profile::instance()->toPortablePath(m_savePath).toStdString();
resumeData["qBt-ratioLimit"] = static_cast<int>(m_ratioLimit * 1000);
resumeData["qBt-seedingTimeLimit"] = m_seedingTimeLimit;
resumeData["qBt-category"] = m_category.toStdString();
resumeData["qBt-tags"] = setToEntryList(m_tags);
resumeData["qBt-name"] = m_name.toStdString();
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
resumeData["qBt-contentLayout"] = Utils::String::fromEnum(m_contentLayout).toStdString();
resumeData["qBt-firstLastPiecePriority"] = m_hasFirstLastPiecePriority;
m_session->handleTorrentResumeDataReady(this, resumeDataPtr);
} }
void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p) void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
@@ -1702,8 +1839,7 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()(); m_moveFinishedTriggers.takeFirst()();
if (isPaused() && (m_renameCount == 0)) m_session->handleTorrentNeedSaveResumeData(this);
saveResumeData(); // otherwise the new path will not be saved
} }
void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p) void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
@@ -1720,8 +1856,7 @@ void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty()) while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()(); m_moveFinishedTriggers.takeFirst()();
if (isPaused() && (m_renameCount == 0)) m_session->handleTorrentNeedSaveResumeData(this);
saveResumeData(); // otherwise the new path will not be saved
} }
void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p) void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
@@ -1743,11 +1878,10 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
void TorrentImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p) void TorrentImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
{ {
Q_UNUSED(p); Q_UNUSED(p);
qDebug("Metadata received for torrent %s.", qUtf8Printable(name())); qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
m_maintenanceJob = MaintenanceJob::HandleMetadata; m_maintenanceJob = MaintenanceJob::HandleMetadata;
saveResumeData(); m_session->handleTorrentNeedSaveResumeData(this);
} }
void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
@@ -1945,6 +2079,7 @@ void TorrentImpl::setRatioLimit(qreal limit)
if (m_ratioLimit != limit) if (m_ratioLimit != limit)
{ {
m_ratioLimit = limit; m_ratioLimit = limit;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentShareLimitChanged(this); m_session->handleTorrentShareLimitChanged(this);
} }
} }
@@ -1959,6 +2094,7 @@ void TorrentImpl::setSeedingTimeLimit(int limit)
if (m_seedingTimeLimit != limit) if (m_seedingTimeLimit != limit)
{ {
m_seedingTimeLimit = limit; m_seedingTimeLimit = limit;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentShareLimitChanged(this); m_session->handleTorrentShareLimitChanged(this);
} }
} }
@@ -1969,7 +2105,7 @@ void TorrentImpl::setUploadLimit(const int limit)
return; return;
m_nativeHandle.set_upload_limit(limit); m_nativeHandle.set_upload_limit(limit);
saveResumeData(); m_session->handleTorrentNeedSaveResumeData(this);
} }
void TorrentImpl::setDownloadLimit(const int limit) void TorrentImpl::setDownloadLimit(const int limit)
@@ -1978,7 +2114,7 @@ void TorrentImpl::setDownloadLimit(const int limit)
return; return;
m_nativeHandle.set_download_limit(limit); m_nativeHandle.set_download_limit(limit);
saveResumeData(); m_session->handleTorrentNeedSaveResumeData(this);
} }
void TorrentImpl::setSuperSeeding(const bool enable) void TorrentImpl::setSuperSeeding(const bool enable)
@@ -1990,7 +2126,8 @@ void TorrentImpl::setSuperSeeding(const bool enable)
m_nativeHandle.set_flags(lt::torrent_flags::super_seeding); m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
else else
m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding); m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
} }
void TorrentImpl::setDHTDisabled(const bool disable) void TorrentImpl::setDHTDisabled(const bool disable)
@@ -2002,7 +2139,8 @@ void TorrentImpl::setDHTDisabled(const bool disable)
m_nativeHandle.set_flags(lt::torrent_flags::disable_dht); m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
else else
m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht); m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
} }
void TorrentImpl::setPEXDisabled(const bool disable) void TorrentImpl::setPEXDisabled(const bool disable)
@@ -2014,7 +2152,8 @@ void TorrentImpl::setPEXDisabled(const bool disable)
m_nativeHandle.set_flags(lt::torrent_flags::disable_pex); m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
else else
m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex); m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
} }
void TorrentImpl::setLSDDisabled(const bool disable) void TorrentImpl::setLSDDisabled(const bool disable)
@@ -2026,7 +2165,8 @@ void TorrentImpl::setLSDDisabled(const bool disable)
m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd); m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
else else
m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd); m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
} }
void TorrentImpl::flushCache() const void TorrentImpl::flushCache() const

View File

@@ -68,7 +68,6 @@ namespace BitTorrent
bool forced = false; bool forced = false;
bool paused = false; bool paused = false;
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
@@ -99,7 +98,7 @@ namespace BitTorrent
bool isValid() const; bool isValid() const;
InfoHash hash() const override; InfoHash infoHash() const override;
QString name() const override; QString name() const override;
QDateTime creationDate() const override; QDateTime creationDate() const override;
QString creator() const override; QString creator() const override;
@@ -301,7 +300,7 @@ namespace BitTorrent
TorrentInfo m_torrentInfo; TorrentInfo m_torrentInfo;
SpeedMonitor m_speedMonitor; SpeedMonitor m_speedMonitor;
InfoHash m_hash; InfoHash m_infoHash;
// 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

View File

@@ -184,10 +184,15 @@ bool TorrentInfo::isValid() const
return (m_nativeInfo && m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0)); return (m_nativeInfo && m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
} }
InfoHash TorrentInfo::hash() const InfoHash TorrentInfo::infoHash() const
{ {
if (!isValid()) return {}; if (!isValid()) return {};
#if (LIBTORRENT_VERSION_NUM >= 20000)
return m_nativeInfo->info_hashes();
#else
return m_nativeInfo->info_hash(); return m_nativeInfo->info_hash();
#endif
} }
QString TorrentInfo::name() const QString TorrentInfo::name() const
@@ -302,7 +307,7 @@ QVector<TrackerEntry> TorrentInfo::trackers() const
ret.reserve(trackers.size()); ret.reserve(trackers.size());
for (const lt::announce_entry &tracker : trackers) for (const lt::announce_entry &tracker : trackers)
ret.append(tracker); ret.append({QString::fromStdString(tracker.url)});
return ret; return ret;
} }
@@ -374,7 +379,7 @@ QVector<QByteArray> TorrentInfo::pieceHashes() const
hashes.reserve(count); hashes.reserve(count);
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
hashes += {m_nativeInfo->hash_for_piece_ptr(lt::piece_index_t {i}), InfoHash::length()}; hashes += {m_nativeInfo->hash_for_piece_ptr(lt::piece_index_t {i}), SHA1Hash::length()};
return hashes; return hashes;
} }
@@ -481,9 +486,13 @@ void TorrentInfo::stripRootFolder()
void TorrentInfo::addRootFolder() void TorrentInfo::addRootFolder()
{ {
const QString rootFolder = name(); const QString originalName = name();
Q_ASSERT(!rootFolder.isEmpty()); Q_ASSERT(!originalName.isEmpty());
const QString extension = Utils::Fs::fileExtension(originalName);
const QString rootFolder = extension.isEmpty()
? originalName
: originalName.chopped(extension.size() + 1);
const std::string rootPrefix = Utils::Fs::toNativePath(rootFolder + QLatin1Char {'/'}).toStdString(); const std::string rootPrefix = Utils::Fs::toNativePath(rootFolder + QLatin1Char {'/'}).toStdString();
lt::file_storage files = m_nativeInfo->files(); lt::file_storage files = m_nativeInfo->files();
files.set_name(rootFolder.toStdString()); files.set_name(rootFolder.toStdString());

View File

@@ -45,7 +45,7 @@ class QUrl;
namespace BitTorrent namespace BitTorrent
{ {
class InfoHash; class InfoHash;
class TrackerEntry; struct TrackerEntry;
class TorrentInfo final : public AbstractFileStorage class TorrentInfo final : public AbstractFileStorage
{ {
@@ -62,7 +62,7 @@ namespace BitTorrent
TorrentInfo &operator=(const TorrentInfo &other); TorrentInfo &operator=(const TorrentInfo &other);
bool isValid() const; bool isValid() const;
InfoHash hash() const; InfoHash infoHash() const;
QString name() const; QString name() const;
QDateTime creationDate() const; QDateTime creationDate() const;
QString creator() const; QString creator() const;

View File

@@ -153,7 +153,7 @@ struct Tracker::TrackerAnnounceRequest
{ {
QHostAddress socketAddress; QHostAddress socketAddress;
QByteArray claimedAddress; // self claimed by peer QByteArray claimedAddress; // self claimed by peer
InfoHash infoHash; TorrentID torrentID;
QString event; QString event;
Peer peer; Peer peer;
int numwant = 50; int numwant = 50;
@@ -295,11 +295,11 @@ void Tracker::processAnnounceRequest()
if (infoHashIter == queryParams.end()) if (infoHashIter == queryParams.end())
throw TrackerError("Missing \"info_hash\" parameter"); throw TrackerError("Missing \"info_hash\" parameter");
const InfoHash infoHash(infoHashIter->toHex()); const auto torrentID = TorrentID::fromString(infoHashIter->toHex());
if (!infoHash.isValid()) if (!torrentID.isValid())
throw TrackerError("Invalid \"info_hash\" parameter"); throw TrackerError("Invalid \"info_hash\" parameter");
announceReq.infoHash = infoHash; announceReq.torrentID = torrentID;
// 2. peer_id // 2. peer_id
const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID); const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID);
@@ -381,19 +381,19 @@ void Tracker::processAnnounceRequest()
void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq) void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
{ {
if (!m_torrents.contains(announceReq.infoHash)) if (!m_torrents.contains(announceReq.torrentID))
{ {
// Reached max size, remove a random torrent // Reached max size, remove a random torrent
if (m_torrents.size() >= MAX_TORRENTS) if (m_torrents.size() >= MAX_TORRENTS)
m_torrents.erase(m_torrents.begin()); m_torrents.erase(m_torrents.begin());
} }
m_torrents[announceReq.infoHash].setPeer(announceReq.peer); m_torrents[announceReq.torrentID].setPeer(announceReq.peer);
} }
void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq) void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
{ {
const auto torrentStatsIter = m_torrents.find(announceReq.infoHash); const auto torrentStatsIter = m_torrents.find(announceReq.torrentID);
if (torrentStatsIter == m_torrents.end()) if (torrentStatsIter == m_torrents.end())
return; return;
@@ -405,7 +405,7 @@ void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq) void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
{ {
const TorrentStats &torrentStats = m_torrents[announceReq.infoHash]; const TorrentStats &torrentStats = m_torrents[announceReq.torrentID];
lt::entry::dictionary_type replyDict lt::entry::dictionary_type replyDict
{ {

View File

@@ -102,6 +102,6 @@ namespace BitTorrent
Http::Request m_request; Http::Request m_request;
Http::Environment m_env; Http::Environment m_env;
QHash<InfoHash, TorrentStats> m_torrents; QHash<TorrentID, TorrentStats> m_torrents;
}; };
} }

View File

@@ -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, 2021 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
@@ -28,139 +28,15 @@
#include "trackerentry.h" #include "trackerentry.h"
#include <algorithm>
#include <libtorrent/version.hpp>
#include <QString>
#include <QUrl> #include <QUrl>
using namespace BitTorrent;
TrackerEntry::TrackerEntry(const QString &url)
: m_nativeEntry(url.toStdString())
{
}
TrackerEntry::TrackerEntry(const lt::announce_entry &nativeEntry)
: m_nativeEntry(nativeEntry)
{
}
QString TrackerEntry::url() const
{
return QString::fromStdString(nativeEntry().url);
}
int TrackerEntry::tier() const
{
return nativeEntry().tier;
}
TrackerEntry::Status TrackerEntry::status() const
{
const auto &endpoints = nativeEntry().endpoints;
const bool allFailed = !endpoints.empty() && std::all_of(endpoints.begin(), endpoints.end()
, [](const lt::announce_endpoint &endpoint)
{
#if (LIBTORRENT_VERSION_NUM >= 20000)
return std::all_of(endpoint.info_hashes.begin(), endpoint.info_hashes.end()
, [](const lt::announce_infohash &infohash)
{
return (infohash.fails > 0);
});
#else
return (endpoint.fails > 0);
#endif
});
if (allFailed)
return NotWorking;
const bool isUpdating = std::any_of(endpoints.begin(), endpoints.end()
, [](const lt::announce_endpoint &endpoint)
{
#if (LIBTORRENT_VERSION_NUM >= 20000)
return std::any_of(endpoint.info_hashes.begin(), endpoint.info_hashes.end()
, [](const lt::announce_infohash &infohash)
{
return infohash.updating;
});
#else
return endpoint.updating;
#endif
});
if (isUpdating)
return Updating;
if (!nativeEntry().verified)
return NotContacted;
return Working;
}
void TrackerEntry::setTier(const int value)
{
m_nativeEntry.tier = value;
}
int TrackerEntry::numSeeds() const
{
int value = -1;
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
{
#if (LIBTORRENT_VERSION_NUM >= 20000)
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
value = std::max(value, infoHash.scrape_complete);
#else
value = std::max(value, endpoint.scrape_complete);
#endif
}
return value;
}
int TrackerEntry::numLeeches() const
{
int value = -1;
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
{
#if (LIBTORRENT_VERSION_NUM >= 20000)
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
value = std::max(value, infoHash.scrape_incomplete);
#else
value = std::max(value, endpoint.scrape_incomplete);
#endif
}
return value;
}
int TrackerEntry::numDownloaded() const
{
int value = -1;
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
{
#if (LIBTORRENT_VERSION_NUM >= 20000)
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
value = std::max(value, infoHash.scrape_downloaded);
#else
value = std::max(value, endpoint.scrape_downloaded);
#endif
}
return value;
}
const lt::announce_entry &TrackerEntry::nativeEntry() const
{
return m_nativeEntry;
}
bool BitTorrent::operator==(const TrackerEntry &left, const TrackerEntry &right) bool BitTorrent::operator==(const TrackerEntry &left, const TrackerEntry &right)
{ {
return ((left.tier() == right.tier()) return ((left.tier == right.tier)
&& QUrl(left.url()) == QUrl(right.url())); && QUrl(left.url) == QUrl(right.url));
} }
uint BitTorrent::qHash(const TrackerEntry &key, const uint seed) uint BitTorrent::qHash(const TrackerEntry &key, const uint seed)
{ {
return (::qHash(key.url(), seed) ^ ::qHash(key.tier())); return (::qHash(key.url, seed) ^ ::qHash(key.tier));
} }

View File

@@ -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, 2021 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
@@ -28,17 +28,14 @@
#pragma once #pragma once
#include <libtorrent/announce_entry.hpp>
#include <QtGlobal> #include <QtGlobal>
#include <QString>
class QString; #include <QVector>
namespace BitTorrent namespace BitTorrent
{ {
class TrackerEntry struct TrackerEntry
{ {
public:
enum Status enum Status
{ {
NotContacted = 1, NotContacted = 1,
@@ -47,26 +44,26 @@ namespace BitTorrent
NotWorking = 4 NotWorking = 4
}; };
TrackerEntry() = default; struct EndpointStats
TrackerEntry(const QString &url); {
TrackerEntry(const lt::announce_entry &nativeEntry); int protocolVersion = 1;
TrackerEntry(const TrackerEntry &other) = default;
TrackerEntry &operator=(const TrackerEntry &other) = default;
QString url() const; Status status = NotContacted;
Status status() const; int numSeeds = -1;
int numLeeches = -1;
int numDownloaded = -1;
};
int tier() const; QString url;
void setTier(int value); int tier = 0;
int numSeeds() const; QVector<EndpointStats> endpoints {};
int numLeeches() const;
int numDownloaded() const;
const lt::announce_entry &nativeEntry() const; // Deprecated fields
Status status = NotContacted;
private: int numSeeds = -1;
lt::announce_entry m_nativeEntry; int numLeeches = -1;
int numDownloaded = -1;
}; };
bool operator==(const TrackerEntry &left, const TrackerEntry &right); bool operator==(const TrackerEntry &left, const TrackerEntry &right);

121
src/base/digest32.h Normal file
View File

@@ -0,0 +1,121 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2021 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 <libtorrent/sha1_hash.hpp>
#include <QByteArray>
#include <QHash>
#include <QString>
template <int N>
class Digest32
{
public:
using UnderlyingType = lt::digest32<N>;
Digest32() = default;
Digest32(const Digest32 &other) = default;
Digest32(const UnderlyingType &nativeDigest)
: m_valid {true}
, m_nativeDigest {nativeDigest}
{
const QByteArray raw = QByteArray::fromRawData(nativeDigest.data(), length());
m_hashString = QString::fromLatin1(raw.toHex());
}
static constexpr int length()
{
return UnderlyingType::size();
}
bool isValid() const
{
return m_valid;
}
operator UnderlyingType() const
{
return m_nativeDigest;
}
static Digest32 fromString(const QString &digestString)
{
if (digestString.size() != (length() * 2))
return {};
const QByteArray raw = QByteArray::fromHex(digestString.toLatin1());
if (raw.size() != length()) // QByteArray::fromHex() will skip over invalid characters
return {};
Digest32 result;
result.m_valid = true;
result.m_hashString = digestString;
result.m_nativeDigest.assign(raw.constData());
return result;
}
QString toString() const
{
return m_hashString;
}
private:
bool m_valid = false;
UnderlyingType m_nativeDigest;
QString m_hashString;
};
template <int N>
bool operator==(const Digest32<N> &left, const Digest32<N> &right)
{
return (static_cast<typename Digest32<N>::UnderlyingType>(left)
== static_cast<typename Digest32<N>::UnderlyingType>(right));
}
template <int N>
bool operator!=(const Digest32<N> &left, const Digest32<N> &right)
{
return !(left == right);
}
template <int N>
bool operator<(const Digest32<N> &left, const Digest32<N> &right)
{
return static_cast<typename Digest32<N>::UnderlyingType>(left)
< static_cast<typename Digest32<N>::UnderlyingType>(right);
}
template <int N>
uint qHash(const Digest32<N> &key, const uint seed)
{
return ::qHash(std::hash<typename Digest32<N>::UnderlyingType>()(key), seed);
}

View File

@@ -37,7 +37,6 @@
#include <QDebug> #include <QDebug>
#include <QHostInfo> #include <QHostInfo>
#include <QStringList> #include <QStringList>
#include <QTextCodec>
#ifndef QT_NO_OPENSSL #ifndef QT_NO_OPENSSL
#include <QSslSocket> #include <QSslSocket>
@@ -89,6 +88,14 @@ namespace
return hostname.toLocal8Bit(); return hostname.toLocal8Bit();
} }
bool canEncodeAsLatin1(const QStringView string)
{
return std::none_of(string.cbegin(), string.cend(), [](const QChar &ch)
{
return ch > QChar(0xff);
});
}
} // namespace } // namespace
using namespace Net; using namespace Net;
@@ -137,11 +144,10 @@ Smtp::~Smtp()
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body) void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body)
{ {
const Preferences *const pref = Preferences::instance(); const Preferences *const pref = Preferences::instance();
QTextCodec *latin1 = QTextCodec::codecForName("latin1");
m_message = "Date: " + getCurrentDateTime().toLatin1() + "\r\n" m_message = "Date: " + getCurrentDateTime().toLatin1() + "\r\n"
+ encodeMimeHeader("From", from, latin1) + encodeMimeHeader("From", from)
+ encodeMimeHeader("Subject", subject, latin1) + encodeMimeHeader("Subject", subject)
+ encodeMimeHeader("To", to, latin1) + encodeMimeHeader("To", to)
+ "MIME-Version: 1.0\r\n" + "MIME-Version: 1.0\r\n"
+ "Content-Type: text/plain; charset=UTF-8\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n"
+ "Content-Transfer-Encoding: base64\r\n" + "Content-Transfer-Encoding: base64\r\n"
@@ -312,12 +318,12 @@ void Smtp::readyRead()
} }
} }
QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, const QTextCodec *latin1, const QByteArray &prefix) QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, const QByteArray &prefix)
{ {
QByteArray rv = ""; QByteArray rv = "";
QByteArray line = key.toLatin1() + ": "; QByteArray line = key.toLatin1() + ": ";
if (!prefix.isEmpty()) line += prefix; if (!prefix.isEmpty()) line += prefix;
if (!value.contains("=?") && latin1->canEncode(value)) if (!value.contains("=?") && canEncodeAsLatin1(value))
{ {
bool firstWord = true; bool firstWord = true;
for (const QByteArray &word : asConst(value.toLatin1().split(' '))) for (const QByteArray &word : asConst(value.toLatin1().split(' ')))

View File

@@ -88,7 +88,7 @@ namespace Net
AuthCramMD5 AuthCramMD5
}; };
QByteArray encodeMimeHeader(const QString &key, const QString &value, const QTextCodec *latin1, const QByteArray &prefix = {}); QByteArray encodeMimeHeader(const QString &key, const QString &value, const QByteArray &prefix = {});
void ehlo(); void ehlo();
void helo(); void helo();
void parseEhloResponse(const QByteArray &code, bool continued, const QString &line); void parseEhloResponse(const QByteArray &code, bool continued, const QString &line);

View File

@@ -234,6 +234,16 @@ void Preferences::setCloseToTrayNotified(const bool b)
{ {
setValue("Preferences/General/CloseToTrayNotified", b); setValue("Preferences/General/CloseToTrayNotified", b);
} }
bool Preferences::iconsInMenusEnabled() const
{
return value("Preferences/Advanced/EnableIconsInMenus", true).toBool();
}
void Preferences::setIconsInMenusEnabled(const bool enable)
{
setValue("Preferences/Advanced/EnableIconsInMenus", enable);
}
#endif // Q_OS_MACOS #endif // Q_OS_MACOS
bool Preferences::isToolbarDisplayed() const bool Preferences::isToolbarDisplayed() const
@@ -853,7 +863,7 @@ void Preferences::setAutoRunProgram(const QString &program)
setValue("AutoRun/program", program); setValue("AutoRun/program", program);
} }
#if defined(Q_OS_WIN) && (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) #if defined(Q_OS_WIN)
bool Preferences::isAutoRunConsoleEnabled() const bool Preferences::isAutoRunConsoleEnabled() const
{ {
return value("AutoRun/ConsoleEnabled", false).toBool(); return value("AutoRun/ConsoleEnabled", false).toBool();

View File

@@ -248,7 +248,7 @@ public:
void setAutoRunEnabled(bool enabled); void setAutoRunEnabled(bool enabled);
QString getAutoRunProgram() const; QString getAutoRunProgram() const;
void setAutoRunProgram(const QString &program); void setAutoRunProgram(const QString &program);
#if defined(Q_OS_WIN) && (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) #if defined(Q_OS_WIN)
bool isAutoRunConsoleEnabled() const; bool isAutoRunConsoleEnabled() const;
void setAutoRunConsoleEnabled(bool enabled); void setAutoRunConsoleEnabled(bool enabled);
#endif #endif
@@ -313,6 +313,8 @@ public:
void setCloseToTrayNotified(bool b); void setCloseToTrayNotified(bool b);
TrayIcon::Style trayIconStyle() const; TrayIcon::Style trayIconStyle() const;
void setTrayIconStyle(TrayIcon::Style style); void setTrayIconStyle(TrayIcon::Style style);
bool iconsInMenusEnabled() const;
void setIconsInMenusEnabled(bool enable);
#endif // Q_OS_MACOS #endif // Q_OS_MACOS
// Stuff that don't appear in the Options GUI but are saved // Stuff that don't appear in the Options GUI but are saved

View File

@@ -213,10 +213,8 @@ QRegularExpression AutoDownloadRule::cachedRegex(const QString &expression, cons
QRegularExpression &regex = m_dataPtr->cachedRegexes[expression]; QRegularExpression &regex = m_dataPtr->cachedRegexes[expression];
if (regex.pattern().isEmpty()) if (regex.pattern().isEmpty())
{ {
regex = QRegularExpression const QString pattern = (isRegex ? expression : Utils::String::wildcardToRegexPattern(expression));
{ regex = QRegularExpression {pattern, QRegularExpression::CaseInsensitiveOption};
(isRegex ? expression : Utils::String::wildcardToRegex(expression))
, QRegularExpression::CaseInsensitiveOption};
} }
return regex; return regex;

View File

@@ -34,7 +34,7 @@
#include <QGlobalStatic> #include <QGlobalStatic>
#include <QHash> #include <QHash>
#include <QMetaObject> #include <QMetaObject>
#include <QRegExp> #include <QRegularExpression>
#include <QStringList> #include <QStringList>
#include <QVariant> #include <QVariant>
#include <QXmlStreamEntityResolver> #include <QXmlStreamEntityResolver>
@@ -391,12 +391,13 @@ namespace
int nmin = 8; int nmin = 8;
int nsec = 9; int nsec = 9;
// Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm" // Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm"
QRegExp rx("^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$"); QRegularExpression rx {"^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$"};
QRegularExpressionMatch rxMatch;
QStringList parts; QStringList parts;
if (!str.indexOf(rx)) if (str.indexOf(rx, 0, &rxMatch) == 0)
{ {
// Check that if date has '-' separators, both separators are '-'. // Check that if date has '-' separators, both separators are '-'.
parts = rx.capturedTexts(); parts = rxMatch.capturedTexts();
const bool h1 = (parts[3] == QLatin1String("-")); const bool h1 = (parts[3] == QLatin1String("-"));
const bool h2 = (parts[5] == QLatin1String("-")); const bool h2 = (parts[5] == QLatin1String("-"));
if (h1 != h2) if (h1 != h2)
@@ -405,9 +406,10 @@ namespace
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 = QRegExp("^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$"); rx = QRegularExpression {"^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$"};
if (str.indexOf(rx)) if (str.indexOf(rx, 0, &rxMatch) != 0)
return QDateTime::currentDateTime(); return QDateTime::currentDateTime();
nyear = 7; nyear = 7;
nmonth = 2; nmonth = 2;
nday = 3; nday = 3;
@@ -415,7 +417,7 @@ namespace
nhour = 4; nhour = 4;
nmin = 5; nmin = 5;
nsec = 6; nsec = 6;
parts = rx.capturedTexts(); parts = rxMatch.capturedTexts();
} }
bool ok[4]; bool ok[4];
@@ -463,11 +465,11 @@ namespace
bool negOffset = false; bool negOffset = false;
if (parts.count() > 10) if (parts.count() > 10)
{ {
rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$"); rx = QRegularExpression {"^([+-])(\\d\\d)(\\d\\d)$"};
if (!parts[10].indexOf(rx)) if (parts[10].indexOf(rx, 0, &rxMatch) == 0)
{ {
// It's a UTC offset ±hhmm // It's a UTC offset ±hhmm
parts = rx.capturedTexts(); parts = rxMatch.capturedTexts();
offset = parts[2].toInt(&ok[0]) * 3600; offset = parts[2].toInt(&ok[0]) * 3600;
const int offsetMin = parts[3].toInt(&ok[1]); const int offsetMin = parts[3].toInt(&ok[1]);
if (!ok[0] || !ok[1] || offsetMin > 59) if (!ok[0] || !ok[1] || offsetMin > 59)
@@ -547,13 +549,8 @@ Parser::Parser(const QString lastBuildDate)
void Parser::parse(const QByteArray &feedData) void Parser::parse(const QByteArray &feedData)
{ {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(this, [this, feedData]() { parse_impl(feedData); } QMetaObject::invokeMethod(this, [this, feedData]() { parse_impl(feedData); }
, Qt::QueuedConnection); , Qt::QueuedConnection);
#else
QMetaObject::invokeMethod(this, "parse_impl", Qt::QueuedConnection
, Q_ARG(QByteArray, feedData));
#endif
} }
// read and create items from a rss document // read and create items from a rss document

View File

@@ -32,7 +32,7 @@
#include "bittorrent/torrent.h" #include "bittorrent/torrent.h"
const QString TorrentFilter::AnyCategory; const QString TorrentFilter::AnyCategory;
const InfoHashSet TorrentFilter::AnyHash {{}}; const TorrentIDSet TorrentFilter::AnyID {{}};
const QString TorrentFilter::AnyTag; const QString TorrentFilter::AnyTag;
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading); const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
@@ -49,19 +49,19 @@ const TorrentFilter TorrentFilter::ErroredTorrent(TorrentFilter::Errored);
using BitTorrent::Torrent; using BitTorrent::Torrent;
TorrentFilter::TorrentFilter(const Type type, const InfoHashSet &hashSet, const QString &category, const QString &tag) TorrentFilter::TorrentFilter(const Type type, const TorrentIDSet &idSet, const QString &category, const QString &tag)
: m_type(type) : m_type(type)
, m_category(category) , m_category(category)
, m_tag(tag) , m_tag(tag)
, m_hashSet(hashSet) , m_idSet(idSet)
{ {
} }
TorrentFilter::TorrentFilter(const QString &filter, const InfoHashSet &hashSet, const QString &category, const QString &tag) TorrentFilter::TorrentFilter(const QString &filter, const TorrentIDSet &idSet, const QString &category, const QString &tag)
: m_type(All) : m_type(All)
, m_category(category) , m_category(category)
, m_tag(tag) , m_tag(tag)
, m_hashSet(hashSet) , m_idSet(idSet)
{ {
setTypeByName(filter); setTypeByName(filter);
} }
@@ -107,11 +107,11 @@ bool TorrentFilter::setTypeByName(const QString &filter)
return setType(type); return setType(type);
} }
bool TorrentFilter::setHashSet(const InfoHashSet &hashSet) bool TorrentFilter::setTorrentIDSet(const TorrentIDSet &idSet)
{ {
if (m_hashSet != hashSet) if (m_idSet != idSet)
{ {
m_hashSet = hashSet; m_idSet = idSet;
return true; return true;
} }
@@ -189,9 +189,9 @@ bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
bool TorrentFilter::matchHash(const BitTorrent::Torrent *const torrent) const bool TorrentFilter::matchHash(const BitTorrent::Torrent *const torrent) const
{ {
if (m_hashSet == AnyHash) return true; if (m_idSet == AnyID) return true;
return m_hashSet.contains(torrent->hash()); return m_idSet.contains(torrent->id());
} }
bool TorrentFilter::matchCategory(const BitTorrent::Torrent *const torrent) const bool TorrentFilter::matchCategory(const BitTorrent::Torrent *const torrent) const

View File

@@ -38,7 +38,7 @@ namespace BitTorrent
class Torrent; class Torrent;
} }
using InfoHashSet = QSet<BitTorrent::InfoHash>; using TorrentIDSet = QSet<BitTorrent::TorrentID>;
class TorrentFilter class TorrentFilter
{ {
@@ -61,7 +61,7 @@ public:
// These mean any permutation, including no category / tag. // These mean any permutation, including no category / tag.
static const QString AnyCategory; static const QString AnyCategory;
static const InfoHashSet AnyHash; static const TorrentIDSet AnyID;
static const QString AnyTag; static const QString AnyTag;
static const TorrentFilter DownloadingTorrent; static const TorrentFilter DownloadingTorrent;
@@ -79,12 +79,12 @@ public:
TorrentFilter() = default; TorrentFilter() = default;
// category & tags: pass empty string for uncategorized / untagged torrents. // category & tags: pass empty string for uncategorized / untagged torrents.
// Pass null string (QString()) to disable filtering (i.e. all torrents). // Pass null string (QString()) to disable filtering (i.e. all torrents).
TorrentFilter(Type type, const InfoHashSet &hashSet = AnyHash, const QString &category = AnyCategory, const QString &tag = AnyTag); TorrentFilter(Type type, const TorrentIDSet &idSet = AnyID, const QString &category = AnyCategory, const QString &tag = AnyTag);
TorrentFilter(const QString &filter, const InfoHashSet &hashSet = AnyHash, const QString &category = AnyCategory, const QString &tags = AnyTag); TorrentFilter(const QString &filter, const TorrentIDSet &idSet = AnyID, const QString &category = AnyCategory, const QString &tags = AnyTag);
bool setType(Type type); bool setType(Type type);
bool setTypeByName(const QString &filter); bool setTypeByName(const QString &filter);
bool setHashSet(const InfoHashSet &hashSet); bool setTorrentIDSet(const TorrentIDSet &idSet);
bool setCategory(const QString &category); bool setCategory(const QString &category);
bool setTag(const QString &tag); bool setTag(const QString &tag);
@@ -99,5 +99,5 @@ private:
Type m_type {All}; Type m_type {All};
QString m_category; QString m_category;
QString m_tag; QString m_tag;
InfoHashSet m_hashSet; TorrentIDSet m_idSet;
}; };

View File

@@ -78,11 +78,7 @@ QString Utils::Fs::toUniformPath(const QString &path)
QString Utils::Fs::fileExtension(const QString &filename) QString Utils::Fs::fileExtension(const QString &filename)
{ {
const QString name = filename.endsWith(QB_EXT) const QString name = filename.endsWith(QB_EXT)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
? filename.chopped(QB_EXT.length()) ? filename.chopped(QB_EXT.length())
#else
? filename.left(filename.length() - QB_EXT.length())
#endif
: filename; : filename;
return QMimeDatabase().suffixForFileName(name); return QMimeDatabase().suffixForFileName(name);
} }

View File

@@ -28,6 +28,8 @@
#include "misc.h" #include "misc.h"
#include <optional>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <windows.h> #include <windows.h>
#include <powrprof.h> #include <powrprof.h>
@@ -49,6 +51,7 @@
#include <zlib.h> #include <zlib.h>
#include <QCoreApplication> #include <QCoreApplication>
#include <QMimeDatabase>
#include <QRegularExpression> #include <QRegularExpression>
#include <QSet> #include <QSet>
#include <QSysInfo> #include <QSysInfo>
@@ -60,6 +63,7 @@
#include "base/types.h" #include "base/types.h"
#include "base/unicodestrings.h" #include "base/unicodestrings.h"
#include "base/utils/fs.h"
#include "base/utils/string.h" #include "base/utils/string.h"
namespace namespace
@@ -80,21 +84,26 @@ namespace
// see http://en.wikipedia.org/wiki/Kilobyte // see http://en.wikipedia.org/wiki/Kilobyte
// value must be given in bytes // value must be given in bytes
// to send numbers instead of strings with suffixes // to send numbers instead of strings with suffixes
bool splitToFriendlyUnit(const qint64 sizeInBytes, qreal &val, Utils::Misc::SizeUnit &unit) struct SplitToFriendlyUnitResult
{ {
if (sizeInBytes < 0) return false; qreal value;
Utils::Misc::SizeUnit unit;
};
std::optional<SplitToFriendlyUnitResult> splitToFriendlyUnit(const qint64 bytes)
{
if (bytes < 0)
return std::nullopt;
int i = 0; int i = 0;
val = static_cast<qreal>(sizeInBytes); auto value = static_cast<qreal>(bytes);
while ((val >= 1024.) && (i <= static_cast<int>(Utils::Misc::SizeUnit::ExbiByte))) while ((value >= 1024) && (i < static_cast<int>(Utils::Misc::SizeUnit::ExbiByte)))
{ {
val /= 1024.; value /= 1024;
++i; ++i;
} }
return {{value, static_cast<Utils::Misc::SizeUnit>(i)}};
unit = static_cast<Utils::Misc::SizeUnit>(i);
return true;
} }
} }
@@ -249,15 +258,14 @@ QString Utils::Misc::unitString(const SizeUnit unit, const bool isSpeed)
return ret; return ret;
} }
QString Utils::Misc::friendlyUnit(const qint64 bytesValue, const bool isSpeed) QString Utils::Misc::friendlyUnit(const qint64 bytes, const bool isSpeed)
{ {
SizeUnit unit; const std::optional<SplitToFriendlyUnitResult> result = splitToFriendlyUnit(bytes);
qreal friendlyVal; if (!result)
if (!splitToFriendlyUnit(bytesValue, friendlyVal, unit))
return QCoreApplication::translate("misc", "Unknown", "Unknown (size)"); return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
return Utils::String::fromDouble(friendlyVal, friendlyUnitPrecision(unit)) return Utils::String::fromDouble(result->value, friendlyUnitPrecision(result->unit))
+ QString::fromUtf8(C_NON_BREAKING_SPACE) + QString::fromUtf8(C_NON_BREAKING_SPACE)
+ unitString(unit, isSpeed); + unitString(result->unit, isSpeed);
} }
int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit) int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit)
@@ -284,9 +292,17 @@ qlonglong Utils::Misc::sizeInBytes(qreal size, const Utils::Misc::SizeUnit unit)
return size; return size;
} }
bool Utils::Misc::isPreviewable(const QString &extension) bool Utils::Misc::isPreviewable(const QString &filename)
{ {
static const QSet<QString> multimediaExtensions = const QString mime = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension).name();
if (mime.startsWith(QLatin1String("audio"), Qt::CaseInsensitive)
|| mime.startsWith(QLatin1String("video"), Qt::CaseInsensitive))
{
return true;
}
const QSet<QString> multimediaExtensions =
{ {
"3GP", "3GP",
"AAC", "AAC",
@@ -331,7 +347,7 @@ bool Utils::Misc::isPreviewable(const QString &extension)
"WMA", "WMA",
"WMV" "WMV"
}; };
return multimediaExtensions.contains(extension.toUpper()); return multimediaExtensions.contains(Utils::Fs::fileExtension(filename).toUpper());
} }
QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap) QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap)

View File

@@ -73,11 +73,11 @@ namespace Utils::Misc
// return the best user friendly storage unit (B, KiB, MiB, GiB, TiB) // return the best user friendly storage unit (B, KiB, MiB, GiB, TiB)
// value must be given in bytes // value must be given in bytes
QString friendlyUnit(qint64 bytesValue, bool isSpeed = false); QString friendlyUnit(qint64 bytes, bool isSpeed = false);
int friendlyUnitPrecision(SizeUnit unit); int friendlyUnitPrecision(SizeUnit unit);
qint64 sizeInBytes(qreal size, SizeUnit unit); qint64 sizeInBytes(qreal size, SizeUnit unit);
bool isPreviewable(const QString &extension); bool isPreviewable(const QString &filename);
// Take a number of seconds and return a user-friendly // Take a number of seconds and return a user-friendly
// time duration like "1d 2h 10m". // time duration like "1d 2h 10m".

View File

@@ -33,10 +33,15 @@
#include <QCollator> #include <QCollator>
#include <QLocale> #include <QLocale>
#include <QRegExp>
#include <QtGlobal> #include <QtGlobal>
#include <QVector> #include <QVector>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <QRegularExpression>
#else
#include <QRegExp>
#endif
#if defined(Q_OS_MACOS) || defined(__MINGW32__) #if defined(Q_OS_MACOS) || defined(__MINGW32__)
#define QBT_USES_QTHREADSTORAGE #define QBT_USES_QTHREADSTORAGE
#include <QThreadStorage> #include <QThreadStorage>
@@ -181,14 +186,21 @@ QString Utils::String::fromDouble(const double n, const int precision)
return QLocale::system().toString(std::floor(n * prec) / prec, 'f', precision); return QLocale::system().toString(std::floor(n * prec) / prec, 'f', precision);
} }
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QString Utils::String::wildcardToRegexPattern(const QString &pattern)
{
return QRegularExpression::wildcardToRegularExpression(pattern, QRegularExpression::UnanchoredWildcardConversion);
}
#else
// This is marked as internal in QRegExp.cpp, but is exported. The alternative would be to // This is marked as internal in QRegExp.cpp, but is exported. The alternative would be to
// copy the code from QRegExp::wc2rx(). // copy the code from QRegExp::wc2rx().
QString qt_regexp_toCanonical(const QString &pattern, QRegExp::PatternSyntax patternSyntax); QString qt_regexp_toCanonical(const QString &pattern, QRegExp::PatternSyntax patternSyntax);
QString Utils::String::wildcardToRegex(const QString &pattern) QString Utils::String::wildcardToRegexPattern(const QString &pattern)
{ {
return qt_regexp_toCanonical(pattern, QRegExp::Wildcard); return qt_regexp_toCanonical(pattern, QRegExp::Wildcard);
} }
#endif
std::optional<bool> Utils::String::parseBool(const QString &string) std::optional<bool> Utils::String::parseBool(const QString &string)
{ {
@@ -200,6 +212,26 @@ std::optional<bool> Utils::String::parseBool(const QString &string)
return std::nullopt; return std::nullopt;
} }
std::optional<int> Utils::String::parseInt(const QString &string)
{
bool ok = false;
const int result = string.toInt(&ok);
if (ok)
return result;
return std::nullopt;
}
std::optional<double> Utils::String::parseDouble(const QString &string)
{
bool ok = false;
const double result = string.toDouble(&ok);
if (ok)
return result;
return std::nullopt;
}
QString Utils::String::join(const QVector<QStringRef> &strings, const QString &separator) QString Utils::String::join(const QVector<QStringRef> &strings, const QString &separator)
{ {
if (strings.empty()) if (strings.empty())

View File

@@ -50,7 +50,7 @@ namespace Utils::String
return (naturalCompare(left, right, caseSensitivity) < 0); return (naturalCompare(left, right, caseSensitivity) < 0);
} }
QString wildcardToRegex(const QString &pattern); QString wildcardToRegexPattern(const QString &pattern);
template <typename T> template <typename T>
T unquote(const T &str, const QString &quotes = QChar('"')) T unquote(const T &str, const QString &quotes = QChar('"'))
@@ -67,6 +67,8 @@ namespace Utils::String
} }
std::optional<bool> parseBool(const QString &string); std::optional<bool> parseBool(const QString &string);
std::optional<int> parseInt(const QString &string);
std::optional<double> parseDouble(const QString &string);
QString join(const QVector<QStringRef> &strings, const QString &separator); QString join(const QVector<QStringRef> &strings, const QString &separator);

View File

@@ -30,7 +30,7 @@
#define QBT_VERSION_MAJOR 4 #define QBT_VERSION_MAJOR 4
#define QBT_VERSION_MINOR 3 #define QBT_VERSION_MINOR 3
#define QBT_VERSION_BUGFIX 3 #define QBT_VERSION_BUGFIX 4
#define QBT_VERSION_BUILD 0 #define QBT_VERSION_BUILD 0
#define QBT_VERSION_STATUS "" // Should be empty for stable releases! #define QBT_VERSION_STATUS "" // Should be empty for stable releases!

View File

@@ -26,7 +26,7 @@ add_library(qbt_gui STATIC
powermanagement/powermanagement.h powermanagement/powermanagement.h
previewlistdelegate.h previewlistdelegate.h
previewselectdialog.h previewselectdialog.h
progressbardelegate.h progressbarpainter.h
properties/downloadedpiecesbar.h properties/downloadedpiecesbar.h
properties/peerlistsortmodel.h properties/peerlistsortmodel.h
properties/peerlistwidget.h properties/peerlistwidget.h
@@ -106,7 +106,7 @@ add_library(qbt_gui STATIC
powermanagement/powermanagement.cpp powermanagement/powermanagement.cpp
previewlistdelegate.cpp previewlistdelegate.cpp
previewselectdialog.cpp previewselectdialog.cpp
progressbardelegate.cpp progressbarpainter.cpp
properties/downloadedpiecesbar.cpp properties/downloadedpiecesbar.cpp
properties/peerlistsortmodel.cpp properties/peerlistsortmodel.cpp
properties/peerlistwidget.cpp properties/peerlistwidget.cpp

View File

@@ -281,6 +281,9 @@
</property> </property>
<item> <item>
<widget class="QTextBrowser" name="textBrowserTranslation"> <widget class="QTextBrowser" name="textBrowserTranslation">
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="lineWrapMode"> <property name="lineWrapMode">
<enum>QTextEdit::NoWrap</enum> <enum>QTextEdit::NoWrap</enum>
</property> </property>

View File

@@ -270,12 +270,12 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
bool AddNewTorrentDialog::loadTorrentImpl() bool AddNewTorrentDialog::loadTorrentImpl()
{ {
m_hasMetadata = true; m_hasMetadata = true;
const BitTorrent::InfoHash infoHash = m_torrentInfo.hash(); const auto torrentID = BitTorrent::TorrentID::fromInfoHash(m_torrentInfo.infoHash());
// Prevent showing the dialog if download is already present // Prevent showing the dialog if download is already present
if (BitTorrent::Session::instance()->isKnownTorrent(infoHash)) if (BitTorrent::Session::instance()->isKnownTorrent(torrentID))
{ {
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(infoHash); BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(torrentID);
if (torrent) if (torrent)
{ {
if (torrent->isPrivate() || m_torrentInfo.isPrivate()) if (torrent->isPrivate() || m_torrentInfo.isPrivate())
@@ -296,7 +296,7 @@ bool AddNewTorrentDialog::loadTorrentImpl()
return false; return false;
} }
m_ui->labelHashData->setText(infoHash); m_ui->labelHashData->setText(torrentID.toString());
setupTreeview(); setupTreeview();
TMMChanged(m_ui->comboTTM->currentIndex()); TMMChanged(m_ui->comboTTM->currentIndex());
return true; return true;
@@ -312,11 +312,11 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
m_torrentGuard = std::make_unique<TorrentFileGuard>(); m_torrentGuard = std::make_unique<TorrentFileGuard>();
const BitTorrent::InfoHash infoHash = magnetUri.hash(); const auto torrentID = BitTorrent::TorrentID::fromInfoHash(magnetUri.infoHash());
// Prevent showing the dialog if download is already present // Prevent showing the dialog if download is already present
if (BitTorrent::Session::instance()->isKnownTorrent(infoHash)) if (BitTorrent::Session::instance()->isKnownTorrent(torrentID))
{ {
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(infoHash); BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(torrentID);
if (torrent) if (torrent)
{ {
if (torrent->isPrivate()) if (torrent->isPrivate())
@@ -348,7 +348,7 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
BitTorrent::Session::instance()->downloadMetadata(magnetUri); BitTorrent::Session::instance()->downloadMetadata(magnetUri);
setMetadataProgressIndicator(true, tr("Retrieving metadata...")); setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
m_ui->labelHashData->setText(infoHash); m_ui->labelHashData->setText(torrentID.toString());
m_magnetURI = magnetUri; m_magnetURI = magnetUri;
return true; return true;
@@ -502,12 +502,12 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
{ {
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0); const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio) const auto applyPriorities = [this](const BitTorrent::DownloadPriority prio)
{ {
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
for (const QModelIndex &index : selectedRows) for (const QModelIndex &index : selectedRows)
{ {
m_contentModel->setData( m_contentModel->setData(index.sibling(index.row(), PRIORITY)
m_contentModel->index(index.row(), PRIORITY, index.parent())
, static_cast<int>(prio)); , static_cast<int>(prio));
} }
}; };
@@ -517,37 +517,63 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
if (selectedRows.size() == 1) if (selectedRows.size() == 1)
{ {
QAction *actRename = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")); menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
connect(actRename, &QAction::triggered, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); }); , this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
menu->addSeparator(); menu->addSeparator();
} }
QMenu *subMenu = menu->addMenu(tr("Priority")); QMenu *subMenu = menu->addMenu(tr("Priority"));
connect(m_ui->actionNotDownloaded, &QAction::triggered, subMenu, [applyPriorities]() subMenu->addAction(tr("Do not download"), subMenu, [applyPriorities]()
{ {
applyPriorities(BitTorrent::DownloadPriority::Ignored); applyPriorities(BitTorrent::DownloadPriority::Ignored);
}); });
subMenu->addAction(m_ui->actionNotDownloaded); subMenu->addAction(tr("Normal"), subMenu, [applyPriorities]()
connect(m_ui->actionNormal, &QAction::triggered, subMenu, [applyPriorities]()
{ {
applyPriorities(BitTorrent::DownloadPriority::Normal); applyPriorities(BitTorrent::DownloadPriority::Normal);
}); });
subMenu->addAction(m_ui->actionNormal); subMenu->addAction(tr("High"), subMenu, [applyPriorities]()
connect(m_ui->actionHigh, &QAction::triggered, subMenu, [applyPriorities]()
{ {
applyPriorities(BitTorrent::DownloadPriority::High); applyPriorities(BitTorrent::DownloadPriority::High);
}); });
subMenu->addAction(m_ui->actionHigh); subMenu->addAction(tr("Maximum"), subMenu, [applyPriorities]()
connect(m_ui->actionMaximum, &QAction::triggered, subMenu, [applyPriorities]()
{ {
applyPriorities(BitTorrent::DownloadPriority::Maximum); applyPriorities(BitTorrent::DownloadPriority::Maximum);
}); });
subMenu->addAction(m_ui->actionMaximum); subMenu->addSeparator();
subMenu->addAction(tr("By shown file order"), subMenu, [this]()
{
// Equally distribute the selected items into groups and for each group assign
// a download priority that will apply to each item. The number of groups depends on how
// many "download priority" are available to be assigned
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
const int priorityGroups = 3;
const int priorityGroupSize = std::max((selectedRows.length() / priorityGroups), 1);
for (int i = 0; i < selectedRows.length(); ++i)
{
auto priority = BitTorrent::DownloadPriority::Ignored;
switch (i / priorityGroupSize)
{
case 0:
priority = BitTorrent::DownloadPriority::Maximum;
break;
case 1:
priority = BitTorrent::DownloadPriority::High;
break;
default:
case 2:
priority = BitTorrent::DownloadPriority::Normal;
break;
}
const QModelIndex &index = selectedRows[i];
m_contentModel->setData(index.sibling(index.row(), PRIORITY)
, static_cast<int>(priority));
}
});
menu->popup(QCursor::pos()); menu->popup(QCursor::pos());
} }
@@ -603,7 +629,7 @@ void AddNewTorrentDialog::reject()
if (!m_hasMetadata) if (!m_hasMetadata)
{ {
setMetadataProgressIndicator(false); setMetadataProgressIndicator(false);
BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.hash()); BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.infoHash().toTorrentID());
} }
QDialog::reject(); QDialog::reject();
@@ -611,7 +637,7 @@ void AddNewTorrentDialog::reject()
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata) void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
{ {
if (metadata.hash() != m_magnetURI.hash()) return; if (metadata.infoHash() != m_magnetURI.infoHash()) return;
disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata); disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata);

View File

@@ -449,26 +449,6 @@
</layout> </layout>
</item> </item>
</layout> </layout>
<action name="actionNormal">
<property name="text">
<string>Normal</string>
</property>
</action>
<action name="actionHigh">
<property name="text">
<string>High</string>
</property>
</action>
<action name="actionMaximum">
<property name="text">
<string>Maximum</string>
</property>
</action>
<action name="actionNotDownloaded">
<property name="text">
<string>Do not download</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View File

@@ -82,6 +82,9 @@ namespace
DOWNLOAD_TRACKER_FAVICON, DOWNLOAD_TRACKER_FAVICON,
SAVE_PATH_HISTORY_LENGTH, SAVE_PATH_HISTORY_LENGTH,
ENABLE_SPEED_WIDGET, ENABLE_SPEED_WIDGET,
#ifndef Q_OS_MACOS
ENABLE_ICONS_IN_MENUS,
#endif
// embedded tracker // embedded tracker
TRACKER_STATUS, TRACKER_STATUS,
TRACKER_PORT, TRACKER_PORT,
@@ -112,14 +115,11 @@ namespace
OUTGOING_PORT_MIN, OUTGOING_PORT_MIN,
OUTGOING_PORT_MAX, OUTGOING_PORT_MAX,
UPNP_LEASE_DURATION, UPNP_LEASE_DURATION,
PEER_TOS,
UTP_MIX_MODE, UTP_MIX_MODE,
#ifdef HAS_IDN_SUPPORT
IDN_SUPPORT, IDN_SUPPORT,
#endif
MULTI_CONNECTIONS_PER_IP, MULTI_CONNECTIONS_PER_IP,
#ifdef HAS_HTTPS_TRACKER_VALIDATION
VALIDATE_HTTPS_TRACKER_CERTIFICATE, VALIDATE_HTTPS_TRACKER_CERTIFICATE,
#endif
BLOCK_PEERS_ON_PRIVILEGED_PORTS, BLOCK_PEERS_ON_PRIVILEGED_PORTS,
// seeding // seeding
CHOKING_ALGORITHM, CHOKING_ALGORITHM,
@@ -224,18 +224,16 @@ void AdvancedSettings::saveAdvancedSettings()
session->setOutgoingPortsMax(m_spinBoxOutgoingPortsMax.value()); session->setOutgoingPortsMax(m_spinBoxOutgoingPortsMax.value());
// UPnP lease duration // UPnP lease duration
session->setUPnPLeaseDuration(m_spinBoxUPnPLeaseDuration.value()); session->setUPnPLeaseDuration(m_spinBoxUPnPLeaseDuration.value());
// Type of service
session->setPeerToS(m_spinBoxPeerToS.value());
// uTP-TCP mixed mode // uTP-TCP mixed mode
session->setUtpMixedMode(static_cast<BitTorrent::MixedModeAlgorithm>(m_comboBoxUtpMixedMode.currentIndex())); session->setUtpMixedMode(static_cast<BitTorrent::MixedModeAlgorithm>(m_comboBoxUtpMixedMode.currentIndex()));
#ifdef HAS_IDN_SUPPORT
// Support internationalized domain name (IDN) // Support internationalized domain name (IDN)
session->setIDNSupportEnabled(m_checkBoxIDNSupport.isChecked()); session->setIDNSupportEnabled(m_checkBoxIDNSupport.isChecked());
#endif
// multiple connections per IP // multiple connections per IP
session->setMultiConnectionsPerIpEnabled(m_checkBoxMultiConnectionsPerIp.isChecked()); session->setMultiConnectionsPerIpEnabled(m_checkBoxMultiConnectionsPerIp.isChecked());
#ifdef HAS_HTTPS_TRACKER_VALIDATION
// Validate HTTPS tracker certificate // Validate HTTPS tracker certificate
session->setValidateHTTPSTrackerCertificate(m_checkBoxValidateHTTPSTrackerCertificate.isChecked()); session->setValidateHTTPSTrackerCertificate(m_checkBoxValidateHTTPSTrackerCertificate.isChecked());
#endif
// Disallow connection to peers on privileged ports // Disallow connection to peers on privileged ports
session->setBlockPeersOnPrivilegedPorts(m_checkBoxBlockPeersOnPrivilegedPorts.isChecked()); session->setBlockPeersOnPrivilegedPorts(m_checkBoxBlockPeersOnPrivilegedPorts.isChecked());
// Recheck torrents on completion // Recheck torrents on completion
@@ -279,6 +277,9 @@ void AdvancedSettings::saveAdvancedSettings()
mainWindow->setDownloadTrackerFavicon(m_checkBoxTrackerFavicon.isChecked()); mainWindow->setDownloadTrackerFavicon(m_checkBoxTrackerFavicon.isChecked());
AddNewTorrentDialog::setSavePathHistoryLength(m_spinBoxSavePathHistoryLength.value()); AddNewTorrentDialog::setSavePathHistoryLength(m_spinBoxSavePathHistoryLength.value());
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked()); pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
#ifndef Q_OS_MACOS
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
#endif
// Tracker // Tracker
pref->setTrackerPort(m_spinBoxTrackerPort.value()); pref->setTrackerPort(m_spinBoxTrackerPort.value());
@@ -544,31 +545,33 @@ void AdvancedSettings::loadAdvancedSettings()
m_spinBoxUPnPLeaseDuration.setSuffix(tr(" s", " seconds")); m_spinBoxUPnPLeaseDuration.setSuffix(tr(" s", " seconds"));
addRow(UPNP_LEASE_DURATION, (tr("UPnP lease duration [0: Permanent lease]") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#upnp_lease_duration", "(?)")) addRow(UPNP_LEASE_DURATION, (tr("UPnP lease duration [0: Permanent lease]") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#upnp_lease_duration", "(?)"))
, &m_spinBoxUPnPLeaseDuration); , &m_spinBoxUPnPLeaseDuration);
// Type of service
m_spinBoxPeerToS.setMinimum(0);
m_spinBoxPeerToS.setMaximum(255);
m_spinBoxPeerToS.setValue(session->peerToS());
addRow(PEER_TOS, (tr("Type of service (ToS) for connections to peers") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#peer_tos", "(?)"))
, &m_spinBoxPeerToS);
// uTP-TCP mixed mode // uTP-TCP mixed mode
m_comboBoxUtpMixedMode.addItems({tr("Prefer TCP"), tr("Peer proportional (throttles TCP)")}); m_comboBoxUtpMixedMode.addItems({tr("Prefer TCP"), tr("Peer proportional (throttles TCP)")});
m_comboBoxUtpMixedMode.setCurrentIndex(static_cast<int>(session->utpMixedMode())); m_comboBoxUtpMixedMode.setCurrentIndex(static_cast<int>(session->utpMixedMode()));
addRow(UTP_MIX_MODE, (tr("%1-TCP mixed mode algorithm", "uTP-TCP mixed mode algorithm").arg(C_UTP) addRow(UTP_MIX_MODE, (tr("%1-TCP mixed mode algorithm", "uTP-TCP mixed mode algorithm").arg(C_UTP)
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#mixed_mode_algorithm", "(?)")) + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#mixed_mode_algorithm", "(?)"))
, &m_comboBoxUtpMixedMode); , &m_comboBoxUtpMixedMode);
#ifdef HAS_IDN_SUPPORT
// Support internationalized domain name (IDN) // Support internationalized domain name (IDN)
m_checkBoxIDNSupport.setChecked(session->isIDNSupportEnabled()); m_checkBoxIDNSupport.setChecked(session->isIDNSupportEnabled());
addRow(IDN_SUPPORT, (tr("Support internationalized domain name (IDN)") addRow(IDN_SUPPORT, (tr("Support internationalized domain name (IDN)")
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_idna", "(?)")) + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_idna", "(?)"))
, &m_checkBoxIDNSupport); , &m_checkBoxIDNSupport);
#endif
// multiple connections per IP // multiple connections per IP
m_checkBoxMultiConnectionsPerIp.setChecked(session->multiConnectionsPerIpEnabled()); m_checkBoxMultiConnectionsPerIp.setChecked(session->multiConnectionsPerIpEnabled());
addRow(MULTI_CONNECTIONS_PER_IP, (tr("Allow multiple connections from the same IP address") addRow(MULTI_CONNECTIONS_PER_IP, (tr("Allow multiple connections from the same IP address")
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_multiple_connections_per_ip", "(?)")) + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_multiple_connections_per_ip", "(?)"))
, &m_checkBoxMultiConnectionsPerIp); , &m_checkBoxMultiConnectionsPerIp);
#ifdef HAS_HTTPS_TRACKER_VALIDATION
// Validate HTTPS tracker certificate // Validate HTTPS tracker certificate
m_checkBoxValidateHTTPSTrackerCertificate.setChecked(session->validateHTTPSTrackerCertificate()); m_checkBoxValidateHTTPSTrackerCertificate.setChecked(session->validateHTTPSTrackerCertificate());
addRow(VALIDATE_HTTPS_TRACKER_CERTIFICATE, (tr("Validate HTTPS tracker certificates") addRow(VALIDATE_HTTPS_TRACKER_CERTIFICATE, (tr("Validate HTTPS tracker certificates")
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#validate_https_trackers", "(?)")) + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#validate_https_trackers", "(?)"))
, &m_checkBoxValidateHTTPSTrackerCertificate); , &m_checkBoxValidateHTTPSTrackerCertificate);
#endif
// Disallow connection to peers on privileged ports // Disallow connection to peers on privileged ports
m_checkBoxBlockPeersOnPrivilegedPorts.setChecked(session->blockPeersOnPrivilegedPorts()); m_checkBoxBlockPeersOnPrivilegedPorts.setChecked(session->blockPeersOnPrivilegedPorts());
addRow(BLOCK_PEERS_ON_PRIVILEGED_PORTS, (tr("Disallow connection to peers on privileged ports") + ' ' + makeLink("https://libtorrent.org/single-page-ref.html#no_connect_privileged_ports", "(?)")), &m_checkBoxBlockPeersOnPrivilegedPorts); addRow(BLOCK_PEERS_ON_PRIVILEGED_PORTS, (tr("Disallow connection to peers on privileged ports") + ' ' + makeLink("https://libtorrent.org/single-page-ref.html#no_connect_privileged_ports", "(?)")), &m_checkBoxBlockPeersOnPrivilegedPorts);
@@ -647,6 +650,11 @@ void AdvancedSettings::loadAdvancedSettings()
// Enable speed graphs // Enable speed graphs
m_checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled()); m_checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled());
addRow(ENABLE_SPEED_WIDGET, tr("Enable speed graphs"), &m_checkBoxSpeedWidgetEnabled); addRow(ENABLE_SPEED_WIDGET, tr("Enable speed graphs"), &m_checkBoxSpeedWidgetEnabled);
#ifndef Q_OS_MACOS
// Enable icons in menus
m_checkBoxIconsInMenusEnabled.setChecked(pref->iconsInMenusEnabled());
addRow(ENABLE_ICONS_IN_MENUS, tr("Enable icons in menus"), &m_checkBoxIconsInMenusEnabled);
#endif
// Tracker State // Tracker State
m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled()); m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
addRow(TRACKER_STATUS, tr("Enable embedded tracker"), &m_checkBoxTrackerStatus); addRow(TRACKER_STATUS, tr("Enable embedded tracker"), &m_checkBoxTrackerStatus);

View File

@@ -61,7 +61,7 @@ private:
template <typename T> void addRow(int row, const QString &text, T *widget); template <typename T> void addRow(int row, const QString &text, T *widget);
QSpinBox m_spinBoxAsyncIOThreads, m_spinBoxFilePoolSize, m_spinBoxCheckingMemUsage, QSpinBox m_spinBoxAsyncIOThreads, m_spinBoxFilePoolSize, m_spinBoxCheckingMemUsage,
m_spinBoxSaveResumeDataInterval, m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration, m_spinBoxSaveResumeDataInterval, m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration, m_spinBoxPeerToS,
m_spinBoxListRefresh, m_spinBoxTrackerPort, m_spinBoxSendBufferWatermark, m_spinBoxSendBufferLowWatermark, m_spinBoxListRefresh, m_spinBoxTrackerPort, m_spinBoxSendBufferWatermark, m_spinBoxSendBufferLowWatermark,
m_spinBoxSendBufferWatermarkFactor, m_spinBoxSocketBacklogSize, m_spinBoxMaxConcurrentHTTPAnnounces, m_spinBoxStopTrackerTimeout, m_spinBoxSendBufferWatermarkFactor, m_spinBoxSocketBacklogSize, m_spinBoxMaxConcurrentHTTPAnnounces, m_spinBoxStopTrackerTimeout,
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval; m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval;
@@ -84,4 +84,8 @@ private:
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
QComboBox m_comboBoxOSMemoryPriority; QComboBox m_comboBoxOSMemoryPriority;
#endif #endif
#ifndef Q_OS_MACOS
QCheckBox m_checkBoxIconsInMenusEnabled;
#endif
}; };

View File

@@ -79,31 +79,19 @@ void AutoExpandableDialog::showEvent(QShowEvent *e)
// Show dialog and resize textbox to fit the text // Show dialog and resize textbox to fit the text
// NOTE: For unknown reason QFontMetrics gets more accurate when called from showEvent. // NOTE: For unknown reason QFontMetrics gets more accurate when called from showEvent.
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
int wd = m_ui->textEdit->fontMetrics().horizontalAdvance(m_ui->textEdit->text()) + 4; int wd = m_ui->textEdit->fontMetrics().horizontalAdvance(m_ui->textEdit->text()) + 4;
#else
int wd = m_ui->textEdit->fontMetrics().width(m_ui->textEdit->text()) + 4;
#endif
if (!windowTitle().isEmpty()) if (!windowTitle().isEmpty())
{ {
// not really the font metrics in window title, so we enlarge it a bit, // not really the font metrics in window title, so we enlarge it a bit,
// including the small icon and close button width // including the small icon and close button width
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
int w = fontMetrics().horizontalAdvance(windowTitle()) * 1.8; int w = fontMetrics().horizontalAdvance(windowTitle()) * 1.8;
#else
int w = fontMetrics().width(windowTitle()) * 1.8;
#endif
wd = std::max(wd, w); wd = std::max(wd, w);
} }
if (!m_ui->textLabel->text().isEmpty()) if (!m_ui->textLabel->text().isEmpty())
{ {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
int w = m_ui->textLabel->fontMetrics().horizontalAdvance(m_ui->textLabel->text()); int w = m_ui->textLabel->fontMetrics().horizontalAdvance(m_ui->textLabel->text());
#else
int w = m_ui->textLabel->fontMetrics().width(m_ui->textLabel->text());
#endif
wd = std::max(wd, w); wd = std::max(wd, w);
} }

View File

@@ -28,7 +28,6 @@
#include "categoryfilterwidget.h" #include "categoryfilterwidget.h"
#include <QAction>
#include <QMenu> #include <QMenu>
#include "base/bittorrent/session.h" #include "base/bittorrent/session.h"
@@ -110,54 +109,33 @@ void CategoryFilterWidget::showMenu(const QPoint &)
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
const QAction *addAct = menu->addAction( menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add category...")
UIThemeManager::instance()->getIcon("list-add") , this, &CategoryFilterWidget::addCategory);
, tr("Add category..."));
connect(addAct, &QAction::triggered, this, &CategoryFilterWidget::addCategory);
const auto selectedRows = selectionModel()->selectedRows(); const auto selectedRows = selectionModel()->selectedRows();
if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first())) if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first()))
{ {
if (BitTorrent::Session::instance()->isSubcategoriesEnabled()) if (BitTorrent::Session::instance()->isSubcategoriesEnabled())
{ {
const QAction *addSubAct = menu->addAction( menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add subcategory...")
UIThemeManager::instance()->getIcon("list-add") , this, &CategoryFilterWidget::addSubcategory);
, tr("Add subcategory..."));
connect(addSubAct, &QAction::triggered, this, &CategoryFilterWidget::addSubcategory);
} }
const QAction *editAct = menu->addAction( menu->addAction(UIThemeManager::instance()->getIcon("document-edit"), tr("Edit category...")
UIThemeManager::instance()->getIcon("document-edit") , this, &CategoryFilterWidget::editCategory);
, tr("Edit category...")); menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove category")
connect(editAct, &QAction::triggered, this, &CategoryFilterWidget::editCategory); , this, &CategoryFilterWidget::removeCategory);
const QAction *removeAct = menu->addAction(
UIThemeManager::instance()->getIcon("list-remove")
, tr("Remove category"));
connect(removeAct, &QAction::triggered, this, &CategoryFilterWidget::removeCategory);
} }
const QAction *removeUnusedAct = menu->addAction( menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove unused categories")
UIThemeManager::instance()->getIcon("list-remove") , this, &CategoryFilterWidget::removeUnusedCategories);
, tr("Remove unused categories"));
connect(removeUnusedAct, &QAction::triggered, this, &CategoryFilterWidget::removeUnusedCategories);
menu->addSeparator(); menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon("media-playback-start"), tr("Resume torrents")
const QAction *startAct = menu->addAction( , this, &CategoryFilterWidget::actionResumeTorrentsTriggered);
UIThemeManager::instance()->getIcon("media-playback-start") menu->addAction(UIThemeManager::instance()->getIcon("media-playback-pause"), tr("Pause torrents")
, tr("Resume torrents")); , this, &CategoryFilterWidget::actionPauseTorrentsTriggered);
connect(startAct, &QAction::triggered, this, &CategoryFilterWidget::actionResumeTorrentsTriggered); menu->addAction(UIThemeManager::instance()->getIcon("edit-delete"), tr("Delete torrents")
, this, &CategoryFilterWidget::actionDeleteTorrentsTriggered);
const QAction *pauseAct = menu->addAction(
UIThemeManager::instance()->getIcon("media-playback-pause")
, tr("Pause torrents"));
connect(pauseAct, &QAction::triggered, this, &CategoryFilterWidget::actionPauseTorrentsTriggered);
const QAction *deleteTorrentsAct = menu->addAction(
UIThemeManager::instance()->getIcon("edit-delete")
, tr("Delete torrents"));
connect(deleteTorrentsAct, &QAction::triggered, this, &CategoryFilterWidget::actionDeleteTorrentsTriggered);
menu->popup(QCursor::pos()); menu->popup(QCursor::pos());
} }

View File

@@ -91,12 +91,12 @@ void ExecutionLogWidget::displayContextMenu(const QPoint &pos, const LogListView
// only show copy action if any of the row is selected // only show copy action if any of the row is selected
if (view->currentIndex().isValid()) if (view->currentIndex().isValid())
{ {
const QAction *copyAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy")); menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy")
connect(copyAct, &QAction::triggered, view, &LogListView::copySelection); , view, &LogListView::copySelection);
} }
const QAction *clearAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear")); menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear")
connect(clearAct, &QAction::triggered, model, &BaseLogModel::reset); , model, &BaseLogModel::reset);
menu->popup(view->mapToGlobal(pos)); menu->popup(view->mapToGlobal(pos));
} }

View File

@@ -27,7 +27,7 @@ HEADERS += \
$$PWD/powermanagement/powermanagement.h \ $$PWD/powermanagement/powermanagement.h \
$$PWD/previewlistdelegate.h \ $$PWD/previewlistdelegate.h \
$$PWD/previewselectdialog.h \ $$PWD/previewselectdialog.h \
$$PWD/progressbardelegate.h \ $$PWD/progressbarpainter.h \
$$PWD/properties/downloadedpiecesbar.h \ $$PWD/properties/downloadedpiecesbar.h \
$$PWD/properties/peerlistsortmodel.h \ $$PWD/properties/peerlistsortmodel.h \
$$PWD/properties/peerlistwidget.h \ $$PWD/properties/peerlistwidget.h \
@@ -107,7 +107,7 @@ SOURCES += \
$$PWD/powermanagement/powermanagement.cpp \ $$PWD/powermanagement/powermanagement.cpp \
$$PWD/previewlistdelegate.cpp \ $$PWD/previewlistdelegate.cpp \
$$PWD/previewselectdialog.cpp \ $$PWD/previewselectdialog.cpp \
$$PWD/progressbardelegate.cpp \ $$PWD/progressbarpainter.cpp \
$$PWD/properties/downloadedpiecesbar.cpp \ $$PWD/properties/downloadedpiecesbar.cpp \
$$PWD/properties/peerlistsortmodel.cpp \ $$PWD/properties/peerlistsortmodel.cpp \
$$PWD/properties/peerlistwidget.cpp \ $$PWD/properties/peerlistwidget.cpp \

View File

@@ -46,11 +46,7 @@ namespace
int horizontalAdvance(const QFontMetrics &fontMetrics, const QString &text) int horizontalAdvance(const QFontMetrics &fontMetrics, const QString &text)
{ {
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
return fontMetrics.horizontalAdvance(text); return fontMetrics.horizontalAdvance(text);
#else
return fontMetrics.width(text);
#endif
} }
QString logText(const QModelIndex &index) QString logText(const QModelIndex &index)

View File

@@ -30,6 +30,7 @@
#include <chrono> #include <chrono>
#include <QActionGroup>
#include <QClipboard> #include <QClipboard>
#include <QCloseEvent> #include <QCloseEvent>
#include <QDebug> #include <QDebug>
@@ -90,7 +91,6 @@
#include "statsdialog.h" #include "statsdialog.h"
#include "statusbar.h" #include "statusbar.h"
#include "torrentcreatordialog.h" #include "torrentcreatordialog.h"
#include "transferlistfilterswidget.h" #include "transferlistfilterswidget.h"
#include "transferlistmodel.h" #include "transferlistmodel.h"
#include "transferlistwidget.h" #include "transferlistwidget.h"
@@ -105,6 +105,8 @@
#include "programupdater.h" #include "programupdater.h"
#endif #endif
using namespace std::chrono_literals;
namespace namespace
{ {
#define SETTINGS_KEY(name) "GUI/" name #define SETTINGS_KEY(name) "GUI/" name
@@ -148,9 +150,6 @@ MainWindow::MainWindow(QWidget *parent)
, m_posInitialized(false) , m_posInitialized(false)
, m_forceExit(false) , m_forceExit(false)
, m_unlockDlgShowing(false) , m_unlockDlgShowing(false)
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
, m_wasUpdateCheckEnabled(false)
#endif
, m_hasPython(false) , m_hasPython(false)
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
@@ -195,10 +194,8 @@ MainWindow::MainWindow(QWidget *parent)
m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon("preferences-web-browser-cookies")); m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon("preferences-web-browser-cookies"));
auto *lockMenu = new QMenu(this); auto *lockMenu = new QMenu(this);
QAction *defineUiLockPasswdAct = lockMenu->addAction(tr("&Set Password")); lockMenu->addAction(tr("&Set Password"), this, &MainWindow::defineUILockPassword);
connect(defineUiLockPasswdAct, &QAction::triggered, this, &MainWindow::defineUILockPassword); lockMenu->addAction(tr("&Clear Password"), this, &MainWindow::clearUILockPassword);
QAction *clearUiLockPasswdAct = lockMenu->addAction(tr("&Clear Password"));
connect(clearUiLockPasswdAct, &QAction::triggered, this, &MainWindow::clearUILockPassword);
m_ui->actionLock->setMenu(lockMenu); m_ui->actionLock->setMenu(lockMenu);
// Creating Bittorrent session // Creating Bittorrent session
@@ -319,11 +316,11 @@ MainWindow::MainWindow(QWidget *parent)
connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds); connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
m_programUpdateTimer = new QTimer(this); connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); });
m_programUpdateTimer->setInterval(60 * 60 * 1000);
m_programUpdateTimer->setSingleShot(true); // trigger an early check on startup
connect(m_programUpdateTimer, &QTimer::timeout, this, &MainWindow::checkProgramUpdate); if (pref->isUpdateCheckEnabled())
connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, &MainWindow::checkProgramUpdate); checkProgramUpdate(false);
#else #else
m_ui->actionCheckForUpdates->setVisible(false); m_ui->actionCheckForUpdates->setVisible(false);
#endif #endif
@@ -550,16 +547,11 @@ void MainWindow::addToolbarContextMenu()
m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu); m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested); connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only")); QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"), this, &MainWindow::toolbarIconsOnly);
connect(iconsOnly, &QAction::triggered, this, &MainWindow::toolbarIconsOnly); QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"), this, &MainWindow::toolbarTextOnly);
QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only")); QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"), this, &MainWindow::toolbarTextBeside);
connect(textOnly, &QAction::triggered, this, &MainWindow::toolbarTextOnly); QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"), this, &MainWindow::toolbarTextUnder);
QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons")); QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"), this, &MainWindow::toolbarFollowSystem);
connect(textBesideIcons, &QAction::triggered, this, &MainWindow::toolbarTextBeside);
QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"));
connect(textUnderIcons, &QAction::triggered, this, &MainWindow::toolbarTextUnder);
QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"));
connect(followSystemStyle, &QAction::triggered, this, &MainWindow::toolbarFollowSystem);
auto *textPositionGroup = new QActionGroup(m_toolbarMenu); auto *textPositionGroup = new QActionGroup(m_toolbarMenu);
textPositionGroup->addAction(iconsOnly); textPositionGroup->addAction(iconsOnly);
@@ -811,7 +803,8 @@ void MainWindow::cleanup()
m_preventTimer->stop(); m_preventTimer->stop();
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS)) #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
m_programUpdateTimer->stop(); if (m_programUpdateTimer)
m_programUpdateTimer->stop();
#endif #endif
// remove all child widgets // remove all child widgets
@@ -884,36 +877,36 @@ void MainWindow::createKeyboardShortcuts()
m_ui->actionOpen->setShortcut(QKeySequence::Open); m_ui->actionOpen->setShortcut(QKeySequence::Open);
m_ui->actionDelete->setShortcut(QKeySequence::Delete); m_ui->actionDelete->setShortcut(QKeySequence::Delete);
m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_O); m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
m_ui->actionExit->setShortcut(Qt::CTRL + Qt::Key_Q); m_ui->actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
m_ui->actionCloseWindow->setShortcut(QKeySequence::Close); m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
#else #else
m_ui->actionCloseWindow->setVisible(false); m_ui->actionCloseWindow->setVisible(false);
#endif #endif
const auto *switchTransferShortcut = new QShortcut(Qt::ALT + Qt::Key_1, this); const auto *switchTransferShortcut = new QShortcut((Qt::ALT | Qt::Key_1), this);
connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab); connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab);
const auto *switchSearchShortcut = new QShortcut(Qt::ALT + Qt::Key_2, this); const auto *switchSearchShortcut = new QShortcut((Qt::ALT | Qt::Key_2), this);
connect(switchSearchShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displaySearchTab)); connect(switchSearchShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displaySearchTab));
const auto *switchRSSShortcut = new QShortcut(Qt::ALT + Qt::Key_3, this); const auto *switchRSSShortcut = new QShortcut((Qt::ALT | Qt::Key_3), this);
connect(switchRSSShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displayRSSTab)); connect(switchRSSShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displayRSSTab));
const auto *switchExecutionLogShortcut = new QShortcut(Qt::ALT + Qt::Key_4, this); const auto *switchExecutionLogShortcut = new QShortcut((Qt::ALT | Qt::Key_4), this);
connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab); connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab);
const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget); const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget);
connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter); connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents); m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
m_ui->actionOptions->setShortcut(Qt::ALT + Qt::Key_O); m_ui->actionOptions->setShortcut(Qt::ALT | Qt::Key_O);
m_ui->actionStatistics->setShortcut(Qt::CTRL + Qt::Key_I); m_ui->actionStatistics->setShortcut(Qt::CTRL | Qt::Key_I);
m_ui->actionStart->setShortcut(Qt::CTRL + Qt::Key_S); m_ui->actionStart->setShortcut(Qt::CTRL | Qt::Key_S);
m_ui->actionStartAll->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_S); m_ui->actionStartAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
m_ui->actionPause->setShortcut(Qt::CTRL + Qt::Key_P); m_ui->actionPause->setShortcut(Qt::CTRL | Qt::Key_P);
m_ui->actionPauseAll->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_P); m_ui->actionPauseAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus); m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus);
m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL + Qt::Key_Minus); m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Minus);
m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL + Qt::Key_Plus); m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Plus);
m_ui->actionTopQueuePos->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Plus); m_ui->actionTopQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M); m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M);
addAction(m_ui->actionMinimize); addAction(m_ui->actionMinimize);
@@ -966,7 +959,7 @@ void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *co
Preferences *const pref = Preferences::instance(); Preferences *const pref = Preferences::instance();
if (pref->recursiveDownloadDisabled()) return; if (pref->recursiveDownloadDisabled()) return;
const auto torrentHash = torrent->hash(); const auto torrentID = torrent->id();
QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation") QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
, tr("The torrent '%1' contains torrent files, do you want to proceed with their download?").arg(torrent->name()) , tr("The torrent '%1' contains torrent files, do you want to proceed with their download?").arg(torrent->name())
@@ -977,10 +970,10 @@ void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *co
const QPushButton *yes = confirmBox->addButton(tr("Yes"), QMessageBox::YesRole); const QPushButton *yes = confirmBox->addButton(tr("Yes"), QMessageBox::YesRole);
/*QPushButton *no = */ confirmBox->addButton(tr("No"), QMessageBox::NoRole); /*QPushButton *no = */ confirmBox->addButton(tr("No"), QMessageBox::NoRole);
const QPushButton *never = confirmBox->addButton(tr("Never"), QMessageBox::NoRole); const QPushButton *never = confirmBox->addButton(tr("Never"), QMessageBox::NoRole);
connect(confirmBox, &QMessageBox::buttonClicked, this, [torrentHash, yes, never](const QAbstractButton *button) connect(confirmBox, &QMessageBox::buttonClicked, this, [torrentID, yes, never](const QAbstractButton *button)
{ {
if (button == yes) if (button == yes)
BitTorrent::Session::instance()->recursiveTorrentDownload(torrentHash); BitTorrent::Session::instance()->recursiveTorrentDownload(torrentID);
if (button == never) if (button == never)
Preferences::instance()->disableRecursiveDownload(); Preferences::instance()->disableRecursiveDownload();
}); });
@@ -1590,15 +1583,21 @@ void MainWindow::loadPreferences(const bool configureSession)
m_propertiesWidget->reloadPreferences(); m_propertiesWidget->reloadPreferences();
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
if (pref->isUpdateCheckEnabled() && !m_wasUpdateCheckEnabled) if (pref->isUpdateCheckEnabled())
{ {
m_wasUpdateCheckEnabled = true; if (!m_programUpdateTimer)
checkProgramUpdate(); {
m_programUpdateTimer = new QTimer(this);
m_programUpdateTimer->setInterval(24h);
m_programUpdateTimer->setSingleShot(true);
connect(m_programUpdateTimer, &QTimer::timeout, this, [this]() { checkProgramUpdate(false); });
m_programUpdateTimer->start();
}
} }
else if (!pref->isUpdateCheckEnabled() && m_wasUpdateCheckEnabled) else
{ {
m_wasUpdateCheckEnabled = false; delete m_programUpdateTimer;
m_programUpdateTimer->stop(); m_programUpdateTimer = nullptr;
} }
#endif #endif
@@ -1804,21 +1803,21 @@ void MainWindow::on_actionOptions_triggered()
void MainWindow::on_actionTopToolBar_triggered() void MainWindow::on_actionTopToolBar_triggered()
{ {
const bool isVisible = static_cast<QAction*>(sender())->isChecked(); const bool isVisible = static_cast<QAction *>(sender())->isChecked();
m_ui->toolBar->setVisible(isVisible); m_ui->toolBar->setVisible(isVisible);
Preferences::instance()->setToolbarDisplayed(isVisible); Preferences::instance()->setToolbarDisplayed(isVisible);
} }
void MainWindow::on_actionShowStatusbar_triggered() void MainWindow::on_actionShowStatusbar_triggered()
{ {
const bool isVisible = static_cast<QAction*>(sender())->isChecked(); const bool isVisible = static_cast<QAction *>(sender())->isChecked();
Preferences::instance()->setStatusbarDisplayed(isVisible); Preferences::instance()->setStatusbarDisplayed(isVisible);
showStatusBar(isVisible); showStatusBar(isVisible);
} }
void MainWindow::on_actionSpeedInTitleBar_triggered() void MainWindow::on_actionSpeedInTitleBar_triggered()
{ {
m_displaySpeedInTitle = static_cast<QAction * >(sender())->isChecked(); m_displaySpeedInTitle = static_cast<QAction *>(sender())->isChecked();
Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle); Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
if (m_displaySpeedInTitle) if (m_displaySpeedInTitle)
reloadSessionStats(); reloadSessionStats();
@@ -1904,35 +1903,56 @@ void MainWindow::on_actionDownloadFromURL_triggered()
} }
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
void MainWindow::handleUpdateCheckFinished(bool updateAvailable, QString newVersion, bool invokedByUser) void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool invokedByUser)
{ {
QMessageBox::StandardButton answer = QMessageBox::Yes;
if (updateAvailable)
{
answer = QMessageBox::question(this, tr("qBittorrent Update Available")
, tr("A new version is available.") + "<br/>"
+ tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>"
+ QString::fromLatin1("<a href=\"https://www.qbittorrent.org/news.php\">%1</a>").arg(tr("Open changelog..."))
, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if (answer == QMessageBox::Yes)
{
// The user want to update, let's download the update
ProgramUpdater *updater = dynamic_cast<ProgramUpdater * >(sender());
updater->updateProgram();
}
}
else if (invokedByUser)
{
QMessageBox::information(this, tr("Already Using the Latest qBittorrent Version"),
tr("No updates available.\nYou are already using the latest version."));
}
sender()->deleteLater();
m_ui->actionCheckForUpdates->setEnabled(true); m_ui->actionCheckForUpdates->setEnabled(true);
m_ui->actionCheckForUpdates->setText(tr("&Check for Updates")); m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates")); m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates"));
// Don't bother the user again in this session if he chose to ignore the update
if (Preferences::instance()->isUpdateCheckEnabled() && (answer == QMessageBox::Yes)) const auto cleanup = [this, updater]()
m_programUpdateTimer->start(); {
if (m_programUpdateTimer)
m_programUpdateTimer->start();
updater->deleteLater();
};
const QString newVersion = updater->getNewVersion();
if (!newVersion.isEmpty())
{
const QString msg {tr("A new version is available.") + "<br/>"
+ tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>"
+ QString::fromLatin1("<a href=\"https://www.qbittorrent.org/news.php\">%1</a>").arg(tr("Open changelog..."))};
auto *msgBox = new QMessageBox {QMessageBox::Question, tr("qBittorrent Update Available"), msg
, (QMessageBox::Yes | QMessageBox::No), this};
msgBox->setAttribute(Qt::WA_DeleteOnClose);
msgBox->setAttribute(Qt::WA_ShowWithoutActivating);
msgBox->setDefaultButton(QMessageBox::Yes);
connect(msgBox, &QMessageBox::buttonClicked, this, [msgBox, updater](QAbstractButton *button)
{
if (msgBox->buttonRole(button) == QMessageBox::YesRole)
{
updater->updateProgram();
}
});
connect(msgBox, &QDialog::finished, this, cleanup);
msgBox->open();
}
else
{
if (invokedByUser)
{
auto *msgBox = new QMessageBox {QMessageBox::Information, QLatin1String("qBittorrent")
, tr("No updates available.\nYou are already using the latest version.")
, QMessageBox::Ok, this};
msgBox->setAttribute(Qt::WA_DeleteOnClose);
connect(msgBox, &QDialog::finished, this, cleanup);
msgBox->open();
}
else
{
cleanup();
}
}
} }
#endif #endif
@@ -2091,18 +2111,23 @@ QIcon MainWindow::getSystrayIcon() const
#endif // Q_OS_MACOS #endif // Q_OS_MACOS
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
void MainWindow::checkProgramUpdate() void MainWindow::checkProgramUpdate(const bool invokedByUser)
{ {
m_programUpdateTimer->stop(); // If the user had clicked the menu item if (m_programUpdateTimer)
m_programUpdateTimer->stop();
m_ui->actionCheckForUpdates->setEnabled(false); m_ui->actionCheckForUpdates->setEnabled(false);
m_ui->actionCheckForUpdates->setText(tr("Checking for Updates...")); m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background")); m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
bool invokedByUser = m_ui->actionCheckForUpdates == qobject_cast<QAction * >(sender());
ProgramUpdater *updater = new ProgramUpdater(this, invokedByUser); auto *updater = new ProgramUpdater(this);
connect(updater, &ProgramUpdater::updateCheckFinished, this, &MainWindow::handleUpdateCheckFinished); connect(updater, &ProgramUpdater::updateCheckFinished
, this, [this, invokedByUser, updater]()
{
handleUpdateCheckFinished(updater, invokedByUser);
});
updater->checkForUpdates(); updater->checkForUpdates();
} }
#endif #endif
#ifdef Q_OS_WIN #ifdef Q_OS_WIN

View File

@@ -47,6 +47,7 @@ class ExecutionLogWidget;
class LineEdit; class LineEdit;
class OptionsDialog; class OptionsDialog;
class PowerManagement; class PowerManagement;
class ProgramUpdater;
class PropertiesWidget; class PropertiesWidget;
class RSSWidget; class RSSWidget;
class SearchWidget; class SearchWidget;
@@ -134,9 +135,6 @@ private slots:
void finishedTorrent(BitTorrent::Torrent *const torrent) const; void finishedTorrent(BitTorrent::Torrent *const torrent) const;
void askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *const torrent); void askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *const torrent);
void optionsSaved(); void optionsSaved();
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
void handleUpdateCheckFinished(bool updateAvailable, QString newVersion, bool invokedByUser);
#endif
void toggleAlternativeSpeeds(); void toggleAlternativeSpeeds();
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@@ -177,9 +175,7 @@ private slots:
void on_actionLock_triggered(); void on_actionLock_triggered();
// Check for unpaused downloading or seeding torrents and prevent system suspend/sleep according to preferences // Check for unpaused downloading or seeding torrents and prevent system suspend/sleep according to preferences
void updatePowerManagementState(); void updatePowerManagementState();
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
void checkProgramUpdate();
#endif
void toolbarMenuRequested(const QPoint &point); void toolbarMenuRequested(const QPoint &point);
void toolbarIconsOnly(); void toolbarIconsOnly();
void toolbarTextOnly(); void toolbarTextOnly();
@@ -252,10 +248,13 @@ private:
// Power Management // Power Management
PowerManagement *m_pwr; PowerManagement *m_pwr;
QTimer *m_preventTimer; QTimer *m_preventTimer;
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
QTimer *m_programUpdateTimer;
bool m_wasUpdateCheckEnabled;
#endif
bool m_hasPython; bool m_hasPython;
QMenu *m_toolbarMenu; QMenu *m_toolbarMenu;
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
void checkProgramUpdate(bool invokedByUser);
void handleUpdateCheckFinished(ProgramUpdater *updater, bool invokedByUser);
QTimer *m_programUpdateTimer = nullptr;
#endif
}; };

View File

@@ -211,8 +211,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
m_ui->IpFilterRefreshBtn->setIcon(UIThemeManager::instance()->getIcon("view-refresh")); m_ui->IpFilterRefreshBtn->setIcon(UIThemeManager::instance()->getIcon("view-refresh"));
m_ui->labelGlobalRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow_off")), this, 16)); m_ui->labelGlobalRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow_off")), this, 24));
m_ui->labelAltRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow")), this, 16)); m_ui->labelAltRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow")), this, 24));
m_ui->deleteTorrentWarningIcon->setPixmap(QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical).pixmap(16, 16)); m_ui->deleteTorrentWarningIcon->setPixmap(QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical).pixmap(16, 16));
m_ui->deleteTorrentWarningIcon->hide(); m_ui->deleteTorrentWarningIcon->hide();
@@ -233,15 +233,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
m_ui->hsplitter->setCollapsible(0, false); m_ui->hsplitter->setCollapsible(0, false);
m_ui->hsplitter->setCollapsible(1, false); m_ui->hsplitter->setCollapsible(1, false);
// Get apply button in button box // Get apply button in button box
const QList<QAbstractButton *> buttons = m_ui->buttonBox->buttons(); m_applyButton = m_ui->buttonBox->button(QDialogButtonBox::Apply);
for (QAbstractButton *button : buttons) connect(m_applyButton, &QPushButton::clicked, this, &OptionsDialog::applySettings);
{
if (m_ui->buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole)
{
m_applyButton = button;
break;
}
}
m_ui->scanFoldersView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); m_ui->scanFoldersView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
m_ui->scanFoldersView->setModel(ScanFoldersModel::instance()); m_ui->scanFoldersView->setModel(ScanFoldersModel::instance());
@@ -249,7 +242,6 @@ OptionsDialog::OptionsDialog(QWidget *parent)
connect(ScanFoldersModel::instance(), &QAbstractListModel::dataChanged, this, &ThisType::enableApplyButton); connect(ScanFoldersModel::instance(), &QAbstractListModel::dataChanged, this, &ThisType::enableApplyButton);
connect(m_ui->scanFoldersView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ThisType::handleScanFolderViewSelectionChanged); connect(m_ui->scanFoldersView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ThisType::handleScanFolderViewSelectionChanged);
connect(m_ui->buttonBox, &QDialogButtonBox::clicked, this, &OptionsDialog::applySettings);
// Languages supported // Languages supported
initializeLanguageCombo(); initializeLanguageCombo();
@@ -761,7 +753,7 @@ void OptionsDialog::saveOptions()
pref->setMailNotificationSMTPPassword(m_ui->mailNotifPassword->text()); pref->setMailNotificationSMTPPassword(m_ui->mailNotifPassword->text());
pref->setAutoRunEnabled(m_ui->autoRunBox->isChecked()); pref->setAutoRunEnabled(m_ui->autoRunBox->isChecked());
pref->setAutoRunProgram(m_ui->lineEditAutoRun->text().trimmed()); pref->setAutoRunProgram(m_ui->lineEditAutoRun->text().trimmed());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) && defined(Q_OS_WIN) #if defined(Q_OS_WIN)
pref->setAutoRunConsoleEnabled(m_ui->autoRunConsole->isChecked()); pref->setAutoRunConsoleEnabled(m_ui->autoRunConsole->isChecked());
#endif #endif
pref->setActionOnDblClOnTorrentDl(getActionOnDblClOnTorrentDl()); pref->setActionOnDblClOnTorrentDl(getActionOnDblClOnTorrentDl());
@@ -1051,7 +1043,7 @@ void OptionsDialog::loadOptions()
m_ui->autoRunBox->setChecked(pref->isAutoRunEnabled()); m_ui->autoRunBox->setChecked(pref->isAutoRunEnabled());
m_ui->lineEditAutoRun->setText(pref->getAutoRunProgram()); m_ui->lineEditAutoRun->setText(pref->getAutoRunProgram());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) && defined(Q_OS_WIN) #if defined(Q_OS_WIN)
m_ui->autoRunConsole->setChecked(pref->isAutoRunConsoleEnabled()); m_ui->autoRunConsole->setChecked(pref->isAutoRunConsoleEnabled());
#else #else
m_ui->autoRunConsole->hide(); m_ui->autoRunConsole->hide();
@@ -1439,27 +1431,24 @@ void OptionsDialog::on_buttonBox_accepted()
accept(); accept();
} }
void OptionsDialog::applySettings(QAbstractButton *button) void OptionsDialog::applySettings()
{ {
if (button == m_applyButton) if (!schedTimesOk())
{ {
if (!schedTimesOk()) m_ui->tabSelection->setCurrentRow(TAB_SPEED);
{ return;
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
return;
}
if (!webUIAuthenticationOk())
{
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return;
}
if (!isAlternativeWebUIPathValid())
{
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return;
}
saveOptions();
} }
if (!webUIAuthenticationOk())
{
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return;
}
if (!isAlternativeWebUIPathValid())
{
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
return;
}
saveOptions();
} }
void OptionsDialog::closeEvent(QCloseEvent *e) void OptionsDialog::closeEvent(QCloseEvent *e)

View File

@@ -32,7 +32,6 @@
#include "base/settingvalue.h" #include "base/settingvalue.h"
class QAbstractButton;
class QCloseEvent; class QCloseEvent;
class QListWidgetItem; class QListWidgetItem;
@@ -95,7 +94,7 @@ private slots:
void on_buttonBox_accepted(); void on_buttonBox_accepted();
void closeEvent(QCloseEvent *e) override; void closeEvent(QCloseEvent *e) override;
void on_buttonBox_rejected(); void on_buttonBox_rejected();
void applySettings(QAbstractButton *button); void applySettings();
void enableApplyButton(); void enableApplyButton();
void toggleComboRatioLimitAct(); void toggleComboRatioLimitAct();
void changePage(QListWidgetItem *, QListWidgetItem *); void changePage(QListWidgetItem *, QListWidgetItem *);
@@ -181,7 +180,7 @@ private:
SettingValue<QSize> m_storeDialogSize; SettingValue<QSize> m_storeDialogSize;
SettingValue<QStringList> m_storeHSplitterSize; SettingValue<QStringList> m_storeHSplitterSize;
QAbstractButton *m_applyButton; QPushButton *m_applyButton;
AdvancedSettings *m_advancedSettings; AdvancedSettings *m_advancedSettings;

View File

@@ -28,18 +28,11 @@
#include "previewlistdelegate.h" #include "previewlistdelegate.h"
#include <QApplication>
#include <QModelIndex> #include <QModelIndex>
#include <QPainter> #include <QPainter>
#include <QStyleOptionProgressBar>
#include <QStyleOptionViewItem> #include <QStyleOptionViewItem>
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
#include <QProxyStyle>
#endif
#include "base/utils/misc.h" #include "base/utils/misc.h"
#include "base/utils/string.h"
#include "previewselectdialog.h" #include "previewselectdialog.h"
PreviewListDelegate::PreviewListDelegate(QObject *parent) PreviewListDelegate::PreviewListDelegate(QObject *parent)
@@ -61,30 +54,19 @@ void PreviewListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
break; break;
case PreviewSelectDialog::PROGRESS: case PreviewSelectDialog::PROGRESS:
{ {
const qreal progress = (index.data().toReal() * 100); const qreal progress = (index.data().toReal() * 100);
const QString text = (progress >= 100)
? QString::fromLatin1("100%")
: (Utils::String::fromDouble(progress, 1) + '%');
QStyleOptionProgressBar newopt; m_progressBarPainter.paint(painter, option, text, static_cast<int>(progress));
newopt.rect = opt.rect;
newopt.text = ((progress == 100) ? QString("100%") : (Utils::String::fromDouble(progress, 1) + '%'));
newopt.progress = static_cast<int>(progress);
newopt.maximum = 100;
newopt.minimum = 0;
newopt.state |= QStyle::State_Enabled;
newopt.textVisible = true;
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
// XXX: To avoid having the progress text on the right of the bar
QProxyStyle st("fusion");
st.drawControl(QStyle::CE_ProgressBar, &newopt, painter, 0);
#else
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &newopt, painter);
#endif
} }
break; break;
default: default:
QItemDelegate::paint(painter, option, index); QItemDelegate::paint(painter, option, index);
break;
} }
painter->restore(); painter->restore();

View File

@@ -30,6 +30,8 @@
#include <QItemDelegate> #include <QItemDelegate>
#include "progressbarpainter.h"
class PreviewListDelegate final : public QItemDelegate class PreviewListDelegate final : public QItemDelegate
{ {
Q_OBJECT Q_OBJECT
@@ -38,7 +40,9 @@ class PreviewListDelegate final : public QItemDelegate
public: public:
explicit PreviewListDelegate(QObject *parent = nullptr); explicit PreviewListDelegate(QObject *parent = nullptr);
private:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QWidget *createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const override; QWidget *createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const override;
private:
ProgressBarPainter m_progressBarPainter;
}; };

View File

@@ -90,9 +90,8 @@ PreviewSelectDialog::PreviewSelectDialog(QWidget *parent, const BitTorrent::Torr
{ {
QString fileName = torrent->fileName(i); QString fileName = torrent->fileName(i);
if (fileName.endsWith(QB_EXT)) if (fileName.endsWith(QB_EXT))
fileName.chop(4); fileName.chop(QB_EXT.length());
QString extension = Utils::Fs::fileExtension(fileName).toUpper(); if (Utils::Misc::isPreviewable(fileName))
if (Utils::Misc::isPreviewable(extension))
{ {
int row = m_previewListModel->rowCount(); int row = m_previewListModel->rowCount();
m_previewListModel->insertRow(row); m_previewListModel->insertRow(row);

View File

@@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Mike Tzou (Chocobo1)
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2010 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
@@ -36,7 +37,6 @@
#include <QDebug> #include <QDebug>
#include <QDesktopServices> #include <QDesktopServices>
#include <QRegularExpression> #include <QRegularExpression>
#include <QStringList>
#include <QXmlStreamReader> #include <QXmlStreamReader>
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
@@ -44,56 +44,82 @@
#endif #endif
#include "base/net/downloadmanager.h" #include "base/net/downloadmanager.h"
#include "base/utils/version.h"
#include "base/version.h" #include "base/version.h"
namespace namespace
{ {
const QString RSS_URL {QStringLiteral("https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml")}; bool isVersionMoreRecent(const QString &remoteVersion)
{
using Version = Utils::Version<int, 4, 3>;
QString getStringValue(QXmlStreamReader &xml); try
{
const Version newVersion {remoteVersion};
const Version currentVersion {QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD};
if (newVersion == currentVersion)
{
const bool isDevVersion = QString::fromLatin1(QBT_VERSION_STATUS).contains(
QRegularExpression(QLatin1String("(alpha|beta|rc)")));
if (isDevVersion)
return true;
}
return (newVersion > currentVersion);
}
catch (const std::runtime_error &)
{
return false;
}
}
} }
ProgramUpdater::ProgramUpdater(QObject *parent, bool invokedByUser) void ProgramUpdater::checkForUpdates() const
: QObject(parent)
, m_invokedByUser(invokedByUser)
{
}
void ProgramUpdater::checkForUpdates()
{ {
const auto RSS_URL = QString::fromLatin1("https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml");
// Don't change this User-Agent. In case our updater goes haywire, // Don't change this User-Agent. In case our updater goes haywire,
// the filehost can identify it and contact us. // the filehost can identify it and contact us.
Net::DownloadManager::instance()->download( Net::DownloadManager::instance()->download(
Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)") Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)")
, this, &ProgramUpdater::rssDownloadFinished); , this, &ProgramUpdater::rssDownloadFinished);
}
QString ProgramUpdater::getNewVersion() const
{
return m_newVersion;
} }
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result) void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
{ {
if (result.status != Net::DownloadStatus::Success) if (result.status != Net::DownloadStatus::Success)
{ {
qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString; qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString;
emit updateCheckFinished(false, QString(), m_invokedByUser); emit updateCheckFinished();
return; return;
} }
qDebug("Finished downloading the new qBittorrent updates RSS"); qDebug("Finished downloading the new qBittorrent updates RSS");
const auto getStringValue = [](QXmlStreamReader &xml) -> QString
{
xml.readNext();
return (xml.isCharacters() && !xml.isWhitespace())
? xml.text().toString()
: QString {};
};
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
const QString OS_TYPE {"Mac OS X"}; const QString OS_TYPE {"Mac OS X"};
#elif defined(Q_OS_WIN) #elif defined(Q_OS_WIN)
const QString OS_TYPE const QString OS_TYPE {(::IsWindows7OrGreater()
{(::IsWindows7OrGreater() && QSysInfo::currentCpuArchitecture().endsWith("64"))
&& QSysInfo::currentCpuArchitecture().endsWith("64"))
? "Windows x64" : "Windows"}; ? "Windows x64" : "Windows"};
#endif #endif
QString version;
QXmlStreamReader xml(result.data);
bool inItem = false; bool inItem = false;
QString version;
QString updateLink; QString updateLink;
QString type; QString type;
QXmlStreamReader xml(result.data);
while (!xml.atEnd()) while (!xml.atEnd())
{ {
@@ -121,7 +147,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
{ {
qDebug("Detected version is %s", qUtf8Printable(version)); qDebug("Detected version is %s", qUtf8Printable(version));
if (isVersionMoreRecent(version)) if (isVersionMoreRecent(version))
m_updateUrl = updateLink; {
m_newVersion = version;
m_updateURL = updateLink;
}
} }
break; break;
} }
@@ -134,51 +163,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
} }
} }
emit updateCheckFinished(!m_updateUrl.isEmpty(), version, m_invokedByUser); emit updateCheckFinished();
} }
void ProgramUpdater::updateProgram() bool ProgramUpdater::updateProgram() const
{ {
Q_ASSERT(!m_updateUrl.isEmpty()); return QDesktopServices::openUrl(m_updateURL);
QDesktopServices::openUrl(m_updateUrl);
return;
}
bool ProgramUpdater::isVersionMoreRecent(const QString &remoteVersion) const
{
const QRegularExpressionMatch regVerMatch = QRegularExpression("([0-9.]+)").match(QBT_VERSION);
if (regVerMatch.hasMatch())
{
const QString localVersion = regVerMatch.captured(1);
const QVector<QStringRef> remoteParts = remoteVersion.splitRef('.');
const QVector<QStringRef> localParts = localVersion.splitRef('.');
for (int i = 0; i < qMin(remoteParts.size(), localParts.size()); ++i)
{
if (remoteParts[i].toInt() > localParts[i].toInt())
return true;
if (remoteParts[i].toInt() < localParts[i].toInt())
return false;
}
// Compared parts were equal, if remote version is longer, then it's more recent (2.9.2.1 > 2.9.2)
if (remoteParts.size() > localParts.size())
return true;
// versions are equal, check if the local version is a development release, in which case it is older (2.9.2beta < 2.9.2)
const QRegularExpressionMatch regDevelMatch = QRegularExpression("(alpha|beta|rc)").match(QBT_VERSION);
if (regDevelMatch.hasMatch())
return true;
}
return false;
}
namespace
{
QString getStringValue(QXmlStreamReader &xml)
{
xml.readNext();
if (xml.isCharacters() && !xml.isWhitespace())
return xml.text().toString();
return {};
}
} }

View File

@@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Mike Tzou (Chocobo1)
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org> * Copyright (C) 2010 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,32 +30,33 @@
#pragma once #pragma once
#include <QObject> #include <QObject>
#include <QString>
#include <QUrl>
namespace Net namespace Net
{ {
struct DownloadResult; struct DownloadResult;
} }
class ProgramUpdater : public QObject class ProgramUpdater final : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(ProgramUpdater) Q_DISABLE_COPY(ProgramUpdater)
public: public:
explicit ProgramUpdater(QObject *parent = nullptr, bool invokedByUser = false); using QObject::QObject;
void checkForUpdates(); void checkForUpdates() const;
void updateProgram(); QString getNewVersion() const;
bool updateProgram() const;
signals: signals:
void updateCheckFinished(bool updateAvailable, QString version, bool invokedByUser); void updateCheckFinished();
private slots: private slots:
void rssDownloadFinished(const Net::DownloadResult &result); void rssDownloadFinished(const Net::DownloadResult &result);
private: private:
bool isVersionMoreRecent(const QString &remoteVersion) const; QString m_newVersion;
QUrl m_updateURL;
QString m_updateUrl;
bool m_invokedByUser;
}; };

View File

@@ -26,48 +26,46 @@
* exception statement from your version. * exception statement from your version.
*/ */
#include "progressbardelegate.h" #include "progressbarpainter.h"
#include <QApplication>
#include <QModelIndex>
#include <QPainter> #include <QPainter>
#include <QPalette>
#include <QStyleOptionProgressBar>
#include <QStyleOptionViewItem> #include <QStyleOptionViewItem>
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS)) #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
#include <QProxyStyle> #include <QProxyStyle>
#endif #endif
ProgressBarDelegate::ProgressBarDelegate(const int progressColumn, const int dataRole, QObject *parent) ProgressBarPainter::ProgressBarPainter()
: QStyledItemDelegate {parent}
, m_progressColumn {progressColumn}
, m_dataRole {dataRole}
{ {
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS)) #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
m_dummyProgressBar.setStyle(new QProxyStyle {"fusion"}); auto *fusionStyle = new QProxyStyle {"fusion"};
fusionStyle->setParent(&m_dummyProgressBar);
m_dummyProgressBar.setStyle(fusionStyle);
#endif #endif
} }
void ProgressBarDelegate::initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const void ProgressBarPainter::paint(QPainter *painter, const QStyleOptionViewItem &option, const QString &text, const int progress) const
{ {
option.text = index.data().toString(); QStyleOptionProgressBar styleOption;
option.progress = static_cast<int>(index.data(m_dataRole).toReal()); styleOption.initFrom(&m_dummyProgressBar);
option.maximum = 100; // QStyleOptionProgressBar fields
option.minimum = 0; styleOption.maximum = 100;
option.state |= (QStyle::State_Enabled | QStyle::State_Horizontal); styleOption.minimum = 0;
option.textVisible = true; styleOption.progress = progress;
} styleOption.text = text;
styleOption.textVisible = true;
// QStyleOption fields
styleOption.rect = option.rect;
styleOption.state = option.state;
void ProgressBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const const bool isEnabled = option.state.testFlag(QStyle::State_Enabled);
{ styleOption.palette.setCurrentColorGroup(isEnabled ? QPalette::Active : QPalette::Disabled);
if (index.column() != m_progressColumn)
return QStyledItemDelegate::paint(painter, option, index);
QStyleOptionProgressBar newopt;
newopt.initFrom(&m_dummyProgressBar);
newopt.rect = option.rect;
initProgressStyleOption(newopt, index);
painter->save(); painter->save();
m_dummyProgressBar.style()->drawControl(QStyle::CE_ProgressBar, &newopt, painter, &m_dummyProgressBar); const QStyle *style = m_dummyProgressBar.style();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter);
style->drawControl(QStyle::CE_ProgressBar, &styleOption, painter, &m_dummyProgressBar);
painter->restore(); painter->restore();
} }

View File

@@ -29,23 +29,17 @@
#pragma once #pragma once
#include <QProgressBar> #include <QProgressBar>
#include <QStyledItemDelegate>
class QStyleOptionProgressBar; class QStyleOptionViewItem;
class ProgressBarDelegate : public QStyledItemDelegate class ProgressBarPainter
{ {
public: public:
ProgressBarDelegate(int progressColumn, int dataRole, QObject *parent = nullptr); ProgressBarPainter();
protected: void paint(QPainter *painter, const QStyleOptionViewItem &option, const QString &text, int progress) const;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
virtual void initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const;
private: private:
const int m_progressColumn;
const int m_dataRole;
// for painting progressbar with stylesheet option, a dummy progress bar is required // for painting progressbar with stylesheet option, a dummy progress bar is required
QProgressBar m_dummyProgressBar; QProgressBar m_dummyProgressBar;
}; };

View File

@@ -270,8 +270,8 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
// Do not allow user to add peers in a private torrent // Do not allow user to add peers in a private torrent
if (!torrent->isQueued() && !torrent->isChecking() && !torrent->isPrivate()) if (!torrent->isQueued() && !torrent->isChecking() && !torrent->isPrivate())
{ {
const QAction *addPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("user-group-new"), tr("Add a new peer...")); menu->addAction(UIThemeManager::instance()->getIcon("user-group-new"), tr("Add a new peer...")
connect(addPeerAct, &QAction::triggered, this, [this, torrent]() , this, [this, torrent]()
{ {
const QVector<BitTorrent::PeerAddress> peersList = PeersAdditionDialog::askForPeers(this); const QVector<BitTorrent::PeerAddress> peersList = PeersAdditionDialog::askForPeers(this);
const int peerCount = std::count_if(peersList.cbegin(), peersList.cend(), [torrent](const BitTorrent::PeerAddress &peer) const int peerCount = std::count_if(peersList.cbegin(), peersList.cend(), [torrent](const BitTorrent::PeerAddress &peer)
@@ -287,13 +287,11 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
if (!selectionModel()->selectedRows().isEmpty()) if (!selectionModel()->selectedRows().isEmpty())
{ {
const QAction *copyPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy IP:port")); menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy IP:port")
connect(copyPeerAct, &QAction::triggered, this, &PeerListWidget::copySelectedPeers); , this, &PeerListWidget::copySelectedPeers);
menu->addSeparator(); menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon("user-group-delete"), tr("Ban peer permanently")
const QAction *banAct = menu->addAction(UIThemeManager::instance()->getIcon("user-group-delete"), tr("Ban peer permanently")); , this, &PeerListWidget::banSelectedPeers);
connect(banAct, &QAction::triggered, this, &PeerListWidget::banSelectedPeers);
} }
if (menu->isEmpty()) if (menu->isEmpty())

View File

@@ -28,7 +28,6 @@
#include "propertieswidget.h" #include "propertieswidget.h"
#include <QAction>
#include <QClipboard> #include <QClipboard>
#include <QDateTime> #include <QDateTime>
#include <QDebug> #include <QDebug>
@@ -313,8 +312,9 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent)
// Save path // Save path
updateSavePath(m_torrent); updateSavePath(m_torrent);
// Hash // Info hash (Truncated info hash (torrent ID) with libtorrent2)
m_ui->labelHashVal->setText(m_torrent->hash()); // TODO: Update label for this property to express its meaning more clearly (or change it to display real info hash(es))
m_ui->labelHashVal->setText(m_torrent->id().toString());
m_propListModel->model()->clear(); m_propListModel->model()->clear();
if (m_torrent->hasMetadata()) if (m_torrent->hasMetadata())
{ {
@@ -586,57 +586,82 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &)
{ {
const QModelIndex index = selectedRows[0]; const QModelIndex index = selectedRows[0];
const QAction *actOpen = menu->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open")); menu->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open")
connect(actOpen, &QAction::triggered, this, [this, index]() { openItem(index); }); , this, [this, index]() { openItem(index); });
menu->addAction(UIThemeManager::instance()->getIcon("inode-directory"), tr("Open Containing Folder")
const QAction *actOpenContainingFolder = menu->addAction(UIThemeManager::instance()->getIcon("inode-directory"), tr("Open Containing Folder")); , this, [this, index]() { openParentFolder(index); });
connect(actOpenContainingFolder, &QAction::triggered, this, [this, index]() { openParentFolder(index); }); menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); });
const QAction *actRename = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."));
connect(actRename, &QAction::triggered, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); });
menu->addSeparator(); menu->addSeparator();
} }
if (!m_torrent->isSeed()) if (!m_torrent->isSeed())
{ {
QMenu *subMenu = menu->addMenu(tr("Priority")); const auto applyPriorities = [this](const BitTorrent::DownloadPriority prio)
const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio)
{ {
const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
for (const QModelIndex &index : selectedRows) for (const QModelIndex &index : selectedRows)
{ {
m_propListModel->setData( m_propListModel->setData(index.sibling(index.row(), PRIORITY)
m_propListModel->index(index.row(), PRIORITY, index.parent()), static_cast<int>(prio)); , static_cast<int>(prio));
} }
// Save changes // Save changes
filteredFilesChanged(); filteredFilesChanged();
}; };
connect(m_ui->actionNotDownloaded, &QAction::triggered, subMenu, [applyPriorities]() QMenu *subMenu = menu->addMenu(tr("Priority"));
subMenu->addAction(tr("Do not download"), subMenu, [applyPriorities]()
{ {
applyPriorities(BitTorrent::DownloadPriority::Ignored); applyPriorities(BitTorrent::DownloadPriority::Ignored);
}); });
subMenu->addAction(m_ui->actionNotDownloaded); subMenu->addAction(tr("Normal"), subMenu, [applyPriorities]()
connect(m_ui->actionNormal, &QAction::triggered, subMenu, [applyPriorities]()
{ {
applyPriorities(BitTorrent::DownloadPriority::Normal); applyPriorities(BitTorrent::DownloadPriority::Normal);
}); });
subMenu->addAction(m_ui->actionNormal); subMenu->addAction(tr("High"), subMenu, [applyPriorities]()
connect(m_ui->actionHigh, &QAction::triggered, subMenu, [applyPriorities]()
{ {
applyPriorities(BitTorrent::DownloadPriority::High); applyPriorities(BitTorrent::DownloadPriority::High);
}); });
subMenu->addAction(m_ui->actionHigh); subMenu->addAction(tr("Maximum"), subMenu, [applyPriorities]()
connect(m_ui->actionMaximum, &QAction::triggered, subMenu, [applyPriorities]()
{ {
applyPriorities(BitTorrent::DownloadPriority::Maximum); applyPriorities(BitTorrent::DownloadPriority::Maximum);
}); });
subMenu->addAction(m_ui->actionMaximum); subMenu->addSeparator();
subMenu->addAction(tr("By shown file order"), subMenu, [this]()
{
// Equally distribute the selected items into groups and for each group assign
// a download priority that will apply to each item. The number of groups depends on how
// many "download priority" are available to be assigned
const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
const int priorityGroups = 3;
const int priorityGroupSize = std::max((selectedRows.length() / priorityGroups), 1);
for (int i = 0; i < selectedRows.length(); ++i)
{
auto priority = BitTorrent::DownloadPriority::Ignored;
switch (i / priorityGroupSize)
{
case 0:
priority = BitTorrent::DownloadPriority::Maximum;
break;
case 1:
priority = BitTorrent::DownloadPriority::High;
break;
default:
case 2:
priority = BitTorrent::DownloadPriority::Normal;
break;
}
const QModelIndex &index = selectedRows[i];
m_propListModel->setData(index.sibling(index.row(), PRIORITY)
, static_cast<int>(priority));
}
});
} }
// The selected torrent might have disappeared during exec() // The selected torrent might have disappeared during exec()
@@ -660,21 +685,17 @@ void PropertiesWidget::displayWebSeedListMenu(const QPoint &)
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
const QAction *actAdd = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New Web seed")); menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New Web seed"), this, &PropertiesWidget::askWebSeed);
connect(actAdd, &QAction::triggered, this, &PropertiesWidget::askWebSeed);
if (!rows.isEmpty()) if (!rows.isEmpty())
{ {
const QAction *actDel = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove Web seed")); menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove Web seed")
connect(actDel, &QAction::triggered, this, &PropertiesWidget::deleteSelectedUrlSeeds); , this, &PropertiesWidget::deleteSelectedUrlSeeds);
menu->addSeparator(); menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy Web seed URL")
const QAction *actCpy = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy Web seed URL")); , this, &PropertiesWidget::copySelectedWebSeedsToClipboard);
connect(actCpy, &QAction::triggered, this, &PropertiesWidget::copySelectedWebSeedsToClipboard); menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Edit Web seed URL")
, this, &PropertiesWidget::editWebSeed);
const QAction *actEdit = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Edit Web seed URL"));
connect(actEdit, &QAction::triggered, this, &PropertiesWidget::editWebSeed);
} }
menu->popup(QCursor::pos()); menu->popup(QCursor::pos());
@@ -780,7 +801,7 @@ void PropertiesWidget::editWebSeed()
if (!m_ui->listWebSeeds->findItems(newSeed, Qt::MatchFixedString).empty()) if (!m_ui->listWebSeeds->findItems(newSeed, Qt::MatchFixedString).empty())
{ {
QMessageBox::warning(this, tr("qBittorrent"), QMessageBox::warning(this, QLatin1String("qBittorrent"),
tr("This URL seed is already in the list."), tr("This URL seed is already in the list."),
QMessageBox::Ok); QMessageBox::Ok);
return; return;
@@ -804,7 +825,8 @@ void PropertiesWidget::filteredFilesChanged()
void PropertiesWidget::filterText(const QString &filter) void PropertiesWidget::filterText(const QString &filter)
{ {
m_propListModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive, QRegExp::WildcardUnix)); const QString pattern = Utils::String::wildcardToRegexPattern(filter);
m_propListModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
if (filter.isEmpty()) if (filter.isEmpty())
{ {
m_ui->filesList->collapseAll(); m_ui->filesList->collapseAll();

View File

@@ -1086,29 +1086,6 @@
</widget> </widget>
</item> </item>
</layout> </layout>
<action name="actionNotDownloaded">
<property name="text">
<string>Do not download</string>
</property>
<property name="toolTip">
<string>Do not download</string>
</property>
</action>
<action name="actionMaximum">
<property name="text">
<string>Maximum</string>
</property>
</action>
<action name="actionHigh">
<property name="text">
<string>High</string>
</property>
</action>
<action name="actionNormal">
<property name="text">
<string>Normal</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View File

@@ -28,61 +28,22 @@
#include "proplistdelegate.h" #include "proplistdelegate.h"
#include <QApplication>
#include <QComboBox> #include <QComboBox>
#include <QModelIndex> #include <QModelIndex>
#include <QPainter> #include <QPainter>
#include <QPalette>
#include <QProgressBar> #include <QProgressBar>
#include <QStyleOptionProgressBar>
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
#include <QProxyStyle>
#endif
#include "base/bittorrent/downloadpriority.h" #include "base/bittorrent/downloadpriority.h"
#include "base/bittorrent/torrent.h" #include "base/bittorrent/torrent.h"
#include "gui/torrentcontentmodel.h" #include "gui/torrentcontentmodel.h"
#include "propertieswidget.h" #include "propertieswidget.h"
namespace
{
QPalette progressBarDisabledPalette()
{
static const QPalette palette = []()
{
QProgressBar bar;
bar.setEnabled(false);
QStyleOptionProgressBar opt;
opt.initFrom(&bar);
return opt.palette;
}();
return palette;
}
}
PropListDelegate::PropListDelegate(PropertiesWidget *properties) PropListDelegate::PropListDelegate(PropertiesWidget *properties)
: ProgressBarDelegate {PROGRESS, TorrentContentModel::UnderlyingDataRole, properties} : QStyledItemDelegate {properties}
, m_properties(properties) , m_properties {properties}
{ {
} }
void PropListDelegate::initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const
{
ProgressBarDelegate::initProgressStyleOption(option, index);
const int priority
= index.sibling(index.row(), PRIORITY).data(TorrentContentModel::UnderlyingDataRole).toInt();
if (static_cast<BitTorrent::DownloadPriority>(priority) == BitTorrent::DownloadPriority::Ignored)
{
option.state &= ~QStyle::State_Enabled;
option.palette = progressBarDisabledPalette();
}
else
{
option.state |= QStyle::State_Enabled;
}
}
void PropListDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const void PropListDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{ {
auto *combobox = static_cast<QComboBox *>(editor); auto *combobox = static_cast<QComboBox *>(editor);
@@ -156,3 +117,25 @@ void PropListDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionV
{ {
editor->setGeometry(option.rect); editor->setGeometry(option.rect);
} }
void PropListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
switch (index.column())
{
case PropColumn::PROGRESS:
{
const int progress = static_cast<int>(index.data(TorrentContentModel::UnderlyingDataRole).toReal());
const int priority = index.sibling(index.row(), PropColumn::PRIORITY).data(TorrentContentModel::UnderlyingDataRole).toInt();
const bool isEnabled = static_cast<BitTorrent::DownloadPriority>(priority) != BitTorrent::DownloadPriority::Ignored;
QStyleOptionViewItem customOption {option};
customOption.state.setFlag(QStyle::State_Enabled, isEnabled);
m_progressBarPainter.paint(painter, customOption, index.data().toString(), progress);
}
break;
default:
QStyledItemDelegate::paint(painter, option, index);
break;
}
}

View File

@@ -28,11 +28,12 @@
#pragma once #pragma once
#include "gui/progressbardelegate.h" #include <QStyledItemDelegate>
#include "gui/progressbarpainter.h"
class QAbstractItemModel; class QAbstractItemModel;
class QModelIndex; class QModelIndex;
class QPainter;
class QStyleOptionViewItem; class QStyleOptionViewItem;
class PropertiesWidget; class PropertiesWidget;
@@ -48,25 +49,26 @@ enum PropColumn
AVAILABILITY AVAILABILITY
}; };
class PropListDelegate final : public ProgressBarDelegate class PropListDelegate final : public QStyledItemDelegate
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(PropListDelegate)
public: public:
explicit PropListDelegate(PropertiesWidget *properties); explicit PropListDelegate(PropertiesWidget *properties);
void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, const QModelIndex &index) const override; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
public slots: public slots:
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex & /* index */) const override; void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
signals: signals:
void filteredFilesChanged() const; void filteredFilesChanged() const;
private: private:
void initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const override;
PropertiesWidget *m_properties; PropertiesWidget *m_properties;
ProgressBarPainter m_progressBarPainter;
}; };

View File

@@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Prince Gupta <guptaprince8832@gmail.com>
* Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru> * Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -34,33 +35,13 @@
#include <QPainter> #include <QPainter>
#include <QPen> #include <QPen>
#include "base/bittorrent/session.h"
#include "base/global.h" #include "base/global.h"
#include "base/unicodestrings.h" #include "base/unicodestrings.h"
#include "base/utils/misc.h" #include "base/utils/misc.h"
namespace namespace
{ {
enum PeriodInSeconds
{
MIN1_SEC = 60,
MIN5_SEC = 5 * 60,
MIN30_SEC = 30 * 60,
HOUR6_SEC = 6 * 60 * 60,
HOUR12_SEC = 12 * 60 * 60,
HOUR24_SEC = 24 * 60 * 60
};
const int MIN5_BUF_SIZE = 5 * 60;
const int MIN30_BUF_SIZE = 5 * 60;
const int HOUR6_BUF_SIZE = 5 * 60;
const int HOUR12_BUF_SIZE = 10 * 60;
const int HOUR24_BUF_SIZE = 10 * 60;
const int DIVIDER_30MIN = MIN30_SEC / MIN30_BUF_SIZE;
const int DIVIDER_6HOUR = HOUR6_SEC / HOUR6_BUF_SIZE;
const int DIVIDER_12HOUR = HOUR12_SEC / HOUR12_BUF_SIZE;
const int DIVIDER_24HOUR = HOUR24_SEC / HOUR24_BUF_SIZE;
// table of supposed nice steps for grid marks to get nice looking quarters of scale // table of supposed nice steps for grid marks to get nice looking quarters of scale
const double roundingTable[] = {1.2, 1.6, 2, 2.4, 2.8, 3.2, 4, 6, 8}; const double roundingTable[] = {1.2, 1.6, 2, 2.4, 2.8, 3.2, 4, 6, 8};
@@ -118,55 +99,69 @@ namespace
} }
} }
SpeedPlotView::Averager::Averager(int divider, boost::circular_buffer<PointData> &sink) SpeedPlotView::Averager::Averager(const milliseconds duration, const milliseconds resolution)
: m_divider(divider) : m_resolution {resolution}
, m_sink(sink) , m_maxDuration {duration}
, m_counter(0) , m_sink {static_cast<DataCircularBuffer::size_type>(duration / resolution)}
, m_accumulator {}
{ {
m_lastSampleTime.start();
} }
void SpeedPlotView::Averager::push(const PointData &pointData) bool SpeedPlotView::Averager::push(const SampleData &sampleData)
{ {
// Accumulator overflow will be hit in worst case on longest used averaging span, // Accumulator overflow will be hit in worst case on longest used averaging span,
// defined by divider value. Maximum divider is DIVIDER_24HOUR = 144 // defined by divider value. Maximum divider is DIVIDER_24HOUR = 144
// Using int32 for accumulator we get overflow when transfer speed reaches 2^31/144 ~~ 14.2 MBytes/s. // Using int32 for accumulator we get overflow when transfer speed reaches 2^31/144 ~~ 14.2 MBytes/s.
// With quint64 this speed limit is 2^64/144 ~~ 114 PBytes/s. // With quint64 this speed limit is 2^64/144 ~~ 114 PBytes/s.
// This speed is inaccessible to an ordinary user. // This speed is inaccessible to an ordinary user.
m_accumulator.x += pointData.x; ++m_counter;
for (int id = UP; id < NB_GRAPHS; ++id) for (int id = UP; id < NB_GRAPHS; ++id)
m_accumulator.y[id] += pointData.y[id]; m_accumulator[id] += sampleData[id];
m_counter = (m_counter + 1) % m_divider;
if (m_counter != 0) // system may go to sleep, that can cause very big elapsed interval
return; // still accumulating const milliseconds updateInterval {static_cast<int64_t>(BitTorrent::Session::instance()->refreshInterval() * 1.25)};
const milliseconds maxElapsed {std::max(updateInterval, m_resolution)};
const milliseconds elapsed {std::min(milliseconds {m_lastSampleTime.elapsed()}, maxElapsed)};
if (elapsed < m_resolution)
return false; // still accumulating
// it is time final averaging calculations // it is time final averaging calculations
for (int id = UP; id < NB_GRAPHS; ++id) for (int id = UP; id < NB_GRAPHS; ++id)
m_accumulator.y[id] /= m_divider; m_accumulator[id] /= m_counter;
m_accumulator.x /= m_divider;
m_currentDuration += elapsed;
// remove extra data from front if we reached max duration
if (m_currentDuration > m_maxDuration)
{
// once we go above the max duration never go below that
// otherwise it will cause empty space in graphs
while (!m_sink.empty()
&& ((m_currentDuration - m_sink.front().duration) >= m_maxDuration))
{
m_currentDuration -= m_sink.front().duration;
m_sink.pop_front();
}
}
// now flush out averaged data // now flush out averaged data
m_sink.push_back(m_accumulator); Q_ASSERT(m_sink.size() < m_sink.capacity());
m_sink.push_back({elapsed, m_accumulator});
// reset
m_accumulator = {}; m_accumulator = {};
m_counter = 0;
m_lastSampleTime.restart();
return true;
} }
bool SpeedPlotView::Averager::isReady() const const SpeedPlotView::DataCircularBuffer &SpeedPlotView::Averager::data() const
{ {
return m_counter == 0; return m_sink;
} }
SpeedPlotView::SpeedPlotView(QWidget *parent) SpeedPlotView::SpeedPlotView(QWidget *parent)
: QGraphicsView(parent) : QGraphicsView {parent}
, m_data5Min(MIN5_BUF_SIZE)
, m_data30Min(MIN30_BUF_SIZE)
, m_data6Hour(HOUR6_BUF_SIZE)
, m_data12Hour(HOUR12_BUF_SIZE)
, m_data24Hour(HOUR24_BUF_SIZE)
, m_currentData(&m_data5Min)
, m_averager30Min(DIVIDER_30MIN, m_data30Min)
, m_averager6Hour(DIVIDER_6HOUR, m_data6Hour)
, m_averager12Hour(DIVIDER_12HOUR, m_data12Hour)
, m_averager24Hour(DIVIDER_24HOUR, m_data24Hour)
, m_period(MIN5)
, m_viewablePointsCount(MIN5_SEC)
{ {
QPen greenPen; QPen greenPen;
greenPen.setWidthF(1.5); greenPen.setWidthF(1.5);
@@ -205,69 +200,65 @@ void SpeedPlotView::setGraphEnable(GraphID id, bool enable)
viewport()->update(); viewport()->update();
} }
void SpeedPlotView::pushPoint(const SpeedPlotView::PointData &point) void SpeedPlotView::pushPoint(const SpeedPlotView::SampleData &point)
{ {
m_data5Min.push_back(point); for (Averager *averager : {&m_averager5Min, &m_averager30Min
m_averager30Min.push(point); , &m_averager6Hour, &m_averager12Hour
m_averager6Hour.push(point); , &m_averager24Hour})
m_averager12Hour.push(point); {
m_averager24Hour.push(point); if (averager->push(point))
{
if (m_currentAverager == averager)
viewport()->update();
}
}
} }
void SpeedPlotView::setPeriod(const TimePeriod period) void SpeedPlotView::setPeriod(const TimePeriod period)
{ {
m_period = period;
switch (period) switch (period)
{ {
case SpeedPlotView::MIN1: case SpeedPlotView::MIN1:
m_viewablePointsCount = MIN1_SEC; m_currentMaxDuration = 1min;
m_currentData = &m_data5Min; m_currentAverager = &m_averager5Min;
break; break;
case SpeedPlotView::MIN5: case SpeedPlotView::MIN5:
m_viewablePointsCount = MIN5_SEC; m_currentMaxDuration = 5min;
m_currentData = &m_data5Min; m_currentAverager = &m_averager5Min;
break; break;
case SpeedPlotView::MIN30: case SpeedPlotView::MIN30:
m_viewablePointsCount = MIN30_BUF_SIZE; m_currentMaxDuration = 30min;
m_currentData = &m_data30Min; m_currentAverager = &m_averager30Min;
break;
case SpeedPlotView::HOUR3:
m_currentMaxDuration = 3h;
m_currentAverager = &m_averager6Hour;
break; break;
case SpeedPlotView::HOUR6: case SpeedPlotView::HOUR6:
m_viewablePointsCount = HOUR6_BUF_SIZE; m_currentMaxDuration = 6h;
m_currentData = &m_data6Hour; m_currentAverager = &m_averager6Hour;
break; break;
case SpeedPlotView::HOUR12: case SpeedPlotView::HOUR12:
m_viewablePointsCount = HOUR12_BUF_SIZE; m_currentMaxDuration = 12h;
m_currentData = &m_data12Hour; m_currentAverager = &m_averager12Hour;
break; break;
case SpeedPlotView::HOUR24: case SpeedPlotView::HOUR24:
m_viewablePointsCount = HOUR24_BUF_SIZE; m_currentMaxDuration = 24h;
m_currentData = &m_data24Hour; m_currentAverager = &m_averager24Hour;
break; break;
} }
viewport()->update(); viewport()->update();
} }
void SpeedPlotView::replot() const SpeedPlotView::DataCircularBuffer &SpeedPlotView::currentData() const
{ {
if ((m_period == MIN1) return m_currentAverager->data();
|| (m_period == MIN5)
|| ((m_period == MIN30) && m_averager30Min.isReady())
|| ((m_period == HOUR6) && m_averager6Hour.isReady())
|| ((m_period == HOUR12) && m_averager12Hour.isReady())
|| ((m_period == HOUR24) && m_averager24Hour.isReady()) )
viewport()->update();
} }
boost::circular_buffer<SpeedPlotView::PointData> &SpeedPlotView::getCurrentData() quint64 SpeedPlotView::maxYValue() const
{ {
return *m_currentData; const DataCircularBuffer &queue = currentData();
}
quint64 SpeedPlotView::maxYValue()
{
boost::circular_buffer<PointData> &queue = getCurrentData();
quint64 maxYValue = 0; quint64 maxYValue = 0;
for (int id = UP; id < NB_GRAPHS; ++id) for (int id = UP; id < NB_GRAPHS; ++id)
@@ -276,9 +267,14 @@ quint64 SpeedPlotView::maxYValue()
if (!m_properties[static_cast<GraphID>(id)].enable) if (!m_properties[static_cast<GraphID>(id)].enable)
continue; continue;
for (int i = static_cast<int>(queue.size()) - 1, j = 0; (i >= 0) && (j < m_viewablePointsCount); --i, ++j) milliseconds duration {0ms};
if (queue[i].y[id] > maxYValue) for (int i = static_cast<int>(queue.size()) - 1; i >= 0; --i)
maxYValue = queue[i].y[id]; {
maxYValue = std::max(maxYValue, queue[i].data[id]);
duration += queue[i].duration;
if (duration >= m_currentMaxDuration)
break;
}
} }
return maxYValue; return maxYValue;
@@ -308,13 +304,10 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
int yAxisWidth = 0; int yAxisWidth = 0;
for (const QString &label : speedLabels) for (const QString &label : speedLabels)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) {
if (fontMetrics.horizontalAdvance(label) > yAxisWidth) if (fontMetrics.horizontalAdvance(label) > yAxisWidth)
yAxisWidth = fontMetrics.horizontalAdvance(label); yAxisWidth = fontMetrics.horizontalAdvance(label);
#else }
if (fontMetrics.width(label) > yAxisWidth)
yAxisWidth = fontMetrics.width(label);
#endif
int i = 0; int i = 0;
for (const QString &label : speedLabels) for (const QString &label : speedLabels)
@@ -350,12 +343,16 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
painter.setRenderHints(QPainter::Antialiasing); painter.setRenderHints(QPainter::Antialiasing);
// draw graphs // draw graphs
rect.adjust(3, 0, 0, 0); // Need, else graphs cross left gridline // averager is duration based, it may go little above the maxDuration
painter.setClipping(true);
painter.setClipRect(rect);
const double yMultiplier = (niceScale.arg == 0.0) ? 0.0 : (static_cast<double>(rect.height()) / niceScale.sizeInBytes()); const DataCircularBuffer &queue = currentData();
const double xTickSize = static_cast<double>(rect.width()) / (m_viewablePointsCount - 1);
boost::circular_buffer<PointData> &queue = getCurrentData(); // last point will be drawn at x=0, so we don't need it in the calculation of xTickSize
const milliseconds lastDuration {queue.empty() ? 0ms : queue.back().duration};
const double xTickSize = static_cast<double>(rect.width()) / (m_currentMaxDuration - lastDuration).count();
const double yMultiplier = (niceScale.arg == 0) ? 0 : (static_cast<double>(rect.height()) / niceScale.sizeInBytes());
for (int id = UP; id < NB_GRAPHS; ++id) for (int id = UP; id < NB_GRAPHS; ++id)
{ {
@@ -363,18 +360,23 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
continue; continue;
QVector<QPoint> points; QVector<QPoint> points;
for (int i = static_cast<int>(queue.size()) - 1, j = 0; (i >= 0) && (j < m_viewablePointsCount); --i, ++j) milliseconds duration {0ms};
for (int i = static_cast<int>(queue.size()) - 1; i >= 0; --i)
{ {
const int newX = rect.right() - (duration.count() * xTickSize);
int newX = rect.right() - j * xTickSize; const int newY = rect.bottom() - (queue[i].data[id] * yMultiplier);
int newY = rect.bottom() - queue[i].y[id] * yMultiplier;
points.push_back(QPoint(newX, newY)); points.push_back(QPoint(newX, newY));
duration += queue[i].duration;
if (duration >= m_currentMaxDuration)
break;
} }
painter.setPen(m_properties[static_cast<GraphID>(id)].pen); painter.setPen(m_properties[static_cast<GraphID>(id)].pen);
painter.drawPolyline(points.data(), points.size()); painter.drawPolyline(points.data(), points.size());
} }
painter.setClipping(false);
// draw legend // draw legend
QPoint legendTopLeft(rect.left() + 4, fullRect.top() + 4); QPoint legendTopLeft(rect.left() + 4, fullRect.top() + 4);
@@ -386,13 +388,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
if (!property.enable) if (!property.enable)
continue; continue;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
if (fontMetrics.horizontalAdvance(property.name) > legendWidth) if (fontMetrics.horizontalAdvance(property.name) > legendWidth)
legendWidth = fontMetrics.horizontalAdvance(property.name); legendWidth = fontMetrics.horizontalAdvance(property.name);
#else
if (fontMetrics.width(property.name) > legendWidth)
legendWidth = fontMetrics.width(property.name);
#endif
legendHeight += 1.5 * fontMetrics.height(); legendHeight += 1.5 * fontMetrics.height();
} }
@@ -407,11 +404,7 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
if (!property.enable) if (!property.enable)
continue; continue;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
int nameSize = fontMetrics.horizontalAdvance(property.name); int nameSize = fontMetrics.horizontalAdvance(property.name);
#else
int nameSize = fontMetrics.width(property.name);
#endif
double indent = 1.5 * (i++) * fontMetrics.height(); double indent = 1.5 * (i++) * fontMetrics.height();
painter.setPen(property.pen); painter.setPen(property.pen);

View File

@@ -1,5 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Prince Gupta <guptaprince8832@gmail.com>
* Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru> * Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -28,15 +29,22 @@
#pragma once #pragma once
#include <array>
#include <chrono>
#ifndef Q_MOC_RUN #ifndef Q_MOC_RUN
#include <boost/circular_buffer.hpp> #include <boost/circular_buffer.hpp>
#endif #endif
#include <QElapsedTimer>
#include <QGraphicsView> #include <QGraphicsView>
#include <QMap> #include <QMap>
class QPen; class QPen;
using std::chrono::milliseconds;
using namespace std::chrono_literals;
class SpeedPlotView final : public QGraphicsView class SpeedPlotView final : public QGraphicsView
{ {
Q_OBJECT Q_OBJECT
@@ -63,42 +71,50 @@ public:
MIN1 = 0, MIN1 = 0,
MIN5, MIN5,
MIN30, MIN30,
HOUR3,
HOUR6, HOUR6,
HOUR12, HOUR12,
HOUR24 HOUR24
}; };
struct PointData using SampleData = std::array<quint64, NB_GRAPHS>;
{
qint64 x;
quint64 y[NB_GRAPHS];
};
explicit SpeedPlotView(QWidget *parent = nullptr); explicit SpeedPlotView(QWidget *parent = nullptr);
void setGraphEnable(GraphID id, bool enable); void setGraphEnable(GraphID id, bool enable);
void setPeriod(TimePeriod period); void setPeriod(TimePeriod period);
void pushPoint(const PointData &point); void pushPoint(const SampleData &point);
void replot();
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
private: private:
struct Sample
{
milliseconds duration;
SampleData data;
};
using DataCircularBuffer = boost::circular_buffer<Sample>;
class Averager class Averager
{ {
public: public:
Averager(int divider, boost::circular_buffer<PointData> &sink); Averager(milliseconds duration, milliseconds resolution);
void push(const PointData &pointData);
bool isReady() const; bool push(const SampleData &sampleData); // returns true if there is new data to display
const DataCircularBuffer &data() const;
private: private:
const int m_divider; const milliseconds m_resolution;
boost::circular_buffer<PointData> &m_sink; const milliseconds m_maxDuration;
int m_counter; milliseconds m_currentDuration {0ms};
PointData m_accumulator; int m_counter = 0;
SampleData m_accumulator {};
DataCircularBuffer m_sink {};
QElapsedTimer m_lastSampleTime;
}; };
struct GraphProperties struct GraphProperties
@@ -111,22 +127,16 @@ private:
bool enable; bool enable;
}; };
quint64 maxYValue(); quint64 maxYValue() const;
boost::circular_buffer<PointData> &getCurrentData(); const DataCircularBuffer &currentData() const;
boost::circular_buffer<PointData> m_data5Min; Averager m_averager5Min {5min, 1s};
boost::circular_buffer<PointData> m_data30Min; Averager m_averager30Min {30min, 6s};
boost::circular_buffer<PointData> m_data6Hour; Averager m_averager6Hour {6h, 36s};
boost::circular_buffer<PointData> m_data12Hour; Averager m_averager12Hour {12h, 72s};
boost::circular_buffer<PointData> m_data24Hour; Averager m_averager24Hour {24h, 144s};
boost::circular_buffer<PointData> *m_currentData; Averager *m_currentAverager {&m_averager5Min};
Averager m_averager30Min;
Averager m_averager6Hour;
Averager m_averager12Hour;
Averager m_averager24Hour;
QMap<GraphID, GraphProperties> m_properties; QMap<GraphID, GraphProperties> m_properties;
milliseconds m_currentMaxDuration;
TimePeriod m_period;
int m_viewablePointsCount;
}; };

View File

@@ -71,6 +71,7 @@ SpeedWidget::SpeedWidget(PropertiesWidget *parent)
m_periodCombobox->addItem(tr("1 Minute")); m_periodCombobox->addItem(tr("1 Minute"));
m_periodCombobox->addItem(tr("5 Minutes")); m_periodCombobox->addItem(tr("5 Minutes"));
m_periodCombobox->addItem(tr("30 Minutes")); m_periodCombobox->addItem(tr("30 Minutes"));
m_periodCombobox->addItem(tr("3 Hours"));
m_periodCombobox->addItem(tr("6 Hours")); m_periodCombobox->addItem(tr("6 Hours"));
m_periodCombobox->addItem(tr("12 Hours")); m_periodCombobox->addItem(tr("12 Hours"));
m_periodCombobox->addItem(tr("24 Hours")); m_periodCombobox->addItem(tr("24 Hours"));
@@ -109,16 +110,13 @@ SpeedWidget::SpeedWidget(PropertiesWidget *parent)
m_hlayout->addWidget(m_graphsButton); m_hlayout->addWidget(m_graphsButton);
m_plot = new SpeedPlotView(this); m_plot = new SpeedPlotView(this);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::statsUpdated, this, &SpeedWidget::update);
m_layout->addLayout(m_hlayout); m_layout->addLayout(m_hlayout);
m_layout->addWidget(m_plot); m_layout->addWidget(m_plot);
loadSettings(); loadSettings();
QTimer *localUpdateTimer = new QTimer(this);
connect(localUpdateTimer, &QTimer::timeout, this, &SpeedWidget::update);
localUpdateTimer->start(1000);
m_plot->show(); m_plot->show();
} }
@@ -133,21 +131,19 @@ void SpeedWidget::update()
{ {
const BitTorrent::SessionStatus &btStatus = BitTorrent::Session::instance()->status(); const BitTorrent::SessionStatus &btStatus = BitTorrent::Session::instance()->status();
SpeedPlotView::PointData point; SpeedPlotView::SampleData sampleData;
point.x = QDateTime::currentMSecsSinceEpoch() / 1000; sampleData[SpeedPlotView::UP] = btStatus.uploadRate;
point.y[SpeedPlotView::UP] = btStatus.uploadRate; sampleData[SpeedPlotView::DOWN] = btStatus.downloadRate;
point.y[SpeedPlotView::DOWN] = btStatus.downloadRate; sampleData[SpeedPlotView::PAYLOAD_UP] = btStatus.payloadUploadRate;
point.y[SpeedPlotView::PAYLOAD_UP] = btStatus.payloadUploadRate; sampleData[SpeedPlotView::PAYLOAD_DOWN] = btStatus.payloadDownloadRate;
point.y[SpeedPlotView::PAYLOAD_DOWN] = btStatus.payloadDownloadRate; sampleData[SpeedPlotView::OVERHEAD_UP] = btStatus.ipOverheadUploadRate;
point.y[SpeedPlotView::OVERHEAD_UP] = btStatus.ipOverheadUploadRate; sampleData[SpeedPlotView::OVERHEAD_DOWN] = btStatus.ipOverheadDownloadRate;
point.y[SpeedPlotView::OVERHEAD_DOWN] = btStatus.ipOverheadDownloadRate; sampleData[SpeedPlotView::DHT_UP] = btStatus.dhtUploadRate;
point.y[SpeedPlotView::DHT_UP] = btStatus.dhtUploadRate; sampleData[SpeedPlotView::DHT_DOWN] = btStatus.dhtDownloadRate;
point.y[SpeedPlotView::DHT_DOWN] = btStatus.dhtDownloadRate; sampleData[SpeedPlotView::TRACKER_UP] = btStatus.trackerUploadRate;
point.y[SpeedPlotView::TRACKER_UP] = btStatus.trackerUploadRate; sampleData[SpeedPlotView::TRACKER_DOWN] = btStatus.trackerDownloadRate;
point.y[SpeedPlotView::TRACKER_DOWN] = btStatus.trackerDownloadRate;
m_plot->pushPoint(point); m_plot->pushPoint(sampleData);
m_plot->replot();
} }
void SpeedWidget::onPeriodChange(int period) void SpeedWidget::onPeriodChange(int period)

View File

@@ -205,9 +205,7 @@ void TrackerListWidget::moveSelectionUp()
for (int i = NB_STICKY_ITEM; i < topLevelItemCount(); ++i) for (int i = NB_STICKY_ITEM; i < topLevelItemCount(); ++i)
{ {
const QString trackerURL = topLevelItem(i)->data(COL_URL, Qt::DisplayRole).toString(); const QString trackerURL = topLevelItem(i)->data(COL_URL, Qt::DisplayRole).toString();
BitTorrent::TrackerEntry e(trackerURL); trackers.append({trackerURL, (i - NB_STICKY_ITEM)});
e.setTier(i - NB_STICKY_ITEM);
trackers.append(e);
} }
torrent->replaceTrackers(trackers); torrent->replaceTrackers(trackers);
@@ -251,9 +249,7 @@ void TrackerListWidget::moveSelectionDown()
for (int i = NB_STICKY_ITEM; i < topLevelItemCount(); ++i) for (int i = NB_STICKY_ITEM; i < topLevelItemCount(); ++i)
{ {
const QString trackerURL = topLevelItem(i)->data(COL_URL, Qt::DisplayRole).toString(); const QString trackerURL = topLevelItem(i)->data(COL_URL, Qt::DisplayRole).toString();
BitTorrent::TrackerEntry e(trackerURL); trackers.append({trackerURL, (i - NB_STICKY_ITEM)});
e.setTier(i - NB_STICKY_ITEM);
trackers.append(e);
} }
torrent->replaceTrackers(trackers); torrent->replaceTrackers(trackers);
@@ -372,7 +368,7 @@ void TrackerListWidget::loadTrackers()
for (const BitTorrent::TrackerEntry &entry : asConst(torrent->trackers())) for (const BitTorrent::TrackerEntry &entry : asConst(torrent->trackers()))
{ {
const QString trackerURL = entry.url(); const QString trackerURL = entry.url;
QTreeWidgetItem *item = m_trackerItems.value(trackerURL, nullptr); QTreeWidgetItem *item = m_trackerItems.value(trackerURL, nullptr);
if (!item) if (!item)
@@ -387,11 +383,11 @@ void TrackerListWidget::loadTrackers()
oldTrackerURLs.removeOne(trackerURL); oldTrackerURLs.removeOne(trackerURL);
} }
item->setText(COL_TIER, QString::number(entry.tier())); item->setText(COL_TIER, QString::number(entry.tier));
const BitTorrent::TrackerInfo data = trackerData.value(trackerURL); const BitTorrent::TrackerInfo data = trackerData.value(trackerURL);
switch (entry.status()) switch (entry.status)
{ {
case BitTorrent::TrackerEntry::Working: case BitTorrent::TrackerEntry::Working:
item->setText(COL_STATUS, tr("Working")); item->setText(COL_STATUS, tr("Working"));
@@ -412,14 +408,14 @@ void TrackerListWidget::loadTrackers()
} }
item->setText(COL_PEERS, QString::number(data.numPeers)); item->setText(COL_PEERS, QString::number(data.numPeers));
item->setText(COL_SEEDS, ((entry.numSeeds() > -1) item->setText(COL_SEEDS, ((entry.numSeeds > -1)
? QString::number(entry.numSeeds()) ? QString::number(entry.numSeeds)
: tr("N/A"))); : tr("N/A")));
item->setText(COL_LEECHES, ((entry.numLeeches() > -1) item->setText(COL_LEECHES, ((entry.numLeeches > -1)
? QString::number(entry.numLeeches()) ? QString::number(entry.numLeeches)
: tr("N/A"))); : tr("N/A")));
item->setText(COL_DOWNLOADED, ((entry.numDownloaded() > -1) item->setText(COL_DOWNLOADED, ((entry.numDownloaded > -1)
? QString::number(entry.numDownloaded()) ? QString::number(entry.numDownloaded)
: tr("N/A"))); : tr("N/A")));
const Qt::Alignment alignment = (Qt::AlignRight | Qt::AlignVCenter); const Qt::Alignment alignment = (Qt::AlignRight | Qt::AlignVCenter);
@@ -443,7 +439,7 @@ void TrackerListWidget::askForTrackers()
QVector<BitTorrent::TrackerEntry> trackers; QVector<BitTorrent::TrackerEntry> trackers;
for (const QString &tracker : asConst(TrackersAdditionDialog::askForTrackers(this, torrent))) for (const QString &tracker : asConst(TrackersAdditionDialog::askForTrackers(this, torrent)))
trackers << tracker; trackers.append({tracker});
torrent->addTrackers(trackers); torrent->addTrackers(trackers);
} }
@@ -492,7 +488,7 @@ void TrackerListWidget::deleteSelectedTrackers()
for (const BitTorrent::TrackerEntry &entry : trackers) for (const BitTorrent::TrackerEntry &entry : trackers)
{ {
if (!urlsToRemove.contains(entry.url())) if (!urlsToRemove.contains(entry.url))
remainingTrackers.push_back(entry); remainingTrackers.push_back(entry);
} }
@@ -529,18 +525,16 @@ void TrackerListWidget::editSelectedTracker()
bool match = false; bool match = false;
for (BitTorrent::TrackerEntry &entry : trackers) for (BitTorrent::TrackerEntry &entry : trackers)
{ {
if (newTrackerURL == QUrl(entry.url())) if (newTrackerURL == QUrl(entry.url))
{ {
QMessageBox::warning(this, tr("Tracker editing failed"), tr("The tracker URL already exists.")); QMessageBox::warning(this, tr("Tracker editing failed"), tr("The tracker URL already exists."));
return; return;
} }
if (!match && (trackerURL == QUrl(entry.url()))) if (!match && (trackerURL == QUrl(entry.url)))
{ {
match = true; match = true;
BitTorrent::TrackerEntry newEntry(newTrackerURL.toString()); entry.url = newTrackerURL.toString();
newEntry.setTier(entry.tier());
entry = newEntry;
} }
} }
@@ -572,7 +566,7 @@ void TrackerListWidget::reannounceSelected()
// Trackers case // Trackers case
for (int i = 0; i < trackers.size(); ++i) for (int i = 0; i < trackers.size(); ++i)
{ {
if (item->text(COL_URL) == trackers[i].url()) if (item->text(COL_URL) == trackers[i].url)
{ {
torrent->forceReannounce(i); torrent->forceReannounce(i);
break; break;
@@ -592,30 +586,26 @@ void TrackerListWidget::showTrackerListMenu(const QPoint &)
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
// Add actions // Add actions
const QAction *addAct = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add a new tracker...")); menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add a new tracker...")
connect(addAct, &QAction::triggered, this, &TrackerListWidget::askForTrackers); , this, &TrackerListWidget::askForTrackers);
if (!getSelectedTrackerItems().isEmpty()) if (!getSelectedTrackerItems().isEmpty())
{ {
const QAction *editAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"),tr("Edit tracker URL...")); menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"),tr("Edit tracker URL...")
connect(editAct, &QAction::triggered, this, &TrackerListWidget::editSelectedTracker); , this, &TrackerListWidget::editSelectedTracker);
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove tracker")
const QAction *delAct = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove tracker")); , this, &TrackerListWidget::deleteSelectedTrackers);
connect(delAct, &QAction::triggered, this, &TrackerListWidget::deleteSelectedTrackers); menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy tracker URL")
, this, &TrackerListWidget::copyTrackerUrl);
const QAction *copyAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy tracker URL"));
connect(copyAct, &QAction::triggered, this, &TrackerListWidget::copyTrackerUrl);
} }
if (!torrent->isPaused()) if (!torrent->isPaused())
{ {
const QAction *reannounceSelAct = menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to selected trackers")); menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to selected trackers")
connect(reannounceSelAct, &QAction::triggered, this, &TrackerListWidget::reannounceSelected); , this, &TrackerListWidget::reannounceSelected);
menu->addSeparator(); menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to all trackers")
const QAction *reannounceAllAct = menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to all trackers")); , this, [this]()
connect(reannounceAllAct, &QAction::triggered, this, [this]()
{ {
BitTorrent::Torrent *h = m_properties->getCurrentTorrent(); BitTorrent::Torrent *h = m_properties->getCurrentTorrent();
h->forceReannounce(); h->forceReannounce();

View File

@@ -96,7 +96,7 @@ void TrackersAdditionDialog::torrentListDownloadFinished(const Net::DownloadResu
existingTrackers.reserve(trackersFromUser.size()); existingTrackers.reserve(trackersFromUser.size());
for (const QString &userURL : trackersFromUser) for (const QString &userURL : trackersFromUser)
{ {
const BitTorrent::TrackerEntry userTracker(userURL); const BitTorrent::TrackerEntry userTracker {userURL};
if (!existingTrackers.contains(userTracker)) if (!existingTrackers.contains(userTracker))
existingTrackers << userTracker; existingTrackers << userTracker;
} }
@@ -113,7 +113,7 @@ void TrackersAdditionDialog::torrentListDownloadFinished(const Net::DownloadResu
const QString line = buffer.readLine().trimmed(); const QString line = buffer.readLine().trimmed();
if (line.isEmpty()) continue; if (line.isEmpty()) continue;
BitTorrent::TrackerEntry newTracker(line); BitTorrent::TrackerEntry newTracker {line};
if (!existingTrackers.contains(newTracker)) if (!existingTrackers.contains(newTracker))
{ {
m_ui->textEditTrackersList->insertPlainText(line + '\n'); m_ui->textEditTrackersList->insertPlainText(line + '\n');

View File

@@ -50,6 +50,7 @@
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/utils/string.h" #include "base/utils/string.h"
#include "gui/autoexpandabledialog.h" #include "gui/autoexpandabledialog.h"
#include "gui/torrentcategorydialog.h"
#include "gui/uithememanager.h" #include "gui/uithememanager.h"
#include "gui/utils.h" #include "gui/utils.h"
#include "ui_automatedrssdownloader.h" #include "ui_automatedrssdownloader.h"
@@ -68,6 +69,7 @@ AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent)
// Icons // Icons
m_ui->removeRuleBtn->setIcon(UIThemeManager::instance()->getIcon("list-remove")); m_ui->removeRuleBtn->setIcon(UIThemeManager::instance()->getIcon("list-remove"));
m_ui->addRuleBtn->setIcon(UIThemeManager::instance()->getIcon("list-add")); m_ui->addRuleBtn->setIcon(UIThemeManager::instance()->getIcon("list-add"));
m_ui->addCategoryBtn->setIcon(UIThemeManager::instance()->getIcon("list-add"));
// Ui Settings // Ui Settings
m_ui->listRules->setSortingEnabled(true); m_ui->listRules->setSortingEnabled(true);
@@ -185,7 +187,7 @@ void AutomatedRssDownloader::loadFeedList()
{ {
QListWidgetItem *item = new QListWidgetItem(feed->name(), m_ui->listFeeds); QListWidgetItem *item = new QListWidgetItem(feed->name(), m_ui->listFeeds);
item->setData(Qt::UserRole, feed->url()); item->setData(Qt::UserRole, feed->url());
item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsTristate); item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate);
} }
updateFeedList(); updateFeedList();
@@ -405,6 +407,17 @@ void AutomatedRssDownloader::on_removeRuleBtn_clicked()
RSS::AutoDownloader::instance()->removeRule(item->text()); RSS::AutoDownloader::instance()->removeRule(item->text());
} }
void AutomatedRssDownloader::on_addCategoryBtn_clicked()
{
const QString newCategoryName = TorrentCategoryDialog::createCategory(this);
if (!newCategoryName.isEmpty())
{
m_ui->comboCategory->addItem(newCategoryName);
m_ui->comboCategory->setCurrentText(newCategoryName);
}
}
void AutomatedRssDownloader::on_exportBtn_clicked() void AutomatedRssDownloader::on_exportBtn_clicked()
{ {
if (RSS::AutoDownloader::instance()->rules().isEmpty()) if (RSS::AutoDownloader::instance()->rules().isEmpty())
@@ -490,8 +503,8 @@ void AutomatedRssDownloader::displayRulesListMenu()
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
const QAction *addAct = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add new rule...")); menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add new rule...")
connect(addAct, &QAction::triggered, this, &AutomatedRssDownloader::on_addRuleBtn_clicked); , this, &AutomatedRssDownloader::on_addRuleBtn_clicked);
const QList<QListWidgetItem *> selection = m_ui->listRules->selectedItems(); const QList<QListWidgetItem *> selection = m_ui->listRules->selectedItems();
@@ -499,24 +512,21 @@ void AutomatedRssDownloader::displayRulesListMenu()
{ {
if (selection.count() == 1) if (selection.count() == 1)
{ {
const QAction *delAct = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete rule")); menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete rule")
connect(delAct, &QAction::triggered, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked); , this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
menu->addSeparator(); menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename rule...")
const QAction *renameAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename rule...")); , this, &AutomatedRssDownloader::renameSelectedRule);
connect(renameAct, &QAction::triggered, this, &AutomatedRssDownloader::renameSelectedRule);
} }
else else
{ {
const QAction *delAct = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete selected rules")); menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete selected rules")
connect(delAct, &QAction::triggered, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked); , this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
} }
menu->addSeparator(); menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear downloaded episodes...")
const QAction *clearAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear downloaded episodes...")); , this, &AutomatedRssDownloader::clearSelectedRuleDownloadedEpisodeList);
connect(clearAct, &QAction::triggered, this, &AutomatedRssDownloader::clearSelectedRuleDownloadedEpisodeList);
} }
menu->popup(QCursor::pos()); menu->popup(QCursor::pos());
@@ -709,10 +719,14 @@ void AutomatedRssDownloader::updateMustLineValidity()
{ {
QStringList tokens; QStringList tokens;
if (isRegex) if (isRegex)
{
tokens << text; tokens << text;
}
else else
{
for (const QString &token : asConst(text.split('|'))) for (const QString &token : asConst(text.split('|')))
tokens << Utils::String::wildcardToRegex(token); tokens << Utils::String::wildcardToRegexPattern(token);
}
for (const QString &token : asConst(tokens)) for (const QString &token : asConst(tokens))
{ {
@@ -752,10 +766,14 @@ void AutomatedRssDownloader::updateMustNotLineValidity()
{ {
QStringList tokens; QStringList tokens;
if (isRegex) if (isRegex)
{
tokens << text; tokens << text;
}
else else
{
for (const QString &token : asConst(text.split('|'))) for (const QString &token : asConst(text.split('|')))
tokens << Utils::String::wildcardToRegex(token); tokens << Utils::String::wildcardToRegexPattern(token);
}
for (const QString &token : asConst(tokens)) for (const QString &token : asConst(tokens))
{ {

View File

@@ -61,6 +61,7 @@ public:
private slots: private slots:
void on_addRuleBtn_clicked(); void on_addRuleBtn_clicked();
void on_removeRuleBtn_clicked(); void on_removeRuleBtn_clicked();
void on_addCategoryBtn_clicked();
void on_exportBtn_clicked(); void on_exportBtn_clicked();
void on_importBtn_clicked(); void on_importBtn_clicked();

View File

@@ -209,7 +209,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>Assign Category:</string> <string>Category:</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -220,6 +220,9 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QToolButton" name="addCategoryBtn" />
</item>
</layout> </layout>
</item> </item>
<item> <item>

View File

@@ -365,9 +365,9 @@ void SearchJobWidget::fillFilterComboBoxes()
void SearchJobWidget::filterSearchResults(const QString &name) void SearchJobWidget::filterSearchResults(const QString &name)
{ {
const QRegExp::PatternSyntax patternSyntax = Preferences::instance()->getRegexAsFilteringPatternForSearchJob() const QString pattern = (Preferences::instance()->getRegexAsFilteringPatternForSearchJob()
? QRegExp::RegExp : QRegExp::WildcardUnix; ? name : Utils::String::wildcardToRegexPattern(name));
m_proxyModel->setFilterRegExp(QRegExp(name, Qt::CaseInsensitive, patternSyntax)); m_proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
updateResultsCount(); updateResultsCount();
} }
@@ -393,31 +393,21 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
auto *menu = new QMenu(this); auto *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
const QAction *downloadAction = menu->addAction( menu->addAction(UIThemeManager::instance()->getIcon("download"), tr("Download")
UIThemeManager::instance()->getIcon("download"), tr("Download")); , this, &SearchJobWidget::downloadTorrents);
connect(downloadAction, &QAction::triggered, this, &SearchJobWidget::downloadTorrents);
menu->addSeparator(); menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon("application-x-mswinurl"), tr("Open description page")
const QAction *openDescriptionAction = menu->addAction( , this, &SearchJobWidget::openTorrentPages);
UIThemeManager::instance()->getIcon("application-x-mswinurl"), tr("Open description page"));
connect(openDescriptionAction, &QAction::triggered, this, &SearchJobWidget::openTorrentPages);
QMenu *copySubMenu = menu->addMenu( QMenu *copySubMenu = menu->addMenu(
UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy")); UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy"));
const QAction *copyNamesAction = copySubMenu->addAction( copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Name")
UIThemeManager::instance()->getIcon("edit-copy"), tr("Name")); , this, &SearchJobWidget::copyTorrentNames);
connect(copyNamesAction, &QAction::triggered, this, &SearchJobWidget::copyTorrentNames); copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Download link")
const QAction *copyDownloadLinkAction = copySubMenu->addAction(
UIThemeManager::instance()->getIcon("edit-copy"), tr("Download link"));
connect(copyDownloadLinkAction, &QAction::triggered
, this, &SearchJobWidget::copyTorrentDownloadLinks); , this, &SearchJobWidget::copyTorrentDownloadLinks);
copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Description page URL")
const QAction *copyDescriptionAction = copySubMenu->addAction( , this, &SearchJobWidget::copyTorrentURLs);
UIThemeManager::instance()->getIcon("edit-copy"), tr("Description page URL"));
connect(copyDescriptionAction, &QAction::triggered, this, &SearchJobWidget::copyTorrentURLs);
menu->popup(event->globalPos()); menu->popup(event->globalPos());
} }

View File

@@ -28,7 +28,6 @@
#include "tagfilterwidget.h" #include "tagfilterwidget.h"
#include <QAction>
#include <QMenu> #include <QMenu>
#include <QMessageBox> #include <QMessageBox>
@@ -108,44 +107,25 @@ void TagFilterWidget::showMenu(QPoint)
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose); menu->setAttribute(Qt::WA_DeleteOnClose);
const QAction *addAct = menu->addAction( menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add tag...")
UIThemeManager::instance()->getIcon("list-add") , this, &TagFilterWidget::addTag);
, tr("Add tag..."));
connect(addAct, &QAction::triggered, this, &TagFilterWidget::addTag);
const auto selectedRows = selectionModel()->selectedRows(); const auto selectedRows = selectionModel()->selectedRows();
if (!selectedRows.empty() && !TagFilterModel::isSpecialItem(selectedRows.first())) if (!selectedRows.empty() && !TagFilterModel::isSpecialItem(selectedRows.first()))
{ {
const QAction *removeAct = menu->addAction( menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove tag")
UIThemeManager::instance()->getIcon("list-remove") , this, &TagFilterWidget::removeTag);
, tr("Remove tag"));
connect(removeAct, &QAction::triggered, this, &TagFilterWidget::removeTag);
} }
const QAction *removeUnusedAct = menu->addAction( menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove unused tags")
UIThemeManager::instance()->getIcon("list-remove") , this, &TagFilterWidget::removeUnusedTags);
, tr("Remove unused tags"));
connect(removeUnusedAct, &QAction::triggered, this, &TagFilterWidget::removeUnusedTags);
menu->addSeparator(); menu->addSeparator();
menu->addAction(UIThemeManager::instance()->getIcon("media-playback-start"), tr("Resume torrents")
const QAction *startAct = menu->addAction(
UIThemeManager::instance()->getIcon("media-playback-start")
, tr("Resume torrents"));
connect(startAct, &QAction::triggered
, this, &TagFilterWidget::actionResumeTorrentsTriggered); , this, &TagFilterWidget::actionResumeTorrentsTriggered);
menu->addAction(UIThemeManager::instance()->getIcon("media-playback-pause"), tr("Pause torrents")
const QAction *pauseAct = menu->addAction( , this, &TagFilterWidget::actionPauseTorrentsTriggered);
UIThemeManager::instance()->getIcon("media-playback-pause") menu->addAction(UIThemeManager::instance()->getIcon("edit-delete"), tr("Delete torrents")
, tr("Pause torrents")); , this, &TagFilterWidget::actionDeleteTorrentsTriggered);
connect(pauseAct, &QAction::triggered, this
, &TagFilterWidget::actionPauseTorrentsTriggered);
const QAction *deleteTorrentsAct = menu->addAction(
UIThemeManager::instance()->getIcon("edit-delete")
, tr("Delete torrents"));
connect(deleteTorrentsAct, &QAction::triggered, this
, &TagFilterWidget::actionDeleteTorrentsTriggered);
menu->popup(QCursor::pos()); menu->popup(QCursor::pos());
} }

View File

@@ -29,6 +29,7 @@
#include "torrentcategorydialog.h" #include "torrentcategorydialog.h"
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton>
#include "base/bittorrent/session.h" #include "base/bittorrent/session.h"
#include "ui_torrentcategorydialog.h" #include "ui_torrentcategorydialog.h"
@@ -40,6 +41,13 @@ TorrentCategoryDialog::TorrentCategoryDialog(QWidget *parent)
m_ui->setupUi(this); m_ui->setupUi(this);
m_ui->comboSavePath->setMode(FileSystemPathEdit::Mode::DirectorySave); m_ui->comboSavePath->setMode(FileSystemPathEdit::Mode::DirectorySave);
m_ui->comboSavePath->setDialogCaption(tr("Choose save path")); m_ui->comboSavePath->setDialogCaption(tr("Choose save path"));
// disable save button
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
connect(m_ui->textCategoryName, &QLineEdit::textChanged, this, [this](const QString &text)
{
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty());
});
} }
TorrentCategoryDialog::~TorrentCategoryDialog() TorrentCategoryDialog::~TorrentCategoryDialog()

Some files were not shown because too many files have changed in this diff Show More