mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-18 06:28:03 -06:00
Compare commits
99 Commits
release-5.
...
release-4.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a56c3f5df | ||
|
|
b1e2e511bb | ||
|
|
ac1fd66f05 | ||
|
|
6431fe5f73 | ||
|
|
c31931324d | ||
|
|
f1d78563af | ||
|
|
99b5983143 | ||
|
|
c1e8849b40 | ||
|
|
330f20171f | ||
|
|
b53eadaec8 | ||
|
|
26d78f6462 | ||
|
|
9890bb7501 | ||
|
|
c7daaf95fc | ||
|
|
b760f37093 | ||
|
|
7f5271ae7c | ||
|
|
1130bf300a | ||
|
|
3f142360ed | ||
|
|
af07a98784 | ||
|
|
6c546df70b | ||
|
|
a46348ada9 | ||
|
|
5fc9bf5d29 | ||
|
|
556db6a91a | ||
|
|
9ed9ffcaf2 | ||
|
|
bf986fcf4c | ||
|
|
0036a02a72 | ||
|
|
6e268b007b | ||
|
|
2be2ee6fc2 | ||
|
|
be4fa061ee | ||
|
|
665bbc2421 | ||
|
|
8d408ffc8b | ||
|
|
aa39c7aae5 | ||
|
|
7ad667e8d2 | ||
|
|
0c8220c9fd | ||
|
|
e9364b72f9 | ||
|
|
a004f0afc5 | ||
|
|
d7f172060c | ||
|
|
9eae2b8ea9 | ||
|
|
26e220e003 | ||
|
|
d7e9533e8c | ||
|
|
8306a41d11 | ||
|
|
169c4991d5 | ||
|
|
78344a10fa | ||
|
|
f8d9f70e7f | ||
|
|
011ac90a52 | ||
|
|
c1b38221d2 | ||
|
|
c621cae43b | ||
|
|
ede7c8acbb | ||
|
|
9195df5179 | ||
|
|
a3d5ea829b | ||
|
|
c062f86bac | ||
|
|
e5894831ec | ||
|
|
f40e92f186 | ||
|
|
0ab10ef2b3 | ||
|
|
049e376953 | ||
|
|
6dab4615aa | ||
|
|
0b9a1dfd9d | ||
|
|
11c45db2ec | ||
|
|
ba147d72b9 | ||
|
|
97ead6d7c9 | ||
|
|
51cb3ca0c8 | ||
|
|
c514c2b7a8 | ||
|
|
d26e582cc1 | ||
|
|
5d161d2477 | ||
|
|
b9ea6a5dc5 | ||
|
|
7b0b64a04e | ||
|
|
4cb386af35 | ||
|
|
14ab1b015c | ||
|
|
0a4971c994 | ||
|
|
a75ae21434 | ||
|
|
01eed5dae9 | ||
|
|
e73397c750 | ||
|
|
869d079507 | ||
|
|
71174edf72 | ||
|
|
b3d46ecb78 | ||
|
|
80035a2520 | ||
|
|
6790335239 | ||
|
|
48ff494dca | ||
|
|
c5b361ce74 | ||
|
|
397b7b9407 | ||
|
|
6e0c1e2147 | ||
|
|
e93c360db6 | ||
|
|
270e2023cd | ||
|
|
5ac858213b | ||
|
|
f0ee6aba29 | ||
|
|
fa418087c4 | ||
|
|
8493e1ad64 | ||
|
|
fe90fcef5b | ||
|
|
210fd80167 | ||
|
|
0a1e864f74 | ||
|
|
7adccab687 | ||
|
|
67e536d869 | ||
|
|
86e8d848f6 | ||
|
|
88114b4588 | ||
|
|
e468f004f4 | ||
|
|
4cfccc54ea | ||
|
|
5ffa7e4752 | ||
|
|
d7fd576293 | ||
|
|
83b34053a1 | ||
|
|
b9164adb7a |
19
.github/workflows/ci_macos.yaml
vendored
19
.github/workflows/ci_macos.yaml
vendored
@@ -17,6 +17,7 @@ jobs:
|
||||
qt_version: "6.2.0"
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
openssl_root: /usr/local/opt/openssl@1.1
|
||||
|
||||
steps:
|
||||
@@ -28,16 +29,26 @@ jobs:
|
||||
brew update > /dev/null
|
||||
brew install \
|
||||
cmake ninja \
|
||||
boost openssl@1.1 zlib
|
||||
openssl@1.1 zlib
|
||||
|
||||
- name: Setup ccache
|
||||
uses: Chocobo1/setup-ccache-action@v1
|
||||
with:
|
||||
update_packager_index: false
|
||||
|
||||
- name: Install boost
|
||||
run: |
|
||||
curl \
|
||||
-L \
|
||||
-o "${{ runner.temp }}/boost.tar.bz2" \
|
||||
"https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.tar.bz2"
|
||||
tar -xf "${{ runner.temp }}/boost.tar.bz2" -C "${{ github.workspace }}/.."
|
||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v2
|
||||
with:
|
||||
setup-python: false
|
||||
version: ${{ matrix.qt_version }}
|
||||
|
||||
- name: Install libtorrent
|
||||
@@ -54,6 +65,7 @@ jobs:
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-Ddeprecated-functions=OFF \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}"
|
||||
cmake --build build
|
||||
@@ -68,6 +80,7 @@ jobs:
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
|
||||
-DVERBOSE_CONFIGURE=ON \
|
||||
-D${{ matrix.qbt_gui }}
|
||||
@@ -82,6 +95,7 @@ jobs:
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
|
||||
-DQT6=ON \
|
||||
-DVERBOSE_CONFIGURE=ON \
|
||||
@@ -91,7 +105,6 @@ jobs:
|
||||
- name: Prepare build artifacts
|
||||
run: |
|
||||
mkdir upload
|
||||
mv build/qbittorrent*.app upload
|
||||
mkdir upload/cmake
|
||||
cp build/compile_commands.json upload/cmake
|
||||
mkdir upload/cmake/libtorrent
|
||||
@@ -100,5 +113,5 @@ jobs:
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: qBittorrent-CI_macOS_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
|
||||
name: build-info_macOS_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
|
||||
path: upload
|
||||
|
||||
61
Changelog
61
Changelog
@@ -1,3 +1,64 @@
|
||||
Mon Aug 22 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.4.4
|
||||
- BUGFIX: Correctly handle data decompression with Qt 6.3 (brvphoenix)
|
||||
- BUGFIX: Fix wrong file names displayed in tooltip (Chocobo1)
|
||||
- BUGFIX: Fix incorrect "max outgoing port" setting (glassez)
|
||||
- BUGFIX: Make working set limit available only on libtorrent 2.0.x builds (summer)
|
||||
- BUGFIX: Try to recover missing tags (summer)
|
||||
- RSS: Clear RSS parsing error after use (glassez)
|
||||
- WEBAPI: Set HTTP method restriction on WebAPI actions (Chocobo1)
|
||||
- WINDOWS: Work around application stuttering on Windows (Chocobo1)
|
||||
- WINDOWS: NSIS: Update Portuguese, Italian, Korean, Latvian translations(Blackspirits, bovirus, Minseo Lee, Coool)
|
||||
- LINUX: Improve D-Bus notifications handling (glassez)
|
||||
- MACOS: Open destination folders on macOS in separate thread (Nick Korotysh)
|
||||
|
||||
Tue May 24 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.4.3.1
|
||||
- BUGFIX: Fix broken translations (sledgehammer999)
|
||||
|
||||
Sun May 22 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.4.3
|
||||
- BUGFIX: Correctly handle changing of temp save path (glassez)
|
||||
- BUGFIX: Fix storage in SQLite (glassez)
|
||||
- BUGFIX: Correctly apply content layout when "Skip hash check" is enabled (glassez)
|
||||
- BUGFIX: Don't corrupt IDs of v2 torrents (glassez)
|
||||
- BUGFIX: Reduce the number of hashing threads by default (improves hashing speed on HDDs) (summer)
|
||||
- BUGFIX: Prevent the "update dialog" from blocking input on other windows (summer)
|
||||
- BUGFIX: Add trackers in exported .torrent files (glassez)
|
||||
- BUGFIX: Fix wrong GUI behavior in "Optional IP address to bind to" setting (Chocobo1)
|
||||
- WEBUI: Fix WebUI crash due to missing tags from config (An0n)
|
||||
- WEBUI: Show correct location path (Chocobo1)
|
||||
- MACOS: Fix main window freezing after opening a files dialog (glassez)
|
||||
|
||||
Tue Mar 22 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.4.2
|
||||
- FEATURE: Allow to limit max memory working set size (glassez)
|
||||
- BUGFIX: Fix UI crash when torrent is in a non-existent category (Kevin Cox)
|
||||
- BUGFIX: Correctly handle changing of global save paths (glassez)
|
||||
- BUGFIX: Disable performance alert (Chocobo1)
|
||||
- BUGFIX: Prevent loading resume data with inconsistent ID (glassez)
|
||||
- BUGFIX: Properly handle metadata download for an existing torrent (glassez)
|
||||
- BUGFIX: Prevent crash when open torrent destination folder (glassez)
|
||||
- WINDOWS: NSIS: Update Spanish, Spanish International and French translations(Juanjo Jiménez, RqndomHax)
|
||||
|
||||
Tue Feb 15 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.4.1
|
||||
- FEATURE: Restore all torrent settings to the torrent's main context menu (thalieht)
|
||||
- FEATURE: Add confirmation for enabling Auto TMM from context menu (thalieht)
|
||||
- FEATURE: Add tooltip to Automatic Torrent Management context menu action (thalieht)
|
||||
- FEATURE: Add Select All/None buttons in new torrent dialog (thalieht)
|
||||
- BUGFIX: Keep "torrent info" alive while generate .torrent file (glassez)
|
||||
- BUGFIX: Correctly handle Auto TMM in Torrent Files Watcher (glassez)
|
||||
- BUGFIX: Correctly track the root folder name change (glassez)
|
||||
- BUGFIX: Various fixes to the moving torrent code (glassez)
|
||||
- BUGFIX: Update the torrent's download path field when changing category (thalieht)
|
||||
- BUGFIX: Correctly handle received metadata (glassez)
|
||||
- BUGFIX: Store hybrid torrents using legacy filenames (glassez)
|
||||
- BUGFIX: Open correct directory when clicked on Browse button (glassez)
|
||||
- BUGFIX: Fix crash when shutting down and clicing on system tray icon (Chocobo1)
|
||||
- BUGFIX: Fix "Free space on disk" in new torrent dialog (thalieht)
|
||||
- BUGFIX: Optimize completed files handling (Prince Gupta)
|
||||
- BUGFIX: Migrate proxy settings (sledgehammer999)
|
||||
- BUGFIX: Try to recover missing categories (glassez)
|
||||
- WEBUI: WebAPI: fix wrong key used for categories (Chocobo1)
|
||||
- WEBUI: Remove hack for outdated IE 6 browser (Chocobo1)
|
||||
- RSS: Correctly handle XML parsing errors (glassez)
|
||||
|
||||
Thu Jan 06 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.4.0
|
||||
- FEATURE: Support for v2 torrents along with libtorrent 2.0.x support (glassez, Chocobo1)
|
||||
- FEATURE: Support for Qt6 (glassez)
|
||||
|
||||
2
INSTALL
2
INSTALL
@@ -11,7 +11,7 @@ qBittorrent - A BitTorrent client in C++ / Qt
|
||||
|
||||
- OpenSSL >= 1.1.1
|
||||
|
||||
- Qt 5.15.2 - 5.x
|
||||
- Qt 5.15.2 - 5.x || 6.2.0 - 6.x
|
||||
|
||||
- zlib >= 1.2.11
|
||||
|
||||
|
||||
25
cmake/Modules/FindQtTranslations.cmake
Normal file
25
cmake/Modules/FindQtTranslations.cmake
Normal file
@@ -0,0 +1,25 @@
|
||||
# Return Qt translations files as list of paths
|
||||
# It will return .qm files of qt/qtbase that aren't stub files.
|
||||
# Requires that Qt has been found first because it depends on qmake being available
|
||||
|
||||
function(qbt_get_qt_translations qt_translations)
|
||||
get_target_property(QT_QMAKE_EXECUTABLE Qt::qmake IMPORTED_LOCATION)
|
||||
execute_process(COMMAND "${QT_QMAKE_EXECUTABLE}" -query QT_INSTALL_TRANSLATIONS
|
||||
OUTPUT_VARIABLE QT_TRANSLATIONS_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
FILE(GLOB QT_TEMP_TRANSLATIONS CONFIGURE_DEPENDS
|
||||
"${QT_TRANSLATIONS_DIR}/qt_??.qm"
|
||||
"${QT_TRANSLATIONS_DIR}/qt_??_??.qm"
|
||||
"${QT_TRANSLATIONS_DIR}/qtbase_??.qm"
|
||||
"${QT_TRANSLATIONS_DIR}/qtbase_??_??.qm")
|
||||
|
||||
foreach(TRANSLATION ${QT_TEMP_TRANSLATIONS})
|
||||
FILE(SIZE "${TRANSLATION}" translation_size)
|
||||
# Consider files less than 10KB as stub translations
|
||||
if (translation_size GREATER_EQUAL 10240)
|
||||
list(APPEND QT_FINAL_TRANSLATIONS "${TRANSLATION}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
SET(${qt_translations} ${QT_FINAL_TRANSLATIONS} PARENT_SCOPE)
|
||||
endfunction()
|
||||
20
configure
vendored
20
configure
vendored
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.71 for qbittorrent v4.4.0alpha.
|
||||
# Generated by GNU Autoconf 2.71 for qbittorrent v4.4.4.
|
||||
#
|
||||
# Report bugs to <bugs.qbittorrent.org>.
|
||||
#
|
||||
@@ -611,8 +611,8 @@ MAKEFLAGS=
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='qbittorrent'
|
||||
PACKAGE_TARNAME='qbittorrent'
|
||||
PACKAGE_VERSION='v4.4.0alpha'
|
||||
PACKAGE_STRING='qbittorrent v4.4.0alpha'
|
||||
PACKAGE_VERSION='v4.4.4'
|
||||
PACKAGE_STRING='qbittorrent v4.4.4'
|
||||
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
|
||||
PACKAGE_URL='https://www.qbittorrent.org/'
|
||||
|
||||
@@ -1329,7 +1329,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.4.0alpha to adapt to many kinds of systems.
|
||||
\`configure' configures qbittorrent v4.4.4 to adapt to many kinds of systems.
|
||||
|
||||
Usage: $0 [OPTION]... [VAR=VALUE]...
|
||||
|
||||
@@ -1400,7 +1400,7 @@ fi
|
||||
|
||||
if test -n "$ac_init_help"; then
|
||||
case $ac_init_help in
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.4.0alpha:";;
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.4.4:";;
|
||||
esac
|
||||
cat <<\_ACEOF
|
||||
|
||||
@@ -1533,7 +1533,7 @@ fi
|
||||
test -n "$ac_init_help" && exit $ac_status
|
||||
if $ac_init_version; then
|
||||
cat <<\_ACEOF
|
||||
qbittorrent configure v4.4.0alpha
|
||||
qbittorrent configure v4.4.4
|
||||
generated by GNU Autoconf 2.71
|
||||
|
||||
Copyright (C) 2021 Free Software Foundation, Inc.
|
||||
@@ -1648,7 +1648,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.4.0alpha, which was
|
||||
It was created by qbittorrent $as_me v4.4.4, which was
|
||||
generated by GNU Autoconf 2.71. Invocation command line was
|
||||
|
||||
$ $0$ac_configure_args_raw
|
||||
@@ -4779,7 +4779,7 @@ fi
|
||||
|
||||
# Define the identity of the package.
|
||||
PACKAGE='qbittorrent'
|
||||
VERSION='v4.4.0alpha'
|
||||
VERSION='v4.4.4'
|
||||
|
||||
|
||||
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
|
||||
@@ -7254,7 +7254,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.4.0alpha, which was
|
||||
This file was extended by qbittorrent $as_me v4.4.4, which was
|
||||
generated by GNU Autoconf 2.71. Invocation command line was
|
||||
|
||||
CONFIG_FILES = $CONFIG_FILES
|
||||
@@ -7314,7 +7314,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
|
||||
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
|
||||
ac_cs_config='$ac_cs_config_escaped'
|
||||
ac_cs_version="\\
|
||||
qbittorrent config.status v4.4.0alpha
|
||||
qbittorrent config.status v4.4.4
|
||||
configured by $0, generated by GNU Autoconf 2.71,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
AC_INIT([qbittorrent], [v4.4.0alpha], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_INIT([qbittorrent], [v4.4.4], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
: ${CFLAGS=""}
|
||||
|
||||
2
dist/mac/Info.plist
vendored
2
dist/mac/Info.plist
vendored
@@ -55,7 +55,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>4.4.0</string>
|
||||
<string>4.4.4</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
BIN
dist/qt-translations/qt_fa.qm
vendored
BIN
dist/qt-translations/qt_fa.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qt_gl.qm
vendored
BIN
dist/qt-translations/qt_gl.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qt_lt.qm
vendored
BIN
dist/qt-translations/qt_lt.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qt_pt.qm
vendored
BIN
dist/qt-translations/qt_pt.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qt_sl.qm
vendored
BIN
dist/qt-translations/qt_sl.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qt_sv.qm
vendored
BIN
dist/qt-translations/qt_sv.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qt_zh_CN.qm
vendored
BIN
dist/qt-translations/qt_zh_CN.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_ar.qm
vendored
BIN
dist/qt-translations/qtbase_ar.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_bg.qm
vendored
BIN
dist/qt-translations/qtbase_bg.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_ca.qm
vendored
BIN
dist/qt-translations/qtbase_ca.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_cs.qm
vendored
BIN
dist/qt-translations/qtbase_cs.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_da.qm
vendored
BIN
dist/qt-translations/qtbase_da.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_de.qm
vendored
BIN
dist/qt-translations/qtbase_de.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_es.qm
vendored
BIN
dist/qt-translations/qtbase_es.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_fi.qm
vendored
BIN
dist/qt-translations/qtbase_fi.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_fr.qm
vendored
BIN
dist/qt-translations/qtbase_fr.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_gd.qm
vendored
BIN
dist/qt-translations/qtbase_gd.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_he.qm
vendored
BIN
dist/qt-translations/qtbase_he.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_hu.qm
vendored
BIN
dist/qt-translations/qtbase_hu.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_it.qm
vendored
BIN
dist/qt-translations/qtbase_it.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_ja.qm
vendored
BIN
dist/qt-translations/qtbase_ja.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_ko.qm
vendored
BIN
dist/qt-translations/qtbase_ko.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_lv.qm
vendored
BIN
dist/qt-translations/qtbase_lv.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_pl.qm
vendored
BIN
dist/qt-translations/qtbase_pl.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_ru.qm
vendored
BIN
dist/qt-translations/qtbase_ru.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_sk.qm
vendored
BIN
dist/qt-translations/qtbase_sk.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_tr.qm
vendored
BIN
dist/qt-translations/qtbase_tr.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_uk.qm
vendored
BIN
dist/qt-translations/qtbase_uk.qm
vendored
Binary file not shown.
BIN
dist/qt-translations/qtbase_zh_TW.qm
vendored
BIN
dist/qt-translations/qtbase_zh_TW.qm
vendored
Binary file not shown.
@@ -74,6 +74,6 @@
|
||||
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="4.4.0" date="2020-10-18"/>
|
||||
<release version="4.4.4" date="2022-08-22"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
||||
@@ -106,7 +106,7 @@ Name[ja]=qBittorrent
|
||||
Comment[ka]=ჩამოტვირთე და გააზიარე ფაილები Bittorrent-ის საშუალებით
|
||||
GenericName[ka]=BitTorrent კლიენტი
|
||||
Name[ka]=qBittorrent
|
||||
Comment[ko]=BitTorrent를 통해 파일 다운로드 및 공유
|
||||
Comment[ko]=BitTorrent를 통한 파일 다운로드 및 공유
|
||||
GenericName[ko]=BitTorrent 클라이언트
|
||||
Name[ko]=qBittorrent
|
||||
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle
|
||||
@@ -115,6 +115,8 @@ Name[lt]=qBittorrent
|
||||
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
|
||||
GenericName[mk]=BitTorrent клиент
|
||||
Name[mk]=qBittorrent
|
||||
Comment[my]=တောရန့်ဖြင့်ဖိုင်များဒေါင်းလုဒ်ဆွဲရန်နှင့်မျှဝေရန်
|
||||
GenericName[my]=တောရန့်စီမံခန့်ခွဲသည့်အရာ
|
||||
Name[my]=qBittorrent
|
||||
Comment[nb]=Last ned og del filer over BitTorrent
|
||||
GenericName[nb]=BitTorrent-klient
|
||||
|
||||
10
dist/windows/README.txt
vendored
10
dist/windows/README.txt
vendored
@@ -38,10 +38,12 @@ installer-translations
|
||||
translations
|
||||
qt_ar.qm
|
||||
...
|
||||
(all the .qm files found in the 'translations' folder of your Qt install. Those files differ between Qt4 and Qt5.
|
||||
If you want to distribute Qt4 translations it is better to use the ones found in this repo under the path "dist/qt-translations".
|
||||
They contain extra languages not distributed via the official qt4 sources.
|
||||
Don't forget to edit the filelist in installer.nsi + uninstaller.nsi to include all your .qm files.)
|
||||
(All the .qm files found in the 'translations' folder of your Qt install. Those files differ between Qt5 and Qt6.
|
||||
You will need the files that conform to this globbing expression 'qt_??.qm qt_??_??.qm qtbase_??.qm qtbase_??_??.qm'.
|
||||
Some of those files will be stubs. Filter any file that is smaller than 10KB in size.
|
||||
Alternatively you can use the 'gather_qt_translations.py' script found in the same folder as this file.
|
||||
Run it with '--help' to see its usage.
|
||||
**YOU MUST** edit the list of .qm files in the 'installer.nsi' to match whatever files are in the 'translations' subfolder.)
|
||||
qt_zh_TW.qm
|
||||
installer.nsi
|
||||
license.txt
|
||||
|
||||
31
dist/windows/gather_qt_translations.py
vendored
Normal file
31
dist/windows/gather_qt_translations.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
def isNotStub(path: str) -> bool:
|
||||
return (os.path.getsize(path) >= (10 * 1024))
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description='Gather valid Qt translations for NSIS packaging.')
|
||||
parser.add_argument("qt_translations_folder", help="Qt's translations folder")
|
||||
parser.add_argument("nsis_packaging_folder", help="NSIS packaging translations folder")
|
||||
args = parser.parse_args()
|
||||
|
||||
tmp_translations: List[str] = glob.glob(f'{args.qt_translations_folder}/qt_??.qm')
|
||||
tmp_translations += glob.glob(f'{args.qt_translations_folder}/qt_??_??.qm')
|
||||
tmp_translations += glob.glob(f'{args.qt_translations_folder}/qtbase_??.qm')
|
||||
tmp_translations += glob.glob(f'{args.qt_translations_folder}qtbase_??_??.qm')
|
||||
|
||||
filtered = filter(isNotStub, tmp_translations)
|
||||
for file in filtered:
|
||||
shutil.copy2(file, args.nsis_packaging_folder)
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
36
dist/windows/installer-translations/french.nsi
vendored
36
dist/windows/installer-translations/french.nsi
vendored
@@ -3,27 +3,27 @@
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_FRENCH} "qBittorrent (requis)"
|
||||
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_FRENCH} "Créer Raccourci Bureau"
|
||||
LangString inst_dekstop ${LANG_FRENCH} "Créer un Raccourci sur le Bureau"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_FRENCH} "Créer Raccourci dans le Menu Démarrer"
|
||||
LangString inst_startmenu ${LANG_FRENCH} "Créer un Raccourci dans le Menu Démarrer"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_FRENCH} "Démarrez qBittorrent au démarrage de Windows"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_FRENCH} "Ouvrir fichiers .torrent avec qBittorrent"
|
||||
LangString inst_torrent ${LANG_FRENCH} "Ouvrir les fichiers .torrent avec qBittorrent"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_FRENCH} "Ouvrir liens magnet avec qBittorrent"
|
||||
LangString inst_magnet ${LANG_FRENCH} "Ouvrir les liens magnet avec qBittorrent"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_FRENCH} "Ajouter règle Pare-Feu Windows"
|
||||
LangString inst_firewall ${LANG_FRENCH} "Ajouter une règle au Pare-Feu de Windows"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_FRENCH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_FRENCH} "Désactiver la limite de taille du chemin de Windows (limitation de MAX_PATH 260 caractères, nécessite Windows 10 1607 ou plus)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_FRENCH} "Ajout règle Pare-Feu Windows"
|
||||
LangString inst_firewallinfo ${LANG_FRENCH} "Ajout d'une règle au Pare-Feu de Windows"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_FRENCH} "qBittorrent est en cours d'exécution. Veuillez fermer l'application avant l'installation."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_FRENCH} "Une installation précédente a été détectée. Elle sera désinstallée sans supprimer les réglages utilisateur."
|
||||
LangString inst_uninstall_question ${LANG_FRENCH} "Une installation précédente a été détectée. Elle sera désinstallée en conservant les réglages utilisateur."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_FRENCH} "Désinstallation de la version précédente."
|
||||
LangString inst_unist ${LANG_FRENCH} "Désinstallation de la version antérieure."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_FRENCH} "Lancer qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
@@ -31,27 +31,27 @@ LangString inst_requires_64bit ${LANG_FRENCH} "Cet installateur ne fonctionne qu
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_FRENCH} "Cette version de qBittorrent nécessite au moins Windows 7."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_FRENCH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_FRENCH} "Désinstaller qBittorrent"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
|
||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||
LangString remove_files ${LANG_FRENCH} "Supprimer fichiers"
|
||||
LangString remove_files ${LANG_FRENCH} "Supprimer les fichiers"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_FRENCH} "Supprimer raccourcis"
|
||||
LangString remove_shortcuts ${LANG_FRENCH} "Supprimer les raccourcis"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_FRENCH} "Supprimer associations de fichiers"
|
||||
LangString remove_associations ${LANG_FRENCH} "Supprimer les associations de fichiers"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_FRENCH} "Supprimer clés de registre"
|
||||
LangString remove_registry ${LANG_FRENCH} "Supprimer les clés de registre"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_FRENCH} "Supprimer fichiers de configuration"
|
||||
LangString remove_conf ${LANG_FRENCH} "Supprimer les fichiers de configuration"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_FRENCH} "Supprimer règle Pare-Feu Windows"
|
||||
LangString remove_firewall ${LANG_FRENCH} "Supprimer la règle du Pare-Feu de Windows"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_FRENCH} "Suppression règle Pare-Feu Windows"
|
||||
LangString remove_firewallinfo ${LANG_FRENCH} "Suppression de la règle du Pare-Feu de Windows"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_FRENCH} "Supprimer torrents et données cachées"
|
||||
LangString remove_cache ${LANG_FRENCH} "Supprimer les torrents et données cachées"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_FRENCH} "qBittorrent est en cours d'exécution. Veuillez fermer l'application avant la désinstallation."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
|
||||
10
dist/windows/installer-translations/italian.nsi
vendored
10
dist/windows/installer-translations/italian.nsi
vendored
@@ -19,9 +19,9 @@ LangString inst_pathlimit ${LANG_ITALIAN} "Disabilita limite lunghezza percorsi
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_ITALIAN} "Aggiunta regola al firewall di Windows"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_ITALIAN} "qBittorrent è in esecuzione. Chiudilo prima di procedere con l'installazione."
|
||||
LangString inst_warning ${LANG_ITALIAN} "qBittorrent è in esecuzione.$\r$\nChiudilo prima di procedere con l'installazione."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_ITALIAN} "La versione attuale verrà disinstallata. Le impostazioni utente e i torrent rimarranno invariati."
|
||||
LangString inst_uninstall_question ${LANG_ITALIAN} "La versione attuale verrà disinstallata.$\r$\nLe impostazioni utente e i torrent rimarranno invariati."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_ITALIAN} "Disinstallazione versione precedente."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
@@ -53,8 +53,8 @@ LangString remove_firewallinfo ${LANG_ITALIAN} "Rimozione regola dal firewall di
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_ITALIAN} "Rimuovi torrent e dati nella cache"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_ITALIAN} "qBittorrent è in esecuzione. Chiudilo prima di procedere con la disinstallazione."
|
||||
LangString uninst_warning ${LANG_ITALIAN} "qBittorrent è in esecuzione.$\r$\nChiudilo prima di procedere con la disinstallazione."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_ITALIAN} "Associazione file .torrent non rimossa. File associati con:"
|
||||
LangString uninst_tor_warn ${LANG_ITALIAN} "Associazione file .torrent non rimossa.$\r$\nFile associati con:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_ITALIAN} "Associazione file magnet non rimossa. File associati con:"
|
||||
LangString uninst_mag_warn ${LANG_ITALIAN} "Associazione file magnet non rimossa.$\r$\nFile associati con:"
|
||||
|
||||
18
dist/windows/installer-translations/korean.nsi
vendored
18
dist/windows/installer-translations/korean.nsi
vendored
@@ -1,11 +1,11 @@
|
||||
;Installer strings
|
||||
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_KOREAN} "qBittorrent (필요함)"
|
||||
LangString inst_qbt_req ${LANG_KOREAN} "qBittorrent (필요)"
|
||||
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_KOREAN} "바탕화면 바로가기 만들기"
|
||||
LangString inst_dekstop ${LANG_KOREAN} "바탕화면 바로 가기 만들기"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_KOREAN} "시작 메뉴 바로가기 만들기"
|
||||
LangString inst_startmenu ${LANG_KOREAN} "시작 메뉴 바로 가기 만들기"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_KOREAN} "Windows 시작 시 qBittorrent 시작"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
@@ -15,23 +15,23 @@ LangString inst_magnet ${LANG_KOREAN} "qBittorrent로 자석 링크 열기"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_KOREAN} "Windows 방화벽 규칙 추가"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_KOREAN} "Windows 경로 길이 제한 비활성화(260자 MAX_PATH 제한, Windows 10 1607 이상 필요)"
|
||||
LangString inst_pathlimit ${LANG_KOREAN} "Windows 경로 길이 제한 비활성화 (260자 MAX_PATH 제한, Windows 10 1607 이상 필요)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_KOREAN} "Windows 방화벽 규칙 추가하는 중"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_KOREAN} "qBittorrent가 실행 중입니다. 설치하기 전에 응용 프로그램을 닫으십시오."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_KOREAN} "현재 버전이 삭제됩니다. 사용자 설정과 토렌트는 그대로 유지됩니다."
|
||||
LangString inst_uninstall_question ${LANG_KOREAN} "현재 버전이 제거됩니다. 사용자 설정과 토렌트는 그대로 유지됩니다."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_KOREAN} "이전 버전을 삭제하는 중입니다."
|
||||
LangString inst_unist ${LANG_KOREAN} "이전 버전을 제거하는 중입니다."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_KOREAN} "qBittorrent를 실행합니다."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_KOREAN} "이 설치 프로그램은 64비트 윈도우즈 버전에서만 작동합니다."
|
||||
LangString inst_requires_64bit ${LANG_KOREAN} "이 설치 프로그램은 64비트 Windows 버전에서만 작동합니다."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_KOREAN} "이 qBittorrent 버전에는 Windows 7 이상이 필요합니다."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_KOREAN} "qBittorrent 삭제"
|
||||
LangString inst_uninstall_link_description ${LANG_KOREAN} "qBittorrent 제거"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
@@ -39,7 +39,7 @@ LangString inst_uninstall_link_description ${LANG_KOREAN} "qBittorrent 삭제"
|
||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||
LangString remove_files ${LANG_KOREAN} "파일 제거"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_KOREAN} "바로가기 제거"
|
||||
LangString remove_shortcuts ${LANG_KOREAN} "바로 가기 제거"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_KOREAN} "파일 연결 제거"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
|
||||
54
dist/windows/installer-translations/latvian.nsi
vendored
54
dist/windows/installer-translations/latvian.nsi
vendored
@@ -1,60 +1,60 @@
|
||||
;Installer strings
|
||||
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_LATVIAN} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_LATVIAN} "qBittorrent (nepieciešams)"
|
||||
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_LATVIAN} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_LATVIAN} "Izveidot saīsni uz darbvirsmas"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_LATVIAN} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_LATVIAN} "Izveidot izvēlnes Sākt saīsnes"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_LATVIAN} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_LATVIAN} "Startēt qBittorrent Windows startēšanas laikā"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_LATVIAN} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_LATVIAN} "Atvērt .torrent failus ar qBittorrent"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_LATVIAN} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_LATVIAN} "Atvērt magnētu saites ar qBittorrent"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_LATVIAN} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_LATVIAN} "Pievienot Windows ugunsmūra noteikumu"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_LATVIAN} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_LATVIAN} "Atspējot Windows ceļa garuma ierobežojumu (260 rakstzīmju MAX_PATH ierobežojums, nepieciešams Windows 10 1607 vai jaunāka versija)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_LATVIAN} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_LATVIAN} "Windows ugunsmūra noteikumu pievienošana"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_LATVIAN} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_LATVIAN} "qBittorrent darbojas. Lūdzu, aizveriet programmu pirms instalēšanas."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_LATVIAN} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_LATVIAN} "Pašreizējā versija tiks atinstalēta. Lietotāju iestatījumi un torrenti paliks neskarti."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_LATVIAN} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_LATVIAN} "Iepriekšējās versijas atinstalēšana."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_LATVIAN} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_LATVIAN} "Palaist qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_LATVIAN} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_LATVIAN} "Šī instalēšanas programma darbojas tikai 64 bitu Windows versijās."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_LATVIAN} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_LATVIAN} "Šai qBittorrent versijai ir nepieciešama vismaz Windows 7."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_LATVIAN} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_LATVIAN} "Atinstalēt qBittorrent"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
|
||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||
LangString remove_files ${LANG_LATVIAN} "Remove files"
|
||||
LangString remove_files ${LANG_LATVIAN} "Dzēš failus"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_LATVIAN} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_LATVIAN} "Dzēš saīsnes"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_LATVIAN} "Remove file associations"
|
||||
LangString remove_associations ${LANG_LATVIAN} "Noņem failu asociācijas"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_LATVIAN} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_LATVIAN} "Dzēš reģistra atslēgas"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_LATVIAN} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_LATVIAN} "Dzēš konfigurācijas failus"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_LATVIAN} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_LATVIAN} "Dzēst Windows ugunsmūra noteikumu"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_LATVIAN} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_LATVIAN} "Dzēš Windows ugunsmūra noteikumu"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_LATVIAN} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_LATVIAN} "Dzēš torrentus un kešatmiņas datus"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_LATVIAN} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_LATVIAN} "qBittorrent darbojas. Lūdzu, aizveriet programmu pirms atinstalēšanas."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_LATVIAN} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_LATVIAN} "Netiek dzēsta .torrent asociācija. Tā ir saistīta ar:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_LATVIAN} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_LATVIAN} "Netiek dzēsta magnēta asociācija. Tā ir saistīta ar:"
|
||||
|
||||
@@ -15,23 +15,23 @@ LangString inst_magnet ${LANG_PORTUGUESE} "Abrir ligações magnet com o qBittor
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_PORTUGUESE} "Adicionar regra à firewall do Windows"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_PORTUGUESE} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_PORTUGUESE} "Desativar o limite do tamanho do caminho do Windows (limitação de MAX_PATH de 260 caracteres, requer o Windows 10 1607 ou superior)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_PORTUGUESE} "Adicionando regra à firewall do Windows"
|
||||
LangString inst_firewallinfo ${LANG_PORTUGUESE} "A adicionar regra à firewall do Windows"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_PORTUGUESE} "O qBittorrent está a ser executado. Feche a aplicação antes de instalar esta versão."
|
||||
LangString inst_warning ${LANG_PORTUGUESE} "O qBittorrent está a ser executado. Por favor, feche a aplicação antes de instalar esta versão."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_PORTUGUESE} "Uma antiga instalação foi encontrada.Essa mesma será desinstalada sem apagar as definições do usuário."
|
||||
LangString inst_uninstall_question ${LANG_PORTUGUESE} "A versão atual será desinstalada. As definições do utilizador e os torrents permanecerão intactas."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_PORTUGUESE} "A desinstalar versão anterior."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_PORTUGUESE} "Iniciar qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_PORTUGUESE} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_PORTUGUESE} "Este instalador funciona apenas em versões Windows de 64 bits."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_PORTUGUESE} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_PORTUGUESE} "Esta versão qBittorrent requer pelo menos o Windows 7."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_PORTUGUESE} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_PORTUGUESE} "Desinstalar qBittorrent"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
@@ -41,7 +41,7 @@ LangString remove_files ${LANG_PORTUGUESE} "Remover ficheiros"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_PORTUGUESE} "Remover atalhos"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_PORTUGUESE} "Remove associação de ficheiros"
|
||||
LangString remove_associations ${LANG_PORTUGUESE} "Remover associação de ficheiros"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_PORTUGUESE} "Remover chaves de registo"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
@@ -49,12 +49,12 @@ LangString remove_conf ${LANG_PORTUGUESE} "Remover ficheiros de configuração"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_PORTUGUESE} "Remover regra da firewall do Windows"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_PORTUGUESE} "Removendo regra da firewall do Windows"
|
||||
LangString remove_firewallinfo ${LANG_PORTUGUESE} "A remover regra da firewall do Windows"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_PORTUGUESE} "Remover torrents e dados guardados"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_PORTUGUESE} "O qBittorrent está a ser executado. Feche a aplicação antes de desinstalar esta versão."
|
||||
LangString uninst_warning ${LANG_PORTUGUESE} "O qBittorrent está a ser executado. Por favor, feche a aplicação antes de desinstalar esta versão."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_PORTUGUESE} "Associação .torrent não removida. Ficheiros associados a:"
|
||||
LangString uninst_tor_warn ${LANG_PORTUGUESE} "Não pode remover a associação do .torrent. Está associado a:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_PORTUGUESE} "Associação magnet nã removida. Ligações associadas a:"
|
||||
LangString uninst_mag_warn ${LANG_PORTUGUESE} "Não pode remover a associação do magnet. Está associado a:"
|
||||
|
||||
10
dist/windows/installer-translations/spanish.nsi
vendored
10
dist/windows/installer-translations/spanish.nsi
vendored
@@ -7,7 +7,7 @@ LangString inst_dekstop ${LANG_SPANISH} "Crear un acceso directo en el escritori
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_SPANISH} "Crear un acceso directo en el menú inicio"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_SPANISH} "Inicie qBittorrent en el inicio de Windows"
|
||||
LangString inst_startup ${LANG_SPANISH} "Iniciar qBittorrent en el inicio de Windows"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_SPANISH} "Abrir archivos .torrent con qBittorrent"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
@@ -15,7 +15,7 @@ LangString inst_magnet ${LANG_SPANISH} "Abrir enlaces magnet con qBittorrent"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_SPANISH} "Añadir regla al Firewall de Windows"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_SPANISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_SPANISH} "Deshabilitar límite de caracteres del PATH de Windows (limitación MAX_PATH de 260 caracteres, requiere Windows 10 1607 o superior)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_SPANISH} "Añadiendo regla al Firewall de Windows"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
@@ -29,9 +29,9 @@ LangString launch_qbt ${LANG_SPANISH} "Iniciar qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_SPANISH} "Este instalador solo funciona en versiones de 64-bit de Windows."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_SPANISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_SPANISH} "Esta versión de qBittorrent requiere Windows 7 o superior."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_SPANISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_SPANISH} "Desinstalar qBittorrent"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
@@ -41,7 +41,7 @@ LangString remove_files ${LANG_SPANISH} "Quitar archivos"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_SPANISH} "Quitar accesos directos"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_SPANISH} "Deshacer asociaciones"
|
||||
LangString remove_associations ${LANG_SPANISH} "Deshacer asociaciones de archivos"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_SPANISH} "Eliminar claves del registro"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
|
||||
@@ -7,7 +7,7 @@ LangString inst_dekstop ${LANG_SPANISHINTERNATIONAL} "Crear un acceso directo en
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_SPANISHINTERNATIONAL} "Crear un acceso directo en el menú inicio"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_SPANISHINTERNATIONAL} "Inicie qBittorrent en el inicio de Windows"
|
||||
LangString inst_startup ${LANG_SPANISHINTERNATIONAL} "Iniciar qBittorrent en el inicio de Windows"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_SPANISHINTERNATIONAL} "Abrir archivos .torrent con qBittorrent"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
@@ -15,7 +15,7 @@ LangString inst_magnet ${LANG_SPANISHINTERNATIONAL} "Abrir enlaces magnet con qB
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_SPANISHINTERNATIONAL} "Añadir regla al Firewall de Windows"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_SPANISHINTERNATIONAL} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_SPANISHINTERNATIONAL} "Deshabilitar límite de caracteres del PATH de Windows (limitación MAX_PATH de 260 caracteres, requiere Windows 10 1607 o superior)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_SPANISHINTERNATIONAL} "Añadiendo regla al Firewall de Windows"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
@@ -29,9 +29,9 @@ LangString launch_qbt ${LANG_SPANISHINTERNATIONAL} "Iniciar qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_SPANISHINTERNATIONAL} "Este instalador solo funciona en versiones de 64-bit de Windows."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_SPANISHINTERNATIONAL} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_SPANISHINTERNATIONAL} "Esta versión de qBittorrent requiere Windows 7 o superior."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_SPANISHINTERNATIONAL} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_SPANISHINTERNATIONAL} "Desinstalar qBittorrent"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
@@ -41,7 +41,7 @@ LangString remove_files ${LANG_SPANISHINTERNATIONAL} "Quitar archivos"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_SPANISHINTERNATIONAL} "Quitar accesos directos"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_SPANISHINTERNATIONAL} "Deshacer asociaciones"
|
||||
LangString remove_associations ${LANG_SPANISHINTERNATIONAL} "Deshacer asociaciones de archivos"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_SPANISHINTERNATIONAL} "Eliminar claves del registro"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
|
||||
2
dist/windows/options.nsi
vendored
2
dist/windows/options.nsi
vendored
@@ -28,7 +28,7 @@ XPStyle on
|
||||
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
|
||||
|
||||
; Program specific
|
||||
!define PROG_VERSION "4.4.0"
|
||||
!define PROG_VERSION "4.4.4"
|
||||
|
||||
!define MUI_FINISHPAGE_RUN
|
||||
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
|
||||
|
||||
30
macxconf.pri
30
macxconf.pri
@@ -7,13 +7,31 @@ else {
|
||||
include(conf.pri)
|
||||
}
|
||||
|
||||
# Custom function
|
||||
# Return Qt translations files as list of paths
|
||||
# It will return .qm files of qt/qtbase that aren't stub files.
|
||||
defineReplace(qbt_get_qt_translations) {
|
||||
# The $$[] syntax queries qmake properties
|
||||
TMP_TRANSLATIONS = $$files($$[QT_INSTALL_TRANSLATIONS]/qt_??.qm)
|
||||
TMP_TRANSLATIONS += $$files($$[QT_INSTALL_TRANSLATIONS]/qt_??_??.qm)
|
||||
TMP_TRANSLATIONS += $$files($$[QT_INSTALL_TRANSLATIONS]/qtbase_??.qm)
|
||||
TMP_TRANSLATIONS += $$files($$[QT_INSTALL_TRANSLATIONS]/qtbase_??_??.qm)
|
||||
|
||||
# Consider files less than 10KB as stub translations
|
||||
for (TRANSLATION, TMP_TRANSLATIONS) {
|
||||
TRANSLATION_SIZE = $$system("stat -f%z $${TRANSLATION}", true, EXIT_STATUS)
|
||||
equals(EXIT_STATUS, 0):!lessThan(TRANSLATION_SIZE, 10240): FINAL_TRANSLATIONS += $${TRANSLATION}
|
||||
}
|
||||
|
||||
return($$FINAL_TRANSLATIONS)
|
||||
}
|
||||
|
||||
QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14
|
||||
|
||||
DEFINES += _DARWIN_FEATURE_64_BIT_INODE
|
||||
|
||||
LIBS += -framework Carbon -framework IOKit -framework AppKit
|
||||
|
||||
QT_LANG_PATH = ../dist/qt-translations
|
||||
DIST_PATH = ../dist/mac
|
||||
|
||||
document_icon.path = Contents/Resources
|
||||
@@ -25,15 +43,7 @@ qt_conf.files = $$DIST_PATH/qt.conf
|
||||
QMAKE_BUNDLE_DATA += qt_conf
|
||||
|
||||
qt_translations.path = Contents/translations
|
||||
qt_translations.files = $$files($$QT_LANG_PATH/qtbase_*.qm)
|
||||
qt_translations.files += \
|
||||
$$QT_LANG_PATH/qt_fa.qm \
|
||||
$$QT_LANG_PATH/qt_gl.qm \
|
||||
$$QT_LANG_PATH/qt_lt.qm \
|
||||
$$QT_LANG_PATH/qt_pt.qm \
|
||||
$$QT_LANG_PATH/qt_sl.qm \
|
||||
$$QT_LANG_PATH/qt_sv.qm \
|
||||
$$QT_LANG_PATH/qt_zh_CN.qm
|
||||
qt_translations.files = $$qbt_get_qt_translations()
|
||||
QMAKE_BUNDLE_DATA += qt_translations
|
||||
|
||||
ICON = $$DIST_PATH/qbittorrent_mac.icns
|
||||
|
||||
@@ -16,11 +16,6 @@ if (WEBUI)
|
||||
"${qBittorrent_BINARY_DIR}/src/webui/www/translations/webui_translations.qrc" COPYONLY)
|
||||
endif()
|
||||
|
||||
FILE(GLOB QT_TRANSLATIONS "${qBittorrent_SOURCE_DIR}/dist/qt-translations/qtbase_*.qm")
|
||||
foreach(EXTRA_TRANSLATION IN ITEMS "fa" "gl" "lt" "pt" "sl" "sv" "zh_CN")
|
||||
list(APPEND QT_TRANSLATIONS "${qBittorrent_SOURCE_DIR}/dist/qt-translations/qt_${EXTRA_TRANSLATION}.qm")
|
||||
endforeach()
|
||||
|
||||
# Executable target configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -65,16 +60,17 @@ endif()
|
||||
# Additional platform specific configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
# -----------------------------------------------------------------------------
|
||||
set_source_files_properties(${QT_TRANSLATIONS} PROPERTIES MACOSX_PACKAGE_LOCATION translations)
|
||||
set_source_files_properties(
|
||||
"${qBittorrent_SOURCE_DIR}/dist/mac/qt.conf"
|
||||
"${qBittorrent_SOURCE_DIR}/dist/mac/qBitTorrentDocument.icns"
|
||||
"${qBittorrent_SOURCE_DIR}/dist/mac/qbittorrent_mac.icns"
|
||||
PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION Resources
|
||||
)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
include(FindQtTranslations)
|
||||
qbt_get_qt_translations(QT_TRANSLATIONS)
|
||||
set_source_files_properties(${QT_TRANSLATIONS} PROPERTIES MACOSX_PACKAGE_LOCATION translations)
|
||||
set_source_files_properties(
|
||||
"${qBittorrent_SOURCE_DIR}/dist/mac/qt.conf"
|
||||
"${qBittorrent_SOURCE_DIR}/dist/mac/qBitTorrentDocument.icns"
|
||||
"${qBittorrent_SOURCE_DIR}/dist/mac/qbittorrent_mac.icns"
|
||||
PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION Resources
|
||||
)
|
||||
# provide variables for substitution in dist/mac/Info.plist
|
||||
get_target_property(EXECUTABLE_NAME qbt_app OUTPUT_NAME)
|
||||
# This variable name should be changed once qmake is no longer used. Refer to the discussion in PR #14813
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "base/exceptions.h"
|
||||
#include "base/global.h"
|
||||
#include "base/iconprovider.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/downloadmanager.h"
|
||||
@@ -121,6 +122,9 @@ Application::Application(int &argc, char **argv)
|
||||
, m_running(false)
|
||||
, m_shutdownAct(ShutdownDialogAction::Exit)
|
||||
, m_commandLineArgs(parseCommandLine(this->arguments()))
|
||||
#ifdef Q_OS_WIN
|
||||
, m_storeMemoryWorkingSetLimit(SETTINGS_KEY("MemoryWorkingSetLimit"))
|
||||
#endif
|
||||
, m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY("Enabled"))
|
||||
, m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY("Backup"))
|
||||
, m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY("DeleteOld"))
|
||||
@@ -204,6 +208,24 @@ const QBtCommandLineParameters &Application::commandLineArgs() const
|
||||
return m_commandLineArgs;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
int Application::memoryWorkingSetLimit() const
|
||||
{
|
||||
return m_storeMemoryWorkingSetLimit.get(512);
|
||||
}
|
||||
|
||||
void Application::setMemoryWorkingSetLimit(const int size)
|
||||
{
|
||||
if (size == memoryWorkingSetLimit())
|
||||
return;
|
||||
|
||||
m_storeMemoryWorkingSetLimit = size;
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
applyMemoryWorkingSetLimit();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Application::isFileLoggerEnabled() const
|
||||
{
|
||||
return m_storeFileLoggerEnabled.get(true);
|
||||
@@ -382,14 +404,13 @@ void Application::runExternalProgram(const BitTorrent::Torrent *torrent) const
|
||||
LogMsg(tr("Torrent: %1, running external program, command: %2").arg(torrent->name(), program));
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
auto programWchar = std::make_unique<wchar_t[]>(program.length() + 1);
|
||||
program.toWCharArray(programWchar.get());
|
||||
const std::wstring programWStr = program.toStdWString();
|
||||
|
||||
// Need to split arguments manually because QProcess::startDetached(QString)
|
||||
// will strip off empty parameters.
|
||||
// E.g. `python.exe "1" "" "3"` will become `python.exe "1" "3"`
|
||||
int argCount = 0;
|
||||
std::unique_ptr<LPWSTR[], decltype(&::LocalFree)> args {::CommandLineToArgvW(programWchar.get(), &argCount), ::LocalFree};
|
||||
std::unique_ptr<LPWSTR[], decltype(&::LocalFree)> args {::CommandLineToArgvW(programWStr.c_str(), &argCount), ::LocalFree};
|
||||
|
||||
QStringList argList;
|
||||
for (int i = 1; i < argCount; ++i)
|
||||
@@ -602,6 +623,10 @@ void Application::processParams(const QStringList ¶ms)
|
||||
|
||||
int Application::exec(const QStringList ¶ms)
|
||||
{
|
||||
#if (defined(Q_OS_WIN) && defined(QBT_USES_LIBTORRENT2))
|
||||
applyMemoryWorkingSetLimit();
|
||||
#endif
|
||||
|
||||
Net::ProxyConfigurationManager::initInstance();
|
||||
Net::DownloadManager::initInstance();
|
||||
IconProvider::initInstance();
|
||||
@@ -650,8 +675,8 @@ int Application::exec(const QStringList ¶ms)
|
||||
const auto scheme = QString::fromLatin1(pref->isWebUiHttpsEnabled() ? "https" : "http");
|
||||
const auto url = QString::fromLatin1("%1://localhost:%2\n").arg(scheme, QString::number(pref->getWebUiPort()));
|
||||
const QString mesg = QString::fromLatin1("\n******** %1 ********\n").arg(tr("Information"))
|
||||
+ tr("To control qBittorrent, access the WebUI at: %1\n").arg(url);
|
||||
printf("%s", qUtf8Printable(mesg));
|
||||
+ tr("To control qBittorrent, access the WebUI at: %1").arg(url);
|
||||
printf("%s\n", qUtf8Printable(mesg));
|
||||
|
||||
if (pref->getWebUIPassword() == "ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==")
|
||||
{
|
||||
@@ -771,6 +796,29 @@ void Application::shutdownCleanup(QSessionManager &manager)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (defined(Q_OS_WIN) && defined(QBT_USES_LIBTORRENT2))
|
||||
void Application::applyMemoryWorkingSetLimit()
|
||||
{
|
||||
const SIZE_T UNIT_SIZE = 1024 * 1024; // MiB
|
||||
const SIZE_T maxSize = memoryWorkingSetLimit() * UNIT_SIZE;
|
||||
const SIZE_T minSize = std::min<SIZE_T>((64 * UNIT_SIZE), (maxSize / 2));
|
||||
if (!::SetProcessWorkingSetSizeEx(::GetCurrentProcess(), minSize, maxSize, QUOTA_LIMITS_HARDWS_MAX_ENABLE))
|
||||
{
|
||||
const DWORD errorCode = ::GetLastError();
|
||||
QString message;
|
||||
LPVOID lpMsgBuf = nullptr;
|
||||
if (::FormatMessageW((FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS)
|
||||
, nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast<LPWSTR>(&lpMsgBuf), 0, nullptr))
|
||||
{
|
||||
message = QString::fromWCharArray(reinterpret_cast<LPWSTR>(lpMsgBuf)).trimmed();
|
||||
::LocalFree(lpMsgBuf);
|
||||
}
|
||||
LogMsg(tr("Failed to set physical memory (RAM) usage limit. Error code: %1. Error message: \"%2\"")
|
||||
.arg(QString::number(errorCode), message), Log::WARNING);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Application::cleanup()
|
||||
{
|
||||
// cleanup() can be called multiple times during shutdown. We only need it once.
|
||||
@@ -788,8 +836,9 @@ void Application::cleanup()
|
||||
m_window->hide();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
const std::wstring msg = tr("Saving torrent progress...").toStdWString();
|
||||
::ShutdownBlockReasonCreate(reinterpret_cast<HWND>(m_window->effectiveWinId())
|
||||
, tr("Saving torrent progress...").toStdWString().c_str());
|
||||
, msg.c_str());
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
// Do manual cleanup in MainWindow to force widgets
|
||||
|
||||
@@ -88,6 +88,11 @@ public:
|
||||
|
||||
const QBtCommandLineParameters &commandLineArgs() const;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
int memoryWorkingSetLimit() const;
|
||||
void setMemoryWorkingSetLimit(int size);
|
||||
#endif
|
||||
|
||||
// FileLogger properties
|
||||
bool isFileLoggerEnabled() const;
|
||||
void setFileLoggerEnabled(bool value);
|
||||
@@ -121,6 +126,9 @@ private slots:
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if (defined(Q_OS_WIN) && defined(QBT_USES_LIBTORRENT2))
|
||||
void applyMemoryWorkingSetLimit();
|
||||
#endif
|
||||
void initializeTranslation();
|
||||
void processParams(const QStringList ¶ms);
|
||||
void runExternalProgram(const BitTorrent::Torrent *torrent) const;
|
||||
@@ -146,6 +154,9 @@ private:
|
||||
QTranslator m_translator;
|
||||
QStringList m_paramsQueue;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
SettingValue<int> m_storeMemoryWorkingSetLimit;
|
||||
#endif
|
||||
SettingValue<bool> m_storeFileLoggerEnabled;
|
||||
SettingValue<bool> m_storeFileLoggerBackup;
|
||||
SettingValue<bool> m_storeFileLoggerDeleteOld;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
#include <sys/resource.h>
|
||||
@@ -321,17 +322,13 @@ int main(int argc, char *argv[])
|
||||
void reportToUser(const char *str)
|
||||
{
|
||||
const size_t strLen = strlen(str);
|
||||
#ifndef Q_OS_WIN
|
||||
if (write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen))
|
||||
{
|
||||
const auto dummy = write(STDOUT_FILENO, str, strLen);
|
||||
#ifdef Q_OS_WIN
|
||||
if (_write(_fileno(stderr), str, strLen) < static_cast<int>(strLen))
|
||||
std::ignore = _write(_fileno(stdout), str, strLen);
|
||||
#else
|
||||
if (_write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen))
|
||||
{
|
||||
const auto dummy = _write(STDOUT_FILENO, str, strLen);
|
||||
if (write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen))
|
||||
std::ignore = write(STDOUT_FILENO, str, strLen);
|
||||
#endif
|
||||
Q_UNUSED(dummy);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -360,8 +357,10 @@ void sigAbnormalHandler(int signum)
|
||||
reportToUser(msg);
|
||||
reportToUser(sigName);
|
||||
reportToUser("\n");
|
||||
#if !defined Q_OS_WIN
|
||||
print_stacktrace(); // unsafe
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined Q_OS_WIN && !defined DISABLE_GUI
|
||||
StacktraceDialog dlg; // unsafe
|
||||
|
||||
@@ -87,9 +87,11 @@ Qt::HANDLE QtLockedFile::getMutexHandle(const int idx, const bool doCreate)
|
||||
if (idx >= 0)
|
||||
mname += QString::number(idx);
|
||||
|
||||
const std::wstring mnameWStr = mname.toStdWString();
|
||||
|
||||
if (doCreate)
|
||||
{
|
||||
const Qt::HANDLE mutex = ::CreateMutexW(NULL, FALSE, reinterpret_cast<const TCHAR *>(mname.utf16()));
|
||||
const Qt::HANDLE mutex = ::CreateMutexW(NULL, FALSE, mnameWStr.c_str());
|
||||
if (!mutex)
|
||||
{
|
||||
qErrnoWarning("QtLockedFile::lock(): CreateMutex failed");
|
||||
@@ -100,7 +102,7 @@ Qt::HANDLE QtLockedFile::getMutexHandle(const int idx, const bool doCreate)
|
||||
}
|
||||
else
|
||||
{
|
||||
const Qt::HANDLE mutex = ::OpenMutexW((SYNCHRONIZE | MUTEX_MODIFY_STATE), FALSE, reinterpret_cast<const TCHAR *>(mname.utf16()));
|
||||
const Qt::HANDLE mutex = ::OpenMutexW((SYNCHRONIZE | MUTEX_MODIFY_STATE), FALSE, mnameWStr.c_str());
|
||||
if (!mutex)
|
||||
{
|
||||
if (GetLastError() != ERROR_FILE_NOT_FOUND)
|
||||
|
||||
@@ -255,9 +255,10 @@ const QString straceWin::getBacktrace()
|
||||
QTextStream logStream(&log);
|
||||
logStream << "```\n";
|
||||
|
||||
const std::wstring appPath = QCoreApplication::applicationDirPath().toStdWString();
|
||||
HANDLE hProcess = GetCurrentProcess();
|
||||
HANDLE hThread = GetCurrentThread();
|
||||
SymInitializeW(hProcess, QCoreApplication::applicationDirPath().toStdWString().c_str(), TRUE);
|
||||
SymInitializeW(hProcess, appPath.c_str(), TRUE);
|
||||
|
||||
DWORD64 dwDisplacement;
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#include "base/bittorrent/torrentcontentlayout.h"
|
||||
#include "base/logger.h"
|
||||
#include "base/net/proxyconfigurationmanager.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/profile.h"
|
||||
#include "base/settingsstorage.h"
|
||||
@@ -42,7 +43,7 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
const int MIGRATION_VERSION = 2;
|
||||
const int MIGRATION_VERSION = 3;
|
||||
const char MIGRATION_VERSION_KEY[] = "Meta/MigrationVersion";
|
||||
|
||||
void exportWebUIHttpsFiles()
|
||||
@@ -326,6 +327,46 @@ namespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void migrateProxySettingsEnum()
|
||||
{
|
||||
auto *settingsStorage = SettingsStorage::instance();
|
||||
const auto key = QString::fromLatin1("Network/Proxy/Type");
|
||||
const auto value = settingsStorage->loadValue<QString>(key);
|
||||
|
||||
bool ok = false;
|
||||
const auto number = value.toInt(&ok);
|
||||
|
||||
if (ok)
|
||||
{
|
||||
switch (number)
|
||||
{
|
||||
case 0:
|
||||
settingsStorage->storeValue(key, Net::ProxyType::None);
|
||||
break;
|
||||
case 1:
|
||||
settingsStorage->storeValue(key, Net::ProxyType::HTTP);
|
||||
break;
|
||||
case 2:
|
||||
settingsStorage->storeValue(key, Net::ProxyType::SOCKS5);
|
||||
break;
|
||||
case 3:
|
||||
settingsStorage->storeValue(key, Net::ProxyType::HTTP_PW);
|
||||
break;
|
||||
case 4:
|
||||
settingsStorage->storeValue(key, Net::ProxyType::SOCKS5_PW);
|
||||
break;
|
||||
case 5:
|
||||
settingsStorage->storeValue(key, Net::ProxyType::SOCKS4);
|
||||
break;
|
||||
default:
|
||||
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
|
||||
.arg(key, QString::number(number)), Log::WARNING);
|
||||
settingsStorage->removeValue(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool upgrade(const bool /*ask*/)
|
||||
@@ -343,9 +384,13 @@ bool upgrade(const bool /*ask*/)
|
||||
upgradeDNSServiceSettings();
|
||||
upgradeTrayIconStyleSettings();
|
||||
}
|
||||
|
||||
if (version < 2)
|
||||
migrateSettingKeys();
|
||||
|
||||
if (version < 3)
|
||||
migrateProxySettingsEnum();
|
||||
|
||||
version = MIGRATION_VERSION;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlError>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlRecord>
|
||||
#include <QThread>
|
||||
#include <QVector>
|
||||
|
||||
@@ -206,7 +207,7 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const QString &dbPath, QObj
|
||||
else
|
||||
{
|
||||
const int dbVersion = currentDBVersion();
|
||||
if (dbVersion == 1)
|
||||
if ((dbVersion == 1) || !db.record(DB_TABLE_TORRENTS).contains(DB_COLUMN_DOWNLOAD_PATH.name))
|
||||
updateDBFromVersion1();
|
||||
}
|
||||
|
||||
@@ -325,9 +326,6 @@ std::optional<BitTorrent::LoadTorrentParams> BitTorrent::DBResumeDataStorage::lo
|
||||
lt::error_code ec;
|
||||
const lt::bdecode_node root = lt::bdecode(allData, ec);
|
||||
|
||||
resumeData.downloadPath = Profile::instance()->fromPortablePath(
|
||||
Utils::Fs::toUniformPath(fromLTString(root.dict_find_string_value("qBt-downloadPath"))));
|
||||
|
||||
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
|
||||
|
||||
p = lt::read_resume_data(root, ec);
|
||||
@@ -425,6 +423,7 @@ void BitTorrent::DBResumeDataStorage::createDB() const
|
||||
makeColumnDefinition(DB_COLUMN_CATEGORY, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_TAGS, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_TARGET_SAVE_PATH, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_CONTENT_LAYOUT, "TEXT NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_RATIO_LIMIT, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_SEEDING_TIME_LIMIT, "INTEGER NOT NULL"),
|
||||
|
||||
@@ -36,6 +36,13 @@ BitTorrent::InfoHash::InfoHash(const WrappedType &nativeHash)
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
BitTorrent::InfoHash::InfoHash(const SHA1Hash &v1, const SHA256Hash &v2)
|
||||
: InfoHash {WrappedType(v1, v2)}
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
bool BitTorrent::InfoHash::isValid() const
|
||||
{
|
||||
return m_valid;
|
||||
|
||||
@@ -65,6 +65,9 @@ namespace BitTorrent
|
||||
|
||||
InfoHash() = default;
|
||||
InfoHash(const WrappedType &nativeHash);
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
InfoHash(const SHA1Hash &v1, const SHA256Hash &v2);
|
||||
#endif
|
||||
|
||||
bool isValid() const;
|
||||
SHA1Hash v1() const;
|
||||
@@ -85,3 +88,6 @@ namespace BitTorrent
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(BitTorrent::TorrentID)
|
||||
// We can declare it as Q_MOVABLE_TYPE to improve performance
|
||||
// since base type uses QSharedDataPointer as the only member
|
||||
Q_DECLARE_TYPEINFO(BitTorrent::TorrentID, Q_MOVABLE_TYPE);
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace
|
||||
NativeTorrentExtension::NativeTorrentExtension(const lt::torrent_handle &torrentHandle)
|
||||
: m_torrentHandle {torrentHandle}
|
||||
{
|
||||
on_state(m_torrentHandle.status({}).state);
|
||||
}
|
||||
|
||||
bool NativeTorrentExtension::on_pause()
|
||||
@@ -56,7 +57,10 @@ bool NativeTorrentExtension::on_pause()
|
||||
void NativeTorrentExtension::on_state(const lt::torrent_status::state_t state)
|
||||
{
|
||||
if (m_state == lt::torrent_status::downloading_metadata)
|
||||
m_torrentHandle.set_flags(lt::torrent_flags::stop_when_ready);
|
||||
{
|
||||
m_torrentHandle.unset_flags(lt::torrent_flags::auto_managed);
|
||||
m_torrentHandle.pause();
|
||||
}
|
||||
|
||||
m_state = state;
|
||||
}
|
||||
|
||||
@@ -279,8 +279,9 @@ namespace
|
||||
if (!uuid.isNull())
|
||||
return uuid.toString().toUpper(); // Libtorrent expects the GUID in uppercase
|
||||
|
||||
const std::wstring nameWStr = name.toStdWString();
|
||||
NET_LUID luid {};
|
||||
const LONG res = ::ConvertInterfaceNameToLuidW(name.toStdWString().c_str(), &luid);
|
||||
const LONG res = ::ConvertInterfaceNameToLuidW(nameWStr.c_str(), &luid);
|
||||
if (res == 0)
|
||||
{
|
||||
GUID guid;
|
||||
@@ -291,6 +292,20 @@ namespace
|
||||
return {};
|
||||
}
|
||||
#endif
|
||||
|
||||
TorrentID resumeDataID(const BitTorrent::Torrent *torrent)
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
// use legacy ID to store hybrid torrent
|
||||
const InfoHash infoHash = torrent->infoHash();
|
||||
const TorrentID id = ((infoHash.v1().isValid() && infoHash.v2().isValid())
|
||||
? InfoHash(infoHash.v1(), SHA256Hash()).toTorrentID()
|
||||
: torrent->id());
|
||||
#else
|
||||
const TorrentID id = torrent->id();
|
||||
#endif
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
const int addTorrentParamsId = qRegisterMetaType<AddTorrentParams>();
|
||||
@@ -313,7 +328,7 @@ Session::Session(QObject *parent)
|
||||
, m_announceToAllTrackers(BITTORRENT_SESSION_KEY("AnnounceToAllTrackers"), false)
|
||||
, m_announceToAllTiers(BITTORRENT_SESSION_KEY("AnnounceToAllTiers"), true)
|
||||
, m_asyncIOThreads(BITTORRENT_SESSION_KEY("AsyncIOThreadsCount"), 10)
|
||||
, m_hashingThreads(BITTORRENT_SESSION_KEY("HashingThreadsCount"), 2)
|
||||
, m_hashingThreads(BITTORRENT_SESSION_KEY("HashingThreadsCount"), 1)
|
||||
, m_filePoolSize(BITTORRENT_SESSION_KEY("FilePoolSize"), 5000)
|
||||
, m_checkingMemUsage(BITTORRENT_SESSION_KEY("CheckingMemUsageSize"), 32)
|
||||
, m_diskCacheSize(BITTORRENT_SESSION_KEY("DiskCacheSize"), -1)
|
||||
@@ -343,7 +358,7 @@ Session::Session(QObject *parent)
|
||||
, m_outgoingPortsMin(BITTORRENT_SESSION_KEY("OutgoingPortsMin"), 0)
|
||||
, m_outgoingPortsMax(BITTORRENT_SESSION_KEY("OutgoingPortsMax"), 0)
|
||||
, m_UPnPLeaseDuration(BITTORRENT_SESSION_KEY("UPnPLeaseDuration"), 0)
|
||||
, m_peerToS(BITTORRENT_SESSION_KEY("PeerToS"), 0x20)
|
||||
, m_peerToS(BITTORRENT_SESSION_KEY("PeerToS"), 0x04)
|
||||
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY("IgnoreLimitsOnLAN"), false)
|
||||
, m_includeOverheadInLimits(BITTORRENT_SESSION_KEY("IncludeOverheadInLimits"), false)
|
||||
, m_announceIP(BITTORRENT_SESSION_KEY("AnnounceIP"))
|
||||
@@ -549,7 +564,7 @@ void Session::setDownloadPathEnabled(const bool enabled)
|
||||
{
|
||||
m_isDownloadPathEnabled = enabled;
|
||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||
torrent->handleDownloadPathChanged();
|
||||
torrent->handleCategoryOptionsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1055,7 +1070,6 @@ void Session::initializeNativeSession()
|
||||
| lt::alert::file_progress_notification
|
||||
| lt::alert::ip_block_notification
|
||||
| lt::alert::peer_notification
|
||||
| lt::alert::performance_warning
|
||||
| lt::alert::port_mapping_notification
|
||||
| lt::alert::status_notification
|
||||
| lt::alert::storage_notification
|
||||
@@ -1321,7 +1335,7 @@ void Session::loadLTSettings(lt::settings_pack &settingsPack)
|
||||
|
||||
// Outgoing ports
|
||||
settingsPack.set_int(lt::settings_pack::outgoing_port, outgoingPortsMin());
|
||||
settingsPack.set_int(lt::settings_pack::num_outgoing_ports, outgoingPortsMax() - outgoingPortsMin() + 1);
|
||||
settingsPack.set_int(lt::settings_pack::num_outgoing_ports, (outgoingPortsMax() - outgoingPortsMin()));
|
||||
// UPnP lease duration
|
||||
settingsPack.set_int(lt::settings_pack::upnp_lease_duration, UPnPLeaseDuration());
|
||||
// Type of service
|
||||
@@ -1841,7 +1855,7 @@ bool Session::deleteTorrent(const TorrentID &id, const DeleteOption deleteOption
|
||||
}
|
||||
|
||||
// Remove it from torrent resume directory
|
||||
m_resumeDataStorage->remove(torrent->id());
|
||||
m_resumeDataStorage->remove(resumeDataID(torrent));
|
||||
|
||||
delete torrent;
|
||||
return true;
|
||||
@@ -2148,18 +2162,6 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
|
||||
{
|
||||
const TorrentInfo &torrentInfo = std::get<TorrentInfo>(source);
|
||||
|
||||
// if torrent name wasn't explicitly set we handle the case of
|
||||
// initial renaming of torrent content and rename torrent accordingly
|
||||
if (loadTorrentParams.name.isEmpty())
|
||||
{
|
||||
QString contentName = torrentInfo.rootFolder();
|
||||
if (contentName.isEmpty() && (torrentInfo.filesCount() == 1))
|
||||
contentName = Utils::Fs::fileName(torrentInfo.filePath(0));
|
||||
|
||||
if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
|
||||
loadTorrentParams.name = contentName;
|
||||
}
|
||||
|
||||
Q_ASSERT(addTorrentParams.filePaths.isEmpty() || (addTorrentParams.filePaths.size() == torrentInfo.filesCount()));
|
||||
|
||||
const TorrentContentLayout contentLayout = ((loadTorrentParams.contentLayout == TorrentContentLayout::Original)
|
||||
@@ -2167,6 +2169,18 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
|
||||
QStringList filePaths = (!addTorrentParams.filePaths.isEmpty() ? addTorrentParams.filePaths : torrentInfo.filePaths());
|
||||
applyContentLayout(filePaths, contentLayout, Utils::Fs::findRootFolder(torrentInfo.filePaths()));
|
||||
|
||||
// if torrent name wasn't explicitly set we handle the case of
|
||||
// initial renaming of torrent content and rename torrent accordingly
|
||||
if (loadTorrentParams.name.isEmpty())
|
||||
{
|
||||
QString contentName = Utils::Fs::findRootFolder(filePaths);
|
||||
if (contentName.isEmpty() && (filePaths.size() == 1))
|
||||
contentName = Utils::Fs::fileName(filePaths.at(0));
|
||||
|
||||
if (!contentName.isEmpty() && (contentName != torrentInfo.name()))
|
||||
loadTorrentParams.name = contentName;
|
||||
}
|
||||
|
||||
if (!loadTorrentParams.hasSeedStatus)
|
||||
{
|
||||
const QString actualDownloadPath = useAutoTMM
|
||||
@@ -2178,8 +2192,8 @@ bool Session::addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source
|
||||
const auto nativeIndexes = torrentInfo.nativeIndexes();
|
||||
if (!filePaths.isEmpty())
|
||||
{
|
||||
for (int index = 0; index < addTorrentParams.filePaths.size(); ++index)
|
||||
p.renamed_files[nativeIndexes[index]] = Utils::Fs::toNativePath(addTorrentParams.filePaths.at(index)).toStdString();
|
||||
for (int index = 0; index < filePaths.size(); ++index)
|
||||
p.renamed_files[nativeIndexes[index]] = Utils::Fs::toNativePath(filePaths.at(index)).toStdString();
|
||||
}
|
||||
|
||||
Q_ASSERT(p.file_priorities.empty());
|
||||
@@ -2337,9 +2351,9 @@ bool Session::downloadMetadata(const MagnetUri &magnetUri)
|
||||
return true;
|
||||
}
|
||||
|
||||
void Session::exportTorrentFile(const TorrentInfo &torrentInfo, const QString &folderPath, const QString &baseName)
|
||||
void Session::exportTorrentFile(const Torrent *torrent, const QString &folderPath)
|
||||
{
|
||||
const QString validName = Utils::Fs::toValidFileSystemName(baseName);
|
||||
const QString validName = Utils::Fs::toValidFileSystemName(torrent->name());
|
||||
QString torrentExportFilename = QString::fromLatin1("%1.torrent").arg(validName);
|
||||
const QDir exportDir {folderPath};
|
||||
if (exportDir.exists() || exportDir.mkpath(exportDir.absolutePath()))
|
||||
@@ -2353,11 +2367,11 @@ void Session::exportTorrentFile(const TorrentInfo &torrentInfo, const QString &f
|
||||
newTorrentPath = exportDir.absoluteFilePath(torrentExportFilename);
|
||||
}
|
||||
|
||||
const nonstd::expected<void, QString> result = torrentInfo.saveToFile(newTorrentPath);
|
||||
const nonstd::expected<void, QString> result = torrent->exportToFile(newTorrentPath);
|
||||
if (!result)
|
||||
{
|
||||
LogMsg(tr("Couldn't export torrent metadata file '%1'. Reason: %2.")
|
||||
.arg(newTorrentPath, result.error()), Log::WARNING);
|
||||
LogMsg(tr("Failed to export torrent. Torrent: \"%1\". Destination: \"%2\". Reason: \"%3\"")
|
||||
.arg(torrent->name(), newTorrentPath, result.error()), Log::WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2438,31 +2452,59 @@ void Session::setSavePath(const QString &path)
|
||||
const QString resolvedPath = (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, baseSavePath));
|
||||
if (resolvedPath == m_savePath) return;
|
||||
|
||||
m_savePath = resolvedPath;
|
||||
|
||||
if (isDisableAutoTMMWhenDefaultSavePathChanged())
|
||||
{
|
||||
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
|
||||
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
|
||||
{
|
||||
const QString &categoryName = it.key();
|
||||
const CategoryOptions &categoryOptions = it.value();
|
||||
if (QDir::isRelativePath(categoryOptions.savePath))
|
||||
affectedCatogories.insert(categoryName);
|
||||
}
|
||||
|
||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||
torrent->setAutoTMMEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||
torrent->handleCategoryOptionsChanged();
|
||||
{
|
||||
if (affectedCatogories.contains(torrent->category()))
|
||||
torrent->setAutoTMMEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
m_savePath = resolvedPath;
|
||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||
torrent->handleCategoryOptionsChanged();
|
||||
}
|
||||
|
||||
void Session::setDownloadPath(const QString &path)
|
||||
{
|
||||
const QString baseDownloadPath = specialFolderLocation(SpecialFolder::Downloads) + QLatin1String("/temp");
|
||||
const QString resolvedPath = (QDir::isAbsolutePath(path) ? path : Utils::Fs::resolvePath(path, baseDownloadPath));
|
||||
if (resolvedPath != m_downloadPath)
|
||||
if (resolvedPath == m_downloadPath)
|
||||
return;
|
||||
|
||||
if (isDisableAutoTMMWhenDefaultSavePathChanged())
|
||||
{
|
||||
m_downloadPath = resolvedPath;
|
||||
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
|
||||
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
|
||||
{
|
||||
const QString &categoryName = it.key();
|
||||
const CategoryOptions &categoryOptions = it.value();
|
||||
const CategoryOptions::DownloadPathOption downloadPathOption =
|
||||
categoryOptions.downloadPath.value_or(CategoryOptions::DownloadPathOption {isDownloadPathEnabled(), downloadPath()});
|
||||
if (downloadPathOption.enabled && QDir::isRelativePath(downloadPathOption.path))
|
||||
affectedCatogories.insert(categoryName);
|
||||
}
|
||||
|
||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||
torrent->handleDownloadPathChanged();
|
||||
{
|
||||
if (affectedCatogories.contains(torrent->category()))
|
||||
torrent->setAutoTMMEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
m_downloadPath = resolvedPath;
|
||||
for (TorrentImpl *const torrent : asConst(m_torrents))
|
||||
torrent->handleCategoryOptionsChanged();
|
||||
}
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
|
||||
@@ -3019,6 +3061,11 @@ void Session::applyOSMemoryPriority() const
|
||||
if (!setProcessInformation) // only available on Windows >= 8
|
||||
return;
|
||||
|
||||
using SETTHREADINFORMATION = BOOL (WINAPI *)(HANDLE, THREAD_INFORMATION_CLASS, LPVOID, DWORD);
|
||||
const auto setThreadInformation = Utils::Misc::loadWinAPI<SETTHREADINFORMATION>("Kernel32.dll", "SetThreadInformation");
|
||||
if (!setThreadInformation) // only available on Windows >= 8
|
||||
return;
|
||||
|
||||
#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
|
||||
// this dummy struct is required to compile successfully when targeting older Windows version
|
||||
struct MEMORY_PRIORITY_INFORMATION
|
||||
@@ -3055,6 +3102,11 @@ void Session::applyOSMemoryPriority() const
|
||||
break;
|
||||
}
|
||||
setProcessInformation(::GetCurrentProcess(), ProcessMemoryPriority, &prioInfo, sizeof(prioInfo));
|
||||
|
||||
// To avoid thrashing/sluggishness of the app, set "main event loop" thread to normal memory priority
|
||||
// which is higher/equal than other threads
|
||||
prioInfo.MemoryPriority = MEMORY_PRIORITY_NORMAL;
|
||||
setThreadInformation(::GetCurrentThread(), ThreadMemoryPriority, &prioInfo, sizeof(prioInfo));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -3936,16 +3988,8 @@ void Session::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVe
|
||||
|
||||
void Session::handleTorrentMetadataReceived(TorrentImpl *const torrent)
|
||||
{
|
||||
// Copy the torrent file to the export folder
|
||||
if (!torrentExportDirectory().isEmpty())
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file_with_hashes()};
|
||||
#else
|
||||
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file()};
|
||||
#endif
|
||||
exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
|
||||
}
|
||||
exportTorrentFile(torrent, torrentExportDirectory());
|
||||
|
||||
emit torrentMetadataReceived(torrent);
|
||||
}
|
||||
@@ -3992,16 +4036,8 @@ void Session::handleTorrentFinished(TorrentImpl *const torrent)
|
||||
}
|
||||
}
|
||||
|
||||
// Move .torrent file to another folder
|
||||
if (!finishedTorrentExportDirectory().isEmpty())
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file_with_hashes()};
|
||||
#else
|
||||
const TorrentInfo torrentInfo {*torrent->nativeHandle().torrent_file()};
|
||||
#endif
|
||||
exportTorrentFile(torrentInfo, finishedTorrentExportDirectory(), torrent->name());
|
||||
}
|
||||
exportTorrentFile(torrent, finishedTorrentExportDirectory());
|
||||
|
||||
if (!hasUnfinishedTorrents())
|
||||
emit allTorrentsFinished();
|
||||
@@ -4011,7 +4047,7 @@ void Session::handleTorrentResumeDataReady(TorrentImpl *const torrent, const Loa
|
||||
{
|
||||
--m_numResumeData;
|
||||
|
||||
m_resumeDataStorage->store(torrent->id(), data);
|
||||
m_resumeDataStorage->store(resumeDataID(torrent), data);
|
||||
}
|
||||
|
||||
void Session::handleTorrentTrackerReply(TorrentImpl *const torrent, const QString &trackerUrl)
|
||||
@@ -4033,7 +4069,7 @@ bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newP
|
||||
|
||||
if (m_moveStorageQueue.size() > 1)
|
||||
{
|
||||
const auto iter = std::find_if(m_moveStorageQueue.begin() + 1, m_moveStorageQueue.end()
|
||||
auto iter = std::find_if(m_moveStorageQueue.begin() + 1, m_moveStorageQueue.end()
|
||||
, [&torrentHandle](const MoveStorageJob &job)
|
||||
{
|
||||
return job.torrentHandle == torrentHandle;
|
||||
@@ -4042,8 +4078,16 @@ bool Session::addMoveTorrentStorageJob(TorrentImpl *torrent, const QString &newP
|
||||
if (iter != m_moveStorageQueue.end())
|
||||
{
|
||||
// remove existing inactive job
|
||||
m_moveStorageQueue.erase(iter);
|
||||
LogMsg(tr("Cancelled moving \"%1\" from \"%2\" to \"%3\".").arg(torrent->name(), currentLocation, iter->path));
|
||||
iter = m_moveStorageQueue.erase(iter);
|
||||
|
||||
iter = std::find_if(iter, m_moveStorageQueue.end(), [&torrentHandle](const MoveStorageJob &job)
|
||||
{
|
||||
return job.torrentHandle == torrentHandle;
|
||||
});
|
||||
|
||||
const bool torrentHasOutstandingJob = (iter != m_moveStorageQueue.end());
|
||||
torrent->handleMoveStorageJobFinished(torrentHasOutstandingJob);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4346,53 +4390,182 @@ void Session::startUpTorrents()
|
||||
const QVector<TorrentID> torrents = startupStorage->registeredTorrents();
|
||||
int resumedTorrentsCount = 0;
|
||||
QVector<TorrentID> queue;
|
||||
for (const TorrentID &torrentID : torrents)
|
||||
QSet<QString> recoveredCategories;
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const QSet<TorrentID> indexedTorrents {torrents.cbegin(), torrents.cend()};
|
||||
QSet<TorrentID> skippedIDs;
|
||||
#endif
|
||||
for (TorrentID torrentID : torrents)
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
if (skippedIDs.contains(torrentID))
|
||||
continue;
|
||||
#endif
|
||||
|
||||
const std::optional<LoadTorrentParams> loadResumeDataResult = startupStorage->load(torrentID);
|
||||
if (loadResumeDataResult)
|
||||
{
|
||||
LoadTorrentParams resumeData = *loadResumeDataResult;
|
||||
bool needStore = false;
|
||||
|
||||
if (m_resumeDataStorage != startupStorage)
|
||||
{
|
||||
needStore = true;
|
||||
if (isQueueingSystemEnabled() && !resumeData.hasSeedStatus)
|
||||
queue.append(torrentID);
|
||||
}
|
||||
|
||||
// TODO: Remove the following upgrade code in v4.5
|
||||
// == BEGIN UPGRADE CODE ==
|
||||
if (m_needUpgradeDownloadPath && isDownloadPathEnabled())
|
||||
{
|
||||
if (!resumeData.useAutoTMM)
|
||||
{
|
||||
resumeData.downloadPath = downloadPath();
|
||||
needStore = true;
|
||||
}
|
||||
}
|
||||
// == END UPGRADE CODE ==
|
||||
|
||||
if (needStore)
|
||||
m_resumeDataStorage->store(torrentID, resumeData);
|
||||
|
||||
qDebug() << "Starting up torrent" << torrentID.toString() << "...";
|
||||
if (!loadTorrent(resumeData))
|
||||
{
|
||||
LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
|
||||
.arg(torrentID.toString()), Log::CRITICAL);
|
||||
}
|
||||
|
||||
// process add torrent messages before message queue overflow
|
||||
if ((resumedTorrentsCount % 100) == 0) readAlerts();
|
||||
|
||||
++resumedTorrentsCount;
|
||||
}
|
||||
else
|
||||
if (!loadResumeDataResult)
|
||||
{
|
||||
LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
|
||||
.arg(torrentID.toString()), Log::CRITICAL);
|
||||
continue;
|
||||
}
|
||||
|
||||
LoadTorrentParams resumeData = *loadResumeDataResult;
|
||||
bool needStore = false;
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const lt::info_hash_t infoHash = (resumeData.ltAddTorrentParams.ti
|
||||
? resumeData.ltAddTorrentParams.ti->info_hashes()
|
||||
: resumeData.ltAddTorrentParams.info_hashes);
|
||||
const bool isHybrid = infoHash.has_v1() && infoHash.has_v2();
|
||||
const auto torrentIDv2 = TorrentID::fromInfoHash(infoHash);
|
||||
const auto torrentIDv1 = TorrentID::fromInfoHash(lt::info_hash_t(infoHash.v1));
|
||||
if (torrentID == torrentIDv1)
|
||||
{
|
||||
if (isHybrid && indexedTorrents.contains(torrentIDv2))
|
||||
{
|
||||
// if we don't have metadata, try to find it in alternative "resume data"
|
||||
if (!resumeData.ltAddTorrentParams.ti)
|
||||
{
|
||||
const std::optional<LoadTorrentParams> loadAltResumeDataResult = startupStorage->load(torrentIDv2);
|
||||
if (loadAltResumeDataResult)
|
||||
resumeData.ltAddTorrentParams.ti = loadAltResumeDataResult->ltAddTorrentParams.ti;
|
||||
}
|
||||
|
||||
// remove alternative "resume data" and skip the attempt to load it
|
||||
m_resumeDataStorage->remove(torrentIDv2);
|
||||
skippedIDs.insert(torrentIDv2);
|
||||
}
|
||||
}
|
||||
else if (torrentID == torrentIDv2)
|
||||
{
|
||||
if (isHybrid)
|
||||
{
|
||||
torrentID = torrentIDv1;
|
||||
needStore = true;
|
||||
m_resumeDataStorage->remove(torrentIDv2);
|
||||
|
||||
if (indexedTorrents.contains(torrentID))
|
||||
{
|
||||
skippedIDs.insert(torrentID);
|
||||
|
||||
const std::optional<LoadTorrentParams> loadPreferredResumeDataResult = startupStorage->load(torrentID);
|
||||
if (loadPreferredResumeDataResult)
|
||||
{
|
||||
std::shared_ptr<lt::torrent_info> ti = resumeData.ltAddTorrentParams.ti;
|
||||
resumeData = *loadPreferredResumeDataResult;
|
||||
if (!resumeData.ltAddTorrentParams.ti)
|
||||
resumeData.ltAddTorrentParams.ti = ti;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
|
||||
.arg(torrentID.toString()), Log::WARNING);
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
const lt::sha1_hash infoHash = (resumeData.ltAddTorrentParams.ti
|
||||
? resumeData.ltAddTorrentParams.ti->info_hash()
|
||||
: resumeData.ltAddTorrentParams.info_hash);
|
||||
if (torrentID != TorrentID::fromInfoHash(infoHash))
|
||||
{
|
||||
LogMsg(tr("Failed to resume torrent: inconsistent torrent ID is detected. Torrent: \"%1\"")
|
||||
.arg(torrentID.toString()), Log::WARNING);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_resumeDataStorage != startupStorage)
|
||||
{
|
||||
needStore = true;
|
||||
if (isQueueingSystemEnabled() && !resumeData.hasSeedStatus)
|
||||
queue.append(torrentID);
|
||||
}
|
||||
|
||||
// TODO: Remove the following upgrade code in v4.5
|
||||
// == BEGIN UPGRADE CODE ==
|
||||
if (m_needUpgradeDownloadPath && isDownloadPathEnabled())
|
||||
{
|
||||
if (!resumeData.useAutoTMM)
|
||||
{
|
||||
resumeData.downloadPath = downloadPath();
|
||||
needStore = true;
|
||||
}
|
||||
}
|
||||
// == END UPGRADE CODE ==
|
||||
|
||||
if (needStore)
|
||||
m_resumeDataStorage->store(torrentID, resumeData);
|
||||
|
||||
const QString category = resumeData.category;
|
||||
bool isCategoryRecovered = recoveredCategories.contains(category);
|
||||
if (!category.isEmpty() && (isCategoryRecovered || !m_categories.contains(category)))
|
||||
{
|
||||
if (!isCategoryRecovered)
|
||||
{
|
||||
if (addCategory(category))
|
||||
{
|
||||
recoveredCategories.insert(category);
|
||||
isCategoryRecovered = true;
|
||||
LogMsg(tr("Inconsistent data is detected. Category '%1' is assigned to some torrent(s) but it doesn't exist in the configuration file."
|
||||
" Its settings will be reset to default.").arg(category), Log::WARNING);
|
||||
}
|
||||
else
|
||||
{
|
||||
resumeData.category.clear();
|
||||
LogMsg(tr("Inconsistent data is detected. Invalid category '%1' is assigned to torrent '%2'.")
|
||||
.arg(category, torrentID.toString()), Log::WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
// We should check isCategoryRecovered again since the category
|
||||
// can be just recovered by the code above
|
||||
if (isCategoryRecovered && resumeData.useAutoTMM)
|
||||
{
|
||||
const QString storageLocation = Utils::Fs::toUniformPath(QString::fromStdString(resumeData.ltAddTorrentParams.save_path));
|
||||
if ((storageLocation != categorySavePath(resumeData.category)) && (storageLocation != categoryDownloadPath(resumeData.category)))
|
||||
{
|
||||
resumeData.useAutoTMM = false;
|
||||
resumeData.savePath = storageLocation;
|
||||
resumeData.downloadPath.clear();
|
||||
LogMsg(tr("Torrent '%1' is assigned the recovered category '%2' whose paths don't match the torrent's path."
|
||||
" Torrent is switched to \"Manual\" mode.").arg(torrentID.toString(), category), Log::WARNING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Algorithm::removeIf(resumeData.tags, [this, &torrentID](const QString &tag)
|
||||
{
|
||||
if (hasTag(tag))
|
||||
return false;
|
||||
|
||||
if (addTag(tag))
|
||||
{
|
||||
LogMsg(tr("Detected inconsistent data: tag is missing from the configuration file."
|
||||
" Tag will be recovered."
|
||||
" Torrent: \"%1\". Tag: \"%2\"").arg(torrentID.toString(), tag), Log::WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
LogMsg(tr("Detected inconsistent data: invalid tag. Torrent: \"%1\". Tag: \"%2\"")
|
||||
.arg(torrentID.toString(), tag), Log::WARNING);
|
||||
return true;
|
||||
});
|
||||
|
||||
qDebug() << "Starting up torrent" << torrentID.toString() << "...";
|
||||
if (!loadTorrent(resumeData))
|
||||
{
|
||||
LogMsg(tr("Unable to resume torrent '%1'.", "e.g: Unable to resume torrent 'hash'.")
|
||||
.arg(torrentID.toString()), Log::CRITICAL);
|
||||
}
|
||||
|
||||
// process add torrent messages before message queue overflow
|
||||
if ((resumedTorrentsCount % 100) == 0) readAlerts();
|
||||
|
||||
++resumedTorrentsCount;
|
||||
}
|
||||
|
||||
if (m_resumeDataStorage != startupStorage)
|
||||
@@ -4489,6 +4662,7 @@ void Session::handleAlert(const lt::alert *a)
|
||||
case lt::file_prio_alert::alert_type:
|
||||
#endif
|
||||
case lt::file_renamed_alert::alert_type:
|
||||
case lt::file_rename_failed_alert::alert_type:
|
||||
case lt::file_completed_alert::alert_type:
|
||||
case lt::torrent_finished_alert::alert_type:
|
||||
case lt::save_resume_data_alert::alert_type:
|
||||
@@ -4608,17 +4782,13 @@ void Session::createTorrent(const lt::torrent_handle &nativeHandle)
|
||||
}
|
||||
else
|
||||
{
|
||||
m_resumeDataStorage->store(torrent->id(), params);
|
||||
m_resumeDataStorage->store(resumeDataID(torrent), params);
|
||||
|
||||
// The following is useless for newly added magnet
|
||||
if (hasMetadata)
|
||||
{
|
||||
// Copy the torrent file to the export folder
|
||||
if (!torrentExportDirectory().isEmpty())
|
||||
{
|
||||
const TorrentInfo torrentInfo {*params.ltAddTorrentParams.ti};
|
||||
exportTorrentFile(torrentInfo, torrentExportDirectory(), torrent->name());
|
||||
}
|
||||
exportTorrentFile(torrent, torrentExportDirectory());
|
||||
}
|
||||
|
||||
if (isAddTrackersEnabled() && !torrent->isPrivate())
|
||||
|
||||
@@ -609,7 +609,7 @@ namespace BitTorrent
|
||||
bool addTorrent_impl(const std::variant<MagnetUri, TorrentInfo> &source, const AddTorrentParams &addTorrentParams);
|
||||
|
||||
void updateSeedingLimitTimer();
|
||||
void exportTorrentFile(const TorrentInfo &torrentInfo, const QString &folderPath, const QString &baseName);
|
||||
void exportTorrentFile(const Torrent *torrent, const QString &folderPath);
|
||||
|
||||
void handleAlert(const lt::alert *a);
|
||||
void dispatchTorrentAlert(const lt::alert *a);
|
||||
|
||||
@@ -29,10 +29,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
#include <QtContainerFwd>
|
||||
|
||||
#include "base/3rdparty/expected.hpp"
|
||||
#include "base/tagset.h"
|
||||
#include "abstractfilestorage.h"
|
||||
|
||||
@@ -294,6 +295,7 @@ namespace BitTorrent
|
||||
virtual void clearPeers() = 0;
|
||||
|
||||
virtual QString createMagnetURI() const = 0;
|
||||
virtual nonstd::expected<void, QString> exportToFile(const QString &path) const = 0;
|
||||
|
||||
TorrentID id() const;
|
||||
bool isResumed() const;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
#include <libtorrent/address.hpp>
|
||||
#include <libtorrent/alert_types.hpp>
|
||||
#include <libtorrent/create_torrent.hpp>
|
||||
#include <libtorrent/magnet_uri.hpp>
|
||||
#include <libtorrent/session.hpp>
|
||||
#include <libtorrent/storage_defs.hpp>
|
||||
@@ -55,6 +56,7 @@
|
||||
#include "base/logger.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/io.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "common.h"
|
||||
#include "downloadpriority.h"
|
||||
@@ -236,6 +238,14 @@ namespace
|
||||
status.pieces = params.have_pieces;
|
||||
status.verified_pieces = params.verified_pieces;
|
||||
}
|
||||
|
||||
template <typename Vector>
|
||||
Vector resized(const Vector &inVector, const typename Vector::size_type size, const typename Vector::value_type &defaultValue)
|
||||
{
|
||||
Vector outVector = inVector;
|
||||
outVector.resize(size, defaultValue);
|
||||
return outVector;
|
||||
}
|
||||
}
|
||||
|
||||
// TorrentImpl
|
||||
@@ -273,13 +283,16 @@ TorrentImpl::TorrentImpl(Session *session, lt::session *nativeSession
|
||||
m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
|
||||
|
||||
Q_ASSERT(m_filePaths.isEmpty());
|
||||
Q_ASSERT(m_indexMap.isEmpty());
|
||||
const int filesCount = m_torrentInfo.filesCount();
|
||||
m_filePaths.reserve(filesCount);
|
||||
m_indexMap.reserve(filesCount);
|
||||
const std::shared_ptr<const lt::torrent_info> currentInfo = m_nativeHandle.torrent_file();
|
||||
const lt::file_storage &fileStorage = currentInfo->files();
|
||||
for (int i = 0; i < filesCount; ++i)
|
||||
{
|
||||
const lt::file_index_t nativeIndex = m_torrentInfo.nativeIndexes().at(i);
|
||||
m_indexMap[nativeIndex] = i;
|
||||
const QString filePath = Utils::Fs::toUniformPath(QString::fromStdString(fileStorage.file_path(nativeIndex)));
|
||||
m_filePaths.append(filePath);
|
||||
}
|
||||
@@ -412,7 +425,7 @@ void TorrentImpl::setSavePath(const QString &path)
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
|
||||
const bool isFinished = isSeed() || m_hasSeedStatus;
|
||||
if (isFinished)
|
||||
if (isFinished || downloadPath().isEmpty())
|
||||
moveStorage(savePath(), MoveStorageMode::KeepExistingFiles);
|
||||
}
|
||||
|
||||
@@ -444,7 +457,7 @@ QString TorrentImpl::rootPath() const
|
||||
if (!hasMetadata())
|
||||
return {};
|
||||
|
||||
const QString relativeRootPath = m_torrentInfo.rootFolder();
|
||||
const QString relativeRootPath = Utils::Fs::findRootFolder(filePaths());
|
||||
if (relativeRootPath.isEmpty())
|
||||
return {};
|
||||
|
||||
@@ -1500,6 +1513,8 @@ void TorrentImpl::fileSearchFinished(const QString &savePath, const QStringList
|
||||
|
||||
void TorrentImpl::endReceivedMetadataHandling(const QString &savePath, const QStringList &fileNames)
|
||||
{
|
||||
Q_ASSERT(m_filePaths.isEmpty());
|
||||
|
||||
lt::add_torrent_params &p = m_ltAddTorrentParams;
|
||||
|
||||
const std::shared_ptr<lt::torrent_info> metadata = std::const_pointer_cast<lt::torrent_info>(m_nativeHandle.torrent_file());
|
||||
@@ -1507,13 +1522,14 @@ void TorrentImpl::endReceivedMetadataHandling(const QString &savePath, const QSt
|
||||
m_filePaths = fileNames;
|
||||
const auto nativeIndexes = m_torrentInfo.nativeIndexes();
|
||||
for (int i = 0; i < fileNames.size(); ++i)
|
||||
p.renamed_files[nativeIndexes[i]] = fileNames[i].toStdString();
|
||||
{
|
||||
const auto nativeIndex = nativeIndexes.at(i);
|
||||
p.renamed_files[nativeIndex] = fileNames[i].toStdString();
|
||||
}
|
||||
p.save_path = Utils::Fs::toNativePath(savePath).toStdString();
|
||||
p.ti = metadata;
|
||||
|
||||
const int internalFilesCount = p.ti->files().num_files(); // including .pad files
|
||||
// Use qBittorrent default priority rather than libtorrent's (4)
|
||||
p.file_priorities = std::vector(internalFilesCount, LT::toNative(DownloadPriority::Normal));
|
||||
p.file_priorities = resized(p.file_priorities, metadata->files().num_files()
|
||||
, LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
|
||||
|
||||
reload();
|
||||
|
||||
@@ -1555,7 +1571,8 @@ void TorrentImpl::reload()
|
||||
}
|
||||
|
||||
m_nativeHandle = m_nativeSession->add_torrent(p);
|
||||
m_nativeHandle.queue_position_set(queuePos);
|
||||
if (queuePos >= lt::queue_position_t {})
|
||||
m_nativeHandle.queue_position_set(queuePos);
|
||||
}
|
||||
|
||||
void TorrentImpl::pause()
|
||||
@@ -1635,11 +1652,6 @@ void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
|
||||
updateStatus(nativeStatus);
|
||||
}
|
||||
|
||||
void TorrentImpl::handleDownloadPathChanged()
|
||||
{
|
||||
adjustStorageLocation();
|
||||
}
|
||||
|
||||
void TorrentImpl::handleMoveStorageJobFinished(const bool hasOutstandingJob)
|
||||
{
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
@@ -1775,6 +1787,8 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
||||
{
|
||||
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
|
||||
{
|
||||
Q_ASSERT(m_indexMap.isEmpty());
|
||||
|
||||
m_ltAddTorrentParams = p->params;
|
||||
|
||||
m_ltAddTorrentParams.have_pieces.clear();
|
||||
@@ -1784,6 +1798,19 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
||||
|
||||
QStringList filePaths = metadata.filePaths();
|
||||
applyContentLayout(filePaths, m_contentLayout);
|
||||
|
||||
const auto nativeIndexes = metadata.nativeIndexes();
|
||||
const auto &renamedFiles = m_ltAddTorrentParams.renamed_files;
|
||||
m_indexMap.reserve(filePaths.size());
|
||||
for (int i = 0; i < filePaths.size(); ++i)
|
||||
{
|
||||
const auto nativeIndex = nativeIndexes.at(i);
|
||||
m_indexMap[nativeIndex] = i;
|
||||
|
||||
if (const auto it = renamedFiles.find(nativeIndex); it != renamedFiles.cend())
|
||||
filePaths[i] = QString::fromStdString(it->second);
|
||||
}
|
||||
|
||||
m_session->findIncompleteFiles(metadata, savePath(), downloadPath(), filePaths);
|
||||
}
|
||||
else
|
||||
@@ -1862,7 +1889,7 @@ void TorrentImpl::handleFastResumeRejectedAlert(const lt::fastresume_rejected_al
|
||||
|
||||
void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
|
||||
{
|
||||
const int fileIndex = m_torrentInfo.nativeIndexes().indexOf(p->index);
|
||||
const int fileIndex = m_indexMap.value(p->index, -1);
|
||||
Q_ASSERT(fileIndex >= 0);
|
||||
|
||||
// Remove empty leftover folders
|
||||
@@ -1892,9 +1919,10 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
|
||||
++pathIdx;
|
||||
}
|
||||
|
||||
QDir storageDir {actualStorageLocation()};
|
||||
for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i)
|
||||
{
|
||||
QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QString::fromLatin1("/")));
|
||||
storageDir.rmdir(Utils::String::join(oldPathParts, QString::fromLatin1("/")));
|
||||
oldPathParts.removeLast();
|
||||
}
|
||||
|
||||
@@ -1907,7 +1935,7 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
|
||||
|
||||
void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p)
|
||||
{
|
||||
const int fileIndex = m_torrentInfo.nativeIndexes().indexOf(p->index);
|
||||
const int fileIndex = m_indexMap.value(p->index, -1);
|
||||
Q_ASSERT(fileIndex >= 0);
|
||||
|
||||
LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
|
||||
@@ -1922,12 +1950,14 @@ void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
|
||||
|
||||
void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
|
||||
{
|
||||
const int fileIndex = m_torrentInfo.nativeIndexes().indexOf(p->index);
|
||||
Q_ASSERT(fileIndex >= 0);
|
||||
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
|
||||
return;
|
||||
|
||||
qDebug("A file completed download in torrent \"%s\"", qUtf8Printable(name()));
|
||||
if (m_session->isAppendExtensionEnabled())
|
||||
{
|
||||
const int fileIndex = m_indexMap.value(p->index, -1);
|
||||
Q_ASSERT(fileIndex >= 0);
|
||||
|
||||
QString name = filePath(fileIndex);
|
||||
if (name.endsWith(QB_EXT))
|
||||
{
|
||||
@@ -1961,12 +1991,6 @@ void TorrentImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
|
||||
void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
|
||||
{
|
||||
LogMsg((tr("Performance alert: ") + QString::fromStdString(p->message()))
|
||||
, Log::INFO);
|
||||
}
|
||||
|
||||
void TorrentImpl::handleCategoryOptionsChanged()
|
||||
{
|
||||
if (m_useAutoTMM)
|
||||
@@ -2034,9 +2058,6 @@ void TorrentImpl::handleAlert(const lt::alert *a)
|
||||
case lt::torrent_checked_alert::alert_type:
|
||||
handleTorrentCheckedAlert(static_cast<const lt::torrent_checked_alert*>(a));
|
||||
break;
|
||||
case lt::performance_alert::alert_type:
|
||||
handlePerformanceAlert(static_cast<const lt::performance_alert*>(a));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2081,7 +2102,8 @@ void TorrentImpl::adjustStorageLocation()
|
||||
const bool isFinished = isSeed() || m_hasSeedStatus;
|
||||
const QDir targetDir {((isFinished || downloadPath.isEmpty()) ? savePath() : downloadPath)};
|
||||
|
||||
moveStorage(targetDir.absolutePath(), MoveStorageMode::Overwrite);
|
||||
if ((targetDir != QDir(actualStorageLocation())) || isMoveInProgress())
|
||||
moveStorage(targetDir.absolutePath(), MoveStorageMode::Overwrite);
|
||||
}
|
||||
|
||||
lt::torrent_handle TorrentImpl::nativeHandle() const
|
||||
@@ -2228,6 +2250,37 @@ QString TorrentImpl::createMagnetURI() const
|
||||
return QString::fromStdString(lt::make_magnet_uri(m_nativeHandle));
|
||||
}
|
||||
|
||||
nonstd::expected<void, QString> TorrentImpl::exportToFile(const QString &path) const
|
||||
{
|
||||
if (!hasMetadata())
|
||||
return nonstd::make_unexpected(tr("Missing metadata"));
|
||||
|
||||
try
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const std::shared_ptr<lt::torrent_info> completeTorrentInfo = m_nativeHandle.torrent_file_with_hashes();
|
||||
const std::shared_ptr<lt::torrent_info> torrentInfo = (completeTorrentInfo ? completeTorrentInfo : info().nativeInfo());
|
||||
#else
|
||||
const std::shared_ptr<lt::torrent_info> torrentInfo = info().nativeInfo();
|
||||
#endif
|
||||
auto creator = lt::create_torrent(*torrentInfo);
|
||||
|
||||
for (const TrackerEntry &entry : asConst(trackers()))
|
||||
creator.add_tracker(entry.url.toStdString(), entry.tier);
|
||||
|
||||
const lt::entry torrentEntry = creator.generate();
|
||||
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, torrentEntry);
|
||||
if (!result)
|
||||
return result.get_unexpected();
|
||||
}
|
||||
catch (const lt::system_error &err)
|
||||
{
|
||||
return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
|
||||
{
|
||||
if (!hasMetadata()) return;
|
||||
|
||||
@@ -224,6 +224,7 @@ namespace BitTorrent
|
||||
void clearPeers() override;
|
||||
|
||||
QString createMagnetURI() const override;
|
||||
nonstd::expected<void, QString> exportToFile(const QString &path) const;
|
||||
|
||||
bool needSaveResumeData() const;
|
||||
|
||||
@@ -232,7 +233,6 @@ namespace BitTorrent
|
||||
|
||||
void handleAlert(const lt::alert *a);
|
||||
void handleStateUpdate(const lt::torrent_status &nativeStatus);
|
||||
void handleDownloadPathChanged();
|
||||
void handleCategoryOptionsChanged();
|
||||
void handleAppendExtensionToggled();
|
||||
void saveResumeData();
|
||||
@@ -255,7 +255,6 @@ namespace BitTorrent
|
||||
void handleFileRenamedAlert(const lt::file_renamed_alert *p);
|
||||
void handleFileRenameFailedAlert(const lt::file_rename_failed_alert *p);
|
||||
void handleMetadataReceivedAlert(const lt::metadata_received_alert *p);
|
||||
void handlePerformanceAlert(const lt::performance_alert *p) const;
|
||||
void handleSaveResumeDataAlert(const lt::save_resume_data_alert *p);
|
||||
void handleSaveResumeDataFailedAlert(const lt::save_resume_data_failed_alert *p);
|
||||
void handleTorrentCheckedAlert(const lt::torrent_checked_alert *p);
|
||||
@@ -286,6 +285,7 @@ namespace BitTorrent
|
||||
TorrentState m_state = TorrentState::Unknown;
|
||||
TorrentInfo m_torrentInfo;
|
||||
QStringList m_filePaths;
|
||||
QHash<lt::file_index_t, int> m_indexMap;
|
||||
SpeedMonitor m_speedMonitor;
|
||||
|
||||
InfoHash m_infoHash;
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/utils/fs.h"
|
||||
@@ -140,7 +139,7 @@ nonstd::expected<void, QString> TorrentInfo::saveToFile(const QString &path) con
|
||||
|
||||
try
|
||||
{
|
||||
const auto torrentCreator = lt::create_torrent(*nativeInfo());
|
||||
const auto torrentCreator = lt::create_torrent(*m_nativeInfo);
|
||||
const lt::entry torrentEntry = torrentCreator.generate();
|
||||
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, torrentEntry);
|
||||
if (!result)
|
||||
@@ -409,19 +408,6 @@ int TorrentInfo::fileIndex(const QString &fileName) const
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString TorrentInfo::rootFolder() const
|
||||
{
|
||||
if (!isValid())
|
||||
return {};
|
||||
|
||||
return Utils::Fs::findRootFolder(filePaths());
|
||||
}
|
||||
|
||||
bool TorrentInfo::hasRootFolder() const
|
||||
{
|
||||
return !rootFolder().isEmpty();
|
||||
}
|
||||
|
||||
TorrentContentLayout TorrentInfo::contentLayout() const
|
||||
{
|
||||
if (!isValid())
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QtContainerFwd>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/3rdparty/expected.hpp"
|
||||
#include "base/indexrange.h"
|
||||
@@ -92,9 +93,6 @@ namespace BitTorrent
|
||||
PieceRange filePieces(const QString &file) const;
|
||||
PieceRange filePieces(int fileIndex) const;
|
||||
|
||||
QString rootFolder() const;
|
||||
bool hasRootFolder() const;
|
||||
|
||||
std::shared_ptr<lt::torrent_info> nativeInfo() const;
|
||||
QVector<lt::file_index_t> nativeIndexes() const;
|
||||
|
||||
|
||||
@@ -34,10 +34,13 @@
|
||||
|
||||
#include "base/3rdparty/expected.hpp"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/gzip.h"
|
||||
#include "base/utils/io.h"
|
||||
#include "base/utils/misc.h"
|
||||
|
||||
#ifdef QT_NO_COMPRESS
|
||||
#include "base/utils/gzip.h"
|
||||
#endif
|
||||
|
||||
const int MAX_REDIRECTIONS = 20; // the common value for web browsers
|
||||
|
||||
namespace
|
||||
@@ -121,9 +124,13 @@ void DownloadHandlerImpl::processFinishedDownload()
|
||||
}
|
||||
|
||||
// Success
|
||||
#ifdef QT_NO_COMPRESS
|
||||
m_result.data = (m_reply->rawHeader("Content-Encoding") == "gzip")
|
||||
? Utils::Gzip::decompress(m_reply->readAll())
|
||||
: m_reply->readAll();
|
||||
#else
|
||||
m_result.data = m_reply->readAll();
|
||||
#endif
|
||||
|
||||
if (m_downloadRequest.saveToFile())
|
||||
{
|
||||
|
||||
@@ -123,8 +123,12 @@ namespace
|
||||
|
||||
// Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents
|
||||
request.setRawHeader("Referer", request.url().toEncoded().data());
|
||||
// Accept gzip
|
||||
#ifdef QT_NO_COMPRESS
|
||||
// The macro "QT_NO_COMPRESS" defined in QT will disable the zlib releated features
|
||||
// and reply data auto-decompression in QT will also be disabled. But we can support
|
||||
// gzip encoding and manually decompress the reply data.
|
||||
request.setRawHeader("Accept-Encoding", "gzip");
|
||||
#endif
|
||||
// Qt doesn't support Magnet protocol so we need to handle redirections manually
|
||||
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::ManualRedirectPolicy);
|
||||
|
||||
|
||||
@@ -556,7 +556,7 @@ void Parser::parse(const QByteArray &feedData)
|
||||
// read and create items from a rss document
|
||||
void Parser::parse_impl(const QByteArray &feedData)
|
||||
{
|
||||
QXmlStreamReader xml(feedData);
|
||||
QXmlStreamReader xml {feedData};
|
||||
XmlStreamEntityResolver resolver;
|
||||
xml.setEntityResolver(&resolver);
|
||||
bool foundChannel = false;
|
||||
@@ -591,19 +591,20 @@ void Parser::parse_impl(const QByteArray &feedData)
|
||||
xml.skipCurrentElement();
|
||||
}
|
||||
|
||||
if (!foundChannel)
|
||||
{
|
||||
m_result.error = tr("Invalid RSS feed.");
|
||||
}
|
||||
else if (xml.hasError())
|
||||
if (xml.hasError())
|
||||
{
|
||||
m_result.error = tr("%1 (line: %2, column: %3, offset: %4).")
|
||||
.arg(xml.errorString()).arg(xml.lineNumber())
|
||||
.arg(xml.columnNumber()).arg(xml.characterOffset());
|
||||
}
|
||||
else if (!foundChannel)
|
||||
{
|
||||
m_result.error = tr("Invalid RSS feed.");
|
||||
}
|
||||
|
||||
emit finished(m_result);
|
||||
m_result.articles.clear(); // clear articles only
|
||||
m_result.articles.clear();
|
||||
m_result.error.clear();
|
||||
m_articleIDs.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -511,7 +511,8 @@ void TorrentFilesWatcher::Worker::processFolder(const QString &path, const QStri
|
||||
if (path != watchedFolderPath)
|
||||
{
|
||||
const QString subdirPath = watchedDir.relativeFilePath(path);
|
||||
if (addTorrentParams.useAutoTMM)
|
||||
const bool useAutoTMM = addTorrentParams.useAutoTMM.value_or(!BitTorrent::Session::instance()->isAutoTMMDisabledByDefault());
|
||||
if (useAutoTMM)
|
||||
{
|
||||
addTorrentParams.category = addTorrentParams.category.isEmpty()
|
||||
? subdirPath : (addTorrentParams.category + QLatin1Char('/') + subdirPath);
|
||||
@@ -590,7 +591,8 @@ void TorrentFilesWatcher::Worker::processFailedTorrents()
|
||||
if (exactDirPath != dir.path())
|
||||
{
|
||||
const QString subdirPath = dir.relativeFilePath(exactDirPath);
|
||||
if (addTorrentParams.useAutoTMM)
|
||||
const bool useAutoTMM = addTorrentParams.useAutoTMM.value_or(!BitTorrent::Session::instance()->isAutoTMMDisabledByDefault());
|
||||
if (useAutoTMM)
|
||||
{
|
||||
addTorrentParams.category = addTorrentParams.category.isEmpty()
|
||||
? subdirPath : (addTorrentParams.category + QLatin1Char('/') + subdirPath);
|
||||
|
||||
@@ -127,26 +127,17 @@ namespace
|
||||
|
||||
QString getRegValue(const HKEY handle, const QString &name = {})
|
||||
{
|
||||
QString result;
|
||||
|
||||
const std::wstring nameWStr = name.toStdWString();
|
||||
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);
|
||||
::RegQueryValueExW(handle, nameWStr.c_str(), 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;
|
||||
LONG res = ::RegQueryValueExW(handle, nameWStr.c_str(), NULL, &type, (LPBYTE)lpData, &cbData);
|
||||
|
||||
QString result;
|
||||
if (res == ERROR_SUCCESS)
|
||||
{
|
||||
lpData[cBuffer - 1] = 0;
|
||||
@@ -185,18 +176,14 @@ namespace
|
||||
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;
|
||||
const std::wstring version = QString(versions.takeLast() + "\\InstallPath").toStdWString();
|
||||
|
||||
HKEY hkInstallPath;
|
||||
res = ::RegOpenKeyExW(hkPythonCore, lpSubkey, 0, samDesired, &hkInstallPath);
|
||||
delete[] lpSubkey;
|
||||
res = ::RegOpenKeyExW(hkPythonCore, version.c_str(), 0, samDesired, &hkInstallPath);
|
||||
|
||||
if (res == ERROR_SUCCESS)
|
||||
{
|
||||
qDebug("Detected possible Python v%s location", qUtf8Printable(version));
|
||||
qDebug("Detected possible Python v%ls location", version.c_str());
|
||||
path = getRegValue(hkInstallPath);
|
||||
::RegCloseKey(hkInstallPath);
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#include <optional>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <memory>
|
||||
|
||||
#include <windows.h>
|
||||
#include <powrprof.h>
|
||||
#include <Shlobj.h>
|
||||
@@ -393,7 +395,7 @@ QString Utils::Misc::getUserIDString()
|
||||
const int UNLEN = 256;
|
||||
WCHAR buffer[UNLEN + 1] = {0};
|
||||
DWORD buffer_len = sizeof(buffer) / sizeof(*buffer);
|
||||
if (GetUserNameW(buffer, &buffer_len))
|
||||
if (::GetUserNameW(buffer, &buffer_len))
|
||||
uid = QString::fromWCharArray(buffer);
|
||||
#else
|
||||
uid = QString::number(getuid());
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include <QtGlobal>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <memory>
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
@@ -93,14 +92,11 @@ namespace Utils::Misc
|
||||
QString path = windowsSystemPath();
|
||||
if (!path.endsWith('\\'))
|
||||
path += '\\';
|
||||
|
||||
path += source;
|
||||
|
||||
auto pathWchar = std::make_unique<wchar_t[]>(path.length() + 1);
|
||||
path.toWCharArray(pathWchar.get());
|
||||
|
||||
const std::wstring pathWStr = path.toStdWString();
|
||||
return reinterpret_cast<T>(
|
||||
::GetProcAddress(::LoadLibraryW(pathWchar.get()), funcName));
|
||||
::GetProcAddress(::LoadLibraryW(pathWStr.c_str()), funcName));
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
|
||||
#define QBT_VERSION_MAJOR 4
|
||||
#define QBT_VERSION_MINOR 4
|
||||
#define QBT_VERSION_BUGFIX 0
|
||||
#define QBT_VERSION_BUGFIX 4
|
||||
#define QBT_VERSION_BUILD 0
|
||||
#define QBT_VERSION_STATUS "rc1" // Should be empty for stable releases!
|
||||
#define QBT_VERSION_STATUS "" // Should be empty for stable releases!
|
||||
|
||||
#define QBT__STRINGIFY(x) #x
|
||||
#define QBT_STRINGIFY(x) QBT__STRINGIFY(x)
|
||||
|
||||
@@ -204,8 +204,10 @@ target_link_libraries(qbt_gui
|
||||
|
||||
if (DBUS)
|
||||
target_sources(qbt_gui PRIVATE
|
||||
qtnotify/notifications.h
|
||||
qtnotify/notifications.cpp
|
||||
notifications/dbusnotifier.h
|
||||
notifications/dbusnotifier.cpp
|
||||
notifications/dbusnotificationsinterface.h
|
||||
notifications/dbusnotificationsinterface.cpp
|
||||
powermanagement/powermanagement_x11.h
|
||||
powermanagement/powermanagement_x11.cpp
|
||||
)
|
||||
|
||||
@@ -200,7 +200,7 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
|
||||
|
||||
m_ui->startTorrentCheckBox->setChecked(!m_torrentParams.addPaused.value_or(session->isAddTorrentPaused()));
|
||||
|
||||
m_ui->comboTTM->blockSignals(true); // the TreeView size isn't correct if the slot does it job at this point
|
||||
m_ui->comboTTM->blockSignals(true); // the TreeView size isn't correct if the slot does its job at this point
|
||||
m_ui->comboTTM->setCurrentIndex(session->isAutoTMMDisabledByDefault() ? 0 : 1);
|
||||
m_ui->comboTTM->blockSignals(false);
|
||||
|
||||
@@ -874,6 +874,8 @@ void AddNewTorrentDialog::setupTreeview()
|
||||
connect(m_ui->contentTreeView, &QAbstractItemView::clicked, m_ui->contentTreeView
|
||||
, qOverload<const QModelIndex &>(&QAbstractItemView::edit));
|
||||
connect(m_ui->contentTreeView, &QWidget::customContextMenuRequested, this, &AddNewTorrentDialog::displayContentTreeMenu);
|
||||
connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_contentModel, &TorrentContentFilterModel::selectAll);
|
||||
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_contentModel, &TorrentContentFilterModel::selectNone);
|
||||
|
||||
const auto contentLayout = ((m_ui->contentLayoutComboBox->currentIndex() == 0)
|
||||
? BitTorrent::detectContentLayout(m_torrentInfo.filePaths())
|
||||
@@ -964,9 +966,9 @@ void AddNewTorrentDialog::TMMChanged(int index)
|
||||
|
||||
m_ui->groupBoxDownloadPath->blockSignals(true);
|
||||
m_ui->groupBoxDownloadPath->setChecked(!downloadPath.isEmpty());
|
||||
|
||||
updateDiskSpaceLabel();
|
||||
}
|
||||
|
||||
updateDiskSpaceLabel();
|
||||
}
|
||||
|
||||
void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked)
|
||||
|
||||
@@ -392,22 +392,59 @@
|
||||
</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 class="QWidget" name="layoutWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="contentFilterLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonSelectAll">
|
||||
<property name="text">
|
||||
<string>Select All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="buttonSelectNone">
|
||||
<property name="text">
|
||||
<string>Select None</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>168</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<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>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -62,6 +62,9 @@ namespace
|
||||
// qBittorrent section
|
||||
QBITTORRENT_HEADER,
|
||||
RESUME_DATA_STORAGE,
|
||||
#if (defined(Q_OS_WIN) && defined(QBT_USES_LIBTORRENT2))
|
||||
MEMORY_WORKING_SET_LIMIT,
|
||||
#endif
|
||||
#if defined(Q_OS_WIN)
|
||||
OS_MEMORY_PRIORITY,
|
||||
#endif
|
||||
@@ -196,6 +199,10 @@ void AdvancedSettings::saveAdvancedSettings()
|
||||
break;
|
||||
}
|
||||
session->setOSMemoryPriority(prio);
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
static_cast<Application *>(QCoreApplication::instance())->setMemoryWorkingSetLimit(m_spinBoxMemoryWorkingSetLimit.value());
|
||||
#endif
|
||||
#endif
|
||||
// Async IO threads
|
||||
session->setAsyncIOThreads(m_spinBoxAsyncIOThreads.value());
|
||||
@@ -259,23 +266,14 @@ void AdvancedSettings::saveAdvancedSettings()
|
||||
pref->resolvePeerCountries(m_checkBoxResolveCountries.isChecked());
|
||||
pref->resolvePeerHostNames(m_checkBoxResolveHosts.isChecked());
|
||||
// Network interface
|
||||
if (m_comboBoxInterface.currentIndex() == 0)
|
||||
{
|
||||
// All interfaces (default)
|
||||
session->setNetworkInterface(QString());
|
||||
session->setNetworkInterfaceName(QString());
|
||||
}
|
||||
else
|
||||
{
|
||||
session->setNetworkInterface(m_comboBoxInterface.itemData(m_comboBoxInterface.currentIndex()).toString());
|
||||
session->setNetworkInterfaceName(m_comboBoxInterface.currentText());
|
||||
}
|
||||
|
||||
session->setNetworkInterface(m_comboBoxInterface.currentData().toString());
|
||||
session->setNetworkInterfaceName((m_comboBoxInterface.currentIndex() == 0)
|
||||
? QString()
|
||||
: m_comboBoxInterface.currentText());
|
||||
// Interface address
|
||||
// Construct a QHostAddress to filter malformed strings
|
||||
const QHostAddress ifaceAddr(m_comboBoxInterfaceAddress.currentData().toString().trimmed());
|
||||
const QHostAddress ifaceAddr {m_comboBoxInterfaceAddress.currentData().toString()};
|
||||
session->setNetworkInterfaceAddress(ifaceAddr.toString());
|
||||
|
||||
// Announce IP
|
||||
// Construct a QHostAddress to filter malformed strings
|
||||
const QHostAddress addr(m_lineEditAnnounceIP.text().trimmed());
|
||||
@@ -343,45 +341,57 @@ void AdvancedSettings::updateSaveResumeDataIntervalSuffix(const int value)
|
||||
|
||||
void AdvancedSettings::updateInterfaceAddressCombo()
|
||||
{
|
||||
// Try to get the currently selected interface name
|
||||
const QString ifaceName = m_comboBoxInterface.itemData(m_comboBoxInterface.currentIndex()).toString(); // Empty string for the first element
|
||||
const QString currentAddress = BitTorrent::Session::instance()->networkInterfaceAddress();
|
||||
|
||||
// Clear all items and reinsert them, default to all
|
||||
m_comboBoxInterfaceAddress.clear();
|
||||
m_comboBoxInterfaceAddress.addItem(tr("All addresses"), {});
|
||||
m_comboBoxInterfaceAddress.addItem(tr("All IPv4 addresses"), QLatin1String("0.0.0.0"));
|
||||
m_comboBoxInterfaceAddress.addItem(tr("All IPv6 addresses"), QLatin1String("::"));
|
||||
|
||||
const auto populateCombo = [this](const QHostAddress &addr)
|
||||
const auto toString = [](const QHostAddress &address) -> QString
|
||||
{
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol)
|
||||
{
|
||||
const QString str = addr.toString();
|
||||
m_comboBoxInterfaceAddress.addItem(str, str);
|
||||
}
|
||||
else if (addr.protocol() == QAbstractSocket::IPv6Protocol)
|
||||
{
|
||||
const QString str = Utils::Net::canonicalIPv6Addr(addr).toString();
|
||||
m_comboBoxInterfaceAddress.addItem(str, str);
|
||||
switch (address.protocol()) {
|
||||
case QAbstractSocket::IPv4Protocol:
|
||||
return address.toString();
|
||||
case QAbstractSocket::IPv6Protocol:
|
||||
return Utils::Net::canonicalIPv6Addr(address).toString();
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
if (ifaceName.isEmpty())
|
||||
// Clear all items and reinsert them
|
||||
m_comboBoxInterfaceAddress.clear();
|
||||
m_comboBoxInterfaceAddress.addItem(tr("All addresses"), QString());
|
||||
m_comboBoxInterfaceAddress.addItem(tr("All IPv4 addresses"), QHostAddress(QHostAddress::AnyIPv4).toString());
|
||||
m_comboBoxInterfaceAddress.addItem(tr("All IPv6 addresses"), QHostAddress(QHostAddress::AnyIPv6).toString());
|
||||
|
||||
const QString currentIface = m_comboBoxInterface.currentData().toString();
|
||||
if (currentIface.isEmpty()) // `any` interface
|
||||
{
|
||||
for (const QHostAddress &addr : asConst(QNetworkInterface::allAddresses()))
|
||||
populateCombo(addr);
|
||||
for (const QHostAddress &address : asConst(QNetworkInterface::allAddresses()))
|
||||
{
|
||||
const QString addressString = toString(address);
|
||||
m_comboBoxInterfaceAddress.addItem(addressString, addressString);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const QNetworkInterface iface = QNetworkInterface::interfaceFromName(ifaceName);
|
||||
const QList<QNetworkAddressEntry> addresses = iface.addressEntries();
|
||||
const QList<QNetworkAddressEntry> addresses = QNetworkInterface::interfaceFromName(currentIface).addressEntries();
|
||||
for (const QNetworkAddressEntry &entry : addresses)
|
||||
populateCombo(entry.ip());
|
||||
{
|
||||
const QString addressString = toString(entry.ip());
|
||||
m_comboBoxInterfaceAddress.addItem(addressString, addressString);
|
||||
}
|
||||
}
|
||||
|
||||
const QString currentAddress = BitTorrent::Session::instance()->networkInterfaceAddress();
|
||||
const int index = m_comboBoxInterfaceAddress.findData(currentAddress);
|
||||
m_comboBoxInterfaceAddress.setCurrentIndex(std::max(index, 0));
|
||||
if (index > -1)
|
||||
{
|
||||
m_comboBoxInterfaceAddress.setCurrentIndex(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// not found, for the sake of UI consistency, add such entry
|
||||
m_comboBoxInterfaceAddress.addItem(currentAddress, currentAddress);
|
||||
m_comboBoxInterfaceAddress.setCurrentIndex(m_comboBoxInterfaceAddress.count() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvancedSettings::loadAdvancedSettings()
|
||||
@@ -436,6 +446,17 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
addRow(OS_MEMORY_PRIORITY, (tr("Process memory priority (Windows >= 8 only)")
|
||||
+ ' ' + makeLink("https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-memory_priority_information", "(?)"))
|
||||
, &m_comboBoxOSMemoryPriority);
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
m_spinBoxMemoryWorkingSetLimit.setMinimum(1);
|
||||
m_spinBoxMemoryWorkingSetLimit.setMaximum(std::numeric_limits<int>::max());
|
||||
m_spinBoxMemoryWorkingSetLimit.setSuffix(tr(" MiB"));
|
||||
m_spinBoxMemoryWorkingSetLimit.setValue(static_cast<Application *>(QCoreApplication::instance())->memoryWorkingSetLimit());
|
||||
|
||||
addRow(MEMORY_WORKING_SET_LIMIT, (tr("Physical memory (RAM) usage limit")
|
||||
+ ' ' + makeLink("https://wikipedia.org/wiki/Working_set", "(?)"))
|
||||
, &m_spinBoxMemoryWorkingSetLimit);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Async IO threads
|
||||
@@ -626,26 +647,23 @@ void AdvancedSettings::loadAdvancedSettings()
|
||||
m_checkBoxResolveHosts.setChecked(pref->resolvePeerHostNames());
|
||||
addRow(RESOLVE_HOSTS, tr("Resolve peer host names"), &m_checkBoxResolveHosts);
|
||||
// Network interface
|
||||
m_comboBoxInterface.addItem(tr("Any interface", "i.e. Any network interface"));
|
||||
const QString currentInterface = session->networkInterface();
|
||||
bool interfaceExists = currentInterface.isEmpty();
|
||||
int i = 1;
|
||||
m_comboBoxInterface.addItem(tr("Any interface", "i.e. Any network interface"), QString());
|
||||
for (const QNetworkInterface &iface : asConst(QNetworkInterface::allInterfaces()))
|
||||
{
|
||||
m_comboBoxInterface.addItem(iface.humanReadableName(), iface.name());
|
||||
if (!currentInterface.isEmpty() && (iface.name() == currentInterface))
|
||||
{
|
||||
m_comboBoxInterface.setCurrentIndex(i);
|
||||
interfaceExists = true;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
// Saved interface does not exist, show it anyway
|
||||
if (!interfaceExists)
|
||||
|
||||
const QString currentInterface = session->networkInterface();
|
||||
const int ifaceIndex = m_comboBoxInterface.findData(currentInterface);
|
||||
if (ifaceIndex > -1)
|
||||
{
|
||||
m_comboBoxInterface.addItem(session->networkInterfaceName(), currentInterface);
|
||||
m_comboBoxInterface.setCurrentIndex(i);
|
||||
m_comboBoxInterface.setCurrentIndex(ifaceIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Saved interface does not exist, show it
|
||||
m_comboBoxInterface.addItem(session->networkInterfaceName(), currentInterface);
|
||||
m_comboBoxInterface.setCurrentIndex(m_comboBoxInterface.count() - 1);
|
||||
}
|
||||
|
||||
connect(&m_comboBoxInterface, qOverload<int>(&QComboBox::currentIndexChanged)
|
||||
, this, &AdvancedSettings::updateInterfaceAddressCombo);
|
||||
addRow(NETWORK_IFACE, tr("Network interface"), &m_comboBoxInterface);
|
||||
|
||||
@@ -82,6 +82,9 @@ private:
|
||||
// OS dependent settings
|
||||
#if defined(Q_OS_WIN)
|
||||
QComboBox m_comboBoxOSMemoryPriority;
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
QSpinBox m_spinBoxMemoryWorkingSetLimit;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_MACOS
|
||||
|
||||
@@ -111,8 +111,12 @@ FileSystemPathEdit::FileSystemPathEditPrivate::FileSystemPathEditPrivate(
|
||||
void FileSystemPathEdit::FileSystemPathEditPrivate::browseActionTriggered()
|
||||
{
|
||||
Q_Q(FileSystemPathEdit);
|
||||
|
||||
const QFileInfo fileInfo {q->selectedPath()};
|
||||
const QString directory = (m_mode == FileSystemPathEdit::Mode::DirectoryOpen) || (m_mode == FileSystemPathEdit::Mode::DirectorySave)
|
||||
? fileInfo.absoluteFilePath()
|
||||
: fileInfo.absolutePath();
|
||||
QString filter = q->fileNameFilter();
|
||||
QString directory = q->currentDirectory().isEmpty() ? QDir::homePath() : q->currentDirectory();
|
||||
|
||||
QString selectedPath;
|
||||
switch (m_mode)
|
||||
@@ -308,11 +312,6 @@ void FileSystemPathEdit::setDialogCaption(const QString &caption)
|
||||
d->m_dialogCaption = caption;
|
||||
}
|
||||
|
||||
QString FileSystemPathEdit::currentDirectory() const
|
||||
{
|
||||
return QFileInfo(selectedPath()).absoluteDir().absolutePath();
|
||||
}
|
||||
|
||||
QWidget *FileSystemPathEdit::editWidgetImpl() const
|
||||
{
|
||||
Q_D(const FileSystemPathEdit);
|
||||
|
||||
@@ -64,7 +64,6 @@ public:
|
||||
Mode mode() const;
|
||||
void setMode(Mode mode);
|
||||
|
||||
QString currentDirectory() const;
|
||||
QString selectedPath() const;
|
||||
void setSelectedPath(const QString &val);
|
||||
|
||||
|
||||
@@ -169,12 +169,14 @@ win32|macx {
|
||||
|
||||
unix:!macx:dbus {
|
||||
HEADERS += \
|
||||
$$PWD/powermanagement/powermanagement_x11.h \
|
||||
$$PWD/qtnotify/notifications.h
|
||||
$$PWD/notifications/dbusnotifier.h \
|
||||
$$PWD/notifications/dbusnotificationsinterface.h \
|
||||
$$PWD/powermanagement/powermanagement_x11.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/powermanagement/powermanagement_x11.cpp \
|
||||
$$PWD/qtnotify/notifications.cpp
|
||||
$$PWD/notifications/dbusnotifier.cpp \
|
||||
$$PWD/notifications/dbusnotificationsinterface.cpp \
|
||||
$$PWD/powermanagement/powermanagement_x11.cpp
|
||||
}
|
||||
|
||||
macx {
|
||||
|
||||
@@ -104,7 +104,15 @@ namespace MacUtils
|
||||
for (const auto &path : pathsList)
|
||||
[pathURLs addObject:[NSURL fileURLWithPath:path.toNSString()]];
|
||||
|
||||
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:pathURLs];
|
||||
// In some unknown way, the next line affects Qt's main loop causing the crash
|
||||
// in QApplication::exec() on processing next event after this call.
|
||||
// Even crash doesn't happen exactly after this call, it will happen on
|
||||
// application exit. Call stack and disassembly are the same in all cases.
|
||||
// But running it in another thread (aka in background) solves the issue.
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
|
||||
{
|
||||
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:pathURLs];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,9 +48,8 @@
|
||||
#include <QtGlobal>
|
||||
#include <QTimer>
|
||||
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
|
||||
#include <QDBusConnection>
|
||||
#include "qtnotify/notifications.h"
|
||||
#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS
|
||||
#include "notifications/dbusnotifier.h"
|
||||
#endif
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
@@ -128,7 +127,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
, m_storeNotificationEnabled(NOTIFICATIONS_SETTINGS_KEY("Enabled"))
|
||||
, m_storeNotificationTorrentAdded(NOTIFICATIONS_SETTINGS_KEY("TorrentAdded"))
|
||||
, m_storeExecutionLogTypes(EXECUTIONLOG_SETTINGS_KEY("Types"), Log::MsgType::ALL)
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
|
||||
#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS
|
||||
, m_storeNotificationTimeOut(NOTIFICATIONS_SETTINGS_KEY("Timeout"))
|
||||
#endif
|
||||
{
|
||||
@@ -182,6 +181,14 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
m_ui->actionLock->setVisible(true);
|
||||
});
|
||||
|
||||
#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS
|
||||
if (isNotificationsEnabled())
|
||||
{
|
||||
m_notifier = new DBusNotifier(this);
|
||||
connect(m_notifier, &DBusNotifier::messageClicked, this, &MainWindow::balloonClicked);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Creating Bittorrent session
|
||||
updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled());
|
||||
|
||||
@@ -508,9 +515,25 @@ bool MainWindow::isNotificationsEnabled() const
|
||||
return m_storeNotificationEnabled.get(true);
|
||||
}
|
||||
|
||||
void MainWindow::setNotificationsEnabled(bool value)
|
||||
void MainWindow::setNotificationsEnabled(const bool value)
|
||||
{
|
||||
if (m_storeNotificationEnabled == value)
|
||||
return;
|
||||
|
||||
m_storeNotificationEnabled = value;
|
||||
|
||||
#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS
|
||||
if (value)
|
||||
{
|
||||
m_notifier = new DBusNotifier(this);
|
||||
connect(m_notifier, &DBusNotifier::messageClicked, this, &MainWindow::balloonClicked);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete m_notifier;
|
||||
m_notifier = nullptr;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MainWindow::isTorrentAddedNotificationsEnabled() const
|
||||
@@ -968,7 +991,6 @@ void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *co
|
||||
, tr("The torrent '%1' contains torrent files, do you want to proceed with their download?").arg(torrent->name())
|
||||
, QMessageBox::NoButton, this);
|
||||
confirmBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||
confirmBox->setModal(true);
|
||||
|
||||
const QPushButton *yes = confirmBox->addButton(tr("Yes"), QMessageBox::YesRole);
|
||||
/*QPushButton *no = */ confirmBox->addButton(tr("No"), QMessageBox::NoRole);
|
||||
@@ -980,7 +1002,7 @@ void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *co
|
||||
if (button == never)
|
||||
Preferences::instance()->disableRecursiveDownload();
|
||||
});
|
||||
confirmBox->show();
|
||||
confirmBox->open();
|
||||
}
|
||||
|
||||
void MainWindow::handleDownloadFromUrlFailure(const QString &url, const QString &reason) const
|
||||
@@ -1235,6 +1257,7 @@ void MainWindow::closeEvent(QCloseEvent *e)
|
||||
#ifndef Q_OS_MACOS
|
||||
if (m_systrayIcon)
|
||||
{
|
||||
m_systrayIcon->disconnect();
|
||||
m_systrayIcon->setToolTip(tr("qBittorrent is shutting down..."));
|
||||
m_trayIconMenu->setEnabled(false);
|
||||
}
|
||||
@@ -1646,27 +1669,8 @@ void MainWindow::showNotificationBalloon(const QString &title, const QString &ms
|
||||
if (!isNotificationsEnabled())
|
||||
return;
|
||||
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
|
||||
OrgFreedesktopNotificationsInterface notifications(QLatin1String("org.freedesktop.Notifications")
|
||||
, QLatin1String("/org/freedesktop/Notifications")
|
||||
, QDBusConnection::sessionBus());
|
||||
|
||||
// Testing for 'notifications.isValid()' isn't helpful here.
|
||||
// If the notification daemon is configured to run 'as needed'
|
||||
// the above check can be false if the daemon wasn't started
|
||||
// by another application. In this case DBus will be able to
|
||||
// start the notification daemon and complete our request. Such
|
||||
// a daemon is xfce4-notifyd, DBus autostarts it and after
|
||||
// some inactivity shuts it down. Other DEs, like GNOME, choose
|
||||
// to start their daemons at the session startup and have it sit
|
||||
// idling for the whole session.
|
||||
const QVariantMap hints {{QLatin1String("desktop-entry"), QLatin1String("org.qbittorrent.qBittorrent")}};
|
||||
QDBusPendingReply<uint> reply = notifications.Notify(QLatin1String("qBittorrent"), 0
|
||||
, QLatin1String("qbittorrent"), title, msg, {}, hints, getNotificationTimeout());
|
||||
|
||||
reply.waitForFinished();
|
||||
if (!reply.isError())
|
||||
return;
|
||||
#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS
|
||||
m_notifier->showMessage(title, msg, getNotificationTimeout());
|
||||
#elif defined(Q_OS_MACOS)
|
||||
MacUtils::displayNotification(title, msg);
|
||||
#else
|
||||
@@ -1713,7 +1717,9 @@ void MainWindow::createTrayIcon(const int retries)
|
||||
m_systrayIcon->setContextMenu(m_trayIconMenu);
|
||||
|
||||
connect(m_systrayIcon, &QSystemTrayIcon::activated, this, &MainWindow::toggleVisibility);
|
||||
#ifndef QBT_USES_CUSTOMDBUSNOTIFICATIONS
|
||||
connect(m_systrayIcon, &QSystemTrayIcon::messageClicked, this, &MainWindow::balloonClicked);
|
||||
#endif
|
||||
|
||||
m_systrayIcon->show();
|
||||
emit systemTrayIconCreated();
|
||||
@@ -1920,6 +1926,7 @@ void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool i
|
||||
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||
msgBox->setAttribute(Qt::WA_ShowWithoutActivating);
|
||||
msgBox->setDefaultButton(QMessageBox::Yes);
|
||||
msgBox->setWindowModality(Qt::NonModal);
|
||||
connect(msgBox, &QMessageBox::buttonClicked, this, [msgBox, updater](QAbstractButton *button)
|
||||
{
|
||||
if (msgBox->buttonRole(button) == QMessageBox::YesRole)
|
||||
@@ -1928,7 +1935,7 @@ void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool i
|
||||
}
|
||||
});
|
||||
connect(msgBox, &QDialog::finished, this, cleanup);
|
||||
msgBox->open();
|
||||
msgBox->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1938,8 +1945,9 @@ void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool i
|
||||
, tr("No updates available.\nYou are already using the latest version.")
|
||||
, QMessageBox::Ok, this};
|
||||
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
||||
msgBox->setWindowModality(Qt::NonModal);
|
||||
connect(msgBox, &QDialog::finished, this, cleanup);
|
||||
msgBox->open();
|
||||
msgBox->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -71,6 +71,11 @@ namespace Ui
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
|
||||
#define QBT_USES_CUSTOMDBUSNOTIFICATIONS
|
||||
class DBusNotifier;
|
||||
#endif
|
||||
|
||||
class MainWindow final : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -261,8 +266,9 @@ private:
|
||||
SettingValue<bool> m_storeNotificationTorrentAdded;
|
||||
CachedSettingValue<Log::MsgTypes> m_storeExecutionLogTypes;
|
||||
|
||||
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
|
||||
#ifdef QBT_USES_CUSTOMDBUSNOTIFICATIONS
|
||||
SettingValue<int> m_storeNotificationTimeOut;
|
||||
DBusNotifier *m_notifier = nullptr;
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
|
||||
74
src/gui/notifications/dbusnotificationsinterface.cpp
Normal file
74
src/gui/notifications/dbusnotificationsinterface.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "dbusnotificationsinterface.h"
|
||||
|
||||
#include <QDBusConnection>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
DBusNotificationsInterface::DBusNotificationsInterface(const QString &service
|
||||
, const QString &path, const QDBusConnection &connection, QObject *parent)
|
||||
: QDBusAbstractInterface(service, path, DBUS_INTERFACE_NAME, connection, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QDBusPendingReply<QStringList> DBusNotificationsInterface::getCapabilities()
|
||||
{
|
||||
return asyncCall(QLatin1String("GetCapabilities"));
|
||||
}
|
||||
|
||||
QDBusPendingReply<QString, QString, QString, QString> DBusNotificationsInterface::getServerInformation()
|
||||
{
|
||||
return asyncCall(QLatin1String("GetServerInformation"));
|
||||
}
|
||||
|
||||
QDBusReply<QString> DBusNotificationsInterface::getServerInformation(QString &vendor, QString &version, QString &specVersion)
|
||||
{
|
||||
const QDBusMessage reply = call(QDBus::Block, QLatin1String("GetServerInformation"));
|
||||
if ((reply.type() == QDBusMessage::ReplyMessage) && (reply.arguments().count() == 4))
|
||||
{
|
||||
vendor = qdbus_cast<QString>(reply.arguments().at(1));
|
||||
version = qdbus_cast<QString>(reply.arguments().at(2));
|
||||
specVersion = qdbus_cast<QString>(reply.arguments().at(3));
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
QDBusPendingReply<uint> DBusNotificationsInterface::notify(const QString &appName
|
||||
, const uint id, const QString &icon, const QString &summary, const QString &body
|
||||
, const QStringList &actions, const QVariantMap &hints, const int timeout)
|
||||
{
|
||||
return asyncCall(QLatin1String("Notify"), appName, id, icon, summary, body, actions, hints, timeout);
|
||||
}
|
||||
|
||||
QDBusPendingReply<> DBusNotificationsInterface::closeNotification(const uint id)
|
||||
{
|
||||
return asyncCall(QLatin1String("CloseNotification"), id);
|
||||
}
|
||||
64
src/gui/notifications/dbusnotificationsinterface.h
Normal file
64
src/gui/notifications/dbusnotificationsinterface.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDBusAbstractInterface>
|
||||
#include <QDBusPendingReply>
|
||||
#include <QDBusReply>
|
||||
#include <QStringList>
|
||||
|
||||
class QDBusConnection;
|
||||
class QString;
|
||||
class QVariant;
|
||||
|
||||
class DBusNotificationsInterface final : public QDBusAbstractInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(DBusNotificationsInterface)
|
||||
|
||||
public:
|
||||
inline static const char DBUS_INTERFACE_NAME[] = "org.freedesktop.Notifications";
|
||||
|
||||
DBusNotificationsInterface(const QString &service, const QString &path
|
||||
, const QDBusConnection &connection, QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
QDBusPendingReply<QStringList> getCapabilities();
|
||||
QDBusPendingReply<QString, QString, QString, QString> getServerInformation();
|
||||
QDBusReply<QString> getServerInformation(QString &vendor, QString &version, QString &specVersion);
|
||||
QDBusPendingReply<uint> notify(const QString &appName
|
||||
, uint id, const QString &icon, const QString &summary, const QString &body
|
||||
, const QStringList &actions, const QVariantMap &hints, int timeout);
|
||||
QDBusPendingReply<> closeNotification(uint id);
|
||||
|
||||
signals:
|
||||
// Signal names must exactly match the ones from corresponding D-Bus interface
|
||||
void ActionInvoked(uint id, const QString &action);
|
||||
void NotificationClosed(uint id, uint reason);
|
||||
};
|
||||
93
src/gui/notifications/dbusnotifier.cpp
Normal file
93
src/gui/notifications/dbusnotifier.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* 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 "dbusnotifier.h"
|
||||
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusPendingCallWatcher>
|
||||
#include <QDBusPendingReply>
|
||||
|
||||
#include "dbusnotificationsinterface.h"
|
||||
|
||||
DBusNotifier::DBusNotifier(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_notificationsInterface {new DBusNotificationsInterface(QLatin1String("org.freedesktop.Notifications")
|
||||
, QLatin1String("/org/freedesktop/Notifications"), QDBusConnection::sessionBus(), this)}
|
||||
{
|
||||
// Testing for 'DBusNotificationsInterface::isValid()' isn't helpful here.
|
||||
// If the notification daemon is configured to run 'as needed'
|
||||
// the above check can be false if the daemon wasn't started
|
||||
// by another application. In this case DBus will be able to
|
||||
// start the notification daemon and complete our request. Such
|
||||
// a daemon is xfce4-notifyd, DBus autostarts it and after
|
||||
// some inactivity shuts it down. Other DEs, like GNOME, choose
|
||||
// to start their daemons at the session startup and have it sit
|
||||
// idling for the whole session.
|
||||
|
||||
connect(m_notificationsInterface, &DBusNotificationsInterface::ActionInvoked, this, &DBusNotifier::onActionInvoked);
|
||||
connect(m_notificationsInterface, &DBusNotificationsInterface::NotificationClosed, this, &DBusNotifier::onNotificationClosed);
|
||||
}
|
||||
|
||||
void DBusNotifier::showMessage(const QString &title, const QString &message, const int timeout)
|
||||
{
|
||||
// Assign "default" action to notification to make it clickable
|
||||
const QStringList actions {QLatin1String("default"), {}};
|
||||
const QVariantMap hints {{QLatin1String("desktop-entry"), QLatin1String("org.qbittorrent.qBittorrent")}};
|
||||
const QDBusPendingReply<uint> reply = m_notificationsInterface->notify(QLatin1String("qBittorrent"), 0
|
||||
, QLatin1String("qbittorrent"), title, message, actions, hints, timeout);
|
||||
auto *watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *self)
|
||||
{
|
||||
const QDBusPendingReply<uint> reply = *self;
|
||||
if (!reply.isError())
|
||||
{
|
||||
const uint messageID = reply.value();
|
||||
m_activeMessages.insert(messageID);
|
||||
}
|
||||
|
||||
self->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void DBusNotifier::onActionInvoked(const uint messageID, const QString &action)
|
||||
{
|
||||
Q_UNUSED(action);
|
||||
|
||||
// Check whether the notification is sent by qBittorrent
|
||||
// to avoid reacting to unrelated notifictions
|
||||
if (m_activeMessages.contains(messageID))
|
||||
emit messageClicked();
|
||||
}
|
||||
|
||||
void DBusNotifier::onNotificationClosed(const uint messageID, const uint reason)
|
||||
{
|
||||
Q_UNUSED(reason);
|
||||
|
||||
m_activeMessages.remove(messageID);
|
||||
}
|
||||
56
src/gui/notifications/dbusnotifier.h
Normal file
56
src/gui/notifications/dbusnotifier.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
|
||||
class DBusNotificationsInterface;
|
||||
|
||||
class DBusNotifier final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(DBusNotifier)
|
||||
|
||||
public:
|
||||
explicit DBusNotifier(QObject *parent = nullptr);
|
||||
|
||||
void showMessage(const QString &title, const QString &message, int timeout);
|
||||
|
||||
signals:
|
||||
void messageClicked();
|
||||
|
||||
private:
|
||||
void onActionInvoked(uint messageID, const QString &action);
|
||||
void onNotificationClosed(uint messageID, uint reason);
|
||||
|
||||
DBusNotificationsInterface *m_notificationsInterface = nullptr;
|
||||
QSet<uint> m_activeMessages;
|
||||
};
|
||||
@@ -1015,7 +1015,7 @@ Manual: Various torrent properties (e.g. save path) must be assigned manually</s
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelCategoryDefaultPathChanged">
|
||||
<property name="text">
|
||||
<string>When Default Save Path changed:</string>
|
||||
<string>When Default Save/Incomplete Path changed:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -282,7 +282,7 @@ void PiecesBar::showToolTip(const QHelpEvent *e)
|
||||
|
||||
for (int f : files)
|
||||
{
|
||||
const QString filePath {torrentInfo.filePath(f)};
|
||||
const QString filePath {m_torrent->filePath(f)};
|
||||
renderer(Utils::Misc::friendlyUnit(torrentInfo.fileSize(f)), filePath);
|
||||
}
|
||||
stream << "</body></html>";
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* This file was generated by qdbusxml2cpp version 0.8
|
||||
* Command line was: qdbusxml2cpp -p notifications.h:notifications.cpp notifications.xml
|
||||
*
|
||||
* qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd.
|
||||
*
|
||||
* This is an auto-generated file.
|
||||
* This file may have been hand-edited. Look for HAND-EDIT comments
|
||||
* before re-generating it.
|
||||
*/
|
||||
|
||||
#include "notifications.h"
|
||||
|
||||
/*
|
||||
* Implementation of interface class OrgFreedesktopNotificationsInterface
|
||||
*/
|
||||
|
||||
OrgFreedesktopNotificationsInterface::OrgFreedesktopNotificationsInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)
|
||||
: QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)
|
||||
{
|
||||
}
|
||||
|
||||
OrgFreedesktopNotificationsInterface::~OrgFreedesktopNotificationsInterface()
|
||||
{
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* This file was generated by qdbusxml2cpp version 0.8
|
||||
* Command line was: qdbusxml2cpp -p notifications.h:notifications.cpp notifications.xml
|
||||
*
|
||||
* qdbusxml2cpp is Copyright (C) 2020 The Qt Company Ltd.
|
||||
*
|
||||
* This is an auto-generated file.
|
||||
* Do not edit! All changes made to it will be lost.
|
||||
*/
|
||||
|
||||
#ifndef NOTIFICATIONS_H
|
||||
#define NOTIFICATIONS_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtDBus/QtDBus>
|
||||
|
||||
/*
|
||||
* Proxy class for interface org.freedesktop.Notifications
|
||||
*/
|
||||
class OrgFreedesktopNotificationsInterface: public QDBusAbstractInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static inline const char *staticInterfaceName()
|
||||
{ return "org.freedesktop.Notifications"; }
|
||||
|
||||
public:
|
||||
OrgFreedesktopNotificationsInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr);
|
||||
|
||||
~OrgFreedesktopNotificationsInterface();
|
||||
|
||||
public Q_SLOTS: // METHODS
|
||||
inline QDBusPendingReply<> CloseNotification(uint id)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(id);
|
||||
return asyncCallWithArgumentList(QStringLiteral("CloseNotification"), argumentList);
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<QStringList> GetCapabilities()
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
return asyncCallWithArgumentList(QStringLiteral("GetCapabilities"), argumentList);
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<QString, QString, QString, QString> GetServerInformation()
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
return asyncCallWithArgumentList(QStringLiteral("GetServerInformation"), argumentList);
|
||||
}
|
||||
inline QDBusReply<QString> GetServerInformation(QString &return_vendor, QString &return_version, QString &return_spec_version)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
QDBusMessage reply = callWithArgumentList(QDBus::Block, QStringLiteral("GetServerInformation"), argumentList);
|
||||
if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().count() == 4) {
|
||||
return_vendor = qdbus_cast<QString>(reply.arguments().at(1));
|
||||
return_version = qdbus_cast<QString>(reply.arguments().at(2));
|
||||
return_spec_version = qdbus_cast<QString>(reply.arguments().at(3));
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
inline QDBusPendingReply<uint> Notify(const QString &app_name, uint id, const QString &icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout)
|
||||
{
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(app_name) << QVariant::fromValue(id) << QVariant::fromValue(icon) << QVariant::fromValue(summary) << QVariant::fromValue(body) << QVariant::fromValue(actions) << QVariant::fromValue(hints) << QVariant::fromValue(timeout);
|
||||
return asyncCallWithArgumentList(QStringLiteral("Notify"), argumentList);
|
||||
}
|
||||
|
||||
Q_SIGNALS: // SIGNALS
|
||||
};
|
||||
|
||||
namespace org {
|
||||
namespace freedesktop {
|
||||
typedef ::OrgFreedesktopNotificationsInterface Notifications;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,30 +0,0 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.freedesktop.Notifications">
|
||||
<method name="GetServerInformation">
|
||||
<arg name="return_name" type="s" direction="out"/>
|
||||
<arg name="return_vendor" type="s" direction="out"/>
|
||||
<arg name="return_version" type="s" direction="out"/>
|
||||
<arg name="return_spec_version" type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="GetCapabilities">
|
||||
<arg name="return_caps" type="as" direction="out"/>
|
||||
</method>
|
||||
<method name="CloseNotification">
|
||||
<arg name="id" type="u" direction="in"/>
|
||||
</method>
|
||||
<method name="Notify">
|
||||
<arg name="app_name" type="s" direction="in"/>
|
||||
<arg name="id" type="u" direction="in"/>
|
||||
<arg name="icon" type="s" direction="in"/>
|
||||
<arg name="summary" type="s" direction="in"/>
|
||||
<arg name="body" type="s" direction="in"/>
|
||||
<arg name="actions" type="as" direction="in"/>
|
||||
<arg name="hints" type="a{sv}" direction="in"/>
|
||||
<annotation name="org.qtproject.QtDBus.QtTypeName.In6" value="QVariantMap"/>
|
||||
<arg name="timeout" type="i" direction="in"/>
|
||||
<arg name="return_id" type="u" direction="out"/>
|
||||
</method>
|
||||
</interface>
|
||||
</node>
|
||||
@@ -113,9 +113,9 @@ namespace
|
||||
{
|
||||
QPixmap pixmapForExtension(const QString &ext) const override
|
||||
{
|
||||
const QString extWithDot = QLatin1Char('.') + ext;
|
||||
const std::wstring extWStr = QString(QLatin1Char('.') + ext).toStdWString();
|
||||
SHFILEINFO sfi {};
|
||||
HRESULT hr = ::SHGetFileInfoW(extWithDot.toStdWString().c_str(),
|
||||
HRESULT hr = ::SHGetFileInfoW(extWStr.c_str(),
|
||||
FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_USEFILEATTRIBUTES);
|
||||
if (FAILED(hr))
|
||||
return {};
|
||||
@@ -313,7 +313,16 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu
|
||||
item->setName(value.toString());
|
||||
break;
|
||||
case TorrentContentModelItem::COL_PRIO:
|
||||
item->setPriority(static_cast<BitTorrent::DownloadPriority>(value.toInt()));
|
||||
{
|
||||
const BitTorrent::DownloadPriority previousPrio = item->priority();
|
||||
const auto newPrio = static_cast<BitTorrent::DownloadPriority>(value.toInt());
|
||||
item->setPriority(newPrio);
|
||||
if ((newPrio != previousPrio) && ((newPrio == BitTorrent::DownloadPriority::Ignored)
|
||||
|| (previousPrio == BitTorrent::DownloadPriority::Ignored)))
|
||||
{
|
||||
emit filteredFilesChanged();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -519,6 +519,9 @@ void TorrentOptionsDialog::handleCategoryChanged(const int index)
|
||||
{
|
||||
const QString savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->comboCategory->currentText());
|
||||
m_ui->savePath->setSelectedPath(Utils::Fs::toNativePath(savePath));
|
||||
const QString downloadPath = BitTorrent::Session::instance()->categoryDownloadPath(m_ui->comboCategory->currentText());
|
||||
m_ui->downloadPath->setSelectedPath(Utils::Fs::toNativePath(downloadPath));
|
||||
m_ui->checkUseDownloadPath->setChecked(!downloadPath.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -331,6 +331,39 @@ QVector<BitTorrent::Torrent *> TransferListWidget::getVisibleTorrents() const
|
||||
return torrents;
|
||||
}
|
||||
|
||||
void TransferListWidget::setSelectedTorrentsLocation()
|
||||
{
|
||||
const QVector<BitTorrent::Torrent *> torrents = getSelectedTorrents();
|
||||
if (torrents.isEmpty())
|
||||
return;
|
||||
|
||||
const QString oldLocation = torrents[0]->savePath();
|
||||
|
||||
auto fileDialog = new QFileDialog(this, tr("Choose save path"), oldLocation);
|
||||
fileDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
fileDialog->setFileMode(QFileDialog::Directory);
|
||||
fileDialog->setOptions(QFileDialog::DontConfirmOverwrite | QFileDialog::ShowDirsOnly | QFileDialog::HideNameFilterDetails);
|
||||
connect(fileDialog, &QDialog::accepted, this, [this, fileDialog]()
|
||||
{
|
||||
const QVector<BitTorrent::Torrent *> torrents = getSelectedTorrents();
|
||||
if (torrents.isEmpty())
|
||||
return;
|
||||
|
||||
const QString newLocation = fileDialog->selectedFiles().constFirst();
|
||||
if (newLocation.isEmpty() || !QDir(newLocation).exists())
|
||||
return;
|
||||
|
||||
// Actually move storage
|
||||
for (BitTorrent::Torrent *const torrent : torrents)
|
||||
{
|
||||
torrent->setAutoTMMEnabled(false);
|
||||
torrent->setSavePath(newLocation);
|
||||
}
|
||||
});
|
||||
|
||||
fileDialog->open();
|
||||
}
|
||||
|
||||
void TransferListWidget::pauseAllTorrents()
|
||||
{
|
||||
for (BitTorrent::Torrent *const torrent : asConst(BitTorrent::Session::instance()->torrents()))
|
||||
@@ -647,8 +680,28 @@ void TransferListWidget::setSelectedTorrentsSuperSeeding(const bool enabled) con
|
||||
}
|
||||
}
|
||||
|
||||
void TransferListWidget::setSelectedAutoTMMEnabled(const bool enabled) const
|
||||
void TransferListWidget::setSelectedTorrentsSequentialDownload(const bool enabled) const
|
||||
{
|
||||
for (BitTorrent::Torrent *const torrent : asConst(getSelectedTorrents()))
|
||||
torrent->setSequentialDownload(enabled);
|
||||
}
|
||||
|
||||
void TransferListWidget::setSelectedFirstLastPiecePrio(const bool enabled) const
|
||||
{
|
||||
for (BitTorrent::Torrent *const torrent : asConst(getSelectedTorrents()))
|
||||
torrent->setFirstLastPiecePriority(enabled);
|
||||
}
|
||||
|
||||
void TransferListWidget::setSelectedAutoTMMEnabled(const bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
const QMessageBox::StandardButton btn = QMessageBox::question(this, tr("Enable automatic torrent management")
|
||||
, tr("Are you sure you want to enable Automatic Torrent Management for the selected torrent(s)? They may be relocated.")
|
||||
, (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
|
||||
if (btn != QMessageBox::Yes) return;
|
||||
}
|
||||
|
||||
for (BitTorrent::Torrent *const torrent : asConst(getSelectedTorrents()))
|
||||
torrent->setAutoTMMEnabled(enabled);
|
||||
}
|
||||
@@ -796,6 +849,7 @@ void TransferListWidget::displayListMenu(const QPoint &)
|
||||
|
||||
auto *listMenu = new QMenu(this);
|
||||
listMenu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
listMenu->setToolTipsVisible(true);
|
||||
|
||||
// Create actions
|
||||
|
||||
@@ -821,6 +875,8 @@ void TransferListWidget::displayListMenu(const QPoint &)
|
||||
connect(actionTopQueuePos, &QAction::triggered, this, &TransferListWidget::topQueuePosSelectedTorrents);
|
||||
auto *actionBottomQueuePos = new QAction(UIThemeManager::instance()->getIcon("go-bottom"), tr("Move to bottom", "i.e. Move to bottom of the queue"), listMenu);
|
||||
connect(actionBottomQueuePos, &QAction::triggered, this, &TransferListWidget::bottomQueuePosSelectedTorrents);
|
||||
auto *actionSetTorrentPath = new QAction(UIThemeManager::instance()->getIcon("inode-directory"), tr("Set location..."), listMenu);
|
||||
connect(actionSetTorrentPath, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsLocation);
|
||||
auto *actionForceRecheck = new QAction(UIThemeManager::instance()->getIcon("document-edit-verify"), tr("Force recheck"), listMenu);
|
||||
connect(actionForceRecheck, &QAction::triggered, this, &TransferListWidget::recheckSelectedTorrents);
|
||||
auto *actionForceReannounce = new QAction(UIThemeManager::instance()->getIcon("document-edit-verify"), tr("Force reannounce"), listMenu);
|
||||
@@ -839,6 +895,13 @@ void TransferListWidget::displayListMenu(const QPoint &)
|
||||
connect(actionSuperSeedingMode, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsSuperSeeding);
|
||||
auto *actionRename = new QAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename..."), listMenu);
|
||||
connect(actionRename, &QAction::triggered, this, &TransferListWidget::renameSelectedTorrent);
|
||||
auto *actionSequentialDownload = new TriStateAction(tr("Download in sequential order"), listMenu);
|
||||
connect(actionSequentialDownload, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsSequentialDownload);
|
||||
auto *actionFirstLastPiecePrio = new TriStateAction(tr("Download first and last pieces first"), listMenu);
|
||||
connect(actionFirstLastPiecePrio, &QAction::triggered, this, &TransferListWidget::setSelectedFirstLastPiecePrio);
|
||||
auto *actionAutoTMM = new TriStateAction(tr("Automatic Torrent Management"), listMenu);
|
||||
actionAutoTMM->setToolTip(tr("Automatic mode means that various torrent properties (e.g. save path) will be decided by the associated category"));
|
||||
connect(actionAutoTMM, &QAction::triggered, this, &TransferListWidget::setSelectedAutoTMMEnabled);
|
||||
auto *actionEditTracker = new QAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Edit trackers..."), listMenu);
|
||||
connect(actionEditTracker, &QAction::triggered, this, &TransferListWidget::editTorrentTrackers);
|
||||
// End of actions
|
||||
@@ -851,6 +914,8 @@ void TransferListWidget::displayListMenu(const QPoint &)
|
||||
bool sequentialDownloadMode = false, prioritizeFirstLast = false;
|
||||
bool oneHasMetadata = false, oneNotSeed = false;
|
||||
bool allSameCategory = true;
|
||||
bool allSameAutoTMM = true;
|
||||
bool firstAutoTMM = false;
|
||||
QString firstCategory;
|
||||
bool first = true;
|
||||
TagSet tagsInAny;
|
||||
@@ -874,6 +939,7 @@ void TransferListWidget::displayListMenu(const QPoint &)
|
||||
|
||||
if (first)
|
||||
{
|
||||
firstAutoTMM = torrent->isAutoTMMEnabled();
|
||||
tagsInAll = torrentTags;
|
||||
}
|
||||
else
|
||||
@@ -881,6 +947,9 @@ void TransferListWidget::displayListMenu(const QPoint &)
|
||||
tagsInAll.intersect(torrentTags);
|
||||
}
|
||||
|
||||
if (firstAutoTMM != torrent->isAutoTMMEnabled())
|
||||
allSameAutoTMM = false;
|
||||
|
||||
if (torrent->hasMetadata())
|
||||
oneHasMetadata = true;
|
||||
if (!torrent->isSeed())
|
||||
@@ -940,7 +1009,7 @@ void TransferListWidget::displayListMenu(const QPoint &)
|
||||
|
||||
if (oneHasMetadata && oneNotSeed && !allSameSequentialDownloadMode
|
||||
&& !allSamePrioFirstlast && !allSameSuperSeeding && !allSameCategory
|
||||
&& needsStart && needsForce && needsPause && needsPreview
|
||||
&& needsStart && needsForce && needsPause && needsPreview && !allSameAutoTMM
|
||||
&& hasInfohashV1 && hasInfohashV2)
|
||||
{
|
||||
break;
|
||||
@@ -956,10 +1025,36 @@ void TransferListWidget::displayListMenu(const QPoint &)
|
||||
listMenu->addSeparator();
|
||||
listMenu->addAction(actionDelete);
|
||||
listMenu->addSeparator();
|
||||
listMenu->addAction(actionSetTorrentPath);
|
||||
if (selectedIndexes.size() == 1)
|
||||
listMenu->addAction(actionRename);
|
||||
listMenu->addAction(actionEditTracker);
|
||||
|
||||
// Category Menu
|
||||
QStringList categories = BitTorrent::Session::instance()->categories();
|
||||
std::sort(categories.begin(), categories.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
|
||||
|
||||
QMenu *categoryMenu = listMenu->addMenu(UIThemeManager::instance()->getIcon("view-categories"), tr("Category"));
|
||||
|
||||
categoryMenu->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("New...", "New category...")
|
||||
, this, &TransferListWidget::askNewCategoryForSelection);
|
||||
categoryMenu->addAction(UIThemeManager::instance()->getIcon("edit-clear"), tr("Reset", "Reset category")
|
||||
, this, [this]() { setSelectionCategory(""); });
|
||||
categoryMenu->addSeparator();
|
||||
|
||||
for (const QString &category : asConst(categories))
|
||||
{
|
||||
const QString escapedCategory = QString(category).replace('&', "&&"); // avoid '&' becomes accelerator key
|
||||
QAction *categoryAction = categoryMenu->addAction(UIThemeManager::instance()->getIcon("inode-directory"), escapedCategory
|
||||
, this, [this, category]() { setSelectionCategory(category); });
|
||||
|
||||
if (allSameCategory && (category == firstCategory))
|
||||
{
|
||||
categoryAction->setCheckable(true);
|
||||
categoryAction->setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Tag Menu
|
||||
QStringList tags(BitTorrent::Session::instance()->tags().values());
|
||||
std::sort(tags.begin(), tags.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
|
||||
@@ -998,6 +1093,11 @@ void TransferListWidget::displayListMenu(const QPoint &)
|
||||
tagsMenu->addAction(action);
|
||||
}
|
||||
|
||||
actionAutoTMM->setCheckState(allSameAutoTMM
|
||||
? (firstAutoTMM ? Qt::Checked : Qt::Unchecked)
|
||||
: Qt::PartiallyChecked);
|
||||
listMenu->addAction(actionAutoTMM);
|
||||
|
||||
listMenu->addSeparator();
|
||||
listMenu->addAction(actionTorrentOptions);
|
||||
if (!oneNotSeed && oneHasMetadata)
|
||||
@@ -1015,7 +1115,19 @@ void TransferListWidget::displayListMenu(const QPoint &)
|
||||
addedPreviewAction = true;
|
||||
}
|
||||
if (oneNotSeed)
|
||||
{
|
||||
actionSequentialDownload->setCheckState(allSameSequentialDownloadMode
|
||||
? (sequentialDownloadMode ? Qt::Checked : Qt::Unchecked)
|
||||
: Qt::PartiallyChecked);
|
||||
listMenu->addAction(actionSequentialDownload);
|
||||
|
||||
actionFirstLastPiecePrio->setCheckState(allSamePrioFirstlast
|
||||
? (prioritizeFirstLast ? Qt::Checked : Qt::Unchecked)
|
||||
: Qt::PartiallyChecked);
|
||||
listMenu->addAction(actionFirstLastPiecePrio);
|
||||
|
||||
addedPreviewAction = true;
|
||||
}
|
||||
|
||||
if (addedPreviewAction)
|
||||
listMenu->addSeparator();
|
||||
|
||||
@@ -65,6 +65,7 @@ public slots:
|
||||
void addSelectionTag(const QString &tag);
|
||||
void removeSelectionTag(const QString &tag);
|
||||
void clearSelectionTags();
|
||||
void setSelectedTorrentsLocation();
|
||||
void pauseAllTorrents();
|
||||
void resumeAllTorrents();
|
||||
void startSelectedTorrents();
|
||||
@@ -90,7 +91,7 @@ public slots:
|
||||
void setTorrentOptions();
|
||||
void previewSelectedTorrents();
|
||||
void hideQueuePosColumn(bool hide);
|
||||
void displayDLHoSMenu(const QPoint&);
|
||||
void displayDLHoSMenu(const QPoint &);
|
||||
void applyNameFilter(const QString &name);
|
||||
void applyStatusFilter(int f);
|
||||
void applyCategoryFilter(const QString &category);
|
||||
@@ -108,7 +109,9 @@ private slots:
|
||||
void displayListMenu(const QPoint &);
|
||||
void currentChanged(const QModelIndex ¤t, const QModelIndex&) override;
|
||||
void setSelectedTorrentsSuperSeeding(bool enabled) const;
|
||||
void setSelectedAutoTMMEnabled(bool enabled) const;
|
||||
void setSelectedTorrentsSequentialDownload(bool enabled) const;
|
||||
void setSelectedFirstLastPiecePrio(bool enabled) const;
|
||||
void setSelectedAutoTMMEnabled(bool enabled);
|
||||
void askNewCategoryForSelection();
|
||||
void saveSettings();
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include <QRegularExpression>
|
||||
#include <QScreen>
|
||||
#include <QStyle>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
#include <QWidget>
|
||||
#include <QWindow>
|
||||
@@ -167,15 +168,23 @@ void Utils::Gui::openFolderSelect(const QString &absolutePath)
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
HRESULT hresult = ::CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
PIDLIST_ABSOLUTE pidl = ::ILCreateFromPathW(reinterpret_cast<PCTSTR>(Utils::Fs::toNativePath(path).utf16()));
|
||||
if (pidl)
|
||||
auto *thread = QThread::create([path]()
|
||||
{
|
||||
::SHOpenFolderAndSelectItems(pidl, 0, nullptr, 0);
|
||||
::ILFree(pidl);
|
||||
}
|
||||
if ((hresult == S_OK) || (hresult == S_FALSE))
|
||||
::CoUninitialize();
|
||||
if (SUCCEEDED(::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)))
|
||||
{
|
||||
const std::wstring pathWStr = Utils::Fs::toNativePath(path).toStdWString();
|
||||
PIDLIST_ABSOLUTE pidl = ::ILCreateFromPathW(pathWStr.c_str());
|
||||
if (pidl)
|
||||
{
|
||||
::SHOpenFolderAndSelectItems(pidl, 0, nullptr, 0);
|
||||
::ILFree(pidl);
|
||||
}
|
||||
|
||||
::CoUninitialize();
|
||||
}
|
||||
});
|
||||
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
||||
thread->start();
|
||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||
QProcess proc;
|
||||
proc.start("xdg-mime", {"query", "default", "inode/directory"});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user