Compare commits

..

154 commits

Author SHA1 Message Date
sledgehammer999
100ee5dbe0
Bump to 5.0.4 2025-02-18 16:13:57 +02:00
sledgehammer999
310a9d8e1a
Update Changelog 2025-02-18 16:11:44 +02:00
Vladimir Golovnev
677cabcbdf
GHA CI: fix AppImage building
PR #22286.
2025-02-16 17:46:38 +03:00
sledgehammer999
b86079974c
Bump copyright year 2025-02-16 16:39:55 +02:00
sledgehammer999
ca6a89e238
Sync translations from Transifex and run lupdate 2025-02-16 16:27:34 +02:00
Vladimir Golovnev
505c1e1c0a
Backport changes to v5.0.x branch
PR #22207.
2025-02-14 13:56:49 +03:00
Vladimir Golovnev
ecde201ec5
WebAPI: Don't trim string parameters
PR #22266.
Closes #19485.
Closes #22254.
2025-02-12 09:34:37 +03:00
skomerko
730bf957a4
WebUI: Don't keep references to context menu targets
PR https://github.com/qbittorrent/qBittorrent/pull/22234.
2025-02-11 20:26:43 +03:00
Hugo Carvalho
069cd029eb
NSIS: Update Portuguese translation
PR #21632.
2025-02-11 12:19:56 +03:00
Vladimir Golovnev
375e6800e9
Remove stopped torrent from "error" tracker filter
PR #22219.
2025-01-31 15:25:23 +03:00
Vladimir Golovnev
09fb92466a
Handle Qt style options uniformly
PR #22133.
Closes #22061.
2025-01-28 21:18:19 +03:00
thalieht
69321f0e94
Hide zero and infinity values in peer list only when that setting is set to Always
PR #22205.
Closes #21998.
2025-01-27 09:47:54 +03:00
thalieht
f39e066672
Fix torrent content checkbox state under certain conditions
PR #22190.
Closes #22189.
2025-01-26 17:08:19 +03:00
Chocobo1
6a5ea93c92
Avoid memory leak on macOS
Only Mark-of-the-Web and Power Management are affected.

PR #22176.
2025-01-19 16:35:45 +08:00
Chocobo1
35dce07c63
Fix cannot remove trackers via WebAPI
The backport commit c3c7f28bad was insufficient.

Closes 22039.
PR #22071.
2024-12-29 14:40:17 +08:00
sledgehammer999
0188e11dd7
Bump to 5.0.3 2024-12-16 01:51:32 +02:00
sledgehammer999
1dc348539b
Update Changelog 2024-12-16 01:49:38 +02:00
sledgehammer999
241a0e91bf
Sync translations from Transifex and run lupdate 2024-12-16 01:49:09 +02:00
Vladimir Golovnev
68f7295500
Avoid race condition when update tracker entries
PR #21995.
2024-12-15 17:50:19 +03:00
Vladimir Golovnev
53adb7bfa8
Backport changes to v5.0.x branch
PR #21898.
2024-12-09 07:04:06 +03:00
Giacomo411
6128f6eecc
NSIS: Update Italian translation
PR #21920.
2024-12-08 12:18:07 +02:00
sledgehammer999
d156a44f8d
WebUI: Fix reloading page after login
Manual backport of PR  #21832
Original author: Evgenii Ryshkov
See commit: 1e851b3637
2024-12-08 12:15:15 +02:00
Thomas Piccirello
c3c7f28bad
WebUI: Fix removing tracker URL with '|' character
Closes #19074.
PR #21346.
2024-12-07 23:47:49 +02:00
Chocobo1
9ac14cdf9f
Don't follow symlink when creating torrents on Windows
Now on Windows, it won't follow/include .lnk files when creating torrents.
Note that libtorrent will throw errors if we force adding .lnk files.

Non-Windows OS will still follow symlinks.

Closes #13286.
PR #21944.
2024-12-07 21:19:46 +03:00
Vladimir Golovnev
b899ea8c40
Use cached current time when parse RSS feed
PR #21959.
2024-12-07 11:12:31 +03:00
Vladimir Golovnev
0d7c367332
Avoid redundant requests of announce entries from libtorrent
PR #21949.
2024-12-06 20:00:27 +03:00
wavygecko
22826499d5
Don't add duplicate episodes to previously matched
PR #21917.
2024-11-28 15:12:17 +03:00
Vladimir Golovnev
dbfd830b56
Avoid repeatedly creating the same QDateTime values
PR #21904.
2024-11-26 15:11:07 +03:00
Vladimir Golovnev
ad3348b95f
Fix incorrect SQL column definition
PR #21874.
2024-11-23 11:25:40 +03:00
Bartu Özen
44b08fcb74
WebAPI: Fix incorrect key in torrent creator
PR #21879.
2024-11-23 11:22:04 +03:00
Vladimir Golovnev
71b752baf3
Discard obsolete "state update" events after torrent is reloaded
PR #21873.
Closes #21827.
2024-11-23 11:21:17 +03:00
sledgehammer999
15b6091261
Bump to 5.0.2 2024-11-17 23:25:16 +02:00
sledgehammer999
abe457389d
Update Changelog 2024-11-17 23:22:51 +02:00
sledgehammer999
abce4cd1bc
Sync translations from Transifex and run lupdate 2024-11-17 23:22:09 +02:00
3gf8jv4dv
2bfb336905
NSIS: Update Traditional Chinese translation
PR #21694.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-11-17 22:45:24 +02:00
3gf8jv4dv
2dee65fa52
NSIS: Update Simplified Chinese translation
PR #21693.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-11-17 22:45:23 +02:00
Ikko Eltociear Ashimine
423b3ed9bf
NSIS: update luxembourgish
PR #21456.
2024-11-17 22:45:18 +02:00
Vladimir Golovnev
3454f064f0
Backport changes in v5.0.x branch
PR #21698.
2024-11-17 10:50:54 +03:00
Chocobo1
ac9ca4f452
Don't apply Mark-of-the-Web on existing files
`TorrentImpl::isDownloading()` was excessively broad which included unexpected events for the
case here. So use the underlying state directly.

Closes #21788.
PR #21836.
2024-11-16 16:04:51 +03:00
Chocobo1
09899a7d0d
Avoid reapplying Mark-of-the-Web when it already exists
Also use scope guards to handle resources.

Related #21788.
PR #21806.
2024-11-14 10:14:52 +03:00
Hanabishi
9ab3c573dc
WebUI: fix color scheme for iframes
A backport of #21750 as a follow up to #21748.

Original PR #21750.
PR #21810.
2024-11-11 19:03:40 +08:00
Vladimir Golovnev
993eb25323
Preserve initial torrent progress while checking resume data
PR #21784.
2024-11-10 12:51:17 +03:00
sledgehammer999
1e27e6504e
Merge pull request #21748 from sledgehammer999/backport_webui_color_switcher
WebUI: Add color scheme switcher (v5_0_x)

Bacport of #21613
2024-11-09 12:15:47 +02:00
Vladimir Golovnev
330dce6aa2
Correctly handle "torrent finished" events
PR #21786.
Closes #21699.
2024-11-08 11:47:37 +03:00
Vladimir Golovnev
39b965af48
Check real palette darkness to detect "dark theme"
`QStyleHints::colorScheme()` returns chosen color scheme even if current style doesn't support it and uses different palette.

PR #21771.
2024-11-08 11:47:26 +03:00
Vladimir Golovnev
5e105b0348
Optimize checking for outdated tracker endpoints
PR #21768.
2024-11-07 09:42:53 +03:00
Vladimir Golovnev
f2b2a2b034
Optimize converting TCP endpoints to strings
There may be quite a few endpoint names (one for each available network card), and they usually remain unchanged throughout the session, while previously producing such names was performed every time they were accessed. Now they are retrieved from the cache.

PR #21770.
2024-11-07 09:42:43 +03:00
Vladimir Golovnev
10499dffe9
Optimize conversion of time points from libtorrent to Qt clocks
Obtain current date time of Qt and libtorrent clocks only once
for processing entire current libtorrent alerts bunch.

PR #21764.
2024-11-05 16:46:28 +03:00
Vladimir Golovnev
eea01b94a3
Reset tracker entries when pause the session
PR #21738.
2024-11-04 16:28:04 +03:00
Vladimir Golovnev
374951f6f2
Handle Qt style names in a case insensitive way
PR #21720.
Closes #21716.
2024-11-03 10:06:30 +03:00
sledgehammer999
6d6f9bc619
Reorder code to match UI 2024-11-02 17:52:13 +02:00
sledgehammer999
84ee620fdc
Webui: Add color scheme switcher
Closes #21600
2024-11-02 17:32:55 +02:00
Vladimir Golovnev
6079b25419
Fix .torrent file could not be deleted when torrent is canceled
PR #21735.
Closes #21723.
2024-11-02 16:42:11 +03:00
Vladimir Golovnev
fe24bc825b
Remove trackers from previous category when moved to new one
PR #21717.
Closes #21637.
2024-11-02 16:42:07 +03:00
sledgehammer999
94136262a8
Bump to 5.0.1 2024-10-28 18:12:20 +02:00
sledgehammer999
f52947e27e
Update Changelog 2024-10-28 18:09:38 +02:00
sledgehammer999
315e88aee9
Sync translations from Transifex and run lupdate 2024-10-28 18:08:55 +02:00
Vladimir Golovnev
565c6d843a
Correctly delete the moved search tab
PR #21687.
Closes #21675.
2024-10-28 09:45:23 +03:00
Vladimir Golovnev
9104351c89
Backport changes to v5.0.x branch
PR #21679.
2024-10-24 12:55:50 +03:00
sledgehammer999
e58b0a65d2
Merge pull request #21663 from sledgehammer999/backport_dont_ignore_ssl_errors
Don't ignore SSL errors
2024-10-24 11:02:31 +03:00
Chocobo1
878d829904
Fix button state for SSL certificate check
A copy paste error was introduced in PR #20338.

PR #21659.
2024-10-23 08:56:55 +03:00
sledgehammer999
063f77bc6c
Allow to use Qt's default QStyle
Relevant prior PR #21553

PR #21605.
2024-10-21 20:05:54 +03:00
sledgehammer999
2a4077414f
Reorder code to match UI 2024-10-21 19:53:28 +03:00
sledgehammer999
2a44253802
Don't ignore SSL errors 2024-10-21 19:45:32 +03:00
Chocobo1
4712eba0dc
Don't change combobox index after selection
Also keep the list sorted.

PR #21599.
2024-10-21 15:49:18 +03:00
Hanabishi
983b7814aa
Add "Simple pread/pwrite" disk IO type
PR #21300.
2024-10-21 15:47:28 +03:00
Vladimir Golovnev
e082a21751
Improve color scheme change detection
* Fix pieces bars won't correctly detect color scheme change with Qt 6.8.
* Update RSS article content view on color scheme changed.

PR #21625.
Closes #21327.
2024-10-21 09:51:37 +03:00
dyseg
7dd1d1bac8
Free resources allocated by web session once it is destructed
PR #21618.
Closes #20873.
2024-10-21 09:48:24 +03:00
Chocobo1
49f57b1049
WebUI: fix 'rename files' dialog cannot be opened more than once
Added an IIFE around the whole script to suppress variable redeclaration errors.

Closes #21614.
Original PR #21620.
PR #21621.
2024-10-20 16:07:13 +08:00
Vladimir Golovnev
fbf68a0649
Correctly apply filename filter when !qB extension is enabled
PR #21628.
Closes #21624.
2024-10-19 13:39:12 +03:00
xavier2k6
39229dc06a
Sync flag icons with upstream
* Release: 7.2.3
* Contains bug fixes & additional flags

PR #21220.
2024-10-14 11:54:52 +03:00
Vladimir Golovnev
bb314e1555
Correctly handle "torrent finished after move" event
PR #21596.
Closes #21576.
2024-10-14 11:52:56 +03:00
Vladimir Golovnev
a3a8b15828
Always notify user about duplicate torrent
PR #21480.
Closes #21475.
2024-10-14 11:52:47 +03:00
Vladimir Golovnev
b579afe1aa
Allow to choose Qt style
PR #21553.
2024-10-11 16:09:59 +03:00
stalkerok
93096dba56
Disable the ability to create torrents with a piece size of 256MiB
Disabling will reduce the number of users experiencing this issue.
https://github.com/qbittorrent/qBittorrent/issues/21011

PR #21295.
2024-10-10 15:40:05 +03:00
Vladimir Golovnev
6379c33964
Disable "Move to trash" option by default
PR #21528.
2024-10-10 14:16:37 +03:00
Chocobo1
84372de675
Import correct libraries
Fixes "plugin not supported" errors with python 3.8.

PR #21539.
2024-10-10 16:29:51 +08:00
skomerko
403b7c7c35
WebUI: Use proper text color to highlight items in all filter lists
Previously, text color of selected filter items was not applied correctly in all situations, making them difficult to read.
This improves existing styles so that text is always correctly distinguished from the background.

This fixes issue from second post in https://github.com/qbittorrent/qBittorrent/issues/21426

PR #21507.
2024-10-07 22:13:50 +08:00
skomerko
b2fab43865
WebUI: Don't load Tabs & dynamicTable stylesheets in Properties panel
This removes duplicate stylesheet imports that caused the transfer list to be completely collapsed in Chrome-based browsers.

Closes #21426.
PR #21506.
2024-10-07 22:03:54 +08:00
Vladimir Golovnev
387821267f
Don't try to apply Mark-of-the-Web to nonexistent files
Trying to apply it to a nonexistent file is unacceptable, as it may unexpectedly create such a file.

PR #21488.
Closes #21440.
2024-10-05 12:28:09 +03:00
Vladimir Golovnev
dd7ef8e934
WebUI: Fix incorrect row ID
Incorrect row ID prevented the "Torrent content removing mode" option from being displayed on some platforms.

PR #21481.
2024-10-04 14:11:45 +03:00
sledgehammer999
cce295faeb
Bump to 5.0.0 2024-09-29 20:53:45 +03:00
sledgehammer999
db5479ea01
Update Changelog 2024-09-29 20:49:43 +03:00
sledgehammer999
e1216c4c9a
Sync translations from Transifex and run lupdate 2024-09-29 20:36:58 +03:00
sledgehammer999
f4a0868426
Make Program Updater choose the same build for download
We're probably stuck offering the duo of RC_1_2 and RC_2_0 for some
time in the future. So hardcode the choices and make the Program Updater
choose the variant the user currently uses.
2024-09-29 20:28:10 +03:00
sledgehammer999
59a5fcf7d0
Sync translations from Transifex and run lupdate 2024-09-13 11:10:38 +03:00
Vladimir Golovnev
f9a2b02a8d
Backport changes to v5.0.x branch
PR #21241.
2024-09-12 08:42:52 +03:00
skomerko
04f6a565f3
WebUI: Provide 'Merge trackers to existing torrent' option
PR #21302.
2024-09-11 19:18:17 +03:00
Vladimir Golovnev
3e96048ee4
Apply "merge trackers" logic regardless of way the torrent is added
PR #21299.
2024-09-07 09:13:19 +04:00
Prince Gupta
d4ccf3001c
Fix highlighted piece color
PR #20971.
2024-09-02 16:17:57 +03:00
skomerko
64506f16bd
WebUI: Provide 'Use Category paths in Manual Mode' option
PR #21223.
2024-08-26 13:10:05 +03:00
sledgehammer999
24a7a835af
Create new resources for this branch for Transifex 2024-08-26 01:11:05 +03:00
sledgehammer999
93b9bf9552
Sync translations from Transifex and run lupdate 2024-08-26 01:05:21 +03:00
Vladimir Golovnev
f4125601de
Refresh search results colors once color scheme is changed
* Refresh search results colors once color scheme is changed
* Improve color of visited search result items

PR #21189.
Closes #21187.
2024-08-21 15:12:07 +03:00
sledgehammer999
2d67729617
Bump to v5.0.0rc1 2024-08-18 23:21:21 +03:00
sledgehammer999
878ebbed41
Update Changelog 2024-08-18 23:17:25 +03:00
Vladimir Golovnev
c61c3d7cd8
Backport changes to v5.0.x branch
PR #21164.
2024-08-16 07:17:21 +03:00
skomerko
978fbbdc0d
WebUI: Always create generic filter items
PR #21188.
2024-08-15 20:37:19 +03:00
stalkerok
63689cf763
Add a flag about the connection peers are using NAT hole punching
PR #21052.
2024-08-15 20:33:45 +03:00
thalieht
cebc72d3cf
WebUI: Add missing columns in transfer list
* Incomplete Save Path
* Info Hash v1
* Info Hash v2

PR #21158.
2024-08-15 20:32:40 +03:00
Vladimir Golovnev
a67bd271c6
Refresh pieces bar colors once color scheme is changed
PR #21183.
Closes #21155.
2024-08-13 09:37:47 +03:00
skomerko
a8cffbb205
WebUI: Clear trackerList on full update
Like other similar data structures, trackerList also need to be cleared in the event of a full sync update.

PR #21148.
2024-08-11 14:20:45 +03:00
Vladimir Golovnev
7dfb0110d4
Fix Incomplete Save Path cannot be changed for torrents without metadata
PR #21152.
Closes #21140.
2024-08-08 08:22:54 +03:00
Vladimir Golovnev
3ad8fcbdd2
Hide zero status filters when torrents removed
PR #21150.
Closes #21146.
2024-08-08 08:22:51 +03:00
Vladimir Golovnev
195eae5f3d
Backport changes to v5.0.x branch
PR #20996.
2024-08-02 21:22:49 +03:00
Hanabishi
920ae26f7b
WebUI: Fix Torrent Management Mode selector
PR #21053.
2024-07-15 17:40:17 +03:00
David Newhall
09ed0d6b66
WebAPI: Add root_path to torrent/info result
PR #21066.
Closes #21057.
2024-07-15 08:52:52 +03:00
Vladimir Golovnev
4f0cc8aa11
Fix incorrect sorting by "private" column
PR #21041.
2024-07-15 08:52:42 +03:00
ManiMatter
4d490c84e7
Add ability to display torrent "privateness" in UI
PR #20951.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
Co-authored-by: Vladimir Golovnev <glassez@yandex.ru>
Co-authored-by: thalieht <thalieht@users.noreply.github.com>
2024-07-15 08:52:23 +03:00
Vladimir Golovnev
96607ce874
Prevent incorrect size from being used for creating array
PR #21050.
2024-07-12 08:51:08 +03:00
Vladimir Golovnev
418edc7471
Apply bulk changes to correct content widget items
PR #21006.
Closes #21001.
2024-07-08 16:51:33 +03:00
Vladimir Golovnev
bd01b7c4df
WebUI: Correctly apply changed "save path" of RSS rules
PR #21030.
Closes #20141.
2024-07-08 10:18:02 +03:00
Vladimir Golovnev
b0ac763048
Show scroll bar in Torrent Tags dialog
PR #21026.
Closes #21022.
2024-07-07 16:10:07 +03:00
Vladimir Golovnev
127d2d6f0b
Fix handling of tags containing '&' character
PR #21024.
Closes #20773.
2024-07-07 16:10:05 +03:00
Vladimir Golovnev
4149609e78
Allow to move content files to Trash instead of deleting them
PR #20252.
2024-07-07 16:09:48 +03:00
Vladimir Golovnev
78c549f83e
Use custom storage when reloading torrent
PR #20998.
2024-07-07 16:07:22 +03:00
Thomas Piccirello
a3a53e2e0e
WebUI: Fix preference name conflict
PR #20990.
2024-07-07 16:06:55 +03:00
Vladimir Golovnev
5aaa43e01d
Restore ability to use server-side translation by custom WebUI
PR #20968.
2024-06-29 21:59:22 +03:00
Chocobo1
86745d7b07
GHA CI: use static versions of AppImage builder
It does not affect the produced artifacts. The only difference is the
tool itself won't depend on some specific OS image or library version.

PR #20983.
2024-06-25 21:13:20 +03:00
Thomas Piccirello
210650a5ee
Use enabled search plugins by default in WebUI
PR #20969.
Closes #20558.
2024-06-25 21:13:20 +03:00
Chocobo1
fe93b6d0d8
Use proper casting
Previously `m_shutdownTimeout * 1000` was calculated in `int` and now it
is `qint64`.

PR #20982.
2024-06-25 21:13:19 +03:00
Chocobo1
e8b585acd8
Allow numeric types
The canonical type for `size_string` is `str`. However numeric types are also accepted in order
to accommodate poorly written plugins.

PR #20976.
2024-06-25 21:13:19 +03:00
vikas_c
cea20141a9
Show download progress for folders with zero byte size as 100 instead of 0
Fixes the download progress calculation for folders with zero size.
Previously, the progress would be Zero. Now, folders with zero size
show 100% progress.

PR #20567.
2024-06-25 21:13:19 +03:00
Chocobo1
0f5a27ed50
Improve connection handling
1. Previously unhandled connections will stay in pending state. It won't
be closed until timeout happened. This may lead to wasting system
resources. Now the (over-limit) connection is actively rejected.

2. When out-of-memory occurs here, reject the new connection instead of
throwing exception and crash.

3. Also clean up some unused bits.

PR #20961.
2024-06-25 21:13:18 +03:00
Vladimir Golovnev
c2cf898ccd
Allow to use regular expression to filter torrent content
PR #20944.
Closes #19934.
2024-06-25 21:13:18 +03:00
Chocobo1
5e5aa8a563
Add type annotations
A few code are revised because the type checker (mypy) doesn't allow
changing types on a variable.

PR #20935.
2024-06-25 21:13:18 +03:00
ManiMatter
12a4c3fda2
WebAPI: Add "private" filter for 'info' endpoint
PR #20833.

---------

Co-authored-by: Vladimir Golovnev <glassez@yandex.ru>
Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-06-25 21:13:17 +03:00
Vladimir Golovnev
5f50b701d2
Don't use custom "file icon provider" on Windows
PR #20936.
Closes #20908.
2024-06-25 21:13:17 +03:00
Chocobo1
9f20d9c3aa
Revise Protocol column
Add "BT" (BitTorrent) to avoid confusion about which protocol it is referring to.
Also its value doesn't need to be translated.

PR #20897.
2024-06-25 21:13:17 +03:00
Vladimir Golovnev
05e3130baa
Apply share limits when torrent downloading is finished
PR #20917.
Closes #20874.
2024-06-25 21:13:17 +03:00
Vladimir Golovnev
683492648f
Apply filename filter to subfolder names as well
PR #20902.
Closes #14480.
2024-06-25 21:13:17 +03:00
Chocobo1
2f2e158877
WebUI: unify comment format 2024-06-25 21:13:16 +03:00
BurningMop
e60e96cb0e
Add optional headers to search request
PR #20923.
2024-06-25 21:13:16 +03:00
Chocobo1
5f31208bf1
Add required manifest field
https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#assemblyidentity

PR #20907.
2024-06-25 21:13:16 +03:00
Chocobo1
fa58e58e70
WebUI: unify curly bracket usage 2024-06-25 21:13:16 +03:00
dependabot[bot]
671943a9a6
GHA CI: Bump Github Actions versions
PR #20913.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-06-25 21:13:16 +03:00
Chocobo1
8bad80bcdd
Avoid redundant lookup
PR #20890.
2024-06-25 21:13:15 +03:00
thalieht
c44e300507
Increase default height of 'Share ratio limit' dialog in WebUI
PR #20866.
2024-06-25 21:13:15 +03:00
Chocobo1
318a677e8f
Avoid creating redundant temporary file list
PR #20863.
2024-06-25 21:13:15 +03:00
Chocobo1
0246df790a
Use Qt built-in methods 2024-06-25 21:13:15 +03:00
Chocobo1
782fbc1425
Use simpler conversion
The cookie value can only contain ASCII characters.
2024-06-25 21:13:15 +03:00
Chocobo1
7deccd5592
WebUI: add missing break 2024-06-25 21:13:14 +03:00
Chocobo1
4a36fe7278
WebUI: don't auto infer radix parameter 2024-06-25 21:13:14 +03:00
Chocobo1
1c5af96ad8
WebUI: simplify code 2024-06-25 21:13:14 +03:00
Chocobo1
3bb47a5410
WebUI: iterate over own properties only 2024-06-25 21:13:14 +03:00
Chocobo1
d7abeb4bf0
WebUI: use assignment operator shorthand 2024-06-25 21:13:14 +03:00
Chocobo1
a19d623ead
WebUI: prefer arrow function in callbacks 2024-06-25 21:13:13 +03:00
Chocobo1
1ef21bc2b7
WebUI: enforce usage of const whenever possible 2024-06-25 21:13:13 +03:00
Chocobo1
4687b4e8e4
WebUI: enforce string quotes coding style 2024-06-25 19:33:20 +03:00
Thomas Piccirello
d2e5163861
WebUI: Restore previously used tab on load
This PR restores the users previously used tab (Transfer, Search, RSS, etc.) when the WebUI is reloaded.

PR #20705.
2024-06-25 19:30:10 +03:00
sledgehammer999
8a15ea8026
Merge pull request #20963 from sledgehammer999/revert_webui_i18n
Revert i18next
2024-06-25 03:02:24 +03:00
sledgehammer999
2b99554813
Update WebUI translation files 2024-06-17 02:07:15 +03:00
sledgehammer999
e6638f9c19
Revert "Use client side translation for public login page"
This reverts commit ac91c1348b.
2024-06-16 23:31:19 +03:00
sledgehammer999
ec6eac2ba1
Revert "Avoid leaking user locale preference to the web"
This reverts commit 66c34ddb6e.
2024-06-16 23:14:21 +03:00
513 changed files with 125775 additions and 185254 deletions

2
.github/FUNDING.yml vendored
View file

@ -1 +1 @@
custom: "https://www.qbittorrent.org/donate"
custom: "https://www.qbittorrent.org/donate.php"

View file

