mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-18 06:28:03 -06:00
Compare commits
288 Commits
release-4.
...
release-4.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05a82afeb6 | ||
|
|
a456f1b0f9 | ||
|
|
4acc44a5b0 | ||
|
|
9c2a1146df | ||
|
|
807abeae87 | ||
|
|
dd2a0d0484 | ||
|
|
3f3400f43b | ||
|
|
334b57a89a | ||
|
|
00d6c83ee5 | ||
|
|
e8850c7a70 | ||
|
|
2ef96eb218 | ||
|
|
4682e31ab7 | ||
|
|
988f7e2ef8 | ||
|
|
6007913291 | ||
|
|
cdcc7a210b | ||
|
|
a466ff5057 | ||
|
|
e954835579 | ||
|
|
3e9be3a0e8 | ||
|
|
4ab32a76f6 | ||
|
|
bad60058df | ||
|
|
31a6ad1eb6 | ||
|
|
a8bfec081e | ||
|
|
ae21d0f1e2 | ||
|
|
c599976b6f | ||
|
|
bcee784097 | ||
|
|
697fc626cd | ||
|
|
2f15ea9b54 | ||
|
|
d03209a73d | ||
|
|
ac9ba255d8 | ||
|
|
9a7e79bd0e | ||
|
|
e8be3bf939 | ||
|
|
74e52746b1 | ||
|
|
8d26a221e0 | ||
|
|
3fdab88eb7 | ||
|
|
d376d912b3 | ||
|
|
e329c41ef2 | ||
|
|
01e4179555 | ||
|
|
06f503b5df | ||
|
|
e2f3dad7b8 | ||
|
|
377f31085c | ||
|
|
ec13d195f8 | ||
|
|
c01aed8d90 | ||
|
|
ad7b8a9bfa | ||
|
|
5bba1ed208 | ||
|
|
fe94e14bcc | ||
|
|
b0af479ab9 | ||
|
|
24ff369f29 | ||
|
|
979c9a7094 | ||
|
|
7b90ac52c1 | ||
|
|
ecfbda78bc | ||
|
|
9ba7470815 | ||
|
|
6394467cc7 | ||
|
|
f6d72fa79f | ||
|
|
32ed5f1c8e | ||
|
|
5026da5773 | ||
|
|
ef130e4438 | ||
|
|
4fbd52c2d5 | ||
|
|
8f29b70c1e | ||
|
|
9a4dd3ea9d | ||
|
|
fcd3bb6918 | ||
|
|
9f69fd8750 | ||
|
|
ea7e47d113 | ||
|
|
294bb26996 | ||
|
|
4b2e9dba51 | ||
|
|
1d9dcde99b | ||
|
|
32bf448725 | ||
|
|
732d5d6db9 | ||
|
|
3b325106da | ||
|
|
3aeca37c5d | ||
|
|
4253515736 | ||
|
|
e365d57063 | ||
|
|
df6df20969 | ||
|
|
4e5a85dda5 | ||
|
|
bad603454b | ||
|
|
7b006a47ba | ||
|
|
11da8b82e8 | ||
|
|
383a5f11bc | ||
|
|
76ab5f12c5 | ||
|
|
be74987084 | ||
|
|
8f6c305d14 | ||
|
|
e29b9655eb | ||
|
|
ae7fa9ea82 | ||
|
|
fee9030337 | ||
|
|
f48d057c47 | ||
|
|
f14573307c | ||
|
|
560ba8c0b8 | ||
|
|
4b2376c4fd | ||
|
|
76faed3818 | ||
|
|
62657d9fda | ||
|
|
5877308a49 | ||
|
|
24dcbe7d43 | ||
|
|
7649fe0a0e | ||
|
|
b3b334da77 | ||
|
|
03a55da260 | ||
|
|
1a9eadf8e6 | ||
|
|
7b3fb2a35a | ||
|
|
a55ea29919 | ||
|
|
264b689912 | ||
|
|
684cf82f89 | ||
|
|
3f0e0a319a | ||
|
|
0b4d9c72a7 | ||
|
|
ff71f6bcd9 | ||
|
|
7a5c5baad1 | ||
|
|
a18976d0b5 | ||
|
|
6d836ea49c | ||
|
|
2e97311147 | ||
|
|
57bc564b2c | ||
|
|
1295f1e31f | ||
|
|
4916ed0efb | ||
|
|
f15f99cb27 | ||
|
|
93365d3b20 | ||
|
|
c756ab021d | ||
|
|
34528dd544 | ||
|
|
9380209afb | ||
|
|
be2895ac6f | ||
|
|
e26d4642b8 | ||
|
|
f470972bd4 | ||
|
|
443378c041 | ||
|
|
e20dbe34a4 | ||
|
|
86bde47a06 | ||
|
|
e273c777c7 | ||
|
|
17845c6b25 | ||
|
|
27827ce16a | ||
|
|
b444ecc6af | ||
|
|
34995350ee | ||
|
|
73ceee52f8 | ||
|
|
85a3ba0eed | ||
|
|
86cce76e9d | ||
|
|
3358fd8e91 | ||
|
|
120965f823 | ||
|
|
e70ee9a5b6 | ||
|
|
a2d8e84e83 | ||
|
|
4a3648a693 | ||
|
|
baad45e638 | ||
|
|
d9cb00aab2 | ||
|
|
d703d98836 | ||
|
|
2f0646e7f0 | ||
|
|
1a8a6dcef7 | ||
|
|
990f961126 | ||
|
|
06f04dea19 | ||
|
|
8eced2ef1f | ||
|
|
1e486ea92e | ||
|
|
b47f38675e | ||
|
|
864f3393a0 | ||
|
|
cebef74326 | ||
|
|
e257b35cac | ||
|
|
1f33991e4b | ||
|
|
794053f212 | ||
|
|
3a130e1f74 | ||
|
|
3423f93230 | ||
|
|
2219167253 | ||
|
|
a0a32b89a6 | ||
|
|
59162bf426 | ||
|
|
dfd148f55f | ||
|
|
3af720b3bc | ||
|
|
11240d0837 | ||
|
|
e64fd9c544 | ||
|
|
50ef812427 | ||
|
|
bd4d2fa424 | ||
|
|
e2ee928017 | ||
|
|
62e71a15a4 | ||
|
|
c62127e9f1 | ||
|
|
2171d579ee | ||
|
|
6e5a969e2d | ||
|
|
bfbc7ef28a | ||
|
|
b1cefbf9b5 | ||
|
|
201638854e | ||
|
|
847ecdeedb | ||
|
|
acc159fa60 | ||
|
|
bb7e80a8a6 | ||
|
|
39973f1bb1 | ||
|
|
1e9151364a | ||
|
|
fd50d6e9af | ||
|
|
427acf0c46 | ||
|
|
f0a50424be | ||
|
|
aded9afc0e | ||
|
|
060b7480db | ||
|
|
7f2a01dcd6 | ||
|
|
fef0e70c9f | ||
|
|
9cc112aa4e | ||
|
|
44d4d41365 | ||
|
|
a21c386dbf | ||
|
|
1c4139906a | ||
|
|
1a21f45c75 | ||
|
|
0061b75200 | ||
|
|
420c93a99e | ||
|
|
93f1183cd7 | ||
|
|
b8fcc1fed2 | ||
|
|
2b91be1905 | ||
|
|
7c9ef96ef8 | ||
|
|
37b4b69199 | ||
|
|
fc18e6f8df | ||
|
|
4793a35e0b | ||
|
|
4599da3ce1 | ||
|
|
dec4e41fdd | ||
|
|
780ece0c25 | ||
|
|
aac8bfc398 | ||
|
|
1a06a18336 | ||
|
|
2d4f963d65 | ||
|
|
b54fe08201 | ||
|
|
d1d0300491 | ||
|
|
7fff06f07b | ||
|
|
3f9351042d | ||
|
|
9e01dbab0f | ||
|
|
d4a4b02cf6 | ||
|
|
1f2c7a6671 | ||
|
|
5a7b88c16c | ||
|
|
93351476e4 | ||
|
|
e1bfa95a63 | ||
|
|
7030cc08e7 | ||
|
|
a1da9812a5 | ||
|
|
8ebc0f529c | ||
|
|
e0d47649bc | ||
|
|
524d503860 | ||
|
|
cffafa8e9f | ||
|
|
0fda919268 | ||
|
|
7d98c34e17 | ||
|
|
93147e787b | ||
|
|
80435bae7e | ||
|
|
b367e5c197 | ||
|
|
5336c71da5 | ||
|
|
27f6db976d | ||
|
|
8223d61fa7 | ||
|
|
3eef12bd8f | ||
|
|
9e70a6c499 | ||
|
|
fec3a87421 | ||
|
|
59aac32eb9 | ||
|
|
5ef3917769 | ||
|
|
2f767d96d9 | ||
|
|
de24fdfdc2 | ||
|
|
3bb6a68c9d | ||
|
|
f2406eb2f3 | ||
|
|
4923ed7da0 | ||
|
|
82056355f6 | ||
|
|
f3bd2a295f | ||
|
|
cc96760839 | ||
|
|
ae95943f69 | ||
|
|
d3067f939e | ||
|
|
b6addd304c | ||
|
|
d1ae6e8d58 | ||
|
|
4445c2dab2 | ||
|
|
fcc1564a62 | ||
|
|
615eeb7144 | ||
|
|
855bb118b5 | ||
|
|
9f1eb3600a | ||
|
|
fb885d89c1 | ||
|
|
a846916beb | ||
|
|
a574c4a70a | ||
|
|
1e367f818d | ||
|
|
00599c8f02 | ||
|
|
332a836746 | ||
|
|
a1992acc16 | ||
|
|
c3f002a544 | ||
|
|
c28cbe0a74 | ||
|
|
435daaceed | ||
|
|
e29ab0087b | ||
|
|
aadd5a3312 | ||
|
|
7e354ffad3 | ||
|
|
ee6a071fb6 | ||
|
|
bc8b838953 | ||
|
|
5251d93b3d | ||
|
|
84f0dbecfe | ||
|
|
bba0c8b2cc | ||
|
|
2f90be8bd2 | ||
|
|
cb6b6296aa | ||
|
|
9d25fdce2a | ||
|
|
12b2732f1a | ||
|
|
8c9ece73ee | ||
|
|
a7db786387 | ||
|
|
e5bf65c9bd | ||
|
|
900e7d3a14 | ||
|
|
f1ff74a926 | ||
|
|
30bc4b837e | ||
|
|
050a4f8b23 | ||
|
|
487103d58f | ||
|
|
eeea69d4c1 | ||
|
|
00360ad418 | ||
|
|
a733253ae5 | ||
|
|
9788ee042b | ||
|
|
e9c9ea3bba | ||
|
|
312dfb989d | ||
|
|
75deafe5b1 | ||
|
|
4ca257a389 | ||
|
|
03375a78f2 | ||
|
|
423c7066d7 | ||
|
|
5cd5cc71a8 | ||
|
|
45d4d22055 | ||
|
|
916a92aa0d |
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
|
||||
|
||||
74
.travis.yml
74
.travis.yml
@@ -3,7 +3,8 @@ language: cpp
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
osx_image: xcode7.3
|
||||
|
||||
dist: xenial
|
||||
|
||||
env:
|
||||
matrix:
|
||||
@@ -38,11 +39,6 @@ cache:
|
||||
directories:
|
||||
- $HOME/hombebrew_cache
|
||||
|
||||
# opt-in Ubuntu Trusty
|
||||
dist: trusty
|
||||
# container-based builds
|
||||
sudo: false
|
||||
|
||||
addons:
|
||||
coverity_scan:
|
||||
project:
|
||||
@@ -54,34 +50,30 @@ addons:
|
||||
notification_email: sledgehammer999@qbittorrent.org
|
||||
apt:
|
||||
sources:
|
||||
# sources list: https://github.com/travis-ci/apt-source-whitelist/blob/master/ubuntu.json
|
||||
- ubuntu-toolchain-r-test
|
||||
#- boost-latest
|
||||
# sources list: https://github.com/travis-ci/apt-source-safelist/blob/master/ubuntu.json
|
||||
- sourceline: 'ppa:qbittorrent-team/qbittorrent-stable'
|
||||
- sourceline: 'ppa:beineri/opt-qt551-trusty'
|
||||
- sourceline: 'ppa:adrozdoff/cmake'
|
||||
packages:
|
||||
# packages list: https://github.com/travis-ci/apt-package-whitelist/blob/master/ubuntu-precise
|
||||
# packages list: https://github.com/travis-ci/apt-package-safelist/blob/master/ubuntu-trusty
|
||||
- [autoconf, automake, colormake]
|
||||
- [cmake, ninja-build]
|
||||
- [ninja-build]
|
||||
- libssl-dev
|
||||
- [libboost-dev, libboost-system-dev]
|
||||
- libtorrent-rasterbar-dev
|
||||
- [qt55base, qt55svg, qt55tools]
|
||||
- [gcc-6, g++-6]
|
||||
- [qtbase5-dev, qttools5-dev-tools, libqt5svg5-dev]
|
||||
|
||||
before_install:
|
||||
# only allow specific build for coverity scan, others will stop
|
||||
- if [ "$TRAVIS_BRANCH" = "$coverity_branch" ] && ! [ "$TRAVIS_OS_NAME" = "linux" -a "$lt_branch" = "RC_1_0" -a "$gui" = true -a "$build_system" = "qmake" ]; then exit ; fi
|
||||
|
||||
- shopt -s expand_aliases
|
||||
- alias make="colormake -j3" # Using nprocs/2 sometimes may fail (gcc is killed by system)
|
||||
- alias make="colormake -j2" # Using nprocs/2 sometimes may fail (gcc is killed by system)
|
||||
- qbt_path="$HOME/qbt_install"
|
||||
- |
|
||||
if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
||||
qbtconf="$qbtconf --prefix="$qbt_path" PKG_CONFIG_PATH=/opt/qt55/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
else
|
||||
qbtconf="$qbtconf --prefix="$qbt_path""
|
||||
qbtconf="$qbtconf --prefix="$qbt_path" PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATH"
|
||||
CXXFLAGS="$CXXFLAGS -Wno-unused-local-typedefs -Wno-inconsistent-missing-override"
|
||||
fi
|
||||
|
||||
# options for specific branches
|
||||
@@ -90,14 +82,6 @@ before_install:
|
||||
if [ "$TRAVIS_OS_NAME" = "linux" ]; then
|
||||
# setup virtual display for after_success target
|
||||
if [ "$gui" = true ]; then export "DISPLAY=:99.0" && /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 ; fi ;
|
||||
|
||||
# Qt 5
|
||||
PATH=/opt/qt55/bin:${PATH}
|
||||
|
||||
if [ "$build_system" = "cmake" ]; then
|
||||
COMPILER_VERSION=6
|
||||
export CXX="${CXX}-${COMPILER_VERSION}" CC="${CC}-${COMPILER_VERSION}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# print settings
|
||||
@@ -121,41 +105,24 @@ 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 openssl libtorrent-rasterbar
|
||||
PATH="/usr/local/opt/ccache/libexec:$PATH"
|
||||
brew link --force zlib qt
|
||||
|
||||
wget https://builds.shiki.hu/homebrew/version
|
||||
if ! cmp --quiet "version" "$HOME/hombebrew_cache/version" ; then
|
||||
echo "Cached files are different from server. Downloading new ones."
|
||||
# First delete old files
|
||||
rm -r "$HOME/hombebrew_cache"
|
||||
mkdir "$HOME/hombebrew_cache"
|
||||
cp "version" $HOME/hombebrew_cache
|
||||
cd "$HOME/hombebrew_cache"
|
||||
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar.rb
|
||||
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar-1.1.7+git20180422.3ede0b9c20+patched-configure.el_capitan.bottle.tar.gz
|
||||
fi
|
||||
|
||||
# Copy custom libtorrent bottle to homebrew's cache so it can find and install it
|
||||
# Also install our custom libtorrent formula by passing the local path to it
|
||||
# These 2 files are restored from Travis' cache.
|
||||
cp "$HOME/hombebrew_cache/libtorrent-rasterbar-1.1.7+git20180422.3ede0b9c20+patched-configure.el_capitan.bottle.tar.gz" "$(brew --cache)"
|
||||
brew install "$HOME/hombebrew_cache/libtorrent-rasterbar.rb"
|
||||
|
||||
if [ "$build_system" = "cmake" ]; then
|
||||
brew outdated cmake || brew upgrade cmake
|
||||
brew install ninja
|
||||
|
||||
ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs
|
||||
ln -s /usr/local/opt/qt/plugins /usr/local/plugins
|
||||
sudo ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs
|
||||
sudo ln -s /usr/local/opt/qt/plugins /usr/local/plugins
|
||||
|
||||
MY_CMAKE_OPENSSL_HINT="-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/"
|
||||
fi
|
||||
|
||||
MY_CMAKE_OPENSSL_HINT="-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/"
|
||||
fi
|
||||
- |
|
||||
if [ "$TRAVIS_BRANCH" != "$coverity_branch" ]; then
|
||||
export use_ccache=true
|
||||
ccache -M 512M
|
||||
ccache -V && ccache --show-stats && ccache --zero-stats
|
||||
fi
|
||||
|
||||
@@ -174,16 +141,7 @@ script:
|
||||
BUILD_TOOL="ninja"
|
||||
fi
|
||||
if [ "$build_system" = "qmake" ]; then
|
||||
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
|
||||
# For some reason for RC_1_1 we need to also specify the OpenSSL compiler/linker flags
|
||||
# Homebrew doesn't symlink OpenSSL for security reasons
|
||||
./bootstrap.sh && ./configure $qbtconf CXXFLAGS="$(PKG_CONFIG_PATH="/usr/local/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATH" pkg-config --cflags openssl)" LDFLAGS="$(PKG_CONFIG_PATH="/usr/local/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATH" pkg-config --libs openssl)"
|
||||
sed -i "" -e "s/^\(CC.*&&\).*$/\1 $CC/" src/Makefile # workaround for Qt & ccache: https://bugreports.qt.io/browse/QTBUG-31034
|
||||
sed -i "" -e "s/^\(CXX.*&&\).*$/\1 $CXX/" src/Makefile
|
||||
sed -i "" -e 's/^\(CXXFLAGS.*\)$/\1 -Wno-unused-local-typedefs -Wno-inconsistent-missing-override/' src/Makefile
|
||||
else
|
||||
./bootstrap.sh && ./configure $qbtconf
|
||||
fi
|
||||
./bootstrap.sh && ./configure $qbtconf CXXFLAGS="$CXXFLAGS"
|
||||
BUILD_TOOL="make"
|
||||
fi
|
||||
- $BUILD_TOOL && $BUILD_TOOL install
|
||||
|
||||
12
.tx/config
12
.tx/config
@@ -10,10 +10,18 @@ type = QT
|
||||
minimum_perc = 23
|
||||
mode = developer
|
||||
|
||||
|
||||
[qbittorrent.qbittorrentdesktop_master]
|
||||
source_file = dist/unix/qbittorrent.desktop
|
||||
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
|
||||
source_lang = en
|
||||
type = DESKTOP
|
||||
minimum_perc = 23
|
||||
mode = developer
|
||||
|
||||
[qbittorrent.qbittorrent_webui]
|
||||
file_filter = src/webui/www/translations/webui_<lang>.ts
|
||||
lang_map = pt: pt_PT
|
||||
source_file = src/webui/www/translations/webui_en.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
minimum_perc = 23
|
||||
mode = developer
|
||||
|
||||
@@ -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)
|
||||
|
||||
177
Changelog
177
Changelog
@@ -1,3 +1,180 @@
|
||||
* Sun Aug 04 2019 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.7
|
||||
- FEATURE: Add 12 hour and 24 hour speed graphs (dzmat)
|
||||
- FEATURE: Change "Add new torrent" dialog to horizontal layout (Evgeny Lensky)
|
||||
- BUGFIX: Fix messed up symbols in log (Chocobo1)
|
||||
- BUGFIX: Fix incomplete file extension not applied for new torrents (Chocobo1)
|
||||
- BUGFIX: Save updated resume data for completed torrents (Vladimir Golovnev (Glassez))
|
||||
- BUGFIX: Fix requested torrent resume data handling (Vladimir Golovnev (Glassez))
|
||||
- BUGFIX: Prevent command injection via "Run external program" function (Chocobo1)
|
||||
- BUGFIX: Avoid race conditions when adding torrent (Vladimir Golovnev (Glassez))
|
||||
- BUGFIX: Fix torrent checking issues (Vladimir Golovnev (Glassez))
|
||||
- BUGFIX: Use proper log message when there are no error (Chocobo1)
|
||||
- BUGFIX: Fix torrent properties not saved for paused torrents (Chocobo1)
|
||||
- BUGFIX: Some improvements on qtsingleapplication code (Chocobo1)
|
||||
- BUGFIX: Remove limits of "Disk cache expiry interval" setting (Chocobo1)
|
||||
- BUGFIX: Remove upper limit of "Disk cache" setting (Chocobo1)
|
||||
- BUGFIX: Fix crash when removing phantom tags (Chocobo1)
|
||||
- BUGFIX: Improve handleFileErrorAlert error message (Chocobo1)
|
||||
- BUGFIX: Fix updated save path not saved for paused torrents (Chocobo1)
|
||||
- BUGFIX: Log save_resume_data_failed_alert (Chocobo1)
|
||||
- BUGFIX: Don't remove parent directories (Chocobo1)
|
||||
- BUGFIX: Properly remove empty leftover folders after rename (Chocobo1)
|
||||
- BUGFIX: Focus behavior row in Options dialog (silverqx)
|
||||
- BUGFIX: Fix unable to rename folder on Windows when same is used in different case(Chocobo1)
|
||||
- BUGFIX: Fix unable to control add torrent dialogs when opened simultaneously (Chocobo1)
|
||||
- BUGFIX: Disable "Upload mode" when start preloaded torrent (Vladimir Golovnev (Glassez))
|
||||
- BUGFIX: Fix wrong comparison result when sorting items(Chocobo1)
|
||||
- BUGFIX: Fix sequential downloading when redirected (Vladimir Golovnev (Glassez))
|
||||
- BUGFIX: Fix typos (Chocobo1)
|
||||
- BUGFIX: Fix assertion fail (Chocobo1)
|
||||
- BUGFIX: Change number of time axis divisions from 5 to 6 for convenience (dzmat)
|
||||
- BUGFIX: Don't turn window blank when closed to system tray (Ekin Dursun)
|
||||
- WEBUI: Fix WebUI encoding of special characters (Thomas Piccirello)
|
||||
- WEBUI: Change the speed unit from Bytes/s to KiB/s for the rate limiter(jerrymakesjelly)
|
||||
- WEBUI: Fix '+' char not decoded to space correctly (Chocobo1)
|
||||
- RSS: Ignore RSS articles with non-unique identifiers (Vladimir Golovnev (Glassez))
|
||||
- RSS: Perform more RSS parsing in working thread (Vladimir Golovnev (Glassez))
|
||||
- RSS: Download RSS enclosure element if no proper MIME type is found (Matan Bareket)
|
||||
|
||||
* Sun May 05 2019 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.6
|
||||
- BUGFIX: Force recheck multiple torrents one by one in all possible cases. Closes #9120 (glassez)
|
||||
- BUGFIX: Don't query Google for tracker favicons, for privacy reasons (sledgehammer999)
|
||||
- BUGFIX: Work around the crash occurred in QTimer. Closes #9985 (Chocobo1)
|
||||
- BUGFIX: Increase the .torrent file download size limit to 100 MiB (thalieht)
|
||||
- BUGFIX: Disable downloading tracker favicons by default. Works around reported crashes in Linux. Closes #9667 (Chocobo1)
|
||||
- WEBUI: Separate URL components before percent-decoding. Allow special characters in query string parameters. Closes #9116 (glassez)
|
||||
- WEBUI: Prevent login credential appearing in URL. Closes #10221 (Chocobo1)
|
||||
- WEBUI: Display warning when Javascript is disabled (Chocobo1)
|
||||
- WEBUI: Fix translatable strings (Chocobo1)
|
||||
- WEBUI: Correctly handle '+' sign in x-www-form-urlencoded data. Closes #10451 (Chocobo1)
|
||||
- WEBUI: Remove closed connections immediately. Closes #10487 (Chocobo1)
|
||||
- WEBUI: Fix "Create subfolder" option is not working. Closes ##10392 (Chocobo1)
|
||||
- SEARCH: Make num enter key work the same as return in searchjobwidget (thalieht)
|
||||
- LINUX: Make window title bar icon work in Wayland (Peter Eszlari)
|
||||
- LINUX: Update appdata.xml file (Chocobo1)
|
||||
- MACOS: Fix progress bar drawing by using different style than native (Nick Korotysh)
|
||||
- MACOS: Updated and cleaned up fields in Info.plist (Nick Korotysh)
|
||||
- OTHER: Mention more translators in the about tab. Closes #10043 (sledgehammer999)
|
||||
|
||||
* Mon Dec 24 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.5
|
||||
- FEATURE: Add checking_mem_usage option to AdvancedSettings (FranciscoPombal)
|
||||
- FEATURE: Save torrents queue in separate file. Now a new file named 'queue' is created, saving on each line the infohash of each queued torrent in sorted order. (glassez)
|
||||
- BUGFIX: Fix regression on resuming torrents without metadata (thalieht)
|
||||
- BUGFIX: Reorder and rename Tracker list context menu option (Thomas Piccirello)
|
||||
- BUGFIX: Rename Tracker List columns (Thomas Piccirello)
|
||||
- BUGFIX: Show error message when Session failed to start (glassez)
|
||||
- BUGFIX: Embedded tracker: Use ip parameter from tracker request if provided (Chocobo1)
|
||||
- BUGFIX: Fix weekday names translations (Chocobo1)
|
||||
- BUGFIX: Fix strings not translated (Chocobo1)
|
||||
- WEBUI: Change qBittorrent exit message to HTML5 (Chocobo1)
|
||||
- WEBUI: Revise CSP header (Chocobo1)
|
||||
- WEBUI: Enforce referrer-policy in WebUI (Chocobo1)
|
||||
- WEBUI: Add torrent name filtering to WebUI (Thomas Piccirello)
|
||||
- WEBUI: Send numeric status without translation (Thomas Piccirello)
|
||||
- WEBUI: Add WebUI Trackers context menu (Thomas Piccirello)
|
||||
- WEBUI: Add DHT, PeX, and LSD to WebUI Tracker list (Thomas Piccirello)
|
||||
- WEBUI: Add additional Tracker columns to WebUI (Thomas Piccirello)
|
||||
- WEBUI: Bump Web API version
|
||||
- WEBUI: Fix display bugs in WebUI Files tab. Remove <IE9 support (Thomas Piccirello)
|
||||
- WEBUI: Fix incorrect priority value sent from WebUI (Thomas Piccirello)
|
||||
- WEBUI: Set priority for multiple files in one WebAPI request (Thomas Piccirello)
|
||||
- WEBUI: Match WebUI Peers table column order to GUI (Thomas Piccirello)
|
||||
- WEBUI: Fetch data less frequently when torrents tab isn't visible (Thomas Piccirello)
|
||||
- WEBUI: Add Search tab to WebUI (Thomas Piccirello)
|
||||
- WEBUI: Add ability to pass urls to the webui download page (Thomas Piccirello)
|
||||
- WEBUI: Fix JavaScript error (Tom Piccirello)
|
||||
- WEBUI: Disallow setting a blank alternative WebUI location (Thomas Piccirello)
|
||||
- WEBUI: Add slow torrent options (Thomas Piccirello)
|
||||
- WEBUI: Add "Use alternative Web UI" option (Thomas Piccirello)
|
||||
- WEBUI: Add "Apply rate limit to peers on LAN" option (Thomas Piccirello)
|
||||
- WEBUI: Add email "From" option (Thomas Piccirello)
|
||||
- WEBUI: Set WebUI download options using set preferences (Thomas Piccirello)
|
||||
- WEBUI: Show list of categories on WebUI download page (Thomas Piccirello)
|
||||
- WEBUI: Hide WebUI text input for custom monitor save locations (Thomas Piccirello)
|
||||
- WEBUI: Add "When adding a torrent" options (Thomas Piccirello)
|
||||
- WEBUI: Add WebUI Auto TMM options (Thomas Piccirello)
|
||||
- WEBUI: Add speed limit icons to WebUI Speed options (Thomas Piccirello)
|
||||
- WEBUI: Add WebUI Random port button and proxy unencrypted password notice (Thomas Piccirello)
|
||||
- WEBUI: Replace WebUI Options fixed-width labels (Thomas Piccirello)
|
||||
- WEBUI: Reorder WebUI options to match GUI (Thomas Piccirello)
|
||||
- WEBUI: Allow WebUI sidebar to be collapsed (Thomas Piccirello)
|
||||
- WEBUI: Show ellipsis when WebUI sidebar is too narrow (Thomas Piccirello)
|
||||
- WEBUI: Fix WebUI bug on override of Start Download option.Closes #9855. (Tom Piccirello)
|
||||
- WEBUI: Fix missing words in WebUI (Chocobo1)
|
||||
- WEBUI: Add SameSite attribute to WebUI session cookie (Thomas Piccirello)
|
||||
- WEBUI: Put WebUI security related options into a groupbox (Chocobo1)
|
||||
- WEBUI: Add option for WebUI Host header validation (Chocobo1)
|
||||
- WEBUI: Show icon in WebUI sorted column (Thomas Piccirello)
|
||||
- RSS: Keep track of REPACK/PROPER downloads. Closes #9898. (Stephen Dawkins)
|
||||
- SEARCH: Only instantiate SearchPluginManager as needed (Thomas Piccirello)
|
||||
- MACOS: Make file icon look like other macOS icons (Nick Korotysh)
|
||||
- MACOS: Save option to start minimized in Mac (thalieht)
|
||||
|
||||
* Mon Nov 19 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.4
|
||||
- FEATURE: Recognize *.ts files as previewable (silver)
|
||||
- FEATURE: Allow to disable speed graphs (dzmat)
|
||||
- FEATURE: Clear LineEdit on ESC (silverqx)
|
||||
- BUGFIX: Fix divide-by-zero crash (Chocobo1)
|
||||
- BUGFIX: Remove speed limit checkbox in Options dialog (Chocobo1)
|
||||
- BUGFIX: Fix speed graph "high speeds" bug (dzmat)
|
||||
- BUGFIX: Don't update torrent status unnecessarily (glassez)
|
||||
- BUGFIX: Improve force recheck of paused torrent (glassez)
|
||||
- BUGFIX: Restore torrent in two steps (glassez)
|
||||
- BUGFIX: Improve scaling of speed graphs (dzmat)
|
||||
- BUGFIX: Add isNetworkFileSystem() detection on Windows. This allows network mounts to be monitored correctly by polling timer. (Chocobo1)
|
||||
- BUGFIX: Reduce horizontal graphs resolution. Improves perfomance. (dzmat)
|
||||
- BUGFIX: Don't recheck just checked torrent (mj-p, glassez)
|
||||
- BUGFIX: Add SMB2 magic number (Chocobo1)
|
||||
- BUGFIX: Restore startup perfomance to v4.1.2 times. Needs at least libtorrent 1.1.10 (sledgehammer999)
|
||||
- BUGFIX: Make strings actually translatable (sledgehammer999)
|
||||
- WEBUI: Handle downloading .torrent file as success (Tom Piccirello)
|
||||
- WEBUI: Fix Alternative Web UI to be available (glassez)
|
||||
- WEBUI: Consider empty locale setting as not set (glassez)
|
||||
- WEBUI: Add free disk space to WebUI status bar (Thomas Piccirello)
|
||||
- WEBUI: Add WebUI search API controller (Thomas Piccirello)
|
||||
- WEBUI: Fix WebUI Auto TMM context menu bug (Thomas Piccirello)
|
||||
- WEBUI: Use independent translation for WebUI (glassez)
|
||||
- WEBUI: Add categories WebAPI (Thomas Piccirello)
|
||||
- WEBUI: Fix minor JavaScript defects (Thomas Piccirello)
|
||||
- WEBUI: Add locale to js file path (Thomas Piccirello)
|
||||
- WEBUI: Translate WebUI torrents Status column (Thomas Piccirello)
|
||||
- WEBUI: Bump Web API version
|
||||
- RSS: Allow to disable downloading REPACK/PROPER matches (Stephen Dawkins)
|
||||
- RSS: Improve RSS Feed updating (glassez)
|
||||
- SEARCH: Allow resizing search filter in search job (thalieht)
|
||||
- SEARCH: Improve parser for search engine versions.txt (Chocobo1)
|
||||
- SEARCH: Update Python URLs (Chocobo1)
|
||||
- SEARCH: Fix asking to install Python (Chocobo1)
|
||||
- SEARCH: Reformat python code to be compliant with PEP8 (Chocobo1)
|
||||
- OTHER: cmake: restore out-of-source build (Eugene Shalygin)
|
||||
- OTHER: cmake: cmake: use C++14 when available (Eugene Shalygin)
|
||||
|
||||
* Tue Sep 18 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.3
|
||||
- FEATURE: Preselect name without extension when renaming files (thalieht)
|
||||
- FEATURE: Allow setting seq & first/last from context menu without metadata (thalieht)
|
||||
- BUGFIX: Show "N/A" if there is no scrape (thalieht)
|
||||
- BUGFIX: Save option about tracker favicons under correct key (sledgehammer999)
|
||||
- BUGFIX: When file data are unreachable pause torrent and show "Missing Files" status (temporary fix) (sledgehammer999)
|
||||
- BUGFIX: Don't disable DHT when using force proxy (Thomas Piccirello)
|
||||
- BUGFIX: Correctly save torrent queue position/state/priority changes in fastresume (glassez, thalieht, sledgehammer999)
|
||||
- BUGFIX: Fix icon height/width ratio (Chocobo1)
|
||||
- BUGFIX: Fix values sorted wrong in "Last Activity" column (Chocobo1)
|
||||
- BUGFIX: Replace png icons with svg (Chocobo1)
|
||||
- WEBUI: Allow WebUI sidebar filters to be hidden (Thomas Piccirello)
|
||||
- WEBUI: Increase WebUI Options initial height (Thomas Piccirello)
|
||||
- WEBUI: Adjust WebUI Options form alignment (Thomas Piccirello)
|
||||
- WEBUI: Fix WebUI unreachable issue (Chocobo1)
|
||||
- WEBUI: Require torrent category creation to be explicit (Thomas Piccirello)
|
||||
- WEBUI: Include category save path in web api sync data (Thomas Piccirello)
|
||||
- WEBUI: Add save path and editing to WebUI new category dialog (Thomas Piccirello)
|
||||
- WEBUI: Bump Web API version
|
||||
- SEARCH: Refactor in searchjob to always color visited entries (thalieht)
|
||||
- SEARCH: Set "enter" as shortcut to download the selected torrents in search job (thalieht)
|
||||
- SEARCH: Add regex option in the search filter's context menu (thalieht)
|
||||
- LINUX: Fix GUI scaling issue on Linux (Chocobo1)
|
||||
- LINUX: Fix regression that broke installing desktop file (Eli Schwartz)
|
||||
- OPENBSD: Better filesystem support for filewatcher (Elias M. Mariani)
|
||||
|
||||
* Sun Aug 12 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.2
|
||||
- FEATURE: New options for "inhibit sleep" (Lukas Greib)
|
||||
- FEATURE: Add option for regexps in the transferlist search filter's context menu (thalieht)
|
||||
|
||||
49
INSTALL
49
INSTALL
@@ -1,54 +1,49 @@
|
||||
qBittorrent - A BitTorrent client in C++ / Qt4
|
||||
qBittorrent - A BitTorrent client in C++ / Qt
|
||||
------------------------------------------
|
||||
|
||||
1) Compile and install qBittorrent with Qt4 Graphical Interface
|
||||
1) Compile and install qBittorrent with Qt graphical interface
|
||||
|
||||
$ ./configure
|
||||
$ make && make install
|
||||
$ qbittorrent
|
||||
|
||||
will install and execute qBittorrent hopefully without any problems.
|
||||
will install and execute qBittorrent.
|
||||
|
||||
Dependencies:
|
||||
- Qt >= 4.6.0 (libqtgui, libqtcore, libqtnetwork, libqtxml, libqtdbus/optional)
|
||||
- Qt >= 5.5.1
|
||||
|
||||
- pkg-config executable
|
||||
- pkg-config
|
||||
|
||||
- libtorrent-rasterbar by Arvid Norberg (>= 1.0.6)
|
||||
-> http://www.libtorrent.net
|
||||
Be careful: another library (the one used by rTorrent) uses a similar name.
|
||||
- libtorrent-rasterbar >= 1.0.6 (by Arvid Norberg)
|
||||
* https://www.libtorrent.org/
|
||||
* Be careful: another library (the one used by rTorrent) uses a similar name
|
||||
|
||||
- libboost >= 1.35.x (libboost-system)
|
||||
- Boost >= 1.35
|
||||
|
||||
- python >= 2.3 (needed by search engine)
|
||||
* Run time only dependency
|
||||
- Python >= 2.7.9 / 3.3.0 (optional, runtime only)
|
||||
* Required by the internal search engine
|
||||
|
||||
- geoip-database (optional)
|
||||
* If qBittorrent cannot find this database, it will try to resolve countries using the Internet but it will be a lot slower.
|
||||
* Run time only dependency
|
||||
|
||||
2) Compile and install qBittorrent without Qt4 Graphical interface
|
||||
2) Compile and install qBittorrent without Qt graphical interface
|
||||
|
||||
$ ./configure --disable-gui
|
||||
$ make && make install
|
||||
$ qbittorrent
|
||||
$ qbittorrent-nox
|
||||
|
||||
will install and execute qBittorrent hopefully without any problems.
|
||||
will install and execute qBittorrent.
|
||||
|
||||
Dependencies:
|
||||
- Qt >= 4.4.0 (libqt-devel, libqtcore, libqtnetwork)
|
||||
- Qt >= 5.5.1
|
||||
|
||||
- pkg-config executable
|
||||
- pkg-config
|
||||
|
||||
- libtorrent-rasterbar by Arvid Norberg (>= v1.0.6)
|
||||
-> http://www.libtorrent.net
|
||||
Be careful: another library (the one used by rTorrent) uses a similar name.
|
||||
|
||||
- libboost: libboost-filesystem, libboost-date-time, libboost-thread, libboost-serialization
|
||||
- libtorrent-rasterbar >= 1.0.6 (by Arvid Norberg)
|
||||
* https://www.libtorrent.org/
|
||||
* Be careful: another library (the one used by rTorrent) uses a similar name
|
||||
|
||||
- Boost >= 1.35
|
||||
|
||||
DOCUMENTATION:
|
||||
Please note that there is a documentation with a "compiling howto" at http://wiki.qbittorrent.org.
|
||||
Please note that there is a "Compilation" section at http://wiki.qbittorrent.org.
|
||||
|
||||
------------------------------------------
|
||||
Christophe Dumez <chris@qbittorrent.org>
|
||||
sledgehammer999 <sledgehammer999@qbittorrent.org>
|
||||
|
||||
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()
|
||||
@@ -5,6 +5,8 @@ BINDIR = @EXPAND_BINDIR@
|
||||
DATADIR = @EXPAND_DATADIR@
|
||||
MANPREFIX = @EXPAND_MANDIR@
|
||||
|
||||
QMAKE_CC = @QBT_CC@
|
||||
QMAKE_CXX = @QBT_CXX@
|
||||
QMAKE_CXXFLAGS += @QBT_CONF_EXTRA_CFLAGS@
|
||||
|
||||
EXTERNAL_INCLUDES = @QBT_CONF_INCLUDES@
|
||||
|
||||
476
configure
vendored
476
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.2.
|
||||
# Generated by GNU Autoconf 2.69 for qbittorrent v4.1.7.
|
||||
#
|
||||
# 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.2'
|
||||
PACKAGE_STRING='qbittorrent v4.1.2'
|
||||
PACKAGE_VERSION='v4.1.7'
|
||||
PACKAGE_STRING='qbittorrent v4.1.7'
|
||||
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
|
||||
PACKAGE_URL='https://www.qbittorrent.org/'
|
||||
|
||||
@@ -595,6 +595,8 @@ QBT_REMOVE_CONFIG
|
||||
QBT_ADD_CONFIG
|
||||
QBT_CONF_EXTRA_CFLAGS
|
||||
QBT_CONF_INCLUDES
|
||||
QBT_CXX
|
||||
QBT_CC
|
||||
EXPAND_MANDIR
|
||||
EXPAND_DATADIR
|
||||
EXPAND_BINDIR
|
||||
@@ -626,7 +628,6 @@ am__nodep
|
||||
AMDEPBACKSLASH
|
||||
AMDEP_FALSE
|
||||
AMDEP_TRUE
|
||||
am__quote
|
||||
am__include
|
||||
DEPDIR
|
||||
am__untar
|
||||
@@ -709,7 +710,8 @@ PACKAGE_VERSION
|
||||
PACKAGE_TARNAME
|
||||
PACKAGE_NAME
|
||||
PATH_SEPARATOR
|
||||
SHELL'
|
||||
SHELL
|
||||
am__quote'
|
||||
ac_subst_files=''
|
||||
ac_user_opts='
|
||||
enable_option_checking
|
||||
@@ -1297,7 +1299,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.2 to adapt to many kinds of systems.
|
||||
\`configure' configures qbittorrent v4.1.7 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -1368,7 +1370,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.1.2:";;
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.1.7:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@@ -1503,7 +1505,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
qbittorrent configure v4.1.2
|
||||
qbittorrent configure v4.1.7
|
||||
generated by GNU Autoconf 2.69
|
||||
|
||||
Copyright (C) 2012 Free Software Foundation, Inc.
|
||||
@@ -1642,7 +1644,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.2, which was
|
||||
It was created by qbittorrent $as_me v4.1.7, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
$ $0 $@
|
||||
@@ -3274,7 +3276,7 @@ IFS=$ac_save_IFS
|
||||
case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac
|
||||
|
||||
|
||||
am__api_version='1.15'
|
||||
am__api_version='1.16'
|
||||
|
||||
# Find a good install program. We prefer a C program (faster),
|
||||
# so one script is as good as another. But avoid the broken or
|
||||
@@ -3700,45 +3702,45 @@ DEPDIR="${am__leading_dot}deps"
|
||||
|
||||
ac_config_commands="$ac_config_commands depfiles"
|
||||
|
||||
|
||||
am_make=${MAKE-make}
|
||||
cat > confinc << 'END'
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} supports the include directive" >&5
|
||||
$as_echo_n "checking whether ${MAKE-make} supports the include directive... " >&6; }
|
||||
cat > confinc.mk << 'END'
|
||||
am__doit:
|
||||
@echo this is the am__doit target
|
||||
@echo this is the am__doit target >confinc.out
|
||||
.PHONY: am__doit
|
||||
END
|
||||
# If we don't find an include directive, just comment out the code.
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5
|
||||
$as_echo_n "checking for style of include used by $am_make... " >&6; }
|
||||
am__include="#"
|
||||
am__quote=
|
||||
_am_result=none
|
||||
# First try GNU make style include.
|
||||
echo "include confinc" > confmf
|
||||
# Ignore all kinds of additional output from 'make'.
|
||||
case `$am_make -s -f confmf 2> /dev/null` in #(
|
||||
*the\ am__doit\ target*)
|
||||
am__include=include
|
||||
am__quote=
|
||||
_am_result=GNU
|
||||
;;
|
||||
esac
|
||||
# Now try BSD make style include.
|
||||
if test "$am__include" = "#"; then
|
||||
echo '.include "confinc"' > confmf
|
||||
case `$am_make -s -f confmf 2> /dev/null` in #(
|
||||
*the\ am__doit\ target*)
|
||||
am__include=.include
|
||||
am__quote="\""
|
||||
_am_result=BSD
|
||||
# BSD make does it like this.
|
||||
echo '.include "confinc.mk" # ignored' > confmf.BSD
|
||||
# Other make implementations (GNU, Solaris 10, AIX) do it like this.
|
||||
echo 'include confinc.mk # ignored' > confmf.GNU
|
||||
_am_result=no
|
||||
for s in GNU BSD; do
|
||||
{ echo "$as_me:$LINENO: ${MAKE-make} -f confmf.$s && cat confinc.out" >&5
|
||||
(${MAKE-make} -f confmf.$s && cat confinc.out) >&5 2>&5
|
||||
ac_status=$?
|
||||
echo "$as_me:$LINENO: \$? = $ac_status" >&5
|
||||
(exit $ac_status); }
|
||||
case $?:`cat confinc.out 2>/dev/null` in #(
|
||||
'0:this is the am__doit target') :
|
||||
case $s in #(
|
||||
BSD) :
|
||||
am__include='.include' am__quote='"' ;; #(
|
||||
*) :
|
||||
am__include='include' am__quote='' ;;
|
||||
esac ;; #(
|
||||
*) :
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5
|
||||
$as_echo "$_am_result" >&6; }
|
||||
rm -f confinc confmf
|
||||
esac
|
||||
if test "$am__include" != "#"; then
|
||||
_am_result="yes ($s style)"
|
||||
break
|
||||
fi
|
||||
done
|
||||
rm -f confinc.* confmf.*
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${_am_result}" >&5
|
||||
$as_echo "${_am_result}" >&6; }
|
||||
|
||||
# Check whether --enable-dependency-tracking was given.
|
||||
if test "${enable_dependency_tracking+set}" = set; then :
|
||||
@@ -3820,7 +3822,7 @@ fi
|
||||
|
||||
# Define the identity of the package.
|
||||
PACKAGE='qbittorrent'
|
||||
VERSION='v4.1.2'
|
||||
VERSION='v4.1.7'
|
||||
|
||||
|
||||
cat >>confdefs.h <<_ACEOF
|
||||
@@ -3850,8 +3852,8 @@ MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"}
|
||||
|
||||
# For better backward compatibility. To be removed once Automake 1.9.x
|
||||
# dies out for good. For more background, see:
|
||||
# <http://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
|
||||
# <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
|
||||
# <https://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
|
||||
# <https://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
|
||||
mkdir_p='$(MKDIR_P)'
|
||||
|
||||
# We need awk for the "check" target (and possibly the TAP driver). The
|
||||
@@ -4158,7 +4160,7 @@ END
|
||||
Aborting the configuration process, to ensure you take notice of the issue.
|
||||
|
||||
You can download and install GNU coreutils to get an 'rm' implementation
|
||||
that behaves properly: <http://www.gnu.org/software/coreutils/>.
|
||||
that behaves properly: <https://www.gnu.org/software/coreutils/>.
|
||||
|
||||
If you want to complete the configuration process using your problematic
|
||||
'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM
|
||||
@@ -4170,7 +4172,9 @@ END
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# use compiler from env variables if available
|
||||
QBT_CC="$CC"
|
||||
QBT_CXX="$CXX"
|
||||
|
||||
# Define --wth-* and --enable-* arguments
|
||||
|
||||
@@ -4997,10 +5001,10 @@ $as_echo "$as_me: Your boost libraries seems to old (version $_version)." >&6;}
|
||||
$as_echo "#define HAVE_BOOST /**/" >>confdefs.h
|
||||
|
||||
# execute ACTION-IF-FOUND (if present):
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: Boost CPPFLAGS: \"$BOOST_CPPFLAGS\"
|
||||
Boost LDFLAGS: \"$BOOST_LDFLAGS\"" >&5
|
||||
$as_echo "$as_me: Boost CPPFLAGS: \"$BOOST_CPPFLAGS\"
|
||||
Boost LDFLAGS: \"$BOOST_LDFLAGS\"" >&6;}
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: Boost CXXFLAGS: \"$BOOST_CPPFLAGS\"" >&5
|
||||
$as_echo "$as_me: Boost CXXFLAGS: \"$BOOST_CPPFLAGS\"" >&6;}
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: Boost LDFLAGS: \"$BOOST_LDFLAGS\"" >&5
|
||||
$as_echo "$as_me: Boost LDFLAGS: \"$BOOST_LDFLAGS\"" >&6;}
|
||||
fi
|
||||
|
||||
CPPFLAGS="$CPPFLAGS_SAVED"
|
||||
@@ -5011,16 +5015,10 @@ fi
|
||||
|
||||
|
||||
|
||||
CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS"
|
||||
CXXFLAGS="$BOOST_CPPFLAGS $CXXFLAGS"
|
||||
LDFLAGS="$BOOST_LDFLAGS $LDFLAGS"
|
||||
|
||||
# add workaround for problematic boost version
|
||||
ac_ext=cpp
|
||||
ac_cpp='$CXXCPP $CPPFLAGS'
|
||||
ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
|
||||
ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
|
||||
ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
|
||||
|
||||
# taken from ax_boost_base.m4
|
||||
|
||||
|
||||
@@ -5041,12 +5039,6 @@ else
|
||||
QBT_ADD_DEFINES="$QBT_ADD_DEFINES BOOST_NO_CXX11_RVALUE_REFERENCES"
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
|
||||
ac_ext=cpp
|
||||
ac_cpp='$CXXCPP $CPPFLAGS'
|
||||
ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
|
||||
ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
|
||||
ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5400,7 +5392,7 @@ else
|
||||
libtorrent_LIBS=$pkg_cv_libtorrent_LIBS
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
CPPFLAGS="$libtorrent_CFLAGS $CPPFLAGS"
|
||||
CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS"
|
||||
LIBS="$libtorrent_LIBS $LIBS"
|
||||
fi
|
||||
|
||||
@@ -5493,10 +5485,116 @@ else
|
||||
zlib_LIBS=$pkg_cv_zlib_LIBS
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
CPPFLAGS="$zlib_CFLAGS $CPPFLAGS"
|
||||
CXXFLAGS="$zlib_CFLAGS $CXXFLAGS"
|
||||
LIBS="$zlib_LIBS $LIBS"
|
||||
fi
|
||||
|
||||
# Check if already in >= C++11 mode because of the flags returned by one of the above packages
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler is using C++11 or later mode" >&5
|
||||
$as_echo_n "checking if compiler is using C++11 or later mode... " >&6; }
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
#ifndef __cplusplus
|
||||
#error "This is not a C++ compiler"
|
||||
#elif __cplusplus < 201103L
|
||||
#error "This is not a C++11 compiler"
|
||||
#endif
|
||||
int
|
||||
main ()
|
||||
{
|
||||
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_ACEOF
|
||||
if ac_fn_cxx_try_compile "$LINENO"; then :
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
QBT_CXX11_FOUND="yes"
|
||||
else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
|
||||
$as_echo "no" >&6; }
|
||||
QBT_CXX11_FOUND="no"
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
|
||||
|
||||
# In case of no, check if the compiler can support at least C++11
|
||||
# and if yes, enable it leaving a warning to the user
|
||||
if test "x$QBT_CXX11_FOUND" = "xno"; then :
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler supports C++11" >&5
|
||||
$as_echo_n "checking if compiler supports C++11... " >&6; }
|
||||
TMP_CXXFLAGS="$CXXFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS -std=c++11"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
#ifndef __cplusplus
|
||||
#error "This is not a C++ compiler"
|
||||
#elif __cplusplus < 201103L
|
||||
#error "This is not a C++11 compiler"
|
||||
#endif
|
||||
int
|
||||
main ()
|
||||
{
|
||||
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_ACEOF
|
||||
if ac_fn_cxx_try_compile "$LINENO"; then :
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if C++11 is disabled by the set compiler flags" >&5
|
||||
$as_echo_n "checking if C++11 is disabled by the set compiler flags... " >&6; }
|
||||
# prepend the flag so it won't override conflicting user defined flags
|
||||
CXXFLAGS="-std=c++11 $TMP_CXXFLAGS"
|
||||
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
|
||||
/* end confdefs.h. */
|
||||
|
||||
#ifndef __cplusplus
|
||||
#error "This is not a C++ compiler"
|
||||
#elif __cplusplus < 201103L
|
||||
#error "This is not a C++11 compiler"
|
||||
#endif
|
||||
int
|
||||
main ()
|
||||
{
|
||||
|
||||
;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_ACEOF
|
||||
if ac_fn_cxx_try_compile "$LINENO"; then :
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
|
||||
$as_echo "no" >&6; }
|
||||
CXXFLAGS="$TMP_CXXFLAGS -std=c++11"
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: C++11 mode is now force enabled.
|
||||
Make sure you use the same C++ mode for qBittorrent and its dependencies.
|
||||
To explicitly set qBittorrent to a later mode use CXXFLAGS.
|
||||
Example: \`CXXFLAGS=\"\$CXXFLAGS -std=c++14\" ./configure\`" >&5
|
||||
$as_echo "$as_me: WARNING: C++11 mode is now force enabled.
|
||||
Make sure you use the same C++ mode for qBittorrent and its dependencies.
|
||||
To explicitly set qBittorrent to a later mode use CXXFLAGS.
|
||||
Example: \`CXXFLAGS=\"\$CXXFLAGS -std=c++14\" ./configure\`" >&2;}
|
||||
else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
|
||||
$as_echo "yes" >&6; }
|
||||
as_fn_error $? "The compiler supports C++11 but the user or a dependency has explicitly enabled a lower mode." "$LINENO" 5
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
|
||||
else
|
||||
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
|
||||
$as_echo "no" >&6; }
|
||||
as_fn_error $? "A compiler supporting C++11 is required." "$LINENO" 5
|
||||
fi
|
||||
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
|
||||
|
||||
fi
|
||||
|
||||
# These are required because autoconf doesn't expand these **particular**
|
||||
# vars automatically. And qmake cannot autoexpand them.
|
||||
|
||||
@@ -5583,15 +5681,15 @@ extract() {
|
||||
for i in $string; do
|
||||
case "$(echo "$i" | cut -c1)" in
|
||||
'') ;;
|
||||
D) QBT_CONF_DEFINES="$(echo $i | cut -c2-) $QBT_CONF_DEFINES";;
|
||||
I) QBT_CONF_INCLUDES="$(echo $i | cut -c2-) $QBT_CONF_INCLUDES";;
|
||||
*) QBT_CONF_EXTRA_CFLAGS="-$i $QBT_CONF_EXTRA_CFLAGS";;
|
||||
D) QBT_CONF_DEFINES="$QBT_CONF_DEFINES $(echo $i | cut -c2-)";;
|
||||
I) QBT_CONF_INCLUDES="$QBT_CONF_INCLUDES $(echo $i | cut -c2-)";;
|
||||
*) QBT_CONF_EXTRA_CFLAGS="$QBT_CONF_EXTRA_CFLAGS -$i";;
|
||||
esac
|
||||
done
|
||||
IFS=$SAVEIFS
|
||||
}
|
||||
|
||||
extract "$CFLAGS $CPPFLAGS $CXXFLAGS"
|
||||
extract "$CFLAGS $CXXFLAGS"
|
||||
QBT_ADD_DEFINES="$QBT_ADD_DEFINES $QBT_CONF_DEFINES"
|
||||
|
||||
# Substitute the values of these vars in conf.pri.in
|
||||
@@ -5602,6 +5700,8 @@ QBT_ADD_DEFINES="$QBT_ADD_DEFINES $QBT_CONF_DEFINES"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ac_config_files="$ac_config_files conf.pri"
|
||||
|
||||
cat >confcache <<\_ACEOF
|
||||
@@ -6174,7 +6274,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.2, which was
|
||||
This file was extended by qbittorrent $as_me v4.1.7, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -6232,7 +6332,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.2
|
||||
qbittorrent config.status v4.1.7
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
@@ -6340,7 +6440,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
#
|
||||
# INIT-COMMANDS
|
||||
#
|
||||
AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"
|
||||
AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"
|
||||
|
||||
_ACEOF
|
||||
|
||||
@@ -6785,29 +6885,35 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
|
||||
# Older Autoconf quotes --file arguments for eval, but not when files
|
||||
# are listed without --file. Let's play safe and only enable the eval
|
||||
# if we detect the quoting.
|
||||
case $CONFIG_FILES in
|
||||
*\'*) eval set x "$CONFIG_FILES" ;;
|
||||
*) set x $CONFIG_FILES ;;
|
||||
esac
|
||||
# TODO: see whether this extra hack can be removed once we start
|
||||
# requiring Autoconf 2.70 or later.
|
||||
case $CONFIG_FILES in #(
|
||||
*\'*) :
|
||||
eval set x "$CONFIG_FILES" ;; #(
|
||||
*) :
|
||||
set x $CONFIG_FILES ;; #(
|
||||
*) :
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
for mf
|
||||
# Used to flag and report bootstrapping failures.
|
||||
am_rc=0
|
||||
for am_mf
|
||||
do
|
||||
# Strip MF so we end up with the name of the file.
|
||||
mf=`echo "$mf" | sed -e 's/:.*$//'`
|
||||
# Check whether this is an Automake generated Makefile or not.
|
||||
# We used to match only the files named 'Makefile.in', but
|
||||
# some people rename them; so instead we look at the file content.
|
||||
# Grep'ing the first line is not enough: some people post-process
|
||||
# each Makefile.in and add a new line on top of each file to say so.
|
||||
# Grep'ing the whole file is not good either: AIX grep has a line
|
||||
am_mf=`$as_echo "$am_mf" | sed -e 's/:.*$//'`
|
||||
# Check whether this is an Automake generated Makefile which includes
|
||||
# dependency-tracking related rules and includes.
|
||||
# Grep'ing the whole file directly is not great: AIX grep has a line
|
||||
# limit of 2048, but all sed's we know have understand at least 4000.
|
||||
if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
|
||||
dirpart=`$as_dirname -- "$mf" ||
|
||||
$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
|
||||
X"$mf" : 'X\(//\)[^/]' \| \
|
||||
X"$mf" : 'X\(//\)$' \| \
|
||||
X"$mf" : 'X\(/\)' \| . 2>/dev/null ||
|
||||
$as_echo X"$mf" |
|
||||
sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \
|
||||
|| continue
|
||||
am_dirpart=`$as_dirname -- "$am_mf" ||
|
||||
$as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
|
||||
X"$am_mf" : 'X\(//\)[^/]' \| \
|
||||
X"$am_mf" : 'X\(//\)$' \| \
|
||||
X"$am_mf" : 'X\(/\)' \| . 2>/dev/null ||
|
||||
$as_echo X"$am_mf" |
|
||||
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
|
||||
s//\1/
|
||||
q
|
||||
@@ -6825,53 +6931,48 @@ $as_echo X"$mf" |
|
||||
q
|
||||
}
|
||||
s/.*/./; q'`
|
||||
else
|
||||
continue
|
||||
fi
|
||||
# Extract the definition of DEPDIR, am__include, and am__quote
|
||||
# from the Makefile without running 'make'.
|
||||
DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
|
||||
test -z "$DEPDIR" && continue
|
||||
am__include=`sed -n 's/^am__include = //p' < "$mf"`
|
||||
test -z "$am__include" && continue
|
||||
am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
|
||||
# Find all dependency output files, they are included files with
|
||||
# $(DEPDIR) in their names. We invoke sed twice because it is the
|
||||
# simplest approach to changing $(DEPDIR) to its actual value in the
|
||||
# expansion.
|
||||
for file in `sed -n "
|
||||
s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
|
||||
sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do
|
||||
# Make sure the directory exists.
|
||||
test -f "$dirpart/$file" && continue
|
||||
fdir=`$as_dirname -- "$file" ||
|
||||
$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
|
||||
X"$file" : 'X\(//\)[^/]' \| \
|
||||
X"$file" : 'X\(//\)$' \| \
|
||||
X"$file" : 'X\(/\)' \| . 2>/dev/null ||
|
||||
$as_echo X"$file" |
|
||||
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
|
||||
am_filepart=`$as_basename -- "$am_mf" ||
|
||||
$as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \
|
||||
X"$am_mf" : 'X\(//\)$' \| \
|
||||
X"$am_mf" : 'X\(/\)' \| . 2>/dev/null ||
|
||||
$as_echo X/"$am_mf" |
|
||||
sed '/^.*\/\([^/][^/]*\)\/*$/{
|
||||
s//\1/
|
||||
q
|
||||
}
|
||||
/^X\(\/\/\)[^/].*/{
|
||||
/^X\/\(\/\/\)$/{
|
||||
s//\1/
|
||||
q
|
||||
}
|
||||
/^X\(\/\/\)$/{
|
||||
s//\1/
|
||||
q
|
||||
}
|
||||
/^X\(\/\).*/{
|
||||
/^X\/\(\/\).*/{
|
||||
s//\1/
|
||||
q
|
||||
}
|
||||
s/.*/./; q'`
|
||||
as_dir=$dirpart/$fdir; as_fn_mkdir_p
|
||||
# echo "creating $dirpart/$file"
|
||||
echo '# dummy' > "$dirpart/$file"
|
||||
done
|
||||
{ echo "$as_me:$LINENO: cd "$am_dirpart" \
|
||||
&& sed -e '/# am--include-marker/d' "$am_filepart" \
|
||||
| $MAKE -f - am--depfiles" >&5
|
||||
(cd "$am_dirpart" \
|
||||
&& sed -e '/# am--include-marker/d' "$am_filepart" \
|
||||
| $MAKE -f - am--depfiles) >&5 2>&5
|
||||
ac_status=$?
|
||||
echo "$as_me:$LINENO: \$? = $ac_status" >&5
|
||||
(exit $ac_status); } || am_rc=$?
|
||||
done
|
||||
if test $am_rc -ne 0; then
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||
as_fn_error $? "Something went wrong bootstrapping makefile fragments
|
||||
for automatic dependency tracking. Try re-running configure with the
|
||||
'--disable-dependency-tracking' option to at least be able to build
|
||||
the package (albeit without support for automatic dependency tracking).
|
||||
See \`config.log' for more details" "$LINENO" 5; }
|
||||
fi
|
||||
{ am_dirpart=; unset am_dirpart;}
|
||||
{ am_filepart=; unset am_filepart;}
|
||||
{ am_mf=; unset am_mf;}
|
||||
{ am_rc=; unset am_rc;}
|
||||
rm -f conftest-deps.mk
|
||||
}
|
||||
;;
|
||||
|
||||
@@ -7489,7 +7590,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.2, which was
|
||||
This file was extended by qbittorrent $as_me v4.1.7, which was
|
||||
generated by GNU Autoconf 2.69. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -7547,7 +7648,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.2
|
||||
qbittorrent config.status v4.1.7
|
||||
configured by $0, generated by GNU Autoconf 2.69,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
@@ -7655,7 +7756,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
#
|
||||
# INIT-COMMANDS
|
||||
#
|
||||
AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"
|
||||
AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"
|
||||
|
||||
_ACEOF
|
||||
|
||||
@@ -8101,29 +8202,35 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
|
||||
# Older Autoconf quotes --file arguments for eval, but not when files
|
||||
# are listed without --file. Let's play safe and only enable the eval
|
||||
# if we detect the quoting.
|
||||
case $CONFIG_FILES in
|
||||
*\'*) eval set x "$CONFIG_FILES" ;;
|
||||
*) set x $CONFIG_FILES ;;
|
||||
esac
|
||||
# TODO: see whether this extra hack can be removed once we start
|
||||
# requiring Autoconf 2.70 or later.
|
||||
case $CONFIG_FILES in #(
|
||||
*\'*) :
|
||||
eval set x "$CONFIG_FILES" ;; #(
|
||||
*) :
|
||||
set x $CONFIG_FILES ;; #(
|
||||
*) :
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
for mf
|
||||
# Used to flag and report bootstrapping failures.
|
||||
am_rc=0
|
||||
for am_mf
|
||||
do
|
||||
# Strip MF so we end up with the name of the file.
|
||||
mf=`echo "$mf" | sed -e 's/:.*$//'`
|
||||
# Check whether this is an Automake generated Makefile or not.
|
||||
# We used to match only the files named 'Makefile.in', but
|
||||
# some people rename them; so instead we look at the file content.
|
||||
# Grep'ing the first line is not enough: some people post-process
|
||||
# each Makefile.in and add a new line on top of each file to say so.
|
||||
# Grep'ing the whole file is not good either: AIX grep has a line
|
||||
am_mf=`$as_echo "$am_mf" | sed -e 's/:.*$//'`
|
||||
# Check whether this is an Automake generated Makefile which includes
|
||||
# dependency-tracking related rules and includes.
|
||||
# Grep'ing the whole file directly is not great: AIX grep has a line
|
||||
# limit of 2048, but all sed's we know have understand at least 4000.
|
||||
if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
|
||||
dirpart=`$as_dirname -- "$mf" ||
|
||||
$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
|
||||
X"$mf" : 'X\(//\)[^/]' \| \
|
||||
X"$mf" : 'X\(//\)$' \| \
|
||||
X"$mf" : 'X\(/\)' \| . 2>/dev/null ||
|
||||
$as_echo X"$mf" |
|
||||
sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \
|
||||
|| continue
|
||||
am_dirpart=`$as_dirname -- "$am_mf" ||
|
||||
$as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
|
||||
X"$am_mf" : 'X\(//\)[^/]' \| \
|
||||
X"$am_mf" : 'X\(//\)$' \| \
|
||||
X"$am_mf" : 'X\(/\)' \| . 2>/dev/null ||
|
||||
$as_echo X"$am_mf" |
|
||||
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
|
||||
s//\1/
|
||||
q
|
||||
@@ -8141,53 +8248,48 @@ $as_echo X"$mf" |
|
||||
q
|
||||
}
|
||||
s/.*/./; q'`
|
||||
else
|
||||
continue
|
||||
fi
|
||||
# Extract the definition of DEPDIR, am__include, and am__quote
|
||||
# from the Makefile without running 'make'.
|
||||
DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
|
||||
test -z "$DEPDIR" && continue
|
||||
am__include=`sed -n 's/^am__include = //p' < "$mf"`
|
||||
test -z "$am__include" && continue
|
||||
am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
|
||||
# Find all dependency output files, they are included files with
|
||||
# $(DEPDIR) in their names. We invoke sed twice because it is the
|
||||
# simplest approach to changing $(DEPDIR) to its actual value in the
|
||||
# expansion.
|
||||
for file in `sed -n "
|
||||
s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
|
||||
sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do
|
||||
# Make sure the directory exists.
|
||||
test -f "$dirpart/$file" && continue
|
||||
fdir=`$as_dirname -- "$file" ||
|
||||
$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
|
||||
X"$file" : 'X\(//\)[^/]' \| \
|
||||
X"$file" : 'X\(//\)$' \| \
|
||||
X"$file" : 'X\(/\)' \| . 2>/dev/null ||
|
||||
$as_echo X"$file" |
|
||||
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
|
||||
am_filepart=`$as_basename -- "$am_mf" ||
|
||||
$as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \
|
||||
X"$am_mf" : 'X\(//\)$' \| \
|
||||
X"$am_mf" : 'X\(/\)' \| . 2>/dev/null ||
|
||||
$as_echo X/"$am_mf" |
|
||||
sed '/^.*\/\([^/][^/]*\)\/*$/{
|
||||
s//\1/
|
||||
q
|
||||
}
|
||||
/^X\(\/\/\)[^/].*/{
|
||||
/^X\/\(\/\/\)$/{
|
||||
s//\1/
|
||||
q
|
||||
}
|
||||
/^X\(\/\/\)$/{
|
||||
s//\1/
|
||||
q
|
||||
}
|
||||
/^X\(\/\).*/{
|
||||
/^X\/\(\/\).*/{
|
||||
s//\1/
|
||||
q
|
||||
}
|
||||
s/.*/./; q'`
|
||||
as_dir=$dirpart/$fdir; as_fn_mkdir_p
|
||||
# echo "creating $dirpart/$file"
|
||||
echo '# dummy' > "$dirpart/$file"
|
||||
done
|
||||
{ echo "$as_me:$LINENO: cd "$am_dirpart" \
|
||||
&& sed -e '/# am--include-marker/d' "$am_filepart" \
|
||||
| $MAKE -f - am--depfiles" >&5
|
||||
(cd "$am_dirpart" \
|
||||
&& sed -e '/# am--include-marker/d' "$am_filepart" \
|
||||
| $MAKE -f - am--depfiles) >&5 2>&5
|
||||
ac_status=$?
|
||||
echo "$as_me:$LINENO: \$? = $ac_status" >&5
|
||||
(exit $ac_status); } || am_rc=$?
|
||||
done
|
||||
if test $am_rc -ne 0; then
|
||||
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
|
||||
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
|
||||
as_fn_error $? "Something went wrong bootstrapping makefile fragments
|
||||
for automatic dependency tracking. Try re-running configure with the
|
||||
'--disable-dependency-tracking' option to at least be able to build
|
||||
the package (albeit without support for automatic dependency tracking).
|
||||
See \`config.log' for more details" "$LINENO" 5; }
|
||||
fi
|
||||
{ am_dirpart=; unset am_dirpart;}
|
||||
{ am_filepart=; unset am_filepart;}
|
||||
{ am_mf=; unset am_mf;}
|
||||
{ am_rc=; unset am_rc;}
|
||||
rm -f conftest-deps.mk
|
||||
}
|
||||
;;
|
||||
|
||||
|
||||
60
configure.ac
60
configure.ac
@@ -1,4 +1,4 @@
|
||||
AC_INIT([qbittorrent], [v4.1.2], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_INIT([qbittorrent], [v4.1.7], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_PROG_CC
|
||||
@@ -8,7 +8,9 @@ AC_LANG(C++)
|
||||
AC_CANONICAL_HOST
|
||||
AM_INIT_AUTOMAKE
|
||||
|
||||
|
||||
# use compiler from env variables if available
|
||||
QBT_CC="$CC"
|
||||
QBT_CXX="$CXX"
|
||||
|
||||
# Define --wth-* and --enable-* arguments
|
||||
|
||||
@@ -162,14 +164,13 @@ AS_CASE(["x$enable_qt_dbus"],
|
||||
|
||||
|
||||
AX_BOOST_BASE([1.35],
|
||||
[AC_MSG_NOTICE([Boost CPPFLAGS: "$BOOST_CPPFLAGS"
|
||||
Boost LDFLAGS: "$BOOST_LDFLAGS"])],
|
||||
[AC_MSG_NOTICE([Boost CXXFLAGS: "$BOOST_CPPFLAGS"])
|
||||
AC_MSG_NOTICE([Boost LDFLAGS: "$BOOST_LDFLAGS"])],
|
||||
[AC_MSG_ERROR([Could not find Boost])])
|
||||
CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS"
|
||||
CXXFLAGS="$BOOST_CPPFLAGS $CXXFLAGS"
|
||||
LDFLAGS="$BOOST_LDFLAGS $LDFLAGS"
|
||||
|
||||
# add workaround for problematic boost version
|
||||
AC_LANG_PUSH(C++)
|
||||
# taken from ax_boost_base.m4
|
||||
m4_define([DETECT_BOOST_VERSION_PROGRAM],
|
||||
[AC_LANG_PROGRAM([[#include <boost/version.hpp>]],
|
||||
@@ -177,7 +178,6 @@ m4_define([DETECT_BOOST_VERSION_PROGRAM],
|
||||
|
||||
AC_COMPILE_IFELSE([DETECT_BOOST_VERSION_PROGRAM(106000)], [],
|
||||
[QBT_ADD_DEFINES="$QBT_ADD_DEFINES BOOST_NO_CXX11_RVALUE_REFERENCES"])
|
||||
AC_LANG_POP([C++])
|
||||
|
||||
AX_BOOST_SYSTEM()
|
||||
AC_MSG_NOTICE([Boost.System LIB: "$BOOST_SYSTEM_LIB"])
|
||||
@@ -196,14 +196,46 @@ AS_CASE(["x$with_qtsingleapplication"],
|
||||
|
||||
PKG_CHECK_MODULES(libtorrent,
|
||||
[libtorrent-rasterbar >= 1.0.6],
|
||||
[CPPFLAGS="$libtorrent_CFLAGS $CPPFLAGS"
|
||||
[CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS"
|
||||
LIBS="$libtorrent_LIBS $LIBS"])
|
||||
|
||||
PKG_CHECK_MODULES(zlib,
|
||||
[zlib >= 1.2.5.2],
|
||||
[CPPFLAGS="$zlib_CFLAGS $CPPFLAGS"
|
||||
[CXXFLAGS="$zlib_CFLAGS $CXXFLAGS"
|
||||
LIBS="$zlib_LIBS $LIBS"])
|
||||
|
||||
# Check if already in >= C++11 mode because of the flags returned by one of the above packages
|
||||
AC_MSG_CHECKING([if compiler is using C++11 or later mode])
|
||||
AC_COMPILE_IFELSE([DETECT_CPP11_PROGRAM()],
|
||||
[AC_MSG_RESULT([yes])
|
||||
QBT_CXX11_FOUND="yes"],
|
||||
[AC_MSG_RESULT([no])
|
||||
QBT_CXX11_FOUND="no"])
|
||||
|
||||
# In case of no, check if the compiler can support at least C++11
|
||||
# and if yes, enable it leaving a warning to the user
|
||||
AS_IF([test "x$QBT_CXX11_FOUND" = "xno"],
|
||||
[AC_MSG_CHECKING([if compiler supports C++11])
|
||||
TMP_CXXFLAGS="$CXXFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS -std=c++11"
|
||||
AC_COMPILE_IFELSE([DETECT_CPP11_PROGRAM()],
|
||||
[AC_MSG_RESULT([yes])
|
||||
AC_MSG_CHECKING([if C++11 is disabled by the set compiler flags])
|
||||
# prepend the flag so it won't override conflicting user defined flags
|
||||
CXXFLAGS="-std=c++11 $TMP_CXXFLAGS"
|
||||
AC_COMPILE_IFELSE([DETECT_CPP11_PROGRAM()],
|
||||
[AC_MSG_RESULT([no])
|
||||
CXXFLAGS="$TMP_CXXFLAGS -std=c++11"
|
||||
AC_MSG_WARN([C++11 mode is now force enabled.
|
||||
Make sure you use the same C++ mode for qBittorrent and its dependencies.
|
||||
To explicitly set qBittorrent to a later mode use CXXFLAGS.
|
||||
Example: `CXXFLAGS="\$CXXFLAGS -std=c++14" ./configure`])],
|
||||
[AC_MSG_RESULT([yes])
|
||||
AC_MSG_ERROR([The compiler supports C++11 but the user or a dependency has explicitly enabled a lower mode.])])],
|
||||
[AC_MSG_RESULT([no])
|
||||
AC_MSG_ERROR([A compiler supporting C++11 is required.])])
|
||||
])
|
||||
|
||||
# These are required because autoconf doesn't expand these **particular**
|
||||
# vars automatically. And qmake cannot autoexpand them.
|
||||
AX_DEFINE_DIR([EXPAND_PREFIX], [prefix])
|
||||
@@ -230,18 +262,20 @@ extract() {
|
||||
for i in $string; do
|
||||
case "$(echo "$i" | cut -c1)" in
|
||||
'') ;;
|
||||
D) QBT_CONF_DEFINES="$(echo $i | cut -c2-) $QBT_CONF_DEFINES";;
|
||||
I) QBT_CONF_INCLUDES="$(echo $i | cut -c2-) $QBT_CONF_INCLUDES";;
|
||||
*) QBT_CONF_EXTRA_CFLAGS="-$i $QBT_CONF_EXTRA_CFLAGS";;
|
||||
D) QBT_CONF_DEFINES="$QBT_CONF_DEFINES $(echo $i | cut -c2-)";;
|
||||
I) QBT_CONF_INCLUDES="$QBT_CONF_INCLUDES $(echo $i | cut -c2-)";;
|
||||
*) QBT_CONF_EXTRA_CFLAGS="$QBT_CONF_EXTRA_CFLAGS -$i";;
|
||||
esac
|
||||
done
|
||||
IFS=$SAVEIFS
|
||||
}
|
||||
|
||||
extract "$CFLAGS $CPPFLAGS $CXXFLAGS"
|
||||
extract "$CFLAGS $CXXFLAGS"
|
||||
QBT_ADD_DEFINES="$QBT_ADD_DEFINES $QBT_CONF_DEFINES"
|
||||
|
||||
# Substitute the values of these vars in conf.pri.in
|
||||
AC_SUBST(QBT_CC)
|
||||
AC_SUBST(QBT_CXX)
|
||||
AC_SUBST(QBT_CONF_INCLUDES)
|
||||
AC_SUBST(QBT_CONF_EXTRA_CFLAGS)
|
||||
AC_SUBST(QBT_ADD_CONFIG)
|
||||
|
||||
22
dist/mac/Info.plist
vendored
22
dist/mac/Info.plist
vendored
@@ -2,6 +2,10 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>qBittorrent</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -21,6 +25,10 @@
|
||||
<array>
|
||||
<string>org.bittorrent.torrent</string>
|
||||
</array>
|
||||
<key>NSExportableTypes</key>
|
||||
<array>
|
||||
<string>org.bittorrent.torrent</string>
|
||||
</array>
|
||||
<key>LSIsAppleDefaultForType</key>
|
||||
<true/>
|
||||
</dict>
|
||||
@@ -28,6 +36,8 @@
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>magnet</string>
|
||||
@@ -45,21 +55,19 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.1.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>qBit</string>
|
||||
<string>4.1.7</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>@EXECUTABLE@</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.qbittorrent</string>
|
||||
<string>org.qbittorrent.qBittorrent</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOSX_DEPLOYMENT_TARGET}.0</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<string>True</string>
|
||||
<key>NSAppleScriptEnabled</key>
|
||||
<string>YES</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2006-2018 The qBittorrent project</string>
|
||||
<string>Copyright © 2006-2019 The qBittorrent project</string>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
||||
BIN
dist/mac/qBitTorrentDocument.icns
vendored
BIN
dist/mac/qBitTorrentDocument.icns
vendored
Binary file not shown.
6
dist/unix/CMakeLists.txt
vendored
6
dist/unix/CMakeLists.txt
vendored
@@ -31,12 +31,12 @@ if (Qt5Widgets_FOUND)
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor
|
||||
FILES_MATCHING PATTERN "*.png")
|
||||
|
||||
install(FILES qbittorrent.desktop
|
||||
install(FILES org.qbittorrent.qBittorrent.desktop
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications/
|
||||
COMPONENT data)
|
||||
|
||||
install(FILES qbittorrent.appdata.xml
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/appdata/
|
||||
install(FILES org.qbittorrent.qBittorrent.appdata.xml
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo/
|
||||
COMPONENT data)
|
||||
|
||||
install(FILES
|
||||
|
||||
75
dist/unix/org.qbittorrent.qBittorrent.appdata.xml
vendored
Normal file
75
dist/unix/org.qbittorrent.qBittorrent.appdata.xml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2014 sledgehammer999 <sledgehammer999@qbittorrent.org> -->
|
||||
<component type="desktop">
|
||||
<id>org.qbittorrent.qBittorrent</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-2.0 and OpenSSL</project_license>
|
||||
<name>qBittorrent</name>
|
||||
<summary>An open-source Bittorrent client</summary>
|
||||
<description>
|
||||
<p>
|
||||
The qBittorrent project aims to provide an open-source software alternative to µTorrent.
|
||||
Additionally, qBittorrent runs and provides the same features on all major platforms (FreeBSD, Linux, macOS, OS/2, Windows).
|
||||
qBittorrent is based on the Qt toolkit and libtorrent-rasterbar library.
|
||||
</p>
|
||||
<ul>
|
||||
<li>Polished µTorrent-like User Interface</li>
|
||||
<li>
|
||||
Well-integrated and extensible Search Engine
|
||||
<ul>
|
||||
<li>Simultaneous search in many Torrent search sites</li>
|
||||
<li>Category-specific search requests (e.g. Books, Music, Software)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>RSS feed support with advanced download filters (incl. regex)</li>
|
||||
<li>
|
||||
Many Bittorrent extensions supported:
|
||||
<ul>
|
||||
<li>Magnet links</li>
|
||||
<li>Distributed hash table (DHT), peer exchange protocol (PEX), local peer discovery (LSD)</li>
|
||||
<li>Private torrents</li>
|
||||
<li>Encrypted connections</li>
|
||||
<li>and many more...</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Remote control through Web user interface, written with AJAX</li>
|
||||
<li>Sequential downloading (Download in order)</li>
|
||||
<li>
|
||||
Advanced control over torrents, trackers and peers
|
||||
<ul>
|
||||
<li>Torrents queueing and prioritizing</li>
|
||||
<li>Torrent content selection and prioritizing</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Bandwidth scheduler</li>
|
||||
<li>Torrent creation tool</li>
|
||||
<li>IP Filtering (eMule & PeerGuardian format compatible)</li>
|
||||
<li>IPv6 compliant</li>
|
||||
<li>UPnP / NAT-PMP port forwarding support</li>
|
||||
<li>Available on all platforms: Windows, Linux, macOS, FreeBSD, OS/2</li>
|
||||
<li>Available in ~70 languages</li>
|
||||
</ul>
|
||||
</description>
|
||||
<launchable type="desktop-id">org.qbittorrent.qBittorrent.desktop</launchable>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_01.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_02.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_03.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_04.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<update_contact>sledgehammer999@qbittorrent.org</update_contact>
|
||||
<developer_name>The qBittorrent Project</developer_name>
|
||||
<url type="homepage">https://www.qbittorrent.org/</url>
|
||||
<url type="bugtracker">http://bugs.qbittorrent.org/</url>
|
||||
<url type="donation">https://www.qbittorrent.org/donate</url>
|
||||
<url type="help">http://forum.qbittorrent.org/</url>
|
||||
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
|
||||
</component>
|
||||
66
dist/unix/qbittorrent.appdata.xml
vendored
66
dist/unix/qbittorrent.appdata.xml
vendored
@@ -1,66 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2014 sledgehammer999 <sledgehammer999@qbittorrent.org> -->
|
||||
<component type="desktop">
|
||||
<id>qbittorrent.desktop</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-2.0 and OpenSSL</project_license>
|
||||
<name>qBittorrent</name>
|
||||
<summary>A Bittorrent Client</summary>
|
||||
<description>
|
||||
<p>
|
||||
Aiming to be a good alternative to all other bittorrent clients out
|
||||
there, qBittorrent is fast, stable and provides unicode support as well
|
||||
as many other features. Additionally, qBittorrent runs and provides those
|
||||
same features on all major platforms (Linux, Mac OS X, Windows, FreeBSD).
|
||||
</p>
|
||||
<p>
|
||||
It is programmed in C++ / Qt and uses libtorrent (sometimes called
|
||||
libtorrent-rasterbar) by Arvid Norberg. GeoLite data, created by MaxMind,
|
||||
are included in qBittorrent. Its features include:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Polished µTorrent-like User Interface</li>
|
||||
<li>Well-integrated and extensible Search Engine</li>
|
||||
<li>All Bittorrent extensions (DHT, Peer Exchange, Full encryption, Magnet/BitComet URIs, ...)</li>
|
||||
<li>Remote control through a Web user interface</li>
|
||||
<li>Advanced control over trackers, peers and torrents</li>
|
||||
<li>UPnP / NAT-PMP port forwarding support</li>
|
||||
<li>Available in ~25 languages (Unicode support)</li>
|
||||
<li>Torrent creation tool</li>
|
||||
<li>Advanced RSS support with download filters (inc. regex)</li>
|
||||
<li>Bandwidth scheduler</li>
|
||||
<li>IP Filtering (eMule and PeerGuardian compatible)</li>
|
||||
<li>IPv6 compliant</li>
|
||||
<li>Sequential downloading (aka "Download in order")</li>
|
||||
</ul>
|
||||
</description>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image height="675" width="1200">
|
||||
https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_01.png
|
||||
</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image height="675" width="1200">
|
||||
https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_02.png
|
||||
</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image height="675" width="1200">
|
||||
https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_03.png
|
||||
</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image height="675" width="1200">
|
||||
https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_04.png
|
||||
</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<url type="homepage">https://www.qbittorrent.org/</url>
|
||||
<update_contact>sledgehammer999@qbittorrent.org</update_contact>
|
||||
<developer_name>The qBittorrent Project</developer_name>
|
||||
<url type="bugtracker">http://bugs.qbittorrent.org/</url>
|
||||
<url type="donation">https://www.qbittorrent.org/donate</url>
|
||||
<url type="help">http://forum.qbittorrent.org/</url>
|
||||
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
|
||||
</component>
|
||||
4
dist/windows/options.nsi
vendored
4
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.2"
|
||||
!define PROG_VERSION "4.1.7"
|
||||
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
||||
@@ -50,7 +50,7 @@ XPStyle on
|
||||
;Installer Version Information
|
||||
VIAddVersionKey "ProductName" "qBittorrent"
|
||||
VIAddVersionKey "CompanyName" "The qBittorrent project"
|
||||
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2018 The qBittorrent project"
|
||||
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2019 The qBittorrent project"
|
||||
VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client"
|
||||
VIAddVersionKey "FileVersion" "${PROG_VERSION}"
|
||||
|
||||
|
||||
@@ -36,3 +36,16 @@ AC_DEFUN([FIND_QTDBUS],
|
||||
[AC_MSG_RESULT([not found])
|
||||
HAVE_QTDBUS=[false]])
|
||||
])
|
||||
|
||||
# DETECT_CPP11_PROGRAM()
|
||||
# Detects if at least C++11 mode is enabled.
|
||||
# --------------------------------------
|
||||
AC_DEFUN([DETECT_CPP11_PROGRAM],
|
||||
[AC_LANG_PROGRAM([[
|
||||
#ifndef __cplusplus
|
||||
#error "This is not a C++ compiler"
|
||||
#elif __cplusplus < 201103L
|
||||
#error "This is not a C++11 compiler"
|
||||
#endif]],
|
||||
[[]])
|
||||
])
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
#include <QMessageBox>
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QSessionManager>
|
||||
#include <QSharedMemory>
|
||||
@@ -61,6 +62,7 @@
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
#include "base/exceptions.h"
|
||||
#include "base/iconprovider.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
@@ -72,6 +74,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"
|
||||
@@ -121,6 +124,7 @@ Application::Application(const QString &id, int &argc, char **argv)
|
||||
qRegisterMetaType<Log::Msg>("Log::Msg");
|
||||
|
||||
setApplicationName("qBittorrent");
|
||||
setOrganizationDomain("qbittorrent.org");
|
||||
validateCommandLineParameters();
|
||||
|
||||
QString profileDir = m_commandLineArgs.portableMode
|
||||
@@ -142,6 +146,9 @@ Application::Application(const QString &id, int &argc, char **argv)
|
||||
#if !defined(DISABLE_GUI)
|
||||
setAttribute(Qt::AA_UseHighDpiPixmaps, true); // opt-in to the high DPI pixmap support
|
||||
setQuitOnLastWindowClosed(false);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
|
||||
setDesktopFileName("org.qbittorrent.qBittorrent");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
|
||||
@@ -325,7 +332,11 @@ void Application::runExternalProgram(const BitTorrent::TorrentHandle *torrent) c
|
||||
|
||||
::LocalFree(args);
|
||||
#else
|
||||
QProcess::startDetached(QLatin1String("/bin/sh"), {QLatin1String("-c"), program});
|
||||
// Cannot give users shell environment by default, as doing so could
|
||||
// enable command injection via torrent name and other arguments
|
||||
// (especially when some automated download mechanism has been setup).
|
||||
// See: https://github.com/qbittorrent/qBittorrent/issues/10925
|
||||
QProcess::startDetached(program);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -427,7 +438,7 @@ void Application::processParams(const QStringList ¶ms)
|
||||
BitTorrent::AddTorrentParams torrentParams;
|
||||
TriStateBool skipTorrentDialog;
|
||||
|
||||
foreach (QString param, params) {
|
||||
for (QString param : params) {
|
||||
param = param.trimmed();
|
||||
|
||||
// Process strings indicating options specified by the user.
|
||||
@@ -494,26 +505,42 @@ int Application::exec(const QStringList ¶ms)
|
||||
GuiIconProvider::initInstance();
|
||||
#endif
|
||||
|
||||
BitTorrent::Session::initInstance();
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection);
|
||||
try {
|
||||
BitTorrent::Session::initInstance();
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished);
|
||||
connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection);
|
||||
|
||||
#ifndef DISABLE_COUNTRIES_RESOLUTION
|
||||
Net::GeoIPManager::initInstance();
|
||||
Net::GeoIPManager::initInstance();
|
||||
#endif
|
||||
ScanFoldersModel::initInstance(this);
|
||||
ScanFoldersModel::initInstance(this);
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
m_webui = new WebUI;
|
||||
m_webui = new WebUI;
|
||||
#ifdef DISABLE_GUI
|
||||
if (m_webui->isErrored())
|
||||
return 1;
|
||||
connect(m_webui, &WebUI::fatalError, this, []() { QCoreApplication::exit(1); });
|
||||
if (m_webui->isErrored())
|
||||
return 1;
|
||||
connect(m_webui, &WebUI::fatalError, this, []() { QCoreApplication::exit(1); });
|
||||
#endif // DISABLE_GUI
|
||||
#endif // DISABLE_WEBUI
|
||||
|
||||
new RSS::Session; // create RSS::Session singleton
|
||||
new RSS::AutoDownloader; // create RSS::AutoDownloader singleton
|
||||
new RSS::Session; // create RSS::Session singleton
|
||||
new RSS::AutoDownloader; // create RSS::AutoDownloader singleton
|
||||
}
|
||||
catch (const RuntimeError &err) {
|
||||
#ifdef DISABLE_GUI
|
||||
fprintf(stderr, "%s", err.what());
|
||||
#else
|
||||
QMessageBox msgBox;
|
||||
msgBox.setIcon(QMessageBox::Critical);
|
||||
msgBox.setText(tr("Application failed to start."));
|
||||
msgBox.setInformativeText(err.message());
|
||||
msgBox.show(); // Need to be shown or to moveToCenter does not work
|
||||
msgBox.move(Utils::Misc::screenCenter(&msgBox));
|
||||
msgBox.exec();
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef DISABLE_GUI
|
||||
#ifndef DISABLE_WEBUI
|
||||
@@ -723,6 +750,7 @@ void Application::cleanup()
|
||||
delete m_fileLogger;
|
||||
Logger::freeInstance();
|
||||
IconProvider::freeInstance();
|
||||
SearchPluginManager::freeInstance();
|
||||
Utils::Fs::removeDirRecursive(Utils::Fs::tempPath());
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include <QMessageBox>
|
||||
#endif
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
|
||||
@@ -486,7 +487,7 @@ CommandLineParameterError::CommandLineParameterError(const QString &messageForUs
|
||||
{
|
||||
}
|
||||
|
||||
const QString& CommandLineParameterError::messageForUser() const
|
||||
const QString &CommandLineParameterError::messageForUser() const
|
||||
{
|
||||
return m_messageForUser;
|
||||
}
|
||||
@@ -497,7 +498,7 @@ QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN
|
||||
QStringList lines = {words.first()};
|
||||
int currentLineMaxLength = wrapAtColumn - initialIndentation;
|
||||
|
||||
foreach (const QString &word, words.mid(1)) {
|
||||
for (const QString &word : asConst(words.mid(1))) {
|
||||
if (lines.last().length() + word.length() + 1 < currentLineMaxLength) {
|
||||
lines.last().append(' ' + word);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/utils/fs.h"
|
||||
|
||||
@@ -50,7 +51,7 @@ FileLogger::FileLogger(const QString &path, const bool backup, const int maxSize
|
||||
this->deleteOld(age, ageType);
|
||||
|
||||
const Logger *const logger = Logger::instance();
|
||||
foreach (const Log::Msg &msg, logger->getMessages())
|
||||
for (const Log::Msg &msg : asConst(logger->getMessages()))
|
||||
addLogMessage(msg);
|
||||
|
||||
connect(logger, &Logger::newLogMessage, this, &FileLogger::addLogMessage);
|
||||
@@ -87,7 +88,7 @@ void FileLogger::deleteOld(const int age, const FileLogAgeType ageType)
|
||||
QDateTime date = QDateTime::currentDateTime();
|
||||
QDir dir(Utils::Fs::branchPath(m_path));
|
||||
|
||||
foreach (const QFileInfo file, dir.entryInfoList(QStringList("qbittorrent.log.bak*"), QDir::Files | QDir::Writable, QDir::Time | QDir::Reversed)) {
|
||||
for (const QFileInfo &file : asConst(dir.entryInfoList(QStringList("qbittorrent.log.bak*"), QDir::Files | QDir::Writable, QDir::Time | QDir::Reversed))) {
|
||||
QDateTime modificationDate = file.lastModified();
|
||||
switch (ageType) {
|
||||
case DAYS:
|
||||
|
||||
@@ -102,10 +102,6 @@ void displayBadArgMessage(const QString &message);
|
||||
|
||||
#if !defined(DISABLE_GUI)
|
||||
void showSplashScreen();
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
void setupDpi();
|
||||
#endif // Q_OS_UNIX
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
// Main
|
||||
@@ -120,10 +116,6 @@ int main(int argc, char *argv[])
|
||||
macMigratePlists();
|
||||
#endif
|
||||
|
||||
#if !defined(DISABLE_GUI) && defined(Q_OS_UNIX)
|
||||
setupDpi();
|
||||
#endif
|
||||
|
||||
try {
|
||||
// Create Application
|
||||
QString appId = QLatin1String("qBittorrent-") + Utils::Misc::getUserIDString();
|
||||
@@ -210,7 +202,7 @@ int main(int argc, char *argv[])
|
||||
// this is the default in Qt6
|
||||
app->setAttribute(Qt::AA_DisableWindowContextHelpButton);
|
||||
#endif
|
||||
#endif
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
// Since Apple made difficult for users to set PATH, we set here for convenience.
|
||||
@@ -330,14 +322,6 @@ void showSplashScreen()
|
||||
QTimer::singleShot(1500, splash, &QObject::deleteLater);
|
||||
qApp->processEvents();
|
||||
}
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
void setupDpi()
|
||||
{
|
||||
if (qEnvironmentVariableIsEmpty("QT_AUTO_SCREEN_SCALE_FACTOR"))
|
||||
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1");
|
||||
}
|
||||
#endif // Q_OS_UNIX
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
void displayVersion()
|
||||
|
||||
@@ -101,6 +101,7 @@ QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
|
||||
#endif
|
||||
|
||||
server = new QLocalServer(this);
|
||||
server->setSocketOptions(QLocalServer::UserAccessOption);
|
||||
QString lockName = QDir(QDir::tempPath()).absolutePath()
|
||||
+ QLatin1Char('/') + socketName
|
||||
+ QLatin1String("-lockfile");
|
||||
@@ -191,6 +192,12 @@ void QtLocalPeer::receiveConnection()
|
||||
QByteArray uMsg;
|
||||
quint32 remaining;
|
||||
ds >> remaining;
|
||||
if (remaining > 65535) {
|
||||
// drop suspiciously large data
|
||||
delete socket;
|
||||
return;
|
||||
}
|
||||
|
||||
uMsg.resize(remaining);
|
||||
int got = 0;
|
||||
char* uMsgBuf = uMsg.data();
|
||||
|
||||
@@ -83,7 +83,7 @@ bool userAcceptsUpgrade()
|
||||
msgBox.move(Utils::Misc::screenCenter(&msgBox));
|
||||
if (msgBox.exec() == QMessageBox::Ok)
|
||||
return true;
|
||||
#endif
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -179,9 +179,9 @@ bool upgrade(bool ask = true)
|
||||
|
||||
// ****************************************************************************************
|
||||
// Silently converts old v3.3.x .fastresume files
|
||||
QStringList backupFiles_3_3 = backupFolderDir.entryList(
|
||||
const QStringList backupFiles_3_3 = backupFolderDir.entryList(
|
||||
QStringList(QLatin1String("*.fastresume.*")), QDir::Files, QDir::Unsorted);
|
||||
foreach (const QString &backupFile, backupFiles_3_3)
|
||||
for (const QString &backupFile : backupFiles_3_3)
|
||||
upgradeResumeFile(backupFolderDir.absoluteFilePath(backupFile));
|
||||
// ****************************************************************************************
|
||||
|
||||
@@ -197,10 +197,10 @@ bool upgrade(bool ask = true)
|
||||
|
||||
if (ask && !userAcceptsUpgrade()) return false;
|
||||
|
||||
QStringList backupFiles = backupFolderDir.entryList(
|
||||
const QStringList backupFiles = backupFolderDir.entryList(
|
||||
QStringList(QLatin1String("*.fastresume")), QDir::Files, QDir::Unsorted);
|
||||
const QRegularExpression rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
|
||||
foreach (QString backupFile, backupFiles) {
|
||||
for (const QString &backupFile : backupFiles) {
|
||||
const QRegularExpressionMatch rxMatch = rx.match(backupFile);
|
||||
if (rxMatch.hasMatch()) {
|
||||
const QString hashStr = rxMatch.captured(1);
|
||||
@@ -265,7 +265,7 @@ void migratePlistToIni(const QString &application)
|
||||
plistFile->setFallbacksEnabled(false);
|
||||
const QStringList plist = plistFile->allKeys();
|
||||
if (!plist.isEmpty()) {
|
||||
foreach (const QString &key, plist)
|
||||
for (const QString &key : plist)
|
||||
iniFile.setValue(key, plistFile->value(key));
|
||||
plistFile->clear();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ add_library(qbt_base STATIC
|
||||
# headers
|
||||
bittorrent/addtorrentparams.h
|
||||
bittorrent/cachestatus.h
|
||||
bittorrent/filepriority.h
|
||||
bittorrent/infohash.h
|
||||
bittorrent/magneturi.h
|
||||
bittorrent/peerinfo.h
|
||||
@@ -76,6 +77,7 @@ types.h
|
||||
unicodestrings.h
|
||||
|
||||
# sources
|
||||
bittorrent/filepriority.cpp
|
||||
bittorrent/infohash.cpp
|
||||
bittorrent/magneturi.cpp
|
||||
bittorrent/peerinfo.cpp
|
||||
|
||||
@@ -38,12 +38,12 @@ AsyncFileStorage::AsyncFileStorage(const QString &storageFolderPath, QObject *pa
|
||||
, m_lockFile(m_storageDir.absoluteFilePath(QStringLiteral("storage.lock")))
|
||||
{
|
||||
if (!m_storageDir.mkpath(m_storageDir.absolutePath()))
|
||||
throw AsyncFileStorageError(
|
||||
QString("Could not create directory '%1'.").arg(m_storageDir.absolutePath()));
|
||||
throw AsyncFileStorageError {tr("Could not create directory '%1'.")
|
||||
.arg(m_storageDir.absolutePath())};
|
||||
|
||||
// TODO: This folder locking approach does not work for UNIX systems. Implement it.
|
||||
if (!m_lockFile.open(QFile::WriteOnly))
|
||||
throw AsyncFileStorageError(m_lockFile.errorString());
|
||||
throw AsyncFileStorageError {m_lockFile.errorString()};
|
||||
}
|
||||
|
||||
AsyncFileStorage::~AsyncFileStorage()
|
||||
@@ -76,13 +76,3 @@ void AsyncFileStorage::store_impl(const QString &fileName, const QByteArray &dat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AsyncFileStorageError::AsyncFileStorageError(const QString &message)
|
||||
: std::runtime_error(message.toUtf8().data())
|
||||
{
|
||||
}
|
||||
|
||||
QString AsyncFileStorageError::message() const
|
||||
{
|
||||
return what();
|
||||
}
|
||||
|
||||
@@ -28,22 +28,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QObject>
|
||||
|
||||
class AsyncFileStorageError : public std::runtime_error
|
||||
#include "base/exceptions.h"
|
||||
|
||||
class AsyncFileStorageError : public RuntimeError
|
||||
{
|
||||
public:
|
||||
explicit AsyncFileStorageError(const QString &message);
|
||||
QString message() const;
|
||||
using RuntimeError::RuntimeError;
|
||||
};
|
||||
|
||||
class AsyncFileStorage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(AsyncFileStorage)
|
||||
|
||||
public:
|
||||
explicit AsyncFileStorage(const QString &storageFolderPath, QObject *parent = nullptr);
|
||||
|
||||
@@ -3,6 +3,7 @@ HEADERS += \
|
||||
$$PWD/asyncfilestorage.h \
|
||||
$$PWD/bittorrent/addtorrentparams.h \
|
||||
$$PWD/bittorrent/cachestatus.h \
|
||||
$$PWD/bittorrent/filepriority.h \
|
||||
$$PWD/bittorrent/infohash.h \
|
||||
$$PWD/bittorrent/magneturi.h \
|
||||
$$PWD/bittorrent/peerinfo.h \
|
||||
@@ -75,6 +76,7 @@ HEADERS += \
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/asyncfilestorage.cpp \
|
||||
$$PWD/bittorrent/filepriority.cpp \
|
||||
$$PWD/bittorrent/infohash.cpp \
|
||||
$$PWD/bittorrent/magneturi.cpp \
|
||||
$$PWD/bittorrent/peerinfo.cpp \
|
||||
|
||||
46
src/base/bittorrent/filepriority.cpp
Normal file
46
src/base/bittorrent/filepriority.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "filepriority.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
bool isValidFilePriority(const BitTorrent::FilePriority priority)
|
||||
{
|
||||
switch (priority) {
|
||||
case BitTorrent::FilePriority::Ignored:
|
||||
case BitTorrent::FilePriority::Normal:
|
||||
case BitTorrent::FilePriority::High:
|
||||
case BitTorrent::FilePriority::Maximum:
|
||||
case BitTorrent::FilePriority::Mixed:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/base/bittorrent/filepriority.h
Normal file
43
src/base/bittorrent/filepriority.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
enum class FilePriority : int
|
||||
{
|
||||
Ignored = 0,
|
||||
Normal = 1,
|
||||
High = 6,
|
||||
Maximum = 7,
|
||||
Mixed = -1
|
||||
};
|
||||
|
||||
bool isValidFilePriority(BitTorrent::FilePriority priority);
|
||||
}
|
||||
@@ -83,10 +83,10 @@ MagnetUri::MagnetUri(const QString &source)
|
||||
m_hash = m_addTorrentParams.info_hash;
|
||||
m_name = QString::fromStdString(m_addTorrentParams.name);
|
||||
|
||||
foreach (const std::string &tracker, m_addTorrentParams.trackers)
|
||||
for (const std::string &tracker : m_addTorrentParams.trackers)
|
||||
m_trackers.append(QString::fromStdString(tracker));
|
||||
|
||||
foreach (const std::string &urlSeed, m_addTorrentParams.url_seeds)
|
||||
for (const std::string &urlSeed : m_addTorrentParams.url_seeds)
|
||||
m_urlSeeds.append(QUrl(urlSeed.c_str()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -39,18 +39,23 @@ ResumeDataSavingManager::ResumeDataSavingManager(const QString &resumeFolderPath
|
||||
{
|
||||
}
|
||||
|
||||
void ResumeDataSavingManager::saveResumeData(QString infoHash, QByteArray data) const
|
||||
void ResumeDataSavingManager::save(const QString &filename, const QByteArray &data) const
|
||||
{
|
||||
QString filename = QString("%1.fastresume").arg(infoHash);
|
||||
QString filepath = m_resumeDataDir.absoluteFilePath(filename);
|
||||
const QString filepath = m_resumeDataDir.absoluteFilePath(filename);
|
||||
|
||||
qDebug() << "Saving resume data in" << filepath;
|
||||
QSaveFile resumeFile(filepath);
|
||||
if (resumeFile.open(QIODevice::WriteOnly)) {
|
||||
resumeFile.write(data);
|
||||
if (!resumeFile.commit()) {
|
||||
Logger::instance()->addMessage(QString("Couldn't save resume data in %1. Error: %2")
|
||||
.arg(filepath, resumeFile.errorString()), Log::WARNING);
|
||||
QSaveFile file {filepath};
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(data);
|
||||
if (!file.commit()) {
|
||||
Logger::instance()->addMessage(QString("Couldn't save data in '%1'. Error: %2")
|
||||
.arg(filepath, file.errorString()), Log::WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResumeDataSavingManager::remove(const QString &filename) const
|
||||
{
|
||||
const QString filepath = m_resumeDataDir.absoluteFilePath(filename);
|
||||
|
||||
Utils::Fs::forceRemove(filepath);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -26,8 +26,7 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#ifndef RESUMEDATASAVINGMANAGER_H
|
||||
#define RESUMEDATASAVINGMANAGER_H
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
@@ -36,15 +35,15 @@
|
||||
class ResumeDataSavingManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(ResumeDataSavingManager)
|
||||
|
||||
public:
|
||||
explicit ResumeDataSavingManager(const QString &resumeFolderPath);
|
||||
|
||||
public slots:
|
||||
void saveResumeData(QString infoHash, QByteArray data) const;
|
||||
void save(const QString &filename, const QByteArray &data) const;
|
||||
void remove(const QString &filename) const;
|
||||
|
||||
private:
|
||||
QDir m_resumeDataDir;
|
||||
};
|
||||
|
||||
#endif // RESUMEDATASAVINGMANAGER_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -237,7 +237,7 @@ namespace BitTorrent
|
||||
int diskJobTime = 0;
|
||||
} disk;
|
||||
};
|
||||
#endif
|
||||
#endif // LIBTORRENT_VERSION_NUM >= 10100
|
||||
|
||||
class Session : public QObject
|
||||
{
|
||||
@@ -377,6 +377,8 @@ namespace BitTorrent
|
||||
void setAnnounceToAllTiers(bool val);
|
||||
int asyncIOThreads() const;
|
||||
void setAsyncIOThreads(int num);
|
||||
int checkingMemUsage() const;
|
||||
void setCheckingMemUsage(int size);
|
||||
int diskCacheSize() const;
|
||||
void setDiskCacheSize(int size);
|
||||
int diskCacheTTL() const;
|
||||
@@ -480,6 +482,7 @@ namespace BitTorrent
|
||||
void bottomTorrentsPriority(const QStringList &hashes);
|
||||
|
||||
// TorrentHandle interface
|
||||
void handleTorrentSaveResumeDataRequested(TorrentHandle *const torrent);
|
||||
void handleTorrentShareLimitChanged(TorrentHandle *const torrent);
|
||||
void handleTorrentNameChanged(TorrentHandle *const torrent);
|
||||
void handleTorrentSavePathChanged(TorrentHandle *const torrent);
|
||||
@@ -606,13 +609,13 @@ namespace BitTorrent
|
||||
|
||||
void updateSeedingLimitTimer();
|
||||
void exportTorrentFile(TorrentHandle *const torrent, TorrentExportFolder folder = TorrentExportFolder::Regular);
|
||||
void saveTorrentResumeData(TorrentHandle *const torrent, bool finalSave = false);
|
||||
|
||||
void handleAlert(libtorrent::alert *a);
|
||||
void dispatchTorrentAlert(libtorrent::alert *a);
|
||||
void handleAddTorrentAlert(libtorrent::add_torrent_alert *p);
|
||||
void handleStateUpdateAlert(libtorrent::state_update_alert *p);
|
||||
void handleMetadataReceivedAlert(libtorrent::metadata_received_alert *p);
|
||||
void handleMetadataReceivedAlert(const libtorrent::metadata_received_alert *p);
|
||||
void handleTorrentPausedAlert(const libtorrent::torrent_paused_alert *p);
|
||||
void handleFileErrorAlert(libtorrent::file_error_alert *p);
|
||||
void handleTorrentRemovedAlert(libtorrent::torrent_removed_alert *p);
|
||||
void handleTorrentDeletedAlert(libtorrent::torrent_deleted_alert *p);
|
||||
@@ -632,6 +635,8 @@ namespace BitTorrent
|
||||
void createTorrentHandle(const libtorrent::torrent_handle &nativeHandle);
|
||||
|
||||
void saveResumeData();
|
||||
void saveTorrentsQueue();
|
||||
void removeTorrentsQueue();
|
||||
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
void dispatchAlerts(libtorrent::alert *alertPtr);
|
||||
@@ -656,6 +661,7 @@ namespace BitTorrent
|
||||
CachedSettingValue<bool> m_announceToAllTrackers;
|
||||
CachedSettingValue<bool> m_announceToAllTiers;
|
||||
CachedSettingValue<int> m_asyncIOThreads;
|
||||
CachedSettingValue<int> m_checkingMemUsage;
|
||||
CachedSettingValue<int> m_diskCacheSize;
|
||||
CachedSettingValue<int> m_diskCacheTTL;
|
||||
CachedSettingValue<bool> m_useOSCache;
|
||||
|
||||
@@ -110,7 +110,7 @@ void TorrentCreatorThread::run()
|
||||
QStringList fileNames;
|
||||
QHash<QString, boost::int64_t> fileSizeMap;
|
||||
|
||||
for (const auto &dir : qAsConst(dirs)) {
|
||||
for (const auto &dir : asConst(dirs)) {
|
||||
QStringList tmpNames; // natural sort files within each dir
|
||||
|
||||
QDirIterator fileIter(dir, QDir::Files);
|
||||
@@ -126,7 +126,7 @@ void TorrentCreatorThread::run()
|
||||
fileNames += tmpNames;
|
||||
}
|
||||
|
||||
for (const auto &fileName : qAsConst(fileNames))
|
||||
for (const auto &fileName : asConst(fileNames))
|
||||
fs.add_file(fileName.toStdString(), fileSizeMap[fileName]);
|
||||
}
|
||||
|
||||
@@ -141,14 +141,14 @@ void TorrentCreatorThread::run()
|
||||
#endif
|
||||
|
||||
// Add url seeds
|
||||
foreach (QString seed, m_params.urlSeeds) {
|
||||
for (QString seed : asConst(m_params.urlSeeds)) {
|
||||
seed = seed.trimmed();
|
||||
if (!seed.isEmpty())
|
||||
newTorrent.add_url_seed(seed.toStdString());
|
||||
}
|
||||
|
||||
int tier = 0;
|
||||
foreach (const QString &tracker, m_params.trackers) {
|
||||
for (const QString &tracker : asConst(m_params.trackers)) {
|
||||
if (tracker.isEmpty())
|
||||
++tier;
|
||||
else
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/profile.h"
|
||||
@@ -77,7 +78,7 @@ namespace
|
||||
ListType setToEntryList(const QSet<QString> &input)
|
||||
{
|
||||
ListType entryList;
|
||||
foreach (const QString &setValue, input)
|
||||
for (const QString &setValue : input)
|
||||
entryList.emplace_back(setValue.toStdString());
|
||||
return entryList;
|
||||
}
|
||||
@@ -158,14 +159,14 @@ namespace
|
||||
{
|
||||
// new constructor is available
|
||||
template<typename T, typename std::enable_if<std::is_constructible<T, libt::torrent_info, bool>::value, int>::type = 0>
|
||||
T makeTorrentCreator(const libtorrent::torrent_info & ti)
|
||||
T makeTorrentCreator(const libtorrent::torrent_info &ti)
|
||||
{
|
||||
return T(ti, true);
|
||||
}
|
||||
|
||||
// new constructor isn't available
|
||||
template<typename T, typename std::enable_if<!std::is_constructible<T, libt::torrent_info, bool>::value, int>::type = 0>
|
||||
T makeTorrentCreator(const libtorrent::torrent_info & ti)
|
||||
T makeTorrentCreator(const libtorrent::torrent_info &ti)
|
||||
{
|
||||
return T(ti);
|
||||
}
|
||||
@@ -187,10 +188,12 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
|
||||
, m_ratioLimit(params.ratioLimit)
|
||||
, m_seedingTimeLimit(params.seedingTimeLimit)
|
||||
, m_tempPathDisabled(params.disableTempPath)
|
||||
, m_fastresumeDataRejected(false)
|
||||
, m_hasMissingFiles(false)
|
||||
, m_hasRootFolder(params.hasRootFolder)
|
||||
, m_needsToSetFirstLastPiecePriority(false)
|
||||
, m_pauseAfterRecheck(false)
|
||||
, m_needsToStartForced(params.forced)
|
||||
, m_pauseWhenReady(params.paused)
|
||||
{
|
||||
if (m_useAutoTMM)
|
||||
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
|
||||
@@ -216,17 +219,17 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
|
||||
m_hasRootFolder = false;
|
||||
}
|
||||
|
||||
// "started" means "all initialization has completed and torrent has started regular processing".
|
||||
// When torrent added/restored in "paused" state it become "started" immediately after construction.
|
||||
// When it is added/restored in "resumed" state, it become "started" after it is really resumed
|
||||
// (i.e. after receiving "torrent resumed" alert).
|
||||
m_started = (params.restored && hasMetadata() ? isPaused() : params.paused);
|
||||
|
||||
if (!m_started) {
|
||||
if (!params.restored || !hasMetadata()) {
|
||||
if (!hasMetadata()) {
|
||||
// There is nothing to prepare
|
||||
if (!m_pauseWhenReady) {
|
||||
// Resume torrent because it was added in "resumed" state
|
||||
// but it's actually paused during initialization
|
||||
resume(params.forced);
|
||||
// but it's actually paused during initialization.
|
||||
m_startupState = Starting;
|
||||
resume_impl(m_needsToStartForced);
|
||||
}
|
||||
else {
|
||||
m_startupState = Started;
|
||||
m_pauseWhenReady = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,10 +377,9 @@ QString TorrentHandle::nativeActualSavePath() const
|
||||
QList<TrackerEntry> TorrentHandle::trackers() const
|
||||
{
|
||||
QList<TrackerEntry> entries;
|
||||
std::vector<libt::announce_entry> announces;
|
||||
const std::vector<libt::announce_entry> announces = m_nativeHandle.trackers();
|
||||
|
||||
announces = m_nativeHandle.trackers();
|
||||
foreach (const libt::announce_entry &tracker, announces)
|
||||
for (const libt::announce_entry &tracker : announces)
|
||||
entries << tracker;
|
||||
|
||||
return entries;
|
||||
@@ -391,7 +393,7 @@ QHash<QString, TrackerInfo> TorrentHandle::trackerInfos() const
|
||||
void TorrentHandle::addTrackers(const QList<TrackerEntry> &trackers)
|
||||
{
|
||||
QList<TrackerEntry> addedTrackers;
|
||||
foreach (const TrackerEntry &tracker, trackers) {
|
||||
for (const TrackerEntry &tracker : trackers) {
|
||||
if (addTracker(tracker))
|
||||
addedTrackers << tracker;
|
||||
}
|
||||
@@ -400,13 +402,13 @@ void TorrentHandle::addTrackers(const QList<TrackerEntry> &trackers)
|
||||
m_session->handleTorrentTrackersAdded(this, addedTrackers);
|
||||
}
|
||||
|
||||
void TorrentHandle::replaceTrackers(QList<TrackerEntry> trackers)
|
||||
void TorrentHandle::replaceTrackers(const QList<TrackerEntry> &trackers)
|
||||
{
|
||||
QList<TrackerEntry> existingTrackers = this->trackers();
|
||||
QList<TrackerEntry> addedTrackers;
|
||||
|
||||
std::vector<libt::announce_entry> announces;
|
||||
foreach (const TrackerEntry &tracker, trackers) {
|
||||
for (const TrackerEntry &tracker : trackers) {
|
||||
announces.push_back(tracker.nativeEntry());
|
||||
if (!existingTrackers.contains(tracker))
|
||||
addedTrackers << tracker;
|
||||
@@ -438,9 +440,9 @@ bool TorrentHandle::addTracker(const TrackerEntry &tracker)
|
||||
QList<QUrl> TorrentHandle::urlSeeds() const
|
||||
{
|
||||
QList<QUrl> urlSeeds;
|
||||
std::set<std::string> seeds = m_nativeHandle.url_seeds();
|
||||
const std::set<std::string> seeds = m_nativeHandle.url_seeds();
|
||||
|
||||
foreach (const std::string &urlSeed, seeds)
|
||||
for (const std::string &urlSeed : seeds)
|
||||
urlSeeds.append(QUrl(urlSeed.c_str()));
|
||||
|
||||
return urlSeeds;
|
||||
@@ -449,7 +451,7 @@ QList<QUrl> TorrentHandle::urlSeeds() const
|
||||
void TorrentHandle::addUrlSeeds(const QList<QUrl> &urlSeeds)
|
||||
{
|
||||
QList<QUrl> addedUrlSeeds;
|
||||
foreach (const QUrl &urlSeed, urlSeeds) {
|
||||
for (const QUrl &urlSeed : urlSeeds) {
|
||||
if (addUrlSeed(urlSeed))
|
||||
addedUrlSeeds << urlSeed;
|
||||
}
|
||||
@@ -461,7 +463,7 @@ void TorrentHandle::addUrlSeeds(const QList<QUrl> &urlSeeds)
|
||||
void TorrentHandle::removeUrlSeeds(const QList<QUrl> &urlSeeds)
|
||||
{
|
||||
QList<QUrl> removedUrlSeeds;
|
||||
foreach (const QUrl &urlSeed, urlSeeds) {
|
||||
for (const QUrl &urlSeed : urlSeeds) {
|
||||
if (removeUrlSeed(urlSeed))
|
||||
removedUrlSeeds << urlSeed;
|
||||
}
|
||||
@@ -504,12 +506,10 @@ bool TorrentHandle::needSaveResumeData() const
|
||||
return m_nativeHandle.need_save_resume_data();
|
||||
}
|
||||
|
||||
void TorrentHandle::saveResumeData(bool updateStatus)
|
||||
void TorrentHandle::saveResumeData()
|
||||
{
|
||||
if (updateStatus) // to update queue_position, see discussion in PR #6154
|
||||
this->updateStatus();
|
||||
|
||||
m_nativeHandle.save_resume_data();
|
||||
m_session->handleTorrentSaveResumeDataRequested(this);
|
||||
}
|
||||
|
||||
int TorrentHandle::filesCount() const
|
||||
@@ -599,8 +599,7 @@ bool TorrentHandle::removeTag(const QString &tag)
|
||||
|
||||
void TorrentHandle::removeAllTags()
|
||||
{
|
||||
// QT automatically copies the container in foreach, so it's safe to mutate it.
|
||||
foreach (const QString &tag, m_tags)
|
||||
for (const QString &tag : asConst(tags()))
|
||||
removeTag(tag);
|
||||
}
|
||||
|
||||
@@ -626,7 +625,7 @@ QString TorrentHandle::filePath(int index) const
|
||||
|
||||
QString TorrentHandle::fileName(int index) const
|
||||
{
|
||||
if (!hasMetadata()) return QString();
|
||||
if (!hasMetadata()) return QString();
|
||||
return Utils::Fs::fileName(filePath(index));
|
||||
}
|
||||
|
||||
@@ -639,7 +638,7 @@ qlonglong TorrentHandle::fileSize(int index) const
|
||||
// to all files in a torrent
|
||||
QStringList TorrentHandle::absoluteFilePaths() const
|
||||
{
|
||||
if (!hasMetadata()) return QStringList();
|
||||
if (!hasMetadata()) return QStringList();
|
||||
|
||||
QDir saveDir(savePath(true));
|
||||
QStringList res;
|
||||
@@ -650,7 +649,7 @@ QStringList TorrentHandle::absoluteFilePaths() const
|
||||
|
||||
QStringList TorrentHandle::absoluteFilePathsUnwanted() const
|
||||
{
|
||||
if (!hasMetadata()) return QStringList();
|
||||
if (!hasMetadata()) return QStringList();
|
||||
|
||||
QDir saveDir(savePath(true));
|
||||
QStringList res;
|
||||
@@ -810,7 +809,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()) {
|
||||
@@ -842,9 +844,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;
|
||||
@@ -886,9 +885,9 @@ bool TorrentHandle::hasError() const
|
||||
|
||||
bool TorrentHandle::hasFilteredPieces() const
|
||||
{
|
||||
std::vector<int> pp = m_nativeHandle.piece_priorities();
|
||||
const std::vector<int> pp = m_nativeHandle.piece_priorities();
|
||||
|
||||
foreach (const int priority, pp)
|
||||
for (const int priority : pp)
|
||||
if (priority == 0) return true;
|
||||
|
||||
return false;
|
||||
@@ -976,12 +975,13 @@ qulonglong TorrentHandle::eta() const
|
||||
QVector<qreal> TorrentHandle::filesProgress() const
|
||||
{
|
||||
std::vector<boost::int64_t> fp;
|
||||
QVector<qreal> result;
|
||||
m_nativeHandle.file_progress(fp, libt::torrent_handle::piece_granularity);
|
||||
|
||||
int count = static_cast<int>(fp.size());
|
||||
const int count = static_cast<int>(fp.size());
|
||||
QVector<qreal> result;
|
||||
result.reserve(count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
qlonglong size = fileSize(i);
|
||||
const qlonglong size = fileSize(i);
|
||||
if ((size <= 0) || (fp[i] == size))
|
||||
result << 1;
|
||||
else
|
||||
@@ -1089,7 +1089,7 @@ QList<PeerInfo> TorrentHandle::peers() const
|
||||
|
||||
m_nativeHandle.get_peer_info(nativePeers);
|
||||
|
||||
foreach (const libt::peer_info &peer, nativePeers)
|
||||
for (const libt::peer_info &peer : nativePeers)
|
||||
peers << PeerInfo(this, peer);
|
||||
|
||||
return peers;
|
||||
@@ -1213,12 +1213,8 @@ void TorrentHandle::setName(const QString &name)
|
||||
bool TorrentHandle::setCategory(const QString &category)
|
||||
{
|
||||
if (m_category != category) {
|
||||
if (!category.isEmpty()) {
|
||||
if (!Session::isValidCategoryName(category)) return false;
|
||||
if (!m_session->categories().contains(category))
|
||||
if (!m_session->addCategory(category))
|
||||
return false;
|
||||
}
|
||||
if (!category.isEmpty() && !m_session->categories().contains(category))
|
||||
return false;
|
||||
|
||||
QString oldCategory = m_category;
|
||||
m_category = category;
|
||||
@@ -1237,6 +1233,8 @@ bool TorrentHandle::setCategory(const QString &category)
|
||||
|
||||
void TorrentHandle::move(QString path)
|
||||
{
|
||||
if (m_startupState != Started) return;
|
||||
|
||||
m_useAutoTMM = false;
|
||||
m_session->handleTorrentSavingModeChanged(this);
|
||||
|
||||
@@ -1275,14 +1273,17 @@ void TorrentHandle::forceDHTAnnounce()
|
||||
|
||||
void TorrentHandle::forceRecheck()
|
||||
{
|
||||
if (m_startupState != Started) return;
|
||||
if (!hasMetadata()) return;
|
||||
|
||||
if (isPaused()) {
|
||||
m_pauseAfterRecheck = true;
|
||||
resume_impl(true, true);
|
||||
}
|
||||
|
||||
m_nativeHandle.force_recheck();
|
||||
m_unchecked = false;
|
||||
|
||||
if (isPaused()) {
|
||||
m_nativeHandle.stop_when_ready(true);
|
||||
m_nativeHandle.auto_managed(true);
|
||||
m_pauseWhenReady = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentHandle::setSequentialDownload(bool b)
|
||||
@@ -1291,6 +1292,8 @@ void TorrentHandle::setSequentialDownload(bool b)
|
||||
m_nativeHandle.set_sequential_download(b);
|
||||
m_nativeStatus.sequential_download = b; // prevent return cached value
|
||||
}
|
||||
|
||||
saveResumeData();
|
||||
}
|
||||
|
||||
void TorrentHandle::toggleSequentialDownload()
|
||||
@@ -1337,6 +1340,8 @@ void TorrentHandle::setFirstLastPiecePriorityImpl(const bool enabled, const QVec
|
||||
|
||||
LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
|
||||
.arg((enabled ? tr("On") : tr("Off")), name()));
|
||||
|
||||
saveResumeData();
|
||||
}
|
||||
|
||||
void TorrentHandle::toggleFirstLastPiecePriority()
|
||||
@@ -1346,24 +1351,44 @@ void TorrentHandle::toggleFirstLastPiecePriority()
|
||||
|
||||
void TorrentHandle::pause()
|
||||
{
|
||||
if (m_startupState != Started) return;
|
||||
if (m_pauseWhenReady) return;
|
||||
if (isChecking()) {
|
||||
m_pauseWhenReady = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPaused()) return;
|
||||
|
||||
m_nativeHandle.auto_managed(false);
|
||||
m_nativeHandle.pause();
|
||||
|
||||
// Libtorrent doesn't emit a torrent_paused_alert when the
|
||||
// torrent is queued (no I/O)
|
||||
// We test on the cached m_nativeStatus
|
||||
if (isQueued())
|
||||
m_session->handleTorrentPaused(this);
|
||||
}
|
||||
|
||||
void TorrentHandle::resume(bool forced)
|
||||
{
|
||||
resume_impl(forced, false);
|
||||
if (m_startupState != Started) return;
|
||||
|
||||
m_pauseWhenReady = false;
|
||||
resume_impl(forced);
|
||||
}
|
||||
|
||||
void TorrentHandle::resume_impl(bool forced, bool uploadMode)
|
||||
void TorrentHandle::resume_impl(bool forced)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1398,6 +1423,7 @@ void TorrentHandle::setTrackerLogin(const QString &username, const QString &pass
|
||||
|
||||
void TorrentHandle::renameFile(int index, const QString &name)
|
||||
{
|
||||
m_oldPath[LTFileIndex {index}].push_back(filePath(index));
|
||||
++m_renameCount;
|
||||
qDebug() << Q_FUNC_INFO << index << name;
|
||||
m_nativeHandle.rename_file(index, Utils::Fs::toNativePath(name).toStdString());
|
||||
@@ -1419,16 +1445,6 @@ bool TorrentHandle::saveTorrentFile(const QString &path)
|
||||
return false;
|
||||
}
|
||||
|
||||
void TorrentHandle::setFilePriority(int index, int priority)
|
||||
{
|
||||
std::vector<int> priorities = m_nativeHandle.file_priorities();
|
||||
|
||||
if ((priorities.size() > static_cast<quint64>(index)) && (priorities[index] != priority)) {
|
||||
priorities[index] = priority;
|
||||
prioritizeFiles(QVector<int>::fromStdVector(priorities));
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentHandle::handleStateUpdate(const libt::torrent_status &nativeStatus)
|
||||
{
|
||||
updateStatus(nativeStatus);
|
||||
@@ -1553,21 +1569,39 @@ 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 == Preparing) {
|
||||
if (!m_pauseWhenReady) {
|
||||
if (!m_hasMissingFiles) {
|
||||
// Resume torrent because it was added in "resumed" state
|
||||
// but it's actually paused during initialization.
|
||||
m_startupState = Starting;
|
||||
resume_impl(m_needsToStartForced);
|
||||
}
|
||||
else {
|
||||
// Torrent that has missing files is paused.
|
||||
m_startupState = Started;
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_startupState = Started;
|
||||
m_pauseWhenReady = false;
|
||||
if (m_fastresumeDataRejected && !m_hasMissingFiles)
|
||||
saveResumeData();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -1576,25 +1610,25 @@ 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");
|
||||
m_hasMissingFiles = false;
|
||||
if (m_hasSeedStatus) return;
|
||||
|
||||
updateStatus();
|
||||
m_hasMissingFiles = false;
|
||||
m_hasSeedStatus = true;
|
||||
|
||||
adjustActualSavePath();
|
||||
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);
|
||||
}
|
||||
@@ -1603,19 +1637,27 @@ 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) {
|
||||
if (!m_pauseWhenReady) {
|
||||
updateStatus();
|
||||
m_speedMonitor.reset();
|
||||
m_session->handleTorrentPaused(this);
|
||||
}
|
||||
else {
|
||||
m_pauseWhenReady = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -1626,8 +1668,8 @@ void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data
|
||||
libtorrent::entry &resumeData = useDummyResumeData ? dummyEntry : *(p->resume_data);
|
||||
if (useDummyResumeData) {
|
||||
resumeData["qBt-magnetUri"] = toMagnetUri().toStdString();
|
||||
resumeData["qBt-paused"] = isPaused();
|
||||
resumeData["qBt-forced"] = isForced();
|
||||
resumeData["paused"] = isPaused();
|
||||
resumeData["auto_managed"] = m_nativeStatus.auto_managed;
|
||||
// Both firstLastPiecePriority and sequential need to be stored in the
|
||||
// resume data if there is no metadata, otherwise they won't be
|
||||
// restored if qBittorrent quits before the metadata are retrieved:
|
||||
@@ -1646,9 +1688,17 @@ void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data
|
||||
resumeData["qBt-name"] = m_name.toStdString();
|
||||
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
|
||||
resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled;
|
||||
resumeData["qBt-queuePosition"] = queuePosition();
|
||||
resumeData["qBt-queuePosition"] = (nativeHandle().queue_position() + 1); // qBt starts queue at 1
|
||||
resumeData["qBt-hasRootFolder"] = m_hasRootFolder;
|
||||
|
||||
if (m_pauseWhenReady) {
|
||||
// We need to redefine these values when torrent starting/rechecking
|
||||
// in "paused" state since native values can be logically wrong
|
||||
// (torrent can be not paused and auto_managed when it is checking).
|
||||
resumeData["paused"] = true;
|
||||
resumeData["auto_managed"] = false;
|
||||
}
|
||||
|
||||
m_session->handleTorrentResumeDataReady(this, resumeData);
|
||||
}
|
||||
|
||||
@@ -1656,74 +1706,100 @@ void TorrentHandle::handleSaveResumeDataFailedAlert(const libtorrent::save_resum
|
||||
{
|
||||
// if torrent has no metadata we should save dummy fastresume data
|
||||
// containing Magnet URI and qBittorrent own resume data only
|
||||
if (p->error.value() == libt::errors::no_metadata)
|
||||
if (p->error.value() == libt::errors::no_metadata) {
|
||||
handleSaveResumeDataAlert(nullptr);
|
||||
else
|
||||
}
|
||||
else {
|
||||
LogMsg(tr("Save resume data failed. Torrent: \"%1\", error: \"%2\"")
|
||||
.arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
|
||||
m_session->handleTorrentResumeDataFailed(this);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentHandle::handleFastResumeRejectedAlert(const libtorrent::fastresume_rejected_alert *p)
|
||||
{
|
||||
qDebug("/!\\ Fast resume failed for %s, reason: %s", qUtf8Printable(name()), p->message().c_str());
|
||||
m_fastresumeDataRejected = true;
|
||||
|
||||
updateStatus();
|
||||
if (p->error.value() == libt::errors::mismatching_file_size) {
|
||||
// Mismatching file size (files were probably moved)
|
||||
LogMsg(tr("File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL);
|
||||
m_hasMissingFiles = true;
|
||||
if (!isPaused())
|
||||
pause();
|
||||
LogMsg(tr("File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL);
|
||||
}
|
||||
else {
|
||||
LogMsg(tr("Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...")
|
||||
.arg(name(), QString::fromStdString(p->message())), Log::CRITICAL);
|
||||
.arg(name(), QString::fromStdString(p->message())), Log::WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert *p)
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 10100
|
||||
QString newName = Utils::Fs::fromNativePath(QString::fromStdString(p->name));
|
||||
// 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()};
|
||||
|
||||
// remove empty leftover folders
|
||||
// for example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
|
||||
// be removed if they are empty
|
||||
const QString oldFilePath = m_oldPath[LTFileIndex {p->index}].takeFirst();
|
||||
const QString newFilePath = Utils::Fs::fromNativePath(p->new_name());
|
||||
|
||||
if (m_oldPath[LTFileIndex {p->index}].isEmpty())
|
||||
m_oldPath.remove(LTFileIndex {p->index});
|
||||
|
||||
QVector<QStringRef> oldPathParts = oldFilePath.splitRef('/', QString::SkipEmptyParts);
|
||||
oldPathParts.removeLast(); // drop file name part
|
||||
QVector<QStringRef> newPathParts = newFilePath.splitRef('/', QString::SkipEmptyParts);
|
||||
newPathParts.removeLast(); // drop file name part
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
|
||||
#else
|
||||
QString newName = Utils::Fs::fromNativePath(p->new_name());
|
||||
const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive;
|
||||
#endif
|
||||
|
||||
// TODO: Check this!
|
||||
if (filesCount() > 1) {
|
||||
// Check if folders were renamed
|
||||
QStringList oldPathParts = m_torrentInfo.origFilePath(p->index).split('/');
|
||||
oldPathParts.removeLast();
|
||||
QString oldPath = oldPathParts.join('/');
|
||||
QStringList newPathParts = newName.split('/');
|
||||
newPathParts.removeLast();
|
||||
QString newPath = newPathParts.join('/');
|
||||
if (!newPathParts.isEmpty() && (oldPath != newPath)) {
|
||||
qDebug("oldPath(%s) != newPath(%s)", qUtf8Printable(oldPath), qUtf8Printable(newPath));
|
||||
oldPath = QString("%1/%2").arg(savePath(true), oldPath);
|
||||
qDebug("Detected folder renaming, attempt to delete old folder: %s", qUtf8Printable(oldPath));
|
||||
QDir().rmpath(oldPath);
|
||||
}
|
||||
int pathIdx = 0;
|
||||
while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size())) {
|
||||
if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0)
|
||||
break;
|
||||
++pathIdx;
|
||||
}
|
||||
|
||||
updateStatus();
|
||||
for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i) {
|
||||
QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QLatin1String("/")));
|
||||
oldPathParts.removeLast();
|
||||
}
|
||||
|
||||
--m_renameCount;
|
||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
|
||||
if (isPaused() && (m_renameCount == 0))
|
||||
saveResumeData(); // otherwise the new path will not be saved
|
||||
}
|
||||
|
||||
void TorrentHandle::handleFileRenameFailedAlert(const libtorrent::file_rename_failed_alert *p)
|
||||
{
|
||||
Q_UNUSED(p);
|
||||
LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
|
||||
.arg(name(), filePath(p->index)
|
||||
, QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
|
||||
|
||||
m_oldPath[LTFileIndex {p->index}].removeFirst();
|
||||
if (m_oldPath[LTFileIndex {p->index}].isEmpty())
|
||||
m_oldPath.remove(LTFileIndex {p->index});
|
||||
|
||||
--m_renameCount;
|
||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||
m_moveFinishedTriggers.takeFirst()();
|
||||
|
||||
if (isPaused() && (m_renameCount == 0))
|
||||
saveResumeData(); // otherwise the new path will not be saved
|
||||
}
|
||||
|
||||
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()) {
|
||||
@@ -1938,6 +2014,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)
|
||||
@@ -1979,8 +2062,6 @@ void TorrentHandle::setDownloadLimit(int limit)
|
||||
void TorrentHandle::setSuperSeeding(bool enable)
|
||||
{
|
||||
m_nativeHandle.super_seeding(enable);
|
||||
if (superSeeding() != enable)
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
void TorrentHandle::flushCache()
|
||||
@@ -2039,7 +2120,7 @@ void TorrentHandle::prioritizeFiles(const QVector<int> &priorities)
|
||||
if (created) {
|
||||
// Hide the folder on Windows
|
||||
qDebug() << "Hiding folder (Windows)";
|
||||
std::wstring winPath = Utils::Fs::toNativePath(unwantedAbsPath).toStdWString();
|
||||
std::wstring winPath = Utils::Fs::toNativePath(unwantedAbsPath).toStdWString();
|
||||
DWORD dwAttrs = ::GetFileAttributesW(winPath.c_str());
|
||||
bool ret = ::SetFileAttributesW(winPath.c_str(), dwAttrs | FILE_ATTRIBUTE_HIDDEN);
|
||||
Q_ASSERT(ret != 0); Q_UNUSED(ret);
|
||||
@@ -2073,13 +2154,11 @@ 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
|
||||
{
|
||||
const auto filesCount = this->filesCount();
|
||||
const int filesCount = this->filesCount();
|
||||
if (filesCount < 0) return {};
|
||||
|
||||
const QVector<int> piecesAvailability = pieceAvailability();
|
||||
@@ -2088,12 +2167,13 @@ QVector<qreal> TorrentHandle::availableFileFractions() const
|
||||
|
||||
QVector<qreal> res;
|
||||
res.reserve(filesCount);
|
||||
TorrentInfo info = this->info();
|
||||
for (int file = 0; file < filesCount; ++file) {
|
||||
TorrentInfo::PieceRange filePieces = info.filePieces(file);
|
||||
const TorrentInfo info = this->info();
|
||||
for (int i = 0; i < filesCount; ++i) {
|
||||
const TorrentInfo::PieceRange filePieces = info.filePieces(i);
|
||||
|
||||
int availablePieces = 0;
|
||||
for (int piece = filePieces.first(); piece <= filePieces.last(); ++piece) {
|
||||
availablePieces += piecesAvailability[piece] > 0 ? 1 : 0;
|
||||
availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
|
||||
}
|
||||
res.push_back(static_cast<qreal>(availablePieces) / filePieces.size());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -347,7 +348,6 @@ namespace BitTorrent
|
||||
void renameFile(int index, const QString &name);
|
||||
bool saveTorrentFile(const QString &path);
|
||||
void prioritizeFiles(const QVector<int> &priorities);
|
||||
void setFilePriority(int index, int priority);
|
||||
void setRatioLimit(qreal limit);
|
||||
void setSeedingTimeLimit(int limit);
|
||||
void setUploadLimit(int limit);
|
||||
@@ -355,7 +355,7 @@ namespace BitTorrent
|
||||
void setSuperSeeding(bool enable);
|
||||
void flushCache();
|
||||
void addTrackers(const QList<TrackerEntry> &trackers);
|
||||
void replaceTrackers(QList<TrackerEntry> trackers);
|
||||
void replaceTrackers(const QList<TrackerEntry> &trackers);
|
||||
void addUrlSeeds(const QList<QUrl> &urlSeeds);
|
||||
void removeUrlSeeds(const QList<QUrl> &urlSeeds);
|
||||
bool connectPeer(const PeerAddress &peerAddress);
|
||||
@@ -372,7 +372,7 @@ namespace BitTorrent
|
||||
void handleTempPathChanged();
|
||||
void handleCategorySavePathChanged();
|
||||
void handleAppendExtensionToggled();
|
||||
void saveResumeData(bool updateStatus = false);
|
||||
void saveResumeData();
|
||||
|
||||
/**
|
||||
* @brief fraction of file pieces that are available at least from one peer
|
||||
@@ -385,6 +385,12 @@ namespace BitTorrent
|
||||
private:
|
||||
typedef boost::function<void ()> EventTrigger;
|
||||
|
||||
#if (LIBTORRENT_VERSION_NUM < 10200)
|
||||
using LTFileIndex = int;
|
||||
#else
|
||||
using LTFileIndex = lt::file_index_t;
|
||||
#endif
|
||||
|
||||
void updateStatus();
|
||||
void updateStatus(const libtorrent::torrent_status &nativeStatus);
|
||||
void updateState();
|
||||
@@ -408,7 +414,7 @@ namespace BitTorrent
|
||||
void handleMetadataReceivedAlert(const libtorrent::metadata_received_alert *p);
|
||||
void handleStatsAlert(const libtorrent::stats_alert *p);
|
||||
|
||||
void resume_impl(bool forced, bool uploadMode);
|
||||
void resume_impl(bool forced);
|
||||
bool isMoveInProgress() const;
|
||||
QString nativeActualSavePath() const;
|
||||
|
||||
@@ -446,6 +452,10 @@ namespace BitTorrent
|
||||
QQueue<EventTrigger> m_moveFinishedTriggers;
|
||||
int m_renameCount;
|
||||
|
||||
// Until libtorrent provide an "old_name" field in `file_renamed_alert`
|
||||
// we will rely on this workaround to remove empty leftover folders
|
||||
QHash<LTFileIndex, QVector<QString>> m_oldPath;
|
||||
|
||||
bool m_useAutoTMM;
|
||||
|
||||
// Persistent data
|
||||
@@ -457,14 +467,26 @@ namespace BitTorrent
|
||||
qreal m_ratioLimit;
|
||||
int m_seedingTimeLimit;
|
||||
bool m_tempPathDisabled;
|
||||
bool m_fastresumeDataRejected;
|
||||
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
|
||||
{
|
||||
Preparing, // torrent is preparing to start regular processing
|
||||
Starting, // torrent is prepared and starting to perform regular processing
|
||||
Started // torrent is performing regular processing
|
||||
};
|
||||
StartupState m_startupState = Preparing;
|
||||
// Handle torrent state when it starts performing some service job
|
||||
// being in Paused state so it might be unpaused internally and then paused again
|
||||
bool m_pauseWhenReady;
|
||||
|
||||
bool m_unchecked = false;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
@@ -105,10 +106,9 @@ TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexc
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
const qint64 fileSizeLimit = 100 * 1024 * 1024; // 100 MB
|
||||
if (file.size() > fileSizeLimit) {
|
||||
if (file.size() > MAX_TORRENT_SIZE) {
|
||||
if (error)
|
||||
*error = tr("File size exceeds max limit %1").arg(fileSizeLimit);
|
||||
*error = tr("File size exceeds max limit %1").arg(Utils::Misc::friendlyUnit(MAX_TORRENT_SIZE));
|
||||
return TorrentInfo();
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ QList<TrackerEntry> TorrentInfo::trackers() const
|
||||
if (!isValid()) return QList<TrackerEntry>();
|
||||
|
||||
QList<TrackerEntry> trackers;
|
||||
foreach (const libt::announce_entry &tracker, m_nativeInfo->trackers())
|
||||
for (const libt::announce_entry &tracker : m_nativeInfo->trackers())
|
||||
trackers.append(tracker);
|
||||
|
||||
return trackers;
|
||||
@@ -258,7 +258,7 @@ QList<QUrl> TorrentInfo::urlSeeds() const
|
||||
if (!isValid()) return QList<QUrl>();
|
||||
|
||||
QList<QUrl> urlSeeds;
|
||||
foreach (const libt::web_seed_entry &webSeed, m_nativeInfo->web_seeds())
|
||||
for (const libt::web_seed_entry &webSeed : m_nativeInfo->web_seeds())
|
||||
if (webSeed.type == libt::web_seed_entry::url_seed)
|
||||
urlSeeds.append(QUrl(webSeed.url.c_str()));
|
||||
|
||||
@@ -339,18 +339,23 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(int fileIndex) const
|
||||
|
||||
const libt::file_storage &files = nativeInfo()->files();
|
||||
const auto fileSize = files.file_size(fileIndex);
|
||||
const auto firstOffset = files.file_offset(fileIndex);
|
||||
return makeInterval(static_cast<int>(firstOffset / pieceLength()),
|
||||
static_cast<int>((firstOffset + fileSize - 1) / pieceLength()));
|
||||
const auto fileOffset = files.file_offset(fileIndex);
|
||||
|
||||
const int beginIdx = (fileOffset / pieceLength());
|
||||
const int endIdx = ((fileOffset + fileSize - 1) / pieceLength());
|
||||
|
||||
if (fileSize <= 0)
|
||||
return {beginIdx, 0};
|
||||
return makeInterval(beginIdx, endIdx);
|
||||
}
|
||||
|
||||
void TorrentInfo::renameFile(uint index, const QString &newPath)
|
||||
void TorrentInfo::renameFile(const int index, const QString &newPath)
|
||||
{
|
||||
if (!isValid()) return;
|
||||
nativeInfo()->rename_file(index, Utils::Fs::toNativePath(newPath).toStdString());
|
||||
}
|
||||
|
||||
int BitTorrent::TorrentInfo::fileIndex(const QString& fileName) const
|
||||
int BitTorrent::TorrentInfo::fileIndex(const QString &fileName) const
|
||||
{
|
||||
// the check whether the object is valid is not needed here
|
||||
// because if filesCount() returns -1 the loop exits immediately
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace BitTorrent
|
||||
PieceRange filePieces(const QString &file) const;
|
||||
PieceRange filePieces(int fileIndex) const;
|
||||
|
||||
void renameFile(uint index, const QString &newPath);
|
||||
void renameFile(int index, const QString &newPath);
|
||||
|
||||
QString rootFolder() const;
|
||||
bool hasRootFolder() const;
|
||||
|
||||
@@ -29,16 +29,11 @@
|
||||
|
||||
#include "tracker.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <libtorrent/bencode.hpp>
|
||||
#include <libtorrent/entry.hpp>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/http/server.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/bytearray.h"
|
||||
#include "base/utils/string.h"
|
||||
|
||||
// static limits
|
||||
static const int MAX_TORRENTS = 100;
|
||||
@@ -60,7 +55,7 @@ bool Peer::operator==(const Peer &other) const
|
||||
|
||||
QString Peer::uid() const
|
||||
{
|
||||
return ip + ':' + QString::number(port);
|
||||
return ip.toString() + ':' + QString::number(port);
|
||||
}
|
||||
|
||||
libtorrent::entry Peer::toEntry(bool noPeerId) const
|
||||
@@ -68,7 +63,7 @@ libtorrent::entry Peer::toEntry(bool noPeerId) const
|
||||
libtorrent::entry::dictionary_type peerMap;
|
||||
if (!noPeerId)
|
||||
peerMap["id"] = libtorrent::entry(peerId.toStdString());
|
||||
peerMap["ip"] = libtorrent::entry(ip.toStdString());
|
||||
peerMap["ip"] = libtorrent::entry(ip.toString().toStdString());
|
||||
peerMap["port"] = libtorrent::entry(port);
|
||||
|
||||
return libtorrent::entry(peerMap);
|
||||
@@ -133,22 +128,13 @@ Http::Response Tracker::processRequest(const Http::Request &request, const Http:
|
||||
|
||||
void Tracker::respondToAnnounceRequest()
|
||||
{
|
||||
QMap<QString, QByteArray> queryParams;
|
||||
// Parse GET parameters
|
||||
using namespace Utils::ByteArray;
|
||||
for (const QByteArray ¶m : copyAsConst(splitToViews(m_request.query, "&"))) {
|
||||
const int sepPos = param.indexOf('=');
|
||||
if (sepPos <= 0) continue; // ignores params without name
|
||||
|
||||
const QString paramName {QString::fromUtf8(param.constData(), sepPos)};
|
||||
const QByteArray paramValue {param.mid(sepPos + 1)};
|
||||
queryParams[paramName] = paramValue;
|
||||
}
|
||||
|
||||
TrackerAnnounceRequest annonceReq;
|
||||
const QMap<QString, QByteArray> &queryParams = m_request.query;
|
||||
TrackerAnnounceRequest announceReq;
|
||||
|
||||
// IP
|
||||
annonceReq.peer.ip = m_env.clientAddress.toString();
|
||||
// Use the "ip" parameter provided from tracker request first, then fall back to client IP if invalid
|
||||
const QHostAddress paramIP {QString::fromLatin1(queryParams.value("ip"))};
|
||||
announceReq.peer.ip = paramIP.isNull() ? m_env.clientAddress : paramIP;
|
||||
|
||||
// 1. Get info_hash
|
||||
if (!queryParams.contains("info_hash")) {
|
||||
@@ -156,7 +142,7 @@ void Tracker::respondToAnnounceRequest()
|
||||
status(101, "Missing info_hash");
|
||||
return;
|
||||
}
|
||||
annonceReq.infoHash = queryParams.value("info_hash");
|
||||
announceReq.infoHash = queryParams.value("info_hash");
|
||||
// info_hash cannot be longer than 20 bytes
|
||||
/*if (annonce_req.info_hash.toLatin1().length() > 20) {
|
||||
qDebug("Tracker: Info_hash is not 20 byte long: %s (%d)", qUtf8Printable(annonce_req.info_hash), annonce_req.info_hash.toLatin1().length());
|
||||
@@ -170,7 +156,7 @@ void Tracker::respondToAnnounceRequest()
|
||||
status(102, "Missing peer_id");
|
||||
return;
|
||||
}
|
||||
annonceReq.peer.peerId = queryParams.value("peer_id");
|
||||
announceReq.peer.peerId = queryParams.value("peer_id");
|
||||
// peer_id cannot be longer than 20 bytes
|
||||
/*if (annonce_req.peer.peer_id.length() > 20) {
|
||||
qDebug("Tracker: peer_id is not 20 byte long: %s", qUtf8Printable(annonce_req.peer.peer_id));
|
||||
@@ -185,52 +171,52 @@ void Tracker::respondToAnnounceRequest()
|
||||
return;
|
||||
}
|
||||
bool ok = false;
|
||||
annonceReq.peer.port = queryParams.value("port").toInt(&ok);
|
||||
if (!ok || (annonceReq.peer.port < 0) || (annonceReq.peer.port > 65535)) {
|
||||
qDebug("Tracker: Invalid port number (%d)", annonceReq.peer.port);
|
||||
announceReq.peer.port = queryParams.value("port").toInt(&ok);
|
||||
if (!ok || (announceReq.peer.port < 0) || (announceReq.peer.port > 65535)) {
|
||||
qDebug("Tracker: Invalid port number (%d)", announceReq.peer.port);
|
||||
status(103, "Missing port");
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Get event
|
||||
annonceReq.event = "";
|
||||
announceReq.event = "";
|
||||
if (queryParams.contains("event")) {
|
||||
annonceReq.event = queryParams.value("event");
|
||||
qDebug("Tracker: event is %s", qUtf8Printable(annonceReq.event));
|
||||
announceReq.event = queryParams.value("event");
|
||||
qDebug("Tracker: event is %s", qUtf8Printable(announceReq.event));
|
||||
}
|
||||
|
||||
// 5. Get numwant
|
||||
annonceReq.numwant = 50;
|
||||
announceReq.numwant = 50;
|
||||
if (queryParams.contains("numwant")) {
|
||||
int tmp = queryParams.value("numwant").toInt();
|
||||
if (tmp > 0) {
|
||||
qDebug("Tracker: numwant = %d", tmp);
|
||||
annonceReq.numwant = tmp;
|
||||
announceReq.numwant = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. no_peer_id (extension)
|
||||
annonceReq.noPeerId = false;
|
||||
announceReq.noPeerId = false;
|
||||
if (queryParams.contains("no_peer_id"))
|
||||
annonceReq.noPeerId = true;
|
||||
announceReq.noPeerId = true;
|
||||
|
||||
// 7. TODO: support "compact" extension
|
||||
|
||||
// Done parsing, now let's reply
|
||||
if (annonceReq.event == "stopped") {
|
||||
unregisterPeer(annonceReq);
|
||||
if (announceReq.event == "stopped") {
|
||||
unregisterPeer(announceReq);
|
||||
}
|
||||
else {
|
||||
registerPeer(annonceReq);
|
||||
replyWithPeerList(annonceReq);
|
||||
registerPeer(announceReq);
|
||||
replyWithPeerList(announceReq);
|
||||
}
|
||||
}
|
||||
|
||||
void Tracker::registerPeer(const TrackerAnnounceRequest &annonceReq)
|
||||
void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
|
||||
{
|
||||
if (annonceReq.peer.port == 0) return;
|
||||
if (announceReq.peer.port == 0) return;
|
||||
|
||||
if (!m_torrents.contains(annonceReq.infoHash)) {
|
||||
if (!m_torrents.contains(announceReq.infoHash)) {
|
||||
// Unknown torrent
|
||||
if (m_torrents.size() == MAX_TORRENTS) {
|
||||
// Reached max size, remove a random torrent
|
||||
@@ -239,34 +225,34 @@ void Tracker::registerPeer(const TrackerAnnounceRequest &annonceReq)
|
||||
}
|
||||
|
||||
// Register the user
|
||||
PeerList &peers = m_torrents[annonceReq.infoHash];
|
||||
if (!peers.contains(annonceReq.peer.uid())) {
|
||||
PeerList &peers = m_torrents[announceReq.infoHash];
|
||||
if (!peers.contains(announceReq.peer.uid())) {
|
||||
// Unknown peer
|
||||
if (peers.size() == MAX_PEERS_PER_TORRENT) {
|
||||
// Too many peers, remove a random one
|
||||
peers.erase(peers.begin());
|
||||
}
|
||||
}
|
||||
peers[annonceReq.peer.uid()] = annonceReq.peer;
|
||||
peers[announceReq.peer.uid()] = announceReq.peer;
|
||||
}
|
||||
|
||||
void Tracker::unregisterPeer(const TrackerAnnounceRequest &annonceReq)
|
||||
void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
|
||||
{
|
||||
if (annonceReq.peer.port == 0) return;
|
||||
if (announceReq.peer.port == 0) return;
|
||||
|
||||
if (m_torrents[annonceReq.infoHash].remove(annonceReq.peer.uid()) > 0)
|
||||
if (m_torrents[announceReq.infoHash].remove(announceReq.peer.uid()) > 0)
|
||||
qDebug("Tracker: Peer stopped downloading, deleting it from the list");
|
||||
}
|
||||
|
||||
void Tracker::replyWithPeerList(const TrackerAnnounceRequest &annonceReq)
|
||||
void Tracker::replyWithPeerList(const TrackerAnnounceRequest &announceReq)
|
||||
{
|
||||
// Prepare the entry for bencoding
|
||||
libtorrent::entry::dictionary_type replyDict;
|
||||
replyDict["interval"] = libtorrent::entry(ANNOUNCE_INTERVAL);
|
||||
|
||||
libtorrent::entry::list_type peerList;
|
||||
for (const Peer &p : m_torrents.value(annonceReq.infoHash))
|
||||
peerList.push_back(p.toEntry(annonceReq.noPeerId));
|
||||
for (const Peer &p : m_torrents.value(announceReq.infoHash))
|
||||
peerList.push_back(p.toEntry(announceReq.noPeerId));
|
||||
replyDict["peers"] = libtorrent::entry(peerList);
|
||||
|
||||
const libtorrent::entry replyEntry(replyDict);
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include "base/http/irequesthandler.h"
|
||||
#include "base/http/responsebuilder.h"
|
||||
#include "base/http/types.h"
|
||||
|
||||
namespace libtorrent
|
||||
{
|
||||
@@ -51,7 +51,7 @@ namespace BitTorrent
|
||||
{
|
||||
struct Peer
|
||||
{
|
||||
QString ip;
|
||||
QHostAddress ip;
|
||||
QByteArray peerId;
|
||||
int port;
|
||||
|
||||
@@ -90,9 +90,9 @@ namespace BitTorrent
|
||||
|
||||
private:
|
||||
void respondToAnnounceRequest();
|
||||
void registerPeer(const TrackerAnnounceRequest &annonceReq);
|
||||
void unregisterPeer(const TrackerAnnounceRequest &annonceReq);
|
||||
void replyWithPeerList(const TrackerAnnounceRequest &annonceReq);
|
||||
void registerPeer(const TrackerAnnounceRequest &announceReq);
|
||||
void unregisterPeer(const TrackerAnnounceRequest &announceReq);
|
||||
void replyWithPeerList(const TrackerAnnounceRequest &announceReq);
|
||||
|
||||
Http::Server *m_server;
|
||||
TorrentList m_torrents;
|
||||
|
||||
@@ -44,10 +44,10 @@ namespace BitTorrent
|
||||
public:
|
||||
enum Status
|
||||
{
|
||||
NotContacted,
|
||||
Working,
|
||||
Updating,
|
||||
NotWorking
|
||||
NotContacted = 1,
|
||||
Working = 2,
|
||||
Updating = 3,
|
||||
NotWorking = 4
|
||||
};
|
||||
|
||||
TrackerEntry(const QString &url);
|
||||
|
||||
@@ -30,11 +30,10 @@
|
||||
|
||||
RuntimeError::RuntimeError(const QString &message)
|
||||
: std::runtime_error {message.toUtf8().data()}
|
||||
, m_message {message}
|
||||
{
|
||||
}
|
||||
|
||||
QString RuntimeError::message() const
|
||||
{
|
||||
return m_message;
|
||||
return what();
|
||||
}
|
||||
|
||||
@@ -36,7 +36,4 @@ class RuntimeError : public std::runtime_error
|
||||
public:
|
||||
explicit RuntimeError(const QString &message = "");
|
||||
QString message() const;
|
||||
|
||||
private:
|
||||
const QString m_message;
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
#include <cstring>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/param.h>
|
||||
@@ -40,6 +40,7 @@
|
||||
#include "base/bittorrent/magneturi.h"
|
||||
#include "base/bittorrent/torrentinfo.h"
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/fs.h"
|
||||
|
||||
@@ -57,18 +58,14 @@ FileSystemWatcher::FileSystemWatcher(QObject *parent)
|
||||
m_partialTorrentTimer.setSingleShot(true);
|
||||
connect(&m_partialTorrentTimer, &QTimer::timeout, this, &FileSystemWatcher::processPartialTorrents);
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
connect(&m_watchTimer, &QTimer::timeout, this, &FileSystemWatcher::scanNetworkFolders);
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList FileSystemWatcher::directories() const
|
||||
{
|
||||
QStringList dirs = QFileSystemWatcher::directories();
|
||||
#ifndef Q_OS_WIN
|
||||
for (const QDir &dir : qAsConst(m_watchedFolders))
|
||||
for (const QDir &dir : asConst(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))
|
||||
for (const QDir &dir : asConst(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
|
||||
|
||||
@@ -32,17 +32,15 @@
|
||||
#include <QtGlobal>
|
||||
|
||||
const char C_TORRENT_FILE_EXTENSION[] = ".torrent";
|
||||
const int MAX_TORRENT_SIZE = 100 * 1024 * 1024; // 100 MiB
|
||||
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
|
||||
template <typename T>
|
||||
constexpr typename std::add_const<T>::type &qAsConst(T &t) noexcept { return t; }
|
||||
constexpr typename std::add_const<T>::type &asConst(T &t) noexcept { return t; }
|
||||
|
||||
// prevent rvalue arguments:
|
||||
// Forward rvalue as const
|
||||
template <typename T>
|
||||
void qAsConst(const T &&) = delete;
|
||||
#endif
|
||||
constexpr typename std::add_const<T>::type asConst(T &&t) noexcept { return std::move(t); }
|
||||
|
||||
// returns a const object copy
|
||||
// Prevent const rvalue arguments
|
||||
template <typename T>
|
||||
constexpr typename std::add_const<T>::type copyAsConst(T &&t) noexcept { return std::move(t); }
|
||||
void asConst(const T &&) = delete;
|
||||
|
||||
@@ -66,7 +66,7 @@ void Connection::read()
|
||||
case RequestParser::ParseStatus::Incomplete: {
|
||||
const long bufferLimit = RequestParser::MAX_CONTENT_SIZE * 1.1; // some margin for headers
|
||||
if (m_receivedData.size() > bufferLimit) {
|
||||
Logger::instance()->addMessage(tr("Http request size exceeds limiation, closing socket. Limit: %ld, IP: %s")
|
||||
Logger::instance()->addMessage(tr("Http request size exceeds limiation, closing socket. Limit: %1, IP: %2")
|
||||
.arg(bufferLimit).arg(m_socket->peerAddress().toString()), Log::WARNING);
|
||||
|
||||
Response resp(413, "Payload Too Large");
|
||||
@@ -79,7 +79,7 @@ void Connection::read()
|
||||
return;
|
||||
|
||||
case RequestParser::ParseStatus::BadRequest: {
|
||||
Logger::instance()->addMessage(tr("Bad Http request, closing socket. IP: %s")
|
||||
Logger::instance()->addMessage(tr("Bad Http request, closing socket. IP: %1")
|
||||
.arg(m_socket->peerAddress().toString()), Log::WARNING);
|
||||
|
||||
Response resp(400, "Bad Request");
|
||||
@@ -133,7 +133,7 @@ bool Connection::acceptsGzipEncoding(QString codings)
|
||||
|
||||
const auto isCodingAvailable = [](const QStringList &list, const QString &encoding) -> bool
|
||||
{
|
||||
foreach (const QString &str, list) {
|
||||
for (const QString &str : list) {
|
||||
if (!str.startsWith(encoding))
|
||||
continue;
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/utils/bytearray.h"
|
||||
#include "base/utils/string.h"
|
||||
|
||||
@@ -180,11 +181,29 @@ bool RequestParser::parseRequestLine(const QString &line)
|
||||
m_request.method = match.captured(1);
|
||||
|
||||
// Request Target
|
||||
const QByteArray decodedUrl {QByteArray::fromPercentEncoding(match.captured(2).toLatin1())};
|
||||
const int sepPos = decodedUrl.indexOf('?');
|
||||
m_request.path = QString::fromUtf8(decodedUrl.constData(), (sepPos == -1 ? decodedUrl.size() : sepPos));
|
||||
if (sepPos >= 0)
|
||||
m_request.query = decodedUrl.mid(sepPos + 1);
|
||||
const QByteArray url {match.captured(2).toLatin1()};
|
||||
const int sepPos = url.indexOf('?');
|
||||
const QByteArray pathComponent = ((sepPos == -1) ? url : midView(url, 0, sepPos));
|
||||
|
||||
m_request.path = QString::fromUtf8(QByteArray::fromPercentEncoding(pathComponent));
|
||||
|
||||
if (sepPos >= 0) {
|
||||
const QByteArray query = midView(url, (sepPos + 1));
|
||||
|
||||
// [rfc3986] 2.4 When to Encode or Decode
|
||||
// URL components should be separated before percent-decoding
|
||||
for (const QByteArray ¶m : asConst(splitToViews(query, "&"))) {
|
||||
const int eqCharPos = param.indexOf('=');
|
||||
if (eqCharPos <= 0) continue; // ignores params without name
|
||||
|
||||
const QByteArray nameComponent = midView(param, 0, eqCharPos);
|
||||
const QByteArray valueComponent = midView(param, (eqCharPos + 1));
|
||||
const QString paramName = QString::fromUtf8(QByteArray::fromPercentEncoding(nameComponent).replace('+', ' '));
|
||||
const QByteArray paramValue = QByteArray::fromPercentEncoding(valueComponent).replace('+', ' ');
|
||||
|
||||
m_request.query[paramName] = paramValue;
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP-version
|
||||
m_request.version = match.captured(3);
|
||||
@@ -200,7 +219,10 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
|
||||
|
||||
// application/x-www-form-urlencoded
|
||||
if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_ENCODED)) {
|
||||
QListIterator<QStringPair> i(QUrlQuery(data).queryItems(QUrl::FullyDecoded));
|
||||
// [URL Standard] 5.1 application/x-www-form-urlencoded parsing
|
||||
const QByteArray processedData = QByteArray(data).replace('+', ' ');
|
||||
|
||||
QListIterator<QStringPair> i(QUrlQuery(processedData).queryItems(QUrl::FullyDecoded));
|
||||
while (i.hasNext()) {
|
||||
const QStringPair pair = i.next();
|
||||
m_request.posts[pair.first] = pair.second;
|
||||
|
||||
@@ -97,16 +97,23 @@ void Server::incomingConnection(qintptr socketDescriptor)
|
||||
#endif
|
||||
|
||||
Connection *c = new Connection(serverSocket, m_requestHandler, this);
|
||||
m_connections.append(c);
|
||||
m_connections.insert(c);
|
||||
connect(serverSocket, &QAbstractSocket::disconnected, this, [c, this]() { removeConnection(c); });
|
||||
}
|
||||
|
||||
void Server::removeConnection(Connection *connection)
|
||||
{
|
||||
m_connections.remove(connection);
|
||||
connection->deleteLater();
|
||||
}
|
||||
|
||||
void Server::dropTimedOutConnection()
|
||||
{
|
||||
QMutableListIterator<Connection *> i(m_connections);
|
||||
QMutableSetIterator<Connection *> i(m_connections);
|
||||
while (i.hasNext()) {
|
||||
auto connection = i.next();
|
||||
if (connection->isClosed() || connection->hasExpired(KEEP_ALIVE_DURATION)) {
|
||||
delete connection;
|
||||
Connection *connection = i.next();
|
||||
if (connection->hasExpired(KEEP_ALIVE_DURATION)) {
|
||||
connection->deleteLater();
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
@@ -146,9 +153,9 @@ QList<QSslCipher> Server::safeCipherList() const
|
||||
const QStringList badCiphers = {"idea", "rc4"};
|
||||
const QList<QSslCipher> allCiphers = QSslSocket::supportedCiphers();
|
||||
QList<QSslCipher> safeCiphers;
|
||||
foreach (const QSslCipher &cipher, allCiphers) {
|
||||
for (const QSslCipher &cipher : allCiphers) {
|
||||
bool isSafe = true;
|
||||
foreach (const QString &badCipher, badCiphers) {
|
||||
for (const QString &badCipher : badCiphers) {
|
||||
if (cipher.name().contains(badCipher, Qt::CaseInsensitive)) {
|
||||
isSafe = false;
|
||||
break;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#ifndef HTTP_SERVER_H
|
||||
#define HTTP_SERVER_H
|
||||
|
||||
#include <QSet>
|
||||
#include <QTcpServer>
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
@@ -63,9 +64,10 @@ namespace Http
|
||||
|
||||
private:
|
||||
void incomingConnection(qintptr socketDescriptor);
|
||||
void removeConnection(Connection *connection);
|
||||
|
||||
IRequestHandler *m_requestHandler;
|
||||
QList<Connection *> m_connections; // for tracking persistent connections
|
||||
QSet<Connection *> m_connections; // for tracking persistent connections
|
||||
|
||||
#ifndef QT_NO_OPENSSL
|
||||
QList<QSslCipher> safeCipherList() const;
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace Http
|
||||
const char HEADER_HOST[] = "host";
|
||||
const char HEADER_ORIGIN[] = "origin";
|
||||
const char HEADER_REFERER[] = "referer";
|
||||
const char HEADER_REFERRER_POLICY[] = "referrer-policy";
|
||||
const char HEADER_SET_COOKIE[] = "set-cookie";
|
||||
const char HEADER_X_CONTENT_TYPE_OPTIONS[] = "x-content-type-options";
|
||||
const char HEADER_X_FORWARDED_HOST[] = "x-forwarded-host";
|
||||
@@ -96,8 +97,8 @@ namespace Http
|
||||
QString version;
|
||||
QString method;
|
||||
QString path;
|
||||
QByteArray query;
|
||||
QStringMap headers;
|
||||
QMap<QString, QByteArray> query;
|
||||
QStringMap posts;
|
||||
QVector<UploadedFile> files;
|
||||
};
|
||||
@@ -107,7 +108,7 @@ namespace Http
|
||||
uint code;
|
||||
QString text;
|
||||
|
||||
ResponseStatus(uint code = 200, const QString& text = "OK"): code(code), text(text) {}
|
||||
ResponseStatus(uint code = 200, const QString &text = "OK"): code(code), text(text) {}
|
||||
};
|
||||
|
||||
struct Response
|
||||
@@ -116,7 +117,7 @@ namespace Http
|
||||
QStringMap headers;
|
||||
QByteArray content;
|
||||
|
||||
Response(uint code = 200, const QString& text = "OK"): status(code, text) {}
|
||||
Response(uint code = 200, const QString &text = "OK"): status(code, text) {}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
#include "iconprovider.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
|
||||
IconProvider::IconProvider(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
@@ -57,7 +59,13 @@ IconProvider *IconProvider::instance()
|
||||
|
||||
QString IconProvider::getIconPath(const QString &iconId) const
|
||||
{
|
||||
return ":/icons/qbt-theme/" + iconId + ".png";
|
||||
// there are a few icons not available in svg
|
||||
const QString pathSvg = ":/icons/qbt-theme/" + iconId + ".svg";
|
||||
if (QFileInfo::exists(pathSvg))
|
||||
return pathSvg;
|
||||
|
||||
const QString pathPng = ":/icons/qbt-theme/" + iconId + ".png";
|
||||
return pathPng;
|
||||
}
|
||||
|
||||
IconProvider *IconProvider::m_instance = nullptr;
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <QSslError>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/preferences.h"
|
||||
#include "downloadhandler.h"
|
||||
#include "proxyconfigurationmanager.h"
|
||||
@@ -56,7 +57,7 @@ namespace
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QList<QNetworkCookie> cookies = Preferences::instance()->getNetworkCookies();
|
||||
foreach (const QNetworkCookie &cookie, Preferences::instance()->getNetworkCookies()) {
|
||||
for (const QNetworkCookie &cookie : asConst(Preferences::instance()->getNetworkCookies())) {
|
||||
if (cookie.isSessionCookie() || (cookie.expirationDate() <= now))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
@@ -64,11 +65,11 @@ namespace
|
||||
setAllCookies(cookies);
|
||||
}
|
||||
|
||||
~NetworkCookieJar()
|
||||
~NetworkCookieJar() override
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QList<QNetworkCookie> cookies = allCookies();
|
||||
foreach (const QNetworkCookie &cookie, allCookies()) {
|
||||
for (const QNetworkCookie &cookie : asConst(allCookies())) {
|
||||
if (cookie.isSessionCookie() || (cookie.expirationDate() <= now))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
@@ -83,7 +84,7 @@ namespace
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QList<QNetworkCookie> cookies = QNetworkCookieJar::cookiesForUrl(url);
|
||||
foreach (const QNetworkCookie &cookie, QNetworkCookieJar::cookiesForUrl(url)) {
|
||||
for (const QNetworkCookie &cookie : asConst(QNetworkCookieJar::cookiesForUrl(url))) {
|
||||
if (!cookie.isSessionCookie() && (cookie.expirationDate() <= now))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
@@ -95,7 +96,7 @@ namespace
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QList<QNetworkCookie> cookies = cookieList;
|
||||
foreach (const QNetworkCookie &cookie, cookieList) {
|
||||
for (const QNetworkCookie &cookie : cookieList) {
|
||||
if (!cookie.isSessionCookie() && (cookie.expirationDate() <= now))
|
||||
cookies.removeAll(cookie);
|
||||
}
|
||||
@@ -244,7 +245,7 @@ void Net::DownloadManager::applyProxySettings()
|
||||
|
||||
void Net::DownloadManager::handleReplyFinished(QNetworkReply *reply)
|
||||
{
|
||||
const ServiceID id = ServiceID::fromURL(reply->url());
|
||||
const ServiceID id = ServiceID::fromURL(reply->request().url());
|
||||
auto waitingJobsIter = m_waitingJobs.find(id);
|
||||
if ((waitingJobsIter == m_waitingJobs.end()) || waitingJobsIter.value().isEmpty()) {
|
||||
m_busyServices.remove(id);
|
||||
|
||||
@@ -92,12 +92,12 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
|
||||
QFile file(filename);
|
||||
if (file.size() > MAX_FILE_SIZE) {
|
||||
error = tr("Unsupported database file size.");
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
error = file.errorString();
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
db = new GeoIPDatabase(file.size());
|
||||
@@ -105,13 +105,13 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
|
||||
if (file.read(reinterpret_cast<char *>(db->m_data), db->m_size) != db->m_size) {
|
||||
error = file.errorString();
|
||||
delete db;
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
|
||||
delete db;
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return db;
|
||||
@@ -122,7 +122,7 @@ GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
|
||||
GeoIPDatabase *db = nullptr;
|
||||
if (data.size() > MAX_FILE_SIZE) {
|
||||
error = tr("Unsupported database file size.");
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
db = new GeoIPDatabase(data.size());
|
||||
@@ -131,7 +131,7 @@ GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
|
||||
|
||||
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
|
||||
delete db;
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return db;
|
||||
|
||||
@@ -40,7 +40,7 @@ const QString KEY_PASSWORD = SETTINGS_KEY("Password");
|
||||
|
||||
namespace
|
||||
{
|
||||
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
|
||||
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
|
||||
|
||||
inline bool isSameConfig(const Net::ProxyConfiguration &conf1, const Net::ProxyConfiguration &conf2)
|
||||
{
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include <QTcpSocket>
|
||||
#endif
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
|
||||
@@ -291,7 +292,7 @@ QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, QTex
|
||||
if (!prefix.isEmpty()) line += prefix;
|
||||
if (!value.contains("=?") && latin1->canEncode(value)) {
|
||||
bool firstWord = true;
|
||||
foreach (const QByteArray& word, value.toLatin1().split(' ')) {
|
||||
for (const QByteArray &word : asConst(value.toLatin1().split(' '))) {
|
||||
if (line.size() > 78) {
|
||||
rv = rv + line + "\r\n";
|
||||
line.clear();
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <shlobj.h>
|
||||
#include <winreg.h>
|
||||
#include <QRegularExpression>
|
||||
#endif
|
||||
|
||||
@@ -51,6 +50,7 @@
|
||||
#include <CoreServices/CoreServices.h>
|
||||
#endif
|
||||
|
||||
#include "global.h"
|
||||
#include "logger.h"
|
||||
#include "settingsstorage.h"
|
||||
#include "utils/fs.h"
|
||||
@@ -92,7 +92,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)
|
||||
@@ -212,7 +213,7 @@ void Preferences::setCloseToTrayNotified(bool b)
|
||||
{
|
||||
setValue("Preferences/General/CloseToTrayNotified", b);
|
||||
}
|
||||
#endif
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
bool Preferences::isToolbarDisplayed() const
|
||||
{
|
||||
@@ -293,7 +294,7 @@ void Preferences::setWinStartup(bool b)
|
||||
settings.remove("qBittorrent");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
// Downloads
|
||||
QString Preferences::lastLocationPath() const
|
||||
@@ -505,7 +506,7 @@ void Preferences::setWebUiAuthSubnetWhitelistEnabled(bool enabled)
|
||||
QList<Utils::Net::Subnet> Preferences::getWebUiAuthSubnetWhitelist() const
|
||||
{
|
||||
QList<Utils::Net::Subnet> subnets;
|
||||
foreach (const QString &rawSubnet, value("Preferences/WebUI/AuthSubnetWhitelist").toStringList()) {
|
||||
for (const QString &rawSubnet : asConst(value("Preferences/WebUI/AuthSubnetWhitelist").toStringList())) {
|
||||
bool ok = false;
|
||||
const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(rawSubnet.trimmed(), &ok);
|
||||
if (ok)
|
||||
@@ -530,7 +531,7 @@ void Preferences::setWebUiAuthSubnetWhitelist(QStringList subnets)
|
||||
|
||||
QString Preferences::getServerDomains() const
|
||||
{
|
||||
return value("Preferences/WebUI/ServerDomains", '*').toString();
|
||||
return value("Preferences/WebUI/ServerDomains", QChar('*')).toString();
|
||||
}
|
||||
|
||||
void Preferences::setServerDomains(const QString &str)
|
||||
@@ -540,7 +541,7 @@ void Preferences::setServerDomains(const QString &str)
|
||||
|
||||
QString Preferences::getWebUiAddress() const
|
||||
{
|
||||
return value("Preferences/WebUI/Address", '*').toString().trimmed();
|
||||
return value("Preferences/WebUI/Address", QChar('*')).toString().trimmed();
|
||||
}
|
||||
|
||||
void Preferences::setWebUiAddress(const QString &addr)
|
||||
@@ -626,6 +627,16 @@ void Preferences::setWebUiCSRFProtectionEnabled(bool enabled)
|
||||
setValue("Preferences/WebUI/CSRFProtection", enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUIHostHeaderValidationEnabled() const
|
||||
{
|
||||
return value("Preferences/WebUI/HostHeaderValidation", true).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setWebUIHostHeaderValidationEnabled(const bool enabled)
|
||||
{
|
||||
setValue("Preferences/WebUI/HostHeaderValidation", enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUiHttpsEnabled() const
|
||||
{
|
||||
return value("Preferences/WebUI/HTTPS/Enabled", false).toBool();
|
||||
@@ -878,153 +889,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();
|
||||
@@ -1104,7 +968,7 @@ void Preferences::setMagnetLinkAssoc(bool set)
|
||||
|
||||
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0);
|
||||
}
|
||||
#endif
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
namespace
|
||||
@@ -1160,7 +1024,7 @@ void Preferences::setMagnetLinkAssoc()
|
||||
CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
|
||||
LSSetDefaultHandlerForURLScheme(magnetUrlScheme, myBundleId);
|
||||
}
|
||||
#endif
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
int Preferences::getTrackerPort() const
|
||||
{
|
||||
@@ -1429,6 +1293,16 @@ void Preferences::setSearchTabHeaderState(const QByteArray &state)
|
||||
setValue("SearchTab/qt5/HeaderState", state);
|
||||
}
|
||||
|
||||
bool Preferences::getRegexAsFilteringPatternForSearchJob() const
|
||||
{
|
||||
return value("SearchTab/UseRegexAsFilteringPattern", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setRegexAsFilteringPatternForSearchJob(const bool checked)
|
||||
{
|
||||
setValue("SearchTab/UseRegexAsFilteringPattern", checked);
|
||||
}
|
||||
|
||||
QStringList Preferences::getSearchEngDisabled() const
|
||||
{
|
||||
return value("SearchEngines/disabledEngines").toStringList();
|
||||
@@ -1519,12 +1393,12 @@ void Preferences::setTransHeaderState(const QByteArray &state)
|
||||
setValue("TransferList/qt5/HeaderState", state);
|
||||
}
|
||||
|
||||
bool Preferences::getRegexAsFilteringPattern() const
|
||||
bool Preferences::getRegexAsFilteringPatternForTransferList() const
|
||||
{
|
||||
return value("TransferList/UseRegexAsFilteringPattern", false).toBool();
|
||||
}
|
||||
|
||||
void Preferences::setRegexAsFilteringPattern(const bool checked)
|
||||
void Preferences::setRegexAsFilteringPatternForTransferList(const bool checked)
|
||||
{
|
||||
setValue("TransferList/UseRegexAsFilteringPattern", checked);
|
||||
}
|
||||
@@ -1553,8 +1427,8 @@ void Preferences::setToolbarTextPosition(const int position)
|
||||
QList<QNetworkCookie> Preferences::getNetworkCookies() const
|
||||
{
|
||||
QList<QNetworkCookie> cookies;
|
||||
QStringList rawCookies = value("Network/Cookies").toStringList();
|
||||
foreach (const QString &rawCookie, rawCookies)
|
||||
const QStringList rawCookies = value("Network/Cookies").toStringList();
|
||||
for (const QString &rawCookie : rawCookies)
|
||||
cookies << QNetworkCookie::parseCookies(rawCookie.toUtf8());
|
||||
|
||||
return cookies;
|
||||
@@ -1563,12 +1437,22 @@ QList<QNetworkCookie> Preferences::getNetworkCookies() const
|
||||
void Preferences::setNetworkCookies(const QList<QNetworkCookie> &cookies)
|
||||
{
|
||||
QStringList rawCookies;
|
||||
foreach (const QNetworkCookie &cookie, cookies)
|
||||
for (const QNetworkCookie &cookie : cookies)
|
||||
rawCookies << cookie.toRawForm();
|
||||
|
||||
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();
|
||||
@@ -1594,10 +1478,10 @@ void Preferences::upgrade()
|
||||
{
|
||||
SettingsStorage *settingsStorage = SettingsStorage::instance();
|
||||
|
||||
QStringList labels = value("TransferListFilters/customLabels").toStringList();
|
||||
const QStringList labels = value("TransferListFilters/customLabels").toStringList();
|
||||
if (!labels.isEmpty()) {
|
||||
QVariantMap categories = value("BitTorrent/Session/Categories").toMap();
|
||||
foreach (const QString &label, labels) {
|
||||
for (const QString &label : labels) {
|
||||
if (!categories.contains(label))
|
||||
categories[label] = "";
|
||||
}
|
||||
|
||||
@@ -201,6 +201,8 @@ public:
|
||||
void setWebUiClickjackingProtectionEnabled(bool enabled);
|
||||
bool isWebUiCSRFProtectionEnabled() const;
|
||||
void setWebUiCSRFProtectionEnabled(bool enabled);
|
||||
bool isWebUIHostHeaderValidationEnabled() const;
|
||||
void setWebUIHostHeaderValidationEnabled(bool enabled);
|
||||
|
||||
// HTTPS
|
||||
bool isWebUiHttpsEnabled() const;
|
||||
@@ -259,7 +261,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();
|
||||
@@ -298,7 +299,7 @@ public:
|
||||
void setCloseToTrayNotified(bool b);
|
||||
TrayIcon::Style trayIconStyle() const;
|
||||
void setTrayIconStyle(TrayIcon::Style style);
|
||||
#endif
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
// Stuff that don't appear in the Options GUI but are saved
|
||||
// in the same file.
|
||||
@@ -342,6 +343,8 @@ public:
|
||||
void setRssMainSplitterState(const QByteArray &state);
|
||||
QByteArray getSearchTabHeaderState() const;
|
||||
void setSearchTabHeaderState(const QByteArray &state);
|
||||
bool getRegexAsFilteringPatternForSearchJob() const;
|
||||
void setRegexAsFilteringPatternForSearchJob(bool checked);
|
||||
QStringList getSearchEngDisabled() const;
|
||||
void setSearchEngDisabled(const QStringList &engines);
|
||||
QString getTorImportLastContentDir() const;
|
||||
@@ -356,8 +359,8 @@ public:
|
||||
void setTransSelFilter(const int &index);
|
||||
QByteArray getTransHeaderState() const;
|
||||
void setTransHeaderState(const QByteArray &state);
|
||||
bool getRegexAsFilteringPattern() const;
|
||||
void setRegexAsFilteringPattern(bool checked);
|
||||
bool getRegexAsFilteringPatternForTransferList() const;
|
||||
void setRegexAsFilteringPatternForTransferList(bool checked);
|
||||
int getToolbarTextPosition() const;
|
||||
void setToolbarTextPosition(const int position);
|
||||
|
||||
@@ -370,6 +373,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;
|
||||
|
||||
@@ -435,13 +435,13 @@ namespace
|
||||
if (leapSecond)
|
||||
second = 59; // apparently a leap second - validate below, once time zone is known
|
||||
int month = 0;
|
||||
for ( ; (month < 12) && (parts[nmonth] != shortMonth[month]); ++month);
|
||||
for ( ; (month < 12) && (parts[nmonth] != shortMonth[month]); ++month);
|
||||
int dayOfWeek = -1;
|
||||
if (!parts[nwday].isEmpty()) {
|
||||
// Look up the weekday name
|
||||
while (++dayOfWeek < 7 && (shortDay[dayOfWeek] != parts[nwday]));
|
||||
while ((++dayOfWeek < 7) && (shortDay[dayOfWeek] != parts[nwday]));
|
||||
if (dayOfWeek >= 7)
|
||||
for (dayOfWeek = 0; dayOfWeek < 7 && (longDay[dayOfWeek] != parts[nwday]); ++dayOfWeek);
|
||||
for (dayOfWeek = 0; (dayOfWeek < 7) && (longDay[dayOfWeek] != parts[nwday]); ++dayOfWeek);
|
||||
}
|
||||
|
||||
// if (month >= 12 || dayOfWeek >= 7
|
||||
@@ -450,7 +450,7 @@ namespace
|
||||
int i = parts[nyear].size();
|
||||
if (i < 4) {
|
||||
// It's an obsolete year specification with less than 4 digits
|
||||
year += (i == 2 && year < 50) ? 2000 : 1900;
|
||||
year += ((i == 2) && (year < 50)) ? 2000 : 1900;
|
||||
}
|
||||
|
||||
// Parse the UTC offset part
|
||||
@@ -473,17 +473,17 @@ namespace
|
||||
else {
|
||||
// Check for an obsolete time zone name
|
||||
QByteArray zone = parts[10].toLatin1();
|
||||
if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J') {
|
||||
if ((zone.length() == 1) && (isalpha(zone[0])) && (toupper(zone[0]) != 'J')) {
|
||||
negOffset = true; // military zone: RFC 2822 treats as '-0000'
|
||||
}
|
||||
else if (zone != "UT" && zone != "GMT") { // treated as '+0000'
|
||||
else if ((zone != "UT") && (zone != "GMT")) { // treated as '+0000'
|
||||
offset = (zone == "EDT")
|
||||
? -4 * 3600
|
||||
: ((zone == "EST") || (zone == "CDT"))
|
||||
? -5 * 3600
|
||||
: ((zone == "CST") || (zone == "MDT"))
|
||||
? -6 * 3600
|
||||
: (zone == "MST" || zone == "PDT")
|
||||
: ((zone == "MST") || (zone == "PDT"))
|
||||
? -7 * 3600
|
||||
: (zone == "PST")
|
||||
? -8 * 3600
|
||||
@@ -502,12 +502,12 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
QDate qdate(year, month + 1, day); // convert date, and check for out-of-range
|
||||
if (!qdate.isValid())
|
||||
QDate qDate(year, month + 1, day); // convert date, and check for out-of-range
|
||||
if (!qDate.isValid())
|
||||
return QDateTime::currentDateTime();
|
||||
|
||||
QTime qTime(hour, minute, second);
|
||||
QDateTime result(qdate, qTime, Qt::UTC);
|
||||
QDateTime result(qDate, qTime, Qt::UTC);
|
||||
if (offset)
|
||||
result = result.addSecs(-offset);
|
||||
if (!result.isValid())
|
||||
@@ -582,24 +582,16 @@ 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
|
||||
m_articleIDs.clear();
|
||||
}
|
||||
|
||||
void Parser::parseRssArticle(QXmlStreamReader &xml)
|
||||
{
|
||||
QVariantHash article;
|
||||
QString altTorrentUrl;
|
||||
|
||||
while (!xml.atEnd()) {
|
||||
xml.readNext();
|
||||
@@ -615,6 +607,8 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
|
||||
else if (name == QLatin1String("enclosure")) {
|
||||
if (xml.attributes().value("type") == QLatin1String("application/x-bittorrent"))
|
||||
article[Article::KeyTorrentURL] = xml.attributes().value(QLatin1String("url")).toString();
|
||||
else if (xml.attributes().value("type").isEmpty())
|
||||
altTorrentUrl = xml.attributes().value(QLatin1String("url")).toString();
|
||||
}
|
||||
else if (name == QLatin1String("link")) {
|
||||
const QString text {xml.readElementText().trimmed()};
|
||||
@@ -641,7 +635,10 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
|
||||
}
|
||||
}
|
||||
|
||||
m_result.articles.prepend(article);
|
||||
if (article[Article::KeyTorrentURL].toString().isEmpty())
|
||||
article[Article::KeyTorrentURL] = altTorrentUrl;
|
||||
|
||||
addArticle(article);
|
||||
}
|
||||
|
||||
void Parser::parseRSSChannel(QXmlStreamReader &xml)
|
||||
@@ -736,7 +733,7 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
|
||||
}
|
||||
}
|
||||
|
||||
m_result.articles.prepend(article);
|
||||
addArticle(article);
|
||||
}
|
||||
|
||||
void Parser::parseAtomChannel(QXmlStreamReader &xml)
|
||||
@@ -766,3 +763,34 @@ void Parser::parseAtomChannel(QXmlStreamReader &xml)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::addArticle(QVariantHash article)
|
||||
{
|
||||
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()) {
|
||||
// The article could not be uniquely identified
|
||||
// since it has no appropriate data.
|
||||
// Just ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_articleIDs.contains(localId.toString())) {
|
||||
// The article could not be uniquely identified
|
||||
// since the Feed has duplicate identifiers.
|
||||
// Just ignore it.
|
||||
return;
|
||||
}
|
||||
|
||||
m_articleIDs.insert(localId.toString());
|
||||
m_result.articles.prepend(article);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QVariantHash>
|
||||
|
||||
@@ -65,9 +66,11 @@ namespace RSS
|
||||
void parseRSSChannel(QXmlStreamReader &xml);
|
||||
void parseAtomArticle(QXmlStreamReader &xml);
|
||||
void parseAtomChannel(QXmlStreamReader &xml);
|
||||
void addArticle(QVariantHash article);
|
||||
|
||||
QString m_baseUrl;
|
||||
ParsingResult m_result;
|
||||
QSet<QString> m_articleIDs;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -240,7 +241,7 @@ void AutoDownloader::importRules(const QByteArray &data, AutoDownloader::RulesFi
|
||||
QByteArray AutoDownloader::exportRulesToJSONFormat() const
|
||||
{
|
||||
QJsonObject jsonObj;
|
||||
for (const auto &rule : copyAsConst(rules()))
|
||||
for (const auto &rule : asConst(rules()))
|
||||
jsonObj.insert(rule.name(), rule.toJsonObject());
|
||||
|
||||
return QJsonDocument(jsonObj).toJson();
|
||||
@@ -248,14 +249,14 @@ QByteArray AutoDownloader::exportRulesToJSONFormat() const
|
||||
|
||||
void AutoDownloader::importRulesFromJSONFormat(const QByteArray &data)
|
||||
{
|
||||
for (const auto &rule : copyAsConst(rulesFromJSON(data)))
|
||||
for (const auto &rule : asConst(rulesFromJSON(data)))
|
||||
insertRule(rule);
|
||||
}
|
||||
|
||||
QByteArray AutoDownloader::exportRulesToLegacyFormat() const
|
||||
{
|
||||
QVariantHash dict;
|
||||
for (const auto &rule : copyAsConst(rules()))
|
||||
for (const auto &rule : asConst(rules()))
|
||||
dict[rule.name()] = rule.toLegacyDict();
|
||||
|
||||
QByteArray data;
|
||||
@@ -275,7 +276,7 @@ void AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data)
|
||||
if (in.status() != QDataStream::Ok)
|
||||
throw ParsingError(tr("Invalid data format"));
|
||||
|
||||
for (const QVariant &val : qAsConst(dict))
|
||||
for (const QVariant &val : asConst(dict))
|
||||
insertRule(AutoDownloadRule::fromLegacyDict(val.toHash()));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -362,7 +373,7 @@ void AutoDownloader::addJobForArticle(Article *article)
|
||||
|
||||
void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
|
||||
{
|
||||
for (AutoDownloadRule &rule: m_rules) {
|
||||
for (AutoDownloadRule &rule : m_rules) {
|
||||
if (!rule.isEnabled()) continue;
|
||||
if (!rule.feedURLs().contains(job->feedURL)) continue;
|
||||
if (!rule.accepts(job->articleData)) continue;
|
||||
@@ -424,8 +435,8 @@ void AutoDownloader::loadRules(const QByteArray &data)
|
||||
void AutoDownloader::loadRulesLegacy()
|
||||
{
|
||||
SettingsPtr settings = Profile::instance().applicationSettings(QStringLiteral("qBittorrent-rss"));
|
||||
QVariantHash rules = settings->value(QStringLiteral("download_rules")).toHash();
|
||||
foreach (const QVariant &ruleVar, rules) {
|
||||
const QVariantHash rules = settings->value(QStringLiteral("download_rules")).toHash();
|
||||
for (const QVariant &ruleVar : rules) {
|
||||
auto rule = AutoDownloadRule::fromLegacyDict(ruleVar.toHash());
|
||||
if (!rule.name().isEmpty())
|
||||
insertRule(rule);
|
||||
@@ -440,7 +451,7 @@ void AutoDownloader::store()
|
||||
m_savingTimer.stop();
|
||||
|
||||
QJsonObject jsonObj;
|
||||
foreach (auto rule, m_rules)
|
||||
for (const auto &rule : asConst(m_rules))
|
||||
jsonObj.insert(rule.name(), rule.toJsonObject());
|
||||
|
||||
m_fileStorage->store(RulesFileName, QJsonDocument(jsonObj).toJson());
|
||||
@@ -462,7 +473,7 @@ void AutoDownloader::resetProcessingQueue()
|
||||
m_processingQueue.clear();
|
||||
if (!m_processingEnabled) return;
|
||||
|
||||
foreach (Article *article, Session::instance()->rootFolder()->articles()) {
|
||||
for (Article *article : asConst(Session::instance()->rootFolder()->articles())) {
|
||||
if (!article->isRead() && !article->torrentUrl().isEmpty())
|
||||
addJobForArticle(article);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace RSS
|
||||
bool smartFilter = false;
|
||||
QStringList previouslyMatchedEpisodes;
|
||||
|
||||
mutable QString lastComputedEpisode;
|
||||
mutable QStringList lastComputedEpisodes;
|
||||
mutable QHash<QString, QRegularExpression> cachedRegexes;
|
||||
|
||||
bool operator==(const AutoDownloadRuleData &other) const
|
||||
@@ -237,7 +237,7 @@ bool AutoDownloadRule::matchesMustContainExpression(const QString &articleTitle)
|
||||
|
||||
// Each expression is either a regex, or a set of wildcards separated by whitespace.
|
||||
// Accept if any complete expression matches.
|
||||
for (const QString &expression : qAsConst(m_dataPtr->mustContain)) {
|
||||
for (const QString &expression : asConst(m_dataPtr->mustContain)) {
|
||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||
if (matchesExpression(articleTitle, expression))
|
||||
return true;
|
||||
@@ -246,14 +246,14 @@ bool AutoDownloadRule::matchesMustContainExpression(const QString &articleTitle)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::matchesMustNotContainExpression(const QString& articleTitle) const
|
||||
bool AutoDownloadRule::matchesMustNotContainExpression(const QString &articleTitle) const
|
||||
{
|
||||
if (m_dataPtr->mustNotContain.empty())
|
||||
return true;
|
||||
|
||||
// Each expression is either a regex, or a set of wildcards separated by whitespace.
|
||||
// Reject if any complete expression matches.
|
||||
for (const QString &expression : qAsConst(m_dataPtr->mustNotContain)) {
|
||||
for (const QString &expression : asConst(m_dataPtr->mustNotContain)) {
|
||||
// A regex of the form "expr|" will always match, so do the same for wildcards
|
||||
if (matchesExpression(articleTitle, expression))
|
||||
return false;
|
||||
@@ -262,10 +262,10 @@ bool AutoDownloadRule::matchesMustNotContainExpression(const QString& articleTit
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString& articleTitle) const
|
||||
bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitle) const
|
||||
{
|
||||
// Reset the lastComputedEpisode, we don't want to leak it between matches
|
||||
m_dataPtr->lastComputedEpisode.clear();
|
||||
m_dataPtr->lastComputedEpisodes.clear();
|
||||
|
||||
if (m_dataPtr->episodeFilter.isEmpty())
|
||||
return true;
|
||||
@@ -332,7 +332,7 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString& articleTitl
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString& articleTitle) const
|
||||
bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString &articleTitle) const
|
||||
{
|
||||
if (!useSmartFilter())
|
||||
return true;
|
||||
@@ -343,11 +343,35 @@ 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)
|
||||
return false;
|
||||
if (previouslyMatched) {
|
||||
if (!AutoDownloader::instance()->downloadRepacks())
|
||||
return false;
|
||||
|
||||
m_dataPtr->lastComputedEpisode = episodeStr;
|
||||
// Now see if we've downloaded this particular repack/proper combination
|
||||
const bool isRepack = articleTitle.contains("REPACK", Qt::CaseInsensitive);
|
||||
const bool isProper = articleTitle.contains("PROPER", Qt::CaseInsensitive);
|
||||
|
||||
if (!isRepack && !isProper)
|
||||
return false;
|
||||
|
||||
const QString fullEpisodeStr = QString("%1%2%3").arg(episodeStr,
|
||||
isRepack ? "-REPACK" : "",
|
||||
isProper ? "-PROPER" : "");
|
||||
const bool previouslyMatchedFull = m_dataPtr->previouslyMatchedEpisodes.contains(fullEpisodeStr);
|
||||
if (previouslyMatchedFull)
|
||||
return false;
|
||||
|
||||
m_dataPtr->lastComputedEpisodes.append(fullEpisodeStr);
|
||||
|
||||
// If this is a REPACK and PROPER download, add the individual entries to the list
|
||||
// so we don't download those
|
||||
if (isRepack && isProper) {
|
||||
m_dataPtr->lastComputedEpisodes.append(QString("%1-REPACK").arg(episodeStr));
|
||||
m_dataPtr->lastComputedEpisodes.append(QString("%1-PROPER").arg(episodeStr));
|
||||
}
|
||||
}
|
||||
|
||||
m_dataPtr->lastComputedEpisodes.append(episodeStr);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -379,10 +403,10 @@ bool AutoDownloadRule::accepts(const QVariantHash &articleData)
|
||||
|
||||
setLastMatch(articleData[Article::KeyDate].toDateTime());
|
||||
|
||||
if (!m_dataPtr->lastComputedEpisode.isEmpty()) {
|
||||
// TODO: probably need to add a marker for PROPER/REPACK to avoid duplicate downloads
|
||||
m_dataPtr->previouslyMatchedEpisodes.append(m_dataPtr->lastComputedEpisode);
|
||||
m_dataPtr->lastComputedEpisode.clear();
|
||||
// If there's a matched episode string, add that to the previously matched list
|
||||
if (!m_dataPtr->lastComputedEpisodes.isEmpty()) {
|
||||
m_dataPtr->previouslyMatchedEpisodes.append(m_dataPtr->lastComputedEpisodes);
|
||||
m_dataPtr->lastComputedEpisodes.clear();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -442,7 +466,7 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
|
||||
QStringList feedURLs;
|
||||
if (feedsVal.isString())
|
||||
feedURLs << feedsVal.toString();
|
||||
else foreach (const QJsonValue &urlVal, feedsVal.toArray())
|
||||
else for (const QJsonValue &urlVal : asConst(feedsVal.toArray()))
|
||||
feedURLs << urlVal.toString();
|
||||
rule.setFeedURLs(feedURLs);
|
||||
|
||||
@@ -452,7 +476,7 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
|
||||
previouslyMatched << previouslyMatchedVal.toString();
|
||||
}
|
||||
else {
|
||||
foreach (const QJsonValue &val, previouslyMatchedVal.toArray())
|
||||
for (const QJsonValue &val : asConst(previouslyMatchedVal.toArray()))
|
||||
previouslyMatched << val.toString();
|
||||
}
|
||||
rule.setPreviouslyMatchedEpisodes(previouslyMatched);
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
|
||||
#include "rss_feed.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
@@ -106,7 +109,7 @@ QList<Article *> Feed::articles() const
|
||||
void Feed::markAsRead()
|
||||
{
|
||||
auto oldUnreadCount = m_unreadCount;
|
||||
foreach (Article *article, m_articles) {
|
||||
for (Article *article : asConst(m_articles)) {
|
||||
if (!article->isRead()) {
|
||||
article->disconnect(this);
|
||||
article->markAsRead();
|
||||
@@ -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);
|
||||
@@ -296,9 +282,9 @@ void Feed::loadArticles(const QByteArray &data)
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray jsonArr = jsonDoc.array();
|
||||
const QJsonArray jsonArr = jsonDoc.array();
|
||||
int i = -1;
|
||||
foreach (const QJsonValue &jsonVal, jsonArr) {
|
||||
for (const QJsonValue &jsonVal : jsonArr) {
|
||||
++i;
|
||||
if (!jsonVal.isObject()) {
|
||||
LogMsg(tr("Couldn't load RSS article '%1#%2'. Invalid data format.").arg(m_url).arg(i)
|
||||
@@ -318,9 +304,9 @@ void Feed::loadArticles(const QByteArray &data)
|
||||
void Feed::loadArticlesLegacy()
|
||||
{
|
||||
SettingsPtr qBTRSSFeeds = Profile::instance().applicationSettings(QStringLiteral("qBittorrent-rss-feeds"));
|
||||
QVariantHash allOldItems = qBTRSSFeeds->value("old_items").toHash();
|
||||
const QVariantHash allOldItems = qBTRSSFeeds->value("old_items").toHash();
|
||||
|
||||
foreach (const QVariant &var, allOldItems.value(m_url).toList()) {
|
||||
for (const QVariant &var : asConst(allOldItems.value(m_url).toList())) {
|
||||
auto hash = var.toHash();
|
||||
// update legacy keys
|
||||
hash[Article::KeyLink] = hash.take(QLatin1String("news_link"));
|
||||
@@ -343,7 +329,7 @@ void Feed::store()
|
||||
m_savingTimer.stop();
|
||||
|
||||
QJsonArray jsonArr;
|
||||
foreach (Article *article, m_articles)
|
||||
for (Article *article :asConst(m_articles))
|
||||
jsonArr << article->toJsonObject();
|
||||
|
||||
m_session->dataFileStorage()->store(m_dataFileName, QJsonDocument(jsonArr).toJson());
|
||||
@@ -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,71 @@ 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) {
|
||||
// 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(article[Article::KeyId].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() > static_cast<uint>(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;
|
||||
@@ -442,7 +493,7 @@ QJsonValue Feed::toJsonValue(bool withData) const
|
||||
jsonObj.insert(KEY_HASERROR, hasError());
|
||||
|
||||
QJsonArray jsonArr;
|
||||
for (Article *article : qAsConst(m_articles))
|
||||
for (Article *article : asConst(m_articles))
|
||||
jsonArr << article->toJsonObject();
|
||||
jsonObj.insert(KEY_ARTICLES, jsonArr);
|
||||
}
|
||||
|
||||
@@ -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,7 +47,7 @@ Folder::~Folder()
|
||||
{
|
||||
emit aboutToBeDestroyed(this);
|
||||
|
||||
foreach (auto item, items())
|
||||
for (auto item : asConst(items()))
|
||||
delete item;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ QList<Article *> Folder::articles() const
|
||||
{
|
||||
QList<Article *> news;
|
||||
|
||||
foreach (Item *item, items()) {
|
||||
for (Item *item : asConst(items())) {
|
||||
int n = news.size();
|
||||
news << item->articles();
|
||||
std::inplace_merge(news.begin(), news.begin() + n, news.end()
|
||||
@@ -70,20 +70,20 @@ QList<Article *> Folder::articles() const
|
||||
int Folder::unreadCount() const
|
||||
{
|
||||
int count = 0;
|
||||
foreach (Item *item, items())
|
||||
for (Item *item : asConst(items()))
|
||||
count += item->unreadCount();
|
||||
return count;
|
||||
}
|
||||
|
||||
void Folder::markAsRead()
|
||||
{
|
||||
foreach (Item *item, items())
|
||||
for (Item *item : asConst(items()))
|
||||
item->markAsRead();
|
||||
}
|
||||
|
||||
void Folder::refresh()
|
||||
{
|
||||
foreach (Item *item, items())
|
||||
for (Item *item : asConst(items()))
|
||||
item->refresh();
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ QList<Item *> Folder::items() const
|
||||
QJsonValue Folder::toJsonValue(bool withData) const
|
||||
{
|
||||
QJsonObject jsonObj;
|
||||
foreach (Item *item, items())
|
||||
for (Item *item : asConst(items()))
|
||||
jsonObj.insert(item->name(), item->toJsonValue(withData));
|
||||
|
||||
return jsonObj;
|
||||
@@ -108,7 +108,7 @@ void Folder::handleItemUnreadCountChanged()
|
||||
|
||||
void Folder::cleanup()
|
||||
{
|
||||
foreach (Item *item, items())
|
||||
for (Item *item : asConst(items()))
|
||||
item->cleanup();
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ void Folder::addItem(Item *item)
|
||||
connect(item, &Item::articleAboutToBeRemoved, this, &Item::articleAboutToBeRemoved);
|
||||
connect(item, &Item::unreadCountChanged, this, &Folder::handleItemUnreadCountChanged);
|
||||
|
||||
for (auto article: copyAsConst(item->articles()))
|
||||
for (auto article : asConst(item->articles()))
|
||||
emit newArticle(article);
|
||||
|
||||
if (item->unreadCount() > 0)
|
||||
@@ -134,7 +134,7 @@ void Folder::removeItem(Item *item)
|
||||
{
|
||||
Q_ASSERT(m_items.contains(item));
|
||||
|
||||
for (auto article: copyAsConst(item->articles()))
|
||||
for (auto article : asConst(item->articles()))
|
||||
emit articleAboutToBeRemoved(article);
|
||||
|
||||
item->disconnect(this);
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include <QVariantHash>
|
||||
|
||||
#include "../asyncfilestorage.h"
|
||||
#include "../global.h"
|
||||
#include "../logger.h"
|
||||
#include "../profile.h"
|
||||
#include "../settingsstorage.h"
|
||||
@@ -283,7 +284,7 @@ void Session::load()
|
||||
void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
|
||||
{
|
||||
bool updated = false;
|
||||
foreach (const QString &key, jsonObj.keys()) {
|
||||
for (const QString &key : asConst(jsonObj.keys())) {
|
||||
const QJsonValue val {jsonObj[key]};
|
||||
if (val.isString()) {
|
||||
// previous format (reduced form) doesn't contain UID
|
||||
@@ -355,7 +356,7 @@ void Session::loadLegacy()
|
||||
const QString parentFolderPath = Item::parentPath(legacyPath);
|
||||
const QString feedUrl = Item::relativeName(legacyPath);
|
||||
|
||||
foreach (const QString &folderPath, Item::expandPath(parentFolderPath))
|
||||
for (const QString &folderPath : asConst(Item::expandPath(parentFolderPath)))
|
||||
addFolder(folderPath);
|
||||
|
||||
const QString feedPath = feedAliases[i].isEmpty()
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
#include "bittorrent/session.h"
|
||||
#include "filesystemwatcher.h"
|
||||
#include "global.h"
|
||||
#include "preferences.h"
|
||||
#include "utils/fs.h"
|
||||
|
||||
@@ -254,7 +255,7 @@ void ScanFoldersModel::addToFSWatcher(const QStringList &watchPaths)
|
||||
if (!m_fsWatcher)
|
||||
return; // addPath() wasn't called before this
|
||||
|
||||
foreach (const QString &path, watchPaths) {
|
||||
for (const QString &path : watchPaths) {
|
||||
QDir watchDir(path);
|
||||
const QString canonicalWatchPath = watchDir.canonicalPath();
|
||||
m_fsWatcher->addPath(canonicalWatchPath);
|
||||
@@ -282,7 +283,7 @@ bool ScanFoldersModel::removePath(const QString &path, bool removeFromFSWatcher)
|
||||
|
||||
void ScanFoldersModel::removeFromFSWatcher(const QStringList &watchPaths)
|
||||
{
|
||||
foreach (const QString &path, watchPaths)
|
||||
for (const QString &path : watchPaths)
|
||||
m_fsWatcher->removePath(path);
|
||||
}
|
||||
|
||||
@@ -326,7 +327,7 @@ void ScanFoldersModel::makePersistent()
|
||||
{
|
||||
QVariantHash dirs;
|
||||
|
||||
foreach (const PathData *pathData, m_pathList) {
|
||||
for (const PathData *pathData : asConst(m_pathList)) {
|
||||
if (pathData->downloadType == CUSTOM_LOCATION)
|
||||
dirs.insert(Utils::Fs::fromNativePath(pathData->watchPath), Utils::Fs::fromNativePath(pathData->downloadPath));
|
||||
else
|
||||
@@ -350,7 +351,7 @@ void ScanFoldersModel::configure()
|
||||
|
||||
void ScanFoldersModel::addTorrentsToSession(const QStringList &pathList)
|
||||
{
|
||||
foreach (const QString &file, pathList) {
|
||||
for (const QString &file : pathList) {
|
||||
qDebug("File %s added", qUtf8Printable(file));
|
||||
|
||||
BitTorrent::AddTorrentParams params;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <QProcess>
|
||||
#include <QTimer>
|
||||
|
||||
#include "../global.h"
|
||||
#include "../utils/foreignapps.h"
|
||||
#include "../utils/fs.h"
|
||||
#include "searchpluginmanager.h"
|
||||
@@ -138,7 +139,7 @@ void SearchHandler::readSearchOutput()
|
||||
m_searchResultLineTruncated = lines.takeLast().trimmed();
|
||||
|
||||
QList<SearchResult> searchResultList;
|
||||
foreach (const QByteArray &line, lines) {
|
||||
for (const QByteArray &line : asConst(lines)) {
|
||||
SearchResult searchResult;
|
||||
if (parseSearchResult(QString::fromUtf8(line), searchResult))
|
||||
searchResultList << searchResult;
|
||||
|
||||
@@ -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"
|
||||
@@ -64,7 +65,7 @@ namespace
|
||||
while (iter.hasNext())
|
||||
dirs += iter.next();
|
||||
|
||||
for (const QString &dir : qAsConst(dirs)) {
|
||||
for (const QString &dir : asConst(dirs)) {
|
||||
// python 3: remove "__pycache__" folders
|
||||
if (dir.endsWith("/__pycache__")) {
|
||||
Utils::Fs::removeDirRecursive(dir);
|
||||
@@ -73,7 +74,7 @@ namespace
|
||||
|
||||
// python 2: remove "*.pyc" files
|
||||
const QStringList files = QDir(dir).entryList(QDir::Files);
|
||||
for (const QString file : files) {
|
||||
for (const QString &file : files) {
|
||||
if (file.endsWith(".pyc"))
|
||||
Utils::Fs::forceRemove(file);
|
||||
}
|
||||
@@ -100,9 +101,17 @@ SearchPluginManager::~SearchPluginManager()
|
||||
|
||||
SearchPluginManager *SearchPluginManager::instance()
|
||||
{
|
||||
if (!m_instance)
|
||||
m_instance = new SearchPluginManager;
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
void SearchPluginManager::freeInstance()
|
||||
{
|
||||
if (m_instance)
|
||||
delete m_instance;
|
||||
}
|
||||
|
||||
QStringList SearchPluginManager::allPlugins() const
|
||||
{
|
||||
return m_plugins.keys();
|
||||
@@ -111,7 +120,7 @@ QStringList SearchPluginManager::allPlugins() const
|
||||
QStringList SearchPluginManager::enabledPlugins() const
|
||||
{
|
||||
QStringList plugins;
|
||||
for (const PluginInfo *plugin : qAsConst(m_plugins)) {
|
||||
for (const PluginInfo *plugin : asConst(m_plugins)) {
|
||||
if (plugin->enabled)
|
||||
plugins << plugin->name;
|
||||
}
|
||||
@@ -122,9 +131,9 @@ QStringList SearchPluginManager::enabledPlugins() const
|
||||
QStringList SearchPluginManager::supportedCategories() const
|
||||
{
|
||||
QStringList result;
|
||||
for (const PluginInfo *plugin : qAsConst(m_plugins)) {
|
||||
for (const PluginInfo *plugin : asConst(m_plugins)) {
|
||||
if (plugin->enabled) {
|
||||
foreach (QString cat, plugin->supportedCategories) {
|
||||
for (const QString &cat : plugin->supportedCategories) {
|
||||
if (!result.contains(cat))
|
||||
result << cat;
|
||||
}
|
||||
@@ -145,8 +154,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 : asConst(plugins)) {
|
||||
const PluginInfo *plugin = pluginInfo(name);
|
||||
if (!plugin) continue; // plugin wasn't found
|
||||
for (const QString &category : plugin->supportedCategories)
|
||||
categories << category;
|
||||
@@ -187,8 +196,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 +222,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 +247,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 +262,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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,9 +277,8 @@ bool SearchPluginManager::uninstallPlugin(const QString &name)
|
||||
QDir pluginsFolder(pluginsLocation());
|
||||
QStringList filters;
|
||||
filters << name + ".*";
|
||||
QStringList files = pluginsFolder.entryList(filters, QDir::Files, QDir::Unsorted);
|
||||
QString file;
|
||||
foreach (file, files)
|
||||
const QStringList files = pluginsFolder.entryList(filters, QDir::Files, QDir::Unsorted);
|
||||
for (const QString &file : files)
|
||||
Utils::Fs::forceRemove(pluginsFolder.absoluteFilePath(file));
|
||||
// Remove it from supported engines
|
||||
delete m_plugins.take(name);
|
||||
@@ -494,37 +501,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 +540,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ public:
|
||||
~SearchPluginManager() override;
|
||||
|
||||
static SearchPluginManager *instance();
|
||||
static void freeInstance();
|
||||
|
||||
QStringList allPlugins() const;
|
||||
QStringList enabledPlugins() const;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <QFile>
|
||||
#include <QHash>
|
||||
|
||||
#include "global.h"
|
||||
#include "logger.h"
|
||||
#include "profile.h"
|
||||
#include "utils/fs.h"
|
||||
@@ -286,7 +287,7 @@ QString TransactionalSettings::deserialize(const QString &name, QVariantHash &da
|
||||
// Copy everything into memory. This means even keys inserted in the file manually
|
||||
// or that we don't touch directly in this code (eg disabled by ifdef). This ensures
|
||||
// that they will be copied over when save our settings to disk.
|
||||
foreach (const QString &key, settings->allKeys())
|
||||
for (const QString &key : asConst(settings->allKeys()))
|
||||
data.insert(key, settings->value(key));
|
||||
|
||||
return settings->fileName();
|
||||
|
||||
@@ -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")
|
||||
@@ -36,8 +38,6 @@
|
||||
// See issue #3059 for more details (https://github.com/qbittorrent/qBittorrent/issues/3059).
|
||||
const char C_INFINITY[] = "∞";
|
||||
const char C_NON_BREAKING_SPACE[] = " ";
|
||||
const char C_UP[] = "▲";
|
||||
const char C_DOWN[] = "▼";
|
||||
const char C_COPYRIGHT[] = "©";
|
||||
const char C_THIN_SPACE[] = " ";
|
||||
const char C_UTP[] = "μTP";
|
||||
|
||||
@@ -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 // Q_OS_WIN
|
||||
}
|
||||
|
||||
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>
|
||||
@@ -44,7 +48,7 @@
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <Windows.h>
|
||||
#elif defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
|
||||
#elif defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
|
||||
#include <sys/param.h>
|
||||
#include <sys/mount.h>
|
||||
#elif defined(Q_OS_HAIKU)
|
||||
@@ -129,7 +133,7 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
|
||||
std::sort(dirList.begin(), dirList.end()
|
||||
, [](const QString &l, const QString &r) { return l.count('/') > r.count('/'); });
|
||||
|
||||
for (const QString &p : qAsConst(dirList)) {
|
||||
for (const QString &p : asConst(dirList)) {
|
||||
// remove unwanted files
|
||||
for (const QString &f : deleteFilesList) {
|
||||
forceRemove(p + f);
|
||||
@@ -137,7 +141,7 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
|
||||
|
||||
// remove temp files on linux (file ends with '~'), e.g. `filename~`
|
||||
QDir dir(p);
|
||||
QStringList tmpFileList = dir.entryList(QDir::Files);
|
||||
const QStringList tmpFileList = dir.entryList(QDir::Files);
|
||||
for (const QString &f : tmpFileList) {
|
||||
if (f.endsWith('~'))
|
||||
forceRemove(p + f);
|
||||
@@ -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;
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// 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;
|
||||
#else // Q_OS_WIN
|
||||
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));
|
||||
#endif
|
||||
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 // Q_OS_WIN
|
||||
}
|
||||
#endif
|
||||
#endif // Q_OS_HAIKU
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
||||
#include "base/utils/version.h"
|
||||
#endif
|
||||
#endif
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "base/unicodestrings.h"
|
||||
@@ -84,6 +84,27 @@ namespace
|
||||
QT_TRANSLATE_NOOP3("misc", "PiB", "pebibytes (1024 tebibytes)"),
|
||||
QT_TRANSLATE_NOOP3("misc", "EiB", "exbibytes (1024 pebibytes)")
|
||||
};
|
||||
|
||||
// return best userfriendly storage unit (B, KiB, MiB, GiB, TiB, ...)
|
||||
// use Binary prefix standards from IEC 60027-2
|
||||
// see http://en.wikipedia.org/wiki/Kilobyte
|
||||
// value must be given in bytes
|
||||
// to send numbers instead of strings with suffixes
|
||||
bool splitToFriendlyUnit(const qint64 sizeInBytes, qreal &val, Utils::Misc::SizeUnit &unit)
|
||||
{
|
||||
if (sizeInBytes < 0) return false;
|
||||
|
||||
int i = 0;
|
||||
qreal rawVal = static_cast<qreal>(sizeInBytes);
|
||||
|
||||
while ((rawVal >= 1024.) && (i <= static_cast<int>(Utils::Misc::SizeUnit::ExbiByte))) {
|
||||
rawVal /= 1024.;
|
||||
++i;
|
||||
}
|
||||
val = rawVal;
|
||||
unit = static_cast<Utils::Misc::SizeUnit>(i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
|
||||
@@ -237,52 +258,30 @@ QPoint Utils::Misc::screenCenter(const QWidget *w)
|
||||
}
|
||||
#endif
|
||||
|
||||
QString Utils::Misc::unitString(Utils::Misc::SizeUnit unit)
|
||||
QString Utils::Misc::unitString(const SizeUnit unit, const bool isSpeed)
|
||||
{
|
||||
return QCoreApplication::translate("misc",
|
||||
units[static_cast<int>(unit)].source, units[static_cast<int>(unit)].comment);
|
||||
}
|
||||
|
||||
// return best userfriendly storage unit (B, KiB, MiB, GiB, TiB, ...)
|
||||
// use Binary prefix standards from IEC 60027-2
|
||||
// see http://en.wikipedia.org/wiki/Kilobyte
|
||||
// value must be given in bytes
|
||||
// to send numbers instead of strings with suffixes
|
||||
bool Utils::Misc::friendlyUnit(qint64 sizeInBytes, qreal &val, Utils::Misc::SizeUnit &unit)
|
||||
{
|
||||
if (sizeInBytes < 0) return false;
|
||||
|
||||
int i = 0;
|
||||
qreal rawVal = static_cast<qreal>(sizeInBytes);
|
||||
|
||||
while ((rawVal >= 1024.) && (i <= static_cast<int>(SizeUnit::ExbiByte))) {
|
||||
rawVal /= 1024.;
|
||||
++i;
|
||||
}
|
||||
val = rawVal;
|
||||
unit = static_cast<SizeUnit>(i);
|
||||
return true;
|
||||
const auto &unitString = units[static_cast<int>(unit)];
|
||||
QString ret = QCoreApplication::translate("misc", unitString.source, unitString.comment);
|
||||
if (isSpeed)
|
||||
ret += QCoreApplication::translate("misc", "/s", "per second");
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString Utils::Misc::friendlyUnit(qint64 bytesValue, bool isSpeed)
|
||||
{
|
||||
SizeUnit unit;
|
||||
qreal friendlyVal;
|
||||
if (!friendlyUnit(bytesValue, friendlyVal, unit))
|
||||
if (!splitToFriendlyUnit(bytesValue, friendlyVal, unit))
|
||||
return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
|
||||
QString ret;
|
||||
if (unit == SizeUnit::Byte)
|
||||
ret = QString::number(bytesValue) + QString::fromUtf8(C_NON_BREAKING_SPACE) + unitString(unit);
|
||||
else
|
||||
ret = Utils::String::fromDouble(friendlyVal, friendlyUnitPrecision(unit)) + QString::fromUtf8(C_NON_BREAKING_SPACE) + unitString(unit);
|
||||
if (isSpeed)
|
||||
ret += QCoreApplication::translate("misc", "/s", "per second");
|
||||
return ret;
|
||||
return Utils::String::fromDouble(friendlyVal, friendlyUnitPrecision(unit))
|
||||
+ QString::fromUtf8(C_NON_BREAKING_SPACE)
|
||||
+ unitString(unit, isSpeed);
|
||||
}
|
||||
|
||||
int Utils::Misc::friendlyUnitPrecision(SizeUnit unit)
|
||||
{
|
||||
// friendlyUnit's number of digits after the decimal point
|
||||
if (unit == SizeUnit::Byte) return 0;
|
||||
if (unit <= SizeUnit::MebiByte) return 1;
|
||||
else if (unit == SizeUnit::GibiByte) return 2;
|
||||
else return 3;
|
||||
@@ -335,6 +334,7 @@ bool Utils::Misc::isPreviewable(const QString &extension)
|
||||
"RMVB",
|
||||
"SWA",
|
||||
"SWF",
|
||||
"TS",
|
||||
"VOB",
|
||||
"WAV",
|
||||
"WMA",
|
||||
@@ -391,7 +391,7 @@ QString Utils::Misc::getUserIDString()
|
||||
QStringList Utils::Misc::toStringList(const QList<bool> &l)
|
||||
{
|
||||
QStringList ret;
|
||||
foreach (const bool &b, l)
|
||||
for (const bool b : l)
|
||||
ret << (b ? "1" : "0");
|
||||
return ret;
|
||||
}
|
||||
@@ -399,7 +399,7 @@ QStringList Utils::Misc::toStringList(const QList<bool> &l)
|
||||
QList<int> Utils::Misc::intListfromStringList(const QStringList &l)
|
||||
{
|
||||
QList<int> ret;
|
||||
foreach (const QString &s, l)
|
||||
for (const QString &s : l)
|
||||
ret << s.toInt();
|
||||
return ret;
|
||||
}
|
||||
@@ -407,7 +407,7 @@ QList<int> Utils::Misc::intListfromStringList(const QStringList &l)
|
||||
QList<bool> Utils::Misc::boolListfromStringList(const QStringList &l)
|
||||
{
|
||||
QList<bool> ret;
|
||||
foreach (const QString &s, l)
|
||||
for (const QString &s : l)
|
||||
ret << (s == "1");
|
||||
return ret;
|
||||
}
|
||||
@@ -581,7 +581,7 @@ QString Utils::Misc::libtorrentVersionString()
|
||||
QString Utils::Misc::windowsSystemPath()
|
||||
{
|
||||
static const QString path = []() -> QString {
|
||||
WCHAR systemPath[64] = {0};
|
||||
WCHAR systemPath[MAX_PATH] = {0};
|
||||
GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR));
|
||||
return QString::fromWCharArray(systemPath);
|
||||
}();
|
||||
|
||||
@@ -79,11 +79,10 @@ namespace Utils
|
||||
QString boostVersionString();
|
||||
QString libtorrentVersionString();
|
||||
|
||||
QString unitString(SizeUnit unit);
|
||||
QString unitString(SizeUnit unit, bool isSpeed = false);
|
||||
|
||||
// return the best user friendly storage unit (B, KiB, MiB, GiB, TiB)
|
||||
// value must be given in bytes
|
||||
bool friendlyUnit(qint64 sizeInBytes, qreal &val, SizeUnit &unit);
|
||||
QString friendlyUnit(qint64 bytesValue, bool isSpeed = false);
|
||||
int friendlyUnitPrecision(SizeUnit unit);
|
||||
qint64 sizeInBytes(qreal size, SizeUnit unit);
|
||||
@@ -125,7 +124,7 @@ namespace Utils
|
||||
return reinterpret_cast<T>(
|
||||
::GetProcAddress(::LoadLibraryW(pathWchar.get()), funcName));
|
||||
}
|
||||
#endif
|
||||
#endif // Q_OS_WIN
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,18 +97,29 @@ namespace
|
||||
}
|
||||
else if (leftChar.isDigit() && rightChar.isDigit()) {
|
||||
// Both are digits, compare the numbers
|
||||
const auto consumeNumber = [](const QString &str, int &pos) -> int
|
||||
|
||||
const auto numberView = [](const QString &str, int &pos) -> QStringRef
|
||||
{
|
||||
const int start = pos;
|
||||
while ((pos < str.size()) && str[pos].isDigit())
|
||||
++pos;
|
||||
return str.midRef(start, (pos - start)).toInt();
|
||||
return str.midRef(start, (pos - start));
|
||||
};
|
||||
|
||||
const int numL = consumeNumber(left, posL);
|
||||
const int numR = consumeNumber(right, posR);
|
||||
if (numL != numR)
|
||||
return (numL - numR);
|
||||
const QStringRef numViewL = numberView(left, posL);
|
||||
const QStringRef numViewR = numberView(right, posR);
|
||||
|
||||
if (numViewL.length() != numViewR.length())
|
||||
return (numViewL.length() - numViewR.length());
|
||||
|
||||
// both string/view has the same length
|
||||
for (int i = 0; i < numViewL.length(); ++i) {
|
||||
const QChar numL = numViewL.at(i);
|
||||
const QChar numR = numViewR.at(i);
|
||||
|
||||
if (numL != numR)
|
||||
return (numL.unicode() - numR.unicode());
|
||||
}
|
||||
|
||||
// String + digits do match and we haven't hit the end of both strings
|
||||
// then continue to consume the remainings
|
||||
@@ -202,3 +213,14 @@ TriStateBool Utils::String::parseTriStateBool(const QString &string)
|
||||
return TriStateBool::False;
|
||||
return TriStateBool::Undefined;
|
||||
}
|
||||
|
||||
QString Utils::String::join(const QVector<QStringRef> &strings, const QString &separator)
|
||||
{
|
||||
if (strings.empty())
|
||||
return {};
|
||||
|
||||
QString ret = strings[0].toString();
|
||||
for (int i = 1; i < strings.count(); ++i)
|
||||
ret += (separator + strings[i]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#define UTILS_STRING_H
|
||||
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
class QByteArray;
|
||||
class QLatin1String;
|
||||
@@ -60,7 +61,7 @@ namespace Utils
|
||||
{
|
||||
if (str.length() < 2) return str;
|
||||
|
||||
for (auto const quote : quotes) {
|
||||
for (const auto "e : quotes) {
|
||||
if (str.startsWith(quote) && str.endsWith(quote))
|
||||
return str.mid(1, str.length() - 2);
|
||||
}
|
||||
@@ -70,6 +71,8 @@ namespace Utils
|
||||
|
||||
bool parseBool(const QString &string, const bool defaultValue);
|
||||
TriStateBool parseTriStateBool(const QString &string);
|
||||
|
||||
QString join(const QVector<QStringRef> &strings, const QString &separator);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -168,12 +168,12 @@ namespace Utils
|
||||
{
|
||||
if ((static_cast<std::size_t>(versionParts.size()) > N)
|
||||
|| (static_cast<std::size_t>(versionParts.size()) < Mandatory))
|
||||
throw std::runtime_error ("Incorrect number of version components");
|
||||
throw std::runtime_error("Incorrect number of version components");
|
||||
|
||||
bool ok = false;
|
||||
ComponentsArray res {{}};
|
||||
for (std::size_t i = 0; i < static_cast<std::size_t>(versionParts.size()); ++i) {
|
||||
res[i] = static_cast<T>(versionParts[i].toInt(&ok));
|
||||
res[i] = static_cast<T>(versionParts[static_cast<typename StringsList::size_type>(i)].toInt(&ok));
|
||||
if (!ok)
|
||||
throw std::runtime_error("Can not parse version component");
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public:
|
||||
"</table>"
|
||||
"</p>")
|
||||
.arg(tr("An advanced BitTorrent client programmed in C++, based on Qt toolkit and libtorrent-rasterbar.")
|
||||
, tr("Copyright %1 2006-2018 The qBittorrent project").arg(QString::fromUtf8(C_COPYRIGHT))
|
||||
, tr("Copyright %1 2006-2019 The qBittorrent project").arg(QString::fromUtf8(C_COPYRIGHT))
|
||||
, tr("Home Page:")
|
||||
, tr("Forum:")
|
||||
, tr("Bug Tracker:"));
|
||||
|
||||
@@ -30,23 +30,24 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
#include <QShortcut>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/bittorrent/filepriority.h"
|
||||
#include "base/bittorrent/magneturi.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
#include "base/bittorrent/torrentinfo.h"
|
||||
#include "base/global.h"
|
||||
#include "base/net/downloadhandler.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/settingsstorage.h"
|
||||
#include "base/torrentfileguard.h"
|
||||
#include "base/unicodestrings.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "base/utils/string.h"
|
||||
@@ -61,16 +62,14 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
#define SETTINGS_KEY(name) QStringLiteral("AddNewTorrentDialog/" name)
|
||||
const QString KEY_ENABLED = SETTINGS_KEY("Enabled");
|
||||
const QString KEY_DEFAULTCATEGORY = SETTINGS_KEY("DefaultCategory");
|
||||
const QString KEY_TREEHEADERSTATE = SETTINGS_KEY("TreeHeaderState");
|
||||
const QString KEY_WIDTH = SETTINGS_KEY("Width");
|
||||
const QString KEY_EXPANDED = SETTINGS_KEY("Expanded");
|
||||
const QString KEY_TOPLEVEL = SETTINGS_KEY("TopLevel");
|
||||
const QString KEY_SAVEPATHHISTORY = SETTINGS_KEY("SavePathHistory");
|
||||
const QString KEY_SAVEPATHHISTORYLENGTH = SETTINGS_KEY("SavePathHistoryLength");
|
||||
const QString KEY_REMEMBERLASTSAVEPATH = SETTINGS_KEY("RememberLastSavePath");
|
||||
#define SETTINGS_KEY(name) "AddNewTorrentDialog/" name
|
||||
const QString KEY_ENABLED = QStringLiteral(SETTINGS_KEY("Enabled"));
|
||||
const QString KEY_DEFAULTCATEGORY = QStringLiteral(SETTINGS_KEY("DefaultCategory"));
|
||||
const QString KEY_TREEHEADERSTATE = QStringLiteral(SETTINGS_KEY("TreeHeaderState"));
|
||||
const QString KEY_TOPLEVEL = QStringLiteral(SETTINGS_KEY("TopLevel"));
|
||||
const QString KEY_SAVEPATHHISTORY = QStringLiteral(SETTINGS_KEY("SavePathHistory"));
|
||||
const QString KEY_SAVEPATHHISTORYLENGTH = QStringLiteral(SETTINGS_KEY("SavePathHistoryLength"));
|
||||
const QString KEY_REMEMBERLASTSAVEPATH = QStringLiteral(SETTINGS_KEY("RememberLastSavePath"));
|
||||
|
||||
// just a shortcut
|
||||
inline SettingsStorage *settings()
|
||||
@@ -90,10 +89,13 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
|
||||
, m_hasMetadata(false)
|
||||
, m_oldIndex(0)
|
||||
, m_torrentParams(inParams)
|
||||
, m_storeDialogSize(SETTINGS_KEY("DialogSize"))
|
||||
, m_storeSplitterState(SETTINGS_KEY("SplitterState"))
|
||||
{
|
||||
// TODO: set dialog file properties using m_torrentParams.filePriorities
|
||||
m_ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
m_ui->lblMetaLoading->setVisible(false);
|
||||
m_ui->progMetaLoading->setVisible(false);
|
||||
|
||||
@@ -143,18 +145,19 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
|
||||
m_ui->categoryComboBox->addItem(defaultCategory);
|
||||
m_ui->categoryComboBox->addItem("");
|
||||
|
||||
foreach (const QString &category, categories)
|
||||
for (const QString &category : asConst(categories))
|
||||
if (category != defaultCategory && category != m_torrentParams.category)
|
||||
m_ui->categoryComboBox->addItem(category);
|
||||
|
||||
m_ui->contentTreeView->header()->setSortIndicator(0, Qt::AscendingOrder);
|
||||
loadState();
|
||||
// Signal / slots
|
||||
connect(m_ui->toolButtonAdvanced, &QToolButton::clicked, this, &AddNewTorrentDialog::showAdvancedSettings);
|
||||
connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked);
|
||||
QShortcut *editHotkey = new QShortcut(Qt::Key_F2, m_ui->contentTreeView, nullptr, nullptr, Qt::WidgetShortcut);
|
||||
connect(editHotkey, &QShortcut::activated, this, &AddNewTorrentDialog::renameSelectedFile);
|
||||
connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked, this, &AddNewTorrentDialog::renameSelectedFile);
|
||||
connect(editHotkey, &QShortcut::activated
|
||||
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
|
||||
connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked
|
||||
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
|
||||
|
||||
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
|
||||
}
|
||||
@@ -208,22 +211,17 @@ void AddNewTorrentDialog::setSavePathHistoryLength(int value)
|
||||
|
||||
void AddNewTorrentDialog::loadState()
|
||||
{
|
||||
Utils::Gui::resize(this, m_storeDialogSize);
|
||||
m_ui->splitter->restoreState(m_storeSplitterState);
|
||||
m_headerState = settings()->loadValue(KEY_TREEHEADERSTATE).toByteArray();
|
||||
|
||||
const QSize newSize = Utils::Gui::scaledSize(this, size());
|
||||
const int width = settings()->loadValue(KEY_WIDTH, newSize.width()).toInt();
|
||||
const int height = newSize.height();
|
||||
resize(width, height);
|
||||
|
||||
m_ui->toolButtonAdvanced->setChecked(settings()->loadValue(KEY_EXPANDED).toBool());
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::saveState()
|
||||
{
|
||||
m_storeDialogSize = size();
|
||||
m_storeSplitterState = m_ui->splitter->saveState();
|
||||
if (m_contentModel)
|
||||
settings()->storeValue(KEY_TREEHEADERSTATE, m_ui->contentTreeView->header()->saveState());
|
||||
settings()->storeValue(KEY_WIDTH, width());
|
||||
settings()->storeValue(KEY_EXPANDED, m_ui->toolButtonAdvanced->isChecked());
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
|
||||
@@ -234,7 +232,7 @@ void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParam
|
||||
// Launch downloader
|
||||
// TODO: Don't save loaded torrent to file, just use downloaded data!
|
||||
Net::DownloadHandler *handler = Net::DownloadManager::instance()->download(
|
||||
Net::DownloadRequest(source).limit(10485760 /* 10MB */).handleRedirectToMagnet(true).saveToFile(true));
|
||||
Net::DownloadRequest(source).limit(MAX_TORRENT_SIZE).handleRedirectToMagnet(true).saveToFile(true));
|
||||
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QString &)>(&Net::DownloadHandler::downloadFinished)
|
||||
, dlg, &AddNewTorrentDialog::handleDownloadFinished);
|
||||
connect(handler, &Net::DownloadHandler::downloadFailed, dlg, &AddNewTorrentDialog::handleDownloadFailed);
|
||||
@@ -249,11 +247,7 @@ void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParam
|
||||
ok = dlg->loadTorrent(source);
|
||||
|
||||
if (ok)
|
||||
#ifdef Q_OS_MAC
|
||||
dlg->exec();
|
||||
#else
|
||||
dlg->open();
|
||||
#endif
|
||||
dlg->QDialog::show();
|
||||
else
|
||||
delete dlg;
|
||||
}
|
||||
@@ -313,7 +307,7 @@ bool AddNewTorrentDialog::loadTorrent(const QString &torrentPath)
|
||||
return false;
|
||||
}
|
||||
|
||||
m_ui->lblhash->setText(m_hash);
|
||||
m_ui->labelHashData->setText(m_hash);
|
||||
setupTreeview();
|
||||
TMMChanged(m_ui->comboTTM->currentIndex());
|
||||
return true;
|
||||
@@ -358,7 +352,7 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
|
||||
|
||||
BitTorrent::Session::instance()->loadMetadata(magnetUri);
|
||||
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
|
||||
m_ui->lblhash->setText(m_hash);
|
||||
m_ui->labelHashData->setText(m_hash);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -372,33 +366,12 @@ void AddNewTorrentDialog::showEvent(QShowEvent *event)
|
||||
raise();
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::showAdvancedSettings(bool show)
|
||||
{
|
||||
const int minimumW = minimumWidth();
|
||||
setMinimumWidth(width()); // to remain the same width
|
||||
if (show) {
|
||||
m_ui->toolButtonAdvanced->setText(QString::fromUtf8(C_UP));
|
||||
m_ui->groupBoxSettings->setVisible(true);
|
||||
m_ui->infoGroup->setVisible(true);
|
||||
m_ui->contentTreeView->setVisible(m_hasMetadata);
|
||||
static_cast<QVBoxLayout *>(layout())->insertWidget(layout()->indexOf(m_ui->checkBoxNeverShow) + 1, m_ui->toolButtonAdvanced);
|
||||
}
|
||||
else {
|
||||
m_ui->toolButtonAdvanced->setText(QString::fromUtf8(C_DOWN));
|
||||
m_ui->groupBoxSettings->setVisible(false);
|
||||
m_ui->infoGroup->setVisible(false);
|
||||
m_ui->buttonsHLayout->insertWidget(0, layout()->takeAt(layout()->indexOf(m_ui->checkBoxNeverShow) + 1)->widget());
|
||||
}
|
||||
adjustSize();
|
||||
setMinimumWidth(minimumW);
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::saveSavePathHistory() const
|
||||
{
|
||||
// Get current history
|
||||
QStringList history = settings()->loadValue(KEY_SAVEPATHHISTORY).toStringList();
|
||||
QVector<QDir> historyDirs;
|
||||
for (const QString &path : qAsConst(history))
|
||||
for (const QString &path : asConst(history))
|
||||
historyDirs << QDir {path};
|
||||
|
||||
const QDir selectedSavePath {m_ui->savePath->selectedPath()};
|
||||
@@ -426,7 +399,7 @@ int AddNewTorrentDialog::indexOfSavePath(const QString &savePath)
|
||||
void AddNewTorrentDialog::updateDiskSpaceLabel()
|
||||
{
|
||||
// Determine torrent size
|
||||
qulonglong torrentSize = 0;
|
||||
qlonglong torrentSize = 0;
|
||||
|
||||
if (m_hasMetadata) {
|
||||
if (m_contentModel) {
|
||||
@@ -446,7 +419,7 @@ void AddNewTorrentDialog::updateDiskSpaceLabel()
|
||||
sizeString += tr("Free space on disk: %1").arg(Utils::Misc::friendlyUnit(Utils::Fs::freeDiskSpaceOnPath(
|
||||
m_ui->savePath->selectedPath())));
|
||||
sizeString += ')';
|
||||
m_ui->labelSize->setText(sizeString);
|
||||
m_ui->labelSizeData->setText(sizeString);
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::onSavePathChanged(const QString &newPath)
|
||||
@@ -479,107 +452,6 @@ void AddNewTorrentDialog::setSavePath(const QString &newPath)
|
||||
onSavePathChanged(newPath);
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::renameSelectedFile()
|
||||
{
|
||||
const QModelIndexList selectedIndexes = m_ui->contentTreeView->selectionModel()->selectedRows(0);
|
||||
if (selectedIndexes.size() != 1) return;
|
||||
|
||||
const QModelIndex modelIndex = selectedIndexes.first();
|
||||
if (!modelIndex.isValid()) return;
|
||||
|
||||
// Ask for new name
|
||||
bool ok = false;
|
||||
QString newName = AutoExpandableDialog::getText(this, tr("Renaming"), tr("New name:"), QLineEdit::Normal, modelIndex.data().toString(), &ok)
|
||||
.trimmed();
|
||||
if (!ok) return;
|
||||
|
||||
if (newName.isEmpty() || !Utils::Fs::isValidFileSystemName(newName)) {
|
||||
RaisedMessageBox::warning(this, tr("Rename error"),
|
||||
tr("The name is empty or contains forbidden characters, please choose a different one."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_contentModel->itemType(modelIndex) == TorrentContentModelItem::FileType) {
|
||||
// renaming a file
|
||||
const int fileIndex = m_contentModel->getFileIndex(modelIndex);
|
||||
|
||||
if (newName.endsWith(QB_EXT))
|
||||
newName.chop(QB_EXT.size());
|
||||
const QString oldFileName = m_torrentInfo.fileName(fileIndex);
|
||||
const QString oldFilePath = m_torrentInfo.filePath(fileIndex);
|
||||
const QString newFilePath = oldFilePath.leftRef(oldFilePath.size() - oldFileName.size()) + newName;
|
||||
|
||||
if (oldFileName == newName) {
|
||||
qDebug("Name did not change: %s", qUtf8Printable(oldFileName));
|
||||
return;
|
||||
}
|
||||
|
||||
// check if that name is already used
|
||||
for (int i = 0; i < m_torrentInfo.filesCount(); ++i) {
|
||||
if (i == fileIndex) continue;
|
||||
if (Utils::Fs::sameFileNames(m_torrentInfo.filePath(i), newFilePath)) {
|
||||
RaisedMessageBox::warning(this, tr("Rename error"),
|
||||
tr("This name is already in use in this folder. Please use a different name."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug("Renaming %s to %s", qUtf8Printable(oldFilePath), qUtf8Printable(newFilePath));
|
||||
m_torrentInfo.renameFile(fileIndex, newFilePath);
|
||||
|
||||
m_contentModel->setData(modelIndex, newName);
|
||||
}
|
||||
else {
|
||||
// renaming a folder
|
||||
QStringList pathItems;
|
||||
pathItems << modelIndex.data().toString();
|
||||
QModelIndex parent = m_contentModel->parent(modelIndex);
|
||||
while (parent.isValid()) {
|
||||
pathItems.prepend(parent.data().toString());
|
||||
parent = m_contentModel->parent(parent);
|
||||
}
|
||||
const QString oldPath = pathItems.join('/');
|
||||
pathItems.removeLast();
|
||||
pathItems << newName;
|
||||
QString newPath = pathItems.join('/');
|
||||
if (Utils::Fs::sameFileNames(oldPath, newPath)) {
|
||||
qDebug("Name did not change");
|
||||
return;
|
||||
}
|
||||
if (!newPath.endsWith('/')) newPath += '/';
|
||||
// Check for overwriting
|
||||
for (int i = 0; i < m_torrentInfo.filesCount(); ++i) {
|
||||
const QString currentName = m_torrentInfo.filePath(i);
|
||||
#if defined(Q_OS_UNIX) || defined(Q_WS_QWS)
|
||||
if (currentName.startsWith(newPath, Qt::CaseSensitive)) {
|
||||
#else
|
||||
if (currentName.startsWith(newPath, Qt::CaseInsensitive)) {
|
||||
#endif
|
||||
RaisedMessageBox::warning(this, tr("The folder could not be renamed"),
|
||||
tr("This name is already in use in this folder. Please use a different name."),
|
||||
QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Replace path in all files
|
||||
for (int i = 0; i < m_torrentInfo.filesCount(); ++i) {
|
||||
const QString currentName = m_torrentInfo.filePath(i);
|
||||
if (currentName.startsWith(oldPath)) {
|
||||
QString newName = currentName;
|
||||
newName.replace(0, oldPath.length(), newPath);
|
||||
newName = Utils::Fs::expandPath(newName);
|
||||
qDebug("Rename %s to %s", qUtf8Printable(currentName), qUtf8Printable(newName));
|
||||
m_torrentInfo.renameFile(i, newName);
|
||||
}
|
||||
}
|
||||
|
||||
// Rename folder in torrent files model too
|
||||
m_contentModel->setData(modelIndex, newName);
|
||||
}
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::populateSavePathComboBox()
|
||||
{
|
||||
m_ui->savePath->clear();
|
||||
@@ -619,21 +491,21 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
|
||||
QAction *act = myFilesLlistMenu.exec(QCursor::pos());
|
||||
if (act) {
|
||||
if (act == actRename) {
|
||||
renameSelectedFile();
|
||||
m_ui->contentTreeView->renameSelectedFile(m_torrentInfo);
|
||||
}
|
||||
else {
|
||||
int prio = prio::NORMAL;
|
||||
BitTorrent::FilePriority prio = BitTorrent::FilePriority::Normal;
|
||||
if (act == m_ui->actionHigh)
|
||||
prio = prio::HIGH;
|
||||
prio = BitTorrent::FilePriority::High;
|
||||
else if (act == m_ui->actionMaximum)
|
||||
prio = prio::MAXIMUM;
|
||||
prio = BitTorrent::FilePriority::Maximum;
|
||||
else if (act == m_ui->actionNotDownloaded)
|
||||
prio = prio::IGNORED;
|
||||
prio = BitTorrent::FilePriority::Ignored;
|
||||
|
||||
qDebug("Setting files priority");
|
||||
foreach (const QModelIndex &index, selectedRows) {
|
||||
qDebug("Setting priority(%d) for file at row %d", prio, index.row());
|
||||
m_contentModel->setData(m_contentModel->index(index.row(), PRIORITY, index.parent()), prio);
|
||||
for (const QModelIndex &index : selectedRows) {
|
||||
qDebug("Setting priority(%d) for file at row %d", static_cast<int>(prio), index.row());
|
||||
m_contentModel->setData(m_contentModel->index(index.row(), PRIORITY, index.parent()), static_cast<int>(prio));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -641,9 +513,6 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
|
||||
|
||||
void AddNewTorrentDialog::accept()
|
||||
{
|
||||
if (!m_hasMetadata)
|
||||
disconnect(this, SLOT(updateMetadata(const BitTorrent::TorrentInfo&)));
|
||||
|
||||
// TODO: Check if destination actually exists
|
||||
m_torrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked();
|
||||
|
||||
@@ -689,7 +558,6 @@ void AddNewTorrentDialog::accept()
|
||||
void AddNewTorrentDialog::reject()
|
||||
{
|
||||
if (!m_hasMetadata) {
|
||||
disconnect(this, SLOT(updateMetadata(BitTorrent::TorrentInfo)));
|
||||
setMetadataProgressIndicator(false);
|
||||
BitTorrent::Session::instance()->cancelLoadMetadata(m_hash);
|
||||
}
|
||||
@@ -701,7 +569,8 @@ void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &info)
|
||||
{
|
||||
if (info.hash() != m_hash) return;
|
||||
|
||||
disconnect(this, SLOT(updateMetadata(BitTorrent::TorrentInfo)));
|
||||
disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataLoaded, this, &AddNewTorrentDialog::updateMetadata);
|
||||
|
||||
if (!info.isValid()) {
|
||||
RaisedMessageBox::critical(this, tr("I/O Error"), ("Invalid metadata."));
|
||||
setMetadataProgressIndicator(false, tr("Invalid metadata"));
|
||||
@@ -729,16 +598,16 @@ void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, co
|
||||
void AddNewTorrentDialog::setupTreeview()
|
||||
{
|
||||
if (!m_hasMetadata) {
|
||||
setCommentText(tr("Not Available", "This comment is unavailable"));
|
||||
m_ui->labelDate->setText(tr("Not Available", "This date is unavailable"));
|
||||
m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
|
||||
m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
|
||||
}
|
||||
else {
|
||||
// Set dialog title
|
||||
setWindowTitle(m_torrentInfo.name());
|
||||
|
||||
// Set torrent information
|
||||
setCommentText(Utils::Misc::parseHtmlLinks(m_torrentInfo.comment()));
|
||||
m_ui->labelDate->setText(!m_torrentInfo.creationDate().isNull() ? m_torrentInfo.creationDate().toString(Qt::DefaultLocaleShortDate) : tr("Not available"));
|
||||
m_ui->labelCommentData->setText(Utils::Misc::parseHtmlLinks(m_torrentInfo.comment()));
|
||||
m_ui->labelDateData->setText(!m_torrentInfo.creationDate().isNull() ? m_torrentInfo.creationDate().toString(Qt::DefaultLocaleShortDate) : tr("Not available"));
|
||||
|
||||
// Prepare content tree
|
||||
m_contentModel = new TorrentContentFilterModel(this);
|
||||
@@ -765,7 +634,6 @@ void AddNewTorrentDialog::setupTreeview()
|
||||
}
|
||||
|
||||
updateDiskSpaceLabel();
|
||||
showAdvancedSettings(settings()->loadValue(KEY_EXPANDED, false).toBool());
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::handleDownloadFailed(const QString &url, const QString &reason)
|
||||
@@ -800,7 +668,6 @@ void AddNewTorrentDialog::TMMChanged(int index)
|
||||
m_ui->groupBoxSavePath->setEnabled(true);
|
||||
m_ui->savePath->blockSignals(false);
|
||||
m_ui->savePath->setCurrentIndex(m_oldIndex < m_ui->savePath->count() ? m_oldIndex : m_ui->savePath->count() - 1);
|
||||
m_ui->toolButtonAdvanced->setEnabled(true);
|
||||
}
|
||||
else {
|
||||
m_ui->groupBoxSavePath->setEnabled(false);
|
||||
@@ -808,23 +675,9 @@ void AddNewTorrentDialog::TMMChanged(int index)
|
||||
m_ui->savePath->clear();
|
||||
QString savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->categoryComboBox->currentText());
|
||||
m_ui->savePath->addItem(savePath);
|
||||
m_ui->toolButtonAdvanced->setChecked(true);
|
||||
m_ui->toolButtonAdvanced->setEnabled(false);
|
||||
showAdvancedSettings(true);
|
||||
}
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::setCommentText(const QString &str) const
|
||||
{
|
||||
m_ui->commentLabel->setText(str);
|
||||
|
||||
// workaround for the additional space introduced by QScrollArea
|
||||
int lineHeight = m_ui->commentLabel->fontMetrics().lineSpacing();
|
||||
int lines = 1 + str.count('\n');
|
||||
int height = lineHeight * lines;
|
||||
m_ui->scrollArea->setMaximumHeight(height);
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked)
|
||||
{
|
||||
m_torrentGuard->setAutoRemove(!checked);
|
||||
|
||||
@@ -31,11 +31,11 @@
|
||||
|
||||
#include <QDialog>
|
||||
#include <QScopedPointer>
|
||||
#include <QShortcut>
|
||||
|
||||
#include "base/bittorrent/addtorrentparams.h"
|
||||
#include "base/bittorrent/infohash.h"
|
||||
#include "base/bittorrent/torrentinfo.h"
|
||||
#include "base/settingvalue.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
@@ -72,11 +72,9 @@ public:
|
||||
static void show(QString source, QWidget *parent);
|
||||
|
||||
private slots:
|
||||
void showAdvancedSettings(bool show);
|
||||
void displayContentTreeMenu(const QPoint &);
|
||||
void updateDiskSpaceLabel();
|
||||
void onSavePathChanged(const QString &newPath);
|
||||
void renameSelectedFile();
|
||||
void updateMetadata(const BitTorrent::TorrentInfo &info);
|
||||
void handleDownloadFailed(const QString &url, const QString &reason);
|
||||
void handleRedirectedToMagnet(const QString &url, const QString &magnetUri);
|
||||
@@ -99,7 +97,6 @@ private:
|
||||
void saveState();
|
||||
void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = QString());
|
||||
void setupTreeview();
|
||||
void setCommentText(const QString &str) const;
|
||||
void setSavePath(const QString &newPath);
|
||||
|
||||
void showEvent(QShowEvent *event) override;
|
||||
@@ -115,6 +112,9 @@ private:
|
||||
int m_oldIndex;
|
||||
QScopedPointer<TorrentFileGuard> m_torrentGuard;
|
||||
BitTorrent::AddTorrentParams m_torrentParams;
|
||||
|
||||
CachedSettingValue<QSize> m_storeDialogSize;
|
||||
CachedSettingValue<QByteArray> m_storeSplitterState;
|
||||
};
|
||||
|
||||
#endif // ADDNEWTORRENTDIALOG_H
|
||||
|
||||
@@ -6,37 +6,346 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>414</width>
|
||||
<height>630</height>
|
||||
<width>900</width>
|
||||
<height>620</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="AddNewTorrentDialogLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="childrenCollapsible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QFrame" name="torrentoptionsFrame">
|
||||
<layout class="QVBoxLayout" name="mainlayout_addui">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="managementLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelTorrentManagementMode">
|
||||
<property name="text">
|
||||
<string>Torrent Management Mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboTTM">
|
||||
<property name="toolTip">
|
||||
<string>Automatic mode means that various torrent properties(eg save path) will be decided by the associated category</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Manual</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Automatic</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxSavePath">
|
||||
<property name="title">
|
||||
<string>Save at</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="FileSystemPathComboEdit" name="savePath" native="true"/>
|
||||
</item>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QCheckBox" name="checkBoxRememberLastSavePath">
|
||||
<property name="text">
|
||||
<string>Remember last used save path</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxSettings">
|
||||
<property name="title">
|
||||
<string>Torrent settings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="categoryLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelCategory">
|
||||
<property name="text">
|
||||
<string>Category:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="categoryComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="insertPolicy">
|
||||
<enum>QComboBox::InsertAtTop</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QCheckBox" name="defaultCategoryCheckbox">
|
||||
<property name="text">
|
||||
<string>Set as default category</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="doNotDeleteTorrentCheckBox">
|
||||
<property name="toolTip">
|
||||
<string>When checked, the .torrent file will not be deleted despite the settings at the "Download" page of the options dialog</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Do not delete .torrent file</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="firstLastCheckBox">
|
||||
<property name="text">
|
||||
<string>Download first and last pieces first</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="skipCheckingCheckBox">
|
||||
<property name="text">
|
||||
<string>Skip hash check</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="sequentialCheckBox">
|
||||
<property name="text">
|
||||
<string>Download in sequential order</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="createSubfolderCheckBox">
|
||||
<property name="text">
|
||||
<string>Create subfolder</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="startTorrentCheckBox">
|
||||
<property name="text">
|
||||
<string>Start torrent</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="infoGroup">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Torrent information</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelDate">
|
||||
<property name="text">
|
||||
<string>Date:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="labelSizeData"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="labelDateData"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelSize">
|
||||
<property name="text">
|
||||
<string>Size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="labelHashData">
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color: rgba(0, 0, 0, 0);</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>421</width>
|
||||
<height>68</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelCommentData">
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelHash">
|
||||
<property name="text">
|
||||
<string>Hash:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelComment">
|
||||
<property name="text">
|
||||
<string>Comment:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="TorrentContentTreeView" name="contentTreeView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>1</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="buttonsHLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelTorrentManagementMode">
|
||||
<widget class="QCheckBox" name="checkBoxNeverShow">
|
||||
<property name="text">
|
||||
<string>Torrent Management Mode:</string>
|
||||
<string>Never show again</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboTTM">
|
||||
<property name="toolTip">
|
||||
<string>Automatic mode means that various torrent properties(eg save path) will be decided by the associated category</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Manual</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Automatic</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
@@ -53,289 +362,7 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxSavePath">
|
||||
<property name="title">
|
||||
<string>Save at</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="FileSystemPathComboEdit" name="savePath" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxRememberLastSavePath">
|
||||
<property name="text">
|
||||
<string>Remember last used save path</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="doNotDeleteTorrentCheckBox">
|
||||
<property name="toolTip">
|
||||
<string>When checked, the .torrent file will not be deleted despite the settings at the "Download" page of the options dialog</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Do not delete .torrent file</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxNeverShow">
|
||||
<property name="text">
|
||||
<string>Never show again</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="toolButtonAdvanced">
|
||||
<property name="text">
|
||||
<string notr="true">▼</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxSettings">
|
||||
<property name="title">
|
||||
<string>Torrent settings</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="1" column="2">
|
||||
<widget class="QCheckBox" name="defaultCategoryCheckbox">
|
||||
<property name="text">
|
||||
<string>Set as default category</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_1">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Category:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="categoryComboBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="insertPolicy">
|
||||
<enum>QComboBox::InsertAtTop</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="startTorrentCheckBox">
|
||||
<property name="text">
|
||||
<string>Start torrent</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="skipCheckingCheckBox">
|
||||
<property name="text">
|
||||
<string>Skip hash check</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<spacer name="horizontalSpacer2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>35</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="createSubfolderCheckBox">
|
||||
<property name="text">
|
||||
<string>Create subfolder</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="sequentialCheckBox">
|
||||
<property name="text">
|
||||
<string>Download in sequential order</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="firstLastCheckBox">
|
||||
<property name="text">
|
||||
<string>Download first and last pieces first</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="infoGroup">
|
||||
<property name="title">
|
||||
<string>Torrent information</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="lblhash">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Hash:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="TorrentContentTreeView" name="contentTreeView">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="labelDate">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Date:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="labelSize">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Comment:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>321</width>
|
||||
<height>69</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="commentLabel">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextBrowserInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="buttonsHLayout">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progMetaLoading">
|
||||
<property name="sizePolicy">
|
||||
@@ -353,13 +380,17 @@
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="format">
|
||||
<string notr="true">%p%</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblMetaLoading"/>
|
||||
<widget class="QLabel" name="lblMetaLoading">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
@@ -405,18 +436,6 @@
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>savePath</tabstop>
|
||||
<tabstop>checkBoxRememberLastSavePath</tabstop>
|
||||
<tabstop>checkBoxNeverShow</tabstop>
|
||||
<tabstop>toolButtonAdvanced</tabstop>
|
||||
<tabstop>startTorrentCheckBox</tabstop>
|
||||
<tabstop>skipCheckingCheckBox</tabstop>
|
||||
<tabstop>categoryComboBox</tabstop>
|
||||
<tabstop>defaultCategoryCheckbox</tabstop>
|
||||
<tabstop>scrollArea</tabstop>
|
||||
<tabstop>contentTreeView</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
@@ -426,8 +445,8 @@
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>403</x>
|
||||
<y>579</y>
|
||||
<x>928</x>
|
||||
<y>855</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
@@ -442,8 +461,8 @@
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>403</x>
|
||||
<y>579</y>
|
||||
<x>928</x>
|
||||
<y>855</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
@@ -451,22 +470,6 @@
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>categoryComboBox</sender>
|
||||
<signal>currentIndexChanged(int)</signal>
|
||||
<receiver>AddNewTorrentDialog</receiver>
|
||||
<slot>categoryChanged(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>337</x>
|
||||
<y>205</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>403</x>
|
||||
<y>160</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>comboTTM</sender>
|
||||
<signal>currentIndexChanged(int)</signal>
|
||||
@@ -474,12 +477,28 @@
|
||||
<slot>TMMChanged(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>200</x>
|
||||
<y>19</y>
|
||||
<x>250</x>
|
||||
<y>53</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>206</x>
|
||||
<y>294</y>
|
||||
<x>467</x>
|
||||
<y>249</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>categoryComboBox</sender>
|
||||
<signal>currentIndexChanged(int)</signal>
|
||||
<receiver>AddNewTorrentDialog</receiver>
|
||||
<slot>categoryChanged(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>266</x>
|
||||
<y>231</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>467</x>
|
||||
<y>249</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <QNetworkInterface>
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/global.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/unicodestrings.h"
|
||||
#include "app/application.h"
|
||||
@@ -73,6 +74,7 @@ enum AdvSettingsRows
|
||||
CONFIRM_REMOVE_ALL_TAGS,
|
||||
DOWNLOAD_TRACKER_FAVICON,
|
||||
SAVE_PATH_HISTORY_LENGTH,
|
||||
ENABLE_SPEED_WIDGET,
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC))
|
||||
USE_ICON_THEME,
|
||||
#endif
|
||||
@@ -82,6 +84,7 @@ enum AdvSettingsRows
|
||||
#if LIBTORRENT_VERSION_NUM >= 10100
|
||||
ASYNC_IO_THREADS,
|
||||
#endif
|
||||
CHECKING_MEM_USAGE,
|
||||
// cache
|
||||
DISK_CACHE,
|
||||
DISK_CACHE_TTL,
|
||||
@@ -151,6 +154,8 @@ void AdvancedSettings::saveAdvancedSettings()
|
||||
// Async IO threads
|
||||
session->setAsyncIOThreads(spinBoxAsyncIOThreads.value());
|
||||
#endif
|
||||
// Checking Memory Usage
|
||||
session->setCheckingMemUsage(spinBoxCheckingMemUsage.value());
|
||||
// Disk write cache
|
||||
session->setDiskCacheSize(spinBoxCache.value());
|
||||
session->setDiskCacheTTL(spinBoxCacheTTL.value());
|
||||
@@ -218,6 +223,7 @@ void AdvancedSettings::saveAdvancedSettings()
|
||||
// Misc GUI properties
|
||||
mainWindow->setDownloadTrackerFavicon(checkBoxTrackerFavicon.isChecked());
|
||||
AddNewTorrentDialog::setSavePathHistoryLength(spinBoxSavePathHistoryLength.value());
|
||||
pref->setSpeedWidgetEnabled(checkBoxSpeedWidgetEnabled.isChecked());
|
||||
|
||||
// Tracker
|
||||
session->setTrackerEnabled(checkBoxTrackerStatus.isChecked());
|
||||
@@ -284,13 +290,13 @@ void AdvancedSettings::updateInterfaceAddressCombo()
|
||||
};
|
||||
|
||||
if (ifaceName.isEmpty()) {
|
||||
foreach (const QHostAddress &ip, QNetworkInterface::allAddresses())
|
||||
for (const QHostAddress &ip : asConst(QNetworkInterface::allAddresses()))
|
||||
populateCombo(ip.toString(), ip.protocol());
|
||||
}
|
||||
else {
|
||||
const QNetworkInterface iface = QNetworkInterface::interfaceFromName(ifaceName);
|
||||
const QList<QNetworkAddressEntry> addresses = iface.addressEntries();
|
||||
foreach (const QNetworkAddressEntry &entry, addresses) {
|
||||
for (const QNetworkAddressEntry &entry : addresses) {
|
||||
const QHostAddress ip = entry.ip();
|
||||
populateCombo(ip.toString(), ip.protocol());
|
||||
}
|
||||
@@ -323,12 +329,19 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
spinBoxAsyncIOThreads.setValue(session->asyncIOThreads());
|
||||
addRow(ASYNC_IO_THREADS, tr("Asynchronous I/O threads"), &spinBoxAsyncIOThreads);
|
||||
#endif
|
||||
|
||||
// Checking Memory Usage
|
||||
spinBoxCheckingMemUsage.setMinimum(1);
|
||||
spinBoxCheckingMemUsage.setValue(session->checkingMemUsage());
|
||||
spinBoxCheckingMemUsage.setSuffix(tr(" MiB"));
|
||||
addRow(CHECKING_MEM_USAGE, tr("Outstanding memory when checking torrents"), &spinBoxCheckingMemUsage);
|
||||
|
||||
// Disk write cache
|
||||
spinBoxCache.setMinimum(-1);
|
||||
// When build as 32bit binary, set the maximum at less than 2GB to prevent crashes.
|
||||
// These macros may not be available on compilers other than MSVC and GCC
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
spinBoxCache.setMaximum(4096);
|
||||
spinBoxCache.setMaximum(33554431); // 32768GiB
|
||||
#else
|
||||
// allocate 1536MiB and leave 512MiB to the rest of program data in RAM
|
||||
spinBoxCache.setMaximum(1536);
|
||||
@@ -337,8 +350,8 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
updateCacheSpinSuffix(spinBoxCache.value());
|
||||
addRow(DISK_CACHE, tr("Disk cache"), &spinBoxCache);
|
||||
// Disk cache expiry
|
||||
spinBoxCacheTTL.setMinimum(15);
|
||||
spinBoxCacheTTL.setMaximum(600);
|
||||
spinBoxCacheTTL.setMinimum(1);
|
||||
spinBoxCacheTTL.setMaximum(std::numeric_limits<int>::max());
|
||||
spinBoxCacheTTL.setValue(session->diskCacheTTL());
|
||||
spinBoxCacheTTL.setSuffix(tr(" s", " seconds"));
|
||||
addRow(DISK_CACHE_TTL, tr("Disk cache expiry interval"), &spinBoxCacheTTL);
|
||||
@@ -423,7 +436,7 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
const QString currentInterface = session->networkInterface();
|
||||
bool interfaceExists = currentInterface.isEmpty();
|
||||
int i = 1;
|
||||
foreach (const QNetworkInterface& iface, QNetworkInterface::allInterfaces()) {
|
||||
for (const QNetworkInterface &iface : asConst(QNetworkInterface::allInterfaces())) {
|
||||
// This line fixes a Qt bug => https://bugreports.qt.io/browse/QTBUG-52633
|
||||
// Tested in Qt 5.6.0. For more info see:
|
||||
// https://github.com/qbittorrent/qBittorrent/issues/5131
|
||||
@@ -467,6 +480,9 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
spinBoxSavePathHistoryLength.setRange(AddNewTorrentDialog::minPathHistoryLength, AddNewTorrentDialog::maxPathHistoryLength);
|
||||
spinBoxSavePathHistoryLength.setValue(AddNewTorrentDialog::savePathHistoryLength());
|
||||
addRow(SAVE_PATH_HISTORY_LENGTH, tr("Save path history length"), &spinBoxSavePathHistoryLength);
|
||||
// Enable speed graphs
|
||||
checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled());
|
||||
addRow(ENABLE_SPEED_WIDGET, tr("Enable speed graphs"), &checkBoxSpeedWidgetEnabled);
|
||||
// Tracker State
|
||||
checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
|
||||
addRow(TRACKER_STATUS, tr("Enable embedded tracker"), &checkBoxTrackerStatus);
|
||||
|
||||
@@ -59,13 +59,13 @@ private:
|
||||
template <typename T> void addRow(int row, const QString &rowText, T *widget);
|
||||
|
||||
QLabel labelQbtLink, labelLibtorrentLink;
|
||||
QSpinBox spinBoxAsyncIOThreads, spinBoxCache, spinBoxSaveResumeDataInterval, spinBoxOutgoingPortsMin, spinBoxOutgoingPortsMax, spinBoxListRefresh, spinBoxMaxHalfOpen,
|
||||
QSpinBox spinBoxAsyncIOThreads, spinBoxCheckingMemUsage, spinBoxCache, spinBoxSaveResumeDataInterval, spinBoxOutgoingPortsMin, spinBoxOutgoingPortsMax, spinBoxListRefresh, spinBoxMaxHalfOpen,
|
||||
spinBoxTrackerPort, spinBoxCacheTTL, spinBoxSendBufferWatermark, spinBoxSendBufferLowWatermark,
|
||||
spinBoxSendBufferWatermarkFactor, spinBoxSavePathHistoryLength;
|
||||
QCheckBox checkBoxOsCache, checkBoxRecheckCompleted, checkBoxResolveCountries, checkBoxResolveHosts, checkBoxSuperSeeding,
|
||||
checkBoxProgramNotifications, checkBoxTorrentAddedNotifications, checkBoxTrackerFavicon, checkBoxTrackerStatus,
|
||||
checkBoxConfirmTorrentRecheck, checkBoxConfirmRemoveAllTags, checkBoxListenIPv6, checkBoxAnnounceAllTrackers, checkBoxAnnounceAllTiers,
|
||||
checkBoxGuidedReadCache, checkBoxMultiConnectionsPerIp, checkBoxSuggestMode, checkBoxCoalesceRW;
|
||||
checkBoxGuidedReadCache, checkBoxMultiConnectionsPerIp, checkBoxSuggestMode, checkBoxCoalesceRW, checkBoxSpeedWidgetEnabled;
|
||||
QComboBox comboBoxInterface, comboBoxInterfaceAddress, comboBoxUtpMixedMode, comboBoxChokingAlgorithm, comboBoxSeedChokingAlgorithm;
|
||||
QLineEdit lineEditAnnounceIP;
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ AutoExpandableDialog::~AutoExpandableDialog()
|
||||
|
||||
QString AutoExpandableDialog::getText(QWidget *parent, const QString &title, const QString &label,
|
||||
QLineEdit::EchoMode mode, const QString &text,
|
||||
bool *ok, Qt::InputMethodHints inputMethodHints)
|
||||
bool *ok, const bool excludeExtension, Qt::InputMethodHints inputMethodHints)
|
||||
{
|
||||
AutoExpandableDialog d(parent);
|
||||
d.setWindowTitle(title);
|
||||
@@ -57,6 +57,16 @@ QString AutoExpandableDialog::getText(QWidget *parent, const QString &title, con
|
||||
d.m_ui->textEdit->setEchoMode(mode);
|
||||
d.m_ui->textEdit->setInputMethodHints(inputMethodHints);
|
||||
|
||||
d.m_ui->textEdit->selectAll();
|
||||
if (excludeExtension) {
|
||||
int lastDotIndex = text.lastIndexOf('.');
|
||||
if ((lastDotIndex > 3) && (text.mid(lastDotIndex - 4, 4).toLower() == ".tar"))
|
||||
lastDotIndex -= 4;
|
||||
// Select file name without extension, except dot files like .gitignore
|
||||
if (lastDotIndex > 0)
|
||||
d.m_ui->textEdit->setSelection(0, lastDotIndex);
|
||||
}
|
||||
|
||||
bool res = d.exec();
|
||||
if (ok)
|
||||
*ok = res;
|
||||
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
|
||||
static QString getText(QWidget *parent, const QString &title, const QString &label,
|
||||
QLineEdit::EchoMode mode = QLineEdit::Normal, const QString &text = QString(),
|
||||
bool *ok = nullptr, Qt::InputMethodHints inputMethodHints = Qt::ImhNone);
|
||||
bool *ok = nullptr, bool excludeExtension = false, Qt::InputMethodHints inputMethodHints = Qt::ImhNone);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *e) override;
|
||||
|
||||
@@ -107,8 +107,8 @@ void BanListOptionsDialog::on_buttonBanIP_clicked()
|
||||
|
||||
void BanListOptionsDialog::on_buttonDeleteIP_clicked()
|
||||
{
|
||||
QModelIndexList selection = m_ui->bannedIPList->selectionModel()->selectedIndexes();
|
||||
for (auto &i : selection)
|
||||
const QModelIndexList selection = m_ui->bannedIPList->selectionModel()->selectedIndexes();
|
||||
for (const auto &i : selection)
|
||||
m_sortFilter->removeRow(i.row());
|
||||
|
||||
m_modified = true;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrenthandle.h"
|
||||
#include "base/global.h"
|
||||
#include "guiiconprovider.h"
|
||||
|
||||
class CategoryModelItem
|
||||
@@ -228,7 +229,7 @@ QVariant CategoryFilterModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
Qt::ItemFlags CategoryFilterModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) return 0;
|
||||
if (!index.isValid()) return Qt::NoItemFlags;
|
||||
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
@@ -407,7 +408,7 @@ void CategoryFilterModel::populate()
|
||||
const QString &category = i.key();
|
||||
if (m_isSubcategoriesEnabled) {
|
||||
CategoryModelItem *parent = m_rootItem;
|
||||
foreach (const QString &subcat, session->expandCategory(category)) {
|
||||
for (const QString &subcat : asConst(session->expandCategory(category))) {
|
||||
const QString subcatName = shortName(subcat);
|
||||
if (!parent->hasChild(subcatName)) {
|
||||
new CategoryModelItem(
|
||||
@@ -436,7 +437,7 @@ CategoryModelItem *CategoryFilterModel::findItem(const QString &fullName) const
|
||||
return m_rootItem->child(fullName);
|
||||
|
||||
CategoryModelItem *item = m_rootItem;
|
||||
foreach (const QString &subcat, BitTorrent::Session::expandCategory(fullName)) {
|
||||
for (const QString &subcat : asConst(BitTorrent::Session::expandCategory(fullName))) {
|
||||
const QString subcatName = shortName(subcat);
|
||||
if (!item->hasChild(subcatName)) return nullptr;
|
||||
item = item->child(subcatName);
|
||||
|
||||
@@ -231,7 +231,7 @@ void CategoryFilterWidget::removeCategory()
|
||||
void CategoryFilterWidget::removeUnusedCategories()
|
||||
{
|
||||
auto session = BitTorrent::Session::instance();
|
||||
for (const QString &category : copyAsConst(session->categories().keys())) {
|
||||
for (const QString &category : asConst(session->categories().keys())) {
|
||||
if (model()->data(static_cast<CategoryFilterProxyModel *>(model())->index(category), Qt::UserRole) == 0)
|
||||
session->removeCategory(category);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include <QTreeView>
|
||||
#pragma once
|
||||
|
||||
#include <QTreeView>
|
||||
|
||||
class CategoryFilterWidget : public QTreeView
|
||||
{
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
#include "base/settingsstorage.h"
|
||||
#include "cookiesmodel.h"
|
||||
@@ -100,6 +101,6 @@ void CookiesDialog::onButtonDeleteClicked()
|
||||
}
|
||||
);
|
||||
|
||||
for (const QModelIndex &idx : idxs)
|
||||
for (const QModelIndex &idx : asConst(idxs))
|
||||
m_cookiesModel->removeRow(idx.row());
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user