mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-25 09:48:04 -06:00
Compare commits
217 Commits
release-4.
...
release-4.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0061b75200 | ||
|
|
420c93a99e | ||
|
|
93f1183cd7 | ||
|
|
b8fcc1fed2 | ||
|
|
2b91be1905 | ||
|
|
7c9ef96ef8 | ||
|
|
37b4b69199 | ||
|
|
fc18e6f8df | ||
|
|
4793a35e0b | ||
|
|
4599da3ce1 | ||
|
|
dec4e41fdd | ||
|
|
780ece0c25 | ||
|
|
aac8bfc398 | ||
|
|
1a06a18336 | ||
|
|
2d4f963d65 | ||
|
|
b54fe08201 | ||
|
|
d1d0300491 | ||
|
|
7fff06f07b | ||
|
|
3f9351042d | ||
|
|
9e01dbab0f | ||
|
|
d4a4b02cf6 | ||
|
|
1f2c7a6671 | ||
|
|
5a7b88c16c | ||
|
|
93351476e4 | ||
|
|
e1bfa95a63 | ||
|
|
7030cc08e7 | ||
|
|
a1da9812a5 | ||
|
|
8ebc0f529c | ||
|
|
e0d47649bc | ||
|
|
524d503860 | ||
|
|
cffafa8e9f | ||
|
|
0fda919268 | ||
|
|
7d98c34e17 | ||
|
|
93147e787b | ||
|
|
80435bae7e | ||
|
|
b367e5c197 | ||
|
|
5336c71da5 | ||
|
|
27f6db976d | ||
|
|
8223d61fa7 | ||
|
|
3eef12bd8f | ||
|
|
9e70a6c499 | ||
|
|
fec3a87421 | ||
|
|
59aac32eb9 | ||
|
|
5ef3917769 | ||
|
|
2f767d96d9 | ||
|
|
de24fdfdc2 | ||
|
|
3bb6a68c9d | ||
|
|
f2406eb2f3 | ||
|
|
4923ed7da0 | ||
|
|
82056355f6 | ||
|
|
f3bd2a295f | ||
|
|
cc96760839 | ||
|
|
ae95943f69 | ||
|
|
d3067f939e | ||
|
|
b6addd304c | ||
|
|
d1ae6e8d58 | ||
|
|
4445c2dab2 | ||
|
|
fcc1564a62 | ||
|
|
615eeb7144 | ||
|
|
855bb118b5 | ||
|
|
9f1eb3600a | ||
|
|
fb885d89c1 | ||
|
|
a846916beb | ||
|
|
a574c4a70a | ||
|
|
1e367f818d | ||
|
|
00599c8f02 | ||
|
|
332a836746 | ||
|
|
a1992acc16 | ||
|
|
c3f002a544 | ||
|
|
c28cbe0a74 | ||
|
|
435daaceed | ||
|
|
e29ab0087b | ||
|
|
aadd5a3312 | ||
|
|
7e354ffad3 | ||
|
|
ee6a071fb6 | ||
|
|
bc8b838953 | ||
|
|
5251d93b3d | ||
|
|
84f0dbecfe | ||
|
|
bba0c8b2cc | ||
|
|
2f90be8bd2 | ||
|
|
cb6b6296aa | ||
|
|
9d25fdce2a | ||
|
|
12b2732f1a | ||
|
|
8c9ece73ee | ||
|
|
a7db786387 | ||
|
|
e5bf65c9bd | ||
|
|
900e7d3a14 | ||
|
|
f1ff74a926 | ||
|
|
30bc4b837e | ||
|
|
050a4f8b23 | ||
|
|
487103d58f | ||
|
|
eeea69d4c1 | ||
|
|
00360ad418 | ||
|
|
a733253ae5 | ||
|
|
9788ee042b | ||
|
|
e9c9ea3bba | ||
|
|
312dfb989d | ||
|
|
75deafe5b1 | ||
|
|
4ca257a389 | ||
|
|
03375a78f2 | ||
|
|
423c7066d7 | ||
|
|
5cd5cc71a8 | ||
|
|
45d4d22055 | ||
|
|
916a92aa0d | ||
|
|
d1ebbcb35d | ||
|
|
2743d998a8 | ||
|
|
dbbfbaff9f | ||
|
|
0be8439cf6 | ||
|
|
66982c5524 | ||
|
|
85af8547f7 | ||
|
|
e26977ab2c | ||
|
|
ec1cc783a6 | ||
|
|
03b00ec045 | ||
|
|
5e90156e9e | ||
|
|
052206efa1 | ||
|
|
305d73180b | ||
|
|
80000bf0fd | ||
|
|
06ebe756e8 | ||
|
|
5fa3d9f19c | ||
|
|
5b4c6d3665 | ||
|
|
77bd0f17d1 | ||
|
|
03a702cfbd | ||
|
|
a932cd2ec1 | ||
|
|
8e5743380a | ||
|
|
8001eb0368 | ||
|
|
f214dc88fc | ||
|
|
5cff5ab135 | ||
|
|
82ba154b64 | ||
|
|
4ea44bbd2b | ||
|
|
a5e68a8725 | ||
|
|
70291014d1 | ||
|
|
1aabcfc30c | ||
|
|
aba80e2b1c | ||
|
|
be683fbcd3 | ||
|
|
2bcf09cfa5 | ||
|
|
697325af63 | ||
|
|
c21bd77be5 | ||
|
|
d5430adaaa | ||
|
|
9e99a0d3f5 | ||
|
|
d088ab6f43 | ||
|
|
820d510c12 | ||
|
|
676847fcd0 | ||
|
|
0204630ee6 | ||
|
|
b515c7eda4 | ||
|
|
73fcecac76 | ||
|
|
a7b82ebcb5 | ||
|
|
f8598b010d | ||
|
|
93779bcc4b | ||
|
|
938f5b9dd9 | ||
|
|
3b4d9f49d5 | ||
|
|
171c93af50 | ||
|
|
6f81e40106 | ||
|
|
e19b5cb2ce | ||
|
|
2c69faca58 | ||
|
|
9272151d0a | ||
|
|
d45ebf5a43 | ||
|
|
8074be7644 | ||
|
|
c99ac99a99 | ||
|
|
976e2450ec | ||
|
|
7e4db8fafd | ||
|
|
115a409d92 | ||
|
|
c203ab3d16 | ||
|
|
5dff96496d | ||
|
|
f813935011 | ||
|
|
2be719449f | ||
|
|
2094c870d5 | ||
|
|
4fe93ae8b8 | ||
|
|
fff1103cf4 | ||
|
|
8cede43a45 | ||
|
|
9b1fa3a5af | ||
|
|
409e73c074 | ||
|
|
c893729d62 | ||
|
|
945466968c | ||
|
|
54f080b755 | ||
|
|
bfad14d552 | ||
|
|
2972e1596d | ||
|
|
987d2aae88 | ||
|
|
4707d34fad | ||
|
|
2ffc09d097 | ||
|
|
afa8d6bb8f | ||
|
|
a37ead98e8 | ||
|
|
c73cd8d618 | ||
|
|
800a3aa61e | ||
|
|
ebd815be75 | ||
|
|
ef669acf89 | ||
|
|
ac6426eab1 | ||
|
|
b107b745f2 | ||
|
|
3d851a448f | ||
|
|
ce133f01aa | ||
|
|
492d378537 | ||
|
|
7ece484423 | ||
|
|
be5ad63e21 | ||
|
|
bdac8f8db8 | ||
|
|
bb893e70c5 | ||
|
|
57ec9db532 | ||
|
|
0287481001 | ||
|
|
0167496ecb | ||
|
|
d86cf193a0 | ||
|
|
246cad1108 | ||
|
|
23bf86a8a8 | ||
|
|
6ce4c885b9 | ||
|
|
faf84e483a | ||
|
|
576004c840 | ||
|
|
c93b05c293 | ||
|
|
55c3813fac | ||
|
|
725c6857be | ||
|
|
86767c9ab4 | ||
|
|
46aa631d2b | ||
|
|
7c61a937c9 | ||
|
|
b8d65dcc45 | ||
|
|
b9ab83eaf2 | ||
|
|
8b7b563992 | ||
|
|
b813a878d7 | ||
|
|
54e486c389 | ||
|
|
12d0a3acc1 | ||
|
|
6ad2a13386 | ||
|
|
2a9c401db9 |
@@ -45,8 +45,7 @@ before_build:
|
||||
- CALL "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat"
|
||||
- SET PATH=%PATH%;c:\qbt\qt5_32\bin;%CACHE_DIR%\jom;
|
||||
# setup project
|
||||
- COPY /Y "%CACHE_DIR%\winconf.pri" "%REPO_DIR%"
|
||||
- COPY /Y "%CACHE_DIR%\winconf-msvc.pri" "%REPO_DIR%"
|
||||
- COPY /Y "%CACHE_DIR%\conf.pri" "%REPO_DIR%"
|
||||
# workarounds
|
||||
- MKLINK /J "c:\qbt\base" "%CACHE_DIR%\base"
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,6 +22,7 @@ qrc_*.cpp
|
||||
ui_*.h
|
||||
*.moc
|
||||
src/lang/qbittorrent_*.qm
|
||||
src/webui/www/translations/webui_*.qm
|
||||
.DS_Store
|
||||
.qmake.stash
|
||||
src/qbittorrent.app
|
||||
|
||||
30
.travis.yml
30
.travis.yml
@@ -3,7 +3,6 @@ language: cpp
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
osx_image: xcode7.3
|
||||
|
||||
env:
|
||||
matrix:
|
||||
@@ -121,34 +120,16 @@ install:
|
||||
# dependencies
|
||||
brew update > /dev/null
|
||||
brew outdated "pkg-config" || brew upgrade "pkg-config"
|
||||
brew install colormake ccache zlib qt
|
||||
brew install colormake ccache zlib qt libtorrent-rasterbar
|
||||
PATH="/usr/local/opt/ccache/libexec:$PATH"
|
||||
brew link --force zlib qt
|
||||
|
||||
wget https://builds.shiki.hu/homebrew/version
|
||||
if ! cmp --quiet "version" "$HOME/hombebrew_cache/version" ; then
|
||||
echo "Cached files are different from server. Downloading new ones."
|
||||
# First delete old files
|
||||
rm -r "$HOME/hombebrew_cache"
|
||||
mkdir "$HOME/hombebrew_cache"
|
||||
cp "version" $HOME/hombebrew_cache
|
||||
cd "$HOME/hombebrew_cache"
|
||||
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar.rb
|
||||
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar-1.1.7+git20180422.3ede0b9c20+patched-configure.el_capitan.bottle.tar.gz
|
||||
fi
|
||||
|
||||
# Copy custom libtorrent bottle to homebrew's cache so it can find and install it
|
||||
# Also install our custom libtorrent formula by passing the local path to it
|
||||
# These 2 files are restored from Travis' cache.
|
||||
cp "$HOME/hombebrew_cache/libtorrent-rasterbar-1.1.7+git20180422.3ede0b9c20+patched-configure.el_capitan.bottle.tar.gz" "$(brew --cache)"
|
||||
brew install "$HOME/hombebrew_cache/libtorrent-rasterbar.rb"
|
||||
|
||||
if [ "$build_system" = "cmake" ]; then
|
||||
brew outdated cmake || brew upgrade cmake
|
||||
brew install ninja
|
||||
|
||||
ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs
|
||||
ln -s /usr/local/opt/qt/plugins /usr/local/plugins
|
||||
sudo ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs
|
||||
sudo ln -s /usr/local/opt/qt/plugins /usr/local/plugins
|
||||
fi
|
||||
|
||||
MY_CMAKE_OPENSSL_HINT="-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/"
|
||||
@@ -166,7 +147,10 @@ script:
|
||||
if [ "$build_system" = "cmake" ]; then
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DGUI=${gui} -DCMAKE_INSTALL_PREFIX="$qbt_path" "$MY_CMAKE_OPENSSL_HINT" \
|
||||
if [ "$gui" = "false" ]; then
|
||||
DISABLE_GUI_OPTION="-DCMAKE_DISABLE_FIND_PACKAGE_Qt5Widgets=ON"
|
||||
fi
|
||||
cmake $DISABLE_GUI_OPTION -DCMAKE_INSTALL_PREFIX="$qbt_path" "$MY_CMAKE_OPENSSL_HINT" \
|
||||
-G "Ninja" -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=TRUE ..
|
||||
BUILD_TOOL="ninja"
|
||||
fi
|
||||
|
||||
@@ -12,7 +12,7 @@ mode = developer
|
||||
|
||||
|
||||
[qbittorrent.qbittorrentdesktop_master]
|
||||
source_file = src/icons/qBittorrent.desktop
|
||||
source_file = dist/unix/qbittorrent.desktop
|
||||
source_lang = en
|
||||
type = DESKTOP
|
||||
minimum_perc = 23
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_policy(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
|
||||
|
||||
message(WARNING "No official support for cmake build system. If it is broken, please submit patches!")
|
||||
message(AUTHOR_WARNING "If the build fails, please try the autotools/qmake method.")
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules)
|
||||
include(FunctionReadVersion)
|
||||
@@ -27,32 +26,29 @@ add_definitions(-DQBT_VERSION_BUILD=${VER_BUILD})
|
||||
add_definitions(-DQBT_VERSION="v${PROJECT_VERSION}")
|
||||
add_definitions(-DQBT_VERSION_2="${PROJECT_VERSION}")
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
include(GNUInstallDirs)
|
||||
endif (UNIX AND NOT APPLE)
|
||||
include(GNUInstallDirs)
|
||||
include(FeatureSummary)
|
||||
|
||||
# version requirements
|
||||
set(requiredBoostVersion 1.35)
|
||||
set(requiredQtVersion 5.5.1)
|
||||
|
||||
if(WIN32)
|
||||
include(winconf)
|
||||
endif(WIN32)
|
||||
|
||||
# we need options here, because they are used not only in "src" subdir, but in the "dist" dir too
|
||||
include(CMakeDependentOption)
|
||||
|
||||
option(SYSTEM_QTSINGLEAPPLICATION
|
||||
"Use the system qtsingleapplication library or shipped one otherwise")
|
||||
|
||||
option(GUI "Allows to disable GUI for headless running. Disables QtDBus and the GeoIP Database" ON)
|
||||
|
||||
option(WEBUI "Allows to disable the WebUI." ON)
|
||||
|
||||
option(STACKTRACE "Enable stacktrace feature" ON)
|
||||
|
||||
if (UNIX)
|
||||
cmake_dependent_option(SYSTEMD "Install the systemd service file (headless only)" OFF
|
||||
"NOT GUI" OFF)
|
||||
cmake_dependent_option(DBUS "Enable use of QtDBus (GUI only)" ON "GUI" OFF)
|
||||
endif(UNIX)
|
||||
# we need options here, at the top level, because they are used not only in "src" subdir, but in the "dist" dir too
|
||||
include(CompileFeature)
|
||||
|
||||
optional_compile_definitions(COUNTRIES_RESOLUTION FEATURE DESCRIPTION "Enable resolving peers IP addresses to countries"
|
||||
DEFAULT ON DISABLED DISABLE_COUNTRIES_RESOLUTION)
|
||||
optional_compile_definitions(STACKTRACE FEATURE DESCRIPTION "Enable stacktraces"
|
||||
DEFAULT ON ENABLED STACKTRACE)
|
||||
optional_compile_definitions(WEBUI FEATURE DESCRIPTION "Enables built-in HTTP server for headless use"
|
||||
DEFAULT ON DISABLED DISABLE_WEBUI)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(dist)
|
||||
|
||||
feature_summary(DESCRIPTION "\nConfiguration results:" WHAT ALL)
|
||||
|
||||
@@ -6,6 +6,31 @@ For programming languages other than C++ (e.g. JavaScript) used in this reposito
|
||||
**Note 2:** You can use the `uncrustify` program/tool to clean up any source file. Use it with the `uncrustify.cfg` configuration file found in the root folder.
|
||||
**Note 3:** There is also a style for QtCreator but it doesn't cover all cases. In QtCreator `Tools->Options...->C++->Code Style->Import...` and choose the `codingStyleQtCreator.xml` file found in the root folder.
|
||||
|
||||
### Table Of Contents
|
||||
|
||||
* [1. New lines & curly braces](#1-new-lines--curly-braces)
|
||||
* [a. Function blocks, class/struct definitions, namespaces](#a-function-blocks-classstruct-definitions-namespaces)
|
||||
* [b. Other code blocks](#b-other-code-blocks)
|
||||
* [c. Blocks in switch's case labels](#c-blocks-in-switchs-case-labels)
|
||||
* [d. If-else statements](#d-if-else-statements)
|
||||
* [e. Single statement if blocks](#e-single-statement-if-blocks)
|
||||
* [f. Acceptable conditions to omit braces](#f-acceptable-conditions-to-omit-braces)
|
||||
* [g. Brace enclosed initializers](#g-brace-enclosed-initializers)
|
||||
* [2. Indentation](#2-indentation)
|
||||
* [3. File encoding and line endings](#3-file-encoding-and-line-endings)
|
||||
* [4. Initialization lists](#4-initialization-lists)
|
||||
* [5. Enums](#5-enums)
|
||||
* [6. Names](#6-names)
|
||||
* [a. Type names and namespaces](#a-type-names-and-namespaces)
|
||||
* [b. Variable names](#b-variable-names)
|
||||
* [c. Private member variable names](#c-private-member-variable-names)
|
||||
* [7. Header inclusion order](#7-header-inclusion-order)
|
||||
* [8. Include guard](#8-include-guard)
|
||||
* [9. Misc](#9-misc)
|
||||
* [10. Git commit message](#10-git-commit-message)
|
||||
* [11. Not covered above](#11-not-covered-above)
|
||||
---
|
||||
|
||||
### 1. New lines & curly braces ###
|
||||
|
||||
#### a. Function blocks, class/struct definitions, namespaces ####
|
||||
@@ -165,11 +190,11 @@ QVariantMap map {{"key1", 5}, {"key2", 10}};
|
||||
### 2. Indentation ###
|
||||
4 spaces.
|
||||
|
||||
### 3. File encoding and line endings. ###
|
||||
### 3. File encoding and line endings ###
|
||||
|
||||
UTF-8 and Unix-like line ending (LF). Unless some platform specific files need other encodings/line endings.
|
||||
|
||||
### 4. Initialization lists. ###
|
||||
### 4. Initialization lists ###
|
||||
Initialization lists should be vertical. This will allow for more easily readable diffs. The initialization colon should be indented and in its own line along with first argument. The rest of the arguments should be indented too and have the comma prepended.
|
||||
```c++
|
||||
myClass::myClass(int a, int b, int c, int d)
|
||||
@@ -182,7 +207,7 @@ myClass::myClass(int a, int b, int c, int d)
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Enums. ###
|
||||
### 5. Enums ###
|
||||
Enums should be vertical. This will allow for more easily readable diffs. The members should be indented.
|
||||
```c++
|
||||
enum Days
|
||||
@@ -197,7 +222,7 @@ enum Days
|
||||
};
|
||||
```
|
||||
|
||||
### 6. Names. ###
|
||||
### 6. Names ###
|
||||
All names should be camelCased.
|
||||
|
||||
#### a. Type names and namespaces ####
|
||||
@@ -231,7 +256,7 @@ class MyClass
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Header inclusion order. ###
|
||||
### 7. Header inclusion order ###
|
||||
The headers should be placed in the following group order:
|
||||
1. Module header (in .cpp)
|
||||
2. C++ Standard Library headers
|
||||
@@ -297,7 +322,7 @@ Example:
|
||||
#include "ui_examplewidget.h"
|
||||
```
|
||||
|
||||
### 8. Include guard. ###
|
||||
### 8. Include guard ###
|
||||
`#pragma once` should be used instead of "include guard" in new code:
|
||||
```c++
|
||||
// examplewidget.h
|
||||
@@ -313,7 +338,7 @@ class ExampleWidget : public QWidget
|
||||
|
||||
```
|
||||
|
||||
### 9. Misc. ###
|
||||
### 9. Misc ###
|
||||
|
||||
* Line breaks for long lines with operation:
|
||||
|
||||
|
||||
112
Changelog
112
Changelog
@@ -1,3 +1,115 @@
|
||||
* Mon Nov 19 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.4
|
||||
- FEATURE: Recognize *.ts files as previewable (silver)
|
||||
- FEATURE: Allow to disable speed graphs (dzmat)
|
||||
- FEATURE: Clear LineEdit on ESC (silverqx)
|
||||
- BUGFIX: Fix divide-by-zero crash (Chocobo1)
|
||||
- BUGFIX: Remove speed limit checkbox in Options dialog (Chocobo1)
|
||||
- BUGFIX: Fix speed graph "high speeds" bug (dzmat)
|
||||
- BUGFIX: Don't update torrent status unnecessarily (glassez)
|
||||
- BUGFIX: Improve force recheck of paused torrent (glassez)
|
||||
- BUGFIX: Restore torrent in two steps (glassez)
|
||||
- BUGFIX: Improve scaling of speed graphs (dzmat)
|
||||
- BUGFIX: Add isNetworkFileSystem() detection on Windows. This allows network mounts to be monitored correctly by polling timer. (Chocobo1)
|
||||
- BUGFIX: Reduce horizontal graphs resolution. Improves perfomance. (dzmat)
|
||||
- BUGFIX: Don't recheck just checked torrent (mj-p, glassez)
|
||||
- BUGFIX: Add SMB2 magic number (Chocobo1)
|
||||
- BUGFIX: Restore startup perfomance to v4.1.2 times. Needs at least libtorrent 1.1.10 (sledgehammer999)
|
||||
- BUGFIX: Make strings actually translatable (sledgehammer999)
|
||||
- WEBUI: Handle downloading .torrent file as success (Tom Piccirello)
|
||||
- WEBUI: Fix Alternative Web UI to be available (glassez)
|
||||
- WEBUI: Consider empty locale setting as not set (glassez)
|
||||
- WEBUI: Add free disk space to WebUI status bar (Thomas Piccirello)
|
||||
- WEBUI: Add WebUI search API controller (Thomas Piccirello)
|
||||
- WEBUI: Fix WebUI Auto TMM context menu bug (Thomas Piccirello)
|
||||
- WEBUI: Use independent translation for WebUI (glassez)
|
||||
- WEBUI: Add categories WebAPI (Thomas Piccirello)
|
||||
- WEBUI: Fix minor JavaScript defects (Thomas Piccirello)
|
||||
- WEBUI: Add locale to js file path (Thomas Piccirello)
|
||||
- WEBUI: Translate WebUI torrents Status column (Thomas Piccirello)
|
||||
- WEBUI: Bump Web API version
|
||||
- RSS: Allow to disable downloading REPACK/PROPER matches (Stephen Dawkins)
|
||||
- RSS: Improve RSS Feed updating (glassez)
|
||||
- SEARCH: Allow resizing search filter in search job (thalieht)
|
||||
- SEARCH: Improve parser for search engine versions.txt (Chocobo1)
|
||||
- SEARCH: Update Python URLs (Chocobo1)
|
||||
- SEARCH: Fix asking to install Python (Chocobo1)
|
||||
- SEARCH: Reformat python code to be compliant with PEP8 (Chocobo1)
|
||||
- OTHER: cmake: restore out-of-source build (Eugene Shalygin)
|
||||
- OTHER: cmake: cmake: use C++14 when available (Eugene Shalygin)
|
||||
|
||||
* Tue Sep 18 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.3
|
||||
- FEATURE: Preselect name without extension when renaming files (thalieht)
|
||||
- FEATURE: Allow setting seq & first/last from context menu without metadata (thalieht)
|
||||
- BUGFIX: Show "N/A" if there is no scrape (thalieht)
|
||||
- BUGFIX: Save option about tracker favicons under correct key (sledgehammer999)
|
||||
- BUGFIX: When file data are unreachable pause torrent and show "Missing Files" status (temporary fix) (sledgehammer999)
|
||||
- BUGFIX: Don't disable DHT when using force proxy (Thomas Piccirello)
|
||||
- BUGFIX: Correctly save torrent queue position/state/priority changes in fastresume (glassez, thalieht, sledgehammer999)
|
||||
- BUGFIX: Fix icon height/width ratio (Chocobo1)
|
||||
- BUGFIX: Fix values sorted wrong in "Last Activity" column (Chocobo1)
|
||||
- BUGFIX: Replace png icons with svg (Chocobo1)
|
||||
- WEBUI: Allow WebUI sidebar filters to be hidden (Thomas Piccirello)
|
||||
- WEBUI: Increase WebUI Options initial height (Thomas Piccirello)
|
||||
- WEBUI: Adjust WebUI Options form alignment (Thomas Piccirello)
|
||||
- WEBUI: Fix WebUI unreachable issue (Chocobo1)
|
||||
- WEBUI: Require torrent category creation to be explicit (Thomas Piccirello)
|
||||
- WEBUI: Include category save path in web api sync data (Thomas Piccirello)
|
||||
- WEBUI: Add save path and editing to WebUI new category dialog (Thomas Piccirello)
|
||||
- WEBUI: Bump Web API version
|
||||
- SEARCH: Refactor in searchjob to always color visited entries (thalieht)
|
||||
- SEARCH: Set "enter" as shortcut to download the selected torrents in search job (thalieht)
|
||||
- SEARCH: Add regex option in the search filter's context menu (thalieht)
|
||||
- LINUX: Fix GUI scaling issue on Linux (Chocobo1)
|
||||
- LINUX: Fix regression that broke installing desktop file (Eli Schwartz)
|
||||
- OPENBSD: Better filesystem support for filewatcher (Elias M. Mariani)
|
||||
|
||||
* Sun Aug 12 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.2
|
||||
- FEATURE: New options for "inhibit sleep" (Lukas Greib)
|
||||
- FEATURE: Add option for regexps in the transferlist search filter's context menu (thalieht)
|
||||
- FEATURE: Add async io threads option to AdvancedSettings (tjjh89017)
|
||||
- FEATURE: Allow save resume interval to be disabled (Chocobo1)
|
||||
- FEATURE: Add checkbox for recursive download dialog (Chocobo1)
|
||||
- FEATURE: Add changelog link in program updater (Chocobo1)
|
||||
- BUGFIX: Avoid allocating large memory when loading a .torrent file (Couchy)
|
||||
- BUGFIX: Notify users on 1st time close/minimize to tray (sledgehammer999)
|
||||
- BUGFIX: Fix I/O error after fetching magnet metadata (Chocobo1)
|
||||
- BUGFIX: Never save resume data for already paused torrents (glassez)
|
||||
- BUGFIX: Make ProgramUpdater upgrade to 64-bit qbt when running on 64-bit Windows (Chocobo1)
|
||||
- BUGFIX: Put temporary files in qbt own temp folder (Chocobo1)
|
||||
- BUGFIX: Avoid potentially setting the wrong piece priorities (Chocobo1)
|
||||
- BUGFIX: Various code refactorings/improvements (Chocobo1, thalieht, glassez)
|
||||
- BUGFIX: Add options "Download in sequential order" and "Download first and last pieces first" in AddNewTorrentDialog (Chocobo1)
|
||||
- BUGFIX: Download favicon using appropriate protocol (glassez)
|
||||
- BUGFIX: Apply proxy settings on DownloadManager creation (glassez)
|
||||
- BUGFIX: Improve torrent initialization (glassez)
|
||||
- BUGFIX: Save resume data on torrent change events (glassez)
|
||||
- BUGFIX: Increase default resume data save interval (Chocobo1)
|
||||
- BUGFIX: Work around crash when procesing recursive download. Closes #9086 (Chocobo1)
|
||||
- BUGFIX: Reduce queries to python version (Chocobo1)
|
||||
- BUGFIX: Disable certain mouse wheel events in Options dialog (Chocobo1)
|
||||
- WEBUI: Send all rechecks in one request (Thomas Piccirello)
|
||||
- WEBUI: Add WebUI Force Reannounce option (Thomas Piccirello)
|
||||
- WEBUI: Create non-existing path in setLocationAction() (Goshik)
|
||||
- WEBUI: Add WebUI support for Mac ⌘ (Command) key (Thomas Piccirello)
|
||||
- WEBUI: Show current save path in 'Set location' window (Goshik)
|
||||
- WEBUI: Fix WebUI cache behavior for css files (Chocobo1)
|
||||
- WEBUI: Send Cache-Control header in WebUI responses (Chocobo1)
|
||||
- WEBUI: Add form-action to CSP (Thomas Piccirello)
|
||||
- WEBUI: Add upgrade-insecure-requests to CSP when HTTPS is enabled (Thomas Piccirello)
|
||||
- WEBUI: Reset WebUI ban counter on login success (Chocobo1)
|
||||
- WEBUI: Add logging messages in WebUI login action (Chocobo1)
|
||||
- WEBUI: Add option to control CSRF protection (Chocobo1)
|
||||
- WEBUI: Add option to control WebUI clickjacking protection (Chocobo1)
|
||||
- RSS: Implement "Sequential downloading" feature. Closes #6835 (glassez)
|
||||
- RSS: Don't use RSS feed URLs as base for file names. Closes #8399 (glassez)
|
||||
- SEARCH: Add a name filter for search results (thalieht)
|
||||
- SEARCH: Fix python version detection (Chocobo1)
|
||||
- SEARCH: Clear python cache conditionally (Chocobo1)
|
||||
- SEARCH: Properly normalize version string before parsing it (hannsen)
|
||||
- WINDOWS: Turn on Control Flow Guard for MSVC builds (Chocobo1)
|
||||
- MACOS: Replace deprecated function IOPMAssertionCreate() on macOS (Chocobo1)
|
||||
- OTHER: Fix CMake build with QtSingleApplication. Fixes #9196 (Eugene Shalygin)
|
||||
|
||||
* Sun May 27 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.1
|
||||
- FEATURE: Add 'Moving' state for torrents being relocated/moved (sledgehammer999)
|
||||
- FEATURE: Show rechecking progress (sledgehammer999)
|
||||
|
||||
49
INSTALL
49
INSTALL
@@ -1,54 +1,49 @@
|
||||
qBittorrent - A BitTorrent client in C++ / Qt4
|
||||
qBittorrent - A BitTorrent client in C++ / Qt
|
||||
------------------------------------------
|
||||
|
||||
1) Compile and install qBittorrent with Qt4 Graphical Interface
|
||||
1) Compile and install qBittorrent with Qt graphical interface
|
||||
|
||||
$ ./configure
|
||||
$ make && make install
|
||||
$ qbittorrent
|
||||
|
||||
will install and execute qBittorrent hopefully without any problems.
|
||||
will install and execute qBittorrent.
|
||||
|
||||
Dependencies:
|
||||
- Qt >= 4.6.0 (libqtgui, libqtcore, libqtnetwork, libqtxml, libqtdbus/optional)
|
||||
- Qt >= 5.5.1
|
||||
|
||||
- pkg-config executable
|
||||
- pkg-config
|
||||
|
||||
- libtorrent-rasterbar by Arvid Norberg (>= 1.0.6)
|
||||
-> http://www.libtorrent.net
|
||||
Be careful: another library (the one used by rTorrent) uses a similar name.
|
||||
- libtorrent-rasterbar >= 1.0.6 (by Arvid Norberg)
|
||||
* https://www.libtorrent.org/
|
||||
* Be careful: another library (the one used by rTorrent) uses a similar name
|
||||
|
||||
- libboost >= 1.35.x (libboost-system)
|
||||
- Boost >= 1.35
|
||||
|
||||
- python >= 2.3 (needed by search engine)
|
||||
* Run time only dependency
|
||||
- Python >= 2.7.9 / 3.3.0 (optional, runtime only)
|
||||
* Required by the internal search engine
|
||||
|
||||
- geoip-database (optional)
|
||||
* If qBittorrent cannot find this database, it will try to resolve countries using the Internet but it will be a lot slower.
|
||||
* Run time only dependency
|
||||
|
||||
2) Compile and install qBittorrent without Qt4 Graphical interface
|
||||
2) Compile and install qBittorrent without Qt graphical interface
|
||||
|
||||
$ ./configure --disable-gui
|
||||
$ make && make install
|
||||
$ qbittorrent
|
||||
$ qbittorrent-nox
|
||||
|
||||
will install and execute qBittorrent hopefully without any problems.
|
||||
will install and execute qBittorrent.
|
||||
|
||||
Dependencies:
|
||||
- Qt >= 4.4.0 (libqt-devel, libqtcore, libqtnetwork)
|
||||
- Qt >= 5.5.1
|
||||
|
||||
- pkg-config executable
|
||||
- pkg-config
|
||||
|
||||
- libtorrent-rasterbar by Arvid Norberg (>= v1.0.6)
|
||||
-> http://www.libtorrent.net
|
||||
Be careful: another library (the one used by rTorrent) uses a similar name.
|
||||
|
||||
- libboost: libboost-filesystem, libboost-date-time, libboost-thread, libboost-serialization
|
||||
- libtorrent-rasterbar >= 1.0.6 (by Arvid Norberg)
|
||||
* https://www.libtorrent.org/
|
||||
* Be careful: another library (the one used by rTorrent) uses a similar name
|
||||
|
||||
- Boost >= 1.35
|
||||
|
||||
DOCUMENTATION:
|
||||
Please note that there is a documentation with a "compiling howto" at http://wiki.qbittorrent.org.
|
||||
Please note that there is a "Compilation" section at http://wiki.qbittorrent.org.
|
||||
|
||||
------------------------------------------
|
||||
Christophe Dumez <chris@qbittorrent.org>
|
||||
sledgehammer999 <sledgehammer999@qbittorrent.org>
|
||||
|
||||
22
cmake/Modules/CompileFeature.cmake
Normal file
22
cmake/Modules/CompileFeature.cmake
Normal file
@@ -0,0 +1,22 @@
|
||||
# Helper function for coupling add_feature_info(), option(), and add_definitions()
|
||||
|
||||
function(optional_compile_definitions _name)
|
||||
set(options FEATURE)
|
||||
set(oneValueArgs DESCRIPTION DEFAULT)
|
||||
set(multiValueArgs ENABLED DISABLED)
|
||||
cmake_parse_arguments(OCD "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
option(${_name} "${OCD_DESCRIPTION}" ${OCD_DEFAULT})
|
||||
if (${${_name}})
|
||||
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY COMPILE_DEFINITIONS ${OCD_ENABLED})
|
||||
else()
|
||||
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY COMPILE_DEFINITIONS ${OCD_DISABLED})
|
||||
endif()
|
||||
if(${OCD_FEATURE})
|
||||
add_feature_info(${_name} ${_name} "${OCD_DESCRIPTION}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
macro(feature_option _name _description _default)
|
||||
option(${_name} "${_description}" ${_default})
|
||||
add_feature_info(${_name} ${_name} "${_description}")
|
||||
endmacro()
|
||||
@@ -99,6 +99,7 @@ list(FIND LibtorrentRasterbar_DEFINITIONS -DTORRENT_USE_OPENSSL LibtorrentRaster
|
||||
if(LibtorrentRasterbar_ENCRYPTION_INDEX GREATER -1)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
set(LibtorrentRasterbar_LIBRARIES ${LibtorrentRasterbar_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto)
|
||||
list(APPEND LibtorrentRasterbar_INCLUDE_DIRS "${OPENSSL_INCLUDE_DIR}")
|
||||
set(LibtorrentRasterbar_OPENSSL_ENABLED ON)
|
||||
endif()
|
||||
|
||||
@@ -113,10 +114,10 @@ mark_as_advanced(LibtorrentRasterbar_INCLUDE_DIR LibtorrentRasterbar_LIBRARY
|
||||
LibtorrentRasterbar_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES
|
||||
LibtorrentRasterbar_ENCRYPTION_INDEX)
|
||||
|
||||
if (LibtorrentRasterbar_FOUND AND NOT TARGET LibtorrentRasterbar::LibTorrent)
|
||||
add_library(LibtorrentRasterbar::LibTorrent UNKNOWN IMPORTED)
|
||||
if (LibtorrentRasterbar_FOUND AND NOT TARGET LibtorrentRasterbar::torrent-rasterbar)
|
||||
add_library(LibtorrentRasterbar::torrent-rasterbar UNKNOWN IMPORTED)
|
||||
|
||||
set_target_properties(LibtorrentRasterbar::LibTorrent PROPERTIES
|
||||
set_target_properties(LibtorrentRasterbar::torrent-rasterbar PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
|
||||
IMPORTED_LOCATION "${LibtorrentRasterbar_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LibtorrentRasterbar_INCLUDE_DIRS}"
|
||||
|
||||
@@ -1,94 +1,79 @@
|
||||
# - Try to find the QtSingleApplication includes and library
|
||||
# which defines
|
||||
#
|
||||
# QTSINGLEAPPLICATION_FOUND - system has QtSingleApplication
|
||||
# QTSINGLEAPPLICATION_INCLUDE_DIR - where to find header QtSingleApplication
|
||||
# QTSINGLEAPPLICATION_LIBRARIES - the libraries to link against to use QtSingleApplication
|
||||
# QTSINGLEAPPLICATION_LIBRARY - where to find the QtSingleApplication library (not for general use)
|
||||
# QtSingleApplication_FOUND - system has QtSingleApplication
|
||||
# QtSingleApplication_INCLUDE_DIR - where to find header QtSingleApplication
|
||||
# QtSingleApplication_LIBRARIES - the libraries to link against to use QtSingleApplication
|
||||
# QtSingleApplication_LIBRARY - where to find the QtSingleApplication library (not for general use)
|
||||
|
||||
# copyright (c) 2013 TI_Eugene ti.eugene@gmail.com
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the FreeBSD license.
|
||||
|
||||
SET(QTSINGLEAPPLICATION_FOUND FALSE)
|
||||
SET(QtSingleApplication_FOUND FALSE)
|
||||
|
||||
IF(QT4_FOUND)
|
||||
message(STATUS "Looking for Qt4 single application library")
|
||||
FIND_PATH(QTSINGLEAPPLICATION_INCLUDE_DIR QtSingleApplication
|
||||
# standard locations
|
||||
/usr/include
|
||||
/usr/include/QtSolutions
|
||||
# qt4 location except mac's frameworks
|
||||
"${QT_INCLUDE_DIR}/QtSolutions"
|
||||
# mac's frameworks
|
||||
${FRAMEWORK_INCLUDE_DIR}/QtSolutions
|
||||
)
|
||||
if (Qt5Widgets_FOUND)
|
||||
set(_includeFileName qtsingleapplication.h)
|
||||
else()
|
||||
set(_includeFileName qtsinglecoreapplication.h)
|
||||
endif()
|
||||
|
||||
SET(QTSINGLEAPPLICATION_NAMES ${QTSINGLEAPPLICATION_NAMES}
|
||||
QtSolutions_SingleApplication-2.6 libQtSolutions_SingleApplication-2.6)
|
||||
FIND_LIBRARY(QTSINGLEAPPLICATION_LIBRARY
|
||||
NAMES ${QTSINGLEAPPLICATION_NAMES}
|
||||
PATHS ${QT_LIBRARY_DIR}
|
||||
)
|
||||
ELSEIF(Qt5Core_FOUND)
|
||||
message(STATUS "Looking for Qt5 single application library")
|
||||
FOREACH(TOP_INCLUDE_PATH in ${Qt5Core_INCLUDE_DIRS} ${FRAMEWORK_INCLUDE_DIR})
|
||||
FIND_PATH(QTSINGLEAPPLICATION_INCLUDE_DIR QtSingleApplication ${TOP_INCLUDE_PATH}/QtSolutions)
|
||||
FOREACH(TOP_INCLUDE_PATH in ${Qt5Core_INCLUDE_DIRS} ${FRAMEWORK_INCLUDE_DIR})
|
||||
FIND_PATH(QtSingleApplication_INCLUDE_DIR ${_includeFileName} ${TOP_INCLUDE_PATH}/QtSolutions)
|
||||
|
||||
IF(QTSINGLEAPPLICATION_INCLUDE_DIR)
|
||||
BREAK()
|
||||
ENDIF()
|
||||
ENDFOREACH()
|
||||
IF(QtSingleApplication_INCLUDE_DIR)
|
||||
BREAK()
|
||||
ENDIF()
|
||||
ENDFOREACH()
|
||||
|
||||
SET(QTSINGLEAPPLICATION_NAMES ${QTSINGLEAPPLICATION_NAMES}
|
||||
Qt5Solutions_SingleApplication-2.6 libQt5Solutions_SingleApplication-2.6
|
||||
QtSolutions_SingleApplication-2.6 libQtSolutions_SingleApplication-2.6)
|
||||
GET_TARGET_PROPERTY(_QT5_CORELIBRARY Qt5::Core LOCATION)
|
||||
GET_FILENAME_COMPONENT(_QT5_CORELIBRARYPATH ${_QT5_CORELIBRARY} PATH)
|
||||
SET(QtSingleApplication_NAMES ${QtSingleApplication_NAMES}
|
||||
Qt5Solutions_SingleApplication-2.6 libQt5Solutions_SingleApplication-2.6
|
||||
QtSolutions_SingleApplication-2.6 libQtSolutions_SingleApplication-2.6)
|
||||
GET_TARGET_PROPERTY(_QT5_CORELIBRARY Qt5::Core LOCATION)
|
||||
GET_FILENAME_COMPONENT(_QT5_CORELIBRARYPATH ${_QT5_CORELIBRARY} PATH)
|
||||
|
||||
FIND_LIBRARY(QTSINGLEAPPLICATION_LIBRARY
|
||||
NAMES ${QTSINGLEAPPLICATION_NAMES}
|
||||
PATHS ${_QT5_CORELIBRARYPATH}
|
||||
)
|
||||
ENDIF()
|
||||
FIND_LIBRARY(QtSingleApplication_LIBRARY
|
||||
NAMES ${QtSingleApplication_NAMES}
|
||||
PATHS ${_QT5_CORELIBRARYPATH}
|
||||
)
|
||||
|
||||
IF (QTSINGLEAPPLICATION_LIBRARY AND QTSINGLEAPPLICATION_INCLUDE_DIR)
|
||||
IF (QtSingleApplication_LIBRARY AND QtSingleApplication_INCLUDE_DIR)
|
||||
|
||||
SET(QTSINGLEAPPLICATION_LIBRARIES ${QTSINGLEAPPLICATION_LIBRARY})
|
||||
SET(QTSINGLEAPPLICATION_FOUND TRUE)
|
||||
SET(QtSingleApplication_LIBRARIES ${QtSingleApplication_LIBRARY})
|
||||
SET(QtSingleApplication_FOUND TRUE)
|
||||
|
||||
IF (CYGWIN)
|
||||
IF(BUILD_SHARED_LIBS)
|
||||
# No need to define QTSINGLEAPPLICATION_USE_DLL here, because it's default for Cygwin.
|
||||
# No need to define QtSingleApplication_USE_DLL here, because it's default for Cygwin.
|
||||
ELSE(BUILD_SHARED_LIBS)
|
||||
SET (QTSINGLEAPPLICATION_DEFINITIONS -DQTSINGLEAPPLICATION_STATIC)
|
||||
SET (QtSingleApplication_DEFINITIONS -DQTSINGLEAPPLICATION_STATIC)
|
||||
ENDIF(BUILD_SHARED_LIBS)
|
||||
ENDIF (CYGWIN)
|
||||
|
||||
ENDIF (QTSINGLEAPPLICATION_LIBRARY AND QTSINGLEAPPLICATION_INCLUDE_DIR)
|
||||
ENDIF (QtSingleApplication_LIBRARY AND QtSingleApplication_INCLUDE_DIR)
|
||||
|
||||
IF (QTSINGLEAPPLICATION_FOUND)
|
||||
IF (NOT QtSingleApplication_FIND_QUIETLY)
|
||||
MESSAGE(STATUS "Found QtSingleApplication: ${QTSINGLEAPPLICATION_LIBRARY}")
|
||||
MESSAGE(STATUS " includes: ${QTSINGLEAPPLICATION_INCLUDE_DIR}")
|
||||
ENDIF (NOT QtSingleApplication_FIND_QUIETLY)
|
||||
ELSE (QTSINGLEAPPLICATION_FOUND)
|
||||
IF (QtSingleApplication_FOUND)
|
||||
IF (NOT QtSingleApplication_FIND_QUIETLY)
|
||||
MESSAGE(STATUS "Found QtSingleApplication: ${QtSingleApplication_LIBRARY}")
|
||||
MESSAGE(STATUS " includes: ${QtSingleApplication_INCLUDE_DIR}")
|
||||
ENDIF (NOT QtSingleApplication_FIND_QUIETLY)
|
||||
if(NOT TARGET QtSingleApplication::QtSingleApplication)
|
||||
add_library(QtSingleApplication::QtSingleApplication UNKNOWN IMPORTED)
|
||||
set_target_properties(QtSingleApplication::QtSingleApplication PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QtSingleApplication_INCLUDE_DIR}"
|
||||
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${QtSingleApplication_INCLUDE_DIR}"
|
||||
)
|
||||
if(EXISTS "${QtSingleApplication_LIBRARY}")
|
||||
set_target_properties(QtSingleApplication::QtSingleApplication PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
|
||||
IMPORTED_LOCATION "${QtSingleApplication_LIBRARY}")
|
||||
endif()
|
||||
endif(NOT TARGET QtSingleApplication::QtSingleApplication)
|
||||
|
||||
ELSE (QtSingleApplication_FOUND)
|
||||
IF (QtSingleApplication_FIND_REQUIRED)
|
||||
MESSAGE(FATAL_ERROR "Could not find QtSingleApplication library")
|
||||
ENDIF (QtSingleApplication_FIND_REQUIRED)
|
||||
ENDIF (QTSINGLEAPPLICATION_FOUND)
|
||||
ENDIF (QtSingleApplication_FOUND)
|
||||
|
||||
MARK_AS_ADVANCED(QTSINGLEAPPLICATION_INCLUDE_DIR QTSINGLEAPPLICATION_LIBRARY)
|
||||
|
||||
if(NOT TARGET QtSingleApplication::QtSingleApplication)
|
||||
add_library(QtSingleApplication::QtSingleApplication UNKNOWN IMPORTED)
|
||||
set_target_properties(QtSingleApplication::QtSingleApplication PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QTSINGLEAPPLICATION_INCLUDE_DIR}"
|
||||
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${QTSINGLEAPPLICATION_INCLUDE_DIR}"
|
||||
)
|
||||
if(EXISTS "${QTSINGLEAPPLICATION_LIBRARY}")
|
||||
set_target_properties(QtSingleApplication::QtSingleApplication PROPERTIES
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
|
||||
IMPORTED_LOCATION "${QTSINGLEAPPLICATION_LIBRARY}")
|
||||
endif()
|
||||
endif(NOT TARGET QtSingleApplication::QtSingleApplication)
|
||||
MARK_AS_ADVANCED(QtSingleApplication_INCLUDE_DIR QtSingleApplication_LIBRARY)
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
# a helper function which appends source to the main qBt target
|
||||
# sources file names are relative to the the ${qBittorrent_SOURCE_DIR}
|
||||
# a helper function which appends source to the target
|
||||
# sources file names are relative to the the target source dir
|
||||
|
||||
function (qbt_target_sources)
|
||||
set (_sources_rel "")
|
||||
foreach (_source IN ITEMS ${ARGN})
|
||||
if (IS_ABSOLUTE "${_source}")
|
||||
set(source_abs "${_source}")
|
||||
function (qbt_target_sources _target _scope)
|
||||
get_target_property(targetSourceDir ${_target} SOURCE_DIR)
|
||||
set(sourcesRelative "")
|
||||
foreach(source IN ITEMS ${ARGN})
|
||||
if(IS_ABSOLUTE "${source}")
|
||||
set(sourceAbsolutePath "${source}")
|
||||
else()
|
||||
get_filename_component(_source_abs "${_source}" ABSOLUTE)
|
||||
get_filename_component(sourceAbsolutePath "${source}" ABSOLUTE)
|
||||
endif()
|
||||
file (RELATIVE_PATH _source_rel "${qbt_executable_SOURCE_DIR}" "${_source_abs}")
|
||||
list (APPEND _sources_rel "${_source_rel}")
|
||||
file(RELATIVE_PATH sourceRelativePath "${targetSourceDir}" "${sourceAbsolutePath}")
|
||||
list(APPEND sourcesRelative "${sourceRelativePath}")
|
||||
endforeach()
|
||||
target_sources (qBittorrent PRIVATE "${_sources_rel}")
|
||||
endfunction (qbt_target_sources)
|
||||
target_sources(${_target} ${_scope} "${sourcesRelative}")
|
||||
endfunction(qbt_target_sources)
|
||||
|
||||
48
cmake/Modules/QbtTranslations.cmake
Normal file
48
cmake/Modules/QbtTranslations.cmake
Normal file
@@ -0,0 +1,48 @@
|
||||
# macros to handle translation files
|
||||
|
||||
# qbt_add_translations(<target> QRC_FILE <filename> TS_FILES <filenames>)
|
||||
# handles out of source builds for Qt resource files that include translations
|
||||
# The function generates translations out of the supplied list of .ts files in the build directory,
|
||||
# copies the .qrc file there, calls qt5_add_resources() adds its output to the target sources list.
|
||||
function(qbt_add_translations _target)
|
||||
set(oneValueArgs QRC_FILE)
|
||||
set(multiValueArgs TS_FILES)
|
||||
cmake_parse_arguments(QBT_TR "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
get_target_property(_binaryDir ${_target} BINARY_DIR)
|
||||
|
||||
if (NOT QBT_TR_QRC_FILE)
|
||||
message(FATAL_ERROR "QRC file is empty")
|
||||
endif()
|
||||
if (NOT QBT_TR_TS_FILES)
|
||||
message(FATAL_ERROR "TS_FILES files are empty")
|
||||
endif()
|
||||
|
||||
if(IS_ABSOLUTE "${QBT_TR_QRC_FILE}")
|
||||
file(RELATIVE_PATH _qrcToTs "${CMAKE_CURRENT_SOURCE_DIR}" "${QBT_TR_QRC_FILE}")
|
||||
else()
|
||||
set(_qrcToTs "${QBT_TR_QRC_FILE}")
|
||||
endif()
|
||||
|
||||
get_filename_component(_qrcToTsDir "${_qrcToTs}" DIRECTORY)
|
||||
|
||||
get_filename_component(_qmFilesBinaryDir "${CMAKE_CURRENT_BINARY_DIR}/${_qrcToTsDir}" ABSOLUTE)
|
||||
# to make qt5_add_translation() work as we need
|
||||
set_source_files_properties(${QBT_TR_TS_FILES} PROPERTIES OUTPUT_LOCATION "${_qmFilesBinaryDir}")
|
||||
qt5_add_translation(_qmFiles ${QBT_TR_TS_FILES})
|
||||
|
||||
set(_qrc_dest_dir "${_binaryDir}/${_qrcToTsDir}")
|
||||
set(_qrc_dest_file "${_binaryDir}/${QBT_TR_QRC_FILE}")
|
||||
|
||||
message(STATUS "copying ${QBT_TR_QRC_FILE} to ${_qrc_dest_dir}")
|
||||
file(COPY ${QBT_TR_QRC_FILE} DESTINATION ${_qrc_dest_dir})
|
||||
|
||||
set_source_files_properties("${_qrc_dest_file}" PROPERTIES
|
||||
GENERATED True
|
||||
OBJECT_DEPENDS "${_qmFiles}")
|
||||
|
||||
# With AUTORCC enabled rcc is ran by cmake before language files are generated,
|
||||
# and thus we call rcc explicitly
|
||||
qt5_add_resources(_resources "${_qrc_dest_file}")
|
||||
target_sources(${_target} PRIVATE "${_resources}")
|
||||
endfunction()
|
||||
@@ -1,9 +1,9 @@
|
||||
if (STACKTRACE_WIN)
|
||||
if (STACKTRACE)
|
||||
if ("${WINXXBITS}" NOT STREQUAL "Win64")
|
||||
add_compile_options(-fno-omit-frame-pointer)
|
||||
endif ("${WINXXBITS}" NOT STREQUAL "Win64")
|
||||
link_libraries(libdbghelp -Wl,--export-all-symbols)
|
||||
endif (STACKTRACE_WIN)
|
||||
endif (STACKTRACE)
|
||||
|
||||
if (("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") OR ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo"))
|
||||
link_libraries(-Wl,--dynamicbase)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
if (STACKTRACE_WIN)
|
||||
if (STACKTRACE)
|
||||
if ("${WINXXBITS}" STREQUAL "Win64")
|
||||
add_compile_options(-Zi)
|
||||
else ("${WINXXBITS}" STREQUAL "Win64")
|
||||
@@ -6,7 +6,7 @@ if (STACKTRACE_WIN)
|
||||
add_compile_options(-Oy-)
|
||||
endif ("${WINXXBITS}" STREQUAL "Win64")
|
||||
link_libraries(dbghelp.lib)
|
||||
endif (STACKTRACE_WIN)
|
||||
endif (STACKTRACE)
|
||||
|
||||
# Enable Wide characters
|
||||
add_definitions(-DTORRENT_USE_WPATH)
|
||||
|
||||
@@ -50,6 +50,8 @@ DEFINES += BOOST_USE_WINAPI_VERSION=0x0501
|
||||
#DEFINES += BOOST_ASIO_SEPARATE_COMPILATION
|
||||
# Enable if building against libtorrent 1.0.x (RC_1_0) (dynamic linking)
|
||||
#DEFINES += BOOST_ASIO_DYN_LINK
|
||||
# Enable if encountered build error with boost version <= 1.59
|
||||
#DEFINES += BOOST_NO_CXX11_RVALUE_REFERENCES
|
||||
|
||||
# Enable if building against libtorrent 1.1.x (RC_1_1)
|
||||
# built with this flag defined
|
||||
@@ -59,3 +61,9 @@ DEFINES += BOOST_USE_WINAPI_VERSION=0x0501
|
||||
|
||||
# Enable stack trace support
|
||||
CONFIG += stacktrace
|
||||
|
||||
win32-msvc* {
|
||||
QMAKE_CXXFLAGS += "/guard:cf"
|
||||
QMAKE_LFLAGS += "/guard:cf"
|
||||
QMAKE_LFLAGS_RELEASE += "/OPT:REF /OPT:ICF"
|
||||
}
|
||||
|
||||
58
configure
vendored
58
configure
vendored
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.69 for qbittorrent v4.1.1.
|
||||
# Generated by GNU Autoconf 2.69 for qbittorrent v4.1.4.
|
||||
#
|
||||
# Report bugs to <bugs.qbittorrent.org>.
|
||||
#
|
||||
@@ -580,8 +580,8 @@ MAKEFLAGS=
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='qbittorrent'
|
||||
PACKAGE_TARNAME='qbittorrent'
|
||||
PACKAGE_VERSION='v4.1.1'
|
||||
PACKAGE_STRING='qbittorrent v4.1.1'
|
||||
PACKAGE_VERSION='v4.1.4'
|
||||
PACKAGE_STRING='qbittorrent v4.1.4'
|
||||
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
|
||||
PACKAGE_URL='https://www.qbittorrent.org/'
|
||||
|
||||
@@ -1297,7 +1297,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.1.1 to adapt to many kinds of systems.
|
||||
\`configure' configures qbittorrent v4.1.4 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -1368,7 +1368,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.1.1:";;
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.1.4:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@@ -1503,7 +1503,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
qbittorrent configure v4.1.1
|
||||
qbittorrent configure v4.1.4
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
@@ -1642,7 +1642,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.1.1, which was
|
||||
It was created by qbittorrent $as_me v4.1.4, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@@ -3820,7 +3820,7 @@ fi
|
||||
|
||||
# Define the identity of the package.
|
||||
PACKAGE='qbittorrent'
|
||||
VERSION='v4.1.1'
|
||||
VERSION='v4.1.4'
|
||||
|
||||
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
@@ -5014,6 +5014,40 @@ fi
|
||||
CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS"
|
||||
LDFLAGS="$BOOST_LDFLAGS $LDFLAGS"
|
||||
|
||||
# add workaround for problematic boost version
|
||||
ac_ext=cpp
|
||||
ac_cpp='$CXXCPP $CPPFLAGS'
|
||||
ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
|
||||
ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
|
||||
ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
|
||||
|
||||
# taken from ax_boost_base.m4
|
||||
|
||||
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
#include <boost/version.hpp>
|
||||
int
|
||||
main ()
|
||||
{
|
||||
(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < (106000))]));
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
_ACEOF
|
||||
if ac_fn_cxx_try_compile "$LINENO"; then :
|
||||
|
||||
else
|
||||
QBT_ADD_DEFINES="$QBT_ADD_DEFINES BOOST_NO_CXX11_RVALUE_REFERENCES"
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
|
||||
ac_ext=cpp
|
||||
ac_cpp='$CXXCPP $CPPFLAGS'
|
||||
ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
|
||||
ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
|
||||
ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
|
||||
|
||||
|
||||
|
||||
|
||||
# Check whether --with-boost-system was given.
|
||||
@@ -6140,7 +6174,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.1.1, which was
|
||||
This file was extended by qbittorrent $as_me v4.1.4, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -6198,7 +6232,7 @@ _ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
qbittorrent config.status v4.1.1
|
||||
qbittorrent config.status v4.1.4
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
@@ -7455,7 +7489,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.1.1, which was
|
||||
This file was extended by qbittorrent $as_me v4.1.4, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -7513,7 +7547,7 @@ _ACEOF
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
|
||||
ac_cs_version="\\
|
||||
qbittorrent config.status v4.1.1
|
||||
qbittorrent config.status v4.1.4
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
13
configure.ac
13
configure.ac
@@ -1,4 +1,4 @@
|
||||
AC_INIT([qbittorrent], [v4.1.1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_INIT([qbittorrent], [v4.1.4], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_PROG_CC
|
||||
@@ -168,6 +168,17 @@ AX_BOOST_BASE([1.35],
|
||||
CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS"
|
||||
LDFLAGS="$BOOST_LDFLAGS $LDFLAGS"
|
||||
|
||||
# add workaround for problematic boost version
|
||||
AC_LANG_PUSH(C++)
|
||||
# taken from ax_boost_base.m4
|
||||
m4_define([DETECT_BOOST_VERSION_PROGRAM],
|
||||
[AC_LANG_PROGRAM([[#include <boost/version.hpp>]],
|
||||
[[(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($1))]));]])])
|
||||
|
||||
AC_COMPILE_IFELSE([DETECT_BOOST_VERSION_PROGRAM(106000)], [],
|
||||
[QBT_ADD_DEFINES="$QBT_ADD_DEFINES BOOST_NO_CXX11_RVALUE_REFERENCES"])
|
||||
AC_LANG_POP([C++])
|
||||
|
||||
AX_BOOST_SYSTEM()
|
||||
AC_MSG_NOTICE([Boost.System LIB: "$BOOST_SYSTEM_LIB"])
|
||||
LIBS="$BOOST_SYSTEM_LIB $LIBS"
|
||||
|
||||
2
dist/CMakeLists.txt
vendored
2
dist/CMakeLists.txt
vendored
@@ -1,3 +1,5 @@
|
||||
find_package(Qt5Widgets ${requiredQtVersion}) # to conditionally install desktop-related files
|
||||
|
||||
if (APPLE)
|
||||
add_subdirectory(mac)
|
||||
else (APPLE)
|
||||
|
||||
2
dist/mac/Info.plist
vendored
2
dist/mac/Info.plist
vendored
@@ -45,7 +45,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.1.1</string>
|
||||
<string>4.1.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>qBit</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
||||
30
dist/unix/CMakeLists.txt
vendored
30
dist/unix/CMakeLists.txt
vendored
@@ -1,31 +1,37 @@
|
||||
if (SYSTEMD)
|
||||
find_package(Systemd)
|
||||
if (SYSTEMD_FOUND)
|
||||
if (NOT Qt5Widgets_FOUND)
|
||||
feature_option(SYSTEMD "Install systemd service file (headless only)" OFF)
|
||||
if (SYSTEMD)
|
||||
if (NOT Systemd_SERVICES_INSTALL_DIR)
|
||||
find_package(Systemd)
|
||||
if (NOT Systemd_FOUND)
|
||||
message(FATAL_ERROR "Could not locate systemd services install dir."
|
||||
" Either pass -DSystemd_SERVICES_INSTALL_DIR=/path/to/systemd/services option or install systemd pkg-config")
|
||||
endif(NOT Systemd_FOUND)
|
||||
endif(NOT Systemd_SERVICES_INSTALL_DIR)
|
||||
set(EXPAND_BINDIR ${CMAKE_INSTALL_FULL_BINDIR})
|
||||
configure_file(systemd/qbittorrent-nox@.service.in ${CMAKE_CURRENT_BINARY_DIR}/qbittorrent-nox@.service @ONLY)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qbittorrent-nox@.service
|
||||
DESTINATION ${SYSTEMD_SERVICES_INSTALL_DIR}
|
||||
DESTINATION ${Systemd_SERVICES_INSTALL_DIR}
|
||||
COMPONENT data)
|
||||
endif(SYSTEMD_FOUND)
|
||||
endif(SYSTEMD)
|
||||
endif(SYSTEMD)
|
||||
endif()
|
||||
|
||||
|
||||
if (GUI)
|
||||
if (Qt5Widgets_FOUND)
|
||||
list(APPEND MAN_FILES ${qBittorrent_SOURCE_DIR}/doc/qbittorrent.1)
|
||||
else (GUI)
|
||||
else (Qt5Widgets_FOUND)
|
||||
list(APPEND MAN_FILES ${qBittorrent_SOURCE_DIR}/doc/qbittorrent-nox.1)
|
||||
endif (GUI)
|
||||
endif (Qt5Widgets_FOUND)
|
||||
|
||||
install(FILES ${MAN_FILES}
|
||||
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
|
||||
COMPONENT doc)
|
||||
|
||||
if (GUI)
|
||||
if (Qt5Widgets_FOUND)
|
||||
install(DIRECTORY menuicons/
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor
|
||||
FILES_MATCHING PATTERN "*.png")
|
||||
|
||||
install(FILES ${qBittorrent_SOURCE_DIR}/src/icons/qbittorrent.desktop
|
||||
install(FILES qbittorrent.desktop
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications/
|
||||
COMPONENT data)
|
||||
|
||||
|
||||
2
dist/windows/options.nsi
vendored
2
dist/windows/options.nsi
vendored
@@ -27,7 +27,7 @@ XPStyle on
|
||||
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
|
||||
|
||||
; Program specific
|
||||
!define PROG_VERSION "4.1.1"
|
||||
!define PROG_VERSION "4.1.4"
|
||||
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
||||
|
||||
@@ -3,7 +3,6 @@ TEMPLATE = subdirs
|
||||
SUBDIRS += src
|
||||
|
||||
include(version.pri)
|
||||
include(qm_gen.pri)
|
||||
|
||||
# Make target to create release tarball. Use 'make tarball'
|
||||
tarball.commands += rm -fR ../$${PROJECT_NAME}-$${PROJECT_VERSION}/ &&
|
||||
@@ -18,6 +17,10 @@ tarball.commands += rm -fR $${PROJECT_NAME}-$${PROJECT_VERSION}
|
||||
|
||||
QMAKE_EXTRA_TARGETS += tarball
|
||||
|
||||
# Translations included here (at top level) is to avoid regenerating the .qm files
|
||||
# every time when src.pro is processed
|
||||
include(src/lang/lang.pri)
|
||||
|
||||
# For Qt Creator beautifier
|
||||
DISTFILES += \
|
||||
uncrustify.cfg
|
||||
|
||||
21
qm_gen.pri
21
qm_gen.pri
@@ -1,21 +0,0 @@
|
||||
TS_IN = $$fromfile(src/src.pro,TRANSLATIONS)
|
||||
TS_IN_NOEXT = $$replace(TS_IN,".ts","")
|
||||
|
||||
isEmpty(QMAKE_LRELEASE) {
|
||||
win32:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]\\lrelease.exe
|
||||
else:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease
|
||||
unix {
|
||||
equals(QT_MAJOR_VERSION, 5) {
|
||||
!exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease-qt5 }
|
||||
}
|
||||
} else {
|
||||
!exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease }
|
||||
}
|
||||
}
|
||||
|
||||
message("Building translations")
|
||||
for(L,TS_IN_NOEXT) {
|
||||
message("Processing $${L}")
|
||||
system("$$QMAKE_LRELEASE -silent src/$${L}.ts -qm src/$${L}.qm")
|
||||
!exists("src/$${L}.qm"):error("Building translations failed, cannot continue")
|
||||
}
|
||||
@@ -1,40 +1,53 @@
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
set(CMAKE_CXX_STANDARD "11")
|
||||
add_definitions(-DBOOST_NO_CXX11_RVALUE_REFERENCES)
|
||||
|
||||
# If C++14 is available, use it as libtorent ABI depends on 11/14 version
|
||||
if (cxx_std_14 IN_LIST CMAKE_CXX_COMPILE_FEATURES)
|
||||
message(STATUS "Building in C++14 mode")
|
||||
set(CMAKE_CXX_STANDARD "14")
|
||||
else()
|
||||
message(STATUS "Building in C++11 mode")
|
||||
set(CMAKE_CXX_STANDARD "11")
|
||||
endif()
|
||||
|
||||
include(MacroQbtCompilerSettings)
|
||||
qbt_set_compiler_options()
|
||||
|
||||
include(MacroLinkQtComponents)
|
||||
include(QbtTargetSources)
|
||||
|
||||
find_package(Boost ${requiredBoostVersion} REQUIRED)
|
||||
find_package(LibtorrentRasterbar REQUIRED)
|
||||
|
||||
# Qt
|
||||
list(APPEND QBT_QT_COMPONENTS Core Network Xml)
|
||||
if (GUI)
|
||||
list (APPEND QBT_QT_COMPONENTS Gui Svg Widgets)
|
||||
if (WIN32)
|
||||
list (APPEND QBT_QT_COMPONENTS WinExtras)
|
||||
endif(WIN32)
|
||||
if (APPLE)
|
||||
list (APPEND QBT_GUI_OPTIONAL_LINK_LIBRARIES objc)
|
||||
list (APPEND QBT_QT_COMPONENTS MacExtras)
|
||||
endif (APPLE)
|
||||
endif (GUI)
|
||||
if (DBUS)
|
||||
list (APPEND QBT_QT_COMPONENTS DBus)
|
||||
endif (DBUS)
|
||||
find_package(Qt5 5.5.1 COMPONENTS ${QBT_QT_COMPONENTS} REQUIRED)
|
||||
if (Boost_VERSION VERSION_LESS 106000)
|
||||
add_definitions(-DBOOST_NO_CXX11_RVALUE_REFERENCES)
|
||||
endif()
|
||||
|
||||
if (GUI AND APPLE)
|
||||
# Fix MOC inability to detect macOS. This seems to only affect cmake.
|
||||
# Relevant issue: https://bugreports.qt.io/browse/QTBUG-58325
|
||||
set(CMAKE_AUTOMOC_MOC_OPTIONS ${CMAKE_AUTOMOC_MOC_OPTIONS} -DQ_OS_MAC)
|
||||
endif ()
|
||||
find_package(Qt5 ${requiredQtVersion} REQUIRED COMPONENTS Core Network Xml LinguistTools)
|
||||
find_package(Qt5Widgets ${requiredQtVersion})
|
||||
if (Qt5Widgets_FOUND)
|
||||
find_package(Qt5DBus ${requiredQtVersion})
|
||||
else()
|
||||
add_definitions(-DDISABLE_GUI)
|
||||
endif()
|
||||
|
||||
set_package_properties(Qt5Widgets PROPERTIES
|
||||
DESCRIPTION "Set of components for creating classic desktop-style UIs for the Qt5 framework"
|
||||
PURPOSE "Enables qBittorrent GUI. Unneeded for headless configuration."
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
|
||||
set_package_properties(Qt5DBus PROPERTIES
|
||||
DESCRIPTION "Qt5 module for inter-process communication over the D-Bus protocol"
|
||||
PURPOSE "Enables communication with other system components (e.g. notification service) via D-Bus. "
|
||||
TYPE RECOMMENDED
|
||||
)
|
||||
|
||||
set(CMAKE_AUTOMOC True)
|
||||
list(APPEND CMAKE_AUTORCC_OPTIONS -compress 9 -threshold 5)
|
||||
if (APPLE)
|
||||
# Workaround CMake bug (autogen does not pass required parameters to moc)
|
||||
# Relevant issue: https://gitlab.kitware.com/cmake/cmake/issues/18041
|
||||
list(APPEND CMAKE_AUTOMOC_MOC_OPTIONS -DQ_OS_MAC -DQ_OS_DARWIN)
|
||||
endif ()
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
@@ -43,56 +56,34 @@ add_definitions(-DQT_NO_CAST_TO_ASCII)
|
||||
# Efficient construction for QString & QByteArray (Qt >= 4.8)
|
||||
add_definitions(-DQT_USE_QSTRINGBUILDER)
|
||||
|
||||
if (NOT GUI)
|
||||
add_definitions(-DDISABLE_GUI -DDISABLE_COUNTRIES_RESOLUTION)
|
||||
endif (NOT GUI)
|
||||
|
||||
if (NOT WEBUI)
|
||||
add_definitions(-DDISABLE_WEBUI)
|
||||
endif (NOT WEBUI)
|
||||
|
||||
if (STACKTRACE)
|
||||
add_definitions(-DSTACKTRACE)
|
||||
endif(STACKTRACE)
|
||||
# nogui {
|
||||
# TARGET = qbittorrent-nox
|
||||
# } else {
|
||||
# CONFIG(static) {
|
||||
# DEFINES += QBT_STATIC_QT
|
||||
# QTPLUGIN += qico
|
||||
# }
|
||||
# TARGET = qbittorrent
|
||||
# }
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
add_compile_options(-Wformat -Wformat-security)
|
||||
endif ()
|
||||
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
if (CMAKE_BUILD_TYPE MATCHES "Debug")
|
||||
message(STATUS "Project is built in DEBUG mode.")
|
||||
else (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
else()
|
||||
message(STATUS "Project is built in RELEASE mode.")
|
||||
message(STATUS "Disabling debug output.")
|
||||
add_definitions(-DQT_NO_DEBUG_OUTPUT)
|
||||
endif (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
|
||||
set(QBT_USE_GUI ${GUI})
|
||||
set(QBT_USE_WEBUI ${WEBUI})
|
||||
endif()
|
||||
|
||||
configure_file(config.h.cmakein ${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
||||
|
||||
if (SYSTEM_QTSINGLEAPPLICATION)
|
||||
find_package(QtSingleApplication REQUIRED)
|
||||
else (SYSTEM_QTSINGLEAPPLICATION)
|
||||
find_package(QtSingleApplication)
|
||||
set_package_properties(QtSingleApplication PROPERTIES
|
||||
URL "https://code.qt.io/cgit/qt-solutions/qt-solutions.git/"
|
||||
DESCRIPTION "Qt library to start applications only once per user"
|
||||
TYPE RECOMMENDED
|
||||
PURPOSE "Use the system qtsingleapplication library or shipped one otherwise"
|
||||
)
|
||||
|
||||
if (NOT QtSingleApplication_FOUND)
|
||||
add_subdirectory(app/qtsingleapplication)
|
||||
endif (SYSTEM_QTSINGLEAPPLICATION)
|
||||
endif ()
|
||||
|
||||
add_subdirectory(app)
|
||||
add_subdirectory(base)
|
||||
|
||||
if (GUI)
|
||||
if (Qt5Widgets_FOUND)
|
||||
add_subdirectory(gui)
|
||||
endif (GUI)
|
||||
endif ()
|
||||
|
||||
if (WEBUI)
|
||||
add_subdirectory(webui)
|
||||
|
||||
@@ -1,43 +1,41 @@
|
||||
project(qbt_executable)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(QBT_APP_HEADERS
|
||||
add_executable(qBittorrent
|
||||
application.h
|
||||
cmdoptions.h
|
||||
filelogger.h
|
||||
)
|
||||
|
||||
set(QBT_APP_SOURCES
|
||||
upgrade.h
|
||||
application.cpp
|
||||
cmdoptions.cpp
|
||||
filelogger.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_include_directories(qBittorrent PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_link_libraries(qBittorrent
|
||||
PRIVATE
|
||||
qbt_base
|
||||
)
|
||||
|
||||
set_target_properties(qBittorrent
|
||||
PROPERTIES
|
||||
AUTOUIC True
|
||||
AUTORCC True
|
||||
MACOSX_BUNDLE True
|
||||
)
|
||||
|
||||
# translations
|
||||
include(QbtTranslations)
|
||||
|
||||
file(GLOB QBT_TS_FILES ../lang/*.ts)
|
||||
get_filename_component(QBT_QM_FILES_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/../lang" ABSOLUTE)
|
||||
set_source_files_properties(${QBT_TS_FILES} PROPERTIES OUTPUT_LOCATION "${QBT_QM_FILES_BINARY_DIR}")
|
||||
qbt_add_translations(qBittorrent QRC_FILE "../lang/lang.qrc" TS_FILES ${QBT_TS_FILES})
|
||||
|
||||
find_package(Qt5 COMPONENTS LinguistTools REQUIRED)
|
||||
qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES})
|
||||
|
||||
get_filename_component(_lang_qrc_src "${CMAKE_CURRENT_SOURCE_DIR}/../lang.qrc" ABSOLUTE)
|
||||
get_filename_component(_lang_qrc_dst "${CMAKE_CURRENT_BINARY_DIR}/../lang.qrc" ABSOLUTE)
|
||||
get_filename_component(_lang_qrc_dst_dir "${CMAKE_CURRENT_BINARY_DIR}/../" ABSOLUTE)
|
||||
|
||||
message(STATUS "copying ${_lang_qrc_src} -> ${_lang_qrc_dst}")
|
||||
file(COPY ${_lang_qrc_src} DESTINATION ${_lang_qrc_dst_dir})
|
||||
|
||||
set_source_files_properties("${_lang_qrc_dst}" PROPERTIES GENERATED True)
|
||||
foreach(qm_file ${QBT_QM_FILES})
|
||||
set_source_files_properties("${_lang_qrc_dst}" PROPERTIES OBJECT_DEPENDS ${qm_file})
|
||||
endforeach()
|
||||
if (WEBUI)
|
||||
file(GLOB QBT_WEBUI_TS_FILES ../webui/www/translations/*.ts)
|
||||
qbt_add_translations(qBittorrent QRC_FILE "../webui/www/translations/webui_translations.qrc" TS_FILES ${QBT_WEBUI_TS_FILES})
|
||||
endif()
|
||||
|
||||
set(QBT_APP_RESOURCES
|
||||
../icons.qrc
|
||||
../searchengine.qrc
|
||||
"${_lang_qrc_dst}"
|
||||
../icons/icons.qrc
|
||||
../searchengine/searchengine.qrc
|
||||
)
|
||||
|
||||
# With AUTORCC rcc is ran by cmake before language files are generated,
|
||||
@@ -46,51 +44,41 @@ qt5_add_resources(QBT_APP_RESOURCE_SOURCE ${QBT_APP_RESOURCES})
|
||||
|
||||
if (WIN32)
|
||||
if (MINGW)
|
||||
list (APPEND QBT_APP_SOURCES ../qbittorrent_mingw.rc)
|
||||
target_sources(qBittorrent PRIVATE ../qbittorrent_mingw.rc)
|
||||
else (MINGW)
|
||||
list (APPEND QBT_APP_SOURCES ../qbittorrent.rc)
|
||||
target_sources(qBittorrent PRIVATE ../qbittorrent.rc)
|
||||
endif (MINGW)
|
||||
list(APPEND QBT_APP_SOURCES ../qbittorrent.exe.manifest)
|
||||
target_sources(qBittorrent PRIVATE ../qbittorrent.exe.manifest)
|
||||
endif (WIN32)
|
||||
|
||||
if (STACKTRACE)
|
||||
if (UNIX)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace.h)
|
||||
target_sources(qBittorrent PRIVATE stacktrace.h)
|
||||
else (UNIX)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace_win.h)
|
||||
if (GUI)
|
||||
list(APPEND QBT_APP_HEADERS stacktrace_win_dlg.h)
|
||||
endif (GUI)
|
||||
target_sources(qBittorrent PRIVATE stacktrace_win.h)
|
||||
if (Qt5Widgets_FOUND)
|
||||
target_sources(qBittorrent PRIVATE stacktracedialog.h)
|
||||
endif (Qt5Widgets_FOUND)
|
||||
endif (UNIX)
|
||||
endif (STACKTRACE)
|
||||
|
||||
# usesystemqtsingleapplication {
|
||||
# nogui {
|
||||
# CONFIG += qtsinglecoreapplication
|
||||
# } else {
|
||||
# CONFIG += qtsingleapplication
|
||||
# }
|
||||
# } else {
|
||||
# nogui {
|
||||
# include(qtsingleapplication/qtsinglecoreapplication.pri)
|
||||
# } else {
|
||||
# include(qtsingleapplication/qtsingleapplication.pri)
|
||||
# }
|
||||
# }
|
||||
|
||||
# upgrade code
|
||||
list(APPEND QBT_APP_HEADERS upgrade.h)
|
||||
list(APPEND QBT_TARGET_LIBRARIES qbt_base)
|
||||
|
||||
if (GUI)
|
||||
list(APPEND QBT_TARGET_LIBRARIES qbt_searchengine qbt_gui)
|
||||
include_directories(../gui
|
||||
${CMAKE_CURRENT_BINARY_DIR}/../gui
|
||||
if (Qt5Widgets_FOUND)
|
||||
target_link_libraries(qBittorrent PRIVATE qbt_searchengine qbt_gui)
|
||||
set_target_properties(qBittorrent
|
||||
PROPERTIES
|
||||
OUTPUT_NAME qbittorrent
|
||||
WIN32_EXECUTABLE True
|
||||
)
|
||||
endif (GUI)
|
||||
else(Qt5Widgets_FOUND)
|
||||
set_target_properties(qBittorrent
|
||||
PROPERTIES
|
||||
OUTPUT_NAME qbittorrent-nox
|
||||
)
|
||||
endif (Qt5Widgets_FOUND)
|
||||
|
||||
if (WEBUI)
|
||||
list(APPEND QBT_TARGET_LIBRARIES qbt_webui)
|
||||
target_link_libraries(qBittorrent PRIVATE qbt_webui)
|
||||
endif (WEBUI)
|
||||
|
||||
# we have to include resources into the bundle
|
||||
@@ -142,30 +130,11 @@ if (APPLE)
|
||||
PROPERTIES MACOSX_PACKAGE_LOCATION translations)
|
||||
endif (APPLE)
|
||||
|
||||
add_executable(qBittorrent ${QBT_APP_HEADERS} ${QBT_APP_SOURCES} ${QBT_QM_FILES} ${QBT_APP_RESOURCE_SOURCE})
|
||||
if (GUI)
|
||||
set_target_properties(qBittorrent
|
||||
PROPERTIES
|
||||
OUTPUT_NAME qbittorrent
|
||||
WIN32_EXECUTABLE True
|
||||
)
|
||||
else (GUI)
|
||||
set_target_properties(qBittorrent
|
||||
PROPERTIES
|
||||
OUTPUT_NAME qbittorrent-nox
|
||||
)
|
||||
endif (GUI)
|
||||
|
||||
set_target_properties(qBittorrent
|
||||
PROPERTIES
|
||||
AUTOUIC True
|
||||
AUTORCC True
|
||||
MACOSX_BUNDLE True
|
||||
)
|
||||
target_sources(qBittorrent PRIVATE ${QBT_QM_FILES} ${QBT_APP_RESOURCE_SOURCE})
|
||||
|
||||
get_target_property(QBT_EXECUTABLE_NAME qBittorrent OUTPUT_NAME)
|
||||
|
||||
target_link_libraries(qBittorrent ${QBT_TARGET_LIBRARIES} QtSingleApplication::QtSingleApplication)
|
||||
target_link_libraries(qBittorrent PRIVATE ${QBT_TARGET_LIBRARIES} QtSingleApplication::QtSingleApplication)
|
||||
|
||||
if (APPLE)
|
||||
set(qbt_BUNDLE_NAME ${QBT_EXECUTABLE_NAME})
|
||||
@@ -186,6 +155,7 @@ install(TARGETS qBittorrent
|
||||
BUNDLE DESTINATION .
|
||||
COMPONENT runtime)
|
||||
|
||||
if (GUI AND APPLE)
|
||||
if (Qt5Widgets_FOUND AND APPLE)
|
||||
find_package(Qt5Svg REQUIRED)
|
||||
include(bundle)
|
||||
endif (GUI AND APPLE)
|
||||
endif (Qt5Widgets_FOUND AND APPLE)
|
||||
|
||||
@@ -32,8 +32,8 @@ stacktrace {
|
||||
else {
|
||||
HEADERS += $$PWD/stacktrace_win.h
|
||||
!nogui {
|
||||
HEADERS += $$PWD/stacktrace_win_dlg.h
|
||||
FORMS += $$PWD/stacktrace_win_dlg.ui
|
||||
HEADERS += $$PWD/stacktracedialog.h
|
||||
FORMS += $$PWD/stacktracedialog.ui
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,18 +31,34 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <memory>
|
||||
#endif
|
||||
|
||||
#include <QAtomicInt>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QLibraryInfo>
|
||||
#include <QLocale>
|
||||
#include <QProcess>
|
||||
#include <QSysInfo>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <memory>
|
||||
#include <Shellapi.h>
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QSessionManager>
|
||||
#include <QSharedMemory>
|
||||
#endif // Q_OS_WIN
|
||||
#ifdef Q_OS_MAC
|
||||
#include <QFileOpenEvent>
|
||||
#endif // Q_OS_MAC
|
||||
#include "addnewtorrentdialog.h"
|
||||
#include "gui/guiiconprovider.h"
|
||||
#include "mainwindow.h"
|
||||
#include "shutdownconfirmdialog.h"
|
||||
#else // DISABLE_GUI
|
||||
#include <cstdio>
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
#include "base/iconprovider.h"
|
||||
@@ -56,34 +72,13 @@
|
||||
#include "base/rss/rss_autodownloader.h"
|
||||
#include "base/rss/rss_session.h"
|
||||
#include "base/scanfoldersmodel.h"
|
||||
#include "base/search/searchpluginmanager.h"
|
||||
#include "base/settingsstorage.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "filelogger.h"
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QSessionManager>
|
||||
#include <QSharedMemory>
|
||||
#endif // Q_OS_WIN
|
||||
#ifdef Q_OS_MAC
|
||||
#include <QFileOpenEvent>
|
||||
#include <QFont>
|
||||
#include <QUrl>
|
||||
#endif // Q_OS_MAC
|
||||
#include "addnewtorrentdialog.h"
|
||||
#include "gui/guiiconprovider.h"
|
||||
#include "mainwindow.h"
|
||||
#include "shutdownconfirmdlg.h"
|
||||
#else // DISABLE_GUI
|
||||
#include <cstdio>
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Shellapi.h>
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
#include "webui/webui.h"
|
||||
#endif
|
||||
@@ -102,13 +97,13 @@ namespace
|
||||
const QString KEY_FILELOGGER_AGE = FILELOGGER_SETTINGS_KEY("Age");
|
||||
const QString KEY_FILELOGGER_AGETYPE = FILELOGGER_SETTINGS_KEY("AgeType");
|
||||
|
||||
//just a shortcut
|
||||
// just a shortcut
|
||||
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
|
||||
|
||||
const QString LOG_FOLDER("logs");
|
||||
const char PARAMS_SEPARATOR[] = "|";
|
||||
const QString LOG_FOLDER = QStringLiteral("logs");
|
||||
const QChar PARAMS_SEPARATOR = '|';
|
||||
|
||||
const QString DEFAULT_PORTABLE_MODE_PROFILE_DIR = QLatin1String("profile");
|
||||
const QString DEFAULT_PORTABLE_MODE_PROFILE_DIR = QStringLiteral("profile");
|
||||
|
||||
const int MIN_FILELOG_SIZE = 1024; // 1KiB
|
||||
const int MAX_FILELOG_SIZE = 1000 * 1024 * 1024; // 1000MiB
|
||||
@@ -261,17 +256,17 @@ void Application::setFileLoggerAge(const int value)
|
||||
int Application::fileLoggerAgeType() const
|
||||
{
|
||||
int val = settings()->loadValue(KEY_FILELOGGER_AGETYPE, 1).toInt();
|
||||
return (val < 0 || val > 2) ? 1 : val;
|
||||
return ((val < 0) || (val > 2)) ? 1 : val;
|
||||
}
|
||||
|
||||
void Application::setFileLoggerAgeType(const int value)
|
||||
{
|
||||
settings()->storeValue(KEY_FILELOGGER_AGETYPE, (value < 0 || value > 2) ? 1 : value);
|
||||
settings()->storeValue(KEY_FILELOGGER_AGETYPE, ((value < 0) || (value > 2)) ? 1 : value);
|
||||
}
|
||||
|
||||
void Application::processMessage(const QString &message)
|
||||
{
|
||||
QStringList params = message.split(QLatin1String(PARAMS_SEPARATOR), QString::SkipEmptyParts);
|
||||
QStringList params = message.split(PARAMS_SEPARATOR, QString::SkipEmptyParts);
|
||||
// If Application is not running (i.e., other
|
||||
// components are not ready) store params
|
||||
if (m_running)
|
||||
@@ -338,12 +333,12 @@ void Application::runExternalProgram(const BitTorrent::TorrentHandle *torrent) c
|
||||
void Application::sendNotificationEmail(const BitTorrent::TorrentHandle *torrent)
|
||||
{
|
||||
// Prepare mail content
|
||||
const QString content = tr("Torrent name: %1").arg(torrent->name()) + "\n"
|
||||
+ tr("Torrent size: %1").arg(Utils::Misc::friendlyUnit(torrent->wantedSize())) + "\n"
|
||||
const QString content = tr("Torrent name: %1").arg(torrent->name()) + '\n'
|
||||
+ tr("Torrent size: %1").arg(Utils::Misc::friendlyUnit(torrent->wantedSize())) + '\n'
|
||||
+ tr("Save path: %1").arg(torrent->savePath()) + "\n\n"
|
||||
+ tr("The torrent was downloaded in %1.", "The torrent was downloaded in 1 hour and 20 seconds")
|
||||
.arg(Utils::Misc::userFriendlyDuration(torrent->activeTime())) + "\n\n\n"
|
||||
+ tr("Thank you for using qBittorrent.") + "\n";
|
||||
+ tr("Thank you for using qBittorrent.") + '\n';
|
||||
|
||||
// Send the notification email
|
||||
const Preferences *pref = Preferences::instance();
|
||||
@@ -394,7 +389,7 @@ void Application::allTorrentsFinished()
|
||||
// do nothing & skip confirm
|
||||
}
|
||||
else {
|
||||
if (!ShutdownConfirmDlg::askForConfirmation(m_window, action)) return;
|
||||
if (!ShutdownConfirmDialog::askForConfirmation(m_window, action)) return;
|
||||
}
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
@@ -415,7 +410,7 @@ void Application::allTorrentsFinished()
|
||||
|
||||
bool Application::sendParams(const QStringList ¶ms)
|
||||
{
|
||||
return sendMessage(params.join(QLatin1String(PARAMS_SEPARATOR)));
|
||||
return sendMessage(params.join(PARAMS_SEPARATOR));
|
||||
}
|
||||
|
||||
// As program parameters, we can get paths or urls.
|
||||
@@ -520,10 +515,11 @@ int Application::exec(const QStringList ¶ms)
|
||||
|
||||
new RSS::Session; // create RSS::Session singleton
|
||||
new RSS::AutoDownloader; // create RSS::AutoDownloader singleton
|
||||
new SearchPluginManager;
|
||||
|
||||
#ifdef DISABLE_GUI
|
||||
#ifndef DISABLE_WEBUI
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Preferences *const pref = Preferences::instance();
|
||||
// Display some information to the user
|
||||
const QString mesg = QString("\n******** %1 ********\n").arg(tr("Information"))
|
||||
+ tr("To control qBittorrent, access the Web UI at %1")
|
||||
@@ -619,7 +615,7 @@ bool Application::notify(QObject *receiver, QEvent *event)
|
||||
|
||||
void Application::initializeTranslation()
|
||||
{
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Preferences *const pref = Preferences::instance();
|
||||
// Load translation
|
||||
QString localeStr = pref->getLocale();
|
||||
|
||||
@@ -685,7 +681,7 @@ void Application::cleanup()
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
if (m_window) {
|
||||
// Hide the window and not leave it on screen as
|
||||
// Hide the window and don't leave it on screen as
|
||||
// unresponsive. Also for Windows take the WinId
|
||||
// after it's hidden, because hide() may cause a
|
||||
// WinId change.
|
||||
@@ -714,6 +710,7 @@ void Application::cleanup()
|
||||
delete m_webui;
|
||||
#endif
|
||||
|
||||
delete SearchPluginManager::instance();
|
||||
delete RSS::AutoDownloader::instance();
|
||||
delete RSS::Session::instance();
|
||||
|
||||
|
||||
@@ -40,15 +40,13 @@ typedef QtSingleApplication BaseApplication;
|
||||
class MainWindow;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QSessionManager;
|
||||
QT_END_NAMESPACE
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
#else
|
||||
#include "qtsinglecoreapplication.h"
|
||||
typedef QtSingleCoreApplication BaseApplication;
|
||||
#endif
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
#include "base/utils/misc.h"
|
||||
#include "cmdoptions.h"
|
||||
@@ -112,7 +110,7 @@ protected:
|
||||
#ifdef Q_OS_MAC
|
||||
bool event(QEvent *) override;
|
||||
#endif
|
||||
bool notify(QObject* receiver, QEvent* event) override;
|
||||
bool notify(QObject *receiver, QEvent *event) override;
|
||||
#endif
|
||||
|
||||
private slots:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 Eugene Shalygin <eugene.shalygin@gmail.com>
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -26,8 +26,6 @@
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include "cmdoptions.h"
|
||||
@@ -100,7 +98,7 @@ namespace
|
||||
};
|
||||
|
||||
// Boolean option.
|
||||
class BoolOption: protected Option
|
||||
class BoolOption : protected Option
|
||||
{
|
||||
public:
|
||||
constexpr BoolOption(const char *name, char shortcut = 0)
|
||||
@@ -118,7 +116,7 @@ namespace
|
||||
{
|
||||
QString val = env.value(envVarName());
|
||||
// we accept "1" and "true" (upper or lower cased) as boolean 'true' values
|
||||
return (val == QLatin1String("1") || val.toUpper() == QLatin1String("TRUE"));
|
||||
return ((val == QLatin1String("1")) || (val.toUpper() == QLatin1String("TRUE")));
|
||||
}
|
||||
|
||||
QString usage() const
|
||||
@@ -137,7 +135,7 @@ namespace
|
||||
}
|
||||
|
||||
// Option with string value. May not have a shortcut
|
||||
struct StringOption: protected Option
|
||||
struct StringOption : protected Option
|
||||
{
|
||||
public:
|
||||
constexpr StringOption(const char *name)
|
||||
@@ -184,7 +182,7 @@ namespace
|
||||
}
|
||||
|
||||
// Option with integer value. May not have a shortcut
|
||||
class IntOption: protected StringOption
|
||||
class IntOption : protected StringOption
|
||||
{
|
||||
public:
|
||||
constexpr IntOption(const char *name)
|
||||
@@ -230,7 +228,7 @@ namespace
|
||||
|
||||
// Option that is explicitly set to true or false, and whose value is undefined when unspecified.
|
||||
// May not have a shortcut.
|
||||
class TriStateBoolOption: protected Option
|
||||
class TriStateBoolOption : protected Option
|
||||
{
|
||||
public:
|
||||
constexpr TriStateBoolOption(const char *name, bool defaultValue)
|
||||
@@ -260,10 +258,10 @@ namespace
|
||||
else if (parts.size() == 2) {
|
||||
QString val = parts[1];
|
||||
|
||||
if (val.toUpper() == QLatin1String("TRUE") || val == QLatin1String("1")) {
|
||||
if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1"))) {
|
||||
return TriStateBool::True;
|
||||
}
|
||||
else if (val.toUpper() == QLatin1String("FALSE") || val == QLatin1String("0")) {
|
||||
else if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0"))) {
|
||||
return TriStateBool::False;
|
||||
}
|
||||
}
|
||||
@@ -285,10 +283,10 @@ namespace
|
||||
else if (val == QLatin1String("-1")) {
|
||||
return TriStateBool::Undefined;
|
||||
}
|
||||
else if (val.toUpper() == QLatin1String("TRUE") || val == QLatin1String("1")) {
|
||||
else if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1"))) {
|
||||
return TriStateBool::True;
|
||||
}
|
||||
else if (val.toUpper() == QLatin1String("FALSE") || val == QLatin1String("0")) {
|
||||
else if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0"))) {
|
||||
return TriStateBool::False;
|
||||
}
|
||||
else {
|
||||
@@ -360,7 +358,7 @@ QStringList QBtCommandLineParameters::paramList() const
|
||||
// the user has specified. Here we place special strings that are
|
||||
// almost certainly not going to collide with a file path or URL
|
||||
// specified by the user, and placing them at the beginning of the
|
||||
// string listr so that they will be processed before the list of
|
||||
// string list so that they will be processed before the list of
|
||||
// torrent paths or URLs.
|
||||
|
||||
if (!savePath.isEmpty())
|
||||
@@ -404,9 +402,9 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
||||
const QString &arg = args[i];
|
||||
|
||||
if ((arg.startsWith("--") && !arg.endsWith(".torrent"))
|
||||
|| (arg.startsWith("-") && (arg.size() == 2))) {
|
||||
|| (arg.startsWith('-') && (arg.size() == 2))) {
|
||||
// Parse known parameters
|
||||
if ((arg == SHOW_HELP_OPTION)) {
|
||||
if (arg == SHOW_HELP_OPTION) {
|
||||
result.showHelp = true;
|
||||
}
|
||||
#ifndef Q_OS_WIN
|
||||
@@ -501,7 +499,7 @@ QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN
|
||||
|
||||
foreach (const QString &word, words.mid(1)) {
|
||||
if (lines.last().length() + word.length() + 1 < currentLineMaxLength) {
|
||||
lines.last().append(" " + word);
|
||||
lines.last().append(' ' + word);
|
||||
}
|
||||
else {
|
||||
lines.append(QString(initialIndentation, ' ') + word);
|
||||
@@ -509,7 +507,7 @@ QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
QString makeUsage(const QString &prgName)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 Eugene Shalygin <eugene.shalygin@gmail.com>
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -26,8 +26,6 @@
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#ifndef APP_OPTIONS_H
|
||||
@@ -62,11 +60,11 @@ struct QBtCommandLineParameters
|
||||
QStringList paramList() const;
|
||||
};
|
||||
|
||||
class CommandLineParameterError: public std::runtime_error
|
||||
class CommandLineParameterError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
CommandLineParameterError(const QString &messageForUser);
|
||||
const QString& messageForUser() const;
|
||||
const QString &messageForUser() const;
|
||||
|
||||
private:
|
||||
const QString m_messageForUser;
|
||||
|
||||
@@ -26,11 +26,13 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "filelogger.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include "filelogger.h"
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "base/utils/fs.h"
|
||||
|
||||
@@ -47,8 +49,8 @@ FileLogger::FileLogger(const QString &path, const bool backup, const int maxSize
|
||||
if (deleteOld)
|
||||
this->deleteOld(age, ageType);
|
||||
|
||||
const Logger* const logger = Logger::instance();
|
||||
foreach (const Log::Msg& msg, logger->getMessages())
|
||||
const Logger *const logger = Logger::instance();
|
||||
foreach (const Log::Msg &msg, logger->getMessages())
|
||||
addLogMessage(msg);
|
||||
|
||||
connect(logger, &Logger::newLogMessage, this, &FileLogger::addLogMessage);
|
||||
@@ -61,7 +63,7 @@ FileLogger::~FileLogger()
|
||||
delete m_logFile;
|
||||
}
|
||||
|
||||
void FileLogger::changePath(const QString& newPath)
|
||||
void FileLogger::changePath(const QString &newPath)
|
||||
{
|
||||
QString tmpPath = Utils::Fs::fromNativePath(newPath);
|
||||
QDir dir(tmpPath);
|
||||
|
||||
@@ -76,4 +76,3 @@ private:
|
||||
};
|
||||
|
||||
#endif // FILELOGGER_H
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -25,8 +25,6 @@
|
||||
* 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.
|
||||
*
|
||||
* Contact : chris@qbittorrent.org
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
@@ -63,14 +61,14 @@ Q_IMPORT_PLUGIN(QICOPlugin)
|
||||
#include "stacktrace.h"
|
||||
#else
|
||||
#include "stacktrace_win.h"
|
||||
#include "stacktrace_win_dlg.h"
|
||||
#include "stacktracedialog.h"
|
||||
#endif // Q_OS_UNIX
|
||||
#endif //STACKTRACE
|
||||
|
||||
#include "application.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/profile.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/preferences.h"
|
||||
#include "application.h"
|
||||
#include "cmdoptions.h"
|
||||
#include "upgrade.h"
|
||||
|
||||
@@ -95,7 +93,7 @@ const char *sysSigName[] = {
|
||||
};
|
||||
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
void reportToUser(const char* str);
|
||||
void reportToUser(const char *str);
|
||||
#endif
|
||||
|
||||
void displayVersion();
|
||||
@@ -104,10 +102,6 @@ void displayBadArgMessage(const QString &message);
|
||||
|
||||
#if !defined(DISABLE_GUI)
|
||||
void showSplashScreen();
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
void setupDpi();
|
||||
#endif // Q_OS_UNIX
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
// Main
|
||||
@@ -122,10 +116,6 @@ int main(int argc, char *argv[])
|
||||
macMigratePlists();
|
||||
#endif
|
||||
|
||||
#if !defined(DISABLE_GUI) && defined(Q_OS_UNIX)
|
||||
setupDpi();
|
||||
#endif
|
||||
|
||||
try {
|
||||
// Create Application
|
||||
QString appId = QLatin1String("qBittorrent-") + Utils::Misc::getUserIDString();
|
||||
@@ -236,7 +226,7 @@ int main(int argc, char *argv[])
|
||||
#ifdef DISABLE_GUI
|
||||
if (params.shouldDaemonize) {
|
||||
app.reset(); // Destroy current application
|
||||
if ((daemon(1, 0) == 0)) {
|
||||
if (daemon(1, 0) == 0) {
|
||||
app.reset(new Application(appId, argc, argv));
|
||||
if (app->isRunning()) {
|
||||
// Another instance had time to start.
|
||||
@@ -269,7 +259,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
void reportToUser(const char* str)
|
||||
void reportToUser(const char *str)
|
||||
{
|
||||
const size_t strLen = strlen(str);
|
||||
if (write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen)) {
|
||||
@@ -308,7 +298,7 @@ void sigAbnormalHandler(int signum)
|
||||
#endif
|
||||
|
||||
#if defined Q_OS_WIN
|
||||
StraceDlg dlg; // unsafe
|
||||
StacktraceDialog dlg; // unsafe
|
||||
dlg.setStacktraceString(QLatin1String(sigName), straceWin::getBacktrace());
|
||||
dlg.exec();
|
||||
#endif
|
||||
@@ -321,25 +311,17 @@ void sigAbnormalHandler(int signum)
|
||||
#if !defined(DISABLE_GUI)
|
||||
void showSplashScreen()
|
||||
{
|
||||
QPixmap splash_img(":/icons/skin/splash.png");
|
||||
QPainter painter(&splash_img);
|
||||
QPixmap splashImg(":/icons/skin/splash.png");
|
||||
QPainter painter(&splashImg);
|
||||
QString version = QBT_VERSION;
|
||||
painter.setPen(QPen(Qt::white));
|
||||
painter.setFont(QFont("Arial", 22, QFont::Black));
|
||||
painter.drawText(224 - painter.fontMetrics().width(version), 270, version);
|
||||
QSplashScreen *splash = new QSplashScreen(splash_img);
|
||||
QSplashScreen *splash = new QSplashScreen(splashImg);
|
||||
splash->show();
|
||||
QTimer::singleShot(1500, splash, &QObject::deleteLater);
|
||||
qApp->processEvents();
|
||||
}
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
void setupDpi()
|
||||
{
|
||||
if (qEnvironmentVariableIsEmpty("QT_AUTO_SCREEN_SCALE_FACTOR"))
|
||||
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1");
|
||||
}
|
||||
#endif // Q_OS_UNIX
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
void displayVersion()
|
||||
@@ -347,7 +329,7 @@ void displayVersion()
|
||||
printf("%s %s\n", qUtf8Printable(qApp->applicationName()), QBT_VERSION);
|
||||
}
|
||||
|
||||
void displayBadArgMessage(const QString& message)
|
||||
void displayBadArgMessage(const QString &message)
|
||||
{
|
||||
QString help = QObject::tr("Run application with -h option to read about command line parameters.");
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -366,7 +348,7 @@ void displayBadArgMessage(const QString& message)
|
||||
|
||||
bool userAgreesWithLegalNotice()
|
||||
{
|
||||
Preferences* const pref = Preferences::instance();
|
||||
Preferences *const pref = Preferences::instance();
|
||||
if (pref->getAcceptedLegal()) // Already accepted once
|
||||
return true;
|
||||
|
||||
@@ -378,7 +360,7 @@ bool userAgreesWithLegalNotice()
|
||||
printf("%s", qUtf8Printable(eula));
|
||||
|
||||
char ret = getchar(); // Read pressed key
|
||||
if (ret == 'y' || ret == 'Y') {
|
||||
if ((ret == 'y') || (ret == 'Y')) {
|
||||
// Save the answer
|
||||
pref->setAcceptedLegal(true);
|
||||
return true;
|
||||
@@ -388,16 +370,16 @@ bool userAgreesWithLegalNotice()
|
||||
msgBox.setText(QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.\n\nNo further notices will be issued."));
|
||||
msgBox.setWindowTitle(QObject::tr("Legal notice"));
|
||||
msgBox.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
|
||||
QAbstractButton *agree_button = msgBox.addButton(QObject::tr("I Agree"), QMessageBox::AcceptRole);
|
||||
QAbstractButton *agreeButton = msgBox.addButton(QObject::tr("I Agree"), QMessageBox::AcceptRole);
|
||||
msgBox.show(); // Need to be shown or to moveToCenter does not work
|
||||
msgBox.move(Utils::Misc::screenCenter(&msgBox));
|
||||
msgBox.exec();
|
||||
if (msgBox.clickedButton() == agree_button) {
|
||||
if (msgBox.clickedButton() == agreeButton) {
|
||||
// Save the answer
|
||||
pref->setAcceptedLegal(true);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -8,24 +8,24 @@ set(QBT_QTSINGLEAPPLICATION_SOURCES
|
||||
qtlocalpeer.cpp
|
||||
)
|
||||
|
||||
if (GUI)
|
||||
if (Qt5Widgets_FOUND)
|
||||
list(APPEND QBT_QTSINGLEAPPLICATION_HEADERS qtsingleapplication.h)
|
||||
list(APPEND QBT_QTSINGLEAPPLICATION_SOURCES qtsingleapplication.cpp)
|
||||
else (GUI)
|
||||
else (Qt5Widgets_FOUND)
|
||||
list(APPEND QBT_QTSINGLEAPPLICATION_HEADERS qtsinglecoreapplication.h)
|
||||
list(APPEND QBT_QTSINGLEAPPLICATION_SOURCES qtsinglecoreapplication.cpp)
|
||||
endif (GUI)
|
||||
endif (Qt5Widgets_FOUND)
|
||||
|
||||
add_library(qtsingleapplication STATIC ${QBT_QTSINGLEAPPLICATION_HEADERS} ${QBT_QTSINGLEAPPLICATION_SOURCES})
|
||||
target_include_directories(qtsingleapplication INTERFACE "${qtsingleapplication_SOURCE_DIR}")
|
||||
target_link_qt_components(qtsingleapplication Network)
|
||||
target_link_libraries(qtsingleapplication PRIVATE Qt5::Network)
|
||||
|
||||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
|
||||
target_compile_options(qtsingleapplication PRIVATE "-w") # disable warning for 3rdparty code
|
||||
endif()
|
||||
|
||||
if (GUI)
|
||||
target_link_qt_components(qtsingleapplication Widgets)
|
||||
endif (GUI)
|
||||
if (Qt5Widgets_FOUND)
|
||||
target_link_libraries(qtsingleapplication PRIVATE Qt5::Widgets)
|
||||
endif (Qt5Widgets_FOUND)
|
||||
|
||||
add_library(QtSingleApplication::QtSingleApplication ALIAS qtsingleapplication)
|
||||
|
||||
@@ -27,20 +27,21 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STACKTRACE_WIN_DLG_H
|
||||
#define STACKTRACE_WIN_DLG_H
|
||||
#ifndef STACKTRACEDIALOG_H
|
||||
#define STACKTRACEDIALOG_H
|
||||
|
||||
#include <QString>
|
||||
#include <QDialog>
|
||||
#include "base/utils/misc.h"
|
||||
#include "ui_stacktrace_win_dlg.h"
|
||||
|
||||
class StraceDlg : public QDialog, private Ui::errorDialog
|
||||
#include "base/utils/misc.h"
|
||||
#include "ui_stacktracedialog.h"
|
||||
|
||||
class StacktraceDialog : public QDialog, private Ui::StacktraceDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
StraceDlg(QWidget *parent = nullptr)
|
||||
StacktraceDialog(QWidget *parent = nullptr)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setupUi(this);
|
||||
@@ -83,4 +84,4 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif // STACKTRACEDIALOG_H
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>errorDialog</class>
|
||||
<widget class="QDialog" name="errorDialog">
|
||||
<class>StacktraceDialog</class>
|
||||
<widget class="QDialog" name="StacktraceDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
@@ -29,34 +29,34 @@
|
||||
#ifndef UPGRADE_H
|
||||
#define UPGRADE_H
|
||||
|
||||
#include <libtorrent/version.hpp>
|
||||
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||
#include <libtorrent/bdecode.hpp>
|
||||
#endif
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/entry.hpp>
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||
#include <libtorrent/bdecode.hpp>
|
||||
#else
|
||||
#include <libtorrent/lazy_entry.hpp>
|
||||
#endif
|
||||
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
#include <QMessageBox>
|
||||
#endif
|
||||
#include <QRegExp>
|
||||
#include <QString>
|
||||
#ifdef Q_OS_MAC
|
||||
#include <QSettings>
|
||||
#endif
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/profile.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "base/preferences.h"
|
||||
|
||||
bool userAcceptsUpgrade()
|
||||
{
|
||||
@@ -114,8 +114,9 @@ bool upgradeResumeFile(const QString &filepath, const QVariantHash &oldTorrent =
|
||||
bool v3_3 = false;
|
||||
int queuePosition = 0;
|
||||
QString outFilePath = filepath;
|
||||
QRegExp rx(QLatin1String("([A-Fa-f0-9]{40})\\.fastresume\\.(.+)$"));
|
||||
if (rx.indexIn(filepath) != -1) {
|
||||
static const QRegularExpression rx(QLatin1String("([A-Fa-f0-9]{40})\\.fastresume\\.(.+)$"));
|
||||
const QRegularExpressionMatch rxMatch = rx.match(filepath);
|
||||
if (rxMatch.hasMatch()) {
|
||||
// Old v3.3.x format had a number at the end indicating the queue position.
|
||||
// The naming scheme was '<infohash>.fastresume.<queueposition>'.
|
||||
// However, QSaveFile, which uses QTemporaryFile internally, might leave
|
||||
@@ -127,14 +128,14 @@ bool upgradeResumeFile(const QString &filepath, const QVariantHash &oldTorrent =
|
||||
// and is deleted, because it may be a corrupted/incomplete fastresume.
|
||||
// NOTE: When the upgrade code is removed, we must continue to perform
|
||||
// cleanup of non-commited QSaveFile/QTemporaryFile fastresumes
|
||||
queuePosition = rx.cap(2).toInt();
|
||||
if ((rx.cap(2).size() == 6) && (queuePosition <= 99999)) {
|
||||
queuePosition = rxMatch.captured(2).toInt();
|
||||
if ((rxMatch.captured(2).size() == 6) && (queuePosition <= 99999)) {
|
||||
Utils::Fs::forceRemove(filepath);
|
||||
return true;
|
||||
}
|
||||
|
||||
v3_3 = true;
|
||||
outFilePath.replace(QRegExp("\\.fastresume\\..+$"), ".fastresume");
|
||||
outFilePath.replace(QRegularExpression("\\.fastresume\\..+$"), ".fastresume");
|
||||
}
|
||||
else {
|
||||
queuePosition = fastOld.dict_find_int_value("qBt-queuePosition", 0);
|
||||
@@ -198,13 +199,15 @@ bool upgrade(bool ask = true)
|
||||
|
||||
QStringList backupFiles = backupFolderDir.entryList(
|
||||
QStringList(QLatin1String("*.fastresume")), QDir::Files, QDir::Unsorted);
|
||||
QRegExp rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
|
||||
const QRegularExpression rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
|
||||
foreach (QString backupFile, backupFiles) {
|
||||
if (rx.indexIn(backupFile) != -1) {
|
||||
if (upgradeResumeFile(backupFolderDir.absoluteFilePath(backupFile), oldResumeData[rx.cap(1)].toHash()))
|
||||
oldResumeData.remove(rx.cap(1));
|
||||
const QRegularExpressionMatch rxMatch = rx.match(backupFile);
|
||||
if (rxMatch.hasMatch()) {
|
||||
const QString hashStr = rxMatch.captured(1);
|
||||
if (upgradeResumeFile(backupFolderDir.absoluteFilePath(backupFile), oldResumeData[hashStr].toHash()))
|
||||
oldResumeData.remove(hashStr);
|
||||
else
|
||||
Logger::instance()->addMessage(QObject::tr("Couldn't migrate torrent with hash: %1").arg(rx.cap(1)), Log::WARNING);
|
||||
Logger::instance()->addMessage(QObject::tr("Couldn't migrate torrent with hash: %1").arg(hashStr), Log::WARNING);
|
||||
}
|
||||
else {
|
||||
Logger::instance()->addMessage(QObject::tr("Couldn't migrate torrent. Invalid fastresume file name: %1").arg(backupFile), Log::WARNING);
|
||||
@@ -296,6 +299,6 @@ void migrateRSS()
|
||||
qBTRSSLegacy->remove("old_items");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
#endif // UPGRADE_H
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
find_package(ZLIB 1.2.5.2 REQUIRED)
|
||||
|
||||
set(QBT_BASE_HEADERS
|
||||
add_library(qbt_base STATIC
|
||||
# headers
|
||||
bittorrent/addtorrentparams.h
|
||||
bittorrent/cachestatus.h
|
||||
bittorrent/infohash.h
|
||||
@@ -48,6 +49,7 @@ search/searchdownloadhandler.h
|
||||
search/searchhandler.h
|
||||
search/searchpluginmanager.h
|
||||
utils/bytearray.h
|
||||
utils/foreignapps.h
|
||||
utils/fs.h
|
||||
utils/gzip.h
|
||||
utils/misc.h
|
||||
@@ -72,9 +74,8 @@ torrentfilter.h
|
||||
tristatebool.h
|
||||
types.h
|
||||
unicodestrings.h
|
||||
)
|
||||
|
||||
set(QBT_BASE_SOURCES
|
||||
# sources
|
||||
bittorrent/infohash.cpp
|
||||
bittorrent/magneturi.cpp
|
||||
bittorrent/peerinfo.cpp
|
||||
@@ -117,6 +118,7 @@ search/searchdownloadhandler.cpp
|
||||
search/searchhandler.cpp
|
||||
search/searchpluginmanager.cpp
|
||||
utils/bytearray.cpp
|
||||
utils/foreignapps.cpp
|
||||
utils/fs.cpp
|
||||
utils/gzip.cpp
|
||||
utils/misc.cpp
|
||||
@@ -137,16 +139,20 @@ torrentfilter.cpp
|
||||
tristatebool.cpp
|
||||
)
|
||||
|
||||
add_library(qbt_base STATIC ${QBT_BASE_HEADERS} ${QBT_BASE_SOURCES})
|
||||
target_link_libraries(qbt_base PRIVATE ZLIB::ZLIB PUBLIC LibtorrentRasterbar::LibTorrent)
|
||||
target_link_qt_components(qbt_base PUBLIC Core Network Xml)
|
||||
target_link_libraries(qbt_base
|
||||
PRIVATE
|
||||
ZLIB::ZLIB
|
||||
PUBLIC
|
||||
LibtorrentRasterbar::torrent-rasterbar
|
||||
Qt5::Core Qt5::Network Qt5::Xml
|
||||
)
|
||||
|
||||
if (GUI)
|
||||
if (Qt5Widgets_FOUND)
|
||||
target_link_libraries(qbt_base PUBLIC Qt5::Gui Qt5::Widgets)
|
||||
endif (GUI)
|
||||
endif (Qt5Widgets_FOUND)
|
||||
|
||||
if (DBUS)
|
||||
target_link_qt_components(qbt_base PRIVATE DBus)
|
||||
if (Qt5DBus_FOUND)
|
||||
target_link_libraries(qbt_base PRIVATE Qt5::DBus)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
|
||||
@@ -64,6 +64,7 @@ HEADERS += \
|
||||
$$PWD/types.h \
|
||||
$$PWD/unicodestrings.h \
|
||||
$$PWD/utils/bytearray.h \
|
||||
$$PWD/utils/foreignapps.h \
|
||||
$$PWD/utils/fs.h \
|
||||
$$PWD/utils/gzip.h \
|
||||
$$PWD/utils/misc.h \
|
||||
@@ -127,6 +128,7 @@ SOURCES += \
|
||||
$$PWD/torrentfilter.cpp \
|
||||
$$PWD/tristatebool.cpp \
|
||||
$$PWD/utils/bytearray.cpp \
|
||||
$$PWD/utils/foreignapps.cpp \
|
||||
$$PWD/utils/fs.cpp \
|
||||
$$PWD/utils/gzip.cpp \
|
||||
$$PWD/utils/misc.cpp \
|
||||
|
||||
@@ -26,9 +26,10 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <QHash>
|
||||
#include "infohash.h"
|
||||
|
||||
#include <QHash>
|
||||
|
||||
using namespace BitTorrent;
|
||||
|
||||
InfoHash::InfoHash()
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
#ifndef BITTORRENT_INFOHASH_H
|
||||
#define BITTORRENT_INFOHASH_H
|
||||
|
||||
#include <QString>
|
||||
#include <libtorrent/sha1_hash.hpp>
|
||||
#include <QString>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
|
||||
@@ -26,16 +26,17 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QRegExp>
|
||||
#include <QStringList>
|
||||
#include "magneturi.h"
|
||||
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/error_code.hpp>
|
||||
#include <libtorrent/magnet_uri.hpp>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
|
||||
#include "base/utils/string.h"
|
||||
#include "magneturi.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -45,7 +46,7 @@ namespace
|
||||
rawBc = rawBc.mid(8); // skip bc://bt/
|
||||
rawBc = QByteArray::fromBase64(rawBc); // Decode base64
|
||||
// Format is now AA/url_encoded_filename/size_bytes/info_hash/ZZ
|
||||
QStringList parts = QString(rawBc).split("/");
|
||||
QStringList parts = QString(rawBc).split('/');
|
||||
if (parts.size() != 5) return QString();
|
||||
|
||||
QString filename = parts.at(1);
|
||||
@@ -69,8 +70,8 @@ MagnetUri::MagnetUri(const QString &source)
|
||||
qDebug("Creating magnet link from bc link");
|
||||
m_url = bcLinkToMagnet(source);
|
||||
}
|
||||
else if (((source.size() == 40) && !source.contains(QRegExp("[^0-9A-Fa-f]")))
|
||||
|| ((source.size() == 32) && !source.contains(QRegExp("[^2-7A-Za-z]")))) {
|
||||
else if (((source.size() == 40) && !source.contains(QRegularExpression("[^0-9A-Fa-f]")))
|
||||
|| ((source.size() == 32) && !source.contains(QRegularExpression("[^2-7A-Za-z]")))) {
|
||||
m_url = "magnet:?xt=urn:btih:" + source;
|
||||
}
|
||||
|
||||
|
||||
@@ -246,8 +246,8 @@ QString PeerInfo::connectionType() const
|
||||
|
||||
void PeerInfo::calcRelevance(const TorrentHandle *torrent)
|
||||
{
|
||||
const QBitArray &allPieces = torrent->pieces();
|
||||
const QBitArray &peerPieces = pieces();
|
||||
const QBitArray allPieces = torrent->pieces();
|
||||
const QBitArray peerPieces = pieces();
|
||||
|
||||
int localMissing = 0;
|
||||
int remoteHaves = 0;
|
||||
@@ -377,7 +377,7 @@ void PeerInfo::determineFlags()
|
||||
|
||||
// L = Peer is local
|
||||
if (fromLSD()) {
|
||||
m_flags += "L";
|
||||
m_flags += 'L';
|
||||
flagsDescriptionList += "L = "
|
||||
+ tr("peer from LSD");
|
||||
}
|
||||
|
||||
@@ -407,24 +407,24 @@ int FilterParserThread::parseP2PFilterFile()
|
||||
int FilterParserThread::getlineInStream(QDataStream &stream, std::string &name, char delim)
|
||||
{
|
||||
char c;
|
||||
int total_read = 0;
|
||||
int totalRead = 0;
|
||||
int read;
|
||||
do {
|
||||
read = stream.readRawData(&c, 1);
|
||||
total_read += read;
|
||||
totalRead += read;
|
||||
if (read > 0) {
|
||||
if (c != delim) {
|
||||
name += c;
|
||||
}
|
||||
else {
|
||||
// Delim found
|
||||
return total_read;
|
||||
return totalRead;
|
||||
}
|
||||
}
|
||||
}
|
||||
while(read > 0);
|
||||
while (read > 0);
|
||||
|
||||
return total_read;
|
||||
return totalRead;
|
||||
}
|
||||
|
||||
// Parser for PeerGuardian ip filter in p2p format
|
||||
@@ -455,7 +455,7 @@ int FilterParserThread::parseP2BFilterFile()
|
||||
unsigned int start, end;
|
||||
|
||||
std::string name;
|
||||
while(getlineInStream(stream, name, '\0') && !m_abort) {
|
||||
while (getlineInStream(stream, name, '\0') && !m_abort) {
|
||||
if (!stream.readRawData(reinterpret_cast<char*>(&start), sizeof(start))
|
||||
|| !stream.readRawData(reinterpret_cast<char*>(&end), sizeof(end))) {
|
||||
LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
|
||||
|
||||
@@ -26,12 +26,13 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "resumedatasavingmanager.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QSaveFile>
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "resumedatasavingmanager.h"
|
||||
|
||||
ResumeDataSavingManager::ResumeDataSavingManager(const QString &resumeFolderPath)
|
||||
: m_resumeDataDir(resumeFolderPath)
|
||||
|
||||
@@ -28,7 +28,6 @@ private:
|
||||
void save() const;
|
||||
void load();
|
||||
|
||||
private:
|
||||
BitTorrent::Session *m_session;
|
||||
// Will overflow at 15.9 EiB
|
||||
quint64 m_alltimeUL;
|
||||
|
||||
@@ -35,14 +35,13 @@
|
||||
#include <string>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkAddressEntry>
|
||||
#include <QNetworkInterface>
|
||||
#include <QProcess>
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
@@ -112,7 +111,7 @@ using namespace BitTorrent;
|
||||
namespace
|
||||
{
|
||||
bool readFile(const QString &path, QByteArray &buf);
|
||||
bool loadTorrentResumeData(const QByteArray &data, AddTorrentData &torrentData, int &prio, MagnetUri &magnetUri);
|
||||
bool loadTorrentResumeData(const QByteArray &data, CreateTorrentParams &torrentParams, int &prio, MagnetUri &magnetUri);
|
||||
|
||||
void torrentQueuePositionUp(const libt::torrent_handle &handle);
|
||||
void torrentQueuePositionDown(const libt::torrent_handle &handle);
|
||||
@@ -275,6 +274,7 @@ Session::Session(QObject *parent)
|
||||
, m_IPFilterFile(BITTORRENT_SESSION_KEY("IPFilter"))
|
||||
, m_announceToAllTrackers(BITTORRENT_SESSION_KEY("AnnounceToAllTrackers"), false)
|
||||
, m_announceToAllTiers(BITTORRENT_SESSION_KEY("AnnounceToAllTiers"), true)
|
||||
, m_asyncIOThreads(BITTORRENT_SESSION_KEY("AsyncIOThreadsCount"), 4)
|
||||
, m_diskCacheSize(BITTORRENT_SESSION_KEY("DiskCacheSize"), 64)
|
||||
, m_diskCacheTTL(BITTORRENT_SESSION_KEY("DiskCacheTTL"), 60)
|
||||
, m_useOSCache(BITTORRENT_SESSION_KEY("UseOSCache"), true)
|
||||
@@ -331,7 +331,7 @@ Session::Session(QObject *parent)
|
||||
, m_altGlobalUploadSpeedLimit(BITTORRENT_SESSION_KEY("AlternativeGlobalUPSpeedLimit"), 10, lowerLimited(0))
|
||||
, m_isAltGlobalSpeedLimitEnabled(BITTORRENT_SESSION_KEY("UseAlternativeGlobalSpeedLimit"), false)
|
||||
, m_isBandwidthSchedulerEnabled(BITTORRENT_SESSION_KEY("BandwidthSchedulerEnabled"), false)
|
||||
, m_saveResumeDataInterval(BITTORRENT_SESSION_KEY("SaveResumeDataInterval"), 3)
|
||||
, m_saveResumeDataInterval(BITTORRENT_SESSION_KEY("SaveResumeDataInterval"), 60)
|
||||
, m_port(BITTORRENT_SESSION_KEY("Port"), 8999)
|
||||
, m_useRandomPort(BITTORRENT_SESSION_KEY("UseRandomPort"), false)
|
||||
, m_networkInterface(BITTORRENT_SESSION_KEY("Interface"))
|
||||
@@ -392,7 +392,11 @@ Session::Session(QObject *parent)
|
||||
| libt::alert::tracker_notification
|
||||
| libt::alert::status_notification
|
||||
| libt::alert::ip_block_notification
|
||||
#if LIBTORRENT_VERSION_NUM < 10110
|
||||
| libt::alert::progress_notification
|
||||
#else
|
||||
| libt::alert::file_progress_notification
|
||||
#endif
|
||||
| libt::alert::stats_notification;
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
@@ -511,11 +515,6 @@ Session::Session(QObject *parent)
|
||||
connect(m_refreshTimer, &QTimer::timeout, this, &Session::refresh);
|
||||
m_refreshTimer->start();
|
||||
|
||||
// Regular saving of fastresume data
|
||||
m_resumeDataTimer = new QTimer(this);
|
||||
m_resumeDataTimer->setInterval(saveResumeDataInterval() * 60 * 1000);
|
||||
connect(m_resumeDataTimer, &QTimer::timeout, this, [this]() { generateResumeData(); });
|
||||
|
||||
m_statistics = new Statistics(this);
|
||||
|
||||
updateSeedingLimitTimer();
|
||||
@@ -537,7 +536,15 @@ Session::Session(QObject *parent)
|
||||
m_resumeDataSavingManager->moveToThread(m_ioThread);
|
||||
connect(m_ioThread, &QThread::finished, m_resumeDataSavingManager, &QObject::deleteLater);
|
||||
m_ioThread->start();
|
||||
m_resumeDataTimer->start();
|
||||
|
||||
// Regular saving of fastresume data
|
||||
m_resumeDataTimer = new QTimer(this);
|
||||
connect(m_resumeDataTimer, &QTimer::timeout, this, [this]() { generateResumeData(); });
|
||||
const uint saveInterval = saveResumeDataInterval();
|
||||
if (saveInterval > 0) {
|
||||
m_resumeDataTimer->setInterval(saveInterval * 60 * 1000);
|
||||
m_resumeDataTimer->start();
|
||||
}
|
||||
|
||||
// initialize PortForwarder instance
|
||||
Net::PortForwarder::initInstance(m_nativeSession);
|
||||
@@ -685,15 +692,15 @@ QString Session::torrentTempPath(const TorrentInfo &torrentInfo) const
|
||||
if ((torrentInfo.filesCount() > 1) && !torrentInfo.hasRootFolder())
|
||||
return tempPath()
|
||||
+ QString::fromStdString(torrentInfo.nativeInfo()->orig_files().name())
|
||||
+ "/";
|
||||
+ '/';
|
||||
|
||||
return tempPath();
|
||||
}
|
||||
|
||||
bool Session::isValidCategoryName(const QString &name)
|
||||
{
|
||||
QRegExp re(R"(^([^\\\/]|[^\\\/]([^\\\/]|\/(?=[^\/]))*[^\\\/])$)");
|
||||
if (!name.isEmpty() && (re.indexIn(name) != 0)) {
|
||||
static const QRegularExpression re(R"(^([^\\\/]|[^\\\/]([^\\\/]|\/(?=[^\/]))*[^\\\/])$)");
|
||||
if (!name.isEmpty() && (name.indexOf(re) != 0)) {
|
||||
qDebug() << "Incorrect category name:" << name;
|
||||
return false;
|
||||
}
|
||||
@@ -790,7 +797,7 @@ bool Session::removeCategory(const QString &name)
|
||||
bool result = false;
|
||||
if (isSubcategoriesEnabled()) {
|
||||
// remove subcategories
|
||||
const QString test = name + "/";
|
||||
const QString test = name + '/';
|
||||
Dict::removeIf(m_categories, [this, &test, &result](const QString &category, const QString &)
|
||||
{
|
||||
if (category.startsWith(test)) {
|
||||
@@ -1308,6 +1315,8 @@ void Session::configure(libtorrent::settings_pack &settingsPack)
|
||||
settingsPack.set_bool(libt::settings_pack::announce_to_all_trackers, announceToAllTrackers());
|
||||
settingsPack.set_bool(libt::settings_pack::announce_to_all_tiers, announceToAllTiers());
|
||||
|
||||
settingsPack.set_int(libt::settings_pack::aio_threads, asyncIOThreads());
|
||||
|
||||
const int cacheSize = (diskCacheSize() > -1) ? (diskCacheSize() * 64) : -1;
|
||||
settingsPack.set_int(libt::settings_pack::cache_size, cacheSize);
|
||||
settingsPack.set_int(libt::settings_pack::cache_expiry, diskCacheTTL());
|
||||
@@ -1349,11 +1358,7 @@ void Session::configure(libtorrent::settings_pack &settingsPack)
|
||||
settingsPack.set_int(libt::settings_pack::active_tracker_limit, -1);
|
||||
settingsPack.set_int(libt::settings_pack::active_dht_limit, -1);
|
||||
settingsPack.set_int(libt::settings_pack::active_lsd_limit, -1);
|
||||
// 1 active torrent force 2 connections. If you have more active torrents * 2 than connection limit,
|
||||
// connection limit will get extended. Multiply max connections or active torrents by 10 for queue.
|
||||
// Ignore -1 values because we don't want to set a max int message queue
|
||||
settingsPack.set_int(libt::settings_pack::alert_queue_size, std::max(1000,
|
||||
10 * std::max(maxActiveTorrents() * 2, maxConnections())));
|
||||
settingsPack.set_int(libt::settings_pack::alert_queue_size, std::numeric_limits<int>::max() / 2);
|
||||
|
||||
// Outgoing ports
|
||||
settingsPack.set_int(libt::settings_pack::outgoing_port, outgoingPortsMin());
|
||||
@@ -1628,11 +1633,7 @@ void Session::configure(libtorrent::session_settings &sessionSettings)
|
||||
sessionSettings.active_tracker_limit = -1;
|
||||
sessionSettings.active_dht_limit = -1;
|
||||
sessionSettings.active_lsd_limit = -1;
|
||||
// 1 active torrent force 2 connections. If you have more active torrents * 2 than connection limit,
|
||||
// connection limit will get extended. Multiply max connections or active torrents by 10 for queue.
|
||||
// Ignore -1 values because we don't want to set a max int message queue
|
||||
sessionSettings.alert_queue_size = std::max(1000,
|
||||
10 * std::max(maxActiveTorrents() * 2, maxConnections()));
|
||||
sessionSettings.alert_queue_size = std::numeric_limits<int>::max() / 2;
|
||||
|
||||
// Outgoing ports
|
||||
sessionSettings.outgoing_ports = std::make_pair(outgoingPortsMin(), outgoingPortsMax());
|
||||
@@ -1844,11 +1845,10 @@ void Session::handleRedirectedToMagnet(const QString &url, const QString &magnet
|
||||
}
|
||||
|
||||
// Add to BitTorrent session the downloaded torrent file
|
||||
void Session::handleDownloadFinished(const QString &url, const QString &filePath)
|
||||
void Session::handleDownloadFinished(const QString &url, const QByteArray &data)
|
||||
{
|
||||
emit downloadFromUrlFinished(url);
|
||||
addTorrent_impl(m_downloadedTorrents.take(url), MagnetUri(), TorrentInfo::loadFromFile(filePath));
|
||||
Utils::Fs::forceRemove(filePath); // remove temporary file
|
||||
addTorrent_impl(m_downloadedTorrents.take(url), MagnetUri(), TorrentInfo::load(data));
|
||||
}
|
||||
|
||||
// Return the torrent handle, given its hash
|
||||
@@ -1859,20 +1859,26 @@ TorrentHandle *Session::findTorrent(const InfoHash &hash) const
|
||||
|
||||
bool Session::hasActiveTorrents() const
|
||||
{
|
||||
foreach (TorrentHandle *const torrent, m_torrents)
|
||||
if (TorrentFilter::ActiveTorrent.match(torrent))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
return std::any_of(m_torrents.begin(), m_torrents.end(), [](TorrentHandle *torrent)
|
||||
{
|
||||
return TorrentFilter::ActiveTorrent.match(torrent);
|
||||
});
|
||||
}
|
||||
|
||||
bool Session::hasUnfinishedTorrents() const
|
||||
{
|
||||
foreach (TorrentHandle *const torrent, m_torrents)
|
||||
if (!torrent->isSeed() && !torrent->isPaused())
|
||||
return true;
|
||||
return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentHandle *torrent)
|
||||
{
|
||||
return (!torrent->isSeed() && !torrent->isPaused());
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
bool Session::hasRunningSeed() const
|
||||
{
|
||||
return std::any_of(m_torrents.begin(), m_torrents.end(), [](const TorrentHandle *torrent)
|
||||
{
|
||||
return (torrent->isSeed() && !torrent->isPaused());
|
||||
});
|
||||
}
|
||||
|
||||
void Session::banIP(const QString &ip)
|
||||
@@ -1988,6 +1994,8 @@ void Session::increaseTorrentsPriority(const QStringList &hashes)
|
||||
torrentQueuePositionUp(torrent->nativeHandle());
|
||||
torrentQueue.pop();
|
||||
}
|
||||
|
||||
handleTorrentsPrioritiesChanged();
|
||||
}
|
||||
|
||||
void Session::decreaseTorrentsPriority(const QStringList &hashes)
|
||||
@@ -2012,6 +2020,8 @@ void Session::decreaseTorrentsPriority(const QStringList &hashes)
|
||||
|
||||
for (auto i = m_loadedMetadata.cbegin(); i != m_loadedMetadata.cend(); ++i)
|
||||
torrentQueuePositionBottom(m_nativeSession->find_torrent(i.key()));
|
||||
|
||||
handleTorrentsPrioritiesChanged();
|
||||
}
|
||||
|
||||
void Session::topTorrentsPriority(const QStringList &hashes)
|
||||
@@ -2033,6 +2043,8 @@ void Session::topTorrentsPriority(const QStringList &hashes)
|
||||
torrentQueuePositionTop(torrent->nativeHandle());
|
||||
torrentQueue.pop();
|
||||
}
|
||||
|
||||
handleTorrentsPrioritiesChanged();
|
||||
}
|
||||
|
||||
void Session::bottomTorrentsPriority(const QStringList &hashes)
|
||||
@@ -2057,6 +2069,8 @@ void Session::bottomTorrentsPriority(const QStringList &hashes)
|
||||
|
||||
for (auto i = m_loadedMetadata.cbegin(); i != m_loadedMetadata.cend(); ++i)
|
||||
torrentQueuePositionBottom(m_nativeSession->find_torrent(i.key()));
|
||||
|
||||
handleTorrentsPrioritiesChanged();
|
||||
}
|
||||
|
||||
QHash<InfoHash, TorrentHandle *> Session::torrents() const
|
||||
@@ -2073,25 +2087,26 @@ TorrentStatusReport Session::torrentStatusReport() const
|
||||
bool Session::addTorrent(QString source, const AddTorrentParams ¶ms)
|
||||
{
|
||||
MagnetUri magnetUri(source);
|
||||
if (magnetUri.isValid()) {
|
||||
if (magnetUri.isValid())
|
||||
return addTorrent_impl(params, magnetUri);
|
||||
}
|
||||
else if (Utils::Misc::isUrl(source)) {
|
||||
Logger::instance()->addMessage(tr("Downloading '%1', please wait...", "e.g: Downloading 'xxx.torrent', please wait...").arg(source));
|
||||
|
||||
if (Utils::Misc::isUrl(source)) {
|
||||
LogMsg(tr("Downloading '%1', please wait...", "e.g: Downloading 'xxx.torrent', please wait...").arg(source));
|
||||
// Launch downloader
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(source, true, 10485760 /* 10MB */, true);
|
||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QString &)>(&Net::DownloadHandler::downloadFinished)
|
||||
Net::DownloadHandler *handler =
|
||||
Net::DownloadManager::instance()->download(Net::DownloadRequest(source).limit(10485760 /* 10MB */).handleRedirectToMagnet(true));
|
||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
|
||||
, this, &Session::handleDownloadFinished);
|
||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &Session::handleDownloadFailed);
|
||||
connect(handler, &Net::DownloadHandler::redirectedToMagnet, this, &Session::handleRedirectedToMagnet);
|
||||
m_downloadedTorrents[handler->url()] = params;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
TorrentFileGuard guard(source);
|
||||
if (addTorrent_impl(params, MagnetUri(), TorrentInfo::loadFromFile(source))) {
|
||||
guard.markAsAddedToSession();
|
||||
return true;
|
||||
}
|
||||
|
||||
TorrentFileGuard guard(source);
|
||||
if (addTorrent_impl(params, MagnetUri(), TorrentInfo::loadFromFile(source))) {
|
||||
guard.markAsAddedToSession();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -2105,29 +2120,24 @@ bool Session::addTorrent(const TorrentInfo &torrentInfo, const AddTorrentParams
|
||||
}
|
||||
|
||||
// Add a torrent to the BitTorrent session
|
||||
bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri,
|
||||
bool Session::addTorrent_impl(CreateTorrentParams params, const MagnetUri &magnetUri,
|
||||
TorrentInfo torrentInfo, const QByteArray &fastresumeData)
|
||||
{
|
||||
addData.savePath = normalizeSavePath(addData.savePath, "");
|
||||
params.savePath = normalizeSavePath(params.savePath, "");
|
||||
|
||||
if (!addData.category.isEmpty()) {
|
||||
if (!m_categories.contains(addData.category) && !addCategory(addData.category)) {
|
||||
qWarning() << "Couldn't create category" << addData.category;
|
||||
addData.category = "";
|
||||
if (!params.category.isEmpty()) {
|
||||
if (!m_categories.contains(params.category) && !addCategory(params.category)) {
|
||||
qWarning() << "Couldn't create category" << params.category;
|
||||
params.category = "";
|
||||
}
|
||||
}
|
||||
|
||||
// If empty then Automatic mode, otherwise Manual mode
|
||||
QString savePath = params.savePath.isEmpty() ? categorySavePath(params.category) : params.savePath;
|
||||
libt::add_torrent_params p;
|
||||
InfoHash hash;
|
||||
std::vector<boost::uint8_t> filePriorities;
|
||||
const bool fromMagnetUri = magnetUri.isValid();
|
||||
|
||||
QString savePath;
|
||||
if (addData.savePath.isEmpty()) // using Automatic mode
|
||||
savePath = categorySavePath(addData.category);
|
||||
else // using Manual mode
|
||||
savePath = addData.savePath;
|
||||
|
||||
bool fromMagnetUri = magnetUri.isValid();
|
||||
if (fromMagnetUri) {
|
||||
hash = magnetUri.hash();
|
||||
|
||||
@@ -2146,7 +2156,7 @@ bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri
|
||||
adjustLimits();
|
||||
|
||||
// use common 2nd step of torrent addition
|
||||
m_addingTorrents.insert(hash, addData);
|
||||
m_addingTorrents.insert(hash, params);
|
||||
createTorrentHandle(handle);
|
||||
return true;
|
||||
}
|
||||
@@ -2154,23 +2164,23 @@ bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri
|
||||
p = magnetUri.addTorrentParams();
|
||||
}
|
||||
else if (torrentInfo.isValid()) {
|
||||
if (!addData.resumed) {
|
||||
if (!addData.hasRootFolder)
|
||||
if (!params.restored) {
|
||||
if (!params.hasRootFolder)
|
||||
torrentInfo.stripRootFolder();
|
||||
|
||||
// Metadata
|
||||
if (!addData.hasSeedStatus)
|
||||
if (!params.hasSeedStatus)
|
||||
findIncompleteFiles(torrentInfo, savePath);
|
||||
|
||||
// if torrent name wasn't explicitly set we handle the case of
|
||||
// initial renaming of torrent content and rename torrent accordingly
|
||||
if (addData.name.isEmpty()) {
|
||||
if (params.name.isEmpty()) {
|
||||
QString contentName = torrentInfo.rootFolder();
|
||||
if (contentName.isEmpty() && (torrentInfo.filesCount() == 1))
|
||||
contentName = torrentInfo.fileName(0);
|
||||
|
||||
if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
|
||||
addData.name = contentName;
|
||||
params.name = contentName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2184,23 +2194,12 @@ bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri
|
||||
return false;
|
||||
}
|
||||
|
||||
if (addData.resumed && !fromMagnetUri) {
|
||||
// Set torrent fast resume data
|
||||
p.resume_data = {fastresumeData.constData(), fastresumeData.constData() + fastresumeData.size()};
|
||||
p.flags |= libt::add_torrent_params::flag_use_resume_save_path;
|
||||
}
|
||||
else {
|
||||
foreach (int prio, addData.filePriorities)
|
||||
filePriorities.push_back(prio);
|
||||
p.file_priorities = filePriorities;
|
||||
}
|
||||
|
||||
// We should not add torrent if it already
|
||||
// processed or adding to session
|
||||
if (m_addingTorrents.contains(hash) || m_loadedMetadata.contains(hash)) return false;
|
||||
|
||||
if (m_torrents.contains(hash)) {
|
||||
TorrentHandle *const torrent = m_torrents.value(hash);
|
||||
TorrentHandle *const torrent = m_torrents.value(hash);
|
||||
if (torrent) {
|
||||
if (torrent->isPrivate() || (!fromMagnetUri && torrentInfo.isPrivate()))
|
||||
return false;
|
||||
torrent->addTrackers(fromMagnetUri ? magnetUri.trackers() : torrentInfo.trackers());
|
||||
@@ -2223,19 +2222,36 @@ bool Session::addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri
|
||||
|
||||
// Seeding mode
|
||||
// Skip checking and directly start seeding (new in libtorrent v0.15)
|
||||
if (addData.skipChecking)
|
||||
if (params.skipChecking)
|
||||
p.flags |= libt::add_torrent_params::flag_seed_mode;
|
||||
else
|
||||
p.flags &= ~libt::add_torrent_params::flag_seed_mode;
|
||||
|
||||
if (!fromMagnetUri) {
|
||||
if (params.restored) {
|
||||
// Set torrent fast resume data
|
||||
p.resume_data = {fastresumeData.constData(), fastresumeData.constData() + fastresumeData.size()};
|
||||
p.flags |= libt::add_torrent_params::flag_use_resume_save_path;
|
||||
}
|
||||
else {
|
||||
p.file_priorities = {params.filePriorities.begin(), params.filePriorities.end()};
|
||||
}
|
||||
}
|
||||
|
||||
if (params.restored && !params.paused) {
|
||||
// Make sure the torrent will restored in "paused" state
|
||||
// Then we will start it if needed
|
||||
p.flags |= libt::add_torrent_params::flag_stop_when_ready;
|
||||
}
|
||||
|
||||
// Limits
|
||||
p.max_connections = maxConnectionsPerTorrent();
|
||||
p.max_uploads = maxUploadsPerTorrent();
|
||||
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
||||
p.upload_limit = addData.uploadLimit;
|
||||
p.download_limit = addData.downloadLimit;
|
||||
p.upload_limit = params.uploadLimit;
|
||||
p.download_limit = params.downloadLimit;
|
||||
|
||||
m_addingTorrents.insert(hash, addData);
|
||||
m_addingTorrents.insert(hash, params);
|
||||
// Adding torrent to BitTorrent session
|
||||
m_nativeSession->async_add_torrent(p);
|
||||
return true;
|
||||
@@ -2302,7 +2318,7 @@ bool Session::loadMetadata(const MagnetUri &magnetUri)
|
||||
p.max_connections = maxConnectionsPerTorrent();
|
||||
p.max_uploads = maxUploadsPerTorrent();
|
||||
|
||||
QString savePath = QString("%1/%2").arg(QDir::tempPath(), hash);
|
||||
const QString savePath = Utils::Fs::tempPath() + static_cast<QString>(hash);
|
||||
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
||||
|
||||
// Forced start
|
||||
@@ -2352,12 +2368,11 @@ void Session::generateResumeData(bool final)
|
||||
{
|
||||
foreach (TorrentHandle *const torrent, m_torrents) {
|
||||
if (!torrent->isValid()) continue;
|
||||
if (torrent->hasMissingFiles()) continue;
|
||||
if (torrent->isChecking() || torrent->hasError()) continue;
|
||||
if (torrent->isChecking() || torrent->isPaused()) continue;
|
||||
if (!final && !torrent->needSaveResumeData()) continue;
|
||||
if (torrent->hasMissingFiles() || torrent->hasError()) continue;
|
||||
|
||||
saveTorrentResumeData(torrent, final);
|
||||
qDebug("Saving fastresume data for %s", qUtf8Printable(torrent->name()));
|
||||
saveTorrentResumeData(torrent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2645,7 +2660,7 @@ int Session::downloadSpeedLimit() const
|
||||
: globalDownloadSpeedLimit();
|
||||
}
|
||||
|
||||
void Session::setDownloadSpeedLimit(int limit)
|
||||
void Session::setDownloadSpeedLimit(const int limit)
|
||||
{
|
||||
if (isAltGlobalSpeedLimitEnabled())
|
||||
setAltGlobalDownloadSpeedLimit(limit);
|
||||
@@ -2660,7 +2675,7 @@ int Session::uploadSpeedLimit() const
|
||||
: globalUploadSpeedLimit();
|
||||
}
|
||||
|
||||
void Session::setUploadSpeedLimit(int limit)
|
||||
void Session::setUploadSpeedLimit(const int limit)
|
||||
{
|
||||
if (isAltGlobalSpeedLimitEnabled())
|
||||
setAltGlobalUploadSpeedLimit(limit);
|
||||
@@ -2673,7 +2688,7 @@ bool Session::isAltGlobalSpeedLimitEnabled() const
|
||||
return m_isAltGlobalSpeedLimitEnabled;
|
||||
}
|
||||
|
||||
void Session::setAltGlobalSpeedLimitEnabled(bool enabled)
|
||||
void Session::setAltGlobalSpeedLimitEnabled(const bool enabled)
|
||||
{
|
||||
if (enabled == isAltGlobalSpeedLimitEnabled()) return;
|
||||
|
||||
@@ -2705,11 +2720,19 @@ uint Session::saveResumeDataInterval() const
|
||||
return m_saveResumeDataInterval;
|
||||
}
|
||||
|
||||
void Session::setSaveResumeDataInterval(uint value)
|
||||
void Session::setSaveResumeDataInterval(const uint value)
|
||||
{
|
||||
if (value != saveResumeDataInterval()) {
|
||||
m_saveResumeDataInterval = value;
|
||||
if (value == m_saveResumeDataInterval)
|
||||
return;
|
||||
|
||||
m_saveResumeDataInterval = value;
|
||||
|
||||
if (value > 0) {
|
||||
m_resumeDataTimer->setInterval(value * 60 * 1000);
|
||||
m_resumeDataTimer->start();
|
||||
}
|
||||
else {
|
||||
m_resumeDataTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3018,6 +3041,20 @@ void Session::setAnnounceToAllTiers(bool val)
|
||||
}
|
||||
}
|
||||
|
||||
int Session::asyncIOThreads() const
|
||||
{
|
||||
return qBound(1, m_asyncIOThreads.value(), 1024);
|
||||
}
|
||||
|
||||
void Session::setAsyncIOThreads(const int num)
|
||||
{
|
||||
if (num == m_asyncIOThreads)
|
||||
return;
|
||||
|
||||
m_asyncIOThreads = num;
|
||||
configureDeferred();
|
||||
}
|
||||
|
||||
int Session::diskCacheSize() const
|
||||
{
|
||||
int size = m_diskCacheSize;
|
||||
@@ -3480,7 +3517,7 @@ void Session::setMaxRatioAction(MaxRatioAction act)
|
||||
}
|
||||
|
||||
// If this functions returns true, we cannot add torrent to session,
|
||||
// but it is still possible to merge trackers in some case
|
||||
// but it is still possible to merge trackers in some cases
|
||||
bool Session::isKnownTorrent(const InfoHash &hash) const
|
||||
{
|
||||
return (m_torrents.contains(hash)
|
||||
@@ -3502,45 +3539,70 @@ void Session::updateSeedingLimitTimer()
|
||||
|
||||
void Session::handleTorrentShareLimitChanged(TorrentHandle *const torrent)
|
||||
{
|
||||
Q_UNUSED(torrent);
|
||||
saveTorrentResumeData(torrent);
|
||||
updateSeedingLimitTimer();
|
||||
}
|
||||
|
||||
void Session::saveTorrentResumeData(TorrentHandle *const torrent, bool finalSave)
|
||||
void Session::handleTorrentsPrioritiesChanged()
|
||||
{
|
||||
torrent->saveResumeData(finalSave);
|
||||
// Save fastresume for the torrents that changed queue position
|
||||
for (TorrentHandle *const torrent : torrents()) {
|
||||
if (!torrent->isSeed()) {
|
||||
// cached vs actual queue position, qBt starts queue at 1
|
||||
if (torrent->queuePosition() != (torrent->nativeHandle().queue_position() + 1))
|
||||
saveTorrentResumeData(torrent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Session::saveTorrentResumeData(TorrentHandle *const torrent)
|
||||
{
|
||||
qDebug("Saving fastresume data for %s", qUtf8Printable(torrent->name()));
|
||||
torrent->saveResumeData();
|
||||
++m_numResumeData;
|
||||
}
|
||||
|
||||
void Session::handleTorrentNameChanged(TorrentHandle *const torrent)
|
||||
{
|
||||
saveTorrentResumeData(torrent);
|
||||
}
|
||||
|
||||
void Session::handleTorrentSavePathChanged(TorrentHandle *const torrent)
|
||||
{
|
||||
saveTorrentResumeData(torrent);
|
||||
emit torrentSavePathChanged(torrent);
|
||||
}
|
||||
|
||||
void Session::handleTorrentCategoryChanged(TorrentHandle *const torrent, const QString &oldCategory)
|
||||
{
|
||||
saveTorrentResumeData(torrent);
|
||||
emit torrentCategoryChanged(torrent, oldCategory);
|
||||
}
|
||||
|
||||
void Session::handleTorrentTagAdded(TorrentHandle *const torrent, const QString &tag)
|
||||
{
|
||||
saveTorrentResumeData(torrent);
|
||||
emit torrentTagAdded(torrent, tag);
|
||||
}
|
||||
|
||||
void Session::handleTorrentTagRemoved(TorrentHandle *const torrent, const QString &tag)
|
||||
{
|
||||
saveTorrentResumeData(torrent);
|
||||
emit torrentTagRemoved(torrent, tag);
|
||||
}
|
||||
|
||||
void Session::handleTorrentSavingModeChanged(TorrentHandle *const torrent)
|
||||
{
|
||||
saveTorrentResumeData(torrent);
|
||||
emit torrentSavingModeChanged(torrent);
|
||||
}
|
||||
|
||||
void Session::handleTorrentTrackersAdded(TorrentHandle *const torrent, const QList<TrackerEntry> &newTrackers)
|
||||
{
|
||||
foreach (const TrackerEntry &newTracker, newTrackers)
|
||||
Logger::instance()->addMessage(tr("Tracker '%1' was added to torrent '%2'").arg(newTracker.url(), torrent->name()));
|
||||
saveTorrentResumeData(torrent);
|
||||
|
||||
for (const TrackerEntry &newTracker : newTrackers)
|
||||
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);
|
||||
@@ -3549,8 +3611,10 @@ void Session::handleTorrentTrackersAdded(TorrentHandle *const torrent, const QLi
|
||||
|
||||
void Session::handleTorrentTrackersRemoved(TorrentHandle *const torrent, const QList<TrackerEntry> &deletedTrackers)
|
||||
{
|
||||
foreach (const TrackerEntry &deletedTracker, deletedTrackers)
|
||||
Logger::instance()->addMessage(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url(), torrent->name()));
|
||||
saveTorrentResumeData(torrent);
|
||||
|
||||
for (const TrackerEntry &deletedTracker : deletedTrackers)
|
||||
LogMsg(tr("Tracker '%1' was deleted from torrent '%2'").arg(deletedTracker.url(), torrent->name()));
|
||||
emit trackersRemoved(torrent, deletedTrackers);
|
||||
if (torrent->trackers().size() == 0)
|
||||
emit trackerlessStateChanged(torrent, true);
|
||||
@@ -3559,19 +3623,22 @@ void Session::handleTorrentTrackersRemoved(TorrentHandle *const torrent, const Q
|
||||
|
||||
void Session::handleTorrentTrackersChanged(TorrentHandle *const torrent)
|
||||
{
|
||||
saveTorrentResumeData(torrent);
|
||||
emit trackersChanged(torrent);
|
||||
}
|
||||
|
||||
void Session::handleTorrentUrlSeedsAdded(TorrentHandle *const torrent, const QList<QUrl> &newUrlSeeds)
|
||||
{
|
||||
foreach (const QUrl &newUrlSeed, newUrlSeeds)
|
||||
Logger::instance()->addMessage(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name()));
|
||||
saveTorrentResumeData(torrent);
|
||||
for (const QUrl &newUrlSeed : newUrlSeeds)
|
||||
LogMsg(tr("URL seed '%1' was added to torrent '%2'").arg(newUrlSeed.toString(), torrent->name()));
|
||||
}
|
||||
|
||||
void Session::handleTorrentUrlSeedsRemoved(TorrentHandle *const torrent, const QList<QUrl> &urlSeeds)
|
||||
{
|
||||
foreach (const QUrl &urlSeed, urlSeeds)
|
||||
Logger::instance()->addMessage(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name()));
|
||||
saveTorrentResumeData(torrent);
|
||||
for (const QUrl &urlSeed : urlSeeds)
|
||||
LogMsg(tr("URL seed '%1' was removed from torrent '%2'").arg(urlSeed.toString(), torrent->name()));
|
||||
}
|
||||
|
||||
void Session::handleTorrentMetadataReceived(TorrentHandle *const torrent)
|
||||
@@ -3599,6 +3666,7 @@ void Session::handleTorrentPaused(TorrentHandle *const torrent)
|
||||
|
||||
void Session::handleTorrentResumed(TorrentHandle *const torrent)
|
||||
{
|
||||
saveTorrentResumeData(torrent);
|
||||
emit torrentResumed(torrent);
|
||||
}
|
||||
|
||||
@@ -3609,8 +3677,11 @@ void Session::handleTorrentChecked(TorrentHandle *const torrent)
|
||||
|
||||
void Session::handleTorrentFinished(TorrentHandle *const torrent)
|
||||
{
|
||||
if (!torrent->hasError() && !torrent->hasMissingFiles())
|
||||
if (!torrent->hasError() && !torrent->hasMissingFiles()) {
|
||||
saveTorrentResumeData(torrent);
|
||||
if (isQueueingSystemEnabled())
|
||||
handleTorrentsPrioritiesChanged();
|
||||
}
|
||||
emit torrentFinished(torrent);
|
||||
|
||||
qDebug("Checking if the torrent contains torrent files to download");
|
||||
@@ -3619,7 +3690,7 @@ void Session::handleTorrentFinished(TorrentHandle *const torrent)
|
||||
const QString torrentRelpath = torrent->filePath(i);
|
||||
if (torrentRelpath.endsWith(".torrent", Qt::CaseInsensitive)) {
|
||||
qDebug("Found possible recursive torrent download.");
|
||||
const QString torrentFullpath = torrent->savePath(true) + "/" + torrentRelpath;
|
||||
const QString torrentFullpath = torrent->savePath(true) + '/' + torrentRelpath;
|
||||
qDebug("Full subtorrent path is %s", qUtf8Printable(torrentFullpath));
|
||||
TorrentInfo torrentInfo = TorrentInfo::loadFromFile(torrentFullpath);
|
||||
if (torrentInfo.isValid()) {
|
||||
@@ -3769,7 +3840,7 @@ void Session::recursiveTorrentDownload(const InfoHash &hash)
|
||||
tr("Recursive download of file '%1' embedded in torrent '%2'"
|
||||
, "Recursive download of 'test.torrent' embedded in torrent 'test2'")
|
||||
.arg(Utils::Fs::toNativePath(torrentRelpath), torrent->name()));
|
||||
const QString torrentFullpath = torrent->savePath() + "/" + torrentRelpath;
|
||||
const QString torrentFullpath = torrent->savePath() + '/' + torrentRelpath;
|
||||
|
||||
AddTorrentParams params;
|
||||
// Passing the save path along to the sub torrent file
|
||||
@@ -3804,7 +3875,7 @@ void Session::startUpTorrents()
|
||||
{
|
||||
QString hash;
|
||||
MagnetUri magnetUri;
|
||||
AddTorrentData addTorrentData;
|
||||
CreateTorrentParams addTorrentData;
|
||||
QByteArray data;
|
||||
} TorrentResumeData;
|
||||
|
||||
@@ -3829,19 +3900,20 @@ void Session::startUpTorrents()
|
||||
QMap<int, TorrentResumeData> queuedResumeData;
|
||||
int nextQueuePosition = 1;
|
||||
int numOfRemappedFiles = 0;
|
||||
QRegExp rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
|
||||
const QRegularExpression rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
|
||||
foreach (const QString &fastresumeName, fastresumes) {
|
||||
if (rx.indexIn(fastresumeName) == -1) continue;
|
||||
const QRegularExpressionMatch rxMatch = rx.match(fastresumeName);
|
||||
if (!rxMatch.hasMatch()) continue;
|
||||
|
||||
QString hash = rx.cap(1);
|
||||
QString hash = rxMatch.captured(1);
|
||||
QString fastresumePath = resumeDataDir.absoluteFilePath(fastresumeName);
|
||||
QByteArray data;
|
||||
AddTorrentData resumeData;
|
||||
CreateTorrentParams torrentParams;
|
||||
MagnetUri magnetUri;
|
||||
int queuePosition;
|
||||
if (readFile(fastresumePath, data) && loadTorrentResumeData(data, resumeData, queuePosition, magnetUri)) {
|
||||
if (readFile(fastresumePath, data) && loadTorrentResumeData(data, torrentParams, queuePosition, magnetUri)) {
|
||||
if (queuePosition <= nextQueuePosition) {
|
||||
startupTorrent({ hash, magnetUri, resumeData, data });
|
||||
startupTorrent({ hash, magnetUri, torrentParams, data });
|
||||
|
||||
if (queuePosition == nextQueuePosition) {
|
||||
++nextQueuePosition;
|
||||
@@ -3858,7 +3930,7 @@ void Session::startUpTorrents()
|
||||
if (q != queuePosition) {
|
||||
++numOfRemappedFiles;
|
||||
}
|
||||
queuedResumeData[q] = {hash, magnetUri, resumeData, data};
|
||||
queuedResumeData[q] = {hash, magnetUri, torrentParams, data};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3984,6 +4056,7 @@ void Session::handleAlert(libt::alert *a)
|
||||
case libt::storage_moved_alert::alert_type:
|
||||
case libt::storage_moved_failed_alert::alert_type:
|
||||
case libt::torrent_paused_alert::alert_type:
|
||||
case libt::torrent_resumed_alert::alert_type:
|
||||
case libt::tracker_error_alert::alert_type:
|
||||
case libt::tracker_reply_alert::alert_type:
|
||||
case libt::tracker_warning_alert::alert_type:
|
||||
@@ -4061,25 +4134,19 @@ void Session::createTorrentHandle(const libt::torrent_handle &nativeHandle)
|
||||
// Magnet added for preload its metadata
|
||||
if (!m_addingTorrents.contains(nativeHandle.info_hash())) return;
|
||||
|
||||
AddTorrentData data = m_addingTorrents.take(nativeHandle.info_hash());
|
||||
CreateTorrentParams params = m_addingTorrents.take(nativeHandle.info_hash());
|
||||
|
||||
TorrentHandle *const torrent = new TorrentHandle(this, nativeHandle, data);
|
||||
TorrentHandle *const torrent = new TorrentHandle(this, nativeHandle, params);
|
||||
m_torrents.insert(torrent->hash(), torrent);
|
||||
|
||||
Logger *const logger = Logger::instance();
|
||||
|
||||
bool fromMagnetUri = !torrent->hasMetadata();
|
||||
|
||||
if (data.resumed) {
|
||||
if (fromMagnetUri && !data.addPaused)
|
||||
torrent->resume(data.addForced);
|
||||
|
||||
logger->addMessage(tr("'%1' resumed. (fast resume)", "'torrent name' was resumed. (fast resume)")
|
||||
.arg(torrent->name()));
|
||||
if (params.restored) {
|
||||
logger->addMessage(tr("'%1' restored.", "'torrent name' restored.").arg(torrent->name()));
|
||||
}
|
||||
else {
|
||||
qDebug("This is a NEW torrent (first time)...");
|
||||
|
||||
// The following is useless for newly added magnet
|
||||
if (!fromMagnetUri) {
|
||||
// Backup torrent file
|
||||
@@ -4098,9 +4165,6 @@ void Session::createTorrentHandle(const libt::torrent_handle &nativeHandle)
|
||||
if (isAddTrackersEnabled() && !torrent->isPrivate())
|
||||
torrent->addTrackers(m_additionalTrackerList);
|
||||
|
||||
// Start torrent because it was added in paused state
|
||||
if (!data.addPaused)
|
||||
torrent->resume();
|
||||
logger->addMessage(tr("'%1' added to download list.", "'torrent name' was added to download list.")
|
||||
.arg(torrent->name()));
|
||||
|
||||
@@ -4116,7 +4180,7 @@ void Session::createTorrentHandle(const libt::torrent_handle &nativeHandle)
|
||||
// Send torrent addition signal
|
||||
emit torrentAdded(torrent);
|
||||
// Send new torrent signal
|
||||
if (!data.resumed)
|
||||
if (!params.restored)
|
||||
emit torrentNew(torrent);
|
||||
}
|
||||
|
||||
@@ -4271,7 +4335,7 @@ void Session::handleListenSucceededAlert(libt::listen_succeeded_alert *p)
|
||||
proto = "TCP";
|
||||
else if (p->sock_type == libt::listen_succeeded_alert::tcp_ssl)
|
||||
proto = "TCP_SSL";
|
||||
qDebug() << "Successfully listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port();
|
||||
qDebug() << "Successfully listening on " << proto << p->endpoint.address().to_string(ec).c_str() << '/' << p->endpoint.port();
|
||||
Logger::instance()->addMessage(
|
||||
tr("qBittorrent is successfully listening on interface %1 port: %2/%3", "e.g: qBittorrent is successfully listening on interface 192.168.0.1 port: TCP/6881")
|
||||
.arg(p->endpoint.address().to_string(ec).c_str(), proto, QString::number(p->endpoint.port())), Log::INFO);
|
||||
@@ -4298,7 +4362,7 @@ void Session::handleListenFailedAlert(libt::listen_failed_alert *p)
|
||||
proto = "I2P";
|
||||
else if (p->sock_type == libt::listen_failed_alert::socks5)
|
||||
proto = "SOCKS5";
|
||||
qDebug() << "Failed listening on " << proto << p->endpoint.address().to_string(ec).c_str() << "/" << p->endpoint.port();
|
||||
qDebug() << "Failed listening on " << proto << p->endpoint.address().to_string(ec).c_str() << '/' << p->endpoint.port();
|
||||
Logger::instance()->addMessage(
|
||||
tr("qBittorrent failed listening on interface %1 port: %2/%3. Reason: %4.",
|
||||
"e.g: qBittorrent failed listening on interface 192.168.0.1 port: TCP/6881. Reason: already in use.")
|
||||
@@ -4466,11 +4530,11 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
bool loadTorrentResumeData(const QByteArray &data, AddTorrentData &torrentData, int &prio, MagnetUri &magnetUri)
|
||||
bool loadTorrentResumeData(const QByteArray &data, CreateTorrentParams &torrentParams, int &prio, MagnetUri &magnetUri)
|
||||
{
|
||||
torrentData = AddTorrentData();
|
||||
torrentData.resumed = true;
|
||||
torrentData.skipChecking = false;
|
||||
torrentParams = CreateTorrentParams();
|
||||
torrentParams.restored = true;
|
||||
torrentParams.skipChecking = false;
|
||||
|
||||
libt::error_code ec;
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
@@ -4483,36 +4547,38 @@ namespace
|
||||
if (ec || (fast.type() != libt::bdecode_node::dict_t)) return false;
|
||||
#endif
|
||||
|
||||
torrentData.savePath = Profile::instance().fromPortablePath(
|
||||
torrentParams.savePath = Profile::instance().fromPortablePath(
|
||||
Utils::Fs::fromNativePath(QString::fromStdString(fast.dict_find_string_value("qBt-savePath"))));
|
||||
|
||||
std::string ratioLimitString = fast.dict_find_string_value("qBt-ratioLimit");
|
||||
if (ratioLimitString.empty())
|
||||
torrentData.ratioLimit = fast.dict_find_int_value("qBt-ratioLimit", TorrentHandle::USE_GLOBAL_RATIO * 1000) / 1000.0;
|
||||
torrentParams.ratioLimit = fast.dict_find_int_value("qBt-ratioLimit", TorrentHandle::USE_GLOBAL_RATIO * 1000) / 1000.0;
|
||||
else
|
||||
torrentData.ratioLimit = QString::fromStdString(ratioLimitString).toDouble();
|
||||
torrentData.seedingTimeLimit = fast.dict_find_int_value("qBt-seedingTimeLimit", TorrentHandle::USE_GLOBAL_SEEDING_TIME);
|
||||
torrentParams.ratioLimit = QString::fromStdString(ratioLimitString).toDouble();
|
||||
torrentParams.seedingTimeLimit = fast.dict_find_int_value("qBt-seedingTimeLimit", TorrentHandle::USE_GLOBAL_SEEDING_TIME);
|
||||
// **************************************************************************************
|
||||
// Workaround to convert legacy label to category
|
||||
// TODO: Should be removed in future
|
||||
torrentData.category = QString::fromStdString(fast.dict_find_string_value("qBt-label"));
|
||||
if (torrentData.category.isEmpty())
|
||||
torrentParams.category = QString::fromStdString(fast.dict_find_string_value("qBt-label"));
|
||||
if (torrentParams.category.isEmpty())
|
||||
// **************************************************************************************
|
||||
torrentData.category = QString::fromStdString(fast.dict_find_string_value("qBt-category"));
|
||||
torrentParams.category = QString::fromStdString(fast.dict_find_string_value("qBt-category"));
|
||||
// auto because the return type depends on the #if above.
|
||||
const auto tagsEntry = fast.dict_find_list("qBt-tags");
|
||||
if (isList(tagsEntry))
|
||||
torrentData.tags = entryListToSet(tagsEntry);
|
||||
torrentData.name = QString::fromStdString(fast.dict_find_string_value("qBt-name"));
|
||||
torrentData.hasSeedStatus = fast.dict_find_int_value("qBt-seedStatus");
|
||||
torrentData.disableTempPath = fast.dict_find_int_value("qBt-tempPathDisabled");
|
||||
torrentData.hasRootFolder = fast.dict_find_int_value("qBt-hasRootFolder");
|
||||
torrentParams.tags = entryListToSet(tagsEntry);
|
||||
torrentParams.name = QString::fromStdString(fast.dict_find_string_value("qBt-name"));
|
||||
torrentParams.hasSeedStatus = fast.dict_find_int_value("qBt-seedStatus");
|
||||
torrentParams.disableTempPath = fast.dict_find_int_value("qBt-tempPathDisabled");
|
||||
torrentParams.hasRootFolder = fast.dict_find_int_value("qBt-hasRootFolder");
|
||||
|
||||
magnetUri = MagnetUri(QString::fromStdString(fast.dict_find_string_value("qBt-magnetUri")));
|
||||
torrentData.addPaused = fast.dict_find_int_value("qBt-paused");
|
||||
torrentData.addForced = fast.dict_find_int_value("qBt-forced");
|
||||
torrentData.firstLastPiecePriority = fast.dict_find_int_value("qBt-firstLastPiecePriority");
|
||||
torrentData.sequential = fast.dict_find_int_value("qBt-sequential");
|
||||
const bool isAutoManaged = fast.dict_find_int_value("auto_managed");
|
||||
const bool isPaused = fast.dict_find_int_value("paused");
|
||||
torrentParams.paused = fast.dict_find_int_value("qBt-paused", (isPaused && !isAutoManaged));
|
||||
torrentParams.forced = fast.dict_find_int_value("qBt-forced", (!isPaused && !isAutoManaged));
|
||||
torrentParams.firstLastPiecePriority = fast.dict_find_int_value("qBt-firstLastPiecePriority");
|
||||
torrentParams.sequential = fast.dict_find_int_value("qBt-sequential");
|
||||
|
||||
prio = fast.dict_find_int_value("qBt-queuePosition");
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace BitTorrent
|
||||
class Tracker;
|
||||
class MagnetUri;
|
||||
class TrackerEntry;
|
||||
struct AddTorrentData;
|
||||
struct CreateTorrentParams;
|
||||
|
||||
struct TorrentStatusReport
|
||||
{
|
||||
@@ -375,6 +375,8 @@ namespace BitTorrent
|
||||
void setAnnounceToAllTrackers(bool val);
|
||||
bool announceToAllTiers() const;
|
||||
void setAnnounceToAllTiers(bool val);
|
||||
int asyncIOThreads() const;
|
||||
void setAsyncIOThreads(int num);
|
||||
int diskCacheSize() const;
|
||||
void setDiskCacheSize(int size);
|
||||
int diskCacheTTL() const;
|
||||
@@ -452,6 +454,7 @@ namespace BitTorrent
|
||||
TorrentStatusReport torrentStatusReport() const;
|
||||
bool hasActiveTorrents() const;
|
||||
bool hasUnfinishedTorrents() const;
|
||||
bool hasRunningSeed() const;
|
||||
const SessionStatus &status() const;
|
||||
const CacheStatus &cacheStatus() const;
|
||||
quint64 getAlltimeDL() const;
|
||||
@@ -478,6 +481,8 @@ namespace BitTorrent
|
||||
|
||||
// TorrentHandle interface
|
||||
void handleTorrentShareLimitChanged(TorrentHandle *const torrent);
|
||||
void handleTorrentsPrioritiesChanged();
|
||||
void handleTorrentNameChanged(TorrentHandle *const torrent);
|
||||
void handleTorrentSavePathChanged(TorrentHandle *const torrent);
|
||||
void handleTorrentCategoryChanged(TorrentHandle *const torrent, const QString &oldCategory);
|
||||
void handleTorrentTagAdded(TorrentHandle *const torrent, const QString &tag);
|
||||
@@ -547,7 +552,7 @@ namespace BitTorrent
|
||||
void generateResumeData(bool final = false);
|
||||
void handleIPFilterParsed(int ruleCount);
|
||||
void handleIPFilterError();
|
||||
void handleDownloadFinished(const QString &url, const QString &filePath);
|
||||
void handleDownloadFinished(const QString &url, const QByteArray &data);
|
||||
void handleDownloadFailed(const QString &url, const QString &reason);
|
||||
void handleRedirectedToMagnet(const QString &url, const QString &magnetUri);
|
||||
|
||||
@@ -595,14 +600,14 @@ namespace BitTorrent
|
||||
void enableIPFilter();
|
||||
void disableIPFilter();
|
||||
|
||||
bool addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri,
|
||||
bool addTorrent_impl(CreateTorrentParams params, const MagnetUri &magnetUri,
|
||||
TorrentInfo torrentInfo = TorrentInfo(),
|
||||
const QByteArray &fastresumeData = QByteArray());
|
||||
bool findIncompleteFiles(TorrentInfo &torrentInfo, QString &savePath) const;
|
||||
|
||||
void updateSeedingLimitTimer();
|
||||
void exportTorrentFile(TorrentHandle *const torrent, TorrentExportFolder folder = TorrentExportFolder::Regular);
|
||||
void saveTorrentResumeData(TorrentHandle *const torrent, bool finalSave = false);
|
||||
void saveTorrentResumeData(TorrentHandle *const torrent);
|
||||
|
||||
void handleAlert(libtorrent::alert *a);
|
||||
void dispatchTorrentAlert(libtorrent::alert *a);
|
||||
@@ -651,6 +656,7 @@ namespace BitTorrent
|
||||
CachedSettingValue<QString> m_IPFilterFile;
|
||||
CachedSettingValue<bool> m_announceToAllTrackers;
|
||||
CachedSettingValue<bool> m_announceToAllTiers;
|
||||
CachedSettingValue<int> m_asyncIOThreads;
|
||||
CachedSettingValue<int> m_diskCacheSize;
|
||||
CachedSettingValue<int> m_diskCacheTTL;
|
||||
CachedSettingValue<bool> m_useOSCache;
|
||||
@@ -754,7 +760,7 @@ namespace BitTorrent
|
||||
|
||||
QHash<InfoHash, TorrentInfo> m_loadedMetadata;
|
||||
QHash<InfoHash, TorrentHandle *> m_torrents;
|
||||
QHash<InfoHash, AddTorrentData> m_addingTorrents;
|
||||
QHash<InfoHash, CreateTorrentParams> m_addingTorrents;
|
||||
QHash<QString, AddTorrentParams> m_downloadedTorrents;
|
||||
QHash<InfoHash, RemovingTorrentData> m_removingTorrents;
|
||||
TorrentStatusReport m_torrentStatusReport;
|
||||
|
||||
@@ -89,7 +89,7 @@ void TorrentCreatorThread::run()
|
||||
emit updateProgress(0);
|
||||
|
||||
try {
|
||||
const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + "/";
|
||||
const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + '/';
|
||||
|
||||
// Adding files to the torrent
|
||||
libt::file_storage fs;
|
||||
|
||||
@@ -85,16 +85,16 @@ namespace
|
||||
|
||||
// AddTorrentData
|
||||
|
||||
AddTorrentData::AddTorrentData()
|
||||
: resumed(false)
|
||||
CreateTorrentParams::CreateTorrentParams()
|
||||
: restored(false)
|
||||
, disableTempPath(false)
|
||||
, sequential(false)
|
||||
, firstLastPiecePriority(false)
|
||||
, hasSeedStatus(false)
|
||||
, skipChecking(false)
|
||||
, hasRootFolder(true)
|
||||
, addForced(false)
|
||||
, addPaused(false)
|
||||
, forced(false)
|
||||
, paused(false)
|
||||
, uploadLimit(-1)
|
||||
, downloadLimit(-1)
|
||||
, ratioLimit(TorrentHandle::USE_GLOBAL_RATIO)
|
||||
@@ -102,8 +102,8 @@ AddTorrentData::AddTorrentData()
|
||||
{
|
||||
}
|
||||
|
||||
AddTorrentData::AddTorrentData(const AddTorrentParams ¶ms)
|
||||
: resumed(false)
|
||||
CreateTorrentParams::CreateTorrentParams(const AddTorrentParams ¶ms)
|
||||
: restored(false)
|
||||
, name(params.name)
|
||||
, category(params.category)
|
||||
, tags(params.tags)
|
||||
@@ -116,8 +116,8 @@ AddTorrentData::AddTorrentData(const AddTorrentParams ¶ms)
|
||||
, hasRootFolder(params.createSubfolder == TriStateBool::Undefined
|
||||
? Session::instance()->isCreateTorrentSubfolder()
|
||||
: params.createSubfolder == TriStateBool::True)
|
||||
, addForced(params.addForced == TriStateBool::True)
|
||||
, addPaused(params.addPaused == TriStateBool::Undefined
|
||||
, forced(params.addForced == TriStateBool::True)
|
||||
, paused(params.addPaused == TriStateBool::Undefined
|
||||
? Session::instance()->isAddTorrentPaused()
|
||||
: params.addPaused == TriStateBool::True)
|
||||
, uploadLimit(params.uploadLimit)
|
||||
@@ -172,26 +172,25 @@ namespace
|
||||
}
|
||||
|
||||
TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle &nativeHandle,
|
||||
const AddTorrentData &data)
|
||||
const CreateTorrentParams ¶ms)
|
||||
: QObject(session)
|
||||
, m_session(session)
|
||||
, m_nativeHandle(nativeHandle)
|
||||
, m_state(TorrentState::Unknown)
|
||||
, m_renameCount(0)
|
||||
, m_useAutoTMM(data.savePath.isEmpty())
|
||||
, m_name(data.name)
|
||||
, m_savePath(Utils::Fs::toNativePath(data.savePath))
|
||||
, m_category(data.category)
|
||||
, m_tags(data.tags)
|
||||
, m_hasSeedStatus(data.hasSeedStatus)
|
||||
, m_ratioLimit(data.ratioLimit)
|
||||
, m_seedingTimeLimit(data.seedingTimeLimit)
|
||||
, m_tempPathDisabled(data.disableTempPath)
|
||||
, m_useAutoTMM(params.savePath.isEmpty())
|
||||
, m_name(params.name)
|
||||
, m_savePath(Utils::Fs::toNativePath(params.savePath))
|
||||
, m_category(params.category)
|
||||
, m_tags(params.tags)
|
||||
, m_hasSeedStatus(params.hasSeedStatus)
|
||||
, m_ratioLimit(params.ratioLimit)
|
||||
, m_seedingTimeLimit(params.seedingTimeLimit)
|
||||
, m_tempPathDisabled(params.disableTempPath)
|
||||
, m_hasMissingFiles(false)
|
||||
, m_hasRootFolder(data.hasRootFolder)
|
||||
, m_hasRootFolder(params.hasRootFolder)
|
||||
, m_needsToSetFirstLastPiecePriority(false)
|
||||
, m_pauseAfterRecheck(false)
|
||||
, m_needSaveResumeData(false)
|
||||
, m_needsToStartForced(params.forced)
|
||||
{
|
||||
if (m_useAutoTMM)
|
||||
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
|
||||
@@ -207,15 +206,29 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
|
||||
// download sequentially or have first/last piece priority enabled when
|
||||
// its resume data was saved. These two settings are restored later. But
|
||||
// if we set them to false now, both will erroneously not be restored.
|
||||
if (!data.resumed || data.sequential)
|
||||
setSequentialDownload(data.sequential);
|
||||
if (!data.resumed || data.firstLastPiecePriority)
|
||||
setFirstLastPiecePriority(data.firstLastPiecePriority);
|
||||
if (!params.restored || params.sequential)
|
||||
setSequentialDownload(params.sequential);
|
||||
if (!params.restored || params.firstLastPiecePriority)
|
||||
setFirstLastPiecePriority(params.firstLastPiecePriority);
|
||||
|
||||
if (!data.resumed && hasMetadata()) {
|
||||
if (!params.restored && hasMetadata()) {
|
||||
if (filesCount() == 1)
|
||||
m_hasRootFolder = false;
|
||||
}
|
||||
|
||||
// "started" means "all initialization has completed and torrent has started regular processing".
|
||||
// When torrent added/restored in "paused" state it become "started" immediately after construction.
|
||||
// When it is added/restored in "resumed" state, it become "started" after it is really resumed
|
||||
// (i.e. after receiving "torrent resumed" alert).
|
||||
if (params.paused) {
|
||||
m_startupState = Started;
|
||||
}
|
||||
else if (!params.restored) {
|
||||
// Resume torrent because it was added in "resumed" state
|
||||
// but it's actually paused during initialization
|
||||
m_startupState = Starting;
|
||||
resume(params.forced);
|
||||
}
|
||||
}
|
||||
|
||||
TorrentHandle::~TorrentHandle() {}
|
||||
@@ -315,7 +328,7 @@ QString TorrentHandle::rootPath(bool actual) const
|
||||
return QString();
|
||||
|
||||
QString firstFilePath = filePath(0);
|
||||
const int slashIndex = firstFilePath.indexOf("/");
|
||||
const int slashIndex = firstFilePath.indexOf('/');
|
||||
if (slashIndex >= 0)
|
||||
return QDir(savePath(actual)).absoluteFilePath(firstFilePath.left(slashIndex));
|
||||
else
|
||||
@@ -488,18 +501,12 @@ bool TorrentHandle::connectPeer(const PeerAddress &peerAddress)
|
||||
|
||||
bool TorrentHandle::needSaveResumeData() const
|
||||
{
|
||||
if (m_needSaveResumeData) return true;
|
||||
|
||||
return m_nativeHandle.need_save_resume_data();
|
||||
}
|
||||
|
||||
void TorrentHandle::saveResumeData(bool updateStatus)
|
||||
void TorrentHandle::saveResumeData()
|
||||
{
|
||||
if (updateStatus) // to update queue_position, see discussion in PR #6154
|
||||
this->updateStatus();
|
||||
|
||||
m_nativeHandle.save_resume_data();
|
||||
m_needSaveResumeData = false;
|
||||
}
|
||||
|
||||
int TorrentHandle::filesCount() const
|
||||
@@ -546,7 +553,7 @@ bool TorrentHandle::belongsToCategory(const QString &category) const
|
||||
|
||||
if (m_category == category) return true;
|
||||
|
||||
if (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + "/"))
|
||||
if (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + '/'))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@@ -573,7 +580,6 @@ bool TorrentHandle::addTag(const QString &tag)
|
||||
return false;
|
||||
m_tags.insert(tag);
|
||||
m_session->handleTorrentTagAdded(this, tag);
|
||||
m_needSaveResumeData = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -583,7 +589,6 @@ bool TorrentHandle::removeTag(const QString &tag)
|
||||
{
|
||||
if (m_tags.remove(tag)) {
|
||||
m_session->handleTorrentTagRemoved(this, tag);
|
||||
m_needSaveResumeData = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -802,7 +807,10 @@ TorrentState TorrentHandle::state() const
|
||||
|
||||
void TorrentHandle::updateState()
|
||||
{
|
||||
if (isMoveInProgress()) {
|
||||
if (m_nativeStatus.state == libt::torrent_status::checking_resume_data) {
|
||||
m_state = TorrentState::CheckingResumeData;
|
||||
}
|
||||
else if (isMoveInProgress()) {
|
||||
m_state = TorrentState::Moving;
|
||||
}
|
||||
else if (isPaused()) {
|
||||
@@ -834,9 +842,6 @@ void TorrentHandle::updateState()
|
||||
m_state = TorrentState::QueuedForChecking;
|
||||
break;
|
||||
#endif
|
||||
case libt::torrent_status::checking_resume_data:
|
||||
m_state = TorrentState::CheckingResumeData;
|
||||
break;
|
||||
case libt::torrent_status::checking_files:
|
||||
m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
|
||||
break;
|
||||
@@ -1198,23 +1203,18 @@ void TorrentHandle::setName(const QString &name)
|
||||
{
|
||||
if (m_name != name) {
|
||||
m_name = name;
|
||||
m_needSaveResumeData = true;
|
||||
m_session->handleTorrentNameChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool TorrentHandle::setCategory(const QString &category)
|
||||
{
|
||||
if (m_category != category) {
|
||||
if (!category.isEmpty()) {
|
||||
if (!Session::isValidCategoryName(category)) return false;
|
||||
if (!m_session->categories().contains(category))
|
||||
if (!m_session->addCategory(category))
|
||||
return false;
|
||||
}
|
||||
if (!category.isEmpty() && !m_session->categories().contains(category))
|
||||
return false;
|
||||
|
||||
QString oldCategory = m_category;
|
||||
m_category = category;
|
||||
m_needSaveResumeData = true;
|
||||
m_session->handleTorrentCategoryChanged(this, oldCategory);
|
||||
|
||||
if (m_useAutoTMM) {
|
||||
@@ -1252,7 +1252,6 @@ void TorrentHandle::move_impl(QString path, bool overwrite)
|
||||
}
|
||||
else {
|
||||
m_savePath = path;
|
||||
m_needSaveResumeData = true;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
}
|
||||
}
|
||||
@@ -1271,12 +1270,13 @@ void TorrentHandle::forceRecheck()
|
||||
{
|
||||
if (!hasMetadata()) return;
|
||||
|
||||
m_nativeHandle.force_recheck();
|
||||
m_unchecked = false;
|
||||
|
||||
if (isPaused()) {
|
||||
m_pauseAfterRecheck = true;
|
||||
m_nativeHandle.stop_when_ready(true);
|
||||
resume_impl(true, true);
|
||||
}
|
||||
|
||||
m_nativeHandle.force_recheck();
|
||||
}
|
||||
|
||||
void TorrentHandle::setSequentialDownload(bool b)
|
||||
@@ -1294,13 +1294,21 @@ void TorrentHandle::toggleSequentialDownload()
|
||||
|
||||
void TorrentHandle::setFirstLastPiecePriority(const bool enabled)
|
||||
{
|
||||
setFirstLastPiecePriorityImpl(enabled);
|
||||
}
|
||||
|
||||
void TorrentHandle::setFirstLastPiecePriorityImpl(const bool enabled, const QVector<int> &updatedFilePrio)
|
||||
{
|
||||
// Download first and last pieces first for every file in the torrent
|
||||
|
||||
if (!hasMetadata()) {
|
||||
m_needsToSetFirstLastPiecePriority = enabled;
|
||||
return;
|
||||
}
|
||||
|
||||
// Download first and last pieces first for every file in the torrent
|
||||
const std::vector<int> filePriorities = nativeHandle().file_priorities();
|
||||
// Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
|
||||
// we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
|
||||
const std::vector<int> filePriorities = !updatedFilePrio.isEmpty() ? updatedFilePrio.toStdVector() : nativeHandle().file_priorities();
|
||||
std::vector<int> piecePriorities = nativeHandle().piece_priorities();
|
||||
for (int index = 0; index < static_cast<int>(filePriorities.size()); ++index) {
|
||||
const int filePrio = filePriorities[index];
|
||||
@@ -1336,6 +1344,12 @@ void TorrentHandle::pause()
|
||||
|
||||
m_nativeHandle.auto_managed(false);
|
||||
m_nativeHandle.pause();
|
||||
|
||||
// Libtorrent doesn't emit a torrent_paused_alert when the
|
||||
// torrent is queued (no I/O)
|
||||
// We test on the cached m_nativeStatus
|
||||
if (isQueued())
|
||||
m_session->handleTorrentPaused(this);
|
||||
}
|
||||
|
||||
void TorrentHandle::resume(bool forced)
|
||||
@@ -1347,7 +1361,12 @@ void TorrentHandle::resume_impl(bool forced, bool uploadMode)
|
||||
{
|
||||
if (hasError())
|
||||
m_nativeHandle.clear_error();
|
||||
m_hasMissingFiles = false;
|
||||
|
||||
if (m_hasMissingFiles) {
|
||||
m_hasMissingFiles = false;
|
||||
m_nativeHandle.force_recheck();
|
||||
}
|
||||
|
||||
m_nativeHandle.auto_managed(!forced);
|
||||
m_nativeHandle.set_upload_mode(uploadMode);
|
||||
m_nativeHandle.resume();
|
||||
@@ -1539,21 +1558,32 @@ void TorrentHandle::handleTrackerErrorAlert(const libtorrent::tracker_error_aler
|
||||
void TorrentHandle::handleTorrentCheckedAlert(const libtorrent::torrent_checked_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
qDebug("%s have just finished checking", qUtf8Printable(hash()));
|
||||
qDebug("\"%s\" have just finished checking", qUtf8Printable(name()));
|
||||
|
||||
if (m_startupState == NotStarted) {
|
||||
if (!m_hasMissingFiles) {
|
||||
// Resume torrent because it was added in "resumed" state
|
||||
// but it's actually paused during initialization.
|
||||
m_startupState = Starting;
|
||||
resume(m_needsToStartForced);
|
||||
}
|
||||
else {
|
||||
// Torrent that has missing files is marked as "started"
|
||||
// but it remains paused.
|
||||
m_startupState = Started;
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus();
|
||||
|
||||
if ((progress() < 1.0) && (wantedSize() > 0))
|
||||
m_hasSeedStatus = false;
|
||||
else if (progress() == 1.0)
|
||||
m_hasSeedStatus = true;
|
||||
if (!m_hasMissingFiles) {
|
||||
if ((progress() < 1.0) && (wantedSize() > 0))
|
||||
m_hasSeedStatus = false;
|
||||
else if (progress() == 1.0)
|
||||
m_hasSeedStatus = true;
|
||||
|
||||
adjustActualSavePath();
|
||||
manageIncompleteFiles();
|
||||
|
||||
if (m_pauseAfterRecheck) {
|
||||
m_pauseAfterRecheck = false;
|
||||
pause();
|
||||
adjustActualSavePath();
|
||||
manageIncompleteFiles();
|
||||
}
|
||||
|
||||
m_session->handleTorrentChecked(this);
|
||||
@@ -1562,7 +1592,7 @@ void TorrentHandle::handleTorrentCheckedAlert(const libtorrent::torrent_checked_
|
||||
void TorrentHandle::handleTorrentFinishedAlert(const libtorrent::torrent_finished_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
qDebug("Got a torrent finished alert for %s", qUtf8Printable(name()));
|
||||
qDebug("Got a torrent finished alert for \"%s\"", qUtf8Printable(name()));
|
||||
qDebug("Torrent has seed status: %s", m_hasSeedStatus ? "yes" : "no");
|
||||
if (m_hasSeedStatus) return;
|
||||
|
||||
@@ -1574,13 +1604,13 @@ void TorrentHandle::handleTorrentFinishedAlert(const libtorrent::torrent_finishe
|
||||
manageIncompleteFiles();
|
||||
|
||||
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
|
||||
if (isMoveInProgress() || m_renameCount > 0) {
|
||||
if (isMoveInProgress() || (m_renameCount > 0)) {
|
||||
if (recheckTorrentsOnCompletion)
|
||||
m_moveFinishedTriggers.append(boost::bind(&TorrentHandle::forceRecheck, this));
|
||||
m_moveFinishedTriggers.append(boost::bind(&Session::handleTorrentFinished, m_session, this));
|
||||
}
|
||||
else {
|
||||
if (recheckTorrentsOnCompletion)
|
||||
if (recheckTorrentsOnCompletion && m_unchecked)
|
||||
forceRecheck();
|
||||
m_session->handleTorrentFinished(this);
|
||||
}
|
||||
@@ -1589,15 +1619,22 @@ void TorrentHandle::handleTorrentFinishedAlert(const libtorrent::torrent_finishe
|
||||
void TorrentHandle::handleTorrentPausedAlert(const libtorrent::torrent_paused_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
updateStatus();
|
||||
m_speedMonitor.reset();
|
||||
m_session->handleTorrentPaused(this);
|
||||
|
||||
if (m_startupState == Started) {
|
||||
updateStatus();
|
||||
m_speedMonitor.reset();
|
||||
m_session->handleTorrentPaused(this);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentHandle::handleTorrentResumedAlert(const libtorrent::torrent_resumed_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
m_session->handleTorrentResumed(this);
|
||||
|
||||
if (m_startupState == Started)
|
||||
m_session->handleTorrentResumed(this);
|
||||
else if (m_startupState == Starting)
|
||||
m_startupState = Started;
|
||||
}
|
||||
|
||||
void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data_alert *p)
|
||||
@@ -1628,7 +1665,7 @@ void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data
|
||||
resumeData["qBt-name"] = m_name.toStdString();
|
||||
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
|
||||
resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled;
|
||||
resumeData["qBt-queuePosition"] = queuePosition();
|
||||
resumeData["qBt-queuePosition"] = (nativeHandle().queue_position() + 1); // qBt starts queue at 1
|
||||
resumeData["qBt-hasRootFolder"] = m_hasRootFolder;
|
||||
|
||||
m_session->handleTorrentResumeDataReady(this, resumeData);
|
||||
@@ -1646,15 +1683,10 @@ void TorrentHandle::handleSaveResumeDataFailedAlert(const libtorrent::save_resum
|
||||
|
||||
void TorrentHandle::handleFastResumeRejectedAlert(const libtorrent::fastresume_rejected_alert *p)
|
||||
{
|
||||
qDebug("/!\\ Fast resume failed for %s, reason: %s", qUtf8Printable(name()), p->message().c_str());
|
||||
|
||||
updateStatus();
|
||||
if (p->error.value() == libt::errors::mismatching_file_size) {
|
||||
// Mismatching file size (files were probably moved)
|
||||
LogMsg(tr("File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL);
|
||||
m_hasMissingFiles = true;
|
||||
if (!isPaused())
|
||||
pause();
|
||||
LogMsg(tr("File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL);
|
||||
}
|
||||
else {
|
||||
LogMsg(tr("Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...")
|
||||
@@ -1673,12 +1705,12 @@ void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert
|
||||
// TODO: Check this!
|
||||
if (filesCount() > 1) {
|
||||
// Check if folders were renamed
|
||||
QStringList oldPathParts = m_torrentInfo.origFilePath(p->index).split("/");
|
||||
QStringList oldPathParts = m_torrentInfo.origFilePath(p->index).split('/');
|
||||
oldPathParts.removeLast();
|
||||
QString oldPath = oldPathParts.join("/");
|
||||
QStringList newPathParts = newName.split("/");
|
||||
QString oldPath = oldPathParts.join('/');
|
||||
QStringList newPathParts = newName.split('/');
|
||||
newPathParts.removeLast();
|
||||
QString newPath = newPathParts.join("/");
|
||||
QString newPath = newPathParts.join('/');
|
||||
if (!newPathParts.isEmpty() && (oldPath != newPath)) {
|
||||
qDebug("oldPath(%s) != newPath(%s)", qUtf8Printable(oldPath), qUtf8Printable(newPath));
|
||||
oldPath = QString("%1/%2").arg(savePath(true), oldPath);
|
||||
@@ -1687,7 +1719,9 @@ void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus();
|
||||
// We don't really need to call updateStatus() in this place.
|
||||
// All we need to do is make sure we have a valid instance of the TorrentInfo object.
|
||||
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
|
||||
|
||||
--m_renameCount;
|
||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||
@@ -1705,7 +1739,9 @@ void TorrentHandle::handleFileRenameFailedAlert(const libtorrent::file_rename_fa
|
||||
|
||||
void TorrentHandle::handleFileCompletedAlert(const libtorrent::file_completed_alert *p)
|
||||
{
|
||||
updateStatus();
|
||||
// We don't really need to call updateStatus() in this place.
|
||||
// All we need to do is make sure we have a valid instance of the TorrentInfo object.
|
||||
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
|
||||
|
||||
qDebug("A file completed download in torrent \"%s\"", qUtf8Printable(name()));
|
||||
if (m_session->isAppendExtensionEnabled()) {
|
||||
@@ -1806,6 +1842,9 @@ void TorrentHandle::handleAlert(libtorrent::alert *a)
|
||||
case libt::torrent_paused_alert::alert_type:
|
||||
handleTorrentPausedAlert(static_cast<libt::torrent_paused_alert*>(a));
|
||||
break;
|
||||
case libt::torrent_resumed_alert::alert_type:
|
||||
handleTorrentResumedAlert(static_cast<libt::torrent_resumed_alert*>(a));
|
||||
break;
|
||||
case libt::tracker_error_alert::alert_type:
|
||||
handleTrackerErrorAlert(static_cast<libt::tracker_error_alert*>(a));
|
||||
break;
|
||||
@@ -1831,7 +1870,7 @@ void TorrentHandle::manageIncompleteFiles()
|
||||
{
|
||||
const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled();
|
||||
QVector<qreal> fp = filesProgress();
|
||||
if( fp.size() != filesCount() ) {
|
||||
if (fp.size() != filesCount()) {
|
||||
qDebug() << "skip manageIncompleteFiles because of invalid torrent meta-data or empty file-progress";
|
||||
return;
|
||||
}
|
||||
@@ -1917,6 +1956,13 @@ void TorrentHandle::updateStatus(const libtorrent::torrent_status &nativeStatus)
|
||||
|
||||
updateState();
|
||||
updateTorrentInfo();
|
||||
|
||||
// NOTE: Don't change the order of these conditionals!
|
||||
// Otherwise it will not work properly since torrent can be CheckingDownloading.
|
||||
if (isChecking())
|
||||
m_unchecked = false;
|
||||
else if (isDownloading())
|
||||
m_unchecked = true;
|
||||
}
|
||||
|
||||
void TorrentHandle::setRatioLimit(qreal limit)
|
||||
@@ -1928,7 +1974,6 @@ void TorrentHandle::setRatioLimit(qreal limit)
|
||||
|
||||
if (m_ratioLimit != limit) {
|
||||
m_ratioLimit = limit;
|
||||
m_needSaveResumeData = true;
|
||||
m_session->handleTorrentShareLimitChanged(this);
|
||||
}
|
||||
}
|
||||
@@ -1942,7 +1987,6 @@ void TorrentHandle::setSeedingTimeLimit(int limit)
|
||||
|
||||
if (m_seedingTimeLimit != limit) {
|
||||
m_seedingTimeLimit = limit;
|
||||
m_needSaveResumeData = true;
|
||||
m_session->handleTorrentShareLimitChanged(this);
|
||||
}
|
||||
}
|
||||
@@ -1960,8 +2004,6 @@ void TorrentHandle::setDownloadLimit(int limit)
|
||||
void TorrentHandle::setSuperSeeding(bool enable)
|
||||
{
|
||||
m_nativeHandle.super_seeding(enable);
|
||||
if (superSeeding() != enable)
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
void TorrentHandle::flushCache()
|
||||
@@ -1980,7 +2022,7 @@ void TorrentHandle::prioritizeFiles(const QVector<int> &priorities)
|
||||
if (priorities.size() != filesCount()) return;
|
||||
|
||||
// Save first/last piece first option state
|
||||
bool firstLastPieceFirst = hasFirstLastPiecePriority();
|
||||
const bool firstLastPieceFirst = hasFirstLastPiecePriority();
|
||||
|
||||
// Reset 'm_hasSeedStatus' if needed in order to react again to
|
||||
// 'torrent_finished_alert' and eg show tray notifications
|
||||
@@ -2007,7 +2049,7 @@ void TorrentHandle::prioritizeFiles(const QVector<int> &priorities)
|
||||
// Make sure the file does not already exists
|
||||
if (QDir(parentAbsPath).dirName() != ".unwanted") {
|
||||
QString unwantedAbsPath = parentAbsPath + "/.unwanted";
|
||||
QString newAbsPath = unwantedAbsPath + "/" + Utils::Fs::fileName(filepath);
|
||||
QString newAbsPath = unwantedAbsPath + '/' + Utils::Fs::fileName(filepath);
|
||||
qDebug() << "Unwanted path is" << unwantedAbsPath;
|
||||
if (QFile::exists(newAbsPath)) {
|
||||
qWarning() << "File" << newAbsPath << "already exists at destination.";
|
||||
@@ -2027,8 +2069,8 @@ void TorrentHandle::prioritizeFiles(const QVector<int> &priorities)
|
||||
}
|
||||
#endif
|
||||
QString parentPath = Utils::Fs::branchPath(filepath);
|
||||
if (!parentPath.isEmpty() && !parentPath.endsWith("/"))
|
||||
parentPath += "/";
|
||||
if (!parentPath.isEmpty() && !parentPath.endsWith('/'))
|
||||
parentPath += '/';
|
||||
renameFile(i, parentPath + ".unwanted/" + Utils::Fs::fileName(filepath));
|
||||
}
|
||||
}
|
||||
@@ -2045,17 +2087,15 @@ void TorrentHandle::prioritizeFiles(const QVector<int> &priorities)
|
||||
renameFile(i, QDir(newRelPath).filePath(oldName));
|
||||
|
||||
// Remove .unwanted directory if empty
|
||||
qDebug() << "Attempting to remove .unwanted folder at " << QDir(spath + "/" + newRelPath).absoluteFilePath(".unwanted");
|
||||
QDir(spath + "/" + newRelPath).rmdir(".unwanted");
|
||||
qDebug() << "Attempting to remove .unwanted folder at " << QDir(spath + '/' + newRelPath).absoluteFilePath(".unwanted");
|
||||
QDir(spath + '/' + newRelPath).rmdir(".unwanted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore first/last piece first option if necessary
|
||||
if (firstLastPieceFirst)
|
||||
setFirstLastPiecePriority(true);
|
||||
|
||||
updateStatus();
|
||||
setFirstLastPiecePriorityImpl(true, priorities);
|
||||
}
|
||||
|
||||
QVector<qreal> TorrentHandle::availableFileFractions() const
|
||||
|
||||
@@ -88,10 +88,10 @@ namespace BitTorrent
|
||||
class TrackerEntry;
|
||||
struct AddTorrentParams;
|
||||
|
||||
struct AddTorrentData
|
||||
struct CreateTorrentParams
|
||||
{
|
||||
bool resumed;
|
||||
// for both new and resumed torrents
|
||||
bool restored; // is existing torrent job?
|
||||
// for both new and restored torrents
|
||||
QString name;
|
||||
QString category;
|
||||
QSet<QString> tags;
|
||||
@@ -102,18 +102,18 @@ namespace BitTorrent
|
||||
bool hasSeedStatus;
|
||||
bool skipChecking;
|
||||
bool hasRootFolder;
|
||||
bool addForced;
|
||||
bool addPaused;
|
||||
bool forced;
|
||||
bool paused;
|
||||
int uploadLimit;
|
||||
int downloadLimit;
|
||||
// for new torrents
|
||||
QVector<int> filePriorities;
|
||||
// for resumed torrents
|
||||
// for restored torrents
|
||||
qreal ratioLimit;
|
||||
int seedingTimeLimit;
|
||||
|
||||
AddTorrentData();
|
||||
AddTorrentData(const AddTorrentParams ¶ms);
|
||||
CreateTorrentParams();
|
||||
CreateTorrentParams(const AddTorrentParams ¶ms);
|
||||
};
|
||||
|
||||
struct TrackerInfo
|
||||
@@ -158,6 +158,7 @@ namespace BitTorrent
|
||||
class TorrentHandle : public QObject
|
||||
{
|
||||
Q_DISABLE_COPY(TorrentHandle)
|
||||
Q_DECLARE_TR_FUNCTIONS(BitTorrent::TorrentHandle)
|
||||
|
||||
public:
|
||||
static const qreal USE_GLOBAL_RATIO;
|
||||
@@ -170,7 +171,7 @@ namespace BitTorrent
|
||||
static const int MAX_SEEDING_TIME;
|
||||
|
||||
TorrentHandle(Session *session, const libtorrent::torrent_handle &nativeHandle,
|
||||
const AddTorrentData &data);
|
||||
const CreateTorrentParams ¶ms);
|
||||
~TorrentHandle();
|
||||
|
||||
bool isValid() const;
|
||||
@@ -372,7 +373,7 @@ namespace BitTorrent
|
||||
void handleTempPathChanged();
|
||||
void handleCategorySavePathChanged();
|
||||
void handleAppendExtensionToggled();
|
||||
void saveResumeData(bool updateStatus = false);
|
||||
void saveResumeData();
|
||||
|
||||
/**
|
||||
* @brief fraction of file pieces that are available at least from one peer
|
||||
@@ -420,6 +421,7 @@ namespace BitTorrent
|
||||
bool addTracker(const TrackerEntry &tracker);
|
||||
bool addUrlSeed(const QUrl &urlSeed);
|
||||
bool removeUrlSeed(const QUrl &urlSeed);
|
||||
void setFirstLastPiecePriorityImpl(bool enabled, const QVector<int> &updatedFilePrio = {});
|
||||
|
||||
Session *const m_session;
|
||||
libtorrent::torrent_handle m_nativeHandle;
|
||||
@@ -459,10 +461,19 @@ namespace BitTorrent
|
||||
bool m_hasMissingFiles;
|
||||
bool m_hasRootFolder;
|
||||
bool m_needsToSetFirstLastPiecePriority;
|
||||
bool m_needsToStartForced;
|
||||
|
||||
bool m_pauseAfterRecheck;
|
||||
bool m_needSaveResumeData;
|
||||
QHash<QString, TrackerInfo> m_trackerInfos;
|
||||
|
||||
enum StartupState
|
||||
{
|
||||
NotStarted,
|
||||
Starting,
|
||||
Started
|
||||
};
|
||||
|
||||
StartupState m_startupState = NotStarted;
|
||||
bool m_unchecked = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -62,46 +62,6 @@ TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
|
||||
|
||||
TorrentInfo TorrentInfo::load(const QByteArray &data, QString *error) noexcept
|
||||
{
|
||||
libt::error_code ec;
|
||||
TorrentInfo info(NativePtr(new libt::torrent_info(data.constData(), data.size(), ec)));
|
||||
if (error) {
|
||||
if (ec)
|
||||
*error = QString::fromStdString(ec.message());
|
||||
else
|
||||
error->clear();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexcept
|
||||
{
|
||||
if (error)
|
||||
error->clear();
|
||||
|
||||
QFile file {path};
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
if (error)
|
||||
*error = file.errorString();
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
const qint64 fileSizeLimit = 100 * 1024 * 1024; // 100 MB
|
||||
if (file.size() > fileSizeLimit) {
|
||||
if (error)
|
||||
*error = tr("File size exceeds max limit %1").arg(fileSizeLimit);
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
const QByteArray data = file.read(fileSizeLimit);
|
||||
if (data.size() != file.size()) {
|
||||
if (error)
|
||||
*error = tr("Torrent file read error");
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
// 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are
|
||||
// used in `torrent_info()` constructor
|
||||
const int depthLimit = 100;
|
||||
@@ -133,6 +93,45 @@ TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexc
|
||||
return info;
|
||||
}
|
||||
|
||||
TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexcept
|
||||
{
|
||||
if (error)
|
||||
error->clear();
|
||||
|
||||
QFile file {path};
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
if (error)
|
||||
*error = file.errorString();
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
const qint64 fileSizeLimit = 100 * 1024 * 1024; // 100 MB
|
||||
if (file.size() > fileSizeLimit) {
|
||||
if (error)
|
||||
*error = tr("File size exceeds max limit %1").arg(fileSizeLimit);
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
QByteArray data;
|
||||
try {
|
||||
data = file.readAll();
|
||||
}
|
||||
catch (const std::bad_alloc &e) {
|
||||
if (error)
|
||||
*error = tr("Torrent file read error: %1").arg(e.what());
|
||||
return TorrentInfo();
|
||||
}
|
||||
if (data.size() != file.size()) {
|
||||
if (error)
|
||||
*error = tr("Torrent file read error: size mismatch");
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
return load(data, error);
|
||||
}
|
||||
|
||||
bool TorrentInfo::isValid() const
|
||||
{
|
||||
return (m_nativeInfo && m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
|
||||
@@ -340,12 +339,12 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(int fileIndex) const
|
||||
|
||||
const libt::file_storage &files = nativeInfo()->files();
|
||||
const auto fileSize = files.file_size(fileIndex);
|
||||
const auto firstOffset = files.file_offset(fileIndex);
|
||||
return makeInterval(static_cast<int>(firstOffset / pieceLength()),
|
||||
static_cast<int>((firstOffset + fileSize - 1) / pieceLength()));
|
||||
const auto fileOffset = files.file_offset(fileIndex);
|
||||
return makeInterval(static_cast<int>(fileOffset / pieceLength()),
|
||||
static_cast<int>((fileOffset + fileSize - 1) / pieceLength()));
|
||||
}
|
||||
|
||||
void TorrentInfo::renameFile(uint index, const QString &newPath)
|
||||
void TorrentInfo::renameFile(const int index, const QString &newPath)
|
||||
{
|
||||
if (!isValid()) return;
|
||||
nativeInfo()->rename_file(index, Utils::Fs::toNativePath(newPath).toStdString());
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace BitTorrent
|
||||
PieceRange filePieces(const QString &file) const;
|
||||
PieceRange filePieces(int fileIndex) const;
|
||||
|
||||
void renameFile(uint index, const QString &newPath);
|
||||
void renameFile(int index, const QString &newPath);
|
||||
|
||||
QString rootFolder() const;
|
||||
bool hasRootFolder() const;
|
||||
|
||||
@@ -60,7 +60,7 @@ bool Peer::operator==(const Peer &other) const
|
||||
|
||||
QString Peer::uid() const
|
||||
{
|
||||
return ip + ":" + QString::number(port);
|
||||
return ip + ':' + QString::number(port);
|
||||
}
|
||||
|
||||
libtorrent::entry Peer::toEntry(bool noPeerId) const
|
||||
|
||||
@@ -26,11 +26,12 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "trackerentry.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "trackerentry.h"
|
||||
|
||||
using namespace BitTorrent;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
#include <cstring>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/param.h>
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "base/bittorrent/magneturi.h"
|
||||
#include "base/bittorrent/torrentinfo.h"
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/fs.h"
|
||||
|
||||
@@ -57,18 +58,14 @@ FileSystemWatcher::FileSystemWatcher(QObject *parent)
|
||||
m_partialTorrentTimer.setSingleShot(true);
|
||||
connect(&m_partialTorrentTimer, &QTimer::timeout, this, &FileSystemWatcher::processPartialTorrents);
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
connect(&m_watchTimer, &QTimer::timeout, this, &FileSystemWatcher::scanNetworkFolders);
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList FileSystemWatcher::directories() const
|
||||
{
|
||||
QStringList dirs = QFileSystemWatcher::directories();
|
||||
#ifndef Q_OS_WIN
|
||||
for (const QDir &dir : qAsConst(m_watchedFolders))
|
||||
dirs << dir.canonicalPath();
|
||||
#endif
|
||||
return dirs;
|
||||
}
|
||||
|
||||
@@ -76,15 +73,14 @@ void FileSystemWatcher::addPath(const QString &path)
|
||||
{
|
||||
if (path.isEmpty()) return;
|
||||
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
#if !defined Q_OS_HAIKU
|
||||
const QDir dir(path);
|
||||
if (!dir.exists()) return;
|
||||
|
||||
// Check if the path points to a network file system or not
|
||||
if (Utils::Fs::isNetworkFileSystem(path)) {
|
||||
// Network mode
|
||||
qDebug("Network folder detected: %s", qUtf8Printable(path));
|
||||
qDebug("Using file polling mode instead of inotify...");
|
||||
LogMsg(tr("Watching remote folder: \"%1\"").arg(Utils::Fs::toNativePath(path)));
|
||||
m_watchedFolders << dir;
|
||||
|
||||
m_watchTimer.start(WATCH_INTERVAL);
|
||||
@@ -93,20 +89,19 @@ void FileSystemWatcher::addPath(const QString &path)
|
||||
#endif
|
||||
|
||||
// Normal mode
|
||||
qDebug("FS Watcher is watching %s in normal mode", qUtf8Printable(path));
|
||||
LogMsg(tr("Watching local folder: \"%1\"").arg(Utils::Fs::toNativePath(path)));
|
||||
QFileSystemWatcher::addPath(path);
|
||||
scanLocalFolder(path);
|
||||
}
|
||||
|
||||
void FileSystemWatcher::removePath(const QString &path)
|
||||
{
|
||||
#ifndef Q_OS_WIN
|
||||
if (m_watchedFolders.removeOne(path)) {
|
||||
if (m_watchedFolders.isEmpty())
|
||||
m_watchTimer.stop();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Normal mode
|
||||
QFileSystemWatcher::removePath(path);
|
||||
}
|
||||
@@ -116,13 +111,11 @@ void FileSystemWatcher::scanLocalFolder(const QString &path)
|
||||
QTimer::singleShot(2000, this, [this, path]() { processTorrentsInDir(path); });
|
||||
}
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
void FileSystemWatcher::scanNetworkFolders()
|
||||
{
|
||||
for (const QDir &dir : qAsConst(m_watchedFolders))
|
||||
processTorrentsInDir(dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
void FileSystemWatcher::processPartialTorrents()
|
||||
{
|
||||
|
||||
@@ -56,9 +56,7 @@ signals:
|
||||
protected slots:
|
||||
void scanLocalFolder(const QString &path);
|
||||
void processPartialTorrents();
|
||||
#ifndef Q_OS_WIN
|
||||
void scanNetworkFolders();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void processTorrentsInDir(const QDir &dir);
|
||||
@@ -67,10 +65,8 @@ private:
|
||||
QHash<QString, int> m_partialTorrents;
|
||||
QTimer m_partialTorrentTimer;
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
QList<QDir> m_watchedFolders;
|
||||
QTimer m_watchTimer;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // FILESYSTEMWATCHER_H
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
#include <QRegExp>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include "base/logger.h"
|
||||
|
||||
@@ -63,10 +63,12 @@ namespace Http
|
||||
const char HEADER_REQUEST_METHOD_POST[] = "POST";
|
||||
|
||||
const char CONTENT_TYPE_HTML[] = "text/html";
|
||||
const char CONTENT_TYPE_CSS[] = "text/css";
|
||||
const char CONTENT_TYPE_TXT[] = "text/plain";
|
||||
const char CONTENT_TYPE_JS[] = "application/javascript";
|
||||
const char CONTENT_TYPE_JSON[] = "application/json";
|
||||
const char CONTENT_TYPE_GIF[] = "image/gif";
|
||||
const char CONTENT_TYPE_PNG[] = "image/png";
|
||||
const char CONTENT_TYPE_TXT[] = "text/plain";
|
||||
const char CONTENT_TYPE_FORM_ENCODED[] = "application/x-www-form-urlencoded";
|
||||
const char CONTENT_TYPE_FORM_DATA[] = "multipart/form-data";
|
||||
|
||||
|
||||
@@ -27,9 +27,10 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <QString>
|
||||
#include "iconprovider.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
IconProvider::IconProvider(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
@@ -56,9 +57,15 @@ IconProvider *IconProvider::instance()
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
QString IconProvider::getIconPath(const QString &iconId)
|
||||
QString IconProvider::getIconPath(const QString &iconId) const
|
||||
{
|
||||
return ":/icons/qbt-theme/" + iconId + ".png";
|
||||
// there are a few icons not available in svg
|
||||
const QString pathSvg = ":/icons/qbt-theme/" + iconId + ".svg";
|
||||
if (QFileInfo::exists(pathSvg))
|
||||
return pathSvg;
|
||||
|
||||
const QString pathPng = ":/icons/qbt-theme/" + iconId + ".png";
|
||||
return pathPng;
|
||||
}
|
||||
|
||||
IconProvider *IconProvider::m_instance = nullptr;
|
||||
|
||||
@@ -31,8 +31,7 @@
|
||||
#define ICONPROVIDER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QString;
|
||||
#include <QString>
|
||||
|
||||
class IconProvider : public QObject
|
||||
{
|
||||
@@ -43,7 +42,7 @@ public:
|
||||
static void freeInstance();
|
||||
static IconProvider *instance();
|
||||
|
||||
virtual QString getIconPath(const QString &iconId);
|
||||
virtual QString getIconPath(const QString &iconId) const;
|
||||
|
||||
protected:
|
||||
explicit IconProvider(QObject *parent = nullptr);
|
||||
|
||||
@@ -38,30 +38,30 @@ class IndexInterval
|
||||
public:
|
||||
using IndexType = Index;
|
||||
|
||||
IndexInterval(IndexType first, IndexType last)
|
||||
IndexInterval(IndexType first, IndexType last) // add constexpr when using C++14
|
||||
: m_first {first}
|
||||
, m_last {last}
|
||||
{
|
||||
Q_ASSERT(first <= last);
|
||||
}
|
||||
|
||||
IndexType first() const
|
||||
constexpr IndexType first() const
|
||||
{
|
||||
return m_first;
|
||||
}
|
||||
|
||||
IndexType last() const
|
||||
constexpr IndexType last() const
|
||||
{
|
||||
return m_last;
|
||||
}
|
||||
|
||||
private:
|
||||
IndexType m_first;
|
||||
IndexType m_last;
|
||||
const IndexType m_first;
|
||||
const IndexType m_last;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline IndexInterval<T> makeInterval(T first, T last)
|
||||
constexpr IndexInterval<T> makeInterval(T first, T last)
|
||||
{
|
||||
return {first, last};
|
||||
}
|
||||
@@ -99,7 +99,7 @@ public:
|
||||
|
||||
constexpr IndexType end() const
|
||||
{
|
||||
return m_first + m_size;
|
||||
return (m_first + m_size);
|
||||
}
|
||||
|
||||
constexpr IndexDiffType size() const
|
||||
@@ -114,12 +114,12 @@ public:
|
||||
|
||||
constexpr IndexType last() const
|
||||
{
|
||||
return m_first + m_size - 1;
|
||||
return (m_first + m_size - 1);
|
||||
}
|
||||
|
||||
constexpr bool isEmpty() const
|
||||
{
|
||||
return m_size == 0;
|
||||
return (m_size == 0);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -26,15 +26,15 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "dnsupdater.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QRegExp>
|
||||
#include <QStringList>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "base/net/downloadhandler.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "dnsupdater.h"
|
||||
|
||||
using namespace Net;
|
||||
|
||||
@@ -74,9 +74,8 @@ void DNSUpdater::checkPublicIP()
|
||||
{
|
||||
Q_ASSERT(m_state == OK);
|
||||
|
||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
|
||||
"http://checkip.dyndns.org", false, 0, false,
|
||||
"qBittorrent/" QBT_VERSION_2);
|
||||
DownloadHandler *handler = DownloadManager::instance()->download(
|
||||
DownloadRequest("http://checkip.dyndns.org").userAgent("qBittorrent/" QBT_VERSION_2));
|
||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
|
||||
, this, &DNSUpdater::ipRequestFinished);
|
||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &DNSUpdater::ipRequestFailed);
|
||||
@@ -89,9 +88,9 @@ void DNSUpdater::ipRequestFinished(const QString &url, const QByteArray &data)
|
||||
Q_UNUSED(url);
|
||||
|
||||
// Parse response
|
||||
QRegExp ipregex("Current IP Address:\\s+([^<]+)</body>");
|
||||
if (ipregex.indexIn(data) >= 0) {
|
||||
QString ipStr = ipregex.cap(1);
|
||||
const QRegularExpressionMatch ipRegexMatch = QRegularExpression("Current IP Address:\\s+([^<]+)</body>").match(data);
|
||||
if (ipRegexMatch.hasMatch()) {
|
||||
QString ipStr = ipRegexMatch.captured(1);
|
||||
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr;
|
||||
QHostAddress newIp(ipStr);
|
||||
if (!newIp.isNull()) {
|
||||
@@ -122,9 +121,8 @@ void DNSUpdater::updateDNSService()
|
||||
qDebug() << Q_FUNC_INFO;
|
||||
|
||||
m_lastIPCheckTime = QDateTime::currentDateTime();
|
||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
|
||||
getUpdateUrl(), false, 0, false,
|
||||
"qBittorrent/" QBT_VERSION_2);
|
||||
DownloadHandler *handler = DownloadManager::instance()->download(
|
||||
DownloadRequest(getUpdateUrl()).userAgent("qBittorrent/" QBT_VERSION_2));
|
||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
|
||||
, this, &DNSUpdater::ipUpdateFinished);
|
||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &DNSUpdater::ipUpdateFailed);
|
||||
@@ -183,7 +181,7 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
|
||||
{
|
||||
Logger *const logger = Logger::instance();
|
||||
qDebug() << Q_FUNC_INFO << reply;
|
||||
QString code = reply.split(" ").first();
|
||||
QString code = reply.split(' ').first();
|
||||
qDebug() << Q_FUNC_INFO << "Code:" << code;
|
||||
|
||||
if ((code == "good") || (code == "nochg")) {
|
||||
@@ -246,8 +244,8 @@ void DNSUpdater::updateCredentials()
|
||||
}
|
||||
if (m_domain != pref->getDynDomainName()) {
|
||||
m_domain = pref->getDynDomainName();
|
||||
QRegExp domain_regex("^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$");
|
||||
if (domain_regex.indexIn(m_domain) < 0) {
|
||||
const QRegularExpressionMatch domainRegexMatch = QRegularExpression("^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$").match(m_domain);
|
||||
if (!domainRegexMatch.hasMatch()) {
|
||||
logger->addMessage(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL);
|
||||
m_lastIP.clear();
|
||||
m_ipCheckTimer.stop();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -29,11 +29,11 @@
|
||||
|
||||
#include "downloadhandler.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkCookie>
|
||||
#include <QNetworkProxy>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QTemporaryFile>
|
||||
#include <QUrl>
|
||||
@@ -43,35 +43,57 @@
|
||||
#include "base/utils/misc.h"
|
||||
#include "downloadmanager.h"
|
||||
|
||||
static QString errorCodeToString(QNetworkReply::NetworkError status);
|
||||
namespace
|
||||
{
|
||||
bool saveToFile(const QByteArray &replyData, QString &filePath)
|
||||
{
|
||||
QTemporaryFile tmpfile {Utils::Fs::tempPath() + "XXXXXX"};
|
||||
tmpfile.setAutoRemove(false);
|
||||
|
||||
using namespace Net;
|
||||
if (!tmpfile.open())
|
||||
return false;
|
||||
|
||||
DownloadHandler::DownloadHandler(QNetworkReply *reply, DownloadManager *manager, bool saveToFile, qint64 limit, bool handleRedirectToMagnet)
|
||||
filePath = tmpfile.fileName();
|
||||
|
||||
tmpfile.write(replyData);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Net::DownloadHandler::DownloadHandler(QNetworkReply *reply, DownloadManager *manager, const DownloadRequest &downloadRequest)
|
||||
: QObject(manager)
|
||||
, m_reply(reply)
|
||||
, m_manager(manager)
|
||||
, m_saveToFile(saveToFile)
|
||||
, m_sizeLimit(limit)
|
||||
, m_handleRedirectToMagnet(handleRedirectToMagnet)
|
||||
, m_url(reply->url().toString())
|
||||
, m_downloadRequest(downloadRequest)
|
||||
{
|
||||
init();
|
||||
if (reply)
|
||||
assignNetworkReply(reply);
|
||||
}
|
||||
|
||||
DownloadHandler::~DownloadHandler()
|
||||
Net::DownloadHandler::~DownloadHandler()
|
||||
{
|
||||
if (m_reply)
|
||||
delete m_reply;
|
||||
}
|
||||
|
||||
// Returns original url
|
||||
QString DownloadHandler::url() const
|
||||
void Net::DownloadHandler::assignNetworkReply(QNetworkReply *reply)
|
||||
{
|
||||
return m_url;
|
||||
Q_ASSERT(reply);
|
||||
|
||||
m_reply = reply;
|
||||
m_reply->setParent(this);
|
||||
if (m_downloadRequest.limit() > 0)
|
||||
connect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize);
|
||||
connect(m_reply, &QNetworkReply::finished, this, &Net::DownloadHandler::processFinishedDownload);
|
||||
}
|
||||
|
||||
void DownloadHandler::processFinishedDownload()
|
||||
// Returns original url
|
||||
QString Net::DownloadHandler::url() const
|
||||
{
|
||||
return m_downloadRequest.url();
|
||||
}
|
||||
|
||||
void Net::DownloadHandler::processFinishedDownload()
|
||||
{
|
||||
QString url = m_reply->url().toString();
|
||||
qDebug("Download finished: %s", qUtf8Printable(url));
|
||||
@@ -79,7 +101,7 @@ void DownloadHandler::processFinishedDownload()
|
||||
if (m_reply->error() != QNetworkReply::NoError) {
|
||||
// Failure
|
||||
qDebug("Download failure (%s), reason: %s", qUtf8Printable(url), qUtf8Printable(errorCodeToString(m_reply->error())));
|
||||
emit downloadFailed(m_url, errorCodeToString(m_reply->error()));
|
||||
emit downloadFailed(m_downloadRequest.url(), errorCodeToString(m_reply->error()));
|
||||
this->deleteLater();
|
||||
}
|
||||
else {
|
||||
@@ -97,15 +119,15 @@ void DownloadHandler::processFinishedDownload()
|
||||
replyData = Utils::Gzip::decompress(replyData);
|
||||
}
|
||||
|
||||
if (m_saveToFile) {
|
||||
if (m_downloadRequest.saveToFile()) {
|
||||
QString filePath;
|
||||
if (saveToFile(replyData, filePath))
|
||||
emit downloadFinished(m_url, filePath);
|
||||
emit downloadFinished(m_downloadRequest.url(), filePath);
|
||||
else
|
||||
emit downloadFailed(m_url, tr("I/O Error"));
|
||||
}
|
||||
emit downloadFailed(m_downloadRequest.url(), tr("I/O Error"));
|
||||
}
|
||||
else {
|
||||
emit downloadFinished(m_url, replyData);
|
||||
emit downloadFinished(m_downloadRequest.url(), replyData);
|
||||
}
|
||||
|
||||
this->deleteLater();
|
||||
@@ -113,137 +135,116 @@ void DownloadHandler::processFinishedDownload()
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadHandler::checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal)
|
||||
void Net::DownloadHandler::checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal)
|
||||
{
|
||||
QString msg = tr("The file size is %1. It exceeds the download limit of %2.");
|
||||
|
||||
if (bytesTotal > 0) {
|
||||
// Total number of bytes is available
|
||||
if (bytesTotal > m_sizeLimit) {
|
||||
if (bytesTotal > m_downloadRequest.limit()) {
|
||||
m_reply->abort();
|
||||
emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesTotal), Utils::Misc::friendlyUnit(m_sizeLimit)));
|
||||
emit downloadFailed(m_downloadRequest.url(), msg.arg(Utils::Misc::friendlyUnit(bytesTotal), Utils::Misc::friendlyUnit(m_downloadRequest.limit())));
|
||||
}
|
||||
else {
|
||||
disconnect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize);
|
||||
}
|
||||
}
|
||||
else if (bytesReceived > m_sizeLimit) {
|
||||
else if (bytesReceived > m_downloadRequest.limit()) {
|
||||
m_reply->abort();
|
||||
emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesReceived), Utils::Misc::friendlyUnit(m_sizeLimit)));
|
||||
emit downloadFailed(m_downloadRequest.url(), msg.arg(Utils::Misc::friendlyUnit(bytesReceived), Utils::Misc::friendlyUnit(m_downloadRequest.limit())));
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadHandler::init()
|
||||
{
|
||||
m_reply->setParent(this);
|
||||
if (m_sizeLimit > 0)
|
||||
connect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize);
|
||||
connect(m_reply, &QNetworkReply::finished, this, &Net::DownloadHandler::processFinishedDownload);
|
||||
}
|
||||
|
||||
bool DownloadHandler::saveToFile(const QByteArray &replyData, QString &filePath)
|
||||
{
|
||||
QTemporaryFile *tmpfile = new QTemporaryFile(Utils::Fs::tempPath() + "XXXXXX");
|
||||
if (!tmpfile->open()) {
|
||||
delete tmpfile;
|
||||
return false;
|
||||
}
|
||||
|
||||
tmpfile->setAutoRemove(false);
|
||||
filePath = tmpfile->fileName();
|
||||
qDebug("Temporary filename is: %s", qUtf8Printable(filePath));
|
||||
if (m_reply->isOpen() || m_reply->open(QIODevice::ReadOnly)) {
|
||||
tmpfile->write(replyData);
|
||||
tmpfile->close();
|
||||
// XXX: tmpfile needs to be deleted on Windows before using the file
|
||||
// or it will complain that the file is used by another process.
|
||||
delete tmpfile;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
delete tmpfile;
|
||||
Utils::Fs::forceRemove(filePath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void DownloadHandler::handleRedirection(QUrl newUrl)
|
||||
void Net::DownloadHandler::handleRedirection(QUrl newUrl)
|
||||
{
|
||||
// Resolve relative urls
|
||||
if (newUrl.isRelative())
|
||||
newUrl = m_reply->url().resolved(newUrl);
|
||||
|
||||
const QString newUrlString = newUrl.toString();
|
||||
qDebug("Redirecting from %s to %s", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString));
|
||||
qDebug("Redirecting from %s to %s...", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString));
|
||||
|
||||
// Redirect to magnet workaround
|
||||
if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive)) {
|
||||
qDebug("Magnet redirect detected.");
|
||||
m_reply->abort();
|
||||
if (m_handleRedirectToMagnet)
|
||||
emit redirectedToMagnet(m_url, newUrlString);
|
||||
if (m_downloadRequest.handleRedirectToMagnet())
|
||||
emit redirectedToMagnet(m_downloadRequest.url(), newUrlString);
|
||||
else
|
||||
emit downloadFailed(m_url, tr("Unexpected redirect to magnet URI."));
|
||||
emit downloadFailed(m_downloadRequest.url(), tr("Unexpected redirect to magnet URI."));
|
||||
|
||||
this->deleteLater();
|
||||
}
|
||||
else {
|
||||
DownloadHandler *tmp = m_manager->downloadUrl(newUrlString, m_saveToFile, m_sizeLimit, m_handleRedirectToMagnet);
|
||||
m_reply->deleteLater();
|
||||
m_reply = tmp->m_reply;
|
||||
init();
|
||||
tmp->m_reply = nullptr;
|
||||
delete tmp;
|
||||
DownloadHandler *redirected = m_manager->download(DownloadRequest(m_downloadRequest).url(newUrlString));
|
||||
connect(redirected, &DownloadHandler::destroyed, this, &DownloadHandler::deleteLater);
|
||||
connect(redirected, &DownloadHandler::downloadFailed, this, [this](const QString &, const QString &reason)
|
||||
{
|
||||
emit downloadFailed(url(), reason);
|
||||
});
|
||||
connect(redirected, &DownloadHandler::redirectedToMagnet, this, [this](const QString &, const QString &magnetUri)
|
||||
{
|
||||
emit redirectedToMagnet(url(), magnetUri);
|
||||
});
|
||||
connect(redirected, static_cast<void (DownloadHandler::*)(const QString &, const QString &)>(&DownloadHandler::downloadFinished)
|
||||
, this, [this](const QString &, const QString &fileName)
|
||||
{
|
||||
emit downloadFinished(url(), fileName);
|
||||
});
|
||||
connect(redirected, static_cast<void (DownloadHandler::*)(const QString &, const QByteArray &)>(&DownloadHandler::downloadFinished)
|
||||
, this, [this](const QString &, const QByteArray &data)
|
||||
{
|
||||
emit downloadFinished(url(), data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QString errorCodeToString(QNetworkReply::NetworkError status)
|
||||
QString Net::DownloadHandler::errorCodeToString(const QNetworkReply::NetworkError status)
|
||||
{
|
||||
switch (status) {
|
||||
case QNetworkReply::HostNotFoundError:
|
||||
return QObject::tr("The remote host name was not found (invalid hostname)");
|
||||
return tr("The remote host name was not found (invalid hostname)");
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
return QObject::tr("The operation was canceled");
|
||||
return tr("The operation was canceled");
|
||||
case QNetworkReply::RemoteHostClosedError:
|
||||
return QObject::tr("The remote server closed the connection prematurely, before the entire reply was received and processed");
|
||||
return tr("The remote server closed the connection prematurely, before the entire reply was received and processed");
|
||||
case QNetworkReply::TimeoutError:
|
||||
return QObject::tr("The connection to the remote server timed out");
|
||||
return tr("The connection to the remote server timed out");
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
return QObject::tr("SSL/TLS handshake failed");
|
||||
return tr("SSL/TLS handshake failed");
|
||||
case QNetworkReply::ConnectionRefusedError:
|
||||
return QObject::tr("The remote server refused the connection");
|
||||
return tr("The remote server refused the connection");
|
||||
case QNetworkReply::ProxyConnectionRefusedError:
|
||||
return QObject::tr("The connection to the proxy server was refused");
|
||||
return tr("The connection to the proxy server was refused");
|
||||
case QNetworkReply::ProxyConnectionClosedError:
|
||||
return QObject::tr("The proxy server closed the connection prematurely");
|
||||
return tr("The proxy server closed the connection prematurely");
|
||||
case QNetworkReply::ProxyNotFoundError:
|
||||
return QObject::tr("The proxy host name was not found");
|
||||
return tr("The proxy host name was not found");
|
||||
case QNetworkReply::ProxyTimeoutError:
|
||||
return QObject::tr("The connection to the proxy timed out or the proxy did not reply in time to the request sent");
|
||||
return tr("The connection to the proxy timed out or the proxy did not reply in time to the request sent");
|
||||
case QNetworkReply::ProxyAuthenticationRequiredError:
|
||||
return QObject::tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered");
|
||||
return tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered");
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
return QObject::tr("The access to the remote content was denied (401)");
|
||||
return tr("The access to the remote content was denied (401)");
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
return QObject::tr("The operation requested on the remote content is not permitted");
|
||||
return tr("The operation requested on the remote content is not permitted");
|
||||
case QNetworkReply::ContentNotFoundError:
|
||||
return QObject::tr("The remote content was not found at the server (404)");
|
||||
return tr("The remote content was not found at the server (404)");
|
||||
case QNetworkReply::AuthenticationRequiredError:
|
||||
return QObject::tr("The remote server requires authentication to serve the content but the credentials provided were not accepted");
|
||||
return tr("The remote server requires authentication to serve the content but the credentials provided were not accepted");
|
||||
case QNetworkReply::ProtocolUnknownError:
|
||||
return QObject::tr("The Network Access API cannot honor the request because the protocol is not known");
|
||||
return tr("The Network Access API cannot honor the request because the protocol is not known");
|
||||
case QNetworkReply::ProtocolInvalidOperationError:
|
||||
return QObject::tr("The requested operation is invalid for this protocol");
|
||||
return tr("The requested operation is invalid for this protocol");
|
||||
case QNetworkReply::UnknownNetworkError:
|
||||
return QObject::tr("An unknown network-related error was detected");
|
||||
return tr("An unknown network-related error was detected");
|
||||
case QNetworkReply::UnknownProxyError:
|
||||
return QObject::tr("An unknown proxy-related error was detected");
|
||||
return tr("An unknown proxy-related error was detected");
|
||||
case QNetworkReply::UnknownContentError:
|
||||
return QObject::tr("An unknown error related to the remote content was detected");
|
||||
return tr("An unknown error related to the remote content was detected");
|
||||
case QNetworkReply::ProtocolFailure:
|
||||
return QObject::tr("A breakdown in protocol was detected");
|
||||
return tr("A breakdown in protocol was detected");
|
||||
default:
|
||||
return QObject::tr("Unknown error");
|
||||
return tr("Unknown error");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -30,10 +30,11 @@
|
||||
#ifndef NET_DOWNLOADHANDLER_H
|
||||
#define NET_DOWNLOADHANDLER_H
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
#include "downloadmanager.h"
|
||||
|
||||
class QUrl;
|
||||
|
||||
namespace Net
|
||||
@@ -43,10 +44,14 @@ namespace Net
|
||||
class DownloadHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(DownloadHandler)
|
||||
|
||||
friend class DownloadManager;
|
||||
|
||||
DownloadHandler(QNetworkReply *reply, DownloadManager *manager, const DownloadRequest &downloadRequest);
|
||||
|
||||
public:
|
||||
DownloadHandler(QNetworkReply *reply, DownloadManager *manager, bool saveToFile = false, qint64 limit = 0, bool handleRedirectToMagnet = false);
|
||||
~DownloadHandler();
|
||||
~DownloadHandler() override;
|
||||
|
||||
QString url() const;
|
||||
|
||||
@@ -61,16 +66,14 @@ namespace Net
|
||||
void checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal);
|
||||
|
||||
private:
|
||||
void init();
|
||||
bool saveToFile(const QByteArray &replyData, QString &filePath);
|
||||
void assignNetworkReply(QNetworkReply *reply);
|
||||
void handleRedirection(QUrl newUrl);
|
||||
|
||||
static QString errorCodeToString(QNetworkReply::NetworkError status);
|
||||
|
||||
QNetworkReply *m_reply;
|
||||
DownloadManager *m_manager;
|
||||
bool m_saveToFile;
|
||||
qint64 m_sizeLimit;
|
||||
bool m_handleRedirectToMagnet;
|
||||
QString m_url;
|
||||
const DownloadRequest m_downloadRequest;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -64,7 +64,7 @@ namespace
|
||||
setAllCookies(cookies);
|
||||
}
|
||||
|
||||
~NetworkCookieJar()
|
||||
~NetworkCookieJar() override
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QList<QNetworkCookie> cookies = allCookies();
|
||||
@@ -103,28 +103,47 @@ namespace
|
||||
return QNetworkCookieJar::setCookiesFromUrl(cookies, url);
|
||||
}
|
||||
};
|
||||
|
||||
QNetworkRequest createNetworkRequest(const Net::DownloadRequest &downloadRequest)
|
||||
{
|
||||
QNetworkRequest request {downloadRequest.url()};
|
||||
|
||||
if (downloadRequest.userAgent().isEmpty())
|
||||
request.setRawHeader("User-Agent", DEFAULT_USER_AGENT);
|
||||
else
|
||||
request.setRawHeader("User-Agent", downloadRequest.userAgent().toUtf8());
|
||||
|
||||
// Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents
|
||||
request.setRawHeader("Referer", request.url().toEncoded().data());
|
||||
// Accept gzip
|
||||
request.setRawHeader("Accept-Encoding", "gzip");
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
using namespace Net;
|
||||
Net::DownloadManager *Net::DownloadManager::m_instance = nullptr;
|
||||
|
||||
DownloadManager *DownloadManager::m_instance = nullptr;
|
||||
|
||||
DownloadManager::DownloadManager(QObject *parent)
|
||||
Net::DownloadManager::DownloadManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
#ifndef QT_NO_OPENSSL
|
||||
connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this, &Net::DownloadManager::ignoreSslErrors);
|
||||
#endif
|
||||
connect(&m_networkManager, &QNetworkAccessManager::finished, this, &DownloadManager::handleReplyFinished);
|
||||
connect(ProxyConfigurationManager::instance(), &ProxyConfigurationManager::proxyConfigurationChanged
|
||||
, this, &DownloadManager::applyProxySettings);
|
||||
m_networkManager.setCookieJar(new NetworkCookieJar(this));
|
||||
applyProxySettings();
|
||||
}
|
||||
|
||||
void DownloadManager::initInstance()
|
||||
void Net::DownloadManager::initInstance()
|
||||
{
|
||||
if (!m_instance)
|
||||
m_instance = new DownloadManager;
|
||||
}
|
||||
|
||||
void DownloadManager::freeInstance()
|
||||
void Net::DownloadManager::freeInstance()
|
||||
{
|
||||
if (m_instance) {
|
||||
delete m_instance;
|
||||
@@ -132,62 +151,65 @@ void DownloadManager::freeInstance()
|
||||
}
|
||||
}
|
||||
|
||||
DownloadManager *DownloadManager::instance()
|
||||
Net::DownloadManager *Net::DownloadManager::instance()
|
||||
{
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
DownloadHandler *DownloadManager::downloadUrl(const QString &url, bool saveToFile, qint64 limit, bool handleRedirectToMagnet, const QString &userAgent)
|
||||
Net::DownloadHandler *Net::DownloadManager::download(const DownloadRequest &downloadRequest)
|
||||
{
|
||||
// Update proxy settings
|
||||
applyProxySettings();
|
||||
|
||||
// Process download request
|
||||
qDebug("url is %s", qUtf8Printable(url));
|
||||
const QUrl qurl = QUrl(url);
|
||||
QNetworkRequest request(qurl);
|
||||
const QNetworkRequest request = createNetworkRequest(downloadRequest);
|
||||
const ServiceID id = ServiceID::fromURL(request.url());
|
||||
const bool isSequentialService = m_sequentialServices.contains(id);
|
||||
if (!isSequentialService || !m_busyServices.contains(id)) {
|
||||
qDebug("Downloading %s...", qUtf8Printable(downloadRequest.url()));
|
||||
if (isSequentialService)
|
||||
m_busyServices.insert(id);
|
||||
return new DownloadHandler {
|
||||
m_networkManager.get(request), this, downloadRequest};
|
||||
}
|
||||
|
||||
if (userAgent.isEmpty())
|
||||
request.setRawHeader("User-Agent", DEFAULT_USER_AGENT);
|
||||
else
|
||||
request.setRawHeader("User-Agent", userAgent.toUtf8());
|
||||
|
||||
// Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents
|
||||
request.setRawHeader("Referer", request.url().toEncoded().data());
|
||||
|
||||
qDebug("Downloading %s...", request.url().toEncoded().data());
|
||||
qDebug() << "Cookies:" << m_networkManager.cookieJar()->cookiesForUrl(request.url());
|
||||
// accept gzip
|
||||
request.setRawHeader("Accept-Encoding", "gzip");
|
||||
return new DownloadHandler(m_networkManager.get(request), this, saveToFile, limit, handleRedirectToMagnet);
|
||||
auto *downloadHandler = new DownloadHandler {nullptr, this, downloadRequest};
|
||||
connect(downloadHandler, &DownloadHandler::destroyed, this, [this, id, downloadHandler]()
|
||||
{
|
||||
m_waitingJobs[id].removeOne(downloadHandler);
|
||||
});
|
||||
m_waitingJobs[id].enqueue(downloadHandler);
|
||||
return downloadHandler;
|
||||
}
|
||||
|
||||
QList<QNetworkCookie> DownloadManager::cookiesForUrl(const QUrl &url) const
|
||||
void Net::DownloadManager::registerSequentialService(const Net::ServiceID &serviceID)
|
||||
{
|
||||
m_sequentialServices.insert(serviceID);
|
||||
}
|
||||
|
||||
QList<QNetworkCookie> Net::DownloadManager::cookiesForUrl(const QUrl &url) const
|
||||
{
|
||||
return m_networkManager.cookieJar()->cookiesForUrl(url);
|
||||
}
|
||||
|
||||
bool DownloadManager::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
|
||||
bool Net::DownloadManager::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
|
||||
{
|
||||
return m_networkManager.cookieJar()->setCookiesFromUrl(cookieList, url);
|
||||
}
|
||||
|
||||
QList<QNetworkCookie> DownloadManager::allCookies() const
|
||||
QList<QNetworkCookie> Net::DownloadManager::allCookies() const
|
||||
{
|
||||
return static_cast<NetworkCookieJar *>(m_networkManager.cookieJar())->allCookies();
|
||||
}
|
||||
|
||||
void DownloadManager::setAllCookies(const QList<QNetworkCookie> &cookieList)
|
||||
void Net::DownloadManager::setAllCookies(const QList<QNetworkCookie> &cookieList)
|
||||
{
|
||||
static_cast<NetworkCookieJar *>(m_networkManager.cookieJar())->setAllCookies(cookieList);
|
||||
}
|
||||
|
||||
bool DownloadManager::deleteCookie(const QNetworkCookie &cookie)
|
||||
bool Net::DownloadManager::deleteCookie(const QNetworkCookie &cookie)
|
||||
{
|
||||
return static_cast<NetworkCookieJar *>(m_networkManager.cookieJar())->deleteCookie(cookie);
|
||||
}
|
||||
|
||||
void DownloadManager::applyProxySettings()
|
||||
void Net::DownloadManager::applyProxySettings()
|
||||
{
|
||||
auto proxyManager = ProxyConfigurationManager::instance();
|
||||
ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
|
||||
@@ -208,7 +230,7 @@ void DownloadManager::applyProxySettings()
|
||||
}
|
||||
// Authentication?
|
||||
if (proxyManager->isAuthenticationRequired()) {
|
||||
qDebug("Proxy requires authentication, authenticating");
|
||||
qDebug("Proxy requires authentication, authenticating...");
|
||||
proxy.setUser(proxyConfig.username);
|
||||
proxy.setPassword(proxyConfig.password);
|
||||
}
|
||||
@@ -220,11 +242,101 @@ void DownloadManager::applyProxySettings()
|
||||
m_networkManager.setProxy(proxy);
|
||||
}
|
||||
|
||||
void Net::DownloadManager::handleReplyFinished(QNetworkReply *reply)
|
||||
{
|
||||
const ServiceID id = ServiceID::fromURL(reply->url());
|
||||
auto waitingJobsIter = m_waitingJobs.find(id);
|
||||
if ((waitingJobsIter == m_waitingJobs.end()) || waitingJobsIter.value().isEmpty()) {
|
||||
m_busyServices.remove(id);
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadHandler *handler = waitingJobsIter.value().dequeue();
|
||||
qDebug("Downloading %s...", qUtf8Printable(handler->m_downloadRequest.url()));
|
||||
handler->assignNetworkReply(m_networkManager.get(createNetworkRequest(handler->m_downloadRequest)));
|
||||
handler->disconnect(this);
|
||||
}
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
void DownloadManager::ignoreSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
|
||||
void Net::DownloadManager::ignoreSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
|
||||
{
|
||||
Q_UNUSED(errors)
|
||||
// Ignore all SSL errors
|
||||
reply->ignoreSslErrors();
|
||||
}
|
||||
#endif
|
||||
|
||||
Net::DownloadRequest::DownloadRequest(const QString &url)
|
||||
: m_url {url}
|
||||
{
|
||||
}
|
||||
|
||||
QString Net::DownloadRequest::url() const
|
||||
{
|
||||
return m_url;
|
||||
}
|
||||
|
||||
Net::DownloadRequest &Net::DownloadRequest::url(const QString &value)
|
||||
{
|
||||
m_url = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QString Net::DownloadRequest::userAgent() const
|
||||
{
|
||||
return m_userAgent;
|
||||
}
|
||||
|
||||
Net::DownloadRequest &Net::DownloadRequest::userAgent(const QString &value)
|
||||
{
|
||||
m_userAgent = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
qint64 Net::DownloadRequest::limit() const
|
||||
{
|
||||
return m_limit;
|
||||
}
|
||||
|
||||
Net::DownloadRequest &Net::DownloadRequest::limit(qint64 value)
|
||||
{
|
||||
m_limit = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Net::DownloadRequest::saveToFile() const
|
||||
{
|
||||
return m_saveToFile;
|
||||
}
|
||||
|
||||
Net::DownloadRequest &Net::DownloadRequest::saveToFile(bool value)
|
||||
{
|
||||
m_saveToFile = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Net::DownloadRequest::handleRedirectToMagnet() const
|
||||
{
|
||||
return m_handleRedirectToMagnet;
|
||||
}
|
||||
|
||||
Net::DownloadRequest &Net::DownloadRequest::handleRedirectToMagnet(bool value)
|
||||
{
|
||||
m_handleRedirectToMagnet = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Net::ServiceID Net::ServiceID::fromURL(const QUrl &url)
|
||||
{
|
||||
return {url.host(), url.port(80)};
|
||||
}
|
||||
|
||||
uint Net::qHash(const ServiceID &serviceID, uint seed)
|
||||
{
|
||||
return ::qHash(serviceID.hostName, seed) ^ serviceID.port;
|
||||
}
|
||||
|
||||
bool Net::operator==(const ServiceID &lhs, const ServiceID &rhs)
|
||||
{
|
||||
return ((lhs.hostName == rhs.hostName) && (lhs.port == rhs.port));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -30,8 +30,12 @@
|
||||
#ifndef NET_DOWNLOADMANAGER_H
|
||||
#define NET_DOWNLOADMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
#include <QSet>
|
||||
|
||||
class QNetworkReply;
|
||||
class QNetworkCookie;
|
||||
@@ -42,16 +46,57 @@ namespace Net
|
||||
{
|
||||
class DownloadHandler;
|
||||
|
||||
class DownloadRequest
|
||||
{
|
||||
public:
|
||||
DownloadRequest(const QString &url);
|
||||
DownloadRequest(const DownloadRequest &other) = default;
|
||||
|
||||
QString url() const;
|
||||
DownloadRequest &url(const QString &value);
|
||||
|
||||
QString userAgent() const;
|
||||
DownloadRequest &userAgent(const QString &value);
|
||||
|
||||
qint64 limit() const;
|
||||
DownloadRequest &limit(qint64 value);
|
||||
|
||||
bool saveToFile() const;
|
||||
DownloadRequest &saveToFile(bool value);
|
||||
|
||||
bool handleRedirectToMagnet() const;
|
||||
DownloadRequest &handleRedirectToMagnet(bool value);
|
||||
|
||||
private:
|
||||
QString m_url;
|
||||
QString m_userAgent;
|
||||
qint64 m_limit = 0;
|
||||
bool m_saveToFile = false;
|
||||
bool m_handleRedirectToMagnet = false;
|
||||
};
|
||||
|
||||
struct ServiceID
|
||||
{
|
||||
QString hostName;
|
||||
int port;
|
||||
|
||||
static ServiceID fromURL(const QUrl &url);
|
||||
};
|
||||
|
||||
class DownloadManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(DownloadManager)
|
||||
|
||||
public:
|
||||
static void initInstance();
|
||||
static void freeInstance();
|
||||
static DownloadManager *instance();
|
||||
|
||||
DownloadHandler *downloadUrl(const QString &url, bool saveToFile = false, qint64 limit = 0, bool handleRedirectToMagnet = false, const QString &userAgent = "");
|
||||
DownloadHandler *download(const DownloadRequest &downloadRequest);
|
||||
|
||||
void registerSequentialService(const ServiceID &serviceID);
|
||||
|
||||
QList<QNetworkCookie> cookiesForUrl(const QUrl &url) const;
|
||||
bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url);
|
||||
QList<QNetworkCookie> allCookies() const;
|
||||
@@ -60,17 +105,25 @@ namespace Net
|
||||
|
||||
private slots:
|
||||
#ifndef QT_NO_OPENSSL
|
||||
void ignoreSslErrors(QNetworkReply *,const QList<QSslError> &);
|
||||
void ignoreSslErrors(QNetworkReply *, const QList<QSslError> &);
|
||||
#endif
|
||||
|
||||
private:
|
||||
explicit DownloadManager(QObject *parent = nullptr);
|
||||
|
||||
void applyProxySettings();
|
||||
void handleReplyFinished(QNetworkReply *reply);
|
||||
|
||||
static DownloadManager *m_instance;
|
||||
QNetworkAccessManager m_networkManager;
|
||||
|
||||
QSet<ServiceID> m_sequentialServices;
|
||||
QSet<ServiceID> m_busyServices;
|
||||
QHash<ServiceID, QQueue<DownloadHandler *>> m_waitingJobs;
|
||||
};
|
||||
|
||||
uint qHash(const ServiceID &serviceID, uint seed);
|
||||
bool operator==(const ServiceID &lhs, const ServiceID &rhs);
|
||||
}
|
||||
|
||||
#endif // NET_DOWNLOADMANAGER_H
|
||||
|
||||
@@ -118,7 +118,7 @@ void GeoIPManager::manageDatabaseUpdate()
|
||||
|
||||
void GeoIPManager::downloadDatabaseFile()
|
||||
{
|
||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(DATABASE_URL);
|
||||
DownloadHandler *handler = DownloadManager::instance()->download({DATABASE_URL});
|
||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
|
||||
, this, &GeoIPManager::downloadFinished);
|
||||
connect(handler, &Net::DownloadHandler::downloadFailed, this, &GeoIPManager::downloadFailed);
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QHash>
|
||||
#include <QHostAddress>
|
||||
#include <QVariant>
|
||||
|
||||
@@ -93,12 +92,12 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
|
||||
QFile file(filename);
|
||||
if (file.size() > MAX_FILE_SIZE) {
|
||||
error = tr("Unsupported database file size.");
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
error = file.errorString();
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
db = new GeoIPDatabase(file.size());
|
||||
@@ -106,13 +105,13 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
|
||||
if (file.read(reinterpret_cast<char *>(db->m_data), db->m_size) != db->m_size) {
|
||||
error = file.errorString();
|
||||
delete db;
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
|
||||
delete db;
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return db;
|
||||
@@ -123,7 +122,7 @@ GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
|
||||
GeoIPDatabase *db = nullptr;
|
||||
if (data.size() > MAX_FILE_SIZE) {
|
||||
error = tr("Unsupported database file size.");
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
db = new GeoIPDatabase(data.size());
|
||||
@@ -132,7 +131,7 @@ GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
|
||||
|
||||
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
|
||||
delete db;
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return db;
|
||||
|
||||
@@ -75,7 +75,7 @@ void ReverseResolution::resolve(const QString &ip)
|
||||
|
||||
void ReverseResolution::hostResolved(const QHostInfo &host)
|
||||
{
|
||||
const QString &ip = m_lookups.take(host.lookupId());
|
||||
const QString ip = m_lookups.take(host.lookupId());
|
||||
Q_ASSERT(!ip.isNull());
|
||||
|
||||
if (host.error() != QHostInfo::NoError) {
|
||||
@@ -83,7 +83,7 @@ void ReverseResolution::hostResolved(const QHostInfo &host)
|
||||
return;
|
||||
}
|
||||
|
||||
const QString &hostname = host.hostName();
|
||||
const QString hostname = host.hostName();
|
||||
|
||||
qDebug() << Q_FUNC_INFO << ip << QString("->") << hostname;
|
||||
m_cache.insert(ip, new QString(hostname));
|
||||
|
||||
@@ -34,12 +34,9 @@
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QHostAddress>
|
||||
#include <QHostInfo>
|
||||
#include <QNetworkInterface>
|
||||
#include <QStringList>
|
||||
#include <QTextCodec>
|
||||
#include <QTextStream>
|
||||
#ifndef QT_NO_OPENSSL
|
||||
#include <QSslSocket>
|
||||
#else
|
||||
@@ -67,7 +64,7 @@ namespace
|
||||
// ascii characters 0x36 ("6") and 0x5c ("\") are selected because they have large
|
||||
// Hamming distance (http://en.wikipedia.org/wiki/Hamming_distance)
|
||||
|
||||
for (int i = 0; i < key.length(); i++) {
|
||||
for (int i = 0; i < key.length(); ++i) {
|
||||
innerPadding[i] = innerPadding[i] ^ key.at(i); // XOR operation between every byte in key and innerpadding, of key length
|
||||
outerPadding[i] = outerPadding[i] ^ key.at(i); // XOR operation between every byte in key and outerpadding, of key length
|
||||
}
|
||||
@@ -302,7 +299,7 @@ QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, QTex
|
||||
if (firstWord)
|
||||
line += word;
|
||||
else
|
||||
line += " " + word;
|
||||
line += ' ' + word;
|
||||
firstWord = false;
|
||||
}
|
||||
}
|
||||
@@ -426,7 +423,7 @@ void Smtp::authenticate()
|
||||
// Skip authentication
|
||||
logError("The SMTP server does not seem to support any of the authentications modes "
|
||||
"we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, "
|
||||
"knowing it is likely to fail... Server Auth Modes: " + auth.join("|"));
|
||||
"knowing it is likely to fail... Server Auth Modes: " + auth.join('|'));
|
||||
m_state = Authenticated;
|
||||
// At this point the server will not send any response
|
||||
// So fill the buffer with a fake one to pass the tests
|
||||
@@ -506,7 +503,7 @@ void Smtp::authLogin()
|
||||
void Smtp::logError(const QString &msg)
|
||||
{
|
||||
qDebug() << "Email Notification Error:" << msg;
|
||||
Logger::instance()->addMessage(tr("Email Notification Error:") + " " + msg, Log::CRITICAL);
|
||||
Logger::instance()->addMessage(tr("Email Notification Error:") + ' ' + msg, Log::CRITICAL);
|
||||
}
|
||||
|
||||
QString Smtp::getCurrentDateTime() const
|
||||
@@ -532,7 +529,7 @@ QString Smtp::getCurrentDateTime() const
|
||||
std::snprintf(buf, sizeof(buf), "%+05d", timeOffset);
|
||||
QString timeOffsetStr = buf;
|
||||
|
||||
QString ret = weekDayStr + ", " + dayStr + " " + monthStr + " " + yearStr + " " + timeStr + " " + timeOffsetStr;
|
||||
QString ret = weekDayStr + ", " + dayStr + ' ' + monthStr + ' ' + yearStr + ' ' + timeStr + ' ' + timeOffsetStr;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ class QSslSocket;
|
||||
class QTcpSocket;
|
||||
#endif
|
||||
class QTextCodec;
|
||||
class QTextStream;
|
||||
|
||||
namespace Net
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 sledgehammer999 <sledgehammer999@qbittorrent.org>
|
||||
* Copyright (C) 2014 sledgehammer999 <hammered999@gmail.com>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -33,7 +33,6 @@
|
||||
#include <QDir>
|
||||
#include <QLocale>
|
||||
#include <QMutableListIterator>
|
||||
#include <QPair>
|
||||
#include <QSettings>
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
@@ -44,7 +43,7 @@
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <shlobj.h>
|
||||
#include <winreg.h>
|
||||
#include <QRegularExpression>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
@@ -92,7 +91,8 @@ void Preferences::setValue(const QString &key, const QVariant &value)
|
||||
// General options
|
||||
QString Preferences::getLocale() const
|
||||
{
|
||||
return value("Preferences/General/Locale", QLocale::system().name()).toString();
|
||||
const QString localeName = value("Preferences/General/Locale").toString();
|
||||
return (localeName.isEmpty() ? QLocale::system().name() : localeName);
|
||||
}
|
||||
|
||||
void Preferences::setLocale(const QString &locale)
|
||||
@@ -183,6 +183,16 @@ void Preferences::setMinimizeToTray(bool b)
|
||||
setValue("Preferences/General/MinimizeToTray", b);
|
||||
}
|
||||
|
||||
bool Preferences::minimizeToTrayNotified() const
|
||||
{
|
||||
return value("Preferences/General/MinimizeToTrayNotified", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setMinimizeToTrayNotified(bool b)
|
||||
{
|
||||
setValue("Preferences/General/MinimizeToTrayNotified", b);
|
||||
}
|
||||
|
||||
bool Preferences::closeToTray() const
|
||||
{
|
||||
return value("Preferences/General/CloseToTray", true).toBool();
|
||||
@@ -192,6 +202,16 @@ void Preferences::setCloseToTray(bool b)
|
||||
{
|
||||
setValue("Preferences/General/CloseToTray", b);
|
||||
}
|
||||
|
||||
bool Preferences::closeToTrayNotified() const
|
||||
{
|
||||
return value("Preferences/General/CloseToTrayNotified", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setCloseToTrayNotified(bool b)
|
||||
{
|
||||
setValue("Preferences/General/CloseToTrayNotified", b);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Preferences::isToolbarDisplayed() const
|
||||
@@ -235,14 +255,24 @@ void Preferences::setSplashScreenDisabled(bool b)
|
||||
}
|
||||
|
||||
// Preventing from system suspend while active torrents are presented.
|
||||
bool Preferences::preventFromSuspend() const
|
||||
bool Preferences::preventFromSuspendWhenDownloading() const
|
||||
{
|
||||
return value("Preferences/General/PreventFromSuspend", false).toBool();
|
||||
return value("Preferences/General/PreventFromSuspendWhenDownloading", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setPreventFromSuspend(bool b)
|
||||
void Preferences::setPreventFromSuspendWhenDownloading(bool b)
|
||||
{
|
||||
setValue("Preferences/General/PreventFromSuspend", b);
|
||||
setValue("Preferences/General/PreventFromSuspendWhenDownloading", b);
|
||||
}
|
||||
|
||||
bool Preferences::preventFromSuspendWhenSeeding() const
|
||||
{
|
||||
return value("Preferences/General/PreventFromSuspendWhenSeeding", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setPreventFromSuspendWhenSeeding(bool b)
|
||||
{
|
||||
setValue("Preferences/General/PreventFromSuspendWhenSeeding", b);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
@@ -256,8 +286,8 @@ void Preferences::setWinStartup(bool b)
|
||||
{
|
||||
QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);
|
||||
if (b) {
|
||||
const QString bin_path = "\"" + Utils::Fs::toNativePath(qApp->applicationFilePath()) + "\"";
|
||||
settings.setValue("qBittorrent", bin_path);
|
||||
const QString binPath = '"' + Utils::Fs::toNativePath(qApp->applicationFilePath()) + '"';
|
||||
settings.setValue("qBittorrent", binPath);
|
||||
}
|
||||
else {
|
||||
settings.remove("qBittorrent");
|
||||
@@ -500,7 +530,7 @@ void Preferences::setWebUiAuthSubnetWhitelist(QStringList subnets)
|
||||
|
||||
QString Preferences::getServerDomains() const
|
||||
{
|
||||
return value("Preferences/WebUI/ServerDomains", "*").toString();
|
||||
return value("Preferences/WebUI/ServerDomains", QChar('*')).toString();
|
||||
}
|
||||
|
||||
void Preferences::setServerDomains(const QString &str)
|
||||
@@ -510,7 +540,7 @@ void Preferences::setServerDomains(const QString &str)
|
||||
|
||||
QString Preferences::getWebUiAddress() const
|
||||
{
|
||||
return value("Preferences/WebUI/Address", "*").toString().trimmed();
|
||||
return value("Preferences/WebUI/Address", QChar('*')).toString().trimmed();
|
||||
}
|
||||
|
||||
void Preferences::setWebUiAddress(const QString &addr)
|
||||
@@ -554,28 +584,48 @@ void Preferences::setWebUiUsername(const QString &username)
|
||||
|
||||
QString Preferences::getWebUiPassword() const
|
||||
{
|
||||
QString pass_ha1 = value("Preferences/WebUI/Password_ha1").toString();
|
||||
if (pass_ha1.isEmpty()) {
|
||||
QString passHa1 = value("Preferences/WebUI/Password_ha1").toString();
|
||||
if (passHa1.isEmpty()) {
|
||||
QCryptographicHash md5(QCryptographicHash::Md5);
|
||||
md5.addData("adminadmin");
|
||||
pass_ha1 = md5.result().toHex();
|
||||
passHa1 = md5.result().toHex();
|
||||
}
|
||||
return pass_ha1;
|
||||
return passHa1;
|
||||
}
|
||||
|
||||
void Preferences::setWebUiPassword(const QString &new_password)
|
||||
void Preferences::setWebUiPassword(const QString &newPassword)
|
||||
{
|
||||
// Do not overwrite current password with its hash
|
||||
if (new_password == getWebUiPassword())
|
||||
if (newPassword == getWebUiPassword())
|
||||
return;
|
||||
|
||||
// Encode to md5 and save
|
||||
QCryptographicHash md5(QCryptographicHash::Md5);
|
||||
md5.addData(new_password.toLocal8Bit());
|
||||
md5.addData(newPassword.toLocal8Bit());
|
||||
|
||||
setValue("Preferences/WebUI/Password_ha1", md5.result().toHex());
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiClickjackingProtectionEnabled() const
|
||||
{
|
||||
return value("Preferences/WebUI/ClickjackingProtection", true).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setWebUiClickjackingProtectionEnabled(bool enabled)
|
||||
{
|
||||
setValue("Preferences/WebUI/ClickjackingProtection", enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiCSRFProtectionEnabled() const
|
||||
{
|
||||
return value("Preferences/WebUI/CSRFProtection", true).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setWebUiCSRFProtectionEnabled(bool enabled)
|
||||
{
|
||||
setValue("Preferences/WebUI/CSRFProtection", enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiHttpsEnabled() const
|
||||
{
|
||||
return value("Preferences/WebUI/HTTPS/Enabled", false).toBool();
|
||||
@@ -828,153 +878,6 @@ void Preferences::disableRecursiveDownload(bool disable)
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
namespace
|
||||
{
|
||||
enum REG_SEARCH_TYPE
|
||||
{
|
||||
USER,
|
||||
SYSTEM_32BIT,
|
||||
SYSTEM_64BIT
|
||||
};
|
||||
|
||||
QStringList getRegSubkeys(HKEY handle)
|
||||
{
|
||||
QStringList keys;
|
||||
|
||||
DWORD cSubKeys = 0;
|
||||
DWORD cMaxSubKeyLen = 0;
|
||||
LONG res = ::RegQueryInfoKeyW(handle, NULL, NULL, NULL, &cSubKeys, &cMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
|
||||
if (res == ERROR_SUCCESS) {
|
||||
cMaxSubKeyLen++; // For null character
|
||||
LPWSTR lpName = new WCHAR[cMaxSubKeyLen];
|
||||
DWORD cName;
|
||||
|
||||
for (DWORD i = 0; i < cSubKeys; ++i) {
|
||||
cName = cMaxSubKeyLen;
|
||||
res = ::RegEnumKeyExW(handle, i, lpName, &cName, NULL, NULL, NULL, NULL);
|
||||
if (res == ERROR_SUCCESS)
|
||||
keys.push_back(QString::fromWCharArray(lpName));
|
||||
}
|
||||
|
||||
delete[] lpName;
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
QString getRegValue(HKEY handle, const QString &name = QString())
|
||||
{
|
||||
QString result;
|
||||
|
||||
DWORD type = 0;
|
||||
DWORD cbData = 0;
|
||||
LPWSTR lpValueName = NULL;
|
||||
if (!name.isEmpty()) {
|
||||
lpValueName = new WCHAR[name.size() + 1];
|
||||
name.toWCharArray(lpValueName);
|
||||
lpValueName[name.size()] = 0;
|
||||
}
|
||||
|
||||
// Discover the size of the value
|
||||
::RegQueryValueExW(handle, lpValueName, NULL, &type, NULL, &cbData);
|
||||
DWORD cBuffer = (cbData / sizeof(WCHAR)) + 1;
|
||||
LPWSTR lpData = new WCHAR[cBuffer];
|
||||
LONG res = ::RegQueryValueExW(handle, lpValueName, NULL, &type, (LPBYTE)lpData, &cbData);
|
||||
if (lpValueName)
|
||||
delete[] lpValueName;
|
||||
|
||||
if (res == ERROR_SUCCESS) {
|
||||
lpData[cBuffer - 1] = 0;
|
||||
result = QString::fromWCharArray(lpData);
|
||||
}
|
||||
delete[] lpData;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString pythonSearchReg(const REG_SEARCH_TYPE type)
|
||||
{
|
||||
HKEY hkRoot;
|
||||
if (type == USER)
|
||||
hkRoot = HKEY_CURRENT_USER;
|
||||
else
|
||||
hkRoot = HKEY_LOCAL_MACHINE;
|
||||
|
||||
REGSAM samDesired = KEY_READ;
|
||||
if (type == SYSTEM_32BIT)
|
||||
samDesired |= KEY_WOW64_32KEY;
|
||||
else if (type == SYSTEM_64BIT)
|
||||
samDesired |= KEY_WOW64_64KEY;
|
||||
|
||||
QString path;
|
||||
LONG res = 0;
|
||||
HKEY hkPythonCore;
|
||||
res = ::RegOpenKeyExW(hkRoot, L"SOFTWARE\\Python\\PythonCore", 0, samDesired, &hkPythonCore);
|
||||
|
||||
if (res == ERROR_SUCCESS) {
|
||||
QStringList versions = getRegSubkeys(hkPythonCore);
|
||||
qDebug("Python versions nb: %d", versions.size());
|
||||
versions.sort();
|
||||
|
||||
bool found = false;
|
||||
while (!found && !versions.empty()) {
|
||||
const QString version = versions.takeLast() + "\\InstallPath";
|
||||
LPWSTR lpSubkey = new WCHAR[version.size() + 1];
|
||||
version.toWCharArray(lpSubkey);
|
||||
lpSubkey[version.size()] = 0;
|
||||
|
||||
HKEY hkInstallPath;
|
||||
res = ::RegOpenKeyExW(hkPythonCore, lpSubkey, 0, samDesired, &hkInstallPath);
|
||||
delete[] lpSubkey;
|
||||
|
||||
if (res == ERROR_SUCCESS) {
|
||||
qDebug("Detected possible Python v%s location", qUtf8Printable(version));
|
||||
path = getRegValue(hkInstallPath);
|
||||
::RegCloseKey(hkInstallPath);
|
||||
|
||||
if (!path.isEmpty() && QDir(path).exists("python.exe")) {
|
||||
qDebug("Found python.exe at %s", qUtf8Printable(path));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
path = QString();
|
||||
|
||||
::RegCloseKey(hkPythonCore);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
QString Preferences::getPythonPath()
|
||||
{
|
||||
QString path = pythonSearchReg(USER);
|
||||
if (!path.isEmpty())
|
||||
return path;
|
||||
|
||||
path = pythonSearchReg(SYSTEM_32BIT);
|
||||
if (!path.isEmpty())
|
||||
return path;
|
||||
|
||||
path = pythonSearchReg(SYSTEM_64BIT);
|
||||
if (!path.isEmpty())
|
||||
return path;
|
||||
|
||||
// Fallback: Detect python from default locations
|
||||
const QStringList dirs = QDir("C:/").entryList(QStringList("Python*"), QDir::Dirs, QDir::Name | QDir::Reversed);
|
||||
foreach (const QString &dir, dirs) {
|
||||
const QString path("C:/" + dir + "/");
|
||||
if (QFile::exists(path + "python.exe"))
|
||||
return path;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool Preferences::neverCheckFileAssoc() const
|
||||
{
|
||||
return value("Preferences/Win32/NeverCheckFileAssocation", false).toBool();
|
||||
@@ -1001,13 +904,14 @@ bool Preferences::isMagnetLinkAssocSet()
|
||||
QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat);
|
||||
|
||||
// Check magnet link assoc
|
||||
QRegExp exe_reg("\"([^\"]+)\".*");
|
||||
QString shell_command = Utils::Fs::toNativePath(settings.value("magnet/shell/open/command/Default", "").toString());
|
||||
if (exe_reg.indexIn(shell_command) < 0)
|
||||
const QString shellCommand = Utils::Fs::toNativePath(settings.value("magnet/shell/open/command/Default", "").toString());
|
||||
|
||||
const QRegularExpressionMatch exeRegMatch = QRegularExpression("\"([^\"]+)\".*").match(shellCommand);
|
||||
if (!exeRegMatch.hasMatch())
|
||||
return false;
|
||||
QString assoc_exe = exe_reg.cap(1);
|
||||
qDebug("exe: %s", qUtf8Printable(assoc_exe));
|
||||
if (assoc_exe.compare(Utils::Fs::toNativePath(qApp->applicationFilePath()), Qt::CaseInsensitive) != 0)
|
||||
|
||||
const QString assocExe = exeRegMatch.captured(1);
|
||||
if (assocExe.compare(Utils::Fs::toNativePath(qApp->applicationFilePath()), Qt::CaseInsensitive) != 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -1019,9 +923,9 @@ void Preferences::setTorrentFileAssoc(bool set)
|
||||
|
||||
// .Torrent association
|
||||
if (set) {
|
||||
QString old_progid = settings.value(".torrent/Default").toString();
|
||||
if (!old_progid.isEmpty() && (old_progid != "qBittorrent"))
|
||||
settings.setValue(".torrent/OpenWithProgids/" + old_progid, "");
|
||||
QString oldProgId = settings.value(".torrent/Default").toString();
|
||||
if (!oldProgId.isEmpty() && (oldProgId != "qBittorrent"))
|
||||
settings.setValue(".torrent/OpenWithProgids/" + oldProgId, "");
|
||||
settings.setValue(".torrent/Default", "qBittorrent");
|
||||
}
|
||||
else if (isTorrentFileAssocSet()) {
|
||||
@@ -1037,15 +941,15 @@ void Preferences::setMagnetLinkAssoc(bool set)
|
||||
|
||||
// Magnet association
|
||||
if (set) {
|
||||
const QString command_str = "\"" + qApp->applicationFilePath() + "\" \"%1\"";
|
||||
const QString icon_str = "\"" + qApp->applicationFilePath() + "\",1";
|
||||
const QString commandStr = '"' + qApp->applicationFilePath() + "\" \"%1\"";
|
||||
const QString iconStr = '"' + qApp->applicationFilePath() + "\",1";
|
||||
|
||||
settings.setValue("magnet/Default", "URL:Magnet link");
|
||||
settings.setValue("magnet/Content Type", "application/x-magnet");
|
||||
settings.setValue("magnet/URL Protocol", "");
|
||||
settings.setValue("magnet/DefaultIcon/Default", Utils::Fs::toNativePath(icon_str));
|
||||
settings.setValue("magnet/DefaultIcon/Default", Utils::Fs::toNativePath(iconStr));
|
||||
settings.setValue("magnet/shell/Default", "open");
|
||||
settings.setValue("magnet/shell/open/command/Default", Utils::Fs::toNativePath(command_str));
|
||||
settings.setValue("magnet/shell/open/command/Default", Utils::Fs::toNativePath(commandStr));
|
||||
}
|
||||
else if (isMagnetLinkAssocSet()) {
|
||||
settings.remove("magnet");
|
||||
@@ -1378,6 +1282,16 @@ void Preferences::setSearchTabHeaderState(const QByteArray &state)
|
||||
setValue("SearchTab/qt5/HeaderState", state);
|
||||
}
|
||||
|
||||
bool Preferences::getRegexAsFilteringPatternForSearchJob() const
|
||||
{
|
||||
return value("SearchTab/UseRegexAsFilteringPattern", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setRegexAsFilteringPatternForSearchJob(const bool checked)
|
||||
{
|
||||
setValue("SearchTab/UseRegexAsFilteringPattern", checked);
|
||||
}
|
||||
|
||||
QStringList Preferences::getSearchEngDisabled() const
|
||||
{
|
||||
return value("SearchEngines/disabledEngines").toStringList();
|
||||
@@ -1468,6 +1382,16 @@ void Preferences::setTransHeaderState(const QByteArray &state)
|
||||
setValue("TransferList/qt5/HeaderState", state);
|
||||
}
|
||||
|
||||
bool Preferences::getRegexAsFilteringPatternForTransferList() const
|
||||
{
|
||||
return value("TransferList/UseRegexAsFilteringPattern", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setRegexAsFilteringPatternForTransferList(const bool checked)
|
||||
{
|
||||
setValue("TransferList/UseRegexAsFilteringPattern", checked);
|
||||
}
|
||||
|
||||
// From old RssSettings class
|
||||
bool Preferences::isRSSWidgetEnabled() const
|
||||
{
|
||||
@@ -1508,6 +1432,16 @@ void Preferences::setNetworkCookies(const QList<QNetworkCookie> &cookies)
|
||||
setValue("Network/Cookies", rawCookies);
|
||||
}
|
||||
|
||||
bool Preferences::isSpeedWidgetEnabled() const
|
||||
{
|
||||
return value("SpeedWidget/Enabled", true).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setSpeedWidgetEnabled(bool enabled)
|
||||
{
|
||||
setValue("SpeedWidget/Enabled", enabled);
|
||||
}
|
||||
|
||||
int Preferences::getSpeedWidgetPeriod() const
|
||||
{
|
||||
return value("SpeedWidget/period", 1).toInt();
|
||||
@@ -1531,6 +1465,8 @@ void Preferences::setSpeedWidgetGraphEnable(int id, const bool enable)
|
||||
|
||||
void Preferences::upgrade()
|
||||
{
|
||||
SettingsStorage *settingsStorage = SettingsStorage::instance();
|
||||
|
||||
QStringList labels = value("TransferListFilters/customLabels").toStringList();
|
||||
if (!labels.isEmpty()) {
|
||||
QVariantMap categories = value("BitTorrent/Session/Categories").toMap();
|
||||
@@ -1539,10 +1475,17 @@ void Preferences::upgrade()
|
||||
categories[label] = "";
|
||||
}
|
||||
setValue("BitTorrent/Session/Categories", categories);
|
||||
SettingsStorage::instance()->removeValue("TransferListFilters/customLabels");
|
||||
settingsStorage->removeValue("TransferListFilters/customLabels");
|
||||
}
|
||||
|
||||
SettingsStorage::instance()->removeValue("Preferences/Downloads/AppendLabel");
|
||||
settingsStorage->removeValue("Preferences/Downloads/AppendLabel");
|
||||
|
||||
// Inhibit sleep based on running downloads/available seeds rather than network activity.
|
||||
if (value("Preferences/General/PreventFromSuspend", false).toBool()) {
|
||||
setPreventFromSuspendWhenDownloading(true);
|
||||
setPreventFromSuspendWhenSeeding(true);
|
||||
}
|
||||
settingsStorage->removeValue("Preferences/General/PreventFromSuspend");
|
||||
}
|
||||
|
||||
void Preferences::apply()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014 sledgehammer999 <sledgehammer999@qbittorrent.org>
|
||||
* Copyright (C) 2014 sledgehammer999 <hammered999@gmail.com>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -123,8 +123,10 @@ public:
|
||||
void setStartMinimized(bool b);
|
||||
bool isSplashScreenDisabled() const;
|
||||
void setSplashScreenDisabled(bool b);
|
||||
bool preventFromSuspend() const;
|
||||
void setPreventFromSuspend(bool b);
|
||||
bool preventFromSuspendWhenDownloading() const;
|
||||
void setPreventFromSuspendWhenDownloading(bool b);
|
||||
bool preventFromSuspendWhenSeeding() const;
|
||||
void setPreventFromSuspendWhenSeeding(bool b);
|
||||
#ifdef Q_OS_WIN
|
||||
bool WinStartup() const;
|
||||
void setWinStartup(bool b);
|
||||
@@ -192,7 +194,13 @@ public:
|
||||
QString getWebUiUsername() const;
|
||||
void setWebUiUsername(const QString &username);
|
||||
QString getWebUiPassword() const;
|
||||
void setWebUiPassword(const QString &new_password);
|
||||
void setWebUiPassword(const QString &newPassword);
|
||||
|
||||
// WebUI security
|
||||
bool isWebUiClickjackingProtectionEnabled() const;
|
||||
void setWebUiClickjackingProtectionEnabled(bool enabled);
|
||||
bool isWebUiCSRFProtectionEnabled() const;
|
||||
void setWebUiCSRFProtectionEnabled(bool enabled);
|
||||
|
||||
// HTTPS
|
||||
bool isWebUiHttpsEnabled() const;
|
||||
@@ -251,7 +259,6 @@ public:
|
||||
bool recursiveDownloadDisabled() const;
|
||||
void disableRecursiveDownload(bool disable = true);
|
||||
#ifdef Q_OS_WIN
|
||||
static QString getPythonPath();
|
||||
bool neverCheckFileAssoc() const;
|
||||
void setNeverCheckFileAssoc(bool check = true);
|
||||
static bool isTorrentFileAssocSet();
|
||||
@@ -280,10 +287,14 @@ public:
|
||||
#ifndef Q_OS_MAC
|
||||
bool systrayIntegration() const;
|
||||
void setSystrayIntegration(bool enabled);
|
||||
bool minimizeToTrayNotified() const;
|
||||
void setMinimizeToTrayNotified(bool b);
|
||||
bool minimizeToTray() const;
|
||||
void setMinimizeToTray(bool b);
|
||||
bool closeToTray() const;
|
||||
void setCloseToTray(bool b);
|
||||
bool closeToTrayNotified() const;
|
||||
void setCloseToTrayNotified(bool b);
|
||||
TrayIcon::Style trayIconStyle() const;
|
||||
void setTrayIconStyle(TrayIcon::Style style);
|
||||
#endif
|
||||
@@ -330,6 +341,8 @@ public:
|
||||
void setRssMainSplitterState(const QByteArray &state);
|
||||
QByteArray getSearchTabHeaderState() const;
|
||||
void setSearchTabHeaderState(const QByteArray &state);
|
||||
bool getRegexAsFilteringPatternForSearchJob() const;
|
||||
void setRegexAsFilteringPatternForSearchJob(bool checked);
|
||||
QStringList getSearchEngDisabled() const;
|
||||
void setSearchEngDisabled(const QStringList &engines);
|
||||
QString getTorImportLastContentDir() const;
|
||||
@@ -344,6 +357,8 @@ public:
|
||||
void setTransSelFilter(const int &index);
|
||||
QByteArray getTransHeaderState() const;
|
||||
void setTransHeaderState(const QByteArray &state);
|
||||
bool getRegexAsFilteringPatternForTransferList() const;
|
||||
void setRegexAsFilteringPatternForTransferList(bool checked);
|
||||
int getToolbarTextPosition() const;
|
||||
void setToolbarTextPosition(const int position);
|
||||
|
||||
@@ -356,6 +371,8 @@ public:
|
||||
void setNetworkCookies(const QList<QNetworkCookie> &cookies);
|
||||
|
||||
// SpeedWidget
|
||||
bool isSpeedWidgetEnabled() const;
|
||||
void setSpeedWidgetEnabled(bool enabled);
|
||||
int getSpeedWidgetPeriod() const;
|
||||
void setSpeedWidgetPeriod(const int period);
|
||||
bool getSpeedWidgetGraphEnable(int id) const;
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "profile_p.h"
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef QBT_PROFILE_H
|
||||
|
||||
@@ -582,16 +582,6 @@ void Parser::parse_impl(const QByteArray &feedData)
|
||||
.arg(xml.errorString()).arg(xml.lineNumber())
|
||||
.arg(xml.columnNumber()).arg(xml.characterOffset());
|
||||
}
|
||||
else {
|
||||
// Sort article list chronologically
|
||||
// NOTE: We don't need to sort it here if articles are always
|
||||
// sorted in fetched XML in reverse chronological order
|
||||
std::sort(m_result.articles.begin(), m_result.articles.end()
|
||||
, [](const QVariantHash &a1, const QVariantHash &a2)
|
||||
{
|
||||
return a1["date"].toDateTime() < a2["date"].toDateTime();
|
||||
});
|
||||
}
|
||||
|
||||
emit finished(m_result);
|
||||
m_result.articles.clear(); // clear articles only
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
|
||||
#include "rss_article.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <QJsonObject>
|
||||
#include <QVariant>
|
||||
|
||||
@@ -73,23 +72,6 @@ Article::Article(Feed *feed, const QVariantHash &varHash)
|
||||
, m_isRead(varHash.value(KeyIsRead, false).toBool())
|
||||
, m_data(varHash)
|
||||
{
|
||||
if (!m_date.isValid())
|
||||
throw std::runtime_error("Bad RSS Article data");
|
||||
|
||||
// If item does not have a guid, fall back to some other identifier
|
||||
if (m_guid.isEmpty())
|
||||
m_guid = varHash.value(KeyTorrentURL).toString();
|
||||
if (m_guid.isEmpty())
|
||||
m_guid = varHash.value(KeyTitle).toString();
|
||||
if (m_guid.isEmpty())
|
||||
throw std::runtime_error("Bad RSS Article data");
|
||||
|
||||
m_data[KeyId] = m_guid;
|
||||
|
||||
if (m_torrentURL.isEmpty()) {
|
||||
m_torrentURL = m_link;
|
||||
m_data[KeyTorrentURL] = m_torrentURL;
|
||||
}
|
||||
}
|
||||
|
||||
Article::Article(Feed *feed, const QJsonObject &jsonObj)
|
||||
|
||||
@@ -66,6 +66,7 @@ const QString RulesFileName(QStringLiteral("download_rules.json"));
|
||||
|
||||
const QString SettingsKey_ProcessingEnabled(QStringLiteral("RSS/AutoDownloader/EnableProcessing"));
|
||||
const QString SettingsKey_SmartEpisodeFilter(QStringLiteral("RSS/AutoDownloader/SmartEpisodeFilter"));
|
||||
const QString SettingsKey_DownloadRepacks(QStringLiteral("RSS/AutoDownloader/DownloadRepacks"));
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -310,6 +311,16 @@ void AutoDownloader::setSmartEpisodeFilters(const QStringList &filters)
|
||||
m_smartEpisodeRegex.setPattern(regex);
|
||||
}
|
||||
|
||||
bool AutoDownloader::downloadRepacks() const
|
||||
{
|
||||
return SettingsStorage::instance()->loadValue(SettingsKey_DownloadRepacks, true).toBool();
|
||||
}
|
||||
|
||||
void AutoDownloader::setDownloadRepacks(const bool downloadRepacks)
|
||||
{
|
||||
SettingsStorage::instance()->storeValue(SettingsKey_DownloadRepacks, downloadRepacks);
|
||||
}
|
||||
|
||||
void AutoDownloader::process()
|
||||
{
|
||||
if (m_processingQueue.isEmpty()) return; // processing was disabled
|
||||
|
||||
@@ -85,6 +85,9 @@ namespace RSS
|
||||
void setSmartEpisodeFilters(const QStringList &filters);
|
||||
QRegularExpression smartEpisodeRegex() const;
|
||||
|
||||
bool downloadRepacks() const;
|
||||
void setDownloadRepacks(bool downloadRepacks);
|
||||
|
||||
bool hasRule(const QString &ruleName) const;
|
||||
AutoDownloadRule ruleByName(const QString &ruleName) const;
|
||||
QList<AutoDownloadRule> rules() const;
|
||||
|
||||
@@ -344,7 +344,7 @@ bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString& articleTitle) co
|
||||
// See if this episode has been downloaded before
|
||||
const bool previouslyMatched = m_dataPtr->previouslyMatchedEpisodes.contains(episodeStr);
|
||||
const bool isRepack = articleTitle.contains("REPACK", Qt::CaseInsensitive) || articleTitle.contains("PROPER", Qt::CaseInsensitive);
|
||||
if (previouslyMatched && !isRepack)
|
||||
if (previouslyMatched && (!isRepack || !AutoDownloader::instance()->downloadRepacks()))
|
||||
return false;
|
||||
|
||||
m_dataPtr->lastComputedEpisode = episodeStr;
|
||||
@@ -502,7 +502,7 @@ void AutoDownloadRule::setMustContain(const QString &tokens)
|
||||
if (m_dataPtr->useRegex)
|
||||
m_dataPtr->mustContain = QStringList() << tokens;
|
||||
else
|
||||
m_dataPtr->mustContain = tokens.split("|");
|
||||
m_dataPtr->mustContain = tokens.split('|');
|
||||
|
||||
// Check for single empty string - if so, no condition
|
||||
if ((m_dataPtr->mustContain.size() == 1) && m_dataPtr->mustContain[0].isEmpty())
|
||||
@@ -516,7 +516,7 @@ void AutoDownloadRule::setMustNotContain(const QString &tokens)
|
||||
if (m_dataPtr->useRegex)
|
||||
m_dataPtr->mustNotContain = QStringList() << tokens;
|
||||
else
|
||||
m_dataPtr->mustNotContain = tokens.split("|");
|
||||
m_dataPtr->mustNotContain = tokens.split('|');
|
||||
|
||||
// Check for single empty string - if so, no condition
|
||||
if ((m_dataPtr->mustNotContain.size() == 1) && m_dataPtr->mustNotContain[0].isEmpty())
|
||||
@@ -605,12 +605,12 @@ int AutoDownloadRule::ignoreDays() const
|
||||
|
||||
QString AutoDownloadRule::mustContain() const
|
||||
{
|
||||
return m_dataPtr->mustContain.join("|");
|
||||
return m_dataPtr->mustContain.join('|');
|
||||
}
|
||||
|
||||
QString AutoDownloadRule::mustNotContain() const
|
||||
{
|
||||
return m_dataPtr->mustNotContain.join("|");
|
||||
return m_dataPtr->mustNotContain.join('|');
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::useSmartFilter() const
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
|
||||
#include "rss_feed.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
@@ -41,6 +43,7 @@
|
||||
#include <QUrl>
|
||||
|
||||
#include "../asyncfilestorage.h"
|
||||
#include "../global.h"
|
||||
#include "../logger.h"
|
||||
#include "../net/downloadhandler.h"
|
||||
#include "../net/downloadmanager.h"
|
||||
@@ -50,21 +53,30 @@
|
||||
#include "rss_article.h"
|
||||
#include "rss_session.h"
|
||||
|
||||
const QString Str_Url(QStringLiteral("url"));
|
||||
const QString Str_Title(QStringLiteral("title"));
|
||||
const QString Str_LastBuildDate(QStringLiteral("lastBuildDate"));
|
||||
const QString Str_IsLoading(QStringLiteral("isLoading"));
|
||||
const QString Str_HasError(QStringLiteral("hasError"));
|
||||
const QString Str_Articles(QStringLiteral("articles"));
|
||||
const QString KEY_UID(QStringLiteral("uid"));
|
||||
const QString KEY_URL(QStringLiteral("url"));
|
||||
const QString KEY_TITLE(QStringLiteral("title"));
|
||||
const QString KEY_LASTBUILDDATE(QStringLiteral("lastBuildDate"));
|
||||
const QString KEY_ISLOADING(QStringLiteral("isLoading"));
|
||||
const QString KEY_HASERROR(QStringLiteral("hasError"));
|
||||
const QString KEY_ARTICLES(QStringLiteral("articles"));
|
||||
|
||||
using namespace RSS;
|
||||
|
||||
Feed::Feed(const QString &url, const QString &path, Session *session)
|
||||
Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *session)
|
||||
: Item(path)
|
||||
, m_session(session)
|
||||
, m_uid(uid)
|
||||
, m_url(url)
|
||||
{
|
||||
m_dataFileName = QString("%1.json").arg(Utils::Fs::toValidFileSystemName(m_url, false, QLatin1String("_")));
|
||||
m_dataFileName = QString::fromLatin1(m_uid.toRfc4122().toHex()) + QLatin1String(".json");
|
||||
|
||||
// Move to new file naming scheme (since v4.1.2)
|
||||
const QString legacyFilename {Utils::Fs::toValidFileSystemName(m_url, false, QLatin1String("_"))
|
||||
+ QLatin1String(".json")};
|
||||
const QDir storageDir {m_session->dataFileStorage()->storageDir()};
|
||||
if (!QFile::exists(storageDir.absoluteFilePath(m_dataFileName)))
|
||||
QFile::rename(storageDir.absoluteFilePath(legacyFilename), storageDir.absoluteFilePath(m_dataFileName));
|
||||
|
||||
m_parser = new Private::Parser(m_lastBuildDate);
|
||||
m_parser->moveToThread(m_session->workingThread());
|
||||
@@ -78,6 +90,8 @@ Feed::Feed(const QString &url, const QString &path, Session *session)
|
||||
else
|
||||
connect(m_session, &Session::processingStateChanged, this, &Feed::handleSessionProcessingEnabledChanged);
|
||||
|
||||
Net::DownloadManager::instance()->registerSequentialService(Net::ServiceID::fromURL(m_url));
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
@@ -117,7 +131,7 @@ void Feed::refresh()
|
||||
|
||||
// NOTE: Should we allow manually refreshing for disabled session?
|
||||
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(m_url);
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->download({m_url});
|
||||
connect(handler
|
||||
, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
|
||||
, this, &Feed::handleDownloadFinished);
|
||||
@@ -127,6 +141,11 @@ void Feed::refresh()
|
||||
emit stateChanged(this);
|
||||
}
|
||||
|
||||
QUuid Feed::uid() const
|
||||
{
|
||||
return m_uid;
|
||||
}
|
||||
|
||||
QString Feed::url() const
|
||||
{
|
||||
return m_url;
|
||||
@@ -199,47 +218,30 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
|
||||
{
|
||||
m_hasError = !result.error.isEmpty();
|
||||
|
||||
if (!result.title.isEmpty() && (title() != result.title)) {
|
||||
m_title = result.title;
|
||||
m_dirty = true;
|
||||
emit titleChanged(this);
|
||||
}
|
||||
|
||||
if (!result.lastBuildDate.isEmpty()) {
|
||||
m_lastBuildDate = result.lastBuildDate;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
// For some reason, the RSS feed may contain malformed XML data and it may not be
|
||||
// successfully parsed by the XML parser. We are still trying to load as many articles
|
||||
// as possible until we encounter corrupted data. So we can have some articles here
|
||||
// even in case of parsing error.
|
||||
if (!m_hasError || !result.articles.isEmpty()) {
|
||||
if (title() != result.title) {
|
||||
m_title = result.title;
|
||||
emit titleChanged(this);
|
||||
}
|
||||
|
||||
m_lastBuildDate = result.lastBuildDate;
|
||||
|
||||
int newArticlesCount = 0;
|
||||
const QDateTime now {QDateTime::currentDateTime()};
|
||||
for (QVariantHash varHash : result.articles) {
|
||||
// if article has no publication date we use feed update time as a fallback
|
||||
QVariant &articleDate = varHash[Article::KeyDate];
|
||||
if (!articleDate.toDateTime().isValid())
|
||||
articleDate = now;
|
||||
|
||||
try {
|
||||
auto article = new Article(this, varHash);
|
||||
if (addArticle(article))
|
||||
++newArticlesCount;
|
||||
else
|
||||
delete article;
|
||||
}
|
||||
catch (const std::runtime_error&) {}
|
||||
}
|
||||
|
||||
m_dirty = (newArticlesCount > 0);
|
||||
store();
|
||||
|
||||
LogMsg(tr("RSS feed at '%1' updated. Added %2 new articles.")
|
||||
.arg(m_url, QString::number(newArticlesCount)));
|
||||
}
|
||||
const int newArticlesCount = updateArticles(result.articles);
|
||||
store();
|
||||
|
||||
if (m_hasError) {
|
||||
LogMsg(tr("Failed to parse RSS feed at '%1'. Reason: %2").arg(m_url, result.error)
|
||||
, Log::WARNING);
|
||||
}
|
||||
LogMsg(tr("RSS feed at '%1' updated. Added %2 new articles.")
|
||||
.arg(url(), QString::number(newArticlesCount)));
|
||||
|
||||
m_isLoading = false;
|
||||
emit stateChanged(this);
|
||||
@@ -342,9 +344,7 @@ void Feed::storeDeferred()
|
||||
bool Feed::addArticle(Article *article)
|
||||
{
|
||||
Q_ASSERT(article);
|
||||
|
||||
if (m_articles.contains(article->guid()))
|
||||
return false;
|
||||
Q_ASSERT(!m_articles.contains(article->guid()));
|
||||
|
||||
// Insertion sort
|
||||
const int maxArticles = m_session->maxArticlesPerFeed();
|
||||
@@ -359,6 +359,8 @@ bool Feed::addArticle(Article *article)
|
||||
increaseUnreadCount();
|
||||
connect(article, &Article::read, this, &Feed::handleArticleRead);
|
||||
}
|
||||
|
||||
m_dirty = true;
|
||||
emit newArticle(article);
|
||||
|
||||
if (m_articlesByDate.size() > maxArticles)
|
||||
@@ -401,12 +403,92 @@ void Feed::downloadIcon()
|
||||
// XXX: This works for most sites but it is not perfect
|
||||
const QUrl url(m_url);
|
||||
auto iconUrl = QString("%1://%2/favicon.ico").arg(url.scheme(), url.host());
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl, true);
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->download(
|
||||
Net::DownloadRequest(iconUrl).saveToFile(true));
|
||||
connect(handler
|
||||
, static_cast<void (Net::DownloadHandler::*)(const QString &, const QString &)>(&Net::DownloadHandler::downloadFinished)
|
||||
, this, &Feed::handleIconDownloadFinished);
|
||||
}
|
||||
|
||||
int Feed::updateArticles(const QList<QVariantHash> &loadedArticles)
|
||||
{
|
||||
if (loadedArticles.empty())
|
||||
return 0;
|
||||
|
||||
QDateTime dummyPubDate {QDateTime::currentDateTime()};
|
||||
QVector<QVariantHash> newArticles;
|
||||
newArticles.reserve(loadedArticles.size());
|
||||
for (QVariantHash article : loadedArticles) {
|
||||
QVariant &torrentURL = article[Article::KeyTorrentURL];
|
||||
if (torrentURL.toString().isEmpty())
|
||||
torrentURL = article[Article::KeyLink];
|
||||
|
||||
// If item does not have an ID, fall back to some other identifier.
|
||||
QVariant &localId = article[Article::KeyId];
|
||||
if (localId.toString().isEmpty())
|
||||
localId = article.value(Article::KeyTorrentURL);
|
||||
if (localId.toString().isEmpty())
|
||||
localId = article.value(Article::KeyTitle);
|
||||
|
||||
if (localId.toString().isEmpty())
|
||||
continue;
|
||||
|
||||
// If article has no publication date we use feed update time as a fallback.
|
||||
// To prevent processing of "out-of-limit" articles we must not assign dates
|
||||
// that are earlier than the dates of existing articles.
|
||||
const Article *existingArticle = articleByGUID(localId.toString());
|
||||
if (existingArticle) {
|
||||
dummyPubDate = existingArticle->date().addMSecs(-1);
|
||||
continue;
|
||||
}
|
||||
|
||||
QVariant &articleDate = article[Article::KeyDate];
|
||||
if (!articleDate.toDateTime().isValid())
|
||||
articleDate = dummyPubDate;
|
||||
|
||||
newArticles.append(article);
|
||||
}
|
||||
|
||||
if (newArticles.empty())
|
||||
return 0;
|
||||
|
||||
using ArticleSortAdaptor = QPair<QDateTime, const QVariantHash *>;
|
||||
std::vector<ArticleSortAdaptor> sortData;
|
||||
const QList<Article *> existingArticles = articles();
|
||||
sortData.reserve(existingArticles.size() + newArticles.size());
|
||||
std::transform(existingArticles.begin(), existingArticles.end(), std::back_inserter(sortData)
|
||||
, [](const Article *article)
|
||||
{
|
||||
return qMakePair(article->date(), nullptr);
|
||||
});
|
||||
std::transform(newArticles.begin(), newArticles.end(), std::back_inserter(sortData)
|
||||
, [](const QVariantHash &article)
|
||||
{
|
||||
return qMakePair(article[Article::KeyDate].toDateTime(), &article);
|
||||
});
|
||||
|
||||
// Sort article list in reverse chronological order
|
||||
std::sort(sortData.begin(), sortData.end()
|
||||
, [](const ArticleSortAdaptor &a1, const ArticleSortAdaptor &a2)
|
||||
{
|
||||
return (a1.first > a2.first);
|
||||
});
|
||||
|
||||
if (sortData.size() > m_session->maxArticlesPerFeed())
|
||||
sortData.resize(m_session->maxArticlesPerFeed());
|
||||
|
||||
int newArticlesCount = 0;
|
||||
std::for_each(sortData.crbegin(), sortData.crend(), [this, &newArticlesCount](const ArticleSortAdaptor &a)
|
||||
{
|
||||
if (a.second) {
|
||||
addArticle(new Article {this, *a.second});
|
||||
++newArticlesCount;
|
||||
}
|
||||
});
|
||||
|
||||
return newArticlesCount;
|
||||
}
|
||||
|
||||
QString Feed::iconPath() const
|
||||
{
|
||||
return m_iconPath;
|
||||
@@ -414,25 +496,21 @@ QString Feed::iconPath() const
|
||||
|
||||
QJsonValue Feed::toJsonValue(bool withData) const
|
||||
{
|
||||
if (!withData) {
|
||||
// if feed alias is empty we create "reduced" JSON
|
||||
// value for it since its name is equal to its URL
|
||||
return (name() == url() ? "" : url());
|
||||
// if we'll need storing some more properties we should check
|
||||
// for its default values and produce JSON object instead of (if it's required)
|
||||
}
|
||||
|
||||
QJsonArray jsonArr;
|
||||
foreach (Article *article, m_articles)
|
||||
jsonArr << article->toJsonObject();
|
||||
|
||||
QJsonObject jsonObj;
|
||||
jsonObj.insert(Str_Url, url());
|
||||
jsonObj.insert(Str_Title, title());
|
||||
jsonObj.insert(Str_LastBuildDate, lastBuildDate());
|
||||
jsonObj.insert(Str_IsLoading, isLoading());
|
||||
jsonObj.insert(Str_HasError, hasError());
|
||||
jsonObj.insert(Str_Articles, jsonArr);
|
||||
jsonObj.insert(KEY_UID, uid().toString());
|
||||
jsonObj.insert(KEY_URL, url());
|
||||
|
||||
if (withData) {
|
||||
jsonObj.insert(KEY_TITLE, title());
|
||||
jsonObj.insert(KEY_LASTBUILDDATE, lastBuildDate());
|
||||
jsonObj.insert(KEY_ISLOADING, isLoading());
|
||||
jsonObj.insert(KEY_HASERROR, hasError());
|
||||
|
||||
QJsonArray jsonArr;
|
||||
for (Article *article : qAsConst(m_articles))
|
||||
jsonArr << article->toJsonObject();
|
||||
jsonObj.insert(KEY_ARTICLES, jsonArr);
|
||||
}
|
||||
|
||||
return jsonObj;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <QBasicTimer>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QUuid>
|
||||
|
||||
#include "rss_item.h"
|
||||
|
||||
@@ -56,7 +57,7 @@ namespace RSS
|
||||
|
||||
friend class Session;
|
||||
|
||||
Feed(const QString &url, const QString &path, Session *session);
|
||||
Feed(const QUuid &uid, const QString &url, const QString &path, Session *session);
|
||||
~Feed() override;
|
||||
|
||||
public:
|
||||
@@ -65,6 +66,7 @@ namespace RSS
|
||||
void markAsRead() override;
|
||||
void refresh() override;
|
||||
|
||||
QUuid uid() const;
|
||||
QString url() const;
|
||||
QString title() const;
|
||||
QString lastBuildDate() const;
|
||||
@@ -102,9 +104,11 @@ namespace RSS
|
||||
void increaseUnreadCount();
|
||||
void decreaseUnreadCount();
|
||||
void downloadIcon();
|
||||
int updateArticles(const QList<QVariantHash> &loadedArticles);
|
||||
|
||||
Session *m_session;
|
||||
Private::Parser *m_parser;
|
||||
const QUuid m_uid;
|
||||
const QString m_url;
|
||||
QString m_title;
|
||||
QString m_lastBuildDate;
|
||||
|
||||
@@ -166,7 +166,7 @@ bool Session::addFeed(const QString &url, const QString &path, QString *error)
|
||||
if (!destFolder)
|
||||
return false;
|
||||
|
||||
addItem(new Feed(url, path, this), destFolder);
|
||||
addItem(new Feed(generateUID(), url, path, this), destFolder);
|
||||
store();
|
||||
if (m_processingEnabled)
|
||||
feedByURL(url)->refresh();
|
||||
@@ -282,36 +282,61 @@ void Session::load()
|
||||
|
||||
void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
|
||||
{
|
||||
bool updated = false;
|
||||
foreach (const QString &key, jsonObj.keys()) {
|
||||
QJsonValue val = jsonObj[key];
|
||||
const QJsonValue val {jsonObj[key]};
|
||||
if (val.isString()) {
|
||||
// previous format (reduced form) doesn't contain UID
|
||||
QString url = val.toString();
|
||||
if (url.isEmpty())
|
||||
url = key;
|
||||
addFeedToFolder(url, key, folder);
|
||||
addFeedToFolder(generateUID(), url, key, folder);
|
||||
updated = true;
|
||||
}
|
||||
else if (!val.isObject()) {
|
||||
Logger::instance()->addMessage(
|
||||
QString("Couldn't load RSS Item '%1'. Invalid data format.")
|
||||
.arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING);
|
||||
}
|
||||
else {
|
||||
QJsonObject valObj = val.toObject();
|
||||
else if (val.isObject()) {
|
||||
const QJsonObject valObj {val.toObject()};
|
||||
if (valObj.contains("url")) {
|
||||
if (!valObj["url"].isString()) {
|
||||
Logger::instance()->addMessage(
|
||||
QString("Couldn't load RSS Feed '%1'. URL is required.")
|
||||
.arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING);
|
||||
LogMsg(QString("Couldn't load RSS Feed '%1'. URL is required.")
|
||||
.arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
addFeedToFolder(valObj["url"].toString(), key, folder);
|
||||
QUuid uid;
|
||||
if (valObj.contains("uid")) {
|
||||
uid = QUuid {valObj["uid"].toString()};
|
||||
if (uid.isNull()) {
|
||||
LogMsg(QString("Couldn't load RSS Feed '%1'. UID is invalid.")
|
||||
.arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_feedsByUID.contains(uid)) {
|
||||
LogMsg(QString("Duplicate RSS Feed UID: %1. Configuration seems to be corrupted.")
|
||||
.arg(uid.toString()), Log::WARNING);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// previous format doesn't contain UID
|
||||
uid = generateUID();
|
||||
updated = true;
|
||||
}
|
||||
|
||||
addFeedToFolder(uid, valObj["url"].toString(), key, folder);
|
||||
}
|
||||
else {
|
||||
loadFolder(valObj, addSubfolder(key, folder));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LogMsg(QString("Couldn't load RSS Item '%1'. Invalid data format.")
|
||||
.arg(QString("%1\\%2").arg(folder->path(), key)), Log::WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
store(); // convert to updated format
|
||||
}
|
||||
|
||||
void Session::loadLegacy()
|
||||
@@ -324,7 +349,7 @@ void Session::loadLegacy()
|
||||
}
|
||||
|
||||
uint i = 0;
|
||||
foreach (QString legacyPath, legacyFeedPaths) {
|
||||
for (QString legacyPath : legacyFeedPaths) {
|
||||
if (Item::PathSeparator == QString(legacyPath[0]))
|
||||
legacyPath.remove(0, 1);
|
||||
const QString parentFolderPath = Item::parentPath(legacyPath);
|
||||
@@ -380,9 +405,9 @@ Folder *Session::addSubfolder(const QString &name, Folder *parentFolder)
|
||||
return folder;
|
||||
}
|
||||
|
||||
Feed *Session::addFeedToFolder(const QString &url, const QString &name, Folder *parentFolder)
|
||||
Feed *Session::addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder)
|
||||
{
|
||||
auto feed = new Feed(url, Item::joinPath(parentFolder->path(), name), this);
|
||||
auto feed = new Feed(uid, url, Item::joinPath(parentFolder->path(), name), this);
|
||||
addItem(feed, parentFolder);
|
||||
return feed;
|
||||
}
|
||||
@@ -393,6 +418,7 @@ void Session::addItem(Item *item, Folder *destFolder)
|
||||
connect(feed, &Feed::titleChanged, this, &Session::handleFeedTitleChanged);
|
||||
connect(feed, &Feed::iconLoaded, this, &Session::feedIconLoaded);
|
||||
connect(feed, &Feed::stateChanged, this, &Session::feedStateChanged);
|
||||
m_feedsByUID[feed->uid()] = feed;
|
||||
m_feedsByURL[feed->url()] = feed;
|
||||
}
|
||||
|
||||
@@ -473,8 +499,10 @@ void Session::handleItemAboutToBeDestroyed(Item *item)
|
||||
{
|
||||
m_itemsByPath.remove(item->path());
|
||||
auto feed = qobject_cast<Feed *>(item);
|
||||
if (feed)
|
||||
if (feed) {
|
||||
m_feedsByUID.remove(feed->uid());
|
||||
m_feedsByURL.remove(feed->url());
|
||||
}
|
||||
}
|
||||
|
||||
void Session::handleFeedTitleChanged(Feed *feed)
|
||||
@@ -485,6 +513,15 @@ void Session::handleFeedTitleChanged(Feed *feed)
|
||||
moveItem(feed, Item::joinPath(Item::parentPath(feed->path()), feed->title()));
|
||||
}
|
||||
|
||||
QUuid Session::generateUID() const
|
||||
{
|
||||
QUuid uid = QUuid::createUuid();
|
||||
while (m_feedsByUID.contains(uid))
|
||||
uid = QUuid::createUuid();
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
int Session::maxArticlesPerFeed() const
|
||||
{
|
||||
return m_maxArticlesPerFeed;
|
||||
|
||||
@@ -37,13 +37,19 @@
|
||||
* {
|
||||
* "folder1": {
|
||||
* "subfolder1": {
|
||||
* "Feed name (Alias)": "http://some-feed-url1",
|
||||
* "http://some-feed-url2": ""
|
||||
* "Feed name 1 (Alias)": {
|
||||
* "uid": "feed unique identifier",
|
||||
* "url": "http://some-feed-url1"
|
||||
* }
|
||||
* "Feed name 2 (Alias)": {
|
||||
* "uid": "feed unique identifier",
|
||||
* "url": "http://some-feed-url2"
|
||||
* }
|
||||
* },
|
||||
* "subfolder2": {},
|
||||
* "http://some-feed-url3": "",
|
||||
* "Feed name (Alias)": {
|
||||
* "url": "http://some-feed-url4",
|
||||
* "Feed name 3 (Alias)": {
|
||||
* "uid": "feed unique identifier",
|
||||
* "url": "http://some-feed-url3"
|
||||
* }
|
||||
* },
|
||||
* "folder2": {},
|
||||
@@ -53,14 +59,12 @@
|
||||
*
|
||||
* 1. Document is JSON object (the same as Folder)
|
||||
* 2. Folder is JSON object (keys are Item names, values are Items)
|
||||
* 3.1. Feed is JSON object (keys are property names, values are property values; 'url' is required)
|
||||
* 3.2. (Reduced format) Feed is JSON string (string is URL unless it's empty, otherwise we take Feed URL from name)
|
||||
* 3. Feed is JSON object (keys are property names, values are property values; 'uid' and 'url' are required)
|
||||
*/
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
|
||||
class QThread;
|
||||
@@ -130,13 +134,14 @@ namespace RSS
|
||||
void handleFeedTitleChanged(Feed *feed);
|
||||
|
||||
private:
|
||||
QUuid generateUID() const;
|
||||
void load();
|
||||
void loadFolder(const QJsonObject &jsonObj, Folder *folder);
|
||||
void loadLegacy();
|
||||
void store();
|
||||
Folder *prepareItemDest(const QString &path, QString *error);
|
||||
Folder *addSubfolder(const QString &name, Folder *parentFolder);
|
||||
Feed *addFeedToFolder(const QString &url, const QString &name, Folder *parentFolder);
|
||||
Feed *addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder);
|
||||
void addItem(Item *item, Folder *destFolder);
|
||||
|
||||
static QPointer<Session> m_instance;
|
||||
@@ -149,6 +154,7 @@ namespace RSS
|
||||
uint m_refreshInterval;
|
||||
int m_maxArticlesPerFeed;
|
||||
QHash<QString, Item *> m_itemsByPath;
|
||||
QHash<QUuid, Feed *> m_feedsByUID;
|
||||
QHash<QString, Feed *> m_feedsByURL;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -212,11 +212,11 @@ ScanFoldersModel::PathStatus ScanFoldersModel::addPath(const QString &watchPath,
|
||||
if (!watchDir.exists()) return DoesNotExist;
|
||||
if (!watchDir.isReadable()) return CannotRead;
|
||||
|
||||
const QString &canonicalWatchPath = watchDir.canonicalPath();
|
||||
const QString canonicalWatchPath = watchDir.canonicalPath();
|
||||
if (findPathData(canonicalWatchPath) != -1) return AlreadyInList;
|
||||
|
||||
QDir downloadDir(downloadPath);
|
||||
const QString &canonicalDownloadPath = downloadDir.canonicalPath();
|
||||
const QString canonicalDownloadPath = downloadDir.canonicalPath();
|
||||
|
||||
if (!m_fsWatcher) {
|
||||
m_fsWatcher = new FileSystemWatcher(this);
|
||||
@@ -236,12 +236,12 @@ ScanFoldersModel::PathStatus ScanFoldersModel::addPath(const QString &watchPath,
|
||||
ScanFoldersModel::PathStatus ScanFoldersModel::updatePath(const QString &watchPath, const PathType &downloadType, const QString &downloadPath)
|
||||
{
|
||||
QDir watchDir(watchPath);
|
||||
const QString &canonicalWatchPath = watchDir.canonicalPath();
|
||||
const QString canonicalWatchPath = watchDir.canonicalPath();
|
||||
int row = findPathData(canonicalWatchPath);
|
||||
if (row == -1) return DoesNotExist;
|
||||
|
||||
QDir downloadDir(downloadPath);
|
||||
const QString &canonicalDownloadPath = downloadDir.canonicalPath();
|
||||
const QString canonicalDownloadPath = downloadDir.canonicalPath();
|
||||
|
||||
m_pathList.at(row)->downloadType = downloadType;
|
||||
m_pathList.at(row)->downloadPath = Utils::Fs::toNativePath(canonicalDownloadPath);
|
||||
@@ -256,7 +256,7 @@ void ScanFoldersModel::addToFSWatcher(const QStringList &watchPaths)
|
||||
|
||||
foreach (const QString &path, watchPaths) {
|
||||
QDir watchDir(path);
|
||||
const QString &canonicalWatchPath = watchDir.canonicalPath();
|
||||
const QString canonicalWatchPath = watchDir.canonicalPath();
|
||||
m_fsWatcher->addPath(canonicalWatchPath);
|
||||
}
|
||||
}
|
||||
@@ -388,7 +388,7 @@ void ScanFoldersModel::addTorrentsToSession(const QStringList &pathList)
|
||||
|
||||
QString ScanFoldersModel::pathTypeDisplayName(const PathType type)
|
||||
{
|
||||
switch(type) {
|
||||
switch (type) {
|
||||
case DOWNLOAD_IN_WATCH_FOLDER:
|
||||
return tr("Monitored folder");
|
||||
case DEFAULT_LOCATION:
|
||||
|
||||
@@ -104,7 +104,6 @@ private:
|
||||
QString downloadPathTorrentFolder(const QString &filePath) const;
|
||||
int findPathData(const QString &path) const;
|
||||
|
||||
private:
|
||||
static ScanFoldersModel *m_instance;
|
||||
struct PathData;
|
||||
|
||||
|
||||
@@ -30,8 +30,8 @@
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
#include "../utils/foreignapps.h"
|
||||
#include "../utils/fs.h"
|
||||
#include "../utils/misc.h"
|
||||
#include "searchpluginmanager.h"
|
||||
|
||||
SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QString &url, SearchPluginManager *manager)
|
||||
@@ -48,7 +48,7 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QStri
|
||||
url
|
||||
};
|
||||
// Launch search
|
||||
m_downloadProcess->start(Utils::Misc::pythonExecutable(), params, QIODevice::ReadOnly);
|
||||
m_downloadProcess->start(Utils::ForeignApps::pythonInfo().executableName, params, QIODevice::ReadOnly);
|
||||
}
|
||||
|
||||
void SearchDownloadHandler::downloadProcessFinished(int exitcode)
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
#include <QProcess>
|
||||
#include <QTimer>
|
||||
|
||||
#include "../utils/foreignapps.h"
|
||||
#include "../utils/fs.h"
|
||||
#include "../utils/misc.h"
|
||||
#include "searchpluginmanager.h"
|
||||
|
||||
namespace
|
||||
@@ -65,13 +65,13 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
|
||||
|
||||
const QStringList params {
|
||||
Utils::Fs::toNativePath(m_manager->engineLocation() + "/nova2.py"),
|
||||
m_usedPlugins.join(","),
|
||||
m_usedPlugins.join(','),
|
||||
m_category
|
||||
};
|
||||
|
||||
// Launch search
|
||||
m_searchProcess->setProgram(Utils::Misc::pythonExecutable());
|
||||
m_searchProcess->setArguments(params + m_pattern.split(" "));
|
||||
m_searchProcess->setProgram(Utils::ForeignApps::pythonInfo().executableName);
|
||||
m_searchProcess->setArguments(params + m_pattern.split(' '));
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
connect(m_searchProcess, &QProcess::errorOccurred, this, &SearchHandler::processFailed);
|
||||
@@ -161,7 +161,7 @@ void SearchHandler::processFailed()
|
||||
// file url | file name | file size | nb seeds | nb leechers | Search engine url
|
||||
bool SearchHandler::parseSearchResult(const QString &line, SearchResult &searchResult)
|
||||
{
|
||||
const QStringList parts = line.split("|");
|
||||
const QStringList parts = line.split('|');
|
||||
const int nbFields = parts.size();
|
||||
if (nbFields < (NB_PLUGIN_COLUMNS - 1)) return false; // -1 because desc_link is optional
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QDomDocument>
|
||||
#include <QDomElement>
|
||||
#include <QDomNode>
|
||||
@@ -46,6 +47,8 @@
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/profile.h"
|
||||
#include "base/utils/bytearray.h"
|
||||
#include "base/utils/foreignapps.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "searchdownloadhandler.h"
|
||||
@@ -53,17 +56,36 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
inline void removePythonScriptIfExists(const QString &scriptPath)
|
||||
void clearPythonCache(const QString &path)
|
||||
{
|
||||
Utils::Fs::forceRemove(scriptPath);
|
||||
Utils::Fs::forceRemove(scriptPath + "c");
|
||||
// remove python cache artifacts in `path` and subdirs
|
||||
|
||||
QStringList dirs = {path};
|
||||
QDirIterator iter {path, (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
|
||||
while (iter.hasNext())
|
||||
dirs += iter.next();
|
||||
|
||||
for (const QString &dir : qAsConst(dirs)) {
|
||||
// python 3: remove "__pycache__" folders
|
||||
if (dir.endsWith("/__pycache__")) {
|
||||
Utils::Fs::removeDirRecursive(dir);
|
||||
continue;
|
||||
}
|
||||
|
||||
// python 2: remove "*.pyc" files
|
||||
const QStringList files = QDir(dir).entryList(QDir::Files);
|
||||
for (const QString &file : files) {
|
||||
if (file.endsWith(".pyc"))
|
||||
Utils::Fs::forceRemove(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPointer<SearchPluginManager> SearchPluginManager::m_instance = nullptr;
|
||||
|
||||
SearchPluginManager::SearchPluginManager()
|
||||
: m_updateUrl(QString("http://searchplugins.qbittorrent.org/%1/engines/").arg(Utils::Misc::pythonVersion() >= 3 ? "nova3" : "nova"))
|
||||
: m_updateUrl(QString("http://searchplugins.qbittorrent.org/%1/engines/").arg(Utils::ForeignApps::pythonInfo().version.majorNumber() >= 3 ? "nova3" : "nova"))
|
||||
{
|
||||
Q_ASSERT(!m_instance); // only one instance is allowed
|
||||
m_instance = this;
|
||||
@@ -124,8 +146,8 @@ QStringList SearchPluginManager::getPluginCategories(const QString &pluginName)
|
||||
plugins << pluginName.trimmed();
|
||||
|
||||
QSet<QString> categories;
|
||||
for (const QString &pluginName : qAsConst(plugins)) {
|
||||
const PluginInfo *plugin = pluginInfo(pluginName);
|
||||
for (const QString &name : qAsConst(plugins)) {
|
||||
const PluginInfo *plugin = pluginInfo(name);
|
||||
if (!plugin) continue; // plugin wasn't found
|
||||
for (const QString &category : plugin->supportedCategories)
|
||||
categories << category;
|
||||
@@ -166,11 +188,11 @@ void SearchPluginManager::updatePlugin(const QString &name)
|
||||
// Install or update plugin from file or url
|
||||
void SearchPluginManager::installPlugin(const QString &source)
|
||||
{
|
||||
qDebug("Asked to install plugin at %s", qUtf8Printable(source));
|
||||
clearPythonCache(engineLocation());
|
||||
|
||||
if (Utils::Misc::isUrl(source)) {
|
||||
using namespace Net;
|
||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(source, true);
|
||||
DownloadHandler *handler = DownloadManager::instance()->download(DownloadRequest(source).saveToFile(true));
|
||||
connect(handler, static_cast<void (DownloadHandler::*)(const QString &, const QString &)>(&DownloadHandler::downloadFinished)
|
||||
, this, &SearchPluginManager::pluginDownloaded);
|
||||
connect(handler, &DownloadHandler::downloadFailed, this, &SearchPluginManager::pluginDownloadFailed);
|
||||
@@ -181,7 +203,7 @@ void SearchPluginManager::installPlugin(const QString &source)
|
||||
path = QUrl(path).toLocalFile();
|
||||
|
||||
QString pluginName = Utils::Fs::fileName(path);
|
||||
pluginName.chop(pluginName.size() - pluginName.lastIndexOf("."));
|
||||
pluginName.chop(pluginName.size() - pluginName.lastIndexOf('.'));
|
||||
|
||||
if (!path.endsWith(".py", Qt::CaseInsensitive))
|
||||
emit pluginInstallationFailed(pluginName, tr("Unknown search engine plugin file format."));
|
||||
@@ -192,12 +214,10 @@ void SearchPluginManager::installPlugin(const QString &source)
|
||||
|
||||
void SearchPluginManager::installPlugin_impl(const QString &name, const QString &path)
|
||||
{
|
||||
PluginVersion newVersion = getPluginVersion(path);
|
||||
qDebug() << "Version to be installed:" << newVersion;
|
||||
|
||||
const PluginVersion newVersion = getPluginVersion(path);
|
||||
PluginInfo *plugin = pluginInfo(name);
|
||||
if (plugin && !(plugin->version < newVersion)) {
|
||||
qDebug("Apparently update is not needed, we have a more recent version");
|
||||
LogMsg(tr("Plugin already at version %1, which is greater than %2").arg(plugin->version, newVersion), Log::INFO);
|
||||
emit pluginUpdateFailed(name, tr("A more recent version of this plugin is already installed."));
|
||||
return;
|
||||
}
|
||||
@@ -209,7 +229,6 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
|
||||
// Backup in case install fails
|
||||
QFile::copy(destPath, destPath + ".bak");
|
||||
Utils::Fs::forceRemove(destPath);
|
||||
Utils::Fs::forceRemove(destPath + "c");
|
||||
updated = true;
|
||||
}
|
||||
// Copy the plugin
|
||||
@@ -220,6 +239,7 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
|
||||
if (!m_plugins.contains(name)) {
|
||||
// Remove broken file
|
||||
Utils::Fs::forceRemove(destPath);
|
||||
LogMsg(tr("Plugin %1 is not supported.").arg(name), Log::INFO);
|
||||
if (updated) {
|
||||
// restore backup
|
||||
QFile::copy(destPath + ".bak", destPath);
|
||||
@@ -234,13 +254,17 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
|
||||
}
|
||||
else {
|
||||
// Install was successful, remove backup
|
||||
if (updated)
|
||||
if (updated) {
|
||||
LogMsg(tr("Plugin %1 has been successfully updated.").arg(name), Log::INFO);
|
||||
Utils::Fs::forceRemove(destPath + ".bak");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SearchPluginManager::uninstallPlugin(const QString &name)
|
||||
{
|
||||
clearPythonCache(engineLocation());
|
||||
|
||||
// remove it from hard drive
|
||||
QDir pluginsFolder(pluginsLocation());
|
||||
QStringList filters;
|
||||
@@ -274,7 +298,7 @@ void SearchPluginManager::checkForUpdates()
|
||||
{
|
||||
// Download version file from update server
|
||||
using namespace Net;
|
||||
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(m_updateUrl + "versions.txt");
|
||||
DownloadHandler *handler = DownloadManager::instance()->download({m_updateUrl + "versions.txt"});
|
||||
connect(handler, static_cast<void (DownloadHandler::*)(const QString &, const QByteArray &)>(&DownloadHandler::downloadFinished)
|
||||
, this, &SearchPluginManager::versionInfoDownloaded);
|
||||
connect(handler, &DownloadHandler::downloadFailed, this, &SearchPluginManager::versionInfoDownloadFailed);
|
||||
@@ -321,13 +345,16 @@ QString SearchPluginManager::pluginsLocation()
|
||||
|
||||
QString SearchPluginManager::engineLocation()
|
||||
{
|
||||
QString folder = "nova";
|
||||
if (Utils::Misc::pythonVersion() >= 3)
|
||||
folder = "nova3";
|
||||
const QString location = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + folder);
|
||||
QDir locationDir(location);
|
||||
if (!locationDir.exists())
|
||||
static QString location;
|
||||
if (location.isEmpty()) {
|
||||
const QString folder = (Utils::ForeignApps::pythonInfo().version.majorNumber() >= 3)
|
||||
? "nova3" : "nova";
|
||||
location = Utils::Fs::expandPathAbs(specialFolderLocation(SpecialFolder::Data) + folder);
|
||||
|
||||
const QDir locationDir(location);
|
||||
locationDir.mkpath(locationDir.absolutePath());
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
@@ -348,7 +375,7 @@ void SearchPluginManager::pluginDownloaded(const QString &url, QString filePath)
|
||||
filePath = Utils::Fs::fromNativePath(filePath);
|
||||
|
||||
QString pluginName = Utils::Fs::fileName(url);
|
||||
pluginName.chop(pluginName.size() - pluginName.lastIndexOf(".")); // Remove extension
|
||||
pluginName.chop(pluginName.size() - pluginName.lastIndexOf('.')); // Remove extension
|
||||
installPlugin_impl(pluginName, filePath);
|
||||
Utils::Fs::forceRemove(filePath);
|
||||
}
|
||||
@@ -366,81 +393,56 @@ void SearchPluginManager::pluginDownloadFailed(const QString &url, const QString
|
||||
// Update nova.py search plugin if necessary
|
||||
void SearchPluginManager::updateNova()
|
||||
{
|
||||
qDebug("Updating nova");
|
||||
|
||||
// create nova directory if necessary
|
||||
QDir searchDir(engineLocation());
|
||||
QString novaFolder = Utils::Misc::pythonVersion() >= 3 ? "searchengine/nova3" : "searchengine/nova";
|
||||
const QDir searchDir(engineLocation());
|
||||
const QString novaFolder = Utils::ForeignApps::pythonInfo().version.majorNumber() >= 3
|
||||
? "searchengine/nova3" : "searchengine/nova";
|
||||
|
||||
QFile packageFile(searchDir.absoluteFilePath("__init__.py"));
|
||||
packageFile.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
packageFile.open(QIODevice::WriteOnly);
|
||||
packageFile.close();
|
||||
if (!searchDir.exists("engines"))
|
||||
searchDir.mkdir("engines");
|
||||
Utils::Fs::removeDirRecursive(searchDir.absoluteFilePath("__pycache__"));
|
||||
|
||||
searchDir.mkdir("engines");
|
||||
|
||||
QFile packageFile2(searchDir.absolutePath() + "/engines/__init__.py");
|
||||
packageFile2.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
packageFile2.open(QIODevice::WriteOnly);
|
||||
packageFile2.close();
|
||||
|
||||
// Copy search plugin files (if necessary)
|
||||
QString filePath = searchDir.absoluteFilePath("nova2.py");
|
||||
if (getPluginVersion(":/" + novaFolder + "/nova2.py") > getPluginVersion(filePath)) {
|
||||
removePythonScriptIfExists(filePath);
|
||||
QFile::copy(":/" + novaFolder + "/nova2.py", filePath);
|
||||
}
|
||||
const auto updateFile = [&novaFolder](const QString &filename, const bool compareVersion)
|
||||
{
|
||||
const QString filePathBundled = ":/" + novaFolder + '/' + filename;
|
||||
const QString filePathDisk = QDir(engineLocation()).absoluteFilePath(filename);
|
||||
|
||||
filePath = searchDir.absoluteFilePath("nova2dl.py");
|
||||
if (getPluginVersion(":/" + novaFolder + "/nova2dl.py") > getPluginVersion(filePath)) {
|
||||
removePythonScriptIfExists(filePath);
|
||||
QFile::copy(":/" + novaFolder + "/nova2dl.py", filePath);
|
||||
}
|
||||
if (compareVersion && (getPluginVersion(filePathBundled) <= getPluginVersion(filePathDisk)))
|
||||
return;
|
||||
|
||||
filePath = searchDir.absoluteFilePath("fix_encoding.py");
|
||||
QFile::copy(":/" + novaFolder + "/fix_encoding.py", filePath);
|
||||
Utils::Fs::forceRemove(filePathDisk);
|
||||
QFile::copy(filePathBundled, filePathDisk);
|
||||
};
|
||||
|
||||
filePath = searchDir.absoluteFilePath("novaprinter.py");
|
||||
if (getPluginVersion(":/" + novaFolder + "/novaprinter.py") > getPluginVersion(filePath)) {
|
||||
removePythonScriptIfExists(filePath);
|
||||
QFile::copy(":/" + novaFolder + "/novaprinter.py", filePath);
|
||||
}
|
||||
updateFile("helpers.py", true);
|
||||
updateFile("nova2.py", true);
|
||||
updateFile("nova2dl.py", true);
|
||||
updateFile("novaprinter.py", true);
|
||||
updateFile("socks.py", false);
|
||||
|
||||
filePath = searchDir.absoluteFilePath("helpers.py");
|
||||
if (getPluginVersion(":/" + novaFolder + "/helpers.py") > getPluginVersion(filePath)) {
|
||||
removePythonScriptIfExists(filePath);
|
||||
QFile::copy(":/" + novaFolder + "/helpers.py", filePath);
|
||||
}
|
||||
|
||||
filePath = searchDir.absoluteFilePath("socks.py");
|
||||
removePythonScriptIfExists(filePath);
|
||||
QFile::copy(":/" + novaFolder + "/socks.py", filePath);
|
||||
|
||||
if (novaFolder.endsWith("nova")) {
|
||||
filePath = searchDir.absoluteFilePath("fix_encoding.py");
|
||||
removePythonScriptIfExists(filePath);
|
||||
QFile::copy(":/" + novaFolder + "/fix_encoding.py", filePath);
|
||||
}
|
||||
else if (novaFolder.endsWith("nova3")) {
|
||||
filePath = searchDir.absoluteFilePath("sgmllib3.py");
|
||||
removePythonScriptIfExists(filePath);
|
||||
QFile::copy(":/" + novaFolder + "/sgmllib3.py", filePath);
|
||||
}
|
||||
|
||||
QDir destDir(pluginsLocation());
|
||||
Utils::Fs::removeDirRecursive(destDir.absoluteFilePath("__pycache__"));
|
||||
if (Utils::ForeignApps::pythonInfo().version.majorNumber() >= 3)
|
||||
updateFile("sgmllib3.py", false);
|
||||
else
|
||||
updateFile("fix_encoding.py", false);
|
||||
}
|
||||
|
||||
void SearchPluginManager::update()
|
||||
{
|
||||
QProcess nova;
|
||||
nova.setEnvironment(QProcess::systemEnvironment());
|
||||
QStringList params;
|
||||
params << Utils::Fs::toNativePath(engineLocation() + "/nova2.py");
|
||||
params << "--capabilities";
|
||||
nova.start(Utils::Misc::pythonExecutable(), params, QIODevice::ReadOnly);
|
||||
nova.waitForStarted();
|
||||
nova.setProcessEnvironment(QProcessEnvironment::systemEnvironment());
|
||||
|
||||
const QStringList params {Utils::Fs::toNativePath(engineLocation() + "/nova2.py"), "--capabilities"};
|
||||
nova.start(Utils::ForeignApps::pythonInfo().executableName, params, QIODevice::ReadOnly);
|
||||
nova.waitForFinished();
|
||||
|
||||
QString capabilities = QString(nova.readAll());
|
||||
QString capabilities = nova.readAll();
|
||||
QDomDocument xmlDoc;
|
||||
if (!xmlDoc.setContent(capabilities)) {
|
||||
qWarning() << "Could not parse Nova search engine capabilities, msg: " << capabilities.toLocal8Bit().data();
|
||||
@@ -465,7 +467,8 @@ void SearchPluginManager::update()
|
||||
plugin->fullName = engineElem.elementsByTagName("name").at(0).toElement().text();
|
||||
plugin->url = engineElem.elementsByTagName("url").at(0).toElement().text();
|
||||
|
||||
foreach (QString cat, engineElem.elementsByTagName("categories").at(0).toElement().text().split(" ")) {
|
||||
const auto categories = engineElem.elementsByTagName("categories").at(0).toElement().text().split(' ');
|
||||
for (QString cat : categories) {
|
||||
cat = cat.trimmed();
|
||||
if (!cat.isEmpty())
|
||||
plugin->supportedCategories << cat;
|
||||
@@ -491,37 +494,37 @@ void SearchPluginManager::update()
|
||||
|
||||
void SearchPluginManager::parseVersionInfo(const QByteArray &info)
|
||||
{
|
||||
qDebug("Checking if update is needed");
|
||||
|
||||
QHash<QString, PluginVersion> updateInfo;
|
||||
bool dataCorrect = false;
|
||||
QList<QByteArray> lines = info.split('\n');
|
||||
foreach (QByteArray line, lines) {
|
||||
int numCorrectData = 0;
|
||||
|
||||
const QList<QByteArray> lines = Utils::ByteArray::splitToViews(info, "\n", QString::SkipEmptyParts);
|
||||
for (QByteArray line : lines) {
|
||||
line = line.trimmed();
|
||||
if (line.isEmpty()) continue;
|
||||
if (line.startsWith("#")) continue;
|
||||
if (line.startsWith('#')) continue;
|
||||
|
||||
QList<QByteArray> list = line.split(' ');
|
||||
const QList<QByteArray> list = Utils::ByteArray::splitToViews(line, ":", QString::SkipEmptyParts);
|
||||
if (list.size() != 2) continue;
|
||||
|
||||
QString pluginName = QString(list.first());
|
||||
if (!pluginName.endsWith(":")) continue;
|
||||
const QString pluginName = list.first().trimmed();
|
||||
const PluginVersion version = PluginVersion::tryParse(list.last().trimmed(), {});
|
||||
|
||||
pluginName.chop(1); // remove trailing ':'
|
||||
PluginVersion version = PluginVersion::tryParse(list.last(), {});
|
||||
if (version == PluginVersion()) continue;
|
||||
if (!version.isValid()) continue;
|
||||
|
||||
dataCorrect = true;
|
||||
++numCorrectData;
|
||||
if (isUpdateNeeded(pluginName, version)) {
|
||||
qDebug("Plugin: %s is outdated", qUtf8Printable(pluginName));
|
||||
LogMsg(tr("Plugin \"%1\" is outdated, updating to version %2").arg(pluginName, version), Log::INFO);
|
||||
updateInfo[pluginName] = version;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dataCorrect)
|
||||
emit checkForUpdatesFailed(tr("An incorrect update info received."));
|
||||
else
|
||||
if (numCorrectData < lines.size()) {
|
||||
emit checkForUpdatesFailed(tr("Incorrect update info received for %1 out of %2 plugins.")
|
||||
.arg(QString::number(lines.size() - numCorrectData), QString::number(lines.size())));
|
||||
}
|
||||
else {
|
||||
emit checkForUpdatesFinished(updateInfo);
|
||||
}
|
||||
}
|
||||
|
||||
bool SearchPluginManager::isUpdateNeeded(QString pluginName, PluginVersion newVersion) const
|
||||
@@ -530,7 +533,6 @@ bool SearchPluginManager::isUpdateNeeded(QString pluginName, PluginVersion newVe
|
||||
if (!plugin) return true;
|
||||
|
||||
PluginVersion oldVersion = plugin->version;
|
||||
qDebug() << "IsUpdate needed? to be installed:" << newVersion << ", already installed:" << oldVersion;
|
||||
return (newVersion > oldVersion);
|
||||
}
|
||||
|
||||
@@ -539,34 +541,25 @@ QString SearchPluginManager::pluginPath(const QString &name)
|
||||
return QString("%1/%2.py").arg(pluginsLocation(), name);
|
||||
}
|
||||
|
||||
PluginVersion SearchPluginManager::getPluginVersion(QString filePath)
|
||||
PluginVersion SearchPluginManager::getPluginVersion(const QString &filePath)
|
||||
{
|
||||
QFile plugin(filePath);
|
||||
if (!plugin.exists()) {
|
||||
qDebug("%s plugin does not exist, returning 0.0", qUtf8Printable(filePath));
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!plugin.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
QFile pluginFile(filePath);
|
||||
if (!pluginFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
return {};
|
||||
|
||||
const PluginVersion invalidVersion;
|
||||
while (!pluginFile.atEnd()) {
|
||||
const QString line = QString(pluginFile.readLine()).remove(' ');
|
||||
if (!line.startsWith("#VERSION:", Qt::CaseInsensitive)) continue;
|
||||
|
||||
PluginVersion version;
|
||||
while (!plugin.atEnd()) {
|
||||
QByteArray line = plugin.readLine();
|
||||
if (line.startsWith("#VERSION: ")) {
|
||||
line = line.split(' ').last().trimmed();
|
||||
version = PluginVersion::tryParse(line, invalidVersion);
|
||||
if (version == invalidVersion) {
|
||||
LogMsg(tr("Search plugin '%1' contains invalid version string ('%2')")
|
||||
.arg(Utils::Fs::fileName(filePath), QString::fromUtf8(line)), Log::MsgType::WARNING);
|
||||
}
|
||||
else {
|
||||
qDebug() << "plugin" << filePath << "version: " << version;
|
||||
}
|
||||
break;
|
||||
}
|
||||
const QString versionStr = line.mid(9);
|
||||
const PluginVersion version = PluginVersion::tryParse(versionStr, {});
|
||||
if (version.isValid())
|
||||
return version;
|
||||
|
||||
LogMsg(tr("Search plugin '%1' contains invalid version string ('%2')")
|
||||
.arg(Utils::Fs::fileName(filePath), versionStr), Log::MsgType::WARNING);
|
||||
break;
|
||||
}
|
||||
return version;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public:
|
||||
SearchHandler *startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins);
|
||||
SearchDownloadHandler *downloadTorrent(const QString &siteUrl, const QString &url);
|
||||
|
||||
static PluginVersion getPluginVersion(QString filePath);
|
||||
static PluginVersion getPluginVersion(const QString &filePath);
|
||||
static QString categoryFullName(const QString &categoryName);
|
||||
QString pluginFullName(const QString &pluginName);
|
||||
static QString pluginsLocation();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014 sledgehammer999 <sledgehammer999@qbittorrent.org>
|
||||
* Copyright (C) 2014 sledgehammer999 <hammered999@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -32,7 +32,6 @@
|
||||
#include <memory>
|
||||
#include <QFile>
|
||||
#include <QHash>
|
||||
#include <QStringList>
|
||||
|
||||
#include "logger.h"
|
||||
#include "profile.h"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2016 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014 sledgehammer999 <sledgehammer999@qbittorrent.org>
|
||||
* Copyright (C) 2014 sledgehammer999 <hammered999@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
|
||||
@@ -26,9 +26,10 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "bittorrent/torrenthandle.h"
|
||||
#include "torrentfilter.h"
|
||||
|
||||
#include "bittorrent/torrenthandle.h"
|
||||
|
||||
const QString TorrentFilter::AnyCategory;
|
||||
const QStringSet TorrentFilter::AnyHash = (QStringSet() << QString());
|
||||
const QString TorrentFilter::AnyTag;
|
||||
@@ -137,14 +138,14 @@ bool TorrentFilter::setTag(const QString &tag)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TorrentFilter::match(TorrentHandle *const torrent) const
|
||||
bool TorrentFilter::match(const TorrentHandle *const torrent) const
|
||||
{
|
||||
if (!torrent) return false;
|
||||
|
||||
return (matchState(torrent) && matchHash(torrent) && matchCategory(torrent) && matchTag(torrent));
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchState(BitTorrent::TorrentHandle *const torrent) const
|
||||
bool TorrentFilter::matchState(const BitTorrent::TorrentHandle *const torrent) const
|
||||
{
|
||||
switch (m_type) {
|
||||
case All:
|
||||
@@ -170,19 +171,19 @@ bool TorrentFilter::matchState(BitTorrent::TorrentHandle *const torrent) const
|
||||
}
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchHash(BitTorrent::TorrentHandle *const torrent) const
|
||||
bool TorrentFilter::matchHash(const BitTorrent::TorrentHandle *const torrent) const
|
||||
{
|
||||
if (m_hashSet == AnyHash) return true;
|
||||
else return m_hashSet.contains(torrent->hash());
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchCategory(BitTorrent::TorrentHandle *const torrent) const
|
||||
bool TorrentFilter::matchCategory(const BitTorrent::TorrentHandle *const torrent) const
|
||||
{
|
||||
if (m_category.isNull()) return true;
|
||||
else return (torrent->belongsToCategory(m_category));
|
||||
}
|
||||
|
||||
bool TorrentFilter::matchTag(BitTorrent::TorrentHandle *const torrent) const
|
||||
bool TorrentFilter::matchTag(const BitTorrent::TorrentHandle *const torrent) const
|
||||
{
|
||||
// Empty tag is a special value to indicate we're filtering for untagged torrents.
|
||||
if (m_tag.isNull()) return true;
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
// These mean any permutation, including no category / tag.
|
||||
static const QString AnyCategory;
|
||||
static const QStringSet AnyHash;
|
||||
static const QString AnyTag;
|
||||
static const QString AnyTag;
|
||||
|
||||
static const TorrentFilter DownloadingTorrent;
|
||||
static const TorrentFilter SeedingTorrent;
|
||||
@@ -81,13 +81,13 @@ public:
|
||||
bool setCategory(const QString &category);
|
||||
bool setTag(const QString &tag);
|
||||
|
||||
bool match(BitTorrent::TorrentHandle *const torrent) const;
|
||||
bool match(const BitTorrent::TorrentHandle *torrent) const;
|
||||
|
||||
private:
|
||||
bool matchState(BitTorrent::TorrentHandle *const torrent) const;
|
||||
bool matchHash(BitTorrent::TorrentHandle *const torrent) const;
|
||||
bool matchCategory(BitTorrent::TorrentHandle *const torrent) const;
|
||||
bool matchTag(BitTorrent::TorrentHandle *const torrent) const;
|
||||
bool matchState(const BitTorrent::TorrentHandle *torrent) const;
|
||||
bool matchHash(const BitTorrent::TorrentHandle *torrent) const;
|
||||
bool matchCategory(const BitTorrent::TorrentHandle *torrent) const;
|
||||
bool matchTag(const BitTorrent::TorrentHandle *torrent) const;
|
||||
|
||||
Type m_type;
|
||||
QString m_category;
|
||||
|
||||
@@ -31,28 +31,3 @@
|
||||
const TriStateBool TriStateBool::Undefined(-1);
|
||||
const TriStateBool TriStateBool::False(0);
|
||||
const TriStateBool TriStateBool::True(1);
|
||||
|
||||
TriStateBool::operator int() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
TriStateBool::TriStateBool(int value)
|
||||
{
|
||||
if (value < 0)
|
||||
m_value = -1;
|
||||
else if (value > 0)
|
||||
m_value = 1;
|
||||
else
|
||||
m_value = 0;
|
||||
}
|
||||
|
||||
bool TriStateBool::operator==(const TriStateBool &other) const
|
||||
{
|
||||
return (m_value == other.m_value);
|
||||
}
|
||||
|
||||
bool TriStateBool::operator!=(const TriStateBool &other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
@@ -36,15 +36,29 @@ public:
|
||||
static const TriStateBool False;
|
||||
static const TriStateBool True;
|
||||
|
||||
TriStateBool() = default;
|
||||
TriStateBool(const TriStateBool &other) = default;
|
||||
constexpr TriStateBool() = default;
|
||||
constexpr TriStateBool(const TriStateBool &other) = default;
|
||||
explicit constexpr TriStateBool(int value)
|
||||
: m_value(value < 0 ? -1 : (value > 0 ? 1 : 0))
|
||||
{
|
||||
}
|
||||
|
||||
explicit TriStateBool(int value);
|
||||
explicit operator int() const;
|
||||
explicit constexpr operator int() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
TriStateBool &operator=(const TriStateBool &other) = default;
|
||||
bool operator==(const TriStateBool &other) const;
|
||||
bool operator!=(const TriStateBool &other) const;
|
||||
TriStateBool &operator=(const TriStateBool &other) = default; // add constexpr when using C++14
|
||||
|
||||
constexpr bool operator==(const TriStateBool &other) const
|
||||
{
|
||||
return (m_value == other.m_value);
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const TriStateBool &other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
private:
|
||||
signed char m_value = -1; // Undefined by default
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// This file must be encoded in "UTF-8 with BOM"
|
||||
#ifdef _MSC_VER
|
||||
#pragma execution_character_set("utf-8")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user