Compare commits

..

97 Commits

Author SHA1 Message Date
sledgehammer999
332b173e08 Bump to 4.3.4.1 2021-03-24 21:21:06 +02:00
sledgehammer999
e921cf677a Update Changelog 2021-03-24 21:19:55 +02:00
Vladimir Golovnev (Glassez)
973b5a4809 Correctly draw progress bar in Qt 6 2021-03-24 21:15:49 +02:00
Chocobo1
688e11a911 Remove wrong parentheses
Fix up 87ad8a1495.
2021-03-24 19:17:30 +02:00
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 48585 additions and 54454 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:
branches: [ master ]
pull_request:
types: [edited, opened, reopened, synchronize]
branches: [ master ]
env:
# Qt: 5.15.1
# libtorrent: RC_1_2 HEAD, 1.2.11
VCPKG_COMMIT: 133051b793486ef14e67e9d1f48c9cfe64dc127e
VCPKG_COMMIT: e4ce66eecfd3e5cca5eac06c971921bf8e37cf88
VCPKG_DEST_MACOS: /Users/runner/qbt_tools/vcpkg
VCPKG_DEST_WIN: C:\qbt_tools\vcpkg
LIBTORRENT_VERSION_TAG: v1.2.11
LIBTORRENT_VERSION_TAG: v1.2.12
jobs:
@@ -26,7 +23,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-20.04, ubuntu-18.04]
os: [ubuntu-20.04]
qbt_gui: ["GUI=ON", "GUI=OFF"]
fail-fast: false
@@ -125,10 +122,15 @@ jobs:
"qt5-svg:x64-windows-static-release",
"qt5-tools: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)
{
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg.exe install $package `
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
--overlay-ports=${{ github.workspace }}/vcpkg `
--clean-after-build
}
@@ -199,13 +201,6 @@ jobs:
Add-Content ${{ github.workspace }}/triplets_overlay/x64-osx-release.cmake `
-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
run: |
$packages = `
@@ -215,10 +210,15 @@ jobs:
"qt5-svg:x64-osx-release",
"qt5-tools: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)
{
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg install $package `
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
--overlay-ports=${{ github.workspace }}/vcpkg `
--clean-after-build
}

View File

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

View File

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

View File

@@ -119,7 +119,7 @@ Translations authors:
- 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)
- 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)
- Japanese: Masato Hashimoto (cabezon.hashimoto@gmail.com)
- Korean: Jin Woo Sin (jin828sin@users.sourceforge.net)

View File

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

View File

@@ -1,3 +1,51 @@
Wed Mar 24 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.4.1
- BUGFIX: Correctly draw progress bar (glassez)
- WEBUI: Fix javascript code which broke the UI (Chocobo1)
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
- FEATURE: New languages: Azerbaijani, Estonian
- 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
- libtorrent-rasterbar >= 1.2.11 (by Arvid Norberg)
- libtorrent-rasterbar >= 1.2.12 (by Arvid Norberg)
* https://www.libtorrent.org/
* Be careful: another library (the one used by rTorrent) uses a similar name
- OpenSSL >= 1.1.1
- Qt >= 5.9.5
- Qt >= 5.12
- zlib >= 1.2.11

