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
497 changed files with 160499 additions and 213865 deletions

View File

@@ -50,7 +50,7 @@ before_build:
build_script: build_script:
- cd "%REPO_DIR%" - 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) # it also chokes with the sources from src/app/qtlocalpeer (formerly qtsingleapplication)
# Workaround: temporarily rename them to run lupdate with the .pro file # Workaround: temporarily rename them to run lupdate with the .pro file
- RENAME conf.pri conf.pri.temp - 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 *.png binary
*.qm binary *.qm binary
*.zip binary *.zip binary
test/testdata/crlf.txt text eol=crlf

View File

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

View File

@@ -4,7 +4,6 @@ on: [pull_request, push]
permissions: permissions:
actions: write actions: write
security-events: write
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -18,11 +17,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
libt_version: ["2.0.9", "1.2.19"] libt_version: ["2.0.8", "1.2.18"]
qbt_gui: ["GUI=ON", "GUI=OFF"] qbt_gui: ["GUI=ON", "GUI=OFF"]
qt_version: ["5.15.2", "6.2.0"] qt_version: ["5.15.2", "6.2.0"]
exclude: exclude:
- libt_version: "1.2.19" - libt_version: "1.2.18"
qt_version: "6.2.0" qt_version: "6.2.0"
steps: steps:
@@ -60,23 +59,14 @@ jobs:
cmake \ cmake \
-B build \ -B build \
-G "Ninja" \ -G "Ninja" \
-DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-Ddeprecated-functions=OFF -Ddeprecated-functions=OFF
cmake --build build cmake --build build
sudo cmake --install 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) - name: Build qBittorrent (Qt5)
if: startsWith(matrix.qt_version, 5) if: ${{ startsWith(matrix.qt_version, 5) }}
run: | run: |
CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \ CXXFLAGS="$CXXFLAGS -Werror -Wno-error=deprecated-declarations" \
LDFLAGS="$LDFLAGS -gz" \ LDFLAGS="$LDFLAGS -gz" \
@@ -95,7 +85,7 @@ jobs:
DESTDIR="qbittorrent" cmake --install build DESTDIR="qbittorrent" cmake --install build
- name: Build qBittorrent (Qt6) - name: Build qBittorrent (Qt6)
if: startsWith(matrix.qt_version, 6) if: ${{ startsWith(matrix.qt_version, 6) }}
run: | run: |
CXXFLAGS="$CXXFLAGS -Werror" \ CXXFLAGS="$CXXFLAGS -Werror" \
LDFLAGS="$LDFLAGS -gz" \ LDFLAGS="$LDFLAGS -gz" \
@@ -114,12 +104,6 @@ jobs:
cmake --build build --target check cmake --build build --target check
DESTDIR="qbittorrent" cmake --install build 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 - name: Prepare build artifacts
run: | run: |
mkdir upload mkdir upload

View File

