Compare commits

...

264 Commits

Author SHA1 Message Date
Chocobo1
fbe93f0c47 Improve "apply memory working set" routine
Now it will try to raise the hard limit.
And also the log shows a more specific message when the new limit is not
applicable.

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

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

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

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

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

PR #18858.
2023-04-18 13:59:55 +08:00
Vladimir Golovnev
51132c817b Improve move storage handling
PR #18857.
Closes #18795.
2023-04-18 08:06:18 +03:00
Chocobo1
1fe006d16f Merge pull request #18853 from Chocobo1/exportTorrent
Work around Chrome download limit
2023-04-17 00:12:30 +08:00
Sentox6
bd31eddb94 Inhibit system sleep while torrents are moving
PR #18783.
2023-04-16 18:09:34 +03:00
DivineHawk
0defb7d79d WebUI: Use workaround for IOS file picker
PR #18837.
Fixes #18683.
2023-04-16 14:30:30 +03:00
七海千秋
1e400df324 Set "SameSite=None" if CSRF Protection is disabled
PR #18843.
2023-04-16 14:27:49 +03:00
Chocobo1
9ea48539b4 Inline variable declared in header 2023-04-15 14:53:13 +08:00
Chocobo1
d63e0ad78f Work around Chrome download limit
Closes #18775.
2023-04-15 14:51:27 +08:00
Vladimir Golovnev
eaee38a19e Disable UPnP for web UI by default
PR #18832.
2023-04-13 06:22:18 +03:00
Vladimir Golovnev
b3e9c46eff Don't miss saving "download path" in SQLite storage
PR #18844.
Closes #18842.
2023-04-13 06:18:09 +03:00
Chocobo1
5dcc14153f Move feature macro declaration to build scripts
PR #18825.
2023-04-10 13:38:00 +08:00
Chocobo1
4a66d705b8 Merge pull request #18812 from Chocobo1/buf
Use KiB unit for socket buffer sizes
2023-04-09 22:17:39 +08:00
Chocobo1
9d7fcea5d6 Describe special values in label
https://github.com/qbittorrent/qBittorrent/pull/18806#discussion_r1158346210
https://github.com/qbittorrent/qBittorrent/pull/18812#issuecomment-1500303976
2023-04-08 15:46:02 +08:00
Vladimir Golovnev
b8cd614775 Allow to edit RSS feed URL
PR #18807.
Closes #5489.
2023-04-07 14:22:50 +03:00
Chocobo1
a9ab2d9b9e Use KiB unit for socket buffer sizes
https://github.com/qbittorrent/qBittorrent/pull/18806#issuecomment-1499894871
2023-04-07 18:33:24 +08:00
Chocobo1
cecf2d28e6 Merge pull request #18806 from Chocobo1/buf
Expose 'socket send/receive buffer size' options
2023-04-07 18:19:46 +08:00
Chocobo1
a01f1014b9 Inline variable defined in header 2023-04-05 18:32:18 +08:00
Chocobo1
77411760a0 Expose 'socket send/receive buffer size' options
Closes #18794.
2023-04-05 18:11:13 +08:00
Vladimir Golovnev
0dcb65bb7c Add option to auto hide zero status filters
* Extract transfer list filter classes into separate files
* Add option to auto hide zero status filters

PR #18801.
Closes #13996.
2023-04-03 10:38:08 +03:00
Vladimir Golovnev
d40be79c69 Implement torrent tags editing dialog
PR #18797.
2023-04-03 10:36:28 +03:00
Bartu Özen
b55d4b1733 WebUI: Implement subcategories
PR #18740.
2023-04-02 11:02:22 +03:00
Chocobo1
40e28930a4 GHA CI: add missing dll
Closes #18383.
PR #18792.
2023-04-02 13:30:26 +08:00
Chocobo1
5a3579a3f9 Merge pull request #18779 from Chocobo1/webui
WebUI: Add checker for html
2023-03-30 13:24:21 +08:00
Chocobo1
9de8abadb6 Fix wrong end tag 2023-03-28 22:26:11 +08:00
Chocobo1
05c5cdab96 Add alternative text for images 2023-03-28 22:20:37 +08:00
Chocobo1
1e1c1725ab Always use quotes for attributes
This is to unify coding style.
2023-03-28 22:20:37 +08:00
Chocobo1
b5c57af869 Escape special characters properly 2023-03-28 22:20:37 +08:00
Chocobo1
eb875ac8c1 Add attributes explicitly 2023-03-28 22:20:37 +08:00
Chocobo1
a36358d7d0 Remove deprecated rules 2023-03-28 22:20:37 +08:00
Chocobo1
679e592a5c Add checker for html 2023-03-28 22:20:33 +08:00
Vladimir Golovnev
b922441a7c Correctly handle redirections
PR #18771.
Fixes regression introduced by #18528.
Closes #18764.
2023-03-28 06:26:40 +03:00
Vladimir Golovnev
941c587c68 Don't forget to clear I2P peer items
PR #18753.
2023-03-24 14:06:46 +03:00
Vladimir Golovnev
77bd09bb8b Use tray icon from system theme only if option is set
PR #18733.
2023-03-22 13:51:39 +03:00
Vladimir Golovnev
8bcac1bed2 Reduce default file pool size
PR #18734.
2023-03-21 08:39:06 +03:00
Vladimir Golovnev
cdded6cef7 Add (experimental) I2P support
PR #18717.
Closes #16257.
2023-03-21 08:33:46 +03:00
thalieht
8cbe4a571c Initialize a few groupboxes' check state after their children
PR #18727.
2023-03-20 10:49:25 +03:00
Vladimir Golovnev
ee9d2173e0 Combine all the column filter related widgets
PR #18726.
2023-03-20 08:23:41 +03:00
Chocobo1
a450a7c6e1 Delegate string hashing to standard library
Since standard library could have platform dependent specialized hashing functions.
Also the main idea is to let `qHash` handle whatever integer type `std::hash` returns and mix it with `seed` accordingly.

PR #18715.
2023-03-18 14:09:35 +08:00
thalieht
d41a77841d Save UI Theme Dialog size
PR #18709.
2023-03-17 22:15:56 +03:00
thalieht
ae06daba6a Correctly initialize group box children as disabled
PR #18710.
2023-03-17 22:13:19 +03:00
Vladimir Golovnev
77aa85fbd3 Provide UI Theme editor
PR #18655.
2023-03-16 10:03:05 +03:00
Vladimir Golovnev
989b1e6c2c Improve finished torrent handling
PR #18704.
Closes #18694.
2023-03-16 09:45:49 +03:00
Vladimir Golovnev
9ef23d524d Prevent incorrect log message about torrent content deletion
PR #18692.
Closes #18689.
2023-03-16 09:40:21 +03:00
Chocobo1
f16e903623 Merge pull request #18687 from Chocobo1/codeql
GHA CI: add CodeQL scanning
2023-03-15 15:21:27 +08:00
Tom
0bb0829a9a Allow to filter torrent list by save path
PR #18600.
2023-03-15 09:46:22 +03:00
Chocobo1
fa30b70453 Fix code defects 2023-03-14 15:04:54 +08:00
Christian Danížek
e4f90730b2 NSIS: Add Slovak translation
PR #18676.
2023-03-13 12:04:47 +03:00
Chocobo1
6fd522472c GHA CI: drop needless syntax
https://docs.github.com/en/actions/learn-github-actions/expressions#about-expressions
>When you use expressions in an if conditional, you may omit the
>expression syntax (${{ }}) because GitHub automatically evaluates the if conditional as an expression.
2023-03-12 14:27:10 +08:00
Chocobo1
0f32de9d8c GHA CI: add CodeQL scanning
This enable codebase scanning for C++ and JavaScript languages.

https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/about-code-scanning-with-codeql
https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/customizing-code-scanning
2023-03-12 14:27:10 +08:00
thalieht
f630d84858 WebUI: Add "Add to top of queue" option
PR #18660.
2023-03-08 18:58:42 +03:00
Vladimir Golovnev
ee6f699b48 Apply adjacent changes within single transaction
PR #18635.
2023-03-04 08:46:19 +03:00
Vladimir Golovnev
ce9bdaef5c Correctly check for database needs to be updated
* Correctly check for database needs to be updated
* Create index only if not exists
* Double check whether database needs to be updated

PR #18638.
2023-03-02 20:31:38 +03:00
Vladimir Golovnev
37c04fdeed Prevent possible problem of using incomplete type
PR #18639.
2023-03-02 06:18:51 +03:00
Vladimir Golovnev
c51aa2d573 Index torrents table by queue position
PR #18623.
2023-02-28 08:57:51 +03:00
sledgehammer999
b922e1ae73 Sync translations from Transifex and run lupdate 2023-02-28 00:20:58 +02:00
sledgehammer999
dd48f62d66 Migrate transifex tool config to new version 2023-02-28 00:10:16 +02:00
Vladimir Golovnev
f5b5312cf0 Merge pull request #18528 from glassez/proxy
Allow to use proxy per subsystem
2023-02-27 20:34:51 +03:00
Vladimir Golovnev
58a654a70f Reject requests that contain backslash in path
PR #18626.
Closes #18618.
2023-02-27 16:50:50 +03:00
Vladimir Golovnev
ff0f3b4975 WebAPI: Allow to set read-only directory as torrent location
PR #18613.
Closes #18480.
2023-02-27 09:09:33 +03:00
Vladimir Golovnev
8df68ac878 Prevent RSS folder from being moved into itself
PR #18619.
Closes #18446.
2023-02-27 09:08:18 +03:00
Vladimir Golovnev
2f9b313287 Perform own tracking of files progress
PR #18597.
2023-02-26 14:44:58 +03:00
Vladimir Golovnev (Glassez)
cbf591a8b5 Improve SOCKS5 error messages 2023-02-26 14:27:59 +03:00
Vladimir Golovnev (Glassez)
96da685e5d Expand the scope of "Proxy hostname lookup" option 2023-02-26 14:27:59 +03:00
Vladimir Golovnev (Glassez)
6ac14d0c57 Allow to use proxy per subsystem 2023-02-26 14:27:41 +03:00
Vladimir Golovnev (Glassez)
4745a40f0b Allow to specify proxy option per request 2023-02-25 17:30:26 +03:00
Vladimir Golovnev (Glassez)
8993d87b32 Let Search Engine handle its proxy usage 2023-02-25 17:25:41 +03:00
brvphoenix
8df80b67f9 GHA CI: Add missing dependencies
PR #18596.
2023-02-19 17:02:59 +03:00
loligans
466314675c WebUI: Add multi-file renaming
PR #18287.
Closes #16239.
2023-02-19 14:07:55 +03:00
Hanabishi
d75fd3fcde Show filtered port numbers in logs
PR #18544.
2023-02-18 08:14:27 +03:00
Chocobo1
7ae83df5a5 GHA CI: compress debug symbols
The result binary is smaller.
2023-02-17 13:00:10 +08:00
Chocobo1
19f55512c1 GHA CI: don't overwrite system default compile flags
System might have some default compile flags which are crucial for security hardening so we
should append our flags instead of overwriting them.
2023-02-17 13:00:10 +08:00
Chocobo1
6e25db444e GHA CI: revert "[CI Ubuntu] Strip installed components"
For tester convenience, the binaries should ship with debug symbols.
This reverts commit b8aa9e5609.
2023-02-17 13:00:10 +08:00
Chocobo1
d2c4b69f47 GHA CI: use least permission level
`actions: write` is required by Chocobo1/setup-ccache-action.
`pull-requests: write` is required by actions/stale.
https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
2023-02-17 13:00:10 +08:00
Chocobo1
4170b4e21b GHA CI: speed up package installation on macOS
Setup time is shortened by cutting down unnecessary operations.

https://docs.brew.sh/Manpage#environment
2023-02-17 13:00:10 +08:00
Vladimir Golovnev
9fb9ca47f6 Update the cached state once recheck is started
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.

PR #18579.
Closes #18559.
2023-02-17 07:12:56 +03:00
Luka Čelebić
917190d936 Add shortcut for "Ban peer permanently" function
PR #18576.
2023-02-17 07:09:23 +03:00
Vladimir Golovnev
1e913f46f0 Rename isSeed to isFinished to correctly represent its meaning
PR #18580.
2023-02-17 07:08:00 +03:00
Vladimir Golovnev
4c0ebc0e0f Access some more data in a non-blocking manner
* Cache URL seeds to access in a non-blocking manner
* Provide non-blocking way to create magnet URI for torrent

PR #18572.
2023-02-16 08:49:16 +03:00
shitcod3r
1b0f5b8567 NSIS: Add Uzbek translation
PR #18568.
2023-02-15 11:58:06 +03:00
Chocobo1
6a4bb5c1b7 Migrate away from unsafe function
MooTools More has CVE-2021-20088 and qbt is affected by it by using the
unsafe function call `String.parseQueryString()`, so migrate away from
it.

PR #18554.
2023-02-15 13:59:21 +08:00
Burak Yavuz
3fea9f5a33 NSIS: Update Turkish translation
PR #18552.
2023-02-14 08:30:46 +03:00
Vladimir Golovnev
7600f59f3a Prevent precise timers from being used when unnecessary
The implementation of QTimer::singleShot() uses Qt::PreciseTimer if interval is less than 2 seconds. This isn't mentioned in the docs.
Qt::PreciseTimer increases the system's timer resolution which negatively affects power consumption.

PR #18555.
Closes #18350.
2023-02-14 08:26:08 +03:00
sledgehammer999
915121a0dd Sync translations from Transifex and run lupdate 2023-02-12 01:09:18 +02:00
Vladimir Golovnev
1be5b3abd8 Revamp torrent content widget
PR #18162.
2023-02-11 15:22:01 +03:00
brvphoenix
e37661d53a WebUI: Add filelog settings
PR #18506.
Closes #17421.
2023-02-10 20:12:22 +03:00
Vladimir Golovnev
d06f78dbbd Improve sync API performance
PR #18394.
2023-02-10 17:16:46 +03:00
Vladimir Golovnev
5d4766edbe Allow to add new torrents to queue top
PR #18518.
Closes #11599.
2023-02-10 17:15:41 +03:00
Vladimir Golovnev
72ac92ec68 Allow to use another icons in dark mode
PR #18435.
2023-02-07 22:07:15 +03:00
sledgehammer999
22ea508ff6 Merge pull request #18500 from sledgehammer999/tls_webui
Harden the SSL/TLS web server
2023-02-06 22:40:44 +02:00
sledgehammer999
b2213ded6d Support TLS 1.2+ only in the server
Closes #18122
2023-02-06 10:32:01 +02:00
sledgehammer999
1ea2fe5b8d Blacklist bad ciphers for TLS in the server
Prevents the ROBOT attack.
Closes #18483
2023-02-06 10:32:01 +02:00
Vladimir Golovnev
7227d2b2b2 Revert changes of conflict resolution strategy on automatic move
PR #18516.
Closes #18297.
Closes #18495.
2023-02-05 09:29:43 +03:00
Vladimir Golovnev
0dcbf9f698 Improve command line parameters dispatching
Encapsulate parameters dispatching in Application class.
Avoid serializing parameters when it is not necessary.

PR #18469.
2023-02-02 10:02:51 +03:00
Vladimir Golovnev
09e58df03f Don't increase limits when prefetching metadata for added magnets
Adjusting limits was made based on the belief that "forced" torrents (internally used for prefetching metadata)
are still under limits, but ignore only the queue. This is not really the case. "Forced" torrents ignore the limits
like "maximum active torrents/downloads", so adjusting limits is not required, and what's more, it really causes the
problem of unexpectedly activated previously queued torrents when adding some magnet using "Add new torrent" dialog.

PR #18503.
Fixes #18490.
2023-02-02 06:16:32 +03:00
sledgehammer999
d256db5072 Merge pull request #18466 from sledgehammer999/icon_for_status_pause
Use previous color for pause icon for indicating status
2023-01-27 20:27:48 +02:00
sledgehammer999
10153f0063 Use previous color for pause icon for indicating status
Affects transfer list and status filters
Related to PR #18110
2023-01-27 18:45:07 +02:00
sledgehammer999
c6a1b977b3 Merge pull request #18456 from sledgehammer999/win_shorcut_cwd
Fix PDB loading for valid stacktraces
2023-01-27 17:52:39 +02:00
sledgehammer999
870bb42e4f [WebUI] Use new pause icon color for toolbar/menu
This the webui part of PR #18110
2023-01-27 17:42:48 +02:00
sledgehammer999
b61c7b7220 Adjust env variable for PDB discovery 2023-01-26 13:42:09 +02:00
Vladimir Golovnev
c58fb92365 Suppress warning when session cookie name isn't overridden
PR #18455.
2023-01-26 07:00:20 +03:00
sledgehammer999
5e952a561b NSIS: Set shortcut's workind dir to install path 2023-01-25 20:35:21 +02:00
sledgehammer999
ca72360b6f Merge pull request #18432 from sledgehammer999/chinese_fix
Migrate setting about Simplified Chinese locale
2023-01-25 01:17:54 +02:00
sledgehammer999
630b4ed3b9 Migrate settings much earlier 2023-01-24 10:23:12 +02:00
Deividas
cba9680ef9 NSIS: Update Lithuanian translation
PR #18434.
2023-01-23 14:34:36 +03:00
Vladimir Golovnev
2310dcd136 Reload system tray icon to replace menu
PR #18250.
Closes #18074.
2023-01-22 16:48:58 +03:00
Nick Korotysh
ee00a80796 Drop extra semicolon
Fixes build with -pedantic flag.

PR #18431.
2023-01-22 14:47:19 +03:00
sledgehammer999
051bac5e59 Migrate setting about Simplified Chinese locale
Related to PR #17978
2023-01-22 02:31:58 +02:00
Vladimir Golovnev
771c58d000 WebAPI: Allow to specify session cookie name
PR #18384.
Closes #18329.
2023-01-17 09:31:17 +03:00
Vladimir Golovnev
53cec6db09 Provide scaled pixmaps by UIThemeManager
Avoid leaking the paths of the theme resource files outside of the theme support implementation.

PR #18269.
2023-01-17 09:29:00 +03:00
Midhun V Nadh
43e059801e Remove suggestions while searching for torrents
Don't want torrent search history to pop up next time you try to search for torrents, right?
There are people who would search for 18+ content and what they searched would load up next time they are about to search.

PR #18285.
2023-01-16 15:12:35 +03:00
Nowshed H. Imran
ce35a06ec3 Fix Pause icon
PR #18110.
2023-01-16 15:01:20 +03:00
Vladimir Golovnev
32e4371208 Improve startup window state handling
Replace current "Start qBittorrent minimized" option with "Initial window state" that allows to start qBittorrent as "hidden in system tray" while retaining regular "minimize to panel" functionality.

PR #18252.
Closes #487.
2023-01-16 14:57:56 +03:00
brvphoenix
0d376e7fd6 WebUI: Add log viewer
The javascript implementation of multi-select menu is from the source
https://github.com/PhilippeMarcMeyer/vanillaSelectBox. It is licensed
under the MIT License. Some minor fixes is made to pass the lint.

Co-authored-by: brvphoenix <30111323+brvphoenix@users.noreply.github.com>
Co-authored-by: ttyS3 <ttys3.rust@gmail.com>

PR #18290.
2023-01-16 14:55:44 +03:00
Vladimir Golovnev
2b20d5b260 Fix startup performance on Qt5
Use more appropriate container (QList) for resume data queue buffer.
QVector in Qt5 has poor performance of the first element taking operation,
which is used to process the resume data queue. In Qt6, QVector is just an
alias for QList, so there was no problem there.

PR #18387.
Fixes #18341.
2023-01-16 14:45:12 +03:00
Vladimir Golovnev
719e4afd8c Remove confusing helpers from Session interface
Such helpers do not make practical sense, since they can be trivially implemented on top of the base interface, but at the same time they can lead to undesirable consequences when some calling code requires slightly different behavior than another.

PR #18367.
Fixes #18338.
2023-01-16 14:43:36 +03:00
Vladimir Golovnev
9cdf660ddb Use QThreadPool to invoke free disk space checking jobs
Prevent the creation of an excessive number of threads.
PR #18347.
Closes #18202.
2023-01-16 14:31:49 +03:00
Vladimir Golovnev
5dbccf3473 Add all torrents passed via the command line
PR #18296.
Closes #18289.
2023-01-16 06:54:02 +03:00
Vladimir Golovnev
8db2d04dbb Allow to modify default UI theme
PR #18214.
2023-01-14 14:02:20 +03:00
thalieht
209850064a WebUI: Add "Resume data storage type" option
PR #18357.
2023-01-13 10:58:46 +03:00
brvphoenix
e628b7d527 WebUI: Add missing icons
PR #18380.
2023-01-13 10:52:53 +03:00
Fidel Selva
61dbb211c0 WebUI: Improve hotkeys
PR #18326.
Fixes #18325.
Fixes #14033.
2023-01-06 22:17:15 +03:00
Jason Carr
71f4a5667c WebUI: change order of accepted types of file input
PR #18286.
2022-12-28 13:20:02 +03:00
brvphoenix
b33dc7d831 Unify the way to generate the language list in WebUI and GUI
PR #17994.
2022-12-25 16:44:37 +03:00
qbittorrentfan
c5a4a0db2c properties endpoint returns name/torrentID
PR #18218.
2022-12-25 16:41:55 +03:00
sotiris-bos
b9e045e80b WebAPI: Expose "IS PRIVATE" flag
PR #18227.
Closes #16052.
2022-12-25 16:41:00 +03:00
Vladimir Golovnev
cfd0c5433e Re-allow to use icons from system theme
PR #18195.
2022-12-25 16:25:56 +03:00
Vladimir Golovnev
ebad387c1a Apply correct tab order to Category options dialog
Also pre-select (sub)category name for editing when dialog is opened for creating new (sub)category.

PR #18270.
Closes #18265.
2022-12-25 16:14:59 +03:00
xavier2k6
3f39bd9f35 GHA CI: Bump Boost version to 1.81.0 on Windows/macOS
PR #18279.
2022-12-24 20:03:12 +03:00
Jonatan
f8236eb397 NSIS: Update Swedish translation
PR #18240.
2022-12-22 15:01:23 +03:00
David Xuang
23a56c95e3 Prevent incorrect line breaking
PR #18236.
2022-12-22 14:58:26 +03:00
Nowshed H. Imran
6f8aa07a10 Fix icon colors inconsistencies
PR #18226.
Fixes #18163.
Fixes #18222.
2022-12-22 14:14:29 +03:00
Vladimir Golovnev
594f9e8632 Use "additional trackers" when metadata retrieving
This can help when the DHT nodes are few.

PR #18251.
Closes #18244.
2022-12-22 08:21:29 +03:00
Vladimir Golovnev
aeae065007 Correctly count the number of torrents in subcategories
PR #18261.
Closes #18137.
2022-12-22 08:19:33 +03:00
Vladimir Golovnev
b12fdcf018 Correctly detect drive letter in path
PR #18258.
Closes #18224.
2022-12-20 07:14:31 +03:00
Vladimir Golovnev
84fabf14c8 Merge pull request #18034 from glassez/fetch-async
Fetch data asynchronously
2022-12-15 06:59:29 +03:00
Vladimir Golovnev
0ec47db9cd Don't drop !qB extension when rename incomplete file
PR #18186.
Closes #18181.
2022-12-15 06:57:07 +03:00
sledgehammer999
3cf0004665 Merge pull request #18200 from sledgehammer999/ci_appimage
CI: Build AppImage
2022-12-14 10:17:43 +02:00
Vladimir Golovnev (Glassez)
40258f6a2f Stop async worker at correct place 2022-12-14 10:42:40 +03:00
Vladimir Golovnev (Glassez)
b335114219 Use better method to set bit 2022-12-14 10:41:04 +03:00
Vladimir Golovnev (Glassez)
998b08f5d8 Set metadata asynchronously 2022-12-14 10:41:04 +03:00
Vladimir Golovnev (Glassez)
991c30943a Allow to fetch data asynchronously 2022-12-14 10:41:04 +03:00
sledgehammer999
ad2be39c33 [CI Ubuntu] Build AppImage
Upload an AppImage artifact on CI builds. This AppImage is a
simplified version of the official one. It is meant to help
with debugging PRs that fix issues.
2022-12-14 03:28:05 +02:00
BallsOfSpaghetti
c3936cd4b6 Add "Rename rule" button to RSS Downloader
PR #18141.
2022-12-11 11:18:40 +03:00
Chocobo1
d2e595aac3 Remove docker information
It has been moved to its own repo: https://github.com/qbittorrent/docker-qbittorrent-nox

PR #18199.
2022-12-11 11:24:13 +08:00
sledgehammer999
b8aa9e5609 [CI Ubuntu] Strip installed components 2022-12-10 16:53:42 +02:00
Torsten Schwarz
2109c4e1ae WebUI: Make rename file dialog resizable
PR #18154.
2022-12-10 10:17:21 +03:00
Vladimir Golovnev
ac3ad17a9e Ensure thread is stopped before deleting QThread
PR #18037.
2022-12-08 08:37:14 +03:00
Vladimir Golovnev
31c7306bd2 Correctly load folder based UI theme
PR #18173.
2022-12-08 08:33:55 +03:00
Chocobo1
4741aab7a3 Merge pull request #18094 from Chocobo1/color
Revise text color for completed status
2022-12-02 13:17:00 +08:00
Vladimir Golovnev
679e3b8bea WebAPI: return paths using platform-independent separator format
PR #18118.
Closes #18096.
2022-12-01 08:16:43 +03:00
Vladimir Golovnev
25ea0d274b Destroy desktop integration at correct place
Otherwise it is destructed in QObject destructor, i.e. after GUI application is already destructed.
This can be related to some problems with system tray icon.

PR #18108.
Closes #18093.
2022-11-30 20:09:51 +03:00
sledgehammer999
12b58be8c2 Merge pull request #18104 from sledgehammer999/remove_dead_code
Remove dead code
2022-11-30 15:19:54 +02:00
Chocobo1
e4f1485c82 Fix wrong color code
Must have been a copy-paste error...
2022-11-30 15:23:49 +08:00
Vladimir Golovnev
1b2ff0f6f8 Handle tracker status updates asynchronously
* Add a helper for performing jobs in Session context
* Handle tracker status updates asynchronously

PR #18010.
2022-11-30 09:54:30 +03:00
Vladimir Golovnev
a31755bbc8 Switch SQLite to use WAL journaling mode
PR #18048.
2022-11-30 09:50:26 +03:00
sledgehammer999
311e0f21eb Remove dead code
Leftover from the system tray code refactoring.
2022-11-29 21:30:26 +02:00
sledgehammer999
b86366f243 Merge pull request #18083 from now-im/patch-1
Fix Speed limit icon size
2022-11-29 17:27:16 +02:00
sledgehammer999
58d1c80b12 Merge pull request #18076 from thalieht/iconqueued
WebUI: Fix missing "queued" icon
2022-11-29 17:26:38 +02:00
Chocobo1
182915f801 Revise color for completed status
Now it uses the purple color which matches the completed status icon color.

Related: #18078.
2022-11-29 07:27:00 +08:00
sledgehammer999
50c08e55cd Merge pull request #18086 from sledgehammer999/translation_fix
Remove trailing newline from translation file
2022-11-28 21:05:09 +02:00
sledgehammer999
4307a09621 Remove trailing newline from translation file
I also fixed it on Transifex.
2022-11-28 18:41:26 +02:00
Nowshed H. Imran
d531d6d221 Fix Speed limit icon size
Fixes #18067.
2022-11-28 19:59:09 +06:00
thalieht
4cf94a6fa0 WebUI: Fix missing "queued" icon 2022-11-28 01:45:41 +02:00
sledgehammer999
4cb60f4870 Sync translations from Transifex and run lupdate 2022-11-23 21:22:54 +02:00
Vladimir Golovnev
d82edb2838 Bump to 4.6.0alpha1 2022-11-21 22:54:37 +03:00
sledgehammer999
c91eefe469 Update Changelog 2022-11-21 01:13:29 +02:00
sledgehammer999
327affa340 Merge pull request #18002 from sledgehammer999/v450_changelog
Update Changelog
2022-11-20 22:11:01 +02:00
sledgehammer999
4e7c2589e4 Update Changelog 2022-11-20 21:01:54 +02:00
sledgehammer999
17ce07230d Merge pull request #18038 from glassez/tree-update
Revert "Use another workaround to update files tree view"
2022-11-20 20:57:27 +02:00
Vladimir Golovnev
fda6c9a3d9 Prevent object from being used after destruction
PR #18031.
2022-11-20 15:03:36 +03:00
Vladimir Golovnev (glassez)
92af2922c7 Revert "Use another workaround to update files tree view"
This reverts commit 0f82c16936.
2022-11-19 21:44:15 +03:00
Vladimir Golovnev
1cee69da6c Don't miss to store metadata of new torrent
PR #18033.
2022-11-19 13:33:38 +03:00
Vladimir Golovnev
f54b66eb75 Merge pull request #17996 from glassez/non-blocking
Perform some unavoidable blocking calls asynchronously
2022-11-14 20:55:12 +03:00
Vladimir Golovnev (Glassez)
3563bad5fc Revamp implementation of port forwarder 2022-11-14 08:28:35 +03:00
Vladimir Golovnev (Glassez)
1f3f96f7aa Set metadata asynchronously 2022-11-14 08:27:01 +03:00
Vladimir Golovnev (Glassez)
7022adb89b Change current IP filter asynchronously 2022-11-14 08:26:49 +03:00
Vladimir Golovnev (Glassez)
bac57de5f5 Update listening status using native session extension 2022-11-14 08:22:12 +03:00
Vladimir Golovnev
bdd56a52d3 Destroy object within appropriate thread
PR #18008.
2022-11-13 08:28:33 +03:00
Vladimir Golovnev
dcdbd02102 Delete database file only after it is released
PR #18005.
2022-11-13 08:27:48 +03:00
Vladimir Golovnev
b68c4e2106 Save correct resume data when added new torrent
PR #18003.
2022-11-13 08:26:25 +03:00
Vladimir Golovnev
67cb75e9d3 Merge pull request #18001 from glassez/update-tree
Use another workaround to update files tree view