80
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# 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.1.
#
# Report bugs to <bugs.qbittorrent.org>.
#
@@ -610,8 +610,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='qbittorrent'
PACKAGE_TARNAME='qbittorrent'
PACKAGE_VERSION='v4.3.3'
PACKAGE_STRING='qbittorrent v4.3.3'
PACKAGE_VERSION='v4.3.4.1'
PACKAGE_STRING='qbittorrent v4.3.4.1'
PACKAGE_BUGREPORT='bugs.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.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures qbittorrent v4.3.3 to adapt to many kinds of systems.
\`configure' configures qbittorrent v4.3.4.1 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1401,7 +1401,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of qbittorrent v4.3.3:";;
short | recursive ) echo "Configuration of qbittorrent v4.3.4.1:";;
esac
cat <<\_ACEOF
@@ -1455,7 +1455,7 @@ Some influential environment variables:
directories to add to pkg-config's search path
PKG_CONFIG_LIBDIR
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
C compiler 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
if $ac_init_version; then
cat <<\_ACEOF
qbittorrent configure v4.3.3
qbittorrent configure v4.3.4.1
generated by GNU Autoconf 2.70
Copyright (C) 2020 Free Software Foundation, Inc.
@@ -1700,7 +1700,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
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.1, which was
generated by GNU Autoconf 2.70. Invocation command line was
$ $0$ac_configure_args_raw
@@ -4848,7 +4848,7 @@ fi
# Define the identity of the package.
PACKAGE='qbittorrent'
VERSION='v4.3.3'
VERSION='v4.3.4.1'
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -5525,8 +5525,8 @@ printf "%s\n" "$enable_webui" >&6; }
esac
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.9.5\""; } >&5
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.9.5") 2>&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.12") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
@@ -5535,12 +5535,12 @@ if test -n "$QT_QMAKE"; then
pkg_cv_QT_QMAKE="$QT_QMAKE"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.9.5\""; } >&5
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.9.5") 2>&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.12") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
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
else
pkg_failed=yes
@@ -5570,8 +5570,8 @@ fi
fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5 qmake >= 5.9.5" >&5
printf %s "checking for Qt5 qmake >= 5.9.5... " >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5 qmake >= 5.12" >&5
printf %s "checking for Qt5 qmake >= 5.12... " >&6; }
if test "x$QT_QMAKE" != "x"
then :
{ 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"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.5.1\""; } >&5
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.5.1") 2>&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.12") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
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
else
pkg_failed=yes
@@ -5615,12 +5615,12 @@ if test -n "$Qt5Svg_LIBS"; then
pkg_cv_Qt5Svg_LIBS="$Qt5Svg_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.5.1\""; } >&5
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.5.1") 2>&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.12") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
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
else
pkg_failed=yes
@@ -5641,14 +5641,14 @@ else
_pkg_short_errors_supported=no
fi
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
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
# Put the nasty error message in config.log where it belongs
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
@@ -5688,11 +5688,11 @@ case "x$enable_qt_dbus" in #(
"xyes") :
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
printf "%s\n" "yes" >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5DBus >= 5.9.5" >&5
printf %s "checking for Qt5DBus >= 5.9.5... " >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5DBus >= 5.12" >&5
printf %s "checking for Qt5DBus >= 5.12... " >&6; }
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5DBus >= 5.9.5\""; } >&5
($PKG_CONFIG --exists --print-errors "Qt5DBus >= 5.9.5") 2>&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.12") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
@@ -6354,12 +6354,12 @@ if test -n "$libtorrent_CFLAGS"; then
pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.11\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.11") 2>&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.12") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
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
else
pkg_failed=yes
@@ -6371,12 +6371,12 @@ if test -n "$libtorrent_LIBS"; then
pkg_cv_libtorrent_LIBS="$libtorrent_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.11\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.11") 2>&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.12") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
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
else
pkg_failed=yes
@@ -6397,14 +6397,14 @@ else
_pkg_short_errors_supported=no
fi
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
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
# Put the nasty error message in config.log where it belongs
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
@@ -7401,7 +7401,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
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.1, which was
generated by GNU Autoconf 2.70. Invocation command line was
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
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
qbittorrent config.status v4.3.3
qbittorrent config.status v4.3.4.1
configured by $0, generated by GNU Autoconf 2.70,
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.1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])
: ${CFLAGS=""}
@@ -141,7 +141,7 @@ AS_IF([test "x$QT_QMAKE" = "x"],
[AC_MSG_ERROR([Could not find qmake])
])
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])
AS_CASE(["x$enable_qt_dbus"],
@@ -180,7 +180,7 @@ AC_MSG_NOTICE([Boost.System LIB: "$BOOST_SYSTEM_LIB"])
LIBS="$BOOST_SYSTEM_LIB $LIBS"
PKG_CHECK_MODULES(libtorrent,
[libtorrent-rasterbar >= 1.2.11],
[libtorrent-rasterbar >= 1.2.12],
[CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS"
LIBS="$libtorrent_LIBS $LIBS"])

2
dist/mac/Info.plist vendored
View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
Description=qBittorrent-nox service for user %I
Documentation=man:qbittorrent-nox(1)
Wants=network-online.target
After=network-online.target nss-lookup.target
After=local-fs.target network-online.target nss-lookup.target
[Service]
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_CZECH} "Vytvořit zástupce v nabídce Start"
;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_CZECH} "Otevírat .torrent soubory pomocí 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_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_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_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_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."

View File

@@ -1,59 +1,59 @@
;Installer strings
;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_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_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_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_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_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_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_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_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_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_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_ESTONIAN} "Uninstalling previous version."
LangString inst_unist ${LANG_ESTONIAN} "Uninstallitakse eelmist versiooni."
;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_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_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
;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_ESTONIAN} "Remove shortcuts"
LangString remove_shortcuts ${LANG_ESTONIAN} "Eemalda otseteed"
;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_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_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_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_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_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_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_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_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_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_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_PORTUGUESEBR} "Adicionando regra no firewall do Windows"
;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_PORTUGUESEBR} "Executar qBittorrent."
;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_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_PORTUGUESEBR} "Remover associação de arquivos"
;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_PORTUGUESEBR} "Remover configurações"
;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_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_PORTUGUESEBR} "Remover torrents e dados em cache"
;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
; Program specific
!define PROG_VERSION "4.3.3"
!define PROG_VERSION "4.3.4.1"
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
@@ -55,7 +55,7 @@ VIAddVersionKey "LegalCopyright" "Copyright ©2006-2021 The qBittorrent project"
VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client"
VIAddVersionKey "FileVersion" "${PROG_VERSION}"
VIProductVersion "${PROG_VERSION}.0"
VIProductVersion "${PROG_VERSION}"
; The default installation directory. It changes depending if we install in the 64bit dir or not.
; A caveat of this is if a user has installed a 32bit version and then runs the 64bit installer

View File

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

View File

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

View File

@@ -313,14 +313,6 @@ void Application::processMessage(const QString &message)
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)
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;
};
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
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();
logger->addMessage(tr("Torrent: %1, running external program, command: %2").arg(torrent->name(), program));
QString program = Preferences::instance()->getAutoRunProgram().trimmed();
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)
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)
argList += QString::fromWCharArray(args[i]);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QProcess proc;
proc.setProgram(QString::fromWCharArray(args[0]));
proc.setArguments(argList);
@@ -384,9 +428,6 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
args->startupInfo->hStdError = nullptr;
});
proc.startDetached();
#else
QProcess::startDetached(QString::fromWCharArray(args[0]), argList);
#endif // QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
#else // Q_OS_WIN
// Cannot give users shell environment by default, as doing so could
// 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
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
app->setAttribute(Qt::AA_DisableWindowContextHelpButton);
#endif
@@ -251,6 +251,9 @@ int main(int argc, char *argv[])
// On OS X the standard is to not show icons in the menus
app->setAttribute(Qt::AA_DontShowIconsInMenus);
#else
if (!Preferences::instance()->iconsInMenusEnabled())
app->setAttribute(Qt::AA_DontShowIconsInMenus);
#endif
if (!firstTimeUser)
@@ -378,11 +381,7 @@ void showSplashScreen()
const QString version = QBT_VERSION;
painter.setPen(QPen(Qt::white));
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);
#else
painter.drawText(224 - painter.fontMetrics().width(version), 270, version);
#endif
QSplashScreen *splash = new QSplashScreen(splashImg);
splash->show();
QTimer::singleShot(1500, splash, &QObject::deleteLater);

View File

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

View File

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

View File

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

View File

@@ -55,13 +55,8 @@ AsyncFileStorage::~AsyncFileStorage()
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); }
, Qt::QueuedConnection);
#else
QMetaObject::invokeMethod(this, "store_impl", Qt::QueuedConnection
, Q_ARG(QString, fileName), Q_ARG(QByteArray, data));
#endif
}
QDir AsyncFileStorage::storageDir() const

View File

@@ -31,6 +31,7 @@ HEADERS += \
$$PWD/bittorrent/torrentinfo.h \
$$PWD/bittorrent/tracker.h \
$$PWD/bittorrent/trackerentry.h \
$$PWD/digest32.h \
$$PWD/exceptions.h \
$$PWD/filesystemwatcher.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))
renamingFileIndex = i;
if (areSameFileNames(path, newFilePath))
else if (areSameFileNames(path, 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))
renamingFileIndexes.append(i);
if (path.startsWith(newFolderPath, CASE_SENSITIVITY))
else if (path.startsWith(newFolderPath, CASE_SENSITIVITY))
throw RuntimeError {tr("The folder already exists: '%1'.").arg(newFolderPath)};
}

View File

@@ -33,7 +33,7 @@
#include "base/bittorrent/common.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 auto findInDir = [](const QString &dirPath, QStringList &fileNames) -> bool

View File

@@ -32,7 +32,7 @@
namespace BitTorrent
{
class InfoHash;
class TorrentID;
}
class FileSearcher final : public QObject
@@ -44,9 +44,9 @@ public:
FileSearcher() = default;
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);
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.
* 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
* modify it under the terms of the GNU General Public License
@@ -28,68 +28,54 @@
#include "infohash.h"
#include <QByteArray>
#include <QHash>
const int TorrentIDTypeId = qRegisterMetaType<BitTorrent::TorrentID>();
using namespace BitTorrent;
const int InfoHashTypeId = qRegisterMetaType<InfoHash>();
InfoHash::InfoHash(const lt::sha1_hash &nativeHash)
: m_valid(true)
, m_nativeHash(nativeHash)
BitTorrent::InfoHash::InfoHash(const WrappedType &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)
: 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
bool BitTorrent::InfoHash::isValid() const
{
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;
}
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)
== static_cast<lt::sha1_hash>(right));
return infoHash.toTorrentID();
}
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);
}
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.
* 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
* modify it under the terms of the GNU General Public License
@@ -28,41 +28,60 @@
#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 <QString>
#include "base/digest32.h"
using SHA1Hash = Digest32<160>;
using SHA256Hash = Digest32<256>;
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
{
public:
InfoHash() = default;
InfoHash(const lt::sha1_hash &nativeHash);
InfoHash(const QString &hashString);
InfoHash(const InfoHash &other) = default;
#if (LIBTORRENT_VERSION_NUM >= 20000)
using WrappedType = lt::info_hash_t;
#else
using WrappedType = lt::sha1_hash;
#endif
static constexpr int length()
{
return lt::sha1_hash::size();
}
InfoHash() = default;
InfoHash(const InfoHash &other) = default;
InfoHash(const WrappedType &nativeHash);
bool isValid() const;
TorrentID toTorrentID() const;
operator lt::sha1_hash() const;
operator QString() const;
operator WrappedType() const;
private:
bool m_valid = false;
lt::sha1_hash m_nativeHash;
QString m_hashString;
WrappedType m_nativeHash;
};
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);
uint qHash(const InfoHash &key, uint seed);
}
Q_DECLARE_METATYPE(BitTorrent::InfoHash)
Q_DECLARE_METATYPE(BitTorrent::TorrentID)

View File

@@ -40,15 +40,15 @@
namespace
{
bool isBitTorrentInfoHash(const QString &string)
bool isSHA1Hash(const QString &string)
{
// There are 2 representations for BitTorrent info hash:
// 1. 40 chars hex-encoded string
// == 20 (SHA-1 length in bytes) * 2 (each byte maps to 2 hex characters)
// 2. 32 chars Base32 encoded string
// == 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_BASE32_SIZE = BitTorrent::InfoHash::length() * 1.6;
const int SHA1_HEX_SIZE = SHA1Hash::length() * 2;
const int SHA1_BASE32_SIZE = SHA1Hash::length() * 1.6;
return ((((string.size() == SHA1_HEX_SIZE))
&& !string.contains(QRegularExpression(QLatin1String("[^0-9A-Fa-f]"))))
@@ -65,7 +65,7 @@ MagnetUri::MagnetUri(const QString &source)
{
if (source.isEmpty()) return;
if (isBitTorrentInfoHash(source))
if (isSHA1Hash(source))
m_url = QLatin1String("magnet:?xt=urn:btih:") + source;
lt::error_code ec;
@@ -73,12 +73,18 @@ MagnetUri::MagnetUri(const QString &source)
if (ec) return;
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_trackers.reserve(m_addTorrentParams.trackers.size());
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());
for (const std::string &urlSeed : m_addTorrentParams.url_seeds)
@@ -90,9 +96,9 @@ bool MagnetUri::isValid() const
return m_valid;
}
InfoHash MagnetUri::hash() const
InfoHash MagnetUri::infoHash() const
{
return m_hash;
return m_infoHash;
}
QString MagnetUri::name() const

View File

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

View File

@@ -44,6 +44,7 @@
#include <libtorrent/alert_types.hpp>
#include <libtorrent/bdecode.hpp>
#include <libtorrent/bencode.hpp>
#include <libtorrent/entry.hpp>
#include <libtorrent/error_code.hpp>
#include <libtorrent/extensions/smart_ban.hpp>
#include <libtorrent/extensions/ut_metadata.hpp>
@@ -55,6 +56,7 @@
#include <libtorrent/session_stats.hpp>
#include <libtorrent/session_status.hpp>
#include <libtorrent/torrent_info.hpp>
#include <libtorrent/write_resume_data.hpp>
#include <QDebug>
#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
QString convertIfaceNameToGuid(const QString &name)
{
@@ -372,6 +385,7 @@ Session::Session(QObject *parent)
, m_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0)
, m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
, m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY("UPnPLeaseDuration"), 0)
, m_peerToS(BITTORRENT_SESSION_KEY("PeerToS"), 0x20)
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), false)
, m_includeOverheadInLimits(BITTORRENT_SESSION_KEY("IncludeOverheadInLimits"), false)
, m_announceIP(BITTORRENT_SESSION_KEY("AnnounceIP"))
@@ -388,7 +402,7 @@ Session::Session(QObject *parent)
, clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
, m_IDNSupportEnabled(BITTORRENT_SESSION_KEY("IDNSupportEnabled"), 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_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false)
, m_additionalTrackers(BITTORRENT_SESSION_KEY("AdditionalTrackers"))
@@ -1110,11 +1124,7 @@ void Session::initializeNativeSession()
m_nativeSession->set_alert_notify([this]()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(this, &Session::readAlerts, Qt::QueuedConnection);
#else
QMetaObject::invokeMethod(this, "readAlerts", Qt::QueuedConnection);
#endif
});
// Enabling plugins
@@ -1335,9 +1345,10 @@ void Session::loadLTSettings(lt::settings_pack &settingsPack)
// Outgoing ports
settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
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());
// Type of service
settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
// Include overhead in transfer limits
settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
// IP address to announce to trackers
@@ -1387,15 +1398,11 @@ void Session::loadLTSettings(lt::settings_pack &settingsPack)
break;
}
#ifdef HAS_IDN_SUPPORT
settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
#endif
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());
#endif
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};
if (!addr.isNull())
{
const QString ip = ((addr.protocol() == QAbstractSocket::IPv6Protocol)
? ('[' + Utils::Net::canonicalIPv6Addr(addr).toString() + ']')
: addr.toString());
endpoints << (ip + portString);
const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
const QString ip = isIPv6
? Utils::Net::canonicalIPv6Addr(addr).toString()
: 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;
}
else
@@ -1608,7 +1617,7 @@ void Session::populateAdditionalTrackers()
{
tracker = tracker.trimmed();
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
// since `deleteTorrent()` modifies it indirectly
const QHash<InfoHash, TorrentImpl *> torrents {m_torrents};
const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
for (TorrentImpl *const torrent : torrents)
{
if (torrent->isSeed() && !torrent->isForced())
@@ -1640,12 +1649,12 @@ void Session::processShareLimits()
if (m_maxRatioAction == Remove)
{
LogMsg(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name()));
deleteTorrent(torrent->hash());
deleteTorrent(torrent->id());
}
else if (m_maxRatioAction == DeleteFiles)
{
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())
{
@@ -1679,12 +1688,12 @@ void Session::processShareLimits()
if (m_maxRatioAction == Remove)
{
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)
{
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())
{
@@ -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);
if (torrent)
@@ -1747,9 +1756,9 @@ void Session::fileSearchFinished(const InfoHash &id, const QString &savePath, co
}
// 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
@@ -1764,7 +1773,7 @@ bool Session::hasUnfinishedTorrents() const
{
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
// 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;
qDebug("Deleting torrent with hash: %s", qUtf8Printable(torrent->hash()));
qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
emit torrentAboutToBeRemoved(torrent);
// Remove it from session
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 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();
}
m_removingTorrents[torrent->hash()] = {torrent->name(), rootPath, deleteOption};
m_removingTorrents[torrent->id()] = {torrent->name(), rootPath, deleteOption};
if (m_moveStorageQueue.size() > 1)
{
@@ -1856,48 +1865,44 @@ bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOptio
}
// Remove it from torrent resume directory
const QString resumedataFile = QString::fromLatin1("%1.fastresume").arg(torrent->hash());
const QString metadataFile = QString::fromLatin1("%1.torrent").arg(torrent->hash());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
const QString resumedataFile = QString::fromLatin1("%1.fastresume").arg(torrent->id().toString());
const QString metadataFile = QString::fromLatin1("%1.torrent").arg(torrent->id().toString());
QMetaObject::invokeMethod(m_resumeDataSavingManager, [this, resumedataFile, metadataFile]()
{
m_resumeDataSavingManager->remove(resumedataFile);
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;
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;
m_downloadedMetadata.erase(downloadedMetadataIter);
--m_extraLimit;
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;
}
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::vector<ElementType>
, std::greater<ElementType>> torrentQueue;
// Sort torrents by queue position
for (const InfoHash &infoHash : hashes)
for (const TorrentID &id : ids)
{
TorrentImpl *const torrent = m_torrents.value(infoHash);
if (torrent && !torrent->isSeed())
torrentQueue.emplace(torrent->queuePosition(), torrent);
const TorrentImpl *torrent = m_torrents.value(id);
if (!torrent) continue;
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)
@@ -1911,17 +1916,18 @@ void Session::increaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
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;
// Sort torrents by queue position
for (const InfoHash &infoHash : hashes)
for (const TorrentID &id : ids)
{
TorrentImpl *const torrent = m_torrents.value(infoHash);
if (torrent && !torrent->isSeed())
torrentQueue.emplace(torrent->queuePosition(), torrent);
const TorrentImpl *torrent = m_torrents.value(id);
if (!torrent) continue;
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)
@@ -1938,17 +1944,18 @@ void Session::decreaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
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;
// Sort torrents by queue position
for (const InfoHash &infoHash : hashes)
for (const TorrentID &id : ids)
{
TorrentImpl *const torrent = m_torrents.value(infoHash);
if (torrent && !torrent->isSeed())
torrentQueue.emplace(torrent->queuePosition(), torrent);
const TorrentImpl *torrent = m_torrents.value(id);
if (!torrent) continue;
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)
@@ -1962,19 +1969,20 @@ void Session::topTorrentsQueuePos(const QVector<InfoHash> &hashes)
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::vector<ElementType>
, std::greater<ElementType>> torrentQueue;
// Sort torrents by queue position
for (const InfoHash &infoHash : hashes)
for (const TorrentID &id : ids)
{
TorrentImpl *const torrent = m_torrents.value(infoHash);
if (torrent && !torrent->isSeed())
torrentQueue.emplace(torrent->queuePosition(), torrent);
const TorrentImpl *torrent = m_torrents.value(id);
if (!torrent) continue;
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)
@@ -1991,6 +1999,25 @@ void Session::bottomTorrentsQueuePos(const QVector<InfoHash> &hashes)
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)
{
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);
TorrentInfo metadata = (hasMetadata ? std::get<TorrentInfo>(source) : TorrentInfo {});
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,
// but as previous experience has shown, it actually creates unnecessary
// problems and unwanted behavior due to the fact that it was originally
// added with parameters other than those provided by the user.
cancelDownloadMetadata(hash);
cancelDownloadMetadata(id);
// We should not add the torrent if it is already
// processed or is pending to add to session
if (m_loadingTorrents.contains(hash))
if (m_loadingTorrents.contains(id))
return false;
TorrentImpl *const torrent = m_torrents.value(hash);
TorrentImpl *const torrent = m_torrents.value(id);
if (torrent)
{ // a duplicate torrent is added
if (torrent->isPrivate() || (hasMetadata && metadata.isPrivate()))
@@ -2190,7 +2217,7 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
if (!isFindingIncompleteFiles)
return loadTorrent(loadTorrentParams);
m_loadingTorrents.insert(hash, loadTorrentParams);
m_loadingTorrents.insert(id, loadTorrentParams);
return true;
}
@@ -2207,8 +2234,12 @@ bool Session::loadTorrent(LoadTorrentParams params)
p.max_uploads = maxUploadsPerTorrent();
const bool hasMetadata = (p.ti && p.ti->is_valid());
const InfoHash hash = (hasMetadata ? p.ti->info_hash() : p.info_hash);
m_loadingTorrents.insert(hash, params);
#if (LIBTORRENT_VERSION_NUM >= 20000)
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
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
{
const InfoHash searchId = torrentInfo.hash();
const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
const QStringList originalFileNames = torrentInfo.filePaths();
const QString completeSavePath = savePath;
const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {});
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(m_fileSearcher, [=]()
{
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
@@ -2240,17 +2265,17 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri)
{
if (!magnetUri.isValid()) return false;
const InfoHash hash = magnetUri.hash();
const auto id = TorrentID::fromInfoHash(magnetUri.infoHash());
const QString name = magnetUri.name();
// We should not add torrent if it's already
// processed or adding to session
if (m_torrents.contains(hash)) return false;
if (m_loadingTorrents.contains(hash)) return false;
if (m_downloadedMetadata.contains(hash)) return false;
if (m_torrents.contains(id)) return false;
if (m_loadingTorrents.contains(id)) return false;
if (m_downloadedMetadata.contains(id)) return false;
qDebug("Adding torrent to preload metadata...");
qDebug(" -> Hash: %s", qUtf8Printable(hash));
qDebug(" -> Torrent ID: %s", qUtf8Printable(id.toString()));
qDebug(" -> Name: %s", qUtf8Printable(name));
lt::add_torrent_params p = magnetUri.addTorrentParams();
@@ -2266,7 +2291,7 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri)
p.max_connections = maxConnectionsPerTorrent();
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();
// Forced start
@@ -2299,7 +2324,7 @@ void Session::exportTorrentFile(const Torrent *torrent, TorrentExportFolder fold
((folder == TorrentExportFolder::Finished) && !finishedTorrentExportDirectory().isEmpty()));
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);
const QString torrentPath = QDir(m_resumeFolderPath).absoluteFilePath(torrentFilename);
const QDir exportPath(folder == TorrentExportFolder::Regular ? torrentExportDirectory() : finishedTorrentExportDirectory());
@@ -2326,7 +2351,10 @@ void Session::generateResumeData()
if (!torrent->isValid()) continue;
if (torrent->needSaveResumeData())
{
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
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!
const int queuePos = static_cast<LTUnderlyingType<lt::queue_position_t>>(torrent->nativeHandle().queue_position());
if (queuePos >= 0)
queue[queuePos] = torrent->hash();
queue[queuePos] = torrent->id().toString();
}
QByteArray data;
data.reserve(((InfoHash::length() * 2) + 1) * queue.size());
for (const QString &hash : asConst(queue))
data += (hash.toLatin1() + '\n');
data.reserve(((TorrentID::length() * 2) + 1) * queue.size());
for (const QString &torrentID : asConst(queue))
data += (torrentID.toLatin1() + '\n');
const QString filename = QLatin1String {"queue"};
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(m_resumeDataSavingManager
, [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"};
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(m_resumeDataSavingManager
, [this, filename]() { m_resumeDataSavingManager->remove(filename); });
#else
QMetaObject::invokeMethod(m_resumeDataSavingManager, "remove", Q_ARG(QString, filename));
#endif
}
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
{
return m_ignoreLimitsOnLAN;
@@ -3742,11 +3775,11 @@ void Session::setMaxRatioAction(const MaxRatioAction act)
// If this functions returns true, we cannot add torrent to session,
// 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)
|| m_loadingTorrents.contains(hash)
|| m_downloadedMetadata.contains(hash));
return (m_torrents.contains(id)
|| m_loadingTorrents.contains(id)
|| m_downloadedMetadata.contains(id));
}
void Session::updateSeedingLimitTimer()
@@ -3765,51 +3798,43 @@ void Session::updateSeedingLimitTimer()
void Session::handleTorrentShareLimitChanged(TorrentImpl *const torrent)
{
torrent->saveResumeData();
updateSeedingLimitTimer();
}
void Session::handleTorrentNameChanged(TorrentImpl *const torrent)
{
torrent->saveResumeData();
Q_UNUSED(torrent);
}
void Session::handleTorrentSavePathChanged(TorrentImpl *const torrent)
{
torrent->saveResumeData();
emit torrentSavePathChanged(torrent);
}
void Session::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
{
torrent->saveResumeData();
emit torrentCategoryChanged(torrent, oldCategory);
}
void Session::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
{
torrent->saveResumeData();
emit torrentTagAdded(torrent, tag);
}
void Session::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag)
{
torrent->saveResumeData();
emit torrentTagRemoved(torrent, tag);
}
void Session::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
{
torrent->saveResumeData();
emit torrentSavingModeChanged(torrent);
}
void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
{
torrent->saveResumeData();
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);
if (torrent->trackers().size() == newTrackers.size())
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)
{
torrent->saveResumeData();
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);
if (torrent->trackers().empty())
emit trackerlessStateChanged(torrent, true);
@@ -3830,20 +3853,17 @@ void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVe
void Session::handleTorrentTrackersChanged(TorrentImpl *const torrent)
{
torrent->saveResumeData();
emit trackersChanged(torrent);
}
void Session::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
{
torrent->saveResumeData();
for (const QUrl &newUrlSeed : newUrlSeeds)
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)
{
torrent->saveResumeData();
for (const QUrl &urlSeed : urlSeeds)
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
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
{
torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName));
@@ -3871,13 +3891,11 @@ void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
void Session::handleTorrentPaused(TorrentImpl *const torrent)
{
torrent->saveResumeData();
emit torrentPaused(torrent);
}
void Session::handleTorrentResumed(TorrentImpl *const torrent)
{
torrent->saveResumeData();
emit torrentResumed(torrent);
}
@@ -3888,8 +3906,6 @@ void Session::handleTorrentChecked(TorrentImpl *const torrent)
void Session::handleTorrentFinished(TorrentImpl *const torrent)
{
if (!torrent->hasError() && !torrent->hasMissingFiles())
torrent->saveResumeData();
emit torrentFinished(torrent);
qDebug("Checking if the torrent contains torrent files to download");
@@ -3925,21 +3941,52 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent)
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;
// 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.
// Copying lt::entry objects around isn't cheap.
const QString filename = QString::fromLatin1("%1.fastresume").arg(torrent->hash());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
auto resumeDataPtr = std::make_shared<lt::entry>(lt::write_resume_data(p));
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
, [this, filename, data]() { m_resumeDataSavingManager->save(filename, data); });
#else
QMetaObject::invokeMethod(m_resumeDataSavingManager, "save"
, Q_ARG(QString, filename), Q_ARG(std::shared_ptr<lt::entry>, data));
#endif
, [this, filename, resumeDataPtr]() { m_resumeDataSavingManager->save(filename, resumeDataPtr); });
}
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
{
const InfoHash infoHash = job.torrentHandle.info_hash();
const TorrentImpl *torrent = m_torrents.value(infoHash);
const QString torrentName = (torrent ? torrent->name() : QString {infoHash});
#if (LIBTORRENT_VERSION_NUM >= 20000)
const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
#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));
job.torrentHandle.move_storage(job.path.toUtf8().constData()
@@ -4094,15 +4145,9 @@ void Session::configureDeferred()
{
if (m_deferredConfigureScheduled)
return;
m_deferredConfigureScheduled = true;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
QMetaObject::invokeMethod(this
, qOverload<>(&Session::configure)
, Qt::QueuedConnection);
#else
QMetaObject::invokeMethod(this, "configure", Qt::QueuedConnection);
#endif
m_deferredConfigureScheduled = true;
QMetaObject::invokeMethod(this, qOverload<>(&Session::configure), Qt::QueuedConnection);
}
// Enable IP Filtering
@@ -4141,9 +4186,9 @@ void Session::disableIPFilter()
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;
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());
if (!hasMetadata && !root.dict_find("info-hash"))
{
// 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 false;
return true;
}
@@ -4568,7 +4569,7 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle)
const LoadTorrentParams params = m_loadingTorrents.take(nativeHandle.info_hash());
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();
@@ -4583,7 +4584,7 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle)
{
// Backup torrent file
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
{
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)
{
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->deleteOption == DeleteTorrent)
@@ -4656,8 +4661,13 @@ void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
{
const InfoHash infoHash {p->info_hash};
const auto removingTorrentDataIter = m_removingTorrents.find(infoHash);
#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(id);
if (removingTorrentDataIter == m_removingTorrents.end())
return;
@@ -4669,8 +4679,13 @@ void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
{
const InfoHash infoHash {p->info_hash};
const auto removingTorrentDataIter = m_removingTorrents.find(infoHash);
#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(id);
if (removingTorrentDataIter == m_removingTorrents.end())
return;
@@ -4694,8 +4709,13 @@ void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_ale
void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
{
const InfoHash hash {p->handle.info_hash()};
const auto downloadedMetadataIter = m_downloadedMetadata.find(hash);
#if (LIBTORRENT_VERSION_NUM >= 20000)
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())
{
@@ -4716,11 +4736,11 @@ void Session::handleFileErrorAlert(const lt::file_error_alert *p)
if (!torrent)
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());
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()};
Q_ASSERT(newPath == currentJob.path);
const InfoHash infoHash = currentJob.torrentHandle.info_hash();
TorrentImpl *torrent = m_torrents.value(infoHash);
const QString torrentName = (torrent ? torrent->name() : QString {infoHash});
#if (LIBTORRENT_VERSION_NUM >= 20000)
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
#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));
handleMoveTorrentStorageJobFinished();
@@ -4934,9 +4959,14 @@ void Session::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
Q_ASSERT(currentJob.torrentHandle == p->handle);
const InfoHash infoHash = currentJob.torrentHandle.info_hash();
TorrentImpl *torrent = m_torrents.value(infoHash);
const QString torrentName = (torrent ? torrent->name() : QString {infoHash});
#if (LIBTORRENT_VERSION_NUM >= 20000)
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
#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 errorMessage = QString::fromStdString(p->message());
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)
{
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)
continue;

View File

@@ -51,14 +51,6 @@
#include "sessionstatus.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 QNetworkConfiguration;
class QNetworkConfigurationManager;
@@ -107,8 +99,8 @@ namespace BitTorrent
class Torrent;
class TorrentImpl;
class Tracker;
class TrackerEntry;
struct LoadTorrentParams;
struct TrackerEntry;
enum class MoveStorageMode;
@@ -392,6 +384,8 @@ namespace BitTorrent
void setOutgoingPortsMax(int max);
int UPnPLeaseDuration() const;
void setUPnPLeaseDuration(int duration);
int peerToS() const;
void setPeerToS(int value);
bool ignoreLimitsOnLAN() const;
void setIgnoreLimitsOnLAN(bool ignore);
bool includeOverheadInLimits() const;
@@ -440,7 +434,7 @@ namespace BitTorrent
#endif
void startUpTorrents();
Torrent *findTorrent(const InfoHash &hash) const;
Torrent *findTorrent(const TorrentID &id) const;
QVector<Torrent *> torrents() const;
bool hasActiveTorrents() const;
bool hasUnfinishedTorrents() const;
@@ -456,21 +450,22 @@ namespace BitTorrent
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 MagnetUri &magnetUri, 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 cancelDownloadMetadata(const InfoHash &hash);
bool cancelDownloadMetadata(const TorrentID &id);
void recursiveTorrentDownload(const InfoHash &hash);
void increaseTorrentsQueuePos(const QVector<InfoHash> &hashes);
void decreaseTorrentsQueuePos(const QVector<InfoHash> &hashes);
void topTorrentsQueuePos(const QVector<InfoHash> &hashes);
void bottomTorrentsQueuePos(const QVector<InfoHash> &hashes);
void recursiveTorrentDownload(const TorrentID &id);
void increaseTorrentsQueuePos(const QVector<TorrentID> &ids);
void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids);
void topTorrentsQueuePos(const QVector<TorrentID> &ids);
void bottomTorrentsQueuePos(const QVector<TorrentID> &ids);
// Torrent interface
void handleTorrentNeedSaveResumeData(const TorrentImpl *torrent);
void handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent);
void handleTorrentShareLimitChanged(TorrentImpl *const torrent);
void handleTorrentNameChanged(TorrentImpl *const torrent);
@@ -489,7 +484,7 @@ namespace BitTorrent
void handleTorrentTrackersChanged(TorrentImpl *const torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds);
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 handleTorrentTrackerWarning(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 handleIPFilterError();
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
void networkOnlineStateChanged(bool online);
@@ -632,8 +627,8 @@ namespace BitTorrent
void createTorrent(const lt::torrent_handle &nativeHandle);
void saveResumeData();
void saveTorrentsQueue();
void removeTorrentsQueue();
void saveTorrentsQueue() const;
void removeTorrentsQueue() 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_outgoingPortsMax;
CachedSettingValue<int> m_UPnPLeaseDuration;
CachedSettingValue<int> m_peerToS;
CachedSettingValue<bool> m_ignoreLimitsOnLAN;
CachedSettingValue<bool> m_includeOverheadInLimits;
CachedSettingValue<QString> m_announceIP;
@@ -769,17 +765,18 @@ namespace BitTorrent
ResumeDataSavingManager *m_resumeDataSavingManager = nullptr;
FileSearcher *m_fileSearcher = nullptr;
QSet<InfoHash> m_downloadedMetadata;
QSet<TorrentID> m_downloadedMetadata;
QHash<InfoHash, TorrentImpl *> m_torrents;
QHash<InfoHash, LoadTorrentParams> m_loadingTorrents;
QHash<TorrentID, TorrentImpl *> m_torrents;
QHash<TorrentID, LoadTorrentParams> m_loadingTorrents;
QHash<QString, AddTorrentParams> m_downloadedTorrents;
QHash<InfoHash, RemovingTorrentData> m_removingTorrents;
QHash<TorrentID, RemovingTorrentData> m_removingTorrents;
QSet<TorrentID> m_needSaveResumeDataTorrents;
QStringMap m_categories;
QSet<QString> m_tags;
// I/O errored torrents
QSet<InfoHash> m_recentErroredTorrents;
QSet<TorrentID> m_recentErroredTorrents;
QTimer *m_recentErroredTorrentsTimer = nullptr;
SessionMetricIndices m_metricIndices;
@@ -795,8 +792,3 @@ namespace BitTorrent
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 <type_traits>
#include <QHash>
#include "infohash.h"
namespace BitTorrent
{
uint qHash(const TorrentState key, const uint seed)
@@ -42,15 +42,20 @@ namespace BitTorrent
// Torrent
const qreal Torrent::USE_GLOBAL_RATIO = -2.;
const qreal Torrent::NO_RATIO_LIMIT = -1.;
const qreal Torrent::USE_GLOBAL_RATIO = -2;
const qreal Torrent::NO_RATIO_LIMIT = -1;
const int Torrent::USE_GLOBAL_SEEDING_TIME = -2;
const int Torrent::NO_SEEDING_TIME_LIMIT = -1;
const qreal Torrent::MAX_RATIO = 9999.;
const qreal Torrent::MAX_RATIO = 9999;
const int Torrent::MAX_SEEDING_TIME = 525600;
TorrentID Torrent::id() const
{
return infoHash().toTorrentID();
}
bool Torrent::isResumed() const
{
return !isPaused();

View File

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

View File

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

View File

@@ -39,13 +39,15 @@
#include <libtorrent/address.hpp>
#include <libtorrent/alert_types.hpp>
#include <libtorrent/entry.hpp>
#include <libtorrent/magnet_uri.hpp>
#include <libtorrent/session.hpp>
#include <libtorrent/storage_defs.hpp>
#include <libtorrent/time.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 <QDebug>
@@ -57,7 +59,6 @@
#include "base/global.h"
#include "base/logger.h"
#include "base/preferences.h"
#include "base/profile.h"
#include "base/utils/fs.h"
#include "base/utils/string.h"
#include "common.h"
@@ -87,14 +88,135 @@ namespace
return out;
}
using ListType = lt::entry::list_type;
ListType setToEntryList(const QSet<QString> &input)
lt::announce_entry makeNativeAnnouncerEntry(const QString &url, const int tier)
{
ListType entryList;
for (const QString &setValue : input)
entryList.emplace_back(setValue.toStdString());
return entryList;
lt::announce_entry entry {url.toStdString()};
entry.tier = tier;
return entry;
}
#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_nativeSession(nativeSession)
, 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_savePath(Utils::Fs::toNativePath(params.savePath))
, m_category(params.category)
@@ -123,7 +250,6 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
if (m_useAutoTMM)
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
m_hash = InfoHash {m_nativeHandle.info_hash()};
if (m_ltAddTorrentParams.ti)
{
// 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()};
}
updateStatus();
initializeStatus(m_nativeStatus, m_ltAddTorrentParams);
updateState();
if (hasMetadata())
applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
@@ -168,9 +295,9 @@ bool TorrentImpl::isValid() const
return m_nativeHandle.is_valid();
}
InfoHash TorrentImpl::hash() const
InfoHash TorrentImpl::infoHash() const
{
return m_hash;
return m_infoHash;
}
QString TorrentImpl::name() const
@@ -185,7 +312,7 @@ QString TorrentImpl::name() const
if (!name.isEmpty())
return name;
return m_hash;
return id().toString();
}
QDateTime TorrentImpl::creationDate() const
@@ -284,6 +411,7 @@ void TorrentImpl::setAutoTMMEnabled(bool enabled)
if (m_useAutoTMM == enabled) return;
m_useAutoTMM = enabled;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentSavingModeChanged(this);
if (m_useAutoTMM)
@@ -311,7 +439,11 @@ QVector<TrackerEntry> TorrentImpl::trackers() const
entries.reserve(nativeTrackers.size());
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;
}
@@ -325,7 +457,7 @@ void TorrentImpl::addTrackers(const QVector<TrackerEntry> &trackers)
{
QSet<TrackerEntry> currentTrackers;
for (const lt::announce_entry &entry : m_nativeHandle.trackers())
currentTrackers << entry;
currentTrackers.insert({QString::fromStdString(entry.url), entry.tier});
QVector<TrackerEntry> newTrackers;
newTrackers.reserve(trackers.size());
@@ -334,13 +466,16 @@ void TorrentImpl::addTrackers(const QVector<TrackerEntry> &trackers)
{
if (!currentTrackers.contains(tracker))
{
m_nativeHandle.add_tracker(tracker.nativeEntry());
m_nativeHandle.add_tracker(makeNativeAnnouncerEntry(tracker.url, tracker.tier));
newTrackers << tracker;
}
}
if (!newTrackers.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentTrackersAdded(this, newTrackers);
}
}
void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
@@ -355,7 +490,7 @@ void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
for (const TrackerEntry &tracker : trackers)
{
nativeTrackers.emplace_back(tracker.nativeEntry());
nativeTrackers.emplace_back(makeNativeAnnouncerEntry(tracker.url, tracker.tier));
if (!currentTrackers.removeOne(tracker))
newTrackers << tracker;
@@ -363,6 +498,8 @@ void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
m_nativeHandle.replace_trackers(nativeTrackers);
m_session->handleTorrentNeedSaveResumeData(this);
if (newTrackers.isEmpty() && currentTrackers.isEmpty())
{
// when existing tracker reorders
@@ -414,7 +551,10 @@ void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
}
if (!addedUrlSeeds.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds);
}
}
void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
@@ -435,7 +575,10 @@ void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
}
if (!removedUrlSeeds.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds);
}
}
void TorrentImpl::clearPeers()
@@ -467,9 +610,7 @@ bool TorrentImpl::connectPeer(const PeerAddress &peerAddress)
bool TorrentImpl::needSaveResumeData() const
{
if (m_isStopped && !(m_nativeStatus.flags & lt::torrent_flags::auto_managed))
return false;
return m_nativeStatus.need_save_resume;
return m_nativeHandle.need_save_resume_data();
}
void TorrentImpl::saveResumeData()
@@ -548,6 +689,7 @@ bool TorrentImpl::addTag(const QString &tag)
if (!m_session->addTag(tag))
return false;
m_tags.insert(tag);
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentTagAdded(this, tag);
return true;
}
@@ -558,6 +700,7 @@ bool TorrentImpl::removeTag(const QString &tag)
{
if (m_tags.remove(tag))
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentTagRemoved(this, tag);
return true;
}
@@ -818,9 +961,7 @@ bool TorrentImpl::hasFilteredPieces() 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) + 1;
return static_cast<int>(m_nativeStatus.queue_position);
}
QString TorrentImpl::error() const
@@ -1150,6 +1291,7 @@ void TorrentImpl::setName(const QString &name)
if (m_name != name)
{
m_name = name;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentNameChanged(this);
}
}
@@ -1163,6 +1305,7 @@ bool TorrentImpl::setCategory(const QString &category)
const QString oldCategory = m_category;
m_category = category;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentCategoryChanged(this, oldCategory);
if (m_useAutoTMM)
@@ -1182,6 +1325,7 @@ void TorrentImpl::move(QString path)
if (m_useAutoTMM)
{
m_useAutoTMM = false;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentSavingModeChanged(this);
}
@@ -1197,8 +1341,8 @@ void TorrentImpl::move(QString path)
void TorrentImpl::move_impl(QString path, const MoveStorageMode mode)
{
if (path == savePath()) return;
path = Utils::Fs::toNativePath(path);
path = Utils::Fs::toNativePath(path);
if (!useTempPath())
{
moveStorage(path, mode);
@@ -1206,6 +1350,7 @@ void TorrentImpl::move_impl(QString path, const MoveStorageMode mode)
else
{
m_savePath = path;
m_session->handleTorrentNeedSaveResumeData(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
}
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
}
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'")
.arg((enabled ? tr("On") : tr("Off")), name()));
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
}
void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector<DownloadPriority> &updatedFilePrio)
@@ -1364,6 +1509,7 @@ void TorrentImpl::pause()
if (!m_isStopped)
{
m_isStopped = true;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentPaused(this);
}
@@ -1387,6 +1533,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
{
m_hasMissingFiles = false;
m_isStopped = false;
m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
reload();
updateStatus();
return;
@@ -1399,6 +1546,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready);
m_isStopped = false;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentResumed(this);
}
@@ -1434,6 +1582,7 @@ void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
{
m_session->handleTorrentNeedSaveResumeData(this);
m_storageIsMoving = hasOutstandingJob;
updateStatus();
@@ -1444,10 +1593,21 @@ void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
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())
m_moveFinishedTriggers.takeFirst()();
while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()();
}
}
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 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);
}
@@ -1503,7 +1663,8 @@ void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
return;
}
saveResumeData();
if (m_nativeHandle.need_save_resume_data())
m_session->handleTorrentNeedSaveResumeData(this);
if (m_fastresumeDataRejected && !m_hasMissingFiles)
m_fastresumeDataRejected = false;
@@ -1538,6 +1699,8 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
adjustActualSavePath();
manageIncompleteFiles();
m_session->handleTorrentNeedSaveResumeData(this);
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
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)
{
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
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.save_path = Profile::instance()->toPortablePath(
QString::fromStdString(m_ltAddTorrentParams.save_path)).toStdString();
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);
}
auto resumeDataPtr = std::make_shared<lt::entry>(lt::write_resume_data(m_ltAddTorrentParams));
lt::entry &resumeData = *resumeDataPtr;
LoadTorrentParams resumeData;
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.
// === 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);
m_session->handleTorrentResumeDataReady(this, resumeData);
}
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())
m_moveFinishedTriggers.takeFirst()();
if (isPaused() && (m_renameCount == 0))
saveResumeData(); // otherwise the new path will not be saved
m_session->handleTorrentNeedSaveResumeData(this);
}
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())
m_moveFinishedTriggers.takeFirst()();
if (isPaused() && (m_renameCount == 0))
saveResumeData(); // otherwise the new path will not be saved
m_session->handleTorrentNeedSaveResumeData(this);
}
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)
{
Q_UNUSED(p);
qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
m_maintenanceJob = MaintenanceJob::HandleMetadata;
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
}
void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
@@ -1945,6 +2079,7 @@ void TorrentImpl::setRatioLimit(qreal limit)
if (m_ratioLimit != limit)
{
m_ratioLimit = limit;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentShareLimitChanged(this);
}
}
@@ -1959,6 +2094,7 @@ void TorrentImpl::setSeedingTimeLimit(int limit)
if (m_seedingTimeLimit != limit)
{
m_seedingTimeLimit = limit;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentShareLimitChanged(this);
}
}
@@ -1969,7 +2105,7 @@ void TorrentImpl::setUploadLimit(const int limit)
return;
m_nativeHandle.set_upload_limit(limit);
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
}
void TorrentImpl::setDownloadLimit(const int limit)
@@ -1978,7 +2114,7 @@ void TorrentImpl::setDownloadLimit(const int limit)
return;
m_nativeHandle.set_download_limit(limit);
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
}
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);
else
m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
}
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);
else
m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
}
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);
else
m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
}
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);
else
m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
saveResumeData();
m_session->handleTorrentNeedSaveResumeData(this);
}
void TorrentImpl::flushCache() const

View File

@@ -68,7 +68,6 @@ namespace BitTorrent
bool forced = false;
bool paused = false;
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
@@ -99,7 +98,7 @@ namespace BitTorrent
bool isValid() const;
InfoHash hash() const override;
InfoHash infoHash() const override;
QString name() const override;
QDateTime creationDate() const override;
QString creator() const override;
@@ -301,7 +300,7 @@ namespace BitTorrent
TorrentInfo m_torrentInfo;
SpeedMonitor m_speedMonitor;
InfoHash m_hash;
InfoHash m_infoHash;
// m_moveFinishedTriggers is activated only when the following conditions are met:
// 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));
}
InfoHash TorrentInfo::hash() const
InfoHash TorrentInfo::infoHash() const
{
if (!isValid()) return {};
#if (LIBTORRENT_VERSION_NUM >= 20000)
return m_nativeInfo->info_hashes();
#else
return m_nativeInfo->info_hash();
#endif
}
QString TorrentInfo::name() const
@@ -302,7 +307,7 @@ QVector<TrackerEntry> TorrentInfo::trackers() const
ret.reserve(trackers.size());
for (const lt::announce_entry &tracker : trackers)
ret.append(tracker);
ret.append({QString::fromStdString(tracker.url)});
return ret;
}
@@ -374,7 +379,7 @@ QVector<QByteArray> TorrentInfo::pieceHashes() const
hashes.reserve(count);
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;
}
@@ -481,9 +486,13 @@ void TorrentInfo::stripRootFolder()
void TorrentInfo::addRootFolder()
{
const QString rootFolder = name();
Q_ASSERT(!rootFolder.isEmpty());
const QString originalName = name();
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();
lt::file_storage files = m_nativeInfo->files();
files.set_name(rootFolder.toStdString());

View File

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

View File

@@ -153,7 +153,7 @@ struct Tracker::TrackerAnnounceRequest
{
QHostAddress socketAddress;
QByteArray claimedAddress; // self claimed by peer
InfoHash infoHash;
TorrentID torrentID;
QString event;
Peer peer;
int numwant = 50;
@@ -295,11 +295,11 @@ void Tracker::processAnnounceRequest()
if (infoHashIter == queryParams.end())
throw TrackerError("Missing \"info_hash\" parameter");
const InfoHash infoHash(infoHashIter->toHex());
if (!infoHash.isValid())
const auto torrentID = TorrentID::fromString(infoHashIter->toHex());
if (!torrentID.isValid())
throw TrackerError("Invalid \"info_hash\" parameter");
announceReq.infoHash = infoHash;
announceReq.torrentID = torrentID;
// 2. peer_id
const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID);
@@ -381,19 +381,19 @@ void Tracker::processAnnounceRequest()
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
if (m_torrents.size() >= MAX_TORRENTS)
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)
{
const auto torrentStatsIter = m_torrents.find(announceReq.infoHash);
const auto torrentStatsIter = m_torrents.find(announceReq.torrentID);
if (torrentStatsIter == m_torrents.end())
return;
@@ -405,7 +405,7 @@ void Tracker::unregisterPeer(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
{

View File

@@ -102,6 +102,6 @@ namespace BitTorrent
Http::Request m_request;
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.
* 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
* modify it under the terms of the GNU General Public License
@@ -28,139 +28,15 @@
#include "trackerentry.h"
#include <algorithm>
#include <libtorrent/version.hpp>
#include <QString>
#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)
{
return ((left.tier() == right.tier())
&& QUrl(left.url()) == QUrl(right.url()));
return ((left.tier == right.tier)
&& QUrl(left.url) == QUrl(right.url));
}
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.
* 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
* modify it under the terms of the GNU General Public License
@@ -28,17 +28,14 @@
#pragma once
#include <libtorrent/announce_entry.hpp>
#include <QtGlobal>
class QString;
#include <QString>
#include <QVector>
namespace BitTorrent
{
class TrackerEntry
struct TrackerEntry
{
public:
enum Status
{
NotContacted = 1,
@@ -47,26 +44,26 @@ namespace BitTorrent
NotWorking = 4
};
TrackerEntry() = default;
TrackerEntry(const QString &url);
TrackerEntry(const lt::announce_entry &nativeEntry);
TrackerEntry(const TrackerEntry &other) = default;
TrackerEntry &operator=(const TrackerEntry &other) = default;
struct EndpointStats
{
int protocolVersion = 1;
QString url() const;
Status status() const;
Status status = NotContacted;
int numSeeds = -1;
int numLeeches = -1;
int numDownloaded = -1;
};
int tier() const;
void setTier(int value);
QString url;
int tier = 0;
int numSeeds() const;
int numLeeches() const;
int numDownloaded() const;
QVector<EndpointStats> endpoints {};
const lt::announce_entry &nativeEntry() const;
private:
lt::announce_entry m_nativeEntry;
// Deprecated fields
Status status = NotContacted;
int numSeeds = -1;
int numLeeches = -1;
int numDownloaded = -1;
};
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 <QHostInfo>
#include <QStringList>
#include <QTextCodec>
#ifndef QT_NO_OPENSSL
#include <QSslSocket>
@@ -89,6 +88,14 @@ namespace
return hostname.toLocal8Bit();
}
bool canEncodeAsLatin1(const QStringView string)
{
return std::none_of(string.cbegin(), string.cend(), [](const QChar &ch)
{
return ch > QChar(0xff);
});
}
} // namespace
using namespace Net;
@@ -137,11 +144,10 @@ Smtp::~Smtp()
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body)
{
const Preferences *const pref = Preferences::instance();
QTextCodec *latin1 = QTextCodec::codecForName("latin1");
m_message = "Date: " + getCurrentDateTime().toLatin1() + "\r\n"
+ encodeMimeHeader("From", from, latin1)
+ encodeMimeHeader("Subject", subject, latin1)
+ encodeMimeHeader("To", to, latin1)
+ encodeMimeHeader("From", from)
+ encodeMimeHeader("Subject", subject)
+ encodeMimeHeader("To", to)
+ "MIME-Version: 1.0\r\n"
+ "Content-Type: text/plain; charset=UTF-8\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 line = key.toLatin1() + ": ";
if (!prefix.isEmpty()) line += prefix;
if (!value.contains("=?") && latin1->canEncode(value))
if (!value.contains("=?") && canEncodeAsLatin1(value))
{
bool firstWord = true;
for (const QByteArray &word : asConst(value.toLatin1().split(' ')))

View File

@@ -88,7 +88,7 @@ namespace Net
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 helo();
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);
}
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
bool Preferences::isToolbarDisplayed() const
@@ -853,7 +863,7 @@ void Preferences::setAutoRunProgram(const QString &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
{
return value("AutoRun/ConsoleEnabled", false).toBool();

View File

@@ -248,7 +248,7 @@ public:
void setAutoRunEnabled(bool enabled);
QString getAutoRunProgram() const;
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;
void setAutoRunConsoleEnabled(bool enabled);
#endif
@@ -313,6 +313,8 @@ public:
void setCloseToTrayNotified(bool b);
TrayIcon::Style trayIconStyle() const;
void setTrayIconStyle(TrayIcon::Style style);
bool iconsInMenusEnabled() const;
void setIconsInMenusEnabled(bool enable);
#endif // Q_OS_MACOS
// 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];
if (regex.pattern().isEmpty())
{
regex = QRegularExpression
{
(isRegex ? expression : Utils::String::wildcardToRegex(expression))
, QRegularExpression::CaseInsensitiveOption};
const QString pattern = (isRegex ? expression : Utils::String::wildcardToRegexPattern(expression));
regex = QRegularExpression {pattern, QRegularExpression::CaseInsensitiveOption};
}
return regex;

View File

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

View File

@@ -32,7 +32,7 @@
#include "bittorrent/torrent.h"
const QString TorrentFilter::AnyCategory;
const InfoHashSet TorrentFilter::AnyHash {{}};
const TorrentIDSet TorrentFilter::AnyID {{}};
const QString TorrentFilter::AnyTag;
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
@@ -49,19 +49,19 @@ const TorrentFilter TorrentFilter::ErroredTorrent(TorrentFilter::Errored);
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_category(category)
, 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_category(category)
, m_tag(tag)
, m_hashSet(hashSet)
, m_idSet(idSet)
{
setTypeByName(filter);
}
@@ -107,11 +107,11 @@ bool TorrentFilter::setTypeByName(const QString &filter)
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;
}
@@ -189,9 +189,9 @@ bool TorrentFilter::matchState(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

View File

@@ -38,7 +38,7 @@ namespace BitTorrent
class Torrent;
}
using InfoHashSet = QSet<BitTorrent::InfoHash>;
using TorrentIDSet = QSet<BitTorrent::TorrentID>;
class TorrentFilter
{
@@ -61,7 +61,7 @@ public:
// These mean any permutation, including no category / tag.
static const QString AnyCategory;
static const InfoHashSet AnyHash;
static const TorrentIDSet AnyID;
static const QString AnyTag;
static const TorrentFilter DownloadingTorrent;
@@ -79,12 +79,12 @@ public:
TorrentFilter() = default;
// category & tags: pass empty string for uncategorized / untagged 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(const QString &filter, const InfoHashSet &hashSet = AnyHash, const QString &category = AnyCategory, const QString &tags = AnyTag);
TorrentFilter(Type type, const TorrentIDSet &idSet = AnyID, const QString &category = AnyCategory, const QString &tag = AnyTag);
TorrentFilter(const QString &filter, const TorrentIDSet &idSet = AnyID, const QString &category = AnyCategory, const QString &tags = AnyTag);
bool setType(Type type);
bool setTypeByName(const QString &filter);
bool setHashSet(const InfoHashSet &hashSet);
bool setTorrentIDSet(const TorrentIDSet &idSet);
bool setCategory(const QString &category);
bool setTag(const QString &tag);
@@ -99,5 +99,5 @@ private:
Type m_type {All};
QString m_category;
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)
{
const QString name = filename.endsWith(QB_EXT)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
? filename.chopped(QB_EXT.length())
#else
? filename.left(filename.length() - QB_EXT.length())
#endif
: filename;
return QMimeDatabase().suffixForFileName(name);
}

View File

@@ -28,6 +28,8 @@
#include "misc.h"
#include <optional>
#ifdef Q_OS_WIN
#include <windows.h>
#include <powrprof.h>
@@ -49,6 +51,7 @@
#include <zlib.h>
#include <QCoreApplication>
#include <QMimeDatabase>
#include <QRegularExpression>
#include <QSet>
#include <QSysInfo>
@@ -60,6 +63,7 @@
#include "base/types.h"
#include "base/unicodestrings.h"
#include "base/utils/fs.h"
#include "base/utils/string.h"
namespace
@@ -80,21 +84,26 @@ namespace
// see http://en.wikipedia.org/wiki/Kilobyte
// value must be given in bytes
// 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;
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;
}
unit = static_cast<Utils::Misc::SizeUnit>(i);
return true;
return {{value, static_cast<Utils::Misc::SizeUnit>(i)}};
}
}
@@ -249,15 +258,14 @@ QString Utils::Misc::unitString(const SizeUnit unit, const bool isSpeed)
return ret;
}
QString Utils::Misc::friendlyUnit(const qint64 bytesValue, const bool isSpeed)
QString Utils::Misc::friendlyUnit(const qint64 bytes, const bool isSpeed)
{
SizeUnit unit;
qreal friendlyVal;
if (!splitToFriendlyUnit(bytesValue, friendlyVal, unit))
const std::optional<SplitToFriendlyUnitResult> result = splitToFriendlyUnit(bytes);
if (!result)
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)
+ unitString(unit, isSpeed);
+ unitString(result->unit, isSpeed);
}
int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit)
@@ -284,9 +292,17 @@ qlonglong Utils::Misc::sizeInBytes(qreal size, const Utils::Misc::SizeUnit unit)
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",
"AAC",
@@ -331,7 +347,7 @@ bool Utils::Misc::isPreviewable(const QString &extension)
"WMA",
"WMV"
};
return multimediaExtensions.contains(extension.toUpper());
return multimediaExtensions.contains(Utils::Fs::fileExtension(filename).toUpper());
}
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)
// value must be given in bytes
QString friendlyUnit(qint64 bytesValue, bool isSpeed = false);
QString friendlyUnit(qint64 bytes, bool isSpeed = false);
int friendlyUnitPrecision(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
// time duration like "1d 2h 10m".

View File

@@ -33,10 +33,15 @@
#include <QCollator>
#include <QLocale>
#include <QRegExp>
#include <QtGlobal>
#include <QVector>
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <QRegularExpression>
#else
#include <QRegExp>
#endif
#if defined(Q_OS_MACOS) || defined(__MINGW32__)
#define QBT_USES_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);
}
#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
// copy the code from QRegExp::wc2rx().
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);
}
#endif
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;
}
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)
{
if (strings.empty())

View File

@@ -50,7 +50,7 @@ namespace Utils::String
return (naturalCompare(left, right, caseSensitivity) < 0);
}
QString wildcardToRegex(const QString &pattern);
QString wildcardToRegexPattern(const QString &pattern);
template <typename T>
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<int> parseInt(const QString &string);
std::optional<double> parseDouble(const QString &string);
QString join(const QVector<QStringRef> &strings, const QString &separator);

View File

@@ -30,8 +30,8 @@
#define QBT_VERSION_MAJOR 4
#define QBT_VERSION_MINOR 3
#define QBT_VERSION_BUGFIX 3
#define QBT_VERSION_BUILD 0
#define QBT_VERSION_BUGFIX 4
#define QBT_VERSION_BUILD 1
#define QBT_VERSION_STATUS "" // Should be empty for stable releases!
#define QBT__STRINGIFY(x) #x

View File

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

View File

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

View File

@@ -270,12 +270,12 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
bool AddNewTorrentDialog::loadTorrentImpl()
{
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
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->isPrivate() || m_torrentInfo.isPrivate())
@@ -296,7 +296,7 @@ bool AddNewTorrentDialog::loadTorrentImpl()
return false;
}
m_ui->labelHashData->setText(infoHash);
m_ui->labelHashData->setText(torrentID.toString());
setupTreeview();
TMMChanged(m_ui->comboTTM->currentIndex());
return true;
@@ -312,11 +312,11 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
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
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->isPrivate())
@@ -348,7 +348,7 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
BitTorrent::Session::instance()->downloadMetadata(magnetUri);
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
m_ui->labelHashData->setText(infoHash);
m_ui->labelHashData->setText(torrentID.toString());
m_magnetURI = magnetUri;
return true;
@@ -502,12 +502,12 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
{
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)
{
m_contentModel->setData(
m_contentModel->index(index.row(), PRIORITY, index.parent())
m_contentModel->setData(index.sibling(index.row(), PRIORITY)
, static_cast<int>(prio));
}
};
@@ -517,37 +517,63 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
if (selectedRows.size() == 1)
{
QAction *actRename = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."));
connect(actRename, &QAction::triggered, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
menu->addSeparator();
}
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);
});
subMenu->addAction(m_ui->actionNotDownloaded);
connect(m_ui->actionNormal, &QAction::triggered, subMenu, [applyPriorities]()
subMenu->addAction(tr("Normal"), subMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::Normal);
});
subMenu->addAction(m_ui->actionNormal);
connect(m_ui->actionHigh, &QAction::triggered, subMenu, [applyPriorities]()
subMenu->addAction(tr("High"), subMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::High);
});
subMenu->addAction(m_ui->actionHigh);
connect(m_ui->actionMaximum, &QAction::triggered, subMenu, [applyPriorities]()
subMenu->addAction(tr("Maximum"), subMenu, [applyPriorities]()
{
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());
}
@@ -603,7 +629,7 @@ void AddNewTorrentDialog::reject()
if (!m_hasMetadata)
{
setMetadataProgressIndicator(false);
BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.hash());
BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.infoHash().toTorrentID());
}
QDialog::reject();
@@ -611,7 +637,7 @@ void AddNewTorrentDialog::reject()
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);

View File

@@ -449,26 +449,6 @@
</layout>
</item>
</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>
<customwidgets>
<customwidget>

View File

@@ -82,6 +82,9 @@ namespace
DOWNLOAD_TRACKER_FAVICON,
SAVE_PATH_HISTORY_LENGTH,
ENABLE_SPEED_WIDGET,
#ifndef Q_OS_MACOS
ENABLE_ICONS_IN_MENUS,
#endif
// embedded tracker
TRACKER_STATUS,
TRACKER_PORT,
@@ -112,14 +115,11 @@ namespace
OUTGOING_PORT_MIN,
OUTGOING_PORT_MAX,
UPNP_LEASE_DURATION,
PEER_TOS,
UTP_MIX_MODE,
#ifdef HAS_IDN_SUPPORT
IDN_SUPPORT,
#endif
MULTI_CONNECTIONS_PER_IP,
#ifdef HAS_HTTPS_TRACKER_VALIDATION
VALIDATE_HTTPS_TRACKER_CERTIFICATE,
#endif
BLOCK_PEERS_ON_PRIVILEGED_PORTS,
// seeding
CHOKING_ALGORITHM,
@@ -224,18 +224,16 @@ void AdvancedSettings::saveAdvancedSettings()
session->setOutgoingPortsMax(m_spinBoxOutgoingPortsMax.value());
// UPnP lease duration
session->setUPnPLeaseDuration(m_spinBoxUPnPLeaseDuration.value());
// Type of service
session->setPeerToS(m_spinBoxPeerToS.value());
// uTP-TCP mixed mode
session->setUtpMixedMode(static_cast<BitTorrent::MixedModeAlgorithm>(m_comboBoxUtpMixedMode.currentIndex()));
#ifdef HAS_IDN_SUPPORT
// Support internationalized domain name (IDN)
session->setIDNSupportEnabled(m_checkBoxIDNSupport.isChecked());
#endif
// multiple connections per IP
session->setMultiConnectionsPerIpEnabled(m_checkBoxMultiConnectionsPerIp.isChecked());
#ifdef HAS_HTTPS_TRACKER_VALIDATION
// Validate HTTPS tracker certificate
session->setValidateHTTPSTrackerCertificate(m_checkBoxValidateHTTPSTrackerCertificate.isChecked());
#endif
// Disallow connection to peers on privileged ports
session->setBlockPeersOnPrivilegedPorts(m_checkBoxBlockPeersOnPrivilegedPorts.isChecked());
// Recheck torrents on completion
@@ -279,6 +277,9 @@ void AdvancedSettings::saveAdvancedSettings()
mainWindow->setDownloadTrackerFavicon(m_checkBoxTrackerFavicon.isChecked());
AddNewTorrentDialog::setSavePathHistoryLength(m_spinBoxSavePathHistoryLength.value());
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
#ifndef Q_OS_MACOS
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
#endif
// Tracker
pref->setTrackerPort(m_spinBoxTrackerPort.value());
@@ -544,31 +545,33 @@ void AdvancedSettings::loadAdvancedSettings()
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", "(?)"))
, &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
m_comboBoxUtpMixedMode.addItems({tr("Prefer TCP"), tr("Peer proportional (throttles TCP)")});
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)
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#mixed_mode_algorithm", "(?)"))
, &m_comboBoxUtpMixedMode);
#ifdef HAS_IDN_SUPPORT
// Support internationalized domain name (IDN)
m_checkBoxIDNSupport.setChecked(session->isIDNSupportEnabled());
addRow(IDN_SUPPORT, (tr("Support internationalized domain name (IDN)")
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_idna", "(?)"))
, &m_checkBoxIDNSupport);
#endif
// multiple connections per IP
m_checkBoxMultiConnectionsPerIp.setChecked(session->multiConnectionsPerIpEnabled());
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", "(?)"))
, &m_checkBoxMultiConnectionsPerIp);
#ifdef HAS_HTTPS_TRACKER_VALIDATION
// Validate HTTPS tracker certificate
m_checkBoxValidateHTTPSTrackerCertificate.setChecked(session->validateHTTPSTrackerCertificate());
addRow(VALIDATE_HTTPS_TRACKER_CERTIFICATE, (tr("Validate HTTPS tracker certificates")
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#validate_https_trackers", "(?)"))
, &m_checkBoxValidateHTTPSTrackerCertificate);
#endif
// Disallow connection to peers on privileged ports
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);
@@ -647,6 +650,11 @@ void AdvancedSettings::loadAdvancedSettings()
// Enable speed graphs
m_checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled());
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
m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
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);
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_spinBoxSendBufferWatermarkFactor, m_spinBoxSocketBacklogSize, m_spinBoxMaxConcurrentHTTPAnnounces, m_spinBoxStopTrackerTimeout,
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval;
@@ -84,4 +84,8 @@ private:
#if defined(Q_OS_WIN)
QComboBox m_comboBoxOSMemoryPriority;
#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
// 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;
#else
int wd = m_ui->textEdit->fontMetrics().width(m_ui->textEdit->text()) + 4;
#endif
if (!windowTitle().isEmpty())
{
// not really the font metrics in window title, so we enlarge it a bit,
// including the small icon and close button width
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
int w = fontMetrics().horizontalAdvance(windowTitle()) * 1.8;
#else
int w = fontMetrics().width(windowTitle()) * 1.8;
#endif
wd = std::max(wd, w);
}
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());
#else
int w = m_ui->textLabel->fontMetrics().width(m_ui->textLabel->text());
#endif
wd = std::max(wd, w);
}

View File

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

View File

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

View File

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

View File

@@ -30,6 +30,7 @@
#include <chrono>
#include <QActionGroup>
#include <QClipboard>
#include <QCloseEvent>
#include <QDebug>
@@ -90,7 +91,6 @@
#include "statsdialog.h"
#include "statusbar.h"
#include "torrentcreatordialog.h"
#include "transferlistfilterswidget.h"
#include "transferlistmodel.h"
#include "transferlistwidget.h"
@@ -105,6 +105,8 @@
#include "programupdater.h"
#endif
using namespace std::chrono_literals;
namespace
{
#define SETTINGS_KEY(name) "GUI/" name
@@ -148,9 +150,6 @@ MainWindow::MainWindow(QWidget *parent)
, m_posInitialized(false)
, m_forceExit(false)
, m_unlockDlgShowing(false)
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
, m_wasUpdateCheckEnabled(false)
#endif
, m_hasPython(false)
{
m_ui->setupUi(this);
@@ -195,10 +194,8 @@ MainWindow::MainWindow(QWidget *parent)
m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon("preferences-web-browser-cookies"));
auto *lockMenu = new QMenu(this);
QAction *defineUiLockPasswdAct = lockMenu->addAction(tr("&Set Password"));
connect(defineUiLockPasswdAct, &QAction::triggered, this, &MainWindow::defineUILockPassword);
QAction *clearUiLockPasswdAct = lockMenu->addAction(tr("&Clear Password"));
connect(clearUiLockPasswdAct, &QAction::triggered, this, &MainWindow::clearUILockPassword);
lockMenu->addAction(tr("&Set Password"), this, &MainWindow::defineUILockPassword);
lockMenu->addAction(tr("&Clear Password"), this, &MainWindow::clearUILockPassword);
m_ui->actionLock->setMenu(lockMenu);
// Creating Bittorrent session
@@ -319,11 +316,11 @@ MainWindow::MainWindow(QWidget *parent)
connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
m_programUpdateTimer = new QTimer(this);
m_programUpdateTimer->setInterval(60 * 60 * 1000);
m_programUpdateTimer->setSingleShot(true);
connect(m_programUpdateTimer, &QTimer::timeout, this, &MainWindow::checkProgramUpdate);
connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, &MainWindow::checkProgramUpdate);
connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); });
// trigger an early check on startup
if (pref->isUpdateCheckEnabled())
checkProgramUpdate(false);
#else
m_ui->actionCheckForUpdates->setVisible(false);
#endif
@@ -550,16 +547,11 @@ void MainWindow::addToolbarContextMenu()
m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"));
connect(iconsOnly, &QAction::triggered, this, &MainWindow::toolbarIconsOnly);
QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"));
connect(textOnly, &QAction::triggered, this, &MainWindow::toolbarTextOnly);
QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"));
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);
QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"), this, &MainWindow::toolbarIconsOnly);
QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"), this, &MainWindow::toolbarTextOnly);
QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"), this, &MainWindow::toolbarTextBeside);
QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"), this, &MainWindow::toolbarTextUnder);
QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"), this, &MainWindow::toolbarFollowSystem);
auto *textPositionGroup = new QActionGroup(m_toolbarMenu);
textPositionGroup->addAction(iconsOnly);
@@ -811,7 +803,8 @@ void MainWindow::cleanup()
m_preventTimer->stop();
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
m_programUpdateTimer->stop();
if (m_programUpdateTimer)
m_programUpdateTimer->stop();
#endif
// remove all child widgets
@@ -884,36 +877,36 @@ void MainWindow::createKeyboardShortcuts()
m_ui->actionOpen->setShortcut(QKeySequence::Open);
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->actionDownloadFromURL->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_O);
m_ui->actionExit->setShortcut(Qt::CTRL + Qt::Key_Q);
m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
m_ui->actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
#ifdef Q_OS_MACOS
m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
#else
m_ui->actionCloseWindow->setVisible(false);
#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);
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));
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));
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);
const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget);
connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
m_ui->actionOptions->setShortcut(Qt::ALT + Qt::Key_O);
m_ui->actionStatistics->setShortcut(Qt::CTRL + Qt::Key_I);
m_ui->actionStart->setShortcut(Qt::CTRL + Qt::Key_S);
m_ui->actionStartAll->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_S);
m_ui->actionPause->setShortcut(Qt::CTRL + Qt::Key_P);
m_ui->actionPauseAll->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_P);
m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus);
m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL + Qt::Key_Minus);
m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL + Qt::Key_Plus);
m_ui->actionTopQueuePos->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Plus);
m_ui->actionOptions->setShortcut(Qt::ALT | Qt::Key_O);
m_ui->actionStatistics->setShortcut(Qt::CTRL | Qt::Key_I);
m_ui->actionStart->setShortcut(Qt::CTRL | Qt::Key_S);
m_ui->actionStartAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
m_ui->actionPause->setShortcut(Qt::CTRL | Qt::Key_P);
m_ui->actionPauseAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus);
m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Minus);
m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Plus);
m_ui->actionTopQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus);
#ifdef Q_OS_MACOS
m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M);
addAction(m_ui->actionMinimize);
@@ -966,7 +959,7 @@ void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *co
Preferences *const pref = Preferences::instance();
if (pref->recursiveDownloadDisabled()) return;
const auto torrentHash = torrent->hash();
const auto torrentID = torrent->id();
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())
@@ -977,10 +970,10 @@ void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *co
const QPushButton *yes = confirmBox->addButton(tr("Yes"), QMessageBox::YesRole);
/*QPushButton *no = */ confirmBox->addButton(tr("No"), 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)
BitTorrent::Session::instance()->recursiveTorrentDownload(torrentHash);
BitTorrent::Session::instance()->recursiveTorrentDownload(torrentID);
if (button == never)
Preferences::instance()->disableRecursiveDownload();
});
@@ -1590,15 +1583,21 @@ void MainWindow::loadPreferences(const bool configureSession)
m_propertiesWidget->reloadPreferences();
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
if (pref->isUpdateCheckEnabled() && !m_wasUpdateCheckEnabled)
if (pref->isUpdateCheckEnabled())
{
m_wasUpdateCheckEnabled = true;
checkProgramUpdate();
if (!m_programUpdateTimer)
{
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;
m_programUpdateTimer->stop();
delete m_programUpdateTimer;
m_programUpdateTimer = nullptr;
}
#endif
@@ -1804,21 +1803,21 @@ void MainWindow::on_actionOptions_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);
Preferences::instance()->setToolbarDisplayed(isVisible);
}
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);
showStatusBar(isVisible);
}
void MainWindow::on_actionSpeedInTitleBar_triggered()
{
m_displaySpeedInTitle = static_cast<QAction * >(sender())->isChecked();
m_displaySpeedInTitle = static_cast<QAction *>(sender())->isChecked();
Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
if (m_displaySpeedInTitle)
reloadSessionStats();
@@ -1904,35 +1903,56 @@ void MainWindow::on_actionDownloadFromURL_triggered()
}
#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->setText(tr("&Check for 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))
m_programUpdateTimer->start();
const auto cleanup = [this, updater]()
{
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
@@ -2091,18 +2111,23 @@ QIcon MainWindow::getSystrayIcon() const
#endif // 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->setText(tr("Checking for Updates..."));
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);
connect(updater, &ProgramUpdater::updateCheckFinished, this, &MainWindow::handleUpdateCheckFinished);
auto *updater = new ProgramUpdater(this);
connect(updater, &ProgramUpdater::updateCheckFinished
, this, [this, invokedByUser, updater]()
{
handleUpdateCheckFinished(updater, invokedByUser);
});
updater->checkForUpdates();
}
#endif
#ifdef Q_OS_WIN

View File

@@ -47,6 +47,7 @@ class ExecutionLogWidget;
class LineEdit;
class OptionsDialog;
class PowerManagement;
class ProgramUpdater;
class PropertiesWidget;
class RSSWidget;
class SearchWidget;
@@ -134,9 +135,6 @@ private slots:
void finishedTorrent(BitTorrent::Torrent *const torrent) const;
void askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *const torrent);
void optionsSaved();
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
void handleUpdateCheckFinished(bool updateAvailable, QString newVersion, bool invokedByUser);
#endif
void toggleAlternativeSpeeds();
#ifdef Q_OS_WIN
@@ -177,9 +175,7 @@ private slots:
void on_actionLock_triggered();
// Check for unpaused downloading or seeding torrents and prevent system suspend/sleep according to preferences
void updatePowerManagementState();
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
void checkProgramUpdate();
#endif
void toolbarMenuRequested(const QPoint &point);
void toolbarIconsOnly();
void toolbarTextOnly();
@@ -252,10 +248,13 @@ private:
// Power Management
PowerManagement *m_pwr;
QTimer *m_preventTimer;
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
QTimer *m_programUpdateTimer;
bool m_wasUpdateCheckEnabled;
#endif
bool m_hasPython;
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->labelGlobalRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow_off")), this, 16));
m_ui->labelAltRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow")), 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, 24));
m_ui->deleteTorrentWarningIcon->setPixmap(QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical).pixmap(16, 16));
m_ui->deleteTorrentWarningIcon->hide();
@@ -233,15 +233,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
m_ui->hsplitter->setCollapsible(0, false);
m_ui->hsplitter->setCollapsible(1, false);
// Get apply button in button box
const QList<QAbstractButton *> buttons = m_ui->buttonBox->buttons();
for (QAbstractButton *button : buttons)
{
if (m_ui->buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole)
{
m_applyButton = button;
break;
}
}
m_applyButton = m_ui->buttonBox->button(QDialogButtonBox::Apply);
connect(m_applyButton, &QPushButton::clicked, this, &OptionsDialog::applySettings);
m_ui->scanFoldersView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
m_ui->scanFoldersView->setModel(ScanFoldersModel::instance());
@@ -249,7 +242,6 @@ OptionsDialog::OptionsDialog(QWidget *parent)
connect(ScanFoldersModel::instance(), &QAbstractListModel::dataChanged, this, &ThisType::enableApplyButton);
connect(m_ui->scanFoldersView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ThisType::handleScanFolderViewSelectionChanged);
connect(m_ui->buttonBox, &QDialogButtonBox::clicked, this, &OptionsDialog::applySettings);
// Languages supported
initializeLanguageCombo();
@@ -761,7 +753,7 @@ void OptionsDialog::saveOptions()
pref->setMailNotificationSMTPPassword(m_ui->mailNotifPassword->text());
pref->setAutoRunEnabled(m_ui->autoRunBox->isChecked());
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());
#endif
pref->setActionOnDblClOnTorrentDl(getActionOnDblClOnTorrentDl());
@@ -1051,7 +1043,7 @@ void OptionsDialog::loadOptions()
m_ui->autoRunBox->setChecked(pref->isAutoRunEnabled());
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());
#else
m_ui->autoRunConsole->hide();
@@ -1439,27 +1431,24 @@ void OptionsDialog::on_buttonBox_accepted()
accept();
}
void OptionsDialog::applySettings(QAbstractButton *button)
void OptionsDialog::applySettings()
{
if (button == m_applyButton)
if (!schedTimesOk())
{
if (!schedTimesOk())
{
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();
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();
}
void OptionsDialog::closeEvent(QCloseEvent *e)

View File

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

View File

@@ -28,18 +28,11 @@
#include "previewlistdelegate.h"
#include <QApplication>
#include <QModelIndex>
#include <QPainter>
#include <QStyleOptionProgressBar>
#include <QStyleOptionViewItem>
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
#include <QProxyStyle>
#endif
#include "base/utils/misc.h"
#include "base/utils/string.h"
#include "previewselectdialog.h"
PreviewListDelegate::PreviewListDelegate(QObject *parent)
@@ -61,30 +54,19 @@ void PreviewListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
break;
case PreviewSelectDialog::PROGRESS:
{
{
const qreal progress = (index.data().toReal() * 100);
const QString text = (progress >= 100)
? QString::fromLatin1("100%")
: (Utils::String::fromDouble(progress, 1) + '%');
QStyleOptionProgressBar newopt;
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
m_progressBarPainter.paint(painter, option, text, static_cast<int>(progress));
}
break;
default:
QItemDelegate::paint(painter, option, index);
break;
}
painter->restore();

View File

@@ -30,6 +30,8 @@
#include <QItemDelegate>
#include "progressbarpainter.h"
class PreviewListDelegate final : public QItemDelegate
{
Q_OBJECT
@@ -38,7 +40,9 @@ class PreviewListDelegate final : public QItemDelegate
public:
explicit PreviewListDelegate(QObject *parent = nullptr);
private:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) 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);
if (fileName.endsWith(QB_EXT))
fileName.chop(4);
QString extension = Utils::Fs::fileExtension(fileName).toUpper();
if (Utils::Misc::isPreviewable(extension))
fileName.chop(QB_EXT.length());
if (Utils::Misc::isPreviewable(fileName))
{
int row = m_previewListModel->rowCount();
m_previewListModel->insertRow(row);

View File

@@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Mike Tzou (Chocobo1)
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -36,7 +37,6 @@
#include <QDebug>
#include <QDesktopServices>
#include <QRegularExpression>
#include <QStringList>
#include <QXmlStreamReader>
#if defined(Q_OS_WIN)
@@ -44,56 +44,82 @@
#endif
#include "base/net/downloadmanager.h"
#include "base/utils/version.h"
#include "base/version.h"
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)
: QObject(parent)
, m_invokedByUser(invokedByUser)
{
}
void ProgramUpdater::checkForUpdates()
void ProgramUpdater::checkForUpdates() const
{
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,
// the filehost can identify it and contact us.
Net::DownloadManager::instance()->download(
Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)")
, this, &ProgramUpdater::rssDownloadFinished);
Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)")
, this, &ProgramUpdater::rssDownloadFinished);
}
QString ProgramUpdater::getNewVersion() const
{
return m_newVersion;
}
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
{
if (result.status != Net::DownloadStatus::Success)
{
qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString;
emit updateCheckFinished(false, QString(), m_invokedByUser);
emit updateCheckFinished();
return;
}
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
const QString OS_TYPE {"Mac OS X"};
#elif defined(Q_OS_WIN)
const QString OS_TYPE
{(::IsWindows7OrGreater()
&& QSysInfo::currentCpuArchitecture().endsWith("64"))
const QString OS_TYPE {(::IsWindows7OrGreater()
&& QSysInfo::currentCpuArchitecture().endsWith("64"))
? "Windows x64" : "Windows"};
#endif
QString version;
QXmlStreamReader xml(result.data);
bool inItem = false;
QString version;
QString updateLink;
QString type;
QXmlStreamReader xml(result.data);
while (!xml.atEnd())
{
@@ -121,7 +147,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
{
qDebug("Detected version is %s", qUtf8Printable(version));
if (isVersionMoreRecent(version))
m_updateUrl = updateLink;
{
m_newVersion = version;
m_updateURL = updateLink;
}
}
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());
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 {};
}
return QDesktopServices::openUrl(m_updateURL);
}

View File

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

View File

@@ -26,48 +26,47 @@
* exception statement from your version.
*/
#include "progressbardelegate.h"
#include "progressbarpainter.h"
#include <QApplication>
#include <QModelIndex>
#include <QPainter>
#include <QPalette>
#include <QStyleOptionProgressBar>
#include <QStyleOptionViewItem>
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
#include <QProxyStyle>
#endif
ProgressBarDelegate::ProgressBarDelegate(const int progressColumn, const int dataRole, QObject *parent)
: QStyledItemDelegate {parent}
, m_progressColumn {progressColumn}
, m_dataRole {dataRole}
ProgressBarPainter::ProgressBarPainter()
{
#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
}
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();
option.progress = static_cast<int>(index.data(m_dataRole).toReal());
option.maximum = 100;
option.minimum = 0;
option.state |= (QStyle::State_Enabled | QStyle::State_Horizontal);
option.textVisible = true;
}
QStyleOptionProgressBar styleOption;
styleOption.initFrom(&m_dummyProgressBar);
// QStyleOptionProgressBar fields
styleOption.maximum = 100;
styleOption.minimum = 0;
styleOption.progress = progress;
styleOption.text = text;
styleOption.textVisible = true;
// QStyleOption fields
styleOption.rect = option.rect;
// Qt 6 requires QStyle::State_Horizontal to be set for correctly drawing horizontal progress bar
styleOption.state = option.state | QStyle::State_Horizontal;
void ProgressBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if (index.column() != m_progressColumn)
return QStyledItemDelegate::paint(painter, option, index);
QStyleOptionProgressBar newopt;
newopt.initFrom(&m_dummyProgressBar);
newopt.rect = option.rect;
initProgressStyleOption(newopt, index);
const bool isEnabled = option.state.testFlag(QStyle::State_Enabled);
styleOption.palette.setCurrentColorGroup(isEnabled ? QPalette::Active : QPalette::Disabled);
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();
}

View File

@@ -29,23 +29,17 @@
#pragma once
#include <QProgressBar>
#include <QStyledItemDelegate>
class QStyleOptionProgressBar;
class QStyleOptionViewItem;
class ProgressBarDelegate : public QStyledItemDelegate
class ProgressBarPainter
{
public:
ProgressBarDelegate(int progressColumn, int dataRole, QObject *parent = nullptr);
ProgressBarPainter();
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
virtual void initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QString &text, int progress) const;
private:
const int m_progressColumn;
const int m_dataRole;
// for painting progressbar with stylesheet option, a dummy progress bar is required
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
if (!torrent->isQueued() && !torrent->isChecking() && !torrent->isPrivate())
{
const QAction *addPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("user-group-new"), tr("Add a new peer..."));
connect(addPeerAct, &QAction::triggered, this, [this, torrent]()
menu->addAction(UIThemeManager::instance()->getIcon("user-group-new"), tr("Add a new peer...")
, this, [this, torrent]()
{
const QVector<BitTorrent::PeerAddress> peersList = PeersAdditionDialog::askForPeers(this);
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())
{
const QAction *copyPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy IP:port"));
connect(copyPeerAct, &QAction::triggered, this, &PeerListWidget::copySelectedPeers);
menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy IP:port")
, this, &PeerListWidget::copySelectedPeers);
menu->addSeparator();
const QAction *banAct = menu->addAction(UIThemeManager::instance()->getIcon("user-group-delete"), tr("Ban peer permanently"));
connect(banAct, &QAction::triggered, this, &PeerListWidget::banSelectedPeers);
menu->addAction(UIThemeManager::instance()->getIcon("user-group-delete"), tr("Ban peer permanently")
, this, &PeerListWidget::banSelectedPeers);
}
if (menu->isEmpty())

View File

@@ -28,7 +28,6 @@
#include "propertieswidget.h"
#include <QAction>
#include <QClipboard>
#include <QDateTime>
#include <QDebug>
@@ -313,8 +312,9 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent)
// Save path
updateSavePath(m_torrent);
// Hash
m_ui->labelHashVal->setText(m_torrent->hash());
// Info hash (Truncated info hash (torrent ID) with libtorrent2)
// 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();
if (m_torrent->hasMetadata())
{
@@ -586,57 +586,82 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &)
{
const QModelIndex index = selectedRows[0];
const QAction *actOpen = menu->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open"));
connect(actOpen, &QAction::triggered, this, [this, index]() { openItem(index); });
const QAction *actOpenContainingFolder = menu->addAction(UIThemeManager::instance()->getIcon("inode-directory"), tr("Open Containing Folder"));
connect(actOpenContainingFolder, &QAction::triggered, this, [this, index]() { openParentFolder(index); });
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->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open")
, this, [this, index]() { openItem(index); });
menu->addAction(UIThemeManager::instance()->getIcon("inode-directory"), tr("Open Containing Folder")
, this, [this, index]() { openParentFolder(index); });
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); });
menu->addSeparator();
}
if (!m_torrent->isSeed())
{
QMenu *subMenu = menu->addMenu(tr("Priority"));
const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio)
const auto applyPriorities = [this](const BitTorrent::DownloadPriority prio)
{
const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
for (const QModelIndex &index : selectedRows)
{
m_propListModel->setData(
m_propListModel->index(index.row(), PRIORITY, index.parent()), static_cast<int>(prio));
m_propListModel->setData(index.sibling(index.row(), PRIORITY)
, static_cast<int>(prio));
}
// Save changes
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);
});
subMenu->addAction(m_ui->actionNotDownloaded);
connect(m_ui->actionNormal, &QAction::triggered, subMenu, [applyPriorities]()
subMenu->addAction(tr("Normal"), subMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::Normal);
});
subMenu->addAction(m_ui->actionNormal);
connect(m_ui->actionHigh, &QAction::triggered, subMenu, [applyPriorities]()
subMenu->addAction(tr("High"), subMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::High);
});
subMenu->addAction(m_ui->actionHigh);
connect(m_ui->actionMaximum, &QAction::triggered, subMenu, [applyPriorities]()
subMenu->addAction(tr("Maximum"), subMenu, [applyPriorities]()
{
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()
@@ -660,21 +685,17 @@ void PropertiesWidget::displayWebSeedListMenu(const QPoint &)
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
const QAction *actAdd = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New Web seed"));
connect(actAdd, &QAction::triggered, this, &PropertiesWidget::askWebSeed);
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New Web seed"), this, &PropertiesWidget::askWebSeed);
if (!rows.isEmpty())
{
const QAction *actDel = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove Web seed"));
connect(actDel, &QAction::triggered, this, &PropertiesWidget::deleteSelectedUrlSeeds);
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove Web seed")
, this, &PropertiesWidget::deleteSelectedUrlSeeds);
menu->addSeparator();
const QAction *actCpy = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy Web seed URL"));
connect(actCpy, &QAction::triggered, this, &PropertiesWidget::copySelectedWebSeedsToClipboard);
const QAction *actEdit = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Edit Web seed URL"));
connect(actEdit, &QAction::triggered, this, &PropertiesWidget::editWebSeed);
menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy Web seed URL")
, this, &PropertiesWidget::copySelectedWebSeedsToClipboard);
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Edit Web seed URL")
, this, &PropertiesWidget::editWebSeed);
}
menu->popup(QCursor::pos());
@@ -780,7 +801,7 @@ void PropertiesWidget::editWebSeed()
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."),
QMessageBox::Ok);
return;
@@ -804,7 +825,8 @@ void PropertiesWidget::filteredFilesChanged()
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())
{
m_ui->filesList->collapseAll();

View File

@@ -1086,29 +1086,6 @@
</widget>
</item>
</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>
<customwidgets>
<customwidget>

View File

@@ -28,61 +28,22 @@
#include "proplistdelegate.h"
#include <QApplication>
#include <QComboBox>
#include <QModelIndex>
#include <QPainter>
#include <QPalette>
#include <QProgressBar>
#include <QStyleOptionProgressBar>
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
#include <QProxyStyle>
#endif
#include "base/bittorrent/downloadpriority.h"
#include "base/bittorrent/torrent.h"
#include "gui/torrentcontentmodel.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)
: ProgressBarDelegate {PROGRESS, TorrentContentModel::UnderlyingDataRole, properties}
, m_properties(properties)
: QStyledItemDelegate {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
{
auto *combobox = static_cast<QComboBox *>(editor);
@@ -156,3 +117,25 @@ void PropListDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionV
{
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
#include "gui/progressbardelegate.h"
#include <QStyledItemDelegate>
#include "gui/progressbarpainter.h"
class QAbstractItemModel;
class QModelIndex;
class QPainter;
class QStyleOptionViewItem;
class PropertiesWidget;
@@ -48,25 +49,26 @@ enum PropColumn
AVAILABILITY
};
class PropListDelegate final : public ProgressBarDelegate
class PropListDelegate final : public QStyledItemDelegate
{
Q_OBJECT
Q_DISABLE_COPY(PropListDelegate)
public:
explicit PropListDelegate(PropertiesWidget *properties);
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:
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:
void filteredFilesChanged() const;
private:
void initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const override;
PropertiesWidget *m_properties;
ProgressBarPainter m_progressBarPainter;
};

View File

@@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Prince Gupta <guptaprince8832@gmail.com>
* Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru>
*
* This program is free software; you can redistribute it and/or
@@ -34,33 +35,13 @@
#include <QPainter>
#include <QPen>
#include "base/bittorrent/session.h"
#include "base/global.h"
#include "base/unicodestrings.h"
#include "base/utils/misc.h"
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
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)
: m_divider(divider)
, m_sink(sink)
, m_counter(0)
, m_accumulator {}
SpeedPlotView::Averager::Averager(const milliseconds duration, const milliseconds resolution)
: m_resolution {resolution}
, m_maxDuration {duration}
, m_sink {static_cast<DataCircularBuffer::size_type>(duration / resolution)}
{
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,
// 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.
// With quint64 this speed limit is 2^64/144 ~~ 114 PBytes/s.
// This speed is inaccessible to an ordinary user.
m_accumulator.x += pointData.x;
++m_counter;
for (int id = UP; id < NB_GRAPHS; ++id)
m_accumulator.y[id] += pointData.y[id];
m_counter = (m_counter + 1) % m_divider;
if (m_counter != 0)
return; // still accumulating
m_accumulator[id] += sampleData[id];
// system may go to sleep, that can cause very big elapsed interval
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
for (int id = UP; id < NB_GRAPHS; ++id)
m_accumulator.y[id] /= m_divider;
m_accumulator.x /= m_divider;
m_accumulator[id] /= m_counter;
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
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_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)
: 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)
: QGraphicsView {parent}
{
QPen greenPen;
greenPen.setWidthF(1.5);
@@ -205,69 +200,65 @@ void SpeedPlotView::setGraphEnable(GraphID id, bool enable)
viewport()->update();
}
void SpeedPlotView::pushPoint(const SpeedPlotView::PointData &point)
void SpeedPlotView::pushPoint(const SpeedPlotView::SampleData &point)
{
m_data5Min.push_back(point);
m_averager30Min.push(point);
m_averager6Hour.push(point);
m_averager12Hour.push(point);
m_averager24Hour.push(point);
for (Averager *averager : {&m_averager5Min, &m_averager30Min
, &m_averager6Hour, &m_averager12Hour
, &m_averager24Hour})
{
if (averager->push(point))
{
if (m_currentAverager == averager)
viewport()->update();
}
}
}
void SpeedPlotView::setPeriod(const TimePeriod period)
{
m_period = period;
switch (period)
{
case SpeedPlotView::MIN1:
m_viewablePointsCount = MIN1_SEC;
m_currentData = &m_data5Min;
m_currentMaxDuration = 1min;
m_currentAverager = &m_averager5Min;
break;
case SpeedPlotView::MIN5:
m_viewablePointsCount = MIN5_SEC;
m_currentData = &m_data5Min;
m_currentMaxDuration = 5min;
m_currentAverager = &m_averager5Min;
break;
case SpeedPlotView::MIN30:
m_viewablePointsCount = MIN30_BUF_SIZE;
m_currentData = &m_data30Min;
m_currentMaxDuration = 30min;
m_currentAverager = &m_averager30Min;
break;
case SpeedPlotView::HOUR3:
m_currentMaxDuration = 3h;
m_currentAverager = &m_averager6Hour;
break;
case SpeedPlotView::HOUR6:
m_viewablePointsCount = HOUR6_BUF_SIZE;
m_currentData = &m_data6Hour;
m_currentMaxDuration = 6h;
m_currentAverager = &m_averager6Hour;
break;
case SpeedPlotView::HOUR12:
m_viewablePointsCount = HOUR12_BUF_SIZE;
m_currentData = &m_data12Hour;
m_currentMaxDuration = 12h;
m_currentAverager = &m_averager12Hour;
break;
case SpeedPlotView::HOUR24:
m_viewablePointsCount = HOUR24_BUF_SIZE;
m_currentData = &m_data24Hour;
m_currentMaxDuration = 24h;
m_currentAverager = &m_averager24Hour;
break;
}
viewport()->update();
}
void SpeedPlotView::replot()
const SpeedPlotView::DataCircularBuffer &SpeedPlotView::currentData() const
{
if ((m_period == MIN1)
|| (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();
return m_currentAverager->data();
}
boost::circular_buffer<SpeedPlotView::PointData> &SpeedPlotView::getCurrentData()
quint64 SpeedPlotView::maxYValue() const
{
return *m_currentData;
}
quint64 SpeedPlotView::maxYValue()
{
boost::circular_buffer<PointData> &queue = getCurrentData();
const DataCircularBuffer &queue = currentData();
quint64 maxYValue = 0;
for (int id = UP; id < NB_GRAPHS; ++id)
@@ -276,9 +267,14 @@ quint64 SpeedPlotView::maxYValue()
if (!m_properties[static_cast<GraphID>(id)].enable)
continue;
for (int i = static_cast<int>(queue.size()) - 1, j = 0; (i >= 0) && (j < m_viewablePointsCount); --i, ++j)
if (queue[i].y[id] > maxYValue)
maxYValue = queue[i].y[id];
milliseconds duration {0ms};
for (int i = static_cast<int>(queue.size()) - 1; i >= 0; --i)
{
maxYValue = std::max(maxYValue, queue[i].data[id]);
duration += queue[i].duration;
if (duration >= m_currentMaxDuration)
break;
}
}
return maxYValue;
@@ -308,13 +304,10 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
int yAxisWidth = 0;
for (const QString &label : speedLabels)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
{
if (fontMetrics.horizontalAdvance(label) > yAxisWidth)
yAxisWidth = fontMetrics.horizontalAdvance(label);
#else
if (fontMetrics.width(label) > yAxisWidth)
yAxisWidth = fontMetrics.width(label);
#endif
}
int i = 0;
for (const QString &label : speedLabels)
@@ -350,12 +343,16 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
painter.setRenderHints(QPainter::Antialiasing);
// 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 double xTickSize = static_cast<double>(rect.width()) / (m_viewablePointsCount - 1);
const DataCircularBuffer &queue = currentData();
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)
{
@@ -363,18 +360,23 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
continue;
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)
{
int newX = rect.right() - j * xTickSize;
int newY = rect.bottom() - queue[i].y[id] * yMultiplier;
const int newX = rect.right() - (duration.count() * xTickSize);
const int newY = rect.bottom() - (queue[i].data[id] * yMultiplier);
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.drawPolyline(points.data(), points.size());
}
painter.setClipping(false);
// draw legend
QPoint legendTopLeft(rect.left() + 4, fullRect.top() + 4);
@@ -386,13 +388,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
if (!property.enable)
continue;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
if (fontMetrics.horizontalAdvance(property.name) > legendWidth)
legendWidth = fontMetrics.horizontalAdvance(property.name);
#else
if (fontMetrics.width(property.name) > legendWidth)
legendWidth = fontMetrics.width(property.name);
#endif
legendHeight += 1.5 * fontMetrics.height();
}
@@ -407,11 +404,7 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
if (!property.enable)
continue;
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
int nameSize = fontMetrics.horizontalAdvance(property.name);
#else
int nameSize = fontMetrics.width(property.name);
#endif
double indent = 1.5 * (i++) * fontMetrics.height();
painter.setPen(property.pen);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,6 +61,7 @@ public:
private slots:
void on_addRuleBtn_clicked();
void on_removeRuleBtn_clicked();
void on_addCategoryBtn_clicked();
void on_exportBtn_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>
</property>
<property name="text">
<string>Assign Category:</string>
<string>Category:</string>
</property>
</widget>
</item>
@@ -220,6 +220,9 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addCategoryBtn" />
</item>
</layout>
</item>
<item>

View File

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

View File

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

View File

@@ -29,6 +29,7 @@
#include "torrentcategorydialog.h"
#include <QMessageBox>
#include <QPushButton>
#include "base/bittorrent/session.h"
#include "ui_torrentcategorydialog.h"
@@ -40,6 +41,13 @@ TorrentCategoryDialog::TorrentCategoryDialog(QWidget *parent)
m_ui->setupUi(this);
m_ui->comboSavePath->setMode(FileSystemPathEdit::Mode::DirectorySave);
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()

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