@@ -2,8 +2,7 @@ name: CI - WebUI
on: [pull_request, push] on: [pull_request, push]
permissions: permissions: {}
security-events: write
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -37,12 +36,3 @@ jobs:
run: | run: |
npm run format npm run format
git diff --exit-code 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
libt_version: ["2.0.9", "1.2.19"] libt_version: ["2.0.8", "1.2.18"]
env: env:
boost_path: "${{ github.workspace }}/../boost" boost_path: "${{ github.workspace }}/../boost"
@@ -70,7 +70,7 @@ jobs:
- name: Install boost - name: Install boost
run: | run: |
aria2c ` 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 }}" ` -d "${{ runner.temp }}" `
-o "boost.7z" -o "boost.7z"
7z x "${{ runner.temp }}/boost.7z" -o"${{ github.workspace }}/.." 7z x "${{ runner.temp }}/boost.7z" -o"${{ github.workspace }}/.."
@@ -79,7 +79,7 @@ jobs:
- name: Install Qt - name: Install Qt
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
with: with:
version: "6.5.0" version: "6.4.0"
archives: qtbase qtsvg qttools archives: qtbase qtsvg qttools
- name: Install libtorrent - name: Install libtorrent

View File

@@ -26,13 +26,13 @@ jobs:
- name: Install Qt - name: Install Qt
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
with: with:
version: "6.5.0" version: "6.4.0"
archives: icu qtbase qtsvg qttools archives: icu qtbase qtsvg qttools
- name: Install libtorrent - name: Install libtorrent
run: | run: |
git clone \ git clone \
--branch "v2.0.9" \ --branch "v2.0.8" \
--depth 1 \ --depth 1 \
--recurse-submodules \ --recurse-submodules \
https://github.com/arvidn/libtorrent.git https://github.com/arvidn/libtorrent.git
@@ -69,7 +69,7 @@ jobs:
- name: Submit the result to Coverity Scan - name: Submit the result to Coverity Scan
run: | run: |
tar -caf qbittorrent.xz cov-int tar caf qbittorrent.xz cov-int
curl \ curl \
--form token="${{ secrets.COVERITY_SCAN_TOKEN }}" \ --form token="${{ secrets.COVERITY_SCAN_TOKEN }}" \
--form email=sledgehammer999@qbittorrent.org \ --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 # this file is called from AppRun so 'root_dir' will point to where AppRun is
root_dir="$(readlink -f "$(dirname "$0")")" root_dir="$(readlink -f "$(dirname "$0")")"
# Insert the default values because after the test we prepend our path # 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 it will create problems with DEs (eg KDE) that don't set the variable
# and rely on the default paths # 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/" XDG_DATA_DIRS="/usr/local/share/:/usr/share/"
fi 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 runs-on: ubuntu-latest
steps: steps:
- name: Mark and close stale PRs - name: Mark and close stale PRs
uses: actions/stale@v8 uses: actions/stale@v5
with: 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." 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." 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: hooks:
- id: check-translation-tag - id: check-translation-tag
name: Check newline characters in <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 language: script
exclude: | exclude: |
(?x)^( (?x)^(
@@ -13,7 +13,7 @@ repos:
- ts - ts
- repo: https://github.com/pre-commit/pre-commit-hooks.git - repo: https://github.com/pre-commit/pre-commit-hooks.git
rev: v4.4.0 rev: v4.3.0
hooks: hooks:
- id: check-json - id: check-json
name: Check JSON files name: Check JSON files
@@ -33,19 +33,17 @@ repos:
args: ["--fix=lf"] args: ["--fix=lf"]
exclude: | exclude: |
(?x)^( (?x)^(
src/webui/www/private/css/lib/.* | compile_commands.json |
src/webui/www/private/scripts/lib/.* | src/webui/www/private/scripts/lib/.*
test/testdata/crlf.txt
)$ )$
- id: end-of-file-fixer - id: end-of-file-fixer
name: Check trailing newlines name: Check trailing newlines
exclude: | exclude: |
(?x)^( (?x)^(
compile_commands.json |
configure | configure |
src/webui/www/private/css/lib/.* | src/webui/www/private/scripts/lib/.*
src/webui/www/private/scripts/lib/.* |
test/testdata/crlf.txt
)$ )$
exclude_types: exclude_types:
- svg - svg
@@ -55,54 +53,7 @@ repos:
name: Check trailing whitespaces name: Check trailing whitespaces
exclude: | exclude: |
(?x)^( (?x)^(
src/webui/www/private/css/lib/.* |
src/webui/www/private/scripts/lib/.* src/webui/www/private/scripts/lib/.*
)$ )$
exclude_types: exclude_types:
- ts - 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] [main]
host = https://www.transifex.com 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 file_filter = src/lang/qbittorrent_<lang>.ts
source_file = src/lang/qbittorrent_en.ts source_file = src/lang/qbittorrent_en.ts
source_lang = en source_lang = en
@@ -9,7 +9,7 @@ type = QT
minimum_perc = 23 minimum_perc = 23
lang_map = pt: pt_PT, zh: zh_CN 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 file_filter = src/webui/www/translations/webui_<lang>.ts
source_file = src/webui/www/translations/webui_en.ts source_file = src/webui/www/translations/webui_en.ts
source_lang = en source_lang = en

View File

@@ -29,17 +29,13 @@ Code from other projects:
copyright: Dan Haim <negativeiq@users.sourceforge.net> copyright: Dan Haim <negativeiq@users.sourceforge.net>
license: BSD 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: Images Authors:
* files: src/icons/qbittorrent-tray.svg * 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) copyright: Provided by HVS <hvs linuxmail org> (raster first proposal) and Atif Afzal(@atfzl github) <atif5801@gmail.com> (vectorized and modified)
license: GPLv2+ license: GPLv2+
* files: src/qbittorrent_file.ico src/icons/fileicon.svg * 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+ license: GPLv3+
* files: src/icons/flags/*.svg * files: src/icons/flags/*.svg

View File

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

View File

@@ -200,7 +200,7 @@ Following these guidelines helps maintainers and the community understand your s
[coding-guidelines-url]: https://github.com/qbittorrent/qBittorrent/blob/master/CODING_GUIDELINES.md [coding-guidelines-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 [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 [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 [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 [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 [merging-vs-rebasing-url]: https://www.atlassian.com/git/tutorials/merging-vs-rebasing

View File

@@ -1,4 +1,76 @@
Unreleased - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.5.0 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)
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: Add `Auto resize columns` functionality (Chocobo1)
- FEATURE: Allow to use Category paths in `Manual` mode (glassez) - FEATURE: Allow to use Category paths in `Manual` mode (glassez)
- FEATURE: Allow to disable Automatic mode when default "temp" path changed (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 - 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/ * By Arvid Norberg, https://www.libtorrent.org/
* Be careful: another library (the one used by rTorrent) uses a similar name * 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 * - pkg-config *
* Compile-time only on *nix systems * Compile-time only on *nix systems
- Python >= 3.7.0 - Python >= 3.5.0
* Optional, run-time only * Optional, run-time only
* Used by the bundled search engine * 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. will install and execute qBittorrent.
DOCUMENTATION: 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> sledgehammer999 <sledgehammer999@qbittorrent.org>

View File

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

View File

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

86
configure vendored
View File

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

View File

@@ -1,4 +1,4 @@
AC_INIT([qbittorrent], [v4.6.0RC2], [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_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])
: ${CFLAGS=""} : ${CFLAGS=""}
@@ -165,7 +165,7 @@ AS_CASE(["x$enable_qt_dbus"],
FIND_QTDBUS() FIND_QTDBUS()
AS_IF([test "x$HAVE_QTDBUS" = "xfalse"], AS_IF([test "x$HAVE_QTDBUS" = "xfalse"],
[AC_MSG_ERROR([Could not find QtDBus])], [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"], ["xno"],
[AC_MSG_RESULT([no]) [AC_MSG_RESULT([no])
@@ -188,10 +188,10 @@ m4_define([DETECT_BOOST_VERSION_PROGRAM],
[[(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($1))]));]])]) [[(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($1))]));]])])
PKG_CHECK_MODULES(libtorrent, 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"], [CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS" LIBS="$libtorrent_LIBS $LIBS" QBT_ADD_DEFINES="$QBT_ADD_DEFINES QBT_USES_LIBTORRENT2"],
[PKG_CHECK_MODULES(libtorrent, [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"])]) [CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS" LIBS="$libtorrent_LIBS $LIBS"])])
PKG_CHECK_MODULES(openssl, PKG_CHECK_MODULES(openssl,

2
dist/mac/Info.plist vendored
View File

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

View File

@@ -68,12 +68,12 @@
<update_contact>sledgehammer999@qbittorrent.org</update_contact> <update_contact>sledgehammer999@qbittorrent.org</update_contact>
<developer_name>The qBittorrent Project</developer_name> <developer_name>The qBittorrent Project</developer_name>
<url type="homepage">https://www.qbittorrent.org/</url> <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="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> <url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
<content_rating type="oars-1.1"/> <content_rating type="oars-1.1"/>
<releases> <releases>
<release version="4.5.0" date="2022-01-06"/> <release version="4.5.5" date="2023-08-29"/>
</releases> </releases>
</component> </component>

View File

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

View File

@@ -42,6 +42,6 @@ number.
8080). 8080).
.SH BUGS .SH BUGS
.PP .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 .SH AUTHORS
Christophe Dumez <chris@qbittorrent.org>. Christophe Dumez <chris@qbittorrent.org>.

View File

@@ -38,4 +38,4 @@ the default account user name is "admin" with "adminadmin" as a password.
# BUGS # 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). 8080).
.SH BUGS .SH BUGS
.PP .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 .SH AUTHORS
Christophe Dumez <chris@qbittorrent.org>. Christophe Dumez <chris@qbittorrent.org>.

View File

@@ -33,4 +33,4 @@ FAST extension (mainline) and PeX support (utorrent compatible).
# BUGS # 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,7 +97,6 @@
#include "gui/shutdownconfirmdialog.h" #include "gui/shutdownconfirmdialog.h"
#include "gui/uithememanager.h" #include "gui/uithememanager.h"
#include "gui/utils.h" #include "gui/utils.h"
#include "gui/windowstate.h"
#endif // DISABLE_GUI #endif // DISABLE_GUI
#ifndef DISABLE_WEBUI #ifndef DISABLE_WEBUI
@@ -108,12 +107,12 @@ namespace
{ {
#define SETTINGS_KEY(name) u"Application/" name #define SETTINGS_KEY(name) u"Application/" name
#define FILELOGGER_SETTINGS_KEY(name) (SETTINGS_KEY(u"FileLogger/") 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 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 MIN_FILELOG_SIZE = 1024; // 1KiB
const int MAX_FILELOG_SIZE = 1000 * 1024 * 1024; // 1000MiB const int MAX_FILELOG_SIZE = 1000 * 1024 * 1024; // 1000MiB
@@ -122,132 +121,34 @@ namespace
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB const int PIXMAP_CACHE_SIZE = 64 * 1024 * 1024; // 64MiB
#endif #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) Application::Application(int &argc, char **argv)
: BaseApplication(argc, argv) : BaseApplication(argc, argv)
, m_commandLineArgs(parseCommandLine(Application::arguments())) , m_shutdownAct(ShutdownDialogAction::Exit)
, m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY(u"Enabled"_s)) , m_commandLineArgs(parseCommandLine(this->arguments()))
, m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY(u"Backup"_s)) , m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY(u"Enabled"_qs))
, m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY(u"DeleteOld"_s)) , m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY(u"Backup"_qs))
, m_storeFileLoggerMaxSize(FILELOGGER_SETTINGS_KEY(u"MaxSizeBytes"_s)) , m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY(u"DeleteOld"_qs))
, m_storeFileLoggerAge(FILELOGGER_SETTINGS_KEY(u"Age"_s)) , m_storeFileLoggerMaxSize(FILELOGGER_SETTINGS_KEY(u"MaxSizeBytes"_qs))
, m_storeFileLoggerAgeType(FILELOGGER_SETTINGS_KEY(u"AgeType"_s)) , m_storeFileLoggerAge(FILELOGGER_SETTINGS_KEY(u"Age"_qs))
, m_storeFileLoggerPath(FILELOGGER_SETTINGS_KEY(u"Path"_s)) , m_storeFileLoggerAgeType(FILELOGGER_SETTINGS_KEY(u"AgeType"_qs))
, m_storeMemoryWorkingSetLimit(SETTINGS_KEY(u"MemoryWorkingSetLimit"_s)) , m_storeFileLoggerPath(FILELOGGER_SETTINGS_KEY(u"Path"_qs))
, m_storeMemoryWorkingSetLimit(SETTINGS_KEY(u"MemoryWorkingSetLimit"_qs))
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
, m_processMemoryPriority(SETTINGS_KEY(u"ProcessMemoryPriority"_s)) , m_processMemoryPriority(SETTINGS_KEY(u"ProcessMemoryPriority"_qs))
#endif #endif
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
, m_startUpWindowState(u"GUI/StartUpWindowState"_s) , m_storeNotificationTorrentAdded(NOTIFICATIONS_SETTINGS_KEY(u"TorrentAdded"_qs))
, m_storeNotificationTorrentAdded(NOTIFICATIONS_SETTINGS_KEY(u"TorrentAdded"_s))
#endif #endif
{ {
qRegisterMetaType<Log::Msg>("Log::Msg"); qRegisterMetaType<Log::Msg>("Log::Msg");
qRegisterMetaType<Log::Peer>("Log::Peer"); qRegisterMetaType<Log::Peer>("Log::Peer");
setApplicationName(u"qBittorrent"_s); setApplicationName(u"qBittorrent"_qs);
setOrganizationDomain(u"qbittorrent.org"_s); setOrganizationDomain(u"qbittorrent.org"_qs);
#if !defined(DISABLE_GUI) #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)) #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
setAttribute(Qt::AA_UseHighDpiPixmaps, true); // opt-in to the high DPI pixmap support setAttribute(Qt::AA_UseHighDpiPixmaps, true); // opt-in to the high DPI pixmap support
#endif #endif
@@ -275,7 +176,7 @@ Application::Application(int &argc, char **argv)
if (!firstTimeUser) if (!firstTimeUser)
{ {
if (!upgrade()) 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); handleChangedDefaults(DefaultPreferencesMode::Legacy);
} }
else else
@@ -296,7 +197,7 @@ Application::Application(int &argc, char **argv)
{ {
LogMsg(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir.toString())); LogMsg(tr("Running in portable mode. Auto detected profile folder at: %1").arg(profileDir.toString()));
if (m_commandLineArgs.relativeFastresumePaths) 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 else
{ {
@@ -311,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 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; port = m_commandLineArgs.torrentingPort;
} }
} }
@@ -334,16 +235,6 @@ MainWindow *Application::mainWindow()
return m_window; return m_window;
} }
WindowState Application::startUpWindowState() const
{
return m_startUpWindowState;
}
void Application::setStartUpWindowState(const WindowState windowState)
{
m_startUpWindowState = windowState;
}
bool Application::isTorrentAddedNotificationsEnabled() const bool Application::isTorrentAddedNotificationsEnabled() const
{ {
return m_storeNotificationTorrentAdded; return m_storeNotificationTorrentAdded;
@@ -487,7 +378,7 @@ void Application::processMessage(const QString &message)
} }
#endif #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 // If Application is not allowed to process params immediately
// (i.e., other components are not ready) store params // (i.e., other components are not ready) store params
if (m_isProcessingParamsAllowed) if (m_isProcessingParamsAllowed)
@@ -523,13 +414,13 @@ void Application::runExternalProgram(const QString &programTemplate, const BitTo
str.replace(i, 2, torrent->contentPath().toString()); str.replace(i, 2, torrent->contentPath().toString());
break; break;
case u'G': case u'G':
str.replace(i, 2, torrent->tags().join(u","_s)); str.replace(i, 2, torrent->tags().join(u","_qs));
break; break;
case u'I': 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; break;
case u'J': 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; break;
case u'K': case u'K':
str.replace(i, 2, torrent->id().toString()); str.replace(i, 2, torrent->id().toString());
@@ -735,12 +626,71 @@ void Application::allTorrentsFinished()
exit(); 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 #ifndef DISABLE_GUI
// There are two circumstances in which we want to show the torrent // There are two circumstances in which we want to show the torrent
@@ -748,21 +698,22 @@ void Application::processParams(const QBtCommandLineParameters &params)
// be shown and skipTorrentDialog is undefined. The other is when // be shown and skipTorrentDialog is undefined. The other is when
// skipTorrentDialog is false, meaning that the application setting // skipTorrentDialog is false, meaning that the application setting
// should be overridden. // should be overridden.
const bool showDialog = !params.skipDialog.value_or(!AddNewTorrentDialog::isEnabled()); const bool showDialogForThisTorrent = !params.skipTorrentDialog.value_or(!AddNewTorrentDialog::isEnabled());
if (showDialog) if (showDialogForThisTorrent)
{ {
for (const QString &torrentSource : params.torrentSources) for (const QString &torrentSource : params.torrentSources)
AddNewTorrentDialog::show(torrentSource, params.addTorrentParams, m_window); AddNewTorrentDialog::show(torrentSource, params.torrentParams, m_window);
} }
else else
#endif #endif
{ {
for (const QString &torrentSource : params.torrentSources) 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) #if !defined(DISABLE_WEBUI) && defined(DISABLE_GUI)
const QString loadingStr = tr("WebUI will be started shortly after internal preparations. Please wait..."); const QString loadingStr = tr("WebUI will be started shortly after internal preparations. Please wait...");
@@ -791,7 +742,7 @@ int Application::exec()
#ifndef Q_OS_MACOS #ifndef Q_OS_MACOS
auto *desktopIntegrationMenu = new QMenu; auto *desktopIntegrationMenu = new QMenu;
auto *actionExit = new QAction(tr("E&xit"), desktopIntegrationMenu); 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->setMenuRole(QAction::QuitRole);
actionExit->setShortcut(Qt::CTRL | Qt::Key_Q); actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
connect(actionExit, &QAction::triggered, this, [] connect(actionExit, &QAction::triggered, this, []
@@ -801,8 +752,11 @@ int Application::exec()
desktopIntegrationMenu->addAction(actionExit); desktopIntegrationMenu->addAction(actionExit);
m_desktopIntegration->setMenu(desktopIntegrationMenu); 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 #else
const bool isHidden = false; const bool isHidden = false;
#endif #endif
@@ -812,7 +766,7 @@ int Application::exec()
createStartupProgressDialog(); createStartupProgressDialog();
// Add a small delay to avoid "flashing" the progress dialog in case there are not many torrents to restore. // Add a small delay to avoid "flashing" the progress dialog in case there are not many torrents to restore.
m_startupProgressDialog->setMinimumDuration(1000); m_startupProgressDialog->setMinimumDuration(1000);
if (startUpWindowState() != WindowState::Normal) if (pref->startMinimized())
m_startupProgressDialog->setWindowState(Qt::WindowMinimized); m_startupProgressDialog->setWindowState(Qt::WindowMinimized);
} }
else else
@@ -865,14 +819,8 @@ int Application::exec()
}); });
disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog); disconnect(m_desktopIntegration, &DesktopIntegration::activationRequested, this, &Application::createStartupProgressDialog);
#ifndef Q_OS_MACOS const MainWindow::State windowState = (!m_startupProgressDialog || (m_startupProgressDialog->windowState() & Qt::WindowMinimized))
const WindowState windowState = !m_startupProgressDialog ? WindowState::Hidden ? MainWindow::Minimized : MainWindow::Normal;
: (m_startupProgressDialog->windowState() & Qt::WindowMinimized) ? WindowState::Minimized
: WindowState::Normal;
#else
const WindowState windowState = (m_startupProgressDialog->windowState() & Qt::WindowMinimized)
? WindowState::Minimized : WindowState::Normal;
#endif
m_window = new MainWindow(this, windowState); m_window = new MainWindow(this, windowState);
delete m_startupProgressDialog; delete m_startupProgressDialog;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
@@ -903,16 +851,16 @@ int Application::exec()
const Preferences *pref = Preferences::instance(); const Preferences *pref = Preferences::instance();
const auto scheme = pref->isWebUiHttpsEnabled() ? u"https"_s : u"http"_s; const auto scheme = pref->isWebUiHttpsEnabled() ? u"https"_qs : u"http"_qs;
const auto url = u"%1://localhost:%2\n"_s.arg(scheme, QString::number(pref->getWebUiPort())); const auto url = u"%1://localhost:%2\n"_qs.arg(scheme, QString::number(pref->getWebUiPort()));
const QString mesg = u"\n******** %1 ********\n"_s.arg(tr("Information")) const QString mesg = u"\n******** %1 ********\n"_qs.arg(tr("Information"))
+ tr("To control qBittorrent, access the WebUI at: %1").arg(url); + tr("To control qBittorrent, access the WebUI at: %1").arg(url);
printf("%s\n", qUtf8Printable(mesg)); printf("%s\n", qUtf8Printable(mesg));
if (pref->getWebUIPassword() == QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==")) if (pref->getWebUIPassword() == QByteArrayLiteral("ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ=="))
{ {
const QString warning = tr("The Web UI administrator username is: %1").arg(pref->getWebUiUsername()) + u'\n' 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'; + tr("This is a security risk, please change your password in program preferences.") + u'\n';
printf("%s", qUtf8Printable(warning)); printf("%s", qUtf8Printable(warning));
} }
@@ -920,17 +868,31 @@ int Application::exec()
#endif // DISABLE_WEBUI #endif // DISABLE_WEBUI
m_isProcessingParamsAllowed = true; m_isProcessingParamsAllowed = true;
for (const QBtCommandLineParameters &params : m_paramsQueue) for (const AddTorrentParams &params : m_paramsQueue)
processParams(params); processParams(params);
m_paramsQueue.clear(); m_paramsQueue.clear();
}); });
const QBtCommandLineParameters params = commandLineArgs(); if (!params.isEmpty())
if (!params.torrentSources.isEmpty()) m_paramsQueue.append(parseParams(params));
m_paramsQueue.append(params);
return BaseApplication::exec(); 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() bool Application::isRunning()
{ {
@@ -998,8 +960,7 @@ bool Application::event(QEvent *ev)
path = static_cast<QFileOpenEvent *>(ev)->url().toString(); path = static_cast<QFileOpenEvent *>(ev)->url().toString();
qDebug("Received a mac file open event: %s", qUtf8Printable(path)); qDebug("Received a mac file open event: %s", qUtf8Printable(path));
QBtCommandLineParameters params; const AddTorrentParams params = parseParams({path});
params.torrentSources.append(path);
// If Application is not allowed to process params immediately // If Application is not allowed to process params immediately
// (i.e., other components are not ready) store params // (i.e., other components are not ready) store params
if (m_isProcessingParamsAllowed) if (m_isProcessingParamsAllowed)
@@ -1106,22 +1067,7 @@ void Application::applyMemoryWorkingSetLimit() const
if (::getrlimit(RLIMIT_RSS, &limit) != 0) if (::getrlimit(RLIMIT_RSS, &limit) != 0)
return; return;
const size_t newSize = memoryWorkingSetLimit() * MiB; limit.rlim_cur = memoryWorkingSetLimit() * MiB;
if (newSize > limit.rlim_max)
{
// try to raise the hard limit
rlimit newLimit = limit;
newLimit.rlim_max = newSize;
if (::setrlimit(RLIMIT_RSS, &newLimit) != 0)
{
const auto message = QString::fromLocal8Bit(strerror(errno));
LogMsg(tr("Failed to set physical memory (RAM) usage hard limit. Requested size: %1. System hard limit: %2. Error code: %3. Error message: \"%4\"")
.arg(QString::number(newSize), QString::number(limit.rlim_max), QString::number(errno), message), Log::WARNING);
return;
}
}
limit.rlim_cur = newSize;
if (::setrlimit(RLIMIT_RSS, &limit) != 0) if (::setrlimit(RLIMIT_RSS, &limit) != 0)
{ {
const auto message = QString::fromLocal8Bit(strerror(errno)); const auto message = QString::fromLocal8Bit(strerror(errno));
@@ -1149,12 +1095,12 @@ void Application::setProcessMemoryPriority(const MemoryPriority priority)
void Application::applyMemoryPriority() const void Application::applyMemoryPriority() const
{ {
using SETPROCESSINFORMATION = BOOL (WINAPI *)(HANDLE, PROCESS_INFORMATION_CLASS, LPVOID, DWORD); 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 if (!setProcessInformation) // only available on Windows >= 8
return; return;
using SETTHREADINFORMATION = BOOL (WINAPI *)(HANDLE, THREAD_INFORMATION_CLASS, LPVOID, DWORD); 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 if (!setThreadInformation) // only available on Windows >= 8
return; return;

View File

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

View File

@@ -32,7 +32,6 @@
#include <cstdio> #include <cstdio>
#include <QCoreApplication>
#include <QDebug> #include <QDebug>
#include <QFileInfo> #include <QFileInfo>
#include <QProcessEnvironment> #include <QProcessEnvironment>
@@ -152,16 +151,16 @@ namespace
{ {
QStringList parts = arg.split(u'='); QStringList parts = arg.split(u'=');
if (parts.size() == 2) if (parts.size() == 2)
return Utils::String::unquote(parts[1], u"'\""_s); return Utils::String::unquote(parts[1], u"'\""_qs);
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'") "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 value(const QProcessEnvironment &env, const QString &defaultValue = {}) const
{ {
QString val = env.value(envVarName()); 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 QString usage(const QString &valueName) const
@@ -199,15 +198,13 @@ namespace
int value(const QString &arg) const int value(const QString &arg) const
{ {
const QString val = StringOption::value(arg); QString val = StringOption::value(arg);
bool ok = false; bool ok = false;
const int res = val.toInt(&ok); int res = val.toInt(&ok);
if (!ok) if (!ok)
{ throw CommandLineParameterError(QObject::tr("Parameter '%1' must follow syntax '%1=%2'",
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "Parameter '%1' must follow syntax '%1=%2'",
"e.g. Parameter '--webui-port' must follow syntax '--webui-port=<value>'") "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; return res;
} }
@@ -220,7 +217,7 @@ namespace
int res = val.toInt(&ok); int res = val.toInt(&ok);
if (!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); .arg(envVarName(), val);
return defaultValue; 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 " "e.g. Parameter '--add-paused' must follow syntax "
"'--add-paused=<true|false>'") "'--add-paused=<true|false>'")
.arg(fullParameter(), u"<true|false>"_s)); .arg(fullParameter(), u"<true|false>"_qs));
} }
std::optional<bool> value(const QProcessEnvironment &env) const 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()) if (val.isEmpty())
{ {
@@ -303,8 +300,8 @@ namespace
return false; return false;
} }
qDebug() << QCoreApplication::translate("CMD Options", "Expected %1 in environment variable '%2', but got '%3'") qDebug() << QObject::tr("Expected %1 in environment variable '%2', but got '%3'")
.arg(u"true|false"_s, envVarName(), val); .arg(u"true|false"_qs, envVarName(), val);
return std::nullopt; return std::nullopt;
} }
@@ -343,7 +340,14 @@ namespace
} }
QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &env) 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 #ifndef DISABLE_GUI
, noSplash(NO_SPLASH_OPTION.value(env)) , noSplash(NO_SPLASH_OPTION.value(env))
#elif !defined(Q_OS_WIN) #elif !defined(Q_OS_WIN)
@@ -351,16 +355,49 @@ QBtCommandLineParameters::QBtCommandLineParameters(const QProcessEnvironment &en
#endif #endif
, webUiPort(WEBUI_PORT_OPTION.value(env, -1)) , webUiPort(WEBUI_PORT_OPTION.value(env, -1))
, torrentingPort(TORRENTING_PORT_OPTION.value(env, -1)) , torrentingPort(TORRENTING_PORT_OPTION.value(env, -1))
, addPaused(PAUSED_OPTION.value(env))
, skipDialog(SKIP_DIALOG_OPTION.value(env)) , skipDialog(SKIP_DIALOG_OPTION.value(env))
, profileDir(PROFILE_OPTION.value(env)) , profileDir(PROFILE_OPTION.value(env))
, configurationName(CONFIGURATION_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); QStringList QBtCommandLineParameters::paramList() const
addTorrentParams.sequential = SEQUENTIAL_OPTION.value(env); {
addTorrentParams.firstLastPiecePriority = FIRST_AND_LAST_OPTION.value(env); QStringList result;
addTorrentParams.addPaused = PAUSED_OPTION.value(env); // 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) QBtCommandLineParameters parseCommandLine(const QStringList &args)
@@ -389,16 +426,16 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
{ {
result.webUiPort = WEBUI_PORT_OPTION.value(arg); result.webUiPort = WEBUI_PORT_OPTION.value(arg);
if ((result.webUiPort < 1) || (result.webUiPort > 65535)) if ((result.webUiPort < 1) || (result.webUiPort > 65535))
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).") throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
.arg(u"--webui-port"_s)); .arg(u"--webui-port"_qs));
} }
else if (arg == TORRENTING_PORT_OPTION) else if (arg == TORRENTING_PORT_OPTION)
{ {
result.torrentingPort = TORRENTING_PORT_OPTION.value(arg); result.torrentingPort = TORRENTING_PORT_OPTION.value(arg);
if ((result.torrentingPort < 1) || (result.torrentingPort > 65535)) if ((result.torrentingPort < 1) || (result.torrentingPort > 65535))
{ {
throw CommandLineParameterError(QCoreApplication::translate("CMD Options", "%1 must specify a valid port (1 to 65535).") throw CommandLineParameterError(QObject::tr("%1 must specify a valid port (1 to 65535).")
.arg(u"--torrenting-port"_s)); .arg(u"--torrenting-port"_qs));
} }
} }
#ifndef DISABLE_GUI #ifndef DISABLE_GUI
@@ -426,27 +463,27 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
} }
else if (arg == SAVE_PATH_OPTION) 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) 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) else if (arg == SKIP_HASH_CHECK_OPTION)
{ {
result.addTorrentParams.skipChecking = true; result.skipChecking = true;
} }
else if (arg == CATEGORY_OPTION) else if (arg == CATEGORY_OPTION)
{ {
result.addTorrentParams.category = CATEGORY_OPTION.value(arg); result.category = CATEGORY_OPTION.value(arg);
} }
else if (arg == SEQUENTIAL_OPTION) else if (arg == SEQUENTIAL_OPTION)
{ {
result.addTorrentParams.sequential = true; result.sequential = true;
} }
else if (arg == FIRST_AND_LAST_OPTION) else if (arg == FIRST_AND_LAST_OPTION)
{ {
result.addTorrentParams.firstLastPiecePriority = true; result.firstLastPiecePriority = true;
} }
else if (arg == SKIP_DIALOG_OPTION) else if (arg == SKIP_DIALOG_OPTION)
{ {
@@ -465,9 +502,9 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
torrentPath.setFile(arg); torrentPath.setFile(arg);
if (torrentPath.exists()) if (torrentPath.exists())
result.torrentSources += torrentPath.absoluteFilePath(); result.torrents += torrentPath.absoluteFilePath();
else else
result.torrentSources += arg; result.torrents += arg;
} }
} }
@@ -500,58 +537,58 @@ QString makeUsage(const QString &prgName)
{ {
const QString indentation {USAGE_INDENTATION, u' '}; const QString indentation {USAGE_INDENTATION, u' '};
const QString text = QCoreApplication::translate("CMD Options", "Usage:") + u'\n' const QString text = QObject::tr("Usage:") + u'\n'
+ indentation + prgName + u' ' + QCoreApplication::translate("CMD Options", "[options] [(<filename> | <url>)...]") + 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) #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 #endif
+ SHOW_HELP_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Display this help message and exit")) + u'\n' + SHOW_HELP_OPTION.usage() + wrapText(QObject::tr("Display this help message and exit")) + u'\n'
+ WEBUI_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port")) + WEBUI_PORT_OPTION.usage(QObject::tr("port"))
+ wrapText(QCoreApplication::translate("CMD Options", "Change the Web UI port")) + wrapText(QObject::tr("Change the Web UI port"))
+ u'\n' + u'\n'
+ TORRENTING_PORT_OPTION.usage(QCoreApplication::translate("CMD Options", "port")) + TORRENTING_PORT_OPTION.usage(QObject::tr("port"))
+ wrapText(QCoreApplication::translate("CMD Options", "Change the torrenting port")) + wrapText(QObject::tr("Change the torrenting port"))
+ u'\n' + u'\n'
#ifndef DISABLE_GUI #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) #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 #endif
//: Use appropriate short form or abbreviation of "directory" //: Use appropriate short form or abbreviation of "directory"
+ PROFILE_OPTION.usage(QCoreApplication::translate("CMD Options", "dir")) + PROFILE_OPTION.usage(QObject::tr("dir"))
+ wrapText(QCoreApplication::translate("CMD Options", "Store configuration files in <dir>")) + u'\n' + wrapText(QObject::tr("Store configuration files in <dir>")) + u'\n'
+ CONFIGURATION_OPTION.usage(QCoreApplication::translate("CMD Options", "name")) + CONFIGURATION_OPTION.usage(QObject::tr("name"))
+ wrapText(QCoreApplication::translate("CMD Options", "Store configuration files in directories qBittorrent_<name>")) + u'\n' + wrapText(QObject::tr("Store configuration files in directories qBittorrent_<name>")) + u'\n'
+ RELATIVE_FASTRESUME.usage() + 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' "to the profile directory")) + u'\n'
+ Option::padUsageText(QCoreApplication::translate("CMD Options", "files or URLs")) + Option::padUsageText(QObject::tr("files or URLs"))
+ wrapText(QCoreApplication::translate("CMD Options", "Download the torrents passed by the user")) + u'\n' + wrapText(QObject::tr("Download the torrents passed by the user")) + u'\n'
+ u'\n' + u'\n'
+ wrapText(QCoreApplication::translate("CMD Options", "Options when adding new torrents:"), 0) + u'\n' + wrapText(QObject::tr("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' + SAVE_PATH_OPTION.usage(QObject::tr("path")) + wrapText(QObject::tr("Torrent save path")) + u'\n'
+ PAUSED_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Add torrents as started or paused")) + u'\n' + PAUSED_OPTION.usage() + wrapText(QObject::tr("Add torrents as started or paused")) + u'\n'
+ SKIP_HASH_CHECK_OPTION.usage() + wrapText(QCoreApplication::translate("CMD Options", "Skip hash check")) + u'\n' + SKIP_HASH_CHECK_OPTION.usage() + wrapText(QObject::tr("Skip hash check")) + u'\n'
+ CATEGORY_OPTION.usage(QCoreApplication::translate("CMD Options", "name")) + CATEGORY_OPTION.usage(QObject::tr("name"))
+ wrapText(QCoreApplication::translate("CMD Options", "Assign torrents to category. If the category doesn't exist, it will be " + wrapText(QObject::tr("Assign torrents to category. If the category doesn't exist, it will be "
"created.")) + u'\n' "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() + 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() + 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' "torrent.")) + u'\n'
+ 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 " "'parameter-name', environment variable name is 'QBT_PARAMETER_NAME' (in upper "
"case, '-' replaced with '_'). To pass flag values, set the variable to '1' or " "case, '-' replaced with '_'). To pass flag values, set the variable to '1' or "
"'TRUE'. For example, to disable the splash screen: "), 0) + u'\n' "'TRUE'. For example, to disable the splash screen: "), 0) + u'\n'
+ u"QBT_NO_SPLASH=1 " + prgName + 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; return text;
} }
@@ -559,7 +596,7 @@ QString makeUsage(const QString &prgName)
void displayUsage(const QString &prgName) void displayUsage(const QString &prgName)
{ {
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI) #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.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Gui::screenCenter(&msgBox)); msgBox.move(Utils::Gui::screenCenter(&msgBox));
msgBox.exec(); msgBox.exec();

View File

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

View File

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

View File

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

View File

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

View File

@@ -96,7 +96,7 @@ namespace
void abnormalExitHandler(const int signum) void abnormalExitHandler(const int signum)
{ {
const char msg[] = "\n\n*************************************************************\n" 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" "qBittorrent version: " QBT_VERSION "\n\n"
"Caught signal: "; "Caught signal: ";
const char *sigName = sysSigName[signum]; const char *sigName = sysSigName[signum];

View File

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

View File

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

View File

@@ -34,7 +34,6 @@ add_library(qbt_base STATIC
bittorrent/sessionstatus.h bittorrent/sessionstatus.h
bittorrent/speedmonitor.h bittorrent/speedmonitor.h
bittorrent/torrent.h bittorrent/torrent.h
bittorrent/torrentcontenthandler.h
bittorrent/torrentcontentlayout.h bittorrent/torrentcontentlayout.h
bittorrent/torrentcreatorthread.h bittorrent/torrentcreatorthread.h
bittorrent/torrentimpl.h bittorrent/torrentimpl.h
@@ -110,7 +109,6 @@ add_library(qbt_base STATIC
applicationcomponent.cpp applicationcomponent.cpp
asyncfilestorage.cpp asyncfilestorage.cpp
bittorrent/abstractfilestorage.cpp bittorrent/abstractfilestorage.cpp
bittorrent/addtorrentparams.cpp
bittorrent/bandwidthscheduler.cpp bittorrent/bandwidthscheduler.cpp
bittorrent/bencoderesumedatastorage.cpp bittorrent/bencoderesumedatastorage.cpp
bittorrent/categoryoptions.cpp bittorrent/categoryoptions.cpp
@@ -131,7 +129,6 @@ add_library(qbt_base STATIC
bittorrent/sessionimpl.cpp bittorrent/sessionimpl.cpp
bittorrent/speedmonitor.cpp bittorrent/speedmonitor.cpp
bittorrent/torrent.cpp bittorrent/torrent.cpp
bittorrent/torrentcontenthandler.cpp
bittorrent/torrentcreatorthread.cpp bittorrent/torrentcreatorthread.cpp
bittorrent/torrentimpl.cpp bittorrent/torrentimpl.cpp
bittorrent/torrentinfo.cpp bittorrent/torrentinfo.cpp

View File

@@ -63,11 +63,4 @@ namespace Algorithm
while (it != set.end()) while (it != set.end())
it = (p(*it) ? set.erase(it) : ++it); 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) AsyncFileStorage::AsyncFileStorage(const Path &storageFolderPath, QObject *parent)
: QObject(parent) : QObject(parent)
, m_storageDir(storageFolderPath) , 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()); Q_ASSERT(m_storageDir.isAbsolute());

View File

@@ -34,7 +34,6 @@ HEADERS += \
$$PWD/bittorrent/speedmonitor.h \ $$PWD/bittorrent/speedmonitor.h \
$$PWD/bittorrent/torrent.h \ $$PWD/bittorrent/torrent.h \
$$PWD/bittorrent/torrentcontentlayout.h \ $$PWD/bittorrent/torrentcontentlayout.h \
$$PWD/bittorrent/torrentcontenthandler.h \
$$PWD/bittorrent/torrentcreatorthread.h \ $$PWD/bittorrent/torrentcreatorthread.h \
$$PWD/bittorrent/torrentimpl.h \ $$PWD/bittorrent/torrentimpl.h \
$$PWD/bittorrent/torrentinfo.h \ $$PWD/bittorrent/torrentinfo.h \
@@ -110,7 +109,6 @@ SOURCES += \
$$PWD/applicationcomponent.cpp \ $$PWD/applicationcomponent.cpp \
$$PWD/asyncfilestorage.cpp \ $$PWD/asyncfilestorage.cpp \
$$PWD/bittorrent/abstractfilestorage.cpp \ $$PWD/bittorrent/abstractfilestorage.cpp \
$$PWD/bittorrent/addtorrentparams.cpp \
$$PWD/bittorrent/bandwidthscheduler.cpp \ $$PWD/bittorrent/bandwidthscheduler.cpp \
$$PWD/bittorrent/bencoderesumedatastorage.cpp \ $$PWD/bittorrent/bencoderesumedatastorage.cpp \
$$PWD/bittorrent/categoryoptions.cpp \ $$PWD/bittorrent/categoryoptions.cpp \
@@ -131,7 +129,6 @@ SOURCES += \
$$PWD/bittorrent/sessionimpl.cpp \ $$PWD/bittorrent/sessionimpl.cpp \
$$PWD/bittorrent/speedmonitor.cpp \ $$PWD/bittorrent/speedmonitor.cpp \
$$PWD/bittorrent/torrent.cpp \ $$PWD/bittorrent/torrent.cpp \
$$PWD/bittorrent/torrentcontenthandler.h \
$$PWD/bittorrent/torrentcreatorthread.cpp \ $$PWD/bittorrent/torrentcreatorthread.cpp \
$$PWD/bittorrent/torrentimpl.cpp \ $$PWD/bittorrent/torrentimpl.cpp \
$$PWD/bittorrent/torrentinfo.cpp \ $$PWD/bittorrent/torrentinfo.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. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@@ -39,8 +39,6 @@
#include "torrent.h" #include "torrent.h"
#include "torrentcontentlayout.h" #include "torrentcontentlayout.h"
class QJsonObject;
namespace BitTorrent namespace BitTorrent
{ {
enum class DownloadPriority; enum class DownloadPriority;
@@ -56,7 +54,6 @@ namespace BitTorrent
bool sequential = false; bool sequential = false;
bool firstLastPiecePriority = false; bool firstLastPiecePriority = false;
bool addForced = false; bool addForced = false;
std::optional<bool> addToQueueTop;
std::optional<bool> addPaused; std::optional<bool> addPaused;
std::optional<Torrent::StopCondition> stopCondition; std::optional<Torrent::StopCondition> stopCondition;
PathList filePaths; // used if TorrentInfo is set PathList filePaths; // used if TorrentInfo is set
@@ -67,14 +64,8 @@ namespace BitTorrent
int uploadLimit = -1; int uploadLimit = -1;
int downloadLimit = -1; int downloadLimit = -1;
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME;
int inactiveSeedingTimeLimit = Torrent::USE_GLOBAL_INACTIVE_SEEDING_TIME;
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; 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) Q_DECLARE_METATYPE(BitTorrent::AddTorrentParams)

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,4 +32,8 @@
#include "base/global.h" #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 , const char *buf, std::shared_ptr<lt::disk_observer> diskObserver
, std::function<void (const lt::storage_error &)> handler, lt::disk_job_flags_t flags) , 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 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 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) , [=, 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)) 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; m_storageData[storage].savePath = newSavePath;
handler(status, path, error); 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) , std::function<void (lt::status_t, const lt::storage_error &)> handler)
{ {
handleCompleteFiles(storage, m_storageData[storage].savePath); 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) 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 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) , 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) m_nativeDiskIO->async_set_file_priority(storage, priorities
, [=, handler = std::move(handler)](const lt::storage_error &error, const lt::aux::vector<lt::download_priority_t, lt::file_index_t> &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; m_storageData[storage].filePriorities = priorities;
handler(error, priorities); handler(error, priorities);

View File

@@ -28,8 +28,6 @@
#include "dbresumedatastorage.h" #include "dbresumedatastorage.h"
#include <memory>
#include <queue>
#include <utility> #include <utility>
#include <libtorrent/bdecode.hpp> #include <libtorrent/bdecode.hpp>
@@ -40,8 +38,7 @@
#include <libtorrent/write_resume_data.hpp> #include <libtorrent/write_resume_data.hpp>
#include <QByteArray> #include <QByteArray>
#include <QDebug> #include <QFile>
#include <QMutex>
#include <QSet> #include <QSet>
#include <QSqlDatabase> #include <QSqlDatabase>
#include <QSqlError> #include <QSqlError>
@@ -49,69 +46,28 @@
#include <QSqlRecord> #include <QSqlRecord>
#include <QThread> #include <QThread>
#include <QVector> #include <QVector>
#include <QWaitCondition>
#include "base/exceptions.h" #include "base/exceptions.h"
#include "base/global.h" #include "base/global.h"
#include "base/logger.h" #include "base/logger.h"
#include "base/path.h" #include "base/path.h"
#include "base/preferences.h"
#include "base/profile.h" #include "base/profile.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/utils/string.h" #include "base/utils/string.h"
#include "common.h"
#include "infohash.h" #include "infohash.h"
#include "loadtorrentparams.h" #include "loadtorrentparams.h"
namespace 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_META = u"meta"_qs;
const QString DB_TABLE_TORRENTS = u"torrents"_s; const QString DB_TABLE_TORRENTS = u"torrents"_qs;
const QString META_VERSION = u"version"_s; const QString META_VERSION = u"version"_qs;
using namespace BitTorrent;
class Job
{
public:
virtual ~Job() = default;
virtual void perform(QSqlDatabase db) = 0;
};
class StoreJob final : public Job
{
public:
StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData);
void perform(QSqlDatabase db) override;
private:
const TorrentID m_torrentID;
const LoadTorrentParams m_resumeData;
};
class RemoveJob final : public Job
{
public:
explicit RemoveJob(const TorrentID &torrentID);
void perform(QSqlDatabase db) override;
private:
const TorrentID m_torrentID;
};
class StoreQueueJob final : public Job
{
public:
explicit StoreQueueJob(const QVector<TorrentID> &queue);
void perform(QSqlDatabase db) override;
private:
const QVector<TorrentID> m_queue;
};
struct Column struct Column
{ {
@@ -135,7 +91,6 @@ namespace
const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn("content_layout"); const Column DB_COLUMN_CONTENT_LAYOUT = makeColumn("content_layout");
const Column DB_COLUMN_RATIO_LIMIT = makeColumn("ratio_limit"); const Column DB_COLUMN_RATIO_LIMIT = makeColumn("ratio_limit");
const Column DB_COLUMN_SEEDING_TIME_LIMIT = makeColumn("seeding_time_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_OUTER_PIECES_PRIORITY = makeColumn("has_outer_pieces_priority");
const Column DB_COLUMN_HAS_SEED_STATUS = makeColumn("has_seed_status"); const Column DB_COLUMN_HAS_SEED_STATUS = makeColumn("has_seed_status");
const Column DB_COLUMN_OPERATING_MODE = makeColumn("operating_mode"); const Column DB_COLUMN_OPERATING_MODE = makeColumn("operating_mode");
@@ -160,7 +115,7 @@ namespace
QString makeCreateTableStatement(const QString &tableName, const QStringList &items) 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) std::pair<QString, QString> joinColumns(const QVector<Column> &columns)
@@ -191,123 +146,115 @@ namespace
QString makeInsertStatement(const QString &tableName, const QVector<Column> &columns) QString makeInsertStatement(const QString &tableName, const QVector<Column> &columns)
{ {
const auto [names, values] = joinColumns(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); .arg(quoted(tableName), names, values);
} }
QString makeUpdateStatement(const QString &tableName, const QVector<Column> &columns) QString makeUpdateStatement(const QString &tableName, const QVector<Column> &columns)
{ {
const auto [names, values] = joinColumns(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); .arg(quoted(tableName), names, values);
} }
QString makeOnConflictUpdateStatement(const Column &constraint, const QVector<Column> &columns) QString makeOnConflictUpdateStatement(const Column &constraint, const QVector<Column> &columns)
{ {
const auto [names, values] = joinColumns(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); .arg(quoted(constraint.name), names, values);
} }
QString makeColumnDefinition(const Column &column, const char *definition) QString makeColumnDefinition(const Column &column, const char *definition)
{ {
return u"%1 %2"_s.arg(quoted(column.name), QString::fromLatin1(definition)); return u"%1 %2"_qs.arg(quoted(column.name), QString::fromLatin1(definition));
}
LoadTorrentParams parseQueryResultRow(const QSqlQuery &query)
{
LoadTorrentParams resumeData;
resumeData.name = query.value(DB_COLUMN_NAME.name).toString();
resumeData.category = query.value(DB_COLUMN_CATEGORY.name).toString();
const QString tagsData = query.value(DB_COLUMN_TAGS.name).toString();
if (!tagsData.isEmpty())
{
const QStringList tagList = tagsData.split(u',');
resumeData.tags.insert(tagList.cbegin(), tagList.cend());
}
resumeData.hasFinishedStatus = query.value(DB_COLUMN_HAS_SEED_STATUS.name).toBool();
resumeData.firstLastPiecePriority = query.value(DB_COLUMN_HAS_OUTER_PIECES_PRIORITY.name).toBool();
resumeData.ratioLimit = query.value(DB_COLUMN_RATIO_LIMIT.name).toInt() / 1000.0;
resumeData.seedingTimeLimit = query.value(DB_COLUMN_SEEDING_TIME_LIMIT.name).toInt();
resumeData.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;
} }
} }
namespace BitTorrent namespace BitTorrent
{ {
class DBResumeDataStorage::Worker final : public QThread class DBResumeDataStorage::Worker final : public QObject
{ {
Q_DISABLE_COPY_MOVE(Worker) Q_DISABLE_COPY_MOVE(Worker)
public: public:
Worker(const Path &dbPath, QReadWriteLock &dbLock); Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock);
void run() override; void openDatabase() const;
void requestInterruption(); void closeDatabase() const;
void store(const TorrentID &id, const LoadTorrentParams &resumeData); void store(const TorrentID &id, const LoadTorrentParams &resumeData) const;
void remove(const TorrentID &id); void remove(const TorrentID &id) const;
void storeQueue(const QVector<TorrentID> &queue); void storeQueue(const QVector<TorrentID> &queue) const;
private: private:
void addJob(std::unique_ptr<Job> job);
const QString m_connectionName = u"ResumeDataStorageWorker"_s;
const Path m_path; const Path m_path;
const QString m_connectionName;
QReadWriteLock &m_dbLock; 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) BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject *parent)
@@ -316,7 +263,7 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject
{ {
const bool needCreateDB = !dbPath.exists(); 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()); db.setDatabaseName(dbPath.data());
if (!db.open()) if (!db.open())
throw RuntimeError(db.lastError().text()); throw RuntimeError(db.lastError().text());
@@ -332,20 +279,37 @@ BitTorrent::DBResumeDataStorage::DBResumeDataStorage(const Path &dbPath, QObject
updateDB(dbVersion); updateDB(dbVersion);
} }
m_asyncWorker = new Worker(dbPath, m_dbLock); m_asyncWorker = new Worker(dbPath, u"ResumeDataStorageWorker"_qs, m_dbLock);
m_asyncWorker->start(); 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() BitTorrent::DBResumeDataStorage::~DBResumeDataStorage()
{ {
m_asyncWorker->requestInterruption(); QMetaObject::invokeMethod(m_asyncWorker, &Worker::closeDatabase);
m_asyncWorker->wait();
QSqlDatabase::removeDatabase(DB_CONNECTION_NAME); QSqlDatabase::removeDatabase(DB_CONNECTION_NAME);
} }
QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents() const QVector<BitTorrent::TorrentID> BitTorrent::DBResumeDataStorage::registeredTorrents() const
{ {
const auto selectTorrentIDStatement = u"SELECT %1 FROM %2 ORDER BY %3;"_s 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)); .arg(quoted(DB_COLUMN_TORRENT_ID.name), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
auto db = QSqlDatabase::database(DB_CONNECTION_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 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); .arg(quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_TORRENT_ID.name), DB_COLUMN_TORRENT_ID.placeholder);
auto db = QSqlDatabase::database(DB_CONNECTION_NAME); 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 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 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 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 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()); db.setDatabaseName(path().data());
if (!db.open()) if (!db.open())
throw RuntimeError(db.lastError().text()); throw RuntimeError(db.lastError().text());
QSqlQuery query {db}; 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)); .arg(quoted(DB_COLUMN_TORRENT_ID.name), quoted(DB_TABLE_TORRENTS), quoted(DB_COLUMN_QUEUE_POSITION.name));
const QReadLocker locker {&m_dbLock}; const QReadLocker locker {&m_dbLock};
@@ -432,7 +405,7 @@ void BitTorrent::DBResumeDataStorage::doLoadAll() const
emit const_cast<DBResumeDataStorage *>(this)->loadStarted(registeredTorrents); 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)) if (!query.exec(selectStatement))
throw RuntimeError(query.lastError().text()); throw RuntimeError(query.lastError().text());
@@ -450,7 +423,7 @@ void BitTorrent::DBResumeDataStorage::doLoadAll() const
int BitTorrent::DBResumeDataStorage::currentDBVersion() 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); .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); auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
@@ -479,18 +452,10 @@ int BitTorrent::DBResumeDataStorage::currentDBVersion() const
void BitTorrent::DBResumeDataStorage::createDB() 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); auto db = QSqlDatabase::database(DB_CONNECTION_NAME);
const QWriteLocker locker {&m_dbLock};
if (!db.transaction()) if (!db.transaction())
throw RuntimeError(db.lastError().text()); 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_CONTENT_LAYOUT, "TEXT NOT NULL"),
makeColumnDefinition(DB_COLUMN_RATIO_LIMIT, "INTEGER NOT NULL"), makeColumnDefinition(DB_COLUMN_RATIO_LIMIT, "INTEGER NOT NULL"),
makeColumnDefinition(DB_COLUMN_SEEDING_TIME_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_OUTER_PIECES_PRIORITY, "INTEGER NOT NULL"),
makeColumnDefinition(DB_COLUMN_HAS_SEED_STATUS, "INTEGER NOT NULL"), makeColumnDefinition(DB_COLUMN_HAS_SEED_STATUS, "INTEGER NOT NULL"),
makeColumnDefinition(DB_COLUMN_OPERATING_MODE, "TEXT NOT NULL"), makeColumnDefinition(DB_COLUMN_OPERATING_MODE, "TEXT NOT NULL"),
@@ -542,12 +506,6 @@ void BitTorrent::DBResumeDataStorage::createDB() const
if (!query.exec(createTableTorrentsQuery)) if (!query.exec(createTableTorrentsQuery))
throw RuntimeError(query.lastError().text()); 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()) if (!db.commit())
throw RuntimeError(db.lastError().text()); throw RuntimeError(db.lastError().text());
} }
@@ -576,11 +534,11 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
{ {
if (fromVersion == 1) 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)); .arg(quoted(DB_COLUMN_DOWNLOAD_PATH.name), quoted(DB_TABLE_TORRENTS));
if (!query.exec(testQuery)) 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")); .arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_DOWNLOAD_PATH, "TEXT"));
if (!query.exec(alterTableTorrentsQuery)) if (!query.exec(alterTableTorrentsQuery))
throw RuntimeError(query.lastError().text()); throw RuntimeError(query.lastError().text());
@@ -589,34 +547,17 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
if (fromVersion <= 2) 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)); .arg(quoted(DB_COLUMN_STOP_CONDITION.name), quoted(DB_TABLE_TORRENTS));
if (!query.exec(testQuery)) 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`")); .arg(quoted(DB_TABLE_TORRENTS), makeColumnDefinition(DB_COLUMN_STOP_CONDITION, "TEXT NOT NULL DEFAULT `None`"));
if (!query.exec(alterTableTorrentsQuery)) if (!query.exec(alterTableTorrentsQuery))
throw RuntimeError(query.lastError().text()); 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 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}); const QString updateMetaVersionQuery = makeUpdateStatement(DB_TABLE_META, {DB_COLUMN_NAME, DB_COLUMN_VALUE});
if (!query.prepare(updateMetaVersionQuery)) if (!query.prepare(updateMetaVersionQuery))
throw RuntimeError(query.lastError().text()); throw RuntimeError(query.lastError().text());
@@ -637,307 +578,217 @@ void BitTorrent::DBResumeDataStorage::updateDB(const int fromVersion) const
} }
} }
void BitTorrent::DBResumeDataStorage::enableWALMode() const BitTorrent::DBResumeDataStorage::Worker::Worker(const Path &dbPath, const QString &dbConnectionName, QReadWriteLock &dbLock)
{
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)
: m_path {dbPath} : m_path {dbPath}
, m_connectionName {dbConnectionName}
, m_dbLock {dbLock} , m_dbLock {dbLock}
{ {
} }
void BitTorrent::DBResumeDataStorage::Worker::run() void BitTorrent::DBResumeDataStorage::Worker::openDatabase() const
{ {
{ auto db = QSqlDatabase::addDatabase(u"QSQLITE"_qs, m_connectionName);
auto db = QSqlDatabase::addDatabase(u"QSQLITE"_s, m_connectionName); db.setDatabaseName(m_path.data());
db.setDatabaseName(m_path.data()); if (!db.open())
if (!db.open()) throw RuntimeError(db.lastError().text());
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();
}
void BitTorrent::DBResumeDataStorage::Worker::closeDatabase() const
{
QSqlDatabase::removeDatabase(m_connectionName); QSqlDatabase::removeDatabase(m_connectionName);
} }
void DBResumeDataStorage::Worker::requestInterruption() void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData) const
{ {
QThread::requestInterruption(); // We need to adjust native libtorrent resume data
m_waitCondition.wakeAll(); lt::add_torrent_params p = resumeData.ltAddTorrentParams;
} p.save_path = Profile::instance()->toPortablePath(Path(p.save_path))
.toString().toStdString();
void BitTorrent::DBResumeDataStorage::Worker::store(const TorrentID &id, const LoadTorrentParams &resumeData) if (resumeData.stopped)
{
addJob(std::make_unique<StoreJob>(id, resumeData));
}
void BitTorrent::DBResumeDataStorage::Worker::remove(const TorrentID &id)
{
addJob(std::make_unique<RemoveJob>(id));
}
void BitTorrent::DBResumeDataStorage::Worker::storeQueue(const QVector<TorrentID> &queue)
{
addJob(std::make_unique<StoreQueueJob>(queue));
}
void BitTorrent::DBResumeDataStorage::Worker::addJob(std::unique_ptr<Job> job)
{
m_jobsMutex.lock();
m_jobs.push(std::move(job));
m_jobsMutex.unlock();
m_waitCondition.wakeAll();
}
namespace
{
using namespace BitTorrent;
StoreJob::StoreJob(const TorrentID &torrentID, const LoadTorrentParams &resumeData)
: m_torrentID {torrentID}
, m_resumeData {resumeData}
{ {
p.flags |= lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
} }
else
void StoreJob::perform(QSqlDatabase db)
{ {
// We need to adjust native libtorrent resume data // Torrent can be actually "running" but temporarily "paused" to perform some
lt::add_torrent_params p = m_resumeData.ltAddTorrentParams; // service jobs behind the scenes so we need to restore it as "running"
p.save_path = Profile::instance()->toPortablePath(Path(p.save_path)) if (resumeData.operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged)
.toString().toStdString();
if (m_resumeData.stopped)
{ {
p.flags |= lt::torrent_flags::paused; p.flags |= lt::torrent_flags::auto_managed;
p.flags &= ~lt::torrent_flags::auto_managed;
} }
else else
{ {
// Torrent can be actually "running" but temporarily "paused" to perform some p.flags &= ~lt::torrent_flags::paused;
// service jobs behind the scenes so we need to restore it as "running" p.flags &= ~lt::torrent_flags::auto_managed;
if (m_resumeData.operatingMode == BitTorrent::TorrentOperatingMode::AutoManaged)
{
p.flags |= lt::torrent_flags::auto_managed;
}
else
{
p.flags &= ~lt::torrent_flags::paused;
p.flags &= ~lt::torrent_flags::auto_managed;
}
} }
}
QVector<Column> columns { QVector<Column> columns {
DB_COLUMN_TORRENT_ID, DB_COLUMN_TORRENT_ID,
DB_COLUMN_NAME, DB_COLUMN_NAME,
DB_COLUMN_CATEGORY, DB_COLUMN_CATEGORY,
DB_COLUMN_TAGS, DB_COLUMN_TAGS,
DB_COLUMN_TARGET_SAVE_PATH, DB_COLUMN_TARGET_SAVE_PATH,
DB_COLUMN_DOWNLOAD_PATH, DB_COLUMN_DOWNLOAD_PATH,
DB_COLUMN_CONTENT_LAYOUT, DB_COLUMN_CONTENT_LAYOUT,
DB_COLUMN_RATIO_LIMIT, DB_COLUMN_RATIO_LIMIT,
DB_COLUMN_SEEDING_TIME_LIMIT, DB_COLUMN_SEEDING_TIME_LIMIT,
DB_COLUMN_INACTIVE_SEEDING_TIME_LIMIT, DB_COLUMN_HAS_OUTER_PIECES_PRIORITY,
DB_COLUMN_HAS_OUTER_PIECES_PRIORITY, DB_COLUMN_HAS_SEED_STATUS,
DB_COLUMN_HAS_SEED_STATUS, DB_COLUMN_OPERATING_MODE,
DB_COLUMN_OPERATING_MODE, DB_COLUMN_STOPPED,
DB_COLUMN_STOPPED, DB_COLUMN_STOP_CONDITION,
DB_COLUMN_STOP_CONDITION, DB_COLUMN_RESUMEDATA
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 // metadata is stored in separate column
QByteArray bencodedMetadata; QByteArray bencodedMetadata;
if (p.ti) 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(); bencodedMetadata.reserve(512 * 1024);
lt::entry metadata {lt::entry::dictionary_t}; lt::bencode(std::back_inserter(bencodedMetadata), metadata);
lt::entry::dictionary_type &metadataDict = metadata.dict(); }
metadataDict.insert(dataDict.extract("info")); catch (const std::exception &err)
metadataDict.insert(dataDict.extract("creation date")); {
metadataDict.insert(dataDict.extract("created by")); LogMsg(tr("Couldn't save torrent metadata. Error: %1.")
metadataDict.insert(dataDict.extract("comment")); .arg(QString::fromLocal8Bit(err.what())), Log::CRITICAL);
return;
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);
} }
QByteArray bencodedResumeData; columns.append(DB_COLUMN_METADATA);
bencodedResumeData.reserve(256 * 1024); }
lt::bencode(std::back_inserter(bencodedResumeData), data);
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}; QSqlQuery query {db};
try 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)) if (!query.prepare(updateQueuePosStatement))
throw RuntimeError(query.lastError().text()); throw RuntimeError(query.lastError().text());
int pos = 0; 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_TORRENT_ID.placeholder, torrentID.toString());
query.bindValue(DB_COLUMN_QUEUE_POSITION.placeholder, pos++); query.bindValue(DB_COLUMN_QUEUE_POSITION.placeholder, pos++);
if (!query.exec()) if (!query.exec())
throw RuntimeError(query.lastError().text()); 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") db.rollback();
.arg(err.message()), Log::CRITICAL); 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; int currentDBVersion() const;
void createDB() const; void createDB() const;
void updateDB(int fromVersion) const; void updateDB(int fromVersion) const;
void enableWALMode() const;
Utils::Thread::UniquePtr m_ioThread; Utils::Thread::UniquePtr m_ioThread;

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@@ -28,8 +28,6 @@
#pragma once #pragma once
#include <set>
#include <string>
#include <vector> #include <vector>
#include <libtorrent/announce_entry.hpp> #include <libtorrent/announce_entry.hpp>
@@ -46,5 +44,4 @@ struct ExtensionData
{ {
lt::torrent_status status; lt::torrent_status status;
std::vector<lt::announce_entry> trackers; std::vector<lt::announce_entry> trackers;
std::set<std::string> urlSeeds;
}; };

