Compare commits

..

463 commits

Author SHA1 Message Date
sledgehammer999
0b3bce8993
Sync translations from Transifex and run lupdate 2025-04-21 12:30:57 +03:00
bolshoytoster
0160aa28b6
WebUI: Don't update UI if the page is hidden
Currently, there is unnecessary CPU/network usage by the web UI when it's running in the background, this PR prevents it from refreshing in the background.

Closes #22565.
PR #22567.
2025-04-21 17:23:09 +08:00
Chocobo1
0187f19f60
WebUI: migrate away from recursion calls
Browsers have limited recursion depth about ~10000.

PR #22580.
2025-04-21 17:21:18 +08:00
sledgehammer999
e87dfe35f3
Bump copyright year 2025-04-20 23:38:34 +03:00
sledgehammer999
e51be45ce6
Sync translations from Transifex and run lupdate 2025-04-20 23:34:53 +03:00
tehcneko
b4a16f6464
WebUI: Optimize table performance with virtual list
Adding virtual list support to dynamic tables to improve performance on large lists, I observed a 100x performance improvement on rendering on a torrent table with 5000 torrents.
This optimization is disabled by default and can be enabled in options.

PR #22502.
2025-04-20 17:18:26 +08:00
Chocobo1
250fef4ee7
Improve error messages
Print error message to stderr instead of stdout.

PR #22581.
2025-04-20 16:54:49 +08:00
Chocobo1
8fc5d0914d
Add versioning to socks.py
Also mark variable as private in novaprinter.py.

PR #22578.
2025-04-20 16:47:45 +08:00
Kostiantyn Chernenok
fc5daf6e1d
Clamp seeding time limit in session
Add clamping for seeding and inactive seeding time limit on setting from dialog and loading from config.

Closes #21953.
PR #22558.

Signed-off-by: Kostiantyn <kos.chernenok@gmail.com>
2025-04-20 16:34:04 +08:00
Kostiantyn Chernenok
c878a09d27
Swap add file/link buttons on toolbar
Swap "Add torrent file" with "Add torrent link" button to be consistent with order in File menu.

Closes #22420.
PR #22557.

Signed-off-by: Kostiantyn <kos.chernenok@gmail.com>
2025-04-19 07:57:39 +08:00
Chocobo1
2aee875642
Enforce SOCKS proxy setting in search engine plugins
Previously it require each plugin to import helpers.py to setup SOCKS proxy.
Now it is enforced by default for all plugins.
Also added a function for plugins to ignore/restore the socket to
default state.

PR #22554.
2025-04-19 07:11:50 +08:00
Vladimir Golovnev
2785636d3f
Prevent crash due to corrupted resume data
PR #22569.
Closes #22540.
2025-04-17 11:16:17 +03:00
Vladimir Golovnev
15069b2643
Fix the torrent relocates files when switching to "manual" mode
PR #22564.
Closes #22283.
Closes #22546.
2025-04-16 10:23:34 +03:00
Chocobo1
f0361f1bed
Use the proper keyboard shortcut for deleting items on macOS
Closes #20187.
PR #22544.
2025-04-15 15:13:36 +08:00
Vladimir Golovnev
110e6d32b4
Explicitly reject opened Add torrent dialogs when exiting app
PR #22535.
Closes #19933.
Supercedes #22533.
2025-04-14 09:51:59 +03:00
Chocobo1
3d73026ff2
Add SOCKS4/SOCKS4a proxy support to search engine
Pass 'Perform hostname lookup via proxy' setting along the way.
Also add underline to variables and functions that are private to the python module.

PR #22510.
2025-04-13 16:25:38 +08:00
Chocobo1
abafbc0685
WebUI: avoid saving invalid size
Don't save the wrong size when the tab is collapsed.
Reported in: https://github.com/qbittorrent/qBittorrent/pull/21215/files#r1966052959

PR #22537.
2025-04-12 17:59:42 +08:00
Chocobo1
5465605377
WebUI: fix dark mode in RSS entry viewer
Use `allow-same-origin` sandbox directive to allow fetching the parent CSS.

PR #22536.
2025-04-12 17:54:55 +08:00
luzpaz
9331580e86
Fix grammar
ref: https://github.com/qbittorrent/qBittorrent/pull/19333#discussion_r1793252710

