Compare commits

...

91 Commits

Author SHA1 Message Date
sledgehammer999
2bbfd317ce Sync translations from Transifex and run lupdate 2023-06-18 01:37:12 +03:00
Chocobo1
f6b58f36e2 WebUI: set Cross Origin Opener Policy to same-origin
This separates browsing context for different origin sites and prevents
leaking data from it.
This header is only present when using built-in WebUI. Alternative WebUI
is not affected.
https://web.dev/why-coop-coep/#coop

PR #19157.
2023-06-14 13:38:48 +08:00
Chocobo1
79ca2e145f Don't read unlimited data from files
It now guards against reading infinite files such as `/dev/zero`.
And most readings are bound with a (lax) limit.
As a side effect, more checking are done when reading a file and
overall the reading procedure is more robust.

PR #19095.
2023-06-14 13:38:19 +08:00
Chocobo1
81bc910d68 Provide context to translation strings
PR #19120.
2023-06-12 14:03:12 +08:00
Vort
ff5d02bcf2 Make I2P session options configurable
PR #19079.
Closes #18980.
2023-06-06 08:35:40 +03:00
tearfur
2e87e6e0df Use hostname instead of domain name in tracker filter list
Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>

PR #19062.
Closes #19035.
2023-06-05 14:57:37 +03:00
Vladimir Golovnev
a5e8af5070 Allow to assign priority to RSS download rule
PR #19000.
2023-06-05 14:55:41 +03:00
Vladimir Golovnev
cf415dd7fe Allow to disable confirmation of Pause/Resume All
PR #19067.
Closes #18155.
2023-06-04 08:57:14 +03:00
Chocobo1
83e6afcb71 Merge pull request #19069 from Chocobo1/sort
WebUI: use natural sort on tracker list
2023-06-04 12:52:29 +08:00
Chocobo1
62d96c068a Remove SGML parser
This library is unmaintained, outdated and plugin authors are encouraged to use html.parser
from Python Standard Library instead.

https://docs.python.org/3/library/html.parser.html

PR #19068.
2023-06-04 12:52:06 +08:00
xavier2k6
040c3c7ef8 Sync "expected lite" with upstream
PR #19049.
2023-06-03 17:42:57 +03:00
Raymond Ha
3ef8726083 WebUI: Set Connection status and Speed limits tooltips
PR #19052.
Fixes #18958.
2023-06-03 17:39:58 +03:00
Chocobo1
dad9157d84 Don't overwrite original variable 2023-06-02 18:12:01 +08:00
Chocobo1
5cea69472f Use natural sort 2023-06-02 17:44:17 +08:00
ttys3
b1492bcd7d WebUI: Show only hosts in tracker filter list
PR #18190.
2023-06-02 17:36:33 +08:00
sledgehammer999
d571ab2be1 Update AppVeyor config
The config needs some updating to accommodate the new structure.

PR #19030.
2023-06-02 17:02:31 +08:00
Vladimir Golovnev
4550469bb9 Fix incorrect height of Filter line edit
PR #19058.
2023-06-02 11:47:53 +03:00
Vladimir Golovnev
160af4feef Show I2P peer addresses
PR #18845.
2023-06-01 17:16:03 +03:00
Priit Uring
b27e839405 Sync flag icons with upstream
PR #19027.
2023-06-01 06:49:09 +03:00
sledgehammer999
ecc08dee09 Bump to 4.6.0beta1 2023-05-29 16:03:44 +03:00
Chocobo1
11ac4e7620 GHA CI: upload macOS bundles
Hopefully those bundles will be runnable on users machine.

PR #19023.
2023-05-29 12:24:12 +08:00
Chocobo1
fbe93f0c47 Improve "apply memory working set" routine
Now it will try to raise the hard limit.
And also the log shows a more specific message when the new limit is not
applicable.

PR #19022.
2023-05-28 13:41:44 +08:00
sledgehammer999
11945eef3f Sync translations from Transifex and run lupdate 2023-05-28 01:40:37 +03:00
Raymond Ha
a35dbc6df7 WebUI: Fix category save path
PR #19008.
2023-05-26 11:52:37 +03:00
Chocobo1
3fb4e4d293 GHA CI: build libtorrent as a static library
Since appimage is bundling the libraries it make sense to embed libtorrent statically into qbt binary.
Another side effect is now qbt binary includes debug symbols from libtorrent too (which I consider a good thing for debugging). Previously appimage seems to (unnecessarily) strip the libtorrent debug symbols.

PR #19014.
2023-05-25 13:08:53 +08:00
Vladimir Golovnev
f5a4065101 Raise minimum libtorrent versions
PR #19011.
2023-05-25 06:31:28 +03:00
xavier2k6
ba93d55a6d GHA CI: Bump libtorrent version(s)
PR #19006.
2023-05-24 05:10:57 +03:00
Vladimir Golovnev
a59301712e Avoid race condition when waking worker thread
PR #19005.
2023-05-23 02:22:16 +03:00
Chocobo1
b406d669b3 Bump python version minimum requirement
PR #18996.
2023-05-22 12:37:02 +08:00
Chocobo1
4ef8f39f23 Use python isolate mode
This (more or less) avoids user's environment variables tampering the
search process.
And also remove usages of `eval()` and `exec()`.

PR #18995.
2023-05-21 14:04:44 +08:00
Vladimir Golovnev
34802362ad Fix inconsistent background of filters widget
PR #18956.
Fixes regression of #18918.
2023-05-11 09:11:11 +03:00
Vladimir Golovnev
c10f1f0ad2 Consider explicitly specified parameters when resolving optional ones
PR #18955.
Closes #18951.
2023-05-11 09:09:57 +03:00
Vladimir Golovnev
58ae98026b Try to detect system wake-up event
PR #18934.
Closes #17898.
2023-05-10 15:02:15 +03:00
Chocobo1
32a55551fe Merge pull request #18936 from Chocobo1/tidy
Code clean up
2023-05-08 13:11:11 +08:00
Vladimir Golovnev
7880fe8440 Merge pull request #18824 from glassez/atp-edit
Unify/improve "add torrent parameters" template editing UI
2023-05-07 19:38:35 +03:00
Vladimir Golovnev (Glassez)
bb959bda8c Add helpers for suggesting torrent paths 2023-05-07 16:34:56 +03:00
Vladimir Golovnev (Glassez)
d629c77184 Improve FlowLayout to support vertical alignment 2023-05-07 16:34:56 +03:00
Vladimir Golovnev (Glassez)
b953d223e4 Use check box to represent "Skip checking" 2023-05-07 16:34:56 +03:00
Vladimir Golovnev (Glassez)
6fa53b5ed8 Override add torrent params in a more comprehensible way 2023-05-07 16:34:56 +03:00
Vladimir Golovnev (Glassez)
c777ed3299 Correctly use fallback value for "Add to top of queue" option 2023-05-07 16:34:56 +03:00
Vladimir Golovnev (Glassez)
341b2f345a Use FlowLayout in AddTorrentParamsWidget 2023-05-07 16:34:55 +03:00
Vladimir Golovnev (Glassez)
905f141657 Revamp "Automated RSS downloader" dialog 2023-05-07 16:34:52 +03:00
Vladimir Golovnev (Glassez)
0a87bb368f Extract "add torrent params" serialization code 2023-05-07 16:33:32 +03:00
Vladimir Golovnev (Glassez)
93a1e58554 Revamp "Watched folder options" dialog 2023-05-07 16:33:32 +03:00
Vladimir Golovnev (Glassez)
0cc29f1851 Implement "Add torrent params" editing widget 2023-05-07 16:33:20 +03:00
Chocobo1
81daad92ec Combine identical branches 2023-05-07 19:41:55 +08:00
Chocobo1
41be7e9bbe Make function const 2023-05-07 19:41:55 +08:00
Chocobo1
179a61d75e Add curly braces 2023-05-07 19:41:55 +08:00
Chocobo1
73134d5f4d Initialize member variables 2023-05-07 16:30:56 +08:00
Chocobo1
29c05ed3e8 Use std::make_unique 2023-05-07 14:38:03 +08:00
Chocobo1
e375f3ee0b Use reference 2023-05-07 14:38:02 +08:00
Chocobo1
b185153254 Merge pull request #18931 from Chocobo1/tidy
Clean up code
2023-05-07 14:20:47 +08:00
Chocobo1
e7e5c38384 Remove superfluous header include 2023-05-06 16:48:49 +08:00
Chocobo1
9a00839a75 Simplify code 2023-05-06 16:35:13 +08:00
Chocobo1
79e85d01fa Mark move functions as noexcept 2023-05-06 16:35:13 +08:00
Chocobo1
e408973ee6 Add pointer qualifications to auto-typed variables 2023-05-06 16:35:13 +08:00
Chocobo1
8c9b6e2f2d Use reference whenever possible 2023-05-06 16:35:13 +08:00
Chocobo1
5b43782f58 Remove redundant virtual specifier 2023-05-06 16:35:12 +08:00
Chocobo1
2059825597 Don't use instance for accessing static functions 2023-05-06 16:35:12 +08:00
Chocobo1
e1be46820b Remove redundant initialization 2023-05-06 16:35:12 +08:00
Chocobo1
8219b1f695 Use default constructor, destructor 2023-05-06 16:35:12 +08:00
Chocobo1
3fbe380582 Remove redundant function declaration 2023-05-05 14:51:02 +08:00
Chocobo1
5f00d42a49 Drop superfluous const 2023-05-05 14:51:02 +08:00
Chocobo1
15de7aed9a Use perfect forwarding 2023-05-05 14:51:02 +08:00
Chocobo1
5c38cc00d9 Add support for clang-tidy 2023-05-05 14:51:02 +08:00
Vladimir Golovnev
5a1dcbae9c Don't make assertion about 3rd party logic
PR #18913.
2023-05-03 07:24:52 +03:00
Vladimir Golovnev
7c6a852f85 Revamp TransferListFiltersWidget implementation
Avoid using style sheets to better support color mode switching.

PR #18918.
2023-05-03 07:24:03 +03:00
Vladimir Golovnev
147b22ddd3 Revamp LineEdit implementation
PR #18917.
2023-05-03 07:23:19 +03:00
Vladimir Golovnev
d83b2a6131 Make sure ResumeSessionContext is destroyed before start processing
PR #18912.
2023-05-02 09:48:49 +03:00
Vladimir Golovnev
821e946bbe Remove outdated code
PR #18908.
2023-05-01 08:29:35 +03:00
Deltadroid
634eb4a183 Replace status_t with disk_status
libtorrent 2.1 has made the following change:
"make status_t a proper flag type, to clean up oversized_file indication
from disk subsystem"

PR #18879.
2023-04-30 10:12:15 +03:00
Vladimir Golovnev
758ea7edca Improve logging of running external program
PR #18901.
2023-04-30 10:10:03 +03:00
Vladimir Golovnev
1bd499565e Completely initialize native status on torrent creation
PR #18900.
2023-04-30 10:09:09 +03:00
Chocobo1
be9ec5a329 Merge pull request #18877 from Chocobo1/ci
Clean up CI scripts
2023-04-29 13:36:41 +08:00
Chocobo1
df895cb2a7 Improve script compatibility
Now the script is conforms to POSIX shell script which is universal on
all linux.
Also make it executable.
2023-04-24 13:31:19 +08:00
Chocobo1
3b72859980 Bump various action versions 2023-04-24 13:18:17 +08:00
Chocobo1
69df85f564 Move script into its own subfolder 2023-04-24 13:18:10 +08:00
Chocobo1
1f1da32371 Rename CI script
See: https://stackoverflow.com/questions/22268952/what-is-the-difference-between-yaml-and-yml-extension
2023-04-24 13:18:08 +08:00
xavier2k6
cddf8c199c GHA CI: Update some dependencies
PR #18870.
2023-04-24 13:15:46 +08:00
Chocobo1
bbd5ed1142 Switch URLs to https
PR #18876.
2023-04-23 15:09:58 +08:00
sledgehammer999
0f033ec9c8 Regenerate translation files 2023-04-20 03:34:29 +03:00
Chocobo1
7397c80837 WebUI: improve 'exporting torrent' behavior
Don't stop the whole operation when a torrent doesn't exists and try to export the remaining
existing ones.

PR #18858.
2023-04-18 13:59:55 +08:00
Vladimir Golovnev
51132c817b Improve move storage handling
PR #18857.
Closes #18795.
2023-04-18 08:06:18 +03:00
Chocobo1
1fe006d16f Merge pull request #18853 from Chocobo1/exportTorrent
Work around Chrome download limit
2023-04-17 00:12:30 +08:00
Sentox6
bd31eddb94 Inhibit system sleep while torrents are moving
PR #18783.
2023-04-16 18:09:34 +03:00
DivineHawk
0defb7d79d WebUI: Use workaround for IOS file picker
PR #18837.
Fixes #18683.
2023-04-16 14:30:30 +03:00
七海千秋
1e400df324 Set "SameSite=None" if CSRF Protection is disabled
PR #18843.
2023-04-16 14:27:49 +03:00
Chocobo1
9ea48539b4 Inline variable declared in header 2023-04-15 14:53:13 +08:00
Chocobo1
d63e0ad78f Work around Chrome download limit
Closes #18775.
2023-04-15 14:51:27 +08:00
Vladimir Golovnev
eaee38a19e Disable UPnP for web UI by default
PR #18832.
2023-04-13 06:22:18 +03:00
Vladimir Golovnev
b3e9c46eff Don't miss saving "download path" in SQLite storage
PR #18844.
Closes #18842.
2023-04-13 06:18:09 +03:00
376 changed files with 148819 additions and 139349 deletions

View File

@@ -37,8 +37,6 @@ install:
RMDIR /S /Q "%CACHE_DIR%" & MKDIR "%CACHE_DIR%" &&
appveyor DownloadFile "%QBT_LIB_URL%" -FileName "c:\qbt_lib.7z" && 7z x "c:\qbt_lib.7z" -o"%CACHE_DIR%" > nul &&
COPY "c:\version_new" "%CACHE_DIR%\version")
# Qt stay compressed in cache
- 7z x "%CACHE_DIR%\qt5_64.7z" -o"c:\qbt" > nul
before_build:
# setup env
@@ -47,6 +45,7 @@ before_build:
# setup project
- COPY /Y "%CACHE_DIR%\conf.pri" "%REPO_DIR%"
# workarounds
- MKDIR "c:\qbt"
- MKLINK /J "c:\qbt\base" "%CACHE_DIR%\base"
build_script:
@@ -69,8 +68,11 @@ after_build:
- COPY src\release\qbittorrent.exe upload
- COPY src\release\qbittorrent.pdb upload
- COPY "%CACHE_DIR%\base\bin\libcrypto-1_1-x64.dll" upload
- COPY "%CACHE_DIR%\base\bin\libcrypto-1_1-x64.pdb" upload
- COPY "%CACHE_DIR%\base\bin\libssl-1_1-x64.dll" upload
- COPY "%CACHE_DIR%\base\lib\torrent-rasterbar.dll" upload
- COPY "%CACHE_DIR%\base\bin\libssl-1_1-x64.pdb" upload
- COPY "%CACHE_DIR%\base\bin\torrent-rasterbar.dll" upload
- COPY "%CACHE_DIR%\base\bin\torrent-rasterbar.pdb" upload
- COPY "%CACHE_DIR%\base\lib\zlib1.dll" upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Core.dll upload
- COPY C:\Qt\5.15.2\msvc2019_64\bin\Qt5Gui.dll upload

82
.clang-tidy Normal file
View File

@@ -0,0 +1,82 @@
Checks: >
bugprone-*,
cert-*,
concurrency-*,
cppcoreguidelines-*,
misc-*,
modernize-*,
performance-*,
portability-*,
readability-*,
-# not applicable at all,
-bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-macro-parentheses,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-const-cast,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-virtual-class-destructor,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-unused-parameters,
-modernize-avoid-c-arrays,
-modernize-pass-by-value,
-modernize-use-auto,
-modernize-use-nodiscard,
-modernize-use-trailing-return-type,
-modernize-use-using,
-readability-function-cognitive-complexity,
-readability-function-size,
-readability-identifier-length,
-readability-implicit-bool-conversion,
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-named-parameter,
-readability-redundant-access-specifiers,
-readability-simplify-boolean-expr,
-readability-uppercase-literal-suffix,
-# only sometimes useful,
-bugprone-narrowing-conversions,
-cert-dcl58-cpp,
-cert-err33-c,
-cert-err58-cpp,
-clang-analyzer-core.CallAndMessage,
-clang-analyzer-cplusplus.NewDelete,
-clang-analyzer-cplusplus.NewDeleteLeaks,
-concurrency-mt-unsafe,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-type-static-cast-downcast,
-misc-definitions-in-headers,
-modernize-concat-nested-namespaces,
-modernize-loop-convert,
-modernize-raw-string-literal,
-modernize-unary-static-assert,
-performance-no-automatic-move,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
-readability-redundant-declaration,
-# obsoleted,
-cert-dcl21-cpp
CheckOptions:
- { key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors, value: true }
- { key: modernize-use-override.IgnoreDestructors, value: true }
- { key: performance-for-range-copy.AllowedTypes, value: "QJsonValue" }
- { key: performance-for-range-copy.WarnOnAllAutoCopies, value: true }
- { key: readability-braces-around-statements.ShortStatementLines, value: 3 }
HeaderFilterRegex: ".+/src/.*\\.h"
WarningsAsErrors: "*"

View File

