mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-31 20:58:07 -06:00
Compare commits
93 Commits
release-4.
...
release-4.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7e6b96493 | ||
|
|
88bf6f11c7 | ||
|
|
90e2236990 | ||
|
|
6ad7cadc4b | ||
|
|
0499111156 | ||
|
|
ae44e59c9a | ||
|
|
1de52f9bcf | ||
|
|
448e55031e | ||
|
|
3b748178c2 | ||
|
|
a4a54ce712 | ||
|
|
d19b524d2d | ||
|
|
1e2bf50e66 | ||
|
|
e7f3409053 | ||
|
|
9758633eeb | ||
|
|
3def5e40c4 | ||
|
|
ca923ed02c | ||
|
|
e4c3bad93a | ||
|
|
3b52c5ce97 | ||
|
|
44b94803a4 | ||
|
|
5d4644c4fc | ||
|
|
a2ef115c66 | ||
|
|
1356f200b8 | ||
|
|
3c68896b1d | ||
|
|
265da50791 | ||
|
|
4037143f4e | ||
|
|
8cae8ad5c5 | ||
|
|
50bd845682 | ||
|
|
ed5aa07526 | ||
|
|
437b51b3a5 | ||
|
|
c2ccc9dfa4 | ||
|
|
b2c7d8211f | ||
|
|
726455ac3e | ||
|
|
ae2bb4efeb | ||
|
|
9971329121 | ||
|
|
d0ec1c4a86 | ||
|
|
9c55600d81 | ||
|
|
b45fb74e01 | ||
|
|
f16c585a77 | ||
|
|
9c664d04ae | ||
|
|
3d0ca83474 | ||
|
|
e713ffb064 | ||
|
|
cf1e61bcf5 | ||
|
|
42b22d6645 | ||
|
|
2d607f8c1a | ||
|
|
69256905c2 | ||
|
|
305316b1fc | ||
|
|
27e222455b | ||
|
|
2b18318e0c | ||
|
|
49cadce253 | ||
|
|
f1b908b95b | ||
|
|
4acfcef8da | ||
|
|
69f2196a22 | ||
|
|
b20a3c5b8e | ||
|
|
2c5271b3b2 | ||
|
|
7696895a88 | ||
|
|
c1ae5d2572 | ||
|
|
0e635c7fdd | ||
|
|
58345e5bbf | ||
|
|
89382d4ec2 | ||
|
|
372f5af36b | ||
|
|
f38736729d | ||
|
|
bf67ef21c6 | ||
|
|
cfd40adcb5 | ||
|
|
8210f9841e | ||
|
|
ae3d17ec01 | ||
|
|
349e958be3 | ||
|
|
42acc75394 | ||
|
|
8b91dcedb0 | ||
|
|
a454a0303d | ||
|
|
789c6de2e8 | ||
|
|
c2fb51159f | ||
|
|
bfb0afe3cf | ||
|
|
26a2d4f24d | ||
|
|
f6e88c8c55 | ||
|
|
51033c212a | ||
|
|
16c858cf61 | ||
|
|
0496543fce | ||
|
|
746e8a7be1 | ||
|
|
6d301ccf55 | ||
|
|
d441b18da0 | ||
|
|
13023ba70a | ||
|
|
ecb7c02d4c | ||
|
|
fd1ac43157 | ||
|
|
c6d4a1f7d4 | ||
|
|
01110690da | ||
|
|
c998c7d38d | ||
|
|
230f98da4a | ||
|
|
c86db0004f | ||
|
|
e645514c8f | ||
|
|
f3c9dbd512 | ||
|
|
ef650293e3 | ||
|
|
05e217537c | ||
|
|
13cb3b5ca1 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
custom: "https://www.qbittorrent.org/donate.php"
|
||||||
26
.github/workflows/ci.yaml
vendored
26
.github/workflows/ci.yaml
vendored
@@ -8,16 +8,13 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [edited, opened, reopened, synchronize]
|
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Qt: 5.15.1
|
VCPKG_COMMIT: e4ce66eecfd3e5cca5eac06c971921bf8e37cf88
|
||||||
# libtorrent: RC_1_2 HEAD, 1.2.11
|
|
||||||
VCPKG_COMMIT: 133051b793486ef14e67e9d1f48c9cfe64dc127e
|
|
||||||
VCPKG_DEST_MACOS: /Users/runner/qbt_tools/vcpkg
|
VCPKG_DEST_MACOS: /Users/runner/qbt_tools/vcpkg
|
||||||
VCPKG_DEST_WIN: C:\qbt_tools\vcpkg
|
VCPKG_DEST_WIN: C:\qbt_tools\vcpkg
|
||||||
LIBTORRENT_VERSION_TAG: v1.2.11
|
LIBTORRENT_VERSION_TAG: v1.2.12
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@@ -26,7 +23,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-20.04, ubuntu-18.04]
|
os: [ubuntu-20.04]
|
||||||
qbt_gui: ["GUI=ON", "GUI=OFF"]
|
qbt_gui: ["GUI=ON", "GUI=OFF"]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
||||||
@@ -125,10 +122,15 @@ jobs:
|
|||||||
"qt5-svg:x64-windows-static-release",
|
"qt5-svg:x64-windows-static-release",
|
||||||
"qt5-tools:x64-windows-static-release",
|
"qt5-tools:x64-windows-static-release",
|
||||||
"qt5-winextras:x64-windows-static-release"
|
"qt5-winextras:x64-windows-static-release"
|
||||||
|
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg.exe upgrade `
|
||||||
|
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
|
||||||
|
--overlay-ports=${{ github.workspace }}/vcpkg `
|
||||||
|
--no-dry-run
|
||||||
foreach($package in $packages)
|
foreach($package in $packages)
|
||||||
{
|
{
|
||||||
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg.exe install $package `
|
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg.exe install $package `
|
||||||
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
|
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
|
||||||
|
--overlay-ports=${{ github.workspace }}/vcpkg `
|
||||||
--clean-after-build
|
--clean-after-build
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,13 +201,6 @@ jobs:
|
|||||||
Add-Content ${{ github.workspace }}/triplets_overlay/x64-osx-release.cmake `
|
Add-Content ${{ github.workspace }}/triplets_overlay/x64-osx-release.cmake `
|
||||||
-Value "set(VCPKG_BUILD_TYPE release)","set(VCPKG_OSX_DEPLOYMENT_TARGET 10.15)"
|
-Value "set(VCPKG_BUILD_TYPE release)","set(VCPKG_OSX_DEPLOYMENT_TARGET 10.15)"
|
||||||
|
|
||||||
# NOTE: Avoids a libtorrent ABI issue. See https://github.com/arvidn/libtorrent/issues/4965
|
|
||||||
- name: force AppleClang to compile libtorrent with C++17
|
|
||||||
run: |
|
|
||||||
(Get-Content -path ${{ env.RUNVCPKG_VCPKG_ROOT }}/ports/libtorrent/portfile.cmake).Replace( `
|
|
||||||
'${FEATURE_OPTIONS}', '${FEATURE_OPTIONS} -DCMAKE_CXX_STANDARD=17') `
|
|
||||||
| Set-Content -Path ${{ env.RUNVCPKG_VCPKG_ROOT }}/ports/libtorrent/portfile.cmake
|
|
||||||
|
|
||||||
- name: install dependencies via vcpkg
|
- name: install dependencies via vcpkg
|
||||||
run: |
|
run: |
|
||||||
$packages = `
|
$packages = `
|
||||||
@@ -215,10 +210,15 @@ jobs:
|
|||||||
"qt5-svg:x64-osx-release",
|
"qt5-svg:x64-osx-release",
|
||||||
"qt5-tools:x64-osx-release",
|
"qt5-tools:x64-osx-release",
|
||||||
"qt5-macextras:x64-osx-release"
|
"qt5-macextras:x64-osx-release"
|
||||||
|
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg upgrade `
|
||||||
|
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
|
||||||
|
--overlay-ports=${{ github.workspace }}/vcpkg `
|
||||||
|
--no-dry-run
|
||||||
foreach($package in $packages)
|
foreach($package in $packages)
|
||||||
{
|
{
|
||||||
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg install $package `
|
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg install $package `
|
||||||
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
|
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
|
||||||
|
--overlay-ports=${{ github.workspace }}/vcpkg `
|
||||||
--clean-after-build
|
--clean-after-build
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
.github/workflows/file_health.yaml
vendored
7
.github/workflows/file_health.yaml
vendored
@@ -1,11 +1,6 @@
|
|||||||
name: GitHub Actions file health check
|
name: GitHub Actions file health check
|
||||||
|
|
||||||
on:
|
on: [pull_request, push]
|
||||||
push:
|
|
||||||
branches: [ '**' ]
|
|
||||||
pull_request:
|
|
||||||
types: [edited, opened, reopened, synchronize]
|
|
||||||
branches: [ '**' ]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_file_health:
|
check_file_health:
|
||||||
|
|||||||
10
.travis.yml
10
.travis.yml
@@ -106,8 +106,8 @@ install:
|
|||||||
|
|
||||||
brew update > /dev/null
|
brew update > /dev/null
|
||||||
brew upgrade cmake
|
brew upgrade cmake
|
||||||
brew install ccache colormake boost openssl qt zlib
|
brew install ccache colormake boost openssl qt@5 zlib
|
||||||
brew link --force qt zlib
|
brew link --force qt@5 zlib
|
||||||
|
|
||||||
if [ "$build_system" = "cmake" ]; then
|
if [ "$build_system" = "cmake" ]; then
|
||||||
sudo ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs
|
sudo ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs
|
||||||
@@ -125,7 +125,7 @@ install:
|
|||||||
pushd "$HOME"
|
pushd "$HOME"
|
||||||
git clone --single-branch --branch RC_1_2 https://github.com/arvidn/libtorrent.git
|
git clone --single-branch --branch RC_1_2 https://github.com/arvidn/libtorrent.git
|
||||||
cd libtorrent
|
cd libtorrent
|
||||||
git checkout tags/v1.2.11
|
git checkout tags/v1.2.12
|
||||||
|
|
||||||
cmake \
|
cmake \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
@@ -140,13 +140,13 @@ install:
|
|||||||
pushd "$HOME"
|
pushd "$HOME"
|
||||||
git clone --single-branch --branch RC_2_0 https://github.com/arvidn/libtorrent.git
|
git clone --single-branch --branch RC_2_0 https://github.com/arvidn/libtorrent.git
|
||||||
cd libtorrent
|
cd libtorrent
|
||||||
git checkout tags/v2.0.1
|
git checkout tags/v2.0.2
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
|
||||||
cmake \
|
cmake \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_CXX_STANDARD=17 \
|
-DCMAKE_CXX_STANDARD=17 \
|
||||||
-Ddeprecated-functions=ON \
|
-Ddeprecated-functions=OFF \
|
||||||
-DOPENSSL_ROOT_DIR="$openssl_root_path" \
|
-DOPENSSL_ROOT_DIR="$openssl_root_path" \
|
||||||
./
|
./
|
||||||
make
|
make
|
||||||
|
|||||||
2
AUTHORS
2
AUTHORS
@@ -119,7 +119,7 @@ Translations authors:
|
|||||||
- German: Niels Hoffmann (zentralmaschine@users.sourceforge.net)
|
- German: Niels Hoffmann (zentralmaschine@users.sourceforge.net)
|
||||||
- Greek: Tsvetan Bankov (emerge_life@users.sourceforge.net), Stephanos Antaris (santaris@csd.auth.gr), sledgehammer999(hammered999@gmail.com) and Γιάννης Ανθυμίδης Evropi(Transifex)
|
- Greek: Tsvetan Bankov (emerge_life@users.sourceforge.net), Stephanos Antaris (santaris@csd.auth.gr), sledgehammer999(hammered999@gmail.com) and Γιάννης Ανθυμίδης Evropi(Transifex)
|
||||||
- Hebrew: David Deutsch (d.deffo@gmail.com)
|
- Hebrew: David Deutsch (d.deffo@gmail.com)
|
||||||
- Hungarian: Majoros Péter (majoros.j.p@t-online.hu)
|
- Hungarian: Majoros Péter
|
||||||
- Italian: bovirus (bovirus@live.it) and Matteo Sechi (bu17714@gmail.com)
|
- Italian: bovirus (bovirus@live.it) and Matteo Sechi (bu17714@gmail.com)
|
||||||
- Japanese: Masato Hashimoto (cabezon.hashimoto@gmail.com)
|
- Japanese: Masato Hashimoto (cabezon.hashimoto@gmail.com)
|
||||||
- Korean: Jin Woo Sin (jin828sin@users.sourceforge.net)
|
- Korean: Jin Woo Sin (jin828sin@users.sourceforge.net)
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ project(qBittorrent
|
|||||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
|
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
|
||||||
# version requirements - older vesions may work, but you are on your own
|
# version requirements - older vesions may work, but you are on your own
|
||||||
set(minBoostVersion 1.65)
|
set(minBoostVersion 1.65)
|
||||||
set(minQtVersion 5.9.5)
|
set(minQtVersion 5.12)
|
||||||
set(minOpenSSLVersion 1.1.1)
|
set(minOpenSSLVersion 1.1.1)
|
||||||
set(minLibtorrentVersion 1.2.11)
|
set(minLibtorrentVersion 1.2.12)
|
||||||
set(minZlibVersion 1.2.11)
|
set(minZlibVersion 1.2.11)
|
||||||
|
|
||||||
# features (some are platform-specific)
|
# features (some are platform-specific)
|
||||||
@@ -61,5 +61,4 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Generate version header
|
# Generate version header
|
||||||
file(READ "src/base/version.h.in" versionHeader)
|
configure_file("src/base/version.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/base/version.h" @ONLY)
|
||||||
file(WRITE "src/base/version.h" "${versionHeader}")
|
|
||||||
|
|||||||
44
Changelog
44
Changelog
@@ -1,3 +1,47 @@
|
|||||||
|
Tue Mar 23 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.4
|
||||||
|
- FEATURE: Add ability to prioritize selected items by shown file order (Chocobo1)
|
||||||
|
- FEATURE: Allow tab to escape the text box in "Edit trackers" dialog (Christoph Rackwitz)
|
||||||
|
- FEATURE: Support sub-sorting in Transferlist (jagannatharjun)
|
||||||
|
- FEATURE: Expose ToS setting from libtorrent (Chocobo1)
|
||||||
|
- FEATURE: Improve tracker entries handling (glassez)
|
||||||
|
- BUGFIX: Drop extension from generated content folder name (glassez)
|
||||||
|
- BUGFIX: Change qBittorrent Updater window title (xavier2k6)
|
||||||
|
- BUGFIX: Validate HTTPS Tracker Certificate by default (an0n666)
|
||||||
|
- BUGFIX: Don't let "program update" dialog steal focus (Chocobo1)
|
||||||
|
- BUGFIX: Disable expand on double click in TorrentContentTreeView (jagannatharjun)
|
||||||
|
- BUGFIX: Add hyperlink to Transifex on translator list (Si Yong Kim)
|
||||||
|
- BUGFIX: Enlarge "speed limit" icon slightly (Chocobo1)
|
||||||
|
- BUGFIX: Don't prevent system sleep due to errored torrents (dyumin)
|
||||||
|
- BUGFIX: Use stable sorting in transfer list (Chocobo1)
|
||||||
|
- BUGFIX: Allow "missing files" torrents to save more resume data (glassez)
|
||||||
|
- BUGFIX: Restart "missing files" torrents after changing location (glassez)
|
||||||
|
- BUGFIX: Show proper string when torrent availability is not available (Chocobo1)
|
||||||
|
- BUGFIX: Apply "Hide zero/infinity values" to "Time Active", "Down/Up Limit" and ETA columns (Chocobo1)
|
||||||
|
- BUGFIX: Fix potential out-of-bounds access (Chocobo1)
|
||||||
|
- BUGFIX: Make SpeedPlotView averager time aware (jagannatharjun)
|
||||||
|
- BUGFIX: Add a 3-Hour graph (jagannatharjun)
|
||||||
|
- BUGFIX: Add an option to disable icons in menus (always disabled on MacOS) (Michał Kopeć)
|
||||||
|
- BUGFIX: Improve detection of filename extension of audio/video files (Chocobo1)
|
||||||
|
- BUGFIX: Various drawing improvements of progress bar (Chocobo1)
|
||||||
|
- BUGFIX: Properly stop torrent creation if aborted (Chocobo1)
|
||||||
|
- BUGFIX: Replace external program parameters in one step (Chocobo1)
|
||||||
|
- BUGFIX: Improve "save resume data" handling (glassez)
|
||||||
|
- BUGFIX: Fix bad IPv6 address format for outgoingInterfaces (treysis)
|
||||||
|
- WEBUI: Properly decode strings (brvphoenix)
|
||||||
|
- WEBUI: Accept "share limits" when adding torrent using WebAPI (glassez)
|
||||||
|
- WEBUI: Add seeding time to the active time column (thalieht)
|
||||||
|
- WEBUI: Fix incorrect seeding time string in General tab (thalieht)
|
||||||
|
- WEBUI: Allow >100 days in WebUI function "friendlyDuration" (thalieht)
|
||||||
|
- WEBUI: Avoid decoding strings repeatedly (brvphoenix)
|
||||||
|
- RSS: Add category button on AutomatedRSSDownloader on GUI (Si Yong Kim)
|
||||||
|
- WINDOWS: NSIS: Update Czech translation (slrslr)
|
||||||
|
- WINDOWS: NSIS: Update Portuguese BR translation (Alex)
|
||||||
|
- WINDOWS: NSIS: Add Estonian translation (PriitUring)
|
||||||
|
- WINDOWS: Allow change-case-only file renaming (glassez)
|
||||||
|
- LINUX: Systemd: wait for mounting of local filesystems (Juraj Oršulić)
|
||||||
|
- OTHER: Raise minimum libtorrent version to 1.2.12 (glassez)
|
||||||
|
- OTHER: Raise minimum Qt version to 5.12 (glassez)
|
||||||
|
|
||||||
Tue Jan 19 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.3
|
Tue Jan 19 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.3
|
||||||
- FEATURE: New languages: Azerbaijani, Estonian
|
- FEATURE: New languages: Azerbaijani, Estonian
|
||||||
- BUGFIX: Unify global speed dialogs for normal/alternative speeds (thalieht)
|
- BUGFIX: Unify global speed dialogs for normal/alternative speeds (thalieht)
|
||||||
|
|||||||
4
INSTALL
4
INSTALL
@@ -5,13 +5,13 @@ qBittorrent - A BitTorrent client in C++ / Qt
|
|||||||
|
|
||||||
- Boost >= 1.65
|
- Boost >= 1.65
|
||||||
|
|
||||||
- libtorrent-rasterbar >= 1.2.11 (by Arvid Norberg)
|
- libtorrent-rasterbar >= 1.2.12 (by Arvid Norberg)
|
||||||
* https://www.libtorrent.org/
|
* https://www.libtorrent.org/
|
||||||
* Be careful: another library (the one used by rTorrent) uses a similar name
|
* Be careful: another library (the one used by rTorrent) uses a similar name
|
||||||
|
|
||||||
- OpenSSL >= 1.1.1
|
- OpenSSL >= 1.1.1
|
||||||
|
|
||||||
- Qt >= 5.9.5
|
- Qt >= 5.12
|
||||||
|
|
||||||
- zlib >= 1.2.11
|
- zlib >= 1.2.11
|
||||||
|
|
||||||
|
|||||||
80
configure
vendored
80
configure
vendored
@@ -1,6 +1,6 @@
|
|||||||
#! /bin/sh
|
#! /bin/sh
|
||||||
# Guess values for system-dependent variables and create Makefiles.
|
# Guess values for system-dependent variables and create Makefiles.
|
||||||
# Generated by GNU Autoconf 2.70 for qbittorrent v4.3.3.
|
# Generated by GNU Autoconf 2.70 for qbittorrent v4.3.4.
|
||||||
#
|
#
|
||||||
# Report bugs to <bugs.qbittorrent.org>.
|
# Report bugs to <bugs.qbittorrent.org>.
|
||||||
#
|
#
|
||||||
@@ -610,8 +610,8 @@ MAKEFLAGS=
|
|||||||
# Identity of this package.
|
# Identity of this package.
|
||||||
PACKAGE_NAME='qbittorrent'
|
PACKAGE_NAME='qbittorrent'
|
||||||
PACKAGE_TARNAME='qbittorrent'
|
PACKAGE_TARNAME='qbittorrent'
|
||||||
PACKAGE_VERSION='v4.3.3'
|
PACKAGE_VERSION='v4.3.4'
|
||||||
PACKAGE_STRING='qbittorrent v4.3.3'
|
PACKAGE_STRING='qbittorrent v4.3.4'
|
||||||
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
|
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
|
||||||
PACKAGE_URL='https://www.qbittorrent.org/'
|
PACKAGE_URL='https://www.qbittorrent.org/'
|
||||||
|
|
||||||
@@ -1330,7 +1330,7 @@ if test "$ac_init_help" = "long"; then
|
|||||||
# Omit some internal or obsolete options to make the list less imposing.
|
# Omit some internal or obsolete options to make the list less imposing.
|
||||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||||
cat <<_ACEOF
|
cat <<_ACEOF
|
||||||
\`configure' configures qbittorrent v4.3.3 to adapt to many kinds of systems.
|
\`configure' configures qbittorrent v4.3.4 to adapt to many kinds of systems.
|
||||||
|
|
||||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||||
|
|
||||||
@@ -1401,7 +1401,7 @@ fi
|
|||||||
|
|
||||||
if test -n "$ac_init_help"; then
|
if test -n "$ac_init_help"; then
|
||||||
case $ac_init_help in
|
case $ac_init_help in
|
||||||
short | recursive ) echo "Configuration of qbittorrent v4.3.3:";;
|
short | recursive ) echo "Configuration of qbittorrent v4.3.4:";;
|
||||||
esac
|
esac
|
||||||
cat <<\_ACEOF
|
cat <<\_ACEOF
|
||||||
|
|
||||||
@@ -1455,7 +1455,7 @@ Some influential environment variables:
|
|||||||
directories to add to pkg-config's search path
|
directories to add to pkg-config's search path
|
||||||
PKG_CONFIG_LIBDIR
|
PKG_CONFIG_LIBDIR
|
||||||
path overriding pkg-config's built-in search path
|
path overriding pkg-config's built-in search path
|
||||||
QT_QMAKE value of host_bins for Qt5Core >= 5.9.5, overriding pkg-config
|
QT_QMAKE value of host_bins for Qt5Core >= 5.12, overriding pkg-config
|
||||||
Qt5Svg_CFLAGS
|
Qt5Svg_CFLAGS
|
||||||
C compiler flags for Qt5Svg, overriding pkg-config
|
C compiler flags for Qt5Svg, overriding pkg-config
|
||||||
Qt5Svg_LIBS linker flags for Qt5Svg, overriding pkg-config
|
Qt5Svg_LIBS linker flags for Qt5Svg, overriding pkg-config
|
||||||
@@ -1538,7 +1538,7 @@ fi
|
|||||||
test -n "$ac_init_help" && exit $ac_status
|
test -n "$ac_init_help" && exit $ac_status
|
||||||
if $ac_init_version; then
|
if $ac_init_version; then
|
||||||
cat <<\_ACEOF
|
cat <<\_ACEOF
|
||||||
qbittorrent configure v4.3.3
|
qbittorrent configure v4.3.4
|
||||||
generated by GNU Autoconf 2.70
|
generated by GNU Autoconf 2.70
|
||||||
|
|
||||||
Copyright (C) 2020 Free Software Foundation, Inc.
|
Copyright (C) 2020 Free Software Foundation, Inc.
|
||||||
@@ -1700,7 +1700,7 @@ cat >config.log <<_ACEOF
|
|||||||
This file contains any messages produced by compilers while
|
This file contains any messages produced by compilers while
|
||||||
running configure, to aid debugging if configure makes a mistake.
|
running configure, to aid debugging if configure makes a mistake.
|
||||||
|
|
||||||
It was created by qbittorrent $as_me v4.3.3, which was
|
It was created by qbittorrent $as_me v4.3.4, which was
|
||||||
generated by GNU Autoconf 2.70. Invocation command line was
|
generated by GNU Autoconf 2.70. Invocation command line was
|
||||||
|
|
||||||
$ $0$ac_configure_args_raw
|
$ $0$ac_configure_args_raw
|
||||||
@@ -4848,7 +4848,7 @@ fi
|
|||||||
|
|
||||||
# Define the identity of the package.
|
# Define the identity of the package.
|
||||||
PACKAGE='qbittorrent'
|
PACKAGE='qbittorrent'
|
||||||
VERSION='v4.3.3'
|
VERSION='v4.3.4'
|
||||||
|
|
||||||
|
|
||||||
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
|
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
|
||||||
@@ -5525,8 +5525,8 @@ printf "%s\n" "$enable_webui" >&6; }
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
if test -n "$PKG_CONFIG" && \
|
if test -n "$PKG_CONFIG" && \
|
||||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.9.5\""; } >&5
|
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.12\""; } >&5
|
||||||
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.9.5") 2>&5
|
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.12") 2>&5
|
||||||
ac_status=$?
|
ac_status=$?
|
||||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||||
test $ac_status = 0; }; then
|
test $ac_status = 0; }; then
|
||||||
@@ -5535,12 +5535,12 @@ if test -n "$QT_QMAKE"; then
|
|||||||
pkg_cv_QT_QMAKE="$QT_QMAKE"
|
pkg_cv_QT_QMAKE="$QT_QMAKE"
|
||||||
elif test -n "$PKG_CONFIG"; then
|
elif test -n "$PKG_CONFIG"; then
|
||||||
if test -n "$PKG_CONFIG" && \
|
if test -n "$PKG_CONFIG" && \
|
||||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.9.5\""; } >&5
|
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.12\""; } >&5
|
||||||
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.9.5") 2>&5
|
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.12") 2>&5
|
||||||
ac_status=$?
|
ac_status=$?
|
||||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||||
test $ac_status = 0; }; then
|
test $ac_status = 0; }; then
|
||||||
pkg_cv_QT_QMAKE=`$PKG_CONFIG --variable="host_bins" "Qt5Core >= 5.9.5" 2>/dev/null`
|
pkg_cv_QT_QMAKE=`$PKG_CONFIG --variable="host_bins" "Qt5Core >= 5.12" 2>/dev/null`
|
||||||
test "x$?" != "x0" && pkg_failed=yes
|
test "x$?" != "x0" && pkg_failed=yes
|
||||||
else
|
else
|
||||||
pkg_failed=yes
|
pkg_failed=yes
|
||||||
@@ -5570,8 +5570,8 @@ fi
|
|||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5 qmake >= 5.9.5" >&5
|
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5 qmake >= 5.12" >&5
|
||||||
printf %s "checking for Qt5 qmake >= 5.9.5... " >&6; }
|
printf %s "checking for Qt5 qmake >= 5.12... " >&6; }
|
||||||
if test "x$QT_QMAKE" != "x"
|
if test "x$QT_QMAKE" != "x"
|
||||||
then :
|
then :
|
||||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $QT_QMAKE" >&5
|
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $QT_QMAKE" >&5
|
||||||
@@ -5598,12 +5598,12 @@ if test -n "$Qt5Svg_CFLAGS"; then
|
|||||||
pkg_cv_Qt5Svg_CFLAGS="$Qt5Svg_CFLAGS"
|
pkg_cv_Qt5Svg_CFLAGS="$Qt5Svg_CFLAGS"
|
||||||
elif test -n "$PKG_CONFIG"; then
|
elif test -n "$PKG_CONFIG"; then
|
||||||
if test -n "$PKG_CONFIG" && \
|
if test -n "$PKG_CONFIG" && \
|
||||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.5.1\""; } >&5
|
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.12\""; } >&5
|
||||||
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.5.1") 2>&5
|
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.12") 2>&5
|
||||||
ac_status=$?
|
ac_status=$?
|
||||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||||
test $ac_status = 0; }; then
|
test $ac_status = 0; }; then
|
||||||
pkg_cv_Qt5Svg_CFLAGS=`$PKG_CONFIG --cflags "Qt5Svg >= 5.5.1" 2>/dev/null`
|
pkg_cv_Qt5Svg_CFLAGS=`$PKG_CONFIG --cflags "Qt5Svg >= 5.12" 2>/dev/null`
|
||||||
test "x$?" != "x0" && pkg_failed=yes
|
test "x$?" != "x0" && pkg_failed=yes
|
||||||
else
|
else
|
||||||
pkg_failed=yes
|
pkg_failed=yes
|
||||||
@@ -5615,12 +5615,12 @@ if test -n "$Qt5Svg_LIBS"; then
|
|||||||
pkg_cv_Qt5Svg_LIBS="$Qt5Svg_LIBS"
|
pkg_cv_Qt5Svg_LIBS="$Qt5Svg_LIBS"
|
||||||
elif test -n "$PKG_CONFIG"; then
|
elif test -n "$PKG_CONFIG"; then
|
||||||
if test -n "$PKG_CONFIG" && \
|
if test -n "$PKG_CONFIG" && \
|
||||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.5.1\""; } >&5
|
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.12\""; } >&5
|
||||||
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.5.1") 2>&5
|
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.12") 2>&5
|
||||||
ac_status=$?
|
ac_status=$?
|
||||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||||
test $ac_status = 0; }; then
|
test $ac_status = 0; }; then
|
||||||
pkg_cv_Qt5Svg_LIBS=`$PKG_CONFIG --libs "Qt5Svg >= 5.5.1" 2>/dev/null`
|
pkg_cv_Qt5Svg_LIBS=`$PKG_CONFIG --libs "Qt5Svg >= 5.12" 2>/dev/null`
|
||||||
test "x$?" != "x0" && pkg_failed=yes
|
test "x$?" != "x0" && pkg_failed=yes
|
||||||
else
|
else
|
||||||
pkg_failed=yes
|
pkg_failed=yes
|
||||||
@@ -5641,14 +5641,14 @@ else
|
|||||||
_pkg_short_errors_supported=no
|
_pkg_short_errors_supported=no
|
||||||
fi
|
fi
|
||||||
if test $_pkg_short_errors_supported = yes; then
|
if test $_pkg_short_errors_supported = yes; then
|
||||||
Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "Qt5Svg >= 5.5.1" 2>&1`
|
Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "Qt5Svg >= 5.12" 2>&1`
|
||||||
else
|
else
|
||||||
Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "Qt5Svg >= 5.5.1" 2>&1`
|
Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "Qt5Svg >= 5.12" 2>&1`
|
||||||
fi
|
fi
|
||||||
# Put the nasty error message in config.log where it belongs
|
# Put the nasty error message in config.log where it belongs
|
||||||
echo "$Qt5Svg_PKG_ERRORS" >&5
|
echo "$Qt5Svg_PKG_ERRORS" >&5
|
||||||
|
|
||||||
as_fn_error $? "Package requirements (Qt5Svg >= 5.5.1) were not met:
|
as_fn_error $? "Package requirements (Qt5Svg >= 5.12) were not met:
|
||||||
|
|
||||||
$Qt5Svg_PKG_ERRORS
|
$Qt5Svg_PKG_ERRORS
|
||||||
|
|
||||||
@@ -5688,11 +5688,11 @@ case "x$enable_qt_dbus" in #(
|
|||||||
"xyes") :
|
"xyes") :
|
||||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||||
printf "%s\n" "yes" >&6; }
|
printf "%s\n" "yes" >&6; }
|
||||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5DBus >= 5.9.5" >&5
|
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5DBus >= 5.12" >&5
|
||||||
printf %s "checking for Qt5DBus >= 5.9.5... " >&6; }
|
printf %s "checking for Qt5DBus >= 5.12... " >&6; }
|
||||||
if test -n "$PKG_CONFIG" && \
|
if test -n "$PKG_CONFIG" && \
|
||||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5DBus >= 5.9.5\""; } >&5
|
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5DBus >= 5.12\""; } >&5
|
||||||
($PKG_CONFIG --exists --print-errors "Qt5DBus >= 5.9.5") 2>&5
|
($PKG_CONFIG --exists --print-errors "Qt5DBus >= 5.12") 2>&5
|
||||||
ac_status=$?
|
ac_status=$?
|
||||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||||
test $ac_status = 0; }; then
|
test $ac_status = 0; }; then
|
||||||
@@ -6354,12 +6354,12 @@ if test -n "$libtorrent_CFLAGS"; then
|
|||||||
pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS"
|
pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS"
|
||||||
elif test -n "$PKG_CONFIG"; then
|
elif test -n "$PKG_CONFIG"; then
|
||||||
if test -n "$PKG_CONFIG" && \
|
if test -n "$PKG_CONFIG" && \
|
||||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.11\""; } >&5
|
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.12\""; } >&5
|
||||||
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.11") 2>&5
|
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.12") 2>&5
|
||||||
ac_status=$?
|
ac_status=$?
|
||||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||||
test $ac_status = 0; }; then
|
test $ac_status = 0; }; then
|
||||||
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.11" 2>/dev/null`
|
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.12" 2>/dev/null`
|
||||||
test "x$?" != "x0" && pkg_failed=yes
|
test "x$?" != "x0" && pkg_failed=yes
|
||||||
else
|
else
|
||||||
pkg_failed=yes
|
pkg_failed=yes
|
||||||
@@ -6371,12 +6371,12 @@ if test -n "$libtorrent_LIBS"; then
|
|||||||
pkg_cv_libtorrent_LIBS="$libtorrent_LIBS"
|
pkg_cv_libtorrent_LIBS="$libtorrent_LIBS"
|
||||||
elif test -n "$PKG_CONFIG"; then
|
elif test -n "$PKG_CONFIG"; then
|
||||||
if test -n "$PKG_CONFIG" && \
|
if test -n "$PKG_CONFIG" && \
|
||||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.11\""; } >&5
|
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.12\""; } >&5
|
||||||
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.11") 2>&5
|
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.12") 2>&5
|
||||||
ac_status=$?
|
ac_status=$?
|
||||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||||
test $ac_status = 0; }; then
|
test $ac_status = 0; }; then
|
||||||
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.11" 2>/dev/null`
|
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.12" 2>/dev/null`
|
||||||
test "x$?" != "x0" && pkg_failed=yes
|
test "x$?" != "x0" && pkg_failed=yes
|
||||||
else
|
else
|
||||||
pkg_failed=yes
|
pkg_failed=yes
|
||||||
@@ -6397,14 +6397,14 @@ else
|
|||||||
_pkg_short_errors_supported=no
|
_pkg_short_errors_supported=no
|
||||||
fi
|
fi
|
||||||
if test $_pkg_short_errors_supported = yes; then
|
if test $_pkg_short_errors_supported = yes; then
|
||||||
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.11" 2>&1`
|
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.12" 2>&1`
|
||||||
else
|
else
|
||||||
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.11" 2>&1`
|
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.12" 2>&1`
|
||||||
fi
|
fi
|
||||||
# Put the nasty error message in config.log where it belongs
|
# Put the nasty error message in config.log where it belongs
|
||||||
echo "$libtorrent_PKG_ERRORS" >&5
|
echo "$libtorrent_PKG_ERRORS" >&5
|
||||||
|
|
||||||
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.11) were not met:
|
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.12) were not met:
|
||||||
|
|
||||||
$libtorrent_PKG_ERRORS
|
$libtorrent_PKG_ERRORS
|
||||||
|
|
||||||
@@ -7401,7 +7401,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
|||||||
# report actual input values of CONFIG_FILES etc. instead of their
|
# report actual input values of CONFIG_FILES etc. instead of their
|
||||||
# values after options handling.
|
# values after options handling.
|
||||||
ac_log="
|
ac_log="
|
||||||
This file was extended by qbittorrent $as_me v4.3.3, which was
|
This file was extended by qbittorrent $as_me v4.3.4, which was
|
||||||
generated by GNU Autoconf 2.70. Invocation command line was
|
generated by GNU Autoconf 2.70. Invocation command line was
|
||||||
|
|
||||||
CONFIG_FILES = $CONFIG_FILES
|
CONFIG_FILES = $CONFIG_FILES
|
||||||
@@ -7461,7 +7461,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
|
|||||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||||
ac_cs_config='$ac_cs_config_escaped'
|
ac_cs_config='$ac_cs_config_escaped'
|
||||||
ac_cs_version="\\
|
ac_cs_version="\\
|
||||||
qbittorrent config.status v4.3.3
|
qbittorrent config.status v4.3.4
|
||||||
configured by $0, generated by GNU Autoconf 2.70,
|
configured by $0, generated by GNU Autoconf 2.70,
|
||||||
with options \\"\$ac_cs_config\\"
|
with options \\"\$ac_cs_config\\"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
AC_INIT([qbittorrent], [v4.3.3], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
AC_INIT([qbittorrent], [v4.3.4], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||||
AC_CONFIG_AUX_DIR([build-aux])
|
AC_CONFIG_AUX_DIR([build-aux])
|
||||||
AC_CONFIG_MACRO_DIR([m4])
|
AC_CONFIG_MACRO_DIR([m4])
|
||||||
: ${CFLAGS=""}
|
: ${CFLAGS=""}
|
||||||
@@ -141,7 +141,7 @@ AS_IF([test "x$QT_QMAKE" = "x"],
|
|||||||
[AC_MSG_ERROR([Could not find qmake])
|
[AC_MSG_ERROR([Could not find qmake])
|
||||||
])
|
])
|
||||||
AS_IF([test "x$enable_gui" = "xyes"],
|
AS_IF([test "x$enable_gui" = "xyes"],
|
||||||
[PKG_CHECK_MODULES(Qt5Svg, [Qt5Svg >= 5.5.1])
|
[PKG_CHECK_MODULES(Qt5Svg, [Qt5Svg >= 5.12])
|
||||||
])
|
])
|
||||||
AC_MSG_CHECKING([whether QtDBus should be enabled])
|
AC_MSG_CHECKING([whether QtDBus should be enabled])
|
||||||
AS_CASE(["x$enable_qt_dbus"],
|
AS_CASE(["x$enable_qt_dbus"],
|
||||||
@@ -180,7 +180,7 @@ AC_MSG_NOTICE([Boost.System LIB: "$BOOST_SYSTEM_LIB"])
|
|||||||
LIBS="$BOOST_SYSTEM_LIB $LIBS"
|
LIBS="$BOOST_SYSTEM_LIB $LIBS"
|
||||||
|
|
||||||
PKG_CHECK_MODULES(libtorrent,
|
PKG_CHECK_MODULES(libtorrent,
|
||||||
[libtorrent-rasterbar >= 1.2.11],
|
[libtorrent-rasterbar >= 1.2.12],
|
||||||
[CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS"
|
[CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS"
|
||||||
LIBS="$libtorrent_LIBS $LIBS"])
|
LIBS="$libtorrent_LIBS $LIBS"])
|
||||||
|
|
||||||
|
|||||||
2
dist/mac/Info.plist
vendored
2
dist/mac/Info.plist
vendored
@@ -55,7 +55,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>4.3.3</string>
|
<string>4.3.4</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>@EXECUTABLE@</string>
|
<string>@EXECUTABLE@</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
|
|||||||
@@ -74,6 +74,6 @@
|
|||||||
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
|
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
|
||||||
<content_rating type="oars-1.1"/>
|
<content_rating type="oars-1.1"/>
|
||||||
<releases>
|
<releases>
|
||||||
<release version="4.3.3" date="2021-01-19"/>
|
<release version="4.3.4" date="2021-03-23"/>
|
||||||
</releases>
|
</releases>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
15
dist/unix/org.qbittorrent.qBittorrent.desktop
vendored
15
dist/unix/org.qbittorrent.qBittorrent.desktop
vendored
@@ -118,6 +118,9 @@ Name[lt]=qBittorrent
|
|||||||
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
|
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
|
||||||
GenericName[mk]=BitTorrent клиент
|
GenericName[mk]=BitTorrent клиент
|
||||||
Name[mk]=qBittorrent
|
Name[mk]=qBittorrent
|
||||||
|
Comment[en_AU]=Download and share files over BitTorrent
|
||||||
|
GenericName[en_AU]=BitTorrent client
|
||||||
|
Name[en_AU]=qBittorrent
|
||||||
Comment[nb]=Last ned og del filer over BitTorrent
|
Comment[nb]=Last ned og del filer over BitTorrent
|
||||||
GenericName[nb]=BitTorrent-klient
|
GenericName[nb]=BitTorrent-klient
|
||||||
Name[nb]=qBittorrent
|
Name[nb]=qBittorrent
|
||||||
@@ -162,9 +165,9 @@ Name[uz@Latn]=qBittorrent
|
|||||||
Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent
|
Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent
|
||||||
GenericName[ltg]=BitTorrent klients
|
GenericName[ltg]=BitTorrent klients
|
||||||
Name[ltg]=qBittorrent
|
Name[ltg]=qBittorrent
|
||||||
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
|
Comment[hi_IN]=बिटटौरेंट द्वारा फाइल डाउनलोड व सहभाजन
|
||||||
GenericName[hi_IN]=BitTorrent साधन
|
GenericName[hi_IN]=बिटटौरेंट साधन
|
||||||
Name[hi_IN]=qBittorrent
|
Name[hi_IN]=क्यूबिटटौरेंट
|
||||||
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
||||||
GenericName[tr]=BitTorrent istemcisi
|
GenericName[tr]=BitTorrent istemcisi
|
||||||
Name[tr]=qBittorrent
|
Name[tr]=qBittorrent
|
||||||
@@ -207,9 +210,9 @@ Name[ne_NP]=qBittorrent
|
|||||||
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
|
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
|
||||||
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
|
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
|
||||||
Name[te]=క్యు బిట్ టొరెంట్
|
Name[te]=క్యు బిట్ టొరెంట్
|
||||||
Comment[en_AU]=Download and share files over BitTorrent
|
Comment[pt_PT]=Transferir e partilhar ficheiros por BitTorrent
|
||||||
GenericName[en_AU]=BitTorrent client
|
GenericName[pt_PT]=Cliente BitTorrent
|
||||||
Name[en_AU]=qBittorrent
|
Name[pt_PT]=qBittorrent
|
||||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ด้วยบิททอเร้น
|
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ด้วยบิททอเร้น
|
||||||
GenericName[th]=โปรแกรมบิททอเร้น
|
GenericName[th]=โปรแกรมบิททอเร้น
|
||||||
Name[th]=qBittorrent
|
Name[th]=qBittorrent
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Description=qBittorrent-nox service for user %I
|
Description=qBittorrent-nox service for user %I
|
||||||
Documentation=man:qbittorrent-nox(1)
|
Documentation=man:qbittorrent-nox(1)
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target nss-lookup.target
|
After=local-fs.target network-online.target nss-lookup.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ LangString inst_dekstop ${LANG_CZECH} "Vytvořit zástupce na ploše"
|
|||||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||||
LangString inst_startmenu ${LANG_CZECH} "Vytvořit zástupce v nabídce Start"
|
LangString inst_startmenu ${LANG_CZECH} "Vytvořit zástupce v nabídce Start"
|
||||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||||
LangString inst_startup ${LANG_CZECH} "Spusťte aplikaci qBittorrent ve Windows"
|
LangString inst_startup ${LANG_CZECH} "Spustit qBittorrent při startu Windows"
|
||||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||||
LangString inst_torrent ${LANG_CZECH} "Otevírat .torrent soubory pomocí qBittorrent"
|
LangString inst_torrent ${LANG_CZECH} "Otevírat .torrent soubory pomocí qBittorrent"
|
||||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||||
@@ -15,9 +15,9 @@ LangString inst_magnet ${LANG_CZECH} "Otevírat odkazy Magnet pomocí qBittorren
|
|||||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||||
LangString inst_firewall ${LANG_CZECH} "Vytvořit pravidlo ve Windows Firewall"
|
LangString inst_firewall ${LANG_CZECH} "Vytvořit pravidlo ve Windows Firewall"
|
||||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||||
LangString inst_pathlimit ${LANG_CZECH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
LangString inst_pathlimit ${LANG_CZECH} "Vypnout MAX_PATH limit 260 znaků pro cesty (vyžaduje Windows 10 1607 a novější)"
|
||||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||||
LangString inst_firewallinfo ${LANG_CZECH} "Vytváření pravidla ve Windows Firewall"
|
LangString inst_firewallinfo ${LANG_CZECH} "Přidávání pravidla do Windows Firewall"
|
||||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||||
LangString inst_warning ${LANG_CZECH} "qBittorrent je spuštěn. Před instalací aplikaci ukončete, prosím."
|
LangString inst_warning ${LANG_CZECH} "qBittorrent je spuštěn. Před instalací aplikaci ukončete, prosím."
|
||||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||||
|
|||||||
52
dist/windows/installer-translations/estonian.nsi
vendored
52
dist/windows/installer-translations/estonian.nsi
vendored
@@ -1,59 +1,59 @@
|
|||||||
;Installer strings
|
;Installer strings
|
||||||
|
|
||||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||||
LangString inst_qbt_req ${LANG_ESTONIAN} "qBittorrent (required)"
|
LangString inst_qbt_req ${LANG_ESTONIAN} "qBittorrent (vajalik)"
|
||||||
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||||
LangString inst_dekstop ${LANG_ESTONIAN} "Create Desktop Shortcut"
|
LangString inst_dekstop ${LANG_ESTONIAN} "Loo Töölaua Otsetee"
|
||||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||||
LangString inst_startmenu ${LANG_ESTONIAN} "Create Start Menu Shortcut"
|
LangString inst_startmenu ${LANG_ESTONIAN} "Loo Start Menüü Otsetee"
|
||||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||||
LangString inst_startup ${LANG_ESTONIAN} "Start qBittorrent on Windows start up"
|
LangString inst_startup ${LANG_ESTONIAN} "Käivita qBittorrent Windowsi käivitamisel"
|
||||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||||
LangString inst_torrent ${LANG_ESTONIAN} "Open .torrent files with qBittorrent"
|
LangString inst_torrent ${LANG_ESTONIAN} "Ava .torrent failid qBittorrentiga"
|
||||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||||
LangString inst_magnet ${LANG_ESTONIAN} "Open magnet links with qBittorrent"
|
LangString inst_magnet ${LANG_ESTONIAN} "Ava magnet lingid qBittorrentiga"
|
||||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||||
LangString inst_firewall ${LANG_ESTONIAN} "Add Windows Firewall rule"
|
LangString inst_firewall ${LANG_ESTONIAN} "Lisa reegel Windowsi Tulemüüri"
|
||||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||||
LangString inst_pathlimit ${LANG_ESTONIAN} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
LangString inst_pathlimit ${LANG_ESTONIAN} "Keela Windowsi kaustade pikkuse limiit (260 karakterit MAX_PATH limiit, vajalik Windows 10 1607 või uuemat)"
|
||||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||||
LangString inst_firewallinfo ${LANG_ESTONIAN} "Adding Windows Firewall rule"
|
LangString inst_firewallinfo ${LANG_ESTONIAN} "Lisatakse Windowsi Tulemüüri reegel"
|
||||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||||
LangString inst_warning ${LANG_ESTONIAN} "qBittorrent is running. Please close the application before installing."
|
LangString inst_warning ${LANG_ESTONIAN} "qBittorrent töötab. Palun sulge see programm, enne installi."
|
||||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||||
LangString inst_uninstall_question ${LANG_ESTONIAN} "Current version will be uninstalled. User settings and torrents will remain intact."
|
LangString inst_uninstall_question ${LANG_ESTONIAN} "Praegune versioon uninstallitakse. Kasutaja sätted ja torrenti failid jäetakse alles."
|
||||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||||
LangString inst_unist ${LANG_ESTONIAN} "Uninstalling previous version."
|
LangString inst_unist ${LANG_ESTONIAN} "Uninstallitakse eelmist versiooni."
|
||||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||||
LangString launch_qbt ${LANG_ESTONIAN} "Launch qBittorrent."
|
LangString launch_qbt ${LANG_ESTONIAN} "Käivita qBittorrent."
|
||||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||||
LangString inst_requires_64bit ${LANG_ESTONIAN} "This installer works only in 64-bit Windows versions."
|
LangString inst_requires_64bit ${LANG_ESTONIAN} "See installer töötab ainult 64-bit Windowsi versioonides."
|
||||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||||
LangString inst_requires_win7 ${LANG_ESTONIAN} "This qBittorrent version requires at least Windows 7."
|
LangString inst_requires_win7 ${LANG_ESTONIAN} "Selle qBittorrenti versiooni jaoks on vajalik vähemalt Windows 7."
|
||||||
|
|
||||||
|
|
||||||
;------------------------------------
|
;------------------------------------
|
||||||
;Uninstaller strings
|
;Uninstaller strings
|
||||||
|
|
||||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||||
LangString remove_files ${LANG_ESTONIAN} "Remove files"
|
LangString remove_files ${LANG_ESTONIAN} "Eemalda failid"
|
||||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||||
LangString remove_shortcuts ${LANG_ESTONIAN} "Remove shortcuts"
|
LangString remove_shortcuts ${LANG_ESTONIAN} "Eemalda otseteed"
|
||||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||||
LangString remove_associations ${LANG_ESTONIAN} "Remove file associations"
|
LangString remove_associations ${LANG_ESTONIAN} "Eemalda failide seotus"
|
||||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||||
LangString remove_registry ${LANG_ESTONIAN} "Remove registry keys"
|
LangString remove_registry ${LANG_ESTONIAN} "Eemalda registri võtmed"
|
||||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||||
LangString remove_conf ${LANG_ESTONIAN} "Remove configuration files"
|
LangString remove_conf ${LANG_ESTONIAN} "Eemalda seadistuste failid"
|
||||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||||
LangString remove_firewall ${LANG_ESTONIAN} "Remove Windows Firewall rule"
|
LangString remove_firewall ${LANG_ESTONIAN} "Eemalda Windowsi Tulemüüri reegel"
|
||||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||||
LangString remove_firewallinfo ${LANG_ESTONIAN} "Removing Windows Firewall rule"
|
LangString remove_firewallinfo ${LANG_ESTONIAN} "Eemaldatakse Windowsi Tulemüüri reegel"
|
||||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||||
LangString remove_cache ${LANG_ESTONIAN} "Remove torrents and cached data"
|
LangString remove_cache ${LANG_ESTONIAN} "Eemalda torrentid ja ajutised andmed"
|
||||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||||
LangString uninst_warning ${LANG_ESTONIAN} "qBittorrent is running. Please close the application before uninstalling."
|
LangString uninst_warning ${LANG_ESTONIAN} "qBittorrent töötab. Palun sulge programm, enne uninstallimist."
|
||||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||||
LangString uninst_tor_warn ${LANG_ESTONIAN} "Not removing .torrent association. It is associated with:"
|
LangString uninst_tor_warn ${LANG_ESTONIAN} "Ei eemaldata .torrent seotust. Mis on seotud:"
|
||||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||||
LangString uninst_mag_warn ${LANG_ESTONIAN} "Not removing magnet association. It is associated with:"
|
LangString uninst_mag_warn ${LANG_ESTONIAN} "Ei eemaldata magnetitega seotust. Mis on seotud:"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ LangString inst_magnet ${LANG_PORTUGUESEBR} "Abrir links magnéticos com qBittor
|
|||||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||||
LangString inst_firewall ${LANG_PORTUGUESEBR} "Adicionar regra no firewall do Windows"
|
LangString inst_firewall ${LANG_PORTUGUESEBR} "Adicionar regra no firewall do Windows"
|
||||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||||
LangString inst_pathlimit ${LANG_PORTUGUESEBR} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
LangString inst_pathlimit ${LANG_PORTUGUESEBR} "Desativar limite de caracteres em caminhos do Windows (limite MAX_PATH de 260 caracteres, requer Windows 10 1067 ou superior)"
|
||||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||||
LangString inst_firewallinfo ${LANG_PORTUGUESEBR} "Adicionando regra no firewall do Windows"
|
LangString inst_firewallinfo ${LANG_PORTUGUESEBR} "Adicionando regra no firewall do Windows"
|
||||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||||
@@ -27,9 +27,9 @@ LangString inst_unist ${LANG_PORTUGUESEBR} "Desinstalando versão anterior."
|
|||||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||||
LangString launch_qbt ${LANG_PORTUGUESEBR} "Executar qBittorrent."
|
LangString launch_qbt ${LANG_PORTUGUESEBR} "Executar qBittorrent."
|
||||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||||
LangString inst_requires_64bit ${LANG_PORTUGUESEBR} "This installer works only in 64-bit Windows versions."
|
LangString inst_requires_64bit ${LANG_PORTUGUESEBR} "Este instalador apenas funciona em versões 64-bit do Windows."
|
||||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||||
LangString inst_requires_win7 ${LANG_PORTUGUESEBR} "This qBittorrent version requires at least Windows 7."
|
LangString inst_requires_win7 ${LANG_PORTUGUESEBR} "Esta versão do qBittorrent requer no mínimo o Windows 7."
|
||||||
|
|
||||||
|
|
||||||
;------------------------------------
|
;------------------------------------
|
||||||
@@ -42,13 +42,13 @@ LangString remove_shortcuts ${LANG_PORTUGUESEBR} "Remover atalhos"
|
|||||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||||
LangString remove_associations ${LANG_PORTUGUESEBR} "Remover associação de arquivos"
|
LangString remove_associations ${LANG_PORTUGUESEBR} "Remover associação de arquivos"
|
||||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||||
LangString remove_registry ${LANG_PORTUGUESEBR} "Remover registros"
|
LangString remove_registry ${LANG_PORTUGUESEBR} "Remover chaves do registro"
|
||||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||||
LangString remove_conf ${LANG_PORTUGUESEBR} "Remover configurações"
|
LangString remove_conf ${LANG_PORTUGUESEBR} "Remover configurações"
|
||||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||||
LangString remove_firewall ${LANG_PORTUGUESEBR} "Remover regras do firewall do Windows"
|
LangString remove_firewall ${LANG_PORTUGUESEBR} "Remover regra do Firewall do Windows"
|
||||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||||
LangString remove_firewallinfo ${LANG_PORTUGUESEBR} "Removendo regras do firewall do Windows"
|
LangString remove_firewallinfo ${LANG_PORTUGUESEBR} "Removendo regra do Firewall do Windows"
|
||||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||||
LangString remove_cache ${LANG_PORTUGUESEBR} "Remover torrents e dados em cache"
|
LangString remove_cache ${LANG_PORTUGUESEBR} "Remover torrents e dados em cache"
|
||||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||||
|
|||||||
2
dist/windows/options.nsi
vendored
2
dist/windows/options.nsi
vendored
@@ -28,7 +28,7 @@ XPStyle on
|
|||||||
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
|
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
|
||||||
|
|
||||||
; Program specific
|
; Program specific
|
||||||
!define PROG_VERSION "4.3.3"
|
!define PROG_VERSION "4.3.4"
|
||||||
|
|
||||||
!define MUI_FINISHPAGE_RUN
|
!define MUI_FINISHPAGE_RUN
|
||||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
# Sets the QT_QMAKE variable to the path of Qt5 qmake if found.
|
# Sets the QT_QMAKE variable to the path of Qt5 qmake if found.
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
AC_DEFUN([FIND_QT5],
|
AC_DEFUN([FIND_QT5],
|
||||||
[PKG_CHECK_EXISTS([Qt5Core >= 5.9.5],
|
[PKG_CHECK_EXISTS([Qt5Core >= 5.12],
|
||||||
[PKG_CHECK_VAR(QT_QMAKE,
|
[PKG_CHECK_VAR(QT_QMAKE,
|
||||||
[Qt5Core >= 5.9.5],
|
[Qt5Core >= 5.12],
|
||||||
[host_bins])
|
[host_bins])
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ AS_IF([test -f "$QT_QMAKE/qmake"],
|
|||||||
[QT_QMAKE=""])
|
[QT_QMAKE=""])
|
||||||
])
|
])
|
||||||
|
|
||||||
AC_MSG_CHECKING([for Qt5 qmake >= 5.9.5])
|
AC_MSG_CHECKING([for Qt5 qmake >= 5.12])
|
||||||
AS_IF([test "x$QT_QMAKE" != "x"],
|
AS_IF([test "x$QT_QMAKE" != "x"],
|
||||||
[AC_MSG_RESULT([$QT_QMAKE])],
|
[AC_MSG_RESULT([$QT_QMAKE])],
|
||||||
[AC_MSG_RESULT([not found])]
|
[AC_MSG_RESULT([not found])]
|
||||||
@@ -29,8 +29,8 @@ AS_IF([test "x$QT_QMAKE" != "x"],
|
|||||||
# Sets the HAVE_QTDBUS variable to true or false.
|
# Sets the HAVE_QTDBUS variable to true or false.
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
AC_DEFUN([FIND_QTDBUS],
|
AC_DEFUN([FIND_QTDBUS],
|
||||||
[AC_MSG_CHECKING([for Qt5DBus >= 5.9.5])
|
[AC_MSG_CHECKING([for Qt5DBus >= 5.12])
|
||||||
PKG_CHECK_EXISTS([Qt5DBus >= 5.9.5],
|
PKG_CHECK_EXISTS([Qt5DBus >= 5.12],
|
||||||
[AC_MSG_RESULT([found])
|
[AC_MSG_RESULT([found])
|
||||||
HAVE_QTDBUS=[true]],
|
HAVE_QTDBUS=[true]],
|
||||||
[AC_MSG_RESULT([not found])
|
[AC_MSG_RESULT([not found])
|
||||||
|
|||||||
@@ -4,22 +4,14 @@
|
|||||||
# Based on https://gist.github.com/giraldeau/546ba5512a74dfe9d8ea0862d66db412
|
# Based on https://gist.github.com/giraldeau/546ba5512a74dfe9d8ea0862d66db412
|
||||||
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts")
|
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts")
|
||||||
set_source_files_properties(${QBT_TS_FILES} PROPERTIES OUTPUT_LOCATION "${qBittorrent_BINARY_DIR}/src/lang")
|
set_source_files_properties(${QBT_TS_FILES} PROPERTIES OUTPUT_LOCATION "${qBittorrent_BINARY_DIR}/src/lang")
|
||||||
if (Qt5_VERSION VERSION_LESS 5.12)
|
qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES} OPTIONS -silent)
|
||||||
qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES})
|
|
||||||
else()
|
|
||||||
qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES} OPTIONS -silent)
|
|
||||||
endif()
|
|
||||||
configure_file("${qBittorrent_SOURCE_DIR}/src/lang/lang.qrc" "${qBittorrent_BINARY_DIR}/src/lang/lang.qrc" COPYONLY)
|
configure_file("${qBittorrent_SOURCE_DIR}/src/lang/lang.qrc" "${qBittorrent_BINARY_DIR}/src/lang/lang.qrc" COPYONLY)
|
||||||
|
|
||||||
if (WEBUI)
|
if (WEBUI)
|
||||||
file(GLOB QBT_WEBUI_TS_FILES "${qBittorrent_SOURCE_DIR}/src/webui/www/translations/*.ts")
|
file(GLOB QBT_WEBUI_TS_FILES "${qBittorrent_SOURCE_DIR}/src/webui/www/translations/*.ts")
|
||||||
set_source_files_properties(${QBT_WEBUI_TS_FILES}
|
set_source_files_properties(${QBT_WEBUI_TS_FILES}
|
||||||
PROPERTIES OUTPUT_LOCATION "${qBittorrent_BINARY_DIR}/src/webui/www/translations")
|
PROPERTIES OUTPUT_LOCATION "${qBittorrent_BINARY_DIR}/src/webui/www/translations")
|
||||||
if (Qt5_VERSION VERSION_LESS 5.12)
|
qt5_add_translation(QBT_WEBUI_QM_FILES ${QBT_WEBUI_TS_FILES} OPTIONS -silent)
|
||||||
qt5_add_translation(QBT_WEBUI_QM_FILES ${QBT_WEBUI_TS_FILES})
|
|
||||||
else()
|
|
||||||
qt5_add_translation(QBT_WEBUI_QM_FILES ${QBT_WEBUI_TS_FILES} OPTIONS -silent)
|
|
||||||
endif()
|
|
||||||
configure_file("${qBittorrent_SOURCE_DIR}/src/webui/www/translations/webui_translations.qrc"
|
configure_file("${qBittorrent_SOURCE_DIR}/src/webui/www/translations/webui_translations.qrc"
|
||||||
"${qBittorrent_BINARY_DIR}/src/webui/www/translations/webui_translations.qrc" COPYONLY)
|
"${qBittorrent_BINARY_DIR}/src/webui/www/translations/webui_translations.qrc" COPYONLY)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -313,14 +313,6 @@ void Application::processMessage(const QString &message)
|
|||||||
|
|
||||||
void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
|
void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
|
||||||
{
|
{
|
||||||
QString program = Preferences::instance()->getAutoRunProgram().trimmed();
|
|
||||||
program.replace("%N", torrent->name());
|
|
||||||
program.replace("%L", torrent->category());
|
|
||||||
|
|
||||||
QStringList tags = torrent->tags().values();
|
|
||||||
std::sort(tags.begin(), tags.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
|
|
||||||
program.replace("%G", tags.join(','));
|
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
const auto chopPathSep = [](const QString &str) -> QString
|
const auto chopPathSep = [](const QString &str) -> QString
|
||||||
{
|
{
|
||||||
@@ -328,21 +320,74 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
|
|||||||
return str.mid(0, (str.length() -1));
|
return str.mid(0, (str.length() -1));
|
||||||
return str;
|
return str;
|
||||||
};
|
};
|
||||||
program.replace("%F", chopPathSep(Utils::Fs::toNativePath(torrent->contentPath())));
|
|
||||||
program.replace("%R", chopPathSep(Utils::Fs::toNativePath(torrent->rootPath())));
|
|
||||||
program.replace("%D", chopPathSep(Utils::Fs::toNativePath(torrent->savePath())));
|
|
||||||
#else
|
|
||||||
program.replace("%F", Utils::Fs::toNativePath(torrent->contentPath()));
|
|
||||||
program.replace("%R", Utils::Fs::toNativePath(torrent->rootPath()));
|
|
||||||
program.replace("%D", Utils::Fs::toNativePath(torrent->savePath()));
|
|
||||||
#endif
|
#endif
|
||||||
program.replace("%C", QString::number(torrent->filesCount()));
|
|
||||||
program.replace("%Z", QString::number(torrent->totalSize()));
|
|
||||||
program.replace("%T", torrent->currentTracker());
|
|
||||||
program.replace("%I", torrent->hash());
|
|
||||||
|
|
||||||
Logger *logger = Logger::instance();
|
QString program = Preferences::instance()->getAutoRunProgram().trimmed();
|
||||||
logger->addMessage(tr("Torrent: %1, running external program, command: %2").arg(torrent->name(), program));
|
|
||||||
|
for (int i = (program.length() - 2); i >= 0; --i)
|
||||||
|
{
|
||||||
|
if (program[i] != QLatin1Char('%'))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const ushort specifier = program[i + 1].unicode();
|
||||||
|
switch (specifier)
|
||||||
|
{
|
||||||
|
case u'C':
|
||||||
|
program.replace(i, 2, QString::number(torrent->filesCount()));
|
||||||
|
break;
|
||||||
|
case u'D':
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->savePath())));
|
||||||
|
#else
|
||||||
|
program.replace(i, 2, Utils::Fs::toNativePath(torrent->savePath()));
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
case u'F':
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->contentPath())));
|
||||||
|
#else
|
||||||
|
program.replace(i, 2, Utils::Fs::toNativePath(torrent->contentPath()));
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
case u'G':
|
||||||
|
{
|
||||||
|
QStringList tags = torrent->tags().values();
|
||||||
|
std::sort(tags.begin(), tags.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
|
||||||
|
program.replace(i, 2, tags.join(','));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case u'I':
|
||||||
|
program.replace(i, 2, torrent->id().toString());
|
||||||
|
break;
|
||||||
|
case u'L':
|
||||||
|
program.replace(i, 2, torrent->category());
|
||||||
|
break;
|
||||||
|
case u'N':
|
||||||
|
program.replace(i, 2, torrent->name());
|
||||||
|
break;
|
||||||
|
case u'R':
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->rootPath())));
|
||||||
|
#else
|
||||||
|
program.replace(i, 2, Utils::Fs::toNativePath(torrent->rootPath()));
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
case u'T':
|
||||||
|
program.replace(i, 2, torrent->currentTracker());
|
||||||
|
break;
|
||||||
|
case u'Z':
|
||||||
|
program.replace(i, 2, QString::number(torrent->totalSize()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// do nothing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrement `i` to avoid unwanted replacement, example pattern: "%%N"
|
||||||
|
--i;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogMsg(tr("Torrent: %1, running external program, command: %2").arg(torrent->name(), program));
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
auto programWchar = std::make_unique<wchar_t[]>(program.length() + 1);
|
auto programWchar = std::make_unique<wchar_t[]>(program.length() + 1);
|
||||||
@@ -358,7 +403,6 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
|
|||||||
for (int i = 1; i < argCount; ++i)
|
for (int i = 1; i < argCount; ++i)
|
||||||
argList += QString::fromWCharArray(args[i]);
|
argList += QString::fromWCharArray(args[i]);
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
QProcess proc;
|
QProcess proc;
|
||||||
proc.setProgram(QString::fromWCharArray(args[0]));
|
proc.setProgram(QString::fromWCharArray(args[0]));
|
||||||
proc.setArguments(argList);
|
proc.setArguments(argList);
|
||||||
@@ -384,9 +428,6 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
|
|||||||
args->startupInfo->hStdError = nullptr;
|
args->startupInfo->hStdError = nullptr;
|
||||||
});
|
});
|
||||||
proc.startDetached();
|
proc.startDetached();
|
||||||
#else
|
|
||||||
QProcess::startDetached(QString::fromWCharArray(args[0]), argList);
|
|
||||||
#endif // QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
|
||||||
#else // Q_OS_WIN
|
#else // Q_OS_WIN
|
||||||
// Cannot give users shell environment by default, as doing so could
|
// Cannot give users shell environment by default, as doing so could
|
||||||
// enable command injection via torrent name and other arguments
|
// enable command injection via torrent name and other arguments
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ int main(int argc, char *argv[])
|
|||||||
// 3. https://bugreports.qt.io/browse/QTBUG-46015
|
// 3. https://bugreports.qt.io/browse/QTBUG-46015
|
||||||
|
|
||||||
qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
|
qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) && !defined(DISABLE_GUI)
|
#if !defined(DISABLE_GUI)
|
||||||
// this is the default in Qt6
|
// this is the default in Qt6
|
||||||
app->setAttribute(Qt::AA_DisableWindowContextHelpButton);
|
app->setAttribute(Qt::AA_DisableWindowContextHelpButton);
|
||||||
#endif
|
#endif
|
||||||
@@ -251,6 +251,9 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
// On OS X the standard is to not show icons in the menus
|
// On OS X the standard is to not show icons in the menus
|
||||||
app->setAttribute(Qt::AA_DontShowIconsInMenus);
|
app->setAttribute(Qt::AA_DontShowIconsInMenus);
|
||||||
|
#else
|
||||||
|
if (!Preferences::instance()->iconsInMenusEnabled())
|
||||||
|
app->setAttribute(Qt::AA_DontShowIconsInMenus);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!firstTimeUser)
|
if (!firstTimeUser)
|
||||||
@@ -378,11 +381,7 @@ void showSplashScreen()
|
|||||||
const QString version = QBT_VERSION;
|
const QString version = QBT_VERSION;
|
||||||
painter.setPen(QPen(Qt::white));
|
painter.setPen(QPen(Qt::white));
|
||||||
painter.setFont(QFont("Arial", 22, QFont::Black));
|
painter.setFont(QFont("Arial", 22, QFont::Black));
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
|
||||||
painter.drawText(224 - painter.fontMetrics().horizontalAdvance(version), 270, version);
|
painter.drawText(224 - painter.fontMetrics().horizontalAdvance(version), 270, version);
|
||||||
#else
|
|
||||||
painter.drawText(224 - painter.fontMetrics().width(version), 270, version);
|
|
||||||
#endif
|
|
||||||
QSplashScreen *splash = new QSplashScreen(splashImg);
|
QSplashScreen *splash = new QSplashScreen(splashImg);
|
||||||
splash->show();
|
splash->show();
|
||||||
QTimer::singleShot(1500, splash, &QObject::deleteLater);
|
QTimer::singleShot(1500, splash, &QObject::deleteLater);
|
||||||
|
|||||||
@@ -79,6 +79,7 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
|
|||||||
#endif
|
#endif
|
||||||
prefix = id.section(QLatin1Char('/'), -1);
|
prefix = id.section(QLatin1Char('/'), -1);
|
||||||
}
|
}
|
||||||
prefix.remove(QRegExp("[^a-zA-Z]"));
|
prefix.remove(QRegularExpression("[^a-zA-Z]"));
|
||||||
prefix.truncate(6);
|
prefix.truncate(6);
|
||||||
|
|
||||||
QByteArray idc = id.toUtf8();
|
QByteArray idc = id.toUtf8();
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <dbghelp.h>
|
#include <dbghelp.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
#ifdef __MINGW32__
|
#ifdef __MINGW32__
|
||||||
@@ -256,7 +257,7 @@ const QString straceWin::getBacktrace()
|
|||||||
|
|
||||||
HANDLE hProcess = GetCurrentProcess();
|
HANDLE hProcess = GetCurrentProcess();
|
||||||
HANDLE hThread = GetCurrentThread();
|
HANDLE hThread = GetCurrentThread();
|
||||||
SymInitialize(hProcess, NULL, TRUE);
|
SymInitializeW(hProcess, QCoreApplication::applicationDirPath().toStdWString().c_str(), TRUE);
|
||||||
|
|
||||||
DWORD64 dwDisplacement;
|
DWORD64 dwDisplacement;
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ add_library(qbt_base STATIC
|
|||||||
bittorrent/torrentinfo.h
|
bittorrent/torrentinfo.h
|
||||||
bittorrent/tracker.h
|
bittorrent/tracker.h
|
||||||
bittorrent/trackerentry.h
|
bittorrent/trackerentry.h
|
||||||
|
digest32.h
|
||||||
exceptions.h
|
exceptions.h
|
||||||
filesystemwatcher.h
|
filesystemwatcher.h
|
||||||
global.h
|
global.h
|
||||||
|
|||||||
@@ -55,13 +55,8 @@ AsyncFileStorage::~AsyncFileStorage()
|
|||||||
|
|
||||||
void AsyncFileStorage::store(const QString &fileName, const QByteArray &data)
|
void AsyncFileStorage::store(const QString &fileName, const QByteArray &data)
|
||||||
{
|
{
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
QMetaObject::invokeMethod(this, [this, data, fileName]() { store_impl(fileName, data); }
|
QMetaObject::invokeMethod(this, [this, data, fileName]() { store_impl(fileName, data); }
|
||||||
, Qt::QueuedConnection);
|
, Qt::QueuedConnection);
|
||||||
#else
|
|
||||||
QMetaObject::invokeMethod(this, "store_impl", Qt::QueuedConnection
|
|
||||||
, Q_ARG(QString, fileName), Q_ARG(QByteArray, data));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir AsyncFileStorage::storageDir() const
|
QDir AsyncFileStorage::storageDir() const
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ HEADERS += \
|
|||||||
$$PWD/bittorrent/torrentinfo.h \
|
$$PWD/bittorrent/torrentinfo.h \
|
||||||
$$PWD/bittorrent/tracker.h \
|
$$PWD/bittorrent/tracker.h \
|
||||||
$$PWD/bittorrent/trackerentry.h \
|
$$PWD/bittorrent/trackerentry.h \
|
||||||
|
$$PWD/digest32.h \
|
||||||
$$PWD/exceptions.h \
|
$$PWD/exceptions.h \
|
||||||
$$PWD/filesystemwatcher.h \
|
$$PWD/filesystemwatcher.h \
|
||||||
$$PWD/global.h \
|
$$PWD/global.h \
|
||||||
|
|||||||
@@ -78,8 +78,7 @@ void BitTorrent::AbstractFileStorage::renameFile(const QString &oldPath, const Q
|
|||||||
|
|
||||||
if ((renamingFileIndex < 0) && areSameFileNames(path, oldFilePath))
|
if ((renamingFileIndex < 0) && areSameFileNames(path, oldFilePath))
|
||||||
renamingFileIndex = i;
|
renamingFileIndex = i;
|
||||||
|
else if (areSameFileNames(path, newFilePath))
|
||||||
if (areSameFileNames(path, newFilePath))
|
|
||||||
throw RuntimeError {tr("The file already exists: '%1'.").arg(newFilePath)};
|
throw RuntimeError {tr("The file already exists: '%1'.").arg(newFilePath)};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,8 +123,7 @@ void BitTorrent::AbstractFileStorage::renameFolder(const QString &oldPath, const
|
|||||||
|
|
||||||
if (path.startsWith(oldFolderPath, CASE_SENSITIVITY))
|
if (path.startsWith(oldFolderPath, CASE_SENSITIVITY))
|
||||||
renamingFileIndexes.append(i);
|
renamingFileIndexes.append(i);
|
||||||
|
else if (path.startsWith(newFolderPath, CASE_SENSITIVITY))
|
||||||
if (path.startsWith(newFolderPath, CASE_SENSITIVITY))
|
|
||||||
throw RuntimeError {tr("The folder already exists: '%1'.").arg(newFolderPath)};
|
throw RuntimeError {tr("The folder already exists: '%1'.").arg(newFolderPath)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
#include "base/bittorrent/common.h"
|
#include "base/bittorrent/common.h"
|
||||||
#include "base/bittorrent/infohash.h"
|
#include "base/bittorrent/infohash.h"
|
||||||
|
|
||||||
void FileSearcher::search(const BitTorrent::InfoHash &id, const QStringList &originalFileNames
|
void FileSearcher::search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames
|
||||||
, const QString &completeSavePath, const QString &incompleteSavePath)
|
, const QString &completeSavePath, const QString &incompleteSavePath)
|
||||||
{
|
{
|
||||||
const auto findInDir = [](const QString &dirPath, QStringList &fileNames) -> bool
|
const auto findInDir = [](const QString &dirPath, QStringList &fileNames) -> bool
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
class InfoHash;
|
class TorrentID;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileSearcher final : public QObject
|
class FileSearcher final : public QObject
|
||||||
@@ -44,9 +44,9 @@ public:
|
|||||||
FileSearcher() = default;
|
FileSearcher() = default;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void search(const BitTorrent::InfoHash &id, const QStringList &originalFileNames
|
void search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames
|
||||||
, const QString &completeSavePath, const QString &incompleteSavePath);
|
, const QString &completeSavePath, const QString &incompleteSavePath);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void searchFinished(const BitTorrent::InfoHash &id, const QString &savePath, const QStringList &fileNames);
|
void searchFinished(const BitTorrent::TorrentID &id, const QString &savePath, const QStringList &fileNames);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2015, 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@@ -28,68 +28,54 @@
|
|||||||
|
|
||||||
#include "infohash.h"
|
#include "infohash.h"
|
||||||
|
|
||||||
#include <QByteArray>
|
const int TorrentIDTypeId = qRegisterMetaType<BitTorrent::TorrentID>();
|
||||||
#include <QHash>
|
|
||||||
|
|
||||||
using namespace BitTorrent;
|
BitTorrent::InfoHash::InfoHash(const WrappedType &nativeHash)
|
||||||
|
: m_valid {true}
|
||||||
const int InfoHashTypeId = qRegisterMetaType<InfoHash>();
|
, m_nativeHash {nativeHash}
|
||||||
|
|
||||||
InfoHash::InfoHash(const lt::sha1_hash &nativeHash)
|
|
||||||
: m_valid(true)
|
|
||||||
, m_nativeHash(nativeHash)
|
|
||||||
{
|
{
|
||||||
const QByteArray raw = QByteArray::fromRawData(nativeHash.data(), length());
|
|
||||||
m_hashString = QString::fromLatin1(raw.toHex());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoHash::InfoHash(const QString &hashString)
|
bool BitTorrent::InfoHash::isValid() const
|
||||||
: m_valid(false)
|
|
||||||
{
|
|
||||||
if (hashString.size() != (length() * 2))
|
|
||||||
return;
|
|
||||||
|
|
||||||
const QByteArray raw = QByteArray::fromHex(hashString.toLatin1());
|
|
||||||
if (raw.size() != length()) // QByteArray::fromHex() will skip over invalid characters
|
|
||||||
return;
|
|
||||||
|
|
||||||
m_valid = true;
|
|
||||||
m_hashString = hashString;
|
|
||||||
m_nativeHash.assign(raw.constData());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InfoHash::isValid() const
|
|
||||||
{
|
{
|
||||||
return m_valid;
|
return m_valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoHash::operator lt::sha1_hash() const
|
BitTorrent::TorrentID BitTorrent::InfoHash::toTorrentID() const
|
||||||
|
{
|
||||||
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
|
return m_nativeHash.get_best();
|
||||||
|
#else
|
||||||
|
return {m_nativeHash};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
BitTorrent::InfoHash::operator WrappedType() const
|
||||||
{
|
{
|
||||||
return m_nativeHash;
|
return m_nativeHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoHash::operator QString() const
|
BitTorrent::TorrentID BitTorrent::TorrentID::fromString(const QString &hashString)
|
||||||
{
|
{
|
||||||
return m_hashString;
|
return {BaseType::fromString(hashString)};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BitTorrent::operator==(const InfoHash &left, const InfoHash &right)
|
BitTorrent::TorrentID BitTorrent::TorrentID::fromInfoHash(const BitTorrent::InfoHash &infoHash)
|
||||||
{
|
{
|
||||||
return (static_cast<lt::sha1_hash>(left)
|
return infoHash.toTorrentID();
|
||||||
== static_cast<lt::sha1_hash>(right));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BitTorrent::operator!=(const InfoHash &left, const InfoHash &right)
|
uint BitTorrent::qHash(const BitTorrent::TorrentID &key, const uint seed)
|
||||||
|
{
|
||||||
|
return ::qHash(std::hash<TorrentID::UnderlyingType>()(key), seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BitTorrent::operator==(const BitTorrent::InfoHash &left, const BitTorrent::InfoHash &right)
|
||||||
|
{
|
||||||
|
return (static_cast<InfoHash::WrappedType>(left) == static_cast<InfoHash::WrappedType>(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BitTorrent::operator!=(const BitTorrent::InfoHash &left, const BitTorrent::InfoHash &right)
|
||||||
{
|
{
|
||||||
return !(left == right);
|
return !(left == right);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BitTorrent::operator<(const InfoHash &left, const InfoHash &right)
|
|
||||||
{
|
|
||||||
return static_cast<lt::sha1_hash>(left) < static_cast<lt::sha1_hash>(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint BitTorrent::qHash(const InfoHash &key, const uint seed)
|
|
||||||
{
|
|
||||||
return ::qHash((std::hash<lt::sha1_hash> {})(key), seed);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2015, 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@@ -28,41 +28,60 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <libtorrent/sha1_hash.hpp>
|
#include <libtorrent/version.hpp>
|
||||||
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
|
#include <libtorrent/info_hash.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QString>
|
|
||||||
|
#include "base/digest32.h"
|
||||||
|
|
||||||
|
using SHA1Hash = Digest32<160>;
|
||||||
|
using SHA256Hash = Digest32<256>;
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
|
class InfoHash;
|
||||||
|
|
||||||
|
class TorrentID : public Digest32<160>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using BaseType = Digest32<160>;
|
||||||
|
using BaseType::BaseType;
|
||||||
|
|
||||||
|
static TorrentID fromString(const QString &hashString);
|
||||||
|
static TorrentID fromInfoHash(const InfoHash &infoHash);
|
||||||
|
};
|
||||||
|
|
||||||
class InfoHash
|
class InfoHash
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
InfoHash() = default;
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
InfoHash(const lt::sha1_hash &nativeHash);
|
using WrappedType = lt::info_hash_t;
|
||||||
InfoHash(const QString &hashString);
|
#else
|
||||||
InfoHash(const InfoHash &other) = default;
|
using WrappedType = lt::sha1_hash;
|
||||||
|
#endif
|
||||||
|
|
||||||
static constexpr int length()
|
InfoHash() = default;
|
||||||
{
|
InfoHash(const InfoHash &other) = default;
|
||||||
return lt::sha1_hash::size();
|
InfoHash(const WrappedType &nativeHash);
|
||||||
}
|
|
||||||
|
|
||||||
bool isValid() const;
|
bool isValid() const;
|
||||||
|
TorrentID toTorrentID() const;
|
||||||
|
|
||||||
operator lt::sha1_hash() const;
|
operator WrappedType() const;
|
||||||
operator QString() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_valid = false;
|
bool m_valid = false;
|
||||||
lt::sha1_hash m_nativeHash;
|
WrappedType m_nativeHash;
|
||||||
QString m_hashString;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
uint qHash(const TorrentID &key, uint seed);
|
||||||
|
|
||||||
bool operator==(const InfoHash &left, const InfoHash &right);
|
bool operator==(const InfoHash &left, const InfoHash &right);
|
||||||
bool operator!=(const InfoHash &left, const InfoHash &right);
|
bool operator!=(const InfoHash &left, const InfoHash &right);
|
||||||
bool operator<(const InfoHash &left, const InfoHash &right);
|
|
||||||
uint qHash(const InfoHash &key, uint seed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(BitTorrent::InfoHash)
|
Q_DECLARE_METATYPE(BitTorrent::TorrentID)
|
||||||
|
|||||||
@@ -40,15 +40,15 @@
|
|||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
bool isBitTorrentInfoHash(const QString &string)
|
bool isSHA1Hash(const QString &string)
|
||||||
{
|
{
|
||||||
// There are 2 representations for BitTorrent info hash:
|
// There are 2 representations for BitTorrent info hash:
|
||||||
// 1. 40 chars hex-encoded string
|
// 1. 40 chars hex-encoded string
|
||||||
// == 20 (SHA-1 length in bytes) * 2 (each byte maps to 2 hex characters)
|
// == 20 (SHA-1 length in bytes) * 2 (each byte maps to 2 hex characters)
|
||||||
// 2. 32 chars Base32 encoded string
|
// 2. 32 chars Base32 encoded string
|
||||||
// == 20 (SHA-1 length in bytes) * 1.6 (the efficiency of Base32 encoding)
|
// == 20 (SHA-1 length in bytes) * 1.6 (the efficiency of Base32 encoding)
|
||||||
const int SHA1_HEX_SIZE = BitTorrent::InfoHash::length() * 2;
|
const int SHA1_HEX_SIZE = SHA1Hash::length() * 2;
|
||||||
const int SHA1_BASE32_SIZE = BitTorrent::InfoHash::length() * 1.6;
|
const int SHA1_BASE32_SIZE = SHA1Hash::length() * 1.6;
|
||||||
|
|
||||||
return ((((string.size() == SHA1_HEX_SIZE))
|
return ((((string.size() == SHA1_HEX_SIZE))
|
||||||
&& !string.contains(QRegularExpression(QLatin1String("[^0-9A-Fa-f]"))))
|
&& !string.contains(QRegularExpression(QLatin1String("[^0-9A-Fa-f]"))))
|
||||||
@@ -65,7 +65,7 @@ MagnetUri::MagnetUri(const QString &source)
|
|||||||
{
|
{
|
||||||
if (source.isEmpty()) return;
|
if (source.isEmpty()) return;
|
||||||
|
|
||||||
if (isBitTorrentInfoHash(source))
|
if (isSHA1Hash(source))
|
||||||
m_url = QLatin1String("magnet:?xt=urn:btih:") + source;
|
m_url = QLatin1String("magnet:?xt=urn:btih:") + source;
|
||||||
|
|
||||||
lt::error_code ec;
|
lt::error_code ec;
|
||||||
@@ -73,12 +73,18 @@ MagnetUri::MagnetUri(const QString &source)
|
|||||||
if (ec) return;
|
if (ec) return;
|
||||||
|
|
||||||
m_valid = true;
|
m_valid = true;
|
||||||
m_hash = m_addTorrentParams.info_hash;
|
|
||||||
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
|
m_infoHash = m_addTorrentParams.info_hashes;
|
||||||
|
#else
|
||||||
|
m_infoHash = m_addTorrentParams.info_hash;
|
||||||
|
#endif
|
||||||
|
|
||||||
m_name = QString::fromStdString(m_addTorrentParams.name);
|
m_name = QString::fromStdString(m_addTorrentParams.name);
|
||||||
|
|
||||||
m_trackers.reserve(m_addTorrentParams.trackers.size());
|
m_trackers.reserve(m_addTorrentParams.trackers.size());
|
||||||
for (const std::string &tracker : m_addTorrentParams.trackers)
|
for (const std::string &tracker : m_addTorrentParams.trackers)
|
||||||
m_trackers.append(lt::announce_entry {tracker});
|
m_trackers.append({QString::fromStdString(tracker)});
|
||||||
|
|
||||||
m_urlSeeds.reserve(m_addTorrentParams.url_seeds.size());
|
m_urlSeeds.reserve(m_addTorrentParams.url_seeds.size());
|
||||||
for (const std::string &urlSeed : m_addTorrentParams.url_seeds)
|
for (const std::string &urlSeed : m_addTorrentParams.url_seeds)
|
||||||
@@ -90,9 +96,9 @@ bool MagnetUri::isValid() const
|
|||||||
return m_valid;
|
return m_valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoHash MagnetUri::hash() const
|
InfoHash MagnetUri::infoHash() const
|
||||||
{
|
{
|
||||||
return m_hash;
|
return m_infoHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MagnetUri::name() const
|
QString MagnetUri::name() const
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace BitTorrent
|
|||||||
explicit MagnetUri(const QString &source = {});
|
explicit MagnetUri(const QString &source = {});
|
||||||
|
|
||||||
bool isValid() const;
|
bool isValid() const;
|
||||||
InfoHash hash() const;
|
InfoHash infoHash() const;
|
||||||
QString name() const;
|
QString name() const;
|
||||||
QVector<TrackerEntry> trackers() const;
|
QVector<TrackerEntry> trackers() const;
|
||||||
QVector<QUrl> urlSeeds() const;
|
QVector<QUrl> urlSeeds() const;
|
||||||
@@ -57,7 +57,7 @@ namespace BitTorrent
|
|||||||
private:
|
private:
|
||||||
bool m_valid;
|
bool m_valid;
|
||||||
QString m_url;
|
QString m_url;
|
||||||
InfoHash m_hash;
|
InfoHash m_infoHash;
|
||||||
QString m_name;
|
QString m_name;
|
||||||
QVector<TrackerEntry> m_trackers;
|
QVector<TrackerEntry> m_trackers;
|
||||||
QVector<QUrl> m_urlSeeds;
|
QVector<QUrl> m_urlSeeds;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
#include <libtorrent/alert_types.hpp>
|
#include <libtorrent/alert_types.hpp>
|
||||||
#include <libtorrent/bdecode.hpp>
|
#include <libtorrent/bdecode.hpp>
|
||||||
#include <libtorrent/bencode.hpp>
|
#include <libtorrent/bencode.hpp>
|
||||||
|
#include <libtorrent/entry.hpp>
|
||||||
#include <libtorrent/error_code.hpp>
|
#include <libtorrent/error_code.hpp>
|
||||||
#include <libtorrent/extensions/smart_ban.hpp>
|
#include <libtorrent/extensions/smart_ban.hpp>
|
||||||
#include <libtorrent/extensions/ut_metadata.hpp>
|
#include <libtorrent/extensions/ut_metadata.hpp>
|
||||||
@@ -55,6 +56,7 @@
|
|||||||
#include <libtorrent/session_stats.hpp>
|
#include <libtorrent/session_stats.hpp>
|
||||||
#include <libtorrent/session_status.hpp>
|
#include <libtorrent/session_status.hpp>
|
||||||
#include <libtorrent/torrent_info.hpp>
|
#include <libtorrent/torrent_info.hpp>
|
||||||
|
#include <libtorrent/write_resume_data.hpp>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
@@ -303,6 +305,17 @@ namespace
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using ListType = lt::entry::list_type;
|
||||||
|
|
||||||
|
ListType setToEntryList(const QSet<QString> &input)
|
||||||
|
{
|
||||||
|
ListType entryList;
|
||||||
|
entryList.reserve(input.size());
|
||||||
|
for (const QString &setValue : input)
|
||||||
|
entryList.emplace_back(setValue.toStdString());
|
||||||
|
return entryList;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
QString convertIfaceNameToGuid(const QString &name)
|
QString convertIfaceNameToGuid(const QString &name)
|
||||||
{
|
{
|
||||||
@@ -372,6 +385,7 @@ Session::Session(QObject *parent)
|
|||||||
, m_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0)
|
, m_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0)
|
||||||
, m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
|
, m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
|
||||||
, m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY("UPnPLeaseDuration"), 0)
|
, m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY("UPnPLeaseDuration"), 0)
|
||||||
|
, m_peerToS(BITTORRENT_SESSION_KEY("PeerToS"), 0x20)
|
||||||
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), false)
|
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), false)
|
||||||
, m_includeOverheadInLimits(BITTORRENT_SESSION_KEY("IncludeOverheadInLimits"), false)
|
, m_includeOverheadInLimits(BITTORRENT_SESSION_KEY("IncludeOverheadInLimits"), false)
|
||||||
, m_announceIP(BITTORRENT_SESSION_KEY("AnnounceIP"))
|
, m_announceIP(BITTORRENT_SESSION_KEY("AnnounceIP"))
|
||||||
@@ -388,7 +402,7 @@ Session::Session(QObject *parent)
|
|||||||
, clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
|
, clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
|
||||||
, m_IDNSupportEnabled(BITTORRENT_SESSION_KEY("IDNSupportEnabled"), false)
|
, m_IDNSupportEnabled(BITTORRENT_SESSION_KEY("IDNSupportEnabled"), false)
|
||||||
, m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY("MultiConnectionsPerIp"), false)
|
, m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY("MultiConnectionsPerIp"), false)
|
||||||
, m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY("ValidateHTTPSTrackerCertificate"), false)
|
, m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY("ValidateHTTPSTrackerCertificate"), true)
|
||||||
, m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY("BlockPeersOnPrivilegedPorts"), false)
|
, m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY("BlockPeersOnPrivilegedPorts"), false)
|
||||||
, m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false)
|
, m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false)
|
||||||
, m_additionalTrackers(BITTORRENT_SESSION_KEY("AdditionalTrackers"))
|
, m_additionalTrackers(BITTORRENT_SESSION_KEY("AdditionalTrackers"))
|
||||||
@@ -1110,11 +1124,7 @@ void Session::initializeNativeSession()
|
|||||||
|
|
||||||
m_nativeSession->set_alert_notify([this]()
|
m_nativeSession->set_alert_notify([this]()
|
||||||
{
|
{
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
QMetaObject::invokeMethod(this, &Session::readAlerts, Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &Session::readAlerts, Qt::QueuedConnection);
|
||||||
#else
|
|
||||||
QMetaObject::invokeMethod(this, "readAlerts", Qt::QueuedConnection);
|
|
||||||
#endif
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Enabling plugins
|
// Enabling plugins
|
||||||
@@ -1335,9 +1345,10 @@ void Session::loadLTSettings(lt::settings_pack &settingsPack)
|
|||||||
// Outgoing ports
|
// Outgoing ports
|
||||||
settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
|
settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
|
||||||
settingsPack.set_int(lt::settings_pack::num_outgoing_ports, outgoingPortsMax() - outgoingPortsMin() + 1);
|
settingsPack.set_int(lt::settings_pack::num_outgoing_ports, outgoingPortsMax() - outgoingPortsMin() + 1);
|
||||||
|
// UPnP lease duration
|
||||||
settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
|
settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
|
||||||
|
// Type of service
|
||||||
|
settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
|
||||||
// Include overhead in transfer limits
|
// Include overhead in transfer limits
|
||||||
settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
|
settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
|
||||||
// IP address to announce to trackers
|
// IP address to announce to trackers
|
||||||
@@ -1387,15 +1398,11 @@ void Session::loadLTSettings(lt::settings_pack &settingsPack)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAS_IDN_SUPPORT
|
|
||||||
settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
|
settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
|
||||||
#endif
|
|
||||||
|
|
||||||
settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
|
settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
|
||||||
|
|
||||||
#ifdef HAS_HTTPS_TRACKER_VALIDATION
|
|
||||||
settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
|
settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
|
||||||
#endif
|
|
||||||
|
|
||||||
settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
|
settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
|
||||||
|
|
||||||
@@ -1450,12 +1457,14 @@ void Session::configureNetworkInterfaces(lt::settings_pack &settingsPack)
|
|||||||
const QHostAddress addr {ip};
|
const QHostAddress addr {ip};
|
||||||
if (!addr.isNull())
|
if (!addr.isNull())
|
||||||
{
|
{
|
||||||
const QString ip = ((addr.protocol() == QAbstractSocket::IPv6Protocol)
|
const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
|
||||||
? ('[' + Utils::Net::canonicalIPv6Addr(addr).toString() + ']')
|
const QString ip = isIPv6
|
||||||
: addr.toString());
|
? Utils::Net::canonicalIPv6Addr(addr).toString()
|
||||||
endpoints << (ip + portString);
|
: addr.toString();
|
||||||
|
|
||||||
if ((ip != "0.0.0.0") && (ip != "[::]"))
|
endpoints << ((isIPv6 ? ('[' + ip + ']') : ip) + portString);
|
||||||
|
|
||||||
|
if ((ip != QLatin1String("0.0.0.0")) && (ip != QLatin1String("::")))
|
||||||
outgoingInterfaces << ip;
|
outgoingInterfaces << ip;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1608,7 +1617,7 @@ void Session::populateAdditionalTrackers()
|
|||||||
{
|
{
|
||||||
tracker = tracker.trimmed();
|
tracker = tracker.trimmed();
|
||||||
if (!tracker.isEmpty())
|
if (!tracker.isEmpty())
|
||||||
m_additionalTrackerList << tracker.toString();
|
m_additionalTrackerList.append({tracker.toString()});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1618,7 +1627,7 @@ void Session::processShareLimits()
|
|||||||
|
|
||||||
// We shouldn't iterate over `m_torrents` in the loop below
|
// We shouldn't iterate over `m_torrents` in the loop below
|
||||||
// since `deleteTorrent()` modifies it indirectly
|
// since `deleteTorrent()` modifies it indirectly
|
||||||
const QHash<InfoHash, TorrentImpl *> torrents {m_torrents};
|
const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
|
||||||
for (TorrentImpl *const torrent : torrents)
|
for (TorrentImpl *const torrent : torrents)
|
||||||
{
|
{
|
||||||
if (torrent->isSeed() && !torrent->isForced())
|
if (torrent->isSeed() && !torrent->isForced())
|
||||||
@@ -1640,12 +1649,12 @@ void Session::processShareLimits()
|
|||||||
if (m_maxRatioAction == Remove)
|
if (m_maxRatioAction == Remove)
|
||||||
{
|
{
|
||||||
LogMsg(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name()));
|
LogMsg(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name()));
|
||||||
deleteTorrent(torrent->hash());
|
deleteTorrent(torrent->id());
|
||||||
}
|
}
|
||||||
else if (m_maxRatioAction == DeleteFiles)
|
else if (m_maxRatioAction == DeleteFiles)
|
||||||
{
|
{
|
||||||
LogMsg(tr("'%1' reached the maximum ratio you set. Removed torrent and its files.").arg(torrent->name()));
|
LogMsg(tr("'%1' reached the maximum ratio you set. Removed torrent and its files.").arg(torrent->name()));
|
||||||
deleteTorrent(torrent->hash(), DeleteTorrentAndFiles);
|
deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
|
||||||
}
|
}
|
||||||
else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
|
else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
|
||||||
{
|
{
|
||||||
@@ -1679,12 +1688,12 @@ void Session::processShareLimits()
|
|||||||
if (m_maxRatioAction == Remove)
|
if (m_maxRatioAction == Remove)
|
||||||
{
|
{
|
||||||
LogMsg(tr("'%1' reached the maximum seeding time you set. Removed.").arg(torrent->name()));
|
LogMsg(tr("'%1' reached the maximum seeding time you set. Removed.").arg(torrent->name()));
|
||||||
deleteTorrent(torrent->hash());
|
deleteTorrent(torrent->id());
|
||||||
}
|
}
|
||||||
else if (m_maxRatioAction == DeleteFiles)
|
else if (m_maxRatioAction == DeleteFiles)
|
||||||
{
|
{
|
||||||
LogMsg(tr("'%1' reached the maximum seeding time you set. Removed torrent and its files.").arg(torrent->name()));
|
LogMsg(tr("'%1' reached the maximum seeding time you set. Removed torrent and its files.").arg(torrent->name()));
|
||||||
deleteTorrent(torrent->hash(), DeleteTorrentAndFiles);
|
deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
|
||||||
}
|
}
|
||||||
else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
|
else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
|
||||||
{
|
{
|
||||||
@@ -1721,7 +1730,7 @@ void Session::handleDownloadFinished(const Net::DownloadResult &result)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::fileSearchFinished(const InfoHash &id, const QString &savePath, const QStringList &fileNames)
|
void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames)
|
||||||
{
|
{
|
||||||
TorrentImpl *torrent = m_torrents.value(id);
|
TorrentImpl *torrent = m_torrents.value(id);
|
||||||
if (torrent)
|
if (torrent)
|
||||||
@@ -1747,9 +1756,9 @@ void Session::fileSearchFinished(const InfoHash &id, const QString &savePath, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return the torrent handle, given its hash
|
// Return the torrent handle, given its hash
|
||||||
Torrent *Session::findTorrent(const InfoHash &hash) const
|
Torrent *Session::findTorrent(const TorrentID &id) const
|
||||||
{
|
{
|
||||||
return m_torrents.value(hash);
|
return m_torrents.value(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Session::hasActiveTorrents() const
|
bool Session::hasActiveTorrents() const
|
||||||
@@ -1764,7 +1773,7 @@ bool Session::hasUnfinishedTorrents() const
|
|||||||
{
|
{
|
||||||
return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
|
return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
|
||||||
{
|
{
|
||||||
return (!torrent->isSeed() && !torrent->isPaused());
|
return (!torrent->isSeed() && !torrent->isPaused() && !torrent->isErrored());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1797,18 +1806,18 @@ void Session::banIP(const QString &ip)
|
|||||||
|
|
||||||
// Delete a torrent from the session, given its hash
|
// Delete a torrent from the session, given its hash
|
||||||
// and from the disk, if the corresponding deleteOption is chosen
|
// and from the disk, if the corresponding deleteOption is chosen
|
||||||
bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOption)
|
bool Session::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
|
||||||
{
|
{
|
||||||
TorrentImpl *const torrent = m_torrents.take(hash);
|
TorrentImpl *const torrent = m_torrents.take(id);
|
||||||
if (!torrent) return false;
|
if (!torrent) return false;
|
||||||
|
|
||||||
qDebug("Deleting torrent with hash: %s", qUtf8Printable(torrent->hash()));
|
qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
|
||||||
emit torrentAboutToBeRemoved(torrent);
|
emit torrentAboutToBeRemoved(torrent);
|
||||||
|
|
||||||
// Remove it from session
|
// Remove it from session
|
||||||
if (deleteOption == DeleteTorrent)
|
if (deleteOption == DeleteTorrent)
|
||||||
{
|
{
|
||||||
m_removingTorrents[torrent->hash()] = {torrent->name(), "", deleteOption};
|
m_removingTorrents[torrent->id()] = {torrent->name(), "", deleteOption};
|
||||||
|
|
||||||
const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
|
const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
|
||||||
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
|
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
|
||||||
@@ -1837,7 +1846,7 @@ bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOptio
|
|||||||
rootPath = torrent->actualStorageLocation();
|
rootPath = torrent->actualStorageLocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_removingTorrents[torrent->hash()] = {torrent->name(), rootPath, deleteOption};
|
m_removingTorrents[torrent->id()] = {torrent->name(), rootPath, deleteOption};
|
||||||
|
|
||||||
if (m_moveStorageQueue.size() > 1)
|
if (m_moveStorageQueue.size() > 1)
|
||||||
{
|
{
|
||||||
@@ -1856,48 +1865,44 @@ bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOptio
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove it from torrent resume directory
|
// Remove it from torrent resume directory
|
||||||
const QString resumedataFile = QString::fromLatin1("%1.fastresume").arg(torrent->hash());
|
const QString resumedataFile = QString::fromLatin1("%1.fastresume").arg(torrent->id().toString());
|
||||||
const QString metadataFile = QString::fromLatin1("%1.torrent").arg(torrent->hash());
|
const QString metadataFile = QString::fromLatin1("%1.torrent").arg(torrent->id().toString());
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, [this, resumedataFile, metadataFile]()
|
QMetaObject::invokeMethod(m_resumeDataSavingManager, [this, resumedataFile, metadataFile]()
|
||||||
{
|
{
|
||||||
m_resumeDataSavingManager->remove(resumedataFile);
|
m_resumeDataSavingManager->remove(resumedataFile);
|
||||||
m_resumeDataSavingManager->remove(metadataFile);
|
m_resumeDataSavingManager->remove(metadataFile);
|
||||||
});
|
});
|
||||||
#else
|
|
||||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, "remove", Q_ARG(QString, resumedataFile));
|
|
||||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, "remove", Q_ARG(QString, metadataFile));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
delete torrent;
|
delete torrent;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Session::cancelDownloadMetadata(const InfoHash &hash)
|
bool Session::cancelDownloadMetadata(const TorrentID &id)
|
||||||
{
|
{
|
||||||
const auto downloadedMetadataIter = m_downloadedMetadata.find(hash);
|
const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
|
||||||
if (downloadedMetadataIter == m_downloadedMetadata.end()) return false;
|
if (downloadedMetadataIter == m_downloadedMetadata.end()) return false;
|
||||||
|
|
||||||
m_downloadedMetadata.erase(downloadedMetadataIter);
|
m_downloadedMetadata.erase(downloadedMetadataIter);
|
||||||
--m_extraLimit;
|
--m_extraLimit;
|
||||||
adjustLimits();
|
adjustLimits();
|
||||||
m_nativeSession->remove_torrent(m_nativeSession->find_torrent(hash), lt::session::delete_files);
|
m_nativeSession->remove_torrent(m_nativeSession->find_torrent(id), lt::session::delete_files);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::increaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
void Session::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
|
||||||
{
|
{
|
||||||
using ElementType = std::pair<int, TorrentImpl *>;
|
using ElementType = std::pair<int, const TorrentImpl *>;
|
||||||
std::priority_queue<ElementType
|
std::priority_queue<ElementType
|
||||||
, std::vector<ElementType>
|
, std::vector<ElementType>
|
||||||
, std::greater<ElementType>> torrentQueue;
|
, std::greater<ElementType>> torrentQueue;
|
||||||
|
|
||||||
// Sort torrents by queue position
|
// Sort torrents by queue position
|
||||||
for (const InfoHash &infoHash : hashes)
|
for (const TorrentID &id : ids)
|
||||||
{
|
{
|
||||||
TorrentImpl *const torrent = m_torrents.value(infoHash);
|
const TorrentImpl *torrent = m_torrents.value(id);
|
||||||
if (torrent && !torrent->isSeed())
|
if (!torrent) continue;
|
||||||
torrentQueue.emplace(torrent->queuePosition(), torrent);
|
if (const int position = torrent->queuePosition(); position >= 0)
|
||||||
|
torrentQueue.emplace(position, torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increase torrents queue position (starting with the one in the highest queue position)
|
// Increase torrents queue position (starting with the one in the highest queue position)
|
||||||
@@ -1911,17 +1916,18 @@ void Session::increaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
|||||||
saveTorrentsQueue();
|
saveTorrentsQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::decreaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
void Session::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
|
||||||
{
|
{
|
||||||
using ElementType = std::pair<int, TorrentImpl *>;
|
using ElementType = std::pair<int, const TorrentImpl *>;
|
||||||
std::priority_queue<ElementType> torrentQueue;
|
std::priority_queue<ElementType> torrentQueue;
|
||||||
|
|
||||||
// Sort torrents by queue position
|
// Sort torrents by queue position
|
||||||
for (const InfoHash &infoHash : hashes)
|
for (const TorrentID &id : ids)
|
||||||
{
|
{
|
||||||
TorrentImpl *const torrent = m_torrents.value(infoHash);
|
const TorrentImpl *torrent = m_torrents.value(id);
|
||||||
if (torrent && !torrent->isSeed())
|
if (!torrent) continue;
|
||||||
torrentQueue.emplace(torrent->queuePosition(), torrent);
|
if (const int position = torrent->queuePosition(); position >= 0)
|
||||||
|
torrentQueue.emplace(position, torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrease torrents queue position (starting with the one in the lowest queue position)
|
// Decrease torrents queue position (starting with the one in the lowest queue position)
|
||||||
@@ -1938,17 +1944,18 @@ void Session::decreaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
|||||||
saveTorrentsQueue();
|
saveTorrentsQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::topTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
void Session::topTorrentsQueuePos(const QVector<TorrentID> &ids)
|
||||||
{
|
{
|
||||||
using ElementType = std::pair<int, TorrentImpl *>;
|
using ElementType = std::pair<int, const TorrentImpl *>;
|
||||||
std::priority_queue<ElementType> torrentQueue;
|
std::priority_queue<ElementType> torrentQueue;
|
||||||
|
|
||||||
// Sort torrents by queue position
|
// Sort torrents by queue position
|
||||||
for (const InfoHash &infoHash : hashes)
|
for (const TorrentID &id : ids)
|
||||||
{
|
{
|
||||||
TorrentImpl *const torrent = m_torrents.value(infoHash);
|
const TorrentImpl *torrent = m_torrents.value(id);
|
||||||
if (torrent && !torrent->isSeed())
|
if (!torrent) continue;
|
||||||
torrentQueue.emplace(torrent->queuePosition(), torrent);
|
if (const int position = torrent->queuePosition(); position >= 0)
|
||||||
|
torrentQueue.emplace(position, torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Top torrents queue position (starting with the one in the lowest queue position)
|
// Top torrents queue position (starting with the one in the lowest queue position)
|
||||||
@@ -1962,19 +1969,20 @@ void Session::topTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
|||||||
saveTorrentsQueue();
|
saveTorrentsQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::bottomTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
void Session::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
|
||||||
{
|
{
|
||||||
using ElementType = std::pair<int, TorrentImpl *>;
|
using ElementType = std::pair<int, const TorrentImpl *>;
|
||||||
std::priority_queue<ElementType
|
std::priority_queue<ElementType
|
||||||
, std::vector<ElementType>
|
, std::vector<ElementType>
|
||||||
, std::greater<ElementType>> torrentQueue;
|
, std::greater<ElementType>> torrentQueue;
|
||||||
|
|
||||||
// Sort torrents by queue position
|
// Sort torrents by queue position
|
||||||
for (const InfoHash &infoHash : hashes)
|
for (const TorrentID &id : ids)
|
||||||
{
|
{
|
||||||
TorrentImpl *const torrent = m_torrents.value(infoHash);
|
const TorrentImpl *torrent = m_torrents.value(id);
|
||||||
if (torrent && !torrent->isSeed())
|
if (!torrent) continue;
|
||||||
torrentQueue.emplace(torrent->queuePosition(), torrent);
|
if (const int position = torrent->queuePosition(); position >= 0)
|
||||||
|
torrentQueue.emplace(position, torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom torrents queue position (starting with the one in the highest queue position)
|
// Bottom torrents queue position (starting with the one in the highest queue position)
|
||||||
@@ -1991,6 +1999,25 @@ void Session::bottomTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
|||||||
saveTorrentsQueue();
|
saveTorrentsQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Session::handleTorrentNeedSaveResumeData(const TorrentImpl *torrent)
|
||||||
|
{
|
||||||
|
if (m_needSaveResumeDataTorrents.empty())
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, [this]()
|
||||||
|
{
|
||||||
|
for (const TorrentID &torrentID : asConst(m_needSaveResumeDataTorrents))
|
||||||
|
{
|
||||||
|
TorrentImpl *torrent = m_torrents.value(torrentID);
|
||||||
|
if (torrent)
|
||||||
|
torrent->saveResumeData();
|
||||||
|
}
|
||||||
|
m_needSaveResumeDataTorrents.clear();
|
||||||
|
}, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_needSaveResumeDataTorrents.insert(torrent->id());
|
||||||
|
}
|
||||||
|
|
||||||
void Session::handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent)
|
void Session::handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent)
|
||||||
{
|
{
|
||||||
qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
|
qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
|
||||||
@@ -2086,20 +2113,20 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
|
|||||||
const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
|
const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
|
||||||
TorrentInfo metadata = (hasMetadata ? std::get<TorrentInfo>(source) : TorrentInfo {});
|
TorrentInfo metadata = (hasMetadata ? std::get<TorrentInfo>(source) : TorrentInfo {});
|
||||||
const MagnetUri &magnetUri = (hasMetadata ? MagnetUri {} : std::get<MagnetUri>(source));
|
const MagnetUri &magnetUri = (hasMetadata ? MagnetUri {} : std::get<MagnetUri>(source));
|
||||||
const InfoHash hash = (hasMetadata ? metadata.hash() : magnetUri.hash());
|
const auto id = TorrentID::fromInfoHash(hasMetadata ? metadata.infoHash() : magnetUri.infoHash());
|
||||||
|
|
||||||
// It looks illogical that we don't just use an existing handle,
|
// It looks illogical that we don't just use an existing handle,
|
||||||
// but as previous experience has shown, it actually creates unnecessary
|
// but as previous experience has shown, it actually creates unnecessary
|
||||||
// problems and unwanted behavior due to the fact that it was originally
|
// problems and unwanted behavior due to the fact that it was originally
|
||||||
// added with parameters other than those provided by the user.
|
// added with parameters other than those provided by the user.
|
||||||
cancelDownloadMetadata(hash);
|
cancelDownloadMetadata(id);
|
||||||
|
|
||||||
// We should not add the torrent if it is already
|
// We should not add the torrent if it is already
|
||||||
// processed or is pending to add to session
|
// processed or is pending to add to session
|
||||||
if (m_loadingTorrents.contains(hash))
|
if (m_loadingTorrents.contains(id))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
TorrentImpl *const torrent = m_torrents.value(hash);
|
TorrentImpl *const torrent = m_torrents.value(id);
|
||||||
if (torrent)
|
if (torrent)
|
||||||
{ // a duplicate torrent is added
|
{ // a duplicate torrent is added
|
||||||
if (torrent->isPrivate() || (hasMetadata && metadata.isPrivate()))
|
if (torrent->isPrivate() || (hasMetadata && metadata.isPrivate()))
|
||||||
@@ -2190,7 +2217,7 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
|
|||||||
if (!isFindingIncompleteFiles)
|
if (!isFindingIncompleteFiles)
|
||||||
return loadTorrent(loadTorrentParams);
|
return loadTorrent(loadTorrentParams);
|
||||||
|
|
||||||
m_loadingTorrents.insert(hash, loadTorrentParams);
|
m_loadingTorrents.insert(id, loadTorrentParams);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2207,8 +2234,12 @@ bool Session::loadTorrent(LoadTorrentParams params)
|
|||||||
p.max_uploads = maxUploadsPerTorrent();
|
p.max_uploads = maxUploadsPerTorrent();
|
||||||
|
|
||||||
const bool hasMetadata = (p.ti && p.ti->is_valid());
|
const bool hasMetadata = (p.ti && p.ti->is_valid());
|
||||||
const InfoHash hash = (hasMetadata ? p.ti->info_hash() : p.info_hash);
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
m_loadingTorrents.insert(hash, params);
|
const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hashes() : p.info_hashes);
|
||||||
|
#else
|
||||||
|
const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hash() : p.info_hash);
|
||||||
|
#endif
|
||||||
|
m_loadingTorrents.insert(id, params);
|
||||||
|
|
||||||
// Adding torrent to BitTorrent session
|
// Adding torrent to BitTorrent session
|
||||||
m_nativeSession->async_add_torrent(p);
|
m_nativeSession->async_add_torrent(p);
|
||||||
@@ -2218,20 +2249,14 @@ bool Session::loadTorrent(LoadTorrentParams params)
|
|||||||
|
|
||||||
void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const
|
void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const
|
||||||
{
|
{
|
||||||
const InfoHash searchId = torrentInfo.hash();
|
const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
|
||||||
const QStringList originalFileNames = torrentInfo.filePaths();
|
const QStringList originalFileNames = torrentInfo.filePaths();
|
||||||
const QString completeSavePath = savePath;
|
const QString completeSavePath = savePath;
|
||||||
const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {});
|
const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {});
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
QMetaObject::invokeMethod(m_fileSearcher, [=]()
|
QMetaObject::invokeMethod(m_fileSearcher, [=]()
|
||||||
{
|
{
|
||||||
m_fileSearcher->search(searchId, originalFileNames, completeSavePath, incompleteSavePath);
|
m_fileSearcher->search(searchId, originalFileNames, completeSavePath, incompleteSavePath);
|
||||||
});
|
});
|
||||||
#else
|
|
||||||
QMetaObject::invokeMethod(m_fileSearcher, "search"
|
|
||||||
, Q_ARG(BitTorrent::InfoHash, searchId), Q_ARG(QStringList, originalFileNames)
|
|
||||||
, Q_ARG(QString, completeSavePath), Q_ARG(QString, incompleteSavePath));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a torrent to libtorrent session in hidden mode
|
// Add a torrent to libtorrent session in hidden mode
|
||||||
@@ -2240,17 +2265,17 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri)
|
|||||||
{
|
{
|
||||||
if (!magnetUri.isValid()) return false;
|
if (!magnetUri.isValid()) return false;
|
||||||
|
|
||||||
const InfoHash hash = magnetUri.hash();
|
const auto id = TorrentID::fromInfoHash(magnetUri.infoHash());
|
||||||
const QString name = magnetUri.name();
|
const QString name = magnetUri.name();
|
||||||
|
|
||||||
// We should not add torrent if it's already
|
// We should not add torrent if it's already
|
||||||
// processed or adding to session
|
// processed or adding to session
|
||||||
if (m_torrents.contains(hash)) return false;
|
if (m_torrents.contains(id)) return false;
|
||||||
if (m_loadingTorrents.contains(hash)) return false;
|
if (m_loadingTorrents.contains(id)) return false;
|
||||||
if (m_downloadedMetadata.contains(hash)) return false;
|
if (m_downloadedMetadata.contains(id)) return false;
|
||||||
|
|
||||||
qDebug("Adding torrent to preload metadata...");
|
qDebug("Adding torrent to preload metadata...");
|
||||||
qDebug(" -> Hash: %s", qUtf8Printable(hash));
|
qDebug(" -> Torrent ID: %s", qUtf8Printable(id.toString()));
|
||||||
qDebug(" -> Name: %s", qUtf8Printable(name));
|
qDebug(" -> Name: %s", qUtf8Printable(name));
|
||||||
|
|
||||||
lt::add_torrent_params p = magnetUri.addTorrentParams();
|
lt::add_torrent_params p = magnetUri.addTorrentParams();
|
||||||
@@ -2266,7 +2291,7 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri)
|
|||||||
p.max_connections = maxConnectionsPerTorrent();
|
p.max_connections = maxConnectionsPerTorrent();
|
||||||
p.max_uploads = maxUploadsPerTorrent();
|
p.max_uploads = maxUploadsPerTorrent();
|
||||||
|
|
||||||
const QString savePath = Utils::Fs::tempPath() + static_cast<QString>(hash);
|
const QString savePath = Utils::Fs::tempPath() + id.toString();
|
||||||
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
||||||
|
|
||||||
// Forced start
|
// Forced start
|
||||||
@@ -2299,7 +2324,7 @@ void Session::exportTorrentFile(const Torrent *torrent, TorrentExportFolder fold
|
|||||||
((folder == TorrentExportFolder::Finished) && !finishedTorrentExportDirectory().isEmpty()));
|
((folder == TorrentExportFolder::Finished) && !finishedTorrentExportDirectory().isEmpty()));
|
||||||
|
|
||||||
const QString validName = Utils::Fs::toValidFileSystemName(torrent->name());
|
const QString validName = Utils::Fs::toValidFileSystemName(torrent->name());
|
||||||
const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(torrent->hash());
|
const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(torrent->id().toString());
|
||||||
QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName);
|
QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName);
|
||||||
const QString torrentPath = QDir(m_resumeFolderPath).absoluteFilePath(torrentFilename);
|
const QString torrentPath = QDir(m_resumeFolderPath).absoluteFilePath(torrentFilename);
|
||||||
const QDir exportPath(folder == TorrentExportFolder::Regular ? torrentExportDirectory() : finishedTorrentExportDirectory());
|
const QDir exportPath(folder == TorrentExportFolder::Regular ? torrentExportDirectory() : finishedTorrentExportDirectory());
|
||||||
@@ -2326,7 +2351,10 @@ void Session::generateResumeData()
|
|||||||
if (!torrent->isValid()) continue;
|
if (!torrent->isValid()) continue;
|
||||||
|
|
||||||
if (torrent->needSaveResumeData())
|
if (torrent->needSaveResumeData())
|
||||||
|
{
|
||||||
torrent->saveResumeData();
|
torrent->saveResumeData();
|
||||||
|
m_needSaveResumeDataTorrents.remove(torrent->id());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2363,7 +2391,7 @@ void Session::saveResumeData()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::saveTorrentsQueue()
|
void Session::saveTorrentsQueue() const
|
||||||
{
|
{
|
||||||
// store hash in textual representation
|
// store hash in textual representation
|
||||||
QMap<int, QString> queue; // Use QMap since it should be ordered by key
|
QMap<int, QString> queue; // Use QMap since it should be ordered by key
|
||||||
@@ -2372,33 +2400,24 @@ void Session::saveTorrentsQueue()
|
|||||||
// We require actual (non-cached) queue position here!
|
// We require actual (non-cached) queue position here!
|
||||||
const int queuePos = static_cast<LTUnderlyingType<lt::queue_position_t>>(torrent->nativeHandle().queue_position());
|
const int queuePos = static_cast<LTUnderlyingType<lt::queue_position_t>>(torrent->nativeHandle().queue_position());
|
||||||
if (queuePos >= 0)
|
if (queuePos >= 0)
|
||||||
queue[queuePos] = torrent->hash();
|
queue[queuePos] = torrent->id().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
data.reserve(((InfoHash::length() * 2) + 1) * queue.size());
|
data.reserve(((TorrentID::length() * 2) + 1) * queue.size());
|
||||||
for (const QString &hash : asConst(queue))
|
for (const QString &torrentID : asConst(queue))
|
||||||
data += (hash.toLatin1() + '\n');
|
data += (torrentID.toLatin1() + '\n');
|
||||||
|
|
||||||
const QString filename = QLatin1String {"queue"};
|
const QString filename = QLatin1String {"queue"};
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
QMetaObject::invokeMethod(m_resumeDataSavingManager
|
QMetaObject::invokeMethod(m_resumeDataSavingManager
|
||||||
, [this, data, filename]() { m_resumeDataSavingManager->save(filename, data); });
|
, [this, data, filename]() { m_resumeDataSavingManager->save(filename, data); });
|
||||||
#else
|
|
||||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, "save"
|
|
||||||
, Q_ARG(QString, filename), Q_ARG(QByteArray, data));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::removeTorrentsQueue()
|
void Session::removeTorrentsQueue() const
|
||||||
{
|
{
|
||||||
const QString filename = QLatin1String {"queue"};
|
const QString filename = QLatin1String {"queue"};
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
QMetaObject::invokeMethod(m_resumeDataSavingManager
|
QMetaObject::invokeMethod(m_resumeDataSavingManager
|
||||||
, [this, filename]() { m_resumeDataSavingManager->remove(filename); });
|
, [this, filename]() { m_resumeDataSavingManager->remove(filename); });
|
||||||
#else
|
|
||||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, "remove", Q_ARG(QString, filename));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::setDefaultSavePath(QString path)
|
void Session::setDefaultSavePath(QString path)
|
||||||
@@ -3516,6 +3535,20 @@ void Session::setUPnPLeaseDuration(const int duration)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Session::peerToS() const
|
||||||
|
{
|
||||||
|
return m_peerToS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Session::setPeerToS(const int value)
|
||||||
|
{
|
||||||
|
if (value == m_peerToS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_peerToS = value;
|
||||||
|
configureDeferred();
|
||||||
|
}
|
||||||
|
|
||||||
bool Session::ignoreLimitsOnLAN() const
|
bool Session::ignoreLimitsOnLAN() const
|
||||||
{
|
{
|
||||||
return m_ignoreLimitsOnLAN;
|
return m_ignoreLimitsOnLAN;
|
||||||
@@ -3742,11 +3775,11 @@ void Session::setMaxRatioAction(const MaxRatioAction act)
|
|||||||
|
|
||||||
// If this functions returns true, we cannot add torrent to session,
|
// If this functions returns true, we cannot add torrent to session,
|
||||||
// but it is still possible to merge trackers in some cases
|
// but it is still possible to merge trackers in some cases
|
||||||
bool Session::isKnownTorrent(const InfoHash &hash) const
|
bool Session::isKnownTorrent(const TorrentID &id) const
|
||||||
{
|
{
|
||||||
return (m_torrents.contains(hash)
|
return (m_torrents.contains(id)
|
||||||
|| m_loadingTorrents.contains(hash)
|
|| m_loadingTorrents.contains(id)
|
||||||
|| m_downloadedMetadata.contains(hash));
|
|| m_downloadedMetadata.contains(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::updateSeedingLimitTimer()
|
void Session::updateSeedingLimitTimer()
|
||||||
@@ -3765,51 +3798,43 @@ void Session::updateSeedingLimitTimer()
|
|||||||
|
|
||||||
void Session::handleTorrentShareLimitChanged(TorrentImpl *const torrent)
|
void Session::handleTorrentShareLimitChanged(TorrentImpl *const torrent)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
updateSeedingLimitTimer();
|
updateSeedingLimitTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentNameChanged(TorrentImpl *const torrent)
|
void Session::handleTorrentNameChanged(TorrentImpl *const torrent)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
Q_UNUSED(torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentSavePathChanged(TorrentImpl *const torrent)
|
void Session::handleTorrentSavePathChanged(TorrentImpl *const torrent)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
emit torrentSavePathChanged(torrent);
|
emit torrentSavePathChanged(torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
|
void Session::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
emit torrentCategoryChanged(torrent, oldCategory);
|
emit torrentCategoryChanged(torrent, oldCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
|
void Session::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
emit torrentTagAdded(torrent, tag);
|
emit torrentTagAdded(torrent, tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag)
|
void Session::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
emit torrentTagRemoved(torrent, tag);
|
emit torrentTagRemoved(torrent, tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
|
void Session::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
emit torrentSavingModeChanged(torrent);
|
emit torrentSavingModeChanged(torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
|
void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
|
|
||||||
for (const TrackerEntry &newTracker : newTrackers)
|
for (const TrackerEntry &newTracker : newTrackers)
|
||||||
LogMsg(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url(), torrent->name()));
|
LogMsg(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url, torrent->name()));
|
||||||
emit trackersAdded(torrent, newTrackers);
|
emit trackersAdded(torrent, newTrackers);
|
||||||
if (torrent->trackers().size() == newTrackers.size())
|
if (torrent->trackers().size() == newTrackers.size())
|
||||||
emit trackerlessStateChanged(torrent, false);
|
emit trackerlessStateChanged(torrent, false);
|
||||||
@@ -3818,10 +3843,8 @@ void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVect
|
|||||||
|
|
||||||
void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVector<TrackerEntry> &deletedTrackers)
|
void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVector<TrackerEntry> &deletedTrackers)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
|
|
||||||
for (const TrackerEntry &deletedTracker : deletedTrackers)
|
for (const TrackerEntry &deletedTracker : deletedTrackers)
|
||||||
LogMsg(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url(), torrent->name()));
|
LogMsg(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url, torrent->name()));
|
||||||
emit trackersRemoved(torrent, deletedTrackers);
|
emit trackersRemoved(torrent, deletedTrackers);
|
||||||
if (torrent->trackers().empty())
|
if (torrent->trackers().empty())
|
||||||
emit trackerlessStateChanged(torrent, true);
|
emit trackerlessStateChanged(torrent, true);
|
||||||
@@ -3830,20 +3853,17 @@ void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVe
|
|||||||
|
|
||||||
void Session::handleTorrentTrackersChanged(TorrentImpl *const torrent)
|
void Session::handleTorrentTrackersChanged(TorrentImpl *const torrent)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
emit trackersChanged(torrent);
|
emit trackersChanged(torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
|
void Session::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
for (const QUrl &newUrlSeed : newUrlSeeds)
|
for (const QUrl &newUrlSeed : newUrlSeeds)
|
||||||
LogMsg(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name()));
|
LogMsg(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
|
void Session::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
for (const QUrl &urlSeed : urlSeeds)
|
for (const QUrl &urlSeed : urlSeeds)
|
||||||
LogMsg(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name()));
|
LogMsg(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name()));
|
||||||
}
|
}
|
||||||
@@ -3852,7 +3872,7 @@ void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
|
|||||||
{
|
{
|
||||||
// Save metadata
|
// Save metadata
|
||||||
const QDir resumeDataDir {m_resumeFolderPath};
|
const QDir resumeDataDir {m_resumeFolderPath};
|
||||||
const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->hash())};
|
const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->id().toString())};
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName));
|
torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName));
|
||||||
@@ -3871,13 +3891,11 @@ void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
|
|||||||
|
|
||||||
void Session::handleTorrentPaused(TorrentImpl *const torrent)
|
void Session::handleTorrentPaused(TorrentImpl *const torrent)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
emit torrentPaused(torrent);
|
emit torrentPaused(torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentResumed(TorrentImpl *const torrent)
|
void Session::handleTorrentResumed(TorrentImpl *const torrent)
|
||||||
{
|
{
|
||||||
torrent->saveResumeData();
|
|
||||||
emit torrentResumed(torrent);
|
emit torrentResumed(torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3888,8 +3906,6 @@ void Session::handleTorrentChecked(TorrentImpl *const torrent)
|
|||||||
|
|
||||||
void Session::handleTorrentFinished(TorrentImpl *const torrent)
|
void Session::handleTorrentFinished(TorrentImpl *const torrent)
|
||||||
{
|
{
|
||||||
if (!torrent->hasError() && !torrent->hasMissingFiles())
|
|
||||||
torrent->saveResumeData();
|
|
||||||
emit torrentFinished(torrent);
|
emit torrentFinished(torrent);
|
||||||
|
|
||||||
qDebug("Checking if the torrent contains torrent files to download");
|
qDebug("Checking if the torrent contains torrent files to download");
|
||||||
@@ -3925,21 +3941,52 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent)
|
|||||||
emit allTorrentsFinished();
|
emit allTorrentsFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentResumeDataReady(TorrentImpl *const torrent, const std::shared_ptr<lt::entry> &data)
|
void Session::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
|
||||||
{
|
{
|
||||||
--m_numResumeData;
|
--m_numResumeData;
|
||||||
|
|
||||||
|
// We need to adjust native libtorrent resume data
|
||||||
|
lt::add_torrent_params p = data.ltAddTorrentParams;
|
||||||
|
p.save_path = Profile::instance()->toPortablePath(QString::fromStdString(p.save_path)).toStdString();
|
||||||
|
if (data.paused)
|
||||||
|
{
|
||||||
|
p.flags |= lt::torrent_flags::paused;
|
||||||
|
p.flags &= ~lt::torrent_flags::auto_managed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Torrent can be actually "running" but temporarily "paused" to perform some
|
||||||
|
// service jobs behind the scenes so we need to restore it as "running"
|
||||||
|
if (!data.forced)
|
||||||
|
{
|
||||||
|
p.flags |= lt::torrent_flags::auto_managed;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
p.flags &= ~lt::torrent_flags::paused;
|
||||||
|
p.flags &= ~lt::torrent_flags::auto_managed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Separated thread is used for the blocking IO which results in slow processing of many torrents.
|
// Separated thread is used for the blocking IO which results in slow processing of many torrents.
|
||||||
// Copying lt::entry objects around isn't cheap.
|
// Copying lt::entry objects around isn't cheap.
|
||||||
|
|
||||||
const QString filename = QString::fromLatin1("%1.fastresume").arg(torrent->hash());
|
auto resumeDataPtr = std::make_shared<lt::entry>(lt::write_resume_data(p));
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
lt::entry &resumeData = *resumeDataPtr;
|
||||||
|
|
||||||
|
resumeData["qBt-savePath"] = Profile::instance()->toPortablePath(data.savePath).toStdString();
|
||||||
|
resumeData["qBt-ratioLimit"] = static_cast<int>(data.ratioLimit * 1000);
|
||||||
|
resumeData["qBt-seedingTimeLimit"] = data.seedingTimeLimit;
|
||||||
|
resumeData["qBt-category"] = data.category.toStdString();
|
||||||
|
resumeData["qBt-tags"] = setToEntryList(data.tags);
|
||||||
|
resumeData["qBt-name"] = data.name.toStdString();
|
||||||
|
resumeData["qBt-seedStatus"] = data.hasSeedStatus;
|
||||||
|
resumeData["qBt-contentLayout"] = Utils::String::fromEnum(data.contentLayout).toStdString();
|
||||||
|
resumeData["qBt-firstLastPiecePriority"] = data.firstLastPiecePriority;
|
||||||
|
|
||||||
|
const QString filename = QString::fromLatin1("%1.fastresume").arg(torrent->id().toString());
|
||||||
QMetaObject::invokeMethod(m_resumeDataSavingManager
|
QMetaObject::invokeMethod(m_resumeDataSavingManager
|
||||||
, [this, filename, data]() { m_resumeDataSavingManager->save(filename, data); });
|
, [this, filename, resumeDataPtr]() { m_resumeDataSavingManager->save(filename, resumeDataPtr); });
|
||||||
#else
|
|
||||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, "save"
|
|
||||||
, Q_ARG(QString, filename), Q_ARG(std::shared_ptr<lt::entry>, data));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl)
|
void Session::handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl)
|
||||||
@@ -4008,9 +4055,13 @@ bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newP
|
|||||||
|
|
||||||
void Session::moveTorrentStorage(const MoveStorageJob &job) const
|
void Session::moveTorrentStorage(const MoveStorageJob &job) const
|
||||||
{
|
{
|
||||||
const InfoHash infoHash = job.torrentHandle.info_hash();
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
const TorrentImpl *torrent = m_torrents.value(infoHash);
|
const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
|
||||||
const QString torrentName = (torrent ? torrent->name() : QString {infoHash});
|
#else
|
||||||
|
const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
|
||||||
|
#endif
|
||||||
|
const TorrentImpl *torrent = m_torrents.value(id);
|
||||||
|
const QString torrentName = (torrent ? torrent->name() : id.toString());
|
||||||
LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path));
|
LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path));
|
||||||
|
|
||||||
job.torrentHandle.move_storage(job.path.toUtf8().constData()
|
job.torrentHandle.move_storage(job.path.toUtf8().constData()
|
||||||
@@ -4094,15 +4145,9 @@ void Session::configureDeferred()
|
|||||||
{
|
{
|
||||||
if (m_deferredConfigureScheduled)
|
if (m_deferredConfigureScheduled)
|
||||||
return;
|
return;
|
||||||
m_deferredConfigureScheduled = true;
|
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
m_deferredConfigureScheduled = true;
|
||||||
QMetaObject::invokeMethod(this
|
QMetaObject::invokeMethod(this, qOverload<>(&Session::configure), Qt::QueuedConnection);
|
||||||
, qOverload<>(&Session::configure)
|
|
||||||
, Qt::QueuedConnection);
|
|
||||||
#else
|
|
||||||
QMetaObject::invokeMethod(this, "configure", Qt::QueuedConnection);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable IP Filtering
|
// Enable IP Filtering
|
||||||
@@ -4141,9 +4186,9 @@ void Session::disableIPFilter()
|
|||||||
m_nativeSession->set_ip_filter(filter);
|
m_nativeSession->set_ip_filter(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::recursiveTorrentDownload(const InfoHash &hash)
|
void Session::recursiveTorrentDownload(const TorrentID &id)
|
||||||
{
|
{
|
||||||
TorrentImpl *const torrent = m_torrents.value(hash);
|
TorrentImpl *const torrent = m_torrents.value(id);
|
||||||
if (!torrent) return;
|
if (!torrent) return;
|
||||||
|
|
||||||
for (int i = 0; i < torrent->filesCount(); ++i)
|
for (int i = 0; i < torrent->filesCount(); ++i)
|
||||||
@@ -4259,51 +4304,7 @@ bool Session::loadTorrentResumeData(const QByteArray &data, const TorrentInfo &m
|
|||||||
|
|
||||||
const bool hasMetadata = (p.ti && p.ti->is_valid());
|
const bool hasMetadata = (p.ti && p.ti->is_valid());
|
||||||
if (!hasMetadata && !root.dict_find("info-hash"))
|
if (!hasMetadata && !root.dict_find("info-hash"))
|
||||||
{
|
return false;
|
||||||
// TODO: The following code is deprecated. Remove after several releases in 4.3.x.
|
|
||||||
// === BEGIN DEPRECATED CODE === //
|
|
||||||
// Try to load from legacy data used in older versions for torrents w/o metadata
|
|
||||||
const lt::bdecode_node magnetURINode = root.dict_find("qBt-magnetUri");
|
|
||||||
if (magnetURINode.type() == lt::bdecode_node::string_t)
|
|
||||||
{
|
|
||||||
lt::parse_magnet_uri(magnetURINode.string_value(), p, ec);
|
|
||||||
|
|
||||||
if (isTempPathEnabled())
|
|
||||||
{
|
|
||||||
p.save_path = Utils::Fs::toNativePath(tempPath()).toStdString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If empty then Automatic mode, otherwise Manual mode
|
|
||||||
const QString savePath = torrentParams.savePath.isEmpty() ? categorySavePath(torrentParams.category) : torrentParams.savePath;
|
|
||||||
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preallocation mode
|
|
||||||
p.storage_mode = (isPreallocationEnabled() ? lt::storage_mode_allocate : lt::storage_mode_sparse);
|
|
||||||
|
|
||||||
const lt::bdecode_node addedTimeNode = root.dict_find("qBt-addedTime");
|
|
||||||
if (addedTimeNode.type() == lt::bdecode_node::int_t)
|
|
||||||
p.added_time = addedTimeNode.int_value();
|
|
||||||
|
|
||||||
const lt::bdecode_node sequentialNode = root.dict_find("qBt-sequential");
|
|
||||||
if (sequentialNode.type() == lt::bdecode_node::int_t)
|
|
||||||
{
|
|
||||||
if (static_cast<bool>(sequentialNode.int_value()))
|
|
||||||
p.flags |= lt::torrent_flags::sequential_download;
|
|
||||||
else
|
|
||||||
p.flags &= ~lt::torrent_flags::sequential_download;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (torrentParams.name.isEmpty() && !p.name.empty())
|
|
||||||
torrentParams.name = QString::fromStdString(p.name);
|
|
||||||
}
|
|
||||||
// === END DEPRECATED CODE === //
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -4568,7 +4569,7 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle)
|
|||||||
const LoadTorrentParams params = m_loadingTorrents.take(nativeHandle.info_hash());
|
const LoadTorrentParams params = m_loadingTorrents.take(nativeHandle.info_hash());
|
||||||
|
|
||||||
auto *const torrent = new TorrentImpl {this, m_nativeSession, nativeHandle, params};
|
auto *const torrent = new TorrentImpl {this, m_nativeSession, nativeHandle, params};
|
||||||
m_torrents.insert(torrent->hash(), torrent);
|
m_torrents.insert(torrent->id(), torrent);
|
||||||
|
|
||||||
const bool hasMetadata = torrent->hasMetadata();
|
const bool hasMetadata = torrent->hasMetadata();
|
||||||
|
|
||||||
@@ -4583,7 +4584,7 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle)
|
|||||||
{
|
{
|
||||||
// Backup torrent file
|
// Backup torrent file
|
||||||
const QDir resumeDataDir {m_resumeFolderPath};
|
const QDir resumeDataDir {m_resumeFolderPath};
|
||||||
const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->hash())};
|
const QString torrentFileName {QString::fromLatin1("%1.torrent").arg(torrent->id().toString())};
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName));
|
torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName));
|
||||||
@@ -4641,9 +4642,13 @@ void Session::handleAddTorrentAlert(const lt::add_torrent_alert *p)
|
|||||||
|
|
||||||
void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
|
void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
|
||||||
{
|
{
|
||||||
const InfoHash infoHash {p->info_hash};
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
|
const auto id = TorrentID::fromInfoHash(p->info_hashes);
|
||||||
|
#else
|
||||||
|
const auto id = TorrentID::fromInfoHash(p->info_hash);
|
||||||
|
#endif
|
||||||
|
|
||||||
const auto removingTorrentDataIter = m_removingTorrents.find(infoHash);
|
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
||||||
if (removingTorrentDataIter != m_removingTorrents.end())
|
if (removingTorrentDataIter != m_removingTorrents.end())
|
||||||
{
|
{
|
||||||
if (removingTorrentDataIter->deleteOption == DeleteTorrent)
|
if (removingTorrentDataIter->deleteOption == DeleteTorrent)
|
||||||
@@ -4656,8 +4661,13 @@ void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
|
|||||||
|
|
||||||
void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
|
void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
|
||||||
{
|
{
|
||||||
const InfoHash infoHash {p->info_hash};
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
const auto removingTorrentDataIter = m_removingTorrents.find(infoHash);
|
const auto id = TorrentID::fromInfoHash(p->info_hashes);
|
||||||
|
#else
|
||||||
|
const auto id = TorrentID::fromInfoHash(p->info_hash);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
||||||
|
|
||||||
if (removingTorrentDataIter == m_removingTorrents.end())
|
if (removingTorrentDataIter == m_removingTorrents.end())
|
||||||
return;
|
return;
|
||||||
@@ -4669,8 +4679,13 @@ void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
|
|||||||
|
|
||||||
void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
|
void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
|
||||||
{
|
{
|
||||||
const InfoHash infoHash {p->info_hash};
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
const auto removingTorrentDataIter = m_removingTorrents.find(infoHash);
|
const auto id = TorrentID::fromInfoHash(p->info_hashes);
|
||||||
|
#else
|
||||||
|
const auto id = TorrentID::fromInfoHash(p->info_hash);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
||||||
|
|
||||||
if (removingTorrentDataIter == m_removingTorrents.end())
|
if (removingTorrentDataIter == m_removingTorrents.end())
|
||||||
return;
|
return;
|
||||||
@@ -4694,8 +4709,13 @@ void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_ale
|
|||||||
|
|
||||||
void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
|
void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
|
||||||
{
|
{
|
||||||
const InfoHash hash {p->handle.info_hash()};
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
const auto downloadedMetadataIter = m_downloadedMetadata.find(hash);
|
const auto id = TorrentID::fromInfoHash(p->handle.info_hashes());
|
||||||
|
#else
|
||||||
|
const auto id = TorrentID::fromInfoHash(p->handle.info_hash());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
|
||||||
|
|
||||||
if (downloadedMetadataIter != m_downloadedMetadata.end())
|
if (downloadedMetadataIter != m_downloadedMetadata.end())
|
||||||
{
|
{
|
||||||
@@ -4716,11 +4736,11 @@ void Session::handleFileErrorAlert(const lt::file_error_alert *p)
|
|||||||
if (!torrent)
|
if (!torrent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const InfoHash hash = torrent->hash();
|
const TorrentID id = torrent->id();
|
||||||
|
|
||||||
if (!m_recentErroredTorrents.contains(hash))
|
if (!m_recentErroredTorrents.contains(id))
|
||||||
{
|
{
|
||||||
m_recentErroredTorrents.insert(hash);
|
m_recentErroredTorrents.insert(id);
|
||||||
|
|
||||||
const QString msg = QString::fromStdString(p->message());
|
const QString msg = QString::fromStdString(p->message());
|
||||||
LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: %3")
|
LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: %3")
|
||||||
@@ -4919,9 +4939,14 @@ void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p)
|
|||||||
const QString newPath {p->storage_path()};
|
const QString newPath {p->storage_path()};
|
||||||
Q_ASSERT(newPath == currentJob.path);
|
Q_ASSERT(newPath == currentJob.path);
|
||||||
|
|
||||||
const InfoHash infoHash = currentJob.torrentHandle.info_hash();
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
TorrentImpl *torrent = m_torrents.value(infoHash);
|
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
|
||||||
const QString torrentName = (torrent ? torrent->name() : QString {infoHash});
|
#else
|
||||||
|
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TorrentImpl *torrent = m_torrents.value(id);
|
||||||
|
const QString torrentName = (torrent ? torrent->name() : id.toString());
|
||||||
LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath));
|
LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath));
|
||||||
|
|
||||||
handleMoveTorrentStorageJobFinished();
|
handleMoveTorrentStorageJobFinished();
|
||||||
@@ -4934,9 +4959,14 @@ void Session::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert
|
|||||||
const MoveStorageJob ¤tJob = m_moveStorageQueue.first();
|
const MoveStorageJob ¤tJob = m_moveStorageQueue.first();
|
||||||
Q_ASSERT(currentJob.torrentHandle == p->handle);
|
Q_ASSERT(currentJob.torrentHandle == p->handle);
|
||||||
|
|
||||||
const InfoHash infoHash = currentJob.torrentHandle.info_hash();
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
TorrentImpl *torrent = m_torrents.value(infoHash);
|
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
|
||||||
const QString torrentName = (torrent ? torrent->name() : QString {infoHash});
|
#else
|
||||||
|
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TorrentImpl *torrent = m_torrents.value(id);
|
||||||
|
const QString torrentName = (torrent ? torrent->name() : id.toString());
|
||||||
const QString currentLocation = QString::fromStdString(p->handle.status(lt::torrent_handle::query_save_path).save_path);
|
const QString currentLocation = QString::fromStdString(p->handle.status(lt::torrent_handle::query_save_path).save_path);
|
||||||
const QString errorMessage = QString::fromStdString(p->message());
|
const QString errorMessage = QString::fromStdString(p->message());
|
||||||
LogMsg(tr("Failed to move \"%1\" from \"%2\" to \"%3\". Reason: %4.")
|
LogMsg(tr("Failed to move \"%1\" from \"%2\" to \"%3\". Reason: %4.")
|
||||||
@@ -4952,8 +4982,12 @@ void Session::handleStateUpdateAlert(const lt::state_update_alert *p)
|
|||||||
|
|
||||||
for (const lt::torrent_status &status : p->status)
|
for (const lt::torrent_status &status : p->status)
|
||||||
{
|
{
|
||||||
TorrentImpl *const torrent = m_torrents.value(status.info_hash);
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
|
const auto id = TorrentID::fromInfoHash(status.info_hashes);
|
||||||
|
#else
|
||||||
|
const auto id = TorrentID::fromInfoHash(status.info_hash);
|
||||||
|
#endif
|
||||||
|
TorrentImpl *const torrent = m_torrents.value(id);
|
||||||
if (!torrent)
|
if (!torrent)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|||||||
@@ -51,14 +51,6 @@
|
|||||||
#include "sessionstatus.h"
|
#include "sessionstatus.h"
|
||||||
#include "torrentinfo.h"
|
#include "torrentinfo.h"
|
||||||
|
|
||||||
#if !defined(Q_OS_WIN) || (LIBTORRENT_VERSION_NUM >= 10212)
|
|
||||||
#define HAS_HTTPS_TRACKER_VALIDATION
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if ((LIBTORRENT_VERSION_NUM >= 10212) && (LIBTORRENT_VERSION_NUM < 20000)) || (LIBTORRENT_VERSION_NUM >= 20002)
|
|
||||||
#define HAS_IDN_SUPPORT
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class QFile;
|
class QFile;
|
||||||
class QNetworkConfiguration;
|
class QNetworkConfiguration;
|
||||||
class QNetworkConfigurationManager;
|
class QNetworkConfigurationManager;
|
||||||
@@ -107,8 +99,8 @@ namespace BitTorrent
|
|||||||
class Torrent;
|
class Torrent;
|
||||||
class TorrentImpl;
|
class TorrentImpl;
|
||||||
class Tracker;
|
class Tracker;
|
||||||
class TrackerEntry;
|
|
||||||
struct LoadTorrentParams;
|
struct LoadTorrentParams;
|
||||||
|
struct TrackerEntry;
|
||||||
|
|
||||||
enum class MoveStorageMode;
|
enum class MoveStorageMode;
|
||||||
|
|
||||||
@@ -392,6 +384,8 @@ namespace BitTorrent
|
|||||||
void setOutgoingPortsMax(int max);
|
void setOutgoingPortsMax(int max);
|
||||||
int UPnPLeaseDuration() const;
|
int UPnPLeaseDuration() const;
|
||||||
void setUPnPLeaseDuration(int duration);
|
void setUPnPLeaseDuration(int duration);
|
||||||
|
int peerToS() const;
|
||||||
|
void setPeerToS(int value);
|
||||||
bool ignoreLimitsOnLAN() const;
|
bool ignoreLimitsOnLAN() const;
|
||||||
void setIgnoreLimitsOnLAN(bool ignore);
|
void setIgnoreLimitsOnLAN(bool ignore);
|
||||||
bool includeOverheadInLimits() const;
|
bool includeOverheadInLimits() const;
|
||||||
@@ -440,7 +434,7 @@ namespace BitTorrent
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void startUpTorrents();
|
void startUpTorrents();
|
||||||
Torrent *findTorrent(const InfoHash &hash) const;
|
Torrent *findTorrent(const TorrentID &id) const;
|
||||||
QVector<Torrent *> torrents() const;
|
QVector<Torrent *> torrents() const;
|
||||||
bool hasActiveTorrents() const;
|
bool hasActiveTorrents() const;
|
||||||
bool hasUnfinishedTorrents() const;
|
bool hasUnfinishedTorrents() const;
|
||||||
@@ -456,21 +450,22 @@ namespace BitTorrent
|
|||||||
|
|
||||||
void banIP(const QString &ip);
|
void banIP(const QString &ip);
|
||||||
|
|
||||||
bool isKnownTorrent(const InfoHash &hash) const;
|
bool isKnownTorrent(const TorrentID &id) const;
|
||||||
bool addTorrent(const QString &source, const AddTorrentParams ¶ms = AddTorrentParams());
|
bool addTorrent(const QString &source, const AddTorrentParams ¶ms = AddTorrentParams());
|
||||||
bool addTorrent(const MagnetUri &magnetUri, const AddTorrentParams ¶ms = AddTorrentParams());
|
bool addTorrent(const MagnetUri &magnetUri, const AddTorrentParams ¶ms = AddTorrentParams());
|
||||||
bool addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams ¶ms = AddTorrentParams());
|
bool addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams ¶ms = AddTorrentParams());
|
||||||
bool deleteTorrent(const InfoHash &hash, DeleteOption deleteOption = DeleteTorrent);
|
bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteTorrent);
|
||||||
bool downloadMetadata(const MagnetUri &magnetUri);
|
bool downloadMetadata(const MagnetUri &magnetUri);
|
||||||
bool cancelDownloadMetadata(const InfoHash &hash);
|
bool cancelDownloadMetadata(const TorrentID &id);
|
||||||
|
|
||||||
void recursiveTorrentDownload(const InfoHash &hash);
|
void recursiveTorrentDownload(const TorrentID &id);
|
||||||
void increaseTorrentsQueuePos(const QVector<InfoHash> &hashes);
|
void increaseTorrentsQueuePos(const QVector<TorrentID> &ids);
|
||||||
void decreaseTorrentsQueuePos(const QVector<InfoHash> &hashes);
|
void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids);
|
||||||
void topTorrentsQueuePos(const QVector<InfoHash> &hashes);
|
void topTorrentsQueuePos(const QVector<TorrentID> &ids);
|
||||||
void bottomTorrentsQueuePos(const QVector<InfoHash> &hashes);
|
void bottomTorrentsQueuePos(const QVector<TorrentID> &ids);
|
||||||
|
|
||||||
// Torrent interface
|
// Torrent interface
|
||||||
|
void handleTorrentNeedSaveResumeData(const TorrentImpl *torrent);
|
||||||
void handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent);
|
void handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent);
|
||||||
void handleTorrentShareLimitChanged(TorrentImpl *const torrent);
|
void handleTorrentShareLimitChanged(TorrentImpl *const torrent);
|
||||||
void handleTorrentNameChanged(TorrentImpl *const torrent);
|
void handleTorrentNameChanged(TorrentImpl *const torrent);
|
||||||
@@ -489,7 +484,7 @@ namespace BitTorrent
|
|||||||
void handleTorrentTrackersChanged(TorrentImpl *const torrent);
|
void handleTorrentTrackersChanged(TorrentImpl *const torrent);
|
||||||
void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds);
|
void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds);
|
||||||
void handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds);
|
void handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds);
|
||||||
void handleTorrentResumeDataReady(TorrentImpl *const torrent, const std::shared_ptr<lt::entry> &data);
|
void handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data);
|
||||||
void handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl);
|
void handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl);
|
||||||
void handleTorrentTrackerWarning(TorrentImpl *const torrent, const QString &trackerUrl);
|
void handleTorrentTrackerWarning(TorrentImpl *const torrent, const QString &trackerUrl);
|
||||||
void handleTorrentTrackerError(TorrentImpl *const torrent, const QString &trackerUrl);
|
void handleTorrentTrackerError(TorrentImpl *const torrent, const QString &trackerUrl);
|
||||||
@@ -545,7 +540,7 @@ namespace BitTorrent
|
|||||||
void handleIPFilterParsed(int ruleCount);
|
void handleIPFilterParsed(int ruleCount);
|
||||||
void handleIPFilterError();
|
void handleIPFilterError();
|
||||||
void handleDownloadFinished(const Net::DownloadResult &result);
|
void handleDownloadFinished(const Net::DownloadResult &result);
|
||||||
void fileSearchFinished(const InfoHash &id, const QString &savePath, const QStringList &fileNames);
|
void fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames);
|
||||||
|
|
||||||
// Session reconfiguration triggers
|
// Session reconfiguration triggers
|
||||||
void networkOnlineStateChanged(bool online);
|
void networkOnlineStateChanged(bool online);
|
||||||
@@ -632,8 +627,8 @@ namespace BitTorrent
|
|||||||
void createTorrent(const lt::torrent_handle &nativeHandle);
|
void createTorrent(const lt::torrent_handle &nativeHandle);
|
||||||
|
|
||||||
void saveResumeData();
|
void saveResumeData();
|
||||||
void saveTorrentsQueue();
|
void saveTorrentsQueue() const;
|
||||||
void removeTorrentsQueue();
|
void removeTorrentsQueue() const;
|
||||||
|
|
||||||
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
|
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
|
||||||
|
|
||||||
@@ -681,6 +676,7 @@ namespace BitTorrent
|
|||||||
CachedSettingValue<int> m_outgoingPortsMin;
|
CachedSettingValue<int> m_outgoingPortsMin;
|
||||||
CachedSettingValue<int> m_outgoingPortsMax;
|
CachedSettingValue<int> m_outgoingPortsMax;
|
||||||
CachedSettingValue<int> m_UPnPLeaseDuration;
|
CachedSettingValue<int> m_UPnPLeaseDuration;
|
||||||
|
CachedSettingValue<int> m_peerToS;
|
||||||
CachedSettingValue<bool> m_ignoreLimitsOnLAN;
|
CachedSettingValue<bool> m_ignoreLimitsOnLAN;
|
||||||
CachedSettingValue<bool> m_includeOverheadInLimits;
|
CachedSettingValue<bool> m_includeOverheadInLimits;
|
||||||
CachedSettingValue<QString> m_announceIP;
|
CachedSettingValue<QString> m_announceIP;
|
||||||
@@ -769,17 +765,18 @@ namespace BitTorrent
|
|||||||
ResumeDataSavingManager *m_resumeDataSavingManager = nullptr;
|
ResumeDataSavingManager *m_resumeDataSavingManager = nullptr;
|
||||||
FileSearcher *m_fileSearcher = nullptr;
|
FileSearcher *m_fileSearcher = nullptr;
|
||||||
|
|
||||||
QSet<InfoHash> m_downloadedMetadata;
|
QSet<TorrentID> m_downloadedMetadata;
|
||||||
|
|
||||||
QHash<InfoHash, TorrentImpl *> m_torrents;
|
QHash<TorrentID, TorrentImpl *> m_torrents;
|
||||||
QHash<InfoHash, LoadTorrentParams> m_loadingTorrents;
|
QHash<TorrentID, LoadTorrentParams> m_loadingTorrents;
|
||||||
QHash<QString, AddTorrentParams> m_downloadedTorrents;
|
QHash<QString, AddTorrentParams> m_downloadedTorrents;
|
||||||
QHash<InfoHash, RemovingTorrentData> m_removingTorrents;
|
QHash<TorrentID, RemovingTorrentData> m_removingTorrents;
|
||||||
|
QSet<TorrentID> m_needSaveResumeDataTorrents;
|
||||||
QStringMap m_categories;
|
QStringMap m_categories;
|
||||||
QSet<QString> m_tags;
|
QSet<QString> m_tags;
|
||||||
|
|
||||||
// I/O errored torrents
|
// I/O errored torrents
|
||||||
QSet<InfoHash> m_recentErroredTorrents;
|
QSet<TorrentID> m_recentErroredTorrents;
|
||||||
QTimer *m_recentErroredTorrentsTimer = nullptr;
|
QTimer *m_recentErroredTorrentsTimer = nullptr;
|
||||||
|
|
||||||
SessionMetricIndices m_metricIndices;
|
SessionMetricIndices m_metricIndices;
|
||||||
@@ -795,8 +792,3 @@ namespace BitTorrent
|
|||||||
static Session *m_instance;
|
static Session *m_instance;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
Q_DECLARE_METATYPE(std::shared_ptr<lt::entry>)
|
|
||||||
const int sharedPtrLtEntryTypeID = qRegisterMetaType<std::shared_ptr<lt::entry>>();
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -29,10 +29,10 @@
|
|||||||
|
|
||||||
#include "torrent.h"
|
#include "torrent.h"
|
||||||
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
|
||||||
|
#include "infohash.h"
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
uint qHash(const TorrentState key, const uint seed)
|
uint qHash(const TorrentState key, const uint seed)
|
||||||
@@ -42,15 +42,20 @@ namespace BitTorrent
|
|||||||
|
|
||||||
// Torrent
|
// Torrent
|
||||||
|
|
||||||
const qreal Torrent::USE_GLOBAL_RATIO = -2.;
|
const qreal Torrent::USE_GLOBAL_RATIO = -2;
|
||||||
const qreal Torrent::NO_RATIO_LIMIT = -1.;
|
const qreal Torrent::NO_RATIO_LIMIT = -1;
|
||||||
|
|
||||||
const int Torrent::USE_GLOBAL_SEEDING_TIME = -2;
|
const int Torrent::USE_GLOBAL_SEEDING_TIME = -2;
|
||||||
const int Torrent::NO_SEEDING_TIME_LIMIT = -1;
|
const int Torrent::NO_SEEDING_TIME_LIMIT = -1;
|
||||||
|
|
||||||
const qreal Torrent::MAX_RATIO = 9999.;
|
const qreal Torrent::MAX_RATIO = 9999;
|
||||||
const int Torrent::MAX_SEEDING_TIME = 525600;
|
const int Torrent::MAX_SEEDING_TIME = 525600;
|
||||||
|
|
||||||
|
TorrentID Torrent::id() const
|
||||||
|
{
|
||||||
|
return infoHash().toTorrentID();
|
||||||
|
}
|
||||||
|
|
||||||
bool Torrent::isResumed() const
|
bool Torrent::isResumed() const
|
||||||
{
|
{
|
||||||
return !isPaused();
|
return !isPaused();
|
||||||
|
|||||||
@@ -44,9 +44,10 @@ namespace BitTorrent
|
|||||||
enum class DownloadPriority;
|
enum class DownloadPriority;
|
||||||
class InfoHash;
|
class InfoHash;
|
||||||
class PeerInfo;
|
class PeerInfo;
|
||||||
|
class TorrentID;
|
||||||
class TorrentInfo;
|
class TorrentInfo;
|
||||||
class TrackerEntry;
|
|
||||||
struct PeerAddress;
|
struct PeerAddress;
|
||||||
|
struct TrackerEntry;
|
||||||
|
|
||||||
enum class TorrentOperatingMode
|
enum class TorrentOperatingMode
|
||||||
{
|
{
|
||||||
@@ -105,7 +106,7 @@ namespace BitTorrent
|
|||||||
|
|
||||||
virtual ~Torrent() = default;
|
virtual ~Torrent() = default;
|
||||||
|
|
||||||
virtual InfoHash hash() const = 0;
|
virtual InfoHash infoHash() const = 0;
|
||||||
virtual QString name() const = 0;
|
virtual QString name() const = 0;
|
||||||
virtual QDateTime creationDate() const = 0;
|
virtual QDateTime creationDate() const = 0;
|
||||||
virtual QString creator() const = 0;
|
virtual QString creator() const = 0;
|
||||||
@@ -290,6 +291,7 @@ namespace BitTorrent
|
|||||||
|
|
||||||
virtual QString createMagnetURI() const = 0;
|
virtual QString createMagnetURI() const = 0;
|
||||||
|
|
||||||
|
TorrentID id() const;
|
||||||
bool isResumed() const;
|
bool isResumed() const;
|
||||||
qlonglong remainingSize() const;
|
qlonglong remainingSize() const;
|
||||||
|
|
||||||
|
|||||||
@@ -100,8 +100,6 @@ void TorrentCreatorThread::sendProgressSignal(int currentPieceIdx, int totalPiec
|
|||||||
|
|
||||||
void TorrentCreatorThread::run()
|
void TorrentCreatorThread::run()
|
||||||
{
|
{
|
||||||
const QString creatorStr("qBittorrent " QBT_VERSION);
|
|
||||||
|
|
||||||
emit updateProgress(0);
|
emit updateProgress(0);
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -178,17 +176,19 @@ void TorrentCreatorThread::run()
|
|||||||
newTorrent.add_tracker(tracker.trimmed().toStdString(), tier);
|
newTorrent.add_tracker(tracker.trimmed().toStdString(), tier);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInterruptionRequested()) return;
|
|
||||||
|
|
||||||
// calculate the hash for all pieces
|
// calculate the hash for all pieces
|
||||||
lt::set_piece_hashes(newTorrent, Utils::Fs::toNativePath(parentPath).toStdString()
|
lt::set_piece_hashes(newTorrent, Utils::Fs::toNativePath(parentPath).toStdString()
|
||||||
, [this, &newTorrent](const lt::piece_index_t n)
|
, [this, &newTorrent](const lt::piece_index_t n)
|
||||||
{
|
{
|
||||||
|
if (isInterruptionRequested())
|
||||||
|
throw RuntimeError {tr("Create new torrent aborted.")};
|
||||||
|
|
||||||
sendProgressSignal(static_cast<LTUnderlyingType<lt::piece_index_t>>(n), newTorrent.num_pieces());
|
sendProgressSignal(static_cast<LTUnderlyingType<lt::piece_index_t>>(n), newTorrent.num_pieces());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set qBittorrent as creator and add user comment to
|
// Set qBittorrent as creator and add user comment to
|
||||||
// torrent_info structure
|
// torrent_info structure
|
||||||
newTorrent.set_creator(creatorStr.toUtf8().constData());
|
newTorrent.set_creator("qBittorrent " QBT_VERSION);
|
||||||
newTorrent.set_comment(m_params.comment.toUtf8().constData());
|
newTorrent.set_comment(m_params.comment.toUtf8().constData());
|
||||||
// Is private ?
|
// Is private ?
|
||||||
newTorrent.set_priv(m_params.isPrivate);
|
newTorrent.set_priv(m_params.isPrivate);
|
||||||
@@ -207,8 +207,7 @@ void TorrentCreatorThread::run()
|
|||||||
QFile outfile {m_params.savePath};
|
QFile outfile {m_params.savePath};
|
||||||
if (!outfile.open(QIODevice::WriteOnly))
|
if (!outfile.open(QIODevice::WriteOnly))
|
||||||
{
|
{
|
||||||
throw RuntimeError
|
throw RuntimeError {tr("Create new torrent file failed. Reason: %1")
|
||||||
{tr("Create new torrent file failed. Reason: %1")
|
|
||||||
.arg(outfile.errorString())};
|
.arg(outfile.errorString())};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,8 +216,7 @@ void TorrentCreatorThread::run()
|
|||||||
lt::bencode(Utils::IO::FileDeviceOutputIterator {outfile}, entry);
|
lt::bencode(Utils::IO::FileDeviceOutputIterator {outfile}, entry);
|
||||||
if (outfile.error() != QFileDevice::NoError)
|
if (outfile.error() != QFileDevice::NoError)
|
||||||
{
|
{
|
||||||
throw RuntimeError
|
throw RuntimeError {tr("Create new torrent file failed. Reason: %1")
|
||||||
{tr("Create new torrent file failed. Reason: %1")
|
|
||||||
.arg(outfile.errorString())};
|
.arg(outfile.errorString())};
|
||||||
}
|
}
|
||||||
outfile.close();
|
outfile.close();
|
||||||
|
|||||||
@@ -39,13 +39,15 @@
|
|||||||
|
|
||||||
#include <libtorrent/address.hpp>
|
#include <libtorrent/address.hpp>
|
||||||
#include <libtorrent/alert_types.hpp>
|
#include <libtorrent/alert_types.hpp>
|
||||||
#include <libtorrent/entry.hpp>
|
|
||||||
#include <libtorrent/magnet_uri.hpp>
|
#include <libtorrent/magnet_uri.hpp>
|
||||||
#include <libtorrent/session.hpp>
|
#include <libtorrent/session.hpp>
|
||||||
#include <libtorrent/storage_defs.hpp>
|
#include <libtorrent/storage_defs.hpp>
|
||||||
#include <libtorrent/time.hpp>
|
#include <libtorrent/time.hpp>
|
||||||
#include <libtorrent/version.hpp>
|
#include <libtorrent/version.hpp>
|
||||||
#include <libtorrent/write_resume_data.hpp>
|
|
||||||
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
|
#include <libtorrent/info_hash.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <QBitArray>
|
#include <QBitArray>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
@@ -57,7 +59,6 @@
|
|||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/logger.h"
|
#include "base/logger.h"
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
#include "base/profile.h"
|
|
||||||
#include "base/utils/fs.h"
|
#include "base/utils/fs.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
@@ -87,14 +88,135 @@ namespace
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
using ListType = lt::entry::list_type;
|
lt::announce_entry makeNativeAnnouncerEntry(const QString &url, const int tier)
|
||||||
|
|
||||||
ListType setToEntryList(const QSet<QString> &input)
|
|
||||||
{
|
{
|
||||||
ListType entryList;
|
lt::announce_entry entry {url.toStdString()};
|
||||||
for (const QString &setValue : input)
|
entry.tier = tier;
|
||||||
entryList.emplace_back(setValue.toStdString());
|
return entry;
|
||||||
return entryList;
|
}
|
||||||
|
|
||||||
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
|
TrackerEntry fromNativeAnnouncerEntry(const lt::announce_entry &nativeEntry, const lt::info_hash_t &hashes)
|
||||||
|
#else
|
||||||
|
TrackerEntry fromNativeAnnouncerEntry(const lt::announce_entry &nativeEntry)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
TrackerEntry trackerEntry {QString::fromStdString(nativeEntry.url), nativeEntry.tier};
|
||||||
|
|
||||||
|
int numUpdating = 0;
|
||||||
|
int numWorking = 0;
|
||||||
|
int numNotWorking = 0;
|
||||||
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
|
const int numEndpoints = nativeEntry.endpoints.size() * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1);
|
||||||
|
trackerEntry.endpoints.reserve(numEndpoints);
|
||||||
|
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
|
||||||
|
{
|
||||||
|
for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2})
|
||||||
|
{
|
||||||
|
if (hashes.has(protocolVersion))
|
||||||
|
{
|
||||||
|
const lt::announce_infohash &infoHash = endpoint.info_hashes[protocolVersion];
|
||||||
|
|
||||||
|
TrackerEntry::EndpointStats trackerEndpoint;
|
||||||
|
trackerEndpoint.protocolVersion = (protocolVersion == lt::protocol_version::V1) ? 1 : 2;
|
||||||
|
trackerEndpoint.numSeeds = infoHash.scrape_complete;
|
||||||
|
trackerEndpoint.numLeeches = infoHash.scrape_incomplete;
|
||||||
|
trackerEndpoint.numDownloaded = infoHash.scrape_downloaded;
|
||||||
|
if (infoHash.updating)
|
||||||
|
{
|
||||||
|
trackerEndpoint.status = TrackerEntry::Updating;
|
||||||
|
++numUpdating;
|
||||||
|
}
|
||||||
|
else if (infoHash.fails > 0)
|
||||||
|
{
|
||||||
|
trackerEndpoint.status = TrackerEntry::NotWorking;
|
||||||
|
++numNotWorking;
|
||||||
|
}
|
||||||
|
else if (nativeEntry.verified)
|
||||||
|
{
|
||||||
|
trackerEndpoint.status = TrackerEntry::Working;
|
||||||
|
++numWorking;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trackerEndpoint.status = TrackerEntry::NotContacted;
|
||||||
|
}
|
||||||
|
trackerEntry.endpoints.append(trackerEndpoint);
|
||||||
|
|
||||||
|
trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, infoHash.scrape_complete);
|
||||||
|
trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, infoHash.scrape_incomplete);
|
||||||
|
trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, infoHash.scrape_downloaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
const int numEndpoints = nativeEntry.endpoints.size();
|
||||||
|
trackerEntry.endpoints.reserve(numEndpoints);
|
||||||
|
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
|
||||||
|
{
|
||||||
|
TrackerEntry::EndpointStats trackerEndpoint;
|
||||||
|
trackerEndpoint.numSeeds = endpoint.scrape_complete;
|
||||||
|
trackerEndpoint.numLeeches = endpoint.scrape_incomplete;
|
||||||
|
trackerEndpoint.numDownloaded = endpoint.scrape_downloaded;
|
||||||
|
if (endpoint.updating)
|
||||||
|
{
|
||||||
|
trackerEndpoint.status = TrackerEntry::Updating;
|
||||||
|
++numUpdating;
|
||||||
|
}
|
||||||
|
else if (endpoint.fails > 0)
|
||||||
|
{
|
||||||
|
trackerEndpoint.status = TrackerEntry::NotWorking;
|
||||||
|
++numNotWorking;
|
||||||
|
}
|
||||||
|
else if (nativeEntry.verified)
|
||||||
|
{
|
||||||
|
trackerEndpoint.status = TrackerEntry::Working;
|
||||||
|
++numWorking;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trackerEndpoint.status = TrackerEntry::NotContacted;
|
||||||
|
}
|
||||||
|
trackerEntry.endpoints.append(trackerEndpoint);
|
||||||
|
|
||||||
|
trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, endpoint.scrape_complete);
|
||||||
|
trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, endpoint.scrape_incomplete);
|
||||||
|
trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, endpoint.scrape_downloaded);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (numEndpoints > 0)
|
||||||
|
{
|
||||||
|
if (numUpdating > 0)
|
||||||
|
trackerEntry.status = TrackerEntry::Updating;
|
||||||
|
else if (numWorking > 0)
|
||||||
|
trackerEntry.status = TrackerEntry::Working;
|
||||||
|
else if (numNotWorking == numEndpoints)
|
||||||
|
trackerEntry.status = TrackerEntry::NotWorking;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trackerEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initializeStatus(lt::torrent_status &status, const lt::add_torrent_params ¶ms)
|
||||||
|
{
|
||||||
|
status.flags = params.flags;
|
||||||
|
status.active_duration = lt::seconds {params.active_time};
|
||||||
|
status.finished_duration = lt::seconds {params.finished_time};
|
||||||
|
status.seeding_duration = lt::seconds {params.seeding_time};
|
||||||
|
status.num_complete = params.num_complete;
|
||||||
|
status.num_incomplete = params.num_incomplete;
|
||||||
|
status.all_time_download = params.total_downloaded;
|
||||||
|
status.all_time_upload = params.total_uploaded;
|
||||||
|
status.added_time = params.added_time;
|
||||||
|
status.last_seen_complete = params.last_seen_complete;
|
||||||
|
status.last_download = lt::time_point {lt::seconds {params.last_download}};
|
||||||
|
status.last_upload = lt::time_point {lt::seconds {params.last_upload}};
|
||||||
|
status.completed_time = params.completed_time;
|
||||||
|
status.save_path = params.save_path;
|
||||||
|
status.connections_limit = params.max_connections;
|
||||||
|
status.pieces = params.have_pieces;
|
||||||
|
status.verified_pieces = params.verified_pieces;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +228,11 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
|
|||||||
, m_session(session)
|
, m_session(session)
|
||||||
, m_nativeSession(nativeSession)
|
, m_nativeSession(nativeSession)
|
||||||
, m_nativeHandle(nativeHandle)
|
, m_nativeHandle(nativeHandle)
|
||||||
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
|
, m_infoHash(m_nativeHandle.info_hashes())
|
||||||
|
#else
|
||||||
|
, m_infoHash(m_nativeHandle.info_hash())
|
||||||
|
#endif
|
||||||
, m_name(params.name)
|
, m_name(params.name)
|
||||||
, m_savePath(Utils::Fs::toNativePath(params.savePath))
|
, m_savePath(Utils::Fs::toNativePath(params.savePath))
|
||||||
, m_category(params.category)
|
, m_category(params.category)
|
||||||
@@ -123,7 +250,6 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
|
|||||||
if (m_useAutoTMM)
|
if (m_useAutoTMM)
|
||||||
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
|
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
|
||||||
|
|
||||||
m_hash = InfoHash {m_nativeHandle.info_hash()};
|
|
||||||
if (m_ltAddTorrentParams.ti)
|
if (m_ltAddTorrentParams.ti)
|
||||||
{
|
{
|
||||||
// Initialize it only if torrent is added with metadata.
|
// Initialize it only if torrent is added with metadata.
|
||||||
@@ -131,7 +257,8 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
|
|||||||
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
|
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatus();
|
initializeStatus(m_nativeStatus, m_ltAddTorrentParams);
|
||||||
|
updateState();
|
||||||
|
|
||||||
if (hasMetadata())
|
if (hasMetadata())
|
||||||
applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
|
applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
|
||||||
@@ -168,9 +295,9 @@ bool TorrentImpl::isValid() const
|
|||||||
return m_nativeHandle.is_valid();
|
return m_nativeHandle.is_valid();
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoHash TorrentImpl::hash() const
|
InfoHash TorrentImpl::infoHash() const
|
||||||
{
|
{
|
||||||
return m_hash;
|
return m_infoHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TorrentImpl::name() const
|
QString TorrentImpl::name() const
|
||||||
@@ -185,7 +312,7 @@ QString TorrentImpl::name() const
|
|||||||
if (!name.isEmpty())
|
if (!name.isEmpty())
|
||||||
return name;
|
return name;
|
||||||
|
|
||||||
return m_hash;
|
return id().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime TorrentImpl::creationDate() const
|
QDateTime TorrentImpl::creationDate() const
|
||||||
@@ -284,6 +411,7 @@ void TorrentImpl::setAutoTMMEnabled(bool enabled)
|
|||||||
if (m_useAutoTMM == enabled) return;
|
if (m_useAutoTMM == enabled) return;
|
||||||
|
|
||||||
m_useAutoTMM = enabled;
|
m_useAutoTMM = enabled;
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentSavingModeChanged(this);
|
m_session->handleTorrentSavingModeChanged(this);
|
||||||
|
|
||||||
if (m_useAutoTMM)
|
if (m_useAutoTMM)
|
||||||
@@ -311,7 +439,11 @@ QVector<TrackerEntry> TorrentImpl::trackers() const
|
|||||||
entries.reserve(nativeTrackers.size());
|
entries.reserve(nativeTrackers.size());
|
||||||
|
|
||||||
for (const lt::announce_entry &tracker : nativeTrackers)
|
for (const lt::announce_entry &tracker : nativeTrackers)
|
||||||
entries << tracker;
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
|
entries << fromNativeAnnouncerEntry(tracker, m_nativeHandle.info_hashes());
|
||||||
|
#else
|
||||||
|
entries << fromNativeAnnouncerEntry(tracker);
|
||||||
|
#endif
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
@@ -325,7 +457,7 @@ void TorrentImpl::addTrackers(const QVector<TrackerEntry> &trackers)
|
|||||||
{
|
{
|
||||||
QSet<TrackerEntry> currentTrackers;
|
QSet<TrackerEntry> currentTrackers;
|
||||||
for (const lt::announce_entry &entry : m_nativeHandle.trackers())
|
for (const lt::announce_entry &entry : m_nativeHandle.trackers())
|
||||||
currentTrackers << entry;
|
currentTrackers.insert({QString::fromStdString(entry.url), entry.tier});
|
||||||
|
|
||||||
QVector<TrackerEntry> newTrackers;
|
QVector<TrackerEntry> newTrackers;
|
||||||
newTrackers.reserve(trackers.size());
|
newTrackers.reserve(trackers.size());
|
||||||
@@ -334,13 +466,16 @@ void TorrentImpl::addTrackers(const QVector<TrackerEntry> &trackers)
|
|||||||
{
|
{
|
||||||
if (!currentTrackers.contains(tracker))
|
if (!currentTrackers.contains(tracker))
|
||||||
{
|
{
|
||||||
m_nativeHandle.add_tracker(tracker.nativeEntry());
|
m_nativeHandle.add_tracker(makeNativeAnnouncerEntry(tracker.url, tracker.tier));
|
||||||
newTrackers << tracker;
|
newTrackers << tracker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newTrackers.isEmpty())
|
if (!newTrackers.isEmpty())
|
||||||
|
{
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentTrackersAdded(this, newTrackers);
|
m_session->handleTorrentTrackersAdded(this, newTrackers);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
|
void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
|
||||||
@@ -355,7 +490,7 @@ void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
|
|||||||
|
|
||||||
for (const TrackerEntry &tracker : trackers)
|
for (const TrackerEntry &tracker : trackers)
|
||||||
{
|
{
|
||||||
nativeTrackers.emplace_back(tracker.nativeEntry());
|
nativeTrackers.emplace_back(makeNativeAnnouncerEntry(tracker.url, tracker.tier));
|
||||||
|
|
||||||
if (!currentTrackers.removeOne(tracker))
|
if (!currentTrackers.removeOne(tracker))
|
||||||
newTrackers << tracker;
|
newTrackers << tracker;
|
||||||
@@ -363,6 +498,8 @@ void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
|
|||||||
|
|
||||||
m_nativeHandle.replace_trackers(nativeTrackers);
|
m_nativeHandle.replace_trackers(nativeTrackers);
|
||||||
|
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
|
|
||||||
if (newTrackers.isEmpty() && currentTrackers.isEmpty())
|
if (newTrackers.isEmpty() && currentTrackers.isEmpty())
|
||||||
{
|
{
|
||||||
// when existing tracker reorders
|
// when existing tracker reorders
|
||||||
@@ -414,7 +551,10 @@ void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!addedUrlSeeds.isEmpty())
|
if (!addedUrlSeeds.isEmpty())
|
||||||
|
{
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds);
|
m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
|
void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
|
||||||
@@ -435,7 +575,10 @@ void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!removedUrlSeeds.isEmpty())
|
if (!removedUrlSeeds.isEmpty())
|
||||||
|
{
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds);
|
m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::clearPeers()
|
void TorrentImpl::clearPeers()
|
||||||
@@ -467,9 +610,7 @@ bool TorrentImpl::connectPeer(const PeerAddress &peerAddress)
|
|||||||
|
|
||||||
bool TorrentImpl::needSaveResumeData() const
|
bool TorrentImpl::needSaveResumeData() const
|
||||||
{
|
{
|
||||||
if (m_isStopped && !(m_nativeStatus.flags & lt::torrent_flags::auto_managed))
|
return m_nativeHandle.need_save_resume_data();
|
||||||
return false;
|
|
||||||
return m_nativeStatus.need_save_resume;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::saveResumeData()
|
void TorrentImpl::saveResumeData()
|
||||||
@@ -548,6 +689,7 @@ bool TorrentImpl::addTag(const QString &tag)
|
|||||||
if (!m_session->addTag(tag))
|
if (!m_session->addTag(tag))
|
||||||
return false;
|
return false;
|
||||||
m_tags.insert(tag);
|
m_tags.insert(tag);
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentTagAdded(this, tag);
|
m_session->handleTorrentTagAdded(this, tag);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -558,6 +700,7 @@ bool TorrentImpl::removeTag(const QString &tag)
|
|||||||
{
|
{
|
||||||
if (m_tags.remove(tag))
|
if (m_tags.remove(tag))
|
||||||
{
|
{
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentTagRemoved(this, tag);
|
m_session->handleTorrentTagRemoved(this, tag);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -818,9 +961,7 @@ bool TorrentImpl::hasFilteredPieces() const
|
|||||||
|
|
||||||
int TorrentImpl::queuePosition() const
|
int TorrentImpl::queuePosition() const
|
||||||
{
|
{
|
||||||
if (m_nativeStatus.queue_position < lt::queue_position_t {0}) return 0;
|
return static_cast<int>(m_nativeStatus.queue_position);
|
||||||
|
|
||||||
return static_cast<int>(m_nativeStatus.queue_position) + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TorrentImpl::error() const
|
QString TorrentImpl::error() const
|
||||||
@@ -1150,6 +1291,7 @@ void TorrentImpl::setName(const QString &name)
|
|||||||
if (m_name != name)
|
if (m_name != name)
|
||||||
{
|
{
|
||||||
m_name = name;
|
m_name = name;
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentNameChanged(this);
|
m_session->handleTorrentNameChanged(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1163,6 +1305,7 @@ bool TorrentImpl::setCategory(const QString &category)
|
|||||||
|
|
||||||
const QString oldCategory = m_category;
|
const QString oldCategory = m_category;
|
||||||
m_category = category;
|
m_category = category;
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentCategoryChanged(this, oldCategory);
|
m_session->handleTorrentCategoryChanged(this, oldCategory);
|
||||||
|
|
||||||
if (m_useAutoTMM)
|
if (m_useAutoTMM)
|
||||||
@@ -1182,6 +1325,7 @@ void TorrentImpl::move(QString path)
|
|||||||
if (m_useAutoTMM)
|
if (m_useAutoTMM)
|
||||||
{
|
{
|
||||||
m_useAutoTMM = false;
|
m_useAutoTMM = false;
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentSavingModeChanged(this);
|
m_session->handleTorrentSavingModeChanged(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1197,8 +1341,8 @@ void TorrentImpl::move(QString path)
|
|||||||
void TorrentImpl::move_impl(QString path, const MoveStorageMode mode)
|
void TorrentImpl::move_impl(QString path, const MoveStorageMode mode)
|
||||||
{
|
{
|
||||||
if (path == savePath()) return;
|
if (path == savePath()) return;
|
||||||
path = Utils::Fs::toNativePath(path);
|
|
||||||
|
|
||||||
|
path = Utils::Fs::toNativePath(path);
|
||||||
if (!useTempPath())
|
if (!useTempPath())
|
||||||
{
|
{
|
||||||
moveStorage(path, mode);
|
moveStorage(path, mode);
|
||||||
@@ -1206,6 +1350,7 @@ void TorrentImpl::move_impl(QString path, const MoveStorageMode mode)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_savePath = path;
|
m_savePath = path;
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentSavePathChanged(this);
|
m_session->handleTorrentSavePathChanged(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1249,7 +1394,7 @@ void TorrentImpl::setSequentialDownload(const bool enable)
|
|||||||
m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
|
m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
|
||||||
}
|
}
|
||||||
|
|
||||||
saveResumeData();
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
|
void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
|
||||||
@@ -1264,7 +1409,7 @@ void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
|
|||||||
LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
|
LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
|
||||||
.arg((enabled ? tr("On") : tr("Off")), name()));
|
.arg((enabled ? tr("On") : tr("Off")), name()));
|
||||||
|
|
||||||
saveResumeData();
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector<DownloadPriority> &updatedFilePrio)
|
void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector<DownloadPriority> &updatedFilePrio)
|
||||||
@@ -1364,6 +1509,7 @@ void TorrentImpl::pause()
|
|||||||
if (!m_isStopped)
|
if (!m_isStopped)
|
||||||
{
|
{
|
||||||
m_isStopped = true;
|
m_isStopped = true;
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentPaused(this);
|
m_session->handleTorrentPaused(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1387,6 +1533,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
|
|||||||
{
|
{
|
||||||
m_hasMissingFiles = false;
|
m_hasMissingFiles = false;
|
||||||
m_isStopped = false;
|
m_isStopped = false;
|
||||||
|
m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
|
||||||
reload();
|
reload();
|
||||||
updateStatus();
|
updateStatus();
|
||||||
return;
|
return;
|
||||||
@@ -1399,6 +1546,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
|
|||||||
m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready);
|
m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready);
|
||||||
|
|
||||||
m_isStopped = false;
|
m_isStopped = false;
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentResumed(this);
|
m_session->handleTorrentResumed(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1434,6 +1582,7 @@ void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
|
|||||||
|
|
||||||
void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
|
void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
|
||||||
{
|
{
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_storageIsMoving = hasOutstandingJob;
|
m_storageIsMoving = hasOutstandingJob;
|
||||||
|
|
||||||
updateStatus();
|
updateStatus();
|
||||||
@@ -1444,10 +1593,21 @@ void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
|
|||||||
m_session->handleTorrentSavePathChanged(this);
|
m_session->handleTorrentSavePathChanged(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveResumeData();
|
if (!m_storageIsMoving)
|
||||||
|
{
|
||||||
|
if (m_hasMissingFiles)
|
||||||
|
{
|
||||||
|
// it can be moved to the proper location
|
||||||
|
m_hasMissingFiles = false;
|
||||||
|
m_ltAddTorrentParams.save_path = m_nativeStatus.save_path;
|
||||||
|
m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
|
||||||
|
reload();
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||||
m_moveFinishedTriggers.takeFirst()();
|
m_moveFinishedTriggers.takeFirst()();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::handleTrackerReplyAlert(const lt::tracker_reply_alert *p)
|
void TorrentImpl::handleTrackerReplyAlert(const lt::tracker_reply_alert *p)
|
||||||
@@ -1484,9 +1644,9 @@ void TorrentImpl::handleTrackerErrorAlert(const lt::tracker_error_alert *p)
|
|||||||
const QVector<TrackerEntry> trackerList = trackers();
|
const QVector<TrackerEntry> trackerList = trackers();
|
||||||
const auto iter = std::find_if(trackerList.cbegin(), trackerList.cend(), [&trackerUrl](const TrackerEntry &entry)
|
const auto iter = std::find_if(trackerList.cbegin(), trackerList.cend(), [&trackerUrl](const TrackerEntry &entry)
|
||||||
{
|
{
|
||||||
return (entry.url() == trackerUrl);
|
return (entry.url == trackerUrl);
|
||||||
});
|
});
|
||||||
if ((iter != trackerList.cend()) && (iter->status() == TrackerEntry::NotWorking))
|
if ((iter != trackerList.cend()) && (iter->status == TrackerEntry::NotWorking))
|
||||||
m_session->handleTorrentTrackerError(this, trackerUrl);
|
m_session->handleTorrentTrackerError(this, trackerUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1503,7 +1663,8 @@ void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveResumeData();
|
if (m_nativeHandle.need_save_resume_data())
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
|
|
||||||
if (m_fastresumeDataRejected && !m_hasMissingFiles)
|
if (m_fastresumeDataRejected && !m_hasMissingFiles)
|
||||||
m_fastresumeDataRejected = false;
|
m_fastresumeDataRejected = false;
|
||||||
@@ -1538,6 +1699,8 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
|
|||||||
adjustActualSavePath();
|
adjustActualSavePath();
|
||||||
manageIncompleteFiles();
|
manageIncompleteFiles();
|
||||||
|
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
|
|
||||||
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
|
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
|
||||||
if (isMoveInProgress() || (m_renameCount > 0))
|
if (isMoveInProgress() || (m_renameCount > 0))
|
||||||
{
|
{
|
||||||
@@ -1565,35 +1728,25 @@ void TorrentImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p)
|
|||||||
|
|
||||||
void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
||||||
{
|
{
|
||||||
if (!m_hasMissingFiles)
|
if (m_hasMissingFiles)
|
||||||
|
{
|
||||||
|
const auto havePieces = m_ltAddTorrentParams.have_pieces;
|
||||||
|
const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
|
||||||
|
const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
|
||||||
|
|
||||||
|
// Update recent resume data but preserve existing progress
|
||||||
|
m_ltAddTorrentParams = p->params;
|
||||||
|
m_ltAddTorrentParams.have_pieces = havePieces;
|
||||||
|
m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
|
||||||
|
m_ltAddTorrentParams.verified_pieces = verifiedPieces;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// Update recent resume data
|
// Update recent resume data
|
||||||
m_ltAddTorrentParams = p->params;
|
m_ltAddTorrentParams = p->params;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_isStopped)
|
|
||||||
{
|
|
||||||
m_ltAddTorrentParams.flags |= lt::torrent_flags::paused;
|
|
||||||
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::auto_managed;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Torrent can be actually "running" but temporarily "paused" to perform some
|
|
||||||
// service jobs behind the scenes so we need to restore it as "running"
|
|
||||||
if (m_operatingMode == TorrentOperatingMode::AutoManaged)
|
|
||||||
{
|
|
||||||
m_ltAddTorrentParams.flags |= lt::torrent_flags::auto_managed;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::paused;
|
|
||||||
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::auto_managed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ltAddTorrentParams.added_time = addedTime().toSecsSinceEpoch();
|
m_ltAddTorrentParams.added_time = addedTime().toSecsSinceEpoch();
|
||||||
m_ltAddTorrentParams.save_path = Profile::instance()->toPortablePath(
|
|
||||||
QString::fromStdString(m_ltAddTorrentParams.save_path)).toStdString();
|
|
||||||
|
|
||||||
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
|
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
|
||||||
{
|
{
|
||||||
@@ -1606,37 +1759,21 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
|||||||
m_session->findIncompleteFiles(metadata, m_savePath);
|
m_session->findIncompleteFiles(metadata, m_savePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto resumeDataPtr = std::make_shared<lt::entry>(lt::write_resume_data(m_ltAddTorrentParams));
|
LoadTorrentParams resumeData;
|
||||||
lt::entry &resumeData = *resumeDataPtr;
|
resumeData.name = m_name;
|
||||||
|
resumeData.category = m_category;
|
||||||
|
resumeData.savePath = m_useAutoTMM ? "" : m_savePath;
|
||||||
|
resumeData.tags = m_tags;
|
||||||
|
resumeData.contentLayout = m_contentLayout;
|
||||||
|
resumeData.ratioLimit = m_ratioLimit;
|
||||||
|
resumeData.seedingTimeLimit = m_seedingTimeLimit;
|
||||||
|
resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
|
||||||
|
resumeData.hasSeedStatus = m_hasSeedStatus;
|
||||||
|
resumeData.paused = m_isStopped;
|
||||||
|
resumeData.forced = (m_operatingMode == TorrentOperatingMode::Forced);
|
||||||
|
resumeData.ltAddTorrentParams = m_ltAddTorrentParams;
|
||||||
|
|
||||||
// TODO: The following code is deprecated. Remove after several releases in 4.3.x.
|
m_session->handleTorrentResumeDataReady(this, resumeData);
|
||||||
// === BEGIN DEPRECATED CODE === //
|
|
||||||
const bool useDummyResumeData = !hasMetadata();
|
|
||||||
if (useDummyResumeData)
|
|
||||||
{
|
|
||||||
updateStatus();
|
|
||||||
|
|
||||||
resumeData["qBt-magnetUri"] = createMagnetURI().toStdString();
|
|
||||||
// sequentialDownload needs to be stored in the
|
|
||||||
// resume data if there is no metadata, otherwise they won't be
|
|
||||||
// restored if qBittorrent quits before the metadata are retrieved:
|
|
||||||
resumeData["qBt-sequential"] = isSequentialDownload();
|
|
||||||
|
|
||||||
resumeData["qBt-addedTime"] = addedTime().toSecsSinceEpoch();
|
|
||||||
}
|
|
||||||
// === END DEPRECATED CODE === //
|
|
||||||
|
|
||||||
resumeData["qBt-savePath"] = m_useAutoTMM ? "" : Profile::instance()->toPortablePath(m_savePath).toStdString();
|
|
||||||
resumeData["qBt-ratioLimit"] = static_cast<int>(m_ratioLimit * 1000);
|
|
||||||
resumeData["qBt-seedingTimeLimit"] = m_seedingTimeLimit;
|
|
||||||
resumeData["qBt-category"] = m_category.toStdString();
|
|
||||||
resumeData["qBt-tags"] = setToEntryList(m_tags);
|
|
||||||
resumeData["qBt-name"] = m_name.toStdString();
|
|
||||||
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
|
|
||||||
resumeData["qBt-contentLayout"] = Utils::String::fromEnum(m_contentLayout).toStdString();
|
|
||||||
resumeData["qBt-firstLastPiecePriority"] = m_hasFirstLastPiecePriority;
|
|
||||||
|
|
||||||
m_session->handleTorrentResumeDataReady(this, resumeDataPtr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
|
void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
|
||||||
@@ -1702,8 +1839,7 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
|
|||||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||||
m_moveFinishedTriggers.takeFirst()();
|
m_moveFinishedTriggers.takeFirst()();
|
||||||
|
|
||||||
if (isPaused() && (m_renameCount == 0))
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
saveResumeData(); // otherwise the new path will not be saved
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
|
void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
|
||||||
@@ -1720,8 +1856,7 @@ void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
|
|||||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||||
m_moveFinishedTriggers.takeFirst()();
|
m_moveFinishedTriggers.takeFirst()();
|
||||||
|
|
||||||
if (isPaused() && (m_renameCount == 0))
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
saveResumeData(); // otherwise the new path will not be saved
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
|
void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
|
||||||
@@ -1743,11 +1878,10 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
|
|||||||
void TorrentImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
|
void TorrentImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
|
||||||
{
|
{
|
||||||
Q_UNUSED(p);
|
Q_UNUSED(p);
|
||||||
|
|
||||||
qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
|
qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
|
||||||
|
|
||||||
m_maintenanceJob = MaintenanceJob::HandleMetadata;
|
m_maintenanceJob = MaintenanceJob::HandleMetadata;
|
||||||
saveResumeData();
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
|
void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
|
||||||
@@ -1945,6 +2079,7 @@ void TorrentImpl::setRatioLimit(qreal limit)
|
|||||||
if (m_ratioLimit != limit)
|
if (m_ratioLimit != limit)
|
||||||
{
|
{
|
||||||
m_ratioLimit = limit;
|
m_ratioLimit = limit;
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentShareLimitChanged(this);
|
m_session->handleTorrentShareLimitChanged(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1959,6 +2094,7 @@ void TorrentImpl::setSeedingTimeLimit(int limit)
|
|||||||
if (m_seedingTimeLimit != limit)
|
if (m_seedingTimeLimit != limit)
|
||||||
{
|
{
|
||||||
m_seedingTimeLimit = limit;
|
m_seedingTimeLimit = limit;
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
m_session->handleTorrentShareLimitChanged(this);
|
m_session->handleTorrentShareLimitChanged(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1969,7 +2105,7 @@ void TorrentImpl::setUploadLimit(const int limit)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
m_nativeHandle.set_upload_limit(limit);
|
m_nativeHandle.set_upload_limit(limit);
|
||||||
saveResumeData();
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::setDownloadLimit(const int limit)
|
void TorrentImpl::setDownloadLimit(const int limit)
|
||||||
@@ -1978,7 +2114,7 @@ void TorrentImpl::setDownloadLimit(const int limit)
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
m_nativeHandle.set_download_limit(limit);
|
m_nativeHandle.set_download_limit(limit);
|
||||||
saveResumeData();
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::setSuperSeeding(const bool enable)
|
void TorrentImpl::setSuperSeeding(const bool enable)
|
||||||
@@ -1990,7 +2126,8 @@ void TorrentImpl::setSuperSeeding(const bool enable)
|
|||||||
m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
|
m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
|
||||||
else
|
else
|
||||||
m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
|
m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
|
||||||
saveResumeData();
|
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::setDHTDisabled(const bool disable)
|
void TorrentImpl::setDHTDisabled(const bool disable)
|
||||||
@@ -2002,7 +2139,8 @@ void TorrentImpl::setDHTDisabled(const bool disable)
|
|||||||
m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
|
m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
|
||||||
else
|
else
|
||||||
m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
|
m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
|
||||||
saveResumeData();
|
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::setPEXDisabled(const bool disable)
|
void TorrentImpl::setPEXDisabled(const bool disable)
|
||||||
@@ -2014,7 +2152,8 @@ void TorrentImpl::setPEXDisabled(const bool disable)
|
|||||||
m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
|
m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
|
||||||
else
|
else
|
||||||
m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
|
m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
|
||||||
saveResumeData();
|
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::setLSDDisabled(const bool disable)
|
void TorrentImpl::setLSDDisabled(const bool disable)
|
||||||
@@ -2026,7 +2165,8 @@ void TorrentImpl::setLSDDisabled(const bool disable)
|
|||||||
m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
|
m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
|
||||||
else
|
else
|
||||||
m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
|
m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
|
||||||
saveResumeData();
|
|
||||||
|
m_session->handleTorrentNeedSaveResumeData(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TorrentImpl::flushCache() const
|
void TorrentImpl::flushCache() const
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ namespace BitTorrent
|
|||||||
bool forced = false;
|
bool forced = false;
|
||||||
bool paused = false;
|
bool paused = false;
|
||||||
|
|
||||||
|
|
||||||
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
|
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
|
||||||
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
|
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ namespace BitTorrent
|
|||||||
|
|
||||||
bool isValid() const;
|
bool isValid() const;
|
||||||
|
|
||||||
InfoHash hash() const override;
|
InfoHash infoHash() const override;
|
||||||
QString name() const override;
|
QString name() const override;
|
||||||
QDateTime creationDate() const override;
|
QDateTime creationDate() const override;
|
||||||
QString creator() const override;
|
QString creator() const override;
|
||||||
@@ -301,7 +300,7 @@ namespace BitTorrent
|
|||||||
TorrentInfo m_torrentInfo;
|
TorrentInfo m_torrentInfo;
|
||||||
SpeedMonitor m_speedMonitor;
|
SpeedMonitor m_speedMonitor;
|
||||||
|
|
||||||
InfoHash m_hash;
|
InfoHash m_infoHash;
|
||||||
|
|
||||||
// m_moveFinishedTriggers is activated only when the following conditions are met:
|
// m_moveFinishedTriggers is activated only when the following conditions are met:
|
||||||
// all file rename jobs complete, all file move jobs complete
|
// all file rename jobs complete, all file move jobs complete
|
||||||
|
|||||||
@@ -184,10 +184,15 @@ bool TorrentInfo::isValid() const
|
|||||||
return (m_nativeInfo && m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
|
return (m_nativeInfo && m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoHash TorrentInfo::hash() const
|
InfoHash TorrentInfo::infoHash() const
|
||||||
{
|
{
|
||||||
if (!isValid()) return {};
|
if (!isValid()) return {};
|
||||||
|
|
||||||
|
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||||
|
return m_nativeInfo->info_hashes();
|
||||||
|
#else
|
||||||
return m_nativeInfo->info_hash();
|
return m_nativeInfo->info_hash();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TorrentInfo::name() const
|
QString TorrentInfo::name() const
|
||||||
@@ -302,7 +307,7 @@ QVector<TrackerEntry> TorrentInfo::trackers() const
|
|||||||
ret.reserve(trackers.size());
|
ret.reserve(trackers.size());
|
||||||
|
|
||||||
for (const lt::announce_entry &tracker : trackers)
|
for (const lt::announce_entry &tracker : trackers)
|
||||||
ret.append(tracker);
|
ret.append({QString::fromStdString(tracker.url)});
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,7 +379,7 @@ QVector<QByteArray> TorrentInfo::pieceHashes() const
|
|||||||
hashes.reserve(count);
|
hashes.reserve(count);
|
||||||
|
|
||||||
for (int i = 0; i < count; ++i)
|
for (int i = 0; i < count; ++i)
|
||||||
hashes += {m_nativeInfo->hash_for_piece_ptr(lt::piece_index_t {i}), InfoHash::length()};
|
hashes += {m_nativeInfo->hash_for_piece_ptr(lt::piece_index_t {i}), SHA1Hash::length()};
|
||||||
|
|
||||||
return hashes;
|
return hashes;
|
||||||
}
|
}
|
||||||
@@ -481,9 +486,13 @@ void TorrentInfo::stripRootFolder()
|
|||||||
|
|
||||||
void TorrentInfo::addRootFolder()
|
void TorrentInfo::addRootFolder()
|
||||||
{
|
{
|
||||||
const QString rootFolder = name();
|
const QString originalName = name();
|
||||||
Q_ASSERT(!rootFolder.isEmpty());
|
Q_ASSERT(!originalName.isEmpty());
|
||||||
|
|
||||||
|
const QString extension = Utils::Fs::fileExtension(originalName);
|
||||||
|
const QString rootFolder = extension.isEmpty()
|
||||||
|
? originalName
|
||||||
|
: originalName.chopped(extension.size() + 1);
|
||||||
const std::string rootPrefix = Utils::Fs::toNativePath(rootFolder + QLatin1Char {'/'}).toStdString();
|
const std::string rootPrefix = Utils::Fs::toNativePath(rootFolder + QLatin1Char {'/'}).toStdString();
|
||||||
lt::file_storage files = m_nativeInfo->files();
|
lt::file_storage files = m_nativeInfo->files();
|
||||||
files.set_name(rootFolder.toStdString());
|
files.set_name(rootFolder.toStdString());
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class QUrl;
|
|||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
class InfoHash;
|
class InfoHash;
|
||||||
class TrackerEntry;
|
struct TrackerEntry;
|
||||||
|
|
||||||
class TorrentInfo final : public AbstractFileStorage
|
class TorrentInfo final : public AbstractFileStorage
|
||||||
{
|
{
|
||||||
@@ -62,7 +62,7 @@ namespace BitTorrent
|
|||||||
TorrentInfo &operator=(const TorrentInfo &other);
|
TorrentInfo &operator=(const TorrentInfo &other);
|
||||||
|
|
||||||
bool isValid() const;
|
bool isValid() const;
|
||||||
InfoHash hash() const;
|
InfoHash infoHash() const;
|
||||||
QString name() const;
|
QString name() const;
|
||||||
QDateTime creationDate() const;
|
QDateTime creationDate() const;
|
||||||
QString creator() const;
|
QString creator() const;
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ struct Tracker::TrackerAnnounceRequest
|
|||||||
{
|
{
|
||||||
QHostAddress socketAddress;
|
QHostAddress socketAddress;
|
||||||
QByteArray claimedAddress; // self claimed by peer
|
QByteArray claimedAddress; // self claimed by peer
|
||||||
InfoHash infoHash;
|
TorrentID torrentID;
|
||||||
QString event;
|
QString event;
|
||||||
Peer peer;
|
Peer peer;
|
||||||
int numwant = 50;
|
int numwant = 50;
|
||||||
@@ -295,11 +295,11 @@ void Tracker::processAnnounceRequest()
|
|||||||
if (infoHashIter == queryParams.end())
|
if (infoHashIter == queryParams.end())
|
||||||
throw TrackerError("Missing \"info_hash\" parameter");
|
throw TrackerError("Missing \"info_hash\" parameter");
|
||||||
|
|
||||||
const InfoHash infoHash(infoHashIter->toHex());
|
const auto torrentID = TorrentID::fromString(infoHashIter->toHex());
|
||||||
if (!infoHash.isValid())
|
if (!torrentID.isValid())
|
||||||
throw TrackerError("Invalid \"info_hash\" parameter");
|
throw TrackerError("Invalid \"info_hash\" parameter");
|
||||||
|
|
||||||
announceReq.infoHash = infoHash;
|
announceReq.torrentID = torrentID;
|
||||||
|
|
||||||
// 2. peer_id
|
// 2. peer_id
|
||||||
const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID);
|
const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID);
|
||||||
@@ -381,19 +381,19 @@ void Tracker::processAnnounceRequest()
|
|||||||
|
|
||||||
void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
|
void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
|
||||||
{
|
{
|
||||||
if (!m_torrents.contains(announceReq.infoHash))
|
if (!m_torrents.contains(announceReq.torrentID))
|
||||||
{
|
{
|
||||||
// Reached max size, remove a random torrent
|
// Reached max size, remove a random torrent
|
||||||
if (m_torrents.size() >= MAX_TORRENTS)
|
if (m_torrents.size() >= MAX_TORRENTS)
|
||||||
m_torrents.erase(m_torrents.begin());
|
m_torrents.erase(m_torrents.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
m_torrents[announceReq.infoHash].setPeer(announceReq.peer);
|
m_torrents[announceReq.torrentID].setPeer(announceReq.peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
|
void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
|
||||||
{
|
{
|
||||||
const auto torrentStatsIter = m_torrents.find(announceReq.infoHash);
|
const auto torrentStatsIter = m_torrents.find(announceReq.torrentID);
|
||||||
if (torrentStatsIter == m_torrents.end())
|
if (torrentStatsIter == m_torrents.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -405,7 +405,7 @@ void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
|
|||||||
|
|
||||||
void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
|
void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
|
||||||
{
|
{
|
||||||
const TorrentStats &torrentStats = m_torrents[announceReq.infoHash];
|
const TorrentStats &torrentStats = m_torrents[announceReq.torrentID];
|
||||||
|
|
||||||
lt::entry::dictionary_type replyDict
|
lt::entry::dictionary_type replyDict
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -102,6 +102,6 @@ namespace BitTorrent
|
|||||||
Http::Request m_request;
|
Http::Request m_request;
|
||||||
Http::Environment m_env;
|
Http::Environment m_env;
|
||||||
|
|
||||||
QHash<InfoHash, TorrentStats> m_torrents;
|
QHash<TorrentID, TorrentStats> m_torrents;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2015, 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@@ -28,139 +28,15 @@
|
|||||||
|
|
||||||
#include "trackerentry.h"
|
#include "trackerentry.h"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include <libtorrent/version.hpp>
|
|
||||||
|
|
||||||
#include <QString>
|
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
using namespace BitTorrent;
|
|
||||||
|
|
||||||
TrackerEntry::TrackerEntry(const QString &url)
|
|
||||||
: m_nativeEntry(url.toStdString())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackerEntry::TrackerEntry(const lt::announce_entry &nativeEntry)
|
|
||||||
: m_nativeEntry(nativeEntry)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
QString TrackerEntry::url() const
|
|
||||||
{
|
|
||||||
return QString::fromStdString(nativeEntry().url);
|
|
||||||
}
|
|
||||||
|
|
||||||
int TrackerEntry::tier() const
|
|
||||||
{
|
|
||||||
return nativeEntry().tier;
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackerEntry::Status TrackerEntry::status() const
|
|
||||||
{
|
|
||||||
const auto &endpoints = nativeEntry().endpoints;
|
|
||||||
|
|
||||||
const bool allFailed = !endpoints.empty() && std::all_of(endpoints.begin(), endpoints.end()
|
|
||||||
, [](const lt::announce_endpoint &endpoint)
|
|
||||||
{
|
|
||||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
|
||||||
return std::all_of(endpoint.info_hashes.begin(), endpoint.info_hashes.end()
|
|
||||||
, [](const lt::announce_infohash &infohash)
|
|
||||||
{
|
|
||||||
return (infohash.fails > 0);
|
|
||||||
});
|
|
||||||
#else
|
|
||||||
return (endpoint.fails > 0);
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
if (allFailed)
|
|
||||||
return NotWorking;
|
|
||||||
|
|
||||||
const bool isUpdating = std::any_of(endpoints.begin(), endpoints.end()
|
|
||||||
, [](const lt::announce_endpoint &endpoint)
|
|
||||||
{
|
|
||||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
|
||||||
return std::any_of(endpoint.info_hashes.begin(), endpoint.info_hashes.end()
|
|
||||||
, [](const lt::announce_infohash &infohash)
|
|
||||||
{
|
|
||||||
return infohash.updating;
|
|
||||||
});
|
|
||||||
#else
|
|
||||||
return endpoint.updating;
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
if (isUpdating)
|
|
||||||
return Updating;
|
|
||||||
|
|
||||||
if (!nativeEntry().verified)
|
|
||||||
return NotContacted;
|
|
||||||
|
|
||||||
return Working;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TrackerEntry::setTier(const int value)
|
|
||||||
{
|
|
||||||
m_nativeEntry.tier = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int TrackerEntry::numSeeds() const
|
|
||||||
{
|
|
||||||
int value = -1;
|
|
||||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
|
|
||||||
{
|
|
||||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
|
||||||
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
|
|
||||||
value = std::max(value, infoHash.scrape_complete);
|
|
||||||
#else
|
|
||||||
value = std::max(value, endpoint.scrape_complete);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int TrackerEntry::numLeeches() const
|
|
||||||
{
|
|
||||||
int value = -1;
|
|
||||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
|
|
||||||
{
|
|
||||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
|
||||||
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
|
|
||||||
value = std::max(value, infoHash.scrape_incomplete);
|
|
||||||
#else
|
|
||||||
value = std::max(value, endpoint.scrape_incomplete);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
int TrackerEntry::numDownloaded() const
|
|
||||||
{
|
|
||||||
int value = -1;
|
|
||||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
|
|
||||||
{
|
|
||||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
|
||||||
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
|
|
||||||
value = std::max(value, infoHash.scrape_downloaded);
|
|
||||||
#else
|
|
||||||
value = std::max(value, endpoint.scrape_downloaded);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lt::announce_entry &TrackerEntry::nativeEntry() const
|
|
||||||
{
|
|
||||||
return m_nativeEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool BitTorrent::operator==(const TrackerEntry &left, const TrackerEntry &right)
|
bool BitTorrent::operator==(const TrackerEntry &left, const TrackerEntry &right)
|
||||||
{
|
{
|
||||||
return ((left.tier() == right.tier())
|
return ((left.tier == right.tier)
|
||||||
&& QUrl(left.url()) == QUrl(right.url()));
|
&& QUrl(left.url) == QUrl(right.url));
|
||||||
}
|
}
|
||||||
|
|
||||||
uint BitTorrent::qHash(const TrackerEntry &key, const uint seed)
|
uint BitTorrent::qHash(const TrackerEntry &key, const uint seed)
|
||||||
{
|
{
|
||||||
return (::qHash(key.url(), seed) ^ ::qHash(key.tier()));
|
return (::qHash(key.url, seed) ^ ::qHash(key.tier));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
* Copyright (C) 2015, 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU General Public License
|
* modify it under the terms of the GNU General Public License
|
||||||
@@ -28,17 +28,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <libtorrent/announce_entry.hpp>
|
|
||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
|
#include <QString>
|
||||||
class QString;
|
#include <QVector>
|
||||||
|
|
||||||
namespace BitTorrent
|
namespace BitTorrent
|
||||||
{
|
{
|
||||||
class TrackerEntry
|
struct TrackerEntry
|
||||||
{
|
{
|
||||||
public:
|
|
||||||
enum Status
|
enum Status
|
||||||
{
|
{
|
||||||
NotContacted = 1,
|
NotContacted = 1,
|
||||||
@@ -47,26 +44,26 @@ namespace BitTorrent
|
|||||||
NotWorking = 4
|
NotWorking = 4
|
||||||
};
|
};
|
||||||
|
|
||||||
TrackerEntry() = default;
|
struct EndpointStats
|
||||||
TrackerEntry(const QString &url);
|
{
|
||||||
TrackerEntry(const lt::announce_entry &nativeEntry);
|
int protocolVersion = 1;
|
||||||
TrackerEntry(const TrackerEntry &other) = default;
|
|
||||||
TrackerEntry &operator=(const TrackerEntry &other) = default;
|
|
||||||
|
|
||||||
QString url() const;
|
Status status = NotContacted;
|
||||||
Status status() const;
|
int numSeeds = -1;
|
||||||
|
int numLeeches = -1;
|
||||||
|
int numDownloaded = -1;
|
||||||
|
};
|
||||||
|
|
||||||
int tier() const;
|
QString url;
|
||||||
void setTier(int value);
|
int tier = 0;
|
||||||
|
|
||||||
int numSeeds() const;
|
QVector<EndpointStats> endpoints {};
|
||||||
int numLeeches() const;
|
|
||||||
int numDownloaded() const;
|
|
||||||
|
|
||||||
const lt::announce_entry &nativeEntry() const;
|
// Deprecated fields
|
||||||
|
Status status = NotContacted;
|
||||||
private:
|
int numSeeds = -1;
|
||||||
lt::announce_entry m_nativeEntry;
|
int numLeeches = -1;
|
||||||
|
int numDownloaded = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool operator==(const TrackerEntry &left, const TrackerEntry &right);
|
bool operator==(const TrackerEntry &left, const TrackerEntry &right);
|
||||||
|
|||||||
121
src/base/digest32.h
Normal file
121
src/base/digest32.h
Normal 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);
|
||||||
|
}
|
||||||
@@ -37,7 +37,6 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QHostInfo>
|
#include <QHostInfo>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QTextCodec>
|
|
||||||
|
|
||||||
#ifndef QT_NO_OPENSSL
|
#ifndef QT_NO_OPENSSL
|
||||||
#include <QSslSocket>
|
#include <QSslSocket>
|
||||||
@@ -89,6 +88,14 @@ namespace
|
|||||||
|
|
||||||
return hostname.toLocal8Bit();
|
return hostname.toLocal8Bit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool canEncodeAsLatin1(const QStringView string)
|
||||||
|
{
|
||||||
|
return std::none_of(string.cbegin(), string.cend(), [](const QChar &ch)
|
||||||
|
{
|
||||||
|
return ch > QChar(0xff);
|
||||||
|
});
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
using namespace Net;
|
using namespace Net;
|
||||||
@@ -137,11 +144,10 @@ Smtp::~Smtp()
|
|||||||
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body)
|
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body)
|
||||||
{
|
{
|
||||||
const Preferences *const pref = Preferences::instance();
|
const Preferences *const pref = Preferences::instance();
|
||||||
QTextCodec *latin1 = QTextCodec::codecForName("latin1");
|
|
||||||
m_message = "Date: " + getCurrentDateTime().toLatin1() + "\r\n"
|
m_message = "Date: " + getCurrentDateTime().toLatin1() + "\r\n"
|
||||||
+ encodeMimeHeader("From", from, latin1)
|
+ encodeMimeHeader("From", from)
|
||||||
+ encodeMimeHeader("Subject", subject, latin1)
|
+ encodeMimeHeader("Subject", subject)
|
||||||
+ encodeMimeHeader("To", to, latin1)
|
+ encodeMimeHeader("To", to)
|
||||||
+ "MIME-Version: 1.0\r\n"
|
+ "MIME-Version: 1.0\r\n"
|
||||||
+ "Content-Type: text/plain; charset=UTF-8\r\n"
|
+ "Content-Type: text/plain; charset=UTF-8\r\n"
|
||||||
+ "Content-Transfer-Encoding: base64\r\n"
|
+ "Content-Transfer-Encoding: base64\r\n"
|
||||||
@@ -312,12 +318,12 @@ void Smtp::readyRead()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, const QTextCodec *latin1, const QByteArray &prefix)
|
QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, const QByteArray &prefix)
|
||||||
{
|
{
|
||||||
QByteArray rv = "";
|
QByteArray rv = "";
|
||||||
QByteArray line = key.toLatin1() + ": ";
|
QByteArray line = key.toLatin1() + ": ";
|
||||||
if (!prefix.isEmpty()) line += prefix;
|
if (!prefix.isEmpty()) line += prefix;
|
||||||
if (!value.contains("=?") && latin1->canEncode(value))
|
if (!value.contains("=?") && canEncodeAsLatin1(value))
|
||||||
{
|
{
|
||||||
bool firstWord = true;
|
bool firstWord = true;
|
||||||
for (const QByteArray &word : asConst(value.toLatin1().split(' ')))
|
for (const QByteArray &word : asConst(value.toLatin1().split(' ')))
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ namespace Net
|
|||||||
AuthCramMD5
|
AuthCramMD5
|
||||||
};
|
};
|
||||||
|
|
||||||
QByteArray encodeMimeHeader(const QString &key, const QString &value, const QTextCodec *latin1, const QByteArray &prefix = {});
|
QByteArray encodeMimeHeader(const QString &key, const QString &value, const QByteArray &prefix = {});
|
||||||
void ehlo();
|
void ehlo();
|
||||||
void helo();
|
void helo();
|
||||||
void parseEhloResponse(const QByteArray &code, bool continued, const QString &line);
|
void parseEhloResponse(const QByteArray &code, bool continued, const QString &line);
|
||||||
|
|||||||
@@ -234,6 +234,16 @@ void Preferences::setCloseToTrayNotified(const bool b)
|
|||||||
{
|
{
|
||||||
setValue("Preferences/General/CloseToTrayNotified", b);
|
setValue("Preferences/General/CloseToTrayNotified", b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Preferences::iconsInMenusEnabled() const
|
||||||
|
{
|
||||||
|
return value("Preferences/Advanced/EnableIconsInMenus", true).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Preferences::setIconsInMenusEnabled(const bool enable)
|
||||||
|
{
|
||||||
|
setValue("Preferences/Advanced/EnableIconsInMenus", enable);
|
||||||
|
}
|
||||||
#endif // Q_OS_MACOS
|
#endif // Q_OS_MACOS
|
||||||
|
|
||||||
bool Preferences::isToolbarDisplayed() const
|
bool Preferences::isToolbarDisplayed() const
|
||||||
@@ -853,7 +863,7 @@ void Preferences::setAutoRunProgram(const QString &program)
|
|||||||
setValue("AutoRun/program", program);
|
setValue("AutoRun/program", program);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) && (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
#if defined(Q_OS_WIN)
|
||||||
bool Preferences::isAutoRunConsoleEnabled() const
|
bool Preferences::isAutoRunConsoleEnabled() const
|
||||||
{
|
{
|
||||||
return value("AutoRun/ConsoleEnabled", false).toBool();
|
return value("AutoRun/ConsoleEnabled", false).toBool();
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ public:
|
|||||||
void setAutoRunEnabled(bool enabled);
|
void setAutoRunEnabled(bool enabled);
|
||||||
QString getAutoRunProgram() const;
|
QString getAutoRunProgram() const;
|
||||||
void setAutoRunProgram(const QString &program);
|
void setAutoRunProgram(const QString &program);
|
||||||
#if defined(Q_OS_WIN) && (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
#if defined(Q_OS_WIN)
|
||||||
bool isAutoRunConsoleEnabled() const;
|
bool isAutoRunConsoleEnabled() const;
|
||||||
void setAutoRunConsoleEnabled(bool enabled);
|
void setAutoRunConsoleEnabled(bool enabled);
|
||||||
#endif
|
#endif
|
||||||
@@ -313,6 +313,8 @@ public:
|
|||||||
void setCloseToTrayNotified(bool b);
|
void setCloseToTrayNotified(bool b);
|
||||||
TrayIcon::Style trayIconStyle() const;
|
TrayIcon::Style trayIconStyle() const;
|
||||||
void setTrayIconStyle(TrayIcon::Style style);
|
void setTrayIconStyle(TrayIcon::Style style);
|
||||||
|
bool iconsInMenusEnabled() const;
|
||||||
|
void setIconsInMenusEnabled(bool enable);
|
||||||
#endif // Q_OS_MACOS
|
#endif // Q_OS_MACOS
|
||||||
|
|
||||||
// Stuff that don't appear in the Options GUI but are saved
|
// Stuff that don't appear in the Options GUI but are saved
|
||||||
|
|||||||
@@ -213,10 +213,8 @@ QRegularExpression AutoDownloadRule::cachedRegex(const QString &expression, cons
|
|||||||
QRegularExpression ®ex = m_dataPtr->cachedRegexes[expression];
|
QRegularExpression ®ex = m_dataPtr->cachedRegexes[expression];
|
||||||
if (regex.pattern().isEmpty())
|
if (regex.pattern().isEmpty())
|
||||||
{
|
{
|
||||||
regex = QRegularExpression
|
const QString pattern = (isRegex ? expression : Utils::String::wildcardToRegexPattern(expression));
|
||||||
{
|
regex = QRegularExpression {pattern, QRegularExpression::CaseInsensitiveOption};
|
||||||
(isRegex ? expression : Utils::String::wildcardToRegex(expression))
|
|
||||||
, QRegularExpression::CaseInsensitiveOption};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return regex;
|
return regex;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
#include <QGlobalStatic>
|
#include <QGlobalStatic>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
#include <QRegExp>
|
#include <QRegularExpression>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QXmlStreamEntityResolver>
|
#include <QXmlStreamEntityResolver>
|
||||||
@@ -391,12 +391,13 @@ namespace
|
|||||||
int nmin = 8;
|
int nmin = 8;
|
||||||
int nsec = 9;
|
int nsec = 9;
|
||||||
// Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm"
|
// Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm"
|
||||||
QRegExp rx("^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$");
|
QRegularExpression rx {"^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$"};
|
||||||
|
QRegularExpressionMatch rxMatch;
|
||||||
QStringList parts;
|
QStringList parts;
|
||||||
if (!str.indexOf(rx))
|
if (str.indexOf(rx, 0, &rxMatch) == 0)
|
||||||
{
|
{
|
||||||
// Check that if date has '-' separators, both separators are '-'.
|
// Check that if date has '-' separators, both separators are '-'.
|
||||||
parts = rx.capturedTexts();
|
parts = rxMatch.capturedTexts();
|
||||||
const bool h1 = (parts[3] == QLatin1String("-"));
|
const bool h1 = (parts[3] == QLatin1String("-"));
|
||||||
const bool h2 = (parts[5] == QLatin1String("-"));
|
const bool h2 = (parts[5] == QLatin1String("-"));
|
||||||
if (h1 != h2)
|
if (h1 != h2)
|
||||||
@@ -405,9 +406,10 @@ namespace
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
|
// Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
|
||||||
rx = QRegExp("^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$");
|
rx = QRegularExpression {"^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$"};
|
||||||
if (str.indexOf(rx))
|
if (str.indexOf(rx, 0, &rxMatch) != 0)
|
||||||
return QDateTime::currentDateTime();
|
return QDateTime::currentDateTime();
|
||||||
|
|
||||||
nyear = 7;
|
nyear = 7;
|
||||||
nmonth = 2;
|
nmonth = 2;
|
||||||
nday = 3;
|
nday = 3;
|
||||||
@@ -415,7 +417,7 @@ namespace
|
|||||||
nhour = 4;
|
nhour = 4;
|
||||||
nmin = 5;
|
nmin = 5;
|
||||||
nsec = 6;
|
nsec = 6;
|
||||||
parts = rx.capturedTexts();
|
parts = rxMatch.capturedTexts();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ok[4];
|
bool ok[4];
|
||||||
@@ -463,11 +465,11 @@ namespace
|
|||||||
bool negOffset = false;
|
bool negOffset = false;
|
||||||
if (parts.count() > 10)
|
if (parts.count() > 10)
|
||||||
{
|
{
|
||||||
rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$");
|
rx = QRegularExpression {"^([+-])(\\d\\d)(\\d\\d)$"};
|
||||||
if (!parts[10].indexOf(rx))
|
if (parts[10].indexOf(rx, 0, &rxMatch) == 0)
|
||||||
{
|
{
|
||||||
// It's a UTC offset ±hhmm
|
// It's a UTC offset ±hhmm
|
||||||
parts = rx.capturedTexts();
|
parts = rxMatch.capturedTexts();
|
||||||
offset = parts[2].toInt(&ok[0]) * 3600;
|
offset = parts[2].toInt(&ok[0]) * 3600;
|
||||||
const int offsetMin = parts[3].toInt(&ok[1]);
|
const int offsetMin = parts[3].toInt(&ok[1]);
|
||||||
if (!ok[0] || !ok[1] || offsetMin > 59)
|
if (!ok[0] || !ok[1] || offsetMin > 59)
|
||||||
@@ -547,13 +549,8 @@ Parser::Parser(const QString lastBuildDate)
|
|||||||
|
|
||||||
void Parser::parse(const QByteArray &feedData)
|
void Parser::parse(const QByteArray &feedData)
|
||||||
{
|
{
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
QMetaObject::invokeMethod(this, [this, feedData]() { parse_impl(feedData); }
|
QMetaObject::invokeMethod(this, [this, feedData]() { parse_impl(feedData); }
|
||||||
, Qt::QueuedConnection);
|
, Qt::QueuedConnection);
|
||||||
#else
|
|
||||||
QMetaObject::invokeMethod(this, "parse_impl", Qt::QueuedConnection
|
|
||||||
, Q_ARG(QByteArray, feedData));
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// read and create items from a rss document
|
// read and create items from a rss document
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
#include "bittorrent/torrent.h"
|
#include "bittorrent/torrent.h"
|
||||||
|
|
||||||
const QString TorrentFilter::AnyCategory;
|
const QString TorrentFilter::AnyCategory;
|
||||||
const InfoHashSet TorrentFilter::AnyHash {{}};
|
const TorrentIDSet TorrentFilter::AnyID {{}};
|
||||||
const QString TorrentFilter::AnyTag;
|
const QString TorrentFilter::AnyTag;
|
||||||
|
|
||||||
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
|
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
|
||||||
@@ -49,19 +49,19 @@ const TorrentFilter TorrentFilter::ErroredTorrent(TorrentFilter::Errored);
|
|||||||
|
|
||||||
using BitTorrent::Torrent;
|
using BitTorrent::Torrent;
|
||||||
|
|
||||||
TorrentFilter::TorrentFilter(const Type type, const InfoHashSet &hashSet, const QString &category, const QString &tag)
|
TorrentFilter::TorrentFilter(const Type type, const TorrentIDSet &idSet, const QString &category, const QString &tag)
|
||||||
: m_type(type)
|
: m_type(type)
|
||||||
, m_category(category)
|
, m_category(category)
|
||||||
, m_tag(tag)
|
, m_tag(tag)
|
||||||
, m_hashSet(hashSet)
|
, m_idSet(idSet)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
TorrentFilter::TorrentFilter(const QString &filter, const InfoHashSet &hashSet, const QString &category, const QString &tag)
|
TorrentFilter::TorrentFilter(const QString &filter, const TorrentIDSet &idSet, const QString &category, const QString &tag)
|
||||||
: m_type(All)
|
: m_type(All)
|
||||||
, m_category(category)
|
, m_category(category)
|
||||||
, m_tag(tag)
|
, m_tag(tag)
|
||||||
, m_hashSet(hashSet)
|
, m_idSet(idSet)
|
||||||
{
|
{
|
||||||
setTypeByName(filter);
|
setTypeByName(filter);
|
||||||
}
|
}
|
||||||
@@ -107,11 +107,11 @@ bool TorrentFilter::setTypeByName(const QString &filter)
|
|||||||
return setType(type);
|
return setType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TorrentFilter::setHashSet(const InfoHashSet &hashSet)
|
bool TorrentFilter::setTorrentIDSet(const TorrentIDSet &idSet)
|
||||||
{
|
{
|
||||||
if (m_hashSet != hashSet)
|
if (m_idSet != idSet)
|
||||||
{
|
{
|
||||||
m_hashSet = hashSet;
|
m_idSet = idSet;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,9 +189,9 @@ bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
|||||||
|
|
||||||
bool TorrentFilter::matchHash(const BitTorrent::Torrent *const torrent) const
|
bool TorrentFilter::matchHash(const BitTorrent::Torrent *const torrent) const
|
||||||
{
|
{
|
||||||
if (m_hashSet == AnyHash) return true;
|
if (m_idSet == AnyID) return true;
|
||||||
|
|
||||||
return m_hashSet.contains(torrent->hash());
|
return m_idSet.contains(torrent->id());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TorrentFilter::matchCategory(const BitTorrent::Torrent *const torrent) const
|
bool TorrentFilter::matchCategory(const BitTorrent::Torrent *const torrent) const
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace BitTorrent
|
|||||||
class Torrent;
|
class Torrent;
|
||||||
}
|
}
|
||||||
|
|
||||||
using InfoHashSet = QSet<BitTorrent::InfoHash>;
|
using TorrentIDSet = QSet<BitTorrent::TorrentID>;
|
||||||
|
|
||||||
class TorrentFilter
|
class TorrentFilter
|
||||||
{
|
{
|
||||||
@@ -61,7 +61,7 @@ public:
|
|||||||
|
|
||||||
// These mean any permutation, including no category / tag.
|
// These mean any permutation, including no category / tag.
|
||||||
static const QString AnyCategory;
|
static const QString AnyCategory;
|
||||||
static const InfoHashSet AnyHash;
|
static const TorrentIDSet AnyID;
|
||||||
static const QString AnyTag;
|
static const QString AnyTag;
|
||||||
|
|
||||||
static const TorrentFilter DownloadingTorrent;
|
static const TorrentFilter DownloadingTorrent;
|
||||||
@@ -79,12 +79,12 @@ public:
|
|||||||
TorrentFilter() = default;
|
TorrentFilter() = default;
|
||||||
// category & tags: pass empty string for uncategorized / untagged torrents.
|
// category & tags: pass empty string for uncategorized / untagged torrents.
|
||||||
// Pass null string (QString()) to disable filtering (i.e. all torrents).
|
// Pass null string (QString()) to disable filtering (i.e. all torrents).
|
||||||
TorrentFilter(Type type, const InfoHashSet &hashSet = AnyHash, const QString &category = AnyCategory, const QString &tag = AnyTag);
|
TorrentFilter(Type type, const TorrentIDSet &idSet = AnyID, const QString &category = AnyCategory, const QString &tag = AnyTag);
|
||||||
TorrentFilter(const QString &filter, const InfoHashSet &hashSet = AnyHash, const QString &category = AnyCategory, const QString &tags = AnyTag);
|
TorrentFilter(const QString &filter, const TorrentIDSet &idSet = AnyID, const QString &category = AnyCategory, const QString &tags = AnyTag);
|
||||||
|
|
||||||
bool setType(Type type);
|
bool setType(Type type);
|
||||||
bool setTypeByName(const QString &filter);
|
bool setTypeByName(const QString &filter);
|
||||||
bool setHashSet(const InfoHashSet &hashSet);
|
bool setTorrentIDSet(const TorrentIDSet &idSet);
|
||||||
bool setCategory(const QString &category);
|
bool setCategory(const QString &category);
|
||||||
bool setTag(const QString &tag);
|
bool setTag(const QString &tag);
|
||||||
|
|
||||||
@@ -99,5 +99,5 @@ private:
|
|||||||
Type m_type {All};
|
Type m_type {All};
|
||||||
QString m_category;
|
QString m_category;
|
||||||
QString m_tag;
|
QString m_tag;
|
||||||
InfoHashSet m_hashSet;
|
TorrentIDSet m_idSet;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -78,11 +78,7 @@ QString Utils::Fs::toUniformPath(const QString &path)
|
|||||||
QString Utils::Fs::fileExtension(const QString &filename)
|
QString Utils::Fs::fileExtension(const QString &filename)
|
||||||
{
|
{
|
||||||
const QString name = filename.endsWith(QB_EXT)
|
const QString name = filename.endsWith(QB_EXT)
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
? filename.chopped(QB_EXT.length())
|
? filename.chopped(QB_EXT.length())
|
||||||
#else
|
|
||||||
? filename.left(filename.length() - QB_EXT.length())
|
|
||||||
#endif
|
|
||||||
: filename;
|
: filename;
|
||||||
return QMimeDatabase().suffixForFileName(name);
|
return QMimeDatabase().suffixForFileName(name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <powrprof.h>
|
#include <powrprof.h>
|
||||||
@@ -49,6 +51,7 @@
|
|||||||
#include <zlib.h>
|
#include <zlib.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QMimeDatabase>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QSysInfo>
|
#include <QSysInfo>
|
||||||
@@ -60,6 +63,7 @@
|
|||||||
|
|
||||||
#include "base/types.h"
|
#include "base/types.h"
|
||||||
#include "base/unicodestrings.h"
|
#include "base/unicodestrings.h"
|
||||||
|
#include "base/utils/fs.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -80,21 +84,26 @@ namespace
|
|||||||
// see http://en.wikipedia.org/wiki/Kilobyte
|
// see http://en.wikipedia.org/wiki/Kilobyte
|
||||||
// value must be given in bytes
|
// value must be given in bytes
|
||||||
// to send numbers instead of strings with suffixes
|
// to send numbers instead of strings with suffixes
|
||||||
bool splitToFriendlyUnit(const qint64 sizeInBytes, qreal &val, Utils::Misc::SizeUnit &unit)
|
struct SplitToFriendlyUnitResult
|
||||||
{
|
{
|
||||||
if (sizeInBytes < 0) return false;
|
qreal value;
|
||||||
|
Utils::Misc::SizeUnit unit;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<SplitToFriendlyUnitResult> splitToFriendlyUnit(const qint64 bytes)
|
||||||
|
{
|
||||||
|
if (bytes < 0)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
val = static_cast<qreal>(sizeInBytes);
|
auto value = static_cast<qreal>(bytes);
|
||||||
|
|
||||||
while ((val >= 1024.) && (i <= static_cast<int>(Utils::Misc::SizeUnit::ExbiByte)))
|
while ((value >= 1024) && (i < static_cast<int>(Utils::Misc::SizeUnit::ExbiByte)))
|
||||||
{
|
{
|
||||||
val /= 1024.;
|
value /= 1024;
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
return {{value, static_cast<Utils::Misc::SizeUnit>(i)}};
|
||||||
unit = static_cast<Utils::Misc::SizeUnit>(i);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,15 +258,14 @@ QString Utils::Misc::unitString(const SizeUnit unit, const bool isSpeed)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Utils::Misc::friendlyUnit(const qint64 bytesValue, const bool isSpeed)
|
QString Utils::Misc::friendlyUnit(const qint64 bytes, const bool isSpeed)
|
||||||
{
|
{
|
||||||
SizeUnit unit;
|
const std::optional<SplitToFriendlyUnitResult> result = splitToFriendlyUnit(bytes);
|
||||||
qreal friendlyVal;
|
if (!result)
|
||||||
if (!splitToFriendlyUnit(bytesValue, friendlyVal, unit))
|
|
||||||
return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
|
return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
|
||||||
return Utils::String::fromDouble(friendlyVal, friendlyUnitPrecision(unit))
|
return Utils::String::fromDouble(result->value, friendlyUnitPrecision(result->unit))
|
||||||
+ QString::fromUtf8(C_NON_BREAKING_SPACE)
|
+ QString::fromUtf8(C_NON_BREAKING_SPACE)
|
||||||
+ unitString(unit, isSpeed);
|
+ unitString(result->unit, isSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit)
|
int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit)
|
||||||
@@ -284,9 +292,17 @@ qlonglong Utils::Misc::sizeInBytes(qreal size, const Utils::Misc::SizeUnit unit)
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::Misc::isPreviewable(const QString &extension)
|
bool Utils::Misc::isPreviewable(const QString &filename)
|
||||||
{
|
{
|
||||||
static const QSet<QString> multimediaExtensions =
|
const QString mime = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension).name();
|
||||||
|
|
||||||
|
if (mime.startsWith(QLatin1String("audio"), Qt::CaseInsensitive)
|
||||||
|
|| mime.startsWith(QLatin1String("video"), Qt::CaseInsensitive))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QSet<QString> multimediaExtensions =
|
||||||
{
|
{
|
||||||
"3GP",
|
"3GP",
|
||||||
"AAC",
|
"AAC",
|
||||||
@@ -331,7 +347,7 @@ bool Utils::Misc::isPreviewable(const QString &extension)
|
|||||||
"WMA",
|
"WMA",
|
||||||
"WMV"
|
"WMV"
|
||||||
};
|
};
|
||||||
return multimediaExtensions.contains(extension.toUpper());
|
return multimediaExtensions.contains(Utils::Fs::fileExtension(filename).toUpper());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap)
|
QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap)
|
||||||
|
|||||||
@@ -73,11 +73,11 @@ namespace Utils::Misc
|
|||||||
|
|
||||||
// return the best user friendly storage unit (B, KiB, MiB, GiB, TiB)
|
// return the best user friendly storage unit (B, KiB, MiB, GiB, TiB)
|
||||||
// value must be given in bytes
|
// value must be given in bytes
|
||||||
QString friendlyUnit(qint64 bytesValue, bool isSpeed = false);
|
QString friendlyUnit(qint64 bytes, bool isSpeed = false);
|
||||||
int friendlyUnitPrecision(SizeUnit unit);
|
int friendlyUnitPrecision(SizeUnit unit);
|
||||||
qint64 sizeInBytes(qreal size, SizeUnit unit);
|
qint64 sizeInBytes(qreal size, SizeUnit unit);
|
||||||
|
|
||||||
bool isPreviewable(const QString &extension);
|
bool isPreviewable(const QString &filename);
|
||||||
|
|
||||||
// Take a number of seconds and return a user-friendly
|
// Take a number of seconds and return a user-friendly
|
||||||
// time duration like "1d 2h 10m".
|
// time duration like "1d 2h 10m".
|
||||||
|
|||||||
@@ -33,10 +33,15 @@
|
|||||||
|
|
||||||
#include <QCollator>
|
#include <QCollator>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
#include <QRegExp>
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#else
|
||||||
|
#include <QRegExp>
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_MACOS) || defined(__MINGW32__)
|
#if defined(Q_OS_MACOS) || defined(__MINGW32__)
|
||||||
#define QBT_USES_QTHREADSTORAGE
|
#define QBT_USES_QTHREADSTORAGE
|
||||||
#include <QThreadStorage>
|
#include <QThreadStorage>
|
||||||
@@ -181,14 +186,21 @@ QString Utils::String::fromDouble(const double n, const int precision)
|
|||||||
return QLocale::system().toString(std::floor(n * prec) / prec, 'f', precision);
|
return QLocale::system().toString(std::floor(n * prec) / prec, 'f', precision);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||||
|
QString Utils::String::wildcardToRegexPattern(const QString &pattern)
|
||||||
|
{
|
||||||
|
return QRegularExpression::wildcardToRegularExpression(pattern, QRegularExpression::UnanchoredWildcardConversion);
|
||||||
|
}
|
||||||
|
#else
|
||||||
// This is marked as internal in QRegExp.cpp, but is exported. The alternative would be to
|
// This is marked as internal in QRegExp.cpp, but is exported. The alternative would be to
|
||||||
// copy the code from QRegExp::wc2rx().
|
// copy the code from QRegExp::wc2rx().
|
||||||
QString qt_regexp_toCanonical(const QString &pattern, QRegExp::PatternSyntax patternSyntax);
|
QString qt_regexp_toCanonical(const QString &pattern, QRegExp::PatternSyntax patternSyntax);
|
||||||
|
|
||||||
QString Utils::String::wildcardToRegex(const QString &pattern)
|
QString Utils::String::wildcardToRegexPattern(const QString &pattern)
|
||||||
{
|
{
|
||||||
return qt_regexp_toCanonical(pattern, QRegExp::Wildcard);
|
return qt_regexp_toCanonical(pattern, QRegExp::Wildcard);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
std::optional<bool> Utils::String::parseBool(const QString &string)
|
std::optional<bool> Utils::String::parseBool(const QString &string)
|
||||||
{
|
{
|
||||||
@@ -200,6 +212,26 @@ std::optional<bool> Utils::String::parseBool(const QString &string)
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<int> Utils::String::parseInt(const QString &string)
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
const int result = string.toInt(&ok);
|
||||||
|
if (ok)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<double> Utils::String::parseDouble(const QString &string)
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
const double result = string.toDouble(&ok);
|
||||||
|
if (ok)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
QString Utils::String::join(const QVector<QStringRef> &strings, const QString &separator)
|
QString Utils::String::join(const QVector<QStringRef> &strings, const QString &separator)
|
||||||
{
|
{
|
||||||
if (strings.empty())
|
if (strings.empty())
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ namespace Utils::String
|
|||||||
return (naturalCompare(left, right, caseSensitivity) < 0);
|
return (naturalCompare(left, right, caseSensitivity) < 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString wildcardToRegex(const QString &pattern);
|
QString wildcardToRegexPattern(const QString &pattern);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T unquote(const T &str, const QString "es = QChar('"'))
|
T unquote(const T &str, const QString "es = QChar('"'))
|
||||||
@@ -67,6 +67,8 @@ namespace Utils::String
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<bool> parseBool(const QString &string);
|
std::optional<bool> parseBool(const QString &string);
|
||||||
|
std::optional<int> parseInt(const QString &string);
|
||||||
|
std::optional<double> parseDouble(const QString &string);
|
||||||
|
|
||||||
QString join(const QVector<QStringRef> &strings, const QString &separator);
|
QString join(const QVector<QStringRef> &strings, const QString &separator);
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
#define QBT_VERSION_MAJOR 4
|
#define QBT_VERSION_MAJOR 4
|
||||||
#define QBT_VERSION_MINOR 3
|
#define QBT_VERSION_MINOR 3
|
||||||
#define QBT_VERSION_BUGFIX 3
|
#define QBT_VERSION_BUGFIX 4
|
||||||
#define QBT_VERSION_BUILD 0
|
#define QBT_VERSION_BUILD 0
|
||||||
#define QBT_VERSION_STATUS "" // Should be empty for stable releases!
|
#define QBT_VERSION_STATUS "" // Should be empty for stable releases!
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ add_library(qbt_gui STATIC
|
|||||||
powermanagement/powermanagement.h
|
powermanagement/powermanagement.h
|
||||||
previewlistdelegate.h
|
previewlistdelegate.h
|
||||||
previewselectdialog.h
|
previewselectdialog.h
|
||||||
progressbardelegate.h
|
progressbarpainter.h
|
||||||
properties/downloadedpiecesbar.h
|
properties/downloadedpiecesbar.h
|
||||||
properties/peerlistsortmodel.h
|
properties/peerlistsortmodel.h
|
||||||
properties/peerlistwidget.h
|
properties/peerlistwidget.h
|
||||||
@@ -106,7 +106,7 @@ add_library(qbt_gui STATIC
|
|||||||
powermanagement/powermanagement.cpp
|
powermanagement/powermanagement.cpp
|
||||||
previewlistdelegate.cpp
|
previewlistdelegate.cpp
|
||||||
previewselectdialog.cpp
|
previewselectdialog.cpp
|
||||||
progressbardelegate.cpp
|
progressbarpainter.cpp
|
||||||
properties/downloadedpiecesbar.cpp
|
properties/downloadedpiecesbar.cpp
|
||||||
properties/peerlistsortmodel.cpp
|
properties/peerlistsortmodel.cpp
|
||||||
properties/peerlistwidget.cpp
|
properties/peerlistwidget.cpp
|
||||||
|
|||||||
@@ -281,6 +281,9 @@
|
|||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QTextBrowser" name="textBrowserTranslation">
|
<widget class="QTextBrowser" name="textBrowserTranslation">
|
||||||
|
<property name="openExternalLinks">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="lineWrapMode">
|
<property name="lineWrapMode">
|
||||||
<enum>QTextEdit::NoWrap</enum>
|
<enum>QTextEdit::NoWrap</enum>
|
||||||
</property>
|
</property>
|
||||||
|
|||||||
@@ -270,12 +270,12 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
|
|||||||
bool AddNewTorrentDialog::loadTorrentImpl()
|
bool AddNewTorrentDialog::loadTorrentImpl()
|
||||||
{
|
{
|
||||||
m_hasMetadata = true;
|
m_hasMetadata = true;
|
||||||
const BitTorrent::InfoHash infoHash = m_torrentInfo.hash();
|
const auto torrentID = BitTorrent::TorrentID::fromInfoHash(m_torrentInfo.infoHash());
|
||||||
|
|
||||||
// Prevent showing the dialog if download is already present
|
// Prevent showing the dialog if download is already present
|
||||||
if (BitTorrent::Session::instance()->isKnownTorrent(infoHash))
|
if (BitTorrent::Session::instance()->isKnownTorrent(torrentID))
|
||||||
{
|
{
|
||||||
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(infoHash);
|
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(torrentID);
|
||||||
if (torrent)
|
if (torrent)
|
||||||
{
|
{
|
||||||
if (torrent->isPrivate() || m_torrentInfo.isPrivate())
|
if (torrent->isPrivate() || m_torrentInfo.isPrivate())
|
||||||
@@ -296,7 +296,7 @@ bool AddNewTorrentDialog::loadTorrentImpl()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ui->labelHashData->setText(infoHash);
|
m_ui->labelHashData->setText(torrentID.toString());
|
||||||
setupTreeview();
|
setupTreeview();
|
||||||
TMMChanged(m_ui->comboTTM->currentIndex());
|
TMMChanged(m_ui->comboTTM->currentIndex());
|
||||||
return true;
|
return true;
|
||||||
@@ -312,11 +312,11 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
|
|||||||
|
|
||||||
m_torrentGuard = std::make_unique<TorrentFileGuard>();
|
m_torrentGuard = std::make_unique<TorrentFileGuard>();
|
||||||
|
|
||||||
const BitTorrent::InfoHash infoHash = magnetUri.hash();
|
const auto torrentID = BitTorrent::TorrentID::fromInfoHash(magnetUri.infoHash());
|
||||||
// Prevent showing the dialog if download is already present
|
// Prevent showing the dialog if download is already present
|
||||||
if (BitTorrent::Session::instance()->isKnownTorrent(infoHash))
|
if (BitTorrent::Session::instance()->isKnownTorrent(torrentID))
|
||||||
{
|
{
|
||||||
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(infoHash);
|
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(torrentID);
|
||||||
if (torrent)
|
if (torrent)
|
||||||
{
|
{
|
||||||
if (torrent->isPrivate())
|
if (torrent->isPrivate())
|
||||||
@@ -348,7 +348,7 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
|
|||||||
|
|
||||||
BitTorrent::Session::instance()->downloadMetadata(magnetUri);
|
BitTorrent::Session::instance()->downloadMetadata(magnetUri);
|
||||||
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
||||||
m_ui->labelHashData->setText(infoHash);
|
m_ui->labelHashData->setText(torrentID.toString());
|
||||||
|
|
||||||
m_magnetURI = magnetUri;
|
m_magnetURI = magnetUri;
|
||||||
return true;
|
return true;
|
||||||
@@ -502,12 +502,12 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
|
|||||||
{
|
{
|
||||||
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
|
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
|
||||||
|
|
||||||
const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio)
|
const auto applyPriorities = [this](const BitTorrent::DownloadPriority prio)
|
||||||
{
|
{
|
||||||
|
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
|
||||||
for (const QModelIndex &index : selectedRows)
|
for (const QModelIndex &index : selectedRows)
|
||||||
{
|
{
|
||||||
m_contentModel->setData(
|
m_contentModel->setData(index.sibling(index.row(), PRIORITY)
|
||||||
m_contentModel->index(index.row(), PRIORITY, index.parent())
|
|
||||||
, static_cast<int>(prio));
|
, static_cast<int>(prio));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -517,37 +517,63 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
|
|||||||
|
|
||||||
if (selectedRows.size() == 1)
|
if (selectedRows.size() == 1)
|
||||||
{
|
{
|
||||||
QAction *actRename = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."));
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
|
||||||
connect(actRename, &QAction::triggered, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
|
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
QMenu *subMenu = menu->addMenu(tr("Priority"));
|
QMenu *subMenu = menu->addMenu(tr("Priority"));
|
||||||
|
|
||||||
connect(m_ui->actionNotDownloaded, &QAction::triggered, subMenu, [applyPriorities]()
|
subMenu->addAction(tr("Do not download"), subMenu, [applyPriorities]()
|
||||||
{
|
{
|
||||||
applyPriorities(BitTorrent::DownloadPriority::Ignored);
|
applyPriorities(BitTorrent::DownloadPriority::Ignored);
|
||||||
});
|
});
|
||||||
subMenu->addAction(m_ui->actionNotDownloaded);
|
subMenu->addAction(tr("Normal"), subMenu, [applyPriorities]()
|
||||||
|
|
||||||
connect(m_ui->actionNormal, &QAction::triggered, subMenu, [applyPriorities]()
|
|
||||||
{
|
{
|
||||||
applyPriorities(BitTorrent::DownloadPriority::Normal);
|
applyPriorities(BitTorrent::DownloadPriority::Normal);
|
||||||
});
|
});
|
||||||
subMenu->addAction(m_ui->actionNormal);
|
subMenu->addAction(tr("High"), subMenu, [applyPriorities]()
|
||||||
|
|
||||||
connect(m_ui->actionHigh, &QAction::triggered, subMenu, [applyPriorities]()
|
|
||||||
{
|
{
|
||||||
applyPriorities(BitTorrent::DownloadPriority::High);
|
applyPriorities(BitTorrent::DownloadPriority::High);
|
||||||
});
|
});
|
||||||
subMenu->addAction(m_ui->actionHigh);
|
subMenu->addAction(tr("Maximum"), subMenu, [applyPriorities]()
|
||||||
|
|
||||||
connect(m_ui->actionMaximum, &QAction::triggered, subMenu, [applyPriorities]()
|
|
||||||
{
|
{
|
||||||
applyPriorities(BitTorrent::DownloadPriority::Maximum);
|
applyPriorities(BitTorrent::DownloadPriority::Maximum);
|
||||||
});
|
});
|
||||||
subMenu->addAction(m_ui->actionMaximum);
|
subMenu->addSeparator();
|
||||||
|
subMenu->addAction(tr("By shown file order"), subMenu, [this]()
|
||||||
|
{
|
||||||
|
// Equally distribute the selected items into groups and for each group assign
|
||||||
|
// a download priority that will apply to each item. The number of groups depends on how
|
||||||
|
// many "download priority" are available to be assigned
|
||||||
|
|
||||||
|
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
|
||||||
|
|
||||||
|
const int priorityGroups = 3;
|
||||||
|
const int priorityGroupSize = std::max((selectedRows.length() / priorityGroups), 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < selectedRows.length(); ++i)
|
||||||
|
{
|
||||||
|
auto priority = BitTorrent::DownloadPriority::Ignored;
|
||||||
|
switch (i / priorityGroupSize)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
priority = BitTorrent::DownloadPriority::Maximum;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
priority = BitTorrent::DownloadPriority::High;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case 2:
|
||||||
|
priority = BitTorrent::DownloadPriority::Normal;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QModelIndex &index = selectedRows[i];
|
||||||
|
m_contentModel->setData(index.sibling(index.row(), PRIORITY)
|
||||||
|
, static_cast<int>(priority));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
menu->popup(QCursor::pos());
|
menu->popup(QCursor::pos());
|
||||||
}
|
}
|
||||||
@@ -603,7 +629,7 @@ void AddNewTorrentDialog::reject()
|
|||||||
if (!m_hasMetadata)
|
if (!m_hasMetadata)
|
||||||
{
|
{
|
||||||
setMetadataProgressIndicator(false);
|
setMetadataProgressIndicator(false);
|
||||||
BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.hash());
|
BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.infoHash().toTorrentID());
|
||||||
}
|
}
|
||||||
|
|
||||||
QDialog::reject();
|
QDialog::reject();
|
||||||
@@ -611,7 +637,7 @@ void AddNewTorrentDialog::reject()
|
|||||||
|
|
||||||
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
|
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
|
||||||
{
|
{
|
||||||
if (metadata.hash() != m_magnetURI.hash()) return;
|
if (metadata.infoHash() != m_magnetURI.infoHash()) return;
|
||||||
|
|
||||||
disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata);
|
disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata);
|
||||||
|
|
||||||
|
|||||||
@@ -449,26 +449,6 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
<action name="actionNormal">
|
|
||||||
<property name="text">
|
|
||||||
<string>Normal</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionHigh">
|
|
||||||
<property name="text">
|
|
||||||
<string>High</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionMaximum">
|
|
||||||
<property name="text">
|
|
||||||
<string>Maximum</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionNotDownloaded">
|
|
||||||
<property name="text">
|
|
||||||
<string>Do not download</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ namespace
|
|||||||
DOWNLOAD_TRACKER_FAVICON,
|
DOWNLOAD_TRACKER_FAVICON,
|
||||||
SAVE_PATH_HISTORY_LENGTH,
|
SAVE_PATH_HISTORY_LENGTH,
|
||||||
ENABLE_SPEED_WIDGET,
|
ENABLE_SPEED_WIDGET,
|
||||||
|
#ifndef Q_OS_MACOS
|
||||||
|
ENABLE_ICONS_IN_MENUS,
|
||||||
|
#endif
|
||||||
// embedded tracker
|
// embedded tracker
|
||||||
TRACKER_STATUS,
|
TRACKER_STATUS,
|
||||||
TRACKER_PORT,
|
TRACKER_PORT,
|
||||||
@@ -112,14 +115,11 @@ namespace
|
|||||||
OUTGOING_PORT_MIN,
|
OUTGOING_PORT_MIN,
|
||||||
OUTGOING_PORT_MAX,
|
OUTGOING_PORT_MAX,
|
||||||
UPNP_LEASE_DURATION,
|
UPNP_LEASE_DURATION,
|
||||||
|
PEER_TOS,
|
||||||
UTP_MIX_MODE,
|
UTP_MIX_MODE,
|
||||||
#ifdef HAS_IDN_SUPPORT
|
|
||||||
IDN_SUPPORT,
|
IDN_SUPPORT,
|
||||||
#endif
|
|
||||||
MULTI_CONNECTIONS_PER_IP,
|
MULTI_CONNECTIONS_PER_IP,
|
||||||
#ifdef HAS_HTTPS_TRACKER_VALIDATION
|
|
||||||
VALIDATE_HTTPS_TRACKER_CERTIFICATE,
|
VALIDATE_HTTPS_TRACKER_CERTIFICATE,
|
||||||
#endif
|
|
||||||
BLOCK_PEERS_ON_PRIVILEGED_PORTS,
|
BLOCK_PEERS_ON_PRIVILEGED_PORTS,
|
||||||
// seeding
|
// seeding
|
||||||
CHOKING_ALGORITHM,
|
CHOKING_ALGORITHM,
|
||||||
@@ -224,18 +224,16 @@ void AdvancedSettings::saveAdvancedSettings()
|
|||||||
session->setOutgoingPortsMax(m_spinBoxOutgoingPortsMax.value());
|
session->setOutgoingPortsMax(m_spinBoxOutgoingPortsMax.value());
|
||||||
// UPnP lease duration
|
// UPnP lease duration
|
||||||
session->setUPnPLeaseDuration(m_spinBoxUPnPLeaseDuration.value());
|
session->setUPnPLeaseDuration(m_spinBoxUPnPLeaseDuration.value());
|
||||||
|
// Type of service
|
||||||
|
session->setPeerToS(m_spinBoxPeerToS.value());
|
||||||
// uTP-TCP mixed mode
|
// uTP-TCP mixed mode
|
||||||
session->setUtpMixedMode(static_cast<BitTorrent::MixedModeAlgorithm>(m_comboBoxUtpMixedMode.currentIndex()));
|
session->setUtpMixedMode(static_cast<BitTorrent::MixedModeAlgorithm>(m_comboBoxUtpMixedMode.currentIndex()));
|
||||||
#ifdef HAS_IDN_SUPPORT
|
|
||||||
// Support internationalized domain name (IDN)
|
// Support internationalized domain name (IDN)
|
||||||
session->setIDNSupportEnabled(m_checkBoxIDNSupport.isChecked());
|
session->setIDNSupportEnabled(m_checkBoxIDNSupport.isChecked());
|
||||||
#endif
|
|
||||||
// multiple connections per IP
|
// multiple connections per IP
|
||||||
session->setMultiConnectionsPerIpEnabled(m_checkBoxMultiConnectionsPerIp.isChecked());
|
session->setMultiConnectionsPerIpEnabled(m_checkBoxMultiConnectionsPerIp.isChecked());
|
||||||
#ifdef HAS_HTTPS_TRACKER_VALIDATION
|
|
||||||
// Validate HTTPS tracker certificate
|
// Validate HTTPS tracker certificate
|
||||||
session->setValidateHTTPSTrackerCertificate(m_checkBoxValidateHTTPSTrackerCertificate.isChecked());
|
session->setValidateHTTPSTrackerCertificate(m_checkBoxValidateHTTPSTrackerCertificate.isChecked());
|
||||||
#endif
|
|
||||||
// Disallow connection to peers on privileged ports
|
// Disallow connection to peers on privileged ports
|
||||||
session->setBlockPeersOnPrivilegedPorts(m_checkBoxBlockPeersOnPrivilegedPorts.isChecked());
|
session->setBlockPeersOnPrivilegedPorts(m_checkBoxBlockPeersOnPrivilegedPorts.isChecked());
|
||||||
// Recheck torrents on completion
|
// Recheck torrents on completion
|
||||||
@@ -279,6 +277,9 @@ void AdvancedSettings::saveAdvancedSettings()
|
|||||||
mainWindow->setDownloadTrackerFavicon(m_checkBoxTrackerFavicon.isChecked());
|
mainWindow->setDownloadTrackerFavicon(m_checkBoxTrackerFavicon.isChecked());
|
||||||
AddNewTorrentDialog::setSavePathHistoryLength(m_spinBoxSavePathHistoryLength.value());
|
AddNewTorrentDialog::setSavePathHistoryLength(m_spinBoxSavePathHistoryLength.value());
|
||||||
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
|
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
|
||||||
|
#ifndef Q_OS_MACOS
|
||||||
|
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
|
||||||
|
#endif
|
||||||
|
|
||||||
// Tracker
|
// Tracker
|
||||||
pref->setTrackerPort(m_spinBoxTrackerPort.value());
|
pref->setTrackerPort(m_spinBoxTrackerPort.value());
|
||||||
@@ -544,31 +545,33 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||||||
m_spinBoxUPnPLeaseDuration.setSuffix(tr(" s", " seconds"));
|
m_spinBoxUPnPLeaseDuration.setSuffix(tr(" s", " seconds"));
|
||||||
addRow(UPNP_LEASE_DURATION, (tr("UPnP lease duration [0: Permanent lease]") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#upnp_lease_duration", "(?)"))
|
addRow(UPNP_LEASE_DURATION, (tr("UPnP lease duration [0: Permanent lease]") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#upnp_lease_duration", "(?)"))
|
||||||
, &m_spinBoxUPnPLeaseDuration);
|
, &m_spinBoxUPnPLeaseDuration);
|
||||||
|
// Type of service
|
||||||
|
m_spinBoxPeerToS.setMinimum(0);
|
||||||
|
m_spinBoxPeerToS.setMaximum(255);
|
||||||
|
m_spinBoxPeerToS.setValue(session->peerToS());
|
||||||
|
addRow(PEER_TOS, (tr("Type of service (ToS) for connections to peers") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#peer_tos", "(?)"))
|
||||||
|
, &m_spinBoxPeerToS);
|
||||||
// uTP-TCP mixed mode
|
// uTP-TCP mixed mode
|
||||||
m_comboBoxUtpMixedMode.addItems({tr("Prefer TCP"), tr("Peer proportional (throttles TCP)")});
|
m_comboBoxUtpMixedMode.addItems({tr("Prefer TCP"), tr("Peer proportional (throttles TCP)")});
|
||||||
m_comboBoxUtpMixedMode.setCurrentIndex(static_cast<int>(session->utpMixedMode()));
|
m_comboBoxUtpMixedMode.setCurrentIndex(static_cast<int>(session->utpMixedMode()));
|
||||||
addRow(UTP_MIX_MODE, (tr("%1-TCP mixed mode algorithm", "uTP-TCP mixed mode algorithm").arg(C_UTP)
|
addRow(UTP_MIX_MODE, (tr("%1-TCP mixed mode algorithm", "uTP-TCP mixed mode algorithm").arg(C_UTP)
|
||||||
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#mixed_mode_algorithm", "(?)"))
|
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#mixed_mode_algorithm", "(?)"))
|
||||||
, &m_comboBoxUtpMixedMode);
|
, &m_comboBoxUtpMixedMode);
|
||||||
#ifdef HAS_IDN_SUPPORT
|
|
||||||
// Support internationalized domain name (IDN)
|
// Support internationalized domain name (IDN)
|
||||||
m_checkBoxIDNSupport.setChecked(session->isIDNSupportEnabled());
|
m_checkBoxIDNSupport.setChecked(session->isIDNSupportEnabled());
|
||||||
addRow(IDN_SUPPORT, (tr("Support internationalized domain name (IDN)")
|
addRow(IDN_SUPPORT, (tr("Support internationalized domain name (IDN)")
|
||||||
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_idna", "(?)"))
|
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_idna", "(?)"))
|
||||||
, &m_checkBoxIDNSupport);
|
, &m_checkBoxIDNSupport);
|
||||||
#endif
|
|
||||||
// multiple connections per IP
|
// multiple connections per IP
|
||||||
m_checkBoxMultiConnectionsPerIp.setChecked(session->multiConnectionsPerIpEnabled());
|
m_checkBoxMultiConnectionsPerIp.setChecked(session->multiConnectionsPerIpEnabled());
|
||||||
addRow(MULTI_CONNECTIONS_PER_IP, (tr("Allow multiple connections from the same IP address")
|
addRow(MULTI_CONNECTIONS_PER_IP, (tr("Allow multiple connections from the same IP address")
|
||||||
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_multiple_connections_per_ip", "(?)"))
|
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_multiple_connections_per_ip", "(?)"))
|
||||||
, &m_checkBoxMultiConnectionsPerIp);
|
, &m_checkBoxMultiConnectionsPerIp);
|
||||||
#ifdef HAS_HTTPS_TRACKER_VALIDATION
|
|
||||||
// Validate HTTPS tracker certificate
|
// Validate HTTPS tracker certificate
|
||||||
m_checkBoxValidateHTTPSTrackerCertificate.setChecked(session->validateHTTPSTrackerCertificate());
|
m_checkBoxValidateHTTPSTrackerCertificate.setChecked(session->validateHTTPSTrackerCertificate());
|
||||||
addRow(VALIDATE_HTTPS_TRACKER_CERTIFICATE, (tr("Validate HTTPS tracker certificates")
|
addRow(VALIDATE_HTTPS_TRACKER_CERTIFICATE, (tr("Validate HTTPS tracker certificates")
|
||||||
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#validate_https_trackers", "(?)"))
|
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#validate_https_trackers", "(?)"))
|
||||||
, &m_checkBoxValidateHTTPSTrackerCertificate);
|
, &m_checkBoxValidateHTTPSTrackerCertificate);
|
||||||
#endif
|
|
||||||
// Disallow connection to peers on privileged ports
|
// Disallow connection to peers on privileged ports
|
||||||
m_checkBoxBlockPeersOnPrivilegedPorts.setChecked(session->blockPeersOnPrivilegedPorts());
|
m_checkBoxBlockPeersOnPrivilegedPorts.setChecked(session->blockPeersOnPrivilegedPorts());
|
||||||
addRow(BLOCK_PEERS_ON_PRIVILEGED_PORTS, (tr("Disallow connection to peers on privileged ports") + ' ' + makeLink("https://libtorrent.org/single-page-ref.html#no_connect_privileged_ports", "(?)")), &m_checkBoxBlockPeersOnPrivilegedPorts);
|
addRow(BLOCK_PEERS_ON_PRIVILEGED_PORTS, (tr("Disallow connection to peers on privileged ports") + ' ' + makeLink("https://libtorrent.org/single-page-ref.html#no_connect_privileged_ports", "(?)")), &m_checkBoxBlockPeersOnPrivilegedPorts);
|
||||||
@@ -647,6 +650,11 @@ void AdvancedSettings::loadAdvancedSettings()
|
|||||||
// Enable speed graphs
|
// Enable speed graphs
|
||||||
m_checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled());
|
m_checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled());
|
||||||
addRow(ENABLE_SPEED_WIDGET, tr("Enable speed graphs"), &m_checkBoxSpeedWidgetEnabled);
|
addRow(ENABLE_SPEED_WIDGET, tr("Enable speed graphs"), &m_checkBoxSpeedWidgetEnabled);
|
||||||
|
#ifndef Q_OS_MACOS
|
||||||
|
// Enable icons in menus
|
||||||
|
m_checkBoxIconsInMenusEnabled.setChecked(pref->iconsInMenusEnabled());
|
||||||
|
addRow(ENABLE_ICONS_IN_MENUS, tr("Enable icons in menus"), &m_checkBoxIconsInMenusEnabled);
|
||||||
|
#endif
|
||||||
// Tracker State
|
// Tracker State
|
||||||
m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
|
m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
|
||||||
addRow(TRACKER_STATUS, tr("Enable embedded tracker"), &m_checkBoxTrackerStatus);
|
addRow(TRACKER_STATUS, tr("Enable embedded tracker"), &m_checkBoxTrackerStatus);
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ private:
|
|||||||
template <typename T> void addRow(int row, const QString &text, T *widget);
|
template <typename T> void addRow(int row, const QString &text, T *widget);
|
||||||
|
|
||||||
QSpinBox m_spinBoxAsyncIOThreads, m_spinBoxFilePoolSize, m_spinBoxCheckingMemUsage,
|
QSpinBox m_spinBoxAsyncIOThreads, m_spinBoxFilePoolSize, m_spinBoxCheckingMemUsage,
|
||||||
m_spinBoxSaveResumeDataInterval, m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration,
|
m_spinBoxSaveResumeDataInterval, m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration, m_spinBoxPeerToS,
|
||||||
m_spinBoxListRefresh, m_spinBoxTrackerPort, m_spinBoxSendBufferWatermark, m_spinBoxSendBufferLowWatermark,
|
m_spinBoxListRefresh, m_spinBoxTrackerPort, m_spinBoxSendBufferWatermark, m_spinBoxSendBufferLowWatermark,
|
||||||
m_spinBoxSendBufferWatermarkFactor, m_spinBoxSocketBacklogSize, m_spinBoxMaxConcurrentHTTPAnnounces, m_spinBoxStopTrackerTimeout,
|
m_spinBoxSendBufferWatermarkFactor, m_spinBoxSocketBacklogSize, m_spinBoxMaxConcurrentHTTPAnnounces, m_spinBoxStopTrackerTimeout,
|
||||||
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval;
|
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval;
|
||||||
@@ -84,4 +84,8 @@ private:
|
|||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
QComboBox m_comboBoxOSMemoryPriority;
|
QComboBox m_comboBoxOSMemoryPriority;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef Q_OS_MACOS
|
||||||
|
QCheckBox m_checkBoxIconsInMenusEnabled;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -79,31 +79,19 @@ void AutoExpandableDialog::showEvent(QShowEvent *e)
|
|||||||
|
|
||||||
// Show dialog and resize textbox to fit the text
|
// Show dialog and resize textbox to fit the text
|
||||||
// NOTE: For unknown reason QFontMetrics gets more accurate when called from showEvent.
|
// NOTE: For unknown reason QFontMetrics gets more accurate when called from showEvent.
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
|
||||||
int wd = m_ui->textEdit->fontMetrics().horizontalAdvance(m_ui->textEdit->text()) + 4;
|
int wd = m_ui->textEdit->fontMetrics().horizontalAdvance(m_ui->textEdit->text()) + 4;
|
||||||
#else
|
|
||||||
int wd = m_ui->textEdit->fontMetrics().width(m_ui->textEdit->text()) + 4;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!windowTitle().isEmpty())
|
if (!windowTitle().isEmpty())
|
||||||
{
|
{
|
||||||
// not really the font metrics in window title, so we enlarge it a bit,
|
// not really the font metrics in window title, so we enlarge it a bit,
|
||||||
// including the small icon and close button width
|
// including the small icon and close button width
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
|
||||||
int w = fontMetrics().horizontalAdvance(windowTitle()) * 1.8;
|
int w = fontMetrics().horizontalAdvance(windowTitle()) * 1.8;
|
||||||
#else
|
|
||||||
int w = fontMetrics().width(windowTitle()) * 1.8;
|
|
||||||
#endif
|
|
||||||
wd = std::max(wd, w);
|
wd = std::max(wd, w);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_ui->textLabel->text().isEmpty())
|
if (!m_ui->textLabel->text().isEmpty())
|
||||||
{
|
{
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
|
||||||
int w = m_ui->textLabel->fontMetrics().horizontalAdvance(m_ui->textLabel->text());
|
int w = m_ui->textLabel->fontMetrics().horizontalAdvance(m_ui->textLabel->text());
|
||||||
#else
|
|
||||||
int w = m_ui->textLabel->fontMetrics().width(m_ui->textLabel->text());
|
|
||||||
#endif
|
|
||||||
wd = std::max(wd, w);
|
wd = std::max(wd, w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
|
|
||||||
#include "categoryfilterwidget.h"
|
#include "categoryfilterwidget.h"
|
||||||
|
|
||||||
#include <QAction>
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
|
||||||
#include "base/bittorrent/session.h"
|
#include "base/bittorrent/session.h"
|
||||||
@@ -110,54 +109,33 @@ void CategoryFilterWidget::showMenu(const QPoint &)
|
|||||||
QMenu *menu = new QMenu(this);
|
QMenu *menu = new QMenu(this);
|
||||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
const QAction *addAct = menu->addAction(
|
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add category...")
|
||||||
UIThemeManager::instance()->getIcon("list-add")
|
, this, &CategoryFilterWidget::addCategory);
|
||||||
, tr("Add category..."));
|
|
||||||
connect(addAct, &QAction::triggered, this, &CategoryFilterWidget::addCategory);
|
|
||||||
|
|
||||||
const auto selectedRows = selectionModel()->selectedRows();
|
const auto selectedRows = selectionModel()->selectedRows();
|
||||||
if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first()))
|
if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first()))
|
||||||
{
|
{
|
||||||
if (BitTorrent::Session::instance()->isSubcategoriesEnabled())
|
if (BitTorrent::Session::instance()->isSubcategoriesEnabled())
|
||||||
{
|
{
|
||||||
const QAction *addSubAct = menu->addAction(
|
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add subcategory...")
|
||||||
UIThemeManager::instance()->getIcon("list-add")
|
, this, &CategoryFilterWidget::addSubcategory);
|
||||||
, tr("Add subcategory..."));
|
|
||||||
connect(addSubAct, &QAction::triggered, this, &CategoryFilterWidget::addSubcategory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QAction *editAct = menu->addAction(
|
menu->addAction(UIThemeManager::instance()->getIcon("document-edit"), tr("Edit category...")
|
||||||
UIThemeManager::instance()->getIcon("document-edit")
|
, this, &CategoryFilterWidget::editCategory);
|
||||||
, tr("Edit category..."));
|
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove category")
|
||||||
connect(editAct, &QAction::triggered, this, &CategoryFilterWidget::editCategory);
|
, this, &CategoryFilterWidget::removeCategory);
|
||||||
|
|
||||||
const QAction *removeAct = menu->addAction(
|
|
||||||
UIThemeManager::instance()->getIcon("list-remove")
|
|
||||||
, tr("Remove category"));
|
|
||||||
connect(removeAct, &QAction::triggered, this, &CategoryFilterWidget::removeCategory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QAction *removeUnusedAct = menu->addAction(
|
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove unused categories")
|
||||||
UIThemeManager::instance()->getIcon("list-remove")
|
, this, &CategoryFilterWidget::removeUnusedCategories);
|
||||||
, tr("Remove unused categories"));
|
|
||||||
connect(removeUnusedAct, &QAction::triggered, this, &CategoryFilterWidget::removeUnusedCategories);
|
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
menu->addAction(UIThemeManager::instance()->getIcon("media-playback-start"), tr("Resume torrents")
|
||||||
const QAction *startAct = menu->addAction(
|
, this, &CategoryFilterWidget::actionResumeTorrentsTriggered);
|
||||||
UIThemeManager::instance()->getIcon("media-playback-start")
|
menu->addAction(UIThemeManager::instance()->getIcon("media-playback-pause"), tr("Pause torrents")
|
||||||
, tr("Resume torrents"));
|
, this, &CategoryFilterWidget::actionPauseTorrentsTriggered);
|
||||||
connect(startAct, &QAction::triggered, this, &CategoryFilterWidget::actionResumeTorrentsTriggered);
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-delete"), tr("Delete torrents")
|
||||||
|
, this, &CategoryFilterWidget::actionDeleteTorrentsTriggered);
|
||||||
const QAction *pauseAct = menu->addAction(
|
|
||||||
UIThemeManager::instance()->getIcon("media-playback-pause")
|
|
||||||
, tr("Pause torrents"));
|
|
||||||
connect(pauseAct, &QAction::triggered, this, &CategoryFilterWidget::actionPauseTorrentsTriggered);
|
|
||||||
|
|
||||||
const QAction *deleteTorrentsAct = menu->addAction(
|
|
||||||
UIThemeManager::instance()->getIcon("edit-delete")
|
|
||||||
, tr("Delete torrents"));
|
|
||||||
connect(deleteTorrentsAct, &QAction::triggered, this, &CategoryFilterWidget::actionDeleteTorrentsTriggered);
|
|
||||||
|
|
||||||
menu->popup(QCursor::pos());
|
menu->popup(QCursor::pos());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,12 +91,12 @@ void ExecutionLogWidget::displayContextMenu(const QPoint &pos, const LogListView
|
|||||||
// only show copy action if any of the row is selected
|
// only show copy action if any of the row is selected
|
||||||
if (view->currentIndex().isValid())
|
if (view->currentIndex().isValid())
|
||||||
{
|
{
|
||||||
const QAction *copyAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy"));
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy")
|
||||||
connect(copyAct, &QAction::triggered, view, &LogListView::copySelection);
|
, view, &LogListView::copySelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QAction *clearAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear"));
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear")
|
||||||
connect(clearAct, &QAction::triggered, model, &BaseLogModel::reset);
|
, model, &BaseLogModel::reset);
|
||||||
|
|
||||||
menu->popup(view->mapToGlobal(pos));
|
menu->popup(view->mapToGlobal(pos));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ HEADERS += \
|
|||||||
$$PWD/powermanagement/powermanagement.h \
|
$$PWD/powermanagement/powermanagement.h \
|
||||||
$$PWD/previewlistdelegate.h \
|
$$PWD/previewlistdelegate.h \
|
||||||
$$PWD/previewselectdialog.h \
|
$$PWD/previewselectdialog.h \
|
||||||
$$PWD/progressbardelegate.h \
|
$$PWD/progressbarpainter.h \
|
||||||
$$PWD/properties/downloadedpiecesbar.h \
|
$$PWD/properties/downloadedpiecesbar.h \
|
||||||
$$PWD/properties/peerlistsortmodel.h \
|
$$PWD/properties/peerlistsortmodel.h \
|
||||||
$$PWD/properties/peerlistwidget.h \
|
$$PWD/properties/peerlistwidget.h \
|
||||||
@@ -107,7 +107,7 @@ SOURCES += \
|
|||||||
$$PWD/powermanagement/powermanagement.cpp \
|
$$PWD/powermanagement/powermanagement.cpp \
|
||||||
$$PWD/previewlistdelegate.cpp \
|
$$PWD/previewlistdelegate.cpp \
|
||||||
$$PWD/previewselectdialog.cpp \
|
$$PWD/previewselectdialog.cpp \
|
||||||
$$PWD/progressbardelegate.cpp \
|
$$PWD/progressbarpainter.cpp \
|
||||||
$$PWD/properties/downloadedpiecesbar.cpp \
|
$$PWD/properties/downloadedpiecesbar.cpp \
|
||||||
$$PWD/properties/peerlistsortmodel.cpp \
|
$$PWD/properties/peerlistsortmodel.cpp \
|
||||||
$$PWD/properties/peerlistwidget.cpp \
|
$$PWD/properties/peerlistwidget.cpp \
|
||||||
|
|||||||
@@ -46,11 +46,7 @@ namespace
|
|||||||
|
|
||||||
int horizontalAdvance(const QFontMetrics &fontMetrics, const QString &text)
|
int horizontalAdvance(const QFontMetrics &fontMetrics, const QString &text)
|
||||||
{
|
{
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
|
||||||
return fontMetrics.horizontalAdvance(text);
|
return fontMetrics.horizontalAdvance(text);
|
||||||
#else
|
|
||||||
return fontMetrics.width(text);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString logText(const QModelIndex &index)
|
QString logText(const QModelIndex &index)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
|
#include <QActionGroup>
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QCloseEvent>
|
#include <QCloseEvent>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
@@ -90,7 +91,6 @@
|
|||||||
#include "statsdialog.h"
|
#include "statsdialog.h"
|
||||||
#include "statusbar.h"
|
#include "statusbar.h"
|
||||||
#include "torrentcreatordialog.h"
|
#include "torrentcreatordialog.h"
|
||||||
|
|
||||||
#include "transferlistfilterswidget.h"
|
#include "transferlistfilterswidget.h"
|
||||||
#include "transferlistmodel.h"
|
#include "transferlistmodel.h"
|
||||||
#include "transferlistwidget.h"
|
#include "transferlistwidget.h"
|
||||||
@@ -105,6 +105,8 @@
|
|||||||
#include "programupdater.h"
|
#include "programupdater.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
#define SETTINGS_KEY(name) "GUI/" name
|
#define SETTINGS_KEY(name) "GUI/" name
|
||||||
@@ -148,9 +150,6 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
, m_posInitialized(false)
|
, m_posInitialized(false)
|
||||||
, m_forceExit(false)
|
, m_forceExit(false)
|
||||||
, m_unlockDlgShowing(false)
|
, m_unlockDlgShowing(false)
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
|
||||||
, m_wasUpdateCheckEnabled(false)
|
|
||||||
#endif
|
|
||||||
, m_hasPython(false)
|
, m_hasPython(false)
|
||||||
{
|
{
|
||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
@@ -195,10 +194,8 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon("preferences-web-browser-cookies"));
|
m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon("preferences-web-browser-cookies"));
|
||||||
|
|
||||||
auto *lockMenu = new QMenu(this);
|
auto *lockMenu = new QMenu(this);
|
||||||
QAction *defineUiLockPasswdAct = lockMenu->addAction(tr("&Set Password"));
|
lockMenu->addAction(tr("&Set Password"), this, &MainWindow::defineUILockPassword);
|
||||||
connect(defineUiLockPasswdAct, &QAction::triggered, this, &MainWindow::defineUILockPassword);
|
lockMenu->addAction(tr("&Clear Password"), this, &MainWindow::clearUILockPassword);
|
||||||
QAction *clearUiLockPasswdAct = lockMenu->addAction(tr("&Clear Password"));
|
|
||||||
connect(clearUiLockPasswdAct, &QAction::triggered, this, &MainWindow::clearUILockPassword);
|
|
||||||
m_ui->actionLock->setMenu(lockMenu);
|
m_ui->actionLock->setMenu(lockMenu);
|
||||||
|
|
||||||
// Creating Bittorrent session
|
// Creating Bittorrent session
|
||||||
@@ -319,11 +316,11 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
|
connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||||
m_programUpdateTimer = new QTimer(this);
|
connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); });
|
||||||
m_programUpdateTimer->setInterval(60 * 60 * 1000);
|
|
||||||
m_programUpdateTimer->setSingleShot(true);
|
// trigger an early check on startup
|
||||||
connect(m_programUpdateTimer, &QTimer::timeout, this, &MainWindow::checkProgramUpdate);
|
if (pref->isUpdateCheckEnabled())
|
||||||
connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, &MainWindow::checkProgramUpdate);
|
checkProgramUpdate(false);
|
||||||
#else
|
#else
|
||||||
m_ui->actionCheckForUpdates->setVisible(false);
|
m_ui->actionCheckForUpdates->setVisible(false);
|
||||||
#endif
|
#endif
|
||||||
@@ -550,16 +547,11 @@ void MainWindow::addToolbarContextMenu()
|
|||||||
m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
|
m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
|
connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
|
||||||
|
|
||||||
QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"));
|
QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"), this, &MainWindow::toolbarIconsOnly);
|
||||||
connect(iconsOnly, &QAction::triggered, this, &MainWindow::toolbarIconsOnly);
|
QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"), this, &MainWindow::toolbarTextOnly);
|
||||||
QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"));
|
QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"), this, &MainWindow::toolbarTextBeside);
|
||||||
connect(textOnly, &QAction::triggered, this, &MainWindow::toolbarTextOnly);
|
QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"), this, &MainWindow::toolbarTextUnder);
|
||||||
QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"));
|
QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"), this, &MainWindow::toolbarFollowSystem);
|
||||||
connect(textBesideIcons, &QAction::triggered, this, &MainWindow::toolbarTextBeside);
|
|
||||||
QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"));
|
|
||||||
connect(textUnderIcons, &QAction::triggered, this, &MainWindow::toolbarTextUnder);
|
|
||||||
QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"));
|
|
||||||
connect(followSystemStyle, &QAction::triggered, this, &MainWindow::toolbarFollowSystem);
|
|
||||||
|
|
||||||
auto *textPositionGroup = new QActionGroup(m_toolbarMenu);
|
auto *textPositionGroup = new QActionGroup(m_toolbarMenu);
|
||||||
textPositionGroup->addAction(iconsOnly);
|
textPositionGroup->addAction(iconsOnly);
|
||||||
@@ -811,7 +803,8 @@ void MainWindow::cleanup()
|
|||||||
m_preventTimer->stop();
|
m_preventTimer->stop();
|
||||||
|
|
||||||
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
|
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
|
||||||
m_programUpdateTimer->stop();
|
if (m_programUpdateTimer)
|
||||||
|
m_programUpdateTimer->stop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// remove all child widgets
|
// remove all child widgets
|
||||||
@@ -884,36 +877,36 @@ void MainWindow::createKeyboardShortcuts()
|
|||||||
m_ui->actionOpen->setShortcut(QKeySequence::Open);
|
m_ui->actionOpen->setShortcut(QKeySequence::Open);
|
||||||
m_ui->actionDelete->setShortcut(QKeySequence::Delete);
|
m_ui->actionDelete->setShortcut(QKeySequence::Delete);
|
||||||
m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
|
m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
|
||||||
m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_O);
|
m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
|
||||||
m_ui->actionExit->setShortcut(Qt::CTRL + Qt::Key_Q);
|
m_ui->actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
|
m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
|
||||||
#else
|
#else
|
||||||
m_ui->actionCloseWindow->setVisible(false);
|
m_ui->actionCloseWindow->setVisible(false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const auto *switchTransferShortcut = new QShortcut(Qt::ALT + Qt::Key_1, this);
|
const auto *switchTransferShortcut = new QShortcut((Qt::ALT | Qt::Key_1), this);
|
||||||
connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab);
|
connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab);
|
||||||
const auto *switchSearchShortcut = new QShortcut(Qt::ALT + Qt::Key_2, this);
|
const auto *switchSearchShortcut = new QShortcut((Qt::ALT | Qt::Key_2), this);
|
||||||
connect(switchSearchShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displaySearchTab));
|
connect(switchSearchShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displaySearchTab));
|
||||||
const auto *switchRSSShortcut = new QShortcut(Qt::ALT + Qt::Key_3, this);
|
const auto *switchRSSShortcut = new QShortcut((Qt::ALT | Qt::Key_3), this);
|
||||||
connect(switchRSSShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displayRSSTab));
|
connect(switchRSSShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displayRSSTab));
|
||||||
const auto *switchExecutionLogShortcut = new QShortcut(Qt::ALT + Qt::Key_4, this);
|
const auto *switchExecutionLogShortcut = new QShortcut((Qt::ALT | Qt::Key_4), this);
|
||||||
connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab);
|
connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab);
|
||||||
const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget);
|
const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget);
|
||||||
connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
|
connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
|
||||||
|
|
||||||
m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
|
m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
|
||||||
m_ui->actionOptions->setShortcut(Qt::ALT + Qt::Key_O);
|
m_ui->actionOptions->setShortcut(Qt::ALT | Qt::Key_O);
|
||||||
m_ui->actionStatistics->setShortcut(Qt::CTRL + Qt::Key_I);
|
m_ui->actionStatistics->setShortcut(Qt::CTRL | Qt::Key_I);
|
||||||
m_ui->actionStart->setShortcut(Qt::CTRL + Qt::Key_S);
|
m_ui->actionStart->setShortcut(Qt::CTRL | Qt::Key_S);
|
||||||
m_ui->actionStartAll->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_S);
|
m_ui->actionStartAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
|
||||||
m_ui->actionPause->setShortcut(Qt::CTRL + Qt::Key_P);
|
m_ui->actionPause->setShortcut(Qt::CTRL | Qt::Key_P);
|
||||||
m_ui->actionPauseAll->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_P);
|
m_ui->actionPauseAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
|
||||||
m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus);
|
m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus);
|
||||||
m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL + Qt::Key_Minus);
|
m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Minus);
|
||||||
m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL + Qt::Key_Plus);
|
m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Plus);
|
||||||
m_ui->actionTopQueuePos->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Plus);
|
m_ui->actionTopQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus);
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M);
|
m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M);
|
||||||
addAction(m_ui->actionMinimize);
|
addAction(m_ui->actionMinimize);
|
||||||
@@ -966,7 +959,7 @@ void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *co
|
|||||||
Preferences *const pref = Preferences::instance();
|
Preferences *const pref = Preferences::instance();
|
||||||
if (pref->recursiveDownloadDisabled()) return;
|
if (pref->recursiveDownloadDisabled()) return;
|
||||||
|
|
||||||
const auto torrentHash = torrent->hash();
|
const auto torrentID = torrent->id();
|
||||||
|
|
||||||
QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
|
QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
|
||||||
, tr("The torrent '%1' contains torrent files, do you want to proceed with their download?").arg(torrent->name())
|
, tr("The torrent '%1' contains torrent files, do you want to proceed with their download?").arg(torrent->name())
|
||||||
@@ -977,10 +970,10 @@ void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *co
|
|||||||
const QPushButton *yes = confirmBox->addButton(tr("Yes"), QMessageBox::YesRole);
|
const QPushButton *yes = confirmBox->addButton(tr("Yes"), QMessageBox::YesRole);
|
||||||
/*QPushButton *no = */ confirmBox->addButton(tr("No"), QMessageBox::NoRole);
|
/*QPushButton *no = */ confirmBox->addButton(tr("No"), QMessageBox::NoRole);
|
||||||
const QPushButton *never = confirmBox->addButton(tr("Never"), QMessageBox::NoRole);
|
const QPushButton *never = confirmBox->addButton(tr("Never"), QMessageBox::NoRole);
|
||||||
connect(confirmBox, &QMessageBox::buttonClicked, this, [torrentHash, yes, never](const QAbstractButton *button)
|
connect(confirmBox, &QMessageBox::buttonClicked, this, [torrentID, yes, never](const QAbstractButton *button)
|
||||||
{
|
{
|
||||||
if (button == yes)
|
if (button == yes)
|
||||||
BitTorrent::Session::instance()->recursiveTorrentDownload(torrentHash);
|
BitTorrent::Session::instance()->recursiveTorrentDownload(torrentID);
|
||||||
if (button == never)
|
if (button == never)
|
||||||
Preferences::instance()->disableRecursiveDownload();
|
Preferences::instance()->disableRecursiveDownload();
|
||||||
});
|
});
|
||||||
@@ -1590,15 +1583,21 @@ void MainWindow::loadPreferences(const bool configureSession)
|
|||||||
m_propertiesWidget->reloadPreferences();
|
m_propertiesWidget->reloadPreferences();
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||||
if (pref->isUpdateCheckEnabled() && !m_wasUpdateCheckEnabled)
|
if (pref->isUpdateCheckEnabled())
|
||||||
{
|
{
|
||||||
m_wasUpdateCheckEnabled = true;
|
if (!m_programUpdateTimer)
|
||||||
checkProgramUpdate();
|
{
|
||||||
|
m_programUpdateTimer = new QTimer(this);
|
||||||
|
m_programUpdateTimer->setInterval(24h);
|
||||||
|
m_programUpdateTimer->setSingleShot(true);
|
||||||
|
connect(m_programUpdateTimer, &QTimer::timeout, this, [this]() { checkProgramUpdate(false); });
|
||||||
|
m_programUpdateTimer->start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!pref->isUpdateCheckEnabled() && m_wasUpdateCheckEnabled)
|
else
|
||||||
{
|
{
|
||||||
m_wasUpdateCheckEnabled = false;
|
delete m_programUpdateTimer;
|
||||||
m_programUpdateTimer->stop();
|
m_programUpdateTimer = nullptr;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1804,21 +1803,21 @@ void MainWindow::on_actionOptions_triggered()
|
|||||||
|
|
||||||
void MainWindow::on_actionTopToolBar_triggered()
|
void MainWindow::on_actionTopToolBar_triggered()
|
||||||
{
|
{
|
||||||
const bool isVisible = static_cast<QAction*>(sender())->isChecked();
|
const bool isVisible = static_cast<QAction *>(sender())->isChecked();
|
||||||
m_ui->toolBar->setVisible(isVisible);
|
m_ui->toolBar->setVisible(isVisible);
|
||||||
Preferences::instance()->setToolbarDisplayed(isVisible);
|
Preferences::instance()->setToolbarDisplayed(isVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionShowStatusbar_triggered()
|
void MainWindow::on_actionShowStatusbar_triggered()
|
||||||
{
|
{
|
||||||
const bool isVisible = static_cast<QAction*>(sender())->isChecked();
|
const bool isVisible = static_cast<QAction *>(sender())->isChecked();
|
||||||
Preferences::instance()->setStatusbarDisplayed(isVisible);
|
Preferences::instance()->setStatusbarDisplayed(isVisible);
|
||||||
showStatusBar(isVisible);
|
showStatusBar(isVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_actionSpeedInTitleBar_triggered()
|
void MainWindow::on_actionSpeedInTitleBar_triggered()
|
||||||
{
|
{
|
||||||
m_displaySpeedInTitle = static_cast<QAction * >(sender())->isChecked();
|
m_displaySpeedInTitle = static_cast<QAction *>(sender())->isChecked();
|
||||||
Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
|
Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
|
||||||
if (m_displaySpeedInTitle)
|
if (m_displaySpeedInTitle)
|
||||||
reloadSessionStats();
|
reloadSessionStats();
|
||||||
@@ -1904,35 +1903,56 @@ void MainWindow::on_actionDownloadFromURL_triggered()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||||
void MainWindow::handleUpdateCheckFinished(bool updateAvailable, QString newVersion, bool invokedByUser)
|
void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool invokedByUser)
|
||||||
{
|
{
|
||||||
QMessageBox::StandardButton answer = QMessageBox::Yes;
|
|
||||||
if (updateAvailable)
|
|
||||||
{
|
|
||||||
answer = QMessageBox::question(this, tr("qBittorrent Update Available")
|
|
||||||
, tr("A new version is available.") + "<br/>"
|
|
||||||
+ tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>"
|
|
||||||
+ QString::fromLatin1("<a href=\"https://www.qbittorrent.org/news.php\">%1</a>").arg(tr("Open changelog..."))
|
|
||||||
, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
|
||||||
if (answer == QMessageBox::Yes)
|
|
||||||
{
|
|
||||||
// The user want to update, let's download the update
|
|
||||||
ProgramUpdater *updater = dynamic_cast<ProgramUpdater * >(sender());
|
|
||||||
updater->updateProgram();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (invokedByUser)
|
|
||||||
{
|
|
||||||
QMessageBox::information(this, tr("Already Using the Latest qBittorrent Version"),
|
|
||||||
tr("No updates available.\nYou are already using the latest version."));
|
|
||||||
}
|
|
||||||
sender()->deleteLater();
|
|
||||||
m_ui->actionCheckForUpdates->setEnabled(true);
|
m_ui->actionCheckForUpdates->setEnabled(true);
|
||||||
m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
|
m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
|
||||||
m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates"));
|
m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates"));
|
||||||
// Don't bother the user again in this session if he chose to ignore the update
|
|
||||||
if (Preferences::instance()->isUpdateCheckEnabled() && (answer == QMessageBox::Yes))
|
const auto cleanup = [this, updater]()
|
||||||
m_programUpdateTimer->start();
|
{
|
||||||
|
if (m_programUpdateTimer)
|
||||||
|
m_programUpdateTimer->start();
|
||||||
|
updater->deleteLater();
|
||||||
|
};
|
||||||
|
|
||||||
|
const QString newVersion = updater->getNewVersion();
|
||||||
|
if (!newVersion.isEmpty())
|
||||||
|
{
|
||||||
|
const QString msg {tr("A new version is available.") + "<br/>"
|
||||||
|
+ tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>"
|
||||||
|
+ QString::fromLatin1("<a href=\"https://www.qbittorrent.org/news.php\">%1</a>").arg(tr("Open changelog..."))};
|
||||||
|
auto *msgBox = new QMessageBox {QMessageBox::Question, tr("qBittorrent Update Available"), msg
|
||||||
|
, (QMessageBox::Yes | QMessageBox::No), this};
|
||||||
|
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
msgBox->setAttribute(Qt::WA_ShowWithoutActivating);
|
||||||
|
msgBox->setDefaultButton(QMessageBox::Yes);
|
||||||
|
connect(msgBox, &QMessageBox::buttonClicked, this, [msgBox, updater](QAbstractButton *button)
|
||||||
|
{
|
||||||
|
if (msgBox->buttonRole(button) == QMessageBox::YesRole)
|
||||||
|
{
|
||||||
|
updater->updateProgram();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(msgBox, &QDialog::finished, this, cleanup);
|
||||||
|
msgBox->open();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (invokedByUser)
|
||||||
|
{
|
||||||
|
auto *msgBox = new QMessageBox {QMessageBox::Information, QLatin1String("qBittorrent")
|
||||||
|
, tr("No updates available.\nYou are already using the latest version.")
|
||||||
|
, QMessageBox::Ok, this};
|
||||||
|
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
connect(msgBox, &QDialog::finished, this, cleanup);
|
||||||
|
msgBox->open();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -2091,18 +2111,23 @@ QIcon MainWindow::getSystrayIcon() const
|
|||||||
#endif // Q_OS_MACOS
|
#endif // Q_OS_MACOS
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||||
void MainWindow::checkProgramUpdate()
|
void MainWindow::checkProgramUpdate(const bool invokedByUser)
|
||||||
{
|
{
|
||||||
m_programUpdateTimer->stop(); // If the user had clicked the menu item
|
if (m_programUpdateTimer)
|
||||||
|
m_programUpdateTimer->stop();
|
||||||
|
|
||||||
m_ui->actionCheckForUpdates->setEnabled(false);
|
m_ui->actionCheckForUpdates->setEnabled(false);
|
||||||
m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
|
m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
|
||||||
m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
|
m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
|
||||||
bool invokedByUser = m_ui->actionCheckForUpdates == qobject_cast<QAction * >(sender());
|
|
||||||
ProgramUpdater *updater = new ProgramUpdater(this, invokedByUser);
|
auto *updater = new ProgramUpdater(this);
|
||||||
connect(updater, &ProgramUpdater::updateCheckFinished, this, &MainWindow::handleUpdateCheckFinished);
|
connect(updater, &ProgramUpdater::updateCheckFinished
|
||||||
|
, this, [this, invokedByUser, updater]()
|
||||||
|
{
|
||||||
|
handleUpdateCheckFinished(updater, invokedByUser);
|
||||||
|
});
|
||||||
updater->checkForUpdates();
|
updater->checkForUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class ExecutionLogWidget;
|
|||||||
class LineEdit;
|
class LineEdit;
|
||||||
class OptionsDialog;
|
class OptionsDialog;
|
||||||
class PowerManagement;
|
class PowerManagement;
|
||||||
|
class ProgramUpdater;
|
||||||
class PropertiesWidget;
|
class PropertiesWidget;
|
||||||
class RSSWidget;
|
class RSSWidget;
|
||||||
class SearchWidget;
|
class SearchWidget;
|
||||||
@@ -134,9 +135,6 @@ private slots:
|
|||||||
void finishedTorrent(BitTorrent::Torrent *const torrent) const;
|
void finishedTorrent(BitTorrent::Torrent *const torrent) const;
|
||||||
void askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *const torrent);
|
void askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *const torrent);
|
||||||
void optionsSaved();
|
void optionsSaved();
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
|
||||||
void handleUpdateCheckFinished(bool updateAvailable, QString newVersion, bool invokedByUser);
|
|
||||||
#endif
|
|
||||||
void toggleAlternativeSpeeds();
|
void toggleAlternativeSpeeds();
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
@@ -177,9 +175,7 @@ private slots:
|
|||||||
void on_actionLock_triggered();
|
void on_actionLock_triggered();
|
||||||
// Check for unpaused downloading or seeding torrents and prevent system suspend/sleep according to preferences
|
// Check for unpaused downloading or seeding torrents and prevent system suspend/sleep according to preferences
|
||||||
void updatePowerManagementState();
|
void updatePowerManagementState();
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
|
||||||
void checkProgramUpdate();
|
|
||||||
#endif
|
|
||||||
void toolbarMenuRequested(const QPoint &point);
|
void toolbarMenuRequested(const QPoint &point);
|
||||||
void toolbarIconsOnly();
|
void toolbarIconsOnly();
|
||||||
void toolbarTextOnly();
|
void toolbarTextOnly();
|
||||||
@@ -252,10 +248,13 @@ private:
|
|||||||
// Power Management
|
// Power Management
|
||||||
PowerManagement *m_pwr;
|
PowerManagement *m_pwr;
|
||||||
QTimer *m_preventTimer;
|
QTimer *m_preventTimer;
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
|
||||||
QTimer *m_programUpdateTimer;
|
|
||||||
bool m_wasUpdateCheckEnabled;
|
|
||||||
#endif
|
|
||||||
bool m_hasPython;
|
bool m_hasPython;
|
||||||
QMenu *m_toolbarMenu;
|
QMenu *m_toolbarMenu;
|
||||||
|
|
||||||
|
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||||
|
void checkProgramUpdate(bool invokedByUser);
|
||||||
|
void handleUpdateCheckFinished(ProgramUpdater *updater, bool invokedByUser);
|
||||||
|
|
||||||
|
QTimer *m_programUpdateTimer = nullptr;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -211,8 +211,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
|||||||
|
|
||||||
m_ui->IpFilterRefreshBtn->setIcon(UIThemeManager::instance()->getIcon("view-refresh"));
|
m_ui->IpFilterRefreshBtn->setIcon(UIThemeManager::instance()->getIcon("view-refresh"));
|
||||||
|
|
||||||
m_ui->labelGlobalRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow_off")), this, 16));
|
m_ui->labelGlobalRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow_off")), this, 24));
|
||||||
m_ui->labelAltRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow")), this, 16));
|
m_ui->labelAltRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow")), this, 24));
|
||||||
|
|
||||||
m_ui->deleteTorrentWarningIcon->setPixmap(QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical).pixmap(16, 16));
|
m_ui->deleteTorrentWarningIcon->setPixmap(QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical).pixmap(16, 16));
|
||||||
m_ui->deleteTorrentWarningIcon->hide();
|
m_ui->deleteTorrentWarningIcon->hide();
|
||||||
@@ -233,15 +233,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
|||||||
m_ui->hsplitter->setCollapsible(0, false);
|
m_ui->hsplitter->setCollapsible(0, false);
|
||||||
m_ui->hsplitter->setCollapsible(1, false);
|
m_ui->hsplitter->setCollapsible(1, false);
|
||||||
// Get apply button in button box
|
// Get apply button in button box
|
||||||
const QList<QAbstractButton *> buttons = m_ui->buttonBox->buttons();
|
m_applyButton = m_ui->buttonBox->button(QDialogButtonBox::Apply);
|
||||||
for (QAbstractButton *button : buttons)
|
connect(m_applyButton, &QPushButton::clicked, this, &OptionsDialog::applySettings);
|
||||||
{
|
|
||||||
if (m_ui->buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole)
|
|
||||||
{
|
|
||||||
m_applyButton = button;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_ui->scanFoldersView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
m_ui->scanFoldersView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||||
m_ui->scanFoldersView->setModel(ScanFoldersModel::instance());
|
m_ui->scanFoldersView->setModel(ScanFoldersModel::instance());
|
||||||
@@ -249,7 +242,6 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
|||||||
connect(ScanFoldersModel::instance(), &QAbstractListModel::dataChanged, this, &ThisType::enableApplyButton);
|
connect(ScanFoldersModel::instance(), &QAbstractListModel::dataChanged, this, &ThisType::enableApplyButton);
|
||||||
connect(m_ui->scanFoldersView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ThisType::handleScanFolderViewSelectionChanged);
|
connect(m_ui->scanFoldersView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ThisType::handleScanFolderViewSelectionChanged);
|
||||||
|
|
||||||
connect(m_ui->buttonBox, &QDialogButtonBox::clicked, this, &OptionsDialog::applySettings);
|
|
||||||
// Languages supported
|
// Languages supported
|
||||||
initializeLanguageCombo();
|
initializeLanguageCombo();
|
||||||
|
|
||||||
@@ -761,7 +753,7 @@ void OptionsDialog::saveOptions()
|
|||||||
pref->setMailNotificationSMTPPassword(m_ui->mailNotifPassword->text());
|
pref->setMailNotificationSMTPPassword(m_ui->mailNotifPassword->text());
|
||||||
pref->setAutoRunEnabled(m_ui->autoRunBox->isChecked());
|
pref->setAutoRunEnabled(m_ui->autoRunBox->isChecked());
|
||||||
pref->setAutoRunProgram(m_ui->lineEditAutoRun->text().trimmed());
|
pref->setAutoRunProgram(m_ui->lineEditAutoRun->text().trimmed());
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) && defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
pref->setAutoRunConsoleEnabled(m_ui->autoRunConsole->isChecked());
|
pref->setAutoRunConsoleEnabled(m_ui->autoRunConsole->isChecked());
|
||||||
#endif
|
#endif
|
||||||
pref->setActionOnDblClOnTorrentDl(getActionOnDblClOnTorrentDl());
|
pref->setActionOnDblClOnTorrentDl(getActionOnDblClOnTorrentDl());
|
||||||
@@ -1051,7 +1043,7 @@ void OptionsDialog::loadOptions()
|
|||||||
|
|
||||||
m_ui->autoRunBox->setChecked(pref->isAutoRunEnabled());
|
m_ui->autoRunBox->setChecked(pref->isAutoRunEnabled());
|
||||||
m_ui->lineEditAutoRun->setText(pref->getAutoRunProgram());
|
m_ui->lineEditAutoRun->setText(pref->getAutoRunProgram());
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) && defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
m_ui->autoRunConsole->setChecked(pref->isAutoRunConsoleEnabled());
|
m_ui->autoRunConsole->setChecked(pref->isAutoRunConsoleEnabled());
|
||||||
#else
|
#else
|
||||||
m_ui->autoRunConsole->hide();
|
m_ui->autoRunConsole->hide();
|
||||||
@@ -1439,27 +1431,24 @@ void OptionsDialog::on_buttonBox_accepted()
|
|||||||
accept();
|
accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OptionsDialog::applySettings(QAbstractButton *button)
|
void OptionsDialog::applySettings()
|
||||||
{
|
{
|
||||||
if (button == m_applyButton)
|
if (!schedTimesOk())
|
||||||
{
|
{
|
||||||
if (!schedTimesOk())
|
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
|
||||||
{
|
return;
|
||||||
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!webUIAuthenticationOk())
|
|
||||||
{
|
|
||||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isAlternativeWebUIPathValid())
|
|
||||||
{
|
|
||||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
saveOptions();
|
|
||||||
}
|
}
|
||||||
|
if (!webUIAuthenticationOk())
|
||||||
|
{
|
||||||
|
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isAlternativeWebUIPathValid())
|
||||||
|
{
|
||||||
|
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OptionsDialog::closeEvent(QCloseEvent *e)
|
void OptionsDialog::closeEvent(QCloseEvent *e)
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
|
|
||||||
#include "base/settingvalue.h"
|
#include "base/settingvalue.h"
|
||||||
|
|
||||||
class QAbstractButton;
|
|
||||||
class QCloseEvent;
|
class QCloseEvent;
|
||||||
class QListWidgetItem;
|
class QListWidgetItem;
|
||||||
|
|
||||||
@@ -95,7 +94,7 @@ private slots:
|
|||||||
void on_buttonBox_accepted();
|
void on_buttonBox_accepted();
|
||||||
void closeEvent(QCloseEvent *e) override;
|
void closeEvent(QCloseEvent *e) override;
|
||||||
void on_buttonBox_rejected();
|
void on_buttonBox_rejected();
|
||||||
void applySettings(QAbstractButton *button);
|
void applySettings();
|
||||||
void enableApplyButton();
|
void enableApplyButton();
|
||||||
void toggleComboRatioLimitAct();
|
void toggleComboRatioLimitAct();
|
||||||
void changePage(QListWidgetItem *, QListWidgetItem *);
|
void changePage(QListWidgetItem *, QListWidgetItem *);
|
||||||
@@ -181,7 +180,7 @@ private:
|
|||||||
SettingValue<QSize> m_storeDialogSize;
|
SettingValue<QSize> m_storeDialogSize;
|
||||||
SettingValue<QStringList> m_storeHSplitterSize;
|
SettingValue<QStringList> m_storeHSplitterSize;
|
||||||
|
|
||||||
QAbstractButton *m_applyButton;
|
QPushButton *m_applyButton;
|
||||||
|
|
||||||
AdvancedSettings *m_advancedSettings;
|
AdvancedSettings *m_advancedSettings;
|
||||||
|
|
||||||
|
|||||||
@@ -28,18 +28,11 @@
|
|||||||
|
|
||||||
#include "previewlistdelegate.h"
|
#include "previewlistdelegate.h"
|
||||||
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QModelIndex>
|
#include <QModelIndex>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QStyleOptionProgressBar>
|
|
||||||
#include <QStyleOptionViewItem>
|
#include <QStyleOptionViewItem>
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
|
||||||
#include <QProxyStyle>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/string.h"
|
|
||||||
#include "previewselectdialog.h"
|
#include "previewselectdialog.h"
|
||||||
|
|
||||||
PreviewListDelegate::PreviewListDelegate(QObject *parent)
|
PreviewListDelegate::PreviewListDelegate(QObject *parent)
|
||||||
@@ -61,30 +54,19 @@ void PreviewListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case PreviewSelectDialog::PROGRESS:
|
case PreviewSelectDialog::PROGRESS:
|
||||||
{
|
{
|
||||||
const qreal progress = (index.data().toReal() * 100);
|
const qreal progress = (index.data().toReal() * 100);
|
||||||
|
const QString text = (progress >= 100)
|
||||||
|
? QString::fromLatin1("100%")
|
||||||
|
: (Utils::String::fromDouble(progress, 1) + '%');
|
||||||
|
|
||||||
QStyleOptionProgressBar newopt;
|
m_progressBarPainter.paint(painter, option, text, static_cast<int>(progress));
|
||||||
newopt.rect = opt.rect;
|
|
||||||
newopt.text = ((progress == 100) ? QString("100%") : (Utils::String::fromDouble(progress, 1) + '%'));
|
|
||||||
newopt.progress = static_cast<int>(progress);
|
|
||||||
newopt.maximum = 100;
|
|
||||||
newopt.minimum = 0;
|
|
||||||
newopt.state |= QStyle::State_Enabled;
|
|
||||||
newopt.textVisible = true;
|
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
|
||||||
// XXX: To avoid having the progress text on the right of the bar
|
|
||||||
QProxyStyle st("fusion");
|
|
||||||
st.drawControl(QStyle::CE_ProgressBar, &newopt, painter, 0);
|
|
||||||
#else
|
|
||||||
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &newopt, painter);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
QItemDelegate::paint(painter, option, index);
|
QItemDelegate::paint(painter, option, index);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
painter->restore();
|
painter->restore();
|
||||||
|
|||||||
@@ -30,6 +30,8 @@
|
|||||||
|
|
||||||
#include <QItemDelegate>
|
#include <QItemDelegate>
|
||||||
|
|
||||||
|
#include "progressbarpainter.h"
|
||||||
|
|
||||||
class PreviewListDelegate final : public QItemDelegate
|
class PreviewListDelegate final : public QItemDelegate
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -38,7 +40,9 @@ class PreviewListDelegate final : public QItemDelegate
|
|||||||
public:
|
public:
|
||||||
explicit PreviewListDelegate(QObject *parent = nullptr);
|
explicit PreviewListDelegate(QObject *parent = nullptr);
|
||||||
|
|
||||||
private:
|
|
||||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
QWidget *createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const override;
|
QWidget *createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ProgressBarPainter m_progressBarPainter;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -90,9 +90,8 @@ PreviewSelectDialog::PreviewSelectDialog(QWidget *parent, const BitTorrent::Torr
|
|||||||
{
|
{
|
||||||
QString fileName = torrent->fileName(i);
|
QString fileName = torrent->fileName(i);
|
||||||
if (fileName.endsWith(QB_EXT))
|
if (fileName.endsWith(QB_EXT))
|
||||||
fileName.chop(4);
|
fileName.chop(QB_EXT.length());
|
||||||
QString extension = Utils::Fs::fileExtension(fileName).toUpper();
|
if (Utils::Misc::isPreviewable(fileName))
|
||||||
if (Utils::Misc::isPreviewable(extension))
|
|
||||||
{
|
{
|
||||||
int row = m_previewListModel->rowCount();
|
int row = m_previewListModel->rowCount();
|
||||||
m_previewListModel->insertRow(row);
|
m_previewListModel->insertRow(row);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2021 Mike Tzou (Chocobo1)
|
||||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@@ -36,7 +37,6 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QStringList>
|
|
||||||
#include <QXmlStreamReader>
|
#include <QXmlStreamReader>
|
||||||
|
|
||||||
#if defined(Q_OS_WIN)
|
#if defined(Q_OS_WIN)
|
||||||
@@ -44,56 +44,82 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "base/net/downloadmanager.h"
|
#include "base/net/downloadmanager.h"
|
||||||
|
#include "base/utils/version.h"
|
||||||
#include "base/version.h"
|
#include "base/version.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
const QString RSS_URL {QStringLiteral("https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml")};
|
bool isVersionMoreRecent(const QString &remoteVersion)
|
||||||
|
{
|
||||||
|
using Version = Utils::Version<int, 4, 3>;
|
||||||
|
|
||||||
QString getStringValue(QXmlStreamReader &xml);
|
try
|
||||||
|
{
|
||||||
|
const Version newVersion {remoteVersion};
|
||||||
|
const Version currentVersion {QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD};
|
||||||
|
if (newVersion == currentVersion)
|
||||||
|
{
|
||||||
|
const bool isDevVersion = QString::fromLatin1(QBT_VERSION_STATUS).contains(
|
||||||
|
QRegularExpression(QLatin1String("(alpha|beta|rc)")));
|
||||||
|
if (isDevVersion)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return (newVersion > currentVersion);
|
||||||
|
}
|
||||||
|
catch (const std::runtime_error &)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgramUpdater::ProgramUpdater(QObject *parent, bool invokedByUser)
|
void ProgramUpdater::checkForUpdates() const
|
||||||
: QObject(parent)
|
|
||||||
, m_invokedByUser(invokedByUser)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProgramUpdater::checkForUpdates()
|
|
||||||
{
|
{
|
||||||
|
const auto RSS_URL = QString::fromLatin1("https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml");
|
||||||
// Don't change this User-Agent. In case our updater goes haywire,
|
// Don't change this User-Agent. In case our updater goes haywire,
|
||||||
// the filehost can identify it and contact us.
|
// the filehost can identify it and contact us.
|
||||||
Net::DownloadManager::instance()->download(
|
Net::DownloadManager::instance()->download(
|
||||||
Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)")
|
Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)")
|
||||||
, this, &ProgramUpdater::rssDownloadFinished);
|
, this, &ProgramUpdater::rssDownloadFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProgramUpdater::getNewVersion() const
|
||||||
|
{
|
||||||
|
return m_newVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (result.status != Net::DownloadStatus::Success)
|
if (result.status != Net::DownloadStatus::Success)
|
||||||
{
|
{
|
||||||
qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString;
|
qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString;
|
||||||
emit updateCheckFinished(false, QString(), m_invokedByUser);
|
emit updateCheckFinished();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug("Finished downloading the new qBittorrent updates RSS");
|
qDebug("Finished downloading the new qBittorrent updates RSS");
|
||||||
|
|
||||||
|
const auto getStringValue = [](QXmlStreamReader &xml) -> QString
|
||||||
|
{
|
||||||
|
xml.readNext();
|
||||||
|
return (xml.isCharacters() && !xml.isWhitespace())
|
||||||
|
? xml.text().toString()
|
||||||
|
: QString {};
|
||||||
|
};
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_MACOS
|
||||||
const QString OS_TYPE {"Mac OS X"};
|
const QString OS_TYPE {"Mac OS X"};
|
||||||
#elif defined(Q_OS_WIN)
|
#elif defined(Q_OS_WIN)
|
||||||
const QString OS_TYPE
|
const QString OS_TYPE {(::IsWindows7OrGreater()
|
||||||
{(::IsWindows7OrGreater()
|
&& QSysInfo::currentCpuArchitecture().endsWith("64"))
|
||||||
&& QSysInfo::currentCpuArchitecture().endsWith("64"))
|
|
||||||
? "Windows x64" : "Windows"};
|
? "Windows x64" : "Windows"};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QString version;
|
|
||||||
QXmlStreamReader xml(result.data);
|
|
||||||
bool inItem = false;
|
bool inItem = false;
|
||||||
|
QString version;
|
||||||
QString updateLink;
|
QString updateLink;
|
||||||
QString type;
|
QString type;
|
||||||
|
QXmlStreamReader xml(result.data);
|
||||||
|
|
||||||
while (!xml.atEnd())
|
while (!xml.atEnd())
|
||||||
{
|
{
|
||||||
@@ -121,7 +147,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
|||||||
{
|
{
|
||||||
qDebug("Detected version is %s", qUtf8Printable(version));
|
qDebug("Detected version is %s", qUtf8Printable(version));
|
||||||
if (isVersionMoreRecent(version))
|
if (isVersionMoreRecent(version))
|
||||||
m_updateUrl = updateLink;
|
{
|
||||||
|
m_newVersion = version;
|
||||||
|
m_updateURL = updateLink;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -134,51 +163,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit updateCheckFinished(!m_updateUrl.isEmpty(), version, m_invokedByUser);
|
emit updateCheckFinished();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgramUpdater::updateProgram()
|
bool ProgramUpdater::updateProgram() const
|
||||||
{
|
{
|
||||||
Q_ASSERT(!m_updateUrl.isEmpty());
|
return QDesktopServices::openUrl(m_updateURL);
|
||||||
QDesktopServices::openUrl(m_updateUrl);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ProgramUpdater::isVersionMoreRecent(const QString &remoteVersion) const
|
|
||||||
{
|
|
||||||
const QRegularExpressionMatch regVerMatch = QRegularExpression("([0-9.]+)").match(QBT_VERSION);
|
|
||||||
if (regVerMatch.hasMatch())
|
|
||||||
{
|
|
||||||
const QString localVersion = regVerMatch.captured(1);
|
|
||||||
const QVector<QStringRef> remoteParts = remoteVersion.splitRef('.');
|
|
||||||
const QVector<QStringRef> localParts = localVersion.splitRef('.');
|
|
||||||
|
|
||||||
for (int i = 0; i < qMin(remoteParts.size(), localParts.size()); ++i)
|
|
||||||
{
|
|
||||||
if (remoteParts[i].toInt() > localParts[i].toInt())
|
|
||||||
return true;
|
|
||||||
if (remoteParts[i].toInt() < localParts[i].toInt())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Compared parts were equal, if remote version is longer, then it's more recent (2.9.2.1 > 2.9.2)
|
|
||||||
if (remoteParts.size() > localParts.size())
|
|
||||||
return true;
|
|
||||||
// versions are equal, check if the local version is a development release, in which case it is older (2.9.2beta < 2.9.2)
|
|
||||||
const QRegularExpressionMatch regDevelMatch = QRegularExpression("(alpha|beta|rc)").match(QBT_VERSION);
|
|
||||||
if (regDevelMatch.hasMatch())
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
QString getStringValue(QXmlStreamReader &xml)
|
|
||||||
{
|
|
||||||
xml.readNext();
|
|
||||||
if (xml.isCharacters() && !xml.isWhitespace())
|
|
||||||
return xml.text().toString();
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2021 Mike Tzou (Chocobo1)
|
||||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@@ -29,32 +30,33 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
namespace Net
|
namespace Net
|
||||||
{
|
{
|
||||||
struct DownloadResult;
|
struct DownloadResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProgramUpdater : public QObject
|
class ProgramUpdater final : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(ProgramUpdater)
|
Q_DISABLE_COPY(ProgramUpdater)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ProgramUpdater(QObject *parent = nullptr, bool invokedByUser = false);
|
using QObject::QObject;
|
||||||
|
|
||||||
void checkForUpdates();
|
void checkForUpdates() const;
|
||||||
void updateProgram();
|
QString getNewVersion() const;
|
||||||
|
bool updateProgram() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateCheckFinished(bool updateAvailable, QString version, bool invokedByUser);
|
void updateCheckFinished();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void rssDownloadFinished(const Net::DownloadResult &result);
|
void rssDownloadFinished(const Net::DownloadResult &result);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isVersionMoreRecent(const QString &remoteVersion) const;
|
QString m_newVersion;
|
||||||
|
QUrl m_updateURL;
|
||||||
QString m_updateUrl;
|
|
||||||
bool m_invokedByUser;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,48 +26,46 @@
|
|||||||
* exception statement from your version.
|
* exception statement from your version.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "progressbardelegate.h"
|
#include "progressbarpainter.h"
|
||||||
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QModelIndex>
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
#include <QPalette>
|
||||||
|
#include <QStyleOptionProgressBar>
|
||||||
#include <QStyleOptionViewItem>
|
#include <QStyleOptionViewItem>
|
||||||
|
|
||||||
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
|
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
|
||||||
#include <QProxyStyle>
|
#include <QProxyStyle>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ProgressBarDelegate::ProgressBarDelegate(const int progressColumn, const int dataRole, QObject *parent)
|
ProgressBarPainter::ProgressBarPainter()
|
||||||
: QStyledItemDelegate {parent}
|
|
||||||
, m_progressColumn {progressColumn}
|
|
||||||
, m_dataRole {dataRole}
|
|
||||||
{
|
{
|
||||||
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
|
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
|
||||||
m_dummyProgressBar.setStyle(new QProxyStyle {"fusion"});
|
auto *fusionStyle = new QProxyStyle {"fusion"};
|
||||||
|
fusionStyle->setParent(&m_dummyProgressBar);
|
||||||
|
m_dummyProgressBar.setStyle(fusionStyle);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgressBarDelegate::initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const
|
void ProgressBarPainter::paint(QPainter *painter, const QStyleOptionViewItem &option, const QString &text, const int progress) const
|
||||||
{
|
{
|
||||||
option.text = index.data().toString();
|
QStyleOptionProgressBar styleOption;
|
||||||
option.progress = static_cast<int>(index.data(m_dataRole).toReal());
|
styleOption.initFrom(&m_dummyProgressBar);
|
||||||
option.maximum = 100;
|
// QStyleOptionProgressBar fields
|
||||||
option.minimum = 0;
|
styleOption.maximum = 100;
|
||||||
option.state |= (QStyle::State_Enabled | QStyle::State_Horizontal);
|
styleOption.minimum = 0;
|
||||||
option.textVisible = true;
|
styleOption.progress = progress;
|
||||||
}
|
styleOption.text = text;
|
||||||
|
styleOption.textVisible = true;
|
||||||
|
// QStyleOption fields
|
||||||
|
styleOption.rect = option.rect;
|
||||||
|
styleOption.state = option.state;
|
||||||
|
|
||||||
void ProgressBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
const bool isEnabled = option.state.testFlag(QStyle::State_Enabled);
|
||||||
{
|
styleOption.palette.setCurrentColorGroup(isEnabled ? QPalette::Active : QPalette::Disabled);
|
||||||
if (index.column() != m_progressColumn)
|
|
||||||
return QStyledItemDelegate::paint(painter, option, index);
|
|
||||||
|
|
||||||
QStyleOptionProgressBar newopt;
|
|
||||||
newopt.initFrom(&m_dummyProgressBar);
|
|
||||||
newopt.rect = option.rect;
|
|
||||||
initProgressStyleOption(newopt, index);
|
|
||||||
|
|
||||||
painter->save();
|
painter->save();
|
||||||
m_dummyProgressBar.style()->drawControl(QStyle::CE_ProgressBar, &newopt, painter, &m_dummyProgressBar);
|
const QStyle *style = m_dummyProgressBar.style();
|
||||||
|
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter);
|
||||||
|
style->drawControl(QStyle::CE_ProgressBar, &styleOption, painter, &m_dummyProgressBar);
|
||||||
painter->restore();
|
painter->restore();
|
||||||
}
|
}
|
||||||
@@ -29,23 +29,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QProgressBar>
|
#include <QProgressBar>
|
||||||
#include <QStyledItemDelegate>
|
|
||||||
|
|
||||||
class QStyleOptionProgressBar;
|
class QStyleOptionViewItem;
|
||||||
|
|
||||||
class ProgressBarDelegate : public QStyledItemDelegate
|
class ProgressBarPainter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ProgressBarDelegate(int progressColumn, int dataRole, QObject *parent = nullptr);
|
ProgressBarPainter();
|
||||||
|
|
||||||
protected:
|
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QString &text, int progress) const;
|
||||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
|
||||||
virtual void initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const int m_progressColumn;
|
|
||||||
const int m_dataRole;
|
|
||||||
|
|
||||||
// for painting progressbar with stylesheet option, a dummy progress bar is required
|
// for painting progressbar with stylesheet option, a dummy progress bar is required
|
||||||
QProgressBar m_dummyProgressBar;
|
QProgressBar m_dummyProgressBar;
|
||||||
};
|
};
|
||||||
@@ -270,8 +270,8 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
|
|||||||
// Do not allow user to add peers in a private torrent
|
// Do not allow user to add peers in a private torrent
|
||||||
if (!torrent->isQueued() && !torrent->isChecking() && !torrent->isPrivate())
|
if (!torrent->isQueued() && !torrent->isChecking() && !torrent->isPrivate())
|
||||||
{
|
{
|
||||||
const QAction *addPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("user-group-new"), tr("Add a new peer..."));
|
menu->addAction(UIThemeManager::instance()->getIcon("user-group-new"), tr("Add a new peer...")
|
||||||
connect(addPeerAct, &QAction::triggered, this, [this, torrent]()
|
, this, [this, torrent]()
|
||||||
{
|
{
|
||||||
const QVector<BitTorrent::PeerAddress> peersList = PeersAdditionDialog::askForPeers(this);
|
const QVector<BitTorrent::PeerAddress> peersList = PeersAdditionDialog::askForPeers(this);
|
||||||
const int peerCount = std::count_if(peersList.cbegin(), peersList.cend(), [torrent](const BitTorrent::PeerAddress &peer)
|
const int peerCount = std::count_if(peersList.cbegin(), peersList.cend(), [torrent](const BitTorrent::PeerAddress &peer)
|
||||||
@@ -287,13 +287,11 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
|
|||||||
|
|
||||||
if (!selectionModel()->selectedRows().isEmpty())
|
if (!selectionModel()->selectedRows().isEmpty())
|
||||||
{
|
{
|
||||||
const QAction *copyPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy IP:port"));
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy IP:port")
|
||||||
connect(copyPeerAct, &QAction::triggered, this, &PeerListWidget::copySelectedPeers);
|
, this, &PeerListWidget::copySelectedPeers);
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
menu->addAction(UIThemeManager::instance()->getIcon("user-group-delete"), tr("Ban peer permanently")
|
||||||
const QAction *banAct = menu->addAction(UIThemeManager::instance()->getIcon("user-group-delete"), tr("Ban peer permanently"));
|
, this, &PeerListWidget::banSelectedPeers);
|
||||||
connect(banAct, &QAction::triggered, this, &PeerListWidget::banSelectedPeers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (menu->isEmpty())
|
if (menu->isEmpty())
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
|
|
||||||
#include "propertieswidget.h"
|
#include "propertieswidget.h"
|
||||||
|
|
||||||
#include <QAction>
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
@@ -313,8 +312,9 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent)
|
|||||||
|
|
||||||
// Save path
|
// Save path
|
||||||
updateSavePath(m_torrent);
|
updateSavePath(m_torrent);
|
||||||
// Hash
|
// Info hash (Truncated info hash (torrent ID) with libtorrent2)
|
||||||
m_ui->labelHashVal->setText(m_torrent->hash());
|
// TODO: Update label for this property to express its meaning more clearly (or change it to display real info hash(es))
|
||||||
|
m_ui->labelHashVal->setText(m_torrent->id().toString());
|
||||||
m_propListModel->model()->clear();
|
m_propListModel->model()->clear();
|
||||||
if (m_torrent->hasMetadata())
|
if (m_torrent->hasMetadata())
|
||||||
{
|
{
|
||||||
@@ -586,57 +586,82 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &)
|
|||||||
{
|
{
|
||||||
const QModelIndex index = selectedRows[0];
|
const QModelIndex index = selectedRows[0];
|
||||||
|
|
||||||
const QAction *actOpen = menu->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open"));
|
menu->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open")
|
||||||
connect(actOpen, &QAction::triggered, this, [this, index]() { openItem(index); });
|
, this, [this, index]() { openItem(index); });
|
||||||
|
menu->addAction(UIThemeManager::instance()->getIcon("inode-directory"), tr("Open Containing Folder")
|
||||||
const QAction *actOpenContainingFolder = menu->addAction(UIThemeManager::instance()->getIcon("inode-directory"), tr("Open Containing Folder"));
|
, this, [this, index]() { openParentFolder(index); });
|
||||||
connect(actOpenContainingFolder, &QAction::triggered, this, [this, index]() { openParentFolder(index); });
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
|
||||||
|
, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); });
|
||||||
const QAction *actRename = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."));
|
|
||||||
connect(actRename, &QAction::triggered, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); });
|
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_torrent->isSeed())
|
if (!m_torrent->isSeed())
|
||||||
{
|
{
|
||||||
QMenu *subMenu = menu->addMenu(tr("Priority"));
|
const auto applyPriorities = [this](const BitTorrent::DownloadPriority prio)
|
||||||
|
|
||||||
const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio)
|
|
||||||
{
|
{
|
||||||
|
const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
|
||||||
for (const QModelIndex &index : selectedRows)
|
for (const QModelIndex &index : selectedRows)
|
||||||
{
|
{
|
||||||
m_propListModel->setData(
|
m_propListModel->setData(index.sibling(index.row(), PRIORITY)
|
||||||
m_propListModel->index(index.row(), PRIORITY, index.parent()), static_cast<int>(prio));
|
, static_cast<int>(prio));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save changes
|
// Save changes
|
||||||
filteredFilesChanged();
|
filteredFilesChanged();
|
||||||
};
|
};
|
||||||
|
|
||||||
connect(m_ui->actionNotDownloaded, &QAction::triggered, subMenu, [applyPriorities]()
|
QMenu *subMenu = menu->addMenu(tr("Priority"));
|
||||||
|
|
||||||
|
subMenu->addAction(tr("Do not download"), subMenu, [applyPriorities]()
|
||||||
{
|
{
|
||||||
applyPriorities(BitTorrent::DownloadPriority::Ignored);
|
applyPriorities(BitTorrent::DownloadPriority::Ignored);
|
||||||
});
|
});
|
||||||
subMenu->addAction(m_ui->actionNotDownloaded);
|
subMenu->addAction(tr("Normal"), subMenu, [applyPriorities]()
|
||||||
|
|
||||||
connect(m_ui->actionNormal, &QAction::triggered, subMenu, [applyPriorities]()
|
|
||||||
{
|
{
|
||||||
applyPriorities(BitTorrent::DownloadPriority::Normal);
|
applyPriorities(BitTorrent::DownloadPriority::Normal);
|
||||||
});
|
});
|
||||||
subMenu->addAction(m_ui->actionNormal);
|
subMenu->addAction(tr("High"), subMenu, [applyPriorities]()
|
||||||
|
|
||||||
connect(m_ui->actionHigh, &QAction::triggered, subMenu, [applyPriorities]()
|
|
||||||
{
|
{
|
||||||
applyPriorities(BitTorrent::DownloadPriority::High);
|
applyPriorities(BitTorrent::DownloadPriority::High);
|
||||||
});
|
});
|
||||||
subMenu->addAction(m_ui->actionHigh);
|
subMenu->addAction(tr("Maximum"), subMenu, [applyPriorities]()
|
||||||
|
|
||||||
connect(m_ui->actionMaximum, &QAction::triggered, subMenu, [applyPriorities]()
|
|
||||||
{
|
{
|
||||||
applyPriorities(BitTorrent::DownloadPriority::Maximum);
|
applyPriorities(BitTorrent::DownloadPriority::Maximum);
|
||||||
});
|
});
|
||||||
subMenu->addAction(m_ui->actionMaximum);
|
subMenu->addSeparator();
|
||||||
|
subMenu->addAction(tr("By shown file order"), subMenu, [this]()
|
||||||
|
{
|
||||||
|
// Equally distribute the selected items into groups and for each group assign
|
||||||
|
// a download priority that will apply to each item. The number of groups depends on how
|
||||||
|
// many "download priority" are available to be assigned
|
||||||
|
|
||||||
|
const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
|
||||||
|
|
||||||
|
const int priorityGroups = 3;
|
||||||
|
const int priorityGroupSize = std::max((selectedRows.length() / priorityGroups), 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < selectedRows.length(); ++i)
|
||||||
|
{
|
||||||
|
auto priority = BitTorrent::DownloadPriority::Ignored;
|
||||||
|
switch (i / priorityGroupSize)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
priority = BitTorrent::DownloadPriority::Maximum;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
priority = BitTorrent::DownloadPriority::High;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
case 2:
|
||||||
|
priority = BitTorrent::DownloadPriority::Normal;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QModelIndex &index = selectedRows[i];
|
||||||
|
m_propListModel->setData(index.sibling(index.row(), PRIORITY)
|
||||||
|
, static_cast<int>(priority));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// The selected torrent might have disappeared during exec()
|
// The selected torrent might have disappeared during exec()
|
||||||
@@ -660,21 +685,17 @@ void PropertiesWidget::displayWebSeedListMenu(const QPoint &)
|
|||||||
QMenu *menu = new QMenu(this);
|
QMenu *menu = new QMenu(this);
|
||||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
const QAction *actAdd = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New Web seed"));
|
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New Web seed"), this, &PropertiesWidget::askWebSeed);
|
||||||
connect(actAdd, &QAction::triggered, this, &PropertiesWidget::askWebSeed);
|
|
||||||
|
|
||||||
if (!rows.isEmpty())
|
if (!rows.isEmpty())
|
||||||
{
|
{
|
||||||
const QAction *actDel = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove Web seed"));
|
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove Web seed")
|
||||||
connect(actDel, &QAction::triggered, this, &PropertiesWidget::deleteSelectedUrlSeeds);
|
, this, &PropertiesWidget::deleteSelectedUrlSeeds);
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy Web seed URL")
|
||||||
const QAction *actCpy = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy Web seed URL"));
|
, this, &PropertiesWidget::copySelectedWebSeedsToClipboard);
|
||||||
connect(actCpy, &QAction::triggered, this, &PropertiesWidget::copySelectedWebSeedsToClipboard);
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Edit Web seed URL")
|
||||||
|
, this, &PropertiesWidget::editWebSeed);
|
||||||
const QAction *actEdit = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Edit Web seed URL"));
|
|
||||||
connect(actEdit, &QAction::triggered, this, &PropertiesWidget::editWebSeed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
menu->popup(QCursor::pos());
|
menu->popup(QCursor::pos());
|
||||||
@@ -780,7 +801,7 @@ void PropertiesWidget::editWebSeed()
|
|||||||
|
|
||||||
if (!m_ui->listWebSeeds->findItems(newSeed, Qt::MatchFixedString).empty())
|
if (!m_ui->listWebSeeds->findItems(newSeed, Qt::MatchFixedString).empty())
|
||||||
{
|
{
|
||||||
QMessageBox::warning(this, tr("qBittorrent"),
|
QMessageBox::warning(this, QLatin1String("qBittorrent"),
|
||||||
tr("This URL seed is already in the list."),
|
tr("This URL seed is already in the list."),
|
||||||
QMessageBox::Ok);
|
QMessageBox::Ok);
|
||||||
return;
|
return;
|
||||||
@@ -804,7 +825,8 @@ void PropertiesWidget::filteredFilesChanged()
|
|||||||
|
|
||||||
void PropertiesWidget::filterText(const QString &filter)
|
void PropertiesWidget::filterText(const QString &filter)
|
||||||
{
|
{
|
||||||
m_propListModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive, QRegExp::WildcardUnix));
|
const QString pattern = Utils::String::wildcardToRegexPattern(filter);
|
||||||
|
m_propListModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
||||||
if (filter.isEmpty())
|
if (filter.isEmpty())
|
||||||
{
|
{
|
||||||
m_ui->filesList->collapseAll();
|
m_ui->filesList->collapseAll();
|
||||||
|
|||||||
@@ -1086,29 +1086,6 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
<action name="actionNotDownloaded">
|
|
||||||
<property name="text">
|
|
||||||
<string>Do not download</string>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Do not download</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionMaximum">
|
|
||||||
<property name="text">
|
|
||||||
<string>Maximum</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionHigh">
|
|
||||||
<property name="text">
|
|
||||||
<string>High</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionNormal">
|
|
||||||
<property name="text">
|
|
||||||
<string>Normal</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|||||||
@@ -28,61 +28,22 @@
|
|||||||
|
|
||||||
#include "proplistdelegate.h"
|
#include "proplistdelegate.h"
|
||||||
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QModelIndex>
|
#include <QModelIndex>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPalette>
|
|
||||||
#include <QProgressBar>
|
#include <QProgressBar>
|
||||||
#include <QStyleOptionProgressBar>
|
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
|
||||||
#include <QProxyStyle>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "base/bittorrent/downloadpriority.h"
|
#include "base/bittorrent/downloadpriority.h"
|
||||||
#include "base/bittorrent/torrent.h"
|
#include "base/bittorrent/torrent.h"
|
||||||
#include "gui/torrentcontentmodel.h"
|
#include "gui/torrentcontentmodel.h"
|
||||||
#include "propertieswidget.h"
|
#include "propertieswidget.h"
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
QPalette progressBarDisabledPalette()
|
|
||||||
{
|
|
||||||
static const QPalette palette = []()
|
|
||||||
{
|
|
||||||
QProgressBar bar;
|
|
||||||
bar.setEnabled(false);
|
|
||||||
QStyleOptionProgressBar opt;
|
|
||||||
opt.initFrom(&bar);
|
|
||||||
return opt.palette;
|
|
||||||
}();
|
|
||||||
return palette;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PropListDelegate::PropListDelegate(PropertiesWidget *properties)
|
PropListDelegate::PropListDelegate(PropertiesWidget *properties)
|
||||||
: ProgressBarDelegate {PROGRESS, TorrentContentModel::UnderlyingDataRole, properties}
|
: QStyledItemDelegate {properties}
|
||||||
, m_properties(properties)
|
, m_properties {properties}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void PropListDelegate::initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const
|
|
||||||
{
|
|
||||||
ProgressBarDelegate::initProgressStyleOption(option, index);
|
|
||||||
const int priority
|
|
||||||
= index.sibling(index.row(), PRIORITY).data(TorrentContentModel::UnderlyingDataRole).toInt();
|
|
||||||
if (static_cast<BitTorrent::DownloadPriority>(priority) == BitTorrent::DownloadPriority::Ignored)
|
|
||||||
{
|
|
||||||
option.state &= ~QStyle::State_Enabled;
|
|
||||||
option.palette = progressBarDisabledPalette();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
option.state |= QStyle::State_Enabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PropListDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
void PropListDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||||
{
|
{
|
||||||
auto *combobox = static_cast<QComboBox *>(editor);
|
auto *combobox = static_cast<QComboBox *>(editor);
|
||||||
@@ -156,3 +117,25 @@ void PropListDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionV
|
|||||||
{
|
{
|
||||||
editor->setGeometry(option.rect);
|
editor->setGeometry(option.rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PropListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
switch (index.column())
|
||||||
|
{
|
||||||
|
case PropColumn::PROGRESS:
|
||||||
|
{
|
||||||
|
const int progress = static_cast<int>(index.data(TorrentContentModel::UnderlyingDataRole).toReal());
|
||||||
|
const int priority = index.sibling(index.row(), PropColumn::PRIORITY).data(TorrentContentModel::UnderlyingDataRole).toInt();
|
||||||
|
const bool isEnabled = static_cast<BitTorrent::DownloadPriority>(priority) != BitTorrent::DownloadPriority::Ignored;
|
||||||
|
|
||||||
|
QStyleOptionViewItem customOption {option};
|
||||||
|
customOption.state.setFlag(QStyle::State_Enabled, isEnabled);
|
||||||
|
|
||||||
|
m_progressBarPainter.paint(painter, customOption, index.data().toString(), progress);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
QStyledItemDelegate::paint(painter, option, index);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,11 +28,12 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "gui/progressbardelegate.h"
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
|
#include "gui/progressbarpainter.h"
|
||||||
|
|
||||||
class QAbstractItemModel;
|
class QAbstractItemModel;
|
||||||
class QModelIndex;
|
class QModelIndex;
|
||||||
class QPainter;
|
|
||||||
class QStyleOptionViewItem;
|
class QStyleOptionViewItem;
|
||||||
|
|
||||||
class PropertiesWidget;
|
class PropertiesWidget;
|
||||||
@@ -48,25 +49,26 @@ enum PropColumn
|
|||||||
AVAILABILITY
|
AVAILABILITY
|
||||||
};
|
};
|
||||||
|
|
||||||
class PropListDelegate final : public ProgressBarDelegate
|
class PropListDelegate final : public QStyledItemDelegate
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(PropListDelegate)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit PropListDelegate(PropertiesWidget *properties);
|
explicit PropListDelegate(PropertiesWidget *properties);
|
||||||
|
|
||||||
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||||
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, const QModelIndex &index) const override;
|
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
|
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
|
||||||
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex & /* index */) const override;
|
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void filteredFilesChanged() const;
|
void filteredFilesChanged() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const override;
|
|
||||||
|
|
||||||
PropertiesWidget *m_properties;
|
PropertiesWidget *m_properties;
|
||||||
|
ProgressBarPainter m_progressBarPainter;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2021 Prince Gupta <guptaprince8832@gmail.com>
|
||||||
* Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru>
|
* Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@@ -34,33 +35,13 @@
|
|||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QPen>
|
#include <QPen>
|
||||||
|
|
||||||
|
#include "base/bittorrent/session.h"
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/unicodestrings.h"
|
#include "base/unicodestrings.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
enum PeriodInSeconds
|
|
||||||
{
|
|
||||||
MIN1_SEC = 60,
|
|
||||||
MIN5_SEC = 5 * 60,
|
|
||||||
MIN30_SEC = 30 * 60,
|
|
||||||
HOUR6_SEC = 6 * 60 * 60,
|
|
||||||
HOUR12_SEC = 12 * 60 * 60,
|
|
||||||
HOUR24_SEC = 24 * 60 * 60
|
|
||||||
};
|
|
||||||
|
|
||||||
const int MIN5_BUF_SIZE = 5 * 60;
|
|
||||||
const int MIN30_BUF_SIZE = 5 * 60;
|
|
||||||
const int HOUR6_BUF_SIZE = 5 * 60;
|
|
||||||
const int HOUR12_BUF_SIZE = 10 * 60;
|
|
||||||
const int HOUR24_BUF_SIZE = 10 * 60;
|
|
||||||
const int DIVIDER_30MIN = MIN30_SEC / MIN30_BUF_SIZE;
|
|
||||||
const int DIVIDER_6HOUR = HOUR6_SEC / HOUR6_BUF_SIZE;
|
|
||||||
const int DIVIDER_12HOUR = HOUR12_SEC / HOUR12_BUF_SIZE;
|
|
||||||
const int DIVIDER_24HOUR = HOUR24_SEC / HOUR24_BUF_SIZE;
|
|
||||||
|
|
||||||
|
|
||||||
// table of supposed nice steps for grid marks to get nice looking quarters of scale
|
// table of supposed nice steps for grid marks to get nice looking quarters of scale
|
||||||
const double roundingTable[] = {1.2, 1.6, 2, 2.4, 2.8, 3.2, 4, 6, 8};
|
const double roundingTable[] = {1.2, 1.6, 2, 2.4, 2.8, 3.2, 4, 6, 8};
|
||||||
|
|
||||||
@@ -118,55 +99,69 @@ namespace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeedPlotView::Averager::Averager(int divider, boost::circular_buffer<PointData> &sink)
|
SpeedPlotView::Averager::Averager(const milliseconds duration, const milliseconds resolution)
|
||||||
: m_divider(divider)
|
: m_resolution {resolution}
|
||||||
, m_sink(sink)
|
, m_maxDuration {duration}
|
||||||
, m_counter(0)
|
, m_sink {static_cast<DataCircularBuffer::size_type>(duration / resolution)}
|
||||||
, m_accumulator {}
|
|
||||||
{
|
{
|
||||||
|
m_lastSampleTime.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpeedPlotView::Averager::push(const PointData &pointData)
|
bool SpeedPlotView::Averager::push(const SampleData &sampleData)
|
||||||
{
|
{
|
||||||
// Accumulator overflow will be hit in worst case on longest used averaging span,
|
// Accumulator overflow will be hit in worst case on longest used averaging span,
|
||||||
// defined by divider value. Maximum divider is DIVIDER_24HOUR = 144
|
// defined by divider value. Maximum divider is DIVIDER_24HOUR = 144
|
||||||
// Using int32 for accumulator we get overflow when transfer speed reaches 2^31/144 ~~ 14.2 MBytes/s.
|
// Using int32 for accumulator we get overflow when transfer speed reaches 2^31/144 ~~ 14.2 MBytes/s.
|
||||||
// With quint64 this speed limit is 2^64/144 ~~ 114 PBytes/s.
|
// With quint64 this speed limit is 2^64/144 ~~ 114 PBytes/s.
|
||||||
// This speed is inaccessible to an ordinary user.
|
// This speed is inaccessible to an ordinary user.
|
||||||
m_accumulator.x += pointData.x;
|
++m_counter;
|
||||||
for (int id = UP; id < NB_GRAPHS; ++id)
|
for (int id = UP; id < NB_GRAPHS; ++id)
|
||||||
m_accumulator.y[id] += pointData.y[id];
|
m_accumulator[id] += sampleData[id];
|
||||||
m_counter = (m_counter + 1) % m_divider;
|
|
||||||
if (m_counter != 0)
|
// system may go to sleep, that can cause very big elapsed interval
|
||||||
return; // still accumulating
|
const milliseconds updateInterval {static_cast<int64_t>(BitTorrent::Session::instance()->refreshInterval() * 1.25)};
|
||||||
|
const milliseconds maxElapsed {std::max(updateInterval, m_resolution)};
|
||||||
|
const milliseconds elapsed {std::min(milliseconds {m_lastSampleTime.elapsed()}, maxElapsed)};
|
||||||
|
if (elapsed < m_resolution)
|
||||||
|
return false; // still accumulating
|
||||||
|
|
||||||
// it is time final averaging calculations
|
// it is time final averaging calculations
|
||||||
for (int id = UP; id < NB_GRAPHS; ++id)
|
for (int id = UP; id < NB_GRAPHS; ++id)
|
||||||
m_accumulator.y[id] /= m_divider;
|
m_accumulator[id] /= m_counter;
|
||||||
m_accumulator.x /= m_divider;
|
|
||||||
|
m_currentDuration += elapsed;
|
||||||
|
|
||||||
|
// remove extra data from front if we reached max duration
|
||||||
|
if (m_currentDuration > m_maxDuration)
|
||||||
|
{
|
||||||
|
// once we go above the max duration never go below that
|
||||||
|
// otherwise it will cause empty space in graphs
|
||||||
|
while (!m_sink.empty()
|
||||||
|
&& ((m_currentDuration - m_sink.front().duration) >= m_maxDuration))
|
||||||
|
{
|
||||||
|
m_currentDuration -= m_sink.front().duration;
|
||||||
|
m_sink.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// now flush out averaged data
|
// now flush out averaged data
|
||||||
m_sink.push_back(m_accumulator);
|
Q_ASSERT(m_sink.size() < m_sink.capacity());
|
||||||
|
m_sink.push_back({elapsed, m_accumulator});
|
||||||
|
|
||||||
|
// reset
|
||||||
m_accumulator = {};
|
m_accumulator = {};
|
||||||
|
m_counter = 0;
|
||||||
|
m_lastSampleTime.restart();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SpeedPlotView::Averager::isReady() const
|
const SpeedPlotView::DataCircularBuffer &SpeedPlotView::Averager::data() const
|
||||||
{
|
{
|
||||||
return m_counter == 0;
|
return m_sink;
|
||||||
}
|
}
|
||||||
|
|
||||||
SpeedPlotView::SpeedPlotView(QWidget *parent)
|
SpeedPlotView::SpeedPlotView(QWidget *parent)
|
||||||
: QGraphicsView(parent)
|
: QGraphicsView {parent}
|
||||||
, m_data5Min(MIN5_BUF_SIZE)
|
|
||||||
, m_data30Min(MIN30_BUF_SIZE)
|
|
||||||
, m_data6Hour(HOUR6_BUF_SIZE)
|
|
||||||
, m_data12Hour(HOUR12_BUF_SIZE)
|
|
||||||
, m_data24Hour(HOUR24_BUF_SIZE)
|
|
||||||
, m_currentData(&m_data5Min)
|
|
||||||
, m_averager30Min(DIVIDER_30MIN, m_data30Min)
|
|
||||||
, m_averager6Hour(DIVIDER_6HOUR, m_data6Hour)
|
|
||||||
, m_averager12Hour(DIVIDER_12HOUR, m_data12Hour)
|
|
||||||
, m_averager24Hour(DIVIDER_24HOUR, m_data24Hour)
|
|
||||||
, m_period(MIN5)
|
|
||||||
, m_viewablePointsCount(MIN5_SEC)
|
|
||||||
{
|
{
|
||||||
QPen greenPen;
|
QPen greenPen;
|
||||||
greenPen.setWidthF(1.5);
|
greenPen.setWidthF(1.5);
|
||||||
@@ -205,69 +200,65 @@ void SpeedPlotView::setGraphEnable(GraphID id, bool enable)
|
|||||||
viewport()->update();
|
viewport()->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpeedPlotView::pushPoint(const SpeedPlotView::PointData &point)
|
void SpeedPlotView::pushPoint(const SpeedPlotView::SampleData &point)
|
||||||
{
|
{
|
||||||
m_data5Min.push_back(point);
|
for (Averager *averager : {&m_averager5Min, &m_averager30Min
|
||||||
m_averager30Min.push(point);
|
, &m_averager6Hour, &m_averager12Hour
|
||||||
m_averager6Hour.push(point);
|
, &m_averager24Hour})
|
||||||
m_averager12Hour.push(point);
|
{
|
||||||
m_averager24Hour.push(point);
|
if (averager->push(point))
|
||||||
|
{
|
||||||
|
if (m_currentAverager == averager)
|
||||||
|
viewport()->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpeedPlotView::setPeriod(const TimePeriod period)
|
void SpeedPlotView::setPeriod(const TimePeriod period)
|
||||||
{
|
{
|
||||||
m_period = period;
|
|
||||||
|
|
||||||
switch (period)
|
switch (period)
|
||||||
{
|
{
|
||||||
case SpeedPlotView::MIN1:
|
case SpeedPlotView::MIN1:
|
||||||
m_viewablePointsCount = MIN1_SEC;
|
m_currentMaxDuration = 1min;
|
||||||
m_currentData = &m_data5Min;
|
m_currentAverager = &m_averager5Min;
|
||||||
break;
|
break;
|
||||||
case SpeedPlotView::MIN5:
|
case SpeedPlotView::MIN5:
|
||||||
m_viewablePointsCount = MIN5_SEC;
|
m_currentMaxDuration = 5min;
|
||||||
m_currentData = &m_data5Min;
|
m_currentAverager = &m_averager5Min;
|
||||||
break;
|
break;
|
||||||
case SpeedPlotView::MIN30:
|
case SpeedPlotView::MIN30:
|
||||||
m_viewablePointsCount = MIN30_BUF_SIZE;
|
m_currentMaxDuration = 30min;
|
||||||
m_currentData = &m_data30Min;
|
m_currentAverager = &m_averager30Min;
|
||||||
|
break;
|
||||||
|
case SpeedPlotView::HOUR3:
|
||||||
|
m_currentMaxDuration = 3h;
|
||||||
|
m_currentAverager = &m_averager6Hour;
|
||||||
break;
|
break;
|
||||||
case SpeedPlotView::HOUR6:
|
case SpeedPlotView::HOUR6:
|
||||||
m_viewablePointsCount = HOUR6_BUF_SIZE;
|
m_currentMaxDuration = 6h;
|
||||||
m_currentData = &m_data6Hour;
|
m_currentAverager = &m_averager6Hour;
|
||||||
break;
|
break;
|
||||||
case SpeedPlotView::HOUR12:
|
case SpeedPlotView::HOUR12:
|
||||||
m_viewablePointsCount = HOUR12_BUF_SIZE;
|
m_currentMaxDuration = 12h;
|
||||||
m_currentData = &m_data12Hour;
|
m_currentAverager = &m_averager12Hour;
|
||||||
break;
|
break;
|
||||||
case SpeedPlotView::HOUR24:
|
case SpeedPlotView::HOUR24:
|
||||||
m_viewablePointsCount = HOUR24_BUF_SIZE;
|
m_currentMaxDuration = 24h;
|
||||||
m_currentData = &m_data24Hour;
|
m_currentAverager = &m_averager24Hour;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewport()->update();
|
viewport()->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpeedPlotView::replot()
|
const SpeedPlotView::DataCircularBuffer &SpeedPlotView::currentData() const
|
||||||
{
|
{
|
||||||
if ((m_period == MIN1)
|
return m_currentAverager->data();
|
||||||
|| (m_period == MIN5)
|
|
||||||
|| ((m_period == MIN30) && m_averager30Min.isReady())
|
|
||||||
|| ((m_period == HOUR6) && m_averager6Hour.isReady())
|
|
||||||
|| ((m_period == HOUR12) && m_averager12Hour.isReady())
|
|
||||||
|| ((m_period == HOUR24) && m_averager24Hour.isReady()) )
|
|
||||||
viewport()->update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::circular_buffer<SpeedPlotView::PointData> &SpeedPlotView::getCurrentData()
|
quint64 SpeedPlotView::maxYValue() const
|
||||||
{
|
{
|
||||||
return *m_currentData;
|
const DataCircularBuffer &queue = currentData();
|
||||||
}
|
|
||||||
|
|
||||||
quint64 SpeedPlotView::maxYValue()
|
|
||||||
{
|
|
||||||
boost::circular_buffer<PointData> &queue = getCurrentData();
|
|
||||||
|
|
||||||
quint64 maxYValue = 0;
|
quint64 maxYValue = 0;
|
||||||
for (int id = UP; id < NB_GRAPHS; ++id)
|
for (int id = UP; id < NB_GRAPHS; ++id)
|
||||||
@@ -276,9 +267,14 @@ quint64 SpeedPlotView::maxYValue()
|
|||||||
if (!m_properties[static_cast<GraphID>(id)].enable)
|
if (!m_properties[static_cast<GraphID>(id)].enable)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for (int i = static_cast<int>(queue.size()) - 1, j = 0; (i >= 0) && (j < m_viewablePointsCount); --i, ++j)
|
milliseconds duration {0ms};
|
||||||
if (queue[i].y[id] > maxYValue)
|
for (int i = static_cast<int>(queue.size()) - 1; i >= 0; --i)
|
||||||
maxYValue = queue[i].y[id];
|
{
|
||||||
|
maxYValue = std::max(maxYValue, queue[i].data[id]);
|
||||||
|
duration += queue[i].duration;
|
||||||
|
if (duration >= m_currentMaxDuration)
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return maxYValue;
|
return maxYValue;
|
||||||
@@ -308,13 +304,10 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
|||||||
|
|
||||||
int yAxisWidth = 0;
|
int yAxisWidth = 0;
|
||||||
for (const QString &label : speedLabels)
|
for (const QString &label : speedLabels)
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
{
|
||||||
if (fontMetrics.horizontalAdvance(label) > yAxisWidth)
|
if (fontMetrics.horizontalAdvance(label) > yAxisWidth)
|
||||||
yAxisWidth = fontMetrics.horizontalAdvance(label);
|
yAxisWidth = fontMetrics.horizontalAdvance(label);
|
||||||
#else
|
}
|
||||||
if (fontMetrics.width(label) > yAxisWidth)
|
|
||||||
yAxisWidth = fontMetrics.width(label);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const QString &label : speedLabels)
|
for (const QString &label : speedLabels)
|
||||||
@@ -350,12 +343,16 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
|||||||
painter.setRenderHints(QPainter::Antialiasing);
|
painter.setRenderHints(QPainter::Antialiasing);
|
||||||
|
|
||||||
// draw graphs
|
// draw graphs
|
||||||
rect.adjust(3, 0, 0, 0); // Need, else graphs cross left gridline
|
// averager is duration based, it may go little above the maxDuration
|
||||||
|
painter.setClipping(true);
|
||||||
|
painter.setClipRect(rect);
|
||||||
|
|
||||||
const double yMultiplier = (niceScale.arg == 0.0) ? 0.0 : (static_cast<double>(rect.height()) / niceScale.sizeInBytes());
|
const DataCircularBuffer &queue = currentData();
|
||||||
const double xTickSize = static_cast<double>(rect.width()) / (m_viewablePointsCount - 1);
|
|
||||||
|
|
||||||
boost::circular_buffer<PointData> &queue = getCurrentData();
|
// last point will be drawn at x=0, so we don't need it in the calculation of xTickSize
|
||||||
|
const milliseconds lastDuration {queue.empty() ? 0ms : queue.back().duration};
|
||||||
|
const double xTickSize = static_cast<double>(rect.width()) / (m_currentMaxDuration - lastDuration).count();
|
||||||
|
const double yMultiplier = (niceScale.arg == 0) ? 0 : (static_cast<double>(rect.height()) / niceScale.sizeInBytes());
|
||||||
|
|
||||||
for (int id = UP; id < NB_GRAPHS; ++id)
|
for (int id = UP; id < NB_GRAPHS; ++id)
|
||||||
{
|
{
|
||||||
@@ -363,18 +360,23 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
QVector<QPoint> points;
|
QVector<QPoint> points;
|
||||||
for (int i = static_cast<int>(queue.size()) - 1, j = 0; (i >= 0) && (j < m_viewablePointsCount); --i, ++j)
|
milliseconds duration {0ms};
|
||||||
|
|
||||||
|
for (int i = static_cast<int>(queue.size()) - 1; i >= 0; --i)
|
||||||
{
|
{
|
||||||
|
const int newX = rect.right() - (duration.count() * xTickSize);
|
||||||
int newX = rect.right() - j * xTickSize;
|
const int newY = rect.bottom() - (queue[i].data[id] * yMultiplier);
|
||||||
int newY = rect.bottom() - queue[i].y[id] * yMultiplier;
|
|
||||||
|
|
||||||
points.push_back(QPoint(newX, newY));
|
points.push_back(QPoint(newX, newY));
|
||||||
|
|
||||||
|
duration += queue[i].duration;
|
||||||
|
if (duration >= m_currentMaxDuration)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
painter.setPen(m_properties[static_cast<GraphID>(id)].pen);
|
painter.setPen(m_properties[static_cast<GraphID>(id)].pen);
|
||||||
painter.drawPolyline(points.data(), points.size());
|
painter.drawPolyline(points.data(), points.size());
|
||||||
}
|
}
|
||||||
|
painter.setClipping(false);
|
||||||
|
|
||||||
// draw legend
|
// draw legend
|
||||||
QPoint legendTopLeft(rect.left() + 4, fullRect.top() + 4);
|
QPoint legendTopLeft(rect.left() + 4, fullRect.top() + 4);
|
||||||
@@ -386,13 +388,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
|||||||
if (!property.enable)
|
if (!property.enable)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
|
||||||
if (fontMetrics.horizontalAdvance(property.name) > legendWidth)
|
if (fontMetrics.horizontalAdvance(property.name) > legendWidth)
|
||||||
legendWidth = fontMetrics.horizontalAdvance(property.name);
|
legendWidth = fontMetrics.horizontalAdvance(property.name);
|
||||||
#else
|
|
||||||
if (fontMetrics.width(property.name) > legendWidth)
|
|
||||||
legendWidth = fontMetrics.width(property.name);
|
|
||||||
#endif
|
|
||||||
legendHeight += 1.5 * fontMetrics.height();
|
legendHeight += 1.5 * fontMetrics.height();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,11 +404,7 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
|||||||
if (!property.enable)
|
if (!property.enable)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
|
||||||
int nameSize = fontMetrics.horizontalAdvance(property.name);
|
int nameSize = fontMetrics.horizontalAdvance(property.name);
|
||||||
#else
|
|
||||||
int nameSize = fontMetrics.width(property.name);
|
|
||||||
#endif
|
|
||||||
double indent = 1.5 * (i++) * fontMetrics.height();
|
double indent = 1.5 * (i++) * fontMetrics.height();
|
||||||
|
|
||||||
painter.setPen(property.pen);
|
painter.setPen(property.pen);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Bittorrent Client using Qt and libtorrent.
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2021 Prince Gupta <guptaprince8832@gmail.com>
|
||||||
* Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru>
|
* Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or
|
* This program is free software; you can redistribute it and/or
|
||||||
@@ -28,15 +29,22 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
#ifndef Q_MOC_RUN
|
#ifndef Q_MOC_RUN
|
||||||
#include <boost/circular_buffer.hpp>
|
#include <boost/circular_buffer.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QGraphicsView>
|
#include <QGraphicsView>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
|
|
||||||
class QPen;
|
class QPen;
|
||||||
|
|
||||||
|
using std::chrono::milliseconds;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
class SpeedPlotView final : public QGraphicsView
|
class SpeedPlotView final : public QGraphicsView
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -63,42 +71,50 @@ public:
|
|||||||
MIN1 = 0,
|
MIN1 = 0,
|
||||||
MIN5,
|
MIN5,
|
||||||
MIN30,
|
MIN30,
|
||||||
|
HOUR3,
|
||||||
HOUR6,
|
HOUR6,
|
||||||
HOUR12,
|
HOUR12,
|
||||||
HOUR24
|
HOUR24
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PointData
|
using SampleData = std::array<quint64, NB_GRAPHS>;
|
||||||
{
|
|
||||||
qint64 x;
|
|
||||||
quint64 y[NB_GRAPHS];
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit SpeedPlotView(QWidget *parent = nullptr);
|
explicit SpeedPlotView(QWidget *parent = nullptr);
|
||||||
|
|
||||||
void setGraphEnable(GraphID id, bool enable);
|
void setGraphEnable(GraphID id, bool enable);
|
||||||
void setPeriod(TimePeriod period);
|
void setPeriod(TimePeriod period);
|
||||||
|
|
||||||
void pushPoint(const PointData &point);
|
void pushPoint(const SampleData &point);
|
||||||
|
|
||||||
void replot();
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *event) override;
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct Sample
|
||||||
|
{
|
||||||
|
milliseconds duration;
|
||||||
|
SampleData data;
|
||||||
|
};
|
||||||
|
|
||||||
|
using DataCircularBuffer = boost::circular_buffer<Sample>;
|
||||||
|
|
||||||
class Averager
|
class Averager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Averager(int divider, boost::circular_buffer<PointData> &sink);
|
Averager(milliseconds duration, milliseconds resolution);
|
||||||
void push(const PointData &pointData);
|
|
||||||
bool isReady() const;
|
bool push(const SampleData &sampleData); // returns true if there is new data to display
|
||||||
|
|
||||||
|
const DataCircularBuffer &data() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const int m_divider;
|
const milliseconds m_resolution;
|
||||||
boost::circular_buffer<PointData> &m_sink;
|
const milliseconds m_maxDuration;
|
||||||
int m_counter;
|
milliseconds m_currentDuration {0ms};
|
||||||
PointData m_accumulator;
|
int m_counter = 0;
|
||||||
|
SampleData m_accumulator {};
|
||||||
|
DataCircularBuffer m_sink {};
|
||||||
|
QElapsedTimer m_lastSampleTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GraphProperties
|
struct GraphProperties
|
||||||
@@ -111,22 +127,16 @@ private:
|
|||||||
bool enable;
|
bool enable;
|
||||||
};
|
};
|
||||||
|
|
||||||
quint64 maxYValue();
|
quint64 maxYValue() const;
|
||||||
boost::circular_buffer<PointData> &getCurrentData();
|
const DataCircularBuffer ¤tData() const;
|
||||||
|
|
||||||
boost::circular_buffer<PointData> m_data5Min;
|
Averager m_averager5Min {5min, 1s};
|
||||||
boost::circular_buffer<PointData> m_data30Min;
|
Averager m_averager30Min {30min, 6s};
|
||||||
boost::circular_buffer<PointData> m_data6Hour;
|
Averager m_averager6Hour {6h, 36s};
|
||||||
boost::circular_buffer<PointData> m_data12Hour;
|
Averager m_averager12Hour {12h, 72s};
|
||||||
boost::circular_buffer<PointData> m_data24Hour;
|
Averager m_averager24Hour {24h, 144s};
|
||||||
boost::circular_buffer<PointData> *m_currentData;
|
Averager *m_currentAverager {&m_averager5Min};
|
||||||
Averager m_averager30Min;
|
|
||||||
Averager m_averager6Hour;
|
|
||||||
Averager m_averager12Hour;
|
|
||||||
Averager m_averager24Hour;
|
|
||||||
|
|
||||||
QMap<GraphID, GraphProperties> m_properties;
|
QMap<GraphID, GraphProperties> m_properties;
|
||||||
|
milliseconds m_currentMaxDuration;
|
||||||
TimePeriod m_period;
|
|
||||||
int m_viewablePointsCount;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ SpeedWidget::SpeedWidget(PropertiesWidget *parent)
|
|||||||
m_periodCombobox->addItem(tr("1 Minute"));
|
m_periodCombobox->addItem(tr("1 Minute"));
|
||||||
m_periodCombobox->addItem(tr("5 Minutes"));
|
m_periodCombobox->addItem(tr("5 Minutes"));
|
||||||
m_periodCombobox->addItem(tr("30 Minutes"));
|
m_periodCombobox->addItem(tr("30 Minutes"));
|
||||||
|
m_periodCombobox->addItem(tr("3 Hours"));
|
||||||
m_periodCombobox->addItem(tr("6 Hours"));
|
m_periodCombobox->addItem(tr("6 Hours"));
|
||||||
m_periodCombobox->addItem(tr("12 Hours"));
|
m_periodCombobox->addItem(tr("12 Hours"));
|
||||||
m_periodCombobox->addItem(tr("24 Hours"));
|
m_periodCombobox->addItem(tr("24 Hours"));
|
||||||
@@ -109,16 +110,13 @@ SpeedWidget::SpeedWidget(PropertiesWidget *parent)
|
|||||||
m_hlayout->addWidget(m_graphsButton);
|
m_hlayout->addWidget(m_graphsButton);
|
||||||
|
|
||||||
m_plot = new SpeedPlotView(this);
|
m_plot = new SpeedPlotView(this);
|
||||||
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::statsUpdated, this, &SpeedWidget::update);
|
||||||
|
|
||||||
m_layout->addLayout(m_hlayout);
|
m_layout->addLayout(m_hlayout);
|
||||||
m_layout->addWidget(m_plot);
|
m_layout->addWidget(m_plot);
|
||||||
|
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
QTimer *localUpdateTimer = new QTimer(this);
|
|
||||||
connect(localUpdateTimer, &QTimer::timeout, this, &SpeedWidget::update);
|
|
||||||
localUpdateTimer->start(1000);
|
|
||||||
|
|
||||||
m_plot->show();
|
m_plot->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,21 +131,19 @@ void SpeedWidget::update()
|
|||||||
{
|
{
|
||||||
const BitTorrent::SessionStatus &btStatus = BitTorrent::Session::instance()->status();
|
const BitTorrent::SessionStatus &btStatus = BitTorrent::Session::instance()->status();
|
||||||
|
|
||||||
SpeedPlotView::PointData point;
|
SpeedPlotView::SampleData sampleData;
|
||||||
point.x = QDateTime::currentMSecsSinceEpoch() / 1000;
|
sampleData[SpeedPlotView::UP] = btStatus.uploadRate;
|
||||||
point.y[SpeedPlotView::UP] = btStatus.uploadRate;
|
sampleData[SpeedPlotView::DOWN] = btStatus.downloadRate;
|
||||||
point.y[SpeedPlotView::DOWN] = btStatus.downloadRate;
|
sampleData[SpeedPlotView::PAYLOAD_UP] = btStatus.payloadUploadRate;
|
||||||
point.y[SpeedPlotView::PAYLOAD_UP] = btStatus.payloadUploadRate;
|
sampleData[SpeedPlotView::PAYLOAD_DOWN] = btStatus.payloadDownloadRate;
|
||||||
point.y[SpeedPlotView::PAYLOAD_DOWN] = btStatus.payloadDownloadRate;
|
sampleData[SpeedPlotView::OVERHEAD_UP] = btStatus.ipOverheadUploadRate;
|
||||||
point.y[SpeedPlotView::OVERHEAD_UP] = btStatus.ipOverheadUploadRate;
|
sampleData[SpeedPlotView::OVERHEAD_DOWN] = btStatus.ipOverheadDownloadRate;
|
||||||
point.y[SpeedPlotView::OVERHEAD_DOWN] = btStatus.ipOverheadDownloadRate;
|
sampleData[SpeedPlotView::DHT_UP] = btStatus.dhtUploadRate;
|
||||||
point.y[SpeedPlotView::DHT_UP] = btStatus.dhtUploadRate;
|
sampleData[SpeedPlotView::DHT_DOWN] = btStatus.dhtDownloadRate;
|
||||||
point.y[SpeedPlotView::DHT_DOWN] = btStatus.dhtDownloadRate;
|
sampleData[SpeedPlotView::TRACKER_UP] = btStatus.trackerUploadRate;
|
||||||
point.y[SpeedPlotView::TRACKER_UP] = btStatus.trackerUploadRate;
|
sampleData[SpeedPlotView::TRACKER_DOWN] = btStatus.trackerDownloadRate;
|
||||||
point.y[SpeedPlotView::TRACKER_DOWN] = btStatus.trackerDownloadRate;
|
|
||||||
|
|
||||||
m_plot->pushPoint(point);
|
m_plot->pushPoint(sampleData);
|
||||||
m_plot->replot();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpeedWidget::onPeriodChange(int period)
|
void SpeedWidget::onPeriodChange(int period)
|
||||||
|
|||||||
@@ -205,9 +205,7 @@ void TrackerListWidget::moveSelectionUp()
|
|||||||
for (int i = NB_STICKY_ITEM; i < topLevelItemCount(); ++i)
|
for (int i = NB_STICKY_ITEM; i < topLevelItemCount(); ++i)
|
||||||
{
|
{
|
||||||
const QString trackerURL = topLevelItem(i)->data(COL_URL, Qt::DisplayRole).toString();
|
const QString trackerURL = topLevelItem(i)->data(COL_URL, Qt::DisplayRole).toString();
|
||||||
BitTorrent::TrackerEntry e(trackerURL);
|
trackers.append({trackerURL, (i - NB_STICKY_ITEM)});
|
||||||
e.setTier(i - NB_STICKY_ITEM);
|
|
||||||
trackers.append(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent->replaceTrackers(trackers);
|
torrent->replaceTrackers(trackers);
|
||||||
@@ -251,9 +249,7 @@ void TrackerListWidget::moveSelectionDown()
|
|||||||
for (int i = NB_STICKY_ITEM; i < topLevelItemCount(); ++i)
|
for (int i = NB_STICKY_ITEM; i < topLevelItemCount(); ++i)
|
||||||
{
|
{
|
||||||
const QString trackerURL = topLevelItem(i)->data(COL_URL, Qt::DisplayRole).toString();
|
const QString trackerURL = topLevelItem(i)->data(COL_URL, Qt::DisplayRole).toString();
|
||||||
BitTorrent::TrackerEntry e(trackerURL);
|
trackers.append({trackerURL, (i - NB_STICKY_ITEM)});
|
||||||
e.setTier(i - NB_STICKY_ITEM);
|
|
||||||
trackers.append(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent->replaceTrackers(trackers);
|
torrent->replaceTrackers(trackers);
|
||||||
@@ -372,7 +368,7 @@ void TrackerListWidget::loadTrackers()
|
|||||||
|
|
||||||
for (const BitTorrent::TrackerEntry &entry : asConst(torrent->trackers()))
|
for (const BitTorrent::TrackerEntry &entry : asConst(torrent->trackers()))
|
||||||
{
|
{
|
||||||
const QString trackerURL = entry.url();
|
const QString trackerURL = entry.url;
|
||||||
|
|
||||||
QTreeWidgetItem *item = m_trackerItems.value(trackerURL, nullptr);
|
QTreeWidgetItem *item = m_trackerItems.value(trackerURL, nullptr);
|
||||||
if (!item)
|
if (!item)
|
||||||
@@ -387,11 +383,11 @@ void TrackerListWidget::loadTrackers()
|
|||||||
oldTrackerURLs.removeOne(trackerURL);
|
oldTrackerURLs.removeOne(trackerURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
item->setText(COL_TIER, QString::number(entry.tier()));
|
item->setText(COL_TIER, QString::number(entry.tier));
|
||||||
|
|
||||||
const BitTorrent::TrackerInfo data = trackerData.value(trackerURL);
|
const BitTorrent::TrackerInfo data = trackerData.value(trackerURL);
|
||||||
|
|
||||||
switch (entry.status())
|
switch (entry.status)
|
||||||
{
|
{
|
||||||
case BitTorrent::TrackerEntry::Working:
|
case BitTorrent::TrackerEntry::Working:
|
||||||
item->setText(COL_STATUS, tr("Working"));
|
item->setText(COL_STATUS, tr("Working"));
|
||||||
@@ -412,14 +408,14 @@ void TrackerListWidget::loadTrackers()
|
|||||||
}
|
}
|
||||||
|
|
||||||
item->setText(COL_PEERS, QString::number(data.numPeers));
|
item->setText(COL_PEERS, QString::number(data.numPeers));
|
||||||
item->setText(COL_SEEDS, ((entry.numSeeds() > -1)
|
item->setText(COL_SEEDS, ((entry.numSeeds > -1)
|
||||||
? QString::number(entry.numSeeds())
|
? QString::number(entry.numSeeds)
|
||||||
: tr("N/A")));
|
: tr("N/A")));
|
||||||
item->setText(COL_LEECHES, ((entry.numLeeches() > -1)
|
item->setText(COL_LEECHES, ((entry.numLeeches > -1)
|
||||||
? QString::number(entry.numLeeches())
|
? QString::number(entry.numLeeches)
|
||||||
: tr("N/A")));
|
: tr("N/A")));
|
||||||
item->setText(COL_DOWNLOADED, ((entry.numDownloaded() > -1)
|
item->setText(COL_DOWNLOADED, ((entry.numDownloaded > -1)
|
||||||
? QString::number(entry.numDownloaded())
|
? QString::number(entry.numDownloaded)
|
||||||
: tr("N/A")));
|
: tr("N/A")));
|
||||||
|
|
||||||
const Qt::Alignment alignment = (Qt::AlignRight | Qt::AlignVCenter);
|
const Qt::Alignment alignment = (Qt::AlignRight | Qt::AlignVCenter);
|
||||||
@@ -443,7 +439,7 @@ void TrackerListWidget::askForTrackers()
|
|||||||
|
|
||||||
QVector<BitTorrent::TrackerEntry> trackers;
|
QVector<BitTorrent::TrackerEntry> trackers;
|
||||||
for (const QString &tracker : asConst(TrackersAdditionDialog::askForTrackers(this, torrent)))
|
for (const QString &tracker : asConst(TrackersAdditionDialog::askForTrackers(this, torrent)))
|
||||||
trackers << tracker;
|
trackers.append({tracker});
|
||||||
|
|
||||||
torrent->addTrackers(trackers);
|
torrent->addTrackers(trackers);
|
||||||
}
|
}
|
||||||
@@ -492,7 +488,7 @@ void TrackerListWidget::deleteSelectedTrackers()
|
|||||||
|
|
||||||
for (const BitTorrent::TrackerEntry &entry : trackers)
|
for (const BitTorrent::TrackerEntry &entry : trackers)
|
||||||
{
|
{
|
||||||
if (!urlsToRemove.contains(entry.url()))
|
if (!urlsToRemove.contains(entry.url))
|
||||||
remainingTrackers.push_back(entry);
|
remainingTrackers.push_back(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,18 +525,16 @@ void TrackerListWidget::editSelectedTracker()
|
|||||||
bool match = false;
|
bool match = false;
|
||||||
for (BitTorrent::TrackerEntry &entry : trackers)
|
for (BitTorrent::TrackerEntry &entry : trackers)
|
||||||
{
|
{
|
||||||
if (newTrackerURL == QUrl(entry.url()))
|
if (newTrackerURL == QUrl(entry.url))
|
||||||
{
|
{
|
||||||
QMessageBox::warning(this, tr("Tracker editing failed"), tr("The tracker URL already exists."));
|
QMessageBox::warning(this, tr("Tracker editing failed"), tr("The tracker URL already exists."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!match && (trackerURL == QUrl(entry.url())))
|
if (!match && (trackerURL == QUrl(entry.url)))
|
||||||
{
|
{
|
||||||
match = true;
|
match = true;
|
||||||
BitTorrent::TrackerEntry newEntry(newTrackerURL.toString());
|
entry.url = newTrackerURL.toString();
|
||||||
newEntry.setTier(entry.tier());
|
|
||||||
entry = newEntry;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +566,7 @@ void TrackerListWidget::reannounceSelected()
|
|||||||
// Trackers case
|
// Trackers case
|
||||||
for (int i = 0; i < trackers.size(); ++i)
|
for (int i = 0; i < trackers.size(); ++i)
|
||||||
{
|
{
|
||||||
if (item->text(COL_URL) == trackers[i].url())
|
if (item->text(COL_URL) == trackers[i].url)
|
||||||
{
|
{
|
||||||
torrent->forceReannounce(i);
|
torrent->forceReannounce(i);
|
||||||
break;
|
break;
|
||||||
@@ -592,30 +586,26 @@ void TrackerListWidget::showTrackerListMenu(const QPoint &)
|
|||||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
// Add actions
|
// Add actions
|
||||||
const QAction *addAct = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add a new tracker..."));
|
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add a new tracker...")
|
||||||
connect(addAct, &QAction::triggered, this, &TrackerListWidget::askForTrackers);
|
, this, &TrackerListWidget::askForTrackers);
|
||||||
|
|
||||||
if (!getSelectedTrackerItems().isEmpty())
|
if (!getSelectedTrackerItems().isEmpty())
|
||||||
{
|
{
|
||||||
const QAction *editAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"),tr("Edit tracker URL..."));
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"),tr("Edit tracker URL...")
|
||||||
connect(editAct, &QAction::triggered, this, &TrackerListWidget::editSelectedTracker);
|
, this, &TrackerListWidget::editSelectedTracker);
|
||||||
|
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove tracker")
|
||||||
const QAction *delAct = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove tracker"));
|
, this, &TrackerListWidget::deleteSelectedTrackers);
|
||||||
connect(delAct, &QAction::triggered, this, &TrackerListWidget::deleteSelectedTrackers);
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy tracker URL")
|
||||||
|
, this, &TrackerListWidget::copyTrackerUrl);
|
||||||
const QAction *copyAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy tracker URL"));
|
|
||||||
connect(copyAct, &QAction::triggered, this, &TrackerListWidget::copyTrackerUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!torrent->isPaused())
|
if (!torrent->isPaused())
|
||||||
{
|
{
|
||||||
const QAction *reannounceSelAct = menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to selected trackers"));
|
menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to selected trackers")
|
||||||
connect(reannounceSelAct, &QAction::triggered, this, &TrackerListWidget::reannounceSelected);
|
, this, &TrackerListWidget::reannounceSelected);
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to all trackers")
|
||||||
const QAction *reannounceAllAct = menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to all trackers"));
|
, this, [this]()
|
||||||
connect(reannounceAllAct, &QAction::triggered, this, [this]()
|
|
||||||
{
|
{
|
||||||
BitTorrent::Torrent *h = m_properties->getCurrentTorrent();
|
BitTorrent::Torrent *h = m_properties->getCurrentTorrent();
|
||||||
h->forceReannounce();
|
h->forceReannounce();
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ void TrackersAdditionDialog::torrentListDownloadFinished(const Net::DownloadResu
|
|||||||
existingTrackers.reserve(trackersFromUser.size());
|
existingTrackers.reserve(trackersFromUser.size());
|
||||||
for (const QString &userURL : trackersFromUser)
|
for (const QString &userURL : trackersFromUser)
|
||||||
{
|
{
|
||||||
const BitTorrent::TrackerEntry userTracker(userURL);
|
const BitTorrent::TrackerEntry userTracker {userURL};
|
||||||
if (!existingTrackers.contains(userTracker))
|
if (!existingTrackers.contains(userTracker))
|
||||||
existingTrackers << userTracker;
|
existingTrackers << userTracker;
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ void TrackersAdditionDialog::torrentListDownloadFinished(const Net::DownloadResu
|
|||||||
const QString line = buffer.readLine().trimmed();
|
const QString line = buffer.readLine().trimmed();
|
||||||
if (line.isEmpty()) continue;
|
if (line.isEmpty()) continue;
|
||||||
|
|
||||||
BitTorrent::TrackerEntry newTracker(line);
|
BitTorrent::TrackerEntry newTracker {line};
|
||||||
if (!existingTrackers.contains(newTracker))
|
if (!existingTrackers.contains(newTracker))
|
||||||
{
|
{
|
||||||
m_ui->textEditTrackersList->insertPlainText(line + '\n');
|
m_ui->textEditTrackersList->insertPlainText(line + '\n');
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
#include "base/utils/fs.h"
|
#include "base/utils/fs.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
#include "gui/autoexpandabledialog.h"
|
#include "gui/autoexpandabledialog.h"
|
||||||
|
#include "gui/torrentcategorydialog.h"
|
||||||
#include "gui/uithememanager.h"
|
#include "gui/uithememanager.h"
|
||||||
#include "gui/utils.h"
|
#include "gui/utils.h"
|
||||||
#include "ui_automatedrssdownloader.h"
|
#include "ui_automatedrssdownloader.h"
|
||||||
@@ -68,6 +69,7 @@ AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent)
|
|||||||
// Icons
|
// Icons
|
||||||
m_ui->removeRuleBtn->setIcon(UIThemeManager::instance()->getIcon("list-remove"));
|
m_ui->removeRuleBtn->setIcon(UIThemeManager::instance()->getIcon("list-remove"));
|
||||||
m_ui->addRuleBtn->setIcon(UIThemeManager::instance()->getIcon("list-add"));
|
m_ui->addRuleBtn->setIcon(UIThemeManager::instance()->getIcon("list-add"));
|
||||||
|
m_ui->addCategoryBtn->setIcon(UIThemeManager::instance()->getIcon("list-add"));
|
||||||
|
|
||||||
// Ui Settings
|
// Ui Settings
|
||||||
m_ui->listRules->setSortingEnabled(true);
|
m_ui->listRules->setSortingEnabled(true);
|
||||||
@@ -185,7 +187,7 @@ void AutomatedRssDownloader::loadFeedList()
|
|||||||
{
|
{
|
||||||
QListWidgetItem *item = new QListWidgetItem(feed->name(), m_ui->listFeeds);
|
QListWidgetItem *item = new QListWidgetItem(feed->name(), m_ui->listFeeds);
|
||||||
item->setData(Qt::UserRole, feed->url());
|
item->setData(Qt::UserRole, feed->url());
|
||||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsTristate);
|
item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateFeedList();
|
updateFeedList();
|
||||||
@@ -405,6 +407,17 @@ void AutomatedRssDownloader::on_removeRuleBtn_clicked()
|
|||||||
RSS::AutoDownloader::instance()->removeRule(item->text());
|
RSS::AutoDownloader::instance()->removeRule(item->text());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AutomatedRssDownloader::on_addCategoryBtn_clicked()
|
||||||
|
{
|
||||||
|
const QString newCategoryName = TorrentCategoryDialog::createCategory(this);
|
||||||
|
|
||||||
|
if (!newCategoryName.isEmpty())
|
||||||
|
{
|
||||||
|
m_ui->comboCategory->addItem(newCategoryName);
|
||||||
|
m_ui->comboCategory->setCurrentText(newCategoryName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AutomatedRssDownloader::on_exportBtn_clicked()
|
void AutomatedRssDownloader::on_exportBtn_clicked()
|
||||||
{
|
{
|
||||||
if (RSS::AutoDownloader::instance()->rules().isEmpty())
|
if (RSS::AutoDownloader::instance()->rules().isEmpty())
|
||||||
@@ -490,8 +503,8 @@ void AutomatedRssDownloader::displayRulesListMenu()
|
|||||||
QMenu *menu = new QMenu(this);
|
QMenu *menu = new QMenu(this);
|
||||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
const QAction *addAct = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add new rule..."));
|
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add new rule...")
|
||||||
connect(addAct, &QAction::triggered, this, &AutomatedRssDownloader::on_addRuleBtn_clicked);
|
, this, &AutomatedRssDownloader::on_addRuleBtn_clicked);
|
||||||
|
|
||||||
const QList<QListWidgetItem *> selection = m_ui->listRules->selectedItems();
|
const QList<QListWidgetItem *> selection = m_ui->listRules->selectedItems();
|
||||||
|
|
||||||
@@ -499,24 +512,21 @@ void AutomatedRssDownloader::displayRulesListMenu()
|
|||||||
{
|
{
|
||||||
if (selection.count() == 1)
|
if (selection.count() == 1)
|
||||||
{
|
{
|
||||||
const QAction *delAct = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete rule"));
|
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete rule")
|
||||||
connect(delAct, &QAction::triggered, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
|
, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename rule...")
|
||||||
const QAction *renameAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename rule..."));
|
, this, &AutomatedRssDownloader::renameSelectedRule);
|
||||||
connect(renameAct, &QAction::triggered, this, &AutomatedRssDownloader::renameSelectedRule);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const QAction *delAct = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete selected rules"));
|
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete selected rules")
|
||||||
connect(delAct, &QAction::triggered, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
|
, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear downloaded episodes...")
|
||||||
const QAction *clearAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear downloaded episodes..."));
|
, this, &AutomatedRssDownloader::clearSelectedRuleDownloadedEpisodeList);
|
||||||
connect(clearAct, &QAction::triggered, this, &AutomatedRssDownloader::clearSelectedRuleDownloadedEpisodeList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
menu->popup(QCursor::pos());
|
menu->popup(QCursor::pos());
|
||||||
@@ -709,10 +719,14 @@ void AutomatedRssDownloader::updateMustLineValidity()
|
|||||||
{
|
{
|
||||||
QStringList tokens;
|
QStringList tokens;
|
||||||
if (isRegex)
|
if (isRegex)
|
||||||
|
{
|
||||||
tokens << text;
|
tokens << text;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
for (const QString &token : asConst(text.split('|')))
|
for (const QString &token : asConst(text.split('|')))
|
||||||
tokens << Utils::String::wildcardToRegex(token);
|
tokens << Utils::String::wildcardToRegexPattern(token);
|
||||||
|
}
|
||||||
|
|
||||||
for (const QString &token : asConst(tokens))
|
for (const QString &token : asConst(tokens))
|
||||||
{
|
{
|
||||||
@@ -752,10 +766,14 @@ void AutomatedRssDownloader::updateMustNotLineValidity()
|
|||||||
{
|
{
|
||||||
QStringList tokens;
|
QStringList tokens;
|
||||||
if (isRegex)
|
if (isRegex)
|
||||||
|
{
|
||||||
tokens << text;
|
tokens << text;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
for (const QString &token : asConst(text.split('|')))
|
for (const QString &token : asConst(text.split('|')))
|
||||||
tokens << Utils::String::wildcardToRegex(token);
|
tokens << Utils::String::wildcardToRegexPattern(token);
|
||||||
|
}
|
||||||
|
|
||||||
for (const QString &token : asConst(tokens))
|
for (const QString &token : asConst(tokens))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ public:
|
|||||||
private slots:
|
private slots:
|
||||||
void on_addRuleBtn_clicked();
|
void on_addRuleBtn_clicked();
|
||||||
void on_removeRuleBtn_clicked();
|
void on_removeRuleBtn_clicked();
|
||||||
|
void on_addCategoryBtn_clicked();
|
||||||
void on_exportBtn_clicked();
|
void on_exportBtn_clicked();
|
||||||
void on_importBtn_clicked();
|
void on_importBtn_clicked();
|
||||||
|
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Assign Category:</string>
|
<string>Category:</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -220,6 +220,9 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QToolButton" name="addCategoryBtn" />
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
|||||||
@@ -365,9 +365,9 @@ void SearchJobWidget::fillFilterComboBoxes()
|
|||||||
|
|
||||||
void SearchJobWidget::filterSearchResults(const QString &name)
|
void SearchJobWidget::filterSearchResults(const QString &name)
|
||||||
{
|
{
|
||||||
const QRegExp::PatternSyntax patternSyntax = Preferences::instance()->getRegexAsFilteringPatternForSearchJob()
|
const QString pattern = (Preferences::instance()->getRegexAsFilteringPatternForSearchJob()
|
||||||
? QRegExp::RegExp : QRegExp::WildcardUnix;
|
? name : Utils::String::wildcardToRegexPattern(name));
|
||||||
m_proxyModel->setFilterRegExp(QRegExp(name, Qt::CaseInsensitive, patternSyntax));
|
m_proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
||||||
updateResultsCount();
|
updateResultsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,31 +393,21 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
|
|||||||
auto *menu = new QMenu(this);
|
auto *menu = new QMenu(this);
|
||||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
const QAction *downloadAction = menu->addAction(
|
menu->addAction(UIThemeManager::instance()->getIcon("download"), tr("Download")
|
||||||
UIThemeManager::instance()->getIcon("download"), tr("Download"));
|
, this, &SearchJobWidget::downloadTorrents);
|
||||||
connect(downloadAction, &QAction::triggered, this, &SearchJobWidget::downloadTorrents);
|
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
menu->addAction(UIThemeManager::instance()->getIcon("application-x-mswinurl"), tr("Open description page")
|
||||||
const QAction *openDescriptionAction = menu->addAction(
|
, this, &SearchJobWidget::openTorrentPages);
|
||||||
UIThemeManager::instance()->getIcon("application-x-mswinurl"), tr("Open description page"));
|
|
||||||
connect(openDescriptionAction, &QAction::triggered, this, &SearchJobWidget::openTorrentPages);
|
|
||||||
|
|
||||||
QMenu *copySubMenu = menu->addMenu(
|
QMenu *copySubMenu = menu->addMenu(
|
||||||
UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy"));
|
UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy"));
|
||||||
|
|
||||||
const QAction *copyNamesAction = copySubMenu->addAction(
|
copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Name")
|
||||||
UIThemeManager::instance()->getIcon("edit-copy"), tr("Name"));
|
, this, &SearchJobWidget::copyTorrentNames);
|
||||||
connect(copyNamesAction, &QAction::triggered, this, &SearchJobWidget::copyTorrentNames);
|
copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Download link")
|
||||||
|
|
||||||
const QAction *copyDownloadLinkAction = copySubMenu->addAction(
|
|
||||||
UIThemeManager::instance()->getIcon("edit-copy"), tr("Download link"));
|
|
||||||
connect(copyDownloadLinkAction, &QAction::triggered
|
|
||||||
, this, &SearchJobWidget::copyTorrentDownloadLinks);
|
, this, &SearchJobWidget::copyTorrentDownloadLinks);
|
||||||
|
copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Description page URL")
|
||||||
const QAction *copyDescriptionAction = copySubMenu->addAction(
|
, this, &SearchJobWidget::copyTorrentURLs);
|
||||||
UIThemeManager::instance()->getIcon("edit-copy"), tr("Description page URL"));
|
|
||||||
connect(copyDescriptionAction, &QAction::triggered, this, &SearchJobWidget::copyTorrentURLs);
|
|
||||||
|
|
||||||
menu->popup(event->globalPos());
|
menu->popup(event->globalPos());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
|
|
||||||
#include "tagfilterwidget.h"
|
#include "tagfilterwidget.h"
|
||||||
|
|
||||||
#include <QAction>
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
||||||
@@ -108,44 +107,25 @@ void TagFilterWidget::showMenu(QPoint)
|
|||||||
QMenu *menu = new QMenu(this);
|
QMenu *menu = new QMenu(this);
|
||||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
const QAction *addAct = menu->addAction(
|
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add tag...")
|
||||||
UIThemeManager::instance()->getIcon("list-add")
|
, this, &TagFilterWidget::addTag);
|
||||||
, tr("Add tag..."));
|
|
||||||
connect(addAct, &QAction::triggered, this, &TagFilterWidget::addTag);
|
|
||||||
|
|
||||||
const auto selectedRows = selectionModel()->selectedRows();
|
const auto selectedRows = selectionModel()->selectedRows();
|
||||||
if (!selectedRows.empty() && !TagFilterModel::isSpecialItem(selectedRows.first()))
|
if (!selectedRows.empty() && !TagFilterModel::isSpecialItem(selectedRows.first()))
|
||||||
{
|
{
|
||||||
const QAction *removeAct = menu->addAction(
|
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove tag")
|
||||||
UIThemeManager::instance()->getIcon("list-remove")
|
, this, &TagFilterWidget::removeTag);
|
||||||
, tr("Remove tag"));
|
|
||||||
connect(removeAct, &QAction::triggered, this, &TagFilterWidget::removeTag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QAction *removeUnusedAct = menu->addAction(
|
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove unused tags")
|
||||||
UIThemeManager::instance()->getIcon("list-remove")
|
, this, &TagFilterWidget::removeUnusedTags);
|
||||||
, tr("Remove unused tags"));
|
|
||||||
connect(removeUnusedAct, &QAction::triggered, this, &TagFilterWidget::removeUnusedTags);
|
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
menu->addAction(UIThemeManager::instance()->getIcon("media-playback-start"), tr("Resume torrents")
|
||||||
const QAction *startAct = menu->addAction(
|
|
||||||
UIThemeManager::instance()->getIcon("media-playback-start")
|
|
||||||
, tr("Resume torrents"));
|
|
||||||
connect(startAct, &QAction::triggered
|
|
||||||
, this, &TagFilterWidget::actionResumeTorrentsTriggered);
|
, this, &TagFilterWidget::actionResumeTorrentsTriggered);
|
||||||
|
menu->addAction(UIThemeManager::instance()->getIcon("media-playback-pause"), tr("Pause torrents")
|
||||||
const QAction *pauseAct = menu->addAction(
|
, this, &TagFilterWidget::actionPauseTorrentsTriggered);
|
||||||
UIThemeManager::instance()->getIcon("media-playback-pause")
|
menu->addAction(UIThemeManager::instance()->getIcon("edit-delete"), tr("Delete torrents")
|
||||||
, tr("Pause torrents"));
|
, this, &TagFilterWidget::actionDeleteTorrentsTriggered);
|
||||||
connect(pauseAct, &QAction::triggered, this
|
|
||||||
, &TagFilterWidget::actionPauseTorrentsTriggered);
|
|
||||||
|
|
||||||
const QAction *deleteTorrentsAct = menu->addAction(
|
|
||||||
UIThemeManager::instance()->getIcon("edit-delete")
|
|
||||||
, tr("Delete torrents"));
|
|
||||||
connect(deleteTorrentsAct, &QAction::triggered, this
|
|
||||||
, &TagFilterWidget::actionDeleteTorrentsTriggered);
|
|
||||||
|
|
||||||
menu->popup(QCursor::pos());
|
menu->popup(QCursor::pos());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "torrentcategorydialog.h"
|
#include "torrentcategorydialog.h"
|
||||||
|
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
#include "base/bittorrent/session.h"
|
#include "base/bittorrent/session.h"
|
||||||
#include "ui_torrentcategorydialog.h"
|
#include "ui_torrentcategorydialog.h"
|
||||||
@@ -40,6 +41,13 @@ TorrentCategoryDialog::TorrentCategoryDialog(QWidget *parent)
|
|||||||
m_ui->setupUi(this);
|
m_ui->setupUi(this);
|
||||||
m_ui->comboSavePath->setMode(FileSystemPathEdit::Mode::DirectorySave);
|
m_ui->comboSavePath->setMode(FileSystemPathEdit::Mode::DirectorySave);
|
||||||
m_ui->comboSavePath->setDialogCaption(tr("Choose save path"));
|
m_ui->comboSavePath->setDialogCaption(tr("Choose save path"));
|
||||||
|
|
||||||
|
// disable save button
|
||||||
|
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||||
|
connect(m_ui->textCategoryName, &QLineEdit::textChanged, this, [this](const QString &text)
|
||||||
|
{
|
||||||
|
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TorrentCategoryDialog::~TorrentCategoryDialog()
|
TorrentCategoryDialog::~TorrentCategoryDialog()
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user