This is alternative approach to #17786. It uses the similar workaround as was used a long time with Qt5 and stoped working with Qt6 so it is adjusted in this PR.

Also this fixes content model resetting handling.
2022-11-12 14:00:25 +03:00
Vladimir Golovnev
f9eefe866c Merge pull request #17992 from glassez/preloading-magnet
Improve handling of preloading metadata.
This also allows to avoid blocking calls when performing some actions.
2022-11-10 19:22:20 +03:00
Vladimir Golovnev (Glassez)
c636618cf3 Correctly handle model resetting 2022-11-10 13:32:48 +03:00
Vladimir Golovnev (Glassez)
0f82c16936 Use another workaround to update files tree view 2022-11-10 13:31:08 +03:00
Chocobo1
d328eeb5be Merge pull request #17980 from Chocobo1/model
Reserve space before appending elements
2022-11-10 17:24:17 +08:00
Chocobo1
d90ea0d3be Move increment out of loop 2022-11-10 17:18:28 +08:00
Chocobo1
e7ece66717 Clean up code 2022-11-09 23:17:22 +08:00
Vladimir Golovnev
6c9c40fd7c Avoid blocking call when changing libtorrent session settings
We don't really need to get currently used settings pack in order to apply changes to session settings. It is enough to apply settings pack that contains only updated settings.

PR #17989.
2022-11-09 08:02:34 +03:00
Chocobo1
529c1ec9f4 Reserve space before appending elements 2022-11-08 13:50:03 +08:00
Chocobo1
93429840c8 Fix typos 2022-11-08 13:50:01 +08:00
Vladimir Golovnev (Glassez)
6aee7f95b7 Add torrent for preloading magnet asynchronously 2022-11-08 08:29:16 +03:00
Vladimir Golovnev (Glassez)
0b70ccf9e9 Cache torrent handles of preloading magnets 2022-11-08 08:29:15 +03:00
Vladimir Golovnev (Glassez)
da586828be Don't perform unnecessary actions with preloading magnets 2022-11-08 08:29:10 +03:00
Vladimir Golovnev
2e4431f0b8 Save torrents queue w/o blocking calls
PR #17988.
2022-11-08 07:00:40 +03:00
BallsOfSpaghetti
162273da47 Add confirmation to resume/pause all
This adds a confirmation dialog to Pause All and Resume All. First I wanted to only add it in Tray, but honestly, clicking around in the menu, using hotkeys might trigger it just as easy.

Closes #17683.
PR #17945.
2022-11-08 11:31:18 +08:00
sledgehammer999
22fb9797c4 Merge pull request #17978 from sledgehammer999/fix_chinese_locale
Fine tune translations loading for Chinese locales
2022-11-08 02:41:34 +02:00
Chocobo1
f6735401f4 Add port forwarding option for embedded tracker
Closes #17781.
PR #17981.
2022-11-07 11:32:11 +08:00
sledgehammer999
06c4c58613 Fine tune translations loading for Chinese locales
Closes #17506
2022-11-06 14:01:16 +02:00
Vladimir Golovnev
c80238d66f Don't use extra variable to distinguish restored torrents
PR #17984.
2022-11-06 14:24:49 +03:00
Hanabishi
6a560016dd Implement Peer ID Client column for Peers tab
PR #17940.
2022-11-06 12:21:31 +08:00
Chocobo1
99b7663fa9 Revise interface of port forwarder
This eases the usage of port forwarder as the caller code doesn't need
to store previous used port and now can rely on port forwarder doing
all the hard work.

PR #17967.
2022-11-05 11:33:21 +08:00
Chocobo1
be0f34a69e Merge pull request #17900 from Chocobo1/alerts
Handle all types of alerts on shutdown
2022-11-03 12:29:38 +08:00
Chocobo1
5f2d807861 Ensure ongoing storage moving job will be completed when shutting down
Discussion: https://github.com/qbittorrent/qBittorrent/pull/17885#issuecomment-1282467041
2022-10-23 17:55:58 +08:00
Chocobo1
106adf135c Handle all types of alerts on shutdown
There might be alerts already queued in buffer waiting to be handled at
the time of pausing the session, so don't skip over them.
2022-10-18 14:31:57 +08:00
455 changed files with 216048 additions and 168470 deletions

82
.clang-tidy Normal file
View File

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

View File

@@ -2,6 +2,8 @@ name: CI - File health
on: [pull_request, push]
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: ${{ github.head_ref != '' }}

View File

@@ -2,6 +2,9 @@ name: CI - macOS
on: [pull_request, push]
permissions:
actions: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: ${{ github.head_ref != '' }}
@@ -14,12 +17,12 @@ jobs:
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.8", "1.2.18"]
libt_version: ["2.0.9", "1.2.19"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["5.15.2", "6.4.0"]
qt_version: ["5.15.2", "6.5.0"]
exclude:
- libt_version: "1.2.18"
qt_version: "6.4.0"
- libt_version: "1.2.19"
qt_version: "6.5.0"
env:
boost_path: "${{ github.workspace }}/../boost"
@@ -31,6 +34,9 @@ jobs:
- name: Install dependencies
run: |
export \
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 \
HOMEBREW_NO_INSTALL_CLEANUP=1
brew update > /dev/null
brew install \
cmake ninja \
@@ -46,7 +52,7 @@ jobs:
curl \
-L \
-o "${{ runner.temp }}/boost.tar.bz2" \
"https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.bz2"
"https://boostorg.jfrog.io/artifactory/main/release/1.82.0/source/boost_1_82_0.tar.bz2"
tar -xf "${{ runner.temp }}/boost.tar.bz2" -C "${{ github.workspace }}/.."
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
@@ -78,13 +84,14 @@ jobs:
sudo cmake --install build
- name: Build qBittorrent (Qt5)
if: ${{ startsWith(matrix.qt_version, 5) }}
if: startsWith(matrix.qt_version, 5)
run: |
CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \
LDFLAGS="$LDFLAGS -gz" \
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations" \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}" \
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \
@@ -96,13 +103,14 @@ jobs:
cmake --build build --target check
- name: Build qBittorrent (Qt6)
if: ${{ startsWith(matrix.qt_version, 6) }}
if: startsWith(matrix.qt_version, 6)
run: |
CXXFLAGS="$CXXFLAGS -Wno-gnu-zero-variadic-macro-arguments -Werror -Wno-error=deprecated-declarations" \
LDFLAGS="$LDFLAGS -gz" \
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_FLAGS="-Wno-gnu-zero-variadic-macro-arguments -Werror -Wno-error=deprecated-declarations" \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}" \
-DOPENSSL_ROOT_DIR="${{ env.openssl_root }}" \

View File