@@ -17,12 +17,12 @@ jobs:
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.8", "1.2.18"]
libt_version: ["2.0.9", "1.2.19"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["5.15.2", "6.4.0"]
qt_version: ["5.15.2", "6.5.0"]
exclude:
- libt_version: "1.2.18"
qt_version: "6.4.0"
- libt_version: "1.2.19"
qt_version: "6.5.0"
env:
boost_path: "${{ github.workspace }}/../boost"
@@ -52,7 +52,7 @@ jobs:
curl \
-L \
-o "${{ runner.temp }}/boost.tar.bz2" \
"https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.bz2"
"https://boostorg.jfrog.io/artifactory/main/release/1.82.0/source/boost_1_82_0.tar.bz2"
tar -xf "${{ runner.temp }}/boost.tar.bz2" -C "${{ github.workspace }}/.."
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
@@ -74,6 +74,7 @@ jobs:
cmake \
-B build \
-G "Ninja" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
@@ -124,7 +125,17 @@ jobs:
- name: Prepare build artifacts
run: |
# create .dmg
appName="qbittorrent"
if [ "${{ matrix.qbt_gui }}" = "GUI=OFF" ]; then
appName="qbittorrent-nox"
fi
pushd build
macdeployqt "$appName.app" -dmg -no-strip
popd
# prepare upload folder
mkdir upload
cp "build/$appName.dmg" upload
mkdir upload/cmake
cp build/compile_commands.json upload/cmake
mkdir upload/cmake/libtorrent
@@ -133,5 +144,5 @@ jobs:
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-info_macOS_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
name: qBittorrent-CI_macOS_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
path: upload

View File

@@ -18,11 +18,11 @@ jobs:
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.8", "1.2.18"]
libt_version: ["2.0.9", "1.2.19"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["5.15.2", "6.2.0"]
exclude:
- libt_version: "1.2.18"
- libt_version: "1.2.19"
qt_version: "6.2.0"
steps:
@@ -60,6 +60,7 @@ jobs:
cmake \
-B build \
-G "Ninja" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-Ddeprecated-functions=OFF

View File

@@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.8", "1.2.18"]
libt_version: ["2.0.9", "1.2.19"]
env:
boost_path: "${{ github.workspace }}/../boost"
@@ -70,7 +70,7 @@ jobs:
- name: Install boost
run: |
aria2c `
"https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.7z" `
"https://boostorg.jfrog.io/artifactory/main/release/1.82.0/source/boost_1_82_0.7z" `
-d "${{ runner.temp }}" `
-o "boost.7z"
7z x "${{ runner.temp }}/boost.7z" -o"${{ github.workspace }}/.."
@@ -79,7 +79,7 @@ jobs:
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
version: "6.4.0"
version: "6.5.0"
archives: qtbase qtsvg qttools
- name: Install libtorrent

View File

@@ -26,13 +26,13 @@ jobs:
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
version: "6.4.0"
version: "6.5.0"
archives: icu qtbase qtsvg qttools
- name: Install libtorrent
run: |
git clone \
--branch "v2.0.8" \
--branch "v2.0.9" \
--depth 1 \
--recurse-submodules \
https://github.com/arvidn/libtorrent.git

4
.github/workflows/helper/appimage/export_vars.sh vendored Normal file → Executable file
View File

@@ -1,10 +1,12 @@
#!/bin/sh
# this file is called from AppRun so 'root_dir' will point to where AppRun is
root_dir="$(readlink -f "$(dirname "$0")")"
# Insert the default values because after the test we prepend our path
# and it will create problems with DEs (eg KDE) that don't set the variable
# and rely on the default paths
if [[ -z ${XDG_DATA_DIRS} ]]; then
if [ -z "${XDG_DATA_DIRS}" ]; then
XDG_DATA_DIRS="/usr/local/share/:/usr/share/"
fi

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Mark and close stale PRs
uses: actions/stale@v5
uses: actions/stale@v8
with:
stale-pr-message: "This PR is stale because it has been 60 days with no activity. This PR will be automatically closed within 7 days if there is no further activity."
close-pr-message: "This PR was closed because it has been stalled for some time with no activity."

View File

@@ -3,7 +3,7 @@ repos:
hooks:
- id: check-translation-tag
name: Check newline characters in <translation> tag
entry: .github/workflows/check_translation_tag.py
entry: .github/workflows/helper/pre-commit/check_translation_tag.py
language: script
exclude: |
(?x)^(
@@ -13,7 +13,7 @@ repos:
- ts
- repo: https://github.com/pre-commit/pre-commit-hooks.git
rev: v4.3.0
rev: v4.4.0
hooks:
- id: check-json
name: Check JSON files

View File

@@ -11,8 +11,8 @@ set(minBoostVersion 1.71)
set(minQt5Version 5.15.2)
set(minQt6Version 6.2)
set(minOpenSSLVersion 1.1.1)
set(minLibtorrent1Version 1.2.18)
set(minLibtorrentVersion 2.0.8)
set(minLibtorrent1Version 1.2.19)
set(minLibtorrentVersion 2.0.9)
set(minZlibVersion 1.2.11)
include(CheckCXXSourceCompiles) # TODO: migrate to CheckSourceCompiles in CMake >= 3.19

View File

@@ -200,7 +200,7 @@ Following these guidelines helps maintainers and the community understand your s
[coding-guidelines-url]: https://github.com/qbittorrent/qBittorrent/blob/master/CODING_GUIDELINES.md
[coding-guidelines-git-commit-message-url]: https://github.com/qbittorrent/qBittorrent/blob/master/CODING_GUIDELINES.md#10-git-commit-message
[commit-message-fix-issue-example-url]: https://github.com/qbittorrent/qBittorrent/commit/c07cd440cd46345297debb47cb260f8688975f50
[forum-url]: http://forum.qbittorrent.org/
[forum-url]: https://forum.qbittorrent.org/
[howto-report-bugs-url]: https://www.chiark.greenend.org.uk/~sgtatham/bugs.html
[how-to-translate-url]: https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent
[merging-vs-rebasing-url]: https://www.atlassian.com/git/tutorials/merging-vs-rebasing

View File

@@ -5,7 +5,7 @@ qBittorrent - A BitTorrent client in C++ / Qt
- Boost >= 1.71
- libtorrent-rasterbar 1.2.18 - 1.2.x || 2.0.8 - 2.0.x
- libtorrent-rasterbar 1.2.19 - 1.2.x || 2.0.9 - 2.0.x
* By Arvid Norberg, https://www.libtorrent.org/
* Be careful: another library (the one used by rTorrent) uses a similar name
@@ -18,7 +18,7 @@ qBittorrent - A BitTorrent client in C++ / Qt
- pkg-config *
* Compile-time only on *nix systems
- Python >= 3.5.0
- Python >= 3.7.0
* Optional, run-time only
* Used by the bundled search engine
@@ -43,7 +43,7 @@ Please ensure you are building with an officially supported configuration when r
will install and execute qBittorrent.
DOCUMENTATION:
Please note that there is a "Compilation" section at http://wiki.qbittorrent.org.
Please note that there is a "Compilation" section at https://wiki.qbittorrent.org.
------------------------------------------
sledgehammer999 <sledgehammer999@qbittorrent.org>

View File

@@ -37,13 +37,13 @@ For more information please visit:
https://www.qbittorrent.org
or our wiki here:
http://wiki.qbittorrent.org
https://wiki.qbittorrent.org
Use the forum for troubleshooting before reporting bugs:
http://forum.qbittorrent.org
https://forum.qbittorrent.org
Please report any bug (or feature request) to:
http://bugs.qbittorrent.org
https://bugs.qbittorrent.org
Official IRC channel:
[#qbittorrent on irc.libera.chat](ircs://irc.libera.chat:6697/qbittorrent)

84
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for qbittorrent v4.6.0alpha1.
# Generated by GNU Autoconf 2.71 for qbittorrent v4.6.0beta1.
#
# Report bugs to <bugs.qbittorrent.org>.
#
@@ -611,8 +611,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='qbittorrent'
PACKAGE_TARNAME='qbittorrent'
PACKAGE_VERSION='v4.6.0alpha1'
PACKAGE_STRING='qbittorrent v4.6.0alpha1'
PACKAGE_VERSION='v4.6.0beta1'
PACKAGE_STRING='qbittorrent v4.6.0beta1'
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.6.0alpha1 to adapt to many kinds of systems.
\`configure' configures qbittorrent v4.6.0beta1 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.6.0alpha1:";;
short | recursive ) echo "Configuration of qbittorrent v4.6.0beta1:";;
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.6.0alpha1
qbittorrent configure v4.6.0beta1
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.6.0alpha1, which was
It was created by qbittorrent $as_me v4.6.0beta1, 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.6.0alpha1'
VERSION='v4.6.0beta1'
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -6024,19 +6024,19 @@ LDFLAGS="$BOOST_LDFLAGS $LDFLAGS"
pkg_failed=no
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 2.0.8" >&5
printf %s "checking for libtorrent-rasterbar >= 2.0.8... " >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 2.0.9" >&5
printf %s "checking for libtorrent-rasterbar >= 2.0.9... " >&6; }
if test -n "$libtorrent_CFLAGS"; then
pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 2.0.8\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 2.0.8") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 2.0.9\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 2.0.9") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 2.0.8" 2>/dev/null`
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 2.0.9" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6048,12 +6048,12 @@ if test -n "$libtorrent_LIBS"; then
pkg_cv_libtorrent_LIBS="$libtorrent_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 2.0.8\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 2.0.8") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 2.0.9\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 2.0.9") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 2.0.8" 2>/dev/null`
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 2.0.9" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6074,28 +6074,28 @@ else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 2.0.8" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 2.0.9" 2>&1`
else
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 2.0.8" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 2.0.9" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$libtorrent_PKG_ERRORS" >&5
pkg_failed=no
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" >&5
printf %s "checking for libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2... " >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" >&5
printf %s "checking for libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2... " >&6; }
if test -n "$libtorrent_CFLAGS"; then
pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>/dev/null`
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6107,12 +6107,12 @@ if test -n "$libtorrent_LIBS"; then
pkg_cv_libtorrent_LIBS="$libtorrent_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>/dev/null`
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6133,14 +6133,14 @@ else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>&1`
else
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$libtorrent_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2) were not met:
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2) were not met:
$libtorrent_PKG_ERRORS
@@ -6177,19 +6177,19 @@ elif test $pkg_failed = untried; then
printf "%s\n" "no" >&6; }
pkg_failed=no
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" >&5
printf %s "checking for libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2... " >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" >&5
printf %s "checking for libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2... " >&6; }
if test -n "$libtorrent_CFLAGS"; then
pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>/dev/null`
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6201,12 +6201,12 @@ if test -n "$libtorrent_LIBS"; then
pkg_cv_libtorrent_LIBS="$libtorrent_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>/dev/null`
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6227,14 +6227,14 @@ else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>&1`
else
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$libtorrent_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2) were not met:
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2) were not met:
$libtorrent_PKG_ERRORS
@@ -7237,7 +7237,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.6.0alpha1, which was
This file was extended by qbittorrent $as_me v4.6.0beta1, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -7297,7 +7297,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.6.0alpha1
qbittorrent config.status v4.6.0beta1
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"

View File

@@ -1,4 +1,4 @@
AC_INIT([qbittorrent], [v4.6.0alpha1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_INIT([qbittorrent], [v4.6.0beta1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])
: ${CFLAGS=""}
@@ -188,10 +188,10 @@ m4_define([DETECT_BOOST_VERSION_PROGRAM],
[[(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($1))]));]])])
PKG_CHECK_MODULES(libtorrent,
[libtorrent-rasterbar >= 2.0.8],
[libtorrent-rasterbar >= 2.0.9],
[CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS" LIBS="$libtorrent_LIBS $LIBS" QBT_ADD_DEFINES="$QBT_ADD_DEFINES QBT_USES_LIBTORRENT2"],
[PKG_CHECK_MODULES(libtorrent,
[libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2],
[libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2],
[CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS" LIBS="$libtorrent_LIBS $LIBS"])])
PKG_CHECK_MODULES(openssl,

View File

@@ -68,9 +68,9 @@
<update_contact>sledgehammer999@qbittorrent.org</update_contact>
<developer_name>The qBittorrent Project</developer_name>
<url type="homepage">https://www.qbittorrent.org/</url>
<url type="bugtracker">http://bugs.qbittorrent.org/</url>
<url type="bugtracker">https://bugs.qbittorrent.org/</url>
<url type="donation">https://www.qbittorrent.org/donate</url>
<url type="help">http://forum.qbittorrent.org/</url>
<url type="help">https://forum.qbittorrent.org/</url>
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
<content_rating type="oars-1.1"/>
<releases>

View File

@@ -98,7 +98,7 @@ Name[is]=qBittorrent
Comment[it]=Scarica e condividi file tramite BitTorrent
GenericName[it]=Client BitTorrent
Name[it]=qBittorrent
Comment[ja]=BitTorrentでファイルダウンロードおよび共有
Comment[ja]=BitTorrentでファイルダウンロード共有
GenericName[ja]=BitTorrentクライアント
Name[ja]=qBittorrent
Comment[ka]=გადმოტვირთეთ და გააზიარეთ ფაილები BitTorrent-ის საშუალებით
@@ -160,7 +160,7 @@ Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
Name[te]=qBittorrent
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่าน BitTorrent
GenericName[th]=ไคลเอนต์ BitTorrent
GenericName[th]=โปรแกรมบิททอเร้นท์
Name[th]=qBittorrent
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
GenericName[tr]=BitTorrent istemcisi
@@ -178,7 +178,7 @@ Comment[zh_HK]=經由BitTorrent下載並分享檔案
GenericName[zh_HK]=BitTorrent用戶端
Name[zh_HK]=qBittorrent
Comment[zh_TW]=經由 BitTorrent 下載並分享檔案
GenericName[zh_TW]=BitTorrent 戶端
GenericName[zh_TW]=BitTorrent 戶端
Name[zh_TW]=qBittorrent
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
GenericName[eo]=BitTorrent-kliento
@@ -208,7 +208,7 @@ Name[ltg]=qBittorrent
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
GenericName[hi_IN]=Bittorrent साधन
Name[hi_IN]=qBittorrent
Comment[az@latin]=Faylları BitTorrent vasitəsilə ndərin və paylaşın
Comment[az@latin]=Faylları BitTorrent vasitəsilə endirin və paylaşın
GenericName[az@latin]=BitTorrent client
Name[az@latin]=qBittorrent
Comment[lv_LV]=Lejupielādēt un koplietot failus ar BitTorrent

View File

@@ -42,6 +42,6 @@ number.
8080).
.SH BUGS
.PP
If you find a bug, please report it at http://bugs.qbittorrent.org
If you find a bug, please report it at https://bugs.qbittorrent.org
.SH AUTHORS
Christophe Dumez <chris@qbittorrent.org>.

View File

@@ -38,4 +38,4 @@ the default account user name is "admin" with "adminadmin" as a password.
# BUGS
If you find a bug, please report it at http://bugs.qbittorrent.org
If you find a bug, please report it at https://bugs.qbittorrent.org

View File

@@ -36,6 +36,6 @@ number.
8080).
.SH BUGS
.PP
If you find a bug, please report it at http://bugs.qbittorrent.org
If you find a bug, please report it at https://bugs.qbittorrent.org
.SH AUTHORS
Christophe Dumez <chris@qbittorrent.org>.

View File

@@ -33,4 +33,4 @@ FAST extension (mainline) and PeX support (utorrent compatible).
# BUGS
If you find a bug, please report it at http://bugs.qbittorrent.org
If you find a bug, please report it at https://bugs.qbittorrent.org

View File

@@ -224,8 +224,7 @@ namespace
Application::Application(int &argc, char **argv)
: BaseApplication(argc, argv)
, m_shutdownAct(ShutdownDialogAction::Exit)
, m_commandLineArgs(parseCommandLine(this->arguments()))
, m_commandLineArgs(parseCommandLine(Application::arguments()))
, m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY(u"Enabled"_qs))
, m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY(u"Backup"_qs))
, m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY(u"DeleteOld"_qs))
@@ -563,6 +562,7 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
};
const QString logMsg = tr("Running external program. Torrent: \"%1\". Command: `%2`");
const QString logMsgError = tr("Failed to run external program. Torrent: \"%1\". Command: `%2`");
// The processing sequenece is different for Windows and other OS, this is intentional
#if defined(Q_OS_WIN)
@@ -582,8 +582,6 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
for (int i = 1; i < argCount; ++i)
argList += QString::fromWCharArray(args[i]);
LogMsg(logMsg.arg(torrent->name(), program));
QProcess proc;
proc.setProgram(QString::fromWCharArray(args[0]));
proc.setArguments(argList);
@@ -608,7 +606,11 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
args->startupInfo->hStdOutput = nullptr;
args->startupInfo->hStdError = nullptr;
});
proc.startDetached();
if (proc.startDetached())
LogMsg(logMsg.arg(torrent->name(), program));
else
LogMsg(logMsgError.arg(torrent->name(), program));
#else // Q_OS_WIN
QStringList args = Utils::String::splitCommand(programTemplate);
@@ -624,11 +626,21 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
arg = replaceVariables(arg);
}
// show intended command in log
LogMsg(logMsg.arg(torrent->name(), replaceVariables(programTemplate)));
const QString command = args.takeFirst();
QProcess::startDetached(command, args);
QProcess proc;
proc.setProgram(command);
proc.setArguments(args);
if (proc.startDetached())
{
// show intended command in log
LogMsg(logMsg.arg(torrent->name(), replaceVariables(programTemplate)));
}
else
{
// show intended command in log
LogMsg(logMsgError.arg(torrent->name(), replaceVariables(programTemplate)));
}
#endif
}
@@ -783,7 +795,7 @@ try
actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_qs));
actionExit->setMenuRole(QAction::QuitRole);
actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
connect(actionExit, &QAction::triggered, this, [this]()
connect(actionExit, &QAction::triggered, this, []
{
QApplication::exit();
});
@@ -1110,7 +1122,22 @@ void Application::applyMemoryWorkingSetLimit() const
if (::getrlimit(RLIMIT_RSS, &limit) != 0)
return;
limit.rlim_cur = memoryWorkingSetLimit() * MiB;
const size_t newSize = memoryWorkingSetLimit() * MiB;
if (newSize > limit.rlim_max)
{
// try to raise the hard limit
rlimit newLimit = limit;
newLimit.rlim_max = newSize;
if (::setrlimit(RLIMIT_RSS, &newLimit) != 0)
{
const auto message = QString::fromLocal8Bit(strerror(errno));
LogMsg(tr("Failed to set physical memory (RAM) usage hard limit. Requested size: %1. System hard limit: %2. Error code: %3. Error message: \"%4\"")
.arg(QString::number(newSize), QString::number(limit.rlim_max), QString::number(errno), message), Log::WARNING);
return;
}
}
limit.rlim_cur = newSize;
if (::setrlimit(RLIMIT_RSS, &limit) != 0)
{
const auto message = QString::fromLocal8Bit(strerror(errno));

View File

@@ -173,7 +173,7 @@ private:
ApplicationInstanceManager *m_instanceManager = nullptr;
QAtomicInt m_isCleanupRun;
bool m_isProcessingParamsAllowed = false;
ShutdownDialogAction m_shutdownAct;
ShutdownDialogAction m_shutdownAct = ShutdownDialogAction::Exit;
QBtCommandLineParameters m_commandLineArgs;
// FileLog

View File

@@ -32,6 +32,7 @@
#include <cstdio>
#include <QCoreApplication>
#include <QDebug>
#include <QFileInfo>
#include <QProcessEnvironment>
@@ -152,7 +153,7 @@ namespace
QStringList parts = arg.split(u'=');
if (parts.size() == 2)
return Utils::String::unquote(parts[1], u"'\""_qs);
throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'")
.arg(fullParameter(), u"<value>"_qs));
}
@@ -198,13 +199,15 @@ namespace
int value(const QString &arg) const
{
QString val = StringOption::value(arg);
const QString val = StringOption::value(arg);
bool ok = false;
int res = val.toInt(&ok);
const int res = val.toInt(&ok);
if (!ok)
throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
{
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=<value>'")
.arg(fullParameter(), u"<integer value>"_qs));
}
return res;
}
@@ -217,7 +220,7 @@ namespace
int res = val.toInt(&ok);
if (!ok)
{
qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
qDebug() << QCoreApplication::translate("CMD Options", "Expected integer number in environment variable '%1', but got '%2'")
.arg(envVarName(), val);
return defaultValue;
}
@@ -273,7 +276,7 @@ namespace
}
}
throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--add-paused' must follow syntax "
"'--add-paused=<true|false>'")
.arg(fullParameter(), u"<true|false>"_qs));
@@ -300,7 +303,7 @@ namespace
return false;
}
qDebug() << QObject::tr("Expected %1 in environment variable '%2', but got '%3'")
qDebug() << QCoreApplication::translate("CMD Options", "Expected %1 in environment variable '%2', but got '%3'")
.arg(u"true|false"_qs, envVarName(), val);
return std::nullopt;
}
@@ -386,7 +389,7 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
{
result.webUiPort = WEBUI_PORT_OPTION.value(arg);
if ((result.webUiPort < 1) || (result.webUiPort > 65535))
throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).")
.arg(u"--webui-port"_qs));
}
else if (arg == TORRENTING_PORT_OPTION)
@@ -394,7 +397,7 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
result.torrentingPort = TORRENTING_PORT_OPTION.value(arg);
if ((result.torrentingPort < 1) || (result.torrentingPort > 65535))
{
throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).")
.arg(u"--torrenting-port"_qs));
}
}
@@ -497,58 +500,58 @@ QString makeUsage(const QString &prgName)
{
const QString indentation {USAGE_INDENTATION, u' '};
const QString text = QObject::tr("Usage:") + u'\n'
+ indentation + prgName + u' ' + QObject::tr("[options] [(<filename> | <url>)...]") + u'\n'
const QString text = QCoreApplication::translate("CMD Options", "Usage:") + u'\n'
+ indentation + prgName + u' ' + QCoreApplication::translate("CMD Options", "[options] [(<filename> | <url>)...]") + u'\n'
+ QObject::tr("Options:") + u'\n'
+ QCoreApplication::translate("CMD Options", "Options:") + u'\n'
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
+ SHOW_VERSION_OPTION.usage() + wrapText(QObject::tr("Display program version and exit")) + u'\n'
+ SHOW_VERSION_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display program version and exit")) + u'\n'
#endif
+ SHOW_HELP_OPTION.usage() + wrapText(QObject::tr("Display this help message and exit")) + u'\n'
+ WEBUI_PORT_OPTION.usage(QObject::tr("port"))
+ wrapText(QObject::tr("Change the Web UI port"))
+ SHOW_HELP_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display this help message and exit")) + u'\n'
+ WEBUI_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
+ wrapText(QCoreApplication::translate("CMD Options", "Change the Web UI port"))
+ u'\n'
+ TORRENTING_PORT_OPTION.usage(QObject::tr("port"))
+ wrapText(QObject::tr("Change the torrenting port"))
+ TORRENTING_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
+ wrapText(QCoreApplication::translate("CMD Options", "Change the torrenting port"))
+ u'\n'
#ifndef DISABLE_GUI
+ NO_SPLASH_OPTION.usage() + wrapText(QObject::tr("Disable splash screen")) + u'\n'
+ NO_SPLASH_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Disable splash screen")) + u'\n'
#elif !defined(Q_OS_WIN)
+ DAEMON_OPTION.usage() + wrapText(QObject::tr("Run in daemon-mode (background)")) + u'\n'
+ DAEMON_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Run in daemon-mode (background)")) + u'\n'
#endif
//: Use appropriate short form or abbreviation of "directory"
+ PROFILE_OPTION.usage(QObject::tr("dir"))
+ wrapText(QObject::tr("Store configuration files in <dir>")) + u'\n'
+ CONFIGURATION_OPTION.usage(QObject::tr("name"))
+ wrapText(QObject::tr("Store configuration files in directories qBittorrent_<name>")) + u'\n'
+ PROFILE_OPTION.usage(QCoreApplication::translate("CMD Options", "dir"))
+ wrapText(QCoreApplication::translate("CMD Options", "Store configuration files in <dir>")) + u'\n'
+ CONFIGURATION_OPTION.usage(QCoreApplication::translate("CMD Options", "name"))
+ wrapText(QCoreApplication::translate("CMD Options", "Store configuration files in directories qBittorrent_<name>")) + u'\n'
+ RELATIVE_FASTRESUME.usage()
+ wrapText(QObject::tr("Hack into libtorrent fastresume files and make file paths relative "
+ wrapText(QCoreApplication::translate("CMD Options", "Hack into libtorrent fastresume files and make file paths relative "
"to the profile directory")) + u'\n'
+ Option::padUsageText(QObject::tr("files or URLs"))
+ wrapText(QObject::tr("Download the torrents passed by the user")) + u'\n'
+ Option::padUsageText(QCoreApplication::translate("CMD Options", "files or URLs"))
+ wrapText(QCoreApplication::translate("CMD Options", "Download the torrents passed by the user")) + u'\n'
+ u'\n'
+ wrapText(QObject::tr("Options when adding new torrents:"), 0) + u'\n'
+ SAVE_PATH_OPTION.usage(QObject::tr("path")) + wrapText(QObject::tr("Torrent save path")) + u'\n'
+ PAUSED_OPTION.usage() + wrapText(QObject::tr("Add torrents as started or paused")) + u'\n'
+ SKIP_HASH_CHECK_OPTION.usage() + wrapText(QObject::tr("Skip hash check")) + u'\n'
+ CATEGORY_OPTION.usage(QObject::tr("name"))
+ wrapText(QObject::tr("Assign torrents to category. If the category doesn't exist, it will be "
+ wrapText(QCoreApplication::translate("CMD Options", "Options when adding new torrents:"), 0) + u'\n'
+ SAVE_PATH_OPTION.usage(QCoreApplication::translate("CMD Options", "path")) + wrapText(QCoreApplication::translate("CMD Options", "Torrent save path")) + u'\n'
+ PAUSED_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Add torrents as started or paused")) + u'\n'
+ SKIP_HASH_CHECK_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Skip hash check")) + u'\n'
+ CATEGORY_OPTION.usage(QCoreApplication::translate("CMD Options", "name"))
+ wrapText(QCoreApplication::translate("CMD Options", "Assign torrents to category. If the category doesn't exist, it will be "
"created.")) + u'\n'
+ SEQUENTIAL_OPTION.usage() + wrapText(QObject::tr("Download files in sequential order")) + u'\n'
+ SEQUENTIAL_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Download files in sequential order")) + u'\n'
+ FIRST_AND_LAST_OPTION.usage()
+ wrapText(QObject::tr("Download first and last pieces first")) + u'\n'
+ wrapText(QCoreApplication::translate("CMD Options", "Download first and last pieces first")) + u'\n'
+ SKIP_DIALOG_OPTION.usage()
+ wrapText(QObject::tr("Specify whether the \"Add New Torrent\" dialog opens when adding a "
+ wrapText(QCoreApplication::translate("CMD Options", "Specify whether the \"Add New Torrent\" dialog opens when adding a "
"torrent.")) + u'\n'
+ u'\n'
+ wrapText(QObject::tr("Option values may be supplied via environment variables. For option named "
+ wrapText(QCoreApplication::translate("CMD Options", "Option values may be supplied via environment variables. For option named "
"'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper "
"case, '-' replaced with '_'). To pass flag values, set the variable to '1' or "
"'TRUE'. For example, to disable the splash screen: "), 0) + u'\n'
+ u"QBT_NO_SPLASH=1 " + prgName + u'\n'
+ wrapText(QObject::tr("Command line parameters take precedence over environment variables"), 0) + u'\n';
+ wrapText(QCoreApplication::translate("CMD Options", "Command line parameters take precedence over environment variables"), 0) + u'\n';
return text;
}
@@ -556,7 +559,7 @@ QString makeUsage(const QString &prgName)
void displayUsage(const QString &prgName)
{
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
QMessageBox msgBox(QMessageBox::Information, QObject::tr("Help"), makeUsage(prgName), QMessageBox::Ok);
QMessageBox msgBox(QMessageBox::Information, QCoreApplication::translate("CMD Options", "Help"), makeUsage(prgName), QMessageBox::Ok);
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec();

View File

@@ -45,6 +45,7 @@
#include <io.h>
#endif
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
@@ -132,7 +133,7 @@ int main(int argc, char *argv[])
const QBtCommandLineParameters params = app->commandLineArgs();
if (!params.unknownParameter.isEmpty())
{
throw CommandLineParameterError(QObject::tr("%1 is an unknown command line parameter.",
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 is an unknown command line parameter.",
"--random-parameter is an unknown command line parameter.")
.arg(params.unknownParameter));
}
@@ -144,7 +145,7 @@ int main(int argc, char *argv[])
displayVersion();
return EXIT_SUCCESS;
}
throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 must be the single command line parameter.")
.arg(u"-v (or --version)"_qs));
}
#endif
@@ -155,7 +156,7 @@ int main(int argc, char *argv[])
displayUsage(QString::fromLocal8Bit(argv[0]));
return EXIT_SUCCESS;
}
throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 must be the single command line parameter.")
.arg(u"-h (or --help)"_qs));
}
@@ -187,7 +188,7 @@ int main(int argc, char *argv[])
#if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
if (params.shouldDaemonize)
{
throw CommandLineParameterError(QObject::tr("You cannot use %1: qBittorrent is already running for this user.")
throw CommandLineParameterError(QCoreApplication::translate("Main", "You cannot use %1: qBittorrent is already running for this user.")
.arg(u"-d (or --daemon)"_qs));
}
#endif
@@ -295,15 +296,15 @@ void displayVersion()
void displayBadArgMessage(const QString &message)
{
const QString help = QObject::tr("Run application with -h option to read about command line parameters.");
const QString help = QCoreApplication::translate("Main", "Run application with -h option to read about command line parameters.");
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
QMessageBox msgBox(QMessageBox::Critical, QObject::tr("Bad command line"),
QMessageBox msgBox(QMessageBox::Critical, QCoreApplication::translate("Main", "Bad command line"),
(message + u'\n' + help), QMessageBox::Ok);
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec();
#else
const QString errMsg = QObject::tr("Bad command line: ") + u'\n'
const QString errMsg = QCoreApplication::translate("Main", "Bad command line: ") + u'\n'
+ message + u'\n'
+ help + u'\n';
fprintf(stderr, "%s", qUtf8Printable(errMsg));
@@ -316,10 +317,10 @@ bool userAgreesWithLegalNotice()
Q_ASSERT(!pref->getAcceptedLegal());
#ifdef DISABLE_GUI
const QString eula = u"\n*** %1 ***\n"_qs.arg(QObject::tr("Legal Notice"))
+ QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.") + u"\n\n"
+ QObject::tr("No further notices will be issued.") + u"\n\n"
+ QObject::tr("Press %1 key to accept and continue...").arg(u"'y'"_qs) + u'\n';
const QString eula = u"\n*** %1 ***\n"_qs.arg(QCoreApplication::translate("Main", "Legal Notice"))
+ QCoreApplication::translate("Main", "qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.") + u"\n\n"
+ QCoreApplication::translate("Main", "No further notices will be issued.") + u"\n\n"
+ QCoreApplication::translate("Main", "Press %1 key to accept and continue...").arg(u"'y'"_qs) + u'\n';
printf("%s", qUtf8Printable(eula));
const char ret = getchar(); // Read pressed key
@@ -331,10 +332,10 @@ bool userAgreesWithLegalNotice()
}
#else
QMessageBox msgBox;
msgBox.setText(QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.\n\nNo further notices will be issued."));
msgBox.setWindowTitle(QObject::tr("Legal notice"));
msgBox.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
const QAbstractButton *agreeButton = msgBox.addButton(QObject::tr("I Agree"), QMessageBox::AcceptRole);
msgBox.setText(QCoreApplication::translate("Main", "qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.\n\nNo further notices will be issued."));
msgBox.setWindowTitle(QCoreApplication::translate("Main", "Legal notice"));
msgBox.addButton(QCoreApplication::translate("Main", "Cancel"), QMessageBox::RejectRole);
const QAbstractButton *agreeButton = msgBox.addButton(QCoreApplication::translate("Main", "I Agree"), QMessageBox::AcceptRole);
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec();

View File

@@ -152,10 +152,10 @@ bool QtLocalPeer::sendMessage(const QString &message, const int timeout)
break;
int ms = 250;
#if defined(Q_OS_WIN)
Sleep(DWORD(ms));
::Sleep(DWORD(ms));
#else
struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
nanosleep(&ts, NULL);
::nanosleep(&ts, nullptr);
#endif
}
if (!connOk)

View File

@@ -96,7 +96,7 @@ namespace
void abnormalExitHandler(const int signum)
{
const char msg[] = "\n\n*************************************************************\n"
"Please file a bug report at http://bug.qbittorrent.org and provide the following information:\n\n"
"Please file a bug report at https://bug.qbittorrent.org and provide the following information:\n\n"
"qBittorrent version: " QBT_VERSION "\n\n"
"Caught signal: ";
const char *sigName = sysSigName[signum];

View File

@@ -29,6 +29,7 @@
#include "upgrade.h"
#include <QtGlobal>
#include <QCoreApplication>
#include <QMetaEnum>
#include "base/bittorrent/torrentcontentlayout.h"
@@ -54,7 +55,7 @@ namespace
SettingsStorage *settingsStorage {SettingsStorage::instance()};
const auto oldData {settingsStorage->loadValue<QByteArray>(oldKey)};
const auto newData {settingsStorage->loadValue<QString>(newKey)};
const QString errorMsgFormat {QObject::tr("Migrate preferences failed: WebUI https, file: \"%1\", error: \"%2\"")};
const QString errorMsgFormat {QCoreApplication::translate("Upgrade", "Migrate preferences failed: WebUI https, file: \"%1\", error: \"%2\"")};
if (!newData.isEmpty() || oldData.isEmpty())
return;
@@ -69,7 +70,7 @@ namespace
settingsStorage->storeValue(newKey, savePath);
settingsStorage->removeValue(oldKey);
LogMsg(QObject::tr("Migrated preferences: WebUI https, exported data to file: \"%1\"").arg(savePath.toString())
LogMsg(QCoreApplication::translate("Upgrade", "Migrated preferences: WebUI https, exported data to file: \"%1\"").arg(savePath.toString())
, Log::INFO);
};
@@ -161,7 +162,7 @@ namespace
settingsStorage->storeValue(key, Scheduler::Days::Sunday);
break;
default:
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
LogMsg(QCoreApplication::translate("Upgrade", "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;
@@ -192,7 +193,7 @@ namespace
settingsStorage->storeValue(key, DNS::Service::NoIP);
break;
default:
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
LogMsg(QCoreApplication::translate("Upgrade", "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;
@@ -223,7 +224,7 @@ namespace
settingsStorage->storeValue(key, TrayIcon::Style::MonoLight);
break;
default:
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
LogMsg(QCoreApplication::translate("Upgrade", "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;
@@ -361,7 +362,7 @@ namespace
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\".")
LogMsg(QCoreApplication::translate("Upgrade", "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;

View File

@@ -14,7 +14,7 @@
#define expected_lite_MAJOR 0
#define expected_lite_MINOR 6
#define expected_lite_PATCH 2
#define expected_lite_PATCH 3
#define expected_lite_VERSION expected_STRINGIFY(expected_lite_MAJOR) "." expected_STRINGIFY(expected_lite_MINOR) "." expected_STRINGIFY(expected_lite_PATCH)
@@ -405,7 +405,7 @@ struct is_nothrow_swappable
};
} // namespace detail
// is [nothow] swappable:
// is [nothrow] swappable:
template< typename T >
struct is_swappable : decltype( detail::is_swappable::test<T>(0) ){};
@@ -1002,11 +1002,12 @@ public:
// x.x.5.2.4 Swap
template< typename U=E >
nsel_REQUIRES_R( void,
std17::is_swappable<E>::value
std17::is_swappable<U>::value
)
swap( unexpected_type & other ) noexcept (
std17::is_nothrow_swappable<E>::value
std17::is_nothrow_swappable<U>::value
)
{
using std::swap;
@@ -2164,10 +2165,24 @@ private:
// x.x.4.6 expected<>: comparison operators
template< typename T1, typename E1, typename T2, typename E2 >
template< typename T1, typename E1, typename T2, typename E2
nsel_REQUIRES_T(
!std::is_void<T1>::value && !std::is_void<T2>::value
)
>
constexpr bool operator==( expected<T1,E1> const & x, expected<T2,E2> const & y )
{
return bool(x) != bool(y) ? false : bool(x) == false ? x.error() == y.error() : *x == *y;
return bool(x) != bool(y) ? false : bool(x) ? *x == *y : x.error() == y.error();
}
template< typename T1, typename E1, typename T2, typename E2
nsel_REQUIRES_T(
std::is_void<T1>::value && std::is_void<T2>::value
)
>
constexpr bool operator==( expected<T1,E1> const & x, expected<T2,E2> const & y )
{
return bool(x) != bool(y) ? false : bool(x) || static_cast<bool>( x.error() == y.error() );
}
template< typename T1, typename E1, typename T2, typename E2 >
@@ -2176,12 +2191,6 @@ constexpr bool operator!=( expected<T1,E1> const & x, expected<T2,E2> const & y
return !(x == y);
}
template< typename E1, typename E2 >
constexpr bool operator==( expected<void,E1> const & x, expected<void,E1> const & y )
{
return bool(x) != bool(y) ? false : bool(x) == false ? x.error() == y.error() : true;
}
#if nsel_P0323R <= 2
template< typename T, typename E >
@@ -2212,13 +2221,21 @@ constexpr bool operator>=( expected<T,E> const & x, expected<T,E> const & y )
// x.x.4.7 expected: comparison with T
template< typename T1, typename E1, typename T2 >
template< typename T1, typename E1, typename T2
nsel_REQUIRES_T(
!std::is_void<T1>::value
)
>
constexpr bool operator==( expected<T1,E1> const & x, T2 const & v )
{
return bool(x) ? *x == v : false;
}
template< typename T1, typename E1, typename T2 >
template< typename T1, typename E1, typename T2
nsel_REQUIRES_T(
!std::is_void<T1>::value
)
>
constexpr bool operator==(T2 const & v, expected<T1,E1> const & x )
{
return bool(x) ? v == *x : false;

View File

@@ -110,6 +110,7 @@ add_library(qbt_base STATIC
applicationcomponent.cpp
asyncfilestorage.cpp
bittorrent/abstractfilestorage.cpp
bittorrent/addtorrentparams.cpp
bittorrent/bandwidthscheduler.cpp
bittorrent/bencoderesumedatastorage.cpp
bittorrent/categoryoptions.cpp

View File

@@ -110,6 +110,7 @@ SOURCES += \
$$PWD/applicationcomponent.cpp \
$$PWD/asyncfilestorage.cpp \
$$PWD/bittorrent/abstractfilestorage.cpp \
$$PWD/bittorrent/addtorrentparams.cpp \
$$PWD/bittorrent/bandwidthscheduler.cpp \
$$PWD/bittorrent/bencoderesumedatastorage.cpp \
$$PWD/bittorrent/categoryoptions.cpp \

View File

@@ -0,0 +1,170 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2023 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 "addtorrentparams.h"
#include <tuple>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include "base/utils/string.h"
const QString PARAM_CATEGORY = u"category"_qs;
const QString PARAM_TAGS = u"tags"_qs;
const QString PARAM_SAVEPATH = u"save_path"_qs;
const QString PARAM_USEDOWNLOADPATH = u"use_download_path"_qs;
const QString PARAM_DOWNLOADPATH = u"download_path"_qs;
const QString PARAM_OPERATINGMODE = u"operating_mode"_qs;
const QString PARAM_QUEUETOP = u"add_to_top_of_queue"_qs;
const QString PARAM_STOPPED = u"stopped"_qs;
const QString PARAM_SKIPCHECKING = u"skip_checking"_qs;
const QString PARAM_CONTENTLAYOUT = u"content_layout"_qs;
const QString PARAM_AUTOTMM = u"use_auto_tmm"_qs;
const QString PARAM_UPLOADLIMIT = u"upload_limit"_qs;
const QString PARAM_DOWNLOADLIMIT = u"download_limit"_qs;
const QString PARAM_SEEDINGTIMELIMIT = u"seeding_time_limit"_qs;
const QString PARAM_RATIOLIMIT = u"ratio_limit"_qs;
namespace
{
TagSet parseTagSet(const QJsonArray &jsonArr)
{
TagSet tags;
for (const QJsonValue &jsonVal : jsonArr)
tags.insert(jsonVal.toString());
return tags;
}
QJsonArray serializeTagSet(const TagSet &tags)
{
QJsonArray arr;
for (const QString &tag : tags)
arr.append(tag);
return arr;
}
std::optional<bool> getOptionalBool(const QJsonObject &jsonObj, const QString &key)
{
const QJsonValue jsonVal = jsonObj.value(key);
if (jsonVal.isUndefined() || jsonVal.isNull())
return std::nullopt;
return jsonVal.toBool();
}
template <typename Enum>
std::optional<Enum> getOptionalEnum(const QJsonObject &jsonObj, const QString &key)
{
const QJsonValue jsonVal = jsonObj.value(key);
if (jsonVal.isUndefined() || jsonVal.isNull())
return std::nullopt;
return Utils::String::toEnum<Enum>(jsonVal.toString(), {});
}
template <typename Enum>
Enum getEnum(const QJsonObject &jsonObj, const QString &key)
{
const QJsonValue jsonVal = jsonObj.value(key);
return Utils::String::toEnum<Enum>(jsonVal.toString(), {});
}
}
bool BitTorrent::operator==(const AddTorrentParams &lhs, const AddTorrentParams &rhs)
{
return std::tie(lhs.name, lhs.category, lhs.tags,
lhs.savePath, lhs.useDownloadPath, lhs.downloadPath,
lhs.sequential, lhs.firstLastPiecePriority, lhs.addForced,
lhs.addToQueueTop, lhs.addPaused, lhs.stopCondition,
lhs.filePaths, lhs.filePriorities, lhs.skipChecking,
lhs.contentLayout, lhs.useAutoTMM, lhs.uploadLimit,
lhs.downloadLimit, lhs.seedingTimeLimit, lhs.ratioLimit)
== std::tie(rhs.name, rhs.category, rhs.tags,
rhs.savePath, rhs.useDownloadPath, rhs.downloadPath,
rhs.sequential, rhs.firstLastPiecePriority, rhs.addForced,
rhs.addToQueueTop, rhs.addPaused, rhs.stopCondition,
rhs.filePaths, rhs.filePriorities, rhs.skipChecking,
rhs.contentLayout, rhs.useAutoTMM, rhs.uploadLimit,
rhs.downloadLimit, rhs.seedingTimeLimit, rhs.ratioLimit);
}
BitTorrent::AddTorrentParams BitTorrent::parseAddTorrentParams(const QJsonObject &jsonObj)
{
AddTorrentParams params;
params.category = jsonObj.value(PARAM_CATEGORY).toString();
params.tags = parseTagSet(jsonObj.value(PARAM_TAGS).toArray());
params.savePath = Path(jsonObj.value(PARAM_SAVEPATH).toString());
params.useDownloadPath = getOptionalBool(jsonObj, PARAM_USEDOWNLOADPATH);
params.downloadPath = Path(jsonObj.value(PARAM_DOWNLOADPATH).toString());
params.addForced = (getEnum<BitTorrent::TorrentOperatingMode>(jsonObj, PARAM_OPERATINGMODE) == BitTorrent::TorrentOperatingMode::Forced);
params.addToQueueTop = getOptionalBool(jsonObj, PARAM_QUEUETOP);
params.addPaused = getOptionalBool(jsonObj, PARAM_STOPPED);
params.skipChecking = jsonObj.value(PARAM_SKIPCHECKING).toBool();
params.contentLayout = getOptionalEnum<BitTorrent::TorrentContentLayout>(jsonObj, PARAM_CONTENTLAYOUT);
params.useAutoTMM = getOptionalBool(jsonObj, PARAM_AUTOTMM);
params.uploadLimit = jsonObj.value(PARAM_UPLOADLIMIT).toInt(-1);
params.downloadLimit = jsonObj.value(PARAM_DOWNLOADLIMIT).toInt(-1);
params.seedingTimeLimit = jsonObj.value(PARAM_SEEDINGTIMELIMIT).toInt(BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME);
params.ratioLimit = jsonObj.value(PARAM_RATIOLIMIT).toDouble(BitTorrent::Torrent::USE_GLOBAL_RATIO);
return params;
}
QJsonObject BitTorrent::serializeAddTorrentParams(const AddTorrentParams &params)
{
QJsonObject jsonObj {
{PARAM_CATEGORY, params.category},
{PARAM_TAGS, serializeTagSet(params.tags)},
{PARAM_SAVEPATH, params.savePath.data()},
{PARAM_DOWNLOADPATH, params.downloadPath.data()},
{PARAM_OPERATINGMODE, Utils::String::fromEnum(params.addForced
? BitTorrent::TorrentOperatingMode::Forced : BitTorrent::TorrentOperatingMode::AutoManaged)},
{PARAM_SKIPCHECKING, params.skipChecking},
{PARAM_UPLOADLIMIT, params.uploadLimit},
{PARAM_DOWNLOADLIMIT, params.downloadLimit},
{PARAM_SEEDINGTIMELIMIT, params.seedingTimeLimit},
{PARAM_RATIOLIMIT, params.ratioLimit}
};
if (params.addToQueueTop)
jsonObj[PARAM_QUEUETOP] = *params.addToQueueTop;
if (params.addPaused)
jsonObj[PARAM_STOPPED] = *params.addPaused;
if (params.contentLayout)
jsonObj[PARAM_CONTENTLAYOUT] = Utils::String::fromEnum(*params.contentLayout);
if (params.useAutoTMM)
jsonObj[PARAM_AUTOTMM] = *params.useAutoTMM;
if (params.useDownloadPath)
jsonObj[PARAM_USEDOWNLOADPATH] = *params.useDownloadPath;
return jsonObj;
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -39,6 +39,8 @@
#include "torrent.h"
#include "torrentcontentlayout.h"
class QJsonObject;
namespace BitTorrent
{
enum class DownloadPriority;
@@ -67,6 +69,11 @@ namespace BitTorrent
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
};
bool operator==(const AddTorrentParams &lhs, const AddTorrentParams &rhs);
AddTorrentParams parseAddTorrentParams(const QJsonObject &jsonObj);
QJsonObject serializeAddTorrentParams(const AddTorrentParams &params);
}
Q_DECLARE_METATYPE(BitTorrent::AddTorrentParams)

View File

@@ -41,7 +41,6 @@ using namespace std::chrono_literals;
BandwidthScheduler::BandwidthScheduler(QObject *parent)
: QObject(parent)
, m_lastAlternative(false)
{
connect(&m_timer, &QTimer::timeout, this, &BandwidthScheduler::onTimeout);
}

View File

@@ -49,5 +49,5 @@ private:
void onTimeout();
QTimer m_timer;
bool m_lastAlternative;
bool m_lastAlternative = false;
};

View File

@@ -36,6 +36,7 @@
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QRegularExpression>
#include <QThread>
@@ -133,17 +134,19 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::load(cons
const Path fastresumePath = path() / Path(idString + u".fastresume");
const Path torrentFilePath = path() / Path(idString + u".torrent");
QFile resumeDataFile {fastresumePath.data()};
if (!resumeDataFile.open(QIODevice::ReadOnly))
return nonstd::make_unexpected(tr("Cannot read file %1: %2").arg(fastresumePath.toString(), resumeDataFile.errorString()));
const auto resumeDataReadResult = Utils::IO::readFile(fastresumePath, MAX_TORRENT_SIZE);
if (!resumeDataReadResult)
return nonstd::make_unexpected(resumeDataReadResult.error().message);
QFile metadataFile {torrentFilePath.data()};
if (metadataFile.exists() && !metadataFile.open(QIODevice::ReadOnly))
return nonstd::make_unexpected(tr("Cannot read file %1: %2").arg(torrentFilePath.toString(), metadataFile.errorString()));
const QByteArray data = resumeDataFile.readAll();
const QByteArray metadata = (metadataFile.isOpen() ? metadataFile.readAll() : "");
const auto metadataReadResult = Utils::IO::readFile(torrentFilePath, MAX_TORRENT_SIZE);
if (!metadataReadResult)
{
if (metadataReadResult.error().status != Utils::IO::ReadError::NotExist)
return nonstd::make_unexpected(metadataReadResult.error().message);
}
const QByteArray data = resumeDataReadResult.value();
const QByteArray metadata = metadataReadResult.value_or(QByteArray());
return loadTorrentResumeData(data, metadata);
}
@@ -161,6 +164,8 @@ void BitTorrent::BencodeResumeDataStorage::doLoadAll() const
void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
{
const int lineMaxLength = 48;
QFile queueFile {queueFilename.data()};
if (!queueFile.exists())
return;
@@ -175,7 +180,7 @@ void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
int start = 0;
while (true)
{
const auto line = QString::fromLatin1(queueFile.readLine().trimmed());
const auto line = QString::fromLatin1(queueFile.readLine(lineMaxLength).trimmed());
if (line.isEmpty())
break;

View File

@@ -120,7 +120,11 @@ void CustomDiskIOThread::async_move_storage(lt::storage_index_t storage, std::st
m_nativeDiskIO->async_move_storage(storage, path, flags
, [=, handler = std::move(handler)](lt::status_t status, const std::string &path, const lt::storage_error &error)
{
#if LIBTORRENT_VERSION_NUM < 20100
if ((status != lt::status_t::fatal_disk_error) && (status != lt::status_t::file_exist))
#else
if ((status != lt::disk_status::fatal_disk_error) && (status != lt::disk_status::file_exist))
#endif
m_storageData[storage].savePath = newSavePath;
handler(status, path, error);
@@ -167,7 +171,7 @@ void CustomDiskIOThread::async_set_file_priority(lt::storage_index_t storage, lt
, std::function<void (const lt::storage_error &, lt::aux::vector<lt::download_priority_t, lt::file_index_t>)> handler)
{
m_nativeDiskIO->async_set_file_priority(storage, priorities
, [=, handler = std::move(handler)](const lt::storage_error &error, lt::aux::vector<lt::download_priority_t, lt::file_index_t> priorities)
, [=, handler = std::move(handler)](const lt::storage_error &error, const lt::aux::vector<lt::download_priority_t, lt::file_index_t> &priorities)
{
m_storageData[storage].filePriorities = priorities;
handler(error, priorities);

View File

@@ -41,7 +41,6 @@
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QMutex>
#include <QSet>
#include <QSqlDatabase>
@@ -703,8 +702,8 @@ void BitTorrent::DBResumeDataStorage::Worker::run()
void DBResumeDataStorage::Worker::requestInterruption()
{
m_waitCondition.wakeAll();
QThread::requestInterruption();
m_waitCondition.wakeAll();
}
void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData)
@@ -773,6 +772,7 @@ namespace
DB_COLUMN_CATEGORY,
DB_COLUMN_TAGS,
DB_COLUMN_TARGET_SAVE_PATH,
DB_COLUMN_DOWNLOAD_PATH,
DB_COLUMN_CONTENT_LAYOUT,
DB_COLUMN_RATIO_LIMIT,
DB_COLUMN_SEEDING_TIME_LIMIT,
@@ -830,7 +830,7 @@ namespace
query.bindValue(DB_COLUMN_NAME.placeholder, m_resumeData.name);
query.bindValue(DB_COLUMN_CATEGORY.placeholder, m_resumeData.category);
query.bindValue(DB_COLUMN_TAGS.placeholder, (m_resumeData.tags.isEmpty()
? QVariant(QVariant::String) : m_resumeData.tags.join(u","_qs)));
? QVariant(QVariant::String) : m_resumeData.tags.join(u","_qs)));
query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(m_resumeData.contentLayout));
query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast<int>(m_resumeData.ratioLimit * 1000));
query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, m_resumeData.seedingTimeLimit);

View File

@@ -89,7 +89,7 @@ namespace
}
private:
lt::address_v4::bytes_type m_buf;
lt::address_v4::bytes_type m_buf {};
};
bool parseIPAddress(const char *data, lt::address &address)
@@ -111,7 +111,6 @@ namespace
FilterParserThread::FilterParserThread(QObject *parent)
: QThread(parent)
, m_abort(false)
{
}
@@ -484,9 +483,9 @@ int FilterParserThread::parseP2BFilterFile()
char buf[7];
unsigned char version;
if (!stream.readRawData(buf, sizeof(buf))
|| memcmp(buf, "\xFF\xFF\xFF\xFFP2B", 7)
|| (memcmp(buf, "\xFF\xFF\xFF\xFFP2B", 7) != 0)
|| !stream.readRawData(reinterpret_cast<char*>(&version), sizeof(version)))
{
{
LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
return ruleCount;
}

View File

@@ -55,14 +55,14 @@ protected:
void run() override;
private:
int findAndNullDelimiter(char *const data, char delimiter, int start, int end, bool reverse = false);
int trim(char *const data, int start, int end);
int findAndNullDelimiter(char *data, char delimiter, int start, int end, bool reverse = false);
int trim(char *data, int start, int end);
int parseDATFilterFile();
int parseP2PFilterFile();
int getlineInStream(QDataStream &stream, std::string &name, char delim);
int parseP2BFilterFile();
bool m_abort;
bool m_abort = false;
Path m_filePath;
lt::ip_filter m_filter;
};

View File

@@ -93,7 +93,7 @@ BitTorrent::InfoHash::operator WrappedType() const
BitTorrent::TorrentID BitTorrent::TorrentID::fromString(const QString &hashString)
{
return TorrentID(BaseType::fromString(hashString));
return {BaseType::fromString(hashString)};
}
BitTorrent::TorrentID BitTorrent::TorrentID::fromInfoHash(const BitTorrent::InfoHash &infoHash)
@@ -103,7 +103,7 @@ BitTorrent::TorrentID BitTorrent::TorrentID::fromInfoHash(const BitTorrent::Info
BitTorrent::TorrentID BitTorrent::TorrentID::fromSHA1Hash(const SHA1Hash &hash)
{
return TorrentID(hash);
return {hash};
}
BitTorrent::TorrentID BitTorrent::TorrentID::fromSHA256Hash(const SHA256Hash &hash)

View File

@@ -54,7 +54,7 @@ namespace BitTorrent
bool firstLastPiecePriority = false;
bool hasFinishedStatus = false;
bool stopped = false;
Torrent::StopCondition stopCondition;
Torrent::StopCondition stopCondition = Torrent::StopCondition::None;
bool addToQueueTop = false; // only for new torrents

View File

@@ -75,8 +75,7 @@ using namespace BitTorrent;
const int magnetUriId = qRegisterMetaType<MagnetUri>();
MagnetUri::MagnetUri(const QString &source)
: m_valid(false)
, m_url(source)
: m_url(source)
{
if (source.isEmpty()) return;

View File

@@ -54,7 +54,7 @@ namespace BitTorrent
lt::add_torrent_params addTorrentParams() const;
private:
bool m_valid;
bool m_valid = false;
QString m_url;
InfoHash m_infoHash;
QString m_name;

View File

@@ -35,27 +35,6 @@
namespace
{
void handleAddTorrentAlert([[maybe_unused]] const lt::add_torrent_alert *alert)
{
#ifndef QBT_USES_LIBTORRENT2
if (alert->error)
return;
// libtorrent < 2.0.7 has a bug that add_torrent_alert is posted too early
// (before torrent is fully initialized and torrent extensions are created)
// so we have to fill "extension data" in add_torrent_alert handler
// NOTE: `data` may not exist if a torrent is added behind the scenes to download metadata
auto *data = static_cast<ExtensionData *>(alert->params.userdata);
if (data)
{
data->status = alert->handle.status({});
data->trackers = alert->handle.trackers();
data->urlSeeds = alert->handle.url_seeds();
}
#endif
}
void handleFastresumeRejectedAlert(const lt::fastresume_rejected_alert *alert)
{
alert->handle.unset_flags(lt::torrent_flags::auto_managed);
@@ -91,9 +70,6 @@ void NativeSessionExtension::on_alert(const lt::alert *alert)
case lt::session_stats_alert::alert_type:
handleSessionStatsAlert(static_cast<const lt::session_stats_alert *>(alert));
break;
case lt::add_torrent_alert::alert_type:
handleAddTorrentAlert(static_cast<const lt::add_torrent_alert *>(alert));
break;
case lt::fastresume_rejected_alert::alert_type:
handleFastresumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert *>(alert));
break;

View File

@@ -36,19 +36,12 @@ NativeTorrentExtension::NativeTorrentExtension(const lt::torrent_handle &torrent
{
// NOTE: `data` may not exist if a torrent is added behind the scenes to download metadata
#ifdef QBT_USES_LIBTORRENT2
// libtorrent < 2.0.7 has a bug that add_torrent_alert is posted too early
// (before torrent is fully initialized and torrent extensions are created)
// so we have to fill "extension data" in add_torrent_alert handler and
// we have it already filled at this point
if (m_data)
{
m_data->status = m_torrentHandle.status({});
m_data->status = m_torrentHandle.status();
m_data->trackers = m_torrentHandle.trackers();
m_data->urlSeeds = m_torrentHandle.url_seeds();
}
#endif
on_state(m_data ? m_data->status.state : m_torrentHandle.status({}).state);
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 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
@@ -33,6 +33,7 @@
#include "base/bittorrent/ltqbitarray.h"
#include "base/net/geoipmanager.h"
#include "base/unicodestrings.h"
#include "base/utils/bytearray.h"
#include "peeraddress.h"
using namespace BitTorrent;
@@ -168,6 +169,9 @@ bool PeerInfo::isPlaintextEncrypted() const
PeerAddress PeerInfo::address() const
{
if (useI2PSocket())
return {};
// fast path for platforms which boost.asio internal struct maps to `sockaddr`
return {QHostAddress(m_nativeInfo.ip.data()), m_nativeInfo.ip.port()};
// slow path for the others
@@ -175,6 +179,23 @@ PeerAddress PeerInfo::address() const
// , m_nativeInfo.ip.port()};
}
QString PeerInfo::I2PAddress() const
{
if (!useI2PSocket())
return {};
#ifdef QBT_USES_LIBTORRENT2
if (m_I2PAddress.isEmpty())
{
const lt::sha256_hash destHash = m_nativeInfo.i2p_destination();
const QByteArray base32Dest = Utils::ByteArray::toBase32({destHash.data(), destHash.size()}).replace('=', "").toLower();
m_I2PAddress = QString::fromLatin1(base32Dest) + u".b32.i2p";
}
#endif
return m_I2PAddress;
}
QString PeerInfo::client() const
{
return QString::fromStdString(m_nativeInfo.client);

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 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
@@ -76,6 +76,7 @@ namespace BitTorrent
bool isPlaintextEncrypted() const;
PeerAddress address() const;
QString I2PAddress() const;
QString client() const;
QString peerIdClient() const;
qreal progress() const;
@@ -101,5 +102,6 @@ namespace BitTorrent
QString m_flagsDescription;
mutable QString m_country;
mutable QString m_I2PAddress;
};
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -170,6 +170,9 @@ namespace BitTorrent
virtual bool useCategoryPathsInManualMode() const = 0;
virtual void setUseCategoryPathsInManualMode(bool value) = 0;
virtual Path suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const = 0;
virtual Path suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const = 0;
static bool isValidTag(const QString &tag);
virtual QSet<QString> tags() const = 0;
virtual bool hasTag(const QString &tag) const = 0;
@@ -268,6 +271,14 @@ namespace BitTorrent
virtual void setI2PPort(int port) = 0;
virtual bool I2PMixedMode() const = 0;
virtual void setI2PMixedMode(bool enabled) = 0;
virtual int I2PInboundQuantity() const = 0;
virtual void setI2PInboundQuantity(int value) = 0;
virtual int I2POutboundQuantity() const = 0;
virtual void setI2POutboundQuantity(int value) = 0;
virtual int I2PInboundLength() const = 0;
virtual void setI2PInboundLength(int value) = 0;
virtual int I2POutboundLength() const = 0;
virtual void setI2POutboundLength(int value) = 0;
virtual bool isProxyPeerConnectionsEnabled() const = 0;
virtual void setProxyPeerConnectionsEnabled(bool enabled) = 0;
virtual ChokingAlgorithm chokingAlgorithm() const = 0;
@@ -400,7 +411,7 @@ namespace BitTorrent
virtual bool isTrackerFilteringEnabled() const = 0;
virtual void setTrackerFilteringEnabled(bool enabled) = 0;
virtual bool isExcludedFileNamesEnabled() const = 0;
virtual void setExcludedFileNamesEnabled(const bool enabled) = 0;
virtual void setExcludedFileNamesEnabled(bool enabled) = 0;
virtual QStringList excludedFileNames() const = 0;
virtual void setExcludedFileNames(const QStringList &newList) = 0;
virtual bool isFilenameExcluded(const QString &fileName) const = 0;

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -60,7 +60,6 @@
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QHostAddress>
#include <QJsonArray>
#include <QJsonDocument>
@@ -528,6 +527,10 @@ SessionImpl::SessionImpl(QObject *parent)
, m_I2PAddress {BITTORRENT_SESSION_KEY(u"I2P/Address"_qs), u"127.0.0.1"_qs}
, m_I2PPort {BITTORRENT_SESSION_KEY(u"I2P/Port"_qs), 7656}
, m_I2PMixedMode {BITTORRENT_SESSION_KEY(u"I2P/MixedMode"_qs), false}
, m_I2PInboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/InboundQuantity"_qs), 3}
, m_I2POutboundQuantity {BITTORRENT_SESSION_KEY(u"I2P/OutboundQuantity"_qs), 3}
, m_I2PInboundLength {BITTORRENT_SESSION_KEY(u"I2P/InboundLength"_qs), 3}
, m_I2POutboundLength {BITTORRENT_SESSION_KEY(u"I2P/OutboundLength"_qs), 3}
, m_seedingLimitTimer {new QTimer(this)}
, m_resumeDataTimer {new QTimer(this)}
, m_ioThread {new QThread}
@@ -759,11 +762,13 @@ void SessionImpl::setFinishedTorrentExportDirectory(const Path &path)
Path SessionImpl::savePath() const
{
// TODO: Make sure it is always non-empty
return m_savePath;
}
Path SessionImpl::downloadPath() const
{
// TODO: Make sure it is always non-empty
return m_downloadPath;
}
@@ -943,6 +948,21 @@ void SessionImpl::setUseCategoryPathsInManualMode(const bool value)
m_useCategoryPathsInManualMode = value;
}
Path SessionImpl::suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const
{
const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
const auto path = (useCategoryPaths ? categorySavePath(categoryName) : savePath());
return path;
}
Path SessionImpl::suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const
{
const bool useCategoryPaths = useAutoTMM.value_or(!isAutoTMMDisabledByDefault()) || useCategoryPathsInManualMode();
const auto categoryDownloadPath = this->categoryDownloadPath(categoryName);
const auto path = ((useCategoryPaths && !categoryDownloadPath.isEmpty()) ? categoryDownloadPath : downloadPath());
return path;
}
QSet<QString> SessionImpl::tags() const
{
return m_tags;
@@ -1434,27 +1454,44 @@ void SessionImpl::endStartup(ResumeSessionContext *context)
}
context->deleteLater();
m_nativeSession->resume();
if (m_refreshEnqueued)
m_refreshEnqueued = false;
else
enqueueRefresh();
m_statisticsLastUpdateTimer.start();
// Regular saving of fastresume data
connect(m_resumeDataTimer, &QTimer::timeout, this, &SessionImpl::generateResumeData);
const int saveInterval = saveResumeDataInterval();
if (saveInterval > 0)
connect(context, &QObject::destroyed, this, [this]
{
m_resumeDataTimer->setInterval(std::chrono::minutes(saveInterval));
m_resumeDataTimer->start();
}
m_nativeSession->resume();
if (m_refreshEnqueued)
m_refreshEnqueued = false;
else
enqueueRefresh();
m_isRestored = true;
emit startupProgressUpdated(100);
emit restored();
m_statisticsLastUpdateTimer.start();
// Regular saving of fastresume data
connect(m_resumeDataTimer, &QTimer::timeout, this, &SessionImpl::generateResumeData);
const int saveInterval = saveResumeDataInterval();
if (saveInterval > 0)
{
m_resumeDataTimer->setInterval(std::chrono::minutes(saveInterval));
m_resumeDataTimer->start();
}
m_wakeupCheckTimer = new QTimer(this);
connect(m_wakeupCheckTimer, &QTimer::timeout, this, [this]
{
const auto now = QDateTime::currentDateTime();
if (m_wakeupCheckTimestamp.secsTo(now) > 100)
{
LogMsg(tr("System wake-up event detected. Re-announcing to all the trackers..."));
reannounceToAllTrackers();
}
m_wakeupCheckTimestamp = QDateTime::currentDateTime();
});
m_wakeupCheckTimestamp = QDateTime::currentDateTime();
m_wakeupCheckTimer->start(30s);
m_isRestored = true;
emit startupProgressUpdated(100);
emit restored();
});
}
void SessionImpl::initializeNativeSession()
@@ -1644,11 +1681,19 @@ lt::settings_pack SessionImpl::loadLTSettings() const
settingsPack.set_bool(lt::settings_pack::allow_i2p_mixed, false);
}
#ifdef QBT_USES_LIBTORRENT2
// I2P session options
settingsPack.set_int(lt::settings_pack::i2p_inbound_quantity, I2PInboundQuantity());
settingsPack.set_int(lt::settings_pack::i2p_outbound_quantity, I2POutboundQuantity());
settingsPack.set_int(lt::settings_pack::i2p_inbound_length, I2PInboundLength());
settingsPack.set_int(lt::settings_pack::i2p_outbound_length, I2POutboundLength());
#endif
// proxy
settingsPack.set_int(lt::settings_pack::proxy_type, lt::settings_pack::none);
if (Preferences::instance()->useProxyForBT())
{
const auto proxyManager = Net::ProxyConfigurationManager::instance();
const auto *proxyManager = Net::ProxyConfigurationManager::instance();
const Net::ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
switch (proxyConfig.type)
@@ -2558,14 +2603,13 @@ LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &add
LoadTorrentParams loadTorrentParams;
loadTorrentParams.name = addTorrentParams.name;
loadTorrentParams.useAutoTMM = addTorrentParams.useAutoTMM.value_or(!isAutoTMMDisabledByDefault());
loadTorrentParams.firstLastPiecePriority = addTorrentParams.firstLastPiecePriority;
loadTorrentParams.hasFinishedStatus = addTorrentParams.skipChecking; // do not react on 'torrent_finished_alert' when skipping
loadTorrentParams.contentLayout = addTorrentParams.contentLayout.value_or(torrentContentLayout());
loadTorrentParams.operatingMode = (addTorrentParams.addForced ? TorrentOperatingMode::Forced : TorrentOperatingMode::AutoManaged);
loadTorrentParams.stopped = addTorrentParams.addPaused.value_or(isAddTorrentPaused());
loadTorrentParams.stopCondition = addTorrentParams.stopCondition.value_or(torrentStopCondition());
loadTorrentParams.addToQueueTop = addTorrentParams.addToQueueTop.value_or(false);
loadTorrentParams.addToQueueTop = addTorrentParams.addToQueueTop.value_or(isAddTorrentToQueueTop());
loadTorrentParams.ratioLimit = addTorrentParams.ratioLimit;
loadTorrentParams.seedingTimeLimit = addTorrentParams.seedingTimeLimit;
@@ -2575,28 +2619,32 @@ LoadTorrentParams SessionImpl::initLoadTorrentParams(const AddTorrentParams &add
else
loadTorrentParams.category = category;
const auto defaultSavePath = suggestedSavePath(loadTorrentParams.category, addTorrentParams.useAutoTMM);
const auto defaultDownloadPath = suggestedDownloadPath(loadTorrentParams.category, addTorrentParams.useAutoTMM);
loadTorrentParams.useAutoTMM = addTorrentParams.useAutoTMM.value_or(
addTorrentParams.savePath.isEmpty() && addTorrentParams.downloadPath.isEmpty() && !isAutoTMMDisabledByDefault());
if (!loadTorrentParams.useAutoTMM)
{
if (addTorrentParams.savePath.isAbsolute())
{
loadTorrentParams.savePath = addTorrentParams.savePath;
}
else
{
const Path basePath = useCategoryPathsInManualMode() ? categorySavePath(loadTorrentParams.category) : savePath();
loadTorrentParams.savePath = basePath / addTorrentParams.savePath;
}
loadTorrentParams.savePath = defaultSavePath / addTorrentParams.savePath;
const bool useDownloadPath = addTorrentParams.useDownloadPath.value_or(isDownloadPathEnabled());
// if useDownloadPath isn't specified but downloadPath is explicitly set we prefer to use it
const bool useDownloadPath = addTorrentParams.useDownloadPath.value_or(!addTorrentParams.downloadPath.isEmpty() || isDownloadPathEnabled());
if (useDownloadPath)
{
// Overridden "Download path" settings
if (addTorrentParams.downloadPath.isAbsolute())
{
loadTorrentParams.downloadPath = addTorrentParams.downloadPath;
}
else
{
const Path basePath = useCategoryPathsInManualMode() ? categoryDownloadPath(loadTorrentParams.category) : downloadPath();
const Path basePath = (!defaultDownloadPath.isEmpty() ? defaultDownloadPath : downloadPath());
loadTorrentParams.downloadPath = basePath / addTorrentParams.downloadPath;
}
}
@@ -3603,6 +3651,62 @@ void SessionImpl::setI2PMixedMode(const bool enabled)
}
}
int SessionImpl::I2PInboundQuantity() const
{
return m_I2PInboundQuantity;
}
void SessionImpl::setI2PInboundQuantity(const int value)
{
if (value == m_I2PInboundQuantity)
return;
m_I2PInboundQuantity = value;
configureDeferred();
}
int SessionImpl::I2POutboundQuantity() const
{
return m_I2POutboundQuantity;
}
void SessionImpl::setI2POutboundQuantity(const int value)
{
if (value == m_I2POutboundQuantity)
return;
m_I2POutboundQuantity = value;
configureDeferred();
}
int SessionImpl::I2PInboundLength() const
{
return m_I2PInboundLength;
}
void SessionImpl::setI2PInboundLength(const int value)
{
if (value == m_I2PInboundLength)
return;
m_I2PInboundLength = value;
configureDeferred();
}
int SessionImpl::I2POutboundLength() const
{
return m_I2POutboundLength;
}
void SessionImpl::setI2POutboundLength(const int value)
{
if (value == m_I2POutboundLength)
return;
m_I2POutboundLength = value;
configureDeferred();
}
bool SessionImpl::isProxyPeerConnectionsEnabled() const
{
return m_isProxyPeerConnectionsEnabled;
@@ -4859,17 +4963,18 @@ void SessionImpl::handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoH
}
}
bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, const MoveStorageMode mode)
bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, const MoveStorageMode mode, const MoveStorageContext context)
{
Q_ASSERT(torrent);
const lt::torrent_handle torrentHandle = torrent->nativeHandle();
const Path currentLocation = torrent->actualStorageLocation();
const bool torrentHasActiveJob = !m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle);
if (m_moveStorageQueue.size() > 1)
{
auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
, [&torrentHandle](const MoveStorageJob &job)
, [&torrentHandle](const MoveStorageJob &job)
{
return job.torrentHandle == torrentHandle;
});
@@ -4877,20 +4982,13 @@ bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &new
if (iter != m_moveStorageQueue.end())
{
// remove existing inactive job
torrent->handleMoveStorageJobFinished(currentLocation, iter->context, torrentHasActiveJob);
LogMsg(tr("Torrent move canceled. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), iter->path.toString()));
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(currentLocation, torrentHasOutstandingJob);
m_moveStorageQueue.erase(iter);
}
}
if (!m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle))
if (torrentHasActiveJob)
{
// if there is active job for this torrent prevent creating meaningless
// job that will move torrent to the same location as current one
@@ -4911,7 +5009,7 @@ bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &new
}
}
const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode};
const MoveStorageJob moveStorageJob {torrentHandle, newPath, mode, context};
m_moveStorageQueue << moveStorageJob;
LogMsg(tr("Enqueued torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\"").arg(torrent->name(), currentLocation.toString(), newPath.toString()));
@@ -4942,7 +5040,7 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
moveTorrentStorage(m_moveStorageQueue.first());
const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
, [&finishedJob](const MoveStorageJob &job)
, [&finishedJob](const MoveStorageJob &job)
{
return job.torrentHandle == finishedJob.torrentHandle;
});
@@ -4952,7 +5050,7 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
TorrentImpl *torrent = m_torrents.value(finishedJob.torrentHandle.info_hash());
if (torrent)
{
torrent->handleMoveStorageJobFinished(newPath, torrentHasOutstandingJob);
torrent->handleMoveStorageJobFinished(newPath, finishedJob.context, torrentHasOutstandingJob);
}
else if (!torrentHasOutstandingJob)
{
@@ -4989,7 +5087,7 @@ void SessionImpl::upgradeCategories()
const auto legacyCategories = SettingValue<QVariantMap>(u"BitTorrent/Session/Categories"_qs).get();
for (auto it = legacyCategories.cbegin(); it != legacyCategories.cend(); ++it)
{
const QString categoryName = it.key();
const QString &categoryName = it.key();
CategoryOptions categoryOptions;
categoryOptions.savePath = Path(it.value().toString());
m_categories[categoryName] = categoryOptions;
@@ -5002,8 +5100,8 @@ void SessionImpl::loadCategories()
{
m_categories.clear();
QFile confFile {(specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME).data()};
if (!confFile.exists())
const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
if (!path.exists())
{
// TODO: Remove the following upgrade code in v4.5
// == BEGIN UPGRADE CODE ==
@@ -5014,26 +5112,27 @@ void SessionImpl::loadCategories()
// return;
}
if (!confFile.open(QFile::ReadOnly))
const int fileMaxSize = 1024 * 1024;
const auto readResult = Utils::IO::readFile(path, fileMaxSize);
if (!readResult)
{
LogMsg(tr("Failed to load Categories. File: \"%1\". Error: \"%2\"")
.arg(confFile.fileName(), confFile.errorString()), Log::CRITICAL);
LogMsg(tr("Failed to load Categories. %1").arg(readResult.error().message), Log::WARNING);
return;
}
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll(), &jsonError);
const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
LogMsg(tr("Failed to parse Categories configuration. File: \"%1\". Error: \"%2\"")
.arg(confFile.fileName(), jsonError.errorString()), Log::WARNING);
.arg(path.toString(), jsonError.errorString()), Log::WARNING);
return;
}
if (!jsonDoc.isObject())
{
LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Reason: invalid data format")
.arg(confFile.fileName()), Log::WARNING);
LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Error: \"Invalid data format\"")
.arg(path.toString()), Log::WARNING);
return;
}
@@ -5232,7 +5331,7 @@ void SessionImpl::handleAddTorrentAlerts(const std::vector<lt::alert *> &alerts)
if (a->type() != lt::add_torrent_alert::alert_type)
continue;
auto alert = static_cast<const lt::add_torrent_alert *>(a);
const auto *alert = static_cast<const lt::add_torrent_alert *>(a);
if (alert->error)
{
const QString msg = QString::fromStdString(alert->message());
@@ -5839,7 +5938,7 @@ void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_a
TorrentImpl *torrent = m_torrents.value(id);
const QString torrentName = (torrent ? torrent->name() : id.toString());
const Path currentLocation = (torrent ? torrent->actualStorageLocation()
: Path(p->handle.status(lt::torrent_handle::query_save_path).save_path));
: Path(p->handle.status(lt::torrent_handle::query_save_path).save_path));
const QString errorMessage = QString::fromStdString(p->message());
LogMsg(tr("Failed to move torrent. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: \"%4\"")
.arg(torrentName, currentLocation.toString(), currentJob.path.toString(), errorMessage), Log::WARNING);

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -38,6 +38,7 @@
#include <libtorrent/torrent_handle.hpp>
#include <QtContainerFwd>
#include <QDateTime>
#include <QElapsedTimer>
#include <QHash>
#include <QPointer>
@@ -87,6 +88,7 @@ namespace BitTorrent
struct LoadTorrentParams;
enum class MoveStorageMode;
enum class MoveStorageContext;
struct SessionMetricIndices
{
@@ -159,6 +161,9 @@ namespace BitTorrent
bool useCategoryPathsInManualMode() const override;
void setUseCategoryPathsInManualMode(bool value) override;
Path suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const override;
Path suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const override;
QSet<QString> tags() const override;
bool hasTag(const QString &tag) const override;
bool addTag(const QString &tag) override;
@@ -245,6 +250,14 @@ namespace BitTorrent
void setI2PPort(int port) override;
bool I2PMixedMode() const override;
void setI2PMixedMode(bool enabled) override;
int I2PInboundQuantity() const override;
void setI2PInboundQuantity(int value) override;
int I2POutboundQuantity() const override;
void setI2POutboundQuantity(int value) override;
int I2PInboundLength() const override;
void setI2PInboundLength(int value) override;
int I2POutboundLength() const override;
void setI2POutboundLength(int value) override;
bool isProxyPeerConnectionsEnabled() const override;
void setProxyPeerConnectionsEnabled(bool enabled) override;
ChokingAlgorithm chokingAlgorithm() const override;
@@ -377,9 +390,9 @@ namespace BitTorrent
bool isTrackerFilteringEnabled() const override;
void setTrackerFilteringEnabled(bool enabled) override;
bool isExcludedFileNamesEnabled() const override;
void setExcludedFileNamesEnabled(const bool enabled) override;
void setExcludedFileNamesEnabled(bool enabled) override;
QStringList excludedFileNames() const override;
void setExcludedFileNames(const QStringList &newList) override;
void setExcludedFileNames(const QStringList &excludedFileNames) override;
bool isFilenameExcluded(const QString &fileName) const override;
QStringList bannedIPs() const override;
void setBannedIPs(const QStringList &newList) override;
@@ -419,27 +432,27 @@ namespace BitTorrent
void handleTorrentNeedSaveResumeData(const TorrentImpl *torrent);
void handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent);
void handleTorrentSaveResumeDataFailed(const TorrentImpl *torrent);
void handleTorrentShareLimitChanged(TorrentImpl *const torrent);
void handleTorrentNameChanged(TorrentImpl *const torrent);
void handleTorrentSavePathChanged(TorrentImpl *const torrent);
void handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory);
void handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag);
void handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag);
void handleTorrentSavingModeChanged(TorrentImpl *const torrent);
void handleTorrentMetadataReceived(TorrentImpl *const torrent);
void handleTorrentPaused(TorrentImpl *const torrent);
void handleTorrentResumed(TorrentImpl *const torrent);
void handleTorrentChecked(TorrentImpl *const torrent);
void handleTorrentFinished(TorrentImpl *const torrent);
void handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers);
void handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QStringList &deletedTrackers);
void handleTorrentTrackersChanged(TorrentImpl *const torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds);
void handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data);
void handleTorrentShareLimitChanged(TorrentImpl *torrent);
void handleTorrentNameChanged(TorrentImpl *torrent);
void handleTorrentSavePathChanged(TorrentImpl *torrent);
void handleTorrentCategoryChanged(TorrentImpl *torrent, const QString &oldCategory);
void handleTorrentTagAdded(TorrentImpl *torrent, const QString &tag);
void handleTorrentTagRemoved(TorrentImpl *torrent, const QString &tag);
void handleTorrentSavingModeChanged(TorrentImpl *torrent);
void handleTorrentMetadataReceived(TorrentImpl *torrent);
void handleTorrentPaused(TorrentImpl *torrent);
void handleTorrentResumed(TorrentImpl *torrent);
void handleTorrentChecked(TorrentImpl *torrent);
void handleTorrentFinished(TorrentImpl *torrent);
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QVector<TrackerEntry> &newTrackers);
void handleTorrentTrackersRemoved(TorrentImpl *torrent, const QStringList &deletedTrackers);
void handleTorrentTrackersChanged(TorrentImpl *torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QVector<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QVector<QUrl> &urlSeeds);
void handleTorrentResumeDataReady(TorrentImpl *torrent, const LoadTorrentParams &data);
void handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash);
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode);
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode, MoveStorageContext context);
void findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
, const Path &downloadPath, const PathList &filePaths = {}) const;
@@ -481,14 +494,15 @@ namespace BitTorrent
{
lt::torrent_handle torrentHandle;
Path path;
MoveStorageMode mode;
MoveStorageMode mode {};
MoveStorageContext context {};
};
struct RemovingTorrentData
{
QString name;
Path pathToRemove;
DeleteOption deleteOption;
DeleteOption deleteOption {};
};
explicit SessionImpl(QObject *parent = nullptr);
@@ -693,6 +707,10 @@ namespace BitTorrent
CachedSettingValue<QString> m_I2PAddress;
CachedSettingValue<int> m_I2PPort;
CachedSettingValue<bool> m_I2PMixedMode;
CachedSettingValue<int> m_I2PInboundQuantity;
CachedSettingValue<int> m_I2POutboundQuantity;
CachedSettingValue<int> m_I2PInboundLength;
CachedSettingValue<int> m_I2POutboundLength;
bool m_isRestored = false;
@@ -768,6 +786,9 @@ namespace BitTorrent
bool m_isPortMappingEnabled = false;
QHash<quint16, std::vector<lt::port_mapping_t>> m_mappedPorts;
QTimer *m_wakeupCheckTimer = nullptr;
QDateTime m_wakeupCheckTimestamp;
friend void Session::initInstance();
friend void Session::freeInstance();
friend Session *Session::instance();

View File

@@ -192,7 +192,6 @@ namespace BitTorrent
virtual void setSavePath(const Path &savePath) = 0;
virtual Path downloadPath() const = 0;
virtual void setDownloadPath(const Path &downloadPath) = 0;
virtual Path actualStorageLocation() const = 0;
virtual Path rootPath() const = 0;
virtual Path contentPath() const = 0;
virtual QString category() const = 0;
@@ -212,9 +211,7 @@ namespace BitTorrent
virtual qreal ratioLimit() const = 0;
virtual int seedingTimeLimit() const = 0;
virtual Path actualFilePath(int index) const = 0;
virtual PathList filePaths() const = 0;
virtual QVector<DownloadPriority> filePriorities() const = 0;
virtual TorrentInfo info() const = 0;
virtual bool isFinished() const = 0;
@@ -232,7 +229,6 @@ namespace BitTorrent
virtual bool isSequentialDownload() const = 0;
virtual bool hasFirstLastPiecePriority() const = 0;
virtual TorrentState state() const = 0;
virtual bool hasMetadata() const = 0;
virtual bool hasMissingFiles() const = 0;
virtual bool hasError() const = 0;
virtual int queuePosition() const = 0;
@@ -244,7 +240,6 @@ namespace BitTorrent
virtual qlonglong activeTime() const = 0;
virtual qlonglong finishedTime() const = 0;
virtual qlonglong eta() const = 0;
virtual QVector<qreal> filesProgress() const = 0;
virtual int seedsCount() const = 0;
virtual int peersCount() const = 0;
virtual int leechsCount() const = 0;
@@ -277,13 +272,6 @@ namespace BitTorrent
virtual int connectionsCount() const = 0;
virtual int connectionsLimit() const = 0;
virtual qlonglong nextAnnounce() const = 0;
/**
* @brief fraction of file pieces that are available at least from one peer
*
* This is not the same as torrrent availability, it is just a fraction of pieces
* that can be downloaded right now. It varies between 0 to 1.
*/
virtual QVector<qreal> availableFileFractions() const = 0;
virtual void setName(const QString &name) = 0;
virtual void setSequentialDownload(bool enable) = 0;
@@ -301,7 +289,6 @@ namespace BitTorrent
virtual void setDHTDisabled(bool disable) = 0;
virtual void setPEXDisabled(bool disable) = 0;
virtual void setLSDDisabled(bool disable) = 0;
virtual void flushCache() const = 0;
virtual void addTrackers(QVector<TrackerEntry> trackers) = 0;
virtual void removeTrackers(const QStringList &trackers) = 0;
virtual void replaceTrackers(QVector<TrackerEntry> trackers) = 0;

View File

@@ -46,14 +46,14 @@ namespace BitTorrent
struct TorrentCreatorParams
{
bool isPrivate;
bool isPrivate = false;
#ifdef QBT_USES_LIBTORRENT2
TorrentFormat torrentFormat;
TorrentFormat torrentFormat = TorrentFormat::Hybrid;
#else
bool isAlignmentOptimized;
int paddedFileSizeLimit;
#endif
int pieceSize;
int pieceSize = 0;
Path inputPath;
Path savePath;
QString comment;
@@ -74,7 +74,7 @@ namespace BitTorrent
void create(const TorrentCreatorParams &params);
#ifdef QBT_USES_LIBTORRENT2
static int calculateTotalPieces(const Path &inputPath, const int pieceSize, const TorrentFormat torrentFormat);
static int calculateTotalPieces(const Path &inputPath, int pieceSize, TorrentFormat torrentFormat);
#else
static int calculateTotalPieces(const Path &inputPath
, const int pieceSize, const bool isAlignmentOptimized, int paddedFileSizeLimit);

View File

@@ -46,7 +46,6 @@
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QPointer>
#include <QSet>
#include <QStringList>
@@ -98,7 +97,7 @@ namespace
QString firstTrackerMessage;
QString firstErrorMessage;
#ifdef QBT_USES_LIBTORRENT2
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size() * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1));
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1);
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
{
for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2})
@@ -338,7 +337,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
// == END UPGRADE CODE ==
}
TorrentImpl::~TorrentImpl() {}
TorrentImpl::~TorrentImpl() = default;
bool TorrentImpl::isValid() const
{
@@ -431,12 +430,16 @@ void TorrentImpl::setSavePath(const Path &path)
if (resolvedPath == savePath())
return;
m_savePath = resolvedPath;
m_session->handleTorrentNeedSaveResumeData(this);
if (isFinished() || m_hasFinishedStatus || downloadPath().isEmpty())
moveStorage(savePath(), MoveStorageMode::KeepExistingFiles);
{
moveStorage(resolvedPath, MoveStorageContext::ChangeSavePath);
}
else
{
m_savePath = resolvedPath;
m_session->handleTorrentSavePathChanged(this);
m_session->handleTorrentNeedSaveResumeData(this);
}
}
Path TorrentImpl::downloadPath() const
@@ -454,13 +457,17 @@ void TorrentImpl::setDownloadPath(const Path &path)
if (resolvedPath == m_downloadPath)
return;
m_downloadPath = resolvedPath;
m_session->handleTorrentNeedSaveResumeData(this);
const bool isIncomplete = !(isFinished() || m_hasFinishedStatus);
if (isIncomplete)
moveStorage((m_downloadPath.isEmpty() ? savePath() : m_downloadPath), MoveStorageMode::KeepExistingFiles);
{
moveStorage((resolvedPath.isEmpty() ? savePath() : resolvedPath), MoveStorageContext::ChangeDownloadPath);
}
else
{
m_downloadPath = resolvedPath;
m_session->handleTorrentSavePathChanged(this);
m_session->handleTorrentNeedSaveResumeData(this);
}
}
Path TorrentImpl::rootPath() const
@@ -769,7 +776,13 @@ qreal TorrentImpl::progress() const
return 1.;
const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
Q_ASSERT((progress >= 0.f) && (progress <= 1.f));
if ((progress < 0.f) || (progress > 1.f))
{
LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
.arg(name(), QString::number(m_nativeStatus.total_wanted), QString::number(m_nativeStatus.total_wanted_done))
, Log::WARNING);
}
return progress;
}
@@ -1618,7 +1631,7 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
{
const auto nativeIndex = nativeIndexes.at(i);
const Path actualFilePath = fileNames.at(i);
const Path &actualFilePath = fileNames.at(i);
p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
const Path filePath = actualFilePath.removedExtension(QB_EXT);
@@ -1754,7 +1767,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
}
}
void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageMode mode)
void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
{
if (!hasMetadata())
{
@@ -1762,7 +1775,9 @@ void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageMode mode)
return;
}
if (m_session->addMoveTorrentStorageJob(this, newPath, mode))
const auto mode = (context == MoveStorageContext::AdjustCurrentLocation)
? MoveStorageMode::Overwrite : MoveStorageMode::KeepExistingFiles;
if (m_session->addMoveTorrentStorageJob(this, newPath, mode, context))
{
m_storageIsMoving = true;
updateState();
@@ -1784,16 +1799,17 @@ void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
updateStatus(nativeStatus);
}
void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const bool hasOutstandingJob)
void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStorageContext context, const bool hasOutstandingJob)
{
m_session->handleTorrentNeedSaveResumeData(this);
if (context == MoveStorageContext::ChangeSavePath)
m_savePath = path;
else if (context == MoveStorageContext::ChangeDownloadPath)
m_downloadPath = path;
m_storageIsMoving = hasOutstandingJob;
m_nativeStatus.save_path = path.toString().toStdString();
if (actualStorageLocation() != path)
{
m_nativeStatus.save_path = path.toString().toStdString();
m_session->handleTorrentSavePathChanged(this);
}
m_session->handleTorrentSavePathChanged(this);
m_session->handleTorrentNeedSaveResumeData(this);
if (!m_storageIsMoving)
{
@@ -2221,7 +2237,7 @@ void TorrentImpl::adjustStorageLocation()
const Path targetPath = ((isFinished() || m_hasFinishedStatus || downloadPath.isEmpty()) ? savePath() : downloadPath);
if ((targetPath != actualStorageLocation()) || isMoveInProgress())
moveStorage(targetPath, MoveStorageMode::Overwrite);
moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation);
}
void TorrentImpl::doRenameFile(int index, const Path &path)

View File

@@ -68,6 +68,13 @@ namespace BitTorrent
Overwrite
};
enum class MoveStorageContext
{
AdjustCurrentLocation,
ChangeSavePath,
ChangeDownloadPath
};
enum class MaintenanceJob
{
None,
@@ -77,7 +84,7 @@ namespace BitTorrent
struct FileErrorInfo
{
lt::error_code error;
lt::operation_t operation;
lt::operation_t operation = lt::operation_t::unknown;
};
class TorrentImpl final : public Torrent
@@ -252,7 +259,7 @@ namespace BitTorrent
void handleCategoryOptionsChanged();
void handleAppendExtensionToggled();
void saveResumeData(lt::resume_data_flags_t flags = {});
void handleMoveStorageJobFinished(const Path &path, bool hasOutstandingJob);
void handleMoveStorageJobFinished(const Path &path, MoveStorageContext context, bool hasOutstandingJob);
void fileSearchFinished(const Path &savePath, const PathList &fileNames);
TrackerEntry updateTrackerEntry(const lt::announce_entry &announceEntry, const QMap<TrackerEntry::Endpoint, int> &updateInfo);
@@ -289,7 +296,7 @@ namespace BitTorrent
Path wantedActualPath(int index, const Path &path) const;
void adjustStorageLocation();
void doRenameFile(int index, const Path &path);
void moveStorage(const Path &newPath, MoveStorageMode mode);
void moveStorage(const Path &newPath, MoveStorageContext context);
void manageIncompleteFiles();
void applyFirstLastPiecePriority(bool enabled);

View File

@@ -66,12 +66,6 @@ TorrentInfo::TorrentInfo(const lt::torrent_info &nativeInfo)
}
}
TorrentInfo::TorrentInfo(const TorrentInfo &other)
: m_nativeInfo {other.m_nativeInfo}
, m_nativeIndexes {other.m_nativeIndexes}
{
}
TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
{
if (this != &other)
@@ -109,28 +103,20 @@ nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data)
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const Path &path) noexcept
{
QFile file {path.data()};
if (!file.open(QIODevice::ReadOnly))
return nonstd::make_unexpected(file.errorString());
if (file.size() > MAX_TORRENT_SIZE)
return nonstd::make_unexpected(tr("File size exceeds max limit %1").arg(Utils::Misc::friendlyUnit(MAX_TORRENT_SIZE)));
QByteArray data;
try
{
data = file.readAll();
const auto readResult = Utils::IO::readFile(path, MAX_TORRENT_SIZE);
if (!readResult)
return nonstd::make_unexpected(readResult.error().message);
data = readResult.value();
}
catch (const std::bad_alloc &e)
{
return nonstd::make_unexpected(tr("Torrent file read error: %1").arg(QString::fromLocal8Bit(e.what())));
return nonstd::make_unexpected(tr("Failed to allocate memory when reading file. File: \"%1\". Error: \"%2\"")
.arg(path.toString(), QString::fromLocal8Bit(e.what())));
}
if (data.size() != file.size())
return nonstd::make_unexpected(tr("Torrent file read error: size mismatch"));
file.close();
return load(data);
}

View File

@@ -54,7 +54,7 @@ namespace BitTorrent
public:
TorrentInfo() = default;
TorrentInfo(const TorrentInfo &other);
TorrentInfo(const TorrentInfo &other) = default;
explicit TorrentInfo(const lt::torrent_info &nativeInfo);

View File

@@ -45,7 +45,7 @@ public:
Digest32() = default;
Digest32(const Digest32 &other) = default;
Digest32(Digest32 &&other) = default;
Digest32(Digest32 &&other) noexcept = default;
Digest32(const UnderlyingType &nativeDigest)
: m_dataPtr {new Data(nativeDigest)}
@@ -63,7 +63,7 @@ public:
}
Digest32 &operator=(const Digest32 &other) = default;
Digest32 &operator=(Digest32 &&other) = default;
Digest32 &operator=(Digest32 &&other) noexcept = default;
operator UnderlyingType() const
{

View File

@@ -42,7 +42,7 @@ constexpr typename std::add_const_t<T> &asConst(T &t) noexcept { return t; }
// Forward rvalue as const
template <typename T>
constexpr typename std::add_const_t<T> asConst(T &&t) noexcept { return std::move(t); }
constexpr typename std::add_const_t<T> asConst(T &&t) noexcept { return std::forward<T>(t); }
// Prevent const rvalue arguments
template <typename T>

View File

@@ -37,7 +37,7 @@ namespace Http
class IRequestHandler
{
public:
virtual ~IRequestHandler() {}
virtual ~IRequestHandler() = default;
virtual Response processRequest(const Request &request, const Environment &env) = 0;
};
}

View File

@@ -75,10 +75,6 @@ namespace
}
}
RequestParser::RequestParser()
{
}
RequestParser::ParseResult RequestParser::parse(const QByteArray &data)
{
// Warning! Header names are converted to lowercase

View File

@@ -47,9 +47,9 @@ namespace Http
struct ParseResult
{
// when `status != ParseStatus::OK`, `request` & `frameSize` are undefined
ParseStatus status;
ParseStatus status = ParseStatus::BadRequest;
Request request;
long frameSize; // http request frame size (bytes)
long frameSize = 0; // http request frame size (bytes)
};
static ParseResult parse(const QByteArray &data);
@@ -57,7 +57,7 @@ namespace Http
static const long MAX_CONTENT_SIZE = 64 * 1024 * 1024; // 64 MB
private:
RequestParser();
RequestParser() = default;
ParseResult doParse(const QByteArray &data);
bool parseStartLines(QStringView data);

View File

@@ -47,6 +47,7 @@ namespace Http
inline const QString HEADER_CONTENT_LENGTH = u"content-length"_qs;
inline const QString HEADER_CONTENT_SECURITY_POLICY = u"content-security-policy"_qs;
inline const QString HEADER_CONTENT_TYPE = u"content-type"_qs;
inline const QString HEADER_CROSS_ORIGIN_OPENER_POLICY = u"cross-origin-opener-policy"_qs;
inline const QString HEADER_DATE = u"date"_qs;
inline const QString HEADER_HOST = u"host"_qs;
inline const QString HEADER_ORIGIN = u"origin"_qs;
@@ -79,10 +80,10 @@ namespace Http
struct Environment
{
QHostAddress localAddress;
quint16 localPort;
quint16 localPort = 0;
QHostAddress clientAddress;
quint16 clientPort;
quint16 clientPort = 0;
};
struct UploadedFile

View File

@@ -36,8 +36,6 @@ IconProvider::IconProvider(QObject *parent)
{
}
IconProvider::~IconProvider() {}
void IconProvider::initInstance()
{
if (!m_instance)

View File

@@ -48,7 +48,7 @@ public:
protected:
explicit IconProvider(QObject *parent = nullptr);
~IconProvider();
~IconProvider() = default;
static IconProvider *m_instance;
};

View File

@@ -35,7 +35,7 @@
#include <QString>
#include <QtContainerFwd>
const int MAX_LOG_MESSAGES = 20000;
inline const int MAX_LOG_MESSAGES = 20000;
namespace Log
{
@@ -51,17 +51,17 @@ namespace Log
struct Msg
{
int id;
MsgType type;
qint64 timestamp;
int id = -1;
MsgType type = ALL;
qint64 timestamp = -1;
QString message;
};
struct Peer
{
int id;
bool blocked;
qint64 timestamp;
int id = -1;
bool blocked = false;
qint64 timestamp = -1;
QString ip;
QString reason;
};

View File

@@ -44,8 +44,6 @@ const std::chrono::seconds IP_CHECK_INTERVAL = 30min;
DNSUpdater::DNSUpdater(QObject *parent)
: QObject(parent)
, m_state(OK)
, m_service(DNS::Service::None)
{
updateCredentials();
@@ -217,7 +215,7 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
if (code == u"badagent")
{
LogMsg(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please submit a bug report at http://bugs.qbittorrent.org."),
LogMsg(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please submit a bug report at https://bugs.qbittorrent.org."),
Log::CRITICAL);
m_state = FATAL;
return;
@@ -225,7 +223,7 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
if (code == u"!donator")
{
LogMsg(tr("Dynamic DNS error: %1 was returned by the service, please submit a bug report at http://bugs.qbittorrent.org.").arg(u"!donator"_qs),
LogMsg(tr("Dynamic DNS error: %1 was returned by the service, please submit a bug report at https://bugs.qbittorrent.org.").arg(u"!donator"_qs),
Log::CRITICAL);
m_state = FATAL;
return;

View File

@@ -74,9 +74,9 @@ namespace Net
QHostAddress m_lastIP;
QDateTime m_lastIPCheckTime;
QTimer m_ipCheckTimer;
int m_state;
int m_state = OK;
// Service creds
DNS::Service m_service;
DNS::Service m_service = DNS::Service::None;
QString m_domain;
QString m_username;
QString m_password;

View File

@@ -208,7 +208,7 @@ void Net::DownloadHandlerImpl::handleRedirection(const QUrl &newUrl)
return;
}
auto redirected = static_cast<DownloadHandlerImpl *>(
auto *redirected = static_cast<DownloadHandlerImpl *>(
m_manager->download(DownloadRequest(m_downloadRequest).url(newUrlString), useProxy()));
redirected->m_redirectionCount = m_redirectionCount + 1;
connect(redirected, &DownloadHandlerImpl::finished, this, [this](const DownloadResult &result)

View File

@@ -163,7 +163,7 @@ Net::DownloadHandler *Net::DownloadManager::download(const DownloadRequest &down
const ServiceID id = ServiceID::fromURL(downloadRequest.url());
const bool isSequentialService = m_sequentialServices.contains(id);
auto downloadHandler = new DownloadHandlerImpl(this, downloadRequest, useProxy);
auto *downloadHandler = new DownloadHandlerImpl(this, downloadRequest, useProxy);
connect(downloadHandler, &DownloadHandler::finished, downloadHandler, &QObject::deleteLater);
connect(downloadHandler, &QObject::destroyed, this, [this, id, downloadHandler]()
{
@@ -274,7 +274,7 @@ void Net::DownloadManager::handleDownloadFinished(DownloadHandlerImpl *finishedH
return;
}
auto handler = waitingJobsIter.value().dequeue();
auto *handler = waitingJobsIter.value().dequeue();
qDebug("Downloading %s...", qUtf8Printable(handler->url()));
processRequest(handler);
handler->disconnect(this);

View File

@@ -103,7 +103,7 @@ namespace Net
struct DownloadResult
{
QString url;
DownloadStatus status;
DownloadStatus status = DownloadStatus::Failed;
QString errorString;
QByteArray data;
Path filePath;

View File

@@ -162,7 +162,7 @@ void Smtp::sendMail(const QString &from, const QString &to, const QString &subje
// Connect to SMTP server
const QStringList serverEndpoint = pref->getMailNotificationSMTP().split(u':');
const QString serverAddress = serverEndpoint[0];
const QString &serverAddress = serverEndpoint[0];
const std::optional<int> serverPort = Utils::String::parseInt(serverEndpoint.value(1));
#ifndef QT_NO_OPENSSL

View File

@@ -589,11 +589,7 @@ void Preferences::setWebUiPort(const quint16 port)
bool Preferences::useUPnPForWebUIPort() const
{
#ifdef DISABLE_GUI
return value(u"Preferences/WebUI/UseUPnP"_qs, true);
#else
return value(u"Preferences/WebUI/UseUPnP"_qs, false);
#endif
}
void Preferences::setUPnPForWebUIPort(const bool enabled)
@@ -1228,6 +1224,16 @@ void Preferences::setConfirmRemoveAllTags(const bool enabled)
setValue(u"Preferences/Advanced/confirmRemoveAllTags"_qs, enabled);
}
bool Preferences::confirmPauseAndResumeAll() const
{
return value(u"GUI/ConfirmActions/PauseAndResumeAllTorrents"_qs, true);
}
void Preferences::setConfirmPauseAndResumeAll(const bool enabled)
{
setValue(u"GUI/ConfirmActions/PauseAndResumeAllTorrents"_qs, enabled);
}
#ifndef Q_OS_MACOS
TrayIcon::Style Preferences::trayIconStyle() const
{

View File

@@ -253,7 +253,7 @@ public:
void setUILocked(bool locked);
bool isAutoRunOnTorrentAddedEnabled() const;
void setAutoRunOnTorrentAddedEnabled(const bool enabled);
void setAutoRunOnTorrentAddedEnabled(bool enabled);
QString getAutoRunOnTorrentAddedProgram() const;
void setAutoRunOnTorrentAddedProgram(const QString &program);
bool isAutoRunOnTorrentFinishedEnabled() const;
@@ -315,6 +315,8 @@ public:
void setConfirmTorrentRecheck(bool enabled);
bool confirmRemoveAllTags() const;
void setConfirmRemoveAllTags(bool enabled);
bool confirmPauseAndResumeAll() const;
void setConfirmPauseAndResumeAll(bool enabled);
#ifndef Q_OS_MACOS
bool systemTrayEnabled() const;
void setSystemTrayEnabled(bool enabled);

View File

@@ -116,9 +116,9 @@ Path Private::DefaultProfile::downloadLocation() const
std::unique_ptr<QSettings> Private::DefaultProfile::applicationSettings(const QString &name) const
{
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
return std::unique_ptr<QSettings>(new QSettings(QSettings::IniFormat, QSettings::UserScope, profileName(), name));
return std::make_unique<QSettings>(QSettings::IniFormat, QSettings::UserScope, profileName(), name);
#else
return std::unique_ptr<QSettings>(new QSettings(profileName(), name));
return std::make_unique<QSettings>(profileName(), name);
#endif
}
@@ -177,7 +177,7 @@ std::unique_ptr<QSettings> Private::CustomProfile::applicationSettings(const QSt
const auto CONF_FILE_EXTENSION = u".conf"_qs;
#endif
const Path settingsFilePath = configLocation() / Path(name + CONF_FILE_EXTENSION);
return std::unique_ptr<QSettings>(new QSettings(settingsFilePath.data(), QSettings::IniFormat));
return std::make_unique<QSettings>(settingsFilePath.data(), QSettings::IniFormat);
}
Path Private::NoConvertConverter::fromPortablePath(const Path &portablePath) const

View File

@@ -31,7 +31,6 @@
#include "feed_serializer.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
@@ -46,23 +45,21 @@ const int ARTICLEDATALIST_TYPEID = qRegisterMetaType<QVector<QVariantHash>>();
void RSS::Private::FeedSerializer::load(const Path &dataFileName, const QString &url)
{
QFile file {dataFileName.data()};
const int fileMaxSize = 10 * 1024 * 1024;
const auto readResult = Utils::IO::readFile(dataFileName, fileMaxSize);
if (!readResult)
{
if (readResult.error().status == Utils::IO::ReadError::NotExist)
{
emit loadingFinished({});
return;
}
if (!file.exists())
{
emit loadingFinished({});
}
else if (file.open(QFile::ReadOnly))
{
emit loadingFinished(loadArticles(file.readAll(), url));
file.close();
}
else
{
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2")
.arg(dataFileName.toString(), file.errorString())
, Log::WARNING);
LogMsg(tr("Failed to read RSS session data. %1").arg(readResult.error().message), Log::WARNING);
return;
}
emit loadingFinished(loadArticles(readResult.value(), url));
}
void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QVector<QVariantHash> &articlesData)

View File

@@ -28,6 +28,8 @@
#include "rss_autodownloader.h"
#include <queue>
#include <QDataStream>
#include <QDebug>
#include <QJsonDocument>
@@ -46,6 +48,7 @@
#include "../logger.h"
#include "../profile.h"
#include "../utils/fs.h"
#include "../utils/io.h"
#include "rss_article.h"
#include "rss_autodownloadrule.h"
#include "rss_feed.h"
@@ -166,25 +169,27 @@ AutoDownloader *AutoDownloader::instance()
bool AutoDownloader::hasRule(const QString &ruleName) const
{
return m_rules.contains(ruleName);
return m_rulesByName.contains(ruleName);
}
AutoDownloadRule AutoDownloader::ruleByName(const QString &ruleName) const
{
return m_rules.value(ruleName, AutoDownloadRule(u"Unknown Rule"_qs));
const auto index = m_rulesByName.value(ruleName, -1);
return m_rules.value(index, AutoDownloadRule(u"Unknown Rule"_qs));
}
QList<AutoDownloadRule> AutoDownloader::rules() const
{
return m_rules.values();
return m_rules;
}
void AutoDownloader::insertRule(const AutoDownloadRule &rule)
void AutoDownloader::setRule(const AutoDownloadRule &rule)
{
if (!hasRule(rule.name()))
{
// Insert new rule
setRule_impl(rule);
sortRules();
m_dirty = true;
store();
emit ruleAdded(rule.name());
@@ -194,6 +199,7 @@ void AutoDownloader::insertRule(const AutoDownloadRule &rule)
{
// Update existing rule
setRule_impl(rule);
sortRules();
m_dirty = true;
storeDeferred();
emit ruleChanged(rule.name());
@@ -203,12 +209,12 @@ void AutoDownloader::insertRule(const AutoDownloadRule &rule)
bool AutoDownloader::renameRule(const QString &ruleName, const QString &newRuleName)
{
if (!hasRule(ruleName)) return false;
if (hasRule(newRuleName)) return false;
if (!hasRule(ruleName) || hasRule(newRuleName))
return false;
AutoDownloadRule rule = m_rules.take(ruleName);
rule.setName(newRuleName);
m_rules.insert(newRuleName, rule);
const auto index = m_rulesByName.take(ruleName);
m_rules[index].setName(newRuleName);
m_rulesByName.insert(newRuleName, index);
m_dirty = true;
store();
emit ruleRenamed(newRuleName, ruleName);
@@ -217,13 +223,21 @@ bool AutoDownloader::renameRule(const QString &ruleName, const QString &newRuleN
void AutoDownloader::removeRule(const QString &ruleName)
{
if (m_rules.contains(ruleName))
if (!hasRule(ruleName))
return;
emit ruleAboutToBeRemoved(ruleName);
const auto index = m_rulesByName.take(ruleName);
m_rules.removeAt(index);
for (qsizetype i = index; i < m_rules.size(); ++i)
{
emit ruleAboutToBeRemoved(ruleName);
m_rules.remove(ruleName);
m_dirty = true;
store();
const AutoDownloadRule &rule = m_rules[i];
m_rulesByName[rule.name()] = i;
}
m_dirty = true;
store();
}
QByteArray AutoDownloader::exportRules(AutoDownloader::RulesFileFormat format) const
@@ -261,7 +275,7 @@ QByteArray AutoDownloader::exportRulesToJSONFormat() const
void AutoDownloader::importRulesFromJSONFormat(const QByteArray &data)
{
for (const auto &rule : asConst(rulesFromJSON(data)))
insertRule(rule);
setRule(rule);
}
QByteArray AutoDownloader::exportRulesToLegacyFormat() const
@@ -288,7 +302,7 @@ void AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data)
throw ParsingError(tr("Invalid data format"));
for (const QVariant &val : asConst(dict))
insertRule(AutoDownloadRule::fromLegacyDict(val.toHash()));
setRule(AutoDownloadRule::fromLegacyDict(val.toHash()));
}
QStringList AutoDownloader::smartEpisodeFilters() const
@@ -382,13 +396,13 @@ void AutoDownloader::handleFeedURLChanged(Feed *feed, const QString &oldURL)
}
}
for (QSharedPointer<ProcessingJob> job : asConst(m_processingQueue))
for (const QSharedPointer<ProcessingJob> &job : asConst(m_processingQueue))
{
if (job->feedURL == oldURL)
job->feedURL = feed->url();
}
for (QSharedPointer<ProcessingJob> job : asConst(m_waitingJobs))
for (const QSharedPointer<ProcessingJob> &job : asConst(m_waitingJobs))
{
if (job->feedURL == oldURL)
job->feedURL = feed->url();
@@ -399,7 +413,31 @@ void AutoDownloader::handleFeedURLChanged(Feed *feed, const QString &oldURL)
void AutoDownloader::setRule_impl(const AutoDownloadRule &rule)
{
m_rules.insert(rule.name(), rule);
const QString ruleName = rule.name();
const auto index = m_rulesByName.value(ruleName, -1);
if (index < 0)
{
m_rules.append(rule);
m_rulesByName[ruleName] = m_rules.size() - 1;
}
else
{
m_rules[index] = rule;
}
}
void AutoDownloader::sortRules()
{
std::sort(m_rules.begin(), m_rules.end(), [](const AutoDownloadRule &lhs, const AutoDownloadRule &rhs)
{
return (lhs.priority() < rhs.priority());
});
for (qsizetype i = 0; i < m_rules.size(); ++i)
{
const AutoDownloadRule &rule = m_rules[i];
m_rulesByName[rule.name()] = i;
}
}
void AutoDownloader::addJobForArticle(const Article *article)
@@ -430,15 +468,11 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
m_dirty = true;
storeDeferred();
BitTorrent::AddTorrentParams params;
params.savePath = rule.savePath();
params.category = rule.assignedCategory();
params.addPaused = rule.addPaused();
params.contentLayout = rule.torrentContentLayout();
if (!rule.savePath().isEmpty())
params.useAutoTMM = false;
LogMsg(tr("RSS article '%1' is accepted by rule '%2'. Trying to add torrent...")
.arg(job->articleData.value(Article::KeyTitle).toString(), rule.name()));
const auto torrentURL = job->articleData.value(Article::KeyTorrentURL).toString();
BitTorrent::Session::instance()->addTorrent(torrentURL, params);
BitTorrent::Session::instance()->addTorrent(torrentURL, rule.addTorrentParams());
if (BitTorrent::MagnetUri(torrentURL).isValid())
{
@@ -460,21 +494,21 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
void AutoDownloader::load()
{
QFile rulesFile {(m_fileStorage->storageDir() / Path(RULES_FILE_NAME)).data()};
const qint64 maxFileSize = 10 * 1024 * 1024;
const auto readResult = Utils::IO::readFile((m_fileStorage->storageDir() / Path(RULES_FILE_NAME)), maxFileSize);
if (!readResult)
{
if (readResult.error().status == Utils::IO::ReadError::NotExist)
{
loadRulesLegacy();
return;
}
if (!rulesFile.exists())
{
loadRulesLegacy();
}
else if (rulesFile.open(QFile::ReadOnly))
{
loadRules(rulesFile.readAll());
}
else
{
LogMsg(tr("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
.arg(rulesFile.fileName(), rulesFile.errorString()), Log::CRITICAL);
LogMsg((tr("Failed to read RSS AutoDownloader rules. %1").arg(readResult.error().message)), Log::WARNING);
return;
}
loadRules(readResult.value());
}
void AutoDownloader::loadRules(const QByteArray &data)
@@ -484,6 +518,7 @@ void AutoDownloader::loadRules(const QByteArray &data)
const auto rules = rulesFromJSON(data);
for (const auto &rule : rules)
setRule_impl(rule);
sortRules();
}
catch (const ParsingError &error)
{
@@ -500,7 +535,7 @@ void AutoDownloader::loadRulesLegacy()
{
const auto rule = AutoDownloadRule::fromLegacyDict(ruleVar.toHash());
if (!rule.name().isEmpty())
insertRule(rule);
setRule(rule);
}
}

View File

@@ -94,7 +94,7 @@ namespace RSS
AutoDownloadRule ruleByName(const QString &ruleName) const;
QList<AutoDownloadRule> rules() const;
void insertRule(const AutoDownloadRule &rule);
void setRule(const AutoDownloadRule &rule);
bool renameRule(const QString &ruleName, const QString &newRuleName);
void removeRule(const QString &ruleName);
@@ -118,6 +118,7 @@ namespace RSS
private:
void timerEvent(QTimerEvent *event) override;
void setRule_impl(const AutoDownloadRule &rule);
void sortRules();
void resetProcessingQueue();
void startProcessing();
void addJobForArticle(const Article *article);
@@ -141,7 +142,8 @@ namespace RSS
QTimer *m_processingTimer = nullptr;
Utils::Thread::UniquePtr m_ioThread;
AsyncFileStorage *m_fileStorage = nullptr;
QHash<QString, AutoDownloadRule> m_rules;
QList<AutoDownloadRule> m_rules;
QHash<QString, qsizetype> m_rulesByName;
QList<QSharedPointer<ProcessingJob>> m_processingQueue;
QHash<QString, QSharedPointer<ProcessingJob>> m_waitingJobs;
bool m_dirty = false;

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2017-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -101,22 +101,25 @@ namespace
}
}
const QString Str_Name = u"name"_qs;
const QString Str_Enabled = u"enabled"_qs;
const QString Str_UseRegex = u"useRegex"_qs;
const QString Str_MustContain = u"mustContain"_qs;
const QString Str_MustNotContain = u"mustNotContain"_qs;
const QString Str_EpisodeFilter = u"episodeFilter"_qs;
const QString Str_AffectedFeeds = u"affectedFeeds"_qs;
const QString Str_SavePath = u"savePath"_qs;
const QString Str_AssignedCategory = u"assignedCategory"_qs;
const QString Str_LastMatch = u"lastMatch"_qs;
const QString Str_IgnoreDays = u"ignoreDays"_qs;
const QString Str_AddPaused = u"addPaused"_qs;
const QString Str_CreateSubfolder = u"createSubfolder"_qs;
const QString Str_ContentLayout = u"torrentContentLayout"_qs;
const QString Str_SmartFilter = u"smartFilter"_qs;
const QString Str_PreviouslyMatched = u"previouslyMatchedEpisodes"_qs;
const QString S_NAME = u"name"_qs;
const QString S_ENABLED = u"enabled"_qs;
const QString S_PRIORITY = u"priority"_qs;
const QString S_USE_REGEX = u"useRegex"_qs;
const QString S_MUST_CONTAIN = u"mustContain"_qs;
const QString S_MUST_NOT_CONTAIN = u"mustNotContain"_qs;
const QString S_EPISODE_FILTER = u"episodeFilter"_qs;
const QString S_AFFECTED_FEEDS = u"affectedFeeds"_qs;
const QString S_LAST_MATCH = u"lastMatch"_qs;
const QString S_IGNORE_DAYS = u"ignoreDays"_qs;
const QString S_SMART_FILTER = u"smartFilter"_qs;
const QString S_PREVIOUSLY_MATCHED = u"previouslyMatchedEpisodes"_qs;
const QString S_SAVE_PATH = u"savePath"_qs;
const QString S_ASSIGNED_CATEGORY = u"assignedCategory"_qs;
const QString S_ADD_PAUSED = u"addPaused"_qs;
const QString S_CONTENT_LAYOUT = u"torrentContentLayout"_qs;
const QString S_TORRENT_PARAMS = u"torrentParams"_qs;
namespace RSS
{
@@ -124,6 +127,7 @@ namespace RSS
{
QString name;
bool enabled = true;
int priority = 0;
QStringList mustContain;
QStringList mustNotContain;
@@ -133,10 +137,7 @@ namespace RSS
int ignoreDays = 0;
QDateTime lastMatch;
Path savePath;
QString category;
std::optional<bool> addPaused;
std::optional<BitTorrent::TorrentContentLayout> contentLayout;
BitTorrent::AddTorrentParams addTorrentParams;
bool smartFilter = false;
QStringList previouslyMatchedEpisodes;
@@ -148,6 +149,7 @@ namespace RSS
{
return (left.name == right.name)
&& (left.enabled == right.enabled)
&& (left.priority == right.priority)
&& (left.mustContain == right.mustContain)
&& (left.mustNotContain == right.mustNotContain)
&& (left.episodeFilter == right.episodeFilter)
@@ -155,11 +157,8 @@ namespace RSS
&& (left.useRegex == right.useRegex)
&& (left.ignoreDays == right.ignoreDays)
&& (left.lastMatch == right.lastMatch)
&& (left.savePath == right.savePath)
&& (left.category == right.category)
&& (left.addPaused == right.addPaused)
&& (left.contentLayout == right.contentLayout)
&& (left.smartFilter == right.smartFilter);
&& (left.smartFilter == right.smartFilter)
&& (left.addTorrentParams == right.addTorrentParams);
}
};
@@ -208,12 +207,9 @@ AutoDownloadRule::AutoDownloadRule(const QString &name)
setName(name);
}
AutoDownloadRule::AutoDownloadRule(const AutoDownloadRule &other)
: m_dataPtr(other.m_dataPtr)
{
}
AutoDownloadRule::AutoDownloadRule(const AutoDownloadRule &other) = default;
AutoDownloadRule::~AutoDownloadRule() {}
AutoDownloadRule::~AutoDownloadRule() = default;
QRegularExpression AutoDownloadRule::cachedRegex(const QString &expression, const bool isRegex) const
{
@@ -461,64 +457,48 @@ AutoDownloadRule &AutoDownloadRule::operator=(const AutoDownloadRule &other)
QJsonObject AutoDownloadRule::toJsonObject() const
{
return {{Str_Enabled, isEnabled()}
, {Str_UseRegex, useRegex()}
, {Str_MustContain, mustContain()}
, {Str_MustNotContain, mustNotContain()}
, {Str_EpisodeFilter, episodeFilter()}
, {Str_AffectedFeeds, QJsonArray::fromStringList(feedURLs())}
, {Str_SavePath, savePath().toString()}
, {Str_AssignedCategory, assignedCategory()}
, {Str_LastMatch, lastMatch().toString(Qt::RFC2822Date)}
, {Str_IgnoreDays, ignoreDays()}
, {Str_AddPaused, toJsonValue(addPaused())}
, {Str_ContentLayout, contentLayoutToJsonValue(torrentContentLayout())}
, {Str_SmartFilter, useSmartFilter()}
, {Str_PreviouslyMatched, QJsonArray::fromStringList(previouslyMatchedEpisodes())}};
const BitTorrent::AddTorrentParams &addTorrentParams = m_dataPtr->addTorrentParams;
return {{S_ENABLED, isEnabled()}
, {S_PRIORITY, priority()}
, {S_USE_REGEX, useRegex()}
, {S_MUST_CONTAIN, mustContain()}
, {S_MUST_NOT_CONTAIN, mustNotContain()}
, {S_EPISODE_FILTER, episodeFilter()}
, {S_AFFECTED_FEEDS, QJsonArray::fromStringList(feedURLs())}
, {S_LAST_MATCH, lastMatch().toString(Qt::RFC2822Date)}
, {S_IGNORE_DAYS, ignoreDays()}
, {S_SMART_FILTER, useSmartFilter()}
, {S_PREVIOUSLY_MATCHED, QJsonArray::fromStringList(previouslyMatchedEpisodes())}
// TODO: The following code is deprecated. Replace with the commented one after several releases in 4.6.x.
// === BEGIN DEPRECATED CODE === //
, {S_ADD_PAUSED, toJsonValue(addTorrentParams.addPaused)}
, {S_CONTENT_LAYOUT, contentLayoutToJsonValue(addTorrentParams.contentLayout)}
, {S_SAVE_PATH, addTorrentParams.savePath.toString()}
, {S_ASSIGNED_CATEGORY, addTorrentParams.category}
// === END DEPRECATED CODE === //
, {S_TORRENT_PARAMS, BitTorrent::serializeAddTorrentParams(addTorrentParams)}
};
}
AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, const QString &name)
{
AutoDownloadRule rule(name.isEmpty() ? jsonObj.value(Str_Name).toString() : name);
AutoDownloadRule rule {(name.isEmpty() ? jsonObj.value(S_NAME).toString() : name)};
rule.setUseRegex(jsonObj.value(Str_UseRegex).toBool(false));
rule.setMustContain(jsonObj.value(Str_MustContain).toString());
rule.setMustNotContain(jsonObj.value(Str_MustNotContain).toString());
rule.setEpisodeFilter(jsonObj.value(Str_EpisodeFilter).toString());
rule.setEnabled(jsonObj.value(Str_Enabled).toBool(true));
rule.setSavePath(Path(jsonObj.value(Str_SavePath).toString()));
rule.setCategory(jsonObj.value(Str_AssignedCategory).toString());
rule.setAddPaused(toOptionalBool(jsonObj.value(Str_AddPaused)));
rule.setEnabled(jsonObj.value(S_ENABLED).toBool(true));
rule.setPriority(jsonObj.value(S_PRIORITY).toInt(0));
// TODO: The following code is deprecated. Replace with the commented one after several releases in 4.4.x.
// === BEGIN DEPRECATED CODE === //
if (jsonObj.contains(Str_ContentLayout))
{
rule.setTorrentContentLayout(jsonValueToContentLayout(jsonObj.value(Str_ContentLayout)));
}
else
{
const std::optional<bool> createSubfolder = toOptionalBool(jsonObj.value(Str_CreateSubfolder));
std::optional<BitTorrent::TorrentContentLayout> contentLayout;
if (createSubfolder.has_value())
{
contentLayout = (*createSubfolder
? BitTorrent::TorrentContentLayout::Original
: BitTorrent::TorrentContentLayout::NoSubfolder);
}
rule.setUseRegex(jsonObj.value(S_USE_REGEX).toBool(false));
rule.setMustContain(jsonObj.value(S_MUST_CONTAIN).toString());
rule.setMustNotContain(jsonObj.value(S_MUST_NOT_CONTAIN).toString());
rule.setEpisodeFilter(jsonObj.value(S_EPISODE_FILTER).toString());
rule.setLastMatch(QDateTime::fromString(jsonObj.value(S_LAST_MATCH).toString(), Qt::RFC2822Date));
rule.setIgnoreDays(jsonObj.value(S_IGNORE_DAYS).toInt());
rule.setUseSmartFilter(jsonObj.value(S_SMART_FILTER).toBool(false));
rule.setTorrentContentLayout(contentLayout);
}
// === END DEPRECATED CODE === //
// === BEGIN REPLACEMENT CODE === //
// rule.setTorrentContentLayout(jsonValueToContentLayout(jsonObj.value(Str_ContentLayout)));
// === END REPLACEMENT CODE === //
rule.setLastMatch(QDateTime::fromString(jsonObj.value(Str_LastMatch).toString(), Qt::RFC2822Date));
rule.setIgnoreDays(jsonObj.value(Str_IgnoreDays).toInt());
rule.setUseSmartFilter(jsonObj.value(Str_SmartFilter).toBool(false));
const QJsonValue feedsVal = jsonObj.value(Str_AffectedFeeds);
const QJsonValue feedsVal = jsonObj.value(S_AFFECTED_FEEDS);
QStringList feedURLs;
if (feedsVal.isString())
feedURLs << feedsVal.toString();
@@ -526,7 +506,7 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
feedURLs << urlVal.toString();
rule.setFeedURLs(feedURLs);
const QJsonValue previouslyMatchedVal = jsonObj.value(Str_PreviouslyMatched);
const QJsonValue previouslyMatchedVal = jsonObj.value(S_PREVIOUSLY_MATCHED);
QStringList previouslyMatched;
if (previouslyMatchedVal.isString())
{
@@ -539,20 +519,61 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
}
rule.setPreviouslyMatchedEpisodes(previouslyMatched);
// TODO: The following code is deprecated. Replace with the commented one after several releases in 4.6.x.
// === BEGIN DEPRECATED CODE === //
BitTorrent::AddTorrentParams addTorrentParams;
if (auto it = jsonObj.find(S_TORRENT_PARAMS); it != jsonObj.end())
{
addTorrentParams = BitTorrent::parseAddTorrentParams(it->toObject());
}
else
{
addTorrentParams.savePath = Path(jsonObj.value(S_SAVE_PATH).toString());
addTorrentParams.category = jsonObj.value(S_ASSIGNED_CATEGORY).toString();
addTorrentParams.addPaused = toOptionalBool(jsonObj.value(S_ADD_PAUSED));
if (!addTorrentParams.savePath.isEmpty())
addTorrentParams.useAutoTMM = false;
if (jsonObj.contains(S_CONTENT_LAYOUT))
{
addTorrentParams.contentLayout = jsonValueToContentLayout(jsonObj.value(S_CONTENT_LAYOUT));
}
else
{
const std::optional<bool> createSubfolder = toOptionalBool(jsonObj.value(u"createSubfolder"));
std::optional<BitTorrent::TorrentContentLayout> contentLayout;
if (createSubfolder.has_value())
{
contentLayout = (*createSubfolder
? BitTorrent::TorrentContentLayout::Original
: BitTorrent::TorrentContentLayout::NoSubfolder);
}
addTorrentParams.contentLayout = contentLayout;
}
}
rule.setAddTorrentParams(addTorrentParams);
// === END DEPRECATED CODE === //
// === BEGIN REPLACEMENT CODE === //
// rule.setAddTorrentParams(BitTorrent::parseAddTorrentParams(jsonObj.value(S_TORRENT_PARAMS).object()));
// === END REPLACEMENT CODE === //
return rule;
}
QVariantHash AutoDownloadRule::toLegacyDict() const
{
const BitTorrent::AddTorrentParams &addTorrentParams = m_dataPtr->addTorrentParams;
return {{u"name"_qs, name()},
{u"must_contain"_qs, mustContain()},
{u"must_not_contain"_qs, mustNotContain()},
{u"save_path"_qs, savePath().toString()},
{u"save_path"_qs, addTorrentParams.savePath.toString()},
{u"affected_feeds"_qs, feedURLs()},
{u"enabled"_qs, isEnabled()},
{u"category_assigned"_qs, assignedCategory()},
{u"category_assigned"_qs, addTorrentParams.category},
{u"use_regex"_qs, useRegex()},
{u"add_paused"_qs, toAddPausedLegacy(addPaused())},
{u"add_paused"_qs, toAddPausedLegacy(addTorrentParams.addPaused)},
{u"episode_filter"_qs, episodeFilter()},
{u"last_match"_qs, lastMatch()},
{u"ignore_days"_qs, ignoreDays()}};
@@ -560,7 +581,14 @@ QVariantHash AutoDownloadRule::toLegacyDict() const
AutoDownloadRule AutoDownloadRule::fromLegacyDict(const QVariantHash &dict)
{
AutoDownloadRule rule(dict.value(u"name"_qs).toString());
BitTorrent::AddTorrentParams addTorrentParams;
addTorrentParams.savePath = Path(dict.value(u"save_path"_qs).toString());
addTorrentParams.category = dict.value(u"category_assigned"_qs).toString();
addTorrentParams.addPaused = addPausedLegacyToOptionalBool(dict.value(u"add_paused"_qs).toInt());
if (!addTorrentParams.savePath.isEmpty())
addTorrentParams.useAutoTMM = false;
AutoDownloadRule rule {dict.value(u"name"_qs).toString()};
rule.setUseRegex(dict.value(u"use_regex"_qs, false).toBool());
rule.setMustContain(dict.value(u"must_contain"_qs).toString());
@@ -568,11 +596,9 @@ AutoDownloadRule AutoDownloadRule::fromLegacyDict(const QVariantHash &dict)
rule.setEpisodeFilter(dict.value(u"episode_filter"_qs).toString());
rule.setFeedURLs(dict.value(u"affected_feeds"_qs).toStringList());
rule.setEnabled(dict.value(u"enabled"_qs, false).toBool());
rule.setSavePath(Path(dict.value(u"save_path"_qs).toString()));
rule.setCategory(dict.value(u"category_assigned"_qs).toString());
rule.setAddPaused(addPausedLegacyToOptionalBool(dict.value(u"add_paused"_qs).toInt()));
rule.setLastMatch(dict.value(u"last_match"_qs).toDateTime());
rule.setIgnoreDays(dict.value(u"ignore_days"_qs).toInt());
rule.setAddTorrentParams(addTorrentParams);
return rule;
}
@@ -625,44 +651,14 @@ void AutoDownloadRule::setName(const QString &name)
m_dataPtr->name = name;
}
Path AutoDownloadRule::savePath() const
BitTorrent::AddTorrentParams AutoDownloadRule::addTorrentParams() const
{
return m_dataPtr->savePath;
return m_dataPtr->addTorrentParams;
}
void AutoDownloadRule::setSavePath(const Path &savePath)
void AutoDownloadRule::setAddTorrentParams(BitTorrent::AddTorrentParams addTorrentParams)
{
m_dataPtr->savePath = savePath;
}
std::optional<bool> AutoDownloadRule::addPaused() const
{
return m_dataPtr->addPaused;
}
void AutoDownloadRule::setAddPaused(const std::optional<bool> addPaused)
{
m_dataPtr->addPaused = addPaused;
}
std::optional<BitTorrent::TorrentContentLayout> AutoDownloadRule::torrentContentLayout() const
{
return m_dataPtr->contentLayout;
}
void AutoDownloadRule::setTorrentContentLayout(const std::optional<BitTorrent::TorrentContentLayout> contentLayout)
{
m_dataPtr->contentLayout = contentLayout;
}
QString AutoDownloadRule::assignedCategory() const
{
return m_dataPtr->category;
}
void AutoDownloadRule::setCategory(const QString &category)
{
m_dataPtr->category = category;
m_dataPtr->addTorrentParams = std::move(addTorrentParams);
}
bool AutoDownloadRule::isEnabled() const
@@ -675,6 +671,16 @@ void AutoDownloadRule::setEnabled(const bool enable)
m_dataPtr->enabled = enable;
}
int AutoDownloadRule::priority() const
{
return m_dataPtr->priority;
}
void AutoDownloadRule::setPriority(const int value)
{
m_dataPtr->priority = value;
}
QDateTime AutoDownloadRule::lastMatch() const
{
return m_dataPtr->lastMatch;

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2017-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -35,7 +35,7 @@
#include <QVariant>
#include "base/global.h"
#include "base/bittorrent/torrentcontentlayout.h"
#include "base/bittorrent/addtorrentparams.h"
#include "base/pathfwd.h"
class QDateTime;
@@ -49,7 +49,7 @@ namespace RSS
class AutoDownloadRule
{
public:
explicit AutoDownloadRule(const QString &name = u""_qs);
explicit AutoDownloadRule(const QString &name = {});
AutoDownloadRule(const AutoDownloadRule &other);
~AutoDownloadRule();
@@ -61,6 +61,9 @@ namespace RSS
bool isEnabled() const;
void setEnabled(bool enable);
int priority() const;
void setPriority(int value);
QString mustContain() const;
void setMustContain(const QString &tokens);
QString mustNotContain() const;
@@ -81,14 +84,8 @@ namespace RSS
QStringList previouslyMatchedEpisodes() const;
void setPreviouslyMatchedEpisodes(const QStringList &previouslyMatchedEpisodes);
Path savePath() const;
void setSavePath(const Path &savePath);
std::optional<bool> addPaused() const;
void setAddPaused(std::optional<bool> addPaused);
std::optional<BitTorrent::TorrentContentLayout> torrentContentLayout() const;
void setTorrentContentLayout(std::optional<BitTorrent::TorrentContentLayout> contentLayout);
QString assignedCategory() const;
void setCategory(const QString &category);
BitTorrent::AddTorrentParams addTorrentParams() const;
void setAddTorrentParams(BitTorrent::AddTorrentParams addTorrentParams);
bool matches(const QVariantHash &articleData) const;
bool accepts(const QVariantHash &articleData);
@@ -96,7 +93,7 @@ namespace RSS
friend bool operator==(const AutoDownloadRule &left, const AutoDownloadRule &right);
QJsonObject toJsonObject() const;
static AutoDownloadRule fromJsonObject(const QJsonObject &jsonObj, const QString &name = u""_qs);
static AutoDownloadRule fromJsonObject(const QJsonObject &jsonObj, const QString &name = {});
QVariantHash toLegacyDict() const;
static AutoDownloadRule fromLegacyDict(const QVariantHash &dict);

View File

@@ -345,7 +345,7 @@ bool Feed::addArticle(const QVariantHash &articleData)
void Feed::removeOldestArticle()
{
auto oldestArticle = m_articlesByDate.last();
auto *oldestArticle = m_articlesByDate.last();
emit articleAboutToBeRemoved(oldestArticle);
m_articles.remove(oldestArticle->guid());

View File

@@ -49,7 +49,7 @@ Folder::~Folder()
{
emit aboutToBeDestroyed(this);
for (auto item : asConst(items()))
for (auto *item : asConst(items()))
delete item;
}
@@ -127,7 +127,7 @@ void Folder::addItem(Item *item)
connect(item, &Item::articleAboutToBeRemoved, this, &Item::articleAboutToBeRemoved);
connect(item, &Item::unreadCountChanged, this, &Folder::handleItemUnreadCountChanged);
for (auto article : asConst(item->articles()))
for (auto *article : asConst(item->articles()))
emit newArticle(article);
if (item->unreadCount() > 0)
@@ -138,7 +138,7 @@ void Folder::removeItem(Item *item)
{
Q_ASSERT(m_items.contains(item));
for (auto article : asConst(item->articles()))
for (auto *article : asConst(item->articles()))
emit articleAboutToBeRemoved(article);
item->disconnect(this);

View File

@@ -46,7 +46,7 @@ namespace RSS
friend class Session;
explicit Folder(const QString &path = u""_qs);
explicit Folder(const QString &path = {});
~Folder() override;
public:

View File

@@ -45,8 +45,6 @@ Item::Item(const QString &path)
{
}
Item::~Item() {}
void Item::setPath(const QString &path)
{
if (path != m_path)

View File

@@ -76,7 +76,7 @@ namespace RSS
protected:
explicit Item(const QString &path);
~Item() override;
~Item() override = default;
virtual void cleanup() = 0;

View File

@@ -541,7 +541,7 @@ namespace
const int PARSINGRESULT_TYPEID = qRegisterMetaType<RSS::Private::ParsingResult>();
RSS::Private::Parser::Parser(const QString lastBuildDate)
RSS::Private::Parser::Parser(const QString &lastBuildDate)
{
m_result.lastBuildDate = lastBuildDate;
}
@@ -723,13 +723,16 @@ void RSS::Private::Parser::parseAtomArticle(QXmlStreamReader &xml)
: xml.attributes().value(u"href"_qs).toString());
if (link.startsWith(u"magnet:", Qt::CaseInsensitive))
{
article[Article::KeyTorrentURL] = link; // magnet link instead of a news URL
}
else
{
// Atom feeds can have relative links, work around this and
// take the stress of figuring article full URI from UI
// Assemble full URI
article[Article::KeyLink] = (m_baseUrl.isEmpty() ? link : m_baseUrl + link);
}
}
else if ((name == u"summary") || (name == u"content"))
{

View File

@@ -53,7 +53,7 @@ namespace RSS::Private
Q_DISABLE_COPY_MOVE(Parser)
public:
explicit Parser(QString lastBuildDate);
explicit Parser(const QString &lastBuildDate);
void parse(const QByteArray &feedData);
signals:

View File

@@ -45,6 +45,7 @@
#include "../profile.h"
#include "../settingsstorage.h"
#include "../utils/fs.h"
#include "../utils/io.h"
#include "rss_article.h"
#include "rss_feed.h"
#include "rss_folder.h"
@@ -100,7 +101,7 @@ Session::Session()
// Remove legacy/corrupted settings
// (at least on Windows, QSettings is case-insensitive and it can get
// confused when asked about settings that differ only in their case)
auto settingsStorage = SettingsStorage::instance();
auto *settingsStorage = SettingsStorage::instance();
settingsStorage->removeValue(u"Rss/streamList"_qs);
settingsStorage->removeValue(u"Rss/streamAlias"_qs);
settingsStorage->removeValue(u"Rss/open_folders"_qs);
@@ -140,7 +141,7 @@ nonstd::expected<void, QString> Session::addFolder(const QString &path)
if (!result)
return result.get_unexpected();
const auto destFolder = result.value();
auto *destFolder = result.value();
addItem(new Folder(path), destFolder);
store();
return {};
@@ -155,7 +156,7 @@ nonstd::expected<void, QString> Session::addFeed(const QString &url, const QStri
if (!result)
return result.get_unexpected();
const auto destFolder = result.value();
auto *destFolder = result.value();
auto *feed = new Feed(generateUID(), url, path, this);
addItem(feed, destFolder);
store();
@@ -198,7 +199,7 @@ nonstd::expected<void, QString> Session::moveItem(const QString &itemPath, const
if (itemPath.isEmpty())
return nonstd::make_unexpected(tr("Cannot move root folder."));
auto item = m_itemsByPath.value(itemPath);
auto *item = m_itemsByPath.value(itemPath);
if (!item)
return nonstd::make_unexpected(tr("Item doesn't exist: %1.").arg(itemPath));
@@ -214,11 +215,11 @@ nonstd::expected<void, QString> Session::moveItem(Item *item, const QString &des
if (!result)
return result.get_unexpected();
const auto destFolder = result.value();
auto *destFolder = result.value();
if (static_cast<Item *>(destFolder) == item)
return nonstd::make_unexpected(tr("Couldn't move folder into itself."));
auto srcFolder = static_cast<Folder *>(m_itemsByPath.value(Item::parentPath(item->path())));
auto *srcFolder = static_cast<Folder *>(m_itemsByPath.value(Item::parentPath(item->path())));
if (srcFolder != destFolder)
{
srcFolder->removeItem(item);
@@ -242,7 +243,7 @@ nonstd::expected<void, QString> Session::removeItem(const QString &itemPath)
emit itemAboutToBeRemoved(item);
item->cleanup();
auto folder = static_cast<Folder *>(m_itemsByPath.value(Item::parentPath(item->path())));
auto *folder = static_cast<Folder *>(m_itemsByPath.value(Item::parentPath(item->path())));
folder->removeItem(item);
delete item;
store();
@@ -261,33 +262,35 @@ Item *Session::itemByPath(const QString &path) const
void Session::load()
{
QFile itemsFile {(m_confFileStorage->storageDir() / Path(FEEDS_FILE_NAME)).data()};
if (!itemsFile.exists())
{
loadLegacy();
return;
}
const int fileMaxSize = 10 * 1024 * 1024;
const Path path = m_confFileStorage->storageDir() / Path(FEEDS_FILE_NAME);
if (!itemsFile.open(QFile::ReadOnly))
const auto readResult = Utils::IO::readFile(path, fileMaxSize);
if (!readResult)
{
LogMsg(tr("Couldn't read RSS session data. File: \"%1\". Error: \"%2\"")
.arg(itemsFile.fileName(), itemsFile.errorString()), Log::WARNING);
if (readResult.error().status == Utils::IO::ReadError::NotExist)
{
loadLegacy();
return;
}
LogMsg(tr("Failed to read RSS session data. %1").arg(readResult.error().message), Log::WARNING);
return;
}
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(itemsFile.readAll(), &jsonError);
const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
LogMsg(tr("Couldn't parse RSS session data. File: \"%1\". Error: \"%2\"")
.arg(itemsFile.fileName(), jsonError.errorString()), Log::WARNING);
LogMsg(tr("Failed to parse RSS session data. File: \"%1\". Error: \"%2\"")
.arg(path.toString(), jsonError.errorString()), Log::WARNING);
return;
}
if (!jsonDoc.isObject())
{
LogMsg(tr("Couldn't load RSS session data. File: \"%1\". Error: Invalid data format.")
.arg(itemsFile.fileName()), Log::WARNING);
LogMsg(tr("Failed to load RSS session data. File: \"%1\". Error: \"Invalid data format.\"")
.arg(path.toString()), Log::WARNING);
return;
}
@@ -410,7 +413,7 @@ nonstd::expected<Folder *, QString> Session::prepareItemDest(const QString &path
return nonstd::make_unexpected(tr("RSS item with given path already exists: %1.").arg(path));
const QString destFolderPath = Item::parentPath(path);
const auto destFolder = qobject_cast<Folder *>(m_itemsByPath.value(destFolderPath));
auto *destFolder = qobject_cast<Folder *>(m_itemsByPath.value(destFolderPath));
if (!destFolder)
return nonstd::make_unexpected(tr("Parent folder doesn't exist: %1.").arg(destFolderPath));
@@ -419,21 +422,21 @@ nonstd::expected<Folder *, QString> Session::prepareItemDest(const QString &path
Folder *Session::addSubfolder(const QString &name, Folder *parentFolder)
{
auto folder = new Folder(Item::joinPath(parentFolder->path(), name));
auto *folder = new Folder(Item::joinPath(parentFolder->path(), name));
addItem(folder, parentFolder);
return folder;
}
Feed *Session::addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder)
{
auto feed = new Feed(uid, url, Item::joinPath(parentFolder->path(), name), this);
auto *feed = new Feed(uid, url, Item::joinPath(parentFolder->path(), name), this);
addItem(feed, parentFolder);
return feed;
}
void Session::addItem(Item *item, Folder *destFolder)
{
if (auto feed = qobject_cast<Feed *>(item))
if (auto *feed = qobject_cast<Feed *>(item))
{
connect(feed, &Feed::titleChanged, this, &Session::handleFeedTitleChanged);
connect(feed, &Feed::iconLoaded, this, &Session::feedIconLoaded);
@@ -530,7 +533,7 @@ QThread *Session::workingThread() const
void Session::handleItemAboutToBeDestroyed(Item *item)
{
m_itemsByPath.remove(item->path());
auto feed = qobject_cast<Feed *>(item);
auto *feed = qobject_cast<Feed *>(item);
if (feed)
{
m_feedsByUID.remove(feed->uid());

View File

@@ -46,7 +46,8 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &siteUrl, const QStri
, this, &SearchDownloadHandler::downloadProcessFinished);
const QStringList params
{
(m_manager->engineLocation() / Path(u"nova2dl.py"_qs)).toString(),
Utils::ForeignApps::PYTHON_ISOLATE_MODE_FLAG,
(SearchPluginManager::engineLocation() / Path(u"nova2dl.py"_qs)).toString(),
siteUrl,
url
};

View File

@@ -73,7 +73,8 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
const QStringList params
{
(m_manager->engineLocation() / Path(u"nova2.py"_qs)).toString(),
Utils::ForeignApps::PYTHON_ISOLATE_MODE_FLAG,
(SearchPluginManager::engineLocation() / Path(u"nova2.py"_qs)).toString(),
m_usedPlugins.join(u','),
m_category
};

View File

@@ -42,9 +42,9 @@ struct SearchResult
{
QString fileName;
QString fileUrl;
qlonglong fileSize;
qlonglong nbSeeders;
qlonglong nbLeechers;
qlonglong fileSize = 0;
qlonglong nbSeeders = 0;
qlonglong nbLeechers = 0;
QString siteUrl;
QString descrLink;
};

View File

@@ -36,6 +36,7 @@
#include <QDomDocument>
#include <QDomElement>
#include <QDomNode>
#include <QFile>
#include <QPointer>
#include <QProcess>
#include <QUrl>
@@ -87,7 +88,7 @@ namespace
QPointer<SearchPluginManager> SearchPluginManager::m_instance = nullptr;
SearchPluginManager::SearchPluginManager()
: m_updateUrl(u"http://searchplugins.qbittorrent.org/nova3/engines/"_qs)
: m_updateUrl(u"https://searchplugins.qbittorrent.org/nova3/engines/"_qs)
{
Q_ASSERT(!m_instance); // only one instance is allowed
m_instance = this;
@@ -366,7 +367,7 @@ QString SearchPluginManager::categoryFullName(const QString &categoryName)
return categoryTable.value(categoryName);
}
QString SearchPluginManager::pluginFullName(const QString &pluginName)
QString SearchPluginManager::pluginFullName(const QString &pluginName) const
{
return pluginInfo(pluginName) ? pluginInfo(pluginName)->fullName : QString();
}
@@ -500,7 +501,6 @@ void SearchPluginManager::updateNova()
updateFile(Path(u"nova2.py"_qs), true);
updateFile(Path(u"nova2dl.py"_qs), true);
updateFile(Path(u"novaprinter.py"_qs), true);
updateFile(Path(u"sgmllib3.py"_qs), false);
updateFile(Path(u"socks.py"_qs), false);
}
@@ -509,11 +509,16 @@ void SearchPluginManager::update()
QProcess nova;
nova.setProcessEnvironment(QProcessEnvironment::systemEnvironment());
const QStringList params {(engineLocation() / Path(u"/nova2.py"_qs)).toString(), u"--capabilities"_qs};
const QStringList params
{
Utils::ForeignApps::PYTHON_ISOLATE_MODE_FLAG,
(engineLocation() / Path(u"/nova2.py"_qs)).toString(),
u"--capabilities"_qs
};
nova.start(Utils::ForeignApps::pythonInfo().executableName, params, QIODevice::ReadOnly);
nova.waitForFinished();
const auto capabilities = QString::fromUtf8(nova.readAll());
const auto capabilities = QString::fromUtf8(nova.readAllStandardOutput());
QDomDocument xmlDoc;
if (!xmlDoc.setContent(capabilities))
{
@@ -609,7 +614,7 @@ void SearchPluginManager::parseVersionInfo(const QByteArray &info)
}
}
bool SearchPluginManager::isUpdateNeeded(const QString &pluginName, const PluginVersion newVersion) const
bool SearchPluginManager::isUpdateNeeded(const QString &pluginName, const PluginVersion &newVersion) const
{
const PluginInfo *plugin = pluginInfo(pluginName);
if (!plugin) return true;
@@ -625,13 +630,15 @@ Path SearchPluginManager::pluginPath(const QString &name)
PluginVersion SearchPluginManager::getPluginVersion(const Path &filePath)
{
const int lineMaxLength = 16;
QFile pluginFile {filePath.data()};
if (!pluginFile.open(QIODevice::ReadOnly | QIODevice::Text))
return {};
while (!pluginFile.atEnd())
{
const auto line = QString::fromUtf8(pluginFile.readLine()).remove(u' ');
const auto line = QString::fromUtf8(pluginFile.readLine(lineMaxLength)).remove(u' ');
if (!line.startsWith(u"#VERSION:", Qt::CaseInsensitive)) continue;
const QString versionStr = line.mid(9);

View File

@@ -52,7 +52,7 @@ struct PluginInfo
QString url;
QStringList supportedCategories;
Path iconPath;
bool enabled;
bool enabled = false;
};
class SearchDownloadHandler;
@@ -80,7 +80,7 @@ public:
void updatePlugin(const QString &name);
void installPlugin(const QString &source);
bool uninstallPlugin(const QString &name);
static void updateIconPath(PluginInfo *const plugin);
static void updateIconPath(PluginInfo *plugin);
void checkForUpdates();
SearchHandler *startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins);
@@ -88,7 +88,7 @@ public:
static PluginVersion getPluginVersion(const Path &filePath);
static QString categoryFullName(const QString &categoryName);
QString pluginFullName(const QString &pluginName);
QString pluginFullName(const QString &pluginName) const;
static Path pluginsLocation();
static Path engineLocation();
@@ -109,7 +109,7 @@ private:
void updateNova();
void parseVersionInfo(const QByteArray &info);
void installPlugin_impl(const QString &name, const Path &path);
bool isUpdateNeeded(const QString &pluginName, PluginVersion newVersion) const;
bool isUpdateNeeded(const QString &pluginName, const PluginVersion &newVersion) const;
void versionInfoDownloadFinished(const Net::DownloadResult &result);
void pluginDownloadFinished(const Net::DownloadResult &result);

View File

@@ -59,7 +59,6 @@ FileGuard::~FileGuard()
TorrentFileGuard::TorrentFileGuard(const Path &path, const TorrentFileGuard::AutoDeleteMode mode)
: FileGuard {mode != Never ? path : Path()}
, m_mode {mode}
, m_wasAdded {false}
{
}

View File

@@ -36,10 +36,8 @@
#include <QDirIterator>
#include <QFile>
#include <QFileSystemWatcher>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QSet>
#include <QThread>
#include <QTimer>
@@ -70,124 +68,12 @@ const QString CONF_FILE_NAME = u"watched_folders.json"_qs;
const QString OPTION_ADDTORRENTPARAMS = u"add_torrent_params"_qs;
const QString OPTION_RECURSIVE = u"recursive"_qs;
const QString PARAM_CATEGORY = u"category"_qs;
const QString PARAM_TAGS = u"tags"_qs;
const QString PARAM_SAVEPATH = u"save_path"_qs;
const QString PARAM_USEDOWNLOADPATH = u"use_download_path"_qs;
const QString PARAM_DOWNLOADPATH = u"download_path"_qs;
const QString PARAM_OPERATINGMODE = u"operating_mode"_qs;
const QString PARAM_QUEUETOP = u"add_to_top_of_queue"_qs;
const QString PARAM_STOPPED = u"stopped"_qs;
const QString PARAM_SKIPCHECKING = u"skip_checking"_qs;
const QString PARAM_CONTENTLAYOUT = u"content_layout"_qs;
const QString PARAM_AUTOTMM = u"use_auto_tmm"_qs;
const QString PARAM_UPLOADLIMIT = u"upload_limit"_qs;
const QString PARAM_DOWNLOADLIMIT = u"download_limit"_qs;
const QString PARAM_SEEDINGTIMELIMIT = u"seeding_time_limit"_qs;
const QString PARAM_RATIOLIMIT = u"ratio_limit"_qs;
namespace
{
TagSet parseTagSet(const QJsonArray &jsonArr)
{
TagSet tags;
for (const QJsonValue &jsonVal : jsonArr)
tags.insert(jsonVal.toString());
return tags;
}
QJsonArray serializeTagSet(const TagSet &tags)
{
QJsonArray arr;
for (const QString &tag : tags)
arr.append(tag);
return arr;
}
std::optional<bool> getOptionalBool(const QJsonObject &jsonObj, const QString &key)
{
const QJsonValue jsonVal = jsonObj.value(key);
if (jsonVal.isUndefined() || jsonVal.isNull())
return std::nullopt;
return jsonVal.toBool();
}
template <typename Enum>
std::optional<Enum> getOptionalEnum(const QJsonObject &jsonObj, const QString &key)
{
const QJsonValue jsonVal = jsonObj.value(key);
if (jsonVal.isUndefined() || jsonVal.isNull())
return std::nullopt;
return Utils::String::toEnum<Enum>(jsonVal.toString(), {});
}
template <typename Enum>
Enum getEnum(const QJsonObject &jsonObj, const QString &key)
{
const QJsonValue jsonVal = jsonObj.value(key);
return Utils::String::toEnum<Enum>(jsonVal.toString(), {});
}
BitTorrent::AddTorrentParams parseAddTorrentParams(const QJsonObject &jsonObj)
{
BitTorrent::AddTorrentParams params;
params.category = jsonObj.value(PARAM_CATEGORY).toString();
params.tags = parseTagSet(jsonObj.value(PARAM_TAGS).toArray());
params.savePath = Path(jsonObj.value(PARAM_SAVEPATH).toString());
params.useDownloadPath = getOptionalBool(jsonObj, PARAM_USEDOWNLOADPATH);
params.downloadPath = Path(jsonObj.value(PARAM_DOWNLOADPATH).toString());
params.addForced = (getEnum<BitTorrent::TorrentOperatingMode>(jsonObj, PARAM_OPERATINGMODE) == BitTorrent::TorrentOperatingMode::Forced);
params.addToQueueTop = getOptionalBool(jsonObj, PARAM_QUEUETOP);
params.addPaused = getOptionalBool(jsonObj, PARAM_STOPPED);
params.skipChecking = jsonObj.value(PARAM_SKIPCHECKING).toBool();
params.contentLayout = getOptionalEnum<BitTorrent::TorrentContentLayout>(jsonObj, PARAM_CONTENTLAYOUT);
params.useAutoTMM = getOptionalBool(jsonObj, PARAM_AUTOTMM);
params.uploadLimit = jsonObj.value(PARAM_UPLOADLIMIT).toInt(-1);
params.downloadLimit = jsonObj.value(PARAM_DOWNLOADLIMIT).toInt(-1);
params.seedingTimeLimit = jsonObj.value(PARAM_SEEDINGTIMELIMIT).toInt(BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME);
params.ratioLimit = jsonObj.value(PARAM_RATIOLIMIT).toDouble(BitTorrent::Torrent::USE_GLOBAL_RATIO);
return params;
}
QJsonObject serializeAddTorrentParams(const BitTorrent::AddTorrentParams &params)
{
QJsonObject jsonObj {
{PARAM_CATEGORY, params.category},
{PARAM_TAGS, serializeTagSet(params.tags)},
{PARAM_SAVEPATH, params.savePath.data()},
{PARAM_DOWNLOADPATH, params.downloadPath.data()},
{PARAM_OPERATINGMODE, Utils::String::fromEnum(params.addForced
? BitTorrent::TorrentOperatingMode::Forced : BitTorrent::TorrentOperatingMode::AutoManaged)},
{PARAM_SKIPCHECKING, params.skipChecking},
{PARAM_UPLOADLIMIT, params.uploadLimit},
{PARAM_DOWNLOADLIMIT, params.downloadLimit},
{PARAM_SEEDINGTIMELIMIT, params.seedingTimeLimit},
{PARAM_RATIOLIMIT, params.ratioLimit}
};
if (params.addToQueueTop)
jsonObj[PARAM_QUEUETOP] = *params.addToQueueTop;
if (params.addPaused)
jsonObj[PARAM_STOPPED] = *params.addPaused;
if (params.contentLayout)
jsonObj[PARAM_CONTENTLAYOUT] = Utils::String::fromEnum(*params.contentLayout);
if (params.useAutoTMM)
jsonObj[PARAM_AUTOTMM] = *params.useAutoTMM;
if (params.useDownloadPath)
jsonObj[PARAM_USEDOWNLOADPATH] = *params.useDownloadPath;
return jsonObj;
}
TorrentFilesWatcher::WatchedFolderOptions parseWatchedFolderOptions(const QJsonObject &jsonObj)
{
TorrentFilesWatcher::WatchedFolderOptions options;
options.addTorrentParams = parseAddTorrentParams(jsonObj.value(OPTION_ADDTORRENTPARAMS).toObject());
options.addTorrentParams = BitTorrent::parseAddTorrentParams(jsonObj.value(OPTION_ADDTORRENTPARAMS).toObject());
options.recursive = jsonObj.value(OPTION_RECURSIVE).toBool();
return options;
@@ -195,10 +81,8 @@ namespace
QJsonObject serializeWatchedFolderOptions(const TorrentFilesWatcher::WatchedFolderOptions &options)
{
return {
{OPTION_ADDTORRENTPARAMS, serializeAddTorrentParams(options.addTorrentParams)},
{OPTION_RECURSIVE, options.recursive}
};
return {{OPTION_ADDTORRENTPARAMS, BitTorrent::serializeAddTorrentParams(options.addTorrentParams)},
{OPTION_RECURSIVE, options.recursive}};
}
}
@@ -293,33 +177,35 @@ void TorrentFilesWatcher::initWorker()
void TorrentFilesWatcher::load()
{
QFile confFile {(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FILE_NAME)).data()};
if (!confFile.exists())
{
loadLegacy();
return;
}
const int fileMaxSize = 10 * 1024 * 1024;
const Path path = specialFolderLocation(SpecialFolder::Config) / Path(CONF_FILE_NAME);
if (!confFile.open(QFile::ReadOnly))
const auto readResult = Utils::IO::readFile(path, fileMaxSize);
if (!readResult)
{
LogMsg(tr("Couldn't load Watched Folders configuration from %1. Error: %2")
.arg(confFile.fileName(), confFile.errorString()), Log::WARNING);
if (readResult.error().status == Utils::IO::ReadError::NotExist)
{
loadLegacy();
return;
}
LogMsg(tr("Failed to load Watched Folders configuration. %1").arg(readResult.error().message), Log::WARNING);
return;
}
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll(), &jsonError);
const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
LogMsg(tr("Couldn't parse Watched Folders configuration from %1. Error: %2")
.arg(confFile.fileName(), jsonError.errorString()), Log::WARNING);
LogMsg(tr("Failed to parse Watched Folders configuration from %1. Error: \"%2\"")
.arg(path.toString(), jsonError.errorString()), Log::WARNING);
return;
}
if (!jsonDoc.isObject())
{
LogMsg(tr("Couldn't load Watched Folders configuration from %1. Invalid data format.")
.arg(confFile.fileName()), Log::WARNING);
LogMsg(tr("Failed to load Watched Folders configuration from %1. Error: \"Invalid data format.\"")
.arg(path.toString()), Log::WARNING);
return;
}
@@ -542,17 +428,26 @@ void TorrentFilesWatcher::Worker::processFolder(const Path &path, const Path &wa
if (filePath.hasExtension(u".magnet"_qs))
{
const int fileMaxSize = 100 * 1024 * 1024;
QFile file {filePath.data()};
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
while (!file.atEnd())
if (file.size() <= fileMaxSize)
{
const auto line = QString::fromLatin1(file.readLine()).trimmed();
emit magnetFound(BitTorrent::MagnetUri(line), addTorrentParams);
}
while (!file.atEnd())
{
const auto line = QString::fromLatin1(file.readLine()).trimmed();
emit magnetFound(BitTorrent::MagnetUri(line), addTorrentParams);
}
file.close();
Utils::Fs::removeFile(filePath);
file.close();
Utils::Fs::removeFile(filePath);
}
else
{
LogMsg(tr("Magnet file too big. File: %1").arg(file.errorString()));
}
}
else
{

View File

@@ -62,8 +62,7 @@ TorrentFilter::TorrentFilter(const Type type, const std::optional<TorrentIDSet>
TorrentFilter::TorrentFilter(const QString &filter, const std::optional<TorrentIDSet> &idSet
, const std::optional<QString> &category, const std::optional<QString> &tag)
: m_type(All)
, m_category(category)
: m_category(category)
, m_tag(tag)
, m_idSet(idSet)
{

View File

@@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018 Mike Tzou (Chocobo1)
*
* This program is free software; you can redistribute it and/or
@@ -68,3 +69,45 @@ const QByteArray Utils::ByteArray::midView(const QByteArray &in, const int pos,
: len;
return QByteArray::fromRawData(in.constData() + pos, validLen);
}
QByteArray Utils::ByteArray::toBase32(const QByteArray &in)
{
const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
const char padchar = '=';
const qsizetype inSize = in.size();
auto tmp = QByteArray((inSize + 4) / 5 * 8, Qt::Uninitialized);
qsizetype inIndex = 0;
char *out = tmp.data();
while (inIndex < inSize)
{
// encode 5 bytes at a time
qsizetype inPadLen = 5;
int64_t chunk = 0;
while (inPadLen > 0)
{
chunk |= static_cast<int64_t>(static_cast<uchar>(in.data()[inIndex++])) << (--inPadLen * 8);
if (inIndex == inSize)
break;
}
const int outCharCounts[] = {8, 7, 5, 4, 2};
for (int i = 7; i >= 0; --i)
{
if (i >= (8 - outCharCounts[inPadLen]))
{
const int shift = (i * 5);
const int64_t mask = static_cast<int64_t>(0x1f) << shift;
const int charIndex = (chunk & mask) >> shift;
*out++ = alphabet[charIndex];
}
else
{
*out++ = padchar;
}
}
}
return tmp;
}

View File

@@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018 Mike Tzou (Chocobo1)
*
* This program is free software; you can redistribute it and/or
@@ -36,9 +37,11 @@ class QByteArray;
namespace Utils::ByteArray
{
// Mimic QStringView(in).split(sep, behavior)
QVector<QByteArray> splitToViews(const QByteArray &in, const QByteArray &sep, const Qt::SplitBehavior behavior = Qt::KeepEmptyParts);
QVector<QByteArray> splitToViews(const QByteArray &in, const QByteArray &sep, Qt::SplitBehavior behavior = Qt::KeepEmptyParts);
// Mimic QByteArray::mid(pos, len) but instead of returning a full-copy,
// we only return a partial view
const QByteArray midView(const QByteArray &in, int pos, int len = -1);
QByteArray toBase32(const QByteArray &in);
}

View File

@@ -248,7 +248,7 @@ bool Utils::ForeignApps::PythonInfo::isValid() const
bool Utils::ForeignApps::PythonInfo::isSupportedVersion() const
{
return (version >= Version {3, 5, 0});
return (version >= Version {3, 7, 0});
}
PythonInfo Utils::ForeignApps::pythonInfo()

Some files were not shown because too many files have changed in this diff Show More