Compare commits

..

163 Commits

Author SHA1 Message Date
sledgehammer999
7d7097b024 Bump to 4.5.5 2023-08-29 01:47:20 +03:00
sledgehammer999
ab6f344b65 Update Changelog 2023-08-29 01:44:05 +03:00
sledgehammer999
a3e08c6254 Sync translations from Transifex and run lupdate 2023-08-29 01:42:56 +03:00
MarcDrieu
4a97e1d5be NSIS: Update French translation
PR #19284.
2023-08-29 01:18:37 +03:00
rusu-afanasie
f5c52db838 NSIS: Add Romanian translation
PR #19377.
2023-08-29 01:18:37 +03:00
sledgehammer999
93b1f3a8fd Fix typos
Partial cherry-pick of 6680fdda18
2023-08-29 01:16:40 +03:00
sledgehammer999
7f2cc24f22 NSIS: Fix missing slash in Qt translations script
PR #19196.
2023-08-29 00:50:26 +03:00
sledgehammer999
b863f360a5 Bump copyright year 2023-08-29 00:46:18 +03:00
sledgehammer999
2618472e44 Pull translations from Transifex 2023-08-29 00:40:35 +03:00
Vladimir Golovnev
7376f72d34 Merge pull request #19455 from glassez/v4.5
Backport changes to v4.5.x branch
2023-08-14 17:54:51 +03:00
Omar Abdul Azeez
25d57ff807 Fix overwriting feeds.json with an incomplete load of it
PR #19444.
Closes #19439.

---------

Co-authored-by: Vladimir Golovnev <glassez@yandex.ru>
2023-08-12 21:36:37 +03:00
Vladimir Golovnev
d0f071b625 Map selected indexes to source before modify the data
Changing the data may affect the layout of the sort/filter model, which in turn may invalidate the indexes previously obtained from selection model before we process them all. Therefore, we must map all the selected indexes to source before start processing them.

PR #19372.
Closes #19359.
2023-07-26 20:25:10 +03:00
sledgehammer999
710478686e Merge pull request #19295 from sledgehammer999/noop-update
Disable update checks on versions before Windows 10
2023-07-18 14:47:27 +03:00
sledgehammer999
d1e0c7beaa Sync translations from Transifex and run lupdate 2023-07-12 01:54:25 +03:00
Vladimir Golovnev
aaf757f29e Merge pull request #19205 from glassez/v4.5
Backport changes in v4.5.x branch
2023-07-10 10:55:14 +03:00
sledgehammer999
93204a63ad Disable update checks on versions before Windows 10
On versions before Windows 10 the user is running the Qt5 build.
The next qBittorrent release will change to Qt6 as the default build.
It is counterintuitive to tell the user about available updates when they are
running an incompatible Windows version.
2023-07-10 10:14:29 +03:00
gdim47
7774020ba8 Improve performance when scrolling large torrents
PR #19255.
2023-07-07 16:07:27 +03:00
Burak Yavuz
62d87f384b NSIS: Update Turkish translation
Corrected and updated some strings

PR #19242.
2023-07-07 16:07:23 +03:00
Vladimir Golovnev
6ee6815d24 Always use the same limits when parse bencoded data
PR #19263.
2023-07-04 13:30:51 +03:00
Vladimir Golovnev
4497ca4a0e Immediately update torrent status on moving files
PR #19220.
2023-06-25 13:00:26 +03:00
Vladimir Golovnev
72028be563 Don't miss to enable Apply button
PR #19221.
Closes #19082.
2023-06-25 12:53:55 +03:00
thalieht
b5fddd5408 Fix transfer list tab hotkey
PR #19200.
2023-06-20 07:49:45 +03:00
sledgehammer999
c8763f08da Bump to 4.5.4 2023-06-18 01:49:46 +03:00
sledgehammer999
b8259969ac Update Changelog 2023-06-18 01:46:57 +03:00
sledgehammer999
d3a57e3e01 Sync translations from Transifex and run lupdate 2023-06-18 01:46:34 +03:00
Vladimir Golovnev
fbd228b360 Merge pull request #19084 from glassez/v4.5
Backport changes to v4.5.x branch
2023-06-13 21:20:13 +03:00
Chocobo1
23176b1a56 GHA CI: upload macOS bundles
Hopefully those bundles will be runnable on users machine.

PR #19023.
2023-06-12 12:43:38 +03:00
Chocobo1
46afeb0f32 GHA CI: add missing dll
Closes #18383.
PR #18792.
2023-06-12 12:39:58 +03:00
Raymond Ha
114e2ee1ab WebUI: Fix category save path
PR #19008.
2023-06-12 12:37:35 +03:00
Priit Uring
b5af0f71b9 Sync flag icons with upstream
PR #19027.
2023-06-12 12:36:48 +03:00
sledgehammer999
bd9c42e004 Update AppVeyor config
The config needs some updating to accommodate the new structure.

PR #19030.
2023-06-12 12:32:43 +03:00
Vladimir Golovnev
272ff11d65 Allow to disable confirmation of Pause/Resume All
PR #19067.
Closes #18155.
2023-06-04 16:45:40 +03:00
sledgehammer999
04bd33e5b3 Bump to 4.5.3 2023-05-28 02:01:39 +03:00
sledgehammer999
6a4c423b88 Update Changelog 2023-05-28 01:58:23 +03:00
sledgehammer999
112ab86a6b Sync translations from Transifex and run lupdate 2023-05-28 01:46:53 +03:00
Vladimir Golovnev
fcd38a497e Merge pull request #18909 from glassez/v4.5
Backport changes to v4.5.x branch
2023-05-06 14:19:56 +03:00
Vladimir Golovnev
6e75866ed7 Don't make assertion about 3rd party logic
PR #18913.
2023-05-03 08:24:30 +03:00
Vladimir Golovnev
1f9dde0c37 Make sure ResumeSessionContext is destroyed before start processing
PR #18912.
2023-05-02 10:01:24 +03:00
Vladimir Golovnev (Glassez)
8b13d8f222 Remove outdated code
PR #18908.
2023-04-30 20:27:30 +03:00
Vladimir Golovnev
8d654f9802 Completely initialize native status on torrent creation
PR #18900.
2023-04-30 20:20:26 +03:00
Vladimir Golovnev
cfc73da312 Improve logging of running external program
PR #18901.
2023-04-30 20:20:11 +03:00
sledgehammer999
9801cd0323 Create new resources for this branch for Transifex 2023-04-20 04:10:30 +03:00
sledgehammer999
c01d7a34c1 Regenerate translation files 2023-04-20 03:27:31 +03:00
Vladimir Golovnev
af41a0eece Merge pull request #18848 from glassez/v4.5
Backport changes to v4.5.x branch
2023-04-18 17:43:56 +03:00
Chocobo1
c509f57b66 WebUI: improve 'exporting torrent' behavior
Don't stop the whole operation when a torrent doesn't exists and try to export the remaining
existing ones.

PR #18858.
2023-04-18 09:10:44 +03:00
Chocobo1
8de091f4dd Work around Chrome download limit
Closes #18775.
2023-04-16 19:19:32 +03:00
DivineHawk
e246745776 WebUI: Use workaround for IOS file picker
PR #18837.
Fixes #18683.
2023-04-16 14:33:50 +03:00
Vladimir Golovnev
9d42657468 Don't miss saving "download path" in SQLite storage
PR #18844.
Closes #18842.
2023-04-13 08:22:09 +03:00
Vladimir Golovnev
2b4fcda463 Disable UPnP for web UI by default
PR #18832.
2023-04-13 08:17:47 +03:00
Vladimir Golovnev
70a2d7bd58 Merge pull request #18652 from glassez/v4.5
Backport changes to v4.5.x branch
2023-04-04 20:09:47 +03:00
thalieht
a9c85ab321 Correctly initialize group box children as disabled
PR #18710.
2023-03-17 22:14:50 +03:00
Vladimir Golovnev
e574bc1c57 Improve finished torrent handling
PR #18704.
Closes #18694.
2023-03-16 09:48:16 +03:00
Vladimir Golovnev
fc90953f91 Prevent incorrect log message about torrent content deletion
PR #18692.
Closes #18689.
2023-03-16 09:42:33 +03:00
Christian Danížek
7016aa372b NSIS: Add Slovak translation
PR #18676.
2023-03-13 12:06:28 +03:00
Vladimir Golovnev
cbe9a27a92 Correctly check for database needs to be updated
* Correctly check for database needs to be updated
* Double check whether database needs to be updated

PR #18638.
2023-03-03 12:35:29 +03:00
sledgehammer999
97853f31f2 Bump to 4.5.2 2023-02-28 00:40:57 +02:00
sledgehammer999
66ffb7328d Update Changelog 2023-02-28 00:40:15 +02:00
sledgehammer999
9d9101186d Sync translations from Transifex and run lupdate 2023-02-28 00:22:28 +02:00
sledgehammer999
621ec4e92f Migrate transifex tool config to new version 2023-02-28 00:22:28 +02:00
sledgehammer999
4b752cba4f Merge pull request #18627 from glassez/v4.5
Backport changes to v4.5.x branch
2023-02-27 23:08:07 +02:00
Vladimir Golovnev
38c0864bf2 Reject requests that contain backslash in path
PR #18626.
Closes #18618.
2023-02-27 16:52:42 +03:00
Vladimir Golovnev
c21c3d2300 WebAPI: Allow to set read-only directory as torrent location
PR #18613.
Closes #18480.
2023-02-27 10:14:28 +03:00
Vladimir Golovnev
3be5273246 Prevent RSS folder from being moved into itself
PR #18619.
Closes #18446.
2023-02-27 10:14:10 +03:00
Vladimir Golovnev
37e348ed92 Merge pull request #18567 from glassez/v4.5
Backport changes to v4.5.x branch
2023-02-19 20:47:11 +03:00
brvphoenix
36364121ba GHA CI: Add missing dependencies
PR #18596.
2023-02-19 17:04:56 +03:00
Vladimir Golovnev
df08bd331c Prevent precise timers from being used when unnecessary
The implementation of QTimer::singleShot() uses Qt::PreciseTimer if interval is less than 2 seconds. This isn't mentioned in the docs.
Qt::PreciseTimer increases the system's timer resolution which negatively affects power consumption.

PR #18555.
Closes #18350.
2023-02-19 15:06:54 +02:00
sledgehammer999
abd1ab5539 Support TLS 1.2+ only in the server
Closes #18122
2023-02-19 14:58:30 +02:00
sledgehammer999
632d33b266 Blacklist bad ciphers for TLS in the server
Prevents the ROBOT attack.
Closes #18483
2023-02-19 14:58:23 +02:00
Chocobo1
35f7e1c896 GHA CI: compress debug symbols
The result binary is smaller.
2023-02-19 10:56:01 +03:00
Chocobo1
792301dfe4 GHA CI: don't overwrite system default compile flags
System might have some default compile flags which are crucial for security hardening so we
should append our flags instead of overwriting them.
2023-02-19 10:52:08 +03:00
Chocobo1
e31cf5ac23 GHA CI: revert "[CI Ubuntu] Strip installed components"
For tester convenience, the binaries should ship with debug symbols.
This reverts commit b8aa9e5609.
2023-02-19 10:51:44 +03:00
Chocobo1
0bfe6ff64b GHA CI: use least permission level
`actions: write` is required by Chocobo1/setup-ccache-action.
`pull-requests: write` is required by actions/stale.
https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
2023-02-19 10:51:10 +03:00
Chocobo1
d40c7e8833 GHA CI: speed up package installation on macOS
Setup time is shortened by cutting down unnecessary operations.

https://docs.brew.sh/Manpage#environment
2023-02-19 10:50:39 +03:00
Vladimir Golovnev
8e81d44b3c Update the cached state once recheck is started
We have to force update the cached state, otherwise someone will be able to get an incorrect one during the interval until the cached state is updated in a regular way.

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

PR #18503.
Fixes #18490.
2023-02-16 11:31:43 +03:00
shitcod3r
e9884b9513 NSIS: Add Uzbek translation
PR #18568.
2023-02-15 12:00:31 +03:00
Chocobo1
a63269e3e1 Migrate away from unsafe function
MooTools More has CVE-2021-20088 and qbt is affected by it by using the
unsafe function call `String.parseQueryString()`, so migrate away from
it.

PR #18554.
2023-02-15 12:00:04 +03:00
Burak Yavuz
d03e715708 NSIS: Update Turkish translation
PR #18552.
2023-02-14 09:18:07 +03:00
sledgehammer999
927732f190 Bump to 4.5.1 2023-02-12 01:52:19 +02:00
sledgehammer999
88c991880f Update Changelog 2023-02-12 01:47:11 +02:00
sledgehammer999
29290fa109 Sync translations from Transifex and run lupdate 2023-02-12 01:13:26 +02:00
sledgehammer999
0a8d604ef3 Merge pull request #18452 from sledgehammer999/stage_v4_5_x
Backports for v4_5_x part 2
2023-02-12 00:54:37 +02:00
Vladimir Golovnev
532c985b50 Revert changes of conflict resolution strategy on automatic move
PR #18516.
Closes #18297.
Closes #18495.
2023-02-05 09:30:46 +03:00
sledgehammer999
a32182f794 Use previous color for pause icon for indicating status
Affects transfer list and status filters
Related to PR #18110
2023-01-28 17:05:47 +02:00
sledgehammer999
1aebcd3258 [WebUI] Use new pause icon color for toolbar/menu
This the webui part of PR #18110
2023-01-28 17:05:39 +02:00
sledgehammer999
9f743aab86 Adjust env variable for PDB discovery 2023-01-27 18:04:22 +02:00
sledgehammer999
ece839739e NSIS: Set shortcut's workind dir to install path 2023-01-27 18:04:22 +02:00
Nowshed H. Imran
2204757eca Fix Pause icon
PR #18110.
2023-01-27 18:01:40 +02:00
qbittorrentfan
bfda520ef4 properties endpoint returns name/torrentID
PR #18218.
2023-01-27 18:01:39 +02:00
sotiris-bos
af91f4ed51 WebAPI: Expose "IS PRIVATE" flag
PR #18227.
Closes #16052.
2023-01-27 18:01:39 +02:00
sledgehammer999
41c3a8af01 Migrate settings much earlier 2023-01-27 18:01:38 +02:00
sledgehammer999
cc7f8372a8 Migrate setting about Simplified Chinese locale
Related to PR #17978
2023-01-27 18:01:38 +02:00
Deividas
d20633f9cc NSIS: Update Lithuanian translation
PR #18434.
2023-01-27 18:01:37 +02:00
Midhun V Nadh
961e05e9a8 Remove suggestions while searching for torrents
Don't want torrent search history to pop up next time you try to search for torrents, right?
There are people who would search for 18+ content and what they searched would load up next time they are about to search.

PR #18285.
2023-01-27 18:01:37 +02:00
Fidel Selva
eb98a04245 WebUI: Improve hotkeys
PR #18326.
Fixes #18325.
Fixes #14033.
2023-01-27 18:01:37 +02:00
xavier2k6
5dc1c10848 GHA CI: Bump Boost version to 1.81.0 on Windows/macOS
PR #18279.
2023-01-27 18:01:36 +02:00
Jonatan
dcbff74dc0 NSIS: Update Swedish translation
PR #18240.
2023-01-27 18:01:36 +02:00
David Xuang
5e29960da2 Prevent incorrect line breaking
PR #18236.
2023-01-27 18:01:35 +02:00
sledgehammer999
aa43fc8ff4 [CI Ubuntu] Build AppImage
Upload an AppImage artifact on CI builds. This AppImage is a
simplified version of the official one. It is meant to help
with debugging PRs that fix issues.
2023-01-27 18:01:35 +02:00
sledgehammer999
2517e688d9 [CI Ubuntu] Strip installed components 2023-01-27 18:01:35 +02:00
Chocobo1
40d94fd8e9 Remove docker information
It has been moved to its own repo: https://github.com/qbittorrent/docker-qbittorrent-nox

PR #18199.
2023-01-27 18:01:34 +02:00
Torsten Schwarz
eb97e640cb WebUI: Make rename file dialog resizable
PR #18154.
2023-01-27 18:01:34 +02:00
sledgehammer999
2123c1c259 Remove dead code
Leftover from the system tray code refactoring.
2023-01-27 18:01:33 +02:00
sledgehammer999
6cf1351a77 Remove trailing newline from translation file
I also fixed it on Transifex.
2023-01-27 18:01:33 +02:00
Vladimir Golovnev
c924904308 Merge pull request #18271 from glassez/v4.5
Backport changes to v4.5.x branch
2023-01-25 09:06:23 +03:00
Vladimir Golovnev
904bcc14d5 Reload system tray icon to replace menu
PR #18250.
Closes #18074.
2023-01-22 16:58:30 +03:00
Vladimir Golovnev
c3abe4c2a6 Fix startup performance on Qt5
Use more appropriate container (QList) for resume data queue buffer.
QVector in Qt5 has poor performance of the first element taking operation,
which is used to process the resume data queue. In Qt6, QVector is just an
alias for QList, so there was no problem there.

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

PR #18367.
Fixes #18338.
2023-01-16 14:47:51 +03:00
Vladimir Golovnev
daaa88fa0d Use QThreadPool to invoke free disk space checking jobs
Prevent the creation of an excessive number of threads.
PR #18347.
Closes #18202.
2023-01-16 14:47:39 +03:00
Vladimir Golovnev
0b7c8497f9 Add all torrents passed via the command line
PR #18296.
Closes #18289.
2023-01-16 14:47:11 +03:00
thalieht
e3562be0d6 WebUI: Add "Resume data storage type" option
PR #18357.
2023-01-14 17:16:18 +03:00
brvphoenix
e0d0efcc20 WebUI: Add missing icons
PR #18380.
2023-01-13 11:00:20 +03:00
Jason Carr
fb22b58ce6 WebUI: change order of accepted types of file input
PR #18286.
2022-12-28 13:22:15 +03:00
brvphoenix
c78ac614f5 Unify the way to generate the language list in WebUI and GUI
PR #17994.
Closes #18090.
2022-12-26 10:02:30 +03:00
Vladimir Golovnev
de15907ea7 Re-allow to use icons from system theme
PR #18195.
2022-12-25 16:28:25 +03:00
Vladimir Golovnev
a4289a517f Apply correct tab order to Category options dialog
Also pre-select (sub)category name for editing when dialog is opened for creating new (sub)category.

PR #18270.
Closes #18265.
2022-12-25 16:16:18 +03:00
Nowshed H. Imran
967c3bb55d Fix icon colors inconsistencies
PR #18226.
Fixes #18163.
Fixes #18222.
2022-12-22 14:16:59 +03:00
Vladimir Golovnev
c57896df8f Use "additional trackers" when metadata retrieving
This can help when the DHT nodes are few.

PR #18251.
Closes #18244.
2022-12-22 08:35:54 +03:00
Vladimir Golovnev
911f0d4039 Correctly count the number of torrents in subcategories
PR #18261.
Closes #18137.
2022-12-22 08:35:13 +03:00
Vladimir Golovnev
e822d4fca7 Correctly detect whether desktop integration is active
PR #18259.
2022-12-22 08:22:36 +03:00
Vladimir Golovnev
0da132b69e Correctly detect drive letter in path
PR #18258.
Closes #18224.
2022-12-22 08:22:06 +03:00
Vladimir Golovnev
691cb4fe2b Don't drop !qB extension when rename incomplete file
PR #18186.
Closes #18181.
2022-12-22 07:15:39 +03:00
Vladimir Golovnev
97a053916b Ensure thread is stopped before deleting QThread
PR #18178.
Backports #18037.
2022-12-10 10:14:45 +03:00
Vladimir Golovnev
24bf8eef6d Use identical conversions of tracker names
PR #18146.
Closes #18070.

The problem is that conversions between std::string and QString is not mutually equivalent (i.e. QString::fromStdString(stdStr).toStdString() == stdStr isn't always true).
2022-12-08 17:02:47 +03:00
Vladimir Golovnev
4314bbdf9c Correctly load folder based UI theme
PR #18173.
2022-12-08 17:00:02 +03:00
Vladimir Golovnev
65611cd3dc WebAPI: return paths using platform-independent separator format
PR #18118.
Closes #18096.
2022-12-08 08:36:06 +03:00
Chocobo1
6a4bb1356a Fix wrong color code
Must have been a copy-paste error...
2022-12-04 12:49:37 +08:00
Chocobo1
06593e3678 Revise color for completed status
Now it uses the purple color which matches the completed status icon color.

Related: #18078.
2022-12-04 12:49:37 +08:00
sledgehammer999
18577d9cb0 Merge pull request #18119 from glassez/destruct-tray
Destroy desktop integration at correct place
2022-11-30 21:29:00 +02:00
Vladimir Golovnev
701b84dc48 Destroy desktop integration at correct place
Otherwise it is destructed in QObject destructor, i.e. after GUI application is already destructed.
This can be related to some problems with system tray icon.

PR #18108.
Closes #18093.
2022-11-30 20:11:46 +03:00
sledgehammer999
9a95237b85 Merge pull request #18101 from thalieht/backport
Backport
2022-11-29 20:16:08 +02:00
sledgehammer999
a6a99fbd36 Merge pull request #18102 from now-im/v4_5_x
Fix Speed limit icon size
2022-11-29 20:15:29 +02:00
Nowshed H. Imran
86671bee46 Fix Speed limit icon size 2022-11-29 22:19:00 +06:00
thalieht
f1432a2e3d WebUI: Fix missing "queued" icon 2022-11-29 18:09:09 +02:00
sledgehammer999
480e3f02ca Bump to 4.5.0 2022-11-26 23:16:23 +02:00
sledgehammer999
6b05c716a8 Update Changelog 2022-11-26 23:13:55 +02:00
sledgehammer999
c697829b1b Sync translations from Transifex and run lupdate 2022-11-26 23:12:44 +02:00
sledgehammer999
9a2ec6912b Merge pull request #18061 from sledgehammer999/fix_blocker
Temporarily fix blocker for v4_5_x
2022-11-26 23:09:34 +02:00
sledgehammer999
7601163d32 Revert "Destroy object within appropriate thread"
Temporary solution for #18059

This reverts commit 4f2ac34440.
2022-11-26 21:31:13 +02:00
sledgehammer999
8e2bda2b7a Update Changelog 2022-11-21 01:14:46 +02:00
sledgehammer999
1761f6c58e Update Changelog 2022-11-21 01:14:46 +02:00
sledgehammer999
419cdde4e1 Merge pull request #18043 from glassez/backport1
Prevent object from being used after destruction
2022-11-20 20:57:50 +02:00
sledgehammer999
6ec46a90d1 Merge pull request #18042 from glassez/backport
Revert "Use another workaround to update files tree view"
2022-11-20 20:57:37 +02:00
Vladimir Golovnev (Glassez)
f4051034d7 Prevent object from being used after destruction 2022-11-20 15:06:03 +03:00
Vladimir Golovnev (glassez)
1a8ba00f2c Revert "Use another workaround to update files tree view"
This reverts commit 0f82c16936.
2022-11-20 13:54:42 +03:00
Vladimir Golovnev
de4c1c9265 Don't miss to store metadata of new torrent
PR #18032.
2022-11-19 07:06:52 +03:00
sledgehammer999
bff9189e52 Merge pull request #18014 from sledgehammer999/maybe_backport
Backports to v4_5_x
2022-11-14 17:16:32 +02:00
Vladimir Golovnev
076b3628b1 Save correct resume data when added new torrent
PR #18003.
2022-11-13 21:30:39 +02:00
Vladimir Golovnev (Glassez)
75ccce705e Correctly handle model resetting 2022-11-13 21:30:38 +02:00
Vladimir Golovnev (Glassez)
964bf31775 Use another workaround to update files tree view 2022-11-13 21:30:38 +02:00
Vladimir Golovnev
507ced2fa2 Avoid blocking call when changing libtorrent session settings
We don't really need to get currently used settings pack in order to apply changes to session settings. It is enough to apply settings pack that contains only updated settings.

PR #17989.
2022-11-13 21:30:37 +02:00
Chocobo1
e62f9ef56a Move increment out of loop 2022-11-13 21:28:28 +02:00
Chocobo1
a5a242377b Clean up code 2022-11-13 21:28:28 +02:00
Chocobo1
0758109d15 Reserve space before appending elements 2022-11-13 21:28:28 +02:00
Chocobo1
3970d91d19 Fix typos 2022-11-13 21:28:27 +02:00
BallsOfSpaghetti
4e98b7f0cf Add confirmation to resume/pause all
This adds a confirmation dialog to Pause All and Resume All. First I wanted to only add it in Tray, but honestly, clicking around in the menu, using hotkeys might trigger it just as easy.

Closes #17683.
PR #17945.
2022-11-13 21:28:27 +02:00
sledgehammer999
27a69d9cca Fine tune translations loading for Chinese locales
Closes #17506
2022-11-13 21:28:26 +02:00
Chocobo1
d884ec1731 Add port forwarding option for embedded tracker
Closes #17781.
PR #17981.
2022-11-13 21:28:26 +02:00
Vladimir Golovnev
62b2959cb4 Don't use extra variable to distinguish restored torrents
PR #17984.
2022-11-13 21:28:26 +02:00
Hanabishi
2bdc91c53f Implement Peer ID Client column for Peers tab
PR #17940.
2022-11-13 21:28:25 +02:00
Chocobo1
d829df99aa Revise interface of port forwarder
This eases the usage of port forwarder as the caller code doesn't need
to store previous used port and now can rely on port forwarder doing
all the hard work.

PR #17967.
2022-11-13 21:28:19 +02:00
Vladimir Golovnev
4f2ac34440 Destroy object within appropriate thread
PR #18012.
2022-11-13 08:30:42 +03:00
Vladimir Golovnev
94e9e9fdb2 Delete database file only after it is released
PR #18011.
2022-11-13 08:29:50 +03:00
823 changed files with 172653 additions and 225334 deletions

View File

@@ -50,7 +50,7 @@ before_build:
build_script:
- cd "%REPO_DIR%"
# lupdate chokes when it parses headers from system includes, especially Boost
# lupdate chokes when it parses headers from system inludes, especially Boost
# it also chokes with the sources from src/app/qtlocalpeer (formerly qtsingleapplication)
# Workaround: temporarily rename them to run lupdate with the .pro file
- RENAME conf.pri conf.pri.temp

View File

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

2
.gitattributes vendored
View File

@@ -5,5 +5,3 @@ core.eol=lf
*.png binary
*.qm binary
*.zip binary
test/testdata/crlf.txt text eol=crlf

View File

@@ -17,12 +17,12 @@ jobs:
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.9", "1.2.19"]
libt_version: ["2.0.8", "1.2.18"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["5.15.2", "6.5.0"]
qt_version: ["5.15.2", "6.4.0"]
exclude:
- libt_version: "1.2.19"
qt_version: "6.5.0"
- libt_version: "1.2.18"
qt_version: "6.4.0"
env:
boost_path: "${{ github.workspace }}/../boost"
@@ -33,18 +33,14 @@ jobs:
uses: actions/checkout@v3
- name: Install dependencies
uses: Wandalen/wretry.action@v1
env:
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1
with:
attempt_delay: 20000
attempt_limit: 6
command: |
brew update > /dev/null
brew install \
cmake ninja \
openssl@1.1 zlib
run: |
export \
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 \
HOMEBREW_NO_INSTALL_CLEANUP=1
brew update > /dev/null
brew install \
cmake ninja \
openssl@1.1 zlib
- name: Setup ccache
uses: Chocobo1/setup-ccache-action@v1
@@ -56,7 +52,7 @@ jobs:
curl \
-L \
-o "${{ runner.temp }}/boost.tar.bz2" \
"https://boostorg.jfrog.io/artifactory/main/release/1.82.0/source/boost_1_82_0.tar.bz2"
"https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.tar.bz2"
tar -xf "${{ runner.temp }}/boost.tar.bz2" -C "${{ github.workspace }}/.."
mv "${{ github.workspace }}/.."/boost_* "${{ env.boost_path }}"
@@ -89,7 +85,7 @@ jobs:
sudo cmake --install build
- name: Build qBittorrent (Qt5)
if: startsWith(matrix.qt_version, 5)
if: ${{ startsWith(matrix.qt_version, 5) }}
run: |
CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \
LDFLAGS="$LDFLAGS -gz" \
@@ -108,7 +104,7 @@ jobs:
cmake --build build --target check
- name: Build qBittorrent (Qt6)
if: startsWith(matrix.qt_version, 6)
if: ${{ startsWith(matrix.qt_version, 6) }}
run: |
CXXFLAGS="$CXXFLAGS -Wno-gnu-zero-variadic-macro-arguments -Werror -Wno-error=deprecated-declarations" \
LDFLAGS="$LDFLAGS -gz" \

View File

@@ -4,7 +4,6 @@ on: [pull_request, push]
permissions:
actions: write
security-events: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -18,11 +17,11 @@ jobs:
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.9", "1.2.19"]
libt_version: ["2.0.8", "1.2.18"]
qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["5.15.2", "6.2.0"]
exclude:
- libt_version: "1.2.19"
- libt_version: "1.2.18"
qt_version: "6.2.0"
steps:
@@ -60,23 +59,14 @@ jobs:
cmake \
-B build \
-G "Ninja" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-Ddeprecated-functions=OFF
cmake --build build
sudo cmake --install build
# to avoid scanning 3rdparty codebases, initialize it just before building qbt
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
if: startsWith(matrix.libt_version, 2) && (matrix.qbt_gui == 'GUI=ON') && startsWith(matrix.qt_version, 6)
with:
config-file: ./.github/workflows/helper/codeql/cpp.yaml
languages: cpp
- name: Build qBittorrent (Qt5)
if: startsWith(matrix.qt_version, 5)
if: ${{ startsWith(matrix.qt_version, 5) }}
run: |
CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \
LDFLAGS="$LDFLAGS -gz" \
@@ -95,7 +85,7 @@ jobs:
DESTDIR="qbittorrent" cmake --install build
- name: Build qBittorrent (Qt6)
if: startsWith(matrix.qt_version, 6)
if: ${{ startsWith(matrix.qt_version, 6) }}
run: |
CXXFLAGS="$CXXFLAGS -Werror" \
LDFLAGS="$LDFLAGS -gz" \
@@ -114,12 +104,6 @@ jobs:
cmake --build build --target check
DESTDIR="qbittorrent" cmake --install build
- name: Run CodeQL analysis
uses: github/codeql-action/analyze@v2
if: startsWith(matrix.libt_version, 2) && (matrix.qbt_gui == 'GUI=ON') && startsWith(matrix.qt_version, 6)
with:
category: ${{ github.base_ref || github.ref_name }}
- name: Prepare build artifacts
run: |
mkdir upload

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 }}
@@ -37,12 +36,3 @@ jobs:
run: |
npm run format
git diff --exit-code
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
config-file: ./.github/workflows/helper/codeql/js.yaml
languages: javascript
- name: Run CodeQL analysis
uses: github/codeql-action/analyze@v2

View File

@@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
libt_version: ["2.0.9", "1.2.19"]
libt_version: ["2.0.8", "1.2.18"]
env:
boost_path: "${{ github.workspace }}/../boost"
@@ -70,7 +70,7 @@ jobs:
- name: Install boost
run: |
aria2c `
"https://boostorg.jfrog.io/artifactory/main/release/1.82.0/source/boost_1_82_0.7z" `
"https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_1_81_0.7z" `
-d "${{ runner.temp }}" `
-o "boost.7z"
7z x "${{ runner.temp }}/boost.7z" -o"${{ github.workspace }}/.."
@@ -79,7 +79,7 @@ jobs:
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
version: "6.5.0"
version: "6.4.0"
archives: qtbase qtsvg qttools
- name: Install libtorrent

View File

@@ -26,13 +26,13 @@ jobs:
- name: Install Qt
uses: jurplel/install-qt-action@v3
with:
version: "6.5.0"
version: "6.4.0"
archives: icu qtbase qtsvg qttools
- name: Install libtorrent
run: |
git clone \
--branch "v2.0.9" \
--branch "v2.0.8" \
--depth 1 \
--recurse-submodules \
https://github.com/arvidn/libtorrent.git
@@ -69,7 +69,7 @@ jobs:
- name: Submit the result to Coverity Scan
run: |
tar -caf qbittorrent.xz cov-int
tar caf qbittorrent.xz cov-int
curl \
--form token="${{ secrets.COVERITY_SCAN_TOKEN }}" \
--form email=sledgehammer999@qbittorrent.org \

4
.github/workflows/helper/appimage/export_vars.sh vendored Executable file → Normal file
View File

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

View File

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

View File

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

View File

@@ -1,18 +0,0 @@
# https://github.com/crate-ci/typos/blob/master/docs/reference.md
# https://github.com/crate-ci/typos/blob/master/docs/design.md#identifiers-and-words
# try adding to `identifiers` list first, if doesn't work then `words` list
[default.extend-identifiers]
additionals = "additionals"
caf = "caf"
curren = "curren"
FO = "FO"
ket = "ket"
Q_INVOKABLE = "Q_INVOKABLE"
switchs = "switchs"
ths = "ths"
[default.extend-words]
BA = "BA"
helo = "helo"

View File

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

View File