@@ -2,6 +2,10 @@ name: CI - Ubuntu
on: [pull_request, push]
permissions:
actions: write
security-events: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: ${{ github.head_ref != '' }}
@@ -14,11 +18,11 @@ jobs:
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.8", "1.2.18"]
libt_version: ["2.0.9", "1.2.19"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["5.15.2", "6.2.0"]
exclude:
- libt_version: "1.2.18"
- libt_version: "1.2.19"
qt_version: "6.2.0"
steps:
@@ -30,7 +34,7 @@ jobs:
sudo apt update
sudo apt install \
build-essential cmake ninja-build pkg-config \
libboost-dev libssl-dev zlib1g-dev
libboost-dev libssl-dev libxkbcommon-x11-dev zlib1g-dev
- name: Setup ccache
uses: Chocobo1/setup-ccache-action@v1
@@ -56,20 +60,30 @@ jobs:
cmake \
-B build \
-G "Ninja" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-Ddeprecated-functions=OFF
cmake --build build
sudo cmake --install build
# to avoid scanning 3rdparty codebases, initialize it just before building qbt
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
if: startsWith(matrix.libt_version, 2) && (matrix.qbt_gui == 'GUI=ON') && startsWith(matrix.qt_version, 6)
with:
config-file: ./.github/workflows/helper/codeql/cpp.yaml
languages: cpp
- name: Build qBittorrent (Qt5)
if: ${{ startsWith(matrix.qt_version, 5) }}
if: startsWith(matrix.qt_version, 5)
run: |
CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \
LDFLAGS="$LDFLAGS -gz" \
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations" \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DTESTING=ON \
@@ -81,13 +95,14 @@ jobs:
DESTDIR="qbittorrent" cmake --install build
- name: Build qBittorrent (Qt6)
if: ${{ startsWith(matrix.qt_version, 6) }}
if: startsWith(matrix.qt_version, 6)
run: |
CXXFLAGS="$CXXFLAGS -Werror" \
LDFLAGS="$LDFLAGS -gz" \
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_FLAGS="-Werror" \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DQT6=ON \
@@ -99,6 +114,10 @@ jobs:
cmake --build build --target check
DESTDIR="qbittorrent" cmake --install build
- name: Run CodeQL analysis
uses: github/codeql-action/analyze@v2
if: startsWith(matrix.libt_version, 2) && (matrix.qbt_gui == 'GUI=ON') && startsWith(matrix.qt_version, 6)
- name: Prepare build artifacts
run: |
mkdir upload
@@ -107,8 +126,35 @@ jobs:
mkdir upload/cmake/libtorrent
cp libtorrent/build/compile_commands.json upload/cmake/libtorrent
- name: 'AppImage: Prepare env'
run: |
sudo apt install libfuse2
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
wget https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-plugin-qt-x86_64.AppImage
chmod +x linuxdeploy-plugin-appimage-x86_64.AppImage
- name: 'AppImage: Prepare nox'
if: matrix.qbt_gui == 'GUI=OFF'
run: |
mkdir -p qbittorrent/usr/share/icons/hicolor/scalable/apps/
mkdir -p qbittorrent/usr/share/applications/
cp dist/unix/menuicons/scalable/apps/qbittorrent.svg qbittorrent/usr/share/icons/hicolor/scalable/apps/qbittorrent.svg
cp .github/workflows/helper/appimage/org.qbittorrent.qBittorrent.desktop qbittorrent/usr/share/applications/org.qbittorrent.qBittorrent.desktop
- name: 'AppImage: Package'
run: |
./linuxdeploy-x86_64.AppImage --appdir=qbittorrent --plugin qt
rm qbittorrent/apprun-hooks/*
cp .github/workflows/helper/appimage/export_vars.sh qbittorrent/apprun-hooks/export_vars.sh
NO_APPSTREAM=1 \
OUTPUT=upload/qbittorrent-CI_Ubuntu_x86_64.AppImage \
./linuxdeploy-x86_64.AppImage --appdir=qbittorrent --output appimage
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-info_ubuntu-x64_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
name: qBittorrent-CI_Ubuntu-x64_${{ matrix.qbt_gui }}_libtorrent-${{ matrix.libt_version }}_Qt-${{ matrix.qt_version }}
path: upload

View File

@@ -2,6 +2,9 @@ name: CI - WebUI
on: [pull_request, push]
permissions:
security-events: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: ${{ github.head_ref != '' }}
@@ -34,3 +37,12 @@ jobs:
run: |
npm run format
git diff --exit-code
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
config-file: ./.github/workflows/helper/codeql/js.yaml
languages: javascript
- name: Run CodeQL analysis
uses: github/codeql-action/analyze@v2

View File

@@ -2,6 +2,9 @@ name: CI - Windows
on: [pull_request, push]
permissions:
actions: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: ${{ github.head_ref != '' }}
@@ -14,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.8", "1.2.18"]
libt_version: ["2.0.9", "1.2.19"]
env:
boost_path: "${{ github.workspace }}/../boost"
@@ -67,7 +70,7 @@ jobs:
- name: Install boost
run: |
aria2c `
"https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.7z" `
"https://boostorg.jfrog.io/artifactory/main/release/1.82.0/source/boost_1_82_0.7z" `
-d "${{ runner.temp }}" `
-o "boost.7z"
7z x "${{ runner.temp }}/boost.7z" -o"${{ github.workspace }}/.."
@@ -76,7 +79,7 @@ jobs:
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
version: "6.4.0"
version: "6.5.0"
archives: qtbase qtsvg qttools
- name: Install libtorrent
@@ -149,6 +152,8 @@ jobs:
copy "${{ env.Qt6_DIR }}/plugins/sqldrivers/qsqlite.dll" upload/plugins/sqldrivers
mkdir upload/plugins/styles
copy "${{ env.Qt6_DIR }}/plugins/styles/qwindowsvistastyle.dll" upload/plugins/styles
mkdir upload/plugins/tls
copy "${{ env.Qt6_DIR }}/plugins/tls/qschannelbackend.dll" upload/plugins/tls
# cmake additionals
mkdir upload/cmake
copy build/compile_commands.json upload/cmake

View File

@@ -5,6 +5,8 @@ on:
- cron: '0 0 1 * *' # Monthly (1st day of month at midnight)
workflow_dispatch: # Mainly for testing. Don't forget the Coverity usage limits.
permissions: {}
jobs:
coverity_scan:
name: Scan
@@ -24,13 +26,13 @@ jobs:
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
version: "6.4.0"
version: "6.5.0"
archives: icu qtbase qtsvg qttools
- name: Install libtorrent
run: |
git clone \
--branch "v2.0.8" \
--branch "v2.0.9" \
--depth 1 \
--recurse-submodules \
https://github.com/arvidn/libtorrent.git

View File

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

View File

@@ -0,0 +1,6 @@
[Desktop Entry]
Name=qBittorrent
Exec=qbittorrent-nox %U
Icon=qbittorrent
Type=Application
Categories=Network

View File

@@ -0,0 +1,14 @@
name: "CodeQL config for C++"
queries:
- uses: security-and-quality
query-filters:
- exclude:
id: cpp/commented-out-code
- exclude:
id: cpp/include-non-header
- exclude:
id: cpp/loop-variable-changed
- exclude:
id: cpp/useless-expression

11
.github/workflows/helper/codeql/js.yaml vendored Normal file
View File

@@ -0,0 +1,11 @@
name: "CodeQL config for Javascript"
paths-ignore:
- "**/lib/*"
queries:
- uses: security-and-quality
query-filters:
- exclude:
id: js/superfluous-trailing-arguments

View File

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

View File

@@ -3,7 +3,7 @@ repos:
hooks:
- id: check-translation-tag
name: Check newline characters in <translation> tag
entry: .github/workflows/check_translation_tag.py
entry: .github/workflows/helper/pre-commit/check_translation_tag.py
language: script
exclude: |
(?x)^(
@@ -13,7 +13,7 @@ repos:
- ts
- repo: https://github.com/pre-commit/pre-commit-hooks.git
rev: v4.3.0
rev: v4.4.0
hooks:
- id: check-json
name: Check JSON files
@@ -34,6 +34,7 @@ repos:
exclude: |
(?x)^(
compile_commands.json |
src/webui/www/private/css/lib/.* |
src/webui/www/private/scripts/lib/.*
)$
@@ -43,6 +44,7 @@ repos:
(?x)^(
compile_commands.json |
configure |
src/webui/www/private/css/lib/.* |
src/webui/www/private/scripts/lib/.*
)$
exclude_types:
@@ -53,6 +55,7 @@ repos:
name: Check trailing whitespaces
exclude: |
(?x)^(
src/webui/www/private/css/lib/.* |
src/webui/www/private/scripts/lib/.*
)$
exclude_types:

View File

@@ -1,27 +1,24 @@
[main]
host = https://www.transifex.com
[qbittorrent.qbittorrent_master]
file_filter = src/lang/qbittorrent_<lang>.ts
lang_map = pt: pt_PT
source_file = src/lang/qbittorrent_en.ts
source_lang = en
type = QT
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_master]
file_filter = src/lang/qbittorrent_<lang>.ts
source_file = src/lang/qbittorrent_en.ts
source_lang = en
type = QT
minimum_perc = 23
mode = developer
lang_map = pt: pt_PT, zh: zh_CN
[qbittorrent.qbittorrentdesktop_master]
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
source_lang = en
type = DESKTOP
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui]
file_filter = src/webui/www/translations/webui_<lang>.ts
source_file = src/webui/www/translations/webui_en.ts
source_lang = en
type = QT
minimum_perc = 23
mode = developer
lang_map = pt: pt_PT, zh: zh_CN
[qbittorrent.qbittorrent_webui]
file_filter = src/webui/www/translations/webui_<lang>.ts
lang_map = pt: pt_PT
source_file = src/webui/www/translations/webui_en.ts
source_lang = en
type = QT
[o:sledgehammer999:p:qbittorrent:r:qbittorrentdesktop_master]
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
source_lang = en
type = DESKTOP
minimum_perc = 23
mode = developer

View File

@@ -29,6 +29,10 @@ Code from other projects:
copyright: Dan Haim <negativeiq@users.sourceforge.net>
license: BSD
* files src/webui/www/private/css/lib/vanillaSelectBox.css src/webui/www/private/scripts/lib/vanillaSelectBox.js
copyright: Philippe Meyer <pmg.meyer@gmail.com>
license: MIT
Images Authors:
* files: src/icons/qbittorrent-tray.svg
copyright: Provided by HVS <hvs linuxmail org> (raster first proposal) and Atif Afzal(@atfzl github) <atif5801@gmail.com> (vectorized and modified)

View File

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

View File

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

View File

@@ -1,4 +1,84 @@
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.0
- FEATURE: Add `Auto resize columns` functionality (Chocobo1)
- FEATURE: Allow to use Category paths in `Manual` mode (glassez)
- FEATURE: Allow to disable Automatic mode when default "temp" path changed (glassez)
- FEATURE: Add tuning options related to performance warnings (Chocobo1)
- FEATURE: Add right click menu for status filters (An0n)
- FEATURE: Allow setting the number of maximum active checking torrents (An0n)
- FEATURE: Add option to toggle filters sidebar (AbeniMatteo)
- FEATURE: Allow to set `working set limit` on non-Windows OS (Chocobo1)
- FEATURE: Add `Export .torrent` action (Chocobo1)
- FEATURE: Add keyboard navigation keys (itlezy)
- FEATURE: Allow to use POSIX-compliant disk IO type (Coda)
- FEATURE: Add `Filter files` field in new torrent dialog (thalieht)
- FEATURE: Implement new icon/color theme (now-im, xavier2k6)
- FEATURE: Add file name filter/blacklist (mxtsdev, thalieht)
- FEATURE: Add support for custom SMTP ports (Emil M George)
- FEATURE: Split the OS cache settings into Disk IO read/write modes (summer)
- FEATURE: When duplicate torrent is added set metadata to existing one (glassez)
- FEATURE: Greatly improve startup time with many torrents (glassez, jagannatharjun)
- FEATURE: Add keyboard shortcut to Download URL dialog (Chocobo1)
- FEATURE: Add ability to run external program on torrent added (glassez)
- FEATURE: Add infohash and download path columns (tristanleboss)
- FEATURE: Allow to set torrent stop condition (glassez, thalieht)
- FEATURE: Add a `Moving` status filter (tristanleboss)
- FEATURE: Change color palettes for both dark, light themes (Chocobo1)
- FEATURE: Add a `Use proxy for hostname lookup` option (Nathan Lewis)
- FEATURE: Introduce a `change listen port` cmd option (BallsOfSpaghetti)
- FEATURE: Implement `Peer ID Client` column for `Peers` tab (Hanabishi)
- FEATURE: Add port forwarding option for embedded tracker (Chocobo1)
- BUGFIX: Store hybrid torrents using `torrent ID` as basename (glassez)
- BUGFIX: Enable Combobox editor for the `Mixed` file download priority (Aleksandr Cupacenko)
- BUGFIX: Allow shortcut folders for the Open and Save directory dialogs (Aleksandr Cupacenko)
- BUGFIX: Rename content tab `Size` column to `Total Size` (Aleksandr Cupacenko)
- BUGFIX: Fix scrolling to the lowermost visible torrent (Aleksandr Cupacenko)
- BUGFIX: Allow changing file priorities for finished torrents (An0n)
- BUGFIX: Focus save path when Manual mode is selected initially (Aleksandr Cupacenko)
- BUGFIX: Disable force reannounce when it is not possible (An0n)
- BUGFIX: Add horizontal scrolling for tracker list and torrent content (NotTsunami)
- BUGFIX: Enlarge "speed limits" icons (Chocobo1)
- BUGFIX: Change Downloaded to Times Downloaded in trackers tab (An0n)
- BUGFIX: Remove artificial max limits from `Torrent Queueing` related options (Chocobo1)
- BUGFIX: Preserve `skip hash check` when there is no metadata (glassez)
- BUGFIX: Fix DHT/PeX/LSD status when it is globally disabled (Kacper Michajłow)
- BUGFIX: Fix rate calculation when interval is too low (glassez)
- BUGFIX: Add tooltip message when system tray icon isn't available (Chocobo1)
- BUGFIX: Improve sender field in mail notifications (Dmitry Vodopyanov)
- BUGFIX: Fix "Add torrent dialog" spill-over on smaller screens (Chocobo1)
- BUGFIX: Fix peer count issue when tracker responds with zero figure (summer)
- BUGFIX: Don't merge trackers by default (glassez)
- BUGFIX: Don't inhibit system sleep/auto shutdown for torrents stuck at downloading metadata (summer)
- BUGFIX: Allow to pause a checking torrent from context menu (summer)
- BUGFIX: Allow to use subnet notation in reverse proxy list (Chocobo1)
- BUGFIX: Fine tune translations loading for Chinese locales (sledgehammer999)
- BUGFIX: Fix torrent content checkboxes not updated properly (Chocobo1)
- BUGFIX: Correctly load state of `Use another path for incomplete torrents` in Watched folders (glassez)
- BUGFIX: Add confirmation to resume/pause all (BallsOfSpaghetti)
- BUGFIX: Fix wrong count of errored trackers (Chocobo1)
- WEBUI: Allow blank lines in multipart form-data input (Aleksandr Cupacenko)
- WEBUI: Make various dialogs resizable (Chocobo1)
- WEBUI: Fix wrong v2 hash string displayed (Chocobo1)
- WEBUI: WebAPI: return correct status (Requi)
- WEBUI: Fix empty selection in language combobox (Chocobo1)
- WEBUI: Store WebUI port setting in human readable number (Chocobo1)
- WEBUI: Add support for exporting .torrent (Tom Piccirello)
- WEBUI: WebAPI: Add endpoint to set speed limit mode (glassez)
- WEBUI: Improve progress bar rendering (Mike Lei)
- WEBUI: Add transfer list refresh interval settings (summer)
- WEBUI: Use natural sort (Chocobo1)
- WEBUI: Apply i18n translation only to built-in WebUI (Chocobo1)
- WEBUI: Alert when HTTPS settings are incomplete (Chocobo1)
- WEBUI: Handle drag and drop events (Chocobo1)
- WEBUI: Fix wrong behavior for shutdown action (Chocobo1)
- WEBUI: Don't disable combobox for file priority (Chocobo1)
- RSS: Increase limit of maximum number of articles per feed (summer)
- WINDOWS: Fix `Open destination folder` delay on Windows (Andrew)
- WINDOWS: NSIS: Update Russian, Estonian, Japanese, Dutch, Portuguese BR, German and Indonesian translations (Andrei Stepanov, Priit Uring, maboroshin, Thomas De Rocker, Ícaro, schnurlos, Faisal A. F. Rahman)
- LINUX: Mark as single window app in .desktop file (Nicolas Fella)
- LINUX: Add Dockerfile (Amanuense-del-diavolo, Tom Piccirello, Chocobo1)
- LINUX: Remove option of using icons from system theme (now-im)
- MACOS: Fix wrong background color in properties widget (NotTsunami)
- OTHER: Binary distributions of qbittorrent are GPLv3+ licensed (sledgehammer999)
Thu Jan 06 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.4.0
- FEATURE: Support for v2 torrents along with libtorrent 2.0.x support (glassez, Chocobo1)

View File

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

View File

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

View File

@@ -101,6 +101,10 @@ if (MSVC)
endif()
endif()
if (DBUS)
target_compile_definitions(qbt_common_cfg INTERFACE QBT_USES_DBUS)
endif()
if (LibtorrentRasterbar_VERSION VERSION_GREATER_EQUAL ${minLibtorrentVersion})
target_compile_definitions(qbt_common_cfg INTERFACE QBT_USES_LIBTORRENT2)
endif()

86
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for qbittorrent v4.5.0beta1.
# Generated by GNU Autoconf 2.71 for qbittorrent v4.6.0alpha1.
#
# Report bugs to <bugs.qbittorrent.org>.
#
@@ -611,8 +611,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='qbittorrent'
PACKAGE_TARNAME='qbittorrent'
PACKAGE_VERSION='v4.5.0beta1'
PACKAGE_STRING='qbittorrent v4.5.0beta1'
PACKAGE_VERSION='v4.6.0alpha1'
PACKAGE_STRING='qbittorrent v4.6.0alpha1'
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
PACKAGE_URL='https://www.qbittorrent.org/'
@@ -1329,7 +1329,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures qbittorrent v4.5.0beta1 to adapt to many kinds of systems.
\`configure' configures qbittorrent v4.6.0alpha1 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1400,7 +1400,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of qbittorrent v4.5.0beta1:";;
short | recursive ) echo "Configuration of qbittorrent v4.6.0alpha1:";;
esac
cat <<\_ACEOF
@@ -1533,7 +1533,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
qbittorrent configure v4.5.0beta1
qbittorrent configure v4.6.0alpha1
generated by GNU Autoconf 2.71
Copyright (C) 2021 Free Software Foundation, Inc.
@@ -1648,7 +1648,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by qbittorrent $as_me v4.5.0beta1, which was
It was created by qbittorrent $as_me v4.6.0alpha1, which was
generated by GNU Autoconf 2.71. Invocation command line was
$ $0$ac_configure_args_raw
@@ -4779,7 +4779,7 @@ fi
# Define the identity of the package.
PACKAGE='qbittorrent'
VERSION='v4.5.0beta1'
VERSION='v4.6.0alpha1'
printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -5649,7 +5649,7 @@ fi
then :
as_fn_error $? "Could not find QtDBus" "$LINENO" 5
else $as_nop
QBT_ADD_CONFIG="$QBT_ADD_CONFIG dbus"
QBT_ADD_CONFIG="$QBT_ADD_CONFIG dbus" QBT_ADD_DEFINES="$QBT_ADD_DEFINES QBT_USES_DBUS"
fi ;; #(
"xno") :
@@ -6024,19 +6024,19 @@ LDFLAGS="$BOOST_LDFLAGS $LDFLAGS"
pkg_failed=no
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 2.0.8" >&5
printf %s "checking for libtorrent-rasterbar >= 2.0.8... " >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 2.0.9" >&5
printf %s "checking for libtorrent-rasterbar >= 2.0.9... " >&6; }
if test -n "$libtorrent_CFLAGS"; then
pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 2.0.8\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 2.0.8") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 2.0.9\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 2.0.9") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 2.0.8" 2>/dev/null`
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 2.0.9" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6048,12 +6048,12 @@ if test -n "$libtorrent_LIBS"; then
pkg_cv_libtorrent_LIBS="$libtorrent_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 2.0.8\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 2.0.8") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 2.0.9\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 2.0.9") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 2.0.8" 2>/dev/null`
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 2.0.9" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6074,28 +6074,28 @@ else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 2.0.8" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 2.0.9" 2>&1`
else
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 2.0.8" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 2.0.9" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$libtorrent_PKG_ERRORS" >&5
pkg_failed=no
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" >&5
printf %s "checking for libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2... " >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" >&5
printf %s "checking for libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2... " >&6; }
if test -n "$libtorrent_CFLAGS"; then
pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>/dev/null`
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6107,12 +6107,12 @@ if test -n "$libtorrent_LIBS"; then
pkg_cv_libtorrent_LIBS="$libtorrent_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>/dev/null`
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6133,14 +6133,14 @@ else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>&1`
else
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$libtorrent_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2) were not met:
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2) were not met:
$libtorrent_PKG_ERRORS
@@ -6177,19 +6177,19 @@ elif test $pkg_failed = untried; then
printf "%s\n" "no" >&6; }
pkg_failed=no
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" >&5
printf %s "checking for libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2... " >&6; }
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" >&5
printf %s "checking for libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2... " >&6; }
if test -n "$libtorrent_CFLAGS"; then
pkg_cv_libtorrent_CFLAGS="$libtorrent_CFLAGS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>/dev/null`
pkg_cv_libtorrent_CFLAGS=`$PKG_CONFIG --cflags "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6201,12 +6201,12 @@ if test -n "$libtorrent_LIBS"; then
pkg_cv_libtorrent_LIBS="$libtorrent_LIBS"
elif test -n "$PKG_CONFIG"; then
if test -n "$PKG_CONFIG" && \
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2") 2>&5
{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2\""; } >&5
($PKG_CONFIG --exists --print-errors "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2") 2>&5
ac_status=$?
printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
test $ac_status = 0; }; then
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>/dev/null`
pkg_cv_libtorrent_LIBS=`$PKG_CONFIG --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes
else
pkg_failed=yes
@@ -6227,14 +6227,14 @@ else
_pkg_short_errors_supported=no
fi
if test $_pkg_short_errors_supported = yes; then
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>&1`
else
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2" 2>&1`
libtorrent_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$libtorrent_PKG_ERRORS" >&5
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2) were not met:
as_fn_error $? "Package requirements (libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2) were not met:
$libtorrent_PKG_ERRORS
@@ -7237,7 +7237,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by qbittorrent $as_me v4.5.0beta1, which was
This file was extended by qbittorrent $as_me v4.6.0alpha1, which was
generated by GNU Autoconf 2.71. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -7297,7 +7297,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\
qbittorrent config.status v4.5.0beta1
qbittorrent config.status v4.6.0alpha1
configured by $0, generated by GNU Autoconf 2.71,
with options \\"\$ac_cs_config\\"

View File

@@ -1,4 +1,4 @@
AC_INIT([qbittorrent], [v4.5.0beta1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_INIT([qbittorrent], [v4.6.0alpha1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])
: ${CFLAGS=""}
@@ -165,7 +165,7 @@ AS_CASE(["x$enable_qt_dbus"],
FIND_QTDBUS()
AS_IF([test "x$HAVE_QTDBUS" = "xfalse"],
[AC_MSG_ERROR([Could not find QtDBus])],
[QBT_ADD_CONFIG="$QBT_ADD_CONFIG dbus"]
[QBT_ADD_CONFIG="$QBT_ADD_CONFIG dbus" QBT_ADD_DEFINES="$QBT_ADD_DEFINES QBT_USES_DBUS"]
)],
["xno"],
[AC_MSG_RESULT([no])
@@ -188,10 +188,10 @@ m4_define([DETECT_BOOST_VERSION_PROGRAM],
[[(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($1))]));]])])
PKG_CHECK_MODULES(libtorrent,
[libtorrent-rasterbar >= 2.0.8],
[libtorrent-rasterbar >= 2.0.9],
[CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS" LIBS="$libtorrent_LIBS $LIBS" QBT_ADD_DEFINES="$QBT_ADD_DEFINES QBT_USES_LIBTORRENT2"],
[PKG_CHECK_MODULES(libtorrent,
[libtorrent-rasterbar >= 1.2.18 libtorrent-rasterbar < 2],
[libtorrent-rasterbar >= 1.2.19 libtorrent-rasterbar < 2],
[CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS" LIBS="$libtorrent_LIBS $LIBS"])])
PKG_CHECK_MODULES(openssl,

8
dist/docker/.env vendored
View File

@@ -1,8 +0,0 @@
# refer to Readme.md for an explanation of the variables
QBT_EULA=
QBT_VERSION=devel
QBT_WEBUI_PORT=8080
QBT_CONFIG_PATH=<your_path>/config
QBT_DOWNLOADS_PATH=<your_path>/downloads

View File

@@ -1,62 +0,0 @@
# image for building
FROM alpine:latest AS builder
ARG QBT_VERSION
# alpine linux qbittorrent package: https://git.alpinelinux.org/aports/tree/community/qbittorrent/APKBUILD
RUN \
apk --update-cache add \
boost-dev \
cmake \
g++ \
libtorrent-rasterbar-dev \
ninja \
qt6-qtbase-dev \
qt6-qttools-dev
RUN \
if [ "$QBT_VERSION" = "devel" ]; then \
wget https://github.com/qbittorrent/qBittorrent/archive/refs/heads/master.zip && \
unzip master.zip && \
cd qBittorrent-master ; \
else \
wget "https://github.com/qbittorrent/qBittorrent/archive/refs/tags/release-${QBT_VERSION}.tar.gz" && \
tar -xf "release-${QBT_VERSION}.tar.gz" && \
cd "qBittorrent-release-${QBT_VERSION}" ; \
fi && \
cmake \
-B build \
-G Ninja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DGUI=OFF \
-DQT6=ON \
-DSTACKTRACE=OFF && \
cmake --build build && \
cmake --install build
# image for running
FROM alpine:latest
RUN \
apk --no-cache add \
doas \
libtorrent-rasterbar \
python3 \
qt6-qtbase \
tini
RUN \
adduser \
-D \
-H \
-s /sbin/nologin \
-u 1000 \
qbtUser && \
echo "permit nopass :root" >> "/etc/doas.d/doas.conf"
COPY --from=builder /usr/local/bin/qbittorrent-nox /usr/bin/qbittorrent-nox
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/sbin/tini", "-g", "--", "/entrypoint.sh"]

101
dist/docker/Readme.md vendored
View File

@@ -1,101 +0,0 @@
# qBittorrent-nox Docker Image
This Dockerfile allows you to build a Docker Image containing qBittorrent-nox
## Prerequisites
In order to build/run this image you'll need Docker installed: https://docs.docker.com/get-docker/
If you don't need the GUI, you can just install Docker Engine: https://docs.docker.com/engine/install/
It is also recommended to install Docker Compose as it can significantly ease the process: https://docs.docker.com/compose/install/
## Building Docker Image
* If you are using Docker (not Docker Compose) then run the following commands in this folder:
```shell
export \
QBT_VERSION=devel
docker build \
--build-arg QBT_VERSION \
-t qbittorrent-nox:"$QBT_VERSION" \
.
```
* If you are using Docker Compose then you should edit `.env` file first.
You can find an explanation of the variables in the following [Parameters](#parameters) section. \
Then run the following commands in this folder:
```shell
docker compose build \
--build-arg QBT_VERSION
```
### Parameters
#### Environment variables
* `QBT_EULA` \
This environment variable defines whether you accept the end-user license agreement (EULA) of qBittorrent. \
**Put `accept` only if you understand and accepted the EULA.** You can find
the EULA [here](https://github.com/qbittorrent/qBittorrent/blob/56667e717b82c79433ecb8a5ff6cc2d7b315d773/src/app/main.cpp#L320-L323).
* `QBT_VERSION` \
This environment variable specifies the version of qBittorrent-nox to be built. \
For example, `4.4.0` is a valid entry. You can find all tagged versions [here](https://github.com/qbittorrent/qBittorrent/tags). \
Or you can put `devel` to build the latest development version.
* `QBT_WEBUI_PORT` \
This environment variable sets the port number which qBittorrent WebUI will be binded to.
#### Volumes
There are some paths involved:
* `<your_path>/config` \
Full path to a folder on your host machine which will store qBittorrent configurations.
Using relative path won't work.
* `<your_path>/downloads` \
Full path to a folder on your host machine which will store the files downloaded by qBittorrent.
Using relative path won't work.
## Running container
* Using Docker (not Docker Compose), simply run:
```shell
export \
QBT_EULA=accept \
QBT_VERSION=devel \
QBT_WEBUI_PORT=8080 \
QBT_CONFIG_PATH="/tmp/bbb/config"
QBT_DOWNLOADS_PATH="/tmp/bbb/downloads"
docker run \
-t \
--read-only \
--rm \
--tmpfs /tmp \
--name qbittorrent-nox \
-e QBT_EULA \
-e QBT_WEBUI_PORT \
-p "$QBT_WEBUI_PORT":"$QBT_WEBUI_PORT"/tcp \
-p 6881:6881/tcp \
-p 6881:6881/udp \
-v "$QBT_CONFIG_PATH":/config \
-v "$QBT_DOWNLOADS_PATH":/downloads \
qbittorrent-nox:"$QBT_VERSION"
```
* Using Docker Compose:
```shell
docker compose up
```
Then you can login at: `http://127.0.0.1:8080`
## Stopping container
* Using Docker (not Docker Compose):
```shell
docker stop -t 1800 qbittorrent-nox
```
* Using Docker Compose:
```shell
docker compose down
```

View File

@@ -1,25 +0,0 @@
version: "3.9"
services:
qbittorrent-nox:
build: .
container_name: qbittorrent-nox
environment:
- QBT_EULA=${QBT_EULA}
- QBT_VERSION=${QBT_VERSION}
- QBT_WEBUI_PORT=${QBT_WEBUI_PORT}
image: qbittorrent-nox:${QBT_VERSION}
ports:
# for bittorrent traffic
- 6881:6881/tcp
- 6881:6881/udp
# for WebUI
- ${QBT_WEBUI_PORT}:${QBT_WEBUI_PORT}/tcp
read_only: true
stop_grace_period: 30m
tmpfs:
- /tmp
tty: true
volumes:
- ${QBT_CONFIG_PATH}:/config
- ${QBT_DOWNLOADS_PATH}:/downloads

View File

@@ -1,35 +0,0 @@
#!/bin/sh
downloadsPath="/downloads"
profilePath="/config"
qbtConfigFile="$profilePath/qBittorrent/config/qBittorrent.conf"
if [ ! -f "$qbtConfigFile" ]; then
mkdir -p "$(dirname $qbtConfigFile)"
cat << EOF > "$qbtConfigFile"
[BitTorrent]
Session\DefaultSavePath=/downloads
Session\Port=6881
Session\TempPath=/downloads/temp
[LegalNotice]
Accepted=false
EOF
if [ "$QBT_EULA" = "accept" ]; then
sed -i '/^\[LegalNotice\]$/{$!{N;s|\(\[LegalNotice\]\nAccepted=\).*|\1true|}}' "$qbtConfigFile"
else
sed -i '/^\[LegalNotice\]$/{$!{N;s|\(\[LegalNotice\]\nAccepted=\).*|\1false|}}' "$qbtConfigFile"
fi
fi
# those are owned by root by default
# don't change existing files owner in `$downloadsPath`
chown qbtUser:qbtUser "$downloadsPath"
chown qbtUser:qbtUser -R "$profilePath"
doas -u qbtUser \
qbittorrent-nox \
--profile="$profilePath" \
--webui-port="$QBT_WEBUI_PORT" \
"$@"

View File

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

View File

@@ -98,7 +98,7 @@ Name[is]=qBittorrent
Comment[it]=Scarica e condividi file tramite BitTorrent
GenericName[it]=Client BitTorrent
Name[it]=qBittorrent
Comment[ja]=BitTorrent でファイルをダウンロードおよび共有します
Comment[ja]=BitTorrent でファイルをダウンロードおよび共有
GenericName[ja]=BitTorrent クライアント
Name[ja]=qBittorrent
Comment[ka]=გადმოტვირთეთ და გააზიარეთ ფაილები BitTorrent-ის საშუალებით
@@ -160,7 +160,7 @@ Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
Name[te]=qBittorrent
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่าน BitTorrent
GenericName[th]=ไคลเอนต์ BitTorrent
GenericName[th]=โปรแกรมบิททอเร้นท์
Name[th]=qBittorrent
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
GenericName[tr]=BitTorrent istemcisi

View File

@@ -3,7 +3,7 @@
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
LangString inst_qbt_req ${LANG_LITHUANIAN} "qBittorrent (reikalingas)"
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
LangString inst_dekstop ${LANG_LITHUANIAN} "Sukurti darbalaukyje nuorodą"
LangString inst_dekstop ${LANG_LITHUANIAN} "Sukurti nuorodą darbalaukyje"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_LITHUANIAN} "Sukurti Pradėti meniu nuorodą"
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
@@ -13,27 +13,27 @@ LangString inst_torrent ${LANG_LITHUANIAN} "Atidaryti .torrent failus su qBittor
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_LITHUANIAN} "Atidaryti magneto nuorodas su qBittorrent"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_LITHUANIAN} "Sukurti Windows užkardos leidimą"
LangString inst_firewall ${LANG_LITHUANIAN} "Sukurti Windows interneto užkardos leidimą"
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_LITHUANIAN} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_LITHUANIAN} "Išjungti Windows path ilgio limitaciją (260 ženklų MAX_PATH limitacija, reikalinga versija yra Windows 10 1607 ar naujesnė)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_LITHUANIAN} "Pridedu Windows užkardos leidimą"
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašau uždaryti programą prieš įdiegiant."
LangString inst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašome uždaryti programą prieš įdiegiant."
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_LITHUANIAN} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_LITHUANIAN} "Dabartinė versija bus pašalinta. Naudotojo nustatymai ir torrentai liks nepakeisti."
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_LITHUANIAN} "Šalinu ankstesnę versiją."
LangString inst_unist ${LANG_LITHUANIAN} "Šalinama ankstesnė versija."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
LangString launch_qbt ${LANG_LITHUANIAN} "Paleisti qBittorrent."
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_LITHUANIAN} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_LITHUANIAN} "Šis įdiegėjas veikia tik su 64 bitų Windows versija."
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_LITHUANIAN} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_LITHUANIAN} "Ši qBittorent versija reikalauja bent Windows 7."
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_LITHUANIAN} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_LITHUANIAN} "Šis įdiegėjas reikalauja bent Windows 10 1809."
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_LITHUANIAN} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_LITHUANIAN} "Pašalinti qBittorrent"
;------------------------------------
;Uninstaller strings
@@ -49,14 +49,14 @@ LangString remove_registry ${LANG_LITHUANIAN} "Pašalinti registro raktus"
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
LangString remove_conf ${LANG_LITHUANIAN} "Pašalinti nustatymų failus"
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
LangString remove_firewall ${LANG_LITHUANIAN} "Pašalinti Windows užkardos leidimą"
LangString remove_firewall ${LANG_LITHUANIAN} "Pašalinti Windows interneto užkardos leidimą"
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
LangString remove_firewallinfo ${LANG_LITHUANIAN} "Šalinu Windows užkardos leidimą"
LangString remove_firewallinfo ${LANG_LITHUANIAN} "Šalinamas Windows interneto užkardos leidimas"
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_LITHUANIAN} "Pašalinti torentus ir podėlio duomenis"
LangString remove_cache ${LANG_LITHUANIAN} "Pašalinti torentus ir talpyklos duomenis"
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašau uždarykite programą prieš išdiegiant."
LangString uninst_warning ${LANG_LITHUANIAN} "qBittorrent yra paleistas. Prašome uždaryti programą prieš išdiegiant."
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_LITHUANIAN} "Negaliu pašalinti .torrent asociacijos. Ji yra susieti su:"
LangString uninst_tor_warn ${LANG_LITHUANIAN} "Neįmanoma pašalinti .torrent asociacijos. Ji yra susieta su:"
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
LangString uninst_mag_warn ${LANG_LITHUANIAN} "Negaliu pašalinti magneto asociacijos. Jis susietas su:"
LangString uninst_mag_warn ${LANG_LITHUANIAN} "Neįmanoma pašalinti magneto asociacijos. Ji yra susieta su:"

View File

@@ -1,62 +1,62 @@
;Installer strings
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
LangString inst_qbt_req ${LANG_SLOVAK} "qBittorrent (required)"
LangString inst_qbt_req ${LANG_SLOVAK} "qBittorrent (požadované)"
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
LangString inst_dekstop ${LANG_SLOVAK} "Create Desktop Shortcut"
LangString inst_dekstop ${LANG_SLOVAK} "Vytvoriť odkaz na pracovnej ploche"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_SLOVAK} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_SLOVAK} "Vytvoriť odkaz v štart menu"
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
LangString inst_startup ${LANG_SLOVAK} "Start qBittorrent on Windows start up"
LangString inst_startup ${LANG_SLOVAK} "Sputiť qBittorrent pri štarte Windowsu"
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_SLOVAK} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_SLOVAK} "Otvárať .torrent súbory v qBittorrent"
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_SLOVAK} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_SLOVAK} "Otvárať magnet odkazy v qBittorrent"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_SLOVAK} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_SLOVAK} "Pridať pravidlo do Windows Firewall"
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_SLOVAK} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_SLOVAK} "Vypnúť limit dĺžky cesty Windowsu (260 znaková MAX_PATH limitácia, vyžaduje Windows 10 1607 alebo novšie)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_SLOVAK} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_SLOVAK} "Pridáva sa pravidlo do Windows Firewall"
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_SLOVAK} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_SLOVAK} "qBittorrent je spustený. Zatvorte prosím aplikáciu pred inštaláciou."
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_SLOVAK} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_SLOVAK} "Aktuálna verzia bude odinštalovaná. Užívateľské nastavenia a torrenty sa zachovajú."
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_SLOVAK} "Uninstalling previous version."
LangString inst_unist ${LANG_SLOVAK} "Odinštalácia predchádzajúcej verzie."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
LangString launch_qbt ${LANG_SLOVAK} "Launch qBittorrent."
LangString launch_qbt ${LANG_SLOVAK} "Spustiť qBittorrent."
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_SLOVAK} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_SLOVAK} "Táto inštalácia funguje iba na 64-bitových verziách Windowsu."
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_SLOVAK} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_SLOVAK} "Táto qBittorrent verzia vyžaduje aspoň Windows 7."
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_SLOVAK} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_SLOVAK} "Tento inštalátor vyžaduje aspoň Windows 10 1809."
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_SLOVAK} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_SLOVAK} "Odinštalovať qBittorrent"
;------------------------------------
;Uninstaller strings
;LangString remove_files ${LANG_ENGLISH} "Remove files"
LangString remove_files ${LANG_SLOVAK} "Remove files"
LangString remove_files ${LANG_SLOVAK} "Odstrániť súbory"
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
LangString remove_shortcuts ${LANG_SLOVAK} "Remove shortcuts"
LangString remove_shortcuts ${LANG_SLOVAK} "Odstrániť odkazy"
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
LangString remove_associations ${LANG_SLOVAK} "Remove file associations"
LangString remove_associations ${LANG_SLOVAK} "Odstrániť asociácie súborov"
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
LangString remove_registry ${LANG_SLOVAK} "Remove registry keys"
LangString remove_registry ${LANG_SLOVAK} "Odstrániť kľúče registrov"
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
LangString remove_conf ${LANG_SLOVAK} "Remove configuration files"
LangString remove_conf ${LANG_SLOVAK} "Odstrániť konfiguračné súbory"
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
LangString remove_firewall ${LANG_SLOVAK} "Remove Windows Firewall rule"
LangString remove_firewall ${LANG_SLOVAK} "Odstrániť pravidlo z Windows Firewall"
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
LangString remove_firewallinfo ${LANG_SLOVAK} "Removing Windows Firewall rule"
LangString remove_firewallinfo ${LANG_SLOVAK} "Odstraňuje sa pravidlo z Windows Firewall"
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_SLOVAK} "Remove torrents and cached data"
LangString remove_cache ${LANG_SLOVAK} "Odstrániť torrenty a dáta z vyrovnávacej pamäti"
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_SLOVAK} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_SLOVAK} "qBittorrent je spustený. Zatvorte prosím aplikáciu pred odinštaláciou."
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_SLOVAK} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_SLOVAK} "Neodstraňuje sa .torrent asociácia. Asociované s:"
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
LangString uninst_mag_warn ${LANG_SLOVAK} "Not removing magnet association. It is associated with:"
LangString uninst_mag_warn ${LANG_SLOVAK} "Neodstraňuje sa magnet asociácia. Asociované s:"

View File

@@ -27,11 +27,11 @@ LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
LangString launch_qbt ${LANG_SWEDISH} "Kör qBittorrent."
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_SWEDISH} "Installationsprogrammet fungerar endast i 64-bitars Windows-versioner."
LangString inst_requires_64bit ${LANG_SWEDISH} "Det här installationsprogrammet fungerar endast i 64-bitars Windows-versioner."
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_SWEDISH} "Den här qBittorrent-versionen kräver minst Windows 7."
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_SWEDISH} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_SWEDISH} "Det här installationsprogrammet kräver minst Windows 10 1809."
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_SWEDISH} "Avinstallera qBittorrent"

View File

@@ -31,7 +31,7 @@ LangString inst_requires_64bit ${LANG_TURKISH} "Bu yükleyici sadece 64-bit Wind
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_TURKISH} "Bu qBittorrent sürümü en az Windows 7 gerektirir."
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_TURKISH} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_TURKISH} "Bu yükleyici en az Windows 10 1809 gerektirir."
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_TURKISH} "qBittorrent'i kaldır"

View File

@@ -1,62 +1,62 @@
;Installer strings
;LangString inst_qbt_req ${LANG_ENGLISH} "qBittorrent (required)"
LangString inst_qbt_req ${LANG_UZBEK} "qBittorrent (required)"
LangString inst_qbt_req ${LANG_UZBEK} "qBittorrent (talab qilinadi)"
;LangString inst_dekstop ${LANG_ENGLISH} "Create Desktop Shortcut"
LangString inst_dekstop ${LANG_UZBEK} "Create Desktop Shortcut"
LangString inst_dekstop ${LANG_UZBEK} "Ish Stolida Yorliq Yaratilsin"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_UZBEK} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_UZBEK} "Boshlash Menyusida Yorliq Yaratilsin"
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
LangString inst_startup ${LANG_UZBEK} "Start qBittorrent on Windows start up"
LangString inst_startup ${LANG_UZBEK} "qBittorrent Windows bilan birga ishga tushirilsin"
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_UZBEK} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_UZBEK} ".torrent fayllar qBittorrent bilan ochilsin"
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_UZBEK} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_UZBEK} "Magnit havolalar qBittorrent bilan ochilsin"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_UZBEK} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasi qoʻshilsin"
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_UZBEK} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_UZBEK} "Windows yoʻl uzunligi cheklovi olib tashlansin (260 belgi MAX_PATH cheklovi, Windows 10 1607 va yuqorisi talab qilinadi)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_UZBEK} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasini qoʻshish"
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_UZBEK} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_UZBEK} "qBittorrent ishga tushgan. Iltimos, oʻrnatishdan oldin dasturni yoping."
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_UZBEK} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_UZBEK} "Hozirgi versiya oʻchiriladi. Foydalanuvchi sozlamalari va torrentlar oʻzgarishsiz qoladi."
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_UZBEK} "Uninstalling previous version."
LangString inst_unist ${LANG_UZBEK} "Oldingi versiyani oʻchirish."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
LangString launch_qbt ${LANG_UZBEK} "Launch qBittorrent."
LangString launch_qbt ${LANG_UZBEK} "qBittorrent ishga tushirilsin."
;LangString inst_requires_64bit ${LANG_ENGLISH} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_UZBEK} "This installer works only in 64-bit Windows versions."
LangString inst_requires_64bit ${LANG_UZBEK} "Bu oʻrnatuvchi faqat Windows 64-bit versiyalarda ishlaydi."
;LangString inst_requires_win7 ${LANG_ENGLISH} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_UZBEK} "This qBittorrent version requires at least Windows 7."
LangString inst_requires_win7 ${LANG_UZBEK} "qBittorrent bu versiyasi kamida Windows 7 talab qiladi."
;LangString inst_requires_win10 ${LANG_ENGLISH} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_UZBEK} "This installer requires at least Windows 10 1809."
LangString inst_requires_win10 ${LANG_UZBEK} "Bu oʻrnatuvchi kamida Windows 10 1809 talab qiladi."
;LangString inst_uninstall_link_description ${LANG_ENGLISH} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_UZBEK} "Uninstall qBittorrent"
LangString inst_uninstall_link_description ${LANG_UZBEK} "qBittorrent oʻchirilsin"
;------------------------------------
;Uninstaller strings
;LangString remove_files ${LANG_ENGLISH} "Remove files"
LangString remove_files ${LANG_UZBEK} "Remove files"
LangString remove_files ${LANG_UZBEK} "Fayllar oʻchirilsin"
;LangString remove_shortcuts ${LANG_ENGLISH} "Remove shortcuts"
LangString remove_shortcuts ${LANG_UZBEK} "Remove shortcuts"
LangString remove_shortcuts ${LANG_UZBEK} "Yorliqlar oʻchirilsin"
;LangString remove_associations ${LANG_ENGLISH} "Remove file associations"
LangString remove_associations ${LANG_UZBEK} "Remove file associations"
LangString remove_associations ${LANG_UZBEK} "Fayl birlashmalari oʻchirilsin"
;LangString remove_registry ${LANG_ENGLISH} "Remove registry keys"
LangString remove_registry ${LANG_UZBEK} "Remove registry keys"
LangString remove_registry ${LANG_UZBEK} "Reyester kalitlari oʻchirilsin"
;LangString remove_conf ${LANG_ENGLISH} "Remove configuration files"
LangString remove_conf ${LANG_UZBEK} "Remove configuration files"
LangString remove_conf ${LANG_UZBEK} "Sozlama fayllari oʻchirilsin"
;LangString remove_firewall ${LANG_ENGLISH} "Remove Windows Firewall rule"
LangString remove_firewall ${LANG_UZBEK} "Remove Windows Firewall rule"
LangString remove_firewall ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasi oʻchirilsin"
;LangString remove_firewallinfo ${LANG_ENGLISH} "Removing Windows Firewall rule"
LangString remove_firewallinfo ${LANG_UZBEK} "Removing Windows Firewall rule"
LangString remove_firewallinfo ${LANG_UZBEK} "Windows Xavfsizlik Devori qoidasini oʻchirish"
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_UZBEK} "Remove torrents and cached data"
LangString remove_cache ${LANG_UZBEK} "Torrentlar va keshlangan maʼlumotlar oʻchirilsin"
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_UZBEK} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_UZBEK} "qBittorrent ishga tushgan. Iltimos, oʻchirishdan oldin dasturni yoping."
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_UZBEK} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_UZBEK} ".torrent birlashmasi oʻchirilmadi. U quyidagi bilan birlashgan:"
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"
LangString uninst_mag_warn ${LANG_UZBEK} "Not removing magnet association. It is associated with:"
LangString uninst_mag_warn ${LANG_UZBEK} "Magnit birlashmasi oʻchirilmadi. U quyidagi bilan birlashgan:"

View File

@@ -35,6 +35,8 @@ Section $(inst_qbt_req) ;"qBittorrent (required)"
SetOutPath "$INSTDIR\translations"
; Put files there
File /r "translations\qt*.qm"
; Restore output path because it affects `CreateShortCut`. It affects the "Start in" field.
SetOutPath $INSTDIR
; Write the installation path into the registry
WriteRegStr HKLM "Software\qBittorrent" "InstallLocation" "$INSTDIR"

View File

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

View File

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

View File

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

View File

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

View File

@@ -88,6 +88,7 @@
#include "base/version.h"
#include "applicationinstancemanager.h"
#include "filelogger.h"
#include "upgrade.h"
#ifndef DISABLE_GUI
#include "gui/addnewtorrentdialog.h"
@@ -96,6 +97,7 @@
#include "gui/shutdownconfirmdialog.h"
#include "gui/uithememanager.h"
#include "gui/utils.h"
#include "gui/windowstate.h"
#endif // DISABLE_GUI
#ifndef DISABLE_WEBUI
@@ -120,12 +122,109 @@ namespace
#ifndef DISABLE_GUI
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
#endif
QString serializeParams(const QBtCommandLineParameters &params)
{
QStringList result;
// Because we're passing a string list to the currently running
// qBittorrent process, we need some way of passing along the options
// the user has specified. Here we place special strings that are
// almost certainly not going to collide with a file path or URL
// specified by the user, and placing them at the beginning of the
// string list so that they will be processed before the list of
// torrent paths or URLs.
const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
if (!addTorrentParams.savePath.isEmpty())
result.append(u"@savePath=" + addTorrentParams.savePath.data());
if (addTorrentParams.addPaused.has_value())
result.append(*addTorrentParams.addPaused ? u"@addPaused=1"_qs : u"@addPaused=0"_qs);
if (addTorrentParams.skipChecking)
result.append(u"@skipChecking"_qs);
if (!addTorrentParams.category.isEmpty())
result.append(u"@category=" + addTorrentParams.category);
if (addTorrentParams.sequential)
result.append(u"@sequential"_qs);
if (addTorrentParams.firstLastPiecePriority)
result.append(u"@firstLastPiecePriority"_qs);
if (params.skipDialog.has_value())
result.append(*params.skipDialog ? u"@skipDialog=1"_qs : u"@skipDialog=0"_qs);
result += params.torrentSources;
return result.join(PARAMS_SEPARATOR);
}
QBtCommandLineParameters parseParams(const QString &str)
{
QBtCommandLineParameters parsedParams;
BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
for (QString param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
{
param = param.trimmed();
// Process strings indicating options specified by the user.
if (param.startsWith(u"@savePath="))
{
addTorrentParams.savePath = Path(param.mid(10));
continue;
}
if (param.startsWith(u"@addPaused="))
{
addTorrentParams.addPaused = (QStringView(param).mid(11).toInt() != 0);
continue;
}
if (param == u"@skipChecking")
{
addTorrentParams.skipChecking = true;
continue;
}
if (param.startsWith(u"@category="))
{
addTorrentParams.category = param.mid(10);
continue;
}
if (param == u"@sequential")
{
addTorrentParams.sequential = true;
continue;
}
if (param == u"@firstLastPiecePriority")
{
addTorrentParams.firstLastPiecePriority = true;
continue;
}
if (param.startsWith(u"@skipDialog="))
{
parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0);
continue;
}
parsedParams.torrentSources.append(param);
}
return parsedParams;
}
}
Application::Application(int &argc, char **argv)
: BaseApplication(argc, argv)
, m_shutdownAct(ShutdownDialogAction::Exit)
, m_commandLineArgs(parseCommandLine(this->arguments()))
, m_commandLineArgs(parseCommandLine(Application::arguments()))
, m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY(u"Enabled"_qs))
, m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY(u"Backup"_qs))
, m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY(u"DeleteOld"_qs))
@@ -138,6 +237,7 @@ Application::Application(int &argc, char **argv)
, m_processMemoryPriority(SETTINGS_KEY(u"ProcessMemoryPriority"_qs))
#endif
#ifndef DISABLE_GUI
, m_startUpWindowState(u"GUI/StartUpWindowState"_qs)
, m_storeNotificationTorrentAdded(NOTIFICATIONS_SETTINGS_KEY(u"TorrentAdded"_qs))
#endif
{
@@ -171,6 +271,18 @@ Application::Application(int &argc, char **argv)
SettingsStorage::initInstance();
Preferences::initInstance();
const bool firstTimeUser = !Preferences::instance()->getAcceptedLegal();
if (!firstTimeUser)
{
if (!upgrade())
throw RuntimeError(u"Failed migration of old settings"_qs); // Not translatable. Translation isn't configured yet.
handleChangedDefaults(DefaultPreferencesMode::Legacy);
}
else
{
handleChangedDefaults(DefaultPreferencesMode::Current);
}
initializeTranslation();
connect(this, &QCoreApplication::aboutToQuit, this, &Application::cleanup);
@@ -222,6 +334,16 @@ MainWindow *Application::mainWindow()
return m_window;
}
WindowState Application::startUpWindowState() const
{
return m_startUpWindowState;
}
void Application::setStartUpWindowState(const WindowState windowState)
{
m_startUpWindowState = windowState;
}
bool Application::isTorrentAddedNotificationsEnabled() const
{
return m_storeNotificationTorrentAdded;
@@ -365,7 +487,7 @@ void Application::processMessage(const QString &message)
}
#endif
const AddTorrentParams params = parseParams(message.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts));
const QBtCommandLineParameters params = parseParams(message);
// If Application is not allowed to process params immediately
// (i.e., other components are not ready) store params
if (m_isProcessingParamsAllowed)
@@ -440,6 +562,7 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
};
const QString logMsg = tr("Running external program. Torrent: \"%1\". Command: `%2`");
const QString logMsgError = tr("Failed to run external program. Torrent: \"%1\". Command: `%2`");
// The processing sequenece is different for Windows and other OS, this is intentional
#if defined(Q_OS_WIN)
@@ -459,8 +582,6 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
for (int i = 1; i < argCount; ++i)
argList += QString::fromWCharArray(args[i]);
LogMsg(logMsg.arg(torrent->name(), program));
QProcess proc;
proc.setProgram(QString::fromWCharArray(args[0]));
proc.setArguments(argList);
@@ -485,7 +606,11 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
args->startupInfo->hStdOutput = nullptr;
args->startupInfo->hStdError = nullptr;
});
proc.startDetached();
if (proc.startDetached())
LogMsg(logMsg.arg(torrent->name(), program));
else
LogMsg(logMsgError.arg(torrent->name(), program));
#else // Q_OS_WIN
QStringList args = Utils::String::splitCommand(programTemplate);
@@ -501,11 +626,21 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
arg = replaceVariables(arg);
}
// show intended command in log
LogMsg(logMsg.arg(torrent->name(), replaceVariables(programTemplate)));
const QString command = args.takeFirst();
QProcess::startDetached(command, args);
QProcess proc;
proc.setProgram(command);
proc.setArguments(args);
if (proc.startDetached())
{
// show intended command in log
LogMsg(logMsg.arg(torrent->name(), replaceVariables(programTemplate)));
}
else
{
// show intended command in log
LogMsg(logMsgError.arg(torrent->name(), replaceVariables(programTemplate)));
}
#endif
}
@@ -600,72 +735,12 @@ void Application::allTorrentsFinished()
exit();
}
bool Application::sendParams(const QStringList &params)
bool Application::callMainInstance()
{
return m_instanceManager->sendMessage(params.join(PARAMS_SEPARATOR));
return m_instanceManager->sendMessage(serializeParams(commandLineArgs()));
}
Application::AddTorrentParams Application::parseParams(const QStringList &params) const
{
AddTorrentParams parsedParams;
BitTorrent::AddTorrentParams &torrentParams = parsedParams.torrentParams;
for (QString param : params)
{
param = param.trimmed();
// Process strings indicating options specified by the user.
if (param.startsWith(u"@savePath="))
{
torrentParams.savePath = Path(param.mid(10));
continue;
}
if (param.startsWith(u"@addPaused="))
{
torrentParams.addPaused = (QStringView(param).mid(11).toInt() != 0);
continue;
}
if (param == u"@skipChecking")
{
torrentParams.skipChecking = true;
continue;
}
if (param.startsWith(u"@category="))
{
torrentParams.category = param.mid(10);
continue;
}
if (param == u"@sequential")
{
torrentParams.sequential = true;
continue;
}
if (param == u"@firstLastPiecePriority")
{
torrentParams.firstLastPiecePriority = true;
continue;
}
if (param.startsWith(u"@skipDialog="))
{
parsedParams.skipTorrentDialog = (QStringView(param).mid(12).toInt() != 0);
continue;
}
parsedParams.torrentSource = param;
break;
}
return parsedParams;
}
void Application::processParams(const AddTorrentParams &params)
void Application::processParams(const QBtCommandLineParameters &params)
{
#ifndef DISABLE_GUI
// There are two circumstances in which we want to show the torrent
@@ -673,15 +748,21 @@ void Application::processParams(const AddTorrentParams &params)
// be shown and skipTorrentDialog is undefined. The other is when
// skipTorrentDialog is false, meaning that the application setting
// should be overridden.
const bool showDialogForThisTorrent = !params.skipTorrentDialog.value_or(!AddNewTorrentDialog::isEnabled());
if (showDialogForThisTorrent)
AddNewTorrentDialog::show(params.torrentSource, params.torrentParams, m_window);
const bool showDialog = !params.skipDialog.value_or(!AddNewTorrentDialog::isEnabled());
if (showDialog)
{
for (const QString &torrentSource : params.torrentSources)
AddNewTorrentDialog::show(torrentSource, params.addTorrentParams, m_window);
}
else
#endif
BitTorrent::Session::instance()->addTorrent(params.torrentSource, params.torrentParams);
{
for (const QString &torrentSource : params.torrentSources)
BitTorrent::Session::instance()->addTorrent(torrentSource, params.addTorrentParams);
}
}
int Application::exec(const QStringList &params)
int Application::exec()
try
{
#if !defined(DISABLE_WEBUI) && defined(DISABLE_GUI)
@@ -706,7 +787,7 @@ try
#ifndef DISABLE_GUI
UIThemeManager::initInstance();
m_desktopIntegration = new DesktopIntegration(this);
m_desktopIntegration = new DesktopIntegration;
m_desktopIntegration->setToolTip(tr("Loading torrents..."));
#ifndef Q_OS_MACOS
auto *desktopIntegrationMenu = new QMenu;
@@ -714,18 +795,15 @@ try
actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_qs));
actionExit->setMenuRole(QAction::QuitRole);
actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
connect(actionExit, &QAction::triggered, this, [this]()
connect(actionExit, &QAction::triggered, this, []
{
QApplication::exit();
});
desktopIntegrationMenu->addAction(actionExit);
m_desktopIntegration->setMenu(desktopIntegrationMenu);
#endif
const auto *pref = Preferences::instance();
#ifndef Q_OS_MACOS
const bool isHidden = m_desktopIntegration->isActive() && pref->startMinimized() && pref->minimizeToTray();
const bool isHidden = m_desktopIntegration->isActive() && (startUpWindowState() == WindowState::Hidden);
#else
const bool isHidden = false;
#endif
@@ -735,7 +813,7 @@ try
createStartupProgressDialog();
// Add a small delay to avoid "flashing" the progress dialog in case there are not many torrents to restore.
m_startupProgressDialog->setMinimumDuration(1000);
if (pref->startMinimized())
if (startUpWindowState() != WindowState::Normal)
m_startupProgressDialog->setWindowState(Qt::WindowMinimized);
}
else
@@ -788,12 +866,15 @@ try
});
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
// we must not delete menu while it is used by DesktopIntegration
auto *oldMenu = m_desktopIntegration->menu();
const MainWindow::State windowState = (!m_startupProgressDialog || (m_startupProgressDialog->windowState() & Qt::WindowMinimized))
? MainWindow::Minimized : MainWindow::Normal;
#ifndef Q_OS_MACOS
const WindowState windowState = !m_startupProgressDialog ? WindowState::Hidden
: (m_startupProgressDialog->windowState() & Qt::WindowMinimized) ? WindowState::Minimized
: WindowState::Normal;
#else
const WindowState windowState = (m_startupProgressDialog->windowState() & Qt::WindowMinimized)
? WindowState::Minimized : WindowState::Normal;
#endif
m_window = new MainWindow(this, windowState);
delete oldMenu;
delete m_startupProgressDialog;
#ifdef Q_OS_WIN
auto *pref = Preferences::instance();
@@ -840,13 +921,14 @@ try
#endif // DISABLE_WEBUI
m_isProcessingParamsAllowed = true;
for (const AddTorrentParams &params : m_paramsQueue)
for (const QBtCommandLineParameters &params : m_paramsQueue)
processParams(params);
m_paramsQueue.clear();
});
if (!params.isEmpty())
m_paramsQueue.append(parseParams(params));
const QBtCommandLineParameters params = commandLineArgs();
if (!params.torrentSources.isEmpty())
m_paramsQueue.append(params);
return BaseApplication::exec();
}
@@ -882,7 +964,7 @@ void Application::createStartupProgressDialog()
m_startupProgressDialog = new QProgressDialog(tr("Loading torrents..."), tr("Exit"), 0, 100);
m_startupProgressDialog->setAttribute(Qt::WA_DeleteOnClose);
m_startupProgressDialog->setWindowFlag(Qt::WindowMinimizeButtonHint);
m_startupProgressDialog->setMinimumDuration(0); // Show dialog immediatelly by default
m_startupProgressDialog->setMinimumDuration(0); // Show dialog immediately by default
m_startupProgressDialog->setAutoReset(false);
m_startupProgressDialog->setAutoClose(false);
@@ -932,7 +1014,8 @@ bool Application::event(QEvent *ev)
path = static_cast<QFileOpenEvent *>(ev)->url().toString();
qDebug("Received a mac file open event: %s", qUtf8Printable(path));
const AddTorrentParams params = parseParams({path});
QBtCommandLineParameters params;
params.torrentSources.append(path);
// If Application is not allowed to process params immediately
// (i.e., other components are not ready) store params
if (m_isProcessingParamsAllowed)
@@ -1039,7 +1122,22 @@ void Application::applyMemoryWorkingSetLimit() const
if (::getrlimit(RLIMIT_RSS, &limit) != 0)
return;
limit.rlim_cur = memoryWorkingSetLimit() * MiB;
const size_t newSize = memoryWorkingSetLimit() * MiB;
if (newSize > limit.rlim_max)
{
// try to raise the hard limit
rlimit newLimit = limit;
newLimit.rlim_max = newSize;
if (::setrlimit(RLIMIT_RSS, &newLimit) != 0)
{
const auto message = QString::fromLocal8Bit(strerror(errno));
LogMsg(tr("Failed to set physical memory (RAM) usage hard limit. Requested size: %1. System hard limit: %2. Error code: %3. Error message: \"%4\"")
.arg(QString::number(newSize), QString::number(limit.rlim_max), QString::number(errno), message), Log::WARNING);
return;
}
}
limit.rlim_cur = newSize;
if (::setrlimit(RLIMIT_RSS, &limit) != 0)
{
const auto message = QString::fromLocal8Bit(strerror(errno));
@@ -1201,6 +1299,7 @@ void Application::cleanup()
::ShutdownBlockReasonDestroy(reinterpret_cast<HWND>(m_window->effectiveWinId()));
#endif // Q_OS_WIN
delete m_window;
delete m_desktopIntegration;
UIThemeManager::freeInstance();
}
#endif // DISABLE_GUI

View File

@@ -96,10 +96,10 @@ public:
Application(int &argc, char **argv);
~Application() override;
int exec(const QStringList &params);
int exec();
bool isRunning();
bool sendParams(const QStringList &params);
bool callMainInstance();
const QBtCommandLineParameters &commandLineArgs() const;
// FileLogger properties
@@ -130,6 +130,9 @@ public:
DesktopIntegration *desktopIntegration() override;
MainWindow *mainWindow() override;
WindowState startUpWindowState() const override;
void setStartUpWindowState(WindowState windowState) override;
bool isTorrentAddedNotificationsEnabled() const override;
void setTorrentAddedNotificationsEnabled(bool value) override;
#endif
@@ -146,16 +149,8 @@ private slots:
#endif
private:
struct AddTorrentParams
{
QString torrentSource;
BitTorrent::AddTorrentParams torrentParams;
std::optional<bool> skipTorrentDialog;
};
void initializeTranslation();
AddTorrentParams parseParams(const QStringList &params) const;
void processParams(const AddTorrentParams &params);
void processParams(const QBtCommandLineParameters &params);
void runExternalProgram(const QString &programTemplate, const BitTorrent::Torrent *torrent) const;
void sendNotificationEmail(const BitTorrent::Torrent *torrent);
@@ -178,7 +173,7 @@ private:
ApplicationInstanceManager *m_instanceManager = nullptr;
QAtomicInt m_isCleanupRun;
bool m_isProcessingParamsAllowed = false;
ShutdownDialogAction m_shutdownAct;
ShutdownDialogAction m_shutdownAct = ShutdownDialogAction::Exit;
QBtCommandLineParameters m_commandLineArgs;
// FileLog
@@ -187,7 +182,7 @@ private:
QTranslator m_qtTranslator;
QTranslator m_translator;
QList<AddTorrentParams> m_paramsQueue;
QList<QBtCommandLineParameters> m_paramsQueue;
SettingValue<bool> m_storeFileLoggerEnabled;
SettingValue<bool> m_storeFileLoggerBackup;
@@ -203,6 +198,7 @@ private:
#endif
#ifndef DISABLE_GUI
SettingValue<WindowState> m_startUpWindowState;
SettingValue<bool> m_storeNotificationTorrentAdded;
DesktopIntegration *m_desktopIntegration = nullptr;

View File

@@ -198,13 +198,15 @@ namespace
int value(const QString &arg) const
{
QString val = StringOption::value(arg);
const QString val = StringOption::value(arg);
bool ok = false;
int res = val.toInt(&ok);
const int res = val.toInt(&ok);
if (!ok)
{
throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=<value>'")
.arg(fullParameter(), u"<integer value>"_qs));
}
return res;
}
@@ -340,14 +342,7 @@ namespace
}
QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &env)
: showHelp(false)
, relativeFastresumePaths(RELATIVE_FASTRESUME.value(env))
, skipChecking(SKIP_HASH_CHECK_OPTION.value(env))
, sequential(SEQUENTIAL_OPTION.value(env))
, firstLastPiecePriority(FIRST_AND_LAST_OPTION.value(env))
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
, showVersion(false)
#endif
: relativeFastresumePaths(RELATIVE_FASTRESUME.value(env))
#ifndef DISABLE_GUI
, noSplash(NO_SPLASH_OPTION.value(env))
#elif !defined(Q_OS_WIN)
@@ -355,49 +350,16 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en
#endif
, webUiPort(WEBUI_PORT_OPTION.value(env, -1))
, torrentingPort(TORRENTING_PORT_OPTION.value(env, -1))
, addPaused(PAUSED_OPTION.value(env))
, skipDialog(SKIP_DIALOG_OPTION.value(env))
, profileDir(PROFILE_OPTION.value(env))
, configurationName(CONFIGURATION_OPTION.value(env))
, savePath(SAVE_PATH_OPTION.value(env))
, category(CATEGORY_OPTION.value(env))
{
}
QStringList QBtCommandLineParameters::paramList() const
{
QStringList result;
// Because we're passing a string list to the currently running
// qBittorrent process, we need some way of passing along the options
// the user has specified. Here we place special strings that are
// almost certainly not going to collide with a file path or URL
// specified by the user, and placing them at the beginning of the
// string list so that they will be processed before the list of
// torrent paths or URLs.
if (!savePath.isEmpty())
result.append(u"@savePath=" + savePath.data());
if (addPaused.has_value())
result.append(*addPaused ? u"@addPaused=1"_qs : u"@addPaused=0"_qs);
if (skipChecking)
result.append(u"@skipChecking"_qs);
if (!category.isEmpty())
result.append(u"@category=" + category);
if (sequential)
result.append(u"@sequential"_qs);
if (firstLastPiecePriority)
result.append(u"@firstLastPiecePriority"_qs);
if (skipDialog.has_value())
result.append(*skipDialog ? u"@skipDialog=1"_qs : u"@skipDialog=0"_qs);
result += torrents;
return result;
addTorrentParams.savePath = Path(SAVE_PATH_OPTION.value(env));
addTorrentParams.category = CATEGORY_OPTION.value(env);
addTorrentParams.skipChecking = SKIP_HASH_CHECK_OPTION.value(env);
addTorrentParams.sequential = SEQUENTIAL_OPTION.value(env);
addTorrentParams.firstLastPiecePriority = FIRST_AND_LAST_OPTION.value(env);
addTorrentParams.addPaused = PAUSED_OPTION.value(env);
}
QBtCommandLineParameters parseCommandLine(const QStringList &args)
@@ -463,27 +425,27 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
}
else if (arg == SAVE_PATH_OPTION)
{
result.savePath = Path(SAVE_PATH_OPTION.value(arg));
result.addTorrentParams.savePath = Path(SAVE_PATH_OPTION.value(arg));
}
else if (arg == PAUSED_OPTION)
{
result.addPaused = PAUSED_OPTION.value(arg);
result.addTorrentParams.addPaused = PAUSED_OPTION.value(arg);
}
else if (arg == SKIP_HASH_CHECK_OPTION)
{
result.skipChecking = true;
result.addTorrentParams.skipChecking = true;
}
else if (arg == CATEGORY_OPTION)
{
result.category = CATEGORY_OPTION.value(arg);
result.addTorrentParams.category = CATEGORY_OPTION.value(arg);
}
else if (arg == SEQUENTIAL_OPTION)
{
result.sequential = true;
result.addTorrentParams.sequential = true;
}
else if (arg == FIRST_AND_LAST_OPTION)
{
result.firstLastPiecePriority = true;
result.addTorrentParams.firstLastPiecePriority = true;
}
else if (arg == SKIP_DIALOG_OPTION)
{
@@ -502,9 +464,9 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
torrentPath.setFile(arg);
if (torrentPath.exists())
result.torrents += torrentPath.absoluteFilePath();
result.torrentSources += torrentPath.absoluteFilePath();
else
result.torrents += arg;
result.torrentSources += arg;
}
}

View File

@@ -35,6 +35,7 @@
#include <QString>
#include <QStringList>
#include "base/bittorrent/addtorrentparams.h"
#include "base/exceptions.h"
#include "base/path.h"
@@ -42,32 +43,29 @@ class QProcessEnvironment;
struct QBtCommandLineParameters
{
bool showHelp;
bool relativeFastresumePaths;
bool skipChecking;
bool sequential;
bool firstLastPiecePriority;
bool showHelp = false;
bool relativeFastresumePaths = false;
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
bool showVersion;
bool showVersion = false;
#endif
#ifndef DISABLE_GUI
bool noSplash;
bool noSplash = false;
#elif !defined(Q_OS_WIN)
bool shouldDaemonize;
bool shouldDaemonize = false;
#endif
int webUiPort;
int torrentingPort;
std::optional<bool> addPaused;
int webUiPort = -1;
int torrentingPort = -1;
std::optional<bool> skipDialog;
QStringList torrents;
Path profileDir;
QString configurationName;
Path savePath;
QString category;
QStringList torrentSources;
BitTorrent::AddTorrentParams addTorrentParams;
QString unknownParameter;
QBtCommandLineParameters() = default;
explicit QBtCommandLineParameters(const QProcessEnvironment &);
QStringList paramList() const;
};
class CommandLineParameterError : public RuntimeError

View File

@@ -118,6 +118,17 @@ int main(int argc, char *argv[])
// Create Application
auto app = std::make_unique<Application>(argc, argv);
#ifdef Q_OS_WIN
// QCoreApplication::applicationDirPath() needs an Application object instantiated first
// Let's hope that there won't be a crash before this line
const char *envName = "_NT_SYMBOL_PATH";
const QString envValue = qEnvironmentVariable(envName);
if (envValue.isEmpty())
qputenv(envName, Application::applicationDirPath().toLocal8Bit());
else
qputenv(envName, u"%1;%2"_qs.arg(envValue, Application::applicationDirPath()).toLocal8Bit());
#endif
const QBtCommandLineParameters params = app->commandLineArgs();
if (!params.unknownParameter.isEmpty())
{
@@ -182,7 +193,7 @@ int main(int argc, char *argv[])
#endif
QThread::msleep(300);
app->sendParams(params.paramList());
app->callMainInstance();
return EXIT_SUCCESS;
}
@@ -221,26 +232,6 @@ int main(int argc, char *argv[])
app->setAttribute(Qt::AA_DontShowIconsInMenus);
#endif
if (!firstTimeUser)
{
handleChangedDefaults(DefaultPreferencesMode::Legacy);
#ifndef DISABLE_GUI
if (!upgrade()) return EXIT_FAILURE;
#elif defined(Q_OS_WIN)
if (!upgrade(_isatty(_fileno(stdin))
&& _isatty(_fileno(stdout)))) return EXIT_FAILURE;
#else
if (!upgrade(!params.shouldDaemonize
&& isatty(fileno(stdin))
&& isatty(fileno(stdout)))) return EXIT_FAILURE;
#endif
}
else
{
handleChangedDefaults(DefaultPreferencesMode::Current);
}
#if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
if (params.shouldDaemonize)
{
@@ -267,13 +258,18 @@ int main(int argc, char *argv[])
registerSignalHandlers();
return app->exec(params.paramList());
return app->exec();
}
catch (const CommandLineParameterError &er)
{
displayBadArgMessage(er.message());
return EXIT_FAILURE;
}
catch (const RuntimeError &er)
{
qDebug() << er.message();
return EXIT_FAILURE;
}
}
#if !defined(DISABLE_GUI)
@@ -287,7 +283,7 @@ void showSplashScreen()
painter.drawText(224 - painter.fontMetrics().horizontalAdvance(version), 270, version);
QSplashScreen *splash = new QSplashScreen(splashImg);
splash->show();
QTimer::singleShot(1500ms, splash, &QObject::deleteLater);
QTimer::singleShot(1500ms, Qt::CoarseTimer, splash, &QObject::deleteLater);
qApp->processEvents();
}
#endif // DISABLE_GUI

View File

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

View File

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

View File

@@ -39,13 +39,12 @@
#include "base/profile.h"
#include "base/settingsstorage.h"
#include "base/settingvalue.h"
#include "base/utils/fs.h"
#include "base/utils/io.h"
#include "base/utils/string.h"
namespace
{
const int MIGRATION_VERSION = 4;
const int MIGRATION_VERSION = 6;
const QString MIGRATION_VERSION_KEY = u"Meta/MigrationVersion"_qs;
void exportWebUIHttpsFiles()
@@ -344,7 +343,7 @@ namespace
switch (number)
{
case 0:
settingsStorage->storeValue(key, Net::ProxyType::None);
settingsStorage->storeValue(key, u"None"_qs);
break;
case 1:
settingsStorage->storeValue(key, Net::ProxyType::HTTP);
@@ -353,10 +352,10 @@ namespace
settingsStorage->storeValue(key, Net::ProxyType::SOCKS5);
break;
case 3:
settingsStorage->storeValue(key, Net::ProxyType::HTTP_PW);
settingsStorage->storeValue(key, u"HTTP_PW"_qs);
break;
case 4:
settingsStorage->storeValue(key, Net::ProxyType::SOCKS5_PW);
settingsStorage->storeValue(key, u"SOCKS5_PW"_qs);
break;
case 5:
settingsStorage->storeValue(key, Net::ProxyType::SOCKS4);
@@ -370,6 +369,46 @@ namespace
}
}
void migrateProxySettings()
{
auto *settingsStorage = SettingsStorage::instance();
const auto proxyType = settingsStorage->loadValue<QString>(u"Network/Proxy/Type"_qs, u"None"_qs);
const auto onlyForTorrents = settingsStorage->loadValue<bool>(u"Network/Proxy/OnlyForTorrents"_qs)
|| (proxyType == u"SOCKS4");
if (proxyType == u"None")
{
settingsStorage->storeValue(u"Network/Proxy/Type"_qs, Net::ProxyType::HTTP);
settingsStorage->storeValue(u"Network/Proxy/Profiles/BitTorrent"_qs, false);
settingsStorage->storeValue(u"Network/Proxy/Profiles/RSS"_qs, false);
settingsStorage->storeValue(u"Network/Proxy/Profiles/Misc"_qs, false);
}
else
{
settingsStorage->storeValue(u"Network/Proxy/Profiles/BitTorrent"_qs, true);
settingsStorage->storeValue(u"Network/Proxy/Profiles/RSS"_qs, !onlyForTorrents);
settingsStorage->storeValue(u"Network/Proxy/Profiles/Misc"_qs, !onlyForTorrents);
if (proxyType == u"HTTP_PW"_qs)
{
settingsStorage->storeValue(u"Network/Proxy/Type"_qs, Net::ProxyType::HTTP);
settingsStorage->storeValue(u"Network/Proxy/AuthEnabled"_qs, true);
}
else if (proxyType == u"SOCKS5_PW"_qs)
{
settingsStorage->storeValue(u"Network/Proxy/Type"_qs, Net::ProxyType::SOCKS5);
settingsStorage->storeValue(u"Network/Proxy/AuthEnabled"_qs, true);
}
}
settingsStorage->removeValue(u"Network/Proxy/OnlyForTorrents"_qs);
const auto proxyHostnameLookup = settingsStorage->loadValue<bool>(u"BitTorrent/Session/ProxyHostnameLookup"_qs);
settingsStorage->storeValue(u"Network/Proxy/HostnameLookupEnabled"_qs, proxyHostnameLookup);
settingsStorage->removeValue(u"BitTorrent/Session/ProxyHostnameLookup"_qs);
}
#ifdef Q_OS_WIN
void migrateMemoryPrioritySettings()
{
@@ -384,9 +423,33 @@ namespace
}
}
#endif
void migrateStartupWindowState()
{
auto *settingsStorage = SettingsStorage::instance();
if (settingsStorage->hasKey(u"Preferences/General/StartMinimized"_qs))
{
const auto startMinimized = settingsStorage->loadValue<bool>(u"Preferences/General/StartMinimized"_qs);
const auto minimizeToTray = settingsStorage->loadValue<bool>(u"Preferences/General/MinimizeToTray"_qs);
const QString windowState = startMinimized ? (minimizeToTray ? u"Hidden"_qs : u"Minimized"_qs) : u"Normal"_qs;
settingsStorage->storeValue(u"GUI/StartUpWindowState"_qs, windowState);
}
}
void migrateChineseLocale()
{
auto *settingsStorage = SettingsStorage::instance();
const auto key = u"Preferences/General/Locale"_qs;
if (settingsStorage->hasKey(key))
{
const auto locale = settingsStorage->loadValue<QString>(key);
if (locale.compare(u"zh"_qs, Qt::CaseInsensitive) == 0)
settingsStorage->storeValue(key, u"zh_CN"_qs);
}
}
}
bool upgrade(const bool /*ask*/)
bool upgrade()
{
CachedSettingValue<int> version {MIGRATION_VERSION_KEY, 0};
@@ -413,6 +476,15 @@ bool upgrade(const bool /*ask*/)
migrateMemoryPrioritySettings();
#endif
if (version < 5)
{
migrateStartupWindowState();
migrateChineseLocale();
}
if (version < 6)
migrateProxySettings();
version = MIGRATION_VERSION;
}

