mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-04-23 14:07:32 -04:00
Compare commits
463 commits
release-5.
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
0b3bce8993 | ||
|
0160aa28b6 | ||
|
0187f19f60 | ||
|
e87dfe35f3 | ||
|
e51be45ce6 | ||
|
b4a16f6464 | ||
|
250fef4ee7 | ||
|
8fc5d0914d | ||
|
fc5daf6e1d | ||
|
c878a09d27 | ||
|
2aee875642 | ||
|
2785636d3f | ||
|
15069b2643 | ||
|
f0361f1bed | ||
|
110e6d32b4 | ||
|
3d73026ff2 | ||
|
abafbc0685 | ||
|
5465605377 | ||
|
9331580e86 | ||
|
795889c417 | ||
|
ff03eeab5b | ||
|
f0b9a17566 | ||
|
72e8b3272b | ||
|
6c36830e5e | ||
|
cdddaae939 | ||
|
f540381caf | ||
|
055d82bda4 | ||
|
0796f96ee4 | ||
|
841cffafa7 | ||
|
ade39432be | ||
|
830d2c207b | ||
|
97865545c3 | ||
|
3abdc3134b | ||
|
5a716a40fb | ||
|
943e403241 | ||
|
103ea813dc | ||
|
52b1f3588a | ||
|
4bd50672e8 | ||
|
8c8a0ac54c | ||
|
7b4a3fccc6 | ||
|
d21653e8cf | ||
|
627d89813c | ||
|
b28c229f85 | ||
|
8d0870c953 | ||
|
5a4b3b25d3 | ||
|
d174bc75e4 | ||
|
882da47609 | ||
|
b74b334e34 | ||
|
53f919aea8 | ||
|
62a7fd86d6 | ||
|
96295adc08 | ||
|
8f53fb8178 | ||
|
37eb80919c | ||
|
1b044d9476 | ||
|
83599f1f7b | ||
|
6e1b5ec18b | ||
|
249c80aaaf | ||
|
0ac47496d4 | ||
|
4ec80de268 | ||
|
f432c1e615 | ||
|
41d9ee91a1 | ||
|
ba3d89b674 | ||
|
1ca33d45ba | ||
|
a9b54d94a0 | ||
|
693390ff27 | ||
|
5ddc5a8b87 | ||
|
ad9100ac07 | ||
|
1043bea896 | ||
|
955688c125 | ||
|
8da43a4054 | ||
|
ddf6dd5fa2 | ||
|
8c02bbb4bc | ||
|
7e95375cec | ||
|
29201fa016 | ||
|
1a3d0f6fab | ||
|
f58d6ae984 | ||
|
7f0134108a | ||
|
d79dc86d00 | ||
|
38070c6eee | ||
|
c9eb1fbac8 | ||
|
7238bad5a6 | ||
|
bd564a99a3 | ||
|
b052ad0923 | ||
|
c65a68251e | ||
|
93925042dd | ||
|
e55b59d9ca | ||
|
f8469d02f7 | ||
|
dc10b88cec | ||
|
4406a3f173 | ||
|
9c2e698514 | ||
|
463700b76d | ||
|
86387fbe49 | ||
|
a018cfa56c | ||
|
b76054beba | ||
|
f8536162f2 | ||
|
af65ddd012 | ||
|
fe9dc131bc | ||
|
bb4a668ddd | ||
|
3978137534 | ||
|
3ef4d0d798 | ||
|
e2341f5217 | ||
|
abd3cd54bc | ||
|
dc8ac38494 | ||
|
e3eacf2bf7 | ||
|
5098519d46 | ||
|
82c36aea89 | ||
|
05787d94ec | ||
|
f8c48349a1 | ||
|
1ee84033ec | ||
|
f2eecf8a4e | ||
|
76e1040232 | ||
|
4686d6709e | ||
|
2cc7ec90a8 | ||
|
99adb16090 | ||
|
c622d50814 | ||
|
11991e62f5 | ||
|
82d90e599c | ||
|
45b7947cd0 | ||
|
2e21cf76de | ||
|
76151110e5 | ||
|
5875d8bff3 | ||
|
68ecb13d14 | ||
|
f9f4b60b83 | ||
|
4fc36b9e99 | ||
|
4f3d77963f | ||
|
d911928c59 | ||
|
22e156e0af | ||
|
6fe02895a8 | ||
|
395dbaa5c6 | ||
|
efe06f133d | ||
|
9c0475ebfa | ||
|
e740a42366 | ||
|
cc31a90931 | ||
|
90e457a671 | ||
|
7487cd7e6d | ||
|
bbc3c2832f | ||
|
879c6bf9ff | ||
|
f2097dc4b5 | ||
|
166feb5bdf | ||
|
a841fe9320 | ||
|
9709672b34 | ||
|
e2db0bc866 | ||
|
fee45e4ba6 | ||
|
257d928ab3 | ||
|
34c8849f22 | ||
|
1c82eb3dff | ||
|
d96ab6ba84 | ||
|
7886ca65f9 | ||
|
85c4ddf616 | ||
|
0a36171999 | ||
|
eb2eea8d34 | ||
|
14684c8c83 | ||
|
c887a6f7d8 | ||
|
4c6dd8e68d | ||
|
27451469fa | ||
|
a311c259cc | ||
|
0ad65ceef6 | ||
|
cbf7c09bf4 | ||
|
3fcc298539 | ||
|
7080f85b59 | ||
|
9f0fa4c215 | ||
|
6f31a13f22 | ||
|
200f7fc628 | ||
|
a180162405 | ||
|
2d1c4fc809 | ||
|
e8d8de8f19 | ||
|
90aecfea02 | ||
|
a85736fd27 | ||
|
dafbcf8709 | ||
|
d11622e3c0 | ||
|
d90a9d15ac | ||
|
f8aaea3476 | ||
|
b84a51c76d | ||
|
83b0dd3026 | ||
|
ef5506321a | ||
|
24d349ffba | ||
|
2109e13746 | ||
|
5eec0c0213 | ||
|
f34787e6ba | ||
|
15ea836bb9 | ||
|
72e033db79 | ||
|
e1bd1038c0 | ||
|
3ebdb50457 | ||
|
b0fe6e6c59 | ||
|
8d847eeb18 | ||
|
f022ce8f84 | ||
|
78a5e4ff3e | ||
|
61ff683f11 | ||
|
7300b9f759 | ||
|
6ce2869108 | ||
|
0eba285ff1 | ||
|
88161a6467 | ||
|
7f901a812d | ||
|
6578fd06fd | ||
|
6ddde3f4b6 | ||
|
530631322d | ||
|
928de36093 | ||
|
1e851b3637 | ||
|
c9c85eeb95 | ||
|
ea35aa45d6 | ||
|
e51fcc6ea0 | ||
|
f4eec75488 | ||
|
f73f31619d | ||
|
ede08f3845 | ||
|
c9a55fce95 | ||
|
1cd3c586c1 | ||
|
0f12d077c8 | ||
|
92daca1fef | ||
|
889df72ab3 | ||
|
631e873ff2 | ||
|
69f19d4a0b | ||
|
3ec645674a | ||
|
71f83cf9ba | ||
|
06fe3e5fb0 | ||
|
fe153f8919 | ||
|
568de90923 | ||
|
f89c4c32ed | ||
|
fb9b3c0f34 | ||
|
33e3fb2f46 | ||
|
4bec9b90c4 | ||
|
a6c7aef6c1 | ||
|
4527536858 | ||
|
3da9444688 | ||
|
75d1ac8889 | ||
|
051d7137ea | ||
|
b462a2bf0c | ||
|
c02f80cec5 | ||
|
3bb1e34233 | ||
|
dc30b9c2ec | ||
|
b083029841 | ||
|
6f642776b6 | ||
|
1a7ebfc8f0 | ||
|
0771970627 | ||
|
0f18e80154 | ||
|
08b51fc869 | ||
|
13e3192444 | ||
|
3aefc16c57 | ||
|
7b0b3a1522 | ||
|
8991d994c2 | ||
|
72cbc83569 | ||
|
7af6ac18aa | ||
|
41236d8e58 | ||
|
03dfd983d0 | ||
|
84d895231c | ||
|
91b2687032 | ||
|
be3eefd8de | ||
|
e0e61ffd02 | ||
|
c3c91be578 | ||
|
e0431e3ffb | ||
|
67b6cf5a6f | ||
|
e8dc6b3f73 | ||
|
dfe9daf25d | ||
|
ca933c60a1 | ||
|
c080fc3aa0 | ||
|
5dd41f506e | ||
|
a3ac692c25 | ||
|
e91412ec8b | ||
|
337730ddef | ||
|
3ab9fe55e5 | ||
|
ab8d0d1dae | ||
|
25dbea1388 | ||
|
a47e1cdb48 | ||
|
4805afc1a2 | ||
|
5a0914e333 | ||
|
7031c52d16 | ||
|
8e941a06f1 | ||
|
966387859a | ||
|
fb40275507 | ||
|
3d9e9715b4 | ||
|
21b0367629 | ||
|
ac646f47a2 | ||
|
c4eeb4a14a | ||
|
6418033cc8 | ||
|
cbcb46bcfb | ||
|
0704049026 | ||
|
81509dfb65 | ||
|
b1fd61af3a | ||
|
2d185dc1c7 | ||
|
2d857b6200 | ||
|
871438f557 | ||
|
87644441ad | ||
|
d73201c098 | ||
|
3ea2be41e7 | ||
|
6bbedbea8a | ||
|
56a0692b68 | ||
|
dfa4eebbce | ||
|
6ed662c68b | ||
|
f81d8a85e9 | ||
|
9e5433bcf8 | ||
|
3fb5d7764c | ||
|
e75bcbed6d | ||
|
7f38216d22 | ||
|
e309148147 | ||
|
6981217369 | ||
|
4ff0687b94 | ||
|
dc02a0fc56 | ||
|
c48d2c1dde | ||
|
b5b34c9ff4 | ||
|
7b45566efc | ||
|
c30a07702d | ||
|
d8e24314ec | ||
|
449ca96e28 | ||
|
cebaedf485 | ||
|
fd311fd5ff | ||
|
50acb670b0 | ||
|
3888b465d8 | ||
|
6bbb7b71cd | ||
|
10eb921d70 | ||
|
5e3161a3f9 | ||
|
4cc3fedf37 | ||
|
2952480f37 | ||
|
6bfabad92f | ||
|
0e03e4f8a7 | ||
|
10149de205 | ||
|
960edd95cc | ||
|
8b2d8f3afd | ||
|
81def39d8c | ||
|
a23f45cc70 | ||
|
8a6207d3fc | ||
|
c3224459db | ||
|
183c7c75b1 | ||
|
1c43286616 | ||
|
4555a46e5d | ||
|
23f7275bd5 | ||
|
d2b2afad23 | ||
|
d19f7b12d9 | ||
|
6df1f68ead | ||
|
e06b7f8f4d | ||
|
3058158b69 | ||
|
0ea35c54a3 | ||
|
dbef6da544 | ||
|
a0c32110f1 | ||
|
435385816a | ||
|
1b53fdf9ee | ||
|
f00c5c9fa3 | ||
|
130c0d8487 | ||
|
d9bc7935eb | ||
|
944499814b | ||
|
0e63b83aed | ||
|
f681e954c7 | ||
|
a7f7c5fb73 | ||
|
9d0fa213be | ||
|
fc82abe7f6 | ||
|
72feee6fdd | ||
|
58eab8d453 | ||
|
7ab4758279 | ||
|
e6cd9b90d2 | ||
|
5b7c9d5725 | ||
|
39dd415d43 | ||
|
9a9c375b9d | ||
|
fda797cb76 | ||
|
a91bac8aa0 | ||
|
0904f4a89b | ||
|
9c370bf391 | ||
|
f09d43d073 | ||
|
f818d0dbe0 | ||
|
98623b2cf7 | ||
|
29379232aa | ||
|
0c580c3174 | ||
|
1179fc3de3 | ||
|
e069fbc37f | ||
|
efdc4af448 | ||
|
0535993e41 | ||
|
f5aa0bb126 | ||
|
0da383e7b6 | ||
|
c5b7c82344 | ||
|
b1d2b9d02b | ||
|
d9667b5221 | ||
|
155fe96bdd | ||
|
9a8572bd21 | ||
|
04eb40376e | ||
|
ea06eb9fe6 | ||
|
3e18b1d30c | ||
|
9df3ee0de8 | ||
|
0c7045042d | ||
|
5afeecbf18 | ||
|
4570c0ef9e | ||
|
d0af02cc17 | ||
|
62c5f41f39 | ||
|
bee56f2567 | ||
|
cbabe56fcf | ||
|
2d9e3b3330 | ||
|
989b1d176d | ||
|
142780b863 | ||
|
7b2886e477 | ||
|
66c1acbce2 | ||
|
49507ad670 | ||
|
d74f49111b | ||
|
642a9c29eb | ||
|
9d494e84bf | ||
|
aed103d06e | ||
|
b67495464d | ||
|
bf7e1516d5 | ||
|
7131d1bd6b | ||
|
062904c2bd | ||
|
6b52a04ff1 | ||
|
69a829dfb0 | ||
|
3c5baac150 | ||
|
8e9680bf69 | ||
|
b75c42f850 | ||
|
3b38d0de7f | ||
|
8b7fdf0f22 | ||
|
83d730ffda | ||
|
3acd5409a6 | ||
|
a61df019b3 | ||
|
7df98e1c9a | ||
|
c3b7dfa918 | ||
|
0fd24358ce | ||
|
7e8e6269d0 | ||
|
25dd6c72f7 | ||
|
adde3c3f65 | ||
|
7119de9b8d | ||
|
3999b9a4f9 | ||
|
7f4cb43a33 | ||
|
9feefc8144 | ||
|
9c26e5d4d6 | ||
|
815ab180c1 | ||
|
eba5cbb803 | ||
|
87a202c71e | ||
|
a4f63a5c30 | ||
|
ccdf178ee7 | ||
|
b52fa98a02 | ||
|
d87533bf4c | ||
|
5ef2a1df07 | ||
|
d2fceaa228 | ||
|
4e27e88f6a | ||
|
c5fa05299b | ||
|
0cbe4882c3 | ||
|
610d5ef5ff | ||
|
9d87a813b2 | ||
|
5740238933 | ||
|
ea918da931 | ||
|
9317c25ecb | ||
|
7a2bfae5e4 | ||
|
9894f654cf | ||
|
d71086e400 | ||
|
2000be12ba | ||
|
914728d9a1 | ||
|
c36100fa85 | ||
|
1c49e0973c | ||
|
65d143d4c4 | ||
|
d89f289f82 | ||
|
648dd9988d | ||
|
dd34c85884 | ||
|
1903ddada1 | ||
|
bf4e0df386 | ||
|
b9a1bbbb8a | ||
|
41d8f473b7 | ||
|
4155d4660f | ||
|
455a04b68e | ||
|
4c57318e89 | ||
|
d52995015e | ||
|
b1b6685663 | ||
|
534615373e | ||
|
1ba69be869 | ||
|
c54750469e | ||
|
3ebd15d408 | ||
|
64dfb7e122 | ||
|
b07afa3ea9 | ||
|
24a1537cdd | ||
|
55bff4f07a | ||
|
cb90b6769c | ||
|
6d073771ca |
994 changed files with 241573 additions and 163185 deletions
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1 +1 @@
|
|||
custom: "https://www.qbittorrent.org/donate.php"
|
||||
custom: "https://www.qbittorrent.org/donate"
|
||||
|
|
18
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
18
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -7,19 +7,17 @@ body:
|
|||
#### ADVISORY
|
||||
"We do not support any versions older than the current release series"
|
||||
|
||||
"We do not support any 3rd party/forked versions e.g. `portableapps`/`Enhanced Edition`etc."
|
||||
"We do not support any 3rd party/forked versions e.g. `portableapps`/`Enhanced Edition` etc."
|
||||
|
||||
"Please post all details in **English**."
|
||||
|
||||
#### Prerequisites before submitting an issue!
|
||||
- Read the issue reporting section in the **[contributing guidelines](https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md)**, to know how to submit a good bug report with the required information.
|
||||
- Verify that the issue is not fixed and is reproducible in the **[latest official qBittorrent version](https://www.qbittorrent.org/download.php).**
|
||||
- (Optional, but recommended) Verify that the issue is not fixed and is reproducible in the latest CI (currently only on **[Windows](https://github.com/qbittorrent/qBittorrent/actions/workflows/ci_windows.yaml?query=branch%3Amaster+event%3Apush)**) builds.
|
||||
- Check the **[frequent/common issues list](https://github.com/qbittorrent/qBittorrent/projects/2)** and perform a **[search of the issue tracker (including closed ones)](https://github.com/qbittorrent/qBittorrent/issues)** to avoid posting a duplicate.
|
||||
- (Optional, but recommended) Verify that the issue is not fixed and is reproducible in the latest CI (**[macOS](https://github.com/qbittorrent/qBittorrent/actions/workflows/ci_macos.yaml?query=branch%3Amaster+event%3Apush)** / **[Ubuntu](https://github.com/qbittorrent/qBittorrent/actions/workflows/ci_ubuntu.yaml?query=branch%3Amaster+event%3Apush)** / **[Windows](https://github.com/qbittorrent/qBittorrent/actions/workflows/ci_windows.yaml?query=branch%3Amaster+event%3Apush)**) builds.
|
||||
- Perform a **[search of the issue tracker (including closed ones)](https://github.com/qbittorrent/qBittorrent/issues?q=is%3Aissue+is%3Aopen+-label%3A%22Feature+request%22)** to avoid posting a duplicate.
|
||||
- Make sure this is not a support request or question, both of which are better suited for either the **[discussions section](https://github.com/qbittorrent/qBittorrent/discussions)**, **[forum](https://qbforums.shiki.hu/)**, or **[subreddit](https://www.reddit.com/r/qBittorrent/)**.
|
||||
- Verify that the **[wiki](https://github.com/qbittorrent/qBittorrent/wiki)** did not contain a suitable solution either.
|
||||
- If relevant to issue/when asked, the qBittorrent preferences file, qBittorrent.log & watched_folders.json (if using "Watched Folders" feature) must be provided.
|
||||
See **[Where does qBittorrent save its settings?](https://github.com/qbittorrent/qBittorrent/wiki/Frequently-Asked-Questions#Where_does_qBittorrent_save_its_settings)**
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
@ -28,10 +26,10 @@ body:
|
|||
Qt and libtorrent-rasterbar versions are required when: 1. You are using linux. 2. You are not using an official build downloaded from our website.
|
||||
|
||||
Example of preferred formatting:
|
||||
qBittorrent: 4.3.7 x64
|
||||
Operating system: Windows 10 Pro 21H1/2009 x64
|
||||
Qt: 5.15.2
|
||||
libtorrent-rasterbar: 1.2.14
|
||||
qBittorrent: 4.6.6 x64
|
||||
Operating system: Windows 10 Pro x64 (22H2) 10.0.19045
|
||||
Qt: 6.4.3
|
||||
libtorrent-rasterbar: 1.2.19
|
||||
placeholder: |
|
||||
qBittorrent:
|
||||
Operating system:
|
||||
|
@ -73,4 +71,4 @@ body:
|
|||
See **[Where does qBittorrent save its settings?](https://github.com/qbittorrent/qBittorrent/wiki/Frequently-Asked-Questions#Where_does_qBittorrent_save_its_settings)**
|
||||
#### Note: It's the user's responsibility to redact any sensitive information
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
|
|
31
.github/workflows/ci_file_health.yaml
vendored
31
.github/workflows/ci_file_health.yaml
vendored
|
@ -12,11 +12,15 @@ jobs:
|
|||
ci:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install tools
|
||||
- name: Setup python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "*"
|
||||
|
@ -32,7 +36,7 @@ jobs:
|
|||
curl \
|
||||
-L \
|
||||
-o "${{ runner.temp }}/pandoc.tar.gz" \
|
||||
"https://github.com/jgm/pandoc/releases/download/3.1.7/pandoc-3.1.7-linux-amd64.tar.gz"
|
||||
"https://github.com/jgm/pandoc/releases/download/3.6/pandoc-3.6-linux-amd64.tar.gz"
|
||||
tar -xf "${{ runner.temp }}/pandoc.tar.gz" -C "${{ github.workspace }}/.."
|
||||
mv "${{ github.workspace }}/.."/pandoc-* "${{ env.pandoc_path }}"
|
||||
# run pandoc
|
||||
|
@ -42,3 +46,26 @@ jobs:
|
|||
done
|
||||
# check diff, ignore "Automatically generated by ..." part
|
||||
git diff -I '\.\\".*' --exit-code
|
||||
|
||||
- name: Check GitHub Actions workflow
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
pip install zizmor
|
||||
IGNORE_RULEID='(.ruleId != "template-injection")
|
||||
and (.ruleId != "unpinned-uses")'
|
||||
IGNORE_ID='(.id != "template-injection")
|
||||
and (.id != "unpinned-uses")'
|
||||
zizmor \
|
||||
--format sarif \
|
||||
--pedantic \
|
||||
./ \
|
||||
| jq "(.runs[].results |= map(select($IGNORE_RULEID)))
|
||||
| (.runs[].tool.driver.rules |= map(select($IGNORE_ID)))" \
|
||||
> "${{ runner.temp }}/zizmor_results.sarif"
|
||||
|
||||
- name: Upload zizmor results
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
category: zizmor
|
||||
sarif_file: "${{ runner.temp }}/zizmor_results.sarif"
|
||||
|
|
35
.github/workflows/ci_macos.yaml
vendored
35
.github/workflows/ci_macos.yaml
vendored
|
@ -2,8 +2,7 @@ name: CI - macOS
|
|||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
|
@ -13,22 +12,25 @@ jobs:
|
|||
ci:
|
||||
name: Build
|
||||
runs-on: macos-latest
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
libt_version: ["2.0.10", "1.2.19"]
|
||||
libt_version: ["2.0.11", "1.2.20"]
|
||||
qbt_gui: ["GUI=ON", "GUI=OFF"]
|
||||
qt_version: ["6.7.0"]
|
||||
qt_version: ["6.9.0"]
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
openssl_root: "$(brew --prefix openssl@3)"
|
||||
libtorrent_path: "${{ github.workspace }}/../libtorrent"
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install dependencies
|
||||
uses: Wandalen/wretry.action@v3
|
||||
|
@ -50,15 +52,15 @@ jobs:
|
|||
store_cache: ${{ github.ref == 'refs/heads/master' }}
|
||||
update_packager_index: false
|
||||
ccache_options: |
|
||||
max_size=2G
|
||||
max_size=1G
|
||||
|
||||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "85"
|
||||
BOOST_MINOR_VERSION: "86"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
boost_url="https://boostorg.jfrog.io/artifactory/main/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
set +e
|
||||
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
|
||||
|
@ -68,9 +70,12 @@ jobs:
|
|||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
|
||||
fi
|
||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
cd "${{ env.boost_path }}"
|
||||
./bootstrap.sh
|
||||
./b2 stage --stagedir=./ --with-headers
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
archives: qtbase qtdeclarative qtsvg qttools
|
||||
|
@ -91,25 +96,23 @@ jobs:
|
|||
-G "Ninja" \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_STANDARD=17 \
|
||||
-DCMAKE_CXX_STANDARD=20 \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-Ddeprecated-functions=OFF \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}"
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-Ddeprecated-functions=OFF
|
||||
cmake --build build
|
||||
sudo cmake --install build
|
||||
|
||||
- name: Build qBittorrent
|
||||
run: |
|
||||
CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \
|
||||
CXXFLAGS="$CXXFLAGS -DQT_FORCE_ASSERTS -Werror -Wno-error=deprecated-declarations" \
|
||||
LDFLAGS="$LDFLAGS -gz" \
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-DTESTING=ON \
|
||||
-DVERBOSE_CONFIGURE=ON \
|
||||
-D${{ matrix.qbt_gui }}
|
||||
|
|
18
.github/workflows/ci_python.yaml
vendored
18
.github/workflows/ci_python.yaml
vendored
|
@ -16,6 +16,8 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup python (auxiliary scripts)
|
||||
uses: actions/setup-python@v5
|
||||
|
@ -34,7 +36,7 @@ jobs:
|
|||
- name: Lint code (auxiliary scripts)
|
||||
run: |
|
||||
pyflakes $PY_FILES
|
||||
bandit --skip B314,B405 $PY_FILES
|
||||
bandit --skip B101,B314,B405 $PY_FILES
|
||||
|
||||
- name: Format code (auxiliary scripts)
|
||||
run: |
|
||||
|
@ -50,10 +52,10 @@ jobs:
|
|||
- name: Setup python (search engine)
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.7'
|
||||
python-version: '3.9'
|
||||
|
||||
- name: Install tools (search engine)
|
||||
run: pip install bandit pycodestyle pyflakes
|
||||
run: pip install bandit mypy pycodestyle pyflakes pyright
|
||||
|
||||
- name: Gather files (search engine)
|
||||
run: |
|
||||
|
@ -61,6 +63,16 @@ jobs:
|
|||
echo $PY_FILES
|
||||
echo "PY_FILES=$PY_FILES" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Check typings (search engine)
|
||||
run: |
|
||||
MYPYPATH="src/searchengine/nova3" \
|
||||
mypy \
|
||||
--follow-imports skip \
|
||||
--strict \
|
||||
$PY_FILES
|
||||
pyright \
|
||||
$PY_FILES
|
||||
|
||||
- name: Lint code (search engine)
|
||||
run: |
|
||||
pyflakes $PY_FILES
|
||||
|
|
32
.github/workflows/ci_ubuntu.yaml
vendored
32
.github/workflows/ci_ubuntu.yaml
vendored
|
@ -2,9 +2,7 @@ name: CI - Ubuntu
|
|||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
security-events: write
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
|
@ -14,22 +12,27 @@ jobs:
|
|||
ci:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
libt_version: ["2.0.10", "1.2.19"]
|
||||
libt_version: ["2.0.11", "1.2.20"]
|
||||
qbt_gui: ["GUI=ON", "GUI=OFF"]
|
||||
qt_version: ["6.5.2"]
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
harden_flags: "-D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS"
|
||||
harden_flags: "-D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS"
|
||||
libtorrent_path: "${{ github.workspace }}/../libtorrent"
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
@ -44,15 +47,15 @@ jobs:
|
|||
store_cache: ${{ github.ref == 'refs/heads/master' }}
|
||||
update_packager_index: false
|
||||
ccache_options: |
|
||||
max_size=2G
|
||||
max_size=1G
|
||||
|
||||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "76"
|
||||
BOOST_MINOR_VERSION: "77"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
boost_url="https://boostorg.jfrog.io/artifactory/main/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
set +e
|
||||
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
|
||||
|
@ -62,9 +65,12 @@ jobs:
|
|||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
|
||||
fi
|
||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
cd "${{ env.boost_path }}"
|
||||
./bootstrap.sh
|
||||
./b2 stage --stagedir=./ --with-headers
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
archives: icu qtbase qtdeclarative qtsvg qttools
|
||||
|
@ -85,8 +91,9 @@ jobs:
|
|||
-G "Ninja" \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_CXX_STANDARD=20 \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-Ddeprecated-functions=OFF
|
||||
cmake --build build
|
||||
sudo cmake --install build
|
||||
|
@ -101,14 +108,14 @@ jobs:
|
|||
|
||||
- name: Build qBittorrent
|
||||
run: |
|
||||
CXXFLAGS="$CXXFLAGS ${{ env.harden_flags }} -Werror" \
|
||||
CXXFLAGS="$CXXFLAGS ${{ env.harden_flags }} -DQT_FORCE_ASSERTS -Werror" \
|
||||
LDFLAGS="$LDFLAGS -gz" \
|
||||
cmake \
|
||||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-DCMAKE_INSTALL_PREFIX="/usr" \
|
||||
-DTESTING=ON \
|
||||
-DVERBOSE_CONFIGURE=ON \
|
||||
|
@ -134,7 +141,6 @@ jobs:
|
|||
|
||||
- name: Install AppImage
|
||||
run: |
|
||||
sudo apt install libfuse2
|
||||
curl \
|
||||
-L \
|
||||
-Z \
|
||||
|
|
17
.github/workflows/ci_webui.yaml
vendored
17
.github/workflows/ci_webui.yaml
vendored
|
@ -2,8 +2,7 @@ name: CI - WebUI
|
|||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
security-events: write
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
|
@ -13,6 +12,8 @@ jobs:
|
|||
ci:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
|
@ -21,6 +22,8 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup nodejs
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -28,7 +31,15 @@ jobs:
|
|||
node-version: 'lts/*'
|
||||
|
||||
- name: Install tools
|
||||
run: npm install
|
||||
run: |
|
||||
npm install
|
||||
npm ls
|
||||
echo "::group::npm ls --all"
|
||||
npm ls --all
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Lint code
|
||||
run: npm run lint
|
||||
|
|
61
.github/workflows/ci_windows.yaml
vendored
61
.github/workflows/ci_windows.yaml
vendored
|
@ -2,8 +2,7 @@ name: CI - Windows
|
|||
|
||||
on: [pull_request, push]
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
|
@ -13,11 +12,13 @@ jobs:
|
|||
ci:
|
||||
name: Build
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
libt_version: ["2.0.10", "1.2.19"]
|
||||
libt_version: ["2.0.11", "1.2.20"]
|
||||
|
||||
env:
|
||||
boost_path: "${{ github.workspace }}/../boost"
|
||||
|
@ -27,6 +28,8 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup devcmd
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
@ -64,6 +67,7 @@ jobs:
|
|||
"set(VCPKG_BUILD_TYPE release)")
|
||||
# clear buildtrees after each package installation to reduce disk space requirements
|
||||
$packages = `
|
||||
"boost-build:x64-windows-static-md-release",
|
||||
"openssl:x64-windows-static-md-release",
|
||||
"zlib:x64-windows-static-md-release"
|
||||
${{ env.vcpkg_path }}/vcpkg.exe upgrade `
|
||||
|
@ -78,10 +82,10 @@ jobs:
|
|||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "85"
|
||||
BOOST_MINOR_VERSION: "86"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
$boost_url="https://boostorg.jfrog.io/artifactory/main/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
$boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
$boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
|
||||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."
|
||||
|
@ -91,11 +95,19 @@ jobs:
|
|||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."
|
||||
}
|
||||
move "${{ github.workspace }}/../boost_*" "${{ env.boost_path }}"
|
||||
cd "${{ env.boost_path }}"
|
||||
#.\bootstrap.bat
|
||||
${{ env.vcpkg_path }}/installed/x64-windows-static-md-release/tools/boost-build/b2.exe `
|
||||
stage `
|
||||
toolset=msvc `
|
||||
--stagedir=.\ `
|
||||
--with-headers
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: "6.7.0"
|
||||
version: "6.9.0"
|
||||
arch: win64_msvc2022_64
|
||||
archives: qtbase qtsvg qttools
|
||||
cache: true
|
||||
|
||||
|
@ -114,10 +126,11 @@ jobs:
|
|||
-B build `
|
||||
-G "Ninja" `
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
|
||||
-DCMAKE_CXX_STANDARD=20 `
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
|
||||
-DCMAKE_INSTALL_PREFIX="${{ env.libtorrent_path }}/install" `
|
||||
-DCMAKE_TOOLCHAIN_FILE="${{ env.vcpkg_path }}/scripts/buildsystems/vcpkg.cmake" `
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" `
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" `
|
||||
-DBUILD_SHARED_LIBS=OFF `
|
||||
-Ddeprecated-functions=OFF `
|
||||
-Dstatic_runtime=OFF `
|
||||
|
@ -127,14 +140,14 @@ jobs:
|
|||
|
||||
- name: Build qBittorrent
|
||||
run: |
|
||||
$env:CXXFLAGS+=" /WX"
|
||||
$env:CXXFLAGS+="/DQT_FORCE_ASSERTS /WX"
|
||||
cmake `
|
||||
-B build `
|
||||
-G "Ninja" `
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
|
||||
-DCMAKE_TOOLCHAIN_FILE="${{ env.vcpkg_path }}/scripts/buildsystems/vcpkg.cmake" `
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" `
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" `
|
||||
-DLibtorrentRasterbar_DIR="${{ env.libtorrent_path }}/install/lib/cmake/LibtorrentRasterbar" `
|
||||
-DMSVC_RUNTIME_DYNAMIC=ON `
|
||||
-DTESTING=ON `
|
||||
|
@ -153,26 +166,26 @@ jobs:
|
|||
copy build/qbittorrent.pdb upload/qBittorrent
|
||||
copy dist/windows/qt.conf upload/qBittorrent
|
||||
# runtimes
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Core.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Gui.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Network.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Sql.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Svg.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Widgets.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt6_DIR }}/bin/Qt6Xml.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Core.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Gui.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Network.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Sql.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Svg.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Widgets.dll" upload/qBittorrent
|
||||
copy "${{ env.Qt_ROOT_DIR }}/bin/Qt6Xml.dll" upload/qBittorrent
|
||||
mkdir upload/qBittorrent/plugins/iconengines
|
||||
copy "${{ env.Qt6_DIR }}/plugins/iconengines/qsvgicon.dll" upload/qBittorrent/plugins/iconengines
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/iconengines/qsvgicon.dll" upload/qBittorrent/plugins/iconengines
|
||||
mkdir upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt6_DIR }}/plugins/imageformats/qico.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt6_DIR }}/plugins/imageformats/qsvg.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qico.dll" upload/qBittorrent/plugins/imageformats
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/imageformats/qsvg.dll" upload/qBittorrent/plugins/imageformats
|
||||
mkdir upload/qBittorrent/plugins/platforms
|
||||
copy "${{ env.Qt6_DIR }}/plugins/platforms/qwindows.dll" upload/qBittorrent/plugins/platforms
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/platforms/qwindows.dll" upload/qBittorrent/plugins/platforms
|
||||
mkdir upload/qBittorrent/plugins/sqldrivers
|
||||
copy "${{ env.Qt6_DIR }}/plugins/sqldrivers/qsqlite.dll" upload/qBittorrent/plugins/sqldrivers
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/sqldrivers/qsqlite.dll" upload/qBittorrent/plugins/sqldrivers
|
||||
mkdir upload/qBittorrent/plugins/styles
|
||||
copy "${{ env.Qt6_DIR }}/plugins/styles/qmodernwindowsstyle.dll" upload/qBittorrent/plugins/styles
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/styles/qmodernwindowsstyle.dll" upload/qBittorrent/plugins/styles
|
||||
mkdir upload/qBittorrent/plugins/tls
|
||||
copy "${{ env.Qt6_DIR }}/plugins/tls/qschannelbackend.dll" upload/qBittorrent/plugins/tls
|
||||
copy "${{ env.Qt_ROOT_DIR }}/plugins/tls/qschannelbackend.dll" upload/qBittorrent/plugins/tls
|
||||
# cmake additionals
|
||||
mkdir upload/cmake
|
||||
copy build/compile_commands.json upload/cmake
|
||||
|
|
18
.github/workflows/coverity-scan.yaml
vendored
18
.github/workflows/coverity-scan.yaml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
libt_version: ["2.0.10"]
|
||||
libt_version: ["2.0.11"]
|
||||
qbt_gui: ["GUI=ON"]
|
||||
qt_version: ["6.5.2"]
|
||||
|
||||
|
@ -26,6 +26,8 @@ jobs:
|
|||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
@ -37,10 +39,10 @@ jobs:
|
|||
- name: Install boost
|
||||
env:
|
||||
BOOST_MAJOR_VERSION: "1"
|
||||
BOOST_MINOR_VERSION: "85"
|
||||
BOOST_MINOR_VERSION: "86"
|
||||
BOOST_PATCH_VERSION: "0"
|
||||
run: |
|
||||
boost_url="https://boostorg.jfrog.io/artifactory/main/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
|
||||
set +e
|
||||
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
|
||||
|
@ -50,9 +52,12 @@ jobs:
|
|||
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
|
||||
fi
|
||||
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
|
||||
cd "${{ env.boost_path }}"
|
||||
./bootstrap.sh
|
||||
./b2 stage --stagedir=./ --with-headers
|
||||
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
archives: icu qtbase qtdeclarative qtsvg qttools
|
||||
|
@ -71,7 +76,8 @@ jobs:
|
|||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DCMAKE_CXX_STANDARD=20 \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-Ddeprecated-functions=OFF
|
||||
cmake --build build
|
||||
sudo cmake --install build
|
||||
|
@ -95,7 +101,7 @@ jobs:
|
|||
-B build \
|
||||
-G "Ninja" \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}" \
|
||||
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
|
||||
-DVERBOSE_CONFIGURE=ON \
|
||||
-D${{ matrix.qbt_gui }}
|
||||
PATH="${{ env.coverity_path }}/bin:$PATH" \
|
||||
|
|
|
@ -16,3 +16,5 @@ ths = "ths"
|
|||
[default.extend-words]
|
||||
BA = "BA"
|
||||
helo = "helo"
|
||||
Pn = "Pn"
|
||||
UIU = "UIU"
|
||||
|
|
95
.github/workflows/helper/pre-commit/check_grid_items_order.py
vendored
Executable file
95
.github/workflows/helper/pre-commit/check_grid_items_order.py
vendored
Executable file
|
@ -0,0 +1,95 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# A pre-commit hook for checking items order in grid layouts
|
||||
# Copyright (C) 2024 Mike Tzou (Chocobo1)
|
||||
#
|
||||
# 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.
|
||||
|
||||
from collections.abc import Callable, Sequence
|
||||
from typing import Optional
|
||||
import argparse
|
||||
import re
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
import sys
|
||||
|
||||
|
||||
def traversePostOrder(root: ElementTree.Element, visitFunc: Callable[[ElementTree.Element], None]) -> None:
|
||||
stack = [(root, False)]
|
||||
|
||||
while len(stack) > 0:
|
||||
(element, visit) = stack.pop()
|
||||
if visit:
|
||||
visitFunc(element)
|
||||
else:
|
||||
stack.append((element, True))
|
||||
stack.extend((child, False) for child in reversed(element))
|
||||
|
||||
|
||||
def modifyElement(element: ElementTree.Element) -> None:
|
||||
def getSortKey(e: ElementTree.Element) -> tuple[int, int]:
|
||||
if e.tag == 'item':
|
||||
return (int(e.attrib['row']), int(e.attrib['column']))
|
||||
return (-1, -1) # don't care
|
||||
|
||||
if element.tag == 'layout' and element.attrib['class'] == 'QGridLayout' and len(element) > 0:
|
||||
element[:] = sorted(element, key=getSortKey)
|
||||
|
||||
# workaround_2a: ElementTree will unescape `"` and we need to escape it back
|
||||
if element.tag == 'string' and element.text is not None:
|
||||
element.text = element.text.replace('"', '"')
|
||||
|
||||
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
for filename in args.filenames:
|
||||
with open(filename, 'r+') as f:
|
||||
orig = f.read()
|
||||
root = ElementTree.fromstring(orig)
|
||||
traversePostOrder(root, modifyElement)
|
||||
ElementTree.indent(root, ' ')
|
||||
|
||||
# workaround_1: cannot use `xml_declaration=True` since it uses single quotes instead of Qt preferred double quotes
|
||||
ret = f'<?xml version="1.0" encoding="UTF-8"?>\n{ElementTree.tostring(root, 'unicode')}\n'
|
||||
|
||||
# workaround_2b: ElementTree will turn `"` into `&quot;`, so revert it back
|
||||
ret = ret.replace('&quot;', '"')
|
||||
|
||||
# workaround_3: Qt prefers no whitespaces in self-closing tags
|
||||
ret = re.sub('<(.+) +/>', r'<\1/>', ret)
|
||||
|
||||
if ret != orig:
|
||||
print(f'Tip: run this script to apply the fix: `python {__file__} {filename}`', file=sys.stderr)
|
||||
|
||||
f.seek(0)
|
||||
f.write(ret)
|
||||
f.truncate()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
|
@ -26,9 +26,11 @@
|
|||
# but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
# exception statement from your version.
|
||||
|
||||
from typing import Optional, Sequence
|
||||
from collections.abc import Sequence
|
||||
from typing import Optional
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def main(argv: Optional[Sequence[str]] = None) -> int:
|
||||
|
@ -67,4 +69,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
sys.exit(main())
|
||||
|
|
5
.github/workflows/stale_bot.yaml
vendored
5
.github/workflows/stale_bot.yaml
vendored
|
@ -4,12 +4,13 @@ on:
|
|||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Mark and close stale PRs
|
||||
uses: actions/stale@v9
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -41,7 +41,3 @@ src/icons/skin/build-icons/icons/*.png
|
|||
|
||||
# CMake build directory
|
||||
build/
|
||||
|
||||
# Web UI tools
|
||||
node_modules
|
||||
package-lock.json
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: check-grid-order
|
||||
name: Check items order in grid layouts
|
||||
entry: .github/workflows/helper/pre-commit/check_grid_items_order.py
|
||||
language: script
|
||||
files: \.ui$
|
||||
|
||||
- id: check-translation-tag
|
||||
name: Check newline characters in <translation> tag
|
||||
entry: .github/workflows/helper/pre-commit/check_translation_tag.py
|
||||
|
@ -13,7 +19,7 @@ repos:
|
|||
- ts
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks.git
|
||||
rev: v4.5.0
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: check-json
|
||||
name: Check JSON files
|
||||
|
@ -63,32 +69,26 @@ repos:
|
|||
- ts
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell.git
|
||||
rev: v2.2.6
|
||||
rev: v2.4.0
|
||||
hooks:
|
||||
- id: codespell
|
||||
name: Check spelling (codespell)
|
||||
args: ["--ignore-words-list", "additionals,curren,fo,ist,ket,searchin,superseeding,te,ths"]
|
||||
args: ["--ignore-words-list", "additionals,categor,curren,fo,indexIn,ist,ket,notin,searchin,sectionin,superseeding,te,ths"]
|
||||
exclude: |
|
||||
(?x)^(
|
||||
.*\.desktop |
|
||||
.*\.qrc |
|
||||
build-aux/.* |
|
||||
Changelog |
|
||||
dist/windows/installer-translations/.* |
|
||||
m4/.* |
|
||||
src/base/3rdparty/.* |
|
||||
src/searchengine/nova3/socks.py |
|
||||
src/webui/www/private/lang/.* |
|
||||
src/webui/www/private/scripts/lib/.* |
|
||||
src/webui/www/public/lang/.* |
|
||||
src/webui/www/public/scripts/lib/.* |
|
||||
src/webui/www/transifex/.*
|
||||
src/webui/www/private/scripts/lib/.*
|
||||
)$
|
||||
exclude_types:
|
||||
- ts
|
||||
|
||||
- repo: https://github.com/crate-ci/typos.git
|
||||
rev: v1.16.18
|
||||
rev: v1.29.4
|
||||
hooks:
|
||||
- id: typos
|
||||
name: Check spelling (typos)
|
||||
|
@ -99,18 +99,11 @@ repos:
|
|||
.*\.desktop |
|
||||
.*\.qrc |
|
||||
\.pre-commit-config\.yaml |
|
||||
build-aux/.* |
|
||||
Changelog |
|
||||
configure.* |
|
||||
dist/windows/installer-translations/.* |
|
||||
m4/.* |
|
||||
src/base/3rdparty/.* |
|
||||
src/searchengine/nova3/socks.py |
|
||||
src/webui/www/private/lang/.* |
|
||||
src/webui/www/private/scripts/lib/.* |
|
||||
src/webui/www/public/lang/.* |
|
||||
src/webui/www/public/scripts/lib/.* |
|
||||
src/webui/www/transifex/.*
|
||||
src/webui/www/private/scripts/lib/.*
|
||||
)$
|
||||
exclude_types:
|
||||
- svg
|
||||
|
|
|
@ -17,14 +17,6 @@ type = QT
|
|||
minimum_perc = 23
|
||||
lang_map = pt: pt_PT, zh: zh_CN
|
||||
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui_json]
|
||||
file_filter = src/webui/www/transifex/<lang>.json
|
||||
source_file = src/webui/www/transifex/en.json
|
||||
source_lang = en
|
||||
type = KEYVALUEJSON
|
||||
minimum_perc = 23
|
||||
lang_map = pt: pt_PT, zh: zh_CN
|
||||
|
||||
[o:sledgehammer999:p:qbittorrent:r:qbittorrentdesktop_master]
|
||||
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
|
||||
source_lang = en
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
<https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
@ -304,8 +304,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||
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.
|
||||
with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
|
@ -329,8 +328,8 @@ necessary. Here is a sample; alter the names:
|
|||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
<signature of Moe Ghoul>, 1 April 1989
|
||||
Moe Ghoul, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
|
|
101
Changelog
101
Changelog
|
@ -1,4 +1,26 @@
|
|||
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
||||
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.0
|
||||
|
||||
Mon Oct 28th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.1
|
||||
- FEATURE: Add "Simple pread/pwrite" disk IO type (Hanabishi)
|
||||
- BUGFIX: Don't ignore SSL errors (sledgehammer999)
|
||||
- BUGFIX: Don't try to apply Mark-of-the-Web to nonexistent files (glassez)
|
||||
- BUGFIX: Disable "Move to trash" option by default (glassez)
|
||||
- BUGFIX: Disable the ability to create torrents with a piece size of 256MiB (stalkerok)
|
||||
- BUGFIX: Allow to choose Qt style (glassez)
|
||||
- BUGFIX: Always notify user about duplicate torrent (glassez)
|
||||
- BUGFIX: Correctly handle "torrent finished after move" event (glassez)
|
||||
- BUGFIX: Correctly apply filename filter when `!qB` extension is enabled (glassez)
|
||||
- BUGFIX: Improve color scheme change detection (glassez)
|
||||
- BUGFIX: Fix button state for SSL certificate check (Chocobo1)
|
||||
- WEBUI: Fix CSS that results in hidden torrent list in some browsers (skomerko)
|
||||
- WEBUI: Use proper text color to highlight items in all filter lists (skomerko)
|
||||
- WEBUI: Fix 'rename files' dialog cannot be opened more than once (Chocobo1)
|
||||
- WEBUI: Fix UI of Advanced Settings to show all settings (glassez)
|
||||
- WEBUI: Free resources allocated by web session once it is destructed (dyseg)
|
||||
- SEARCH: Import correct libraries (Chocobo1)
|
||||
- OTHER: Sync flag icons with upstream (xavier2k6)
|
||||
|
||||
Sun Sep 29th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
||||
- FEATURE: Support creating .torrent with larger piece size (Chocobo1)
|
||||
- FEATURE: Improve tracker entries handling (glassez)
|
||||
- FEATURE: Add separate filter item for tracker errors (glassez)
|
||||
|
@ -12,14 +34,30 @@ Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
|||
- FEATURE: Enable Ctrl+F hotkey for more inputs (thalieht)
|
||||
- FEATURE: Add seeding limits to RSS and Watched folders options UI (glassez)
|
||||
- FEATURE: Subcategories implicitly follow the parent category options (glassez)
|
||||
- FEATURE: Add support for SSL torrents (Chocobo1, Radu Carpa)
|
||||
- FEATURE: Add option to name each qbittorrent instance (Chocobo1)
|
||||
- FEATURE: Add button for sending test email (Thomas Piccirello)
|
||||
- FEATURE: Allow torrents to override default share limit action (glassez)
|
||||
- FEATURE: Use Start/Stop instead of Resume/Pause (thalieht)
|
||||
- FEATURE: Add the Popularity metric (Aliaksei Urbanski)
|
||||
- FEATURE: Focus on Download button if torrent link retrieved from the clipboard (glassez)
|
||||
- FEATURE: Add ability to pause/resume entire BitTorrent session (glassez)
|
||||
- FEATURE: Add an option to set BitTorrent session shutdown timeout (glassez)
|
||||
- FEATURE: Apply "Excluded file names" to folder names as well (glassez)
|
||||
- FEATURE: Allow to use regular expression to filter torrent content (glassez)
|
||||
- FEATURE: Allow to move content files to Trash instead of deleting them (glassez)
|
||||
- FEATURE: Add ability to display torrent "privateness" in UI (ManiMatter)
|
||||
- FEATURE: Add a flag in `Peers` tab denoting a connection using NAT hole punching (stalkerok)
|
||||
- BUGFIX: Display error message when unrecoverable error occurred (glassez)
|
||||
- BUGFIX: Update size of selected files when selection is changed (glassez)
|
||||
- BUGFIX: Normalize tags by trimming leading/trailing whitespace (glassez)
|
||||
- BUGFIX: Correctly handle share limits in torrent options dialog (glassez)
|
||||
- BUGFIX: Adjust tracker tier when adding additional trackers (Chocobo1)
|
||||
- BUGFIX: Fix inconsistent naming between `Done/Progress` column (luzpaz)
|
||||
- BUGFIX: Sanitize peer client names (Hanabishi)
|
||||
- BUGFIX: Apply share limits immediately when torrent downloading is finished (glassez)
|
||||
- BUGFIX: Show download progress for folders with zero byte size as 100 instead of 0 (vikas_c)
|
||||
- BUGFIX: Fix highlighted piece color (Prince Gupta)
|
||||
- BUGFIX: Apply "merge trackers" logic regardless of way the torrent is added (glassez)
|
||||
- WEBUI: Improve WebUI responsiveness (Chocobo1)
|
||||
- WEBUI: Do not exit the app when WebUI has failed to start (Hanabishi)
|
||||
- WEBUI: Add `Moving` filter to side panel (xavier2k6)
|
||||
|
@ -28,14 +66,37 @@ Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
|||
- WEBUI: Leave the fields empty when value is invalid (Chocobo1)
|
||||
- WEBUI: Use natural sorting (Chocobo1)
|
||||
- WEBUI: Improve WebUI login behavior (JayRet)
|
||||
- WEBUI: Conditionally show filters sidebar (Thomas Piccirello)
|
||||
- WEBUI: Add support for running concurrent searches (Thomas Piccirello)
|
||||
- WEBUI: Improve accuracy of trackers list (Thomas Piccirello)
|
||||
- WEBUI: Fix error when category doesn't exist (Thomas Piccirello)
|
||||
- WEBUI: Improve table scrolling and selection on mobile (Thomas Piccirello)
|
||||
- WEBUI: Restore search tabs on load (Thomas Piccirello)
|
||||
- WEBUI: Restore previously used tab on load (Thomas Piccirello)
|
||||
- WEBUI: Increase default height of `Share ratio limit` dialog (thalieht)
|
||||
- WEBUI: Use enabled search plugins by default (Thomas Piccirello)
|
||||
- WEBUI: Add columns `Incomplete Save Path`, `Info Hash v1`, `Info Hash v2` (thalieht)
|
||||
- WEBUI: Always create generic filter items (skomerko)
|
||||
- WEBUI: Provide `Use Category paths in Manual Mode` option (skomerko)
|
||||
- WEBUI: Provide `Merge trackers to existing torrent` option (skomerko)
|
||||
- WEBAPI: Fix wrong timestamp values (Chocobo1)
|
||||
- WEBAPI: Send binary data with filename and mime type specified (glassez)
|
||||
- WEBAPI: Expose API for the torrent creator (glassez, Radu Carpa)
|
||||
- WEBAPI: Add support for SSL torrents (Chocobo1, Radu Carpa)
|
||||
- WEBAPI: Provide endpoint for listing directory content (Paweł Kotiuk)
|
||||
- WEBAPI: Provide "private" flag via "torrents/info" endpoint (ManiMatter)
|
||||
- WEBAPI: Add a way to download .torrent file using search plugin (glassez)
|
||||
- WEBAPI: Add "private" filter for "torrents/info" endpoint (ManiMatter)
|
||||
- WEBAPI: Add root_path to "torrents/info" result (David Newhall)
|
||||
- RSS: Show RSS feed title in HTML browser (Jay)
|
||||
- RSS: Allow to set delay between requests to the same host (jNullj)
|
||||
- SEARCH: Allow users to specify Python executable path (Chocobo1)
|
||||
- SEARCH: Lazy load search plugins (milahu)
|
||||
- SEARCH: Add date column to the built-in search engine (ducalex)
|
||||
- SEARCH: Allow to rearrange search tabs (glassez)
|
||||
- WINDOWS: Use Fusion style on Windows 10+. It has better compatibility with dark mode (glassez)
|
||||
- WINDOWS: Allow to set qBittorrent as default program (glassez)
|
||||
- WINDOWS: Don't access "Favorites" folder unexpectedly (glassez)
|
||||
- LINUX: Add support for systemd power management (Chocobo1)
|
||||
- LINUX: Add support for localized man pages (Victor Chernyakin)
|
||||
- LINUX: Specify a locale if none is set (Chocobo1)
|
||||
|
@ -45,6 +106,42 @@ Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
|
|||
- OTHER: Minimum supported versions: Qt: 6.5, Boost: 1.76, OpenSSL: 3.0.2
|
||||
- OTHER: Switch to C++20
|
||||
|
||||
Mon Sep 16th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.6.7
|
||||
- BUGFIX: The updater will launch the link to the build variant you're currently using (sledgehammer999)
|
||||
- BUGFIX: Focus on Download button if torrent link retrieved from the clipboard (glassez)
|
||||
- WEBUI: RSS: The list of feeds wouldn't load for Apply Rule (glassez)
|
||||
|
||||
Sun Aug 18th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.6.6
|
||||
- BUGFIX: Fix handling of tags containing '&' character (glassez)
|
||||
- BUGFIX: Show scroll bar in Torrent Tags dialog (glassez)
|
||||
- BUGFIX: Apply bulk changes to correct content widget items (glassez)
|
||||
- BUGFIX: Hide zero status filters when torrents are removed (glassez)
|
||||
- BUGFIX: Fix `Incomplete Save Path` cannot be changed for torrents without metadata (glassez)
|
||||
- WEBUI: Correctly apply changed "save path" of RSS rules (glassez)
|
||||
- WEBUI: Clear tracker list on full update (skomerko)
|
||||
- OTHER: Update User-Agent string for internal downloader and search engines (cayenne17)
|
||||
|
||||
Sun May 26th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.6.5
|
||||
- BUGFIX: Prevent app from being closed when disabling system tray icon (glassez)
|
||||
- BUGFIX: Fix <kbd>Enter</kbd> key behavior in Add new torrent dialog (glassez)
|
||||
- BUGFIX: Prevent invalid status filter index from being used (glassez)
|
||||
- BUGFIX: Add extra offset for dialog frame (glassez)
|
||||
- BUGFIX: Don't overwrite stored layout of main window with incorrect one (glassez)
|
||||
- BUGFIX: Don't forget to resume "missing files" torrent when rechecking (glassez)
|
||||
- WEBUI: Restore ability to use server-side translation by custom WebUI (glassez)
|
||||
- WEBUI: Fix wrong peer number (Chocobo1)
|
||||
- LINUX: Improve AppStream metadata (Chocobo1)
|
||||
|
||||
Sun Mar 24th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.6.4
|
||||
- BUGFIX: Correctly adjust "Add New torrent" dialog position in all the cases (glassez)
|
||||
- BUGFIX: Change "metadata received" stop condition behavior (glassez)
|
||||
- BUGFIX: Add a small delay before processing the key input of search boxes (Chocobo1)
|
||||
- BUGFIX: Ensure the profile path is pointing to a directory (Chocobo1)
|
||||
- RSS: Use better icons for RSS articles (glassez)
|
||||
- WINDOWS: NSIS: Update French, Hungarian translations (MarcDrieu, foxi69)
|
||||
- LINUX: Fix sorting when ICU isn't used (Chocobo1)
|
||||
- LINUX: Fix invisible tray icon on Plasma 6 (tehcneko)
|
||||
|
||||
Mon Jan 15th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.6.3
|
||||
- BUGFIX: Correctly update number of filtered items (glassez)
|
||||
- BUGFIX: Don't forget to store Stop condition value (glassez)
|
||||
|
|
2
INSTALL
2
INSTALL
|
@ -18,7 +18,7 @@ qBittorrent - A BitTorrent client in C++ / Qt
|
|||
- CMake >= 3.16
|
||||
* Compile-time only
|
||||
|
||||
- Python >= 3.7.0
|
||||
- Python >= 3.9.0
|
||||
* Optional, run-time only
|
||||
* Used by the bundled search engine
|
||||
|
||||
|
|
18
SECURITY.md
Normal file
18
SECURITY.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Security Policy
|
||||
|
||||
qBittorrent takes the security of our software seriously, including all source code repositories managed through our GitHub organisation.
|
||||
If you believe you have found a security vulnerability in qBittorrent, please report it to us as described below.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
Please do not report security vulnerabilities through public GitHub issues. Instead, please use GitHubs private vulnerability reporting functionality associated to this repository. Additionally, you may email us with all security-related inquiries and notifications at `security@qbittorrent.org`.
|
||||
|
||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||
1. Type of issue
|
||||
2. Step-by-step instructions to reproduce the issue
|
||||
3. Proof-of-concept or exploit code (if possible)
|
||||
4. Potential impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly. Any and all CVEs will be requested and issued through GitHubs private vulnerability reporting functionality, which will be published alongside the disclosure.
|
||||
|
||||
This security policy only applies to the most recent stable branch of qBittorrent. Flaws in old versions that are not present in the current stable branch will not be fixed.
|
5
WebAPI_Changelog.md
Normal file
5
WebAPI_Changelog.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# 2.11.6
|
||||
* https://github.com/qbittorrent/qBittorrent/pull/22460
|
||||
* `app/setPreferences` allows only one of `max_ratio_enabled`, `max_ratio` to be present
|
||||
* `app/setPreferences` allows only one of `max_seeding_time_enabled`, `max_seeding_time` to be present
|
||||
* `app/setPreferences` allows only one of `max_inactive_seeding_time_enabled`, `max_inactive_seeding_time` to be present
|
4
dist/mac/Info.plist
vendored
4
dist/mac/Info.plist
vendored
|
@ -55,7 +55,7 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>5.0.0</string>
|
||||
<string>5.2.0</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
@ -67,7 +67,7 @@
|
|||
<key>NSAppleScriptEnabled</key>
|
||||
<string>YES</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2006-2024 The qBittorrent project</string>
|
||||
<string>Copyright © 2006-2025 The qBittorrent project</string>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
|
146
dist/unix/org.qbittorrent.qBittorrent.desktop
vendored
146
dist/unix/org.qbittorrent.qBittorrent.desktop
vendored
|
@ -14,216 +14,220 @@ Keywords=bittorrent;torrent;magnet;download;p2p;
|
|||
SingleMainWindow=true
|
||||
|
||||
# Translations
|
||||
Comment[af]=Aflaai en deel lêers oor BitTorrent
|
||||
GenericName[af]=BitTorrent kliënt
|
||||
Comment[af]=Aflaai en deel lêers oor BitTorrent
|
||||
Name[af]=qBittorrent
|
||||
Comment[ar]=نزّل وشارك الملفات عبر كيوبتتورنت
|
||||
GenericName[ar]=عميل بتتورنت
|
||||
Comment[ar]=نزّل وشارك الملفات عبر كيوبتتورنت
|
||||
Name[ar]=qBittorrent
|
||||
Comment[be]=Спампоўванне і раздача файлаў праз пратакол BitTorrent
|
||||
GenericName[be]=Кліент BitTorrent
|
||||
Comment[be]=Спампоўванне і раздача файлаў праз пратакол BitTorrent
|
||||
Name[be]=qBittorrent
|
||||
Comment[bg]=Сваляне и споделяне на файлове чрез BitTorrent
|
||||
GenericName[bg]=BitTorrent клиент
|
||||
Comment[bg]=Сваляне и споделяне на файлове чрез BitTorrent
|
||||
Name[bg]=qBittorrent
|
||||
Comment[bn]=বিটটরেন্টে ফাইল ডাউনলোড এবং শেয়ার করুন
|
||||
GenericName[bn]=বিটটরেন্ট ক্লায়েন্ট
|
||||
Comment[bn]=বিটটরেন্টে ফাইল ডাউনলোড এবং শেয়ার করুন
|
||||
Name[bn]=qBittorrent
|
||||
Comment[zh]=通过 BitTorrent 下载和分享文件
|
||||
GenericName[zh]=BitTorrent 客户端
|
||||
Comment[zh]=通过 BitTorrent 下载和分享文件
|
||||
Name[zh]=qBittorrent
|
||||
Comment[bs]=Preuzmi i dijeli datoteke preko BitTorrent-a
|
||||
GenericName[bs]=BitTorrent klijent
|
||||
Comment[bs]=Preuzmi i dijeli datoteke preko BitTorrent-a
|
||||
Name[bs]=qBittorrent
|
||||
Comment[ca]=Baixeu i compartiu fitxers amb el BitTorrent
|
||||
GenericName[ca]=Client de BitTorrent
|
||||
Comment[ca]=Baixeu i compartiu fitxers amb el BitTorrent
|
||||
Name[ca]=qBittorrent
|
||||
Comment[cs]=Stahování a sdílení souborů přes síť BitTorrent
|
||||
GenericName[cs]=BitTorrent klient
|
||||
Comment[cs]=Stahování a sdílení souborů přes síť BitTorrent
|
||||
Name[cs]=qBittorrent
|
||||
Comment[da]=Download og del filer over BitTorrent
|
||||
GenericName[da]=BitTorrent-klient
|
||||
Comment[da]=Download og del filer over BitTorrent
|
||||
Name[da]=qBittorrent
|
||||
Comment[de]=Über BitTorrent Dateien herunterladen und teilen
|
||||
GenericName[de]=BitTorrent Client
|
||||
Comment[de]=Über BitTorrent Dateien herunterladen und teilen
|
||||
Name[de]=qBittorrent
|
||||
Comment[el]=Κάντε λήψη και μοιραστείτε αρχεία μέσω BitTorrent
|
||||
GenericName[el]=BitTorrent client
|
||||
Comment[el]=Κάντε λήψη και μοιραστείτε αρχεία μέσω BitTorrent
|
||||
Name[el]=qBittorrent
|
||||
Comment[en_GB]=Download and share files over BitTorrent
|
||||
GenericName[en_GB]=BitTorrent client
|
||||
Comment[en_GB]=Download and share files over BitTorrent
|
||||
Name[en_GB]=qBittorrent
|
||||
Comment[es]=Descargue y comparta archivos por BitTorrent
|
||||
GenericName[es]=Cliente BitTorrent
|
||||
Comment[es]=Descargue y comparta archivos por BitTorrent
|
||||
Name[es]=qBittorrent
|
||||
Comment[et]=Lae alla ja jaga faile üle BitTorrenti
|
||||
GenericName[et]=BitTorrent klient
|
||||
Comment[et]=Lae alla ja jaga faile üle BitTorrenti
|
||||
Name[et]=qBittorrent
|
||||
Comment[eu]=Jeitsi eta elkarbanatu agiriak BitTorrent bidez
|
||||
GenericName[eu]=BitTorrent bezeroa
|
||||
Comment[eu]=Jeitsi eta elkarbanatu agiriak BitTorrent bidez
|
||||
Name[eu]=qBittorrent
|
||||
Comment[fa]=دانلود و به اشتراک گذاری فایل های بوسیله بیت تورنت
|
||||
GenericName[fa]=بیت تورنت نسخه کلاینت
|
||||
Comment[fa]=دانلود و به اشتراک گذاری فایل های بوسیله بیت تورنت
|
||||
Name[fa]=qBittorrent
|
||||
Comment[fi]=Lataa ja jaa tiedostoja BitTorrentia käyttäen
|
||||
GenericName[fi]=BitTorrent-asiakasohjelma
|
||||
Comment[fi]=Lataa ja jaa tiedostoja BitTorrentia käyttäen
|
||||
Name[fi]=qBittorrent
|
||||
Comment[fr]=Télécharger et partager des fichiers sur BitTorrent
|
||||
GenericName[fr]=Client BitTorrent
|
||||
Comment[fr]=Télécharger et partager des fichiers sur BitTorrent
|
||||
Name[fr]=qBittorrent
|
||||
Comment[gl]=Descargar e compartir ficheiros co protocolo BitTorrent
|
||||
GenericName[gl]=Cliente BitTorrent
|
||||
Comment[gl]=Descargar e compartir ficheiros co protocolo BitTorrent
|
||||
Name[gl]=qBittorrent
|
||||
Comment[gu]=બિટ્ટોરેંટ પર ફાઈલો ડાઉનલોડ અને શેર કરો
|
||||
GenericName[gu]=બિટ્ટોરેંટ ક્લાયન્ટ
|
||||
Comment[gu]=બિટ્ટોરેંટ પર ફાઈલો ડાઉનલોડ અને શેર કરો
|
||||
Name[gu]=qBittorrent
|
||||
Comment[he]=הורד ושתף קבצים על גבי ביטורנט
|
||||
GenericName[he]=לקוח ביטורנט
|
||||
Comment[he]=הורד ושתף קבצים על גבי ביטורנט
|
||||
Name[he]=qBittorrent
|
||||
Comment[hr]=Preuzmite i dijelite datoteke putem BitTorrenta
|
||||
GenericName[hr]=BitTorrent klijent
|
||||
Comment[hr]=Preuzmite i dijelite datoteke putem BitTorrenta
|
||||
Name[hr]=qBittorrent
|
||||
Comment[hu]=Fájlok letöltése és megosztása a BitTorrent hálózaton keresztül
|
||||
GenericName[hu]=BitTorrent kliens
|
||||
Comment[hu]=Fájlok letöltése és megosztása a BitTorrent hálózaton keresztül
|
||||
Name[hu]=qBittorrent
|
||||
Comment[hy]=Նիշքերի փոխանցում BitTorrent-ի միջոցով
|
||||
GenericName[hy]=BitTorrent սպասառու
|
||||
Comment[hy]=Նիշքերի փոխանցում BitTorrent-ի միջոցով
|
||||
Name[hy]=qBittorrent
|
||||
Comment[id]=Unduh dan berbagi berkas melalui BitTorrent
|
||||
GenericName[id]=Klien BitTorrent
|
||||
Comment[id]=Unduh dan berbagi berkas melalui BitTorrent
|
||||
Name[id]=qBittorrent
|
||||
Comment[is]=Sækja og deila skrám yfir BitTorrent
|
||||
GenericName[is]=BitTorrent biðlarar
|
||||
Comment[is]=Sækja og deila skrám yfir BitTorrent
|
||||
Name[is]=qBittorrent
|
||||
Comment[it]=Scarica e condividi file tramite BitTorrent
|
||||
GenericName[it]=Client BitTorrent
|
||||
Comment[it]=Scarica e condividi file tramite BitTorrent
|
||||
Name[it]=qBittorrent
|
||||
Comment[ja]=BitTorrentでファイルのダウンロードと共有
|
||||
GenericName[ja]=BitTorrentクライアント
|
||||
Comment[ja]=BitTorrentでファイルのダウンロードと共有
|
||||
Name[ja]=qBittorrent
|
||||
Comment[ka]=გადმოტვირთეთ და გააზიარეთ ფაილები BitTorrent-ის საშუალებით
|
||||
GenericName[ka]=BitTorrent კლიენტი
|
||||
Comment[ka]=გადმოტვირთეთ და გააზიარეთ ფაილები BitTorrent-ის საშუალებით
|
||||
Name[ka]=qBittorrent
|
||||
Comment[ko]=BitTorrent를 통한 파일 내려받기 및 공유
|
||||
GenericName[ko]=BitTorrent 클라이언트
|
||||
Comment[ko]=BitTorrent를 통해 파일 다운로드 및 공유
|
||||
Name[ko]=qBittorrent
|
||||
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle
|
||||
GenericName[lt]=BitTorrent klientas
|
||||
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle
|
||||
Name[lt]=qBittorrent
|
||||
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
|
||||
GenericName[mk]=BitTorrent клиент
|
||||
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
|
||||
Name[mk]=qBittorrent
|
||||
Comment[my]=တောရန့်ဖြင့်ဖိုင်များဒေါင်းလုဒ်ဆွဲရန်နှင့်မျှဝေရန်
|
||||
GenericName[my]=တောရန့်စီမံခန့်ခွဲသည့်အရာ
|
||||
Comment[my]=တောရန့်ဖြင့်ဖိုင်များဒေါင်းလုဒ်ဆွဲရန်နှင့်မျှဝေရန်
|
||||
Name[my]=qBittorrent
|
||||
Comment[nb]=Last ned og del filer over BitTorrent
|
||||
GenericName[nb]=BitTorrent-klient
|
||||
Comment[nb]=Last ned og del filer over BitTorrent
|
||||
Name[nb]=qBittorrent
|
||||
Comment[nl]=Bestanden downloaden en delen via BitTorrent
|
||||
GenericName[nl]=BitTorrent-client
|
||||
Comment[nl]=Bestanden downloaden en delen via BitTorrent
|
||||
Name[nl]=qBittorrent
|
||||
Comment[pl]=Pobieraj i dziel się plikami przez BitTorrent
|
||||
GenericName[pl]=Klient BitTorrent
|
||||
Comment[pl]=Pobieraj i dziel się plikami przez BitTorrent
|
||||
Name[pl]=qBittorrent
|
||||
Comment[pt]=Transferir e partilhar ficheiros por BitTorrent
|
||||
GenericName[pt]=Cliente BitTorrent
|
||||
Comment[pt]=Transferir e partilhar ficheiros por BitTorrent
|
||||
Name[pt]=qBittorrent
|
||||
Comment[pt_BR]=Baixe e compartilhe arquivos pelo BitTorrent
|
||||
GenericName[pt_BR]=Cliente BitTorrent
|
||||
Comment[pt_BR]=Baixe e compartilhe arquivos pelo BitTorrent
|
||||
Name[pt_BR]=qBittorrent
|
||||
Comment[ro]=Descărcați și partajați fișiere prin BitTorrent
|
||||
GenericName[ro]=Client BitTorrent
|
||||
Comment[ro]=Descărcați și partajați fișiere prin BitTorrent
|
||||
Name[ro]=qBittorrent
|
||||
Comment[ru]=Обмен файлами по сети БитТоррент
|
||||
GenericName[ru]=Клиент сети БитТоррент
|
||||
Comment[ru]=Обмен файлами по сети БитТоррент
|
||||
Name[ru]=qBittorrent
|
||||
Comment[sk]=Sťahovanie a zdieľanie súborov prostredníctvom siete BitTorrent
|
||||
GenericName[sk]=Klient siete BitTorrent
|
||||
Comment[sk]=Sťahovanie a zdieľanie súborov prostredníctvom siete BitTorrent
|
||||
Name[sk]=qBittorrent
|
||||
Comment[sl]=Prenesite in delite datoteke preko BitTorrenta
|
||||
GenericName[sl]=BitTorrent odjemalec
|
||||
Comment[sl]=Prenesite in delite datoteke preko BitTorrenta
|
||||
Name[sl]=qBittorrent
|
||||
GenericName[sq]=Klienti BitTorrent
|
||||
Comment[sq]=Shkarko dhe shpërndaj skedarë në BitTorrent
|
||||
Name[sq]=qBittorrent
|
||||
Comment[sr]=Преузимајте и делите фајлове преко BitTorrent протокола
|
||||
GenericName[sr]=BitTorrent-клијент
|
||||
GenericName[sr]=BitTorrent клијент
|
||||
Comment[sr]=Преузимајте и делите фајлове преко BitTorrent-а
|
||||
Name[sr]=qBittorrent
|
||||
Comment[sr@latin]=Preuzimanje i deljenje fajlova preko BitTorrent-a
|
||||
GenericName[sr@latin]=BitTorrent klijent
|
||||
Comment[sr@latin]=Preuzimanje i deljenje fajlova preko BitTorrent-a
|
||||
Name[sr@latin]=qBittorrent
|
||||
Comment[sv]=Hämta och dela filer över BitTorrent
|
||||
GenericName[sv]=BitTorrent-klient
|
||||
Comment[sv]=Hämta och dela filer över BitTorrent
|
||||
Name[sv]=qBittorrent
|
||||
Comment[ta]=BitTorrent வழியாக கோப்புகளை பதிவிறக்க மற்றும் பகிர
|
||||
GenericName[ta]=BitTorrent வாடிக்கையாளர்
|
||||
Comment[ta]=BitTorrent வழியாக கோப்புகளை பதிவிறக்க மற்றும் பகிர
|
||||
Name[ta]=qBittorrent
|
||||
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
|
||||
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
|
||||
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
|
||||
Name[te]=qBittorrent
|
||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่าน BitTorrent
|
||||
GenericName[th]=โปรแกรมบิททอเร้นท์
|
||||
GenericName[th]=ไคลเอนต์บิททอร์เรนต์
|
||||
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่านบิตทอร์เรนต์
|
||||
Name[th]=qBittorrent
|
||||
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
||||
GenericName[tr]=BitTorrent istemcisi
|
||||
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
|
||||
Name[tr]=qBittorrent
|
||||
Comment[ur]=BitTorrent پر فائلوں کو ڈاؤن لوڈ کریں اور اشتراک کریں
|
||||
GenericName[ur]=قیو بٹ ٹورنٹ کلائنٹ
|
||||
Comment[ur]=BitTorrent پر فائلوں کو ڈاؤن لوڈ کریں اور اشتراک کریں
|
||||
Name[ur]=qBittorrent
|
||||
Comment[uk]=Завантажуйте та поширюйте файли через BitTorrent
|
||||
GenericName[uk]=BitTorrent-клієнт
|
||||
Comment[uk]=Завантажуйте та поширюйте файли через BitTorrent
|
||||
Name[uk]=qBittorrent
|
||||
Comment[vi]=Tải xuống và chia sẻ tệp qua BitTorrent
|
||||
GenericName[vi]=Máy khách BitTorrent
|
||||
Comment[vi]=Tải xuống và chia sẻ tệp qua BitTorrent
|
||||
Name[vi]=qBittorrent
|
||||
Comment[zh_HK]=經由BitTorrent下載並分享檔案
|
||||
GenericName[zh_HK]=BitTorrent用戶端
|
||||
Comment[zh_HK]=經由BitTorrent下載並分享檔案
|
||||
Name[zh_HK]=qBittorrent
|
||||
Comment[zh_TW]=經由 BitTorrent 下載並分享檔案
|
||||
GenericName[zh_TW]=BitTorrent 用戶端
|
||||
Comment[zh_TW]=使用 BitTorrent 下載並分享檔案
|
||||
Name[zh_TW]=qBittorrent
|
||||
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
|
||||
GenericName[eo]=BitTorrent-kliento
|
||||
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
|
||||
Name[eo]=qBittorrent
|
||||
Comment[kk]=BitTorrent арқылы файл жүктеу және бөлісу
|
||||
GenericName[kk]=BitTorrent клиенті
|
||||
Comment[kk]=BitTorrent арқылы файл жүктеу және бөлісу
|
||||
Name[kk]=qBittorrent
|
||||
Comment[en_AU]=Download and share files over BitTorrent
|
||||
GenericName[en_AU]=BitTorrent client
|
||||
Comment[en_AU]=Download and share files over BitTorrent
|
||||
Name[en_AU]=qBittorrent
|
||||
Name[rm]=qBittorrent
|
||||
Name[jv]=qBittorrent
|
||||
Comment[oc]=Telecargar e partejar de fichièrs amb BitTorrent
|
||||
GenericName[oc]=Client BitTorrent
|
||||
Comment[oc]=Telecargar e partejar de fichièrs amb BitTorrent
|
||||
Name[oc]=qBittorrent
|
||||
Name[ug]=qBittorrent
|
||||
Name[yi]=qBittorrent
|
||||
Comment[nqo]=ߞߐߕߐ߯ߘߐ ߟߎ߬ ߟߊߖߌ߰ ߞߊ߬ ߓߊ߲߫ ߞߵߊ߬ߟߎ߬ ߘߐߕߟߊ߫ ߓߌߙߏߙߍ߲ߕ ߞߊ߲߬
|
||||
GenericName[nqo]=ߓߌߙߏߙߍ߲ߕ ߕߣߐ߬ߓߐ߬ߟߊ
|
||||
Comment[nqo]=ߞߐߕߐ߯ߘߐ ߟߎ߬ ߟߊߖߌ߰ ߞߊ߬ ߓߊ߲߫ ߞߵߊ߬ߟߎ߬ ߘߐߕߟߊ߫ ߓߌߙߏߙߍ߲ߕ ߞߊ߲߬
|
||||
Name[nqo]=qBittorrent
|
||||
Comment[uz@Latn]=BitTorrent orqali fayllarni yuklab olish va baham ko‘rish
|
||||
GenericName[uz@Latn]=BitTorrent mijozi
|
||||
Comment[uz@Latn]=BitTorrent orqali fayllarni yuklab olish va baham ko‘rish
|
||||
Name[uz@Latn]=qBittorrent
|
||||
Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent
|
||||
GenericName[ltg]=BitTorrent klients
|
||||
Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent
|
||||
Name[ltg]=qBittorrent
|
||||
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
|
||||
GenericName[hi_IN]=Bittorrent साधन
|
||||
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
|
||||
Name[hi_IN]=qBittorrent
|
||||
Comment[az@latin]=Faylları BitTorrent vasitəsilə endirin və paylaşın
|
||||
GenericName[az@latin]=BitTorrent client
|
||||
Comment[az@latin]=Faylları BitTorrent vasitəsilə endirin və paylaşın
|
||||
Name[az@latin]=qBittorrent
|
||||
Comment[lv_LV]=Lejupielādēt un koplietot failus ar BitTorrent
|
||||
GenericName[lv_LV]=BitTorrent klients
|
||||
Comment[lv_LV]=Lejupielādēt un koplietot failus ar BitTorrent
|
||||
Name[lv_LV]=qBittorrent
|
||||
Comment[ms_MY]=Muat turun dan kongsi fail melalui BitTorrent
|
||||
GenericName[ms_MY]=Klien BitTorrent
|
||||
Comment[ms_MY]=Muat turun dan kongsi fail melalui BitTorrent
|
||||
Name[ms_MY]=qBittorrent
|
||||
Comment[mn_MN]=BitTorrent-оор файлуудаа тат, түгээ
|
||||
GenericName[mn_MN]=BitTorrent татагч
|
||||
Comment[mn_MN]=BitTorrent-оор файлуудаа тат, түгээ
|
||||
Name[mn_MN]=qBittorrent
|
||||
Comment[ne_NP]=फाइलहरू डाउनलोड गर्नुहोस् र BitTorrent मा साझा गर्नुहोस्
|
||||
GenericName[ne_NP]=BitTorrent क्लाइन्ट
|
||||
Comment[ne_NP]=फाइलहरू डाउनलोड गर्नुहोस् र BitTorrent मा साझा गर्नुहोस्
|
||||
Name[ne_NP]=qBittorrent
|
||||
Comment[pt_PT]=Transferir e partilhar ficheiros por BitTorrent
|
||||
GenericName[pt_PT]=Cliente BitTorrent
|
||||
Comment[pt_PT]=Transferir e partilhar ficheiros por BitTorrent
|
||||
Name[pt_PT]=qBittorrent
|
||||
GenericName[si_LK]=BitTorrent සේවාදායකයා
|
||||
Comment[si_LK]=BitTorrent හරහා ගොනු බාගත කර බෙදාගන්න.
|
||||
Name[si_LK]=qBittorrent
|
||||
|
|
|
@ -62,6 +62,6 @@
|
|||
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="5.0.0~beta1" date="2024-03-19"/>
|
||||
<release version="5.2.0~alpha1" date="2025-02-11"/>
|
||||
</releases>
|
||||
</component>
|
||||
|
|
4
dist/windows/config.nsh
vendored
4
dist/windows/config.nsh
vendored
|
@ -14,7 +14,7 @@
|
|||
; 4.5.1.3 -> good
|
||||
; 4.5.1.3.2 -> bad
|
||||
; 4.5.0beta -> bad
|
||||
!define /ifndef QBT_VERSION "5.0.0"
|
||||
!define /ifndef QBT_VERSION "5.2.0"
|
||||
|
||||
; Option that controls the installer's window name
|
||||
; If set, its value will be used like this:
|
||||
|
@ -86,7 +86,7 @@ OutFile "qbittorrent_${QBT_INSTALLER_FILENAME}_setup.exe"
|
|||
;Installer Version Information
|
||||
VIAddVersionKey "ProductName" "qBittorrent"
|
||||
VIAddVersionKey "CompanyName" "The qBittorrent project"
|
||||
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2024 The qBittorrent project"
|
||||
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2025 The qBittorrent project"
|
||||
VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client"
|
||||
VIAddVersionKey "FileVersion" "${QBT_VERSION}"
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ LangString launch_qbt ${LANG_ITALIAN} "Esegui qBittorrent."
|
|||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_ITALIAN} "Questo installer funziona solo con versioni di Windows a 64bit."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_ITALIAN} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_ITALIAN} "Questo installer richiede almeno Windows 10 (1809) / Windows Server 2019."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_ITALIAN} "Disinstalla qBittorrent"
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ LangString inst_magnet ${LANG_LUXEMBOURGISH} "Magnet-Linken mat qBittorrent opma
|
|||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_LUXEMBOURGISH} "Reegel an der Windows Firewall dobäisetzen"
|
||||
;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_LUXEMBOURGISH} "D'Windows path lenght (Padlängtbeschränkung) desaktivéieren (260 Zeechen MAX_PATH Beschränkung, erfuerdert min. Windows 10 1607)"
|
||||
LangString inst_pathlimit ${LANG_LUXEMBOURGISH} "D'Windows path length (Padlängtbeschränkung) desaktivéieren (260 Zeechen MAX_PATH Beschränkung, erfuerdert min. Windows 10 1607)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_LUXEMBOURGISH} "Reegel an der Windows Firewall dobäisetzen"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
|
|
|
@ -7,7 +7,7 @@ LangString inst_desktop ${LANG_PORTUGUESE} "Criar atalho no ambiente de trabalho
|
|||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_PORTUGUESE} "Criar atalho no menu Iniciar"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_PORTUGUESE} "Iniciar o qBittorrent na inicialização do Windows"
|
||||
LangString inst_startup ${LANG_PORTUGUESE} "Iniciar o qBittorrent no arranque do Windows"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_PORTUGUESE} "Abrir ficheiros .torrent com o qBittorrent"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
|
@ -29,7 +29,7 @@ LangString launch_qbt ${LANG_PORTUGUESE} "Iniciar qBittorrent."
|
|||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_PORTUGUESE} "Este instalador funciona apenas em versões Windows de 64 bits."
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_PORTUGUESE} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_PORTUGUESE} "Este instalador requer, pelo menos, o Windows 10 (1809) / Windows Server 2019."
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_PORTUGUESE} "Desinstalar qBittorrent"
|
||||
|
||||
|
|
|
@ -23,13 +23,13 @@ LangString inst_warning ${LANG_SIMPCHINESE} "qBittorrent 正在运行。 安装
|
|||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_SIMPCHINESE} "当前版本会被卸载。 用户设置和种子会被完整保留。"
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_SIMPCHINESE} "卸载以前的版本。"
|
||||
LangString inst_unist ${LANG_SIMPCHINESE} "正在卸载以前的版本。"
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
LangString launch_qbt ${LANG_SIMPCHINESE} "启动 qBittorrent。"
|
||||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_SIMPCHINESE} "此安装程序仅支持 64 位 Windows 系统。"
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_SIMPCHINESE} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_SIMPCHINESE} "此安装程序仅支持 Windows 10 (1809) / Windows Server 2019 或更新的系统。"
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_SIMPCHINESE} "卸载 qBittorrent"
|
||||
|
||||
|
|
12
dist/windows/installer-translations/swedish.nsh
vendored
12
dist/windows/installer-translations/swedish.nsh
vendored
|
@ -7,21 +7,21 @@ LangString inst_desktop ${LANG_SWEDISH} "Skapa skrivbordsgenväg"
|
|||
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
|
||||
LangString inst_startmenu ${LANG_SWEDISH} "Skapa startmenygenväg"
|
||||
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
|
||||
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows start"
|
||||
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows-uppstart"
|
||||
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
|
||||
LangString inst_torrent ${LANG_SWEDISH} "Öppna .torrent-filer med qBittorrent"
|
||||
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
|
||||
LangString inst_magnet ${LANG_SWEDISH} "Öppna magnetlänkar med qBittorrent"
|
||||
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
|
||||
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggregel"
|
||||
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggsregel"
|
||||
;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_SWEDISH} "Inaktivera gränsen för Windows-sökvägslängd (260 tecken MAX_PATH-begränsning, kräver Windows 10 1607 eller senare)"
|
||||
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
|
||||
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggregel"
|
||||
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggsregel"
|
||||
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
|
||||
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du installerar."
|
||||
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du installerar."
|
||||
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
|
||||
LangString inst_uninstall_question ${LANG_SWEDISH} "Nuvarande version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
|
||||
LangString inst_uninstall_question ${LANG_SWEDISH} "Aktuell version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
|
||||
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
|
||||
LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
|
||||
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
|
||||
|
@ -53,7 +53,7 @@ LangString remove_firewallinfo ${LANG_SWEDISH} "Tar bort Windows-brandväggsrege
|
|||
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
|
||||
LangString remove_cache ${LANG_SWEDISH} "Ta bort torrenter och cachade data"
|
||||
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
|
||||
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du avinstallerar."
|
||||
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du avinstallerar."
|
||||
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
|
||||
LangString uninst_tor_warn ${LANG_SWEDISH} "Tar inte bort .torrent-association. Den är associerad med:"
|
||||
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
|
||||
|
|
|
@ -29,7 +29,7 @@ LangString launch_qbt ${LANG_TRADCHINESE} "啟動 qBittorrent"
|
|||
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
|
||||
LangString inst_requires_64bit ${LANG_TRADCHINESE} "此安裝程式僅支援 64 位元版本的 Windows。"
|
||||
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_TRADCHINESE} "This installer requires at least Windows 10 (1809) / Windows Server 2019."
|
||||
LangString inst_requires_win10 ${LANG_TRADCHINESE} "此安裝程式僅支援 Windows 10 (1809) / Windows Server 2019 以上的系統。"
|
||||
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
|
||||
LangString inst_uninstall_link_description ${LANG_TRADCHINESE} "移除 qBittorrent"
|
||||
|
||||
|
|
|
@ -1,41 +1,44 @@
|
|||
.\" Automatically generated by Pandoc 3.1.7
|
||||
.\" Automatically generated by Pandoc 3.4
|
||||
.\"
|
||||
.TH "QBITTORRENT-NOX" "1" "January 16th 2010" "Command line Bittorrent client written in C++ / Qt" ""
|
||||
.TH "QBITTORRENT\-NOX" "1" "January 16th 2010" "Command line Bittorrent client written in C++ / Qt"
|
||||
.SH NAME
|
||||
qBittorrent-nox - a command line Bittorrent client written in C++ / Qt
|
||||
qBittorrent\-nox \- a command line Bittorrent client written in C++ / Qt
|
||||
.SH SYNOPSIS
|
||||
\f[B]qbittorrent-nox\f[R]
|
||||
\f[CR][--d|--daemon] [--webui-port=x] [TORRENT_FILE | URL]...\f[R]
|
||||
\f[B]qbittorrent\-nox\f[R]
|
||||
\f[CR][\-\-d|\-\-daemon] [\-\-webui\-port=x] [TORRENT_FILE | URL]...\f[R]
|
||||
.PP
|
||||
\f[B]qbittorrent-nox\f[R] \f[CR]--help\f[R]
|
||||
\f[B]qbittorrent\-nox\f[R] \f[CR]\-\-help\f[R]
|
||||
.PP
|
||||
\f[B]qbittorrent-nox\f[R] \f[CR]--version\f[R]
|
||||
\f[B]qbittorrent\-nox\f[R] \f[CR]\-\-version\f[R]
|
||||
.SH DESCRIPTION
|
||||
\f[B]qBittorrent-nox\f[R] is an advanced command-line Bittorrent client
|
||||
written in C++ / Qt using the \f[B]libtorrent-rasterbar\f[R] library by
|
||||
Arvid Norberg.
|
||||
qBittorrent-nox aims to be a good alternative to other command line
|
||||
\f[B]qBittorrent\-nox\f[R] is an advanced command\-line Bittorrent
|
||||
client written in C++ / Qt using the \f[B]libtorrent\-rasterbar\f[R]
|
||||
library by Arvid Norberg.
|
||||
qBittorrent\-nox aims to be a good alternative to other command line
|
||||
bittorrent clients and provides features similar to popular graphical
|
||||
clients.
|
||||
.PP
|
||||
qBittorrent-nox is fast, stable, light and it supports unicode.
|
||||
It also comes with UPnP port forwarding / NAT-PMP, encryption (Vuze
|
||||
qBittorrent\-nox is fast, stable, light and it supports unicode.
|
||||
It also comes with UPnP port forwarding / NAT\-PMP, encryption (Vuze
|
||||
compatible), FAST extension (mainline) and PeX support (utorrent
|
||||
compatible).
|
||||
.PP
|
||||
qBittorrent-nox is meant to be controlled via its feature-rich Web UI
|
||||
qBittorrent\-nox is meant to be controlled via its feature\-rich Web UI
|
||||
which is accessible as a default on http://localhost:8080.
|
||||
The Web UI access is secured and the default account user name is
|
||||
\[lq]admin\[rq] with \[lq]adminadmin\[rq] as a password.
|
||||
.SH OPTIONS
|
||||
\f[B]\f[CB]--help\f[B]\f[R] Prints the command line options.
|
||||
\f[B]\f[CB]\-\-help\f[B]\f[R] Prints the command line options.
|
||||
.PP
|
||||
\f[B]\f[CB]--version\f[B]\f[R] Prints qbittorrent program version
|
||||
\f[B]\f[CB]\-\-version\f[B]\f[R] Prints qbittorrent program version
|
||||
number.
|
||||
.PP
|
||||
\f[B]\f[CB]--webui-port=x\f[B]\f[R] Changes Web UI port to x (default:
|
||||
8080).
|
||||
\f[B]\f[CB]\-\-webui\-port=x\f[B]\f[R] Changes Web UI port to x
|
||||
(default: 8080).
|
||||
.SH BUGS
|
||||
If you find a bug, please report it at https://bugs.qbittorrent.org
|
||||
.SH AUTHORS
|
||||
Christophe Dumez <chris@qbittorrent.org>.
|
||||
Christophe Dumez \c
|
||||
.MT chris@qbittorrent.org
|
||||
.ME \c
|
||||
\&.
|
||||
|
|
|
@ -1,35 +1,38 @@
|
|||
.\" Automatically generated by Pandoc 3.1.7
|
||||
.\" Automatically generated by Pandoc 3.4
|
||||
.\"
|
||||
.TH "QBITTORRENT" "1" "January 16th 2010" "Bittorrent client written in C++ / Qt" ""
|
||||
.TH "QBITTORRENT" "1" "January 16th 2010" "Bittorrent client written in C++ / Qt"
|
||||
.SH NAME
|
||||
qBittorrent - a Bittorrent client written in C++ / Qt
|
||||
qBittorrent \- a Bittorrent client written in C++ / Qt
|
||||
.SH SYNOPSIS
|
||||
\f[B]qbittorrent\f[R]
|
||||
\f[CR][--no-splash] [--webui-port=x] [TORRENT_FILE | URL]...\f[R]
|
||||
\f[CR][\-\-no\-splash] [\-\-webui\-port=x] [TORRENT_FILE | URL]...\f[R]
|
||||
.PP
|
||||
\f[B]qbittorrent\f[R] \f[CR]--help\f[R]
|
||||
\f[B]qbittorrent\f[R] \f[CR]\-\-help\f[R]
|
||||
.PP
|
||||
\f[B]qbittorrent\f[R] \f[CR]--version\f[R]
|
||||
\f[B]qbittorrent\f[R] \f[CR]\-\-version\f[R]
|
||||
.SH DESCRIPTION
|
||||
\f[B]qBittorrent\f[R] is an advanced Bittorrent client written in C++ /
|
||||
Qt, using the \f[B]libtorrent-rasterbar\f[R] library by Arvid Norberg.
|
||||
Qt, using the \f[B]libtorrent\-rasterbar\f[R] library by Arvid Norberg.
|
||||
qBittorrent is similar to uTorrent.
|
||||
qBittorrent is fast, stable, light, it supports unicode and it provides
|
||||
a good integrated search engine.
|
||||
It also comes with UPnP port forwarding / NAT-PMP, encryption (Vuze
|
||||
It also comes with UPnP port forwarding / NAT\-PMP, encryption (Vuze
|
||||
compatible), FAST extension (mainline) and PeX support (utorrent
|
||||
compatible).
|
||||
.SH OPTIONS
|
||||
\f[B]\f[CB]--help\f[B]\f[R] Prints the command line options.
|
||||
\f[B]\f[CB]\-\-help\f[B]\f[R] Prints the command line options.
|
||||
.PP
|
||||
\f[B]\f[CB]--version\f[B]\f[R] Prints qbittorrent program version
|
||||
\f[B]\f[CB]\-\-version\f[B]\f[R] Prints qbittorrent program version
|
||||
number.
|
||||
.PP
|
||||
\f[B]\f[CB]--no-splash\f[B]\f[R] Disables splash screen on startup.
|
||||
\f[B]\f[CB]\-\-no\-splash\f[B]\f[R] Disables splash screen on startup.
|
||||
.PP
|
||||
\f[B]\f[CB]--webui-port=x\f[B]\f[R] Changes Web UI port to x (default:
|
||||
8080).
|
||||
\f[B]\f[CB]\-\-webui\-port=x\f[B]\f[R] Changes Web UI port to x
|
||||
(default: 8080).
|
||||
.SH BUGS
|
||||
If you find a bug, please report it at https://bugs.qbittorrent.org
|
||||
.SH AUTHORS
|
||||
Christophe Dumez <chris@qbittorrent.org>.
|
||||
Christophe Dumez \c
|
||||
.MT chris@qbittorrent.org
|
||||
.ME \c
|
||||
\&.
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
.\" Automatically generated by Pandoc 3.1.7
|
||||
.\" Automatically generated by Pandoc 3.4
|
||||
.\"
|
||||
.TH "QBITTORRENT-NOX" "1" "16 января 2010" "Клиент сети БитТоррент для командной строки" ""
|
||||
.TH "QBITTORRENT\-NOX" "1" "16 января 2010" "Клиент сети БитТоррент для командной строки"
|
||||
.SH НАЗВАНИЕ
|
||||
qBittorrent-nox \[em] клиент сети БитТоррент для командной строки.
|
||||
qBittorrent\-nox \[em] клиент сети БитТоррент для командной строки.
|
||||
.SH АВТОРЫ
|
||||
Christophe Dumez <chris@qbittorrent.org>.
|
||||
Christophe Dumez \c
|
||||
.MT chris@qbittorrent.org
|
||||
.ME \c
|
||||
\&.
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
.\" Automatically generated by Pandoc 3.1.7
|
||||
.\" Automatically generated by Pandoc 3.4
|
||||
.\"
|
||||
.TH "QBITTORRENT" "1" "16 января 2010" "Клиент сети БитТоррент" ""
|
||||
.TH "QBITTORRENT" "1" "16 января 2010" "Клиент сети БитТоррент"
|
||||
.SH НАЗВАНИЕ
|
||||
qBittorrent \[em] клиент сети БитТоррент.
|
||||
.SH АВТОРЫ
|
||||
Christophe Dumez <chris@qbittorrent.org>.
|
||||
Christophe Dumez \c
|
||||
.MT chris@qbittorrent.org
|
||||
.ME \c
|
||||
\&.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -124,6 +124,28 @@ namespace
|
|||
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
|
||||
#endif
|
||||
|
||||
const QString PARAM_ADDSTOPPED = u"@addStopped"_s;
|
||||
const QString PARAM_CATEGORY = u"@category"_s;
|
||||
const QString PARAM_FIRSTLASTPIECEPRIORITY = u"@firstLastPiecePriority"_s;
|
||||
const QString PARAM_SAVEPATH = u"@savePath"_s;
|
||||
const QString PARAM_SEQUENTIAL = u"@sequential"_s;
|
||||
const QString PARAM_SKIPCHECKING = u"@skipChecking"_s;
|
||||
const QString PARAM_SKIPDIALOG = u"@skipDialog"_s;
|
||||
|
||||
QString bindParamValue(const QStringView paramName, const QStringView paramValue)
|
||||
{
|
||||
return paramName + u'=' + paramValue;
|
||||
}
|
||||
|
||||
std::pair<QStringView, QStringView> parseParam(const QStringView param)
|
||||
{
|
||||
const qsizetype sepIndex = param.indexOf(u'=');
|
||||
if (sepIndex >= 0)
|
||||
return {param.first(sepIndex), param.sliced(sepIndex + 1)};
|
||||
|
||||
return {param, {}};
|
||||
}
|
||||
|
||||
QString serializeParams(const QBtCommandLineParameters ¶ms)
|
||||
{
|
||||
QStringList result;
|
||||
|
@ -138,85 +160,86 @@ namespace
|
|||
const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
|
||||
|
||||
if (!addTorrentParams.savePath.isEmpty())
|
||||
result.append(u"@savePath=" + addTorrentParams.savePath.data());
|
||||
result.append(bindParamValue(PARAM_SAVEPATH, addTorrentParams.savePath.data()));
|
||||
|
||||
if (addTorrentParams.addStopped.has_value())
|
||||
result.append(*addTorrentParams.addStopped ? u"@addStopped=1"_s : u"@addStopped=0"_s);
|
||||
result.append(bindParamValue(PARAM_ADDSTOPPED, (*addTorrentParams.addStopped ? u"1" : u"0")));
|
||||
|
||||
if (addTorrentParams.skipChecking)
|
||||
result.append(u"@skipChecking"_s);
|
||||
result.append(PARAM_SKIPCHECKING);
|
||||
|
||||
if (!addTorrentParams.category.isEmpty())
|
||||
result.append(u"@category=" + addTorrentParams.category);
|
||||
result.append(bindParamValue(PARAM_CATEGORY, addTorrentParams.category));
|
||||
|
||||
if (addTorrentParams.sequential)
|
||||
result.append(u"@sequential"_s);
|
||||
result.append(PARAM_SEQUENTIAL);
|
||||
|
||||
if (addTorrentParams.firstLastPiecePriority)
|
||||
result.append(u"@firstLastPiecePriority"_s);
|
||||
result.append(PARAM_FIRSTLASTPIECEPRIORITY);
|
||||
|
||||
if (params.skipDialog.has_value())
|
||||
result.append(*params.skipDialog ? u"@skipDialog=1"_s : u"@skipDialog=0"_s);
|
||||
result.append(bindParamValue(PARAM_SKIPDIALOG, (*params.skipDialog ? u"1" : u"0")));
|
||||
|
||||
result += params.torrentSources;
|
||||
|
||||
return result.join(PARAMS_SEPARATOR);
|
||||
}
|
||||
|
||||
QBtCommandLineParameters parseParams(const QString &str)
|
||||
QBtCommandLineParameters parseParams(const QStringView str)
|
||||
{
|
||||
QBtCommandLineParameters parsedParams;
|
||||
BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
|
||||
|
||||
for (QString param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
|
||||
for (QStringView param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
|
||||
{
|
||||
param = param.trimmed();
|
||||
const auto [paramName, paramValue] = parseParam(param);
|
||||
|
||||
// Process strings indicating options specified by the user.
|
||||
|
||||
if (param.startsWith(u"@savePath="))
|
||||
if (paramName == PARAM_SAVEPATH)
|
||||
{
|
||||
addTorrentParams.savePath = Path(param.mid(10));
|
||||
addTorrentParams.savePath = Path(paramValue.toString());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(u"@addStopped="))
|
||||
if (paramName == PARAM_ADDSTOPPED)
|
||||
{
|
||||
addTorrentParams.addStopped = (QStringView(param).mid(11).toInt() != 0);
|
||||
addTorrentParams.addStopped = (paramValue.toInt() != 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == u"@skipChecking")
|
||||
if (paramName == PARAM_SKIPCHECKING)
|
||||
{
|
||||
addTorrentParams.skipChecking = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(u"@category="))
|
||||
if (paramName == PARAM_CATEGORY)
|
||||
{
|
||||
addTorrentParams.category = param.mid(10);
|
||||
addTorrentParams.category = paramValue.toString();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == u"@sequential")
|
||||
if (paramName == PARAM_SEQUENTIAL)
|
||||
{
|
||||
addTorrentParams.sequential = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param == u"@firstLastPiecePriority")
|
||||
if (paramName == PARAM_FIRSTLASTPIECEPRIORITY)
|
||||
{
|
||||
addTorrentParams.firstLastPiecePriority = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.startsWith(u"@skipDialog="))
|
||||
if (paramName == PARAM_SKIPDIALOG)
|
||||
{
|
||||
parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0);
|
||||
parsedParams.skipDialog = (paramValue.toInt() != 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
parsedParams.torrentSources.append(param);
|
||||
parsedParams.torrentSources.append(param.toString());
|
||||
}
|
||||
|
||||
return parsedParams;
|
||||
|
@ -579,7 +602,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
|
||||
// The processing sequence is different for Windows and other OS, this is intentional
|
||||
#if defined(Q_OS_WIN)
|
||||
const QString program = replaceVariables(programTemplate);
|
||||
const std::wstring programWStr = program.toStdWString();
|
||||
|
@ -636,7 +659,13 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
|
|||
{
|
||||
// strip redundant quotes
|
||||
if (arg.startsWith(u'"') && arg.endsWith(u'"'))
|
||||
arg = arg.mid(1, (arg.size() - 2));
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
arg.slice(1, (arg.size() - 2));
|
||||
#else
|
||||
arg.removeLast().removeFirst();
|
||||
#endif
|
||||
}
|
||||
|
||||
arg = replaceVariables(arg);
|
||||
}
|
||||
|
@ -645,6 +674,9 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
|
|||
QProcess proc;
|
||||
proc.setProgram(command);
|
||||
proc.setArguments(args);
|
||||
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
|
||||
proc.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
|
||||
#endif
|
||||
|
||||
if (proc.startDetached())
|
||||
{
|
||||
|
@ -897,10 +929,10 @@ int Application::exec()
|
|||
m_desktopIntegration->showNotification(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name()));
|
||||
});
|
||||
connect(m_addTorrentManager, &AddTorrentManager::addTorrentFailed, this
|
||||
, [this](const QString &source, const QString &reason)
|
||||
, [this](const QString &source, const BitTorrent::AddTorrentError &reason)
|
||||
{
|
||||
m_desktopIntegration->showNotification(tr("Add torrent failed")
|
||||
, tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason));
|
||||
, tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason.message));
|
||||
});
|
||||
|
||||
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QStringView>
|
||||
|
||||
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
|
||||
#include <QMessageBox>
|
||||
|
@ -60,7 +61,7 @@ namespace
|
|||
class Option
|
||||
{
|
||||
protected:
|
||||
explicit constexpr Option(const char *name, char shortcut = 0)
|
||||
explicit constexpr Option(const QStringView name, const QChar shortcut = QChar::Null)
|
||||
: m_name {name}
|
||||
, m_shortcut {shortcut}
|
||||
{
|
||||
|
@ -68,23 +69,23 @@ namespace
|
|||
|
||||
QString fullParameter() const
|
||||
{
|
||||
return u"--" + QString::fromLatin1(m_name);
|
||||
return u"--" + m_name.toString();
|
||||
}
|
||||
|
||||
QString shortcutParameter() const
|
||||
{
|
||||
return u"-" + QChar::fromLatin1(m_shortcut);
|
||||
return u"-" + m_shortcut;
|
||||
}
|
||||
|
||||
bool hasShortcut() const
|
||||
{
|
||||
return m_shortcut != 0;
|
||||
return !m_shortcut.isNull();
|
||||
}
|
||||
|
||||
QString envVarName() const
|
||||
{
|
||||
return u"QBT_"
|
||||
+ QString::fromLatin1(m_name).toUpper().replace(u'-', u'_');
|
||||
+ m_name.toString().toUpper().replace(u'-', u'_');
|
||||
}
|
||||
|
||||
public:
|
||||
|
@ -99,15 +100,15 @@ namespace
|
|||
}
|
||||
|
||||
private:
|
||||
const char *m_name = nullptr;
|
||||
const char m_shortcut;
|
||||
const QStringView m_name;
|
||||
const QChar m_shortcut;
|
||||
};
|
||||
|
||||
// Boolean option.
|
||||
class BoolOption : protected Option
|
||||
{
|
||||
public:
|
||||
explicit constexpr BoolOption(const char *name, char shortcut = 0)
|
||||
explicit constexpr BoolOption(const QStringView name, const QChar shortcut = QChar::Null)
|
||||
: Option {name, shortcut}
|
||||
{
|
||||
}
|
||||
|
@ -139,8 +140,8 @@ namespace
|
|||
struct StringOption : protected Option
|
||||
{
|
||||
public:
|
||||
explicit constexpr StringOption(const char *name)
|
||||
: Option {name, 0}
|
||||
explicit constexpr StringOption(const QStringView name)
|
||||
: Option {name, QChar::Null}
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -181,7 +182,7 @@ namespace
|
|||
class IntOption : protected StringOption
|
||||
{
|
||||
public:
|
||||
explicit constexpr IntOption(const char *name)
|
||||
explicit constexpr IntOption(const QStringView name)
|
||||
: StringOption {name}
|
||||
{
|
||||
}
|
||||
|
@ -229,8 +230,8 @@ namespace
|
|||
class TriStateBoolOption : protected Option
|
||||
{
|
||||
public:
|
||||
constexpr TriStateBoolOption(const char *name, bool defaultValue)
|
||||
: Option {name, 0}
|
||||
constexpr TriStateBoolOption(const QStringView name, const bool defaultValue)
|
||||
: Option {name, QChar::Null}
|
||||
, m_defaultValue(defaultValue)
|
||||
{
|
||||
}
|
||||
|
@ -299,31 +300,32 @@ namespace
|
|||
return arg.section(u'=', 0, 0) == option.fullParameter();
|
||||
}
|
||||
|
||||
bool m_defaultValue;
|
||||
private:
|
||||
bool m_defaultValue = false;
|
||||
};
|
||||
|
||||
constexpr const BoolOption SHOW_HELP_OPTION {"help", 'h'};
|
||||
constexpr const BoolOption SHOW_HELP_OPTION {u"help", u'h'};
|
||||
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
|
||||
constexpr const BoolOption SHOW_VERSION_OPTION {"version", 'v'};
|
||||
constexpr const BoolOption SHOW_VERSION_OPTION {u"version", u'v'};
|
||||
#endif
|
||||
constexpr const BoolOption CONFIRM_LEGAL_NOTICE {"confirm-legal-notice"};
|
||||
constexpr const BoolOption CONFIRM_LEGAL_NOTICE {u"confirm-legal-notice"};
|
||||
#if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
|
||||
constexpr const BoolOption DAEMON_OPTION {"daemon", 'd'};
|
||||
constexpr const BoolOption DAEMON_OPTION {u"daemon", u'd'};
|
||||
#else
|
||||
constexpr const BoolOption NO_SPLASH_OPTION {"no-splash"};
|
||||
constexpr const BoolOption NO_SPLASH_OPTION {u"no-splash"};
|
||||
#endif
|
||||
constexpr const IntOption WEBUI_PORT_OPTION {"webui-port"};
|
||||
constexpr const IntOption TORRENTING_PORT_OPTION {"torrenting-port"};
|
||||
constexpr const StringOption PROFILE_OPTION {"profile"};
|
||||
constexpr const StringOption CONFIGURATION_OPTION {"configuration"};
|
||||
constexpr const BoolOption RELATIVE_FASTRESUME {"relative-fastresume"};
|
||||
constexpr const StringOption SAVE_PATH_OPTION {"save-path"};
|
||||
constexpr const TriStateBoolOption STOPPED_OPTION {"add-stopped", true};
|
||||
constexpr const BoolOption SKIP_HASH_CHECK_OPTION {"skip-hash-check"};
|
||||
constexpr const StringOption CATEGORY_OPTION {"category"};
|
||||
constexpr const BoolOption SEQUENTIAL_OPTION {"sequential"};
|
||||
constexpr const BoolOption FIRST_AND_LAST_OPTION {"first-and-last"};
|
||||
constexpr const TriStateBoolOption SKIP_DIALOG_OPTION {"skip-dialog", true};
|
||||
constexpr const IntOption WEBUI_PORT_OPTION {u"webui-port"};
|
||||
constexpr const IntOption TORRENTING_PORT_OPTION {u"torrenting-port"};
|
||||
constexpr const StringOption PROFILE_OPTION {u"profile"};
|
||||
constexpr const StringOption CONFIGURATION_OPTION {u"configuration"};
|
||||
constexpr const BoolOption RELATIVE_FASTRESUME {u"relative-fastresume"};
|
||||
constexpr const StringOption SAVE_PATH_OPTION {u"save-path"};
|
||||
constexpr const TriStateBoolOption STOPPED_OPTION {u"add-stopped", true};
|
||||
constexpr const BoolOption SKIP_HASH_CHECK_OPTION {u"skip-hash-check"};
|
||||
constexpr const StringOption CATEGORY_OPTION {u"category"};
|
||||
constexpr const BoolOption SEQUENTIAL_OPTION {u"sequential"};
|
||||
constexpr const BoolOption FIRST_AND_LAST_OPTION {u"first-and-last"};
|
||||
constexpr const TriStateBoolOption SKIP_DIALOG_OPTION {u"skip-dialog", true};
|
||||
}
|
||||
|
||||
QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &env)
|
||||
|
@ -463,13 +465,13 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
|
|||
return result;
|
||||
}
|
||||
|
||||
QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN, int wrapAtColumn = WRAP_AT_COLUMN)
|
||||
QString wrapText(const QString &text, const int initialIndentation = USAGE_TEXT_COLUMN, const int wrapAtColumn = WRAP_AT_COLUMN)
|
||||
{
|
||||
QStringList words = text.split(u' ');
|
||||
const QStringList words = text.split(u' ');
|
||||
QStringList lines = {words.first()};
|
||||
int currentLineMaxLength = wrapAtColumn - initialIndentation;
|
||||
|
||||
for (const QString &word : asConst(words.mid(1)))
|
||||
for (const QString &word : asConst(words.sliced(1)))
|
||||
{
|
||||
if (lines.last().length() + word.length() + 1 < currentLineMaxLength)
|
||||
{
|
||||
|
|
|
@ -32,8 +32,8 @@
|
|||
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QList>
|
||||
#include <QTextStream>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2014-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2014-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -58,10 +58,6 @@
|
|||
#include <QSplashScreen>
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QOperatingSystemVersion>
|
||||
#endif
|
||||
|
||||
#ifdef QBT_STATIC_QT
|
||||
#include <QtPlugin>
|
||||
Q_IMPORT_PLUGIN(QICOPlugin)
|
||||
|
@ -189,11 +185,6 @@ int main(int argc, char *argv[])
|
|||
// We must save it here because QApplication constructor may change it
|
||||
const bool isOneArg = (argc == 2);
|
||||
|
||||
#if !defined(DISABLE_GUI) && defined(Q_OS_WIN)
|
||||
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10)
|
||||
QApplication::setStyle(u"Fusion"_s);
|
||||
#endif
|
||||
|
||||
// `app` must be declared out of try block to allow display message box in case of exception
|
||||
std::unique_ptr<Application> app;
|
||||
try
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDataStream>
|
||||
#include <QFileInfo>
|
||||
#include <QLocalServer>
|
||||
|
@ -90,7 +91,7 @@ namespace QtLP_Private
|
|||
#endif
|
||||
}
|
||||
|
||||
const char ACK[] = "ack";
|
||||
const QByteArray ACK = QByteArrayLiteral("ack");
|
||||
|
||||
QtLocalPeer::QtLocalPeer(const QString &path, QObject *parent)
|
||||
: QObject(parent)
|
||||
|
@ -169,7 +170,7 @@ bool QtLocalPeer::sendMessage(const QString &message, const int timeout)
|
|||
{
|
||||
res &= socket.waitForReadyRead(timeout); // wait for ack
|
||||
if (res)
|
||||
res &= (socket.read(qstrlen(ACK)) == ACK);
|
||||
res &= (socket.read(ACK.size()) == ACK);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
@ -220,7 +221,7 @@ void QtLocalPeer::receiveConnection()
|
|||
return;
|
||||
}
|
||||
QString message(QString::fromUtf8(uMsg));
|
||||
socket->write(ACK, qstrlen(ACK));
|
||||
socket->write(ACK);
|
||||
socket->waitForBytesWritten(1000);
|
||||
socket->waitForDisconnected(1000); // make sure client reads ack
|
||||
delete socket;
|
||||
|
|
|
@ -71,8 +71,8 @@
|
|||
#include <QFile>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#endif
|
||||
|
||||
namespace QtLP_Private
|
||||
|
@ -105,7 +105,7 @@ namespace QtLP_Private
|
|||
|
||||
Qt::HANDLE m_writeMutex = nullptr;
|
||||
Qt::HANDLE m_readMutex = nullptr;
|
||||
QVector<Qt::HANDLE> m_readMutexes;
|
||||
QList<Qt::HANDLE> m_readMutexes;
|
||||
QString m_mutexName;
|
||||
#endif
|
||||
|
||||
|
|
1096
src/base/3rdparty/expected.hpp
vendored
1096
src/base/3rdparty/expected.hpp
vendored
File diff suppressed because it is too large
Load diff
|
@ -6,7 +6,9 @@ add_library(qbt_base STATIC
|
|||
applicationcomponent.h
|
||||
asyncfilestorage.h
|
||||
bittorrent/abstractfilestorage.h
|
||||
bittorrent/addtorrenterror.h
|
||||
bittorrent/addtorrentparams.h
|
||||
bittorrent/announcetimepoint.h
|
||||
bittorrent/bandwidthscheduler.h
|
||||
bittorrent/bencoderesumedatastorage.h
|
||||
bittorrent/cachestatus.h
|
||||
|
@ -38,6 +40,8 @@ add_library(qbt_base STATIC
|
|||
bittorrent/torrent.h
|
||||
bittorrent/torrentcontenthandler.h
|
||||
bittorrent/torrentcontentlayout.h
|
||||
bittorrent/torrentcontentremoveoption.h
|
||||
bittorrent/torrentcontentremover.h
|
||||
bittorrent/torrentcreationmanager.h
|
||||
bittorrent/torrentcreationtask.h
|
||||
bittorrent/torrentcreator.h
|
||||
|
@ -51,6 +55,7 @@ add_library(qbt_base STATIC
|
|||
concepts/stringable.h
|
||||
digest32.h
|
||||
exceptions.h
|
||||
freediskspacechecker.h
|
||||
global.h
|
||||
http/connection.h
|
||||
http/httperror.h
|
||||
|
@ -145,6 +150,7 @@ add_library(qbt_base STATIC
|
|||
bittorrent/sslparameters.cpp
|
||||
bittorrent/torrent.cpp
|
||||
bittorrent/torrentcontenthandler.cpp
|
||||
bittorrent/torrentcontentremover.cpp
|
||||
bittorrent/torrentcreationmanager.cpp
|
||||
bittorrent/torrentcreationtask.cpp
|
||||
bittorrent/torrentcreator.cpp
|
||||
|
@ -155,6 +161,7 @@ add_library(qbt_base STATIC
|
|||
bittorrent/trackerentry.cpp
|
||||
bittorrent/trackerentrystatus.cpp
|
||||
exceptions.cpp
|
||||
freediskspacechecker.cpp
|
||||
http/connection.cpp
|
||||
http/httperror.cpp
|
||||
http/requestparser.cpp
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
#include "addtorrentmanager.h"
|
||||
|
||||
#include "base/bittorrent/addtorrenterror.h"
|
||||
#include "base/bittorrent/infohash.h"
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/bittorrent/torrentdescriptor.h"
|
||||
|
@ -140,7 +141,7 @@ void AddTorrentManager::onSessionTorrentAdded(BitTorrent::Torrent *torrent)
|
|||
}
|
||||
}
|
||||
|
||||
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason)
|
||||
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const BitTorrent::AddTorrentError &reason)
|
||||
{
|
||||
if (const QString source = m_sourcesByInfoHash.take(infoHash); !source.isEmpty())
|
||||
{
|
||||
|
@ -154,14 +155,40 @@ void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &in
|
|||
void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QString &reason)
|
||||
{
|
||||
LogMsg(tr("Failed to add torrent. Source: \"%1\". Reason: \"%2\"").arg(source, reason), Log::WARNING);
|
||||
emit addTorrentFailed(source, reason);
|
||||
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::Other, reason});
|
||||
}
|
||||
|
||||
void AddTorrentManager::handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message)
|
||||
void AddTorrentManager::handleDuplicateTorrent(const QString &source
|
||||
, const BitTorrent::TorrentDescriptor &torrentDescr, BitTorrent::Torrent *existingTorrent)
|
||||
{
|
||||
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
|
||||
.arg(source, torrent->name(), message));
|
||||
emit addTorrentFailed(source, message);
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
if (hasMetadata)
|
||||
{
|
||||
// Trying to set metadata to existing torrent in case if it has none
|
||||
existingTorrent->setMetadata(*torrentDescr.info());
|
||||
}
|
||||
|
||||
const bool isPrivate = existingTorrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
||||
QString message;
|
||||
if (!btSession()->isMergeTrackersEnabled())
|
||||
{
|
||||
message = tr("Merging of trackers is disabled");
|
||||
}
|
||||
else if (isPrivate)
|
||||
{
|
||||
message = tr("Trackers cannot be merged because it is a private torrent");
|
||||
}
|
||||
else
|
||||
{
|
||||
// merge trackers and web seeds
|
||||
existingTorrent->addTrackers(torrentDescr.trackers());
|
||||
existingTorrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||
message = tr("Trackers are merged from new source");
|
||||
}
|
||||
|
||||
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: \"%2\". Torrent infohash: %3. Result: %4")
|
||||
.arg(source, existingTorrent->name(), existingTorrent->infoHash().toString(), message));
|
||||
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::DuplicateTorrent, message});
|
||||
}
|
||||
|
||||
void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard)
|
||||
|
@ -169,11 +196,9 @@ void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_p
|
|||
m_guardedTorrentFiles.emplace(source, std::move(torrentFileGuard));
|
||||
}
|
||||
|
||||
void AddTorrentManager::releaseTorrentFileGuard(const QString &source)
|
||||
std::shared_ptr<TorrentFileGuard> AddTorrentManager::releaseTorrentFileGuard(const QString &source)
|
||||
{
|
||||
auto torrentFileGuard = m_guardedTorrentFiles.take(source);
|
||||
if (torrentFileGuard)
|
||||
torrentFileGuard->setAutoRemove(false);
|
||||
return m_guardedTorrentFiles.take(source);
|
||||
}
|
||||
|
||||
bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||
|
@ -184,32 +209,7 @@ bool AddTorrentManager::processTorrent(const QString &source, const BitTorrent::
|
|||
if (BitTorrent::Torrent *torrent = btSession()->findTorrent(infoHash))
|
||||
{
|
||||
// a duplicate torrent is being added
|
||||
|
||||
const bool hasMetadata = torrentDescr.info().has_value();
|
||||
if (hasMetadata)
|
||||
{
|
||||
// Trying to set metadata to existing torrent in case if it has none
|
||||
torrent->setMetadata(*torrentDescr.info());
|
||||
}
|
||||
|
||||
if (!btSession()->isMergeTrackersEnabled())
|
||||
{
|
||||
handleDuplicateTorrent(source, torrent, tr("Merging of trackers is disabled"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool isPrivate = torrent->isPrivate() || (hasMetadata && torrentDescr.info()->isPrivate());
|
||||
if (isPrivate)
|
||||
{
|
||||
handleDuplicateTorrent(source, torrent, tr("Trackers cannot be merged because it is a private torrent"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// merge trackers and web seeds
|
||||
torrent->addTrackers(torrentDescr.trackers());
|
||||
torrent->addUrlSeeds(torrentDescr.urlSeeds());
|
||||
|
||||
handleDuplicateTorrent(source, torrent, tr("Trackers are merged from new source"));
|
||||
handleDuplicateTorrent(source, torrentDescr, torrent);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,7 @@ namespace BitTorrent
|
|||
class Session;
|
||||
class Torrent;
|
||||
class TorrentDescriptor;
|
||||
struct AddTorrentError;
|
||||
}
|
||||
|
||||
namespace Net
|
||||
|
@ -66,20 +67,20 @@ public:
|
|||
|
||||
signals:
|
||||
void torrentAdded(const QString &source, BitTorrent::Torrent *torrent);
|
||||
void addTorrentFailed(const QString &source, const QString &reason);
|
||||
void addTorrentFailed(const QString &source, const BitTorrent::AddTorrentError &reason);
|
||||
|
||||
protected:
|
||||
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||
, const BitTorrent::AddTorrentParams &addTorrentParams);
|
||||
void handleAddTorrentFailed(const QString &source, const QString &reason);
|
||||
void handleDuplicateTorrent(const QString &source, BitTorrent::Torrent *torrent, const QString &message);
|
||||
void handleDuplicateTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr, BitTorrent::Torrent *existingTorrent);
|
||||
void setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard);
|
||||
void releaseTorrentFileGuard(const QString &source);
|
||||
std::shared_ptr<TorrentFileGuard> releaseTorrentFileGuard(const QString &source);
|
||||
|
||||
private:
|
||||
void onDownloadFinished(const Net::DownloadResult &result);
|
||||
void onSessionTorrentAdded(BitTorrent::Torrent *torrent);
|
||||
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason);
|
||||
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const BitTorrent::AddTorrentError &reason);
|
||||
bool processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
|
||||
, const BitTorrent::AddTorrentParams &addTorrentParams);
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
#include <QDir>
|
||||
#include <QHash>
|
||||
#include <QVector>
|
||||
#include <QList>
|
||||
|
||||
#include "base/exceptions.h"
|
||||
#include "base/path.h"
|
||||
|
@ -71,7 +71,7 @@ void BitTorrent::AbstractFileStorage::renameFolder(const Path &oldFolderPath, co
|
|||
if (newFolderPath.isAbsolute())
|
||||
throw RuntimeError(tr("Absolute path isn't allowed: '%1'.").arg(newFolderPath.toString()));
|
||||
|
||||
QVector<int> renamingFileIndexes;
|
||||
QList<int> renamingFileIndexes;
|
||||
renamingFileIndexes.reserve(filesCount());
|
||||
|
||||
for (int i = 0; i < filesCount(); ++i)
|
||||
|
|
49
src/base/bittorrent/addtorrenterror.h
Normal file
49
src/base/bittorrent/addtorrenterror.h
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2025 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 <QMetaType>
|
||||
#include <QString>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
struct AddTorrentError
|
||||
{
|
||||
enum Kind
|
||||
{
|
||||
DuplicateTorrent,
|
||||
Other
|
||||
};
|
||||
|
||||
Kind kind = Other;
|
||||
QString message;
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(BitTorrent::AddTorrentError)
|
|
@ -30,9 +30,9 @@
|
|||
|
||||
#include <optional>
|
||||
|
||||
#include <QList>
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/path.h"
|
||||
#include "base/tagset.h"
|
||||
|
@ -62,7 +62,7 @@ namespace BitTorrent
|
|||
std::optional<bool> addStopped;
|
||||
std::optional<Torrent::StopCondition> stopCondition;
|
||||
PathList filePaths; // used if TorrentInfo is set
|
||||
QVector<DownloadPriority> filePriorities; // used if TorrentInfo is set
|
||||
QList<DownloadPriority> filePriorities; // used if TorrentInfo is set
|
||||
bool skipChecking = false;
|
||||
std::optional<BitTorrent::TorrentContentLayout> contentLayout;
|
||||
std::optional<bool> useAutoTMM;
|
||||
|
|
36
src/base/bittorrent/announcetimepoint.h
Normal file
36
src/base/bittorrent/announcetimepoint.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 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 <chrono>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
using AnnounceTimePoint = std::chrono::high_resolution_clock::time_point;
|
||||
}
|
|
@ -102,7 +102,7 @@ bool BandwidthScheduler::isTimeForAlternative() const
|
|||
alternative = !alternative;
|
||||
break;
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
#include <QRegularExpression>
|
||||
#include <QThread>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/exceptions.h"
|
||||
#include "base/global.h"
|
||||
#include "base/logger.h"
|
||||
|
@ -65,7 +64,7 @@ namespace BitTorrent
|
|||
|
||||
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const;
|
||||
void remove(const TorrentID &id) const;
|
||||
void storeQueue(const QVector<TorrentID> &queue) const;
|
||||
void storeQueue(const QList<TorrentID> &queue) const;
|
||||
|
||||
private:
|
||||
const Path m_resumeDataDir;
|
||||
|
@ -132,10 +131,11 @@ BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path,
|
|||
|
||||
m_asyncWorker->moveToThread(m_ioThread.get());
|
||||
connect(m_ioThread.get(), &QThread::finished, m_asyncWorker, &QObject::deleteLater);
|
||||
m_ioThread->setObjectName("BencodeResumeDataStorage m_ioThread");
|
||||
m_ioThread->start();
|
||||
}
|
||||
|
||||
QVector<BitTorrent::TorrentID> BitTorrent::BencodeResumeDataStorage::registeredTorrents() const
|
||||
QList<BitTorrent::TorrentID> BitTorrent::BencodeResumeDataStorage::registeredTorrents() const
|
||||
{
|
||||
return m_registeredTorrents;
|
||||
}
|
||||
|
@ -290,10 +290,11 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
|
|||
lt::add_torrent_params &p = torrentParams.ltAddTorrentParams;
|
||||
|
||||
p = lt::read_resume_data(resumeDataRoot, ec);
|
||||
if (ec)
|
||||
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
|
||||
|
||||
if (!metadata.isEmpty())
|
||||
{
|
||||
const auto *pref = Preferences::instance();
|
||||
const lt::bdecode_node torentInfoRoot = lt::bdecode(metadata, ec
|
||||
, nullptr, pref->getBdecodeDepthLimit(), pref->getBdecodeTokenLimit());
|
||||
if (ec)
|
||||
|
@ -321,6 +322,8 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
|
|||
|
||||
p.save_path = Profile::instance()->fromPortablePath(
|
||||
Path(fromLTString(p.save_path))).toString().toStdString();
|
||||
if (p.save_path.empty())
|
||||
return nonstd::make_unexpected(tr("Corrupted resume data: %1").arg(tr("save_path is invalid")));
|
||||
|
||||
torrentParams.stopped = (p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed);
|
||||
torrentParams.operatingMode = (p.flags & lt::torrent_flags::paused) || (p.flags & lt::torrent_flags::auto_managed)
|
||||
|
@ -355,7 +358,7 @@ void BitTorrent::BencodeResumeDataStorage::remove(const TorrentID &id) const
|
|||
});
|
||||
}
|
||||
|
||||
void BitTorrent::BencodeResumeDataStorage::storeQueue(const QVector<TorrentID> &queue) const
|
||||
void BitTorrent::BencodeResumeDataStorage::storeQueue(const QList<TorrentID> &queue) const
|
||||
{
|
||||
QMetaObject::invokeMethod(m_asyncWorker, [this, queue]()
|
||||
{
|
||||
|
@ -461,7 +464,7 @@ void BitTorrent::BencodeResumeDataStorage::Worker::remove(const TorrentID &id) c
|
|||
Utils::Fs::removeFile(m_resumeDataDir / torrentFilename);
|
||||
}
|
||||
|
||||
void BitTorrent::BencodeResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue) const
|
||||
void BitTorrent::BencodeResumeDataStorage::Worker::storeQueue(const QList<TorrentID> &queue) const
|
||||
{
|
||||
QByteArray data;
|
||||
data.reserve(((BitTorrent::TorrentID::length() * 2) + 1) * queue.size());
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QVector>
|
||||
#include <QList>
|
||||
|
||||
#include "base/pathfwd.h"
|
||||
#include "base/utils/thread.h"
|
||||
|
@ -37,7 +37,6 @@
|
|||
#include "resumedatastorage.h"
|
||||
|
||||
class QByteArray;
|
||||
class QThread;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
|
@ -49,18 +48,18 @@ namespace BitTorrent
|
|||
public:
|
||||
explicit BencodeResumeDataStorage(const Path &path, QObject *parent = nullptr);
|
||||
|
||||
QVector<TorrentID> registeredTorrents() const override;
|
||||
QList<TorrentID> registeredTorrents() const override;
|
||||
LoadResumeDataResult load(const TorrentID &id) const override;
|
||||
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const override;
|
||||
void remove(const TorrentID &id) const override;
|
||||
void storeQueue(const QVector<TorrentID> &queue) const override;
|
||||
void storeQueue(const QList<TorrentID> &queue) const override;
|
||||
|
||||
private:
|
||||
void doLoadAll() const override;
|
||||
void loadQueue(const Path &queueFilename);
|
||||
LoadResumeDataResult loadTorrentResumeData(const QByteArray &data, const QByteArray &metadata) const;
|
||||
|
||||
QVector<TorrentID> m_registeredTorrents;
|
||||
QList<TorrentID> m_registeredTorrents;
|
||||
Utils::Thread::UniquePtr m_ioThread;
|
||||
|
||||
class Worker;
|
||||
|
|
|
@ -240,11 +240,11 @@ void CustomDiskIOThread::handleCompleteFiles(lt::storage_index_t storage, const
|
|||
|
||||
lt::storage_interface *customStorageConstructor(const lt::storage_params ¶ms, lt::file_pool &pool)
|
||||
{
|
||||
return new CustomStorage {params, pool};
|
||||
return new CustomStorage(params, pool);
|
||||
}
|
||||
|
||||
CustomStorage::CustomStorage(const lt::storage_params ¶ms, lt::file_pool &filePool)
|
||||
: lt::default_storage {params, filePool}
|
||||
: lt::default_storage(params, filePool)
|
||||
, m_savePath {params.path}
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2021-2025 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
|
||||
|
@ -41,6 +41,7 @@
|
|||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
#include <QSet>
|
||||
#include <QSqlDatabase>
|
||||
|
@ -48,7 +49,6 @@
|
|||
#include <QSqlQuery>
|
||||
#include <QSqlRecord>
|
||||
#include <QThread>
|
||||
#include <QVector>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "base/exceptions.h"
|
||||
|
@ -67,7 +67,7 @@ namespace
|
|||
{
|
||||
const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_s;
|
||||
|
||||
const int DB_VERSION = 7;
|
||||
const int DB_VERSION = 8;
|
||||
|
||||
const QString DB_TABLE_META = u"meta"_s;
|
||||
const QString DB_TABLE_TORRENTS = u"torrents"_s;
|
||||
|
@ -107,11 +107,11 @@ namespace
|
|||
class StoreQueueJob final : public Job
|
||||
{
|
||||
public:
|
||||
explicit StoreQueueJob(const QVector<TorrentID> &queue);
|
||||
explicit StoreQueueJob(const QList<TorrentID> &queue);
|
||||
void perform(QSqlDatabase db) override;
|
||||
|
||||
private:
|
||||
const QVector<TorrentID> m_queue;
|
||||
const QList<TorrentID> m_queue;
|
||||
};
|
||||
|
||||
struct Column
|
||||
|
@ -120,36 +120,35 @@ namespace
|
|||
QString placeholder;
|
||||
};
|
||||
|
||||
Column makeColumn(const char *columnName)
|
||||
Column makeColumn(const QString &columnName)
|
||||
{
|
||||
const QString name = QString::fromLatin1(columnName);
|
||||
return {.name = name, .placeholder = (u':' + name)};
|
||||
return {.name = columnName, .placeholder = (u':' + columnName)};
|
||||
}
|
||||
|
||||
const Column DB_COLUMN_ID = makeColumn("id");
|
||||
const Column DB_COLUMN_TORRENT_ID = makeColumn("torrent_id");
|
||||
const Column DB_COLUMN_QUEUE_POSITION = makeColumn("queue_position");
|
||||
const Column DB_COLUMN_NAME = makeColumn("name");
|
||||
const Column DB_COLUMN_CATEGORY = makeColumn("category");
|
||||
const Column DB_COLUMN_TAGS = makeColumn("tags");
|
||||
const Column DB_COLUMN_TARGET_SAVE_PATH = makeColumn("target_save_path");
|
||||
const Column DB_COLUMN_DOWNLOAD_PATH = makeColumn("download_path");
|
||||
const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn("content_layout");
|
||||
const Column DB_COLUMN_RATIO_LIMIT = makeColumn("ratio_limit");
|
||||
const Column DB_COLUMN_SEEDING_TIME_LIMIT = makeColumn("seeding_time_limit");
|
||||
const Column DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT = makeColumn("inactive_seeding_time_limit");
|
||||
const Column DB_COLUMN_SHARE_LIMIT_ACTION = makeColumn("share_limit_action");
|
||||
const Column DB_COLUMN_HAS_OUTER_PIECES_PRIORITY = makeColumn("has_outer_pieces_priority");
|
||||
const Column DB_COLUMN_HAS_SEED_STATUS = makeColumn("has_seed_status");
|
||||
const Column DB_COLUMN_OPERATING_MODE = makeColumn("operating_mode");
|
||||
const Column DB_COLUMN_STOPPED = makeColumn("stopped");
|
||||
const Column DB_COLUMN_STOP_CONDITION = makeColumn("stop_condition");
|
||||
const Column DB_COLUMN_SSL_CERTIFICATE = makeColumn("ssl_certificate");
|
||||
const Column DB_COLUMN_SSL_PRIVATE_KEY = makeColumn("ssl_private_key");
|
||||
const Column DB_COLUMN_SSL_DH_PARAMS = makeColumn("ssl_dh_params");
|
||||
const Column DB_COLUMN_RESUMEDATA = makeColumn("libtorrent_resume_data");
|
||||
const Column DB_COLUMN_METADATA = makeColumn("metadata");
|
||||
const Column DB_COLUMN_VALUE = makeColumn("value");
|
||||
const Column DB_COLUMN_ID = makeColumn(u"id"_s);
|
||||
const Column DB_COLUMN_TORRENT_ID = makeColumn(u"torrent_id"_s);
|
||||
const Column DB_COLUMN_QUEUE_POSITION = makeColumn(u"queue_position"_s);
|
||||
const Column DB_COLUMN_NAME = makeColumn(u"name"_s);
|
||||
const Column DB_COLUMN_CATEGORY = makeColumn(u"category"_s);
|
||||
const Column DB_COLUMN_TAGS = makeColumn(u"tags"_s);
|
||||
const Column DB_COLUMN_TARGET_SAVE_PATH = makeColumn(u"target_save_path"_s);
|
||||
const Column DB_COLUMN_DOWNLOAD_PATH = makeColumn(u"download_path"_s);
|
||||
const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn(u"content_layout"_s);
|
||||
const Column DB_COLUMN_RATIO_LIMIT = makeColumn(u"ratio_limit"_s);
|
||||
const Column DB_COLUMN_SEEDING_TIME_LIMIT = makeColumn(u"seeding_time_limit"_s);
|
||||
const Column DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT = makeColumn(u"inactive_seeding_time_limit"_s);
|
||||
const Column DB_COLUMN_SHARE_LIMIT_ACTION = makeColumn(u"share_limit_action"_s);
|
||||
const Column DB_COLUMN_HAS_OUTER_PIECES_PRIORITY = makeColumn(u"has_outer_pieces_priority"_s);
|
||||
const Column DB_COLUMN_HAS_SEED_STATUS = makeColumn(u"has_seed_status"_s);
|
||||
const Column DB_COLUMN_OPERATING_MODE = makeColumn(u"operating_mode"_s);
|
||||
const Column DB_COLUMN_STOPPED = makeColumn(u"stopped"_s);
|
||||
const Column DB_COLUMN_STOP_CONDITION = makeColumn(u"stop_condition"_s);
|
||||
const Column DB_COLUMN_SSL_CERTIFICATE = makeColumn(u"ssl_certificate"_s);
|
||||
const Column DB_COLUMN_SSL_PRIVATE_KEY = makeColumn(u"ssl_private_key"_s);
|
||||
const Column DB_COLUMN_SSL_DH_PARAMS = makeColumn(u"ssl_dh_params"_s);
|
||||
const Column DB_COLUMN_RESUMEDATA = makeColumn(u"libtorrent_resume_data"_s);
|
||||
const Column DB_COLUMN_METADATA = makeColumn(u"metadata"_s);
|
||||
const Column DB_COLUMN_VALUE = makeColumn(u"value"_s);
|
||||
|
||||
template <typename LTStr>
|
||||
QString fromLTString(const LTStr &str)
|
||||
|
@ -168,7 +167,7 @@ namespace
|
|||
return u"CREATE TABLE %1 (%2)"_s.arg(quoted(tableName), items.join(u','));
|
||||
}
|
||||
|
||||
std::pair<QString, QString> joinColumns(const QVector<Column> &columns)
|
||||
std::pair<QString, QString> joinColumns(const QList<Column> &columns)
|
||||
{
|
||||
int namesSize = columns.size();
|
||||
int valuesSize = columns.size();
|
||||
|
@ -193,104 +192,30 @@ namespace
|
|||
return std::make_pair(names, values);
|
||||
}
|
||||
|
||||
QString makeInsertStatement(const QString &tableName, const QVector<Column> &columns)
|
||||
QString makeInsertStatement(const QString &tableName, const QList<Column> &columns)
|
||||
{
|
||||
const auto [names, values] = joinColumns(columns);
|
||||
return u"INSERT INTO %1 (%2) VALUES (%3)"_s
|
||||
.arg(quoted(tableName), names, values);
|
||||
}
|
||||
|
||||
QString makeUpdateStatement(const QString &tableName, const QVector<Column> &columns)
|
||||
QString makeUpdateStatement(const QString &tableName, const QList<Column> &columns)
|
||||
{
|
||||
const auto [names, values] = joinColumns(columns);
|
||||
return u"UPDATE %1 SET (%2) = (%3)"_s
|
||||
.arg(quoted(tableName), names, values);
|
||||
}
|
||||
|
||||
QString makeOnConflictUpdateStatement(const Column &constraint, const QVector<Column> &columns)
|
||||
QString makeOnConflictUpdateStatement(const Column &constraint, const QList<Column> &columns)
|
||||
{
|
||||
const auto [names, values] = joinColumns(columns);
|
||||
return u" ON CONFLICT (%1) DO UPDATE SET (%2) = (%3)"_s
|
||||
.arg(quoted(constraint.name), names, values);
|
||||
}
|
||||
|
||||
QString makeColumnDefinition(const Column &column, const char *definition)
|
||||
QString makeColumnDefinition(const Column &column, const QString &definition)
|
||||
{
|
||||
return u"%1 %2"_s.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.inactiveSeedingTimeLimit = query.value(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name).toInt();
|
||||
resumeData.shareLimitAction = Utils::String::toEnum<ShareLimitAction>(
|
||||
query.value(DB_COLUMN_SHARE_LIMIT_ACTION.name).toString(), ShareLimitAction::Default);
|
||||
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.sslParameters =
|
||||
{
|
||||
.certificate = QSslCertificate(query.value(DB_COLUMN_SSL_CERTIFICATE.name).toByteArray()),
|
||||
.privateKey = Utils::SSLKey::load(query.value(DB_COLUMN_SSL_PRIVATE_KEY.name).toByteArray()),
|
||||
.dhParams = query.value(DB_COLUMN_SSL_DH_PARAMS.name).toByteArray()
|
||||
};
|
||||
|
||||
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();
|
||||
const auto *pref = Preferences::instance();
|
||||
const int bdecodeDepthLimit = pref->getBdecodeDepthLimit();
|
||||
const int bdecodeTokenLimit = pref->getBdecodeTokenLimit();
|
||||
|
||||
lt::error_code ec;
|
||||
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec
|
||||
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
|
||||
|
||||
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
|
||||
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
|
||||
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;
|
||||
return u"%1 %2"_s.arg(quoted(column.name), definition);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,7 +233,7 @@ namespace BitTorrent
|
|||
|
||||
void store(const TorrentID &id, const LoadTorrentParams &resumeData);
|
||||
void remove(const TorrentID &id);
|
||||
void storeQueue(const QVector<TorrentID> &queue);
|
||||
void storeQueue(const QList<TorrentID> &queue);
|
||||
|
||||
private:
|
||||
void addJob(std::unique_ptr<Job> job);
|
||||
|
@ -325,7 +250,6 @@ namespace BitTorrent
|
|||
|
||||
BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent)
|
||||
: ResumeDataStorage(dbPath, parent)
|
||||
, m_ioThread {new QThread}
|
||||
{
|
||||
const bool needCreateDB = !dbPath.exists();
|
||||
|
||||
|
@ -356,7 +280,7 @@ BitTorrent::DBResumeDataStorage::~DBResumeDataStorage()
|
|||
QSqlDatabase::removeDatabase(DB_CONNECTION_NAME);
|
||||
}
|
||||
|
||||
QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents() const
|
||||
QList<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents() const
|
||||
{
|
||||
const auto selectTorrentIDStatement = u"SELECT %1 FROM %2 ORDER BY %3;"_s
|
||||
.arg(quoted(DB_COLUMN_TORRENT_ID.name), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
|
||||
|
@ -367,7 +291,7 @@ QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorren
|
|||
if (!query.exec(selectTorrentIDStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
QVector<TorrentID> registeredTorrents;
|
||||
QList<TorrentID> registeredTorrents;
|
||||
registeredTorrents.reserve(query.size());
|
||||
while (query.next())
|
||||
registeredTorrents.append(BitTorrent::TorrentID::fromString(query.value(0).toString()));
|
||||
|
@ -413,7 +337,7 @@ void BitTorrent::DBResumeDataStorage::remove(const BitTorrent::TorrentID &id) co
|
|||
m_asyncWorker->remove(id);
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::storeQueue(const QVector<TorrentID> &queue) const
|
||||
void BitTorrent::DBResumeDataStorage::storeQueue(const QList<TorrentID> &queue) const
|
||||
{
|
||||
m_asyncWorker->storeQueue(queue);
|
||||
}
|
||||
|
@ -438,7 +362,7 @@ void BitTorrent::DBResumeDataStorage::doLoadAll() const
|
|||
if (!query.exec(selectTorrentIDStatement))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
QVector<TorrentID> registeredTorrents;
|
||||
QList<TorrentID> registeredTorrents;
|
||||
registeredTorrents.reserve(query.size());
|
||||
while (query.next())
|
||||
registeredTorrents.append(TorrentID::fromString(query.value(0).toString()));
|
||||
|
@ -512,9 +436,9 @@ void BitTorrent::DBResumeDataStorage::createDB() const
|
|||
try
|
||||
{
|
||||
const QStringList tableMetaItems = {
|
||||
makeColumnDefinition(DB_COLUMN_ID, "INTEGER PRIMARY KEY"),
|
||||
makeColumnDefinition(DB_COLUMN_NAME, "TEXT NOT NULL UNIQUE"),
|
||||
makeColumnDefinition(DB_COLUMN_VALUE, "BLOB")
|
||||
makeColumnDefinition(DB_COLUMN_ID, u"INTEGER PRIMARY KEY"_s),
|
||||
makeColumnDefinition(DB_COLUMN_NAME, u"TEXT NOT NULL UNIQUE"_s),
|
||||
makeColumnDefinition(DB_COLUMN_VALUE, u"BLOB"_s)
|
||||
};
|
||||
const QString createTableMetaQuery = makeCreateTableStatement(DB_TABLE_META, tableMetaItems);
|
||||
if (!query.exec(createTableMetaQuery))
|
||||
|
@ -531,29 +455,29 @@ void BitTorrent::DBResumeDataStorage::createDB() const
|
|||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
const QStringList tableTorrentsItems = {
|
||||
makeColumnDefinition(DB_COLUMN_ID, "INTEGER PRIMARY KEY"),
|
||||
makeColumnDefinition(DB_COLUMN_TORRENT_ID, "BLOB NOT NULL UNIQUE"),
|
||||
makeColumnDefinition(DB_COLUMN_QUEUE_POSITION, "INTEGER NOT NULL DEFAULT -1"),
|
||||
makeColumnDefinition(DB_COLUMN_NAME, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_CATEGORY, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_TAGS, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_TARGET_SAVE_PATH, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_CONTENT_LAYOUT, "TEXT NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_RATIO_LIMIT, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_SEEDING_TIME_LIMIT, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_SHARE_LIMIT_ACTION, "TEXT NOT NULL DEFAULT `Default`"),
|
||||
makeColumnDefinition(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_HAS_SEED_STATUS, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_OPERATING_MODE, "TEXT NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_STOPPED, "INTEGER NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"),
|
||||
makeColumnDefinition(DB_COLUMN_SSL_CERTIFICATE, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_SSL_PRIVATE_KEY, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_SSL_DH_PARAMS, "TEXT"),
|
||||
makeColumnDefinition(DB_COLUMN_RESUMEDATA, "BLOB NOT NULL"),
|
||||
makeColumnDefinition(DB_COLUMN_METADATA, "BLOB")
|
||||
makeColumnDefinition(DB_COLUMN_ID, u"INTEGER PRIMARY KEY"_s),
|
||||
makeColumnDefinition(DB_COLUMN_TORRENT_ID, u"BLOB NOT NULL UNIQUE"_s),
|
||||
makeColumnDefinition(DB_COLUMN_QUEUE_POSITION, u"INTEGER NOT NULL DEFAULT -1"_s),
|
||||
makeColumnDefinition(DB_COLUMN_NAME, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_CATEGORY, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_TAGS, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_TARGET_SAVE_PATH, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_CONTENT_LAYOUT, u"TEXT NOT NULL"_s),
|
||||
makeColumnDefinition(DB_COLUMN_RATIO_LIMIT, u"INTEGER NOT NULL"_s),
|
||||
makeColumnDefinition(DB_COLUMN_SEEDING_TIME_LIMIT, u"INTEGER NOT NULL"_s),
|
||||
makeColumnDefinition(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, u"INTEGER NOT NULL"_s),
|
||||
makeColumnDefinition(DB_COLUMN_SHARE_LIMIT_ACTION, u"TEXT NOT NULL DEFAULT `Default`"_s),
|
||||
makeColumnDefinition(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY, u"INTEGER NOT NULL"_s),
|
||||
makeColumnDefinition(DB_COLUMN_HAS_SEED_STATUS, u"INTEGER NOT NULL"_s),
|
||||
makeColumnDefinition(DB_COLUMN_OPERATING_MODE, u"TEXT NOT NULL"_s),
|
||||
makeColumnDefinition(DB_COLUMN_STOPPED, u"INTEGER NOT NULL"_s),
|
||||
makeColumnDefinition(DB_COLUMN_STOP_CONDITION, u"TEXT NOT NULL DEFAULT `None`"_s),
|
||||
makeColumnDefinition(DB_COLUMN_SSL_CERTIFICATE, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_SSL_PRIVATE_KEY, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_SSL_DH_PARAMS, u"TEXT"_s),
|
||||
makeColumnDefinition(DB_COLUMN_RESUMEDATA, u"BLOB NOT NULL"_s),
|
||||
makeColumnDefinition(DB_COLUMN_METADATA, u"BLOB"_s)
|
||||
};
|
||||
const QString createTableTorrentsQuery = makeCreateTableStatement(DB_TABLE_TORRENTS, tableTorrentsItems);
|
||||
if (!query.exec(createTableTorrentsQuery))
|
||||
|
@ -591,7 +515,7 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
|
|||
|
||||
try
|
||||
{
|
||||
const auto addColumn = [&query](const QString &table, const Column &column, const char *definition)
|
||||
const auto addColumn = [&query](const QString &table, const Column &column, const QString &definition)
|
||||
{
|
||||
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_s.arg(quoted(column.name), quoted(table));
|
||||
if (query.exec(testQuery))
|
||||
|
@ -603,10 +527,10 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
|
|||
};
|
||||
|
||||
if (fromVersion <= 1)
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_DOWNLOAD_PATH, "TEXT");
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_DOWNLOAD_PATH, u"TEXT"_s);
|
||||
|
||||
if (fromVersion <= 2)
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`");
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_STOP_CONDITION, u"TEXT NOT NULL DEFAULT `None`"_s);
|
||||
|
||||
if (fromVersion <= 3)
|
||||
{
|
||||
|
@ -618,17 +542,41 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
|
|||
}
|
||||
|
||||
if (fromVersion <= 4)
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, "INTEGER NOT NULL DEFAULT -2");
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, u"INTEGER NOT NULL DEFAULT -2"_s);
|
||||
|
||||
if (fromVersion <= 5)
|
||||
{
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_CERTIFICATE, "TEXT");
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_PRIVATE_KEY, "TEXT");
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_DH_PARAMS, "TEXT");
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_CERTIFICATE, u"TEXT"_s);
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_PRIVATE_KEY, u"TEXT"_s);
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_DH_PARAMS, u"TEXT"_s);
|
||||
}
|
||||
|
||||
if (fromVersion <= 6)
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SHARE_LIMIT_ACTION, "TEXTNOT NULL DEFAULT `Default`");
|
||||
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SHARE_LIMIT_ACTION, u"TEXT NOT NULL DEFAULT `Default`"_s);
|
||||
|
||||
if (fromVersion == 7)
|
||||
{
|
||||
const QString TEMP_COLUMN_NAME = DB_COLUMN_SHARE_LIMIT_ACTION.name + u"_temp";
|
||||
|
||||
auto queryStr = u"ALTER TABLE %1 ADD %2 %3"_s
|
||||
.arg(quoted(DB_TABLE_TORRENTS), TEMP_COLUMN_NAME, u"TEXT NOT NULL DEFAULT `Default`");
|
||||
if (!query.exec(queryStr))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
queryStr = u"UPDATE %1 SET %2 = %3"_s
|
||||
.arg(quoted(DB_TABLE_TORRENTS), quoted(TEMP_COLUMN_NAME), quoted(DB_COLUMN_SHARE_LIMIT_ACTION.name));
|
||||
if (!query.exec(queryStr))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
queryStr = u"ALTER TABLE %1 DROP %2"_s.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_SHARE_LIMIT_ACTION.name));
|
||||
if (!query.exec(queryStr))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
|
||||
queryStr = u"ALTER TABLE %1 RENAME %2 TO %3"_s
|
||||
.arg(quoted(DB_TABLE_TORRENTS), quoted(TEMP_COLUMN_NAME), quoted(DB_COLUMN_SHARE_LIMIT_ACTION.name));
|
||||
if (!query.exec(queryStr))
|
||||
throw RuntimeError(query.lastError().text());
|
||||
}
|
||||
|
||||
const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE});
|
||||
if (!query.prepare(updateMetaVersionQuery))
|
||||
|
@ -666,6 +614,90 @@ void BitTorrent::DBResumeDataStorage::enableWALMode() const
|
|||
throw RuntimeError(tr("WAL mode is probably unsupported due to filesystem limitations."));
|
||||
}
|
||||
|
||||
LoadResumeDataResult DBResumeDataStorage::parseQueryResultRow(const QSqlQuery &query) const
|
||||
{
|
||||
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.inactiveSeedingTimeLimit = query.value(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name).toInt();
|
||||
resumeData.shareLimitAction = Utils::String::toEnum<ShareLimitAction>(
|
||||
query.value(DB_COLUMN_SHARE_LIMIT_ACTION.name).toString(), ShareLimitAction::Default);
|
||||
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.sslParameters =
|
||||
{
|
||||
.certificate = QSslCertificate(query.value(DB_COLUMN_SSL_CERTIFICATE.name).toByteArray()),
|
||||
.privateKey = Utils::SSLKey::load(query.value(DB_COLUMN_SSL_PRIVATE_KEY.name).toByteArray()),
|
||||
.dhParams = query.value(DB_COLUMN_SSL_DH_PARAMS.name).toByteArray()
|
||||
};
|
||||
|
||||
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();
|
||||
const auto *pref = Preferences::instance();
|
||||
const int bdecodeDepthLimit = pref->getBdecodeDepthLimit();
|
||||
const int bdecodeTokenLimit = pref->getBdecodeTokenLimit();
|
||||
|
||||
lt::error_code ec;
|
||||
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
|
||||
if (ec)
|
||||
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
|
||||
|
||||
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
|
||||
|
||||
p = lt::read_resume_data(resumeDataRoot, ec);
|
||||
if (ec)
|
||||
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
|
||||
|
||||
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray()
|
||||
; !bencodedMetadata.isEmpty())
|
||||
{
|
||||
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec
|
||||
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
|
||||
if (ec)
|
||||
return nonstd::make_unexpected(tr("Cannot parse torrent info: %1").arg(QString::fromStdString(ec.message())));
|
||||
|
||||
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
|
||||
if (ec)
|
||||
return nonstd::make_unexpected(tr("Cannot parse torrent info: %1").arg(QString::fromStdString(ec.message())));
|
||||
}
|
||||
|
||||
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
|
||||
.toString().toStdString();
|
||||
if (p.save_path.empty())
|
||||
return nonstd::make_unexpected(tr("Corrupted resume data: %1").arg(tr("save_path is invalid")));
|
||||
|
||||
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::Worker::Worker(const Path &dbPath, QReadWriteLock &dbLock, QObject *parent)
|
||||
: QThread(parent)
|
||||
, m_path {dbPath}
|
||||
|
@ -747,7 +779,7 @@ void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id)
|
|||
addJob(std::make_unique<RemoveJob>(id));
|
||||
}
|
||||
|
||||
void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue)
|
||||
void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QList<TorrentID> &queue)
|
||||
{
|
||||
addJob(std::make_unique<StoreQueueJob>(queue));
|
||||
}
|
||||
|
@ -797,7 +829,7 @@ namespace
|
|||
}
|
||||
}
|
||||
|
||||
QVector<Column> columns {
|
||||
QList<Column> columns {
|
||||
DB_COLUMN_TORRENT_ID,
|
||||
DB_COLUMN_NAME,
|
||||
DB_COLUMN_CATEGORY,
|
||||
|
@ -929,7 +961,7 @@ namespace
|
|||
}
|
||||
}
|
||||
|
||||
StoreQueueJob::StoreQueueJob(const QVector<TorrentID> &queue)
|
||||
StoreQueueJob::StoreQueueJob(const QList<TorrentID> &queue)
|
||||
: m_queue {queue}
|
||||
{
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2021-2022 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2021-2025 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,10 +31,9 @@
|
|||
#include <QReadWriteLock>
|
||||
|
||||
#include "base/pathfwd.h"
|
||||
#include "base/utils/thread.h"
|
||||
#include "resumedatastorage.h"
|
||||
|
||||
class QThread;
|
||||
class QSqlQuery;
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
|
@ -47,12 +46,12 @@ namespace BitTorrent
|
|||
explicit DBResumeDataStorage(const Path &dbPath, QObject *parent = nullptr);
|
||||
~DBResumeDataStorage() override;
|
||||
|
||||
QVector<TorrentID> registeredTorrents() const override;
|
||||
QList<TorrentID> registeredTorrents() const override;
|
||||
LoadResumeDataResult load(const TorrentID &id) const override;
|
||||
|
||||
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const override;
|
||||
void remove(const TorrentID &id) const override;
|
||||
void storeQueue(const QVector<TorrentID> &queue) const override;
|
||||
void storeQueue(const QList<TorrentID> &queue) const override;
|
||||
|
||||
private:
|
||||
void doLoadAll() const override;
|
||||
|
@ -60,8 +59,7 @@ namespace BitTorrent
|
|||
void createDB() const;
|
||||
void updateDB(int fromVersion) const;
|
||||
void enableWALMode() const;
|
||||
|
||||
Utils::Thread::UniquePtr m_ioThread;
|
||||
LoadResumeDataResult parseQueryResultRow(const QSqlQuery &query) const;
|
||||
|
||||
class Worker;
|
||||
Worker *m_asyncWorker = nullptr;
|
||||
|
|
|
@ -133,7 +133,7 @@ int FilterParserThread::parseDATFilterFile()
|
|||
return ruleCount;
|
||||
}
|
||||
|
||||
std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QVector
|
||||
std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QList
|
||||
qint64 bytesRead = 0;
|
||||
int offset = 0;
|
||||
int start = 0;
|
||||
|
@ -297,7 +297,7 @@ int FilterParserThread::parseP2PFilterFile()
|
|||
return ruleCount;
|
||||
}
|
||||
|
||||
std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QVector
|
||||
std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QList
|
||||
qint64 bytesRead = 0;
|
||||
int offset = 0;
|
||||
int start = 0;
|
||||
|
|
|
@ -29,6 +29,9 @@
|
|||
#include "infohash.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
#include "base/global.h"
|
||||
|
||||
const int TorrentIDTypeId = qRegisterMetaType<BitTorrent::TorrentID>();
|
||||
|
||||
|
@ -86,6 +89,28 @@ BitTorrent::TorrentID BitTorrent::InfoHash::toTorrentID() const
|
|||
#endif
|
||||
}
|
||||
|
||||
QString BitTorrent::InfoHash::toString() const
|
||||
{
|
||||
// Returns a string that is suitable for logging purpose
|
||||
|
||||
QString ret;
|
||||
ret.reserve(40 + 64 + 2); // v1 hash length + v2 hash length + comma
|
||||
|
||||
const SHA1Hash v1Hash = v1();
|
||||
const bool v1IsValid = v1Hash.isValid();
|
||||
if (v1IsValid)
|
||||
ret += v1Hash.toString();
|
||||
|
||||
if (const SHA256Hash v2Hash = v2(); v2Hash.isValid())
|
||||
{
|
||||
if (v1IsValid)
|
||||
ret += u", ";
|
||||
ret += v2Hash.toString();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
BitTorrent::InfoHash::operator WrappedType() const
|
||||
{
|
||||
return m_nativeHash;
|
||||
|
|
|
@ -36,6 +36,8 @@
|
|||
|
||||
#include "base/digest32.h"
|
||||
|
||||
class QString;
|
||||
|
||||
using SHA1Hash = Digest32<160>;
|
||||
using SHA256Hash = Digest32<256>;
|
||||
|
||||
|
@ -79,6 +81,8 @@ namespace BitTorrent
|
|||
SHA256Hash v2() const;
|
||||
TorrentID toTorrentID() const;
|
||||
|
||||
QString toString() const;
|
||||
|
||||
operator WrappedType() const;
|
||||
|
||||
private:
|
||||
|
|
|
@ -39,7 +39,11 @@ PeerAddress PeerAddress::parse(const QStringView address)
|
|||
if (address.startsWith(u'[') && address.contains(u"]:"))
|
||||
{ // IPv6
|
||||
ipPort = address.split(u"]:");
|
||||
ipPort[0] = ipPort[0].mid(1); // chop '['
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
ipPort[0].slice(1); // chop '['
|
||||
#else
|
||||
ipPort[0] = ipPort[0].sliced(1); // chop '['
|
||||
#endif
|
||||
}
|
||||
else if (address.contains(u':'))
|
||||
{ // IPv4
|
||||
|
|
|
@ -367,6 +367,10 @@ void PeerInfo::determineFlags()
|
|||
if (useUTPSocket())
|
||||
updateFlags(u'P', C_UTP);
|
||||
|
||||
// h = Peer is using NAT hole punching
|
||||
if (isHolepunched())
|
||||
updateFlags(u'h', tr("Peer is using NAT hole punching"));
|
||||
|
||||
m_flags.chop(1);
|
||||
m_flagsDescription.chop(1);
|
||||
}
|
||||
|
|
|
@ -30,12 +30,12 @@
|
|||
|
||||
#include <utility>
|
||||
|
||||
#include <QList>
|
||||
#include <QMetaObject>
|
||||
#include <QMutexLocker>
|
||||
#include <QThread>
|
||||
#include <QVector>
|
||||
|
||||
const int TORRENTIDLIST_TYPEID = qRegisterMetaType<QVector<BitTorrent::TorrentID>>();
|
||||
const int TORRENTIDLIST_TYPEID = qRegisterMetaType<QList<BitTorrent::TorrentID>>();
|
||||
|
||||
BitTorrent::ResumeDataStorage::ResumeDataStorage(const Path &path, QObject *parent)
|
||||
: QObject(parent)
|
||||
|
@ -56,6 +56,7 @@ void BitTorrent::ResumeDataStorage::loadAll() const
|
|||
{
|
||||
doLoadAll();
|
||||
});
|
||||
loadingThread->setObjectName("ResumeDataStorage::loadAll loadingThread");
|
||||
connect(loadingThread, &QThread::finished, loadingThread, &QObject::deleteLater);
|
||||
loadingThread->start();
|
||||
}
|
||||
|
|
|
@ -58,17 +58,17 @@ namespace BitTorrent
|
|||
|
||||
Path path() const;
|
||||
|
||||
virtual QVector<TorrentID> registeredTorrents() const = 0;
|
||||
virtual QList<TorrentID> registeredTorrents() const = 0;
|
||||
virtual LoadResumeDataResult load(const TorrentID &id) const = 0;
|
||||
virtual void store(const TorrentID &id, const LoadTorrentParams &resumeData) const = 0;
|
||||
virtual void remove(const TorrentID &id) const = 0;
|
||||
virtual void storeQueue(const QVector<TorrentID> &queue) const = 0;
|
||||
virtual void storeQueue(const QList<TorrentID> &queue) const = 0;
|
||||
|
||||
void loadAll() const;
|
||||
QList<LoadedResumeData> fetchLoadedResumeData() const;
|
||||
|
||||
signals:
|
||||
void loadStarted(const QVector<BitTorrent::TorrentID> &torrents);
|
||||
void loadStarted(const QList<BitTorrent::TorrentID> &torrents);
|
||||
void loadFinished();
|
||||
|
||||
protected:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -34,20 +34,16 @@
|
|||
|
||||
#include "base/pathfwd.h"
|
||||
#include "base/tagset.h"
|
||||
#include "addtorrenterror.h"
|
||||
#include "addtorrentparams.h"
|
||||
#include "categoryoptions.h"
|
||||
#include "sharelimitaction.h"
|
||||
#include "torrentcontentremoveoption.h"
|
||||
#include "trackerentry.h"
|
||||
#include "trackerentrystatus.h"
|
||||
|
||||
class QString;
|
||||
|
||||
enum DeleteOption
|
||||
{
|
||||
DeleteTorrent,
|
||||
DeleteTorrentAndFiles
|
||||
};
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class InfoHash;
|
||||
|
@ -58,6 +54,12 @@ namespace BitTorrent
|
|||
struct CacheStatus;
|
||||
struct SessionStatus;
|
||||
|
||||
enum class TorrentRemoveOption
|
||||
{
|
||||
KeepContent,
|
||||
RemoveContent
|
||||
};
|
||||
|
||||
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
|
||||
// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files.
|
||||
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
|
||||
|
@ -91,7 +93,8 @@ namespace BitTorrent
|
|||
{
|
||||
Default = 0,
|
||||
MMap = 1,
|
||||
Posix = 2
|
||||
Posix = 2,
|
||||
SimplePreadPwrite = 3
|
||||
};
|
||||
Q_ENUM_NS(DiskIOType)
|
||||
|
||||
|
@ -235,6 +238,12 @@ namespace BitTorrent
|
|||
virtual Path finishedTorrentExportDirectory() const = 0;
|
||||
virtual void setFinishedTorrentExportDirectory(const Path &path) = 0;
|
||||
|
||||
virtual bool isAddTrackersFromURLEnabled() const = 0;
|
||||
virtual void setAddTrackersFromURLEnabled(bool enabled) = 0;
|
||||
virtual QString additionalTrackersURL() const = 0;
|
||||
virtual void setAdditionalTrackersURL(const QString &url) = 0;
|
||||
virtual QString additionalTrackersFromURL() const = 0;
|
||||
|
||||
virtual int globalDownloadSpeedLimit() const = 0;
|
||||
virtual void setGlobalDownloadSpeedLimit(int limit) = 0;
|
||||
virtual int globalUploadSpeedLimit() const = 0;
|
||||
|
@ -256,6 +265,8 @@ namespace BitTorrent
|
|||
virtual void setPerformanceWarningEnabled(bool enable) = 0;
|
||||
virtual int saveResumeDataInterval() const = 0;
|
||||
virtual void setSaveResumeDataInterval(int value) = 0;
|
||||
virtual std::chrono::minutes saveStatisticsInterval() const = 0;
|
||||
virtual void setSaveStatisticsInterval(std::chrono::minutes value) = 0;
|
||||
virtual int shutdownTimeout() const = 0;
|
||||
virtual void setShutdownTimeout(int value) = 0;
|
||||
virtual int port() const = 0;
|
||||
|
@ -382,6 +393,8 @@ namespace BitTorrent
|
|||
virtual void setIncludeOverheadInLimits(bool include) = 0;
|
||||
virtual QString announceIP() const = 0;
|
||||
virtual void setAnnounceIP(const QString &ip) = 0;
|
||||
virtual int announcePort() const = 0;
|
||||
virtual void setAnnouncePort(int port) = 0;
|
||||
virtual int maxConcurrentHTTPAnnounces() const = 0;
|
||||
virtual void setMaxConcurrentHTTPAnnounces(int value) = 0;
|
||||
virtual bool isReannounceWhenAddressChangedEnabled() const = 0;
|
||||
|
@ -409,6 +422,8 @@ namespace BitTorrent
|
|||
virtual void setUTPRateLimited(bool limited) = 0;
|
||||
virtual MixedModeAlgorithm utpMixedMode() const = 0;
|
||||
virtual void setUtpMixedMode(MixedModeAlgorithm mode) = 0;
|
||||
virtual int hostnameCacheTTL() const = 0;
|
||||
virtual void setHostnameCacheTTL(int value) = 0;
|
||||
virtual bool isIDNSupportEnabled() const = 0;
|
||||
virtual void setIDNSupportEnabled(bool enabled) = 0;
|
||||
virtual bool multiConnectionsPerIpEnabled() const = 0;
|
||||
|
@ -425,7 +440,7 @@ namespace BitTorrent
|
|||
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;
|
||||
virtual void applyFilenameFilter(const PathList &files, QList<BitTorrent::DownloadPriority> &priorities) = 0;
|
||||
virtual QStringList bannedIPs() const = 0;
|
||||
virtual void setBannedIPs(const QStringList &newList) = 0;
|
||||
virtual ResumeDataStorageType resumeDataStorageType() const = 0;
|
||||
|
@ -434,6 +449,8 @@ namespace BitTorrent
|
|||
virtual void setMergeTrackersEnabled(bool enabled) = 0;
|
||||
virtual bool isStartPaused() const = 0;
|
||||
virtual void setStartPaused(bool value) = 0;
|
||||
virtual TorrentContentRemoveOption torrentContentRemoveOption() const = 0;
|
||||
virtual void setTorrentContentRemoveOption(TorrentContentRemoveOption option) = 0;
|
||||
|
||||
virtual bool isRestored() const = 0;
|
||||
|
||||
|
@ -443,7 +460,7 @@ namespace BitTorrent
|
|||
|
||||
virtual Torrent *getTorrent(const TorrentID &id) const = 0;
|
||||
virtual Torrent *findTorrent(const InfoHash &infoHash) const = 0;
|
||||
virtual QVector<Torrent *> torrents() const = 0;
|
||||
virtual QList<Torrent *> torrents() const = 0;
|
||||
virtual qsizetype torrentsCount() const = 0;
|
||||
virtual const SessionStatus &status() const = 0;
|
||||
virtual const CacheStatus &cacheStatus() const = 0;
|
||||
|
@ -453,18 +470,23 @@ namespace BitTorrent
|
|||
|
||||
virtual bool isKnownTorrent(const InfoHash &infoHash) const = 0;
|
||||
virtual bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) = 0;
|
||||
virtual bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteOption::DeleteTorrent) = 0;
|
||||
virtual bool removeTorrent(const TorrentID &id, TorrentRemoveOption deleteOption = TorrentRemoveOption::KeepContent) = 0;
|
||||
virtual bool downloadMetadata(const TorrentDescriptor &torrentDescr) = 0;
|
||||
virtual bool cancelDownloadMetadata(const TorrentID &id) = 0;
|
||||
|
||||
virtual void increaseTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
|
||||
virtual void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
|
||||
virtual void topTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
|
||||
virtual void bottomTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
|
||||
virtual void increaseTorrentsQueuePos(const QList<TorrentID> &ids) = 0;
|
||||
virtual void decreaseTorrentsQueuePos(const QList<TorrentID> &ids) = 0;
|
||||
virtual void topTorrentsQueuePos(const QList<TorrentID> &ids) = 0;
|
||||
virtual void bottomTorrentsQueuePos(const QList<TorrentID> &ids) = 0;
|
||||
|
||||
virtual QString lastExternalIPv4Address() const = 0;
|
||||
virtual QString lastExternalIPv6Address() const = 0;
|
||||
|
||||
virtual qint64 freeDiskSpace() const = 0;
|
||||
|
||||
signals:
|
||||
void startupProgressUpdated(int progress);
|
||||
void addTorrentFailed(const InfoHash &infoHash, const QString &reason);
|
||||
void addTorrentFailed(const InfoHash &infoHash, const AddTorrentError &reason);
|
||||
void allTorrentsFinished();
|
||||
void categoryAdded(const QString &categoryName);
|
||||
void categoryRemoved(const QString &categoryName);
|
||||
|
@ -491,16 +513,17 @@ namespace BitTorrent
|
|||
void torrentStarted(Torrent *torrent);
|
||||
void torrentSavePathChanged(Torrent *torrent);
|
||||
void torrentSavingModeChanged(Torrent *torrent);
|
||||
void torrentsLoaded(const QVector<Torrent *> &torrents);
|
||||
void torrentsUpdated(const QVector<Torrent *> &torrents);
|
||||
void torrentsLoaded(const QList<Torrent *> &torrents);
|
||||
void torrentsUpdated(const QList<Torrent *> &torrents);
|
||||
void torrentTagAdded(Torrent *torrent, const Tag &tag);
|
||||
void torrentTagRemoved(Torrent *torrent, const Tag &tag);
|
||||
void trackerError(Torrent *torrent, const QString &tracker);
|
||||
void trackersAdded(Torrent *torrent, const QVector<TrackerEntry> &trackers);
|
||||
void trackersAdded(Torrent *torrent, const QList<TrackerEntry> &trackers);
|
||||
void trackersChanged(Torrent *torrent);
|
||||
void trackersRemoved(Torrent *torrent, const QStringList &trackers);
|
||||
void trackerSuccess(Torrent *torrent, const QString &tracker);
|
||||
void trackerWarning(Torrent *torrent, const QString &tracker);
|
||||
void trackerEntryStatusesUpdated(Torrent *torrent, const QHash<QString, TrackerEntryStatus> &updatedTrackers);
|
||||
void freeDiskSpaceChecked(qint64 result);
|
||||
};
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2025 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,6 +29,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
|
@ -37,13 +38,14 @@
|
|||
#include <libtorrent/torrent_handle.hpp>
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QDateTime>
|
||||
#include <QElapsedTimer>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
#include <QVector>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include "base/path.h"
|
||||
#include "base/settingvalue.h"
|
||||
|
@ -54,17 +56,15 @@
|
|||
#include "session.h"
|
||||
#include "sessionstatus.h"
|
||||
#include "torrentinfo.h"
|
||||
#include "trackerentrystatus.h"
|
||||
|
||||
class QString;
|
||||
class QThread;
|
||||
class QThreadPool;
|
||||
class QTimer;
|
||||
class QUrl;
|
||||
|
||||
class BandwidthScheduler;
|
||||
class FileSearcher;
|
||||
class FilterParserThread;
|
||||
class FreeDiskSpaceChecker;
|
||||
class NativeSessionExtension;
|
||||
|
||||
namespace BitTorrent
|
||||
|
@ -75,6 +75,7 @@ namespace BitTorrent
|
|||
class InfoHash;
|
||||
class ResumeDataStorage;
|
||||
class Torrent;
|
||||
class TorrentContentRemover;
|
||||
class TorrentDescriptor;
|
||||
class TorrentImpl;
|
||||
class Tracker;
|
||||
|
@ -233,6 +234,8 @@ namespace BitTorrent
|
|||
void setPerformanceWarningEnabled(bool enable) override;
|
||||
int saveResumeDataInterval() const override;
|
||||
void setSaveResumeDataInterval(int value) override;
|
||||
std::chrono::minutes saveStatisticsInterval() const override;
|
||||
void setSaveStatisticsInterval(std::chrono::minutes value) override;
|
||||
int shutdownTimeout() const override;
|
||||
void setShutdownTimeout(int value) override;
|
||||
int port() const override;
|
||||
|
@ -359,6 +362,8 @@ namespace BitTorrent
|
|||
void setIncludeOverheadInLimits(bool include) override;
|
||||
QString announceIP() const override;
|
||||
void setAnnounceIP(const QString &ip) override;
|
||||
int announcePort() const override;
|
||||
void setAnnouncePort(int port) override;
|
||||
int maxConcurrentHTTPAnnounces() const override;
|
||||
void setMaxConcurrentHTTPAnnounces(int value) override;
|
||||
bool isReannounceWhenAddressChangedEnabled() const override;
|
||||
|
@ -386,6 +391,8 @@ namespace BitTorrent
|
|||
void setUTPRateLimited(bool limited) override;
|
||||
MixedModeAlgorithm utpMixedMode() const override;
|
||||
void setUtpMixedMode(MixedModeAlgorithm mode) override;
|
||||
int hostnameCacheTTL() const override;
|
||||
void setHostnameCacheTTL(int value) override;
|
||||
bool isIDNSupportEnabled() const override;
|
||||
void setIDNSupportEnabled(bool enabled) override;
|
||||
bool multiConnectionsPerIpEnabled() const override;
|
||||
|
@ -402,7 +409,7 @@ namespace BitTorrent
|
|||
void setExcludedFileNamesEnabled(bool enabled) override;
|
||||
QStringList excludedFileNames() const override;
|
||||
void setExcludedFileNames(const QStringList &excludedFileNames) override;
|
||||
bool isFilenameExcluded(const QString &fileName) const override;
|
||||
void applyFilenameFilter(const PathList &files, QList<BitTorrent::DownloadPriority> &priorities) override;
|
||||
QStringList bannedIPs() const override;
|
||||
void setBannedIPs(const QStringList &newList) override;
|
||||
ResumeDataStorageType resumeDataStorageType() const override;
|
||||
|
@ -411,6 +418,8 @@ namespace BitTorrent
|
|||
void setMergeTrackersEnabled(bool enabled) override;
|
||||
bool isStartPaused() const override;
|
||||
void setStartPaused(bool value) override;
|
||||
TorrentContentRemoveOption torrentContentRemoveOption() const override;
|
||||
void setTorrentContentRemoveOption(TorrentContentRemoveOption option) override;
|
||||
|
||||
bool isRestored() const override;
|
||||
|
||||
|
@ -420,7 +429,7 @@ namespace BitTorrent
|
|||
|
||||
Torrent *getTorrent(const TorrentID &id) const override;
|
||||
Torrent *findTorrent(const InfoHash &infoHash) const override;
|
||||
QVector<Torrent *> torrents() const override;
|
||||
QList<Torrent *> torrents() const override;
|
||||
qsizetype torrentsCount() const override;
|
||||
const SessionStatus &status() const override;
|
||||
const CacheStatus &cacheStatus() const override;
|
||||
|
@ -430,14 +439,19 @@ namespace BitTorrent
|
|||
|
||||
bool isKnownTorrent(const InfoHash &infoHash) const override;
|
||||
bool addTorrent(const TorrentDescriptor &torrentDescr, const AddTorrentParams ¶ms = {}) override;
|
||||
bool deleteTorrent(const TorrentID &id, DeleteOption deleteOption = DeleteTorrent) override;
|
||||
bool removeTorrent(const TorrentID &id, TorrentRemoveOption deleteOption = TorrentRemoveOption::KeepContent) override;
|
||||
bool downloadMetadata(const TorrentDescriptor &torrentDescr) override;
|
||||
bool cancelDownloadMetadata(const TorrentID &id) override;
|
||||
|
||||
void increaseTorrentsQueuePos(const QVector<TorrentID> &ids) override;
|
||||
void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids) override;
|
||||
void topTorrentsQueuePos(const QVector<TorrentID> &ids) override;
|
||||
void bottomTorrentsQueuePos(const QVector<TorrentID> &ids) override;
|
||||
void increaseTorrentsQueuePos(const QList<TorrentID> &ids) override;
|
||||
void decreaseTorrentsQueuePos(const QList<TorrentID> &ids) override;
|
||||
void topTorrentsQueuePos(const QList<TorrentID> &ids) override;
|
||||
void bottomTorrentsQueuePos(const QList<TorrentID> &ids) override;
|
||||
|
||||
QString lastExternalIPv4Address() const override;
|
||||
QString lastExternalIPv6Address() const override;
|
||||
|
||||
qint64 freeDiskSpace() const override;
|
||||
|
||||
// Torrent interface
|
||||
void handleTorrentResumeDataRequested(const TorrentImpl *torrent);
|
||||
|
@ -453,11 +467,11 @@ namespace BitTorrent
|
|||
void handleTorrentStarted(TorrentImpl *torrent);
|
||||
void handleTorrentChecked(TorrentImpl *torrent);
|
||||
void handleTorrentFinished(TorrentImpl *torrent);
|
||||
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QVector<TrackerEntry> &newTrackers);
|
||||
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QList<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 handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QList<QUrl> &newUrlSeeds);
|
||||
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QList<QUrl> &urlSeeds);
|
||||
void handleTorrentResumeDataReady(TorrentImpl *torrent, const LoadTorrentParams &data);
|
||||
void handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash);
|
||||
void handleTorrentStorageMovingStateChanged(TorrentImpl *torrent);
|
||||
|
@ -478,7 +492,17 @@ namespace BitTorrent
|
|||
QMetaObject::invokeMethod(this, std::forward<Func>(func), Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void invokeAsync(std::function<void ()> func);
|
||||
template <typename Func>
|
||||
void invokeAsync(Func &&func)
|
||||
{
|
||||
m_asyncWorker->start(std::forward<Func>(func));
|
||||
}
|
||||
|
||||
bool isAddTrackersFromURLEnabled() const override;
|
||||
void setAddTrackersFromURLEnabled(bool enabled) override;
|
||||
QString additionalTrackersURL() const override;
|
||||
void setAdditionalTrackersURL(const QString &url) override;
|
||||
QString additionalTrackersFromURL() const override;
|
||||
|
||||
signals:
|
||||
void addTorrentAlertsReceived(qsizetype count);
|
||||
|
@ -487,11 +511,11 @@ namespace BitTorrent
|
|||
void configureDeferred();
|
||||
void readAlerts();
|
||||
void enqueueRefresh();
|
||||
void processShareLimits();
|
||||
void generateResumeData();
|
||||
void handleIPFilterParsed(int ruleCount);
|
||||
void handleIPFilterError();
|
||||
void fileSearchFinished(const TorrentID &id, const Path &savePath, const PathList &fileNames);
|
||||
void torrentContentRemovingFinished(const QString &torrentName, const QString &errorMessage);
|
||||
|
||||
private:
|
||||
struct ResumeSessionContext;
|
||||
|
@ -507,8 +531,9 @@ namespace BitTorrent
|
|||
struct RemovingTorrentData
|
||||
{
|
||||
QString name;
|
||||
Path pathToRemove;
|
||||
DeleteOption deleteOption {};
|
||||
Path contentStoragePath;
|
||||
PathList fileNames;
|
||||
TorrentRemoveOption removeOption {};
|
||||
};
|
||||
|
||||
explicit SessionImpl(QObject *parent = nullptr);
|
||||
|
@ -535,7 +560,7 @@ namespace BitTorrent
|
|||
void populateAdditionalTrackers();
|
||||
void enableIPFilter();
|
||||
void disableIPFilter();
|
||||
void processTrackerStatuses();
|
||||
void processTorrentShareLimits(TorrentImpl *torrent);
|
||||
void populateExcludedFileNamesRegExpList();
|
||||
void prepareStartup();
|
||||
void handleLoadedResumeData(ResumeSessionContext *context);
|
||||
|
@ -584,10 +609,13 @@ namespace BitTorrent
|
|||
void saveTorrentsQueue();
|
||||
void removeTorrentsQueue();
|
||||
|
||||
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
|
||||
void populateAdditionalTrackersFromURL();
|
||||
|
||||
void fetchPendingAlerts(lt::time_duration time = lt::time_duration::zero());
|
||||
|
||||
void moveTorrentStorage(const MoveStorageJob &job) const;
|
||||
void handleMoveTorrentStorageJobFinished(const Path &newPath);
|
||||
void processPendingFinishedTorrents();
|
||||
|
||||
void loadCategories();
|
||||
void storeCategories() const;
|
||||
|
@ -597,15 +625,12 @@ namespace BitTorrent
|
|||
void saveStatistics() const;
|
||||
void loadStatistics();
|
||||
|
||||
void updateTrackerEntryStatuses(lt::torrent_handle torrentHandle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers);
|
||||
void updateTrackerEntryStatuses(lt::torrent_handle torrentHandle);
|
||||
|
||||
// BitTorrent
|
||||
lt::session *m_nativeSession = nullptr;
|
||||
NativeSessionExtension *m_nativeSessionExtension = nullptr;
|
||||
void handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError = {});
|
||||
|
||||
bool m_deferredConfigureScheduled = false;
|
||||
bool m_IPFilteringConfigured = false;
|
||||
mutable bool m_listenInterfaceConfigured = false;
|
||||
void setAdditionalTrackersFromURL(const QString &trackers);
|
||||
void updateTrackersFromURL();
|
||||
|
||||
CachedSettingValue<QString> m_DHTBootstrapNodes;
|
||||
CachedSettingValue<bool> m_isDHTEnabled;
|
||||
|
@ -652,6 +677,7 @@ namespace BitTorrent
|
|||
CachedSettingValue<bool> m_ignoreLimitsOnLAN;
|
||||
CachedSettingValue<bool> m_includeOverheadInLimits;
|
||||
CachedSettingValue<QString> m_announceIP;
|
||||
CachedSettingValue<int> m_announcePort;
|
||||
CachedSettingValue<int> m_maxConcurrentHTTPAnnounces;
|
||||
CachedSettingValue<bool> m_isReannounceWhenAddressChangedEnabled;
|
||||
CachedSettingValue<int> m_stopTrackerTimeout;
|
||||
|
@ -662,6 +688,7 @@ namespace BitTorrent
|
|||
CachedSettingValue<BTProtocol> m_btProtocol;
|
||||
CachedSettingValue<bool> m_isUTPRateLimited;
|
||||
CachedSettingValue<MixedModeAlgorithm> m_utpMixedMode;
|
||||
CachedSettingValue<int> m_hostnameCacheTTL;
|
||||
CachedSettingValue<bool> m_IDNSupportEnabled;
|
||||
CachedSettingValue<bool> m_multiConnectionsPerIpEnabled;
|
||||
CachedSettingValue<bool> m_validateHTTPSTrackerCertificate;
|
||||
|
@ -669,6 +696,8 @@ namespace BitTorrent
|
|||
CachedSettingValue<bool> m_blockPeersOnPrivilegedPorts;
|
||||
CachedSettingValue<bool> m_isAddTrackersEnabled;
|
||||
CachedSettingValue<QString> m_additionalTrackers;
|
||||
CachedSettingValue<bool> m_isAddTrackersFromURLEnabled;
|
||||
CachedSettingValue<QString> m_additionalTrackersURL;
|
||||
CachedSettingValue<qreal> m_globalMaxRatio;
|
||||
CachedSettingValue<int> m_globalMaxSeedingMinutes;
|
||||
CachedSettingValue<int> m_globalMaxInactiveSeedingMinutes;
|
||||
|
@ -690,6 +719,7 @@ namespace BitTorrent
|
|||
CachedSettingValue<bool> m_isBandwidthSchedulerEnabled;
|
||||
CachedSettingValue<bool> m_isPerformanceWarningEnabled;
|
||||
CachedSettingValue<int> m_saveResumeDataInterval;
|
||||
CachedSettingValue<int> m_saveStatisticsInterval;
|
||||
CachedSettingValue<int> m_shutdownTimeout;
|
||||
CachedSettingValue<int> m_port;
|
||||
CachedSettingValue<bool> m_sslEnabled;
|
||||
|
@ -731,8 +761,19 @@ namespace BitTorrent
|
|||
CachedSettingValue<int> m_I2POutboundQuantity;
|
||||
CachedSettingValue<int> m_I2PInboundLength;
|
||||
CachedSettingValue<int> m_I2POutboundLength;
|
||||
CachedSettingValue<TorrentContentRemoveOption> m_torrentContentRemoveOption;
|
||||
SettingValue<bool> m_startPaused;
|
||||
|
||||
lt::session *m_nativeSession = nullptr;
|
||||
NativeSessionExtension *m_nativeSessionExtension = nullptr;
|
||||
|
||||
bool m_deferredConfigureScheduled = false;
|
||||
bool m_IPFilteringConfigured = false;
|
||||
mutable bool m_listenInterfaceConfigured = false;
|
||||
|
||||
QString m_additionalTrackersFromURL;
|
||||
QTimer *m_updateTrackersFromURLTimer = nullptr;
|
||||
|
||||
bool m_isRestored = false;
|
||||
bool m_isPaused = isStartPaused();
|
||||
|
||||
|
@ -742,8 +783,9 @@ namespace BitTorrent
|
|||
const bool m_wasPexEnabled = m_isPeXEnabled;
|
||||
|
||||
int m_numResumeData = 0;
|
||||
QVector<TrackerEntry> m_additionalTrackerEntries;
|
||||
QVector<QRegularExpression> m_excludedFileNamesRegExpList;
|
||||
QList<TrackerEntry> m_additionalTrackerEntries;
|
||||
QList<TrackerEntry> m_additionalTrackerEntriesFromURL;
|
||||
QList<QRegularExpression> m_excludedFileNamesRegExpList;
|
||||
|
||||
// Statistics
|
||||
mutable QElapsedTimer m_statisticsLastUpdateTimer;
|
||||
|
@ -766,6 +808,7 @@ namespace BitTorrent
|
|||
QThreadPool *m_asyncWorker = nullptr;
|
||||
ResumeDataStorage *m_resumeDataStorage = nullptr;
|
||||
FileSearcher *m_fileSearcher = nullptr;
|
||||
TorrentContentRemover *m_torrentContentRemover = nullptr;
|
||||
|
||||
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
|
||||
|
||||
|
@ -777,12 +820,14 @@ namespace BitTorrent
|
|||
QMap<QString, CategoryOptions> m_categories;
|
||||
TagSet m_tags;
|
||||
|
||||
std::vector<lt::alert *> m_alerts; // make it a class variable so it can preserve its allocated `capacity`
|
||||
qsizetype m_receivedAddTorrentAlertsCount = 0;
|
||||
QList<Torrent *> m_loadedTorrents;
|
||||
|
||||
// This field holds amounts of peers reported by trackers in their responses to announces
|
||||
// (torrent.tracker_name.tracker_local_endpoint.protocol_version.num_peers)
|
||||
QHash<lt::torrent_handle, QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>>> m_updatedTrackerStatuses;
|
||||
QMutex m_updatedTrackerStatusesMutex;
|
||||
|
||||
// I/O errored torrents
|
||||
QSet<TorrentID> m_recentErroredTorrents;
|
||||
|
@ -796,7 +841,8 @@ namespace BitTorrent
|
|||
|
||||
QList<MoveStorageJob> m_moveStorageQueue;
|
||||
|
||||
QString m_lastExternalIP;
|
||||
QString m_lastExternalIPv4Address;
|
||||
QString m_lastExternalIPv6Address;
|
||||
|
||||
bool m_needUpgradeDownloadPath = false;
|
||||
|
||||
|
@ -806,8 +852,13 @@ namespace BitTorrent
|
|||
bool m_isPortMappingEnabled = false;
|
||||
QHash<quint16, std::vector<lt::port_mapping_t>> m_mappedPorts;
|
||||
|
||||
QTimer *m_wakeupCheckTimer = nullptr;
|
||||
QDateTime m_wakeupCheckTimestamp;
|
||||
QElapsedTimer m_wakeupCheckTimestamp;
|
||||
|
||||
QList<TorrentImpl *> m_pendingFinishedTorrents;
|
||||
|
||||
FreeDiskSpaceChecker *m_freeDiskSpaceChecker = nullptr;
|
||||
QTimer *m_freeDiskSpaceCheckingTimer = nullptr;
|
||||
qint64 m_freeDiskSpace = -1;
|
||||
|
||||
friend void Session::initInstance();
|
||||
friend void Session::freeInstance();
|
||||
|
|
|
@ -215,7 +215,15 @@ namespace BitTorrent
|
|||
virtual int piecesCount() const = 0;
|
||||
virtual int piecesHave() const = 0;
|
||||
virtual qreal progress() const = 0;
|
||||
|
||||
virtual QDateTime addedTime() const = 0;
|
||||
virtual QDateTime completedTime() const = 0;
|
||||
virtual QDateTime lastSeenComplete() const = 0;
|
||||
virtual qlonglong activeTime() const = 0;
|
||||
virtual qlonglong finishedTime() const = 0;
|
||||
virtual qlonglong timeSinceUpload() const = 0;
|
||||
virtual qlonglong timeSinceDownload() const = 0;
|
||||
virtual qlonglong timeSinceActivity() const = 0;
|
||||
|
||||
// Share limits
|
||||
virtual qreal ratioLimit() const = 0;
|
||||
|
@ -228,6 +236,7 @@ namespace BitTorrent
|
|||
virtual void setShareLimitAction(ShareLimitAction action) = 0;
|
||||
|
||||
virtual PathList filePaths() const = 0;
|
||||
virtual PathList actualFilePaths() const = 0;
|
||||
|
||||
virtual TorrentInfo info() const = 0;
|
||||
virtual bool isFinished() const = 0;
|
||||
|
@ -248,13 +257,11 @@ namespace BitTorrent
|
|||
virtual bool hasMissingFiles() const = 0;
|
||||
virtual bool hasError() const = 0;
|
||||
virtual int queuePosition() const = 0;
|
||||
virtual QVector<TrackerEntryStatus> trackers() const = 0;
|
||||
virtual QVector<QUrl> urlSeeds() const = 0;
|
||||
virtual QList<TrackerEntryStatus> trackers() const = 0;
|
||||
virtual QList<QUrl> urlSeeds() const = 0;
|
||||
virtual QString error() const = 0;
|
||||
virtual qlonglong totalDownload() const = 0;
|
||||
virtual qlonglong totalUpload() const = 0;
|
||||
virtual qlonglong activeTime() const = 0;
|
||||
virtual qlonglong finishedTime() const = 0;
|
||||
virtual qlonglong eta() const = 0;
|
||||
virtual int seedsCount() const = 0;
|
||||
virtual int peersCount() const = 0;
|
||||
|
@ -262,21 +269,16 @@ namespace BitTorrent
|
|||
virtual int totalSeedsCount() const = 0;
|
||||
virtual int totalPeersCount() const = 0;
|
||||
virtual int totalLeechersCount() const = 0;
|
||||
virtual QDateTime lastSeenComplete() const = 0;
|
||||
virtual QDateTime completedTime() const = 0;
|
||||
virtual qlonglong timeSinceUpload() const = 0;
|
||||
virtual qlonglong timeSinceDownload() const = 0;
|
||||
virtual qlonglong timeSinceActivity() const = 0;
|
||||
virtual int downloadLimit() const = 0;
|
||||
virtual int uploadLimit() const = 0;
|
||||
virtual bool superSeeding() const = 0;
|
||||
virtual bool isDHTDisabled() const = 0;
|
||||
virtual bool isPEXDisabled() const = 0;
|
||||
virtual bool isLSDDisabled() const = 0;
|
||||
virtual QVector<PeerInfo> peers() const = 0;
|
||||
virtual QList<PeerInfo> peers() const = 0;
|
||||
virtual QBitArray pieces() const = 0;
|
||||
virtual QBitArray downloadingPieces() const = 0;
|
||||
virtual QVector<int> pieceAvailability() const = 0;
|
||||
virtual QList<int> pieceAvailability() const = 0;
|
||||
virtual qreal distributedCopies() const = 0;
|
||||
virtual qreal maxRatio() const = 0;
|
||||
virtual int maxSeedingTime() const = 0;
|
||||
|
@ -305,11 +307,11 @@ namespace BitTorrent
|
|||
virtual void setDHTDisabled(bool disable) = 0;
|
||||
virtual void setPEXDisabled(bool disable) = 0;
|
||||
virtual void setLSDDisabled(bool disable) = 0;
|
||||
virtual void addTrackers(QVector<TrackerEntry> trackers) = 0;
|
||||
virtual void addTrackers(QList<TrackerEntry> trackers) = 0;
|
||||
virtual void removeTrackers(const QStringList &trackers) = 0;
|
||||
virtual void replaceTrackers(QVector<TrackerEntry> trackers) = 0;
|
||||
virtual void addUrlSeeds(const QVector<QUrl> &urlSeeds) = 0;
|
||||
virtual void removeUrlSeeds(const QVector<QUrl> &urlSeeds) = 0;
|
||||
virtual void replaceTrackers(QList<TrackerEntry> trackers) = 0;
|
||||
virtual void addUrlSeeds(const QList<QUrl> &urlSeeds) = 0;
|
||||
virtual void removeUrlSeeds(const QList<QUrl> &urlSeeds) = 0;
|
||||
virtual bool connectPeer(const PeerAddress &peerAddress) = 0;
|
||||
virtual void clearPeers() = 0;
|
||||
virtual void setMetadata(const TorrentInfo &torrentInfo) = 0;
|
||||
|
@ -323,9 +325,9 @@ 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 fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const = 0;
|
||||
virtual void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const = 0;
|
||||
virtual void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const = 0;
|
||||
virtual void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const = 0;
|
||||
|
||||
TorrentID id() const;
|
||||
|
|
|
@ -44,18 +44,18 @@ namespace BitTorrent
|
|||
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;
|
||||
virtual QList<DownloadPriority> filePriorities() const = 0;
|
||||
virtual QList<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 QList<qreal> availableFileFractions() const = 0;
|
||||
virtual void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const = 0;
|
||||
|
||||
virtual void prioritizeFiles(const QVector<DownloadPriority> &priorities) = 0;
|
||||
virtual void prioritizeFiles(const QList<DownloadPriority> &priorities) = 0;
|
||||
virtual void flushCache() const = 0;
|
||||
};
|
||||
}
|
||||
|
|
50
src/base/bittorrent/torrentcontentremoveoption.h
Normal file
50
src/base/bittorrent/torrentcontentremoveoption.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 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 <QMetaEnum>
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
// Using `Q_ENUM_NS()` without a wrapper namespace in our case is not advised
|
||||
// since `Q_NAMESPACE` cannot be used when the same namespace resides at different files.
|
||||
// https://www.kdab.com/new-qt-5-8-meta-object-support-namespaces/#comment-143779
|
||||
inline namespace TorrentContentRemoveOptionNS
|
||||
{
|
||||
Q_NAMESPACE
|
||||
|
||||
enum class TorrentContentRemoveOption
|
||||
{
|
||||
Delete,
|
||||
MoveToTrash
|
||||
};
|
||||
|
||||
Q_ENUM_NS(TorrentContentRemoveOption)
|
||||
}
|
||||
}
|
61
src/base/bittorrent/torrentcontentremover.cpp
Normal file
61
src/base/bittorrent/torrentcontentremover.cpp
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 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 "torrentcontentremover.h"
|
||||
|
||||
#include "base/utils/fs.h"
|
||||
|
||||
void BitTorrent::TorrentContentRemover::performJob(const QString &torrentName, const Path &basePath
|
||||
, const PathList &fileNames, const TorrentContentRemoveOption option)
|
||||
{
|
||||
QString errorMessage;
|
||||
|
||||
if (!fileNames.isEmpty())
|
||||
{
|
||||
const auto removeFileFn = [&option](const Path &filePath)
|
||||
{
|
||||
return ((option == TorrentContentRemoveOption::MoveToTrash)
|
||||
? Utils::Fs::moveFileToTrash : Utils::Fs::removeFile)(filePath);
|
||||
};
|
||||
|
||||
for (const Path &fileName : fileNames)
|
||||
{
|
||||
if (const auto result = removeFileFn(basePath / fileName)
|
||||
; !result && errorMessage.isEmpty())
|
||||
{
|
||||
errorMessage = result.error();
|
||||
}
|
||||
}
|
||||
|
||||
const Path rootPath = Path::findRootFolder(fileNames);
|
||||
if (!rootPath.isEmpty())
|
||||
Utils::Fs::smartRemoveEmptyFolderTree(basePath / rootPath);
|
||||
}
|
||||
|
||||
emit jobFinished(torrentName, errorMessage);
|
||||
}
|
53
src/base/bittorrent/torrentcontentremover.h
Normal file
53
src/base/bittorrent/torrentcontentremover.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2024 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/path.h"
|
||||
#include "torrentcontentremoveoption.h"
|
||||
|
||||
namespace BitTorrent
|
||||
{
|
||||
class TorrentContentRemover final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(TorrentContentRemover)
|
||||
|
||||
public:
|
||||
using QObject::QObject;
|
||||
|
||||
public slots:
|
||||
void performJob(const QString &torrentName, const Path &basePath
|
||||
, const PathList &fileNames, TorrentContentRemoveOption option);
|
||||
|
||||
signals:
|
||||
void jobFinished(const QString &torrentName, const QString &errorMessage);
|
||||
};
|
||||
}
|
|
@ -62,7 +62,10 @@ BitTorrent::TorrentCreationManager::TorrentCreationManager(IApplication *app, QO
|
|||
, m_maxTasks {SETTINGS_KEY(u"MaxTasks"_s), 256}
|
||||
, m_numThreads {SETTINGS_KEY(u"NumThreads"_s), 1}
|
||||
, m_tasks {std::make_unique<TaskSet>()}
|
||||
, m_threadPool(this)
|
||||
{
|
||||
m_threadPool.setObjectName("TorrentCreationManager m_threadPool");
|
||||
|
||||
if (m_numThreads > 0)
|
||||
m_threadPool.setMaxThreadCount(m_numThreads);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include <libtorrent/file_storage.hpp>
|
||||
#include <libtorrent/torrent_info.hpp>
|
||||
|
||||
#include <QtSystemDetection>
|
||||
#include <QDirIterator>
|
||||
#include <QFileInfo>
|
||||
#include <QHash>
|
||||
|
@ -123,7 +124,14 @@ void TorrentCreator::run()
|
|||
// need to sort the file names by natural sort order
|
||||
QStringList dirs = {m_params.sourcePath.data()};
|
||||
|
||||
QDirIterator dirIter {m_params.sourcePath.data(), (QDir::AllDirs | QDir::NoDotAndDotDot), QDirIterator::Subdirectories};
|
||||
#ifdef Q_OS_WIN
|
||||
// libtorrent couldn't handle .lnk files on Windows
|
||||
// Also, Windows users do not expect torrent creator to traverse into .lnk files so skip over them
|
||||
const QDir::Filters dirFilters {QDir::AllDirs | QDir::NoDotAndDotDot | QDir::NoSymLinks};
|
||||
#else
|
||||
const QDir::Filters dirFilters {QDir::AllDirs | QDir::NoDotAndDotDot};
|
||||
#endif
|
||||
QDirIterator dirIter {m_params.sourcePath.data(), dirFilters, QDirIterator::Subdirectories};
|
||||
while (dirIter.hasNext())
|
||||
{
|
||||
const QString filePath = dirIter.next();
|
||||
|
@ -138,7 +146,12 @@ void TorrentCreator::run()
|
|||
{
|
||||
QStringList tmpNames; // natural sort files within each dir
|
||||
|
||||
QDirIterator fileIter {dir, QDir::Files};
|
||||
#ifdef Q_OS_WIN
|
||||
const QDir::Filters fileFilters {QDir::Files | QDir::NoSymLinks};
|
||||
#else
|
||||
const QDir::Filters fileFilters {QDir::Files};
|
||||
#endif
|
||||
QDirIterator fileIter {dir, fileFilters};
|
||||
while (fileIter.hasNext())
|
||||
{
|
||||
const QFileInfo fileInfo = fileIter.nextFileInfo();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 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,9 +35,7 @@
|
|||
#include <libtorrent/write_resume_data.hpp>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/global.h"
|
||||
|
@ -147,7 +145,13 @@ BitTorrent::TorrentDescriptor::TorrentDescriptor(lt::add_torrent_params ltAddTor
|
|||
: m_ltAddTorrentParams {std::move(ltAddTorrentParams)}
|
||||
{
|
||||
if (m_ltAddTorrentParams.ti && m_ltAddTorrentParams.ti->is_valid())
|
||||
{
|
||||
m_info.emplace(*m_ltAddTorrentParams.ti);
|
||||
if (m_ltAddTorrentParams.ti->creation_date() > 0)
|
||||
m_creationDate = QDateTime::fromSecsSinceEpoch(m_ltAddTorrentParams.ti->creation_date());
|
||||
m_creator = QString::fromStdString(m_ltAddTorrentParams.ti->creator());
|
||||
m_comment = QString::fromStdString(m_ltAddTorrentParams.ti->comment());
|
||||
}
|
||||
}
|
||||
|
||||
BitTorrent::InfoHash BitTorrent::TorrentDescriptor::infoHash() const
|
||||
|
@ -166,18 +170,17 @@ QString BitTorrent::TorrentDescriptor::name() const
|
|||
|
||||
QDateTime BitTorrent::TorrentDescriptor::creationDate() const
|
||||
{
|
||||
return ((m_ltAddTorrentParams.ti->creation_date() != 0)
|
||||
? QDateTime::fromSecsSinceEpoch(m_ltAddTorrentParams.ti->creation_date()) : QDateTime());
|
||||
return m_creationDate;
|
||||
}
|
||||
|
||||
QString BitTorrent::TorrentDescriptor::creator() const
|
||||
{
|
||||
return QString::fromStdString(m_ltAddTorrentParams.ti->creator());
|
||||
return m_creator;
|
||||
}
|
||||
|
||||
QString BitTorrent::TorrentDescriptor::comment() const
|
||||
{
|
||||
return QString::fromStdString(m_ltAddTorrentParams.ti->comment());
|
||||
return m_comment;
|
||||
}
|
||||
|
||||
const std::optional<BitTorrent::TorrentInfo> &BitTorrent::TorrentDescriptor::info() const
|
||||
|
@ -204,9 +207,9 @@ void BitTorrent::TorrentDescriptor::setTorrentInfo(TorrentInfo torrentInfo)
|
|||
}
|
||||
}
|
||||
|
||||
QVector<BitTorrent::TrackerEntry> BitTorrent::TorrentDescriptor::trackers() const
|
||||
QList<BitTorrent::TrackerEntry> BitTorrent::TorrentDescriptor::trackers() const
|
||||
{
|
||||
QVector<TrackerEntry> ret;
|
||||
QList<TrackerEntry> ret;
|
||||
ret.reserve(static_cast<decltype(ret)::size_type>(m_ltAddTorrentParams.trackers.size()));
|
||||
std::size_t i = 0;
|
||||
for (const std::string &tracker : m_ltAddTorrentParams.trackers)
|
||||
|
@ -215,9 +218,9 @@ QVector<BitTorrent::TrackerEntry> BitTorrent::TorrentDescriptor::trackers() cons
|
|||
return ret;
|
||||
}
|
||||
|
||||
QVector<QUrl> BitTorrent::TorrentDescriptor::urlSeeds() const
|
||||
QList<QUrl> BitTorrent::TorrentDescriptor::urlSeeds() const
|
||||
{
|
||||
QVector<QUrl> urlSeeds;
|
||||
QList<QUrl> urlSeeds;
|
||||
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(m_ltAddTorrentParams.url_seeds.size()));
|
||||
|
||||
for (const std::string &nativeURLSeed : m_ltAddTorrentParams.url_seeds)
|
||||
|
|
|
@ -33,16 +33,15 @@
|
|||
#include <libtorrent/add_torrent_params.hpp>
|
||||
|
||||
#include <QtContainerFwd>
|
||||
#include <QDateTime>
|
||||
#include <QMetaType>
|
||||
#include <QString>
|
||||
|
||||
#include "base/3rdparty/expected.hpp"
|
||||
#include "base/path.h"
|
||||
#include "torrentdescriptor.h"
|
||||
#include "torrentinfo.h"
|
||||
|
||||
class QByteArray;
|
||||
class QDateTime;
|
||||
class QString;
|
||||
class QUrl;
|
||||
|
||||
namespace BitTorrent
|
||||
|
@ -60,8 +59,8 @@ namespace BitTorrent
|
|||
QDateTime creationDate() const;
|
||||
QString creator() const;
|
||||
QString comment() const;
|
||||
QVector<TrackerEntry> trackers() const;
|
||||
QVector<QUrl> urlSeeds() const;
|
||||
QList<TrackerEntry> trackers() const;
|
||||
QList<QUrl> urlSeeds() const;
|
||||
const std::optional<TorrentInfo> &info() const;
|
||||
|
||||
void setTorrentInfo(TorrentInfo torrentInfo);
|
||||
|
@ -78,6 +77,9 @@ namespace BitTorrent
|
|||
|
||||
lt::add_torrent_params m_ltAddTorrentParams;
|
||||
std::optional<TorrentInfo> m_info;
|
||||
QDateTime m_creationDate;
|
||||
QString m_creator;
|
||||
QString m_comment;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
|
||||
#include <QtSystemDetection>
|
||||
#include <QByteArray>
|
||||
#include <QCache>
|
||||
#include <QDebug>
|
||||
#include <QPointer>
|
||||
#include <QSet>
|
||||
|
@ -62,6 +63,7 @@
|
|||
#include "base/types.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/io.h"
|
||||
#include "base/utils/string.h"
|
||||
#include "common.h"
|
||||
#include "downloadpriority.h"
|
||||
#include "extensiondata.h"
|
||||
|
@ -77,6 +79,10 @@
|
|||
#include "base/utils/os.h"
|
||||
#endif // Q_OS_MACOS || Q_OS_WIN
|
||||
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
#include "customstorage.h"
|
||||
#endif
|
||||
|
||||
using namespace BitTorrent;
|
||||
|
||||
namespace
|
||||
|
@ -88,18 +94,16 @@ namespace
|
|||
return entry;
|
||||
}
|
||||
|
||||
QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint)
|
||||
{
|
||||
const auto ltNow = lt::clock_type::now();
|
||||
const auto qNow = QDateTime::currentDateTime();
|
||||
const auto secsSinceNow = lt::duration_cast<lt::seconds>(timePoint - ltNow + lt::milliseconds(500)).count();
|
||||
|
||||
return qNow.addSecs(secsSinceNow);
|
||||
}
|
||||
|
||||
QString toString(const lt::tcp::endpoint <TCPEndpoint)
|
||||
{
|
||||
return QString::fromStdString((std::stringstream() << ltTCPEndpoint).str());
|
||||
static QCache<lt::tcp::endpoint, QString> cache;
|
||||
|
||||
if (const QString *endpointName = cache.object(ltTCPEndpoint))
|
||||
return *endpointName;
|
||||
|
||||
const auto endpointName = Utils::String::fromLatin1((std::ostringstream() << ltTCPEndpoint).str());
|
||||
cache.insert(ltTCPEndpoint, new QString(endpointName));
|
||||
return endpointName;
|
||||
}
|
||||
|
||||
void updateTrackerEntryStatus(TrackerEntryStatus &trackerEntryStatus, const lt::announce_entry &nativeEntry
|
||||
|
@ -109,16 +113,6 @@ namespace
|
|||
|
||||
trackerEntryStatus.tier = nativeEntry.tier;
|
||||
|
||||
// remove outdated endpoints
|
||||
trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash<std::pair<QString, int>, TrackerEndpointStatus>::iterator &iter)
|
||||
{
|
||||
return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend()
|
||||
, [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint)
|
||||
{
|
||||
return (endpointName == toString(existingEndpoint.local_endpoint));
|
||||
});
|
||||
});
|
||||
|
||||
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * btProtocols.size();
|
||||
|
||||
int numUpdating = 0;
|
||||
|
@ -150,8 +144,8 @@ namespace
|
|||
trackerEndpointStatus.numSeeds = ltAnnounceInfo.scrape_complete;
|
||||
trackerEndpointStatus.numLeeches = ltAnnounceInfo.scrape_incomplete;
|
||||
trackerEndpointStatus.numDownloaded = ltAnnounceInfo.scrape_downloaded;
|
||||
trackerEndpointStatus.nextAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.next_announce);
|
||||
trackerEndpointStatus.minAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.min_announce);
|
||||
trackerEndpointStatus.nextAnnounceTime = ltAnnounceInfo.next_announce;
|
||||
trackerEndpointStatus.minAnnounceTime = ltAnnounceInfo.min_announce;
|
||||
|
||||
if (ltAnnounceInfo.updating)
|
||||
{
|
||||
|
@ -201,6 +195,19 @@ namespace
|
|||
}
|
||||
}
|
||||
|
||||
if (trackerEntryStatus.endpoints.size() > numEndpoints)
|
||||
{
|
||||
// remove outdated endpoints
|
||||
trackerEntryStatus.endpoints.removeIf([&nativeEntry](const QHash<std::pair<QString, int>, TrackerEndpointStatus>::iterator &iter)
|
||||
{
|
||||
return std::none_of(nativeEntry.endpoints.cbegin(), nativeEntry.endpoints.cend()
|
||||
, [&endpointName = std::get<0>(iter.key())](const auto &existingEndpoint)
|
||||
{
|
||||
return (endpointName == toString(existingEndpoint.local_endpoint));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (numEndpoints > 0)
|
||||
{
|
||||
if (numUpdating > 0)
|
||||
|
@ -229,8 +236,8 @@ namespace
|
|||
trackerEntryStatus.numSeeds = -1;
|
||||
trackerEntryStatus.numLeeches = -1;
|
||||
trackerEntryStatus.numDownloaded = -1;
|
||||
trackerEntryStatus.nextAnnounceTime = QDateTime();
|
||||
trackerEntryStatus.minAnnounceTime = QDateTime();
|
||||
trackerEntryStatus.nextAnnounceTime = {};
|
||||
trackerEntryStatus.minAnnounceTime = {};
|
||||
trackerEntryStatus.message.clear();
|
||||
|
||||
for (const TrackerEndpointStatus &endpointStatus : asConst(trackerEntryStatus.endpoints))
|
||||
|
@ -242,7 +249,7 @@ namespace
|
|||
|
||||
if (endpointStatus.state == trackerEntryStatus.state)
|
||||
{
|
||||
if (!trackerEntryStatus.nextAnnounceTime.isValid() || (trackerEntryStatus.nextAnnounceTime > endpointStatus.nextAnnounceTime))
|
||||
if ((trackerEntryStatus.nextAnnounceTime == AnnounceTimePoint()) || (trackerEntryStatus.nextAnnounceTime > endpointStatus.nextAnnounceTime))
|
||||
{
|
||||
trackerEntryStatus.nextAnnounceTime = endpointStatus.nextAnnounceTime;
|
||||
trackerEntryStatus.minAnnounceTime = endpointStatus.minAnnounceTime;
|
||||
|
@ -313,6 +320,11 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
|||
{
|
||||
if (m_ltAddTorrentParams.ti)
|
||||
{
|
||||
if (const std::time_t creationDate = m_ltAddTorrentParams.ti->creation_date(); creationDate > 0)
|
||||
m_creationDate = QDateTime::fromSecsSinceEpoch(creationDate);
|
||||
m_creator = QString::fromStdString(m_ltAddTorrentParams.ti->creator());
|
||||
m_comment = QString::fromStdString(m_ltAddTorrentParams.ti->comment());
|
||||
|
||||
// Initialize it only if torrent is added with metadata.
|
||||
// Otherwise it should be initialized in "Metadata received" handler.
|
||||
m_torrentInfo = TorrentInfo(*m_ltAddTorrentParams.ti);
|
||||
|
@ -356,6 +368,12 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
|
|||
m_urlSeeds.append(QString::fromStdString(urlSeed));
|
||||
m_nativeStatus = extensionData->status;
|
||||
|
||||
m_addedTime = QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
|
||||
if (m_nativeStatus.completed_time > 0)
|
||||
m_completedTime = QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
|
||||
if (m_nativeStatus.last_seen_complete > 0)
|
||||
m_lastSeenComplete = QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
|
||||
|
||||
if (hasMetadata())
|
||||
updateProgress();
|
||||
|
||||
|
@ -399,17 +417,17 @@ QString TorrentImpl::name() const
|
|||
|
||||
QDateTime TorrentImpl::creationDate() const
|
||||
{
|
||||
return m_torrentInfo.creationDate();
|
||||
return m_creationDate;
|
||||
}
|
||||
|
||||
QString TorrentImpl::creator() const
|
||||
{
|
||||
return m_torrentInfo.creator();
|
||||
return m_creator;
|
||||
}
|
||||
|
||||
QString TorrentImpl::comment() const
|
||||
{
|
||||
return m_torrentInfo.comment();
|
||||
return m_comment;
|
||||
}
|
||||
|
||||
bool TorrentImpl::isPrivate() const
|
||||
|
@ -445,7 +463,13 @@ qlonglong TorrentImpl::wastedSize() const
|
|||
|
||||
QString TorrentImpl::currentTracker() const
|
||||
{
|
||||
return QString::fromStdString(m_nativeStatus.current_tracker);
|
||||
if (!m_nativeStatus.current_tracker.empty())
|
||||
return QString::fromStdString(m_nativeStatus.current_tracker);
|
||||
|
||||
if (!m_trackerEntryStatuses.isEmpty())
|
||||
return m_trackerEntryStatuses.constFirst().url;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Path TorrentImpl::savePath() const
|
||||
|
@ -456,6 +480,8 @@ Path TorrentImpl::savePath() const
|
|||
void TorrentImpl::setSavePath(const Path &path)
|
||||
{
|
||||
Q_ASSERT(!isAutoTMMEnabled());
|
||||
if (isAutoTMMEnabled()) [[unlikely]]
|
||||
return;
|
||||
|
||||
const Path basePath = m_session->useCategoryPathsInManualMode()
|
||||
? m_session->categorySavePath(category()) : m_session->savePath();
|
||||
|
@ -483,6 +509,8 @@ Path TorrentImpl::downloadPath() const
|
|||
void TorrentImpl::setDownloadPath(const Path &path)
|
||||
{
|
||||
Q_ASSERT(!isAutoTMMEnabled());
|
||||
if (isAutoTMMEnabled()) [[unlikely]]
|
||||
return;
|
||||
|
||||
const Path basePath = m_session->useCategoryPathsInManualMode()
|
||||
? m_session->categoryDownloadPath(category()) : m_session->downloadPath();
|
||||
|
@ -602,12 +630,12 @@ Path TorrentImpl::makeUserPath(const Path &path) const
|
|||
return userPath;
|
||||
}
|
||||
|
||||
QVector<TrackerEntryStatus> TorrentImpl::trackers() const
|
||||
QList<TrackerEntryStatus> TorrentImpl::trackers() const
|
||||
{
|
||||
return m_trackerEntryStatuses;
|
||||
}
|
||||
|
||||
void TorrentImpl::addTrackers(QVector<TrackerEntry> trackers)
|
||||
void TorrentImpl::addTrackers(QList<TrackerEntry> trackers)
|
||||
{
|
||||
trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
|
||||
|
||||
|
@ -620,7 +648,7 @@ void TorrentImpl::addTrackers(QVector<TrackerEntry> trackers)
|
|||
if (newTrackerSet.isEmpty())
|
||||
return;
|
||||
|
||||
trackers = QVector<TrackerEntry>(newTrackerSet.cbegin(), newTrackerSet.cend());
|
||||
trackers = QList<TrackerEntry>(newTrackerSet.cbegin(), newTrackerSet.cend());
|
||||
for (const TrackerEntry &tracker : asConst(trackers))
|
||||
{
|
||||
m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier));
|
||||
|
@ -656,13 +684,13 @@ void TorrentImpl::removeTrackers(const QStringList &trackers)
|
|||
}
|
||||
}
|
||||
|
||||
void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
|
||||
void TorrentImpl::replaceTrackers(QList<TrackerEntry> trackers)
|
||||
{
|
||||
trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
|
||||
|
||||
// Filter out duplicate trackers
|
||||
const auto uniqueTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend());
|
||||
trackers = QVector<TrackerEntry>(uniqueTrackers.cbegin(), uniqueTrackers.cend());
|
||||
trackers = QList<TrackerEntry>(uniqueTrackers.cbegin(), uniqueTrackers.cend());
|
||||
std::sort(trackers.begin(), trackers.end()
|
||||
, [](const TrackerEntry &left, const TrackerEntry &right) { return left.tier < right.tier; });
|
||||
|
||||
|
@ -687,12 +715,12 @@ void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
|
|||
m_session->handleTorrentTrackersChanged(this);
|
||||
}
|
||||
|
||||
QVector<QUrl> TorrentImpl::urlSeeds() const
|
||||
QList<QUrl> TorrentImpl::urlSeeds() const
|
||||
{
|
||||
return m_urlSeeds;
|
||||
}
|
||||
|
||||
void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
|
||||
void TorrentImpl::addUrlSeeds(const QList<QUrl> &urlSeeds)
|
||||
{
|
||||
m_session->invokeAsync([urlSeeds, session = m_session
|
||||
, nativeHandle = m_nativeHandle
|
||||
|
@ -701,12 +729,12 @@ void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
|
|||
try
|
||||
{
|
||||
const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
|
||||
QVector<QUrl> currentSeeds;
|
||||
QList<QUrl> currentSeeds;
|
||||
currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
|
||||
for (const std::string &urlSeed : nativeSeeds)
|
||||
currentSeeds.append(QString::fromStdString(urlSeed));
|
||||
|
||||
QVector<QUrl> addedUrlSeeds;
|
||||
QList<QUrl> addedUrlSeeds;
|
||||
addedUrlSeeds.reserve(urlSeeds.size());
|
||||
|
||||
for (const QUrl &url : urlSeeds)
|
||||
|
@ -736,7 +764,7 @@ void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
|
|||
});
|
||||
}
|
||||
|
||||
void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
|
||||
void TorrentImpl::removeUrlSeeds(const QList<QUrl> &urlSeeds)
|
||||
{
|
||||
m_session->invokeAsync([urlSeeds, session = m_session
|
||||
, nativeHandle = m_nativeHandle
|
||||
|
@ -745,12 +773,12 @@ void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
|
|||
try
|
||||
{
|
||||
const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
|
||||
QVector<QUrl> currentSeeds;
|
||||
QList<QUrl> currentSeeds;
|
||||
currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
|
||||
for (const std::string &urlSeed : nativeSeeds)
|
||||
currentSeeds.append(QString::fromStdString(urlSeed));
|
||||
|
||||
QVector<QUrl> removedUrlSeeds;
|
||||
QList<QUrl> removedUrlSeeds;
|
||||
removedUrlSeeds.reserve(urlSeeds.size());
|
||||
|
||||
for (const QUrl &url : urlSeeds)
|
||||
|
@ -934,7 +962,52 @@ void TorrentImpl::removeAllTags()
|
|||
|
||||
QDateTime TorrentImpl::addedTime() const
|
||||
{
|
||||
return QDateTime::fromSecsSinceEpoch(m_nativeStatus.added_time);
|
||||
return m_addedTime;
|
||||
}
|
||||
|
||||
QDateTime TorrentImpl::completedTime() const
|
||||
{
|
||||
return m_completedTime;
|
||||
}
|
||||
|
||||
QDateTime TorrentImpl::lastSeenComplete() const
|
||||
{
|
||||
return m_lastSeenComplete;
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::activeTime() const
|
||||
{
|
||||
return lt::total_seconds(m_nativeStatus.active_duration);
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::finishedTime() const
|
||||
{
|
||||
return lt::total_seconds(m_nativeStatus.finished_duration);
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::timeSinceUpload() const
|
||||
{
|
||||
if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
|
||||
return -1;
|
||||
|
||||
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::timeSinceDownload() const
|
||||
{
|
||||
if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
|
||||
return -1;
|
||||
|
||||
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::timeSinceActivity() const
|
||||
{
|
||||
const qlonglong upTime = timeSinceUpload();
|
||||
const qlonglong downTime = timeSinceDownload();
|
||||
return ((upTime < 0) != (downTime < 0))
|
||||
? std::max(upTime, downTime)
|
||||
: std::min(upTime, downTime);
|
||||
}
|
||||
|
||||
qreal TorrentImpl::ratioLimit() const
|
||||
|
@ -962,7 +1035,7 @@ Path TorrentImpl::filePath(const int index) const
|
|||
|
||||
Path TorrentImpl::actualFilePath(const int index) const
|
||||
{
|
||||
const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
|
||||
const QList<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
|
||||
|
||||
Q_ASSERT(index >= 0);
|
||||
Q_ASSERT(index < nativeIndexes.size());
|
||||
|
@ -982,7 +1055,22 @@ PathList TorrentImpl::filePaths() const
|
|||
return m_filePaths;
|
||||
}
|
||||
|
||||
QVector<DownloadPriority> TorrentImpl::filePriorities() const
|
||||
PathList TorrentImpl::actualFilePaths() const
|
||||
{
|
||||
if (!hasMetadata())
|
||||
return {};
|
||||
|
||||
PathList paths;
|
||||
paths.reserve(filesCount());
|
||||
|
||||
const lt::file_storage files = nativeTorrentInfo()->files();
|
||||
for (const lt::file_index_t &nativeIndex : asConst(m_torrentInfo.nativeIndexes()))
|
||||
paths.emplaceBack(files.file_path(nativeIndex));
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
QList<DownloadPriority> TorrentImpl::filePriorities() const
|
||||
{
|
||||
return m_filePriorities;
|
||||
}
|
||||
|
@ -1217,12 +1305,12 @@ int TorrentImpl::queuePosition() const
|
|||
QString TorrentImpl::error() const
|
||||
{
|
||||
if (m_nativeStatus.errc)
|
||||
return QString::fromLocal8Bit(m_nativeStatus.errc.message().c_str());
|
||||
return Utils::String::fromLocal8Bit(m_nativeStatus.errc.message());
|
||||
|
||||
if (m_nativeStatus.flags & lt::torrent_flags::upload_mode)
|
||||
{
|
||||
return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
|
||||
.arg(QString::fromLocal8Bit(m_lastFileError.error.message().c_str()));
|
||||
.arg(Utils::String::fromLocal8Bit(m_lastFileError.error.message()));
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -1238,16 +1326,6 @@ qlonglong TorrentImpl::totalUpload() const
|
|||
return m_nativeStatus.all_time_upload;
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::activeTime() const
|
||||
{
|
||||
return lt::total_seconds(m_nativeStatus.active_duration);
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::finishedTime() const
|
||||
{
|
||||
return lt::total_seconds(m_nativeStatus.finished_duration);
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::eta() const
|
||||
{
|
||||
if (isStopped()) return MAX_ETA;
|
||||
|
@ -1298,7 +1376,7 @@ qlonglong TorrentImpl::eta() const
|
|||
return (wantedSize() - completedSize()) / speedAverage.download;
|
||||
}
|
||||
|
||||
QVector<qreal> TorrentImpl::filesProgress() const
|
||||
QList<qreal> TorrentImpl::filesProgress() const
|
||||
{
|
||||
if (!hasMetadata())
|
||||
return {};
|
||||
|
@ -1309,9 +1387,9 @@ QVector<qreal> TorrentImpl::filesProgress() const
|
|||
return {};
|
||||
|
||||
if (m_completedFiles.count(true) == count)
|
||||
return QVector<qreal>(count, 1);
|
||||
return QList<qreal>(count, 1);
|
||||
|
||||
QVector<qreal> result;
|
||||
QList<qreal> result;
|
||||
result.reserve(count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
|
@ -1357,45 +1435,6 @@ int TorrentImpl::totalLeechersCount() const
|
|||
return (m_nativeStatus.num_incomplete > -1) ? m_nativeStatus.num_incomplete : (m_nativeStatus.list_peers - m_nativeStatus.list_seeds);
|
||||
}
|
||||
|
||||
QDateTime TorrentImpl::lastSeenComplete() const
|
||||
{
|
||||
if (m_nativeStatus.last_seen_complete > 0)
|
||||
return QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
QDateTime TorrentImpl::completedTime() const
|
||||
{
|
||||
if (m_nativeStatus.completed_time > 0)
|
||||
return QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::timeSinceUpload() const
|
||||
{
|
||||
if (m_nativeStatus.last_upload.time_since_epoch().count() == 0)
|
||||
return -1;
|
||||
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_upload);
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::timeSinceDownload() const
|
||||
{
|
||||
if (m_nativeStatus.last_download.time_since_epoch().count() == 0)
|
||||
return -1;
|
||||
return lt::total_seconds(lt::clock_type::now() - m_nativeStatus.last_download);
|
||||
}
|
||||
|
||||
qlonglong TorrentImpl::timeSinceActivity() const
|
||||
{
|
||||
const qlonglong upTime = timeSinceUpload();
|
||||
const qlonglong downTime = timeSinceDownload();
|
||||
return ((upTime < 0) != (downTime < 0))
|
||||
? std::max(upTime, downTime)
|
||||
: std::min(upTime, downTime);
|
||||
}
|
||||
|
||||
int TorrentImpl::downloadLimit() const
|
||||
{
|
||||
return m_downloadLimit;;
|
||||
|
@ -1426,12 +1465,12 @@ bool TorrentImpl::isLSDDisabled() const
|
|||
return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
|
||||
}
|
||||
|
||||
QVector<PeerInfo> TorrentImpl::peers() const
|
||||
QList<PeerInfo> TorrentImpl::peers() const
|
||||
{
|
||||
std::vector<lt::peer_info> nativePeers;
|
||||
m_nativeHandle.get_peer_info(nativePeers);
|
||||
|
||||
QVector<PeerInfo> peers;
|
||||
QList<PeerInfo> peers;
|
||||
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
|
||||
|
||||
for (const lt::peer_info &peer : nativePeers)
|
||||
|
@ -1447,18 +1486,20 @@ QBitArray TorrentImpl::pieces() const
|
|||
|
||||
QBitArray TorrentImpl::downloadingPieces() const
|
||||
{
|
||||
QBitArray result(piecesCount());
|
||||
if (!hasMetadata())
|
||||
return {};
|
||||
|
||||
std::vector<lt::partial_piece_info> queue;
|
||||
m_nativeHandle.get_download_queue(queue);
|
||||
|
||||
QBitArray result {piecesCount()};
|
||||
for (const lt::partial_piece_info &info : queue)
|
||||
result.setBit(LT::toUnderlyingType(info.piece_index));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QVector<int> TorrentImpl::pieceAvailability() const
|
||||
QList<int> TorrentImpl::pieceAvailability() const
|
||||
{
|
||||
std::vector<int> avail;
|
||||
m_nativeHandle.piece_availability(avail);
|
||||
|
@ -1574,18 +1615,20 @@ bool TorrentImpl::setCategory(const QString &category)
|
|||
if (!category.isEmpty() && !m_session->categories().contains(category))
|
||||
return false;
|
||||
|
||||
if (m_session->isDisableAutoTMMWhenCategoryChanged())
|
||||
{
|
||||
// This should be done before changing the category name
|
||||
// to prevent the torrent from being moved at the path of new category.
|
||||
setAutoTMMEnabled(false);
|
||||
}
|
||||
|
||||
const QString oldCategory = m_category;
|
||||
m_category = category;
|
||||
deferredRequestResumeData();
|
||||
m_session->handleTorrentCategoryChanged(this, oldCategory);
|
||||
|
||||
if (m_useAutoTMM)
|
||||
{
|
||||
if (!m_session->isDisableAutoTMMWhenCategoryChanged())
|
||||
adjustStorageLocation();
|
||||
else
|
||||
setAutoTMMEnabled(false);
|
||||
}
|
||||
adjustStorageLocation();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1607,9 +1650,19 @@ void TorrentImpl::forceRecheck()
|
|||
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_nativeStatus.pieces.clear_all();
|
||||
m_nativeStatus.num_pieces = 0;
|
||||
m_ltAddTorrentParams.have_pieces.clear();
|
||||
m_ltAddTorrentParams.verified_pieces.clear();
|
||||
m_ltAddTorrentParams.unfinished_pieces.clear();
|
||||
m_completedFiles.fill(false);
|
||||
m_filesProgress.fill(0);
|
||||
m_pieces.fill(false);
|
||||
m_unchecked = false;
|
||||
|
||||
if (m_hasMissingFiles)
|
||||
{
|
||||
|
@ -1622,14 +1675,6 @@ void TorrentImpl::forceRecheck()
|
|||
}
|
||||
}
|
||||
|
||||
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 (isStopped())
|
||||
{
|
||||
// When "force recheck" is applied on Stopped torrent, we start them to perform checking
|
||||
|
@ -1734,7 +1779,9 @@ TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entr
|
|||
#else
|
||||
const QSet<int> btProtocols {1};
|
||||
#endif
|
||||
|
||||
::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo);
|
||||
|
||||
return *it;
|
||||
}
|
||||
|
||||
|
@ -1791,12 +1838,13 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
|
|||
const Path filePath = actualFilePath.removedExtension(QB_EXT);
|
||||
m_filePaths.append(filePath);
|
||||
|
||||
lt::download_priority_t &nativePriority = p.file_priorities[LT::toUnderlyingType(nativeIndex)];
|
||||
if ((nativePriority != lt::dont_download) && m_session->isFilenameExcluded(filePath.filename()))
|
||||
nativePriority = lt::dont_download;
|
||||
const auto priority = LT::fromNative(nativePriority);
|
||||
m_filePriorities.append(priority);
|
||||
m_filePriorities.append(LT::fromNative(p.file_priorities[LT::toUnderlyingType(nativeIndex)]));
|
||||
}
|
||||
|
||||
m_session->applyFilenameFilter(m_filePaths, m_filePriorities);
|
||||
for (int i = 0; i < m_filePriorities.size(); ++i)
|
||||
p.file_priorities[LT::toUnderlyingType(nativeIndexes[i])] = LT::toNative(m_filePriorities[i]);
|
||||
|
||||
p.save_path = savePath.toString().toStdString();
|
||||
p.ti = metadata;
|
||||
|
||||
|
@ -1859,6 +1907,9 @@ void TorrentImpl::reload()
|
|||
|
||||
auto *const extensionData = new ExtensionData;
|
||||
p.userdata = LTClientData(extensionData);
|
||||
#ifndef QBT_USES_LIBTORRENT2
|
||||
p.storage = customStorageConstructor;
|
||||
#endif
|
||||
m_nativeHandle = m_nativeSession->add_torrent(p);
|
||||
|
||||
m_nativeStatus = extensionData->status;
|
||||
|
@ -1933,8 +1984,17 @@ void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext cont
|
|||
{
|
||||
if (!hasMetadata())
|
||||
{
|
||||
m_savePath = newPath;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
if (context == MoveStorageContext::ChangeSavePath)
|
||||
{
|
||||
m_savePath = newPath;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
}
|
||||
else if (context == MoveStorageContext::ChangeDownloadPath)
|
||||
{
|
||||
m_downloadPath = newPath;
|
||||
m_session->handleTorrentSavePathChanged(this);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2090,7 +2150,7 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
|||
// 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; });
|
||||
fetchURLSeeds([this](const QList<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
|
||||
}
|
||||
|
||||
if ((m_maintenanceJob == MaintenanceJob::HandleMetadata) && p->params.ti)
|
||||
|
@ -2104,6 +2164,7 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
|||
|
||||
m_ltAddTorrentParams.have_pieces.clear();
|
||||
m_ltAddTorrentParams.verified_pieces.clear();
|
||||
m_ltAddTorrentParams.unfinished_pieces.clear();
|
||||
|
||||
m_nativeStatus.torrent_file = m_ltAddTorrentParams.ti;
|
||||
|
||||
|
@ -2146,23 +2207,37 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
|
|||
|
||||
void TorrentImpl::prepareResumeData(const lt::add_torrent_params ¶ms)
|
||||
{
|
||||
if (m_hasMissingFiles)
|
||||
{
|
||||
const auto havePieces = m_ltAddTorrentParams.have_pieces;
|
||||
const auto unfinishedPieces = m_ltAddTorrentParams.unfinished_pieces;
|
||||
const auto verifiedPieces = m_ltAddTorrentParams.verified_pieces;
|
||||
decltype(params.have_pieces) havePieces;
|
||||
decltype(params.unfinished_pieces) unfinishedPieces;
|
||||
decltype(params.verified_pieces) verifiedPieces;
|
||||
|
||||
// The resume data obtained from libtorrent contains an empty "progress" in the following cases:
|
||||
// 1. when it was requested at a time when the initial resume data has not yet been checked,
|
||||
// 2. when initial resume data was rejected
|
||||
// We should preserve the initial "progress" in such cases.
|
||||
const bool needPreserveProgress = m_hasMissingFiles
|
||||
|| (!m_ltAddTorrentParams.have_pieces.empty() && params.have_pieces.empty());
|
||||
const bool preserveSeedMode = !m_hasMissingFiles && !hasMetadata()
|
||||
&& (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode);
|
||||
|
||||
if (needPreserveProgress)
|
||||
{
|
||||
havePieces = std::move(m_ltAddTorrentParams.have_pieces);
|
||||
unfinishedPieces = std::move(m_ltAddTorrentParams.unfinished_pieces);
|
||||
verifiedPieces = std::move(m_ltAddTorrentParams.verified_pieces);
|
||||
}
|
||||
|
||||
// Update recent resume data but preserve existing progress
|
||||
m_ltAddTorrentParams = params;
|
||||
m_ltAddTorrentParams.have_pieces = havePieces;
|
||||
m_ltAddTorrentParams.unfinished_pieces = unfinishedPieces;
|
||||
m_ltAddTorrentParams.verified_pieces = verifiedPieces;
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool preserveSeedMode = (!hasMetadata() && (m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode));
|
||||
// Update recent resume data
|
||||
m_ltAddTorrentParams = params;
|
||||
|
||||
if (needPreserveProgress)
|
||||
{
|
||||
m_ltAddTorrentParams.have_pieces = std::move(havePieces);
|
||||
m_ltAddTorrentParams.unfinished_pieces = std::move(unfinishedPieces);
|
||||
m_ltAddTorrentParams.verified_pieces = std::move(verifiedPieces);
|
||||
}
|
||||
|
||||
if (preserveSeedMode)
|
||||
m_ltAddTorrentParams.flags |= lt::torrent_flags::seed_mode;
|
||||
}
|
||||
|
@ -2201,7 +2276,7 @@ void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_fai
|
|||
if (p->error != lt::errors::resume_data_not_modified)
|
||||
{
|
||||
LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
|
||||
.arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
|
||||
.arg(name(), Utils::String::fromLocal8Bit(p->error.message())), Log::CRITICAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2289,7 +2364,7 @@ void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
|
|||
Q_ASSERT(fileIndex >= 0);
|
||||
|
||||
LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
|
||||
.arg(name(), filePath(fileIndex).toString(), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
|
||||
.arg(name(), filePath(fileIndex).toString(), Utils::String::fromLocal8Bit(p->error.message())), Log::WARNING);
|
||||
|
||||
--m_renameCount;
|
||||
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
|
||||
|
@ -2312,7 +2387,8 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
|
|||
|
||||
#if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
|
||||
// only apply Mark-of-the-Web to new download files
|
||||
if (Preferences::instance()->isMarkOfTheWebEnabled() && isDownloading())
|
||||
if (Preferences::instance()->isMarkOfTheWebEnabled()
|
||||
&& (m_nativeStatus.state == lt::torrent_status::downloading))
|
||||
{
|
||||
const Path fullpath = actualStorageLocation() / actualPath;
|
||||
Utils::OS::applyMarkOfTheWeb(fullpath);
|
||||
|
@ -2467,7 +2543,7 @@ void TorrentImpl::adjustStorageLocation()
|
|||
|
||||
void TorrentImpl::doRenameFile(const int index, const Path &path)
|
||||
{
|
||||
const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
|
||||
const QList<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
|
||||
|
||||
Q_ASSERT(index >= 0);
|
||||
Q_ASSERT(index < nativeIndexes.size());
|
||||
|
@ -2558,11 +2634,23 @@ bool TorrentImpl::isMoveInProgress() const
|
|||
|
||||
void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
|
||||
{
|
||||
// Since libtorrent alerts are handled asynchronously there can be obsolete
|
||||
// "state update" event reached here after torrent was reloaded in libtorrent.
|
||||
// Just discard such events.
|
||||
if (nativeStatus.handle != m_nativeHandle) [[unlikely]]
|
||||
return;
|
||||
|
||||
const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
|
||||
|
||||
if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
|
||||
updateProgress();
|
||||
|
||||
if (m_nativeStatus.completed_time != oldStatus.completed_time)
|
||||
m_completedTime = (m_nativeStatus.completed_time > 0) ? QDateTime::fromSecsSinceEpoch(m_nativeStatus.completed_time) : QDateTime();
|
||||
|
||||
if (m_nativeStatus.last_seen_complete != oldStatus.last_seen_complete)
|
||||
m_lastSeenComplete = QDateTime::fromSecsSinceEpoch(m_nativeStatus.last_seen_complete);
|
||||
|
||||
updateState();
|
||||
|
||||
m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
|
||||
|
@ -2765,33 +2853,26 @@ QString TorrentImpl::createMagnetURI() const
|
|||
|
||||
const SHA1Hash infoHash1 = infoHash().v1();
|
||||
if (infoHash1.isValid())
|
||||
{
|
||||
ret += u"xt=urn:btih:" + infoHash1.toString();
|
||||
}
|
||||
|
||||
const SHA256Hash infoHash2 = infoHash().v2();
|
||||
if (infoHash2.isValid())
|
||||
if (const SHA256Hash infoHash2 = infoHash().v2(); infoHash2.isValid())
|
||||
{
|
||||
if (infoHash1.isValid())
|
||||
ret += u'&';
|
||||
ret += u"xt=urn:btmh:1220" + infoHash2.toString();
|
||||
}
|
||||
|
||||
const QString displayName = name();
|
||||
if (displayName != id().toString())
|
||||
{
|
||||
if (const QString displayName = name(); displayName != id().toString())
|
||||
ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
|
||||
}
|
||||
|
||||
if (hasMetadata())
|
||||
ret += u"&xl=" + QString::number(totalSize());
|
||||
|
||||
for (const TrackerEntryStatus &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());
|
||||
}
|
||||
ret += u"&ws=" + urlSeed.toString(QUrl::FullyEncoded);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -2849,15 +2930,15 @@ nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) cons
|
|||
return {};
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const
|
||||
void TorrentImpl::fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QVector<PeerInfo>
|
||||
invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QList<PeerInfo>
|
||||
{
|
||||
try
|
||||
{
|
||||
std::vector<lt::peer_info> nativePeers;
|
||||
nativeHandle.get_peer_info(nativePeers);
|
||||
QVector<PeerInfo> peers;
|
||||
QList<PeerInfo> peers;
|
||||
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
|
||||
for (const lt::peer_info &peer : nativePeers)
|
||||
peers.append(PeerInfo(peer, allPieces));
|
||||
|
@ -2870,14 +2951,14 @@ void TorrentImpl::fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHa
|
|||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const
|
||||
void TorrentImpl::fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<QUrl>
|
||||
invokeAsync([nativeHandle = m_nativeHandle]() -> QList<QUrl>
|
||||
{
|
||||
try
|
||||
{
|
||||
const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
|
||||
QVector<QUrl> urlSeeds;
|
||||
QList<QUrl> urlSeeds;
|
||||
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
|
||||
for (const std::string &urlSeed : currentSeeds)
|
||||
urlSeeds.append(QString::fromStdString(urlSeed));
|
||||
|
@ -2890,15 +2971,15 @@ void TorrentImpl::fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandle
|
|||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const
|
||||
void TorrentImpl::fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<int>
|
||||
invokeAsync([nativeHandle = m_nativeHandle]() -> QList<int>
|
||||
{
|
||||
try
|
||||
{
|
||||
std::vector<int> piecesAvailability;
|
||||
nativeHandle.piece_availability(piecesAvailability);
|
||||
return QVector<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
|
||||
return QList<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
|
||||
}
|
||||
catch (const std::exception &) {}
|
||||
|
||||
|
@ -2932,9 +3013,9 @@ void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultH
|
|||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const
|
||||
void TorrentImpl::fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const
|
||||
{
|
||||
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QVector<qreal>
|
||||
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QList<qreal>
|
||||
{
|
||||
if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
|
||||
return {};
|
||||
|
@ -2946,9 +3027,9 @@ void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>
|
|||
const int filesCount = torrentInfo.filesCount();
|
||||
// libtorrent returns empty array for seeding only torrents
|
||||
if (piecesAvailability.empty())
|
||||
return QVector<qreal>(filesCount, -1);
|
||||
return QList<qreal>(filesCount, -1);
|
||||
|
||||
QVector<qreal> result;
|
||||
QList<qreal> result;
|
||||
result.reserve(filesCount);
|
||||
for (int i = 0; i < filesCount; ++i)
|
||||
{
|
||||
|
@ -2972,7 +3053,7 @@ void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>
|
|||
, std::move(resultHandler));
|
||||
}
|
||||
|
||||
void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
|
||||
void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
|
||||
{
|
||||
if (!hasMetadata())
|
||||
return;
|
||||
|
@ -2981,7 +3062,7 @@ void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
|
|||
|
||||
// Reset 'm_hasSeedStatus' if needed in order to react again to
|
||||
// 'torrent_finished_alert' and eg show tray notifications
|
||||
const QVector<DownloadPriority> oldPriorities = filePriorities();
|
||||
const QList<DownloadPriority> oldPriorities = filePriorities();
|
||||
for (int i = 0; i < oldPriorities.size(); ++i)
|
||||
{
|
||||
if ((oldPriorities[i] == DownloadPriority::Ignored)
|
||||
|
@ -3009,18 +3090,18 @@ void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
|
|||
manageActualFilePaths();
|
||||
}
|
||||
|
||||
QVector<qreal> TorrentImpl::availableFileFractions() const
|
||||
QList<qreal> TorrentImpl::availableFileFractions() const
|
||||
{
|
||||
Q_ASSERT(hasMetadata());
|
||||
|
||||
const int filesCount = this->filesCount();
|
||||
if (filesCount <= 0) return {};
|
||||
|
||||
const QVector<int> piecesAvailability = pieceAvailability();
|
||||
const QList<int> piecesAvailability = pieceAvailability();
|
||||
// libtorrent returns empty array for seeding only torrents
|
||||
if (piecesAvailability.empty()) return QVector<qreal>(filesCount, -1);
|
||||
if (piecesAvailability.empty()) return QList<qreal>(filesCount, -1);
|
||||
|
||||
QVector<qreal> res;
|
||||
QList<qreal> res;
|
||||
res.reserve(filesCount);
|
||||
for (int i = 0; i < filesCount; ++i)
|
||||
{
|
||||
|
|
|
@ -41,11 +41,11 @@
|
|||
#include <QBitArray>
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QQueue>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/path.h"
|
||||
#include "base/tagset.h"
|
||||
|
@ -138,7 +138,15 @@ namespace BitTorrent
|
|||
int piecesCount() const override;
|
||||
int piecesHave() const override;
|
||||
qreal progress() const override;
|
||||
|
||||
QDateTime addedTime() const override;
|
||||
QDateTime completedTime() const override;
|
||||
QDateTime lastSeenComplete() const override;
|
||||
qlonglong activeTime() const override;
|
||||
qlonglong finishedTime() const override;
|
||||
qlonglong timeSinceUpload() const override;
|
||||
qlonglong timeSinceDownload() const override;
|
||||
qlonglong timeSinceActivity() const override;
|
||||
|
||||
qreal ratioLimit() const override;
|
||||
void setRatioLimit(qreal limit) override;
|
||||
|
@ -153,7 +161,8 @@ namespace BitTorrent
|
|||
Path actualFilePath(int index) const override;
|
||||
qlonglong fileSize(int index) const override;
|
||||
PathList filePaths() const override;
|
||||
QVector<DownloadPriority> filePriorities() const override;
|
||||
PathList actualFilePaths() const override;
|
||||
QList<DownloadPriority> filePriorities() const override;
|
||||
|
||||
TorrentInfo info() const override;
|
||||
bool isFinished() const override;
|
||||
|
@ -175,36 +184,29 @@ namespace BitTorrent
|
|||
bool hasMissingFiles() const override;
|
||||
bool hasError() const override;
|
||||
int queuePosition() const override;
|
||||
QVector<TrackerEntryStatus> trackers() const override;
|
||||
QVector<QUrl> urlSeeds() const override;
|
||||
QList<TrackerEntryStatus> trackers() const override;
|
||||
QList<QUrl> urlSeeds() const override;
|
||||
QString error() const override;
|
||||
qlonglong totalDownload() const override;
|
||||
qlonglong totalUpload() const override;
|
||||
qlonglong activeTime() const override;
|
||||
qlonglong finishedTime() const override;
|
||||
qlonglong eta() const override;
|
||||
QVector<qreal> filesProgress() const override;
|
||||
QList<qreal> filesProgress() const override;
|
||||
int seedsCount() const override;
|
||||
int peersCount() const override;
|
||||
int leechsCount() const override;
|
||||
int totalSeedsCount() const override;
|
||||
int totalPeersCount() const override;
|
||||
int totalLeechersCount() const override;
|
||||
QDateTime lastSeenComplete() const override;
|
||||
QDateTime completedTime() const override;
|
||||
qlonglong timeSinceUpload() const override;
|
||||
qlonglong timeSinceDownload() const override;
|
||||
qlonglong timeSinceActivity() const override;
|
||||
int downloadLimit() const override;
|
||||
int uploadLimit() const override;
|
||||
bool superSeeding() const override;
|
||||
bool isDHTDisabled() const override;
|
||||
bool isPEXDisabled() const override;
|
||||
bool isLSDDisabled() const override;
|
||||
QVector<PeerInfo> peers() const override;
|
||||
QList<PeerInfo> peers() const override;
|
||||
QBitArray pieces() const override;
|
||||
QBitArray downloadingPieces() const override;
|
||||
QVector<int> pieceAvailability() const override;
|
||||
QList<int> pieceAvailability() const override;
|
||||
qreal distributedCopies() const override;
|
||||
qreal maxRatio() const override;
|
||||
int maxSeedingTime() const override;
|
||||
|
@ -218,7 +220,7 @@ namespace BitTorrent
|
|||
int connectionsCount() const override;
|
||||
int connectionsLimit() const override;
|
||||
qlonglong nextAnnounce() const override;
|
||||
QVector<qreal> availableFileFractions() const override;
|
||||
QList<qreal> availableFileFractions() const override;
|
||||
|
||||
void setName(const QString &name) override;
|
||||
void setSequentialDownload(bool enable) override;
|
||||
|
@ -229,7 +231,7 @@ namespace BitTorrent
|
|||
void forceDHTAnnounce() override;
|
||||
void forceRecheck() override;
|
||||
void renameFile(int index, const Path &path) override;
|
||||
void prioritizeFiles(const QVector<DownloadPriority> &priorities) override;
|
||||
void prioritizeFiles(const QList<DownloadPriority> &priorities) override;
|
||||
void setUploadLimit(int limit) override;
|
||||
void setDownloadLimit(int limit) override;
|
||||
void setSuperSeeding(bool enable) override;
|
||||
|
@ -237,11 +239,11 @@ namespace BitTorrent
|
|||
void setPEXDisabled(bool disable) override;
|
||||
void setLSDDisabled(bool disable) override;
|
||||
void flushCache() const override;
|
||||
void addTrackers(QVector<TrackerEntry> trackers) override;
|
||||
void addTrackers(QList<TrackerEntry> trackers) override;
|
||||
void removeTrackers(const QStringList &trackers) override;
|
||||
void replaceTrackers(QVector<TrackerEntry> trackers) override;
|
||||
void addUrlSeeds(const QVector<QUrl> &urlSeeds) override;
|
||||
void removeUrlSeeds(const QVector<QUrl> &urlSeeds) override;
|
||||
void replaceTrackers(QList<TrackerEntry> trackers) override;
|
||||
void addUrlSeeds(const QList<QUrl> &urlSeeds) override;
|
||||
void removeUrlSeeds(const QList<QUrl> &urlSeeds) override;
|
||||
bool connectPeer(const PeerAddress &peerAddress) override;
|
||||
void clearPeers() override;
|
||||
void setMetadata(const TorrentInfo &torrentInfo) override;
|
||||
|
@ -256,11 +258,11 @@ 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 fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const override;
|
||||
void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const override;
|
||||
void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const override;
|
||||
void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const override;
|
||||
void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const override;
|
||||
void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const override;
|
||||
|
||||
bool needSaveResumeData() const;
|
||||
|
||||
|
@ -335,12 +337,20 @@ namespace BitTorrent
|
|||
TorrentInfo m_torrentInfo;
|
||||
PathList m_filePaths;
|
||||
QHash<lt::file_index_t, int> m_indexMap;
|
||||
QVector<DownloadPriority> m_filePriorities;
|
||||
QList<DownloadPriority> m_filePriorities;
|
||||
QBitArray m_completedFiles;
|
||||
SpeedMonitor m_payloadRateMonitor;
|
||||
|
||||
InfoHash m_infoHash;
|
||||
|
||||
QDateTime m_creationDate;
|
||||
QString m_creator;
|
||||
QString m_comment;
|
||||
|
||||
QDateTime m_addedTime;
|
||||
QDateTime m_completedTime;
|
||||
QDateTime m_lastSeenComplete;
|
||||
|
||||
// m_moveFinishedTriggers is activated only when the following conditions are met:
|
||||
// all file rename jobs complete, all file move jobs complete
|
||||
QQueue<EventTrigger> m_moveFinishedTriggers;
|
||||
|
@ -351,8 +361,8 @@ namespace BitTorrent
|
|||
|
||||
MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
|
||||
|
||||
QVector<TrackerEntryStatus> m_trackerEntryStatuses;
|
||||
QVector<QUrl> m_urlSeeds;
|
||||
QList<TrackerEntryStatus> m_trackerEntryStatuses;
|
||||
QList<QUrl> m_urlSeeds;
|
||||
FileErrorInfo m_lastFileError;
|
||||
|
||||
// Persistent data
|
||||
|
@ -383,7 +393,7 @@ namespace BitTorrent
|
|||
int m_uploadLimit = 0;
|
||||
|
||||
QBitArray m_pieces;
|
||||
QVector<std::int64_t> m_filesProgress;
|
||||
QList<std::int64_t> m_filesProgress;
|
||||
|
||||
bool m_deferredRequestResumeDataInvoked = false;
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 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,24 +28,15 @@
|
|||
|
||||
#include "torrentinfo.h"
|
||||
|
||||
#include <libtorrent/create_torrent.hpp>
|
||||
#include <libtorrent/error_code.hpp>
|
||||
#include <libtorrent/version.hpp>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QUrl>
|
||||
|
||||
#include "base/global.h"
|
||||
#include "base/path.h"
|
||||
#include "base/preferences.h"
|
||||
#include "base/utils/fs.h"
|
||||
#include "base/utils/io.h"
|
||||
#include "base/utils/misc.h"
|
||||
#include "infohash.h"
|
||||
#include "trackerentry.h"
|
||||
|
||||
|
@ -82,66 +73,6 @@ bool TorrentInfo::isValid() const
|
|||
return (m_nativeInfo != nullptr);
|
||||
}
|
||||
|
||||
nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data) noexcept
|
||||
{
|
||||
// 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are
|
||||
// used in `torrent_info()` constructor
|
||||
const auto *pref = Preferences::instance();
|
||||
|
||||
lt::error_code ec;
|
||||
const lt::bdecode_node node = lt::bdecode(data, ec
|
||||
, nullptr, pref->getBdecodeDepthLimit(), pref->getBdecodeTokenLimit());
|
||||
if (ec)
|
||||
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
|
||||
|
||||
const lt::torrent_info nativeInfo {node, ec};
|
||||
if (ec)
|
||||
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
|
||||
|
||||
return TorrentInfo(nativeInfo);
|
||||
}
|
||||
|
||||
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const Path &path) noexcept
|
||||
{
|
||||
QByteArray data;
|
||||
try
|
||||
{
|
||||
const qint64 torrentSizeLimit = Preferences::instance()->getTorrentFileSizeLimit();
|
||||
const auto readResult = Utils::IO::readFile(path, torrentSizeLimit);
|
||||
if (!readResult)
|
||||
return nonstd::make_unexpected(readResult.error().message);
|
||||
data = readResult.value();
|
||||
}
|
||||
catch (const std::bad_alloc &e)
|
||||
{
|
||||
return nonstd::make_unexpected(tr("Failed to allocate memory when reading file. File: \"%1\". Error: \"%2\"")
|
||||
.arg(path.toString(), QString::fromLocal8Bit(e.what())));
|
||||
}
|
||||
|
||||
return load(data);
|
||||
}
|
||||
|
||||
nonstd::expected<void, QString> TorrentInfo::saveToFile(const Path &path) const
|
||||
{
|
||||
if (!isValid())
|
||||
return nonstd::make_unexpected(tr("Invalid metadata"));
|
||||
|
||||
try
|
||||
{
|
||||
const auto torrentCreator = lt::create_torrent(*m_nativeInfo);
|
||||
const lt::entry torrentEntry = torrentCreator.generate();
|
||||
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, torrentEntry);
|
||||
if (!result)
|
||||
return result.get_unexpected();
|
||||
}
|
||||
catch (const lt::system_error &err)
|
||||
{
|
||||
return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
InfoHash TorrentInfo::infoHash() const
|
||||
{
|
||||
if (!isValid()) return {};
|
||||
|
@ -160,28 +91,6 @@ QString TorrentInfo::name() const
|
|||
return QString::fromStdString(m_nativeInfo->orig_files().name());
|
||||
}
|
||||
|
||||
QDateTime TorrentInfo::creationDate() const
|
||||
{
|
||||
if (!isValid()) return {};
|
||||
|
||||
const std::time_t date = m_nativeInfo->creation_date();
|
||||
return ((date != 0) ? QDateTime::fromSecsSinceEpoch(date) : QDateTime());
|
||||
}
|
||||
|
||||
QString TorrentInfo::creator() const
|
||||
{
|
||||
if (!isValid()) return {};
|
||||
|
||||
return QString::fromStdString(m_nativeInfo->creator());
|
||||
}
|
||||
|
||||
QString TorrentInfo::comment() const
|
||||
{
|
||||
if (!isValid()) return {};
|
||||
|
||||
return QString::fromStdString(m_nativeInfo->comment());
|
||||
}
|
||||
|
||||
bool TorrentInfo::isPrivate() const
|
||||
{
|
||||
if (!isValid()) return false;
|
||||
|
@ -270,43 +179,7 @@ qlonglong TorrentInfo::fileOffset(const int index) const
|
|||
return m_nativeInfo->orig_files().file_offset(m_nativeIndexes[index]);
|
||||
}
|
||||
|
||||
QVector<TrackerEntry> TorrentInfo::trackers() const
|
||||
{
|
||||
if (!isValid()) return {};
|
||||
|
||||
const std::vector<lt::announce_entry> trackers = m_nativeInfo->trackers();
|
||||
|
||||
QVector<TrackerEntry> ret;
|
||||
ret.reserve(static_cast<decltype(ret)::size_type>(trackers.size()));
|
||||
for (const lt::announce_entry &tracker : trackers)
|
||||
ret.append({.url = QString::fromStdString(tracker.url), .tier = tracker.tier});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QVector<QUrl> TorrentInfo::urlSeeds() const
|
||||
{
|
||||
if (!isValid()) return {};
|
||||
|
||||
const std::vector<lt::web_seed_entry> &nativeWebSeeds = m_nativeInfo->web_seeds();
|
||||
|
||||
QVector<QUrl> urlSeeds;
|
||||
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(nativeWebSeeds.size()));
|
||||
|
||||
for (const lt::web_seed_entry &webSeed : nativeWebSeeds)
|
||||
{
|
||||
#if LIBTORRENT_VERSION_NUM < 20100
|
||||
if (webSeed.type == lt::web_seed_entry::url_seed)
|
||||
urlSeeds.append(QUrl(QString::fromStdString(webSeed.url)));
|
||||
#else
|
||||
urlSeeds.append(QUrl(QString::fromStdString(webSeed.url)));
|
||||
#endif
|
||||
}
|
||||
|
||||
return urlSeeds;
|
||||
}
|
||||
|
||||
QByteArray TorrentInfo::metadata() const
|
||||
QByteArray TorrentInfo::rawData() const
|
||||
{
|
||||
if (!isValid()) return {};
|
||||
#ifdef QBT_USES_LIBTORRENT2
|
||||
|
@ -320,7 +193,7 @@ QByteArray TorrentInfo::metadata() const
|
|||
PathList TorrentInfo::filesForPiece(const int pieceIndex) const
|
||||
{
|
||||
// no checks here because fileIndicesForPiece() will return an empty list
|
||||
const QVector<int> fileIndices = fileIndicesForPiece(pieceIndex);
|
||||
const QList<int> fileIndices = fileIndicesForPiece(pieceIndex);
|
||||
|
||||
PathList res;
|
||||
res.reserve(fileIndices.size());
|
||||
|
@ -330,14 +203,14 @@ PathList TorrentInfo::filesForPiece(const int pieceIndex) const
|
|||
return res;
|
||||
}
|
||||
|
||||
QVector<int> TorrentInfo::fileIndicesForPiece(const int pieceIndex) const
|
||||
QList<int> TorrentInfo::fileIndicesForPiece(const int pieceIndex) const
|
||||
{
|
||||
if (!isValid() || (pieceIndex < 0) || (pieceIndex >= piecesCount()))
|
||||
return {};
|
||||
|
||||
const std::vector<lt::file_slice> files = m_nativeInfo->map_block(
|
||||
lt::piece_index_t {pieceIndex}, 0, m_nativeInfo->piece_size(lt::piece_index_t {pieceIndex}));
|
||||
QVector<int> res;
|
||||
QList<int> res;
|
||||
res.reserve(static_cast<decltype(res)::size_type>(files.size()));
|
||||
for (const lt::file_slice &fileSlice : files)
|
||||
{
|
||||
|
@ -349,13 +222,13 @@ QVector<int> TorrentInfo::fileIndicesForPiece(const int pieceIndex) const
|
|||
return res;
|
||||
}
|
||||
|
||||
QVector<QByteArray> TorrentInfo::pieceHashes() const
|
||||
QList<QByteArray> TorrentInfo::pieceHashes() const
|
||||
{
|
||||
if (!isValid())
|
||||
return {};
|
||||
|
||||
const int count = piecesCount();
|
||||
QVector<QByteArray> hashes;
|
||||
QList<QByteArray> hashes;
|
||||
hashes.reserve(count);
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
|
@ -366,16 +239,7 @@ QVector<QByteArray> TorrentInfo::pieceHashes() const
|
|||
|
||||
TorrentInfo::PieceRange TorrentInfo::filePieces(const Path &filePath) const
|
||||
{
|
||||
if (!isValid()) // if we do not check here the debug message will be printed, which would be not correct
|
||||
return {};
|
||||
|
||||
const int index = fileIndex(filePath);
|
||||
if (index == -1)
|
||||
{
|
||||
qDebug() << "Filename" << filePath.toString() << "was not found in torrent" << name();
|
||||
return {};
|
||||
}
|
||||
return filePieces(index);
|
||||
return filePieces(fileIndex(filePath));
|
||||
}
|
||||
|
||||
TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
|
||||
|
@ -384,10 +248,7 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
|
|||
return {};
|
||||
|
||||
if ((fileIndex < 0) || (fileIndex >= filesCount()))
|
||||
{
|
||||
qDebug() << "File index (" << fileIndex << ") is out of range for torrent" << name();
|
||||
return {};
|
||||
}
|
||||
|
||||
const lt::file_storage &files = m_nativeInfo->orig_files();
|
||||
const auto fileSize = files.file_size(m_nativeIndexes[fileIndex]);
|
||||
|
@ -450,7 +311,7 @@ std::shared_ptr<lt::torrent_info> TorrentInfo::nativeInfo() const
|
|||
return std::make_shared<lt::torrent_info>(*m_nativeInfo);
|
||||
}
|
||||
|
||||
QVector<lt::file_index_t> TorrentInfo::nativeIndexes() const
|
||||
QList<lt::file_index_t> TorrentInfo::nativeIndexes() const
|
||||
{
|
||||
return m_nativeIndexes;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 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,9 +32,8 @@
|
|||
|
||||
#include <QtContainerFwd>
|
||||
#include <QCoreApplication>
|
||||
#include <QVector>
|
||||
#include <QList>
|
||||
|
||||
#include "base/3rdparty/expected.hpp"
|
||||
#include "base/indexrange.h"
|
||||
#include "base/pathfwd.h"
|
||||
|
||||
|
@ -50,26 +49,17 @@ namespace BitTorrent
|
|||
|
||||
class TorrentInfo
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(TorrentInfo)
|
||||
|
||||
public:
|
||||
TorrentInfo() = default;
|
||||
TorrentInfo(const TorrentInfo &other) = default;
|
||||
|
||||
explicit TorrentInfo(const lt::torrent_info &nativeInfo);
|
||||
|
||||
static nonstd::expected<TorrentInfo, QString> load(const QByteArray &data) noexcept;
|
||||
static nonstd::expected<TorrentInfo, QString> loadFromFile(const Path &path) noexcept;
|
||||
nonstd::expected<void, QString> saveToFile(const Path &path) const;
|
||||
|
||||
TorrentInfo &operator=(const TorrentInfo &other);
|
||||
|
||||
bool isValid() const;
|
||||
InfoHash infoHash() const;
|
||||
QString name() const;
|
||||
QDateTime creationDate() const;
|
||||
QString creator() const;
|
||||
QString comment() const;
|
||||
bool isPrivate() const;
|
||||
qlonglong totalSize() const;
|
||||
int filesCount() const;
|
||||
|
@ -80,12 +70,9 @@ namespace BitTorrent
|
|||
PathList filePaths() const;
|
||||
qlonglong fileSize(int index) const;
|
||||
qlonglong fileOffset(int index) const;
|
||||
QVector<TrackerEntry> trackers() const;
|
||||
QVector<QUrl> urlSeeds() const;
|
||||
QByteArray metadata() const;
|
||||
PathList filesForPiece(int pieceIndex) const;
|
||||
QVector<int> fileIndicesForPiece(int pieceIndex) const;
|
||||
QVector<QByteArray> pieceHashes() const;
|
||||
QList<int> fileIndicesForPiece(int pieceIndex) const;
|
||||
QList<QByteArray> pieceHashes() const;
|
||||
|
||||
using PieceRange = IndexRange<int>;
|
||||
// returns pair of the first and the last pieces into which
|
||||
|
@ -93,10 +80,12 @@ namespace BitTorrent
|
|||
PieceRange filePieces(const Path &filePath) const;
|
||||
PieceRange filePieces(int fileIndex) const;
|
||||
|
||||
QByteArray rawData() const;
|
||||
|
||||
bool matchesInfoHash(const InfoHash &otherInfoHash) const;
|
||||
|
||||
std::shared_ptr<lt::torrent_info> nativeInfo() const;
|
||||
QVector<lt::file_index_t> nativeIndexes() const;
|
||||
QList<lt::file_index_t> nativeIndexes() const;
|
||||
|
||||
private:
|
||||
// returns file index or -1 if fileName is not found
|
||||
|
@ -106,7 +95,7 @@ namespace BitTorrent
|
|||
|
||||
// internal indexes of files (payload only, excluding any .pad files)
|
||||
// by which they are addressed in libtorrent
|
||||
QVector<lt::file_index_t> m_nativeIndexes;
|
||||
QList<lt::file_index_t> m_nativeIndexes;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -380,7 +380,7 @@ void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
|
|||
{
|
||||
// Reached max size, remove a random torrent
|
||||
if (m_torrents.size() >= MAX_TORRENTS)
|
||||
m_torrents.erase(m_torrents.begin());
|
||||
m_torrents.erase(m_torrents.cbegin());
|
||||
}
|
||||
|
||||
m_torrents[announceReq.torrentID].setPeer(announceReq.peer);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2015-2024 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,10 +28,11 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
|
||||
#include "announcetimepoint.h"
|
||||
|
||||
class QStringView;
|
||||
|
||||
namespace BitTorrent
|
||||
|
@ -59,8 +60,8 @@ namespace BitTorrent
|
|||
int numLeeches = -1;
|
||||
int numDownloaded = -1;
|
||||
|
||||
QDateTime nextAnnounceTime {};
|
||||
QDateTime minAnnounceTime {};
|
||||
AnnounceTimePoint nextAnnounceTime {};
|
||||
AnnounceTimePoint minAnnounceTime {};
|
||||
};
|
||||
|
||||
struct TrackerEntryStatus
|
||||
|
@ -76,8 +77,8 @@ namespace BitTorrent
|
|||
int numLeeches = -1;
|
||||
int numDownloaded = -1;
|
||||
|
||||
QDateTime nextAnnounceTime {};
|
||||
QDateTime minAnnounceTime {};
|
||||
AnnounceTimePoint nextAnnounceTime {};
|
||||
AnnounceTimePoint minAnnounceTime {};
|
||||
|
||||
QHash<std::pair<QString, int>, TrackerEndpointStatus> endpoints {};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -29,16 +29,24 @@
|
|||
|
||||
#include "freediskspacechecker.h"
|
||||
|
||||
#include "base/bittorrent/session.h"
|
||||
#include "base/utils/fs.h"
|
||||
|
||||
qint64 FreeDiskSpaceChecker::lastResult() const
|
||||
FreeDiskSpaceChecker::FreeDiskSpaceChecker(const Path &pathToCheck)
|
||||
: m_pathToCheck {pathToCheck}
|
||||
{
|
||||
return m_lastResult;
|
||||
}
|
||||
|
||||
Path FreeDiskSpaceChecker::pathToCheck() const
|
||||
{
|
||||
return m_pathToCheck;
|
||||
}
|
||||
|
||||
void FreeDiskSpaceChecker::setPathToCheck(const Path &newPathToCheck)
|
||||
{
|
||||
m_pathToCheck = newPathToCheck;
|
||||
}
|
||||
|
||||
void FreeDiskSpaceChecker::check()
|
||||
{
|
||||
m_lastResult = Utils::Fs::freeDiskSpaceOnPath(BitTorrent::Session::instance()->savePath());
|
||||
emit checked(m_lastResult);
|
||||
emit checked(Utils::Fs::freeDiskSpaceOnPath(m_pathToCheck));
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Bittorrent Client using Qt and libtorrent.
|
||||
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
|
||||
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
|
@ -31,15 +31,18 @@
|
|||
|
||||
#include <QObject>
|
||||
|
||||
#include "base/path.h"
|
||||
|
||||
class FreeDiskSpaceChecker final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(FreeDiskSpaceChecker)
|
||||
|
||||
public:
|
||||
using QObject::QObject;
|
||||
FreeDiskSpaceChecker(const Path &pathToCheck);
|
||||
|
||||
qint64 lastResult() const;
|
||||
Path pathToCheck() const;
|
||||
void setPathToCheck(const Path &newPathToCheck);
|
||||
|
||||
public slots:
|
||||
void check();
|
||||
|
@ -48,5 +51,5 @@ signals:
|
|||
void checked(qint64 freeSpaceSize);
|
||||
|
||||
private:
|
||||
qint64 m_lastResult = 0;
|
||||
Path m_pathToCheck;
|
||||
};
|
|
@ -44,6 +44,7 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
|||
, m_requestHandler(requestHandler)
|
||||
{
|
||||
m_socket->setParent(this);
|
||||
connect(m_socket, &QAbstractSocket::disconnected, this, &Connection::closed);
|
||||
|
||||
// reserve common size for requests, don't use the max allowed size which is too big for
|
||||
// memory constrained platforms
|
||||
|
@ -62,11 +63,6 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
|
|||
});
|
||||
}
|
||||
|
||||
Connection::~Connection()
|
||||
{
|
||||
m_socket->close();
|
||||
}
|
||||
|
||||
void Connection::read()
|
||||
{
|
||||
// reuse existing buffer and avoid unnecessary memory allocation/relocation
|
||||
|
@ -159,12 +155,16 @@ void Connection::read()
|
|||
sendResponse(resp);
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
m_receivedData.slice(result.frameSize);
|
||||
#else
|
||||
m_receivedData.remove(0, result.frameSize);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
Q_UNREACHABLE();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -182,11 +182,6 @@ bool Connection::hasExpired(const qint64 timeout) const
|
|||
&& m_idleTimer.hasExpired(timeout);
|
||||
}
|
||||
|
||||
bool Connection::isClosed() const
|
||||
{
|
||||
return (m_socket->state() == QAbstractSocket::UnconnectedState);
|
||||
}
|
||||
|
||||
bool Connection::acceptsGzipEncoding(QString codings)
|
||||
{
|
||||
// [rfc7231] 5.3.4. Accept-Encoding
|
||||
|
|
|
@ -47,10 +47,11 @@ namespace Http
|
|||
|
||||
public:
|
||||
Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObject *parent = nullptr);
|
||||
~Connection();
|
||||
|
||||
bool hasExpired(qint64 timeout) const;
|
||||
bool isClosed() const;
|
||||
|
||||
signals:
|
||||
void closed();
|
||||
|
||||
private:
|
||||
static bool acceptsGzipEncoding(QString codings);
|
||||
|
|
|
@ -50,7 +50,7 @@ using QStringPair = std::pair<QString, QString>;
|
|||
|
||||
namespace
|
||||
{
|
||||
const QByteArray EOH = QByteArray(CRLF).repeated(2);
|
||||
const QByteArray EOH = CRLF.repeated(2);
|
||||
|
||||
const QByteArrayView viewWithoutEndingWith(const QByteArrayView in, const QByteArrayView str)
|
||||
{
|
||||
|
@ -69,8 +69,8 @@ namespace
|
|||
return false;
|
||||
}
|
||||
|
||||
const QString name = line.left(i).trimmed().toString().toLower();
|
||||
const QString value = line.mid(i + 1).trimmed().toString();
|
||||
const QString name = line.first(i).trimmed().toString().toLower();
|
||||
const QString value = line.sliced(i + 1).trimmed().toString();
|
||||
out[name] = value;
|
||||
|
||||
return true;
|
||||
|
@ -164,7 +164,7 @@ bool RequestParser::parseStartLines(const QStringView data)
|
|||
if (line.at(0).isSpace() && !requestLines.isEmpty())
|
||||
{
|
||||
// continuation of previous line
|
||||
requestLines.last() += line.toString();
|
||||
requestLines.last() += line;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -204,15 +204,15 @@ bool RequestParser::parseRequestLine(const QString &line)
|
|||
m_request.method = match.captured(1);
|
||||
|
||||
// Request Target
|
||||
const QByteArray url {match.captured(2).toLatin1()};
|
||||
const QByteArray url {match.capturedView(2).toLatin1()};
|
||||
const int sepPos = url.indexOf('?');
|
||||
const QByteArrayView pathComponent = ((sepPos == -1) ? url : QByteArrayView(url).mid(0, sepPos));
|
||||
const QByteArrayView pathComponent = ((sepPos == -1) ? url : QByteArrayView(url).first(sepPos));
|
||||
|
||||
m_request.path = QString::fromUtf8(QByteArray::fromPercentEncoding(asQByteArray(pathComponent)));
|
||||
|
||||
if (sepPos >= 0)
|
||||
{
|
||||
const QByteArrayView query = QByteArrayView(url).mid(sepPos + 1);
|
||||
const QByteArrayView query = QByteArrayView(url).sliced(sepPos + 1);
|
||||
|
||||
// [rfc3986] 2.4 When to Encode or Decode
|
||||
// URL components should be separated before percent-decoding
|
||||
|
@ -221,13 +221,11 @@ bool RequestParser::parseRequestLine(const QString &line)
|
|||
const int eqCharPos = param.indexOf('=');
|
||||
if (eqCharPos <= 0) continue; // ignores params without name
|
||||
|
||||
const QByteArrayView nameComponent = param.mid(0, eqCharPos);
|
||||
const QByteArrayView valueComponent = param.mid(eqCharPos + 1);
|
||||
const QByteArrayView nameComponent = param.first(eqCharPos);
|
||||
const QByteArrayView valueComponent = param.sliced(eqCharPos + 1);
|
||||
const QString paramName = QString::fromUtf8(
|
||||
QByteArray::fromPercentEncoding(asQByteArray(nameComponent)).replace('+', ' '));
|
||||
const QByteArray paramValue = valueComponent.isNull()
|
||||
? QByteArray("")
|
||||
: QByteArray::fromPercentEncoding(asQByteArray(valueComponent)).replace('+', ' ');
|
||||
const QByteArray paramValue = QByteArray::fromPercentEncoding(asQByteArray(valueComponent)).replace('+', ' ');
|
||||
|
||||
m_request.query[paramName] = paramValue;
|
||||
}
|
||||
|
@ -272,7 +270,7 @@ bool RequestParser::parsePostMessage(const QByteArrayView data)
|
|||
return false;
|
||||
}
|
||||
|
||||
const QByteArray delimiter = Utils::String::unquote(QStringView(contentType).mid(idx + boundaryFieldName.size())).toLatin1();
|
||||
const QByteArray delimiter = Utils::String::unquote(QStringView(contentType).sliced(idx + boundaryFieldName.size())).toLatin1();
|
||||
if (delimiter.isEmpty())
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "boundary delimiter field empty!";
|
||||
|
@ -281,7 +279,7 @@ bool RequestParser::parsePostMessage(const QByteArrayView data)
|
|||
|
||||
// split data by "dash-boundary"
|
||||
const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF;
|
||||
QList<QByteArrayView> multipart = splitToViews(data, dashDelimiter, Qt::SkipEmptyParts);
|
||||
QList<QByteArrayView> multipart = splitToViews(data, dashDelimiter);
|
||||
if (multipart.isEmpty())
|
||||
{
|
||||
qWarning() << Q_FUNC_INFO << "multipart empty";
|
||||
|
@ -312,8 +310,8 @@ bool RequestParser::parseFormData(const QByteArrayView data)
|
|||
return false;
|
||||
}
|
||||
|
||||
const QString headers = QString::fromLatin1(data.mid(0, eohPos));
|
||||
const QByteArrayView payload = viewWithoutEndingWith(data.mid((eohPos + EOH.size()), data.size()), CRLF);
|
||||
const QString headers = QString::fromLatin1(data.first(eohPos));
|
||||
const QByteArrayView payload = viewWithoutEndingWith(data.sliced((eohPos + EOH.size())), CRLF);
|
||||
|
||||
HeaderMap headersMap;
|
||||
const QList<QStringView> headerLines = QStringView(headers).split(QString::fromLatin1(CRLF), Qt::SkipEmptyParts);
|
||||
|
@ -330,14 +328,14 @@ bool RequestParser::parseFormData(const QByteArrayView data)
|
|||
if (idx < 0)
|
||||
continue;
|
||||
|
||||
const QString name = directive.left(idx).trimmed().toString().toLower();
|
||||
const QString value = Utils::String::unquote(directive.mid(idx + 1).trimmed()).toString();
|
||||
const QString name = directive.first(idx).trimmed().toString().toLower();
|
||||
const QString value = Utils::String::unquote(directive.sliced(idx + 1).trimmed()).toString();
|
||||
headersMap[name] = value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!parseHeaderLine(line.toString(), headersMap))
|
||||
if (!parseHeaderLine(line, headersMap))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -348,7 +346,8 @@ bool RequestParser::parseFormData(const QByteArrayView data)
|
|||
|
||||
if (headersMap.contains(filename))
|
||||
{
|
||||
m_request.files.append({headersMap[filename], headersMap[HEADER_CONTENT_TYPE], payload.toByteArray()});
|
||||
m_request.files.append({.filename = headersMap[filename], .type = headersMap[HEADER_CONTENT_TYPE]
|
||||
, .data = payload.toByteArray()});
|
||||
}
|
||||
else if (headersMap.contains(name))
|
||||
{
|
||||
|
|
|
@ -32,15 +32,18 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
|
||||
#include <QtLogging>
|
||||
#include <QNetworkProxy>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslCipher>
|
||||
#include <QSslConfiguration>
|
||||
#include <QSslKey>
|
||||
#include <QSslSocket>
|
||||
#include <QStringList>
|
||||
#include <QTimer>
|
||||
|
||||
#include "base/algorithm.h"
|
||||
#include "base/global.h"
|
||||
#include "base/utils/net.h"
|
||||
#include "base/utils/sslkey.h"
|
||||
|
@ -98,13 +101,12 @@ using namespace Http;
|
|||
Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
||||
: QTcpServer(parent)
|
||||
, m_requestHandler(requestHandler)
|
||||
, m_sslConfig {QSslConfiguration::defaultConfiguration()}
|
||||
{
|
||||
setProxy(QNetworkProxy::NoProxy);
|
||||
|
||||
QSslConfiguration sslConf {QSslConfiguration::defaultConfiguration()};
|
||||
sslConf.setProtocol(QSsl::TlsV1_2OrLater);
|
||||
sslConf.setCiphers(safeCipherList());
|
||||
QSslConfiguration::setDefaultConfiguration(sslConf);
|
||||
m_sslConfig.setCiphers(safeCipherList());
|
||||
m_sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
|
||||
auto *dropConnectionTimer = new QTimer(this);
|
||||
connect(dropConnectionTimer, &QTimer::timeout, this, &Server::dropTimedOutConnection);
|
||||
|
@ -113,32 +115,35 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent)
|
|||
|
||||
void Server::incomingConnection(const qintptr socketDescriptor)
|
||||
{
|
||||
if (m_connections.size() >= CONNECTIONS_LIMIT) return;
|
||||
|
||||
QTcpSocket *serverSocket = nullptr;
|
||||
if (m_https)
|
||||
serverSocket = new QSslSocket(this);
|
||||
else
|
||||
serverSocket = new QTcpSocket(this);
|
||||
|
||||
std::unique_ptr<QTcpSocket> serverSocket = isHttps() ? std::make_unique<QSslSocket>(this) : std::make_unique<QTcpSocket>(this);
|
||||
if (!serverSocket->setSocketDescriptor(socketDescriptor))
|
||||
return;
|
||||
|
||||
if (m_connections.size() >= CONNECTIONS_LIMIT)
|
||||
{
|
||||
delete serverSocket;
|
||||
qWarning("Too many connections. Exceeded CONNECTIONS_LIMIT (%d). Connection closed.", CONNECTIONS_LIMIT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_https)
|
||||
try
|
||||
{
|
||||
static_cast<QSslSocket *>(serverSocket)->setProtocol(QSsl::SecureProtocols);
|
||||
static_cast<QSslSocket *>(serverSocket)->setPrivateKey(m_key);
|
||||
static_cast<QSslSocket *>(serverSocket)->setLocalCertificateChain(m_certificates);
|
||||
static_cast<QSslSocket *>(serverSocket)->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
static_cast<QSslSocket *>(serverSocket)->startServerEncryption();
|
||||
}
|
||||
if (isHttps())
|
||||
{
|
||||
auto *sslSocket = static_cast<QSslSocket *>(serverSocket.get());
|
||||
sslSocket->setSslConfiguration(m_sslConfig);
|
||||
sslSocket->startServerEncryption();
|
||||
}
|
||||
|
||||
auto *c = new Connection(serverSocket, m_requestHandler, this);
|
||||
m_connections.insert(c);
|
||||
connect(serverSocket, &QAbstractSocket::disconnected, this, [c, this]() { removeConnection(c); });
|
||||
auto *connection = new Connection(serverSocket.release(), m_requestHandler, this);
|
||||
m_connections.insert(connection);
|
||||
connect(connection, &Connection::closed, this, [this, connection] { removeConnection(connection); });
|
||||
}
|
||||
catch (const std::bad_alloc &exception)
|
||||
{
|
||||
// drop the connection instead of throwing exception and crash
|
||||
qWarning("Failed to allocate memory for HTTP connection. Connection closed.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Server::removeConnection(Connection *connection)
|
||||
|
@ -170,17 +175,17 @@ bool Server::setupHttps(const QByteArray &certificates, const QByteArray &privat
|
|||
return false;
|
||||
}
|
||||
|
||||
m_key = key;
|
||||
m_certificates = certs;
|
||||
m_sslConfig.setLocalCertificateChain(certs);
|
||||
m_sslConfig.setPrivateKey(key);
|
||||
m_https = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Server::disableHttps()
|
||||
{
|
||||
m_sslConfig.setLocalCertificateChain({});
|
||||
m_sslConfig.setPrivateKey({});
|
||||
m_https = false;
|
||||
m_certificates.clear();
|
||||
m_key.clear();
|
||||
}
|
||||
|
||||
bool Server::isHttps() const
|
||||
|
|
|
@ -31,8 +31,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <QSet>
|
||||
#include <QSslCertificate>
|
||||
#include <QSslKey>
|
||||
#include <QSslConfiguration>
|
||||
#include <QTcpServer>
|
||||
|
||||
namespace Http
|
||||
|
@ -63,7 +62,6 @@ namespace Http
|
|||
QSet<Connection *> m_connections; // for tracking persistent connections
|
||||
|
||||
bool m_https = false;
|
||||
QList<QSslCertificate> m_certificates;
|
||||
QSslKey m_key;
|
||||
QSslConfiguration m_sslConfig;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -29,9 +29,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
#include <QHostAddress>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include "base/global.h"
|
||||
|
||||
|
@ -57,6 +60,7 @@ namespace Http
|
|||
inline const QString HEADER_X_CONTENT_TYPE_OPTIONS = u"x-content-type-options"_s;
|
||||
inline const QString HEADER_X_FORWARDED_FOR = u"x-forwarded-for"_s;
|
||||
inline const QString HEADER_X_FORWARDED_HOST = u"x-forwarded-host"_s;
|
||||
inline const QString HEADER_X_FORWARDED_PROTO = u"X-forwarded-proto"_s;
|
||||
inline const QString HEADER_X_FRAME_OPTIONS = u"x-frame-options"_s;
|
||||
inline const QString HEADER_X_XSS_PROTECTION = u"x-xss-protection"_s;
|
||||
|
||||
|
@ -75,7 +79,7 @@ namespace Http
|
|||
inline const QString CONTENT_TYPE_FORM_DATA = u"multipart/form-data"_s;
|
||||
|
||||
// portability: "\r\n" doesn't guarantee mapping to the correct symbol
|
||||
inline const char CRLF[] = {0x0D, 0x0A, '\0'};
|
||||
inline const QByteArray CRLF = QByteArrayLiteral("\x0D\x0A");
|
||||
|
||||
struct Environment
|
||||
{
|
||||
|
@ -109,7 +113,7 @@ namespace Http
|
|||
HeaderMap headers;
|
||||
QHash<QString, QByteArray> query;
|
||||
QHash<QString, QString> posts;
|
||||
QVector<UploadedFile> files;
|
||||
QList<UploadedFile> files;
|
||||
};
|
||||
|
||||
struct ResponseStatus
|
||||
|
|
|
@ -31,14 +31,14 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QVector>
|
||||
#include <QList>
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
QVector<T> loadFromBuffer(const boost::circular_buffer_space_optimized<T> &src, const int offset = 0)
|
||||
QList<T> loadFromBuffer(const boost::circular_buffer_space_optimized<T> &src, const int offset = 0)
|
||||
{
|
||||
QVector<T> ret;
|
||||
QList<T> ret;
|
||||
ret.reserve(static_cast<typename decltype(ret)::size_type>(src.size()) - offset);
|
||||
std::copy((src.begin() + offset), src.end(), std::back_inserter(ret));
|
||||
return ret;
|
||||
|
@ -90,7 +90,7 @@ void Logger::addPeer(const QString &ip, const bool blocked, const QString &reaso
|
|||
emit newLogPeer(msg);
|
||||
}
|
||||
|
||||
QVector<Log::Msg> Logger::getMessages(const int lastKnownId) const
|
||||
QList<Log::Msg> Logger::getMessages(const int lastKnownId) const
|
||||
{
|
||||
const QReadLocker locker(&m_lock);
|
||||
|
||||
|
@ -106,7 +106,7 @@ QVector<Log::Msg> Logger::getMessages(const int lastKnownId) const
|
|||
return loadFromBuffer(m_messages, (size - diff));
|
||||
}
|
||||
|
||||
QVector<Log::Peer> Logger::getPeers(const int lastKnownId) const
|
||||
QList<Log::Peer> Logger::getPeers(const int lastKnownId) const
|
||||
{
|
||||
const QReadLocker locker(&m_lock);
|
||||
|
||||
|
|
|
@ -81,8 +81,8 @@ public:
|
|||
|
||||
void addMessage(const QString &message, const Log::MsgType &type = Log::NORMAL);
|
||||
void addPeer(const QString &ip, bool blocked, const QString &reason = {});
|
||||
QVector<Log::Msg> getMessages(int lastKnownId = -1) const;
|
||||
QVector<Log::Peer> getPeers(int lastKnownId = -1) const;
|
||||
QList<Log::Msg> getMessages(int lastKnownId = -1) const;
|
||||
QList<Log::Peer> getPeers(int lastKnownId = -1) const;
|
||||
|
||||
signals:
|
||||
void newLogMessage(const Log::Msg &message);
|
||||
|
|
|
@ -96,9 +96,9 @@ void DNSUpdater::ipRequestFinished(const DownloadResult &result)
|
|||
const QRegularExpressionMatch ipRegexMatch = QRegularExpression(u"Current IP Address:\\s+([^<]+)</body>"_s).match(QString::fromUtf8(result.data));
|
||||
if (ipRegexMatch.hasMatch())
|
||||
{
|
||||
QString ipStr = ipRegexMatch.captured(1);
|
||||
const QString ipStr = ipRegexMatch.captured(1);
|
||||
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr;
|
||||
QHostAddress newIp(ipStr);
|
||||
const QHostAddress newIp {ipStr};
|
||||
if (!newIp.isNull())
|
||||
{
|
||||
if (m_lastIP != newIp)
|
||||
|
@ -153,7 +153,7 @@ QString DNSUpdater::getUpdateUrl() const
|
|||
break;
|
||||
default:
|
||||
qWarning() << "Unrecognized Dynamic DNS service!";
|
||||
Q_ASSERT(false);
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
url.setPath(u"/nic/update"_s);
|
||||
|
@ -305,7 +305,7 @@ QUrl DNSUpdater::getRegistrationUrl(const DNS::Service service)
|
|||
case DNS::Service::NoIP:
|
||||
return {u"https://www.noip.com/remote-access"_s};
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
Q_UNREACHABLE();
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
|
|
|
@ -148,10 +148,20 @@ Net::DownloadManager::DownloadManager(QObject *parent)
|
|||
QStringList errorList;
|
||||
for (const QSslError &error : errors)
|
||||
errorList += error.errorString();
|
||||
LogMsg(tr("Ignoring SSL error, URL: \"%1\", errors: \"%2\"").arg(reply->url().toString(), errorList.join(u". ")), Log::WARNING);
|
||||
|
||||
// Ignore all SSL errors
|
||||
reply->ignoreSslErrors();
|
||||
QString errorMsg;
|
||||
if (!Preferences::instance()->isIgnoreSSLErrors())
|
||||
{
|
||||
errorMsg = tr("SSL error, URL: \"%1\", errors: \"%2\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMsg = tr("Ignoring SSL error, URL: \"%1\", errors: \"%2\"");
|
||||
// Ignore all SSL errors
|
||||
reply->ignoreSslErrors();
|
||||
}
|
||||
|
||||
LogMsg(errorMsg.arg(reply->url().toString(), errorList.join(u". ")), Log::WARNING);
|
||||
});
|
||||
|
||||
connect(ProxyConfigurationManager::instance(), &ProxyConfigurationManager::proxyConfigurationChanged
|
||||
|
@ -255,38 +265,37 @@ void Net::DownloadManager::applyProxySettings()
|
|||
const auto *proxyManager = ProxyConfigurationManager::instance();
|
||||
const ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
|
||||
|
||||
m_proxy = QNetworkProxy(QNetworkProxy::NoProxy);
|
||||
|
||||
if ((proxyConfig.type == Net::ProxyType::None) || (proxyConfig.type == ProxyType::SOCKS4))
|
||||
return;
|
||||
|
||||
// Proxy enabled
|
||||
if (proxyConfig.type == ProxyType::SOCKS5)
|
||||
switch (proxyConfig.type)
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "using SOCKS proxy";
|
||||
m_proxy.setType(QNetworkProxy::Socks5Proxy);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << Q_FUNC_INFO << "using HTTP proxy";
|
||||
m_proxy.setType(QNetworkProxy::HttpProxy);
|
||||
}
|
||||
case Net::ProxyType::None:
|
||||
case Net::ProxyType::SOCKS4:
|
||||
m_proxy = QNetworkProxy(QNetworkProxy::NoProxy);
|
||||
break;
|
||||
|
||||
m_proxy.setHostName(proxyConfig.ip);
|
||||
m_proxy.setPort(proxyConfig.port);
|
||||
case Net::ProxyType::HTTP:
|
||||
m_proxy = QNetworkProxy(
|
||||
QNetworkProxy::HttpProxy
|
||||
, proxyConfig.ip
|
||||
, proxyConfig.port
|
||||
, (proxyConfig.authEnabled ? proxyConfig.username : QString())
|
||||
, (proxyConfig.authEnabled ? proxyConfig.password : QString()));
|
||||
m_proxy.setCapabilities(proxyConfig.hostnameLookupEnabled
|
||||
? (m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability)
|
||||
: (m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability));
|
||||
break;
|
||||
|
||||
// Authentication?
|
||||
if (proxyConfig.authEnabled)
|
||||
{
|
||||
qDebug("Proxy requires authentication, authenticating...");
|
||||
m_proxy.setUser(proxyConfig.username);
|
||||
m_proxy.setPassword(proxyConfig.password);
|
||||
}
|
||||
|
||||
if (proxyConfig.hostnameLookupEnabled)
|
||||
m_proxy.setCapabilities(m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability);
|
||||
else
|
||||
m_proxy.setCapabilities(m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability);
|
||||
case Net::ProxyType::SOCKS5:
|
||||
m_proxy = QNetworkProxy(
|
||||
QNetworkProxy::Socks5Proxy
|
||||
, proxyConfig.ip
|
||||
, proxyConfig.port
|
||||
, (proxyConfig.authEnabled ? proxyConfig.username : QString())
|
||||
, (proxyConfig.authEnabled ? proxyConfig.password : QString()));
|
||||
m_proxy.setCapabilities(proxyConfig.hostnameLookupEnabled
|
||||
? (m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability)
|
||||
: (m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability));
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
void Net::DownloadManager::processWaitingJobs(const ServiceID &serviceID)
|
||||
|
@ -310,14 +319,11 @@ void Net::DownloadManager::processRequest(DownloadHandlerImpl *downloadHandler)
|
|||
|
||||
const DownloadRequest downloadRequest = downloadHandler->downloadRequest();
|
||||
QNetworkRequest request {downloadRequest.url()};
|
||||
|
||||
if (downloadRequest.userAgent().isEmpty())
|
||||
request.setRawHeader("User-Agent", getBrowserUserAgent());
|
||||
else
|
||||
request.setRawHeader("User-Agent", downloadRequest.userAgent().toUtf8());
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, (downloadRequest.userAgent().isEmpty()
|
||||
? getBrowserUserAgent() : downloadRequest.userAgent().toUtf8()));
|
||||
|
||||
// Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents
|
||||
request.setRawHeader("Referer", request.url().toEncoded().data());
|
||||
request.setRawHeader("Referer", request.url().toEncoded());
|
||||
#ifdef QT_NO_COMPRESS
|
||||
// The macro "QT_NO_COMPRESS" defined in QT will disable the zlib related features
|
||||
// and reply data auto-decompression in QT will also be disabled. But we can support
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include "geoipdatabase.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
|
@ -41,7 +42,7 @@ namespace
|
|||
{
|
||||
const qint32 MAX_FILE_SIZE = 67108864; // 64MB
|
||||
const quint32 MAX_METADATA_SIZE = 131072; // 128KB
|
||||
const char METADATA_BEGIN_MARK[] = "\xab\xcd\xefMaxMind.com";
|
||||
const QByteArray METADATA_BEGIN_MARK = QByteArrayLiteral("\xab\xcd\xefMaxMind.com");
|
||||
const char DATA_SECTION_SEPARATOR[16] = {0};
|
||||
|
||||
enum class DataType
|
||||
|
@ -309,7 +310,7 @@ QVariantHash GeoIPDatabase::readMetadata() const
|
|||
{
|
||||
if (m_size > MAX_METADATA_SIZE)
|
||||
index += (m_size - MAX_METADATA_SIZE); // from begin of all data
|
||||
auto offset = static_cast<quint32>(index + strlen(METADATA_BEGIN_MARK));
|
||||
auto offset = static_cast<quint32>(index + METADATA_BEGIN_MARK.size());
|
||||
const QVariant metadata = readDataField(offset);
|
||||
if (metadata.userType() == QMetaType::QVariantHash)
|
||||
return metadata.toHash();
|
||||
|
|
|
@ -456,15 +456,15 @@ void GeoIPManager::downloadFinished(const DownloadResult &result)
|
|||
Utils::Fs::mkpath(targetPath);
|
||||
|
||||
const auto path = targetPath / Path(GEODB_FILENAME);
|
||||
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
|
||||
if (result)
|
||||
const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, data);
|
||||
if (saveResult)
|
||||
{
|
||||
LogMsg(tr("Successfully updated IP geolocation database."), Log::INFO);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogMsg(tr("Couldn't save downloaded IP geolocation database file. Reason: %1")
|
||||
.arg(result.error()), Log::WARNING);
|
||||
.arg(saveResult.error()), Log::WARNING);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -148,8 +148,7 @@ void Smtp::sendMail(const QString &from, const QString &to, const QString &subje
|
|||
// Encode the body in base64
|
||||
QString crlfBody = body;
|
||||
const QByteArray b = crlfBody.replace(u"\n"_s, u"\r\n"_s).toUtf8().toBase64();
|
||||
const int ct = b.length();
|
||||
for (int i = 0; i < ct; i += 78)
|
||||
for (int i = 0, end = b.length(); i < end; i += 78)
|
||||
m_message += b.mid(i, 78);
|
||||
m_from = from;
|
||||
m_rcpt = to;
|
||||
|
@ -190,8 +189,12 @@ void Smtp::readyRead()
|
|||
{
|
||||
const int pos = m_buffer.indexOf("\r\n");
|
||||
if (pos < 0) return; // Loop exit condition
|
||||
const QByteArray line = m_buffer.left(pos);
|
||||
const QByteArray line = m_buffer.first(pos);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
m_buffer.slice(pos + 2);
|
||||
#else
|
||||
m_buffer.remove(0, (pos + 2));
|
||||
#endif
|
||||
qDebug() << "Response line:" << line;
|
||||
// Extract response code
|
||||
const QByteArray code = line.left(3);
|
||||
|
@ -565,29 +568,11 @@ void Smtp::logError(const QString &msg)
|
|||
|
||||
QString Smtp::getCurrentDateTime() const
|
||||
{
|
||||
// return date & time in the format specified in RFC 2822, section 3.3
|
||||
const QDateTime nowDateTime = QDateTime::currentDateTime();
|
||||
const QDate nowDate = nowDateTime.date();
|
||||
const QLocale eng(QLocale::English);
|
||||
|
||||
const QString timeStr = nowDateTime.time().toString(u"HH:mm:ss");
|
||||
const QString weekDayStr = eng.dayName(nowDate.dayOfWeek(), QLocale::ShortFormat);
|
||||
const QString dayStr = QString::number(nowDate.day());
|
||||
const QString monthStr = eng.monthName(nowDate.month(), QLocale::ShortFormat);
|
||||
const QString yearStr = QString::number(nowDate.year());
|
||||
|
||||
QDateTime tmp = nowDateTime;
|
||||
tmp.setTimeSpec(Qt::UTC);
|
||||
const int timeOffsetHour = nowDateTime.secsTo(tmp) / 3600;
|
||||
const int timeOffsetMin = nowDateTime.secsTo(tmp) / 60 - (60 * timeOffsetHour);
|
||||
const int timeOffset = timeOffsetHour * 100 + timeOffsetMin;
|
||||
// buf size = 11 to avoid format truncation warnings from snprintf
|
||||
char buf[11] = {0};
|
||||
std::snprintf(buf, sizeof(buf), "%+05d", timeOffset);
|
||||
const auto timeOffsetStr = QString::fromUtf8(buf);
|
||||
|
||||
const QString ret = weekDayStr + u", " + dayStr + u' ' + monthStr + u' ' + yearStr + u' ' + timeStr + u' ' + timeOffsetStr;
|
||||
return ret;
|
||||
// [rfc2822] 3.3. Date and Time Specification
|
||||
const auto now = QDateTime::currentDateTime();
|
||||
const QLocale eng {QLocale::English};
|
||||
const QString weekday = eng.dayName(now.date().dayOfWeek(), QLocale::ShortFormat);
|
||||
return (weekday + u", " + now.toString(Qt::RFC2822Date));
|
||||
}
|
||||
|
||||
void Smtp::error(QAbstractSocket::SocketError socketError)
|
||||
|
|
|
@ -43,7 +43,6 @@ class QSslSocket;
|
|||
#else
|
||||
class QTcpSocket;
|
||||
#endif
|
||||
class QTextCodec;
|
||||
|
||||
namespace Net
|
||||
{
|
||||
|
|
|
@ -94,7 +94,13 @@ bool Path::isValid() const
|
|||
#if defined(Q_OS_WIN)
|
||||
QStringView view = m_pathStr;
|
||||
if (hasDriveLetter(view))
|
||||
view = view.mid(3);
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
view.slice(3);
|
||||
#else
|
||||
view = view.sliced(3);
|
||||
#endif
|
||||
}
|
||||
|
||||
// \\37 is using base-8 number system
|
||||
const QRegularExpression regex {u"[\\0-\\37:?\"*<>|]"_s};
|
||||
|
@ -147,9 +153,9 @@ Path Path::rootItem() const
|
|||
#ifdef Q_OS_WIN
|
||||
// should be `c:/` instead of `c:`
|
||||
if ((slashIndex == 2) && hasDriveLetter(m_pathStr))
|
||||
return createUnchecked(m_pathStr.left(slashIndex + 1));
|
||||
return createUnchecked(m_pathStr.first(slashIndex + 1));
|
||||
#endif
|
||||
return createUnchecked(m_pathStr.left(slashIndex));
|
||||
return createUnchecked(m_pathStr.first(slashIndex));
|
||||
}
|
||||
|
||||
Path Path::parentPath() const
|
||||
|
@ -167,9 +173,9 @@ Path Path::parentPath() const
|
|||
// should be `c:/` instead of `c:`
|
||||
// Windows "drive letter" is limited to one alphabet
|
||||
if ((slashIndex == 2) && hasDriveLetter(m_pathStr))
|
||||
return (m_pathStr.size() == 3) ? Path() : createUnchecked(m_pathStr.left(slashIndex + 1));
|
||||
return (m_pathStr.size() == 3) ? Path() : createUnchecked(m_pathStr.first(slashIndex + 1));
|
||||
#endif
|
||||
return createUnchecked(m_pathStr.left(slashIndex));
|
||||
return createUnchecked(m_pathStr.first(slashIndex));
|
||||
}
|
||||
|
||||
QString Path::filename() const
|
||||
|
@ -178,7 +184,7 @@ QString Path::filename() const
|
|||
if (slashIndex == -1)
|
||||
return m_pathStr;
|
||||
|
||||
return m_pathStr.mid(slashIndex + 1);
|
||||
return m_pathStr.sliced(slashIndex + 1);
|
||||
}
|
||||
|
||||
QString Path::extension() const
|
||||
|
@ -188,9 +194,9 @@ QString Path::extension() const
|
|||
return (u"." + suffix);
|
||||
|
||||
const int slashIndex = m_pathStr.lastIndexOf(u'/');
|
||||
const auto filename = QStringView(m_pathStr).mid(slashIndex + 1);
|
||||
const auto filename = QStringView(m_pathStr).sliced(slashIndex + 1);
|
||||
const int dotIndex = filename.lastIndexOf(u'.', -2);
|
||||
return ((dotIndex == -1) ? QString() : filename.mid(dotIndex).toString());
|
||||
return ((dotIndex == -1) ? QString() : filename.sliced(dotIndex).toString());
|
||||
}
|
||||
|
||||
bool Path::hasExtension(const QStringView ext) const
|
||||
|
@ -293,7 +299,7 @@ Path Path::commonPath(const Path &left, const Path &right)
|
|||
if (commonItemsCount > 0)
|
||||
commonPathSize += (commonItemsCount - 1); // size of intermediate separators
|
||||
|
||||
return Path::createUnchecked(left.m_pathStr.left(commonPathSize));
|
||||
return Path::createUnchecked(left.m_pathStr.first(commonPathSize));
|
||||
}
|
||||
|
||||
Path Path::findRootFolder(const PathList &filePaths)
|
||||
|
@ -322,7 +328,13 @@ void Path::stripRootFolder(PathList &filePaths)
|
|||
return;
|
||||
|
||||
for (Path &filePath : filePaths)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
filePath.m_pathStr.slice(commonRootFolder.m_pathStr.size() + 1);
|
||||
#else
|
||||
filePath.m_pathStr.remove(0, (commonRootFolder.m_pathStr.size() + 1));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void Path::addRootFolder(PathList &filePaths, const Path &rootFolder)
|
||||
|
|
|
@ -134,17 +134,17 @@ void Preferences::setCustomUIThemePath(const Path &path)
|
|||
setValue(u"Preferences/General/CustomUIThemePath"_s, path);
|
||||
}
|
||||
|
||||
bool Preferences::deleteTorrentFilesAsDefault() const
|
||||
bool Preferences::removeTorrentContent() const
|
||||
{
|
||||
return value(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setDeleteTorrentFilesAsDefault(const bool del)
|
||||
void Preferences::setRemoveTorrentContent(const bool remove)
|
||||
{
|
||||
if (del == deleteTorrentFilesAsDefault())
|
||||
if (remove == removeTorrentContent())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, del);
|
||||
setValue(u"Preferences/General/DeleteTorrentsFilesAsDefault"_s, remove);
|
||||
}
|
||||
|
||||
bool Preferences::confirmOnExit() const
|
||||
|
@ -359,6 +359,32 @@ void Preferences::setStatusbarDisplayed(const bool displayed)
|
|||
setValue(u"Preferences/General/StatusbarDisplayed"_s, displayed);
|
||||
}
|
||||
|
||||
bool Preferences::isStatusbarFreeDiskSpaceDisplayed() const
|
||||
{
|
||||
return value(u"Preferences/General/StatusbarFreeDiskSpaceDisplayed"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setStatusbarFreeDiskSpaceDisplayed(const bool displayed)
|
||||
{
|
||||
if (displayed == isStatusbarFreeDiskSpaceDisplayed())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/General/StatusbarFreeDiskSpaceDisplayed"_s, displayed);
|
||||
}
|
||||
|
||||
bool Preferences::isStatusbarExternalIPDisplayed() const
|
||||
{
|
||||
return value(u"Preferences/General/StatusbarExternalIPDisplayed"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setStatusbarExternalIPDisplayed(const bool displayed)
|
||||
{
|
||||
if (displayed == isStatusbarExternalIPDisplayed())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/General/StatusbarExternalIPDisplayed"_s, displayed);
|
||||
}
|
||||
|
||||
bool Preferences::isSplashScreenDisabled() const
|
||||
{
|
||||
return value(u"Preferences/General/NoSplashScreen"_s, true);
|
||||
|
@ -429,6 +455,19 @@ void Preferences::setWinStartup(const bool b)
|
|||
settings.remove(profileID);
|
||||
}
|
||||
}
|
||||
|
||||
QString Preferences::getStyle() const
|
||||
{
|
||||
return value<QString>(u"Appearance/Style"_s);
|
||||
}
|
||||
|
||||
void Preferences::setStyle(const QString &styleName)
|
||||
{
|
||||
if (styleName == getStyle())
|
||||
return;
|
||||
|
||||
setValue(u"Appearance/Style"_s, styleName);
|
||||
}
|
||||
#endif // Q_OS_WIN
|
||||
|
||||
// Downloads
|
||||
|
@ -629,6 +668,47 @@ void Preferences::setSearchEnabled(const bool enabled)
|
|||
setValue(u"Preferences/Search/SearchEnabled"_s, enabled);
|
||||
}
|
||||
|
||||
int Preferences::searchHistoryLength() const
|
||||
{
|
||||
const int val = value(u"Search/HistoryLength"_s, 50);
|
||||
return std::clamp(val, 0, 99);
|
||||
}
|
||||
|
||||
void Preferences::setSearchHistoryLength(const int length)
|
||||
{
|
||||
const int clampedLength = std::clamp(length, 0, 99);
|
||||
if (clampedLength == searchHistoryLength())
|
||||
return;
|
||||
|
||||
setValue(u"Search/HistoryLength"_s, clampedLength);
|
||||
}
|
||||
|
||||
bool Preferences::storeOpenedSearchTabs() const
|
||||
{
|
||||
return value(u"Search/StoreOpenedSearchTabs"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setStoreOpenedSearchTabs(const bool enabled)
|
||||
{
|
||||
if (enabled == storeOpenedSearchTabs())
|
||||
return;
|
||||
|
||||
setValue(u"Search/StoreOpenedSearchTabs"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::storeOpenedSearchTabResults() const
|
||||
{
|
||||
return value(u"Search/StoreOpenedSearchTabResults"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setStoreOpenedSearchTabResults(const bool enabled)
|
||||
{
|
||||
if (enabled == storeOpenedSearchTabResults())
|
||||
return;
|
||||
|
||||
setValue(u"Search/StoreOpenedSearchTabResults"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isWebUIEnabled() const
|
||||
{
|
||||
#ifdef DISABLE_GUI
|
||||
|
@ -673,11 +753,11 @@ void Preferences::setWebUIAuthSubnetWhitelistEnabled(const bool enabled)
|
|||
setValue(u"Preferences/WebUI/AuthSubnetWhitelistEnabled"_s, enabled);
|
||||
}
|
||||
|
||||
QVector<Utils::Net::Subnet> Preferences::getWebUIAuthSubnetWhitelist() const
|
||||
QList<Utils::Net::Subnet> Preferences::getWebUIAuthSubnetWhitelist() const
|
||||
{
|
||||
const auto subnets = value<QStringList>(u"Preferences/WebUI/AuthSubnetWhitelist"_s);
|
||||
|
||||
QVector<Utils::Net::Subnet> ret;
|
||||
QList<Utils::Net::Subnet> ret;
|
||||
ret.reserve(subnets.size());
|
||||
|
||||
for (const QString &rawSubnet : subnets)
|
||||
|
@ -1330,6 +1410,19 @@ void Preferences::setMarkOfTheWebEnabled(const bool enabled)
|
|||
setValue(u"Preferences/Advanced/markOfTheWeb"_s, enabled);
|
||||
}
|
||||
|
||||
bool Preferences::isIgnoreSSLErrors() const
|
||||
{
|
||||
return value(u"Preferences/Advanced/IgnoreSSLErrors"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setIgnoreSSLErrors(const bool enabled)
|
||||
{
|
||||
if (enabled == isIgnoreSSLErrors())
|
||||
return;
|
||||
|
||||
setValue(u"Preferences/Advanced/IgnoreSSLErrors"_s, enabled);
|
||||
}
|
||||
|
||||
Path Preferences::getPythonExecutablePath() const
|
||||
{
|
||||
return value(u"Preferences/Search/pythonExecutablePath"_s, Path());
|
||||
|
@ -1974,6 +2067,19 @@ void Preferences::setAddNewTorrentDialogSavePathHistoryLength(const int value)
|
|||
setValue(u"AddNewTorrentDialog/SavePathHistoryLength"_s, clampedValue);
|
||||
}
|
||||
|
||||
bool Preferences::isAddNewTorrentDialogAttached() const
|
||||
{
|
||||
return value(u"AddNewTorrentDialog/Attached"_s, false);
|
||||
}
|
||||
|
||||
void Preferences::setAddNewTorrentDialogAttached(const bool attached)
|
||||
{
|
||||
if (attached == isAddNewTorrentDialogAttached())
|
||||
return;
|
||||
|
||||
setValue(u"AddNewTorrentDialog/Attached"_s, attached);
|
||||
}
|
||||
|
||||
void Preferences::apply()
|
||||
{
|
||||
if (SettingsStorage::instance()->save())
|
||||
|
|
|
@ -105,8 +105,8 @@ public:
|
|||
void setUseCustomUITheme(bool use);
|
||||
Path customUIThemePath() const;
|
||||
void setCustomUIThemePath(const Path &path);
|
||||
bool deleteTorrentFilesAsDefault() const;
|
||||
void setDeleteTorrentFilesAsDefault(bool del);
|
||||
bool removeTorrentContent() const;
|
||||
void setRemoveTorrentContent(bool remove);
|
||||
bool confirmOnExit() const;
|
||||
void setConfirmOnExit(bool confirm);
|
||||
bool speedInTitleBar() const;
|
||||
|
@ -119,6 +119,10 @@ public:
|
|||
void setHideZeroComboValues(int n);
|
||||
bool isStatusbarDisplayed() const;
|
||||
void setStatusbarDisplayed(bool displayed);
|
||||
bool isStatusbarFreeDiskSpaceDisplayed() const;
|
||||
void setStatusbarFreeDiskSpaceDisplayed(bool displayed);
|
||||
bool isStatusbarExternalIPDisplayed() const;
|
||||
void setStatusbarExternalIPDisplayed(bool displayed);
|
||||
bool isToolbarDisplayed() const;
|
||||
void setToolbarDisplayed(bool displayed);
|
||||
bool isSplashScreenDisabled() const;
|
||||
|
@ -130,6 +134,8 @@ public:
|
|||
#ifdef Q_OS_WIN
|
||||
bool WinStartup() const;
|
||||
void setWinStartup(bool b);
|
||||
QString getStyle() const;
|
||||
void setStyle(const QString &styleName);
|
||||
#endif
|
||||
|
||||
// Downloads
|
||||
|
@ -168,6 +174,14 @@ public:
|
|||
bool isSearchEnabled() const;
|
||||
void setSearchEnabled(bool enabled);
|
||||
|
||||
// Search UI
|
||||
int searchHistoryLength() const;
|
||||
void setSearchHistoryLength(int length);
|
||||
bool storeOpenedSearchTabs() const;
|
||||
void setStoreOpenedSearchTabs(bool enabled);
|
||||
bool storeOpenedSearchTabResults() const;
|
||||
void setStoreOpenedSearchTabResults(bool enabled);
|
||||
|
||||
// HTTP Server
|
||||
bool isWebUIEnabled() const;
|
||||
void setWebUIEnabled(bool enabled);
|
||||
|
@ -185,7 +199,7 @@ public:
|
|||
void setWebUILocalAuthEnabled(bool enabled);
|
||||
bool isWebUIAuthSubnetWhitelistEnabled() const;
|
||||
void setWebUIAuthSubnetWhitelistEnabled(bool enabled);
|
||||
QVector<Utils::Net::Subnet> getWebUIAuthSubnetWhitelist() const;
|
||||
QList<Utils::Net::Subnet> getWebUIAuthSubnetWhitelist() const;
|
||||
void setWebUIAuthSubnetWhitelist(QStringList subnets);
|
||||
QString getWebUIUsername() const;
|
||||
void setWebUIUsername(const QString &username);
|
||||
|
@ -293,6 +307,8 @@ public:
|
|||
void setTrackerPortForwardingEnabled(bool enabled);
|
||||
bool isMarkOfTheWebEnabled() const;
|
||||
void setMarkOfTheWebEnabled(bool enabled);
|
||||
bool isIgnoreSSLErrors() const;
|
||||
void setIgnoreSSLErrors(bool enabled);
|
||||
Path getPythonExecutablePath() const;
|
||||
void setPythonExecutablePath(const Path &path);
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
||||
|
@ -419,6 +435,8 @@ public:
|
|||
void setAddNewTorrentDialogTopLevel(bool value);
|
||||
int addNewTorrentDialogSavePathHistoryLength() const;
|
||||
void setAddNewTorrentDialogSavePathHistoryLength(int value);
|
||||
bool isAddNewTorrentDialogAttached() const;
|
||||
void setAddNewTorrentDialogAttached(bool attached);
|
||||
|
||||
public slots:
|
||||
void setStatusFilterState(bool checked);
|
||||
|
|
|
@ -34,14 +34,14 @@
|
|||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QVector>
|
||||
#include <QList>
|
||||
|
||||
#include "base/logger.h"
|
||||
#include "base/path.h"
|
||||
#include "base/utils/io.h"
|
||||
#include "rss_article.h"
|
||||
|
||||
const int ARTICLEDATALIST_TYPEID = qRegisterMetaType<QVector<QVariantHash>>();
|
||||
const int ARTICLEDATALIST_TYPEID = qRegisterMetaType<QList<QVariantHash>>();
|
||||
|
||||
void RSS::Private::FeedSerializer::load(const Path &dataFileName, const QString &url)
|
||||
{
|
||||
|
@ -61,7 +61,7 @@ void RSS::Private::FeedSerializer::load(const Path &dataFileName, const QString
|
|||
emit loadingFinished(loadArticles(readResult.value(), url));
|
||||
}
|
||||
|
||||
void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QVector<QVariantHash> &articlesData)
|
||||
void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QList<QVariantHash> &articlesData)
|
||||
{
|
||||
QJsonArray arr;
|
||||
for (const QVariantHash &data : articlesData)
|
||||
|
@ -73,7 +73,7 @@ void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QVector
|
|||
arr << jsonObj;
|
||||
}
|
||||
|
||||
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(dataFileName, QJsonDocument(arr).toJson());
|
||||
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(dataFileName, QJsonDocument(arr).toJson(QJsonDocument::Compact));
|
||||
if (!result)
|
||||
{
|
||||
LogMsg(tr("Failed to save RSS feed in '%1', Reason: %2").arg(dataFileName.toString(), result.error())
|
||||
|
@ -81,7 +81,7 @@ void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QVector
|
|||
}
|
||||
}
|
||||
|
||||
QVector<QVariantHash> RSS::Private::FeedSerializer::loadArticles(const QByteArray &data, const QString &url)
|
||||
QList<QVariantHash> RSS::Private::FeedSerializer::loadArticles(const QByteArray &data, const QString &url)
|
||||
{
|
||||
QJsonParseError jsonError;
|
||||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
|
||||
|
@ -98,7 +98,7 @@ QVector<QVariantHash> RSS::Private::FeedSerializer::loadArticles(const QByteArra
|
|||
return {};
|
||||
}
|
||||
|
||||
QVector<QVariantHash> result;
|
||||
QList<QVariantHash> result;
|
||||
const QJsonArray jsonArr = jsonDoc.array();
|
||||
result.reserve(jsonArr.size());
|
||||
for (int i = 0; i < jsonArr.size(); ++i)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue