mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-27 02:38:04 -06:00
Compare commits
264 Commits
v4_5_x
...
release-4.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbe93f0c47 | ||
|
|
11945eef3f | ||
|
|
a35dbc6df7 | ||
|
|
3fb4e4d293 | ||
|
|
f5a4065101 | ||
|
|
ba93d55a6d | ||
|
|
a59301712e | ||
|
|
b406d669b3 | ||
|
|
4ef8f39f23 | ||
|
|
34802362ad | ||
|
|
c10f1f0ad2 | ||
|
|
58ae98026b | ||
|
|
32a55551fe | ||
|
|
7880fe8440 | ||
|
|
bb959bda8c | ||
|
|
d629c77184 | ||
|
|
b953d223e4 | ||
|
|
6fa53b5ed8 | ||
|
|
c777ed3299 | ||
|
|
341b2f345a | ||
|
|
905f141657 | ||
|
|
0a87bb368f | ||
|
|
93a1e58554 | ||
|
|
0cc29f1851 | ||
|
|
81daad92ec | ||
|
|
41be7e9bbe | ||
|
|
179a61d75e | ||
|
|
73134d5f4d | ||
|
|
29c05ed3e8 | ||
|
|
e375f3ee0b | ||
|
|
b185153254 | ||
|
|
e7e5c38384 | ||
|
|
9a00839a75 | ||
|
|
79e85d01fa | ||
|
|
e408973ee6 | ||
|
|
8c9b6e2f2d | ||
|
|
5b43782f58 | ||
|
|
2059825597 | ||
|
|
e1be46820b | ||
|
|
8219b1f695 | ||
|
|
3fbe380582 | ||
|
|
5f00d42a49 | ||
|
|
15de7aed9a | ||
|
|
5c38cc00d9 | ||
|
|
5a1dcbae9c | ||
|
|
7c6a852f85 | ||
|
|
147b22ddd3 | ||
|
|
d83b2a6131 | ||
|
|
821e946bbe | ||
|
|
634eb4a183 | ||
|
|
758ea7edca | ||
|
|
1bd499565e | ||
|
|
be9ec5a329 | ||
|
|
df895cb2a7 | ||
|
|
3b72859980 | ||
|
|
69df85f564 | ||
|
|
1f1da32371 | ||
|
|
cddf8c199c | ||
|
|
bbd5ed1142 | ||
|
|
0f033ec9c8 | ||
|
|
7397c80837 | ||
|
|
51132c817b | ||
|
|
1fe006d16f | ||
|
|
bd31eddb94 | ||
|
|
0defb7d79d | ||
|
|
1e400df324 | ||
|
|
9ea48539b4 | ||
|
|
d63e0ad78f | ||
|
|
eaee38a19e | ||
|
|
b3e9c46eff | ||
|
|
5dcc14153f | ||
|
|
4a66d705b8 | ||
|
|
9d7fcea5d6 | ||
|
|
b8cd614775 | ||
|
|
a9ab2d9b9e | ||
|
|
cecf2d28e6 | ||
|
|
a01f1014b9 | ||
|
|
77411760a0 | ||
|
|
0dcb65bb7c | ||
|
|
d40be79c69 | ||
|
|
b55d4b1733 | ||
|
|
40e28930a4 | ||
|
|
5a3579a3f9 | ||
|
|
9de8abadb6 | ||
|
|
05c5cdab96 | ||
|
|
1e1c1725ab | ||
|
|
b5c57af869 | ||
|
|
eb875ac8c1 | ||
|
|
a36358d7d0 | ||
|
|
679e592a5c | ||
|
|
b922441a7c | ||
|
|
941c587c68 | ||
|
|
77bd09bb8b | ||
|
|
8bcac1bed2 | ||
|
|
cdded6cef7 | ||
|
|
8cbe4a571c | ||
|
|
ee9d2173e0 | ||
|
|
a450a7c6e1 | ||
|
|
d41a77841d | ||
|
|
ae06daba6a | ||
|
|
77aa85fbd3 | ||
|
|
989b1e6c2c | ||
|
|
9ef23d524d | ||
|
|
f16e903623 | ||
|
|
0bb0829a9a | ||
|
|
fa30b70453 | ||
|
|
e4f90730b2 | ||
|
|
6fd522472c | ||
|
|
0f32de9d8c | ||
|
|
f630d84858 | ||
|
|
ee6f699b48 | ||
|
|
ce9bdaef5c | ||
|
|
37c04fdeed | ||
|
|
c51aa2d573 | ||
|
|
b922e1ae73 | ||
|
|
dd48f62d66 | ||
|
|
f5b5312cf0 | ||
|
|
58a654a70f | ||
|
|
ff0f3b4975 | ||
|
|
8df68ac878 | ||
|
|
2f9b313287 | ||
|
|
cbf591a8b5 | ||
|
|
96da685e5d | ||
|
|
6ac14d0c57 | ||
|
|
4745a40f0b | ||
|
|
8993d87b32 | ||
|
|
8df80b67f9 | ||
|
|
466314675c | ||
|
|
d75fd3fcde | ||
|
|
7ae83df5a5 | ||
|
|
19f55512c1 | ||
|
|
6e25db444e | ||
|
|
d2c4b69f47 | ||
|
|
4170b4e21b | ||
|
|
9fb9ca47f6 | ||
|
|
917190d936 | ||
|
|
1e913f46f0 | ||
|
|
4c0ebc0e0f | ||
|
|
1b0f5b8567 | ||
|
|
6a4bb5c1b7 | ||
|
|
3fea9f5a33 | ||
|
|
7600f59f3a | ||
|
|
915121a0dd | ||
|
|
1be5b3abd8 | ||
|
|
e37661d53a | ||
|
|
d06f78dbbd | ||
|
|
5d4766edbe | ||
|
|
72ac92ec68 | ||
|
|
22ea508ff6 | ||
|
|
b2213ded6d | ||
|
|
1ea2fe5b8d | ||
|
|
7227d2b2b2 | ||
|
|
0dcbf9f698 | ||
|
|
09e58df03f | ||
|
|
d256db5072 | ||
|
|
10153f0063 | ||
|
|
c6a1b977b3 | ||
|
|
870bb42e4f | ||
|
|
b61c7b7220 | ||
|
|
c58fb92365 | ||
|
|
5e952a561b | ||
|
|
ca72360b6f | ||
|
|
630b4ed3b9 | ||
|
|
cba9680ef9 | ||
|
|
2310dcd136 | ||
|
|
ee00a80796 | ||
|
|
051bac5e59 | ||
|
|
771c58d000 | ||
|
|
53cec6db09 | ||
|
|
43e059801e | ||
|
|
ce35a06ec3 | ||
|
|
32e4371208 | ||
|
|
0d376e7fd6 | ||
|
|
2b20d5b260 | ||
|
|
719e4afd8c | ||
|
|
9cdf660ddb | ||
|
|
5dbccf3473 | ||
|
|
8db2d04dbb | ||
|
|
209850064a | ||
|
|
e628b7d527 | ||
|
|
61dbb211c0 | ||
|
|
71f4a5667c | ||
|
|
b33dc7d831 | ||
|
|
c5a4a0db2c | ||
|
|
b9e045e80b | ||
|
|
cfd0c5433e | ||
|
|
ebad387c1a | ||
|
|
3f39bd9f35 | ||
|
|
f8236eb397 | ||
|
|
23a56c95e3 | ||
|
|
6f8aa07a10 | ||
|
|
594f9e8632 | ||
|
|
aeae065007 | ||
|
|
b12fdcf018 | ||
|
|
84fabf14c8 | ||
|
|
0ec47db9cd | ||
|
|
3cf0004665 | ||
|
|
40258f6a2f | ||
|
|
b335114219 | ||
|
|
998b08f5d8 | ||
|
|
991c30943a | ||
|
|
ad2be39c33 | ||
|
|
c3936cd4b6 | ||
|
|
d2e595aac3 | ||
|
|
b8aa9e5609 | ||
|
|
2109c4e1ae | ||
|
|
ac3ad17a9e | ||
|
|
31c7306bd2 | ||
|
|
4741aab7a3 | ||
|
|
679e3b8bea | ||
|
|
25ea0d274b | ||
|
|
12b58be8c2 | ||
|
|
e4f1485c82 | ||
|
|
1b2ff0f6f8 | ||
|
|
a31755bbc8 | ||
|
|
311e0f21eb | ||
|
|
b86366f243 | ||
|
|
58d1c80b12 | ||
|
|
182915f801 | ||
|
|
50c08e55cd | ||
|
|
4307a09621 | ||
|
|
d531d6d221 | ||
|
|
4cf94a6fa0 | ||
|
|
4cb60f4870 | ||
|
|
d82edb2838 | ||
|
|
c91eefe469 | ||
|
|
327affa340 | ||
|
|
4e7c2589e4 | ||
|
|
17ce07230d | ||
|
|
fda6c9a3d9 | ||
|
|
92af2922c7 | ||
|
|
1cee69da6c | ||
|
|
f54b66eb75 | ||
|
|
3563bad5fc | ||
|
|
1f3f96f7aa | ||
|
|
7022adb89b | ||
|
|
bac57de5f5 | ||
|
|
bdd56a52d3 | ||
|
|
dcdbd02102 | ||
|
|
b68c4e2106 | ||
|
|
67cb75e9d3 | ||
|
|
f9eefe866c | ||
|
|
c636618cf3 | ||
|
|
0f82c16936 | ||
|
|
d328eeb5be | ||
|
|
d90ea0d3be | ||
|
|
e7ece66717 | ||
|
|
6c9c40fd7c | ||
|
|
529c1ec9f4 | ||
|
|
93429840c8 | ||
|
|
6aee7f95b7 | ||
|
|
0b70ccf9e9 | ||
|
|
da586828be | ||
|
|
2e4431f0b8 | ||
|
|
162273da47 | ||
|
|
22fb9797c4 | ||
|
|
f6735401f4 | ||
|
|
06c4c58613 | ||
|
|
c80238d66f | ||
|
|
6a560016dd | ||
|
|
99b7663fa9 | ||
|
|
be0f34a69e | ||
|
|
5f2d807861 | ||
|
|
106adf135c |
82
.clang-tidy
Normal file
82
.clang-tidy
Normal 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: "*"
|
||||
2
.github/workflows/ci_file_health.yaml
vendored
2
.github/workflows/ci_file_health.yaml
vendored
@@ -2,6 +2,8 @@ name: CI - File health
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.head_ref != '' }}
|
||||
|
||||
26
.github/workflows/ci_macos.yaml
vendored
26
.github/workflows/ci_macos.yaml
vendored
@@ -2,6 +2,9 @@ name: CI - macOS
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.head_ref != '' }}
|
||||
@@ -14,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"
|
||||
@@ -31,6 +34,9 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
export \
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 \
|
||||
HOMEBREW_NO_INSTALL_CLEANUP=1
|
||||
brew update > /dev/null
|
||||
brew install \
|
||||
cmake ninja \
|
||||
@@ -46,7 +52,7 @@ jobs:
|
||||
curl \
|
||||
-L \
|
||||
-o "${{ runner.temp }}/boost.tar.bz2" \
|
||||
"https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_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 }}"
|
||||
|
||||
@@ -78,13 +84,14 @@ jobs:
|
||||
sudo cmake --install build
|
||||
|
||||
- name: Build qBittorrent (Qt5)
|
||||
if: ${{ startsWith(matrix.qt_version, 5) }}
|
||||
if: startsWith(matrix.qt_version, 5)
|
||||
run: |
|
||||
CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \
|
||||
LDFLAGS="$LDFLAGS -gz" \
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations" \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
|
||||
@@ -96,13 +103,14 @@ jobs:
|
||||
cmake --build build --target check
|
||||
|
||||
- name: Build qBittorrent (Qt6)
|
||||
if: ${{ startsWith(matrix.qt_version, 6) }}
|
||||
if: startsWith(matrix.qt_version, 6)
|
||||
run: |
|
||||
CXXFLAGS="$CXXFLAGS -Wno-gnu-zero-variadic-macro-arguments -Werror -Wno-error=deprecated-declarations" \
|
||||
LDFLAGS="$LDFLAGS -gz" \
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS="-Wno-gnu-zero-variadic-macro-arguments -Werror -Wno-error=deprecated-declarations" \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
|
||||
|
||||
62
.github/workflows/ci_ubuntu.yaml
vendored
62
.github/workflows/ci_ubuntu.yaml
vendored
@@ -2,6 +2,10 @@ name: CI - Ubuntu
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
security-events: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.head_ref != '' }}
|
||||
@@ -14,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:
|
||||
@@ -30,7 +34,7 @@ jobs:
|
||||
sudo apt update
|
||||
sudo apt install \
|
||||
build-essential cmake ninja-build pkg-config \
|
||||
libboost-dev libssl-dev zlib1g-dev
|
||||
libboost-dev libssl-dev libxkbcommon-x11-dev zlib1g-dev
|
||||
|
||||
- name: Setup ccache
|
||||
uses: Chocobo1/setup-ccache-action@v1
|
||||
@@ -56,20 +60,30 @@ jobs:
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-Ddeprecated-functions=OFF
|
||||
cmake --build build
|
||||
sudo cmake --install build
|
||||
|
||||
# to avoid scanning 3rdparty codebases, initialize it just before building qbt
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
if: startsWith(matrix.libt_version, 2) && (matrix.qbt_gui == 'GUI=ON') && startsWith(matrix.qt_version, 6)
|
||||
with:
|
||||
config-file: ./.github/workflows/helper/codeql/cpp.yaml
|
||||
languages: cpp
|
||||
|
||||
- name: Build qBittorrent (Qt5)
|
||||
if: ${{ startsWith(matrix.qt_version, 5) }}
|
||||
if: startsWith(matrix.qt_version, 5)
|
||||
run: |
|
||||
CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \
|
||||
LDFLAGS="$LDFLAGS -gz" \
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations" \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DCMAKE_INSTALL_PREFIX="/usr" \
|
||||
-DTESTING=ON \
|
||||
@@ -81,13 +95,14 @@ jobs:
|
||||
DESTDIR="qbittorrent" cmake --install build
|
||||
|
||||
- name: Build qBittorrent (Qt6)
|
||||
if: ${{ startsWith(matrix.qt_version, 6) }}
|
||||
if: startsWith(matrix.qt_version, 6)
|
||||
run: |
|
||||
CXXFLAGS="$CXXFLAGS -Werror" \
|
||||
LDFLAGS="$LDFLAGS -gz" \
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_FLAGS="-Werror" \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DCMAKE_INSTALL_PREFIX="/usr" \
|
||||
-DQT6=ON \
|
||||
@@ -99,6 +114,10 @@ jobs:
|
||||
cmake --build build --target check
|
||||
DESTDIR="qbittorrent" cmake --install build
|
||||
|
||||
- name: Run CodeQL analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
if: startsWith(matrix.libt_version, 2) && (matrix.qbt_gui == 'GUI=ON') && startsWith(matrix.qt_version, 6)
|
||||
|
||||
- name: Prepare build artifacts
|
||||
run: |
|
||||
mkdir upload
|
||||
@@ -107,8 +126,35 @@ jobs:
|
||||
mkdir upload/cmake/libtorrent
|
||||
cp libtorrent/build/compile_commands.json upload/cmake/libtorrent
|
||||
|
||||
- name: 'AppImage: Prepare env'
|
||||
run: |
|
||||
sudo apt install libfuse2
|
||||
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||
wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
|
||||
wget https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage
|
||||
chmod +x linuxdeploy-x86_64.AppImage
|
||||
chmod +x linuxdeploy-plugin-qt-x86_64.AppImage
|
||||
chmod +x linuxdeploy-plugin-appimage-x86_64.AppImage
|
||||
|
||||
- name: 'AppImage: Prepare nox'
|
||||
if: matrix.qbt_gui == 'GUI=OFF'
|
||||
run: |
|
||||
mkdir -p qbittorrent/usr/share/icons/hicolor/scalable/apps/
|
||||
mkdir -p qbittorrent/usr/share/applications/
|
||||
cp dist/unix/menuicons/scalable/apps/qbittorrent.svg qbittorrent/usr/share/icons/hicolor/scalable/apps/qbittorrent.svg
|
||||
cp .github/workflows/helper/appimage/org.qbittorrent.qBittorrent.desktop qbittorrent/usr/share/applications/org.qbittorrent.qBittorrent.desktop
|
||||
|
||||
- name: 'AppImage: Package'
|
||||
run: |
|
||||
./linuxdeploy-x86_64.AppImage --appdir=qbittorrent --plugin qt
|
||||
rm qbittorrent/apprun-hooks/*
|
||||
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
|
||||
NO_APPSTREAM=1 \
|
||||
OUTPUT=upload/qbittorrent-CI_Ubuntu_x86_64.AppImage \
|
||||
./linuxdeploy-x86_64.AppImage --appdir=qbittorrent --output appimage
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: build-info_ubuntu-x64_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
|
||||
name: qBittorrent-CI_Ubuntu-x64_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
|
||||
path: upload
|
||||
|
||||
12
.github/workflows/ci_webui.yaml
vendored
12
.github/workflows/ci_webui.yaml
vendored
@@ -2,6 +2,9 @@ name: CI - WebUI
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.head_ref != '' }}
|
||||
@@ -34,3 +37,12 @@ jobs:
|
||||
run: |
|
||||
npm run format
|
||||
git diff --exit-code
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
config-file: ./.github/workflows/helper/codeql/js.yaml
|
||||
languages: javascript
|
||||
|
||||
- name: Run CodeQL analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
11
.github/workflows/ci_windows.yaml
vendored
11
.github/workflows/ci_windows.yaml
vendored
@@ -2,6 +2,9 @@ name: CI - Windows
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: ${{ github.head_ref != '' }}
|
||||
@@ -14,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"
|
||||
@@ -67,7 +70,7 @@ jobs:
|
||||
- name: Install boost
|
||||
run: |
|
||||
aria2c `
|
||||
"https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_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 }}/.."
|
||||
@@ -76,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
|
||||
@@ -149,6 +152,8 @@ jobs:
|
||||
copy "${{ env.Qt6_DIR }}/plugins/sqldrivers/qsqlite.dll" upload/plugins/sqldrivers
|
||||
mkdir upload/plugins/styles
|
||||
copy "${{ env.Qt6_DIR }}/plugins/styles/qwindowsvistastyle.dll" upload/plugins/styles
|
||||
mkdir upload/plugins/tls
|
||||
copy "${{ env.Qt6_DIR }}/plugins/tls/qschannelbackend.dll" upload/plugins/tls
|
||||
# cmake additionals
|
||||
mkdir upload/cmake
|
||||
copy build/compile_commands.json upload/cmake
|
||||
|
||||
@@ -5,6 +5,8 @@ on:
|
||||
- cron: '0 0 1 * *' # Monthly (1st day of month at midnight)
|
||||
workflow_dispatch: # Mainly for testing. Don't forget the Coverity usage limits.
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
coverity_scan:
|
||||
name: Scan
|
||||
@@ -24,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
|
||||
13
.github/workflows/helper/appimage/export_vars.sh
vendored
Executable file
13
.github/workflows/helper/appimage/export_vars.sh
vendored
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/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
|
||||
XDG_DATA_DIRS="/usr/local/share/:/usr/share/"
|
||||
fi
|
||||
|
||||
export XDG_DATA_DIRS="${root_dir}/usr/share:${XDG_DATA_DIRS}"
|
||||
6
.github/workflows/helper/appimage/org.qbittorrent.qBittorrent.desktop
vendored
Normal file
6
.github/workflows/helper/appimage/org.qbittorrent.qBittorrent.desktop
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Name=qBittorrent
|
||||
Exec=qbittorrent-nox %U
|
||||
Icon=qbittorrent
|
||||
Type=Application
|
||||
Categories=Network
|
||||
14
.github/workflows/helper/codeql/cpp.yaml
vendored
Normal file
14
.github/workflows/helper/codeql/cpp.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: "CodeQL config for C++"
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- exclude:
|
||||
id: cpp/commented-out-code
|
||||
- exclude:
|
||||
id: cpp/include-non-header
|
||||
- exclude:
|
||||
id: cpp/loop-variable-changed
|
||||
- exclude:
|
||||
id: cpp/useless-expression
|
||||
11
.github/workflows/helper/codeql/js.yaml
vendored
Normal file
11
.github/workflows/helper/codeql/js.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
name: "CodeQL config for Javascript"
|
||||
|
||||
paths-ignore:
|
||||
- "**/lib/*"
|
||||
|
||||
queries:
|
||||
- uses: security-and-quality
|
||||
|
||||
query-filters:
|
||||
- exclude:
|
||||
id: js/superfluous-trailing-arguments
|
||||
5
.github/workflows/stale_bot.yaml
vendored
5
.github/workflows/stale_bot.yaml
vendored
@@ -4,12 +4,15 @@ on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
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."
|
||||
|
||||
@@ -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
|
||||
@@ -34,6 +34,7 @@ repos:
|
||||
exclude: |
|
||||
(?x)^(
|
||||
compile_commands.json |
|
||||
src/webui/www/private/css/lib/.* |
|
||||
src/webui/www/private/scripts/lib/.*
|
||||
)$
|
||||
|
||||
@@ -43,6 +44,7 @@ repos:
|
||||
(?x)^(
|
||||
compile_commands.json |
|
||||
configure |
|
||||
src/webui/www/private/css/lib/.* |
|
||||
src/webui/www/private/scripts/lib/.*
|
||||
)$
|
||||
exclude_types:
|
||||
@@ -53,6 +55,7 @@ repos:
|
||||
name: Check trailing whitespaces
|
||||
exclude: |
|
||||
(?x)^(
|
||||
src/webui/www/private/css/lib/.* |
|
||||
src/webui/www/private/scripts/lib/.*
|
||||
)$
|
||||
exclude_types:
|
||||
|
||||
35
.tx/config
35
.tx/config
@@ -1,27 +1,24 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[qbittorrent.qbittorrent_master]
|
||||
file_filter = src/lang/qbittorrent_<lang>.ts
|
||||
lang_map = pt: pt_PT
|
||||
source_file = src/lang/qbittorrent_en.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_master]
|
||||
file_filter = src/lang/qbittorrent_<lang>.ts
|
||||
source_file = src/lang/qbittorrent_en.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
minimum_perc = 23
|
||||
mode = developer
|
||||
lang_map = pt: pt_PT, zh: zh_CN
|
||||
|
||||
[qbittorrent.qbittorrentdesktop_master]
|
||||
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
|
||||
source_lang = en
|
||||
type = DESKTOP
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui]
|
||||
file_filter = src/webui/www/translations/webui_<lang>.ts
|
||||
source_file = src/webui/www/translations/webui_en.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
minimum_perc = 23
|
||||
mode = developer
|
||||
lang_map = pt: pt_PT, zh: zh_CN
|
||||
|
||||
[qbittorrent.qbittorrent_webui]
|
||||
file_filter = src/webui/www/translations/webui_<lang>.ts
|
||||
lang_map = pt: pt_PT
|
||||
source_file = src/webui/www/translations/webui_en.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrentdesktop_master]
|
||||
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
|
||||
source_lang = en
|
||||
type = DESKTOP
|
||||
minimum_perc = 23
|
||||
mode = developer
|
||||
|
||||
4
AUTHORS
4
AUTHORS
@@ -29,6 +29,10 @@ Code from other projects:
|
||||
copyright: Dan Haim <negativeiq@users.sourceforge.net>
|
||||
license: BSD
|
||||
|
||||
* files src/webui/www/private/css/lib/vanillaSelectBox.css src/webui/www/private/scripts/lib/vanillaSelectBox.js
|
||||
copyright: Philippe Meyer <pmg.meyer@gmail.com>
|
||||
license: MIT
|
||||
|
||||
Images Authors:
|
||||
* files: src/icons/qbittorrent-tray.svg
|
||||
copyright: Provided by HVS <hvs linuxmail org> (raster first proposal) and Atif Afzal(@atfzl github) <atif5801@gmail.com> (vectorized and modified)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
80
Changelog
80
Changelog
@@ -1,4 +1,84 @@
|
||||
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.0
|
||||
- FEATURE: Add `Auto resize columns` functionality (Chocobo1)
|
||||
- FEATURE: Allow to use Category paths in `Manual` mode (glassez)
|
||||
- FEATURE: Allow to disable Automatic mode when default "temp" path changed (glassez)
|
||||
- FEATURE: Add tuning options related to performance warnings (Chocobo1)
|
||||
- FEATURE: Add right click menu for status filters (An0n)
|
||||
- FEATURE: Allow setting the number of maximum active checking torrents (An0n)
|
||||
- FEATURE: Add option to toggle filters sidebar (AbeniMatteo)
|
||||
- FEATURE: Allow to set `working set limit` on non-Windows OS (Chocobo1)
|
||||
- FEATURE: Add `Export .torrent` action (Chocobo1)
|
||||
- FEATURE: Add keyboard navigation keys (itlezy)
|
||||
- FEATURE: Allow to use POSIX-compliant disk IO type (Coda)
|
||||
- FEATURE: Add `Filter files` field in new torrent dialog (thalieht)
|
||||
- FEATURE: Implement new icon/color theme (now-im, xavier2k6)
|
||||
- FEATURE: Add file name filter/blacklist (mxtsdev, thalieht)
|
||||
- FEATURE: Add support for custom SMTP ports (Emil M George)
|
||||
- FEATURE: Split the OS cache settings into Disk IO read/write modes (summer)
|
||||
- FEATURE: When duplicate torrent is added set metadata to existing one (glassez)
|
||||
- FEATURE: Greatly improve startup time with many torrents (glassez, jagannatharjun)
|
||||
- FEATURE: Add keyboard shortcut to Download URL dialog (Chocobo1)
|
||||
- FEATURE: Add ability to run external program on torrent added (glassez)
|
||||
- FEATURE: Add infohash and download path columns (tristanleboss)
|
||||
- FEATURE: Allow to set torrent stop condition (glassez, thalieht)
|
||||
- FEATURE: Add a `Moving` status filter (tristanleboss)
|
||||
- FEATURE: Change color palettes for both dark, light themes (Chocobo1)
|
||||
- FEATURE: Add a `Use proxy for hostname lookup` option (Nathan Lewis)
|
||||
- FEATURE: Introduce a `change listen port` cmd option (BallsOfSpaghetti)
|
||||
- FEATURE: Implement `Peer ID Client` column for `Peers` tab (Hanabishi)
|
||||
- FEATURE: Add port forwarding option for embedded tracker (Chocobo1)
|
||||
- BUGFIX: Store hybrid torrents using `torrent ID` as basename (glassez)
|
||||
- BUGFIX: Enable Combobox editor for the `Mixed` file download priority (Aleksandr Cupacenko)
|
||||
- BUGFIX: Allow shortcut folders for the Open and Save directory dialogs (Aleksandr Cupacenko)
|
||||
- BUGFIX: Rename content tab `Size` column to `Total Size` (Aleksandr Cupacenko)
|
||||
- BUGFIX: Fix scrolling to the lowermost visible torrent (Aleksandr Cupacenko)
|
||||
- BUGFIX: Allow changing file priorities for finished torrents (An0n)
|
||||
- BUGFIX: Focus save path when Manual mode is selected initially (Aleksandr Cupacenko)
|
||||
- BUGFIX: Disable force reannounce when it is not possible (An0n)
|
||||
- BUGFIX: Add horizontal scrolling for tracker list and torrent content (NotTsunami)
|
||||
- BUGFIX: Enlarge "speed limits" icons (Chocobo1)
|
||||
- BUGFIX: Change Downloaded to Times Downloaded in trackers tab (An0n)
|
||||
- BUGFIX: Remove artificial max limits from `Torrent Queueing` related options (Chocobo1)
|
||||
- BUGFIX: Preserve `skip hash check` when there is no metadata (glassez)
|
||||
- BUGFIX: Fix DHT/PeX/LSD status when it is globally disabled (Kacper Michajłow)
|
||||
- BUGFIX: Fix rate calculation when interval is too low (glassez)
|
||||
- BUGFIX: Add tooltip message when system tray icon isn't available (Chocobo1)
|
||||
- BUGFIX: Improve sender field in mail notifications (Dmitry Vodopyanov)
|
||||
- BUGFIX: Fix "Add torrent dialog" spill-over on smaller screens (Chocobo1)
|
||||
- BUGFIX: Fix peer count issue when tracker responds with zero figure (summer)
|
||||
- BUGFIX: Don't merge trackers by default (glassez)
|
||||
- BUGFIX: Don't inhibit system sleep/auto shutdown for torrents stuck at downloading metadata (summer)
|
||||
- BUGFIX: Allow to pause a checking torrent from context menu (summer)
|
||||
- BUGFIX: Allow to use subnet notation in reverse proxy list (Chocobo1)
|
||||
- BUGFIX: Fine tune translations loading for Chinese locales (sledgehammer999)
|
||||
- BUGFIX: Fix torrent content checkboxes not updated properly (Chocobo1)
|
||||
- BUGFIX: Correctly load state of `Use another path for incomplete torrents` in Watched folders (glassez)
|
||||
- BUGFIX: Add confirmation to resume/pause all (BallsOfSpaghetti)
|
||||
- BUGFIX: Fix wrong count of errored trackers (Chocobo1)
|
||||
- WEBUI: Allow blank lines in multipart form-data input (Aleksandr Cupacenko)
|
||||
- WEBUI: Make various dialogs resizable (Chocobo1)
|
||||
- WEBUI: Fix wrong v2 hash string displayed (Chocobo1)
|
||||
- WEBUI: WebAPI: return correct status (Requi)
|
||||
- WEBUI: Fix empty selection in language combobox (Chocobo1)
|
||||
- WEBUI: Store WebUI port setting in human readable number (Chocobo1)
|
||||
- WEBUI: Add support for exporting .torrent (Tom Piccirello)
|
||||
- WEBUI: WebAPI: Add endpoint to set speed limit mode (glassez)
|
||||
- WEBUI: Improve progress bar rendering (Mike Lei)
|
||||
- WEBUI: Add transfer list refresh interval settings (summer)
|
||||
- WEBUI: Use natural sort (Chocobo1)
|
||||
- WEBUI: Apply i18n translation only to built-in WebUI (Chocobo1)
|
||||
- WEBUI: Alert when HTTPS settings are incomplete (Chocobo1)
|
||||
- WEBUI: Handle drag and drop events (Chocobo1)
|
||||
- WEBUI: Fix wrong behavior for shutdown action (Chocobo1)
|
||||
- WEBUI: Don't disable combobox for file priority (Chocobo1)
|
||||
- RSS: Increase limit of maximum number of articles per feed (summer)
|
||||
- WINDOWS: Fix `Open destination folder` delay on Windows (Andrew)
|
||||
- WINDOWS: NSIS: Update Russian, Estonian, Japanese, Dutch, Portuguese BR, German and Indonesian translations (Andrei Stepanov, Priit Uring, maboroshin, Thomas De Rocker, Ícaro, schnurlos, Faisal A. F. Rahman)
|
||||
- LINUX: Mark as single window app in .desktop file (Nicolas Fella)
|
||||
- LINUX: Add Dockerfile (Amanuense-del-diavolo, Tom Piccirello, Chocobo1)
|
||||
- LINUX: Remove option of using icons from system theme (now-im)
|
||||
- MACOS: Fix wrong background color in properties widget (NotTsunami)
|
||||
- OTHER: Binary distributions of qbittorrent are GPLv3+ licensed (sledgehammer999)
|
||||
|
||||
Thu Jan 06 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.4.0
|
||||
- FEATURE: Support for v2 torrents along with libtorrent 2.0.x support (glassez, Chocobo1)
|
||||
|
||||
6
INSTALL
6
INSTALL
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -101,6 +101,10 @@ if (MSVC)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (DBUS)
|
||||
target_compile_definitions(qbt_common_cfg INTERFACE QBT_USES_DBUS)
|
||||
endif()
|
||||
|
||||
if (LibtorrentRasterbar_VERSION VERSION_GREATER_EQUAL ${minLibtorrentVersion})
|
||||
target_compile_definitions(qbt_common_cfg INTERFACE QBT_USES_LIBTORRENT2)
|
||||
endif()
|
||||
|
||||
86
configure
vendored
86
configure
vendored
@@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
# Guess values for system-dependent variables and create Makefiles.
|
||||
# Generated by GNU Autoconf 2.71 for qbittorrent v4.5.0beta1.
|
||||
# Generated by GNU Autoconf 2.71 for qbittorrent v4.6.0alpha1.
|
||||
#
|
||||
# Report bugs to <bugs.qbittorrent.org>.
|
||||
#
|
||||
@@ -611,8 +611,8 @@ MAKEFLAGS=
|
||||
# Identity of this package.
|
||||
PACKAGE_NAME='qbittorrent'
|
||||
PACKAGE_TARNAME='qbittorrent'
|
||||
PACKAGE_VERSION='v4.5.0beta1'
|
||||
PACKAGE_STRING='qbittorrent v4.5.0beta1'
|
||||
PACKAGE_VERSION='v4.6.0alpha1'
|
||||
PACKAGE_STRING='qbittorrent v4.6.0alpha1'
|
||||
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.5.0beta1 to adapt to many kinds of systems.
|
||||
\`configure' configures qbittorrent v4.6.0alpha1 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.5.0beta1:";;
|
||||
short | recursive ) echo "Configuration of qbittorrent v4.6.0alpha1:";;
|
||||
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.5.0beta1
|
||||
qbittorrent configure v4.6.0alpha1
|
||||
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.5.0beta1, which was
|
||||
It was created by qbittorrent $as_me v4.6.0alpha1, 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.5.0beta1'
|
||||
VERSION='v4.6.0alpha1'
|
||||
|
||||
|
||||
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
|
||||
@@ -5649,7 +5649,7 @@ fi
|
||||
then :
|
||||
as_fn_error $? "Could not find QtDBus" "$LINENO" 5
|
||||
else $as_nop
|
||||
QBT_ADD_CONFIG="$QBT_ADD_CONFIG dbus"
|
||||
QBT_ADD_CONFIG="$QBT_ADD_CONFIG dbus" QBT_ADD_DEFINES="$QBT_ADD_DEFINES QBT_USES_DBUS"
|
||||
|
||||
fi ;; #(
|
||||
"xno") :
|
||||
@@ -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.5.0beta1, which was
|
||||
This file was extended by qbittorrent $as_me v4.6.0alpha1, 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.5.0beta1
|
||||
qbittorrent config.status v4.6.0alpha1
|
||||
configured by $0, generated by GNU Autoconf 2.71,
|
||||
with options \\"\$ac_cs_config\\"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
AC_INIT([qbittorrent], [v4.5.0beta1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_INIT([qbittorrent], [v4.6.0alpha1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
|
||||
AC_CONFIG_AUX_DIR([build-aux])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
: ${CFLAGS=""}
|
||||
@@ -165,7 +165,7 @@ AS_CASE(["x$enable_qt_dbus"],
|
||||
FIND_QTDBUS()
|
||||
AS_IF([test "x$HAVE_QTDBUS" = "xfalse"],
|
||||
[AC_MSG_ERROR([Could not find QtDBus])],
|
||||
[QBT_ADD_CONFIG="$QBT_ADD_CONFIG dbus"]
|
||||
[QBT_ADD_CONFIG="$QBT_ADD_CONFIG dbus" QBT_ADD_DEFINES="$QBT_ADD_DEFINES QBT_USES_DBUS"]
|
||||
)],
|
||||
["xno"],
|
||||
[AC_MSG_RESULT([no])
|
||||
@@ -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,
|
||||
|
||||
8
dist/docker/.env
vendored
8
dist/docker/.env
vendored
@@ -1,8 +0,0 @@
|
||||
# refer to Readme.md for an explanation of the variables
|
||||
|
||||
QBT_EULA=
|
||||
QBT_VERSION=devel
|
||||
QBT_WEBUI_PORT=8080
|
||||
|
||||
QBT_CONFIG_PATH=<your_path>/config
|
||||
QBT_DOWNLOADS_PATH=<your_path>/downloads
|
||||
62
dist/docker/Dockerfile
vendored
62
dist/docker/Dockerfile
vendored
@@ -1,62 +0,0 @@
|
||||
# image for building
|
||||
FROM alpine:latest AS builder
|
||||
|
||||
ARG QBT_VERSION
|
||||
|
||||
# alpine linux qbittorrent package: https://git.alpinelinux.org/aports/tree/community/qbittorrent/APKBUILD
|
||||
|
||||
RUN \
|
||||
apk --update-cache add \
|
||||
boost-dev \
|
||||
cmake \
|
||||
g++ \
|
||||
libtorrent-rasterbar-dev \
|
||||
ninja \
|
||||
qt6-qtbase-dev \
|
||||
qt6-qttools-dev
|
||||
|
||||
RUN \
|
||||
if [ "$QBT_VERSION" = "devel" ]; then \
|
||||
wget https://github.com/qbittorrent/qBittorrent/archive/refs/heads/master.zip && \
|
||||
unzip master.zip && \
|
||||
cd qBittorrent-master ; \
|
||||
else \
|
||||
wget "https://github.com/qbittorrent/qBittorrent/archive/refs/tags/release-${QBT_VERSION}.tar.gz" && \
|
||||
tar -xf "release-${QBT_VERSION}.tar.gz" && \
|
||||
cd "qBittorrent-release-${QBT_VERSION}" ; \
|
||||
fi && \
|
||||
cmake \
|
||||
-B build \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DGUI=OFF \
|
||||
-DQT6=ON \
|
||||
-DSTACKTRACE=OFF && \
|
||||
cmake --build build && \
|
||||
cmake --install build
|
||||
|
||||
# image for running
|
||||
FROM alpine:latest
|
||||
|
||||
RUN \
|
||||
apk --no-cache add \
|
||||
doas \
|
||||
libtorrent-rasterbar \
|
||||
python3 \
|
||||
qt6-qtbase \
|
||||
tini
|
||||
|
||||
RUN \
|
||||
adduser \
|
||||
-D \
|
||||
-H \
|
||||
-s /sbin/nologin \
|
||||
-u 1000 \
|
||||
qbtUser && \
|
||||
echo "permit nopass :root" >> "/etc/doas.d/doas.conf"
|
||||
|
||||
COPY --from=builder /usr/local/bin/qbittorrent-nox /usr/bin/qbittorrent-nox
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "-g", "--", "/entrypoint.sh"]
|
||||
101
dist/docker/Readme.md
vendored
101
dist/docker/Readme.md
vendored
@@ -1,101 +0,0 @@
|
||||
# qBittorrent-nox Docker Image
|
||||
|
||||
This Dockerfile allows you to build a Docker Image containing qBittorrent-nox
|
||||
|
||||
## Prerequisites
|
||||
|
||||
In order to build/run this image you'll need Docker installed: https://docs.docker.com/get-docker/
|
||||
|
||||
If you don't need the GUI, you can just install Docker Engine: https://docs.docker.com/engine/install/
|
||||
|
||||
It is also recommended to install Docker Compose as it can significantly ease the process: https://docs.docker.com/compose/install/
|
||||
|
||||
## Building Docker Image
|
||||
|
||||
* If you are using Docker (not Docker Compose) then run the following commands in this folder:
|
||||
```shell
|
||||
export \
|
||||
QBT_VERSION=devel
|
||||
docker build \
|
||||
--build-arg QBT_VERSION \
|
||||
-t qbittorrent-nox:"$QBT_VERSION" \
|
||||
.
|
||||
```
|
||||
|
||||
* If you are using Docker Compose then you should edit `.env` file first.
|
||||
You can find an explanation of the variables in the following [Parameters](#parameters) section. \
|
||||
Then run the following commands in this folder:
|
||||
```shell
|
||||
docker compose build \
|
||||
--build-arg QBT_VERSION
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
#### Environment variables
|
||||
|
||||
* `QBT_EULA` \
|
||||
This environment variable defines whether you accept the end-user license agreement (EULA) of qBittorrent. \
|
||||
**Put `accept` only if you understand and accepted the EULA.** You can find
|
||||
the EULA [here](https://github.com/qbittorrent/qBittorrent/blob/56667e717b82c79433ecb8a5ff6cc2d7b315d773/src/app/main.cpp#L320-L323).
|
||||
* `QBT_VERSION` \
|
||||
This environment variable specifies the version of qBittorrent-nox to be built. \
|
||||
For example, `4.4.0` is a valid entry. You can find all tagged versions [here](https://github.com/qbittorrent/qBittorrent/tags). \
|
||||
Or you can put `devel` to build the latest development version.
|
||||
* `QBT_WEBUI_PORT` \
|
||||
This environment variable sets the port number which qBittorrent WebUI will be binded to.
|
||||
|
||||
#### Volumes
|
||||
|
||||
There are some paths involved:
|
||||
* `<your_path>/config` \
|
||||
Full path to a folder on your host machine which will store qBittorrent configurations.
|
||||
Using relative path won't work.
|
||||
* `<your_path>/downloads` \
|
||||
Full path to a folder on your host machine which will store the files downloaded by qBittorrent.
|
||||
Using relative path won't work.
|
||||
|
||||
## Running container
|
||||
|
||||
* Using Docker (not Docker Compose), simply run:
|
||||
```shell
|
||||
export \
|
||||
QBT_EULA=accept \
|
||||
QBT_VERSION=devel \
|
||||
QBT_WEBUI_PORT=8080 \
|
||||
QBT_CONFIG_PATH="/tmp/bbb/config"
|
||||
QBT_DOWNLOADS_PATH="/tmp/bbb/downloads"
|
||||
docker run \
|
||||
-t \
|
||||
--read-only \
|
||||
--rm \
|
||||
--tmpfs /tmp \
|
||||
--name qbittorrent-nox \
|
||||
-e QBT_EULA \
|
||||
-e QBT_WEBUI_PORT \
|
||||
-p "$QBT_WEBUI_PORT":"$QBT_WEBUI_PORT"/tcp \
|
||||
-p 6881:6881/tcp \
|
||||
-p 6881:6881/udp \
|
||||
-v "$QBT_CONFIG_PATH":/config \
|
||||
-v "$QBT_DOWNLOADS_PATH":/downloads \
|
||||
qbittorrent-nox:"$QBT_VERSION"
|
||||
```
|
||||
|
||||
* Using Docker Compose:
|
||||
```shell
|
||||
docker compose up
|
||||
```
|
||||
|
||||
Then you can login at: `http://127.0.0.1:8080`
|
||||
|
||||
## Stopping container
|
||||
|
||||
* Using Docker (not Docker Compose):
|
||||
```shell
|
||||
docker stop -t 1800 qbittorrent-nox
|
||||
```
|
||||
|
||||
* Using Docker Compose:
|
||||
```shell
|
||||
docker compose down
|
||||
```
|
||||
25
dist/docker/docker-compose.yml
vendored
25
dist/docker/docker-compose.yml
vendored
@@ -1,25 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
qbittorrent-nox:
|
||||
build: .
|
||||
container_name: qbittorrent-nox
|
||||
environment:
|
||||
- QBT_EULA=${QBT_EULA}
|
||||
- QBT_VERSION=${QBT_VERSION}
|
||||
- QBT_WEBUI_PORT=${QBT_WEBUI_PORT}
|
||||
image: qbittorrent-nox:${QBT_VERSION}
|
||||
ports:
|
||||
# for bittorrent traffic
|
||||
- 6881:6881/tcp
|
||||
- 6881:6881/udp
|
||||
# for WebUI
|
||||
- ${QBT_WEBUI_PORT}:${QBT_WEBUI_PORT}/tcp
|
||||
read_only: true
|
||||
stop_grace_period: 30m
|
||||
tmpfs:
|
||||
- /tmp
|
||||
tty: true
|
||||
volumes:
|
||||
- ${QBT_CONFIG_PATH}:/config
|
||||
- ${QBT_DOWNLOADS_PATH}:/downloads
|
||||
35
dist/docker/entrypoint.sh
vendored
35
dist/docker/entrypoint.sh
vendored
@@ -1,35 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
downloadsPath="/downloads"
|
||||
profilePath="/config"
|
||||
qbtConfigFile="$profilePath/qBittorrent/config/qBittorrent.conf"
|
||||
|
||||
if [ ! -f "$qbtConfigFile" ]; then
|
||||
mkdir -p "$(dirname $qbtConfigFile)"
|
||||
cat << EOF > "$qbtConfigFile"
|
||||
[BitTorrent]
|
||||
Session\DefaultSavePath=/downloads
|
||||
Session\Port=6881
|
||||
Session\TempPath=/downloads/temp
|
||||
|
||||
[LegalNotice]
|
||||
Accepted=false
|
||||
EOF
|
||||
|
||||
if [ "$QBT_EULA" = "accept" ]; then
|
||||
sed -i '/^\[LegalNotice\]$/{$!{N;s|\(\[LegalNotice\]\nAccepted=\).*|\1true|}}' "$qbtConfigFile"
|
||||
else
|
||||
sed -i '/^\[LegalNotice\]$/{$!{N;s|\(\[LegalNotice\]\nAccepted=\).*|\1false|}}' "$qbtConfigFile"
|
||||
fi
|
||||
fi
|
||||
|
||||
# those are owned by root by default
|
||||
# don't change existing files owner in `$downloadsPath`
|
||||
chown qbtUser:qbtUser "$downloadsPath"
|
||||
chown qbtUser:qbtUser -R "$profilePath"
|
||||
|
||||
doas -u qbtUser \
|
||||
qbittorrent-nox \
|
||||
--profile="$profilePath" \
|
||||
--webui-port="$QBT_WEBUI_PORT" \
|
||||
"$@"
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_LITHUANIAN} "qBittorrent (reikalingas)"
|
||||
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_LITHUANIAN} "Sukurti darbalaukyje nuorodą"
|
||||
LangString inst_dekstop ${LANG_LITHUANIAN} "Sukurti nuorodą darbalaukyje"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_LITHUANIAN} "Sukurti Pradėti meniu nuorodą"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
@@ -13,27 +13,27 @@ LangString inst_torrent ${LANG_LITHUANIAN} "Atidaryti .torrent failus su qBittor
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_LITHUANIAN} "Atidaryti magneto nuorodas su qBittorrent"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_LITHUANIAN} "Sukurti Windows užkardos leidimą"
|
||||
LangString inst_firewall ${LANG_LITHUANIAN} "Sukurti Windows interneto užkardos leidimą"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_LITHUANIAN} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_LITHUANIAN} "Išjungti Windows path ilgio limitaciją (260 ženklų MAX_PATH limitacija, reikalinga versija yra Windows 10 1607 ar naujesnė)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_LITHUANIAN} "Pridedu Windows užkardos leidimą"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašau uždaryti programą prieš įdiegiant."
|
||||
LangString inst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašome uždaryti programą prieš įdiegiant."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_LITHUANIAN} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_LITHUANIAN} "Dabartinė versija bus pašalinta. Naudotojo nustatymai ir torrentai liks nepakeisti."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_LITHUANIAN} "Šalinu ankstesnę versiją."
|
||||
LangString inst_unist ${LANG_LITHUANIAN} "Šalinama ankstesnė versija."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_LITHUANIAN} "Paleisti qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_LITHUANIAN} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_LITHUANIAN} "Šis įdiegėjas veikia tik su 64 bitų Windows versija."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_LITHUANIAN} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_LITHUANIAN} "Ši qBittorent versija reikalauja bent Windows 7."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_LITHUANIAN} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_LITHUANIAN} "Šis įdiegėjas reikalauja bent Windows 10 1809."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_LITHUANIAN} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_LITHUANIAN} "Pašalinti qBittorrent"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
@@ -49,14 +49,14 @@ LangString remove_registry ${LANG_LITHUANIAN} "Pašalinti registro raktus"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_LITHUANIAN} "Pašalinti nustatymų failus"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_LITHUANIAN} "Pašalinti Windows užkardos leidimą"
|
||||
LangString remove_firewall ${LANG_LITHUANIAN} "Pašalinti Windows interneto užkardos leidimą"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_LITHUANIAN} "Šalinu Windows užkardos leidimą"
|
||||
LangString remove_firewallinfo ${LANG_LITHUANIAN} "Šalinamas Windows interneto užkardos leidimas"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_LITHUANIAN} "Pašalinti torentus ir podėlio duomenis"
|
||||
LangString remove_cache ${LANG_LITHUANIAN} "Pašalinti torentus ir talpyklos duomenis"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašau uždarykite programą prieš išdiegiant."
|
||||
LangString uninst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašome uždaryti programą prieš išdiegiant."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_LITHUANIAN} "Negaliu pašalinti .torrent asociacijos. Ji yra susieti su:"
|
||||
LangString uninst_tor_warn ${LANG_LITHUANIAN} "Neįmanoma pašalinti .torrent asociacijos. Ji yra susieta su:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_LITHUANIAN} "Negaliu pašalinti magneto asociacijos. Jis susietas su:"
|
||||
LangString uninst_mag_warn ${LANG_LITHUANIAN} "Neįmanoma pašalinti magneto asociacijos. Ji yra susieta su:"
|
||||
|
||||
56
dist/windows/installer-translations/slovak.nsi
vendored
56
dist/windows/installer-translations/slovak.nsi
vendored
@@ -1,62 +1,62 @@
|
||||
;Installer strings
|
||||
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_SLOVAK} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_SLOVAK} "qBittorrent (požadované)"
|
||||
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_SLOVAK} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_SLOVAK} "Vytvoriť odkaz na pracovnej ploche"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_SLOVAK} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_SLOVAK} "Vytvoriť odkaz v štart menu"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_SLOVAK} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_SLOVAK} "Sputiť qBittorrent pri štarte Windowsu"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_SLOVAK} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_SLOVAK} "Otvárať .torrent súbory v qBittorrent"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_SLOVAK} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_SLOVAK} "Otvárať magnet odkazy v qBittorrent"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_SLOVAK} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_SLOVAK} "Pridať pravidlo do Windows Firewall"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_SLOVAK} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_SLOVAK} "Vypnúť limit dĺžky cesty Windowsu (260 znaková MAX_PATH limitácia, vyžaduje Windows 10 1607 alebo novšie)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_SLOVAK} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_SLOVAK} "Pridáva sa pravidlo do Windows Firewall"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_SLOVAK} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_SLOVAK} "qBittorrent je spustený. Zatvorte prosím aplikáciu pred inštaláciou."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_SLOVAK} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_SLOVAK} "Aktuálna verzia bude odinštalovaná. Užívateľské nastavenia a torrenty sa zachovajú."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_SLOVAK} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_SLOVAK} "Odinštalácia predchádzajúcej verzie."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_SLOVAK} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_SLOVAK} "Spustiť qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_SLOVAK} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_SLOVAK} "Táto inštalácia funguje iba na 64-bitových verziách Windowsu."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_SLOVAK} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_SLOVAK} "Táto qBittorrent verzia vyžaduje aspoň Windows 7."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_SLOVAK} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_SLOVAK} "Tento inštalátor vyžaduje aspoň Windows 10 1809."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_SLOVAK} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_SLOVAK} "Odinštalovať qBittorrent"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
|
||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||
LangString remove_files ${LANG_SLOVAK} "Remove files"
|
||||
LangString remove_files ${LANG_SLOVAK} "Odstrániť súbory"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_SLOVAK} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_SLOVAK} "Odstrániť odkazy"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_SLOVAK} "Remove file associations"
|
||||
LangString remove_associations ${LANG_SLOVAK} "Odstrániť asociácie súborov"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_SLOVAK} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_SLOVAK} "Odstrániť kľúče registrov"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_SLOVAK} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_SLOVAK} "Odstrániť konfiguračné súbory"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_SLOVAK} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_SLOVAK} "Odstrániť pravidlo z Windows Firewall"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_SLOVAK} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_SLOVAK} "Odstraňuje sa pravidlo z Windows Firewall"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_SLOVAK} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_SLOVAK} "Odstrániť torrenty a dáta z vyrovnávacej pamäti"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_SLOVAK} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_SLOVAK} "qBittorrent je spustený. Zatvorte prosím aplikáciu pred odinštaláciou."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_SLOVAK} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_SLOVAK} "Neodstraňuje sa .torrent asociácia. Asociované s:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_SLOVAK} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_SLOVAK} "Neodstraňuje sa magnet asociácia. Asociované s:"
|
||||
|
||||
@@ -27,11 +27,11 @@ LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_SWEDISH} "Kör qBittorrent."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_SWEDISH} "Installationsprogrammet fungerar endast i 64-bitars Windows-versioner."
|
||||
LangString inst_requires_64bit ${LANG_SWEDISH} "Det här installationsprogrammet fungerar endast i 64-bitars Windows-versioner."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_SWEDISH} "Den här qBittorrent-versionen kräver minst Windows 7."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_SWEDISH} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_SWEDISH} "Det här installationsprogrammet kräver minst Windows 10 1809."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_SWEDISH} "Avinstallera qBittorrent"
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ LangString inst_requires_64bit ${LANG_TURKISH} "Bu yükleyici sadece 64-bit Wind
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_TURKISH} "Bu qBittorrent sürümü en az Windows 7 gerektirir."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_TURKISH} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_TURKISH} "Bu yükleyici en az Windows 10 1809 gerektirir."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_TURKISH} "qBittorrent'i kaldır"
|
||||
|
||||
|
||||
56
dist/windows/installer-translations/uzbek.nsi
vendored
56
dist/windows/installer-translations/uzbek.nsi
vendored
@@ -1,62 +1,62 @@
|
||||
;Installer strings
|
||||
|
||||
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_UZBEK} "qBittorrent (required)"
|
||||
LangString inst_qbt_req ${LANG_UZBEK} "qBittorrent (talab qilinadi)"
|
||||
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_UZBEK} "Create Desktop Shortcut"
|
||||
LangString inst_dekstop ${LANG_UZBEK} "Ish Stolida Yorliq Yaratilsin"
|
||||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_UZBEK} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_UZBEK} "Boshlash Menyusida Yorliq Yaratilsin"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_UZBEK} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_UZBEK} "qBittorrent Windows bilan birga ishga tushirilsin"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_UZBEK} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_UZBEK} ".torrent fayllar qBittorrent bilan ochilsin"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_UZBEK} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_UZBEK} "Magnit havolalar qBittorrent bilan ochilsin"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_UZBEK} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasi qoʻshilsin"
|
||||
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_UZBEK} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
|
||||
LangString inst_pathlimit ${LANG_UZBEK} "Windows yoʻl uzunligi cheklovi olib tashlansin (260 belgi MAX_PATH cheklovi, Windows 10 1607 va yuqorisi talab qilinadi)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_UZBEK} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasini qoʻshish"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_UZBEK} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_UZBEK} "qBittorrent ishga tushgan. Iltimos, oʻrnatishdan oldin dasturni yoping."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_UZBEK} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_UZBEK} "Hozirgi versiya oʻchiriladi. Foydalanuvchi sozlamalari va torrentlar oʻzgarishsiz qoladi."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_UZBEK} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_UZBEK} "Oldingi versiyani oʻchirish."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_UZBEK} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_UZBEK} "qBittorrent ishga tushirilsin."
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_UZBEK} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_UZBEK} "Bu oʻrnatuvchi faqat Windows 64-bit versiyalarda ishlaydi."
|
||||
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_UZBEK} "This qBittorrent version requires at least Windows 7."
|
||||
LangString inst_requires_win7 ${LANG_UZBEK} "qBittorrent bu versiyasi kamida Windows 7 talab qiladi."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_UZBEK} "This installer requires at least Windows 10 1809."
|
||||
LangString inst_requires_win10 ${LANG_UZBEK} "Bu oʻrnatuvchi kamida Windows 10 1809 talab qiladi."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_UZBEK} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_UZBEK} "qBittorrent oʻchirilsin"
|
||||
|
||||
;------------------------------------
|
||||
;Uninstaller strings
|
||||
|
||||
;LangString remove_files ${LANG_ENGLISH} "Remove files"
|
||||
LangString remove_files ${LANG_UZBEK} "Remove files"
|
||||
LangString remove_files ${LANG_UZBEK} "Fayllar oʻchirilsin"
|
||||
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_UZBEK} "Remove shortcuts"
|
||||
LangString remove_shortcuts ${LANG_UZBEK} "Yorliqlar oʻchirilsin"
|
||||
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
|
||||
LangString remove_associations ${LANG_UZBEK} "Remove file associations"
|
||||
LangString remove_associations ${LANG_UZBEK} "Fayl birlashmalari oʻchirilsin"
|
||||
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_UZBEK} "Remove registry keys"
|
||||
LangString remove_registry ${LANG_UZBEK} "Reyester kalitlari oʻchirilsin"
|
||||
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_UZBEK} "Remove configuration files"
|
||||
LangString remove_conf ${LANG_UZBEK} "Sozlama fayllari oʻchirilsin"
|
||||
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_UZBEK} "Remove Windows Firewall rule"
|
||||
LangString remove_firewall ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasi oʻchirilsin"
|
||||
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_UZBEK} "Removing Windows Firewall rule"
|
||||
LangString remove_firewallinfo ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasini oʻchirish"
|
||||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_UZBEK} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_UZBEK} "Torrentlar va keshlangan maʼlumotlar oʻchirilsin"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_UZBEK} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_UZBEK} "qBittorrent ishga tushgan. Iltimos, oʻchirishdan oldin dasturni yoping."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_UZBEK} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_UZBEK} ".torrent birlashmasi oʻchirilmadi. U quyidagi bilan birlashgan:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_UZBEK} "Not removing magnet association. It is associated with:"
|
||||
LangString uninst_mag_warn ${LANG_UZBEK} "Magnit birlashmasi oʻchirilmadi. U quyidagi bilan birlashgan:"
|
||||
|
||||
2
dist/windows/installer.nsi
vendored
2
dist/windows/installer.nsi
vendored
@@ -35,6 +35,8 @@ Section $(inst_qbt_req) ;"qBittorrent (required)"
|
||||
SetOutPath "$INSTDIR\translations"
|
||||
; Put files there
|
||||
File /r "translations\qt*.qm"
|
||||
; Restore output path because it affects `CreateShortCut`. It affects the "Start in" field.
|
||||
SetOutPath $INSTDIR
|
||||
|
||||
; Write the installation path into the registry
|
||||
WriteRegStr HKLM "Software\qBittorrent" "InstallLocation" "$INSTDIR"
|
||||
|
||||
@@ -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>.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
#include "base/version.h"
|
||||
#include "applicationinstancemanager.h"
|
||||
#include "filelogger.h"
|
||||
#include "upgrade.h"
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
#include "gui/addnewtorrentdialog.h"
|
||||
@@ -96,6 +97,7 @@
|
||||
#include "gui/shutdownconfirmdialog.h"
|
||||
#include "gui/uithememanager.h"
|
||||
#include "gui/utils.h"
|
||||
#include "gui/windowstate.h"
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
#ifndef DISABLE_WEBUI
|
||||
@@ -120,12 +122,109 @@ namespace
|
||||
#ifndef DISABLE_GUI
|
||||
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
|
||||
#endif
|
||||
|
||||
QString serializeParams(const QBtCommandLineParameters ¶ms)
|
||||
{
|
||||
QStringList result;
|
||||
// Because we're passing a string list to the currently running
|
||||
// qBittorrent process, we need some way of passing along the options
|
||||
// the user has specified. Here we place special strings that are
|
||||
// almost certainly not going to collide with a file path or URL
|
||||
// specified by the user, and placing them at the beginning of the
|
||||
// string list so that they will be processed before the list of
|
||||
// torrent paths or URLs.
|
||||
|
||||
const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
|
||||
|
||||
if (!addTorrentParams.savePath.isEmpty())
|
||||
result.append(u"@savePath=" + addTorrentParams.savePath.data());
|
||||
|
||||
if (addTorrentParams.addPaused.has_value())
|
||||
result.append(*addTorrentParams.addPaused ? u"@addPaused=1"_qs : u"@addPaused=0"_qs);
|
||||
|
||||
if (addTorrentParams.skipChecking)
|
||||
result.append(u"@skipChecking"_qs);
|
||||
|
||||
if (!addTorrentParams.category.isEmpty())
|
||||
result.append(u"@category=" + addTorrentParams.category);
|
||||
|
||||
if (addTorrentParams.sequential)
|
||||
result.append(u"@sequential"_qs);
|
||||
|
||||
if (addTorrentParams.firstLastPiecePriority)
|
||||
result.append(u"@firstLastPiecePriority"_qs);
|
||||
|
||||
if (params.skipDialog.has_value())
|
||||
result.append(*params.skipDialog ? u"@skipDialog=1"_qs : u"@skipDialog=0"_qs);
|
||||
|
||||
result += params.torrentSources;
|
||||
|
||||
return result.join(PARAMS_SEPARATOR);
|
||||
}
|
||||
|
||||
QBtCommandLineParameters parseParams(const QString &str)
|
||||
{
|
||||
QBtCommandLineParameters parsedParams;
|
||||
BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
|
||||
|
||||
for (QString param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
|
||||
{
|
||||
param = param.trimmed();
|
||||
|
||||
// Process strings indicating options specified by the user.
|
||||
|
||||
if (param.startsWith(u"@savePath="))
|
||||
{
|
||||
addTorrentParams.savePath = Path(param.mid(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(u"@addPaused="))
|
||||
{
|
||||
addTorrentParams.addPaused = (QStringView(param).mid(11).toInt() != 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == u"@skipChecking")
|
||||
{
|
||||
addTorrentParams.skipChecking = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(u"@category="))
|
||||
{
|
||||
addTorrentParams.category = param.mid(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == u"@sequential")
|
||||
{
|
||||
addTorrentParams.sequential = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == u"@firstLastPiecePriority")
|
||||
{
|
||||
addTorrentParams.firstLastPiecePriority = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(u"@skipDialog="))
|
||||
{
|
||||
parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedParams.torrentSources.append(param);
|
||||
}
|
||||
|
||||
return parsedParams;
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
@@ -138,6 +237,7 @@ Application::Application(int &argc, char **argv)
|
||||
, m_processMemoryPriority(SETTINGS_KEY(u"ProcessMemoryPriority"_qs))
|
||||
#endif
|
||||
#ifndef DISABLE_GUI
|
||||
, m_startUpWindowState(u"GUI/StartUpWindowState"_qs)
|
||||
, m_storeNotificationTorrentAdded(NOTIFICATIONS_SETTINGS_KEY(u"TorrentAdded"_qs))
|
||||
#endif
|
||||
{
|
||||
@@ -171,6 +271,18 @@ Application::Application(int &argc, char **argv)
|
||||
SettingsStorage::initInstance();
|
||||
Preferences::initInstance();
|
||||
|
||||
const bool firstTimeUser = !Preferences::instance()->getAcceptedLegal();
|
||||
if (!firstTimeUser)
|
||||
{
|
||||
if (!upgrade())
|
||||
throw RuntimeError(u"Failed migration of old settings"_qs); // Not translatable. Translation isn't configured yet.
|
||||
handleChangedDefaults(DefaultPreferencesMode::Legacy);
|
||||
}
|
||||
else
|
||||
{
|
||||
handleChangedDefaults(DefaultPreferencesMode::Current);
|
||||
}
|
||||
|
||||
initializeTranslation();
|
||||
|
||||
connect(this, &QCoreApplication::aboutToQuit, this, &Application::cleanup);
|
||||
@@ -222,6 +334,16 @@ MainWindow *Application::mainWindow()
|
||||
return m_window;
|
||||
}
|
||||
|
||||
WindowState Application::startUpWindowState() const
|
||||
{
|
||||
return m_startUpWindowState;
|
||||
}
|
||||
|
||||
void Application::setStartUpWindowState(const WindowState windowState)
|
||||
{
|
||||
m_startUpWindowState = windowState;
|
||||
}
|
||||
|
||||
bool Application::isTorrentAddedNotificationsEnabled() const
|
||||
{
|
||||
return m_storeNotificationTorrentAdded;
|
||||
@@ -365,7 +487,7 @@ void Application::processMessage(const QString &message)
|
||||
}
|
||||
#endif
|
||||
|
||||
const AddTorrentParams params = parseParams(message.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts));
|
||||
const QBtCommandLineParameters params = parseParams(message);
|
||||
// If Application is not allowed to process params immediately
|
||||
// (i.e., other components are not ready) store params
|
||||
if (m_isProcessingParamsAllowed)
|
||||
@@ -440,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)
|
||||
@@ -459,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);
|
||||
@@ -485,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);
|
||||
|
||||
@@ -501,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
|
||||
}
|
||||
|
||||
@@ -600,72 +735,12 @@ void Application::allTorrentsFinished()
|
||||
exit();
|
||||
}
|
||||
|
||||
bool Application::sendParams(const QStringList ¶ms)
|
||||
bool Application::callMainInstance()
|
||||
{
|
||||
return m_instanceManager->sendMessage(params.join(PARAMS_SEPARATOR));
|
||||
return m_instanceManager->sendMessage(serializeParams(commandLineArgs()));
|
||||
}
|
||||
|
||||
Application::AddTorrentParams Application::parseParams(const QStringList ¶ms) const
|
||||
{
|
||||
AddTorrentParams parsedParams;
|
||||
BitTorrent::AddTorrentParams &torrentParams = parsedParams.torrentParams;
|
||||
|
||||
for (QString param : params)
|
||||
{
|
||||
param = param.trimmed();
|
||||
|
||||
// Process strings indicating options specified by the user.
|
||||
|
||||
if (param.startsWith(u"@savePath="))
|
||||
{
|
||||
torrentParams.savePath = Path(param.mid(10));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(u"@addPaused="))
|
||||
{
|
||||
torrentParams.addPaused = (QStringView(param).mid(11).toInt() != 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == u"@skipChecking")
|
||||
{
|
||||
torrentParams.skipChecking = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(u"@category="))
|
||||
{
|
||||
torrentParams.category = param.mid(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == u"@sequential")
|
||||
{
|
||||
torrentParams.sequential = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == u"@firstLastPiecePriority")
|
||||
{
|
||||
torrentParams.firstLastPiecePriority = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(u"@skipDialog="))
|
||||
{
|
||||
parsedParams.skipTorrentDialog = (QStringView(param).mid(12).toInt() != 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedParams.torrentSource = param;
|
||||
break;
|
||||
}
|
||||
|
||||
return parsedParams;
|
||||
}
|
||||
|
||||
void Application::processParams(const AddTorrentParams ¶ms)
|
||||
void Application::processParams(const QBtCommandLineParameters ¶ms)
|
||||
{
|
||||
#ifndef DISABLE_GUI
|
||||
// There are two circumstances in which we want to show the torrent
|
||||
@@ -673,15 +748,21 @@ void Application::processParams(const AddTorrentParams ¶ms)
|
||||
// be shown and skipTorrentDialog is undefined. The other is when
|
||||
// skipTorrentDialog is false, meaning that the application setting
|
||||
// should be overridden.
|
||||
const bool showDialogForThisTorrent = !params.skipTorrentDialog.value_or(!AddNewTorrentDialog::isEnabled());
|
||||
if (showDialogForThisTorrent)
|
||||
AddNewTorrentDialog::show(params.torrentSource, params.torrentParams, m_window);
|
||||
const bool showDialog = !params.skipDialog.value_or(!AddNewTorrentDialog::isEnabled());
|
||||
if (showDialog)
|
||||
{
|
||||
for (const QString &torrentSource : params.torrentSources)
|
||||
AddNewTorrentDialog::show(torrentSource, params.addTorrentParams, m_window);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
BitTorrent::Session::instance()->addTorrent(params.torrentSource, params.torrentParams);
|
||||
{
|
||||
for (const QString &torrentSource : params.torrentSources)
|
||||
BitTorrent::Session::instance()->addTorrent(torrentSource, params.addTorrentParams);
|
||||
}
|
||||
}
|
||||
|
||||
int Application::exec(const QStringList ¶ms)
|
||||
int Application::exec()
|
||||
try
|
||||
{
|
||||
#if !defined(DISABLE_WEBUI) && defined(DISABLE_GUI)
|
||||
@@ -706,7 +787,7 @@ try
|
||||
#ifndef DISABLE_GUI
|
||||
UIThemeManager::initInstance();
|
||||
|
||||
m_desktopIntegration = new DesktopIntegration(this);
|
||||
m_desktopIntegration = new DesktopIntegration;
|
||||
m_desktopIntegration->setToolTip(tr("Loading torrents..."));
|
||||
#ifndef Q_OS_MACOS
|
||||
auto *desktopIntegrationMenu = new QMenu;
|
||||
@@ -714,18 +795,15 @@ 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();
|
||||
});
|
||||
desktopIntegrationMenu->addAction(actionExit);
|
||||
|
||||
m_desktopIntegration->setMenu(desktopIntegrationMenu);
|
||||
#endif
|
||||
|
||||
const auto *pref = Preferences::instance();
|
||||
#ifndef Q_OS_MACOS
|
||||
const bool isHidden = m_desktopIntegration->isActive() && pref->startMinimized() && pref->minimizeToTray();
|
||||
const bool isHidden = m_desktopIntegration->isActive() && (startUpWindowState() == WindowState::Hidden);
|
||||
#else
|
||||
const bool isHidden = false;
|
||||
#endif
|
||||
@@ -735,7 +813,7 @@ try
|
||||
createStartupProgressDialog();
|
||||
// Add a small delay to avoid "flashing" the progress dialog in case there are not many torrents to restore.
|
||||
m_startupProgressDialog->setMinimumDuration(1000);
|
||||
if (pref->startMinimized())
|
||||
if (startUpWindowState() != WindowState::Normal)
|
||||
m_startupProgressDialog->setWindowState(Qt::WindowMinimized);
|
||||
}
|
||||
else
|
||||
@@ -788,12 +866,15 @@ try
|
||||
});
|
||||
|
||||
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
|
||||
// we must not delete menu while it is used by DesktopIntegration
|
||||
auto *oldMenu = m_desktopIntegration->menu();
|
||||
const MainWindow::State windowState = (!m_startupProgressDialog || (m_startupProgressDialog->windowState() & Qt::WindowMinimized))
|
||||
? MainWindow::Minimized : MainWindow::Normal;
|
||||
#ifndef Q_OS_MACOS
|
||||
const WindowState windowState = !m_startupProgressDialog ? WindowState::Hidden
|
||||
: (m_startupProgressDialog->windowState() & Qt::WindowMinimized) ? WindowState::Minimized
|
||||
: WindowState::Normal;
|
||||
#else
|
||||
const WindowState windowState = (m_startupProgressDialog->windowState() & Qt::WindowMinimized)
|
||||
? WindowState::Minimized : WindowState::Normal;
|
||||
#endif
|
||||
m_window = new MainWindow(this, windowState);
|
||||
delete oldMenu;
|
||||
delete m_startupProgressDialog;
|
||||
#ifdef Q_OS_WIN
|
||||
auto *pref = Preferences::instance();
|
||||
@@ -840,13 +921,14 @@ try
|
||||
#endif // DISABLE_WEBUI
|
||||
|
||||
m_isProcessingParamsAllowed = true;
|
||||
for (const AddTorrentParams ¶ms : m_paramsQueue)
|
||||
for (const QBtCommandLineParameters ¶ms : m_paramsQueue)
|
||||
processParams(params);
|
||||
m_paramsQueue.clear();
|
||||
});
|
||||
|
||||
if (!params.isEmpty())
|
||||
m_paramsQueue.append(parseParams(params));
|
||||
const QBtCommandLineParameters params = commandLineArgs();
|
||||
if (!params.torrentSources.isEmpty())
|
||||
m_paramsQueue.append(params);
|
||||
|
||||
return BaseApplication::exec();
|
||||
}
|
||||
@@ -882,7 +964,7 @@ void Application::createStartupProgressDialog()
|
||||
m_startupProgressDialog = new QProgressDialog(tr("Loading torrents..."), tr("Exit"), 0, 100);
|
||||
m_startupProgressDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
m_startupProgressDialog->setWindowFlag(Qt::WindowMinimizeButtonHint);
|
||||
m_startupProgressDialog->setMinimumDuration(0); // Show dialog immediatelly by default
|
||||
m_startupProgressDialog->setMinimumDuration(0); // Show dialog immediately by default
|
||||
m_startupProgressDialog->setAutoReset(false);
|
||||
m_startupProgressDialog->setAutoClose(false);
|
||||
|
||||
@@ -932,7 +1014,8 @@ bool Application::event(QEvent *ev)
|
||||
path = static_cast<QFileOpenEvent *>(ev)->url().toString();
|
||||
qDebug("Received a mac file open event: %s", qUtf8Printable(path));
|
||||
|
||||
const AddTorrentParams params = parseParams({path});
|
||||
QBtCommandLineParameters params;
|
||||
params.torrentSources.append(path);
|
||||
// If Application is not allowed to process params immediately
|
||||
// (i.e., other components are not ready) store params
|
||||
if (m_isProcessingParamsAllowed)
|
||||
@@ -1039,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));
|
||||
@@ -1201,6 +1299,7 @@ void Application::cleanup()
|
||||
::ShutdownBlockReasonDestroy(reinterpret_cast<HWND>(m_window->effectiveWinId()));
|
||||
#endif // Q_OS_WIN
|
||||
delete m_window;
|
||||
delete m_desktopIntegration;
|
||||
UIThemeManager::freeInstance();
|
||||
}
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
@@ -96,10 +96,10 @@ public:
|
||||
Application(int &argc, char **argv);
|
||||
~Application() override;
|
||||
|
||||
int exec(const QStringList ¶ms);
|
||||
int exec();
|
||||
|
||||
bool isRunning();
|
||||
bool sendParams(const QStringList ¶ms);
|
||||
bool callMainInstance();
|
||||
const QBtCommandLineParameters &commandLineArgs() const;
|
||||
|
||||
// FileLogger properties
|
||||
@@ -130,6 +130,9 @@ public:
|
||||
DesktopIntegration *desktopIntegration() override;
|
||||
MainWindow *mainWindow() override;
|
||||
|
||||
WindowState startUpWindowState() const override;
|
||||
void setStartUpWindowState(WindowState windowState) override;
|
||||
|
||||
bool isTorrentAddedNotificationsEnabled() const override;
|
||||
void setTorrentAddedNotificationsEnabled(bool value) override;
|
||||
#endif
|
||||
@@ -146,16 +149,8 @@ private slots:
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct AddTorrentParams
|
||||
{
|
||||
QString torrentSource;
|
||||
BitTorrent::AddTorrentParams torrentParams;
|
||||
std::optional<bool> skipTorrentDialog;
|
||||
};
|
||||
|
||||
void initializeTranslation();
|
||||
AddTorrentParams parseParams(const QStringList ¶ms) const;
|
||||
void processParams(const AddTorrentParams ¶ms);
|
||||
void processParams(const QBtCommandLineParameters ¶ms);
|
||||
void runExternalProgram(const QString &programTemplate, const BitTorrent::Torrent *torrent) const;
|
||||
void sendNotificationEmail(const BitTorrent::Torrent *torrent);
|
||||
|
||||
@@ -178,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
|
||||
@@ -187,7 +182,7 @@ private:
|
||||
QTranslator m_qtTranslator;
|
||||
QTranslator m_translator;
|
||||
|
||||
QList<AddTorrentParams> m_paramsQueue;
|
||||
QList<QBtCommandLineParameters> m_paramsQueue;
|
||||
|
||||
SettingValue<bool> m_storeFileLoggerEnabled;
|
||||
SettingValue<bool> m_storeFileLoggerBackup;
|
||||
@@ -203,6 +198,7 @@ private:
|
||||
#endif
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
SettingValue<WindowState> m_startUpWindowState;
|
||||
SettingValue<bool> m_storeNotificationTorrentAdded;
|
||||
|
||||
DesktopIntegration *m_desktopIntegration = nullptr;
|
||||
|
||||
@@ -198,13 +198,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'",
|
||||
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=<value>'")
|
||||
.arg(fullParameter(), u"<integer value>"_qs));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -340,14 +342,7 @@ namespace
|
||||
}
|
||||
|
||||
QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &env)
|
||||
: showHelp(false)
|
||||
, relativeFastresumePaths(RELATIVE_FASTRESUME.value(env))
|
||||
, skipChecking(SKIP_HASH_CHECK_OPTION.value(env))
|
||||
, sequential(SEQUENTIAL_OPTION.value(env))
|
||||
, firstLastPiecePriority(FIRST_AND_LAST_OPTION.value(env))
|
||||
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
|
||||
, showVersion(false)
|
||||
#endif
|
||||
: relativeFastresumePaths(RELATIVE_FASTRESUME.value(env))
|
||||
#ifndef DISABLE_GUI
|
||||
, noSplash(NO_SPLASH_OPTION.value(env))
|
||||
#elif !defined(Q_OS_WIN)
|
||||
@@ -355,49 +350,16 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en
|
||||
#endif
|
||||
, webUiPort(WEBUI_PORT_OPTION.value(env, -1))
|
||||
, torrentingPort(TORRENTING_PORT_OPTION.value(env, -1))
|
||||
, addPaused(PAUSED_OPTION.value(env))
|
||||
, skipDialog(SKIP_DIALOG_OPTION.value(env))
|
||||
, profileDir(PROFILE_OPTION.value(env))
|
||||
, configurationName(CONFIGURATION_OPTION.value(env))
|
||||
, savePath(SAVE_PATH_OPTION.value(env))
|
||||
, category(CATEGORY_OPTION.value(env))
|
||||
{
|
||||
}
|
||||
|
||||
QStringList QBtCommandLineParameters::paramList() const
|
||||
{
|
||||
QStringList result;
|
||||
// Because we're passing a string list to the currently running
|
||||
// qBittorrent process, we need some way of passing along the options
|
||||
// the user has specified. Here we place special strings that are
|
||||
// almost certainly not going to collide with a file path or URL
|
||||
// specified by the user, and placing them at the beginning of the
|
||||
// string list so that they will be processed before the list of
|
||||
// torrent paths or URLs.
|
||||
|
||||
if (!savePath.isEmpty())
|
||||
result.append(u"@savePath=" + savePath.data());
|
||||
|
||||
if (addPaused.has_value())
|
||||
result.append(*addPaused ? u"@addPaused=1"_qs : u"@addPaused=0"_qs);
|
||||
|
||||
if (skipChecking)
|
||||
result.append(u"@skipChecking"_qs);
|
||||
|
||||
if (!category.isEmpty())
|
||||
result.append(u"@category=" + category);
|
||||
|
||||
if (sequential)
|
||||
result.append(u"@sequential"_qs);
|
||||
|
||||
if (firstLastPiecePriority)
|
||||
result.append(u"@firstLastPiecePriority"_qs);
|
||||
|
||||
if (skipDialog.has_value())
|
||||
result.append(*skipDialog ? u"@skipDialog=1"_qs : u"@skipDialog=0"_qs);
|
||||
|
||||
result += torrents;
|
||||
return result;
|
||||
addTorrentParams.savePath = Path(SAVE_PATH_OPTION.value(env));
|
||||
addTorrentParams.category = CATEGORY_OPTION.value(env);
|
||||
addTorrentParams.skipChecking = SKIP_HASH_CHECK_OPTION.value(env);
|
||||
addTorrentParams.sequential = SEQUENTIAL_OPTION.value(env);
|
||||
addTorrentParams.firstLastPiecePriority = FIRST_AND_LAST_OPTION.value(env);
|
||||
addTorrentParams.addPaused = PAUSED_OPTION.value(env);
|
||||
}
|
||||
|
||||
QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
||||
@@ -463,27 +425,27 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
||||
}
|
||||
else if (arg == SAVE_PATH_OPTION)
|
||||
{
|
||||
result.savePath = Path(SAVE_PATH_OPTION.value(arg));
|
||||
result.addTorrentParams.savePath = Path(SAVE_PATH_OPTION.value(arg));
|
||||
}
|
||||
else if (arg == PAUSED_OPTION)
|
||||
{
|
||||
result.addPaused = PAUSED_OPTION.value(arg);
|
||||
result.addTorrentParams.addPaused = PAUSED_OPTION.value(arg);
|
||||
}
|
||||
else if (arg == SKIP_HASH_CHECK_OPTION)
|
||||
{
|
||||
result.skipChecking = true;
|
||||
result.addTorrentParams.skipChecking = true;
|
||||
}
|
||||
else if (arg == CATEGORY_OPTION)
|
||||
{
|
||||
result.category = CATEGORY_OPTION.value(arg);
|
||||
result.addTorrentParams.category = CATEGORY_OPTION.value(arg);
|
||||
}
|
||||
else if (arg == SEQUENTIAL_OPTION)
|
||||
{
|
||||
result.sequential = true;
|
||||
result.addTorrentParams.sequential = true;
|
||||
}
|
||||
else if (arg == FIRST_AND_LAST_OPTION)
|
||||
{
|
||||
result.firstLastPiecePriority = true;
|
||||
result.addTorrentParams.firstLastPiecePriority = true;
|
||||
}
|
||||
else if (arg == SKIP_DIALOG_OPTION)
|
||||
{
|
||||
@@ -502,9 +464,9 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
||||
torrentPath.setFile(arg);
|
||||
|
||||
if (torrentPath.exists())
|
||||
result.torrents += torrentPath.absoluteFilePath();
|
||||
result.torrentSources += torrentPath.absoluteFilePath();
|
||||
else
|
||||
result.torrents += arg;
|
||||
result.torrentSources += arg;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include "base/bittorrent/addtorrentparams.h"
|
||||
#include "base/exceptions.h"
|
||||
#include "base/path.h"
|
||||
|
||||
@@ -42,32 +43,29 @@ class QProcessEnvironment;
|
||||
|
||||
struct QBtCommandLineParameters
|
||||
{
|
||||
bool showHelp;
|
||||
bool relativeFastresumePaths;
|
||||
bool skipChecking;
|
||||
bool sequential;
|
||||
bool firstLastPiecePriority;
|
||||
bool showHelp = false;
|
||||
bool relativeFastresumePaths = false;
|
||||
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
|
||||
bool showVersion;
|
||||
bool showVersion = false;
|
||||
#endif
|
||||
#ifndef DISABLE_GUI
|
||||
bool noSplash;
|
||||
bool noSplash = false;
|
||||
#elif !defined(Q_OS_WIN)
|
||||
bool shouldDaemonize;
|
||||
bool shouldDaemonize = false;
|
||||
#endif
|
||||
int webUiPort;
|
||||
int torrentingPort;
|
||||
std::optional<bool> addPaused;
|
||||
int webUiPort = -1;
|
||||
int torrentingPort = -1;
|
||||
std::optional<bool> skipDialog;
|
||||
QStringList torrents;
|
||||
Path profileDir;
|
||||
QString configurationName;
|
||||
Path savePath;
|
||||
QString category;
|
||||
|
||||
QStringList torrentSources;
|
||||
BitTorrent::AddTorrentParams addTorrentParams;
|
||||
|
||||
QString unknownParameter;
|
||||
|
||||
QBtCommandLineParameters() = default;
|
||||
explicit QBtCommandLineParameters(const QProcessEnvironment &);
|
||||
QStringList paramList() const;
|
||||
};
|
||||
|
||||
class CommandLineParameterError : public RuntimeError
|
||||
|
||||
@@ -118,6 +118,17 @@ int main(int argc, char *argv[])
|
||||
// Create Application
|
||||
auto app = std::make_unique<Application>(argc, argv);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// QCoreApplication::applicationDirPath() needs an Application object instantiated first
|
||||
// Let's hope that there won't be a crash before this line
|
||||
const char *envName = "_NT_SYMBOL_PATH";
|
||||
const QString envValue = qEnvironmentVariable(envName);
|
||||
if (envValue.isEmpty())
|
||||
qputenv(envName, Application::applicationDirPath().toLocal8Bit());
|
||||
else
|
||||
qputenv(envName, u"%1;%2"_qs.arg(envValue, Application::applicationDirPath()).toLocal8Bit());
|
||||
#endif
|
||||
|
||||
const QBtCommandLineParameters params = app->commandLineArgs();
|
||||
if (!params.unknownParameter.isEmpty())
|
||||
{
|
||||
@@ -182,7 +193,7 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
QThread::msleep(300);
|
||||
app->sendParams(params.paramList());
|
||||
app->callMainInstance();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -221,26 +232,6 @@ int main(int argc, char *argv[])
|
||||
app->setAttribute(Qt::AA_DontShowIconsInMenus);
|
||||
#endif
|
||||
|
||||
if (!firstTimeUser)
|
||||
{
|
||||
handleChangedDefaults(DefaultPreferencesMode::Legacy);
|
||||
|
||||
#ifndef DISABLE_GUI
|
||||
if (!upgrade()) return EXIT_FAILURE;
|
||||
#elif defined(Q_OS_WIN)
|
||||
if (!upgrade(_isatty(_fileno(stdin))
|
||||
&& _isatty(_fileno(stdout)))) return EXIT_FAILURE;
|
||||
#else
|
||||
if (!upgrade(!params.shouldDaemonize
|
||||
&& isatty(fileno(stdin))
|
||||
&& isatty(fileno(stdout)))) return EXIT_FAILURE;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
handleChangedDefaults(DefaultPreferencesMode::Current);
|
||||
}
|
||||
|
||||
#if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
|
||||
if (params.shouldDaemonize)
|
||||
{
|
||||
@@ -267,13 +258,18 @@ int main(int argc, char *argv[])
|
||||
|
||||
registerSignalHandlers();
|
||||
|
||||
return app->exec(params.paramList());
|
||||
return app->exec();
|
||||
}
|
||||
catch (const CommandLineParameterError &er)
|
||||
{
|
||||
displayBadArgMessage(er.message());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
catch (const RuntimeError &er)
|
||||
{
|
||||
qDebug() << er.message();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(DISABLE_GUI)
|
||||
@@ -287,7 +283,7 @@ void showSplashScreen()
|
||||
painter.drawText(224 - painter.fontMetrics().horizontalAdvance(version), 270, version);
|
||||
QSplashScreen *splash = new QSplashScreen(splashImg);
|
||||
splash->show();
|
||||
QTimer::singleShot(1500ms, splash, &QObject::deleteLater);
|
||||
QTimer::singleShot(1500ms, Qt::CoarseTimer, splash, &QObject::deleteLater);
|
||||
qApp->processEvents();
|
||||
}
|
||||
#endif // DISABLE_GUI
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -39,13 +39,12 @@
|
||||
#include "base/profile.h"
|
||||
#include "base/settingsstorage.h"
|
||||
#include "base/settingvalue.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/io.h"
|
||||
#include "base/utils/string.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
const int MIGRATION_VERSION = 4;
|
||||
const int MIGRATION_VERSION = 6;
|
||||
const QString MIGRATION_VERSION_KEY = u"Meta/MigrationVersion"_qs;
|
||||
|
||||
void exportWebUIHttpsFiles()
|
||||
@@ -344,7 +343,7 @@ namespace
|
||||
switch (number)
|
||||
{
|
||||
case 0:
|
||||
settingsStorage->storeValue(key, Net::ProxyType::None);
|
||||
settingsStorage->storeValue(key, u"None"_qs);
|
||||
break;
|
||||
case 1:
|
||||
settingsStorage->storeValue(key, Net::ProxyType::HTTP);
|
||||
@@ -353,10 +352,10 @@ namespace
|
||||
settingsStorage->storeValue(key, Net::ProxyType::SOCKS5);
|
||||
break;
|
||||
case 3:
|
||||
settingsStorage->storeValue(key, Net::ProxyType::HTTP_PW);
|
||||
settingsStorage->storeValue(key, u"HTTP_PW"_qs);
|
||||
break;
|
||||
case 4:
|
||||
settingsStorage->storeValue(key, Net::ProxyType::SOCKS5_PW);
|
||||
settingsStorage->storeValue(key, u"SOCKS5_PW"_qs);
|
||||
break;
|
||||
case 5:
|
||||
settingsStorage->storeValue(key, Net::ProxyType::SOCKS4);
|
||||
@@ -370,6 +369,46 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void migrateProxySettings()
|
||||
{
|
||||
auto *settingsStorage = SettingsStorage::instance();
|
||||
const auto proxyType = settingsStorage->loadValue<QString>(u"Network/Proxy/Type"_qs, u"None"_qs);
|
||||
const auto onlyForTorrents = settingsStorage->loadValue<bool>(u"Network/Proxy/OnlyForTorrents"_qs)
|
||||
|| (proxyType == u"SOCKS4");
|
||||
|
||||
if (proxyType == u"None")
|
||||
{
|
||||
settingsStorage->storeValue(u"Network/Proxy/Type"_qs, Net::ProxyType::HTTP);
|
||||
|
||||
settingsStorage->storeValue(u"Network/Proxy/Profiles/BitTorrent"_qs, false);
|
||||
settingsStorage->storeValue(u"Network/Proxy/Profiles/RSS"_qs, false);
|
||||
settingsStorage->storeValue(u"Network/Proxy/Profiles/Misc"_qs, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsStorage->storeValue(u"Network/Proxy/Profiles/BitTorrent"_qs, true);
|
||||
settingsStorage->storeValue(u"Network/Proxy/Profiles/RSS"_qs, !onlyForTorrents);
|
||||
settingsStorage->storeValue(u"Network/Proxy/Profiles/Misc"_qs, !onlyForTorrents);
|
||||
|
||||
if (proxyType == u"HTTP_PW"_qs)
|
||||
{
|
||||
settingsStorage->storeValue(u"Network/Proxy/Type"_qs, Net::ProxyType::HTTP);
|
||||
settingsStorage->storeValue(u"Network/Proxy/AuthEnabled"_qs, true);
|
||||
}
|
||||
else if (proxyType == u"SOCKS5_PW"_qs)
|
||||
{
|
||||
settingsStorage->storeValue(u"Network/Proxy/Type"_qs, Net::ProxyType::SOCKS5);
|
||||
settingsStorage->storeValue(u"Network/Proxy/AuthEnabled"_qs, true);
|
||||
}
|
||||
}
|
||||
|
||||
settingsStorage->removeValue(u"Network/Proxy/OnlyForTorrents"_qs);
|
||||
|
||||
const auto proxyHostnameLookup = settingsStorage->loadValue<bool>(u"BitTorrent/Session/ProxyHostnameLookup"_qs);
|
||||
settingsStorage->storeValue(u"Network/Proxy/HostnameLookupEnabled"_qs, proxyHostnameLookup);
|
||||
settingsStorage->removeValue(u"BitTorrent/Session/ProxyHostnameLookup"_qs);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
void migrateMemoryPrioritySettings()
|
||||
{
|
||||
@@ -384,9 +423,33 @@ namespace
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void migrateStartupWindowState()
|
||||
{
|
||||
auto *settingsStorage = SettingsStorage::instance();
|
||||
if (settingsStorage->hasKey(u"Preferences/General/StartMinimized"_qs))
|
||||
{
|
||||
const auto startMinimized = settingsStorage->loadValue<bool>(u"Preferences/General/StartMinimized"_qs);
|
||||
const auto minimizeToTray = settingsStorage->loadValue<bool>(u"Preferences/General/MinimizeToTray"_qs);
|
||||
const QString windowState = startMinimized ? (minimizeToTray ? u"Hidden"_qs : u"Minimized"_qs) : u"Normal"_qs;
|
||||
settingsStorage->storeValue(u"GUI/StartUpWindowState"_qs, windowState);
|
||||
}
|
||||
}
|
||||
|
||||
void migrateChineseLocale()
|
||||
{
|
||||
auto *settingsStorage = SettingsStorage::instance();
|
||||
const auto key = u"Preferences/General/Locale"_qs;
|
||||
if (settingsStorage->hasKey(key))
|
||||
{
|
||||
const auto locale = settingsStorage->loadValue<QString>(key);
|
||||
if (locale.compare(u"zh"_qs, Qt::CaseInsensitive) == 0)
|
||||
settingsStorage->storeValue(key, u"zh_CN"_qs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool upgrade(const bool /*ask*/)
|
||||
bool upgrade()
|
||||
{
|
||||
CachedSettingValue<int> version {MIGRATION_VERSION_KEY, 0};
|
||||
|
||||
@@ -413,6 +476,15 @@ bool upgrade(const bool /*ask*/)
|
||||
migrateMemoryPrioritySettings();
|
||||
#endif
|
||||
|
||||
if (version < 5)
|
||||
{
|
||||
migrateStartupWindowState();
|
||||
migrateChineseLocale();
|
||||
}
|
||||
|
||||
if (version < 6)
|
||||
migrateProxySettings();
|
||||
|
||||
version = MIGRATION_VERSION;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,5 +35,5 @@ enum class DefaultPreferencesMode
|
||||
};
|
||||
|
||||
void handleChangedDefaults(DefaultPreferencesMode mode);
|
||||
bool upgrade(bool ask = true);
|
||||
bool upgrade();
|
||||
void setCurrentMigrationVersion();
|
||||
|
||||
@@ -34,6 +34,7 @@ add_library(qbt_base STATIC
|
||||
bittorrent/sessionstatus.h
|
||||
bittorrent/speedmonitor.h
|
||||
bittorrent/torrent.h
|
||||
bittorrent/torrentcontenthandler.h
|
||||
bittorrent/torrentcontentlayout.h
|
||||
bittorrent/torrentcreatorthread.h
|
||||
bittorrent/torrentimpl.h
|
||||
@@ -101,6 +102,7 @@ add_library(qbt_base STATIC
|
||||
utils/password.h
|
||||
utils/random.h
|
||||
utils/string.h
|
||||
utils/thread.h
|
||||
utils/version.h
|
||||
version.h
|
||||
|
||||
@@ -108,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
|
||||
@@ -128,6 +131,7 @@ add_library(qbt_base STATIC
|
||||
bittorrent/sessionimpl.cpp
|
||||
bittorrent/speedmonitor.cpp
|
||||
bittorrent/torrent.cpp
|
||||
bittorrent/torrentcontenthandler.cpp
|
||||
bittorrent/torrentcreatorthread.cpp
|
||||
bittorrent/torrentimpl.cpp
|
||||
bittorrent/torrentinfo.cpp
|
||||
@@ -183,6 +187,7 @@ add_library(qbt_base STATIC
|
||||
utils/password.cpp
|
||||
utils/random.cpp
|
||||
utils/string.cpp
|
||||
utils/thread.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(qbt_base
|
||||
|
||||
@@ -63,4 +63,11 @@ namespace Algorithm
|
||||
while (it != set.end())
|
||||
it = (p(*it) ? set.erase(it) : ++it);
|
||||
}
|
||||
|
||||
template <typename List>
|
||||
List sorted(List list)
|
||||
{
|
||||
list.sort();
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ HEADERS += \
|
||||
$$PWD/bittorrent/speedmonitor.h \
|
||||
$$PWD/bittorrent/torrent.h \
|
||||
$$PWD/bittorrent/torrentcontentlayout.h \
|
||||
$$PWD/bittorrent/torrentcontenthandler.h \
|
||||
$$PWD/bittorrent/torrentcreatorthread.h \
|
||||
$$PWD/bittorrent/torrentimpl.h \
|
||||
$$PWD/bittorrent/torrentinfo.h \
|
||||
@@ -101,6 +102,7 @@ HEADERS += \
|
||||
$$PWD/utils/password.h \
|
||||
$$PWD/utils/random.h \
|
||||
$$PWD/utils/string.h \
|
||||
$$PWD/utils/thread.h \
|
||||
$$PWD/utils/version.h \
|
||||
$$PWD/version.h
|
||||
|
||||
@@ -108,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 \
|
||||
@@ -128,6 +131,7 @@ SOURCES += \
|
||||
$$PWD/bittorrent/sessionimpl.cpp \
|
||||
$$PWD/bittorrent/speedmonitor.cpp \
|
||||
$$PWD/bittorrent/torrent.cpp \
|
||||
$$PWD/bittorrent/torrentcontenthandler.h \
|
||||
$$PWD/bittorrent/torrentcreatorthread.cpp \
|
||||
$$PWD/bittorrent/torrentimpl.cpp \
|
||||
$$PWD/bittorrent/torrentinfo.cpp \
|
||||
@@ -182,4 +186,5 @@ SOURCES += \
|
||||
$$PWD/utils/net.cpp \
|
||||
$$PWD/utils/password.cpp \
|
||||
$$PWD/utils/random.cpp \
|
||||
$$PWD/utils/string.cpp
|
||||
$$PWD/utils/string.cpp \
|
||||
$$PWD/utils/thread.cpp
|
||||
|
||||
170
src/base/bittorrent/addtorrentparams.cpp
Normal file
170
src/base/bittorrent/addtorrentparams.cpp
Normal 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 ¶ms)
|
||||
{
|
||||
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;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -54,6 +56,7 @@ namespace BitTorrent
|
||||
bool sequential = false;
|
||||
bool firstLastPiecePriority = false;
|
||||
bool addForced = false;
|
||||
std::optional<bool> addToQueueTop;
|
||||
std::optional<bool> addPaused;
|
||||
std::optional<Torrent::StopCondition> stopCondition;
|
||||
PathList filePaths; // used if TorrentInfo is set
|
||||
@@ -66,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 ¶ms);
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(BitTorrent::AddTorrentParams)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -49,5 +49,5 @@ private:
|
||||
void onTimeout();
|
||||
|
||||
QTimer m_timer;
|
||||
bool m_lastAlternative;
|
||||
bool m_lastAlternative = false;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -91,7 +91,7 @@ namespace
|
||||
|
||||
BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path, QObject *parent)
|
||||
: ResumeDataStorage(path, parent)
|
||||
, m_ioThread {new QThread(this)}
|
||||
, m_ioThread {new QThread}
|
||||
, m_asyncWorker {new Worker(path)}
|
||||
{
|
||||
Q_ASSERT(path.isAbsolute());
|
||||
@@ -117,17 +117,11 @@ BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path,
|
||||
|
||||
qDebug() << "Registered torrents count: " << m_registeredTorrents.size();
|
||||
|
||||
m_asyncWorker->moveToThread(m_ioThread);
|
||||
connect(m_ioThread, &QThread::finished, m_asyncWorker, &QObject::deleteLater);
|
||||
m_asyncWorker->moveToThread(m_ioThread.get());
|
||||
connect(m_ioThread.get(), &QThread::finished, m_asyncWorker, &QObject::deleteLater);
|
||||
m_ioThread->start();
|
||||
}
|
||||
|
||||
BitTorrent::BencodeResumeDataStorage::~BencodeResumeDataStorage()
|
||||
{
|
||||
m_ioThread->quit();
|
||||
m_ioThread->wait();
|
||||
}
|
||||
|
||||
QVector<BitTorrent::TorrentID> BitTorrent::BencodeResumeDataStorage::registeredTorrents() const
|
||||
{
|
||||
return m_registeredTorrents;
|
||||
@@ -210,10 +204,9 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
|
||||
return nonstd::make_unexpected(tr("Cannot parse resume data: invalid format"));
|
||||
|
||||
LoadTorrentParams torrentParams;
|
||||
torrentParams.restored = true;
|
||||
torrentParams.category = fromLTString(resumeDataRoot.dict_find_string_value("qBt-category"));
|
||||
torrentParams.name = fromLTString(resumeDataRoot.dict_find_string_value("qBt-name"));
|
||||
torrentParams.hasSeedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus");
|
||||
torrentParams.hasFinishedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus");
|
||||
torrentParams.firstLastPiecePriority = resumeDataRoot.dict_find_int_value("qBt-firstLastPiecePriority");
|
||||
torrentParams.seedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME);
|
||||
|
||||
@@ -387,7 +380,7 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
|
||||
data["qBt-category"] = resumeData.category.toStdString();
|
||||
data["qBt-tags"] = setToEntryList(resumeData.tags);
|
||||
data["qBt-name"] = resumeData.name.toStdString();
|
||||
data["qBt-seedStatus"] = resumeData.hasSeedStatus;
|
||||
data["qBt-seedStatus"] = resumeData.hasFinishedStatus;
|
||||
data["qBt-contentLayout"] = Utils::String::fromEnum(resumeData.contentLayout).toStdString();
|
||||
data["qBt-firstLastPiecePriority"] = resumeData.firstLastPiecePriority;
|
||||
data["qBt-stopCondition"] = Utils::String::fromEnum(resumeData.stopCondition).toStdString();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -32,6 +32,8 @@
|
||||
#include <QVector>
|
||||
|
||||
#include "base/pathfwd.h"
|
||||
#include "base/utils/thread.h"
|
||||
|
||||
#include "resumedatastorage.h"
|
||||
|
||||
class QByteArray;
|
||||
@@ -46,7 +48,6 @@ namespace BitTorrent
|
||||
|
||||
public:
|
||||
explicit BencodeResumeDataStorage(const Path &path, QObject *parent = nullptr);
|
||||
~BencodeResumeDataStorage() override;
|
||||
|
||||
QVector<TorrentID> registeredTorrents() const override;
|
||||
LoadResumeDataResult load(const TorrentID &id) const override;
|
||||
@@ -60,7 +61,7 @@ namespace BitTorrent
|
||||
LoadResumeDataResult loadTorrentResumeData(const QByteArray &data, const QByteArray &metadata) const;
|
||||
|
||||
QVector<TorrentID> m_registeredTorrents;
|
||||
QThread *m_ioThread = nullptr;
|
||||
Utils::Thread::UniquePtr m_ioThread;
|
||||
|
||||
class Worker;
|
||||
Worker *m_asyncWorker = nullptr;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2021-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
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
#include "dbresumedatastorage.h"
|
||||
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <utility>
|
||||
|
||||
#include <libtorrent/bdecode.hpp>
|
||||
@@ -38,7 +40,9 @@
|
||||
#include <libtorrent/write_resume_data.hpp>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QMutex>
|
||||
#include <QSet>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlError>
|
||||
@@ -46,6 +50,7 @@
|
||||
#include <QSqlRecord>
|
||||
#include <QThread>
|
||||
#include <QVector>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "base/exceptions.h"
|
||||
#include "base/global.h"
|
||||
@@ -61,13 +66,53 @@ namespace
|
||||
{
|
||||
const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_qs;
|
||||
|
||||
const int DB_VERSION = 3;
|
||||
const int DB_VERSION = 4;
|
||||
|
||||
const QString DB_TABLE_META = u"meta"_qs;
|
||||
const QString DB_TABLE_TORRENTS = u"torrents"_qs;
|
||||
|
||||
const QString META_VERSION = u"version"_qs;
|
||||
|
||||
using namespace BitTorrent;
|
||||
|
||||
class Job
|
||||
{
|
||||
public:
|
||||
virtual ~Job() = default;
|
||||
virtual void perform(QSqlDatabase db) = 0;
|
||||
};
|
||||
|
||||
class StoreJob final : public Job
|
||||
{
|
||||
public:
|
||||
StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData);
|
||||
void perform(QSqlDatabase db) override;
|
||||
|
||||
private:
|
||||
const TorrentID m_torrentID;
|
||||
const LoadTorrentParams m_resumeData;
|
||||
};
|
||||
|
||||
class RemoveJob final : public Job
|
||||
{
|
||||
public:
|
||||
explicit RemoveJob(const TorrentID &torrentID);
|
||||
void perform(QSqlDatabase db) override;
|
||||
|
||||
private:
|
||||
const TorrentID m_torrentID;
|
||||
};
|
||||
|
||||
class StoreQueueJob final : public Job
|
||||
{
|
||||
public:
|
||||
explicit StoreQueueJob(const QVector<TorrentID> &queue);
|
||||
void perform(QSqlDatabase db) override;
|
||||
|
||||
private:
|
||||
const QVector<TorrentID> m_queue;
|
||||
};
|
||||
|
||||
struct Column
|
||||
{
|
||||
QString name;
|
||||
@@ -167,97 +212,100 @@ namespace
|
||||
{
|
||||
return u"%1 %2"_qs.arg(quoted(column.name), QString::fromLatin1(definition));
|
||||
}
|
||||
|
||||
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
|
||||
{
|
||||
LoadTorrentParams resumeData;
|
||||
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
|
||||
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
|
||||
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
|
||||
if (!tagsData.isEmpty())
|
||||
{
|
||||
const QStringList tagList = tagsData.split(u',');
|
||||
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
|
||||
}
|
||||
resumeData.hasFinishedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
|
||||
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
|
||||
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
|
||||
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
|
||||
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
|
||||
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
|
||||
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
|
||||
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
|
||||
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
|
||||
resumeData.stopCondition = Utils::String::toEnum(
|
||||
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
|
||||
|
||||
resumeData.savePath = Profile::instance()->fromPortablePath(
|
||||
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
|
||||
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
|
||||
if (!resumeData.useAutoTMM)
|
||||
{
|
||||
resumeData.downloadPath = Profile::instance()->fromPortablePath(
|
||||
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
|
||||
}
|
||||
|
||||
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
|
||||
|
||||
lt::error_code ec;
|
||||
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec);
|
||||
|
||||
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
|
||||
|
||||
p = lt::read_resume_data(resumeDataRoot, ec);
|
||||
|
||||
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray()
|
||||
; !bencodedMetadata.isEmpty())
|
||||
{
|
||||
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec);
|
||||
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
|
||||
}
|
||||
|
||||
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
|
||||
.toString().toStdString();
|
||||
|
||||
if (p.flags & lt::torrent_flags::stop_when_ready)
|
||||
{
|
||||
p.flags &= ~lt::torrent_flags::stop_when_ready;
|
||||
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
|
||||
}
|
||||
|
||||
return resumeData;
|
||||
}
|
||||
}
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class DBResumeDataStorage::Worker final : public QObject
|
||||
class DBResumeDataStorage::Worker final : public QThread
|
||||
{
|
||||
Q_DISABLE_COPY_MOVE(Worker)
|
||||
|
||||
public:
|
||||
Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock);
|
||||
Worker(const Path &dbPath, QReadWriteLock &dbLock);
|
||||
|
||||
void openDatabase() const;
|
||||
void closeDatabase() const;
|
||||
void run() override;
|
||||
void requestInterruption();
|
||||
|
||||
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const;
|
||||
void remove(const TorrentID &id) const;
|
||||
void storeQueue(const QVector<TorrentID> &queue) const;
|
||||
void store(const TorrentID &id, const LoadTorrentParams &resumeData);
|
||||
void remove(const TorrentID &id);
|
||||
void storeQueue(const QVector<TorrentID> &queue);
|
||||
|
||||
private:
|
||||
void addJob(std::unique_ptr<Job> job);
|
||||
|
||||
const QString m_connectionName = u"ResumeDataStorageWorker"_qs;
|
||||
const Path m_path;
|
||||
const QString m_connectionName;
|
||||
QReadWriteLock &m_dbLock;
|
||||
|
||||
std::queue<std::unique_ptr<Job>> m_jobs;
|
||||
QMutex m_jobsMutex;
|
||||
QWaitCondition m_waitCondition;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
|
||||
{
|
||||
LoadTorrentParams resumeData;
|
||||
resumeData.restored = true;
|
||||
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
|
||||
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
|
||||
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
|
||||
if (!tagsData.isEmpty())
|
||||
{
|
||||
const QStringList tagList = tagsData.split(u',');
|
||||
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
|
||||
}
|
||||
resumeData.hasSeedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
|
||||
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
|
||||
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
|
||||
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
|
||||
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
|
||||
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
|
||||
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
|
||||
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
|
||||
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
|
||||
resumeData.stopCondition = Utils::String::toEnum(
|
||||
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
|
||||
|
||||
resumeData.savePath = Profile::instance()->fromPortablePath(
|
||||
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
|
||||
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
|
||||
if (!resumeData.useAutoTMM)
|
||||
{
|
||||
resumeData.downloadPath = Profile::instance()->fromPortablePath(
|
||||
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
|
||||
}
|
||||
|
||||
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
|
||||
|
||||
lt::error_code ec;
|
||||
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec);
|
||||
|
||||
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
|
||||
|
||||
p = lt::read_resume_data(resumeDataRoot, ec);
|
||||
|
||||
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray(); !bencodedMetadata.isEmpty())
|
||||
{
|
||||
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec);
|
||||
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
|
||||
}
|
||||
|
||||
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
|
||||
.toString().toStdString();
|
||||
|
||||
if (p.flags & lt::torrent_flags::stop_when_ready)
|
||||
{
|
||||
p.flags &= ~lt::torrent_flags::stop_when_ready;
|
||||
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
|
||||
}
|
||||
|
||||
return resumeData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent)
|
||||
: ResumeDataStorage(dbPath, parent)
|
||||
, m_ioThread {new QThread(this)}
|
||||
, m_ioThread {new QThread}
|
||||
{
|
||||
const bool needCreateDB = !dbPath.exists();
|
||||
|
||||
@@ -273,39 +321,19 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject
|
||||
else
|
||||
{
|
||||
const int dbVersion = (!db.record(DB_TABLE_TORRENTS).contains(DB_COLUMN_DOWNLOAD_PATH.name) ? 1 : currentDBVersion());
|
||||
if (dbVersion != DB_VERSION)
|
||||
if (dbVersion < DB_VERSION)
|
||||
updateDB(dbVersion);
|
||||
}
|
||||
|
||||
m_asyncWorker = new Worker(dbPath, u"ResumeDataStorageWorker"_qs, m_dbLock);
|
||||
m_asyncWorker->moveToThread(m_ioThread);
|
||||
connect(m_ioThread, &QThread::finished, m_asyncWorker, &QObject::deleteLater);
|
||||
m_ioThread->start();
|
||||
|
||||
RuntimeError *errPtr = nullptr;
|
||||
QMetaObject::invokeMethod(m_asyncWorker, [this, &errPtr]()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_asyncWorker->openDatabase();
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
errPtr = new RuntimeError(err);
|
||||
}
|
||||
}, Qt::BlockingQueuedConnection);
|
||||
|
||||
if (errPtr)
|
||||
throw *errPtr;
|
||||
m_asyncWorker = new Worker(dbPath, m_dbLock);
|
||||
m_asyncWorker->start();
|
||||
}
|
||||
|
||||
BitTorrent::DBResumeDataStorage::~DBResumeDataStorage()
|
||||
{
|
||||
QMetaObject::invokeMethod(m_asyncWorker, &Worker::closeDatabase);
|
||||
m_asyncWorker->requestInterruption();
|
||||
m_asyncWorker->wait();
|
||||
QSqlDatabase::removeDatabase(DB_CONNECTION_NAME);
|
||||
|
||||
m_ioThread->quit();
|
||||
m_ioThread->wait();
|
||||
}
|
||||
|
||||
QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents() const
|
||||
@@ -357,26 +385,17 @@ BitTorrent::LoadResumeDataResult BitTorrent::DBResumeDataStorage::load(const Tor
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
|
||||
{
|
||||
QMetaObject::invokeMethod(m_asyncWorker, [this, id, resumeData]()
|
||||
{
|
||||
m_asyncWorker->store(id, resumeData);
|
||||
});
|
||||
m_asyncWorker->store(id, resumeData);
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::remove(const BitTorrent::TorrentID &id) const
|
||||
{
|
||||
QMetaObject::invokeMethod(m_asyncWorker, [this, id]()
|
||||
{
|
||||
m_asyncWorker->remove(id);
|
||||
});
|
||||
m_asyncWorker->remove(id);
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::storeQueue(const QVector<TorrentID> &queue) const
|
||||
{
|
||||
QMetaObject::invokeMethod(m_asyncWorker, [this, queue]()
|
||||
{
|
||||
m_asyncWorker->storeQueue(queue);
|
||||
});
|
||||
m_asyncWorker->storeQueue(queue);
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::doLoadAll() const
|
||||
@@ -453,9 +472,17 @@ int BitTorrent::DBResumeDataStorage::currentDBVersion() const
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::createDB() const
|
||||
{
|
||||
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
|
||||
try
|
||||
{
|
||||
enableWALMode();
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
LogMsg(tr("Couldn't enable Write-Ahead Logging (WAL) journaling mode. Error: %1.")
|
||||
.arg(err.message()), Log::WARNING);
|
||||
}
|
||||
|
||||
const QWriteLocker locker {&m_dbLock};
|
||||
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
|
||||
|
||||
if (!db.transaction())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
@@ -507,6 +534,12 @@ void BitTorrent::DBResumeDataStorage::createDB() const
|
||||
if (!query.exec(createTableTorrentsQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
const QString torrentsQueuePositionIndexName = u"%1_%2_INDEX"_qs.arg(DB_TABLE_TORRENTS, DB_COLUMN_QUEUE_POSITION.name);
|
||||
const QString createTorrentsQueuePositionIndexQuery = u"CREATE INDEX %1 ON %2 (%3)"_qs
|
||||
.arg(quoted(torrentsQueuePositionIndexName), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
|
||||
if (!query.exec(createTorrentsQueuePositionIndexQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
if (!db.commit())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
}
|
||||
@@ -535,17 +568,36 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
|
||||
{
|
||||
if (fromVersion == 1)
|
||||
{
|
||||
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"));
|
||||
if (!query.exec(alterTableTorrentsQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_qs
|
||||
.arg(quoted(DB_COLUMN_DOWNLOAD_PATH.name), quoted(DB_TABLE_TORRENTS));
|
||||
if (!query.exec(testQuery))
|
||||
{
|
||||
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"));
|
||||
if (!query.exec(alterTableTorrentsQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
}
|
||||
|
||||
if (fromVersion <= 2)
|
||||
{
|
||||
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"));
|
||||
if (!query.exec(alterTableTorrentsQuery))
|
||||
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_qs
|
||||
.arg(quoted(DB_COLUMN_STOP_CONDITION.name), quoted(DB_TABLE_TORRENTS));
|
||||
if (!query.exec(testQuery))
|
||||
{
|
||||
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"));
|
||||
if (!query.exec(alterTableTorrentsQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
}
|
||||
|
||||
if (fromVersion <= 3)
|
||||
{
|
||||
const QString torrentsQueuePositionIndexName = u"%1_%2_INDEX"_qs.arg(DB_TABLE_TORRENTS, DB_COLUMN_QUEUE_POSITION.name);
|
||||
const QString createTorrentsQueuePositionIndexQuery = u"CREATE INDEX IF NOT EXISTS %1 ON %2 (%3)"_qs
|
||||
.arg(quoted(torrentsQueuePositionIndexName), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
|
||||
if (!query.exec(createTorrentsQueuePositionIndexQuery))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
|
||||
@@ -569,216 +621,305 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
|
||||
}
|
||||
}
|
||||
|
||||
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock)
|
||||
void BitTorrent::DBResumeDataStorage::enableWALMode() const
|
||||
{
|
||||
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
|
||||
QSqlQuery query {db};
|
||||
|
||||
if (!query.exec(u"PRAGMA journal_mode = WAL;"_qs))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
if (!query.next())
|
||||
throw RuntimeError(tr("Couldn't obtain query result."));
|
||||
|
||||
const QString result = query.value(0).toString();
|
||||
if (result.compare(u"WAL"_qs, Qt::CaseInsensitive) != 0)
|
||||
throw RuntimeError(tr("WAL mode is probably unsupported due to filesystem limitations."));
|
||||
}
|
||||
|
||||
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, QReadWriteLock &dbLock)
|
||||
: m_path {dbPath}
|
||||
, m_connectionName {dbConnectionName}
|
||||
, m_dbLock {dbLock}
|
||||
{
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::openDatabase() const
|
||||
void BitTorrent::DBResumeDataStorage::Worker::run()
|
||||
{
|
||||
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_qs, m_connectionName);
|
||||
db.setDatabaseName(m_path.data());
|
||||
if (!db.open())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
}
|
||||
{
|
||||
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_qs, m_connectionName);
|
||||
db.setDatabaseName(m_path.data());
|
||||
if (!db.open())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
|
||||
int64_t transactedJobsCount = 0;
|
||||
while (true)
|
||||
{
|
||||
m_jobsMutex.lock();
|
||||
if (m_jobs.empty())
|
||||
{
|
||||
if (transactedJobsCount > 0)
|
||||
{
|
||||
db.commit();
|
||||
m_dbLock.unlock();
|
||||
|
||||
qDebug() << "Resume data changes are commited. Transacted jobs:" << transactedJobsCount;
|
||||
transactedJobsCount = 0;
|
||||
}
|
||||
|
||||
if (isInterruptionRequested())
|
||||
{
|
||||
m_jobsMutex.unlock();
|
||||
break;
|
||||
}
|
||||
|
||||
m_waitCondition.wait(&m_jobsMutex);
|
||||
if (isInterruptionRequested())
|
||||
{
|
||||
m_jobsMutex.unlock();
|
||||
break;
|
||||
}
|
||||
|
||||
m_dbLock.lockForWrite();
|
||||
if (!db.transaction())
|
||||
{
|
||||
LogMsg(tr("Couldn't begin transaction. Error: %1").arg(db.lastError().text()), Log::WARNING);
|
||||
m_dbLock.unlock();
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::unique_ptr<Job> job = std::move(m_jobs.front());
|
||||
m_jobs.pop();
|
||||
m_jobsMutex.unlock();
|
||||
|
||||
job->perform(db);
|
||||
++transactedJobsCount;
|
||||
}
|
||||
|
||||
db.close();
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::closeDatabase() const
|
||||
{
|
||||
QSqlDatabase::removeDatabase(m_connectionName);
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
|
||||
void DBResumeDataStorage::Worker::requestInterruption()
|
||||
{
|
||||
// We need to adjust native libtorrent resume data
|
||||
lt::add_torrent_params p = resumeData.ltAddTorrentParams;
|
||||
p.save_path = Profile::instance()->toPortablePath(Path(p.save_path))
|
||||
.toString().toStdString();
|
||||
if (resumeData.stopped)
|
||||
QThread::requestInterruption();
|
||||
m_waitCondition.wakeAll();
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData)
|
||||
{
|
||||
addJob(std::make_unique<StoreJob>(id, resumeData));
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id)
|
||||
{
|
||||
addJob(std::make_unique<RemoveJob>(id));
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue)
|
||||
{
|
||||
addJob(std::make_unique<StoreQueueJob>(queue));
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::addJob(std::unique_ptr<Job> job)
|
||||
{
|
||||
m_jobsMutex.lock();
|
||||
m_jobs.push(std::move(job));
|
||||
m_jobsMutex.unlock();
|
||||
|
||||
m_waitCondition.wakeAll();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace BitTorrent;
|
||||
|
||||
StoreJob::StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData)
|
||||
: m_torrentID {torrentID}
|
||||
, m_resumeData {resumeData}
|
||||
{
|
||||
p.flags |= lt::torrent_flags::paused;
|
||||
p.flags &= ~lt::torrent_flags::auto_managed;
|
||||
}
|
||||
else
|
||||
|
||||
void StoreJob::perform(QSqlDatabase db)
|
||||
{
|
||||
// Torrent can be actually "running" but temporarily "paused" to perform some
|
||||
// service jobs behind the scenes so we need to restore it as "running"
|
||||
if (resumeData.operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged)
|
||||
// We need to adjust native libtorrent resume data
|
||||
lt::add_torrent_params p = m_resumeData.ltAddTorrentParams;
|
||||
p.save_path = Profile::instance()->toPortablePath(Path(p.save_path))
|
||||
.toString().toStdString();
|
||||
if (m_resumeData.stopped)
|
||||
{
|
||||
p.flags |= lt::torrent_flags::auto_managed;
|
||||
p.flags |= lt::torrent_flags::paused;
|
||||
p.flags &= ~lt::torrent_flags::auto_managed;
|
||||
}
|
||||
else
|
||||
{
|
||||
p.flags &= ~lt::torrent_flags::paused;
|
||||
p.flags &= ~lt::torrent_flags::auto_managed;
|
||||
// Torrent can be actually "running" but temporarily "paused" to perform some
|
||||
// service jobs behind the scenes so we need to restore it as "running"
|
||||
if (m_resumeData.operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged)
|
||||
{
|
||||
p.flags |= lt::torrent_flags::auto_managed;
|
||||
}
|
||||
else
|
||||
{
|
||||
p.flags &= ~lt::torrent_flags::paused;
|
||||
p.flags &= ~lt::torrent_flags::auto_managed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector<Column> columns {
|
||||
DB_COLUMN_TORRENT_ID,
|
||||
DB_COLUMN_NAME,
|
||||
DB_COLUMN_CATEGORY,
|
||||
DB_COLUMN_TAGS,
|
||||
DB_COLUMN_TARGET_SAVE_PATH,
|
||||
DB_COLUMN_CONTENT_LAYOUT,
|
||||
DB_COLUMN_RATIO_LIMIT,
|
||||
DB_COLUMN_SEEDING_TIME_LIMIT,
|
||||
DB_COLUMN_HAS_OUTER_PIECES_PRIORITY,
|
||||
DB_COLUMN_HAS_SEED_STATUS,
|
||||
DB_COLUMN_OPERATING_MODE,
|
||||
DB_COLUMN_STOPPED,
|
||||
DB_COLUMN_STOP_CONDITION,
|
||||
DB_COLUMN_RESUMEDATA
|
||||
};
|
||||
QVector<Column> columns {
|
||||
DB_COLUMN_TORRENT_ID,
|
||||
DB_COLUMN_NAME,
|
||||
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,
|
||||
DB_COLUMN_HAS_OUTER_PIECES_PRIORITY,
|
||||
DB_COLUMN_HAS_SEED_STATUS,
|
||||
DB_COLUMN_OPERATING_MODE,
|
||||
DB_COLUMN_STOPPED,
|
||||
DB_COLUMN_STOP_CONDITION,
|
||||
DB_COLUMN_RESUMEDATA
|
||||
};
|
||||
|
||||
lt::entry data = lt::write_resume_data(p);
|
||||
lt::entry data = lt::write_resume_data(p);
|
||||
|
||||
// metadata is stored in separate column
|
||||
QByteArray bencodedMetadata;
|
||||
if (p.ti)
|
||||
{
|
||||
lt::entry::dictionary_type &dataDict = data.dict();
|
||||
lt::entry metadata {lt::entry::dictionary_t};
|
||||
lt::entry::dictionary_type &metadataDict = metadata.dict();
|
||||
metadataDict.insert(dataDict.extract("info"));
|
||||
metadataDict.insert(dataDict.extract("creation date"));
|
||||
metadataDict.insert(dataDict.extract("created by"));
|
||||
metadataDict.insert(dataDict.extract("comment"));
|
||||
|
||||
try
|
||||
// metadata is stored in separate column
|
||||
QByteArray bencodedMetadata;
|
||||
if (p.ti)
|
||||
{
|
||||
bencodedMetadata.reserve(512 * 1024);
|
||||
lt::bencode(std::back_inserter(bencodedMetadata), metadata);
|
||||
}
|
||||
catch (const std::exception &err)
|
||||
{
|
||||
LogMsg(tr("Couldn't save torrent metadata. Error: %1.")
|
||||
.arg(QString::fromLocal8Bit(err.what())), Log::CRITICAL);
|
||||
return;
|
||||
lt::entry::dictionary_type &dataDict = data.dict();
|
||||
lt::entry metadata {lt::entry::dictionary_t};
|
||||
lt::entry::dictionary_type &metadataDict = metadata.dict();
|
||||
metadataDict.insert(dataDict.extract("info"));
|
||||
metadataDict.insert(dataDict.extract("creation date"));
|
||||
metadataDict.insert(dataDict.extract("created by"));
|
||||
metadataDict.insert(dataDict.extract("comment"));
|
||||
|
||||
try
|
||||
{
|
||||
bencodedMetadata.reserve(512 * 1024);
|
||||
lt::bencode(std::back_inserter(bencodedMetadata), metadata);
|
||||
}
|
||||
catch (const std::exception &err)
|
||||
{
|
||||
LogMsg(ResumeDataStorage::tr("Couldn't save torrent metadata. Error: %1.")
|
||||
.arg(QString::fromLocal8Bit(err.what())), Log::CRITICAL);
|
||||
return;
|
||||
}
|
||||
|
||||
columns.append(DB_COLUMN_METADATA);
|
||||
}
|
||||
|
||||
columns.append(DB_COLUMN_METADATA);
|
||||
}
|
||||
|
||||
QByteArray bencodedResumeData;
|
||||
bencodedResumeData.reserve(256 * 1024);
|
||||
lt::bencode(std::back_inserter(bencodedResumeData), data);
|
||||
|
||||
const QString insertTorrentStatement = makeInsertStatement(DB_TABLE_TORRENTS, columns)
|
||||
+ makeOnConflictUpdateStatement(DB_COLUMN_TORRENT_ID, columns);
|
||||
auto db = QSqlDatabase::database(m_connectionName);
|
||||
QSqlQuery query {db};
|
||||
|
||||
try
|
||||
{
|
||||
if (!query.prepare(insertTorrentStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, id.toString());
|
||||
query.bindValue(DB_COLUMN_NAME.placeholder, resumeData.name);
|
||||
query.bindValue(DB_COLUMN_CATEGORY.placeholder, resumeData.category);
|
||||
query.bindValue(DB_COLUMN_TAGS.placeholder, (resumeData.tags.isEmpty()
|
||||
? QVariant(QVariant::String) : resumeData.tags.join(u","_qs)));
|
||||
query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(resumeData.contentLayout));
|
||||
query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast<int>(resumeData.ratioLimit * 1000));
|
||||
query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, resumeData.seedingTimeLimit);
|
||||
query.bindValue(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.placeholder, resumeData.firstLastPiecePriority);
|
||||
query.bindValue(DB_COLUMN_HAS_SEED_STATUS.placeholder, resumeData.hasSeedStatus);
|
||||
query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(resumeData.operatingMode));
|
||||
query.bindValue(DB_COLUMN_STOPPED.placeholder, resumeData.stopped);
|
||||
query.bindValue(DB_COLUMN_STOP_CONDITION.placeholder, Utils::String::fromEnum(resumeData.stopCondition));
|
||||
|
||||
if (!resumeData.useAutoTMM)
|
||||
{
|
||||
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath).data());
|
||||
query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.downloadPath).data());
|
||||
}
|
||||
|
||||
query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData);
|
||||
if (!bencodedMetadata.isEmpty())
|
||||
query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata);
|
||||
|
||||
const QWriteLocker locker {&m_dbLock};
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
LogMsg(tr("Couldn't store resume data for torrent '%1'. Error: %2")
|
||||
.arg(id.toString(), err.message()), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id) const
|
||||
{
|
||||
const auto deleteTorrentStatement = u"DELETE FROM %1 WHERE %2 = %3;"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
|
||||
|
||||
auto db = QSqlDatabase::database(m_connectionName);
|
||||
QSqlQuery query {db};
|
||||
|
||||
try
|
||||
{
|
||||
if (!query.prepare(deleteTorrentStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, id.toString());
|
||||
|
||||
const QWriteLocker locker {&m_dbLock};
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
LogMsg(tr("Couldn't delete resume data of torrent '%1'. Error: %2")
|
||||
.arg(id.toString(), err.message()), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue) const
|
||||
{
|
||||
const auto updateQueuePosStatement = u"UPDATE %1 SET %2 = %3 WHERE %4 = %5;"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name), DB_COLUMN_QUEUE_POSITION.placeholder
|
||||
, quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
|
||||
|
||||
auto db = QSqlDatabase::database(m_connectionName);
|
||||
|
||||
try
|
||||
{
|
||||
const QWriteLocker locker {&m_dbLock};
|
||||
|
||||
if (!db.transaction())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
QByteArray bencodedResumeData;
|
||||
bencodedResumeData.reserve(256 * 1024);
|
||||
lt::bencode(std::back_inserter(bencodedResumeData), data);
|
||||
|
||||
const QString insertTorrentStatement = makeInsertStatement(DB_TABLE_TORRENTS, columns)
|
||||
+ makeOnConflictUpdateStatement(DB_COLUMN_TORRENT_ID, columns);
|
||||
QSqlQuery query {db};
|
||||
|
||||
try
|
||||
{
|
||||
if (!query.prepare(insertTorrentStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, m_torrentID.toString());
|
||||
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)));
|
||||
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);
|
||||
query.bindValue(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.placeholder, m_resumeData.firstLastPiecePriority);
|
||||
query.bindValue(DB_COLUMN_HAS_SEED_STATUS.placeholder, m_resumeData.hasFinishedStatus);
|
||||
query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(m_resumeData.operatingMode));
|
||||
query.bindValue(DB_COLUMN_STOPPED.placeholder, m_resumeData.stopped);
|
||||
query.bindValue(DB_COLUMN_STOP_CONDITION.placeholder, Utils::String::fromEnum(m_resumeData.stopCondition));
|
||||
|
||||
if (!m_resumeData.useAutoTMM)
|
||||
{
|
||||
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(m_resumeData.savePath).data());
|
||||
query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(m_resumeData.downloadPath).data());
|
||||
}
|
||||
|
||||
query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData);
|
||||
if (!bencodedMetadata.isEmpty())
|
||||
query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata);
|
||||
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
LogMsg(ResumeDataStorage::tr("Couldn't store resume data for torrent '%1'. Error: %2")
|
||||
.arg(m_torrentID.toString(), err.message()), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
RemoveJob::RemoveJob(const TorrentID &torrentID)
|
||||
: m_torrentID {torrentID}
|
||||
{
|
||||
}
|
||||
|
||||
void RemoveJob::perform(QSqlDatabase db)
|
||||
{
|
||||
const auto deleteTorrentStatement = u"DELETE FROM %1 WHERE %2 = %3;"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
|
||||
|
||||
QSqlQuery query {db};
|
||||
try
|
||||
{
|
||||
if (!query.prepare(deleteTorrentStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, m_torrentID.toString());
|
||||
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
LogMsg(ResumeDataStorage::tr("Couldn't delete resume data of torrent '%1'. Error: %2")
|
||||
.arg(m_torrentID.toString(), err.message()), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
StoreQueueJob::StoreQueueJob(const QVector<TorrentID> &queue)
|
||||
: m_queue {queue}
|
||||
{
|
||||
}
|
||||
|
||||
void StoreQueueJob::perform(QSqlDatabase db)
|
||||
{
|
||||
const auto updateQueuePosStatement = u"UPDATE %1 SET %2 = %3 WHERE %4 = %5;"_qs
|
||||
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name), DB_COLUMN_QUEUE_POSITION.placeholder
|
||||
, quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
|
||||
|
||||
try
|
||||
{
|
||||
QSqlQuery query {db};
|
||||
|
||||
if (!query.prepare(updateQueuePosStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
int pos = 0;
|
||||
for (const TorrentID &torrentID : queue)
|
||||
for (const TorrentID &torrentID : m_queue)
|
||||
{
|
||||
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, torrentID.toString());
|
||||
query.bindValue(DB_COLUMN_QUEUE_POSITION.placeholder, pos++);
|
||||
if (!query.exec())
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
|
||||
if (!db.commit())
|
||||
throw RuntimeError(db.lastError().text());
|
||||
}
|
||||
catch (const RuntimeError &)
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
db.rollback();
|
||||
throw;
|
||||
LogMsg(ResumeDataStorage::tr("Couldn't store torrents queue positions. Error: %1")
|
||||
.arg(err.message()), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
catch (const RuntimeError &err)
|
||||
{
|
||||
LogMsg(tr("Couldn't store torrents queue positions. Error: %1")
|
||||
.arg(err.message()), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2021-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <QReadWriteLock>
|
||||
|
||||
#include "base/pathfwd.h"
|
||||
#include "base/utils/thread.h"
|
||||
#include "resumedatastorage.h"
|
||||
|
||||
class QThread;
|
||||
@@ -58,8 +59,9 @@ namespace BitTorrent
|
||||
int currentDBVersion() const;
|
||||
void createDB() const;
|
||||
void updateDB(int fromVersion) const;
|
||||
void enableWALMode() const;
|
||||
|
||||
QThread *m_ioThread = nullptr;
|
||||
Utils::Thread::UniquePtr m_ioThread;
|
||||
|
||||
class Worker;
|
||||
Worker *m_asyncWorker = nullptr;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2022-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
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <libtorrent/announce_entry.hpp>
|
||||
@@ -44,4 +46,5 @@ struct ExtensionData
|
||||
{
|
||||
lt::torrent_status status;
|
||||
std::vector<lt::announce_entry> trackers;
|
||||
std::set<std::string> urlSeeds;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -52,13 +52,13 @@ namespace BitTorrent
|
||||
TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged;
|
||||
bool useAutoTMM = false;
|
||||
bool firstLastPiecePriority = false;
|
||||
bool hasSeedStatus = false;
|
||||
bool hasFinishedStatus = false;
|
||||
bool stopped = false;
|
||||
Torrent::StopCondition stopCondition;
|
||||
Torrent::StopCondition stopCondition = Torrent::StopCondition::None;
|
||||
|
||||
bool addToQueueTop = false; // only for new torrents
|
||||
|
||||
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
|
||||
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
|
||||
|
||||
bool restored = false; // is existing torrent job?
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2020-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2020-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
|
||||
@@ -35,26 +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();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void handleFastresumeRejectedAlert(const lt::fastresume_rejected_alert *alert)
|
||||
{
|
||||
alert->handle.unset_flags(lt::torrent_flags::auto_managed);
|
||||
@@ -62,6 +42,17 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
bool NativeSessionExtension::isSessionListening() const
|
||||
{
|
||||
const QReadLocker locker {&m_lock};
|
||||
return m_isSesssionListening;
|
||||
}
|
||||
|
||||
void NativeSessionExtension::added(const lt::session_handle &nativeSession)
|
||||
{
|
||||
m_nativeSession = nativeSession;
|
||||
}
|
||||
|
||||
lt::feature_flags_t NativeSessionExtension::implemented_features()
|
||||
{
|
||||
return alert_feature;
|
||||
@@ -76,8 +67,8 @@ void NativeSessionExtension::on_alert(const lt::alert *alert)
|
||||
{
|
||||
switch (alert->type())
|
||||
{
|
||||
case lt::add_torrent_alert::alert_type:
|
||||
handleAddTorrentAlert(static_cast<const lt::add_torrent_alert *>(alert));
|
||||
case lt::session_stats_alert::alert_type:
|
||||
handleSessionStatsAlert(static_cast<const lt::session_stats_alert *>(alert));
|
||||
break;
|
||||
case lt::fastresume_rejected_alert::alert_type:
|
||||
handleFastresumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert *>(alert));
|
||||
@@ -86,3 +77,9 @@ void NativeSessionExtension::on_alert(const lt::alert *alert)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NativeSessionExtension::handleSessionStatsAlert([[maybe_unused]] const lt::session_stats_alert *alert)
|
||||
{
|
||||
const QWriteLocker locker {&m_lock};
|
||||
m_isSesssionListening = m_nativeSession.is_listening();
|
||||
}
|
||||
|
||||
@@ -29,12 +29,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <libtorrent/extensions.hpp>
|
||||
#include <libtorrent/fwd.hpp>
|
||||
#include <libtorrent/session_handle.hpp>
|
||||
|
||||
#include <QReadWriteLock>
|
||||
|
||||
#include "extensiondata.h"
|
||||
|
||||
class NativeSessionExtension final : public lt::plugin
|
||||
{
|
||||
public:
|
||||
bool isSessionListening() const;
|
||||
|
||||
private:
|
||||
void added(const lt::session_handle &nativeSession) override;
|
||||
lt::feature_flags_t implemented_features() override;
|
||||
std::shared_ptr<lt::torrent_plugin> new_torrent(const lt::torrent_handle &torrentHandle, LTClientData clientData) override;
|
||||
void on_alert(const lt::alert *alert) override;
|
||||
|
||||
void handleSessionStatsAlert(const lt::session_stats_alert *alert);
|
||||
|
||||
lt::session_handle m_nativeSession;
|
||||
|
||||
mutable QReadWriteLock m_lock;
|
||||
bool m_isSesssionListening = false;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2020-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2020-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
|
||||
@@ -36,18 +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);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -31,16 +31,15 @@
|
||||
#include <QBitArray>
|
||||
|
||||
#include "base/bittorrent/ltqbitarray.h"
|
||||
#include "base/bittorrent/torrent.h"
|
||||
#include "base/net/geoipmanager.h"
|
||||
#include "base/unicodestrings.h"
|
||||
#include "peeraddress.h"
|
||||
|
||||
using namespace BitTorrent;
|
||||
|
||||
PeerInfo::PeerInfo(const Torrent *torrent, const lt::peer_info &nativeInfo)
|
||||
PeerInfo::PeerInfo(const lt::peer_info &nativeInfo, const QBitArray &allPieces)
|
||||
: m_nativeInfo(nativeInfo)
|
||||
, m_relevance(calcRelevance(torrent))
|
||||
, m_relevance(calcRelevance(allPieces))
|
||||
{
|
||||
determineFlags();
|
||||
}
|
||||
@@ -181,6 +180,31 @@ QString PeerInfo::client() const
|
||||
return QString::fromStdString(m_nativeInfo.client);
|
||||
}
|
||||
|
||||
QString PeerInfo::peerIdClient() const
|
||||
{
|
||||
// when peer ID is not known yet it contains only zero bytes,
|
||||
// do not create string in such case, return empty string instead
|
||||
if (m_nativeInfo.pid.is_all_zeros())
|
||||
return {};
|
||||
|
||||
QString result;
|
||||
|
||||
// interesting part of a typical peer ID is first 8 chars
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
const std::uint8_t c = m_nativeInfo.pid[i];
|
||||
|
||||
// ensure that the peer ID slice consists only of printable ASCII characters,
|
||||
// this should filter out most of the improper IDs
|
||||
if ((c < 32) || (c > 126))
|
||||
return tr("Unknown");
|
||||
|
||||
result += QChar::fromLatin1(c);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
qreal PeerInfo::progress() const
|
||||
{
|
||||
return m_nativeInfo.progress;
|
||||
@@ -221,9 +245,8 @@ QString PeerInfo::connectionType() const
|
||||
: u"Web"_qs;
|
||||
}
|
||||
|
||||
qreal PeerInfo::calcRelevance(const Torrent *torrent) const
|
||||
qreal PeerInfo::calcRelevance(const QBitArray &allPieces) const
|
||||
{
|
||||
const QBitArray allPieces = torrent->pieces();
|
||||
const int localMissing = allPieces.count(false);
|
||||
if (localMissing <= 0)
|
||||
return 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -36,7 +36,6 @@ class QBitArray;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class Torrent;
|
||||
struct PeerAddress;
|
||||
|
||||
class PeerInfo
|
||||
@@ -45,7 +44,7 @@ namespace BitTorrent
|
||||
|
||||
public:
|
||||
PeerInfo() = default;
|
||||
PeerInfo(const Torrent *torrent, const lt::peer_info &nativeInfo);
|
||||
PeerInfo(const lt::peer_info &nativeInfo, const QBitArray &allPieces);
|
||||
|
||||
bool fromDHT() const;
|
||||
bool fromPeX() const;
|
||||
@@ -78,6 +77,7 @@ namespace BitTorrent
|
||||
|
||||
PeerAddress address() const;
|
||||
QString client() const;
|
||||
QString peerIdClient() const;
|
||||
qreal progress() const;
|
||||
int payloadUpSpeed() const;
|
||||
int payloadDownSpeed() const;
|
||||
@@ -92,7 +92,7 @@ namespace BitTorrent
|
||||
int downloadingPieceIndex() const;
|
||||
|
||||
private:
|
||||
qreal calcRelevance(const Torrent *torrent) const;
|
||||
qreal calcRelevance(const QBitArray &allPieces) const;
|
||||
void determineFlags();
|
||||
|
||||
lt::peer_info m_nativeInfo = {};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2019 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2019-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -28,12 +28,12 @@
|
||||
|
||||
#include "portforwarderimpl.h"
|
||||
|
||||
#include <libtorrent/session.hpp>
|
||||
#include <utility>
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "base/bittorrent/sessionimpl.h"
|
||||
|
||||
PortForwarderImpl::PortForwarderImpl(lt::session *provider, QObject *parent)
|
||||
: Net::PortForwarder {parent}
|
||||
PortForwarderImpl::PortForwarderImpl(BitTorrent::SessionImpl *provider, QObject *parent)
|
||||
: Net::PortForwarder(parent)
|
||||
, m_storeActive {u"Network/PortForwardingEnabled"_qs, true}
|
||||
, m_provider {provider}
|
||||
{
|
||||
@@ -63,60 +63,30 @@ void PortForwarderImpl::setEnabled(const bool enabled)
|
||||
m_storeActive = enabled;
|
||||
}
|
||||
|
||||
void PortForwarderImpl::addPort(const quint16 port)
|
||||
void PortForwarderImpl::setPorts(const QString &profile, QSet<quint16> ports)
|
||||
{
|
||||
if (m_mappedPorts.contains(port))
|
||||
return;
|
||||
const QSet<quint16> oldForwardedPorts = std::accumulate(m_portProfiles.cbegin(), m_portProfiles.cend(), QSet<quint16>());
|
||||
|
||||
if (isEnabled())
|
||||
m_mappedPorts.insert(port, m_provider->add_port_mapping(lt::session::tcp, port, port));
|
||||
else
|
||||
m_mappedPorts.insert(port, {});
|
||||
m_portProfiles[profile] = ports;
|
||||
const QSet<quint16> newForwardedPorts = std::accumulate(m_portProfiles.cbegin(), m_portProfiles.cend(), QSet<quint16>());
|
||||
|
||||
m_provider->removeMappedPorts(oldForwardedPorts - newForwardedPorts);
|
||||
m_provider->addMappedPorts(newForwardedPorts - oldForwardedPorts);
|
||||
}
|
||||
|
||||
void PortForwarderImpl::deletePort(const quint16 port)
|
||||
void PortForwarderImpl::removePorts(const QString &profile)
|
||||
{
|
||||
const auto iter = m_mappedPorts.find(port);
|
||||
if (iter == m_mappedPorts.end())
|
||||
return;
|
||||
|
||||
if (isEnabled())
|
||||
{
|
||||
for (const lt::port_mapping_t &portMapping : *iter)
|
||||
m_provider->delete_port_mapping(portMapping);
|
||||
}
|
||||
|
||||
m_mappedPorts.erase(iter);
|
||||
setPorts(profile, {});
|
||||
}
|
||||
|
||||
void PortForwarderImpl::start()
|
||||
{
|
||||
lt::settings_pack settingsPack = m_provider->get_settings();
|
||||
settingsPack.set_bool(lt::settings_pack::enable_upnp, true);
|
||||
settingsPack.set_bool(lt::settings_pack::enable_natpmp, true);
|
||||
m_provider->apply_settings(settingsPack);
|
||||
|
||||
for (auto iter = m_mappedPorts.begin(); iter != m_mappedPorts.end(); ++iter)
|
||||
{
|
||||
Q_ASSERT(iter.value().empty());
|
||||
|
||||
const quint16 port = iter.key();
|
||||
iter.value() = m_provider->add_port_mapping(lt::session::tcp, port, port);
|
||||
}
|
||||
|
||||
LogMsg(tr("UPnP/NAT-PMP support: ON"), Log::INFO);
|
||||
m_provider->enablePortMapping();
|
||||
for (const QSet<quint16> &ports : asConst(m_portProfiles))
|
||||
m_provider->addMappedPorts(ports);
|
||||
}
|
||||
|
||||
void PortForwarderImpl::stop()
|
||||
{
|
||||
lt::settings_pack settingsPack = m_provider->get_settings();
|
||||
settingsPack.set_bool(lt::settings_pack::enable_upnp, false);
|
||||
settingsPack.set_bool(lt::settings_pack::enable_natpmp, false);
|
||||
m_provider->apply_settings(settingsPack);
|
||||
|
||||
// don't clear m_mappedPorts so a later `start()` call can restore the port forwarding
|
||||
for (auto iter = m_mappedPorts.begin(); iter != m_mappedPorts.end(); ++iter)
|
||||
iter.value().clear();
|
||||
|
||||
LogMsg(tr("UPnP/NAT-PMP support: OFF"), Log::INFO);
|
||||
m_provider->disablePortMapping();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2019 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2019-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@@ -28,36 +28,38 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <libtorrent/fwd.hpp>
|
||||
#include <libtorrent/portmap.hpp>
|
||||
|
||||
#include <QHash>
|
||||
#include <QSet>
|
||||
|
||||
#include "base/net/portforwarder.h"
|
||||
#include "base/settingvalue.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class SessionImpl;
|
||||
}
|
||||
|
||||
class PortForwarderImpl final : public Net::PortForwarder
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(PortForwarderImpl)
|
||||
|
||||
public:
|
||||
explicit PortForwarderImpl(lt::session *provider, QObject *parent = nullptr);
|
||||
explicit PortForwarderImpl(BitTorrent::SessionImpl *provider, QObject *parent = nullptr);
|
||||
~PortForwarderImpl() override;
|
||||
|
||||
bool isEnabled() const override;
|
||||
void setEnabled(bool enabled) override;
|
||||
|
||||
void addPort(quint16 port) override;
|
||||
void deletePort(quint16 port) override;
|
||||
void setPorts(const QString &profile, QSet<quint16> ports) override;
|
||||
void removePorts(const QString &profile) override;
|
||||
|
||||
private:
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
CachedSettingValue<bool> m_storeActive;
|
||||
lt::session *const m_provider = nullptr;
|
||||
QHash<quint16, std::vector<lt::port_mapping_t>> m_mappedPorts;
|
||||
|
||||
BitTorrent::SessionImpl *const m_provider = nullptr;
|
||||
QHash<QString, QSet<quint16>> m_portProfiles;
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <QMetaObject>
|
||||
#include <QMutexLocker>
|
||||
#include <QThread>
|
||||
#include <QVector>
|
||||
|
||||
const int TORRENTIDLIST_TYPEID = qRegisterMetaType<QVector<BitTorrent::TorrentID>>();
|
||||
|
||||
@@ -59,11 +60,11 @@ void BitTorrent::ResumeDataStorage::loadAll() const
|
||||
loadingThread->start();
|
||||
}
|
||||
|
||||
QVector<BitTorrent::LoadedResumeData> BitTorrent::ResumeDataStorage::fetchLoadedResumeData() const
|
||||
QList<BitTorrent::LoadedResumeData> BitTorrent::ResumeDataStorage::fetchLoadedResumeData() const
|
||||
{
|
||||
const QMutexLocker locker {&m_loadedResumeDataMutex};
|
||||
|
||||
const QVector<BitTorrent::LoadedResumeData> loadedResumeData = m_loadedResumeData;
|
||||
const QList<BitTorrent::LoadedResumeData> loadedResumeData = m_loadedResumeData;
|
||||
m_loadedResumeData.clear();
|
||||
|
||||
return loadedResumeData;
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/3rdparty/expected.hpp"
|
||||
#include "base/path.h"
|
||||
@@ -65,7 +65,7 @@ namespace BitTorrent
|
||||
virtual void storeQueue(const QVector<TorrentID> &queue) const = 0;
|
||||
|
||||
void loadAll() const;
|
||||
QVector<LoadedResumeData> fetchLoadedResumeData() const;
|
||||
QList<LoadedResumeData> fetchLoadedResumeData() const;
|
||||
|
||||
signals:
|
||||
void loadStarted(const QVector<BitTorrent::TorrentID> &torrents);
|
||||
@@ -78,7 +78,7 @@ namespace BitTorrent
|
||||
virtual void doLoadAll() const = 0;
|
||||
|
||||
const Path m_path;
|
||||
mutable QVector<LoadedResumeData> m_loadedResumeData;
|
||||
mutable QList<LoadedResumeData> m_loadedResumeData;
|
||||
mutable QMutex m_loadedResumeDataMutex;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -206,6 +209,8 @@ namespace BitTorrent
|
||||
virtual void setLSDEnabled(bool enabled) = 0;
|
||||
virtual bool isPeXEnabled() const = 0;
|
||||
virtual void setPeXEnabled(bool enabled) = 0;
|
||||
virtual bool isAddTorrentToQueueTop() const = 0;
|
||||
virtual void setAddTorrentToQueueTop(bool value) = 0;
|
||||
virtual bool isAddTorrentPaused() const = 0;
|
||||
virtual void setAddTorrentPaused(bool value) = 0;
|
||||
virtual Torrent::StopCondition torrentStopCondition() const = 0;
|
||||
@@ -258,10 +263,16 @@ namespace BitTorrent
|
||||
virtual void setEncryption(int state) = 0;
|
||||
virtual int maxActiveCheckingTorrents() const = 0;
|
||||
virtual void setMaxActiveCheckingTorrents(int val) = 0;
|
||||
virtual bool isI2PEnabled() const = 0;
|
||||
virtual void setI2PEnabled(bool enabled) = 0;
|
||||
virtual QString I2PAddress() const = 0;
|
||||
virtual void setI2PAddress(const QString &address) = 0;
|
||||
virtual int I2PPort() const = 0;
|
||||
virtual void setI2PPort(int port) = 0;
|
||||
virtual bool I2PMixedMode() const = 0;
|
||||
virtual void setI2PMixedMode(bool enabled) = 0;
|
||||
virtual bool isProxyPeerConnectionsEnabled() const = 0;
|
||||
virtual void setProxyPeerConnectionsEnabled(bool enabled) = 0;
|
||||
virtual bool isProxyHostnameLookupEnabled() const = 0;
|
||||
virtual void setProxyHostnameLookupEnabled(bool enabled) = 0;
|
||||
virtual ChokingAlgorithm chokingAlgorithm() const = 0;
|
||||
virtual void setChokingAlgorithm(ChokingAlgorithm mode) = 0;
|
||||
virtual SeedChokingAlgorithm seedChokingAlgorithm() const = 0;
|
||||
@@ -320,6 +331,10 @@ namespace BitTorrent
|
||||
virtual void setSendBufferWatermarkFactor(int value) = 0;
|
||||
virtual int connectionSpeed() const = 0;
|
||||
virtual void setConnectionSpeed(int value) = 0;
|
||||
virtual int socketSendBufferSize() const = 0;
|
||||
virtual void setSocketSendBufferSize(int value) = 0;
|
||||
virtual int socketReceiveBufferSize() const = 0;
|
||||
virtual void setSocketReceiveBufferSize(int value) = 0;
|
||||
virtual int socketBacklogSize() const = 0;
|
||||
virtual void setSocketBacklogSize(int value) = 0;
|
||||
virtual bool isAnonymousModeEnabled() const = 0;
|
||||
@@ -388,7 +403,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;
|
||||
@@ -403,9 +418,6 @@ namespace BitTorrent
|
||||
virtual Torrent *findTorrent(const InfoHash &infoHash) const = 0;
|
||||
virtual QVector<Torrent *> torrents() const = 0;
|
||||
virtual qsizetype torrentsCount() const = 0;
|
||||
virtual bool hasActiveTorrents() const = 0;
|
||||
virtual bool hasUnfinishedTorrents() const = 0;
|
||||
virtual bool hasRunningSeed() const = 0;
|
||||
virtual const SessionStatus &status() const = 0;
|
||||
virtual const CacheStatus &cacheStatus() const = 0;
|
||||
virtual bool isListening() const = 0;
|
||||
@@ -434,6 +446,7 @@ namespace BitTorrent
|
||||
void allTorrentsFinished();
|
||||
void categoryAdded(const QString &categoryName);
|
||||
void categoryRemoved(const QString &categoryName);
|
||||
void categoryOptionsChanged(const QString &categoryName);
|
||||
void downloadFromUrlFailed(const QString &url, const QString &reason);
|
||||
void downloadFromUrlFinished(const QString &url);
|
||||
void fullDiskError(Torrent *torrent, const QString &msg);
|
||||
@@ -468,6 +481,6 @@ namespace BitTorrent
|
||||
void trackersRemoved(Torrent *torrent, const QStringList &trackers);
|
||||
void trackerSuccess(Torrent *torrent, const QString &tracker);
|
||||
void trackerWarning(Torrent *torrent, const QString &tracker);
|
||||
void trackerEntriesUpdated(const QHash<Torrent *, QSet<QString>> &updateInfos);
|
||||
void trackerEntriesUpdated(Torrent *torrent, const QHash<QString, TrackerEntry> &updatedTrackerEntries);
|
||||
};
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -29,22 +29,26 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <libtorrent/fwd.hpp>
|
||||
#include <libtorrent/portmap.hpp>
|
||||
#include <libtorrent/torrent_handle.hpp>
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QDateTime>
|
||||
#include <QElapsedTimer>
|
||||
#include <QHash>
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
#include <QtContainerFwd>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/path.h"
|
||||
#include "base/settingvalue.h"
|
||||
#include "base/types.h"
|
||||
#include "base/utils/thread.h"
|
||||
#include "addtorrentparams.h"
|
||||
#include "cachestatus.h"
|
||||
#include "categoryoptions.h"
|
||||
@@ -59,12 +63,14 @@ class QNetworkConfigurationManager;
|
||||
#endif
|
||||
class QString;
|
||||
class QThread;
|
||||
class QThreadPool;
|
||||
class QTimer;
|
||||
class QUrl;
|
||||
|
||||
class BandwidthScheduler;
|
||||
class FileSearcher;
|
||||
class FilterParserThread;
|
||||
class NativeSessionExtension;
|
||||
|
||||
namespace Net
|
||||
{
|
||||
@@ -82,6 +88,7 @@ namespace BitTorrent
|
||||
struct LoadTorrentParams;
|
||||
|
||||
enum class MoveStorageMode;
|
||||
enum class MoveStorageContext;
|
||||
|
||||
struct SessionMetricIndices
|
||||
{
|
||||
@@ -154,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;
|
||||
@@ -178,6 +188,8 @@ namespace BitTorrent
|
||||
void setLSDEnabled(bool enabled) override;
|
||||
bool isPeXEnabled() const override;
|
||||
void setPeXEnabled(bool enabled) override;
|
||||
bool isAddTorrentToQueueTop() const override;
|
||||
void setAddTorrentToQueueTop(bool value) override;
|
||||
bool isAddTorrentPaused() const override;
|
||||
void setAddTorrentPaused(bool value) override;
|
||||
Torrent::StopCondition torrentStopCondition() const override;
|
||||
@@ -230,10 +242,16 @@ namespace BitTorrent
|
||||
void setEncryption(int state) override;
|
||||
int maxActiveCheckingTorrents() const override;
|
||||
void setMaxActiveCheckingTorrents(int val) override;
|
||||
bool isI2PEnabled() const override;
|
||||
void setI2PEnabled(bool enabled) override;
|
||||
QString I2PAddress() const override;
|
||||
void setI2PAddress(const QString &address) override;
|
||||
int I2PPort() const override;
|
||||
void setI2PPort(int port) override;
|
||||
bool I2PMixedMode() const override;
|
||||
void setI2PMixedMode(bool enabled) override;
|
||||
bool isProxyPeerConnectionsEnabled() const override;
|
||||
void setProxyPeerConnectionsEnabled(bool enabled) override;
|
||||
bool isProxyHostnameLookupEnabled() const override;
|
||||
void setProxyHostnameLookupEnabled(bool enabled) override;
|
||||
ChokingAlgorithm chokingAlgorithm() const override;
|
||||
void setChokingAlgorithm(ChokingAlgorithm mode) override;
|
||||
SeedChokingAlgorithm seedChokingAlgorithm() const override;
|
||||
@@ -292,6 +310,10 @@ namespace BitTorrent
|
||||
void setSendBufferWatermarkFactor(int value) override;
|
||||
int connectionSpeed() const override;
|
||||
void setConnectionSpeed(int value) override;
|
||||
int socketSendBufferSize() const override;
|
||||
void setSocketSendBufferSize(int value) override;
|
||||
int socketReceiveBufferSize() const override;
|
||||
void setSocketReceiveBufferSize(int value) override;
|
||||
int socketBacklogSize() const override;
|
||||
void setSocketBacklogSize(int value) override;
|
||||
bool isAnonymousModeEnabled() const override;
|
||||
@@ -360,9 +382,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;
|
||||
@@ -375,9 +397,6 @@ namespace BitTorrent
|
||||
Torrent *findTorrent(const InfoHash &infoHash) const override;
|
||||
QVector<Torrent *> torrents() const override;
|
||||
qsizetype torrentsCount() const override;
|
||||
bool hasActiveTorrents() const override;
|
||||
bool hasUnfinishedTorrents() const override;
|
||||
bool hasRunningSeed() const override;
|
||||
const SessionStatus &status() const override;
|
||||
const CacheStatus &cacheStatus() const override;
|
||||
bool isListening() const override;
|
||||
@@ -405,31 +424,44 @@ 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;
|
||||
|
||||
void enablePortMapping();
|
||||
void disablePortMapping();
|
||||
void addMappedPorts(const QSet<quint16> &ports);
|
||||
void removeMappedPorts(const QSet<quint16> &ports);
|
||||
|
||||
template <typename Func>
|
||||
void invoke(Func &&func)
|
||||
{
|
||||
QMetaObject::invokeMethod(this, std::forward<Func>(func), Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void invokeAsync(std::function<void ()> func);
|
||||
|
||||
private slots:
|
||||
void configureDeferred();
|
||||
void readAlerts();
|
||||
@@ -454,14 +486,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);
|
||||
@@ -474,13 +507,10 @@ namespace BitTorrent
|
||||
Q_INVOKABLE void configure();
|
||||
void configureComponents();
|
||||
void initializeNativeSession();
|
||||
void loadLTSettings(lt::settings_pack &settingsPack);
|
||||
void configureNetworkInterfaces(lt::settings_pack &settingsPack);
|
||||
lt::settings_pack loadLTSettings() const;
|
||||
void applyNetworkInterfacesSettings(lt::settings_pack &settingsPack) const;
|
||||
void configurePeerClasses();
|
||||
void adjustLimits(lt::settings_pack &settingsPack) const;
|
||||
void applyBandwidthLimits(lt::settings_pack &settingsPack) const;
|
||||
void initMetrics();
|
||||
void adjustLimits();
|
||||
void applyBandwidthLimits();
|
||||
void processBannedIPs(lt::ip_filter &filter);
|
||||
QStringList getListeningIPs() const;
|
||||
@@ -533,8 +563,8 @@ namespace BitTorrent
|
||||
TorrentImpl *createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms);
|
||||
|
||||
void saveResumeData();
|
||||
void saveTorrentsQueue() const;
|
||||
void removeTorrentsQueue() const;
|
||||
void saveTorrentsQueue();
|
||||
void removeTorrentsQueue();
|
||||
|
||||
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
|
||||
|
||||
@@ -550,10 +580,11 @@ namespace BitTorrent
|
||||
|
||||
// BitTorrent
|
||||
lt::session *m_nativeSession = nullptr;
|
||||
NativeSessionExtension *m_nativeSessionExtension = nullptr;
|
||||
|
||||
bool m_deferredConfigureScheduled = false;
|
||||
bool m_IPFilteringConfigured = false;
|
||||
bool m_listenInterfaceConfigured = false;
|
||||
mutable bool m_listenInterfaceConfigured = false;
|
||||
|
||||
CachedSettingValue<bool> m_isDHTEnabled;
|
||||
CachedSettingValue<bool> m_isLSDEnabled;
|
||||
@@ -580,6 +611,8 @@ namespace BitTorrent
|
||||
CachedSettingValue<int> m_sendBufferLowWatermark;
|
||||
CachedSettingValue<int> m_sendBufferWatermarkFactor;
|
||||
CachedSettingValue<int> m_connectionSpeed;
|
||||
CachedSettingValue<int> m_socketSendBufferSize;
|
||||
CachedSettingValue<int> m_socketReceiveBufferSize;
|
||||
CachedSettingValue<int> m_socketBacklogSize;
|
||||
CachedSettingValue<bool> m_isAnonymousModeEnabled;
|
||||
CachedSettingValue<bool> m_isQueueingEnabled;
|
||||
@@ -616,6 +649,7 @@ namespace BitTorrent
|
||||
CachedSettingValue<QString> m_additionalTrackers;
|
||||
CachedSettingValue<qreal> m_globalMaxRatio;
|
||||
CachedSettingValue<int> m_globalMaxSeedingMinutes;
|
||||
CachedSettingValue<bool> m_isAddTorrentToQueueTop;
|
||||
CachedSettingValue<bool> m_isAddTorrentPaused;
|
||||
CachedSettingValue<Torrent::StopCondition> m_torrentStopCondition;
|
||||
CachedSettingValue<TorrentContentLayout> m_torrentContentLayout;
|
||||
@@ -639,7 +673,6 @@ namespace BitTorrent
|
||||
CachedSettingValue<int> m_encryption;
|
||||
CachedSettingValue<int> m_maxActiveCheckingTorrents;
|
||||
CachedSettingValue<bool> m_isProxyPeerConnectionsEnabled;
|
||||
CachedSettingValue<bool> m_isProxyHostnameLookupEnabled;
|
||||
CachedSettingValue<ChokingAlgorithm> m_chokingAlgorithm;
|
||||
CachedSettingValue<SeedChokingAlgorithm> m_seedChokingAlgorithm;
|
||||
CachedSettingValue<QStringList> m_storedTags;
|
||||
@@ -662,6 +695,10 @@ namespace BitTorrent
|
||||
CachedSettingValue<QStringList> m_excludedFileNames;
|
||||
CachedSettingValue<QStringList> m_bannedIPs;
|
||||
CachedSettingValue<ResumeDataStorageType> m_resumeDataStorageType;
|
||||
CachedSettingValue<bool> m_isI2PEnabled;
|
||||
CachedSettingValue<QString> m_I2PAddress;
|
||||
CachedSettingValue<int> m_I2PPort;
|
||||
CachedSettingValue<bool> m_I2PMixedMode;
|
||||
|
||||
bool m_isRestored = false;
|
||||
|
||||
@@ -671,7 +708,6 @@ namespace BitTorrent
|
||||
const bool m_wasPexEnabled = m_isPeXEnabled;
|
||||
|
||||
int m_numResumeData = 0;
|
||||
int m_extraLimit = 0;
|
||||
QVector<TrackerEntry> m_additionalTrackerList;
|
||||
QVector<QRegularExpression> m_excludedFileNamesRegExpList;
|
||||
|
||||
@@ -681,6 +717,8 @@ namespace BitTorrent
|
||||
qint64 m_previouslyUploaded = 0;
|
||||
qint64 m_previouslyDownloaded = 0;
|
||||
|
||||
bool m_torrentsQueueChanged = false;
|
||||
bool m_needSaveTorrentsQueue = false;
|
||||
bool m_refreshEnqueued = false;
|
||||
QTimer *m_seedingLimitTimer = nullptr;
|
||||
QTimer *m_resumeDataTimer = nullptr;
|
||||
@@ -690,11 +728,12 @@ namespace BitTorrent
|
||||
// Tracker
|
||||
QPointer<Tracker> m_tracker;
|
||||
|
||||
QThread *m_ioThread = nullptr;
|
||||
Utils::Thread::UniquePtr m_ioThread;
|
||||
QThreadPool *m_asyncWorker = nullptr;
|
||||
ResumeDataStorage *m_resumeDataStorage = nullptr;
|
||||
FileSearcher *m_fileSearcher = nullptr;
|
||||
|
||||
QSet<TorrentID> m_downloadedMetadata;
|
||||
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
|
||||
|
||||
QHash<TorrentID, TorrentImpl *> m_torrents;
|
||||
QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID;
|
||||
@@ -706,7 +745,9 @@ namespace BitTorrent
|
||||
QMap<QString, CategoryOptions> m_categories;
|
||||
QSet<QString> m_tags;
|
||||
|
||||
QHash<Torrent *, QSet<QString>> m_updatedTrackerEntries;
|
||||
// This field holds amounts of peers reported by trackers in their responses to announces
|
||||
// (torrent.tracker_name.tracker_local_endpoint.num_peers)
|
||||
QHash<lt::torrent_handle, QHash<std::string, QMap<TrackerEntry::Endpoint, int>>> m_updatedTrackerEntries;
|
||||
|
||||
// I/O errored torrents
|
||||
QSet<TorrentID> m_recentErroredTorrents;
|
||||
@@ -727,6 +768,15 @@ namespace BitTorrent
|
||||
|
||||
bool m_needUpgradeDownloadPath = false;
|
||||
|
||||
// All port mapping related routines are invoked from working thread
|
||||
// so there are no synchronization used. If multithreaded access is
|
||||
// ever required, synchronization should also be provided.
|
||||
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();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
@@ -37,7 +37,7 @@
|
||||
#include "base/3rdparty/expected.hpp"
|
||||
#include "base/pathfwd.h"
|
||||
#include "base/tagset.h"
|
||||
#include "abstractfilestorage.h"
|
||||
#include "torrentcontenthandler.h"
|
||||
|
||||
class QBitArray;
|
||||
class QByteArray;
|
||||
@@ -106,9 +106,10 @@ namespace BitTorrent
|
||||
uint qHash(TorrentState key, uint seed = 0);
|
||||
#endif
|
||||
|
||||
class Torrent : public AbstractFileStorage
|
||||
class Torrent : public TorrentContentHandler
|
||||
{
|
||||
Q_GADGET
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(Torrent)
|
||||
|
||||
public:
|
||||
enum class StopCondition
|
||||
@@ -128,7 +129,7 @@ namespace BitTorrent
|
||||
static const qreal MAX_RATIO;
|
||||
static const int MAX_SEEDING_TIME;
|
||||
|
||||
virtual ~Torrent() = default;
|
||||
using TorrentContentHandler::TorrentContentHandler;
|
||||
|
||||
virtual InfoHash infoHash() const = 0;
|
||||
virtual QString name() const = 0;
|
||||
@@ -191,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;
|
||||
@@ -211,12 +211,10 @@ 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 isSeed() const = 0;
|
||||
virtual bool isFinished() const = 0;
|
||||
virtual bool isPaused() const = 0;
|
||||
virtual bool isQueued() const = 0;
|
||||
virtual bool isForced() const = 0;
|
||||
@@ -231,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;
|
||||
@@ -243,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;
|
||||
@@ -276,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;
|
||||
@@ -292,7 +281,6 @@ namespace BitTorrent
|
||||
virtual void forceReannounce(int index = -1) = 0;
|
||||
virtual void forceDHTAnnounce() = 0;
|
||||
virtual void forceRecheck() = 0;
|
||||
virtual void prioritizeFiles(const QVector<DownloadPriority> &priorities) = 0;
|
||||
virtual void setRatioLimit(qreal limit) = 0;
|
||||
virtual void setSeedingTimeLimit(int limit) = 0;
|
||||
virtual void setUploadLimit(int limit) = 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;
|
||||
@@ -309,7 +296,7 @@ namespace BitTorrent
|
||||
virtual void removeUrlSeeds(const QVector<QUrl> &urlSeeds) = 0;
|
||||
virtual bool connectPeer(const PeerAddress &peerAddress) = 0;
|
||||
virtual void clearPeers() = 0;
|
||||
virtual bool setMetadata(const TorrentInfo &torrentInfo) = 0;
|
||||
virtual void setMetadata(const TorrentInfo &torrentInfo) = 0;
|
||||
|
||||
virtual StopCondition stopCondition() const = 0;
|
||||
virtual void setStopCondition(StopCondition stopCondition) = 0;
|
||||
@@ -318,6 +305,11 @@ namespace BitTorrent
|
||||
virtual nonstd::expected<QByteArray, QString> exportToBuffer() const = 0;
|
||||
virtual nonstd::expected<void, QString> exportToFile(const Path &path) const = 0;
|
||||
|
||||
virtual void fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const = 0;
|
||||
virtual void fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const = 0;
|
||||
virtual void fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const = 0;
|
||||
virtual void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const = 0;
|
||||
|
||||
TorrentID id() const;
|
||||
bool isResumed() const;
|
||||
qlonglong remainingSize() const;
|
||||
|
||||
29
src/base/bittorrent/torrentcontenthandler.cpp
Normal file
29
src/base/bittorrent/torrentcontenthandler.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* In addition, as a special exception, the copyright holders give permission to
|
||||
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
||||
* modified versions of it that use the same license as the "OpenSSL" library),
|
||||
* and distribute the linked executables. You must obey the GNU General Public
|
||||
* License in all respects for all of the code used other than "OpenSSL". If you
|
||||
* modify file(s), you may extend this exception to your version of the file(s),
|
||||
* but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
* exception statement from your version.
|
||||
*/
|
||||
|
||||
#include "torrentcontenthandler.h"
|
||||
61
src/base/bittorrent/torrentcontenthandler.h
Normal file
61
src/base/bittorrent/torrentcontenthandler.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2022-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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "base/pathfwd.h"
|
||||
#include "abstractfilestorage.h"
|
||||
#include "downloadpriority.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentContentHandler : public QObject, public AbstractFileStorage
|
||||
{
|
||||
public:
|
||||
using QObject::QObject;
|
||||
|
||||
virtual bool hasMetadata() const = 0;
|
||||
virtual Path actualStorageLocation() const = 0;
|
||||
virtual Path actualFilePath(int fileIndex) const = 0;
|
||||
virtual QVector<DownloadPriority> filePriorities() const = 0;
|
||||
virtual QVector<qreal> filesProgress() 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 fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const = 0;
|
||||
|
||||
virtual void prioritizeFiles(const QVector<DownloadPriority> &priorities) = 0;
|
||||
virtual void flushCache() const = 0;
|
||||
};
|
||||
}
|
||||
@@ -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 ¶ms);
|
||||
|
||||
#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);
|
||||
|
||||
@@ -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
|
||||
@@ -36,7 +36,6 @@
|
||||
#include <libtorrent/address.hpp>
|
||||
#include <libtorrent/alert_types.hpp>
|
||||
#include <libtorrent/create_torrent.hpp>
|
||||
#include <libtorrent/magnet_uri.hpp>
|
||||
#include <libtorrent/session.hpp>
|
||||
#include <libtorrent/storage_defs.hpp>
|
||||
#include <libtorrent/time.hpp>
|
||||
@@ -48,6 +47,7 @@
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
@@ -82,10 +82,10 @@ namespace
|
||||
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
|
||||
, const lt::info_hash_t &hashes, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
|
||||
, const lt::info_hash_t &hashes, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
|
||||
#else
|
||||
void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
|
||||
, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
|
||||
, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
|
||||
#endif
|
||||
{
|
||||
Q_ASSERT(trackerEntry.url == QString::fromStdString(nativeEntry.url));
|
||||
@@ -98,7 +98,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})
|
||||
@@ -236,7 +236,7 @@ namespace
|
||||
|
||||
TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||
, const lt::torrent_handle &nativeHandle, const LoadTorrentParams ¶ms)
|
||||
: QObject(session)
|
||||
: Torrent(session)
|
||||
, m_session(session)
|
||||
, m_nativeSession(nativeSession)
|
||||
, m_nativeHandle(nativeHandle)
|
||||
@@ -254,7 +254,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||
, m_seedingTimeLimit(params.seedingTimeLimit)
|
||||
, m_operatingMode(params.operatingMode)
|
||||
, m_contentLayout(params.contentLayout)
|
||||
, m_hasSeedStatus(params.hasSeedStatus)
|
||||
, m_hasFinishedStatus(params.hasFinishedStatus)
|
||||
, m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
|
||||
, m_useAutoTMM(params.useAutoTMM)
|
||||
, m_isStopped(params.stopped)
|
||||
@@ -279,6 +279,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||
, LT::toNative(m_ltAddTorrentParams.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
|
||||
|
||||
m_completedFiles.fill(static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode), filesCount);
|
||||
m_filesProgress.resize(filesCount);
|
||||
|
||||
for (int i = 0; i < filesCount; ++i)
|
||||
{
|
||||
@@ -301,8 +302,14 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||
m_trackerEntries.reserve(static_cast<decltype(m_trackerEntries)::size_type>(extensionData->trackers.size()));
|
||||
for (const lt::announce_entry &announceEntry : extensionData->trackers)
|
||||
m_trackerEntries.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
|
||||
m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(extensionData->urlSeeds.size()));
|
||||
for (const std::string &urlSeed : extensionData->urlSeeds)
|
||||
m_urlSeeds.append(QString::fromStdString(urlSeed));
|
||||
m_nativeStatus = extensionData->status;
|
||||
|
||||
if (hasMetadata())
|
||||
updateProgress();
|
||||
|
||||
updateState();
|
||||
|
||||
if (hasMetadata())
|
||||
@@ -331,7 +338,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||
// == END UPGRADE CODE ==
|
||||
}
|
||||
|
||||
TorrentImpl::~TorrentImpl() {}
|
||||
TorrentImpl::~TorrentImpl() = default;
|
||||
|
||||
bool TorrentImpl::isValid() const
|
||||
{
|
||||
@@ -424,13 +431,16 @@ void TorrentImpl::setSavePath(const Path &path)
|
||||
if (resolvedPath == savePath())
|
||||
return;
|
||||
|
||||
m_savePath = resolvedPath;
|
||||
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
|
||||
const bool isFinished = isSeed() || m_hasSeedStatus;
|
||||
if (isFinished || downloadPath().isEmpty())
|
||||
moveStorage(savePath(), MoveStorageMode::KeepExistingFiles);
|
||||
if (isFinished() || m_hasFinishedStatus || downloadPath().isEmpty())
|
||||
{
|
||||
moveStorage(resolvedPath, MoveStorageContext::ChangeSavePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_savePath = resolvedPath;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
}
|
||||
|
||||
Path TorrentImpl::downloadPath() const
|
||||
@@ -448,13 +458,17 @@ void TorrentImpl::setDownloadPath(const Path &path)
|
||||
if (resolvedPath == m_downloadPath)
|
||||
return;
|
||||
|
||||
m_downloadPath = resolvedPath;
|
||||
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
|
||||
const bool isFinished = isSeed() || m_hasSeedStatus;
|
||||
if (!isFinished)
|
||||
moveStorage((m_downloadPath.isEmpty() ? savePath() : m_downloadPath), MoveStorageMode::KeepExistingFiles);
|
||||
const bool isIncomplete = !(isFinished() || m_hasFinishedStatus);
|
||||
if (isIncomplete)
|
||||
{
|
||||
moveStorage((resolvedPath.isEmpty() ? savePath() : resolvedPath), MoveStorageContext::ChangeDownloadPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_downloadPath = resolvedPath;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
}
|
||||
}
|
||||
|
||||
Path TorrentImpl::rootPath() const
|
||||
@@ -520,11 +534,19 @@ void TorrentImpl::setAutoManaged(const bool enable)
|
||||
m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
|
||||
}
|
||||
|
||||
Path TorrentImpl::wantedActualPath(int index, const Path &path) const
|
||||
{
|
||||
if (m_session->isAppendExtensionEnabled()
|
||||
&& (fileSize(index) > 0) && !m_completedFiles.at(index))
|
||||
{
|
||||
return path + QB_EXT;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
QVector<TrackerEntry> TorrentImpl::trackers() const
|
||||
{
|
||||
if (!m_updatedTrackerEntries.isEmpty())
|
||||
refreshTrackerEntries();
|
||||
|
||||
return m_trackerEntries;
|
||||
}
|
||||
|
||||
@@ -599,63 +621,95 @@ void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
|
||||
|
||||
QVector<QUrl> TorrentImpl::urlSeeds() const
|
||||
{
|
||||
const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
|
||||
|
||||
QVector<QUrl> urlSeeds;
|
||||
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
|
||||
|
||||
for (const std::string &urlSeed : currentSeeds)
|
||||
urlSeeds.append(QString::fromStdString(urlSeed));
|
||||
|
||||
return urlSeeds;
|
||||
return m_urlSeeds;
|
||||
}
|
||||
|
||||
void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
|
||||
{
|
||||
const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
|
||||
|
||||
QVector<QUrl> addedUrlSeeds;
|
||||
addedUrlSeeds.reserve(urlSeeds.size());
|
||||
|
||||
for (const QUrl &url : urlSeeds)
|
||||
m_session->invokeAsync([urlSeeds, session = m_session
|
||||
, nativeHandle = m_nativeHandle
|
||||
, thisTorrent = QPointer<TorrentImpl>(this)]
|
||||
{
|
||||
const std::string nativeUrl = url.toString().toStdString();
|
||||
if (currentSeeds.find(nativeUrl) == currentSeeds.end())
|
||||
try
|
||||
{
|
||||
m_nativeHandle.add_url_seed(nativeUrl);
|
||||
addedUrlSeeds << url;
|
||||
}
|
||||
}
|
||||
const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
|
||||
QVector<QUrl> currentSeeds;
|
||||
currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
|
||||
for (const std::string &urlSeed : nativeSeeds)
|
||||
currentSeeds.append(QString::fromStdString(urlSeed));
|
||||
|
||||
if (!addedUrlSeeds.isEmpty())
|
||||
{
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds);
|
||||
}
|
||||
QVector<QUrl> addedUrlSeeds;
|
||||
addedUrlSeeds.reserve(urlSeeds.size());
|
||||
|
||||
for (const QUrl &url : urlSeeds)
|
||||
{
|
||||
if (!currentSeeds.contains(url))
|
||||
{
|
||||
nativeHandle.add_url_seed(url.toString().toStdString());
|
||||
addedUrlSeeds.append(url);
|
||||
}
|
||||
}
|
||||
|
||||
currentSeeds.append(addedUrlSeeds);
|
||||
session->invoke([session, thisTorrent, currentSeeds, addedUrlSeeds]
|
||||
{
|
||||
if (!thisTorrent)
|
||||
return;
|
||||
|
||||
thisTorrent->m_urlSeeds = currentSeeds;
|
||||
if (!addedUrlSeeds.isEmpty())
|
||||
{
|
||||
session->handleTorrentNeedSaveResumeData(thisTorrent);
|
||||
session->handleTorrentUrlSeedsAdded(thisTorrent, addedUrlSeeds);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
|
||||
{
|
||||
const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
|
||||
|
||||
QVector<QUrl> removedUrlSeeds;
|
||||
removedUrlSeeds.reserve(urlSeeds.size());
|
||||
|
||||
for (const QUrl &url : urlSeeds)
|
||||
m_session->invokeAsync([urlSeeds, session = m_session
|
||||
, nativeHandle = m_nativeHandle
|
||||
, thisTorrent = QPointer<TorrentImpl>(this)]
|
||||
{
|
||||
const std::string nativeUrl = url.toString().toStdString();
|
||||
if (currentSeeds.find(nativeUrl) != currentSeeds.end())
|
||||
try
|
||||
{
|
||||
m_nativeHandle.remove_url_seed(nativeUrl);
|
||||
removedUrlSeeds << url;
|
||||
}
|
||||
}
|
||||
const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
|
||||
QVector<QUrl> currentSeeds;
|
||||
currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
|
||||
for (const std::string &urlSeed : nativeSeeds)
|
||||
currentSeeds.append(QString::fromStdString(urlSeed));
|
||||
|
||||
if (!removedUrlSeeds.isEmpty())
|
||||
{
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds);
|
||||
}
|
||||
QVector<QUrl> removedUrlSeeds;
|
||||
removedUrlSeeds.reserve(urlSeeds.size());
|
||||
|
||||
for (const QUrl &url : urlSeeds)
|
||||
{
|
||||
if (currentSeeds.removeOne(url))
|
||||
{
|
||||
nativeHandle.remove_url_seed(url.toString().toStdString());
|
||||
removedUrlSeeds.append(url);
|
||||
}
|
||||
}
|
||||
|
||||
session->invoke([session, thisTorrent, currentSeeds, removedUrlSeeds]
|
||||
{
|
||||
if (!thisTorrent)
|
||||
return;
|
||||
|
||||
thisTorrent->m_urlSeeds = currentSeeds;
|
||||
|
||||
if (!removedUrlSeeds.isEmpty())
|
||||
{
|
||||
session->handleTorrentNeedSaveResumeData(thisTorrent);
|
||||
session->handleTorrentUrlSeedsRemoved(thisTorrent, removedUrlSeeds);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
});
|
||||
}
|
||||
|
||||
void TorrentImpl::clearPeers()
|
||||
@@ -690,9 +744,9 @@ bool TorrentImpl::needSaveResumeData() const
|
||||
return m_nativeStatus.need_save_resume;
|
||||
}
|
||||
|
||||
void TorrentImpl::saveResumeData()
|
||||
void TorrentImpl::saveResumeData(lt::resume_data_flags_t flags)
|
||||
{
|
||||
m_nativeHandle.save_resume_data();
|
||||
m_nativeHandle.save_resume_data(flags);
|
||||
m_session->handleTorrentSaveResumeDataRequested(this);
|
||||
}
|
||||
|
||||
@@ -723,7 +777,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;
|
||||
}
|
||||
|
||||
@@ -956,7 +1016,7 @@ bool TorrentImpl::isErrored() const
|
||||
|| (m_state == TorrentState::Error));
|
||||
}
|
||||
|
||||
bool TorrentImpl::isSeed() const
|
||||
bool TorrentImpl::isFinished() const
|
||||
{
|
||||
return ((m_nativeStatus.state == lt::torrent_status::finished)
|
||||
|| (m_nativeStatus.state == lt::torrent_status::seeding));
|
||||
@@ -1012,9 +1072,9 @@ void TorrentImpl::updateState()
|
||||
else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isPaused())
|
||||
{
|
||||
// If the torrent is not just in the "checking" state, but is being actually checked
|
||||
m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
|
||||
m_state = m_hasFinishedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
|
||||
}
|
||||
else if (isSeed())
|
||||
else if (isFinished())
|
||||
{
|
||||
if (isPaused())
|
||||
m_state = TorrentState::PausedUploading;
|
||||
@@ -1102,7 +1162,7 @@ qlonglong TorrentImpl::eta() const
|
||||
|
||||
const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
|
||||
|
||||
if (isSeed())
|
||||
if (isFinished())
|
||||
{
|
||||
const qreal maxRatioValue = maxRatio();
|
||||
const int maxSeedingTimeValue = maxSeedingTime();
|
||||
@@ -1142,20 +1202,20 @@ QVector<qreal> TorrentImpl::filesProgress() const
|
||||
if (!hasMetadata())
|
||||
return {};
|
||||
|
||||
if (m_completedFiles.count(true) == filesCount())
|
||||
return QVector<qreal>(filesCount(), 1);
|
||||
const int count = m_filesProgress.size();
|
||||
Q_ASSERT(count == filesCount());
|
||||
if (Q_UNLIKELY(count != filesCount()))
|
||||
return {};
|
||||
|
||||
std::vector<int64_t> fp;
|
||||
m_nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity);
|
||||
if (m_completedFiles.count(true) == count)
|
||||
return QVector<qreal>(count, 1);
|
||||
|
||||
const int count = filesCount();
|
||||
const auto nativeIndexes = m_torrentInfo.nativeIndexes();
|
||||
QVector<qreal> result;
|
||||
result.reserve(count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
const int64_t progress = fp[LT::toUnderlyingType(nativeIndexes[i])];
|
||||
const qlonglong size = fileSize(i);
|
||||
const int64_t progress = m_filesProgress.at(i);
|
||||
const int64_t size = fileSize(i);
|
||||
if ((size <= 0) || (progress == size))
|
||||
result << 1;
|
||||
else
|
||||
@@ -1274,15 +1334,13 @@ QVector<PeerInfo> TorrentImpl::peers() const
|
||||
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
|
||||
|
||||
for (const lt::peer_info &peer : nativePeers)
|
||||
peers << PeerInfo(this, peer);
|
||||
peers.append(PeerInfo(peer, pieces()));
|
||||
|
||||
return peers;
|
||||
}
|
||||
|
||||
QBitArray TorrentImpl::pieces() const
|
||||
{
|
||||
if (m_pieces.isEmpty())
|
||||
m_pieces = LT::toQBitArray(m_nativeStatus.pieces);
|
||||
return m_pieces;
|
||||
}
|
||||
|
||||
@@ -1427,12 +1485,22 @@ void TorrentImpl::forceDHTAnnounce()
|
||||
|
||||
void TorrentImpl::forceRecheck()
|
||||
{
|
||||
if (!hasMetadata()) return;
|
||||
if (!hasMetadata())
|
||||
return;
|
||||
|
||||
m_nativeHandle.force_recheck();
|
||||
// We have to force update the cached state, otherwise someone will be able to get
|
||||
// an incorrect one during the interval until the cached state is updated in a regular way.
|
||||
m_nativeStatus.state = lt::torrent_status::checking_resume_data;
|
||||
|
||||
m_hasMissingFiles = false;
|
||||
m_unchecked = false;
|
||||
|
||||
m_completedFiles.fill(false);
|
||||
m_filesProgress.fill(0);
|
||||
m_pieces.fill(false);
|
||||
m_nativeStatus.pieces.clear_all();
|
||||
m_nativeStatus.num_pieces = 0;
|
||||
|
||||
if (isPaused())
|
||||
{
|
||||
@@ -1515,43 +1583,25 @@ void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileN
|
||||
endReceivedMetadataHandling(savePath, fileNames);
|
||||
}
|
||||
|
||||
void TorrentImpl::updatePeerCount(const QString &trackerURL, const TrackerEntry::Endpoint &endpoint, const int count)
|
||||
TrackerEntry TorrentImpl::updateTrackerEntry(const lt::announce_entry &announceEntry, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
|
||||
{
|
||||
m_updatedTrackerEntries[trackerURL][endpoint] = count;
|
||||
}
|
||||
|
||||
void TorrentImpl::invalidateTrackerEntry(const QString &trackerURL)
|
||||
{
|
||||
std::ignore = m_updatedTrackerEntries[trackerURL];
|
||||
}
|
||||
|
||||
void TorrentImpl::refreshTrackerEntries() const
|
||||
{
|
||||
const std::vector<lt::announce_entry> nativeTrackers = m_nativeHandle.trackers();
|
||||
Q_ASSERT(nativeTrackers.size() == m_trackerEntries.size());
|
||||
|
||||
for (TrackerEntry &trackerEntry : m_trackerEntries)
|
||||
const auto it = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end()
|
||||
, [&announceEntry](const TrackerEntry &trackerEntry)
|
||||
{
|
||||
const auto updatedTrackerIter = m_updatedTrackerEntries.find(trackerEntry.url);
|
||||
if (updatedTrackerIter == m_updatedTrackerEntries.end())
|
||||
continue;
|
||||
return (trackerEntry.url == QString::fromStdString(announceEntry.url));
|
||||
});
|
||||
|
||||
const auto nativeTrackerIter = std::find_if(nativeTrackers.cbegin(), nativeTrackers.cend()
|
||||
, [trackerURL = trackerEntry.url.toStdString()](const lt::announce_entry &announceEntry)
|
||||
{
|
||||
return (announceEntry.url == trackerURL);
|
||||
});
|
||||
Q_ASSERT(nativeTrackerIter != nativeTrackers.cend());
|
||||
Q_ASSERT(it != m_trackerEntries.end());
|
||||
// TODO: use [[unlikely]] in C++20
|
||||
if (Q_UNLIKELY(it == m_trackerEntries.end()))
|
||||
return {};
|
||||
|
||||
const lt::announce_entry &announceEntry = *nativeTrackerIter;
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
updateTrackerEntry(trackerEntry, announceEntry, m_nativeHandle.info_hashes(), updatedTrackerIter.value());
|
||||
::updateTrackerEntry(*it, announceEntry, nativeHandle().info_hashes(), updateInfo);
|
||||
#else
|
||||
updateTrackerEntry(trackerEntry, announceEntry, updatedTrackerIter.value());
|
||||
::updateTrackerEntry(*it, announceEntry, updateInfo);
|
||||
#endif
|
||||
}
|
||||
|
||||
m_updatedTrackerEntries.clear();
|
||||
return *it;
|
||||
}
|
||||
|
||||
std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
|
||||
@@ -1575,12 +1625,14 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
|
||||
, LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
|
||||
|
||||
m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount());
|
||||
m_filesProgress.resize(filesCount());
|
||||
updateProgress();
|
||||
|
||||
for (int i = 0; i < fileNames.size(); ++i)
|
||||
{
|
||||
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);
|
||||
@@ -1622,7 +1674,10 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
|
||||
void TorrentImpl::reload()
|
||||
{
|
||||
m_completedFiles.fill(false);
|
||||
m_pieces.clear();
|
||||
m_filesProgress.fill(0);
|
||||
m_pieces.fill(false);
|
||||
m_nativeStatus.pieces.clear_all();
|
||||
m_nativeStatus.num_pieces = 0;
|
||||
|
||||
const auto queuePos = m_nativeHandle.queue_position();
|
||||
|
||||
@@ -1713,7 +1768,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())
|
||||
{
|
||||
@@ -1721,7 +1776,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();
|
||||
@@ -1730,15 +1787,12 @@ void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageMode mode)
|
||||
|
||||
void TorrentImpl::renameFile(const int index, const Path &path)
|
||||
{
|
||||
const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
|
||||
|
||||
Q_ASSERT(index >= 0);
|
||||
Q_ASSERT(index < nativeIndexes.size());
|
||||
if ((index < 0) || (index >= nativeIndexes.size()))
|
||||
Q_ASSERT((index >= 0) && (index < filesCount()));
|
||||
if (Q_UNLIKELY((index < 0) || (index >= filesCount())))
|
||||
return;
|
||||
|
||||
++m_renameCount;
|
||||
m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
|
||||
const Path wantedPath = wantedActualPath(index, path);
|
||||
doRenameFile(index, wantedPath);
|
||||
}
|
||||
|
||||
void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
|
||||
@@ -1746,16 +1800,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)
|
||||
{
|
||||
@@ -1794,9 +1849,9 @@ void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
|
||||
if (!m_hasMissingFiles)
|
||||
{
|
||||
if ((progress() < 1.0) && (wantedSize() > 0))
|
||||
m_hasSeedStatus = false;
|
||||
m_hasFinishedStatus = false;
|
||||
else if (progress() == 1.0)
|
||||
m_hasSeedStatus = true;
|
||||
m_hasFinishedStatus = true;
|
||||
|
||||
adjustStorageLocation();
|
||||
manageIncompleteFiles();
|
||||
@@ -1823,30 +1878,29 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
|
||||
Q_UNUSED(p);
|
||||
|
||||
m_hasMissingFiles = false;
|
||||
if (m_hasSeedStatus)
|
||||
if (m_hasFinishedStatus)
|
||||
return;
|
||||
|
||||
m_statusUpdatedTriggers.enqueue([this]()
|
||||
{
|
||||
m_hasSeedStatus = true;
|
||||
|
||||
adjustStorageLocation();
|
||||
manageIncompleteFiles();
|
||||
|
||||
m_session->handleTorrentNeedSaveResumeData(this);
|
||||
|
||||
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
|
||||
if (isMoveInProgress() || (m_renameCount > 0))
|
||||
if (recheckTorrentsOnCompletion && m_unchecked)
|
||||
{
|
||||
if (recheckTorrentsOnCompletion)
|
||||
m_moveFinishedTriggers.enqueue([this]() { forceRecheck(); });
|
||||
m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
|
||||
forceRecheck();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (recheckTorrentsOnCompletion && m_unchecked)
|
||||
forceRecheck();
|
||||
m_session->handleTorrentFinished(this);
|
||||
m_hasFinishedStatus = true;
|
||||
|
||||
if (isMoveInProgress() || (m_renameCount > 0))
|
||||
m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
|
||||
else
|
||||
m_session->handleTorrentFinished(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1863,6 +1917,14 @@ void TorrentImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p)
|
||||
|
||||
void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
||||
{
|
||||
if (m_ltAddTorrentParams.url_seeds != p->params.url_seeds)
|
||||
{
|
||||
// URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
|
||||
// Unfortunately, URL seed list containing in "resume data" is generated according to different rules
|
||||
// than the list we usually cache, so we have to request it from the appropriate source.
|
||||
fetchURLSeeds([this](const QVector<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
|
||||
}
|
||||
|
||||
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
|
||||
{
|
||||
Q_ASSERT(m_indexMap.isEmpty());
|
||||
@@ -1947,7 +2009,7 @@ void TorrentImpl::prepareResumeData(const lt::add_torrent_params ¶ms)
|
||||
resumeData.ratioLimit = m_ratioLimit;
|
||||
resumeData.seedingTimeLimit = m_seedingTimeLimit;
|
||||
resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
|
||||
resumeData.hasSeedStatus = m_hasSeedStatus;
|
||||
resumeData.hasFinishedStatus = m_hasFinishedStatus;
|
||||
resumeData.stopped = m_isStopped;
|
||||
resumeData.stopCondition = m_stopCondition;
|
||||
resumeData.operatingMode = m_operatingMode;
|
||||
@@ -1990,13 +2052,12 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
|
||||
// For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
|
||||
// be removed if they are empty
|
||||
const Path oldFilePath = m_filePaths.at(fileIndex);
|
||||
const Path newFilePath {QString::fromUtf8(p->new_name())};
|
||||
const Path newFilePath = Path(QString::fromUtf8(p->new_name())).removedExtension(QB_EXT);
|
||||
|
||||
// Check if ".!qB" extension was just added or removed
|
||||
// We should compare path in a case sensitive manner even on case insensitive
|
||||
// platforms since it can be renamed by only changing case of some character(s)
|
||||
if ((oldFilePath.data() != newFilePath.data())
|
||||
&& ((oldFilePath + QB_EXT) != newFilePath))
|
||||
if (oldFilePath.data() != newFilePath.data())
|
||||
{
|
||||
m_filePaths[fileIndex] = newFilePath;
|
||||
|
||||
@@ -2039,7 +2100,7 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
|
||||
const int fileIndex = m_indexMap.value(p->index, -1);
|
||||
Q_ASSERT(fileIndex >= 0);
|
||||
|
||||
m_completedFiles[fileIndex] = true;
|
||||
m_completedFiles.setBit(fileIndex);
|
||||
|
||||
if (m_session->isAppendExtensionEnabled())
|
||||
{
|
||||
@@ -2048,7 +2109,7 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
|
||||
if (actualPath != path)
|
||||
{
|
||||
qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString()));
|
||||
renameFile(fileIndex, path);
|
||||
doRenameFile(fileIndex, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2155,7 +2216,6 @@ void TorrentImpl::manageIncompleteFiles()
|
||||
{
|
||||
const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
|
||||
const lt::file_storage &nativeFiles = nativeInfo->files();
|
||||
const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled();
|
||||
|
||||
for (int i = 0; i < filesCount(); ++i)
|
||||
{
|
||||
@@ -2163,23 +2223,11 @@ void TorrentImpl::manageIncompleteFiles()
|
||||
|
||||
const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i);
|
||||
const Path actualPath {nativeFiles.file_path(nativeIndex)};
|
||||
|
||||
if (isAppendExtensionEnabled && (fileSize(i) > 0) && !m_completedFiles.at(i))
|
||||
const Path wantedPath = wantedActualPath(i, path);
|
||||
if (actualPath != wantedPath)
|
||||
{
|
||||
const Path wantedPath = path + QB_EXT;
|
||||
if (actualPath != wantedPath)
|
||||
{
|
||||
qDebug() << "Renaming" << actualPath.toString() << "to" << wantedPath.toString();
|
||||
renameFile(i, wantedPath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (actualPath != path)
|
||||
{
|
||||
qDebug() << "Renaming" << actualPath.toString() << "to" << path.toString();
|
||||
renameFile(i, path);
|
||||
}
|
||||
qDebug() << "Renaming" << actualPath.toString() << "to" << wantedPath.toString();
|
||||
doRenameFile(i, wantedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2187,11 +2235,23 @@ void TorrentImpl::manageIncompleteFiles()
|
||||
void TorrentImpl::adjustStorageLocation()
|
||||
{
|
||||
const Path downloadPath = this->downloadPath();
|
||||
const bool isFinished = isSeed() || m_hasSeedStatus;
|
||||
const Path targetPath = ((isFinished || downloadPath.isEmpty()) ? savePath() : downloadPath);
|
||||
const Path targetPath = ((isFinished() || m_hasFinishedStatus || downloadPath.isEmpty()) ? savePath() : downloadPath);
|
||||
|
||||
if ((targetPath != actualStorageLocation()) || isMoveInProgress())
|
||||
moveStorage(targetPath, MoveStorageMode::FailIfExist);
|
||||
moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation);
|
||||
}
|
||||
|
||||
void TorrentImpl::doRenameFile(int index, const Path &path)
|
||||
{
|
||||
const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
|
||||
|
||||
Q_ASSERT(index >= 0);
|
||||
Q_ASSERT(index < nativeIndexes.size());
|
||||
if (Q_UNLIKELY((index < 0) || (index >= nativeIndexes.size())))
|
||||
return;
|
||||
|
||||
++m_renameCount;
|
||||
m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
|
||||
}
|
||||
|
||||
lt::torrent_handle TorrentImpl::nativeHandle() const
|
||||
@@ -2199,17 +2259,24 @@ lt::torrent_handle TorrentImpl::nativeHandle() const
|
||||
return m_nativeHandle;
|
||||
}
|
||||
|
||||
bool TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
|
||||
void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
|
||||
{
|
||||
if (hasMetadata())
|
||||
return false;
|
||||
return;
|
||||
|
||||
m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo]
|
||||
{
|
||||
try
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
return m_nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
|
||||
nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
|
||||
#else
|
||||
const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
|
||||
return m_nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
|
||||
const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
|
||||
nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
|
||||
#endif
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
});
|
||||
}
|
||||
|
||||
Torrent::StopCondition TorrentImpl::stopCondition() const
|
||||
@@ -2241,8 +2308,11 @@ bool TorrentImpl::isMoveInProgress() const
|
||||
|
||||
void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
|
||||
{
|
||||
m_pieces.clear();
|
||||
m_nativeStatus = nativeStatus;
|
||||
const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
|
||||
|
||||
if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
|
||||
updateProgress();
|
||||
|
||||
updateState();
|
||||
|
||||
m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
|
||||
@@ -2262,6 +2332,44 @@ void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
|
||||
std::invoke(m_statusUpdatedTriggers.dequeue());
|
||||
}
|
||||
|
||||
void TorrentImpl::updateProgress()
|
||||
{
|
||||
Q_ASSERT(hasMetadata());
|
||||
if (Q_UNLIKELY(!hasMetadata()))
|
||||
return;
|
||||
|
||||
Q_ASSERT(!m_filesProgress.isEmpty());
|
||||
if (Q_UNLIKELY(m_filesProgress.isEmpty()))
|
||||
m_filesProgress.resize(filesCount());
|
||||
|
||||
const QBitArray oldPieces = std::exchange(m_pieces, LT::toQBitArray(m_nativeStatus.pieces));
|
||||
const QBitArray newPieces = m_pieces ^ oldPieces;
|
||||
|
||||
const int64_t pieceSize = m_torrentInfo.pieceLength();
|
||||
for (qsizetype index = 0; index < newPieces.size(); ++index)
|
||||
{
|
||||
if (!newPieces.at(index))
|
||||
continue;
|
||||
|
||||
int64_t size = m_torrentInfo.pieceLength(index);
|
||||
int64_t pieceOffset = index * pieceSize;
|
||||
|
||||
for (const int fileIndex : asConst(m_torrentInfo.fileIndicesForPiece(index)))
|
||||
{
|
||||
const int64_t fileOffsetInPiece = pieceOffset - m_torrentInfo.fileOffset(fileIndex);
|
||||
const int64_t add = std::min<int64_t>((m_torrentInfo.fileSize(fileIndex) - fileOffsetInPiece), size);
|
||||
|
||||
m_filesProgress[fileIndex] += add;
|
||||
|
||||
size -= add;
|
||||
if (size <= 0)
|
||||
break;
|
||||
|
||||
pieceOffset += add;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::setRatioLimit(qreal limit)
|
||||
{
|
||||
if (limit < USE_GLOBAL_RATIO)
|
||||
@@ -2373,7 +2481,39 @@ void TorrentImpl::flushCache() const
|
||||
|
||||
QString TorrentImpl::createMagnetURI() const
|
||||
{
|
||||
return QString::fromStdString(lt::make_magnet_uri(m_nativeHandle));
|
||||
QString ret = u"magnet:?"_qs;
|
||||
|
||||
const SHA1Hash infoHash1 = infoHash().v1();
|
||||
if (infoHash1.isValid())
|
||||
{
|
||||
ret += u"xt=urn:btih:" + infoHash1.toString();
|
||||
}
|
||||
|
||||
const SHA256Hash infoHash2 = infoHash().v2();
|
||||
if (infoHash2.isValid())
|
||||
{
|
||||
if (infoHash1.isValid())
|
||||
ret += u'&';
|
||||
ret += u"xt=urn:btmh:1220" + infoHash2.toString();
|
||||
}
|
||||
|
||||
const QString displayName = name();
|
||||
if (displayName != id().toString())
|
||||
{
|
||||
ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
|
||||
}
|
||||
|
||||
for (const TrackerEntry &tracker : asConst(trackers()))
|
||||
{
|
||||
ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
|
||||
}
|
||||
|
||||
for (const QUrl &urlSeed : asConst(urlSeeds()))
|
||||
{
|
||||
ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
|
||||
@@ -2429,6 +2569,129 @@ nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) cons
|
||||
return {};
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QVector<PeerInfo>
|
||||
{
|
||||
try
|
||||
{
|
||||
std::vector<lt::peer_info> nativePeers;
|
||||
nativeHandle.get_peer_info(nativePeers);
|
||||
QVector<PeerInfo> peers;
|
||||
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
|
||||
for (const lt::peer_info &peer : nativePeers)
|
||||
peers.append(PeerInfo(peer, allPieces));
|
||||
return peers;
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<QUrl>
|
||||
{
|
||||
try
|
||||
{
|
||||
const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
|
||||
QVector<QUrl> urlSeeds;
|
||||
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
|
||||
for (const std::string &urlSeed : currentSeeds)
|
||||
urlSeeds.append(QString::fromStdString(urlSeed));
|
||||
return urlSeeds;
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<int>
|
||||
{
|
||||
try
|
||||
{
|
||||
std::vector<int> piecesAvailability;
|
||||
nativeHandle.piece_availability(piecesAvailability);
|
||||
return QVector<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
|
||||
{
|
||||
try
|
||||
{
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
const std::vector<lt::partial_piece_info> queue = nativeHandle.get_download_queue();
|
||||
#else
|
||||
std::vector<lt::partial_piece_info> queue;
|
||||
nativeHandle.get_download_queue(queue);
|
||||
#endif
|
||||
QBitArray result;
|
||||
result.resize(torrentInfo.piecesCount());
|
||||
for (const lt::partial_piece_info &info : queue)
|
||||
result.setBit(LT::toUnderlyingType(info.piece_index));
|
||||
return result;
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QVector<qreal>
|
||||
{
|
||||
if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
|
||||
return {};
|
||||
|
||||
try
|
||||
{
|
||||
std::vector<int> piecesAvailability;
|
||||
nativeHandle.piece_availability(piecesAvailability);
|
||||
const int filesCount = torrentInfo.filesCount();
|
||||
// libtorrent returns empty array for seeding only torrents
|
||||
if (piecesAvailability.empty())
|
||||
return QVector<qreal>(filesCount, -1);
|
||||
|
||||
QVector<qreal> result;
|
||||
result.reserve(filesCount);
|
||||
for (int i = 0; i < filesCount; ++i)
|
||||
{
|
||||
const TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(i);
|
||||
|
||||
int availablePieces = 0;
|
||||
for (const int piece : filePieces)
|
||||
availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
|
||||
|
||||
const qreal availability = filePieces.isEmpty()
|
||||
? 1 // the file has no pieces, so it is available by default
|
||||
: static_cast<qreal>(availablePieces) / filePieces.size();
|
||||
result.append(availability);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
|
||||
return {};
|
||||
}
|
||||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
|
||||
{
|
||||
if (!hasMetadata()) return;
|
||||
@@ -2444,7 +2707,7 @@ void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
|
||||
&& (priorities[i] > DownloadPriority::Ignored)
|
||||
&& !m_completedFiles.at(i))
|
||||
{
|
||||
m_hasSeedStatus = false;
|
||||
m_hasFinishedStatus = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2492,3 +2755,19 @@ QVector<qreal> TorrentImpl::availableFileFractions() const
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename Func, typename Callback>
|
||||
void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
|
||||
{
|
||||
m_session->invokeAsync([session = m_session
|
||||
, func = std::move(func)
|
||||
, resultHandler = std::move(resultHandler)
|
||||
, thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
|
||||
{
|
||||
session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
|
||||
{
|
||||
if (thisTorrent)
|
||||
resultHandler(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -68,6 +68,13 @@ namespace BitTorrent
|
||||
Overwrite
|
||||
};
|
||||
|
||||
enum class MoveStorageContext
|
||||
{
|
||||
AdjustCurrentLocation,
|
||||
ChangeSavePath,
|
||||
ChangeDownloadPath
|
||||
};
|
||||
|
||||
enum class MaintenanceJob
|
||||
{
|
||||
None,
|
||||
@@ -77,13 +84,13 @@ namespace BitTorrent
|
||||
struct FileErrorInfo
|
||||
{
|
||||
lt::error_code error;
|
||||
lt::operation_t operation;
|
||||
lt::operation_t operation = lt::operation_t::unknown;
|
||||
};
|
||||
|
||||
class TorrentImpl final : public QObject, public Torrent
|
||||
class TorrentImpl final : public Torrent
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(TorrentImpl)
|
||||
Q_DECLARE_TR_FUNCTIONS(BitTorrent::TorrentImpl)
|
||||
|
||||
public:
|
||||
TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
||||
@@ -139,7 +146,7 @@ namespace BitTorrent
|
||||
QVector<DownloadPriority> filePriorities() const override;
|
||||
|
||||
TorrentInfo info() const override;
|
||||
bool isSeed() const override;
|
||||
bool isFinished() const override;
|
||||
bool isPaused() const override;
|
||||
bool isQueued() const override;
|
||||
bool isForced() const override;
|
||||
@@ -227,7 +234,7 @@ namespace BitTorrent
|
||||
void removeUrlSeeds(const QVector<QUrl> &urlSeeds) override;
|
||||
bool connectPeer(const PeerAddress &peerAddress) override;
|
||||
void clearPeers() override;
|
||||
bool setMetadata(const TorrentInfo &torrentInfo) override;
|
||||
void setMetadata(const TorrentInfo &torrentInfo) override;
|
||||
|
||||
StopCondition stopCondition() const override;
|
||||
void setStopCondition(StopCondition stopCondition) override;
|
||||
@@ -236,6 +243,12 @@ namespace BitTorrent
|
||||
nonstd::expected<QByteArray, QString> exportToBuffer() const override;
|
||||
nonstd::expected<void, QString> exportToFile(const Path &path) const override;
|
||||
|
||||
void fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const override;
|
||||
void fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const override;
|
||||
void fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const override;
|
||||
void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const override;
|
||||
void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const override;
|
||||
|
||||
bool needSaveResumeData() const;
|
||||
|
||||
// Session interface
|
||||
@@ -245,19 +258,18 @@ namespace BitTorrent
|
||||
void handleStateUpdate(const lt::torrent_status &nativeStatus);
|
||||
void handleCategoryOptionsChanged();
|
||||
void handleAppendExtensionToggled();
|
||||
void saveResumeData();
|
||||
void handleMoveStorageJobFinished(const Path &path, bool hasOutstandingJob);
|
||||
void saveResumeData(lt::resume_data_flags_t flags = {});
|
||||
void handleMoveStorageJobFinished(const Path &path, MoveStorageContext context, bool hasOutstandingJob);
|
||||
void fileSearchFinished(const Path &savePath, const PathList &fileNames);
|
||||
void updatePeerCount(const QString &trackerURL, const TrackerEntry::Endpoint &endpoint, int count);
|
||||
void invalidateTrackerEntry(const QString &trackerURL);
|
||||
TrackerEntry updateTrackerEntry(const lt::announce_entry &announceEntry, const QMap<TrackerEntry::Endpoint, int> &updateInfo);
|
||||
|
||||
private:
|
||||
using EventTrigger = std::function<void ()>;
|
||||
|
||||
std::shared_ptr<const lt::torrent_info> nativeTorrentInfo() const;
|
||||
|
||||
void refreshTrackerEntries() const;
|
||||
void updateStatus(const lt::torrent_status &nativeStatus);
|
||||
void updateProgress();
|
||||
void updateState();
|
||||
|
||||
void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p);
|
||||
@@ -281,8 +293,10 @@ namespace BitTorrent
|
||||
|
||||
void setAutoManaged(bool enable);
|
||||
|
||||
Path wantedActualPath(int index, const Path &path) const;
|
||||
void adjustStorageLocation();
|
||||
void moveStorage(const Path &newPath, MoveStorageMode mode);
|
||||
void doRenameFile(int index, const Path &path);
|
||||
void moveStorage(const Path &newPath, MoveStorageContext context);
|
||||
void manageIncompleteFiles();
|
||||
void applyFirstLastPiecePriority(bool enabled);
|
||||
|
||||
@@ -292,6 +306,9 @@ namespace BitTorrent
|
||||
|
||||
nonstd::expected<lt::entry, QString> exportTorrent() const;
|
||||
|
||||
template <typename Func, typename Callback>
|
||||
void invokeAsync(Func func, Callback resultHandler) const;
|
||||
|
||||
SessionImpl *const m_session = nullptr;
|
||||
lt::session *m_nativeSession = nullptr;
|
||||
lt::torrent_handle m_nativeHandle;
|
||||
@@ -316,10 +333,8 @@ namespace BitTorrent
|
||||
|
||||
MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
|
||||
|
||||
// TODO: Use QHash<TrackerEntry::Endpoint, int> once Qt5 is dropped.
|
||||
using TrackerEntryUpdateInfo = QMap<TrackerEntry::Endpoint, int>;
|
||||
mutable QHash<QString, TrackerEntryUpdateInfo> m_updatedTrackerEntries;
|
||||
mutable QVector<TrackerEntry> m_trackerEntries;
|
||||
QVector<TrackerEntry> m_trackerEntries;
|
||||
QVector<QUrl> m_urlSeeds;
|
||||
FileErrorInfo m_lastFileError;
|
||||
|
||||
// Persistent data
|
||||
@@ -332,7 +347,7 @@ namespace BitTorrent
|
||||
int m_seedingTimeLimit;
|
||||
TorrentOperatingMode m_operatingMode;
|
||||
TorrentContentLayout m_contentLayout;
|
||||
bool m_hasSeedStatus;
|
||||
bool m_hasFinishedStatus;
|
||||
bool m_hasMissingFiles = false;
|
||||
bool m_hasFirstLastPiecePriority = false;
|
||||
bool m_useAutoTMM;
|
||||
@@ -346,6 +361,7 @@ namespace BitTorrent
|
||||
int m_downloadLimit = 0;
|
||||
int m_uploadLimit = 0;
|
||||
|
||||
mutable QBitArray m_pieces;
|
||||
QBitArray m_pieces;
|
||||
QVector<std::int64_t> m_filesProgress;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -203,12 +203,12 @@ Tracker::Tracker(QObject *parent)
|
||||
|
||||
bool Tracker::start()
|
||||
{
|
||||
const QHostAddress ip = QHostAddress::Any;
|
||||
const int port = Preferences::instance()->getTrackerPort();
|
||||
|
||||
if (m_server->isListening())
|
||||
{
|
||||
if (m_server->serverPort() == port)
|
||||
if (const int oldPort = m_server->serverPort()
|
||||
; oldPort == port)
|
||||
{
|
||||
// Already listening on the right port, just return
|
||||
return true;
|
||||
@@ -218,9 +218,9 @@ bool Tracker::start()
|
||||
m_server->close();
|
||||
}
|
||||
|
||||
// Listen on the predefined port
|
||||
// Listen on port
|
||||
const QHostAddress ip = QHostAddress::Any;
|
||||
const bool listenSuccess = m_server->listen(ip, port);
|
||||
|
||||
if (listenSuccess)
|
||||
{
|
||||
LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2")
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -167,6 +167,6 @@ std::size_t qHash(const Digest32<N> &key, const std::size_t seed = 0)
|
||||
template <int N>
|
||||
uint qHash(const Digest32<N> &key, const uint seed = 0)
|
||||
{
|
||||
return static_cast<uint>((std::hash<typename Digest32<N>::UnderlyingType> {})(key)) ^ seed;
|
||||
return ::qHash(std::hash<typename Digest32<N>::UnderlyingType> {}(key), seed);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Http
|
||||
class IRequestHandler
|
||||
{
|
||||
public:
|
||||
virtual ~IRequestHandler() {}
|
||||
virtual ~IRequestHandler() = default;
|
||||
virtual Response processRequest(const Request &request, const Environment &env) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,10 +75,6 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
RequestParser::RequestParser()
|
||||
{
|
||||
}
|
||||
|
||||
RequestParser::ParseResult RequestParser::parse(const QByteArray &data)
|
||||
{
|
||||
// Warning! Header names are converted to lowercase
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -56,10 +56,33 @@ namespace
|
||||
QList<QSslCipher> safeCipherList()
|
||||
{
|
||||
const QStringList badCiphers {u"idea"_qs, u"rc4"_qs};
|
||||
// Contains Ciphersuites that use RSA for the Key Exchange but they don't mention it in their name
|
||||
const QStringList badRSAShorthandSuites {
|
||||
u"AES256-GCM-SHA384"_qs, u"AES128-GCM-SHA256"_qs, u"AES256-SHA256"_qs,
|
||||
u"AES128-SHA256"_qs, u"AES256-SHA"_qs, u"AES128-SHA"_qs};
|
||||
// Contains Ciphersuites that use AES CBC mode but they don't mention it in their name
|
||||
const QStringList badAESShorthandSuites {
|
||||
u"ECDHE-ECDSA-AES256-SHA384"_qs, u"ECDHE-RSA-AES256-SHA384"_qs, u"DHE-RSA-AES256-SHA256"_qs,
|
||||
u"ECDHE-ECDSA-AES128-SHA256"_qs, u"ECDHE-RSA-AES128-SHA256"_qs, u"DHE-RSA-AES128-SHA256"_qs,
|
||||
u"ECDHE-ECDSA-AES256-SHA"_qs, u"ECDHE-RSA-AES256-SHA"_qs, u"DHE-RSA-AES256-SHA"_qs,
|
||||
u"ECDHE-ECDSA-AES128-SHA"_qs, u"ECDHE-RSA-AES128-SHA"_qs, u"DHE-RSA-AES128-SHA"_qs};
|
||||
const QList<QSslCipher> allCiphers {QSslConfiguration::supportedCiphers()};
|
||||
QList<QSslCipher> safeCiphers;
|
||||
std::copy_if(allCiphers.cbegin(), allCiphers.cend(), std::back_inserter(safeCiphers), [&badCiphers](const QSslCipher &cipher)
|
||||
std::copy_if(allCiphers.cbegin(), allCiphers.cend(), std::back_inserter(safeCiphers),
|
||||
[&badCiphers, &badRSAShorthandSuites, &badAESShorthandSuites](const QSslCipher &cipher)
|
||||
{
|
||||
const QString name = cipher.name();
|
||||
if (name.contains(u"-cbc-"_qs, Qt::CaseInsensitive) // AES CBC mode is considered vulnerable to BEAST attack
|
||||
|| name.startsWith(u"adh-"_qs, Qt::CaseInsensitive) // Key Exchange: Diffie-Hellman, doesn't support Perfect Forward Secrecy
|
||||
|| name.startsWith(u"aecdh-"_qs, Qt::CaseInsensitive) // Key Exchange: Elliptic Curve Diffie-Hellman, doesn't support Perfect Forward Secrecy
|
||||
|| name.startsWith(u"psk-"_qs, Qt::CaseInsensitive) // Key Exchange: Pre-Shared Key, doesn't support Perfect Forward Secrecy
|
||||
|| name.startsWith(u"rsa-"_qs, Qt::CaseInsensitive) // Key Exchange: Rivest Shamir Adleman (RSA), doesn't support Perfect Forward Secrecy
|
||||
|| badRSAShorthandSuites.contains(name, Qt::CaseInsensitive)
|
||||
|| badAESShorthandSuites.contains(name, Qt::CaseInsensitive))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::none_of(badCiphers.cbegin(), badCiphers.cend(), [&cipher](const QString &badCipher)
|
||||
{
|
||||
return cipher.name().contains(badCipher, Qt::CaseInsensitive);
|
||||
@@ -78,6 +101,7 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
||||
setProxy(QNetworkProxy::NoProxy);
|
||||
|
||||
QSslConfiguration sslConf {QSslConfiguration::defaultConfiguration()};
|
||||
sslConf.setProtocol(QSsl::TlsV1_2OrLater);
|
||||
sslConf.setCiphers(safeCipherList());
|
||||
QSslConfiguration::setDefaultConfiguration(sslConf);
|
||||
|
||||
|
||||
@@ -79,10 +79,10 @@ namespace Http
|
||||
struct Environment
|
||||
{
|
||||
QHostAddress localAddress;
|
||||
quint16 localPort;
|
||||
quint16 localPort = 0;
|
||||
|
||||
QHostAddress clientAddress;
|
||||
quint16 clientPort;
|
||||
quint16 clientPort = 0;
|
||||
};
|
||||
|
||||
struct UploadedFile
|
||||
|
||||
@@ -36,8 +36,6 @@ IconProvider::IconProvider(QObject *parent)
|
||||
{
|
||||
}
|
||||
|
||||
IconProvider::~IconProvider() {}
|
||||
|
||||
void IconProvider::initInstance()
|
||||
{
|
||||
if (!m_instance)
|
||||
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
|
||||
protected:
|
||||
explicit IconProvider(QObject *parent = nullptr);
|
||||
~IconProvider();
|
||||
~IconProvider() = default;
|
||||
|
||||
static IconProvider *m_instance;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user