View File

@@ -35,5 +35,5 @@ enum class DefaultPreferencesMode
};
void handleChangedDefaults(DefaultPreferencesMode mode);
bool upgrade(bool ask = true);
bool upgrade();
void setCurrentMigrationVersion();

View File

@@ -34,6 +34,7 @@ add_library(qbt_base STATIC
bittorrent/sessionstatus.h
bittorrent/speedmonitor.h
bittorrent/torrent.h
bittorrent/torrentcontenthandler.h
bittorrent/torrentcontentlayout.h
bittorrent/torrentcreatorthread.h
bittorrent/torrentimpl.h
@@ -101,6 +102,7 @@ add_library(qbt_base STATIC
utils/password.h
utils/random.h
utils/string.h
utils/thread.h
utils/version.h
version.h
@@ -108,6 +110,7 @@ add_library(qbt_base STATIC
applicationcomponent.cpp
asyncfilestorage.cpp
bittorrent/abstractfilestorage.cpp
bittorrent/addtorrentparams.cpp
bittorrent/bandwidthscheduler.cpp
bittorrent/bencoderesumedatastorage.cpp
bittorrent/categoryoptions.cpp
@@ -128,6 +131,7 @@ add_library(qbt_base STATIC
bittorrent/sessionimpl.cpp
bittorrent/speedmonitor.cpp
bittorrent/torrent.cpp
bittorrent/torrentcontenthandler.cpp
bittorrent/torrentcreatorthread.cpp
bittorrent/torrentimpl.cpp
bittorrent/torrentinfo.cpp
@@ -183,6 +187,7 @@ add_library(qbt_base STATIC
utils/password.cpp
utils/random.cpp
utils/string.cpp
utils/thread.cpp
)
target_link_libraries(qbt_base

View File

@@ -63,4 +63,11 @@ namespace Algorithm
while (it != set.end())
it = (p(*it) ? set.erase(it) : ++it);
}
template <typename List>
List sorted(List list)
{
list.sort();
return list;
}
}

View File

@@ -34,6 +34,7 @@ HEADERS += \
$$PWD/bittorrent/speedmonitor.h \
$$PWD/bittorrent/torrent.h \
$$PWD/bittorrent/torrentcontentlayout.h \
$$PWD/bittorrent/torrentcontenthandler.h \
$$PWD/bittorrent/torrentcreatorthread.h \
$$PWD/bittorrent/torrentimpl.h \
$$PWD/bittorrent/torrentinfo.h \
@@ -101,6 +102,7 @@ HEADERS += \
$$PWD/utils/password.h \
$$PWD/utils/random.h \
$$PWD/utils/string.h \
$$PWD/utils/thread.h \
$$PWD/utils/version.h \
$$PWD/version.h
@@ -108,6 +110,7 @@ SOURCES += \
$$PWD/applicationcomponent.cpp \
$$PWD/asyncfilestorage.cpp \
$$PWD/bittorrent/abstractfilestorage.cpp \
$$PWD/bittorrent/addtorrentparams.cpp \
$$PWD/bittorrent/bandwidthscheduler.cpp \
$$PWD/bittorrent/bencoderesumedatastorage.cpp \
$$PWD/bittorrent/categoryoptions.cpp \
@@ -128,6 +131,7 @@ SOURCES += \
$$PWD/bittorrent/sessionimpl.cpp \
$$PWD/bittorrent/speedmonitor.cpp \
$$PWD/bittorrent/torrent.cpp \
$$PWD/bittorrent/torrentcontenthandler.h \
$$PWD/bittorrent/torrentcreatorthread.cpp \
$$PWD/bittorrent/torrentimpl.cpp \
$$PWD/bittorrent/torrentinfo.cpp \
@@ -182,4 +186,5 @@ SOURCES += \
$$PWD/utils/net.cpp \
$$PWD/utils/password.cpp \
$$PWD/utils/random.cpp \
$$PWD/utils/string.cpp
$$PWD/utils/string.cpp \
$$PWD/utils/thread.cpp

View File

@@ -0,0 +1,170 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "addtorrentparams.h"
#include <tuple>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include "base/utils/string.h"
const QString PARAM_CATEGORY = u"category"_qs;
const QString PARAM_TAGS = u"tags"_qs;
const QString PARAM_SAVEPATH = u"save_path"_qs;
const QString PARAM_USEDOWNLOADPATH = u"use_download_path"_qs;
const QString PARAM_DOWNLOADPATH = u"download_path"_qs;
const QString PARAM_OPERATINGMODE = u"operating_mode"_qs;
const QString PARAM_QUEUETOP = u"add_to_top_of_queue"_qs;
const QString PARAM_STOPPED = u"stopped"_qs;
const QString PARAM_SKIPCHECKING = u"skip_checking"_qs;
const QString PARAM_CONTENTLAYOUT = u"content_layout"_qs;
const QString PARAM_AUTOTMM = u"use_auto_tmm"_qs;
const QString PARAM_UPLOADLIMIT = u"upload_limit"_qs;
const QString PARAM_DOWNLOADLIMIT = u"download_limit"_qs;
const QString PARAM_SEEDINGTIMELIMIT = u"seeding_time_limit"_qs;
const QString PARAM_RATIOLIMIT = u"ratio_limit"_qs;
namespace
{
TagSet parseTagSet(const QJsonArray &jsonArr)
{
TagSet tags;
for (const QJsonValue &jsonVal : jsonArr)
tags.insert(jsonVal.toString());
return tags;
}
QJsonArray serializeTagSet(const TagSet &tags)
{
QJsonArray arr;
for (const QString &tag : tags)
arr.append(tag);
return arr;
}
std::optional<bool> getOptionalBool(const QJsonObject &jsonObj, const QString &key)
{
const QJsonValue jsonVal = jsonObj.value(key);
if (jsonVal.isUndefined() || jsonVal.isNull())
return std::nullopt;
return jsonVal.toBool();
}
template <typename Enum>
std::optional<Enum> getOptionalEnum(const QJsonObject &jsonObj, const QString &key)
{
const QJsonValue jsonVal = jsonObj.value(key);
if (jsonVal.isUndefined() || jsonVal.isNull())
return std::nullopt;
return Utils::String::toEnum<Enum>(jsonVal.toString(), {});
}
template <typename Enum>
Enum getEnum(const QJsonObject &jsonObj, const QString &key)
{
const QJsonValue jsonVal = jsonObj.value(key);
return Utils::String::toEnum<Enum>(jsonVal.toString(), {});
}
}
bool BitTorrent::operator==(const AddTorrentParams &lhs, const AddTorrentParams &rhs)
{
return std::tie(lhs.name, lhs.category, lhs.tags,
lhs.savePath, lhs.useDownloadPath, lhs.downloadPath,
lhs.sequential, lhs.firstLastPiecePriority, lhs.addForced,
lhs.addToQueueTop, lhs.addPaused, lhs.stopCondition,
lhs.filePaths, lhs.filePriorities, lhs.skipChecking,
lhs.contentLayout, lhs.useAutoTMM, lhs.uploadLimit,
lhs.downloadLimit, lhs.seedingTimeLimit, lhs.ratioLimit)
== std::tie(rhs.name, rhs.category, rhs.tags,
rhs.savePath, rhs.useDownloadPath, rhs.downloadPath,
rhs.sequential, rhs.firstLastPiecePriority, rhs.addForced,
rhs.addToQueueTop, rhs.addPaused, rhs.stopCondition,
rhs.filePaths, rhs.filePriorities, rhs.skipChecking,
rhs.contentLayout, rhs.useAutoTMM, rhs.uploadLimit,
rhs.downloadLimit, rhs.seedingTimeLimit, rhs.ratioLimit);
}
BitTorrent::AddTorrentParams BitTorrent::parseAddTorrentParams(const QJsonObject &jsonObj)
{
AddTorrentParams params;
params.category = jsonObj.value(PARAM_CATEGORY).toString();
params.tags = parseTagSet(jsonObj.value(PARAM_TAGS).toArray());
params.savePath = Path(jsonObj.value(PARAM_SAVEPATH).toString());
params.useDownloadPath = getOptionalBool(jsonObj, PARAM_USEDOWNLOADPATH);
params.downloadPath = Path(jsonObj.value(PARAM_DOWNLOADPATH).toString());
params.addForced = (getEnum<BitTorrent::TorrentOperatingMode>(jsonObj, PARAM_OPERATINGMODE) == BitTorrent::TorrentOperatingMode::Forced);
params.addToQueueTop = getOptionalBool(jsonObj, PARAM_QUEUETOP);
params.addPaused = getOptionalBool(jsonObj, PARAM_STOPPED);
params.skipChecking = jsonObj.value(PARAM_SKIPCHECKING).toBool();
params.contentLayout = getOptionalEnum<BitTorrent::TorrentContentLayout>(jsonObj, PARAM_CONTENTLAYOUT);
params.useAutoTMM = getOptionalBool(jsonObj, PARAM_AUTOTMM);
params.uploadLimit = jsonObj.value(PARAM_UPLOADLIMIT).toInt(-1);
params.downloadLimit = jsonObj.value(PARAM_DOWNLOADLIMIT).toInt(-1);
params.seedingTimeLimit = jsonObj.value(PARAM_SEEDINGTIMELIMIT).toInt(BitTorrent::Torrent::USE_GLOBAL_SEEDING_TIME);
params.ratioLimit = jsonObj.value(PARAM_RATIOLIMIT).toDouble(BitTorrent::Torrent::USE_GLOBAL_RATIO);
return params;
}
QJsonObject BitTorrent::serializeAddTorrentParams(const AddTorrentParams &params)
{
QJsonObject jsonObj {
{PARAM_CATEGORY, params.category},
{PARAM_TAGS, serializeTagSet(params.tags)},
{PARAM_SAVEPATH, params.savePath.data()},
{PARAM_DOWNLOADPATH, params.downloadPath.data()},
{PARAM_OPERATINGMODE, Utils::String::fromEnum(params.addForced
? BitTorrent::TorrentOperatingMode::Forced : BitTorrent::TorrentOperatingMode::AutoManaged)},
{PARAM_SKIPCHECKING, params.skipChecking},
{PARAM_UPLOADLIMIT, params.uploadLimit},
{PARAM_DOWNLOADLIMIT, params.downloadLimit},
{PARAM_SEEDINGTIMELIMIT, params.seedingTimeLimit},
{PARAM_RATIOLIMIT, params.ratioLimit}
};
if (params.addToQueueTop)
jsonObj[PARAM_QUEUETOP] = *params.addToQueueTop;
if (params.addPaused)
jsonObj[PARAM_STOPPED] = *params.addPaused;
if (params.contentLayout)
jsonObj[PARAM_CONTENTLAYOUT] = Utils::String::fromEnum(*params.contentLayout);
if (params.useAutoTMM)
jsonObj[PARAM_AUTOTMM] = *params.useAutoTMM;
if (params.useDownloadPath)
jsonObj[PARAM_USEDOWNLOADPATH] = *params.useDownloadPath;
return jsonObj;
}

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -91,7 +91,7 @@ namespace
BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path, QObject *parent)
: ResumeDataStorage(path, parent)
, m_ioThread {new QThread(this)}
, m_ioThread {new QThread}
, m_asyncWorker {new Worker(path)}
{
Q_ASSERT(path.isAbsolute());
@@ -117,17 +117,11 @@ BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path,
qDebug() << "Registered torrents count: " << m_registeredTorrents.size();
m_asyncWorker->moveToThread(m_ioThread);
connect(m_ioThread, &QThread::finished, m_asyncWorker, &QObject::deleteLater);
m_asyncWorker->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_asyncWorker, &QObject::deleteLater);
m_ioThread->start();
}
BitTorrent::BencodeResumeDataStorage::~BencodeResumeDataStorage()
{
m_ioThread->quit();
m_ioThread->wait();
}
QVector<BitTorrent::TorrentID> BitTorrent::BencodeResumeDataStorage::registeredTorrents() const
{
return m_registeredTorrents;
@@ -210,10 +204,9 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
return nonstd::make_unexpected(tr("Cannot parse resume data: invalid format"));
LoadTorrentParams torrentParams;
torrentParams.restored = true;
torrentParams.category = fromLTString(resumeDataRoot.dict_find_string_value("qBt-category"));
torrentParams.name = fromLTString(resumeDataRoot.dict_find_string_value("qBt-name"));
torrentParams.hasSeedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus");
torrentParams.hasFinishedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus");
torrentParams.firstLastPiecePriority = resumeDataRoot.dict_find_int_value("qBt-firstLastPiecePriority");
torrentParams.seedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME);
@@ -387,7 +380,7 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
data["qBt-category"] = resumeData.category.toStdString();
data["qBt-tags"] = setToEntryList(resumeData.tags);
data["qBt-name"] = resumeData.name.toStdString();
data["qBt-seedStatus"] = resumeData.hasSeedStatus;
data["qBt-seedStatus"] = resumeData.hasFinishedStatus;
data["qBt-contentLayout"] = Utils::String::fromEnum(resumeData.contentLayout).toStdString();
data["qBt-firstLastPiecePriority"] = resumeData.firstLastPiecePriority;
data["qBt-stopCondition"] = Utils::String::fromEnum(resumeData.stopCondition).toStdString();

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -32,6 +32,8 @@
#include <QVector>
#include "base/pathfwd.h"
#include "base/utils/thread.h"
#include "resumedatastorage.h"
class QByteArray;
@@ -46,7 +48,6 @@ namespace BitTorrent
public:
explicit BencodeResumeDataStorage(const Path &path, QObject *parent = nullptr);
~BencodeResumeDataStorage() override;
QVector<TorrentID> registeredTorrents() const override;
LoadResumeDataResult load(const TorrentID &id) const override;
@@ -60,7 +61,7 @@ namespace BitTorrent
LoadResumeDataResult loadTorrentResumeData(const QByteArray &data, const QByteArray &metadata) const;
QVector<TorrentID> m_registeredTorrents;
QThread *m_ioThread = nullptr;
Utils::Thread::UniquePtr m_ioThread;
class Worker;
Worker *m_asyncWorker = nullptr;

View File

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

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2021-2023 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -28,6 +28,8 @@
#include "dbresumedatastorage.h"
#include <memory>
#include <queue>
#include <utility>
#include <libtorrent/bdecode.hpp>
@@ -38,7 +40,9 @@
#include <libtorrent/write_resume_data.hpp>
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QMutex>
#include <QSet>
#include <QSqlDatabase>
#include <QSqlError>
@@ -46,6 +50,7 @@
#include <QSqlRecord>
#include <QThread>
#include <QVector>
#include <QWaitCondition>
#include "base/exceptions.h"
#include "base/global.h"
@@ -61,13 +66,53 @@ namespace
{
const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_qs;
const int DB_VERSION = 3;
const int DB_VERSION = 4;
const QString DB_TABLE_META = u"meta"_qs;
const QString DB_TABLE_TORRENTS = u"torrents"_qs;
const QString META_VERSION = u"version"_qs;
using namespace BitTorrent;
class Job
{
public:
virtual ~Job() = default;
virtual void perform(QSqlDatabase db) = 0;
};
class StoreJob final : public Job
{
public:
StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData);
void perform(QSqlDatabase db) override;
private:
const TorrentID m_torrentID;
const LoadTorrentParams m_resumeData;
};
class RemoveJob final : public Job
{
public:
explicit RemoveJob(const TorrentID &torrentID);
void perform(QSqlDatabase db) override;
private:
const TorrentID m_torrentID;
};
class StoreQueueJob final : public Job
{
public:
explicit StoreQueueJob(const QVector<TorrentID> &queue);
void perform(QSqlDatabase db) override;
private:
const QVector<TorrentID> m_queue;
};
struct Column
{
QString name;
@@ -167,97 +212,100 @@ namespace
{
return u"%1 %2"_qs.arg(quoted(column.name), QString::fromLatin1(definition));
}
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
{
LoadTorrentParams resumeData;
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
if (!tagsData.isEmpty())
{
const QStringList tagList = tagsData.split(u',');
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
}
resumeData.hasFinishedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.stopCondition = Utils::String::toEnum(
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
resumeData.savePath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
lt::error_code ec;
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec);
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray()
; !bencodedMetadata.isEmpty())
{
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec);
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
}
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
.toString().toStdString();
if (p.flags & lt::torrent_flags::stop_when_ready)
{
p.flags &= ~lt::torrent_flags::stop_when_ready;
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
}
return resumeData;
}
}
namespace BitTorrent
{
class DBResumeDataStorage::Worker final : public QObject
class DBResumeDataStorage::Worker final : public QThread
{
Q_DISABLE_COPY_MOVE(Worker)
public:
Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock);
Worker(const Path &dbPath, QReadWriteLock &dbLock);
void openDatabase() const;
void closeDatabase() const;
void run() override;
void requestInterruption();
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const;
void remove(const TorrentID &id) const;
void storeQueue(const QVector<TorrentID> &queue) const;
void store(const TorrentID &id, const LoadTorrentParams &resumeData);
void remove(const TorrentID &id);
void storeQueue(const QVector<TorrentID> &queue);
private:
void addJob(std::unique_ptr<Job> job);
const QString m_connectionName = u"ResumeDataStorageWorker"_qs;
const Path m_path;
const QString m_connectionName;
QReadWriteLock &m_dbLock;
std::queue<std::unique_ptr<Job>> m_jobs;
QMutex m_jobsMutex;
QWaitCondition m_waitCondition;
};
namespace
{
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
{
LoadTorrentParams resumeData;
resumeData.restored = true;
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
if (!tagsData.isEmpty())
{
const QStringList tagList = tagsData.split(u',');
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
}
resumeData.hasSeedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.stopCondition = Utils::String::toEnum(
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
resumeData.savePath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
lt::error_code ec;
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec);
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray(); !bencodedMetadata.isEmpty())
{
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec);
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
}
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
.toString().toStdString();
if (p.flags & lt::torrent_flags::stop_when_ready)
{
p.flags &= ~lt::torrent_flags::stop_when_ready;
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
}
return resumeData;
}
}
}
BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent)
: ResumeDataStorage(dbPath, parent)
, m_ioThread {new QThread(this)}
, m_ioThread {new QThread}
{
const bool needCreateDB = !dbPath.exists();
@@ -273,39 +321,19 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject
else
{
const int dbVersion = (!db.record(DB_TABLE_TORRENTS).contains(DB_COLUMN_DOWNLOAD_PATH.name) ? 1 : currentDBVersion());
if (dbVersion != DB_VERSION)
if (dbVersion < DB_VERSION)
updateDB(dbVersion);
}
m_asyncWorker = new Worker(dbPath, u"ResumeDataStorageWorker"_qs, m_dbLock);
m_asyncWorker->moveToThread(m_ioThread);
connect(m_ioThread, &QThread::finished, m_asyncWorker, &QObject::deleteLater);
m_ioThread->start();
RuntimeError *errPtr = nullptr;
QMetaObject::invokeMethod(m_asyncWorker, [this, &errPtr]()
{
try
{
m_asyncWorker->openDatabase();
}
catch (const RuntimeError &err)
{
errPtr = new RuntimeError(err);
}
}, Qt::BlockingQueuedConnection);
if (errPtr)
throw *errPtr;
m_asyncWorker = new Worker(dbPath, m_dbLock);
m_asyncWorker->start();
}
BitTorrent::DBResumeDataStorage::~DBResumeDataStorage()
{
QMetaObject::invokeMethod(m_asyncWorker, &Worker::closeDatabase);
m_asyncWorker->requestInterruption();
m_asyncWorker->wait();
QSqlDatabase::removeDatabase(DB_CONNECTION_NAME);
m_ioThread->quit();
m_ioThread->wait();
}
QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents() const
@@ -357,26 +385,17 @@ BitTorrent::LoadResumeDataResult BitTorrent::DBResumeDataStorage::load(const Tor
void BitTorrent::DBResumeDataStorage::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
{
QMetaObject::invokeMethod(m_asyncWorker, [this, id, resumeData]()
{
m_asyncWorker->store(id, resumeData);
});
m_asyncWorker->store(id, resumeData);
}
void BitTorrent::DBResumeDataStorage::remove(const BitTorrent::TorrentID &id) const
{
QMetaObject::invokeMethod(m_asyncWorker, [this, id]()
{
m_asyncWorker->remove(id);
});
m_asyncWorker->remove(id);
}
void BitTorrent::DBResumeDataStorage::storeQueue(const QVector<TorrentID> &queue) const
{
QMetaObject::invokeMethod(m_asyncWorker, [this, queue]()
{
m_asyncWorker->storeQueue(queue);
});
m_asyncWorker->storeQueue(queue);
}
void BitTorrent::DBResumeDataStorage::doLoadAll() const
@@ -453,9 +472,17 @@ int BitTorrent::DBResumeDataStorage::currentDBVersion() const
void BitTorrent::DBResumeDataStorage::createDB() const
{
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
try
{
enableWALMode();
}
catch (const RuntimeError &err)
{
LogMsg(tr("Couldn't enable Write-Ahead Logging (WAL) journaling mode. Error: %1.")
.arg(err.message()), Log::WARNING);
}
const QWriteLocker locker {&m_dbLock};
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
if (!db.transaction())
throw RuntimeError(db.lastError().text());
@@ -507,6 +534,12 @@ void BitTorrent::DBResumeDataStorage::createDB() const
if (!query.exec(createTableTorrentsQuery))
throw RuntimeError(query.lastError().text());
const QString torrentsQueuePositionIndexName = u"%1_%2_INDEX"_qs.arg(DB_TABLE_TORRENTS, DB_COLUMN_QUEUE_POSITION.name);
const QString createTorrentsQueuePositionIndexQuery = u"CREATE INDEX %1 ON %2 (%3)"_qs
.arg(quoted(torrentsQueuePositionIndexName), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
if (!query.exec(createTorrentsQueuePositionIndexQuery))
throw RuntimeError(query.lastError().text());
if (!db.commit())
throw RuntimeError(db.lastError().text());
}
@@ -535,17 +568,36 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
{
if (fromVersion == 1)
{
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"));
if (!query.exec(alterTableTorrentsQuery))
throw RuntimeError(query.lastError().text());
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_qs
.arg(quoted(DB_COLUMN_DOWNLOAD_PATH.name), quoted(DB_TABLE_TORRENTS));
if (!query.exec(testQuery))
{
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"));
if (!query.exec(alterTableTorrentsQuery))
throw RuntimeError(query.lastError().text());
}
}
if (fromVersion <= 2)
{
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"));
if (!query.exec(alterTableTorrentsQuery))
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_qs
.arg(quoted(DB_COLUMN_STOP_CONDITION.name), quoted(DB_TABLE_TORRENTS));
if (!query.exec(testQuery))
{
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"));
if (!query.exec(alterTableTorrentsQuery))
throw RuntimeError(query.lastError().text());
}
}
if (fromVersion <= 3)
{
const QString torrentsQueuePositionIndexName = u"%1_%2_INDEX"_qs.arg(DB_TABLE_TORRENTS, DB_COLUMN_QUEUE_POSITION.name);
const QString createTorrentsQueuePositionIndexQuery = u"CREATE INDEX IF NOT EXISTS %1 ON %2 (%3)"_qs
.arg(quoted(torrentsQueuePositionIndexName), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
if (!query.exec(createTorrentsQueuePositionIndexQuery))
throw RuntimeError(query.lastError().text());
}
@@ -569,216 +621,305 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
}
}
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock)
void BitTorrent::DBResumeDataStorage::enableWALMode() const
{
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
QSqlQuery query {db};
if (!query.exec(u"PRAGMA journal_mode = WAL;"_qs))
throw RuntimeError(query.lastError().text());
if (!query.next())
throw RuntimeError(tr("Couldn't obtain query result."));
const QString result = query.value(0).toString();
if (result.compare(u"WAL"_qs, Qt::CaseInsensitive) != 0)
throw RuntimeError(tr("WAL mode is probably unsupported due to filesystem limitations."));
}
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, QReadWriteLock &dbLock)
: m_path {dbPath}
, m_connectionName {dbConnectionName}
, m_dbLock {dbLock}
{
}
void BitTorrent::DBResumeDataStorage::Worker::openDatabase() const
void BitTorrent::DBResumeDataStorage::Worker::run()
{
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_qs, m_connectionName);
db.setDatabaseName(m_path.data());
if (!db.open())
throw RuntimeError(db.lastError().text());
}
{
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_qs, m_connectionName);
db.setDatabaseName(m_path.data());
if (!db.open())
throw RuntimeError(db.lastError().text());
int64_t transactedJobsCount = 0;
while (true)
{
m_jobsMutex.lock();
if (m_jobs.empty())
{
if (transactedJobsCount > 0)
{
db.commit();
m_dbLock.unlock();
qDebug() << "Resume data changes are commited. Transacted jobs:" << transactedJobsCount;
transactedJobsCount = 0;
}
if (isInterruptionRequested())
{
m_jobsMutex.unlock();
break;
}
m_waitCondition.wait(&m_jobsMutex);
if (isInterruptionRequested())
{
m_jobsMutex.unlock();
break;
}
m_dbLock.lockForWrite();
if (!db.transaction())
{
LogMsg(tr("Couldn't begin transaction. Error: %1").arg(db.lastError().text()), Log::WARNING);
m_dbLock.unlock();
break;
}
}
std::unique_ptr<Job> job = std::move(m_jobs.front());
m_jobs.pop();
m_jobsMutex.unlock();
job->perform(db);
++transactedJobsCount;
}
db.close();
}
void BitTorrent::DBResumeDataStorage::Worker::closeDatabase() const
{
QSqlDatabase::removeDatabase(m_connectionName);
}
void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
void DBResumeDataStorage::Worker::requestInterruption()
{
// We need to adjust native libtorrent resume data
lt::add_torrent_params p = resumeData.ltAddTorrentParams;
p.save_path = Profile::instance()->toPortablePath(Path(p.save_path))
.toString().toStdString();
if (resumeData.stopped)
QThread::requestInterruption();
m_waitCondition.wakeAll();
}
void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData)
{
addJob(std::make_unique<StoreJob>(id, resumeData));
}
void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id)
{
addJob(std::make_unique<RemoveJob>(id));
}
void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue)
{
addJob(std::make_unique<StoreQueueJob>(queue));
}
void BitTorrent::DBResumeDataStorage::Worker::addJob(std::unique_ptr<Job> job)
{
m_jobsMutex.lock();
m_jobs.push(std::move(job));
m_jobsMutex.unlock();
m_waitCondition.wakeAll();
}
namespace
{
using namespace BitTorrent;
StoreJob::StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData)
: m_torrentID {torrentID}
, m_resumeData {resumeData}
{
p.flags |= lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
}
else
void StoreJob::perform(QSqlDatabase db)
{
// Torrent can be actually "running" but temporarily "paused" to perform some
// service jobs behind the scenes so we need to restore it as "running"
if (resumeData.operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged)
// We need to adjust native libtorrent resume data
lt::add_torrent_params p = m_resumeData.ltAddTorrentParams;
p.save_path = Profile::instance()->toPortablePath(Path(p.save_path))
.toString().toStdString();
if (m_resumeData.stopped)
{
p.flags |= lt::torrent_flags::auto_managed;
p.flags |= lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
}
else
{
p.flags &= ~lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
// Torrent can be actually "running" but temporarily "paused" to perform some
// service jobs behind the scenes so we need to restore it as "running"
if (m_resumeData.operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged)
{
p.flags |= lt::torrent_flags::auto_managed;
}
else
{
p.flags &= ~lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
}
}
}
QVector<Column> columns {
DB_COLUMN_TORRENT_ID,
DB_COLUMN_NAME,
DB_COLUMN_CATEGORY,
DB_COLUMN_TAGS,
DB_COLUMN_TARGET_SAVE_PATH,
DB_COLUMN_CONTENT_LAYOUT,
DB_COLUMN_RATIO_LIMIT,
DB_COLUMN_SEEDING_TIME_LIMIT,
DB_COLUMN_HAS_OUTER_PIECES_PRIORITY,
DB_COLUMN_HAS_SEED_STATUS,
DB_COLUMN_OPERATING_MODE,
DB_COLUMN_STOPPED,
DB_COLUMN_STOP_CONDITION,
DB_COLUMN_RESUMEDATA
};
QVector<Column> columns {
DB_COLUMN_TORRENT_ID,
DB_COLUMN_NAME,
DB_COLUMN_CATEGORY,
DB_COLUMN_TAGS,
DB_COLUMN_TARGET_SAVE_PATH,
DB_COLUMN_DOWNLOAD_PATH,
DB_COLUMN_CONTENT_LAYOUT,
DB_COLUMN_RATIO_LIMIT,
DB_COLUMN_SEEDING_TIME_LIMIT,
DB_COLUMN_HAS_OUTER_PIECES_PRIORITY,
DB_COLUMN_HAS_SEED_STATUS,
DB_COLUMN_OPERATING_MODE,
DB_COLUMN_STOPPED,
DB_COLUMN_STOP_CONDITION,
DB_COLUMN_RESUMEDATA
};
lt::entry data = lt::write_resume_data(p);
lt::entry data = lt::write_resume_data(p);
// metadata is stored in separate column
QByteArray bencodedMetadata;
if (p.ti)
{
lt::entry::dictionary_type &dataDict = data.dict();
lt::entry metadata {lt::entry::dictionary_t};
lt::entry::dictionary_type &metadataDict = metadata.dict();
metadataDict.insert(dataDict.extract("info"));
metadataDict.insert(dataDict.extract("creation date"));
metadataDict.insert(dataDict.extract("created by"));
metadataDict.insert(dataDict.extract("comment"));
try
// metadata is stored in separate column
QByteArray bencodedMetadata;
if (p.ti)
{
bencodedMetadata.reserve(512 * 1024);
lt::bencode(std::back_inserter(bencodedMetadata), metadata);
}
catch (const std::exception &err)
{
LogMsg(tr("Couldn't save torrent metadata. Error: %1.")
.arg(QString::fromLocal8Bit(err.what())), Log::CRITICAL);
return;
lt::entry::dictionary_type &dataDict = data.dict();
lt::entry metadata {lt::entry::dictionary_t};
lt::entry::dictionary_type &metadataDict = metadata.dict();
metadataDict.insert(dataDict.extract("info"));
metadataDict.insert(dataDict.extract("creation date"));
metadataDict.insert(dataDict.extract("created by"));
metadataDict.insert(dataDict.extract("comment"));
try
{
bencodedMetadata.reserve(512 * 1024);
lt::bencode(std::back_inserter(bencodedMetadata), metadata);
}
catch (const std::exception &err)
{
LogMsg(ResumeDataStorage::tr("Couldn't save torrent metadata. Error: %1.")
.arg(QString::fromLocal8Bit(err.what())), Log::CRITICAL);
return;
}
columns.append(DB_COLUMN_METADATA);
}
columns.append(DB_COLUMN_METADATA);
}
QByteArray bencodedResumeData;
bencodedResumeData.reserve(256 * 1024);
lt::bencode(std::back_inserter(bencodedResumeData), data);
const QString insertTorrentStatement = makeInsertStatement(DB_TABLE_TORRENTS, columns)
+ makeOnConflictUpdateStatement(DB_COLUMN_TORRENT_ID, columns);
auto db = QSqlDatabase::database(m_connectionName);
QSqlQuery query {db};
try
{
if (!query.prepare(insertTorrentStatement))
throw RuntimeError(query.lastError().text());
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, id.toString());
query.bindValue(DB_COLUMN_NAME.placeholder, resumeData.name);
query.bindValue(DB_COLUMN_CATEGORY.placeholder, resumeData.category);
query.bindValue(DB_COLUMN_TAGS.placeholder, (resumeData.tags.isEmpty()
? QVariant(QVariant::String) : resumeData.tags.join(u","_qs)));
query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(resumeData.contentLayout));
query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast<int>(resumeData.ratioLimit * 1000));
query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, resumeData.seedingTimeLimit);
query.bindValue(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.placeholder, resumeData.firstLastPiecePriority);
query.bindValue(DB_COLUMN_HAS_SEED_STATUS.placeholder, resumeData.hasSeedStatus);
query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(resumeData.operatingMode));
query.bindValue(DB_COLUMN_STOPPED.placeholder, resumeData.stopped);
query.bindValue(DB_COLUMN_STOP_CONDITION.placeholder, Utils::String::fromEnum(resumeData.stopCondition));
if (!resumeData.useAutoTMM)
{
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath).data());
query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.downloadPath).data());
}
query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData);
if (!bencodedMetadata.isEmpty())
query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata);
const QWriteLocker locker {&m_dbLock};
if (!query.exec())
throw RuntimeError(query.lastError().text());
}
catch (const RuntimeError &err)
{
LogMsg(tr("Couldn't store resume data for torrent '%1'. Error: %2")
.arg(id.toString(), err.message()), Log::CRITICAL);
}
}
void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id) const
{
const auto deleteTorrentStatement = u"DELETE FROM %1 WHERE %2 = %3;"_qs
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
auto db = QSqlDatabase::database(m_connectionName);
QSqlQuery query {db};
try
{
if (!query.prepare(deleteTorrentStatement))
throw RuntimeError(query.lastError().text());
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, id.toString());
const QWriteLocker locker {&m_dbLock};
if (!query.exec())
throw RuntimeError(query.lastError().text());
}
catch (const RuntimeError &err)
{
LogMsg(tr("Couldn't delete resume data of torrent '%1'. Error: %2")
.arg(id.toString(), err.message()), Log::CRITICAL);
}
}
void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue) const
{
const auto updateQueuePosStatement = u"UPDATE %1 SET %2 = %3 WHERE %4 = %5;"_qs
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name), DB_COLUMN_QUEUE_POSITION.placeholder
, quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
auto db = QSqlDatabase::database(m_connectionName);
try
{
const QWriteLocker locker {&m_dbLock};
if (!db.transaction())
throw RuntimeError(db.lastError().text());
QByteArray bencodedResumeData;
bencodedResumeData.reserve(256 * 1024);
lt::bencode(std::back_inserter(bencodedResumeData), data);
const QString insertTorrentStatement = makeInsertStatement(DB_TABLE_TORRENTS, columns)
+ makeOnConflictUpdateStatement(DB_COLUMN_TORRENT_ID, columns);
QSqlQuery query {db};
try
{
if (!query.prepare(insertTorrentStatement))
throw RuntimeError(query.lastError().text());
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, m_torrentID.toString());
query.bindValue(DB_COLUMN_NAME.placeholder, m_resumeData.name);
query.bindValue(DB_COLUMN_CATEGORY.placeholder, m_resumeData.category);
query.bindValue(DB_COLUMN_TAGS.placeholder, (m_resumeData.tags.isEmpty()
? QVariant(QVariant::String) : m_resumeData.tags.join(u","_qs)));
query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(m_resumeData.contentLayout));
query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast<int>(m_resumeData.ratioLimit * 1000));
query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, m_resumeData.seedingTimeLimit);
query.bindValue(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.placeholder, m_resumeData.firstLastPiecePriority);
query.bindValue(DB_COLUMN_HAS_SEED_STATUS.placeholder, m_resumeData.hasFinishedStatus);
query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(m_resumeData.operatingMode));
query.bindValue(DB_COLUMN_STOPPED.placeholder, m_resumeData.stopped);
query.bindValue(DB_COLUMN_STOP_CONDITION.placeholder, Utils::String::fromEnum(m_resumeData.stopCondition));
if (!m_resumeData.useAutoTMM)
{
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(m_resumeData.savePath).data());
query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(m_resumeData.downloadPath).data());
}
query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData);
if (!bencodedMetadata.isEmpty())
query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata);
if (!query.exec())
throw RuntimeError(query.lastError().text());
}
catch (const RuntimeError &err)
{
LogMsg(ResumeDataStorage::tr("Couldn't store resume data for torrent '%1'. Error: %2")
.arg(m_torrentID.toString(), err.message()), Log::CRITICAL);
}
}
RemoveJob::RemoveJob(const TorrentID &torrentID)
: m_torrentID {torrentID}
{
}
void RemoveJob::perform(QSqlDatabase db)
{
const auto deleteTorrentStatement = u"DELETE FROM %1 WHERE %2 = %3;"_qs
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
QSqlQuery query {db};
try
{
if (!query.prepare(deleteTorrentStatement))
throw RuntimeError(query.lastError().text());
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, m_torrentID.toString());
if (!query.exec())
throw RuntimeError(query.lastError().text());
}
catch (const RuntimeError &err)
{
LogMsg(ResumeDataStorage::tr("Couldn't delete resume data of torrent '%1'. Error: %2")
.arg(m_torrentID.toString(), err.message()), Log::CRITICAL);
}
}
StoreQueueJob::StoreQueueJob(const QVector<TorrentID> &queue)
: m_queue {queue}
{
}
void StoreQueueJob::perform(QSqlDatabase db)
{
const auto updateQueuePosStatement = u"UPDATE %1 SET %2 = %3 WHERE %4 = %5;"_qs
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name), DB_COLUMN_QUEUE_POSITION.placeholder
, quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
try
{
QSqlQuery query {db};
if (!query.prepare(updateQueuePosStatement))
throw RuntimeError(query.lastError().text());
int pos = 0;
for (const TorrentID &torrentID : queue)
for (const TorrentID &torrentID : m_queue)
{
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, torrentID.toString());
query.bindValue(DB_COLUMN_QUEUE_POSITION.placeholder, pos++);
if (!query.exec())
throw RuntimeError(query.lastError().text());
}
if (!db.commit())
throw RuntimeError(db.lastError().text());
}
catch (const RuntimeError &)
catch (const RuntimeError &err)
{
db.rollback();
throw;
LogMsg(ResumeDataStorage::tr("Couldn't store torrents queue positions. Error: %1")
.arg(err.message()), Log::CRITICAL);
}
}
catch (const RuntimeError &err)
{
LogMsg(tr("Couldn't store torrents queue positions. Error: %1")
.arg(err.message()), Log::CRITICAL);
}
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2021-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -31,6 +31,7 @@
#include <QReadWriteLock>
#include "base/pathfwd.h"
#include "base/utils/thread.h"
#include "resumedatastorage.h"
class QThread;
@@ -58,8 +59,9 @@ namespace BitTorrent
int currentDBVersion() const;
void createDB() const;
void updateDB(int fromVersion) const;
void enableWALMode() const;
QThread *m_ioThread = nullptr;
Utils::Thread::UniquePtr m_ioThread;
class Worker;
Worker *m_asyncWorker = nullptr;

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -28,6 +28,8 @@
#pragma once
#include <set>
#include <string>
#include <vector>
#include <libtorrent/announce_entry.hpp>
@@ -44,4 +46,5 @@ struct ExtensionData
{
lt::torrent_status status;
std::vector<lt::announce_entry> trackers;
std::set<std::string> urlSeeds;
};

