mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-24 09:18:04 -06:00
Compare commits
97 Commits
release-4.
...
release-4.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332b173e08 | ||
|
|
e921cf677a | ||
|
|
973b5a4809 | ||
|
|
688e11a911 | ||
|
|
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:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
types: [edited, opened, reopened, synchronize]
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
# Qt: 5.15.1
|
||||
# libtorrent: RC_1_2 HEAD, 1.2.11
|
||||
VCPKG_COMMIT: 133051b793486ef14e67e9d1f48c9cfe64dc127e
|
||||
VCPKG_COMMIT: e4ce66eecfd3e5cca5eac06c971921bf8e37cf88
|
||||
VCPKG_DEST_MACOS: /Users/runner/qbt_tools/vcpkg
|
||||
VCPKG_DEST_WIN: C:\qbt_tools\vcpkg
|
||||
LIBTORRENT_VERSION_TAG: v1.2.11
|
||||
LIBTORRENT_VERSION_TAG: v1.2.12
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -26,7 +23,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-18.04]
|
||||
os: [ubuntu-20.04]
|
||||
qbt_gui: ["GUI=ON", "GUI=OFF"]
|
||||
fail-fast: false
|
||||
|
||||
@@ -125,10 +122,15 @@ jobs:
|
||||
"qt5-svg:x64-windows-static-release",
|
||||
"qt5-tools:x64-windows-static-release",
|
||||
"qt5-winextras:x64-windows-static-release"
|
||||
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg.exe upgrade `
|
||||
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
|
||||
--overlay-ports=${{ github.workspace }}/vcpkg `
|
||||
--no-dry-run
|
||||
foreach($package in $packages)
|
||||
{
|
||||
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg.exe install $package `
|
||||
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
|
||||
--overlay-ports=${{ github.workspace }}/vcpkg `
|
||||
--clean-after-build
|
||||
}
|
||||
|
||||
@@ -199,13 +201,6 @@ jobs:
|
||||
Add-Content ${{ github.workspace }}/triplets_overlay/x64-osx-release.cmake `
|
||||
-Value "set(VCPKG_BUILD_TYPE release)","set(VCPKG_OSX_DEPLOYMENT_TARGET 10.15)"
|
||||
|
||||
# NOTE: Avoids a libtorrent ABI issue. See https://github.com/arvidn/libtorrent/issues/4965
|
||||
- name: force AppleClang to compile libtorrent with C++17
|
||||
run: |
|
||||
(Get-Content -path ${{ env.RUNVCPKG_VCPKG_ROOT }}/ports/libtorrent/portfile.cmake).Replace( `
|
||||
'${FEATURE_OPTIONS}', '${FEATURE_OPTIONS} -DCMAKE_CXX_STANDARD=17') `
|
||||
| Set-Content -Path ${{ env.RUNVCPKG_VCPKG_ROOT }}/ports/libtorrent/portfile.cmake
|
||||
|
||||
- name: install dependencies via vcpkg
|
||||
run: |
|
||||
$packages = `
|
||||
@@ -215,10 +210,15 @@ jobs:
|
||||
"qt5-svg:x64-osx-release",
|
||||
"qt5-tools:x64-osx-release",
|
||||
"qt5-macextras:x64-osx-release"
|
||||
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg upgrade `
|
||||
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
|
||||
--overlay-ports=${{ github.workspace }}/vcpkg `
|
||||
--no-dry-run
|
||||
foreach($package in $packages)
|
||||
{
|
||||
${{ env.RUNVCPKG_VCPKG_ROOT }}/vcpkg install $package `
|
||||
--overlay-triplets=${{ github.workspace }}/triplets_overlay `
|
||||
--overlay-ports=${{ github.workspace }}/vcpkg `
|
||||
--clean-after-build
|
||||
}
|
||||
|
||||
|
||||
7
.github/workflows/file_health.yaml
vendored
7
.github/workflows/file_health.yaml
vendored
@@ -1,11 +1,6 @@
|
||||
name: GitHub Actions file health check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '**' ]
|
||||
pull_request:
|
||||
types: [edited, opened, reopened, synchronize]
|
||||
branches: [ '**' ]
|
||||
on: [pull_request, push]
|
||||
|
||||
jobs:
|
||||
check_file_health:
|
||||
|
||||
10
.travis.yml
10
.travis.yml
@@ -106,8 +106,8 @@ install:
|
||||
|
||||
brew update > /dev/null
|
||||
brew upgrade cmake
|
||||
brew install ccache colormake boost openssl qt zlib
|
||||
brew link --force qt zlib
|
||||
brew install ccache colormake boost openssl qt@5 zlib
|
||||
brew link --force qt@5 zlib
|
||||
|
||||
if [ "$build_system" = "cmake" ]; then
|
||||
sudo ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs
|
||||
@@ -125,7 +125,7 @@ install:
|
||||
pushd "$HOME"
|
||||
git clone --single-branch --branch RC_1_2 https://github.com/arvidn/libtorrent.git
|
||||
cd libtorrent
|
||||
git checkout tags/v1.2.11
|
||||
git checkout tags/v1.2.12
|
||||
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
@@ -140,13 +140,13 @@ install:
|
||||
pushd "$HOME"
|
||||
git clone --single-branch --branch RC_2_0 https://github.com/arvidn/libtorrent.git
|
||||
cd libtorrent
|
||||
git checkout tags/v2.0.1
|
||||
git checkout tags/v2.0.2
|
||||
git submodule update --init --recursive
|
||||
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-Ddeprecated-functions=ON \
|
||||
-Ddeprecated-functions=OFF \
|
||||
-DOPENSSL_ROOT_DIR="$openssl_root_path" \
|
||||
./
|
||||
make
|
||||
|
||||
2
AUTHORS
2
AUTHORS
@@ -119,7 +119,7 @@ Translations authors:
|
||||
- German: Niels Hoffmann (zentralmaschine@users.sourceforge.net)
|
||||
- Greek: Tsvetan Bankov (emerge_life@users.sourceforge.net), Stephanos Antaris (santaris@csd.auth.gr), sledgehammer999(hammered999@gmail.com) and Γιάννης Ανθυμίδης Evropi(Transifex)
|
||||
- Hebrew: David Deutsch (d.deffo@gmail.com)
|
||||
- Hungarian: Majoros Péter (majoros.j.p@t-online.hu)
|
||||
- Hungarian: Majoros Péter
|
||||
- Italian: bovirus (bovirus@live.it) and Matteo Sechi (bu17714@gmail.com)
|
||||
- Japanese: Masato Hashimoto (cabezon.hashimoto@gmail.com)
|
||||
- Korean: Jin Woo Sin (jin828sin@users.sourceforge.net)
|
||||
|
||||
@@ -12,9 +12,9 @@ project(qBittorrent
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON)
|
||||
# version requirements - older vesions may work, but you are on your own
|
||||
set(minBoostVersion 1.65)
|
||||
set(minQtVersion 5.9.5)
|
||||
set(minQtVersion 5.12)
|
||||
set(minOpenSSLVersion 1.1.1)
|
||||
set(minLibtorrentVersion 1.2.11)
|
||||
set(minLibtorrentVersion 1.2.12)
|
||||
set(minZlibVersion 1.2.11)
|
||||
|
||||
# features (some are platform-specific)
|
||||
@@ -61,5 +61,4 @@ else()
|
||||
endif()
|
||||
|
||||
# Generate version header
|
||||
file(READ "src/base/version.h.in" versionHeader)
|
||||
file(WRITE "src/base/version.h" "${versionHeader}")
|
||||
configure_file("src/base/version.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/src/base/version.h" @ONLY)
|
||||
|
||||
48
Changelog
48
Changelog
@@ -1,3 +1,51 @@
|
||||
Wed Mar 24 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.4.1
|
||||
- BUGFIX: Correctly draw progress bar (glassez)
|
||||
- WEBUI: Fix javascript code which broke the UI (Chocobo1)
|
||||
|
||||
Tue Mar 23 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.4
|
||||
- FEATURE: Add ability to prioritize selected items by shown file order (Chocobo1)
|
||||
- FEATURE: Allow tab to escape the text box in "Edit trackers" dialog (Christoph Rackwitz)
|
||||
- FEATURE: Support sub-sorting in Transferlist (jagannatharjun)
|
||||
- FEATURE: Expose ToS setting from libtorrent (Chocobo1)
|
||||
- FEATURE: Improve tracker entries handling (glassez)
|
||||
- BUGFIX: Drop extension from generated content folder name (glassez)
|
||||
- BUGFIX: Change qBittorrent Updater window title (xavier2k6)
|
||||
- BUGFIX: Validate HTTPS Tracker Certificate by default (an0n666)
|
||||
- BUGFIX: Don't let "program update" dialog steal focus (Chocobo1)
|
||||
- BUGFIX: Disable expand on double click in TorrentContentTreeView (jagannatharjun)
|
||||
- BUGFIX: Add hyperlink to Transifex on translator list (Si Yong Kim)
|
||||
- BUGFIX: Enlarge "speed limit" icon slightly (Chocobo1)
|
||||
- BUGFIX: Don't prevent system sleep due to errored torrents (dyumin)
|
||||
- BUGFIX: Use stable sorting in transfer list (Chocobo1)
|
||||
- BUGFIX: Allow "missing files" torrents to save more resume data (glassez)
|
||||
- BUGFIX: Restart "missing files" torrents after changing location (glassez)
|
||||
- BUGFIX: Show proper string when torrent availability is not available (Chocobo1)
|
||||
- BUGFIX: Apply "Hide zero/infinity values" to "Time Active", "Down/Up Limit" and ETA columns (Chocobo1)
|
||||
- BUGFIX: Fix potential out-of-bounds access (Chocobo1)
|
||||
- BUGFIX: Make SpeedPlotView averager time aware (jagannatharjun)
|
||||
- BUGFIX: Add a 3-Hour graph (jagannatharjun)
|
||||
- BUGFIX: Add an option to disable icons in menus (always disabled on MacOS) (Michał Kopeć)
|
||||
- BUGFIX: Improve detection of filename extension of audio/video files (Chocobo1)
|
||||
- BUGFIX: Various drawing improvements of progress bar (Chocobo1)
|
||||
- BUGFIX: Properly stop torrent creation if aborted (Chocobo1)
|
||||
- BUGFIX: Replace external program parameters in one step (Chocobo1)
|
||||
- BUGFIX: Improve "save resume data" handling (glassez)
|
||||
- BUGFIX: Fix bad IPv6 address format for outgoingInterfaces (treysis)
|
||||
- WEBUI: Properly decode strings (brvphoenix)
|
||||
- WEBUI: Accept "share limits" when adding torrent using WebAPI (glassez)
|
||||
- WEBUI: Add seeding time to the active time column (thalieht)
|
||||
- WEBUI: Fix incorrect seeding time string in General tab (thalieht)
|
||||
- WEBUI: Allow >100 days in WebUI function "friendlyDuration" (thalieht)
|
||||
- WEBUI: Avoid decoding strings repeatedly (brvphoenix)
|
||||
- RSS: Add category button on AutomatedRSSDownloader on GUI (Si Yong Kim)
|
||||
- WINDOWS: NSIS: Update Czech translation (slrslr)
|
||||
- WINDOWS: NSIS: Update Portuguese BR translation (Alex)
|
||||
- WINDOWS: NSIS: Add Estonian translation (PriitUring)
|
||||
- WINDOWS: Allow change-case-only file renaming (glassez)
|
||||
- LINUX: Systemd: wait for mounting of local filesystems (Juraj Oršulić)
|
||||
- OTHER: Raise minimum libtorrent version to 1.2.12 (glassez)
|
||||
- OTHER: Raise minimum Qt version to 5.12 (glassez)
|
||||
|
||||
Tue Jan 19 2021 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.3.3
|
||||
- FEATURE: New languages: Azerbaijani, Estonian
|
||||
- BUGFIX: Unify global speed dialogs for normal/alternative speeds (thalieht)
|
||||
|
||||
4
INSTALL
4
INSTALL
@@ -5,13 +5,13 @@ qBittorrent - A BitTorrent client in C++ / Qt
|
||||
|
||||
- Boost >= 1.65
|
||||
|
||||
- libtorrent-rasterbar >= 1.2.11 (by Arvid Norberg)
|
||||
- libtorrent-rasterbar >= 1.2.12 (by Arvid Norberg)
|
||||
* https://www.libtorrent.org/
|
||||
* Be careful: another library (the one used by rTorrent) uses a similar name
|
||||
|
||||
- OpenSSL >= 1.1.1
|
||||
|
||||
- Qt >= 5.9.5
|
||||
- Qt >= 5.12
|
||||
|
||||
- zlib >= 1.2.11
|
||||
|
||||
|
||||
80
configure
vendored
80
configure
vendored
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.70 for qbittorrent v4.3.3.
|
||||
# Generated by GNU Autoconf 2.70 for qbittorrent v4.3.4.1.
|
||||
#
|
||||
# Report bugs to <bugs.qbittorrent.org>.
|
||||
#
|
||||
@@ -610,8 +610,8 @@ MAKEFLAGS=
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='qbittorrent'
|
||||
PACKAGE_TARNAME='qbittorrent'
|
||||
PACKAGE_VERSION='v4.3.3'
|
||||
PACKAGE_STRING='qbittorrent v4.3.3'
|
||||
PACKAGE_VERSION='v4.3.4.1'
|
||||
PACKAGE_STRING='qbittorrent v4.3.4.1'
|
||||
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
|
||||
PACKAGE_URL='https://www.qbittorrent.org/'
|
||||
|
||||
@@ -1330,7 +1330,7 @@ if test "$ac_init_help" = "long"; then
|
||||
# Omit some internal or obsolete options to make the list less imposing.
|
||||
# This message is too long to be a string in the A/UX 3.1 sh.
|
||||
cat <<_ACEOF
|
||||
\`configure' configures qbittorrent v4.3.3 to adapt to many kinds of systems.
|
||||
\`configure' configures qbittorrent v4.3.4.1 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -1401,7 +1401,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.3.3:";;
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.3.4.1:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@@ -1455,7 +1455,7 @@ Some influential environment variables:
|
||||
directories to add to pkg-config's search path
|
||||
PKG_CONFIG_LIBDIR
|
||||
path overriding pkg-config's built-in search path
|
||||
QT_QMAKE value of host_bins for Qt5Core >= 5.9.5, overriding pkg-config
|
||||
QT_QMAKE value of host_bins for Qt5Core >= 5.12, overriding pkg-config
|
||||
Qt5Svg_CFLAGS
|
||||
C compiler flags for Qt5Svg, overriding pkg-config
|
||||
Qt5Svg_LIBS linker flags for Qt5Svg, overriding pkg-config
|
||||
@@ -1538,7 +1538,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
qbittorrent configure v4.3.3
|
||||
qbittorrent configure v4.3.4.1
|
||||
generated by GNU Autoconf 2.70
|
||||
|
||||
Copyright (C) 2020 Free Software Foundation, Inc.
|
||||
@@ -1700,7 +1700,7 @@ cat >config.log <<_ACEOF
|
||||
This file contains any messages produced by compilers while
|
||||
running configure, to aid debugging if configure makes a mistake.
|
||||
|
||||
It was created by qbittorrent $as_me v4.3.3, which was
|
||||
It was created by qbittorrent $as_me v4.3.4.1, which was
|
||||
generated by GNU Autoconf 2.70. Invocation command line was
|
||||
|
||||
$ $0$ac_configure_args_raw
|
||||
@@ -4848,7 +4848,7 @@ fi
|
||||
|
||||
# Define the identity of the package.
|
||||
PACKAGE='qbittorrent'
|
||||
VERSION='v4.3.3'
|
||||
VERSION='v4.3.4.1'
|
||||
|
||||
|
||||
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
|
||||
@@ -5525,8 +5525,8 @@ printf "%s\n" "$enable_webui" >&6; }
|
||||
esac
|
||||
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.9.5\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.9.5") 2>&5
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.12\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.12") 2>&5
|
||||
ac_status=$?
|
||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; }; then
|
||||
@@ -5535,12 +5535,12 @@ if test -n "$QT_QMAKE"; then
|
||||
pkg_cv_QT_QMAKE="$QT_QMAKE"
|
||||
elif test -n "$PKG_CONFIG"; then
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.9.5\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.9.5") 2>&5
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Core >= 5.12\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "Qt5Core >= 5.12") 2>&5
|
||||
ac_status=$?
|
||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; }; then
|
||||
pkg_cv_QT_QMAKE=`$PKG_CONFIG --variable="host_bins" "Qt5Core >= 5.9.5" 2>/dev/null`
|
||||
pkg_cv_QT_QMAKE=`$PKG_CONFIG --variable="host_bins" "Qt5Core >= 5.12" 2>/dev/null`
|
||||
test "x$?" != "x0" && pkg_failed=yes
|
||||
else
|
||||
pkg_failed=yes
|
||||
@@ -5570,8 +5570,8 @@ fi
|
||||
|
||||
fi
|
||||
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5 qmake >= 5.9.5" >&5
|
||||
printf %s "checking for Qt5 qmake >= 5.9.5... " >&6; }
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5 qmake >= 5.12" >&5
|
||||
printf %s "checking for Qt5 qmake >= 5.12... " >&6; }
|
||||
if test "x$QT_QMAKE" != "x"
|
||||
then :
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $QT_QMAKE" >&5
|
||||
@@ -5598,12 +5598,12 @@ if test -n "$Qt5Svg_CFLAGS"; then
|
||||
pkg_cv_Qt5Svg_CFLAGS="$Qt5Svg_CFLAGS"
|
||||
elif test -n "$PKG_CONFIG"; then
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.5.1\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.5.1") 2>&5
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.12\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.12") 2>&5
|
||||
ac_status=$?
|
||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; }; then
|
||||
pkg_cv_Qt5Svg_CFLAGS=`$PKG_CONFIG --cflags "Qt5Svg >= 5.5.1" 2>/dev/null`
|
||||
pkg_cv_Qt5Svg_CFLAGS=`$PKG_CONFIG --cflags "Qt5Svg >= 5.12" 2>/dev/null`
|
||||
test "x$?" != "x0" && pkg_failed=yes
|
||||
else
|
||||
pkg_failed=yes
|
||||
@@ -5615,12 +5615,12 @@ if test -n "$Qt5Svg_LIBS"; then
|
||||
pkg_cv_Qt5Svg_LIBS="$Qt5Svg_LIBS"
|
||||
elif test -n "$PKG_CONFIG"; then
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.5.1\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.5.1") 2>&5
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5Svg >= 5.12\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "Qt5Svg >= 5.12") 2>&5
|
||||
ac_status=$?
|
||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; }; then
|
||||
pkg_cv_Qt5Svg_LIBS=`$PKG_CONFIG --libs "Qt5Svg >= 5.5.1" 2>/dev/null`
|
||||
pkg_cv_Qt5Svg_LIBS=`$PKG_CONFIG --libs "Qt5Svg >= 5.12" 2>/dev/null`
|
||||
test "x$?" != "x0" && pkg_failed=yes
|
||||
else
|
||||
pkg_failed=yes
|
||||
@@ -5641,14 +5641,14 @@ else
|
||||
_pkg_short_errors_supported=no
|
||||
fi
|
||||
if test $_pkg_short_errors_supported = yes; then
|
||||
Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "Qt5Svg >= 5.5.1" 2>&1`
|
||||
Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "Qt5Svg >= 5.12" 2>&1`
|
||||
else
|
||||
Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "Qt5Svg >= 5.5.1" 2>&1`
|
||||
Qt5Svg_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "Qt5Svg >= 5.12" 2>&1`
|
||||
fi
|
||||
# Put the nasty error message in config.log where it belongs
|
||||
echo "$Qt5Svg_PKG_ERRORS" >&5
|
||||
|
||||
as_fn_error $? "Package requirements (Qt5Svg >= 5.5.1) were not met:
|
||||
as_fn_error $? "Package requirements (Qt5Svg >= 5.12) were not met:
|
||||
|
||||
$Qt5Svg_PKG_ERRORS
|
||||
|
||||
@@ -5688,11 +5688,11 @@ case "x$enable_qt_dbus" in #(
|
||||
"xyes") :
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
printf "%s\n" "yes" >&6; }
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5DBus >= 5.9.5" >&5
|
||||
printf %s "checking for Qt5DBus >= 5.9.5... " >&6; }
|
||||
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for Qt5DBus >= 5.12" >&5
|
||||
printf %s "checking for Qt5DBus >= 5.12... " >&6; }
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5DBus >= 5.9.5\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "Qt5DBus >= 5.9.5") 2>&5
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"Qt5DBus >= 5.12\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "Qt5DBus >= 5.12") 2>&5
|
||||
ac_status=$?
|
||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; }; then
|
||||
@@ -6354,12 +6354,12 @@ if test -n "$libtorrent_CFLAGS"; then
|
||||
pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS"
|
||||
elif test -n "$PKG_CONFIG"; then
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.11\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.11") 2>&5
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.12\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.12") 2>&5
|
||||
ac_status=$?
|
||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; }; then
|
||||
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.11" 2>/dev/null`
|
||||
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.12" 2>/dev/null`
|
||||
test "x$?" != "x0" && pkg_failed=yes
|
||||
else
|
||||
pkg_failed=yes
|
||||
@@ -6371,12 +6371,12 @@ if test -n "$libtorrent_LIBS"; then
|
||||
pkg_cv_libtorrent_LIBS="$libtorrent_LIBS"
|
||||
elif test -n "$PKG_CONFIG"; then
|
||||
if test -n "$PKG_CONFIG" && \
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.11\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.11") 2>&5
|
||||
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.12\""; } >&5
|
||||
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.12") 2>&5
|
||||
ac_status=$?
|
||||
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
|
||||
test $ac_status = 0; }; then
|
||||
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.11" 2>/dev/null`
|
||||
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.12" 2>/dev/null`
|
||||
test "x$?" != "x0" && pkg_failed=yes
|
||||
else
|
||||
pkg_failed=yes
|
||||
@@ -6397,14 +6397,14 @@ else
|
||||
_pkg_short_errors_supported=no
|
||||
fi
|
||||
if test $_pkg_short_errors_supported = yes; then
|
||||
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.11" 2>&1`
|
||||
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.12" 2>&1`
|
||||
else
|
||||
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.11" 2>&1`
|
||||
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.12" 2>&1`
|
||||
fi
|
||||
# Put the nasty error message in config.log where it belongs
|
||||
echo "$libtorrent_PKG_ERRORS" >&5
|
||||
|
||||
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.11) were not met:
|
||||
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.12) were not met:
|
||||
|
||||
$libtorrent_PKG_ERRORS
|
||||
|
||||
@@ -7401,7 +7401,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
|
||||
# report actual input values of CONFIG_FILES etc. instead of their
|
||||
# values after options handling.
|
||||
ac_log="
|
||||
This file was extended by qbittorrent $as_me v4.3.3, which was
|
||||
This file was extended by qbittorrent $as_me v4.3.4.1, which was
|
||||
generated by GNU Autoconf 2.70. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -7461,7 +7461,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config='$ac_cs_config_escaped'
|
||||
ac_cs_version="\\
|
||||
qbittorrent config.status v4.3.3
|
||||
qbittorrent config.status v4.3.4.1
|
||||
configured by $0, generated by GNU Autoconf 2.70,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
AC_INIT([qbittorrent], [v4.3.3], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_INIT([qbittorrent], [v4.3.4.1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
: ${CFLAGS=""}
|
||||
@@ -141,7 +141,7 @@ AS_IF([test "x$QT_QMAKE" = "x"],
|
||||
[AC_MSG_ERROR([Could not find qmake])
|
||||
])
|
||||
AS_IF([test "x$enable_gui" = "xyes"],
|
||||
[PKG_CHECK_MODULES(Qt5Svg, [Qt5Svg >= 5.5.1])
|
||||
[PKG_CHECK_MODULES(Qt5Svg, [Qt5Svg >= 5.12])
|
||||
])
|
||||
AC_MSG_CHECKING([whether QtDBus should be enabled])
|
||||
AS_CASE(["x$enable_qt_dbus"],
|
||||
@@ -180,7 +180,7 @@ AC_MSG_NOTICE([Boost.System LIB: "$BOOST_SYSTEM_LIB"])
|
||||
LIBS="$BOOST_SYSTEM_LIB $LIBS"
|
||||
|
||||
PKG_CHECK_MODULES(libtorrent,
|
||||
[libtorrent-rasterbar >= 1.2.11],
|
||||
[libtorrent-rasterbar >= 1.2.12],
|
||||
[CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS"
|
||||
LIBS="$libtorrent_LIBS $LIBS"])
|
||||
|
||||
|
||||
2
dist/mac/Info.plist
vendored
2
dist/mac/Info.plist
vendored
@@ -55,7 +55,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.3.3</string>
|
||||
<string>4.3.4.1</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>@EXECUTABLE@</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
@@ -74,6 +74,6 @@
|
||||
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="4.3.3" date="2021-01-19"/>
|
||||
<release version="4.3.4.1" date="2021-03-24"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
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
|
||||
GenericName[mk]=BitTorrent клиент
|
||||
Name[mk]=qBittorrent
|
||||
Comment[en_AU]=Download and share files over BitTorrent
|
||||
GenericName[en_AU]=BitTorrent client
|
||||
Name[en_AU]=qBittorrent
|
||||
Comment[nb]=Last ned og del filer over BitTorrent
|
||||
GenericName[nb]=BitTorrent-klient
|
||||
Name[nb]=qBittorrent
|
||||
@@ -162,9 +165,9 @@ Name[uz@Latn]=qBittorrent
|
||||
Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent
|
||||
GenericName[ltg]=BitTorrent klients
|
||||
Name[ltg]=qBittorrent
|
||||
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
|
||||
GenericName[hi_IN]=BitTorrent साधन
|
||||
Name[hi_IN]=qBittorrent
|
||||
Comment[hi_IN]=बिटटौरेंट द्वारा फाइल डाउनलोड व सहभाजन
|
||||
GenericName[hi_IN]=बिटटौरेंट साधन
|
||||
Name[hi_IN]=क्यूबिटटौरेंट
|
||||
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
||||
GenericName[tr]=BitTorrent istemcisi
|
||||
Name[tr]=qBittorrent
|
||||
@@ -207,9 +210,9 @@ Name[ne_NP]=qBittorrent
|
||||
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
|
||||
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
|
||||
Name[te]=క్యు బిట్ టొరెంట్
|
||||
Comment[en_AU]=Download and share files over BitTorrent
|
||||
GenericName[en_AU]=BitTorrent client
|
||||
Name[en_AU]=qBittorrent
|
||||
Comment[pt_PT]=Transferir e partilhar ficheiros por BitTorrent
|
||||
GenericName[pt_PT]=Cliente BitTorrent
|
||||
Name[pt_PT]=qBittorrent
|
||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ด้วยบิททอเร้น
|
||||
GenericName[th]=โปรแกรมบิททอเร้น
|
||||
Name[th]=qBittorrent
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Description=qBittorrent-nox service for user %I
|
||||
Documentation=man:qbittorrent-nox(1)
|
||||
Wants=network-online.target
|
||||
After=network-online.target nss-lookup.target
|
||||
After=local-fs.target network-online.target nss-lookup.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
|
||||
@@ -7,7 +7,7 @@ LangString inst_dekstop ${LANG_CZECH} "Vytvořit zástupce na ploše"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_CZECH} "Vytvořit zástupce v nabídce Start"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_CZECH} "Spusťte aplikaci qBittorrent ve Windows"
|
||||
LangString inst_startup ${LANG_CZECH} "Spustit qBittorrent při startu Windows"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_CZECH} "Otevírat .torrent soubory pomocí qBittorrent"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
@@ -15,9 +15,9 @@ LangString inst_magnet ${LANG_CZECH} "Otevírat odkazy Magnet pomocí qBittorren
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_CZECH} "Vytvořit pravidlo ve Windows Firewall"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_CZECH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_CZECH} "Vypnout MAX_PATH limit 260 znaků pro cesty (vyžaduje Windows 10 1607 a novější)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_CZECH} "Vytváření pravidla ve Windows Firewall"
|
||||
LangString inst_firewallinfo ${LANG_CZECH} "Přidávání pravidla do Windows Firewall"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_CZECH} "qBittorrent je spuštěn. Před instalací aplikaci ukončete, prosím."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
|
||||
52
dist/windows/installer-translations/estonian.nsi
vendored
52
dist/windows/installer-translations/estonian.nsi
vendored
@@ -1,59 +1,59 @@
|
||||
;Installer strings
|
||||
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_ESTONIAN} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_ESTONIAN} "qBittorrent (vajalik)"
|
||||
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_ESTONIAN} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_ESTONIAN} "Loo Töölaua Otsetee"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_ESTONIAN} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_ESTONIAN} "Loo Start Menüü Otsetee"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_ESTONIAN} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_ESTONIAN} "Käivita qBittorrent Windowsi käivitamisel"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_ESTONIAN} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_ESTONIAN} "Ava .torrent failid qBittorrentiga"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_ESTONIAN} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_ESTONIAN} "Ava magnet lingid qBittorrentiga"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_ESTONIAN} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_ESTONIAN} "Lisa reegel Windowsi Tulemüüri"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_ESTONIAN} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_ESTONIAN} "Keela Windowsi kaustade pikkuse limiit (260 karakterit MAX_PATH limiit, vajalik Windows 10 1607 või uuemat)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_ESTONIAN} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_ESTONIAN} "Lisatakse Windowsi Tulemüüri reegel"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_ESTONIAN} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_ESTONIAN} "qBittorrent töötab. Palun sulge see programm, enne installi."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_ESTONIAN} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_ESTONIAN} "Praegune versioon uninstallitakse. Kasutaja sätted ja torrenti failid jäetakse alles."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_ESTONIAN} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_ESTONIAN} "Uninstallitakse eelmist versiooni."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_ESTONIAN} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_ESTONIAN} "Käivita qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_ESTONIAN} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_ESTONIAN} "See installer töötab ainult 64-bit Windowsi versioonides."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_ESTONIAN} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_ESTONIAN} "Selle qBittorrenti versiooni jaoks on vajalik vähemalt Windows 7."
|
||||
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
|
||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||
LangString remove_files ${LANG_ESTONIAN} "Remove files"
|
||||
LangString remove_files ${LANG_ESTONIAN} "Eemalda failid"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_ESTONIAN} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_ESTONIAN} "Eemalda otseteed"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_ESTONIAN} "Remove file associations"
|
||||
LangString remove_associations ${LANG_ESTONIAN} "Eemalda failide seotus"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_ESTONIAN} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_ESTONIAN} "Eemalda registri võtmed"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_ESTONIAN} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_ESTONIAN} "Eemalda seadistuste failid"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_ESTONIAN} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_ESTONIAN} "Eemalda Windowsi Tulemüüri reegel"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_ESTONIAN} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_ESTONIAN} "Eemaldatakse Windowsi Tulemüüri reegel"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_ESTONIAN} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_ESTONIAN} "Eemalda torrentid ja ajutised andmed"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_ESTONIAN} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_ESTONIAN} "qBittorrent töötab. Palun sulge programm, enne uninstallimist."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_ESTONIAN} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_ESTONIAN} "Ei eemaldata .torrent seotust. Mis on seotud:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_ESTONIAN} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_ESTONIAN} "Ei eemaldata magnetitega seotust. Mis on seotud:"
|
||||
|
||||
@@ -15,7 +15,7 @@ LangString inst_magnet ${LANG_PORTUGUESEBR} "Abrir links magnéticos com qBittor
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_PORTUGUESEBR} "Adicionar regra no firewall do Windows"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_PORTUGUESEBR} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_PORTUGUESEBR} "Desativar limite de caracteres em caminhos do Windows (limite MAX_PATH de 260 caracteres, requer Windows 10 1067 ou superior)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_PORTUGUESEBR} "Adicionando regra no firewall do Windows"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
@@ -27,9 +27,9 @@ LangString inst_unist ${LANG_PORTUGUESEBR} "Desinstalando versão anterior."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_PORTUGUESEBR} "Executar qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_PORTUGUESEBR} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_PORTUGUESEBR} "Este instalador apenas funciona em versões 64-bit do Windows."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_PORTUGUESEBR} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_PORTUGUESEBR} "Esta versão do qBittorrent requer no mínimo o Windows 7."
|
||||
|
||||
|
||||
;------------------------------------
|
||||
@@ -42,13 +42,13 @@ LangString remove_shortcuts ${LANG_PORTUGUESEBR} "Remover atalhos"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_PORTUGUESEBR} "Remover associação de arquivos"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_PORTUGUESEBR} "Remover registros"
|
||||
LangString remove_registry ${LANG_PORTUGUESEBR} "Remover chaves do registro"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_PORTUGUESEBR} "Remover configurações"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_PORTUGUESEBR} "Remover regras do firewall do Windows"
|
||||
LangString remove_firewall ${LANG_PORTUGUESEBR} "Remover regra do Firewall do Windows"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_PORTUGUESEBR} "Removendo regras do firewall do Windows"
|
||||
LangString remove_firewallinfo ${LANG_PORTUGUESEBR} "Removendo regra do Firewall do Windows"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_PORTUGUESEBR} "Remover torrents e dados em cache"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
|
||||
4
dist/windows/options.nsi
vendored
4
dist/windows/options.nsi
vendored
@@ -28,7 +28,7 @@ XPStyle on
|
||||
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
|
||||
|
||||
; Program specific
|
||||
!define PROG_VERSION "4.3.3"
|
||||
!define PROG_VERSION "4.3.4.1"
|
||||
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
||||
@@ -55,7 +55,7 @@ VIAddVersionKey "LegalCopyright" "Copyright ©2006-2021 The qBittorrent project"
|
||||
VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client"
|
||||
VIAddVersionKey "FileVersion" "${PROG_VERSION}"
|
||||
|
||||
VIProductVersion "${PROG_VERSION}.0"
|
||||
VIProductVersion "${PROG_VERSION}"
|
||||
|
||||
; The default installation directory. It changes depending if we install in the 64bit dir or not.
|
||||
; A caveat of this is if a user has installed a 32bit version and then runs the 64bit installer
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
# Sets the QT_QMAKE variable to the path of Qt5 qmake if found.
|
||||
# --------------------------------------
|
||||
AC_DEFUN([FIND_QT5],
|
||||
[PKG_CHECK_EXISTS([Qt5Core >= 5.9.5],
|
||||
[PKG_CHECK_EXISTS([Qt5Core >= 5.12],
|
||||
[PKG_CHECK_VAR(QT_QMAKE,
|
||||
[Qt5Core >= 5.9.5],
|
||||
[Qt5Core >= 5.12],
|
||||
[host_bins])
|
||||
])
|
||||
|
||||
@@ -18,7 +18,7 @@ AS_IF([test -f "$QT_QMAKE/qmake"],
|
||||
[QT_QMAKE=""])
|
||||
])
|
||||
|
||||
AC_MSG_CHECKING([for Qt5 qmake >= 5.9.5])
|
||||
AC_MSG_CHECKING([for Qt5 qmake >= 5.12])
|
||||
AS_IF([test "x$QT_QMAKE" != "x"],
|
||||
[AC_MSG_RESULT([$QT_QMAKE])],
|
||||
[AC_MSG_RESULT([not found])]
|
||||
@@ -29,8 +29,8 @@ AS_IF([test "x$QT_QMAKE" != "x"],
|
||||
# Sets the HAVE_QTDBUS variable to true or false.
|
||||
# --------------------------------------
|
||||
AC_DEFUN([FIND_QTDBUS],
|
||||
[AC_MSG_CHECKING([for Qt5DBus >= 5.9.5])
|
||||
PKG_CHECK_EXISTS([Qt5DBus >= 5.9.5],
|
||||
[AC_MSG_CHECKING([for Qt5DBus >= 5.12])
|
||||
PKG_CHECK_EXISTS([Qt5DBus >= 5.12],
|
||||
[AC_MSG_RESULT([found])
|
||||
HAVE_QTDBUS=[true]],
|
||||
[AC_MSG_RESULT([not found])
|
||||
|
||||
@@ -4,22 +4,14 @@
|
||||
# Based on https://gist.github.com/giraldeau/546ba5512a74dfe9d8ea0862d66db412
|
||||
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts")
|
||||
set_source_files_properties(${QBT_TS_FILES} PROPERTIES OUTPUT_LOCATION "${qBittorrent_BINARY_DIR}/src/lang")
|
||||
if (Qt5_VERSION VERSION_LESS 5.12)
|
||||
qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES})
|
||||
else()
|
||||
qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES} OPTIONS -silent)
|
||||
endif()
|
||||
qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES} OPTIONS -silent)
|
||||
configure_file("${qBittorrent_SOURCE_DIR}/src/lang/lang.qrc" "${qBittorrent_BINARY_DIR}/src/lang/lang.qrc" COPYONLY)
|
||||
|
||||
if (WEBUI)
|
||||
file(GLOB QBT_WEBUI_TS_FILES "${qBittorrent_SOURCE_DIR}/src/webui/www/translations/*.ts")
|
||||
set_source_files_properties(${QBT_WEBUI_TS_FILES}
|
||||
PROPERTIES OUTPUT_LOCATION "${qBittorrent_BINARY_DIR}/src/webui/www/translations")
|
||||
if (Qt5_VERSION VERSION_LESS 5.12)
|
||||
qt5_add_translation(QBT_WEBUI_QM_FILES ${QBT_WEBUI_TS_FILES})
|
||||
else()
|
||||
qt5_add_translation(QBT_WEBUI_QM_FILES ${QBT_WEBUI_TS_FILES} OPTIONS -silent)
|
||||
endif()
|
||||
qt5_add_translation(QBT_WEBUI_QM_FILES ${QBT_WEBUI_TS_FILES} OPTIONS -silent)
|
||||
configure_file("${qBittorrent_SOURCE_DIR}/src/webui/www/translations/webui_translations.qrc"
|
||||
"${qBittorrent_BINARY_DIR}/src/webui/www/translations/webui_translations.qrc" COPYONLY)
|
||||
endif()
|
||||
|
||||
@@ -313,14 +313,6 @@ void Application::processMessage(const QString &message)
|
||||
|
||||
void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
|
||||
{
|
||||
QString program = Preferences::instance()->getAutoRunProgram().trimmed();
|
||||
program.replace("%N", torrent->name());
|
||||
program.replace("%L", torrent->category());
|
||||
|
||||
QStringList tags = torrent->tags().values();
|
||||
std::sort(tags.begin(), tags.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
|
||||
program.replace("%G", tags.join(','));
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
const auto chopPathSep = [](const QString &str) -> QString
|
||||
{
|
||||
@@ -328,21 +320,74 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
|
||||
return str.mid(0, (str.length() -1));
|
||||
return str;
|
||||
};
|
||||
program.replace("%F", chopPathSep(Utils::Fs::toNativePath(torrent->contentPath())));
|
||||
program.replace("%R", chopPathSep(Utils::Fs::toNativePath(torrent->rootPath())));
|
||||
program.replace("%D", chopPathSep(Utils::Fs::toNativePath(torrent->savePath())));
|
||||
#else
|
||||
program.replace("%F", Utils::Fs::toNativePath(torrent->contentPath()));
|
||||
program.replace("%R", Utils::Fs::toNativePath(torrent->rootPath()));
|
||||
program.replace("%D", Utils::Fs::toNativePath(torrent->savePath()));
|
||||
#endif
|
||||
program.replace("%C", QString::number(torrent->filesCount()));
|
||||
program.replace("%Z", QString::number(torrent->totalSize()));
|
||||
program.replace("%T", torrent->currentTracker());
|
||||
program.replace("%I", torrent->hash());
|
||||
|
||||
Logger *logger = Logger::instance();
|
||||
logger->addMessage(tr("Torrent: %1, running external program, command: %2").arg(torrent->name(), program));
|
||||
QString program = Preferences::instance()->getAutoRunProgram().trimmed();
|
||||
|
||||
for (int i = (program.length() - 2); i >= 0; --i)
|
||||
{
|
||||
if (program[i] != QLatin1Char('%'))
|
||||
continue;
|
||||
|
||||
const ushort specifier = program[i + 1].unicode();
|
||||
switch (specifier)
|
||||
{
|
||||
case u'C':
|
||||
program.replace(i, 2, QString::number(torrent->filesCount()));
|
||||
break;
|
||||
case u'D':
|
||||
#if defined(Q_OS_WIN)
|
||||
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->savePath())));
|
||||
#else
|
||||
program.replace(i, 2, Utils::Fs::toNativePath(torrent->savePath()));
|
||||
#endif
|
||||
break;
|
||||
case u'F':
|
||||
#if defined(Q_OS_WIN)
|
||||
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->contentPath())));
|
||||
#else
|
||||
program.replace(i, 2, Utils::Fs::toNativePath(torrent->contentPath()));
|
||||
#endif
|
||||
break;
|
||||
case u'G':
|
||||
{
|
||||
QStringList tags = torrent->tags().values();
|
||||
std::sort(tags.begin(), tags.end(), Utils::String::naturalLessThan<Qt::CaseInsensitive>);
|
||||
program.replace(i, 2, tags.join(','));
|
||||
}
|
||||
break;
|
||||
case u'I':
|
||||
program.replace(i, 2, torrent->id().toString());
|
||||
break;
|
||||
case u'L':
|
||||
program.replace(i, 2, torrent->category());
|
||||
break;
|
||||
case u'N':
|
||||
program.replace(i, 2, torrent->name());
|
||||
break;
|
||||
case u'R':
|
||||
#if defined(Q_OS_WIN)
|
||||
program.replace(i, 2, chopPathSep(Utils::Fs::toNativePath(torrent->rootPath())));
|
||||
#else
|
||||
program.replace(i, 2, Utils::Fs::toNativePath(torrent->rootPath()));
|
||||
#endif
|
||||
break;
|
||||
case u'T':
|
||||
program.replace(i, 2, torrent->currentTracker());
|
||||
break;
|
||||
case u'Z':
|
||||
program.replace(i, 2, QString::number(torrent->totalSize()));
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
// decrement `i` to avoid unwanted replacement, example pattern: "%%N"
|
||||
--i;
|
||||
}
|
||||
|
||||
LogMsg(tr("Torrent: %1, running external program, command: %2").arg(torrent->name(), program));
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
auto programWchar = std::make_unique<wchar_t[]>(program.length() + 1);
|
||||
@@ -358,7 +403,6 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
|
||||
for (int i = 1; i < argCount; ++i)
|
||||
argList += QString::fromWCharArray(args[i]);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
QProcess proc;
|
||||
proc.setProgram(QString::fromWCharArray(args[0]));
|
||||
proc.setArguments(argList);
|
||||
@@ -384,9 +428,6 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
|
||||
args->startupInfo->hStdError = nullptr;
|
||||
});
|
||||
proc.startDetached();
|
||||
#else
|
||||
QProcess::startDetached(QString::fromWCharArray(args[0]), argList);
|
||||
#endif // QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
|
||||
#else // Q_OS_WIN
|
||||
// Cannot give users shell environment by default, as doing so could
|
||||
// enable command injection via torrent name and other arguments
|
||||
|
||||
@@ -235,7 +235,7 @@ int main(int argc, char *argv[])
|
||||
// 3. https://bugreports.qt.io/browse/QTBUG-46015
|
||||
|
||||
qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) && !defined(DISABLE_GUI)
|
||||
#if !defined(DISABLE_GUI)
|
||||
// this is the default in Qt6
|
||||
app->setAttribute(Qt::AA_DisableWindowContextHelpButton);
|
||||
#endif
|
||||
@@ -251,6 +251,9 @@ int main(int argc, char *argv[])
|
||||
|
||||
// On OS X the standard is to not show icons in the menus
|
||||
app->setAttribute(Qt::AA_DontShowIconsInMenus);
|
||||
#else
|
||||
if (!Preferences::instance()->iconsInMenusEnabled())
|
||||
app->setAttribute(Qt::AA_DontShowIconsInMenus);
|
||||
#endif
|
||||
|
||||
if (!firstTimeUser)
|
||||
@@ -378,11 +381,7 @@ void showSplashScreen()
|
||||
const QString version = QBT_VERSION;
|
||||
painter.setPen(QPen(Qt::white));
|
||||
painter.setFont(QFont("Arial", 22, QFont::Black));
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
painter.drawText(224 - painter.fontMetrics().horizontalAdvance(version), 270, version);
|
||||
#else
|
||||
painter.drawText(224 - painter.fontMetrics().width(version), 270, version);
|
||||
#endif
|
||||
QSplashScreen *splash = new QSplashScreen(splashImg);
|
||||
splash->show();
|
||||
QTimer::singleShot(1500, splash, &QObject::deleteLater);
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
#include <QDir>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "base/utils/misc.h"
|
||||
|
||||
@@ -108,7 +109,7 @@ QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
|
||||
#endif
|
||||
prefix = id.section(QLatin1Char('/'), -1);
|
||||
}
|
||||
prefix.remove(QRegExp("[^a-zA-Z]"));
|
||||
prefix.remove(QRegularExpression("[^a-zA-Z]"));
|
||||
prefix.truncate(6);
|
||||
|
||||
QByteArray idc = id.toUtf8();
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <dbghelp.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QTextStream>
|
||||
#ifdef __MINGW32__
|
||||
@@ -256,7 +257,7 @@ const QString straceWin::getBacktrace()
|
||||
|
||||
HANDLE hProcess = GetCurrentProcess();
|
||||
HANDLE hThread = GetCurrentThread();
|
||||
SymInitialize(hProcess, NULL, TRUE);
|
||||
SymInitializeW(hProcess, QCoreApplication::applicationDirPath().toStdWString().c_str(), TRUE);
|
||||
|
||||
DWORD64 dwDisplacement;
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ add_library(qbt_base STATIC
|
||||
bittorrent/torrentinfo.h
|
||||
bittorrent/tracker.h
|
||||
bittorrent/trackerentry.h
|
||||
digest32.h
|
||||
exceptions.h
|
||||
filesystemwatcher.h
|
||||
global.h
|
||||
|
||||
@@ -55,13 +55,8 @@ AsyncFileStorage::~AsyncFileStorage()
|
||||
|
||||
void AsyncFileStorage::store(const QString &fileName, const QByteArray &data)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
QMetaObject::invokeMethod(this, [this, data, fileName]() { store_impl(fileName, data); }
|
||||
, Qt::QueuedConnection);
|
||||
#else
|
||||
QMetaObject::invokeMethod(this, "store_impl", Qt::QueuedConnection
|
||||
, Q_ARG(QString, fileName), Q_ARG(QByteArray, data));
|
||||
#endif
|
||||
}
|
||||
|
||||
QDir AsyncFileStorage::storageDir() const
|
||||
|
||||
@@ -31,6 +31,7 @@ HEADERS += \
|
||||
$$PWD/bittorrent/torrentinfo.h \
|
||||
$$PWD/bittorrent/tracker.h \
|
||||
$$PWD/bittorrent/trackerentry.h \
|
||||
$$PWD/digest32.h \
|
||||
$$PWD/exceptions.h \
|
||||
$$PWD/filesystemwatcher.h \
|
||||
$$PWD/global.h \
|
||||
|
||||
@@ -78,8 +78,7 @@ void BitTorrent::AbstractFileStorage::renameFile(const QString &oldPath, const Q
|
||||
|
||||
if ((renamingFileIndex < 0) && areSameFileNames(path, oldFilePath))
|
||||
renamingFileIndex = i;
|
||||
|
||||
if (areSameFileNames(path, newFilePath))
|
||||
else if (areSameFileNames(path, newFilePath))
|
||||
throw RuntimeError {tr("The file already exists: '%1'.").arg(newFilePath)};
|
||||
}
|
||||
|
||||
@@ -124,8 +123,7 @@ void BitTorrent::AbstractFileStorage::renameFolder(const QString &oldPath, const
|
||||
|
||||
if (path.startsWith(oldFolderPath, CASE_SENSITIVITY))
|
||||
renamingFileIndexes.append(i);
|
||||
|
||||
if (path.startsWith(newFolderPath, CASE_SENSITIVITY))
|
||||
else if (path.startsWith(newFolderPath, CASE_SENSITIVITY))
|
||||
throw RuntimeError {tr("The folder already exists: '%1'.").arg(newFolderPath)};
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
#include "base/bittorrent/common.h"
|
||||
#include "base/bittorrent/infohash.h"
|
||||
|
||||
void FileSearcher::search(const BitTorrent::InfoHash &id, const QStringList &originalFileNames
|
||||
void FileSearcher::search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames
|
||||
, const QString &completeSavePath, const QString &incompleteSavePath)
|
||||
{
|
||||
const auto findInDir = [](const QString &dirPath, QStringList &fileNames) -> bool
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class InfoHash;
|
||||
class TorrentID;
|
||||
}
|
||||
|
||||
class FileSearcher final : public QObject
|
||||
@@ -44,9 +44,9 @@ public:
|
||||
FileSearcher() = default;
|
||||
|
||||
public slots:
|
||||
void search(const BitTorrent::InfoHash &id, const QStringList &originalFileNames
|
||||
void search(const BitTorrent::TorrentID &id, const QStringList &originalFileNames
|
||||
, const QString &completeSavePath, const QString &incompleteSavePath);
|
||||
|
||||
signals:
|
||||
void searchFinished(const BitTorrent::InfoHash &id, const QString &savePath, const QStringList &fileNames);
|
||||
void searchFinished(const BitTorrent::TorrentID &id, const QString &savePath, const QStringList &fileNames);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -28,68 +28,54 @@
|
||||
|
||||
#include "infohash.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
const int TorrentIDTypeId = qRegisterMetaType<BitTorrent::TorrentID>();
|
||||
|
||||
using namespace BitTorrent;
|
||||
|
||||
const int InfoHashTypeId = qRegisterMetaType<InfoHash>();
|
||||
|
||||
InfoHash::InfoHash(const lt::sha1_hash &nativeHash)
|
||||
: m_valid(true)
|
||||
, m_nativeHash(nativeHash)
|
||||
BitTorrent::InfoHash::InfoHash(const WrappedType &nativeHash)
|
||||
: m_valid {true}
|
||||
, m_nativeHash {nativeHash}
|
||||
{
|
||||
const QByteArray raw = QByteArray::fromRawData(nativeHash.data(), length());
|
||||
m_hashString = QString::fromLatin1(raw.toHex());
|
||||
}
|
||||
|
||||
InfoHash::InfoHash(const QString &hashString)
|
||||
: m_valid(false)
|
||||
{
|
||||
if (hashString.size() != (length() * 2))
|
||||
return;
|
||||
|
||||
const QByteArray raw = QByteArray::fromHex(hashString.toLatin1());
|
||||
if (raw.size() != length()) // QByteArray::fromHex() will skip over invalid characters
|
||||
return;
|
||||
|
||||
m_valid = true;
|
||||
m_hashString = hashString;
|
||||
m_nativeHash.assign(raw.constData());
|
||||
}
|
||||
|
||||
bool InfoHash::isValid() const
|
||||
bool BitTorrent::InfoHash::isValid() const
|
||||
{
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
InfoHash::operator lt::sha1_hash() const
|
||||
BitTorrent::TorrentID BitTorrent::InfoHash::toTorrentID() const
|
||||
{
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
return m_nativeHash.get_best();
|
||||
#else
|
||||
return {m_nativeHash};
|
||||
#endif
|
||||
}
|
||||
|
||||
BitTorrent::InfoHash::operator WrappedType() const
|
||||
{
|
||||
return m_nativeHash;
|
||||
}
|
||||
|
||||
InfoHash::operator QString() const
|
||||
BitTorrent::TorrentID BitTorrent::TorrentID::fromString(const QString &hashString)
|
||||
{
|
||||
return m_hashString;
|
||||
return {BaseType::fromString(hashString)};
|
||||
}
|
||||
|
||||
bool BitTorrent::operator==(const InfoHash &left, const InfoHash &right)
|
||||
BitTorrent::TorrentID BitTorrent::TorrentID::fromInfoHash(const BitTorrent::InfoHash &infoHash)
|
||||
{
|
||||
return (static_cast<lt::sha1_hash>(left)
|
||||
== static_cast<lt::sha1_hash>(right));
|
||||
return infoHash.toTorrentID();
|
||||
}
|
||||
|
||||
bool BitTorrent::operator!=(const InfoHash &left, const InfoHash &right)
|
||||
uint BitTorrent::qHash(const BitTorrent::TorrentID &key, const uint seed)
|
||||
{
|
||||
return ::qHash(std::hash<TorrentID::UnderlyingType>()(key), seed);
|
||||
}
|
||||
|
||||
bool BitTorrent::operator==(const BitTorrent::InfoHash &left, const BitTorrent::InfoHash &right)
|
||||
{
|
||||
return (static_cast<InfoHash::WrappedType>(left) == static_cast<InfoHash::WrappedType>(right));
|
||||
}
|
||||
|
||||
bool BitTorrent::operator!=(const BitTorrent::InfoHash &left, const BitTorrent::InfoHash &right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
bool BitTorrent::operator<(const InfoHash &left, const InfoHash &right)
|
||||
{
|
||||
return static_cast<lt::sha1_hash>(left) < static_cast<lt::sha1_hash>(right);
|
||||
}
|
||||
|
||||
uint BitTorrent::qHash(const InfoHash &key, const uint seed)
|
||||
{
|
||||
return ::qHash((std::hash<lt::sha1_hash> {})(key), seed);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -28,41 +28,60 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libtorrent/sha1_hash.hpp>
|
||||
#include <libtorrent/version.hpp>
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
#include <libtorrent/info_hash.hpp>
|
||||
#endif
|
||||
|
||||
#include <QHash>
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
|
||||
#include "base/digest32.h"
|
||||
|
||||
using SHA1Hash = Digest32<160>;
|
||||
using SHA256Hash = Digest32<256>;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class InfoHash;
|
||||
|
||||
class TorrentID : public Digest32<160>
|
||||
{
|
||||
public:
|
||||
using BaseType = Digest32<160>;
|
||||
using BaseType::BaseType;
|
||||
|
||||
static TorrentID fromString(const QString &hashString);
|
||||
static TorrentID fromInfoHash(const InfoHash &infoHash);
|
||||
};
|
||||
|
||||
class InfoHash
|
||||
{
|
||||
public:
|
||||
InfoHash() = default;
|
||||
InfoHash(const lt::sha1_hash &nativeHash);
|
||||
InfoHash(const QString &hashString);
|
||||
InfoHash(const InfoHash &other) = default;
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
using WrappedType = lt::info_hash_t;
|
||||
#else
|
||||
using WrappedType = lt::sha1_hash;
|
||||
#endif
|
||||
|
||||
static constexpr int length()
|
||||
{
|
||||
return lt::sha1_hash::size();
|
||||
}
|
||||
InfoHash() = default;
|
||||
InfoHash(const InfoHash &other) = default;
|
||||
InfoHash(const WrappedType &nativeHash);
|
||||
|
||||
bool isValid() const;
|
||||
TorrentID toTorrentID() const;
|
||||
|
||||
operator lt::sha1_hash() const;
|
||||
operator QString() const;
|
||||
operator WrappedType() const;
|
||||
|
||||
private:
|
||||
bool m_valid = false;
|
||||
lt::sha1_hash m_nativeHash;
|
||||
QString m_hashString;
|
||||
WrappedType m_nativeHash;
|
||||
};
|
||||
|
||||
uint qHash(const TorrentID &key, uint seed);
|
||||
|
||||
bool operator==(const InfoHash &left, const InfoHash &right);
|
||||
bool operator!=(const InfoHash &left, const InfoHash &right);
|
||||
bool operator<(const InfoHash &left, const InfoHash &right);
|
||||
uint qHash(const InfoHash &key, uint seed);
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(BitTorrent::InfoHash)
|
||||
Q_DECLARE_METATYPE(BitTorrent::TorrentID)
|
||||
|
||||
@@ -40,15 +40,15 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
bool isBitTorrentInfoHash(const QString &string)
|
||||
bool isSHA1Hash(const QString &string)
|
||||
{
|
||||
// There are 2 representations for BitTorrent info hash:
|
||||
// 1. 40 chars hex-encoded string
|
||||
// == 20 (SHA-1 length in bytes) * 2 (each byte maps to 2 hex characters)
|
||||
// 2. 32 chars Base32 encoded string
|
||||
// == 20 (SHA-1 length in bytes) * 1.6 (the efficiency of Base32 encoding)
|
||||
const int SHA1_HEX_SIZE = BitTorrent::InfoHash::length() * 2;
|
||||
const int SHA1_BASE32_SIZE = BitTorrent::InfoHash::length() * 1.6;
|
||||
const int SHA1_HEX_SIZE = SHA1Hash::length() * 2;
|
||||
const int SHA1_BASE32_SIZE = SHA1Hash::length() * 1.6;
|
||||
|
||||
return ((((string.size() == SHA1_HEX_SIZE))
|
||||
&& !string.contains(QRegularExpression(QLatin1String("[^0-9A-Fa-f]"))))
|
||||
@@ -65,7 +65,7 @@ MagnetUri::MagnetUri(const QString &source)
|
||||
{
|
||||
if (source.isEmpty()) return;
|
||||
|
||||
if (isBitTorrentInfoHash(source))
|
||||
if (isSHA1Hash(source))
|
||||
m_url = QLatin1String("magnet:?xt=urn:btih:") + source;
|
||||
|
||||
lt::error_code ec;
|
||||
@@ -73,12 +73,18 @@ MagnetUri::MagnetUri(const QString &source)
|
||||
if (ec) return;
|
||||
|
||||
m_valid = true;
|
||||
m_hash = m_addTorrentParams.info_hash;
|
||||
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
m_infoHash = m_addTorrentParams.info_hashes;
|
||||
#else
|
||||
m_infoHash = m_addTorrentParams.info_hash;
|
||||
#endif
|
||||
|
||||
m_name = QString::fromStdString(m_addTorrentParams.name);
|
||||
|
||||
m_trackers.reserve(m_addTorrentParams.trackers.size());
|
||||
for (const std::string &tracker : m_addTorrentParams.trackers)
|
||||
m_trackers.append(lt::announce_entry {tracker});
|
||||
m_trackers.append({QString::fromStdString(tracker)});
|
||||
|
||||
m_urlSeeds.reserve(m_addTorrentParams.url_seeds.size());
|
||||
for (const std::string &urlSeed : m_addTorrentParams.url_seeds)
|
||||
@@ -90,9 +96,9 @@ bool MagnetUri::isValid() const
|
||||
return m_valid;
|
||||
}
|
||||
|
||||
InfoHash MagnetUri::hash() const
|
||||
InfoHash MagnetUri::infoHash() const
|
||||
{
|
||||
return m_hash;
|
||||
return m_infoHash;
|
||||
}
|
||||
|
||||
QString MagnetUri::name() const
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace BitTorrent
|
||||
explicit MagnetUri(const QString &source = {});
|
||||
|
||||
bool isValid() const;
|
||||
InfoHash hash() const;
|
||||
InfoHash infoHash() const;
|
||||
QString name() const;
|
||||
QVector<TrackerEntry> trackers() const;
|
||||
QVector<QUrl> urlSeeds() const;
|
||||
@@ -57,7 +57,7 @@ namespace BitTorrent
|
||||
private:
|
||||
bool m_valid;
|
||||
QString m_url;
|
||||
InfoHash m_hash;
|
||||
InfoHash m_infoHash;
|
||||
QString m_name;
|
||||
QVector<TrackerEntry> m_trackers;
|
||||
QVector<QUrl> m_urlSeeds;
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include <libtorrent/alert_types.hpp>
|
||||
#include <libtorrent/bdecode.hpp>
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/entry.hpp>
|
||||
#include <libtorrent/error_code.hpp>
|
||||
#include <libtorrent/extensions/smart_ban.hpp>
|
||||
#include <libtorrent/extensions/ut_metadata.hpp>
|
||||
@@ -55,6 +56,7 @@
|
||||
#include <libtorrent/session_stats.hpp>
|
||||
#include <libtorrent/session_status.hpp>
|
||||
#include <libtorrent/torrent_info.hpp>
|
||||
#include <libtorrent/write_resume_data.hpp>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
@@ -303,6 +305,17 @@ namespace
|
||||
};
|
||||
}
|
||||
|
||||
using ListType = lt::entry::list_type;
|
||||
|
||||
ListType setToEntryList(const QSet<QString> &input)
|
||||
{
|
||||
ListType entryList;
|
||||
entryList.reserve(input.size());
|
||||
for (const QString &setValue : input)
|
||||
entryList.emplace_back(setValue.toStdString());
|
||||
return entryList;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QString convertIfaceNameToGuid(const QString &name)
|
||||
{
|
||||
@@ -372,6 +385,7 @@ Session::Session(QObject *parent)
|
||||
, m_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0)
|
||||
, m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
|
||||
, m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY("UPnPLeaseDuration"), 0)
|
||||
, m_peerToS(BITTORRENT_SESSION_KEY("PeerToS"), 0x20)
|
||||
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), false)
|
||||
, m_includeOverheadInLimits(BITTORRENT_SESSION_KEY("IncludeOverheadInLimits"), false)
|
||||
, m_announceIP(BITTORRENT_SESSION_KEY("AnnounceIP"))
|
||||
@@ -388,7 +402,7 @@ Session::Session(QObject *parent)
|
||||
, clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
|
||||
, m_IDNSupportEnabled(BITTORRENT_SESSION_KEY("IDNSupportEnabled"), false)
|
||||
, m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY("MultiConnectionsPerIp"), false)
|
||||
, m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY("ValidateHTTPSTrackerCertificate"), false)
|
||||
, m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY("ValidateHTTPSTrackerCertificate"), true)
|
||||
, m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY("BlockPeersOnPrivilegedPorts"), false)
|
||||
, m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY("AddTrackersEnabled"), false)
|
||||
, m_additionalTrackers(BITTORRENT_SESSION_KEY("AdditionalTrackers"))
|
||||
@@ -1110,11 +1124,7 @@ void Session::initializeNativeSession()
|
||||
|
||||
m_nativeSession->set_alert_notify([this]()
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
QMetaObject::invokeMethod(this, &Session::readAlerts, Qt::QueuedConnection);
|
||||
#else
|
||||
QMetaObject::invokeMethod(this, "readAlerts", Qt::QueuedConnection);
|
||||
#endif
|
||||
});
|
||||
|
||||
// Enabling plugins
|
||||
@@ -1335,9 +1345,10 @@ void Session::loadLTSettings(lt::settings_pack &settingsPack)
|
||||
// Outgoing ports
|
||||
settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
|
||||
settingsPack.set_int(lt::settings_pack::num_outgoing_ports, outgoingPortsMax() - outgoingPortsMin() + 1);
|
||||
|
||||
// UPnP lease duration
|
||||
settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
|
||||
|
||||
// Type of service
|
||||
settingsPack.set_int(lt::settings_pack::peer_tos, peerToS());
|
||||
// Include overhead in transfer limits
|
||||
settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
|
||||
// IP address to announce to trackers
|
||||
@@ -1387,15 +1398,11 @@ void Session::loadLTSettings(lt::settings_pack &settingsPack)
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef HAS_IDN_SUPPORT
|
||||
settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
|
||||
#endif
|
||||
|
||||
settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
|
||||
|
||||
#ifdef HAS_HTTPS_TRACKER_VALIDATION
|
||||
settingsPack.set_bool(lt::settings_pack::validate_https_trackers, validateHTTPSTrackerCertificate());
|
||||
#endif
|
||||
|
||||
settingsPack.set_bool(lt::settings_pack::no_connect_privileged_ports, blockPeersOnPrivilegedPorts());
|
||||
|
||||
@@ -1450,12 +1457,14 @@ void Session::configureNetworkInterfaces(lt::settings_pack &settingsPack)
|
||||
const QHostAddress addr {ip};
|
||||
if (!addr.isNull())
|
||||
{
|
||||
const QString ip = ((addr.protocol() == QAbstractSocket::IPv6Protocol)
|
||||
? ('[' + Utils::Net::canonicalIPv6Addr(addr).toString() + ']')
|
||||
: addr.toString());
|
||||
endpoints << (ip + portString);
|
||||
const bool isIPv6 = (addr.protocol() == QAbstractSocket::IPv6Protocol);
|
||||
const QString ip = isIPv6
|
||||
? Utils::Net::canonicalIPv6Addr(addr).toString()
|
||||
: addr.toString();
|
||||
|
||||
if ((ip != "0.0.0.0") && (ip != "[::]"))
|
||||
endpoints << ((isIPv6 ? ('[' + ip + ']') : ip) + portString);
|
||||
|
||||
if ((ip != QLatin1String("0.0.0.0")) && (ip != QLatin1String("::")))
|
||||
outgoingInterfaces << ip;
|
||||
}
|
||||
else
|
||||
@@ -1608,7 +1617,7 @@ void Session::populateAdditionalTrackers()
|
||||
{
|
||||
tracker = tracker.trimmed();
|
||||
if (!tracker.isEmpty())
|
||||
m_additionalTrackerList << tracker.toString();
|
||||
m_additionalTrackerList.append({tracker.toString()});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1618,7 +1627,7 @@ void Session::processShareLimits()
|
||||
|
||||
// We shouldn't iterate over `m_torrents` in the loop below
|
||||
// since `deleteTorrent()` modifies it indirectly
|
||||
const QHash<InfoHash, TorrentImpl *> torrents {m_torrents};
|
||||
const QHash<TorrentID, TorrentImpl *> torrents {m_torrents};
|
||||
for (TorrentImpl *const torrent : torrents)
|
||||
{
|
||||
if (torrent->isSeed() && !torrent->isForced())
|
||||
@@ -1640,12 +1649,12 @@ void Session::processShareLimits()
|
||||
if (m_maxRatioAction == Remove)
|
||||
{
|
||||
LogMsg(tr("'%1' reached the maximum ratio you set. Removed.").arg(torrent->name()));
|
||||
deleteTorrent(torrent->hash());
|
||||
deleteTorrent(torrent->id());
|
||||
}
|
||||
else if (m_maxRatioAction == DeleteFiles)
|
||||
{
|
||||
LogMsg(tr("'%1' reached the maximum ratio you set. Removed torrent and its files.").arg(torrent->name()));
|
||||
deleteTorrent(torrent->hash(), DeleteTorrentAndFiles);
|
||||
deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
|
||||
}
|
||||
else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
|
||||
{
|
||||
@@ -1679,12 +1688,12 @@ void Session::processShareLimits()
|
||||
if (m_maxRatioAction == Remove)
|
||||
{
|
||||
LogMsg(tr("'%1' reached the maximum seeding time you set. Removed.").arg(torrent->name()));
|
||||
deleteTorrent(torrent->hash());
|
||||
deleteTorrent(torrent->id());
|
||||
}
|
||||
else if (m_maxRatioAction == DeleteFiles)
|
||||
{
|
||||
LogMsg(tr("'%1' reached the maximum seeding time you set. Removed torrent and its files.").arg(torrent->name()));
|
||||
deleteTorrent(torrent->hash(), DeleteTorrentAndFiles);
|
||||
deleteTorrent(torrent->id(), DeleteTorrentAndFiles);
|
||||
}
|
||||
else if ((m_maxRatioAction == Pause) && !torrent->isPaused())
|
||||
{
|
||||
@@ -1721,7 +1730,7 @@ void Session::handleDownloadFinished(const Net::DownloadResult &result)
|
||||
}
|
||||
}
|
||||
|
||||
void Session::fileSearchFinished(const InfoHash &id, const QString &savePath, const QStringList &fileNames)
|
||||
void Session::fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames)
|
||||
{
|
||||
TorrentImpl *torrent = m_torrents.value(id);
|
||||
if (torrent)
|
||||
@@ -1747,9 +1756,9 @@ void Session::fileSearchFinished(const InfoHash &id, const QString &savePath, co
|
||||
}
|
||||
|
||||
// Return the torrent handle, given its hash
|
||||
Torrent *Session::findTorrent(const InfoHash &hash) const
|
||||
Torrent *Session::findTorrent(const TorrentID &id) const
|
||||
{
|
||||
return m_torrents.value(hash);
|
||||
return m_torrents.value(id);
|
||||
}
|
||||
|
||||
bool Session::hasActiveTorrents() const
|
||||
@@ -1764,7 +1773,7 @@ bool Session::hasUnfinishedTorrents() const
|
||||
{
|
||||
return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentImpl *torrent)
|
||||
{
|
||||
return (!torrent->isSeed() && !torrent->isPaused());
|
||||
return (!torrent->isSeed() && !torrent->isPaused() && !torrent->isErrored());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1797,18 +1806,18 @@ void Session::banIP(const QString &ip)
|
||||
|
||||
// Delete a torrent from the session, given its hash
|
||||
// and from the disk, if the corresponding deleteOption is chosen
|
||||
bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOption)
|
||||
bool Session::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption)
|
||||
{
|
||||
TorrentImpl *const torrent = m_torrents.take(hash);
|
||||
TorrentImpl *const torrent = m_torrents.take(id);
|
||||
if (!torrent) return false;
|
||||
|
||||
qDebug("Deleting torrent with hash: %s", qUtf8Printable(torrent->hash()));
|
||||
qDebug("Deleting torrent with ID: %s", qUtf8Printable(torrent->id().toString()));
|
||||
emit torrentAboutToBeRemoved(torrent);
|
||||
|
||||
// Remove it from session
|
||||
if (deleteOption == DeleteTorrent)
|
||||
{
|
||||
m_removingTorrents[torrent->hash()] = {torrent->name(), "", deleteOption};
|
||||
m_removingTorrents[torrent->id()] = {torrent->name(), "", deleteOption};
|
||||
|
||||
const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
|
||||
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
|
||||
@@ -1837,7 +1846,7 @@ bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOptio
|
||||
rootPath = torrent->actualStorageLocation();
|
||||
}
|
||||
|
||||
m_removingTorrents[torrent->hash()] = {torrent->name(), rootPath, deleteOption};
|
||||
m_removingTorrents[torrent->id()] = {torrent->name(), rootPath, deleteOption};
|
||||
|
||||
if (m_moveStorageQueue.size() > 1)
|
||||
{
|
||||
@@ -1856,48 +1865,44 @@ bool Session::deleteTorrent(const InfoHash &hash, const DeleteOption deleteOptio
|
||||
}
|
||||
|
||||
// Remove it from torrent resume directory
|
||||
const QString resumedataFile = QString::fromLatin1("%1.fastresume").arg(torrent->hash());
|
||||
const QString metadataFile = QString::fromLatin1("%1.torrent").arg(torrent->hash());
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
const QString resumedataFile = QString::fromLatin1("%1.fastresume").arg(torrent->id().toString());
|
||||
const QString metadataFile = QString::fromLatin1("%1.torrent").arg(torrent->id().toString());
|
||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, [this, resumedataFile, metadataFile]()
|
||||
{
|
||||
m_resumeDataSavingManager->remove(resumedataFile);
|
||||
m_resumeDataSavingManager->remove(metadataFile);
|
||||
});
|
||||
#else
|
||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, "remove", Q_ARG(QString, resumedataFile));
|
||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, "remove", Q_ARG(QString, metadataFile));
|
||||
#endif
|
||||
|
||||
delete torrent;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Session::cancelDownloadMetadata(const InfoHash &hash)
|
||||
bool Session::cancelDownloadMetadata(const TorrentID &id)
|
||||
{
|
||||
const auto downloadedMetadataIter = m_downloadedMetadata.find(hash);
|
||||
const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
|
||||
if (downloadedMetadataIter == m_downloadedMetadata.end()) return false;
|
||||
|
||||
m_downloadedMetadata.erase(downloadedMetadataIter);
|
||||
--m_extraLimit;
|
||||
adjustLimits();
|
||||
m_nativeSession->remove_torrent(m_nativeSession->find_torrent(hash), lt::session::delete_files);
|
||||
m_nativeSession->remove_torrent(m_nativeSession->find_torrent(id), lt::session::delete_files);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Session::increaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
||||
void Session::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
|
||||
{
|
||||
using ElementType = std::pair<int, TorrentImpl *>;
|
||||
using ElementType = std::pair<int, const TorrentImpl *>;
|
||||
std::priority_queue<ElementType
|
||||
, std::vector<ElementType>
|
||||
, std::greater<ElementType>> torrentQueue;
|
||||
|
||||
// Sort torrents by queue position
|
||||
for (const InfoHash &infoHash : hashes)
|
||||
for (const TorrentID &id : ids)
|
||||
{
|
||||
TorrentImpl *const torrent = m_torrents.value(infoHash);
|
||||
if (torrent && !torrent->isSeed())
|
||||
torrentQueue.emplace(torrent->queuePosition(), torrent);
|
||||
const TorrentImpl *torrent = m_torrents.value(id);
|
||||
if (!torrent) continue;
|
||||
if (const int position = torrent->queuePosition(); position >= 0)
|
||||
torrentQueue.emplace(position, torrent);
|
||||
}
|
||||
|
||||
// Increase torrents queue position (starting with the one in the highest queue position)
|
||||
@@ -1911,17 +1916,18 @@ void Session::increaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
||||
saveTorrentsQueue();
|
||||
}
|
||||
|
||||
void Session::decreaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
||||
void Session::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
|
||||
{
|
||||
using ElementType = std::pair<int, TorrentImpl *>;
|
||||
using ElementType = std::pair<int, const TorrentImpl *>;
|
||||
std::priority_queue<ElementType> torrentQueue;
|
||||
|
||||
// Sort torrents by queue position
|
||||
for (const InfoHash &infoHash : hashes)
|
||||
for (const TorrentID &id : ids)
|
||||
{
|
||||
TorrentImpl *const torrent = m_torrents.value(infoHash);
|
||||
if (torrent && !torrent->isSeed())
|
||||
torrentQueue.emplace(torrent->queuePosition(), torrent);
|
||||
const TorrentImpl *torrent = m_torrents.value(id);
|
||||
if (!torrent) continue;
|
||||
if (const int position = torrent->queuePosition(); position >= 0)
|
||||
torrentQueue.emplace(position, torrent);
|
||||
}
|
||||
|
||||
// Decrease torrents queue position (starting with the one in the lowest queue position)
|
||||
@@ -1938,17 +1944,18 @@ void Session::decreaseTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
||||
saveTorrentsQueue();
|
||||
}
|
||||
|
||||
void Session::topTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
||||
void Session::topTorrentsQueuePos(const QVector<TorrentID> &ids)
|
||||
{
|
||||
using ElementType = std::pair<int, TorrentImpl *>;
|
||||
using ElementType = std::pair<int, const TorrentImpl *>;
|
||||
std::priority_queue<ElementType> torrentQueue;
|
||||
|
||||
// Sort torrents by queue position
|
||||
for (const InfoHash &infoHash : hashes)
|
||||
for (const TorrentID &id : ids)
|
||||
{
|
||||
TorrentImpl *const torrent = m_torrents.value(infoHash);
|
||||
if (torrent && !torrent->isSeed())
|
||||
torrentQueue.emplace(torrent->queuePosition(), torrent);
|
||||
const TorrentImpl *torrent = m_torrents.value(id);
|
||||
if (!torrent) continue;
|
||||
if (const int position = torrent->queuePosition(); position >= 0)
|
||||
torrentQueue.emplace(position, torrent);
|
||||
}
|
||||
|
||||
// Top torrents queue position (starting with the one in the lowest queue position)
|
||||
@@ -1962,19 +1969,20 @@ void Session::topTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
||||
saveTorrentsQueue();
|
||||
}
|
||||
|
||||
void Session::bottomTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
||||
void Session::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
|
||||
{
|
||||
using ElementType = std::pair<int, TorrentImpl *>;
|
||||
using ElementType = std::pair<int, const TorrentImpl *>;
|
||||
std::priority_queue<ElementType
|
||||
, std::vector<ElementType>
|
||||
, std::greater<ElementType>> torrentQueue;
|
||||
|
||||
// Sort torrents by queue position
|
||||
for (const InfoHash &infoHash : hashes)
|
||||
for (const TorrentID &id : ids)
|
||||
{
|
||||
TorrentImpl *const torrent = m_torrents.value(infoHash);
|
||||
if (torrent && !torrent->isSeed())
|
||||
torrentQueue.emplace(torrent->queuePosition(), torrent);
|
||||
const TorrentImpl *torrent = m_torrents.value(id);
|
||||
if (!torrent) continue;
|
||||
if (const int position = torrent->queuePosition(); position >= 0)
|
||||
torrentQueue.emplace(position, torrent);
|
||||
}
|
||||
|
||||
// Bottom torrents queue position (starting with the one in the highest queue position)
|
||||
@@ -1991,6 +1999,25 @@ void Session::bottomTorrentsQueuePos(const QVector<InfoHash> &hashes)
|
||||
saveTorrentsQueue();
|
||||
}
|
||||
|
||||
void Session::handleTorrentNeedSaveResumeData(const TorrentImpl *torrent)
|
||||
{
|
||||
if (m_needSaveResumeDataTorrents.empty())
|
||||
{
|
||||
QMetaObject::invokeMethod(this, [this]()
|
||||
{
|
||||
for (const TorrentID &torrentID : asConst(m_needSaveResumeDataTorrents))
|
||||
{
|
||||
TorrentImpl *torrent = m_torrents.value(torrentID);
|
||||
if (torrent)
|
||||
torrent->saveResumeData();
|
||||
}
|
||||
m_needSaveResumeDataTorrents.clear();
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
m_needSaveResumeDataTorrents.insert(torrent->id());
|
||||
}
|
||||
|
||||
void Session::handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent)
|
||||
{
|
||||
qDebug("Saving resume data is requested for torrent '%s'...", qUtf8Printable(torrent->name()));
|
||||
@@ -2086,20 +2113,20 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
|
||||
const bool hasMetadata = std::holds_alternative<TorrentInfo>(source);
|
||||
TorrentInfo metadata = (hasMetadata ? std::get<TorrentInfo>(source) : TorrentInfo {});
|
||||
const MagnetUri &magnetUri = (hasMetadata ? MagnetUri {} : std::get<MagnetUri>(source));
|
||||
const InfoHash hash = (hasMetadata ? metadata.hash() : magnetUri.hash());
|
||||
const auto id = TorrentID::fromInfoHash(hasMetadata ? metadata.infoHash() : magnetUri.infoHash());
|
||||
|
||||
// It looks illogical that we don't just use an existing handle,
|
||||
// but as previous experience has shown, it actually creates unnecessary
|
||||
// problems and unwanted behavior due to the fact that it was originally
|
||||
// added with parameters other than those provided by the user.
|
||||
cancelDownloadMetadata(hash);
|
||||
cancelDownloadMetadata(id);
|
||||
|
||||
// We should not add the torrent if it is already
|
||||
// processed or is pending to add to session
|
||||
if (m_loadingTorrents.contains(hash))
|
||||
if (m_loadingTorrents.contains(id))
|
||||
return false;
|
||||
|
||||
TorrentImpl *const torrent = m_torrents.value(hash);
|
||||
TorrentImpl *const torrent = m_torrents.value(id);
|
||||
if (torrent)
|
||||
{ // a duplicate torrent is added
|
||||
if (torrent->isPrivate() || (hasMetadata && metadata.isPrivate()))
|
||||
@@ -2190,7 +2217,7 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
|
||||
if (!isFindingIncompleteFiles)
|
||||
return loadTorrent(loadTorrentParams);
|
||||
|
||||
m_loadingTorrents.insert(hash, loadTorrentParams);
|
||||
m_loadingTorrents.insert(id, loadTorrentParams);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2207,8 +2234,12 @@ bool Session::loadTorrent(LoadTorrentParams params)
|
||||
p.max_uploads = maxUploadsPerTorrent();
|
||||
|
||||
const bool hasMetadata = (p.ti && p.ti->is_valid());
|
||||
const InfoHash hash = (hasMetadata ? p.ti->info_hash() : p.info_hash);
|
||||
m_loadingTorrents.insert(hash, params);
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hashes() : p.info_hashes);
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(hasMetadata ? p.ti->info_hash() : p.info_hash);
|
||||
#endif
|
||||
m_loadingTorrents.insert(id, params);
|
||||
|
||||
// Adding torrent to BitTorrent session
|
||||
m_nativeSession->async_add_torrent(p);
|
||||
@@ -2218,20 +2249,14 @@ bool Session::loadTorrent(LoadTorrentParams params)
|
||||
|
||||
void Session::findIncompleteFiles(const TorrentInfo &torrentInfo, const QString &savePath) const
|
||||
{
|
||||
const InfoHash searchId = torrentInfo.hash();
|
||||
const auto searchId = TorrentID::fromInfoHash(torrentInfo.infoHash());
|
||||
const QStringList originalFileNames = torrentInfo.filePaths();
|
||||
const QString completeSavePath = savePath;
|
||||
const QString incompleteSavePath = (isTempPathEnabled() ? torrentTempPath(torrentInfo) : QString {});
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
QMetaObject::invokeMethod(m_fileSearcher, [=]()
|
||||
{
|
||||
m_fileSearcher->search(searchId, originalFileNames, completeSavePath, incompleteSavePath);
|
||||
});
|
||||
#else
|
||||
QMetaObject::invokeMethod(m_fileSearcher, "search"
|
||||
, Q_ARG(BitTorrent::InfoHash, searchId), Q_ARG(QStringList, originalFileNames)
|
||||
, Q_ARG(QString, completeSavePath), Q_ARG(QString, incompleteSavePath));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Add a torrent to libtorrent session in hidden mode
|
||||
@@ -2240,17 +2265,17 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri)
|
||||
{
|
||||
if (!magnetUri.isValid()) return false;
|
||||
|
||||
const InfoHash hash = magnetUri.hash();
|
||||
const auto id = TorrentID::fromInfoHash(magnetUri.infoHash());
|
||||
const QString name = magnetUri.name();
|
||||
|
||||
// We should not add torrent if it's already
|
||||
// processed or adding to session
|
||||
if (m_torrents.contains(hash)) return false;
|
||||
if (m_loadingTorrents.contains(hash)) return false;
|
||||
if (m_downloadedMetadata.contains(hash)) return false;
|
||||
if (m_torrents.contains(id)) return false;
|
||||
if (m_loadingTorrents.contains(id)) return false;
|
||||
if (m_downloadedMetadata.contains(id)) return false;
|
||||
|
||||
qDebug("Adding torrent to preload metadata...");
|
||||
qDebug(" -> Hash: %s", qUtf8Printable(hash));
|
||||
qDebug(" -> Torrent ID: %s", qUtf8Printable(id.toString()));
|
||||
qDebug(" -> Name: %s", qUtf8Printable(name));
|
||||
|
||||
lt::add_torrent_params p = magnetUri.addTorrentParams();
|
||||
@@ -2266,7 +2291,7 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri)
|
||||
p.max_connections = maxConnectionsPerTorrent();
|
||||
p.max_uploads = maxUploadsPerTorrent();
|
||||
|
||||
const QString savePath = Utils::Fs::tempPath() + static_cast<QString>(hash);
|
||||
const QString savePath = Utils::Fs::tempPath() + id.toString();
|
||||
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
||||
|
||||
// Forced start
|
||||
@@ -2299,7 +2324,7 @@ void Session::exportTorrentFile(const Torrent *torrent, TorrentExportFolder fold
|
||||
((folder == TorrentExportFolder::Finished) && !finishedTorrentExportDirectory().isEmpty()));
|
||||
|
||||
const QString validName = Utils::Fs::toValidFileSystemName(torrent->name());
|
||||
const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(torrent->hash());
|
||||
const QString torrentFilename = QString::fromLatin1("%1.torrent").arg(torrent->id().toString());
|
||||
QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName);
|
||||
const QString torrentPath = QDir(m_resumeFolderPath).absoluteFilePath(torrentFilename);
|
||||
const QDir exportPath(folder == TorrentExportFolder::Regular ? torrentExportDirectory() : finishedTorrentExportDirectory());
|
||||
@@ -2326,7 +2351,10 @@ void Session::generateResumeData()
|
||||
if (!torrent->isValid()) continue;
|
||||
|
||||
if (torrent->needSaveResumeData())
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
m_needSaveResumeDataTorrents.remove(torrent->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2363,7 +2391,7 @@ void Session::saveResumeData()
|
||||
}
|
||||
}
|
||||
|
||||
void Session::saveTorrentsQueue()
|
||||
void Session::saveTorrentsQueue() const
|
||||
{
|
||||
// store hash in textual representation
|
||||
QMap<int, QString> queue; // Use QMap since it should be ordered by key
|
||||
@@ -2372,33 +2400,24 @@ void Session::saveTorrentsQueue()
|
||||
// We require actual (non-cached) queue position here!
|
||||
const int queuePos = static_cast<LTUnderlyingType<lt::queue_position_t>>(torrent->nativeHandle().queue_position());
|
||||
if (queuePos >= 0)
|
||||
queue[queuePos] = torrent->hash();
|
||||
queue[queuePos] = torrent->id().toString();
|
||||
}
|
||||
|
||||
QByteArray data;
|
||||
data.reserve(((InfoHash::length() * 2) + 1) * queue.size());
|
||||
for (const QString &hash : asConst(queue))
|
||||
data += (hash.toLatin1() + '\n');
|
||||
data.reserve(((TorrentID::length() * 2) + 1) * queue.size());
|
||||
for (const QString &torrentID : asConst(queue))
|
||||
data += (torrentID.toLatin1() + '\n');
|
||||
|
||||
const QString filename = QLatin1String {"queue"};
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
QMetaObject::invokeMethod(m_resumeDataSavingManager
|
||||
, [this, data, filename]() { m_resumeDataSavingManager->save(filename, data); });
|
||||
#else
|
||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, "save"
|
||||
, Q_ARG(QString, filename), Q_ARG(QByteArray, data));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Session::removeTorrentsQueue()
|
||||
void Session::removeTorrentsQueue() const
|
||||
{
|
||||
const QString filename = QLatin1String {"queue"};
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
QMetaObject::invokeMethod(m_resumeDataSavingManager
|
||||
, [this, filename]() { m_resumeDataSavingManager->remove(filename); });
|
||||
#else
|
||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, "remove", Q_ARG(QString, filename));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Session::setDefaultSavePath(QString path)
|
||||
@@ -3516,6 +3535,20 @@ void Session::setUPnPLeaseDuration(const int duration)
|
||||
}
|
||||
}
|
||||
|
||||
int Session::peerToS() const
|
||||
{
|
||||
return m_peerToS;
|
||||
}
|
||||
|
||||
void Session::setPeerToS(const int value)
|
||||
{
|
||||
if (value == m_peerToS)
|
||||
return;
|
||||
|
||||
m_peerToS = value;
|
||||
configureDeferred();
|
||||
}
|
||||
|
||||
bool Session::ignoreLimitsOnLAN() const
|
||||
{
|
||||
return m_ignoreLimitsOnLAN;
|
||||
@@ -3742,11 +3775,11 @@ void Session::setMaxRatioAction(const MaxRatioAction act)
|
||||
|
||||
// If this functions returns true, we cannot add torrent to session,
|
||||
// but it is still possible to merge trackers in some cases
|
||||
bool Session::isKnownTorrent(const InfoHash &hash) const
|
||||
bool Session::isKnownTorrent(const TorrentID &id) const
|
||||
{
|
||||
return (m_torrents.contains(hash)
|
||||
|| m_loadingTorrents.contains(hash)
|
||||
|| m_downloadedMetadata.contains(hash));
|
||||
return (m_torrents.contains(id)
|
||||
|| m_loadingTorrents.contains(id)
|
||||
|| m_downloadedMetadata.contains(id));
|
||||
}
|
||||
|
||||
void Session::updateSeedingLimitTimer()
|
||||
@@ -3765,51 +3798,43 @@ void Session::updateSeedingLimitTimer()
|
||||
|
||||
void Session::handleTorrentShareLimitChanged(TorrentImpl *const torrent)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
updateSeedingLimitTimer();
|
||||
}
|
||||
|
||||
void Session::handleTorrentNameChanged(TorrentImpl *const torrent)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
Q_UNUSED(torrent);
|
||||
}
|
||||
|
||||
void Session::handleTorrentSavePathChanged(TorrentImpl *const torrent)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
emit torrentSavePathChanged(torrent);
|
||||
}
|
||||
|
||||
void Session::handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
emit torrentCategoryChanged(torrent, oldCategory);
|
||||
}
|
||||
|
||||
void Session::handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
emit torrentTagAdded(torrent, tag);
|
||||
}
|
||||
|
||||
void Session::handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
emit torrentTagRemoved(torrent, tag);
|
||||
}
|
||||
|
||||
void Session::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
emit torrentSavingModeChanged(torrent);
|
||||
}
|
||||
|
||||
void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
|
||||
for (const TrackerEntry &newTracker : newTrackers)
|
||||
LogMsg(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url(), torrent->name()));
|
||||
LogMsg(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url, torrent->name()));
|
||||
emit trackersAdded(torrent, newTrackers);
|
||||
if (torrent->trackers().size() == newTrackers.size())
|
||||
emit trackerlessStateChanged(torrent, false);
|
||||
@@ -3818,10 +3843,8 @@ void Session::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVect
|
||||
|
||||
void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVector<TrackerEntry> &deletedTrackers)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
|
||||
for (const TrackerEntry &deletedTracker : deletedTrackers)
|
||||
LogMsg(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url(), torrent->name()));
|
||||
LogMsg(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url, torrent->name()));
|
||||
emit trackersRemoved(torrent, deletedTrackers);
|
||||
if (torrent->trackers().empty())
|
||||
emit trackerlessStateChanged(torrent, true);
|
||||
@@ -3830,20 +3853,17 @@ void Session::handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QVe
|
||||
|
||||
void Session::handleTorrentTrackersChanged(TorrentImpl *const torrent)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
emit trackersChanged(torrent);
|
||||
}
|
||||
|
||||
void Session::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
for (const QUrl &newUrlSeed : newUrlSeeds)
|
||||
LogMsg(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name()));
|
||||
}
|
||||
|
||||
void Session::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
for (const QUrl &urlSeed : urlSeeds)
|
||||
LogMsg(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name()));
|
||||
}
|
||||
@@ -3852,7 +3872,7 @@ void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
|
||||
{
|
||||
// Save metadata
|
||||
const QDir resumeDataDir {m_resumeFolderPath};
|
||||
const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->hash())};
|
||||
const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->id().toString())};
|
||||
try
|
||||
{
|
||||
torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName));
|
||||
@@ -3871,13 +3891,11 @@ void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
|
||||
|
||||
void Session::handleTorrentPaused(TorrentImpl *const torrent)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
emit torrentPaused(torrent);
|
||||
}
|
||||
|
||||
void Session::handleTorrentResumed(TorrentImpl *const torrent)
|
||||
{
|
||||
torrent->saveResumeData();
|
||||
emit torrentResumed(torrent);
|
||||
}
|
||||
|
||||
@@ -3888,8 +3906,6 @@ void Session::handleTorrentChecked(TorrentImpl *const torrent)
|
||||
|
||||
void Session::handleTorrentFinished(TorrentImpl *const torrent)
|
||||
{
|
||||
if (!torrent->hasError() && !torrent->hasMissingFiles())
|
||||
torrent->saveResumeData();
|
||||
emit torrentFinished(torrent);
|
||||
|
||||
qDebug("Checking if the torrent contains torrent files to download");
|
||||
@@ -3925,21 +3941,52 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent)
|
||||
emit allTorrentsFinished();
|
||||
}
|
||||
|
||||
void Session::handleTorrentResumeDataReady(TorrentImpl *const torrent, const std::shared_ptr<lt::entry> &data)
|
||||
void Session::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
|
||||
{
|
||||
--m_numResumeData;
|
||||
|
||||
// We need to adjust native libtorrent resume data
|
||||
lt::add_torrent_params p = data.ltAddTorrentParams;
|
||||
p.save_path = Profile::instance()->toPortablePath(QString::fromStdString(p.save_path)).toStdString();
|
||||
if (data.paused)
|
||||
{
|
||||
p.flags |= lt::torrent_flags::paused;
|
||||
p.flags &= ~lt::torrent_flags::auto_managed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Torrent can be actually "running" but temporarily "paused" to perform some
|
||||
// service jobs behind the scenes so we need to restore it as "running"
|
||||
if (!data.forced)
|
||||
{
|
||||
p.flags |= lt::torrent_flags::auto_managed;
|
||||
}
|
||||
else
|
||||
{
|
||||
p.flags &= ~lt::torrent_flags::paused;
|
||||
p.flags &= ~lt::torrent_flags::auto_managed;
|
||||
}
|
||||
}
|
||||
|
||||
// Separated thread is used for the blocking IO which results in slow processing of many torrents.
|
||||
// Copying lt::entry objects around isn't cheap.
|
||||
|
||||
const QString filename = QString::fromLatin1("%1.fastresume").arg(torrent->hash());
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
auto resumeDataPtr = std::make_shared<lt::entry>(lt::write_resume_data(p));
|
||||
lt::entry &resumeData = *resumeDataPtr;
|
||||
|
||||
resumeData["qBt-savePath"] = Profile::instance()->toPortablePath(data.savePath).toStdString();
|
||||
resumeData["qBt-ratioLimit"] = static_cast<int>(data.ratioLimit * 1000);
|
||||
resumeData["qBt-seedingTimeLimit"] = data.seedingTimeLimit;
|
||||
resumeData["qBt-category"] = data.category.toStdString();
|
||||
resumeData["qBt-tags"] = setToEntryList(data.tags);
|
||||
resumeData["qBt-name"] = data.name.toStdString();
|
||||
resumeData["qBt-seedStatus"] = data.hasSeedStatus;
|
||||
resumeData["qBt-contentLayout"] = Utils::String::fromEnum(data.contentLayout).toStdString();
|
||||
resumeData["qBt-firstLastPiecePriority"] = data.firstLastPiecePriority;
|
||||
|
||||
const QString filename = QString::fromLatin1("%1.fastresume").arg(torrent->id().toString());
|
||||
QMetaObject::invokeMethod(m_resumeDataSavingManager
|
||||
, [this, filename, data]() { m_resumeDataSavingManager->save(filename, data); });
|
||||
#else
|
||||
QMetaObject::invokeMethod(m_resumeDataSavingManager, "save"
|
||||
, Q_ARG(QString, filename), Q_ARG(std::shared_ptr<lt::entry>, data));
|
||||
#endif
|
||||
, [this, filename, resumeDataPtr]() { m_resumeDataSavingManager->save(filename, resumeDataPtr); });
|
||||
}
|
||||
|
||||
void Session::handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl)
|
||||
@@ -4008,9 +4055,13 @@ bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newP
|
||||
|
||||
void Session::moveTorrentStorage(const MoveStorageJob &job) const
|
||||
{
|
||||
const InfoHash infoHash = job.torrentHandle.info_hash();
|
||||
const TorrentImpl *torrent = m_torrents.value(infoHash);
|
||||
const QString torrentName = (torrent ? torrent->name() : QString {infoHash});
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hashes());
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(job.torrentHandle.info_hash());
|
||||
#endif
|
||||
const TorrentImpl *torrent = m_torrents.value(id);
|
||||
const QString torrentName = (torrent ? torrent->name() : id.toString());
|
||||
LogMsg(tr("Moving \"%1\" to \"%2\"...").arg(torrentName, job.path));
|
||||
|
||||
job.torrentHandle.move_storage(job.path.toUtf8().constData()
|
||||
@@ -4094,15 +4145,9 @@ void Session::configureDeferred()
|
||||
{
|
||||
if (m_deferredConfigureScheduled)
|
||||
return;
|
||||
m_deferredConfigureScheduled = true;
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
QMetaObject::invokeMethod(this
|
||||
, qOverload<>(&Session::configure)
|
||||
, Qt::QueuedConnection);
|
||||
#else
|
||||
QMetaObject::invokeMethod(this, "configure", Qt::QueuedConnection);
|
||||
#endif
|
||||
m_deferredConfigureScheduled = true;
|
||||
QMetaObject::invokeMethod(this, qOverload<>(&Session::configure), Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
// Enable IP Filtering
|
||||
@@ -4141,9 +4186,9 @@ void Session::disableIPFilter()
|
||||
m_nativeSession->set_ip_filter(filter);
|
||||
}
|
||||
|
||||
void Session::recursiveTorrentDownload(const InfoHash &hash)
|
||||
void Session::recursiveTorrentDownload(const TorrentID &id)
|
||||
{
|
||||
TorrentImpl *const torrent = m_torrents.value(hash);
|
||||
TorrentImpl *const torrent = m_torrents.value(id);
|
||||
if (!torrent) return;
|
||||
|
||||
for (int i = 0; i < torrent->filesCount(); ++i)
|
||||
@@ -4259,51 +4304,7 @@ bool Session::loadTorrentResumeData(const QByteArray &data, const TorrentInfo &m
|
||||
|
||||
const bool hasMetadata = (p.ti && p.ti->is_valid());
|
||||
if (!hasMetadata && !root.dict_find("info-hash"))
|
||||
{
|
||||
// TODO: The following code is deprecated. Remove after several releases in 4.3.x.
|
||||
// === BEGIN DEPRECATED CODE === //
|
||||
// Try to load from legacy data used in older versions for torrents w/o metadata
|
||||
const lt::bdecode_node magnetURINode = root.dict_find("qBt-magnetUri");
|
||||
if (magnetURINode.type() == lt::bdecode_node::string_t)
|
||||
{
|
||||
lt::parse_magnet_uri(magnetURINode.string_value(), p, ec);
|
||||
|
||||
if (isTempPathEnabled())
|
||||
{
|
||||
p.save_path = Utils::Fs::toNativePath(tempPath()).toStdString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If empty then Automatic mode, otherwise Manual mode
|
||||
const QString savePath = torrentParams.savePath.isEmpty() ? categorySavePath(torrentParams.category) : torrentParams.savePath;
|
||||
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
||||
}
|
||||
|
||||
// Preallocation mode
|
||||
p.storage_mode = (isPreallocationEnabled() ? lt::storage_mode_allocate : lt::storage_mode_sparse);
|
||||
|
||||
const lt::bdecode_node addedTimeNode = root.dict_find("qBt-addedTime");
|
||||
if (addedTimeNode.type() == lt::bdecode_node::int_t)
|
||||
p.added_time = addedTimeNode.int_value();
|
||||
|
||||
const lt::bdecode_node sequentialNode = root.dict_find("qBt-sequential");
|
||||
if (sequentialNode.type() == lt::bdecode_node::int_t)
|
||||
{
|
||||
if (static_cast<bool>(sequentialNode.int_value()))
|
||||
p.flags |= lt::torrent_flags::sequential_download;
|
||||
else
|
||||
p.flags &= ~lt::torrent_flags::sequential_download;
|
||||
}
|
||||
|
||||
if (torrentParams.name.isEmpty() && !p.name.empty())
|
||||
torrentParams.name = QString::fromStdString(p.name);
|
||||
}
|
||||
// === END DEPRECATED CODE === //
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -4568,7 +4569,7 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle)
|
||||
const LoadTorrentParams params = m_loadingTorrents.take(nativeHandle.info_hash());
|
||||
|
||||
auto *const torrent = new TorrentImpl {this, m_nativeSession, nativeHandle, params};
|
||||
m_torrents.insert(torrent->hash(), torrent);
|
||||
m_torrents.insert(torrent->id(), torrent);
|
||||
|
||||
const bool hasMetadata = torrent->hasMetadata();
|
||||
|
||||
@@ -4583,7 +4584,7 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle)
|
||||
{
|
||||
// Backup torrent file
|
||||
const QDir resumeDataDir {m_resumeFolderPath};
|
||||
const QString torrentFileName {QString {"%1.torrent"}.arg(torrent->hash())};
|
||||
const QString torrentFileName {QString::fromLatin1("%1.torrent").arg(torrent->id().toString())};
|
||||
try
|
||||
{
|
||||
torrent->info().saveToFile(resumeDataDir.absoluteFilePath(torrentFileName));
|
||||
@@ -4641,9 +4642,13 @@ void Session::handleAddTorrentAlert(const lt::add_torrent_alert *p)
|
||||
|
||||
void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
|
||||
{
|
||||
const InfoHash infoHash {p->info_hash};
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
const auto id = TorrentID::fromInfoHash(p->info_hashes);
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(p->info_hash);
|
||||
#endif
|
||||
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(infoHash);
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
||||
if (removingTorrentDataIter != m_removingTorrents.end())
|
||||
{
|
||||
if (removingTorrentDataIter->deleteOption == DeleteTorrent)
|
||||
@@ -4656,8 +4661,13 @@ void Session::handleTorrentRemovedAlert(const lt::torrent_removed_alert *p)
|
||||
|
||||
void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
|
||||
{
|
||||
const InfoHash infoHash {p->info_hash};
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(infoHash);
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
const auto id = TorrentID::fromInfoHash(p->info_hashes);
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(p->info_hash);
|
||||
#endif
|
||||
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
||||
|
||||
if (removingTorrentDataIter == m_removingTorrents.end())
|
||||
return;
|
||||
@@ -4669,8 +4679,13 @@ void Session::handleTorrentDeletedAlert(const lt::torrent_deleted_alert *p)
|
||||
|
||||
void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_alert *p)
|
||||
{
|
||||
const InfoHash infoHash {p->info_hash};
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(infoHash);
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
const auto id = TorrentID::fromInfoHash(p->info_hashes);
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(p->info_hash);
|
||||
#endif
|
||||
|
||||
const auto removingTorrentDataIter = m_removingTorrents.find(id);
|
||||
|
||||
if (removingTorrentDataIter == m_removingTorrents.end())
|
||||
return;
|
||||
@@ -4694,8 +4709,13 @@ void Session::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed_ale
|
||||
|
||||
void Session::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
|
||||
{
|
||||
const InfoHash hash {p->handle.info_hash()};
|
||||
const auto downloadedMetadataIter = m_downloadedMetadata.find(hash);
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
const auto id = TorrentID::fromInfoHash(p->handle.info_hashes());
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(p->handle.info_hash());
|
||||
#endif
|
||||
|
||||
const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
|
||||
|
||||
if (downloadedMetadataIter != m_downloadedMetadata.end())
|
||||
{
|
||||
@@ -4716,11 +4736,11 @@ void Session::handleFileErrorAlert(const lt::file_error_alert *p)
|
||||
if (!torrent)
|
||||
return;
|
||||
|
||||
const InfoHash hash = torrent->hash();
|
||||
const TorrentID id = torrent->id();
|
||||
|
||||
if (!m_recentErroredTorrents.contains(hash))
|
||||
if (!m_recentErroredTorrents.contains(id))
|
||||
{
|
||||
m_recentErroredTorrents.insert(hash);
|
||||
m_recentErroredTorrents.insert(id);
|
||||
|
||||
const QString msg = QString::fromStdString(p->message());
|
||||
LogMsg(tr("File error alert. Torrent: \"%1\". File: \"%2\". Reason: %3")
|
||||
@@ -4919,9 +4939,14 @@ void Session::handleStorageMovedAlert(const lt::storage_moved_alert *p)
|
||||
const QString newPath {p->storage_path()};
|
||||
Q_ASSERT(newPath == currentJob.path);
|
||||
|
||||
const InfoHash infoHash = currentJob.torrentHandle.info_hash();
|
||||
TorrentImpl *torrent = m_torrents.value(infoHash);
|
||||
const QString torrentName = (torrent ? torrent->name() : QString {infoHash});
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
|
||||
#endif
|
||||
|
||||
TorrentImpl *torrent = m_torrents.value(id);
|
||||
const QString torrentName = (torrent ? torrent->name() : id.toString());
|
||||
LogMsg(tr("\"%1\" is successfully moved to \"%2\".").arg(torrentName, newPath));
|
||||
|
||||
handleMoveTorrentStorageJobFinished();
|
||||
@@ -4934,9 +4959,14 @@ void Session::handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert
|
||||
const MoveStorageJob ¤tJob = m_moveStorageQueue.first();
|
||||
Q_ASSERT(currentJob.torrentHandle == p->handle);
|
||||
|
||||
const InfoHash infoHash = currentJob.torrentHandle.info_hash();
|
||||
TorrentImpl *torrent = m_torrents.value(infoHash);
|
||||
const QString torrentName = (torrent ? torrent->name() : QString {infoHash});
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hashes());
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(currentJob.torrentHandle.info_hash());
|
||||
#endif
|
||||
|
||||
TorrentImpl *torrent = m_torrents.value(id);
|
||||
const QString torrentName = (torrent ? torrent->name() : id.toString());
|
||||
const QString currentLocation = QString::fromStdString(p->handle.status(lt::torrent_handle::query_save_path).save_path);
|
||||
const QString errorMessage = QString::fromStdString(p->message());
|
||||
LogMsg(tr("Failed to move \"%1\" from \"%2\" to \"%3\". Reason: %4.")
|
||||
@@ -4952,8 +4982,12 @@ void Session::handleStateUpdateAlert(const lt::state_update_alert *p)
|
||||
|
||||
for (const lt::torrent_status &status : p->status)
|
||||
{
|
||||
TorrentImpl *const torrent = m_torrents.value(status.info_hash);
|
||||
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
const auto id = TorrentID::fromInfoHash(status.info_hashes);
|
||||
#else
|
||||
const auto id = TorrentID::fromInfoHash(status.info_hash);
|
||||
#endif
|
||||
TorrentImpl *const torrent = m_torrents.value(id);
|
||||
if (!torrent)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -51,14 +51,6 @@
|
||||
#include "sessionstatus.h"
|
||||
#include "torrentinfo.h"
|
||||
|
||||
#if !defined(Q_OS_WIN) || (LIBTORRENT_VERSION_NUM >= 10212)
|
||||
#define HAS_HTTPS_TRACKER_VALIDATION
|
||||
#endif
|
||||
|
||||
#if ((LIBTORRENT_VERSION_NUM >= 10212) && (LIBTORRENT_VERSION_NUM < 20000)) || (LIBTORRENT_VERSION_NUM >= 20002)
|
||||
#define HAS_IDN_SUPPORT
|
||||
#endif
|
||||
|
||||
class QFile;
|
||||
class QNetworkConfiguration;
|
||||
class QNetworkConfigurationManager;
|
||||
@@ -107,8 +99,8 @@ namespace BitTorrent
|
||||
class Torrent;
|
||||
class TorrentImpl;
|
||||
class Tracker;
|
||||
class TrackerEntry;
|
||||
struct LoadTorrentParams;
|
||||
struct TrackerEntry;
|
||||
|
||||
enum class MoveStorageMode;
|
||||
|
||||
@@ -392,6 +384,8 @@ namespace BitTorrent
|
||||
void setOutgoingPortsMax(int max);
|
||||
int UPnPLeaseDuration() const;
|
||||
void setUPnPLeaseDuration(int duration);
|
||||
int peerToS() const;
|
||||
void setPeerToS(int value);
|
||||
bool ignoreLimitsOnLAN() const;
|
||||
void setIgnoreLimitsOnLAN(bool ignore);
|
||||
bool includeOverheadInLimits() const;
|
||||
@@ -440,7 +434,7 @@ namespace BitTorrent
|
||||
#endif
|
||||
|
||||
void startUpTorrents();
|
||||
Torrent *findTorrent(const InfoHash &hash) const;
|
||||
Torrent *findTorrent(const TorrentID &id) const;
|
||||
QVector<Torrent *> torrents() const;
|
||||
bool hasActiveTorrents() const;
|
||||
bool hasUnfinishedTorrents() const;
|
||||
@@ -456,21 +450,22 @@ namespace BitTorrent
|
||||
|
||||
void banIP(const QString &ip);
|
||||
|
||||
bool isKnownTorrent(const InfoHash &hash) const;
|
||||
bool isKnownTorrent(const TorrentID &id) const;
|
||||
bool addTorrent(const QString &source, const AddTorrentParams ¶ms = AddTorrentParams());
|
||||
bool addTorrent(const MagnetUri &magnetUri, 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 cancelDownloadMetadata(const InfoHash &hash);
|
||||
bool cancelDownloadMetadata(const TorrentID &id);
|
||||
|
||||
void recursiveTorrentDownload(const InfoHash &hash);
|
||||
void increaseTorrentsQueuePos(const QVector<InfoHash> &hashes);
|
||||
void decreaseTorrentsQueuePos(const QVector<InfoHash> &hashes);
|
||||
void topTorrentsQueuePos(const QVector<InfoHash> &hashes);
|
||||
void bottomTorrentsQueuePos(const QVector<InfoHash> &hashes);
|
||||
void recursiveTorrentDownload(const TorrentID &id);
|
||||
void increaseTorrentsQueuePos(const QVector<TorrentID> &ids);
|
||||
void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids);
|
||||
void topTorrentsQueuePos(const QVector<TorrentID> &ids);
|
||||
void bottomTorrentsQueuePos(const QVector<TorrentID> &ids);
|
||||
|
||||
// Torrent interface
|
||||
void handleTorrentNeedSaveResumeData(const TorrentImpl *torrent);
|
||||
void handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent);
|
||||
void handleTorrentShareLimitChanged(TorrentImpl *const torrent);
|
||||
void handleTorrentNameChanged(TorrentImpl *const torrent);
|
||||
@@ -489,7 +484,7 @@ namespace BitTorrent
|
||||
void handleTorrentTrackersChanged(TorrentImpl *const torrent);
|
||||
void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds);
|
||||
void handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds);
|
||||
void handleTorrentResumeDataReady(TorrentImpl *const torrent, const std::shared_ptr<lt::entry> &data);
|
||||
void handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data);
|
||||
void handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl);
|
||||
void handleTorrentTrackerWarning(TorrentImpl *const torrent, const QString &trackerUrl);
|
||||
void handleTorrentTrackerError(TorrentImpl *const torrent, const QString &trackerUrl);
|
||||
@@ -545,7 +540,7 @@ namespace BitTorrent
|
||||
void handleIPFilterParsed(int ruleCount);
|
||||
void handleIPFilterError();
|
||||
void handleDownloadFinished(const Net::DownloadResult &result);
|
||||
void fileSearchFinished(const InfoHash &id, const QString &savePath, const QStringList &fileNames);
|
||||
void fileSearchFinished(const TorrentID &id, const QString &savePath, const QStringList &fileNames);
|
||||
|
||||
// Session reconfiguration triggers
|
||||
void networkOnlineStateChanged(bool online);
|
||||
@@ -632,8 +627,8 @@ namespace BitTorrent
|
||||
void createTorrent(const lt::torrent_handle &nativeHandle);
|
||||
|
||||
void saveResumeData();
|
||||
void saveTorrentsQueue();
|
||||
void removeTorrentsQueue();
|
||||
void saveTorrentsQueue() const;
|
||||
void removeTorrentsQueue() const;
|
||||
|
||||
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
|
||||
|
||||
@@ -681,6 +676,7 @@ namespace BitTorrent
|
||||
CachedSettingValue<int> m_outgoingPortsMin;
|
||||
CachedSettingValue<int> m_outgoingPortsMax;
|
||||
CachedSettingValue<int> m_UPnPLeaseDuration;
|
||||
CachedSettingValue<int> m_peerToS;
|
||||
CachedSettingValue<bool> m_ignoreLimitsOnLAN;
|
||||
CachedSettingValue<bool> m_includeOverheadInLimits;
|
||||
CachedSettingValue<QString> m_announceIP;
|
||||
@@ -769,17 +765,18 @@ namespace BitTorrent
|
||||
ResumeDataSavingManager *m_resumeDataSavingManager = nullptr;
|
||||
FileSearcher *m_fileSearcher = nullptr;
|
||||
|
||||
QSet<InfoHash> m_downloadedMetadata;
|
||||
QSet<TorrentID> m_downloadedMetadata;
|
||||
|
||||
QHash<InfoHash, TorrentImpl *> m_torrents;
|
||||
QHash<InfoHash, LoadTorrentParams> m_loadingTorrents;
|
||||
QHash<TorrentID, TorrentImpl *> m_torrents;
|
||||
QHash<TorrentID, LoadTorrentParams> m_loadingTorrents;
|
||||
QHash<QString, AddTorrentParams> m_downloadedTorrents;
|
||||
QHash<InfoHash, RemovingTorrentData> m_removingTorrents;
|
||||
QHash<TorrentID, RemovingTorrentData> m_removingTorrents;
|
||||
QSet<TorrentID> m_needSaveResumeDataTorrents;
|
||||
QStringMap m_categories;
|
||||
QSet<QString> m_tags;
|
||||
|
||||
// I/O errored torrents
|
||||
QSet<InfoHash> m_recentErroredTorrents;
|
||||
QSet<TorrentID> m_recentErroredTorrents;
|
||||
QTimer *m_recentErroredTorrentsTimer = nullptr;
|
||||
|
||||
SessionMetricIndices m_metricIndices;
|
||||
@@ -795,8 +792,3 @@ namespace BitTorrent
|
||||
static Session *m_instance;
|
||||
};
|
||||
}
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<lt::entry>)
|
||||
const int sharedPtrLtEntryTypeID = qRegisterMetaType<std::shared_ptr<lt::entry>>();
|
||||
#endif
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
|
||||
#include "torrent.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <QHash>
|
||||
|
||||
#include "infohash.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
uint qHash(const TorrentState key, const uint seed)
|
||||
@@ -42,15 +42,20 @@ namespace BitTorrent
|
||||
|
||||
// Torrent
|
||||
|
||||
const qreal Torrent::USE_GLOBAL_RATIO = -2.;
|
||||
const qreal Torrent::NO_RATIO_LIMIT = -1.;
|
||||
const qreal Torrent::USE_GLOBAL_RATIO = -2;
|
||||
const qreal Torrent::NO_RATIO_LIMIT = -1;
|
||||
|
||||
const int Torrent::USE_GLOBAL_SEEDING_TIME = -2;
|
||||
const int Torrent::NO_SEEDING_TIME_LIMIT = -1;
|
||||
|
||||
const qreal Torrent::MAX_RATIO = 9999.;
|
||||
const qreal Torrent::MAX_RATIO = 9999;
|
||||
const int Torrent::MAX_SEEDING_TIME = 525600;
|
||||
|
||||
TorrentID Torrent::id() const
|
||||
{
|
||||
return infoHash().toTorrentID();
|
||||
}
|
||||
|
||||
bool Torrent::isResumed() const
|
||||
{
|
||||
return !isPaused();
|
||||
|
||||
@@ -44,9 +44,10 @@ namespace BitTorrent
|
||||
enum class DownloadPriority;
|
||||
class InfoHash;
|
||||
class PeerInfo;
|
||||
class TorrentID;
|
||||
class TorrentInfo;
|
||||
class TrackerEntry;
|
||||
struct PeerAddress;
|
||||
struct TrackerEntry;
|
||||
|
||||
enum class TorrentOperatingMode
|
||||
{
|
||||
@@ -105,7 +106,7 @@ namespace BitTorrent
|
||||
|
||||
virtual ~Torrent() = default;
|
||||
|
||||
virtual InfoHash hash() const = 0;
|
||||
virtual InfoHash infoHash() const = 0;
|
||||
virtual QString name() const = 0;
|
||||
virtual QDateTime creationDate() const = 0;
|
||||
virtual QString creator() const = 0;
|
||||
@@ -290,6 +291,7 @@ namespace BitTorrent
|
||||
|
||||
virtual QString createMagnetURI() const = 0;
|
||||
|
||||
TorrentID id() const;
|
||||
bool isResumed() const;
|
||||
qlonglong remainingSize() const;
|
||||
|
||||
|
||||
@@ -100,8 +100,6 @@ void TorrentCreatorThread::sendProgressSignal(int currentPieceIdx, int totalPiec
|
||||
|
||||
void TorrentCreatorThread::run()
|
||||
{
|
||||
const QString creatorStr("qBittorrent " QBT_VERSION);
|
||||
|
||||
emit updateProgress(0);
|
||||
|
||||
try
|
||||
@@ -178,17 +176,19 @@ void TorrentCreatorThread::run()
|
||||
newTorrent.add_tracker(tracker.trimmed().toStdString(), tier);
|
||||
}
|
||||
|
||||
if (isInterruptionRequested()) return;
|
||||
|
||||
// calculate the hash for all pieces
|
||||
lt::set_piece_hashes(newTorrent, Utils::Fs::toNativePath(parentPath).toStdString()
|
||||
, [this, &newTorrent](const lt::piece_index_t n)
|
||||
{
|
||||
if (isInterruptionRequested())
|
||||
throw RuntimeError {tr("Create new torrent aborted.")};
|
||||
|
||||
sendProgressSignal(static_cast<LTUnderlyingType<lt::piece_index_t>>(n), newTorrent.num_pieces());
|
||||
});
|
||||
|
||||
// Set qBittorrent as creator and add user comment to
|
||||
// torrent_info structure
|
||||
newTorrent.set_creator(creatorStr.toUtf8().constData());
|
||||
newTorrent.set_creator("qBittorrent " QBT_VERSION);
|
||||
newTorrent.set_comment(m_params.comment.toUtf8().constData());
|
||||
// Is private ?
|
||||
newTorrent.set_priv(m_params.isPrivate);
|
||||
@@ -207,8 +207,7 @@ void TorrentCreatorThread::run()
|
||||
QFile outfile {m_params.savePath};
|
||||
if (!outfile.open(QIODevice::WriteOnly))
|
||||
{
|
||||
throw RuntimeError
|
||||
{tr("Create new torrent file failed. Reason: %1")
|
||||
throw RuntimeError {tr("Create new torrent file failed. Reason: %1")
|
||||
.arg(outfile.errorString())};
|
||||
}
|
||||
|
||||
@@ -217,8 +216,7 @@ void TorrentCreatorThread::run()
|
||||
lt::bencode(Utils::IO::FileDeviceOutputIterator {outfile}, entry);
|
||||
if (outfile.error() != QFileDevice::NoError)
|
||||
{
|
||||
throw RuntimeError
|
||||
{tr("Create new torrent file failed. Reason: %1")
|
||||
throw RuntimeError {tr("Create new torrent file failed. Reason: %1")
|
||||
.arg(outfile.errorString())};
|
||||
}
|
||||
outfile.close();
|
||||
|
||||
@@ -39,13 +39,15 @@
|
||||
|
||||
#include <libtorrent/address.hpp>
|
||||
#include <libtorrent/alert_types.hpp>
|
||||
#include <libtorrent/entry.hpp>
|
||||
#include <libtorrent/magnet_uri.hpp>
|
||||
#include <libtorrent/session.hpp>
|
||||
#include <libtorrent/storage_defs.hpp>
|
||||
#include <libtorrent/time.hpp>
|
||||
#include <libtorrent/version.hpp>
|
||||
#include <libtorrent/write_resume_data.hpp>
|
||||
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
#include <libtorrent/info_hash.hpp>
|
||||
#endif
|
||||
|
||||
#include <QBitArray>
|
||||
#include <QDebug>
|
||||
@@ -57,7 +59,6 @@
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/profile.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "common.h"
|
||||
@@ -87,14 +88,135 @@ namespace
|
||||
return out;
|
||||
}
|
||||
|
||||
using ListType = lt::entry::list_type;
|
||||
|
||||
ListType setToEntryList(const QSet<QString> &input)
|
||||
lt::announce_entry makeNativeAnnouncerEntry(const QString &url, const int tier)
|
||||
{
|
||||
ListType entryList;
|
||||
for (const QString &setValue : input)
|
||||
entryList.emplace_back(setValue.toStdString());
|
||||
return entryList;
|
||||
lt::announce_entry entry {url.toStdString()};
|
||||
entry.tier = tier;
|
||||
return entry;
|
||||
}
|
||||
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
TrackerEntry fromNativeAnnouncerEntry(const lt::announce_entry &nativeEntry, const lt::info_hash_t &hashes)
|
||||
#else
|
||||
TrackerEntry fromNativeAnnouncerEntry(const lt::announce_entry &nativeEntry)
|
||||
#endif
|
||||
{
|
||||
TrackerEntry trackerEntry {QString::fromStdString(nativeEntry.url), nativeEntry.tier};
|
||||
|
||||
int numUpdating = 0;
|
||||
int numWorking = 0;
|
||||
int numNotWorking = 0;
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
const int numEndpoints = nativeEntry.endpoints.size() * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1);
|
||||
trackerEntry.endpoints.reserve(numEndpoints);
|
||||
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
|
||||
{
|
||||
for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2})
|
||||
{
|
||||
if (hashes.has(protocolVersion))
|
||||
{
|
||||
const lt::announce_infohash &infoHash = endpoint.info_hashes[protocolVersion];
|
||||
|
||||
TrackerEntry::EndpointStats trackerEndpoint;
|
||||
trackerEndpoint.protocolVersion = (protocolVersion == lt::protocol_version::V1) ? 1 : 2;
|
||||
trackerEndpoint.numSeeds = infoHash.scrape_complete;
|
||||
trackerEndpoint.numLeeches = infoHash.scrape_incomplete;
|
||||
trackerEndpoint.numDownloaded = infoHash.scrape_downloaded;
|
||||
if (infoHash.updating)
|
||||
{
|
||||
trackerEndpoint.status = TrackerEntry::Updating;
|
||||
++numUpdating;
|
||||
}
|
||||
else if (infoHash.fails > 0)
|
||||
{
|
||||
trackerEndpoint.status = TrackerEntry::NotWorking;
|
||||
++numNotWorking;
|
||||
}
|
||||
else if (nativeEntry.verified)
|
||||
{
|
||||
trackerEndpoint.status = TrackerEntry::Working;
|
||||
++numWorking;
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerEndpoint.status = TrackerEntry::NotContacted;
|
||||
}
|
||||
trackerEntry.endpoints.append(trackerEndpoint);
|
||||
|
||||
trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, infoHash.scrape_complete);
|
||||
trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, infoHash.scrape_incomplete);
|
||||
trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, infoHash.scrape_downloaded);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
const int numEndpoints = nativeEntry.endpoints.size();
|
||||
trackerEntry.endpoints.reserve(numEndpoints);
|
||||
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
|
||||
{
|
||||
TrackerEntry::EndpointStats trackerEndpoint;
|
||||
trackerEndpoint.numSeeds = endpoint.scrape_complete;
|
||||
trackerEndpoint.numLeeches = endpoint.scrape_incomplete;
|
||||
trackerEndpoint.numDownloaded = endpoint.scrape_downloaded;
|
||||
if (endpoint.updating)
|
||||
{
|
||||
trackerEndpoint.status = TrackerEntry::Updating;
|
||||
++numUpdating;
|
||||
}
|
||||
else if (endpoint.fails > 0)
|
||||
{
|
||||
trackerEndpoint.status = TrackerEntry::NotWorking;
|
||||
++numNotWorking;
|
||||
}
|
||||
else if (nativeEntry.verified)
|
||||
{
|
||||
trackerEndpoint.status = TrackerEntry::Working;
|
||||
++numWorking;
|
||||
}
|
||||
else
|
||||
{
|
||||
trackerEndpoint.status = TrackerEntry::NotContacted;
|
||||
}
|
||||
trackerEntry.endpoints.append(trackerEndpoint);
|
||||
|
||||
trackerEntry.numSeeds = std::max(trackerEntry.numSeeds, endpoint.scrape_complete);
|
||||
trackerEntry.numLeeches = std::max(trackerEntry.numLeeches, endpoint.scrape_incomplete);
|
||||
trackerEntry.numDownloaded = std::max(trackerEntry.numDownloaded, endpoint.scrape_downloaded);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (numEndpoints > 0)
|
||||
{
|
||||
if (numUpdating > 0)
|
||||
trackerEntry.status = TrackerEntry::Updating;
|
||||
else if (numWorking > 0)
|
||||
trackerEntry.status = TrackerEntry::Working;
|
||||
else if (numNotWorking == numEndpoints)
|
||||
trackerEntry.status = TrackerEntry::NotWorking;
|
||||
}
|
||||
|
||||
return trackerEntry;
|
||||
}
|
||||
|
||||
void initializeStatus(lt::torrent_status &status, const lt::add_torrent_params ¶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_nativeSession(nativeSession)
|
||||
, m_nativeHandle(nativeHandle)
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
, m_infoHash(m_nativeHandle.info_hashes())
|
||||
#else
|
||||
, m_infoHash(m_nativeHandle.info_hash())
|
||||
#endif
|
||||
, m_name(params.name)
|
||||
, m_savePath(Utils::Fs::toNativePath(params.savePath))
|
||||
, m_category(params.category)
|
||||
@@ -123,7 +250,6 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
|
||||
if (m_useAutoTMM)
|
||||
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
|
||||
|
||||
m_hash = InfoHash {m_nativeHandle.info_hash()};
|
||||
if (m_ltAddTorrentParams.ti)
|
||||
{
|
||||
// Initialize it only if torrent is added with metadata.
|
||||
@@ -131,7 +257,8 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
|
||||
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
|
||||
}
|
||||
|
||||
updateStatus();
|
||||
initializeStatus(m_nativeStatus, m_ltAddTorrentParams);
|
||||
updateState();
|
||||
|
||||
if (hasMetadata())
|
||||
applyFirstLastPiecePriority(m_hasFirstLastPiecePriority);
|
||||
@@ -168,9 +295,9 @@ bool TorrentImpl::isValid() const
|
||||
return m_nativeHandle.is_valid();
|
||||
}
|
||||
|
||||
InfoHash TorrentImpl::hash() const
|
||||
InfoHash TorrentImpl::infoHash() const
|
||||
{
|
||||
return m_hash;
|
||||
return m_infoHash;
|
||||
}
|
||||
|
||||
QString TorrentImpl::name() const
|
||||
@@ -185,7 +312,7 @@ QString TorrentImpl::name() const
|
||||
if (!name.isEmpty())
|
||||
return name;
|
||||
|
||||
return m_hash;
|
||||
return id().toString();
|
||||
}
|
||||
|
||||
QDateTime TorrentImpl::creationDate() const
|
||||
@@ -284,6 +411,7 @@ void TorrentImpl::setAutoTMMEnabled(bool enabled)
|
||||
if (m_useAutoTMM == enabled) return;
|
||||
|
||||
m_useAutoTMM = enabled;
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentSavingModeChanged(this);
|
||||
|
||||
if (m_useAutoTMM)
|
||||
@@ -311,7 +439,11 @@ QVector<TrackerEntry> TorrentImpl::trackers() const
|
||||
entries.reserve(nativeTrackers.size());
|
||||
|
||||
for (const lt::announce_entry &tracker : nativeTrackers)
|
||||
entries << tracker;
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
entries << fromNativeAnnouncerEntry(tracker, m_nativeHandle.info_hashes());
|
||||
#else
|
||||
entries << fromNativeAnnouncerEntry(tracker);
|
||||
#endif
|
||||
|
||||
return entries;
|
||||
}
|
||||
@@ -325,7 +457,7 @@ void TorrentImpl::addTrackers(const QVector<TrackerEntry> &trackers)
|
||||
{
|
||||
QSet<TrackerEntry> currentTrackers;
|
||||
for (const lt::announce_entry &entry : m_nativeHandle.trackers())
|
||||
currentTrackers << entry;
|
||||
currentTrackers.insert({QString::fromStdString(entry.url), entry.tier});
|
||||
|
||||
QVector<TrackerEntry> newTrackers;
|
||||
newTrackers.reserve(trackers.size());
|
||||
@@ -334,13 +466,16 @@ void TorrentImpl::addTrackers(const QVector<TrackerEntry> &trackers)
|
||||
{
|
||||
if (!currentTrackers.contains(tracker))
|
||||
{
|
||||
m_nativeHandle.add_tracker(tracker.nativeEntry());
|
||||
m_nativeHandle.add_tracker(makeNativeAnnouncerEntry(tracker.url, tracker.tier));
|
||||
newTrackers << tracker;
|
||||
}
|
||||
}
|
||||
|
||||
if (!newTrackers.isEmpty())
|
||||
{
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentTrackersAdded(this, newTrackers);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
|
||||
@@ -355,7 +490,7 @@ void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
|
||||
|
||||
for (const TrackerEntry &tracker : trackers)
|
||||
{
|
||||
nativeTrackers.emplace_back(tracker.nativeEntry());
|
||||
nativeTrackers.emplace_back(makeNativeAnnouncerEntry(tracker.url, tracker.tier));
|
||||
|
||||
if (!currentTrackers.removeOne(tracker))
|
||||
newTrackers << tracker;
|
||||
@@ -363,6 +498,8 @@ void TorrentImpl::replaceTrackers(const QVector<TrackerEntry> &trackers)
|
||||
|
||||
m_nativeHandle.replace_trackers(nativeTrackers);
|
||||
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
|
||||
if (newTrackers.isEmpty() && currentTrackers.isEmpty())
|
||||
{
|
||||
// when existing tracker reorders
|
||||
@@ -414,7 +551,10 @@ void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
|
||||
}
|
||||
|
||||
if (!addedUrlSeeds.isEmpty())
|
||||
{
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
|
||||
@@ -435,7 +575,10 @@ void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
|
||||
}
|
||||
|
||||
if (!removedUrlSeeds.isEmpty())
|
||||
{
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::clearPeers()
|
||||
@@ -467,9 +610,7 @@ bool TorrentImpl::connectPeer(const PeerAddress &peerAddress)
|
||||
|
||||
bool TorrentImpl::needSaveResumeData() const
|
||||
{
|
||||
if (m_isStopped && !(m_nativeStatus.flags & lt::torrent_flags::auto_managed))
|
||||
return false;
|
||||
return m_nativeStatus.need_save_resume;
|
||||
return m_nativeHandle.need_save_resume_data();
|
||||
}
|
||||
|
||||
void TorrentImpl::saveResumeData()
|
||||
@@ -548,6 +689,7 @@ bool TorrentImpl::addTag(const QString &tag)
|
||||
if (!m_session->addTag(tag))
|
||||
return false;
|
||||
m_tags.insert(tag);
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentTagAdded(this, tag);
|
||||
return true;
|
||||
}
|
||||
@@ -558,6 +700,7 @@ bool TorrentImpl::removeTag(const QString &tag)
|
||||
{
|
||||
if (m_tags.remove(tag))
|
||||
{
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentTagRemoved(this, tag);
|
||||
return true;
|
||||
}
|
||||
@@ -818,9 +961,7 @@ bool TorrentImpl::hasFilteredPieces() const
|
||||
|
||||
int TorrentImpl::queuePosition() const
|
||||
{
|
||||
if (m_nativeStatus.queue_position < lt::queue_position_t {0}) return 0;
|
||||
|
||||
return static_cast<int>(m_nativeStatus.queue_position) + 1;
|
||||
return static_cast<int>(m_nativeStatus.queue_position);
|
||||
}
|
||||
|
||||
QString TorrentImpl::error() const
|
||||
@@ -1150,6 +1291,7 @@ void TorrentImpl::setName(const QString &name)
|
||||
if (m_name != name)
|
||||
{
|
||||
m_name = name;
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentNameChanged(this);
|
||||
}
|
||||
}
|
||||
@@ -1163,6 +1305,7 @@ bool TorrentImpl::setCategory(const QString &category)
|
||||
|
||||
const QString oldCategory = m_category;
|
||||
m_category = category;
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentCategoryChanged(this, oldCategory);
|
||||
|
||||
if (m_useAutoTMM)
|
||||
@@ -1182,6 +1325,7 @@ void TorrentImpl::move(QString path)
|
||||
if (m_useAutoTMM)
|
||||
{
|
||||
m_useAutoTMM = false;
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentSavingModeChanged(this);
|
||||
}
|
||||
|
||||
@@ -1197,8 +1341,8 @@ void TorrentImpl::move(QString path)
|
||||
void TorrentImpl::move_impl(QString path, const MoveStorageMode mode)
|
||||
{
|
||||
if (path == savePath()) return;
|
||||
path = Utils::Fs::toNativePath(path);
|
||||
|
||||
path = Utils::Fs::toNativePath(path);
|
||||
if (!useTempPath())
|
||||
{
|
||||
moveStorage(path, mode);
|
||||
@@ -1206,6 +1350,7 @@ void TorrentImpl::move_impl(QString path, const MoveStorageMode mode)
|
||||
else
|
||||
{
|
||||
m_savePath = path;
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
}
|
||||
}
|
||||
@@ -1249,7 +1394,7 @@ void TorrentImpl::setSequentialDownload(const bool enable)
|
||||
m_nativeStatus.flags &= ~lt::torrent_flags::sequential_download; // prevent return cached value
|
||||
}
|
||||
|
||||
saveResumeData();
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
|
||||
@@ -1264,7 +1409,7 @@ void TorrentImpl::setFirstLastPiecePriority(const bool enabled)
|
||||
LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
|
||||
.arg((enabled ? tr("On") : tr("Off")), name()));
|
||||
|
||||
saveResumeData();
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::applyFirstLastPiecePriority(const bool enabled, const QVector<DownloadPriority> &updatedFilePrio)
|
||||
@@ -1364,6 +1509,7 @@ void TorrentImpl::pause()
|
||||
if (!m_isStopped)
|
||||
{
|
||||
m_isStopped = true;
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentPaused(this);
|
||||
}
|
||||
|
||||
@@ -1387,6 +1533,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
|
||||
{
|
||||
m_hasMissingFiles = false;
|
||||
m_isStopped = false;
|
||||
m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
|
||||
reload();
|
||||
updateStatus();
|
||||
return;
|
||||
@@ -1399,6 +1546,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
|
||||
m_nativeHandle.unset_flags(lt::torrent_flags::stop_when_ready);
|
||||
|
||||
m_isStopped = false;
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentResumed(this);
|
||||
}
|
||||
|
||||
@@ -1434,6 +1582,7 @@ void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
|
||||
|
||||
void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
|
||||
{
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_storageIsMoving = hasOutstandingJob;
|
||||
|
||||
updateStatus();
|
||||
@@ -1444,10 +1593,21 @@ void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
}
|
||||
|
||||
saveResumeData();
|
||||
if (!m_storageIsMoving)
|
||||
{
|
||||
if (m_hasMissingFiles)
|
||||
{
|
||||
// it can be moved to the proper location
|
||||
m_hasMissingFiles = false;
|
||||
m_ltAddTorrentParams.save_path = m_nativeStatus.save_path;
|
||||
m_ltAddTorrentParams.ti = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
|
||||
reload();
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
while ((m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::handleTrackerReplyAlert(const lt::tracker_reply_alert *p)
|
||||
@@ -1484,9 +1644,9 @@ void TorrentImpl::handleTrackerErrorAlert(const lt::tracker_error_alert *p)
|
||||
const QVector<TrackerEntry> trackerList = trackers();
|
||||
const auto iter = std::find_if(trackerList.cbegin(), trackerList.cend(), [&trackerUrl](const TrackerEntry &entry)
|
||||
{
|
||||
return (entry.url() == trackerUrl);
|
||||
return (entry.url == trackerUrl);
|
||||
});
|
||||
if ((iter != trackerList.cend()) && (iter->status() == TrackerEntry::NotWorking))
|
||||
if ((iter != trackerList.cend()) && (iter->status == TrackerEntry::NotWorking))
|
||||
m_session->handleTorrentTrackerError(this, trackerUrl);
|
||||
}
|
||||
|
||||
@@ -1503,7 +1663,8 @@ void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
|
||||
return;
|
||||
}
|
||||
|
||||
saveResumeData();
|
||||
if (m_nativeHandle.need_save_resume_data())
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
|
||||
if (m_fastresumeDataRejected && !m_hasMissingFiles)
|
||||
m_fastresumeDataRejected = false;
|
||||
@@ -1538,6 +1699,8 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
|
||||
adjustActualSavePath();
|
||||
manageIncompleteFiles();
|
||||
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
|
||||
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
|
||||
if (isMoveInProgress() || (m_renameCount > 0))
|
||||
{
|
||||
@@ -1565,35 +1728,25 @@ void TorrentImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p)
|
||||
|
||||
void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
||||
{
|
||||
if (!m_hasMissingFiles)
|
||||
if (m_hasMissingFiles)
|
||||
{
|
||||
const auto havePieces = m_ltAddTorrentParams.have_pieces;
|
||||
const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
|
||||
const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
|
||||
|
||||
// Update recent resume data but preserve existing progress
|
||||
m_ltAddTorrentParams = p->params;
|
||||
m_ltAddTorrentParams.have_pieces = havePieces;
|
||||
m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
|
||||
m_ltAddTorrentParams.verified_pieces = verifiedPieces;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update recent resume data
|
||||
m_ltAddTorrentParams = p->params;
|
||||
}
|
||||
|
||||
if (m_isStopped)
|
||||
{
|
||||
m_ltAddTorrentParams.flags |= lt::torrent_flags::paused;
|
||||
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::auto_managed;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Torrent can be actually "running" but temporarily "paused" to perform some
|
||||
// service jobs behind the scenes so we need to restore it as "running"
|
||||
if (m_operatingMode == TorrentOperatingMode::AutoManaged)
|
||||
{
|
||||
m_ltAddTorrentParams.flags |= lt::torrent_flags::auto_managed;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::paused;
|
||||
m_ltAddTorrentParams.flags &= ~lt::torrent_flags::auto_managed;
|
||||
}
|
||||
}
|
||||
|
||||
m_ltAddTorrentParams.added_time = addedTime().toSecsSinceEpoch();
|
||||
m_ltAddTorrentParams.save_path = Profile::instance()->toPortablePath(
|
||||
QString::fromStdString(m_ltAddTorrentParams.save_path)).toStdString();
|
||||
|
||||
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
|
||||
{
|
||||
@@ -1606,37 +1759,21 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
||||
m_session->findIncompleteFiles(metadata, m_savePath);
|
||||
}
|
||||
|
||||
auto resumeDataPtr = std::make_shared<lt::entry>(lt::write_resume_data(m_ltAddTorrentParams));
|
||||
lt::entry &resumeData = *resumeDataPtr;
|
||||
LoadTorrentParams resumeData;
|
||||
resumeData.name = m_name;
|
||||
resumeData.category = m_category;
|
||||
resumeData.savePath = m_useAutoTMM ? "" : m_savePath;
|
||||
resumeData.tags = m_tags;
|
||||
resumeData.contentLayout = m_contentLayout;
|
||||
resumeData.ratioLimit = m_ratioLimit;
|
||||
resumeData.seedingTimeLimit = m_seedingTimeLimit;
|
||||
resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
|
||||
resumeData.hasSeedStatus = m_hasSeedStatus;
|
||||
resumeData.paused = m_isStopped;
|
||||
resumeData.forced = (m_operatingMode == TorrentOperatingMode::Forced);
|
||||
resumeData.ltAddTorrentParams = m_ltAddTorrentParams;
|
||||
|
||||
// TODO: The following code is deprecated. Remove after several releases in 4.3.x.
|
||||
// === BEGIN DEPRECATED CODE === //
|
||||
const bool useDummyResumeData = !hasMetadata();
|
||||
if (useDummyResumeData)
|
||||
{
|
||||
updateStatus();
|
||||
|
||||
resumeData["qBt-magnetUri"] = createMagnetURI().toStdString();
|
||||
// sequentialDownload needs to be stored in the
|
||||
// resume data if there is no metadata, otherwise they won't be
|
||||
// restored if qBittorrent quits before the metadata are retrieved:
|
||||
resumeData["qBt-sequential"] = isSequentialDownload();
|
||||
|
||||
resumeData["qBt-addedTime"] = addedTime().toSecsSinceEpoch();
|
||||
}
|
||||
// === END DEPRECATED CODE === //
|
||||
|
||||
resumeData["qBt-savePath"] = m_useAutoTMM ? "" : Profile::instance()->toPortablePath(m_savePath).toStdString();
|
||||
resumeData["qBt-ratioLimit"] = static_cast<int>(m_ratioLimit * 1000);
|
||||
resumeData["qBt-seedingTimeLimit"] = m_seedingTimeLimit;
|
||||
resumeData["qBt-category"] = m_category.toStdString();
|
||||
resumeData["qBt-tags"] = setToEntryList(m_tags);
|
||||
resumeData["qBt-name"] = m_name.toStdString();
|
||||
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
|
||||
resumeData["qBt-contentLayout"] = Utils::String::fromEnum(m_contentLayout).toStdString();
|
||||
resumeData["qBt-firstLastPiecePriority"] = m_hasFirstLastPiecePriority;
|
||||
|
||||
m_session->handleTorrentResumeDataReady(this, resumeDataPtr);
|
||||
m_session->handleTorrentResumeDataReady(this, resumeData);
|
||||
}
|
||||
|
||||
void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p)
|
||||
@@ -1702,8 +1839,7 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
|
||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
|
||||
if (isPaused() && (m_renameCount == 0))
|
||||
saveResumeData(); // otherwise the new path will not be saved
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
|
||||
@@ -1720,8 +1856,7 @@ void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
|
||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
|
||||
if (isPaused() && (m_renameCount == 0))
|
||||
saveResumeData(); // otherwise the new path will not be saved
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
|
||||
@@ -1743,11 +1878,10 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
|
||||
void TorrentImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
|
||||
qDebug("Metadata received for torrent %s.", qUtf8Printable(name()));
|
||||
|
||||
m_maintenanceJob = MaintenanceJob::HandleMetadata;
|
||||
saveResumeData();
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
|
||||
@@ -1945,6 +2079,7 @@ void TorrentImpl::setRatioLimit(qreal limit)
|
||||
if (m_ratioLimit != limit)
|
||||
{
|
||||
m_ratioLimit = limit;
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentShareLimitChanged(this);
|
||||
}
|
||||
}
|
||||
@@ -1959,6 +2094,7 @@ void TorrentImpl::setSeedingTimeLimit(int limit)
|
||||
if (m_seedingTimeLimit != limit)
|
||||
{
|
||||
m_seedingTimeLimit = limit;
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentShareLimitChanged(this);
|
||||
}
|
||||
}
|
||||
@@ -1969,7 +2105,7 @@ void TorrentImpl::setUploadLimit(const int limit)
|
||||
return;
|
||||
|
||||
m_nativeHandle.set_upload_limit(limit);
|
||||
saveResumeData();
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::setDownloadLimit(const int limit)
|
||||
@@ -1978,7 +2114,7 @@ void TorrentImpl::setDownloadLimit(const int limit)
|
||||
return;
|
||||
|
||||
m_nativeHandle.set_download_limit(limit);
|
||||
saveResumeData();
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::setSuperSeeding(const bool enable)
|
||||
@@ -1990,7 +2126,8 @@ void TorrentImpl::setSuperSeeding(const bool enable)
|
||||
m_nativeHandle.set_flags(lt::torrent_flags::super_seeding);
|
||||
else
|
||||
m_nativeHandle.unset_flags(lt::torrent_flags::super_seeding);
|
||||
saveResumeData();
|
||||
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::setDHTDisabled(const bool disable)
|
||||
@@ -2002,7 +2139,8 @@ void TorrentImpl::setDHTDisabled(const bool disable)
|
||||
m_nativeHandle.set_flags(lt::torrent_flags::disable_dht);
|
||||
else
|
||||
m_nativeHandle.unset_flags(lt::torrent_flags::disable_dht);
|
||||
saveResumeData();
|
||||
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::setPEXDisabled(const bool disable)
|
||||
@@ -2014,7 +2152,8 @@ void TorrentImpl::setPEXDisabled(const bool disable)
|
||||
m_nativeHandle.set_flags(lt::torrent_flags::disable_pex);
|
||||
else
|
||||
m_nativeHandle.unset_flags(lt::torrent_flags::disable_pex);
|
||||
saveResumeData();
|
||||
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::setLSDDisabled(const bool disable)
|
||||
@@ -2026,7 +2165,8 @@ void TorrentImpl::setLSDDisabled(const bool disable)
|
||||
m_nativeHandle.set_flags(lt::torrent_flags::disable_lsd);
|
||||
else
|
||||
m_nativeHandle.unset_flags(lt::torrent_flags::disable_lsd);
|
||||
saveResumeData();
|
||||
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::flushCache() const
|
||||
|
||||
@@ -68,7 +68,6 @@ namespace BitTorrent
|
||||
bool forced = false;
|
||||
bool paused = false;
|
||||
|
||||
|
||||
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
|
||||
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
|
||||
|
||||
@@ -99,7 +98,7 @@ namespace BitTorrent
|
||||
|
||||
bool isValid() const;
|
||||
|
||||
InfoHash hash() const override;
|
||||
InfoHash infoHash() const override;
|
||||
QString name() const override;
|
||||
QDateTime creationDate() const override;
|
||||
QString creator() const override;
|
||||
@@ -301,7 +300,7 @@ namespace BitTorrent
|
||||
TorrentInfo m_torrentInfo;
|
||||
SpeedMonitor m_speedMonitor;
|
||||
|
||||
InfoHash m_hash;
|
||||
InfoHash m_infoHash;
|
||||
|
||||
// m_moveFinishedTriggers is activated only when the following conditions are met:
|
||||
// all file rename jobs complete, all file move jobs complete
|
||||
|
||||
@@ -184,10 +184,15 @@ bool TorrentInfo::isValid() const
|
||||
return (m_nativeInfo && m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
|
||||
}
|
||||
|
||||
InfoHash TorrentInfo::hash() const
|
||||
InfoHash TorrentInfo::infoHash() const
|
||||
{
|
||||
if (!isValid()) return {};
|
||||
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
return m_nativeInfo->info_hashes();
|
||||
#else
|
||||
return m_nativeInfo->info_hash();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString TorrentInfo::name() const
|
||||
@@ -302,7 +307,7 @@ QVector<TrackerEntry> TorrentInfo::trackers() const
|
||||
ret.reserve(trackers.size());
|
||||
|
||||
for (const lt::announce_entry &tracker : trackers)
|
||||
ret.append(tracker);
|
||||
ret.append({QString::fromStdString(tracker.url)});
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -374,7 +379,7 @@ QVector<QByteArray> TorrentInfo::pieceHashes() const
|
||||
hashes.reserve(count);
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
hashes += {m_nativeInfo->hash_for_piece_ptr(lt::piece_index_t {i}), InfoHash::length()};
|
||||
hashes += {m_nativeInfo->hash_for_piece_ptr(lt::piece_index_t {i}), SHA1Hash::length()};
|
||||
|
||||
return hashes;
|
||||
}
|
||||
@@ -481,9 +486,13 @@ void TorrentInfo::stripRootFolder()
|
||||
|
||||
void TorrentInfo::addRootFolder()
|
||||
{
|
||||
const QString rootFolder = name();
|
||||
Q_ASSERT(!rootFolder.isEmpty());
|
||||
const QString originalName = name();
|
||||
Q_ASSERT(!originalName.isEmpty());
|
||||
|
||||
const QString extension = Utils::Fs::fileExtension(originalName);
|
||||
const QString rootFolder = extension.isEmpty()
|
||||
? originalName
|
||||
: originalName.chopped(extension.size() + 1);
|
||||
const std::string rootPrefix = Utils::Fs::toNativePath(rootFolder + QLatin1Char {'/'}).toStdString();
|
||||
lt::file_storage files = m_nativeInfo->files();
|
||||
files.set_name(rootFolder.toStdString());
|
||||
|
||||
@@ -45,7 +45,7 @@ class QUrl;
|
||||
namespace BitTorrent
|
||||
{
|
||||
class InfoHash;
|
||||
class TrackerEntry;
|
||||
struct TrackerEntry;
|
||||
|
||||
class TorrentInfo final : public AbstractFileStorage
|
||||
{
|
||||
@@ -62,7 +62,7 @@ namespace BitTorrent
|
||||
TorrentInfo &operator=(const TorrentInfo &other);
|
||||
|
||||
bool isValid() const;
|
||||
InfoHash hash() const;
|
||||
InfoHash infoHash() const;
|
||||
QString name() const;
|
||||
QDateTime creationDate() const;
|
||||
QString creator() const;
|
||||
|
||||
@@ -153,7 +153,7 @@ struct Tracker::TrackerAnnounceRequest
|
||||
{
|
||||
QHostAddress socketAddress;
|
||||
QByteArray claimedAddress; // self claimed by peer
|
||||
InfoHash infoHash;
|
||||
TorrentID torrentID;
|
||||
QString event;
|
||||
Peer peer;
|
||||
int numwant = 50;
|
||||
@@ -295,11 +295,11 @@ void Tracker::processAnnounceRequest()
|
||||
if (infoHashIter == queryParams.end())
|
||||
throw TrackerError("Missing \"info_hash\" parameter");
|
||||
|
||||
const InfoHash infoHash(infoHashIter->toHex());
|
||||
if (!infoHash.isValid())
|
||||
const auto torrentID = TorrentID::fromString(infoHashIter->toHex());
|
||||
if (!torrentID.isValid())
|
||||
throw TrackerError("Invalid \"info_hash\" parameter");
|
||||
|
||||
announceReq.infoHash = infoHash;
|
||||
announceReq.torrentID = torrentID;
|
||||
|
||||
// 2. peer_id
|
||||
const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID);
|
||||
@@ -381,19 +381,19 @@ void Tracker::processAnnounceRequest()
|
||||
|
||||
void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
|
||||
{
|
||||
if (!m_torrents.contains(announceReq.infoHash))
|
||||
if (!m_torrents.contains(announceReq.torrentID))
|
||||
{
|
||||
// Reached max size, remove a random torrent
|
||||
if (m_torrents.size() >= MAX_TORRENTS)
|
||||
m_torrents.erase(m_torrents.begin());
|
||||
}
|
||||
|
||||
m_torrents[announceReq.infoHash].setPeer(announceReq.peer);
|
||||
m_torrents[announceReq.torrentID].setPeer(announceReq.peer);
|
||||
}
|
||||
|
||||
void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
|
||||
{
|
||||
const auto torrentStatsIter = m_torrents.find(announceReq.infoHash);
|
||||
const auto torrentStatsIter = m_torrents.find(announceReq.torrentID);
|
||||
if (torrentStatsIter == m_torrents.end())
|
||||
return;
|
||||
|
||||
@@ -405,7 +405,7 @@ void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
|
||||
|
||||
void Tracker::prepareAnnounceResponse(const TrackerAnnounceRequest &announceReq)
|
||||
{
|
||||
const TorrentStats &torrentStats = m_torrents[announceReq.infoHash];
|
||||
const TorrentStats &torrentStats = m_torrents[announceReq.torrentID];
|
||||
|
||||
lt::entry::dictionary_type replyDict
|
||||
{
|
||||
|
||||
@@ -102,6 +102,6 @@ namespace BitTorrent
|
||||
Http::Request m_request;
|
||||
Http::Environment m_env;
|
||||
|
||||
QHash<InfoHash, TorrentStats> m_torrents;
|
||||
QHash<TorrentID, TorrentStats> m_torrents;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -28,139 +28,15 @@
|
||||
|
||||
#include "trackerentry.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
using namespace BitTorrent;
|
||||
|
||||
TrackerEntry::TrackerEntry(const QString &url)
|
||||
: m_nativeEntry(url.toStdString())
|
||||
{
|
||||
}
|
||||
|
||||
TrackerEntry::TrackerEntry(const lt::announce_entry &nativeEntry)
|
||||
: m_nativeEntry(nativeEntry)
|
||||
{
|
||||
}
|
||||
|
||||
QString TrackerEntry::url() const
|
||||
{
|
||||
return QString::fromStdString(nativeEntry().url);
|
||||
}
|
||||
|
||||
int TrackerEntry::tier() const
|
||||
{
|
||||
return nativeEntry().tier;
|
||||
}
|
||||
|
||||
TrackerEntry::Status TrackerEntry::status() const
|
||||
{
|
||||
const auto &endpoints = nativeEntry().endpoints;
|
||||
|
||||
const bool allFailed = !endpoints.empty() && std::all_of(endpoints.begin(), endpoints.end()
|
||||
, [](const lt::announce_endpoint &endpoint)
|
||||
{
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
return std::all_of(endpoint.info_hashes.begin(), endpoint.info_hashes.end()
|
||||
, [](const lt::announce_infohash &infohash)
|
||||
{
|
||||
return (infohash.fails > 0);
|
||||
});
|
||||
#else
|
||||
return (endpoint.fails > 0);
|
||||
#endif
|
||||
});
|
||||
if (allFailed)
|
||||
return NotWorking;
|
||||
|
||||
const bool isUpdating = std::any_of(endpoints.begin(), endpoints.end()
|
||||
, [](const lt::announce_endpoint &endpoint)
|
||||
{
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
return std::any_of(endpoint.info_hashes.begin(), endpoint.info_hashes.end()
|
||||
, [](const lt::announce_infohash &infohash)
|
||||
{
|
||||
return infohash.updating;
|
||||
});
|
||||
#else
|
||||
return endpoint.updating;
|
||||
#endif
|
||||
});
|
||||
if (isUpdating)
|
||||
return Updating;
|
||||
|
||||
if (!nativeEntry().verified)
|
||||
return NotContacted;
|
||||
|
||||
return Working;
|
||||
}
|
||||
|
||||
void TrackerEntry::setTier(const int value)
|
||||
{
|
||||
m_nativeEntry.tier = value;
|
||||
}
|
||||
|
||||
int TrackerEntry::numSeeds() const
|
||||
{
|
||||
int value = -1;
|
||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
|
||||
{
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
|
||||
value = std::max(value, infoHash.scrape_complete);
|
||||
#else
|
||||
value = std::max(value, endpoint.scrape_complete);
|
||||
#endif
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int TrackerEntry::numLeeches() const
|
||||
{
|
||||
int value = -1;
|
||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
|
||||
{
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
|
||||
value = std::max(value, infoHash.scrape_incomplete);
|
||||
#else
|
||||
value = std::max(value, endpoint.scrape_incomplete);
|
||||
#endif
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int TrackerEntry::numDownloaded() const
|
||||
{
|
||||
int value = -1;
|
||||
for (const lt::announce_endpoint &endpoint : nativeEntry().endpoints)
|
||||
{
|
||||
#if (LIBTORRENT_VERSION_NUM >= 20000)
|
||||
for (const lt::announce_infohash &infoHash : endpoint.info_hashes)
|
||||
value = std::max(value, infoHash.scrape_downloaded);
|
||||
#else
|
||||
value = std::max(value, endpoint.scrape_downloaded);
|
||||
#endif
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const lt::announce_entry &TrackerEntry::nativeEntry() const
|
||||
{
|
||||
return m_nativeEntry;
|
||||
}
|
||||
|
||||
bool BitTorrent::operator==(const TrackerEntry &left, const TrackerEntry &right)
|
||||
{
|
||||
return ((left.tier() == right.tier())
|
||||
&& QUrl(left.url()) == QUrl(right.url()));
|
||||
return ((left.tier == right.tier)
|
||||
&& QUrl(left.url) == QUrl(right.url));
|
||||
}
|
||||
|
||||
uint BitTorrent::qHash(const TrackerEntry &key, const uint seed)
|
||||
{
|
||||
return (::qHash(key.url(), seed) ^ ::qHash(key.tier()));
|
||||
return (::qHash(key.url, seed) ^ ::qHash(key.tier));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -28,17 +28,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libtorrent/announce_entry.hpp>
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
class QString;
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TrackerEntry
|
||||
struct TrackerEntry
|
||||
{
|
||||
public:
|
||||
enum Status
|
||||
{
|
||||
NotContacted = 1,
|
||||
@@ -47,26 +44,26 @@ namespace BitTorrent
|
||||
NotWorking = 4
|
||||
};
|
||||
|
||||
TrackerEntry() = default;
|
||||
TrackerEntry(const QString &url);
|
||||
TrackerEntry(const lt::announce_entry &nativeEntry);
|
||||
TrackerEntry(const TrackerEntry &other) = default;
|
||||
TrackerEntry &operator=(const TrackerEntry &other) = default;
|
||||
struct EndpointStats
|
||||
{
|
||||
int protocolVersion = 1;
|
||||
|
||||
QString url() const;
|
||||
Status status() const;
|
||||
Status status = NotContacted;
|
||||
int numSeeds = -1;
|
||||
int numLeeches = -1;
|
||||
int numDownloaded = -1;
|
||||
};
|
||||
|
||||
int tier() const;
|
||||
void setTier(int value);
|
||||
QString url;
|
||||
int tier = 0;
|
||||
|
||||
int numSeeds() const;
|
||||
int numLeeches() const;
|
||||
int numDownloaded() const;
|
||||
QVector<EndpointStats> endpoints {};
|
||||
|
||||
const lt::announce_entry &nativeEntry() const;
|
||||
|
||||
private:
|
||||
lt::announce_entry m_nativeEntry;
|
||||
// Deprecated fields
|
||||
Status status = NotContacted;
|
||||
int numSeeds = -1;
|
||||
int numLeeches = -1;
|
||||
int numDownloaded = -1;
|
||||
};
|
||||
|
||||
bool operator==(const TrackerEntry &left, const TrackerEntry &right);
|
||||
|
||||
121
src/base/digest32.h
Normal file
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 <QHostInfo>
|
||||
#include <QStringList>
|
||||
#include <QTextCodec>
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
#include <QSslSocket>
|
||||
@@ -89,6 +88,14 @@ namespace
|
||||
|
||||
return hostname.toLocal8Bit();
|
||||
}
|
||||
|
||||
bool canEncodeAsLatin1(const QStringView string)
|
||||
{
|
||||
return std::none_of(string.cbegin(), string.cend(), [](const QChar &ch)
|
||||
{
|
||||
return ch > QChar(0xff);
|
||||
});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
using namespace Net;
|
||||
@@ -137,11 +144,10 @@ Smtp::~Smtp()
|
||||
void Smtp::sendMail(const QString &from, const QString &to, const QString &subject, const QString &body)
|
||||
{
|
||||
const Preferences *const pref = Preferences::instance();
|
||||
QTextCodec *latin1 = QTextCodec::codecForName("latin1");
|
||||
m_message = "Date: " + getCurrentDateTime().toLatin1() + "\r\n"
|
||||
+ encodeMimeHeader("From", from, latin1)
|
||||
+ encodeMimeHeader("Subject", subject, latin1)
|
||||
+ encodeMimeHeader("To", to, latin1)
|
||||
+ encodeMimeHeader("From", from)
|
||||
+ encodeMimeHeader("Subject", subject)
|
||||
+ encodeMimeHeader("To", to)
|
||||
+ "MIME-Version: 1.0\r\n"
|
||||
+ "Content-Type: text/plain; charset=UTF-8\r\n"
|
||||
+ "Content-Transfer-Encoding: base64\r\n"
|
||||
@@ -312,12 +318,12 @@ void Smtp::readyRead()
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, const QTextCodec *latin1, const QByteArray &prefix)
|
||||
QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, const QByteArray &prefix)
|
||||
{
|
||||
QByteArray rv = "";
|
||||
QByteArray line = key.toLatin1() + ": ";
|
||||
if (!prefix.isEmpty()) line += prefix;
|
||||
if (!value.contains("=?") && latin1->canEncode(value))
|
||||
if (!value.contains("=?") && canEncodeAsLatin1(value))
|
||||
{
|
||||
bool firstWord = true;
|
||||
for (const QByteArray &word : asConst(value.toLatin1().split(' ')))
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Net
|
||||
AuthCramMD5
|
||||
};
|
||||
|
||||
QByteArray encodeMimeHeader(const QString &key, const QString &value, const QTextCodec *latin1, const QByteArray &prefix = {});
|
||||
QByteArray encodeMimeHeader(const QString &key, const QString &value, const QByteArray &prefix = {});
|
||||
void ehlo();
|
||||
void helo();
|
||||
void parseEhloResponse(const QByteArray &code, bool continued, const QString &line);
|
||||
|
||||
@@ -234,6 +234,16 @@ void Preferences::setCloseToTrayNotified(const bool b)
|
||||
{
|
||||
setValue("Preferences/General/CloseToTrayNotified", b);
|
||||
}
|
||||
|
||||
bool Preferences::iconsInMenusEnabled() const
|
||||
{
|
||||
return value("Preferences/Advanced/EnableIconsInMenus", true).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setIconsInMenusEnabled(const bool enable)
|
||||
{
|
||||
setValue("Preferences/Advanced/EnableIconsInMenus", enable);
|
||||
}
|
||||
#endif // Q_OS_MACOS
|
||||
|
||||
bool Preferences::isToolbarDisplayed() const
|
||||
@@ -853,7 +863,7 @@ void Preferences::setAutoRunProgram(const QString &program)
|
||||
setValue("AutoRun/program", program);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN) && (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
#if defined(Q_OS_WIN)
|
||||
bool Preferences::isAutoRunConsoleEnabled() const
|
||||
{
|
||||
return value("AutoRun/ConsoleEnabled", false).toBool();
|
||||
|
||||
@@ -248,7 +248,7 @@ public:
|
||||
void setAutoRunEnabled(bool enabled);
|
||||
QString getAutoRunProgram() const;
|
||||
void setAutoRunProgram(const QString &program);
|
||||
#if defined(Q_OS_WIN) && (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
#if defined(Q_OS_WIN)
|
||||
bool isAutoRunConsoleEnabled() const;
|
||||
void setAutoRunConsoleEnabled(bool enabled);
|
||||
#endif
|
||||
@@ -313,6 +313,8 @@ public:
|
||||
void setCloseToTrayNotified(bool b);
|
||||
TrayIcon::Style trayIconStyle() const;
|
||||
void setTrayIconStyle(TrayIcon::Style style);
|
||||
bool iconsInMenusEnabled() const;
|
||||
void setIconsInMenusEnabled(bool enable);
|
||||
#endif // Q_OS_MACOS
|
||||
|
||||
// Stuff that don't appear in the Options GUI but are saved
|
||||
|
||||
@@ -213,10 +213,8 @@ QRegularExpression AutoDownloadRule::cachedRegex(const QString &expression, cons
|
||||
QRegularExpression ®ex = m_dataPtr->cachedRegexes[expression];
|
||||
if (regex.pattern().isEmpty())
|
||||
{
|
||||
regex = QRegularExpression
|
||||
{
|
||||
(isRegex ? expression : Utils::String::wildcardToRegex(expression))
|
||||
, QRegularExpression::CaseInsensitiveOption};
|
||||
const QString pattern = (isRegex ? expression : Utils::String::wildcardToRegexPattern(expression));
|
||||
regex = QRegularExpression {pattern, QRegularExpression::CaseInsensitiveOption};
|
||||
}
|
||||
|
||||
return regex;
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#include <QGlobalStatic>
|
||||
#include <QHash>
|
||||
#include <QMetaObject>
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include <QXmlStreamEntityResolver>
|
||||
@@ -391,12 +391,13 @@ namespace
|
||||
int nmin = 8;
|
||||
int nsec = 9;
|
||||
// Also accept obsolete form "Weekday, DD-Mon-YY HH:MM:SS ±hhmm"
|
||||
QRegExp rx("^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$");
|
||||
QRegularExpression rx {"^(?:([A-Z][a-z]+),\\s*)?(\\d{1,2})(\\s+|-)([^-\\s]+)(\\s+|-)(\\d{2,4})\\s+(\\d\\d):(\\d\\d)(?::(\\d\\d))?\\s+(\\S+)$"};
|
||||
QRegularExpressionMatch rxMatch;
|
||||
QStringList parts;
|
||||
if (!str.indexOf(rx))
|
||||
if (str.indexOf(rx, 0, &rxMatch) == 0)
|
||||
{
|
||||
// Check that if date has '-' separators, both separators are '-'.
|
||||
parts = rx.capturedTexts();
|
||||
parts = rxMatch.capturedTexts();
|
||||
const bool h1 = (parts[3] == QLatin1String("-"));
|
||||
const bool h2 = (parts[5] == QLatin1String("-"));
|
||||
if (h1 != h2)
|
||||
@@ -405,9 +406,10 @@ namespace
|
||||
else
|
||||
{
|
||||
// Check for the obsolete form "Wdy Mon DD HH:MM:SS YYYY"
|
||||
rx = QRegExp("^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$");
|
||||
if (str.indexOf(rx))
|
||||
rx = QRegularExpression {"^([A-Z][a-z]+)\\s+(\\S+)\\s+(\\d\\d)\\s+(\\d\\d):(\\d\\d):(\\d\\d)\\s+(\\d\\d\\d\\d)$"};
|
||||
if (str.indexOf(rx, 0, &rxMatch) != 0)
|
||||
return QDateTime::currentDateTime();
|
||||
|
||||
nyear = 7;
|
||||
nmonth = 2;
|
||||
nday = 3;
|
||||
@@ -415,7 +417,7 @@ namespace
|
||||
nhour = 4;
|
||||
nmin = 5;
|
||||
nsec = 6;
|
||||
parts = rx.capturedTexts();
|
||||
parts = rxMatch.capturedTexts();
|
||||
}
|
||||
|
||||
bool ok[4];
|
||||
@@ -463,11 +465,11 @@ namespace
|
||||
bool negOffset = false;
|
||||
if (parts.count() > 10)
|
||||
{
|
||||
rx = QRegExp("^([+-])(\\d\\d)(\\d\\d)$");
|
||||
if (!parts[10].indexOf(rx))
|
||||
rx = QRegularExpression {"^([+-])(\\d\\d)(\\d\\d)$"};
|
||||
if (parts[10].indexOf(rx, 0, &rxMatch) == 0)
|
||||
{
|
||||
// It's a UTC offset ±hhmm
|
||||
parts = rx.capturedTexts();
|
||||
parts = rxMatch.capturedTexts();
|
||||
offset = parts[2].toInt(&ok[0]) * 3600;
|
||||
const int offsetMin = parts[3].toInt(&ok[1]);
|
||||
if (!ok[0] || !ok[1] || offsetMin > 59)
|
||||
@@ -547,13 +549,8 @@ Parser::Parser(const QString lastBuildDate)
|
||||
|
||||
void Parser::parse(const QByteArray &feedData)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
QMetaObject::invokeMethod(this, [this, feedData]() { parse_impl(feedData); }
|
||||
, Qt::QueuedConnection);
|
||||
#else
|
||||
QMetaObject::invokeMethod(this, "parse_impl", Qt::QueuedConnection
|
||||
, Q_ARG(QByteArray, feedData));
|
||||
#endif
|
||||
}
|
||||
|
||||
// read and create items from a rss document
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "bittorrent/torrent.h"
|
||||
|
||||
const QString TorrentFilter::AnyCategory;
|
||||
const InfoHashSet TorrentFilter::AnyHash {{}};
|
||||
const TorrentIDSet TorrentFilter::AnyID {{}};
|
||||
const QString TorrentFilter::AnyTag;
|
||||
|
||||
const TorrentFilter TorrentFilter::DownloadingTorrent(TorrentFilter::Downloading);
|
||||
@@ -49,19 +49,19 @@ const TorrentFilter TorrentFilter::ErroredTorrent(TorrentFilter::Errored);
|
||||
|
||||
using BitTorrent::Torrent;
|
||||
|
||||
TorrentFilter::TorrentFilter(const Type type, const InfoHashSet &hashSet, const QString &category, const QString &tag)
|
||||
TorrentFilter::TorrentFilter(const Type type, const TorrentIDSet &idSet, const QString &category, const QString &tag)
|
||||
: m_type(type)
|
||||
, m_category(category)
|
||||
, m_tag(tag)
|
||||
, m_hashSet(hashSet)
|
||||
, m_idSet(idSet)
|
||||
{
|
||||
}
|
||||
|
||||
TorrentFilter::TorrentFilter(const QString &filter, const InfoHashSet &hashSet, const QString &category, const QString &tag)
|
||||
TorrentFilter::TorrentFilter(const QString &filter, const TorrentIDSet &idSet, const QString &category, const QString &tag)
|
||||
: m_type(All)
|
||||
, m_category(category)
|
||||
, m_tag(tag)
|
||||
, m_hashSet(hashSet)
|
||||
, m_idSet(idSet)
|
||||
{
|
||||
setTypeByName(filter);
|
||||
}
|
||||
@@ -107,11 +107,11 @@ bool TorrentFilter::setTypeByName(const QString &filter)
|
||||
return setType(type);
|
||||
}
|
||||
|
||||
bool TorrentFilter::setHashSet(const InfoHashSet &hashSet)
|
||||
bool TorrentFilter::setTorrentIDSet(const TorrentIDSet &idSet)
|
||||
{
|
||||
if (m_hashSet != hashSet)
|
||||
if (m_idSet != idSet)
|
||||
{
|
||||
m_hashSet = hashSet;
|
||||
m_idSet = idSet;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -189,9 +189,9 @@ bool TorrentFilter::matchState(const BitTorrent::Torrent *const torrent) const
|
||||
|
||||
bool TorrentFilter::matchHash(const BitTorrent::Torrent *const torrent) const
|
||||
{
|
||||
if (m_hashSet == AnyHash) return true;
|
||||
if (m_idSet == AnyID) return true;
|
||||
|
||||
return m_hashSet.contains(torrent->hash());
|
||||
return m_idSet.contains(torrent->id());
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchCategory(const BitTorrent::Torrent *const torrent) const
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace BitTorrent
|
||||
class Torrent;
|
||||
}
|
||||
|
||||
using InfoHashSet = QSet<BitTorrent::InfoHash>;
|
||||
using TorrentIDSet = QSet<BitTorrent::TorrentID>;
|
||||
|
||||
class TorrentFilter
|
||||
{
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
|
||||
// These mean any permutation, including no category / tag.
|
||||
static const QString AnyCategory;
|
||||
static const InfoHashSet AnyHash;
|
||||
static const TorrentIDSet AnyID;
|
||||
static const QString AnyTag;
|
||||
|
||||
static const TorrentFilter DownloadingTorrent;
|
||||
@@ -79,12 +79,12 @@ public:
|
||||
TorrentFilter() = default;
|
||||
// category & tags: pass empty string for uncategorized / untagged torrents.
|
||||
// Pass null string (QString()) to disable filtering (i.e. all torrents).
|
||||
TorrentFilter(Type type, const InfoHashSet &hashSet = AnyHash, const QString &category = AnyCategory, const QString &tag = AnyTag);
|
||||
TorrentFilter(const QString &filter, const InfoHashSet &hashSet = AnyHash, const QString &category = AnyCategory, const QString &tags = AnyTag);
|
||||
TorrentFilter(Type type, const TorrentIDSet &idSet = AnyID, const QString &category = AnyCategory, const QString &tag = AnyTag);
|
||||
TorrentFilter(const QString &filter, const TorrentIDSet &idSet = AnyID, const QString &category = AnyCategory, const QString &tags = AnyTag);
|
||||
|
||||
bool setType(Type type);
|
||||
bool setTypeByName(const QString &filter);
|
||||
bool setHashSet(const InfoHashSet &hashSet);
|
||||
bool setTorrentIDSet(const TorrentIDSet &idSet);
|
||||
bool setCategory(const QString &category);
|
||||
bool setTag(const QString &tag);
|
||||
|
||||
@@ -99,5 +99,5 @@ private:
|
||||
Type m_type {All};
|
||||
QString m_category;
|
||||
QString m_tag;
|
||||
InfoHashSet m_hashSet;
|
||||
TorrentIDSet m_idSet;
|
||||
};
|
||||
|
||||
@@ -78,11 +78,7 @@ QString Utils::Fs::toUniformPath(const QString &path)
|
||||
QString Utils::Fs::fileExtension(const QString &filename)
|
||||
{
|
||||
const QString name = filename.endsWith(QB_EXT)
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
||||
? filename.chopped(QB_EXT.length())
|
||||
#else
|
||||
? filename.left(filename.length() - QB_EXT.length())
|
||||
#endif
|
||||
: filename;
|
||||
return QMimeDatabase().suffixForFileName(name);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <powrprof.h>
|
||||
@@ -49,6 +51,7 @@
|
||||
#include <zlib.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QMimeDatabase>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
#include <QSysInfo>
|
||||
@@ -60,6 +63,7 @@
|
||||
|
||||
#include "base/types.h"
|
||||
#include "base/unicodestrings.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/string.h"
|
||||
|
||||
namespace
|
||||
@@ -80,21 +84,26 @@ namespace
|
||||
// see http://en.wikipedia.org/wiki/Kilobyte
|
||||
// value must be given in bytes
|
||||
// to send numbers instead of strings with suffixes
|
||||
bool splitToFriendlyUnit(const qint64 sizeInBytes, qreal &val, Utils::Misc::SizeUnit &unit)
|
||||
struct SplitToFriendlyUnitResult
|
||||
{
|
||||
if (sizeInBytes < 0) return false;
|
||||
qreal value;
|
||||
Utils::Misc::SizeUnit unit;
|
||||
};
|
||||
|
||||
std::optional<SplitToFriendlyUnitResult> splitToFriendlyUnit(const qint64 bytes)
|
||||
{
|
||||
if (bytes < 0)
|
||||
return std::nullopt;
|
||||
|
||||
int i = 0;
|
||||
val = static_cast<qreal>(sizeInBytes);
|
||||
auto value = static_cast<qreal>(bytes);
|
||||
|
||||
while ((val >= 1024.) && (i <= static_cast<int>(Utils::Misc::SizeUnit::ExbiByte)))
|
||||
while ((value >= 1024) && (i < static_cast<int>(Utils::Misc::SizeUnit::ExbiByte)))
|
||||
{
|
||||
val /= 1024.;
|
||||
value /= 1024;
|
||||
++i;
|
||||
}
|
||||
|
||||
unit = static_cast<Utils::Misc::SizeUnit>(i);
|
||||
return true;
|
||||
return {{value, static_cast<Utils::Misc::SizeUnit>(i)}};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,15 +258,14 @@ QString Utils::Misc::unitString(const SizeUnit unit, const bool isSpeed)
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString Utils::Misc::friendlyUnit(const qint64 bytesValue, const bool isSpeed)
|
||||
QString Utils::Misc::friendlyUnit(const qint64 bytes, const bool isSpeed)
|
||||
{
|
||||
SizeUnit unit;
|
||||
qreal friendlyVal;
|
||||
if (!splitToFriendlyUnit(bytesValue, friendlyVal, unit))
|
||||
const std::optional<SplitToFriendlyUnitResult> result = splitToFriendlyUnit(bytes);
|
||||
if (!result)
|
||||
return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
|
||||
return Utils::String::fromDouble(friendlyVal, friendlyUnitPrecision(unit))
|
||||
return Utils::String::fromDouble(result->value, friendlyUnitPrecision(result->unit))
|
||||
+ QString::fromUtf8(C_NON_BREAKING_SPACE)
|
||||
+ unitString(unit, isSpeed);
|
||||
+ unitString(result->unit, isSpeed);
|
||||
}
|
||||
|
||||
int Utils::Misc::friendlyUnitPrecision(const SizeUnit unit)
|
||||
@@ -284,9 +292,17 @@ qlonglong Utils::Misc::sizeInBytes(qreal size, const Utils::Misc::SizeUnit unit)
|
||||
return size;
|
||||
}
|
||||
|
||||
bool Utils::Misc::isPreviewable(const QString &extension)
|
||||
bool Utils::Misc::isPreviewable(const QString &filename)
|
||||
{
|
||||
static const QSet<QString> multimediaExtensions =
|
||||
const QString mime = QMimeDatabase().mimeTypeForFile(filename, QMimeDatabase::MatchExtension).name();
|
||||
|
||||
if (mime.startsWith(QLatin1String("audio"), Qt::CaseInsensitive)
|
||||
|| mime.startsWith(QLatin1String("video"), Qt::CaseInsensitive))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const QSet<QString> multimediaExtensions =
|
||||
{
|
||||
"3GP",
|
||||
"AAC",
|
||||
@@ -331,7 +347,7 @@ bool Utils::Misc::isPreviewable(const QString &extension)
|
||||
"WMA",
|
||||
"WMV"
|
||||
};
|
||||
return multimediaExtensions.contains(extension.toUpper());
|
||||
return multimediaExtensions.contains(Utils::Fs::fileExtension(filename).toUpper());
|
||||
}
|
||||
|
||||
QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap)
|
||||
|
||||
@@ -73,11 +73,11 @@ namespace Utils::Misc
|
||||
|
||||
// return the best user friendly storage unit (B, KiB, MiB, GiB, TiB)
|
||||
// value must be given in bytes
|
||||
QString friendlyUnit(qint64 bytesValue, bool isSpeed = false);
|
||||
QString friendlyUnit(qint64 bytes, bool isSpeed = false);
|
||||
int friendlyUnitPrecision(SizeUnit unit);
|
||||
qint64 sizeInBytes(qreal size, SizeUnit unit);
|
||||
|
||||
bool isPreviewable(const QString &extension);
|
||||
bool isPreviewable(const QString &filename);
|
||||
|
||||
// Take a number of seconds and return a user-friendly
|
||||
// time duration like "1d 2h 10m".
|
||||
|
||||
@@ -33,10 +33,15 @@
|
||||
|
||||
#include <QCollator>
|
||||
#include <QLocale>
|
||||
#include <QRegExp>
|
||||
#include <QtGlobal>
|
||||
#include <QVector>
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
#include <QRegularExpression>
|
||||
#else
|
||||
#include <QRegExp>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_MACOS) || defined(__MINGW32__)
|
||||
#define QBT_USES_QTHREADSTORAGE
|
||||
#include <QThreadStorage>
|
||||
@@ -181,14 +186,21 @@ QString Utils::String::fromDouble(const double n, const int precision)
|
||||
return QLocale::system().toString(std::floor(n * prec) / prec, 'f', precision);
|
||||
}
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
QString Utils::String::wildcardToRegexPattern(const QString &pattern)
|
||||
{
|
||||
return QRegularExpression::wildcardToRegularExpression(pattern, QRegularExpression::UnanchoredWildcardConversion);
|
||||
}
|
||||
#else
|
||||
// This is marked as internal in QRegExp.cpp, but is exported. The alternative would be to
|
||||
// copy the code from QRegExp::wc2rx().
|
||||
QString qt_regexp_toCanonical(const QString &pattern, QRegExp::PatternSyntax patternSyntax);
|
||||
|
||||
QString Utils::String::wildcardToRegex(const QString &pattern)
|
||||
QString Utils::String::wildcardToRegexPattern(const QString &pattern)
|
||||
{
|
||||
return qt_regexp_toCanonical(pattern, QRegExp::Wildcard);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::optional<bool> Utils::String::parseBool(const QString &string)
|
||||
{
|
||||
@@ -200,6 +212,26 @@ std::optional<bool> Utils::String::parseBool(const QString &string)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<int> Utils::String::parseInt(const QString &string)
|
||||
{
|
||||
bool ok = false;
|
||||
const int result = string.toInt(&ok);
|
||||
if (ok)
|
||||
return result;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<double> Utils::String::parseDouble(const QString &string)
|
||||
{
|
||||
bool ok = false;
|
||||
const double result = string.toDouble(&ok);
|
||||
if (ok)
|
||||
return result;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QString Utils::String::join(const QVector<QStringRef> &strings, const QString &separator)
|
||||
{
|
||||
if (strings.empty())
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Utils::String
|
||||
return (naturalCompare(left, right, caseSensitivity) < 0);
|
||||
}
|
||||
|
||||
QString wildcardToRegex(const QString &pattern);
|
||||
QString wildcardToRegexPattern(const QString &pattern);
|
||||
|
||||
template <typename T>
|
||||
T unquote(const T &str, const QString "es = QChar('"'))
|
||||
@@ -67,6 +67,8 @@ namespace Utils::String
|
||||
}
|
||||
|
||||
std::optional<bool> parseBool(const QString &string);
|
||||
std::optional<int> parseInt(const QString &string);
|
||||
std::optional<double> parseDouble(const QString &string);
|
||||
|
||||
QString join(const QVector<QStringRef> &strings, const QString &separator);
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
|
||||
#define QBT_VERSION_MAJOR 4
|
||||
#define QBT_VERSION_MINOR 3
|
||||
#define QBT_VERSION_BUGFIX 3
|
||||
#define QBT_VERSION_BUILD 0
|
||||
#define QBT_VERSION_BUGFIX 4
|
||||
#define QBT_VERSION_BUILD 1
|
||||
#define QBT_VERSION_STATUS "" // Should be empty for stable releases!
|
||||
|
||||
#define QBT__STRINGIFY(x) #x
|
||||
|
||||
@@ -26,7 +26,7 @@ add_library(qbt_gui STATIC
|
||||
powermanagement/powermanagement.h
|
||||
previewlistdelegate.h
|
||||
previewselectdialog.h
|
||||
progressbardelegate.h
|
||||
progressbarpainter.h
|
||||
properties/downloadedpiecesbar.h
|
||||
properties/peerlistsortmodel.h
|
||||
properties/peerlistwidget.h
|
||||
@@ -106,7 +106,7 @@ add_library(qbt_gui STATIC
|
||||
powermanagement/powermanagement.cpp
|
||||
previewlistdelegate.cpp
|
||||
previewselectdialog.cpp
|
||||
progressbardelegate.cpp
|
||||
progressbarpainter.cpp
|
||||
properties/downloadedpiecesbar.cpp
|
||||
properties/peerlistsortmodel.cpp
|
||||
properties/peerlistwidget.cpp
|
||||
|
||||
@@ -281,6 +281,9 @@
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="textBrowserTranslation">
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="lineWrapMode">
|
||||
<enum>QTextEdit::NoWrap</enum>
|
||||
</property>
|
||||
|
||||
@@ -270,12 +270,12 @@ bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
|
||||
bool AddNewTorrentDialog::loadTorrentImpl()
|
||||
{
|
||||
m_hasMetadata = true;
|
||||
const BitTorrent::InfoHash infoHash = m_torrentInfo.hash();
|
||||
const auto torrentID = BitTorrent::TorrentID::fromInfoHash(m_torrentInfo.infoHash());
|
||||
|
||||
// Prevent showing the dialog if download is already present
|
||||
if (BitTorrent::Session::instance()->isKnownTorrent(infoHash))
|
||||
if (BitTorrent::Session::instance()->isKnownTorrent(torrentID))
|
||||
{
|
||||
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(infoHash);
|
||||
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(torrentID);
|
||||
if (torrent)
|
||||
{
|
||||
if (torrent->isPrivate() || m_torrentInfo.isPrivate())
|
||||
@@ -296,7 +296,7 @@ bool AddNewTorrentDialog::loadTorrentImpl()
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ui->labelHashData->setText(infoHash);
|
||||
m_ui->labelHashData->setText(torrentID.toString());
|
||||
setupTreeview();
|
||||
TMMChanged(m_ui->comboTTM->currentIndex());
|
||||
return true;
|
||||
@@ -312,11 +312,11 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
|
||||
|
||||
m_torrentGuard = std::make_unique<TorrentFileGuard>();
|
||||
|
||||
const BitTorrent::InfoHash infoHash = magnetUri.hash();
|
||||
const auto torrentID = BitTorrent::TorrentID::fromInfoHash(magnetUri.infoHash());
|
||||
// Prevent showing the dialog if download is already present
|
||||
if (BitTorrent::Session::instance()->isKnownTorrent(infoHash))
|
||||
if (BitTorrent::Session::instance()->isKnownTorrent(torrentID))
|
||||
{
|
||||
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(infoHash);
|
||||
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(torrentID);
|
||||
if (torrent)
|
||||
{
|
||||
if (torrent->isPrivate())
|
||||
@@ -348,7 +348,7 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
|
||||
|
||||
BitTorrent::Session::instance()->downloadMetadata(magnetUri);
|
||||
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
||||
m_ui->labelHashData->setText(infoHash);
|
||||
m_ui->labelHashData->setText(torrentID.toString());
|
||||
|
||||
m_magnetURI = magnetUri;
|
||||
return true;
|
||||
@@ -502,12 +502,12 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
|
||||
{
|
||||
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
|
||||
|
||||
const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio)
|
||||
const auto applyPriorities = [this](const BitTorrent::DownloadPriority prio)
|
||||
{
|
||||
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
|
||||
for (const QModelIndex &index : selectedRows)
|
||||
{
|
||||
m_contentModel->setData(
|
||||
m_contentModel->index(index.row(), PRIORITY, index.parent())
|
||||
m_contentModel->setData(index.sibling(index.row(), PRIORITY)
|
||||
, static_cast<int>(prio));
|
||||
}
|
||||
};
|
||||
@@ -517,37 +517,63 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
|
||||
|
||||
if (selectedRows.size() == 1)
|
||||
{
|
||||
QAction *actRename = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."));
|
||||
connect(actRename, &QAction::triggered, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
|
||||
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
QMenu *subMenu = menu->addMenu(tr("Priority"));
|
||||
|
||||
connect(m_ui->actionNotDownloaded, &QAction::triggered, subMenu, [applyPriorities]()
|
||||
subMenu->addAction(tr("Do not download"), subMenu, [applyPriorities]()
|
||||
{
|
||||
applyPriorities(BitTorrent::DownloadPriority::Ignored);
|
||||
});
|
||||
subMenu->addAction(m_ui->actionNotDownloaded);
|
||||
|
||||
connect(m_ui->actionNormal, &QAction::triggered, subMenu, [applyPriorities]()
|
||||
subMenu->addAction(tr("Normal"), subMenu, [applyPriorities]()
|
||||
{
|
||||
applyPriorities(BitTorrent::DownloadPriority::Normal);
|
||||
});
|
||||
subMenu->addAction(m_ui->actionNormal);
|
||||
|
||||
connect(m_ui->actionHigh, &QAction::triggered, subMenu, [applyPriorities]()
|
||||
subMenu->addAction(tr("High"), subMenu, [applyPriorities]()
|
||||
{
|
||||
applyPriorities(BitTorrent::DownloadPriority::High);
|
||||
});
|
||||
subMenu->addAction(m_ui->actionHigh);
|
||||
|
||||
connect(m_ui->actionMaximum, &QAction::triggered, subMenu, [applyPriorities]()
|
||||
subMenu->addAction(tr("Maximum"), subMenu, [applyPriorities]()
|
||||
{
|
||||
applyPriorities(BitTorrent::DownloadPriority::Maximum);
|
||||
});
|
||||
subMenu->addAction(m_ui->actionMaximum);
|
||||
subMenu->addSeparator();
|
||||
subMenu->addAction(tr("By shown file order"), subMenu, [this]()
|
||||
{
|
||||
// Equally distribute the selected items into groups and for each group assign
|
||||
// a download priority that will apply to each item. The number of groups depends on how
|
||||
// many "download priority" are available to be assigned
|
||||
|
||||
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
|
||||
|
||||
const int priorityGroups = 3;
|
||||
const int priorityGroupSize = std::max((selectedRows.length() / priorityGroups), 1);
|
||||
|
||||
for (int i = 0; i < selectedRows.length(); ++i)
|
||||
{
|
||||
auto priority = BitTorrent::DownloadPriority::Ignored;
|
||||
switch (i / priorityGroupSize)
|
||||
{
|
||||
case 0:
|
||||
priority = BitTorrent::DownloadPriority::Maximum;
|
||||
break;
|
||||
case 1:
|
||||
priority = BitTorrent::DownloadPriority::High;
|
||||
break;
|
||||
default:
|
||||
case 2:
|
||||
priority = BitTorrent::DownloadPriority::Normal;
|
||||
break;
|
||||
}
|
||||
|
||||
const QModelIndex &index = selectedRows[i];
|
||||
m_contentModel->setData(index.sibling(index.row(), PRIORITY)
|
||||
, static_cast<int>(priority));
|
||||
}
|
||||
});
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
@@ -603,7 +629,7 @@ void AddNewTorrentDialog::reject()
|
||||
if (!m_hasMetadata)
|
||||
{
|
||||
setMetadataProgressIndicator(false);
|
||||
BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.hash());
|
||||
BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.infoHash().toTorrentID());
|
||||
}
|
||||
|
||||
QDialog::reject();
|
||||
@@ -611,7 +637,7 @@ void AddNewTorrentDialog::reject()
|
||||
|
||||
void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
|
||||
{
|
||||
if (metadata.hash() != m_magnetURI.hash()) return;
|
||||
if (metadata.infoHash() != m_magnetURI.infoHash()) return;
|
||||
|
||||
disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata);
|
||||
|
||||
|
||||
@@ -449,26 +449,6 @@
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
<action name="actionNormal">
|
||||
<property name="text">
|
||||
<string>Normal</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionHigh">
|
||||
<property name="text">
|
||||
<string>High</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMaximum">
|
||||
<property name="text">
|
||||
<string>Maximum</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNotDownloaded">
|
||||
<property name="text">
|
||||
<string>Do not download</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
@@ -82,6 +82,9 @@ namespace
|
||||
DOWNLOAD_TRACKER_FAVICON,
|
||||
SAVE_PATH_HISTORY_LENGTH,
|
||||
ENABLE_SPEED_WIDGET,
|
||||
#ifndef Q_OS_MACOS
|
||||
ENABLE_ICONS_IN_MENUS,
|
||||
#endif
|
||||
// embedded tracker
|
||||
TRACKER_STATUS,
|
||||
TRACKER_PORT,
|
||||
@@ -112,14 +115,11 @@ namespace
|
||||
OUTGOING_PORT_MIN,
|
||||
OUTGOING_PORT_MAX,
|
||||
UPNP_LEASE_DURATION,
|
||||
PEER_TOS,
|
||||
UTP_MIX_MODE,
|
||||
#ifdef HAS_IDN_SUPPORT
|
||||
IDN_SUPPORT,
|
||||
#endif
|
||||
MULTI_CONNECTIONS_PER_IP,
|
||||
#ifdef HAS_HTTPS_TRACKER_VALIDATION
|
||||
VALIDATE_HTTPS_TRACKER_CERTIFICATE,
|
||||
#endif
|
||||
BLOCK_PEERS_ON_PRIVILEGED_PORTS,
|
||||
// seeding
|
||||
CHOKING_ALGORITHM,
|
||||
@@ -224,18 +224,16 @@ void AdvancedSettings::saveAdvancedSettings()
|
||||
session->setOutgoingPortsMax(m_spinBoxOutgoingPortsMax.value());
|
||||
// UPnP lease duration
|
||||
session->setUPnPLeaseDuration(m_spinBoxUPnPLeaseDuration.value());
|
||||
// Type of service
|
||||
session->setPeerToS(m_spinBoxPeerToS.value());
|
||||
// uTP-TCP mixed mode
|
||||
session->setUtpMixedMode(static_cast<BitTorrent::MixedModeAlgorithm>(m_comboBoxUtpMixedMode.currentIndex()));
|
||||
#ifdef HAS_IDN_SUPPORT
|
||||
// Support internationalized domain name (IDN)
|
||||
session->setIDNSupportEnabled(m_checkBoxIDNSupport.isChecked());
|
||||
#endif
|
||||
// multiple connections per IP
|
||||
session->setMultiConnectionsPerIpEnabled(m_checkBoxMultiConnectionsPerIp.isChecked());
|
||||
#ifdef HAS_HTTPS_TRACKER_VALIDATION
|
||||
// Validate HTTPS tracker certificate
|
||||
session->setValidateHTTPSTrackerCertificate(m_checkBoxValidateHTTPSTrackerCertificate.isChecked());
|
||||
#endif
|
||||
// Disallow connection to peers on privileged ports
|
||||
session->setBlockPeersOnPrivilegedPorts(m_checkBoxBlockPeersOnPrivilegedPorts.isChecked());
|
||||
// Recheck torrents on completion
|
||||
@@ -279,6 +277,9 @@ void AdvancedSettings::saveAdvancedSettings()
|
||||
mainWindow->setDownloadTrackerFavicon(m_checkBoxTrackerFavicon.isChecked());
|
||||
AddNewTorrentDialog::setSavePathHistoryLength(m_spinBoxSavePathHistoryLength.value());
|
||||
pref->setSpeedWidgetEnabled(m_checkBoxSpeedWidgetEnabled.isChecked());
|
||||
#ifndef Q_OS_MACOS
|
||||
pref->setIconsInMenusEnabled(m_checkBoxIconsInMenusEnabled.isChecked());
|
||||
#endif
|
||||
|
||||
// Tracker
|
||||
pref->setTrackerPort(m_spinBoxTrackerPort.value());
|
||||
@@ -544,31 +545,33 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
m_spinBoxUPnPLeaseDuration.setSuffix(tr(" s", " seconds"));
|
||||
addRow(UPNP_LEASE_DURATION, (tr("UPnP lease duration [0: Permanent lease]") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#upnp_lease_duration", "(?)"))
|
||||
, &m_spinBoxUPnPLeaseDuration);
|
||||
// Type of service
|
||||
m_spinBoxPeerToS.setMinimum(0);
|
||||
m_spinBoxPeerToS.setMaximum(255);
|
||||
m_spinBoxPeerToS.setValue(session->peerToS());
|
||||
addRow(PEER_TOS, (tr("Type of service (ToS) for connections to peers") + ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#peer_tos", "(?)"))
|
||||
, &m_spinBoxPeerToS);
|
||||
// uTP-TCP mixed mode
|
||||
m_comboBoxUtpMixedMode.addItems({tr("Prefer TCP"), tr("Peer proportional (throttles TCP)")});
|
||||
m_comboBoxUtpMixedMode.setCurrentIndex(static_cast<int>(session->utpMixedMode()));
|
||||
addRow(UTP_MIX_MODE, (tr("%1-TCP mixed mode algorithm", "uTP-TCP mixed mode algorithm").arg(C_UTP)
|
||||
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#mixed_mode_algorithm", "(?)"))
|
||||
, &m_comboBoxUtpMixedMode);
|
||||
#ifdef HAS_IDN_SUPPORT
|
||||
// Support internationalized domain name (IDN)
|
||||
m_checkBoxIDNSupport.setChecked(session->isIDNSupportEnabled());
|
||||
addRow(IDN_SUPPORT, (tr("Support internationalized domain name (IDN)")
|
||||
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_idna", "(?)"))
|
||||
, &m_checkBoxIDNSupport);
|
||||
#endif
|
||||
// multiple connections per IP
|
||||
m_checkBoxMultiConnectionsPerIp.setChecked(session->multiConnectionsPerIpEnabled());
|
||||
addRow(MULTI_CONNECTIONS_PER_IP, (tr("Allow multiple connections from the same IP address")
|
||||
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#allow_multiple_connections_per_ip", "(?)"))
|
||||
, &m_checkBoxMultiConnectionsPerIp);
|
||||
#ifdef HAS_HTTPS_TRACKER_VALIDATION
|
||||
// Validate HTTPS tracker certificate
|
||||
m_checkBoxValidateHTTPSTrackerCertificate.setChecked(session->validateHTTPSTrackerCertificate());
|
||||
addRow(VALIDATE_HTTPS_TRACKER_CERTIFICATE, (tr("Validate HTTPS tracker certificates")
|
||||
+ ' ' + makeLink("https://www.libtorrent.org/reference-Settings.html#validate_https_trackers", "(?)"))
|
||||
, &m_checkBoxValidateHTTPSTrackerCertificate);
|
||||
#endif
|
||||
// Disallow connection to peers on privileged ports
|
||||
m_checkBoxBlockPeersOnPrivilegedPorts.setChecked(session->blockPeersOnPrivilegedPorts());
|
||||
addRow(BLOCK_PEERS_ON_PRIVILEGED_PORTS, (tr("Disallow connection to peers on privileged ports") + ' ' + makeLink("https://libtorrent.org/single-page-ref.html#no_connect_privileged_ports", "(?)")), &m_checkBoxBlockPeersOnPrivilegedPorts);
|
||||
@@ -647,6 +650,11 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
// Enable speed graphs
|
||||
m_checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled());
|
||||
addRow(ENABLE_SPEED_WIDGET, tr("Enable speed graphs"), &m_checkBoxSpeedWidgetEnabled);
|
||||
#ifndef Q_OS_MACOS
|
||||
// Enable icons in menus
|
||||
m_checkBoxIconsInMenusEnabled.setChecked(pref->iconsInMenusEnabled());
|
||||
addRow(ENABLE_ICONS_IN_MENUS, tr("Enable icons in menus"), &m_checkBoxIconsInMenusEnabled);
|
||||
#endif
|
||||
// Tracker State
|
||||
m_checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
|
||||
addRow(TRACKER_STATUS, tr("Enable embedded tracker"), &m_checkBoxTrackerStatus);
|
||||
|
||||
@@ -61,7 +61,7 @@ private:
|
||||
template <typename T> void addRow(int row, const QString &text, T *widget);
|
||||
|
||||
QSpinBox m_spinBoxAsyncIOThreads, m_spinBoxFilePoolSize, m_spinBoxCheckingMemUsage,
|
||||
m_spinBoxSaveResumeDataInterval, m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration,
|
||||
m_spinBoxSaveResumeDataInterval, m_spinBoxOutgoingPortsMin, m_spinBoxOutgoingPortsMax, m_spinBoxUPnPLeaseDuration, m_spinBoxPeerToS,
|
||||
m_spinBoxListRefresh, m_spinBoxTrackerPort, m_spinBoxSendBufferWatermark, m_spinBoxSendBufferLowWatermark,
|
||||
m_spinBoxSendBufferWatermarkFactor, m_spinBoxSocketBacklogSize, m_spinBoxMaxConcurrentHTTPAnnounces, m_spinBoxStopTrackerTimeout,
|
||||
m_spinBoxSavePathHistoryLength, m_spinBoxPeerTurnover, m_spinBoxPeerTurnoverCutoff, m_spinBoxPeerTurnoverInterval;
|
||||
@@ -84,4 +84,8 @@ private:
|
||||
#if defined(Q_OS_WIN)
|
||||
QComboBox m_comboBoxOSMemoryPriority;
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_MACOS
|
||||
QCheckBox m_checkBoxIconsInMenusEnabled;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -79,31 +79,19 @@ void AutoExpandableDialog::showEvent(QShowEvent *e)
|
||||
|
||||
// Show dialog and resize textbox to fit the text
|
||||
// NOTE: For unknown reason QFontMetrics gets more accurate when called from showEvent.
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
int wd = m_ui->textEdit->fontMetrics().horizontalAdvance(m_ui->textEdit->text()) + 4;
|
||||
#else
|
||||
int wd = m_ui->textEdit->fontMetrics().width(m_ui->textEdit->text()) + 4;
|
||||
#endif
|
||||
|
||||
if (!windowTitle().isEmpty())
|
||||
{
|
||||
// not really the font metrics in window title, so we enlarge it a bit,
|
||||
// including the small icon and close button width
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
int w = fontMetrics().horizontalAdvance(windowTitle()) * 1.8;
|
||||
#else
|
||||
int w = fontMetrics().width(windowTitle()) * 1.8;
|
||||
#endif
|
||||
wd = std::max(wd, w);
|
||||
}
|
||||
|
||||
if (!m_ui->textLabel->text().isEmpty())
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
int w = m_ui->textLabel->fontMetrics().horizontalAdvance(m_ui->textLabel->text());
|
||||
#else
|
||||
int w = m_ui->textLabel->fontMetrics().width(m_ui->textLabel->text());
|
||||
#endif
|
||||
wd = std::max(wd, w);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
|
||||
#include "categoryfilterwidget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
@@ -110,54 +109,33 @@ void CategoryFilterWidget::showMenu(const QPoint &)
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
const QAction *addAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("list-add")
|
||||
, tr("Add category..."));
|
||||
connect(addAct, &QAction::triggered, this, &CategoryFilterWidget::addCategory);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add category...")
|
||||
, this, &CategoryFilterWidget::addCategory);
|
||||
|
||||
const auto selectedRows = selectionModel()->selectedRows();
|
||||
if (!selectedRows.empty() && !CategoryFilterModel::isSpecialItem(selectedRows.first()))
|
||||
{
|
||||
if (BitTorrent::Session::instance()->isSubcategoriesEnabled())
|
||||
{
|
||||
const QAction *addSubAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("list-add")
|
||||
, tr("Add subcategory..."));
|
||||
connect(addSubAct, &QAction::triggered, this, &CategoryFilterWidget::addSubcategory);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add subcategory...")
|
||||
, this, &CategoryFilterWidget::addSubcategory);
|
||||
}
|
||||
|
||||
const QAction *editAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("document-edit")
|
||||
, tr("Edit category..."));
|
||||
connect(editAct, &QAction::triggered, this, &CategoryFilterWidget::editCategory);
|
||||
|
||||
const QAction *removeAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("list-remove")
|
||||
, tr("Remove category"));
|
||||
connect(removeAct, &QAction::triggered, this, &CategoryFilterWidget::removeCategory);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("document-edit"), tr("Edit category...")
|
||||
, this, &CategoryFilterWidget::editCategory);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove category")
|
||||
, this, &CategoryFilterWidget::removeCategory);
|
||||
}
|
||||
|
||||
const QAction *removeUnusedAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("list-remove")
|
||||
, tr("Remove unused categories"));
|
||||
connect(removeUnusedAct, &QAction::triggered, this, &CategoryFilterWidget::removeUnusedCategories);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove unused categories")
|
||||
, this, &CategoryFilterWidget::removeUnusedCategories);
|
||||
menu->addSeparator();
|
||||
|
||||
const QAction *startAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("media-playback-start")
|
||||
, tr("Resume torrents"));
|
||||
connect(startAct, &QAction::triggered, this, &CategoryFilterWidget::actionResumeTorrentsTriggered);
|
||||
|
||||
const QAction *pauseAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("media-playback-pause")
|
||||
, tr("Pause torrents"));
|
||||
connect(pauseAct, &QAction::triggered, this, &CategoryFilterWidget::actionPauseTorrentsTriggered);
|
||||
|
||||
const QAction *deleteTorrentsAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("edit-delete")
|
||||
, tr("Delete torrents"));
|
||||
connect(deleteTorrentsAct, &QAction::triggered, this, &CategoryFilterWidget::actionDeleteTorrentsTriggered);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("media-playback-start"), tr("Resume torrents")
|
||||
, this, &CategoryFilterWidget::actionResumeTorrentsTriggered);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("media-playback-pause"), tr("Pause torrents")
|
||||
, this, &CategoryFilterWidget::actionPauseTorrentsTriggered);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-delete"), tr("Delete torrents")
|
||||
, this, &CategoryFilterWidget::actionDeleteTorrentsTriggered);
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
@@ -91,12 +91,12 @@ void ExecutionLogWidget::displayContextMenu(const QPoint &pos, const LogListView
|
||||
// only show copy action if any of the row is selected
|
||||
if (view->currentIndex().isValid())
|
||||
{
|
||||
const QAction *copyAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy"));
|
||||
connect(copyAct, &QAction::triggered, view, &LogListView::copySelection);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy")
|
||||
, view, &LogListView::copySelection);
|
||||
}
|
||||
|
||||
const QAction *clearAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear"));
|
||||
connect(clearAct, &QAction::triggered, model, &BaseLogModel::reset);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear")
|
||||
, model, &BaseLogModel::reset);
|
||||
|
||||
menu->popup(view->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ HEADERS += \
|
||||
$$PWD/powermanagement/powermanagement.h \
|
||||
$$PWD/previewlistdelegate.h \
|
||||
$$PWD/previewselectdialog.h \
|
||||
$$PWD/progressbardelegate.h \
|
||||
$$PWD/progressbarpainter.h \
|
||||
$$PWD/properties/downloadedpiecesbar.h \
|
||||
$$PWD/properties/peerlistsortmodel.h \
|
||||
$$PWD/properties/peerlistwidget.h \
|
||||
@@ -107,7 +107,7 @@ SOURCES += \
|
||||
$$PWD/powermanagement/powermanagement.cpp \
|
||||
$$PWD/previewlistdelegate.cpp \
|
||||
$$PWD/previewselectdialog.cpp \
|
||||
$$PWD/progressbardelegate.cpp \
|
||||
$$PWD/progressbarpainter.cpp \
|
||||
$$PWD/properties/downloadedpiecesbar.cpp \
|
||||
$$PWD/properties/peerlistsortmodel.cpp \
|
||||
$$PWD/properties/peerlistwidget.cpp \
|
||||
|
||||
@@ -46,11 +46,7 @@ namespace
|
||||
|
||||
int horizontalAdvance(const QFontMetrics &fontMetrics, const QString &text)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
return fontMetrics.horizontalAdvance(text);
|
||||
#else
|
||||
return fontMetrics.width(text);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString logText(const QModelIndex &index)
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <QActionGroup>
|
||||
#include <QClipboard>
|
||||
#include <QCloseEvent>
|
||||
#include <QDebug>
|
||||
@@ -90,7 +91,6 @@
|
||||
#include "statsdialog.h"
|
||||
#include "statusbar.h"
|
||||
#include "torrentcreatordialog.h"
|
||||
|
||||
#include "transferlistfilterswidget.h"
|
||||
#include "transferlistmodel.h"
|
||||
#include "transferlistwidget.h"
|
||||
@@ -105,6 +105,8 @@
|
||||
#include "programupdater.h"
|
||||
#endif
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
namespace
|
||||
{
|
||||
#define SETTINGS_KEY(name) "GUI/" name
|
||||
@@ -148,9 +150,6 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
, m_posInitialized(false)
|
||||
, m_forceExit(false)
|
||||
, m_unlockDlgShowing(false)
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
, m_wasUpdateCheckEnabled(false)
|
||||
#endif
|
||||
, m_hasPython(false)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
@@ -195,10 +194,8 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon("preferences-web-browser-cookies"));
|
||||
|
||||
auto *lockMenu = new QMenu(this);
|
||||
QAction *defineUiLockPasswdAct = lockMenu->addAction(tr("&Set Password"));
|
||||
connect(defineUiLockPasswdAct, &QAction::triggered, this, &MainWindow::defineUILockPassword);
|
||||
QAction *clearUiLockPasswdAct = lockMenu->addAction(tr("&Clear Password"));
|
||||
connect(clearUiLockPasswdAct, &QAction::triggered, this, &MainWindow::clearUILockPassword);
|
||||
lockMenu->addAction(tr("&Set Password"), this, &MainWindow::defineUILockPassword);
|
||||
lockMenu->addAction(tr("&Clear Password"), this, &MainWindow::clearUILockPassword);
|
||||
m_ui->actionLock->setMenu(lockMenu);
|
||||
|
||||
// Creating Bittorrent session
|
||||
@@ -319,11 +316,11 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
m_programUpdateTimer = new QTimer(this);
|
||||
m_programUpdateTimer->setInterval(60 * 60 * 1000);
|
||||
m_programUpdateTimer->setSingleShot(true);
|
||||
connect(m_programUpdateTimer, &QTimer::timeout, this, &MainWindow::checkProgramUpdate);
|
||||
connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, &MainWindow::checkProgramUpdate);
|
||||
connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); });
|
||||
|
||||
// trigger an early check on startup
|
||||
if (pref->isUpdateCheckEnabled())
|
||||
checkProgramUpdate(false);
|
||||
#else
|
||||
m_ui->actionCheckForUpdates->setVisible(false);
|
||||
#endif
|
||||
@@ -550,16 +547,11 @@ void MainWindow::addToolbarContextMenu()
|
||||
m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
|
||||
|
||||
QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"));
|
||||
connect(iconsOnly, &QAction::triggered, this, &MainWindow::toolbarIconsOnly);
|
||||
QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"));
|
||||
connect(textOnly, &QAction::triggered, this, &MainWindow::toolbarTextOnly);
|
||||
QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"));
|
||||
connect(textBesideIcons, &QAction::triggered, this, &MainWindow::toolbarTextBeside);
|
||||
QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"));
|
||||
connect(textUnderIcons, &QAction::triggered, this, &MainWindow::toolbarTextUnder);
|
||||
QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"));
|
||||
connect(followSystemStyle, &QAction::triggered, this, &MainWindow::toolbarFollowSystem);
|
||||
QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"), this, &MainWindow::toolbarIconsOnly);
|
||||
QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"), this, &MainWindow::toolbarTextOnly);
|
||||
QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"), this, &MainWindow::toolbarTextBeside);
|
||||
QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"), this, &MainWindow::toolbarTextUnder);
|
||||
QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"), this, &MainWindow::toolbarFollowSystem);
|
||||
|
||||
auto *textPositionGroup = new QActionGroup(m_toolbarMenu);
|
||||
textPositionGroup->addAction(iconsOnly);
|
||||
@@ -811,7 +803,8 @@ void MainWindow::cleanup()
|
||||
m_preventTimer->stop();
|
||||
|
||||
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
|
||||
m_programUpdateTimer->stop();
|
||||
if (m_programUpdateTimer)
|
||||
m_programUpdateTimer->stop();
|
||||
#endif
|
||||
|
||||
// remove all child widgets
|
||||
@@ -884,36 +877,36 @@ void MainWindow::createKeyboardShortcuts()
|
||||
m_ui->actionOpen->setShortcut(QKeySequence::Open);
|
||||
m_ui->actionDelete->setShortcut(QKeySequence::Delete);
|
||||
m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
|
||||
m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_O);
|
||||
m_ui->actionExit->setShortcut(Qt::CTRL + Qt::Key_Q);
|
||||
m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
|
||||
m_ui->actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
|
||||
#ifdef Q_OS_MACOS
|
||||
m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
|
||||
#else
|
||||
m_ui->actionCloseWindow->setVisible(false);
|
||||
#endif
|
||||
|
||||
const auto *switchTransferShortcut = new QShortcut(Qt::ALT + Qt::Key_1, this);
|
||||
const auto *switchTransferShortcut = new QShortcut((Qt::ALT | Qt::Key_1), this);
|
||||
connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab);
|
||||
const auto *switchSearchShortcut = new QShortcut(Qt::ALT + Qt::Key_2, this);
|
||||
const auto *switchSearchShortcut = new QShortcut((Qt::ALT | Qt::Key_2), this);
|
||||
connect(switchSearchShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displaySearchTab));
|
||||
const auto *switchRSSShortcut = new QShortcut(Qt::ALT + Qt::Key_3, this);
|
||||
const auto *switchRSSShortcut = new QShortcut((Qt::ALT | Qt::Key_3), this);
|
||||
connect(switchRSSShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displayRSSTab));
|
||||
const auto *switchExecutionLogShortcut = new QShortcut(Qt::ALT + Qt::Key_4, this);
|
||||
const auto *switchExecutionLogShortcut = new QShortcut((Qt::ALT | Qt::Key_4), this);
|
||||
connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab);
|
||||
const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget);
|
||||
connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
|
||||
|
||||
m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
|
||||
m_ui->actionOptions->setShortcut(Qt::ALT + Qt::Key_O);
|
||||
m_ui->actionStatistics->setShortcut(Qt::CTRL + Qt::Key_I);
|
||||
m_ui->actionStart->setShortcut(Qt::CTRL + Qt::Key_S);
|
||||
m_ui->actionStartAll->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_S);
|
||||
m_ui->actionPause->setShortcut(Qt::CTRL + Qt::Key_P);
|
||||
m_ui->actionPauseAll->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_P);
|
||||
m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus);
|
||||
m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL + Qt::Key_Minus);
|
||||
m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL + Qt::Key_Plus);
|
||||
m_ui->actionTopQueuePos->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Plus);
|
||||
m_ui->actionOptions->setShortcut(Qt::ALT | Qt::Key_O);
|
||||
m_ui->actionStatistics->setShortcut(Qt::CTRL | Qt::Key_I);
|
||||
m_ui->actionStart->setShortcut(Qt::CTRL | Qt::Key_S);
|
||||
m_ui->actionStartAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
|
||||
m_ui->actionPause->setShortcut(Qt::CTRL | Qt::Key_P);
|
||||
m_ui->actionPauseAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
|
||||
m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus);
|
||||
m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Minus);
|
||||
m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Plus);
|
||||
m_ui->actionTopQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus);
|
||||
#ifdef Q_OS_MACOS
|
||||
m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M);
|
||||
addAction(m_ui->actionMinimize);
|
||||
@@ -966,7 +959,7 @@ void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *co
|
||||
Preferences *const pref = Preferences::instance();
|
||||
if (pref->recursiveDownloadDisabled()) return;
|
||||
|
||||
const auto torrentHash = torrent->hash();
|
||||
const auto torrentID = torrent->id();
|
||||
|
||||
QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
|
||||
, tr("The torrent '%1' contains torrent files, do you want to proceed with their download?").arg(torrent->name())
|
||||
@@ -977,10 +970,10 @@ void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *co
|
||||
const QPushButton *yes = confirmBox->addButton(tr("Yes"), QMessageBox::YesRole);
|
||||
/*QPushButton *no = */ confirmBox->addButton(tr("No"), QMessageBox::NoRole);
|
||||
const QPushButton *never = confirmBox->addButton(tr("Never"), QMessageBox::NoRole);
|
||||
connect(confirmBox, &QMessageBox::buttonClicked, this, [torrentHash, yes, never](const QAbstractButton *button)
|
||||
connect(confirmBox, &QMessageBox::buttonClicked, this, [torrentID, yes, never](const QAbstractButton *button)
|
||||
{
|
||||
if (button == yes)
|
||||
BitTorrent::Session::instance()->recursiveTorrentDownload(torrentHash);
|
||||
BitTorrent::Session::instance()->recursiveTorrentDownload(torrentID);
|
||||
if (button == never)
|
||||
Preferences::instance()->disableRecursiveDownload();
|
||||
});
|
||||
@@ -1590,15 +1583,21 @@ void MainWindow::loadPreferences(const bool configureSession)
|
||||
m_propertiesWidget->reloadPreferences();
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
if (pref->isUpdateCheckEnabled() && !m_wasUpdateCheckEnabled)
|
||||
if (pref->isUpdateCheckEnabled())
|
||||
{
|
||||
m_wasUpdateCheckEnabled = true;
|
||||
checkProgramUpdate();
|
||||
if (!m_programUpdateTimer)
|
||||
{
|
||||
m_programUpdateTimer = new QTimer(this);
|
||||
m_programUpdateTimer->setInterval(24h);
|
||||
m_programUpdateTimer->setSingleShot(true);
|
||||
connect(m_programUpdateTimer, &QTimer::timeout, this, [this]() { checkProgramUpdate(false); });
|
||||
m_programUpdateTimer->start();
|
||||
}
|
||||
}
|
||||
else if (!pref->isUpdateCheckEnabled() && m_wasUpdateCheckEnabled)
|
||||
else
|
||||
{
|
||||
m_wasUpdateCheckEnabled = false;
|
||||
m_programUpdateTimer->stop();
|
||||
delete m_programUpdateTimer;
|
||||
m_programUpdateTimer = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1804,21 +1803,21 @@ void MainWindow::on_actionOptions_triggered()
|
||||
|
||||
void MainWindow::on_actionTopToolBar_triggered()
|
||||
{
|
||||
const bool isVisible = static_cast<QAction*>(sender())->isChecked();
|
||||
const bool isVisible = static_cast<QAction *>(sender())->isChecked();
|
||||
m_ui->toolBar->setVisible(isVisible);
|
||||
Preferences::instance()->setToolbarDisplayed(isVisible);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionShowStatusbar_triggered()
|
||||
{
|
||||
const bool isVisible = static_cast<QAction*>(sender())->isChecked();
|
||||
const bool isVisible = static_cast<QAction *>(sender())->isChecked();
|
||||
Preferences::instance()->setStatusbarDisplayed(isVisible);
|
||||
showStatusBar(isVisible);
|
||||
}
|
||||
|
||||
void MainWindow::on_actionSpeedInTitleBar_triggered()
|
||||
{
|
||||
m_displaySpeedInTitle = static_cast<QAction * >(sender())->isChecked();
|
||||
m_displaySpeedInTitle = static_cast<QAction *>(sender())->isChecked();
|
||||
Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
|
||||
if (m_displaySpeedInTitle)
|
||||
reloadSessionStats();
|
||||
@@ -1904,35 +1903,56 @@ void MainWindow::on_actionDownloadFromURL_triggered()
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
void MainWindow::handleUpdateCheckFinished(bool updateAvailable, QString newVersion, bool invokedByUser)
|
||||
void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool invokedByUser)
|
||||
{
|
||||
QMessageBox::StandardButton answer = QMessageBox::Yes;
|
||||
if (updateAvailable)
|
||||
{
|
||||
answer = QMessageBox::question(this, tr("qBittorrent Update Available")
|
||||
, tr("A new version is available.") + "<br/>"
|
||||
+ tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>"
|
||||
+ QString::fromLatin1("<a href=\"https://www.qbittorrent.org/news.php\">%1</a>").arg(tr("Open changelog..."))
|
||||
, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
if (answer == QMessageBox::Yes)
|
||||
{
|
||||
// The user want to update, let's download the update
|
||||
ProgramUpdater *updater = dynamic_cast<ProgramUpdater * >(sender());
|
||||
updater->updateProgram();
|
||||
}
|
||||
}
|
||||
else if (invokedByUser)
|
||||
{
|
||||
QMessageBox::information(this, tr("Already Using the Latest qBittorrent Version"),
|
||||
tr("No updates available.\nYou are already using the latest version."));
|
||||
}
|
||||
sender()->deleteLater();
|
||||
m_ui->actionCheckForUpdates->setEnabled(true);
|
||||
m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
|
||||
m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates"));
|
||||
// Don't bother the user again in this session if he chose to ignore the update
|
||||
if (Preferences::instance()->isUpdateCheckEnabled() && (answer == QMessageBox::Yes))
|
||||
m_programUpdateTimer->start();
|
||||
|
||||
const auto cleanup = [this, updater]()
|
||||
{
|
||||
if (m_programUpdateTimer)
|
||||
m_programUpdateTimer->start();
|
||||
updater->deleteLater();
|
||||
};
|
||||
|
||||
const QString newVersion = updater->getNewVersion();
|
||||
if (!newVersion.isEmpty())
|
||||
{
|
||||
const QString msg {tr("A new version is available.") + "<br/>"
|
||||
+ tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>"
|
||||
+ QString::fromLatin1("<a href=\"https://www.qbittorrent.org/news.php\">%1</a>").arg(tr("Open changelog..."))};
|
||||
auto *msgBox = new QMessageBox {QMessageBox::Question, tr("qBittorrent Update Available"), msg
|
||||
, (QMessageBox::Yes | QMessageBox::No), this};
|
||||
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||
msgBox->setAttribute(Qt::WA_ShowWithoutActivating);
|
||||
msgBox->setDefaultButton(QMessageBox::Yes);
|
||||
connect(msgBox, &QMessageBox::buttonClicked, this, [msgBox, updater](QAbstractButton *button)
|
||||
{
|
||||
if (msgBox->buttonRole(button) == QMessageBox::YesRole)
|
||||
{
|
||||
updater->updateProgram();
|
||||
}
|
||||
});
|
||||
connect(msgBox, &QDialog::finished, this, cleanup);
|
||||
msgBox->open();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (invokedByUser)
|
||||
{
|
||||
auto *msgBox = new QMessageBox {QMessageBox::Information, QLatin1String("qBittorrent")
|
||||
, tr("No updates available.\nYou are already using the latest version.")
|
||||
, QMessageBox::Ok, this};
|
||||
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(msgBox, &QDialog::finished, this, cleanup);
|
||||
msgBox->open();
|
||||
}
|
||||
else
|
||||
{
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2091,18 +2111,23 @@ QIcon MainWindow::getSystrayIcon() const
|
||||
#endif // Q_OS_MACOS
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
void MainWindow::checkProgramUpdate()
|
||||
void MainWindow::checkProgramUpdate(const bool invokedByUser)
|
||||
{
|
||||
m_programUpdateTimer->stop(); // If the user had clicked the menu item
|
||||
if (m_programUpdateTimer)
|
||||
m_programUpdateTimer->stop();
|
||||
|
||||
m_ui->actionCheckForUpdates->setEnabled(false);
|
||||
m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
|
||||
m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
|
||||
bool invokedByUser = m_ui->actionCheckForUpdates == qobject_cast<QAction * >(sender());
|
||||
ProgramUpdater *updater = new ProgramUpdater(this, invokedByUser);
|
||||
connect(updater, &ProgramUpdater::updateCheckFinished, this, &MainWindow::handleUpdateCheckFinished);
|
||||
|
||||
auto *updater = new ProgramUpdater(this);
|
||||
connect(updater, &ProgramUpdater::updateCheckFinished
|
||||
, this, [this, invokedByUser, updater]()
|
||||
{
|
||||
handleUpdateCheckFinished(updater, invokedByUser);
|
||||
});
|
||||
updater->checkForUpdates();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
@@ -47,6 +47,7 @@ class ExecutionLogWidget;
|
||||
class LineEdit;
|
||||
class OptionsDialog;
|
||||
class PowerManagement;
|
||||
class ProgramUpdater;
|
||||
class PropertiesWidget;
|
||||
class RSSWidget;
|
||||
class SearchWidget;
|
||||
@@ -134,9 +135,6 @@ private slots:
|
||||
void finishedTorrent(BitTorrent::Torrent *const torrent) const;
|
||||
void askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *const torrent);
|
||||
void optionsSaved();
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
void handleUpdateCheckFinished(bool updateAvailable, QString newVersion, bool invokedByUser);
|
||||
#endif
|
||||
void toggleAlternativeSpeeds();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -177,9 +175,7 @@ private slots:
|
||||
void on_actionLock_triggered();
|
||||
// Check for unpaused downloading or seeding torrents and prevent system suspend/sleep according to preferences
|
||||
void updatePowerManagementState();
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
void checkProgramUpdate();
|
||||
#endif
|
||||
|
||||
void toolbarMenuRequested(const QPoint &point);
|
||||
void toolbarIconsOnly();
|
||||
void toolbarTextOnly();
|
||||
@@ -252,10 +248,13 @@ private:
|
||||
// Power Management
|
||||
PowerManagement *m_pwr;
|
||||
QTimer *m_preventTimer;
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
QTimer *m_programUpdateTimer;
|
||||
bool m_wasUpdateCheckEnabled;
|
||||
#endif
|
||||
bool m_hasPython;
|
||||
QMenu *m_toolbarMenu;
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
void checkProgramUpdate(bool invokedByUser);
|
||||
void handleUpdateCheckFinished(ProgramUpdater *updater, bool invokedByUser);
|
||||
|
||||
QTimer *m_programUpdateTimer = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -211,8 +211,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
||||
|
||||
m_ui->IpFilterRefreshBtn->setIcon(UIThemeManager::instance()->getIcon("view-refresh"));
|
||||
|
||||
m_ui->labelGlobalRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow_off")), this, 16));
|
||||
m_ui->labelAltRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow")), this, 16));
|
||||
m_ui->labelGlobalRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow_off")), this, 24));
|
||||
m_ui->labelAltRate->setPixmap(Utils::Gui::scaledPixmap(UIThemeManager::instance()->getIcon(QLatin1String("slow")), this, 24));
|
||||
|
||||
m_ui->deleteTorrentWarningIcon->setPixmap(QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical).pixmap(16, 16));
|
||||
m_ui->deleteTorrentWarningIcon->hide();
|
||||
@@ -233,15 +233,8 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
||||
m_ui->hsplitter->setCollapsible(0, false);
|
||||
m_ui->hsplitter->setCollapsible(1, false);
|
||||
// Get apply button in button box
|
||||
const QList<QAbstractButton *> buttons = m_ui->buttonBox->buttons();
|
||||
for (QAbstractButton *button : buttons)
|
||||
{
|
||||
if (m_ui->buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole)
|
||||
{
|
||||
m_applyButton = button;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_applyButton = m_ui->buttonBox->button(QDialogButtonBox::Apply);
|
||||
connect(m_applyButton, &QPushButton::clicked, this, &OptionsDialog::applySettings);
|
||||
|
||||
m_ui->scanFoldersView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
m_ui->scanFoldersView->setModel(ScanFoldersModel::instance());
|
||||
@@ -249,7 +242,6 @@ OptionsDialog::OptionsDialog(QWidget *parent)
|
||||
connect(ScanFoldersModel::instance(), &QAbstractListModel::dataChanged, this, &ThisType::enableApplyButton);
|
||||
connect(m_ui->scanFoldersView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ThisType::handleScanFolderViewSelectionChanged);
|
||||
|
||||
connect(m_ui->buttonBox, &QDialogButtonBox::clicked, this, &OptionsDialog::applySettings);
|
||||
// Languages supported
|
||||
initializeLanguageCombo();
|
||||
|
||||
@@ -761,7 +753,7 @@ void OptionsDialog::saveOptions()
|
||||
pref->setMailNotificationSMTPPassword(m_ui->mailNotifPassword->text());
|
||||
pref->setAutoRunEnabled(m_ui->autoRunBox->isChecked());
|
||||
pref->setAutoRunProgram(m_ui->lineEditAutoRun->text().trimmed());
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) && defined(Q_OS_WIN)
|
||||
#if defined(Q_OS_WIN)
|
||||
pref->setAutoRunConsoleEnabled(m_ui->autoRunConsole->isChecked());
|
||||
#endif
|
||||
pref->setActionOnDblClOnTorrentDl(getActionOnDblClOnTorrentDl());
|
||||
@@ -1051,7 +1043,7 @@ void OptionsDialog::loadOptions()
|
||||
|
||||
m_ui->autoRunBox->setChecked(pref->isAutoRunEnabled());
|
||||
m_ui->lineEditAutoRun->setText(pref->getAutoRunProgram());
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) && defined(Q_OS_WIN)
|
||||
#if defined(Q_OS_WIN)
|
||||
m_ui->autoRunConsole->setChecked(pref->isAutoRunConsoleEnabled());
|
||||
#else
|
||||
m_ui->autoRunConsole->hide();
|
||||
@@ -1439,27 +1431,24 @@ void OptionsDialog::on_buttonBox_accepted()
|
||||
accept();
|
||||
}
|
||||
|
||||
void OptionsDialog::applySettings(QAbstractButton *button)
|
||||
void OptionsDialog::applySettings()
|
||||
{
|
||||
if (button == m_applyButton)
|
||||
if (!schedTimesOk())
|
||||
{
|
||||
if (!schedTimesOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
|
||||
return;
|
||||
}
|
||||
if (!webUIAuthenticationOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
}
|
||||
if (!isAlternativeWebUIPathValid())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
}
|
||||
saveOptions();
|
||||
m_ui->tabSelection->setCurrentRow(TAB_SPEED);
|
||||
return;
|
||||
}
|
||||
if (!webUIAuthenticationOk())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
}
|
||||
if (!isAlternativeWebUIPathValid())
|
||||
{
|
||||
m_ui->tabSelection->setCurrentRow(TAB_WEBUI);
|
||||
return;
|
||||
}
|
||||
saveOptions();
|
||||
}
|
||||
|
||||
void OptionsDialog::closeEvent(QCloseEvent *e)
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
|
||||
#include "base/settingvalue.h"
|
||||
|
||||
class QAbstractButton;
|
||||
class QCloseEvent;
|
||||
class QListWidgetItem;
|
||||
|
||||
@@ -95,7 +94,7 @@ private slots:
|
||||
void on_buttonBox_accepted();
|
||||
void closeEvent(QCloseEvent *e) override;
|
||||
void on_buttonBox_rejected();
|
||||
void applySettings(QAbstractButton *button);
|
||||
void applySettings();
|
||||
void enableApplyButton();
|
||||
void toggleComboRatioLimitAct();
|
||||
void changePage(QListWidgetItem *, QListWidgetItem *);
|
||||
@@ -181,7 +180,7 @@ private:
|
||||
SettingValue<QSize> m_storeDialogSize;
|
||||
SettingValue<QStringList> m_storeHSplitterSize;
|
||||
|
||||
QAbstractButton *m_applyButton;
|
||||
QPushButton *m_applyButton;
|
||||
|
||||
AdvancedSettings *m_advancedSettings;
|
||||
|
||||
|
||||
@@ -28,18 +28,11 @@
|
||||
|
||||
#include "previewlistdelegate.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QStyleOptionProgressBar>
|
||||
#include <QStyleOptionViewItem>
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
#include <QProxyStyle>
|
||||
#endif
|
||||
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "previewselectdialog.h"
|
||||
|
||||
PreviewListDelegate::PreviewListDelegate(QObject *parent)
|
||||
@@ -61,30 +54,19 @@ void PreviewListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
|
||||
break;
|
||||
|
||||
case PreviewSelectDialog::PROGRESS:
|
||||
{
|
||||
{
|
||||
const qreal progress = (index.data().toReal() * 100);
|
||||
const QString text = (progress >= 100)
|
||||
? QString::fromLatin1("100%")
|
||||
: (Utils::String::fromDouble(progress, 1) + '%');
|
||||
|
||||
QStyleOptionProgressBar newopt;
|
||||
newopt.rect = opt.rect;
|
||||
newopt.text = ((progress == 100) ? QString("100%") : (Utils::String::fromDouble(progress, 1) + '%'));
|
||||
newopt.progress = static_cast<int>(progress);
|
||||
newopt.maximum = 100;
|
||||
newopt.minimum = 0;
|
||||
newopt.state |= QStyle::State_Enabled;
|
||||
newopt.textVisible = true;
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
// XXX: To avoid having the progress text on the right of the bar
|
||||
QProxyStyle st("fusion");
|
||||
st.drawControl(QStyle::CE_ProgressBar, &newopt, painter, 0);
|
||||
#else
|
||||
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &newopt, painter);
|
||||
#endif
|
||||
m_progressBarPainter.paint(painter, option, text, static_cast<int>(progress));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
QItemDelegate::paint(painter, option, index);
|
||||
break;
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
|
||||
#include <QItemDelegate>
|
||||
|
||||
#include "progressbarpainter.h"
|
||||
|
||||
class PreviewListDelegate final : public QItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -38,7 +40,9 @@ class PreviewListDelegate final : public QItemDelegate
|
||||
public:
|
||||
explicit PreviewListDelegate(QObject *parent = nullptr);
|
||||
|
||||
private:
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
QWidget *createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const override;
|
||||
|
||||
private:
|
||||
ProgressBarPainter m_progressBarPainter;
|
||||
};
|
||||
|
||||
@@ -90,9 +90,8 @@ PreviewSelectDialog::PreviewSelectDialog(QWidget *parent, const BitTorrent::Torr
|
||||
{
|
||||
QString fileName = torrent->fileName(i);
|
||||
if (fileName.endsWith(QB_EXT))
|
||||
fileName.chop(4);
|
||||
QString extension = Utils::Fs::fileExtension(fileName).toUpper();
|
||||
if (Utils::Misc::isPreviewable(extension))
|
||||
fileName.chop(QB_EXT.length());
|
||||
if (Utils::Misc::isPreviewable(fileName))
|
||||
{
|
||||
int row = m_previewListModel->rowCount();
|
||||
m_previewListModel->insertRow(row);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -36,7 +37,6 @@
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
#include <QXmlStreamReader>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
@@ -44,56 +44,82 @@
|
||||
#endif
|
||||
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/utils/version.h"
|
||||
#include "base/version.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const QString RSS_URL {QStringLiteral("https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml")};
|
||||
bool isVersionMoreRecent(const QString &remoteVersion)
|
||||
{
|
||||
using Version = Utils::Version<int, 4, 3>;
|
||||
|
||||
QString getStringValue(QXmlStreamReader &xml);
|
||||
try
|
||||
{
|
||||
const Version newVersion {remoteVersion};
|
||||
const Version currentVersion {QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD};
|
||||
if (newVersion == currentVersion)
|
||||
{
|
||||
const bool isDevVersion = QString::fromLatin1(QBT_VERSION_STATUS).contains(
|
||||
QRegularExpression(QLatin1String("(alpha|beta|rc)")));
|
||||
if (isDevVersion)
|
||||
return true;
|
||||
}
|
||||
return (newVersion > currentVersion);
|
||||
}
|
||||
catch (const std::runtime_error &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProgramUpdater::ProgramUpdater(QObject *parent, bool invokedByUser)
|
||||
: QObject(parent)
|
||||
, m_invokedByUser(invokedByUser)
|
||||
{
|
||||
}
|
||||
|
||||
void ProgramUpdater::checkForUpdates()
|
||||
void ProgramUpdater::checkForUpdates() const
|
||||
{
|
||||
const auto RSS_URL = QString::fromLatin1("https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml");
|
||||
// Don't change this User-Agent. In case our updater goes haywire,
|
||||
// the filehost can identify it and contact us.
|
||||
Net::DownloadManager::instance()->download(
|
||||
Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)")
|
||||
, this, &ProgramUpdater::rssDownloadFinished);
|
||||
Net::DownloadRequest(RSS_URL).userAgent("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)")
|
||||
, this, &ProgramUpdater::rssDownloadFinished);
|
||||
}
|
||||
|
||||
QString ProgramUpdater::getNewVersion() const
|
||||
{
|
||||
return m_newVersion;
|
||||
}
|
||||
|
||||
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
||||
{
|
||||
|
||||
if (result.status != Net::DownloadStatus::Success)
|
||||
{
|
||||
qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString;
|
||||
emit updateCheckFinished(false, QString(), m_invokedByUser);
|
||||
emit updateCheckFinished();
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug("Finished downloading the new qBittorrent updates RSS");
|
||||
|
||||
const auto getStringValue = [](QXmlStreamReader &xml) -> QString
|
||||
{
|
||||
xml.readNext();
|
||||
return (xml.isCharacters() && !xml.isWhitespace())
|
||||
? xml.text().toString()
|
||||
: QString {};
|
||||
};
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
const QString OS_TYPE {"Mac OS X"};
|
||||
#elif defined(Q_OS_WIN)
|
||||
const QString OS_TYPE
|
||||
{(::IsWindows7OrGreater()
|
||||
&& QSysInfo::currentCpuArchitecture().endsWith("64"))
|
||||
const QString OS_TYPE {(::IsWindows7OrGreater()
|
||||
&& QSysInfo::currentCpuArchitecture().endsWith("64"))
|
||||
? "Windows x64" : "Windows"};
|
||||
#endif
|
||||
|
||||
QString version;
|
||||
QXmlStreamReader xml(result.data);
|
||||
bool inItem = false;
|
||||
QString version;
|
||||
QString updateLink;
|
||||
QString type;
|
||||
QXmlStreamReader xml(result.data);
|
||||
|
||||
while (!xml.atEnd())
|
||||
{
|
||||
@@ -121,7 +147,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
||||
{
|
||||
qDebug("Detected version is %s", qUtf8Printable(version));
|
||||
if (isVersionMoreRecent(version))
|
||||
m_updateUrl = updateLink;
|
||||
{
|
||||
m_newVersion = version;
|
||||
m_updateURL = updateLink;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -134,51 +163,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
|
||||
}
|
||||
}
|
||||
|
||||
emit updateCheckFinished(!m_updateUrl.isEmpty(), version, m_invokedByUser);
|
||||
emit updateCheckFinished();
|
||||
}
|
||||
|
||||
void ProgramUpdater::updateProgram()
|
||||
bool ProgramUpdater::updateProgram() const
|
||||
{
|
||||
Q_ASSERT(!m_updateUrl.isEmpty());
|
||||
QDesktopServices::openUrl(m_updateUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
bool ProgramUpdater::isVersionMoreRecent(const QString &remoteVersion) const
|
||||
{
|
||||
const QRegularExpressionMatch regVerMatch = QRegularExpression("([0-9.]+)").match(QBT_VERSION);
|
||||
if (regVerMatch.hasMatch())
|
||||
{
|
||||
const QString localVersion = regVerMatch.captured(1);
|
||||
const QVector<QStringRef> remoteParts = remoteVersion.splitRef('.');
|
||||
const QVector<QStringRef> localParts = localVersion.splitRef('.');
|
||||
|
||||
for (int i = 0; i < qMin(remoteParts.size(), localParts.size()); ++i)
|
||||
{
|
||||
if (remoteParts[i].toInt() > localParts[i].toInt())
|
||||
return true;
|
||||
if (remoteParts[i].toInt() < localParts[i].toInt())
|
||||
return false;
|
||||
}
|
||||
// Compared parts were equal, if remote version is longer, then it's more recent (2.9.2.1 > 2.9.2)
|
||||
if (remoteParts.size() > localParts.size())
|
||||
return true;
|
||||
// versions are equal, check if the local version is a development release, in which case it is older (2.9.2beta < 2.9.2)
|
||||
const QRegularExpressionMatch regDevelMatch = QRegularExpression("(alpha|beta|rc)").match(QBT_VERSION);
|
||||
if (regDevelMatch.hasMatch())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
QString getStringValue(QXmlStreamReader &xml)
|
||||
{
|
||||
xml.readNext();
|
||||
if (xml.isCharacters() && !xml.isWhitespace())
|
||||
return xml.text().toString();
|
||||
|
||||
return {};
|
||||
}
|
||||
return QDesktopServices::openUrl(m_updateURL);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021 Mike Tzou (Chocobo1)
|
||||
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -29,32 +30,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
namespace Net
|
||||
{
|
||||
struct DownloadResult;
|
||||
}
|
||||
|
||||
class ProgramUpdater : public QObject
|
||||
class ProgramUpdater final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ProgramUpdater)
|
||||
|
||||
public:
|
||||
explicit ProgramUpdater(QObject *parent = nullptr, bool invokedByUser = false);
|
||||
using QObject::QObject;
|
||||
|
||||
void checkForUpdates();
|
||||
void updateProgram();
|
||||
void checkForUpdates() const;
|
||||
QString getNewVersion() const;
|
||||
bool updateProgram() const;
|
||||
|
||||
signals:
|
||||
void updateCheckFinished(bool updateAvailable, QString version, bool invokedByUser);
|
||||
void updateCheckFinished();
|
||||
|
||||
private slots:
|
||||
void rssDownloadFinished(const Net::DownloadResult &result);
|
||||
|
||||
private:
|
||||
bool isVersionMoreRecent(const QString &remoteVersion) const;
|
||||
|
||||
QString m_updateUrl;
|
||||
bool m_invokedByUser;
|
||||
QString m_newVersion;
|
||||
QUrl m_updateURL;
|
||||
};
|
||||
|
||||
@@ -26,48 +26,47 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "progressbardelegate.h"
|
||||
#include "progressbarpainter.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QStyleOptionProgressBar>
|
||||
#include <QStyleOptionViewItem>
|
||||
|
||||
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
|
||||
#include <QProxyStyle>
|
||||
#endif
|
||||
|
||||
ProgressBarDelegate::ProgressBarDelegate(const int progressColumn, const int dataRole, QObject *parent)
|
||||
: QStyledItemDelegate {parent}
|
||||
, m_progressColumn {progressColumn}
|
||||
, m_dataRole {dataRole}
|
||||
ProgressBarPainter::ProgressBarPainter()
|
||||
{
|
||||
#if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
|
||||
m_dummyProgressBar.setStyle(new QProxyStyle {"fusion"});
|
||||
auto *fusionStyle = new QProxyStyle {"fusion"};
|
||||
fusionStyle->setParent(&m_dummyProgressBar);
|
||||
m_dummyProgressBar.setStyle(fusionStyle);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ProgressBarDelegate::initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const
|
||||
void ProgressBarPainter::paint(QPainter *painter, const QStyleOptionViewItem &option, const QString &text, const int progress) const
|
||||
{
|
||||
option.text = index.data().toString();
|
||||
option.progress = static_cast<int>(index.data(m_dataRole).toReal());
|
||||
option.maximum = 100;
|
||||
option.minimum = 0;
|
||||
option.state |= (QStyle::State_Enabled | QStyle::State_Horizontal);
|
||||
option.textVisible = true;
|
||||
}
|
||||
QStyleOptionProgressBar styleOption;
|
||||
styleOption.initFrom(&m_dummyProgressBar);
|
||||
// QStyleOptionProgressBar fields
|
||||
styleOption.maximum = 100;
|
||||
styleOption.minimum = 0;
|
||||
styleOption.progress = progress;
|
||||
styleOption.text = text;
|
||||
styleOption.textVisible = true;
|
||||
// QStyleOption fields
|
||||
styleOption.rect = option.rect;
|
||||
// Qt 6 requires QStyle::State_Horizontal to be set for correctly drawing horizontal progress bar
|
||||
styleOption.state = option.state | QStyle::State_Horizontal;
|
||||
|
||||
void ProgressBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
if (index.column() != m_progressColumn)
|
||||
return QStyledItemDelegate::paint(painter, option, index);
|
||||
|
||||
QStyleOptionProgressBar newopt;
|
||||
newopt.initFrom(&m_dummyProgressBar);
|
||||
newopt.rect = option.rect;
|
||||
initProgressStyleOption(newopt, index);
|
||||
const bool isEnabled = option.state.testFlag(QStyle::State_Enabled);
|
||||
styleOption.palette.setCurrentColorGroup(isEnabled ? QPalette::Active : QPalette::Disabled);
|
||||
|
||||
painter->save();
|
||||
m_dummyProgressBar.style()->drawControl(QStyle::CE_ProgressBar, &newopt, painter, &m_dummyProgressBar);
|
||||
const QStyle *style = m_dummyProgressBar.style();
|
||||
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter);
|
||||
style->drawControl(QStyle::CE_ProgressBar, &styleOption, painter, &m_dummyProgressBar);
|
||||
painter->restore();
|
||||
}
|
||||
@@ -29,23 +29,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <QProgressBar>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
class QStyleOptionProgressBar;
|
||||
class QStyleOptionViewItem;
|
||||
|
||||
class ProgressBarDelegate : public QStyledItemDelegate
|
||||
class ProgressBarPainter
|
||||
{
|
||||
public:
|
||||
ProgressBarDelegate(int progressColumn, int dataRole, QObject *parent = nullptr);
|
||||
ProgressBarPainter();
|
||||
|
||||
protected:
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
virtual void initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const;
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QString &text, int progress) const;
|
||||
|
||||
private:
|
||||
const int m_progressColumn;
|
||||
const int m_dataRole;
|
||||
|
||||
// for painting progressbar with stylesheet option, a dummy progress bar is required
|
||||
QProgressBar m_dummyProgressBar;
|
||||
};
|
||||
@@ -270,8 +270,8 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
|
||||
// Do not allow user to add peers in a private torrent
|
||||
if (!torrent->isQueued() && !torrent->isChecking() && !torrent->isPrivate())
|
||||
{
|
||||
const QAction *addPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("user-group-new"), tr("Add a new peer..."));
|
||||
connect(addPeerAct, &QAction::triggered, this, [this, torrent]()
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("user-group-new"), tr("Add a new peer...")
|
||||
, this, [this, torrent]()
|
||||
{
|
||||
const QVector<BitTorrent::PeerAddress> peersList = PeersAdditionDialog::askForPeers(this);
|
||||
const int peerCount = std::count_if(peersList.cbegin(), peersList.cend(), [torrent](const BitTorrent::PeerAddress &peer)
|
||||
@@ -287,13 +287,11 @@ void PeerListWidget::showPeerListMenu(const QPoint &)
|
||||
|
||||
if (!selectionModel()->selectedRows().isEmpty())
|
||||
{
|
||||
const QAction *copyPeerAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy IP:port"));
|
||||
connect(copyPeerAct, &QAction::triggered, this, &PeerListWidget::copySelectedPeers);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy IP:port")
|
||||
, this, &PeerListWidget::copySelectedPeers);
|
||||
menu->addSeparator();
|
||||
|
||||
const QAction *banAct = menu->addAction(UIThemeManager::instance()->getIcon("user-group-delete"), tr("Ban peer permanently"));
|
||||
connect(banAct, &QAction::triggered, this, &PeerListWidget::banSelectedPeers);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("user-group-delete"), tr("Ban peer permanently")
|
||||
, this, &PeerListWidget::banSelectedPeers);
|
||||
}
|
||||
|
||||
if (menu->isEmpty())
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
|
||||
#include "propertieswidget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QClipboard>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
@@ -313,8 +312,9 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent)
|
||||
|
||||
// Save path
|
||||
updateSavePath(m_torrent);
|
||||
// Hash
|
||||
m_ui->labelHashVal->setText(m_torrent->hash());
|
||||
// Info hash (Truncated info hash (torrent ID) with libtorrent2)
|
||||
// TODO: Update label for this property to express its meaning more clearly (or change it to display real info hash(es))
|
||||
m_ui->labelHashVal->setText(m_torrent->id().toString());
|
||||
m_propListModel->model()->clear();
|
||||
if (m_torrent->hasMetadata())
|
||||
{
|
||||
@@ -586,57 +586,82 @@ void PropertiesWidget::displayFilesListMenu(const QPoint &)
|
||||
{
|
||||
const QModelIndex index = selectedRows[0];
|
||||
|
||||
const QAction *actOpen = menu->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open"));
|
||||
connect(actOpen, &QAction::triggered, this, [this, index]() { openItem(index); });
|
||||
|
||||
const QAction *actOpenContainingFolder = menu->addAction(UIThemeManager::instance()->getIcon("inode-directory"), tr("Open Containing Folder"));
|
||||
connect(actOpenContainingFolder, &QAction::triggered, this, [this, index]() { openParentFolder(index); });
|
||||
|
||||
const QAction *actRename = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."));
|
||||
connect(actRename, &QAction::triggered, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); });
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("folder-documents"), tr("Open")
|
||||
, this, [this, index]() { openItem(index); });
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("inode-directory"), tr("Open Containing Folder")
|
||||
, this, [this, index]() { openParentFolder(index); });
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
|
||||
, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); });
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
if (!m_torrent->isSeed())
|
||||
{
|
||||
QMenu *subMenu = menu->addMenu(tr("Priority"));
|
||||
|
||||
const auto applyPriorities = [this, selectedRows](const BitTorrent::DownloadPriority prio)
|
||||
const auto applyPriorities = [this](const BitTorrent::DownloadPriority prio)
|
||||
{
|
||||
const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
|
||||
for (const QModelIndex &index : selectedRows)
|
||||
{
|
||||
m_propListModel->setData(
|
||||
m_propListModel->index(index.row(), PRIORITY, index.parent()), static_cast<int>(prio));
|
||||
m_propListModel->setData(index.sibling(index.row(), PRIORITY)
|
||||
, static_cast<int>(prio));
|
||||
}
|
||||
|
||||
// Save changes
|
||||
filteredFilesChanged();
|
||||
};
|
||||
|
||||
connect(m_ui->actionNotDownloaded, &QAction::triggered, subMenu, [applyPriorities]()
|
||||
QMenu *subMenu = menu->addMenu(tr("Priority"));
|
||||
|
||||
subMenu->addAction(tr("Do not download"), subMenu, [applyPriorities]()
|
||||
{
|
||||
applyPriorities(BitTorrent::DownloadPriority::Ignored);
|
||||
});
|
||||
subMenu->addAction(m_ui->actionNotDownloaded);
|
||||
|
||||
connect(m_ui->actionNormal, &QAction::triggered, subMenu, [applyPriorities]()
|
||||
subMenu->addAction(tr("Normal"), subMenu, [applyPriorities]()
|
||||
{
|
||||
applyPriorities(BitTorrent::DownloadPriority::Normal);
|
||||
});
|
||||
subMenu->addAction(m_ui->actionNormal);
|
||||
|
||||
connect(m_ui->actionHigh, &QAction::triggered, subMenu, [applyPriorities]()
|
||||
subMenu->addAction(tr("High"), subMenu, [applyPriorities]()
|
||||
{
|
||||
applyPriorities(BitTorrent::DownloadPriority::High);
|
||||
});
|
||||
subMenu->addAction(m_ui->actionHigh);
|
||||
|
||||
connect(m_ui->actionMaximum, &QAction::triggered, subMenu, [applyPriorities]()
|
||||
subMenu->addAction(tr("Maximum"), subMenu, [applyPriorities]()
|
||||
{
|
||||
applyPriorities(BitTorrent::DownloadPriority::Maximum);
|
||||
});
|
||||
subMenu->addAction(m_ui->actionMaximum);
|
||||
subMenu->addSeparator();
|
||||
subMenu->addAction(tr("By shown file order"), subMenu, [this]()
|
||||
{
|
||||
// Equally distribute the selected items into groups and for each group assign
|
||||
// a download priority that will apply to each item. The number of groups depends on how
|
||||
// many "download priority" are available to be assigned
|
||||
|
||||
const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
|
||||
|
||||
const int priorityGroups = 3;
|
||||
const int priorityGroupSize = std::max((selectedRows.length() / priorityGroups), 1);
|
||||
|
||||
for (int i = 0; i < selectedRows.length(); ++i)
|
||||
{
|
||||
auto priority = BitTorrent::DownloadPriority::Ignored;
|
||||
switch (i / priorityGroupSize)
|
||||
{
|
||||
case 0:
|
||||
priority = BitTorrent::DownloadPriority::Maximum;
|
||||
break;
|
||||
case 1:
|
||||
priority = BitTorrent::DownloadPriority::High;
|
||||
break;
|
||||
default:
|
||||
case 2:
|
||||
priority = BitTorrent::DownloadPriority::Normal;
|
||||
break;
|
||||
}
|
||||
|
||||
const QModelIndex &index = selectedRows[i];
|
||||
m_propListModel->setData(index.sibling(index.row(), PRIORITY)
|
||||
, static_cast<int>(priority));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The selected torrent might have disappeared during exec()
|
||||
@@ -660,21 +685,17 @@ void PropertiesWidget::displayWebSeedListMenu(const QPoint &)
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
const QAction *actAdd = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New Web seed"));
|
||||
connect(actAdd, &QAction::triggered, this, &PropertiesWidget::askWebSeed);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New Web seed"), this, &PropertiesWidget::askWebSeed);
|
||||
|
||||
if (!rows.isEmpty())
|
||||
{
|
||||
const QAction *actDel = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove Web seed"));
|
||||
connect(actDel, &QAction::triggered, this, &PropertiesWidget::deleteSelectedUrlSeeds);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove Web seed")
|
||||
, this, &PropertiesWidget::deleteSelectedUrlSeeds);
|
||||
menu->addSeparator();
|
||||
|
||||
const QAction *actCpy = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy Web seed URL"));
|
||||
connect(actCpy, &QAction::triggered, this, &PropertiesWidget::copySelectedWebSeedsToClipboard);
|
||||
|
||||
const QAction *actEdit = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Edit Web seed URL"));
|
||||
connect(actEdit, &QAction::triggered, this, &PropertiesWidget::editWebSeed);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy Web seed URL")
|
||||
, this, &PropertiesWidget::copySelectedWebSeedsToClipboard);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Edit Web seed URL")
|
||||
, this, &PropertiesWidget::editWebSeed);
|
||||
}
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
@@ -780,7 +801,7 @@ void PropertiesWidget::editWebSeed()
|
||||
|
||||
if (!m_ui->listWebSeeds->findItems(newSeed, Qt::MatchFixedString).empty())
|
||||
{
|
||||
QMessageBox::warning(this, tr("qBittorrent"),
|
||||
QMessageBox::warning(this, QLatin1String("qBittorrent"),
|
||||
tr("This URL seed is already in the list."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
@@ -804,7 +825,8 @@ void PropertiesWidget::filteredFilesChanged()
|
||||
|
||||
void PropertiesWidget::filterText(const QString &filter)
|
||||
{
|
||||
m_propListModel->setFilterRegExp(QRegExp(filter, Qt::CaseInsensitive, QRegExp::WildcardUnix));
|
||||
const QString pattern = Utils::String::wildcardToRegexPattern(filter);
|
||||
m_propListModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
||||
if (filter.isEmpty())
|
||||
{
|
||||
m_ui->filesList->collapseAll();
|
||||
|
||||
@@ -1086,29 +1086,6 @@
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<action name="actionNotDownloaded">
|
||||
<property name="text">
|
||||
<string>Do not download</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Do not download</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionMaximum">
|
||||
<property name="text">
|
||||
<string>Maximum</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionHigh">
|
||||
<property name="text">
|
||||
<string>High</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionNormal">
|
||||
<property name="text">
|
||||
<string>Normal</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
||||
@@ -28,61 +28,22 @@
|
||||
|
||||
#include "proplistdelegate.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QModelIndex>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
#include <QProgressBar>
|
||||
#include <QStyleOptionProgressBar>
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
#include <QProxyStyle>
|
||||
#endif
|
||||
|
||||
#include "base/bittorrent/downloadpriority.h"
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "gui/torrentcontentmodel.h"
|
||||
#include "propertieswidget.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
QPalette progressBarDisabledPalette()
|
||||
{
|
||||
static const QPalette palette = []()
|
||||
{
|
||||
QProgressBar bar;
|
||||
bar.setEnabled(false);
|
||||
QStyleOptionProgressBar opt;
|
||||
opt.initFrom(&bar);
|
||||
return opt.palette;
|
||||
}();
|
||||
return palette;
|
||||
}
|
||||
}
|
||||
|
||||
PropListDelegate::PropListDelegate(PropertiesWidget *properties)
|
||||
: ProgressBarDelegate {PROGRESS, TorrentContentModel::UnderlyingDataRole, properties}
|
||||
, m_properties(properties)
|
||||
: QStyledItemDelegate {properties}
|
||||
, m_properties {properties}
|
||||
{
|
||||
}
|
||||
|
||||
void PropListDelegate::initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const
|
||||
{
|
||||
ProgressBarDelegate::initProgressStyleOption(option, index);
|
||||
const int priority
|
||||
= index.sibling(index.row(), PRIORITY).data(TorrentContentModel::UnderlyingDataRole).toInt();
|
||||
if (static_cast<BitTorrent::DownloadPriority>(priority) == BitTorrent::DownloadPriority::Ignored)
|
||||
{
|
||||
option.state &= ~QStyle::State_Enabled;
|
||||
option.palette = progressBarDisabledPalette();
|
||||
}
|
||||
else
|
||||
{
|
||||
option.state |= QStyle::State_Enabled;
|
||||
}
|
||||
}
|
||||
|
||||
void PropListDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
auto *combobox = static_cast<QComboBox *>(editor);
|
||||
@@ -156,3 +117,25 @@ void PropListDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionV
|
||||
{
|
||||
editor->setGeometry(option.rect);
|
||||
}
|
||||
|
||||
void PropListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
|
||||
{
|
||||
switch (index.column())
|
||||
{
|
||||
case PropColumn::PROGRESS:
|
||||
{
|
||||
const int progress = static_cast<int>(index.data(TorrentContentModel::UnderlyingDataRole).toReal());
|
||||
const int priority = index.sibling(index.row(), PropColumn::PRIORITY).data(TorrentContentModel::UnderlyingDataRole).toInt();
|
||||
const bool isEnabled = static_cast<BitTorrent::DownloadPriority>(priority) != BitTorrent::DownloadPriority::Ignored;
|
||||
|
||||
QStyleOptionViewItem customOption {option};
|
||||
customOption.state.setFlag(QStyle::State_Enabled, isEnabled);
|
||||
|
||||
m_progressBarPainter.paint(painter, customOption, index.data().toString(), progress);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gui/progressbardelegate.h"
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include "gui/progressbarpainter.h"
|
||||
|
||||
class QAbstractItemModel;
|
||||
class QModelIndex;
|
||||
class QPainter;
|
||||
class QStyleOptionViewItem;
|
||||
|
||||
class PropertiesWidget;
|
||||
@@ -48,25 +49,26 @@ enum PropColumn
|
||||
AVAILABILITY
|
||||
};
|
||||
|
||||
class PropListDelegate final : public ProgressBarDelegate
|
||||
class PropListDelegate final : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(PropListDelegate)
|
||||
|
||||
public:
|
||||
explicit PropListDelegate(PropertiesWidget *properties);
|
||||
|
||||
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
|
||||
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem & /* option */, const QModelIndex &index) const override;
|
||||
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
|
||||
public slots:
|
||||
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
|
||||
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex & /* index */) const override;
|
||||
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
|
||||
signals:
|
||||
void filteredFilesChanged() const;
|
||||
|
||||
private:
|
||||
void initProgressStyleOption(QStyleOptionProgressBar &option, const QModelIndex &index) const override;
|
||||
|
||||
PropertiesWidget *m_properties;
|
||||
ProgressBarPainter m_progressBarPainter;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021 Prince Gupta <guptaprince8832@gmail.com>
|
||||
* Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -34,33 +35,13 @@
|
||||
#include <QPainter>
|
||||
#include <QPen>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "base/unicodestrings.h"
|
||||
#include "base/utils/misc.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
enum PeriodInSeconds
|
||||
{
|
||||
MIN1_SEC = 60,
|
||||
MIN5_SEC = 5 * 60,
|
||||
MIN30_SEC = 30 * 60,
|
||||
HOUR6_SEC = 6 * 60 * 60,
|
||||
HOUR12_SEC = 12 * 60 * 60,
|
||||
HOUR24_SEC = 24 * 60 * 60
|
||||
};
|
||||
|
||||
const int MIN5_BUF_SIZE = 5 * 60;
|
||||
const int MIN30_BUF_SIZE = 5 * 60;
|
||||
const int HOUR6_BUF_SIZE = 5 * 60;
|
||||
const int HOUR12_BUF_SIZE = 10 * 60;
|
||||
const int HOUR24_BUF_SIZE = 10 * 60;
|
||||
const int DIVIDER_30MIN = MIN30_SEC / MIN30_BUF_SIZE;
|
||||
const int DIVIDER_6HOUR = HOUR6_SEC / HOUR6_BUF_SIZE;
|
||||
const int DIVIDER_12HOUR = HOUR12_SEC / HOUR12_BUF_SIZE;
|
||||
const int DIVIDER_24HOUR = HOUR24_SEC / HOUR24_BUF_SIZE;
|
||||
|
||||
|
||||
// table of supposed nice steps for grid marks to get nice looking quarters of scale
|
||||
const double roundingTable[] = {1.2, 1.6, 2, 2.4, 2.8, 3.2, 4, 6, 8};
|
||||
|
||||
@@ -118,55 +99,69 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
SpeedPlotView::Averager::Averager(int divider, boost::circular_buffer<PointData> &sink)
|
||||
: m_divider(divider)
|
||||
, m_sink(sink)
|
||||
, m_counter(0)
|
||||
, m_accumulator {}
|
||||
SpeedPlotView::Averager::Averager(const milliseconds duration, const milliseconds resolution)
|
||||
: m_resolution {resolution}
|
||||
, m_maxDuration {duration}
|
||||
, m_sink {static_cast<DataCircularBuffer::size_type>(duration / resolution)}
|
||||
{
|
||||
m_lastSampleTime.start();
|
||||
}
|
||||
|
||||
void SpeedPlotView::Averager::push(const PointData &pointData)
|
||||
bool SpeedPlotView::Averager::push(const SampleData &sampleData)
|
||||
{
|
||||
// Accumulator overflow will be hit in worst case on longest used averaging span,
|
||||
// defined by divider value. Maximum divider is DIVIDER_24HOUR = 144
|
||||
// Using int32 for accumulator we get overflow when transfer speed reaches 2^31/144 ~~ 14.2 MBytes/s.
|
||||
// With quint64 this speed limit is 2^64/144 ~~ 114 PBytes/s.
|
||||
// This speed is inaccessible to an ordinary user.
|
||||
m_accumulator.x += pointData.x;
|
||||
++m_counter;
|
||||
for (int id = UP; id < NB_GRAPHS; ++id)
|
||||
m_accumulator.y[id] += pointData.y[id];
|
||||
m_counter = (m_counter + 1) % m_divider;
|
||||
if (m_counter != 0)
|
||||
return; // still accumulating
|
||||
m_accumulator[id] += sampleData[id];
|
||||
|
||||
// system may go to sleep, that can cause very big elapsed interval
|
||||
const milliseconds updateInterval {static_cast<int64_t>(BitTorrent::Session::instance()->refreshInterval() * 1.25)};
|
||||
const milliseconds maxElapsed {std::max(updateInterval, m_resolution)};
|
||||
const milliseconds elapsed {std::min(milliseconds {m_lastSampleTime.elapsed()}, maxElapsed)};
|
||||
if (elapsed < m_resolution)
|
||||
return false; // still accumulating
|
||||
|
||||
// it is time final averaging calculations
|
||||
for (int id = UP; id < NB_GRAPHS; ++id)
|
||||
m_accumulator.y[id] /= m_divider;
|
||||
m_accumulator.x /= m_divider;
|
||||
m_accumulator[id] /= m_counter;
|
||||
|
||||
m_currentDuration += elapsed;
|
||||
|
||||
// remove extra data from front if we reached max duration
|
||||
if (m_currentDuration > m_maxDuration)
|
||||
{
|
||||
// once we go above the max duration never go below that
|
||||
// otherwise it will cause empty space in graphs
|
||||
while (!m_sink.empty()
|
||||
&& ((m_currentDuration - m_sink.front().duration) >= m_maxDuration))
|
||||
{
|
||||
m_currentDuration -= m_sink.front().duration;
|
||||
m_sink.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
// now flush out averaged data
|
||||
m_sink.push_back(m_accumulator);
|
||||
Q_ASSERT(m_sink.size() < m_sink.capacity());
|
||||
m_sink.push_back({elapsed, m_accumulator});
|
||||
|
||||
// reset
|
||||
m_accumulator = {};
|
||||
m_counter = 0;
|
||||
m_lastSampleTime.restart();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpeedPlotView::Averager::isReady() const
|
||||
const SpeedPlotView::DataCircularBuffer &SpeedPlotView::Averager::data() const
|
||||
{
|
||||
return m_counter == 0;
|
||||
return m_sink;
|
||||
}
|
||||
|
||||
SpeedPlotView::SpeedPlotView(QWidget *parent)
|
||||
: QGraphicsView(parent)
|
||||
, m_data5Min(MIN5_BUF_SIZE)
|
||||
, m_data30Min(MIN30_BUF_SIZE)
|
||||
, m_data6Hour(HOUR6_BUF_SIZE)
|
||||
, m_data12Hour(HOUR12_BUF_SIZE)
|
||||
, m_data24Hour(HOUR24_BUF_SIZE)
|
||||
, m_currentData(&m_data5Min)
|
||||
, m_averager30Min(DIVIDER_30MIN, m_data30Min)
|
||||
, m_averager6Hour(DIVIDER_6HOUR, m_data6Hour)
|
||||
, m_averager12Hour(DIVIDER_12HOUR, m_data12Hour)
|
||||
, m_averager24Hour(DIVIDER_24HOUR, m_data24Hour)
|
||||
, m_period(MIN5)
|
||||
, m_viewablePointsCount(MIN5_SEC)
|
||||
: QGraphicsView {parent}
|
||||
{
|
||||
QPen greenPen;
|
||||
greenPen.setWidthF(1.5);
|
||||
@@ -205,69 +200,65 @@ void SpeedPlotView::setGraphEnable(GraphID id, bool enable)
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void SpeedPlotView::pushPoint(const SpeedPlotView::PointData &point)
|
||||
void SpeedPlotView::pushPoint(const SpeedPlotView::SampleData &point)
|
||||
{
|
||||
m_data5Min.push_back(point);
|
||||
m_averager30Min.push(point);
|
||||
m_averager6Hour.push(point);
|
||||
m_averager12Hour.push(point);
|
||||
m_averager24Hour.push(point);
|
||||
for (Averager *averager : {&m_averager5Min, &m_averager30Min
|
||||
, &m_averager6Hour, &m_averager12Hour
|
||||
, &m_averager24Hour})
|
||||
{
|
||||
if (averager->push(point))
|
||||
{
|
||||
if (m_currentAverager == averager)
|
||||
viewport()->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedPlotView::setPeriod(const TimePeriod period)
|
||||
{
|
||||
m_period = period;
|
||||
|
||||
switch (period)
|
||||
{
|
||||
case SpeedPlotView::MIN1:
|
||||
m_viewablePointsCount = MIN1_SEC;
|
||||
m_currentData = &m_data5Min;
|
||||
m_currentMaxDuration = 1min;
|
||||
m_currentAverager = &m_averager5Min;
|
||||
break;
|
||||
case SpeedPlotView::MIN5:
|
||||
m_viewablePointsCount = MIN5_SEC;
|
||||
m_currentData = &m_data5Min;
|
||||
m_currentMaxDuration = 5min;
|
||||
m_currentAverager = &m_averager5Min;
|
||||
break;
|
||||
case SpeedPlotView::MIN30:
|
||||
m_viewablePointsCount = MIN30_BUF_SIZE;
|
||||
m_currentData = &m_data30Min;
|
||||
m_currentMaxDuration = 30min;
|
||||
m_currentAverager = &m_averager30Min;
|
||||
break;
|
||||
case SpeedPlotView::HOUR3:
|
||||
m_currentMaxDuration = 3h;
|
||||
m_currentAverager = &m_averager6Hour;
|
||||
break;
|
||||
case SpeedPlotView::HOUR6:
|
||||
m_viewablePointsCount = HOUR6_BUF_SIZE;
|
||||
m_currentData = &m_data6Hour;
|
||||
m_currentMaxDuration = 6h;
|
||||
m_currentAverager = &m_averager6Hour;
|
||||
break;
|
||||
case SpeedPlotView::HOUR12:
|
||||
m_viewablePointsCount = HOUR12_BUF_SIZE;
|
||||
m_currentData = &m_data12Hour;
|
||||
m_currentMaxDuration = 12h;
|
||||
m_currentAverager = &m_averager12Hour;
|
||||
break;
|
||||
case SpeedPlotView::HOUR24:
|
||||
m_viewablePointsCount = HOUR24_BUF_SIZE;
|
||||
m_currentData = &m_data24Hour;
|
||||
m_currentMaxDuration = 24h;
|
||||
m_currentAverager = &m_averager24Hour;
|
||||
break;
|
||||
}
|
||||
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void SpeedPlotView::replot()
|
||||
const SpeedPlotView::DataCircularBuffer &SpeedPlotView::currentData() const
|
||||
{
|
||||
if ((m_period == MIN1)
|
||||
|| (m_period == MIN5)
|
||||
|| ((m_period == MIN30) && m_averager30Min.isReady())
|
||||
|| ((m_period == HOUR6) && m_averager6Hour.isReady())
|
||||
|| ((m_period == HOUR12) && m_averager12Hour.isReady())
|
||||
|| ((m_period == HOUR24) && m_averager24Hour.isReady()) )
|
||||
viewport()->update();
|
||||
return m_currentAverager->data();
|
||||
}
|
||||
|
||||
boost::circular_buffer<SpeedPlotView::PointData> &SpeedPlotView::getCurrentData()
|
||||
quint64 SpeedPlotView::maxYValue() const
|
||||
{
|
||||
return *m_currentData;
|
||||
}
|
||||
|
||||
quint64 SpeedPlotView::maxYValue()
|
||||
{
|
||||
boost::circular_buffer<PointData> &queue = getCurrentData();
|
||||
const DataCircularBuffer &queue = currentData();
|
||||
|
||||
quint64 maxYValue = 0;
|
||||
for (int id = UP; id < NB_GRAPHS; ++id)
|
||||
@@ -276,9 +267,14 @@ quint64 SpeedPlotView::maxYValue()
|
||||
if (!m_properties[static_cast<GraphID>(id)].enable)
|
||||
continue;
|
||||
|
||||
for (int i = static_cast<int>(queue.size()) - 1, j = 0; (i >= 0) && (j < m_viewablePointsCount); --i, ++j)
|
||||
if (queue[i].y[id] > maxYValue)
|
||||
maxYValue = queue[i].y[id];
|
||||
milliseconds duration {0ms};
|
||||
for (int i = static_cast<int>(queue.size()) - 1; i >= 0; --i)
|
||||
{
|
||||
maxYValue = std::max(maxYValue, queue[i].data[id]);
|
||||
duration += queue[i].duration;
|
||||
if (duration >= m_currentMaxDuration)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return maxYValue;
|
||||
@@ -308,13 +304,10 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
||||
|
||||
int yAxisWidth = 0;
|
||||
for (const QString &label : speedLabels)
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
{
|
||||
if (fontMetrics.horizontalAdvance(label) > yAxisWidth)
|
||||
yAxisWidth = fontMetrics.horizontalAdvance(label);
|
||||
#else
|
||||
if (fontMetrics.width(label) > yAxisWidth)
|
||||
yAxisWidth = fontMetrics.width(label);
|
||||
#endif
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (const QString &label : speedLabels)
|
||||
@@ -350,12 +343,16 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
||||
painter.setRenderHints(QPainter::Antialiasing);
|
||||
|
||||
// draw graphs
|
||||
rect.adjust(3, 0, 0, 0); // Need, else graphs cross left gridline
|
||||
// averager is duration based, it may go little above the maxDuration
|
||||
painter.setClipping(true);
|
||||
painter.setClipRect(rect);
|
||||
|
||||
const double yMultiplier = (niceScale.arg == 0.0) ? 0.0 : (static_cast<double>(rect.height()) / niceScale.sizeInBytes());
|
||||
const double xTickSize = static_cast<double>(rect.width()) / (m_viewablePointsCount - 1);
|
||||
const DataCircularBuffer &queue = currentData();
|
||||
|
||||
boost::circular_buffer<PointData> &queue = getCurrentData();
|
||||
// last point will be drawn at x=0, so we don't need it in the calculation of xTickSize
|
||||
const milliseconds lastDuration {queue.empty() ? 0ms : queue.back().duration};
|
||||
const double xTickSize = static_cast<double>(rect.width()) / (m_currentMaxDuration - lastDuration).count();
|
||||
const double yMultiplier = (niceScale.arg == 0) ? 0 : (static_cast<double>(rect.height()) / niceScale.sizeInBytes());
|
||||
|
||||
for (int id = UP; id < NB_GRAPHS; ++id)
|
||||
{
|
||||
@@ -363,18 +360,23 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
||||
continue;
|
||||
|
||||
QVector<QPoint> points;
|
||||
for (int i = static_cast<int>(queue.size()) - 1, j = 0; (i >= 0) && (j < m_viewablePointsCount); --i, ++j)
|
||||
milliseconds duration {0ms};
|
||||
|
||||
for (int i = static_cast<int>(queue.size()) - 1; i >= 0; --i)
|
||||
{
|
||||
|
||||
int newX = rect.right() - j * xTickSize;
|
||||
int newY = rect.bottom() - queue[i].y[id] * yMultiplier;
|
||||
|
||||
const int newX = rect.right() - (duration.count() * xTickSize);
|
||||
const int newY = rect.bottom() - (queue[i].data[id] * yMultiplier);
|
||||
points.push_back(QPoint(newX, newY));
|
||||
|
||||
duration += queue[i].duration;
|
||||
if (duration >= m_currentMaxDuration)
|
||||
break;
|
||||
}
|
||||
|
||||
painter.setPen(m_properties[static_cast<GraphID>(id)].pen);
|
||||
painter.drawPolyline(points.data(), points.size());
|
||||
}
|
||||
painter.setClipping(false);
|
||||
|
||||
// draw legend
|
||||
QPoint legendTopLeft(rect.left() + 4, fullRect.top() + 4);
|
||||
@@ -386,13 +388,8 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
||||
if (!property.enable)
|
||||
continue;
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
if (fontMetrics.horizontalAdvance(property.name) > legendWidth)
|
||||
legendWidth = fontMetrics.horizontalAdvance(property.name);
|
||||
#else
|
||||
if (fontMetrics.width(property.name) > legendWidth)
|
||||
legendWidth = fontMetrics.width(property.name);
|
||||
#endif
|
||||
legendHeight += 1.5 * fontMetrics.height();
|
||||
}
|
||||
|
||||
@@ -407,11 +404,7 @@ void SpeedPlotView::paintEvent(QPaintEvent *)
|
||||
if (!property.enable)
|
||||
continue;
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
int nameSize = fontMetrics.horizontalAdvance(property.name);
|
||||
#else
|
||||
int nameSize = fontMetrics.width(property.name);
|
||||
#endif
|
||||
double indent = 1.5 * (i++) * fontMetrics.height();
|
||||
|
||||
painter.setPen(property.pen);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021 Prince Gupta <guptaprince8832@gmail.com>
|
||||
* Copyright (C) 2015 Anton Lashkov <lenton_91@mail.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -28,15 +29,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
|
||||
#ifndef Q_MOC_RUN
|
||||
#include <boost/circular_buffer.hpp>
|
||||
#endif
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QGraphicsView>
|
||||
#include <QMap>
|
||||
|
||||
class QPen;
|
||||
|
||||
using std::chrono::milliseconds;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class SpeedPlotView final : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -63,42 +71,50 @@ public:
|
||||
MIN1 = 0,
|
||||
MIN5,
|
||||
MIN30,
|
||||
HOUR3,
|
||||
HOUR6,
|
||||
HOUR12,
|
||||
HOUR24
|
||||
};
|
||||
|
||||
struct PointData
|
||||
{
|
||||
qint64 x;
|
||||
quint64 y[NB_GRAPHS];
|
||||
};
|
||||
using SampleData = std::array<quint64, NB_GRAPHS>;
|
||||
|
||||
explicit SpeedPlotView(QWidget *parent = nullptr);
|
||||
|
||||
void setGraphEnable(GraphID id, bool enable);
|
||||
void setPeriod(TimePeriod period);
|
||||
|
||||
void pushPoint(const PointData &point);
|
||||
|
||||
void replot();
|
||||
void pushPoint(const SampleData &point);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
struct Sample
|
||||
{
|
||||
milliseconds duration;
|
||||
SampleData data;
|
||||
};
|
||||
|
||||
using DataCircularBuffer = boost::circular_buffer<Sample>;
|
||||
|
||||
class Averager
|
||||
{
|
||||
public:
|
||||
Averager(int divider, boost::circular_buffer<PointData> &sink);
|
||||
void push(const PointData &pointData);
|
||||
bool isReady() const;
|
||||
Averager(milliseconds duration, milliseconds resolution);
|
||||
|
||||
bool push(const SampleData &sampleData); // returns true if there is new data to display
|
||||
|
||||
const DataCircularBuffer &data() const;
|
||||
|
||||
private:
|
||||
const int m_divider;
|
||||
boost::circular_buffer<PointData> &m_sink;
|
||||
int m_counter;
|
||||
PointData m_accumulator;
|
||||
const milliseconds m_resolution;
|
||||
const milliseconds m_maxDuration;
|
||||
milliseconds m_currentDuration {0ms};
|
||||
int m_counter = 0;
|
||||
SampleData m_accumulator {};
|
||||
DataCircularBuffer m_sink {};
|
||||
QElapsedTimer m_lastSampleTime;
|
||||
};
|
||||
|
||||
struct GraphProperties
|
||||
@@ -111,22 +127,16 @@ private:
|
||||
bool enable;
|
||||
};
|
||||
|
||||
quint64 maxYValue();
|
||||
boost::circular_buffer<PointData> &getCurrentData();
|
||||
quint64 maxYValue() const;
|
||||
const DataCircularBuffer ¤tData() const;
|
||||
|
||||
boost::circular_buffer<PointData> m_data5Min;
|
||||
boost::circular_buffer<PointData> m_data30Min;
|
||||
boost::circular_buffer<PointData> m_data6Hour;
|
||||
boost::circular_buffer<PointData> m_data12Hour;
|
||||
boost::circular_buffer<PointData> m_data24Hour;
|
||||
boost::circular_buffer<PointData> *m_currentData;
|
||||
Averager m_averager30Min;
|
||||
Averager m_averager6Hour;
|
||||
Averager m_averager12Hour;
|
||||
Averager m_averager24Hour;
|
||||
Averager m_averager5Min {5min, 1s};
|
||||
Averager m_averager30Min {30min, 6s};
|
||||
Averager m_averager6Hour {6h, 36s};
|
||||
Averager m_averager12Hour {12h, 72s};
|
||||
Averager m_averager24Hour {24h, 144s};
|
||||
Averager *m_currentAverager {&m_averager5Min};
|
||||
|
||||
QMap<GraphID, GraphProperties> m_properties;
|
||||
|
||||
TimePeriod m_period;
|
||||
int m_viewablePointsCount;
|
||||
milliseconds m_currentMaxDuration;
|
||||
};
|
||||
|
||||
@@ -71,6 +71,7 @@ SpeedWidget::SpeedWidget(PropertiesWidget *parent)
|
||||
m_periodCombobox->addItem(tr("1 Minute"));
|
||||
m_periodCombobox->addItem(tr("5 Minutes"));
|
||||
m_periodCombobox->addItem(tr("30 Minutes"));
|
||||
m_periodCombobox->addItem(tr("3 Hours"));
|
||||
m_periodCombobox->addItem(tr("6 Hours"));
|
||||
m_periodCombobox->addItem(tr("12 Hours"));
|
||||
m_periodCombobox->addItem(tr("24 Hours"));
|
||||
@@ -109,16 +110,13 @@ SpeedWidget::SpeedWidget(PropertiesWidget *parent)
|
||||
m_hlayout->addWidget(m_graphsButton);
|
||||
|
||||
m_plot = new SpeedPlotView(this);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::statsUpdated, this, &SpeedWidget::update);
|
||||
|
||||
m_layout->addLayout(m_hlayout);
|
||||
m_layout->addWidget(m_plot);
|
||||
|
||||
loadSettings();
|
||||
|
||||
QTimer *localUpdateTimer = new QTimer(this);
|
||||
connect(localUpdateTimer, &QTimer::timeout, this, &SpeedWidget::update);
|
||||
localUpdateTimer->start(1000);
|
||||
|
||||
m_plot->show();
|
||||
}
|
||||
|
||||
@@ -133,21 +131,19 @@ void SpeedWidget::update()
|
||||
{
|
||||
const BitTorrent::SessionStatus &btStatus = BitTorrent::Session::instance()->status();
|
||||
|
||||
SpeedPlotView::PointData point;
|
||||
point.x = QDateTime::currentMSecsSinceEpoch() / 1000;
|
||||
point.y[SpeedPlotView::UP] = btStatus.uploadRate;
|
||||
point.y[SpeedPlotView::DOWN] = btStatus.downloadRate;
|
||||
point.y[SpeedPlotView::PAYLOAD_UP] = btStatus.payloadUploadRate;
|
||||
point.y[SpeedPlotView::PAYLOAD_DOWN] = btStatus.payloadDownloadRate;
|
||||
point.y[SpeedPlotView::OVERHEAD_UP] = btStatus.ipOverheadUploadRate;
|
||||
point.y[SpeedPlotView::OVERHEAD_DOWN] = btStatus.ipOverheadDownloadRate;
|
||||
point.y[SpeedPlotView::DHT_UP] = btStatus.dhtUploadRate;
|
||||
point.y[SpeedPlotView::DHT_DOWN] = btStatus.dhtDownloadRate;
|
||||
point.y[SpeedPlotView::TRACKER_UP] = btStatus.trackerUploadRate;
|
||||
point.y[SpeedPlotView::TRACKER_DOWN] = btStatus.trackerDownloadRate;
|
||||
SpeedPlotView::SampleData sampleData;
|
||||
sampleData[SpeedPlotView::UP] = btStatus.uploadRate;
|
||||
sampleData[SpeedPlotView::DOWN] = btStatus.downloadRate;
|
||||
sampleData[SpeedPlotView::PAYLOAD_UP] = btStatus.payloadUploadRate;
|
||||
sampleData[SpeedPlotView::PAYLOAD_DOWN] = btStatus.payloadDownloadRate;
|
||||
sampleData[SpeedPlotView::OVERHEAD_UP] = btStatus.ipOverheadUploadRate;
|
||||
sampleData[SpeedPlotView::OVERHEAD_DOWN] = btStatus.ipOverheadDownloadRate;
|
||||
sampleData[SpeedPlotView::DHT_UP] = btStatus.dhtUploadRate;
|
||||
sampleData[SpeedPlotView::DHT_DOWN] = btStatus.dhtDownloadRate;
|
||||
sampleData[SpeedPlotView::TRACKER_UP] = btStatus.trackerUploadRate;
|
||||
sampleData[SpeedPlotView::TRACKER_DOWN] = btStatus.trackerDownloadRate;
|
||||
|
||||
m_plot->pushPoint(point);
|
||||
m_plot->replot();
|
||||
m_plot->pushPoint(sampleData);
|
||||
}
|
||||
|
||||
void SpeedWidget::onPeriodChange(int period)
|
||||
|
||||
@@ -205,9 +205,7 @@ void TrackerListWidget::moveSelectionUp()
|
||||
for (int i = NB_STICKY_ITEM; i < topLevelItemCount(); ++i)
|
||||
{
|
||||
const QString trackerURL = topLevelItem(i)->data(COL_URL, Qt::DisplayRole).toString();
|
||||
BitTorrent::TrackerEntry e(trackerURL);
|
||||
e.setTier(i - NB_STICKY_ITEM);
|
||||
trackers.append(e);
|
||||
trackers.append({trackerURL, (i - NB_STICKY_ITEM)});
|
||||
}
|
||||
|
||||
torrent->replaceTrackers(trackers);
|
||||
@@ -251,9 +249,7 @@ void TrackerListWidget::moveSelectionDown()
|
||||
for (int i = NB_STICKY_ITEM; i < topLevelItemCount(); ++i)
|
||||
{
|
||||
const QString trackerURL = topLevelItem(i)->data(COL_URL, Qt::DisplayRole).toString();
|
||||
BitTorrent::TrackerEntry e(trackerURL);
|
||||
e.setTier(i - NB_STICKY_ITEM);
|
||||
trackers.append(e);
|
||||
trackers.append({trackerURL, (i - NB_STICKY_ITEM)});
|
||||
}
|
||||
|
||||
torrent->replaceTrackers(trackers);
|
||||
@@ -372,7 +368,7 @@ void TrackerListWidget::loadTrackers()
|
||||
|
||||
for (const BitTorrent::TrackerEntry &entry : asConst(torrent->trackers()))
|
||||
{
|
||||
const QString trackerURL = entry.url();
|
||||
const QString trackerURL = entry.url;
|
||||
|
||||
QTreeWidgetItem *item = m_trackerItems.value(trackerURL, nullptr);
|
||||
if (!item)
|
||||
@@ -387,11 +383,11 @@ void TrackerListWidget::loadTrackers()
|
||||
oldTrackerURLs.removeOne(trackerURL);
|
||||
}
|
||||
|
||||
item->setText(COL_TIER, QString::number(entry.tier()));
|
||||
item->setText(COL_TIER, QString::number(entry.tier));
|
||||
|
||||
const BitTorrent::TrackerInfo data = trackerData.value(trackerURL);
|
||||
|
||||
switch (entry.status())
|
||||
switch (entry.status)
|
||||
{
|
||||
case BitTorrent::TrackerEntry::Working:
|
||||
item->setText(COL_STATUS, tr("Working"));
|
||||
@@ -412,14 +408,14 @@ void TrackerListWidget::loadTrackers()
|
||||
}
|
||||
|
||||
item->setText(COL_PEERS, QString::number(data.numPeers));
|
||||
item->setText(COL_SEEDS, ((entry.numSeeds() > -1)
|
||||
? QString::number(entry.numSeeds())
|
||||
item->setText(COL_SEEDS, ((entry.numSeeds > -1)
|
||||
? QString::number(entry.numSeeds)
|
||||
: tr("N/A")));
|
||||
item->setText(COL_LEECHES, ((entry.numLeeches() > -1)
|
||||
? QString::number(entry.numLeeches())
|
||||
item->setText(COL_LEECHES, ((entry.numLeeches > -1)
|
||||
? QString::number(entry.numLeeches)
|
||||
: tr("N/A")));
|
||||
item->setText(COL_DOWNLOADED, ((entry.numDownloaded() > -1)
|
||||
? QString::number(entry.numDownloaded())
|
||||
item->setText(COL_DOWNLOADED, ((entry.numDownloaded > -1)
|
||||
? QString::number(entry.numDownloaded)
|
||||
: tr("N/A")));
|
||||
|
||||
const Qt::Alignment alignment = (Qt::AlignRight | Qt::AlignVCenter);
|
||||
@@ -443,7 +439,7 @@ void TrackerListWidget::askForTrackers()
|
||||
|
||||
QVector<BitTorrent::TrackerEntry> trackers;
|
||||
for (const QString &tracker : asConst(TrackersAdditionDialog::askForTrackers(this, torrent)))
|
||||
trackers << tracker;
|
||||
trackers.append({tracker});
|
||||
|
||||
torrent->addTrackers(trackers);
|
||||
}
|
||||
@@ -492,7 +488,7 @@ void TrackerListWidget::deleteSelectedTrackers()
|
||||
|
||||
for (const BitTorrent::TrackerEntry &entry : trackers)
|
||||
{
|
||||
if (!urlsToRemove.contains(entry.url()))
|
||||
if (!urlsToRemove.contains(entry.url))
|
||||
remainingTrackers.push_back(entry);
|
||||
}
|
||||
|
||||
@@ -529,18 +525,16 @@ void TrackerListWidget::editSelectedTracker()
|
||||
bool match = false;
|
||||
for (BitTorrent::TrackerEntry &entry : trackers)
|
||||
{
|
||||
if (newTrackerURL == QUrl(entry.url()))
|
||||
if (newTrackerURL == QUrl(entry.url))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Tracker editing failed"), tr("The tracker URL already exists."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!match && (trackerURL == QUrl(entry.url())))
|
||||
if (!match && (trackerURL == QUrl(entry.url)))
|
||||
{
|
||||
match = true;
|
||||
BitTorrent::TrackerEntry newEntry(newTrackerURL.toString());
|
||||
newEntry.setTier(entry.tier());
|
||||
entry = newEntry;
|
||||
entry.url = newTrackerURL.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,7 +566,7 @@ void TrackerListWidget::reannounceSelected()
|
||||
// Trackers case
|
||||
for (int i = 0; i < trackers.size(); ++i)
|
||||
{
|
||||
if (item->text(COL_URL) == trackers[i].url())
|
||||
if (item->text(COL_URL) == trackers[i].url)
|
||||
{
|
||||
torrent->forceReannounce(i);
|
||||
break;
|
||||
@@ -592,30 +586,26 @@ void TrackerListWidget::showTrackerListMenu(const QPoint &)
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
// Add actions
|
||||
const QAction *addAct = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add a new tracker..."));
|
||||
connect(addAct, &QAction::triggered, this, &TrackerListWidget::askForTrackers);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add a new tracker...")
|
||||
, this, &TrackerListWidget::askForTrackers);
|
||||
|
||||
if (!getSelectedTrackerItems().isEmpty())
|
||||
{
|
||||
const QAction *editAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"),tr("Edit tracker URL..."));
|
||||
connect(editAct, &QAction::triggered, this, &TrackerListWidget::editSelectedTracker);
|
||||
|
||||
const QAction *delAct = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove tracker"));
|
||||
connect(delAct, &QAction::triggered, this, &TrackerListWidget::deleteSelectedTrackers);
|
||||
|
||||
const QAction *copyAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy tracker URL"));
|
||||
connect(copyAct, &QAction::triggered, this, &TrackerListWidget::copyTrackerUrl);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"),tr("Edit tracker URL...")
|
||||
, this, &TrackerListWidget::editSelectedTracker);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove tracker")
|
||||
, this, &TrackerListWidget::deleteSelectedTrackers);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy tracker URL")
|
||||
, this, &TrackerListWidget::copyTrackerUrl);
|
||||
}
|
||||
|
||||
if (!torrent->isPaused())
|
||||
{
|
||||
const QAction *reannounceSelAct = menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to selected trackers"));
|
||||
connect(reannounceSelAct, &QAction::triggered, this, &TrackerListWidget::reannounceSelected);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to selected trackers")
|
||||
, this, &TrackerListWidget::reannounceSelected);
|
||||
menu->addSeparator();
|
||||
|
||||
const QAction *reannounceAllAct = menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to all trackers"));
|
||||
connect(reannounceAllAct, &QAction::triggered, this, [this]()
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to all trackers")
|
||||
, this, [this]()
|
||||
{
|
||||
BitTorrent::Torrent *h = m_properties->getCurrentTorrent();
|
||||
h->forceReannounce();
|
||||
|
||||
@@ -96,7 +96,7 @@ void TrackersAdditionDialog::torrentListDownloadFinished(const Net::DownloadResu
|
||||
existingTrackers.reserve(trackersFromUser.size());
|
||||
for (const QString &userURL : trackersFromUser)
|
||||
{
|
||||
const BitTorrent::TrackerEntry userTracker(userURL);
|
||||
const BitTorrent::TrackerEntry userTracker {userURL};
|
||||
if (!existingTrackers.contains(userTracker))
|
||||
existingTrackers << userTracker;
|
||||
}
|
||||
@@ -113,7 +113,7 @@ void TrackersAdditionDialog::torrentListDownloadFinished(const Net::DownloadResu
|
||||
const QString line = buffer.readLine().trimmed();
|
||||
if (line.isEmpty()) continue;
|
||||
|
||||
BitTorrent::TrackerEntry newTracker(line);
|
||||
BitTorrent::TrackerEntry newTracker {line};
|
||||
if (!existingTrackers.contains(newTracker))
|
||||
{
|
||||
m_ui->textEditTrackersList->insertPlainText(line + '\n');
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "gui/autoexpandabledialog.h"
|
||||
#include "gui/torrentcategorydialog.h"
|
||||
#include "gui/uithememanager.h"
|
||||
#include "gui/utils.h"
|
||||
#include "ui_automatedrssdownloader.h"
|
||||
@@ -68,6 +69,7 @@ AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent)
|
||||
// Icons
|
||||
m_ui->removeRuleBtn->setIcon(UIThemeManager::instance()->getIcon("list-remove"));
|
||||
m_ui->addRuleBtn->setIcon(UIThemeManager::instance()->getIcon("list-add"));
|
||||
m_ui->addCategoryBtn->setIcon(UIThemeManager::instance()->getIcon("list-add"));
|
||||
|
||||
// Ui Settings
|
||||
m_ui->listRules->setSortingEnabled(true);
|
||||
@@ -185,7 +187,7 @@ void AutomatedRssDownloader::loadFeedList()
|
||||
{
|
||||
QListWidgetItem *item = new QListWidgetItem(feed->name(), m_ui->listFeeds);
|
||||
item->setData(Qt::UserRole, feed->url());
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsTristate);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate);
|
||||
}
|
||||
|
||||
updateFeedList();
|
||||
@@ -405,6 +407,17 @@ void AutomatedRssDownloader::on_removeRuleBtn_clicked()
|
||||
RSS::AutoDownloader::instance()->removeRule(item->text());
|
||||
}
|
||||
|
||||
void AutomatedRssDownloader::on_addCategoryBtn_clicked()
|
||||
{
|
||||
const QString newCategoryName = TorrentCategoryDialog::createCategory(this);
|
||||
|
||||
if (!newCategoryName.isEmpty())
|
||||
{
|
||||
m_ui->comboCategory->addItem(newCategoryName);
|
||||
m_ui->comboCategory->setCurrentText(newCategoryName);
|
||||
}
|
||||
}
|
||||
|
||||
void AutomatedRssDownloader::on_exportBtn_clicked()
|
||||
{
|
||||
if (RSS::AutoDownloader::instance()->rules().isEmpty())
|
||||
@@ -490,8 +503,8 @@ void AutomatedRssDownloader::displayRulesListMenu()
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
const QAction *addAct = menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add new rule..."));
|
||||
connect(addAct, &QAction::triggered, this, &AutomatedRssDownloader::on_addRuleBtn_clicked);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add new rule...")
|
||||
, this, &AutomatedRssDownloader::on_addRuleBtn_clicked);
|
||||
|
||||
const QList<QListWidgetItem *> selection = m_ui->listRules->selectedItems();
|
||||
|
||||
@@ -499,24 +512,21 @@ void AutomatedRssDownloader::displayRulesListMenu()
|
||||
{
|
||||
if (selection.count() == 1)
|
||||
{
|
||||
const QAction *delAct = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete rule"));
|
||||
connect(delAct, &QAction::triggered, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete rule")
|
||||
, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
|
||||
menu->addSeparator();
|
||||
|
||||
const QAction *renameAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename rule..."));
|
||||
connect(renameAct, &QAction::triggered, this, &AutomatedRssDownloader::renameSelectedRule);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename rule...")
|
||||
, this, &AutomatedRssDownloader::renameSelectedRule);
|
||||
}
|
||||
else
|
||||
{
|
||||
const QAction *delAct = menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete selected rules"));
|
||||
connect(delAct, &QAction::triggered, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Delete selected rules")
|
||||
, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);
|
||||
}
|
||||
|
||||
menu->addSeparator();
|
||||
|
||||
const QAction *clearAct = menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear downloaded episodes..."));
|
||||
connect(clearAct, &QAction::triggered, this, &AutomatedRssDownloader::clearSelectedRuleDownloadedEpisodeList);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Clear downloaded episodes...")
|
||||
, this, &AutomatedRssDownloader::clearSelectedRuleDownloadedEpisodeList);
|
||||
}
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
@@ -709,10 +719,14 @@ void AutomatedRssDownloader::updateMustLineValidity()
|
||||
{
|
||||
QStringList tokens;
|
||||
if (isRegex)
|
||||
{
|
||||
tokens << text;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const QString &token : asConst(text.split('|')))
|
||||
tokens << Utils::String::wildcardToRegex(token);
|
||||
tokens << Utils::String::wildcardToRegexPattern(token);
|
||||
}
|
||||
|
||||
for (const QString &token : asConst(tokens))
|
||||
{
|
||||
@@ -752,10 +766,14 @@ void AutomatedRssDownloader::updateMustNotLineValidity()
|
||||
{
|
||||
QStringList tokens;
|
||||
if (isRegex)
|
||||
{
|
||||
tokens << text;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const QString &token : asConst(text.split('|')))
|
||||
tokens << Utils::String::wildcardToRegex(token);
|
||||
tokens << Utils::String::wildcardToRegexPattern(token);
|
||||
}
|
||||
|
||||
for (const QString &token : asConst(tokens))
|
||||
{
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
private slots:
|
||||
void on_addRuleBtn_clicked();
|
||||
void on_removeRuleBtn_clicked();
|
||||
void on_addCategoryBtn_clicked();
|
||||
void on_exportBtn_clicked();
|
||||
void on_importBtn_clicked();
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Assign Category:</string>
|
||||
<string>Category:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -220,6 +220,9 @@ Supports the formats: S01E01, 1x1, 2017.12.31 and 31.12.2017 (Date formats also
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="addCategoryBtn" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -365,9 +365,9 @@ void SearchJobWidget::fillFilterComboBoxes()
|
||||
|
||||
void SearchJobWidget::filterSearchResults(const QString &name)
|
||||
{
|
||||
const QRegExp::PatternSyntax patternSyntax = Preferences::instance()->getRegexAsFilteringPatternForSearchJob()
|
||||
? QRegExp::RegExp : QRegExp::WildcardUnix;
|
||||
m_proxyModel->setFilterRegExp(QRegExp(name, Qt::CaseInsensitive, patternSyntax));
|
||||
const QString pattern = (Preferences::instance()->getRegexAsFilteringPatternForSearchJob()
|
||||
? name : Utils::String::wildcardToRegexPattern(name));
|
||||
m_proxyModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
|
||||
updateResultsCount();
|
||||
}
|
||||
|
||||
@@ -393,31 +393,21 @@ void SearchJobWidget::contextMenuEvent(QContextMenuEvent *event)
|
||||
auto *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
const QAction *downloadAction = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("download"), tr("Download"));
|
||||
connect(downloadAction, &QAction::triggered, this, &SearchJobWidget::downloadTorrents);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("download"), tr("Download")
|
||||
, this, &SearchJobWidget::downloadTorrents);
|
||||
menu->addSeparator();
|
||||
|
||||
const QAction *openDescriptionAction = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("application-x-mswinurl"), tr("Open description page"));
|
||||
connect(openDescriptionAction, &QAction::triggered, this, &SearchJobWidget::openTorrentPages);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("application-x-mswinurl"), tr("Open description page")
|
||||
, this, &SearchJobWidget::openTorrentPages);
|
||||
|
||||
QMenu *copySubMenu = menu->addMenu(
|
||||
UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy"));
|
||||
|
||||
const QAction *copyNamesAction = copySubMenu->addAction(
|
||||
UIThemeManager::instance()->getIcon("edit-copy"), tr("Name"));
|
||||
connect(copyNamesAction, &QAction::triggered, this, &SearchJobWidget::copyTorrentNames);
|
||||
|
||||
const QAction *copyDownloadLinkAction = copySubMenu->addAction(
|
||||
UIThemeManager::instance()->getIcon("edit-copy"), tr("Download link"));
|
||||
connect(copyDownloadLinkAction, &QAction::triggered
|
||||
copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Name")
|
||||
, this, &SearchJobWidget::copyTorrentNames);
|
||||
copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Download link")
|
||||
, this, &SearchJobWidget::copyTorrentDownloadLinks);
|
||||
|
||||
const QAction *copyDescriptionAction = copySubMenu->addAction(
|
||||
UIThemeManager::instance()->getIcon("edit-copy"), tr("Description page URL"));
|
||||
connect(copyDescriptionAction, &QAction::triggered, this, &SearchJobWidget::copyTorrentURLs);
|
||||
copySubMenu->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Description page URL")
|
||||
, this, &SearchJobWidget::copyTorrentURLs);
|
||||
|
||||
menu->popup(event->globalPos());
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
|
||||
#include "tagfilterwidget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
|
||||
@@ -108,44 +107,25 @@ void TagFilterWidget::showMenu(QPoint)
|
||||
QMenu *menu = new QMenu(this);
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
const QAction *addAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("list-add")
|
||||
, tr("Add tag..."));
|
||||
connect(addAct, &QAction::triggered, this, &TagFilterWidget::addTag);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add tag...")
|
||||
, this, &TagFilterWidget::addTag);
|
||||
|
||||
const auto selectedRows = selectionModel()->selectedRows();
|
||||
if (!selectedRows.empty() && !TagFilterModel::isSpecialItem(selectedRows.first()))
|
||||
{
|
||||
const QAction *removeAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("list-remove")
|
||||
, tr("Remove tag"));
|
||||
connect(removeAct, &QAction::triggered, this, &TagFilterWidget::removeTag);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove tag")
|
||||
, this, &TagFilterWidget::removeTag);
|
||||
}
|
||||
|
||||
const QAction *removeUnusedAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("list-remove")
|
||||
, tr("Remove unused tags"));
|
||||
connect(removeUnusedAct, &QAction::triggered, this, &TagFilterWidget::removeUnusedTags);
|
||||
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove unused tags")
|
||||
, this, &TagFilterWidget::removeUnusedTags);
|
||||
menu->addSeparator();
|
||||
|
||||
const QAction *startAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("media-playback-start")
|
||||
, tr("Resume torrents"));
|
||||
connect(startAct, &QAction::triggered
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("media-playback-start"), tr("Resume torrents")
|
||||
, this, &TagFilterWidget::actionResumeTorrentsTriggered);
|
||||
|
||||
const QAction *pauseAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("media-playback-pause")
|
||||
, tr("Pause torrents"));
|
||||
connect(pauseAct, &QAction::triggered, this
|
||||
, &TagFilterWidget::actionPauseTorrentsTriggered);
|
||||
|
||||
const QAction *deleteTorrentsAct = menu->addAction(
|
||||
UIThemeManager::instance()->getIcon("edit-delete")
|
||||
, tr("Delete torrents"));
|
||||
connect(deleteTorrentsAct, &QAction::triggered, this
|
||||
, &TagFilterWidget::actionDeleteTorrentsTriggered);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("media-playback-pause"), tr("Pause torrents")
|
||||
, this, &TagFilterWidget::actionPauseTorrentsTriggered);
|
||||
menu->addAction(UIThemeManager::instance()->getIcon("edit-delete"), tr("Delete torrents")
|
||||
, this, &TagFilterWidget::actionDeleteTorrentsTriggered);
|
||||
|
||||
menu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "torrentcategorydialog.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "ui_torrentcategorydialog.h"
|
||||
@@ -40,6 +41,13 @@ TorrentCategoryDialog::TorrentCategoryDialog(QWidget *parent)
|
||||
m_ui->setupUi(this);
|
||||
m_ui->comboSavePath->setMode(FileSystemPathEdit::Mode::DirectorySave);
|
||||
m_ui->comboSavePath->setDialogCaption(tr("Choose save path"));
|
||||
|
||||
// disable save button
|
||||
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
connect(m_ui->textCategoryName, &QLineEdit::textChanged, this, [this](const QString &text)
|
||||
{
|
||||
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty());
|
||||
});
|
||||
}
|
||||
|
||||
TorrentCategoryDialog::~TorrentCategoryDialog()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user