PR #22525.
2025-04-12 17:46:48 +08:00
FredBill1
795889c417
Migrate socks.py from SocksiPy to PySocks 1.7.1
Migrate `socks.py` from SocksiPy 1.01 to [PySocks 1.7.1](c2fa43cbe1/socks.py), allowing python 3+ compatibility, [details](https://github.com/qbittorrent/qBittorrent/issues/16447#issuecomment-2776894026).

The content of the `socks.py` is entirely copied from the [PySocks repository](c2fa43cbe1/socks.py), the only modification is the license header at the top of the file and trimming trail whitespaces.

Closes #16447.
PR #22507.
2025-04-09 17:36:50 +08:00
Chocobo1
ff03eeab5b
Show info hash in log when added a duplicate torrent
Closes #22161.
PR #22505.
2025-04-08 16:31:04 +08:00
Chocobo1
f0b9a17566
WebUI: add headers to RSS entry viewer
Introduced Author, 'Open link' headers.
Note that the Author and 'Open link' are not mandatory fields in RSS/Atom feeds. So these
headers will only be displayed when the feed includes them.

PR #22503.
2025-04-08 15:47:47 +08:00
xavier2k6
72e8b3272b
GHA CI: Use Qt 6.9.0 on Windows and macOS
PR #22509.
2025-04-08 15:35:58 +08:00
skomerko
6c36830e5e
WebUI: Set status filter to 'All' if selected filter is no longer visible
Fixup for #21145

To reproduce:
1. Select status filter with 0 torrents
2. Enable 'Auto hide zero status filters' and save settings. Hidden filter is still selected:

PR #22487.
2025-04-05 17:13:14 +08:00
Chocobo1
cdddaae939
WebUI: fix preferences not applied in magnet handler
Thanks for the diagnosis in this [post](https://github.com/qbittorrent/qBittorrent/issues/22495#issue-2958553624).

Closes #21486.
Closes #22495.
PR #22504.
2025-04-05 13:51:08 +08:00
tehcneko
f540381caf
WebUI: Support creating new torrents
Implemented the torrent creator using WebAPI from #20366 in WebUI, the interface is mostly inspired by GUI and VueTorrent.

Closes #5614.
PR #22459.
2025-04-03 17:16:12 +08:00
Vladimir Golovnev
055d82bda4
Add option to enable previous Add new torrent dialog behavior
Some people are still unhappy with "standalone window mode" of "Add new torrent dialog" so just provide them with an option to use old "modal dialog mode" in all the current qBittorrent branches.

PR #22492 (based on original PR #19874).
2025-03-31 09:18:16 +03:00
Chocobo1
0796f96ee4
Merge pull request #22482 from Chocobo1/process_env
Refine environment variable scope
2025-03-30 15:12:10 +08:00
Vladimir Golovnev
841cffafa7
Restore ability to use server-side translation by custom WebUI
PR #20968.
2025-03-30 09:47:21 +03:00
Chocobo1
ade39432be
Revise wordings related to SOCKS4 proxy
The affected options are not really incompatible with SOCKS4 but it is due to Qt missing
implementation. Therefore 'unavailable' is more suitable.

PR #22483.
2025-03-29 21:09:49 +08:00
Chocobo1
830d2c207b
WebAPI: bump version
Related: https://github.com/qbittorrent/qBittorrent/pull/22460#issuecomment-2748821812

And add initial version of WebAPI changelog.

PR #22481.
2025-03-29 20:47:53 +08:00
Chocobo1
97865545c3
WebUI: fix Tag counter counting wrong
Related: 73e9116d21 (r2014898781)

PR #22480.
2025-03-29 20:41:05 +08:00
Hanabishi
3abdc3134b
WebUI: Disable alternative UI in case of the index page being inaccessible
Initial failed access shows an error as before, but on the next reload it falls back to the default WebUI.

PR #22399.
Closes #18401.
2025-03-29 20:32:22 +08:00
Chocobo1
5a716a40fb
Simplify proxy related code 2025-03-28 18:39:25 +08:00
Chocobo1
943e403241
Refine environment variable scope
Previously the proxy environment variable will affect the qbt process globally. Now it is
limited to where it required.
2025-03-28 18:15:53 +08:00
Vladimir Golovnev
103ea813dc
RSS: Fix crash when moving a folder into its subfolder
PR #22479.
Closes #18446.
2025-03-28 09:03:59 +03:00
Vladimir Golovnev
52b1f3588a
RSS: Mark matched article as "read" if refers to duplicate torrent
PR #22477.
2025-03-28 09:01:22 +03:00
Vladimir Golovnev
4bd50672e8
Improve add torrent error handling
PR #22468.
2025-03-25 09:13:15 +03:00
Chocobo1
8c8a0ac54c
WebAPI: improve setting preferences behavior
Now the behavior is more intuitive for a few options when the client send in partial settings.
This change is backward compatible.

For example, now it is possible to have only one of `max_ratio_enabled` or `max_ratio` instead
of requiring both.

PR #22460.
2025-03-24 21:04:35 +08:00
Chocobo1
7b4a3fccc6
WebUI: replace deprecated data type
`Hash` is deprecated by mootools.
Also simplify related code.

PR #22458.
2025-03-23 15:01:39 +08:00
Chocobo1
d21653e8cf
Don't leak parent file descriptors to child processes
It is unexpected for the child process to inherit parent file descriptors.
Requires Qt >= 6.6 and only affects Linux.

Closes #10312.
PR #22457.
2025-03-23 14:48:21 +08:00
Vladimir Golovnev
627d89813c
RSS: Allow to set refresh interval per feed
PR #22448.
2025-03-22 08:43:04 +03:00
Chocobo1
b28c229f85
Add control for 'hostname resolver cache expiry interval'
Also add a few missing units in WebUI.

Closes #22267.
PR #22439.
2025-03-17 19:40:06 +08:00
Chocobo1
8d0870c953
Switch to string view where applicable
PR #22438.
2025-03-17 19:28:38 +08:00
Chocobo1
5a4b3b25d3
Use slice method where applicable
These code segments already have its boundary checked and can thus be faster.

PR #22411.
2025-03-15 14:58:59 +08:00
Vladimir Golovnev
d174bc75e4
Show free disk space in status bar
PR #22407.
Closes #19607.
2025-03-13 14:47:10 +03:00
Chocobo1
882da47609
Use Qt built-in function for comparing values
PR #22389.
2025-03-10 03:19:31 +08:00
Chocobo1
b74b334e34
Add tests for PeerAddress struct
PR #22388.
2025-03-10 03:11:08 +08:00
Vladimir Golovnev
53f919aea8
Add missing includes
PR #22362.
2025-03-05 09:03:00 +03:00
Chocobo1
62a7fd86d6
Improve "split to byte array views" function
1. Utilize string matcher
2. Remove split behavior parameter
   Previously `KeepEmptyParts` behavior doesn't match Qt's
   implementation and since our codebase doesn't really make use of it,
   we can just remove the parameter.
3. Add tests.

PR #22352.
2025-03-03 21:42:03 +08:00
Chocobo1
96295adc08
Merge pull request #22351 from Chocobo1/ci_tweak
Improve CI scripts
2025-03-03 21:28:23 +08:00
skomerko
8f53fb8178
WebUI: Maintain row highlight after rearranging table columns
This PR fixes a bug where row highlight effect would be lost after reordering columns.

PR #22339.
2025-03-02 17:15:21 +08:00
skomerko
37eb80919c
WebUI: Fix bug where the 'Tracker editing' dialog displays incorrect data
In Trackers table, moving the 'URL' column from its default (2) position caused the 'Tracker editing' dialog to display incorrect data.
Steps to reproduce:
1. Move 'URL' column in Trackers table to any position from default
2. Choose tracker URL and click 'Edit tracker URL'

PR #22338.
2025-03-02 17:08:04 +08:00
Chocobo1
1b044d9476
GHA CI: shorten Windows CI build time
Now vcpkg caches b2 tool. Boost doesn't need the exact b2 version to generate the cmake files.
2025-03-01 16:12:59 +08:00
Chocobo1
83599f1f7b
GHA CI: tweak cache size
It seems ~500MB is enough to cache all the build artifacts but we still
make it a bit larger to avoid thrashing.
2025-03-01 16:12:57 +08:00
Vladimir Golovnev
6e1b5ec18b
Don't miss to declare some of the color IDs
PR #22330.
Closes #22326.
2025-02-25 18:56:15 +03:00
Vladimir Golovnev
249c80aaaf
Improve command line parameters serialization
PR #22319.
Closes #22306.
2025-02-25 09:11:03 +03:00
Chocobo1
0ac47496d4
GHA CI: ensure compatibility with newer cmake versions
Fixes #22315.
PR #22320.
2025-02-25 14:08:09 +08:00
Chocobo1
4ec80de268
Update website URL
The website don't use php now.

PR #22321.
2025-02-25 14:03:30 +08:00
skomerko
f432c1e615
WebUI: Show 'Edit tracker URL...' only when one tracker is selected
We can only edit one URL through the dialog, so there's no point in showing this context option when more than one tracker is selected in trackers table.

PR #22311.
2025-02-25 13:55:04 +08:00
Chocobo1
41d9ee91a1
WebUI: tell web crawlers do not index the WebUI
PR #22309.
2025-02-23 15:20:22 +08:00
skomerko
ba3d89b674
WebUI: Update sort icon after changing column order
This PR fixes a bug where the sort icon did not update correctly after reordering columns.

Steps to reproduce:
1. Sort a column
2. Move it to a different position
3. The sort icon remains in its original location

PR #22299.
2025-02-23 15:13:17 +08:00
skomerko
1ca33d45ba
WebUI: Access element attribute/property natively in log tables
#21007 changed pretty much everything already but I spotted some leftovers and replaced them too.

PR #22294.
2025-02-21 20:54:26 +08:00
Chocobo1
a9b54d94a0
Merge pull request #22282 from skomerko/webui-v51-fixes
WebUI v5.1 fixes
2025-02-21 20:44:42 +08:00
Luke Memet
693390ff27
Fix shift-click selection on macOS
PR #22284.
Closes #16818.
2025-02-19 13:52:51 +03:00
Daniel Nylander
5ddc5a8b87
NSIS: Update Swedish translation
PR #22046.
2025-02-19 13:45:59 +03:00
Bark
ad9100ac07
WebAPI: Do not wrap result if offset is invalid
Closes #22158.
PR #22174.
2025-02-18 13:53:30 +08:00
Chocobo1
1043bea896
Refactor power management classes
Mainly it is about moving each platform code to its own file.

PR #22279.
2025-02-18 11:58:43 +08:00
Chocobo1
955688c125
WebUI: replace rounding function from MooTools
The `round()` returning floating point number is not a good idea. This is due to floating point
representation is imprecise and sometimes it cannot faithfully represent a number, for example
`0.09 + 0.01 !== 0.1 `. Therefore, it should be avoided and/or utilize other function
to achieve the goal.

Also, improve `window.qBittorrent.Misc.toFixedPointString()` and add test cases.

PR #22281.
2025-02-17 15:11:55 +08:00
Chocobo1
8da43a4054
Use const accessor
This avoids an unnecessary check to the container internal atomic variable and prevents
potential detachment.

PR #22280.
2025-02-16 15:51:40 +08:00
Chocobo1
ddf6dd5fa2
GHA CI: fix AppImage building
Upstream now defaults to static runtime and the previous URL is invalid now.
Upstream commits:
* c28054bab6
* ce5291e259

Also fuse2 is not needed now as stated on:
https://github.com/AppImage/type2-runtime?tab=readme-ov-file#type2-runtime-

PR #22286.
2025-02-16 05:08:39 +08:00
skomerko
8c02bbb4bc WebUI: Select next available search tab after closing last active tab with X button 2025-02-15 10:59:56 +01:00
skomerko
7e95375cec WebUI: Fix unknown country flag path 2025-02-15 10:59:56 +01:00
skomerko
29201fa016 WebUI: Apply scrollbar style to context menu elements 2025-02-15 10:59:56 +01:00
skomerko
1a3d0f6fab WebUI: Adjust context menu offsets in Search tab & Status filter list 2025-02-15 10:59:56 +01:00
skomerko
f58d6ae984 WebUI: Make context menu target selectors more precise 2025-02-15 10:59:56 +01:00
skomerko
7f0134108a WebUI: Use classlist property to set cell class in trackers table 2025-02-15 10:59:53 +01:00
Chocobo1
d79dc86d00
WebUI: require Subresource Integrity on external links
Also migrate to .mjs format.

PR #22263.
2025-02-12 15:19:07 +08:00
Chocobo1
38070c6eee
WebUI: use recommended function for checking NaN values
Also fix a few variable names along the way.

PR #22264.
2025-02-12 15:11:54 +08:00
Vladimir Golovnev
c9eb1fbac8
WebAPI: Don't trim string parameters
PR #22266.
Closes #19485.
Closes #22254.
2025-02-12 09:33:41 +03:00
sledgehammer999
7238bad5a6
Bump to v5.2.0alpha1 2025-02-11 02:04:46 +02:00
sledgehammer999
bd564a99a3
Sync translations from Transifex and run lupdate 2025-02-11 01:56:49 +02:00
Chocobo1
b052ad0923
WebUI: inline redundant function
This also fix share ratio dialog which had been broken in recent cleanup.

PR #22252.
2025-02-09 16:03:01 +08:00
Chocobo1
c65a68251e
WebUI: use native function when converting to numbers
PR #22246.
2025-02-08 17:58:48 +08:00
skomerko
93925042dd
WebUI: Fix memory leak in context menus
This PR fixes a memory leak in context menus. Previously, for some reason, each menu retained references to its target elements without utilizing them further. Since the targets property was accessible/reachable from the root (window object), these references persisted even after the elements were removed from the DOM, preventing them from being garbage collected.
It's easily reproducible - just add a decent amount of torrents, switch between categories multiple times, then capture heap/detached elements snapshot in the Memory tab (Chrome dev tools). The number of detached elements will continue to increase after each category switch and they won't be cleaned up.
[More context](https://github.com/qbittorrent/qBittorrent/pull/22220/files#r1941137796)

PR #22234.
2025-02-08 17:51:21 +08:00
Chocobo1
e55b59d9ca
Use switch statement
PR #22247.
2025-02-08 17:39:10 +08:00
xavier2k6
f8469d02f7
GHA CI: Bump FORTIFY_SOURCE hardening flag to 3
PR #22248.
2025-02-08 17:00:10 +08:00
Chocobo1
dc10b88cec
GHA CI: explicitly set C++20 mode for libtorrent
Bump Boost version. Boost::asio 1.76 has a bug that prevents compiling in C++20 mode.

PR #22245.
2025-02-08 16:30:36 +08:00
Maxime Thiebaut
4406a3f173
Add announce_port support
The `announce_port` setting permits to overwrite the port passed along to trackers as the `&port=` parameter. If left as the default, the listening port is used. This setting is only meant for very special cases where a seed's listening port differs from the effectively exposed port (e.g., through external NAT-PMP). See https://github.com/arvidn/libtorrent/pull/7771 for an example use-case.

This PR adds the relevant setting alongside the existing `announce_ip` setting.

PR #21692.
2025-02-08 16:12:50 +08:00
skomerko
9c2e698514
WebUI: Replace getElements & getChildren
This PR further reduces Mootools usage.
PR #22220.
2025-02-04 17:08:18 +08:00
xavier2k6
463700b76d
GHA CI: Bump libtorrent versions
PR #22217.
2025-02-03 09:07:35 +03:00
Vladimir Golovnev
86387fbe49
Return first tracker as fallback for "current tracker"
PR #22224.
Closes #20415.
2025-02-01 14:58:12 +03:00
Vladimir Golovnev
a018cfa56c
Remove stopped torrent from "error" tracker filter
PR #22219.
2025-01-31 06:23:04 +03:00
Vladimir Golovnev
b76054beba
Store search history
PR #22208.
2025-01-30 08:59:10 +03:00
Chocobo1
f8536162f2
Use compact format for JSON files
It saves a bit of space on disk and deters novice users from tampering them.

PR #22211.
2025-01-29 15:31:06 +08:00
skomerko
af65ddd012
WebUI: Allow to move state icon to name column in torrents table
PR #22118.
2025-01-28 14:46:09 +08:00
Chocobo1
fe9dc131bc
WebUI: don't wrap regex literal in regex constructor
PR #22206.
2025-01-28 00:33:04 +08:00
thalieht
bb4a668ddd
Hide zero and infinity values in peer list only when that setting is set to Always
PR #22205.
Closes #21998.
2025-01-27 09:40:44 +03:00
Vladimir Golovnev
3978137534
Store opened search tabs
PR #22163.
Closes #167.
2025-01-26 17:12:50 +03:00
Chocobo1
3ef4d0d798
GHA CI: add checking for GHA workflows
PR #22200.

---------

Co-authored-by: userdocs <16525024+userdocs@users.noreply.github.com>
2025-01-26 03:44:59 +08:00
Chocobo1
e2341f5217
Merge pull request #22199 from Chocobo1/webui_eslint
WebUI: improve lint checks
2025-01-26 03:34:34 +08:00
xavier2k6
abd3cd54bc
GHA CI: Bump numerous hook revs
PR #22193.
2025-01-26 03:26:27 +08:00
Chocobo1
dc8ac38494
WebUI: revise lint rules for css
Some rules are already covered by other tools, so remove them.
2025-01-24 23:58:13 +08:00
Chocobo1
e3eacf2bf7
WebUI: migrate stylelint config file format
From https://stylelint.io/user-guide/configure :
>Stylelint currently supports other configuration locations and formats, but we may remove these
>in the future:
>...
>.stylelintrc.json file
2025-01-24 22:26:36 +08:00
Chocobo1
5098519d46
WebUI: enable cache for lint checks
Also, WebUI gets its own .gitignore file.
2025-01-24 22:26:35 +08:00
ze0s
82c36aea89
WebAPI: add new method setTags to upsert tags on torrents
This is another optimization for torrent management on large scale instances with the goal to minimize the amount of required API calls. Ref #22128.

This new function and endpoint torrents/setTags does an upsert to replace the torrent tags and handles the removal and add internally, instead of doing multiple calls to add and remove tags on torrents from the client.

PR #22156.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
Co-authored-by: Vladimir Golovnev <glassez@yandex.ru>
2025-01-24 22:24:35 +08:00
thalieht
05787d94ec
Fix torrent content checkbox state under certain conditions
PR #22190.
Closes #22189.
2025-01-24 12:11:19 +03:00
Chocobo1
f8c48349a1
WebUI: use native function for selecting elements
PR #22179.
2025-01-20 23:36:11 +08:00
Chocobo1
1ee84033ec
WebUI: use template literals instead of string concatenation
PR #22177.
2025-01-18 20:51:47 +08:00
Chocobo1
f2eecf8a4e
Avoid memory leak on macOS
Only Mark-of-the-Web and Power Management are affected.

PR #22176.
2025-01-18 20:30:14 +08:00
ze0s
76e1040232
WebAPI: optionally include trackers list in torrent info response
This PR adds an optional parameter includeTrackers to the Torrent info endpoint /torrents/info to include the trackers list of each torrent in the response under the key trackers.

PR #22128.
2025-01-17 17:10:25 +08:00
Chocobo1
4686d6709e
GHA CI: show installed version
Before this change, it wasn't clear which exact version were installed.

PR #22155.
2025-01-13 17:24:08 +08:00
Chocobo1
2cc7ec90a8
WebUI: add percentage sign for hsl components
This is a workaround to avoid confusing the stylelint checker (and probably some other checker
internally). They cannot accept the fact that the component can be `<number>` but instead
insist it should be `<percentage>`
Reference: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl#formal_syntax

Also, MDN recommended to use `hsl()` instead of `hsla()`.
From https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl :
> Note: The hsla() functional notation is an alias for hsl(). They are
> exactly equivalent. It is recommended to use hsl().

PR #22154.
2025-01-13 17:13:20 +08:00
Vladimir Golovnev
99adb16090
Allow to reuse existing search term
PR #22148.
2025-01-13 10:21:40 +03:00
Chocobo1
c622d50814
WebUI: use native API for accessing query string
PR #22141.
2025-01-12 21:36:59 +08:00
Chocobo1
11991e62f5
WebUI: fix error when opening the same dialog twice
PR #22139.
2025-01-12 21:18:41 +08:00
Chocobo1
82d90e599c
Preserve allocated buffer capacity
PR #22138.
2025-01-12 21:01:39 +08:00
xavier2k6
45b7947cd0
GHA CI: Update Boost URL
The agreement between Boost & JFrog came to an end in December 2024.
Reference: https://github.com/boostorg/boost/issues/924

PR #22125.
2025-01-11 21:33:51 +08:00
Chocobo1
2e21cf76de
WebUI: temporarily pin dependency
The plugin was causing problems in new versions so pin it with an older
version and look into it later.
Upstream issue: https://github.com/JamieMason/eslint-plugin-prefer-arrow-functions/issues/50

PR #22140.
2025-01-11 16:28:56 +08:00
Vladimir Golovnev
76151110e5
Handle Qt style options uniformly
PR #22133.
Closes #22061.
2025-01-11 10:17:38 +03:00
Vladimir Golovnev
5875d8bff3
Allow multiple simultaneous searches
PR #22127.
2025-01-11 09:53:40 +03:00
Hanabishi
68ecb13d14
Change URL seed error message
Current URL seed error message assumes that only possible error is DNS lookup failure, which is not true.
So replace it with a more generic message. Real reason is provided by the 'Error:' part.

PR #22119.
2025-01-10 08:31:28 +03:00
Vladimir Golovnev
f9f4b60b83
Allow to refresh existing search
PR #22122.
Closes #17184.
2025-01-08 17:03:32 +03:00
Thomas Piccirello
4fc36b9e99
Support fetching tracker list from URL
Trackers specified at the URL will be added to newly added public torrents.

This feature is adapted from qBittorrent-Enhanced-Edition to allow for automatically adding trackers retrieved from a URL. @ngosang's trackerlist repo is a good example, however I've opted not to include a default URL.

Partially addresses #14535.
PR #21828.
2025-01-08 14:51:09 +08:00
Chocobo1
4f3d77963f
Add parameter to control whether to unescape HTML entities
Some plugin needed the raw data for further processing.
Related: #22074.

PR #22106.
2025-01-06 19:05:57 +08:00
Chocobo1
d911928c59
WebUI: Remove unnecessary hashing
Now the containers support using string as key so the intermediate hashing/mapping to number
isn't needed now.

PR #22103.
2025-01-06 18:53:18 +08:00
Chocobo1
22e156e0af
Simplify captured variable type
By capturing the specific data instead of torrent handle the lambda can become non-mutable.

PR #22102.
2025-01-06 18:22:43 +08:00
skomerko
6fe02895a8
WebUI: Remove redundant event listener
This PR removes broken event listener attached to RSS Rules table. 

PR #22083.
2024-12-31 21:43:39 +08:00
skomerko
395dbaa5c6
WebUI: Replace getElement with querySelector
All `getElement` instances (Mootools) were changed to `querySelector`.

PR #22082.
2024-12-31 21:31:46 +08:00
Chocobo1
efe06f133d
Revise DHT bootstrap node list
Discussion:
https://github.com/qbittorrent/qBittorrent/pull/21296#issuecomment-2562341328
https://github.com/qbittorrent/qBittorrent/pull/21296#issuecomment-2561373010

PR #22081.
2024-12-31 13:34:08 +08:00
Chocobo1
9c0475ebfa
WebUI: migrate to fetch API
This is the final part of it.

PR #22072.
2024-12-29 15:44:28 +08:00
Chocobo1
e740a42366
Merge pull request #22070 from Chocobo1/py_html_decode
Improve Search engine
2024-12-29 14:39:11 +08:00
Chocobo1
cc31a90931
Provide SSL context field
The allows the caller to provide proper SSL parameters and avoid dirty monkey patching to
suppress SSL errors.
2024-12-27 03:53:46 +08:00
Chocobo1
90e457a671
Use built-in method for decoding HTML entities 2024-12-27 03:52:50 +08:00
Chocobo1
7487cd7e6d
WebUI: disallow unnecessary quotes in property name
Those two forms are the same and from now on we enforce to one style.

PR #22051.
2024-12-24 22:25:18 +08:00
skomerko
bbc3c2832f
WebUI: Use closest() to get parent element
All `getParent()` instances (Mootools) were changed to use `closest()` method:
https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

PR #22048.
2024-12-23 23:07:17 +08:00
Chocobo1
879c6bf9ff
Simplify conversion to string
PR #22036.
2024-12-23 22:59:01 +08:00
Chocobo1
f2097dc4b5
Avoid redundant copy
PR #22035.
2024-12-23 22:43:23 +08:00
Hugo Carvalho
166feb5bdf
NSIS: Update Portuguese translation
PR #21632.
2024-12-23 22:37:46 +08:00
Chocobo1
a841fe9320
WebUI: migrate to fetch API
And away from mootools.

PR #22037.
2024-12-22 17:51:19 +08:00
Patrik Elfström
9709672b34
WebUI: Change filter inputs to type search
Changing input type from text to search for all search and filter inputs
to enable user to easily clear input.

This feature is yet to be implemented in Firefox.
See tracking bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1654288

Also fix search icon horizontal positioning and minor input box paddings.

Closes #15481.
PR #22033.
2024-12-22 17:19:03 +08:00
xavier2k6
e2db0bc866
GHA CI: Update pandoc to latest
* Bumped `pandoc` -> 3.6

PR #22021.
2024-12-20 18:27:05 +08:00
xavier2k6
fee45e4ba6
GHA CI: Bump some pre-commit hook revs
* Bumped `pre-commit-hooks` -> v5.0.0
* Bumped `typos` -> v1.28.4

PR #22020.
2024-12-20 18:16:43 +08:00
Zentino
257d928ab3
Resolve relative URLs within RSS article description
PR #21943.


---------

Co-authored-by: Vladimir Golovnev <glassez@yandex.ru>
2024-12-20 10:19:34 +03:00
Chocobo1
34c8849f22
Fix tab order in RSS widget
Related: https://github.com/qbittorrent/qBittorrent/pull/21996#issuecomment-2543127251

PR #21999.
2024-12-18 02:29:51 +08:00
Chocobo1
1c82eb3dff
Merge pull request #21996 from Chocobo1/check_ui
Sort grid items properly
2024-12-18 02:19:34 +08:00
sledgehammer999
d96ab6ba84
Bump to v5.1.0beta1 2024-12-16 21:56:08 +02:00
Chocobo1
7886ca65f9
Make tab key switch focus
These fields do not expect tab characters.
2024-12-16 01:30:47 +08:00
Chocobo1
85c4ddf616
Make links accessible by keyboard 2024-12-16 01:30:47 +08:00
Chocobo1
0a36171999
Sort grid items properly
Supersedes #21856.
2024-12-16 01:30:47 +08:00
Vladimir Golovnev
eb2eea8d34
Avoid race condition when update tracker entries
PR #21995.
2024-12-15 14:07:36 +03:00
skomerko
14684c8c83
WebUI: Use vanilla JS to create elements
All elements are now created using createElement() method:
https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement

PR #21975.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-12-15 03:31:44 +08:00
Chocobo1
c887a6f7d8
GHA CI: add checks for grid items order
Now all items under `QGridLayout` are required to be sorted. This allow
us to omit tabstop order. The tabstop order will follow the layout order.

The script can be invoked to fix wrong grid items order in .ui files:
```console
python check_grid_items_order.py file.ui
```
2024-12-14 17:08:18 +08:00
Chocobo1
4c6dd8e68d
Remove wrong conditional in Origin trustworthy check
It might block WebUI from logging in under specific network configurations.

Fix up 130c0d8487.
PR #21972.
2024-12-13 16:12:29 +08:00
antanilol
27451469fa
Add eXact Length parameter when creating magnet URI
Include the `xl` (eXact Length) parameter in the magnet URI string inside the function `TorrentImpl::createMagnetURI()`.

Closes #20752.
PR #21958.
2024-12-09 03:29:41 +08:00
Chocobo1
a311c259cc
Use proper data type for elapsed time
PR #21963.
2024-12-08 17:02:20 +08:00
Chocobo1
0ad65ceef6
Remove unused variable
PR #21962.
2024-12-08 16:44:47 +08:00
Chocobo1
cbf7c09bf4
Use built-in method for setting header
PR #21961.
2024-12-08 16:33:35 +08:00
skomerko
3fcc298539
WebUI: Add missing icon to 'Queue' context menu item
PR #21948.
2024-12-08 16:27:14 +08:00
skomerko
7080f85b59
WebUI: Replace Mootools class list manipulation methods
All `addClass()`, `removeClass()` and `hasClass()` instances were changed to use `classList` equivalent:
https://developer.mozilla.org/en-US/docs/Web/API/Element/classList

PR #21946.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-12-08 16:12:57 +08:00
Giacomo411
9f0fa4c215
NSIS: Update Italian translation
PR #21920.
2024-12-07 20:44:20 +08:00
Chocobo1
6f31a13f22
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 20:43:16 +08:00
Vladimir Golovnev
200f7fc628
Use cached current time when parse RSS feed
PR #21959.
2024-12-07 11:10:53 +03:00
Vladimir Golovnev
a180162405
Avoid redundant requests of announce entries from libtorrent
PR #21949.
2024-12-06 19:59:45 +03:00
Chocobo1
2d1c4fc809
WebUI: use native event listeners for keyboard events
PR #21924.
2024-12-02 13:41:19 +08:00
Chocobo1
e8d8de8f19
WebUI: fix window can not close regression
Fix up 1cd3c586c1.
Fix up e1bd1038c0.

PR #21919.
2024-11-29 23:59:58 +08:00
Chocobo1
90aecfea02
WebUI improvements
WebUI improvements
2024-11-29 23:52:55 +08:00
skomerko
a85736fd27
WebUI: Set base background color
This PR ensures that the same base background color is used across different browsers (more consistent styling).
Context: https://github.com/qbittorrent/qBittorrent/pull/21498#issuecomment-2399929576
Used default Chrome colors: https://github.com/qbittorrent/qBittorrent/issues/21894#issuecomment-2494253459

PR #21914.
2024-11-29 23:44:53 +08:00
skomerko
dafbcf8709
WebUI: Add colors to 'Status' column in Trackers table
PR #21820.
2024-11-29 23:36:08 +08:00
wavygecko
d11622e3c0
Don't add duplicate episodes to previously matched
PR #21917.
2024-11-28 11:37:05 +03:00
Vladimir Golovnev
d90a9d15ac
Avoid using QDateTime for announce timestamps
PR #21906.
2024-11-27 21:03:54 +03:00
Chocobo1
f8aaea3476
WebUI: locate element faster 2024-11-27 16:17:57 +08:00
Chocobo1
b84a51c76d
WebUI: revise Edit Category dialog button text
The generic "OK" is suitable for more scenarios.
2024-11-27 01:22:58 +08:00
Chocobo1
83b0dd3026
WebUI: fix checkbox initialization
Previously the checkbox had all options checked regardless of the stored
setting.
2024-11-27 01:12:13 +08:00
Chocobo1
ef5506321a
WebUI: fix invalid style
`initial` isn't applicable to `borderLeft`/`borderRight`.
2024-11-27 01:12:13 +08:00
Chocobo1
24d349ffba
WebUI: fix wrong event property 2024-11-27 01:12:13 +08:00
Chocobo1
2109e13746
WebUI: use proper event for handling text changes 2024-11-27 01:12:13 +08:00
Chocobo1
5eec0c0213
WebUI: use idiomatic string methods 2024-11-27 01:12:13 +08:00
Chocobo1
f34787e6ba
WebUI: use correct property for selecting child elements
`firstChild` will select the first `Node` which is often not intended (it should be
`Element` instead).
2024-11-27 01:12:12 +08:00
Vladimir Golovnev
15ea836bb9
Avoid repeatedly creating the same QDateTime values
PR #21904.
2024-11-26 09:04:59 +03:00
Chocobo1
72e033db79
WebUI: remove child elements directly 2024-11-26 00:43:58 +08:00
Chocobo1
e1bd1038c0
WebUI: simplify close window implementation
The caller site now take the responsibility to ensure the element is valid.

PR #21892.
2024-11-26 00:40:05 +08:00
Chocobo1
3ebdb50457
Verify hash of Python installer
PR #21877.
2024-11-26 00:29:11 +08:00
Chocobo1
b0fe6e6c59
WebUI: ensure cached info are initialized properly
PR #21893.
2024-11-25 14:04:28 +08:00
Chocobo1
8d847eeb18
WebUI: clean up fetch API usage
The `Content-type` header isn't required since `URLSearchParams` is present.
The `method` property is preferred to be always specified for clarity.
The `cache: "no-store"` is preferred for most GET requests to avoid caching.

PR #21891.
2024-11-25 13:49:35 +08:00
Chris B
f022ce8f84
WebAPI: Add forced parameter to torrents/add
Adds the parameter `addForced` to the `/api/v2/torrents/add` API call. Defaults to false if not provided.

PR #21864.
2024-11-23 16:19:19 +08:00
Thomas Piccirello
78a5e4ff3e
WebUI: Display error when download fails
Previously we would still download the file but it would contain the error response, resulting in an invalid file.
To test: export a .torrent file for a torrent that hasn't yet downloaded metadata

PR #21696.

Signed-off-by: Thomas Piccirello <thomas@piccirello.com>
2024-11-23 16:03:43 +08:00
tinyboxvk
61ff683f11
Update link to news
Change `https://www.qbittorrent.org/news.php` to `https://www.qbittorrent.org/news` to avoid redirect.

PR #21872.
2024-11-22 21:05:39 +08:00
skomerko
7300b9f759
WebUI: Eliminate unnecessary Status filter list updates
Only update the Status filter list when torrents are removed, added or their state changed.

PR #21866.
2024-11-22 20:59:23 +08:00
Bartu Özen
6ce2869108
WebAPI: Fix incorrect key in torrent creator
PR #21879.
2024-11-21 13:59:20 +03:00
Vladimir Golovnev
0eba285ff1
Fix incorrect SQL column definition
PR #21874.
2024-11-21 13:57:37 +03:00
Vladimir Golovnev
88161a6467
Discard obsolete "state update" events after torrent is reloaded
PR #21873.
Closes #21827.
2024-11-21 13:56:22 +03:00
Chocobo1
7f901a812d
Improve Python installation process
Instead of waiting it to complete, now it will react on installation process finish.
Also add more logging.

PR #21863.
2024-11-19 18:57:44 +08:00
Chocobo1
6578fd06fd
WebUI: fix failed conversion to number
The value has unit suffix (`px`) and require using `parseInt()` for conversion.

Fix up f73f31619d.
PR #21862.
2024-11-19 03:12:50 +08:00
Chocobo1
6ddde3f4b6
Avoid redundant string length function calls
Also switch to `std::string_view` as it is more generic and can handle more types (including
view types).

PR #21861.
2024-11-19 02:53:16 +08:00
Patrik Elfström
530631322d
WebUI: Fix bug where you can't select RSS rules
Fixes bug caused by #21731.

PR #21857.
2024-11-19 02:40:22 +08:00
Mahdi Hosseinzadeh
928de36093
Improve the speed icons in the status bar
The speed icons in the status bar look weird. Whenever I look at them, there seems to be something off with them. The circle seems to have been stretched. Also, the gap between the needle and the circle is not big enough to be clearly visible (at least on my display).

PR #21853.
2024-11-19 02:29:17 +08:00
Evgenii Ryshkov
1e851b3637
WebUI: Fix reloading page after login
PR  #21832.
---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-11-19 02:20:04 +08:00
skomerko
c9c85eeb95
WebUI: Use event delegation to handle common table events
Event delegation is now used to handle basic table events.
2 minor fixes were added to match GUI behavior:
* Clicking on the table body deselects everything
* Table rows are now scrolled into view when using up/down arrows

PR #21829.
2024-11-19 02:12:26 +08:00
Vladimir Golovnev
ea35aa45d6
Remove wrong dependency on Main Window
Don't depend on upper level widget (Main Window) state.

PR #21816.
2024-11-18 19:13:43 +03:00
skomerko
e51fcc6ea0
WebUI: Show 'Rename...' context menu item only when one torrent is selected
'Rename...' item in torrents table menu is displayed only when exactly one torrent is selected.

PR #21843.
2024-11-17 15:31:30 +08:00
Chocobo1
f4eec75488
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 15:57:55 +08:00
Chocobo1
f73f31619d
WebUI: use native function to convert to numbers
Also replace `parseInt()` since `Number()` behavior is more intuitive.

PR #21831.
2024-11-16 15:41:20 +08:00
Chocobo1
ede08f3845
WebUI: fetch cached info early
The cached info doesn't need to wait until the DOM is loaded. They can be fetched far earlier.

PR #21830.
2024-11-16 15:34:35 +08:00
Patrik Elfström
c9a55fce95
WebUI: Fix wrong log levels
Fixes bug where the first time visiting Execution Log view all log levels are deselected but log items with all levels are still displayed.
This requires you to select a log level and then deselect it to hide that log level.

PR #21812.
2024-11-16 15:22:26 +08:00
Thomas Piccirello
1cd3c586c1
WebUI: Always close one window
Closing all windows is overly broad and never the intention.

PR #21804.
2024-11-16 15:12:35 +08:00
Chocobo1
0f12d077c8
Avoid reapplying Mark-of-the-Web when it already exists
Also use scope guards to handle resources.

Related #21788.
PR #21806.
2024-11-14 15:06:33 +08:00
Chocobo1
92daca1fef
Avoid redundant string length scan
PR #21807.
2024-11-11 19:19:10 +08:00
skomerko
889df72ab3
WebUI: Use thin scrollbars
Thin scrollbars are now used if they are supported by user's browser. The main goal was to make them less intrusive in Chrome-likes on some platforms.

PR #21763.
---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-11-11 19:05:42 +08:00
Hanabishi
631e873ff2
WebUI: fix preferences class name
Fixup for #21780. Account for changed preferences class name in the color scheme logic.

PR #21809.
2024-11-11 18:53:06 +08:00
Vladimir Golovnev
69f19d4a0b
Preserve initial torrent progress while checking resume data
PR #21784.
2024-11-10 12:01:12 +03:00
skomerko
3ec645674a
WebUI: Use modern class syntax to create LocalPreferences class
LocalPreferences class is now created using modern class syntax (minimal changes to remove Mootools bits). In addition, I removed redundant suffix from class name.

PR #21780.
2024-11-09 16:40:25 +08:00
skomerko
71f83cf9ba
WebUI: Display torrent progress percentage in General tab
This PR adds torrent progress percentage next to pieces bar in General tab, as in the GUI.

PR #21756.
2024-11-09 16:03:20 +08:00
Hanabishi
06fe3e5fb0
WebUI: fix color scheme for iframes
Applies the color scheme for iframe dialogs.

Fixup for #21613.
PR #21750.
2024-11-09 16:02:28 +08:00
3gf8jv4dv
fe153f8919
NSIS: Update Traditional Chinese translation
PR #21694.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-11-09 16:01:02 +08:00
3gf8jv4dv
568de90923
NSIS: Update Simplified Chinese translation
PR #21693.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-11-09 16:00:31 +08:00
Thomas Piccirello
f89c4c32ed
Display External IP Address in status bar
This change displays the last detected IPv4 and/or IPv6 address(es) in the GUI and WebUI's status bar. This does not yet handle systems with multiple addresses of the same type (e.g. multiple IPv6 addresses).

PR #21383.

---------

Co-authored-by: Odin Vex <44311901+OdinVex@users.noreply.github.com>
2024-11-09 15:58:13 +08:00
Chocobo1
fb9b3c0f34
WebUI: use Fetch API to login
Fetch API is the modern replacement for XMLHttpRequest.
Also show more detailed error messages. 

Closes #21739.
PR #21744.
2024-11-09 14:08:03 +08:00
Vladimir Golovnev
33e3fb2f46
Correctly handle "torrent finished" events
PR #21786.
Closes #21699.
2024-11-08 11:45:57 +03:00
Vladimir Golovnev
4bec9b90c4
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:45:16 +03:00
Vladimir Golovnev
a6c7aef6c1
Optimize checking for outdated tracker endpoints
PR #21768.
2024-11-07 09:40:33 +03:00
Vladimir Golovnev
4527536858
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:39:33 +03:00
Chocobo1
3da9444688
Reduce dependency on Main Window
PR #21753.
2024-11-06 13:45:14 +08:00
Vladimir Golovnev
75d1ac8889
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:43:43 +03:00
Chocobo1
051d7137ea
Use proper macro for unreachable switch cases
Those are the `default` cases which are not expected to hit (nor reachable) normally.

When the code is compiled with release mode and it reaches `Q_UNREACHABLE()`, it becomes
undefined behavior. So it rely on the developers to catch the errors in debug mode.
The upside of this is that the `switch` statement will be more optimized than not using it.
This also means the statements after `Q_UNREACHABLE()` isn't important. It allow anything to
preserve the intention of the code.

This macro is preferred over C++23 `std::unreachable` because it will automatically insert a
`Q_ASSERT(false)` with it.

PR #21752.
2024-11-05 11:55:55 +08:00
Vladimir Golovnev
b462a2bf0c
Reset tracker entries when pause the session
PR #21738.
2024-11-04 16:27:21 +03:00
Patrik Elfström
c02f80cec5
WebUI: Hide context menu when clicking on a table row
This fixes a bug where the torrents table header menu
could not be closed by clicking on a table row.
This also fixes the same bug for other context menus.

PR #21731.
2024-11-04 19:09:51 +08:00
Patrik Elfström
3bb1e34233
WebUI: Add tooltip to regex filter button
Add a tooltip to indicate the button's function.
And change the cursor to a pointer since label is used as a button.

PR #21695.
2024-11-03 15:35:28 +08:00
Thomas Piccirello
dc30b9c2ec
WebUI: Improve table overflow handling
This PR relies on flexbox to ensure all WebUI tables are the correct height without overflowing. Table headers are now always visible and JS-based dynamic resizing is no longer needed.

PR #21652.
2024-11-03 15:11:30 +08:00
Vladimir Golovnev
b083029841
Handle Qt style names in a case insensitive way
PR #21720.
Closes #21716.
2024-11-03 09:54:57 +03:00
Chocobo1
6f642776b6
Simplify tab handling in Search widget
PR #21729.
2024-11-03 14:43:23 +08:00
Jack Moran
1a7ebfc8f0
Create SECURITY.md
* Create SECURITY.md

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>

PR #21589
2024-11-02 22:03:38 +02:00
sledgehammer999
0771970627
Merge pull request #21613 from sledgehammer999/webui_color_switcher
WebUI: Add color scheme switcher
2024-11-02 16:30:01 +02:00
Vladimir Golovnev
0f18e80154
Fix .torrent file could not be deleted when torrent is canceled
PR #21735.
Closes #21723.
2024-11-02 16:41:05 +03:00
Vladimir Golovnev
08b51fc869
Remove trackers from previous category when moved to new one
PR #21717.
Closes #21637.
2024-11-02 16:40:27 +03:00
sledgehammer999
13e3192444
Reorder code to match UI 2024-11-02 13:11:05 +02:00
sledgehammer999
3aefc16c57
Webui: Add color scheme switcher
Closes #21600
2024-11-02 13:11:05 +02:00
sledgehammer999
7b0b3a1522
Merge pull request #21722 from sledgehammer999/changelog_upkeep
Changelog upkeep
2024-11-01 21:48:12 +02:00
Thomas Piccirello
8991d994c2
WebUI: Eliminate unnecessary torrents table updates
Only update the torrents table when torrents are added, edited, or removed.

PR #21656.
2024-11-01 04:34:17 +08:00
Chocobo1
72cbc83569
WebUI: prefer arrow functions whenever applicable
Compared to plain function, arrow function is simpler to understand (without bindings to `this`, `arguments`, `super`) and to read.
Now, plain function will only be used when this object is required.

PR #21691.
2024-11-01 04:17:41 +08:00
Chocobo1
7af6ac18aa
Merge pull request #21658 from Chocobo1/ssl_setup
Simplify SSL parameters setup
2024-11-01 04:03:51 +08:00
sledgehammer999
41236d8e58
Consolidate all the Changelog entries into master 2024-10-30 22:50:46 +02:00
sledgehammer999
03dfd983d0
Add new version in Changelog
Closes #21718
2024-10-30 22:50:26 +02:00
Vladimir Golovnev
84d895231c
Correctly delete the moved search tab
PR #21687.
Closes #21675.
2024-10-28 09:41:09 +03:00
Vladimir Golovnev
91b2687032
WebAPI: Prevent producing empty sync data
PR #21688.
2024-10-28 09:40:13 +03:00
Thomas Piccirello
be3eefd8de
WebUI: Fix displaying RSS panel on load
The required JS may not yet be loaded, resulting in an error when calling `window.qBittorrent.Rss.init()`.

Signed-off-by: Thomas Piccirello <thomas@piccirello.com>

PR #21689.
2024-10-28 14:06:52 +08:00
Thomas Piccirello
e0e61ffd02
WebUI: Support auto resizing table columns
Auto resize can be triggered by:
1. Double clicking the column's resize handle (its rightmost edge)
2. The table header's context menu

Closes #21627.
PR #21655.
2024-10-28 13:59:13 +08:00
Thomas Piccirello
c3c91be578
WebUI: Clear properties panel when torrent no longer selected
PR #21654.
2024-10-28 13:53:24 +08:00
xavier2k6
e0431e3ffb
Update python installer version for Windows
PR #21643.
2024-10-27 15:05:05 +08:00
skomerko
67b6cf5a6f
WebUI: Don't sort rows with static trackers in Trackers table
Static trackers come before anything else so in this PR I made sure they are not moved when sorting Trackers table columns.

PR #21609.
2024-10-27 14:54:33 +08:00
Thomas Piccirello
e8dc6b3f73
WebUI: Show file filter when Content tab selected on load
This fixes a bug where the file filter is only shown when the Content tab is switched to. The filter is not being shown if the Content tab is already selected on page load (due to being previously selected).

PR #21657.
2024-10-26 03:28:01 +08:00
Hanabishi
dfe9daf25d
WebUI: restore arrow keys table navigation
Fixup for #21007 and #21147. Table navigation with arrows (#15186) has been broken by that changes.
See https://github.com/qbittorrent/qBittorrent/pull/21007#discussion_r1807326166 and https://github.com/qbittorrent/qBittorrent/pull/21147#discussion_r1807361385 for details.
This PR restores the functionally.

PR #21640.
2024-10-26 03:14:36 +08:00
Tyler True
ca933c60a1
Update GPLv2 license to reflect latest version
The license text and terms do not change, but the document has been
updated, to reflect the FSF's current contact points, and to change 
the example name, under the "How to apply..." part, to "Moe Ghoul".

This reflects the FSF's own change, in the www.gnu.org CVS repo, at
https://web.cvs.savannah.gnu.org/viewvc/www/www/licenses/old-licenses/gpl-2.0.txt?annotate=1.5

Related #7749.
PR #21626.
2024-10-26 03:04:02 +08:00
DoubleSpicy
c080fc3aa0
Fix filesize sorting in preview dialog
PR #21563.
Closes #21510.
2024-10-23 09:03:25 +03:00
Chocobo1
5dd41f506e
Fix button state for SSL certificate check
A copy paste error was introduced in PR #20338.

PR #21659.
2024-10-23 13:04:16 +08:00
sledgehammer999
a3ac692c25
Allow to use Qt's default QStyle
Relevant prior PR #21553

PR #21605.
2024-10-21 19:59:55 +03:00
Chocobo1
e91412ec8b
Use default secure protocol list from Qt
In Qt 6.5, `QSsl::SecureProtocols` is the same as `QSsl::TlsV1_2OrLater`. And by using
`QSsl::SecureProtocols` we won't need to worry it being outdated since Qt will regularly adjust
it.
https://github.com/qt/qtbase/blob/v6.5.0/src/plugins/tls/openssl/qsslcontext_openssl.cpp#L425-L429
2024-10-22 00:27:01 +08:00
Chocobo1
337730ddef
Simplify SSL parameters setup
Simplify code by utilizing QSslConfiguration object.
Also don't mess with global default value via `QSslConfiguration::setDefaultConfiguration`. It
should not be done at such local class.
2024-10-21 16:22:45 +08:00
Vladimir Golovnev
3ab9fe55e5
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:50:53 +03:00
dyseg
ab8d0d1dae
Free resources allocated by web session once it is destructed
PR #21618.
Closes #20873.
2024-10-21 09:23:08 +03:00
Chocobo1
25dbea1388
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.
PR #21620.
2024-10-20 16:01:57 +08:00
Vladimir Golovnev
a47e1cdb48
Allow to choose color scheme on Windows
PR #21615.
2024-10-19 13:37:51 +03:00
Vladimir Golovnev
4805afc1a2
Correctly apply filename filter when !qB extension is enabled
PR #21628.
Closes #21624.
2024-10-19 13:36:03 +03:00
skomerko
5a0914e333
WebUI: Use modern class syntax in context menu classes
Context menu classes are now created using vanilla JS syntax (minimal changes to reduce MooTools bits).

PR #21598.
2024-10-19 16:32:20 +08:00
skomerko
7031c52d16
WebUI: Improve sort order in Status column
This commit adds custom compare function to Status column (same sort order as in the GUI).

Closes #15499.
PR #21570.
2024-10-19 16:25:30 +08:00
Vladimir Golovnev
8e941a06f1
Correctly handle "torrent finished after move" event
PR #21596.
Closes #21576.
2024-10-14 11:51:30 +03:00
Vladimir Golovnev
966387859a
Always notify user about duplicate torrent
PR #21480.
Closes #21475.
2024-10-14 11:50:20 +03:00
Chocobo1
fb40275507
Don't change combobox index after selection
Also keep the list sorted.

PR #21599.
2024-10-14 14:39:54 +08:00
sledgehammer999
3d9e9715b4
Merge pull request #21364 from sledgehammer999/dont_ignore_ssl_errors
Don't ignore SSL errors
2024-10-12 10:37:48 +03:00
Chocobo1
21b0367629
Avoid heavy weight function object
Also, by switching to template we can avoid the cost of converting to some specific type and
perfectly forward the parameter to the final function.

PR #21572.
2024-10-12 15:15:39 +08:00
Chocobo1
ac646f47a2
Avoid shadowing function parameter
The function already has a parameter named `result`.
Also remove a duplicate variable since it already has a pref pointer at the start of the function.

PR #21571.
2024-10-12 15:04:47 +08:00
Chocobo1
c4eeb4a14a
Add drag support to torrent content widget
Now qbt supports dragging items from torrent content widget to another app.

Closes #5860.
PR #21569.
2024-10-12 14:49:17 +08:00
algebnaly
6418033cc8
Add support for Thunar file manager
PR #21531.

Co-authored-by: yalikes <algebnaly@qq.com>
Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-10-12 14:41:05 +08:00
skomerko
cbcb46bcfb
WebUI: Setup copy events only once
It is enough to set them up just once during initial load. Number of elements with copyToClipboard class is fixed - they are context menu items.

PR #21558.
2024-10-12 14:29:54 +08:00
skomerko
0704049026
WebUI: Apply box-sizing reset
Box-sizing: border-box is now applied globally. A lot of tiny changes were made but they were necessary to correct resulting inconsistencies. Everything should be pretty much as it was with just some minor exceptions.

Looks like this PR also indirectly fixed #21414.

PR #21464.
2024-10-12 14:22:07 +08:00
skomerko
81509dfb65
WebUI: Improve filter lists
This PR adds following improvements: 
* Remove unused tracker entries while processing sync data
* Take into account filter selection & terms when performing 'Start/stop/delete' context actions in filter lists
  Now, only filtered torrents will be affected by them, just like in the GUI.
* Provide better feedback when performing 'Start/stop/delete' context actions in filter lists
  Small improvement over GUI - now these actions will be disabled if it's not possible to use them.
* Add context menu to status filter list
* Fix error when toggling filter title
  Fixup for small bug introduced in https://github.com/qbittorrent/qBittorrent/pull/21269

PR #21438.
2024-10-12 13:40:18 +08:00
Thomas Piccirello
b1fd61af3a
WebUI: Handle folders when updating RSS feed url
Follow up to #21371 ([context](https://github.com/qbittorrent/qBittorrent/pull/21371#discussion_r1780908605)).

PR #21437.
2024-10-12 13:30:24 +08:00
Vladimir Golovnev
2d185dc1c7
Allow to choose Qt style
PR #21553.
2024-10-11 16:04:57 +03:00
Vladimir Golovnev
2d857b6200
Disable "Move to trash" option by default
PR #21528.
2024-10-10 14:13:40 +03:00
Chocobo1
871438f557
Bump search engine components version
The master branch should have version larger than stable branch.
https://github.com/qbittorrent/qBittorrent/pull/21539/files#r1790078486

PR #21542.
2024-10-10 16:30:45 +08:00
skomerko
87644441ad
WebUI: Add context menu to search tabs
PR #21516.
2024-10-07 22:04:29 +08:00
thalieht
d73201c098
Fix the tab order in dialogs
* Torrent options
* Torrent creator
* Preferences

Closes #21387.
PR #21395.
2024-10-07 21:51:56 +08:00
stalkerok
3ea2be41e7
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-07 21:40:02 +08:00
Chocobo1
6bbedbea8a
WebUI: remove unused variable
PR #21500.
2024-10-06 16:54:42 +08:00
xavier2k6
56a0692b68
GHA CI: Update pre-commit-config workflow
* Bumped numerous revisions
* Removed obsolete exclusions (files/folders were removed previously)
* Implement fix/workaround for "false positives" (typos/codespell)

PR #21460.
2024-10-06 16:49:30 +08:00
xavier2k6
dfa4eebbce
GHA CI: Bump pandoc to latest version
PR #21458.
2024-10-06 16:41:38 +08:00
Chocobo1
6ed662c68b
CI: turn on assertion checks
This turn on assertions from qbt codebase so that testers can verify the assertions really hold.

PR #21499.
2024-10-06 16:13:14 +08:00
Ikko Eltociear Ashimine
f81d8a85e9
NSIS: update luxembourgish
PR #21456.
2024-10-06 15:43:43 +08:00
Chocobo1
9e5433bcf8
Add name to threads
This help identifying threads when debugging.
The naming scheme is using 'class/function name + variable name'.
Note that the length limitaion is 16 chars on linux. On Windows, the limit is 32767 chars.

PR #21403.
2024-10-06 15:23:50 +08:00
Vladimir Golovnev
3fb5d7764c
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:27:03 +03:00
stalkerok
e75bcbed6d
Remove non-working DHT bootstrap nodes
router.utorrent.com has been dead for a long time. router.bittorrent.com died about a month or two ago. dht.aelitis.com doesn't work either. It is no longer possible to get DHT nodes from them.
Added router.silotis.us. Only ipv6!

PR #21296.
2024-10-05 16:45:29 +08:00
thalieht
7f38216d22
Regenerate .ui files
PR #21411.
2024-10-05 16:42:03 +08:00
sledgehammer999
e309148147
Reorder code to match UI 2024-10-04 22:48:46 +03:00
sledgehammer999
6981217369
Don't ignore SSL errors 2024-10-04 22:48:46 +03:00
skomerko
4ff0687b94
WebUI: Add confirm dialog for Auto TMM
Just like in GUI, confirmation dialog shows up if it's possible to enable Auto TMM for any selected torrent. Right now it's not possible to properly test all cases in the WebUI because context menu completely hides TMM option when some torrents have it enabled and some not (no tri-state) - but that's something to add in another PR.

PR #21378.
2024-10-04 22:39:08 +08:00
Chocobo1
dc02a0fc56
WebUI: remove unused variables
PR #21432.
2024-10-04 22:23:04 +08:00
Vladimir Golovnev
c48d2c1dde
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:06:51 +03:00
Hanabishi
b5b34c9ff4
Add "Simple pread/pwrite" disk IO type
PR #21300.
2024-10-01 19:58:35 +03:00
Chocobo1
7b45566efc
Migrate away from deprecated functions in Qt 6.9
Closes #21412.
PR #21415.
2024-09-30 18:31:17 +08:00
xavier2k6
c30a07702d
Replace QVector with QList
Migrated last remnants of QVector to Qlist, reference https://github.com/qbittorrent/qBittorrent/pull/21016#issuecomment-2212403741 onward.

PR #21407.
2024-09-30 18:20:58 +08:00
skomerko
d8e24314ec
WebUI: Improve lookup performance when filtering by tracker
Torrent hashes in tracker list are now kept in sets instead of arrays.
PR #21405.
2024-09-30 18:13:45 +08:00
skomerko
449ca96e28
WebUI: Add 'Engine' column to Search table
This PR adds 'Engine' column to Search table.
I also fixed inconsistent naming and renamed 'Search engine' column to 'Engine URL'.

PR #21397.
2024-09-30 18:05:17 +08:00
Chocobo1
cebaedf485
WebUI: CSS/styling improvements
WebUI: CSS/styling improvements
2024-09-30 17:57:42 +08:00
Chocobo1
fd311fd5ff
Reduce sensitive data instances
There is no reason for `WebUI` class to retain this information.

PR #21373.
2024-09-30 17:45:28 +08:00
Thomas Piccirello
50acb670b0
WebUI: Support updating RSS feed URL
PR #21371.

Signed-off-by: Thomas Piccirello <thomas@piccirello.com>
2024-09-30 17:34:37 +08:00
xavier2k6
3888b465d8
Revise bug_report template
PR #21343.
2024-09-30 17:24:57 +08:00
Thomas Piccirello
6bbb7b71cd
Add WebAPI/WebUI for managing cookies
Closes #21125.
PR #21340.
2024-09-30 17:13:25 +08:00
Chocobo1
10eb921d70
Allow drop action only on transfer list
Now drop action is only allowed on transfer list, previously it was on main window.
Having drop action on the whole main window is not preferred because it could allow drop action
on other unrelated widgets, such as execution log or RSS widget which is unexpected behavior.

PR #21332.
2024-09-30 16:59:57 +08:00
skomerko
5e3161a3f9 WebUI: Improve statistics window 2024-09-28 17:49:52 +02:00
skomerko
4cc3fedf37 WebUI: Reduce padding in torrents table 2024-09-28 17:49:52 +02:00
skomerko
2952480f37 WebUI: Use correct text and background colors in RSS details view 2024-09-28 17:49:52 +02:00
skomerko
6bfabad92f WebUI: Prevent text selection within tabs, menu items 2024-09-28 17:49:52 +02:00
skomerko
0e03e4f8a7 WebUI: Add colors to log table rows 2024-09-28 13:33:14 +02:00
skomerko
10149de205 WebUI: Define global CSS variables in root selector 2024-09-28 13:33:14 +02:00
skomerko
960edd95cc WebUI: Remove redundant imports 2024-09-28 13:33:14 +02:00
skomerko
8b2d8f3afd
WebUI: Use Map instead of Mootools Hash in all dynamic tables
PR #21358.
2024-09-28 15:46:16 +08:00
Thomas Piccirello
81def39d8c
WebUI: Support managing web seeds
Closes #8475.
PR #21055.
2024-09-28 15:37:36 +08:00
Chocobo1
a23f45cc70
Split platform specific code to its own file
PR #21368.
2024-09-22 14:28:40 +08:00
Thomas Piccirello
8a6207d3fc
WebUI: Fix removing tracker URL with '|' character
Closes #19074.
PR #21346.
2024-09-22 14:22:52 +08:00
skomerko
c3224459db
WebUI: Add 'Confirm torrent recheck' option
This PR adds setting & confirmation dialog for torrent recheck.

Closes #19557.
PR #21348.
2024-09-22 14:12:44 +08:00
skomerko
183c7c75b1
WebUI: Display DHT information in the Status bar only when DHT is enabled
GUI completely hides DHT information when DHT is disabled - now WebUI does the same thing.

Closes #18417.
PR #21339.
2024-09-22 14:06:25 +08:00
sledgehammer999
1c43286616 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-16 22:26:57 +03:00
Chocobo1
4555a46e5d
Remove unused function
PR #21334.
2024-09-16 18:10:13 +08:00
Chocobo1
23f7275bd5
Merge pull request #21333 from Chocobo1/random
Improve PRNG functions
2024-09-16 17:56:19 +08:00
Thomas Piccirello
d2b2afad23
Support removing tracker from all torrents in WebUI/WebAPI
Closes #20661.
PR #21056.
2024-09-16 17:47:10 +08:00
skomerko
d19f7b12d9
WebUI: Improve hash copy actions in context menu
This PR provides better feedback for hash context menu actions and now it is clearly shown if there is anything to copy.

PR #21321.
2024-09-16 17:41:14 +08:00
skomerko
6df1f68ead
WebUI: Use Map instead of Mootools Hash in Torrents table
PR #21308.
2024-09-16 17:34:49 +08:00
Burnerelu
e06b7f8f4d
Enable customizing the save statistics time interval
This change extends the Advanced section of the Preferences menu with a new field, allowing changing the time statistics save interval. A zero value will prevent recurrent saving.

This aims to provide the feature requested in issue #21285.

PR #21291.
2024-09-16 17:16:59 +08:00
Chocobo1
3058158b69
Use modern function for getting random numbers on Windows
The previous `RtlGenRandom()` just redirects to `ProcessPrng()` according to "The Windows 10
random number generation infrastructure" whitepaper from MS.

`ProcessPrng()` is also the de facto PRNG for Rust lang:
aa13fa5882/src/windows.rs (L3C1-L22C81)
And for golang:
https://go-review.googlesource.com/c/go/+/536235
2024-09-14 23:20:07 +08:00
skomerko
0ea35c54a3
WebUI: Improve torrent deletion
* Added 'Confirm when deleting torrents' option to the WebUI
* Confirm deletion dialog now uses MUI.Modal

PR #21289.
Closes #18345.
2024-09-13 22:28:02 +08:00
Chocobo1
dbef6da544
Use modern function for getting random numbers on Linux
Now we don't need a file handle anymore and there is no initialization involved in this new
implementation.
2024-09-13 22:27:42 +08:00
Chocobo1
a0c32110f1
Add URL link for reverse proxy setup examples
The link is helpful for users whom needs to setup reverse proxy.

PR #21305.
2024-09-13 22:18:31 +08:00
skomerko
435385816a
WebUI: Provide 'Merge trackers to existing torrent' option
PR #21302.
2024-09-10 11:12:30 +03:00
skomerko
1b53fdf9ee
WebUI: Improve subcategories
Now they should fully match GUI behavior, please let me know if I missed something.
Still plenty of room to improve them further (e.g styling/CSS) but for now I wanted to keep the changes to the minimum.

Also included small tweaks to category context menu actions.

PR #21269.
2024-09-08 15:21:11 +08:00
Andarwinux
f00c5c9fa3
Opt into Windows SegmentHeap
SegmentHeap provides a faster malloc implementation that only available with Dynamic UCRT in Windows 10, version 2004 (build 19041) and later.

PR #21263.
2024-09-08 15:09:34 +08:00
Chocobo1
130c0d8487
Revise cookie 'secure flag' enable condition
The localhost is 'potentially trustworthy' and RFC 6265 allows setting secure flag in this case.
Also check `X-Forwarded-Proto` header value to support reverse proxy usage.

Note: for reverse proxy users, now the `X-Forwarded-Proto` header is expected to be sent to qbt
otherwise the `secure` flag might be set erroneously.

https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2.5
https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy

Closes #21250.
PR #21260.
2024-09-07 21:38:27 +08:00
Vladimir Golovnev
d9bc7935eb
Apply "merge trackers" logic regardless of way the torrent is added
PR #21299.
2024-09-06 16:28:22 +03:00
Chocobo1
944499814b
Revert "Use client side translation for public login page"
This reverts #20520.
PR #21290.
2024-09-06 15:38:31 +08:00
Chocobo1
0e63b83aed
WebUI: do not follow anchor URL
Fix up 5afeecbf18.
PR #21283.
2024-09-06 15:31:26 +08:00
skomerko
f681e954c7
WebUI: Show country/region name next to its flag when 'Resolve peer countries' is enabled
PR #21278.
2024-09-06 15:23:11 +08:00
Prince Gupta
a7f7c5fb73
Fix highlighted piece color
PR #20971.
2024-09-02 08:11:35 +03:00
skomerko
9d0fa213be
WebUI: Allow to display only hostname in the Tracker column
It is now possible to display only hostname in the Tracker column.
Closes #11357.
PR #21243.
2024-09-01 16:34:49 +08:00
Chocobo1
fc82abe7f6
Remove 'loopback address detection' helper function
It is not needed since `QHostAddress::isLoopback()` can do the job.
PR #21259.
2024-08-31 15:53:39 +08:00
sledgehammer999
72feee6fdd
Fix version bump
Closes #21265
2024-08-26 09:46:27 +03:00
skomerko
58eab8d453
WebUI: Don't apply hover/selection styles to flag, highlighted category icons
Related: https://github.com/qbittorrent/qBittorrent/pull/21162#discussion_r1721799836
Fix up: #21162.
PR #21236.
2024-08-25 15:29:05 +08:00
skomerko
7ab4758279
WebUI: Provide 'Use Category paths in Manual Mode' option
This PR adds 'Use Category paths in Manual Mode' option to WebUI.

PR #21223.
2024-08-25 14:51:59 +08:00
xavier2k6
e6cd9b90d2
Sync flag icons with upstream
* Release: 7.2.3
* Contains bug fixes & additional flags

PR #21220.
2024-08-25 14:46:15 +08:00
skomerko
5b7c9d5725
WebUI: Filter list improvements
A couple of tweaks to make them a little bit better:
1. Make highlighting functions more consistent (this also fixes minuscule bug when no filter item in tracker list is highlighted due to a type mismatch)
2. Use [event delegation](https://javascript.info/event-delegation) to handle filter toggling & item selection
3. Other minor improvements (everything should work like it was previously)

PR #21191.
2024-08-25 14:23:35 +08:00
skomerko
39dd415d43
WebUI: Improve torrent deletion dialog
This PR improves torrent deletion dialog.

1. Now shows different message depending on the number of selected torrents
2. Visually pretty much inline with the GUI
3. Adjusts to content on load
4. Now uses XHR load method. Panels / windows loaded using this method become part of the current document so there is no need to import styles or scripts (they should load marginally faster now).

PR #21185.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-08-25 14:11:36 +08:00
sledgehammer999
9a9c375b9d
Bump to v5.1.0alpha1 2024-08-24 21:49:49 +03:00
skomerko
fda797cb76
WebUI: Improve properties panel
It is now possible to expand & collapse it by clicking directly on tabs, just like in GUI.
In addition, collapse state is saved and applied on page load.
Fixed one minor bug and now files search input is properly hidden even when panel is collapsed.

PR #21209.
2024-08-24 14:09:10 +08:00
Chocobo1
a91bac8aa0
Add link to 'List of alternative WebUI' wiki page in Options
PR #21224.
2024-08-23 02:27:24 +08:00
Vladimir Golovnev
0904f4a89b
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:08:14 +03:00
Chocobo1
9c370bf391
Merge pull request #21215 from Chocobo1/webui_event_param
WebUI: improve event handlers
2024-08-19 16:11:38 +08:00
xavier2k6
f09d43d073
GHA CI: Bump boost dependency
PR #21214.
2024-08-19 15:59:09 +08:00
xavier2k6
f818d0dbe0
Sync "expected lite" with upstream
* Release 0.8.0

PR #21213.
2024-08-19 15:32:01 +08:00
Chocobo1
98623b2cf7
WebUI: use passive event handlers
These kind of event handlers can be asynchronously dispatched, freeing up the main thread for
lag-free operation.
2024-08-17 14:03:51 +08:00
Chocobo1
29379232aa
WebUI: implement debounce behavior for resize events 2024-08-17 14:02:15 +08:00
Chocobo1
0c580c3174
WebUI: remove redundant events
The base class already handle them.
Also optimize the base implementation a bit.
2024-08-17 14:02:15 +08:00
Chocobo1
1179fc3de3
WebUI: prevent passing wrong parameter
The `event` object will be passed as the first parameter to the event handler. So wrap the
event handler with a closure to prevent `event` leaking to other functions.
2024-08-17 14:02:13 +08:00
skomerko
e069fbc37f
WebUI: Add missing icons
This adds missing icons to WebUI (in tabs, buttons, etc.).

PR #21162.
2024-08-17 13:38:44 +08:00
ducalex
efdc4af448
Search helpers: Add POST support to retrieve_url
This allows passing request_data to retrieve_url in order to create a post request.

PR #21184.

---------

Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
2024-08-17 13:32:57 +08:00
Russell Martin
0535993e41
Bump WebAPI version
Accounts for the addition of webseed modification endpoints.

PR #21205.
2024-08-16 07:19:53 +03:00
skomerko
f5aa0bb126
WebUI: Always create generic filter items
PR #21188.
2024-08-15 20:36:38 +03:00
stalkerok
0da383e7b6
Add a flag about the connection peers are using NAT hole punching
PR #21052.
2024-08-15 20:27:19 +03:00
Vladimir Golovnev
c5b7c82344
Refresh pieces bar colors once color scheme is changed
PR #21183.
Closes #21155.
2024-08-13 09:11:21 +03:00
Chocobo1
b1d2b9d02b
WebUI: fix wrong property for keyboard keys
Fix up 2d9e3b3330.
PR #21182.
2024-08-12 15:14:17 +08:00
Chocobo1
d9667b5221
Merge pull request #21179 from Chocobo1/webui_style
WebUI: use native property to set styles
2024-08-12 15:08:26 +08:00
Chocobo1
155fe96bdd
Revise quote escaping for translated strings in WebUI
qbt only need to escape double quotes for the sake of HTML attributes. As for single quotes it
can leave them as-is since WebUI enforce using double quotes for strings.

PR #21180.
2024-08-12 14:59:30 +08:00
HamletDuFromage
9a8572bd21
WebUI: Handle regex syntax error for torrent filtering
https://github.com/qbittorrent/qBittorrent/pull/20566#discussion_r1704548226

PR #21173.
2024-08-12 14:54:37 +08:00
skomerko
04eb40376e
WebUI: Allow to filter torrent list by save path
This PR adds ability to filter torrent list by save path. Everything should work exactly like in GUI.

Closes #19393.
PR #21175.
2024-08-11 16:08:13 +08:00
Thomas Piccirello
ea06eb9fe6
Add WebAPI for managing torrent webseeds
Closes #18465.
PR #21043.
2024-08-11 15:58:56 +08:00
Chocobo1
3e18b1d30c
WebUI: remove outdated CSS property
WebUI already has the standard counterparts.
2024-08-10 14:26:10 +08:00
Chocobo1
9df3ee0de8
WebUI: use native property to set styles 2024-08-10 14:26:10 +08:00
Chocobo1
0c7045042d
WebUI: replace deprecated property 2024-08-10 13:01:26 +08:00
Chocobo1
5afeecbf18
WebUI: migrate away from inline HTML code
`innerHTML` &  `outerHTML` setter will more or less evaluate the value which could be used to
inject malicious code. So replace them with safer alternatives.

PR #21163.
2024-08-10 12:55:48 +08:00
skomerko
4570c0ef9e
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-10 12:49:00 +08:00
thalieht
d0af02cc17
WebUI: Add missing columns in transfer list
* Incomplete Save Path
* Info Hash v1
* Info Hash v2

PR #21158.
2024-08-09 14:48:22 +08:00
skomerko
62c5f41f39
WebUI: Implement 'Auto hide zero status filters'
PR #21145.
2024-08-09 14:38:53 +08:00
Vladimir Golovnev
bee56f2567
Fix Incomplete Save Path cannot be changed for torrents without metadata
PR #21152.
Closes #21140.
2024-08-08 08:20:26 +03:00
Vladimir Golovnev
cbabe56fcf
Hide zero status filters when torrents removed
PR #21150.
Closes #21146.
2024-08-08 08:19:53 +03:00
Chocobo1
2d9e3b3330
WebUI: use native functions for event handling
PR #21147.
2024-08-07 22:00:54 +08:00
Harald Nordgren
989b1d176d
Enable adaptive step size for upload and download limits
PR #21138.
2024-08-07 21:51:06 +08:00
skomerko
142780b863
WebUI: Highlight torrent category in context menu
This PR makes it possible to see common category of selected torrents in context menu. Everything should behave exactly like in GUI.

Closes #12701.
PR #21136.
2024-08-07 21:40:21 +08:00
Chocobo1
7b2886e477
Revise label text
PR #21118.
2024-08-05 21:01:49 +08:00
Thomas Piccirello
66c1acbce2
Don't reannounce when removing tracker via WebAPI
Discussion: https://github.com/qbittorrent/qBittorrent/pull/21056#discussion_r1674632942

PR #21077.
2024-08-04 17:21:15 +08:00
Chocobo1
49507ad670
Merge pull request #21123 from skomerko/webui-use-alternating-row-colors
WebUI: Add ability to toggle alternating row colors in tables
2024-08-04 17:16:01 +08:00
Chocobo1
d74f49111b
WebUI: fix accessing wrong variable
Fix up 7131d1bd6b.
PR #21129.
2024-07-30 20:25:19 +08:00
Carmelo Scandaliato
642a9c29eb
WebUI: remove deleted torrents even if they are currently filtered out
Remove the torrent row regardless of it being visible.

I've also removed the return value because:
* it doesn't appear to be used by any caller;
* other functions (e.g. updateRowData) do not return any value;
* it's not clear whether true refers to the torrent being removed from the list of all torrents or just the visible ones.

Closes #21070.
PR #21071.
2024-07-29 16:20:21 +08:00
Chocobo1
9d494e84bf
Merge pull request #21117 from Chocobo1/webui_interval
WebUI: timer related clean ups
2024-07-29 15:06:48 +08:00
skomerko
aed103d06e WebUI: Improve visibility of unread RSS articles 2024-07-28 11:35:01 +02:00
skomerko
b67495464d WebUI: Add ability to toggle alternating row colors in tables 2024-07-28 11:34:52 +02:00
Chocobo1
bf7e1516d5
WebUI: clear timer variable properly
In JS the timer handle pool is reused and therefore require careful handling of it.
2024-07-26 05:08:40 +08:00
Chocobo1
7131d1bd6b
WebUI: listen to resize events properly
The workaround is not needed now.
Also added a debouncer to avoid too many transient resizing events.
2024-07-26 03:19:58 +08:00
Chocobo1
062904c2bd
WebUI: avoid excessive checking 2024-07-25 14:20:57 +08:00
Chocobo1
6b52a04ff1
WebUI: avoid queuing up requests
`setInterval()` will always fire a new timeout regardless previous `updateRssFeedList()` has
completed or not. This patch will now wait for previous request to complete before another
timeout.
2024-07-25 14:20:57 +08:00
Chocobo1
69a829dfb0
Clean up search engine
Notable changes:
1. Prevent excessive engine module imports.
2. Replace trivial usage of `join()`.
3. Keep the output text sorted whenever possible.
4. Close handles properly.
5. Print error to stderr, not stdout.
6. Report search job exit code.
7. Print exception message to stderr if exception was thrown when
   running a search job.
8. Utilize XML library to build XML data
   And use 2 spaces as indentation.

PR #21098.
2024-07-22 16:51:57 +08:00
Chocobo1
3c5baac150
Merge pull request #21097 from Chocobo1/webui_a11y
WebUI: Improve accessibility
2024-07-22 16:44:52 +08:00
Chocobo1
8e9680bf69
WebUI: simplify code 2024-07-19 17:47:34 +08:00
Chocobo1
b75c42f850
WebUI: associate label to input fields 2024-07-19 17:47:34 +08:00
Vladimir Golovnev
3b38d0de7f
Represent by TorrentInfo only info-section related metadata
PR #21084.
2024-07-19 06:25:41 +03:00
Chocobo1
8b7fdf0f22
Bump Python version minimum requirement
The new minimum version is Python 3.9.

Debian Buster (oldoldstable) support ends at [2024.06.30](https://www.debian.org/releases/buster/).
Ubuntu Focal (20.04LTS) support ends at [2025.04](https://ubuntu.com/about/release-cycle).
By the time qbt v5.1 is released, Buster and Focal would have become EOL.

https://packages.debian.org/search?keywords=python3
https://packages.ubuntu.com/search?keywords=python3

PR #21064.
2024-07-17 12:13:58 +08:00
Chocobo1
83d730ffda
Merge pull request #21074 from Chocobo1/webui_html5
WebUI improvements
2024-07-17 12:06:08 +08:00
Hanabishi
3acd5409a6
WebUI: Fix Torrent Management Mode selector
PR #21053.
2024-07-15 18:33:51 +08:00
Chocobo1
a61df019b3
WebUI: provide legend text
It provides semantic meanings for the option group.
2024-07-15 18:27:53 +08:00
Chocobo1
7df98e1c9a
WebUI: specify scope of table header
This may help screen readers.
2024-07-15 18:26:59 +08:00
Chocobo1
c3b7dfa918
WebUI: omit closing on HTML void elements
https://developer.mozilla.org/en-US/docs/Glossary/Void_element#self-closing_tags :
>Self-closing tags (<tag />) do not exist in HTML.
2024-07-15 17:46:33 +08:00
Chocobo1
0fd24358ce
WebUI: check buttons to have valid text (or assistive text)
It helps people using assistive technology.
https://stackoverflow.com/a/22040485
2024-07-15 17:25:27 +08:00
Chocobo1
7e8e6269d0
WebUI: drop unused lint rule 2024-07-15 17:12:20 +08:00
Chocobo1
25dd6c72f7
WebUI: use the correct property for accessing text
In these instances we don't need the rendered result. So use the most efficient
property to access it: `.textContent`.
2024-07-15 17:09:16 +08:00
Chocobo1
adde3c3f65
WebUI: check headings to have textual content 2024-07-15 17:03:21 +08:00
Chocobo1
7119de9b8d
WebUI: provide semantic information of table body
From https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody#usage_notes :
>Specifying such table content groups also provides valuable contextual
>information for assistive technologies
2024-07-15 16:59:05 +08:00
David Newhall
3999b9a4f9
add root_path to torrent/info api output (#21066)
WebAPI: Add root_path to torrent/info result

PR #21066.
Closes #21057.
2024-07-15 08:44:26 +03:00
Vladimir Golovnev
7f4cb43a33
Fix incorrect sorting by "private" column
PR #21041.
2024-07-15 08:42:02 +03:00
Chocobo1
9feefc8144
WebUI: avoid redundant re-initialization
PR #21012.
2024-07-12 15:00:36 +08:00
Chocobo1
9c26e5d4d6
WebUI: access attribute/property natively
It is now clearer to see what property is being accessed.
Previously mootools library would re-map attribute/property to another.

PR #21007.
2024-07-12 14:06:59 +08:00
Vladimir Golovnev
815ab180c1
Prevent incorrect size from being used for creating array
PR #21050.
2024-07-12 08:49:45 +03:00
Vladimir Golovnev
eba5cbb803
WebUI: Correctly apply changed "save path" of RSS rules
PR #21030.
Closes #20141.
2024-07-08 10:08:28 +03:00
ManiMatter
87a202c71e
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-07 19:09:57 +03:00
Vladimir Golovnev
a4f63a5c30
Show scroll bar in Torrent Tags dialog
PR #21026.
Closes #21022.
2024-07-07 08:25:31 +03:00
Vladimir Golovnev
ccdf178ee7
Fix handling of tags containing '&' character
PR #21024.
Closes #20773.
2024-07-07 08:24:30 +03:00
Hanabishi
b52fa98a02
WebUI: Implement double-click behavior controls
PR #21000.
2024-07-05 14:34:05 +08:00
Paweł Kotiuk
d87533bf4c
WebUI: Implement path autocompletion
PR #20906.
2024-07-05 14:24:02 +08:00
Vladimir Golovnev
5ef2a1df07
Use QList explicitly
PR #21016.
2024-07-04 08:30:39 +03:00
Vladimir Golovnev
d2fceaa228
Apply bulk changes to correct content widget items
PR #21006.
Closes #21001.
2024-06-29 21:57:59 +03:00
Vladimir Golovnev
4e27e88f6a
Allow to move content files to Trash instead of deleting them
PR #20252.
2024-06-29 08:21:35 +03:00
Thomas Piccirello
c5fa05299b
WebUI: Fix preference name conflict
PR #20990.
2024-06-28 16:46:21 +08:00
Vladimir Golovnev
0cbe4882c3
Use custom storage when reloading torrent
PR #20998.
2024-06-28 07:14:19 +03:00
Chocobo1
610d5ef5ff
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 13:11:32 +08:00
Thomas Piccirello
9d87a813b2
Use enabled search plugins by default in WebUI
PR #20969.
Closes #20558.
2024-06-24 20:45:14 +03:00
Chocobo1
5740238933
Use proper casting
Previously `m_shutdownTimeout * 1000` was calculated in `int` and now it
is `qint64`.

PR #20982.
2024-06-24 15:02:48 +08:00
Chocobo1
ea918da931
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-23 12:43:35 +08:00
vikas_c
9317c25ecb
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-20 08:08:55 +03:00
Chocobo1
7a2bfae5e4
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-20 12:13:27 +08:00
Vladimir Golovnev
9894f654cf
Allow to use regular expression to filter torrent content
PR #20944.
Closes #19934.
2024-06-19 15:25:48 +03:00
Chocobo1
d71086e400
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-17 13:18:32 +08:00
Chocobo1
2000be12ba
Merge pull request #20928 from Chocobo1/webui_curly
WebUI: unify coding style
2024-06-17 13:09:18 +08:00
ManiMatter
914728d9a1
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-16 10:57:12 +03:00
Vladimir Golovnev
c36100fa85
Don't use custom "file icon provider" on Windows
PR #20936.
Closes #20908.
2024-06-14 22:39:42 +03:00
Chocobo1
1c49e0973c
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-12 15:20:37 +08:00
Vladimir Golovnev
65d143d4c4
Apply share limits when torrent downloading is finished
PR #20917.
Closes #20874.
2024-06-12 09:03:07 +03:00
Vladimir Golovnev
d89f289f82
Apply filename filter to subfolder names as well
PR #20902.
Closes #14480.
2024-06-12 09:02:10 +03:00
Chocobo1
648dd9988d
WebUI: unify comment format 2024-06-11 02:17:10 +08:00
BurningMop
dd34c85884
Add optional headers to search request
PR #20923.
2024-06-09 14:03:39 +08:00
Chocobo1
1903ddada1
Add required manifest field
https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#assemblyidentity

PR #20907.
2024-06-09 14:02:53 +08:00
Chocobo1
bf4e0df386
WebUI: unify curly bracket usage 2024-06-07 02:51:35 +08:00
dependabot[bot]
b9a1bbbb8a
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-04 14:15:44 +08:00
Chocobo1
41d8f473b7
Avoid redundant lookup
PR #20890.
2024-05-30 19:35:58 +08:00
Chocobo1
4155d4660f
Merge pull request #20888 from Chocobo1/cookie
Simplify HTTP cookie code
2024-05-30 19:35:40 +08:00
thalieht
455a04b68e
Increase default height of 'Share ratio limit' dialog in WebUI
PR #20866.
2024-05-29 12:30:28 +08:00
Chocobo1
4c57318e89
Avoid creating redundant temporary file list
PR #20863.
2024-05-29 12:29:48 +08:00
Chocobo1
d52995015e
Merge pull request #20828 from Chocobo1/js_quotes
WebUI: enforce coding styles
2024-05-29 01:39:13 +08:00
Chocobo1
b1b6685663
Use Qt built-in methods 2024-05-27 23:52:39 +08:00
Chocobo1
534615373e
Use simpler conversion
The cookie value can only contain ASCII characters.
2024-05-27 23:40:40 +08:00
Chocobo1
1ba69be869
WebUI: add missing break 2024-05-27 23:02:19 +08:00
Chocobo1
c54750469e
WebUI: don't auto infer radix parameter 2024-05-27 23:00:51 +08:00
Chocobo1
3ebd15d408
WebUI: simplify code 2024-05-27 23:00:30 +08:00
Chocobo1
64dfb7e122
WebUI: iterate over own properties only 2024-05-27 23:00:03 +08:00
Chocobo1
b07afa3ea9
WebUI: use assignment operator shorthand 2024-05-27 22:58:47 +08:00
Chocobo1
24a1537cdd
WebUI: prefer arrow function in callbacks 2024-05-27 22:57:28 +08:00
Chocobo1
55bff4f07a
WebUI: enforce usage of const whenever possible 2024-05-27 22:56:51 +08:00
Chocobo1
cb90b6769c
WebUI: enforce string quotes coding style 2024-05-27 22:50:17 +08:00
Thomas Piccirello
6d073771ca
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-05-27 20:51:02 +08:00
513 changed files with 181545 additions and 122066 deletions

2
.github/FUNDING.yml vendored
View file

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

View file

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

View file

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

View file

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

View file

@ -16,6 +16,8 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Setup python (auxiliary scripts)
uses: actions/setup-python@v5
@ -34,7 +36,7 @@ jobs:
- name: Lint code (auxiliary scripts)
run: |
pyflakes $PY_FILES
bandit --skip B314,B405 $PY_FILES
bandit --skip B101,B314,B405 $PY_FILES
- name: Format code (auxiliary scripts)
run: |
@ -50,7 +52,7 @@ jobs:
- name: Setup python (search engine)
uses: actions/setup-python@v5
with:
python-version: '3.7'
python-version: '3.9'
- name: Install tools (search engine)
run: pip install bandit mypy pycodestyle pyflakes pyright
@ -61,7 +63,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,9 +2,7 @@ name: CI - Ubuntu
on: [pull_request, push]
permissions:
actions: write
security-events: write
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -14,22 +12,27 @@ jobs:
ci:
name: Build
runs-on: ubuntu-latest
permissions:
actions: write
security-events: write
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.10", "1.2.19"]
libt_version: ["2.0.11", "1.2.20"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["6.5.2"]
env:
boost_path: "${{ github.workspace }}/../boost"
harden_flags: "-D_FORTIFY_SOURCE=2 -D_GLIBCXX_ASSERTIONS"
harden_flags: "-D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS"
libtorrent_path: "${{ github.workspace }}/../libtorrent"
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Install dependencies
run: |
@ -44,15 +47,15 @@ jobs:
store_cache: ${{ github.ref == 'refs/heads/master' }}
update_packager_index: false
ccache_options: |
max_size=2G
max_size=1G
- name: Install boost
env:
BOOST_MAJOR_VERSION: "1"
BOOST_MINOR_VERSION: "76"
BOOST_MINOR_VERSION: "77"
BOOST_PATCH_VERSION: "0"
run: |
boost_url="https://boostorg.jfrog.io/artifactory/main/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
boost_url="https://archives.boost.io/release/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/source/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
boost_url2="https://sourceforge.net/projects/boost/files/boost/${{ env.BOOST_MAJOR_VERSION }}.${{ env.BOOST_MINOR_VERSION }}.${{ env.BOOST_PATCH_VERSION }}/boost_${{ env.BOOST_MAJOR_VERSION }}_${{ env.BOOST_MINOR_VERSION }}_${{ env.BOOST_PATCH_VERSION }}.tar.gz"
set +e
curl -L -o "${{ runner.temp }}/boost.tar.gz" "$boost_url"
@ -62,6 +65,9 @@ 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
@ -85,8 +91,9 @@ jobs:
-G "Ninja" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_CXX_STANDARD=20 \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}" \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-Ddeprecated-functions=OFF
cmake --build build
sudo cmake --install build
@ -101,14 +108,14 @@ jobs:
- name: Build qBittorrent
run: |
CXXFLAGS="$CXXFLAGS ${{ env.harden_flags }} -Werror" \
CXXFLAGS="$CXXFLAGS ${{ env.harden_flags }} -DQT_FORCE_ASSERTS -Werror" \
LDFLAGS="$LDFLAGS -gz" \
cmake \
-B build \
-G "Ninja" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DBOOST_ROOT="${{ env.boost_path }}" \
-DBOOST_ROOT="${{ env.boost_path }}/lib/cmake" \
-DCMAKE_INSTALL_PREFIX="/usr" \
-DTESTING=ON \
-DVERBOSE_CONFIGURE=ON \

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,95 @@
#!/usr/bin/env python3
# A pre-commit hook for checking items order in grid layouts
# Copyright (C) 2024 Mike Tzou (Chocobo1)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give permission to
# link this program with the OpenSSL project's "OpenSSL" library (or with
# modified versions of it that use the same license as the "OpenSSL" library),
# and distribute the linked executables. You must obey the GNU General Public
# License in all respects for all of the code used other than "OpenSSL". If you
# modify file(s), you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete this
# exception statement from your version.
from collections.abc import Callable, Sequence
from typing import Optional
import argparse
import re
import xml.etree.ElementTree as ElementTree
import sys
def traversePostOrder(root: ElementTree.Element, visitFunc: Callable[[ElementTree.Element], None]) -> None:
stack = [(root, False)]
while len(stack) > 0:
(element, visit) = stack.pop()
if visit:
visitFunc(element)
else:
stack.append((element, True))
stack.extend((child, False) for child in reversed(element))
def modifyElement(element: ElementTree.Element) -> None:
def getSortKey(e: ElementTree.Element) -> tuple[int, int]:
if e.tag == 'item':
return (int(e.attrib['row']), int(e.attrib['column']))
return (-1, -1) # don't care
if element.tag == 'layout' and element.attrib['class'] == 'QGridLayout' and len(element) > 0:
element[:] = sorted(element, key=getSortKey)
# workaround_2a: ElementTree will unescape `&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,9 +26,11 @@
# but you are not obligated to do so. If you do not wish to do so, delete this
# exception statement from your version.
from typing import Optional, Sequence
from collections.abc import Sequence
from typing import Optional
import argparse
import re
import sys
def main(argv: Optional[Sequence[str]] = None) -> int:
@ -67,4 +69,4 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
if __name__ == '__main__':
exit(main())
sys.exit(main())

View file

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

4
.gitignore vendored
View file

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

View file

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

View file

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

View file

@ -1,39 +1,4 @@
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)
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.1.0
Mon Oct 28th 2024 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v5.0.1
- FEATURE: Add "Simple pread/pwrite" disk IO type (Hanabishi)
@ -141,6 +106,42 @@ 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.7.0
- Python >= 3.9.0
* Optional, run-time only
* Used by the bundled search engine

18
SECURITY.md Normal file
View file

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

5
WebAPI_Changelog.md Normal file
View file

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

2
dist/mac/Info.plist vendored
View file

@ -55,7 +55,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>5.0.4</string>
<string>5.2.0</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.0.4" date="2025-02-18"/>
<release version="5.2.0~alpha1" date="2025-02-11"/>
</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.0.4"
!define /ifndef QBT_VERSION "5.2.0"
; 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 start"
LangString inst_startup ${LANG_SWEDISH} "Starta qBittorrent vid Windows-uppstart"
;LangString inst_torrent ${LANG_ENGLISH} "Open .torrent files with qBittorrent"
LangString inst_torrent ${LANG_SWEDISH} "Öppna .torrent-filer med qBittorrent"
;LangString inst_magnet ${LANG_ENGLISH} "Open magnet links with qBittorrent"
LangString inst_magnet ${LANG_SWEDISH} "Öppna magnetlänkar med qBittorrent"
;LangString inst_firewall ${LANG_ENGLISH} "Add Windows Firewall rule"
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggregel"
LangString inst_firewall ${LANG_SWEDISH} "Lägg till Windows-brandväggsregel"
;LangString inst_pathlimit ${LANG_ENGLISH} "Disable Windows path length limit (260 character MAX_PATH limitation, requires Windows 10 1607 or later)"
LangString inst_pathlimit ${LANG_SWEDISH} "Inaktivera gränsen för Windows-sökvägslängd (260 tecken MAX_PATH-begränsning, kräver Windows 10 1607 eller senare)"
;LangString inst_firewallinfo ${LANG_ENGLISH} "Adding Windows Firewall rule"
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggregel"
LangString inst_firewallinfo ${LANG_SWEDISH} "Lägger till Windows-brandväggsregel"
;LangString inst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before installing."
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du installerar."
LangString inst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du installerar."
;LangString inst_uninstall_question ${LANG_ENGLISH} "Current version will be uninstalled. User settings and torrents will remain intact."
LangString inst_uninstall_question ${LANG_SWEDISH} "Nuvarande version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
LangString inst_uninstall_question ${LANG_SWEDISH} "Aktuell version avinstalleras. Användarinställningar och torrenter kommer att förbli intakta."
;LangString inst_unist ${LANG_ENGLISH} "Uninstalling previous version."
LangString inst_unist ${LANG_SWEDISH} "Avinstallerar tidigare version."
;LangString launch_qbt ${LANG_ENGLISH} "Launch qBittorrent."
@ -53,7 +53,7 @@ LangString remove_firewallinfo ${LANG_SWEDISH} "Tar bort Windows-brandväggsrege
;LangString remove_cache ${LANG_ENGLISH} "Remove torrents and cached data"
LangString remove_cache ${LANG_SWEDISH} "Ta bort torrenter och cachade data"
;LangString uninst_warning ${LANG_ENGLISH} "qBittorrent is running. Please close the application before uninstalling."
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Vänligen stäng programmet innan du avinstallerar."
LangString uninst_warning ${LANG_SWEDISH} "qBittorrent körs. Stäng programmet innan du avinstallerar."
;LangString uninst_tor_warn ${LANG_ENGLISH} "Not removing .torrent association. It is associated with:"
LangString uninst_tor_warn ${LANG_SWEDISH} "Tar inte bort .torrent-association. Den är associerad med:"
;LangString uninst_mag_warn ${LANG_ENGLISH} "Not removing magnet association. It is associated with:"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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;
QVector<Qt::HANDLE> m_readMutexes;
QList<Qt::HANDLE> m_readMutexes;
QString m_mutexName;
#endif

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

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
QVector<DownloadPriority> filePriorities; // used if TorrentInfo is set
QList<DownloadPriority> filePriorities; // used if TorrentInfo is set
bool skipChecking = false;
std::optional<BitTorrent::TorrentContentLayout> contentLayout;
std::optional<bool> useAutoTMM;

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2021-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2021-2025 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -31,10 +31,9 @@
#include <QReadWriteLock>
#include "base/pathfwd.h"
#include "base/utils/thread.h"
#include "resumedatastorage.h"
class QThread;
class QSqlQuery;
namespace BitTorrent
{
@ -47,12 +46,12 @@ namespace BitTorrent
explicit DBResumeDataStorage(const Path &dbPath, QObject *parent = nullptr);
~DBResumeDataStorage() override;
QVector<TorrentID> registeredTorrents() const override;
QList<TorrentID> registeredTorrents() const override;
LoadResumeDataResult load(const TorrentID &id) const override;
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const override;
void remove(const TorrentID &id) const override;
void storeQueue(const QVector<TorrentID> &queue) const override;
void storeQueue(const QList<TorrentID> &queue) const override;
private:
void doLoadAll() const override;
@ -60,8 +59,7 @@ namespace BitTorrent
void createDB() const;
void updateDB(int fromVersion) const;
void enableWALMode() const;
Utils::Thread::UniquePtr m_ioThread;
LoadResumeDataResult parseQueryResultRow(const QSqlQuery &query) const;
class Worker;
Worker *m_asyncWorker = nullptr;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -30,7 +30,6 @@
#include "sessionimpl.h"
#include <algorithm>
#include <chrono>
#include <cstdint>
#include <ctime>
#include <queue>
@ -58,6 +57,7 @@
#include <libtorrent/session_status.hpp>
#include <libtorrent/torrent_info.hpp>
#include <QDateTime>
#include <QDeadlineTimer>
#include <QDebug>
#include <QDir>
@ -72,13 +72,14 @@
#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"
@ -88,6 +89,7 @@
#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"
@ -107,19 +109,20 @@
#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 int STATISTICS_SAVE_INTERVAL = std::chrono::milliseconds(15min).count();
const std::chrono::seconds FREEDISKSPACE_CHECK_TIMEOUT = 30s;
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, router.utorrent.com:6881, dht.aelitis.com:6881"_s;
const QString DEFAULT_DHT_BOOTSTRAP_NODES = u"dht.libtorrent.org:25401, dht.transmissionbt.com:6881, router.bittorrent.com:6881"_s;
void torrentQueuePositionUp(const lt::torrent_handle &handle)
{
@ -225,7 +228,7 @@ namespace
{
try
{
return QString::fromLatin1(address.to_string().c_str());
return Utils::String::fromLatin1(address.to_string());
}
catch (const std::exception &)
{
@ -299,14 +302,15 @@ 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;
}
}
}
@ -361,7 +365,7 @@ QString Session::subcategoryName(const QString &category)
{
const int sepIndex = category.lastIndexOf(u'/');
if (sepIndex >= 0)
return category.mid(sepIndex + 1);
return category.sliced(sepIndex + 1);
return category;
}
@ -370,7 +374,7 @@ QString Session::parentCategoryName(const QString &category)
{
const int sepIndex = category.lastIndexOf(u'/');
if (sepIndex >= 0)
return category.left(sepIndex);
return category.first(sepIndex);
return {};
}
@ -381,7 +385,7 @@ QStringList Session::expandCategory(const QString &category)
int index = 0;
while ((index = category.indexOf(u'/', index)) >= 0)
{
result << category.left(index);
result << category.first(index);
++index;
}
result << category;
@ -443,6 +447,7 @@ 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)
@ -455,6 +460,7 @@ 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)
@ -462,9 +468,13 @@ 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), -1, lowerLimited(-1))
, m_globalMaxInactiveSeedingMinutes(BITTORRENT_SESSION_KEY(u"GlobalMaxInactiveSeedingMinutes"_s), -1, lowerLimited(-1))
, 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_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)
@ -483,6 +493,7 @@ 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)
@ -534,9 +545,14 @@ 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);
@ -591,6 +607,18 @@ 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);
@ -601,8 +629,11 @@ 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();
@ -613,6 +644,15 @@ 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()
@ -660,6 +700,7 @@ 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))
@ -954,23 +995,25 @@ 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);
}
}
else
currentOptions = options;
storeCategories();
for (TorrentImpl *const torrent : asConst(m_torrents))
{
for (TorrentImpl *const torrent : asConst(m_torrents))
{
if (torrent->category() == name)
torrent->handleCategoryOptionsChanged();
}
if (torrent->category() == name)
torrent->handleCategoryOptionsChanged();
}
emit categoryOptionsChanged(name);
@ -1214,8 +1257,7 @@ int SessionImpl::globalMaxSeedingMinutes() const
void SessionImpl::setGlobalMaxSeedingMinutes(int minutes)
{
if (minutes < 0)
minutes = -1;
minutes = std::clamp(minutes, Torrent::NO_SEEDING_TIME_LIMIT, Torrent::MAX_SEEDING_TIME);
if (minutes != globalMaxSeedingMinutes())
{
@ -1231,7 +1273,7 @@ int SessionImpl::globalMaxInactiveSeedingMinutes() const
void SessionImpl::setGlobalMaxInactiveSeedingMinutes(int minutes)
{
minutes = std::max(minutes, -1);
minutes = std::clamp(minutes, Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT, Torrent::MAX_INACTIVE_SEEDING_TIME);
if (minutes != globalMaxInactiveSeedingMinutes())
{
@ -1307,7 +1349,7 @@ void SessionImpl::prepareStartup()
context->startupStorage = m_resumeDataStorage;
connect(context->startupStorage, &ResumeDataStorage::loadStarted, context
, [this, context](const QVector<TorrentID> &torrents)
, [this, context](const QList<TorrentID> &torrents)
{
context->totalResumeDataCount = torrents.size();
#ifdef QBT_USES_LIBTORRENT2
@ -1597,20 +1639,24 @@ void SessionImpl::endStartup(ResumeSessionContext *context)
m_resumeDataTimer->start();
}
m_wakeupCheckTimer = new QTimer(this);
connect(m_wakeupCheckTimer, &QTimer::timeout, this, [this]
auto wakeupCheckTimer = new QTimer(this);
connect(wakeupCheckTimer, &QTimer::timeout, this, [this]
{
const auto now = QDateTime::currentDateTime();
if (m_wakeupCheckTimestamp.secsTo(now) > 100)
#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)
{
LogMsg(tr("System wake-up event detected. Re-announcing to all the trackers..."));
reannounceToAllTrackers();
}
m_wakeupCheckTimestamp = now;
m_wakeupCheckTimestamp.start();
});
m_wakeupCheckTimestamp = QDateTime::currentDateTime();
m_wakeupCheckTimer->start(30s);
m_wakeupCheckTimestamp.start();
wakeupCheckTimer->start(30s);
m_isRestored = true;
emit startupProgressUpdated(100);
@ -1981,6 +2027,10 @@ 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
@ -2026,6 +2076,8 @@ 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());
@ -2259,6 +2311,11 @@ void SessionImpl::populateAdditionalTrackers()
m_additionalTrackerEntries = parseTrackerEntries(additionalTrackers());
}
void SessionImpl::populateAdditionalTrackersFromURL()
{
m_additionalTrackerEntriesFromURL = parseTrackerEntries(additionalTrackersFromURL());
}
void SessionImpl::processTorrentShareLimits(TorrentImpl *torrent)
{
if (!torrent->isFinished() || torrent->isForced())
@ -2428,12 +2485,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.begin(), m_moveStorageQueue.end()
const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
, [&nativeHandle](const MoveStorageJob &job)
{
return job.torrentHandle == nativeHandle;
});
if (iter != m_moveStorageQueue.end())
if (iter != m_moveStorageQueue.cend())
{
// We shouldn't actually remove torrent until existing "move storage jobs" are done
torrentQueuePositionBottom(nativeHandle);
@ -2453,12 +2510,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.begin() + 1), m_moveStorageQueue.end()
const auto iter = std::find_if((m_moveStorageQueue.cbegin() + 1), m_moveStorageQueue.cend()
, [torrent](const MoveStorageJob &job)
{
return job.torrentHandle == torrent->nativeHandle();
});
if (iter != m_moveStorageQueue.end())
if (iter != m_moveStorageQueue.cend())
m_moveStorageQueue.erase(iter);
}
@ -2475,8 +2532,8 @@ bool SessionImpl::removeTorrent(const TorrentID &id, const TorrentRemoveOption d
bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
{
const auto downloadedMetadataIter = m_downloadedMetadata.find(id);
if (downloadedMetadataIter == m_downloadedMetadata.end())
const auto downloadedMetadataIter = m_downloadedMetadata.constFind(id);
if (downloadedMetadataIter == m_downloadedMetadata.cend())
return false;
const lt::torrent_handle nativeHandle = downloadedMetadataIter.value();
@ -2500,7 +2557,7 @@ bool SessionImpl::cancelDownloadMetadata(const TorrentID &id)
return true;
}
void SessionImpl::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
void SessionImpl::increaseTorrentsQueuePos(const QList<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType
@ -2527,7 +2584,7 @@ void SessionImpl::increaseTorrentsQueuePos(const QVector<TorrentID> &ids)
m_torrentsQueueChanged = true;
}
void SessionImpl::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
void SessionImpl::decreaseTorrentsQueuePos(const QList<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType> torrentQueue;
@ -2555,7 +2612,7 @@ void SessionImpl::decreaseTorrentsQueuePos(const QVector<TorrentID> &ids)
m_torrentsQueueChanged = true;
}
void SessionImpl::topTorrentsQueuePos(const QVector<TorrentID> &ids)
void SessionImpl::topTorrentsQueuePos(const QList<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType> torrentQueue;
@ -2580,7 +2637,7 @@ void SessionImpl::topTorrentsQueuePos(const QVector<TorrentID> &ids)
m_torrentsQueueChanged = true;
}
void SessionImpl::bottomTorrentsQueuePos(const QVector<TorrentID> &ids)
void SessionImpl::bottomTorrentsQueuePos(const QList<TorrentID> &ids)
{
using ElementType = std::pair<int, const TorrentImpl *>;
std::priority_queue<ElementType
@ -2616,9 +2673,9 @@ void SessionImpl::handleTorrentResumeDataRequested(const TorrentImpl *torrent)
++m_numResumeData;
}
QVector<Torrent *> SessionImpl::torrents() const
QList<Torrent *> SessionImpl::torrents() const
{
QVector<Torrent *> result;
QList<Torrent *> result;
result.reserve(m_torrents.size());
for (TorrentImpl *torrent : asConst(m_torrents))
result << torrent;
@ -2718,7 +2775,10 @@ 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))
{
@ -2732,16 +2792,20 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
if (!isMergeTrackersEnabled())
{
LogMsg(tr("Detected an attempt to add a duplicate torrent. Existing torrent: %1. Result: %2")
.arg(torrent->name(), tr("Merging of trackers is disabled")));
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});
return false;
}
const bool isPrivate = torrent->isPrivate() || (hasMetadata && source.info()->isPrivate());
if (isPrivate)
{
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")));
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});
return false;
}
@ -2749,8 +2813,10 @@ bool SessionImpl::addTorrent_impl(const TorrentDescriptor &source, const AddTorr
torrent->addTrackers(source.trackers());
torrent->addUrlSeeds(source.urlSeeds());
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")));
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});
return false;
}
@ -2878,6 +2944,21 @@ 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;
@ -3012,11 +3093,6 @@ 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)
@ -3151,10 +3227,10 @@ void SessionImpl::saveResumeData()
break;
}
const std::vector<lt::alert *> alerts = getPendingAlerts(waitTime);
fetchPendingAlerts(waitTime);
bool hasWantedAlert = false;
for (const lt::alert *alert : alerts)
for (const lt::alert *alert : m_alerts)
{
if (const int alertType = alert->type();
(alertType == lt::save_resume_data_alert::alert_type) || (alertType == lt::save_resume_data_failed_alert::alert_type)
@ -3174,7 +3250,7 @@ void SessionImpl::saveResumeData()
void SessionImpl::saveTorrentsQueue()
{
QVector<TorrentID> queue;
QList<TorrentID> queue;
for (const TorrentImpl *torrent : asConst(m_torrents))
{
if (const int queuePos = torrent->queuePosition(); queuePos >= 0)
@ -3204,6 +3280,9 @@ 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)
{
@ -3223,6 +3302,14 @@ 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)
@ -3233,6 +3320,9 @@ 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)
{
@ -3559,6 +3649,16 @@ 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;
@ -3865,6 +3965,82 @@ 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;
@ -4755,6 +4931,20 @@ 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;
@ -4881,6 +5071,20 @@ 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;
@ -4960,6 +5164,21 @@ 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();
@ -5041,7 +5260,7 @@ void SessionImpl::handleTorrentSavingModeChanged(TorrentImpl *const torrent)
emit torrentSavingModeChanged(torrent);
}
void SessionImpl::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers)
void SessionImpl::handleTorrentTrackersAdded(TorrentImpl *const torrent, const QList<TrackerEntry> &newTrackers)
{
for (const TrackerEntry &newTracker : newTrackers)
LogMsg(tr("Added tracker to torrent. Torrent: \"%1\". Tracker: \"%2\"").arg(torrent->name(), newTracker.url));
@ -5060,13 +5279,13 @@ void SessionImpl::handleTorrentTrackersChanged(TorrentImpl *const torrent)
emit trackersChanged(torrent);
}
void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds)
void SessionImpl::handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QList<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 QVector<QUrl> &urlSeeds)
void SessionImpl::handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QList<QUrl> &urlSeeds)
{
for (const QUrl &urlSeed : urlSeeds)
LogMsg(tr("Removed URL seed from torrent. Torrent: \"%1\". URL: \"%2\"").arg(torrent->name(), urlSeed.toString()));
@ -5084,7 +5303,7 @@ void SessionImpl::handleTorrentStopped(TorrentImpl *const torrent)
{
torrent->resetTrackerEntryStatuses();
const QVector<TrackerEntryStatus> trackers = torrent->trackers();
const QList<TrackerEntryStatus> trackers = torrent->trackers();
QHash<QString, TrackerEntryStatus> updatedTrackers;
updatedTrackers.reserve(trackers.size());
@ -5115,8 +5334,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.find(torrent->id());
if (iter != m_changedTorrentIDs.end())
const auto iter = m_changedTorrentIDs.constFind(torrent->id());
if (iter != m_changedTorrentIDs.cend())
{
m_resumeDataStorage->remove(iter.value());
m_changedTorrentIDs.erase(iter);
@ -5149,11 +5368,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.first().torrentHandle == torrentHandle);
const bool torrentHasActiveJob = !m_moveStorageQueue.isEmpty() && (m_moveStorageQueue.constFirst().torrentHandle == torrentHandle);
if (m_moveStorageQueue.size() > 1)
{
auto iter = std::find_if((m_moveStorageQueue.begin() + 1), m_moveStorageQueue.end()
auto iter = std::find_if((m_moveStorageQueue.cbegin() + 1), m_moveStorageQueue.cend()
, [&torrentHandle](const MoveStorageJob &job)
{
return job.torrentHandle == torrentHandle;
@ -5172,7 +5391,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.first().path == newPath)
if (m_moveStorageQueue.constFirst().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()));
@ -5217,7 +5436,7 @@ void SessionImpl::handleMoveTorrentStorageJobFinished(const Path &newPath)
{
const MoveStorageJob finishedJob = m_moveStorageQueue.takeFirst();
if (!m_moveStorageQueue.isEmpty())
moveTorrentStorage(m_moveStorageQueue.first());
moveTorrentStorage(m_moveStorageQueue.constFirst());
const auto iter = std::find_if(m_moveStorageQueue.cbegin(), m_moveStorageQueue.cend()
, [&finishedJob](const MoveStorageJob &job)
@ -5471,14 +5690,13 @@ void SessionImpl::handleIPFilterError()
emit IPFilterParsed(true, 0);
}
std::vector<lt::alert *> SessionImpl::getPendingAlerts(const lt::time_duration time) const
void SessionImpl::fetchPendingAlerts(const lt::time_duration time)
{
if (time > lt::time_duration::zero())
m_nativeSession->wait_for_alert(time);
std::vector<lt::alert *> alerts;
m_nativeSession->pop_alerts(&alerts);
return alerts;
m_alerts.clear();
m_nativeSession->pop_alerts(&m_alerts);
}
TorrentContentLayout SessionImpl::torrentContentLayout() const
@ -5494,12 +5712,7 @@ void SessionImpl::setTorrentContentLayout(const TorrentContentLayout value)
// Read alerts sent by libtorrent session
void SessionImpl::readAlerts()
{
// 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();
fetchPendingAlerts();
Q_ASSERT(m_loadedTorrents.isEmpty());
Q_ASSERT(m_receivedAddTorrentAlertsCount == 0);
@ -5507,7 +5720,7 @@ void SessionImpl::readAlerts()
if (!isRestored())
m_loadedTorrents.reserve(MAX_PROCESSING_RESUMEDATA_COUNT);
for (const lt::alert *a : alerts)
for (const lt::alert *a : m_alerts)
handleAlert(a);
if (m_receivedAddTorrentAlertsCount > 0)
@ -5549,14 +5762,16 @@ 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.find(TorrentID::fromInfoHash(infoHash))
; loadingTorrentsIter != m_loadingTorrents.end())
if (const auto loadingTorrentsIter = m_loadingTorrents.constFind(TorrentID::fromInfoHash(infoHash))
; loadingTorrentsIter != m_loadingTorrents.cend())
{
emit addTorrentFailed(infoHash, msg);
const AddTorrentError::Kind errorKind = (alert->error == lt::errors::duplicate_torrent)
? AddTorrentError::DuplicateTorrent : AddTorrentError::Other;
emit addTorrentFailed(infoHash, {errorKind, msg});
m_loadingTorrents.erase(loadingTorrentsIter);
}
else if (const auto downloadedMetadataIter = m_downloadedMetadata.find(TorrentID::fromInfoHash(infoHash))
; downloadedMetadataIter != m_downloadedMetadata.end())
else if (const auto downloadedMetadataIter = m_downloadedMetadata.constFind(TorrentID::fromInfoHash(infoHash))
; downloadedMetadataIter != m_downloadedMetadata.cend())
{
m_downloadedMetadata.erase(downloadedMetadataIter);
if (infoHash.isHybrid())
@ -5577,8 +5792,8 @@ void SessionImpl::handleAddTorrentAlert(const lt::add_torrent_alert *alert)
#endif
const auto torrentID = TorrentID::fromInfoHash(infoHash);
if (const auto loadingTorrentsIter = m_loadingTorrents.find(torrentID)
; loadingTorrentsIter != m_loadingTorrents.end())
if (const auto loadingTorrentsIter = m_loadingTorrents.constFind(torrentID)
; loadingTorrentsIter != m_loadingTorrents.cend())
{
const LoadTorrentParams params = loadingTorrentsIter.value();
m_loadingTorrents.erase(loadingTorrentsIter);
@ -5812,7 +6027,7 @@ void SessionImpl::handleTorrentDeleteFailedAlert(const lt::torrent_delete_failed
#else
const auto torrentID = TorrentID::fromInfoHash(alert->info_hash);
#endif
const auto errorMessage = alert->error ? QString::fromLocal8Bit(alert->error.message().c_str()) : QString();
const auto errorMessage = alert->error ? Utils::String::fromLocal8Bit(alert->error.message()) : QString();
handleRemovedTorrent(torrentID, errorMessage);
}
@ -5841,7 +6056,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.find(torrentID); iter != m_downloadedMetadata.end())
if (const auto iter = m_downloadedMetadata.constFind(torrentID); iter != m_downloadedMetadata.cend())
{
found = true;
m_downloadedMetadata.erase(iter);
@ -5851,7 +6066,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.find(altID); iter != m_downloadedMetadata.end())
if (const auto iter = m_downloadedMetadata.constFind(altID); iter != m_downloadedMetadata.cend())
{
found = true;
m_downloadedMetadata.erase(iter);
@ -5946,7 +6161,7 @@ void SessionImpl::handleUrlSeedAlert(const lt::url_seed_alert *alert)
if (alert->error)
{
LogMsg(tr("URL seed DNS lookup failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
LogMsg(tr("URL seed connection failed. Torrent: \"%1\". URL: \"%2\". Error: \"%3\"")
.arg(torrent->name(), QString::fromUtf8(alert->server_url()), QString::fromStdString(alert->message()))
, Log::WARNING);
}
@ -5970,7 +6185,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)
, QString::fromLocal8Bit(alert->error.message().c_str())), Log::CRITICAL);
, Utils::String::fromLocal8Bit(alert->error.message())), Log::CRITICAL);
}
void SessionImpl::handleExternalIPAlert(const lt::external_ip_alert *alert)
@ -5979,11 +6194,19 @@ void SessionImpl::handleExternalIPAlert(const lt::external_ip_alert *alert)
LogMsg(tr("Detected external IP. IP: \"%1\"")
.arg(externalIP), Log::INFO);
if (m_lastExternalIP != externalIP)
const bool isIPv6 = alert->external_address.is_v6();
const bool isIPv4 = alert->external_address.is_v4();
if (isIPv6 && (externalIP != m_lastExternalIPv6Address))
{
if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIP.isEmpty())
if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIPv6Address.isEmpty())
reannounceToAllTrackers();
m_lastExternalIP = externalIP;
m_lastExternalIPv6Address = externalIP;
}
else if (isIPv4 && (externalIP != m_lastExternalIPv4Address))
{
if (isReannounceWhenAddressChangedEnabled() && !m_lastExternalIPv4Address.isEmpty())
reannounceToAllTrackers();
m_lastExternalIPv4Address = externalIP;
}
}
@ -6069,8 +6292,14 @@ 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_statisticsLastUpdateTimer.hasExpired(STATISTICS_SAVE_INTERVAL))
saveStatistics();
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();
}
}
m_cacheStatus.totalUsedBuffers = stats[m_metricIndices.disk.diskBlocksInUse];
m_cacheStatus.jobQueueLength = stats[m_metricIndices.disk.queuedDiskJobs];
@ -6099,7 +6328,7 @@ void SessionImpl::handleStorageMovedAlert(const lt::storage_moved_alert *alert)
{
Q_ASSERT(!m_moveStorageQueue.isEmpty());
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
const MoveStorageJob &currentJob = m_moveStorageQueue.constFirst();
Q_ASSERT(currentJob.torrentHandle == alert->handle);
const Path newPath {QString::fromUtf8(alert->storage_path())};
@ -6122,7 +6351,7 @@ void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_a
{
Q_ASSERT(!m_moveStorageQueue.isEmpty());
const MoveStorageJob &currentJob = m_moveStorageQueue.first();
const MoveStorageJob &currentJob = m_moveStorageQueue.constFirst();
Q_ASSERT(currentJob.torrentHandle == alert->handle);
#ifdef QBT_USES_LIBTORRENT2
@ -6144,7 +6373,7 @@ void SessionImpl::handleStorageMovedFailedAlert(const lt::storage_moved_failed_a
void SessionImpl::handleStateUpdateAlert(const lt::state_update_alert *alert)
{
QVector<Torrent *> updatedTorrents;
QList<Torrent *> updatedTorrents;
updatedTorrents.reserve(static_cast<decltype(updatedTorrents)::size_type>(alert->status.size()));
for (const lt::torrent_status &status : alert->status)
@ -6182,7 +6411,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, QString::fromLocal8Bit(alert->error.message().c_str()))
.arg(endpoint, Utils::String::fromLocal8Bit(alert->error.message()))
, Log::WARNING);
}
}
@ -6295,7 +6524,7 @@ void SessionImpl::loadStatistics()
void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle)
{
invokeAsync([this, torrentHandle = std::move(torrentHandle)]() mutable
invokeAsync([this, torrentHandle = std::move(torrentHandle)]
{
try
{
@ -6305,9 +6534,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, torrentHandle, nativeTrackers = std::move(nativeTrackers), updatedTrackers = std::move(updatedTrackers)]
invoke([this, infoHash = torrentHandle.info_hash(), nativeTrackers = std::move(nativeTrackers), updatedTrackers = std::move(updatedTrackers)]
{
TorrentImpl *torrent = m_torrents.value(torrentHandle.info_hash());
TorrentImpl *torrent = m_torrents.value(infoHash);
if (!torrent || torrent->isStopped())
return;
@ -6336,8 +6565,8 @@ void SessionImpl::updateTrackerEntryStatuses(lt::torrent_handle torrentHandle)
void SessionImpl::handleRemovedTorrent(const TorrentID &torrentID, const QString &partfileRemoveError)
{
const auto removingTorrentDataIter = m_removingTorrents.find(torrentID);
if (removingTorrentDataIter == m_removingTorrents.end())
const auto removingTorrentDataIter = m_removingTorrents.constFind(torrentID);
if (removingTorrentDataIter == m_removingTorrents.cend())
return;
if (!partfileRemoveError.isEmpty())
@ -6359,9 +6588,3 @@ 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-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -29,6 +29,7 @@
#pragma once
#include <chrono>
#include <utility>
#include <vector>
@ -37,14 +38,14 @@
#include <libtorrent/torrent_handle.hpp>
#include <QtContainerFwd>
#include <QDateTime>
#include <QElapsedTimer>
#include <QHash>
#include <QList>
#include <QMap>
#include <QMutex>
#include <QPointer>
#include <QSet>
#include <QVector>
#include <QThreadPool>
#include "base/path.h"
#include "base/settingvalue.h"
@ -55,17 +56,15 @@
#include "session.h"
#include "sessionstatus.h"
#include "torrentinfo.h"
#include "trackerentrystatus.h"
class QString;
class QThread;
class QThreadPool;
class QTimer;
class QUrl;
class BandwidthScheduler;
class FileSearcher;
class FilterParserThread;
class FreeDiskSpaceChecker;
class NativeSessionExtension;
namespace BitTorrent
@ -235,6 +234,8 @@ namespace BitTorrent
void setPerformanceWarningEnabled(bool enable) override;
int saveResumeDataInterval() const override;
void setSaveResumeDataInterval(int value) override;
std::chrono::minutes saveStatisticsInterval() const override;
void setSaveStatisticsInterval(std::chrono::minutes value) override;
int shutdownTimeout() const override;
void setShutdownTimeout(int value) override;
int port() const override;
@ -361,6 +362,8 @@ namespace BitTorrent
void setIncludeOverheadInLimits(bool include) override;
QString announceIP() const override;
void setAnnounceIP(const QString &ip) override;
int announcePort() const override;
void setAnnouncePort(int port) override;
int maxConcurrentHTTPAnnounces() const override;
void setMaxConcurrentHTTPAnnounces(int value) override;
bool isReannounceWhenAddressChangedEnabled() const override;
@ -388,6 +391,8 @@ namespace BitTorrent
void setUTPRateLimited(bool limited) override;
MixedModeAlgorithm utpMixedMode() const override;
void setUtpMixedMode(MixedModeAlgorithm mode) override;
int hostnameCacheTTL() const override;
void setHostnameCacheTTL(int value) override;
bool isIDNSupportEnabled() const override;
void setIDNSupportEnabled(bool enabled) override;
bool multiConnectionsPerIpEnabled() const override;
@ -424,7 +429,7 @@ namespace BitTorrent
Torrent *getTorrent(const TorrentID &id) const override;
Torrent *findTorrent(const InfoHash &infoHash) const override;
QVector<Torrent *> torrents() const override;
QList<Torrent *> torrents() const override;
qsizetype torrentsCount() const override;
const SessionStatus &status() const override;
const CacheStatus &cacheStatus() const override;
@ -438,10 +443,15 @@ namespace BitTorrent
bool downloadMetadata(const TorrentDescriptor &torrentDescr) override;
bool cancelDownloadMetadata(const TorrentID &id) override;
void increaseTorrentsQueuePos(const QVector<TorrentID> &ids) override;
void decreaseTorrentsQueuePos(const QVector<TorrentID> &ids) override;
void topTorrentsQueuePos(const QVector<TorrentID> &ids) override;
void bottomTorrentsQueuePos(const QVector<TorrentID> &ids) override;
void increaseTorrentsQueuePos(const QList<TorrentID> &ids) override;
void decreaseTorrentsQueuePos(const QList<TorrentID> &ids) override;
void topTorrentsQueuePos(const QList<TorrentID> &ids) override;
void bottomTorrentsQueuePos(const QList<TorrentID> &ids) override;
QString lastExternalIPv4Address() const override;
QString lastExternalIPv6Address() const override;
qint64 freeDiskSpace() const override;
// Torrent interface
void handleTorrentResumeDataRequested(const TorrentImpl *torrent);
@ -457,11 +467,11 @@ namespace BitTorrent
void handleTorrentStarted(TorrentImpl *torrent);
void handleTorrentChecked(TorrentImpl *torrent);
void handleTorrentFinished(TorrentImpl *torrent);
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QVector<TrackerEntry> &newTrackers);
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QList<TrackerEntry> &newTrackers);
void handleTorrentTrackersRemoved(TorrentImpl *torrent, const QStringList &deletedTrackers);
void handleTorrentTrackersChanged(TorrentImpl *torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QVector<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QVector<QUrl> &urlSeeds);
void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QList<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QList<QUrl> &urlSeeds);
void handleTorrentResumeDataReady(TorrentImpl *torrent, const LoadTorrentParams &data);
void handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash);
void handleTorrentStorageMovingStateChanged(TorrentImpl *torrent);
@ -476,15 +486,23 @@ 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);
}
void invokeAsync(std::function<void ()> func);
template <typename Func>
void invokeAsync(Func &&func)
{
m_asyncWorker->start(std::forward<Func>(func));
}
bool isAddTrackersFromURLEnabled() const override;
void setAddTrackersFromURLEnabled(bool enabled) override;
QString additionalTrackersURL() const override;
void setAdditionalTrackersURL(const QString &url) override;
QString additionalTrackersFromURL() const override;
signals:
void addTorrentAlertsReceived(qsizetype count);
@ -591,7 +609,9 @@ namespace BitTorrent
void saveTorrentsQueue();
void removeTorrentsQueue();
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
void populateAdditionalTrackersFromURL();
void fetchPendingAlerts(lt::time_duration time = lt::time_duration::zero());
void moveTorrentStorage(const MoveStorageJob &job) const;
void handleMoveTorrentStorageJobFinished(const Path &newPath);
@ -609,6 +629,9 @@ 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;
@ -654,6 +677,7 @@ namespace BitTorrent
CachedSettingValue<bool> m_ignoreLimitsOnLAN;
CachedSettingValue<bool> m_includeOverheadInLimits;
CachedSettingValue<QString> m_announceIP;
CachedSettingValue<int> m_announcePort;
CachedSettingValue<int> m_maxConcurrentHTTPAnnounces;
CachedSettingValue<bool> m_isReannounceWhenAddressChangedEnabled;
CachedSettingValue<int> m_stopTrackerTimeout;
@ -664,6 +688,7 @@ namespace BitTorrent
CachedSettingValue<BTProtocol> m_btProtocol;
CachedSettingValue<bool> m_isUTPRateLimited;
CachedSettingValue<MixedModeAlgorithm> m_utpMixedMode;
CachedSettingValue<int> m_hostnameCacheTTL;
CachedSettingValue<bool> m_IDNSupportEnabled;
CachedSettingValue<bool> m_multiConnectionsPerIpEnabled;
CachedSettingValue<bool> m_validateHTTPSTrackerCertificate;
@ -671,6 +696,8 @@ namespace BitTorrent
CachedSettingValue<bool> m_blockPeersOnPrivilegedPorts;
CachedSettingValue<bool> m_isAddTrackersEnabled;
CachedSettingValue<QString> m_additionalTrackers;
CachedSettingValue<bool> m_isAddTrackersFromURLEnabled;
CachedSettingValue<QString> m_additionalTrackersURL;
CachedSettingValue<qreal> m_globalMaxRatio;
CachedSettingValue<int> m_globalMaxSeedingMinutes;
CachedSettingValue<int> m_globalMaxInactiveSeedingMinutes;
@ -692,6 +719,7 @@ namespace BitTorrent
CachedSettingValue<bool> m_isBandwidthSchedulerEnabled;
CachedSettingValue<bool> m_isPerformanceWarningEnabled;
CachedSettingValue<int> m_saveResumeDataInterval;
CachedSettingValue<int> m_saveStatisticsInterval;
CachedSettingValue<int> m_shutdownTimeout;
CachedSettingValue<int> m_port;
CachedSettingValue<bool> m_sslEnabled;
@ -743,6 +771,9 @@ 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();
@ -752,8 +783,9 @@ namespace BitTorrent
const bool m_wasPexEnabled = m_isPeXEnabled;
int m_numResumeData = 0;
QVector<TrackerEntry> m_additionalTrackerEntries;
QVector<QRegularExpression> m_excludedFileNamesRegExpList;
QList<TrackerEntry> m_additionalTrackerEntries;
QList<TrackerEntry> m_additionalTrackerEntriesFromURL;
QList<QRegularExpression> m_excludedFileNamesRegExpList;
// Statistics
mutable QElapsedTimer m_statisticsLastUpdateTimer;
@ -788,6 +820,7 @@ 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;
@ -808,7 +841,8 @@ namespace BitTorrent
QList<MoveStorageJob> m_moveStorageQueue;
QString m_lastExternalIP;
QString m_lastExternalIPv4Address;
QString m_lastExternalIPv6Address;
bool m_needUpgradeDownloadPath = false;
@ -818,13 +852,13 @@ namespace BitTorrent
bool m_isPortMappingEnabled = false;
QHash<quint16, std::vector<lt::port_mapping_t>> m_mappedPorts;
QTimer *m_wakeupCheckTimer = nullptr;
QDateTime m_wakeupCheckTimestamp;
QElapsedTimer m_wakeupCheckTimestamp;
QList<TorrentImpl *> m_pendingFinishedTorrents;
QDateTime m_qNow;
lt::clock_type::time_point m_ltNow;
FreeDiskSpaceChecker *m_freeDiskSpaceChecker = nullptr;
QTimer *m_freeDiskSpaceCheckingTimer = nullptr;
qint64 m_freeDiskSpace = -1;
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 QVector<TrackerEntryStatus> trackers() const = 0;
virtual QVector<QUrl> urlSeeds() const = 0;
virtual QList<TrackerEntryStatus> trackers() const = 0;
virtual QList<QUrl> urlSeeds() const = 0;
virtual QString error() const = 0;
virtual qlonglong totalDownload() const = 0;
virtual qlonglong totalUpload() const = 0;
@ -275,10 +275,10 @@ namespace BitTorrent
virtual bool isDHTDisabled() const = 0;
virtual bool isPEXDisabled() const = 0;
virtual bool isLSDDisabled() const = 0;
virtual QVector<PeerInfo> peers() const = 0;
virtual QList<PeerInfo> peers() const = 0;
virtual QBitArray pieces() const = 0;
virtual QBitArray downloadingPieces() const = 0;
virtual QVector<int> pieceAvailability() const = 0;
virtual QList<int> pieceAvailability() const = 0;
virtual qreal distributedCopies() const = 0;
virtual qreal maxRatio() const = 0;
virtual int maxSeedingTime() const = 0;
@ -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(QVector<TrackerEntry> trackers) = 0;
virtual void addTrackers(QList<TrackerEntry> trackers) = 0;
virtual void removeTrackers(const QStringList &trackers) = 0;
virtual void replaceTrackers(QVector<TrackerEntry> trackers) = 0;
virtual void addUrlSeeds(const QVector<QUrl> &urlSeeds) = 0;
virtual void removeUrlSeeds(const QVector<QUrl> &urlSeeds) = 0;
virtual void replaceTrackers(QList<TrackerEntry> trackers) = 0;
virtual void addUrlSeeds(const QList<QUrl> &urlSeeds) = 0;
virtual void removeUrlSeeds(const QList<QUrl> &urlSeeds) = 0;
virtual bool connectPeer(const PeerAddress &peerAddress) = 0;
virtual void clearPeers() = 0;
virtual void setMetadata(const TorrentInfo &torrentInfo) = 0;
@ -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 (QVector<PeerInfo>)> resultHandler) const = 0;
virtual void fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const = 0;
virtual void fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const = 0;
virtual void fetchPeerInfo(std::function<void (QList<PeerInfo>)> resultHandler) const = 0;
virtual void fetchURLSeeds(std::function<void (QList<QUrl>)> resultHandler) const = 0;
virtual void fetchPieceAvailability(std::function<void (QList<int>)> resultHandler) const = 0;
virtual void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const = 0;
TorrentID id() const;

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 QVector<DownloadPriority> filePriorities() const = 0;
virtual QVector<qreal> filesProgress() const = 0;
virtual QList<DownloadPriority> filePriorities() const = 0;
virtual QList<qreal> filesProgress() const = 0;
/**
* @brief fraction of file pieces that are available at least from one peer
*
* This is not the same as torrrent availability, it is just a fraction of pieces
* that can be downloaded right now. It varies between 0 to 1.
*/
virtual QVector<qreal> availableFileFractions() const = 0;
virtual void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const = 0;
virtual QList<qreal> availableFileFractions() const = 0;
virtual void fetchAvailableFileFractions(std::function<void (QList<qreal>)> resultHandler) const = 0;
virtual void prioritizeFiles(const QVector<DownloadPriority> &priorities) = 0;
virtual void prioritizeFiles(const QList<DownloadPriority> &priorities) = 0;
virtual void flushCache() const = 0;
};
}

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

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.begin());
m_torrents.erase(m_torrents.cbegin());
}
m_torrents[announceReq.torrentID].setPeer(announceReq.peer);

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -28,10 +28,11 @@
#pragma once
#include <QDateTime>
#include <QHash>
#include <QString>
#include "announcetimepoint.h"
class QStringView;
namespace BitTorrent
@ -59,8 +60,8 @@ namespace BitTorrent
int numLeeches = -1;
int numDownloaded = -1;
QDateTime nextAnnounceTime {};
QDateTime minAnnounceTime {};
AnnounceTimePoint nextAnnounceTime {};
AnnounceTimePoint minAnnounceTime {};
};
struct TrackerEntryStatus
@ -76,8 +77,8 @@ namespace BitTorrent
int numLeeches = -1;
int numDownloaded = -1;
QDateTime nextAnnounceTime {};
QDateTime minAnnounceTime {};
AnnounceTimePoint nextAnnounceTime {};
AnnounceTimePoint minAnnounceTime {};
QHash<std::pair<QString, int>, TrackerEndpointStatus> endpoints {};

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
*
* This program is free software; you can redistribute it and/or
@ -29,16 +29,24 @@
#include "freediskspacechecker.h"
#include "base/bittorrent/session.h"
#include "base/utils/fs.h"
qint64 FreeDiskSpaceChecker::lastResult() const
FreeDiskSpaceChecker::FreeDiskSpaceChecker(const Path &pathToCheck)
: m_pathToCheck {pathToCheck}
{
return m_lastResult;
}
Path FreeDiskSpaceChecker::pathToCheck() const
{
return m_pathToCheck;
}
void FreeDiskSpaceChecker::setPathToCheck(const Path &newPathToCheck)
{
m_pathToCheck = newPathToCheck;
}
void FreeDiskSpaceChecker::check()
{
m_lastResult = Utils::Fs::freeDiskSpaceOnPath(BitTorrent::Session::instance()->savePath());
emit checked(m_lastResult);
emit checked(Utils::Fs::freeDiskSpaceOnPath(m_pathToCheck));
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2023-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
*
* This program is free software; you can redistribute it and/or
@ -31,15 +31,18 @@
#include <QObject>
#include "base/path.h"
class FreeDiskSpaceChecker final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(FreeDiskSpaceChecker)
public:
using QObject::QObject;
FreeDiskSpaceChecker(const Path &pathToCheck);
qint64 lastResult() const;
Path pathToCheck() const;
void setPathToCheck(const Path &newPathToCheck);
public slots:
void check();
@ -48,5 +51,5 @@ signals:
void checked(qint64 freeSpaceSize);
private:
qint64 m_lastResult = 0;
Path m_pathToCheck;
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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 = {});
QVector<Log::Msg> getMessages(int lastKnownId = -1) const;
QVector<Log::Peer> getPeers(int lastKnownId = -1) const;
QList<Log::Msg> getMessages(int lastKnownId = -1) const;
QList<Log::Peer> getPeers(int lastKnownId = -1) const;
signals:
void newLogMessage(const Log::Msg &message);

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())
{
QString ipStr = ipRegexMatch.captured(1);
const QString ipStr = ipRegexMatch.captured(1);
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr;
QHostAddress newIp(ipStr);
const QHostAddress newIp {ipStr};
if (!newIp.isNull())
{
if (m_lastIP != newIp)
@ -153,7 +153,7 @@ QString DNSUpdater::getUpdateUrl() const
break;
default:
qWarning() << "Unrecognized Dynamic DNS service!";
Q_ASSERT(false);
Q_UNREACHABLE();
break;
}
url.setPath(u"/nic/update"_s);
@ -305,7 +305,7 @@ QUrl DNSUpdater::getRegistrationUrl(const DNS::Service service)
case DNS::Service::NoIP:
return {u"https://www.noip.com/remote-access"_s};
default:
Q_ASSERT(false);
Q_UNREACHABLE();
break;
}
return {};