View File

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

View File

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

View File

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

View File

@@ -52,13 +52,13 @@ namespace BitTorrent
TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged;
bool useAutoTMM = false;
bool firstLastPiecePriority = false;
bool hasSeedStatus = false;
bool hasFinishedStatus = false;
bool stopped = false;
Torrent::StopCondition stopCondition;
Torrent::StopCondition stopCondition = Torrent::StopCondition::None;
bool addToQueueTop = false; // only for new torrents
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
bool restored = false; // is existing torrent job?
};
}

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2020-2023 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -35,26 +35,6 @@
namespace
{
void handleAddTorrentAlert([[maybe_unused]] const lt::add_torrent_alert *alert)
{
#ifndef QBT_USES_LIBTORRENT2
if (alert->error)
return;
// libtorrent < 2.0.7 has a bug that add_torrent_alert is posted too early
// (before torrent is fully initialized and torrent extensions are created)
// so we have to fill "extension data" in add_torrent_alert handler
// NOTE: `data` may not exist if a torrent is added behind the scenes to download metadata
auto *data = static_cast<ExtensionData *>(alert->params.userdata);
if (data)
{
data->status = alert->handle.status({});
data->trackers = alert->handle.trackers();
}
#endif
}
void handleFastresumeRejectedAlert(const lt::fastresume_rejected_alert *alert)
{
alert->handle.unset_flags(lt::torrent_flags::auto_managed);
@@ -62,6 +42,17 @@ namespace
}
}
bool NativeSessionExtension::isSessionListening() const
{
const QReadLocker locker {&m_lock};
return m_isSesssionListening;
}
void NativeSessionExtension::added(const lt::session_handle &nativeSession)
{
m_nativeSession = nativeSession;
}
lt::feature_flags_t NativeSessionExtension::implemented_features()
{
return alert_feature;
@@ -76,8 +67,8 @@ void NativeSessionExtension::on_alert(const lt::alert *alert)
{
switch (alert->type())
{
case lt::add_torrent_alert::alert_type:
handleAddTorrentAlert(static_cast<const lt::add_torrent_alert *>(alert));
case lt::session_stats_alert::alert_type:
handleSessionStatsAlert(static_cast<const lt::session_stats_alert *>(alert));
break;
case lt::fastresume_rejected_alert::alert_type:
handleFastresumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert *>(alert));
@@ -86,3 +77,9 @@ void NativeSessionExtension::on_alert(const lt::alert *alert)
break;
}
}
void NativeSessionExtension::handleSessionStatsAlert([[maybe_unused]] const lt::session_stats_alert *alert)
{
const QWriteLocker locker {&m_lock};
m_isSesssionListening = m_nativeSession.is_listening();
}