View File

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

View File

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

View File

@@ -93,7 +93,7 @@ BitTorrent::InfoHash::operator WrappedType() const
BitTorrent::TorrentID BitTorrent::TorrentID::fromString(const QString &hashString) 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) 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) BitTorrent::TorrentID BitTorrent::TorrentID::fromSHA1Hash(const SHA1Hash &hash)
{ {
return {hash}; return TorrentID(hash);
} }
BitTorrent::TorrentID BitTorrent::TorrentID::fromSHA256Hash(const SHA256Hash &hash) BitTorrent::TorrentID BitTorrent::TorrentID::fromSHA256Hash(const SHA256Hash &hash)

View File

@@ -52,14 +52,11 @@ namespace BitTorrent
TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged; TorrentOperatingMode operatingMode = TorrentOperatingMode::AutoManaged;
bool useAutoTMM = false; bool useAutoTMM = false;
bool firstLastPiecePriority = false; bool firstLastPiecePriority = false;
bool hasFinishedStatus = false; bool hasSeedStatus = false;
bool stopped = false; bool stopped = false;
Torrent::StopCondition stopCondition = Torrent::StopCondition::None; Torrent::StopCondition stopCondition;
bool addToQueueTop = false; // only for new torrents
qreal ratioLimit = Torrent::USE_GLOBAL_RATIO; qreal ratioLimit = Torrent::USE_GLOBAL_RATIO;
int seedingTimeLimit = Torrent::USE_GLOBAL_SEEDING_TIME; 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; const int V1_BASE32_SIZE = SHA1Hash::length() * 1.6;
return ((((string.size() == V1_HEX_SIZE)) 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.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) bool isV2Hash(const QString &string)
@@ -66,7 +66,7 @@ namespace
const int V2_HEX_SIZE = SHA256Hash::length() * 2; const int V2_HEX_SIZE = SHA256Hash::length() * 2;
return (string.size() == V2_HEX_SIZE) 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>(); const int magnetUriId = qRegisterMetaType<MagnetUri>();
MagnetUri::MagnetUri(const QString &source) MagnetUri::MagnetUri(const QString &source)
: m_url(source) : m_valid(false)
, m_url(source)
{ {
if (source.isEmpty()) return; if (source.isEmpty()) return;

View File

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

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * 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() lt::feature_flags_t NativeSessionExtension::implemented_features()
{ {
return alert_feature; return alert_feature;
@@ -67,9 +56,6 @@ void NativeSessionExtension::on_alert(const lt::alert *alert)
{ {
switch (alert->type()) 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: case lt::fastresume_rejected_alert::alert_type:
handleFastresumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert *>(alert)); handleFastresumeRejectedAlert(static_cast<const lt::fastresume_rejected_alert *>(alert));
break; break;
@@ -77,9 +63,3 @@ void NativeSessionExtension::on_alert(const lt::alert *alert)
break; 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 #pragma once
#include <libtorrent/extensions.hpp> #include <libtorrent/extensions.hpp>
#include <libtorrent/fwd.hpp>
#include <libtorrent/session_handle.hpp>
#include <QReadWriteLock>
#include "extensiondata.h" #include "extensiondata.h"
class NativeSessionExtension final : public lt::plugin 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; lt::feature_flags_t implemented_features() override;
std::shared_ptr<lt::torrent_plugin> new_torrent(const lt::torrent_handle &torrentHandle, LTClientData clientData) 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 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. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * 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->status = m_torrentHandle.status();
m_data->trackers = m_torrentHandle.trackers(); 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); on_state(m_data ? m_data->status.state : m_torrentHandle.status({}).state);

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@@ -31,16 +31,16 @@
#include <QBitArray> #include <QBitArray>
#include "base/bittorrent/ltqbitarray.h" #include "base/bittorrent/ltqbitarray.h"
#include "base/bittorrent/torrent.h"
#include "base/net/geoipmanager.h" #include "base/net/geoipmanager.h"
#include "base/unicodestrings.h" #include "base/unicodestrings.h"
#include "base/utils/bytearray.h"
#include "peeraddress.h" #include "peeraddress.h"
using namespace BitTorrent; 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_nativeInfo(nativeInfo)
, m_relevance(calcRelevance(allPieces)) , m_relevance(calcRelevance(torrent))
{ {
determineFlags(); determineFlags();
} }
@@ -169,9 +169,6 @@ bool PeerInfo::isPlaintextEncrypted() const
PeerAddress PeerInfo::address() const PeerAddress PeerInfo::address() const
{ {
if (useI2PSocket())
return {};
// fast path for platforms which boost.asio internal struct maps to `sockaddr` // fast path for platforms which boost.asio internal struct maps to `sockaddr`
return {QHostAddress(m_nativeInfo.ip.data()), m_nativeInfo.ip.port()}; return {QHostAddress(m_nativeInfo.ip.data()), m_nativeInfo.ip.port()};
// slow path for the others // slow path for the others
@@ -179,23 +176,6 @@ PeerAddress PeerInfo::address() const
// , m_nativeInfo.ip.port()}; // , 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 QString PeerInfo::client() const
{ {
return QString::fromStdString(m_nativeInfo.client); return QString::fromStdString(m_nativeInfo.client);
@@ -262,12 +242,13 @@ QString PeerInfo::connectionType() const
return C_UTP; return C_UTP;
return (m_nativeInfo.connection_type == lt::peer_info::standard_bittorrent) return (m_nativeInfo.connection_type == lt::peer_info::standard_bittorrent)
? u"BT"_s ? u"BT"_qs
: u"Web"_s; : 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); const int localMissing = allPieces.count(false);
if (localMissing <= 0) if (localMissing <= 0)
return 0; return 0;
@@ -287,7 +268,7 @@ void PeerInfo::determineFlags()
const auto updateFlags = [this](const QChar specifier, const QString &explanation) const auto updateFlags = [this](const QChar specifier, const QString &explanation)
{ {
m_flags += (specifier + u' '); 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()) if (isInteresting())

View File

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

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@@ -28,13 +28,14 @@
#include "portforwarderimpl.h" #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) PortForwarderImpl::PortForwarderImpl(lt::session *provider, QObject *parent)
: Net::PortForwarder(parent) : Net::PortForwarder {parent}
, m_storeActive {u"Network/PortForwardingEnabled"_s, true} , m_storeActive {u"Network/PortForwardingEnabled"_qs, true}
, m_provider {provider} , m_provider {provider}
{ {
if (isEnabled()) if (isEnabled())
@@ -65,13 +66,38 @@ void PortForwarderImpl::setEnabled(const bool enabled)
void PortForwarderImpl::setPorts(const QString &profile, QSet<quint16> ports) 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); // remove outdated forwardings
const QSet<quint16> newForwardedPorts = std::accumulate(m_portProfiles.cbegin(), m_portProfiles.cend(), QSet<quint16>()); for (const lt::port_mapping_t &handle : handles)
m_provider->delete_port_mapping(handle);
m_forwardedPorts.remove(port);
m_provider->removeMappedPorts(oldForwardedPorts - newForwardedPorts); return true;
m_provider->addMappedPorts(newForwardedPorts - oldForwardedPorts); });
// 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) void PortForwarderImpl::removePorts(const QString &profile)
@@ -81,12 +107,40 @@ void PortForwarderImpl::removePorts(const QString &profile)
void PortForwarderImpl::start() void PortForwarderImpl::start()
{ {
m_provider->enablePortMapping(); lt::settings_pack settingsPack;
for (const QSet<quint16> &ports : asConst(m_portProfiles)) settingsPack.set_bool(lt::settings_pack::enable_upnp, true);
m_provider->addMappedPorts(ports); 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() 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. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * modify it under the terms of the GNU General Public License
@@ -28,24 +28,24 @@
#pragma once #pragma once
#include <vector>
#include <libtorrent/fwd.hpp>
#include <libtorrent/portmap.hpp>
#include <QHash> #include <QHash>
#include <QSet> #include <QSet>
#include "base/net/portforwarder.h" #include "base/net/portforwarder.h"
#include "base/settingvalue.h" #include "base/settingvalue.h"
namespace BitTorrent
{
class SessionImpl;
}
class PortForwarderImpl final : public Net::PortForwarder class PortForwarderImpl final : public Net::PortForwarder
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY_MOVE(PortForwarderImpl) Q_DISABLE_COPY_MOVE(PortForwarderImpl)
public: public:
explicit PortForwarderImpl(BitTorrent::SessionImpl *provider, QObject *parent = nullptr); explicit PortForwarderImpl(lt::session *provider, QObject *parent = nullptr);
~PortForwarderImpl() override; ~PortForwarderImpl() override;
bool isEnabled() const override; bool isEnabled() const override;
@@ -59,7 +59,9 @@ private:
void stop(); void stop();
CachedSettingValue<bool> m_storeActive; CachedSettingValue<bool> m_storeActive;
lt::session *const m_provider = nullptr;
BitTorrent::SessionImpl *const m_provider = nullptr; using PortMapping = QHash<quint16, std::vector<lt::port_mapping_t>>; // <port, handles>
QHash<QString, QSet<quint16>> m_portProfiles; QHash<QString, PortMapping> m_portProfiles;
QSet<quint16> m_forwardedPorts;
}; };

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -170,9 +170,6 @@ namespace BitTorrent
virtual bool useCategoryPathsInManualMode() const = 0; virtual bool useCategoryPathsInManualMode() const = 0;
virtual void setUseCategoryPathsInManualMode(bool value) = 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); static bool isValidTag(const QString &tag);
virtual QSet<QString> tags() const = 0; virtual QSet<QString> tags() const = 0;
virtual bool hasTag(const QString &tag) const = 0; virtual bool hasTag(const QString &tag) const = 0;
@@ -203,16 +200,12 @@ namespace BitTorrent
virtual void setGlobalMaxRatio(qreal ratio) = 0; virtual void setGlobalMaxRatio(qreal ratio) = 0;
virtual int globalMaxSeedingMinutes() const = 0; virtual int globalMaxSeedingMinutes() const = 0;
virtual void setGlobalMaxSeedingMinutes(int minutes) = 0; virtual void setGlobalMaxSeedingMinutes(int minutes) = 0;
virtual int globalMaxInactiveSeedingMinutes() const = 0;
virtual void setGlobalMaxInactiveSeedingMinutes(int minutes) = 0;
virtual bool isDHTEnabled() const = 0; virtual bool isDHTEnabled() const = 0;
virtual void setDHTEnabled(bool enabled) = 0; virtual void setDHTEnabled(bool enabled) = 0;
virtual bool isLSDEnabled() const = 0; virtual bool isLSDEnabled() const = 0;
virtual void setLSDEnabled(bool enabled) = 0; virtual void setLSDEnabled(bool enabled) = 0;
virtual bool isPeXEnabled() const = 0; virtual bool isPeXEnabled() const = 0;
virtual void setPeXEnabled(bool enabled) = 0; virtual void setPeXEnabled(bool enabled) = 0;
virtual bool isAddTorrentToQueueTop() const = 0;
virtual void setAddTorrentToQueueTop(bool value) = 0;
virtual bool isAddTorrentPaused() const = 0; virtual bool isAddTorrentPaused() const = 0;
virtual void setAddTorrentPaused(bool value) = 0; virtual void setAddTorrentPaused(bool value) = 0;
virtual Torrent::StopCondition torrentStopCondition() const = 0; virtual Torrent::StopCondition torrentStopCondition() const = 0;
@@ -265,24 +258,10 @@ namespace BitTorrent
virtual void setEncryption(int state) = 0; virtual void setEncryption(int state) = 0;
virtual int maxActiveCheckingTorrents() const = 0; virtual int maxActiveCheckingTorrents() const = 0;
virtual void setMaxActiveCheckingTorrents(int val) = 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 bool isProxyPeerConnectionsEnabled() const = 0;
virtual void setProxyPeerConnectionsEnabled(bool enabled) = 0; virtual void setProxyPeerConnectionsEnabled(bool enabled) = 0;
virtual bool isProxyHostnameLookupEnabled() const = 0;
virtual void setProxyHostnameLookupEnabled(bool enabled) = 0;
virtual ChokingAlgorithm chokingAlgorithm() const = 0; virtual ChokingAlgorithm chokingAlgorithm() const = 0;
virtual void setChokingAlgorithm(ChokingAlgorithm mode) = 0; virtual void setChokingAlgorithm(ChokingAlgorithm mode) = 0;
virtual SeedChokingAlgorithm seedChokingAlgorithm() const = 0; virtual SeedChokingAlgorithm seedChokingAlgorithm() const = 0;
@@ -341,10 +320,6 @@ namespace BitTorrent
virtual void setSendBufferWatermarkFactor(int value) = 0; virtual void setSendBufferWatermarkFactor(int value) = 0;
virtual int connectionSpeed() const = 0; virtual int connectionSpeed() const = 0;
virtual void setConnectionSpeed(int value) = 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 int socketBacklogSize() const = 0;
virtual void setSocketBacklogSize(int value) = 0; virtual void setSocketBacklogSize(int value) = 0;
virtual bool isAnonymousModeEnabled() const = 0; virtual bool isAnonymousModeEnabled() const = 0;
@@ -413,7 +388,7 @@ namespace BitTorrent
virtual bool isTrackerFilteringEnabled() const = 0; virtual bool isTrackerFilteringEnabled() const = 0;
virtual void setTrackerFilteringEnabled(bool enabled) = 0; virtual void setTrackerFilteringEnabled(bool enabled) = 0;
virtual bool isExcludedFileNamesEnabled() const = 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 QStringList excludedFileNames() const = 0;
virtual void setExcludedFileNames(const QStringList &newList) = 0; virtual void setExcludedFileNames(const QStringList &newList) = 0;
virtual bool isFilenameExcluded(const QString &fileName) const = 0; virtual bool isFilenameExcluded(const QString &fileName) const = 0;
@@ -421,8 +396,6 @@ namespace BitTorrent
virtual void setBannedIPs(const QStringList &newList) = 0; virtual void setBannedIPs(const QStringList &newList) = 0;
virtual ResumeDataStorageType resumeDataStorageType() const = 0; virtual ResumeDataStorageType resumeDataStorageType() const = 0;
virtual void setResumeDataStorageType(ResumeDataStorageType type) = 0; virtual void setResumeDataStorageType(ResumeDataStorageType type) = 0;
virtual bool isMergeTrackersEnabled() const = 0;
virtual void setMergeTrackersEnabled(bool enabled) = 0;
virtual bool isRestored() const = 0; virtual bool isRestored() const = 0;
@@ -458,7 +431,6 @@ namespace BitTorrent
void allTorrentsFinished(); void allTorrentsFinished();
void categoryAdded(const QString &categoryName); void categoryAdded(const QString &categoryName);
void categoryRemoved(const QString &categoryName); void categoryRemoved(const QString &categoryName);
void categoryOptionsChanged(const QString &categoryName);
void downloadFromUrlFailed(const QString &url, const QString &reason); void downloadFromUrlFailed(const QString &url, const QString &reason);
void downloadFromUrlFinished(const QString &url); void downloadFromUrlFinished(const QString &url);
void fullDiskError(Torrent *torrent, const QString &msg); void fullDiskError(Torrent *torrent, const QString &msg);
@@ -493,6 +465,6 @@ namespace BitTorrent
void trackersRemoved(Torrent *torrent, const QStringList &trackers); void trackersRemoved(Torrent *torrent, const QStringList &trackers);
void trackerSuccess(Torrent *torrent, const QString &tracker); void trackerSuccess(Torrent *torrent, const QString &tracker);
void trackerWarning(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. * 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> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -29,20 +29,17 @@
#pragma once #pragma once
#include <utility>
#include <variant> #include <variant>
#include <vector> #include <vector>
#include <libtorrent/fwd.hpp> #include <libtorrent/fwd.hpp>
#include <libtorrent/portmap.hpp>
#include <libtorrent/torrent_handle.hpp> #include <libtorrent/torrent_handle.hpp>
#include <QtContainerFwd>
#include <QDateTime>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QHash> #include <QHash>
#include <QPointer> #include <QPointer>
#include <QSet> #include <QSet>
#include <QtContainerFwd>
#include <QVector> #include <QVector>
#include "base/path.h" #include "base/path.h"
@@ -63,14 +60,12 @@ class QNetworkConfigurationManager;
#endif #endif
class QString; class QString;
class QThread; class QThread;
class QThreadPool;
class QTimer; class QTimer;
class QUrl; class QUrl;
class BandwidthScheduler; class BandwidthScheduler;
class FileSearcher; class FileSearcher;
class FilterParserThread; class FilterParserThread;
class NativeSessionExtension;
namespace Net namespace Net
{ {
@@ -88,7 +83,6 @@ namespace BitTorrent
struct LoadTorrentParams; struct LoadTorrentParams;
enum class MoveStorageMode; enum class MoveStorageMode;
enum class MoveStorageContext;
struct SessionMetricIndices struct SessionMetricIndices
{ {
@@ -161,9 +155,6 @@ namespace BitTorrent
bool useCategoryPathsInManualMode() const override; bool useCategoryPathsInManualMode() const override;
void setUseCategoryPathsInManualMode(bool value) 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; QSet<QString> tags() const override;
bool hasTag(const QString &tag) const override; bool hasTag(const QString &tag) const override;
bool addTag(const QString &tag) override; bool addTag(const QString &tag) override;
@@ -182,16 +173,12 @@ namespace BitTorrent
void setGlobalMaxRatio(qreal ratio) override; void setGlobalMaxRatio(qreal ratio) override;
int globalMaxSeedingMinutes() const override; int globalMaxSeedingMinutes() const override;
void setGlobalMaxSeedingMinutes(int minutes) override; void setGlobalMaxSeedingMinutes(int minutes) override;
int globalMaxInactiveSeedingMinutes() const override;
void setGlobalMaxInactiveSeedingMinutes(int minutes) override;
bool isDHTEnabled() const override; bool isDHTEnabled() const override;
void setDHTEnabled(bool enabled) override; void setDHTEnabled(bool enabled) override;
bool isLSDEnabled() const override; bool isLSDEnabled() const override;
void setLSDEnabled(bool enabled) override; void setLSDEnabled(bool enabled) override;
bool isPeXEnabled() const override; bool isPeXEnabled() const override;
void setPeXEnabled(bool enabled) override; void setPeXEnabled(bool enabled) override;
bool isAddTorrentToQueueTop() const override;
void setAddTorrentToQueueTop(bool value) override;
bool isAddTorrentPaused() const override; bool isAddTorrentPaused() const override;
void setAddTorrentPaused(bool value) override; void setAddTorrentPaused(bool value) override;
Torrent::StopCondition torrentStopCondition() const override; Torrent::StopCondition torrentStopCondition() const override;
@@ -244,24 +231,10 @@ namespace BitTorrent
void setEncryption(int state) override; void setEncryption(int state) override;
int maxActiveCheckingTorrents() const override; int maxActiveCheckingTorrents() const override;
void setMaxActiveCheckingTorrents(int val) 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; bool isProxyPeerConnectionsEnabled() const override;
void setProxyPeerConnectionsEnabled(bool enabled) override; void setProxyPeerConnectionsEnabled(bool enabled) override;
bool isProxyHostnameLookupEnabled() const override;
void setProxyHostnameLookupEnabled(bool enabled) override;
ChokingAlgorithm chokingAlgorithm() const override; ChokingAlgorithm chokingAlgorithm() const override;
void setChokingAlgorithm(ChokingAlgorithm mode) override; void setChokingAlgorithm(ChokingAlgorithm mode) override;
SeedChokingAlgorithm seedChokingAlgorithm() const override; SeedChokingAlgorithm seedChokingAlgorithm() const override;
@@ -320,10 +293,6 @@ namespace BitTorrent
void setSendBufferWatermarkFactor(int value) override; void setSendBufferWatermarkFactor(int value) override;
int connectionSpeed() const override; int connectionSpeed() const override;
void setConnectionSpeed(int value) 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; int socketBacklogSize() const override;
void setSocketBacklogSize(int value) override; void setSocketBacklogSize(int value) override;
bool isAnonymousModeEnabled() const override; bool isAnonymousModeEnabled() const override;
@@ -392,16 +361,14 @@ namespace BitTorrent
bool isTrackerFilteringEnabled() const override; bool isTrackerFilteringEnabled() const override;
void setTrackerFilteringEnabled(bool enabled) override; void setTrackerFilteringEnabled(bool enabled) override;
bool isExcludedFileNamesEnabled() const override; bool isExcludedFileNamesEnabled() const override;
void setExcludedFileNamesEnabled(bool enabled) override; void setExcludedFileNamesEnabled(const bool enabled) override;
QStringList excludedFileNames() const override; QStringList excludedFileNames() const override;
void setExcludedFileNames(const QStringList &excludedFileNames) override; void setExcludedFileNames(const QStringList &newList) override;
bool isFilenameExcluded(const QString &fileName) const override; bool isFilenameExcluded(const QString &fileName) const override;
QStringList bannedIPs() const override; QStringList bannedIPs() const override;
void setBannedIPs(const QStringList &newList) override; void setBannedIPs(const QStringList &newList) override;
ResumeDataStorageType resumeDataStorageType() const override; ResumeDataStorageType resumeDataStorageType() const override;
void setResumeDataStorageType(ResumeDataStorageType type) override; void setResumeDataStorageType(ResumeDataStorageType type) override;
bool isMergeTrackersEnabled() const override;
void setMergeTrackersEnabled(bool enabled) override;
bool isRestored() const override; bool isRestored() const override;
@@ -436,45 +403,32 @@ namespace BitTorrent
void handleTorrentNeedSaveResumeData(const TorrentImpl *torrent); void handleTorrentNeedSaveResumeData(const TorrentImpl *torrent);
void handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent); void handleTorrentSaveResumeDataRequested(const TorrentImpl *torrent);
void handleTorrentSaveResumeDataFailed(const TorrentImpl *torrent); void handleTorrentSaveResumeDataFailed(const TorrentImpl *torrent);
void handleTorrentShareLimitChanged(TorrentImpl *torrent); void handleTorrentShareLimitChanged(TorrentImpl *const torrent);
void handleTorrentNameChanged(TorrentImpl *torrent); void handleTorrentNameChanged(TorrentImpl *const torrent);
void handleTorrentSavePathChanged(TorrentImpl *torrent); void handleTorrentSavePathChanged(TorrentImpl *const torrent);
void handleTorrentCategoryChanged(TorrentImpl *torrent, const QString &oldCategory); void handleTorrentCategoryChanged(TorrentImpl *const torrent, const QString &oldCategory);
void handleTorrentTagAdded(TorrentImpl *torrent, const QString &tag); void handleTorrentTagAdded(TorrentImpl *const torrent, const QString &tag);
void handleTorrentTagRemoved(TorrentImpl *torrent, const QString &tag); void handleTorrentTagRemoved(TorrentImpl *const torrent, const QString &tag);
void handleTorrentSavingModeChanged(TorrentImpl *torrent); void handleTorrentSavingModeChanged(TorrentImpl *const torrent);
void handleTorrentMetadataReceived(TorrentImpl *torrent); void handleTorrentMetadataReceived(TorrentImpl *const torrent);
void handleTorrentPaused(TorrentImpl *torrent); void handleTorrentPaused(TorrentImpl *const torrent);
void handleTorrentResumed(TorrentImpl *torrent); void handleTorrentResumed(TorrentImpl *const torrent);
void handleTorrentChecked(TorrentImpl *torrent); void handleTorrentChecked(TorrentImpl *const torrent);
void handleTorrentFinished(TorrentImpl *torrent); void handleTorrentFinished(TorrentImpl *const torrent);
void handleTorrentTrackersAdded(TorrentImpl *torrent, const QVector<TrackerEntry> &newTrackers); void handleTorrentTrackersAdded(TorrentImpl *const torrent, const QVector<TrackerEntry> &newTrackers);
void handleTorrentTrackersRemoved(TorrentImpl *torrent, const QStringList &deletedTrackers); void handleTorrentTrackersRemoved(TorrentImpl *const torrent, const QStringList &deletedTrackers);
void handleTorrentTrackersChanged(TorrentImpl *torrent); void handleTorrentTrackersChanged(TorrentImpl *const torrent);
void handleTorrentUrlSeedsAdded(TorrentImpl *torrent, const QVector<QUrl> &newUrlSeeds); void handleTorrentUrlSeedsAdded(TorrentImpl *const torrent, const QVector<QUrl> &newUrlSeeds);
void handleTorrentUrlSeedsRemoved(TorrentImpl *torrent, const QVector<QUrl> &urlSeeds); void handleTorrentUrlSeedsRemoved(TorrentImpl *const torrent, const QVector<QUrl> &urlSeeds);
void handleTorrentResumeDataReady(TorrentImpl *torrent, const LoadTorrentParams &data); void handleTorrentResumeDataReady(TorrentImpl *const torrent, const LoadTorrentParams &data);
void handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash); void handleTorrentInfoHashChanged(TorrentImpl *torrent, const InfoHash &prevInfoHash);
void handleTorrentStorageMovingStateChanged(TorrentImpl *torrent); 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 void findIncompleteFiles(const TorrentInfo &torrentInfo, const Path &savePath
, const Path &downloadPath, const PathList &filePaths = {}) const; , 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: private slots:
void configureDeferred(); void configureDeferred();
void readAlerts(); void readAlerts();
@@ -499,15 +453,14 @@ namespace BitTorrent
{ {
lt::torrent_handle torrentHandle; lt::torrent_handle torrentHandle;
Path path; Path path;
MoveStorageMode mode {}; MoveStorageMode mode;
MoveStorageContext context {};
}; };
struct RemovingTorrentData struct RemovingTorrentData
{ {
QString name; QString name;
Path pathToRemove; Path pathToRemove;
DeleteOption deleteOption {}; DeleteOption deleteOption;
}; };
explicit SessionImpl(QObject *parent = nullptr); explicit SessionImpl(QObject *parent = nullptr);
@@ -515,7 +468,6 @@ namespace BitTorrent
bool hasPerTorrentRatioLimit() const; bool hasPerTorrentRatioLimit() const;
bool hasPerTorrentSeedingTimeLimit() const; bool hasPerTorrentSeedingTimeLimit() const;
bool hasPerTorrentInactiveSeedingTimeLimit() const;
// Session configuration // Session configuration
Q_INVOKABLE void configure(); Q_INVOKABLE void configure();
@@ -577,8 +529,8 @@ namespace BitTorrent
TorrentImpl *createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params); TorrentImpl *createTorrent(const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params);
void saveResumeData(); void saveResumeData();
void saveTorrentsQueue(); void saveTorrentsQueue() const;
void removeTorrentsQueue(); void removeTorrentsQueue() const;
std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const; std::vector<lt::alert *> getPendingAlerts(lt::time_duration time = lt::time_duration::zero()) const;
@@ -594,7 +546,6 @@ namespace BitTorrent
// BitTorrent // BitTorrent
lt::session *m_nativeSession = nullptr; lt::session *m_nativeSession = nullptr;
NativeSessionExtension *m_nativeSessionExtension = nullptr;
bool m_deferredConfigureScheduled = false; bool m_deferredConfigureScheduled = false;
bool m_IPFilteringConfigured = false; bool m_IPFilteringConfigured = false;
@@ -625,8 +576,6 @@ namespace BitTorrent
CachedSettingValue<int> m_sendBufferLowWatermark; CachedSettingValue<int> m_sendBufferLowWatermark;
CachedSettingValue<int> m_sendBufferWatermarkFactor; CachedSettingValue<int> m_sendBufferWatermarkFactor;
CachedSettingValue<int> m_connectionSpeed; CachedSettingValue<int> m_connectionSpeed;
CachedSettingValue<int> m_socketSendBufferSize;
CachedSettingValue<int> m_socketReceiveBufferSize;
CachedSettingValue<int> m_socketBacklogSize; CachedSettingValue<int> m_socketBacklogSize;
CachedSettingValue<bool> m_isAnonymousModeEnabled; CachedSettingValue<bool> m_isAnonymousModeEnabled;
CachedSettingValue<bool> m_isQueueingEnabled; CachedSettingValue<bool> m_isQueueingEnabled;
@@ -663,8 +612,6 @@ namespace BitTorrent
CachedSettingValue<QString> m_additionalTrackers; CachedSettingValue<QString> m_additionalTrackers;
CachedSettingValue<qreal> m_globalMaxRatio; CachedSettingValue<qreal> m_globalMaxRatio;
CachedSettingValue<int> m_globalMaxSeedingMinutes; CachedSettingValue<int> m_globalMaxSeedingMinutes;
CachedSettingValue<int> m_globalMaxInactiveSeedingMinutes;
CachedSettingValue<bool> m_isAddTorrentToQueueTop;
CachedSettingValue<bool> m_isAddTorrentPaused; CachedSettingValue<bool> m_isAddTorrentPaused;
CachedSettingValue<Torrent::StopCondition> m_torrentStopCondition; CachedSettingValue<Torrent::StopCondition> m_torrentStopCondition;
CachedSettingValue<TorrentContentLayout> m_torrentContentLayout; CachedSettingValue<TorrentContentLayout> m_torrentContentLayout;
@@ -688,6 +635,7 @@ namespace BitTorrent
CachedSettingValue<int> m_encryption; CachedSettingValue<int> m_encryption;
CachedSettingValue<int> m_maxActiveCheckingTorrents; CachedSettingValue<int> m_maxActiveCheckingTorrents;
CachedSettingValue<bool> m_isProxyPeerConnectionsEnabled; CachedSettingValue<bool> m_isProxyPeerConnectionsEnabled;
CachedSettingValue<bool> m_isProxyHostnameLookupEnabled;
CachedSettingValue<ChokingAlgorithm> m_chokingAlgorithm; CachedSettingValue<ChokingAlgorithm> m_chokingAlgorithm;
CachedSettingValue<SeedChokingAlgorithm> m_seedChokingAlgorithm; CachedSettingValue<SeedChokingAlgorithm> m_seedChokingAlgorithm;
CachedSettingValue<QStringList> m_storedTags; CachedSettingValue<QStringList> m_storedTags;
@@ -710,15 +658,6 @@ namespace BitTorrent
CachedSettingValue<QStringList> m_excludedFileNames; CachedSettingValue<QStringList> m_excludedFileNames;
CachedSettingValue<QStringList> m_bannedIPs; CachedSettingValue<QStringList> m_bannedIPs;
CachedSettingValue<ResumeDataStorageType> m_resumeDataStorageType; 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; bool m_isRestored = false;
@@ -737,8 +676,6 @@ namespace BitTorrent
qint64 m_previouslyUploaded = 0; qint64 m_previouslyUploaded = 0;
qint64 m_previouslyDownloaded = 0; qint64 m_previouslyDownloaded = 0;
bool m_torrentsQueueChanged = false;
bool m_needSaveTorrentsQueue = false;
bool m_refreshEnqueued = false; bool m_refreshEnqueued = false;
QTimer *m_seedingLimitTimer = nullptr; QTimer *m_seedingLimitTimer = nullptr;
QTimer *m_resumeDataTimer = nullptr; QTimer *m_resumeDataTimer = nullptr;
@@ -749,11 +686,10 @@ namespace BitTorrent
QPointer<Tracker> m_tracker; QPointer<Tracker> m_tracker;
Utils::Thread::UniquePtr m_ioThread; Utils::Thread::UniquePtr m_ioThread;
QThreadPool *m_asyncWorker = nullptr;
ResumeDataStorage *m_resumeDataStorage = nullptr; ResumeDataStorage *m_resumeDataStorage = nullptr;
FileSearcher *m_fileSearcher = 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_torrents;
QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID; QHash<TorrentID, TorrentImpl *> m_hybridTorrentsByAltID;
@@ -765,9 +701,7 @@ namespace BitTorrent
QMap<QString, CategoryOptions> m_categories; QMap<QString, CategoryOptions> m_categories;
QSet<QString> m_tags; QSet<QString> m_tags;
// This field holds amounts of peers reported by trackers in their responses to announces QHash<Torrent *, QSet<QString>> m_updatedTrackerEntries;
// (torrent.tracker_name.tracker_local_endpoint.num_peers)
QHash<lt::torrent_handle, QHash<std::string, QMap<TrackerEntry::Endpoint, int>>> m_updatedTrackerEntries;
// I/O errored torrents // I/O errored torrents
QSet<TorrentID> m_recentErroredTorrents; QSet<TorrentID> m_recentErroredTorrents;
@@ -788,15 +722,6 @@ namespace BitTorrent
bool m_needUpgradeDownloadPath = false; 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::initInstance();
friend void Session::freeInstance(); friend void Session::freeInstance();
friend Session *Session::instance(); friend Session *Session::instance();

View File

@@ -52,12 +52,8 @@ namespace BitTorrent
const int Torrent::USE_GLOBAL_SEEDING_TIME = -2; const int Torrent::USE_GLOBAL_SEEDING_TIME = -2;
const int Torrent::NO_SEEDING_TIME_LIMIT = -1; 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 qreal Torrent::MAX_RATIO = 9999;
const int Torrent::MAX_SEEDING_TIME = 525600; const int Torrent::MAX_SEEDING_TIME = 525600;
const int Torrent::MAX_INACTIVE_SEEDING_TIME = 525600;
TorrentID Torrent::id() const TorrentID Torrent::id() const
{ {

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -37,7 +37,7 @@
#include "base/3rdparty/expected.hpp" #include "base/3rdparty/expected.hpp"
#include "base/pathfwd.h" #include "base/pathfwd.h"
#include "base/tagset.h" #include "base/tagset.h"
#include "torrentcontenthandler.h" #include "abstractfilestorage.h"
class QBitArray; class QBitArray;
class QByteArray; class QByteArray;
@@ -106,10 +106,9 @@ namespace BitTorrent
uint qHash(TorrentState key, uint seed = 0); uint qHash(TorrentState key, uint seed = 0);
#endif #endif
class Torrent : public TorrentContentHandler class Torrent : public AbstractFileStorage
{ {
Q_OBJECT Q_GADGET
Q_DISABLE_COPY_MOVE(Torrent)
public: public:
enum class StopCondition enum class StopCondition
@@ -126,14 +125,10 @@ namespace BitTorrent
static const int USE_GLOBAL_SEEDING_TIME; static const int USE_GLOBAL_SEEDING_TIME;
static const int NO_SEEDING_TIME_LIMIT; 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 qreal MAX_RATIO;
static const int MAX_SEEDING_TIME; 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 InfoHash infoHash() const = 0;
virtual QString name() const = 0; virtual QString name() const = 0;
@@ -196,6 +191,7 @@ namespace BitTorrent
virtual void setSavePath(const Path &savePath) = 0; virtual void setSavePath(const Path &savePath) = 0;
virtual Path downloadPath() const = 0; virtual Path downloadPath() const = 0;
virtual void setDownloadPath(const Path &downloadPath) = 0; virtual void setDownloadPath(const Path &downloadPath) = 0;
virtual Path actualStorageLocation() const = 0;
virtual Path rootPath() const = 0; virtual Path rootPath() const = 0;
virtual Path contentPath() const = 0; virtual Path contentPath() const = 0;
virtual QString category() const = 0; virtual QString category() const = 0;
@@ -214,12 +210,13 @@ namespace BitTorrent
virtual QDateTime addedTime() const = 0; virtual QDateTime addedTime() const = 0;
virtual qreal ratioLimit() const = 0; virtual qreal ratioLimit() const = 0;
virtual int seedingTimeLimit() 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 PathList filePaths() const = 0;
virtual QVector<DownloadPriority> filePriorities() const = 0;
virtual TorrentInfo info() const = 0; virtual TorrentInfo info() const = 0;
virtual bool isFinished() const = 0; virtual bool isSeed() const = 0;
virtual bool isPaused() const = 0; virtual bool isPaused() const = 0;
virtual bool isQueued() const = 0; virtual bool isQueued() const = 0;
virtual bool isForced() const = 0; virtual bool isForced() const = 0;
@@ -234,6 +231,7 @@ namespace BitTorrent
virtual bool isSequentialDownload() const = 0; virtual bool isSequentialDownload() const = 0;
virtual bool hasFirstLastPiecePriority() const = 0; virtual bool hasFirstLastPiecePriority() const = 0;
virtual TorrentState state() const = 0; virtual TorrentState state() const = 0;
virtual bool hasMetadata() const = 0;
virtual bool hasMissingFiles() const = 0; virtual bool hasMissingFiles() const = 0;
virtual bool hasError() const = 0; virtual bool hasError() const = 0;
virtual int queuePosition() const = 0; virtual int queuePosition() const = 0;
@@ -245,6 +243,7 @@ namespace BitTorrent
virtual qlonglong activeTime() const = 0; virtual qlonglong activeTime() const = 0;
virtual qlonglong finishedTime() const = 0; virtual qlonglong finishedTime() const = 0;
virtual qlonglong eta() const = 0; virtual qlonglong eta() const = 0;
virtual QVector<qreal> filesProgress() const = 0;
virtual int seedsCount() const = 0; virtual int seedsCount() const = 0;
virtual int peersCount() const = 0; virtual int peersCount() const = 0;
virtual int leechsCount() const = 0; virtual int leechsCount() const = 0;
@@ -269,7 +268,6 @@ namespace BitTorrent
virtual qreal distributedCopies() const = 0; virtual qreal distributedCopies() const = 0;
virtual qreal maxRatio() const = 0; virtual qreal maxRatio() const = 0;
virtual int maxSeedingTime() const = 0; virtual int maxSeedingTime() const = 0;
virtual int maxInactiveSeedingTime() const = 0;
virtual qreal realRatio() const = 0; virtual qreal realRatio() const = 0;
virtual int uploadPayloadRate() const = 0; virtual int uploadPayloadRate() const = 0;
virtual int downloadPayloadRate() const = 0; virtual int downloadPayloadRate() const = 0;
@@ -278,6 +276,13 @@ namespace BitTorrent
virtual int connectionsCount() const = 0; virtual int connectionsCount() const = 0;
virtual int connectionsLimit() const = 0; virtual int connectionsLimit() const = 0;
virtual qlonglong nextAnnounce() 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 setName(const QString &name) = 0;
virtual void setSequentialDownload(bool enable) = 0; virtual void setSequentialDownload(bool enable) = 0;
@@ -287,15 +292,16 @@ namespace BitTorrent
virtual void forceReannounce(int index = -1) = 0; virtual void forceReannounce(int index = -1) = 0;
virtual void forceDHTAnnounce() = 0; virtual void forceDHTAnnounce() = 0;
virtual void forceRecheck() = 0; virtual void forceRecheck() = 0;
virtual void prioritizeFiles(const QVector<DownloadPriority> &priorities) = 0;
virtual void setRatioLimit(qreal limit) = 0; virtual void setRatioLimit(qreal limit) = 0;
virtual void setSeedingTimeLimit(int limit) = 0; virtual void setSeedingTimeLimit(int limit) = 0;
virtual void setInactiveSeedingTimeLimit(int limit) = 0;
virtual void setUploadLimit(int limit) = 0; virtual void setUploadLimit(int limit) = 0;
virtual void setDownloadLimit(int limit) = 0; virtual void setDownloadLimit(int limit) = 0;
virtual void setSuperSeeding(bool enable) = 0; virtual void setSuperSeeding(bool enable) = 0;
virtual void setDHTDisabled(bool disable) = 0; virtual void setDHTDisabled(bool disable) = 0;
virtual void setPEXDisabled(bool disable) = 0; virtual void setPEXDisabled(bool disable) = 0;
virtual void setLSDDisabled(bool disable) = 0; virtual void setLSDDisabled(bool disable) = 0;
virtual void flushCache() const = 0;
virtual void addTrackers(QVector<TrackerEntry> trackers) = 0; virtual void addTrackers(QVector<TrackerEntry> trackers) = 0;
virtual void removeTrackers(const QStringList &trackers) = 0; virtual void removeTrackers(const QStringList &trackers) = 0;
virtual void replaceTrackers(QVector<TrackerEntry> trackers) = 0; virtual void replaceTrackers(QVector<TrackerEntry> trackers) = 0;
@@ -303,7 +309,7 @@ namespace BitTorrent
virtual void removeUrlSeeds(const QVector<QUrl> &urlSeeds) = 0; virtual void removeUrlSeeds(const QVector<QUrl> &urlSeeds) = 0;
virtual bool connectPeer(const PeerAddress &peerAddress) = 0; virtual bool connectPeer(const PeerAddress &peerAddress) = 0;
virtual void clearPeers() = 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 StopCondition stopCondition() const = 0;
virtual void setStopCondition(StopCondition stopCondition) = 0; virtual void setStopCondition(StopCondition stopCondition) = 0;
@@ -312,11 +318,6 @@ namespace BitTorrent
virtual nonstd::expected<QByteArray, QString> exportToBuffer() const = 0; virtual nonstd::expected<QByteArray, QString> exportToBuffer() const = 0;
virtual nonstd::expected<void, QString> exportToFile(const Path &path) 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; TorrentID id() const;
bool isResumed() const; bool isResumed() const;
qlonglong remainingSize() 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 struct TorrentCreatorParams
{ {
bool isPrivate = false; bool isPrivate;
#ifdef QBT_USES_LIBTORRENT2 #ifdef QBT_USES_LIBTORRENT2
TorrentFormat torrentFormat = TorrentFormat::Hybrid; TorrentFormat torrentFormat;
#else #else
bool isAlignmentOptimized; bool isAlignmentOptimized;
int paddedFileSizeLimit; int paddedFileSizeLimit;
#endif #endif
int pieceSize = 0; int pieceSize;
Path inputPath; Path inputPath;
Path savePath; Path savePath;
QString comment; QString comment;
@@ -74,7 +74,7 @@ namespace BitTorrent
void create(const TorrentCreatorParams &params); void create(const TorrentCreatorParams &params);
#ifdef QBT_USES_LIBTORRENT2 #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 #else
static int calculateTotalPieces(const Path &inputPath static int calculateTotalPieces(const Path &inputPath
, const int pieceSize, const bool isAlignmentOptimized, int paddedFileSizeLimit); , const int pieceSize, const bool isAlignmentOptimized, int paddedFileSizeLimit);

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -36,6 +36,7 @@
#include <libtorrent/address.hpp> #include <libtorrent/address.hpp>
#include <libtorrent/alert_types.hpp> #include <libtorrent/alert_types.hpp>
#include <libtorrent/create_torrent.hpp> #include <libtorrent/create_torrent.hpp>
#include <libtorrent/magnet_uri.hpp>
#include <libtorrent/session.hpp> #include <libtorrent/session.hpp>
#include <libtorrent/storage_defs.hpp> #include <libtorrent/storage_defs.hpp>
#include <libtorrent/time.hpp> #include <libtorrent/time.hpp>
@@ -46,12 +47,11 @@
#include <QByteArray> #include <QByteArray>
#include <QDebug> #include <QDebug>
#include <QPointer> #include <QFile>
#include <QSet> #include <QSet>
#include <QStringList> #include <QStringList>
#include <QUrl> #include <QUrl>
#include "base/exceptions.h"
#include "base/global.h" #include "base/global.h"
#include "base/logger.h" #include "base/logger.h"
#include "base/preferences.h" #include "base/preferences.h"
@@ -82,10 +82,10 @@ namespace
#ifdef QBT_USES_LIBTORRENT2 #ifdef QBT_USES_LIBTORRENT2
void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry 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 #else
void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry void updateTrackerEntry(TrackerEntry &trackerEntry, const lt::announce_entry &nativeEntry
, const QMap<TrackerEntry::Endpoint, int> &updateInfo) , const QMap<TrackerEntry::Endpoint, int> &updateInfo)
#endif #endif
{ {
Q_ASSERT(trackerEntry.url == QString::fromStdString(nativeEntry.url)); Q_ASSERT(trackerEntry.url == QString::fromStdString(nativeEntry.url));
@@ -98,7 +98,7 @@ namespace
QString firstTrackerMessage; QString firstTrackerMessage;
QString firstErrorMessage; QString firstErrorMessage;
#ifdef QBT_USES_LIBTORRENT2 #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 lt::announce_endpoint &endpoint : nativeEntry.endpoints)
{ {
for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2}) for (const auto protocolVersion : {lt::protocol_version::V1, lt::protocol_version::V2})
@@ -236,7 +236,7 @@ namespace
TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
, const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params) , const lt::torrent_handle &nativeHandle, const LoadTorrentParams &params)
: Torrent(session) : QObject(session)
, m_session(session) , m_session(session)
, m_nativeSession(nativeSession) , m_nativeSession(nativeSession)
, m_nativeHandle(nativeHandle) , m_nativeHandle(nativeHandle)
@@ -252,10 +252,9 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
, m_tags(params.tags) , m_tags(params.tags)
, m_ratioLimit(params.ratioLimit) , m_ratioLimit(params.ratioLimit)
, m_seedingTimeLimit(params.seedingTimeLimit) , m_seedingTimeLimit(params.seedingTimeLimit)
, m_inactiveSeedingTimeLimit(params.inactiveSeedingTimeLimit)
, m_operatingMode(params.operatingMode) , m_operatingMode(params.operatingMode)
, m_contentLayout(params.contentLayout) , m_contentLayout(params.contentLayout)
, m_hasFinishedStatus(params.hasFinishedStatus) , m_hasSeedStatus(params.hasSeedStatus)
, m_hasFirstLastPiecePriority(params.firstLastPiecePriority) , m_hasFirstLastPiecePriority(params.firstLastPiecePriority)
, m_useAutoTMM(params.useAutoTMM) , m_useAutoTMM(params.useAutoTMM)
, m_isStopped(params.stopped) , 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)); , 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_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) 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())); m_trackerEntries.reserve(static_cast<decltype(m_trackerEntries)::size_type>(extensionData->trackers.size()));
for (const lt::announce_entry &announceEntry : extensionData->trackers) for (const lt::announce_entry &announceEntry : extensionData->trackers)
m_trackerEntries.append({QString::fromStdString(announceEntry.url), announceEntry.tier}); 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; m_nativeStatus = extensionData->status;
if (hasMetadata())
updateProgress();
updateState(); updateState();
if (hasMetadata()) if (hasMetadata())
@@ -332,14 +324,14 @@ TorrentImpl::TorrentImpl(SessionImpl *session, lt::session *nativeSession
// Remove .unwanted directory if empty // Remove .unwanted directory if empty
const Path newPath = spath / newRelPath; const Path newPath = spath / newRelPath;
qDebug() << "Attempting to remove \".unwanted\" folder at " << (newPath / Path(u".unwanted"_s)).toString(); qDebug() << "Attempting to remove \".unwanted\" folder at " << (newPath / Path(u".unwanted"_qs)).toString();
Utils::Fs::rmdir(newPath / Path(u".unwanted"_s)); Utils::Fs::rmdir(newPath / Path(u".unwanted"_qs));
} }
} }
// == END UPGRADE CODE == // == END UPGRADE CODE ==
} }
TorrentImpl::~TorrentImpl() = default; TorrentImpl::~TorrentImpl() {}
bool TorrentImpl::isValid() const bool TorrentImpl::isValid() const
{ {
@@ -432,16 +424,13 @@ void TorrentImpl::setSavePath(const Path &path)
if (resolvedPath == savePath()) if (resolvedPath == savePath())
return; return;
if (isFinished() || m_hasFinishedStatus || downloadPath().isEmpty()) m_savePath = resolvedPath;
{
moveStorage(resolvedPath, MoveStorageContext::ChangeSavePath); m_session->handleTorrentNeedSaveResumeData(this);
}
else const bool isFinished = isSeed() || m_hasSeedStatus;
{ if (isFinished || downloadPath().isEmpty())
m_savePath = resolvedPath; moveStorage(savePath(), MoveStorageMode::KeepExistingFiles);
m_session->handleTorrentSavePathChanged(this);
m_session->handleTorrentNeedSaveResumeData(this);
}
} }
Path TorrentImpl::downloadPath() const Path TorrentImpl::downloadPath() const
@@ -459,17 +448,13 @@ void TorrentImpl::setDownloadPath(const Path &path)
if (resolvedPath == m_downloadPath) if (resolvedPath == m_downloadPath)
return; return;
const bool isIncomplete = !(isFinished() || m_hasFinishedStatus); m_downloadPath = resolvedPath;
if (isIncomplete)
{ m_session->handleTorrentNeedSaveResumeData(this);
moveStorage((resolvedPath.isEmpty() ? savePath() : resolvedPath), MoveStorageContext::ChangeDownloadPath);
} const bool isFinished = isSeed() || m_hasSeedStatus;
else if (!isFinished)
{ moveStorage((m_downloadPath.isEmpty() ? savePath() : m_downloadPath), MoveStorageMode::KeepExistingFiles);
m_downloadPath = resolvedPath;
m_session->handleTorrentSavePathChanged(this);
m_session->handleTorrentNeedSaveResumeData(this);
}
} }
Path TorrentImpl::rootPath() const Path TorrentImpl::rootPath() const
@@ -548,6 +533,9 @@ Path TorrentImpl::wantedActualPath(int index, const Path &path) const
QVector<TrackerEntry> TorrentImpl::trackers() const QVector<TrackerEntry> TorrentImpl::trackers() const
{ {
if (!m_updatedTrackerEntries.isEmpty())
refreshTrackerEntries();
return m_trackerEntries; return m_trackerEntries;
} }
@@ -622,95 +610,63 @@ void TorrentImpl::replaceTrackers(QVector<TrackerEntry> trackers)
QVector<QUrl> TorrentImpl::urlSeeds() const 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) void TorrentImpl::addUrlSeeds(const QVector<QUrl> &urlSeeds)
{ {
m_session->invokeAsync([urlSeeds, session = m_session const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
, nativeHandle = m_nativeHandle
, thisTorrent = QPointer<TorrentImpl>(this)] 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(); m_nativeHandle.add_url_seed(nativeUrl);
QVector<QUrl> currentSeeds; addedUrlSeeds << url;
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);
}
});
} }
catch (const std::exception &) {} }
});
if (!addedUrlSeeds.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentUrlSeedsAdded(this, addedUrlSeeds);
}
} }
void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds) void TorrentImpl::removeUrlSeeds(const QVector<QUrl> &urlSeeds)
{ {
m_session->invokeAsync([urlSeeds, session = m_session const std::set<std::string> currentSeeds = m_nativeHandle.url_seeds();
, nativeHandle = m_nativeHandle
, thisTorrent = QPointer<TorrentImpl>(this)] 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(); m_nativeHandle.remove_url_seed(nativeUrl);
QVector<QUrl> currentSeeds; removedUrlSeeds << url;
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);
}
});
} }
catch (const std::exception &) {} }
});
if (!removedUrlSeeds.isEmpty())
{
m_session->handleTorrentNeedSaveResumeData(this);
m_session->handleTorrentUrlSeedsRemoved(this, removedUrlSeeds);
}
} }
void TorrentImpl::clearPeers() void TorrentImpl::clearPeers()
@@ -864,11 +820,6 @@ int TorrentImpl::seedingTimeLimit() const
return m_seedingTimeLimit; return m_seedingTimeLimit;
} }
int TorrentImpl::inactiveSeedingTimeLimit() const
{
return m_inactiveSeedingTimeLimit;
}
Path TorrentImpl::filePath(const int index) const Path TorrentImpl::filePath(const int index) const
{ {
Q_ASSERT(index >= 0); Q_ASSERT(index >= 0);
@@ -1022,7 +973,7 @@ bool TorrentImpl::isErrored() const
|| (m_state == TorrentState::Error)); || (m_state == TorrentState::Error));
} }
bool TorrentImpl::isFinished() const bool TorrentImpl::isSeed() const
{ {
return ((m_nativeStatus.state == lt::torrent_status::finished) return ((m_nativeStatus.state == lt::torrent_status::finished)
|| (m_nativeStatus.state == lt::torrent_status::seeding)); || (m_nativeStatus.state == lt::torrent_status::seeding));
@@ -1078,9 +1029,9 @@ void TorrentImpl::updateState()
else if ((m_nativeStatus.state == lt::torrent_status::checking_files) && !isPaused()) 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 // 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()) if (isPaused())
m_state = TorrentState::PausedUploading; m_state = TorrentState::PausedUploading;
@@ -1168,12 +1119,11 @@ qlonglong TorrentImpl::eta() const
const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average(); const SpeedSampleAvg speedAverage = m_payloadRateMonitor.average();
if (isFinished()) if (isSeed())
{ {
const qreal maxRatioValue = maxRatio(); const qreal maxRatioValue = maxRatio();
const int maxSeedingTimeValue = maxSeedingTime(); const int maxSeedingTimeValue = maxSeedingTime();
const int maxInactiveSeedingTimeValue = maxInactiveSeedingTime(); if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0)) return MAX_ETA;
if ((maxRatioValue < 0) && (maxSeedingTimeValue < 0) && (maxInactiveSeedingTimeValue < 0)) return MAX_ETA;
qlonglong ratioEta = MAX_ETA; qlonglong ratioEta = MAX_ETA;
@@ -1196,15 +1146,7 @@ qlonglong TorrentImpl::eta() const
seedingTimeEta = 0; seedingTimeEta = 0;
} }
qlonglong inactiveSeedingTimeEta = MAX_ETA; return std::min(ratioEta, seedingTimeEta);
if (maxInactiveSeedingTimeValue >= 0)
{
inactiveSeedingTimeEta = (maxInactiveSeedingTimeValue * 60) - timeSinceActivity();
inactiveSeedingTimeEta = std::max<qlonglong>(inactiveSeedingTimeEta, 0);
}
return std::min({ratioEta, seedingTimeEta, inactiveSeedingTimeEta});
} }
if (!speedAverage.download) return MAX_ETA; if (!speedAverage.download) return MAX_ETA;
@@ -1217,20 +1159,20 @@ QVector<qreal> TorrentImpl::filesProgress() const
if (!hasMetadata()) if (!hasMetadata())
return {}; return {};
const int count = m_filesProgress.size(); if (m_completedFiles.count(true) == filesCount())
Q_ASSERT(count == filesCount()); return QVector<qreal>(filesCount(), 1);
if (Q_UNLIKELY(count != filesCount()))
return {};
if (m_completedFiles.count(true) == count) std::vector<int64_t> fp;
return QVector<qreal>(count, 1); m_nativeHandle.file_progress(fp, lt::torrent_handle::piece_granularity);
const int count = filesCount();
const auto nativeIndexes = m_torrentInfo.nativeIndexes();
QVector<qreal> result; QVector<qreal> result;
result.reserve(count); result.reserve(count);
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
const int64_t progress = m_filesProgress.at(i); const int64_t progress = fp[LT::toUnderlyingType(nativeIndexes[i])];
const int64_t size = fileSize(i); const qlonglong size = fileSize(i);
if ((size <= 0) || (progress == size)) if ((size <= 0) || (progress == size))
result << 1; result << 1;
else else
@@ -1349,13 +1291,15 @@ QVector<PeerInfo> TorrentImpl::peers() const
peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size())); peers.reserve(static_cast<decltype(peers)::size_type>(nativePeers.size()));
for (const lt::peer_info &peer : nativePeers) for (const lt::peer_info &peer : nativePeers)
peers.append(PeerInfo(peer, pieces())); peers << PeerInfo(this, peer);
return peers; return peers;
} }
QBitArray TorrentImpl::pieces() const QBitArray TorrentImpl::pieces() const
{ {
if (m_pieces.isEmpty())
m_pieces = LT::toQBitArray(m_nativeStatus.pieces);
return m_pieces; return m_pieces;
} }
@@ -1401,14 +1345,6 @@ int TorrentImpl::maxSeedingTime() const
return m_seedingTimeLimit; 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 qreal TorrentImpl::realRatio() const
{ {
const int64_t upload = m_nativeStatus.all_time_upload; const int64_t upload = m_nativeStatus.all_time_upload;
@@ -1508,8 +1444,7 @@ void TorrentImpl::forceDHTAnnounce()
void TorrentImpl::forceRecheck() void TorrentImpl::forceRecheck()
{ {
if (!hasMetadata()) if (!hasMetadata()) return;
return;
m_nativeHandle.force_recheck(); m_nativeHandle.force_recheck();
// We have to force update the cached state, otherwise someone will be able to get // We have to force update the cached state, otherwise someone will be able to get
@@ -1518,12 +1453,7 @@ void TorrentImpl::forceRecheck()
m_hasMissingFiles = false; m_hasMissingFiles = false;
m_unchecked = false; m_unchecked = false;
m_completedFiles.fill(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()) if (isPaused())
{ {
@@ -1603,29 +1533,47 @@ void TorrentImpl::applyFirstLastPiecePriority(const bool enabled)
void TorrentImpl::fileSearchFinished(const Path &savePath, const PathList &fileNames) 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() m_updatedTrackerEntries[trackerURL][endpoint] = count;
, [&announceEntry](const TrackerEntry &trackerEntry) }
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()); const auto trackerIter = std::find_if(m_trackerEntries.begin(), m_trackerEntries.end()
// TODO: use [[unlikely]] in C++20 , [&trackerURL](const TrackerEntry &trackerEntry)
if (Q_UNLIKELY(it == m_trackerEntries.end())) {
return {}; return (trackerEntry.url == trackerURL);
});
Q_ASSERT(trackerIter != m_trackerEntries.end());
TrackerEntry &trackerEntry = *trackerIter;
#ifdef QBT_USES_LIBTORRENT2 #ifdef QBT_USES_LIBTORRENT2
::updateTrackerEntry(*it, announceEntry, nativeHandle().info_hashes(), updateInfo); updateTrackerEntry(trackerEntry, announceEntry, m_nativeHandle.info_hashes(), updatedTrackerIter.value());
#else #else
::updateTrackerEntry(*it, announceEntry, updateInfo); updateTrackerEntry(trackerEntry, announceEntry, updatedTrackerIter.value());
#endif #endif
return *it; }
m_updatedTrackerEntries.clear();
} }
std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo() const
@@ -1637,13 +1585,7 @@ std::shared_ptr<const libtorrent::torrent_info> TorrentImpl::nativeTorrentInfo()
void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathList &fileNames) 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()); Q_ASSERT(m_filePaths.isEmpty());
if (Q_UNLIKELY(!m_filePaths.isEmpty()))
m_filePaths.clear();
lt::add_torrent_params &p = m_ltAddTorrentParams; lt::add_torrent_params &p = m_ltAddTorrentParams;
@@ -1652,17 +1594,15 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
m_filePriorities.reserve(filesCount()); m_filePriorities.reserve(filesCount());
const auto nativeIndexes = m_torrentInfo.nativeIndexes(); const auto nativeIndexes = m_torrentInfo.nativeIndexes();
p.file_priorities = resized(p.file_priorities, metadata->files().num_files() 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_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) for (int i = 0; i < fileNames.size(); ++i)
{ {
const auto nativeIndex = nativeIndexes.at(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(); p.renamed_files[nativeIndex] = actualFilePath.toString().toStdString();
const Path filePath = actualFilePath.removedExtension(QB_EXT); const Path filePath = actualFilePath.removedExtension(QB_EXT);
@@ -1702,13 +1642,9 @@ void TorrentImpl::endReceivedMetadataHandling(const Path &savePath, const PathLi
} }
void TorrentImpl::reload() void TorrentImpl::reload()
try
{ {
m_completedFiles.fill(false); m_completedFiles.fill(false);
m_filesProgress.fill(0); m_pieces.clear();
m_pieces.fill(false);
m_nativeStatus.pieces.clear_all();
m_nativeStatus.num_pieces = 0;
const auto queuePos = m_nativeHandle.queue_position(); const auto queuePos = m_nativeHandle.queue_position();
@@ -1745,11 +1681,6 @@ try
updateState(); 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() void TorrentImpl::pause()
{ {
@@ -1804,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()) if (!hasMetadata())
{ {
@@ -1812,9 +1743,7 @@ void TorrentImpl::moveStorage(const Path &newPath, const MoveStorageContext cont
return; return;
} }
const auto mode = (context == MoveStorageContext::AdjustCurrentLocation) if (m_session->addMoveTorrentStorageJob(this, newPath, mode))
? MoveStorageMode::Overwrite : MoveStorageMode::KeepExistingFiles;
if (m_session->addMoveTorrentStorageJob(this, newPath, mode, context))
{ {
if (!m_storageIsMoving) if (!m_storageIsMoving)
{ {
@@ -1840,17 +1769,16 @@ void TorrentImpl::handleStateUpdate(const lt::torrent_status &nativeStatus)
updateStatus(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_session->handleTorrentNeedSaveResumeData(this);
m_storageIsMoving = hasOutstandingJob;
if (actualStorageLocation() != path)
{
m_nativeStatus.save_path = path.toString().toStdString();
m_session->handleTorrentSavePathChanged(this);
}
if (!m_storageIsMoving) if (!m_storageIsMoving)
{ {
@@ -1892,9 +1820,9 @@ void TorrentImpl::handleTorrentCheckedAlert(const lt::torrent_checked_alert *p)
if (!m_hasMissingFiles) if (!m_hasMissingFiles)
{ {
if ((progress() < 1.0) && (wantedSize() > 0)) if ((progress() < 1.0) && (wantedSize() > 0))
m_hasFinishedStatus = false; m_hasSeedStatus = false;
else if (progress() == 1.0) else if (progress() == 1.0)
m_hasFinishedStatus = true; m_hasSeedStatus = true;
adjustStorageLocation(); adjustStorageLocation();
manageIncompleteFiles(); manageIncompleteFiles();
@@ -1921,7 +1849,7 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
Q_UNUSED(p); Q_UNUSED(p);
m_hasMissingFiles = false; m_hasMissingFiles = false;
if (m_hasFinishedStatus) if (m_hasSeedStatus)
return; return;
m_statusUpdatedTriggers.enqueue([this]() m_statusUpdatedTriggers.enqueue([this]()
@@ -1938,7 +1866,7 @@ void TorrentImpl::handleTorrentFinishedAlert(const lt::torrent_finished_alert *p
} }
else else
{ {
m_hasFinishedStatus = true; m_hasSeedStatus = true;
if (isMoveInProgress() || (m_renameCount > 0)) if (isMoveInProgress() || (m_renameCount > 0))
m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); }); m_moveFinishedTriggers.enqueue([this]() { m_session->handleTorrentFinished(this); });
@@ -1960,14 +1888,6 @@ void TorrentImpl::handleTorrentResumedAlert(const lt::torrent_resumed_alert *p)
void TorrentImpl::handleSaveResumeDataAlert(const lt::save_resume_data_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) if (m_maintenanceJob == MaintenanceJob::HandleMetadata)
{ {
Q_ASSERT(m_indexMap.isEmpty()); Q_ASSERT(m_indexMap.isEmpty());
@@ -2051,9 +1971,8 @@ void TorrentImpl::prepareResumeData(const lt::add_torrent_params &params)
resumeData.contentLayout = m_contentLayout; resumeData.contentLayout = m_contentLayout;
resumeData.ratioLimit = m_ratioLimit; resumeData.ratioLimit = m_ratioLimit;
resumeData.seedingTimeLimit = m_seedingTimeLimit; resumeData.seedingTimeLimit = m_seedingTimeLimit;
resumeData.inactiveSeedingTimeLimit = m_inactiveSeedingTimeLimit;
resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority; resumeData.firstLastPiecePriority = m_hasFirstLastPiecePriority;
resumeData.hasFinishedStatus = m_hasFinishedStatus; resumeData.hasSeedStatus = m_hasSeedStatus;
resumeData.stopped = m_isStopped; resumeData.stopped = m_isStopped;
resumeData.stopCondition = m_stopCondition; resumeData.stopCondition = m_stopCondition;
resumeData.operatingMode = m_operatingMode; resumeData.operatingMode = m_operatingMode;
@@ -2144,7 +2063,7 @@ void TorrentImpl::handleFileCompletedAlert(const lt::file_completed_alert *p)
const int fileIndex = m_indexMap.value(p->index, -1); const int fileIndex = m_indexMap.value(p->index, -1);
Q_ASSERT(fileIndex >= 0); Q_ASSERT(fileIndex >= 0);
m_completedFiles.setBit(fileIndex); m_completedFiles[fileIndex] = true;
if (m_session->isAppendExtensionEnabled()) if (m_session->isAppendExtensionEnabled())
{ {
@@ -2188,7 +2107,7 @@ void TorrentImpl::handleMetadataReceivedAlert(const lt::metadata_received_alert
void TorrentImpl::handlePerformanceAlert(const lt::performance_alert *p) const 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); , Log::INFO);
} }
@@ -2279,10 +2198,11 @@ void TorrentImpl::manageIncompleteFiles()
void TorrentImpl::adjustStorageLocation() void TorrentImpl::adjustStorageLocation()
{ {
const Path downloadPath = this->downloadPath(); 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()) if ((targetPath != actualStorageLocation()) || isMoveInProgress())
moveStorage(targetPath, MoveStorageContext::AdjustCurrentLocation); moveStorage(targetPath, MoveStorageMode::Overwrite);
} }
void TorrentImpl::doRenameFile(int index, const Path &path) void TorrentImpl::doRenameFile(int index, const Path &path)
@@ -2303,24 +2223,17 @@ lt::torrent_handle TorrentImpl::nativeHandle() const
return m_nativeHandle; return m_nativeHandle;
} }
void TorrentImpl::setMetadata(const TorrentInfo &torrentInfo) bool TorrentImpl::setMetadata(const TorrentInfo &torrentInfo)
{ {
if (hasMetadata()) if (hasMetadata())
return; return false;
m_session->invokeAsync([nativeHandle = m_nativeHandle, torrentInfo]
{
try
{
#ifdef QBT_USES_LIBTORRENT2 #ifdef QBT_USES_LIBTORRENT2
nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section()); return m_nativeHandle.set_metadata(torrentInfo.nativeInfo()->info_section());
#else #else
const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo(); const std::shared_ptr<lt::torrent_info> nativeInfo = torrentInfo.nativeInfo();
nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size())); return m_nativeHandle.set_metadata(lt::span<const char>(nativeInfo->metadata().get(), nativeInfo->metadata_size()));
#endif #endif
}
catch (const std::exception &) {}
});
} }
Torrent::StopCondition TorrentImpl::stopCondition() const Torrent::StopCondition TorrentImpl::stopCondition() const
@@ -2352,11 +2265,8 @@ bool TorrentImpl::isMoveInProgress() const
void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus) void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
{ {
const lt::torrent_status oldStatus = std::exchange(m_nativeStatus, nativeStatus); m_pieces.clear();
m_nativeStatus = nativeStatus;
if (m_nativeStatus.num_pieces != oldStatus.num_pieces)
updateProgress();
updateState(); updateState();
m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate m_payloadRateMonitor.addSample({nativeStatus.download_payload_rate
@@ -2376,44 +2286,6 @@ void TorrentImpl::updateStatus(const lt::torrent_status &nativeStatus)
std::invoke(m_statusUpdatedTriggers.dequeue()); 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) void TorrentImpl::setRatioLimit(qreal limit)
{ {
if (limit < USE_GLOBAL_RATIO) if (limit < USE_GLOBAL_RATIO)
@@ -2444,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) void TorrentImpl::setUploadLimit(const int limit)
{ {
const int cleanValue = cleanLimitValue(limit); const int cleanValue = cleanLimitValue(limit);
@@ -2540,39 +2397,7 @@ void TorrentImpl::flushCache() const
QString TorrentImpl::createMagnetURI() const QString TorrentImpl::createMagnetURI() const
{ {
QString ret = u"magnet:?"_s; return QString::fromStdString(lt::make_magnet_uri(m_nativeHandle));
const SHA1Hash infoHash1 = infoHash().v1();
if (infoHash1.isValid())
{
ret += u"xt=urn:btih:" + infoHash1.toString();
}
const SHA256Hash infoHash2 = infoHash().v2();
if (infoHash2.isValid())
{
if (infoHash1.isValid())
ret += u'&';
ret += u"xt=urn:btmh:1220" + infoHash2.toString();
}
const QString displayName = name();
if (displayName != id().toString())
{
ret += u"&dn=" + QString::fromLatin1(QUrl::toPercentEncoding(displayName));
}
for (const TrackerEntry &tracker : asConst(trackers()))
{
ret += u"&tr=" + QString::fromLatin1(QUrl::toPercentEncoding(tracker.url));
}
for (const QUrl &urlSeed : asConst(urlSeeds()))
{
ret += u"&ws=" + QString::fromLatin1(urlSeed.toEncoded());
}
return ret;
} }
nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const nonstd::expected<lt::entry, QString> TorrentImpl::exportTorrent() const
@@ -2628,129 +2453,6 @@ nonstd::expected<void, QString> TorrentImpl::exportToFile(const Path &path) cons
return {}; 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) void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
{ {
if (!hasMetadata()) return; if (!hasMetadata()) return;
@@ -2766,7 +2468,7 @@ void TorrentImpl::prioritizeFiles(const QVector<DownloadPriority> &priorities)
&& (priorities[i] > DownloadPriority::Ignored) && (priorities[i] > DownloadPriority::Ignored)
&& !m_completedFiles.at(i)) && !m_completedFiles.at(i))
{ {
m_hasFinishedStatus = false; m_hasSeedStatus = false;
break; break;
} }
} }
@@ -2814,19 +2516,3 @@ QVector<qreal> TorrentImpl::availableFileFractions() const
} }
return res; 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. * 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> * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
* *
* This program is free software; you can redistribute it and/or * This program is free software; you can redistribute it and/or
@@ -68,13 +68,6 @@ namespace BitTorrent
Overwrite Overwrite
}; };
enum class MoveStorageContext
{
AdjustCurrentLocation,
ChangeSavePath,
ChangeDownloadPath
};
enum class MaintenanceJob enum class MaintenanceJob
{ {
None, None,
@@ -84,13 +77,13 @@ namespace BitTorrent
struct FileErrorInfo struct FileErrorInfo
{ {
lt::error_code error; 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_DISABLE_COPY_MOVE(TorrentImpl)
Q_DECLARE_TR_FUNCTIONS(BitTorrent::TorrentImpl)
public: public:
TorrentImpl(SessionImpl *session, lt::session *nativeSession TorrentImpl(SessionImpl *session, lt::session *nativeSession
@@ -138,7 +131,6 @@ namespace BitTorrent
QDateTime addedTime() const override; QDateTime addedTime() const override;
qreal ratioLimit() const override; qreal ratioLimit() const override;
int seedingTimeLimit() const override; int seedingTimeLimit() const override;
int inactiveSeedingTimeLimit() const override;
Path filePath(int index) const override; Path filePath(int index) const override;
Path actualFilePath(int index) const override; Path actualFilePath(int index) const override;
@@ -147,7 +139,7 @@ namespace BitTorrent
QVector<DownloadPriority> filePriorities() const override; QVector<DownloadPriority> filePriorities() const override;
TorrentInfo info() const override; TorrentInfo info() const override;
bool isFinished() const override; bool isSeed() const override;
bool isPaused() const override; bool isPaused() const override;
bool isQueued() const override; bool isQueued() const override;
bool isForced() const override; bool isForced() const override;
@@ -199,7 +191,6 @@ namespace BitTorrent
qreal distributedCopies() const override; qreal distributedCopies() const override;
qreal maxRatio() const override; qreal maxRatio() const override;
int maxSeedingTime() const override; int maxSeedingTime() const override;
int maxInactiveSeedingTime() const override;
qreal realRatio() const override; qreal realRatio() const override;
int uploadPayloadRate() const override; int uploadPayloadRate() const override;
int downloadPayloadRate() const override; int downloadPayloadRate() const override;
@@ -222,7 +213,6 @@ namespace BitTorrent
void prioritizeFiles(const QVector<DownloadPriority> &priorities) override; void prioritizeFiles(const QVector<DownloadPriority> &priorities) override;
void setRatioLimit(qreal limit) override; void setRatioLimit(qreal limit) override;
void setSeedingTimeLimit(int limit) override; void setSeedingTimeLimit(int limit) override;
void setInactiveSeedingTimeLimit(int limit) override;
void setUploadLimit(int limit) override; void setUploadLimit(int limit) override;
void setDownloadLimit(int limit) override; void setDownloadLimit(int limit) override;
void setSuperSeeding(bool enable) override; void setSuperSeeding(bool enable) override;
@@ -237,7 +227,7 @@ namespace BitTorrent
void removeUrlSeeds(const QVector<QUrl> &urlSeeds) override; void removeUrlSeeds(const QVector<QUrl> &urlSeeds) override;
bool connectPeer(const PeerAddress &peerAddress) override; bool connectPeer(const PeerAddress &peerAddress) override;
void clearPeers() override; void clearPeers() override;
void setMetadata(const TorrentInfo &torrentInfo) override; bool setMetadata(const TorrentInfo &torrentInfo) override;
StopCondition stopCondition() const override; StopCondition stopCondition() const override;
void setStopCondition(StopCondition stopCondition) override; void setStopCondition(StopCondition stopCondition) override;
@@ -246,12 +236,6 @@ namespace BitTorrent
nonstd::expected<QByteArray, QString> exportToBuffer() const override; nonstd::expected<QByteArray, QString> exportToBuffer() const override;
nonstd::expected<void, QString> exportToFile(const Path &path) 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; bool needSaveResumeData() const;
// Session interface // Session interface
@@ -262,17 +246,18 @@ namespace BitTorrent
void handleCategoryOptionsChanged(); void handleCategoryOptionsChanged();
void handleAppendExtensionToggled(); void handleAppendExtensionToggled();
void saveResumeData(lt::resume_data_flags_t flags = {}); 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); 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: private:
using EventTrigger = std::function<void ()>; using EventTrigger = std::function<void ()>;
std::shared_ptr<const lt::torrent_info> nativeTorrentInfo() const; std::shared_ptr<const lt::torrent_info> nativeTorrentInfo() const;
void refreshTrackerEntries() const;
void updateStatus(const lt::torrent_status &nativeStatus); void updateStatus(const lt::torrent_status &nativeStatus);
void updateProgress();
void updateState(); void updateState();
void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p); void handleFastResumeRejectedAlert(const lt::fastresume_rejected_alert *p);
@@ -299,7 +284,7 @@ namespace BitTorrent
Path wantedActualPath(int index, const Path &path) const; Path wantedActualPath(int index, const Path &path) const;
void adjustStorageLocation(); void adjustStorageLocation();
void doRenameFile(int index, const Path &path); void doRenameFile(int index, const Path &path);
void moveStorage(const Path &newPath, MoveStorageContext context); void moveStorage(const Path &newPath, MoveStorageMode mode);
void manageIncompleteFiles(); void manageIncompleteFiles();
void applyFirstLastPiecePriority(bool enabled); void applyFirstLastPiecePriority(bool enabled);
@@ -309,9 +294,6 @@ namespace BitTorrent
nonstd::expected<lt::entry, QString> exportTorrent() const; nonstd::expected<lt::entry, QString> exportTorrent() const;
template <typename Func, typename Callback>
void invokeAsync(Func func, Callback resultHandler) const;
SessionImpl *const m_session = nullptr; SessionImpl *const m_session = nullptr;
lt::session *m_nativeSession = nullptr; lt::session *m_nativeSession = nullptr;
lt::torrent_handle m_nativeHandle; lt::torrent_handle m_nativeHandle;
@@ -336,8 +318,10 @@ namespace BitTorrent
MaintenanceJob m_maintenanceJob = MaintenanceJob::None; MaintenanceJob m_maintenanceJob = MaintenanceJob::None;
QVector<TrackerEntry> m_trackerEntries; // TODO: Use QHash<TrackerEntry::Endpoint, int> once Qt5 is dropped.
QVector<QUrl> m_urlSeeds; using TrackerEntryUpdateInfo = QMap<TrackerEntry::Endpoint, int>;
mutable QHash<QString, TrackerEntryUpdateInfo> m_updatedTrackerEntries;
mutable QVector<TrackerEntry> m_trackerEntries;
FileErrorInfo m_lastFileError; FileErrorInfo m_lastFileError;
// Persistent data // Persistent data
@@ -348,10 +332,9 @@ namespace BitTorrent
TagSet m_tags; TagSet m_tags;
qreal m_ratioLimit; qreal m_ratioLimit;
int m_seedingTimeLimit; int m_seedingTimeLimit;
int m_inactiveSeedingTimeLimit;
TorrentOperatingMode m_operatingMode; TorrentOperatingMode m_operatingMode;
TorrentContentLayout m_contentLayout; TorrentContentLayout m_contentLayout;
bool m_hasFinishedStatus; bool m_hasSeedStatus;
bool m_hasMissingFiles = false; bool m_hasMissingFiles = false;
bool m_hasFirstLastPiecePriority = false; bool m_hasFirstLastPiecePriority = false;
bool m_useAutoTMM; bool m_useAutoTMM;
@@ -365,7 +348,6 @@ namespace BitTorrent
int m_downloadLimit = 0; int m_downloadLimit = 0;
int m_uploadLimit = 0; int m_uploadLimit = 0;
QBitArray m_pieces; mutable QBitArray m_pieces;
QVector<std::int64_t> m_filesProgress;
}; };
} }

View File

@@ -42,10 +42,10 @@
#include "base/global.h" #include "base/global.h"
#include "base/path.h" #include "base/path.h"
#include "base/preferences.h"
#include "base/utils/fs.h" #include "base/utils/fs.h"
#include "base/utils/io.h" #include "base/utils/io.h"
#include "base/utils/misc.h" #include "base/utils/misc.h"
#include "common.h"
#include "infohash.h" #include "infohash.h"
#include "trackerentry.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) TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
{ {
if (this != &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 // 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are
// used in `torrent_info()` constructor // used in `torrent_info()` constructor
const auto *pref = Preferences::instance();
lt::error_code ec; lt::error_code ec;
const lt::bdecode_node node = lt::bdecode(data, ec const lt::bdecode_node node = lt::bdecode(data, ec
, nullptr, pref->getBdecodeDepthLimit(), pref->getBdecodeTokenLimit()); , nullptr, BENCODE_DEPTH_LIMIT, BENCODE_TOKEN_LIMIT);
if (ec) if (ec)
return nonstd::make_unexpected(QString::fromStdString(ec.message())); 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 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; QByteArray data;
try try
{ {
const qint64 torrentSizeLimit = Preferences::instance()->getTorrentFileSizeLimit(); data = file.readAll();
const auto readResult = Utils::IO::readFile(path, torrentSizeLimit);
if (!readResult)
return nonstd::make_unexpected(readResult.error().message);
data = readResult.value();
} }
catch (const std::bad_alloc &e) catch (const std::bad_alloc &e)
{ {
return nonstd::make_unexpected(tr("Failed to allocate memory when reading file. File: \"%1\". Error: \"%2\"") return nonstd::make_unexpected(tr("Torrent file read error: %1").arg(QString::fromLocal8Bit(e.what())));
.arg(path.toString(), 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); return load(data);
} }

View File

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

View File

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

View File

@@ -45,7 +45,7 @@ public:
Digest32() = default; Digest32() = default;
Digest32(const Digest32 &other) = default; Digest32(const Digest32 &other) = default;
Digest32(Digest32 &&other) noexcept = default; Digest32(Digest32 &&other) = default;
Digest32(const UnderlyingType &nativeDigest) Digest32(const UnderlyingType &nativeDigest)
: m_dataPtr {new Data(nativeDigest)} : m_dataPtr {new Data(nativeDigest)}
@@ -63,7 +63,7 @@ public:
} }
Digest32 &operator=(const Digest32 &other) = default; Digest32 &operator=(const Digest32 &other) = default;
Digest32 &operator=(Digest32 &&other) noexcept = default; Digest32 &operator=(Digest32 &&other) = default;
operator UnderlyingType() const operator UnderlyingType() const
{ {
@@ -84,7 +84,7 @@ private:
class Data; class Data;
explicit Digest32(QSharedDataPointer<Data> dataPtr) 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> template <int N>
uint qHash(const Digest32<N> &key, const uint seed = 0) 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 #endif

View File

@@ -1,6 +1,6 @@
/* /*
* Bittorrent Client using Qt and libtorrent. * 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 * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License * 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 // Forward rvalue as const
template <typename T> 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 // Prevent const rvalue arguments
template <typename T> template <typename T>
void asConst(const T &&) = delete; 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)) #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)); 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 #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 <QTcpSocket>
#include "base/logger.h"
#include "irequesthandler.h" #include "irequesthandler.h"
#include "requestparser.h" #include "requestparser.h"
#include "responsegenerator.h" #include "responsegenerator.h"
@@ -45,10 +46,6 @@ Connection::Connection(QTcpSocket *socket, IRequestHandler *requestHandler, QObj
{ {
m_socket->setParent(this); 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 // reset timer when there are activity
m_idleTimer.start(); m_idleTimer.start();
connect(m_socket, &QIODevice::readyRead, this, [this]() connect(m_socket, &QIODevice::readyRead, this, [this]()
@@ -69,18 +66,7 @@ Connection::~Connection()
void Connection::read() void Connection::read()
{ {
// reuse existing buffer and avoid unnecessary memory allocation/relocation m_receivedData.append(m_socket->readAll());
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);
while (!m_receivedData.isEmpty()) while (!m_receivedData.isEmpty())
{ {
@@ -93,11 +79,11 @@ void Connection::read()
const long bufferLimit = RequestParser::MAX_CONTENT_SIZE * 1.1; // some margin for headers const long bufferLimit = RequestParser::MAX_CONTENT_SIZE * 1.1; // some margin for headers
if (m_receivedData.size() > bufferLimit) if (m_receivedData.size() > bufferLimit)
{ {
qWarning("%s", qUtf8Printable(tr("Http request size exceeds limitation, closing socket. Limit: %1, IP: %2") LogMsg(tr("Http request size exceeds limitation, closing socket. Limit: %1, IP: %2")
.arg(QString::number(bufferLimit), m_socket->peerAddress().toString()))); .arg(bufferLimit).arg(m_socket->peerAddress().toString()), Log::WARNING);
Response resp(413, u"Payload Too Large"_s); Response resp(413, u"Payload Too Large"_qs);
resp.headers[HEADER_CONNECTION] = u"close"_s; resp.headers[HEADER_CONNECTION] = u"close"_qs;
sendResponse(resp); sendResponse(resp);
m_socket->close(); m_socket->close();
@@ -105,26 +91,13 @@ void Connection::read()
} }
return; 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: case RequestParser::ParseStatus::BadRequest:
{ {
qWarning("%s", qUtf8Printable(tr("Bad Http request, closing socket. IP: %1") LogMsg(tr("Bad Http request, closing socket. IP: %1")
.arg(m_socket->peerAddress().toString()))); .arg(m_socket->peerAddress().toString()), Log::WARNING);
Response resp(400, u"Bad Request"_s); Response resp(400, u"Bad Request"_qs);
resp.headers[HEADER_CONNECTION] = u"close"_s; resp.headers[HEADER_CONNECTION] = u"close"_qs;
sendResponse(resp); sendResponse(resp);
m_socket->close(); 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()}; const Environment env {m_socket->localAddress(), m_socket->localPort(), m_socket->peerAddress(), m_socket->peerPort()};
if (result.request.method == HEADER_REQUEST_METHOD_HEAD) Response resp = m_requestHandler->processRequest(result.request, env);
{
Request getRequest = result.request;
getRequest.method = HEADER_REQUEST_METHOD_GET;
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_CONNECTION] = u"keep-alive"_qs;
resp.headers[HEADER_CONTENT_LENGTH] = QString::number(resp.content.length());
resp.content.clear();
sendResponse(resp); sendResponse(resp);
} m_receivedData = m_receivedData.mid(result.frameSize);
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);
} }
break; break;
@@ -219,11 +176,11 @@ bool Connection::acceptsGzipEncoding(QString codings)
if (list.isEmpty()) if (list.isEmpty())
return false; return false;
const bool canGzip = isCodingAvailable(list, u"gzip"_s); const bool canGzip = isCodingAvailable(list, u"gzip"_qs);
if (canGzip) if (canGzip)
return true; return true;
const bool canAny = isCodingAvailable(list, u"*"_s); const bool canAny = isCodingAvailable(list, u"*"_qs);
if (canAny) if (canAny)
return true; return true;

View File

@@ -48,41 +48,41 @@ QString HTTPError::statusText() const
} }
BadRequestHTTPError::BadRequestHTTPError(const QString &message) BadRequestHTTPError::BadRequestHTTPError(const QString &message)
: HTTPError(400, u"Bad Request"_s, message) : HTTPError(400, u"Bad Request"_qs, message)
{ {
} }
UnauthorizedHTTPError::UnauthorizedHTTPError(const QString &message) UnauthorizedHTTPError::UnauthorizedHTTPError(const QString &message)
: HTTPError(401, u"Unauthorized"_s, message) : HTTPError(401, u"Unauthorized"_qs, message)
{ {
} }
ForbiddenHTTPError::ForbiddenHTTPError(const QString &message) ForbiddenHTTPError::ForbiddenHTTPError(const QString &message)
: HTTPError(403, u"Forbidden"_s, message) : HTTPError(403, u"Forbidden"_qs, message)
{ {
} }
NotFoundHTTPError::NotFoundHTTPError(const QString &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) 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) ConflictHTTPError::ConflictHTTPError(const QString &message)
: HTTPError(409, u"Conflict"_s, message) : HTTPError(409, u"Conflict"_qs, message)
{ {
} }
UnsupportedMediaTypeHTTPError::UnsupportedMediaTypeHTTPError(const QString &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) 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 class IRequestHandler
{ {
public: public:
virtual ~IRequestHandler() = default; virtual ~IRequestHandler() {}
virtual Response processRequest(const Request &request, const Environment &env) = 0; 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) RequestParser::ParseResult RequestParser::parse(const QByteArray &data)
{ {
// Warning! Header names are converted to lowercase // Warning! Header names are converted to lowercase
@@ -103,7 +107,6 @@ RequestParser::ParseResult RequestParser::doParse(const QByteArray &data)
// handle supported methods // handle supported methods
if ((m_request.method == HEADER_REQUEST_METHOD_GET) || (m_request.method == HEADER_REQUEST_METHOD_HEAD)) if ((m_request.method == HEADER_REQUEST_METHOD_GET) || (m_request.method == HEADER_REQUEST_METHOD_HEAD))
return {ParseStatus::OK, m_request, headerLength}; return {ParseStatus::OK, m_request, headerLength};
if (m_request.method == HEADER_REQUEST_METHOD_POST) if (m_request.method == HEADER_REQUEST_METHOD_POST)
{ {
const auto parseContentLength = [this]() -> int 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::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) bool RequestParser::parseStartLines(const QStringView data)
@@ -189,7 +193,7 @@ bool RequestParser::parseRequestLine(const QString &line)
{ {
// [rfc7230] 3.1.1. Request 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); const QRegularExpressionMatch match = re.match(line);
if (!match.hasMatch()) if (!match.hasMatch())
@@ -264,7 +268,7 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
// [rfc2046] 5.1.1. Common Syntax // [rfc2046] 5.1.1. Common Syntax
// find boundary delimiter // find boundary delimiter
const QString boundaryFieldName = u"boundary="_s; const QString boundaryFieldName = u"boundary="_qs;
const int idx = contentType.indexOf(boundaryFieldName); const int idx = contentType.indexOf(boundaryFieldName);
if (idx < 0) if (idx < 0)
{ {
@@ -343,8 +347,8 @@ bool RequestParser::parseFormData(const QByteArray &data)
} }
// pick data // pick data
const QString filename = u"filename"_s; const QString filename = u"filename"_qs;
const QString name = u"name"_s; const QString name = u"name"_qs;
if (headersMap.contains(filename)) if (headersMap.contains(filename))
{ {

View File

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

View File

@@ -38,7 +38,7 @@ namespace Http
class ResponseBuilder class ResponseBuilder
{ {
public: 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 setHeader(const Header &header);
void print(const QString &text, const QString &type = CONTENT_TYPE_HTML); void print(const QString &text, const QString &type = CONTENT_TYPE_HTML);
void print(const QByteArray &data, 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); compressContent(response);
response.headers[HEADER_CONTENT_LENGTH] = QString::number(response.content.length());
response.headers[HEADER_DATE] = httpDate(); response.headers[HEADER_DATE] = httpDate();
if (QString &value = response.headers[HEADER_CONTENT_LENGTH]; value.isEmpty())
value = QString::number(response.content.length());
QByteArray buf; QByteArray buf;
buf.reserve(1024 + response.content.length()); buf.reserve(1024 + response.content.length());
@@ -64,7 +63,7 @@ QByteArray Http::toByteArray(Response response)
// the first empty line // the first empty line
buf += CRLF; buf += CRLF;
// message body // message body // TODO: support HEAD request
buf += response.content; buf += response.content;
return buf; return buf;
@@ -75,7 +74,7 @@ QString Http::httpDate()
// [RFC 7231] 7.1.1.1. Date/Time Formats // [RFC 7231] 7.1.1.1. Date/Time Formats
// example: "Sun, 06 Nov 1994 08:49:37 GMT" // 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"); .append(u" GMT");
} }
@@ -107,5 +106,5 @@ void Http::compressContent(Response &response)
return; return;
response.content = compressedData; 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() 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 // Contains Ciphersuites that use RSA for the Key Exchange but they don't mention it in their name
const QStringList badRSAShorthandSuites { const QStringList badRSAShorthandSuites {
u"AES256-GCM-SHA384"_s, u"AES128-GCM-SHA256"_s, u"AES256-SHA256"_s, u"AES256-GCM-SHA384"_qs, u"AES128-GCM-SHA256"_qs, u"AES256-SHA256"_qs,
u"AES128-SHA256"_s, u"AES256-SHA"_s, u"AES128-SHA"_s}; 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 // Contains Ciphersuites that use AES CBC mode but they don't mention it in their name
const QStringList badAESShorthandSuites { const QStringList badAESShorthandSuites {
u"ECDHE-ECDSA-AES256-SHA384"_s, u"ECDHE-RSA-AES256-SHA384"_s, u"DHE-RSA-AES256-SHA256"_s, u"ECDHE-ECDSA-AES256-SHA384"_qs, u"ECDHE-RSA-AES256-SHA384"_qs, u"DHE-RSA-AES256-SHA256"_qs,
u"ECDHE-ECDSA-AES128-SHA256"_s, u"ECDHE-RSA-AES128-SHA256"_s, u"DHE-RSA-AES128-SHA256"_s, u"ECDHE-ECDSA-AES128-SHA256"_qs, u"ECDHE-RSA-AES128-SHA256"_qs, u"DHE-RSA-AES128-SHA256"_qs,
u"ECDHE-ECDSA-AES256-SHA"_s, u"ECDHE-RSA-AES256-SHA"_s, u"DHE-RSA-AES256-SHA"_s, u"ECDHE-ECDSA-AES256-SHA"_qs, u"ECDHE-RSA-AES256-SHA"_qs, u"DHE-RSA-AES256-SHA"_qs,
u"ECDHE-ECDSA-AES128-SHA"_s, u"ECDHE-RSA-AES128-SHA"_s, u"DHE-RSA-AES128-SHA"_s}; u"ECDHE-ECDSA-AES128-SHA"_qs, u"ECDHE-RSA-AES128-SHA"_qs, u"DHE-RSA-AES128-SHA"_qs};
const QList<QSslCipher> allCiphers {QSslConfiguration::supportedCiphers()}; const QList<QSslCipher> allCiphers {QSslConfiguration::supportedCiphers()};
QList<QSslCipher> safeCiphers; QList<QSslCipher> safeCiphers;
std::copy_if(allCiphers.cbegin(), allCiphers.cend(), std::back_inserter(safeCiphers), std::copy_if(allCiphers.cbegin(), allCiphers.cend(), std::back_inserter(safeCiphers),
[&badCiphers, &badRSAShorthandSuites, &badAESShorthandSuites](const QSslCipher &cipher) [&badCiphers, &badRSAShorthandSuites, &badAESShorthandSuites](const QSslCipher &cipher)
{ {
const QString name = cipher.name(); const QString name = cipher.name();
if (name.contains(u"-cbc-"_s, Qt::CaseInsensitive) // AES CBC mode is considered vulnerable to BEAST attack if (name.contains(u"-cbc-"_qs, 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"adh-"_qs, 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"aecdh-"_qs, 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"psk-"_qs, 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 || name.startsWith(u"rsa-"_qs, Qt::CaseInsensitive) // Key Exchange: Rivest Shamir Adleman (RSA), doesn't support Perfect Forward Secrecy
|| badRSAShorthandSuites.contains(name, Qt::CaseInsensitive) || badRSAShorthandSuites.contains(name, Qt::CaseInsensitive)
|| badAESShorthandSuites.contains(name, Qt::CaseInsensitive)) || badAESShorthandSuites.contains(name, Qt::CaseInsensitive))
{ {

View File

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

View File

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

View File

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

View File

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

View File

@@ -44,6 +44,8 @@ const std::chrono::seconds IP_CHECK_INTERVAL = 30min;
DNSUpdater::DNSUpdater(QObject *parent) DNSUpdater::DNSUpdater(QObject *parent)
: QObject(parent) : QObject(parent)
, m_state(OK)
, m_service(DNS::Service::None)
{ {
updateCredentials(); updateCredentials();
@@ -78,8 +80,8 @@ void DNSUpdater::checkPublicIP()
Q_ASSERT(m_state == OK); Q_ASSERT(m_state == OK);
DownloadManager::instance()->download( DownloadManager::instance()->download(
DownloadRequest(u"http://checkip.dyndns.org"_s).userAgent(QStringLiteral("qBittorrent/" QBT_VERSION_2)) DownloadRequest(u"http://checkip.dyndns.org"_qs).userAgent(QStringLiteral("qBittorrent/" QBT_VERSION_2))
, Preferences::instance()->useProxyForGeneralPurposes(), this, &DNSUpdater::ipRequestFinished); , this, &DNSUpdater::ipRequestFinished);
m_lastIPCheckTime = QDateTime::currentDateTime(); m_lastIPCheckTime = QDateTime::currentDateTime();
} }
@@ -93,7 +95,7 @@ void DNSUpdater::ipRequestFinished(const DownloadResult &result)
} }
// Parse response // Parse response
const QRegularExpressionMatch ipRegexMatch = QRegularExpression(u"Current IP Address:\\s+([^<]+)</body>"_s).match(QString::fromUtf8(result.data)); const QRegularExpressionMatch ipRegexMatch = QRegularExpression(u"Current IP Address:\\s+([^<]+)</body>"_qs).match(QString::fromUtf8(result.data));
if (ipRegexMatch.hasMatch()) if (ipRegexMatch.hasMatch())
{ {
QString ipStr = ipRegexMatch.captured(1); QString ipStr = ipRegexMatch.captured(1);
@@ -126,17 +128,17 @@ void DNSUpdater::updateDNSService()
m_lastIPCheckTime = QDateTime::currentDateTime(); m_lastIPCheckTime = QDateTime::currentDateTime();
DownloadManager::instance()->download( DownloadManager::instance()->download(
DownloadRequest(getUpdateUrl()).userAgent(QStringLiteral("qBittorrent/" QBT_VERSION_2)) DownloadRequest(getUpdateUrl()).userAgent(QStringLiteral("qBittorrent/" QBT_VERSION_2))
, Preferences::instance()->useProxyForGeneralPurposes(), this, &DNSUpdater::ipUpdateFinished); , this, &DNSUpdater::ipUpdateFinished);
} }
QString DNSUpdater::getUpdateUrl() const QString DNSUpdater::getUpdateUrl() const
{ {
QUrl url; QUrl url;
#ifdef QT_NO_OPENSSL #ifdef QT_NO_OPENSSL
url.setScheme(u"http"_s); url.setScheme(u"http"_qs);
#else #else
url.setScheme(u"https"_s); url.setScheme(u"https"_qs);
#endif #endif
url.setUserName(m_username); url.setUserName(m_username);
url.setPassword(m_password); url.setPassword(m_password);
@@ -146,21 +148,21 @@ QString DNSUpdater::getUpdateUrl() const
switch (m_service) switch (m_service)
{ {
case DNS::Service::DynDNS: case DNS::Service::DynDNS:
url.setHost(u"members.dyndns.org"_s); url.setHost(u"members.dyndns.org"_qs);
break; break;
case DNS::Service::NoIP: case DNS::Service::NoIP:
url.setHost(u"dynupdate.no-ip.com"_s); url.setHost(u"dynupdate.no-ip.com"_qs);
break; break;
default: default:
qWarning() << "Unrecognized Dynamic DNS service!"; qWarning() << "Unrecognized Dynamic DNS service!";
Q_ASSERT(false); Q_ASSERT(false);
break; break;
} }
url.setPath(u"/nic/update"_s); url.setPath(u"/nic/update"_qs);
QUrlQuery urlQuery(url); QUrlQuery urlQuery(url);
urlQuery.addQueryItem(u"hostname"_s, m_domain); urlQuery.addQueryItem(u"hostname"_qs, m_domain);
urlQuery.addQueryItem(u"myip"_s, m_lastIP.toString()); urlQuery.addQueryItem(u"myip"_qs, m_lastIP.toString());
url.setQuery(urlQuery); url.setQuery(urlQuery);
Q_ASSERT(url.isValid()); Q_ASSERT(url.isValid());
@@ -215,7 +217,7 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
if (code == u"badagent") if (code == u"badagent")
{ {
LogMsg(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please submit a bug report at https://bugs.qbittorrent.org."), LogMsg(tr("Dynamic DNS error: qBittorrent was blacklisted by the service, please submit a bug report at http://bugs.qbittorrent.org."),
Log::CRITICAL); Log::CRITICAL);
m_state = FATAL; m_state = FATAL;
return; return;
@@ -223,7 +225,7 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
if (code == u"!donator") if (code == u"!donator")
{ {
LogMsg(tr("Dynamic DNS error: %1 was returned by the service, please submit a bug report at https://bugs.qbittorrent.org.").arg(u"!donator"_s), LogMsg(tr("Dynamic DNS error: %1 was returned by the service, please submit a bug report at http://bugs.qbittorrent.org.").arg(u"!donator"_qs),
Log::CRITICAL); Log::CRITICAL);
m_state = FATAL; m_state = FATAL;
return; return;
@@ -250,7 +252,7 @@ void DNSUpdater::updateCredentials()
if (m_domain != pref->getDynDomainName()) if (m_domain != pref->getDynDomainName())
{ {
m_domain = pref->getDynDomainName(); m_domain = pref->getDynDomainName();
const QRegularExpressionMatch domainRegexMatch = QRegularExpression(u"^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$"_s).match(m_domain); const QRegularExpressionMatch domainRegexMatch = QRegularExpression(u"^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$"_qs).match(m_domain);
if (!domainRegexMatch.hasMatch()) if (!domainRegexMatch.hasMatch())
{ {
LogMsg(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL); LogMsg(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL);
@@ -301,9 +303,9 @@ QUrl DNSUpdater::getRegistrationUrl(const DNS::Service service)
switch (service) switch (service)
{ {
case DNS::Service::DynDNS: case DNS::Service::DynDNS:
return {u"https://account.dyn.com/entrance/"_s}; return {u"https://account.dyn.com/entrance/"_qs};
case DNS::Service::NoIP: case DNS::Service::NoIP:
return {u"https://www.noip.com/remote-access"_s}; return {u"https://www.noip.com/remote-access"_qs};
default: default:
Q_ASSERT(false); Q_ASSERT(false);
break; break;

View File

@@ -74,9 +74,9 @@ namespace Net
QHostAddress m_lastIP; QHostAddress m_lastIP;
QDateTime m_lastIPCheckTime; QDateTime m_lastIPCheckTime;
QTimer m_ipCheckTimer; QTimer m_ipCheckTimer;
int m_state = OK; int m_state;
// Service creds // Service creds
DNS::Service m_service = DNS::Service::None; DNS::Service m_service;
QString m_domain; QString m_domain;
QString m_username; QString m_username;
QString m_password; QString m_password;

View File

@@ -56,18 +56,16 @@ namespace
} }
} }
Net::DownloadHandlerImpl::DownloadHandlerImpl(DownloadManager *manager DownloadHandlerImpl::DownloadHandlerImpl(Net::DownloadManager *manager, const Net::DownloadRequest &downloadRequest)
, const DownloadRequest &downloadRequest, const bool useProxy)
: DownloadHandler {manager} : DownloadHandler {manager}
, m_manager {manager} , m_manager {manager}
, m_downloadRequest {downloadRequest} , m_downloadRequest {downloadRequest}
, m_useProxy {useProxy}
{ {
m_result.url = url(); m_result.url = url();
m_result.status = DownloadStatus::Success; m_result.status = Net::DownloadStatus::Success;
} }
void Net::DownloadHandlerImpl::cancel() void DownloadHandlerImpl::cancel()
{ {
if (m_reply) if (m_reply)
{ {
@@ -80,7 +78,7 @@ void Net::DownloadHandlerImpl::cancel()
} }
} }
void Net::DownloadHandlerImpl::assignNetworkReply(QNetworkReply *reply) void DownloadHandlerImpl::assignNetworkReply(QNetworkReply *reply)
{ {
Q_ASSERT(reply); Q_ASSERT(reply);
Q_ASSERT(!m_reply); Q_ASSERT(!m_reply);
@@ -93,22 +91,17 @@ void Net::DownloadHandlerImpl::assignNetworkReply(QNetworkReply *reply)
} }
// Returns original url // Returns original url
QString Net::DownloadHandlerImpl::url() const QString DownloadHandlerImpl::url() const
{ {
return m_downloadRequest.url(); return m_downloadRequest.url();
} }
Net::DownloadRequest Net::DownloadHandlerImpl::downloadRequest() const const Net::DownloadRequest DownloadHandlerImpl::downloadRequest() const
{ {
return m_downloadRequest; return m_downloadRequest;
} }
bool Net::DownloadHandlerImpl::useProxy() const void DownloadHandlerImpl::processFinishedDownload()
{
return m_useProxy;
}
void Net::DownloadHandlerImpl::processFinishedDownload()
{ {
qDebug("Download finished: %s", qUtf8Printable(url())); qDebug("Download finished: %s", qUtf8Printable(url()));
@@ -163,7 +156,7 @@ void Net::DownloadHandlerImpl::processFinishedDownload()
finish(); finish();
} }
void Net::DownloadHandlerImpl::checkDownloadSize(const qint64 bytesReceived, const qint64 bytesTotal) void DownloadHandlerImpl::checkDownloadSize(const qint64 bytesReceived, const qint64 bytesTotal)
{ {
if ((bytesTotal > 0) && (bytesTotal <= m_downloadRequest.limit())) if ((bytesTotal > 0) && (bytesTotal <= m_downloadRequest.limit()))
{ {
@@ -182,7 +175,7 @@ void Net::DownloadHandlerImpl::checkDownloadSize(const qint64 bytesReceived, con
} }
} }
void Net::DownloadHandlerImpl::handleRedirection(const QUrl &newUrl) void DownloadHandlerImpl::handleRedirection(const QUrl &newUrl)
{ {
if (m_redirectionCount >= MAX_REDIRECTIONS) if (m_redirectionCount >= MAX_REDIRECTIONS)
{ {
@@ -208,10 +201,10 @@ void Net::DownloadHandlerImpl::handleRedirection(const QUrl &newUrl)
return; return;
} }
auto *redirected = static_cast<DownloadHandlerImpl *>( auto redirected = static_cast<DownloadHandlerImpl *>(
m_manager->download(DownloadRequest(m_downloadRequest).url(newUrlString), useProxy())); m_manager->download(Net::DownloadRequest(m_downloadRequest).url(newUrlString)));
redirected->m_redirectionCount = m_redirectionCount + 1; redirected->m_redirectionCount = m_redirectionCount + 1;
connect(redirected, &DownloadHandlerImpl::finished, this, [this](const DownloadResult &result) connect(redirected, &DownloadHandlerImpl::finished, this, [this](const Net::DownloadResult &result)
{ {
m_result = result; m_result = result;
m_result.url = url(); m_result.url = url();
@@ -219,18 +212,18 @@ void Net::DownloadHandlerImpl::handleRedirection(const QUrl &newUrl)
}); });
} }
void Net::DownloadHandlerImpl::setError(const QString &error) void DownloadHandlerImpl::setError(const QString &error)
{ {
m_result.errorString = error; m_result.errorString = error;
m_result.status = DownloadStatus::Failed; m_result.status = Net::DownloadStatus::Failed;
} }
void Net::DownloadHandlerImpl::finish() void DownloadHandlerImpl::finish()
{ {
emit finished(m_result); emit finished(m_result);
} }
QString Net::DownloadHandlerImpl::errorCodeToString(const QNetworkReply::NetworkError status) QString DownloadHandlerImpl::errorCodeToString(const QNetworkReply::NetworkError status)
{ {
switch (status) switch (status)
{ {

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