@ -7,17 +7,19 @@ body:
#### ADVISORY
"We do not support any versions older than the current release series"
"We do not support any 3rd party/forked versions e.g. `portableapps`/`Enhanced Edition` etc."
"We do not support any 3rd party/forked versions e.g. `portableapps`/`Enhanced Edition`etc."
"Please post all details in **English**."
#### Prerequisites before submitting an issue!
- Read the issue reporting section in the **[contributing guidelines](https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md)**, to know how to submit a good bug report with the required information.
- Verify that the issue is not fixed and is reproducible in the **[latest official qBittorrent version](https://www.qbittorrent.org/download.php).**
- (Optional, but recommended) Verify that the issue is not fixed and is reproducible in the latest CI (**[macOS](https://github.com/qbittorrent/qBittorrent/actions/workflows/ci_macos.yaml?query=branch%3Amaster+event%3Apush)** / **[Ubuntu](https://github.com/qbittorrent/qBittorrent/actions/workflows/ci_ubuntu.yaml?query=branch%3Amaster+event%3Apush)** / **[Windows](https://github.com/qbittorrent/qBittorrent/actions/workflows/ci_windows.yaml?query=branch%3Amaster+event%3Apush)**) builds.
- Perform a **[search of the issue tracker (including closed ones)](https://github.com/qbittorrent/qBittorrent/issues?q=is%3Aissue+is%3Aopen+-label%3A%22Feature+request%22)** to avoid posting a duplicate.
- (Optional, but recommended) Verify that the issue is not fixed and is reproducible in the latest CI (currently only on **[Windows](https://github.com/qbittorrent/qBittorrent/actions/workflows/ci_windows.yaml?query=branch%3Amaster+event%3Apush)**) builds.
- Check the **[frequent/common issues list](https://github.com/qbittorrent/qBittorrent/projects/2)** and perform a **[search of the issue tracker (including closed ones)](https://github.com/qbittorrent/qBittorrent/issues)** to avoid posting a duplicate.
- Make sure this is not a support request or question, both of which are better suited for either the **[discussions section](https://github.com/qbittorrent/qBittorrent/discussions)**, **[forum](https://qbforums.shiki.hu/)**, or **[subreddit](https://www.reddit.com/r/qBittorrent/)**.
- Verify that the **[wiki](https://github.com/qbittorrent/qBittorrent/wiki)** did not contain a suitable solution either.
- If relevant to issue/when asked, the qBittorrent preferences file, qBittorrent.log & watched_folders.json (if using "Watched Folders" feature) must be provided.
See **[Where does qBittorrent save its settings?](https://github.com/qbittorrent/qBittorrent/wiki/Frequently-Asked-Questions#Where_does_qBittorrent_save_its_settings)**
- type: textarea
attributes:
@ -26,10 +28,10 @@ body:
Qt and libtorrent-rasterbar versions are required when: 1. You are using linux. 2. You are not using an official build downloaded from our website.
Example of preferred formatting:
qBittorrent: 4.6.6 x64
Operating system: Windows 10 Pro x64 (22H2) 10.0.19045
Qt: 6.4.3
libtorrent-rasterbar: 1.2.19
qBittorrent: 4.3.7 x64
Operating system: Windows 10 Pro 21H1/2009 x64
Qt: 5.15.2
libtorrent-rasterbar: 1.2.14
placeholder: |
qBittorrent:
Operating system:
@ -71,4 +73,4 @@ body:
See **[Where does qBittorrent save its settings?](https://github.com/qbittorrent/qBittorrent/wiki/Frequently-Asked-Questions#Where_does_qBittorrent_save_its_settings)**
#### Note: It's the user's responsibility to redact any sensitive information
validations:
required: true
required: false

View file

@ -12,15 +12,11 @@ jobs:
ci:
name: Check
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup python
- name: Install tools
uses: actions/setup-python@v5
with:
python-version: "*"
@ -36,7 +32,7 @@ jobs:
curl \
-L \
-o "${{ runner.temp }}/pandoc.tar.gz" \
"https://github.com/jgm/pandoc/releases/download/3.6/pandoc-3.6-linux-amd64.tar.gz"
"https://github.com/jgm/pandoc/releases/download/3.1.7/pandoc-3.1.7-linux-amd64.tar.gz"
tar -xf "${{ runner.temp }}/pandoc.tar.gz" -C "${{ github.workspace }}/.."
mv "${{ github.workspace }}/.."/pandoc-* "${{ env.pandoc_path }}"
# run pandoc
@ -46,26 +42,3 @@ jobs:
done
# check diff, ignore "Automatically generated by ..." part
git diff -I '\.\\".*' --exit-code
- name: Check GitHub Actions workflow
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pip install zizmor
IGNORE_RULEID='(.ruleId != "template-injection")
and (.ruleId != "unpinned-uses")'
IGNORE_ID='(.id != "template-injection")
and (.id != "unpinned-uses")'
zizmor \
--format sarif \
--pedantic \
./ \
| jq "(.runs[].results |= map(select($IGNORE_RULEID)))
| (.runs[].tool.driver.rules |= map(select($IGNORE_ID)))" \
> "${{ runner.temp }}/zizmor_results.sarif"
- name: Upload zizmor results
uses: github/codeql-action/upload-sarif@v3
with:
category: zizmor
sarif_file: "${{ runner.temp }}/zizmor_results.sarif"

View file

@ -2,7 +2,8 @@ name: CI - macOS
on: [pull_request, push]
permissions: {}
permissions:
actions: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -12,15 +13,13 @@ jobs:
ci:
name: Build
runs-on: macos-latest
permissions:
actions: write
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.11", "1.2.20"]
libt_version: ["2.0.10", "1.2.19"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["6.9.0"]
qt_version: ["6.7.0"]
env:
boost_path: "${{ github.workspace }}/../boost"
@ -29,8 +28,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Install dependencies
uses: Wandalen/wretry.action@v3
@ -52,15 +49,15 @@ jobs:
store_cache: ${{ github.ref == 'refs/heads/master' }}
update_packager_index: false
ccache_options: |
max_size=1G
max_size=2G
- name: Install boost
env:
BOOST_MAJOR_VERSION: "1"
BOOST_MINOR_VERSION: "86"
BOOST_MINOR_VERSION: "85"
BOOST_PATCH_VERSION: "0"
run: |
boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
boost_url="https://boostorg.jfrog.io/artifactory/main/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
set +e
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
@ -70,9 +67,6 @@ jobs:
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
fi
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
cd "${{ env.boost_path }}"
./bootstrap.sh
./b2 stage --stagedir=./ --with-headers
- name: Install Qt
uses: jurplel/install-qt-action@v4
@ -96,23 +90,23 @@ jobs:
-G "Ninja" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=20 \
-DCMAKE_CXX_STANDARD=17 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-Ddeprecated-functions=OFF
cmake --build build
sudo cmake --install build
- name: Build qBittorrent
run: |
CXXFLAGS="$CXXFLAGS -DQT_FORCE_ASSERTS -Werror -Wno-error=deprecated-declarations" \
CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \
LDFLAGS="$LDFLAGS -gz" \
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-DTESTING=ON \
-DVERBOSE_CONFIGURE=ON \
-D${{ matrix.qbt_gui }}

View file

@ -16,8 +16,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup python (auxiliary scripts)
uses: actions/setup-python@v5
@ -36,7 +34,7 @@ jobs:
- name: Lint code (auxiliary scripts)
run: |
pyflakes $PY_FILES
bandit --skip B101,B314,B405 $PY_FILES
bandit --skip B314,B405 $PY_FILES
- name: Format code (auxiliary scripts)
run: |
@ -52,7 +50,7 @@ jobs:
- name: Setup python (search engine)
uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: '3.7'
- name: Install tools (search engine)
run: pip install bandit mypy pycodestyle pyflakes pyright
@ -63,7 +61,7 @@ jobs:
echo $PY_FILES
echo "PY_FILES=$PY_FILES" >> "$GITHUB_ENV"
- name: Check typings (search engine)
- name: Check typings (search engine)
run: |
MYPYPATH="src/searchengine/nova3" \
mypy \

View file

@ -2,7 +2,9 @@ name: CI - Ubuntu
on: [pull_request, push]
permissions: {}
permissions:
actions: write
security-events: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -12,27 +14,22 @@ jobs:
ci:
name: Build
runs-on: ubuntu-latest
permissions:
actions: write
security-events: write
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.11", "1.2.20"]
libt_version: ["2.0.10", "1.2.19"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["6.5.2"]
env:
boost_path: "${{ github.workspace }}/../boost"
harden_flags: "-D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS"
harden_flags: "-D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS"
libtorrent_path: "${{ github.workspace }}/../libtorrent"
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Install dependencies
run: |
@ -47,15 +44,15 @@ jobs:
store_cache: ${{ github.ref == 'refs/heads/master' }}
update_packager_index: false
ccache_options: |
max_size=1G
max_size=2G
- name: Install boost
env:
BOOST_MAJOR_VERSION: "1"
BOOST_MINOR_VERSION: "77"
BOOST_MINOR_VERSION: "76"
BOOST_PATCH_VERSION: "0"
run: |
boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
boost_url="https://boostorg.jfrog.io/artifactory/main/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
set +e
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
@ -65,9 +62,6 @@ jobs:
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
fi
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
cd "${{ env.boost_path }}"
./bootstrap.sh
./b2 stage --stagedir=./ --with-headers
- name: Install Qt
uses: jurplel/install-qt-action@v4
@ -91,9 +85,8 @@ jobs:
-G "Ninja" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=20 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-Ddeprecated-functions=OFF
cmake --build build
sudo cmake --install build
@ -108,14 +101,14 @@ jobs:
- name: Build qBittorrent
run: |
CXXFLAGS="$CXXFLAGS ${{ env.harden_flags }} -DQT_FORCE_ASSERTS -Werror" \
CXXFLAGS="$CXXFLAGS ${{ env.harden_flags }} -Werror" \
LDFLAGS="$LDFLAGS -gz" \
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DTESTING=ON \
-DVERBOSE_CONFIGURE=ON \

View file

@ -2,7 +2,8 @@ name: CI - WebUI
on: [pull_request, push]
permissions: {}
permissions:
security-events: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -12,8 +13,6 @@ jobs:
ci:
name: Check
runs-on: ubuntu-latest
permissions:
security-events: write
defaults:
run:
@ -22,8 +21,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup nodejs
uses: actions/setup-node@v4
@ -31,15 +28,7 @@ jobs:
node-version: 'lts/*'
- name: Install tools
run: |
npm install
npm ls
echo "::group::npm ls --all"
npm ls --all
echo "::endgroup::"
- name: Run tests
run: npm test
run: npm install
- name: Lint code
run: npm run lint

View file

@ -2,7 +2,8 @@ name: CI - Windows
on: [pull_request, push]
permissions: {}
permissions:
actions: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -12,13 +13,11 @@ jobs:
ci:
name: Build
runs-on: windows-latest
permissions:
actions: write
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.11", "1.2.20"]
libt_version: ["2.0.10", "1.2.19"]
env:
boost_path: "${{ github.workspace }}/../boost"
@ -28,8 +27,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup devcmd
uses: ilammy/msvc-dev-cmd@v1
@ -67,7 +64,6 @@ jobs:
"set(VCPKG_BUILD_TYPE release)")
# clear buildtrees after each package installation to reduce disk space requirements
$packages = `
"boost-build:x64-windows-static-md-release",
"openssl:x64-windows-static-md-release",
"zlib:x64-windows-static-md-release"
${{ env.vcpkg_path }}/vcpkg.exe upgrade `
@ -82,10 +78,10 @@ jobs:
- name: Install boost
env:
BOOST_MAJOR_VERSION: "1"
BOOST_MINOR_VERSION: "86"
BOOST_MINOR_VERSION: "85"
BOOST_PATCH_VERSION: "0"
run: |
$boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
$boost_url="https://boostorg.jfrog.io/artifactory/main/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
$boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."
@ -95,19 +91,11 @@ jobs:
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."
}
move "${{ github.workspace }}/../boost_*" "${{ env.boost_path }}"
cd "${{ env.boost_path }}"
#.\bootstrap.bat
${{ env.vcpkg_path }}/installed/x64-windows-static-md-release/tools/boost-build/b2.exe `
stage `
toolset=msvc `
--stagedir=.\ `
--with-headers
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
version: "6.9.0"
arch: win64_msvc2022_64
version: "6.7.3"
archives: qtbase qtsvg qttools
cache: true
@ -126,11 +114,10 @@ jobs:
-B build `
-G "Ninja" `
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
-DCMAKE_CXX_STANDARD=20 `
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
-DCMAKE_INSTALL_PREFIX="${{ env.libtorrent_path }}/install" `
-DCMAKE_TOOLCHAIN_FILE="${{ env.vcpkg_path }}/scripts/buildsystems/vcpkg.cmake" `
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" `
-DBOOST_ROOT="${{ env.boost_path }}" `
-DBUILD_SHARED_LIBS=OFF `
-Ddeprecated-functions=OFF `
-Dstatic_runtime=OFF `
@ -140,14 +127,14 @@ jobs:
- name: Build qBittorrent
run: |
$env:CXXFLAGS+="/DQT_FORCE_ASSERTS /WX"
$env:CXXFLAGS+=" /WX"
cmake `
-B build `
-G "Ninja" `
-DCMAKE_BUILD_TYPE=RelWithDebInfo `
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON `
-DCMAKE_TOOLCHAIN_FILE="${{ env.vcpkg_path }}/scripts/buildsystems/vcpkg.cmake" `
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" `
-DBOOST_ROOT="${{ env.boost_path }}" `
-DLibtorrentRasterbar_DIR="${{ env.libtorrent_path }}/install/lib/cmake/LibtorrentRasterbar" `
-DMSVC_RUNTIME_DYNAMIC=ON `
-DTESTING=ON `

View file

@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
libt_version: ["2.0.11"]
libt_version: ["2.0.10"]
qbt_gui: ["GUI=ON"]
qt_version: ["6.5.2"]
@ -26,8 +26,6 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Install dependencies
run: |
@ -39,10 +37,10 @@ jobs:
- name: Install boost
env:
BOOST_MAJOR_VERSION: "1"
BOOST_MINOR_VERSION: "86"
BOOST_MINOR_VERSION: "85"
BOOST_PATCH_VERSION: "0"
run: |
boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
boost_url="https://boostorg.jfrog.io/artifactory/main/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
set +e
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
@ -52,9 +50,6 @@ jobs:
tar -xf "${{ runner.temp }}/boost.tar.gz" -C "${{ github.workspace }}/.."; _exitCode="$?"
fi
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
cd "${{ env.boost_path }}"
./bootstrap.sh
./b2 stage --stagedir=./ --with-headers
- name: Install Qt
uses: jurplel/install-qt-action@v4
@ -76,8 +71,7 @@ jobs:
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=20 \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-Ddeprecated-functions=OFF
cmake --build build
sudo cmake --install build
@ -101,7 +95,7 @@ jobs:
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DBOOST_ROOT="${{ env.boost_path }}" \
-DVERBOSE_CONFIGURE=ON \
-D${{ matrix.qbt_gui }}
PATH="${{ env.coverity_path }}/bin:$PATH" \

View file

@ -16,5 +16,3 @@ ths = "ths"
[default.extend-words]
BA = "BA"
helo = "helo"
Pn = "Pn"
UIU = "UIU"

View file

@ -1,95 +0,0 @@
#!/usr/bin/env python3
# A pre-commit hook for checking items order in grid layouts
# Copyright (C) 2024 Mike Tzou (Chocobo1)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give permission to
# link this program with the OpenSSL project's "OpenSSL" library (or with
# modified versions of it that use the same license as the "OpenSSL" library),
# and distribute the linked executables. You must obey the GNU General Public
# License in all respects for all of the code used other than "OpenSSL". If you
# modify file(s), you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete this
# exception statement from your version.
from collections.abc import Callable, Sequence
from typing import Optional
import argparse
import re
import xml.etree.ElementTree as ElementTree
import sys
def traversePostOrder(root: ElementTree.Element, visitFunc: Callable[[ElementTree.Element], None]) -> None:
stack = [(root, False)]
while len(stack) > 0:
(element, visit) = stack.pop()
if visit:
visitFunc(element)
else:
stack.append((element, True))
stack.extend((child, False) for child in reversed(element))
def modifyElement(element: ElementTree.Element) -> None:
def getSortKey(e: ElementTree.Element) -> tuple[int, int]:
if e.tag == 'item':
return (int(e.attrib['row']), int(e.attrib['column']))
return (-1, -1) # don't care
if element.tag == 'layout' and element.attrib['class'] == 'QGridLayout' and len(element) > 0:
element[:] = sorted(element, key=getSortKey)
# workaround_2a: ElementTree will unescape `&quot;` and we need to escape it back
if element.tag == 'string' and element.text is not None:
element.text = element.text.replace('"', '&quot;')
def main(argv: Optional[Sequence[str]] = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to check')
args = parser.parse_args(argv)
for filename in args.filenames:
with open(filename, 'r+') as f:
orig = f.read()
root = ElementTree.fromstring(orig)
traversePostOrder(root, modifyElement)
ElementTree.indent(root, ' ')
# workaround_1: cannot use `xml_declaration=True` since it uses single quotes instead of Qt preferred double quotes
ret = f'<?xml version="1.0" encoding="UTF-8"?>\n{ElementTree.tostring(root, 'unicode')}\n'
# workaround_2b: ElementTree will turn `&quot;` into `&amp;quot;`, so revert it back
ret = ret.replace('&amp;quot;', '&quot;')
# workaround_3: Qt prefers no whitespaces in self-closing tags
ret = re.sub('<(.+) +/>', r'<\1/>', ret)
if ret != orig:
print(f'Tip: run this script to apply the fix: `python {__file__} {filename}`', file=sys.stderr)
f.seek(0)
f.write(ret)
f.truncate()
return 0
if __name__ == '__main__':
sys.exit(main())

View file

@ -26,11 +26,9 @@
# but you are not obligated to do so. If you do not wish to do so, delete this
# exception statement from your version.
from collections.abc import Sequence
from typing import Optional
from typing import Optional, Sequence
import argparse
import re
import sys
def main(argv: Optional[Sequence[str]] = None) -> int:
@ -69,4 +67,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
if __name__ == '__main__':
sys.exit(main())
exit(main())

View file

@ -4,13 +4,12 @@ on:
schedule:
- cron: '0 0 * * *'
permissions: {}
permissions:
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: Mark and close stale PRs
uses: actions/stale@v9

4
.gitignore vendored
View file

@ -41,3 +41,7 @@ src/icons/skin/build-icons/icons/*.png
# CMake build directory
build/
# Web UI tools
node_modules
package-lock.json

View file

@ -1,12 +1,6 @@
repos:
- repo: local
hooks:
- id: check-grid-order
name: Check items order in grid layouts
entry: .github/workflows/helper/pre-commit/check_grid_items_order.py
language: script
files: \.ui$
- id: check-translation-tag
name: Check newline characters in <translation> tag
entry: .github/workflows/helper/pre-commit/check_translation_tag.py
@ -19,7 +13,7 @@ repos:
- ts
- repo: https://github.com/pre-commit/pre-commit-hooks.git
rev: v5.0.0
rev: v4.5.0
hooks:
- id: check-json
name: Check JSON files
@ -69,17 +63,19 @@ repos:
- ts
- repo: https://github.com/codespell-project/codespell.git
rev: v2.4.0
rev: v2.2.6
hooks:
- id: codespell
name: Check spelling (codespell)
args: ["--ignore-words-list", "additionals,categor,curren,fo,indexIn,ist,ket,notin,searchin,sectionin,superseeding,te,ths"]
args: ["--ignore-words-list", "additionals,curren,fo,ist,ket,searchin,superseeding,te,ths"]
exclude: |
(?x)^(
.*\.desktop |
.*\.qrc |
build-aux/.* |
Changelog |
dist/windows/installer-translations/.* |
m4/.* |
src/base/3rdparty/.* |
src/searchengine/nova3/socks.py |
src/webui/www/private/scripts/lib/.*
@ -88,7 +84,7 @@ repos:
- ts
- repo: https://github.com/crate-ci/typos.git
rev: v1.29.4
rev: v1.16.18
hooks:
- id: typos
name: Check spelling (typos)
@ -99,8 +95,11 @@ repos:
.*\.desktop |
.*\.qrc |
\.pre-commit-config\.yaml |
build-aux/.* |
Changelog |
configure.* |
dist/windows/installer-translations/.* |
m4/.* |
src/base/3rdparty/.* |
src/searchengine/nova3/socks.py |
src/webui/www/private/scripts/lib/.*

View file

@ -1,7 +1,7 @@
[main]
host = https://www.transifex.com
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_master]
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_v50x]
file_filter = src/lang/qbittorrent_<lang>.ts
source_file = src/lang/qbittorrent_en.ts
source_lang = en
@ -9,7 +9,7 @@ type = QT
minimum_perc = 23
lang_map = pt: pt_PT, zh: zh_CN
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui]
[o:sledgehammer999:p:qbittorrent:r:qbittorrent_webui_v50x]
file_filter = src/webui/www/translations/webui_<lang>.ts
source_file = src/webui/www/translations/webui_en.ts
source_lang = en

View file

@ -2,7 +2,7 @@
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
<https://fsf.org/>
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@ -304,7 +304,8 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, see <https://www.gnu.org/licenses/>.
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
@ -328,8 +329,8 @@ necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Moe Ghoul>, 1 April 1989
Moe Ghoul, President of Vice
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may

View file

@ -1,4 +1,39 @@
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.0
Tue Feb 18th 2025 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.4
- BUGFIX: Fix cannot remove trackers via WebAPI (Chocobo1)
- BUGFIX: Fix torrent content checkbox state under certain conditions (thalieht)
- BUGFIX: Hide zero and infinity values in peer list only when that setting is set to `Always` (thalieht)
- BUGFIX: Remove stopped torrent from "error" tracker filter (glassez)
- WEBUI: Fix memory leak in context menus (skomerko)
- WEBAPI: Don't trim string parameters (glassez)
- WINDOWS: Handle Qt style options uniformly (glassez)
- WINDOWS: NSIS: Update Portuguese translation (Hugo Carvalho)
- MACOS: Avoid memory leak (Chocobo1)
Mon Dec 16th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.3
- BUGFIX: Discard obsolete "state update" events after torrent is reloaded (glassez)
- BUGFIX: Fix incorrect SQL column definition (glassez)
- BUGFIX: Avoid redundant requests of announce entries from libtorrent (glassez)
- WEBUI: Fix removing tracker URL with '|' character (Thomas Piccirello)
- WEBUI: Fix reloading page after login (Evgenii Ryshkov)
- WEBAPI: Fix incorrect key in torrent creator (Bartu Özen)
- RSS: Don't add duplicate episodes to previously matched (wavygecko)
- RSS: Use cached current time when parsing RSS feed (glassez)
- WINDOWS: Don't follow symlink when creating torrents on Windows (Chocobo1)
- WINDOWS: NSIS: Update Italian translation (Giacomo411)
Sun Nov 17th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.2
- BUGFIX: Remove trackers from previous category when moved to new one (glassez)
- BUGFIX: Fix `.torrent` file could not be deleted when torrent is canceled (glassez)
- BUGFIX: Reset tracker entries when pausing the session (glassez)
- BUGFIX: Check real palette darkness to detect "dark theme" (glassez)
- BUGFIX: Correctly handle "torrent finished" events (glassez)
- BUGFIX: Preserve initial torrent progress while checking resume data (glassez)
- BUGFIX: Avoid reapplying Mark-of-the-Web when it already exists (Chocobo1)
- BUGFIX: Don't apply Mark-of-the-Web on existing files (Chocobo1)
- WEBUI: Add color scheme switcher (sledgehammer999)
- SEARCH: Correctly delete the moved search tab (glassez)
- WINDOWS: Correctly save and restore Qt style setting (glassez)
- WINDOWS: NSIS: update Luxembourgish, Simplified Chinese and Traditional Chinese translations (Ikko Eltociear Ashimine, 3gf8jv4dv)
Mon Oct 28th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.1
- FEATURE: Add "Simple pread/pwrite" disk IO type (Hanabishi)
@ -106,42 +141,6 @@ Sun Sep 29th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.0
- OTHER: Minimum supported versions: Qt: 6.5, Boost: 1.76, OpenSSL: 3.0.2
- OTHER: Switch to C++20
Mon Sep 16th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.6.7
- BUGFIX: The updater will launch the link to the build variant you're currently using (sledgehammer999)
- BUGFIX: Focus on Download button if torrent link retrieved from the clipboard (glassez)
- WEBUI: RSS: The list of feeds wouldn't load for Apply Rule (glassez)
Sun Aug 18th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.6.6
- BUGFIX: Fix handling of tags containing '&' character (glassez)
- BUGFIX: Show scroll bar in Torrent Tags dialog (glassez)
- BUGFIX: Apply bulk changes to correct content widget items (glassez)
- BUGFIX: Hide zero status filters when torrents are removed (glassez)
- BUGFIX: Fix `Incomplete Save Path` cannot be changed for torrents without metadata (glassez)
- WEBUI: Correctly apply changed "save path" of RSS rules (glassez)
- WEBUI: Clear tracker list on full update (skomerko)
- OTHER: Update User-Agent string for internal downloader and search engines (cayenne17)
Sun May 26th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.6.5
- BUGFIX: Prevent app from being closed when disabling system tray icon (glassez)
- BUGFIX: Fix <kbd>Enter</kbd> key behavior in Add new torrent dialog (glassez)
- BUGFIX: Prevent invalid status filter index from being used (glassez)
- BUGFIX: Add extra offset for dialog frame (glassez)
- BUGFIX: Don't overwrite stored layout of main window with incorrect one (glassez)
- BUGFIX: Don't forget to resume "missing files" torrent when rechecking (glassez)
- WEBUI: Restore ability to use server-side translation by custom WebUI (glassez)
- WEBUI: Fix wrong peer number (Chocobo1)
- LINUX: Improve AppStream metadata (Chocobo1)
Sun Mar 24th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.6.4
- BUGFIX: Correctly adjust "Add New torrent" dialog position in all the cases (glassez)
- BUGFIX: Change "metadata received" stop condition behavior (glassez)
- BUGFIX: Add a small delay before processing the key input of search boxes (Chocobo1)
- BUGFIX: Ensure the profile path is pointing to a directory (Chocobo1)
- RSS: Use better icons for RSS articles (glassez)
- WINDOWS: NSIS: Update French, Hungarian translations (MarcDrieu, foxi69)
- LINUX: Fix sorting when ICU isn't used (Chocobo1)
- LINUX: Fix invisible tray icon on Plasma 6 (tehcneko)
Mon Jan 15th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.6.3
- BUGFIX: Correctly update number of filtered items (glassez)
- BUGFIX: Don't forget to store Stop condition value (glassez)

View file

@ -18,7 +18,7 @@ qBittorrent - A BitTorrent client in C++ / Qt
- CMake >= 3.16
* Compile-time only
- Python >= 3.9.0
- Python >= 3.7.0
* Optional, run-time only
* Used by the bundled search engine

View file

@ -1,18 +0,0 @@
# Security Policy
qBittorrent takes the security of our software seriously, including all source code repositories managed through our GitHub organisation.
If you believe you have found a security vulnerability in qBittorrent, please report it to us as described below.
## Reporting Security Issues
Please do not report security vulnerabilities through public GitHub issues. Instead, please use GitHubs private vulnerability reporting functionality associated to this repository. Additionally, you may email us with all security-related inquiries and notifications at `security@qbittorrent.org`.
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
1. Type of issue
2. Step-by-step instructions to reproduce the issue
3. Proof-of-concept or exploit code (if possible)
4. Potential impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly. Any and all CVEs will be requested and issued through GitHubs private vulnerability reporting functionality, which will be published alongside the disclosure.
This security policy only applies to the most recent stable branch of qBittorrent. Flaws in old versions that are not present in the current stable branch will not be fixed.

View file

@ -1,5 +0,0 @@
# 2.11.6
* https://github.com/qbittorrent/qBittorrent/pull/22460
* `app/setPreferences` allows only one of `max_ratio_enabled`, `max_ratio` to be present
* `app/setPreferences` allows only one of `max_seeding_time_enabled`, `max_seeding_time` to be present
* `app/setPreferences` allows only one of `max_inactive_seeding_time_enabled`, `max_inactive_seeding_time` to be present

2
dist/mac/Info.plist vendored
View file

@ -55,7 +55,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>5.2.0</string>
<string>5.0.4</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>

View file

@ -105,7 +105,7 @@ GenericName[ka]=BitTorrent კლიენტი
Comment[ka]= BitTorrent-
Name[ka]=qBittorrent
GenericName[ko]=BitTorrent
Comment[ko]=BitTorrent
Comment[ko]=BitTorrent
Name[ko]=qBittorrent
GenericName[lt]=BitTorrent klientas
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle

View file

@ -62,6 +62,6 @@
<url type="contribute">https://github.com/qbittorrent/qBittorrent/blob/master/CONTRIBUTING.md</url>
<content_rating type="oars-1.1"/>
<releases>
<release version="5.2.0~alpha1" date="2025-02-11"/>
<release version="5.0.4" date="2025-02-18"/>
</releases>
</component>

View file

@ -14,7 +14,7 @@
; 4.5.1.3 -> good
; 4.5.1.3.2 -> bad
; 4.5.0beta -> bad
!define /ifndef QBT_VERSION "5.2.0"
!define /ifndef QBT_VERSION "5.0.4"
; Option that controls the installer's window name
; If set, its value will be used like this:

View file

@ -7,21 +7,21 @@ LangString inst_desktop ${LANG_SWEDISH} "Skapa skrivbordsgenväg"
;LangString inst_startmenu ${LANG_ENGLISH} "Create Start Menu Shortcut"
LangString inst_startmenu ${LANG_SWEDISH} "Skapa startmenygenväg"
;LangString inst_startup ${LANG_ENGLISH} "Start qBittorrent on Windows start up"
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows-uppstart"
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows start"
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_SWEDISH} "Öppna .torrent-filer med qBittorrent"
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_SWEDISH} "Öppna magnetlänkar med qBittorrent"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggsregel"
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggregel"
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_SWEDISH} "Inaktivera gränsen för Windows-sökvägslängd (260 tecken MAX_PATH-begränsning, kräver Windows 10 1607 eller senare)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggsregel"
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggregel"
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du installerar."
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du installerar."
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_SWEDISH} "Aktuell version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
LangString inst_uninstall_question ${LANG_SWEDISH} "Nuvarande version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
@ -53,7 +53,7 @@ LangString remove_firewallinfo ${LANG_SWEDISH} "Tar bort Windows-brandväggsrege
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_SWEDISH} "Ta bort torrenter och cachade data"
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du avinstallerar."
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du avinstallerar."
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_SWEDISH} "Tar inte bort .torrent-association. Den är associerad med:"
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"

View file

@ -1,44 +1,41 @@
.\" Automatically generated by Pandoc 3.4
.\" Automatically generated by Pandoc 3.1.7
.\"
.TH "QBITTORRENT\-NOX" "1" "January 16th 2010" "Command line Bittorrent client written in C++ / Qt"
.TH "QBITTORRENT-NOX" "1" "January 16th 2010" "Command line Bittorrent client written in C++ / Qt" ""
.SH NAME
qBittorrent\-nox \- a command line Bittorrent client written in C++ / Qt
qBittorrent-nox - a command line Bittorrent client written in C++ / Qt
.SH SYNOPSIS
\f[B]qbittorrent\-nox\f[R]
\f[CR][\-\-d|\-\-daemon] [\-\-webui\-port=x] [TORRENT_FILE | URL]...\f[R]
\f[B]qbittorrent-nox\f[R]
\f[CR][--d|--daemon] [--webui-port=x] [TORRENT_FILE | URL]...\f[R]
.PP
\f[B]qbittorrent\-nox\f[R] \f[CR]\-\-help\f[R]
\f[B]qbittorrent-nox\f[R] \f[CR]--help\f[R]
.PP
\f[B]qbittorrent\-nox\f[R] \f[CR]\-\-version\f[R]
\f[B]qbittorrent-nox\f[R] \f[CR]--version\f[R]
.SH DESCRIPTION
\f[B]qBittorrent\-nox\f[R] is an advanced command\-line Bittorrent
client written in C++ / Qt using the \f[B]libtorrent\-rasterbar\f[R]
library by Arvid Norberg.
qBittorrent\-nox aims to be a good alternative to other command line
\f[B]qBittorrent-nox\f[R] is an advanced command-line Bittorrent client
written in C++ / Qt using the \f[B]libtorrent-rasterbar\f[R] library by
Arvid Norberg.
qBittorrent-nox aims to be a good alternative to other command line
bittorrent clients and provides features similar to popular graphical
clients.
.PP
qBittorrent\-nox is fast, stable, light and it supports unicode.
It also comes with UPnP port forwarding / NAT\-PMP, encryption (Vuze
qBittorrent-nox is fast, stable, light and it supports unicode.
It also comes with UPnP port forwarding / NAT-PMP, encryption (Vuze
compatible), FAST extension (mainline) and PeX support (utorrent
compatible).
.PP
qBittorrent\-nox is meant to be controlled via its feature\-rich Web UI
qBittorrent-nox is meant to be controlled via its feature-rich Web UI
which is accessible as a default on http://localhost:8080.
The Web UI access is secured and the default account user name is
\[lq]admin\[rq] with \[lq]adminadmin\[rq] as a password.
.SH OPTIONS
\f[B]\f[CB]\-\-help\f[B]\f[R] Prints the command line options.
\f[B]\f[CB]--help\f[B]\f[R] Prints the command line options.
.PP
\f[B]\f[CB]\-\-version\f[B]\f[R] Prints qbittorrent program version
\f[B]\f[CB]--version\f[B]\f[R] Prints qbittorrent program version
number.
.PP
\f[B]\f[CB]\-\-webui\-port=x\f[B]\f[R] Changes Web UI port to x
(default: 8080).
\f[B]\f[CB]--webui-port=x\f[B]\f[R] Changes Web UI port to x (default:
8080).
.SH BUGS
If you find a bug, please report it at https://bugs.qbittorrent.org
.SH AUTHORS
Christophe Dumez \c
.MT chris@qbittorrent.org
.ME \c
\&.
Christophe Dumez <chris@qbittorrent.org>.

View file

@ -1,38 +1,35 @@
.\" Automatically generated by Pandoc 3.4
.\" Automatically generated by Pandoc 3.1.7
.\"
.TH "QBITTORRENT" "1" "January 16th 2010" "Bittorrent client written in C++ / Qt"
.TH "QBITTORRENT" "1" "January 16th 2010" "Bittorrent client written in C++ / Qt" ""
.SH NAME
qBittorrent \- a Bittorrent client written in C++ / Qt
qBittorrent - a Bittorrent client written in C++ / Qt
.SH SYNOPSIS
\f[B]qbittorrent\f[R]
\f[CR][\-\-no\-splash] [\-\-webui\-port=x] [TORRENT_FILE | URL]...\f[R]
\f[CR][--no-splash] [--webui-port=x] [TORRENT_FILE | URL]...\f[R]
.PP
\f[B]qbittorrent\f[R] \f[CR]\-\-help\f[R]
\f[B]qbittorrent\f[R] \f[CR]--help\f[R]
.PP
\f[B]qbittorrent\f[R] \f[CR]\-\-version\f[R]
\f[B]qbittorrent\f[R] \f[CR]--version\f[R]
.SH DESCRIPTION
\f[B]qBittorrent\f[R] is an advanced Bittorrent client written in C++ /
Qt, using the \f[B]libtorrent\-rasterbar\f[R] library by Arvid Norberg.
Qt, using the \f[B]libtorrent-rasterbar\f[R] library by Arvid Norberg.
qBittorrent is similar to uTorrent.
qBittorrent is fast, stable, light, it supports unicode and it provides
a good integrated search engine.
It also comes with UPnP port forwarding / NAT\-PMP, encryption (Vuze
It also comes with UPnP port forwarding / NAT-PMP, encryption (Vuze
compatible), FAST extension (mainline) and PeX support (utorrent
compatible).
.SH OPTIONS
\f[B]\f[CB]\-\-help\f[B]\f[R] Prints the command line options.
\f[B]\f[CB]--help\f[B]\f[R] Prints the command line options.
.PP
\f[B]\f[CB]\-\-version\f[B]\f[R] Prints qbittorrent program version
\f[B]\f[CB]--version\f[B]\f[R] Prints qbittorrent program version
number.
.PP
\f[B]\f[CB]\-\-no\-splash\f[B]\f[R] Disables splash screen on startup.
\f[B]\f[CB]--no-splash\f[B]\f[R] Disables splash screen on startup.
.PP
\f[B]\f[CB]\-\-webui\-port=x\f[B]\f[R] Changes Web UI port to x
(default: 8080).
\f[B]\f[CB]--webui-port=x\f[B]\f[R] Changes Web UI port to x (default:
8080).
.SH BUGS
If you find a bug, please report it at https://bugs.qbittorrent.org
.SH AUTHORS
Christophe Dumez \c
.MT chris@qbittorrent.org
.ME \c
\&.
Christophe Dumez <chris@qbittorrent.org>.

View file

@ -1,10 +1,7 @@
.\" Automatically generated by Pandoc 3.4
.\" Automatically generated by Pandoc 3.1.7
.\"
.TH "QBITTORRENT\-NOX" "1" "16 января 2010" "Клиент сети БитТоррент для командной строки"
.TH "QBITTORRENT-NOX" "1" "16 января 2010" "Клиент сети БитТоррент для командной строки" ""
.SH НАЗВАНИЕ
qBittorrent\-nox \[em] клиент сети БитТоррент для командной строки.
qBittorrent-nox \[em] клиент сети БитТоррент для командной строки.
.SH АВТОРЫ
Christophe Dumez \c
.MT chris@qbittorrent.org
.ME \c
\&.
Christophe Dumez <chris@qbittorrent.org>.

View file

@ -1,10 +1,7 @@
.\" Automatically generated by Pandoc 3.4
.\" Automatically generated by Pandoc 3.1.7
.\"
.TH "QBITTORRENT" "1" "16 января 2010" "Клиент сети БитТоррент"
.TH "QBITTORRENT" "1" "16 января 2010" "Клиент сети БитТоррент" ""
.SH НАЗВАНИЕ
qBittorrent \[em] клиент сети БитТоррент.
.SH АВТОРЫ
Christophe Dumez \c
.MT chris@qbittorrent.org
.ME \c
\&.
Christophe Dumez <chris@qbittorrent.org>.

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez
*
* This program is free software; you can redistribute it and/or
@ -124,28 +124,6 @@ namespace
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
#endif
const QString PARAM_ADDSTOPPED = u"@addStopped"_s;
const QString PARAM_CATEGORY = u"@category"_s;
const QString PARAM_FIRSTLASTPIECEPRIORITY = u"@firstLastPiecePriority"_s;
const QString PARAM_SAVEPATH = u"@savePath"_s;
const QString PARAM_SEQUENTIAL = u"@sequential"_s;
const QString PARAM_SKIPCHECKING = u"@skipChecking"_s;
const QString PARAM_SKIPDIALOG = u"@skipDialog"_s;
QString bindParamValue(const QStringView paramName, const QStringView paramValue)
{
return paramName + u'=' + paramValue;
}
std::pair<QStringView, QStringView> parseParam(const QStringView param)
{
const qsizetype sepIndex = param.indexOf(u'=');
if (sepIndex >= 0)
return {param.first(sepIndex), param.sliced(sepIndex + 1)};
return {param, {}};
}
QString serializeParams(const QBtCommandLineParameters &params)
{
QStringList result;
@ -160,86 +138,85 @@ namespace
const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
if (!addTorrentParams.savePath.isEmpty())
result.append(bindParamValue(PARAM_SAVEPATH, addTorrentParams.savePath.data()));
result.append(u"@savePath=" + addTorrentParams.savePath.data());
if (addTorrentParams.addStopped.has_value())
result.append(bindParamValue(PARAM_ADDSTOPPED, (*addTorrentParams.addStopped ? u"1" : u"0")));
result.append(*addTorrentParams.addStopped ? u"@addStopped=1"_s : u"@addStopped=0"_s);
if (addTorrentParams.skipChecking)
result.append(PARAM_SKIPCHECKING);
result.append(u"@skipChecking"_s);
if (!addTorrentParams.category.isEmpty())
result.append(bindParamValue(PARAM_CATEGORY, addTorrentParams.category));
result.append(u"@category=" + addTorrentParams.category);
if (addTorrentParams.sequential)
result.append(PARAM_SEQUENTIAL);
result.append(u"@sequential"_s);
if (addTorrentParams.firstLastPiecePriority)
result.append(PARAM_FIRSTLASTPIECEPRIORITY);
result.append(u"@firstLastPiecePriority"_s);
if (params.skipDialog.has_value())
result.append(bindParamValue(PARAM_SKIPDIALOG, (*params.skipDialog ? u"1" : u"0")));
result.append(*params.skipDialog ? u"@skipDialog=1"_s : u"@skipDialog=0"_s);
result += params.torrentSources;
return result.join(PARAMS_SEPARATOR);
}
QBtCommandLineParameters parseParams(const QStringView str)
QBtCommandLineParameters parseParams(const QString &str)
{
QBtCommandLineParameters parsedParams;
BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
for (QStringView param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
for (QString param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
{
param = param.trimmed();
const auto [paramName, paramValue] = parseParam(param);
// Process strings indicating options specified by the user.
if (paramName == PARAM_SAVEPATH)
if (param.startsWith(u"@savePath="))
{
addTorrentParams.savePath = Path(paramValue.toString());
addTorrentParams.savePath = Path(param.mid(10));
continue;
}
if (paramName == PARAM_ADDSTOPPED)
if (param.startsWith(u"@addStopped="))
{
addTorrentParams.addStopped = (paramValue.toInt() != 0);
addTorrentParams.addStopped = (QStringView(param).mid(11).toInt() != 0);
continue;
}
if (paramName == PARAM_SKIPCHECKING)
if (param == u"@skipChecking")
{
addTorrentParams.skipChecking = true;
continue;
}
if (paramName == PARAM_CATEGORY)
if (param.startsWith(u"@category="))
{
addTorrentParams.category = paramValue.toString();
addTorrentParams.category = param.mid(10);
continue;
}
if (paramName == PARAM_SEQUENTIAL)
if (param == u"@sequential")
{
addTorrentParams.sequential = true;
continue;
}
if (paramName == PARAM_FIRSTLASTPIECEPRIORITY)
if (param == u"@firstLastPiecePriority")
{
addTorrentParams.firstLastPiecePriority = true;
continue;
}
if (paramName == PARAM_SKIPDIALOG)
if (param.startsWith(u"@skipDialog="))
{
parsedParams.skipDialog = (paramValue.toInt() != 0);
parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0);
continue;
}
parsedParams.torrentSources.append(param.toString());
parsedParams.torrentSources.append(param);
}
return parsedParams;
@ -602,7 +579,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 sequence is different for Windows and other OS, this is intentional
// The processing sequenece is different for Windows and other OS, this is intentional
#if defined(Q_OS_WIN)
const QString program = replaceVariables(programTemplate);
const std::wstring programWStr = program.toStdWString();
@ -659,13 +636,7 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
{
// strip redundant quotes
if (arg.startsWith(u'"') && arg.endsWith(u'"'))
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
arg.slice(1, (arg.size() - 2));
#else
arg.removeLast().removeFirst();
#endif
}
arg = arg.mid(1, (arg.size() - 2));
arg = replaceVariables(arg);
}
@ -674,9 +645,6 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
QProcess proc;
proc.setProgram(command);
proc.setArguments(args);
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
proc.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
#endif
if (proc.startDetached())
{
@ -929,10 +897,10 @@ int Application::exec()
m_desktopIntegration->showNotification(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name()));
});
connect(m_addTorrentManager, &AddTorrentManager::addTorrentFailed, this
, [this](const QString &source, const BitTorrent::AddTorrentError &reason)
, [this](const QString &source, const QString &reason)
{
m_desktopIntegration->showNotification(tr("Add torrent failed")
, tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason.message));
, tr("Couldn't add torrent '%1', reason: %2.").arg(source, reason));
});
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);

View file

@ -36,7 +36,6 @@
#include <QDebug>
#include <QFileInfo>
#include <QProcessEnvironment>
#include <QStringView>
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
#include <QMessageBox>
@ -61,7 +60,7 @@ namespace
class Option
{
protected:
explicit constexpr Option(const QStringView name, const QChar shortcut = QChar::Null)
explicit constexpr Option(const char *name, char shortcut = 0)
: m_name {name}
, m_shortcut {shortcut}
{
@ -69,23 +68,23 @@ namespace
QString fullParameter() const
{
return u"--" + m_name.toString();
return u"--" + QString::fromLatin1(m_name);
}
QString shortcutParameter() const
{
return u"-" + m_shortcut;
return u"-" + QChar::fromLatin1(m_shortcut);
}
bool hasShortcut() const
{
return !m_shortcut.isNull();
return m_shortcut != 0;
}
QString envVarName() const
{
return u"QBT_"
+ m_name.toString().toUpper().replace(u'-', u'_');
+ QString::fromLatin1(m_name).toUpper().replace(u'-', u'_');
}
public:
@ -100,15 +99,15 @@ namespace
}
private:
const QStringView m_name;
const QChar m_shortcut;
const char *m_name = nullptr;
const char m_shortcut;
};
// Boolean option.
class BoolOption : protected Option
{
public:
explicit constexpr BoolOption(const QStringView name, const QChar shortcut = QChar::Null)
explicit constexpr BoolOption(const char *name, char shortcut = 0)
: Option {name, shortcut}
{
}
@ -140,8 +139,8 @@ namespace
struct StringOption : protected Option
{
public:
explicit constexpr StringOption(const QStringView name)
: Option {name, QChar::Null}
explicit constexpr StringOption(const char *name)
: Option {name, 0}
{
}
@ -182,7 +181,7 @@ namespace
class IntOption : protected StringOption
{
public:
explicit constexpr IntOption(const QStringView name)
explicit constexpr IntOption(const char *name)
: StringOption {name}
{
}
@ -230,8 +229,8 @@ namespace
class TriStateBoolOption : protected Option
{
public:
constexpr TriStateBoolOption(const QStringView name, const bool defaultValue)
: Option {name, QChar::Null}
constexpr TriStateBoolOption(const char *name, bool defaultValue)
: Option {name, 0}
, m_defaultValue(defaultValue)
{
}
@ -300,32 +299,31 @@ namespace
return arg.section(u'=', 0, 0) == option.fullParameter();
}
private:
bool m_defaultValue = false;
bool m_defaultValue;
};
constexpr const BoolOption SHOW_HELP_OPTION {u"help", u'h'};
constexpr const BoolOption SHOW_HELP_OPTION {"help", 'h'};
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
constexpr const BoolOption SHOW_VERSION_OPTION {u"version", u'v'};
constexpr const BoolOption SHOW_VERSION_OPTION {"version", 'v'};
#endif
constexpr const BoolOption CONFIRM_LEGAL_NOTICE {u"confirm-legal-notice"};
constexpr const BoolOption CONFIRM_LEGAL_NOTICE {"confirm-legal-notice"};
#if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
constexpr const BoolOption DAEMON_OPTION {u"daemon", u'd'};
constexpr const BoolOption DAEMON_OPTION {"daemon", 'd'};
#else
constexpr const BoolOption NO_SPLASH_OPTION {u"no-splash"};
constexpr const BoolOption NO_SPLASH_OPTION {"no-splash"};
#endif
constexpr const IntOption WEBUI_PORT_OPTION {u"webui-port"};
constexpr const IntOption TORRENTING_PORT_OPTION {u"torrenting-port"};
constexpr const StringOption PROFILE_OPTION {u"profile"};
constexpr const StringOption CONFIGURATION_OPTION {u"configuration"};
constexpr const BoolOption RELATIVE_FASTRESUME {u"relative-fastresume"};
constexpr const StringOption SAVE_PATH_OPTION {u"save-path"};
constexpr const TriStateBoolOption STOPPED_OPTION {u"add-stopped", true};
constexpr const BoolOption SKIP_HASH_CHECK_OPTION {u"skip-hash-check"};
constexpr const StringOption CATEGORY_OPTION {u"category"};
constexpr const BoolOption SEQUENTIAL_OPTION {u"sequential"};
constexpr const BoolOption FIRST_AND_LAST_OPTION {u"first-and-last"};
constexpr const TriStateBoolOption SKIP_DIALOG_OPTION {u"skip-dialog", true};
constexpr const IntOption WEBUI_PORT_OPTION {"webui-port"};
constexpr const IntOption TORRENTING_PORT_OPTION {"torrenting-port"};
constexpr const StringOption PROFILE_OPTION {"profile"};
constexpr const StringOption CONFIGURATION_OPTION {"configuration"};
constexpr const BoolOption RELATIVE_FASTRESUME {"relative-fastresume"};
constexpr const StringOption SAVE_PATH_OPTION {"save-path"};
constexpr const TriStateBoolOption STOPPED_OPTION {"add-stopped", true};
constexpr const BoolOption SKIP_HASH_CHECK_OPTION {"skip-hash-check"};
constexpr const StringOption CATEGORY_OPTION {"category"};
constexpr const BoolOption SEQUENTIAL_OPTION {"sequential"};
constexpr const BoolOption FIRST_AND_LAST_OPTION {"first-and-last"};
constexpr const TriStateBoolOption SKIP_DIALOG_OPTION {"skip-dialog", true};
}
QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &env)
@ -465,13 +463,13 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
return result;
}
QString wrapText(const QString &text, const int initialIndentation = USAGE_TEXT_COLUMN, const int wrapAtColumn = WRAP_AT_COLUMN)
QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN, int wrapAtColumn = WRAP_AT_COLUMN)
{
const QStringList words = text.split(u' ');
QStringList words = text.split(u' ');
QStringList lines = {words.first()};
int currentLineMaxLength = wrapAtColumn - initialIndentation;
for (const QString &word : asConst(words.sliced(1)))
for (const QString &word : asConst(words.mid(1)))
{
if (lines.last().length() + word.length() + 1 < currentLineMaxLength)
{

View file

@ -32,8 +32,8 @@
#include <QDateTime>
#include <QDir>
#include <QList>
#include <QTextStream>
#include <QVector>
#include "base/global.h"
#include "base/logger.h"

View file

@ -74,7 +74,6 @@
#include <windows.h>
#endif
#include <QByteArray>
#include <QDataStream>
#include <QFileInfo>
#include <QLocalServer>
@ -91,7 +90,7 @@ namespace QtLP_Private
#endif
}
const QByteArray ACK = QByteArrayLiteral("ack");
const char ACK[] = "ack";
QtLocalPeer::QtLocalPeer(const QString &path, QObject *parent)
: QObject(parent)
@ -170,7 +169,7 @@ bool QtLocalPeer::sendMessage(const QString &message, const int timeout)
{
res &= socket.waitForReadyRead(timeout); // wait for ack
if (res)
res &= (socket.read(ACK.size()) == ACK);
res &= (socket.read(qstrlen(ACK)) == ACK);
}
return res;
}
@ -221,7 +220,7 @@ void QtLocalPeer::receiveConnection()
return;
}
QString message(QString::fromUtf8(uMsg));
socket->write(ACK);
socket->write(ACK, qstrlen(ACK));
socket->waitForBytesWritten(1000);
socket->waitForDisconnected(1000); // make sure client reads ack
delete socket;

View file

@ -71,8 +71,8 @@
#include <QFile>
#ifdef Q_OS_WIN
#include <QList>
#include <QString>
#include <QVector>
#endif
namespace QtLP_Private
@ -105,7 +105,7 @@ namespace QtLP_Private
Qt::HANDLE m_writeMutex = nullptr;
Qt::HANDLE m_readMutex = nullptr;
QList<Qt::HANDLE> m_readMutexes;
QVector<Qt::HANDLE> m_readMutexes;
QString m_mutexName;
#endif

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,7 @@ add_library(qbt_base STATIC
applicationcomponent.h
asyncfilestorage.h
bittorrent/abstractfilestorage.h
bittorrent/addtorrenterror.h
bittorrent/addtorrentparams.h
bittorrent/announcetimepoint.h
bittorrent/bandwidthscheduler.h
bittorrent/bencoderesumedatastorage.h
bittorrent/cachestatus.h
@ -55,7 +53,6 @@ add_library(qbt_base STATIC
concepts/stringable.h
digest32.h
exceptions.h
freediskspacechecker.h
global.h
http/connection.h
http/httperror.h
@ -161,7 +158,6 @@ add_library(qbt_base STATIC
bittorrent/trackerentry.cpp
bittorrent/trackerentrystatus.cpp
exceptions.cpp
freediskspacechecker.cpp
http/connection.cpp
http/httperror.cpp
http/requestparser.cpp

View file

@ -29,7 +29,6 @@
#include "addtorrentmanager.h"
#include "base/bittorrent/addtorrenterror.h"
#include "base/bittorrent/infohash.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrentdescriptor.h"
@ -141,7 +140,7 @@ void AddTorrentManager::onSessionTorrentAdded(BitTorrent::Torrent *torrent)
}
}
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const BitTorrent::AddTorrentError &reason)
void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason)
{
if (const QString source = m_sourcesByInfoHash.take(infoHash); !source.isEmpty())
{
@ -155,7 +154,7 @@ void AddTorrentManager::onSessionAddTorrentFailed(const BitTorrent::InfoHash &in
void AddTorrentManager::handleAddTorrentFailed(const QString &source, const QString &reason)
{
LogMsg(tr("Failed to add torrent. Source: \"%1\". Reason: \"%2\"").arg(source, reason), Log::WARNING);
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::Other, reason});
emit addTorrentFailed(source, reason);
}
void AddTorrentManager::handleDuplicateTorrent(const QString &source
@ -186,9 +185,9 @@ void AddTorrentManager::handleDuplicateTorrent(const QString &source
message = tr("Trackers are merged from new source");
}
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: \"%2\". Torrent infohash: %3. Result: %4")
.arg(source, existingTorrent->name(), existingTorrent->infoHash().toString(), message));
emit addTorrentFailed(source, {BitTorrent::AddTorrentError::DuplicateTorrent, message});
LogMsg(tr("Detected an attempt to add a duplicate torrent. Source: %1. Existing torrent: %2. Result: %3")
.arg(source, existingTorrent->name(), message));
emit addTorrentFailed(source, message);
}
void AddTorrentManager::setTorrentFileGuard(const QString &source, std::shared_ptr<TorrentFileGuard> torrentFileGuard)

View file

@ -44,7 +44,6 @@ namespace BitTorrent
class Session;
class Torrent;
class TorrentDescriptor;
struct AddTorrentError;
}
namespace Net
@ -67,7 +66,7 @@ public:
signals:
void torrentAdded(const QString &source, BitTorrent::Torrent *torrent);
void addTorrentFailed(const QString &source, const BitTorrent::AddTorrentError &reason);
void addTorrentFailed(const QString &source, const QString &reason);
protected:
bool addTorrentToSession(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
@ -80,7 +79,7 @@ protected:
private:
void onDownloadFinished(const Net::DownloadResult &result);
void onSessionTorrentAdded(BitTorrent::Torrent *torrent);
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const BitTorrent::AddTorrentError &reason);
void onSessionAddTorrentFailed(const BitTorrent::InfoHash &infoHash, const QString &reason);
bool processTorrent(const QString &source, const BitTorrent::TorrentDescriptor &torrentDescr
, const BitTorrent::AddTorrentParams &addTorrentParams);

View file

@ -30,7 +30,7 @@
#include <QDir>
#include <QHash>
#include <QList>
#include <QVector>
#include "base/exceptions.h"
#include "base/path.h"
@ -71,7 +71,7 @@ void BitTorrent::AbstractFileStorage::renameFolder(const Path &oldFolderPath, co
if (newFolderPath.isAbsolute())
throw RuntimeError(tr("Absolute path isn't allowed: '%1'.").arg(newFolderPath.toString()));
QList<int> renamingFileIndexes;
QVector<int> renamingFileIndexes;
renamingFileIndexes.reserve(filesCount());
for (int i = 0; i < filesCount(); ++i)

View file

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

View file

@ -30,9 +30,9 @@
#include <optional>
#include <QList>
#include <QMetaType>
#include <QString>
#include <QVector>
#include "base/path.h"
#include "base/tagset.h"
@ -62,7 +62,7 @@ namespace BitTorrent
std::optional<bool> addStopped;
std::optional<Torrent::StopCondition> stopCondition;
PathList filePaths; // used if TorrentInfo is set
QList<DownloadPriority> filePriorities; // used if TorrentInfo is set
QVector<DownloadPriority> filePriorities; // used if TorrentInfo is set
bool skipChecking = false;
std::optional<BitTorrent::TorrentContentLayout> contentLayout;
std::optional<bool> useAutoTMM;

View file

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

View file

@ -102,7 +102,7 @@ bool BandwidthScheduler::isTimeForAlternative() const
alternative = !alternative;
break;
default:
Q_UNREACHABLE();
Q_ASSERT(false);
break;
}
}

View file

@ -64,7 +64,7 @@ namespace BitTorrent
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const;
void remove(const TorrentID &id) const;
void storeQueue(const QList<TorrentID> &queue) const;
void storeQueue(const QVector<TorrentID> &queue) const;
private:
const Path m_resumeDataDir;
@ -131,11 +131,10 @@ BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path,
m_asyncWorker->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_asyncWorker, &QObject::deleteLater);
m_ioThread->setObjectName("BencodeResumeDataStorage m_ioThread");
m_ioThread->start();
}
QList<BitTorrent::TorrentID> BitTorrent::BencodeResumeDataStorage::registeredTorrents() const
QVector<BitTorrent::TorrentID> BitTorrent::BencodeResumeDataStorage::registeredTorrents() const
{
return m_registeredTorrents;
}
@ -290,11 +289,10 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
lt::add_torrent_params &p = torrentParams.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
if (!metadata.isEmpty())
{
const auto *pref = Preferences::instance();
const lt::bdecode_node torentInfoRoot = lt::bdecode(metadata, ec
, nullptr, pref->getBdecodeDepthLimit(), pref->getBdecodeTokenLimit());
if (ec)
@ -322,8 +320,6 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
p.save_path = Profile::instance()->fromPortablePath(
Path(fromLTString(p.save_path))).toString().toStdString();
if (p.save_path.empty())
return nonstd::make_unexpected(tr("Corrupted resume data: %1").arg(tr("save_path is invalid")));
torrentParams.stopped = (p.flags & lt::torrent_flags::paused) && !(p.flags & lt::torrent_flags::auto_managed);
torrentParams.operatingMode = (p.flags & lt::torrent_flags::paused) || (p.flags & lt::torrent_flags::auto_managed)
@ -358,7 +354,7 @@ void BitTorrent::BencodeResumeDataStorage::remove(const TorrentID &id) const
});
}
void BitTorrent::BencodeResumeDataStorage::storeQueue(const QList<TorrentID> &queue) const
void BitTorrent::BencodeResumeDataStorage::storeQueue(const QVector<TorrentID> &queue) const
{
QMetaObject::invokeMethod(m_asyncWorker, [this, queue]()
{
@ -464,7 +460,7 @@ void BitTorrent::BencodeResumeDataStorage::Worker::remove(const TorrentID &id) c
Utils::Fs::removeFile(m_resumeDataDir / torrentFilename);
}
void BitTorrent::BencodeResumeDataStorage::Worker::storeQueue(const QList<TorrentID> &queue) const
void BitTorrent::BencodeResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue) const
{
QByteArray data;
data.reserve(((BitTorrent::TorrentID::length() * 2) + 1) * queue.size());

View file

@ -29,7 +29,7 @@
#pragma once
#include <QDir>
#include <QList>
#include <QVector>
#include "base/pathfwd.h"
#include "base/utils/thread.h"
@ -37,6 +37,7 @@
#include "resumedatastorage.h"
class QByteArray;
class QThread;
namespace BitTorrent
{
@ -48,18 +49,18 @@ namespace BitTorrent
public:
explicit BencodeResumeDataStorage(const Path &path, QObject *parent = nullptr);
QList<TorrentID> registeredTorrents() const override;
QVector<TorrentID> registeredTorrents() const override;
LoadResumeDataResult load(const TorrentID &id) const override;
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const override;
void remove(const TorrentID &id) const override;
void storeQueue(const QList<TorrentID> &queue) const override;
void storeQueue(const QVector<TorrentID> &queue) const override;
private:
void doLoadAll() const override;
void loadQueue(const Path &queueFilename);
LoadResumeDataResult loadTorrentResumeData(const QByteArray &data, const QByteArray &metadata) const;
QList<TorrentID> m_registeredTorrents;
QVector<TorrentID> m_registeredTorrents;
Utils::Thread::UniquePtr m_ioThread;
class Worker;

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021-2025 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
@ -41,7 +41,6 @@
#include <QByteArray>
#include <QDebug>
#include <QList>
#include <QMutex>
#include <QSet>
#include <QSqlDatabase>
@ -49,6 +48,7 @@
#include <QSqlQuery>
#include <QSqlRecord>
#include <QThread>
#include <QVector>
#include <QWaitCondition>
#include "base/exceptions.h"
@ -107,11 +107,11 @@ namespace
class StoreQueueJob final : public Job
{
public:
explicit StoreQueueJob(const QList<TorrentID> &queue);
explicit StoreQueueJob(const QVector<TorrentID> &queue);
void perform(QSqlDatabase db) override;
private:
const QList<TorrentID> m_queue;
const QVector<TorrentID> m_queue;
};
struct Column
@ -120,35 +120,36 @@ namespace
QString placeholder;
};
Column makeColumn(const QString &columnName)
Column makeColumn(const char *columnName)
{
return {.name = columnName, .placeholder = (u':' + columnName)};
const QString name = QString::fromLatin1(columnName);
return {.name = name, .placeholder = (u':' + name)};
}
const Column DB_COLUMN_ID = makeColumn(u"id"_s);
const Column DB_COLUMN_TORRENT_ID = makeColumn(u"torrent_id"_s);
const Column DB_COLUMN_QUEUE_POSITION = makeColumn(u"queue_position"_s);
const Column DB_COLUMN_NAME = makeColumn(u"name"_s);
const Column DB_COLUMN_CATEGORY = makeColumn(u"category"_s);
const Column DB_COLUMN_TAGS = makeColumn(u"tags"_s);
const Column DB_COLUMN_TARGET_SAVE_PATH = makeColumn(u"target_save_path"_s);
const Column DB_COLUMN_DOWNLOAD_PATH = makeColumn(u"download_path"_s);
const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn(u"content_layout"_s);
const Column DB_COLUMN_RATIO_LIMIT = makeColumn(u"ratio_limit"_s);
const Column DB_COLUMN_SEEDING_TIME_LIMIT = makeColumn(u"seeding_time_limit"_s);
const Column DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT = makeColumn(u"inactive_seeding_time_limit"_s);
const Column DB_COLUMN_SHARE_LIMIT_ACTION = makeColumn(u"share_limit_action"_s);
const Column DB_COLUMN_HAS_OUTER_PIECES_PRIORITY = makeColumn(u"has_outer_pieces_priority"_s);
const Column DB_COLUMN_HAS_SEED_STATUS = makeColumn(u"has_seed_status"_s);
const Column DB_COLUMN_OPERATING_MODE = makeColumn(u"operating_mode"_s);
const Column DB_COLUMN_STOPPED = makeColumn(u"stopped"_s);
const Column DB_COLUMN_STOP_CONDITION = makeColumn(u"stop_condition"_s);
const Column DB_COLUMN_SSL_CERTIFICATE = makeColumn(u"ssl_certificate"_s);
const Column DB_COLUMN_SSL_PRIVATE_KEY = makeColumn(u"ssl_private_key"_s);
const Column DB_COLUMN_SSL_DH_PARAMS = makeColumn(u"ssl_dh_params"_s);
const Column DB_COLUMN_RESUMEDATA = makeColumn(u"libtorrent_resume_data"_s);
const Column DB_COLUMN_METADATA = makeColumn(u"metadata"_s);
const Column DB_COLUMN_VALUE = makeColumn(u"value"_s);
const Column DB_COLUMN_ID = makeColumn("id");
const Column DB_COLUMN_TORRENT_ID = makeColumn("torrent_id");
const Column DB_COLUMN_QUEUE_POSITION = makeColumn("queue_position");
const Column DB_COLUMN_NAME = makeColumn("name");
const Column DB_COLUMN_CATEGORY = makeColumn("category");
const Column DB_COLUMN_TAGS = makeColumn("tags");
const Column DB_COLUMN_TARGET_SAVE_PATH = makeColumn("target_save_path");
const Column DB_COLUMN_DOWNLOAD_PATH = makeColumn("download_path");
const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn("content_layout");
const Column DB_COLUMN_RATIO_LIMIT = makeColumn("ratio_limit");
const Column DB_COLUMN_SEEDING_TIME_LIMIT = makeColumn("seeding_time_limit");
const Column DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT = makeColumn("inactive_seeding_time_limit");
const Column DB_COLUMN_SHARE_LIMIT_ACTION = makeColumn("share_limit_action");
const Column DB_COLUMN_HAS_OUTER_PIECES_PRIORITY = makeColumn("has_outer_pieces_priority");
const Column DB_COLUMN_HAS_SEED_STATUS = makeColumn("has_seed_status");
const Column DB_COLUMN_OPERATING_MODE = makeColumn("operating_mode");
const Column DB_COLUMN_STOPPED = makeColumn("stopped");
const Column DB_COLUMN_STOP_CONDITION = makeColumn("stop_condition");
const Column DB_COLUMN_SSL_CERTIFICATE = makeColumn("ssl_certificate");
const Column DB_COLUMN_SSL_PRIVATE_KEY = makeColumn("ssl_private_key");
const Column DB_COLUMN_SSL_DH_PARAMS = makeColumn("ssl_dh_params");
const Column DB_COLUMN_RESUMEDATA = makeColumn("libtorrent_resume_data");
const Column DB_COLUMN_METADATA = makeColumn("metadata");
const Column DB_COLUMN_VALUE = makeColumn("value");
template <typename LTStr>
QString fromLTString(const LTStr &str)
@ -167,7 +168,7 @@ namespace
return u"CREATE TABLE %1 (%2)"_s.arg(quoted(tableName), items.join(u','));
}
std::pair<QString, QString> joinColumns(const QList<Column> &columns)
std::pair<QString, QString> joinColumns(const QVector<Column> &columns)
{
int namesSize = columns.size();
int valuesSize = columns.size();
@ -192,30 +193,104 @@ namespace
return std::make_pair(names, values);
}
QString makeInsertStatement(const QString &tableName, const QList<Column> &columns)
QString makeInsertStatement(const QString &tableName, const QVector<Column> &columns)
{
const auto [names, values] = joinColumns(columns);
return u"INSERT INTO %1 (%2) VALUES (%3)"_s
.arg(quoted(tableName), names, values);
}
QString makeUpdateStatement(const QString &tableName, const QList<Column> &columns)
QString makeUpdateStatement(const QString &tableName, const QVector<Column> &columns)
{
const auto [names, values] = joinColumns(columns);
return u"UPDATE %1 SET (%2) = (%3)"_s
.arg(quoted(tableName), names, values);
}
QString makeOnConflictUpdateStatement(const Column &constraint, const QList<Column> &columns)
QString makeOnConflictUpdateStatement(const Column &constraint, const QVector<Column> &columns)
{
const auto [names, values] = joinColumns(columns);
return u" ON CONFLICT (%1) DO UPDATE SET (%2) = (%3)"_s
.arg(quoted(constraint.name), names, values);
}
QString makeColumnDefinition(const Column &column, const QString &definition)
QString makeColumnDefinition(const Column &column, const char *definition)
{
return u"%1 %2"_s.arg(quoted(column.name), definition);
return u"%1 %2"_s.arg(quoted(column.name), QString::fromLatin1(definition));
}
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
{
LoadTorrentParams resumeData;
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
if (!tagsData.isEmpty())
{
const QStringList tagList = tagsData.split(u',');
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
}
resumeData.hasFinishedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
resumeData.inactiveSeedingTimeLimit = query.value(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name).toInt();
resumeData.shareLimitAction = Utils::String::toEnum<ShareLimitAction>(
query.value(DB_COLUMN_SHARE_LIMIT_ACTION.name).toString(), ShareLimitAction::Default);
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.stopCondition = Utils::String::toEnum(
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
resumeData.sslParameters =
{
.certificate = QSslCertificate(query.value(DB_COLUMN_SSL_CERTIFICATE.name).toByteArray()),
.privateKey = Utils::SSLKey::load(query.value(DB_COLUMN_SSL_PRIVATE_KEY.name).toByteArray()),
.dhParams = query.value(DB_COLUMN_SSL_DH_PARAMS.name).toByteArray()
};
resumeData.savePath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
const auto *pref = Preferences::instance();
const int bdecodeDepthLimit = pref->getBdecodeDepthLimit();
const int bdecodeTokenLimit = pref->getBdecodeTokenLimit();
lt::error_code ec;
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray()
; !bencodedMetadata.isEmpty())
{
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
}
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
.toString().toStdString();
if (p.flags & lt::torrent_flags::stop_when_ready)
{
p.flags &= ~lt::torrent_flags::stop_when_ready;
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
}
return resumeData;
}
}
@ -233,7 +308,7 @@ namespace BitTorrent
void store(const TorrentID &id, const LoadTorrentParams &resumeData);
void remove(const TorrentID &id);
void storeQueue(const QList<TorrentID> &queue);
void storeQueue(const QVector<TorrentID> &queue);
private:
void addJob(std::unique_ptr<Job> job);
@ -250,6 +325,7 @@ namespace BitTorrent
BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent)
: ResumeDataStorage(dbPath, parent)
, m_ioThread {new QThread}
{
const bool needCreateDB = !dbPath.exists();
@ -280,7 +356,7 @@ BitTorrent::DBResumeDataStorage::~DBResumeDataStorage()
QSqlDatabase::removeDatabase(DB_CONNECTION_NAME);
}
QList<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents() const
QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents() const
{
const auto selectTorrentIDStatement = u"SELECT %1 FROM %2 ORDER BY %3;"_s
.arg(quoted(DB_COLUMN_TORRENT_ID.name), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
@ -291,7 +367,7 @@ QList<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents
if (!query.exec(selectTorrentIDStatement))
throw RuntimeError(query.lastError().text());
QList<TorrentID> registeredTorrents;
QVector<TorrentID> registeredTorrents;
registeredTorrents.reserve(query.size());
while (query.next())
registeredTorrents.append(BitTorrent::TorrentID::fromString(query.value(0).toString()));
@ -337,7 +413,7 @@ void BitTorrent::DBResumeDataStorage::remove(const BitTorrent::TorrentID &id) co
m_asyncWorker->remove(id);
}
void BitTorrent::DBResumeDataStorage::storeQueue(const QList<TorrentID> &queue) const
void BitTorrent::DBResumeDataStorage::storeQueue(const QVector<TorrentID> &queue) const
{
m_asyncWorker->storeQueue(queue);
}
@ -362,7 +438,7 @@ void BitTorrent::DBResumeDataStorage::doLoadAll() const
if (!query.exec(selectTorrentIDStatement))
throw RuntimeError(query.lastError().text());
QList<TorrentID> registeredTorrents;
QVector<TorrentID> registeredTorrents;
registeredTorrents.reserve(query.size());
while (query.next())
registeredTorrents.append(TorrentID::fromString(query.value(0).toString()));
@ -436,9 +512,9 @@ void BitTorrent::DBResumeDataStorage::createDB() const
try
{
const QStringList tableMetaItems = {
makeColumnDefinition(DB_COLUMN_ID, u"INTEGER PRIMARY KEY"_s),
makeColumnDefinition(DB_COLUMN_NAME, u"TEXT NOT NULL UNIQUE"_s),
makeColumnDefinition(DB_COLUMN_VALUE, u"BLOB"_s)
makeColumnDefinition(DB_COLUMN_ID, "INTEGER PRIMARY KEY"),
makeColumnDefinition(DB_COLUMN_NAME, "TEXT NOT NULL UNIQUE"),
makeColumnDefinition(DB_COLUMN_VALUE, "BLOB")
};
const QString createTableMetaQuery = makeCreateTableStatement(DB_TABLE_META, tableMetaItems);
if (!query.exec(createTableMetaQuery))
@ -455,29 +531,29 @@ void BitTorrent::DBResumeDataStorage::createDB() const
throw RuntimeError(query.lastError().text());
const QStringList tableTorrentsItems = {
makeColumnDefinition(DB_COLUMN_ID, u"INTEGER PRIMARY KEY"_s),
makeColumnDefinition(DB_COLUMN_TORRENT_ID, u"BLOB NOT NULL UNIQUE"_s),
makeColumnDefinition(DB_COLUMN_QUEUE_POSITION, u"INTEGER NOT NULL DEFAULT -1"_s),
makeColumnDefinition(DB_COLUMN_NAME, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_CATEGORY, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_TAGS, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_TARGET_SAVE_PATH, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_CONTENT_LAYOUT, u"TEXT NOT NULL"_s),
makeColumnDefinition(DB_COLUMN_RATIO_LIMIT, u"INTEGER NOT NULL"_s),
makeColumnDefinition(DB_COLUMN_SEEDING_TIME_LIMIT, u"INTEGER NOT NULL"_s),
makeColumnDefinition(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, u"INTEGER NOT NULL"_s),
makeColumnDefinition(DB_COLUMN_SHARE_LIMIT_ACTION, u"TEXT NOT NULL DEFAULT `Default`"_s),
makeColumnDefinition(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY, u"INTEGER NOT NULL"_s),
makeColumnDefinition(DB_COLUMN_HAS_SEED_STATUS, u"INTEGER NOT NULL"_s),
makeColumnDefinition(DB_COLUMN_OPERATING_MODE, u"TEXT NOT NULL"_s),
makeColumnDefinition(DB_COLUMN_STOPPED, u"INTEGER NOT NULL"_s),
makeColumnDefinition(DB_COLUMN_STOP_CONDITION, u"TEXT NOT NULL DEFAULT `None`"_s),
makeColumnDefinition(DB_COLUMN_SSL_CERTIFICATE, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_SSL_PRIVATE_KEY, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_SSL_DH_PARAMS, u"TEXT"_s),
makeColumnDefinition(DB_COLUMN_RESUMEDATA, u"BLOB NOT NULL"_s),
makeColumnDefinition(DB_COLUMN_METADATA, u"BLOB"_s)
makeColumnDefinition(DB_COLUMN_ID, "INTEGER PRIMARY KEY"),
makeColumnDefinition(DB_COLUMN_TORRENT_ID, "BLOB NOT NULL UNIQUE"),
makeColumnDefinition(DB_COLUMN_QUEUE_POSITION, "INTEGER NOT NULL DEFAULT -1"),
makeColumnDefinition(DB_COLUMN_NAME, "TEXT"),
makeColumnDefinition(DB_COLUMN_CATEGORY, "TEXT"),
makeColumnDefinition(DB_COLUMN_TAGS, "TEXT"),
makeColumnDefinition(DB_COLUMN_TARGET_SAVE_PATH, "TEXT"),
makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"),
makeColumnDefinition(DB_COLUMN_CONTENT_LAYOUT, "TEXT NOT NULL"),
makeColumnDefinition(DB_COLUMN_RATIO_LIMIT, "INTEGER NOT NULL"),
makeColumnDefinition(DB_COLUMN_SEEDING_TIME_LIMIT, "INTEGER NOT NULL"),
makeColumnDefinition(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, "INTEGER NOT NULL"),
makeColumnDefinition(DB_COLUMN_SHARE_LIMIT_ACTION, "TEXT NOT NULL DEFAULT `Default`"),
makeColumnDefinition(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY, "INTEGER NOT NULL"),
makeColumnDefinition(DB_COLUMN_HAS_SEED_STATUS, "INTEGER NOT NULL"),
makeColumnDefinition(DB_COLUMN_OPERATING_MODE, "TEXT NOT NULL"),
makeColumnDefinition(DB_COLUMN_STOPPED, "INTEGER NOT NULL"),
makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"),
makeColumnDefinition(DB_COLUMN_SSL_CERTIFICATE, "TEXT"),
makeColumnDefinition(DB_COLUMN_SSL_PRIVATE_KEY, "TEXT"),
makeColumnDefinition(DB_COLUMN_SSL_DH_PARAMS, "TEXT"),
makeColumnDefinition(DB_COLUMN_RESUMEDATA, "BLOB NOT NULL"),
makeColumnDefinition(DB_COLUMN_METADATA, "BLOB")
};
const QString createTableTorrentsQuery = makeCreateTableStatement(DB_TABLE_TORRENTS, tableTorrentsItems);
if (!query.exec(createTableTorrentsQuery))
@ -515,7 +591,7 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
try
{
const auto addColumn = [&query](const QString &table, const Column &column, const QString &definition)
const auto addColumn = [&query](const QString &table, const Column &column, const char *definition)
{
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_s.arg(quoted(column.name), quoted(table));
if (query.exec(testQuery))
@ -527,10 +603,10 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
};
if (fromVersion <= 1)
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_DOWNLOAD_PATH, u"TEXT"_s);
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_DOWNLOAD_PATH, "TEXT");
if (fromVersion <= 2)
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_STOP_CONDITION, u"TEXT NOT NULL DEFAULT `None`"_s);
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`");
if (fromVersion <= 3)
{
@ -542,17 +618,17 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
}
if (fromVersion <= 4)
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, u"INTEGER NOT NULL DEFAULT -2"_s);
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, "INTEGER NOT NULL DEFAULT -2");
if (fromVersion <= 5)
{
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_CERTIFICATE, u"TEXT"_s);
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_PRIVATE_KEY, u"TEXT"_s);
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_DH_PARAMS, u"TEXT"_s);
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_CERTIFICATE, "TEXT");
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_PRIVATE_KEY, "TEXT");
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SSL_DH_PARAMS, "TEXT");
}
if (fromVersion <= 6)
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SHARE_LIMIT_ACTION, u"TEXT NOT NULL DEFAULT `Default`"_s);
addColumn(DB_TABLE_TORRENTS, DB_COLUMN_SHARE_LIMIT_ACTION, "TEXT NOT NULL DEFAULT `Default`");
if (fromVersion == 7)
{
@ -614,90 +690,6 @@ void BitTorrent::DBResumeDataStorage::enableWALMode() const
throw RuntimeError(tr("WAL mode is probably unsupported due to filesystem limitations."));
}
LoadResumeDataResult DBResumeDataStorage::parseQueryResultRow(const QSqlQuery &query) const
{
LoadTorrentParams resumeData;
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
if (!tagsData.isEmpty())
{
const QStringList tagList = tagsData.split(u',');
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
}
resumeData.hasFinishedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
resumeData.inactiveSeedingTimeLimit = query.value(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name).toInt();
resumeData.shareLimitAction = Utils::String::toEnum<ShareLimitAction>(
query.value(DB_COLUMN_SHARE_LIMIT_ACTION.name).toString(), ShareLimitAction::Default);
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.stopCondition = Utils::String::toEnum(
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
resumeData.sslParameters =
{
.certificate = QSslCertificate(query.value(DB_COLUMN_SSL_CERTIFICATE.name).toByteArray()),
.privateKey = Utils::SSLKey::load(query.value(DB_COLUMN_SSL_PRIVATE_KEY.name).toByteArray()),
.dhParams = query.value(DB_COLUMN_SSL_DH_PARAMS.name).toByteArray()
};
resumeData.savePath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
const auto *pref = Preferences::instance();
const int bdecodeDepthLimit = pref->getBdecodeDepthLimit();
const int bdecodeTokenLimit = pref->getBdecodeTokenLimit();
lt::error_code ec;
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
lt::add_torrent_params &p = resumeData.ltAddTorrentParams;
p = lt::read_resume_data(resumeDataRoot, ec);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
if (const QByteArray bencodedMetadata = query.value(DB_COLUMN_METADATA.name).toByteArray()
; !bencodedMetadata.isEmpty())
{
const lt::bdecode_node torentInfoRoot = lt::bdecode(bencodedMetadata, ec
, nullptr, bdecodeDepthLimit, bdecodeTokenLimit);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse torrent info: %1").arg(QString::fromStdString(ec.message())));
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse torrent info: %1").arg(QString::fromStdString(ec.message())));
}
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
.toString().toStdString();
if (p.save_path.empty())
return nonstd::make_unexpected(tr("Corrupted resume data: %1").arg(tr("save_path is invalid")));
if (p.flags & lt::torrent_flags::stop_when_ready)
{
p.flags &= ~lt::torrent_flags::stop_when_ready;
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
}
return resumeData;
}
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, QReadWriteLock &dbLock, QObject *parent)
: QThread(parent)
, m_path {dbPath}
@ -779,7 +771,7 @@ void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id)
addJob(std::make_unique<RemoveJob>(id));
}
void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QList<TorrentID> &queue)
void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue)
{
addJob(std::make_unique<StoreQueueJob>(queue));
}
@ -829,7 +821,7 @@ namespace
}
}
QList<Column> columns {
QVector<Column> columns {
DB_COLUMN_TORRENT_ID,
DB_COLUMN_NAME,
DB_COLUMN_CATEGORY,
@ -961,7 +953,7 @@ namespace
}
}
StoreQueueJob::StoreQueueJob(const QList<TorrentID> &queue)
StoreQueueJob::StoreQueueJob(const QVector<TorrentID> &queue)
: m_queue {queue}
{
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021-2025 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,9 +31,10 @@
#include <QReadWriteLock>
#include "base/pathfwd.h"
#include "base/utils/thread.h"
#include "resumedatastorage.h"
class QSqlQuery;
class QThread;
namespace BitTorrent
{
@ -46,12 +47,12 @@ namespace BitTorrent
explicit DBResumeDataStorage(const Path &dbPath, QObject *parent = nullptr);
~DBResumeDataStorage() override;
QList<TorrentID> registeredTorrents() const override;
QVector<TorrentID> registeredTorrents() const override;
LoadResumeDataResult load(const TorrentID &id) const override;
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const override;
void remove(const TorrentID &id) const override;
void storeQueue(const QList<TorrentID> &queue) const override;
void storeQueue(const QVector<TorrentID> &queue) const override;
private:
void doLoadAll() const override;
@ -59,7 +60,8 @@ namespace BitTorrent
void createDB() const;
void updateDB(int fromVersion) const;
void enableWALMode() const;
LoadResumeDataResult parseQueryResultRow(const QSqlQuery &query) const;
Utils::Thread::UniquePtr m_ioThread;
class Worker;
Worker *m_asyncWorker = nullptr;

View file

@ -133,7 +133,7 @@ int FilterParserThread::parseDATFilterFile()
return ruleCount;
}
std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QList
std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QVector
qint64 bytesRead = 0;
int offset = 0;
int start = 0;
@ -297,7 +297,7 @@ int FilterParserThread::parseP2PFilterFile()
return ruleCount;
}
std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QList
std::vector<char> buffer(BUFFER_SIZE, 0); // seems a bit faster than QVector
qint64 bytesRead = 0;
int offset = 0;
int start = 0;

View file

@ -29,9 +29,6 @@
#include "infohash.h"
#include <QHash>
#include <QString>
#include "base/global.h"
const int TorrentIDTypeId = qRegisterMetaType<BitTorrent::TorrentID>();
@ -89,28 +86,6 @@ BitTorrent::TorrentID BitTorrent::InfoHash::toTorrentID() const
#endif
}
QString BitTorrent::InfoHash::toString() const
{
// Returns a string that is suitable for logging purpose
QString ret;
ret.reserve(40 + 64 + 2); // v1 hash length + v2 hash length + comma
const SHA1Hash v1Hash = v1();
const bool v1IsValid = v1Hash.isValid();
if (v1IsValid)
ret += v1Hash.toString();
if (const SHA256Hash v2Hash = v2(); v2Hash.isValid())
{
if (v1IsValid)
ret += u", ";
ret += v2Hash.toString();
}
return ret;
}
BitTorrent::InfoHash::operator WrappedType() const
{
return m_nativeHash;

View file

@ -36,8 +36,6 @@
#include "base/digest32.h"
class QString;
using SHA1Hash = Digest32<160>;
using SHA256Hash = Digest32<256>;
@ -81,8 +79,6 @@ namespace BitTorrent
SHA256Hash v2() const;
TorrentID toTorrentID() const;
QString toString() const;
operator WrappedType() const;
private:

View file

@ -39,11 +39,7 @@ PeerAddress PeerAddress::parse(const QStringView address)
if (address.startsWith(u'[') && address.contains(u"]:"))
{ // IPv6
ipPort = address.split(u"]:");
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
ipPort[0].slice(1); // chop '['
#else
ipPort[0] = ipPort[0].sliced(1); // chop '['
#endif
ipPort[0] = ipPort[0].mid(1); // chop '['
}
else if (address.contains(u':'))
{ // IPv4

View file

@ -30,12 +30,12 @@
#include <utility>
#include <QList>
#include <QMetaObject>
#include <QMutexLocker>
#include <QThread>
#include <QVector>
const int TORRENTIDLIST_TYPEID = qRegisterMetaType<QList<BitTorrent::TorrentID>>();
const int TORRENTIDLIST_TYPEID = qRegisterMetaType<QVector<BitTorrent::TorrentID>>();
BitTorrent::ResumeDataStorage::ResumeDataStorage(const Path &path, QObject *parent)
: QObject(parent)
@ -56,7 +56,6 @@ void BitTorrent::ResumeDataStorage::loadAll() const
{
doLoadAll();
});
loadingThread->setObjectName("ResumeDataStorage::loadAll loadingThread");
connect(loadingThread, &QThread::finished, loadingThread, &QObject::deleteLater);
loadingThread->start();
}

View file

@ -58,17 +58,17 @@ namespace BitTorrent
Path path() const;
virtual QList<TorrentID> registeredTorrents() const = 0;
virtual QVector<TorrentID> registeredTorrents() const = 0;
virtual LoadResumeDataResult load(const TorrentID &id) const = 0;
virtual void store(const TorrentID &id, const LoadTorrentParams &resumeData) const = 0;
virtual void remove(const TorrentID &id) const = 0;
virtual void storeQueue(const QList<TorrentID> &queue) const = 0;
virtual void storeQueue(const QVector<TorrentID> &queue) const = 0;
void loadAll() const;
QList<LoadedResumeData> fetchLoadedResumeData() const;
signals:
void loadStarted(const QList<BitTorrent::TorrentID> &torrents);
void loadStarted(const QVector<BitTorrent::TorrentID> &torrents);
void loadFinished();
protected:

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -34,7 +34,6 @@
#include "base/pathfwd.h"
#include "base/tagset.h"
#include "addtorrenterror.h"
#include "addtorrentparams.h"
#include "categoryoptions.h"
#include "sharelimitaction.h"
@ -238,12 +237,6 @@ namespace BitTorrent
virtual Path finishedTorrentExportDirectory() const = 0;
virtual void setFinishedTorrentExportDirectory(const Path &path) = 0;
virtual bool isAddTrackersFromURLEnabled() const = 0;
virtual void setAddTrackersFromURLEnabled(bool enabled) = 0;
virtual QString additionalTrackersURL() const = 0;
virtual void setAdditionalTrackersURL(const QString &url) = 0;
virtual QString additionalTrackersFromURL() const = 0;
virtual int globalDownloadSpeedLimit() const = 0;
virtual void setGlobalDownloadSpeedLimit(int limit) = 0;
virtual int globalUploadSpeedLimit() const = 0;
@ -265,8 +258,6 @@ namespace BitTorrent
virtual void setPerformanceWarningEnabled(bool enable) = 0;
virtual int saveResumeDataInterval() const = 0;
virtual void setSaveResumeDataInterval(int value) = 0;
virtual std::chrono::minutes saveStatisticsInterval() const = 0;
virtual void setSaveStatisticsInterval(std::chrono::minutes value) = 0;
virtual int shutdownTimeout() const = 0;
virtual void setShutdownTimeout(int value) = 0;
virtual int port() const = 0;
@ -393,8 +384,6 @@ namespace BitTorrent
virtual void setIncludeOverheadInLimits(bool include) = 0;
virtual QString announceIP() const = 0;
virtual void setAnnounceIP(const QString &ip) = 0;
virtual int announcePort() const = 0;
virtual void setAnnouncePort(int port) = 0;
virtual int maxConcurrentHTTPAnnounces() const = 0;
virtual void setMaxConcurrentHTTPAnnounces(int value) = 0;
virtual bool isReannounceWhenAddressChangedEnabled() const = 0;
@ -422,8 +411,6 @@ namespace BitTorrent
virtual void setUTPRateLimited(bool limited) = 0;
virtual MixedModeAlgorithm utpMixedMode() const = 0;
virtual void setUtpMixedMode(MixedModeAlgorithm mode) = 0;
virtual int hostnameCacheTTL() const = 0;
virtual void setHostnameCacheTTL(int value) = 0;
virtual bool isIDNSupportEnabled() const = 0;
virtual void setIDNSupportEnabled(bool enabled) = 0;
virtual bool multiConnectionsPerIpEnabled() const = 0;
@ -460,7 +447,7 @@ namespace BitTorrent
virtual Torrent *getTorrent(const TorrentID &id) const = 0;
virtual Torrent *findTorrent(const InfoHash &infoHash) const = 0;
virtual QList<Torrent *> torrents() const = 0;
virtual QVector<Torrent *> torrents() const = 0;
virtual qsizetype torrentsCount() const = 0;
virtual const SessionStatus &status() const = 0;
virtual const CacheStatus &cacheStatus() const = 0;
@ -474,19 +461,14 @@ namespace BitTorrent
virtual bool downloadMetadata(const TorrentDescriptor &torrentDescr) = 0;
virtual bool cancelDownloadMetadata(const TorrentID &id) = 0;
virtual void increaseTorrentsQueuePos(const QList<TorrentID> &ids) = 0;
virtual void decreaseTorrentsQueuePos(const QList<TorrentID> &ids) = 0;
virtual void topTorrentsQueuePos(const QList<TorrentID> &ids) = 0;
virtual void bottomTorrentsQueuePos(const QList<TorrentID> &ids) = 0;
virtual QString lastExternalIPv4Address() const = 0;
virtual QString lastExternalIPv6Address() const = 0;
virtual qint64 freeDiskSpace() const = 0;
virtual void increaseTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
virtual void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
virtual void topTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
virtual void bottomTorrentsQueuePos(const QVector<TorrentID> &ids) = 0;
signals:
void startupProgressUpdated(int progress);
void addTorrentFailed(const InfoHash &infoHash, const AddTorrentError &reason);
void addTorrentFailed(const InfoHash &infoHash, const QString &reason);
void allTorrentsFinished();
void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName);
@ -513,17 +495,16 @@ namespace BitTorrent
void torrentStarted(Torrent *torrent);
void torrentSavePathChanged(Torrent *torrent);
void torrentSavingModeChanged(Torrent *torrent);
void torrentsLoaded(const QList<Torrent *> &torrents);
void torrentsUpdated(const QList<Torrent *> &torrents);
void torrentsLoaded(const QVector<Torrent *> &torrents);
void torrentsUpdated(const QVector<Torrent *> &torrents);
void torrentTagAdded(Torrent *torrent, const Tag &tag);
void torrentTagRemoved(Torrent *torrent, const Tag &tag);
void trackerError(Torrent *torrent, const QString &tracker);
void trackersAdded(Torrent *torrent, const QList<TrackerEntry> &trackers);
void trackersAdded(Torrent *torrent, const QVector<TrackerEntry> &trackers);
void trackersChanged(Torrent *torrent);
void trackersRemoved(Torrent *torrent, const QStringList &trackers);
void trackerSuccess(Torrent *torrent, const QString &tracker);
void trackerWarning(Torrent *torrent, const QString &tracker);
void trackerEntryStatusesUpdated(Torrent *torrent, const QHash<QString, TrackerEntryStatus> &updatedTrackers);
void freeDiskSpaceChecked(qint64 result);
};
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -30,6 +30,7 @@
#include "sessionimpl.h"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <ctime>
#include <queue>
@ -57,7 +58,6 @@
#include <libtorrent/session_status.hpp>
#include <libtorrent/torrent_info.hpp>
#include <QDateTime>
#include <QDeadlineTimer>
#include <QDebug>
#include <QDir>
@ -72,14 +72,13 @@
#include <QRegularExpression>
#include <QString>
#include <QThread>
#include <QThreadPool>
#include <QTimer>
#include <QUuid>
#include "base/algorithm.h"
#include "base/freediskspacechecker.h"
#include "base/global.h"
#include "base/logger.h"
#include "base/net/downloadmanager.h"
#include "base/net/proxyconfigurationmanager.h"
#include "base/preferences.h"
#include "base/profile.h"
@ -89,7 +88,6 @@
#include "base/utils/net.h"
#include "base/utils/number.h"
#include "base/utils/random.h"
#include "base/utils/string.h"
#include "base/version.h"
#include "bandwidthscheduler.h"
#include "bencoderesumedatastorage.h"
@ -109,20 +107,19 @@
#include "torrentimpl.h"
#include "tracker.h"
#include "trackerentry.h"
#include "trackerentrystatus.h"
using namespace std::chrono_literals;
using namespace BitTorrent;
const Path CATEGORIES_FILE_NAME {u"categories.json"_s};
const int MAX_PROCESSING_RESUMEDATA_COUNT = 50;
const std::chrono::seconds FREEDISKSPACE_CHECK_TIMEOUT = 30s;
const int STATISTICS_SAVE_INTERVAL = std::chrono::milliseconds(15min).count();
namespace
{
const char PEER_ID[] = "qB";
const auto USER_AGENT = QStringLiteral("qBittorrent/" QBT_VERSION_2);
const QString DEFAULT_DHT_BOOTSTRAP_NODES = u"dht.libtorrent.org:25401, dht.transmissionbt.com:6881, router.bittorrent.com:6881"_s;
const QString DEFAULT_DHT_BOOTSTRAP_NODES = u"dht.libtorrent.org:25401, dht.transmissionbt.com:6881, router.bittorrent.com:6881, router.utorrent.com:6881, dht.aelitis.com:6881"_s;
void torrentQueuePositionUp(const lt::torrent_handle &handle)
{
@ -228,7 +225,7 @@ namespace
{
try
{
return Utils::String::fromLatin1(address.to_string());
return QString::fromLatin1(address.to_string().c_str());
}
catch (const std::exception &)
{
@ -302,15 +299,14 @@ namespace
{
switch (mode)
{
default:
Q_ASSERT(false);
case MoveStorageMode::FailIfExist:
return lt::move_flags_t::fail_if_exist;
case MoveStorageMode::KeepExistingFiles:
return lt::move_flags_t::dont_replace;
case MoveStorageMode::Overwrite:
return lt::move_flags_t::always_replace_files;
default:
Q_UNREACHABLE();
break;
}
}
}
@ -365,7 +361,7 @@ QString Session::subcategoryName(const QString &category)
{
const int sepIndex = category.lastIndexOf(u'/');
if (sepIndex >= 0)
return category.sliced(sepIndex + 1);
return category.mid(sepIndex + 1);
return category;
}
@ -374,7 +370,7 @@ QString Session::parentCategoryName(const QString &category)
{
const int sepIndex = category.lastIndexOf(u'/');
if (sepIndex >= 0)
return category.first(sepIndex);
return category.left(sepIndex);
return {};
}
@ -385,7 +381,7 @@ QStringList Session::expandCategory(const QString &category)
int index = 0;
while ((index = category.indexOf(u'/', index)) >= 0)
{
result << category.first(index);
result << category.left(index);
++index;
}
result << category;
@ -447,7 +443,6 @@ SessionImpl::SessionImpl(QObject *parent)
, m_ignoreLimitsOnLAN(BITTORRENT_SESSION_KEY(u"IgnoreLimitsOnLAN"_s), false)
, m_includeOverheadInLimits(BITTORRENT_SESSION_KEY(u"IncludeOverheadInLimits"_s), false)
, m_announceIP(BITTORRENT_SESSION_KEY(u"AnnounceIP"_s))
, m_announcePort(BITTORRENT_SESSION_KEY(u"AnnouncePort"_s), 0)
, m_maxConcurrentHTTPAnnounces(BITTORRENT_SESSION_KEY(u"MaxConcurrentHTTPAnnounces"_s), 50)
, m_isReannounceWhenAddressChangedEnabled(BITTORRENT_SESSION_KEY(u"ReannounceWhenAddressChanged"_s), false)
, m_stopTrackerTimeout(BITTORRENT_SESSION_KEY(u"StopTrackerTimeout"_s), 2)
@ -460,7 +455,6 @@ SessionImpl::SessionImpl(QObject *parent)
, m_isUTPRateLimited(BITTORRENT_SESSION_KEY(u"uTPRateLimited"_s), true)
, m_utpMixedMode(BITTORRENT_SESSION_KEY(u"uTPMixedMode"_s), MixedModeAlgorithm::TCP
, clampValue(MixedModeAlgorithm::TCP, MixedModeAlgorithm::Proportional))
, m_hostnameCacheTTL(BITTORRENT_SESSION_KEY(u"HostnameCacheTTL"_s), 1200)
, m_IDNSupportEnabled(BITTORRENT_SESSION_KEY(u"IDNSupportEnabled"_s), false)
, m_multiConnectionsPerIpEnabled(BITTORRENT_SESSION_KEY(u"MultiConnectionsPerIp"_s), false)
, m_validateHTTPSTrackerCertificate(BITTORRENT_SESSION_KEY(u"ValidateHTTPSTrackerCertificate"_s), true)
@ -468,13 +462,9 @@ SessionImpl::SessionImpl(QObject *parent)
, m_blockPeersOnPrivilegedPorts(BITTORRENT_SESSION_KEY(u"BlockPeersOnPrivilegedPorts"_s), false)
, m_isAddTrackersEnabled(BITTORRENT_SESSION_KEY(u"AddTrackersEnabled"_s), false)
, m_additionalTrackers(BITTORRENT_SESSION_KEY(u"AdditionalTrackers"_s))
, m_isAddTrackersFromURLEnabled(BITTORRENT_SESSION_KEY(u"AddTrackersFromURLEnabled"_s), false)
, m_additionalTrackersURL(BITTORRENT_SESSION_KEY(u"AdditionalTrackersURL"_s))
, m_globalMaxRatio(BITTORRENT_SESSION_KEY(u"GlobalMaxRatio"_s), -1, [](qreal r) { return r < 0 ? -1. : r;})
, m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_s), Torrent::NO_SEEDING_TIME_LIMIT
, clampValue(Torrent::NO_SEEDING_TIME_LIMIT, Torrent::MAX_SEEDING_TIME))
, m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s), Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT
, clampValue(Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT, Torrent::MAX_INACTIVE_SEEDING_TIME))
, m_globalMaxSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxSeedingMinutes"_s), -1, lowerLimited(-1))
, m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s), -1, lowerLimited(-1))
, m_isAddTorrentToQueueTop(BITTORRENT_SESSION_KEY(u"AddTorrentToTopOfQueue"_s), false)
, m_isAddTorrentStopped(BITTORRENT_SESSION_KEY(u"AddTorrentStopped"_s), false)
, m_torrentStopCondition(BITTORRENT_SESSION_KEY(u"TorrentStopCondition"_s), Torrent::StopCondition::None)
@ -493,7 +483,6 @@ SessionImpl::SessionImpl(QObject *parent)
, m_isBandwidthSchedulerEnabled(BITTORRENT_SESSION_KEY(u"BandwidthSchedulerEnabled"_s), false)
, m_isPerformanceWarningEnabled(BITTORRENT_SESSION_KEY(u"PerformanceWarning"_s), false)
, m_saveResumeDataInterval(BITTORRENT_SESSION_KEY(u"SaveResumeDataInterval"_s), 60)
, m_saveStatisticsInterval(BITTORRENT_SESSION_KEY(u"SaveStatisticsInterval"_s), 15)
, m_shutdownTimeout(BITTORRENT_SESSION_KEY(u"ShutdownTimeout"_s), -1)
, m_port(BITTORRENT_SESSION_KEY(u"Port"_s), -1)
, m_sslEnabled(BITTORRENT_SESSION_KEY(u"SSL/Enabled"_s), false)
@ -545,14 +534,9 @@ SessionImpl::SessionImpl(QObject *parent)
, m_ioThread {new QThread}
, m_asyncWorker {new QThreadPool(this)}
, m_recentErroredTorrentsTimer {new QTimer(this)}
, m_freeDiskSpaceChecker {new FreeDiskSpaceChecker(savePath())}
, m_freeDiskSpaceCheckingTimer {new QTimer(this)}
{
// It is required to perform async access to libtorrent sequentially
m_asyncWorker->setMaxThreadCount(1);
m_asyncWorker->setObjectName("SessionImpl m_asyncWorker");
m_alerts.reserve(1024);
if (port() < 0)
m_port = Utils::Random::rand(1024, 65535);
@ -607,18 +591,6 @@ SessionImpl::SessionImpl(QObject *parent)
, &Net::ProxyConfigurationManager::proxyConfigurationChanged
, this, &SessionImpl::configureDeferred);
m_freeDiskSpaceChecker->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_freeDiskSpaceChecker, &QObject::deleteLater);
m_freeDiskSpaceCheckingTimer->setInterval(FREEDISKSPACE_CHECK_TIMEOUT);
m_freeDiskSpaceCheckingTimer->setSingleShot(true);
connect(m_freeDiskSpaceCheckingTimer, &QTimer::timeout, m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::check);
connect(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::checked, this, [this](const qint64 value)
{
m_freeDiskSpace = value;
m_freeDiskSpaceCheckingTimer->start();
emit freeDiskSpaceChecked(m_freeDiskSpace);
});
m_fileSearcher = new FileSearcher;
m_fileSearcher->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_fileSearcher, &QObject::deleteLater);
@ -629,11 +601,8 @@ SessionImpl::SessionImpl(QObject *parent)
connect(m_ioThread.get(), &QThread::finished, m_torrentContentRemover, &QObject::deleteLater);
connect(m_torrentContentRemover, &TorrentContentRemover::jobFinished, this, &SessionImpl::torrentContentRemovingFinished);
m_ioThread->setObjectName("SessionImpl m_ioThread");
m_ioThread->start();
QMetaObject::invokeMethod(m_freeDiskSpaceChecker, &FreeDiskSpaceChecker::check);
initMetrics();
loadStatistics();
@ -644,15 +613,6 @@ SessionImpl::SessionImpl(QObject *parent)
enableTracker(isTrackerEnabled());
prepareStartup();
m_updateTrackersFromURLTimer = new QTimer(this);
m_updateTrackersFromURLTimer->setInterval(24h);
connect(m_updateTrackersFromURLTimer, &QTimer::timeout, this, &SessionImpl::updateTrackersFromURL);
if (isAddTrackersFromURLEnabled())
{
updateTrackersFromURL();
m_updateTrackersFromURLTimer->start();
}
}
SessionImpl::~SessionImpl()
@ -700,7 +660,6 @@ SessionImpl::~SessionImpl()
qDebug("Deleting libtorrent session...");
delete nativeSessionProxy;
});
sessionTerminateThread->setObjectName("~SessionImpl sessionTerminateThread");
connect(sessionTerminateThread, &QThread::finished, sessionTerminateThread, &QObject::deleteLater);
sessionTerminateThread->start();
if (sessionTerminateThread->wait(shutdownDeadlineTimer))
@ -995,25 +954,23 @@ bool SessionImpl::editCategory(const QString &name, const CategoryOptions &optio
if (options == currentOptions)
return false;
currentOptions = options;
storeCategories();
if (isDisableAutoTMMWhenCategorySavePathChanged())
{
// This should be done before changing the category options
// to prevent the torrent from being moved at the new save path.
for (TorrentImpl *const torrent : asConst(m_torrents))
{
if (torrent->category() == name)
torrent->setAutoTMMEnabled(false);
}
}
currentOptions = options;
storeCategories();
for (TorrentImpl *const torrent : asConst(m_torrents))
else
{
if (torrent->category() == name)
torrent->handleCategoryOptionsChanged();
for (TorrentImpl *const torrent : asConst(m_torrents))
{
if (torrent->category() == name)
torrent->handleCategoryOptionsChanged();
}
}
emit categoryOptionsChanged(name);
@ -1257,7 +1214,8 @@ int SessionImpl::globalMaxSeedingMinutes() const
void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
{
minutes = std::clamp(minutes, Torrent::NO_SEEDING_TIME_LIMIT, Torrent::MAX_SEEDING_TIME);
if (minutes < 0)
minutes = -1;
if (minutes != globalMaxSeedingMinutes())
{
@ -1273,7 +1231,7 @@ int SessionImpl::globalMaxInactiveSeedingMinutes() const
void SessionImpl::setGlobalMaxInactiveSeedingMinutes(int minutes)
{
minutes = std::clamp(minutes, Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT, Torrent::MAX_INACTIVE_SEEDING_TIME);
minutes = std::max(minutes, -1);
if (minutes != globalMaxInactiveSeedingMinutes())
{
@ -1349,7 +1307,7 @@ void SessionImpl::prepareStartup()
context->startupStorage = m_resumeDataStorage;
connect(context->startupStorage, &ResumeDataStorage::loadStarted, context
, [this, context](const QList<TorrentID> &torrents)
, [this, context](const QVector<TorrentID> &torrents)
{
context->totalResumeDataCount = torrents.size();
#ifdef QBT_USES_LIBTORRENT2
@ -1639,24 +1597,20 @@ void SessionImpl::endStartup(ResumeSessionContext *context)
m_resumeDataTimer->start();
}
auto wakeupCheckTimer = new QTimer(this);
connect(wakeupCheckTimer, &QTimer::timeout, this, [this]
m_wakeupCheckTimer = new QTimer(this);
connect(m_wakeupCheckTimer, &QTimer::timeout, this, [this]
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
const bool hasSystemSlept = m_wakeupCheckTimestamp.durationElapsed() > 100s;
#else
const bool hasSystemSlept = m_wakeupCheckTimestamp.elapsed() > std::chrono::milliseconds(100s).count();
#endif
if (hasSystemSlept)
const auto now = QDateTime::currentDateTime();
if (m_wakeupCheckTimestamp.secsTo(now) > 100)
{
LogMsg(tr("System wake-up event detected. Re-announcing to all the trackers..."));
reannounceToAllTrackers();
}
m_wakeupCheckTimestamp.start();
m_wakeupCheckTimestamp = now;
});
m_wakeupCheckTimestamp.start();
wakeupCheckTimer->start(30s);
m_wakeupCheckTimestamp = QDateTime::currentDateTime();
m_wakeupCheckTimer->start(30s);
m_isRestored = true;
emit startupProgressUpdated(100);
@ -2027,10 +1981,6 @@ lt::settings_pack SessionImpl::loadLTSettings() const
settingsPack.set_bool(lt::settings_pack::rate_limit_ip_overhead, includeOverheadInLimits());
// IP address to announce to trackers
settingsPack.set_str(lt::settings_pack::announce_ip, announceIP().toStdString());
#if LIBTORRENT_VERSION_NUM >= 20011
// Port to announce to trackers
settingsPack.set_int(lt::settings_pack::announce_port, announcePort());
#endif
// Max concurrent HTTP announces
settingsPack.set_int(lt::settings_pack::max_concurrent_http_announces, maxConcurrentHTTPAnnounces());
// Stop tracker timeout
@ -2076,8 +2026,6 @@ lt::settings_pack SessionImpl::loadLTSettings() const
break;
}
settingsPack.set_int(lt::settings_pack::resolver_cache_timeout, hostnameCacheTTL());
settingsPack.set_bool(lt::settings_pack::allow_idna, isIDNSupportEnabled());
settingsPack.set_bool(lt::settings_pack::allow_multiple_connections_per_ip, multiConnectionsPerIpEnabled());
@ -2311,11 +2259,6 @@ void SessionImpl::populateAdditionalTrackers()
m_additionalTrackerEntries = parseTrackerEntries(additionalTrackers());
}
void SessionImpl::populateAdditionalTrackersFromURL()
{
m_additionalTrackerEntriesFromURL = parseTrackerEntries(additionalTrackersFromURL());
}
void SessionImpl::processTorrentShareLimits(TorrentImpl *torrent)
{
if (!torrent->isFinished() || torrent->isForced())
@ -2485,12 +2428,12 @@ bool SessionImpl::removeTorrent(const TorrentID &id, const TorrentRemoveOption d
m_removingTorrents[torrentID] = {torrentName, torrent->actualStorageLocation(), {}, deleteOption};
const lt::torrent_handle nativeHandle {torrent->nativeHandle()};
const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
const auto iter = std::find_if(m_moveStorageQueue.begin(), m_moveStorageQueue.end()
, [&nativeHandle](const MoveStorageJob &job)
{
return job.torrentHandle == nativeHandle;
});
if (iter != m_moveStorageQueue.cend())
if (iter != m_moveStorageQueue.end())
{
// We shouldn't actually remove torrent until existing "move storage jobs" are done
torrentQueuePositionBottom(nativeHandle);
@ -2510,12 +2453,12 @@ bool SessionImpl::removeTorrent(const TorrentID &id, const TorrentRemoveOption d
{
// Delete "move storage job" for the deleted torrent
// (note: we shouldn't delete active job)
const auto iter = std::find_if((m_moveStorageQueue.cbegin() + 1), m_moveStorageQueue.cend()
const auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
, [torrent](const MoveStorageJob &job)
{
return job.torrentHandle == torrent->nativeHandle();
});
if (iter != m_moveStorageQueue.cend())
if (iter != m_moveStorageQueue.end())
m_moveStorageQueue.erase(iter);
}
@ -2532,8 +2475,8 @@ bool SessionImpl::removeTorrent(const TorrentID &id, const TorrentRemoveOption d
bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
{
const auto downloadedMetadataIter = m_downloadedMetadata.constFind(id);
if (downloadedMetadataIter == m_downloadedMetadata.cend())
const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
if (downloadedMetadataIter == m_downloadedMetadata.end())
return false;
const lt::torrent_handle nativeHandle = downloadedMetadataIter.value();
@ -2557,7 +2500,7 @@ bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
return true;
}
void SessionImpl::increaseTorrentsQueuePos(const QList<TorrentID> &ids)
void SessionImpl::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType
@ -2584,7 +2527,7 @@ void SessionImpl::increaseTorrentsQueuePos(const QList<TorrentID> &ids)
m_torrentsQueueChanged = true;
}
void SessionImpl::decreaseTorrentsQueuePos(const QList<TorrentID> &ids)
void SessionImpl::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType> torrentQueue;
@ -2612,7 +2555,7 @@ void SessionImpl::decreaseTorrentsQueuePos(const QList<TorrentID> &ids)
m_torrentsQueueChanged = true;
}
void SessionImpl::topTorrentsQueuePos(const QList<TorrentID> &ids)
void SessionImpl::topTorrentsQueuePos(const QVector<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType> torrentQueue;
@ -2637,7 +2580,7 @@ void SessionImpl::topTorrentsQueuePos(const QList<TorrentID> &ids)
m_torrentsQueueChanged = true;
}
void SessionImpl::bottomTorrentsQueuePos(const QList<TorrentID> &ids)
void SessionImpl::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType
@ -2673,9 +2616,9 @@ void SessionImpl::handleTorrentResumeDataRequested(const TorrentImpl *torrent)
++m_numResumeData;
}
QList<Torrent *> SessionImpl::torrents() const
QVector<Torrent *> SessionImpl::torrents() const
{
QList<Torrent *> result;
QVector<Torrent *> result;
result.reserve(m_torrents.size());
for (TorrentImpl *torrent : asConst(m_torrents))
result << torrent;
@ -2775,10 +2718,7 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
// We should not add the torrent if it is already
// processed or is pending to add to session
if (m_loadingTorrents.contains(id) || (infoHash.isHybrid() && m_loadingTorrents.contains(altID)))
{
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, tr("Duplicate torrent")});
return false;
}
if (Torrent *torrent = findTorrent(infoHash))
{
@ -2792,20 +2732,16 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
if (!isMergeTrackersEnabled())
{
const QString message = tr("Merging of trackers is disabled");
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: \"%1\". Torrent infohash: %2. Result: %3")
.arg(torrent->name(), torrent->infoHash().toString(), message));
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, message});
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
.arg(torrent->name(), tr("Merging of trackers is disabled")));
return false;
}
const bool isPrivate = torrent->isPrivate() || (hasMetadata && source.info()->isPrivate());
if (isPrivate)
{
const QString message = tr("Trackers cannot be merged because it is a private torrent");
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: \"%1\". Torrent infohash: %2. Result: %3")
.arg(torrent->name(), torrent->infoHash().toString(), message));
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, message});
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
.arg(torrent->name(), tr("Trackers cannot be merged because it is a private torrent")));
return false;
}
@ -2813,10 +2749,8 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
torrent->addTrackers(source.trackers());
torrent->addUrlSeeds(source.urlSeeds());
const QString message = tr("Trackers are merged from new source");
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: \"%1\". Torrent infohash: %2. Result: %3")
.arg(torrent->name(), torrent->infoHash().toString(), message));
emit addTorrentFailed(infoHash, {AddTorrentError::DuplicateTorrent, message});
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
.arg(torrent->name(), tr("Trackers are merged from new source")));
return false;
}
@ -2944,21 +2878,6 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
}
}
if (isAddTrackersFromURLEnabled() && !(hasMetadata && p.ti->priv()))
{
const auto maxTierIter = std::max_element(p.tracker_tiers.cbegin(), p.tracker_tiers.cend());
const int baseTier = (maxTierIter != p.tracker_tiers.cend()) ? (*maxTierIter + 1) : 0;
p.trackers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerEntriesFromURL.size()));
p.tracker_tiers.reserve(p.trackers.size() + static_cast<std::size_t>(m_additionalTrackerEntriesFromURL.size()));
p.tracker_tiers.resize(p.trackers.size(), 0);
for (const TrackerEntry &trackerEntry : asConst(m_additionalTrackerEntriesFromURL))
{
p.trackers.emplace_back(trackerEntry.url.toStdString());
p.tracker_tiers.emplace_back(Utils::Number::clampingAdd(trackerEntry.tier, baseTier));
}
}
p.upload_limit = addTorrentParams.uploadLimit;
p.download_limit = addTorrentParams.downloadLimit;
@ -3093,6 +3012,11 @@ void SessionImpl::removeMappedPorts(const QSet<quint16> &ports)
});
}
void SessionImpl::invokeAsync(std::function<void ()> func)
{
m_asyncWorker->start(std::move(func));
}
// Add a torrent to libtorrent session in hidden mode
// and force it to download its metadata
bool SessionImpl::downloadMetadata(const TorrentDescriptor &torrentDescr)
@ -3227,10 +3151,10 @@ void SessionImpl::saveResumeData()
break;
}
fetchPendingAlerts(waitTime);
const std::vector<lt::alert *> alerts = getPendingAlerts(waitTime);
bool hasWantedAlert = false;
for (const lt::alert *alert : m_alerts)
for (const lt::alert *alert : alerts)
{
if (const int alertType = alert->type();
(alertType == lt::save_resume_data_alert::alert_type) || (alertType == lt::save_resume_data_failed_alert::alert_type)
@ -3250,7 +3174,7 @@ void SessionImpl::saveResumeData()
void SessionImpl::saveTorrentsQueue()
{
QList<TorrentID> queue;
QVector<TorrentID> queue;
for (const TorrentImpl *torrent : asConst(m_torrents))
{
if (const int queuePos = torrent->queuePosition(); queuePos >= 0)
@ -3280,9 +3204,6 @@ void SessionImpl::setSavePath(const Path &path)
if (isDisableAutoTMMWhenDefaultSavePathChanged())
{
// This should be done before changing the save path
// to prevent the torrent from being moved at the new save path.
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
{
@ -3302,14 +3223,6 @@ void SessionImpl::setSavePath(const Path &path)
m_savePath = newPath;
for (TorrentImpl *const torrent : asConst(m_torrents))
torrent->handleCategoryOptionsChanged();
m_freeDiskSpace = -1;
m_freeDiskSpaceCheckingTimer->stop();
QMetaObject::invokeMethod(m_freeDiskSpaceChecker, [checker = m_freeDiskSpaceChecker, pathToCheck = m_savePath]
{
checker->setPathToCheck(pathToCheck);
checker->check();
});
}
void SessionImpl::setDownloadPath(const Path &path)
@ -3320,9 +3233,6 @@ void SessionImpl::setDownloadPath(const Path &path)
if (isDisableAutoTMMWhenDefaultSavePathChanged())
{
// This should be done before changing the save path
// to prevent the torrent from being moved at the new save path.
QSet<QString> affectedCatogories {{}}; // includes default (unnamed) category
for (auto it = m_categories.cbegin(); it != m_categories.cend(); ++it)
{
@ -3649,16 +3559,6 @@ void SessionImpl::setSaveResumeDataInterval(const int value)
}
}
std::chrono::minutes SessionImpl::saveStatisticsInterval() const
{
return std::chrono::minutes(m_saveStatisticsInterval);
}
void SessionImpl::setSaveStatisticsInterval(const std::chrono::minutes timeInMinutes)
{
m_saveStatisticsInterval = timeInMinutes.count();
}
int SessionImpl::shutdownTimeout() const
{
return m_shutdownTimeout;
@ -3965,82 +3865,6 @@ void SessionImpl::setAdditionalTrackers(const QString &trackers)
populateAdditionalTrackers();
}
bool SessionImpl::isAddTrackersFromURLEnabled() const
{
return m_isAddTrackersFromURLEnabled;
}
void SessionImpl::setAddTrackersFromURLEnabled(const bool enabled)
{
if (enabled != isAddTrackersFromURLEnabled())
{
m_isAddTrackersFromURLEnabled = enabled;
if (enabled)
{
updateTrackersFromURL();
m_updateTrackersFromURLTimer->start();
}
else
{
m_updateTrackersFromURLTimer->stop();
setAdditionalTrackersFromURL({});
}
}
}
QString SessionImpl::additionalTrackersURL() const
{
return m_additionalTrackersURL;
}
void SessionImpl::setAdditionalTrackersURL(const QString &url)
{
if (url != additionalTrackersURL())
{
m_additionalTrackersURL = url.trimmed();
if (isAddTrackersFromURLEnabled())
updateTrackersFromURL();
}
}
QString SessionImpl::additionalTrackersFromURL() const
{
return m_additionalTrackersFromURL;
}
void SessionImpl::setAdditionalTrackersFromURL(const QString &trackers)
{
if (trackers != additionalTrackersFromURL())
{
m_additionalTrackersFromURL = trackers;
populateAdditionalTrackersFromURL();
}
}
void SessionImpl::updateTrackersFromURL()
{
const QString url = additionalTrackersURL();
if (url.isEmpty())
{
setAdditionalTrackersFromURL({});
}
else
{
Net::DownloadManager::instance()->download(Net::DownloadRequest(url)
, Preferences::instance()->useProxyForGeneralPurposes(), this, [this](const Net::DownloadResult &result)
{
if (result.status == Net::DownloadStatus::Success)
{
setAdditionalTrackersFromURL(QString::fromUtf8(result.data));
LogMsg(tr("Tracker list updated"), Log::INFO);
return;
}
LogMsg(tr("Failed to update tracker list. Reason: \"%1\"").arg(result.errorString), Log::WARNING);
});
}
}
bool SessionImpl::isIPFilteringEnabled() const
{
return m_isIPFilteringEnabled;
@ -4931,20 +4755,6 @@ void SessionImpl::setAnnounceIP(const QString &ip)
}
}
int SessionImpl::announcePort() const
{
return m_announcePort;
}
void SessionImpl::setAnnouncePort(const int port)
{
if (port != m_announcePort)
{
m_announcePort = port;
configureDeferred();
}
}
int SessionImpl::maxConcurrentHTTPAnnounces() const
{
return m_maxConcurrentHTTPAnnounces;
@ -5071,20 +4881,6 @@ void SessionImpl::setUtpMixedMode(const MixedModeAlgorithm mode)
configureDeferred();
}
int SessionImpl::hostnameCacheTTL() const
{
return m_hostnameCacheTTL;
}
void SessionImpl::setHostnameCacheTTL(const int value)
{
if (value == hostnameCacheTTL())
return;
m_hostnameCacheTTL = value;
configureDeferred();
}
bool SessionImpl::isIDNSupportEnabled() const
{
return m_IDNSupportEnabled;
@ -5164,21 +4960,6 @@ void SessionImpl::setTrackerFilteringEnabled(const bool enabled)
}
}
QString SessionImpl::lastExternalIPv4Address() const
{
return m_lastExternalIPv4Address;
}
QString SessionImpl::lastExternalIPv6Address() const
{
return m_lastExternalIPv6Address;
}
qint64 SessionImpl::freeDiskSpace() const
{
return m_freeDiskSpace;
}
bool SessionImpl::isListening() const
{
return m_nativeSessionExtension->isSessionListening();
@ -5260,7 +5041,7 @@ void SessionImpl::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
emit torrentSavingModeChanged(torrent);
}
void SessionImpl::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QList<TrackerEntry> &newTrackers)
void SessionImpl::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
{
for (const TrackerEntry &newTracker : newTrackers)
LogMsg(tr("Added tracker to torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), newTracker.url));
@ -5279,13 +5060,13 @@ void SessionImpl::handleTorrentTrackersChanged(TorrentImpl *const torrent)
emit trackersChanged(torrent);
}
void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QList<QUrl> &newUrlSeeds)
void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
{
for (const QUrl &newUrlSeed : newUrlSeeds)
LogMsg(tr("Added URL seed to torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), newUrlSeed.toString()));
}
void SessionImpl::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QList<QUrl> &urlSeeds)
void SessionImpl::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds)
{
for (const QUrl &urlSeed : urlSeeds)
LogMsg(tr("Removed URL seed from torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), urlSeed.toString()));
@ -5303,7 +5084,7 @@ void SessionImpl::handleTorrentStopped(TorrentImpl *const torrent)
{
torrent->resetTrackerEntryStatuses();
const QList<TrackerEntryStatus> trackers = torrent->trackers();
const QVector<TrackerEntryStatus> trackers = torrent->trackers();
QHash<QString, TrackerEntryStatus> updatedTrackers;
updatedTrackers.reserve(trackers.size());
@ -5334,8 +5115,8 @@ void SessionImpl::handleTorrentFinished(TorrentImpl *const torrent)
void SessionImpl::handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data)
{
m_resumeDataStorage->store(torrent->id(), data);
const auto iter = m_changedTorrentIDs.constFind(torrent->id());
if (iter != m_changedTorrentIDs.cend())
const auto iter = m_changedTorrentIDs.find(torrent->id());
if (iter != m_changedTorrentIDs.end())
{
m_resumeDataStorage->remove(iter.value());
m_changedTorrentIDs.erase(iter);
@ -5368,11 +5149,11 @@ bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &new
const lt::torrent_handle torrentHandle = torrent->nativeHandle();
const Path currentLocation = torrent->actualStorageLocation();
const bool torrentHasActiveJob = !m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.constFirst().torrentHandle == torrentHandle);
const bool torrentHasActiveJob = !m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.first().torrentHandle == torrentHandle);
if (m_moveStorageQueue.size() > 1)
{
auto iter = std::find_if((m_moveStorageQueue.cbegin() + 1), m_moveStorageQueue.cend()
auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
, [&torrentHandle](const MoveStorageJob &job)
{
return job.torrentHandle == torrentHandle;
@ -5391,7 +5172,7 @@ bool SessionImpl::addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &new
{
// if there is active job for this torrent prevent creating meaningless
// job that will move torrent to the same location as current one
if (m_moveStorageQueue.constFirst().path == newPath)
if (m_moveStorageQueue.first().path == newPath)
{
LogMsg(tr("Failed to enqueue torrent move. Torrent: \"%1\". Source: \"%2\". Destination: \"%3\". Reason: torrent is currently moving to the destination")
.arg(torrent->name(), currentLocation.toString(), newPath.toString()));
@ -5436,7 +5217,7 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
{
const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
if (!m_moveStorageQueue.isEmpty())
moveTorrentStorage(m_moveStorageQueue.constFirst());
moveTorrentStorage(m_moveStorageQueue.first());
const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
, [&finishedJob](const MoveStorageJob &job)
@ -5690,13 +5471,14 @@ void SessionImpl::handleIPFilterError()
emit IPFilterParsed(true, 0);
}
void SessionImpl::fetchPendingAlerts(const lt::time_duration time)
std::vector<lt::alert *> SessionImpl::getPendingAlerts(const lt::time_duration time) const
{
if (time > lt::time_duration::zero())
m_nativeSession->wait_for_alert(time);
m_alerts.clear();
m_nativeSession->pop_alerts(&m_alerts);
std::vector<lt::alert *> alerts;
m_nativeSession->pop_alerts(&alerts);
return alerts;
}
TorrentContentLayout SessionImpl::torrentContentLayout() const
@ -5712,7 +5494,12 @@ void SessionImpl::setTorrentContentLayout(const TorrentContentLayout value)
// Read alerts sent by libtorrent session
void SessionImpl::readAlerts()
{
fetchPendingAlerts();
// cache current datetime of Qt and libtorrent clocks in order
// to optimize conversion of time points from lt to Qt clocks
m_ltNow = lt::clock_type::now();
m_qNow = QDateTime::currentDateTime();
const std::vector<lt::alert *> alerts = getPendingAlerts();
Q_ASSERT(m_loadedTorrents.isEmpty());
Q_ASSERT(m_receivedAddTorrentAlertsCount == 0);
@ -5720,7 +5507,7 @@ void SessionImpl::readAlerts()
if (!isRestored())
m_loadedTorrents.reserve(MAX_PROCESSING_RESUMEDATA_COUNT);
for (const lt::alert *a : m_alerts)
for (const lt::alert *a : alerts)
handleAlert(a);
if (m_receivedAddTorrentAlertsCount > 0)
@ -5762,16 +5549,14 @@ void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
#else
const InfoHash infoHash {(hasMetadata ? params.ti->info_hash() : params.info_hash)};
#endif
if (const auto loadingTorrentsIter = m_loadingTorrents.constFind(TorrentID::fromInfoHash(infoHash))
; loadingTorrentsIter != m_loadingTorrents.cend())
if (const auto loadingTorrentsIter = m_loadingTorrents.find(TorrentID::fromInfoHash(infoHash))
; loadingTorrentsIter != m_loadingTorrents.end())
{
const AddTorrentError::Kind errorKind = (alert->error == lt::errors::duplicate_torrent)
? AddTorrentError::DuplicateTorrent : AddTorrentError::Other;
emit addTorrentFailed(infoHash, {errorKind, msg});
emit addTorrentFailed(infoHash, msg);
m_loadingTorrents.erase(loadingTorrentsIter);
}
else if (const auto downloadedMetadataIter = m_downloadedMetadata.constFind(TorrentID::fromInfoHash(infoHash))
; downloadedMetadataIter != m_downloadedMetadata.cend())
else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(TorrentID::fromInfoHash(infoHash))
; downloadedMetadataIter != m_downloadedMetadata.end())
{
m_downloadedMetadata.erase(downloadedMetadataIter);
if (infoHash.isHybrid())
@ -5792,8 +5577,8 @@ void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
#endif
const auto torrentID = TorrentID::fromInfoHash(infoHash);
if (const auto loadingTorrentsIter = m_loadingTorrents.constFind(torrentID)
; loadingTorrentsIter != m_loadingTorrents.cend())
if (const auto loadingTorrentsIter = m_loadingTorrents.find(torrentID)
; loadingTorrentsIter != m_loadingTorrents.end())
{
const LoadTorrentParams params = loadingTorrentsIter.value();
m_loadingTorrents.erase(loadingTorrentsIter);
@ -6027,7 +5812,7 @@ void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed
#else
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
#endif
const auto errorMessage = alert->error ? Utils::String::fromLocal8Bit(alert->error.message()) : QString();
const auto errorMessage = alert->error ? QString::fromLocal8Bit(alert->error.message().c_str()) : QString();
handleRemovedTorrent(torrentID, errorMessage);
}
@ -6056,7 +5841,7 @@ void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
const TorrentID torrentID {alert->handle.info_hash()};
bool found = false;
if (const auto iter = m_downloadedMetadata.constFind(torrentID); iter != m_downloadedMetadata.cend())
if (const auto iter = m_downloadedMetadata.find(torrentID); iter != m_downloadedMetadata.end())
{
found = true;
m_downloadedMetadata.erase(iter);
@ -6066,7 +5851,7 @@ void SessionImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
if (infoHash.isHybrid())
{
const auto altID = TorrentID::fromSHA1Hash(infoHash.v1());
if (const auto iter = m_downloadedMetadata.constFind(altID); iter != m_downloadedMetadata.cend())
if (const auto iter = m_downloadedMetadata.find(altID); iter != m_downloadedMetadata.end())
{
found = true;
m_downloadedMetadata.erase(iter);
@ -6161,7 +5946,7 @@ void SessionImpl::handleUrlSeedAlert(const lt::url_seed_alert *alert)
if (alert->error)
{
LogMsg(tr("URL seed connection failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
LogMsg(tr("URL seed DNS lookup failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
.arg(torrent->name(), QString::fromUtf8(alert->server_url()), QString::fromStdString(alert->message()))
, Log::WARNING);
}
@ -6185,7 +5970,7 @@ void SessionImpl::handleListenFailedAlert(const lt::listen_failed_alert *alert)
const QString proto {toString(alert->socket_type)};
LogMsg(tr("Failed to listen on IP. IP: \"%1\". Port: \"%2/%3\". Reason: \"%4\"")
.arg(toString(alert->address), proto, QString::number(alert->port)
, Utils::String::fromLocal8Bit(alert->error.message())), Log::CRITICAL);
, QString::fromLocal8Bit(alert->error.message().c_str())), Log::CRITICAL);
}
void SessionImpl::handleExternalIPAlert(const lt::external_ip_alert *alert)
@ -6194,19 +5979,11 @@ void SessionImpl::handleExternalIPAlert(const lt::external_ip_alert *alert)
LogMsg(tr("Detected external IP. IP: \"%1\"")
.arg(externalIP), Log::INFO);
const bool isIPv6 = alert->external_address.is_v6();
const bool isIPv4 = alert->external_address.is_v4();
if (isIPv6 && (externalIP != m_lastExternalIPv6Address))
if (m_lastExternalIP != externalIP)
{
if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIPv6Address.isEmpty())
if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIP.isEmpty())
reannounceToAllTrackers();
m_lastExternalIPv6Address = externalIP;
}
else if (isIPv4 && (externalIP != m_lastExternalIPv4Address))
{
if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIPv4Address.isEmpty())
reannounceToAllTrackers();
m_lastExternalIPv4Address = externalIP;
m_lastExternalIP = externalIP;
}
}
@ -6292,14 +6069,8 @@ void SessionImpl::handleSessionStatsAlert(const lt::session_stats_alert *alert)
m_status.allTimeDownload = m_previouslyDownloaded + m_status.totalDownload;
m_status.allTimeUpload = m_previouslyUploaded + m_status.totalUpload;
if (m_saveStatisticsInterval > 0)
{
const auto saveInterval = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::minutes(m_saveStatisticsInterval));
if (m_statisticsLastUpdateTimer.hasExpired(saveInterval.count()))
{
saveStatistics();
}
}
if (m_statisticsLastUpdateTimer.hasExpired(STATISTICS_SAVE_INTERVAL))
saveStatistics();
m_cacheStatus.totalUsedBuffers = stats[m_metricIndices.disk.diskBlocksInUse];
m_cacheStatus.jobQueueLength = stats[m_metricIndices.disk.queuedDiskJobs];
@ -6328,7 +6099,7 @@ void SessionImpl::handleStorageMovedAlert(const lt::storage_moved_alert *alert)
{
Q_ASSERT(!m_moveStorageQueue.isEmpty());
const MoveStorageJob &currentJob = m_moveStorageQueue.constFirst();
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
Q_ASSERT(currentJob.torrentHandle == alert->handle);
const Path newPath {QString::fromUtf8(alert->storage_path())};
@ -6351,7 +6122,7 @@ void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_a
{
Q_ASSERT(!m_moveStorageQueue.isEmpty());
const MoveStorageJob &currentJob = m_moveStorageQueue.constFirst();
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
Q_ASSERT(currentJob.torrentHandle == alert->handle);
#ifdef QBT_USES_LIBTORRENT2
@ -6373,7 +6144,7 @@ void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_a
void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *alert)
{
QList<Torrent *> updatedTorrents;
QVector<Torrent *> updatedTorrents;
updatedTorrents.reserve(static_cast<decltype(updatedTorrents)::size_type>(alert->status.size()));
for (const lt::torrent_status &status : alert->status)
@ -6411,7 +6182,7 @@ void SessionImpl::handleSocks5Alert(const lt::socks5_alert *alert) const
const QString endpoint = (addr.is_v6() ? u"[%1]:%2"_s : u"%1:%2"_s)
.arg(QString::fromStdString(addr.to_string()), QString::number(alert->ip.port()));
LogMsg(tr("SOCKS5 proxy error. Address: %1. Message: \"%2\".")
.arg(endpoint, Utils::String::fromLocal8Bit(alert->error.message()))
.arg(endpoint, QString::fromLocal8Bit(alert->error.message().c_str()))
, Log::WARNING);
}
}
@ -6524,7 +6295,7 @@ void SessionImpl::loadStatistics()
void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle)
{
invokeAsync([this, torrentHandle = std::move(torrentHandle)]
invokeAsync([this, torrentHandle = std::move(torrentHandle)]() mutable
{
try
{
@ -6534,9 +6305,9 @@ void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle)
QHash<std::string, QHash<lt::tcp::endpoint, QMap<int, int>>> updatedTrackers = m_updatedTrackerStatuses.take(torrentHandle);
updatedTrackerStatusesLocker.unlock();
invoke([this, infoHash = torrentHandle.info_hash(), nativeTrackers = std::move(nativeTrackers), updatedTrackers = std::move(updatedTrackers)]
invoke([this, torrentHandle, nativeTrackers = std::move(nativeTrackers), updatedTrackers = std::move(updatedTrackers)]
{
TorrentImpl *torrent = m_torrents.value(infoHash);
TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash());
if (!torrent || torrent->isStopped())
return;
@ -6565,8 +6336,8 @@ void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle)
void SessionImpl::handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError)
{
const auto removingTorrentDataIter = m_removingTorrents.constFind(torrentID);
if (removingTorrentDataIter == m_removingTorrents.cend())
const auto removingTorrentDataIter = m_removingTorrents.find(torrentID);
if (removingTorrentDataIter == m_removingTorrents.end())
return;
if (!partfileRemoveError.isEmpty())
@ -6588,3 +6359,9 @@ void SessionImpl::handleRemovedTorrent(const TorrentID &torrentID, const QString
m_removingTorrents.erase(removingTorrentDataIter);
}
QDateTime SessionImpl::fromLTTimePoint32(const lt::time_point32 &timePoint) const
{
const auto secsSinceNow = lt::duration_cast<lt::seconds>(timePoint - m_ltNow + lt::milliseconds(500)).count();
return m_qNow.addSecs(secsSinceNow);
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -29,7 +29,6 @@
#pragma once
#include <chrono>
#include <utility>
#include <vector>
@ -38,14 +37,14 @@
#include <libtorrent/torrent_handle.hpp>
#include <QtContainerFwd>
#include <QDateTime>
#include <QElapsedTimer>
#include <QHash>
#include <QList>
#include <QMap>
#include <QMutex>
#include <QPointer>
#include <QSet>
#include <QThreadPool>
#include <QVector>
#include "base/path.h"
#include "base/settingvalue.h"
@ -56,15 +55,17 @@
#include "session.h"
#include "sessionstatus.h"
#include "torrentinfo.h"
#include "trackerentrystatus.h"
class QString;
class QThread;
class QThreadPool;
class QTimer;
class QUrl;
class BandwidthScheduler;
class FileSearcher;
class FilterParserThread;
class FreeDiskSpaceChecker;
class NativeSessionExtension;
namespace BitTorrent
@ -234,8 +235,6 @@ namespace BitTorrent
void setPerformanceWarningEnabled(bool enable) override;
int saveResumeDataInterval() const override;
void setSaveResumeDataInterval(int value) override;
std::chrono::minutes saveStatisticsInterval() const override;
void setSaveStatisticsInterval(std::chrono::minutes value) override;
int shutdownTimeout() const override;
void setShutdownTimeout(int value) override;
int port() const override;
@ -362,8 +361,6 @@ namespace BitTorrent
void setIncludeOverheadInLimits(bool include) override;
QString announceIP() const override;
void setAnnounceIP(const QString &ip) override;
int announcePort() const override;
void setAnnouncePort(int port) override;
int maxConcurrentHTTPAnnounces() const override;
void setMaxConcurrentHTTPAnnounces(int value) override;
bool isReannounceWhenAddressChangedEnabled() const override;
@ -391,8 +388,6 @@ namespace BitTorrent
void setUTPRateLimited(bool limited) override;
MixedModeAlgorithm utpMixedMode() const override;
void setUtpMixedMode(MixedModeAlgorithm mode) override;
int hostnameCacheTTL() const override;
void setHostnameCacheTTL(int value) override;
bool isIDNSupportEnabled() const override;
void setIDNSupportEnabled(bool enabled) override;
bool multiConnectionsPerIpEnabled() const override;
@ -429,7 +424,7 @@ namespace BitTorrent
Torrent *getTorrent(const TorrentID &id) const override;
Torrent *findTorrent(const InfoHash &infoHash) const override;
QList<Torrent *> torrents() const override;
QVector<Torrent *> torrents() const override;
qsizetype torrentsCount() const override;
const SessionStatus &status() const override;
const CacheStatus &cacheStatus() const override;
@ -443,15 +438,10 @@ namespace BitTorrent
bool downloadMetadata(const TorrentDescriptor &torrentDescr) override;
bool cancelDownloadMetadata(const TorrentID &id) override;
void increaseTorrentsQueuePos(const QList<TorrentID> &ids) override;
void decreaseTorrentsQueuePos(const QList<TorrentID> &ids) override;
void topTorrentsQueuePos(const QList<TorrentID> &ids) override;
void bottomTorrentsQueuePos(const QList<TorrentID> &ids) override;
QString lastExternalIPv4Address() const override;
QString lastExternalIPv6Address() const override;
qint64 freeDiskSpace() const override;
void increaseTorrentsQueuePos(const QVector<TorrentID> &ids) override;
void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids) override;
void topTorrentsQueuePos(const QVector<TorrentID> &ids) override;
void bottomTorrentsQueuePos(const QVector<TorrentID> &ids) override;
// Torrent interface
void handleTorrentResumeDataRequested(const TorrentImpl *torrent);
@ -467,11 +457,11 @@ namespace BitTorrent
void handleTorrentStarted(TorrentImpl *torrent);
void handleTorrentChecked(TorrentImpl *torrent);
void handleTorrentFinished(TorrentImpl *torrent);
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QList<TrackerEntry> &newTrackers);
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QVector<TrackerEntry> &newTrackers);
void handleTorrentTrackersRemoved(TorrentImpl *torrent, const QStringList &deletedTrackers);
void handleTorrentTrackersChanged(TorrentImpl *torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QList<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QList<QUrl> &urlSeeds);
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);
void handleTorrentStorageMovingStateChanged(TorrentImpl *torrent);
@ -486,23 +476,15 @@ namespace BitTorrent
void addMappedPorts(const QSet<quint16> &ports);
void removeMappedPorts(const QSet<quint16> &ports);
QDateTime fromLTTimePoint32(const lt::time_point32 &timePoint) const;
template <typename Func>
void invoke(Func &&func)
{
QMetaObject::invokeMethod(this, std::forward<Func>(func), Qt::QueuedConnection);
}
template <typename Func>
void invokeAsync(Func &&func)
{
m_asyncWorker->start(std::forward<Func>(func));
}
bool isAddTrackersFromURLEnabled() const override;
void setAddTrackersFromURLEnabled(bool enabled) override;
QString additionalTrackersURL() const override;
void setAdditionalTrackersURL(const QString &url) override;
QString additionalTrackersFromURL() const override;
void invokeAsync(std::function<void ()> func);
signals:
void addTorrentAlertsReceived(qsizetype count);
@ -609,9 +591,7 @@ namespace BitTorrent
void saveTorrentsQueue();
void removeTorrentsQueue();
void populateAdditionalTrackersFromURL();
void fetchPendingAlerts(lt::time_duration time = lt::time_duration::zero());
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
void moveTorrentStorage(const MoveStorageJob &job) const;
void handleMoveTorrentStorageJobFinished(const Path &newPath);
@ -629,9 +609,6 @@ namespace BitTorrent
void handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError = {});
void setAdditionalTrackersFromURL(const QString &trackers);
void updateTrackersFromURL();
CachedSettingValue<QString> m_DHTBootstrapNodes;
CachedSettingValue<bool> m_isDHTEnabled;
CachedSettingValue<bool> m_isLSDEnabled;
@ -677,7 +654,6 @@ namespace BitTorrent
CachedSettingValue<bool> m_ignoreLimitsOnLAN;
CachedSettingValue<bool> m_includeOverheadInLimits;
CachedSettingValue<QString> m_announceIP;
CachedSettingValue<int> m_announcePort;
CachedSettingValue<int> m_maxConcurrentHTTPAnnounces;
CachedSettingValue<bool> m_isReannounceWhenAddressChangedEnabled;
CachedSettingValue<int> m_stopTrackerTimeout;
@ -688,7 +664,6 @@ namespace BitTorrent
CachedSettingValue<BTProtocol> m_btProtocol;
CachedSettingValue<bool> m_isUTPRateLimited;
CachedSettingValue<MixedModeAlgorithm> m_utpMixedMode;
CachedSettingValue<int> m_hostnameCacheTTL;
CachedSettingValue<bool> m_IDNSupportEnabled;
CachedSettingValue<bool> m_multiConnectionsPerIpEnabled;
CachedSettingValue<bool> m_validateHTTPSTrackerCertificate;
@ -696,8 +671,6 @@ namespace BitTorrent
CachedSettingValue<bool> m_blockPeersOnPrivilegedPorts;
CachedSettingValue<bool> m_isAddTrackersEnabled;
CachedSettingValue<QString> m_additionalTrackers;
CachedSettingValue<bool> m_isAddTrackersFromURLEnabled;
CachedSettingValue<QString> m_additionalTrackersURL;
CachedSettingValue<qreal> m_globalMaxRatio;
CachedSettingValue<int> m_globalMaxSeedingMinutes;
CachedSettingValue<int> m_globalMaxInactiveSeedingMinutes;
@ -719,7 +692,6 @@ namespace BitTorrent
CachedSettingValue<bool> m_isBandwidthSchedulerEnabled;
CachedSettingValue<bool> m_isPerformanceWarningEnabled;
CachedSettingValue<int> m_saveResumeDataInterval;
CachedSettingValue<int> m_saveStatisticsInterval;
CachedSettingValue<int> m_shutdownTimeout;
CachedSettingValue<int> m_port;
CachedSettingValue<bool> m_sslEnabled;
@ -771,9 +743,6 @@ namespace BitTorrent
bool m_IPFilteringConfigured = false;
mutable bool m_listenInterfaceConfigured = false;
QString m_additionalTrackersFromURL;
QTimer *m_updateTrackersFromURLTimer = nullptr;
bool m_isRestored = false;
bool m_isPaused = isStartPaused();
@ -783,9 +752,8 @@ namespace BitTorrent
const bool m_wasPexEnabled = m_isPeXEnabled;
int m_numResumeData = 0;
QList<TrackerEntry> m_additionalTrackerEntries;
QList<TrackerEntry> m_additionalTrackerEntriesFromURL;
QList<QRegularExpression> m_excludedFileNamesRegExpList;
QVector<TrackerEntry> m_additionalTrackerEntries;
QVector<QRegularExpression> m_excludedFileNamesRegExpList;
// Statistics
mutable QElapsedTimer m_statisticsLastUpdateTimer;
@ -820,7 +788,6 @@ namespace BitTorrent
QMap<QString, CategoryOptions> m_categories;
TagSet m_tags;
std::vector<lt::alert *> m_alerts; // make it a class variable so it can preserve its allocated `capacity`
qsizetype m_receivedAddTorrentAlertsCount = 0;
QList<Torrent *> m_loadedTorrents;
@ -841,8 +808,7 @@ namespace BitTorrent
QList<MoveStorageJob> m_moveStorageQueue;
QString m_lastExternalIPv4Address;
QString m_lastExternalIPv6Address;
QString m_lastExternalIP;
bool m_needUpgradeDownloadPath = false;
@ -852,13 +818,13 @@ namespace BitTorrent
bool m_isPortMappingEnabled = false;
QHash<quint16, std::vector<lt::port_mapping_t>> m_mappedPorts;
QElapsedTimer m_wakeupCheckTimestamp;
QTimer *m_wakeupCheckTimer = nullptr;
QDateTime m_wakeupCheckTimestamp;
QList<TorrentImpl *> m_pendingFinishedTorrents;
FreeDiskSpaceChecker *m_freeDiskSpaceChecker = nullptr;
QTimer *m_freeDiskSpaceCheckingTimer = nullptr;
qint64 m_freeDiskSpace = -1;
QDateTime m_qNow;
lt::clock_type::time_point m_ltNow;
friend void Session::initInstance();
friend void Session::freeInstance();

View file

@ -257,8 +257,8 @@ namespace BitTorrent
virtual bool hasMissingFiles() const = 0;
virtual bool hasError() const = 0;
virtual int queuePosition() const = 0;
virtual QList<TrackerEntryStatus> trackers() const = 0;
virtual QList<QUrl> urlSeeds() const = 0;
virtual QVector<TrackerEntryStatus> trackers() const = 0;
virtual QVector<QUrl> urlSeeds() const = 0;
virtual QString error() const = 0;
virtual qlonglong totalDownload() const = 0;
virtual qlonglong totalUpload() const = 0;
@ -275,10 +275,10 @@ namespace BitTorrent
virtual bool isDHTDisabled() const = 0;
virtual bool isPEXDisabled() const = 0;
virtual bool isLSDDisabled() const = 0;
virtual QList<PeerInfo> peers() const = 0;
virtual QVector<PeerInfo> peers() const = 0;
virtual QBitArray pieces() const = 0;
virtual QBitArray downloadingPieces() const = 0;
virtual QList<int> pieceAvailability() const = 0;
virtual QVector<int> pieceAvailability() const = 0;
virtual qreal distributedCopies() const = 0;
virtual qreal maxRatio() const = 0;
virtual int maxSeedingTime() const = 0;
@ -307,11 +307,11 @@ namespace BitTorrent
virtual void setDHTDisabled(bool disable) = 0;
virtual void setPEXDisabled(bool disable) = 0;
virtual void setLSDDisabled(bool disable) = 0;
virtual void addTrackers(QList<TrackerEntry> trackers) = 0;
virtual void addTrackers(QVector<TrackerEntry> trackers) = 0;
virtual void removeTrackers(const QStringList &trackers) = 0;
virtual void replaceTrackers(QList<TrackerEntry> trackers) = 0;
virtual void addUrlSeeds(const QList<QUrl> &urlSeeds) = 0;
virtual void removeUrlSeeds(const QList<QUrl> &urlSeeds) = 0;
virtual void replaceTrackers(QVector<TrackerEntry> trackers) = 0;
virtual void addUrlSeeds(const QVector<QUrl> &urlSeeds) = 0;
virtual void removeUrlSeeds(const QVector<QUrl> &urlSeeds) = 0;
virtual bool connectPeer(const PeerAddress &peerAddress) = 0;
virtual void clearPeers() = 0;
virtual void setMetadata(const TorrentInfo &torrentInfo) = 0;
@ -325,9 +325,9 @@ namespace BitTorrent
virtual nonstd::expected<QByteArray, QString> exportToBuffer() const = 0;
virtual nonstd::expected<void, QString> exportToFile(const Path &path) const = 0;
virtual void fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const = 0;
virtual void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const = 0;
virtual void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const = 0;
virtual void 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;

View file

@ -44,18 +44,18 @@ namespace BitTorrent
virtual bool hasMetadata() const = 0;
virtual Path actualStorageLocation() const = 0;
virtual Path actualFilePath(int fileIndex) const = 0;
virtual QList<DownloadPriority> filePriorities() const = 0;
virtual QList<qreal> filesProgress() 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 QList<qreal> availableFileFractions() const = 0;
virtual void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const = 0;
virtual QVector<qreal> availableFileFractions() const = 0;
virtual void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const = 0;
virtual void prioritizeFiles(const QList<DownloadPriority> &priorities) = 0;
virtual void prioritizeFiles(const QVector<DownloadPriority> &priorities) = 0;
virtual void flushCache() const = 0;
};
}

View file

@ -62,10 +62,7 @@ BitTorrent::TorrentCreationManager::TorrentCreationManager(IApplication *app, QO
, m_maxTasks {SETTINGS_KEY(u"MaxTasks"_s), 256}
, m_numThreads {SETTINGS_KEY(u"NumThreads"_s), 1}
, m_tasks {std::make_unique<TaskSet>()}
, m_threadPool(this)
{
m_threadPool.setObjectName("TorrentCreationManager m_threadPool");
if (m_numThreads > 0)
m_threadPool.setMaxThreadCount(m_numThreads);
}

View file

@ -207,9 +207,9 @@ void BitTorrent::TorrentDescriptor::setTorrentInfo(TorrentInfo torrentInfo)
}
}
QList<BitTorrent::TrackerEntry> BitTorrent::TorrentDescriptor::trackers() const
QVector<BitTorrent::TrackerEntry> BitTorrent::TorrentDescriptor::trackers() const
{
QList<TrackerEntry> ret;
QVector<TrackerEntry> ret;
ret.reserve(static_cast<decltype(ret)::size_type>(m_ltAddTorrentParams.trackers.size()));
std::size_t i = 0;
for (const std::string &tracker : m_ltAddTorrentParams.trackers)
@ -218,9 +218,9 @@ QList<BitTorrent::TrackerEntry> BitTorrent::TorrentDescriptor::trackers() const
return ret;
}
QList<QUrl> BitTorrent::TorrentDescriptor::urlSeeds() const
QVector<QUrl> BitTorrent::TorrentDescriptor::urlSeeds() const
{
QList<QUrl> urlSeeds;
QVector<QUrl> urlSeeds;
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(m_ltAddTorrentParams.url_seeds.size()));
for (const std::string &nativeURLSeed : m_ltAddTorrentParams.url_seeds)

View file

@ -39,6 +39,7 @@
#include "base/3rdparty/expected.hpp"
#include "base/path.h"
#include "torrentdescriptor.h"
#include "torrentinfo.h"
class QByteArray;
@ -59,8 +60,8 @@ namespace BitTorrent
QDateTime creationDate() const;
QString creator() const;
QString comment() const;
QList<TrackerEntry> trackers() const;
QList<QUrl> urlSeeds() const;
QVector<TrackerEntry> trackers() const;
QVector<QUrl> urlSeeds() const;
const std::optional<TorrentInfo> &info() const;
void setTorrentInfo(TorrentInfo torrentInfo);

View file

@ -63,7 +63,6 @@
#include "base/types.h"
#include "base/utils/fs.h"
#include "base/utils/io.h"
#include "base/utils/string.h"
#include "common.h"
#include "downloadpriority.h"
#include "extensiondata.h"
@ -101,13 +100,16 @@ namespace
if (const QString *endpointName = cache.object(ltTCPEndpoint))
return *endpointName;
const auto endpointName = Utils::String::fromLatin1((std::ostringstream() << ltTCPEndpoint).str());
const std::string tmp = (std::ostringstream() << ltTCPEndpoint).str();
const auto endpointName = QString::fromLatin1(tmp.c_str(), tmp.size());
cache.insert(ltTCPEndpoint, new QString(endpointName));
return endpointName;
}
template <typename FromLTTimePoint32Func>
void updateTrackerEntryStatus(TrackerEntryStatus &trackerEntryStatus, const lt::announce_entry &nativeEntry
, const QSet<int> &btProtocols, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo)
, const QSet<int> &btProtocols, const QHash<lt::tcp::endpoint, QMap<int, int>> &updateInfo
, const FromLTTimePoint32Func &fromLTTimePoint32)
{
Q_ASSERT(trackerEntryStatus.url == QString::fromStdString(nativeEntry.url));
@ -144,8 +146,8 @@ namespace
trackerEndpointStatus.numSeeds = ltAnnounceInfo.scrape_complete;
trackerEndpointStatus.numLeeches = ltAnnounceInfo.scrape_incomplete;
trackerEndpointStatus.numDownloaded = ltAnnounceInfo.scrape_downloaded;
trackerEndpointStatus.nextAnnounceTime = ltAnnounceInfo.next_announce;
trackerEndpointStatus.minAnnounceTime = ltAnnounceInfo.min_announce;
trackerEndpointStatus.nextAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.next_announce);
trackerEndpointStatus.minAnnounceTime = fromLTTimePoint32(ltAnnounceInfo.min_announce);
if (ltAnnounceInfo.updating)
{
@ -236,8 +238,8 @@ namespace
trackerEntryStatus.numSeeds = -1;
trackerEntryStatus.numLeeches = -1;
trackerEntryStatus.numDownloaded = -1;
trackerEntryStatus.nextAnnounceTime = {};
trackerEntryStatus.minAnnounceTime = {};
trackerEntryStatus.nextAnnounceTime = QDateTime();
trackerEntryStatus.minAnnounceTime = QDateTime();
trackerEntryStatus.message.clear();
for (const TrackerEndpointStatus &endpointStatus : asConst(trackerEntryStatus.endpoints))
@ -249,7 +251,7 @@ namespace
if (endpointStatus.state == trackerEntryStatus.state)
{
if ((trackerEntryStatus.nextAnnounceTime == AnnounceTimePoint()) || (trackerEntryStatus.nextAnnounceTime > endpointStatus.nextAnnounceTime))
if (!trackerEntryStatus.nextAnnounceTime.isValid() || (trackerEntryStatus.nextAnnounceTime > endpointStatus.nextAnnounceTime))
{
trackerEntryStatus.nextAnnounceTime = endpointStatus.nextAnnounceTime;
trackerEntryStatus.minAnnounceTime = endpointStatus.minAnnounceTime;
@ -463,13 +465,7 @@ qlonglong TorrentImpl::wastedSize() const
QString TorrentImpl::currentTracker() const
{
if (!m_nativeStatus.current_tracker.empty())
return QString::fromStdString(m_nativeStatus.current_tracker);
if (!m_trackerEntryStatuses.isEmpty())
return m_trackerEntryStatuses.constFirst().url;
return {};
return QString::fromStdString(m_nativeStatus.current_tracker);
}
Path TorrentImpl::savePath() const
@ -630,12 +626,12 @@ Path TorrentImpl::makeUserPath(const Path &path) const
return userPath;
}
QList<TrackerEntryStatus> TorrentImpl::trackers() const
QVector<TrackerEntryStatus> TorrentImpl::trackers() const
{
return m_trackerEntryStatuses;
}
void TorrentImpl::addTrackers(QList<TrackerEntry> trackers)
void TorrentImpl::addTrackers(QVector<TrackerEntry> trackers)
{
trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
@ -648,7 +644,7 @@ void TorrentImpl::addTrackers(QList<TrackerEntry> trackers)
if (newTrackerSet.isEmpty())
return;
trackers = QList<TrackerEntry>(newTrackerSet.cbegin(), newTrackerSet.cend());
trackers = QVector<TrackerEntry>(newTrackerSet.cbegin(), newTrackerSet.cend());
for (const TrackerEntry &tracker : asConst(trackers))
{
m_nativeHandle.add_tracker(makeNativeAnnounceEntry(tracker.url, tracker.tier));
@ -684,13 +680,13 @@ void TorrentImpl::removeTrackers(const QStringList &trackers)
}
}
void TorrentImpl::replaceTrackers(QList<TrackerEntry> trackers)
void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
{
trackers.removeIf([](const TrackerEntry &trackerEntry) { return trackerEntry.url.isEmpty(); });
// Filter out duplicate trackers
const auto uniqueTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend());
trackers = QList<TrackerEntry>(uniqueTrackers.cbegin(), uniqueTrackers.cend());
trackers = QVector<TrackerEntry>(uniqueTrackers.cbegin(), uniqueTrackers.cend());
std::sort(trackers.begin(), trackers.end()
, [](const TrackerEntry &left, const TrackerEntry &right) { return left.tier < right.tier; });
@ -715,12 +711,12 @@ void TorrentImpl::replaceTrackers(QList<TrackerEntry> trackers)
m_session->handleTorrentTrackersChanged(this);
}
QList<QUrl> TorrentImpl::urlSeeds() const
QVector<QUrl> TorrentImpl::urlSeeds() const
{
return m_urlSeeds;
}
void TorrentImpl::addUrlSeeds(const QList<QUrl> &urlSeeds)
void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
{
m_session->invokeAsync([urlSeeds, session = m_session
, nativeHandle = m_nativeHandle
@ -729,12 +725,12 @@ void TorrentImpl::addUrlSeeds(const QList<QUrl> &urlSeeds)
try
{
const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
QList<QUrl> currentSeeds;
QVector<QUrl> currentSeeds;
currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
for (const std::string &urlSeed : nativeSeeds)
currentSeeds.append(QString::fromStdString(urlSeed));
QList<QUrl> addedUrlSeeds;
QVector<QUrl> addedUrlSeeds;
addedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds)
@ -764,7 +760,7 @@ void TorrentImpl::addUrlSeeds(const QList<QUrl> &urlSeeds)
});
}
void TorrentImpl::removeUrlSeeds(const QList<QUrl> &urlSeeds)
void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
{
m_session->invokeAsync([urlSeeds, session = m_session
, nativeHandle = m_nativeHandle
@ -773,12 +769,12 @@ void TorrentImpl::removeUrlSeeds(const QList<QUrl> &urlSeeds)
try
{
const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
QList<QUrl> currentSeeds;
QVector<QUrl> currentSeeds;
currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
for (const std::string &urlSeed : nativeSeeds)
currentSeeds.append(QString::fromStdString(urlSeed));
QList<QUrl> removedUrlSeeds;
QVector<QUrl> removedUrlSeeds;
removedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds)
@ -1035,7 +1031,7 @@ Path TorrentImpl::filePath(const int index) const
Path TorrentImpl::actualFilePath(const int index) const
{
const QList<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
Q_ASSERT(index >= 0);
Q_ASSERT(index < nativeIndexes.size());
@ -1070,7 +1066,7 @@ PathList TorrentImpl::actualFilePaths() const
return paths;
}
QList<DownloadPriority> TorrentImpl::filePriorities() const
QVector<DownloadPriority> TorrentImpl::filePriorities() const
{
return m_filePriorities;
}
@ -1305,12 +1301,12 @@ int TorrentImpl::queuePosition() const
QString TorrentImpl::error() const
{
if (m_nativeStatus.errc)
return Utils::String::fromLocal8Bit(m_nativeStatus.errc.message());
return QString::fromLocal8Bit(m_nativeStatus.errc.message().c_str());
if (m_nativeStatus.flags & lt::torrent_flags::upload_mode)
{
return tr("Couldn't write to file. Reason: \"%1\". Torrent is now in \"upload only\" mode.")
.arg(Utils::String::fromLocal8Bit(m_lastFileError.error.message()));
.arg(QString::fromLocal8Bit(m_lastFileError.error.message().c_str()));
}
return {};
@ -1376,7 +1372,7 @@ qlonglong TorrentImpl::eta() const
return (wantedSize() - completedSize()) / speedAverage.download;
}
QList<qreal> TorrentImpl::filesProgress() const
QVector<qreal> TorrentImpl::filesProgress() const
{
if (!hasMetadata())
return {};
@ -1387,9 +1383,9 @@ QList<qreal> TorrentImpl::filesProgress() const
return {};
if (m_completedFiles.count(true) == count)
return QList<qreal>(count, 1);
return QVector<qreal>(count, 1);
QList<qreal> result;
QVector<qreal> result;
result.reserve(count);
for (int i = 0; i < count; ++i)
{
@ -1465,12 +1461,12 @@ bool TorrentImpl::isLSDDisabled() const
return static_cast<bool>(m_nativeStatus.flags & lt::torrent_flags::disable_lsd);
}
QList<PeerInfo> TorrentImpl::peers() const
QVector<PeerInfo> TorrentImpl::peers() const
{
std::vector<lt::peer_info> nativePeers;
m_nativeHandle.get_peer_info(nativePeers);
QList<PeerInfo> peers;
QVector<PeerInfo> peers;
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
for (const lt::peer_info &peer : nativePeers)
@ -1499,7 +1495,7 @@ QBitArray TorrentImpl::downloadingPieces() const
return result;
}
QList<int> TorrentImpl::pieceAvailability() const
QVector<int> TorrentImpl::pieceAvailability() const
{
std::vector<int> avail;
m_nativeHandle.piece_availability(avail);
@ -1615,20 +1611,18 @@ bool TorrentImpl::setCategory(const QString &category)
if (!category.isEmpty() && !m_session->categories().contains(category))
return false;
if (m_session->isDisableAutoTMMWhenCategoryChanged())
{
// This should be done before changing the category name
// to prevent the torrent from being moved at the path of new category.
setAutoTMMEnabled(false);
}
const QString oldCategory = m_category;
m_category = category;
deferredRequestResumeData();
m_session->handleTorrentCategoryChanged(this, oldCategory);
if (m_useAutoTMM)
adjustStorageLocation();
{
if (!m_session->isDisableAutoTMMWhenCategoryChanged())
adjustStorageLocation();
else
setAutoTMMEnabled(false);
}
}
return true;
@ -1780,7 +1774,11 @@ TrackerEntryStatus TorrentImpl::updateTrackerEntryStatus(const lt::announce_entr
const QSet<int> btProtocols {1};
#endif
::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo);
const auto fromLTTimePoint32 = [this](const lt::time_point32 &timePoint)
{
return m_session->fromLTTimePoint32(timePoint);
};
::updateTrackerEntryStatus(*it, announceEntry, btProtocols, updateInfo, fromLTTimePoint32);
return *it;
}
@ -2150,7 +2148,7 @@ void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
// URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
// Unfortunately, URL seed list containing in "resume data" is generated according to different rules
// than the list we usually cache, so we have to request it from the appropriate source.
fetchURLSeeds([this](const QList<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
fetchURLSeeds([this](const QVector<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
}
if ((m_maintenanceJob == MaintenanceJob::HandleMetadata) && p->params.ti)
@ -2276,7 +2274,7 @@ void TorrentImpl::handleSaveResumeDataFailedAlert(const lt::save_resume_data_fai
if (p->error != lt::errors::resume_data_not_modified)
{
LogMsg(tr("Generate resume data failed. Torrent: \"%1\". Reason: \"%2\"")
.arg(name(), Utils::String::fromLocal8Bit(p->error.message())), Log::CRITICAL);
.arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
}
}
@ -2364,7 +2362,7 @@ void TorrentImpl::handleFileRenameFailedAlert(const lt::file_rename_failed_alert
Q_ASSERT(fileIndex >= 0);
LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
.arg(name(), filePath(fileIndex).toString(), Utils::String::fromLocal8Bit(p->error.message())), Log::WARNING);
.arg(name(), filePath(fileIndex).toString(), QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
--m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
@ -2543,7 +2541,7 @@ void TorrentImpl::adjustStorageLocation()
void TorrentImpl::doRenameFile(const int index, const Path &path)
{
const QList<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
const QVector<lt::file_index_t> nativeIndexes = m_torrentInfo.nativeIndexes();
Q_ASSERT(index >= 0);
Q_ASSERT(index < nativeIndexes.size());
@ -2853,26 +2851,33 @@ QString TorrentImpl::createMagnetURI() const
const SHA1Hash infoHash1 = infoHash().v1();
if (infoHash1.isValid())
{
ret += u"xt=urn:btih:" + infoHash1.toString();
}
if (const SHA256Hash infoHash2 = infoHash().v2(); infoHash2.isValid())
const SHA256Hash infoHash2 = infoHash().v2();
if (infoHash2.isValid())
{
if (infoHash1.isValid())
ret += u'&';
ret += u"xt=urn:btmh:1220" + infoHash2.toString();
}
if (const QString displayName = name(); displayName != id().toString())
const QString displayName = name();
if (displayName != id().toString())
{
ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
if (hasMetadata())
ret += u"&xl=" + QString::number(totalSize());
}
for (const TrackerEntryStatus &tracker : asConst(trackers()))
{
ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
}
for (const QUrl &urlSeed : asConst(urlSeeds()))
ret += u"&ws=" + urlSeed.toString(QUrl::FullyEncoded);
{
ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
}
return ret;
}
@ -2930,15 +2935,15 @@ nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) cons
return {};
}
void TorrentImpl::fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const
void TorrentImpl::fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QList<PeerInfo>
invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QVector<PeerInfo>
{
try
{
std::vector<lt::peer_info> nativePeers;
nativeHandle.get_peer_info(nativePeers);
QList<PeerInfo> peers;
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));
@ -2951,14 +2956,14 @@ void TorrentImpl::fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHand
, std::move(resultHandler));
}
void TorrentImpl::fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const
void TorrentImpl::fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle]() -> QList<QUrl>
invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<QUrl>
{
try
{
const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
QList<QUrl> urlSeeds;
QVector<QUrl> urlSeeds;
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
for (const std::string &urlSeed : currentSeeds)
urlSeeds.append(QString::fromStdString(urlSeed));
@ -2971,15 +2976,15 @@ void TorrentImpl::fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler)
, std::move(resultHandler));
}
void TorrentImpl::fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const
void TorrentImpl::fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle]() -> QList<int>
invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<int>
{
try
{
std::vector<int> piecesAvailability;
nativeHandle.piece_availability(piecesAvailability);
return QList<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
return QVector<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
}
catch (const std::exception &) {}
@ -3013,9 +3018,9 @@ void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultH
, std::move(resultHandler));
}
void TorrentImpl::fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const
void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QList<qreal>
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QVector<qreal>
{
if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
return {};
@ -3027,9 +3032,9 @@ void TorrentImpl::fetchAvailableFileFractions(std::function<void (QList<qreal>)>
const int filesCount = torrentInfo.filesCount();
// libtorrent returns empty array for seeding only torrents
if (piecesAvailability.empty())
return QList<qreal>(filesCount, -1);
return QVector<qreal>(filesCount, -1);
QList<qreal> result;
QVector<qreal> result;
result.reserve(filesCount);
for (int i = 0; i < filesCount; ++i)
{
@ -3053,7 +3058,7 @@ void TorrentImpl::fetchAvailableFileFractions(std::function<void (QList<qreal>)>
, std::move(resultHandler));
}
void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
{
if (!hasMetadata())
return;
@ -3062,7 +3067,7 @@ void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
// Reset 'm_hasSeedStatus' if needed in order to react again to
// 'torrent_finished_alert' and eg show tray notifications
const QList<DownloadPriority> oldPriorities = filePriorities();
const QVector<DownloadPriority> oldPriorities = filePriorities();
for (int i = 0; i < oldPriorities.size(); ++i)
{
if ((oldPriorities[i] == DownloadPriority::Ignored)
@ -3090,18 +3095,18 @@ void TorrentImpl::prioritizeFiles(const QList<DownloadPriority> &priorities)
manageActualFilePaths();
}
QList<qreal> TorrentImpl::availableFileFractions() const
QVector<qreal> TorrentImpl::availableFileFractions() const
{
Q_ASSERT(hasMetadata());
const int filesCount = this->filesCount();
if (filesCount <= 0) return {};
const QList<int> piecesAvailability = pieceAvailability();
const QVector<int> piecesAvailability = pieceAvailability();
// libtorrent returns empty array for seeding only torrents
if (piecesAvailability.empty()) return QList<qreal>(filesCount, -1);
if (piecesAvailability.empty()) return QVector<qreal>(filesCount, -1);
QList<qreal> res;
QVector<qreal> res;
res.reserve(filesCount);
for (int i = 0; i < filesCount; ++i)
{

View file

@ -41,11 +41,11 @@
#include <QBitArray>
#include <QDateTime>
#include <QHash>
#include <QList>
#include <QMap>
#include <QObject>
#include <QQueue>
#include <QString>
#include <QVector>
#include "base/path.h"
#include "base/tagset.h"
@ -162,7 +162,7 @@ namespace BitTorrent
qlonglong fileSize(int index) const override;
PathList filePaths() const override;
PathList actualFilePaths() const override;
QList<DownloadPriority> filePriorities() const override;
QVector<DownloadPriority> filePriorities() const override;
TorrentInfo info() const override;
bool isFinished() const override;
@ -184,13 +184,13 @@ namespace BitTorrent
bool hasMissingFiles() const override;
bool hasError() const override;
int queuePosition() const override;
QList<TrackerEntryStatus> trackers() const override;
QList<QUrl> urlSeeds() const override;
QVector<TrackerEntryStatus> trackers() const override;
QVector<QUrl> urlSeeds() const override;
QString error() const override;
qlonglong totalDownload() const override;
qlonglong totalUpload() const override;
qlonglong eta() const override;
QList<qreal> filesProgress() const override;
QVector<qreal> filesProgress() const override;
int seedsCount() const override;
int peersCount() const override;
int leechsCount() const override;
@ -203,10 +203,10 @@ namespace BitTorrent
bool isDHTDisabled() const override;
bool isPEXDisabled() const override;
bool isLSDDisabled() const override;
QList<PeerInfo> peers() const override;
QVector<PeerInfo> peers() const override;
QBitArray pieces() const override;
QBitArray downloadingPieces() const override;
QList<int> pieceAvailability() const override;
QVector<int> pieceAvailability() const override;
qreal distributedCopies() const override;
qreal maxRatio() const override;
int maxSeedingTime() const override;
@ -220,7 +220,7 @@ namespace BitTorrent
int connectionsCount() const override;
int connectionsLimit() const override;
qlonglong nextAnnounce() const override;
QList<qreal> availableFileFractions() const override;
QVector<qreal> availableFileFractions() const override;
void setName(const QString &name) override;
void setSequentialDownload(bool enable) override;
@ -231,7 +231,7 @@ namespace BitTorrent
void forceDHTAnnounce() override;
void forceRecheck() override;
void renameFile(int index, const Path &path) override;
void prioritizeFiles(const QList<DownloadPriority> &priorities) override;
void prioritizeFiles(const QVector<DownloadPriority> &priorities) override;
void setUploadLimit(int limit) override;
void setDownloadLimit(int limit) override;
void setSuperSeeding(bool enable) override;
@ -239,11 +239,11 @@ namespace BitTorrent
void setPEXDisabled(bool disable) override;
void setLSDDisabled(bool disable) override;
void flushCache() const override;
void addTrackers(QList<TrackerEntry> trackers) override;
void addTrackers(QVector<TrackerEntry> trackers) override;
void removeTrackers(const QStringList &trackers) override;
void replaceTrackers(QList<TrackerEntry> trackers) override;
void addUrlSeeds(const QList<QUrl> &urlSeeds) override;
void removeUrlSeeds(const QList<QUrl> &urlSeeds) override;
void replaceTrackers(QVector<TrackerEntry> trackers) override;
void addUrlSeeds(const QVector<QUrl> &urlSeeds) override;
void removeUrlSeeds(const QVector<QUrl> &urlSeeds) override;
bool connectPeer(const PeerAddress &peerAddress) override;
void clearPeers() override;
void setMetadata(const TorrentInfo &torrentInfo) override;
@ -258,11 +258,11 @@ namespace BitTorrent
nonstd::expected<QByteArray, QString> exportToBuffer() const override;
nonstd::expected<void, QString> exportToFile(const Path &path) const override;
void fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const override;
void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const override;
void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const override;
void 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 (QList<qreal>)> resultHandler) const override;
void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const override;
bool needSaveResumeData() const;
@ -337,7 +337,7 @@ namespace BitTorrent
TorrentInfo m_torrentInfo;
PathList m_filePaths;
QHash<lt::file_index_t, int> m_indexMap;
QList<DownloadPriority> m_filePriorities;
QVector<DownloadPriority> m_filePriorities;
QBitArray m_completedFiles;
SpeedMonitor m_payloadRateMonitor;
@ -361,8 +361,8 @@ namespace BitTorrent
MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
QList<TrackerEntryStatus> m_trackerEntryStatuses;
QList<QUrl> m_urlSeeds;
QVector<TrackerEntryStatus> m_trackerEntryStatuses;
QVector<QUrl> m_urlSeeds;
FileErrorInfo m_lastFileError;
// Persistent data
@ -393,7 +393,7 @@ namespace BitTorrent
int m_uploadLimit = 0;
QBitArray m_pieces;
QList<std::int64_t> m_filesProgress;
QVector<std::int64_t> m_filesProgress;
bool m_deferredRequestResumeDataInvoked = false;
};

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2024 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
@ -28,15 +28,24 @@
#include "torrentinfo.h"
#include <libtorrent/create_torrent.hpp>
#include <libtorrent/error_code.hpp>
#include <libtorrent/version.hpp>
#include <QByteArray>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QString>
#include <QStringList>
#include <QUrl>
#include "base/global.h"
#include "base/path.h"
#include "base/preferences.h"
#include "base/utils/fs.h"
#include "base/utils/io.h"
#include "base/utils/misc.h"
#include "infohash.h"
#include "trackerentry.h"
@ -73,6 +82,66 @@ bool TorrentInfo::isValid() const
return (m_nativeInfo != nullptr);
}
nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data) noexcept
{
// 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are
// used in `torrent_info()` constructor
const auto *pref = Preferences::instance();
lt::error_code ec;
const lt::bdecode_node node = lt::bdecode(data, ec
, nullptr, pref->getBdecodeDepthLimit(), pref->getBdecodeTokenLimit());
if (ec)
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
const lt::torrent_info nativeInfo {node, ec};
if (ec)
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
return TorrentInfo(nativeInfo);
}
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const Path &path) noexcept
{
QByteArray data;
try
{
const qint64 torrentSizeLimit = Preferences::instance()->getTorrentFileSizeLimit();
const auto readResult = Utils::IO::readFile(path, torrentSizeLimit);
if (!readResult)
return nonstd::make_unexpected(readResult.error().message);
data = readResult.value();
}
catch (const std::bad_alloc &e)
{
return nonstd::make_unexpected(tr("Failed to allocate memory when reading file. File: \"%1\". Error: \"%2\"")
.arg(path.toString(), QString::fromLocal8Bit(e.what())));
}
return load(data);
}
nonstd::expected<void, QString> TorrentInfo::saveToFile(const Path &path) const
{
if (!isValid())
return nonstd::make_unexpected(tr("Invalid metadata"));
try
{
const auto torrentCreator = lt::create_torrent(*m_nativeInfo);
const lt::entry torrentEntry = torrentCreator.generate();
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, torrentEntry);
if (!result)
return result.get_unexpected();
}
catch (const lt::system_error &err)
{
return nonstd::make_unexpected(QString::fromLocal8Bit(err.what()));
}
return {};
}
InfoHash TorrentInfo::infoHash() const
{
if (!isValid()) return {};
@ -91,6 +160,28 @@ QString TorrentInfo::name() const
return QString::fromStdString(m_nativeInfo->orig_files().name());
}
QDateTime TorrentInfo::creationDate() const
{
if (!isValid()) return {};
const std::time_t date = m_nativeInfo->creation_date();
return ((date != 0) ? QDateTime::fromSecsSinceEpoch(date) : QDateTime());
}
QString TorrentInfo::creator() const
{
if (!isValid()) return {};
return QString::fromStdString(m_nativeInfo->creator());
}
QString TorrentInfo::comment() const
{
if (!isValid()) return {};
return QString::fromStdString(m_nativeInfo->comment());
}
bool TorrentInfo::isPrivate() const
{
if (!isValid()) return false;
@ -179,7 +270,43 @@ qlonglong TorrentInfo::fileOffset(const int index) const
return m_nativeInfo->orig_files().file_offset(m_nativeIndexes[index]);
}
QByteArray TorrentInfo::rawData() const
QVector<TrackerEntry> TorrentInfo::trackers() const
{
if (!isValid()) return {};
const std::vector<lt::announce_entry> trackers = m_nativeInfo->trackers();
QVector<TrackerEntry> ret;
ret.reserve(static_cast<decltype(ret)::size_type>(trackers.size()));
for (const lt::announce_entry &tracker : trackers)
ret.append({.url = QString::fromStdString(tracker.url), .tier = tracker.tier});
return ret;
}
QVector<QUrl> TorrentInfo::urlSeeds() const
{
if (!isValid()) return {};
const std::vector<lt::web_seed_entry> &nativeWebSeeds = m_nativeInfo->web_seeds();
QVector<QUrl> urlSeeds;
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(nativeWebSeeds.size()));
for (const lt::web_seed_entry &webSeed : nativeWebSeeds)
{
#if LIBTORRENT_VERSION_NUM < 20100
if (webSeed.type == lt::web_seed_entry::url_seed)
urlSeeds.append(QUrl(QString::fromStdString(webSeed.url)));
#else
urlSeeds.append(QUrl(QString::fromStdString(webSeed.url)));
#endif
}
return urlSeeds;
}
QByteArray TorrentInfo::metadata() const
{
if (!isValid()) return {};
#ifdef QBT_USES_LIBTORRENT2
@ -193,7 +320,7 @@ QByteArray TorrentInfo::rawData() const
PathList TorrentInfo::filesForPiece(const int pieceIndex) const
{
// no checks here because fileIndicesForPiece() will return an empty list
const QList<int> fileIndices = fileIndicesForPiece(pieceIndex);
const QVector<int> fileIndices = fileIndicesForPiece(pieceIndex);
PathList res;
res.reserve(fileIndices.size());
@ -203,14 +330,14 @@ PathList TorrentInfo::filesForPiece(const int pieceIndex) const
return res;
}
QList<int> TorrentInfo::fileIndicesForPiece(const int pieceIndex) const
QVector<int> TorrentInfo::fileIndicesForPiece(const int pieceIndex) const
{
if (!isValid() || (pieceIndex < 0) || (pieceIndex >= piecesCount()))
return {};
const std::vector<lt::file_slice> files = m_nativeInfo->map_block(
lt::piece_index_t {pieceIndex}, 0, m_nativeInfo->piece_size(lt::piece_index_t {pieceIndex}));
QList<int> res;
QVector<int> res;
res.reserve(static_cast<decltype(res)::size_type>(files.size()));
for (const lt::file_slice &fileSlice : files)
{
@ -222,13 +349,13 @@ QList<int> TorrentInfo::fileIndicesForPiece(const int pieceIndex) const
return res;
}
QList<QByteArray> TorrentInfo::pieceHashes() const
QVector<QByteArray> TorrentInfo::pieceHashes() const
{
if (!isValid())
return {};
const int count = piecesCount();
QList<QByteArray> hashes;
QVector<QByteArray> hashes;
hashes.reserve(count);
for (int i = 0; i < count; ++i)
@ -239,7 +366,16 @@ QList<QByteArray> TorrentInfo::pieceHashes() const
TorrentInfo::PieceRange TorrentInfo::filePieces(const Path &filePath) const
{
return filePieces(fileIndex(filePath));
if (!isValid()) // if we do not check here the debug message will be printed, which would be not correct
return {};
const int index = fileIndex(filePath);
if (index == -1)
{
qDebug() << "Filename" << filePath.toString() << "was not found in torrent" << name();
return {};
}
return filePieces(index);
}
TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
@ -248,7 +384,10 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(const int fileIndex) const
return {};
if ((fileIndex < 0) || (fileIndex >= filesCount()))
{
qDebug() << "File index (" << fileIndex << ") is out of range for torrent" << name();
return {};
}
const lt::file_storage &files = m_nativeInfo->orig_files();
const auto fileSize = files.file_size(m_nativeIndexes[fileIndex]);
@ -311,7 +450,7 @@ std::shared_ptr<lt::torrent_info> TorrentInfo::nativeInfo() const
return std::make_shared<lt::torrent_info>(*m_nativeInfo);
}
QList<lt::file_index_t> TorrentInfo::nativeIndexes() const
QVector<lt::file_index_t> TorrentInfo::nativeIndexes() const
{
return m_nativeIndexes;
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015 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,8 +32,9 @@
#include <QtContainerFwd>
#include <QCoreApplication>
#include <QList>
#include <QVector>
#include "base/3rdparty/expected.hpp"
#include "base/indexrange.h"
#include "base/pathfwd.h"
@ -49,17 +50,26 @@ namespace BitTorrent
class TorrentInfo
{
Q_DECLARE_TR_FUNCTIONS(TorrentInfo)
public:
TorrentInfo() = default;
TorrentInfo(const TorrentInfo &other) = default;
explicit TorrentInfo(const lt::torrent_info &nativeInfo);
static nonstd::expected<TorrentInfo, QString> load(const QByteArray &data) noexcept;
static nonstd::expected<TorrentInfo, QString> loadFromFile(const Path &path) noexcept;
nonstd::expected<void, QString> saveToFile(const Path &path) const;
TorrentInfo &operator=(const TorrentInfo &other);
bool isValid() const;
InfoHash infoHash() const;
QString name() const;
QDateTime creationDate() const;
QString creator() const;
QString comment() const;
bool isPrivate() const;
qlonglong totalSize() const;
int filesCount() const;
@ -70,9 +80,12 @@ namespace BitTorrent
PathList filePaths() const;
qlonglong fileSize(int index) const;
qlonglong fileOffset(int index) const;
QVector<TrackerEntry> trackers() const;
QVector<QUrl> urlSeeds() const;
QByteArray metadata() const;
PathList filesForPiece(int pieceIndex) const;
QList<int> fileIndicesForPiece(int pieceIndex) const;
QList<QByteArray> pieceHashes() const;
QVector<int> fileIndicesForPiece(int pieceIndex) const;
QVector<QByteArray> pieceHashes() const;
using PieceRange = IndexRange<int>;
// returns pair of the first and the last pieces into which
@ -80,12 +93,10 @@ namespace BitTorrent
PieceRange filePieces(const Path &filePath) const;
PieceRange filePieces(int fileIndex) const;
QByteArray rawData() const;
bool matchesInfoHash(const InfoHash &otherInfoHash) const;
std::shared_ptr<lt::torrent_info> nativeInfo() const;
QList<lt::file_index_t> nativeIndexes() const;
QVector<lt::file_index_t> nativeIndexes() const;
private:
// returns file index or -1 if fileName is not found
@ -95,7 +106,7 @@ namespace BitTorrent
// internal indexes of files (payload only, excluding any .pad files)
// by which they are addressed in libtorrent
QList<lt::file_index_t> m_nativeIndexes;
QVector<lt::file_index_t> m_nativeIndexes;
};
}

View file

@ -380,7 +380,7 @@ void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
{
// Reached max size, remove a random torrent
if (m_torrents.size() >= MAX_TORRENTS)
m_torrents.erase(m_torrents.cbegin());
m_torrents.erase(m_torrents.begin());
}
m_torrents[announceReq.torrentID].setPeer(announceReq.peer);

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2024 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
@ -28,11 +28,10 @@
#pragma once
#include <QDateTime>
#include <QHash>
#include <QString>
#include "announcetimepoint.h"
class QStringView;
namespace BitTorrent
@ -60,8 +59,8 @@ namespace BitTorrent
int numLeeches = -1;
int numDownloaded = -1;
AnnounceTimePoint nextAnnounceTime {};
AnnounceTimePoint minAnnounceTime {};
QDateTime nextAnnounceTime {};
QDateTime minAnnounceTime {};
};
struct TrackerEntryStatus
@ -77,8 +76,8 @@ namespace BitTorrent
int numLeeches = -1;
int numDownloaded = -1;
AnnounceTimePoint nextAnnounceTime {};
AnnounceTimePoint minAnnounceTime {};
QDateTime nextAnnounceTime {};
QDateTime minAnnounceTime {};
QHash<std::pair<QString, int>, TrackerEndpointStatus> endpoints {};

View file

@ -155,16 +155,12 @@ void Connection::read()
sendResponse(resp);
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
m_receivedData.slice(result.frameSize);
#else
m_receivedData.remove(0, result.frameSize);
#endif
}
break;
default:
Q_UNREACHABLE();
Q_ASSERT(false);
return;
}
}

View file

@ -50,7 +50,7 @@ using QStringPair = std::pair<QString, QString>;
namespace
{
const QByteArray EOH = CRLF.repeated(2);
const QByteArray EOH = QByteArray(CRLF).repeated(2);
const QByteArrayView viewWithoutEndingWith(const QByteArrayView in, const QByteArrayView str)
{
@ -69,8 +69,8 @@ namespace
return false;
}
const QString name = line.first(i).trimmed().toString().toLower();
const QString value = line.sliced(i + 1).trimmed().toString();
const QString name = line.left(i).trimmed().toString().toLower();
const QString value = line.mid(i + 1).trimmed().toString();
out[name] = value;
return true;
@ -164,7 +164,7 @@ bool RequestParser::parseStartLines(const QStringView data)
if (line.at(0).isSpace() && !requestLines.isEmpty())
{
// continuation of previous line
requestLines.last() += line;
requestLines.last() += line.toString();
}
else
{
@ -204,15 +204,15 @@ bool RequestParser::parseRequestLine(const QString &line)
m_request.method = match.captured(1);
// Request Target
const QByteArray url {match.capturedView(2).toLatin1()};
const QByteArray url {match.captured(2).toLatin1()};
const int sepPos = url.indexOf('?');
const QByteArrayView pathComponent = ((sepPos == -1) ? url : QByteArrayView(url).first(sepPos));
const QByteArrayView pathComponent = ((sepPos == -1) ? url : QByteArrayView(url).mid(0, sepPos));
m_request.path = QString::fromUtf8(QByteArray::fromPercentEncoding(asQByteArray(pathComponent)));
if (sepPos >= 0)
{
const QByteArrayView query = QByteArrayView(url).sliced(sepPos + 1);
const QByteArrayView query = QByteArrayView(url).mid(sepPos + 1);
// [rfc3986] 2.4 When to Encode or Decode
// URL components should be separated before percent-decoding
@ -221,11 +221,13 @@ bool RequestParser::parseRequestLine(const QString &line)
const int eqCharPos = param.indexOf('=');
if (eqCharPos <= 0) continue; // ignores params without name
const QByteArrayView nameComponent = param.first(eqCharPos);
const QByteArrayView valueComponent = param.sliced(eqCharPos + 1);
const QByteArrayView nameComponent = param.mid(0, eqCharPos);
const QByteArrayView valueComponent = param.mid(eqCharPos + 1);
const QString paramName = QString::fromUtf8(
QByteArray::fromPercentEncoding(asQByteArray(nameComponent)).replace('+', ' '));
const QByteArray paramValue = QByteArray::fromPercentEncoding(asQByteArray(valueComponent)).replace('+', ' ');
const QByteArray paramValue = valueComponent.isNull()
? QByteArray("")
: QByteArray::fromPercentEncoding(asQByteArray(valueComponent)).replace('+', ' ');
m_request.query[paramName] = paramValue;
}
@ -270,7 +272,7 @@ bool RequestParser::parsePostMessage(const QByteArrayView data)
return false;
}
const QByteArray delimiter = Utils::String::unquote(QStringView(contentType).sliced(idx + boundaryFieldName.size())).toLatin1();
const QByteArray delimiter = Utils::String::unquote(QStringView(contentType).mid(idx + boundaryFieldName.size())).toLatin1();
if (delimiter.isEmpty())
{
qWarning() << Q_FUNC_INFO << "boundary delimiter field empty!";
@ -279,7 +281,7 @@ bool RequestParser::parsePostMessage(const QByteArrayView data)
// split data by "dash-boundary"
const QByteArray dashDelimiter = QByteArray("--") + delimiter + CRLF;
QList<QByteArrayView> multipart = splitToViews(data, dashDelimiter);
QList<QByteArrayView> multipart = splitToViews(data, dashDelimiter, Qt::SkipEmptyParts);
if (multipart.isEmpty())
{
qWarning() << Q_FUNC_INFO << "multipart empty";
@ -310,8 +312,8 @@ bool RequestParser::parseFormData(const QByteArrayView data)
return false;
}
const QString headers = QString::fromLatin1(data.first(eohPos));
const QByteArrayView payload = viewWithoutEndingWith(data.sliced((eohPos + EOH.size())), CRLF);
const QString headers = QString::fromLatin1(data.mid(0, eohPos));
const QByteArrayView payload = viewWithoutEndingWith(data.mid((eohPos + EOH.size()), data.size()), CRLF);
HeaderMap headersMap;
const QList<QStringView> headerLines = QStringView(headers).split(QString::fromLatin1(CRLF), Qt::SkipEmptyParts);
@ -328,14 +330,14 @@ bool RequestParser::parseFormData(const QByteArrayView data)
if (idx < 0)
continue;
const QString name = directive.first(idx).trimmed().toString().toLower();
const QString value = Utils::String::unquote(directive.sliced(idx + 1).trimmed()).toString();
const QString name = directive.left(idx).trimmed().toString().toLower();
const QString value = Utils::String::unquote(directive.mid(idx + 1).trimmed()).toString();
headersMap[name] = value;
}
}
else
{
if (!parseHeaderLine(line, headersMap))
if (!parseHeaderLine(line.toString(), headersMap))
return false;
}
}
@ -346,8 +348,7 @@ bool RequestParser::parseFormData(const QByteArrayView data)
if (headersMap.contains(filename))
{
m_request.files.append({.filename = headersMap[filename], .type = headersMap[HEADER_CONTENT_TYPE]
, .data = payload.toByteArray()});
m_request.files.append({headersMap[filename], headersMap[HEADER_CONTENT_TYPE], payload.toByteArray()});
}
else if (headersMap.contains(name))
{

View file

@ -37,9 +37,8 @@
#include <QtLogging>
#include <QNetworkProxy>
#include <QSslCertificate>
#include <QSslCipher>
#include <QSslKey>
#include <QSslConfiguration>
#include <QSslSocket>
#include <QStringList>
#include <QTimer>
@ -101,12 +100,13 @@ using namespace Http;
Server::Server(IRequestHandler *requestHandler, QObject *parent)
: QTcpServer(parent)
, m_requestHandler(requestHandler)
, m_sslConfig {QSslConfiguration::defaultConfiguration()}
{
setProxy(QNetworkProxy::NoProxy);
m_sslConfig.setCiphers(safeCipherList());
m_sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone);
QSslConfiguration sslConf {QSslConfiguration::defaultConfiguration()};
sslConf.setProtocol(QSsl::TlsV1_2OrLater);
sslConf.setCiphers(safeCipherList());
QSslConfiguration::setDefaultConfiguration(sslConf);
auto *dropConnectionTimer = new QTimer(this);
connect(dropConnectionTimer, &QTimer::timeout, this, &Server::dropTimedOutConnection);
@ -115,7 +115,7 @@ Server::Server(IRequestHandler *requestHandler, QObject *parent)
void Server::incomingConnection(const qintptr socketDescriptor)
{
std::unique_ptr<QTcpSocket> serverSocket = isHttps() ? std::make_unique<QSslSocket>(this) : std::make_unique<QTcpSocket>(this);
std::unique_ptr<QTcpSocket> serverSocket = m_https ? std::make_unique<QSslSocket>(this) : std::make_unique<QTcpSocket>(this);
if (!serverSocket->setSocketDescriptor(socketDescriptor))
return;
@ -127,10 +127,13 @@ void Server::incomingConnection(const qintptr socketDescriptor)
try
{
if (isHttps())
if (m_https)
{
auto *sslSocket = static_cast<QSslSocket *>(serverSocket.get());
sslSocket->setSslConfiguration(m_sslConfig);
sslSocket->setProtocol(QSsl::SecureProtocols);
sslSocket->setPrivateKey(m_key);
sslSocket->setLocalCertificateChain(m_certificates);
sslSocket->setPeerVerifyMode(QSslSocket::VerifyNone);
sslSocket->startServerEncryption();
}
@ -175,17 +178,17 @@ bool Server::setupHttps(const QByteArray &certificates, const QByteArray &privat
return false;
}
m_sslConfig.setLocalCertificateChain(certs);
m_sslConfig.setPrivateKey(key);
m_key = key;
m_certificates = certs;
m_https = true;
return true;
}
void Server::disableHttps()
{
m_sslConfig.setLocalCertificateChain({});
m_sslConfig.setPrivateKey({});
m_https = false;
m_certificates.clear();
m_key.clear();
}
bool Server::isHttps() const

View file

@ -31,7 +31,8 @@
#pragma once
#include <QSet>
#include <QSslConfiguration>
#include <QSslCertificate>
#include <QSslKey>
#include <QTcpServer>
namespace Http
@ -62,6 +63,7 @@ namespace Http
QSet<Connection *> m_connections; // for tracking persistent connections
bool m_https = false;
QSslConfiguration m_sslConfig;
QList<QSslCertificate> m_certificates;
QSslKey m_key;
};
}

View file

@ -29,12 +29,9 @@
#pragma once
#include <QByteArray>
#include <QHash>
#include <QHostAddress>
#include <QList>
#include <QMap>
#include <QString>
#include <QVector>
#include "base/global.h"
@ -60,7 +57,6 @@ namespace Http
inline const QString HEADER_X_CONTENT_TYPE_OPTIONS = u"x-content-type-options"_s;
inline const QString HEADER_X_FORWARDED_FOR = u"x-forwarded-for"_s;
inline const QString HEADER_X_FORWARDED_HOST = u"x-forwarded-host"_s;
inline const QString HEADER_X_FORWARDED_PROTO = u"X-forwarded-proto"_s;
inline const QString HEADER_X_FRAME_OPTIONS = u"x-frame-options"_s;
inline const QString HEADER_X_XSS_PROTECTION = u"x-xss-protection"_s;
@ -79,7 +75,7 @@ namespace Http
inline const QString CONTENT_TYPE_FORM_DATA = u"multipart/form-data"_s;
// portability: "\r\n" doesn't guarantee mapping to the correct symbol
inline const QByteArray CRLF = QByteArrayLiteral("\x0D\x0A");
inline const char CRLF[] = {0x0D, 0x0A, '\0'};
struct Environment
{
@ -113,7 +109,7 @@ namespace Http
HeaderMap headers;
QHash<QString, QByteArray> query;
QHash<QString, QString> posts;
QList<UploadedFile> files;
QVector<UploadedFile> files;
};
struct ResponseStatus

View file

@ -31,14 +31,14 @@
#include <algorithm>
#include <QDateTime>
#include <QList>
#include <QVector>
namespace
{
template <typename T>
QList<T> loadFromBuffer(const boost::circular_buffer_space_optimized<T> &src, const int offset = 0)
QVector<T> loadFromBuffer(const boost::circular_buffer_space_optimized<T> &src, const int offset = 0)
{
QList<T> ret;
QVector<T> ret;
ret.reserve(static_cast<typename decltype(ret)::size_type>(src.size()) - offset);
std::copy((src.begin() + offset), src.end(), std::back_inserter(ret));
return ret;
@ -90,7 +90,7 @@ void Logger::addPeer(const QString &ip, const bool blocked, const QString &reaso
emit newLogPeer(msg);
}
QList<Log::Msg> Logger::getMessages(const int lastKnownId) const
QVector<Log::Msg> Logger::getMessages(const int lastKnownId) const
{
const QReadLocker locker(&m_lock);
@ -106,7 +106,7 @@ QList<Log::Msg> Logger::getMessages(const int lastKnownId) const
return loadFromBuffer(m_messages, (size - diff));
}
QList<Log::Peer> Logger::getPeers(const int lastKnownId) const
QVector<Log::Peer> Logger::getPeers(const int lastKnownId) const
{
const QReadLocker locker(&m_lock);

View file

@ -81,8 +81,8 @@ public:
void addMessage(const QString &message, const Log::MsgType &type = Log::NORMAL);
void addPeer(const QString &ip, bool blocked, const QString &reason = {});
QList<Log::Msg> getMessages(int lastKnownId = -1) const;
QList<Log::Peer> getPeers(int lastKnownId = -1) const;
QVector<Log::Msg> getMessages(int lastKnownId = -1) const;
QVector<Log::Peer> getPeers(int lastKnownId = -1) const;
signals:
void newLogMessage(const Log::Msg &message);

View file

@ -96,9 +96,9 @@ void DNSUpdater::ipRequestFinished(const DownloadResult &result)
const QRegularExpressionMatch ipRegexMatch = QRegularExpression(u"Current IP Address:\\s+([^<]+)</body>"_s).match(QString::fromUtf8(result.data));
if (ipRegexMatch.hasMatch())
{
const QString ipStr = ipRegexMatch.captured(1);
QString ipStr = ipRegexMatch.captured(1);
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr;
const QHostAddress newIp {ipStr};
QHostAddress newIp(ipStr);
if (!newIp.isNull())
{
if (m_lastIP != newIp)
@ -153,7 +153,7 @@ QString DNSUpdater::getUpdateUrl() const
break;
default:
qWarning() << "Unrecognized Dynamic DNS service!";
Q_UNREACHABLE();
Q_ASSERT(false);
break;
}
url.setPath(u"/nic/update"_s);
@ -305,7 +305,7 @@ QUrl DNSUpdater::getRegistrationUrl(const DNS::Service service)
case DNS::Service::NoIP:
return {u"https://www.noip.com/remote-access"_s};
default:
Q_UNREACHABLE();
Q_ASSERT(false);
break;
}
return {};

View file

@ -265,37 +265,38 @@ void Net::DownloadManager::applyProxySettings()
const auto *proxyManager = ProxyConfigurationManager::instance();
const ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
switch (proxyConfig.type)
m_proxy = QNetworkProxy(QNetworkProxy::NoProxy);
if ((proxyConfig.type == Net::ProxyType::None) || (proxyConfig.type == ProxyType::SOCKS4))
return;
// Proxy enabled
if (proxyConfig.type == ProxyType::SOCKS5)
{
case Net::ProxyType::None:
case Net::ProxyType::SOCKS4:
m_proxy = QNetworkProxy(QNetworkProxy::NoProxy);
break;
qDebug() << Q_FUNC_INFO << "using SOCKS proxy";
m_proxy.setType(QNetworkProxy::Socks5Proxy);
}
else
{
qDebug() << Q_FUNC_INFO << "using HTTP proxy";
m_proxy.setType(QNetworkProxy::HttpProxy);
}
case Net::ProxyType::HTTP:
m_proxy = QNetworkProxy(
QNetworkProxy::HttpProxy
, proxyConfig.ip
, proxyConfig.port
, (proxyConfig.authEnabled ? proxyConfig.username : QString())
, (proxyConfig.authEnabled ? proxyConfig.password : QString()));
m_proxy.setCapabilities(proxyConfig.hostnameLookupEnabled
? (m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability)
: (m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability));
break;
m_proxy.setHostName(proxyConfig.ip);
m_proxy.setPort(proxyConfig.port);
case Net::ProxyType::SOCKS5:
m_proxy = QNetworkProxy(
QNetworkProxy::Socks5Proxy
, proxyConfig.ip
, proxyConfig.port
, (proxyConfig.authEnabled ? proxyConfig.username : QString())
, (proxyConfig.authEnabled ? proxyConfig.password : QString()));
m_proxy.setCapabilities(proxyConfig.hostnameLookupEnabled
? (m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability)
: (m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability));
break;
};
// Authentication?
if (proxyConfig.authEnabled)
{
qDebug("Proxy requires authentication, authenticating...");
m_proxy.setUser(proxyConfig.username);
m_proxy.setPassword(proxyConfig.password);
}
if (proxyConfig.hostnameLookupEnabled)
m_proxy.setCapabilities(m_proxy.capabilities() | QNetworkProxy::HostNameLookupCapability);
else
m_proxy.setCapabilities(m_proxy.capabilities() & ~QNetworkProxy::HostNameLookupCapability);
}
void Net::DownloadManager::processWaitingJobs(const ServiceID &serviceID)
@ -319,11 +320,14 @@ void Net::DownloadManager::processRequest(DownloadHandlerImpl *downloadHandler)
const DownloadRequest downloadRequest = downloadHandler->downloadRequest();
QNetworkRequest request {downloadRequest.url()};
request.setHeader(QNetworkRequest::UserAgentHeader, (downloadRequest.userAgent().isEmpty()
? getBrowserUserAgent() : downloadRequest.userAgent().toUtf8()));
if (downloadRequest.userAgent().isEmpty())
request.setRawHeader("User-Agent", getBrowserUserAgent());
else
request.setRawHeader("User-Agent", downloadRequest.userAgent().toUtf8());
// Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents
request.setRawHeader("Referer", request.url().toEncoded());
request.setRawHeader("Referer", request.url().toEncoded().data());
#ifdef QT_NO_COMPRESS
// The macro "QT_NO_COMPRESS" defined in QT will disable the zlib related features
// and reply data auto-decompression in QT will also be disabled. But we can support

View file

@ -28,7 +28,6 @@
#include "geoipdatabase.h"
#include <QByteArray>
#include <QDateTime>
#include <QDebug>
#include <QFile>
@ -42,7 +41,7 @@ namespace
{
const qint32 MAX_FILE_SIZE = 67108864; // 64MB
const quint32 MAX_METADATA_SIZE = 131072; // 128KB
const QByteArray METADATA_BEGIN_MARK = QByteArrayLiteral("\xab\xcd\xefMaxMind.com");
const char METADATA_BEGIN_MARK[] = "\xab\xcd\xefMaxMind.com";
const char DATA_SECTION_SEPARATOR[16] = {0};
enum class DataType
@ -310,7 +309,7 @@ QVariantHash GeoIPDatabase::readMetadata() const
{
if (m_size > MAX_METADATA_SIZE)
index += (m_size - MAX_METADATA_SIZE); // from begin of all data
auto offset = static_cast<quint32>(index + METADATA_BEGIN_MARK.size());
auto offset = static_cast<quint32>(index + strlen(METADATA_BEGIN_MARK));
const QVariant metadata = readDataField(offset);
if (metadata.userType() == QMetaType::QVariantHash)
return metadata.toHash();

View file

@ -456,15 +456,15 @@ void GeoIPManager::downloadFinished(const DownloadResult &result)
Utils::Fs::mkpath(targetPath);
const auto path = targetPath / Path(GEODB_FILENAME);
const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, data);
if (saveResult)
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(path, data);
if (result)
{
LogMsg(tr("Successfully updated IP geolocation database."), Log::INFO);
}
else
{
LogMsg(tr("Couldn't save downloaded IP geolocation database file. Reason: %1")
.arg(saveResult.error()), Log::WARNING);
.arg(result.error()), Log::WARNING);
}
}
else

View file

@ -148,7 +148,8 @@ void Smtp::sendMail(const QString &from, const QString &to, const QString &subje
// Encode the body in base64
QString crlfBody = body;
const QByteArray b = crlfBody.replace(u"\n"_s, u"\r\n"_s).toUtf8().toBase64();
for (int i = 0, end = b.length(); i < end; i += 78)
const int ct = b.length();
for (int i = 0; i < ct; i += 78)
m_message += b.mid(i, 78);
m_from = from;
m_rcpt = to;
@ -189,12 +190,8 @@ void Smtp::readyRead()
{
const int pos = m_buffer.indexOf("\r\n");
if (pos < 0) return; // Loop exit condition
const QByteArray line = m_buffer.first(pos);
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
m_buffer.slice(pos + 2);
#else
const QByteArray line = m_buffer.left(pos);
m_buffer.remove(0, (pos + 2));
#endif
qDebug() << "Response line:" << line;
// Extract response code
const QByteArray code = line.left(3);
@ -568,11 +565,29 @@ void Smtp::logError(const QString &msg)
QString Smtp::getCurrentDateTime() const
{
// [rfc2822] 3.3. Date and Time Specification
const auto now = QDateTime::currentDateTime();
const QLocale eng {QLocale::English};
const QString weekday = eng.dayName(now.date().dayOfWeek(), QLocale::ShortFormat);
return (weekday + u", " + now.toString(Qt::RFC2822Date));
// return date & time in the format specified in RFC 2822, section 3.3
const QDateTime nowDateTime = QDateTime::currentDateTime();
const QDate nowDate = nowDateTime.date();
const QLocale eng(QLocale::English);
const QString timeStr = nowDateTime.time().toString(u"HH:mm:ss");
const QString weekDayStr = eng.dayName(nowDate.dayOfWeek(), QLocale::ShortFormat);
const QString dayStr = QString::number(nowDate.day());
const QString monthStr = eng.monthName(nowDate.month(), QLocale::ShortFormat);
const QString yearStr = QString::number(nowDate.year());
QDateTime tmp = nowDateTime;
tmp.setTimeSpec(Qt::UTC);
const int timeOffsetHour = nowDateTime.secsTo(tmp) / 3600;
const int timeOffsetMin = nowDateTime.secsTo(tmp) / 60 - (60 * timeOffsetHour);
const int timeOffset = timeOffsetHour * 100 + timeOffsetMin;
// buf size = 11 to avoid format truncation warnings from snprintf
char buf[11] = {0};
std::snprintf(buf, sizeof(buf), "%+05d", timeOffset);
const auto timeOffsetStr = QString::fromUtf8(buf);
const QString ret = weekDayStr + u", " + dayStr + u' ' + monthStr + u' ' + yearStr + u' ' + timeStr + u' ' + timeOffsetStr;
return ret;
}
void Smtp::error(QAbstractSocket::SocketError socketError)

View file

@ -94,13 +94,7 @@ bool Path::isValid() const
#if defined(Q_OS_WIN)
QStringView view = m_pathStr;
if (hasDriveLetter(view))
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
view.slice(3);
#else
view = view.sliced(3);
#endif
}
view = view.mid(3);
// \\37 is using base-8 number system
const QRegularExpression regex {u"[\\0-\\37:?\"*<>|]"_s};
@ -153,9 +147,9 @@ Path Path::rootItem() const
#ifdef Q_OS_WIN
// should be `c:/` instead of `c:`
if ((slashIndex == 2) && hasDriveLetter(m_pathStr))
return createUnchecked(m_pathStr.first(slashIndex + 1));
return createUnchecked(m_pathStr.left(slashIndex + 1));
#endif
return createUnchecked(m_pathStr.first(slashIndex));
return createUnchecked(m_pathStr.left(slashIndex));
}
Path Path::parentPath() const
@ -173,9 +167,9 @@ Path Path::parentPath() const
// should be `c:/` instead of `c:`
// Windows "drive letter" is limited to one alphabet
if ((slashIndex == 2) && hasDriveLetter(m_pathStr))
return (m_pathStr.size() == 3) ? Path() : createUnchecked(m_pathStr.first(slashIndex + 1));
return (m_pathStr.size() == 3) ? Path() : createUnchecked(m_pathStr.left(slashIndex + 1));
#endif
return createUnchecked(m_pathStr.first(slashIndex));
return createUnchecked(m_pathStr.left(slashIndex));
}
QString Path::filename() const
@ -184,7 +178,7 @@ QString Path::filename() const
if (slashIndex == -1)
return m_pathStr;
return m_pathStr.sliced(slashIndex + 1);
return m_pathStr.mid(slashIndex + 1);
}
QString Path::extension() const
@ -194,9 +188,9 @@ QString Path::extension() const
return (u"." + suffix);
const int slashIndex = m_pathStr.lastIndexOf(u'/');
const auto filename = QStringView(m_pathStr).sliced(slashIndex + 1);
const auto filename = QStringView(m_pathStr).mid(slashIndex + 1);
const int dotIndex = filename.lastIndexOf(u'.', -2);
return ((dotIndex == -1) ? QString() : filename.sliced(dotIndex).toString());
return ((dotIndex == -1) ? QString() : filename.mid(dotIndex).toString());
}
bool Path::hasExtension(const QStringView ext) const
@ -299,7 +293,7 @@ Path Path::commonPath(const Path &left, const Path &right)
if (commonItemsCount > 0)
commonPathSize += (commonItemsCount - 1); // size of intermediate separators
return Path::createUnchecked(left.m_pathStr.first(commonPathSize));
return Path::createUnchecked(left.m_pathStr.left(commonPathSize));
}
Path Path::findRootFolder(const PathList &filePaths)
@ -328,13 +322,7 @@ void Path::stripRootFolder(PathList &filePaths)
return;
for (Path &filePath : filePaths)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
filePath.m_pathStr.slice(commonRootFolder.m_pathStr.size() + 1);
#else
filePath.m_pathStr.remove(0, (commonRootFolder.m_pathStr.size() + 1));
#endif
}
}
void Path::addRootFolder(PathList &filePaths, const Path &rootFolder)

View file

@ -359,32 +359,6 @@ void Preferences::setStatusbarDisplayed(const bool displayed)
setValue(u"Preferences/General/StatusbarDisplayed"_s, displayed);
}
bool Preferences::isStatusbarFreeDiskSpaceDisplayed() const
{
return value(u"Preferences/General/StatusbarFreeDiskSpaceDisplayed"_s, false);
}
void Preferences::setStatusbarFreeDiskSpaceDisplayed(const bool displayed)
{
if (displayed == isStatusbarFreeDiskSpaceDisplayed())
return;
setValue(u"Preferences/General/StatusbarFreeDiskSpaceDisplayed"_s, displayed);
}
bool Preferences::isStatusbarExternalIPDisplayed() const
{
return value(u"Preferences/General/StatusbarExternalIPDisplayed"_s, false);
}
void Preferences::setStatusbarExternalIPDisplayed(const bool displayed)
{
if (displayed == isStatusbarExternalIPDisplayed())
return;
setValue(u"Preferences/General/StatusbarExternalIPDisplayed"_s, displayed);
}
bool Preferences::isSplashScreenDisabled() const
{
return value(u"Preferences/General/NoSplashScreen"_s, true);
@ -668,47 +642,6 @@ void Preferences::setSearchEnabled(const bool enabled)
setValue(u"Preferences/Search/SearchEnabled"_s, enabled);
}
int Preferences::searchHistoryLength() const
{
const int val = value(u"Search/HistoryLength"_s, 50);
return std::clamp(val, 0, 99);
}
void Preferences::setSearchHistoryLength(const int length)
{
const int clampedLength = std::clamp(length, 0, 99);
if (clampedLength == searchHistoryLength())
return;
setValue(u"Search/HistoryLength"_s, clampedLength);
}
bool Preferences::storeOpenedSearchTabs() const
{
return value(u"Search/StoreOpenedSearchTabs"_s, false);
}
void Preferences::setStoreOpenedSearchTabs(const bool enabled)
{
if (enabled == storeOpenedSearchTabs())
return;
setValue(u"Search/StoreOpenedSearchTabs"_s, enabled);
}
bool Preferences::storeOpenedSearchTabResults() const
{
return value(u"Search/StoreOpenedSearchTabResults"_s, false);
}
void Preferences::setStoreOpenedSearchTabResults(const bool enabled)
{
if (enabled == storeOpenedSearchTabResults())
return;
setValue(u"Search/StoreOpenedSearchTabResults"_s, enabled);
}
bool Preferences::isWebUIEnabled() const
{
#ifdef DISABLE_GUI
@ -753,11 +686,11 @@ void Preferences::setWebUIAuthSubnetWhitelistEnabled(const bool enabled)
setValue(u"Preferences/WebUI/AuthSubnetWhitelistEnabled"_s, enabled);
}
QList<Utils::Net::Subnet> Preferences::getWebUIAuthSubnetWhitelist() const
QVector<Utils::Net::Subnet> Preferences::getWebUIAuthSubnetWhitelist() const
{
const auto subnets = value<QStringList>(u"Preferences/WebUI/AuthSubnetWhitelist"_s);
QList<Utils::Net::Subnet> ret;
QVector<Utils::Net::Subnet> ret;
ret.reserve(subnets.size());
for (const QString &rawSubnet : subnets)
@ -2067,19 +2000,6 @@ void Preferences::setAddNewTorrentDialogSavePathHistoryLength(const int value)
setValue(u"AddNewTorrentDialog/SavePathHistoryLength"_s, clampedValue);
}
bool Preferences::isAddNewTorrentDialogAttached() const
{
return value(u"AddNewTorrentDialog/Attached"_s, false);
}
void Preferences::setAddNewTorrentDialogAttached(const bool attached)
{
if (attached == isAddNewTorrentDialogAttached())
return;
setValue(u"AddNewTorrentDialog/Attached"_s, attached);
}
void Preferences::apply()
{
if (SettingsStorage::instance()->save())

View file

@ -119,10 +119,6 @@ public:
void setHideZeroComboValues(int n);
bool isStatusbarDisplayed() const;
void setStatusbarDisplayed(bool displayed);
bool isStatusbarFreeDiskSpaceDisplayed() const;
void setStatusbarFreeDiskSpaceDisplayed(bool displayed);
bool isStatusbarExternalIPDisplayed() const;
void setStatusbarExternalIPDisplayed(bool displayed);
bool isToolbarDisplayed() const;
void setToolbarDisplayed(bool displayed);
bool isSplashScreenDisabled() const;
@ -174,14 +170,6 @@ public:
bool isSearchEnabled() const;
void setSearchEnabled(bool enabled);
// Search UI
int searchHistoryLength() const;
void setSearchHistoryLength(int length);
bool storeOpenedSearchTabs() const;
void setStoreOpenedSearchTabs(bool enabled);
bool storeOpenedSearchTabResults() const;
void setStoreOpenedSearchTabResults(bool enabled);
// HTTP Server
bool isWebUIEnabled() const;
void setWebUIEnabled(bool enabled);
@ -199,7 +187,7 @@ public:
void setWebUILocalAuthEnabled(bool enabled);
bool isWebUIAuthSubnetWhitelistEnabled() const;
void setWebUIAuthSubnetWhitelistEnabled(bool enabled);
QList<Utils::Net::Subnet> getWebUIAuthSubnetWhitelist() const;
QVector<Utils::Net::Subnet> getWebUIAuthSubnetWhitelist() const;
void setWebUIAuthSubnetWhitelist(QStringList subnets);
QString getWebUIUsername() const;
void setWebUIUsername(const QString &username);
@ -435,8 +423,6 @@ public:
void setAddNewTorrentDialogTopLevel(bool value);
int addNewTorrentDialogSavePathHistoryLength() const;
void setAddNewTorrentDialogSavePathHistoryLength(int value);
bool isAddNewTorrentDialogAttached() const;
void setAddNewTorrentDialogAttached(bool attached);
public slots:
void setStatusFilterState(bool checked);

View file

@ -34,14 +34,14 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QList>
#include <QVector>
#include "base/logger.h"
#include "base/path.h"
#include "base/utils/io.h"
#include "rss_article.h"
const int ARTICLEDATALIST_TYPEID = qRegisterMetaType<QList<QVariantHash>>();
const int ARTICLEDATALIST_TYPEID = qRegisterMetaType<QVector<QVariantHash>>();
void RSS::Private::FeedSerializer::load(const Path &dataFileName, const QString &url)
{
@ -61,7 +61,7 @@ void RSS::Private::FeedSerializer::load(const Path &dataFileName, const QString
emit loadingFinished(loadArticles(readResult.value(), url));
}
void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QList<QVariantHash> &articlesData)
void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QVector<QVariantHash> &articlesData)
{
QJsonArray arr;
for (const QVariantHash &data : articlesData)
@ -73,7 +73,7 @@ void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QList<Q
arr << jsonObj;
}
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(dataFileName, QJsonDocument(arr).toJson(QJsonDocument::Compact));
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(dataFileName, QJsonDocument(arr).toJson());
if (!result)
{
LogMsg(tr("Failed to save RSS feed in '%1', Reason: %2").arg(dataFileName.toString(), result.error())
@ -81,7 +81,7 @@ void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QList<Q
}
}
QList<QVariantHash> RSS::Private::FeedSerializer::loadArticles(const QByteArray &data, const QString &url)
QVector<QVariantHash> RSS::Private::FeedSerializer::loadArticles(const QByteArray &data, const QString &url)
{
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
@ -98,7 +98,7 @@ QList<QVariantHash> RSS::Private::FeedSerializer::loadArticles(const QByteArray
return {};
}
QList<QVariantHash> result;
QVector<QVariantHash> result;
const QJsonArray jsonArr = jsonDoc.array();
result.reserve(jsonArr.size());
for (int i = 0; i < jsonArr.size(); ++i)

View file

@ -49,12 +49,12 @@ namespace RSS::Private
using QObject::QObject;
void load(const Path &dataFileName, const QString &url);
void store(const Path &dataFileName, const QList<QVariantHash> &articlesData);
void store(const Path &dataFileName, const QVector<QVariantHash> &articlesData);
signals:
void loadingFinished(const QList<QVariantHash> &articles);
void loadingFinished(const QVector<QVariantHash> &articles);
private:
QList<QVariantHash> loadArticles(const QByteArray &data, const QString &url);
QVector<QVariantHash> loadArticles(const QByteArray &data, const QString &url);
};
}

View file

@ -35,15 +35,14 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QList>
#include <QThread>
#include <QTimer>
#include <QUrl>
#include <QVariant>
#include <QVector>
#include "base/addtorrentmanager.h"
#include "base/asyncfilestorage.h"
#include "base/bittorrent/addtorrenterror.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrentdescriptor.h"
#include "base/global.h"
@ -69,7 +68,7 @@ const QString RULES_FILE_NAME = u"download_rules.json"_s;
namespace
{
QList<RSS::AutoDownloadRule> rulesFromJSON(const QByteArray &jsonData)
QVector<RSS::AutoDownloadRule> rulesFromJSON(const QByteArray &jsonData)
{
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &jsonError);
@ -80,7 +79,7 @@ namespace
throw RSS::ParsingError(RSS::AutoDownloader::tr("Invalid data format."));
const QJsonObject jsonObj {jsonDoc.object()};
QList<RSS::AutoDownloadRule> rules;
QVector<RSS::AutoDownloadRule> rules;
for (auto it = jsonObj.begin(); it != jsonObj.end(); ++it)
{
const QJsonValue jsonVal {it.value()};
@ -124,7 +123,6 @@ AutoDownloader::AutoDownloader(IApplication *app)
.arg(fileName.toString(), errorString), Log::CRITICAL);
});
m_ioThread->setObjectName("RSS::AutoDownloader m_ioThread");
m_ioThread->start();
connect(app->addTorrentManager(), &AddTorrentManager::torrentAdded
@ -376,24 +374,10 @@ void AutoDownloader::handleTorrentAdded(const QString &source)
}
}
void AutoDownloader::handleAddTorrentFailed(const QString &source, const BitTorrent::AddTorrentError &error)
void AutoDownloader::handleAddTorrentFailed(const QString &source)
{
const auto job = m_waitingJobs.take(source);
if (!job)
return;
if (error.kind == BitTorrent::AddTorrentError::DuplicateTorrent)
{
if (Feed *feed = Session::instance()->feedByURL(job->feedURL))
{
if (Article *article = feed->articleByGUID(job->articleData.value(Article::KeyId).toString()))
article->markAsRead();
}
}
else
{
// TODO: Re-schedule job here.
}
m_waitingJobs.remove(source);
// TODO: Re-schedule job here.
}
void AutoDownloader::handleNewArticle(const Article *article)

View file

@ -41,17 +41,13 @@
#include "base/settingvalue.h"
#include "base/utils/thread.h"
class QThread;
class QTimer;
class Application;
class AsyncFileStorage;
struct ProcessingJob;
namespace BitTorrent
{
struct AddTorrentError;
}
namespace RSS
{
class Article;
@ -116,7 +112,7 @@ namespace RSS
private slots:
void process();
void handleTorrentAdded(const QString &source);
void handleAddTorrentFailed(const QString &url, const BitTorrent::AddTorrentError &error);
void handleAddTorrentFailed(const QString &url);
void handleNewArticle(const Article *article);
void handleFeedURLChanged(Feed *feed, const QString &oldURL);

View file

@ -184,10 +184,14 @@ QString computeEpisodeName(const QString &article)
for (int i = 1; i <= match.lastCapturedIndex(); ++i)
{
const QString cap = match.captured(i);
if (cap.isEmpty())
continue;
ret.append(cap);
bool isInt = false;
const int x = cap.toInt(&isInt);
ret.append(isInt ? QString::number(x) : cap);
}
return ret.join(u'x');
}
@ -289,26 +293,20 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
if (!matcher.hasMatch())
return false;
const QStringView season {matcher.capturedView(1)};
const QList<QStringView> episodes {matcher.capturedView(2).split(u';')};
const QString season {matcher.captured(1)};
const QStringList episodes {matcher.captured(2).split(u';')};
const int seasonOurs {season.toInt()};
for (QStringView episode : episodes)
for (QString episode : episodes)
{
if (episode.isEmpty())
continue;
// We need to trim leading zeroes, but if it's all zeros then we want episode zero.
while ((episode.size() > 1) && episode.startsWith(u'0'))
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
episode.slice(1);
#else
episode = episode.sliced(1);
#endif
}
episode = episode.right(episode.size() - 1);
if (episode.contains(u'-'))
if (episode.indexOf(u'-') != -1)
{ // Range detected
const QString partialPattern1 {u"\\bs0?(\\d{1,4})[ -_\\.]?e(0?\\d{1,4})(?:\\D|\\b)"_s};
const QString partialPattern2 {u"\\b(\\d{1,4})x(0?\\d{1,4})(?:\\D|\\b)"_s};
@ -325,25 +323,24 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
if (matched)
{
const int seasonTheirs {matcher.capturedView(1).toInt()};
const int episodeTheirs {matcher.capturedView(2).toInt()};
const int seasonTheirs {matcher.captured(1).toInt()};
const int episodeTheirs {matcher.captured(2).toInt()};
if (episode.endsWith(u'-'))
{ // Infinite range
const int episodeOurs {QStringView(episode).chopped(1).toInt()};
const int episodeOurs {QStringView(episode).left(episode.size() - 1).toInt()};
if (((seasonTheirs == seasonOurs) && (episodeTheirs >= episodeOurs)) || (seasonTheirs > seasonOurs))
return true;
}
else
{ // Normal range
const QList<QStringView> range {episode.split(u'-')};
const QStringList range {episode.split(u'-')};
Q_ASSERT(range.size() == 2);
if (range.first().toInt() > range.last().toInt())
continue; // Ignore this subrule completely
const int episodeOursFirst {range.first().toInt()};
const int episodeOursLast {range.last().toInt()};
if (episodeOursFirst > episodeOursLast)
continue; // Ignore this subrule completely
if ((seasonTheirs == seasonOurs) && ((episodeOursFirst <= episodeTheirs) && (episodeOursLast >= episodeTheirs)))
return true;
}

View file

@ -1,7 +1,7 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2024 Jonathan Ketchker
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
@ -39,8 +39,8 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QList>
#include <QUrl>
#include <QVector>
#include "base/asyncfilestorage.h"
#include "base/global.h"
@ -56,22 +56,19 @@
const QString KEY_UID = u"uid"_s;
const QString KEY_URL = u"url"_s;
const QString KEY_REFRESHINTERVAL = u"refreshInterval"_s;
const QString KEY_TITLE = u"title"_s;
const QString KEY_LASTBUILDDATE = u"lastBuildDate"_s;
const QString KEY_ISLOADING = u"isLoading"_s;
const QString KEY_HASERROR = u"hasError"_s;
const QString KEY_ARTICLES = u"articles"_s;
using namespace std::chrono_literals;
using namespace RSS;
Feed::Feed(Session *session, const QUuid &uid, const QString &url, const QString &path, const std::chrono::seconds refreshInterval)
Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *session)
: Item(path)
, m_session {session}
, m_uid {uid}
, m_url {url}
, m_refreshInterval {refreshInterval}
, m_session(session)
, m_uid(uid)
, m_url(url)
{
const auto uidHex = QString::fromLatin1(m_uid.toRfc4122().toHex());
m_dataFileName = Path(uidHex + u".json");
@ -304,7 +301,7 @@ void Feed::store()
m_dirty = false;
m_savingTimer.stop();
QList<QVariantHash> articlesData;
QVector<QVariantHash> articlesData;
articlesData.reserve(m_articles.size());
for (Article *article :asConst(m_articles))
@ -330,9 +327,9 @@ bool Feed::addArticle(const QVariantHash &articleData)
// Insertion sort
const int maxArticles = m_session->maxArticlesPerFeed();
const auto lowerBound = std::lower_bound(m_articlesByDate.cbegin(), m_articlesByDate.cend()
, articleData.value(Article::KeyDate).toDateTime(), Article::articleDateRecentThan);
if ((lowerBound - m_articlesByDate.cbegin()) >= maxArticles)
const auto lowerBound = std::lower_bound(m_articlesByDate.begin(), m_articlesByDate.end()
, articleData.value(Article::KeyDate).toDateTime(), Article::articleDateRecentThan);
if ((lowerBound - m_articlesByDate.begin()) >= maxArticles)
return false; // we reach max articles
auto *article = new Article(this, articleData);
@ -398,7 +395,7 @@ int Feed::updateArticles(const QList<QVariantHash> &loadedArticles)
return 0;
QDateTime dummyPubDate {QDateTime::currentDateTime()};
QList<QVariantHash> newArticles;
QVector<QVariantHash> newArticles;
newArticles.reserve(loadedArticles.size());
for (QVariantHash article : loadedArticles)
{
@ -465,20 +462,6 @@ Path Feed::iconPath() const
return m_iconPath;
}
std::chrono::seconds Feed::refreshInterval() const
{
return m_refreshInterval;
}
void Feed::setRefreshInterval(const std::chrono::seconds refreshInterval)
{
if (refreshInterval == m_refreshInterval)
return;
const std::chrono::seconds oldRefreshInterval = std::exchange(m_refreshInterval, refreshInterval);
emit refreshIntervalChanged(oldRefreshInterval);
}
void Feed::setURL(const QString &url)
{
const QString oldURL = m_url;
@ -491,8 +474,6 @@ QJsonValue Feed::toJsonValue(const bool withData) const
QJsonObject jsonObj;
jsonObj.insert(KEY_UID, uid().toString());
jsonObj.insert(KEY_URL, url());
if (refreshInterval() > 0s)
jsonObj.insert(KEY_REFRESHINTERVAL, static_cast<qint64>(refreshInterval().count()));
if (withData)
{
@ -535,7 +516,7 @@ void Feed::handleArticleRead(Article *article)
storeDeferred();
}
void Feed::handleArticleLoadFinished(QList<QVariantHash> articles)
void Feed::handleArticleLoadFinished(QVector<QVariantHash> articles)
{
Q_ASSERT(m_articles.isEmpty());
Q_ASSERT(m_unreadCount == 0);

View file

@ -1,7 +1,7 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2024 Jonathan Ketchker
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
@ -31,8 +31,6 @@
#pragma once
#include <chrono>
#include <QtContainerFwd>
#include <QBasicTimer>
#include <QHash>
@ -70,7 +68,7 @@ namespace RSS
friend class Session;
Feed(Session *session, const QUuid &uid, const QString &url, const QString &path, std::chrono::seconds refreshInterval);
Feed(const QUuid &uid, const QString &url, const QString &path, Session *session);
~Feed() override;
public:
@ -89,9 +87,6 @@ namespace RSS
Article *articleByGUID(const QString &guid) const;
Path iconPath() const;
std::chrono::seconds refreshInterval() const;
void setRefreshInterval(std::chrono::seconds refreshInterval);
QJsonValue toJsonValue(bool withData = false) const override;
signals:
@ -99,7 +94,6 @@ namespace RSS
void titleChanged(Feed *feed = nullptr);
void stateChanged(Feed *feed = nullptr);
void urlChanged(const QString &oldURL);
void refreshIntervalChanged(std::chrono::seconds oldRefreshInterval);
private slots:
void handleSessionProcessingEnabledChanged(bool enabled);
@ -108,7 +102,7 @@ namespace RSS
void handleDownloadFinished(const Net::DownloadResult &result);
void handleParsingFinished(const Private::ParsingResult &result);
void handleArticleRead(Article *article);
void handleArticleLoadFinished(QList<QVariantHash> articles);
void handleArticleLoadFinished(QVector<QVariantHash> articles);
private:
void timerEvent(QTimerEvent *event) override;
@ -129,7 +123,6 @@ namespace RSS
Private::FeedSerializer *m_serializer = nullptr;
const QUuid m_uid;
QString m_url;
std::chrono::seconds m_refreshInterval;
QString m_title;
QString m_lastBuildDate;
bool m_hasError = false;

View file

@ -97,7 +97,7 @@ QStringList Item::expandPath(const QString &path)
int index = 0;
while ((index = path.indexOf(Item::PathSeparator, index)) >= 0)
{
result << path.first(index);
result << path.left(index);
++index;
}
result << path;
@ -108,11 +108,11 @@ QStringList Item::expandPath(const QString &path)
QString Item::parentPath(const QString &path)
{
const int pos = path.lastIndexOf(Item::PathSeparator);
return (pos >= 0) ? path.first(pos) : QString();
return (pos >= 0) ? path.left(pos) : QString();
}
QString Item::relativeName(const QString &path)
{
const int pos = path.lastIndexOf(Item::PathSeparator);
return (pos >= 0) ? path.sliced(pos + 1) : path;
int pos;
return ((pos = path.lastIndexOf(Item::PathSeparator)) >= 0 ? path.right(path.size() - (pos + 1)) : path);
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -33,7 +33,6 @@
#include <QHash>
#include <QRegularExpression>
#include <QStringList>
#include <QTimeZone>
#include <QVariant>
#include <QXmlStreamEntityResolver>
#include <QXmlStreamReader>
@ -519,7 +518,7 @@ namespace
return fallbackDate;
const QTime qTime(hour, minute, second);
QDateTime result(qDate, qTime, QTimeZone::UTC);
QDateTime result(qDate, qTime, Qt::UTC);
if (offset)
result = result.addSecs(-offset);
if (!result.isValid())

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or

View file

@ -1,7 +1,7 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2024 Jonathan Ketchker
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
@ -56,7 +56,6 @@ const QString CONF_FOLDER_NAME = u"rss"_s;
const QString DATA_FOLDER_NAME = u"rss/articles"_s;
const QString FEEDS_FILE_NAME = u"feeds.json"_s;
using namespace std::chrono_literals;
using namespace RSS;
QPointer<Session> Session::m_instance = nullptr;
@ -91,14 +90,15 @@ Session::Session()
m_itemsByPath.insert(u""_s, new Folder); // root folder
m_workingThread->setObjectName("RSS::Session m_workingThread");
m_workingThread->start();
load();
m_refreshTimer.setSingleShot(true);
connect(&m_refreshTimer, &QTimer::timeout, this, &Session::refresh);
if (isProcessingEnabled())
{
m_refreshTimer.start(std::chrono::minutes(refreshInterval()));
refresh();
}
// Remove legacy/corrupted settings
// (at least on Windows, QSettings is case-insensitive and it can get
@ -137,20 +137,19 @@ Session *Session::instance()
return m_instance;
}
nonstd::expected<Folder *, QString> Session::addFolder(const QString &path)
nonstd::expected<void, QString> Session::addFolder(const QString &path)
{
const nonstd::expected<Folder *, QString> result = prepareItemDest(path);
if (!result)
return result.get_unexpected();
auto *destFolder = result.value();
auto *folder = new Folder(path);
addItem(folder, destFolder);
addItem(new Folder(path), destFolder);
store();
return folder;
return {};
}
nonstd::expected<Feed *, QString> Session::addFeed(const QString &url, const QString &path, const std::chrono::seconds refreshInterval)
nonstd::expected<void, QString> Session::addFeed(const QString &url, const QString &path)
{
if (m_feedsByURL.contains(url))
return nonstd::make_unexpected(tr("RSS feed with given URL already exists: %1.").arg(url));
@ -160,13 +159,13 @@ nonstd::expected<Feed *, QString> Session::addFeed(const QString &url, const QSt
return result.get_unexpected();
auto *destFolder = result.value();
auto *feed = new Feed(this, generateUID(), url, path, refreshInterval);
auto *feed = new Feed(generateUID(), url, path, this);
addItem(feed, destFolder);
store();
if (isProcessingEnabled())
refreshFeed(feed, std::chrono::system_clock::now());
feed->refresh();
return feed;
return {};
}
nonstd::expected<void, QString> Session::setFeedURL(const QString &path, const QString &url)
@ -192,7 +191,7 @@ nonstd::expected<void, QString> Session::setFeedURL(Feed *feed, const QString &u
feed->setURL(url);
store();
if (isProcessingEnabled())
refreshFeed(feed, std::chrono::system_clock::now());
feed->refresh();
return {};
}
@ -214,20 +213,14 @@ nonstd::expected<void, QString> Session::moveItem(Item *item, const QString &des
Q_ASSERT(item);
Q_ASSERT(item != rootFolder());
if (item->path() == destPath)
return {};
if (auto *folder = static_cast<Folder *>(item)) // if `item` is a `Folder`
{
if (destPath.startsWith(folder->path() + Item::PathSeparator))
return nonstd::make_unexpected(tr("Can't move a folder into itself or its subfolders."));
}
const nonstd::expected<Folder *, QString> result = prepareItemDest(destPath);
if (!result)
return result.get_unexpected();
auto *destFolder = result.value();
if (static_cast<Item *>(destFolder) == item)
return nonstd::make_unexpected(tr("Couldn't move folder into itself."));
auto *srcFolder = static_cast<Folder *>(m_itemsByPath.value(Item::parentPath(item->path())));
if (srcFolder != destFolder)
{
@ -320,7 +313,7 @@ bool Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
QString url = val.toString();
if (url.isEmpty())
url = key;
addFeedToFolder(generateUID(), url, key, folder, 0s);
addFeedToFolder(generateUID(), url, key, folder);
updated = true;
}
else if (val.isObject())
@ -360,9 +353,7 @@ bool Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
updated = true;
}
const auto refreshInterval = std::chrono::seconds(valObj[u"refreshInterval"].toInteger());
addFeedToFolder(uid, valObj[u"url"].toString(), key, folder, refreshInterval);
addFeedToFolder(uid, valObj[u"url"].toString(), key, folder);
}
else
{
@ -393,14 +384,8 @@ void Session::loadLegacy()
uint i = 0;
for (QString legacyPath : legacyFeedPaths)
{
if (legacyPath.startsWith(Item::PathSeparator))
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
legacyPath.slice(1);
#else
if (Item::PathSeparator == legacyPath[0])
legacyPath.remove(0, 1);
#endif
}
const QString parentFolderPath = Item::parentPath(legacyPath);
const QString feedUrl = Item::relativeName(legacyPath);
@ -418,7 +403,7 @@ void Session::loadLegacy()
void Session::store()
{
m_confFileStorage->store(Path(FEEDS_FILE_NAME)
, QJsonDocument(rootFolder()->toJsonValue().toObject()).toJson());
, QJsonDocument(rootFolder()->toJsonValue().toObject()).toJson());
}
nonstd::expected<Folder *, QString> Session::prepareItemDest(const QString &path)
@ -444,9 +429,9 @@ Folder *Session::addSubfolder(const QString &name, Folder *parentFolder)
return folder;
}
Feed *Session::addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder, const std::chrono::seconds refreshInterval)
Feed *Session::addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder)
{
auto *feed = new Feed(this, uid, url, Item::joinPath(parentFolder->path(), name), refreshInterval);
auto *feed = new Feed(uid, url, Item::joinPath(parentFolder->path(), name), this);
addItem(feed, parentFolder);
return feed;
}
@ -468,25 +453,8 @@ void Session::addItem(Item *item, Folder *destFolder)
emit feedURLChanged(feed, oldURL);
});
connect(feed, &Feed::refreshIntervalChanged, this, [this, feed](const std::chrono::seconds oldRefreshInterval)
{
store();
std::chrono::system_clock::time_point &nextRefresh = m_refreshTimepoints[feed];
if (nextRefresh > std::chrono::system_clock::time_point())
nextRefresh += feed->refreshInterval() - oldRefreshInterval;
if (isProcessingEnabled())
{
const std::chrono::seconds oldEffectiveRefreshInterval = (oldRefreshInterval > 0s)
? oldRefreshInterval : std::chrono::minutes(refreshInterval());
if (feed->refreshInterval() < oldEffectiveRefreshInterval)
refresh();
}
});
m_feedsByUID[feed->uid()] = feed;
m_feedsByURL[feed->url()] = feed;
m_refreshTimepoints.emplace(feed, std::chrono::system_clock::time_point());
}
connect(item, &Item::pathChanged, this, &Session::itemPathChanged);
@ -507,9 +475,14 @@ void Session::setProcessingEnabled(const bool enabled)
{
m_storeProcessingEnabled = enabled;
if (enabled)
{
m_refreshTimer.start(std::chrono::minutes(refreshInterval()));
refresh();
}
else
{
m_refreshTimer.stop();
}
emit processingStateChanged(enabled);
}
@ -580,7 +553,6 @@ void Session::handleItemAboutToBeDestroyed(Item *item)
{
m_feedsByUID.remove(feed->uid());
m_feedsByURL.remove(feed->url());
m_refreshTimepoints.remove(feed);
}
}
@ -619,28 +591,6 @@ void Session::setMaxArticlesPerFeed(const int n)
void Session::refresh()
{
const auto currentTimepoint = std::chrono::system_clock::now();
std::chrono::seconds nextRefreshInterval = 0s;
for (auto it = m_refreshTimepoints.begin(); it != m_refreshTimepoints.end(); ++it)
{
Feed *feed = it.key();
std::chrono::system_clock::time_point &timepoint = it.value();
if (timepoint <= currentTimepoint)
timepoint = refreshFeed(feed, currentTimepoint);
const auto interval = std::chrono::duration_cast<std::chrono::seconds>(timepoint - currentTimepoint);
if ((interval < nextRefreshInterval) || (nextRefreshInterval == 0s))
nextRefreshInterval = interval;
}
m_refreshTimer.start(nextRefreshInterval);
}
std::chrono::system_clock::time_point Session::refreshFeed(Feed *feed, const std::chrono::system_clock::time_point &currentTimepoint)
{
feed->refresh();
const std::chrono::seconds feedRefreshInterval = feed->refreshInterval();
const std::chrono::seconds effectiveRefreshInterval = (feedRefreshInterval > 0s) ? feedRefreshInterval : std::chrono::minutes(refreshInterval());
return currentTimepoint + effectiveRefreshInterval;
// NOTE: Should we allow manually refreshing for disabled session?
rootFolder()->refresh();
}

View file

@ -1,7 +1,7 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2017-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2024 Jonathan Ketchker
* Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
* Copyright (C) 2010 Arnaud Demaiziere <arnaud@qbittorrent.org>
*
@ -35,20 +35,26 @@
* RSS Session configuration file format (JSON):
*
* =============== BEGIN ===============
* {
* "folder1": {
* "subfolder1": {
* "Feed name 1 (Alias)": {
*
{
* "folder1":
{
* "subfolder1":
{
* "Feed name 1 (Alias)":
{
* "uid": "feed unique identifier",
* "url": "http://some-feed-url1"
* }
* "Feed name 2 (Alias)": {
* "Feed name 2 (Alias)":
{
* "uid": "feed unique identifier",
* "url": "http://some-feed-url2"
* }
* },
* "subfolder2": {},
* "Feed name 3 (Alias)": {
* "Feed name 3 (Alias)":
{
* "uid": "feed unique identifier",
* "url": "http://some-feed-url3"
* }
@ -114,8 +120,8 @@ namespace RSS
std::chrono::seconds fetchDelay() const;
void setFetchDelay(std::chrono::seconds delay);
nonstd::expected<Folder *, QString> addFolder(const QString &path);
nonstd::expected<Feed *, QString> addFeed(const QString &url, const QString &path, std::chrono::seconds refreshInterval = {});
nonstd::expected<void, QString> addFolder(const QString &path);
nonstd::expected<void, QString> addFeed(const QString &url, const QString &path);
nonstd::expected<void, QString> setFeedURL(const QString &path, const QString &url);
nonstd::expected<void, QString> setFeedURL(Feed *feed, const QString &url);
nonstd::expected<void, QString> moveItem(const QString &itemPath, const QString &destPath);
@ -129,6 +135,9 @@ namespace RSS
Folder *rootFolder() const;
public slots:
void refresh();
signals:
void processingStateChanged(bool enabled);
void maxArticlesPerFeedChanged(int n);
@ -151,10 +160,8 @@ namespace RSS
void store();
nonstd::expected<Folder *, QString> prepareItemDest(const QString &path);
Folder *addSubfolder(const QString &name, Folder *parentFolder);
Feed *addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder, std::chrono::seconds refreshInterval);
Feed *addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder);
void addItem(Item *item, Folder *destFolder);
void refresh();
std::chrono::system_clock::time_point refreshFeed(Feed *feed, const std::chrono::system_clock::time_point &currentTimepoint);
static QPointer<Session> m_instance;
@ -169,6 +176,5 @@ namespace RSS
QHash<QString, Item *> m_itemsByPath;
QHash<QUuid, Feed *> m_feedsByUID;
QHash<QString, Feed *> m_feedsByURL;
QHash<Feed *, std::chrono::system_clock::time_point> m_refreshTimepoints;
};
}

View file

@ -41,10 +41,7 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QS
, m_manager {manager}
, m_downloadProcess {new QProcess(this)}
{
m_downloadProcess->setProcessEnvironment(m_manager->proxyEnvironment());
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
m_downloadProcess->setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
#endif
m_downloadProcess->setEnvironment(QProcess::systemEnvironment());
connect(m_downloadProcess, qOverload<int, QProcess::ExitStatus>(&QProcess::finished)
, this, &SearchDownloadHandler::downloadProcessFinished);
const QStringList params

View file

@ -31,10 +31,10 @@
#include <chrono>
#include <QList>
#include <QMetaObject>
#include <QProcess>
#include <QTimer>
#include <QVector>
#include "base/global.h"
#include "base/path.h"
@ -70,11 +70,7 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
, m_searchTimeout {new QTimer(this)}
{
// Load environment variables (proxy)
m_searchProcess->setProcessEnvironment(m_manager->proxyEnvironment());
m_searchProcess->setProgram(Utils::ForeignApps::pythonInfo().executableName);
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
m_searchProcess->setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
#endif
m_searchProcess->setEnvironment(QProcess::systemEnvironment());
const QStringList params
{
@ -83,6 +79,9 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
m_usedPlugins.join(u','),
m_category
};
// Launch search
m_searchProcess->setProgram(Utils::ForeignApps::pythonInfo().executableName);
m_searchProcess->setArguments(params + m_pattern.split(u' '));
connect(m_searchProcess, &QProcess::errorOccurred, this, &SearchHandler::processFailed);
@ -94,7 +93,6 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
connect(m_searchTimeout, &QTimer::timeout, this, &SearchHandler::cancelSearch);
m_searchTimeout->start(3min);
// Launch search
// deferred start allows clients to handle starting-related signals
QMetaObject::invokeMethod(this, [this]() { m_searchProcess->start(QIODevice::ReadOnly); }
, Qt::QueuedConnection);
@ -147,7 +145,7 @@ void SearchHandler::readSearchOutput()
lines.prepend(m_searchResultLineTruncated + lines.takeFirst());
m_searchResultLineTruncated = lines.takeLast().trimmed();
QList<SearchResult> searchResultList;
QVector<SearchResult> searchResultList;
searchResultList.reserve(lines.size());
for (const QByteArray &line : asConst(lines))

View file

@ -75,7 +75,7 @@ public:
signals:
void searchFinished(bool cancelled = false);
void searchFailed();
void newSearchResults(const QList<SearchResult> &results);
void newSearchResults(const QVector<SearchResult> &results);
private:
void readSearchOutput();

View file

@ -88,7 +88,6 @@ QPointer<SearchPluginManager> SearchPluginManager::m_instance = nullptr;
SearchPluginManager::SearchPluginManager()
: m_updateUrl(u"https://searchplugins.qbittorrent.org/nova3/engines/"_s)
, m_proxyEnv {QProcessEnvironment::systemEnvironment()}
{
Q_ASSERT(!m_instance); // only one instance is allowed
m_instance = this;
@ -360,12 +359,7 @@ SearchHandler *SearchPluginManager::startSearch(const QString &pattern, const QS
// No search pattern entered
Q_ASSERT(!pattern.isEmpty());
return new SearchHandler(pattern, category, usedPlugins, this);
}
QProcessEnvironment SearchPluginManager::proxyEnvironment() const
{
return m_proxyEnv;
return new SearchHandler {pattern, category, usedPlugins, this};
}
QString SearchPluginManager::categoryFullName(const QString &categoryName)
@ -373,14 +367,14 @@ QString SearchPluginManager::categoryFullName(const QString &categoryName)
const QHash<QString, QString> categoryTable
{
{u"all"_s, tr("All categories")},
{u"anime"_s, tr("Anime")},
{u"books"_s, tr("Books")},
{u"games"_s, tr("Games")},
{u"movies"_s, tr("Movies")},
{u"tv"_s, tr("TV shows")},
{u"music"_s, tr("Music")},
{u"pictures"_s, tr("Pictures")},
{u"games"_s, tr("Games")},
{u"anime"_s, tr("Anime")},
{u"software"_s, tr("Software")},
{u"tv"_s, tr("TV shows")}
{u"pictures"_s, tr("Pictures")},
{u"books"_s, tr("Books")}
};
return categoryTable.value(categoryName);
}
@ -409,70 +403,50 @@ Path SearchPluginManager::engineLocation()
void SearchPluginManager::applyProxySettings()
{
// for python `urllib`: https://docs.python.org/3/library/urllib.request.html#urllib.request.ProxyHandler
const QString HTTP_PROXY = u"http_proxy"_s;
const QString HTTPS_PROXY = u"https_proxy"_s;
// for `helpers.setupSOCKSProxy()`: https://everything.curl.dev/usingcurl/proxies/socks.html
const QString SOCKS_PROXY = u"qbt_socks_proxy"_s;
const auto *proxyManager = Net::ProxyConfigurationManager::instance();
const Net::ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
if (!Preferences::instance()->useProxyForGeneralPurposes())
// Define environment variables for urllib in search engine plugins
QString proxyStrHTTP, proxyStrSOCK;
if ((proxyConfig.type != Net::ProxyType::None) && Preferences::instance()->useProxyForGeneralPurposes())
{
m_proxyEnv.remove(HTTP_PROXY);
m_proxyEnv.remove(HTTPS_PROXY);
m_proxyEnv.remove(SOCKS_PROXY);
return;
switch (proxyConfig.type)
{
case Net::ProxyType::HTTP:
if (proxyConfig.authEnabled)
{
proxyStrHTTP = u"http://%1:%2@%3:%4"_s.arg(proxyConfig.username
, proxyConfig.password, proxyConfig.ip, QString::number(proxyConfig.port));
}
else
{
proxyStrHTTP = u"http://%1:%2"_s.arg(proxyConfig.ip, QString::number(proxyConfig.port));
}
break;
case Net::ProxyType::SOCKS5:
if (proxyConfig.authEnabled)
{
proxyStrSOCK = u"%1:%2@%3:%4"_s.arg(proxyConfig.username
, proxyConfig.password, proxyConfig.ip, QString::number(proxyConfig.port));
}
else
{
proxyStrSOCK = u"%1:%2"_s.arg(proxyConfig.ip, QString::number(proxyConfig.port));
}
break;
default:
qDebug("Disabling HTTP communications proxy");
}
qDebug("HTTP communications proxy string: %s"
, qUtf8Printable((proxyConfig.type == Net::ProxyType::SOCKS5) ? proxyStrSOCK : proxyStrHTTP));
}
const Net::ProxyConfiguration proxyConfig = Net::ProxyConfigurationManager::instance()->proxyConfiguration();
switch (proxyConfig.type)
{
case Net::ProxyType::None:
m_proxyEnv.remove(HTTP_PROXY);
m_proxyEnv.remove(HTTPS_PROXY);
m_proxyEnv.remove(SOCKS_PROXY);
break;
case Net::ProxyType::HTTP:
{
const QString credential = proxyConfig.authEnabled
? (proxyConfig.username + u':' + proxyConfig.password + u'@')
: QString();
const QString proxyURL = u"http://%1%2:%3"_s
.arg(credential, proxyConfig.ip, QString::number(proxyConfig.port));
m_proxyEnv.insert(HTTP_PROXY, proxyURL);
m_proxyEnv.insert(HTTPS_PROXY, proxyURL);
m_proxyEnv.remove(SOCKS_PROXY);
}
break;
case Net::ProxyType::SOCKS5:
{
const QString scheme = proxyConfig.hostnameLookupEnabled ? u"socks5h"_s : u"socks5"_s;
const QString credential = proxyConfig.authEnabled
? (proxyConfig.username + u':' + proxyConfig.password + u'@')
: QString();
const QString proxyURL = u"%1://%2%3:%4"_s
.arg(scheme, credential, proxyConfig.ip, QString::number(proxyConfig.port));
m_proxyEnv.remove(HTTP_PROXY);
m_proxyEnv.remove(HTTPS_PROXY);
m_proxyEnv.insert(SOCKS_PROXY, proxyURL);
}
break;
case Net::ProxyType::SOCKS4:
{
const QString scheme = proxyConfig.hostnameLookupEnabled ? u"socks4a"_s : u"socks4"_s;
const QString proxyURL = u"%1://%2:%3"_s
.arg(scheme, proxyConfig.ip, QString::number(proxyConfig.port));
m_proxyEnv.remove(HTTP_PROXY);
m_proxyEnv.remove(HTTPS_PROXY);
m_proxyEnv.insert(SOCKS_PROXY, proxyURL);
}
break;
}
qputenv("http_proxy", proxyStrHTTP.toLocal8Bit());
qputenv("https_proxy", proxyStrHTTP.toLocal8Bit());
qputenv("sock_proxy", proxyStrSOCK.toLocal8Bit());
}
void SearchPluginManager::versionInfoDownloadFinished(const Net::DownloadResult &result)
@ -495,9 +469,9 @@ void SearchPluginManager::pluginDownloadFinished(const Net::DownloadResult &resu
}
else
{
const QString &url = result.url;
const QString pluginName = url.sliced(url.lastIndexOf(u'/') + 1)
.replace(u".py"_s, u""_s, Qt::CaseInsensitive);
const QString url = result.url;
QString pluginName = url.mid(url.lastIndexOf(u'/') + 1);
pluginName.replace(u".py"_s, u""_s, Qt::CaseInsensitive);
if (pluginInfo(pluginName))
emit pluginUpdateFailed(pluginName, tr("Failed to download the plugin file. %1").arg(result.errorString));
@ -523,32 +497,29 @@ void SearchPluginManager::updateNova()
packageFile2.close();
// Copy search plugin files (if necessary)
const auto updateFile = [&enginePath](const Path &filename)
const auto updateFile = [&enginePath](const Path &filename, const bool compareVersion)
{
const Path filePathBundled = Path(u":/searchengine/nova3"_s) / filename;
const Path filePathDisk = enginePath / filename;
if (getPluginVersion(filePathBundled) <= getPluginVersion(filePathDisk))
if (compareVersion && (getPluginVersion(filePathBundled) <= getPluginVersion(filePathDisk)))
return;
Utils::Fs::removeFile(filePathDisk);
Utils::Fs::copyFile(filePathBundled, filePathDisk);
};
updateFile(Path(u"helpers.py"_s));
updateFile(Path(u"nova2.py"_s));
updateFile(Path(u"nova2dl.py"_s));
updateFile(Path(u"novaprinter.py"_s));
updateFile(Path(u"socks.py"_s));
updateFile(Path(u"helpers.py"_s), true);
updateFile(Path(u"nova2.py"_s), true);
updateFile(Path(u"nova2dl.py"_s), true);
updateFile(Path(u"novaprinter.py"_s), true);
updateFile(Path(u"socks.py"_s), false);
}
void SearchPluginManager::update()
{
QProcess nova;
nova.setProcessEnvironment(proxyEnvironment());
#if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0))
nova.setUnixProcessParameters(QProcess::UnixProcessFlag::CloseFileDescriptors);
#endif
nova.setProcessEnvironment(QProcessEnvironment::systemEnvironment());
const QStringList params
{
@ -621,14 +592,14 @@ void SearchPluginManager::parseVersionInfo(const QByteArray &info)
QHash<QString, PluginVersion> updateInfo;
int numCorrectData = 0;
const QList<QByteArrayView> lines = Utils::ByteArray::splitToViews(info, "\n");
const QList<QByteArrayView> lines = Utils::ByteArray::splitToViews(info, "\n", Qt::SkipEmptyParts);
for (QByteArrayView line : lines)
{
line = line.trimmed();
if (line.isEmpty()) continue;
if (line.startsWith('#')) continue;
const QList<QByteArrayView> list = Utils::ByteArray::splitToViews(line, ":");
const QList<QByteArrayView> list = Utils::ByteArray::splitToViews(line, ":", Qt::SkipEmptyParts);
if (list.size() != 2) continue;
const auto pluginName = QString::fromUtf8(list.first().trimmed());
@ -680,10 +651,9 @@ PluginVersion SearchPluginManager::getPluginVersion(const Path &filePath)
while (!pluginFile.atEnd())
{
const auto line = QString::fromUtf8(pluginFile.readLine(lineMaxLength)).remove(u' ');
if (!line.startsWith(u"#VERSION:", Qt::CaseInsensitive))
continue;
if (!line.startsWith(u"#VERSION:", Qt::CaseInsensitive)) continue;
const QString versionStr = line.sliced(9);
const QString versionStr = line.mid(9);
const auto version = PluginVersion::fromString(versionStr);
if (version.isValid())
return version;

View file

@ -32,7 +32,6 @@
#include <QHash>
#include <QMetaType>
#include <QObject>
#include <QProcessEnvironment>
#include "base/path.h"
#include "base/utils/version.h"
@ -88,8 +87,6 @@ public:
SearchHandler *startSearch(const QString &pattern, const QString &category, const QStringList &usedPlugins);
SearchDownloadHandler *downloadTorrent(const QString &pluginName, const QString &url);
QProcessEnvironment proxyEnvironment() const;
static PluginVersion getPluginVersion(const Path &filePath);
static QString categoryFullName(const QString &categoryName);
QString pluginFullName(const QString &pluginName) const;
@ -125,5 +122,4 @@ private:
const QString m_updateUrl;
QHash<QString, PluginInfo*> m_plugins;
QProcessEnvironment m_proxyEnv;
};

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