View File

@@ -29,12 +29,28 @@
#pragma once
#include <libtorrent/extensions.hpp>
#include <libtorrent/fwd.hpp>
#include <libtorrent/session_handle.hpp>
#include <QReadWriteLock>
#include "extensiondata.h"
class NativeSessionExtension final : public lt::plugin
{
public:
bool isSessionListening() const;
private:
void added(const lt::session_handle &nativeSession) override;
lt::feature_flags_t implemented_features() override;
std::shared_ptr<lt::torrent_plugin> new_torrent(const lt::torrent_handle &torrentHandle, LTClientData clientData) override;
void on_alert(const lt::alert *alert) override;
void handleSessionStatsAlert(const lt::session_stats_alert *alert);
lt::session_handle m_nativeSession;
mutable QReadWriteLock m_lock;
bool m_isSesssionListening = false;
};

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2020-2023 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -36,18 +36,12 @@ NativeTorrentExtension::NativeTorrentExtension(const lt::torrent_handle &torrent
{
// NOTE: `data` may not exist if a torrent is added behind the scenes to download metadata
#ifdef QBT_USES_LIBTORRENT2
// libtorrent < 2.0.7 has a bug that add_torrent_alert is posted too early
// (before torrent is fully initialized and torrent extensions are created)
// so we have to fill "extension data" in add_torrent_alert handler and
// we have it already filled at this point
if (m_data)
{
m_data->status = m_torrentHandle.status({});
m_data->status = m_torrentHandle.status();
m_data->trackers = m_torrentHandle.trackers();
m_data->urlSeeds = m_torrentHandle.url_seeds();
}
#endif
on_state(m_data ? m_data->status.state : m_torrentHandle.status({}).state);
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -31,16 +31,15 @@
#include <QBitArray>
#include "base/bittorrent/ltqbitarray.h"
#include "base/bittorrent/torrent.h"
#include "base/net/geoipmanager.h"
#include "base/unicodestrings.h"
#include "peeraddress.h"
using namespace BitTorrent;
PeerInfo::PeerInfo(const Torrent *torrent, const lt::peer_info &nativeInfo)
PeerInfo::PeerInfo(const lt::peer_info &nativeInfo, const QBitArray &allPieces)
: m_nativeInfo(nativeInfo)
, m_relevance(calcRelevance(torrent))
, m_relevance(calcRelevance(allPieces))
{
determineFlags();
}
@@ -181,6 +180,31 @@ QString PeerInfo::client() const
return QString::fromStdString(m_nativeInfo.client);
}
QString PeerInfo::peerIdClient() const
{
// when peer ID is not known yet it contains only zero bytes,
// do not create string in such case, return empty string instead
if (m_nativeInfo.pid.is_all_zeros())
return {};
QString result;
// interesting part of a typical peer ID is first 8 chars
for (int i = 0; i < 8; ++i)
{
const std::uint8_t c = m_nativeInfo.pid[i];
// ensure that the peer ID slice consists only of printable ASCII characters,
// this should filter out most of the improper IDs
if ((c < 32) || (c > 126))
return tr("Unknown");
result += QChar::fromLatin1(c);
}
return result;
}
qreal PeerInfo::progress() const
{
return m_nativeInfo.progress;
@@ -221,9 +245,8 @@ QString PeerInfo::connectionType() const
: u"Web"_qs;
}
qreal PeerInfo::calcRelevance(const Torrent *torrent) const
qreal PeerInfo::calcRelevance(const QBitArray &allPieces) const
{
const QBitArray allPieces = torrent->pieces();
const int localMissing = allPieces.count(false);
if (localMissing <= 0)
return 0;

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -36,7 +36,6 @@ class QBitArray;
namespace BitTorrent
{
class Torrent;
struct PeerAddress;
class PeerInfo
@@ -45,7 +44,7 @@ namespace BitTorrent
public:
PeerInfo() = default;
PeerInfo(const Torrent *torrent, const lt::peer_info &nativeInfo);
PeerInfo(const lt::peer_info &nativeInfo, const QBitArray &allPieces);
bool fromDHT() const;
bool fromPeX() const;
@@ -78,6 +77,7 @@ namespace BitTorrent
PeerAddress address() const;
QString client() const;
QString peerIdClient() const;
qreal progress() const;
int payloadUpSpeed() const;
int payloadDownSpeed() const;
@@ -92,7 +92,7 @@ namespace BitTorrent
int downloadingPieceIndex() const;
private:
qreal calcRelevance(const Torrent *torrent) const;
qreal calcRelevance(const QBitArray &allPieces) const;
void determineFlags();
lt::peer_info m_nativeInfo = {};

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2019 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2019-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -28,12 +28,12 @@
#include "portforwarderimpl.h"
#include <libtorrent/session.hpp>
#include <utility>
#include "base/logger.h"
#include "base/bittorrent/sessionimpl.h"
PortForwarderImpl::PortForwarderImpl(lt::session *provider, QObject *parent)
: Net::PortForwarder {parent}
PortForwarderImpl::PortForwarderImpl(BitTorrent::SessionImpl *provider, QObject *parent)
: Net::PortForwarder(parent)
, m_storeActive {u"Network/PortForwardingEnabled"_qs, true}
, m_provider {provider}
{
@@ -63,60 +63,30 @@ void PortForwarderImpl::setEnabled(const bool enabled)
m_storeActive = enabled;
}
void PortForwarderImpl::addPort(const quint16 port)
void PortForwarderImpl::setPorts(const QString &profile, QSet<quint16> ports)
{
if (m_mappedPorts.contains(port))
return;
const QSet<quint16> oldForwardedPorts = std::accumulate(m_portProfiles.cbegin(), m_portProfiles.cend(), QSet<quint16>());
if (isEnabled())
m_mappedPorts.insert(port, m_provider->add_port_mapping(lt::session::tcp, port, port));
else
m_mappedPorts.insert(port, {});
m_portProfiles[profile] = ports;
const QSet<quint16> newForwardedPorts = std::accumulate(m_portProfiles.cbegin(), m_portProfiles.cend(), QSet<quint16>());
m_provider->removeMappedPorts(oldForwardedPorts - newForwardedPorts);
m_provider->addMappedPorts(newForwardedPorts - oldForwardedPorts);
}
void PortForwarderImpl::deletePort(const quint16 port)
void PortForwarderImpl::removePorts(const QString &profile)
{
const auto iter = m_mappedPorts.find(port);
if (iter == m_mappedPorts.end())
return;
if (isEnabled())
{
for (const lt::port_mapping_t &portMapping : *iter)
m_provider->delete_port_mapping(portMapping);
}
m_mappedPorts.erase(iter);
setPorts(profile, {});
}
void PortForwarderImpl::start()
{
lt::settings_pack settingsPack = m_provider->get_settings();
settingsPack.set_bool(lt::settings_pack::enable_upnp, true);
settingsPack.set_bool(lt::settings_pack::enable_natpmp, true);
m_provider->apply_settings(settingsPack);
for (auto iter = m_mappedPorts.begin(); iter != m_mappedPorts.end(); ++iter)
{
Q_ASSERT(iter.value().empty());
const quint16 port = iter.key();
iter.value() = m_provider->add_port_mapping(lt::session::tcp, port, port);
}
LogMsg(tr("UPnP/NAT-PMP support: ON"), Log::INFO);
m_provider->enablePortMapping();
for (const QSet<quint16> &ports : asConst(m_portProfiles))
m_provider->addMappedPorts(ports);
}
void PortForwarderImpl::stop()
{
lt::settings_pack settingsPack = m_provider->get_settings();
settingsPack.set_bool(lt::settings_pack::enable_upnp, false);
settingsPack.set_bool(lt::settings_pack::enable_natpmp, false);
m_provider->apply_settings(settingsPack);
// don't clear m_mappedPorts so a later `start()` call can restore the port forwarding
for (auto iter = m_mappedPorts.begin(); iter != m_mappedPorts.end(); ++iter)
iter.value().clear();
LogMsg(tr("UPnP/NAT-PMP support: OFF"), Log::INFO);
m_provider->disablePortMapping();
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2019 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2019-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -28,36 +28,38 @@
#pragma once
#include <vector>
#include <libtorrent/fwd.hpp>
#include <libtorrent/portmap.hpp>
#include <QHash>
#include <QSet>
#include "base/net/portforwarder.h"
#include "base/settingvalue.h"
namespace BitTorrent
{
class SessionImpl;
}
class PortForwarderImpl final : public Net::PortForwarder
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(PortForwarderImpl)
public:
explicit PortForwarderImpl(lt::session *provider, QObject *parent = nullptr);
explicit PortForwarderImpl(BitTorrent::SessionImpl *provider, QObject *parent = nullptr);
~PortForwarderImpl() override;
bool isEnabled() const override;
void setEnabled(bool enabled) override;
void addPort(quint16 port) override;
void deletePort(quint16 port) override;
void setPorts(const QString &profile, QSet<quint16> ports) override;
void removePorts(const QString &profile) override;
private:
void start();
void stop();
CachedSettingValue<bool> m_storeActive;
lt::session *const m_provider = nullptr;
QHash<quint16, std::vector<lt::port_mapping_t>> m_mappedPorts;
BitTorrent::SessionImpl *const m_provider = nullptr;
QHash<QString, QSet<quint16>> m_portProfiles;
};

View File

@@ -33,6 +33,7 @@
#include <QMetaObject>
#include <QMutexLocker>
#include <QThread>
#include <QVector>
const int TORRENTIDLIST_TYPEID = qRegisterMetaType<QVector<BitTorrent::TorrentID>>();
@@ -59,11 +60,11 @@ void BitTorrent::ResumeDataStorage::loadAll() const
loadingThread->start();
}
QVector<BitTorrent::LoadedResumeData> BitTorrent::ResumeDataStorage::fetchLoadedResumeData() const
QList<BitTorrent::LoadedResumeData> BitTorrent::ResumeDataStorage::fetchLoadedResumeData() const
{
const QMutexLocker locker {&m_loadedResumeDataMutex};
const QVector<BitTorrent::LoadedResumeData> loadedResumeData = m_loadedResumeData;
const QList<BitTorrent::LoadedResumeData> loadedResumeData = m_loadedResumeData;
m_loadedResumeData.clear();
return loadedResumeData;

View File

@@ -29,9 +29,9 @@
#pragma once
#include <QtContainerFwd>
#include <QList>
#include <QMutex>
#include <QObject>
#include <QVector>
#include "base/3rdparty/expected.hpp"
#include "base/path.h"
@@ -65,7 +65,7 @@ namespace BitTorrent
virtual void storeQueue(const QVector<TorrentID> &queue) const = 0;
void loadAll() const;
QVector<LoadedResumeData> fetchLoadedResumeData() const;
QList<LoadedResumeData> fetchLoadedResumeData() const;
signals:
void loadStarted(const QVector<BitTorrent::TorrentID> &torrents);
@@ -78,7 +78,7 @@ namespace BitTorrent
virtual void doLoadAll() const = 0;
const Path m_path;
mutable QVector<LoadedResumeData> m_loadedResumeData;
mutable QList<LoadedResumeData> m_loadedResumeData;
mutable QMutex m_loadedResumeDataMutex;
};
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -170,6 +170,9 @@ namespace BitTorrent
virtual bool useCategoryPathsInManualMode() const = 0;
virtual void setUseCategoryPathsInManualMode(bool value) = 0;
virtual Path suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const = 0;
virtual Path suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const = 0;
static bool isValidTag(const QString &tag);
virtual QSet<QString> tags() const = 0;
virtual bool hasTag(const QString &tag) const = 0;
@@ -206,6 +209,8 @@ namespace BitTorrent
virtual void setLSDEnabled(bool enabled) = 0;
virtual bool isPeXEnabled() const = 0;
virtual void setPeXEnabled(bool enabled) = 0;
virtual bool isAddTorrentToQueueTop() const = 0;
virtual void setAddTorrentToQueueTop(bool value) = 0;
virtual bool isAddTorrentPaused() const = 0;
virtual void setAddTorrentPaused(bool value) = 0;
virtual Torrent::StopCondition torrentStopCondition() const = 0;
@@ -258,10 +263,16 @@ namespace BitTorrent
virtual void setEncryption(int state) = 0;
virtual int maxActiveCheckingTorrents() const = 0;
virtual void setMaxActiveCheckingTorrents(int val) = 0;
virtual bool isI2PEnabled() const = 0;
virtual void setI2PEnabled(bool enabled) = 0;
virtual QString I2PAddress() const = 0;
virtual void setI2PAddress(const QString &address) = 0;
virtual int I2PPort() const = 0;
virtual void setI2PPort(int port) = 0;
virtual bool I2PMixedMode() const = 0;
virtual void setI2PMixedMode(bool enabled) = 0;
virtual bool isProxyPeerConnectionsEnabled() const = 0;
virtual void setProxyPeerConnectionsEnabled(bool enabled) = 0;
virtual bool isProxyHostnameLookupEnabled() const = 0;
virtual void setProxyHostnameLookupEnabled(bool enabled) = 0;
virtual ChokingAlgorithm chokingAlgorithm() const = 0;
virtual void setChokingAlgorithm(ChokingAlgorithm mode) = 0;
virtual SeedChokingAlgorithm seedChokingAlgorithm() const = 0;
@@ -320,6 +331,10 @@ namespace BitTorrent
virtual void setSendBufferWatermarkFactor(int value) = 0;
virtual int connectionSpeed() const = 0;
virtual void setConnectionSpeed(int value) = 0;
virtual int socketSendBufferSize() const = 0;
virtual void setSocketSendBufferSize(int value) = 0;
virtual int socketReceiveBufferSize() const = 0;
virtual void setSocketReceiveBufferSize(int value) = 0;
virtual int socketBacklogSize() const = 0;
virtual void setSocketBacklogSize(int value) = 0;
virtual bool isAnonymousModeEnabled() const = 0;
@@ -388,7 +403,7 @@ namespace BitTorrent
virtual bool isTrackerFilteringEnabled() const = 0;
virtual void setTrackerFilteringEnabled(bool enabled) = 0;
virtual bool isExcludedFileNamesEnabled() const = 0;
virtual void setExcludedFileNamesEnabled(const bool enabled) = 0;
virtual void setExcludedFileNamesEnabled(bool enabled) = 0;
virtual QStringList excludedFileNames() const = 0;
virtual void setExcludedFileNames(const QStringList &newList) = 0;
virtual bool isFilenameExcluded(const QString &fileName) const = 0;
@@ -403,9 +418,6 @@ namespace BitTorrent
virtual Torrent *findTorrent(const InfoHash &infoHash) const = 0;
virtual QVector<Torrent *> torrents() const = 0;
virtual qsizetype torrentsCount() const = 0;
virtual bool hasActiveTorrents() const = 0;
virtual bool hasUnfinishedTorrents() const = 0;
virtual bool hasRunningSeed() const = 0;
virtual const SessionStatus &status() const = 0;
virtual const CacheStatus &cacheStatus() const = 0;
virtual bool isListening() const = 0;
@@ -434,6 +446,7 @@ namespace BitTorrent
void allTorrentsFinished();
void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName);
void categoryOptionsChanged(const QString &categoryName);
void downloadFromUrlFailed(const QString &url, const QString &reason);
void downloadFromUrlFinished(const QString &url);
void fullDiskError(Torrent *torrent, const QString &msg);
@@ -468,6 +481,6 @@ namespace BitTorrent
void trackersRemoved(Torrent *torrent, const QStringList &trackers);
void trackerSuccess(Torrent *torrent, const QString &tracker);
void trackerWarning(Torrent *torrent, const QString &tracker);
void trackerEntriesUpdated(const QHash<Torrent *, QSet<QString>> &updateInfos);
void trackerEntriesUpdated(Torrent *torrent, const QHash<QString, TrackerEntry> &updatedTrackerEntries);
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -29,22 +29,26 @@
#pragma once
#include <utility>
#include <variant>
#include <vector>
#include <libtorrent/fwd.hpp>
#include <libtorrent/portmap.hpp>
#include <libtorrent/torrent_handle.hpp>
#include <QtContainerFwd>
#include <QDateTime>
#include <QElapsedTimer>
#include <QHash>
#include <QPointer>
#include <QSet>
#include <QtContainerFwd>
#include <QVector>
#include "base/path.h"
#include "base/settingvalue.h"
#include "base/types.h"
#include "base/utils/thread.h"
#include "addtorrentparams.h"
#include "cachestatus.h"
#include "categoryoptions.h"
@@ -59,12 +63,14 @@ class QNetworkConfigurationManager;
#endif
class QString;
class QThread;
class QThreadPool;
class QTimer;
class QUrl;
class BandwidthScheduler;
class FileSearcher;
class FilterParserThread;
class NativeSessionExtension;
namespace Net
{
@@ -82,6 +88,7 @@ namespace BitTorrent
struct LoadTorrentParams;
enum class MoveStorageMode;
enum class MoveStorageContext;
struct SessionMetricIndices
{
@@ -154,6 +161,9 @@ namespace BitTorrent
bool useCategoryPathsInManualMode() const override;
void setUseCategoryPathsInManualMode(bool value) override;
Path suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const override;
Path suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const override;
QSet<QString> tags() const override;
bool hasTag(const QString &tag) const override;
bool addTag(const QString &tag) override;
@@ -178,6 +188,8 @@ namespace BitTorrent
void setLSDEnabled(bool enabled) override;
bool isPeXEnabled() const override;
void setPeXEnabled(bool enabled) override;
bool isAddTorrentToQueueTop() const override;
void setAddTorrentToQueueTop(bool value) override;
bool isAddTorrentPaused() const override;
void setAddTorrentPaused(bool value) override;
Torrent::StopCondition torrentStopCondition() const override;
@@ -230,10 +242,16 @@ namespace BitTorrent
void setEncryption(int state) override;
int maxActiveCheckingTorrents() const override;
void setMaxActiveCheckingTorrents(int val) override;
bool isI2PEnabled() const override;
void setI2PEnabled(bool enabled) override;
QString I2PAddress() const override;
void setI2PAddress(const QString &address) override;
int I2PPort() const override;
void setI2PPort(int port) override;
bool I2PMixedMode() const override;
void setI2PMixedMode(bool enabled) override;
bool isProxyPeerConnectionsEnabled() const override;
void setProxyPeerConnectionsEnabled(bool enabled) override;
bool isProxyHostnameLookupEnabled() const override;
void setProxyHostnameLookupEnabled(bool enabled) override;
ChokingAlgorithm chokingAlgorithm() const override;
void setChokingAlgorithm(ChokingAlgorithm mode) override;
SeedChokingAlgorithm seedChokingAlgorithm() const override;
@@ -292,6 +310,10 @@ namespace BitTorrent
void setSendBufferWatermarkFactor(int value) override;
int connectionSpeed() const override;
void setConnectionSpeed(int value) override;
int socketSendBufferSize() const override;
void setSocketSendBufferSize(int value) override;
int socketReceiveBufferSize() const override;
void setSocketReceiveBufferSize(int value) override;
int socketBacklogSize() const override;
void setSocketBacklogSize(int value) override;
bool isAnonymousModeEnabled() const override;
@@ -360,9 +382,9 @@ namespace BitTorrent
bool isTrackerFilteringEnabled() const override;
void setTrackerFilteringEnabled(bool enabled) override;
bool isExcludedFileNamesEnabled() const override;
void setExcludedFileNamesEnabled(const bool enabled) override;
void setExcludedFileNamesEnabled(bool enabled) override;
QStringList excludedFileNames() const override;
void setExcludedFileNames(const QStringList &newList) override;
void setExcludedFileNames(const QStringList &excludedFileNames) override;
bool isFilenameExcluded(const QString &fileName) const override;
QStringList bannedIPs() const override;
void setBannedIPs(const QStringList &newList) override;
@@ -375,9 +397,6 @@ namespace BitTorrent
Torrent *findTorrent(const InfoHash &infoHash) const override;
QVector<Torrent *> torrents() const override;
qsizetype torrentsCount() const override;
bool hasActiveTorrents() const override;
bool hasUnfinishedTorrents() const override;
bool hasRunningSeed() const override;
const SessionStatus &status() const override;
const CacheStatus &cacheStatus() const override;
bool isListening() const override;
@@ -405,31 +424,44 @@ namespace BitTorrent
void handleTorrentNeedSaveResumeData(const TorrentImpl *torrent);
void handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent);
void handleTorrentSaveResumeDataFailed(const TorrentImpl *torrent);
void handleTorrentShareLimitChanged(TorrentImpl *const torrent);
void handleTorrentNameChanged(TorrentImpl *const torrent);
void handleTorrentSavePathChanged(TorrentImpl *const torrent);
void handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory);
void handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag);
void handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag);
void handleTorrentSavingModeChanged(TorrentImpl *const torrent);
void handleTorrentMetadataReceived(TorrentImpl *const torrent);
void handleTorrentPaused(TorrentImpl *const torrent);
void handleTorrentResumed(TorrentImpl *const torrent);
void handleTorrentChecked(TorrentImpl *const torrent);
void handleTorrentFinished(TorrentImpl *const torrent);
void handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers);
void handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QStringList &deletedTrackers);
void handleTorrentTrackersChanged(TorrentImpl *const torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds);
void handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data);
void handleTorrentShareLimitChanged(TorrentImpl *torrent);
void handleTorrentNameChanged(TorrentImpl *torrent);
void handleTorrentSavePathChanged(TorrentImpl *torrent);
void handleTorrentCategoryChanged(TorrentImpl *torrent, const QString &oldCategory);
void handleTorrentTagAdded(TorrentImpl *torrent, const QString &tag);
void handleTorrentTagRemoved(TorrentImpl *torrent, const QString &tag);
void handleTorrentSavingModeChanged(TorrentImpl *torrent);
void handleTorrentMetadataReceived(TorrentImpl *torrent);
void handleTorrentPaused(TorrentImpl *torrent);
void handleTorrentResumed(TorrentImpl *torrent);
void handleTorrentChecked(TorrentImpl *torrent);
void handleTorrentFinished(TorrentImpl *torrent);
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QVector<TrackerEntry> &newTrackers);
void handleTorrentTrackersRemoved(TorrentImpl *torrent, const QStringList &deletedTrackers);
void handleTorrentTrackersChanged(TorrentImpl *torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QVector<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QVector<QUrl> &urlSeeds);
void handleTorrentResumeDataReady(TorrentImpl *torrent, const LoadTorrentParams &data);
void handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash);
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode);
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode, MoveStorageContext context);
void findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
, const Path &downloadPath, const PathList &filePaths = {}) const;
void enablePortMapping();
void disablePortMapping();
void addMappedPorts(const QSet<quint16> &ports);
void removeMappedPorts(const QSet<quint16> &ports);
template <typename Func>
void invoke(Func &&func)
{
QMetaObject::invokeMethod(this, std::forward<Func>(func), Qt::QueuedConnection);
}
void invokeAsync(std::function<void ()> func);
private slots:
void configureDeferred();
void readAlerts();
@@ -454,14 +486,15 @@ namespace BitTorrent
{
lt::torrent_handle torrentHandle;
Path path;
MoveStorageMode mode;
MoveStorageMode mode {};
MoveStorageContext context {};
};
struct RemovingTorrentData
{
QString name;
Path pathToRemove;
DeleteOption deleteOption;
DeleteOption deleteOption {};
};
explicit SessionImpl(QObject *parent = nullptr);
@@ -474,13 +507,10 @@ namespace BitTorrent
Q_INVOKABLE void configure();
void configureComponents();
void initializeNativeSession();
void loadLTSettings(lt::settings_pack &settingsPack);
void configureNetworkInterfaces(lt::settings_pack &settingsPack);
lt::settings_pack loadLTSettings() const;
void applyNetworkInterfacesSettings(lt::settings_pack &settingsPack) const;
void configurePeerClasses();
void adjustLimits(lt::settings_pack &settingsPack) const;
void applyBandwidthLimits(lt::settings_pack &settingsPack) const;
void initMetrics();
void adjustLimits();
void applyBandwidthLimits();
void processBannedIPs(lt::ip_filter &filter);
QStringList getListeningIPs() const;
@@ -533,8 +563,8 @@ namespace BitTorrent
TorrentImpl *createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params);
void saveResumeData();
void saveTorrentsQueue() const;
void removeTorrentsQueue() const;
void saveTorrentsQueue();
void removeTorrentsQueue();
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
@@ -550,10 +580,11 @@ namespace BitTorrent
// BitTorrent
lt::session *m_nativeSession = nullptr;
NativeSessionExtension *m_nativeSessionExtension = nullptr;
bool m_deferredConfigureScheduled = false;
bool m_IPFilteringConfigured = false;
bool m_listenInterfaceConfigured = false;
mutable bool m_listenInterfaceConfigured = false;
CachedSettingValue<bool> m_isDHTEnabled;
CachedSettingValue<bool> m_isLSDEnabled;
@@ -580,6 +611,8 @@ namespace BitTorrent
CachedSettingValue<int> m_sendBufferLowWatermark;
CachedSettingValue<int> m_sendBufferWatermarkFactor;
CachedSettingValue<int> m_connectionSpeed;
CachedSettingValue<int> m_socketSendBufferSize;
CachedSettingValue<int> m_socketReceiveBufferSize;
CachedSettingValue<int> m_socketBacklogSize;
CachedSettingValue<bool> m_isAnonymousModeEnabled;
CachedSettingValue<bool> m_isQueueingEnabled;
@@ -616,6 +649,7 @@ namespace BitTorrent
CachedSettingValue<QString> m_additionalTrackers;
CachedSettingValue<qreal> m_globalMaxRatio;
CachedSettingValue<int> m_globalMaxSeedingMinutes;
CachedSettingValue<bool> m_isAddTorrentToQueueTop;
CachedSettingValue<bool> m_isAddTorrentPaused;
CachedSettingValue<Torrent::StopCondition> m_torrentStopCondition;
CachedSettingValue<TorrentContentLayout> m_torrentContentLayout;
@@ -639,7 +673,6 @@ namespace BitTorrent
CachedSettingValue<int> m_encryption;
CachedSettingValue<int> m_maxActiveCheckingTorrents;
CachedSettingValue<bool> m_isProxyPeerConnectionsEnabled;
CachedSettingValue<bool> m_isProxyHostnameLookupEnabled;
CachedSettingValue<ChokingAlgorithm> m_chokingAlgorithm;
CachedSettingValue<SeedChokingAlgorithm> m_seedChokingAlgorithm;
CachedSettingValue<QStringList> m_storedTags;
@@ -662,6 +695,10 @@ namespace BitTorrent
CachedSettingValue<QStringList> m_excludedFileNames;
CachedSettingValue<QStringList> m_bannedIPs;
CachedSettingValue<ResumeDataStorageType> m_resumeDataStorageType;
CachedSettingValue<bool> m_isI2PEnabled;
CachedSettingValue<QString> m_I2PAddress;
CachedSettingValue<int> m_I2PPort;
CachedSettingValue<bool> m_I2PMixedMode;
bool m_isRestored = false;
@@ -671,7 +708,6 @@ namespace BitTorrent
const bool m_wasPexEnabled = m_isPeXEnabled;
int m_numResumeData = 0;
int m_extraLimit = 0;
QVector<TrackerEntry> m_additionalTrackerList;
QVector<QRegularExpression> m_excludedFileNamesRegExpList;
@@ -681,6 +717,8 @@ namespace BitTorrent
qint64 m_previouslyUploaded = 0;
qint64 m_previouslyDownloaded = 0;
bool m_torrentsQueueChanged = false;
bool m_needSaveTorrentsQueue = false;
bool m_refreshEnqueued = false;
QTimer *m_seedingLimitTimer = nullptr;
QTimer *m_resumeDataTimer = nullptr;
@@ -690,11 +728,12 @@ namespace BitTorrent
// Tracker
QPointer<Tracker> m_tracker;
QThread *m_ioThread = nullptr;
Utils::Thread::UniquePtr m_ioThread;
QThreadPool *m_asyncWorker = nullptr;
ResumeDataStorage *m_resumeDataStorage = nullptr;
FileSearcher *m_fileSearcher = nullptr;
QSet<TorrentID> m_downloadedMetadata;
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
QHash<TorrentID, TorrentImpl *> m_torrents;
QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID;
@@ -706,7 +745,9 @@ namespace BitTorrent
QMap<QString, CategoryOptions> m_categories;
QSet<QString> m_tags;
QHash<Torrent *, QSet<QString>> m_updatedTrackerEntries;
// This field holds amounts of peers reported by trackers in their responses to announces
// (torrent.tracker_name.tracker_local_endpoint.num_peers)
QHash<lt::torrent_handle, QHash<std::string, QMap<TrackerEntry::Endpoint, int>>> m_updatedTrackerEntries;
// I/O errored torrents
QSet<TorrentID> m_recentErroredTorrents;
@@ -727,6 +768,15 @@ namespace BitTorrent
bool m_needUpgradeDownloadPath = false;
// All port mapping related routines are invoked from working thread
// so there are no synchronization used. If multithreaded access is
// ever required, synchronization should also be provided.
bool m_isPortMappingEnabled = false;
QHash<quint16, std::vector<lt::port_mapping_t>> m_mappedPorts;
QTimer *m_wakeupCheckTimer = nullptr;
QDateTime m_wakeupCheckTimestamp;
friend void Session::initInstance();
friend void Session::freeInstance();
friend Session *Session::instance();

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -37,7 +37,7 @@
#include "base/3rdparty/expected.hpp"
#include "base/pathfwd.h"
#include "base/tagset.h"
#include "abstractfilestorage.h"
#include "torrentcontenthandler.h"
class QBitArray;
class QByteArray;
@@ -106,9 +106,10 @@ namespace BitTorrent
uint qHash(TorrentState key, uint seed = 0);
#endif
class Torrent : public AbstractFileStorage
class Torrent : public TorrentContentHandler
{
Q_GADGET
Q_OBJECT
Q_DISABLE_COPY_MOVE(Torrent)
public:
enum class StopCondition
@@ -128,7 +129,7 @@ namespace BitTorrent
static const qreal MAX_RATIO;
static const int MAX_SEEDING_TIME;
virtual ~Torrent() = default;
using TorrentContentHandler::TorrentContentHandler;
virtual InfoHash infoHash() const = 0;
virtual QString name() const = 0;
@@ -191,7 +192,6 @@ namespace BitTorrent
virtual void setSavePath(const Path &savePath) = 0;
virtual Path downloadPath() const = 0;
virtual void setDownloadPath(const Path &downloadPath) = 0;
virtual Path actualStorageLocation() const = 0;
virtual Path rootPath() const = 0;
virtual Path contentPath() const = 0;
virtual QString category() const = 0;
@@ -211,12 +211,10 @@ namespace BitTorrent
virtual qreal ratioLimit() const = 0;
virtual int seedingTimeLimit() const = 0;
virtual Path actualFilePath(int index) const = 0;
virtual PathList filePaths() const = 0;
virtual QVector<DownloadPriority> filePriorities() const = 0;
virtual TorrentInfo info() const = 0;
virtual bool isSeed() const = 0;
virtual bool isFinished() const = 0;
virtual bool isPaused() const = 0;
virtual bool isQueued() const = 0;
virtual bool isForced() const = 0;
@@ -231,7 +229,6 @@ namespace BitTorrent
virtual bool isSequentialDownload() const = 0;
virtual bool hasFirstLastPiecePriority() const = 0;
virtual TorrentState state() const = 0;
virtual bool hasMetadata() const = 0;
virtual bool hasMissingFiles() const = 0;
virtual bool hasError() const = 0;
virtual int queuePosition() const = 0;
@@ -243,7 +240,6 @@ namespace BitTorrent
virtual qlonglong activeTime() const = 0;
virtual qlonglong finishedTime() const = 0;
virtual qlonglong eta() const = 0;
virtual QVector<qreal> filesProgress() const = 0;
virtual int seedsCount() const = 0;
virtual int peersCount() const = 0;
virtual int leechsCount() const = 0;
@@ -276,13 +272,6 @@ namespace BitTorrent
virtual int connectionsCount() const = 0;
virtual int connectionsLimit() const = 0;
virtual qlonglong nextAnnounce() const = 0;
/**
* @brief fraction of file pieces that are available at least from one peer
*
* This is not the same as torrrent availability, it is just a fraction of pieces
* that can be downloaded right now. It varies between 0 to 1.
*/
virtual QVector<qreal> availableFileFractions() const = 0;
virtual void setName(const QString &name) = 0;
virtual void setSequentialDownload(bool enable) = 0;
@@ -292,7 +281,6 @@ namespace BitTorrent
virtual void forceReannounce(int index = -1) = 0;
virtual void forceDHTAnnounce() = 0;
virtual void forceRecheck() = 0;
virtual void prioritizeFiles(const QVector<DownloadPriority> &priorities) = 0;
virtual void setRatioLimit(qreal limit) = 0;
virtual void setSeedingTimeLimit(int limit) = 0;
virtual void setUploadLimit(int limit) = 0;
@@ -301,7 +289,6 @@ namespace BitTorrent
virtual void setDHTDisabled(bool disable) = 0;
virtual void setPEXDisabled(bool disable) = 0;
virtual void setLSDDisabled(bool disable) = 0;
virtual void flushCache() const = 0;
virtual void addTrackers(QVector<TrackerEntry> trackers) = 0;
virtual void removeTrackers(const QStringList &trackers) = 0;
virtual void replaceTrackers(QVector<TrackerEntry> trackers) = 0;
@@ -309,7 +296,7 @@ namespace BitTorrent
virtual void removeUrlSeeds(const QVector<QUrl> &urlSeeds) = 0;
virtual bool connectPeer(const PeerAddress &peerAddress) = 0;
virtual void clearPeers() = 0;
virtual bool setMetadata(const TorrentInfo &torrentInfo) = 0;
virtual void setMetadata(const TorrentInfo &torrentInfo) = 0;
virtual StopCondition stopCondition() const = 0;
virtual void setStopCondition(StopCondition stopCondition) = 0;
@@ -318,6 +305,11 @@ namespace BitTorrent
virtual nonstd::expected<QByteArray, QString> exportToBuffer() const = 0;
virtual nonstd::expected<void, QString> exportToFile(const Path &path) const = 0;
virtual void fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const = 0;
virtual void fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const = 0;
virtual void fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const = 0;
virtual void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const = 0;
TorrentID id() const;
bool isResumed() const;
qlonglong remainingSize() const;

View File

@@ -0,0 +1,29 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "torrentcontenthandler.h"

View File

@@ -0,0 +1,61 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#pragma once
#include <QObject>
#include "base/pathfwd.h"
#include "abstractfilestorage.h"
#include "downloadpriority.h"
namespace BitTorrent
{
class TorrentContentHandler : public QObject, public AbstractFileStorage
{
public:
using QObject::QObject;
virtual bool hasMetadata() const = 0;
virtual Path actualStorageLocation() const = 0;
virtual Path actualFilePath(int fileIndex) const = 0;
virtual QVector<DownloadPriority> filePriorities() const = 0;
virtual QVector<qreal> filesProgress() const = 0;
/**
* @brief fraction of file pieces that are available at least from one peer
*
* This is not the same as torrrent availability, it is just a fraction of pieces
* that can be downloaded right now. It varies between 0 to 1.
*/
virtual QVector<qreal> availableFileFractions() const = 0;
virtual void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const = 0;
virtual void prioritizeFiles(const QVector<DownloadPriority> &priorities) = 0;
virtual void flushCache() const = 0;
};
}

View File

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

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -36,7 +36,6 @@
#include <libtorrent/address.hpp>
#include <libtorrent/alert_types.hpp>
#include <libtorrent/create_torrent.hpp>
#include <libtorrent/magnet_uri.hpp>
#include <libtorrent/session.hpp>
#include <libtorrent/storage_defs.hpp>
#include <libtorrent/time.hpp>
@@ -48,6 +47,7 @@
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QPointer>
#include <QSet>
#include <QStringList>
#include <QUrl>
@@ -82,10 +82,10 @@ namespace
#ifdef QBT_USES_LIBTORRENT2
void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
, const lt::info_hash_t &hashes, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
, const lt::info_hash_t &hashes, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
#else
void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
#endif
{
Q_ASSERT(trackerEntry.url == QString::fromStdString(nativeEntry.url));
@@ -98,7 +98,7 @@ namespace
QString firstTrackerMessage;
QString firstErrorMessage;
#ifdef QBT_USES_LIBTORRENT2
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size() * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1));
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1);
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
{
for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2})
@@ -236,7 +236,7 @@ namespace
TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
, const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
: QObject(session)
: Torrent(session)
, m_session(session)
, m_nativeSession(nativeSession)
, m_nativeHandle(nativeHandle)
@@ -254,7 +254,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
, m_seedingTimeLimit(params.seedingTimeLimit)
, m_operatingMode(params.operatingMode)
, m_contentLayout(params.contentLayout)
, m_hasSeedStatus(params.hasSeedStatus)
, m_hasFinishedStatus(params.hasFinishedStatus)
, m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
, m_useAutoTMM(params.useAutoTMM)
, m_isStopped(params.stopped)
@@ -279,6 +279,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
, LT::toNative(m_ltAddTorrentParams.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
m_completedFiles.fill(static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode), filesCount);
m_filesProgress.resize(filesCount);
for (int i = 0; i < filesCount; ++i)
{
@@ -301,8 +302,14 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
m_trackerEntries.reserve(static_cast<decltype(m_trackerEntries)::size_type>(extensionData->trackers.size()));
for (const lt::announce_entry &announceEntry : extensionData->trackers)
m_trackerEntries.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(extensionData->urlSeeds.size()));
for (const std::string &urlSeed : extensionData->urlSeeds)
m_urlSeeds.append(QString::fromStdString(urlSeed));
m_nativeStatus = extensionData->status;
if (hasMetadata())
updateProgress();
updateState();
if (hasMetadata())
@@ -331,7 +338,7 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
// == END UPGRADE CODE ==
}
TorrentImpl::~TorrentImpl() {}
TorrentImpl::~TorrentImpl() = default;
bool TorrentImpl::isValid() const
{
@@ -424,13 +431,16 @@ void TorrentImpl::setSavePath(const Path &path)
if (resolvedPath == savePath())
return;
m_savePath = resolvedPath;
m_session->handleTorrentNeedSaveResumeData(this);
const bool isFinished = isSeed() || m_hasSeedStatus;
if (isFinished || downloadPath().isEmpty())
moveStorage(savePath(), MoveStorageMode::KeepExistingFiles);
if (isFinished() || m_hasFinishedStatus || downloadPath().isEmpty())
{
moveStorage(resolvedPath, MoveStorageContext::ChangeSavePath);
}
else
{
m_savePath = resolvedPath;
m_session->handleTorrentSavePathChanged(this);
m_session->handleTorrentNeedSaveResumeData(this);
}
}
Path TorrentImpl::downloadPath() const
@@ -448,13 +458,17 @@ void TorrentImpl::setDownloadPath(const Path &path)
if (resolvedPath == m_downloadPath)
return;
m_downloadPath = resolvedPath;
m_session->handleTorrentNeedSaveResumeData(this);
const bool isFinished = isSeed() || m_hasSeedStatus;
if (!isFinished)
moveStorage((m_downloadPath.isEmpty() ? savePath() : m_downloadPath), MoveStorageMode::KeepExistingFiles);
const bool isIncomplete = !(isFinished() || m_hasFinishedStatus);
if (isIncomplete)
{
moveStorage((resolvedPath.isEmpty() ? savePath() : resolvedPath), MoveStorageContext::ChangeDownloadPath);
}
else
{
m_downloadPath = resolvedPath;
m_session->handleTorrentSavePathChanged(this);
m_session->handleTorrentNeedSaveResumeData(this);
}
}
Path TorrentImpl::rootPath() const
@@ -520,11 +534,19 @@ void TorrentImpl::setAutoManaged(const bool enable)
m_nativeHandle.unset_flags(lt::torrent_flags::auto_managed);
}
Path TorrentImpl::wantedActualPath(int index, const Path &path) const
{
if (m_session->isAppendExtensionEnabled()
&& (fileSize(index) > 0) && !m_completedFiles.at(index))
{
return path + QB_EXT;
}
return path;
}
QVector<TrackerEntry> TorrentImpl::trackers() const
{
if (!m_updatedTrackerEntries.isEmpty())
refreshTrackerEntries();
return m_trackerEntries;
}
@@ -599,63 +621,95 @@ void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
QVector<QUrl> TorrentImpl::urlSeeds() const
{
const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
QVector<QUrl> urlSeeds;
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
for (const std::string &urlSeed : currentSeeds)
urlSeeds.append(QString::fromStdString(urlSeed));
return urlSeeds;
return m_urlSeeds;
}
void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
{
const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
QVector<QUrl> addedUrlSeeds;
addedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds)
m_session->invokeAsync([urlSeeds, session = m_session
, nativeHandle = m_nativeHandle
, thisTorrent = QPointer<TorrentImpl>(this)]
{
const std::string nativeUrl = url.toString().toStdString();
if (currentSeeds.find(nativeUrl) == currentSeeds.end())
try
{
m_nativeHandle.add_url_seed(nativeUrl);
addedUrlSeeds << url;
}
}
const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
QVector<QUrl> currentSeeds;
currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
for (const std::string &urlSeed : nativeSeeds)
currentSeeds.append(QString::fromStdString(urlSeed));
if (!addedUrlSeeds.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds);
}
QVector<QUrl> addedUrlSeeds;
addedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds)
{
if (!currentSeeds.contains(url))
{
nativeHandle.add_url_seed(url.toString().toStdString());
addedUrlSeeds.append(url);
}
}
currentSeeds.append(addedUrlSeeds);
session->invoke([session, thisTorrent, currentSeeds, addedUrlSeeds]
{
if (!thisTorrent)
return;
thisTorrent->m_urlSeeds = currentSeeds;
if (!addedUrlSeeds.isEmpty())
{
session->handleTorrentNeedSaveResumeData(thisTorrent);
session->handleTorrentUrlSeedsAdded(thisTorrent, addedUrlSeeds);
}
});
}
catch (const std::exception &) {}
});
}
void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
{
const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
QVector<QUrl> removedUrlSeeds;
removedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds)
m_session->invokeAsync([urlSeeds, session = m_session
, nativeHandle = m_nativeHandle
, thisTorrent = QPointer<TorrentImpl>(this)]
{
const std::string nativeUrl = url.toString().toStdString();
if (currentSeeds.find(nativeUrl) != currentSeeds.end())
try
{
m_nativeHandle.remove_url_seed(nativeUrl);
removedUrlSeeds << url;
}
}
const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
QVector<QUrl> currentSeeds;
currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
for (const std::string &urlSeed : nativeSeeds)
currentSeeds.append(QString::fromStdString(urlSeed));
if (!removedUrlSeeds.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds);
}
QVector<QUrl> removedUrlSeeds;
removedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds)
{
if (currentSeeds.removeOne(url))
{
nativeHandle.remove_url_seed(url.toString().toStdString());
removedUrlSeeds.append(url);
}
}
session->invoke([session, thisTorrent, currentSeeds, removedUrlSeeds]
{
if (!thisTorrent)
return;
thisTorrent->m_urlSeeds = currentSeeds;
if (!removedUrlSeeds.isEmpty())
{
session->handleTorrentNeedSaveResumeData(thisTorrent);
session->handleTorrentUrlSeedsRemoved(thisTorrent, removedUrlSeeds);
}
});
}
catch (const std::exception &) {}
});
}
void TorrentImpl::clearPeers()
@@ -690,9 +744,9 @@ bool TorrentImpl::needSaveResumeData() const
return m_nativeStatus.need_save_resume;
}
void TorrentImpl::saveResumeData()
void TorrentImpl::saveResumeData(lt::resume_data_flags_t flags)
{
m_nativeHandle.save_resume_data();
m_nativeHandle.save_resume_data(flags);
m_session->handleTorrentSaveResumeDataRequested(this);
}
@@ -723,7 +777,13 @@ qreal TorrentImpl::progress() const
return 1.;
const qreal progress = static_cast<qreal>(m_nativeStatus.total_wanted_done) / m_nativeStatus.total_wanted;
Q_ASSERT((progress >= 0.f) && (progress <= 1.f));
if ((progress < 0.f) || (progress > 1.f))
{
LogMsg(tr("Unexpected data detected. Torrent: %1. Data: total_wanted=%2 total_wanted_done=%3.")
.arg(name(), QString::number(m_nativeStatus.total_wanted), QString::number(m_nativeStatus.total_wanted_done))
, Log::WARNING);
}
return progress;
}
@@ -956,7 +1016,7 @@ bool TorrentImpl::isErrored() const
|| (m_state == TorrentState::Error));
}
bool TorrentImpl::isSeed() const
bool TorrentImpl::isFinished() const
{
return ((m_nativeStatus.state == lt::torrent_status::finished)
|| (m_nativeStatus.state == lt::torrent_status::seeding));
@@ -1012,9 +1072,9 @@ void TorrentImpl::updateState()
else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isPaused())
{
// If the torrent is not just in the "checking" state, but is being actually checked
m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
m_state = m_hasFinishedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
}
else if (isSeed())
else if (isFinished())
{
if (isPaused())
m_state = TorrentState::PausedUploading;
@@ -1102,7 +1162,7 @@ qlonglong TorrentImpl::eta() const
const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
if (isSeed())
if (isFinished())
{
const qreal maxRatioValue = maxRatio();
const int maxSeedingTimeValue = maxSeedingTime();
@@ -1142,20 +1202,20 @@ QVector<qreal> TorrentImpl::filesProgress() const
if (!hasMetadata())
return {};
if (m_completedFiles.count(true) == filesCount())
return QVector<qreal>(filesCount(), 1);
const int count = m_filesProgress.size();
Q_ASSERT(count == filesCount());
if (Q_UNLIKELY(count != filesCount()))
return {};
std::vector<int64_t> fp;
m_nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity);
if (m_completedFiles.count(true) == count)
return QVector<qreal>(count, 1);
const int count = filesCount();
const auto nativeIndexes = m_torrentInfo.nativeIndexes();
QVector<qreal> result;
result.reserve(count);
for (int i = 0; i < count; ++i)
{
const int64_t progress = fp[LT::toUnderlyingType(nativeIndexes[i])];
const qlonglong size = fileSize(i);
const int64_t progress = m_filesProgress.at(i);
const int64_t size = fileSize(i);
if ((size <= 0) || (progress == size))
result << 1;
else
@@ -1274,15 +1334,13 @@ QVector<PeerInfo> TorrentImpl::peers() const
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
for (const lt::peer_info &peer : nativePeers)
peers << PeerInfo(this, peer);
peers.append(PeerInfo(peer, pieces()));
return peers;
}
QBitArray TorrentImpl::pieces() const
{
if (m_pieces.isEmpty())
m_pieces = LT::toQBitArray(m_nativeStatus.pieces);
return m_pieces;
}
@@ -1427,12 +1485,22 @@ void TorrentImpl::forceDHTAnnounce()
void TorrentImpl::forceRecheck()
{
if (!hasMetadata()) return;
if (!hasMetadata())
return;
m_nativeHandle.force_recheck();
// We have to force update the cached state, otherwise someone will be able to get
// an incorrect one during the interval until the cached state is updated in a regular way.
m_nativeStatus.state = lt::torrent_status::checking_resume_data;
m_hasMissingFiles = false;
m_unchecked = false;
m_completedFiles.fill(false);
m_filesProgress.fill(0);
m_pieces.fill(false);
m_nativeStatus.pieces.clear_all();
m_nativeStatus.num_pieces = 0;
if (isPaused())
{
@@ -1515,43 +1583,25 @@ void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileN
endReceivedMetadataHandling(savePath, fileNames);
}
void TorrentImpl::updatePeerCount(const QString &trackerURL, const TrackerEntry::Endpoint &endpoint, const int count)
TrackerEntry TorrentImpl::updateTrackerEntry(const lt::announce_entry &announceEntry, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
{
m_updatedTrackerEntries[trackerURL][endpoint] = count;
}
void TorrentImpl::invalidateTrackerEntry(const QString &trackerURL)
{
std::ignore = m_updatedTrackerEntries[trackerURL];
}
void TorrentImpl::refreshTrackerEntries() const
{
const std::vector<lt::announce_entry> nativeTrackers = m_nativeHandle.trackers();
Q_ASSERT(nativeTrackers.size() == m_trackerEntries.size());
for (TrackerEntry &trackerEntry : m_trackerEntries)
const auto it = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end()
, [&announceEntry](const TrackerEntry &trackerEntry)
{
const auto updatedTrackerIter = m_updatedTrackerEntries.find(trackerEntry.url);
if (updatedTrackerIter == m_updatedTrackerEntries.end())
continue;
return (trackerEntry.url == QString::fromStdString(announceEntry.url));
});
const auto nativeTrackerIter = std::find_if(nativeTrackers.cbegin(), nativeTrackers.cend()
, [trackerURL = trackerEntry.url.toStdString()](const lt::announce_entry &announceEntry)
{
return (announceEntry.url == trackerURL);
});
Q_ASSERT(nativeTrackerIter != nativeTrackers.cend());
Q_ASSERT(it != m_trackerEntries.end());
// TODO: use [[unlikely]] in C++20
if (Q_UNLIKELY(it == m_trackerEntries.end()))
return {};
const lt::announce_entry &announceEntry = *nativeTrackerIter;
#ifdef QBT_USES_LIBTORRENT2
updateTrackerEntry(trackerEntry, announceEntry, m_nativeHandle.info_hashes(), updatedTrackerIter.value());
::updateTrackerEntry(*it, announceEntry, nativeHandle().info_hashes(), updateInfo);
#else
updateTrackerEntry(trackerEntry, announceEntry, updatedTrackerIter.value());
::updateTrackerEntry(*it, announceEntry, updateInfo);
#endif
}
m_updatedTrackerEntries.clear();
return *it;
}
std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
@@ -1575,12 +1625,14 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
, LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount());
m_filesProgress.resize(filesCount());
updateProgress();
for (int i = 0; i < fileNames.size(); ++i)
{
const auto nativeIndex = nativeIndexes.at(i);
const Path actualFilePath = fileNames.at(i);
const Path &actualFilePath = fileNames.at(i);
p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
const Path filePath = actualFilePath.removedExtension(QB_EXT);
@@ -1622,7 +1674,10 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
void TorrentImpl::reload()
{
m_completedFiles.fill(false);
m_pieces.clear();
m_filesProgress.fill(0);
m_pieces.fill(false);
m_nativeStatus.pieces.clear_all();
m_nativeStatus.num_pieces = 0;
const auto queuePos = m_nativeHandle.queue_position();
@@ -1713,7 +1768,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
}
}
void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageMode mode)
void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
{
if (!hasMetadata())
{
@@ -1721,7 +1776,9 @@ void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageMode mode)
return;
}
if (m_session->addMoveTorrentStorageJob(this, newPath, mode))
const auto mode = (context == MoveStorageContext::AdjustCurrentLocation)
? MoveStorageMode::Overwrite : MoveStorageMode::KeepExistingFiles;
if (m_session->addMoveTorrentStorageJob(this, newPath, mode, context))
{
m_storageIsMoving = true;
updateState();
@@ -1730,15 +1787,12 @@ void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageMode mode)
void TorrentImpl::renameFile(const int index, const Path &path)
{
const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
Q_ASSERT(index >= 0);
Q_ASSERT(index < nativeIndexes.size());
if ((index < 0) || (index >= nativeIndexes.size()))
Q_ASSERT((index >= 0) && (index < filesCount()));
if (Q_UNLIKELY((index < 0) || (index >= filesCount())))
return;
++m_renameCount;
m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
const Path wantedPath = wantedActualPath(index, path);
doRenameFile(index, wantedPath);
}
void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
@@ -1746,16 +1800,17 @@ void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
updateStatus(nativeStatus);
}
void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const bool hasOutstandingJob)
void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStorageContext context, const bool hasOutstandingJob)
{
m_session->handleTorrentNeedSaveResumeData(this);
if (context == MoveStorageContext::ChangeSavePath)
m_savePath = path;
else if (context == MoveStorageContext::ChangeDownloadPath)
m_downloadPath = path;
m_storageIsMoving = hasOutstandingJob;
m_nativeStatus.save_path = path.toString().toStdString();
if (actualStorageLocation() != path)
{
m_nativeStatus.save_path = path.toString().toStdString();
m_session->handleTorrentSavePathChanged(this);
}
m_session->handleTorrentSavePathChanged(this);
m_session->handleTorrentNeedSaveResumeData(this);
if (!m_storageIsMoving)
{
@@ -1794,9 +1849,9 @@ void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
if (!m_hasMissingFiles)
{
if ((progress() < 1.0) && (wantedSize() > 0))
m_hasSeedStatus = false;
m_hasFinishedStatus = false;
else if (progress() == 1.0)
m_hasSeedStatus = true;
m_hasFinishedStatus = true;
adjustStorageLocation();
manageIncompleteFiles();
@@ -1823,30 +1878,29 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
Q_UNUSED(p);
m_hasMissingFiles = false;
if (m_hasSeedStatus)
if (m_hasFinishedStatus)
return;
m_statusUpdatedTriggers.enqueue([this]()
{
m_hasSeedStatus = true;
adjustStorageLocation();
manageIncompleteFiles();
m_session->handleTorrentNeedSaveResumeData(this);
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
if (isMoveInProgress() || (m_renameCount > 0))
if (recheckTorrentsOnCompletion && m_unchecked)
{
if (recheckTorrentsOnCompletion)
m_moveFinishedTriggers.enqueue([this]() { forceRecheck(); });
m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
forceRecheck();
}
else
{
if (recheckTorrentsOnCompletion && m_unchecked)
forceRecheck();
m_session->handleTorrentFinished(this);
m_hasFinishedStatus = true;
if (isMoveInProgress() || (m_renameCount > 0))
m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
else
m_session->handleTorrentFinished(this);
}
});
}
@@ -1863,6 +1917,14 @@ void TorrentImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p)
void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
{
if (m_ltAddTorrentParams.url_seeds != p->params.url_seeds)
{
// URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
// Unfortunately, URL seed list containing in "resume data" is generated according to different rules
// than the list we usually cache, so we have to request it from the appropriate source.
fetchURLSeeds([this](const QVector<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
}
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
{
Q_ASSERT(m_indexMap.isEmpty());
@@ -1947,7 +2009,7 @@ void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
resumeData.ratioLimit = m_ratioLimit;
resumeData.seedingTimeLimit = m_seedingTimeLimit;
resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
resumeData.hasSeedStatus = m_hasSeedStatus;
resumeData.hasFinishedStatus = m_hasFinishedStatus;
resumeData.stopped = m_isStopped;
resumeData.stopCondition = m_stopCondition;
resumeData.operatingMode = m_operatingMode;
@@ -1990,13 +2052,12 @@ void TorrentImpl::handleFileRenamedAlert(const lt::file_renamed_alert *p)
// For example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
// be removed if they are empty
const Path oldFilePath = m_filePaths.at(fileIndex);
const Path newFilePath {QString::fromUtf8(p->new_name())};
const Path newFilePath = Path(QString::fromUtf8(p->new_name())).removedExtension(QB_EXT);
// Check if ".!qB" extension was just added or removed
// We should compare path in a case sensitive manner even on case insensitive
// platforms since it can be renamed by only changing case of some character(s)
if ((oldFilePath.data() != newFilePath.data())
&& ((oldFilePath + QB_EXT) != newFilePath))
if (oldFilePath.data() != newFilePath.data())
{
m_filePaths[fileIndex] = newFilePath;
@@ -2039,7 +2100,7 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
const int fileIndex = m_indexMap.value(p->index, -1);
Q_ASSERT(fileIndex >= 0);
m_completedFiles[fileIndex] = true;
m_completedFiles.setBit(fileIndex);
if (m_session->isAppendExtensionEnabled())
{
@@ -2048,7 +2109,7 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
if (actualPath != path)
{
qDebug("Renaming %s to %s", qUtf8Printable(actualPath.toString()), qUtf8Printable(path.toString()));
renameFile(fileIndex, path);
doRenameFile(fileIndex, path);
}
}
}
@@ -2155,7 +2216,6 @@ void TorrentImpl::manageIncompleteFiles()
{
const std::shared_ptr<const lt::torrent_info> nativeInfo = nativeTorrentInfo();
const lt::file_storage &nativeFiles = nativeInfo->files();
const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled();
for (int i = 0; i < filesCount(); ++i)
{
@@ -2163,23 +2223,11 @@ void TorrentImpl::manageIncompleteFiles()
const auto nativeIndex = m_torrentInfo.nativeIndexes().at(i);
const Path actualPath {nativeFiles.file_path(nativeIndex)};
if (isAppendExtensionEnabled && (fileSize(i) > 0) && !m_completedFiles.at(i))
const Path wantedPath = wantedActualPath(i, path);
if (actualPath != wantedPath)
{
const Path wantedPath = path + QB_EXT;
if (actualPath != wantedPath)
{
qDebug() << "Renaming" << actualPath.toString() << "to" << wantedPath.toString();
renameFile(i, wantedPath);
}
}
else
{
if (actualPath != path)
{
qDebug() << "Renaming" << actualPath.toString() << "to" << path.toString();
renameFile(i, path);
}
qDebug() << "Renaming" << actualPath.toString() << "to" << wantedPath.toString();
doRenameFile(i, wantedPath);
}
}
}
@@ -2187,11 +2235,23 @@ void TorrentImpl::manageIncompleteFiles()
void TorrentImpl::adjustStorageLocation()
{
const Path downloadPath = this->downloadPath();
const bool isFinished = isSeed() || m_hasSeedStatus;
const Path targetPath = ((isFinished || downloadPath.isEmpty()) ? savePath() : downloadPath);
const Path targetPath = ((isFinished() || m_hasFinishedStatus || downloadPath.isEmpty()) ? savePath() : downloadPath);
if ((targetPath != actualStorageLocation()) || isMoveInProgress())
moveStorage(targetPath, MoveStorageMode::FailIfExist);
moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation);
}
void TorrentImpl::doRenameFile(int index, const Path &path)
{
const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
Q_ASSERT(index >= 0);
Q_ASSERT(index < nativeIndexes.size());
if (Q_UNLIKELY((index < 0) || (index >= nativeIndexes.size())))
return;
++m_renameCount;
m_nativeHandle.rename_file(nativeIndexes[index], path.toString().toStdString());
}
lt::torrent_handle TorrentImpl::nativeHandle() const
@@ -2199,17 +2259,24 @@ lt::torrent_handle TorrentImpl::nativeHandle() const
return m_nativeHandle;
}
bool TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
{
if (hasMetadata())
return false;
return;
m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo]
{
try
{
#ifdef QBT_USES_LIBTORRENT2
return m_nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
#else
const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
return m_nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
#endif
}
catch (const std::exception &) {}
});
}
Torrent::StopCondition TorrentImpl::stopCondition() const
@@ -2241,8 +2308,11 @@ bool TorrentImpl::isMoveInProgress() const
void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
{
m_pieces.clear();
m_nativeStatus = nativeStatus;
const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
updateProgress();
updateState();
m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
@@ -2262,6 +2332,44 @@ void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
std::invoke(m_statusUpdatedTriggers.dequeue());
}
void TorrentImpl::updateProgress()
{
Q_ASSERT(hasMetadata());
if (Q_UNLIKELY(!hasMetadata()))
return;
Q_ASSERT(!m_filesProgress.isEmpty());
if (Q_UNLIKELY(m_filesProgress.isEmpty()))
m_filesProgress.resize(filesCount());
const QBitArray oldPieces = std::exchange(m_pieces, LT::toQBitArray(m_nativeStatus.pieces));
const QBitArray newPieces = m_pieces ^ oldPieces;
const int64_t pieceSize = m_torrentInfo.pieceLength();
for (qsizetype index = 0; index < newPieces.size(); ++index)
{
if (!newPieces.at(index))
continue;
int64_t size = m_torrentInfo.pieceLength(index);
int64_t pieceOffset = index * pieceSize;
for (const int fileIndex : asConst(m_torrentInfo.fileIndicesForPiece(index)))
{
const int64_t fileOffsetInPiece = pieceOffset - m_torrentInfo.fileOffset(fileIndex);
const int64_t add = std::min<int64_t>((m_torrentInfo.fileSize(fileIndex) - fileOffsetInPiece), size);
m_filesProgress[fileIndex] += add;
size -= add;
if (size <= 0)
break;
pieceOffset += add;
}
}
}
void TorrentImpl::setRatioLimit(qreal limit)
{
if (limit < USE_GLOBAL_RATIO)
@@ -2373,7 +2481,39 @@ void TorrentImpl::flushCache() const
QString TorrentImpl::createMagnetURI() const
{
return QString::fromStdString(lt::make_magnet_uri(m_nativeHandle));
QString ret = u"magnet:?"_qs;
const SHA1Hash infoHash1 = infoHash().v1();
if (infoHash1.isValid())
{
ret += u"xt=urn:btih:" + infoHash1.toString();
}
const SHA256Hash infoHash2 = infoHash().v2();
if (infoHash2.isValid())
{
if (infoHash1.isValid())
ret += u'&';
ret += u"xt=urn:btmh:1220" + infoHash2.toString();
}
const QString displayName = name();
if (displayName != id().toString())
{
ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
}
for (const TrackerEntry &tracker : asConst(trackers()))
{
ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
}
for (const QUrl &urlSeed : asConst(urlSeeds()))
{
ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
}
return ret;
}
nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
@@ -2429,6 +2569,129 @@ nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) cons
return {};
}
void TorrentImpl::fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QVector<PeerInfo>
{
try
{
std::vector<lt::peer_info> nativePeers;
nativeHandle.get_peer_info(nativePeers);
QVector<PeerInfo> peers;
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
for (const lt::peer_info &peer : nativePeers)
peers.append(PeerInfo(peer, allPieces));
return peers;
}
catch (const std::exception &) {}
return {};
}
, std::move(resultHandler));
}
void TorrentImpl::fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<QUrl>
{
try
{
const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
QVector<QUrl> urlSeeds;
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
for (const std::string &urlSeed : currentSeeds)
urlSeeds.append(QString::fromStdString(urlSeed));
return urlSeeds;
}
catch (const std::exception &) {}
return {};
}
, std::move(resultHandler));
}
void TorrentImpl::fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<int>
{
try
{
std::vector<int> piecesAvailability;
nativeHandle.piece_availability(piecesAvailability);
return QVector<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
}
catch (const std::exception &) {}
return {};
}
, std::move(resultHandler));
}
void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
{
try
{
#ifdef QBT_USES_LIBTORRENT2
const std::vector<lt::partial_piece_info> queue = nativeHandle.get_download_queue();
#else
std::vector<lt::partial_piece_info> queue;
nativeHandle.get_download_queue(queue);
#endif
QBitArray result;
result.resize(torrentInfo.piecesCount());
for (const lt::partial_piece_info &info : queue)
result.setBit(LT::toUnderlyingType(info.piece_index));
return result;
}
catch (const std::exception &) {}
return {};
}
, std::move(resultHandler));
}
void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QVector<qreal>
{
if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
return {};
try
{
std::vector<int> piecesAvailability;
nativeHandle.piece_availability(piecesAvailability);
const int filesCount = torrentInfo.filesCount();
// libtorrent returns empty array for seeding only torrents
if (piecesAvailability.empty())
return QVector<qreal>(filesCount, -1);
QVector<qreal> result;
result.reserve(filesCount);
for (int i = 0; i < filesCount; ++i)
{
const TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(i);
int availablePieces = 0;
for (const int piece : filePieces)
availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
const qreal availability = filePieces.isEmpty()
? 1 // the file has no pieces, so it is available by default
: static_cast<qreal>(availablePieces) / filePieces.size();
result.append(availability);
}
return result;
}
catch (const std::exception &) {}
return {};
}
, std::move(resultHandler));
}
void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
{
if (!hasMetadata()) return;
@@ -2444,7 +2707,7 @@ void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
&& (priorities[i] > DownloadPriority::Ignored)
&& !m_completedFiles.at(i))
{
m_hasSeedStatus = false;
m_hasFinishedStatus = false;
break;
}
}
@@ -2492,3 +2755,19 @@ QVector<qreal> TorrentImpl::availableFileFractions() const
}
return res;
}
template <typename Func, typename Callback>
void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
{
m_session->invokeAsync([session = m_session
, func = std::move(func)
, resultHandler = std::move(resultHandler)
, thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
{
session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
{
if (thisTorrent)
resultHandler(result);
});
});
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -68,6 +68,13 @@ namespace BitTorrent
Overwrite
};
enum class MoveStorageContext
{
AdjustCurrentLocation,
ChangeSavePath,
ChangeDownloadPath
};
enum class MaintenanceJob
{
None,
@@ -77,13 +84,13 @@ namespace BitTorrent
struct FileErrorInfo
{
lt::error_code error;
lt::operation_t operation;
lt::operation_t operation = lt::operation_t::unknown;
};
class TorrentImpl final : public QObject, public Torrent
class TorrentImpl final : public Torrent
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(TorrentImpl)
Q_DECLARE_TR_FUNCTIONS(BitTorrent::TorrentImpl)
public:
TorrentImpl(SessionImpl *session, lt::session *nativeSession
@@ -139,7 +146,7 @@ namespace BitTorrent
QVector<DownloadPriority> filePriorities() const override;
TorrentInfo info() const override;
bool isSeed() const override;
bool isFinished() const override;
bool isPaused() const override;
bool isQueued() const override;
bool isForced() const override;
@@ -227,7 +234,7 @@ namespace BitTorrent
void removeUrlSeeds(const QVector<QUrl> &urlSeeds) override;
bool connectPeer(const PeerAddress &peerAddress) override;
void clearPeers() override;
bool setMetadata(const TorrentInfo &torrentInfo) override;
void setMetadata(const TorrentInfo &torrentInfo) override;
StopCondition stopCondition() const override;
void setStopCondition(StopCondition stopCondition) override;
@@ -236,6 +243,12 @@ namespace BitTorrent
nonstd::expected<QByteArray, QString> exportToBuffer() const override;
nonstd::expected<void, QString> exportToFile(const Path &path) const override;
void fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const override;
void fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const override;
void fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const override;
void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const override;
void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const override;
bool needSaveResumeData() const;
// Session interface
@@ -245,19 +258,18 @@ namespace BitTorrent
void handleStateUpdate(const lt::torrent_status &nativeStatus);
void handleCategoryOptionsChanged();
void handleAppendExtensionToggled();
void saveResumeData();
void handleMoveStorageJobFinished(const Path &path, bool hasOutstandingJob);
void saveResumeData(lt::resume_data_flags_t flags = {});
void handleMoveStorageJobFinished(const Path &path, MoveStorageContext context, bool hasOutstandingJob);
void fileSearchFinished(const Path &savePath, const PathList &fileNames);
void updatePeerCount(const QString &trackerURL, const TrackerEntry::Endpoint &endpoint, int count);
void invalidateTrackerEntry(const QString &trackerURL);
TrackerEntry updateTrackerEntry(const lt::announce_entry &announceEntry, const QMap<TrackerEntry::Endpoint, int> &updateInfo);
private:
using EventTrigger = std::function<void ()>;
std::shared_ptr<const lt::torrent_info> nativeTorrentInfo() const;
void refreshTrackerEntries() const;
void updateStatus(const lt::torrent_status &nativeStatus);
void updateProgress();
void updateState();
void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p);
@@ -281,8 +293,10 @@ namespace BitTorrent
void setAutoManaged(bool enable);
Path wantedActualPath(int index, const Path &path) const;
void adjustStorageLocation();
void moveStorage(const Path &newPath, MoveStorageMode mode);
void doRenameFile(int index, const Path &path);
void moveStorage(const Path &newPath, MoveStorageContext context);
void manageIncompleteFiles();
void applyFirstLastPiecePriority(bool enabled);
@@ -292,6 +306,9 @@ namespace BitTorrent
nonstd::expected<lt::entry, QString> exportTorrent() const;
template <typename Func, typename Callback>
void invokeAsync(Func func, Callback resultHandler) const;
SessionImpl *const m_session = nullptr;
lt::session *m_nativeSession = nullptr;
lt::torrent_handle m_nativeHandle;
@@ -316,10 +333,8 @@ namespace BitTorrent
MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
// TODO: Use QHash<TrackerEntry::Endpoint, int> once Qt5 is dropped.
using TrackerEntryUpdateInfo = QMap<TrackerEntry::Endpoint, int>;
mutable QHash<QString, TrackerEntryUpdateInfo> m_updatedTrackerEntries;
mutable QVector<TrackerEntry> m_trackerEntries;
QVector<TrackerEntry> m_trackerEntries;
QVector<QUrl> m_urlSeeds;
FileErrorInfo m_lastFileError;
// Persistent data
@@ -332,7 +347,7 @@ namespace BitTorrent
int m_seedingTimeLimit;
TorrentOperatingMode m_operatingMode;
TorrentContentLayout m_contentLayout;
bool m_hasSeedStatus;
bool m_hasFinishedStatus;
bool m_hasMissingFiles = false;
bool m_hasFirstLastPiecePriority = false;
bool m_useAutoTMM;
@@ -346,6 +361,7 @@ namespace BitTorrent
int m_downloadLimit = 0;
int m_uploadLimit = 0;
mutable QBitArray m_pieces;
QBitArray m_pieces;
QVector<std::int64_t> m_filesProgress;
};
}

