You've already forked qBittorrent
mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-10-12 03:12:18 +02:00
Compare commits
63 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 |
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
|
||||
|
31
.travis.yml
31
.travis.yml
@@ -3,7 +3,6 @@ language: cpp
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
osx_image: xcode7.3
|
||||
|
||||
env:
|
||||
matrix:
|
||||
@@ -121,40 +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/5146d2df7e48f321511273fb9829ebdc3a6dc519f9ef36a344a343ae524c3ae6--libtorrent-rasterbar-1.1.9+git20180812.0bcf6cef23+patched-configure.el_capitan.bottle.tar.gz
|
||||
fi
|
||||
|
||||
# Copy custom libtorrent bottle to homebrew's download 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/5146d2df7e48f321511273fb9829ebdc3a6dc519f9ef36a344a343ae524c3ae6--libtorrent-rasterbar-1.1.9+git20180812.0bcf6cef23+patched-configure.el_capitan.bottle.tar.gz" "$(brew --cache)/downloads"
|
||||
brew install "$HOME/hombebrew_cache/libtorrent-rasterbar.rb"
|
||||
|
||||
# NOTE about the bottle name
|
||||
# The part before the "--" characters is a sha256 hash of the string
|
||||
# of the URL homebrew itself would use to download the bottle.
|
||||
# In this case the URL is the following:
|
||||
# http://127.0.0.1/libtorrent-rasterbar-1.1.9+git20180812.0bcf6cef23+patched-configure.el_capitan.bottle.tar.gz
|
||||
|
||||
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/"
|
||||
|
@@ -1,6 +1,6 @@
|
||||
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)
|
||||
|
39
Changelog
39
Changelog
@@ -1,3 +1,42 @@
|
||||
* 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)
|
||||
|
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()
|
24
configure
vendored
24
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.3.
|
||||
# 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.3'
|
||||
PACKAGE_STRING='qbittorrent v4.1.3'
|
||||
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.3 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.3:";;
|
||||
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.3
|
||||
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.3, 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.3'
|
||||
VERSION='v4.1.4'
|
||||
|
||||
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
@@ -6174,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.3, 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
|
||||
@@ -6232,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.3
|
||||
qbittorrent config.status v4.1.4
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
@@ -7489,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.3, 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
|
||||
@@ -7547,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.3
|
||||
qbittorrent config.status v4.1.4
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
AC_INIT([qbittorrent], [v4.1.3], [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
|
||||
|
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.3</string>
|
||||
<string>4.1.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>qBit</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
|
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.3"
|
||||
!define PROG_VERSION "4.1.4"
|
||||
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
||||
|
@@ -1,5 +1,13 @@
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
set(CMAKE_CXX_STANDARD "11")
|
||||
|
||||
# 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()
|
||||
@@ -13,7 +21,7 @@ if (Boost_VERSION VERSION_LESS 106000)
|
||||
add_definitions(-DBOOST_NO_CXX11_RVALUE_REFERENCES)
|
||||
endif()
|
||||
|
||||
find_package(Qt5 ${requiredQtVersion} REQUIRED COMPONENTS Core Network Xml)
|
||||
find_package(Qt5 ${requiredQtVersion} REQUIRED COMPONENTS Core Network Xml LinguistTools)
|
||||
find_package(Qt5Widgets ${requiredQtVersion})
|
||||
if (Qt5Widgets_FOUND)
|
||||
find_package(Qt5DBus ${requiredQtVersion})
|
||||
|
@@ -23,29 +23,19 @@ set_target_properties(qBittorrent
|
||||
)
|
||||
|
||||
# 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/lang.qrc" ABSOLUTE)
|
||||
get_filename_component(_lang_qrc_dst "${CMAKE_CURRENT_BINARY_DIR}/../lang/lang.qrc" ABSOLUTE)
|
||||
get_filename_component(_lang_qrc_dst_dir "${CMAKE_CURRENT_BINARY_DIR}/../lang" 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/icons.qrc
|
||||
../searchengine/searchengine.qrc
|
||||
"${_lang_qrc_dst}"
|
||||
)
|
||||
|
||||
# With AUTORCC rcc is ran by cmake before language files are generated,
|
||||
|
@@ -72,6 +72,7 @@
|
||||
#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"
|
||||
@@ -514,6 +515,7 @@ 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
|
||||
@@ -708,6 +710,7 @@ void Application::cleanup()
|
||||
delete m_webui;
|
||||
#endif
|
||||
|
||||
delete SearchPluginManager::instance();
|
||||
delete RSS::AutoDownloader::instance();
|
||||
delete RSS::Session::instance();
|
||||
|
||||
|
@@ -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
|
||||
@@ -2083,10 +2087,10 @@ 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)) {
|
||||
|
||||
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 =
|
||||
@@ -2096,13 +2100,13 @@ bool Session::addTorrent(QString source, const AddTorrentParams ¶ms)
|
||||
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;
|
||||
@@ -2190,15 +2194,6 @@ bool Session::addTorrent_impl(CreateTorrentParams params, const MagnetUri &magne
|
||||
return false;
|
||||
}
|
||||
|
||||
if (params.restored && !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 {
|
||||
p.file_priorities = {params.filePriorities.begin(), params.filePriorities.end()};
|
||||
}
|
||||
|
||||
// We should not add torrent if it already
|
||||
// processed or adding to session
|
||||
if (m_addingTorrents.contains(hash) || m_loadedMetadata.contains(hash)) return false;
|
||||
@@ -2232,6 +2227,23 @@ bool Session::addTorrent_impl(CreateTorrentParams params, const MagnetUri &magne
|
||||
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();
|
||||
@@ -2648,7 +2660,7 @@ int Session::downloadSpeedLimit() const
|
||||
: globalDownloadSpeedLimit();
|
||||
}
|
||||
|
||||
void Session::setDownloadSpeedLimit(int limit)
|
||||
void Session::setDownloadSpeedLimit(const int limit)
|
||||
{
|
||||
if (isAltGlobalSpeedLimitEnabled())
|
||||
setAltGlobalDownloadSpeedLimit(limit);
|
||||
@@ -2663,7 +2675,7 @@ int Session::uploadSpeedLimit() const
|
||||
: globalUploadSpeedLimit();
|
||||
}
|
||||
|
||||
void Session::setUploadSpeedLimit(int limit)
|
||||
void Session::setUploadSpeedLimit(const int limit)
|
||||
{
|
||||
if (isAltGlobalSpeedLimitEnabled())
|
||||
setAltGlobalUploadSpeedLimit(limit);
|
||||
@@ -2676,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;
|
||||
|
||||
@@ -4561,8 +4573,10 @@ namespace
|
||||
torrentParams.hasRootFolder = fast.dict_find_int_value("qBt-hasRootFolder");
|
||||
|
||||
magnetUri = MagnetUri(QString::fromStdString(fast.dict_find_string_value("qBt-magnetUri")));
|
||||
torrentParams.paused = fast.dict_find_int_value("qBt-paused");
|
||||
torrentParams.forced = fast.dict_find_int_value("qBt-forced");
|
||||
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");
|
||||
|
||||
|
@@ -190,7 +190,7 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
|
||||
, m_hasMissingFiles(false)
|
||||
, m_hasRootFolder(params.hasRootFolder)
|
||||
, m_needsToSetFirstLastPiecePriority(false)
|
||||
, m_pauseAfterRecheck(false)
|
||||
, m_needsToStartForced(params.forced)
|
||||
{
|
||||
if (m_useAutoTMM)
|
||||
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
|
||||
@@ -220,14 +220,14 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
|
||||
// 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).
|
||||
m_started = (params.restored && hasMetadata() ? isPaused() : params.paused);
|
||||
|
||||
if (!m_started) {
|
||||
if (!params.restored || !hasMetadata()) {
|
||||
// Resume torrent because it was added in "resumed" state
|
||||
// but it's actually paused during initialization
|
||||
resume(params.forced);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -807,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()) {
|
||||
@@ -839,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;
|
||||
@@ -1270,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)
|
||||
@@ -1360,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();
|
||||
@@ -1552,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);
|
||||
@@ -1575,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;
|
||||
|
||||
@@ -1587,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);
|
||||
}
|
||||
@@ -1602,19 +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);
|
||||
|
||||
if (m_started)
|
||||
if (m_startupState == Started)
|
||||
m_session->handleTorrentResumed(this);
|
||||
else
|
||||
m_started = true;
|
||||
else if (m_startupState == Starting)
|
||||
m_startupState = Started;
|
||||
}
|
||||
|
||||
void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data_alert *p)
|
||||
@@ -1665,7 +1685,6 @@ void TorrentHandle::handleFastResumeRejectedAlert(const libtorrent::fastresume_r
|
||||
{
|
||||
if (p->error.value() == libt::errors::mismatching_file_size) {
|
||||
// Mismatching file size (files were probably moved)
|
||||
pause();
|
||||
m_hasMissingFiles = true;
|
||||
LogMsg(tr("File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL);
|
||||
}
|
||||
@@ -1700,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())
|
||||
@@ -1718,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()) {
|
||||
@@ -1933,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)
|
||||
@@ -1974,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()
|
||||
@@ -2068,8 +2096,6 @@ void TorrentHandle::prioritizeFiles(const QVector<int> &priorities)
|
||||
// Restore first/last piece first option if necessary
|
||||
if (firstLastPieceFirst)
|
||||
setFirstLastPiecePriorityImpl(true, priorities);
|
||||
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
QVector<qreal> TorrentHandle::availableFileFractions() const
|
||||
|
@@ -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;
|
||||
@@ -460,11 +461,19 @@ namespace BitTorrent
|
||||
bool m_hasMissingFiles;
|
||||
bool m_hasRootFolder;
|
||||
bool m_needsToSetFirstLastPiecePriority;
|
||||
bool m_needsToStartForced;
|
||||
|
||||
bool m_pauseAfterRecheck;
|
||||
QHash<QString, TrackerInfo> m_trackerInfos;
|
||||
|
||||
bool m_started = false;
|
||||
enum StartupState
|
||||
{
|
||||
NotStarted,
|
||||
Starting,
|
||||
Started
|
||||
};
|
||||
|
||||
StartupState m_startupState = NotStarted;
|
||||
bool m_unchecked = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -43,7 +43,6 @@
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <shlobj.h>
|
||||
#include <winreg.h>
|
||||
#include <QRegularExpression>
|
||||
#endif
|
||||
|
||||
@@ -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)
|
||||
@@ -878,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();
|
||||
@@ -1579,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();
|
||||
|
@@ -259,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();
|
||||
@@ -372,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;
|
||||
|
@@ -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;
|
||||
|
@@ -30,6 +30,9 @@
|
||||
|
||||
#include "rss_feed.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
@@ -215,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);
|
||||
@@ -358,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();
|
||||
@@ -375,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)
|
||||
@@ -424,6 +410,85 @@ void Feed::downloadIcon()
|
||||
, 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;
|
||||
|
@@ -104,6 +104,7 @@ namespace RSS
|
||||
void increaseUnreadCount();
|
||||
void decreaseUnreadCount();
|
||||
void downloadIcon();
|
||||
int updateArticles(const QList<QVariantHash> &loadedArticles);
|
||||
|
||||
Session *m_session;
|
||||
Private::Parser *m_parser;
|
||||
|
@@ -47,6 +47,7 @@
|
||||
#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"
|
||||
@@ -145,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;
|
||||
@@ -187,8 +188,6 @@ 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)) {
|
||||
@@ -215,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;
|
||||
}
|
||||
@@ -242,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);
|
||||
@@ -256,8 +254,10 @@ 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,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;
|
||||
|
||||
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
|
||||
@@ -533,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);
|
||||
}
|
||||
|
||||
|
@@ -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")
|
||||
|
@@ -29,11 +29,19 @@
|
||||
|
||||
#include "foreignapps.h"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QStringList>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <QDir>
|
||||
#endif
|
||||
|
||||
#include "base/logger.h"
|
||||
|
||||
using namespace Utils::ForeignApps;
|
||||
@@ -65,16 +73,163 @@ namespace
|
||||
try {
|
||||
info = {exeName, versionStr.left(idx)};
|
||||
}
|
||||
catch (const std::runtime_error &err) {
|
||||
catch (const std::runtime_error &) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Python detected, version: %1").arg(info.version), Log::INFO);
|
||||
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Python detected, executable name: '%1', version: %2")
|
||||
.arg(info.executableName, info.version), Log::INFO);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
enum REG_SEARCH_TYPE
|
||||
{
|
||||
USER,
|
||||
SYSTEM_32BIT,
|
||||
SYSTEM_64BIT
|
||||
};
|
||||
|
||||
QStringList getRegSubkeys(const 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(const 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")) {
|
||||
found = true;
|
||||
path = QDir(path).filePath("python.exe");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
path = QString();
|
||||
|
||||
::RegCloseKey(hkPythonCore);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
QString findPythonPath()
|
||||
{
|
||||
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 QFileInfoList dirs = QDir("C:/").entryInfoList({"Python*"}, QDir::Dirs, (QDir::Name | QDir::Reversed));
|
||||
for (const QFileInfo &info : dirs) {
|
||||
const QString path {info.absolutePath() + "/python.exe"};
|
||||
if (QFile::exists(path))
|
||||
return path;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Utils::ForeignApps::PythonInfo::isValid() const
|
||||
@@ -82,13 +237,21 @@ bool Utils::ForeignApps::PythonInfo::isValid() const
|
||||
return (!executableName.isEmpty() && version.isValid());
|
||||
}
|
||||
|
||||
bool Utils::ForeignApps::PythonInfo::isSupportedVersion() const
|
||||
{
|
||||
const int majorVer = version.majorNumber();
|
||||
return ((majorVer > 3)
|
||||
|| ((majorVer == 3) && (version >= Version {3, 3, 0}))
|
||||
|| ((majorVer == 2) && (version >= Version {2, 7, 9})));
|
||||
}
|
||||
|
||||
PythonInfo Utils::ForeignApps::pythonInfo()
|
||||
{
|
||||
static PythonInfo pyInfo;
|
||||
if (!pyInfo.isValid()) {
|
||||
#if defined(Q_OS_UNIX)
|
||||
// On Unix-Like Systems python2 and python3 should always exist
|
||||
// https://legacy.python.org/dev/peps/pep-0394/
|
||||
// https://www.python.org/dev/peps/pep-0394/
|
||||
if (testPythonInstallation("python3", pyInfo))
|
||||
return pyInfo;
|
||||
if (testPythonInstallation("python2", pyInfo))
|
||||
@@ -99,6 +262,11 @@ PythonInfo Utils::ForeignApps::pythonInfo()
|
||||
if (testPythonInstallation("python", pyInfo))
|
||||
return pyInfo;
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
if (testPythonInstallation(findPythonPath(), pyInfo))
|
||||
return pyInfo;
|
||||
#endif
|
||||
|
||||
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Python not detected"), Log::INFO);
|
||||
}
|
||||
|
||||
|
@@ -42,6 +42,7 @@ namespace Utils
|
||||
using Version = Utils::Version<quint8, 3, 1>;
|
||||
|
||||
bool isValid() const;
|
||||
bool isSupportedVersion() const;
|
||||
|
||||
QString executableName;
|
||||
Version version;
|
||||
|
@@ -30,6 +30,10 @@
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <memory>
|
||||
#endif
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
@@ -301,9 +305,17 @@ bool Utils::Fs::isRegularFile(const QString &path)
|
||||
return (st.st_mode & S_IFMT) == S_IFREG;
|
||||
}
|
||||
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
#if !defined Q_OS_HAIKU
|
||||
bool Utils::Fs::isNetworkFileSystem(const QString &path)
|
||||
{
|
||||
#if defined(Q_OS_WIN)
|
||||
const std::wstring pathW {path.toStdWString()};
|
||||
std::unique_ptr<wchar_t[]> volumePath {new wchar_t[path.length() + 1] {}};
|
||||
if (!::GetVolumePathNameW(pathW.c_str(), volumePath.get(), (path.length() + 1)))
|
||||
return false;
|
||||
|
||||
return (::GetDriveTypeW(volumePath.get()) == DRIVE_REMOTE);
|
||||
#elif defined(Q_OS_MAC) || defined(Q_OS_OPENBSD)
|
||||
QString file = path;
|
||||
if (!file.endsWith('/'))
|
||||
file += '/';
|
||||
@@ -313,20 +325,32 @@ bool Utils::Fs::isNetworkFileSystem(const QString &path)
|
||||
if (statfs(file.toLocal8Bit().constData(), &buf) != 0)
|
||||
return false;
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_OPENBSD)
|
||||
// XXX: should we make sure HAVE_STRUCT_FSSTAT_F_FSTYPENAME is defined?
|
||||
return ((strncmp(buf.f_fstypename, "nfs", sizeof(buf.f_fstypename)) == 0)
|
||||
|| (strncmp(buf.f_fstypename, "cifs", sizeof(buf.f_fstypename)) == 0)
|
||||
return ((strncmp(buf.f_fstypename, "cifs", sizeof(buf.f_fstypename)) == 0)
|
||||
|| (strncmp(buf.f_fstypename, "nfs", sizeof(buf.f_fstypename)) == 0)
|
||||
|| (strncmp(buf.f_fstypename, "smbfs", sizeof(buf.f_fstypename)) == 0));
|
||||
#else
|
||||
// usually defined in /usr/include/linux/magic.h
|
||||
const unsigned long CIFS_MAGIC_NUMBER = 0xFF534D42;
|
||||
const unsigned long NFS_SUPER_MAGIC = 0x6969;
|
||||
const unsigned long SMB_SUPER_MAGIC = 0x517B;
|
||||
QString file = path;
|
||||
if (!file.endsWith('/'))
|
||||
file += '/';
|
||||
file += '.';
|
||||
|
||||
return ((buf.f_type == CIFS_MAGIC_NUMBER)
|
||||
|| (buf.f_type == NFS_SUPER_MAGIC)
|
||||
|| (buf.f_type == SMB_SUPER_MAGIC));
|
||||
struct statfs buf {};
|
||||
if (statfs(file.toLocal8Bit().constData(), &buf) != 0)
|
||||
return false;
|
||||
|
||||
// Magic number references:
|
||||
// 1. /usr/include/linux/magic.h
|
||||
// 2. https://github.com/coreutils/coreutils/blob/master/src/stat.c
|
||||
switch (buf.f_type) {
|
||||
case 0xFF534D42: // CIFS_MAGIC_NUMBER
|
||||
case 0x6969: // NFS_SUPER_MAGIC
|
||||
case 0x517B: // SMB_SUPER_MAGIC
|
||||
case 0xFE534D42: // S_MAGIC_SMB2
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
@@ -62,7 +62,7 @@ namespace Utils
|
||||
|
||||
QString tempPath();
|
||||
|
||||
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
|
||||
#if !defined Q_OS_HAIKU
|
||||
bool isNetworkFileSystem(const QString &path);
|
||||
#endif
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user