View file

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

View file

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

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> result = Utils::IO::saveToFile(path, data);
if (result)
const nonstd::expected<void, QString> saveResult = Utils::IO::saveToFile(path, data);
if (saveResult)
{
LogMsg(tr("Successfully updated IP geolocation database."), Log::INFO);
}
else
{
LogMsg(tr("Couldn't save downloaded IP geolocation database file. Reason: %1")
.arg(result.error()), Log::WARNING);
.arg(saveResult.error()), Log::WARNING);
}
}
else

View file

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

View file

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

View file

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

View file

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

View file

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

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 QVector<QVariantHash> &articlesData);
void store(const Path &dataFileName, const QList<QVariantHash> &articlesData);
signals:
void loadingFinished(const QVector<QVariantHash> &articles);
void loadingFinished(const QList<QVariantHash> &articles);
private:
QVector<QVariantHash> loadArticles(const QByteArray &data, const QString &url);
QList<QVariantHash> loadArticles(const QByteArray &data, const QString &url);
};
}

View file

@ -35,14 +35,15 @@
#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"
@ -68,7 +69,7 @@ const QString RULES_FILE_NAME = u"download_rules.json"_s;
namespace
{
QVector<RSS::AutoDownloadRule> rulesFromJSON(const QByteArray &jsonData)
QList<RSS::AutoDownloadRule> rulesFromJSON(const QByteArray &jsonData)
{
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &jsonError);
@ -79,7 +80,7 @@ namespace
throw RSS::ParsingError(RSS::AutoDownloader::tr("Invalid data format."));
const QJsonObject jsonObj {jsonDoc.object()};
QVector<RSS::AutoDownloadRule> rules;
QList<RSS::AutoDownloadRule> rules;
for (auto it = jsonObj.begin(); it != jsonObj.end(); ++it)
{
const QJsonValue jsonVal {it.value()};
@ -123,6 +124,7 @@ 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
@ -374,10 +376,24 @@ void AutoDownloader::handleTorrentAdded(const QString &source)
}
}
void AutoDownloader::handleAddTorrentFailed(const QString &source)
void AutoDownloader::handleAddTorrentFailed(const QString &source, const BitTorrent::AddTorrentError &error)
{
m_waitingJobs.remove(source);
// TODO: Re-schedule job here.
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.
}
}
void AutoDownloader::handleNewArticle(const Article *article)