@@ -3,7 +3,7 @@ repos:
hooks:
- id: check-translation-tag
name: Check newline characters in <translation> tag
entry: .github/workflows/helper/pre-commit/check_translation_tag.py
entry: .github/workflows/check_translation_tag.py
language: script
exclude: |
(?x)^(
@@ -13,7 +13,7 @@ repos:
- ts
- repo: https://github.com/pre-commit/pre-commit-hooks.git
rev: v4.4.0
rev: v4.3.0
hooks:
- id: check-json
name: Check JSON files
@@ -33,19 +33,17 @@ repos:
args: ["--fix=lf"]
exclude: |
(?x)^(
src/webui/www/private/css/lib/.* |
src/webui/www/private/scripts/lib/.* |
test/testdata/crlf.txt
compile_commands.json |
src/webui/www/private/scripts/lib/.*
)$
- id: end-of-file-fixer
name: Check trailing newlines
exclude: |
(?x)^(
compile_commands.json |
configure |
src/webui/www/private/css/lib/.* |
src/webui/www/private/scripts/lib/.* |
test/testdata/crlf.txt
src/webui/www/private/scripts/lib/.*
)$
exclude_types:
- svg
@@ -55,54 +53,7 @@ repos:
name: Check trailing whitespaces
exclude: |
(?x)^(
src/webui/www/private/css/lib/.* |
src/webui/www/private/scripts/lib/.*
)$
exclude_types:
- ts
- repo: https://github.com/codespell-project/codespell
rev: v2.2.5
hooks:
- id: codespell
name: Check spelling (codespell)
args: ["--ignore-words-list", "additionals,curren,fo,ket,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/.*
)$
exclude_types:
- ts
- repo: https://github.com/crate-ci/typos
rev: v1.15.5
hooks:
- id: typos
name: Check spelling (typos)
args: ["--config", ".github/workflows/helper/pre-commit/.typos.toml"]
exclude: |
(?x)^(
.*\.asc |
.*\.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/.*
)$
exclude_types:
- svg
- ts

View File

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

View File

@@ -29,17 +29,13 @@ Code from other projects:
copyright: Dan Haim <negativeiq@users.sourceforge.net>
license: BSD
* files src/webui/www/private/css/lib/vanillaSelectBox.css src/webui/www/private/scripts/lib/vanillaSelectBox.js
copyright: Philippe Meyer <pmg.meyer@gmail.com>
license: MIT
Images Authors:
* files: src/icons/qbittorrent-tray.svg
copyright: Provided by HVS <hvs linuxmail org> (raster first proposal) and Atif Afzal(@atfzl github) <atif5801@gmail.com> (vectorized and modified)
license: GPLv2+
* files: src/qbittorrent_file.ico src/icons/fileicon.svg
copyright: 'unknown.svg' (LGPLv3+) from Oxygen Icon Theme was used as base which was slightly modified and 'qbittorrent-tray.svg' (GPLv2+) was overlaid above it.
copyright: 'uknown.svg' (LGPLv3+) from Oxygen Icon Theme was used as base which was slightly modified and 'qbittorrent-tray.svg' (GPLv2+) was overlayed above it.
license: GPLv3+
* files: src/icons/flags/*.svg

View File

@@ -11,8 +11,8 @@ set(minBoostVersion 1.71)
set(minQt5Version 5.15.2)
set(minQt6Version 6.2)
set(minOpenSSLVersion 1.1.1)
set(minLibtorrent1Version 1.2.19)
set(minLibtorrentVersion 2.0.9)
set(minLibtorrent1Version 1.2.18)
set(minLibtorrentVersion 2.0.8)
set(minZlibVersion 1.2.11)
include(CheckCXXSourceCompiles) # TODO: migrate to CheckSourceCompiles in CMake >= 3.19
@@ -30,21 +30,16 @@ feature_option(STACKTRACE "Enable stacktrace support" ON)
feature_option(TESTING "Build internal testing suite" OFF)
feature_option(VERBOSE_CONFIGURE "Show information about PACKAGES_FOUND and PACKAGES_NOT_FOUND in the configure output (only useful for debugging the CMake build scripts)" OFF)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
feature_option_dependent(DBUS
"Enable support for notifications and power-management features via D-Bus"
"Enable support for notifications and power-management features via D-Bus on Linux"
ON "GUI" OFF
)
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
feature_option_dependent(SYSTEMD
"Install systemd service file. Target directory is overridable with `SYSTEMD_SERVICES_INSTALL_DIR` variable"
OFF "NOT GUI" OFF
)
endif()
if (MSVC)
elseif (MSVC)
feature_option(MSVC_RUNTIME_DYNAMIC "Use MSVC dynamic runtime library (-MD) instead of static (-MT)" ON)
endif()

View File

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

133
Changelog
View File

@@ -1,65 +1,76 @@
Sun Oct 22nd 2023 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.6.0
- FEATURE: Add (experimental) I2P support (glassez)
- FEATURE: Provide UI editor for the default theme (glassez)
- FEATURE: Various UI theming improvements (glassez)
- FEATURE: Implement torrent tags editing dialog (glassez)
- FEATURE: Revamp "Watched folder options" and "Automated RSS downloader" dialog (glassez)
- FEATURE: Allow to use another icons in dark mode (glassez)
- FEATURE: Allow to add new torrents to queue top (glassez)
- FEATURE: Allow to filter torrent list by save path (Tom)
- FEATURE: Expose 'socket send/receive buffer size' options (Chocobo1)
- FEATURE: Expose 'max torrent file size' setting (Chocobo1)
- FEATURE: Expose 'bdecode limits' settings (Chocobo1)
- FEATURE: Add options to adjust behavior of merging trackers to existing torrent (glassez)
- FEATURE: Add option to stop seeding when torrent has been inactive (Christopher)
- FEATURE: Allow to use proxy per subsystem (glassez)
- FEATURE: Expand the scope of "Proxy hostname lookup" option (glassez)
- FEATURE: Add shortcut for "Ban peer permanently" function (Luka Čelebić)
- FEATURE: Add option to auto hide zero status filters (glassez)
- FEATURE: Allow to disable confirmation of Pause/Resume All (glassez)
- FEATURE: Add alternative shortcut CTRL+E for CTRL+F (Luka Čelebić)
- FEATURE: Show filtered port numbers in logs (Hanabishi)
- FEATURE: Add button to copy library versions to clipboard (Chocobo1)
- BUGFIX: Ensure ongoing storage moving job will be completed when shutting down (Chocobo1)
- BUGFIX: Refactored many areas to call non UI blocking code (glassez)
- BUGFIX: Various improvements to the SQLite backend (glassez)
- BUGFIX: Improve startup window state handling (glassez)
- BUGFIX: Use tray icon from system theme only if option is set (glassez)
- BUGFIX: Inhibit system sleep while torrents are moving (Sentox6)
- BUGFIX: Use hostname instead of domain name in tracker filter list (tearfur)
- BUGFIX: Visually validate input path in torrent creator dialog (Chocobo1)
- BUGFIX: Disable symlink resolving in Torrent creator (Ignat Loskutov)
- BUGFIX: Change default value for `file pool size` and `stop tracker timeout` settings (stalkerok)
- BUGFIX: Log when duplicate torrents are being added (glassez)
- BUGFIX: Inhibit suspend instead of screen idle (axet)
- BUGFIX: Ensure file name is valid when exporting torrents (glassez)
- BUGFIX: Open "Save path" if torrent has no metadata (Xu Chao)
- BUGFIX: Prevent torrent starting unexpectedly edge case with magnet (Xu Chao)
- BUGFIX: Better ergonomics of the "Add new torrent" dialog (Xu Chao, glassez)
- WEBUI: Add log viewer (brvphoenix)
- WEBUI: WebAPI: Allow to specify session cookie name (glassez)
- WEBUI: Improve sync API performance (glassez)
- WEBUI: Add filelog settings (brvphoenix)
- WEBUI: Add multi-file renaming (loligans)
- WEBUI: Add "Add to top of queue" option (thalieht)
- WEBUI: Implement subcategories (Bartu Özen)
- WEBUI: Set "SameSite=None" if CSRF Protection is disabled (七海千秋)
- WEBUI: Show only hosts in tracker filter list (ttys3)
- WEBUI: Set Connection status and Speed limits tooltips (Raymond Ha)
- WEBUI: set Cross Origin Opener Policy to `same-origin` (Chocobo1)
- WEBUI: Fix response for HTTP HEAD method (Chocobo1)
- WEBUI: Preserve the network interfaces when connection is down (Fabricio Silva)
- WEBUI: Add "Add Tags" field for RSS rules (Matic Babnik)
- WEBUI: Fix missing error icon (Trim21)
- RSS: Add "Rename rule" button to RSS Downloader (BallsOfSpaghetti)
- RSS: Allow to edit RSS feed URL (glassez)
- RSS: Allow to assign priority to RSS download rule (glassez)
- SEARCH: Use python isolate mode (Chocobo1)
- SEARCH: Bump python version minimum requirement to 3.7.0 (Chocobo1)
- OTHER: Enable DBUS cmake option on FreeBSD (yuri@FreeBSD)
- OTHER: Numerous code improvements and refactorings (glassez, Chocobo1)
Tue Aug 29 2023 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.5
- BUGFIX: Fix transfer list tab hotkey (thalieht)
- BUGFIX: Don't forget to enable the Apply button in the Options dialog (glassez)
- BUGFIX: Immediately update torrent status on moving files (glassez)
- BUGFIX: Improve performance when scrolling the file list of large torrents (gdim47)
- BUGFIX: Don't operate on random torrents when multiple are selected and a sort/filter is applied (glassez)
- RSS: Fix overwriting feeds.json with an incomplete load of it (Omar Abdul Azeez)
- WINDOWS: Software update check logic is disabled for < Win10 (sledgehammer999)
- WINDOWS: NSIS: Update Turkish and French translations (Burak Yavuz, MarcDrieu)
- WINDOWS: NSIS: Add Romanian translation (rusu-afanasie)
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.0
Sun Jun 18 2023 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.4
- BUGFIX: Allow to disable confirmation of Pause/Resume All (glassez)
- BUGFIX: Sync flag icons with upstream (Priit Uring)
- WEBUI: Fix category save path (Raymond Ha)
Sun May 28 2023 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.3
- BUGFIX: Correctly check if database needs to be updated (glassez)
- BUGFIX: Prevent incorrect log message about torrent content deletion (glassez)
- BUGFIX: Improve finished torrent handling (glassez)
- BUGFIX: Correctly initialize group box children as disabled in Preferences (thalieht)
- BUGFIX: Don't miss saving "download path" in SQLite storage (glassez)
- BUGFIX: Improve logging of running external program (glassez)
- WEBUI: Disable UPnP for web UI by default (glassez)
- WEBUI: Use workaround for IOS file picker (DivineHawk)
- WEBUI: Work around Chrome download limit (Chocobo1)
- WEBUI: Improve 'exporting torrent' behavior (Chocobo1)
- WINDOWS: NSIS: Add Slovak translation (Christian Danížek)
Tue Feb 28 2023 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.2
- BUGFIX: Don't unexpectedly activate queued torrents when prefetching metadata for added magnets (glassez)
- BUGFIX: Update the cached torrent state once recheck is started (glassez)
- BUGFIX: Be more likely to allow the system to use power saving modes (glassez)
- WEBUI: Migrate away from unsafe function (Chocobo1)
- WEBUI: Blacklist bad ciphers for TLS in the server (sledgehammer999)
- WEBUI: Allow only TLS 1.2+ in the server (sledgehammer999)
- WEBUI: Allow to set read-only directory as torrent location (glassez)
- WEBUI: Reject requests that contain backslash in path (glassez)
- RSS: Prevent RSS folder from being moved into itself (glassez)
- WINDOWS: NSIS: Update Turkish, Uzbek translation (Burak Yavuz, shitcod3r)
Sun Feb 12 2023 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.1
- FEATURE: Re-allow to use icons from system theme (glassez)
- BUGFIX: Fix Speed limit icon size (Nowshed H. Imran)
- BUGFIX: Revise and fix some text colors (Chocobo1, Nowshed H. Imran)
- BUGFIX: Correctly load folder based UI theme (glassez)
- BUGFIX: Fix crash due to invalid encoding of tracker URLs (glassez)
- BUGFIX: Don't drop !qB extension when renaming incomplete file (glassez)
- BUGFIX: Correctly count the number of torrents in subcategories (glassez)
- BUGFIX: Use "additional trackers" when metadata retrieving (glassez)
- BUGFIX: Apply correct tab order to Category options dialog (glassez)
- BUGFIX: Add all torrents passed via the command line (glassez)
- BUGFIX: Fix startup performance on Qt5 (glassez)
- BUGFIX: Automatic move will now overwrite existing files (aka previous behavior) (glassez)
- BUGFIX: Some fixes for loading Chinese locales (sledgehammer999)
- BUGFIX: New Pause icon color for toolbar/menu (Nowshed H. Imran, sledgehammer999)
- BUGFIX: Adjust env variable for PDB discovery (sledgehammer999)
- WEBUI: Fix missing "queued" icon (thalieht)
- WEBUI: Return paths using platform-independent separator format (glassez)
- WEBUI: Change order of accepted types of file input (Jason Carr)
- WEBUI: Add missing icons (brvphoenix)
- WEBUI: Add "Resume data storage type" option (thalieht)
- WEBUI: Make rename file dialog resizable (Torsten Schwarz)
- WEBUI: Prevent incorrect line breaking (David Xuang)
- WEBUI: Improve hotkeys (Fidel Selva)
- WEBUI: Remove suggestions while searching for torrents (Midhun V Nadh)
- WEBUI: Expose "IS PRIVATE" flag (sotiris-bos)
- WEBUI: Return name/hash/infohash_v1/infohash_v2 torrent properties (qbittorrentfan)
- WINDOWS: Correctly detect drive letter in path (glassez)
- WINDOWS: NSIS: Update Swedish, Lithuanian translations (Jonatan, Deividas)
- LINUX: Fix tray icon issues (glassez)
Sat Nov 26 2022 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.0
- FEATURE: Add `Auto resize columns` functionality (Chocobo1)
- FEATURE: Allow to use Category paths in `Manual` mode (glassez)
- FEATURE: Allow to disable Automatic mode when default "temp" path changed (glassez)

View File

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

View File

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

View File

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

86
configure vendored
View File

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

View File

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

2
dist/mac/Info.plist vendored
View File

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

View File

@@ -68,12 +68,12 @@
<update_contact>sledgehammer999@qbittorrent.org</update_contact>
<developer_name>The qBittorrent Project</developer_name>
<url type="homepage">https://www.qbittorrent.org/</url>
<url type="bugtracker">https://bugs.qbittorrent.org/</url>
<url type="bugtracker">http://bugs.qbittorrent.org/</url>
<url type="donation">https://www.qbittorrent.org/donate</url>
<url type="help">https://forum.qbittorrent.org/</url>
<url type="help">http://forum.qbittorrent.org/</url>
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
<content_rating type="oars-1.1"/>
<releases>
<release version="4.6.0" date="2023-10-22"/>
<release version="4.5.5" date="2023-08-29"/>
</releases>
</component>

View File

@@ -14,216 +14,216 @@ Keywords=bittorrent;torrent;magnet;download;p2p;
SingleMainWindow=true
# Translations
GenericName[af]=BitTorrent kliënt
Comment[af]=Aflaai en deel lêers oor BitTorrent
GenericName[af]=BitTorrent kliënt
Name[af]=qBittorrent
GenericName[ar]=عميل بت‎تورنت
Comment[ar]=نزّل وشارك الملفات عبر كيوبت‎تورنت
GenericName[ar]=عميل بت‎تورنت
Name[ar]=qBittorrent
GenericName[be]=Кліент BitTorrent
Comment[be]=Спампоўванне і раздача файлаў праз пратакол BitTorrent
GenericName[be]=Кліент BitTorrent
Name[be]=qBittorrent
GenericName[bg]=BitTorrent клиент
Comment[bg]=Сваляне и споделяне на файлове чрез BitTorrent
GenericName[bg]=BitTorrent клиент
Name[bg]=qBittorrent
GenericName[bn]=বিটটরেন্ট ক্লায়েন্ট
Comment[bn]=বিটটরেন্টে ফাইল ডাউনলোড এবং শেয়ার করুন
GenericName[bn]=বিটটরেন্ট ক্লায়েন্ট
Name[bn]=qBittorrent
GenericName[zh]=BitTorrent 客户端
Comment[zh]=通过 BitTorrent 下载和分享文件
GenericName[zh]=BitTorrent 客户端
Name[zh]=qBittorrent
GenericName[bs]=BitTorrent klijent
Comment[bs]=Preuzmi i dijeli datoteke preko BitTorrent-a
GenericName[bs]=BitTorrent klijent
Name[bs]=qBittorrent
GenericName[ca]=Client de BitTorrent
Comment[ca]=Baixeu i compartiu fitxers amb el BitTorrent
GenericName[ca]=Client de BitTorrent
Name[ca]=qBittorrent
GenericName[cs]=BitTorrent klient
Comment[cs]=Stahování a sdílení souborů přes síť BitTorrent
GenericName[cs]=BitTorrent klient
Name[cs]=qBittorrent
GenericName[da]=BitTorrent-klient
Comment[da]=Download og del filer over BitTorrent
GenericName[da]=BitTorrent-klient
Name[da]=qBittorrent
GenericName[de]=BitTorrent Client
Comment[de]=Über BitTorrent Dateien herunterladen und teilen
GenericName[de]=BitTorrent Client
Name[de]=qBittorrent
GenericName[el]=BitTorrent client
Comment[el]=Κάντε λήψη και μοιραστείτε αρχεία μέσω BitTorrent
GenericName[el]=BitTorrent client
Name[el]=qBittorrent
GenericName[en_GB]=BitTorrent client
Comment[en_GB]=Download and share files over BitTorrent
GenericName[en_GB]=BitTorrent client
Name[en_GB]=qBittorrent
GenericName[es]=Cliente BitTorrent
Comment[es]=Descargue y comparta archivos por BitTorrent
GenericName[es]=Cliente BitTorrent
Name[es]=qBittorrent
GenericName[et]=BitTorrent klient
Comment[et]=Lae alla ja jaga faile üle BitTorrenti
GenericName[et]=BitTorrent klient
Name[et]=qBittorrent
GenericName[eu]=BitTorrent bezeroa
Comment[eu]=Jeitsi eta elkarbanatu agiriak BitTorrent bidez
GenericName[eu]=BitTorrent bezeroa
Name[eu]=qBittorrent
GenericName[fa]=بیت تورنت نسخه کلاینت
Comment[fa]=دانلود و به اشتراک گذاری فایل های بوسیله بیت تورنت
GenericName[fa]=بیت تورنت نسخه کلاینت
Name[fa]=qBittorrent
GenericName[fi]=BitTorrent-asiakasohjelma
Comment[fi]=Lataa ja jaa tiedostoja BitTorrentia käyttäen
GenericName[fi]=BitTorrent-asiakasohjelma
Name[fi]=qBittorrent
GenericName[fr]=Client BitTorrent
Comment[fr]=Télécharger et partager des fichiers sur BitTorrent
GenericName[fr]=Client BitTorrent
Name[fr]=qBittorrent
GenericName[gl]=Cliente BitTorrent
Comment[gl]=Descargar e compartir ficheiros co protocolo BitTorrent
GenericName[gl]=Cliente BitTorrent
Name[gl]=qBittorrent
GenericName[gu]=બિટ્ટોરેંટ ક્લાયન્ટ
Comment[gu]=બિટ્ટોરેંટ પર ફાઈલો ડાઉનલોડ અને શેર કરો
GenericName[gu]=બિટ્ટોરેંટ ક્લાયન્ટ
Name[gu]=qBittorrent
GenericName[he]=לקוח ביטורנט
Comment[he]=הורד ושתף קבצים על גבי ביטורנט
GenericName[he]=לקוח ביטורנט
Name[he]=qBittorrent
GenericName[hr]=BitTorrent klijent
Comment[hr]=Preuzmite i dijelite datoteke putem BitTorrenta
GenericName[hr]=BitTorrent klijent
Name[hr]=qBittorrent
GenericName[hu]=BitTorrent kliens
Comment[hu]=Fájlok letöltése és megosztása a BitTorrent hálózaton keresztül
GenericName[hu]=BitTorrent kliens
Name[hu]=qBittorrent
GenericName[hy]=BitTorrent սպասառու
Comment[hy]=Նիշքերի փոխանցում BitTorrent-ի միջոցով
GenericName[hy]=BitTorrent սպասառու
Name[hy]=qBittorrent
GenericName[id]=Klien BitTorrent
Comment[id]=Unduh dan berbagi berkas melalui BitTorrent
GenericName[id]=Klien BitTorrent
Name[id]=qBittorrent
GenericName[is]=BitTorrent biðlarar
Comment[is]=Sækja og deila skrám yfir BitTorrent
GenericName[is]=BitTorrent biðlarar
Name[is]=qBittorrent
GenericName[it]=Client BitTorrent
Comment[it]=Scarica e condividi file tramite BitTorrent
GenericName[it]=Client BitTorrent
Name[it]=qBittorrent
GenericName[ja]=BitTorrentクライアント
Comment[ja]=BitTorrentでファイルのダウンロードと共有
GenericName[ja]=BitTorrentクライアント
Name[ja]=qBittorrent
GenericName[ka]=BitTorrent კლიენტი
Comment[ka]=გადმოტვირთეთ და გააზიარეთ ფაილები BitTorrent-ის საშუალებით
GenericName[ka]=BitTorrent კლიენტი
Name[ka]=qBittorrent
GenericName[ko]=BitTorrent 클라이언트
Comment[ko]=BitTorrent를 통한 파일 내려받기 및 공유
GenericName[ko]=BitTorrent 클라이언트
Name[ko]=qBittorrent
GenericName[lt]=BitTorrent klientas
Comment[lt]=Atsisiųskite bei dalinkitės failais BitTorrent tinkle
GenericName[lt]=BitTorrent klientas
Name[lt]=qBittorrent
GenericName[mk]=BitTorrent клиент
Comment[mk]=Превземајте и споделувајте фајлови преку BitTorrent
GenericName[mk]=BitTorrent клиент
Name[mk]=qBittorrent
GenericName[my]=တောရန့်စီမံခန့်ခွဲသည့်အရာ
Comment[my]=တောရန့်ဖြင့်ဖိုင်များဒေါင်းလုဒ်ဆွဲရန်နှင့်မျှဝေရန်
GenericName[my]=တောရန့်စီမံခန့်ခွဲသည့်အရာ
Name[my]=qBittorrent
GenericName[nb]=BitTorrent-klient
Comment[nb]=Last ned og del filer over BitTorrent
GenericName[nb]=BitTorrent-klient
Name[nb]=qBittorrent
GenericName[nl]=BitTorrent-client
Comment[nl]=Bestanden downloaden en delen via BitTorrent
GenericName[nl]=BitTorrent-client
Name[nl]=qBittorrent
GenericName[pl]=Klient BitTorrent
Comment[pl]=Pobieraj i dziel się plikami przez BitTorrent
GenericName[pl]=Klient BitTorrent
Name[pl]=qBittorrent
GenericName[pt]=Cliente BitTorrent
Comment[pt]=Transferir e partilhar ficheiros por BitTorrent
GenericName[pt]=Cliente BitTorrent
Name[pt]=qBittorrent
GenericName[pt_BR]=Cliente BitTorrent
Comment[pt_BR]=Baixe e compartilhe arquivos pelo BitTorrent
GenericName[pt_BR]=Cliente BitTorrent
Name[pt_BR]=qBittorrent
GenericName[ro]=Client BitTorrent
Comment[ro]=Descărcați și partajați fișiere prin BitTorrent
GenericName[ro]=Client BitTorrent
Name[ro]=qBittorrent
GenericName[ru]=Клиент сети БитТоррент
Comment[ru]=Обмен файлами по сети БитТоррент
GenericName[ru]=Клиент сети БитТоррент
Name[ru]=qBittorrent
GenericName[sk]=Klient siete BitTorrent
Comment[sk]=Sťahovanie a zdieľanie súborov prostredníctvom siete BitTorrent
GenericName[sk]=Klient siete BitTorrent
Name[sk]=qBittorrent
GenericName[sl]=BitTorrent odjemalec
Comment[sl]=Prenesite in delite datoteke preko BitTorrenta
GenericName[sl]=BitTorrent odjemalec
Name[sl]=qBittorrent
Name[sq]=qBittorrent
GenericName[sr]=BitTorrent-клијент
Comment[sr]=Преузимајте и делите фајлове преко BitTorrent протокола
GenericName[sr]=BitTorrent-клијент
Name[sr]=qBittorrent
GenericName[sr@latin]=BitTorrent klijent
Comment[sr@latin]=Preuzimanje i deljenje fajlova preko BitTorrent-a
GenericName[sr@latin]=BitTorrent klijent
Name[sr@latin]=qBittorrent
GenericName[sv]=BitTorrent-klient
Comment[sv]=Hämta och dela filer över BitTorrent
GenericName[sv]=BitTorrent-klient
Name[sv]=qBittorrent
GenericName[ta]=BitTorrent வாடிக்கையாளர்
Comment[ta]=BitTorrent வழியாக கோப்புகளை பதிவிறக்க மற்றும் பகிர
GenericName[ta]=BitTorrent வாடிக்கையாளர்
Name[ta]=qBittorrent
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
Comment[te]=క్యు బిట్ టొరెంట్ తో ఫైల్స్ దిగుమతి చేసుకోండి , పంచుకోండి
GenericName[te]=క్యు బిట్ టొరెంట్ క్లయింట్
Name[te]=qBittorrent
GenericName[th]=โปรแกรมบิททอเร้นท์
Comment[th]=ดาวน์โหลดและแชร์ไฟล์ผ่าน BitTorrent
GenericName[th]=โปรแกรมบิททอเร้นท์
Name[th]=qBittorrent
GenericName[tr]=BitTorrent istemcisi
Comment[tr]=Dosyaları BitTorrent üzerinden indirin ve paylaşın
GenericName[tr]=BitTorrent istemcisi
Name[tr]=qBittorrent
GenericName[ur]=قیو بٹ ٹورنٹ کلائنٹ
Comment[ur]=BitTorrent پر فائلوں کو ڈاؤن لوڈ کریں اور اشتراک کریں
GenericName[ur]=قیو بٹ ٹورنٹ کلائنٹ
Name[ur]=qBittorrent
GenericName[uk]=BitTorrent-клієнт
Comment[uk]=Завантажуйте та поширюйте файли через BitTorrent
GenericName[uk]=BitTorrent-клієнт
Name[uk]=qBittorrent
GenericName[vi]=Máy khách BitTorrent
Comment[vi]=Tải xuống và chia sẻ tệp qua BitTorrent
GenericName[vi]=Máy khách BitTorrent
Name[vi]=qBittorrent
GenericName[zh_HK]=BitTorrent用戶端
Comment[zh_HK]=經由BitTorrent下載並分享檔案
GenericName[zh_HK]=BitTorrent用戶端
Name[zh_HK]=qBittorrent
GenericName[zh_TW]=BitTorrent 用戶端
Comment[zh_TW]=經由 BitTorrent 下載並分享檔案
GenericName[zh_TW]=BitTorrent 用戶端
Name[zh_TW]=qBittorrent
GenericName[eo]=BitTorrent-kliento
Comment[eo]=Elŝutu kaj kunhavigu dosierojn per BitTorrent
GenericName[eo]=BitTorrent-kliento
Name[eo]=qBittorrent
GenericName[kk]=BitTorrent клиенті
Comment[kk]=BitTorrent арқылы файл жүктеу және бөлісу
GenericName[kk]=BitTorrent клиенті
Name[kk]=qBittorrent
GenericName[en_AU]=BitTorrent client
Comment[en_AU]=Download and share files over BitTorrent
GenericName[en_AU]=BitTorrent client
Name[en_AU]=qBittorrent
Name[rm]=qBittorrent
Name[jv]=qBittorrent
GenericName[oc]=Client BitTorrent
Comment[oc]=Telecargar e partejar de fichièrs amb BitTorrent
GenericName[oc]=Client BitTorrent
Name[oc]=qBittorrent
Name[ug]=qBittorrent
Name[yi]=qBittorrent
GenericName[nqo]=ߓߌߙߏߙߍ߲ߕ ߕߣߐ߬ߓߐ߬ߟߊ
Comment[nqo]=ߞߐߕߐ߯ߘߐ ߟߎ߬ ߟߊߖߌ߰ ߞߊ߬ ߓߊ߲߫ ߞߵߊ߬ߟߎ߬ ߘߐߕߟߊ߫ ߓߌߙߏߙߍ߲ߕ ߞߊ߲߬
GenericName[nqo]=ߓߌߙߏߙߍ߲ߕ ߕߣߐ߬ߓߐ߬ߟߊ
Name[nqo]=qBittorrent
GenericName[uz@Latn]=BitTorrent mijozi
Comment[uz@Latn]=BitTorrent orqali fayllarni yuklab olish va baham korish
GenericName[uz@Latn]=BitTorrent mijozi
Name[uz@Latn]=qBittorrent
GenericName[ltg]=BitTorrent klients
Comment[ltg]=Atsasyuteit i daleit failus ar BitTorrent
GenericName[ltg]=BitTorrent klients
Name[ltg]=qBittorrent
GenericName[hi_IN]=Bittorrent साधन
Comment[hi_IN]=BitTorrent द्वारा फाइल डाउनलोड व सहभाजन
GenericName[hi_IN]=Bittorrent साधन
Name[hi_IN]=qBittorrent
GenericName[az@latin]=BitTorrent client
Comment[az@latin]=Faylları BitTorrent vasitəsilə endirin və paylaşın
GenericName[az@latin]=BitTorrent client
Name[az@latin]=qBittorrent
GenericName[lv_LV]=BitTorrent klients
Comment[lv_LV]=Lejupielādēt un koplietot failus ar BitTorrent
GenericName[lv_LV]=BitTorrent klients
Name[lv_LV]=qBittorrent
GenericName[ms_MY]=Klien BitTorrent
Comment[ms_MY]=Muat turun dan kongsi fail melalui BitTorrent
GenericName[ms_MY]=Klien BitTorrent
Name[ms_MY]=qBittorrent
GenericName[mn_MN]=BitTorrent татагч
Comment[mn_MN]=BitTorrent-оор файлуудаа тат, түгээ
GenericName[mn_MN]=BitTorrent татагч
Name[mn_MN]=qBittorrent
GenericName[ne_NP]=BitTorrent क्लाइन्ट
Comment[ne_NP]=फाइलहरू डाउनलोड गर्नुहोस् र BitTorrent मा साझा गर्नुहोस्
GenericName[ne_NP]=BitTorrent क्लाइन्ट
Name[ne_NP]=qBittorrent
GenericName[pt_PT]=Cliente BitTorrent
Comment[pt_PT]=Transferir e partilhar ficheiros por BitTorrent
GenericName[pt_PT]=Cliente BitTorrent
Name[pt_PT]=qBittorrent
Name[si_LK]=qBittorrent

View File

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

View File

@@ -109,7 +109,7 @@ Section $(inst_torrent) ;"Open .torrent files with qBittorrent"
!insertmacro UAC_AsUser_Call Function inst_torrent_user ${UAC_SYNCREGISTERS}|${UAC_SYNCOUTDIR}|${UAC_SYNCINSTDIR}
System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, p 0, p 0)'
System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)'
SectionEnd
@@ -142,7 +142,7 @@ Section $(inst_magnet) ;"Open magnet links with qBittorrent"
!insertmacro UAC_AsUser_Call Function inst_magnet_user ${UAC_SYNCREGISTERS}|${UAC_SYNCOUTDIR}|${UAC_SYNCINSTDIR}
System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, p 0, p 0)'
System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)'
SectionEnd

View File

@@ -26,17 +26,17 @@ Section "un.$(remove_associations)" ;"un.Remove file associations"
DetailPrint "$(uninst_tor_warn) $0"
DeleteRegValue HKLM "Software\Classes\.torrent" ""
DeleteRegKey /ifempty HKLM "Software\Classes\.torrent"
torrent_end:
torrent_end:
ReadRegStr $0 HKLM "Software\Classes\magnet\shell\open\command" ""
StrCmp $0 '"$INSTDIR\qbittorrent.exe" "%1"' 0 magnet_end
DetailPrint "$(uninst_mag_warn) $0"
DeleteRegKey HKLM "Software\Classes\magnet"
magnet_end:
magnet_end:
!insertmacro UAC_AsUser_Call Function un.remove_associations_user ${UAC_SYNCREGISTERS}|${UAC_SYNCOUTDIR}|${UAC_SYNCINSTDIR}
System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, p 0, p 0)'
System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)'
SectionEnd
Function un.remove_associations_user
@@ -45,12 +45,13 @@ Function un.remove_associations_user
DetailPrint "$(uninst_tor_warn) $0"
DeleteRegValue HKCU "Software\Classes\.torrent" ""
DeleteRegKey /ifempty HKCU "Software\Classes\.torrent"
torrent_end:
torrent_end:
ReadRegStr $0 HKCU "Software\Classes\magnet\shell\open\command" ""
StrCmp $0 '"$INSTDIR\qbittorrent.exe" "%1"' 0 magnet_end
DetailPrint "$(uninst_mag_warn) $0"
DeleteRegKey HKCU "Software\Classes\magnet"
magnet_end:
FunctionEnd
@@ -61,7 +62,7 @@ Section "un.$(remove_registry)" ;"un.Remove registry keys"
DeleteRegKey HKLM "Software\qBittorrent"
DeleteRegKey HKLM "Software\Classes\qBittorrent"
System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, p 0, p 0)'
System::Call 'Shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_IDLIST}, i 0, i 0)'
SectionEnd
Section "un.$(remove_firewall)" ;

View File

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

View File

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

View File

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

View File

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

View File

@@ -97,11 +97,6 @@
#include "gui/shutdownconfirmdialog.h"
#include "gui/uithememanager.h"
#include "gui/utils.h"
#include "gui/windowstate.h"
#ifdef Q_OS_WIN
#include "base/utils/os.h"
#endif // Q_OS_WIN
#endif // DISABLE_GUI
#ifndef DISABLE_WEBUI
@@ -112,12 +107,12 @@ namespace
{
#define SETTINGS_KEY(name) u"Application/" name
#define FILELOGGER_SETTINGS_KEY(name) (SETTINGS_KEY(u"FileLogger/") name)
#define NOTIFICATIONS_SETTINGS_KEY(name) (SETTINGS_KEY(u"GUI/Notifications/"_s) name)
#define NOTIFICATIONS_SETTINGS_KEY(name) (SETTINGS_KEY(u"GUI/Notifications/"_qs) name)
const QString LOG_FOLDER = u"logs"_s;
const QString LOG_FOLDER = u"logs"_qs;
const QChar PARAMS_SEPARATOR = u'|';
const Path DEFAULT_PORTABLE_MODE_PROFILE_DIR {u"profile"_s};
const Path DEFAULT_PORTABLE_MODE_PROFILE_DIR {u"profile"_qs};
const int MIN_FILELOG_SIZE = 1024; // 1KiB
const int MAX_FILELOG_SIZE = 1000 * 1024 * 1024; // 1000MiB
@@ -126,132 +121,34 @@ namespace
#ifndef DISABLE_GUI
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
#endif
QString serializeParams(const QBtCommandLineParameters &params)
{
QStringList result;
// Because we're passing a string list to the currently running
// qBittorrent process, we need some way of passing along the options
// the user has specified. Here we place special strings that are
// almost certainly not going to collide with a file path or URL
// specified by the user, and placing them at the beginning of the
// string list so that they will be processed before the list of
// torrent paths or URLs.
const BitTorrent::AddTorrentParams &addTorrentParams = params.addTorrentParams;
if (!addTorrentParams.savePath.isEmpty())
result.append(u"@savePath=" + addTorrentParams.savePath.data());
if (addTorrentParams.addPaused.has_value())
result.append(*addTorrentParams.addPaused ? u"@addPaused=1"_s : u"@addPaused=0"_s);
if (addTorrentParams.skipChecking)
result.append(u"@skipChecking"_s);
if (!addTorrentParams.category.isEmpty())
result.append(u"@category=" + addTorrentParams.category);
if (addTorrentParams.sequential)
result.append(u"@sequential"_s);
if (addTorrentParams.firstLastPiecePriority)
result.append(u"@firstLastPiecePriority"_s);
if (params.skipDialog.has_value())
result.append(*params.skipDialog ? u"@skipDialog=1"_s : u"@skipDialog=0"_s);
result += params.torrentSources;
return result.join(PARAMS_SEPARATOR);
}
QBtCommandLineParameters parseParams(const QString &str)
{
QBtCommandLineParameters parsedParams;
BitTorrent::AddTorrentParams &addTorrentParams = parsedParams.addTorrentParams;
for (QString param : asConst(str.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts)))
{
param = param.trimmed();
// Process strings indicating options specified by the user.
if (param.startsWith(u"@savePath="))
{
addTorrentParams.savePath = Path(param.mid(10));
continue;
}
if (param.startsWith(u"@addPaused="))
{
addTorrentParams.addPaused = (QStringView(param).mid(11).toInt() != 0);
continue;
}
if (param == u"@skipChecking")
{
addTorrentParams.skipChecking = true;
continue;
}
if (param.startsWith(u"@category="))
{
addTorrentParams.category = param.mid(10);
continue;
}
if (param == u"@sequential")
{
addTorrentParams.sequential = true;
continue;
}
if (param == u"@firstLastPiecePriority")
{
addTorrentParams.firstLastPiecePriority = true;
continue;
}
if (param.startsWith(u"@skipDialog="))
{
parsedParams.skipDialog = (QStringView(param).mid(12).toInt() != 0);
continue;
}
parsedParams.torrentSources.append(param);
}
return parsedParams;
}
}
Application::Application(int &argc, char **argv)
: BaseApplication(argc, argv)
, m_commandLineArgs(parseCommandLine(Application::arguments()))
, m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY(u"Enabled"_s))
, m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY(u"Backup"_s))
, m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY(u"DeleteOld"_s))
, m_storeFileLoggerMaxSize(FILELOGGER_SETTINGS_KEY(u"MaxSizeBytes"_s))
, m_storeFileLoggerAge(FILELOGGER_SETTINGS_KEY(u"Age"_s))
, m_storeFileLoggerAgeType(FILELOGGER_SETTINGS_KEY(u"AgeType"_s))
, m_storeFileLoggerPath(FILELOGGER_SETTINGS_KEY(u"Path"_s))
, m_storeMemoryWorkingSetLimit(SETTINGS_KEY(u"MemoryWorkingSetLimit"_s))
, m_shutdownAct(ShutdownDialogAction::Exit)
, m_commandLineArgs(parseCommandLine(this->arguments()))
, m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY(u"Enabled"_qs))
, m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY(u"Backup"_qs))
, m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY(u"DeleteOld"_qs))
, m_storeFileLoggerMaxSize(FILELOGGER_SETTINGS_KEY(u"MaxSizeBytes"_qs))
, m_storeFileLoggerAge(FILELOGGER_SETTINGS_KEY(u"Age"_qs))
, m_storeFileLoggerAgeType(FILELOGGER_SETTINGS_KEY(u"AgeType"_qs))
, m_storeFileLoggerPath(FILELOGGER_SETTINGS_KEY(u"Path"_qs))
, m_storeMemoryWorkingSetLimit(SETTINGS_KEY(u"MemoryWorkingSetLimit"_qs))
#ifdef Q_OS_WIN
, m_processMemoryPriority(SETTINGS_KEY(u"ProcessMemoryPriority"_s))
, m_processMemoryPriority(SETTINGS_KEY(u"ProcessMemoryPriority"_qs))
#endif
#ifndef DISABLE_GUI
, m_startUpWindowState(u"GUI/StartUpWindowState"_s)
, m_storeNotificationTorrentAdded(NOTIFICATIONS_SETTINGS_KEY(u"TorrentAdded"_s))
, m_storeNotificationTorrentAdded(NOTIFICATIONS_SETTINGS_KEY(u"TorrentAdded"_qs))
#endif
{
qRegisterMetaType<Log::Msg>("Log::Msg");
qRegisterMetaType<Log::Peer>("Log::Peer");
setApplicationName(u"qBittorrent"_s);
setOrganizationDomain(u"qbittorrent.org"_s);
setApplicationName(u"qBittorrent"_qs);
setOrganizationDomain(u"qbittorrent.org"_qs);
#if !defined(DISABLE_GUI)
setDesktopFileName(u"org.qbittorrent.qBittorrent"_s);
setDesktopFileName(u"org.qbittorrent.qBittorrent"_qs);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
setAttribute(Qt::AA_UseHighDpiPixmaps, true); // opt-in to the high DPI pixmap support
#endif
@@ -279,7 +176,7 @@ Application::Application(int &argc, char **argv)
if (!firstTimeUser)
{
if (!upgrade())
throw RuntimeError(u"Failed migration of old settings"_s); // Not translatable. Translation isn't configured yet.
throw RuntimeError(u"Failed migration of old settings"_qs); // Not translatable. Translation isn't configured yet.
handleChangedDefaults(DefaultPreferencesMode::Legacy);
}
else
@@ -300,7 +197,7 @@ Application::Application(int &argc, char **argv)
{
LogMsg(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir.toString()));
if (m_commandLineArgs.relativeFastresumePaths)
LogMsg(tr("Redundant command line flag detected: \"%1\". Portable mode implies relative fastresume.").arg(u"--relative-fastresume"_s), Log::WARNING); // to avoid translating the `--relative-fastresume` string
LogMsg(tr("Redundant command line flag detected: \"%1\". Portable mode implies relative fastresume.").arg(u"--relative-fastresume"_qs), Log::WARNING); // to avoid translating the `--relative-fastresume` string
}
else
{
@@ -315,7 +212,7 @@ Application::Application(int &argc, char **argv)
if (m_commandLineArgs.torrentingPort > 0) // it will be -1 when user did not set any value
{
SettingValue<int> port {u"BitTorrent/Session/Port"_s};
SettingValue<int> port {u"BitTorrent/Session/Port"_qs};
port = m_commandLineArgs.torrentingPort;
}
}
@@ -338,16 +235,6 @@ MainWindow *Application::mainWindow()
return m_window;
}
WindowState Application::startUpWindowState() const
{
return m_startUpWindowState;
}
void Application::setStartUpWindowState(const WindowState windowState)
{
m_startUpWindowState = windowState;
}
bool Application::isTorrentAddedNotificationsEnabled() const
{
return m_storeNotificationTorrentAdded;
@@ -491,7 +378,7 @@ void Application::processMessage(const QString &message)
}
#endif
const QBtCommandLineParameters params = parseParams(message);
const AddTorrentParams params = parseParams(message.split(PARAMS_SEPARATOR, Qt::SkipEmptyParts));
// If Application is not allowed to process params immediately
// (i.e., other components are not ready) store params
if (m_isProcessingParamsAllowed)
@@ -527,13 +414,13 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
str.replace(i, 2, torrent->contentPath().toString());
break;
case u'G':
str.replace(i, 2, torrent->tags().join(u","_s));
str.replace(i, 2, torrent->tags().join(u","_qs));
break;
case u'I':
str.replace(i, 2, (torrent->infoHash().v1().isValid() ? torrent->infoHash().v1().toString() : u"-"_s));
str.replace(i, 2, (torrent->infoHash().v1().isValid() ? torrent->infoHash().v1().toString() : u"-"_qs));
break;
case u'J':
str.replace(i, 2, (torrent->infoHash().v2().isValid() ? torrent->infoHash().v2().toString() : u"-"_s));
str.replace(i, 2, (torrent->infoHash().v2().isValid() ? torrent->infoHash().v2().toString() : u"-"_qs));
break;
case u'K':
str.replace(i, 2, torrent->id().toString());
@@ -739,12 +626,71 @@ void Application::allTorrentsFinished()
exit();
}
bool Application::callMainInstance()
bool Application::sendParams(const QStringList &params)
{
return m_instanceManager->sendMessage(serializeParams(commandLineArgs()));
return m_instanceManager->sendMessage(params.join(PARAMS_SEPARATOR));
}
void Application::processParams(const QBtCommandLineParameters &params)
Application::AddTorrentParams Application::parseParams(const QStringList &params) const
{
AddTorrentParams parsedParams;
BitTorrent::AddTorrentParams &torrentParams = parsedParams.torrentParams;
for (QString param : params)
{
param = param.trimmed();
// Process strings indicating options specified by the user.
if (param.startsWith(u"@savePath="))
{
torrentParams.savePath = Path(param.mid(10));
continue;
}
if (param.startsWith(u"@addPaused="))
{
torrentParams.addPaused = (QStringView(param).mid(11).toInt() != 0);
continue;
}
if (param == u"@skipChecking")
{
torrentParams.skipChecking = true;
continue;
}
if (param.startsWith(u"@category="))
{
torrentParams.category = param.mid(10);
continue;
}
if (param == u"@sequential")
{
torrentParams.sequential = true;
continue;
}
if (param == u"@firstLastPiecePriority")
{
torrentParams.firstLastPiecePriority = true;
continue;
}
if (param.startsWith(u"@skipDialog="))
{
parsedParams.skipTorrentDialog = (QStringView(param).mid(12).toInt() != 0);
continue;
}
parsedParams.torrentSources.append(param);
}
return parsedParams;
}
void Application::processParams(const AddTorrentParams &params)
{
#ifndef DISABLE_GUI
// There are two circumstances in which we want to show the torrent
@@ -752,21 +698,22 @@ void Application::processParams(const QBtCommandLineParameters &params)
// be shown and skipTorrentDialog is undefined. The other is when
// skipTorrentDialog is false, meaning that the application setting
// should be overridden.
const bool showDialog = !params.skipDialog.value_or(!AddNewTorrentDialog::isEnabled());
if (showDialog)
const bool showDialogForThisTorrent = !params.skipTorrentDialog.value_or(!AddNewTorrentDialog::isEnabled());
if (showDialogForThisTorrent)
{
for (const QString &torrentSource : params.torrentSources)
AddNewTorrentDialog::show(torrentSource, params.addTorrentParams, m_window);
AddNewTorrentDialog::show(torrentSource, params.torrentParams, m_window);
}
else
#endif
{
for (const QString &torrentSource : params.torrentSources)
BitTorrent::Session::instance()->addTorrent(torrentSource, params.addTorrentParams);
BitTorrent::Session::instance()->addTorrent(torrentSource, params.torrentParams);
}
}
int Application::exec()
int Application::exec(const QStringList &params)
try
{
#if !defined(DISABLE_WEBUI) && defined(DISABLE_GUI)
const QString loadingStr = tr("WebUI will be started shortly after internal preparations. Please wait...");
@@ -795,7 +742,7 @@ int Application::exec()
#ifndef Q_OS_MACOS
auto *desktopIntegrationMenu = new QMenu;
auto *actionExit = new QAction(tr("E&xit"), desktopIntegrationMenu);
actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_s));
actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_qs));
actionExit->setMenuRole(QAction::QuitRole);
actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
connect(actionExit, &QAction::triggered, this, []
@@ -805,8 +752,11 @@ int Application::exec()
desktopIntegrationMenu->addAction(actionExit);
m_desktopIntegration->setMenu(desktopIntegrationMenu);
#endif
const bool isHidden = m_desktopIntegration->isActive() && (startUpWindowState() == WindowState::Hidden);
const auto *pref = Preferences::instance();
#ifndef Q_OS_MACOS
const bool isHidden = m_desktopIntegration->isActive() && pref->startMinimized() && pref->minimizeToTray();
#else
const bool isHidden = false;
#endif
@@ -816,7 +766,7 @@ int Application::exec()
createStartupProgressDialog();
// Add a small delay to avoid "flashing" the progress dialog in case there are not many torrents to restore.
m_startupProgressDialog->setMinimumDuration(1000);
if (startUpWindowState() != WindowState::Normal)
if (pref->startMinimized())
m_startupProgressDialog->setWindowState(Qt::WindowMinimized);
}
else
@@ -869,26 +819,20 @@ int Application::exec()
});
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
#ifndef Q_OS_MACOS
const WindowState windowState = !m_startupProgressDialog ? WindowState::Hidden
: (m_startupProgressDialog->windowState() & Qt::WindowMinimized) ? WindowState::Minimized
: WindowState::Normal;
#else
const WindowState windowState = (m_startupProgressDialog->windowState() & Qt::WindowMinimized)
? WindowState::Minimized : WindowState::Normal;
#endif
const MainWindow::State windowState = (!m_startupProgressDialog || (m_startupProgressDialog->windowState() & Qt::WindowMinimized))
? MainWindow::Minimized : MainWindow::Normal;
m_window = new MainWindow(this, windowState);
delete m_startupProgressDialog;
#ifdef Q_OS_WIN
auto *pref = Preferences::instance();
if (!pref->neverCheckFileAssoc() && (!Utils::OS::isTorrentFileAssocSet() || !Utils::OS::isMagnetLinkAssocSet()))
if (!pref->neverCheckFileAssoc() && (!Preferences::isTorrentFileAssocSet() || !Preferences::isMagnetLinkAssocSet()))
{
if (QMessageBox::question(m_window, tr("Torrent file association")
, tr("qBittorrent is not the default application for opening torrent files or Magnet links.\nDo you want to make qBittorrent the default application for these?")
, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes)
{
Utils::OS::setTorrentFileAssoc(true);
Utils::OS::setMagnetLinkAssoc(true);
pref->setTorrentFileAssoc(true);
pref->setMagnetLinkAssoc(true);
}
else
{
@@ -907,16 +851,16 @@ int Application::exec()
const Preferences *pref = Preferences::instance();
const auto scheme = pref->isWebUiHttpsEnabled() ? u"https"_s : u"http"_s;
const auto url = u"%1://localhost:%2\n"_s.arg(scheme, QString::number(pref->getWebUiPort()));
const QString mesg = u"\n******** %1 ********\n"_s.arg(tr("Information"))
const auto scheme = pref->isWebUiHttpsEnabled() ? u"https"_qs : u"http"_qs;
const auto url = u"%1://localhost:%2\n"_qs.arg(scheme, QString::number(pref->getWebUiPort()));
const QString mesg = u"\n******** %1 ********\n"_qs.arg(tr("Information"))
+ tr("To control qBittorrent, access the WebUI at: %1").arg(url);
printf("%s\n", qUtf8Printable(mesg));
if (pref->getWebUIPassword() == QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ=="))
{
const QString warning = tr("The Web UI administrator username is: %1").arg(pref->getWebUiUsername()) + u'\n'
+ tr("The Web UI administrator password has not been changed from the default: %1").arg(u"adminadmin"_s) + u'\n'
+ tr("The Web UI administrator password has not been changed from the default: %1").arg(u"adminadmin"_qs) + u'\n'
+ tr("This is a security risk, please change your password in program preferences.") + u'\n';
printf("%s", qUtf8Printable(warning));
}
@@ -924,17 +868,31 @@ int Application::exec()
#endif // DISABLE_WEBUI
m_isProcessingParamsAllowed = true;
for (const QBtCommandLineParameters &params : m_paramsQueue)
for (const AddTorrentParams &params : m_paramsQueue)
processParams(params);
m_paramsQueue.clear();
});
const QBtCommandLineParameters params = commandLineArgs();
if (!params.torrentSources.isEmpty())
m_paramsQueue.append(params);
if (!params.isEmpty())
m_paramsQueue.append(parseParams(params));
return BaseApplication::exec();
}
catch (const RuntimeError &err)
{
#ifdef DISABLE_GUI
fprintf(stderr, "%s", qPrintable(err.message()));
#else
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Critical);
msgBox.setText(QCoreApplication::translate("Application", "Application failed to start."));
msgBox.setInformativeText(err.message());
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec();
#endif
return EXIT_FAILURE;
}
bool Application::isRunning()
{
@@ -1002,8 +960,7 @@ bool Application::event(QEvent *ev)
path = static_cast<QFileOpenEvent *>(ev)->url().toString();
qDebug("Received a mac file open event: %s", qUtf8Printable(path));
QBtCommandLineParameters params;
params.torrentSources.append(path);
const AddTorrentParams params = parseParams({path});
// If Application is not allowed to process params immediately
// (i.e., other components are not ready) store params
if (m_isProcessingParamsAllowed)
@@ -1110,22 +1067,7 @@ void Application::applyMemoryWorkingSetLimit() const
if (::getrlimit(RLIMIT_RSS, &limit) != 0)
return;
const size_t newSize = memoryWorkingSetLimit() * MiB;
if (newSize > limit.rlim_max)
{
// try to raise the hard limit
rlimit newLimit = limit;
newLimit.rlim_max = newSize;
if (::setrlimit(RLIMIT_RSS, &newLimit) != 0)
{
const auto message = QString::fromLocal8Bit(strerror(errno));
LogMsg(tr("Failed to set physical memory (RAM) usage hard limit. Requested size: %1. System hard limit: %2. Error code: %3. Error message: \"%4\"")
.arg(QString::number(newSize), QString::number(limit.rlim_max), QString::number(errno), message), Log::WARNING);
return;
}
}
limit.rlim_cur = newSize;
limit.rlim_cur = memoryWorkingSetLimit() * MiB;
if (::setrlimit(RLIMIT_RSS, &limit) != 0)
{
const auto message = QString::fromLocal8Bit(strerror(errno));
@@ -1153,12 +1095,12 @@ void Application::setProcessMemoryPriority(const MemoryPriority priority)
void Application::applyMemoryPriority() const
{
using SETPROCESSINFORMATION = BOOL (WINAPI *)(HANDLE, PROCESS_INFORMATION_CLASS, LPVOID, DWORD);
const auto setProcessInformation = Utils::Misc::loadWinAPI<SETPROCESSINFORMATION>(u"Kernel32.dll"_s, "SetProcessInformation");
const auto setProcessInformation = Utils::Misc::loadWinAPI<SETPROCESSINFORMATION>(u"Kernel32.dll"_qs, "SetProcessInformation");
if (!setProcessInformation) // only available on Windows >= 8
return;
using SETTHREADINFORMATION = BOOL (WINAPI *)(HANDLE, THREAD_INFORMATION_CLASS, LPVOID, DWORD);
const auto setThreadInformation = Utils::Misc::loadWinAPI<SETTHREADINFORMATION>(u"Kernel32.dll"_s, "SetThreadInformation");
const auto setThreadInformation = Utils::Misc::loadWinAPI<SETTHREADINFORMATION>(u"Kernel32.dll"_qs, "SetThreadInformation");
if (!setThreadInformation) // only available on Windows >= 8
return;

View File

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

View File

@@ -32,7 +32,6 @@
#include <cstdio>
#include <QCoreApplication>
#include <QDebug>
#include <QFileInfo>
#include <QProcessEnvironment>
@@ -152,16 +151,16 @@ namespace
{
QStringList parts = arg.split(u'=');
if (parts.size() == 2)
return Utils::String::unquote(parts[1], u"'\""_s);
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
return Utils::String::unquote(parts[1], u"'\""_qs);
throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=value'")
.arg(fullParameter(), u"<value>"_s));
.arg(fullParameter(), u"<value>"_qs));
}
QString value(const QProcessEnvironment &env, const QString &defaultValue = {}) const
{
QString val = env.value(envVarName());
return val.isEmpty() ? defaultValue : Utils::String::unquote(val, u"'\""_s);
return val.isEmpty() ? defaultValue : Utils::String::unquote(val, u"'\""_qs);
}
QString usage(const QString &valueName) const
@@ -199,15 +198,13 @@ namespace
int value(const QString &arg) const
{
const QString val = StringOption::value(arg);
QString val = StringOption::value(arg);
bool ok = false;
const int res = val.toInt(&ok);
int res = val.toInt(&ok);
if (!ok)
{
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=<value>'")
.arg(fullParameter(), u"<integer value>"_s));
}
.arg(fullParameter(), u"<integer value>"_qs));
return res;
}
@@ -220,7 +217,7 @@ namespace
int res = val.toInt(&ok);
if (!ok)
{
qDebug() << QCoreApplication::translate("CMD Options", "Expected integer number in environment variable '%1', but got '%2'")
qDebug() << QObject::tr("Expected integer number in environment variable '%1', but got '%2'")
.arg(envVarName(), val);
return defaultValue;
}
@@ -276,15 +273,15 @@ namespace
}
}
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--add-paused' must follow syntax "
"'--add-paused=<true|false>'")
.arg(fullParameter(), u"<true|false>"_s));
.arg(fullParameter(), u"<true|false>"_qs));
}
std::optional<bool> value(const QProcessEnvironment &env) const
{
const QString val = env.value(envVarName(), u"-1"_s);
const QString val = env.value(envVarName(), u"-1"_qs);
if (val.isEmpty())
{
@@ -303,8 +300,8 @@ namespace
return false;
}
qDebug() << QCoreApplication::translate("CMD Options", "Expected %1 in environment variable '%2', but got '%3'")
.arg(u"true|false"_s, envVarName(), val);
qDebug() << QObject::tr("Expected %1 in environment variable '%2', but got '%3'")
.arg(u"true|false"_qs, envVarName(), val);
return std::nullopt;
}
@@ -343,7 +340,14 @@ namespace
}
QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &env)
: relativeFastresumePaths(RELATIVE_FASTRESUME.value(env))
: showHelp(false)
, relativeFastresumePaths(RELATIVE_FASTRESUME.value(env))
, skipChecking(SKIP_HASH_CHECK_OPTION.value(env))
, sequential(SEQUENTIAL_OPTION.value(env))
, firstLastPiecePriority(FIRST_AND_LAST_OPTION.value(env))
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
, showVersion(false)
#endif
#ifndef DISABLE_GUI
, noSplash(NO_SPLASH_OPTION.value(env))
#elif !defined(Q_OS_WIN)
@@ -351,16 +355,49 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en
#endif
, webUiPort(WEBUI_PORT_OPTION.value(env, -1))
, torrentingPort(TORRENTING_PORT_OPTION.value(env, -1))
, addPaused(PAUSED_OPTION.value(env))
, skipDialog(SKIP_DIALOG_OPTION.value(env))
, profileDir(PROFILE_OPTION.value(env))
, configurationName(CONFIGURATION_OPTION.value(env))
, savePath(SAVE_PATH_OPTION.value(env))
, category(CATEGORY_OPTION.value(env))
{
addTorrentParams.savePath = Path(SAVE_PATH_OPTION.value(env));
addTorrentParams.category = CATEGORY_OPTION.value(env);
addTorrentParams.skipChecking = SKIP_HASH_CHECK_OPTION.value(env);
addTorrentParams.sequential = SEQUENTIAL_OPTION.value(env);
addTorrentParams.firstLastPiecePriority = FIRST_AND_LAST_OPTION.value(env);
addTorrentParams.addPaused = PAUSED_OPTION.value(env);
}
QStringList QBtCommandLineParameters::paramList() const
{
QStringList result;
// Because we're passing a string list to the currently running
// qBittorrent process, we need some way of passing along the options
// the user has specified. Here we place special strings that are
// almost certainly not going to collide with a file path or URL
// specified by the user, and placing them at the beginning of the
// string list so that they will be processed before the list of
// torrent paths or URLs.
if (!savePath.isEmpty())
result.append(u"@savePath=" + savePath.data());
if (addPaused.has_value())
result.append(*addPaused ? u"@addPaused=1"_qs : u"@addPaused=0"_qs);
if (skipChecking)
result.append(u"@skipChecking"_qs);
if (!category.isEmpty())
result.append(u"@category=" + category);
if (sequential)
result.append(u"@sequential"_qs);
if (firstLastPiecePriority)
result.append(u"@firstLastPiecePriority"_qs);
if (skipDialog.has_value())
result.append(*skipDialog ? u"@skipDialog=1"_qs : u"@skipDialog=0"_qs);
result += torrents;
return result;
}
QBtCommandLineParameters parseCommandLine(const QStringList &args)
@@ -389,16 +426,16 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
{
result.webUiPort = WEBUI_PORT_OPTION.value(arg);
if ((result.webUiPort < 1) || (result.webUiPort > 65535))
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).")
.arg(u"--webui-port"_s));
throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
.arg(u"--webui-port"_qs));
}
else if (arg == TORRENTING_PORT_OPTION)
{
result.torrentingPort = TORRENTING_PORT_OPTION.value(arg);
if ((result.torrentingPort < 1) || (result.torrentingPort > 65535))
{
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).")
.arg(u"--torrenting-port"_s));
throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
.arg(u"--torrenting-port"_qs));
}
}
#ifndef DISABLE_GUI
@@ -426,27 +463,27 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
}
else if (arg == SAVE_PATH_OPTION)
{
result.addTorrentParams.savePath = Path(SAVE_PATH_OPTION.value(arg));
result.savePath = Path(SAVE_PATH_OPTION.value(arg));
}
else if (arg == PAUSED_OPTION)
{
result.addTorrentParams.addPaused = PAUSED_OPTION.value(arg);
result.addPaused = PAUSED_OPTION.value(arg);
}
else if (arg == SKIP_HASH_CHECK_OPTION)
{
result.addTorrentParams.skipChecking = true;
result.skipChecking = true;
}
else if (arg == CATEGORY_OPTION)
{
result.addTorrentParams.category = CATEGORY_OPTION.value(arg);
result.category = CATEGORY_OPTION.value(arg);
}
else if (arg == SEQUENTIAL_OPTION)
{
result.addTorrentParams.sequential = true;
result.sequential = true;
}
else if (arg == FIRST_AND_LAST_OPTION)
{
result.addTorrentParams.firstLastPiecePriority = true;
result.firstLastPiecePriority = true;
}
else if (arg == SKIP_DIALOG_OPTION)
{
@@ -465,9 +502,9 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
torrentPath.setFile(arg);
if (torrentPath.exists())
result.torrentSources += torrentPath.absoluteFilePath();
result.torrents += torrentPath.absoluteFilePath();
else
result.torrentSources += arg;
result.torrents += arg;
}
}
@@ -500,58 +537,58 @@ QString makeUsage(const QString &prgName)
{
const QString indentation {USAGE_INDENTATION, u' '};
const QString text = QCoreApplication::translate("CMD Options", "Usage:") + u'\n'
+ indentation + prgName + u' ' + QCoreApplication::translate("CMD Options", "[options] [(<filename> | <url>)...]") + u'\n'
const QString text = QObject::tr("Usage:") + u'\n'
+ indentation + prgName + u' ' + QObject::tr("[options] [(<filename> | <url>)...]") + u'\n'
+ QCoreApplication::translate("CMD Options", "Options:") + u'\n'
+ QObject::tr("Options:") + u'\n'
#if !defined(Q_OS_WIN) || defined(DISABLE_GUI)
+ SHOW_VERSION_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display program version and exit")) + u'\n'
+ SHOW_VERSION_OPTION.usage() + wrapText(QObject::tr("Display program version and exit")) + u'\n'
#endif
+ SHOW_HELP_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display this help message and exit")) + u'\n'
+ WEBUI_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
+ wrapText(QCoreApplication::translate("CMD Options", "Change the Web UI port"))
+ SHOW_HELP_OPTION.usage() + wrapText(QObject::tr("Display this help message and exit")) + u'\n'
+ WEBUI_PORT_OPTION.usage(QObject::tr("port"))
+ wrapText(QObject::tr("Change the Web UI port"))
+ u'\n'
+ TORRENTING_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port"))
+ wrapText(QCoreApplication::translate("CMD Options", "Change the torrenting port"))
+ TORRENTING_PORT_OPTION.usage(QObject::tr("port"))
+ wrapText(QObject::tr("Change the torrenting port"))
+ u'\n'
#ifndef DISABLE_GUI
+ NO_SPLASH_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Disable splash screen")) + u'\n'
+ NO_SPLASH_OPTION.usage() + wrapText(QObject::tr("Disable splash screen")) + u'\n'
#elif !defined(Q_OS_WIN)
+ DAEMON_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Run in daemon-mode (background)")) + u'\n'
+ DAEMON_OPTION.usage() + wrapText(QObject::tr("Run in daemon-mode (background)")) + u'\n'
#endif
//: Use appropriate short form or abbreviation of "directory"
+ PROFILE_OPTION.usage(QCoreApplication::translate("CMD Options", "dir"))
+ wrapText(QCoreApplication::translate("CMD Options", "Store configuration files in <dir>")) + u'\n'
+ CONFIGURATION_OPTION.usage(QCoreApplication::translate("CMD Options", "name"))
+ wrapText(QCoreApplication::translate("CMD Options", "Store configuration files in directories qBittorrent_<name>")) + u'\n'
+ PROFILE_OPTION.usage(QObject::tr("dir"))
+ wrapText(QObject::tr("Store configuration files in <dir>")) + u'\n'
+ CONFIGURATION_OPTION.usage(QObject::tr("name"))
+ wrapText(QObject::tr("Store configuration files in directories qBittorrent_<name>")) + u'\n'
+ RELATIVE_FASTRESUME.usage()
+ wrapText(QCoreApplication::translate("CMD Options", "Hack into libtorrent fastresume files and make file paths relative "
+ wrapText(QObject::tr("Hack into libtorrent fastresume files and make file paths relative "
"to the profile directory")) + u'\n'
+ Option::padUsageText(QCoreApplication::translate("CMD Options", "files or URLs"))
+ wrapText(QCoreApplication::translate("CMD Options", "Download the torrents passed by the user")) + u'\n'
+ Option::padUsageText(QObject::tr("files or URLs"))
+ wrapText(QObject::tr("Download the torrents passed by the user")) + u'\n'
+ u'\n'
+ wrapText(QCoreApplication::translate("CMD Options", "Options when adding new torrents:"), 0) + u'\n'
+ SAVE_PATH_OPTION.usage(QCoreApplication::translate("CMD Options", "path")) + wrapText(QCoreApplication::translate("CMD Options", "Torrent save path")) + u'\n'
+ PAUSED_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Add torrents as started or paused")) + u'\n'
+ SKIP_HASH_CHECK_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Skip hash check")) + u'\n'
+ CATEGORY_OPTION.usage(QCoreApplication::translate("CMD Options", "name"))
+ wrapText(QCoreApplication::translate("CMD Options", "Assign torrents to category. If the category doesn't exist, it will be "
+ wrapText(QObject::tr("Options when adding new torrents:"), 0) + u'\n'
+ SAVE_PATH_OPTION.usage(QObject::tr("path")) + wrapText(QObject::tr("Torrent save path")) + u'\n'
+ PAUSED_OPTION.usage() + wrapText(QObject::tr("Add torrents as started or paused")) + u'\n'
+ SKIP_HASH_CHECK_OPTION.usage() + wrapText(QObject::tr("Skip hash check")) + u'\n'
+ CATEGORY_OPTION.usage(QObject::tr("name"))
+ wrapText(QObject::tr("Assign torrents to category. If the category doesn't exist, it will be "
"created.")) + u'\n'
+ SEQUENTIAL_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Download files in sequential order")) + u'\n'
+ SEQUENTIAL_OPTION.usage() + wrapText(QObject::tr("Download files in sequential order")) + u'\n'
+ FIRST_AND_LAST_OPTION.usage()
+ wrapText(QCoreApplication::translate("CMD Options", "Download first and last pieces first")) + u'\n'
+ wrapText(QObject::tr("Download first and last pieces first")) + u'\n'
+ SKIP_DIALOG_OPTION.usage()
+ wrapText(QCoreApplication::translate("CMD Options", "Specify whether the \"Add New Torrent\" dialog opens when adding a "
+ wrapText(QObject::tr("Specify whether the \"Add New Torrent\" dialog opens when adding a "
"torrent.")) + u'\n'
+ u'\n'
+ wrapText(QCoreApplication::translate("CMD Options", "Option values may be supplied via environment variables. For option named "
+ wrapText(QObject::tr("Option values may be supplied via environment variables. For option named "
"'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper "
"case, '-' replaced with '_'). To pass flag values, set the variable to '1' or "
"'TRUE'. For example, to disable the splash screen: "), 0) + u'\n'
+ u"QBT_NO_SPLASH=1 " + prgName + u'\n'
+ wrapText(QCoreApplication::translate("CMD Options", "Command line parameters take precedence over environment variables"), 0) + u'\n';
+ wrapText(QObject::tr("Command line parameters take precedence over environment variables"), 0) + u'\n';
return text;
}
@@ -559,7 +596,7 @@ QString makeUsage(const QString &prgName)
void displayUsage(const QString &prgName)
{
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
QMessageBox msgBox(QMessageBox::Information, QCoreApplication::translate("CMD Options", "Help"), makeUsage(prgName), QMessageBox::Ok);
QMessageBox msgBox(QMessageBox::Information, QObject::tr("Help"), makeUsage(prgName), QMessageBox::Ok);
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec();

View File

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

View File

@@ -78,7 +78,7 @@ void FileLogger::changePath(const Path &newPath)
closeLogFile();
m_path = newPath / Path(u"qbittorrent.log"_s);
m_path = newPath / Path(u"qbittorrent.log"_qs);
m_logFile.setFileName(m_path.data());
Utils::Fs::mkpath(newPath);
@@ -89,7 +89,7 @@ void FileLogger::deleteOld(const int age, const FileLogAgeType ageType)
{
const QDateTime date = QDateTime::currentDateTime();
const QDir dir {m_path.parentPath().data()};
const QFileInfoList fileList = dir.entryInfoList(QStringList(u"qbittorrent.log.bak*"_s)
const QFileInfoList fileList = dir.entryInfoList(QStringList(u"qbittorrent.log.bak*"_qs)
, (QDir::Files | QDir::Writable), (QDir::Time | QDir::Reversed));
for (const QFileInfo &file : fileList)

View File

@@ -45,8 +45,7 @@
#include <io.h>
#endif
#include <QCoreApplication>
#include <QString>
#include <QDebug>
#include <QThread>
#ifndef DISABLE_GUI
@@ -86,7 +85,6 @@ using namespace std::chrono_literals;
void displayVersion();
bool userAgreesWithLegalNotice();
void displayBadArgMessage(const QString &message);
void displayErrorMessage(const QString &message);
#ifndef DISABLE_GUI
void showSplashScreen();
@@ -115,12 +113,10 @@ int main(int argc, char *argv[])
Application::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
// `app` must be declared out of try block to allow display message box in case of exception
std::unique_ptr<Application> app;
try
{
// Create Application
app = std::make_unique<Application>(argc, argv);
auto app = std::make_unique<Application>(argc, argv);
#ifdef Q_OS_WIN
// QCoreApplication::applicationDirPath() needs an Application object instantiated first
@@ -130,13 +126,13 @@ int main(int argc, char *argv[])
if (envValue.isEmpty())
qputenv(envName, Application::applicationDirPath().toLocal8Bit());
else
qputenv(envName, u"%1;%2"_s.arg(envValue, Application::applicationDirPath()).toLocal8Bit());
qputenv(envName, u"%1;%2"_qs.arg(envValue, Application::applicationDirPath()).toLocal8Bit());
#endif
const QBtCommandLineParameters params = app->commandLineArgs();
if (!params.unknownParameter.isEmpty())
{
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 is an unknown command line parameter.",
throw CommandLineParameterError(QObject::tr("%1 is an unknown command line parameter.",
"--random-parameter is an unknown command line parameter.")
.arg(params.unknownParameter));
}
@@ -148,8 +144,8 @@ int main(int argc, char *argv[])
displayVersion();
return EXIT_SUCCESS;
}
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 must be the single command line parameter.")
.arg(u"-v (or --version)"_s));
throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
.arg(u"-v (or --version)"_qs));
}
#endif
if (params.showHelp)
@@ -159,8 +155,8 @@ int main(int argc, char *argv[])
displayUsage(QString::fromLocal8Bit(argv[0]));
return EXIT_SUCCESS;
}
throw CommandLineParameterError(QCoreApplication::translate("Main", "%1 must be the single command line parameter.")
.arg(u"-h (or --help)"_s));
throw CommandLineParameterError(QObject::tr("%1 must be the single command line parameter.")
.arg(u"-h (or --help)"_qs));
}
const bool firstTimeUser = !Preferences::instance()->getAcceptedLegal();
@@ -191,13 +187,13 @@ int main(int argc, char *argv[])
#if defined(DISABLE_GUI) && !defined(Q_OS_WIN)
if (params.shouldDaemonize)
{
throw CommandLineParameterError(QCoreApplication::translate("Main", "You cannot use %1: qBittorrent is already running for this user.")
.arg(u"-d (or --daemon)"_s));
throw CommandLineParameterError(QObject::tr("You cannot use %1: qBittorrent is already running for this user.")
.arg(u"-d (or --daemon)"_qs));
}
#endif
QThread::msleep(300);
app->callMainInstance();
app->sendParams(params.paramList());
return EXIT_SUCCESS;
}
@@ -262,7 +258,7 @@ int main(int argc, char *argv[])
registerSignalHandlers();
return app->exec();
return app->exec(params.paramList());
}
catch (const CommandLineParameterError &er)
{
@@ -271,7 +267,7 @@ int main(int argc, char *argv[])
}
catch (const RuntimeError &er)
{
displayErrorMessage(er.message());
qDebug() << er.message();
return EXIT_FAILURE;
}
}
@@ -279,11 +275,11 @@ int main(int argc, char *argv[])
#if !defined(DISABLE_GUI)
void showSplashScreen()
{
QPixmap splashImg(u":/icons/splash.png"_s);
QPixmap splashImg(u":/icons/splash.png"_qs);
QPainter painter(&splashImg);
const auto version = QStringLiteral(QBT_VERSION);
painter.setPen(QPen(Qt::white));
painter.setFont(QFont(u"Arial"_s, 22, QFont::Black));
painter.setFont(QFont(u"Arial"_qs, 22, QFont::Black));
painter.drawText(224 - painter.fontMetrics().horizontalAdvance(version), 270, version);
QSplashScreen *splash = new QSplashScreen(splashImg);
splash->show();
@@ -299,55 +295,31 @@ void displayVersion()
void displayBadArgMessage(const QString &message)
{
const QString help = QCoreApplication::translate("Main", "Run application with -h option to read about command line parameters.");
const QString help = QObject::tr("Run application with -h option to read about command line parameters.");
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
QMessageBox msgBox(QMessageBox::Critical, QCoreApplication::translate("Main", "Bad command line"),
QMessageBox msgBox(QMessageBox::Critical, QObject::tr("Bad command line"),
(message + u'\n' + help), QMessageBox::Ok);
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec();
#else
const QString errMsg = QCoreApplication::translate("Main", "Bad command line: ") + u'\n'
const QString errMsg = QObject::tr("Bad command line: ") + u'\n'
+ message + u'\n'
+ help + u'\n';
fprintf(stderr, "%s", qUtf8Printable(errMsg));
#endif
}
void displayErrorMessage(const QString &message)
{
#ifndef DISABLE_GUI
if (QApplication::instance())
{
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Critical);
msgBox.setText(QCoreApplication::translate("Main", "An unrecoverable error occurred."));
msgBox.setInformativeText(message);
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec();
}
else
{
const QString errMsg = QCoreApplication::translate("Main", "qBittorrent has encountered an unrecoverable error.") + u'\n' + message + u'\n';
fprintf(stderr, "%s", qUtf8Printable(errMsg));
}
#else
const QString errMsg = QCoreApplication::translate("Main", "qBittorrent has encountered an unrecoverable error.") + u'\n' + message + u'\n';
fprintf(stderr, "%s", qUtf8Printable(errMsg));
#endif
}
bool userAgreesWithLegalNotice()
{
Preferences *const pref = Preferences::instance();
Q_ASSERT(!pref->getAcceptedLegal());
#ifdef DISABLE_GUI
const QString eula = u"\n*** %1 ***\n"_s.arg(QCoreApplication::translate("Main", "Legal Notice"))
+ QCoreApplication::translate("Main", "qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.") + u"\n\n"
+ QCoreApplication::translate("Main", "No further notices will be issued.") + u"\n\n"
+ QCoreApplication::translate("Main", "Press %1 key to accept and continue...").arg(u"'y'"_s) + u'\n';
const QString eula = u"\n*** %1 ***\n"_qs.arg(QObject::tr("Legal Notice"))
+ QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.") + u"\n\n"
+ QObject::tr("No further notices will be issued.") + u"\n\n"
+ QObject::tr("Press %1 key to accept and continue...").arg(u"'y'"_qs) + u'\n';
printf("%s", qUtf8Printable(eula));
const char ret = getchar(); // Read pressed key
@@ -359,10 +331,10 @@ bool userAgreesWithLegalNotice()
}
#else
QMessageBox msgBox;
msgBox.setText(QCoreApplication::translate("Main", "qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.\n\nNo further notices will be issued."));
msgBox.setWindowTitle(QCoreApplication::translate("Main", "Legal notice"));
msgBox.addButton(QCoreApplication::translate("Main", "Cancel"), QMessageBox::RejectRole);
const QAbstractButton *agreeButton = msgBox.addButton(QCoreApplication::translate("Main", "I Agree"), QMessageBox::AcceptRole);
msgBox.setText(QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.\n\nNo further notices will be issued."));
msgBox.setWindowTitle(QObject::tr("Legal notice"));
msgBox.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
const QAbstractButton *agreeButton = msgBox.addButton(QObject::tr("I Agree"), QMessageBox::AcceptRole);
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec();

View File

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

View File

@@ -43,7 +43,6 @@
#endif
#include <QCoreApplication>
#include <QMetaObject>
#include "base/version.h"
@@ -90,14 +89,14 @@ namespace
const char *msgs[] = {"Catching signal: ", sysSigName[signum], "\nExiting cleanly\n"};
std::for_each(std::begin(msgs), std::end(msgs), safePrint);
signal(signum, SIG_DFL);
QMetaObject::invokeMethod(qApp, [] { QCoreApplication::exit(); }, Qt::QueuedConnection); // unsafe, but exit anyway
QCoreApplication::exit(); // unsafe, but exit anyway
}
#ifdef STACKTRACE
void abnormalExitHandler(const int signum)
{
const char msg[] = "\n\n*************************************************************\n"
"Please file a bug report at https://bug.qbittorrent.org and provide the following information:\n\n"
"Please file a bug report at http://bug.qbittorrent.org and provide the following information:\n\n"
"qBittorrent version: " QBT_VERSION "\n\n"
"Caught signal: ";
const char *sigName = sysSigName[signum];

View File

@@ -29,7 +29,6 @@
#include "upgrade.h"
#include <QtGlobal>
#include <QCoreApplication>
#include <QMetaEnum>
#include "base/bittorrent/torrentcontentlayout.h"
@@ -40,13 +39,14 @@
#include "base/profile.h"
#include "base/settingsstorage.h"
#include "base/settingvalue.h"
#include "base/utils/fs.h"
#include "base/utils/io.h"
#include "base/utils/string.h"
namespace
{
const int MIGRATION_VERSION = 6;
const QString MIGRATION_VERSION_KEY = u"Meta/MigrationVersion"_s;
const int MIGRATION_VERSION = 4;
const QString MIGRATION_VERSION_KEY = u"Meta/MigrationVersion"_qs;
void exportWebUIHttpsFiles()
{
@@ -55,7 +55,7 @@ namespace
SettingsStorage *settingsStorage {SettingsStorage::instance()};
const auto oldData {settingsStorage->loadValue<QByteArray>(oldKey)};
const auto newData {settingsStorage->loadValue<QString>(newKey)};
const QString errorMsgFormat {QCoreApplication::translate("Upgrade", "Migrate preferences failed: WebUI https, file: \"%1\", error: \"%2\"")};
const QString errorMsgFormat {QObject::tr("Migrate preferences failed: WebUI https, file: \"%1\", error: \"%2\"")};
if (!newData.isEmpty() || oldData.isEmpty())
return;
@@ -70,23 +70,23 @@ namespace
settingsStorage->storeValue(newKey, savePath);
settingsStorage->removeValue(oldKey);
LogMsg(QCoreApplication::translate("Upgrade", "Migrated preferences: WebUI https, exported data to file: \"%1\"").arg(savePath.toString())
LogMsg(QObject::tr("Migrated preferences: WebUI https, exported data to file: \"%1\"").arg(savePath.toString())
, Log::INFO);
};
const Path configPath = specialFolderLocation(SpecialFolder::Config);
migrate(u"Preferences/WebUI/HTTPS/Certificate"_s
, u"Preferences/WebUI/HTTPS/CertificatePath"_s
, (configPath / Path(u"WebUICertificate.crt"_s)));
migrate(u"Preferences/WebUI/HTTPS/Key"_s
, u"Preferences/WebUI/HTTPS/KeyPath"_s
, (configPath / Path(u"WebUIPrivateKey.pem"_s)));
migrate(u"Preferences/WebUI/HTTPS/Certificate"_qs
, u"Preferences/WebUI/HTTPS/CertificatePath"_qs
, (configPath / Path(u"WebUICertificate.crt"_qs)));
migrate(u"Preferences/WebUI/HTTPS/Key"_qs
, u"Preferences/WebUI/HTTPS/KeyPath"_qs
, (configPath / Path(u"WebUIPrivateKey.pem"_qs)));
}
void upgradeTorrentContentLayout()
{
const QString oldKey = u"BitTorrent/Session/CreateTorrentSubfolder"_s;
const QString newKey = u"BitTorrent/Session/TorrentContentLayout"_s;
const QString oldKey = u"BitTorrent/Session/CreateTorrentSubfolder"_qs;
const QString newKey = u"BitTorrent/Session/TorrentContentLayout"_qs;
SettingsStorage *settingsStorage {SettingsStorage::instance()};
const auto oldData {settingsStorage->loadValue<QVariant>(oldKey)};
@@ -105,8 +105,8 @@ namespace
void upgradeListenPortSettings()
{
const auto oldKey = u"BitTorrent/Session/UseRandomPort"_s;
const auto newKey = u"Preferences/Connection/PortRangeMin"_s;
const auto oldKey = u"BitTorrent/Session/UseRandomPort"_qs;
const auto newKey = u"Preferences/Connection/PortRangeMin"_qs;
auto *settingsStorage = SettingsStorage::instance();
if (settingsStorage->hasKey(oldKey))
@@ -121,7 +121,7 @@ namespace
void upgradeSchedulerDaysSettings()
{
auto *settingsStorage = SettingsStorage::instance();
const auto key = u"Preferences/Scheduler/days"_s;
const auto key = u"Preferences/Scheduler/days"_qs;
const auto value = settingsStorage->loadValue<QString>(key);
bool ok = false;
@@ -162,7 +162,7 @@ namespace
settingsStorage->storeValue(key, Scheduler::Days::Sunday);
break;
default:
LogMsg(QCoreApplication::translate("Upgrade", "Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;
@@ -173,7 +173,7 @@ namespace
void upgradeDNSServiceSettings()
{
auto *settingsStorage = SettingsStorage::instance();
const auto key = u"Preferences/DynDNS/Service"_s;
const auto key = u"Preferences/DynDNS/Service"_qs;
const auto value = settingsStorage->loadValue<QString>(key);
bool ok = false;
@@ -193,7 +193,7 @@ namespace
settingsStorage->storeValue(key, DNS::Service::NoIP);
break;
default:
LogMsg(QCoreApplication::translate("Upgrade", "Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;
@@ -204,7 +204,7 @@ namespace
void upgradeTrayIconStyleSettings()
{
auto *settingsStorage = SettingsStorage::instance();
const auto key = u"Preferences/Advanced/TrayIconStyle"_s;
const auto key = u"Preferences/Advanced/TrayIconStyle"_qs;
const auto value = settingsStorage->loadValue<QString>(key);
bool ok = false;
@@ -224,7 +224,7 @@ namespace
settingsStorage->storeValue(key, TrayIcon::Style::MonoLight);
break;
default:
LogMsg(QCoreApplication::translate("Upgrade", "Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;
@@ -242,80 +242,80 @@ namespace
const KeyMapping mappings[] =
{
{u"AddNewTorrentDialog/Enabled"_s, u"Preferences/Downloads/NewAdditionDialog"_s},
{u"AddNewTorrentDialog/Expanded"_s, u"AddNewTorrentDialog/expanded"_s},
{u"AddNewTorrentDialog/Position"_s, u"AddNewTorrentDialog/y"_s},
{u"AddNewTorrentDialog/SavePathHistory"_s, u"TorrentAdditionDlg/save_path_history"_s},
{u"AddNewTorrentDialog/TopLevel"_s, u"Preferences/Downloads/NewAdditionDialogFront"_s},
{u"AddNewTorrentDialog/TreeHeaderState"_s, u"AddNewTorrentDialog/qt5/treeHeaderState"_s},
{u"AddNewTorrentDialog/Width"_s, u"AddNewTorrentDialog/width"_s},
{u"BitTorrent/Session/AddExtensionToIncompleteFiles"_s, u"Preferences/Downloads/UseIncompleteExtension"_s},
{u"BitTorrent/Session/AdditionalTrackers"_s, u"Preferences/Bittorrent/TrackersList"_s},
{u"BitTorrent/Session/AddTorrentPaused"_s, u"Preferences/Downloads/StartInPause"_s},
{u"BitTorrent/Session/AddTrackersEnabled"_s, u"Preferences/Bittorrent/AddTrackers"_s},
{u"BitTorrent/Session/AlternativeGlobalDLSpeedLimit"_s, u"Preferences/Connection/GlobalDLLimitAlt"_s},
{u"BitTorrent/Session/AlternativeGlobalUPSpeedLimit"_s, u"Preferences/Connection/GlobalUPLimitAlt"_s},
{u"BitTorrent/Session/AnnounceIP"_s, u"Preferences/Connection/InetAddress"_s},
{u"BitTorrent/Session/AnnounceToAllTrackers"_s, u"Preferences/Advanced/AnnounceToAllTrackers"_s},
{u"BitTorrent/Session/AnonymousModeEnabled"_s, u"Preferences/Advanced/AnonymousMode"_s},
{u"BitTorrent/Session/BandwidthSchedulerEnabled"_s, u"Preferences/Scheduler/Enabled"_s},
{u"BitTorrent/Session/DefaultSavePath"_s, u"Preferences/Downloads/SavePath"_s},
{u"BitTorrent/Session/DHTEnabled"_s, u"Preferences/Bittorrent/DHT"_s},
{u"BitTorrent/Session/DiskCacheSize"_s, u"Preferences/Downloads/DiskWriteCacheSize"_s},
{u"BitTorrent/Session/DiskCacheTTL"_s, u"Preferences/Downloads/DiskWriteCacheTTL"_s},
{u"BitTorrent/Session/Encryption"_s, u"Preferences/Bittorrent/Encryption"_s},
{u"BitTorrent/Session/FinishedTorrentExportDirectory"_s, u"Preferences/Downloads/FinishedTorrentExportDir"_s},
{u"BitTorrent/Session/ForceProxy"_s, u"Preferences/Connection/ProxyForce"_s},
{u"BitTorrent/Session/GlobalDLSpeedLimit"_s, u"Preferences/Connection/GlobalDLLimit"_s},
{u"BitTorrent/Session/GlobalMaxRatio"_s, u"Preferences/Bittorrent/MaxRatio"_s},
{u"BitTorrent/Session/GlobalUPSpeedLimit"_s, u"Preferences/Connection/GlobalUPLimit"_s},
{u"BitTorrent/Session/IgnoreLimitsOnLAN"_s, u"Preferences/Advanced/IgnoreLimitsLAN"_s},
{u"BitTorrent/Session/IgnoreSlowTorrentsForQueueing"_s, u"Preferences/Queueing/IgnoreSlowTorrents"_s},
{u"BitTorrent/Session/IncludeOverheadInLimits"_s, u"Preferences/Advanced/IncludeOverhead"_s},
{u"BitTorrent/Session/Interface"_s, u"Preferences/Connection/Interface"_s},
{u"BitTorrent/Session/InterfaceAddress"_s, u"Preferences/Connection/InterfaceAddress"_s},
{u"BitTorrent/Session/InterfaceName"_s, u"Preferences/Connection/InterfaceName"_s},
{u"BitTorrent/Session/IPFilter"_s, u"Preferences/IPFilter/File"_s},
{u"BitTorrent/Session/IPFilteringEnabled"_s, u"Preferences/IPFilter/Enabled"_s},
{u"BitTorrent/Session/LSDEnabled"_s, u"Preferences/Bittorrent/LSD"_s},
{u"BitTorrent/Session/MaxActiveDownloads"_s, u"Preferences/Queueing/MaxActiveDownloads"_s},
{u"BitTorrent/Session/MaxActiveTorrents"_s, u"Preferences/Queueing/MaxActiveTorrents"_s},
{u"BitTorrent/Session/MaxActiveUploads"_s, u"Preferences/Queueing/MaxActiveUploads"_s},
{u"BitTorrent/Session/MaxConnections"_s, u"Preferences/Bittorrent/MaxConnecs"_s},
{u"BitTorrent/Session/MaxConnectionsPerTorrent"_s, u"Preferences/Bittorrent/MaxConnecsPerTorrent"_s},
{u"BitTorrent/Session/MaxHalfOpenConnections"_s, u"Preferences/Connection/MaxHalfOpenConnec"_s},
{u"BitTorrent/Session/MaxRatioAction"_s, u"Preferences/Bittorrent/MaxRatioAction"_s},
{u"BitTorrent/Session/MaxUploads"_s, u"Preferences/Bittorrent/MaxUploads"_s},
{u"BitTorrent/Session/MaxUploadsPerTorrent"_s, u"Preferences/Bittorrent/MaxUploadsPerTorrent"_s},
{u"BitTorrent/Session/OutgoingPortsMax"_s, u"Preferences/Advanced/OutgoingPortsMax"_s},
{u"BitTorrent/Session/OutgoingPortsMin"_s, u"Preferences/Advanced/OutgoingPortsMin"_s},
{u"BitTorrent/Session/PeXEnabled"_s, u"Preferences/Bittorrent/PeX"_s},
{u"BitTorrent/Session/Port"_s, u"Preferences/Connection/PortRangeMin"_s},
{u"BitTorrent/Session/Preallocation"_s, u"Preferences/Downloads/PreAllocation"_s},
{u"BitTorrent/Session/ProxyPeerConnections"_s, u"Preferences/Connection/ProxyPeerConnections"_s},
{u"BitTorrent/Session/QueueingSystemEnabled"_s, u"Preferences/Queueing/QueueingEnabled"_s},
{u"BitTorrent/Session/RefreshInterval"_s, u"Preferences/General/RefreshInterval"_s},
{u"BitTorrent/Session/SaveResumeDataInterval"_s, u"Preferences/Downloads/SaveResumeDataInterval"_s},
{u"BitTorrent/Session/SuperSeedingEnabled"_s, u"Preferences/Advanced/SuperSeeding"_s},
{u"BitTorrent/Session/TempPath"_s, u"Preferences/Downloads/TempPath"_s},
{u"BitTorrent/Session/TempPathEnabled"_s, u"Preferences/Downloads/TempPathEnabled"_s},
{u"BitTorrent/Session/TorrentExportDirectory"_s, u"Preferences/Downloads/TorrentExportDir"_s},
{u"BitTorrent/Session/TrackerFilteringEnabled"_s, u"Preferences/IPFilter/FilterTracker"_s},
{u"BitTorrent/Session/UseAlternativeGlobalSpeedLimit"_s, u"Preferences/Connection/alt_speeds_on"_s},
{u"BitTorrent/Session/UseOSCache"_s, u"Preferences/Advanced/osCache"_s},
{u"BitTorrent/Session/UseRandomPort"_s, u"Preferences/General/UseRandomPort"_s},
{u"BitTorrent/Session/uTPEnabled"_s, u"Preferences/Bittorrent/uTP"_s},
{u"BitTorrent/Session/uTPRateLimited"_s, u"Preferences/Bittorrent/uTP_rate_limited"_s},
{u"BitTorrent/TrackerEnabled"_s, u"Preferences/Advanced/trackerEnabled"_s},
{u"Network/PortForwardingEnabled"_s, u"Preferences/Connection/UPnP"_s},
{u"Network/Proxy/Authentication"_s, u"Preferences/Connection/Proxy/Authentication"_s},
{u"Network/Proxy/IP"_s, u"Preferences/Connection/Proxy/IP"_s},
{u"Network/Proxy/OnlyForTorrents"_s, u"Preferences/Connection/ProxyOnlyForTorrents"_s},
{u"Network/Proxy/Password"_s, u"Preferences/Connection/Proxy/Password"_s},
{u"Network/Proxy/Port"_s, u"Preferences/Connection/Proxy/Port"_s},
{u"Network/Proxy/Type"_s, u"Preferences/Connection/ProxyType"_s},
{u"Network/Proxy/Username"_s, u"Preferences/Connection/Proxy/Username"_s},
{u"State/BannedIPs"_s, u"Preferences/IPFilter/BannedIPs"_s}
{u"AddNewTorrentDialog/Enabled"_qs, u"Preferences/Downloads/NewAdditionDialog"_qs},
{u"AddNewTorrentDialog/Expanded"_qs, u"AddNewTorrentDialog/expanded"_qs},
{u"AddNewTorrentDialog/Position"_qs, u"AddNewTorrentDialog/y"_qs},
{u"AddNewTorrentDialog/SavePathHistory"_qs, u"TorrentAdditionDlg/save_path_history"_qs},
{u"AddNewTorrentDialog/TopLevel"_qs, u"Preferences/Downloads/NewAdditionDialogFront"_qs},
{u"AddNewTorrentDialog/TreeHeaderState"_qs, u"AddNewTorrentDialog/qt5/treeHeaderState"_qs},
{u"AddNewTorrentDialog/Width"_qs, u"AddNewTorrentDialog/width"_qs},
{u"BitTorrent/Session/AddExtensionToIncompleteFiles"_qs, u"Preferences/Downloads/UseIncompleteExtension"_qs},
{u"BitTorrent/Session/AdditionalTrackers"_qs, u"Preferences/Bittorrent/TrackersList"_qs},
{u"BitTorrent/Session/AddTorrentPaused"_qs, u"Preferences/Downloads/StartInPause"_qs},
{u"BitTorrent/Session/AddTrackersEnabled"_qs, u"Preferences/Bittorrent/AddTrackers"_qs},
{u"BitTorrent/Session/AlternativeGlobalDLSpeedLimit"_qs, u"Preferences/Connection/GlobalDLLimitAlt"_qs},
{u"BitTorrent/Session/AlternativeGlobalUPSpeedLimit"_qs, u"Preferences/Connection/GlobalUPLimitAlt"_qs},
{u"BitTorrent/Session/AnnounceIP"_qs, u"Preferences/Connection/InetAddress"_qs},
{u"BitTorrent/Session/AnnounceToAllTrackers"_qs, u"Preferences/Advanced/AnnounceToAllTrackers"_qs},
{u"BitTorrent/Session/AnonymousModeEnabled"_qs, u"Preferences/Advanced/AnonymousMode"_qs},
{u"BitTorrent/Session/BandwidthSchedulerEnabled"_qs, u"Preferences/Scheduler/Enabled"_qs},
{u"BitTorrent/Session/DefaultSavePath"_qs, u"Preferences/Downloads/SavePath"_qs},
{u"BitTorrent/Session/DHTEnabled"_qs, u"Preferences/Bittorrent/DHT"_qs},
{u"BitTorrent/Session/DiskCacheSize"_qs, u"Preferences/Downloads/DiskWriteCacheSize"_qs},
{u"BitTorrent/Session/DiskCacheTTL"_qs, u"Preferences/Downloads/DiskWriteCacheTTL"_qs},
{u"BitTorrent/Session/Encryption"_qs, u"Preferences/Bittorrent/Encryption"_qs},
{u"BitTorrent/Session/FinishedTorrentExportDirectory"_qs, u"Preferences/Downloads/FinishedTorrentExportDir"_qs},
{u"BitTorrent/Session/ForceProxy"_qs, u"Preferences/Connection/ProxyForce"_qs},
{u"BitTorrent/Session/GlobalDLSpeedLimit"_qs, u"Preferences/Connection/GlobalDLLimit"_qs},
{u"BitTorrent/Session/GlobalMaxRatio"_qs, u"Preferences/Bittorrent/MaxRatio"_qs},
{u"BitTorrent/Session/GlobalUPSpeedLimit"_qs, u"Preferences/Connection/GlobalUPLimit"_qs},
{u"BitTorrent/Session/IgnoreLimitsOnLAN"_qs, u"Preferences/Advanced/IgnoreLimitsLAN"_qs},
{u"BitTorrent/Session/IgnoreSlowTorrentsForQueueing"_qs, u"Preferences/Queueing/IgnoreSlowTorrents"_qs},
{u"BitTorrent/Session/IncludeOverheadInLimits"_qs, u"Preferences/Advanced/IncludeOverhead"_qs},
{u"BitTorrent/Session/Interface"_qs, u"Preferences/Connection/Interface"_qs},
{u"BitTorrent/Session/InterfaceAddress"_qs, u"Preferences/Connection/InterfaceAddress"_qs},
{u"BitTorrent/Session/InterfaceName"_qs, u"Preferences/Connection/InterfaceName"_qs},
{u"BitTorrent/Session/IPFilter"_qs, u"Preferences/IPFilter/File"_qs},
{u"BitTorrent/Session/IPFilteringEnabled"_qs, u"Preferences/IPFilter/Enabled"_qs},
{u"BitTorrent/Session/LSDEnabled"_qs, u"Preferences/Bittorrent/LSD"_qs},
{u"BitTorrent/Session/MaxActiveDownloads"_qs, u"Preferences/Queueing/MaxActiveDownloads"_qs},
{u"BitTorrent/Session/MaxActiveTorrents"_qs, u"Preferences/Queueing/MaxActiveTorrents"_qs},
{u"BitTorrent/Session/MaxActiveUploads"_qs, u"Preferences/Queueing/MaxActiveUploads"_qs},
{u"BitTorrent/Session/MaxConnections"_qs, u"Preferences/Bittorrent/MaxConnecs"_qs},
{u"BitTorrent/Session/MaxConnectionsPerTorrent"_qs, u"Preferences/Bittorrent/MaxConnecsPerTorrent"_qs},
{u"BitTorrent/Session/MaxHalfOpenConnections"_qs, u"Preferences/Connection/MaxHalfOpenConnec"_qs},
{u"BitTorrent/Session/MaxRatioAction"_qs, u"Preferences/Bittorrent/MaxRatioAction"_qs},
{u"BitTorrent/Session/MaxUploads"_qs, u"Preferences/Bittorrent/MaxUploads"_qs},
{u"BitTorrent/Session/MaxUploadsPerTorrent"_qs, u"Preferences/Bittorrent/MaxUploadsPerTorrent"_qs},
{u"BitTorrent/Session/OutgoingPortsMax"_qs, u"Preferences/Advanced/OutgoingPortsMax"_qs},
{u"BitTorrent/Session/OutgoingPortsMin"_qs, u"Preferences/Advanced/OutgoingPortsMin"_qs},
{u"BitTorrent/Session/PeXEnabled"_qs, u"Preferences/Bittorrent/PeX"_qs},
{u"BitTorrent/Session/Port"_qs, u"Preferences/Connection/PortRangeMin"_qs},
{u"BitTorrent/Session/Preallocation"_qs, u"Preferences/Downloads/PreAllocation"_qs},
{u"BitTorrent/Session/ProxyPeerConnections"_qs, u"Preferences/Connection/ProxyPeerConnections"_qs},
{u"BitTorrent/Session/QueueingSystemEnabled"_qs, u"Preferences/Queueing/QueueingEnabled"_qs},
{u"BitTorrent/Session/RefreshInterval"_qs, u"Preferences/General/RefreshInterval"_qs},
{u"BitTorrent/Session/SaveResumeDataInterval"_qs, u"Preferences/Downloads/SaveResumeDataInterval"_qs},
{u"BitTorrent/Session/SuperSeedingEnabled"_qs, u"Preferences/Advanced/SuperSeeding"_qs},
{u"BitTorrent/Session/TempPath"_qs, u"Preferences/Downloads/TempPath"_qs},
{u"BitTorrent/Session/TempPathEnabled"_qs, u"Preferences/Downloads/TempPathEnabled"_qs},
{u"BitTorrent/Session/TorrentExportDirectory"_qs, u"Preferences/Downloads/TorrentExportDir"_qs},
{u"BitTorrent/Session/TrackerFilteringEnabled"_qs, u"Preferences/IPFilter/FilterTracker"_qs},
{u"BitTorrent/Session/UseAlternativeGlobalSpeedLimit"_qs, u"Preferences/Connection/alt_speeds_on"_qs},
{u"BitTorrent/Session/UseOSCache"_qs, u"Preferences/Advanced/osCache"_qs},
{u"BitTorrent/Session/UseRandomPort"_qs, u"Preferences/General/UseRandomPort"_qs},
{u"BitTorrent/Session/uTPEnabled"_qs, u"Preferences/Bittorrent/uTP"_qs},
{u"BitTorrent/Session/uTPRateLimited"_qs, u"Preferences/Bittorrent/uTP_rate_limited"_qs},
{u"BitTorrent/TrackerEnabled"_qs, u"Preferences/Advanced/trackerEnabled"_qs},
{u"Network/PortForwardingEnabled"_qs, u"Preferences/Connection/UPnP"_qs},
{u"Network/Proxy/Authentication"_qs, u"Preferences/Connection/Proxy/Authentication"_qs},
{u"Network/Proxy/IP"_qs, u"Preferences/Connection/Proxy/IP"_qs},
{u"Network/Proxy/OnlyForTorrents"_qs, u"Preferences/Connection/ProxyOnlyForTorrents"_qs},
{u"Network/Proxy/Password"_qs, u"Preferences/Connection/Proxy/Password"_qs},
{u"Network/Proxy/Port"_qs, u"Preferences/Connection/Proxy/Port"_qs},
{u"Network/Proxy/Type"_qs, u"Preferences/Connection/ProxyType"_qs},
{u"Network/Proxy/Username"_qs, u"Preferences/Connection/Proxy/Username"_qs},
{u"State/BannedIPs"_qs, u"Preferences/IPFilter/BannedIPs"_qs}
};
auto *settingsStorage = SettingsStorage::instance();
@@ -333,7 +333,7 @@ namespace
void migrateProxySettingsEnum()
{
auto *settingsStorage = SettingsStorage::instance();
const auto key = u"Network/Proxy/Type"_s;
const auto key = u"Network/Proxy/Type"_qs;
const auto value = settingsStorage->loadValue<QString>(key);
bool ok = false;
@@ -353,58 +353,29 @@ namespace
settingsStorage->storeValue(key, Net::ProxyType::SOCKS5);
break;
case 3:
settingsStorage->storeValue(key, u"HTTP_PW"_s);
settingsStorage->storeValue(key, Net::ProxyType::HTTP_PW);
break;
case 4:
settingsStorage->storeValue(key, u"SOCKS5_PW"_s);
settingsStorage->storeValue(key, Net::ProxyType::SOCKS5_PW);
break;
case 5:
settingsStorage->storeValue(key, Net::ProxyType::SOCKS4);
break;
default:
LogMsg(QCoreApplication::translate("Upgrade", "Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
LogMsg(QObject::tr("Invalid value found in configuration file, reverting it to default. Key: \"%1\". Invalid value: \"%2\".")
.arg(key, QString::number(number)), Log::WARNING);
settingsStorage->removeValue(key);
break;
}
}
}
void migrateProxySettings()
{
auto *settingsStorage = SettingsStorage::instance();
const auto proxyType = settingsStorage->loadValue<QString>(u"Network/Proxy/Type"_s, u"None"_s);
const auto onlyForTorrents = settingsStorage->loadValue<bool>(u"Network/Proxy/OnlyForTorrents"_s)
|| (proxyType == u"SOCKS4");
settingsStorage->storeValue(u"Network/Proxy/Profiles/BitTorrent"_s, true);
settingsStorage->storeValue(u"Network/Proxy/Profiles/RSS"_s, !onlyForTorrents);
settingsStorage->storeValue(u"Network/Proxy/Profiles/Misc"_s, !onlyForTorrents);
if (proxyType == u"HTTP_PW"_s)
{
settingsStorage->storeValue(u"Network/Proxy/Type"_s, Net::ProxyType::HTTP);
settingsStorage->storeValue(u"Network/Proxy/AuthEnabled"_s, true);
}
else if (proxyType == u"SOCKS5_PW"_s)
{
settingsStorage->storeValue(u"Network/Proxy/Type"_s, Net::ProxyType::SOCKS5);
settingsStorage->storeValue(u"Network/Proxy/AuthEnabled"_s, true);
}
settingsStorage->removeValue(u"Network/Proxy/OnlyForTorrents"_s);
const auto proxyHostnameLookup = settingsStorage->loadValue<bool>(u"BitTorrent/Session/ProxyHostnameLookup"_s);
settingsStorage->storeValue(u"Network/Proxy/HostnameLookupEnabled"_s, proxyHostnameLookup);
settingsStorage->removeValue(u"BitTorrent/Session/ProxyHostnameLookup"_s);
}
#ifdef Q_OS_WIN
void migrateMemoryPrioritySettings()
{
auto *settingsStorage = SettingsStorage::instance();
const QString oldKey = u"BitTorrent/OSMemoryPriority"_s;
const QString newKey = u"Application/ProcessMemoryPriority"_s;
const QString oldKey = u"BitTorrent/OSMemoryPriority"_qs;
const QString newKey = u"Application/ProcessMemoryPriority"_qs;
if (settingsStorage->hasKey(oldKey))
{
@@ -414,27 +385,15 @@ namespace
}
#endif
void migrateStartupWindowState()
{
auto *settingsStorage = SettingsStorage::instance();
if (settingsStorage->hasKey(u"Preferences/General/StartMinimized"_s))
{
const auto startMinimized = settingsStorage->loadValue<bool>(u"Preferences/General/StartMinimized"_s);
const auto minimizeToTray = settingsStorage->loadValue<bool>(u"Preferences/General/MinimizeToTray"_s);
const QString windowState = startMinimized ? (minimizeToTray ? u"Hidden"_s : u"Minimized"_s) : u"Normal"_s;
settingsStorage->storeValue(u"GUI/StartUpWindowState"_s, windowState);
}
}
void migrateChineseLocale()
{
auto *settingsStorage = SettingsStorage::instance();
const auto key = u"Preferences/General/Locale"_s;
const auto key = u"Preferences/General/Locale"_qs;
if (settingsStorage->hasKey(key))
{
const auto locale = settingsStorage->loadValue<QString>(key);
if (locale.compare(u"zh"_s, Qt::CaseInsensitive) == 0)
settingsStorage->storeValue(key, u"zh_CN"_s);
if (locale.compare(u"zh"_qs, Qt::CaseInsensitive) == 0)
settingsStorage->storeValue(key, u"zh_CN"_qs);
}
}
}
@@ -466,15 +425,9 @@ bool upgrade()
migrateMemoryPrioritySettings();
#endif
if (version < 5)
{
migrateStartupWindowState();
migrateChineseLocale();
}
if (version < 6)
migrateProxySettings();
version = MIGRATION_VERSION;
}
@@ -497,7 +450,7 @@ void handleChangedDefaults(const DefaultPreferencesMode mode)
const DefaultValue changedDefaults[] =
{
{u"BitTorrent/Session/QueueingSystemEnabled"_s, true, false}
{u"BitTorrent/Session/QueueingSystemEnabled"_qs, true, false}
};
auto *settingsStorage = SettingsStorage::instance();

View File

@@ -14,7 +14,7 @@
#define expected_lite_MAJOR 0
#define expected_lite_MINOR 6
#define expected_lite_PATCH 3
#define expected_lite_PATCH 2
#define expected_lite_VERSION expected_STRINGIFY(expected_lite_MAJOR) "." expected_STRINGIFY(expected_lite_MINOR) "." expected_STRINGIFY(expected_lite_PATCH)
@@ -405,7 +405,7 @@ struct is_nothrow_swappable
};
} // namespace detail
// is [nothrow] swappable:
// is [nothow] swappable:
template< typename T >
struct is_swappable : decltype( detail::is_swappable::test<T>(0) ){};
@@ -1002,12 +1002,11 @@ public:
// x.x.5.2.4 Swap
template< typename U=E >
nsel_REQUIRES_R( void,
std17::is_swappable<U>::value
std17::is_swappable<E>::value
)
swap( unexpected_type & other ) noexcept (
std17::is_nothrow_swappable<U>::value
std17::is_nothrow_swappable<E>::value
)
{
using std::swap;
@@ -2165,24 +2164,10 @@ private:
// x.x.4.6 expected<>: comparison operators
template< typename T1, typename E1, typename T2, typename E2
nsel_REQUIRES_T(
!std::is_void<T1>::value && !std::is_void<T2>::value
)
>
template< typename T1, typename E1, typename T2, typename E2 >
constexpr bool operator==( expected<T1,E1> const & x, expected<T2,E2> const & y )
{
return bool(x) != bool(y) ? false : bool(x) ? *x == *y : x.error() == y.error();
}
template< typename T1, typename E1, typename T2, typename E2
nsel_REQUIRES_T(
std::is_void<T1>::value && std::is_void<T2>::value
)
>
constexpr bool operator==( expected<T1,E1> const & x, expected<T2,E2> const & y )
{
return bool(x) != bool(y) ? false : bool(x) || static_cast<bool>( x.error() == y.error() );
return bool(x) != bool(y) ? false : bool(x) == false ? x.error() == y.error() : *x == *y;
}
template< typename T1, typename E1, typename T2, typename E2 >
@@ -2191,6 +2176,12 @@ constexpr bool operator!=( expected<T1,E1> const & x, expected<T2,E2> const & y
return !(x == y);
}
template< typename E1, typename E2 >
constexpr bool operator==( expected<void,E1> const & x, expected<void,E1> const & y )
{
return bool(x) != bool(y) ? false : bool(x) == false ? x.error() == y.error() : true;
}
#if nsel_P0323R <= 2
template< typename T, typename E >
@@ -2221,21 +2212,13 @@ constexpr bool operator>=( expected<T,E> const & x, expected<T,E> const & y )
// x.x.4.7 expected: comparison with T
template< typename T1, typename E1, typename T2
nsel_REQUIRES_T(
!std::is_void<T1>::value
)
>
template< typename T1, typename E1, typename T2 >
constexpr bool operator==( expected<T1,E1> const & x, T2 const & v )
{
return bool(x) ? *x == v : false;
}
template< typename T1, typename E1, typename T2
nsel_REQUIRES_T(
!std::is_void<T1>::value
)
>
template< typename T1, typename E1, typename T2 >
constexpr bool operator==(T2 const & v, expected<T1,E1> const & x )
{
return bool(x) ? v == *x : false;

View File

@@ -34,7 +34,6 @@ add_library(qbt_base STATIC
bittorrent/sessionstatus.h
bittorrent/speedmonitor.h
bittorrent/torrent.h
bittorrent/torrentcontenthandler.h
bittorrent/torrentcontentlayout.h
bittorrent/torrentcreatorthread.h
bittorrent/torrentimpl.h
@@ -99,7 +98,6 @@ add_library(qbt_base STATIC
utils/io.h
utils/misc.h
utils/net.h
utils/os.h
utils/password.h
utils/random.h
utils/string.h
@@ -111,7 +109,6 @@ add_library(qbt_base STATIC
applicationcomponent.cpp
asyncfilestorage.cpp
bittorrent/abstractfilestorage.cpp
bittorrent/addtorrentparams.cpp
bittorrent/bandwidthscheduler.cpp
bittorrent/bencoderesumedatastorage.cpp
bittorrent/categoryoptions.cpp
@@ -132,7 +129,6 @@ add_library(qbt_base STATIC
bittorrent/sessionimpl.cpp
bittorrent/speedmonitor.cpp
bittorrent/torrent.cpp
bittorrent/torrentcontenthandler.cpp
bittorrent/torrentcreatorthread.cpp
bittorrent/torrentimpl.cpp
bittorrent/torrentinfo.cpp
@@ -185,7 +181,6 @@ add_library(qbt_base STATIC
utils/io.cpp
utils/misc.cpp
utils/net.cpp
utils/os.cpp
utils/password.cpp
utils/random.cpp
utils/string.cpp

View File

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

View File

@@ -37,7 +37,7 @@
AsyncFileStorage::AsyncFileStorage(const Path &storageFolderPath, QObject *parent)
: QObject(parent)
, m_storageDir(storageFolderPath)
, m_lockFile((m_storageDir / Path(u"storage.lock"_s)).data())
, m_lockFile((m_storageDir / Path(u"storage.lock"_qs)).data())
{
Q_ASSERT(m_storageDir.isAbsolute());

View File

@@ -34,7 +34,6 @@ HEADERS += \
$$PWD/bittorrent/speedmonitor.h \
$$PWD/bittorrent/torrent.h \
$$PWD/bittorrent/torrentcontentlayout.h \
$$PWD/bittorrent/torrentcontenthandler.h \
$$PWD/bittorrent/torrentcreatorthread.h \
$$PWD/bittorrent/torrentimpl.h \
$$PWD/bittorrent/torrentinfo.h \
@@ -99,7 +98,6 @@ HEADERS += \
$$PWD/utils/io.h \
$$PWD/utils/misc.h \
$$PWD/utils/net.h \
$$PWD/utils/os.h \
$$PWD/utils/password.h \
$$PWD/utils/random.h \
$$PWD/utils/string.h \
@@ -111,7 +109,6 @@ SOURCES += \
$$PWD/applicationcomponent.cpp \
$$PWD/asyncfilestorage.cpp \
$$PWD/bittorrent/abstractfilestorage.cpp \
$$PWD/bittorrent/addtorrentparams.cpp \
$$PWD/bittorrent/bandwidthscheduler.cpp \
$$PWD/bittorrent/bencoderesumedatastorage.cpp \
$$PWD/bittorrent/categoryoptions.cpp \
@@ -132,7 +129,6 @@ SOURCES += \
$$PWD/bittorrent/sessionimpl.cpp \
$$PWD/bittorrent/speedmonitor.cpp \
$$PWD/bittorrent/torrent.cpp \
$$PWD/bittorrent/torrentcontenthandler.h \
$$PWD/bittorrent/torrentcreatorthread.cpp \
$$PWD/bittorrent/torrentimpl.cpp \
$$PWD/bittorrent/torrentinfo.cpp \
@@ -185,7 +181,6 @@ SOURCES += \
$$PWD/utils/io.cpp \
$$PWD/utils/misc.cpp \
$$PWD/utils/net.cpp \
$$PWD/utils/os.cpp \
$$PWD/utils/password.cpp \
$$PWD/utils/random.cpp \
$$PWD/utils/string.cpp \

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,7 +36,6 @@
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QRegularExpression>
#include <QThread>
@@ -44,12 +43,12 @@
#include "base/exceptions.h"
#include "base/global.h"
#include "base/logger.h"
#include "base/preferences.h"
#include "base/profile.h"
#include "base/tagset.h"
#include "base/utils/fs.h"
#include "base/utils/io.h"
#include "base/utils/string.h"
#include "common.h"
#include "infohash.h"
#include "loadtorrentparams.h"
@@ -104,8 +103,8 @@ BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path,
.arg(path.toString()));
}
const QRegularExpression filenamePattern {u"^([A-Fa-f0-9]{40})\\.fastresume$"_s};
const QStringList filenames = QDir(path.data()).entryList(QStringList(u"*.fastresume"_s), QDir::Files, QDir::Unsorted);
const QRegularExpression filenamePattern {u"^([A-Fa-f0-9]{40})\\.fastresume$"_qs};
const QStringList filenames = QDir(path.data()).entryList(QStringList(u"*.fastresume"_qs), QDir::Files, QDir::Unsorted);
m_registeredTorrents.reserve(filenames.size());
for (const QString &filename : filenames)
@@ -115,7 +114,7 @@ BitTorrent::BencodeResumeDataStorage::BencodeResumeDataStorage(const Path &path,
m_registeredTorrents.append(TorrentID::fromString(rxMatch.captured(1)));
}
loadQueue(path / Path(u"queue"_s));
loadQueue(path / Path(u"queue"_qs));
qDebug() << "Registered torrents count: " << m_registeredTorrents.size();
@@ -134,21 +133,18 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::load(cons
const QString idString = id.toString();
const Path fastresumePath = path() / Path(idString + u".fastresume");
const Path torrentFilePath = path() / Path(idString + u".torrent");
const qint64 torrentSizeLimit = Preferences::instance()->getTorrentFileSizeLimit();
const auto resumeDataReadResult = Utils::IO::readFile(fastresumePath, torrentSizeLimit);
if (!resumeDataReadResult)
return nonstd::make_unexpected(resumeDataReadResult.error().message);
QFile resumeDataFile {fastresumePath.data()};
if (!resumeDataFile.open(QIODevice::ReadOnly))
return nonstd::make_unexpected(tr("Cannot read file %1: %2").arg(fastresumePath.toString(), resumeDataFile.errorString()));
const auto metadataReadResult = Utils::IO::readFile(torrentFilePath, torrentSizeLimit);
if (!metadataReadResult)
{
if (metadataReadResult.error().status != Utils::IO::ReadError::NotExist)
return nonstd::make_unexpected(metadataReadResult.error().message);
}
QFile metadataFile {torrentFilePath.data()};
if (metadataFile.exists() && !metadataFile.open(QIODevice::ReadOnly))
return nonstd::make_unexpected(tr("Cannot read file %1: %2").arg(torrentFilePath.toString(), metadataFile.errorString()));
const QByteArray data = resumeDataFile.readAll();
const QByteArray metadata = (metadataFile.isOpen() ? metadataFile.readAll() : "");
const QByteArray data = resumeDataReadResult.value();
const QByteArray metadata = metadataReadResult.value_or(QByteArray());
return loadTorrentResumeData(data, metadata);
}
@@ -166,8 +162,6 @@ void BitTorrent::BencodeResumeDataStorage::doLoadAll() const
void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
{
const int lineMaxLength = 48;
QFile queueFile {queueFilename.data()};
if (!queueFile.exists())
return;
@@ -178,11 +172,11 @@ void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
return;
}
const QRegularExpression hashPattern {u"^([A-Fa-f0-9]{40})$"_s};
const QRegularExpression hashPattern {u"^([A-Fa-f0-9]{40})$"_qs};
int start = 0;
while (true)
{
const auto line = QString::fromLatin1(queueFile.readLine(lineMaxLength).trimmed());
const auto line = QString::fromLatin1(queueFile.readLine().trimmed());
if (line.isEmpty())
break;
@@ -202,11 +196,9 @@ void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorrentResumeData(const QByteArray &data, const QByteArray &metadata) const
{
const auto *pref = Preferences::instance();
lt::error_code ec;
const lt::bdecode_node resumeDataRoot = lt::bdecode(data, ec
, nullptr, pref->getBdecodeDepthLimit(), pref->getBdecodeTokenLimit());
, nullptr, BENCODE_DEPTH_LIMIT, BENCODE_TOKEN_LIMIT);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse resume data: %1").arg(QString::fromStdString(ec.message())));
@@ -216,10 +208,9 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
LoadTorrentParams torrentParams;
torrentParams.category = fromLTString(resumeDataRoot.dict_find_string_value("qBt-category"));
torrentParams.name = fromLTString(resumeDataRoot.dict_find_string_value("qBt-name"));
torrentParams.hasFinishedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus");
torrentParams.hasSeedStatus = resumeDataRoot.dict_find_int_value("qBt-seedStatus");
torrentParams.firstLastPiecePriority = resumeDataRoot.dict_find_int_value("qBt-firstLastPiecePriority");
torrentParams.seedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-seedingTimeLimit", Torrent::USE_GLOBAL_SEEDING_TIME);
torrentParams.inactiveSeedingTimeLimit = resumeDataRoot.dict_find_int_value("qBt-inactiveSeedingTimeLimit", Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME);
torrentParams.savePath = Profile::instance()->fromPortablePath(
Path(fromLTString(resumeDataRoot.dict_find_string_value("qBt-savePath"))));
@@ -274,9 +265,8 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::loadTorre
if (!metadata.isEmpty())
{
const auto *pref = Preferences::instance();
const lt::bdecode_node torentInfoRoot = lt::bdecode(metadata, ec
, nullptr, pref->getBdecodeDepthLimit(), pref->getBdecodeTokenLimit());
, nullptr, BENCODE_DEPTH_LIMIT, BENCODE_TOKEN_LIMIT);
if (ec)
return nonstd::make_unexpected(tr("Cannot parse torrent info: %1").arg(QString::fromStdString(ec.message())));
@@ -378,7 +368,7 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
metadataDict.insert(dataDict.extract("created by"));
metadataDict.insert(dataDict.extract("comment"));
const Path torrentFilepath = m_resumeDataDir / Path(u"%1.torrent"_s.arg(id.toString()));
const Path torrentFilepath = m_resumeDataDir / Path(u"%1.torrent"_qs.arg(id.toString()));
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(torrentFilepath, metadata);
if (!result)
{
@@ -390,11 +380,10 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
data["qBt-ratioLimit"] = static_cast<int>(resumeData.ratioLimit * 1000);
data["qBt-seedingTimeLimit"] = resumeData.seedingTimeLimit;
data["qBt-inactiveSeedingTimeLimit"] = resumeData.inactiveSeedingTimeLimit;
data["qBt-category"] = resumeData.category.toStdString();
data["qBt-tags"] = setToEntryList(resumeData.tags);
data["qBt-name"] = resumeData.name.toStdString();
data["qBt-seedStatus"] = resumeData.hasFinishedStatus;
data["qBt-seedStatus"] = resumeData.hasSeedStatus;
data["qBt-contentLayout"] = Utils::String::fromEnum(resumeData.contentLayout).toStdString();
data["qBt-firstLastPiecePriority"] = resumeData.firstLastPiecePriority;
data["qBt-stopCondition"] = Utils::String::fromEnum(resumeData.stopCondition).toStdString();
@@ -405,7 +394,7 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
data["qBt-downloadPath"] = Profile::instance()->toPortablePath(resumeData.downloadPath).data().toStdString();
}
const Path resumeFilepath = m_resumeDataDir / Path(u"%1.fastresume"_s.arg(id.toString()));
const Path resumeFilepath = m_resumeDataDir / Path(u"%1.fastresume"_qs.arg(id.toString()));
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(resumeFilepath, data);
if (!result)
{
@@ -416,10 +405,10 @@ void BitTorrent::BencodeResumeDataStorage::Worker::store(const TorrentID &id, co
void BitTorrent::BencodeResumeDataStorage::Worker::remove(const TorrentID &id) const
{
const Path resumeFilename {u"%1.fastresume"_s.arg(id.toString())};
const Path resumeFilename {u"%1.fastresume"_qs.arg(id.toString())};
Utils::Fs::removeFile(m_resumeDataDir / resumeFilename);
const Path torrentFilename {u"%1.torrent"_s.arg(id.toString())};
const Path torrentFilename {u"%1.torrent"_qs.arg(id.toString())};
Utils::Fs::removeFile(m_resumeDataDir / torrentFilename);
}
@@ -430,7 +419,7 @@ void BitTorrent::BencodeResumeDataStorage::Worker::storeQueue(const QVector<Torr
for (const BitTorrent::TorrentID &torrentID : queue)
data += (torrentID.toString().toLatin1() + '\n');
const Path filepath = m_resumeDataDir / Path(u"queue"_s);
const Path filepath = m_resumeDataDir / Path(u"queue"_qs);
const nonstd::expected<void, QString> result = Utils::IO::saveToFile(filepath, data);
if (!result)
{

View File

@@ -33,8 +33,8 @@
#include "base/global.h"
const QString OPTION_SAVEPATH = u"save_path"_s;
const QString OPTION_DOWNLOADPATH = u"download_path"_s;
const QString OPTION_SAVEPATH = u"save_path"_qs;
const QString OPTION_DOWNLOADPATH = u"download_path"_qs;
BitTorrent::CategoryOptions BitTorrent::CategoryOptions::fromJSON(const QJsonObject &jsonObj)
{

View File

@@ -32,4 +32,8 @@
#include "base/global.h"
inline const QString QB_EXT = u".!qB"_s;
inline const QString QB_EXT = u".!qB"_qs;
inline const int MAX_TORRENT_SIZE = 100 * 1024 * 1024; // 100 MiB
inline const int BENCODE_DEPTH_LIMIT = 100;
inline const int BENCODE_TOKEN_LIMIT = 10'000'000;

View File

@@ -92,7 +92,7 @@ bool CustomDiskIOThread::async_write(lt::storage_index_t storage, const lt::peer
, const char *buf, std::shared_ptr<lt::disk_observer> diskObserver
, std::function<void (const lt::storage_error &)> handler, lt::disk_job_flags_t flags)
{
return m_nativeDiskIO->async_write(storage, peerRequest, buf, std::move(diskObserver), std::move(handler), flags);
return m_nativeDiskIO->async_write(storage, peerRequest, buf, diskObserver, std::move(handler), flags);
}
void CustomDiskIOThread::async_hash(lt::storage_index_t storage, lt::piece_index_t piece
@@ -120,11 +120,7 @@ void CustomDiskIOThread::async_move_storage(lt::storage_index_t storage, std::st
m_nativeDiskIO->async_move_storage(storage, path, flags
, [=, handler = std::move(handler)](lt::status_t status, const std::string &path, const lt::storage_error &error)
{
#if LIBTORRENT_VERSION_NUM < 20100
if ((status != lt::status_t::fatal_disk_error) && (status != lt::status_t::file_exist))
#else
if ((status != lt::disk_status::fatal_disk_error) && (status != lt::disk_status::file_exist))
#endif
m_storageData[storage].savePath = newSavePath;
handler(status, path, error);
@@ -141,7 +137,7 @@ void CustomDiskIOThread::async_check_files(lt::storage_index_t storage, const lt
, std::function<void (lt::status_t, const lt::storage_error &)> handler)
{
handleCompleteFiles(storage, m_storageData[storage].savePath);
m_nativeDiskIO->async_check_files(storage, resume_data, std::move(links), std::move(handler));
m_nativeDiskIO->async_check_files(storage, resume_data, links, std::move(handler));
}
void CustomDiskIOThread::async_stop_torrent(lt::storage_index_t storage, std::function<void ()> handler)
@@ -170,8 +166,8 @@ void CustomDiskIOThread::async_delete_files(lt::storage_index_t storage, lt::rem
void CustomDiskIOThread::async_set_file_priority(lt::storage_index_t storage, lt::aux::vector<lt::download_priority_t, lt::file_index_t> priorities
, std::function<void (const lt::storage_error &, lt::aux::vector<lt::download_priority_t, lt::file_index_t>)> handler)
{
m_nativeDiskIO->async_set_file_priority(storage, std::move(priorities)
, [=, handler = std::move(handler)](const lt::storage_error &error, const lt::aux::vector<lt::download_priority_t, lt::file_index_t> &priorities)
m_nativeDiskIO->async_set_file_priority(storage, priorities
, [=, handler = std::move(handler)](const lt::storage_error &error, lt::aux::vector<lt::download_priority_t, lt::file_index_t> priorities)
{
m_storageData[storage].filePriorities = priorities;
handler(error, priorities);

View File

@@ -28,8 +28,6 @@
#include "dbresumedatastorage.h"
#include <memory>
#include <queue>
#include <utility>
#include <libtorrent/bdecode.hpp>
@@ -40,8 +38,7 @@
#include <libtorrent/write_resume_data.hpp>
#include <QByteArray>
#include <QDebug>
#include <QMutex>
#include <QFile>
#include <QSet>
#include <QSqlDatabase>
#include <QSqlError>
@@ -49,69 +46,28 @@
#include <QSqlRecord>
#include <QThread>
#include <QVector>
#include <QWaitCondition>
#include "base/exceptions.h"
#include "base/global.h"
#include "base/logger.h"
#include "base/path.h"
#include "base/preferences.h"
#include "base/profile.h"
#include "base/utils/fs.h"
#include "base/utils/string.h"
#include "common.h"
#include "infohash.h"
#include "loadtorrentparams.h"
namespace
{
const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_s;
const QString DB_CONNECTION_NAME = u"ResumeDataStorage"_qs;
const int DB_VERSION = 5;
const int DB_VERSION = 3;
const QString DB_TABLE_META = u"meta"_s;
const QString DB_TABLE_TORRENTS = u"torrents"_s;
const QString DB_TABLE_META = u"meta"_qs;
const QString DB_TABLE_TORRENTS = u"torrents"_qs;
const QString META_VERSION = u"version"_s;
using namespace BitTorrent;
class Job
{
public:
virtual ~Job() = default;
virtual void perform(QSqlDatabase db) = 0;
};
class StoreJob final : public Job
{
public:
StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData);
void perform(QSqlDatabase db) override;
private:
const TorrentID m_torrentID;
const LoadTorrentParams m_resumeData;
};
class RemoveJob final : public Job
{
public:
explicit RemoveJob(const TorrentID &torrentID);
void perform(QSqlDatabase db) override;
private:
const TorrentID m_torrentID;
};
class StoreQueueJob final : public Job
{
public:
explicit StoreQueueJob(const QVector<TorrentID> &queue);
void perform(QSqlDatabase db) override;
private:
const QVector<TorrentID> m_queue;
};
const QString META_VERSION = u"version"_qs;
struct Column
{
@@ -135,7 +91,6 @@ namespace
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_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");
@@ -160,7 +115,7 @@ namespace
QString makeCreateTableStatement(const QString &tableName, const QStringList &items)
{
return u"CREATE TABLE %1 (%2)"_s.arg(quoted(tableName), items.join(u','));
return u"CREATE TABLE %1 (%2)"_qs.arg(quoted(tableName), items.join(u','));
}
std::pair<QString, QString> joinColumns(const QVector<Column> &columns)
@@ -191,123 +146,115 @@ namespace
QString makeInsertStatement(const QString &tableName, const QVector<Column> &columns)
{
const auto [names, values] = joinColumns(columns);
return u"INSERT INTO %1 (%2) VALUES (%3)"_s
return u"INSERT INTO %1 (%2) VALUES (%3)"_qs
.arg(quoted(tableName), names, values);
}
QString makeUpdateStatement(const QString &tableName, const QVector<Column> &columns)
{
const auto [names, values] = joinColumns(columns);
return u"UPDATE %1 SET (%2) = (%3)"_s
return u"UPDATE %1 SET (%2) = (%3)"_qs
.arg(quoted(tableName), names, values);
}
QString makeOnConflictUpdateStatement(const Column &constraint, const QVector<Column> &columns)
{
const auto [names, values] = joinColumns(columns);
return u" ON CONFLICT (%1) DO UPDATE SET (%2) = (%3)"_s
return u" ON CONFLICT (%1) DO UPDATE SET (%2) = (%3)"_qs
.arg(quoted(constraint.name), names, values);
}
QString makeColumnDefinition(const Column &column, const char *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.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.stopCondition = Utils::String::toEnum(
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
resumeData.savePath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
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"_qs.arg(quoted(column.name), QString::fromLatin1(definition));
}
}
namespace BitTorrent
{
class DBResumeDataStorage::Worker final : public QThread
class DBResumeDataStorage::Worker final : public QObject
{
Q_DISABLE_COPY_MOVE(Worker)
public:
Worker(const Path &dbPath, QReadWriteLock &dbLock, QObject *parent = nullptr);
Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock);
void run() override;
void requestInterruption();
void openDatabase() const;
void closeDatabase() const;
void store(const TorrentID &id, const LoadTorrentParams &resumeData);
void remove(const TorrentID &id);
void storeQueue(const QVector<TorrentID> &queue);
void store(const TorrentID &id, const LoadTorrentParams &resumeData) const;
void remove(const TorrentID &id) const;
void storeQueue(const QVector<TorrentID> &queue) const;
private:
void addJob(std::unique_ptr<Job> job);
const QString m_connectionName = u"ResumeDataStorageWorker"_s;
const Path m_path;
const QString m_connectionName;
QReadWriteLock &m_dbLock;
std::queue<std::unique_ptr<Job>> m_jobs;
QMutex m_jobsMutex;
QWaitCondition m_waitCondition;
};
namespace
{
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
{
LoadTorrentParams resumeData;
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
if (!tagsData.isEmpty())
{
const QStringList tagList = tagsData.split(u',');
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
}
resumeData.hasSeedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
resumeData.contentLayout = Utils::String::toEnum<TorrentContentLayout>(
query.value(DB_COLUMN_CONTENT_LAYOUT.name).toString(), TorrentContentLayout::Original);
resumeData.operatingMode = Utils::String::toEnum<TorrentOperatingMode>(
query.value(DB_COLUMN_OPERATING_MODE.name).toString(), TorrentOperatingMode::AutoManaged);
resumeData.stopped = query.value(DB_COLUMN_STOPPED.name).toBool();
resumeData.stopCondition = Utils::String::toEnum(
query.value(DB_COLUMN_STOP_CONDITION.name).toString(), Torrent::StopCondition::None);
resumeData.savePath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_TARGET_SAVE_PATH.name).toString()));
resumeData.useAutoTMM = resumeData.savePath.isEmpty();
if (!resumeData.useAutoTMM)
{
resumeData.downloadPath = Profile::instance()->fromPortablePath(
Path(query.value(DB_COLUMN_DOWNLOAD_PATH.name).toString()));
}
const QByteArray bencodedResumeData = query.value(DB_COLUMN_RESUMEDATA.name).toByteArray();
lt::error_code ec;
const lt::bdecode_node resumeDataRoot = lt::bdecode(bencodedResumeData, ec
, nullptr, BENCODE_DEPTH_LIMIT, BENCODE_TOKEN_LIMIT);
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, BENCODE_DEPTH_LIMIT, BENCODE_TOKEN_LIMIT);
p.ti = std::make_shared<lt::torrent_info>(torentInfoRoot, ec);
}
p.save_path = Profile::instance()->fromPortablePath(Path(fromLTString(p.save_path)))
.toString().toStdString();
if (p.flags & lt::torrent_flags::stop_when_ready)
{
p.flags &= ~lt::torrent_flags::stop_when_ready;
resumeData.stopCondition = Torrent::StopCondition::FilesChecked;
}
return resumeData;
}
}
}
BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent)
@@ -316,7 +263,7 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject
{
const bool needCreateDB = !dbPath.exists();
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_s, DB_CONNECTION_NAME);
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_qs, DB_CONNECTION_NAME);
db.setDatabaseName(dbPath.data());
if (!db.open())
throw RuntimeError(db.lastError().text());
@@ -332,20 +279,37 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject
updateDB(dbVersion);
}
m_asyncWorker = new Worker(dbPath, m_dbLock, this);
m_asyncWorker->start();
m_asyncWorker = new Worker(dbPath, u"ResumeDataStorageWorker"_qs, m_dbLock);
m_asyncWorker->moveToThread(m_ioThread.get());
connect(m_ioThread.get(), &QThread::finished, m_asyncWorker, &QObject::deleteLater);
m_ioThread->start();
RuntimeError *errPtr = nullptr;
QMetaObject::invokeMethod(m_asyncWorker, [this, &errPtr]()
{
try
{
m_asyncWorker->openDatabase();
}
catch (const RuntimeError &err)
{
errPtr = new RuntimeError(err);
}
}, Qt::BlockingQueuedConnection);
if (errPtr)
throw *errPtr;
}
BitTorrent::DBResumeDataStorage::~DBResumeDataStorage()
{
m_asyncWorker->requestInterruption();
m_asyncWorker->wait();
QMetaObject::invokeMethod(m_asyncWorker, &Worker::closeDatabase);
QSqlDatabase::removeDatabase(DB_CONNECTION_NAME);
}
QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents() const
{
const auto selectTorrentIDStatement = u"SELECT %1 FROM %2 ORDER BY %3;"_s
const auto selectTorrentIDStatement = u"SELECT %1 FROM %2 ORDER BY %3;"_qs
.arg(quoted(DB_COLUMN_TORRENT_ID.name), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
@@ -364,7 +328,7 @@ QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorren
BitTorrent::LoadResumeDataResult BitTorrent::DBResumeDataStorage::load(const TorrentID &id) const
{
const QString selectTorrentStatement = u"SELECT * FROM %1 WHERE %2 = %3;"_s
const QString selectTorrentStatement = u"SELECT * FROM %1 WHERE %2 = %3;"_qs
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
@@ -392,32 +356,41 @@ BitTorrent::LoadResumeDataResult BitTorrent::DBResumeDataStorage::load(const Tor
void BitTorrent::DBResumeDataStorage::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
{
m_asyncWorker->store(id, resumeData);
QMetaObject::invokeMethod(m_asyncWorker, [this, id, resumeData]()
{
m_asyncWorker->store(id, resumeData);
});
}
void BitTorrent::DBResumeDataStorage::remove(const BitTorrent::TorrentID &id) const
{
m_asyncWorker->remove(id);
QMetaObject::invokeMethod(m_asyncWorker, [this, id]()
{
m_asyncWorker->remove(id);
});
}
void BitTorrent::DBResumeDataStorage::storeQueue(const QVector<TorrentID> &queue) const
{
m_asyncWorker->storeQueue(queue);
QMetaObject::invokeMethod(m_asyncWorker, [this, queue]()
{
m_asyncWorker->storeQueue(queue);
});
}
void BitTorrent::DBResumeDataStorage::doLoadAll() const
{
const QString connectionName = u"ResumeDataStorageLoadAll"_s;
const QString connectionName = u"ResumeDataStorageLoadAll"_qs;
{
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_s, connectionName);
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_qs, connectionName);
db.setDatabaseName(path().data());
if (!db.open())
throw RuntimeError(db.lastError().text());
QSqlQuery query {db};
const auto selectTorrentIDStatement = u"SELECT %1 FROM %2 ORDER BY %3;"_s
const auto selectTorrentIDStatement = u"SELECT %1 FROM %2 ORDER BY %3;"_qs
.arg(quoted(DB_COLUMN_TORRENT_ID.name), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
const QReadLocker locker {&m_dbLock};
@@ -432,7 +405,7 @@ void BitTorrent::DBResumeDataStorage::doLoadAll() const
emit const_cast<DBResumeDataStorage *>(this)->loadStarted(registeredTorrents);
const auto selectStatement = u"SELECT * FROM %1 ORDER BY %2;"_s.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
const auto selectStatement = u"SELECT * FROM %1 ORDER BY %2;"_qs.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
if (!query.exec(selectStatement))
throw RuntimeError(query.lastError().text());
@@ -450,7 +423,7 @@ void BitTorrent::DBResumeDataStorage::doLoadAll() const
int BitTorrent::DBResumeDataStorage::currentDBVersion() const
{
const auto selectDBVersionStatement = u"SELECT %1 FROM %2 WHERE %3 = %4;"_s
const auto selectDBVersionStatement = u"SELECT %1 FROM %2 WHERE %3 = %4;"_qs
.arg(quoted(DB_COLUMN_VALUE.name), quoted(DB_TABLE_META), quoted(DB_COLUMN_NAME.name), DB_COLUMN_NAME.placeholder);
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
@@ -479,18 +452,10 @@ int BitTorrent::DBResumeDataStorage::currentDBVersion() const
void BitTorrent::DBResumeDataStorage::createDB() const
{
try
{
enableWALMode();
}
catch (const RuntimeError &err)
{
LogMsg(tr("Couldn't enable Write-Ahead Logging (WAL) journaling mode. Error: %1.")
.arg(err.message()), Log::WARNING);
}
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
const QWriteLocker locker {&m_dbLock};
if (!db.transaction())
throw RuntimeError(db.lastError().text());
@@ -529,7 +494,6 @@ void BitTorrent::DBResumeDataStorage::createDB() const
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_HAS_OUTER_PIECES_PRIORITY, "INTEGER NOT NULL"),
makeColumnDefinition(DB_COLUMN_HAS_SEED_STATUS, "INTEGER NOT NULL"),
makeColumnDefinition(DB_COLUMN_OPERATING_MODE, "TEXT NOT NULL"),
@@ -542,12 +506,6 @@ void BitTorrent::DBResumeDataStorage::createDB() const
if (!query.exec(createTableTorrentsQuery))
throw RuntimeError(query.lastError().text());
const QString torrentsQueuePositionIndexName = u"%1_%2_INDEX"_s.arg(DB_TABLE_TORRENTS, DB_COLUMN_QUEUE_POSITION.name);
const QString createTorrentsQueuePositionIndexQuery = u"CREATE INDEX %1 ON %2 (%3)"_s
.arg(quoted(torrentsQueuePositionIndexName), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
if (!query.exec(createTorrentsQueuePositionIndexQuery))
throw RuntimeError(query.lastError().text());
if (!db.commit())
throw RuntimeError(db.lastError().text());
}
@@ -576,11 +534,11 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
{
if (fromVersion == 1)
{
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_s
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_qs
.arg(quoted(DB_COLUMN_DOWNLOAD_PATH.name), quoted(DB_TABLE_TORRENTS));
if (!query.exec(testQuery))
{
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_s
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"));
if (!query.exec(alterTableTorrentsQuery))
throw RuntimeError(query.lastError().text());
@@ -589,39 +547,17 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
if (fromVersion <= 2)
{
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_s
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_qs
.arg(quoted(DB_COLUMN_STOP_CONDITION.name), quoted(DB_TABLE_TORRENTS));
if (!query.exec(testQuery))
{
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_s
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_qs
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"));
if (!query.exec(alterTableTorrentsQuery))
throw RuntimeError(query.lastError().text());
}
}
if (fromVersion <= 3)
{
const QString torrentsQueuePositionIndexName = u"%1_%2_INDEX"_s.arg(DB_TABLE_TORRENTS, DB_COLUMN_QUEUE_POSITION.name);
const QString createTorrentsQueuePositionIndexQuery = u"CREATE INDEX IF NOT EXISTS %1 ON %2 (%3)"_s
.arg(quoted(torrentsQueuePositionIndexName), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
if (!query.exec(createTorrentsQueuePositionIndexQuery))
throw RuntimeError(query.lastError().text());
}
if (fromVersion <= 4)
{
const auto testQuery = u"SELECT COUNT(%1) FROM %2;"_s
.arg(quoted(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.name), quoted(DB_TABLE_TORRENTS));
if (!query.exec(testQuery))
{
const auto alterTableTorrentsQuery = u"ALTER TABLE %1 ADD %2"_s
.arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, "INTEGER NOT NULL DEFAULT -2"));
if (!query.exec(alterTableTorrentsQuery))
throw RuntimeError(query.lastError().text());
}
}
const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE});
if (!query.prepare(updateMetaVersionQuery))
throw RuntimeError(query.lastError().text());
@@ -642,308 +578,217 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
}
}
void BitTorrent::DBResumeDataStorage::enableWALMode() const
{
auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
QSqlQuery query {db};
if (!query.exec(u"PRAGMA journal_mode = WAL;"_s))
throw RuntimeError(query.lastError().text());
if (!query.next())
throw RuntimeError(tr("Couldn't obtain query result."));
const QString result = query.value(0).toString();
if (result.compare(u"WAL"_s, Qt::CaseInsensitive) != 0)
throw RuntimeError(tr("WAL mode is probably unsupported due to filesystem limitations."));
}
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, QReadWriteLock &dbLock, QObject *parent)
: QThread(parent)
, m_path {dbPath}
BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock)
: m_path {dbPath}
, m_connectionName {dbConnectionName}
, m_dbLock {dbLock}
{
}
void BitTorrent::DBResumeDataStorage::Worker::run()
void BitTorrent::DBResumeDataStorage::Worker::openDatabase() const
{
{
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_s, m_connectionName);
db.setDatabaseName(m_path.data());
if (!db.open())
throw RuntimeError(db.lastError().text());
int64_t transactedJobsCount = 0;
while (true)
{
m_jobsMutex.lock();
if (m_jobs.empty())
{
if (transactedJobsCount > 0)
{
db.commit();
m_dbLock.unlock();
qDebug() << "Resume data changes are committed. Transacted jobs:" << transactedJobsCount;
transactedJobsCount = 0;
}
if (isInterruptionRequested())
{
m_jobsMutex.unlock();
break;
}
m_waitCondition.wait(&m_jobsMutex);
if (isInterruptionRequested())
{
m_jobsMutex.unlock();
break;
}
m_dbLock.lockForWrite();
if (!db.transaction())
{
LogMsg(tr("Couldn't begin transaction. Error: %1").arg(db.lastError().text()), Log::WARNING);
m_dbLock.unlock();
break;
}
}
std::unique_ptr<Job> job = std::move(m_jobs.front());
m_jobs.pop();
m_jobsMutex.unlock();
job->perform(db);
++transactedJobsCount;
}
db.close();
}
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_qs, m_connectionName);
db.setDatabaseName(m_path.data());
if (!db.open())
throw RuntimeError(db.lastError().text());
}
void BitTorrent::DBResumeDataStorage::Worker::closeDatabase() const
{
QSqlDatabase::removeDatabase(m_connectionName);
}
void DBResumeDataStorage::Worker::requestInterruption()
void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
{
QThread::requestInterruption();
m_waitCondition.wakeAll();
}
void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData)
{
addJob(std::make_unique<StoreJob>(id, resumeData));
}
void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id)
{
addJob(std::make_unique<RemoveJob>(id));
}
void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue)
{
addJob(std::make_unique<StoreQueueJob>(queue));
}
void BitTorrent::DBResumeDataStorage::Worker::addJob(std::unique_ptr<Job> job)
{
m_jobsMutex.lock();
m_jobs.push(std::move(job));
m_jobsMutex.unlock();
m_waitCondition.wakeAll();
}
namespace
{
using namespace BitTorrent;
StoreJob::StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData)
: m_torrentID {torrentID}
, m_resumeData {resumeData}
// We need to adjust native libtorrent resume data
lt::add_torrent_params p = resumeData.ltAddTorrentParams;
p.save_path = Profile::instance()->toPortablePath(Path(p.save_path))
.toString().toStdString();
if (resumeData.stopped)
{
p.flags |= lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
}
void StoreJob::perform(QSqlDatabase db)
else
{
// We need to adjust native libtorrent resume data
lt::add_torrent_params p = m_resumeData.ltAddTorrentParams;
p.save_path = Profile::instance()->toPortablePath(Path(p.save_path))
.toString().toStdString();
if (m_resumeData.stopped)
// Torrent can be actually "running" but temporarily "paused" to perform some
// service jobs behind the scenes so we need to restore it as "running"
if (resumeData.operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged)
{
p.flags |= lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
p.flags |= lt::torrent_flags::auto_managed;
}
else
{
// Torrent can be actually "running" but temporarily "paused" to perform some
// service jobs behind the scenes so we need to restore it as "running"
if (m_resumeData.operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged)
{
p.flags |= lt::torrent_flags::auto_managed;
}
else
{
p.flags &= ~lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
}
p.flags &= ~lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
}
}
QVector<Column> columns {
DB_COLUMN_TORRENT_ID,
DB_COLUMN_NAME,
DB_COLUMN_CATEGORY,
DB_COLUMN_TAGS,
DB_COLUMN_TARGET_SAVE_PATH,
DB_COLUMN_DOWNLOAD_PATH,
DB_COLUMN_CONTENT_LAYOUT,
DB_COLUMN_RATIO_LIMIT,
DB_COLUMN_SEEDING_TIME_LIMIT,
DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT,
DB_COLUMN_HAS_OUTER_PIECES_PRIORITY,
DB_COLUMN_HAS_SEED_STATUS,
DB_COLUMN_OPERATING_MODE,
DB_COLUMN_STOPPED,
DB_COLUMN_STOP_CONDITION,
DB_COLUMN_RESUMEDATA
};
QVector<Column> columns {
DB_COLUMN_TORRENT_ID,
DB_COLUMN_NAME,
DB_COLUMN_CATEGORY,
DB_COLUMN_TAGS,
DB_COLUMN_TARGET_SAVE_PATH,
DB_COLUMN_DOWNLOAD_PATH,
DB_COLUMN_CONTENT_LAYOUT,
DB_COLUMN_RATIO_LIMIT,
DB_COLUMN_SEEDING_TIME_LIMIT,
DB_COLUMN_HAS_OUTER_PIECES_PRIORITY,
DB_COLUMN_HAS_SEED_STATUS,
DB_COLUMN_OPERATING_MODE,
DB_COLUMN_STOPPED,
DB_COLUMN_STOP_CONDITION,
DB_COLUMN_RESUMEDATA
};
lt::entry data = lt::write_resume_data(p);
lt::entry data = lt::write_resume_data(p);
// metadata is stored in separate column
QByteArray bencodedMetadata;
if (p.ti)
// metadata is stored in separate column
QByteArray bencodedMetadata;
if (p.ti)
{
lt::entry::dictionary_type &dataDict = data.dict();
lt::entry metadata {lt::entry::dictionary_t};
lt::entry::dictionary_type &metadataDict = metadata.dict();
metadataDict.insert(dataDict.extract("info"));
metadataDict.insert(dataDict.extract("creation date"));
metadataDict.insert(dataDict.extract("created by"));
metadataDict.insert(dataDict.extract("comment"));
try
{
lt::entry::dictionary_type &dataDict = data.dict();
lt::entry metadata {lt::entry::dictionary_t};
lt::entry::dictionary_type &metadataDict = metadata.dict();
metadataDict.insert(dataDict.extract("info"));
metadataDict.insert(dataDict.extract("creation date"));
metadataDict.insert(dataDict.extract("created by"));
metadataDict.insert(dataDict.extract("comment"));
try
{
bencodedMetadata.reserve(512 * 1024);
lt::bencode(std::back_inserter(bencodedMetadata), metadata);
}
catch (const std::exception &err)
{
LogMsg(ResumeDataStorage::tr("Couldn't save torrent metadata. Error: %1.")
.arg(QString::fromLocal8Bit(err.what())), Log::CRITICAL);
return;
}
columns.append(DB_COLUMN_METADATA);
bencodedMetadata.reserve(512 * 1024);
lt::bencode(std::back_inserter(bencodedMetadata), metadata);
}
catch (const std::exception &err)
{
LogMsg(tr("Couldn't save torrent metadata. Error: %1.")
.arg(QString::fromLocal8Bit(err.what())), Log::CRITICAL);
return;
}
QByteArray bencodedResumeData;
bencodedResumeData.reserve(256 * 1024);
lt::bencode(std::back_inserter(bencodedResumeData), data);
columns.append(DB_COLUMN_METADATA);
}
QByteArray bencodedResumeData;
bencodedResumeData.reserve(256 * 1024);
lt::bencode(std::back_inserter(bencodedResumeData), data);
const QString insertTorrentStatement = makeInsertStatement(DB_TABLE_TORRENTS, columns)
+ makeOnConflictUpdateStatement(DB_COLUMN_TORRENT_ID, columns);
auto db = QSqlDatabase::database(m_connectionName);
QSqlQuery query {db};
try
{
if (!query.prepare(insertTorrentStatement))
throw RuntimeError(query.lastError().text());
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, id.toString());
query.bindValue(DB_COLUMN_NAME.placeholder, resumeData.name);
query.bindValue(DB_COLUMN_CATEGORY.placeholder, resumeData.category);
query.bindValue(DB_COLUMN_TAGS.placeholder, (resumeData.tags.isEmpty()
? QVariant(QVariant::String) : resumeData.tags.join(u","_qs)));
query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(resumeData.contentLayout));
query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast<int>(resumeData.ratioLimit * 1000));
query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, resumeData.seedingTimeLimit);
query.bindValue(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.placeholder, resumeData.firstLastPiecePriority);
query.bindValue(DB_COLUMN_HAS_SEED_STATUS.placeholder, resumeData.hasSeedStatus);
query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(resumeData.operatingMode));
query.bindValue(DB_COLUMN_STOPPED.placeholder, resumeData.stopped);
query.bindValue(DB_COLUMN_STOP_CONDITION.placeholder, Utils::String::fromEnum(resumeData.stopCondition));
if (!resumeData.useAutoTMM)
{
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.savePath).data());
query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(resumeData.downloadPath).data());
}
query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData);
if (!bencodedMetadata.isEmpty())
query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata);
const QWriteLocker locker {&m_dbLock};
if (!query.exec())
throw RuntimeError(query.lastError().text());
}
catch (const RuntimeError &err)
{
LogMsg(tr("Couldn't store resume data for torrent '%1'. Error: %2")
.arg(id.toString(), err.message()), Log::CRITICAL);
}
}
void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id) const
{
const auto deleteTorrentStatement = u"DELETE FROM %1 WHERE %2 = %3;"_qs
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
auto db = QSqlDatabase::database(m_connectionName);
QSqlQuery query {db};
try
{
if (!query.prepare(deleteTorrentStatement))
throw RuntimeError(query.lastError().text());
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, id.toString());
const QWriteLocker locker {&m_dbLock};
if (!query.exec())
throw RuntimeError(query.lastError().text());
}
catch (const RuntimeError &err)
{
LogMsg(tr("Couldn't delete resume data of torrent '%1'. Error: %2")
.arg(id.toString(), err.message()), Log::CRITICAL);
}
}
void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue) const
{
const auto updateQueuePosStatement = u"UPDATE %1 SET %2 = %3 WHERE %4 = %5;"_qs
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name), DB_COLUMN_QUEUE_POSITION.placeholder
, quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
auto db = QSqlDatabase::database(m_connectionName);
try
{
const QWriteLocker locker {&m_dbLock};
if (!db.transaction())
throw RuntimeError(db.lastError().text());
const QString insertTorrentStatement = makeInsertStatement(DB_TABLE_TORRENTS, columns)
+ makeOnConflictUpdateStatement(DB_COLUMN_TORRENT_ID, columns);
QSqlQuery query {db};
try
{
if (!query.prepare(insertTorrentStatement))
throw RuntimeError(query.lastError().text());
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, m_torrentID.toString());
query.bindValue(DB_COLUMN_NAME.placeholder, m_resumeData.name);
query.bindValue(DB_COLUMN_CATEGORY.placeholder, m_resumeData.category);
query.bindValue(DB_COLUMN_TAGS.placeholder, (m_resumeData.tags.isEmpty()
? QVariant(QVariant::String) : m_resumeData.tags.join(u","_s)));
query.bindValue(DB_COLUMN_CONTENT_LAYOUT.placeholder, Utils::String::fromEnum(m_resumeData.contentLayout));
query.bindValue(DB_COLUMN_RATIO_LIMIT.placeholder, static_cast<int>(m_resumeData.ratioLimit * 1000));
query.bindValue(DB_COLUMN_SEEDING_TIME_LIMIT.placeholder, m_resumeData.seedingTimeLimit);
query.bindValue(DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT.placeholder, m_resumeData.inactiveSeedingTimeLimit);
query.bindValue(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.placeholder, m_resumeData.firstLastPiecePriority);
query.bindValue(DB_COLUMN_HAS_SEED_STATUS.placeholder, m_resumeData.hasFinishedStatus);
query.bindValue(DB_COLUMN_OPERATING_MODE.placeholder, Utils::String::fromEnum(m_resumeData.operatingMode));
query.bindValue(DB_COLUMN_STOPPED.placeholder, m_resumeData.stopped);
query.bindValue(DB_COLUMN_STOP_CONDITION.placeholder, Utils::String::fromEnum(m_resumeData.stopCondition));
if (!m_resumeData.useAutoTMM)
{
query.bindValue(DB_COLUMN_TARGET_SAVE_PATH.placeholder, Profile::instance()->toPortablePath(m_resumeData.savePath).data());
query.bindValue(DB_COLUMN_DOWNLOAD_PATH.placeholder, Profile::instance()->toPortablePath(m_resumeData.downloadPath).data());
}
query.bindValue(DB_COLUMN_RESUMEDATA.placeholder, bencodedResumeData);
if (!bencodedMetadata.isEmpty())
query.bindValue(DB_COLUMN_METADATA.placeholder, bencodedMetadata);
if (!query.exec())
throw RuntimeError(query.lastError().text());
}
catch (const RuntimeError &err)
{
LogMsg(ResumeDataStorage::tr("Couldn't store resume data for torrent '%1'. Error: %2")
.arg(m_torrentID.toString(), err.message()), Log::CRITICAL);
}
}
RemoveJob::RemoveJob(const TorrentID &torrentID)
: m_torrentID {torrentID}
{
}
void RemoveJob::perform(QSqlDatabase db)
{
const auto deleteTorrentStatement = u"DELETE FROM %1 WHERE %2 = %3;"_s
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
QSqlQuery query {db};
try
{
if (!query.prepare(deleteTorrentStatement))
throw RuntimeError(query.lastError().text());
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, m_torrentID.toString());
if (!query.exec())
throw RuntimeError(query.lastError().text());
}
catch (const RuntimeError &err)
{
LogMsg(ResumeDataStorage::tr("Couldn't delete resume data of torrent '%1'. Error: %2")
.arg(m_torrentID.toString(), err.message()), Log::CRITICAL);
}
}
StoreQueueJob::StoreQueueJob(const QVector<TorrentID> &queue)
: m_queue {queue}
{
}
void StoreQueueJob::perform(QSqlDatabase db)
{
const auto updateQueuePosStatement = u"UPDATE %1 SET %2 = %3 WHERE %4 = %5;"_s
.arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name), DB_COLUMN_QUEUE_POSITION.placeholder
, quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
try
{
QSqlQuery query {db};
if (!query.prepare(updateQueuePosStatement))
throw RuntimeError(query.lastError().text());
int pos = 0;
for (const TorrentID &torrentID : m_queue)
for (const TorrentID &torrentID : queue)
{
query.bindValue(DB_COLUMN_TORRENT_ID.placeholder, torrentID.toString());
query.bindValue(DB_COLUMN_QUEUE_POSITION.placeholder, pos++);
if (!query.exec())
throw RuntimeError(query.lastError().text());
}
if (!db.commit())
throw RuntimeError(db.lastError().text());
}
catch (const RuntimeError &err)
catch (const RuntimeError &)
{
LogMsg(ResumeDataStorage::tr("Couldn't store torrents queue positions. Error: %1")
.arg(err.message()), Log::CRITICAL);
db.rollback();
throw;
}
}
catch (const RuntimeError &err)
{
LogMsg(tr("Couldn't store torrents queue positions. Error: %1")
.arg(err.message()), Log::CRITICAL);
}
}

View File

@@ -59,7 +59,6 @@ namespace BitTorrent
int currentDBVersion() const;
void createDB() const;
void updateDB(int fromVersion) const;
void enableWALMode() const;
Utils::Thread::UniquePtr m_ioThread;

View File

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

View File

@@ -89,7 +89,7 @@ namespace
}
private:
lt::address_v4::bytes_type m_buf {};
lt::address_v4::bytes_type m_buf;
};
bool parseIPAddress(const char *data, lt::address &address)
@@ -111,6 +111,7 @@ namespace
FilterParserThread::FilterParserThread(QObject *parent)
: QThread(parent)
, m_abort(false)
{
}
@@ -483,9 +484,9 @@ int FilterParserThread::parseP2BFilterFile()
char buf[7];
unsigned char version;
if (!stream.readRawData(buf, sizeof(buf))
|| (memcmp(buf, "\xFF\xFF\xFF\xFFP2B", 7) != 0)
|| memcmp(buf, "\xFF\xFF\xFF\xFFP2B", 7)
|| !stream.readRawData(reinterpret_cast<char*>(&version), sizeof(version)))
{
{
LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);
return ruleCount;
}
@@ -617,17 +618,17 @@ void FilterParserThread::run()
{
qDebug("Processing filter file");
int ruleCount = 0;
if (m_filePath.hasExtension(u".p2p"_s))
if (m_filePath.hasExtension(u".p2p"_qs))
{
// PeerGuardian p2p file
ruleCount = parseP2PFilterFile();
}
else if (m_filePath.hasExtension(u".p2b"_s))
else if (m_filePath.hasExtension(u".p2b"_qs))
{
// PeerGuardian p2b file
ruleCount = parseP2BFilterFile();
}
else if (m_filePath.hasExtension(u".dat"_s))
else if (m_filePath.hasExtension(u".dat"_qs))
{
// eMule DAT format
ruleCount = parseDATFilterFile();

View File

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

View File

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

View File

@@ -52,14 +52,11 @@ namespace BitTorrent
TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged;
bool useAutoTMM = false;
bool firstLastPiecePriority = false;
bool hasFinishedStatus = false;
bool hasSeedStatus = false;
bool stopped = false;
Torrent::StopCondition stopCondition = Torrent::StopCondition::None;
bool addToQueueTop = false; // only for new torrents
Torrent::StopCondition stopCondition;
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
int inactiveSeedingTimeLimit = Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME;
};
}

View File

@@ -53,9 +53,9 @@ namespace
const int V1_BASE32_SIZE = SHA1Hash::length() * 1.6;
return ((((string.size() == V1_HEX_SIZE))
&& !string.contains(QRegularExpression(u"[^0-9A-Fa-f]"_s)))
&& !string.contains(QRegularExpression(u"[^0-9A-Fa-f]"_qs)))
|| ((string.size() == V1_BASE32_SIZE)
&& !string.contains(QRegularExpression(u"[^2-7A-Za-z]"_s))));
&& !string.contains(QRegularExpression(u"[^2-7A-Za-z]"_qs))));
}
bool isV2Hash(const QString &string)
@@ -66,7 +66,7 @@ namespace
const int V2_HEX_SIZE = SHA256Hash::length() * 2;
return (string.size() == V2_HEX_SIZE)
&& !string.contains(QRegularExpression(u"[^0-9A-Fa-f]"_s));
&& !string.contains(QRegularExpression(u"[^0-9A-Fa-f]"_qs));
}
}
@@ -75,7 +75,8 @@ using namespace BitTorrent;
const int magnetUriId = qRegisterMetaType<MagnetUri>();
MagnetUri::MagnetUri(const QString &source)
: m_url(source)
: m_valid(false)
, m_url(source)
{
if (source.isEmpty()) return;

View File

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

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2020-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -42,17 +42,6 @@ namespace
}
}
bool NativeSessionExtension::isSessionListening() const
{
const QReadLocker locker {&m_lock};
return m_isSessionListening;
}
void NativeSessionExtension::added(const lt::session_handle &nativeSession)
{
m_nativeSession = nativeSession;
}
lt::feature_flags_t NativeSessionExtension::implemented_features()
{
return alert_feature;
@@ -67,9 +56,6 @@ void NativeSessionExtension::on_alert(const lt::alert *alert)
{
switch (alert->type())
{
case lt::session_stats_alert::alert_type:
handleSessionStatsAlert(static_cast<const lt::session_stats_alert *>(alert));
break;
case lt::fastresume_rejected_alert::alert_type:
handleFastresumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert *>(alert));
break;
@@ -77,9 +63,3 @@ void NativeSessionExtension::on_alert(const lt::alert *alert)
break;
}
}
void NativeSessionExtension::handleSessionStatsAlert([[maybe_unused]] const lt::session_stats_alert *alert)
{
const QWriteLocker locker {&m_lock};
m_isSessionListening = m_nativeSession.is_listening();
}

View File

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

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2020-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2020-2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -40,7 +40,6 @@ NativeTorrentExtension::NativeTorrentExtension(const lt::torrent_handle &torrent
{
m_data->status = m_torrentHandle.status();
m_data->trackers = m_torrentHandle.trackers();
m_data->urlSeeds = m_torrentHandle.url_seeds();
}
on_state(m_data ? m_data->status.state : m_torrentHandle.status({}).state);

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -31,16 +31,16 @@
#include <QBitArray>
#include "base/bittorrent/ltqbitarray.h"
#include "base/bittorrent/torrent.h"
#include "base/net/geoipmanager.h"
#include "base/unicodestrings.h"
#include "base/utils/bytearray.h"
#include "peeraddress.h"
using namespace BitTorrent;
PeerInfo::PeerInfo(const lt::peer_info &nativeInfo, const QBitArray &allPieces)
PeerInfo::PeerInfo(const Torrent *torrent, const lt::peer_info &nativeInfo)
: m_nativeInfo(nativeInfo)
, m_relevance(calcRelevance(allPieces))
, m_relevance(calcRelevance(torrent))
{
determineFlags();
}
@@ -169,9 +169,6 @@ bool PeerInfo::isPlaintextEncrypted() const
PeerAddress PeerInfo::address() const
{
if (useI2PSocket())
return {};
// fast path for platforms which boost.asio internal struct maps to `sockaddr`
return {QHostAddress(m_nativeInfo.ip.data()), m_nativeInfo.ip.port()};
// slow path for the others
@@ -179,23 +176,6 @@ PeerAddress PeerInfo::address() const
// , m_nativeInfo.ip.port()};
}
QString PeerInfo::I2PAddress() const
{
if (!useI2PSocket())
return {};
#if defined(QBT_USES_LIBTORRENT2) && TORRENT_USE_I2P
if (m_I2PAddress.isEmpty())
{
const lt::sha256_hash destHash = m_nativeInfo.i2p_destination();
const QByteArray base32Dest = Utils::ByteArray::toBase32({destHash.data(), destHash.size()}).replace('=', "").toLower();
m_I2PAddress = QString::fromLatin1(base32Dest) + u".b32.i2p";
}
#endif
return m_I2PAddress;
}
QString PeerInfo::client() const
{
return QString::fromStdString(m_nativeInfo.client);
@@ -262,12 +242,13 @@ QString PeerInfo::connectionType() const
return C_UTP;
return (m_nativeInfo.connection_type == lt::peer_info::standard_bittorrent)
? u"BT"_s
: u"Web"_s;
? u"BT"_qs
: u"Web"_qs;
}
qreal PeerInfo::calcRelevance(const QBitArray &allPieces) const
qreal PeerInfo::calcRelevance(const Torrent *torrent) const
{
const QBitArray allPieces = torrent->pieces();
const int localMissing = allPieces.count(false);
if (localMissing <= 0)
return 0;
@@ -287,7 +268,7 @@ void PeerInfo::determineFlags()
const auto updateFlags = [this](const QChar specifier, const QString &explanation)
{
m_flags += (specifier + u' ');
m_flagsDescription += u"%1 = %2\n"_s.arg(specifier, explanation);
m_flagsDescription += u"%1 = %2\n"_qs.arg(specifier, explanation);
};
if (isInteresting())

View File

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

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2019-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2019 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,13 +28,14 @@
#include "portforwarderimpl.h"
#include <utility>
#include <libtorrent/session.hpp>
#include "base/bittorrent/sessionimpl.h"
#include "base/algorithm.h"
#include "base/logger.h"
PortForwarderImpl::PortForwarderImpl(BitTorrent::SessionImpl *provider, QObject *parent)
: Net::PortForwarder(parent)
, m_storeActive {u"Network/PortForwardingEnabled"_s, true}
PortForwarderImpl::PortForwarderImpl(lt::session *provider, QObject *parent)
: Net::PortForwarder {parent}
, m_storeActive {u"Network/PortForwardingEnabled"_qs, true}
, m_provider {provider}
{
if (isEnabled())
@@ -65,13 +66,38 @@ void PortForwarderImpl::setEnabled(const bool enabled)
void PortForwarderImpl::setPorts(const QString &profile, QSet<quint16> ports)
{
const QSet<quint16> oldForwardedPorts = std::accumulate(m_portProfiles.cbegin(), m_portProfiles.cend(), QSet<quint16>());
PortMapping &portMapping = m_portProfiles[profile];
Algorithm::removeIf(portMapping, [this, &ports](const quint16 port, const std::vector<lt::port_mapping_t> &handles)
{
// keep existing forwardings
const bool isAlreadyMapped = ports.remove(port);
if (isAlreadyMapped)
return false;
m_portProfiles[profile] = std::move(ports);
const QSet<quint16> newForwardedPorts = std::accumulate(m_portProfiles.cbegin(), m_portProfiles.cend(), QSet<quint16>());
// remove outdated forwardings
for (const lt::port_mapping_t &handle : handles)
m_provider->delete_port_mapping(handle);
m_forwardedPorts.remove(port);
m_provider->removeMappedPorts(oldForwardedPorts - newForwardedPorts);
m_provider->addMappedPorts(newForwardedPorts - oldForwardedPorts);
return true;
});
// add new forwardings
for (const quint16 port : ports)
{
// port already forwarded/taken by other profile, don't do anything
if (m_forwardedPorts.contains(port))
continue;
if (isEnabled())
portMapping.insert(port, m_provider->add_port_mapping(lt::session::tcp, port, port));
else
portMapping.insert(port, {});
m_forwardedPorts.insert(port);
}
if (portMapping.isEmpty())
m_portProfiles.remove(profile);
}
void PortForwarderImpl::removePorts(const QString &profile)
@@ -81,12 +107,40 @@ void PortForwarderImpl::removePorts(const QString &profile)
void PortForwarderImpl::start()
{
m_provider->enablePortMapping();
for (const QSet<quint16> &ports : asConst(m_portProfiles))
m_provider->addMappedPorts(ports);
lt::settings_pack settingsPack;
settingsPack.set_bool(lt::settings_pack::enable_upnp, true);
settingsPack.set_bool(lt::settings_pack::enable_natpmp, true);
m_provider->apply_settings(std::move(settingsPack));
for (auto profileIter = m_portProfiles.begin(); profileIter != m_portProfiles.end(); ++profileIter)
{
PortMapping &portMapping = profileIter.value();
for (auto iter = portMapping.begin(); iter != portMapping.end(); ++iter)
{
Q_ASSERT(iter.value().empty());
const quint16 port = iter.key();
iter.value() = m_provider->add_port_mapping(lt::session::tcp, port, port);
}
}
LogMsg(tr("UPnP/NAT-PMP support: ON"), Log::INFO);
}
void PortForwarderImpl::stop()
{
m_provider->disablePortMapping();
lt::settings_pack settingsPack;
settingsPack.set_bool(lt::settings_pack::enable_upnp, false);
settingsPack.set_bool(lt::settings_pack::enable_natpmp, false);
m_provider->apply_settings(std::move(settingsPack));
// don't clear m_portProfiles so a later `start()` call can restore the port forwardings
for (auto profileIter = m_portProfiles.begin(); profileIter != m_portProfiles.end(); ++profileIter)
{
PortMapping &portMapping = profileIter.value();
for (auto iter = portMapping.begin(); iter != portMapping.end(); ++iter)
iter.value().clear();
}
LogMsg(tr("UPnP/NAT-PMP support: OFF"), Log::INFO);
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2019-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2019 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,24 @@
#pragma once
#include <vector>
#include <libtorrent/fwd.hpp>
#include <libtorrent/portmap.hpp>
#include <QHash>
#include <QSet>
#include "base/net/portforwarder.h"
#include "base/settingvalue.h"
namespace BitTorrent
{
class SessionImpl;
}
class PortForwarderImpl final : public Net::PortForwarder
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(PortForwarderImpl)
public:
explicit PortForwarderImpl(BitTorrent::SessionImpl *provider, QObject *parent = nullptr);
explicit PortForwarderImpl(lt::session *provider, QObject *parent = nullptr);
~PortForwarderImpl() override;
bool isEnabled() const override;
@@ -59,7 +59,9 @@ private:
void stop();
CachedSettingValue<bool> m_storeActive;
lt::session *const m_provider = nullptr;
BitTorrent::SessionImpl *const m_provider = nullptr;
QHash<QString, QSet<quint16>> m_portProfiles;
using PortMapping = QHash<quint16, std::vector<lt::port_mapping_t>>; // <port, handles>
QHash<QString, PortMapping> m_portProfiles;
QSet<quint16> m_forwardedPorts;
};

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -170,9 +170,6 @@ namespace BitTorrent
virtual bool useCategoryPathsInManualMode() const = 0;
virtual void setUseCategoryPathsInManualMode(bool value) = 0;
virtual Path suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const = 0;
virtual Path suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const = 0;
static bool isValidTag(const QString &tag);
virtual QSet<QString> tags() const = 0;
virtual bool hasTag(const QString &tag) const = 0;
@@ -203,16 +200,12 @@ namespace BitTorrent
virtual void setGlobalMaxRatio(qreal ratio) = 0;
virtual int globalMaxSeedingMinutes() const = 0;
virtual void setGlobalMaxSeedingMinutes(int minutes) = 0;
virtual int globalMaxInactiveSeedingMinutes() const = 0;
virtual void setGlobalMaxInactiveSeedingMinutes(int minutes) = 0;
virtual bool isDHTEnabled() const = 0;
virtual void setDHTEnabled(bool enabled) = 0;
virtual bool isLSDEnabled() const = 0;
virtual void setLSDEnabled(bool enabled) = 0;
virtual bool isPeXEnabled() const = 0;
virtual void setPeXEnabled(bool enabled) = 0;
virtual bool isAddTorrentToQueueTop() const = 0;
virtual void setAddTorrentToQueueTop(bool value) = 0;
virtual bool isAddTorrentPaused() const = 0;
virtual void setAddTorrentPaused(bool value) = 0;
virtual Torrent::StopCondition torrentStopCondition() const = 0;
@@ -265,24 +258,10 @@ namespace BitTorrent
virtual void setEncryption(int state) = 0;
virtual int maxActiveCheckingTorrents() const = 0;
virtual void setMaxActiveCheckingTorrents(int val) = 0;
virtual bool isI2PEnabled() const = 0;
virtual void setI2PEnabled(bool enabled) = 0;
virtual QString I2PAddress() const = 0;
virtual void setI2PAddress(const QString &address) = 0;
virtual int I2PPort() const = 0;
virtual void setI2PPort(int port) = 0;
virtual bool I2PMixedMode() const = 0;
virtual void setI2PMixedMode(bool enabled) = 0;
virtual int I2PInboundQuantity() const = 0;
virtual void setI2PInboundQuantity(int value) = 0;
virtual int I2POutboundQuantity() const = 0;
virtual void setI2POutboundQuantity(int value) = 0;
virtual int I2PInboundLength() const = 0;
virtual void setI2PInboundLength(int value) = 0;
virtual int I2POutboundLength() const = 0;
virtual void setI2POutboundLength(int value) = 0;
virtual bool isProxyPeerConnectionsEnabled() const = 0;
virtual void setProxyPeerConnectionsEnabled(bool enabled) = 0;
virtual bool isProxyHostnameLookupEnabled() const = 0;
virtual void setProxyHostnameLookupEnabled(bool enabled) = 0;
virtual ChokingAlgorithm chokingAlgorithm() const = 0;
virtual void setChokingAlgorithm(ChokingAlgorithm mode) = 0;
virtual SeedChokingAlgorithm seedChokingAlgorithm() const = 0;
@@ -341,10 +320,6 @@ namespace BitTorrent
virtual void setSendBufferWatermarkFactor(int value) = 0;
virtual int connectionSpeed() const = 0;
virtual void setConnectionSpeed(int value) = 0;
virtual int socketSendBufferSize() const = 0;
virtual void setSocketSendBufferSize(int value) = 0;
virtual int socketReceiveBufferSize() const = 0;
virtual void setSocketReceiveBufferSize(int value) = 0;
virtual int socketBacklogSize() const = 0;
virtual void setSocketBacklogSize(int value) = 0;
virtual bool isAnonymousModeEnabled() const = 0;
@@ -413,7 +388,7 @@ namespace BitTorrent
virtual bool isTrackerFilteringEnabled() const = 0;
virtual void setTrackerFilteringEnabled(bool enabled) = 0;
virtual bool isExcludedFileNamesEnabled() const = 0;
virtual void setExcludedFileNamesEnabled(bool enabled) = 0;
virtual void setExcludedFileNamesEnabled(const bool enabled) = 0;
virtual QStringList excludedFileNames() const = 0;
virtual void setExcludedFileNames(const QStringList &newList) = 0;
virtual bool isFilenameExcluded(const QString &fileName) const = 0;
@@ -421,8 +396,6 @@ namespace BitTorrent
virtual void setBannedIPs(const QStringList &newList) = 0;
virtual ResumeDataStorageType resumeDataStorageType() const = 0;
virtual void setResumeDataStorageType(ResumeDataStorageType type) = 0;
virtual bool isMergeTrackersEnabled() const = 0;
virtual void setMergeTrackersEnabled(bool enabled) = 0;
virtual bool isRestored() const = 0;
@@ -458,7 +431,6 @@ namespace BitTorrent
void allTorrentsFinished();
void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName);
void categoryOptionsChanged(const QString &categoryName);
void downloadFromUrlFailed(const QString &url, const QString &reason);
void downloadFromUrlFinished(const QString &url);
void fullDiskError(Torrent *torrent, const QString &msg);
@@ -493,6 +465,6 @@ namespace BitTorrent
void trackersRemoved(Torrent *torrent, const QStringList &trackers);
void trackerSuccess(Torrent *torrent, const QString &tracker);
void trackerWarning(Torrent *torrent, const QString &tracker);
void trackerEntriesUpdated(Torrent *torrent, const QHash<QString, TrackerEntry> &updatedTrackerEntries);
void trackerEntriesUpdated(const QHash<Torrent *, QSet<QString>> &updateInfos);
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -29,20 +29,17 @@
#pragma once
#include <utility>
#include <variant>
#include <vector>
#include <libtorrent/fwd.hpp>
#include <libtorrent/portmap.hpp>
#include <libtorrent/torrent_handle.hpp>
#include <QtContainerFwd>
#include <QDateTime>
#include <QElapsedTimer>
#include <QHash>
#include <QPointer>
#include <QSet>
#include <QtContainerFwd>
#include <QVector>
#include "base/path.h"
@@ -63,14 +60,12 @@ class QNetworkConfigurationManager;
#endif
class QString;
class QThread;
class QThreadPool;
class QTimer;
class QUrl;
class BandwidthScheduler;
class FileSearcher;
class FilterParserThread;
class NativeSessionExtension;
namespace Net
{
@@ -88,7 +83,6 @@ namespace BitTorrent
struct LoadTorrentParams;
enum class MoveStorageMode;
enum class MoveStorageContext;
struct SessionMetricIndices
{
@@ -161,9 +155,6 @@ namespace BitTorrent
bool useCategoryPathsInManualMode() const override;
void setUseCategoryPathsInManualMode(bool value) override;
Path suggestedSavePath(const QString &categoryName, std::optional<bool> useAutoTMM) const override;
Path suggestedDownloadPath(const QString &categoryName, std::optional<bool> useAutoTMM) const override;
QSet<QString> tags() const override;
bool hasTag(const QString &tag) const override;
bool addTag(const QString &tag) override;
@@ -182,16 +173,12 @@ namespace BitTorrent
void setGlobalMaxRatio(qreal ratio) override;
int globalMaxSeedingMinutes() const override;
void setGlobalMaxSeedingMinutes(int minutes) override;
int globalMaxInactiveSeedingMinutes() const override;
void setGlobalMaxInactiveSeedingMinutes(int minutes) override;
bool isDHTEnabled() const override;
void setDHTEnabled(bool enabled) override;
bool isLSDEnabled() const override;
void setLSDEnabled(bool enabled) override;
bool isPeXEnabled() const override;
void setPeXEnabled(bool enabled) override;
bool isAddTorrentToQueueTop() const override;
void setAddTorrentToQueueTop(bool value) override;
bool isAddTorrentPaused() const override;
void setAddTorrentPaused(bool value) override;
Torrent::StopCondition torrentStopCondition() const override;
@@ -244,24 +231,10 @@ namespace BitTorrent
void setEncryption(int state) override;
int maxActiveCheckingTorrents() const override;
void setMaxActiveCheckingTorrents(int val) override;
bool isI2PEnabled() const override;
void setI2PEnabled(bool enabled) override;
QString I2PAddress() const override;
void setI2PAddress(const QString &address) override;
int I2PPort() const override;
void setI2PPort(int port) override;
bool I2PMixedMode() const override;
void setI2PMixedMode(bool enabled) override;
int I2PInboundQuantity() const override;
void setI2PInboundQuantity(int value) override;
int I2POutboundQuantity() const override;
void setI2POutboundQuantity(int value) override;
int I2PInboundLength() const override;
void setI2PInboundLength(int value) override;
int I2POutboundLength() const override;
void setI2POutboundLength(int value) override;
bool isProxyPeerConnectionsEnabled() const override;
void setProxyPeerConnectionsEnabled(bool enabled) override;
bool isProxyHostnameLookupEnabled() const override;
void setProxyHostnameLookupEnabled(bool enabled) override;
ChokingAlgorithm chokingAlgorithm() const override;
void setChokingAlgorithm(ChokingAlgorithm mode) override;
SeedChokingAlgorithm seedChokingAlgorithm() const override;
@@ -320,10 +293,6 @@ namespace BitTorrent
void setSendBufferWatermarkFactor(int value) override;
int connectionSpeed() const override;
void setConnectionSpeed(int value) override;
int socketSendBufferSize() const override;
void setSocketSendBufferSize(int value) override;
int socketReceiveBufferSize() const override;
void setSocketReceiveBufferSize(int value) override;
int socketBacklogSize() const override;
void setSocketBacklogSize(int value) override;
bool isAnonymousModeEnabled() const override;
@@ -392,16 +361,14 @@ namespace BitTorrent
bool isTrackerFilteringEnabled() const override;
void setTrackerFilteringEnabled(bool enabled) override;
bool isExcludedFileNamesEnabled() const override;
void setExcludedFileNamesEnabled(bool enabled) override;
void setExcludedFileNamesEnabled(const bool enabled) override;
QStringList excludedFileNames() const override;
void setExcludedFileNames(const QStringList &excludedFileNames) override;
void setExcludedFileNames(const QStringList &newList) override;
bool isFilenameExcluded(const QString &fileName) const override;
QStringList bannedIPs() const override;
void setBannedIPs(const QStringList &newList) override;
ResumeDataStorageType resumeDataStorageType() const override;
void setResumeDataStorageType(ResumeDataStorageType type) override;
bool isMergeTrackersEnabled() const override;
void setMergeTrackersEnabled(bool enabled) override;
bool isRestored() const override;
@@ -436,45 +403,32 @@ namespace BitTorrent
void handleTorrentNeedSaveResumeData(const TorrentImpl *torrent);
void handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent);
void handleTorrentSaveResumeDataFailed(const TorrentImpl *torrent);
void handleTorrentShareLimitChanged(TorrentImpl *torrent);
void handleTorrentNameChanged(TorrentImpl *torrent);
void handleTorrentSavePathChanged(TorrentImpl *torrent);
void handleTorrentCategoryChanged(TorrentImpl *torrent, const QString &oldCategory);
void handleTorrentTagAdded(TorrentImpl *torrent, const QString &tag);
void handleTorrentTagRemoved(TorrentImpl *torrent, const QString &tag);
void handleTorrentSavingModeChanged(TorrentImpl *torrent);
void handleTorrentMetadataReceived(TorrentImpl *torrent);
void handleTorrentPaused(TorrentImpl *torrent);
void handleTorrentResumed(TorrentImpl *torrent);
void handleTorrentChecked(TorrentImpl *torrent);
void handleTorrentFinished(TorrentImpl *torrent);
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QVector<TrackerEntry> &newTrackers);
void handleTorrentTrackersRemoved(TorrentImpl *torrent, const QStringList &deletedTrackers);
void handleTorrentTrackersChanged(TorrentImpl *torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QVector<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QVector<QUrl> &urlSeeds);
void handleTorrentResumeDataReady(TorrentImpl *torrent, const LoadTorrentParams &data);
void handleTorrentShareLimitChanged(TorrentImpl *const torrent);
void handleTorrentNameChanged(TorrentImpl *const torrent);
void handleTorrentSavePathChanged(TorrentImpl *const torrent);
void handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory);
void handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag);
void handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag);
void handleTorrentSavingModeChanged(TorrentImpl *const torrent);
void handleTorrentMetadataReceived(TorrentImpl *const torrent);
void handleTorrentPaused(TorrentImpl *const torrent);
void handleTorrentResumed(TorrentImpl *const torrent);
void handleTorrentChecked(TorrentImpl *const torrent);
void handleTorrentFinished(TorrentImpl *const torrent);
void handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers);
void handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QStringList &deletedTrackers);
void handleTorrentTrackersChanged(TorrentImpl *const torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds);
void handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data);
void handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash);
void handleTorrentStorageMovingStateChanged(TorrentImpl *torrent);
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode, MoveStorageContext context);
bool addMoveTorrentStorageJob(TorrentImpl *torrent, const Path &newPath, MoveStorageMode mode);
void findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
, const Path &downloadPath, const PathList &filePaths = {}) const;
void enablePortMapping();
void disablePortMapping();
void addMappedPorts(const QSet<quint16> &ports);
void removeMappedPorts(const QSet<quint16> &ports);
template <typename Func>
void invoke(Func &&func)
{
QMetaObject::invokeMethod(this, std::forward<Func>(func), Qt::QueuedConnection);
}
void invokeAsync(std::function<void ()> func);
private slots:
void configureDeferred();
void readAlerts();
@@ -499,15 +453,14 @@ namespace BitTorrent
{
lt::torrent_handle torrentHandle;
Path path;
MoveStorageMode mode {};
MoveStorageContext context {};
MoveStorageMode mode;
};
struct RemovingTorrentData
{
QString name;
Path pathToRemove;
DeleteOption deleteOption {};
DeleteOption deleteOption;
};
explicit SessionImpl(QObject *parent = nullptr);
@@ -515,7 +468,6 @@ namespace BitTorrent
bool hasPerTorrentRatioLimit() const;
bool hasPerTorrentSeedingTimeLimit() const;
bool hasPerTorrentInactiveSeedingTimeLimit() const;
// Session configuration
Q_INVOKABLE void configure();
@@ -564,13 +516,11 @@ namespace BitTorrent
void handleListenSucceededAlert(const lt::listen_succeeded_alert *p);
void handleListenFailedAlert(const lt::listen_failed_alert *p);
void handleExternalIPAlert(const lt::external_ip_alert *p);
void handleSessionErrorAlert(const lt::session_error_alert *p) const;
void handleSessionStatsAlert(const lt::session_stats_alert *p);
void handleAlertsDroppedAlert(const lt::alerts_dropped_alert *p) const;
void handleStorageMovedAlert(const lt::storage_moved_alert *p);
void handleStorageMovedFailedAlert(const lt::storage_moved_failed_alert *p);
void handleSocks5Alert(const lt::socks5_alert *p) const;
void handleI2PAlert(const lt::i2p_alert *p) const;
void handleTrackerAlert(const lt::tracker_alert *a);
#ifdef QBT_USES_LIBTORRENT2
void handleTorrentConflictAlert(const lt::torrent_conflict_alert *a);
@@ -579,8 +529,8 @@ namespace BitTorrent
TorrentImpl *createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params);
void saveResumeData();
void saveTorrentsQueue();
void removeTorrentsQueue();
void saveTorrentsQueue() const;
void removeTorrentsQueue() const;
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
@@ -596,7 +546,6 @@ namespace BitTorrent
// BitTorrent
lt::session *m_nativeSession = nullptr;
NativeSessionExtension *m_nativeSessionExtension = nullptr;
bool m_deferredConfigureScheduled = false;
bool m_IPFilteringConfigured = false;
@@ -627,8 +576,6 @@ namespace BitTorrent
CachedSettingValue<int> m_sendBufferLowWatermark;
CachedSettingValue<int> m_sendBufferWatermarkFactor;
CachedSettingValue<int> m_connectionSpeed;
CachedSettingValue<int> m_socketSendBufferSize;
CachedSettingValue<int> m_socketReceiveBufferSize;
CachedSettingValue<int> m_socketBacklogSize;
CachedSettingValue<bool> m_isAnonymousModeEnabled;
CachedSettingValue<bool> m_isQueueingEnabled;
@@ -665,8 +612,6 @@ namespace BitTorrent
CachedSettingValue<QString> m_additionalTrackers;
CachedSettingValue<qreal> m_globalMaxRatio;
CachedSettingValue<int> m_globalMaxSeedingMinutes;
CachedSettingValue<int> m_globalMaxInactiveSeedingMinutes;
CachedSettingValue<bool> m_isAddTorrentToQueueTop;
CachedSettingValue<bool> m_isAddTorrentPaused;
CachedSettingValue<Torrent::StopCondition> m_torrentStopCondition;
CachedSettingValue<TorrentContentLayout> m_torrentContentLayout;
@@ -690,6 +635,7 @@ namespace BitTorrent
CachedSettingValue<int> m_encryption;
CachedSettingValue<int> m_maxActiveCheckingTorrents;
CachedSettingValue<bool> m_isProxyPeerConnectionsEnabled;
CachedSettingValue<bool> m_isProxyHostnameLookupEnabled;
CachedSettingValue<ChokingAlgorithm> m_chokingAlgorithm;
CachedSettingValue<SeedChokingAlgorithm> m_seedChokingAlgorithm;
CachedSettingValue<QStringList> m_storedTags;
@@ -712,15 +658,6 @@ namespace BitTorrent
CachedSettingValue<QStringList> m_excludedFileNames;
CachedSettingValue<QStringList> m_bannedIPs;
CachedSettingValue<ResumeDataStorageType> m_resumeDataStorageType;
CachedSettingValue<bool> m_isMergeTrackersEnabled;
CachedSettingValue<bool> m_isI2PEnabled;
CachedSettingValue<QString> m_I2PAddress;
CachedSettingValue<int> m_I2PPort;
CachedSettingValue<bool> m_I2PMixedMode;
CachedSettingValue<int> m_I2PInboundQuantity;
CachedSettingValue<int> m_I2POutboundQuantity;
CachedSettingValue<int> m_I2PInboundLength;
CachedSettingValue<int> m_I2POutboundLength;
bool m_isRestored = false;
@@ -739,8 +676,6 @@ namespace BitTorrent
qint64 m_previouslyUploaded = 0;
qint64 m_previouslyDownloaded = 0;
bool m_torrentsQueueChanged = false;
bool m_needSaveTorrentsQueue = false;
bool m_refreshEnqueued = false;
QTimer *m_seedingLimitTimer = nullptr;
QTimer *m_resumeDataTimer = nullptr;
@@ -751,11 +686,10 @@ namespace BitTorrent
QPointer<Tracker> m_tracker;
Utils::Thread::UniquePtr m_ioThread;
QThreadPool *m_asyncWorker = nullptr;
ResumeDataStorage *m_resumeDataStorage = nullptr;
FileSearcher *m_fileSearcher = nullptr;
QHash<TorrentID, lt::torrent_handle> m_downloadedMetadata;
QSet<TorrentID> m_downloadedMetadata;
QHash<TorrentID, TorrentImpl *> m_torrents;
QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID;
@@ -767,9 +701,7 @@ namespace BitTorrent
QMap<QString, CategoryOptions> m_categories;
QSet<QString> m_tags;
// This field holds amounts of peers reported by trackers in their responses to announces
// (torrent.tracker_name.tracker_local_endpoint.num_peers)
QHash<lt::torrent_handle, QHash<std::string, QMap<TrackerEntry::Endpoint, int>>> m_updatedTrackerEntries;
QHash<Torrent *, QSet<QString>> m_updatedTrackerEntries;
// I/O errored torrents
QSet<TorrentID> m_recentErroredTorrents;
@@ -790,15 +722,6 @@ namespace BitTorrent
bool m_needUpgradeDownloadPath = false;
// All port mapping related routines are invoked from working thread
// so there are no synchronization used. If multithreaded access is
// ever required, synchronization should also be provided.
bool m_isPortMappingEnabled = false;
QHash<quint16, std::vector<lt::port_mapping_t>> m_mappedPorts;
QTimer *m_wakeupCheckTimer = nullptr;
QDateTime m_wakeupCheckTimestamp;
friend void Session::initInstance();
friend void Session::freeInstance();
friend Session *Session::instance();

View File

@@ -52,12 +52,8 @@ namespace BitTorrent
const int Torrent::USE_GLOBAL_SEEDING_TIME = -2;
const int Torrent::NO_SEEDING_TIME_LIMIT = -1;
const int Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME = -2;
const int Torrent::NO_INACTIVE_SEEDING_TIME_LIMIT = -1;
const qreal Torrent::MAX_RATIO = 9999;
const int Torrent::MAX_SEEDING_TIME = 525600;
const int Torrent::MAX_INACTIVE_SEEDING_TIME = 525600;
TorrentID Torrent::id() const
{

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -36,6 +36,7 @@
#include <libtorrent/address.hpp>
#include <libtorrent/alert_types.hpp>
#include <libtorrent/create_torrent.hpp>
#include <libtorrent/magnet_uri.hpp>
#include <libtorrent/session.hpp>
#include <libtorrent/storage_defs.hpp>
#include <libtorrent/time.hpp>
@@ -46,12 +47,11 @@
#include <QByteArray>
#include <QDebug>
#include <QPointer>
#include <QFile>
#include <QSet>
#include <QStringList>
#include <QUrl>
#include "base/exceptions.h"
#include "base/global.h"
#include "base/logger.h"
#include "base/preferences.h"
@@ -82,10 +82,10 @@ namespace
#ifdef QBT_USES_LIBTORRENT2
void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
, const lt::info_hash_t &hashes, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
, const lt::info_hash_t &hashes, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
#else
void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
#endif
{
Q_ASSERT(trackerEntry.url == QString::fromStdString(nativeEntry.url));
@@ -98,7 +98,7 @@ namespace
QString firstTrackerMessage;
QString firstErrorMessage;
#ifdef QBT_USES_LIBTORRENT2
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size()) * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1);
const auto numEndpoints = static_cast<qsizetype>(nativeEntry.endpoints.size() * ((hashes.has_v1() && hashes.has_v2()) ? 2 : 1));
for (const lt::announce_endpoint &endpoint : nativeEntry.endpoints)
{
for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2})
@@ -236,7 +236,7 @@ namespace
TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
, const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
: Torrent(session)
: QObject(session)
, m_session(session)
, m_nativeSession(nativeSession)
, m_nativeHandle(nativeHandle)
@@ -252,10 +252,9 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
, m_tags(params.tags)
, m_ratioLimit(params.ratioLimit)
, m_seedingTimeLimit(params.seedingTimeLimit)
, m_inactiveSeedingTimeLimit(params.inactiveSeedingTimeLimit)
, m_operatingMode(params.operatingMode)
, m_contentLayout(params.contentLayout)
, m_hasFinishedStatus(params.hasFinishedStatus)
, m_hasSeedStatus(params.hasSeedStatus)
, m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
, m_useAutoTMM(params.useAutoTMM)
, m_isStopped(params.stopped)
@@ -280,7 +279,6 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
, LT::toNative(m_ltAddTorrentParams.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
m_completedFiles.fill(static_cast<bool>(m_ltAddTorrentParams.flags & lt::torrent_flags::seed_mode), filesCount);
m_filesProgress.resize(filesCount);
for (int i = 0; i < filesCount; ++i)
{
@@ -303,14 +301,8 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
m_trackerEntries.reserve(static_cast<decltype(m_trackerEntries)::size_type>(extensionData->trackers.size()));
for (const lt::announce_entry &announceEntry : extensionData->trackers)
m_trackerEntries.append({QString::fromStdString(announceEntry.url), announceEntry.tier});
m_urlSeeds.reserve(static_cast<decltype(m_urlSeeds)::size_type>(extensionData->urlSeeds.size()));
for (const std::string &urlSeed : extensionData->urlSeeds)
m_urlSeeds.append(QString::fromStdString(urlSeed));
m_nativeStatus = extensionData->status;
if (hasMetadata())
updateProgress();
updateState();
if (hasMetadata())
@@ -332,14 +324,14 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
// Remove .unwanted directory if empty
const Path newPath = spath / newRelPath;
qDebug() << "Attempting to remove \".unwanted\" folder at " << (newPath / Path(u".unwanted"_s)).toString();
Utils::Fs::rmdir(newPath / Path(u".unwanted"_s));
qDebug() << "Attempting to remove \".unwanted\" folder at " << (newPath / Path(u".unwanted"_qs)).toString();
Utils::Fs::rmdir(newPath / Path(u".unwanted"_qs));
}
}
// == END UPGRADE CODE ==
}
TorrentImpl::~TorrentImpl() = default;
TorrentImpl::~TorrentImpl() {}
bool TorrentImpl::isValid() const
{
@@ -432,16 +424,13 @@ void TorrentImpl::setSavePath(const Path &path)
if (resolvedPath == savePath())
return;
if (isFinished() || m_hasFinishedStatus || downloadPath().isEmpty())
{
moveStorage(resolvedPath, MoveStorageContext::ChangeSavePath);
}
else
{
m_savePath = resolvedPath;
m_session->handleTorrentSavePathChanged(this);
m_session->handleTorrentNeedSaveResumeData(this);
}
m_savePath = resolvedPath;
m_session->handleTorrentNeedSaveResumeData(this);
const bool isFinished = isSeed() || m_hasSeedStatus;
if (isFinished || downloadPath().isEmpty())
moveStorage(savePath(), MoveStorageMode::KeepExistingFiles);
}
Path TorrentImpl::downloadPath() const
@@ -459,17 +448,13 @@ void TorrentImpl::setDownloadPath(const Path &path)
if (resolvedPath == m_downloadPath)
return;
const bool isIncomplete = !(isFinished() || m_hasFinishedStatus);
if (isIncomplete)
{
moveStorage((resolvedPath.isEmpty() ? savePath() : resolvedPath), MoveStorageContext::ChangeDownloadPath);
}
else
{
m_downloadPath = resolvedPath;
m_session->handleTorrentSavePathChanged(this);
m_session->handleTorrentNeedSaveResumeData(this);
}
m_downloadPath = resolvedPath;
m_session->handleTorrentNeedSaveResumeData(this);
const bool isFinished = isSeed() || m_hasSeedStatus;
if (!isFinished)
moveStorage((m_downloadPath.isEmpty() ? savePath() : m_downloadPath), MoveStorageMode::KeepExistingFiles);
}
Path TorrentImpl::rootPath() const
@@ -548,6 +533,9 @@ Path TorrentImpl::wantedActualPath(int index, const Path &path) const
QVector<TrackerEntry> TorrentImpl::trackers() const
{
if (!m_updatedTrackerEntries.isEmpty())
refreshTrackerEntries();
return m_trackerEntries;
}
@@ -600,9 +588,6 @@ void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
{
// TODO: use std::erase_if() in C++20
trackers.erase(std::remove_if(trackers.begin(), trackers.end(), [](const TrackerEntry &entry) { return entry.url.isEmpty(); }), trackers.end());
// Filter out duplicate trackers
const auto uniqueTrackers = QSet<TrackerEntry>(trackers.cbegin(), trackers.cend());
trackers = QVector<TrackerEntry>(uniqueTrackers.cbegin(), uniqueTrackers.cend());
std::sort(trackers.begin(), trackers.end()
, [](const TrackerEntry &lhs, const TrackerEntry &rhs) { return lhs.tier < rhs.tier; });
@@ -625,95 +610,63 @@ void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
QVector<QUrl> TorrentImpl::urlSeeds() const
{
return m_urlSeeds;
const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
QVector<QUrl> urlSeeds;
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
for (const std::string &urlSeed : currentSeeds)
urlSeeds.append(QString::fromStdString(urlSeed));
return urlSeeds;
}
void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
{
m_session->invokeAsync([urlSeeds, session = m_session
, nativeHandle = m_nativeHandle
, thisTorrent = QPointer<TorrentImpl>(this)]
const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
QVector<QUrl> addedUrlSeeds;
addedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds)
{
try
const std::string nativeUrl = url.toString().toStdString();
if (currentSeeds.find(nativeUrl) == currentSeeds.end())
{
const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
QVector<QUrl> currentSeeds;
currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
for (const std::string &urlSeed : nativeSeeds)
currentSeeds.append(QString::fromStdString(urlSeed));
QVector<QUrl> addedUrlSeeds;
addedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds)
{
if (!currentSeeds.contains(url))
{
nativeHandle.add_url_seed(url.toString().toStdString());
addedUrlSeeds.append(url);
}
}
currentSeeds.append(addedUrlSeeds);
session->invoke([session, thisTorrent, currentSeeds, addedUrlSeeds]
{
if (!thisTorrent)
return;
thisTorrent->m_urlSeeds = currentSeeds;
if (!addedUrlSeeds.isEmpty())
{
session->handleTorrentNeedSaveResumeData(thisTorrent);
session->handleTorrentUrlSeedsAdded(thisTorrent, addedUrlSeeds);
}
});
m_nativeHandle.add_url_seed(nativeUrl);
addedUrlSeeds << url;
}
catch (const std::exception &) {}
});
}
if (!addedUrlSeeds.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds);
}
}
void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
{
m_session->invokeAsync([urlSeeds, session = m_session
, nativeHandle = m_nativeHandle
, thisTorrent = QPointer<TorrentImpl>(this)]
const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
QVector<QUrl> removedUrlSeeds;
removedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds)
{
try
const std::string nativeUrl = url.toString().toStdString();
if (currentSeeds.find(nativeUrl) != currentSeeds.end())
{
const std::set<std::string> nativeSeeds = nativeHandle.url_seeds();
QVector<QUrl> currentSeeds;
currentSeeds.reserve(static_cast<decltype(currentSeeds)::size_type>(nativeSeeds.size()));
for (const std::string &urlSeed : nativeSeeds)
currentSeeds.append(QString::fromStdString(urlSeed));
QVector<QUrl> removedUrlSeeds;
removedUrlSeeds.reserve(urlSeeds.size());
for (const QUrl &url : urlSeeds)
{
if (currentSeeds.removeOne(url))
{
nativeHandle.remove_url_seed(url.toString().toStdString());
removedUrlSeeds.append(url);
}
}
session->invoke([session, thisTorrent, currentSeeds, removedUrlSeeds]
{
if (!thisTorrent)
return;
thisTorrent->m_urlSeeds = currentSeeds;
if (!removedUrlSeeds.isEmpty())
{
session->handleTorrentNeedSaveResumeData(thisTorrent);
session->handleTorrentUrlSeedsRemoved(thisTorrent, removedUrlSeeds);
}
});
m_nativeHandle.remove_url_seed(nativeUrl);
removedUrlSeeds << url;
}
catch (const std::exception &) {}
});
}
if (!removedUrlSeeds.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds);
}
}
void TorrentImpl::clearPeers()
@@ -867,11 +820,6 @@ int TorrentImpl::seedingTimeLimit() const
return m_seedingTimeLimit;
}
int TorrentImpl::inactiveSeedingTimeLimit() const
{
return m_inactiveSeedingTimeLimit;
}
Path TorrentImpl::filePath(const int index) const
{
Q_ASSERT(index >= 0);
@@ -1025,7 +973,7 @@ bool TorrentImpl::isErrored() const
|| (m_state == TorrentState::Error));
}
bool TorrentImpl::isFinished() const
bool TorrentImpl::isSeed() const
{
return ((m_nativeStatus.state == lt::torrent_status::finished)
|| (m_nativeStatus.state == lt::torrent_status::seeding));
@@ -1081,9 +1029,9 @@ void TorrentImpl::updateState()
else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isPaused())
{
// If the torrent is not just in the "checking" state, but is being actually checked
m_state = m_hasFinishedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
}
else if (isFinished())
else if (isSeed())
{
if (isPaused())
m_state = TorrentState::PausedUploading;
@@ -1171,12 +1119,11 @@ qlonglong TorrentImpl::eta() const
const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
if (isFinished())
if (isSeed())
{
const qreal maxRatioValue = maxRatio();
const int maxSeedingTimeValue = maxSeedingTime();
const int maxInactiveSeedingTimeValue = maxInactiveSeedingTime();
if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0)) return MAX_ETA;
if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0)) return MAX_ETA;
qlonglong ratioEta = MAX_ETA;
@@ -1199,15 +1146,7 @@ qlonglong TorrentImpl::eta() const
seedingTimeEta = 0;
}
qlonglong inactiveSeedingTimeEta = MAX_ETA;
if (maxInactiveSeedingTimeValue >= 0)
{
inactiveSeedingTimeEta = (maxInactiveSeedingTimeValue * 60) - timeSinceActivity();
inactiveSeedingTimeEta = std::max<qlonglong>(inactiveSeedingTimeEta, 0);
}
return std::min({ratioEta, seedingTimeEta, inactiveSeedingTimeEta});
return std::min(ratioEta, seedingTimeEta);
}
if (!speedAverage.download) return MAX_ETA;
@@ -1220,20 +1159,20 @@ QVector<qreal> TorrentImpl::filesProgress() const
if (!hasMetadata())
return {};
const int count = m_filesProgress.size();
Q_ASSERT(count == filesCount());
if (Q_UNLIKELY(count != filesCount()))
return {};
if (m_completedFiles.count(true) == filesCount())
return QVector<qreal>(filesCount(), 1);
if (m_completedFiles.count(true) == count)
return QVector<qreal>(count, 1);
std::vector<int64_t> fp;
m_nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity);
const int count = filesCount();
const auto nativeIndexes = m_torrentInfo.nativeIndexes();
QVector<qreal> result;
result.reserve(count);
for (int i = 0; i < count; ++i)
{
const int64_t progress = m_filesProgress.at(i);
const int64_t size = fileSize(i);
const int64_t progress = fp[LT::toUnderlyingType(nativeIndexes[i])];
const qlonglong size = fileSize(i);
if ((size <= 0) || (progress == size))
result << 1;
else
@@ -1352,13 +1291,15 @@ QVector<PeerInfo> TorrentImpl::peers() const
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
for (const lt::peer_info &peer : nativePeers)
peers.append(PeerInfo(peer, pieces()));
peers << PeerInfo(this, peer);
return peers;
}
QBitArray TorrentImpl::pieces() const
{
if (m_pieces.isEmpty())
m_pieces = LT::toQBitArray(m_nativeStatus.pieces);
return m_pieces;
}
@@ -1404,14 +1345,6 @@ int TorrentImpl::maxSeedingTime() const
return m_seedingTimeLimit;
}
int TorrentImpl::maxInactiveSeedingTime() const
{
if (m_inactiveSeedingTimeLimit == USE_GLOBAL_INACTIVE_SEEDING_TIME)
return m_session->globalMaxInactiveSeedingMinutes();
return m_inactiveSeedingTimeLimit;
}
qreal TorrentImpl::realRatio() const
{
const int64_t upload = m_nativeStatus.all_time_upload;
@@ -1511,8 +1444,7 @@ void TorrentImpl::forceDHTAnnounce()
void TorrentImpl::forceRecheck()
{
if (!hasMetadata())
return;
if (!hasMetadata()) return;
m_nativeHandle.force_recheck();
// We have to force update the cached state, otherwise someone will be able to get
@@ -1521,12 +1453,7 @@ void TorrentImpl::forceRecheck()
m_hasMissingFiles = false;
m_unchecked = false;
m_completedFiles.fill(false);
m_filesProgress.fill(0);
m_pieces.fill(false);
m_nativeStatus.pieces.clear_all();
m_nativeStatus.num_pieces = 0;
if (isPaused())
{
@@ -1606,29 +1533,47 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames)
{
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
endReceivedMetadataHandling(savePath, fileNames);
endReceivedMetadataHandling(savePath, fileNames);
}
TrackerEntry TorrentImpl::updateTrackerEntry(const lt::announce_entry &announceEntry, const QMap<TrackerEntry::Endpoint, int> &updateInfo)
void TorrentImpl::updatePeerCount(const QString &trackerURL, const TrackerEntry::Endpoint &endpoint, const int count)
{
const auto it = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end()
, [&announceEntry](const TrackerEntry &trackerEntry)
m_updatedTrackerEntries[trackerURL][endpoint] = count;
}
void TorrentImpl::invalidateTrackerEntry(const QString &trackerURL)
{
std::ignore = m_updatedTrackerEntries[trackerURL];
}
void TorrentImpl::refreshTrackerEntries() const
{
const std::vector<lt::announce_entry> nativeTrackers = m_nativeHandle.trackers();
Q_ASSERT(nativeTrackers.size() == m_trackerEntries.size());
for (const lt::announce_entry &announceEntry : nativeTrackers)
{
return (trackerEntry.url == QString::fromStdString(announceEntry.url));
});
const auto trackerURL = QString::fromStdString(announceEntry.url);
const auto updatedTrackerIter = m_updatedTrackerEntries.find(trackerURL);
if (updatedTrackerIter == m_updatedTrackerEntries.end())
continue;
Q_ASSERT(it != m_trackerEntries.end());
// TODO: use [[unlikely]] in C++20
if (Q_UNLIKELY(it == m_trackerEntries.end()))
return {};
const auto trackerIter = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end()
, [&trackerURL](const TrackerEntry &trackerEntry)
{
return (trackerEntry.url == trackerURL);
});
Q_ASSERT(trackerIter != m_trackerEntries.end());
TrackerEntry &trackerEntry = *trackerIter;
#ifdef QBT_USES_LIBTORRENT2
::updateTrackerEntry(*it, announceEntry, nativeHandle().info_hashes(), updateInfo);
updateTrackerEntry(trackerEntry, announceEntry, m_nativeHandle.info_hashes(), updatedTrackerIter.value());
#else
::updateTrackerEntry(*it, announceEntry, updateInfo);
updateTrackerEntry(trackerEntry, announceEntry, updatedTrackerIter.value());
#endif
return *it;
}
m_updatedTrackerEntries.clear();
}
std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
@@ -1640,13 +1585,7 @@ std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo()
void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames)
{
Q_ASSERT(m_maintenanceJob == MaintenanceJob::HandleMetadata);
if (Q_UNLIKELY(m_maintenanceJob != MaintenanceJob::HandleMetadata))
return;
Q_ASSERT(m_filePaths.isEmpty());
if (Q_UNLIKELY(!m_filePaths.isEmpty()))
m_filePaths.clear();
lt::add_torrent_params &p = m_ltAddTorrentParams;
@@ -1655,17 +1594,15 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
m_filePriorities.reserve(filesCount());
const auto nativeIndexes = m_torrentInfo.nativeIndexes();
p.file_priorities = resized(p.file_priorities, metadata->files().num_files()
, LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
, LT::toNative(p.file_priorities.empty() ? DownloadPriority::Normal : DownloadPriority::Ignored));
m_completedFiles.fill(static_cast<bool>(p.flags & lt::torrent_flags::seed_mode), filesCount());
m_filesProgress.resize(filesCount());
updateProgress();
for (int i = 0; i < fileNames.size(); ++i)
{
const auto nativeIndex = nativeIndexes.at(i);
const Path &actualFilePath = fileNames.at(i);
const Path actualFilePath = fileNames.at(i);
p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
const Path filePath = actualFilePath.removedExtension(QB_EXT);
@@ -1705,13 +1642,9 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
}
void TorrentImpl::reload()
try
{
m_completedFiles.fill(false);
m_filesProgress.fill(0);
m_pieces.fill(false);
m_nativeStatus.pieces.clear_all();
m_nativeStatus.num_pieces = 0;
m_pieces.clear();
const auto queuePos = m_nativeHandle.queue_position();
@@ -1748,11 +1681,6 @@ try
updateState();
}
catch (const lt::system_error &err)
{
throw RuntimeError(tr("Failed to reload torrent. Torrent: %1. Reason: %2")
.arg(id().toString(), QString::fromLocal8Bit(err.what())));
}
void TorrentImpl::pause()
{
@@ -1807,7 +1735,7 @@ void TorrentImpl::resume(const TorrentOperatingMode mode)
}
}
void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext context)
void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageMode mode)
{
if (!hasMetadata())
{
@@ -1815,9 +1743,7 @@ void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext cont
return;
}
const auto mode = (context == MoveStorageContext::AdjustCurrentLocation)
? MoveStorageMode::Overwrite : MoveStorageMode::KeepExistingFiles;
if (m_session->addMoveTorrentStorageJob(this, newPath, mode, context))
if (m_session->addMoveTorrentStorageJob(this, newPath, mode))
{
if (!m_storageIsMoving)
{
@@ -1843,17 +1769,16 @@ void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
updateStatus(nativeStatus);
}
void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const MoveStorageContext context, const bool hasOutstandingJob)
void TorrentImpl::handleMoveStorageJobFinished(const Path &path, const bool hasOutstandingJob)
{
if (context == MoveStorageContext::ChangeSavePath)
m_savePath = path;
else if (context == MoveStorageContext::ChangeDownloadPath)
m_downloadPath = path;
m_storageIsMoving = hasOutstandingJob;
m_nativeStatus.save_path = path.toString().toStdString();
m_session->handleTorrentSavePathChanged(this);
m_session->handleTorrentNeedSaveResumeData(this);
m_storageIsMoving = hasOutstandingJob;
if (actualStorageLocation() != path)
{
m_nativeStatus.save_path = path.toString().toStdString();
m_session->handleTorrentSavePathChanged(this);
}
if (!m_storageIsMoving)
{
@@ -1895,9 +1820,9 @@ void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
if (!m_hasMissingFiles)
{
if ((progress() < 1.0) && (wantedSize() > 0))
m_hasFinishedStatus = false;
m_hasSeedStatus = false;
else if (progress() == 1.0)
m_hasFinishedStatus = true;
m_hasSeedStatus = true;
adjustStorageLocation();
manageIncompleteFiles();
@@ -1924,7 +1849,7 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
Q_UNUSED(p);
m_hasMissingFiles = false;
if (m_hasFinishedStatus)
if (m_hasSeedStatus)
return;
m_statusUpdatedTriggers.enqueue([this]()
@@ -1941,7 +1866,7 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
}
else
{
m_hasFinishedStatus = true;
m_hasSeedStatus = true;
if (isMoveInProgress() || (m_renameCount > 0))
m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
@@ -1963,14 +1888,6 @@ void TorrentImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p)
void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_alert *p)
{
if (m_ltAddTorrentParams.url_seeds != p->params.url_seeds)
{
// URL seed list have been changed by libtorrent for some reason, so we need to update cached one.
// Unfortunately, URL seed list containing in "resume data" is generated according to different rules
// than the list we usually cache, so we have to request it from the appropriate source.
fetchURLSeeds([this](const QVector<QUrl> &urlSeeds) { m_urlSeeds = urlSeeds; });
}
if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
{
Q_ASSERT(m_indexMap.isEmpty());
@@ -2054,9 +1971,8 @@ void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
resumeData.contentLayout = m_contentLayout;
resumeData.ratioLimit = m_ratioLimit;
resumeData.seedingTimeLimit = m_seedingTimeLimit;
resumeData.inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit;
resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
resumeData.hasFinishedStatus = m_hasFinishedStatus;
resumeData.hasSeedStatus = m_hasSeedStatus;
resumeData.stopped = m_isStopped;
resumeData.stopCondition = m_stopCondition;
resumeData.operatingMode = m_operatingMode;
@@ -2147,7 +2063,7 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
const int fileIndex = m_indexMap.value(p->index, -1);
Q_ASSERT(fileIndex >= 0);
m_completedFiles.setBit(fileIndex);
m_completedFiles[fileIndex] = true;
if (m_session->isAppendExtensionEnabled())
{
@@ -2191,7 +2107,7 @@ void TorrentImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const
{
LogMsg((tr("Performance alert: %1. More info: %2").arg(QString::fromStdString(p->message()), u"https://libtorrent.org/reference-Alerts.html#enum-performance-warning-t"_s))
LogMsg((tr("Performance alert: %1. More info: %2").arg(QString::fromStdString(p->message()), u"https://libtorrent.org/reference-Alerts.html#enum-performance-warning-t"_qs))
, Log::INFO);
}
@@ -2282,10 +2198,11 @@ void TorrentImpl::manageIncompleteFiles()
void TorrentImpl::adjustStorageLocation()
{
const Path downloadPath = this->downloadPath();
const Path targetPath = ((isFinished() || m_hasFinishedStatus || downloadPath.isEmpty()) ? savePath() : downloadPath);
const bool isFinished = isSeed() || m_hasSeedStatus;
const Path targetPath = ((isFinished || downloadPath.isEmpty()) ? savePath() : downloadPath);
if ((targetPath != actualStorageLocation()) || isMoveInProgress())
moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation);
moveStorage(targetPath, MoveStorageMode::Overwrite);
}
void TorrentImpl::doRenameFile(int index, const Path &path)
@@ -2306,24 +2223,17 @@ lt::torrent_handle TorrentImpl::nativeHandle() const
return m_nativeHandle;
}
void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
bool TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
{
if (hasMetadata())
return;
return false;
m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo]
{
try
{
#ifdef QBT_USES_LIBTORRENT2
nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
return m_nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
#else
const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
return m_nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
#endif
}
catch (const std::exception &) {}
});
}
Torrent::StopCondition TorrentImpl::stopCondition() const
@@ -2355,11 +2265,8 @@ bool TorrentImpl::isMoveInProgress() const
void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
{
const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus);
if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
updateProgress();
m_pieces.clear();
m_nativeStatus = nativeStatus;
updateState();
m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
@@ -2379,44 +2286,6 @@ void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
std::invoke(m_statusUpdatedTriggers.dequeue());
}
void TorrentImpl::updateProgress()
{
Q_ASSERT(hasMetadata());
if (Q_UNLIKELY(!hasMetadata()))
return;
Q_ASSERT(!m_filesProgress.isEmpty());
if (Q_UNLIKELY(m_filesProgress.isEmpty()))
m_filesProgress.resize(filesCount());
const QBitArray oldPieces = std::exchange(m_pieces, LT::toQBitArray(m_nativeStatus.pieces));
const QBitArray newPieces = m_pieces ^ oldPieces;
const int64_t pieceSize = m_torrentInfo.pieceLength();
for (qsizetype index = 0; index < newPieces.size(); ++index)
{
if (!newPieces.at(index))
continue;
int64_t size = m_torrentInfo.pieceLength(index);
int64_t pieceOffset = index * pieceSize;
for (const int fileIndex : asConst(m_torrentInfo.fileIndicesForPiece(index)))
{
const int64_t fileOffsetInPiece = pieceOffset - m_torrentInfo.fileOffset(fileIndex);
const int64_t add = std::min<int64_t>((m_torrentInfo.fileSize(fileIndex) - fileOffsetInPiece), size);
m_filesProgress[fileIndex] += add;
size -= add;
if (size <= 0)
break;
pieceOffset += add;
}
}
}
void TorrentImpl::setRatioLimit(qreal limit)
{
if (limit < USE_GLOBAL_RATIO)
@@ -2447,21 +2316,6 @@ void TorrentImpl::setSeedingTimeLimit(int limit)
}
}
void TorrentImpl::setInactiveSeedingTimeLimit(int limit)
{
if (limit < USE_GLOBAL_INACTIVE_SEEDING_TIME)
limit = NO_INACTIVE_SEEDING_TIME_LIMIT;
else if (limit > MAX_INACTIVE_SEEDING_TIME)
limit = MAX_SEEDING_TIME;
if (m_inactiveSeedingTimeLimit != limit)
{
m_inactiveSeedingTimeLimit = limit;
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentShareLimitChanged(this);
}
}
void TorrentImpl::setUploadLimit(const int limit)
{
const int cleanValue = cleanLimitValue(limit);
@@ -2543,39 +2397,7 @@ void TorrentImpl::flushCache() const
QString TorrentImpl::createMagnetURI() const
{
QString ret = u"magnet:?"_s;
const SHA1Hash infoHash1 = infoHash().v1();
if (infoHash1.isValid())
{
ret += u"xt=urn:btih:" + infoHash1.toString();
}
const SHA256Hash infoHash2 = infoHash().v2();
if (infoHash2.isValid())
{
if (infoHash1.isValid())
ret += u'&';
ret += u"xt=urn:btmh:1220" + infoHash2.toString();
}
const QString displayName = name();
if (displayName != id().toString())
{
ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
}
for (const TrackerEntry &tracker : asConst(trackers()))
{
ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
}
for (const QUrl &urlSeed : asConst(urlSeeds()))
{
ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
}
return ret;
return QString::fromStdString(lt::make_magnet_uri(m_nativeHandle));
}
nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
@@ -2631,129 +2453,6 @@ nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) cons
return {};
}
void TorrentImpl::fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle, allPieces = pieces()]() -> QVector<PeerInfo>
{
try
{
std::vector<lt::peer_info> nativePeers;
nativeHandle.get_peer_info(nativePeers);
QVector<PeerInfo> peers;
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
for (const lt::peer_info &peer : nativePeers)
peers.append(PeerInfo(peer, allPieces));
return peers;
}
catch (const std::exception &) {}
return {};
}
, std::move(resultHandler));
}
void TorrentImpl::fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<QUrl>
{
try
{
const std::set<std::string> currentSeeds = nativeHandle.url_seeds();
QVector<QUrl> urlSeeds;
urlSeeds.reserve(static_cast<decltype(urlSeeds)::size_type>(currentSeeds.size()));
for (const std::string &urlSeed : currentSeeds)
urlSeeds.append(QString::fromStdString(urlSeed));
return urlSeeds;
}
catch (const std::exception &) {}
return {};
}
, std::move(resultHandler));
}
void TorrentImpl::fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle]() -> QVector<int>
{
try
{
std::vector<int> piecesAvailability;
nativeHandle.piece_availability(piecesAvailability);
return QVector<int>(piecesAvailability.cbegin(), piecesAvailability.cend());
}
catch (const std::exception &) {}
return {};
}
, std::move(resultHandler));
}
void TorrentImpl::fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QBitArray
{
try
{
#ifdef QBT_USES_LIBTORRENT2
const std::vector<lt::partial_piece_info> queue = nativeHandle.get_download_queue();
#else
std::vector<lt::partial_piece_info> queue;
nativeHandle.get_download_queue(queue);
#endif
QBitArray result;
result.resize(torrentInfo.piecesCount());
for (const lt::partial_piece_info &info : queue)
result.setBit(LT::toUnderlyingType(info.piece_index));
return result;
}
catch (const std::exception &) {}
return {};
}
, std::move(resultHandler));
}
void TorrentImpl::fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const
{
invokeAsync([nativeHandle = m_nativeHandle, torrentInfo = m_torrentInfo]() -> QVector<qreal>
{
if (!torrentInfo.isValid() || (torrentInfo.filesCount() <= 0))
return {};
try
{
std::vector<int> piecesAvailability;
nativeHandle.piece_availability(piecesAvailability);
const int filesCount = torrentInfo.filesCount();
// libtorrent returns empty array for seeding only torrents
if (piecesAvailability.empty())
return QVector<qreal>(filesCount, -1);
QVector<qreal> result;
result.reserve(filesCount);
for (int i = 0; i < filesCount; ++i)
{
const TorrentInfo::PieceRange filePieces = torrentInfo.filePieces(i);
int availablePieces = 0;
for (const int piece : filePieces)
availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
const qreal availability = filePieces.isEmpty()
? 1 // the file has no pieces, so it is available by default
: static_cast<qreal>(availablePieces) / filePieces.size();
result.append(availability);
}
return result;
}
catch (const std::exception &) {}
return {};
}
, std::move(resultHandler));
}
void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
{
if (!hasMetadata()) return;
@@ -2769,7 +2468,7 @@ void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
&& (priorities[i] > DownloadPriority::Ignored)
&& !m_completedFiles.at(i))
{
m_hasFinishedStatus = false;
m_hasSeedStatus = false;
break;
}
}
@@ -2817,19 +2516,3 @@ QVector<qreal> TorrentImpl::availableFileFractions() const
}
return res;
}
template <typename Func, typename Callback>
void TorrentImpl::invokeAsync(Func func, Callback resultHandler) const
{
m_session->invokeAsync([session = m_session
, func = std::move(func)
, resultHandler = std::move(resultHandler)
, thisTorrent = QPointer<const TorrentImpl>(this)]() mutable
{
session->invoke([result = func(), thisTorrent, resultHandler = std::move(resultHandler)]
{
if (thisTorrent)
resultHandler(result);
});
});
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015-2023 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015-2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -68,13 +68,6 @@ namespace BitTorrent
Overwrite
};
enum class MoveStorageContext
{
AdjustCurrentLocation,
ChangeSavePath,
ChangeDownloadPath
};
enum class MaintenanceJob
{
None,
@@ -84,13 +77,13 @@ namespace BitTorrent
struct FileErrorInfo
{
lt::error_code error;
lt::operation_t operation = lt::operation_t::unknown;
lt::operation_t operation;
};
class TorrentImpl final : public Torrent
class TorrentImpl final : public QObject, public Torrent
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(TorrentImpl)
Q_DECLARE_TR_FUNCTIONS(BitTorrent::TorrentImpl)
public:
TorrentImpl(SessionImpl *session, lt::session *nativeSession
@@ -138,7 +131,6 @@ namespace BitTorrent
QDateTime addedTime() const override;
qreal ratioLimit() const override;
int seedingTimeLimit() const override;
int inactiveSeedingTimeLimit() const override;
Path filePath(int index) const override;
Path actualFilePath(int index) const override;
@@ -147,7 +139,7 @@ namespace BitTorrent
QVector<DownloadPriority> filePriorities() const override;
TorrentInfo info() const override;
bool isFinished() const override;
bool isSeed() const override;
bool isPaused() const override;
bool isQueued() const override;
bool isForced() const override;
@@ -199,7 +191,6 @@ namespace BitTorrent
qreal distributedCopies() const override;
qreal maxRatio() const override;
int maxSeedingTime() const override;
int maxInactiveSeedingTime() const override;
qreal realRatio() const override;
int uploadPayloadRate() const override;
int downloadPayloadRate() const override;
@@ -222,7 +213,6 @@ namespace BitTorrent
void prioritizeFiles(const QVector<DownloadPriority> &priorities) override;
void setRatioLimit(qreal limit) override;
void setSeedingTimeLimit(int limit) override;
void setInactiveSeedingTimeLimit(int limit) override;
void setUploadLimit(int limit) override;
void setDownloadLimit(int limit) override;
void setSuperSeeding(bool enable) override;
@@ -237,7 +227,7 @@ namespace BitTorrent
void removeUrlSeeds(const QVector<QUrl> &urlSeeds) override;
bool connectPeer(const PeerAddress &peerAddress) override;
void clearPeers() override;
void setMetadata(const TorrentInfo &torrentInfo) override;
bool setMetadata(const TorrentInfo &torrentInfo) override;
StopCondition stopCondition() const override;
void setStopCondition(StopCondition stopCondition) override;
@@ -246,12 +236,6 @@ namespace BitTorrent
nonstd::expected<QByteArray, QString> exportToBuffer() const override;
nonstd::expected<void, QString> exportToFile(const Path &path) const override;
void fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const override;
void fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const override;
void fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const override;
void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const override;
void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const override;
bool needSaveResumeData() const;
// Session interface
@@ -262,17 +246,18 @@ namespace BitTorrent
void handleCategoryOptionsChanged();
void handleAppendExtensionToggled();
void saveResumeData(lt::resume_data_flags_t flags = {});
void handleMoveStorageJobFinished(const Path &path, MoveStorageContext context, bool hasOutstandingJob);
void handleMoveStorageJobFinished(const Path &path, bool hasOutstandingJob);
void fileSearchFinished(const Path &savePath, const PathList &fileNames);
TrackerEntry updateTrackerEntry(const lt::announce_entry &announceEntry, const QMap<TrackerEntry::Endpoint, int> &updateInfo);
void updatePeerCount(const QString &trackerURL, const TrackerEntry::Endpoint &endpoint, int count);
void invalidateTrackerEntry(const QString &trackerURL);
private:
using EventTrigger = std::function<void ()>;
std::shared_ptr<const lt::torrent_info> nativeTorrentInfo() const;
void refreshTrackerEntries() const;
void updateStatus(const lt::torrent_status &nativeStatus);
void updateProgress();
void updateState();
void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p);
@@ -299,7 +284,7 @@ namespace BitTorrent
Path wantedActualPath(int index, const Path &path) const;
void adjustStorageLocation();
void doRenameFile(int index, const Path &path);
void moveStorage(const Path &newPath, MoveStorageContext context);
void moveStorage(const Path &newPath, MoveStorageMode mode);
void manageIncompleteFiles();
void applyFirstLastPiecePriority(bool enabled);
@@ -309,9 +294,6 @@ namespace BitTorrent
nonstd::expected<lt::entry, QString> exportTorrent() const;
template <typename Func, typename Callback>
void invokeAsync(Func func, Callback resultHandler) const;
SessionImpl *const m_session = nullptr;
lt::session *m_nativeSession = nullptr;
lt::torrent_handle m_nativeHandle;
@@ -336,8 +318,10 @@ namespace BitTorrent
MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
QVector<TrackerEntry> m_trackerEntries;
QVector<QUrl> m_urlSeeds;
// TODO: Use QHash<TrackerEntry::Endpoint, int> once Qt5 is dropped.
using TrackerEntryUpdateInfo = QMap<TrackerEntry::Endpoint, int>;
mutable QHash<QString, TrackerEntryUpdateInfo> m_updatedTrackerEntries;
mutable QVector<TrackerEntry> m_trackerEntries;
FileErrorInfo m_lastFileError;
// Persistent data
@@ -348,10 +332,9 @@ namespace BitTorrent
TagSet m_tags;
qreal m_ratioLimit;
int m_seedingTimeLimit;
int m_inactiveSeedingTimeLimit;
TorrentOperatingMode m_operatingMode;
TorrentContentLayout m_contentLayout;
bool m_hasFinishedStatus;
bool m_hasSeedStatus;
bool m_hasMissingFiles = false;
bool m_hasFirstLastPiecePriority = false;
bool m_useAutoTMM;
@@ -365,7 +348,6 @@ namespace BitTorrent
int m_downloadLimit = 0;
int m_uploadLimit = 0;
QBitArray m_pieces;
QVector<std::int64_t> m_filesProgress;
mutable QBitArray m_pieces;
};
}

View File

@@ -42,10 +42,10 @@
#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 "common.h"
#include "infohash.h"
#include "trackerentry.h"
@@ -67,6 +67,12 @@ TorrentInfo::TorrentInfo(const lt::torrent_info &nativeInfo)
}
}
TorrentInfo::TorrentInfo(const TorrentInfo &other)
: m_nativeInfo {other.m_nativeInfo}
, m_nativeIndexes {other.m_nativeIndexes}
{
}
TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
{
if (this != &other)
@@ -86,11 +92,9 @@ nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data)
{
// 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());
, nullptr, BENCODE_DEPTH_LIMIT, BENCODE_TOKEN_LIMIT);
if (ec)
return nonstd::make_unexpected(QString::fromStdString(ec.message()));
@@ -103,21 +107,28 @@ nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data)
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const Path &path) noexcept
{
QFile file {path.data()};
if (!file.open(QIODevice::ReadOnly))
return nonstd::make_unexpected(file.errorString());
if (file.size() > MAX_TORRENT_SIZE)
return nonstd::make_unexpected(tr("File size exceeds max limit %1").arg(Utils::Misc::friendlyUnit(MAX_TORRENT_SIZE)));
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();
data = file.readAll();
}
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 nonstd::make_unexpected(tr("Torrent file read error: %1").arg(QString::fromLocal8Bit(e.what())));
}
if (data.size() != file.size())
return nonstd::make_unexpected(tr("Torrent file read error: size mismatch"));
file.close();
return load(data);
}

View File

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

View File

@@ -53,23 +53,23 @@ namespace
// constants
const int PEER_ID_SIZE = 20;
const QString ANNOUNCE_REQUEST_PATH = u"/announce"_s;
const QString ANNOUNCE_REQUEST_PATH = u"/announce"_qs;
const QString ANNOUNCE_REQUEST_COMPACT = u"compact"_s;
const QString ANNOUNCE_REQUEST_INFO_HASH = u"info_hash"_s;
const QString ANNOUNCE_REQUEST_IP = u"ip"_s;
const QString ANNOUNCE_REQUEST_LEFT = u"left"_s;
const QString ANNOUNCE_REQUEST_NO_PEER_ID = u"no_peer_id"_s;
const QString ANNOUNCE_REQUEST_NUM_WANT = u"numwant"_s;
const QString ANNOUNCE_REQUEST_PEER_ID = u"peer_id"_s;
const QString ANNOUNCE_REQUEST_PORT = u"port"_s;
const QString ANNOUNCE_REQUEST_COMPACT = u"compact"_qs;
const QString ANNOUNCE_REQUEST_INFO_HASH = u"info_hash"_qs;
const QString ANNOUNCE_REQUEST_IP = u"ip"_qs;
const QString ANNOUNCE_REQUEST_LEFT = u"left"_qs;
const QString ANNOUNCE_REQUEST_NO_PEER_ID = u"no_peer_id"_qs;
const QString ANNOUNCE_REQUEST_NUM_WANT = u"numwant"_qs;
const QString ANNOUNCE_REQUEST_PEER_ID = u"peer_id"_qs;
const QString ANNOUNCE_REQUEST_PORT = u"port"_qs;
const QString ANNOUNCE_REQUEST_EVENT = u"event"_s;
const QString ANNOUNCE_REQUEST_EVENT_COMPLETED = u"completed"_s;
const QString ANNOUNCE_REQUEST_EVENT_EMPTY = u"empty"_s;
const QString ANNOUNCE_REQUEST_EVENT_STARTED = u"started"_s;
const QString ANNOUNCE_REQUEST_EVENT_STOPPED = u"stopped"_s;
const QString ANNOUNCE_REQUEST_EVENT_PAUSED = u"paused"_s;
const QString ANNOUNCE_REQUEST_EVENT = u"event"_qs;
const QString ANNOUNCE_REQUEST_EVENT_COMPLETED = u"completed"_qs;
const QString ANNOUNCE_REQUEST_EVENT_EMPTY = u"empty"_qs;
const QString ANNOUNCE_REQUEST_EVENT_STARTED = u"started"_qs;
const QString ANNOUNCE_REQUEST_EVENT_STOPPED = u"stopped"_qs;
const QString ANNOUNCE_REQUEST_EVENT_PAUSED = u"paused"_qs;
const char ANNOUNCE_RESPONSE_COMPLETE[] = "complete";
const char ANNOUNCE_RESPONSE_EXTERNAL_IP[] = "external ip";
@@ -297,32 +297,32 @@ void Tracker::processAnnounceRequest()
// 1. info_hash
const auto infoHashIter = queryParams.find(ANNOUNCE_REQUEST_INFO_HASH);
if (infoHashIter == queryParams.end())
throw TrackerError(u"Missing \"info_hash\" parameter"_s);
throw TrackerError(u"Missing \"info_hash\" parameter"_qs);
const auto torrentID = TorrentID::fromString(QString::fromLatin1(infoHashIter->toHex()));
if (!torrentID.isValid())
throw TrackerError(u"Invalid \"info_hash\" parameter"_s);
throw TrackerError(u"Invalid \"info_hash\" parameter"_qs);
announceReq.torrentID = torrentID;
// 2. peer_id
const auto peerIdIter = queryParams.find(ANNOUNCE_REQUEST_PEER_ID);
if (peerIdIter == queryParams.end())
throw TrackerError(u"Missing \"peer_id\" parameter"_s);
throw TrackerError(u"Missing \"peer_id\" parameter"_qs);
if (peerIdIter->size() > PEER_ID_SIZE)
throw TrackerError(u"Invalid \"peer_id\" parameter"_s);
throw TrackerError(u"Invalid \"peer_id\" parameter"_qs);
announceReq.peer.peerId = *peerIdIter;
// 3. port
const auto portIter = queryParams.find(ANNOUNCE_REQUEST_PORT);
if (portIter == queryParams.end())
throw TrackerError(u"Missing \"port\" parameter"_s);
throw TrackerError(u"Missing \"port\" parameter"_qs);
const ushort portNum = portIter->toUShort();
if (portNum == 0)
throw TrackerError(u"Invalid \"port\" parameter"_s);
throw TrackerError(u"Invalid \"port\" parameter"_qs);
announceReq.peer.port = portNum;
@@ -332,7 +332,7 @@ void Tracker::processAnnounceRequest()
{
const int num = numWantIter->toInt();
if (num < 0)
throw TrackerError(u"Invalid \"numwant\" parameter"_s);
throw TrackerError(u"Invalid \"numwant\" parameter"_qs);
announceReq.numwant = num;
}
@@ -377,7 +377,7 @@ void Tracker::processAnnounceRequest()
}
else
{
throw TrackerError(u"Invalid \"event\" parameter"_s);
throw TrackerError(u"Invalid \"event\" parameter"_qs);
}
prepareAnnounceResponse(announceReq);

View File

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

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2016-2023 Mike Tzou (Chocobo1)
* Copyright (C) 2016 Mike Tzou
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -40,24 +40,18 @@ constexpr typename std::add_const_t<T> &asConst(T &t) noexcept { return t; }
// Forward rvalue as const
template <typename T>
constexpr typename std::add_const_t<T> asConst(T &&t) noexcept { return std::forward<T>(t); }
constexpr typename std::add_const_t<T> asConst(T &&t) noexcept { return std::move(t); }
// Prevent const rvalue arguments
template <typename T>
void asConst(const T &&) = delete;
#if (QT_VERSION < QT_VERSION_CHECK(6, 4, 0))
// https://doc.qt.io/qt-6/qstring.html#operator-22-22_s
inline QString operator"" _s(const char16_t *str, const std::size_t size)
{
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
// https://doc.qt.io/qt-6/qstring.html#operator-22-22_qs
inline QString operator"" _qs(const char16_t *str, const std::size_t size)
{
return QString::fromRawData(reinterpret_cast<const QChar *>(str), static_cast<int>(size));
#else
return operator""_qs(str, size);
#endif
}
#else
using namespace Qt::Literals::StringLiterals;
#endif
inline const QString TORRENT_FILE_EXTENSION = u".torrent"_s;
inline const QString TORRENT_FILE_EXTENSION = u".torrent"_qs;

View File

@@ -32,6 +32,7 @@
#include <QTcpSocket>
#include "base/logger.h"
#include "irequesthandler.h"
#include "requestparser.h"
#include "responsegenerator.h"
@@ -45,10 +46,6 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
{
m_socket->setParent(this);
// reserve common size for requests, don't use the max allowed size which is too big for
// memory constrained platforms
m_receivedData.reserve(1024 * 1024);
// reset timer when there are activity
m_idleTimer.start();
connect(m_socket, &QIODevice::readyRead, this, [this]()
@@ -69,18 +66,7 @@ Connection::~Connection()
void Connection::read()
{
// reuse existing buffer and avoid unnecessary memory allocation/relocation
const qsizetype previousSize = m_receivedData.size();
const qint64 bytesAvailable = m_socket->bytesAvailable();
m_receivedData.resize(previousSize + bytesAvailable);
const qint64 bytesRead = m_socket->read((m_receivedData.data() + previousSize), bytesAvailable);
if (Q_UNLIKELY(bytesRead < 0))
{
m_socket->close();
return;
}
if (Q_UNLIKELY(bytesRead < bytesAvailable))
m_receivedData.chop(bytesAvailable - bytesRead);
m_receivedData.append(m_socket->readAll());
while (!m_receivedData.isEmpty())
{
@@ -93,11 +79,11 @@ void Connection::read()
const long bufferLimit = RequestParser::MAX_CONTENT_SIZE * 1.1; // some margin for headers
if (m_receivedData.size() > bufferLimit)
{
qWarning("%s", qUtf8Printable(tr("Http request size exceeds limitation, closing socket. Limit: %1, IP: %2")
.arg(QString::number(bufferLimit), m_socket->peerAddress().toString())));
LogMsg(tr("Http request size exceeds limitation, closing socket. Limit: %1, IP: %2")
.arg(bufferLimit).arg(m_socket->peerAddress().toString()), Log::WARNING);
Response resp(413, u"Payload Too Large"_s);
resp.headers[HEADER_CONNECTION] = u"close"_s;
Response resp(413, u"Payload Too Large"_qs);
resp.headers[HEADER_CONNECTION] = u"close"_qs;
sendResponse(resp);
m_socket->close();
@@ -105,26 +91,13 @@ void Connection::read()
}
return;
case RequestParser::ParseStatus::BadMethod:
{
qWarning("%s", qUtf8Printable(tr("Bad Http request method, closing socket. IP: %1. Method: \"%2\"")
.arg(m_socket->peerAddress().toString(), result.request.method)));
Response resp(501, u"Not Implemented"_s);
resp.headers[HEADER_CONNECTION] = u"close"_s;
sendResponse(resp);
m_socket->close();
}
return;
case RequestParser::ParseStatus::BadRequest:
{
qWarning("%s", qUtf8Printable(tr("Bad Http request, closing socket. IP: %1")
.arg(m_socket->peerAddress().toString())));
LogMsg(tr("Bad Http request, closing socket. IP: %1")
.arg(m_socket->peerAddress().toString()), Log::WARNING);
Response resp(400, u"Bad Request"_s);
resp.headers[HEADER_CONNECTION] = u"close"_s;
Response resp(400, u"Bad Request"_qs);
resp.headers[HEADER_CONNECTION] = u"close"_qs;
sendResponse(resp);
m_socket->close();
@@ -135,31 +108,15 @@ void Connection::read()
{
const Environment env {m_socket->localAddress(), m_socket->localPort(), m_socket->peerAddress(), m_socket->peerPort()};
if (result.request.method == HEADER_REQUEST_METHOD_HEAD)
{
Request getRequest = result.request;
getRequest.method = HEADER_REQUEST_METHOD_GET;
Response resp = m_requestHandler->processRequest(result.request, env);
Response resp = m_requestHandler->processRequest(getRequest, env);
if (acceptsGzipEncoding(result.request.headers[u"accept-encoding"_qs]))
resp.headers[HEADER_CONTENT_ENCODING] = u"gzip"_qs;
resp.headers[HEADER_CONNECTION] = u"keep-alive"_s;
resp.headers[HEADER_CONTENT_LENGTH] = QString::number(resp.content.length());
resp.content.clear();
resp.headers[HEADER_CONNECTION] = u"keep-alive"_qs;
sendResponse(resp);
}
else
{
Response resp = m_requestHandler->processRequest(result.request, env);
if (acceptsGzipEncoding(result.request.headers.value(u"accept-encoding"_s)))
resp.headers[HEADER_CONTENT_ENCODING] = u"gzip"_s;
resp.headers[HEADER_CONNECTION] = u"keep-alive"_s;
sendResponse(resp);
}
m_receivedData.remove(0, result.frameSize);
sendResponse(resp);
m_receivedData = m_receivedData.mid(result.frameSize);
}
break;
@@ -219,11 +176,11 @@ bool Connection::acceptsGzipEncoding(QString codings)
if (list.isEmpty())
return false;
const bool canGzip = isCodingAvailable(list, u"gzip"_s);
const bool canGzip = isCodingAvailable(list, u"gzip"_qs);
if (canGzip)
return true;
const bool canAny = isCodingAvailable(list, u"*"_s);
const bool canAny = isCodingAvailable(list, u"*"_qs);
if (canAny)
return true;

View File

@@ -48,41 +48,41 @@ QString HTTPError::statusText() const
}
BadRequestHTTPError::BadRequestHTTPError(const QString &message)
: HTTPError(400, u"Bad Request"_s, message)
: HTTPError(400, u"Bad Request"_qs, message)
{
}
UnauthorizedHTTPError::UnauthorizedHTTPError(const QString &message)
: HTTPError(401, u"Unauthorized"_s, message)
: HTTPError(401, u"Unauthorized"_qs, message)
{
}
ForbiddenHTTPError::ForbiddenHTTPError(const QString &message)
: HTTPError(403, u"Forbidden"_s, message)
: HTTPError(403, u"Forbidden"_qs, message)
{
}
NotFoundHTTPError::NotFoundHTTPError(const QString &message)
: HTTPError(404, u"Not Found"_s, message)
: HTTPError(404, u"Not Found"_qs, message)
{
}
MethodNotAllowedHTTPError::MethodNotAllowedHTTPError(const QString &message)
: HTTPError(405, u"Method Not Allowed"_s, message)
: HTTPError(405, u"Method Not Allowed"_qs, message)
{
}
ConflictHTTPError::ConflictHTTPError(const QString &message)
: HTTPError(409, u"Conflict"_s, message)
: HTTPError(409, u"Conflict"_qs, message)
{
}
UnsupportedMediaTypeHTTPError::UnsupportedMediaTypeHTTPError(const QString &message)
: HTTPError(415, u"Unsupported Media Type"_s, message)
: HTTPError(415, u"Unsupported Media Type"_qs, message)
{
}
InternalServerErrorHTTPError::InternalServerErrorHTTPError(const QString &message)
: HTTPError(500, u"Internal Server Error"_s, message)
: HTTPError(500, u"Internal Server Error"_qs, message)
{
}

View File

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

View File

@@ -75,6 +75,10 @@ namespace
}
}
RequestParser::RequestParser()
{
}
RequestParser::ParseResult RequestParser::parse(const QByteArray &data)
{
// Warning! Header names are converted to lowercase
@@ -103,7 +107,6 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArray &data)
// handle supported methods
if ((m_request.method == HEADER_REQUEST_METHOD_GET) || (m_request.method == HEADER_REQUEST_METHOD_HEAD))
return {ParseStatus::OK, m_request, headerLength};
if (m_request.method == HEADER_REQUEST_METHOD_POST)
{
const auto parseContentLength = [this]() -> int
@@ -147,7 +150,8 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArray &data)
return {ParseStatus::OK, m_request, (headerLength + contentLength)};
}
return {ParseStatus::BadMethod, m_request, 0};
qWarning() << Q_FUNC_INFO << "unsupported request method: " << m_request.method;
return {ParseStatus::BadRequest, Request(), 0}; // TODO: SHOULD respond "501 Not Implemented"
}
bool RequestParser::parseStartLines(const QStringView data)
@@ -189,7 +193,7 @@ bool RequestParser::parseRequestLine(const QString &line)
{
// [rfc7230] 3.1.1. Request Line
static const QRegularExpression re(u"^([A-Z]+)\\s+(\\S+)\\s+HTTP\\/(\\d\\.\\d)$"_s);
const QRegularExpression re(u"^([A-Z]+)\\s+(\\S+)\\s+HTTP\\/(\\d\\.\\d)$"_qs);
const QRegularExpressionMatch match = re.match(line);
if (!match.hasMatch())
@@ -264,7 +268,7 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
// [rfc2046] 5.1.1. Common Syntax
// find boundary delimiter
const QString boundaryFieldName = u"boundary="_s;
const QString boundaryFieldName = u"boundary="_qs;
const int idx = contentType.indexOf(boundaryFieldName);
if (idx < 0)
{
@@ -343,8 +347,8 @@ bool RequestParser::parseFormData(const QByteArray &data)
}
// pick data
const QString filename = u"filename"_s;
const QString name = u"name"_s;
const QString filename = u"filename"_qs;
const QString name = u"name"_qs;
if (headersMap.contains(filename))
{

View File

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

View File

@@ -38,7 +38,7 @@ namespace Http
class ResponseBuilder
{
public:
void status(uint code = 200, const QString &text = u"OK"_s);
void status(uint code = 200, const QString &text = u"OK"_qs);
void setHeader(const Header &header);
void print(const QString &text, const QString &type = CONTENT_TYPE_HTML);
void print(const QByteArray &data, const QString &type = CONTENT_TYPE_HTML);

View File

@@ -38,9 +38,8 @@ QByteArray Http::toByteArray(Response response)
{
compressContent(response);
response.headers[HEADER_CONTENT_LENGTH] = QString::number(response.content.length());
response.headers[HEADER_DATE] = httpDate();
if (QString &value = response.headers[HEADER_CONTENT_LENGTH]; value.isEmpty())
value = QString::number(response.content.length());
QByteArray buf;
buf.reserve(1024 + response.content.length());
@@ -64,7 +63,7 @@ QByteArray Http::toByteArray(Response response)
// the first empty line
buf += CRLF;
// message body
// message body // TODO: support HEAD request
buf += response.content;
return buf;
@@ -75,7 +74,7 @@ QString Http::httpDate()
// [RFC 7231] 7.1.1.1. Date/Time Formats
// example: "Sun, 06 Nov 1994 08:49:37 GMT"
return QLocale::c().toString(QDateTime::currentDateTimeUtc(), u"ddd, dd MMM yyyy HH:mm:ss"_s)
return QLocale::c().toString(QDateTime::currentDateTimeUtc(), u"ddd, dd MMM yyyy HH:mm:ss"_qs)
.append(u" GMT");
}
@@ -107,5 +106,5 @@ void Http::compressContent(Response &response)
return;
response.content = compressedData;
response.headers[HEADER_CONTENT_ENCODING] = u"gzip"_s;
response.headers[HEADER_CONTENT_ENCODING] = u"gzip"_qs;
}

View File

@@ -55,28 +55,28 @@ namespace
QList<QSslCipher> safeCipherList()
{
const QStringList badCiphers {u"idea"_s, u"rc4"_s};
const QStringList badCiphers {u"idea"_qs, u"rc4"_qs};
// Contains Ciphersuites that use RSA for the Key Exchange but they don't mention it in their name
const QStringList badRSAShorthandSuites {
u"AES256-GCM-SHA384"_s, u"AES128-GCM-SHA256"_s, u"AES256-SHA256"_s,
u"AES128-SHA256"_s, u"AES256-SHA"_s, u"AES128-SHA"_s};
u"AES256-GCM-SHA384"_qs, u"AES128-GCM-SHA256"_qs, u"AES256-SHA256"_qs,
u"AES128-SHA256"_qs, u"AES256-SHA"_qs, u"AES128-SHA"_qs};
// Contains Ciphersuites that use AES CBC mode but they don't mention it in their name
const QStringList badAESShorthandSuites {
u"ECDHE-ECDSA-AES256-SHA384"_s, u"ECDHE-RSA-AES256-SHA384"_s, u"DHE-RSA-AES256-SHA256"_s,
u"ECDHE-ECDSA-AES128-SHA256"_s, u"ECDHE-RSA-AES128-SHA256"_s, u"DHE-RSA-AES128-SHA256"_s,
u"ECDHE-ECDSA-AES256-SHA"_s, u"ECDHE-RSA-AES256-SHA"_s, u"DHE-RSA-AES256-SHA"_s,
u"ECDHE-ECDSA-AES128-SHA"_s, u"ECDHE-RSA-AES128-SHA"_s, u"DHE-RSA-AES128-SHA"_s};
u"ECDHE-ECDSA-AES256-SHA384"_qs, u"ECDHE-RSA-AES256-SHA384"_qs, u"DHE-RSA-AES256-SHA256"_qs,
u"ECDHE-ECDSA-AES128-SHA256"_qs, u"ECDHE-RSA-AES128-SHA256"_qs, u"DHE-RSA-AES128-SHA256"_qs,
u"ECDHE-ECDSA-AES256-SHA"_qs, u"ECDHE-RSA-AES256-SHA"_qs, u"DHE-RSA-AES256-SHA"_qs,
u"ECDHE-ECDSA-AES128-SHA"_qs, u"ECDHE-RSA-AES128-SHA"_qs, u"DHE-RSA-AES128-SHA"_qs};
const QList<QSslCipher> allCiphers {QSslConfiguration::supportedCiphers()};
QList<QSslCipher> safeCiphers;
std::copy_if(allCiphers.cbegin(), allCiphers.cend(), std::back_inserter(safeCiphers),
[&badCiphers, &badRSAShorthandSuites, &badAESShorthandSuites](const QSslCipher &cipher)
{
const QString name = cipher.name();
if (name.contains(u"-cbc-"_s, Qt::CaseInsensitive) // AES CBC mode is considered vulnerable to BEAST attack
|| name.startsWith(u"adh-"_s, Qt::CaseInsensitive) // Key Exchange: Diffie-Hellman, doesn't support Perfect Forward Secrecy
|| name.startsWith(u"aecdh-"_s, Qt::CaseInsensitive) // Key Exchange: Elliptic Curve Diffie-Hellman, doesn't support Perfect Forward Secrecy
|| name.startsWith(u"psk-"_s, Qt::CaseInsensitive) // Key Exchange: Pre-Shared Key, doesn't support Perfect Forward Secrecy
|| name.startsWith(u"rsa-"_s, Qt::CaseInsensitive) // Key Exchange: Rivest Shamir Adleman (RSA), doesn't support Perfect Forward Secrecy
if (name.contains(u"-cbc-"_qs, Qt::CaseInsensitive) // AES CBC mode is considered vulnerable to BEAST attack
|| name.startsWith(u"adh-"_qs, Qt::CaseInsensitive) // Key Exchange: Diffie-Hellman, doesn't support Perfect Forward Secrecy
|| name.startsWith(u"aecdh-"_qs, Qt::CaseInsensitive) // Key Exchange: Elliptic Curve Diffie-Hellman, doesn't support Perfect Forward Secrecy
|| name.startsWith(u"psk-"_qs, Qt::CaseInsensitive) // Key Exchange: Pre-Shared Key, doesn't support Perfect Forward Secrecy
|| name.startsWith(u"rsa-"_qs, Qt::CaseInsensitive) // Key Exchange: Rivest Shamir Adleman (RSA), doesn't support Perfect Forward Secrecy
|| badRSAShorthandSuites.contains(name, Qt::CaseInsensitive)
|| badAESShorthandSuites.contains(name, Qt::CaseInsensitive))
{

View File

@@ -37,42 +37,41 @@
namespace Http
{
inline const QString METHOD_GET = u"GET"_s;
inline const QString METHOD_POST = u"POST"_s;
inline const QString METHOD_GET = u"GET"_qs;
inline const QString METHOD_POST = u"POST"_qs;
inline const QString HEADER_CACHE_CONTROL = u"cache-control"_s;
inline const QString HEADER_CONNECTION = u"connection"_s;
inline const QString HEADER_CONTENT_DISPOSITION = u"content-disposition"_s;
inline const QString HEADER_CONTENT_ENCODING = u"content-encoding"_s;
inline const QString HEADER_CONTENT_LENGTH = u"content-length"_s;
inline const QString HEADER_CONTENT_SECURITY_POLICY = u"content-security-policy"_s;
inline const QString HEADER_CONTENT_TYPE = u"content-type"_s;
inline const QString HEADER_CROSS_ORIGIN_OPENER_POLICY = u"cross-origin-opener-policy"_s;
inline const QString HEADER_DATE = u"date"_s;
inline const QString HEADER_HOST = u"host"_s;
inline const QString HEADER_ORIGIN = u"origin"_s;
inline const QString HEADER_REFERER = u"referer"_s;
inline const QString HEADER_REFERRER_POLICY = u"referrer-policy"_s;
inline const QString HEADER_SET_COOKIE = u"set-cookie"_s;
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_FRAME_OPTIONS = u"x-frame-options"_s;
inline const QString HEADER_X_XSS_PROTECTION = u"x-xss-protection"_s;
inline const QString HEADER_CACHE_CONTROL = u"cache-control"_qs;
inline const QString HEADER_CONNECTION = u"connection"_qs;
inline const QString HEADER_CONTENT_DISPOSITION = u"content-disposition"_qs;
inline const QString HEADER_CONTENT_ENCODING = u"content-encoding"_qs;
inline const QString HEADER_CONTENT_LENGTH = u"content-length"_qs;
inline const QString HEADER_CONTENT_SECURITY_POLICY = u"content-security-policy"_qs;
inline const QString HEADER_CONTENT_TYPE = u"content-type"_qs;
inline const QString HEADER_DATE = u"date"_qs;
inline const QString HEADER_HOST = u"host"_qs;
inline const QString HEADER_ORIGIN = u"origin"_qs;
inline const QString HEADER_REFERER = u"referer"_qs;
inline const QString HEADER_REFERRER_POLICY = u"referrer-policy"_qs;
inline const QString HEADER_SET_COOKIE = u"set-cookie"_qs;
inline const QString HEADER_X_CONTENT_TYPE_OPTIONS = u"x-content-type-options"_qs;
inline const QString HEADER_X_FORWARDED_FOR = u"x-forwarded-for"_qs;
inline const QString HEADER_X_FORWARDED_HOST = u"x-forwarded-host"_qs;
inline const QString HEADER_X_FRAME_OPTIONS = u"x-frame-options"_qs;
inline const QString HEADER_X_XSS_PROTECTION = u"x-xss-protection"_qs;
inline const QString HEADER_REQUEST_METHOD_GET = u"GET"_s;
inline const QString HEADER_REQUEST_METHOD_HEAD = u"HEAD"_s;
inline const QString HEADER_REQUEST_METHOD_POST = u"POST"_s;
inline const QString HEADER_REQUEST_METHOD_GET = u"GET"_qs;
inline const QString HEADER_REQUEST_METHOD_HEAD = u"HEAD"_qs;
inline const QString HEADER_REQUEST_METHOD_POST = u"POST"_qs;
inline const QString CONTENT_TYPE_HTML = u"text/html"_s;
inline const QString CONTENT_TYPE_CSS = u"text/css"_s;
inline const QString CONTENT_TYPE_TXT = u"text/plain; charset=UTF-8"_s;
inline const QString CONTENT_TYPE_JS = u"application/javascript"_s;
inline const QString CONTENT_TYPE_JSON = u"application/json"_s;
inline const QString CONTENT_TYPE_GIF = u"image/gif"_s;
inline const QString CONTENT_TYPE_PNG = u"image/png"_s;
inline const QString CONTENT_TYPE_FORM_ENCODED = u"application/x-www-form-urlencoded"_s;
inline const QString CONTENT_TYPE_FORM_DATA = u"multipart/form-data"_s;
inline const QString CONTENT_TYPE_HTML = u"text/html"_qs;
inline const QString CONTENT_TYPE_CSS = u"text/css"_qs;
inline const QString CONTENT_TYPE_TXT = u"text/plain; charset=UTF-8"_qs;
inline const QString CONTENT_TYPE_JS = u"application/javascript"_qs;
inline const QString CONTENT_TYPE_JSON = u"application/json"_qs;
inline const QString CONTENT_TYPE_GIF = u"image/gif"_qs;
inline const QString CONTENT_TYPE_PNG = u"image/png"_qs;
inline const QString CONTENT_TYPE_FORM_ENCODED = u"application/x-www-form-urlencoded"_qs;
inline const QString CONTENT_TYPE_FORM_DATA = u"multipart/form-data"_qs;
// portability: "\r\n" doesn't guarantee mapping to the correct symbol
inline const char CRLF[] = {0x0D, 0x0A, '\0'};
@@ -80,10 +79,10 @@ namespace Http
struct Environment
{
QHostAddress localAddress;
quint16 localPort = 0;
quint16 localPort;
QHostAddress clientAddress;
quint16 clientPort = 0;
quint16 clientPort;
};
struct UploadedFile
@@ -124,7 +123,7 @@ namespace Http
HeaderMap headers;
QByteArray content;
Response(uint code = 200, const QString &text = u"OK"_s)
Response(uint code = 200, const QString &text = u"OK"_qs)
: status {code, text}
{
}

View File

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

View File

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

View File

@@ -35,7 +35,7 @@
#include <QString>
#include <QtContainerFwd>
inline const int MAX_LOG_MESSAGES = 20000;
const int MAX_LOG_MESSAGES = 20000;
namespace Log
{
@@ -51,17 +51,17 @@ namespace Log
struct Msg
{
int id = -1;
MsgType type = ALL;
qint64 timestamp = -1;
int id;
MsgType type;
qint64 timestamp;
QString message;
};
struct Peer
{
int id = -1;
bool blocked = false;
qint64 timestamp = -1;
int id;
bool blocked;
qint64 timestamp;
QString ip;
QString reason;
};
@@ -69,7 +69,7 @@ namespace Log
Q_DECLARE_OPERATORS_FOR_FLAGS(Log::MsgTypes)
class Logger final : public QObject
class Logger : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(Logger)

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