1
mirror of https://github.com/qbittorrent/qBittorrent synced 2025-10-12 03:12:18 +02:00

Compare commits

...

63 Commits

Author SHA1 Message Date
sledgehammer999
0061b75200 Bump to 4.1.4 2018-11-19 01:42:20 +02:00
sledgehammer999
420c93a99e Update Changelog 2018-11-19 01:38:44 +02:00
sledgehammer999
93f1183cd7 Sync translations from Transifex and run lupdate 2018-11-19 01:36:56 +02:00
Chocobo1
b8fcc1fed2 Fix divide-by-zero crash
Previously here was using a cheap method to avoid divisor becoming < 0, but from
the crash stacktrace it seems this is not enough, now the divisor is properly
clamped to have 1 as the minimum.
Also it will now display "Unknown" for invalid calculation results.

Closes #9857.
2018-11-19 01:19:10 +02:00
Vladimir Golovnev (Glassez)
2b91be1905 Improve RSS Feed updating
Don't process "out-of-limit" articles.
Closes #9833.
2018-11-19 01:19:10 +02:00
sledgehammer999
7c9ef96ef8 Update Changelog 2018-11-15 00:12:33 +02:00
thalieht
37b4b69199 Allow resizing search filter in search job
Allow qBt to resize the search filter in search job because it causes
qBt's width to exceed the screen's width for laptop users.
2018-11-14 23:39:57 +02:00
sledgehammer999
fc18e6f8df Change FossHub RSS url for updates
The new RSS format is compatible with our current parser. FossHub will
redirect old URL to the new one so older clients will not be affected.
2018-11-14 23:39:57 +02:00
Chocobo1
4793a35e0b Don't double delete a pointer
`m_searchFilterAction` is owned by Qt, so we shouldn't delete it
manually.
2018-11-14 23:39:57 +02:00
Eugene Shalygin
4599da3ce1 cmake: use C++14 when available
Libtorrent does the same and we have to follow since
its ABI depends on the C++ standard version.