View file

@ -41,13 +41,17 @@
#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;
@ -112,7 +116,7 @@ namespace RSS
private slots:
void process();
void handleTorrentAdded(const QString &source);
void handleAddTorrentFailed(const QString &url);
void handleAddTorrentFailed(const QString &url, const BitTorrent::AddTorrentError &error);
void handleNewArticle(const Article *article);
void handleFeedURLChanged(Feed *feed, const QString &oldURL);

View file

@ -184,14 +184,10 @@ QString computeEpisodeName(const QString &article)
for (int i = 1; i <= match.lastCapturedIndex(); ++i)
{
const QString cap = match.captured(i);
if (cap.isEmpty())
continue;
bool isInt = false;
const int x = cap.toInt(&isInt);
ret.append(isInt ? QString::number(x) : cap);
ret.append(cap);
}
return ret.join(u'x');
}
@ -293,20 +289,26 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
if (!matcher.hasMatch())
return false;
const QString season {matcher.captured(1)};
const QStringList episodes {matcher.captured(2).split(u';')};
const QStringView season {matcher.capturedView(1)};
const QList<QStringView> episodes {matcher.capturedView(2).split(u';')};
const int seasonOurs {season.toInt()};
for (QString episode : episodes)
for (QStringView 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'))
episode = episode.right(episode.size() - 1);
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
episode.slice(1);
#else
episode = episode.sliced(1);
#endif
}
if (episode.indexOf(u'-') != -1)
if (episode.contains(u'-'))
{ // 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};
@ -323,24 +325,25 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitl
if (matched)
{
const int seasonTheirs {matcher.captured(1).toInt()};
const int episodeTheirs {matcher.captured(2).toInt()};
const int seasonTheirs {matcher.capturedView(1).toInt()};
const int episodeTheirs {matcher.capturedView(2).toInt()};
if (episode.endsWith(u'-'))
{ // Infinite range
const int episodeOurs {QStringView(episode).left(episode.size() - 1).toInt()};
const int episodeOurs {QStringView(episode).chopped(1).toInt()};
if (((seasonTheirs == seasonOurs) && (episodeTheirs >= episodeOurs)) || (seasonTheirs > seasonOurs))
return true;
}
else
{ // Normal range
const QStringList range {episode.split(u'-')};
const QList<QStringView> 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,19 +56,22 @@
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(const QUuid &uid, const QString &url, const QString &path, Session *session)
Feed::Feed(Session *session, const QUuid &uid, const QString &url, const QString &path, const std::chrono::seconds refreshInterval)
: Item(path)
, m_session(session)
, m_uid(uid)
, m_url(url)
, m_session {session}
, m_uid {uid}
, m_url {url}
, m_refreshInterval {refreshInterval}
{
const auto uidHex = QString::fromLatin1(m_uid.toRfc4122().toHex());
m_dataFileName = Path(uidHex + u".json");
@ -301,7 +304,7 @@ void Feed::store()
m_dirty = false;
m_savingTimer.stop();
QVector<QVariantHash> articlesData;
QList<QVariantHash> articlesData;
articlesData.reserve(m_articles.size());
for (Article *article :asConst(m_articles))
@ -327,9 +330,9 @@ bool Feed::addArticle(const QVariantHash &articleData)
// Insertion sort
const int maxArticles = m_session->maxArticlesPerFeed();
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)
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)
return false; // we reach max articles
auto *article = new Article(this, articleData);
@ -395,7 +398,7 @@ int Feed::updateArticles(const QList<QVariantHash> &loadedArticles)
return 0;
QDateTime dummyPubDate {QDateTime::currentDateTime()};
QVector<QVariantHash> newArticles;
QList<QVariantHash> newArticles;
newArticles.reserve(loadedArticles.size());
for (QVariantHash article : loadedArticles)
{
@ -462,6 +465,20 @@ 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;
@ -474,6 +491,8 @@ 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)
{
@ -516,7 +535,7 @@ void Feed::handleArticleRead(Article *article)
storeDeferred();
}
void Feed::handleArticleLoadFinished(QVector<QVariantHash> articles)
void Feed::handleArticleLoadFinished(QList<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,6 +31,8 @@
#pragma once
#include <chrono>
#include <QtContainerFwd>
#include <QBasicTimer>
#include <QHash>
@ -68,7 +70,7 @@ namespace RSS
friend class Session;
Feed(const QUuid &uid, const QString &url, const QString &path, Session *session);
Feed(Session *session, const QUuid &uid, const QString &url, const QString &path, std::chrono::seconds refreshInterval);
~Feed() override;
public:
@ -87,6 +89,9 @@ 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:
@ -94,6 +99,7 @@ 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);
@ -102,7 +108,7 @@ namespace RSS
void handleDownloadFinished(const Net::DownloadResult &result);
void handleParsingFinished(const Private::ParsingResult &result);
void handleArticleRead(Article *article);
void handleArticleLoadFinished(QVector<QVariantHash> articles);
void handleArticleLoadFinished(QList<QVariantHash> articles);
private:
void timerEvent(QTimerEvent *event) override;
@ -123,6 +129,7 @@ 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.left(index);
result << path.first(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.left(pos) : QString();
return (pos >= 0) ? path.first(pos) : QString();
}
QString Item::relativeName(const QString &path)
{
int pos;
return ((pos = path.lastIndexOf(Item::PathSeparator)) >= 0 ? path.right(path.size() - (pos + 1)) : path);
const int pos = path.lastIndexOf(Item::PathSeparator);
return (pos >= 0) ? path.sliced(pos + 1) : path;
}

View file

@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2025 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -33,6 +33,7 @@
#include <QHash>
#include <QRegularExpression>
#include <QStringList>
#include <QTimeZone>
#include <QVariant>
#include <QXmlStreamEntityResolver>
#include <QXmlStreamReader>
@ -518,7 +519,7 @@ namespace
return fallbackDate;
const QTime qTime(hour, minute, second);
QDateTime result(qDate, qTime, Qt::UTC);
QDateTime result(qDate, qTime, QTimeZone::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-2024 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2025 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,6 +56,7 @@ 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;
@ -90,15 +91,14 @@ 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,19 +137,20 @@ Session *Session::instance()
return m_instance;
}
nonstd::expected<void, QString> Session::addFolder(const QString &path)
nonstd::expected<Folder *, QString> Session::addFolder(const QString &path)
{
const nonstd::expected<Folder *, QString> result = prepareItemDest(path);
if (!result)
return result.get_unexpected();
auto *destFolder = result.value();
addItem(new Folder(path), destFolder);
auto *folder = new Folder(path);
addItem(folder, destFolder);
store();
return {};
return folder;
}
nonstd::expected<void, QString> Session::addFeed(const QString &url, const QString &path)
nonstd::expected<Feed *, QString> Session::addFeed(const QString &url, const QString &path, const std::chrono::seconds refreshInterval)
{
if (m_feedsByURL.contains(url))
return nonstd::make_unexpected(tr("RSS feed with given URL already exists: %1.").arg(url));
@ -159,13 +160,13 @@ nonstd::expected<void, QString> Session::addFeed(const QString &url, const QStri
return result.get_unexpected();
auto *destFolder = result.value();
auto *feed = new Feed(generateUID(), url, path, this);
auto *feed = new Feed(this, generateUID(), url, path, refreshInterval);
addItem(feed, destFolder);
store();
if (isProcessingEnabled())
feed->refresh();
refreshFeed(feed, std::chrono::system_clock::now());
return {};
return feed;
}
nonstd::expected<void, QString> Session::setFeedURL(const QString &path, const QString &url)
@ -191,7 +192,7 @@ nonstd::expected<void, QString> Session::setFeedURL(Feed *feed, const QString &u
feed->setURL(url);
store();
if (isProcessingEnabled())
feed->refresh();
refreshFeed(feed, std::chrono::system_clock::now());
return {};
}
@ -213,14 +214,20 @@ 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)
{
@ -313,7 +320,7 @@ bool Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
QString url = val.toString();
if (url.isEmpty())
url = key;
addFeedToFolder(generateUID(), url, key, folder);
addFeedToFolder(generateUID(), url, key, folder, 0s);
updated = true;
}
else if (val.isObject())
@ -353,7 +360,9 @@ bool Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
updated = true;
}
addFeedToFolder(uid, valObj[u"url"].toString(), key, folder);
const auto refreshInterval = std::chrono::seconds(valObj[u"refreshInterval"].toInteger());
addFeedToFolder(uid, valObj[u"url"].toString(), key, folder, refreshInterval);
}
else
{
@ -384,8 +393,14 @@ void Session::loadLegacy()
uint i = 0;
for (QString legacyPath : legacyFeedPaths)
{
if (Item::PathSeparator == legacyPath[0])
if (legacyPath.startsWith(Item::PathSeparator))
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
legacyPath.slice(1);
#else
legacyPath.remove(0, 1);
#endif
}
const QString parentFolderPath = Item::parentPath(legacyPath);
const QString feedUrl = Item::relativeName(legacyPath);
@ -403,7 +418,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)
@ -429,9 +444,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)
Feed *Session::addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder, const std::chrono::seconds refreshInterval)
{
auto *feed = new Feed(uid, url, Item::joinPath(parentFolder->path(), name), this);
auto *feed = new Feed(this, uid, url, Item::joinPath(parentFolder->path(), name), refreshInterval);
addItem(feed, parentFolder);
return feed;
}
@ -453,8 +468,25 @@ 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);
@ -475,14 +507,9 @@ 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);
}
@ -553,6 +580,7 @@ void Session::handleItemAboutToBeDestroyed(Item *item)
{
m_feedsByUID.remove(feed->uid());
m_feedsByURL.remove(feed->url());
m_refreshTimepoints.remove(feed);
}
}
@ -591,6 +619,28 @@ void Session::setMaxArticlesPerFeed(const int n)
void Session::refresh()
{
// NOTE: Should we allow manually refreshing for disabled session?
rootFolder()->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;
}

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,26 +35,20 @@
* 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"
* }
@ -120,8 +114,8 @@ namespace RSS
std::chrono::seconds fetchDelay() const;
void setFetchDelay(std::chrono::seconds delay);
nonstd::expected<void, QString> addFolder(const QString &path);
nonstd::expected<void, QString> addFeed(const QString &url, const QString &path);
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> 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);
@ -135,9 +129,6 @@ namespace RSS
Folder *rootFolder() const;
public slots:
void refresh();
signals:
void processingStateChanged(bool enabled);
void maxArticlesPerFeedChanged(int n);
@ -160,8 +151,10 @@ 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);
Feed *addFeedToFolder(const QUuid &uid, const QString &url, const QString &name, Folder *parentFolder, std::chrono::seconds refreshInterval);
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;
@ -176,5 +169,6 @@ 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,7 +41,10 @@ SearchDownloadHandler::SearchDownloadHandler(const QString &pluginName, const QS
, m_manager {manager}
, m_downloadProcess {new QProcess(this)}
{
m_downloadProcess->setEnvironment(QProcess::systemEnvironment());
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
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,7 +70,11 @@ SearchHandler::SearchHandler(const QString &pattern, const QString &category, co
, m_searchTimeout {new QTimer(this)}
{
// Load environment variables (proxy)
m_searchProcess->setEnvironment(QProcess::systemEnvironment());
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
const QStringList params
{
@ -79,9 +83,6 @@ 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);
@ -93,6 +94,7 @@ 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);
@ -145,7 +147,7 @@ void SearchHandler::readSearchOutput()
lines.prepend(m_searchResultLineTruncated + lines.takeFirst());
m_searchResultLineTruncated = lines.takeLast().trimmed();
QVector<SearchResult> searchResultList;
QList<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 QVector<SearchResult> &results);
void newSearchResults(const QList<SearchResult> &results);
private:
void readSearchOutput();

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