View File

@@ -66,12 +66,6 @@ TorrentInfo::TorrentInfo(const lt::torrent_info &nativeInfo)
}
}
TorrentInfo::TorrentInfo(const TorrentInfo &other)
: m_nativeInfo {other.m_nativeInfo}
, m_nativeIndexes {other.m_nativeIndexes}
{
}
TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
{
if (this != &other)

View File

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

View File

@@ -203,12 +203,12 @@ Tracker::Tracker(QObject *parent)
bool Tracker::start()
{
const QHostAddress ip = QHostAddress::Any;
const int port = Preferences::instance()->getTrackerPort();
if (m_server->isListening())
{
if (m_server->serverPort() == port)
if (const int oldPort = m_server->serverPort()
; oldPort == port)
{
// Already listening on the right port, just return
return true;
@@ -218,9 +218,9 @@ bool Tracker::start()
m_server->close();
}
// Listen on the predefined port
// Listen on port
const QHostAddress ip = QHostAddress::Any;
const bool listenSuccess = m_server->listen(ip, port);
if (listenSuccess)
{
LogMsg(tr("Embedded Tracker: Now listening on IP: %1, port: %2")

View File

@@ -45,7 +45,7 @@ public:
Digest32() = default;
Digest32(const Digest32 &other) = default;
Digest32(Digest32 &&other) = default;
Digest32(Digest32 &&other) noexcept = default;
Digest32(const UnderlyingType &nativeDigest)
: m_dataPtr {new Data(nativeDigest)}
@@ -63,7 +63,7 @@ public:
}
Digest32 &operator=(const Digest32 &other) = default;
Digest32 &operator=(Digest32 &&other) = default;
Digest32 &operator=(Digest32 &&other) noexcept = default;
operator UnderlyingType() const
{
@@ -167,6 +167,6 @@ std::size_t qHash(const Digest32<N> &key, const std::size_t seed = 0)
template <int N>
uint qHash(const Digest32<N> &key, const uint seed = 0)
{
return static_cast<uint>((std::hash<typename Digest32<N>::UnderlyingType> {})(key)) ^ seed;
return ::qHash(std::hash<typename Digest32<N>::UnderlyingType> {}(key), seed);
}
#endif

View File

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

View File

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

View File

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

View File

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

View File

@@ -56,10 +56,33 @@ namespace
QList<QSslCipher> safeCipherList()
{
const QStringList badCiphers {u"idea"_qs, u"rc4"_qs};
// Contains Ciphersuites that use RSA for the Key Exchange but they don't mention it in their name
const QStringList badRSAShorthandSuites {
u"AES256-GCM-SHA384"_qs, u"AES128-GCM-SHA256"_qs, u"AES256-SHA256"_qs,
u"AES128-SHA256"_qs, u"AES256-SHA"_qs, u"AES128-SHA"_qs};
// Contains Ciphersuites that use AES CBC mode but they don't mention it in their name
const QStringList badAESShorthandSuites {
u"ECDHE-ECDSA-AES256-SHA384"_qs, u"ECDHE-RSA-AES256-SHA384"_qs, u"DHE-RSA-AES256-SHA256"_qs,
u"ECDHE-ECDSA-AES128-SHA256"_qs, u"ECDHE-RSA-AES128-SHA256"_qs, u"DHE-RSA-AES128-SHA256"_qs,
u"ECDHE-ECDSA-AES256-SHA"_qs, u"ECDHE-RSA-AES256-SHA"_qs, u"DHE-RSA-AES256-SHA"_qs,
u"ECDHE-ECDSA-AES128-SHA"_qs, u"ECDHE-RSA-AES128-SHA"_qs, u"DHE-RSA-AES128-SHA"_qs};
const QList<QSslCipher> allCiphers {QSslConfiguration::supportedCiphers()};
QList<QSslCipher> safeCiphers;
std::copy_if(allCiphers.cbegin(), allCiphers.cend(), std::back_inserter(safeCiphers), [&badCiphers](const QSslCipher &cipher)
std::copy_if(allCiphers.cbegin(), allCiphers.cend(), std::back_inserter(safeCiphers),
[&badCiphers, &badRSAShorthandSuites, &badAESShorthandSuites](const QSslCipher &cipher)
{
const QString name = cipher.name();
if (name.contains(u"-cbc-"_qs, Qt::CaseInsensitive) // AES CBC mode is considered vulnerable to BEAST attack
|| name.startsWith(u"adh-"_qs, Qt::CaseInsensitive) // Key Exchange: Diffie-Hellman, doesn't support Perfect Forward Secrecy
|| name.startsWith(u"aecdh-"_qs, Qt::CaseInsensitive) // Key Exchange: Elliptic Curve Diffie-Hellman, doesn't support Perfect Forward Secrecy
|| name.startsWith(u"psk-"_qs, Qt::CaseInsensitive) // Key Exchange: Pre-Shared Key, doesn't support Perfect Forward Secrecy
|| name.startsWith(u"rsa-"_qs, Qt::CaseInsensitive) // Key Exchange: Rivest Shamir Adleman (RSA), doesn't support Perfect Forward Secrecy
|| badRSAShorthandSuites.contains(name, Qt::CaseInsensitive)
|| badAESShorthandSuites.contains(name, Qt::CaseInsensitive))
{
return false;
}
return std::none_of(badCiphers.cbegin(), badCiphers.cend(), [&cipher](const QString &badCipher)
{
return cipher.name().contains(badCipher, Qt::CaseInsensitive);
@@ -78,6 +101,7 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent)
setProxy(QNetworkProxy::NoProxy);
QSslConfiguration sslConf {QSslConfiguration::defaultConfiguration()};
sslConf.setProtocol(QSsl::TlsV1_2OrLater);
sslConf.setCiphers(safeCipherList());
QSslConfiguration::setDefaultConfiguration(sslConf);

View File

@@ -79,10 +79,10 @@ namespace Http
struct Environment
{
QHostAddress localAddress;
quint16 localPort;
quint16 localPort = 0;
QHostAddress clientAddress;
quint16 clientPort;
quint16 clientPort = 0;
};
struct UploadedFile

View File

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

View File

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

View File

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

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