Partially closes #9485.
2018-11-14 23:39:57 +02:00
Chocobo1
dec4e41fdd Clean up SpeedLimitDialog class 2018-11-14 23:39:57 +02:00
Chocobo1
780ece0c25 Remove speed limit checkbox in Options dialog
This unifies speed limit UI elements throughout the program.
2018-11-14 23:39:57 +02:00
dzmat
aac8bfc398 Fix speed graph "high speeds" bug 2018-11-14 23:39:57 +02:00
Tom Piccirello
1a06a18336 Handle downloading .torrent file as success
We don't know whether the download will be successful, so default to success. Closes #9811.
2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
2d4f963d65 Don't update torrent status unnecessarily 2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
b54fe08201 Improve force recheck of paused torrent 2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
d1d0300491 Restore torrent in two steps
Add/restore all torrents in "paused" state and then resume those
that need to be really "resumed" (added/restored in "resumed" state).
Keep torrents with missing files paused.
Force recheck torrent with missing files when it's resumed by the user.
2018-11-14 23:39:57 +02:00
Chocobo1
7fff06f07b Improve parser for search engine versions.txt
The parse could fail when there is an extra empty line at the end of
file, this patch fixes it.
2018-11-14 23:39:57 +02:00
Chocobo1
3f9351042d Fix wrong type passed to arg() 2018-11-14 23:39:57 +02:00
Chocobo1
9e01dbab0f Fix defects found by lgtm.com 2018-11-14 23:39:57 +02:00
Chocobo1
d4a4b02cf6 Fix MSVC warning C4804
Full message of the warning:
webui\api\searchcontroller.cpp(54): warning C4804: '>': unsafe use of type 'bool'
in operation
2018-11-14 23:39:57 +02:00
dzmat
1f2c7a6671 Improve scaling of speed graphs
Make Y axis scale to fix on predetermined nice looking positions
2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
5a7b88c16c Fix Alternative Web UI to be available 2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
93351476e4 Consider empty locale setting as not set 2018-11-14 23:39:57 +02:00
Thomas Piccirello
e1bfa95a63 Use QElapsedTimer 2018-11-14 23:39:57 +02:00
Thomas Piccirello
7030cc08e7 Add free disk space to WebUI status bar
Closes #6829.
2018-11-14 23:39:57 +02:00
Thomas Piccirello
a1da9812a5 Catch invalid values 2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
8ebc0f529c Fix indentation in tstool.py 2018-11-14 23:39:57 +02:00
Tom Piccirello
e0d47649bc Bump WebAPI version 2018-11-14 23:39:57 +02:00
silver
524d503860 Recognize *.ts files as previewable 2018-11-14 23:39:57 +02:00
Thomas Piccirello
cffafa8e9f Add WebUI search API controller
Closes #2495.
2018-11-14 23:39:57 +02:00
Thomas Piccirello
0fda919268 Instantiate SearchPluginManager with other application components 2018-11-14 23:39:57 +02:00
Chocobo1
7d98c34e17 Fix TravisCI cmake build on macOS
Instead of hard coding a macOS version to use, now we follow TravisCI default
version, currently: xcode9.4.
2018-11-14 23:39:57 +02:00
Thomas Piccirello
93147e787b Fix WebUI Auto TMM context menu bug
When multiple torrents are selected with different Auto TMM values, the Auto TMM context menu option is hidden. This option is never unhidden, requiring a refresh of the page.
2018-11-14 23:39:57 +02:00
Eugene Shalygin
80435bae7e cmake: restore out-of-source build
Qt translations have to be compiled in a shared library or executable,
and since we use static libraries for the components, webui translation
files have to be compiled into the main executable.
2018-11-14 23:39:57 +02:00
Chocobo1
b367e5c197 Simplify #if conditions 2018-11-14 23:39:57 +02:00
Chocobo1
5336c71da5 Add isNetworkFileSystem() detection on Windows
This allows network mounts to be monitored correctly by polling timer.
2018-11-14 23:39:57 +02:00
dzmat
27f6db976d Reduce horizontal graphs resolution
Rewrite averaging code and reduce horizontal graphs resolution for
30 minutes and 6 hours graphs to decrease CPU usage.
2018-11-14 23:39:57 +02:00
Administrator account
8223d61fa7 Don't recheck just checked torrent
Closes #8743.
Closes #9370.
2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
3eef12bd8f Use independent translation for WebUI 2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
9e70a6c499 Create WebUI translation update tool 2018-11-14 23:39:57 +02:00
Thomas Piccirello
fec3a87421 Add categories WebAPI
Closes #5330.
2018-11-14 23:39:57 +02:00
dzmat
59aac32eb9 Allow to disable speed graphs 2018-11-14 23:39:57 +02:00
Chocobo1
5ef3917769 Add FileSystemWatcher log messages 2018-11-14 23:39:57 +02:00
Chocobo1
2f767d96d9 Add SMB2 magic number
Closes #9671.
2018-11-14 23:39:57 +02:00
silverqx
de24fdfdc2 Clear LineEdit on ESC 2018-11-14 23:39:57 +02:00
Stephen Dawkins
3bb6a68c9d Allow to disable downloading REPACK/PROPER matches 2018-11-14 23:39:57 +02:00
sledgehammer999
f2406eb2f3 Use a more detailed alert mask where possible
Closes #9547
2018-11-14 23:39:57 +02:00
Thomas Piccirello
4923ed7da0 Fix minor JavaScript defects 2018-11-14 23:39:57 +02:00
Thomas Piccirello
82056355f6 Add locale to js file path
This reduces the likelihood of a cached file being used after the locale is changed.
2018-11-14 23:39:57 +02:00
Thomas Piccirello
f3bd2a295f Translate WebUI torrents Status column
Closes #9554.
2018-11-14 23:39:57 +02:00
dzmat
cc96760839 Update uncrustify.cfg
Suddenly uncrustify does not append spaces after comma in function's argument lists. I found only one option which looks fit for it.
2018-11-14 23:39:57 +02:00
Chocobo1
ae95943f69 Remove default parameter in derived function
When derived function have different default value than base, it might cause
unnecessary confusion, see: https://stackoverflow.com/q/3533589
2018-11-14 23:39:57 +02:00
Chocobo1
d3067f939e Avoid variable shadowing 2018-11-14 23:39:57 +02:00
Chocobo1
b6addd304c Add include guard to headers 2018-11-14 23:39:57 +02:00
Chocobo1
d1ae6e8d58 Update Python URLs 2018-11-14 23:39:57 +02:00
Chocobo1
4445c2dab2 Fix asking to install Python
The dialog asking users to install python is borked since the last refactor, this
commit fixes it.
2018-11-14 23:39:57 +02:00
Chocobo1
fcc1564a62 Move python related functions
Also the functions are slightly changed to return full path of the found
python executable.
2018-11-14 23:39:57 +02:00
sledgehammer999
615eeb7144 Make strings actually translatable 2018-11-14 23:39:57 +02:00
sledgehammer999
855bb118b5 Remove unused variable 2018-11-14 23:39:57 +02:00
dzmat
9f1eb3600a Replace magic number with system define 2018-11-14 23:39:57 +02:00
Eugene Shalygin
fb885d89c1 Reword the warning message 2018-11-14 23:39:57 +02:00
Chocobo1
a846916beb Reformat python code to be compliant with PEP8
The following command is used:
`pycodestyle --ignore=E265,E722 --max-line-length=100 <py files>`
2018-11-14 23:39:57 +02:00
202 changed files with 133133 additions and 38660 deletions

1
.gitignore vendored
View File

@@ -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

View File

@@ -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/"

View File

@@ -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)

View File

@@ -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)

View 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
View File

@@ -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\\"

View File

@@ -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
View File

@@ -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>

View File

@@ -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

View File

@@ -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})

View File

@@ -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,

View File

@@ -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 &params)
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();

View File

@@ -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 &params)
{
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 &params)
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");

View File

@@ -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

View File

@@ -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;
};
}

View File

@@ -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()
{

View File

@@ -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

View File

@@ -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();

View File

@@ -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;

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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")

View File

@@ -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);
}

View File

@@ -42,6 +42,7 @@ namespace Utils
using Version = Utils::Version<quint8, 3, 1>;
bool isValid() const;
bool isSupportedVersion() const;
QString executableName;
Version version;

View File

@@ -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

View File

@@ -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