Compare commits

..

288 Commits

Author SHA1 Message Date
sledgehammer999
05a82afeb6 Bump to 4.1.7 2019-08-04 14:17:20 +03:00
sledgehammer999
a456f1b0f9 Update Changelog 2019-08-04 14:14:21 +03:00
sledgehammer999
4acc44a5b0 Sync translations from Transifex and run lupdate/tstool.py 2019-08-04 13:50:34 +03:00
Mike Tzou
9c2a1146df Merge pull request #10991 from Chocobo1/backport
Backport to v4_1_x
2019-07-31 14:29:14 +08:00
Chocobo1
807abeae87 Fix messed up symbols in log 2019-07-31 14:28:05 +08:00
Chocobo1
dd2a0d0484 Fix incomplete file extension not applied for new torrents 2019-07-29 11:20:36 +08:00
Vladimir Golovnev
3f3400f43b Merge pull request #10967 from glassez/fastresume41
Save updated resume data for completed torrents (backport to v4.1.x)
2019-07-24 17:17:50 +03:00
Vladimir Golovnev (Glassez)
334b57a89a Save updated resume data for completed torrents
If fastresume data was rejected we need to save updated
resume data after torrent finishes rechecking.
2019-07-23 20:15:41 +03:00
Vladimir Golovnev (Glassez)
00d6c83ee5 Fix requested torrent resume data handling
Session should increase an appropriate counter each time
the torrent resume data is requested to save.
2019-07-23 20:15:23 +03:00
Mike Tzou
e8850c7a70 Merge pull request #10953 from Chocobo1/backport
Backport to v4_1_x
2019-07-21 21:19:05 +08:00
Chocobo1
2ef96eb218 Prevent command injection via "Run external program" function 2019-07-21 12:07:32 +08:00
Vladimir Golovnev
4682e31ab7 Merge pull request #10929 from glassez/backport
Fix torrent checking issues (backport to v4.1.x)
2019-07-17 17:50:35 +03:00
Vladimir Golovnev (Glassez)
988f7e2ef8 Don't break torrent checking 2019-07-15 20:02:08 +03:00
Vladimir Golovnev (Glassez)
6007913291 Ignore some actions on uninitialized torrents
Some actions can lead to an inconsistent state if applied
to an uninitialized torrent, so we just ignore them.
2019-07-15 20:02:04 +03:00
Vladimir Golovnev (Glassez)
cdcc7a210b Avoid race conditions when adding torrent 2019-07-15 20:01:59 +03:00
Vladimir Golovnev (Glassez)
a466ff5057 Fix torrent checking issues
Start all torrents auto-managed to prevent simultaneous checking
of multiple torrents.
Handle checking state of paused torrent to prevent it from being
resumed when qBittorrent is closed until checking isn't complete.
2019-07-15 20:01:43 +03:00
Mike Tzou
e954835579 Merge pull request #10869 from Chocobo1/backport
Backport to v4_1_x
2019-07-06 12:17:33 +08:00
Chocobo1
3e9be3a0e8 Use proper log message when there are no error 2019-07-06 12:16:43 +08:00
Chocobo1
4ab32a76f6 Fix torrent properties not saved for paused torrents 2019-07-06 12:16:23 +08:00
Chocobo1
bad60058df Restrict QLocalServer access
The default is world access which means even even unprivileged local
accounts can connect to it too.
2019-06-29 15:30:09 +08:00
Chocobo1
31a6ad1eb6 Drop suspiciously large data
This is to avoid exhausting system memory.
2019-06-29 15:30:04 +08:00
Mike Tzou
a8bfec081e Merge pull request #10848 from Chocobo1/backport
Backport to v4_1_x
2019-06-25 09:01:25 +08:00
Chocobo1
ae21d0f1e2 Remove limits of "Disk cache expiry interval" setting 2019-06-24 11:14:21 +08:00
Chocobo1
c599976b6f Remove upper limit of "Disk cache" setting 2019-06-24 11:13:50 +08:00
Mike Tzou
bcee784097 Merge pull request #10842 from Piccirello/v4_1_x
Backport to v4_1_x
2019-06-24 11:00:12 +08:00
Thomas Piccirello
697fc626cd Fix WebUI encoding of special characters 2019-06-22 15:52:19 -07:00
Mike Tzou
2f15ea9b54 Merge pull request #10829 from Chocobo1/backport
Backport to v4_1_x
2019-06-21 11:02:54 +08:00
Chocobo1
d03209a73d Fix crash when removing phantom tags
Normally a tag is stored in both session and torrent's fastresume.
A phantom tag is a tag that is stored in fastresume but not in
session.
This crash can occur when user resets his config file and choose
to remove tag from torrent.

Closes #10569.
2019-06-20 11:38:15 +08:00
Chocobo1
ac9ba255d8 Set wheel event to accepted only if we handle it 2019-06-20 11:38:02 +08:00
Chocobo1
9a7e79bd0e Improve handleFileErrorAlert error message 2019-06-20 11:37:40 +08:00
Mike Tzou
e8be3bf939 Merge pull request #10772 from Chocobo1/backport
Backport to v4_1_x
2019-06-17 01:00:35 +08:00
Chocobo1
74e52746b1 Reorder if conditions slightly 2019-06-16 12:37:16 +08:00
Chocobo1
8d26a221e0 Fix updated save path not saved for paused torrents 2019-06-16 12:37:16 +08:00
Chocobo1
3fdab88eb7 Log save_resume_data_failed_alert 2019-06-16 12:37:16 +08:00
Chocobo1
d376d912b3 Don't remove parent directories
QDir::rmpath removes *all* parent directories while QDir::rmdir removes
the specified directory.
2019-06-16 12:30:57 +08:00
Chocobo1
e329c41ef2 Properly remove empty leftover folders after rename
TorrentInfo::origFilePath will return the very original path from
.torrent file, not the most recent file path before the rename operation
and thus the code would not be working as we expected.
2019-06-16 12:30:42 +08:00
silverqx
01e4179555 Focus behavior row in Options dialog 2019-06-15 11:50:28 +08:00
Chocobo1
06f503b5df Fix unable to rename folder on Windows
The bug occurs when the new path and old path only differ by letter case.
2019-06-10 10:34:15 +08:00
Chocobo1
e2f3dad7b8 Move renameSelectedFile(BitTorrent::TorrentInfo &) 2019-06-10 10:34:10 +08:00
Chocobo1
377f31085c Move renameSelectedFile(BitTorrent::TorrentHandle *) 2019-06-10 10:34:03 +08:00
Chocobo1
ec13d195f8 Fix unable to control add torrent dialogs when opened simultaneously 2019-06-06 23:31:27 +08:00
Chocobo1
c01aed8d90 Remove redundant disconnect()
The dialog is going out-of-scope in these instance and the signal-slot connection will disconnect
automatically.
2019-06-06 23:29:53 +08:00
Vladimir Golovnev
ad7b8a9bfa Merge pull request #10757 from glassez/backport
Backport to v4.1.x
2019-06-04 17:59:10 +03:00
Vladimir Golovnev (Glassez)
5bba1ed208 Ignore RSS articles with non-unique identifiers 2019-06-04 15:08:35 +03:00
Vladimir Golovnev (Glassez)
fe94e14bcc Perform more RSS parsing in working thread 2019-06-04 15:06:29 +03:00
Vladimir Golovnev (Glassez)
b0af479ab9 Disable "Upload mode" when start preloaded torrent 2019-06-04 15:01:10 +03:00
Mike Tzou
24ff369f29 Merge pull request #10731 from Chocobo1/backport
Backport to v4_1_x
2019-06-01 10:07:23 +08:00
Vladimir Golovnev
979c9a7094 Merge pull request #10728 from glassez/fix-seqdl
Fix sequential downloading when redirected
2019-05-31 08:13:50 +03:00
Chocobo1
7b90ac52c1 Fix wrong comparison result
The QString::toInt() might overflow when the string is long.

Closes #10706.
2019-05-31 12:20:39 +08:00
Vladimir Golovnev (Glassez)
ecfbda78bc Fix sequential downloading when redirected 2019-05-30 16:06:18 +03:00
Mike Tzou
9ba7470815 Merge pull request #10615 from Chocobo1/backport
Backport to v4_1_x
2019-05-23 22:14:18 +08:00
Chocobo1
6394467cc7 Fix typos 2019-05-20 17:45:48 +08:00
Chocobo1
f6d72fa79f Fix assertion fail
When fileSize == 0, the second index could be smaller than the first index,
thus trigger the assert check in IndexInterval constructor.

Closes #10611.
2019-05-16 10:49:00 +08:00
dzmat
32ed5f1c8e Change number of time axis divisions from 5 to 6 for convenience 2019-05-15 20:36:00 +08:00
dzmat
5026da5773 Add 12 hour and 24 hour speed graphs 2019-05-15 20:35:50 +08:00
Evgeny Lensky
ef130e4438 Change "Add new torrent" dialog to horizontal layout 2019-05-15 10:19:37 +08:00
jerrymakesjelly
4fbd52c2d5 Change the speed unit from Bytes/s to KiB/s
Updated the adding torrent dialogs in WebUI. Closes #10017.
2019-05-12 13:51:35 +08:00
Matan Bareket
8f29b70c1e Download RSS enclosure element if no proper MIME type is found
In the case where an RSS feed doesn't have the "enclosure" element
with type "application/x-bittorrent", fallback to the last enclosure
element which has no "type" attribute.
2019-05-11 23:39:57 +08:00
Ekin Dursun
9a4dd3ea9d Don't turn screen blank when closed to system tray
Closes #9240 and possibly #9121, as @zeule suggested here: https://github.com/qbittorrent/qBittorrent/issues/9240#issuecomment-408025722.
2019-05-11 12:25:42 +08:00
Chocobo1
fcd3bb6918 Refactor HTTP query parsing 2019-05-10 09:20:03 +08:00
Chocobo1
9f69fd8750 Fix '+' char not decoded to space correctly
Closes #10606.
2019-05-10 09:19:56 +08:00
sledgehammer999
ea7e47d113 Bump to 4.1.6 2019-05-05 20:44:39 +03:00
sledgehammer999
294bb26996 Update Changelog 2019-05-05 20:40:50 +03:00
sledgehammer999
4b2e9dba51 Sync translations from Transifex and run lupdate/tstool.py 2019-05-05 20:20:06 +03:00
sledgehammer999
1d9dcde99b Merge pull request #10567 from Chocobo1/backport
Backport #10445 to v4_1_x
2019-05-05 19:52:14 +03:00
Chocobo1
32bf448725 Initialize class variable via constructor 2019-05-03 23:03:33 +08:00
Chocobo1
732d5d6db9 Disable downloading tracker favicons by default
Workaround for a crash in Qt networking library, source:
https://github.com/qbittorrent/qBittorrent/issues/9667#issuecomment-464445025

Closes #9667.
2019-05-03 22:56:16 +08:00
Mike Tzou
3b325106da Merge pull request #10565 from Chocobo1/backport
Backport to v4_1_x
2019-05-03 19:58:31 +08:00
Chocobo1
3aeca37c5d Fix "Create subfolder" option is not working in WebUI
Closes #10392.
2019-05-03 12:40:45 +08:00
Mike Tzou
4253515736 Merge pull request #10496 from Chocobo1/backport
Backport to v4_1_x
2019-04-17 13:13:25 +08:00
Chocobo1
e365d57063 Fix unsafe type narrowing
Appending the warning below:
qBittorrent\src\base/utils/version.h(176): warning C4267: 'argument': conversion from 'size_t' to 'int', possible loss of data
qBittorrent\src\base/utils/version.h(185): note: see reference to function template instantiation 'std::array<T,2> Utils::Version<T,2,2>::parseList<StringsList>(const StringsList &)' being compiled
        with
        [
            T=unsigned short,
            StringsList=QList<QByteArray>
        ]
2019-04-16 12:37:46 +08:00
Chocobo1
df6df20969 Use QSet for tracking server connections
We don't need to maintain order between connections so QSet would be more suitable.
2019-04-16 12:34:09 +08:00
Chocobo1
4e5a85dda5 Remove closed connections immediately
Previously it relied on a timer to drop dead connections but that proved to
be too slow when there is an incoming burst of connections.

Fixes #10487.
2019-04-16 12:34:00 +08:00
Mike Tzou
bad603454b Merge pull request #10474 from thalieht/backport
Backport #10464 to v4_1_x
2019-04-11 10:16:08 +08:00
thalieht
7b006a47ba Show user friendly size in error 2019-04-10 12:08:47 +03:00
thalieht
11da8b82e8 Increase the download size limit to 100 MiB 2019-04-10 12:02:43 +03:00
Mike Tzou
383a5f11bc Merge pull request #10468 from Chocobo1/backport
Backport recent PRs to v4_1_x
2019-04-10 16:50:19 +08:00
Chocobo1
76ab5f12c5 Work around the crash occurred in QTimer
See Qt commit:
https://code.qt.io/cgit/qt/qtbase.git/commit/src/corelib/kernel/qtimer.cpp?id=a623fe8d2a60ff333d5779f877e3b20f0e141ff1

Fixes #9985.
2019-04-09 13:57:53 +08:00
Chocobo1
be74987084 Correctly handle '+' sign in x-www-form-urlencoded data
Fixes #10451.
2019-04-09 13:57:35 +08:00
Chocobo1
8f6c305d14 Update .appdata descriptions 2019-03-25 13:27:29 +02:00
Chocobo1
e29b9655eb Use reverse DNS convention for metadata files naming
Also update appdata install path.

Closes #10111.
2019-03-25 13:27:22 +02:00
sledgehammer999
ae7fa9ea82 Merge pull request #10403 from thalieht/backport-search-enter-key
Backport #10400 to v4_1_x branch
2019-03-25 13:22:03 +02:00
thalieht
fee9030337 Make num enter key work the same as return in searchjobwidget
Backport #10400 to v4_1_x branch.
2019-03-23 10:51:07 +02:00
Peter Eszlari
f48d057c47 Linux/Wayland: make window title bar icon work 2019-03-17 21:55:50 +02:00
sledgehammer999
f14573307c Don't query Google for tracker favicons
It is a bad idea for user privacy to automatically contact a 3rd party
about tracker domains. Especially when the user isn't informed about
this and the 3rd party is Google.

Reverts 1b9882b3a3
2019-03-17 21:55:50 +02:00
sledgehammer999
560ba8c0b8 Update copyright year 2019-03-17 21:55:49 +02:00
Nick Korotysh
4b2376c4fd Cleanup Info.plist
Added few required/recommended and removed obsolete/deprecated keys
according to Apple developers documentation.
Changed CFBundleIdentifier according to reverse DNS convention.
Updated copyright year.
2019-03-17 21:55:45 +02:00
Nick Korotysh
76faed3818 Draw progress bar in Fusion style on macOS systems
Qt has a bug QTBUG-72558, which leads to incorrect progress bar
position, when drawing it in delegate.
Also, since OS X 10.10 Yosemite macOS default style was changed,
and progress bars became very tiny and without text (percentage).
These two cases make qBittorrent look pretty awful, but drawing
progress bar in Fusion style solves both issues.
2019-03-17 21:55:07 +02:00
sledgehammer999
62657d9fda Mention more translators
Closes #10043
2019-03-17 21:55:07 +02:00
Mike Tzou
5877308a49 Merge pull request #10272 from Chocobo1/login_v41x
Prevent login credential appearing in URL (for v4_1_x branch)
2019-02-10 13:31:20 +08:00
Chocobo1
24dcbe7d43 Fix wrong arg placeholder 2019-02-09 21:26:08 +08:00
Chocobo1
7649fe0a0e Display warning when Javascript is disabled 2019-02-09 15:32:43 +08:00
Chocobo1
b3b334da77 Prevent login credential appearing in URL
Closes #10221.
2019-02-09 15:28:31 +08:00
Vladimir Golovnev
03a55da260 Merge pull request #10266 from glassez/ordered-recheck
Force recheck multiple torrents one by one
2019-02-09 07:14:13 +03:00
Vladimir Golovnev (Glassez)
1a9eadf8e6 Force recheck multiple torrents one by one
Closes #9120.
2019-02-08 08:25:16 +03:00
Vladimir Golovnev
7b3fb2a35a Merge pull request #10229 from glassez/http-request-41
Separate URL components before percent-decoding
2019-01-28 16:54:02 +03:00
Vladimir Golovnev (Glassez)
a55ea29919 Separate URL components before percent-decoding
Allow special characters in query string parameters.
Closes #9116.
2019-01-28 08:54:36 +03:00
sledgehammer999
264b689912 Fix TravisCI build on macOS again 2018-12-27 22:29:38 +02:00
sledgehammer999
684cf82f89 Fix TravisCI build on macOS 2018-12-27 22:11:11 +02:00
sledgehammer999
3f0e0a319a Bump to 4.1.5 2018-12-24 19:24:16 +02:00
sledgehammer999
0b4d9c72a7 Update Changelog 2018-12-24 19:20:07 +02:00
sledgehammer999
ff71f6bcd9 Sync translations from Transifex and run lupdate 2018-12-24 18:52:35 +02:00
sledgehammer999
7a5c5baad1 Update transifex config file 2018-12-24 18:52:34 +02:00
thalieht
a18976d0b5 Fix regression on resuming torrents without metadata 2018-12-24 18:19:30 +02:00
Chocobo1
6d836ea49c Change qbt exit message to HTML5 2018-12-24 18:19:29 +02:00
Chocobo1
2e97311147 Unify translation files loading action
Since it is possible alternative WebUI could be coded in languages other than English,
WebUI must be able to load user-provided webui_en.qm.
At least one translated string must exist in order to generate an usable .qm file.
2018-12-24 18:19:28 +02:00
sledgehammer999
57bc564b2c Use configured locale only for translating
Don't use other aspects of it eg for date formatting. We should depend
on the system locale for all these. The user probably likes it that way,
otherwise he would have changed it.
2018-12-24 18:19:27 +02:00
Stephen Dawkins
1295f1e31f Keep track of REPACK/PROPER downloads
When using the smart episode filter, if the episode contains REPACK and/or
PROPER, these should be stored to prevent it from redownloading a duplicate
episodes.

Closes #9898.
2018-12-24 18:19:26 +02:00
sledgehammer999
4916ed0efb Update transifex config file 2018-12-24 18:19:25 +02:00
sledgehammer999
f15f99cb27 Sync translations from Transifex and run lupdate 2018-12-24 18:19:22 +02:00
sledgehammer999
93365d3b20 Update WebUI .ts files 2018-12-24 18:19:11 +02:00
Chocobo1
c756ab021d Upgrade TravisCI to xenial
* Remove cmake installation, it is already pre-installed by TravisCI.
* Limit ccache cache size to 512 MB. Previously the size was 5 GB for
macOS and it took 1~3 mins just for packing & uploading the cache,
limiting the size should shorten total build time.
2018-12-17 00:49:09 +02:00
Nick Korotysh
34528dd544 Make file icon look like other macOS icons 2018-12-17 00:49:08 +02:00
Chocobo1
9380209afb Revise CSP header
The majority of the CSP is tuned for built-in WebUI, it may not be
suitable for alternative UI.

Also add QLatin1String to strings. This code path is called repeatedly,
it is worth adding QLatin1String to squeeze out the last bit of
performance.
2018-12-17 00:49:07 +02:00
Chocobo1
be2895ac6f Enforce referrer-policy in WebUI
This stops leaking private data to other websites via Referrer header.
2018-12-17 00:49:06 +02:00
Thomas Piccirello
e26d4642b8 Add torrent name filtering to WebUI
Closes #721
2018-12-17 00:49:05 +02:00
Thomas Piccirello
f470972bd4 Send numeric status without translation 2018-12-17 00:49:04 +02:00
Thomas Piccirello
443378c041 Remove condition for unsupported libtorrent version 2018-12-17 00:49:02 +02:00
Thomas Piccirello
e20dbe34a4 Simplify map initialization 2018-12-17 00:49:01 +02:00
Thomas Piccirello
86bde47a06 Add WebUI Trackers context menu 2018-12-17 00:49:00 +02:00
Thomas Piccirello
e273c777c7 Add DHT, PeX, and LSD to WebUI Tracker list 2018-12-17 00:48:59 +02:00
Thomas Piccirello
17845c6b25 Add additional Tracker columns to WebUI 2018-12-17 00:48:58 +02:00
Thomas Piccirello
27827ce16a Use const where appropriate 2018-12-17 00:48:57 +02:00
Thomas Piccirello
b444ecc6af Reorder and rename Tracker list context menu option
Adds an ellipses to indicate that the Edit option opens a dialog. Also moves Edit to top of the list to convey action's prominence.
2018-12-17 00:48:56 +02:00
Thomas Piccirello
34995350ee Rename Tracker List columns
"Received" renamed to "Peers", "Peers" renamed to "Leeches".
2018-12-17 00:48:55 +02:00
sledgehammer999
73ceee52f8 Bump Web API version 2018-12-17 00:48:54 +02:00
Thomas Piccirello
85a3ba0eed Update Copyright email address 2018-12-17 00:48:53 +02:00
Thomas Piccirello
86cce76e9d Fix display bugs in WebUI Files tab. Remove <IE9 support
Priority select boxes would frequently go blank due to an unexpected priority value. On first load, the torrent-scoped file checkbox's state was inconsistent with the state of the torrent's files.
2018-12-17 00:48:52 +02:00
Thomas Piccirello
3358fd8e91 Fix incorrect priority value sent from WebUI
Closes #9070.
2018-12-17 00:48:51 +02:00
Thomas Piccirello
120965f823 Set priority for multiple files in one WebAPI request
Closes #6259.
2018-12-17 00:48:50 +02:00
Thomas Piccirello
e70ee9a5b6 Replace prio namespace with FilePriority enum class 2018-12-17 00:48:48 +02:00
Thomas Piccirello
a2d8e84e83 Match WebUI Peers table column order to GUI 2018-12-17 00:48:47 +02:00
Chocobo1
4a3648a693 Use gcc-5 for TravisCI linux builds
Remove workarounds for CXXFLAGS.
Using 3 compilation jobs should cause process trashing, tune it down to 2.
TravisCI container builds is deprecated, so remove the `sudo: false` command.
2018-12-17 00:48:43 +02:00
Chocobo1
baad45e638 Use CC, CXX from environment when available 2018-12-17 00:47:01 +02:00
Chocobo1
d9cb00aab2 Use correct locale to display date 2018-12-17 00:46:54 +02:00
Vladimir Golovnev (Glassez)
d703d98836 Show error message when Session failed to start 2018-12-17 00:41:30 +02:00
Thomas Piccirello
2f0646e7f0 Fetch data less frequently when torrents tab isn't visible 2018-12-17 00:41:28 +02:00
Thomas Piccirello
1a8a6dcef7 Add Search tab to WebUI
Closes #859, #8107.
2018-12-17 00:41:27 +02:00
Thomas Piccirello
990f961126 Allow tables to be added without a parent panel 2018-12-17 00:41:26 +02:00
Thomas Piccirello
06f04dea19 Simplify implementation 2018-12-17 00:41:25 +02:00
Thomas Piccirello
8eced2ef1f Add ability to pass urls to the webui download page 2018-12-17 00:41:24 +02:00
Tom Piccirello
1e486ea92e Fix JavaScript error
Fixes a JavaScript error caused by the element lookup returning null
2018-12-17 00:41:22 +02:00
Thomas Piccirello
b47f38675e Disallow setting a blank alternative WebUI location 2018-12-17 00:41:21 +02:00
Thomas Piccirello
864f3393a0 Add slow torrent options 2018-12-17 00:41:20 +02:00
Thomas Piccirello
cebef74326 Add "Use alternative Web UI" option 2018-12-17 00:41:19 +02:00
Thomas Piccirello
e257b35cac Add "Apply rate limit to peers on LAN" option 2018-12-17 00:41:17 +02:00
Thomas Piccirello
1f33991e4b Add email "From" option 2018-12-17 00:41:16 +02:00
Thomas Piccirello
794053f212 Set WebUI download options using set preferences
"Start torrent" and "Create subfolder" are now set depending on the user's set preferences, which matches the behavior exhibited by the GUI.
2018-12-17 00:41:14 +02:00
Thomas Piccirello
3a130e1f74 Show list of categories on WebUI download page 2018-12-17 00:41:13 +02:00
Thomas Piccirello
3423f93230 Hide WebUI text input for custom monitor save locations 2018-12-17 00:41:12 +02:00
Thomas Piccirello
2219167253 Add "When adding a torrent" options 2018-12-17 00:41:10 +02:00
Thomas Piccirello
a0a32b89a6 Add WebUI Auto TMM options 2018-12-17 00:41:08 +02:00
Thomas Piccirello
59162bf426 Replace all line breaks and fix legend code style. 2018-12-17 00:41:07 +02:00
Thomas Piccirello
dfd148f55f Add speed limit icons to WebUI Speed options 2018-12-17 00:41:05 +02:00
Thomas Piccirello
3af720b3bc Add WebUI Random port button and proxy unencrypted password notice 2018-12-17 00:41:04 +02:00
Thomas Piccirello
11240d0837 Replace WebUI Options fixed-width labels
This allows the labels to auto-expand based on the language used, and also removes unnecessary whitespace. Additionally, this results in a look more consistent with the GUI which right-aligns labels.
2018-12-17 00:40:53 +02:00
Thomas Piccirello
e64fd9c544 Reorder WebUI options to match GUI 2018-12-17 00:28:35 +02:00
FranciscoPombal
50ef812427 Add checking_mem_usage option to AdvancedSettings 2018-12-17 00:28:34 +02:00
thalieht
bd4d2fa424 Combine qAsConst() with copyAsConst() to asConst() 2018-12-17 00:28:33 +02:00
thalieht
e2ee928017 Convert all foreach() to range-based for() 2018-12-17 00:28:32 +02:00
thalieht
62e71a15a4 Fix coding style for various things 2018-12-17 00:28:31 +02:00
thalieht
c62127e9f1 Save option to start minimized in Mac 2018-12-17 00:28:29 +02:00
Chocobo1
2171d579ee Fix typo 2018-12-17 00:28:28 +02:00
Chocobo1
6e5a969e2d Use ip parameter from tracker request if provided
Closes #9949.
2018-12-17 00:28:28 +02:00
Chocobo1
bfbc7ef28a Use QHostAddress for storing IP 2018-12-17 00:28:26 +02:00
sledgehammer999
b1cefbf9b5 Autotools: Replace CPPFLAGS with CXXFLAGS 2018-12-17 00:28:25 +02:00
sledgehammer999
201638854e Autotools: Print Boost LDFLAGS nicer 2018-12-17 00:28:24 +02:00
sledgehammer999
847ecdeedb Autotools: Improve handling of C++ mode 2018-12-17 00:28:23 +02:00
Chocobo1
acc159fa60 Fix wrong locale used in log message 2018-12-17 00:28:22 +02:00
Chocobo1
bb7e80a8a6 Fix weekday names translations
Closes #9933.
2018-12-17 00:28:21 +02:00
Chocobo1
39973f1bb1 Fix strings not translated
Closes #9934.
2018-12-17 00:28:21 +02:00
Chocobo1
1e9151364a Clean up code 2018-12-17 00:28:20 +02:00
Vladimir Golovnev (Glassez)
fd50d6e9af Save torrents queue in separate file 2018-12-17 00:28:19 +02:00
Vladimir Golovnev (Glassez)
427acf0c46 Fix signed/unsigned integers comparison warning 2018-12-17 00:28:18 +02:00
Thomas Piccirello
f0a50424be Allow WebUI sidebar to be collapsed 2018-12-17 00:28:18 +02:00
Thomas Piccirello
aded9afc0e Show ellipsis when WebUI sidebar is too narrow 2018-12-17 00:28:17 +02:00
Thomas Piccirello
060b7480db Only instantiate SearchPluginManager as needed 2018-12-17 00:28:16 +02:00
Tom Piccirello
7f2a01dcd6 Fix WebUI bug on override of Start Download option
Disabled form values aren't submitted, causing the add_paused value not to be sent when Start Torrent was checked. qBittorrent would then fall back to the global Start Download preference.

Closes #9855.
2018-12-17 00:28:15 +02:00
Chocobo1
fef0e70c9f Fix missing words in WebUI
This is because Qt translator returns empty string when the translation
is not provided, now we fallback to the original string from source code.

Closes #9868.
2018-12-17 00:28:14 +02:00
Thomas Piccirello
9cc112aa4e Add SameSite attribute to WebUI session cookie
This attribute prevents the cookie from being submitted on any cross-site request, strongly limiting CSRF.

Closes #9877.
2018-12-17 00:28:14 +02:00
Chocobo1
44d4d41365 Put WebUI security related options into a groupbox 2018-12-17 00:28:13 +02:00
Chocobo1
a21c386dbf Add option for WebUI Host header validation
Closes #9743.
2018-12-17 00:28:12 +02:00
Thomas Piccirello
1c4139906a Show icon in WebUI sorted column 2018-12-17 00:28:11 +02:00
Chocobo1
1a21f45c75 Implement proper C++11 mode detection
Newer compilers have C++14 mode as default and package maintainers tend
to not specifying a C++ version when building a package, this causes
compatibility issues when (for example) qbt is compiled in C++11 and
dependency lib is in C++14. See issue #9485.

What this commit does:
1. Checks if compiler supports at least C++11
2. Checks if compiler is set in at least C++11 mode
2018-12-17 00:28:04 +02:00
sledgehammer999
0061b75200 Bump to 4.1.4 2018-11-19 01:42:20 +02:00
sledgehammer999
420c93a99e Update Changelog 2018-11-19 01:38:44 +02:00
sledgehammer999
93f1183cd7 Sync translations from Transifex and run lupdate 2018-11-19 01:36:56 +02:00
Chocobo1
b8fcc1fed2 Fix divide-by-zero crash
Previously here was using a cheap method to avoid divisor becoming < 0, but from
the crash stacktrace it seems this is not enough, now the divisor is properly
clamped to have 1 as the minimum.
Also it will now display "Unknown" for invalid calculation results.

Closes #9857.
2018-11-19 01:19:10 +02:00
Vladimir Golovnev (Glassez)
2b91be1905 Improve RSS Feed updating
Don't process "out-of-limit" articles.
Closes #9833.
2018-11-19 01:19:10 +02:00
sledgehammer999
7c9ef96ef8 Update Changelog 2018-11-15 00:12:33 +02:00
thalieht
37b4b69199 Allow resizing search filter in search job
Allow qBt to resize the search filter in search job because it causes
qBt's width to exceed the screen's width for laptop users.
2018-11-14 23:39:57 +02:00
sledgehammer999
fc18e6f8df Change FossHub RSS url for updates
The new RSS format is compatible with our current parser. FossHub will
redirect old URL to the new one so older clients will not be affected.
2018-11-14 23:39:57 +02:00
Chocobo1
4793a35e0b Don't double delete a pointer
`m_searchFilterAction` is owned by Qt, so we shouldn't delete it
manually.
2018-11-14 23:39:57 +02:00
Eugene Shalygin
4599da3ce1 cmake: use C++14 when available
Libtorrent does the same and we have to follow since
its ABI depends on the C++ standard version.

Partially closes #9485.
2018-11-14 23:39:57 +02:00
Chocobo1
dec4e41fdd Clean up SpeedLimitDialog class 2018-11-14 23:39:57 +02:00
Chocobo1
780ece0c25 Remove speed limit checkbox in Options dialog
This unifies speed limit UI elements throughout the program.
2018-11-14 23:39:57 +02:00
dzmat
aac8bfc398 Fix speed graph "high speeds" bug 2018-11-14 23:39:57 +02:00
Tom Piccirello
1a06a18336 Handle downloading .torrent file as success
We don't know whether the download will be successful, so default to success. Closes #9811.
2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
2d4f963d65 Don't update torrent status unnecessarily 2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
b54fe08201 Improve force recheck of paused torrent 2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
d1d0300491 Restore torrent in two steps
Add/restore all torrents in "paused" state and then resume those
that need to be really "resumed" (added/restored in "resumed" state).
Keep torrents with missing files paused.
Force recheck torrent with missing files when it's resumed by the user.
2018-11-14 23:39:57 +02:00
Chocobo1
7fff06f07b Improve parser for search engine versions.txt
The parse could fail when there is an extra empty line at the end of
file, this patch fixes it.
2018-11-14 23:39:57 +02:00
Chocobo1
3f9351042d Fix wrong type passed to arg() 2018-11-14 23:39:57 +02:00
Chocobo1
9e01dbab0f Fix defects found by lgtm.com 2018-11-14 23:39:57 +02:00
Chocobo1
d4a4b02cf6 Fix MSVC warning C4804
Full message of the warning:
webui\api\searchcontroller.cpp(54): warning C4804: '>': unsafe use of type 'bool'
in operation
2018-11-14 23:39:57 +02:00
dzmat
1f2c7a6671 Improve scaling of speed graphs
Make Y axis scale to fix on predetermined nice looking positions
2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
5a7b88c16c Fix Alternative Web UI to be available 2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
93351476e4 Consider empty locale setting as not set 2018-11-14 23:39:57 +02:00
Thomas Piccirello
e1bfa95a63 Use QElapsedTimer 2018-11-14 23:39:57 +02:00
Thomas Piccirello
7030cc08e7 Add free disk space to WebUI status bar
Closes #6829.
2018-11-14 23:39:57 +02:00
Thomas Piccirello
a1da9812a5 Catch invalid values 2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
8ebc0f529c Fix indentation in tstool.py 2018-11-14 23:39:57 +02:00
Tom Piccirello
e0d47649bc Bump WebAPI version 2018-11-14 23:39:57 +02:00
silver
524d503860 Recognize *.ts files as previewable 2018-11-14 23:39:57 +02:00
Thomas Piccirello
cffafa8e9f Add WebUI search API controller
Closes #2495.
2018-11-14 23:39:57 +02:00
Thomas Piccirello
0fda919268 Instantiate SearchPluginManager with other application components 2018-11-14 23:39:57 +02:00
Chocobo1
7d98c34e17 Fix TravisCI cmake build on macOS
Instead of hard coding a macOS version to use, now we follow TravisCI default
version, currently: xcode9.4.
2018-11-14 23:39:57 +02:00
Thomas Piccirello
93147e787b Fix WebUI Auto TMM context menu bug
When multiple torrents are selected with different Auto TMM values, the Auto TMM context menu option is hidden. This option is never unhidden, requiring a refresh of the page.
2018-11-14 23:39:57 +02:00
Eugene Shalygin
80435bae7e cmake: restore out-of-source build
Qt translations have to be compiled in a shared library or executable,
and since we use static libraries for the components, webui translation
files have to be compiled into the main executable.
2018-11-14 23:39:57 +02:00
Chocobo1
b367e5c197 Simplify #if conditions 2018-11-14 23:39:57 +02:00
Chocobo1
5336c71da5 Add isNetworkFileSystem() detection on Windows
This allows network mounts to be monitored correctly by polling timer.
2018-11-14 23:39:57 +02:00
dzmat
27f6db976d Reduce horizontal graphs resolution
Rewrite averaging code and reduce horizontal graphs resolution for
30 minutes and 6 hours graphs to decrease CPU usage.
2018-11-14 23:39:57 +02:00
Administrator account
8223d61fa7 Don't recheck just checked torrent
Closes #8743.
Closes #9370.
2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
3eef12bd8f Use independent translation for WebUI 2018-11-14 23:39:57 +02:00
Vladimir Golovnev (Glassez)
9e70a6c499 Create WebUI translation update tool 2018-11-14 23:39:57 +02:00
Thomas Piccirello
fec3a87421 Add categories WebAPI
Closes #5330.
2018-11-14 23:39:57 +02:00
dzmat
59aac32eb9 Allow to disable speed graphs 2018-11-14 23:39:57 +02:00
Chocobo1
5ef3917769 Add FileSystemWatcher log messages 2018-11-14 23:39:57 +02:00
Chocobo1
2f767d96d9 Add SMB2 magic number
Closes #9671.
2018-11-14 23:39:57 +02:00
silverqx
de24fdfdc2 Clear LineEdit on ESC 2018-11-14 23:39:57 +02:00
Stephen Dawkins
3bb6a68c9d Allow to disable downloading REPACK/PROPER matches 2018-11-14 23:39:57 +02:00
sledgehammer999
f2406eb2f3 Use a more detailed alert mask where possible
Closes #9547
2018-11-14 23:39:57 +02:00
Thomas Piccirello
4923ed7da0 Fix minor JavaScript defects 2018-11-14 23:39:57 +02:00
Thomas Piccirello
82056355f6 Add locale to js file path
This reduces the likelihood of a cached file being used after the locale is changed.
2018-11-14 23:39:57 +02:00
Thomas Piccirello
f3bd2a295f Translate WebUI torrents Status column
Closes #9554.
2018-11-14 23:39:57 +02:00
dzmat
cc96760839 Update uncrustify.cfg
Suddenly uncrustify does not append spaces after comma in function's argument lists. I found only one option which looks fit for it.
2018-11-14 23:39:57 +02:00
Chocobo1
ae95943f69 Remove default parameter in derived function
When derived function have different default value than base, it might cause
unnecessary confusion, see: https://stackoverflow.com/q/3533589
2018-11-14 23:39:57 +02:00
Chocobo1
d3067f939e Avoid variable shadowing 2018-11-14 23:39:57 +02:00
Chocobo1
b6addd304c Add include guard to headers 2018-11-14 23:39:57 +02:00
Chocobo1
d1ae6e8d58 Update Python URLs 2018-11-14 23:39:57 +02:00
Chocobo1
4445c2dab2 Fix asking to install Python
The dialog asking users to install python is borked since the last refactor, this
commit fixes it.
2018-11-14 23:39:57 +02:00
Chocobo1
fcc1564a62 Move python related functions
Also the functions are slightly changed to return full path of the found
python executable.
2018-11-14 23:39:57 +02:00
sledgehammer999
615eeb7144 Make strings actually translatable 2018-11-14 23:39:57 +02:00
sledgehammer999
855bb118b5 Remove unused variable 2018-11-14 23:39:57 +02:00
dzmat
9f1eb3600a Replace magic number with system define 2018-11-14 23:39:57 +02:00
Eugene Shalygin
fb885d89c1 Reword the warning message 2018-11-14 23:39:57 +02:00
Chocobo1
a846916beb Reformat python code to be compliant with PEP8
The following command is used:
`pycodestyle --ignore=E265,E722 --max-line-length=100 <py files>`
2018-11-14 23:39:57 +02:00
sledgehammer999
a574c4a70a Bump to 4.1.3 2018-09-18 22:49:35 +03:00
sledgehammer999
1e367f818d Update Changelog 2018-09-18 22:46:42 +03:00
sledgehammer999
00599c8f02 Sync translations from Transifex and run lupdate 2018-09-18 22:36:43 +03:00
sledgehammer999
332a836746 Bump Web API version 2018-09-18 22:36:16 +03:00
Yaroslav Pronin
a1992acc16 Fix typo in variable name 2018-09-13 22:37:56 +03:00
Thomas Piccirello
c3f002a544 Add save path and editing to WebUI new category dialog 2018-09-13 22:37:56 +03:00
Thomas Piccirello
c28cbe0a74 Include category save path in web api sync data 2018-09-13 22:37:56 +03:00
Thomas Piccirello
435daaceed Require torrent category creation to be explicit 2018-09-13 22:37:56 +03:00
sledgehammer999
e29ab0087b Save state change from queued to paused 2018-09-12 17:30:19 +03:00
sledgehammer999
aadd5a3312 Fix macOS builds in travis-ci 2018-09-12 17:30:19 +03:00
thalieht
7e354ffad3 Preselect name without extension when renaming files
And preselect the whole string for everything else.
2018-09-12 17:30:19 +03:00
thalieht
ee6a071fb6 Refactor in searchjob to always color visited entries
Now it colors multiple entries, when visited at once, via the hotkey or
the Download button.
2018-09-12 17:30:19 +03:00
thalieht
bc8b838953 Set "enter" as shortcut to download the selected torrents in search job 2018-09-12 17:30:19 +03:00
thalieht
5251d93b3d Fix some warnings 2018-09-12 17:30:19 +03:00
thalieht
84f0dbecfe Show "N/A" if there is no scrape
Disambiguates whether the tracker send a response of 0 peers/seeds/downloaded or didn't send one at all.
2018-09-12 17:30:19 +03:00
sledgehammer999
bba0c8b2cc Save option about tracker favicons under correct key 2018-09-12 17:30:19 +03:00
sledgehammer999
2f90be8bd2 Decrease probability of missing important alerts
During startup we can get above 1000 alerts at each pop even with only
30 torrents in the queue. This is because libtorrent will post
piece_finished_alert and file_completed_alert for each torrent. These
alerts push out of the way the ones we care about.
The alert queue will be grown to max only if needed. So we don't use
more memory. It will greatly depend on how many torrents a user has in
their session.

When getting fastresume_rejected_alert we need to act as fast as
possible in pausing it, otherwise there's a chance it will begin
downloading and writing to disk before we pause it.
2018-09-12 17:30:19 +03:00
Thomas Piccirello
cb6b6296aa Allow WebUI sidebar filters to be hidden 2018-09-12 17:30:19 +03:00
Thomas Piccirello
9d25fdce2a Increase WebUI Options initial height 2018-09-12 17:30:19 +03:00
Thomas Piccirello
12b2732f1a Adjust WebUI Options form alignment 2018-09-12 17:30:19 +03:00
Chocobo1
8c9ece73ee Fix GUI scaling issue on Linux
It seems `QT_AUTO_SCREEN_SCALE_FACTOR` doesn't work as expected.
Closes #6935.
2018-09-12 17:30:19 +03:00
Thomas Piccirello
a7db786387 Don't disable DHT when using force proxy
Closes #9292
2018-09-12 17:30:19 +03:00
Chocobo1
e5bf65c9bd Update INSTALL file
Closes #9385.
2018-09-12 17:30:19 +03:00
Chocobo1
900e7d3a14 Reset button text to default
This is to avoid Qt auto-generating code like this:
  `buttonAdd->setText(QStringLiteral(""));`
Which makes no sense and triggers clazy warning (Wclazy-empty-qstringliteral).
2018-09-12 17:30:19 +03:00
Chocobo1
f1ff74a926 Avoid copy-construct QString in for loop 2018-09-12 17:30:19 +03:00
Elias M. Mariani
30bc4b837e Support the OpenBSD filesystem 2018-09-12 17:30:19 +03:00
sledgehammer999
050a4f8b23 Fix TravisCI macOS builds 2018-09-12 17:30:19 +03:00
thalieht
487103d58f Save torrents priorities on torrent finished
Save fastresumes for all torrents that shifted in the queue when a torrent finished.
2018-09-12 17:30:19 +03:00
thalieht
eeea69d4c1 Save fastresumes when changing torrent priorities 2018-09-12 17:30:19 +03:00
Vladimir Golovnev (Glassez)
00360ad418 Always save actual queue position 2018-09-12 17:30:19 +03:00
thalieht
a733253ae5 Allow setting seq & first/last from context menu without metadata 2018-09-12 17:30:19 +03:00
thalieht
9788ee042b Rename 2 methods to eliminate ambiguity 2018-09-12 17:30:19 +03:00
thalieht
e9c9ea3bba Add regex option in the search filter's context menu
In the search job widget.
2018-09-12 17:30:19 +03:00
Chocobo1
312dfb989d Fix WebUI unreachable issue
QVariant doesn't have constructor for plain char, by default it converts
a plain char into an integer, hence the WebUI issue.
Closes #9333.
2018-09-12 17:30:19 +03:00
Chocobo1
75deafe5b1 Add config file for SVGO 2018-09-12 17:30:19 +03:00
Chocobo1
4ca257a389 Fix icon height/width ratio
It was causing misalignment in TransferListFiltersWidget text label.
2018-09-12 17:30:19 +03:00
Chocobo1
03375a78f2 Fix values sorted wrong in "Last Activity" column
I suspect there could be other negative values.
Closes #9012.

Also apply the changes to TR_RATIO_LIMIT, avoiding similar problems.
2018-08-17 21:40:04 +03:00
sledgehammer999
423c7066d7 Fix mingw warning about unrecognized escape sequence
Warning introduced by commit 6203f23f06
2018-08-17 21:40:04 +03:00
Chocobo1
5cd5cc71a8 Replace png icons with svg 2018-08-17 21:40:04 +03:00
Chocobo1
45d4d22055 Remove GuiIconProvider::generateDifferentSizes()
Let Qt do the scaling seems to be fine.
2018-08-17 21:40:04 +03:00
Eli Schwartz
916a92aa0d Fix regression that broke installing desktop file
In commit 5d94db9c79 the desktop file was
moved from src/ to dist/ but the relative path from src/src.pro was
switched to an absolute path from the repository root. This broke
detection of the file from within qmake.

Fix by using the same $DIST_PATH used elsewhere for consistency, which
uses ../dist/.
2018-08-17 21:40:04 +03:00
534 changed files with 210350 additions and 140945 deletions

1
.gitignore vendored
View File

@@ -22,6 +22,7 @@ qrc_*.cpp
ui_*.h
*.moc
src/lang/qbittorrent_*.qm
src/webui/www/translations/webui_*.qm
.DS_Store
.qmake.stash
src/qbittorrent.app

View File

@@ -3,7 +3,8 @@ language: cpp
os:
- linux
- osx
osx_image: xcode7.3
dist: xenial
env:
matrix:
@@ -38,11 +39,6 @@ cache:
directories:
- $HOME/hombebrew_cache
# opt-in Ubuntu Trusty
dist: trusty
# container-based builds
sudo: false
addons:
coverity_scan:
project:
@@ -54,34 +50,30 @@ addons:
notification_email: sledgehammer999@qbittorrent.org
apt:
sources:
# sources list: https://github.com/travis-ci/apt-source-whitelist/blob/master/ubuntu.json
- ubuntu-toolchain-r-test
#- boost-latest
# sources list: https://github.com/travis-ci/apt-source-safelist/blob/master/ubuntu.json
- sourceline: 'ppa:qbittorrent-team/qbittorrent-stable'
- sourceline: 'ppa:beineri/opt-qt551-trusty'
- sourceline: 'ppa:adrozdoff/cmake'
packages:
# packages list: https://github.com/travis-ci/apt-package-whitelist/blob/master/ubuntu-precise
# packages list: https://github.com/travis-ci/apt-package-safelist/blob/master/ubuntu-trusty
- [autoconf, automake, colormake]
- [cmake, ninja-build]
- [ninja-build]
- libssl-dev
- [libboost-dev, libboost-system-dev]
- libtorrent-rasterbar-dev
- [qt55base, qt55svg, qt55tools]
- [gcc-6, g++-6]
- [qtbase5-dev, qttools5-dev-tools, libqt5svg5-dev]
before_install:
# only allow specific build for coverity scan, others will stop
- if [ "$TRAVIS_BRANCH" = "$coverity_branch" ] && ! [ "$TRAVIS_OS_NAME" = "linux" -a "$lt_branch" = "RC_1_0" -a "$gui" = true -a "$build_system" = "qmake" ]; then exit ; fi
- shopt -s expand_aliases
- alias make="colormake -j3" # Using nprocs/2 sometimes may fail (gcc is killed by system)
- alias make="colormake -j2" # Using nprocs/2 sometimes may fail (gcc is killed by system)
- qbt_path="$HOME/qbt_install"
- |
if [ "$TRAVIS_OS_NAME" = "linux" ]; then
qbtconf="$qbtconf --prefix="$qbt_path" PKG_CONFIG_PATH=/opt/qt55/lib/pkgconfig:$PKG_CONFIG_PATH"
else
qbtconf="$qbtconf --prefix="$qbt_path""
qbtconf="$qbtconf --prefix="$qbt_path" PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATH"
CXXFLAGS="$CXXFLAGS -Wno-unused-local-typedefs -Wno-inconsistent-missing-override"
fi
# options for specific branches
@@ -90,14 +82,6 @@ before_install:
if [ "$TRAVIS_OS_NAME" = "linux" ]; then
# setup virtual display for after_success target
if [ "$gui" = true ]; then export "DISPLAY=:99.0" && /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16 ; fi ;
# Qt 5
PATH=/opt/qt55/bin:${PATH}
if [ "$build_system" = "cmake" ]; then
COMPILER_VERSION=6
export CXX="${CXX}-${COMPILER_VERSION}" CC="${CC}-${COMPILER_VERSION}"
fi
fi
# print settings
@@ -121,41 +105,24 @@ install:
# dependencies
brew update > /dev/null
brew outdated "pkg-config" || brew upgrade "pkg-config"
brew install colormake ccache zlib qt
brew install colormake ccache zlib qt openssl libtorrent-rasterbar
PATH="/usr/local/opt/ccache/libexec:$PATH"
brew link --force zlib qt
wget https://builds.shiki.hu/homebrew/version
if ! cmp --quiet "version" "$HOME/hombebrew_cache/version" ; then
echo "Cached files are different from server. Downloading new ones."
# First delete old files
rm -r "$HOME/hombebrew_cache"
mkdir "$HOME/hombebrew_cache"
cp "version" $HOME/hombebrew_cache
cd "$HOME/hombebrew_cache"
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar.rb
wget https://builds.shiki.hu/homebrew/libtorrent-rasterbar-1.1.7+git20180422.3ede0b9c20+patched-configure.el_capitan.bottle.tar.gz
fi
# Copy custom libtorrent bottle to homebrew's cache so it can find and install it
# Also install our custom libtorrent formula by passing the local path to it
# These 2 files are restored from Travis' cache.
cp "$HOME/hombebrew_cache/libtorrent-rasterbar-1.1.7+git20180422.3ede0b9c20+patched-configure.el_capitan.bottle.tar.gz" "$(brew --cache)"
brew install "$HOME/hombebrew_cache/libtorrent-rasterbar.rb"
if [ "$build_system" = "cmake" ]; then
brew outdated cmake || brew upgrade cmake
brew install ninja
ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs
ln -s /usr/local/opt/qt/plugins /usr/local/plugins
sudo ln -s /usr/local/opt/qt/mkspecs /usr/local/mkspecs
sudo ln -s /usr/local/opt/qt/plugins /usr/local/plugins
MY_CMAKE_OPENSSL_HINT="-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/"
fi
MY_CMAKE_OPENSSL_HINT="-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/"
fi
- |
if [ "$TRAVIS_BRANCH" != "$coverity_branch" ]; then
export use_ccache=true
ccache -M 512M
ccache -V && ccache --show-stats && ccache --zero-stats
fi
@@ -174,16 +141,7 @@ script:
BUILD_TOOL="ninja"
fi
if [ "$build_system" = "qmake" ]; then
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
# For some reason for RC_1_1 we need to also specify the OpenSSL compiler/linker flags
# Homebrew doesn't symlink OpenSSL for security reasons
./bootstrap.sh && ./configure $qbtconf CXXFLAGS="$(PKG_CONFIG_PATH="/usr/local/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATH" pkg-config --cflags openssl)" LDFLAGS="$(PKG_CONFIG_PATH="/usr/local/opt/openssl/lib/pkgconfig:$PKG_CONFIG_PATH" pkg-config --libs openssl)"
sed -i "" -e "s/^\(CC.*&&\).*$/\1 $CC/" src/Makefile # workaround for Qt & ccache: https://bugreports.qt.io/browse/QTBUG-31034
sed -i "" -e "s/^\(CXX.*&&\).*$/\1 $CXX/" src/Makefile
sed -i "" -e 's/^\(CXXFLAGS.*\)$/\1 -Wno-unused-local-typedefs -Wno-inconsistent-missing-override/' src/Makefile
else
./bootstrap.sh && ./configure $qbtconf
fi
./bootstrap.sh && ./configure $qbtconf CXXFLAGS="$CXXFLAGS"
BUILD_TOOL="make"
fi
- $BUILD_TOOL && $BUILD_TOOL install

View File

@@ -10,10 +10,18 @@ type = QT
minimum_perc = 23
mode = developer
[qbittorrent.qbittorrentdesktop_master]
source_file = dist/unix/qbittorrent.desktop
source_file = dist/unix/org.qbittorrent.qBittorrent.desktop
source_lang = en
type = DESKTOP
minimum_perc = 23
mode = developer
[qbittorrent.qbittorrent_webui]
file_filter = src/webui/www/translations/webui_<lang>.ts
lang_map = pt: pt_PT
source_file = src/webui/www/translations/webui_en.ts
source_lang = en
type = QT
minimum_perc = 23
mode = developer

View File

@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
message(WARNING "No official support for cmake build system. If it is broken, please submit patches!")
message(AUTHOR_WARNING "If the build fails, please try the autotools/qmake method.")
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules)
include(FunctionReadVersion)

177
Changelog
View File

@@ -1,3 +1,180 @@
* Sun Aug 04 2019 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.7
- FEATURE: Add 12 hour and 24 hour speed graphs (dzmat)
- FEATURE: Change "Add new torrent" dialog to horizontal layout (Evgeny Lensky)
- BUGFIX: Fix messed up symbols in log (Chocobo1)
- BUGFIX: Fix incomplete file extension not applied for new torrents (Chocobo1)
- BUGFIX: Save updated resume data for completed torrents (Vladimir Golovnev (Glassez))
- BUGFIX: Fix requested torrent resume data handling (Vladimir Golovnev (Glassez))
- BUGFIX: Prevent command injection via "Run external program" function (Chocobo1)
- BUGFIX: Avoid race conditions when adding torrent (Vladimir Golovnev (Glassez))
- BUGFIX: Fix torrent checking issues (Vladimir Golovnev (Glassez))
- BUGFIX: Use proper log message when there are no error (Chocobo1)
- BUGFIX: Fix torrent properties not saved for paused torrents (Chocobo1)
- BUGFIX: Some improvements on qtsingleapplication code (Chocobo1)
- BUGFIX: Remove limits of "Disk cache expiry interval" setting (Chocobo1)
- BUGFIX: Remove upper limit of "Disk cache" setting (Chocobo1)
- BUGFIX: Fix crash when removing phantom tags (Chocobo1)
- BUGFIX: Improve handleFileErrorAlert error message (Chocobo1)
- BUGFIX: Fix updated save path not saved for paused torrents (Chocobo1)
- BUGFIX: Log save_resume_data_failed_alert (Chocobo1)
- BUGFIX: Don't remove parent directories (Chocobo1)
- BUGFIX: Properly remove empty leftover folders after rename (Chocobo1)
- BUGFIX: Focus behavior row in Options dialog (silverqx)
- BUGFIX: Fix unable to rename folder on Windows when same is used in different case(Chocobo1)
- BUGFIX: Fix unable to control add torrent dialogs when opened simultaneously (Chocobo1)
- BUGFIX: Disable "Upload mode" when start preloaded torrent (Vladimir Golovnev (Glassez))
- BUGFIX: Fix wrong comparison result when sorting items(Chocobo1)
- BUGFIX: Fix sequential downloading when redirected (Vladimir Golovnev (Glassez))
- BUGFIX: Fix typos (Chocobo1)
- BUGFIX: Fix assertion fail (Chocobo1)
- BUGFIX: Change number of time axis divisions from 5 to 6 for convenience (dzmat)
- BUGFIX: Don't turn window blank when closed to system tray (Ekin Dursun)
- WEBUI: Fix WebUI encoding of special characters (Thomas Piccirello)
- WEBUI: Change the speed unit from Bytes/s to KiB/s for the rate limiter(jerrymakesjelly)
- WEBUI: Fix '+' char not decoded to space correctly (Chocobo1)
- RSS: Ignore RSS articles with non-unique identifiers (Vladimir Golovnev (Glassez))
- RSS: Perform more RSS parsing in working thread (Vladimir Golovnev (Glassez))
- RSS: Download RSS enclosure element if no proper MIME type is found (Matan Bareket)
* Sun May 05 2019 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.6
- BUGFIX: Force recheck multiple torrents one by one in all possible cases. Closes #9120 (glassez)
- BUGFIX: Don't query Google for tracker favicons, for privacy reasons (sledgehammer999)
- BUGFIX: Work around the crash occurred in QTimer. Closes #9985 (Chocobo1)
- BUGFIX: Increase the .torrent file download size limit to 100 MiB (thalieht)
- BUGFIX: Disable downloading tracker favicons by default. Works around reported crashes in Linux. Closes #9667 (Chocobo1)
- WEBUI: Separate URL components before percent-decoding. Allow special characters in query string parameters. Closes #9116 (glassez)
- WEBUI: Prevent login credential appearing in URL. Closes #10221 (Chocobo1)
- WEBUI: Display warning when Javascript is disabled (Chocobo1)
- WEBUI: Fix translatable strings (Chocobo1)
- WEBUI: Correctly handle '+' sign in x-www-form-urlencoded data. Closes #10451 (Chocobo1)
- WEBUI: Remove closed connections immediately. Closes #10487 (Chocobo1)
- WEBUI: Fix "Create subfolder" option is not working. Closes ##10392 (Chocobo1)
- SEARCH: Make num enter key work the same as return in searchjobwidget (thalieht)
- LINUX: Make window title bar icon work in Wayland (Peter Eszlari)
- LINUX: Update appdata.xml file (Chocobo1)
- MACOS: Fix progress bar drawing by using different style than native (Nick Korotysh)
- MACOS: Updated and cleaned up fields in Info.plist (Nick Korotysh)
- OTHER: Mention more translators in the about tab. Closes #10043 (sledgehammer999)
* Mon Dec 24 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.5
- FEATURE: Add checking_mem_usage option to AdvancedSettings (FranciscoPombal)
- FEATURE: Save torrents queue in separate file. Now a new file named 'queue' is created, saving on each line the infohash of each queued torrent in sorted order. (glassez)
- BUGFIX: Fix regression on resuming torrents without metadata (thalieht)
- BUGFIX: Reorder and rename Tracker list context menu option (Thomas Piccirello)
- BUGFIX: Rename Tracker List columns (Thomas Piccirello)
- BUGFIX: Show error message when Session failed to start (glassez)
- BUGFIX: Embedded tracker: Use ip parameter from tracker request if provided (Chocobo1)
- BUGFIX: Fix weekday names translations (Chocobo1)
- BUGFIX: Fix strings not translated (Chocobo1)
- WEBUI: Change qBittorrent exit message to HTML5 (Chocobo1)
- WEBUI: Revise CSP header (Chocobo1)
- WEBUI: Enforce referrer-policy in WebUI (Chocobo1)
- WEBUI: Add torrent name filtering to WebUI (Thomas Piccirello)
- WEBUI: Send numeric status without translation (Thomas Piccirello)
- WEBUI: Add WebUI Trackers context menu (Thomas Piccirello)
- WEBUI: Add DHT, PeX, and LSD to WebUI Tracker list (Thomas Piccirello)
- WEBUI: Add additional Tracker columns to WebUI (Thomas Piccirello)
- WEBUI: Bump Web API version
- WEBUI: Fix display bugs in WebUI Files tab. Remove <IE9 support (Thomas Piccirello)
- WEBUI: Fix incorrect priority value sent from WebUI (Thomas Piccirello)
- WEBUI: Set priority for multiple files in one WebAPI request (Thomas Piccirello)
- WEBUI: Match WebUI Peers table column order to GUI (Thomas Piccirello)
- WEBUI: Fetch data less frequently when torrents tab isn't visible (Thomas Piccirello)
- WEBUI: Add Search tab to WebUI (Thomas Piccirello)
- WEBUI: Add ability to pass urls to the webui download page (Thomas Piccirello)
- WEBUI: Fix JavaScript error (Tom Piccirello)
- WEBUI: Disallow setting a blank alternative WebUI location (Thomas Piccirello)
- WEBUI: Add slow torrent options (Thomas Piccirello)
- WEBUI: Add "Use alternative Web UI" option (Thomas Piccirello)
- WEBUI: Add "Apply rate limit to peers on LAN" option (Thomas Piccirello)
- WEBUI: Add email "From" option (Thomas Piccirello)
- WEBUI: Set WebUI download options using set preferences (Thomas Piccirello)
- WEBUI: Show list of categories on WebUI download page (Thomas Piccirello)
- WEBUI: Hide WebUI text input for custom monitor save locations (Thomas Piccirello)
- WEBUI: Add "When adding a torrent" options (Thomas Piccirello)
- WEBUI: Add WebUI Auto TMM options (Thomas Piccirello)
- WEBUI: Add speed limit icons to WebUI Speed options (Thomas Piccirello)
- WEBUI: Add WebUI Random port button and proxy unencrypted password notice (Thomas Piccirello)
- WEBUI: Replace WebUI Options fixed-width labels (Thomas Piccirello)
- WEBUI: Reorder WebUI options to match GUI (Thomas Piccirello)
- WEBUI: Allow WebUI sidebar to be collapsed (Thomas Piccirello)
- WEBUI: Show ellipsis when WebUI sidebar is too narrow (Thomas Piccirello)
- WEBUI: Fix WebUI bug on override of Start Download option.Closes #9855. (Tom Piccirello)
- WEBUI: Fix missing words in WebUI (Chocobo1)
- WEBUI: Add SameSite attribute to WebUI session cookie (Thomas Piccirello)
- WEBUI: Put WebUI security related options into a groupbox (Chocobo1)
- WEBUI: Add option for WebUI Host header validation (Chocobo1)
- WEBUI: Show icon in WebUI sorted column (Thomas Piccirello)
- RSS: Keep track of REPACK/PROPER downloads. Closes #9898. (Stephen Dawkins)
- SEARCH: Only instantiate SearchPluginManager as needed (Thomas Piccirello)
- MACOS: Make file icon look like other macOS icons (Nick Korotysh)
- MACOS: Save option to start minimized in Mac (thalieht)
* Mon Nov 19 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.4
- FEATURE: Recognize *.ts files as previewable (silver)
- FEATURE: Allow to disable speed graphs (dzmat)
- FEATURE: Clear LineEdit on ESC (silverqx)
- BUGFIX: Fix divide-by-zero crash (Chocobo1)
- BUGFIX: Remove speed limit checkbox in Options dialog (Chocobo1)
- BUGFIX: Fix speed graph "high speeds" bug (dzmat)
- BUGFIX: Don't update torrent status unnecessarily (glassez)
- BUGFIX: Improve force recheck of paused torrent (glassez)
- BUGFIX: Restore torrent in two steps (glassez)
- BUGFIX: Improve scaling of speed graphs (dzmat)
- BUGFIX: Add isNetworkFileSystem() detection on Windows. This allows network mounts to be monitored correctly by polling timer. (Chocobo1)
- BUGFIX: Reduce horizontal graphs resolution. Improves perfomance. (dzmat)
- BUGFIX: Don't recheck just checked torrent (mj-p, glassez)
- BUGFIX: Add SMB2 magic number (Chocobo1)
- BUGFIX: Restore startup perfomance to v4.1.2 times. Needs at least libtorrent 1.1.10 (sledgehammer999)
- BUGFIX: Make strings actually translatable (sledgehammer999)
- WEBUI: Handle downloading .torrent file as success (Tom Piccirello)
- WEBUI: Fix Alternative Web UI to be available (glassez)
- WEBUI: Consider empty locale setting as not set (glassez)
- WEBUI: Add free disk space to WebUI status bar (Thomas Piccirello)
- WEBUI: Add WebUI search API controller (Thomas Piccirello)
- WEBUI: Fix WebUI Auto TMM context menu bug (Thomas Piccirello)
- WEBUI: Use independent translation for WebUI (glassez)
- WEBUI: Add categories WebAPI (Thomas Piccirello)
- WEBUI: Fix minor JavaScript defects (Thomas Piccirello)
- WEBUI: Add locale to js file path (Thomas Piccirello)
- WEBUI: Translate WebUI torrents Status column (Thomas Piccirello)
- WEBUI: Bump Web API version
- RSS: Allow to disable downloading REPACK/PROPER matches (Stephen Dawkins)
- RSS: Improve RSS Feed updating (glassez)
- SEARCH: Allow resizing search filter in search job (thalieht)
- SEARCH: Improve parser for search engine versions.txt (Chocobo1)
- SEARCH: Update Python URLs (Chocobo1)
- SEARCH: Fix asking to install Python (Chocobo1)
- SEARCH: Reformat python code to be compliant with PEP8 (Chocobo1)
- OTHER: cmake: restore out-of-source build (Eugene Shalygin)
- OTHER: cmake: cmake: use C++14 when available (Eugene Shalygin)
* Tue Sep 18 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.3
- FEATURE: Preselect name without extension when renaming files (thalieht)
- FEATURE: Allow setting seq & first/last from context menu without metadata (thalieht)
- BUGFIX: Show "N/A" if there is no scrape (thalieht)
- BUGFIX: Save option about tracker favicons under correct key (sledgehammer999)
- BUGFIX: When file data are unreachable pause torrent and show "Missing Files" status (temporary fix) (sledgehammer999)
- BUGFIX: Don't disable DHT when using force proxy (Thomas Piccirello)
- BUGFIX: Correctly save torrent queue position/state/priority changes in fastresume (glassez, thalieht, sledgehammer999)
- BUGFIX: Fix icon height/width ratio (Chocobo1)
- BUGFIX: Fix values sorted wrong in "Last Activity" column (Chocobo1)
- BUGFIX: Replace png icons with svg (Chocobo1)
- WEBUI: Allow WebUI sidebar filters to be hidden (Thomas Piccirello)
- WEBUI: Increase WebUI Options initial height (Thomas Piccirello)
- WEBUI: Adjust WebUI Options form alignment (Thomas Piccirello)
- WEBUI: Fix WebUI unreachable issue (Chocobo1)
- WEBUI: Require torrent category creation to be explicit (Thomas Piccirello)
- WEBUI: Include category save path in web api sync data (Thomas Piccirello)
- WEBUI: Add save path and editing to WebUI new category dialog (Thomas Piccirello)
- WEBUI: Bump Web API version
- SEARCH: Refactor in searchjob to always color visited entries (thalieht)
- SEARCH: Set "enter" as shortcut to download the selected torrents in search job (thalieht)
- SEARCH: Add regex option in the search filter's context menu (thalieht)
- LINUX: Fix GUI scaling issue on Linux (Chocobo1)
- LINUX: Fix regression that broke installing desktop file (Eli Schwartz)
- OPENBSD: Better filesystem support for filewatcher (Elias M. Mariani)
* Sun Aug 12 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.2
- FEATURE: New options for "inhibit sleep" (Lukas Greib)
- FEATURE: Add option for regexps in the transferlist search filter's context menu (thalieht)

49
INSTALL
View File

@@ -1,54 +1,49 @@
qBittorrent - A BitTorrent client in C++ / Qt4
qBittorrent - A BitTorrent client in C++ / Qt
------------------------------------------
1) Compile and install qBittorrent with Qt4 Graphical Interface
1) Compile and install qBittorrent with Qt graphical interface
$ ./configure
$ make && make install
$ qbittorrent
will install and execute qBittorrent hopefully without any problems.
will install and execute qBittorrent.
Dependencies:
- Qt >= 4.6.0 (libqtgui, libqtcore, libqtnetwork, libqtxml, libqtdbus/optional)
- Qt >= 5.5.1
- pkg-config executable
- pkg-config
- libtorrent-rasterbar by Arvid Norberg (>= 1.0.6)
-> http://www.libtorrent.net
Be careful: another library (the one used by rTorrent) uses a similar name.
- libtorrent-rasterbar >= 1.0.6 (by Arvid Norberg)
* https://www.libtorrent.org/
* Be careful: another library (the one used by rTorrent) uses a similar name
- libboost >= 1.35.x (libboost-system)
- Boost >= 1.35
- python >= 2.3 (needed by search engine)
* Run time only dependency
- Python >= 2.7.9 / 3.3.0 (optional, runtime only)
* Required by the internal search engine
- geoip-database (optional)
* If qBittorrent cannot find this database, it will try to resolve countries using the Internet but it will be a lot slower.
* Run time only dependency
2) Compile and install qBittorrent without Qt4 Graphical interface
2) Compile and install qBittorrent without Qt graphical interface
$ ./configure --disable-gui
$ make && make install
$ qbittorrent
$ qbittorrent-nox
will install and execute qBittorrent hopefully without any problems.
will install and execute qBittorrent.
Dependencies:
- Qt >= 4.4.0 (libqt-devel, libqtcore, libqtnetwork)
- Qt >= 5.5.1
- pkg-config executable
- pkg-config
- libtorrent-rasterbar by Arvid Norberg (>= v1.0.6)
-> http://www.libtorrent.net
Be careful: another library (the one used by rTorrent) uses a similar name.
- libboost: libboost-filesystem, libboost-date-time, libboost-thread, libboost-serialization
- libtorrent-rasterbar >= 1.0.6 (by Arvid Norberg)
* https://www.libtorrent.org/
* Be careful: another library (the one used by rTorrent) uses a similar name
- Boost >= 1.35
DOCUMENTATION:
Please note that there is a documentation with a "compiling howto" at http://wiki.qbittorrent.org.
Please note that there is a "Compilation" section at http://wiki.qbittorrent.org.
------------------------------------------
Christophe Dumez <chris@qbittorrent.org>
sledgehammer999 <sledgehammer999@qbittorrent.org>

View File

@@ -0,0 +1,48 @@
# macros to handle translation files
# qbt_add_translations(<target> QRC_FILE <filename> TS_FILES <filenames>)
# handles out of source builds for Qt resource files that include translations
# The function generates translations out of the supplied list of .ts files in the build directory,
# copies the .qrc file there, calls qt5_add_resources() adds its output to the target sources list.
function(qbt_add_translations _target)
set(oneValueArgs QRC_FILE)
set(multiValueArgs TS_FILES)
cmake_parse_arguments(QBT_TR "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
get_target_property(_binaryDir ${_target} BINARY_DIR)
if (NOT QBT_TR_QRC_FILE)
message(FATAL_ERROR "QRC file is empty")
endif()
if (NOT QBT_TR_TS_FILES)
message(FATAL_ERROR "TS_FILES files are empty")
endif()
if(IS_ABSOLUTE "${QBT_TR_QRC_FILE}")
file(RELATIVE_PATH _qrcToTs "${CMAKE_CURRENT_SOURCE_DIR}" "${QBT_TR_QRC_FILE}")
else()
set(_qrcToTs "${QBT_TR_QRC_FILE}")
endif()
get_filename_component(_qrcToTsDir "${_qrcToTs}" DIRECTORY)
get_filename_component(_qmFilesBinaryDir "${CMAKE_CURRENT_BINARY_DIR}/${_qrcToTsDir}" ABSOLUTE)
# to make qt5_add_translation() work as we need
set_source_files_properties(${QBT_TR_TS_FILES} PROPERTIES OUTPUT_LOCATION "${_qmFilesBinaryDir}")
qt5_add_translation(_qmFiles ${QBT_TR_TS_FILES})
set(_qrc_dest_dir "${_binaryDir}/${_qrcToTsDir}")
set(_qrc_dest_file "${_binaryDir}/${QBT_TR_QRC_FILE}")
message(STATUS "copying ${QBT_TR_QRC_FILE} to ${_qrc_dest_dir}")
file(COPY ${QBT_TR_QRC_FILE} DESTINATION ${_qrc_dest_dir})
set_source_files_properties("${_qrc_dest_file}" PROPERTIES
GENERATED True
OBJECT_DEPENDS "${_qmFiles}")
# With AUTORCC enabled rcc is ran by cmake before language files are generated,
# and thus we call rcc explicitly
qt5_add_resources(_resources "${_qrc_dest_file}")
target_sources(${_target} PRIVATE "${_resources}")
endfunction()

View File

@@ -5,6 +5,8 @@ BINDIR = @EXPAND_BINDIR@
DATADIR = @EXPAND_DATADIR@
MANPREFIX = @EXPAND_MANDIR@
QMAKE_CC = @QBT_CC@
QMAKE_CXX = @QBT_CXX@
QMAKE_CXXFLAGS += @QBT_CONF_EXTRA_CFLAGS@
EXTERNAL_INCLUDES = @QBT_CONF_INCLUDES@

476
configure vendored
View File

@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.69 for qbittorrent v4.1.2.
# Generated by GNU Autoconf 2.69 for qbittorrent v4.1.7.
#
# Report bugs to <bugs.qbittorrent.org>.
#
@@ -580,8 +580,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='qbittorrent'
PACKAGE_TARNAME='qbittorrent'
PACKAGE_VERSION='v4.1.2'
PACKAGE_STRING='qbittorrent v4.1.2'
PACKAGE_VERSION='v4.1.7'
PACKAGE_STRING='qbittorrent v4.1.7'
PACKAGE_BUGREPORT='bugs.qbittorrent.org'
PACKAGE_URL='https://www.qbittorrent.org/'
@@ -595,6 +595,8 @@ QBT_REMOVE_CONFIG
QBT_ADD_CONFIG
QBT_CONF_EXTRA_CFLAGS
QBT_CONF_INCLUDES
QBT_CXX
QBT_CC
EXPAND_MANDIR
EXPAND_DATADIR
EXPAND_BINDIR
@@ -626,7 +628,6 @@ am__nodep
AMDEPBACKSLASH
AMDEP_FALSE
AMDEP_TRUE
am__quote
am__include
DEPDIR
am__untar
@@ -709,7 +710,8 @@ PACKAGE_VERSION
PACKAGE_TARNAME
PACKAGE_NAME
PATH_SEPARATOR
SHELL'
SHELL
am__quote'
ac_subst_files=''
ac_user_opts='
enable_option_checking
@@ -1297,7 +1299,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
\`configure' configures qbittorrent v4.1.2 to adapt to many kinds of systems.
\`configure' configures qbittorrent v4.1.7 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1368,7 +1370,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
short | recursive ) echo "Configuration of qbittorrent v4.1.2:";;
short | recursive ) echo "Configuration of qbittorrent v4.1.7:";;
esac
cat <<\_ACEOF
@@ -1503,7 +1505,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
qbittorrent configure v4.1.2
qbittorrent configure v4.1.7
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1642,7 +1644,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
It was created by qbittorrent $as_me v4.1.2, which was
It was created by qbittorrent $as_me v4.1.7, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -3274,7 +3276,7 @@ IFS=$ac_save_IFS
case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac
am__api_version='1.15'
am__api_version='1.16'
# Find a good install program. We prefer a C program (faster),
# so one script is as good as another. But avoid the broken or
@@ -3700,45 +3702,45 @@ DEPDIR="${am__leading_dot}deps"
ac_config_commands="$ac_config_commands depfiles"
am_make=${MAKE-make}
cat > confinc << 'END'
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} supports the include directive" >&5
$as_echo_n "checking whether ${MAKE-make} supports the include directive... " >&6; }
cat > confinc.mk << 'END'
am__doit:
@echo this is the am__doit target
@echo this is the am__doit target >confinc.out
.PHONY: am__doit
END
# If we don't find an include directive, just comment out the code.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5
$as_echo_n "checking for style of include used by $am_make... " >&6; }
am__include="#"
am__quote=
_am_result=none
# First try GNU make style include.
echo "include confinc" > confmf
# Ignore all kinds of additional output from 'make'.
case `$am_make -s -f confmf 2> /dev/null` in #(
*the\ am__doit\ target*)
am__include=include
am__quote=
_am_result=GNU
;;
esac
# Now try BSD make style include.
if test "$am__include" = "#"; then
echo '.include "confinc"' > confmf
case `$am_make -s -f confmf 2> /dev/null` in #(
*the\ am__doit\ target*)
am__include=.include
am__quote="\""
_am_result=BSD
# BSD make does it like this.
echo '.include "confinc.mk" # ignored' > confmf.BSD
# Other make implementations (GNU, Solaris 10, AIX) do it like this.
echo 'include confinc.mk # ignored' > confmf.GNU
_am_result=no
for s in GNU BSD; do
{ echo "$as_me:$LINENO: ${MAKE-make} -f confmf.$s && cat confinc.out" >&5
(${MAKE-make} -f confmf.$s && cat confinc.out) >&5 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); }
case $?:`cat confinc.out 2>/dev/null` in #(
'0:this is the am__doit target') :
case $s in #(
BSD) :
am__include='.include' am__quote='"' ;; #(
*) :
am__include='include' am__quote='' ;;
esac ;; #(
*) :
;;
esac
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5
$as_echo "$_am_result" >&6; }
rm -f confinc confmf
esac
if test "$am__include" != "#"; then
_am_result="yes ($s style)"
break
fi
done
rm -f confinc.* confmf.*
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${_am_result}" >&5
$as_echo "${_am_result}" >&6; }
# Check whether --enable-dependency-tracking was given.
if test "${enable_dependency_tracking+set}" = set; then :
@@ -3820,7 +3822,7 @@ fi
# Define the identity of the package.
PACKAGE='qbittorrent'
VERSION='v4.1.2'
VERSION='v4.1.7'
cat >>confdefs.h <<_ACEOF
@@ -3850,8 +3852,8 @@ MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"}
# For better backward compatibility. To be removed once Automake 1.9.x
# dies out for good. For more background, see:
# <http://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
# <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
# <https://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
# <https://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
mkdir_p='$(MKDIR_P)'
# We need awk for the "check" target (and possibly the TAP driver). The
@@ -4158,7 +4160,7 @@ END
Aborting the configuration process, to ensure you take notice of the issue.
You can download and install GNU coreutils to get an 'rm' implementation
that behaves properly: <http://www.gnu.org/software/coreutils/>.
that behaves properly: <https://www.gnu.org/software/coreutils/>.
If you want to complete the configuration process using your problematic
'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM
@@ -4170,7 +4172,9 @@ END
fi
# use compiler from env variables if available
QBT_CC="$CC"
QBT_CXX="$CXX"
# Define --wth-* and --enable-* arguments
@@ -4997,10 +5001,10 @@ $as_echo "$as_me: Your boost libraries seems to old (version $_version)." >&6;}
$as_echo "#define HAVE_BOOST /**/" >>confdefs.h
# execute ACTION-IF-FOUND (if present):
{ $as_echo "$as_me:${as_lineno-$LINENO}: Boost CPPFLAGS: \"$BOOST_CPPFLAGS\"
Boost LDFLAGS: \"$BOOST_LDFLAGS\"" >&5
$as_echo "$as_me: Boost CPPFLAGS: \"$BOOST_CPPFLAGS\"
Boost LDFLAGS: \"$BOOST_LDFLAGS\"" >&6;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: Boost CXXFLAGS: \"$BOOST_CPPFLAGS\"" >&5
$as_echo "$as_me: Boost CXXFLAGS: \"$BOOST_CPPFLAGS\"" >&6;}
{ $as_echo "$as_me:${as_lineno-$LINENO}: Boost LDFLAGS: \"$BOOST_LDFLAGS\"" >&5
$as_echo "$as_me: Boost LDFLAGS: \"$BOOST_LDFLAGS\"" >&6;}
fi
CPPFLAGS="$CPPFLAGS_SAVED"
@@ -5011,16 +5015,10 @@ fi
CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS"
CXXFLAGS="$BOOST_CPPFLAGS $CXXFLAGS"
LDFLAGS="$BOOST_LDFLAGS $LDFLAGS"
# add workaround for problematic boost version
ac_ext=cpp
ac_cpp='$CXXCPP $CPPFLAGS'
ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
# taken from ax_boost_base.m4
@@ -5041,12 +5039,6 @@ else
QBT_ADD_DEFINES="$QBT_ADD_DEFINES BOOST_NO_CXX11_RVALUE_REFERENCES"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
ac_ext=cpp
ac_cpp='$CXXCPP $CPPFLAGS'
ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
@@ -5400,7 +5392,7 @@ else
libtorrent_LIBS=$pkg_cv_libtorrent_LIBS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
CPPFLAGS="$libtorrent_CFLAGS $CPPFLAGS"
CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS"
LIBS="$libtorrent_LIBS $LIBS"
fi
@@ -5493,10 +5485,116 @@ else
zlib_LIBS=$pkg_cv_zlib_LIBS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
CPPFLAGS="$zlib_CFLAGS $CPPFLAGS"
CXXFLAGS="$zlib_CFLAGS $CXXFLAGS"
LIBS="$zlib_LIBS $LIBS"
fi
# Check if already in >= C++11 mode because of the flags returned by one of the above packages
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler is using C++11 or later mode" >&5
$as_echo_n "checking if compiler is using C++11 or later mode... " >&6; }
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#ifndef __cplusplus
#error "This is not a C++ compiler"
#elif __cplusplus < 201103L
#error "This is not a C++11 compiler"
#endif
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_compile "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
QBT_CXX11_FOUND="yes"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
QBT_CXX11_FOUND="no"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
# In case of no, check if the compiler can support at least C++11
# and if yes, enable it leaving a warning to the user
if test "x$QBT_CXX11_FOUND" = "xno"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler supports C++11" >&5
$as_echo_n "checking if compiler supports C++11... " >&6; }
TMP_CXXFLAGS="$CXXFLAGS"
CXXFLAGS="$CXXFLAGS -std=c++11"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#ifndef __cplusplus
#error "This is not a C++ compiler"
#elif __cplusplus < 201103L
#error "This is not a C++11 compiler"
#endif
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_compile "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if C++11 is disabled by the set compiler flags" >&5
$as_echo_n "checking if C++11 is disabled by the set compiler flags... " >&6; }
# prepend the flag so it won't override conflicting user defined flags
CXXFLAGS="-std=c++11 $TMP_CXXFLAGS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#ifndef __cplusplus
#error "This is not a C++ compiler"
#elif __cplusplus < 201103L
#error "This is not a C++11 compiler"
#endif
int
main ()
{
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_compile "$LINENO"; then :
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
CXXFLAGS="$TMP_CXXFLAGS -std=c++11"
{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: C++11 mode is now force enabled.
Make sure you use the same C++ mode for qBittorrent and its dependencies.
To explicitly set qBittorrent to a later mode use CXXFLAGS.
Example: \`CXXFLAGS=\"\$CXXFLAGS -std=c++14\" ./configure\`" >&5
$as_echo "$as_me: WARNING: C++11 mode is now force enabled.
Make sure you use the same C++ mode for qBittorrent and its dependencies.
To explicitly set qBittorrent to a later mode use CXXFLAGS.
Example: \`CXXFLAGS=\"\$CXXFLAGS -std=c++14\" ./configure\`" >&2;}
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
as_fn_error $? "The compiler supports C++11 but the user or a dependency has explicitly enabled a lower mode." "$LINENO" 5
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
as_fn_error $? "A compiler supporting C++11 is required." "$LINENO" 5
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
fi
# These are required because autoconf doesn't expand these **particular**
# vars automatically. And qmake cannot autoexpand them.
@@ -5583,15 +5681,15 @@ extract() {
for i in $string; do
case "$(echo "$i" | cut -c1)" in
'') ;;
D) QBT_CONF_DEFINES="$(echo $i | cut -c2-) $QBT_CONF_DEFINES";;
I) QBT_CONF_INCLUDES="$(echo $i | cut -c2-) $QBT_CONF_INCLUDES";;
*) QBT_CONF_EXTRA_CFLAGS="-$i $QBT_CONF_EXTRA_CFLAGS";;
D) QBT_CONF_DEFINES="$QBT_CONF_DEFINES $(echo $i | cut -c2-)";;
I) QBT_CONF_INCLUDES="$QBT_CONF_INCLUDES $(echo $i | cut -c2-)";;
*) QBT_CONF_EXTRA_CFLAGS="$QBT_CONF_EXTRA_CFLAGS -$i";;
esac
done
IFS=$SAVEIFS
}
extract "$CFLAGS $CPPFLAGS $CXXFLAGS"
extract "$CFLAGS $CXXFLAGS"
QBT_ADD_DEFINES="$QBT_ADD_DEFINES $QBT_CONF_DEFINES"
# Substitute the values of these vars in conf.pri.in
@@ -5602,6 +5700,8 @@ QBT_ADD_DEFINES="$QBT_ADD_DEFINES $QBT_CONF_DEFINES"
ac_config_files="$ac_config_files conf.pri"
cat >confcache <<\_ACEOF
@@ -6174,7 +6274,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by qbittorrent $as_me v4.1.2, which was
This file was extended by qbittorrent $as_me v4.1.7, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -6232,7 +6332,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
qbittorrent config.status v4.1.2
qbittorrent config.status v4.1.7
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
@@ -6340,7 +6440,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
#
# INIT-COMMANDS
#
AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"
AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"
_ACEOF
@@ -6785,29 +6885,35 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
# Older Autoconf quotes --file arguments for eval, but not when files
# are listed without --file. Let's play safe and only enable the eval
# if we detect the quoting.
case $CONFIG_FILES in
*\'*) eval set x "$CONFIG_FILES" ;;
*) set x $CONFIG_FILES ;;
esac
# TODO: see whether this extra hack can be removed once we start
# requiring Autoconf 2.70 or later.
case $CONFIG_FILES in #(
*\'*) :
eval set x "$CONFIG_FILES" ;; #(
*) :
set x $CONFIG_FILES ;; #(
*) :
;;
esac
shift
for mf
# Used to flag and report bootstrapping failures.
am_rc=0
for am_mf
do
# Strip MF so we end up with the name of the file.
mf=`echo "$mf" | sed -e 's/:.*$//'`
# Check whether this is an Automake generated Makefile or not.
# We used to match only the files named 'Makefile.in', but
# some people rename them; so instead we look at the file content.
# Grep'ing the first line is not enough: some people post-process
# each Makefile.in and add a new line on top of each file to say so.
# Grep'ing the whole file is not good either: AIX grep has a line
am_mf=`$as_echo "$am_mf" | sed -e 's/:.*$//'`
# Check whether this is an Automake generated Makefile which includes
# dependency-tracking related rules and includes.
# Grep'ing the whole file directly is not great: AIX grep has a line
# limit of 2048, but all sed's we know have understand at least 4000.
if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
dirpart=`$as_dirname -- "$mf" ||
$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$mf" : 'X\(//\)[^/]' \| \
X"$mf" : 'X\(//\)$' \| \
X"$mf" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$mf" |
sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \
|| continue
am_dirpart=`$as_dirname -- "$am_mf" ||
$as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$am_mf" : 'X\(//\)[^/]' \| \
X"$am_mf" : 'X\(//\)$' \| \
X"$am_mf" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$am_mf" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
@@ -6825,53 +6931,48 @@ $as_echo X"$mf" |
q
}
s/.*/./; q'`
else
continue
fi
# Extract the definition of DEPDIR, am__include, and am__quote
# from the Makefile without running 'make'.
DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
test -z "$DEPDIR" && continue
am__include=`sed -n 's/^am__include = //p' < "$mf"`
test -z "$am__include" && continue
am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
# Find all dependency output files, they are included files with
# $(DEPDIR) in their names. We invoke sed twice because it is the
# simplest approach to changing $(DEPDIR) to its actual value in the
# expansion.
for file in `sed -n "
s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do
# Make sure the directory exists.
test -f "$dirpart/$file" && continue
fdir=`$as_dirname -- "$file" ||
$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$file" : 'X\(//\)[^/]' \| \
X"$file" : 'X\(//\)$' \| \
X"$file" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$file" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
am_filepart=`$as_basename -- "$am_mf" ||
$as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \
X"$am_mf" : 'X\(//\)$' \| \
X"$am_mf" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X/"$am_mf" |
sed '/^.*\/\([^/][^/]*\)\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
/^X\/\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
/^X\/\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
as_dir=$dirpart/$fdir; as_fn_mkdir_p
# echo "creating $dirpart/$file"
echo '# dummy' > "$dirpart/$file"
done
{ echo "$as_me:$LINENO: cd "$am_dirpart" \
&& sed -e '/# am--include-marker/d' "$am_filepart" \
| $MAKE -f - am--depfiles" >&5
(cd "$am_dirpart" \
&& sed -e '/# am--include-marker/d' "$am_filepart" \
| $MAKE -f - am--depfiles) >&5 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } || am_rc=$?
done
if test $am_rc -ne 0; then
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "Something went wrong bootstrapping makefile fragments
for automatic dependency tracking. Try re-running configure with the
'--disable-dependency-tracking' option to at least be able to build
the package (albeit without support for automatic dependency tracking).
See \`config.log' for more details" "$LINENO" 5; }
fi
{ am_dirpart=; unset am_dirpart;}
{ am_filepart=; unset am_filepart;}
{ am_mf=; unset am_mf;}
{ am_rc=; unset am_rc;}
rm -f conftest-deps.mk
}
;;
@@ -7489,7 +7590,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
This file was extended by qbittorrent $as_me v4.1.2, which was
This file was extended by qbittorrent $as_me v4.1.7, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -7547,7 +7648,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
qbittorrent config.status v4.1.2
qbittorrent config.status v4.1.7
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
@@ -7655,7 +7756,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
#
# INIT-COMMANDS
#
AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"
AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"
_ACEOF
@@ -8101,29 +8202,35 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
# Older Autoconf quotes --file arguments for eval, but not when files
# are listed without --file. Let's play safe and only enable the eval
# if we detect the quoting.
case $CONFIG_FILES in
*\'*) eval set x "$CONFIG_FILES" ;;
*) set x $CONFIG_FILES ;;
esac
# TODO: see whether this extra hack can be removed once we start
# requiring Autoconf 2.70 or later.
case $CONFIG_FILES in #(
*\'*) :
eval set x "$CONFIG_FILES" ;; #(
*) :
set x $CONFIG_FILES ;; #(
*) :
;;
esac
shift
for mf
# Used to flag and report bootstrapping failures.
am_rc=0
for am_mf
do
# Strip MF so we end up with the name of the file.
mf=`echo "$mf" | sed -e 's/:.*$//'`
# Check whether this is an Automake generated Makefile or not.
# We used to match only the files named 'Makefile.in', but
# some people rename them; so instead we look at the file content.
# Grep'ing the first line is not enough: some people post-process
# each Makefile.in and add a new line on top of each file to say so.
# Grep'ing the whole file is not good either: AIX grep has a line
am_mf=`$as_echo "$am_mf" | sed -e 's/:.*$//'`
# Check whether this is an Automake generated Makefile which includes
# dependency-tracking related rules and includes.
# Grep'ing the whole file directly is not great: AIX grep has a line
# limit of 2048, but all sed's we know have understand at least 4000.
if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
dirpart=`$as_dirname -- "$mf" ||
$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$mf" : 'X\(//\)[^/]' \| \
X"$mf" : 'X\(//\)$' \| \
X"$mf" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$mf" |
sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \
|| continue
am_dirpart=`$as_dirname -- "$am_mf" ||
$as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$am_mf" : 'X\(//\)[^/]' \| \
X"$am_mf" : 'X\(//\)$' \| \
X"$am_mf" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$am_mf" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
s//\1/
q
@@ -8141,53 +8248,48 @@ $as_echo X"$mf" |
q
}
s/.*/./; q'`
else
continue
fi
# Extract the definition of DEPDIR, am__include, and am__quote
# from the Makefile without running 'make'.
DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"`
test -z "$DEPDIR" && continue
am__include=`sed -n 's/^am__include = //p' < "$mf"`
test -z "$am__include" && continue
am__quote=`sed -n 's/^am__quote = //p' < "$mf"`
# Find all dependency output files, they are included files with
# $(DEPDIR) in their names. We invoke sed twice because it is the
# simplest approach to changing $(DEPDIR) to its actual value in the
# expansion.
for file in `sed -n "
s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \
sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do
# Make sure the directory exists.
test -f "$dirpart/$file" && continue
fdir=`$as_dirname -- "$file" ||
$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
X"$file" : 'X\(//\)[^/]' \| \
X"$file" : 'X\(//\)$' \| \
X"$file" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X"$file" |
sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
am_filepart=`$as_basename -- "$am_mf" ||
$as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \
X"$am_mf" : 'X\(//\)$' \| \
X"$am_mf" : 'X\(/\)' \| . 2>/dev/null ||
$as_echo X/"$am_mf" |
sed '/^.*\/\([^/][^/]*\)\/*$/{
s//\1/
q
}
/^X\(\/\/\)[^/].*/{
/^X\/\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\/\)$/{
s//\1/
q
}
/^X\(\/\).*/{
/^X\/\(\/\).*/{
s//\1/
q
}
s/.*/./; q'`
as_dir=$dirpart/$fdir; as_fn_mkdir_p
# echo "creating $dirpart/$file"
echo '# dummy' > "$dirpart/$file"
done
{ echo "$as_me:$LINENO: cd "$am_dirpart" \
&& sed -e '/# am--include-marker/d' "$am_filepart" \
| $MAKE -f - am--depfiles" >&5
(cd "$am_dirpart" \
&& sed -e '/# am--include-marker/d' "$am_filepart" \
| $MAKE -f - am--depfiles) >&5 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } || am_rc=$?
done
if test $am_rc -ne 0; then
{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
as_fn_error $? "Something went wrong bootstrapping makefile fragments
for automatic dependency tracking. Try re-running configure with the
'--disable-dependency-tracking' option to at least be able to build
the package (albeit without support for automatic dependency tracking).
See \`config.log' for more details" "$LINENO" 5; }
fi
{ am_dirpart=; unset am_dirpart;}
{ am_filepart=; unset am_filepart;}
{ am_mf=; unset am_mf;}
{ am_rc=; unset am_rc;}
rm -f conftest-deps.mk
}
;;

View File

@@ -1,4 +1,4 @@
AC_INIT([qbittorrent], [v4.1.2], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_INIT([qbittorrent], [v4.1.7], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_MACRO_DIR([m4])
AC_PROG_CC
@@ -8,7 +8,9 @@ AC_LANG(C++)
AC_CANONICAL_HOST
AM_INIT_AUTOMAKE
# use compiler from env variables if available
QBT_CC="$CC"
QBT_CXX="$CXX"
# Define --wth-* and --enable-* arguments
@@ -162,14 +164,13 @@ AS_CASE(["x$enable_qt_dbus"],
AX_BOOST_BASE([1.35],
[AC_MSG_NOTICE([Boost CPPFLAGS: "$BOOST_CPPFLAGS"
Boost LDFLAGS: "$BOOST_LDFLAGS"])],
[AC_MSG_NOTICE([Boost CXXFLAGS: "$BOOST_CPPFLAGS"])
AC_MSG_NOTICE([Boost LDFLAGS: "$BOOST_LDFLAGS"])],
[AC_MSG_ERROR([Could not find Boost])])
CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS"
CXXFLAGS="$BOOST_CPPFLAGS $CXXFLAGS"
LDFLAGS="$BOOST_LDFLAGS $LDFLAGS"
# add workaround for problematic boost version
AC_LANG_PUSH(C++)
# taken from ax_boost_base.m4
m4_define([DETECT_BOOST_VERSION_PROGRAM],
[AC_LANG_PROGRAM([[#include <boost/version.hpp>]],
@@ -177,7 +178,6 @@ m4_define([DETECT_BOOST_VERSION_PROGRAM],
AC_COMPILE_IFELSE([DETECT_BOOST_VERSION_PROGRAM(106000)], [],
[QBT_ADD_DEFINES="$QBT_ADD_DEFINES BOOST_NO_CXX11_RVALUE_REFERENCES"])
AC_LANG_POP([C++])
AX_BOOST_SYSTEM()
AC_MSG_NOTICE([Boost.System LIB: "$BOOST_SYSTEM_LIB"])
@@ -196,14 +196,46 @@ AS_CASE(["x$with_qtsingleapplication"],
PKG_CHECK_MODULES(libtorrent,
[libtorrent-rasterbar >= 1.0.6],
[CPPFLAGS="$libtorrent_CFLAGS $CPPFLAGS"
[CXXFLAGS="$libtorrent_CFLAGS $CXXFLAGS"
LIBS="$libtorrent_LIBS $LIBS"])
PKG_CHECK_MODULES(zlib,
[zlib >= 1.2.5.2],
[CPPFLAGS="$zlib_CFLAGS $CPPFLAGS"
[CXXFLAGS="$zlib_CFLAGS $CXXFLAGS"
LIBS="$zlib_LIBS $LIBS"])
# Check if already in >= C++11 mode because of the flags returned by one of the above packages
AC_MSG_CHECKING([if compiler is using C++11 or later mode])
AC_COMPILE_IFELSE([DETECT_CPP11_PROGRAM()],
[AC_MSG_RESULT([yes])
QBT_CXX11_FOUND="yes"],
[AC_MSG_RESULT([no])
QBT_CXX11_FOUND="no"])
# In case of no, check if the compiler can support at least C++11
# and if yes, enable it leaving a warning to the user
AS_IF([test "x$QBT_CXX11_FOUND" = "xno"],
[AC_MSG_CHECKING([if compiler supports C++11])
TMP_CXXFLAGS="$CXXFLAGS"
CXXFLAGS="$CXXFLAGS -std=c++11"
AC_COMPILE_IFELSE([DETECT_CPP11_PROGRAM()],
[AC_MSG_RESULT([yes])
AC_MSG_CHECKING([if C++11 is disabled by the set compiler flags])
# prepend the flag so it won't override conflicting user defined flags
CXXFLAGS="-std=c++11 $TMP_CXXFLAGS"
AC_COMPILE_IFELSE([DETECT_CPP11_PROGRAM()],
[AC_MSG_RESULT([no])
CXXFLAGS="$TMP_CXXFLAGS -std=c++11"
AC_MSG_WARN([C++11 mode is now force enabled.
Make sure you use the same C++ mode for qBittorrent and its dependencies.
To explicitly set qBittorrent to a later mode use CXXFLAGS.
Example: `CXXFLAGS="\$CXXFLAGS -std=c++14" ./configure`])],
[AC_MSG_RESULT([yes])
AC_MSG_ERROR([The compiler supports C++11 but the user or a dependency has explicitly enabled a lower mode.])])],
[AC_MSG_RESULT([no])
AC_MSG_ERROR([A compiler supporting C++11 is required.])])
])
# These are required because autoconf doesn't expand these **particular**
# vars automatically. And qmake cannot autoexpand them.
AX_DEFINE_DIR([EXPAND_PREFIX], [prefix])
@@ -230,18 +262,20 @@ extract() {
for i in $string; do
case "$(echo "$i" | cut -c1)" in
'') ;;
D) QBT_CONF_DEFINES="$(echo $i | cut -c2-) $QBT_CONF_DEFINES";;
I) QBT_CONF_INCLUDES="$(echo $i | cut -c2-) $QBT_CONF_INCLUDES";;
*) QBT_CONF_EXTRA_CFLAGS="-$i $QBT_CONF_EXTRA_CFLAGS";;
D) QBT_CONF_DEFINES="$QBT_CONF_DEFINES $(echo $i | cut -c2-)";;
I) QBT_CONF_INCLUDES="$QBT_CONF_INCLUDES $(echo $i | cut -c2-)";;
*) QBT_CONF_EXTRA_CFLAGS="$QBT_CONF_EXTRA_CFLAGS -$i";;
esac
done
IFS=$SAVEIFS
}
extract "$CFLAGS $CPPFLAGS $CXXFLAGS"
extract "$CFLAGS $CXXFLAGS"
QBT_ADD_DEFINES="$QBT_ADD_DEFINES $QBT_CONF_DEFINES"
# Substitute the values of these vars in conf.pri.in
AC_SUBST(QBT_CC)
AC_SUBST(QBT_CXX)
AC_SUBST(QBT_CONF_INCLUDES)
AC_SUBST(QBT_CONF_EXTRA_CFLAGS)
AC_SUBST(QBT_ADD_CONFIG)

22
dist/mac/Info.plist vendored
View File

@@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>qBittorrent</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
@@ -21,6 +25,10 @@
<array>
<string>org.bittorrent.torrent</string>
</array>
<key>NSExportableTypes</key>
<array>
<string>org.bittorrent.torrent</string>
</array>
<key>LSIsAppleDefaultForType</key>
<true/>
</dict>
@@ -28,6 +36,8 @@
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLSchemes</key>
<array>
<string>magnet</string>
@@ -45,21 +55,19 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.1.2</string>
<key>CFBundleSignature</key>
<string>qBit</string>
<string>4.1.7</string>
<key>CFBundleExecutable</key>
<string>@EXECUTABLE@</string>
<key>CFBundleIdentifier</key>
<string>org.qbittorrent</string>
<string>org.qbittorrent.qBittorrent</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}.0</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<string>True</string>
<key>NSAppleScriptEnabled</key>
<string>YES</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2006-2018 The qBittorrent project</string>
<string>Copyright © 2006-2019 The qBittorrent project</string>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>

Binary file not shown.

View File

@@ -31,12 +31,12 @@ if (Qt5Widgets_FOUND)
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor
FILES_MATCHING PATTERN "*.png")
install(FILES qbittorrent.desktop
install(FILES org.qbittorrent.qBittorrent.desktop
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications/
COMPONENT data)
install(FILES qbittorrent.appdata.xml
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/appdata/
install(FILES org.qbittorrent.qBittorrent.appdata.xml
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo/
COMPONENT data)
install(FILES

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2014 sledgehammer999 <sledgehammer999@qbittorrent.org> -->
<component type="desktop">
<id>org.qbittorrent.qBittorrent</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-2.0 and OpenSSL</project_license>
<name>qBittorrent</name>
<summary>An open-source Bittorrent client</summary>
<description>
<p>
The qBittorrent project aims to provide an open-source software alternative to µTorrent.
Additionally, qBittorrent runs and provides the same features on all major platforms (FreeBSD, Linux, macOS, OS/2, Windows).
qBittorrent is based on the Qt toolkit and libtorrent-rasterbar library.
</p>
<ul>
<li>Polished µTorrent-like User Interface</li>
<li>
Well-integrated and extensible Search Engine
<ul>
<li>Simultaneous search in many Torrent search sites</li>
<li>Category-specific search requests (e.g. Books, Music, Software)</li>
</ul>
</li>
<li>RSS feed support with advanced download filters (incl. regex)</li>
<li>
Many Bittorrent extensions supported:
<ul>
<li>Magnet links</li>
<li>Distributed hash table (DHT), peer exchange protocol (PEX), local peer discovery (LSD)</li>
<li>Private torrents</li>
<li>Encrypted connections</li>
<li>and many more...</li>
</ul>
</li>
<li>Remote control through Web user interface, written with AJAX</li>
<li>Sequential downloading (Download in order)</li>
<li>
Advanced control over torrents, trackers and peers
<ul>
<li>Torrents queueing and prioritizing</li>
<li>Torrent content selection and prioritizing</li>
</ul>
</li>
<li>Bandwidth scheduler</li>
<li>Torrent creation tool</li>
<li>IP Filtering (eMule &amp; PeerGuardian format compatible)</li>
<li>IPv6 compliant</li>
<li>UPnP / NAT-PMP port forwarding support</li>
<li>Available on all platforms: Windows, Linux, macOS, FreeBSD, OS/2</li>
<li>Available in ~70 languages</li>
</ul>
</description>
<launchable type="desktop-id">org.qbittorrent.qBittorrent.desktop</launchable>
<screenshots>
<screenshot type="default">
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_01.png</image>
</screenshot>
<screenshot>
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_02.png</image>
</screenshot>
<screenshot>
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_03.png</image>
</screenshot>
<screenshot>
<image height="675" width="1200">https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_04.png</image>
</screenshot>
</screenshots>
<update_contact>sledgehammer999@qbittorrent.org</update_contact>
<developer_name>The qBittorrent Project</developer_name>
<url type="homepage">https://www.qbittorrent.org/</url>
<url type="bugtracker">http://bugs.qbittorrent.org/</url>
<url type="donation">https://www.qbittorrent.org/donate</url>
<url type="help">http://forum.qbittorrent.org/</url>
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
</component>

View File

@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2014 sledgehammer999 <sledgehammer999@qbittorrent.org> -->
<component type="desktop">
<id>qbittorrent.desktop</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-2.0 and OpenSSL</project_license>
<name>qBittorrent</name>
<summary>A Bittorrent Client</summary>
<description>
<p>
Aiming to be a good alternative to all other bittorrent clients out
there, qBittorrent is fast, stable and provides unicode support as well
as many other features. Additionally, qBittorrent runs and provides those
same features on all major platforms (Linux, Mac OS X, Windows, FreeBSD).
</p>
<p>
It is programmed in C++ / Qt and uses libtorrent (sometimes called
libtorrent-rasterbar) by Arvid Norberg. GeoLite data, created by MaxMind,
are included in qBittorrent. Its features include:
</p>
<ul>
<li>Polished µTorrent-like User Interface</li>
<li>Well-integrated and extensible Search Engine</li>
<li>All Bittorrent extensions (DHT, Peer Exchange, Full encryption, Magnet/BitComet URIs, ...)</li>
<li>Remote control through a Web user interface</li>
<li>Advanced control over trackers, peers and torrents</li>
<li>UPnP / NAT-PMP port forwarding support</li>
<li>Available in ~25 languages (Unicode support)</li>
<li>Torrent creation tool</li>
<li>Advanced RSS support with download filters (inc. regex)</li>
<li>Bandwidth scheduler</li>
<li>IP Filtering (eMule and PeerGuardian compatible)</li>
<li>IPv6 compliant</li>
<li>Sequential downloading (aka "Download in order")</li>
</ul>
</description>
<screenshots>
<screenshot type="default">
<image height="675" width="1200">
https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_01.png
</image>
</screenshot>
<screenshot>
<image height="675" width="1200">
https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_02.png
</image>
</screenshot>
<screenshot>
<image height="675" width="1200">
https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_03.png
</image>
</screenshot>
<screenshot>
<image height="675" width="1200">
https://alexpl.fedorapeople.org/AppData/qbittorrent/screens/qbittorrent_04.png
</image>
</screenshot>
</screenshots>
<url type="homepage">https://www.qbittorrent.org/</url>
<update_contact>sledgehammer999@qbittorrent.org</update_contact>
<developer_name>The qBittorrent Project</developer_name>
<url type="bugtracker">http://bugs.qbittorrent.org/</url>
<url type="donation">https://www.qbittorrent.org/donate</url>
<url type="help">http://forum.qbittorrent.org/</url>
<url type="translate">https://github.com/qbittorrent/qBittorrent/wiki/How-to-translate-qBittorrent</url>
</component>

View File

@@ -27,7 +27,7 @@ XPStyle on
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
; Program specific
!define PROG_VERSION "4.1.2"
!define PROG_VERSION "4.1.7"
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun
@@ -50,7 +50,7 @@ XPStyle on
;Installer Version Information
VIAddVersionKey "ProductName" "qBittorrent"
VIAddVersionKey "CompanyName" "The qBittorrent project"
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2018 The qBittorrent project"
VIAddVersionKey "LegalCopyright" "Copyright ©2006-2019 The qBittorrent project"
VIAddVersionKey "FileDescription" "qBittorrent - A Bittorrent Client"
VIAddVersionKey "FileVersion" "${PROG_VERSION}"

View File

@@ -36,3 +36,16 @@ AC_DEFUN([FIND_QTDBUS],
[AC_MSG_RESULT([not found])
HAVE_QTDBUS=[false]])
])
# DETECT_CPP11_PROGRAM()
# Detects if at least C++11 mode is enabled.
# --------------------------------------
AC_DEFUN([DETECT_CPP11_PROGRAM],
[AC_LANG_PROGRAM([[
#ifndef __cplusplus
#error "This is not a C++ compiler"
#elif __cplusplus < 201103L
#error "This is not a C++11 compiler"
#endif]],
[[]])
])

View File

@@ -1,5 +1,13 @@
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_STANDARD "11")
# If C++14 is available, use it as libtorent ABI depends on 11/14 version
if (cxx_std_14 IN_LIST CMAKE_CXX_COMPILE_FEATURES)
message(STATUS "Building in C++14 mode")
set(CMAKE_CXX_STANDARD "14")
else()
message(STATUS "Building in C++11 mode")
set(CMAKE_CXX_STANDARD "11")
endif()
include(MacroQbtCompilerSettings)
qbt_set_compiler_options()
@@ -13,7 +21,7 @@ if (Boost_VERSION VERSION_LESS 106000)
add_definitions(-DBOOST_NO_CXX11_RVALUE_REFERENCES)
endif()
find_package(Qt5 ${requiredQtVersion} REQUIRED COMPONENTS Core Network Xml)
find_package(Qt5 ${requiredQtVersion} REQUIRED COMPONENTS Core Network Xml LinguistTools)
find_package(Qt5Widgets ${requiredQtVersion})
if (Qt5Widgets_FOUND)
find_package(Qt5DBus ${requiredQtVersion})

View File

@@ -23,29 +23,19 @@ set_target_properties(qBittorrent
)
# translations
include(QbtTranslations)
file(GLOB QBT_TS_FILES ../lang/*.ts)
get_filename_component(QBT_QM_FILES_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/../lang" ABSOLUTE)
set_source_files_properties(${QBT_TS_FILES} PROPERTIES OUTPUT_LOCATION "${QBT_QM_FILES_BINARY_DIR}")
qbt_add_translations(qBittorrent QRC_FILE "../lang/lang.qrc" TS_FILES ${QBT_TS_FILES})
find_package(Qt5 COMPONENTS LinguistTools REQUIRED)
qt5_add_translation(QBT_QM_FILES ${QBT_TS_FILES})
get_filename_component(_lang_qrc_src "${CMAKE_CURRENT_SOURCE_DIR}/../lang/lang.qrc" ABSOLUTE)
get_filename_component(_lang_qrc_dst "${CMAKE_CURRENT_BINARY_DIR}/../lang/lang.qrc" ABSOLUTE)
get_filename_component(_lang_qrc_dst_dir "${CMAKE_CURRENT_BINARY_DIR}/../lang" ABSOLUTE)
message(STATUS "copying ${_lang_qrc_src} -> ${_lang_qrc_dst}")
file(COPY ${_lang_qrc_src} DESTINATION ${_lang_qrc_dst_dir})
set_source_files_properties("${_lang_qrc_dst}" PROPERTIES GENERATED True)
foreach(qm_file ${QBT_QM_FILES})
set_source_files_properties("${_lang_qrc_dst}" PROPERTIES OBJECT_DEPENDS ${qm_file})
endforeach()
if (WEBUI)
file(GLOB QBT_WEBUI_TS_FILES ../webui/www/translations/*.ts)
qbt_add_translations(qBittorrent QRC_FILE "../webui/www/translations/webui_translations.qrc" TS_FILES ${QBT_WEBUI_TS_FILES})
endif()
set(QBT_APP_RESOURCES
../icons/icons.qrc
../searchengine/searchengine.qrc
"${_lang_qrc_dst}"
)
# With AUTORCC rcc is ran by cmake before language files are generated,

View File

@@ -44,6 +44,7 @@
#endif
#ifndef DISABLE_GUI
#include <QMessageBox>
#ifdef Q_OS_WIN
#include <QSessionManager>
#include <QSharedMemory>
@@ -61,6 +62,7 @@
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrenthandle.h"
#include "base/exceptions.h"
#include "base/iconprovider.h"
#include "base/logger.h"
#include "base/net/downloadmanager.h"
@@ -72,6 +74,7 @@
#include "base/rss/rss_autodownloader.h"
#include "base/rss/rss_session.h"
#include "base/scanfoldersmodel.h"
#include "base/search/searchpluginmanager.h"
#include "base/settingsstorage.h"
#include "base/utils/fs.h"
#include "base/utils/misc.h"
@@ -121,6 +124,7 @@ Application::Application(const QString &id, int &argc, char **argv)
qRegisterMetaType<Log::Msg>("Log::Msg");
setApplicationName("qBittorrent");
setOrganizationDomain("qbittorrent.org");
validateCommandLineParameters();
QString profileDir = m_commandLineArgs.portableMode
@@ -142,6 +146,9 @@ Application::Application(const QString &id, int &argc, char **argv)
#if !defined(DISABLE_GUI)
setAttribute(Qt::AA_UseHighDpiPixmaps, true); // opt-in to the high DPI pixmap support
setQuitOnLastWindowClosed(false);
#if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
setDesktopFileName("org.qbittorrent.qBittorrent");
#endif
#endif
#if defined(Q_OS_WIN) && !defined(DISABLE_GUI)
@@ -325,7 +332,11 @@ void Application::runExternalProgram(const BitTorrent::TorrentHandle *torrent) c
::LocalFree(args);
#else
QProcess::startDetached(QLatin1String("/bin/sh"), {QLatin1String("-c"), program});
// Cannot give users shell environment by default, as doing so could
// enable command injection via torrent name and other arguments
// (especially when some automated download mechanism has been setup).
// See: https://github.com/qbittorrent/qBittorrent/issues/10925
QProcess::startDetached(program);
#endif
}
@@ -427,7 +438,7 @@ void Application::processParams(const QStringList &params)
BitTorrent::AddTorrentParams torrentParams;
TriStateBool skipTorrentDialog;
foreach (QString param, params) {
for (QString param : params) {
param = param.trimmed();
// Process strings indicating options specified by the user.
@@ -494,26 +505,42 @@ int Application::exec(const QStringList &params)
GuiIconProvider::initInstance();
#endif
BitTorrent::Session::initInstance();
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection);
try {
BitTorrent::Session::initInstance();
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &Application::torrentFinished);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::allTorrentsFinished, this, &Application::allTorrentsFinished, Qt::QueuedConnection);
#ifndef DISABLE_COUNTRIES_RESOLUTION
Net::GeoIPManager::initInstance();
Net::GeoIPManager::initInstance();
#endif
ScanFoldersModel::initInstance(this);
ScanFoldersModel::initInstance(this);
#ifndef DISABLE_WEBUI
m_webui = new WebUI;
m_webui = new WebUI;
#ifdef DISABLE_GUI
if (m_webui->isErrored())
return 1;
connect(m_webui, &WebUI::fatalError, this, []() { QCoreApplication::exit(1); });
if (m_webui->isErrored())
return 1;
connect(m_webui, &WebUI::fatalError, this, []() { QCoreApplication::exit(1); });
#endif // DISABLE_GUI
#endif // DISABLE_WEBUI
new RSS::Session; // create RSS::Session singleton
new RSS::AutoDownloader; // create RSS::AutoDownloader singleton
new RSS::Session; // create RSS::Session singleton
new RSS::AutoDownloader; // create RSS::AutoDownloader singleton
}
catch (const RuntimeError &err) {
#ifdef DISABLE_GUI
fprintf(stderr, "%s", err.what());
#else
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Critical);
msgBox.setText(tr("Application failed to start."));
msgBox.setInformativeText(err.message());
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Misc::screenCenter(&msgBox));
msgBox.exec();
#endif
return 1;
}
#ifdef DISABLE_GUI
#ifndef DISABLE_WEBUI
@@ -723,6 +750,7 @@ void Application::cleanup()
delete m_fileLogger;
Logger::freeInstance();
IconProvider::freeInstance();
SearchPluginManager::freeInstance();
Utils::Fs::removeDirRecursive(Utils::Fs::tempPath());
#ifndef DISABLE_GUI

View File

@@ -41,6 +41,7 @@
#include <QMessageBox>
#endif
#include "base/global.h"
#include "base/utils/misc.h"
#include "base/utils/string.h"
@@ -486,7 +487,7 @@ CommandLineParameterError::CommandLineParameterError(const QString &messageForUs
{
}
const QString& CommandLineParameterError::messageForUser() const
const QString &CommandLineParameterError::messageForUser() const
{
return m_messageForUser;
}
@@ -497,7 +498,7 @@ QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN
QStringList lines = {words.first()};
int currentLineMaxLength = wrapAtColumn - initialIndentation;
foreach (const QString &word, words.mid(1)) {
for (const QString &word : asConst(words.mid(1))) {
if (lines.last().length() + word.length() + 1 < currentLineMaxLength) {
lines.last().append(' ' + word);
}

View File

@@ -33,6 +33,7 @@
#include <QFile>
#include <QTextStream>
#include "base/global.h"
#include "base/logger.h"
#include "base/utils/fs.h"
@@ -50,7 +51,7 @@ FileLogger::FileLogger(const QString &path, const bool backup, const int maxSize
this->deleteOld(age, ageType);
const Logger *const logger = Logger::instance();
foreach (const Log::Msg &msg, logger->getMessages())
for (const Log::Msg &msg : asConst(logger->getMessages()))
addLogMessage(msg);
connect(logger, &Logger::newLogMessage, this, &FileLogger::addLogMessage);
@@ -87,7 +88,7 @@ void FileLogger::deleteOld(const int age, const FileLogAgeType ageType)
QDateTime date = QDateTime::currentDateTime();
QDir dir(Utils::Fs::branchPath(m_path));
foreach (const QFileInfo file, dir.entryInfoList(QStringList("qbittorrent.log.bak*"), QDir::Files | QDir::Writable, QDir::Time | QDir::Reversed)) {
for (const QFileInfo &file : asConst(dir.entryInfoList(QStringList("qbittorrent.log.bak*"), QDir::Files | QDir::Writable, QDir::Time | QDir::Reversed))) {
QDateTime modificationDate = file.lastModified();
switch (ageType) {
case DAYS:

View File

@@ -102,10 +102,6 @@ void displayBadArgMessage(const QString &message);
#if !defined(DISABLE_GUI)
void showSplashScreen();
#if defined(Q_OS_UNIX)
void setupDpi();
#endif // Q_OS_UNIX
#endif // DISABLE_GUI
// Main
@@ -120,10 +116,6 @@ int main(int argc, char *argv[])
macMigratePlists();
#endif
#if !defined(DISABLE_GUI) && defined(Q_OS_UNIX)
setupDpi();
#endif
try {
// Create Application
QString appId = QLatin1String("qBittorrent-") + Utils::Misc::getUserIDString();
@@ -210,7 +202,7 @@ int main(int argc, char *argv[])
// this is the default in Qt6
app->setAttribute(Qt::AA_DisableWindowContextHelpButton);
#endif
#endif
#endif // Q_OS_WIN
#if defined(Q_OS_MAC)
// Since Apple made difficult for users to set PATH, we set here for convenience.
@@ -330,14 +322,6 @@ void showSplashScreen()
QTimer::singleShot(1500, splash, &QObject::deleteLater);
qApp->processEvents();
}
#if defined(Q_OS_UNIX)
void setupDpi()
{
if (qEnvironmentVariableIsEmpty("QT_AUTO_SCREEN_SCALE_FACTOR"))
qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "1");
}
#endif // Q_OS_UNIX
#endif // DISABLE_GUI
void displayVersion()

View File

@@ -101,6 +101,7 @@ QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
#endif
server = new QLocalServer(this);
server->setSocketOptions(QLocalServer::UserAccessOption);
QString lockName = QDir(QDir::tempPath()).absolutePath()
+ QLatin1Char('/') + socketName
+ QLatin1String("-lockfile");
@@ -191,6 +192,12 @@ void QtLocalPeer::receiveConnection()
QByteArray uMsg;
quint32 remaining;
ds >> remaining;
if (remaining > 65535) {
// drop suspiciously large data
delete socket;
return;
}
uMsg.resize(remaining);
int got = 0;
char* uMsgBuf = uMsg.data();

View File

@@ -83,7 +83,7 @@ bool userAcceptsUpgrade()
msgBox.move(Utils::Misc::screenCenter(&msgBox));
if (msgBox.exec() == QMessageBox::Ok)
return true;
#endif
#endif // DISABLE_GUI
return false;
}
@@ -179,9 +179,9 @@ bool upgrade(bool ask = true)
// ****************************************************************************************
// Silently converts old v3.3.x .fastresume files
QStringList backupFiles_3_3 = backupFolderDir.entryList(
const QStringList backupFiles_3_3 = backupFolderDir.entryList(
QStringList(QLatin1String("*.fastresume.*")), QDir::Files, QDir::Unsorted);
foreach (const QString &backupFile, backupFiles_3_3)
for (const QString &backupFile : backupFiles_3_3)
upgradeResumeFile(backupFolderDir.absoluteFilePath(backupFile));
// ****************************************************************************************
@@ -197,10 +197,10 @@ bool upgrade(bool ask = true)
if (ask && !userAcceptsUpgrade()) return false;
QStringList backupFiles = backupFolderDir.entryList(
const QStringList backupFiles = backupFolderDir.entryList(
QStringList(QLatin1String("*.fastresume")), QDir::Files, QDir::Unsorted);
const QRegularExpression rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
foreach (QString backupFile, backupFiles) {
for (const QString &backupFile : backupFiles) {
const QRegularExpressionMatch rxMatch = rx.match(backupFile);
if (rxMatch.hasMatch()) {
const QString hashStr = rxMatch.captured(1);
@@ -265,7 +265,7 @@ void migratePlistToIni(const QString &application)
plistFile->setFallbacksEnabled(false);
const QStringList plist = plistFile->allKeys();
if (!plist.isEmpty()) {
foreach (const QString &key, plist)
for (const QString &key : plist)
iniFile.setValue(key, plistFile->value(key));
plistFile->clear();
}

View File

@@ -4,6 +4,7 @@ add_library(qbt_base STATIC
# headers
bittorrent/addtorrentparams.h
bittorrent/cachestatus.h
bittorrent/filepriority.h
bittorrent/infohash.h
bittorrent/magneturi.h
bittorrent/peerinfo.h
@@ -76,6 +77,7 @@ types.h
unicodestrings.h
# sources
bittorrent/filepriority.cpp
bittorrent/infohash.cpp
bittorrent/magneturi.cpp
bittorrent/peerinfo.cpp

View File

@@ -38,12 +38,12 @@ AsyncFileStorage::AsyncFileStorage(const QString &storageFolderPath, QObject *pa
, m_lockFile(m_storageDir.absoluteFilePath(QStringLiteral("storage.lock")))
{
if (!m_storageDir.mkpath(m_storageDir.absolutePath()))
throw AsyncFileStorageError(
QString("Could not create directory '%1'.").arg(m_storageDir.absolutePath()));
throw AsyncFileStorageError {tr("Could not create directory '%1'.")
.arg(m_storageDir.absolutePath())};
// TODO: This folder locking approach does not work for UNIX systems. Implement it.
if (!m_lockFile.open(QFile::WriteOnly))
throw AsyncFileStorageError(m_lockFile.errorString());
throw AsyncFileStorageError {m_lockFile.errorString()};
}
AsyncFileStorage::~AsyncFileStorage()
@@ -76,13 +76,3 @@ void AsyncFileStorage::store_impl(const QString &fileName, const QByteArray &dat
}
}
}
AsyncFileStorageError::AsyncFileStorageError(const QString &message)
: std::runtime_error(message.toUtf8().data())
{
}
QString AsyncFileStorageError::message() const
{
return what();
}

View File

@@ -28,22 +28,22 @@
#pragma once
#include <stdexcept>
#include <QDir>
#include <QFile>
#include <QObject>
class AsyncFileStorageError : public std::runtime_error
#include "base/exceptions.h"
class AsyncFileStorageError : public RuntimeError
{
public:
explicit AsyncFileStorageError(const QString &message);
QString message() const;
using RuntimeError::RuntimeError;
};
class AsyncFileStorage : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(AsyncFileStorage)
public:
explicit AsyncFileStorage(const QString &storageFolderPath, QObject *parent = nullptr);

View File

@@ -3,6 +3,7 @@ HEADERS += \
$$PWD/asyncfilestorage.h \
$$PWD/bittorrent/addtorrentparams.h \
$$PWD/bittorrent/cachestatus.h \
$$PWD/bittorrent/filepriority.h \
$$PWD/bittorrent/infohash.h \
$$PWD/bittorrent/magneturi.h \
$$PWD/bittorrent/peerinfo.h \
@@ -75,6 +76,7 @@ HEADERS += \
SOURCES += \
$$PWD/asyncfilestorage.cpp \
$$PWD/bittorrent/filepriority.cpp \
$$PWD/bittorrent/infohash.cpp \
$$PWD/bittorrent/magneturi.cpp \
$$PWD/bittorrent/peerinfo.cpp \

View File

@@ -0,0 +1,46 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
*
* 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 "filepriority.h"
namespace BitTorrent
{
bool isValidFilePriority(const BitTorrent::FilePriority priority)
{
switch (priority) {
case BitTorrent::FilePriority::Ignored:
case BitTorrent::FilePriority::Normal:
case BitTorrent::FilePriority::High:
case BitTorrent::FilePriority::Maximum:
case BitTorrent::FilePriority::Mixed:
return true;
default:
return false;
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2018 Thomas Piccirello <thomas.piccirello@gmail.com>
*
* 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
namespace BitTorrent
{
enum class FilePriority : int
{
Ignored = 0,
Normal = 1,
High = 6,
Maximum = 7,
Mixed = -1
};
bool isValidFilePriority(BitTorrent::FilePriority priority);
}

View File

@@ -83,10 +83,10 @@ MagnetUri::MagnetUri(const QString &source)
m_hash = m_addTorrentParams.info_hash;
m_name = QString::fromStdString(m_addTorrentParams.name);
foreach (const std::string &tracker, m_addTorrentParams.trackers)
for (const std::string &tracker : m_addTorrentParams.trackers)
m_trackers.append(QString::fromStdString(tracker));
foreach (const std::string &urlSeed, m_addTorrentParams.url_seeds)
for (const std::string &urlSeed : m_addTorrentParams.url_seeds)
m_urlSeeds.append(QUrl(urlSeed.c_str()));
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015, 2018 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -39,18 +39,23 @@ ResumeDataSavingManager::ResumeDataSavingManager(const QString &resumeFolderPath
{
}
void ResumeDataSavingManager::saveResumeData(QString infoHash, QByteArray data) const
void ResumeDataSavingManager::save(const QString &filename, const QByteArray &data) const
{
QString filename = QString("%1.fastresume").arg(infoHash);
QString filepath = m_resumeDataDir.absoluteFilePath(filename);
const QString filepath = m_resumeDataDir.absoluteFilePath(filename);
qDebug() << "Saving resume data in" << filepath;
QSaveFile resumeFile(filepath);
if (resumeFile.open(QIODevice::WriteOnly)) {
resumeFile.write(data);
if (!resumeFile.commit()) {
Logger::instance()->addMessage(QString("Couldn't save resume data in %1. Error: %2")
.arg(filepath, resumeFile.errorString()), Log::WARNING);
QSaveFile file {filepath};
if (file.open(QIODevice::WriteOnly)) {
file.write(data);
if (!file.commit()) {
Logger::instance()->addMessage(QString("Couldn't save data in '%1'. Error: %2")
.arg(filepath, file.errorString()), Log::WARNING);
}
}
}
void ResumeDataSavingManager::remove(const QString &filename) const
{
const QString filepath = m_resumeDataDir.absoluteFilePath(filename);
Utils::Fs::forceRemove(filepath);
}

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2015, 2018 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
@@ -26,8 +26,7 @@
* exception statement from your version.
*/
#ifndef RESUMEDATASAVINGMANAGER_H
#define RESUMEDATASAVINGMANAGER_H
#pragma once
#include <QByteArray>
#include <QDir>
@@ -36,15 +35,15 @@
class ResumeDataSavingManager : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(ResumeDataSavingManager)
public:
explicit ResumeDataSavingManager(const QString &resumeFolderPath);
public slots:
void saveResumeData(QString infoHash, QByteArray data) const;
void save(const QString &filename, const QByteArray &data) const;
void remove(const QString &filename) const;
private:
QDir m_resumeDataDir;
};
#endif // RESUMEDATASAVINGMANAGER_H

File diff suppressed because it is too large Load Diff

View File

@@ -237,7 +237,7 @@ namespace BitTorrent
int diskJobTime = 0;
} disk;
};
#endif
#endif // LIBTORRENT_VERSION_NUM >= 10100
class Session : public QObject
{
@@ -377,6 +377,8 @@ namespace BitTorrent
void setAnnounceToAllTiers(bool val);
int asyncIOThreads() const;
void setAsyncIOThreads(int num);
int checkingMemUsage() const;
void setCheckingMemUsage(int size);
int diskCacheSize() const;
void setDiskCacheSize(int size);
int diskCacheTTL() const;
@@ -480,6 +482,7 @@ namespace BitTorrent
void bottomTorrentsPriority(const QStringList &hashes);
// TorrentHandle interface
void handleTorrentSaveResumeDataRequested(TorrentHandle *const torrent);
void handleTorrentShareLimitChanged(TorrentHandle *const torrent);
void handleTorrentNameChanged(TorrentHandle *const torrent);
void handleTorrentSavePathChanged(TorrentHandle *const torrent);
@@ -606,13 +609,13 @@ namespace BitTorrent
void updateSeedingLimitTimer();
void exportTorrentFile(TorrentHandle *const torrent, TorrentExportFolder folder = TorrentExportFolder::Regular);
void saveTorrentResumeData(TorrentHandle *const torrent, bool finalSave = false);
void handleAlert(libtorrent::alert *a);
void dispatchTorrentAlert(libtorrent::alert *a);
void handleAddTorrentAlert(libtorrent::add_torrent_alert *p);
void handleStateUpdateAlert(libtorrent::state_update_alert *p);
void handleMetadataReceivedAlert(libtorrent::metadata_received_alert *p);
void handleMetadataReceivedAlert(const libtorrent::metadata_received_alert *p);
void handleTorrentPausedAlert(const libtorrent::torrent_paused_alert *p);
void handleFileErrorAlert(libtorrent::file_error_alert *p);
void handleTorrentRemovedAlert(libtorrent::torrent_removed_alert *p);
void handleTorrentDeletedAlert(libtorrent::torrent_deleted_alert *p);
@@ -632,6 +635,8 @@ namespace BitTorrent
void createTorrentHandle(const libtorrent::torrent_handle &nativeHandle);
void saveResumeData();
void saveTorrentsQueue();
void removeTorrentsQueue();
#if LIBTORRENT_VERSION_NUM < 10100
void dispatchAlerts(libtorrent::alert *alertPtr);
@@ -656,6 +661,7 @@ namespace BitTorrent
CachedSettingValue<bool> m_announceToAllTrackers;
CachedSettingValue<bool> m_announceToAllTiers;
CachedSettingValue<int> m_asyncIOThreads;
CachedSettingValue<int> m_checkingMemUsage;
CachedSettingValue<int> m_diskCacheSize;
CachedSettingValue<int> m_diskCacheTTL;
CachedSettingValue<bool> m_useOSCache;

View File

@@ -110,7 +110,7 @@ void TorrentCreatorThread::run()
QStringList fileNames;
QHash<QString, boost::int64_t> fileSizeMap;
for (const auto &dir : qAsConst(dirs)) {
for (const auto &dir : asConst(dirs)) {
QStringList tmpNames; // natural sort files within each dir
QDirIterator fileIter(dir, QDir::Files);
@@ -126,7 +126,7 @@ void TorrentCreatorThread::run()
fileNames += tmpNames;
}
for (const auto &fileName : qAsConst(fileNames))
for (const auto &fileName : asConst(fileNames))
fs.add_file(fileName.toStdString(), fileSizeMap[fileName]);
}
@@ -141,14 +141,14 @@ void TorrentCreatorThread::run()
#endif
// Add url seeds
foreach (QString seed, m_params.urlSeeds) {
for (QString seed : asConst(m_params.urlSeeds)) {
seed = seed.trimmed();
if (!seed.isEmpty())
newTorrent.add_url_seed(seed.toStdString());
}
int tier = 0;
foreach (const QString &tracker, m_params.trackers) {
for (const QString &tracker : asConst(m_params.trackers)) {
if (tracker.isEmpty())
++tier;
else

View File

@@ -55,6 +55,7 @@
#include <windows.h>
#endif
#include "base/global.h"
#include "base/logger.h"
#include "base/preferences.h"
#include "base/profile.h"
@@ -77,7 +78,7 @@ namespace
ListType setToEntryList(const QSet<QString> &input)
{
ListType entryList;
foreach (const QString &setValue, input)
for (const QString &setValue : input)
entryList.emplace_back(setValue.toStdString());
return entryList;
}
@@ -158,14 +159,14 @@ namespace
{
// new constructor is available
template<typename T, typename std::enable_if<std::is_constructible<T, libt::torrent_info, bool>::value, int>::type = 0>
T makeTorrentCreator(const libtorrent::torrent_info & ti)
T makeTorrentCreator(const libtorrent::torrent_info &ti)
{
return T(ti, true);
}
// new constructor isn't available
template<typename T, typename std::enable_if<!std::is_constructible<T, libt::torrent_info, bool>::value, int>::type = 0>
T makeTorrentCreator(const libtorrent::torrent_info & ti)
T makeTorrentCreator(const libtorrent::torrent_info &ti)
{
return T(ti);
}
@@ -187,10 +188,12 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
, m_ratioLimit(params.ratioLimit)
, m_seedingTimeLimit(params.seedingTimeLimit)
, m_tempPathDisabled(params.disableTempPath)
, m_fastresumeDataRejected(false)
, m_hasMissingFiles(false)
, m_hasRootFolder(params.hasRootFolder)
, m_needsToSetFirstLastPiecePriority(false)
, m_pauseAfterRecheck(false)
, m_needsToStartForced(params.forced)
, m_pauseWhenReady(params.paused)
{
if (m_useAutoTMM)
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
@@ -216,17 +219,17 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
m_hasRootFolder = false;
}
// "started" means "all initialization has completed and torrent has started regular processing".
// When torrent added/restored in "paused" state it become "started" immediately after construction.
// When it is added/restored in "resumed" state, it become "started" after it is really resumed
// (i.e. after receiving "torrent resumed" alert).
m_started = (params.restored && hasMetadata() ? isPaused() : params.paused);
if (!m_started) {
if (!params.restored || !hasMetadata()) {
if (!hasMetadata()) {
// There is nothing to prepare
if (!m_pauseWhenReady) {
// Resume torrent because it was added in "resumed" state
// but it's actually paused during initialization
resume(params.forced);
// but it's actually paused during initialization.
m_startupState = Starting;
resume_impl(m_needsToStartForced);
}
else {
m_startupState = Started;
m_pauseWhenReady = false;
}
}
}
@@ -374,10 +377,9 @@ QString TorrentHandle::nativeActualSavePath() const
QList<TrackerEntry> TorrentHandle::trackers() const
{
QList<TrackerEntry> entries;
std::vector<libt::announce_entry> announces;
const std::vector<libt::announce_entry> announces = m_nativeHandle.trackers();
announces = m_nativeHandle.trackers();
foreach (const libt::announce_entry &tracker, announces)
for (const libt::announce_entry &tracker : announces)
entries << tracker;
return entries;
@@ -391,7 +393,7 @@ QHash<QString, TrackerInfo> TorrentHandle::trackerInfos() const
void TorrentHandle::addTrackers(const QList<TrackerEntry> &trackers)
{
QList<TrackerEntry> addedTrackers;
foreach (const TrackerEntry &tracker, trackers) {
for (const TrackerEntry &tracker : trackers) {
if (addTracker(tracker))
addedTrackers << tracker;
}
@@ -400,13 +402,13 @@ void TorrentHandle::addTrackers(const QList<TrackerEntry> &trackers)
m_session->handleTorrentTrackersAdded(this, addedTrackers);
}
void TorrentHandle::replaceTrackers(QList<TrackerEntry> trackers)
void TorrentHandle::replaceTrackers(const QList<TrackerEntry> &trackers)
{
QList<TrackerEntry> existingTrackers = this->trackers();
QList<TrackerEntry> addedTrackers;
std::vector<libt::announce_entry> announces;
foreach (const TrackerEntry &tracker, trackers) {
for (const TrackerEntry &tracker : trackers) {
announces.push_back(tracker.nativeEntry());
if (!existingTrackers.contains(tracker))
addedTrackers << tracker;
@@ -438,9 +440,9 @@ bool TorrentHandle::addTracker(const TrackerEntry &tracker)
QList<QUrl> TorrentHandle::urlSeeds() const
{
QList<QUrl> urlSeeds;
std::set<std::string> seeds = m_nativeHandle.url_seeds();
const std::set<std::string> seeds = m_nativeHandle.url_seeds();
foreach (const std::string &urlSeed, seeds)
for (const std::string &urlSeed : seeds)
urlSeeds.append(QUrl(urlSeed.c_str()));
return urlSeeds;
@@ -449,7 +451,7 @@ QList<QUrl> TorrentHandle::urlSeeds() const
void TorrentHandle::addUrlSeeds(const QList<QUrl> &urlSeeds)
{
QList<QUrl> addedUrlSeeds;
foreach (const QUrl &urlSeed, urlSeeds) {
for (const QUrl &urlSeed : urlSeeds) {
if (addUrlSeed(urlSeed))
addedUrlSeeds << urlSeed;
}
@@ -461,7 +463,7 @@ void TorrentHandle::addUrlSeeds(const QList<QUrl> &urlSeeds)
void TorrentHandle::removeUrlSeeds(const QList<QUrl> &urlSeeds)
{
QList<QUrl> removedUrlSeeds;
foreach (const QUrl &urlSeed, urlSeeds) {
for (const QUrl &urlSeed : urlSeeds) {
if (removeUrlSeed(urlSeed))
removedUrlSeeds << urlSeed;
}
@@ -504,12 +506,10 @@ bool TorrentHandle::needSaveResumeData() const
return m_nativeHandle.need_save_resume_data();
}
void TorrentHandle::saveResumeData(bool updateStatus)
void TorrentHandle::saveResumeData()
{
if (updateStatus) // to update queue_position, see discussion in PR #6154
this->updateStatus();
m_nativeHandle.save_resume_data();
m_session->handleTorrentSaveResumeDataRequested(this);
}
int TorrentHandle::filesCount() const
@@ -599,8 +599,7 @@ bool TorrentHandle::removeTag(const QString &tag)
void TorrentHandle::removeAllTags()
{
// QT automatically copies the container in foreach, so it's safe to mutate it.
foreach (const QString &tag, m_tags)
for (const QString &tag : asConst(tags()))
removeTag(tag);
}
@@ -626,7 +625,7 @@ QString TorrentHandle::filePath(int index) const
QString TorrentHandle::fileName(int index) const
{
if (!hasMetadata()) return QString();
if (!hasMetadata()) return QString();
return Utils::Fs::fileName(filePath(index));
}
@@ -639,7 +638,7 @@ qlonglong TorrentHandle::fileSize(int index) const
// to all files in a torrent
QStringList TorrentHandle::absoluteFilePaths() const
{
if (!hasMetadata()) return QStringList();
if (!hasMetadata()) return QStringList();
QDir saveDir(savePath(true));
QStringList res;
@@ -650,7 +649,7 @@ QStringList TorrentHandle::absoluteFilePaths() const
QStringList TorrentHandle::absoluteFilePathsUnwanted() const
{
if (!hasMetadata()) return QStringList();
if (!hasMetadata()) return QStringList();
QDir saveDir(savePath(true));
QStringList res;
@@ -810,7 +809,10 @@ TorrentState TorrentHandle::state() const
void TorrentHandle::updateState()
{
if (isMoveInProgress()) {
if (m_nativeStatus.state == libt::torrent_status::checking_resume_data) {
m_state = TorrentState::CheckingResumeData;
}
else if (isMoveInProgress()) {
m_state = TorrentState::Moving;
}
else if (isPaused()) {
@@ -842,9 +844,6 @@ void TorrentHandle::updateState()
m_state = TorrentState::QueuedForChecking;
break;
#endif
case libt::torrent_status::checking_resume_data:
m_state = TorrentState::CheckingResumeData;
break;
case libt::torrent_status::checking_files:
m_state = m_hasSeedStatus ? TorrentState::CheckingUploading : TorrentState::CheckingDownloading;
break;
@@ -886,9 +885,9 @@ bool TorrentHandle::hasError() const
bool TorrentHandle::hasFilteredPieces() const
{
std::vector<int> pp = m_nativeHandle.piece_priorities();
const std::vector<int> pp = m_nativeHandle.piece_priorities();
foreach (const int priority, pp)
for (const int priority : pp)
if (priority == 0) return true;
return false;
@@ -976,12 +975,13 @@ qulonglong TorrentHandle::eta() const
QVector<qreal> TorrentHandle::filesProgress() const
{
std::vector<boost::int64_t> fp;
QVector<qreal> result;
m_nativeHandle.file_progress(fp, libt::torrent_handle::piece_granularity);
int count = static_cast<int>(fp.size());
const int count = static_cast<int>(fp.size());
QVector<qreal> result;
result.reserve(count);
for (int i = 0; i < count; ++i) {
qlonglong size = fileSize(i);
const qlonglong size = fileSize(i);
if ((size <= 0) || (fp[i] == size))
result << 1;
else
@@ -1089,7 +1089,7 @@ QList<PeerInfo> TorrentHandle::peers() const
m_nativeHandle.get_peer_info(nativePeers);
foreach (const libt::peer_info &peer, nativePeers)
for (const libt::peer_info &peer : nativePeers)
peers << PeerInfo(this, peer);
return peers;
@@ -1213,12 +1213,8 @@ void TorrentHandle::setName(const QString &name)
bool TorrentHandle::setCategory(const QString &category)
{
if (m_category != category) {
if (!category.isEmpty()) {
if (!Session::isValidCategoryName(category)) return false;
if (!m_session->categories().contains(category))
if (!m_session->addCategory(category))
return false;
}
if (!category.isEmpty() && !m_session->categories().contains(category))
return false;
QString oldCategory = m_category;
m_category = category;
@@ -1237,6 +1233,8 @@ bool TorrentHandle::setCategory(const QString &category)
void TorrentHandle::move(QString path)
{
if (m_startupState != Started) return;
m_useAutoTMM = false;
m_session->handleTorrentSavingModeChanged(this);
@@ -1275,14 +1273,17 @@ void TorrentHandle::forceDHTAnnounce()
void TorrentHandle::forceRecheck()
{
if (m_startupState != Started) return;
if (!hasMetadata()) return;
if (isPaused()) {
m_pauseAfterRecheck = true;
resume_impl(true, true);
}
m_nativeHandle.force_recheck();
m_unchecked = false;
if (isPaused()) {
m_nativeHandle.stop_when_ready(true);
m_nativeHandle.auto_managed(true);
m_pauseWhenReady = true;
}
}
void TorrentHandle::setSequentialDownload(bool b)
@@ -1291,6 +1292,8 @@ void TorrentHandle::setSequentialDownload(bool b)
m_nativeHandle.set_sequential_download(b);
m_nativeStatus.sequential_download = b; // prevent return cached value
}
saveResumeData();
}
void TorrentHandle::toggleSequentialDownload()
@@ -1337,6 +1340,8 @@ void TorrentHandle::setFirstLastPiecePriorityImpl(const bool enabled, const QVec
LogMsg(tr("Download first and last piece first: %1, torrent: '%2'")
.arg((enabled ? tr("On") : tr("Off")), name()));
saveResumeData();
}
void TorrentHandle::toggleFirstLastPiecePriority()
@@ -1346,24 +1351,44 @@ void TorrentHandle::toggleFirstLastPiecePriority()
void TorrentHandle::pause()
{
if (m_startupState != Started) return;
if (m_pauseWhenReady) return;
if (isChecking()) {
m_pauseWhenReady = true;
return;
}
if (isPaused()) return;
m_nativeHandle.auto_managed(false);
m_nativeHandle.pause();
// Libtorrent doesn't emit a torrent_paused_alert when the
// torrent is queued (no I/O)
// We test on the cached m_nativeStatus
if (isQueued())
m_session->handleTorrentPaused(this);
}
void TorrentHandle::resume(bool forced)
{
resume_impl(forced, false);
if (m_startupState != Started) return;
m_pauseWhenReady = false;
resume_impl(forced);
}
void TorrentHandle::resume_impl(bool forced, bool uploadMode)
void TorrentHandle::resume_impl(bool forced)
{
if (hasError())
m_nativeHandle.clear_error();
m_hasMissingFiles = false;
if (m_hasMissingFiles) {
m_hasMissingFiles = false;
m_nativeHandle.force_recheck();
}
m_nativeHandle.auto_managed(!forced);
m_nativeHandle.set_upload_mode(uploadMode);
m_nativeHandle.resume();
}
@@ -1398,6 +1423,7 @@ void TorrentHandle::setTrackerLogin(const QString &username, const QString &pass
void TorrentHandle::renameFile(int index, const QString &name)
{
m_oldPath[LTFileIndex {index}].push_back(filePath(index));
++m_renameCount;
qDebug() << Q_FUNC_INFO << index << name;
m_nativeHandle.rename_file(index, Utils::Fs::toNativePath(name).toStdString());
@@ -1419,16 +1445,6 @@ bool TorrentHandle::saveTorrentFile(const QString &path)
return false;
}
void TorrentHandle::setFilePriority(int index, int priority)
{
std::vector<int> priorities = m_nativeHandle.file_priorities();
if ((priorities.size() > static_cast<quint64>(index)) && (priorities[index] != priority)) {
priorities[index] = priority;
prioritizeFiles(QVector<int>::fromStdVector(priorities));
}
}
void TorrentHandle::handleStateUpdate(const libt::torrent_status &nativeStatus)
{
updateStatus(nativeStatus);
@@ -1553,21 +1569,39 @@ void TorrentHandle::handleTrackerErrorAlert(const libtorrent::tracker_error_aler
void TorrentHandle::handleTorrentCheckedAlert(const libtorrent::torrent_checked_alert *p)
{
Q_UNUSED(p);
qDebug("%s have just finished checking", qUtf8Printable(hash()));
qDebug("\"%s\" have just finished checking", qUtf8Printable(name()));
if (m_startupState == Preparing) {
if (!m_pauseWhenReady) {
if (!m_hasMissingFiles) {
// Resume torrent because it was added in "resumed" state
// but it's actually paused during initialization.
m_startupState = Starting;
resume_impl(m_needsToStartForced);
}
else {
// Torrent that has missing files is paused.
m_startupState = Started;
}
}
else {
m_startupState = Started;
m_pauseWhenReady = false;
if (m_fastresumeDataRejected && !m_hasMissingFiles)
saveResumeData();
}
}
updateStatus();
if ((progress() < 1.0) && (wantedSize() > 0))
m_hasSeedStatus = false;
else if (progress() == 1.0)
m_hasSeedStatus = true;
if (!m_hasMissingFiles) {
if ((progress() < 1.0) && (wantedSize() > 0))
m_hasSeedStatus = false;
else if (progress() == 1.0)
m_hasSeedStatus = true;
adjustActualSavePath();
manageIncompleteFiles();
if (m_pauseAfterRecheck) {
m_pauseAfterRecheck = false;
pause();
adjustActualSavePath();
manageIncompleteFiles();
}
m_session->handleTorrentChecked(this);
@@ -1576,25 +1610,25 @@ void TorrentHandle::handleTorrentCheckedAlert(const libtorrent::torrent_checked_
void TorrentHandle::handleTorrentFinishedAlert(const libtorrent::torrent_finished_alert *p)
{
Q_UNUSED(p);
qDebug("Got a torrent finished alert for %s", qUtf8Printable(name()));
qDebug("Got a torrent finished alert for \"%s\"", qUtf8Printable(name()));
qDebug("Torrent has seed status: %s", m_hasSeedStatus ? "yes" : "no");
m_hasMissingFiles = false;
if (m_hasSeedStatus) return;
updateStatus();
m_hasMissingFiles = false;
m_hasSeedStatus = true;
adjustActualSavePath();
manageIncompleteFiles();
const bool recheckTorrentsOnCompletion = Preferences::instance()->recheckTorrentsOnCompletion();
if (isMoveInProgress() || m_renameCount > 0) {
if (isMoveInProgress() || (m_renameCount > 0)) {
if (recheckTorrentsOnCompletion)
m_moveFinishedTriggers.append(boost::bind(&TorrentHandle::forceRecheck, this));
m_moveFinishedTriggers.append(boost::bind(&Session::handleTorrentFinished, m_session, this));
}
else {
if (recheckTorrentsOnCompletion)
if (recheckTorrentsOnCompletion && m_unchecked)
forceRecheck();
m_session->handleTorrentFinished(this);
}
@@ -1603,19 +1637,27 @@ void TorrentHandle::handleTorrentFinishedAlert(const libtorrent::torrent_finishe
void TorrentHandle::handleTorrentPausedAlert(const libtorrent::torrent_paused_alert *p)
{
Q_UNUSED(p);
updateStatus();
m_speedMonitor.reset();
m_session->handleTorrentPaused(this);
if (m_startupState == Started) {
if (!m_pauseWhenReady) {
updateStatus();
m_speedMonitor.reset();
m_session->handleTorrentPaused(this);
}
else {
m_pauseWhenReady = false;
}
}
}
void TorrentHandle::handleTorrentResumedAlert(const libtorrent::torrent_resumed_alert *p)
{
Q_UNUSED(p);
if (m_started)
if (m_startupState == Started)
m_session->handleTorrentResumed(this);
else
m_started = true;
else if (m_startupState == Starting)
m_startupState = Started;
}
void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data_alert *p)
@@ -1626,8 +1668,8 @@ void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data
libtorrent::entry &resumeData = useDummyResumeData ? dummyEntry : *(p->resume_data);
if (useDummyResumeData) {
resumeData["qBt-magnetUri"] = toMagnetUri().toStdString();
resumeData["qBt-paused"] = isPaused();
resumeData["qBt-forced"] = isForced();
resumeData["paused"] = isPaused();
resumeData["auto_managed"] = m_nativeStatus.auto_managed;
// Both firstLastPiecePriority and sequential need to be stored in the
// resume data if there is no metadata, otherwise they won't be
// restored if qBittorrent quits before the metadata are retrieved:
@@ -1646,9 +1688,17 @@ void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data
resumeData["qBt-name"] = m_name.toStdString();
resumeData["qBt-seedStatus"] = m_hasSeedStatus;
resumeData["qBt-tempPathDisabled"] = m_tempPathDisabled;
resumeData["qBt-queuePosition"] = queuePosition();
resumeData["qBt-queuePosition"] = (nativeHandle().queue_position() + 1); // qBt starts queue at 1
resumeData["qBt-hasRootFolder"] = m_hasRootFolder;
if (m_pauseWhenReady) {
// We need to redefine these values when torrent starting/rechecking
// in "paused" state since native values can be logically wrong
// (torrent can be not paused and auto_managed when it is checking).
resumeData["paused"] = true;
resumeData["auto_managed"] = false;
}
m_session->handleTorrentResumeDataReady(this, resumeData);
}
@@ -1656,74 +1706,100 @@ void TorrentHandle::handleSaveResumeDataFailedAlert(const libtorrent::save_resum
{
// if torrent has no metadata we should save dummy fastresume data
// containing Magnet URI and qBittorrent own resume data only
if (p->error.value() == libt::errors::no_metadata)
if (p->error.value() == libt::errors::no_metadata) {
handleSaveResumeDataAlert(nullptr);
else
}
else {
LogMsg(tr("Save resume data failed. Torrent: \"%1\", error: \"%2\"")
.arg(name(), QString::fromLocal8Bit(p->error.message().c_str())), Log::CRITICAL);
m_session->handleTorrentResumeDataFailed(this);
}
}
void TorrentHandle::handleFastResumeRejectedAlert(const libtorrent::fastresume_rejected_alert *p)
{
qDebug("/!\\ Fast resume failed for %s, reason: %s", qUtf8Printable(name()), p->message().c_str());
m_fastresumeDataRejected = true;
updateStatus();
if (p->error.value() == libt::errors::mismatching_file_size) {
// Mismatching file size (files were probably moved)
LogMsg(tr("File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL);
m_hasMissingFiles = true;
if (!isPaused())
pause();
LogMsg(tr("File sizes mismatch for torrent '%1', pausing it.").arg(name()), Log::CRITICAL);
}
else {
LogMsg(tr("Fast resume data was rejected for torrent '%1'. Reason: %2. Checking again...")
.arg(name(), QString::fromStdString(p->message())), Log::CRITICAL);
.arg(name(), QString::fromStdString(p->message())), Log::WARNING);
}
}
void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert *p)
{
#if LIBTORRENT_VERSION_NUM < 10100
QString newName = Utils::Fs::fromNativePath(QString::fromStdString(p->name));
// We don't really need to call updateStatus() in this place.
// All we need to do is make sure we have a valid instance of the TorrentInfo object.
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
// remove empty leftover folders
// for example renaming "a/b/c" to "d/b/c", then folders "a/b" and "a" will
// be removed if they are empty
const QString oldFilePath = m_oldPath[LTFileIndex {p->index}].takeFirst();
const QString newFilePath = Utils::Fs::fromNativePath(p->new_name());
if (m_oldPath[LTFileIndex {p->index}].isEmpty())
m_oldPath.remove(LTFileIndex {p->index});
QVector<QStringRef> oldPathParts = oldFilePath.splitRef('/', QString::SkipEmptyParts);
oldPathParts.removeLast(); // drop file name part
QVector<QStringRef> newPathParts = newFilePath.splitRef('/', QString::SkipEmptyParts);
newPathParts.removeLast(); // drop file name part
#if defined(Q_OS_WIN)
const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive;
#else
QString newName = Utils::Fs::fromNativePath(p->new_name());
const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive;
#endif
// TODO: Check this!
if (filesCount() > 1) {
// Check if folders were renamed
QStringList oldPathParts = m_torrentInfo.origFilePath(p->index).split('/');
oldPathParts.removeLast();
QString oldPath = oldPathParts.join('/');
QStringList newPathParts = newName.split('/');
newPathParts.removeLast();
QString newPath = newPathParts.join('/');
if (!newPathParts.isEmpty() && (oldPath != newPath)) {
qDebug("oldPath(%s) != newPath(%s)", qUtf8Printable(oldPath), qUtf8Printable(newPath));
oldPath = QString("%1/%2").arg(savePath(true), oldPath);
qDebug("Detected folder renaming, attempt to delete old folder: %s", qUtf8Printable(oldPath));
QDir().rmpath(oldPath);
}
int pathIdx = 0;
while ((pathIdx < oldPathParts.size()) && (pathIdx < newPathParts.size())) {
if (oldPathParts[pathIdx].compare(newPathParts[pathIdx], caseSensitivity) != 0)
break;
++pathIdx;
}
updateStatus();
for (int i = (oldPathParts.size() - 1); i >= pathIdx; --i) {
QDir().rmdir(savePath() + Utils::String::join(oldPathParts, QLatin1String("/")));
oldPathParts.removeLast();
}
--m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()();
if (isPaused() && (m_renameCount == 0))
saveResumeData(); // otherwise the new path will not be saved
}
void TorrentHandle::handleFileRenameFailedAlert(const libtorrent::file_rename_failed_alert *p)
{
Q_UNUSED(p);
LogMsg(tr("File rename failed. Torrent: \"%1\", file: \"%2\", reason: \"%3\"")
.arg(name(), filePath(p->index)
, QString::fromLocal8Bit(p->error.message().c_str())), Log::WARNING);
m_oldPath[LTFileIndex {p->index}].removeFirst();
if (m_oldPath[LTFileIndex {p->index}].isEmpty())
m_oldPath.remove(LTFileIndex {p->index});
--m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
m_moveFinishedTriggers.takeFirst()();
if (isPaused() && (m_renameCount == 0))
saveResumeData(); // otherwise the new path will not be saved
}
void TorrentHandle::handleFileCompletedAlert(const libtorrent::file_completed_alert *p)
{
updateStatus();
// We don't really need to call updateStatus() in this place.
// All we need to do is make sure we have a valid instance of the TorrentInfo object.
m_torrentInfo = TorrentInfo {m_nativeHandle.torrent_file()};
qDebug("A file completed download in torrent \"%s\"", qUtf8Printable(name()));
if (m_session->isAppendExtensionEnabled()) {
@@ -1938,6 +2014,13 @@ void TorrentHandle::updateStatus(const libtorrent::torrent_status &nativeStatus)
updateState();
updateTorrentInfo();
// NOTE: Don't change the order of these conditionals!
// Otherwise it will not work properly since torrent can be CheckingDownloading.
if (isChecking())
m_unchecked = false;
else if (isDownloading())
m_unchecked = true;
}
void TorrentHandle::setRatioLimit(qreal limit)
@@ -1979,8 +2062,6 @@ void TorrentHandle::setDownloadLimit(int limit)
void TorrentHandle::setSuperSeeding(bool enable)
{
m_nativeHandle.super_seeding(enable);
if (superSeeding() != enable)
updateStatus();
}
void TorrentHandle::flushCache()
@@ -2039,7 +2120,7 @@ void TorrentHandle::prioritizeFiles(const QVector<int> &priorities)
if (created) {
// Hide the folder on Windows
qDebug() << "Hiding folder (Windows)";
std::wstring winPath = Utils::Fs::toNativePath(unwantedAbsPath).toStdWString();
std::wstring winPath = Utils::Fs::toNativePath(unwantedAbsPath).toStdWString();
DWORD dwAttrs = ::GetFileAttributesW(winPath.c_str());
bool ret = ::SetFileAttributesW(winPath.c_str(), dwAttrs | FILE_ATTRIBUTE_HIDDEN);
Q_ASSERT(ret != 0); Q_UNUSED(ret);
@@ -2073,13 +2154,11 @@ void TorrentHandle::prioritizeFiles(const QVector<int> &priorities)
// Restore first/last piece first option if necessary
if (firstLastPieceFirst)
setFirstLastPiecePriorityImpl(true, priorities);
updateStatus();
}
QVector<qreal> TorrentHandle::availableFileFractions() const
{
const auto filesCount = this->filesCount();
const int filesCount = this->filesCount();
if (filesCount < 0) return {};
const QVector<int> piecesAvailability = pieceAvailability();
@@ -2088,12 +2167,13 @@ QVector<qreal> TorrentHandle::availableFileFractions() const
QVector<qreal> res;
res.reserve(filesCount);
TorrentInfo info = this->info();
for (int file = 0; file < filesCount; ++file) {
TorrentInfo::PieceRange filePieces = info.filePieces(file);
const TorrentInfo info = this->info();
for (int i = 0; i < filesCount; ++i) {
const TorrentInfo::PieceRange filePieces = info.filePieces(i);
int availablePieces = 0;
for (int piece = filePieces.first(); piece <= filePieces.last(); ++piece) {
availablePieces += piecesAvailability[piece] > 0 ? 1 : 0;
availablePieces += (piecesAvailability[piece] > 0) ? 1 : 0;
}
res.push_back(static_cast<qreal>(availablePieces) / filePieces.size());
}

View File

@@ -158,6 +158,7 @@ namespace BitTorrent
class TorrentHandle : public QObject
{
Q_DISABLE_COPY(TorrentHandle)
Q_DECLARE_TR_FUNCTIONS(BitTorrent::TorrentHandle)
public:
static const qreal USE_GLOBAL_RATIO;
@@ -347,7 +348,6 @@ namespace BitTorrent
void renameFile(int index, const QString &name);
bool saveTorrentFile(const QString &path);
void prioritizeFiles(const QVector<int> &priorities);
void setFilePriority(int index, int priority);
void setRatioLimit(qreal limit);
void setSeedingTimeLimit(int limit);
void setUploadLimit(int limit);
@@ -355,7 +355,7 @@ namespace BitTorrent
void setSuperSeeding(bool enable);
void flushCache();
void addTrackers(const QList<TrackerEntry> &trackers);
void replaceTrackers(QList<TrackerEntry> trackers);
void replaceTrackers(const QList<TrackerEntry> &trackers);
void addUrlSeeds(const QList<QUrl> &urlSeeds);
void removeUrlSeeds(const QList<QUrl> &urlSeeds);
bool connectPeer(const PeerAddress &peerAddress);
@@ -372,7 +372,7 @@ namespace BitTorrent
void handleTempPathChanged();
void handleCategorySavePathChanged();
void handleAppendExtensionToggled();
void saveResumeData(bool updateStatus = false);
void saveResumeData();
/**
* @brief fraction of file pieces that are available at least from one peer
@@ -385,6 +385,12 @@ namespace BitTorrent
private:
typedef boost::function<void ()> EventTrigger;
#if (LIBTORRENT_VERSION_NUM < 10200)
using LTFileIndex = int;
#else
using LTFileIndex = lt::file_index_t;
#endif
void updateStatus();
void updateStatus(const libtorrent::torrent_status &nativeStatus);
void updateState();
@@ -408,7 +414,7 @@ namespace BitTorrent
void handleMetadataReceivedAlert(const libtorrent::metadata_received_alert *p);
void handleStatsAlert(const libtorrent::stats_alert *p);
void resume_impl(bool forced, bool uploadMode);
void resume_impl(bool forced);
bool isMoveInProgress() const;
QString nativeActualSavePath() const;
@@ -446,6 +452,10 @@ namespace BitTorrent
QQueue<EventTrigger> m_moveFinishedTriggers;
int m_renameCount;
// Until libtorrent provide an "old_name" field in `file_renamed_alert`
// we will rely on this workaround to remove empty leftover folders
QHash<LTFileIndex, QVector<QString>> m_oldPath;
bool m_useAutoTMM;
// Persistent data
@@ -457,14 +467,26 @@ namespace BitTorrent
qreal m_ratioLimit;
int m_seedingTimeLimit;
bool m_tempPathDisabled;
bool m_fastresumeDataRejected;
bool m_hasMissingFiles;
bool m_hasRootFolder;
bool m_needsToSetFirstLastPiecePriority;
bool m_needsToStartForced;
bool m_pauseAfterRecheck;
QHash<QString, TrackerInfo> m_trackerInfos;
bool m_started = false;
enum StartupState
{
Preparing, // torrent is preparing to start regular processing
Starting, // torrent is prepared and starting to perform regular processing
Started // torrent is performing regular processing
};
StartupState m_startupState = Preparing;
// Handle torrent state when it starts performing some service job
// being in Paused state so it might be unpaused internally and then paused again
bool m_pauseWhenReady;
bool m_unchecked = false;
};
}

View File

@@ -35,6 +35,7 @@
#include <QString>
#include <QUrl>
#include "base/global.h"
#include "base/utils/fs.h"
#include "base/utils/misc.h"
#include "base/utils/string.h"
@@ -105,10 +106,9 @@ TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexc
return TorrentInfo();
}
const qint64 fileSizeLimit = 100 * 1024 * 1024; // 100 MB
if (file.size() > fileSizeLimit) {
if (file.size() > MAX_TORRENT_SIZE) {
if (error)
*error = tr("File size exceeds max limit %1").arg(fileSizeLimit);
*error = tr("File size exceeds max limit %1").arg(Utils::Misc::friendlyUnit(MAX_TORRENT_SIZE));
return TorrentInfo();
}
@@ -247,7 +247,7 @@ QList<TrackerEntry> TorrentInfo::trackers() const
if (!isValid()) return QList<TrackerEntry>();
QList<TrackerEntry> trackers;
foreach (const libt::announce_entry &tracker, m_nativeInfo->trackers())
for (const libt::announce_entry &tracker : m_nativeInfo->trackers())
trackers.append(tracker);
return trackers;
@@ -258,7 +258,7 @@ QList<QUrl> TorrentInfo::urlSeeds() const
if (!isValid()) return QList<QUrl>();
QList<QUrl> urlSeeds;
foreach (const libt::web_seed_entry &webSeed, m_nativeInfo->web_seeds())
for (const libt::web_seed_entry &webSeed : m_nativeInfo->web_seeds())
if (webSeed.type == libt::web_seed_entry::url_seed)
urlSeeds.append(QUrl(webSeed.url.c_str()));
@@ -339,18 +339,23 @@ TorrentInfo::PieceRange TorrentInfo::filePieces(int fileIndex) const
const libt::file_storage &files = nativeInfo()->files();
const auto fileSize = files.file_size(fileIndex);
const auto firstOffset = files.file_offset(fileIndex);
return makeInterval(static_cast<int>(firstOffset / pieceLength()),
static_cast<int>((firstOffset + fileSize - 1) / pieceLength()));
const auto fileOffset = files.file_offset(fileIndex);
const int beginIdx = (fileOffset / pieceLength());
const int endIdx = ((fileOffset + fileSize - 1) / pieceLength());
if (fileSize <= 0)
return {beginIdx, 0};
return makeInterval(beginIdx, endIdx);
}
void TorrentInfo::renameFile(uint index, const QString &newPath)
void TorrentInfo::renameFile(const int index, const QString &newPath)
{
if (!isValid()) return;
nativeInfo()->rename_file(index, Utils::Fs::toNativePath(newPath).toStdString());
}
int BitTorrent::TorrentInfo::fileIndex(const QString& fileName) const
int BitTorrent::TorrentInfo::fileIndex(const QString &fileName) const
{
// the check whether the object is valid is not needed here
// because if filesCount() returns -1 the loop exits immediately

View File

@@ -102,7 +102,7 @@ namespace BitTorrent
PieceRange filePieces(const QString &file) const;
PieceRange filePieces(int fileIndex) const;
void renameFile(uint index, const QString &newPath);
void renameFile(int index, const QString &newPath);
QString rootFolder() const;
bool hasRootFolder() const;

View File

@@ -29,16 +29,11 @@
#include "tracker.h"
#include <vector>
#include <libtorrent/bencode.hpp>
#include <libtorrent/entry.hpp>
#include "base/global.h"
#include "base/http/server.h"
#include "base/preferences.h"
#include "base/utils/bytearray.h"
#include "base/utils/string.h"
// static limits
static const int MAX_TORRENTS = 100;
@@ -60,7 +55,7 @@ bool Peer::operator==(const Peer &other) const
QString Peer::uid() const
{
return ip + ':' + QString::number(port);
return ip.toString() + ':' + QString::number(port);
}
libtorrent::entry Peer::toEntry(bool noPeerId) const
@@ -68,7 +63,7 @@ libtorrent::entry Peer::toEntry(bool noPeerId) const
libtorrent::entry::dictionary_type peerMap;
if (!noPeerId)
peerMap["id"] = libtorrent::entry(peerId.toStdString());
peerMap["ip"] = libtorrent::entry(ip.toStdString());
peerMap["ip"] = libtorrent::entry(ip.toString().toStdString());
peerMap["port"] = libtorrent::entry(port);
return libtorrent::entry(peerMap);
@@ -133,22 +128,13 @@ Http::Response Tracker::processRequest(const Http::Request &request, const Http:
void Tracker::respondToAnnounceRequest()
{
QMap<QString, QByteArray> queryParams;
// Parse GET parameters
using namespace Utils::ByteArray;
for (const QByteArray &param : copyAsConst(splitToViews(m_request.query, "&"))) {
const int sepPos = param.indexOf('=');
if (sepPos <= 0) continue; // ignores params without name
const QString paramName {QString::fromUtf8(param.constData(), sepPos)};
const QByteArray paramValue {param.mid(sepPos + 1)};
queryParams[paramName] = paramValue;
}
TrackerAnnounceRequest annonceReq;
const QMap<QString, QByteArray> &queryParams = m_request.query;
TrackerAnnounceRequest announceReq;
// IP
annonceReq.peer.ip = m_env.clientAddress.toString();
// Use the "ip" parameter provided from tracker request first, then fall back to client IP if invalid
const QHostAddress paramIP {QString::fromLatin1(queryParams.value("ip"))};
announceReq.peer.ip = paramIP.isNull() ? m_env.clientAddress : paramIP;
// 1. Get info_hash
if (!queryParams.contains("info_hash")) {
@@ -156,7 +142,7 @@ void Tracker::respondToAnnounceRequest()
status(101, "Missing info_hash");
return;
}
annonceReq.infoHash = queryParams.value("info_hash");
announceReq.infoHash = queryParams.value("info_hash");
// info_hash cannot be longer than 20 bytes
/*if (annonce_req.info_hash.toLatin1().length() > 20) {
qDebug("Tracker: Info_hash is not 20 byte long: %s (%d)", qUtf8Printable(annonce_req.info_hash), annonce_req.info_hash.toLatin1().length());
@@ -170,7 +156,7 @@ void Tracker::respondToAnnounceRequest()
status(102, "Missing peer_id");
return;
}
annonceReq.peer.peerId = queryParams.value("peer_id");
announceReq.peer.peerId = queryParams.value("peer_id");
// peer_id cannot be longer than 20 bytes
/*if (annonce_req.peer.peer_id.length() > 20) {
qDebug("Tracker: peer_id is not 20 byte long: %s", qUtf8Printable(annonce_req.peer.peer_id));
@@ -185,52 +171,52 @@ void Tracker::respondToAnnounceRequest()
return;
}
bool ok = false;
annonceReq.peer.port = queryParams.value("port").toInt(&ok);
if (!ok || (annonceReq.peer.port < 0) || (annonceReq.peer.port > 65535)) {
qDebug("Tracker: Invalid port number (%d)", annonceReq.peer.port);
announceReq.peer.port = queryParams.value("port").toInt(&ok);
if (!ok || (announceReq.peer.port < 0) || (announceReq.peer.port > 65535)) {
qDebug("Tracker: Invalid port number (%d)", announceReq.peer.port);
status(103, "Missing port");
return;
}
// 4. Get event
annonceReq.event = "";
announceReq.event = "";
if (queryParams.contains("event")) {
annonceReq.event = queryParams.value("event");
qDebug("Tracker: event is %s", qUtf8Printable(annonceReq.event));
announceReq.event = queryParams.value("event");
qDebug("Tracker: event is %s", qUtf8Printable(announceReq.event));
}
// 5. Get numwant
annonceReq.numwant = 50;
announceReq.numwant = 50;
if (queryParams.contains("numwant")) {
int tmp = queryParams.value("numwant").toInt();
if (tmp > 0) {
qDebug("Tracker: numwant = %d", tmp);
annonceReq.numwant = tmp;
announceReq.numwant = tmp;
}
}
// 6. no_peer_id (extension)
annonceReq.noPeerId = false;
announceReq.noPeerId = false;
if (queryParams.contains("no_peer_id"))
annonceReq.noPeerId = true;
announceReq.noPeerId = true;
// 7. TODO: support "compact" extension
// Done parsing, now let's reply
if (annonceReq.event == "stopped") {
unregisterPeer(annonceReq);
if (announceReq.event == "stopped") {
unregisterPeer(announceReq);
}
else {
registerPeer(annonceReq);
replyWithPeerList(annonceReq);
registerPeer(announceReq);
replyWithPeerList(announceReq);
}
}
void Tracker::registerPeer(const TrackerAnnounceRequest &annonceReq)
void Tracker::registerPeer(const TrackerAnnounceRequest &announceReq)
{
if (annonceReq.peer.port == 0) return;
if (announceReq.peer.port == 0) return;
if (!m_torrents.contains(annonceReq.infoHash)) {
if (!m_torrents.contains(announceReq.infoHash)) {
// Unknown torrent
if (m_torrents.size() == MAX_TORRENTS) {
// Reached max size, remove a random torrent
@@ -239,34 +225,34 @@ void Tracker::registerPeer(const TrackerAnnounceRequest &annonceReq)
}
// Register the user
PeerList &peers = m_torrents[annonceReq.infoHash];
if (!peers.contains(annonceReq.peer.uid())) {
PeerList &peers = m_torrents[announceReq.infoHash];
if (!peers.contains(announceReq.peer.uid())) {
// Unknown peer
if (peers.size() == MAX_PEERS_PER_TORRENT) {
// Too many peers, remove a random one
peers.erase(peers.begin());
}
}
peers[annonceReq.peer.uid()] = annonceReq.peer;
peers[announceReq.peer.uid()] = announceReq.peer;
}
void Tracker::unregisterPeer(const TrackerAnnounceRequest &annonceReq)
void Tracker::unregisterPeer(const TrackerAnnounceRequest &announceReq)
{
if (annonceReq.peer.port == 0) return;
if (announceReq.peer.port == 0) return;
if (m_torrents[annonceReq.infoHash].remove(annonceReq.peer.uid()) > 0)
if (m_torrents[announceReq.infoHash].remove(announceReq.peer.uid()) > 0)
qDebug("Tracker: Peer stopped downloading, deleting it from the list");
}
void Tracker::replyWithPeerList(const TrackerAnnounceRequest &annonceReq)
void Tracker::replyWithPeerList(const TrackerAnnounceRequest &announceReq)
{
// Prepare the entry for bencoding
libtorrent::entry::dictionary_type replyDict;
replyDict["interval"] = libtorrent::entry(ANNOUNCE_INTERVAL);
libtorrent::entry::list_type peerList;
for (const Peer &p : m_torrents.value(annonceReq.infoHash))
peerList.push_back(p.toEntry(annonceReq.noPeerId));
for (const Peer &p : m_torrents.value(announceReq.infoHash))
peerList.push_back(p.toEntry(announceReq.noPeerId));
replyDict["peers"] = libtorrent::entry(peerList);
const libtorrent::entry replyEntry(replyDict);

View File

@@ -32,10 +32,10 @@
#include <QHash>
#include <QObject>
#include <QHostAddress>
#include "base/http/irequesthandler.h"
#include "base/http/responsebuilder.h"
#include "base/http/types.h"
namespace libtorrent
{
@@ -51,7 +51,7 @@ namespace BitTorrent
{
struct Peer
{
QString ip;
QHostAddress ip;
QByteArray peerId;
int port;
@@ -90,9 +90,9 @@ namespace BitTorrent
private:
void respondToAnnounceRequest();
void registerPeer(const TrackerAnnounceRequest &annonceReq);
void unregisterPeer(const TrackerAnnounceRequest &annonceReq);
void replyWithPeerList(const TrackerAnnounceRequest &annonceReq);
void registerPeer(const TrackerAnnounceRequest &announceReq);
void unregisterPeer(const TrackerAnnounceRequest &announceReq);
void replyWithPeerList(const TrackerAnnounceRequest &announceReq);
Http::Server *m_server;
TorrentList m_torrents;

View File

@@ -44,10 +44,10 @@ namespace BitTorrent
public:
enum Status
{
NotContacted,
Working,
Updating,
NotWorking
NotContacted = 1,
Working = 2,
Updating = 3,
NotWorking = 4
};
TrackerEntry(const QString &url);

View File

@@ -30,11 +30,10 @@
RuntimeError::RuntimeError(const QString &message)
: std::runtime_error {message.toUtf8().data()}
, m_message {message}
{
}
QString RuntimeError::message() const
{
return m_message;
return what();
}

View File

@@ -36,7 +36,4 @@ class RuntimeError : public std::runtime_error
public:
explicit RuntimeError(const QString &message = "");
QString message() const;
private:
const QString m_message;
};

View File

@@ -30,7 +30,7 @@
#include <QtGlobal>
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
#if defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
#include <cstring>
#include <sys/mount.h>
#include <sys/param.h>
@@ -40,6 +40,7 @@
#include "base/bittorrent/magneturi.h"
#include "base/bittorrent/torrentinfo.h"
#include "base/global.h"
#include "base/logger.h"
#include "base/preferences.h"
#include "base/utils/fs.h"
@@ -57,18 +58,14 @@ FileSystemWatcher::FileSystemWatcher(QObject *parent)
m_partialTorrentTimer.setSingleShot(true);
connect(&m_partialTorrentTimer, &QTimer::timeout, this, &FileSystemWatcher::processPartialTorrents);
#ifndef Q_OS_WIN
connect(&m_watchTimer, &QTimer::timeout, this, &FileSystemWatcher::scanNetworkFolders);
#endif
}
QStringList FileSystemWatcher::directories() const
{
QStringList dirs = QFileSystemWatcher::directories();
#ifndef Q_OS_WIN
for (const QDir &dir : qAsConst(m_watchedFolders))
for (const QDir &dir : asConst(m_watchedFolders))
dirs << dir.canonicalPath();
#endif
return dirs;
}
@@ -76,15 +73,14 @@ void FileSystemWatcher::addPath(const QString &path)
{
if (path.isEmpty()) return;
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
#if !defined Q_OS_HAIKU
const QDir dir(path);
if (!dir.exists()) return;
// Check if the path points to a network file system or not
if (Utils::Fs::isNetworkFileSystem(path)) {
// Network mode
qDebug("Network folder detected: %s", qUtf8Printable(path));
qDebug("Using file polling mode instead of inotify...");
LogMsg(tr("Watching remote folder: \"%1\"").arg(Utils::Fs::toNativePath(path)));
m_watchedFolders << dir;
m_watchTimer.start(WATCH_INTERVAL);
@@ -93,20 +89,19 @@ void FileSystemWatcher::addPath(const QString &path)
#endif
// Normal mode
qDebug("FS Watcher is watching %s in normal mode", qUtf8Printable(path));
LogMsg(tr("Watching local folder: \"%1\"").arg(Utils::Fs::toNativePath(path)));
QFileSystemWatcher::addPath(path);
scanLocalFolder(path);
}
void FileSystemWatcher::removePath(const QString &path)
{
#ifndef Q_OS_WIN
if (m_watchedFolders.removeOne(path)) {
if (m_watchedFolders.isEmpty())
m_watchTimer.stop();
return;
}
#endif
// Normal mode
QFileSystemWatcher::removePath(path);
}
@@ -116,13 +111,11 @@ void FileSystemWatcher::scanLocalFolder(const QString &path)
QTimer::singleShot(2000, this, [this, path]() { processTorrentsInDir(path); });
}
#ifndef Q_OS_WIN
void FileSystemWatcher::scanNetworkFolders()
{
for (const QDir &dir : qAsConst(m_watchedFolders))
for (const QDir &dir : asConst(m_watchedFolders))
processTorrentsInDir(dir);
}
#endif
void FileSystemWatcher::processPartialTorrents()
{

View File

@@ -56,9 +56,7 @@ signals:
protected slots:
void scanLocalFolder(const QString &path);
void processPartialTorrents();
#ifndef Q_OS_WIN
void scanNetworkFolders();
#endif
private:
void processTorrentsInDir(const QDir &dir);
@@ -67,10 +65,8 @@ private:
QHash<QString, int> m_partialTorrents;
QTimer m_partialTorrentTimer;
#ifndef Q_OS_WIN
QList<QDir> m_watchedFolders;
QTimer m_watchTimer;
#endif
};
#endif // FILESYSTEMWATCHER_H

View File

@@ -32,17 +32,15 @@
#include <QtGlobal>
const char C_TORRENT_FILE_EXTENSION[] = ".torrent";
const int MAX_TORRENT_SIZE = 100 * 1024 * 1024; // 100 MiB
#if QT_VERSION < QT_VERSION_CHECK(5, 7, 0)
template <typename T>
constexpr typename std::add_const<T>::type &qAsConst(T &t) noexcept { return t; }
constexpr typename std::add_const<T>::type &asConst(T &t) noexcept { return t; }
// prevent rvalue arguments:
// Forward rvalue as const
template <typename T>
void qAsConst(const T &&) = delete;
#endif
constexpr typename std::add_const<T>::type asConst(T &&t) noexcept { return std::move(t); }
// returns a const object copy
// Prevent const rvalue arguments
template <typename T>
constexpr typename std::add_const<T>::type copyAsConst(T &&t) noexcept { return std::move(t); }
void asConst(const T &&) = delete;

View File

@@ -66,7 +66,7 @@ void Connection::read()
case RequestParser::ParseStatus::Incomplete: {
const long bufferLimit = RequestParser::MAX_CONTENT_SIZE * 1.1; // some margin for headers
if (m_receivedData.size() > bufferLimit) {
Logger::instance()->addMessage(tr("Http request size exceeds limiation, closing socket. Limit: %ld, IP: %s")
Logger::instance()->addMessage(tr("Http request size exceeds limiation, closing socket. Limit: %1, IP: %2")
.arg(bufferLimit).arg(m_socket->peerAddress().toString()), Log::WARNING);
Response resp(413, "Payload Too Large");
@@ -79,7 +79,7 @@ void Connection::read()
return;
case RequestParser::ParseStatus::BadRequest: {
Logger::instance()->addMessage(tr("Bad Http request, closing socket. IP: %s")
Logger::instance()->addMessage(tr("Bad Http request, closing socket. IP: %1")
.arg(m_socket->peerAddress().toString()), Log::WARNING);
Response resp(400, "Bad Request");
@@ -133,7 +133,7 @@ bool Connection::acceptsGzipEncoding(QString codings)
const auto isCodingAvailable = [](const QStringList &list, const QString &encoding) -> bool
{
foreach (const QString &str, list) {
for (const QString &str : list) {
if (!str.startsWith(encoding))
continue;

View File

@@ -36,6 +36,7 @@
#include <QUrl>
#include <QUrlQuery>
#include "base/global.h"
#include "base/utils/bytearray.h"
#include "base/utils/string.h"
@@ -180,11 +181,29 @@ bool RequestParser::parseRequestLine(const QString &line)
m_request.method = match.captured(1);
// Request Target
const QByteArray decodedUrl {QByteArray::fromPercentEncoding(match.captured(2).toLatin1())};
const int sepPos = decodedUrl.indexOf('?');
m_request.path = QString::fromUtf8(decodedUrl.constData(), (sepPos == -1 ? decodedUrl.size() : sepPos));
if (sepPos >= 0)
m_request.query = decodedUrl.mid(sepPos + 1);
const QByteArray url {match.captured(2).toLatin1()};
const int sepPos = url.indexOf('?');
const QByteArray pathComponent = ((sepPos == -1) ? url : midView(url, 0, sepPos));
m_request.path = QString::fromUtf8(QByteArray::fromPercentEncoding(pathComponent));
if (sepPos >= 0) {
const QByteArray query = midView(url, (sepPos + 1));
// [rfc3986] 2.4 When to Encode or Decode
// URL components should be separated before percent-decoding
for (const QByteArray &param : asConst(splitToViews(query, "&"))) {
const int eqCharPos = param.indexOf('=');
if (eqCharPos <= 0) continue; // ignores params without name
const QByteArray nameComponent = midView(param, 0, eqCharPos);
const QByteArray valueComponent = midView(param, (eqCharPos + 1));
const QString paramName = QString::fromUtf8(QByteArray::fromPercentEncoding(nameComponent).replace('+', ' '));
const QByteArray paramValue = QByteArray::fromPercentEncoding(valueComponent).replace('+', ' ');
m_request.query[paramName] = paramValue;
}
}
// HTTP-version
m_request.version = match.captured(3);
@@ -200,7 +219,10 @@ bool RequestParser::parsePostMessage(const QByteArray &data)
// application/x-www-form-urlencoded
if (contentTypeLower.startsWith(CONTENT_TYPE_FORM_ENCODED)) {
QListIterator<QStringPair> i(QUrlQuery(data).queryItems(QUrl::FullyDecoded));
// [URL Standard] 5.1 application/x-www-form-urlencoded parsing
const QByteArray processedData = QByteArray(data).replace('+', ' ');
QListIterator<QStringPair> i(QUrlQuery(processedData).queryItems(QUrl::FullyDecoded));
while (i.hasNext()) {
const QStringPair pair = i.next();
m_request.posts[pair.first] = pair.second;

View File

@@ -97,16 +97,23 @@ void Server::incomingConnection(qintptr socketDescriptor)
#endif
Connection *c = new Connection(serverSocket, m_requestHandler, this);
m_connections.append(c);
m_connections.insert(c);
connect(serverSocket, &QAbstractSocket::disconnected, this, [c, this]() { removeConnection(c); });
}
void Server::removeConnection(Connection *connection)
{
m_connections.remove(connection);
connection->deleteLater();
}
void Server::dropTimedOutConnection()
{
QMutableListIterator<Connection *> i(m_connections);
QMutableSetIterator<Connection *> i(m_connections);
while (i.hasNext()) {
auto connection = i.next();
if (connection->isClosed() || connection->hasExpired(KEEP_ALIVE_DURATION)) {
delete connection;
Connection *connection = i.next();
if (connection->hasExpired(KEEP_ALIVE_DURATION)) {
connection->deleteLater();
i.remove();
}
}
@@ -146,9 +153,9 @@ QList<QSslCipher> Server::safeCipherList() const
const QStringList badCiphers = {"idea", "rc4"};
const QList<QSslCipher> allCiphers = QSslSocket::supportedCiphers();
QList<QSslCipher> safeCiphers;
foreach (const QSslCipher &cipher, allCiphers) {
for (const QSslCipher &cipher : allCiphers) {
bool isSafe = true;
foreach (const QString &badCipher, badCiphers) {
for (const QString &badCipher : badCiphers) {
if (cipher.name().contains(badCipher, Qt::CaseInsensitive)) {
isSafe = false;
break;

View File

@@ -31,6 +31,7 @@
#ifndef HTTP_SERVER_H
#define HTTP_SERVER_H
#include <QSet>
#include <QTcpServer>
#ifndef QT_NO_OPENSSL
@@ -63,9 +64,10 @@ namespace Http
private:
void incomingConnection(qintptr socketDescriptor);
void removeConnection(Connection *connection);
IRequestHandler *m_requestHandler;
QList<Connection *> m_connections; // for tracking persistent connections
QSet<Connection *> m_connections; // for tracking persistent connections
#ifndef QT_NO_OPENSSL
QList<QSslCipher> safeCipherList() const;

View File

@@ -52,6 +52,7 @@ namespace Http
const char HEADER_HOST[] = "host";
const char HEADER_ORIGIN[] = "origin";
const char HEADER_REFERER[] = "referer";
const char HEADER_REFERRER_POLICY[] = "referrer-policy";
const char HEADER_SET_COOKIE[] = "set-cookie";
const char HEADER_X_CONTENT_TYPE_OPTIONS[] = "x-content-type-options";
const char HEADER_X_FORWARDED_HOST[] = "x-forwarded-host";
@@ -96,8 +97,8 @@ namespace Http
QString version;
QString method;
QString path;
QByteArray query;
QStringMap headers;
QMap<QString, QByteArray> query;
QStringMap posts;
QVector<UploadedFile> files;
};
@@ -107,7 +108,7 @@ namespace Http
uint code;
QString text;
ResponseStatus(uint code = 200, const QString& text = "OK"): code(code), text(text) {}
ResponseStatus(uint code = 200, const QString &text = "OK"): code(code), text(text) {}
};
struct Response
@@ -116,7 +117,7 @@ namespace Http
QStringMap headers;
QByteArray content;
Response(uint code = 200, const QString& text = "OK"): status(code, text) {}
Response(uint code = 200, const QString &text = "OK"): status(code, text) {}
};
}

View File

@@ -29,6 +29,8 @@
#include "iconprovider.h"
#include <QFileInfo>
IconProvider::IconProvider(QObject *parent)
: QObject(parent)
{
@@ -57,7 +59,13 @@ IconProvider *IconProvider::instance()
QString IconProvider::getIconPath(const QString &iconId) const
{
return ":/icons/qbt-theme/" + iconId + ".png";
// there are a few icons not available in svg
const QString pathSvg = ":/icons/qbt-theme/" + iconId + ".svg";
if (QFileInfo::exists(pathSvg))
return pathSvg;
const QString pathPng = ":/icons/qbt-theme/" + iconId + ".png";
return pathPng;
}
IconProvider *IconProvider::m_instance = nullptr;

View File

@@ -39,6 +39,7 @@
#include <QSslError>
#include <QUrl>
#include "base/global.h"
#include "base/preferences.h"
#include "downloadhandler.h"
#include "proxyconfigurationmanager.h"
@@ -56,7 +57,7 @@ namespace
{
QDateTime now = QDateTime::currentDateTime();
QList<QNetworkCookie> cookies = Preferences::instance()->getNetworkCookies();
foreach (const QNetworkCookie &cookie, Preferences::instance()->getNetworkCookies()) {
for (const QNetworkCookie &cookie : asConst(Preferences::instance()->getNetworkCookies())) {
if (cookie.isSessionCookie() || (cookie.expirationDate() <= now))
cookies.removeAll(cookie);
}
@@ -64,11 +65,11 @@ namespace
setAllCookies(cookies);
}
~NetworkCookieJar()
~NetworkCookieJar() override
{
QDateTime now = QDateTime::currentDateTime();
QList<QNetworkCookie> cookies = allCookies();
foreach (const QNetworkCookie &cookie, allCookies()) {
for (const QNetworkCookie &cookie : asConst(allCookies())) {
if (cookie.isSessionCookie() || (cookie.expirationDate() <= now))
cookies.removeAll(cookie);
}
@@ -83,7 +84,7 @@ namespace
{
QDateTime now = QDateTime::currentDateTime();
QList<QNetworkCookie> cookies = QNetworkCookieJar::cookiesForUrl(url);
foreach (const QNetworkCookie &cookie, QNetworkCookieJar::cookiesForUrl(url)) {
for (const QNetworkCookie &cookie : asConst(QNetworkCookieJar::cookiesForUrl(url))) {
if (!cookie.isSessionCookie() && (cookie.expirationDate() <= now))
cookies.removeAll(cookie);
}
@@ -95,7 +96,7 @@ namespace
{
QDateTime now = QDateTime::currentDateTime();
QList<QNetworkCookie> cookies = cookieList;
foreach (const QNetworkCookie &cookie, cookieList) {
for (const QNetworkCookie &cookie : cookieList) {
if (!cookie.isSessionCookie() && (cookie.expirationDate() <= now))
cookies.removeAll(cookie);
}
@@ -244,7 +245,7 @@ void Net::DownloadManager::applyProxySettings()
void Net::DownloadManager::handleReplyFinished(QNetworkReply *reply)
{
const ServiceID id = ServiceID::fromURL(reply->url());
const ServiceID id = ServiceID::fromURL(reply->request().url());
auto waitingJobsIter = m_waitingJobs.find(id);
if ((waitingJobsIter == m_waitingJobs.end()) || waitingJobsIter.value().isEmpty()) {
m_busyServices.remove(id);

View File

@@ -92,12 +92,12 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
QFile file(filename);
if (file.size() > MAX_FILE_SIZE) {
error = tr("Unsupported database file size.");
return 0;
return nullptr;
}
if (!file.open(QFile::ReadOnly)) {
error = file.errorString();
return 0;
return nullptr;
}
db = new GeoIPDatabase(file.size());
@@ -105,13 +105,13 @@ GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
if (file.read(reinterpret_cast<char *>(db->m_data), db->m_size) != db->m_size) {
error = file.errorString();
delete db;
return 0;
return nullptr;
}
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
delete db;
return 0;
return nullptr;
}
return db;
@@ -122,7 +122,7 @@ GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
GeoIPDatabase *db = nullptr;
if (data.size() > MAX_FILE_SIZE) {
error = tr("Unsupported database file size.");
return 0;
return nullptr;
}
db = new GeoIPDatabase(data.size());
@@ -131,7 +131,7 @@ GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
if (!db->parseMetadata(db->readMetadata(), error) || !db->loadDB(error)) {
delete db;
return 0;
return nullptr;
}
return db;

View File

@@ -40,7 +40,7 @@ const QString KEY_PASSWORD = SETTINGS_KEY("Password");
namespace
{
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
inline bool isSameConfig(const Net::ProxyConfiguration &conf1, const Net::ProxyConfiguration &conf2)
{

View File

@@ -43,6 +43,7 @@
#include <QTcpSocket>
#endif
#include "base/global.h"
#include "base/logger.h"
#include "base/preferences.h"
@@ -291,7 +292,7 @@ QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, QTex
if (!prefix.isEmpty()) line += prefix;
if (!value.contains("=?") && latin1->canEncode(value)) {
bool firstWord = true;
foreach (const QByteArray& word, value.toLatin1().split(' ')) {
for (const QByteArray &word : asConst(value.toLatin1().split(' '))) {
if (line.size() > 78) {
rv = rv + line + "\r\n";
line.clear();

View File

@@ -43,7 +43,6 @@
#ifdef Q_OS_WIN
#include <shlobj.h>
#include <winreg.h>
#include <QRegularExpression>
#endif
@@ -51,6 +50,7 @@
#include <CoreServices/CoreServices.h>
#endif
#include "global.h"
#include "logger.h"
#include "settingsstorage.h"
#include "utils/fs.h"
@@ -92,7 +92,8 @@ void Preferences::setValue(const QString &key, const QVariant &value)
// General options
QString Preferences::getLocale() const
{
return value("Preferences/General/Locale", QLocale::system().name()).toString();
const QString localeName = value("Preferences/General/Locale").toString();
return (localeName.isEmpty() ? QLocale::system().name() : localeName);
}
void Preferences::setLocale(const QString &locale)
@@ -212,7 +213,7 @@ void Preferences::setCloseToTrayNotified(bool b)
{
setValue("Preferences/General/CloseToTrayNotified", b);
}
#endif
#endif // Q_OS_MAC
bool Preferences::isToolbarDisplayed() const
{
@@ -293,7 +294,7 @@ void Preferences::setWinStartup(bool b)
settings.remove("qBittorrent");
}
}
#endif
#endif // Q_OS_WIN
// Downloads
QString Preferences::lastLocationPath() const
@@ -505,7 +506,7 @@ void Preferences::setWebUiAuthSubnetWhitelistEnabled(bool enabled)
QList<Utils::Net::Subnet> Preferences::getWebUiAuthSubnetWhitelist() const
{
QList<Utils::Net::Subnet> subnets;
foreach (const QString &rawSubnet, value("Preferences/WebUI/AuthSubnetWhitelist").toStringList()) {
for (const QString &rawSubnet : asConst(value("Preferences/WebUI/AuthSubnetWhitelist").toStringList())) {
bool ok = false;
const Utils::Net::Subnet subnet = Utils::Net::parseSubnet(rawSubnet.trimmed(), &ok);
if (ok)
@@ -530,7 +531,7 @@ void Preferences::setWebUiAuthSubnetWhitelist(QStringList subnets)
QString Preferences::getServerDomains() const
{
return value("Preferences/WebUI/ServerDomains", '*').toString();
return value("Preferences/WebUI/ServerDomains", QChar('*')).toString();
}
void Preferences::setServerDomains(const QString &str)
@@ -540,7 +541,7 @@ void Preferences::setServerDomains(const QString &str)
QString Preferences::getWebUiAddress() const
{
return value("Preferences/WebUI/Address", '*').toString().trimmed();
return value("Preferences/WebUI/Address", QChar('*')).toString().trimmed();
}
void Preferences::setWebUiAddress(const QString &addr)
@@ -626,6 +627,16 @@ void Preferences::setWebUiCSRFProtectionEnabled(bool enabled)
setValue("Preferences/WebUI/CSRFProtection", enabled);
}
bool Preferences::isWebUIHostHeaderValidationEnabled() const
{
return value("Preferences/WebUI/HostHeaderValidation", true).toBool();
}
void Preferences::setWebUIHostHeaderValidationEnabled(const bool enabled)
{
setValue("Preferences/WebUI/HostHeaderValidation", enabled);
}
bool Preferences::isWebUiHttpsEnabled() const
{
return value("Preferences/WebUI/HTTPS/Enabled", false).toBool();
@@ -878,153 +889,6 @@ void Preferences::disableRecursiveDownload(bool disable)
}
#ifdef Q_OS_WIN
namespace
{
enum REG_SEARCH_TYPE
{
USER,
SYSTEM_32BIT,
SYSTEM_64BIT
};
QStringList getRegSubkeys(HKEY handle)
{
QStringList keys;
DWORD cSubKeys = 0;
DWORD cMaxSubKeyLen = 0;
LONG res = ::RegQueryInfoKeyW(handle, NULL, NULL, NULL, &cSubKeys, &cMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL);
if (res == ERROR_SUCCESS) {
++cMaxSubKeyLen; // For null character
LPWSTR lpName = new WCHAR[cMaxSubKeyLen];
DWORD cName;
for (DWORD i = 0; i < cSubKeys; ++i) {
cName = cMaxSubKeyLen;
res = ::RegEnumKeyExW(handle, i, lpName, &cName, NULL, NULL, NULL, NULL);
if (res == ERROR_SUCCESS)
keys.push_back(QString::fromWCharArray(lpName));
}
delete[] lpName;
}
return keys;
}
QString getRegValue(HKEY handle, const QString &name = QString())
{
QString result;
DWORD type = 0;
DWORD cbData = 0;
LPWSTR lpValueName = NULL;
if (!name.isEmpty()) {
lpValueName = new WCHAR[name.size() + 1];
name.toWCharArray(lpValueName);
lpValueName[name.size()] = 0;
}
// Discover the size of the value
::RegQueryValueExW(handle, lpValueName, NULL, &type, NULL, &cbData);
DWORD cBuffer = (cbData / sizeof(WCHAR)) + 1;
LPWSTR lpData = new WCHAR[cBuffer];
LONG res = ::RegQueryValueExW(handle, lpValueName, NULL, &type, (LPBYTE)lpData, &cbData);
if (lpValueName)
delete[] lpValueName;
if (res == ERROR_SUCCESS) {
lpData[cBuffer - 1] = 0;
result = QString::fromWCharArray(lpData);
}
delete[] lpData;
return result;
}
QString pythonSearchReg(const REG_SEARCH_TYPE type)
{
HKEY hkRoot;
if (type == USER)
hkRoot = HKEY_CURRENT_USER;
else
hkRoot = HKEY_LOCAL_MACHINE;
REGSAM samDesired = KEY_READ;
if (type == SYSTEM_32BIT)
samDesired |= KEY_WOW64_32KEY;
else if (type == SYSTEM_64BIT)
samDesired |= KEY_WOW64_64KEY;
QString path;
LONG res = 0;
HKEY hkPythonCore;
res = ::RegOpenKeyExW(hkRoot, L"SOFTWARE\\Python\\PythonCore", 0, samDesired, &hkPythonCore);
if (res == ERROR_SUCCESS) {
QStringList versions = getRegSubkeys(hkPythonCore);
qDebug("Python versions nb: %d", versions.size());
versions.sort();
bool found = false;
while (!found && !versions.empty()) {
const QString version = versions.takeLast() + "\\InstallPath";
LPWSTR lpSubkey = new WCHAR[version.size() + 1];
version.toWCharArray(lpSubkey);
lpSubkey[version.size()] = 0;
HKEY hkInstallPath;
res = ::RegOpenKeyExW(hkPythonCore, lpSubkey, 0, samDesired, &hkInstallPath);
delete[] lpSubkey;
if (res == ERROR_SUCCESS) {
qDebug("Detected possible Python v%s location", qUtf8Printable(version));
path = getRegValue(hkInstallPath);
::RegCloseKey(hkInstallPath);
if (!path.isEmpty() && QDir(path).exists("python.exe")) {
qDebug("Found python.exe at %s", qUtf8Printable(path));
found = true;
}
}
}
if (!found)
path = QString();
::RegCloseKey(hkPythonCore);
}
return path;
}
}
QString Preferences::getPythonPath()
{
QString path = pythonSearchReg(USER);
if (!path.isEmpty())
return path;
path = pythonSearchReg(SYSTEM_32BIT);
if (!path.isEmpty())
return path;
path = pythonSearchReg(SYSTEM_64BIT);
if (!path.isEmpty())
return path;
// Fallback: Detect python from default locations
const QStringList dirs = QDir("C:/").entryList(QStringList("Python*"), QDir::Dirs, QDir::Name | QDir::Reversed);
foreach (const QString &dir, dirs) {
const QString path("C:/" + dir + '/');
if (QFile::exists(path + "python.exe"))
return path;
}
return QString();
}
bool Preferences::neverCheckFileAssoc() const
{
return value("Preferences/Win32/NeverCheckFileAssocation", false).toBool();
@@ -1104,7 +968,7 @@ void Preferences::setMagnetLinkAssoc(bool set)
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0);
}
#endif
#endif // Q_OS_WIN
#ifdef Q_OS_MAC
namespace
@@ -1160,7 +1024,7 @@ void Preferences::setMagnetLinkAssoc()
CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
LSSetDefaultHandlerForURLScheme(magnetUrlScheme, myBundleId);
}
#endif
#endif // Q_OS_MAC
int Preferences::getTrackerPort() const
{
@@ -1429,6 +1293,16 @@ void Preferences::setSearchTabHeaderState(const QByteArray &state)
setValue("SearchTab/qt5/HeaderState", state);
}
bool Preferences::getRegexAsFilteringPatternForSearchJob() const
{
return value("SearchTab/UseRegexAsFilteringPattern", false).toBool();
}
void Preferences::setRegexAsFilteringPatternForSearchJob(const bool checked)
{
setValue("SearchTab/UseRegexAsFilteringPattern", checked);
}
QStringList Preferences::getSearchEngDisabled() const
{
return value("SearchEngines/disabledEngines").toStringList();
@@ -1519,12 +1393,12 @@ void Preferences::setTransHeaderState(const QByteArray &state)
setValue("TransferList/qt5/HeaderState", state);
}
bool Preferences::getRegexAsFilteringPattern() const
bool Preferences::getRegexAsFilteringPatternForTransferList() const
{
return value("TransferList/UseRegexAsFilteringPattern", false).toBool();
}
void Preferences::setRegexAsFilteringPattern(const bool checked)
void Preferences::setRegexAsFilteringPatternForTransferList(const bool checked)
{
setValue("TransferList/UseRegexAsFilteringPattern", checked);
}
@@ -1553,8 +1427,8 @@ void Preferences::setToolbarTextPosition(const int position)
QList<QNetworkCookie> Preferences::getNetworkCookies() const
{
QList<QNetworkCookie> cookies;
QStringList rawCookies = value("Network/Cookies").toStringList();
foreach (const QString &rawCookie, rawCookies)
const QStringList rawCookies = value("Network/Cookies").toStringList();
for (const QString &rawCookie : rawCookies)
cookies << QNetworkCookie::parseCookies(rawCookie.toUtf8());
return cookies;
@@ -1563,12 +1437,22 @@ QList<QNetworkCookie> Preferences::getNetworkCookies() const
void Preferences::setNetworkCookies(const QList<QNetworkCookie> &cookies)
{
QStringList rawCookies;
foreach (const QNetworkCookie &cookie, cookies)
for (const QNetworkCookie &cookie : cookies)
rawCookies << cookie.toRawForm();
setValue("Network/Cookies", rawCookies);
}
bool Preferences::isSpeedWidgetEnabled() const
{
return value("SpeedWidget/Enabled", true).toBool();
}
void Preferences::setSpeedWidgetEnabled(bool enabled)
{
setValue("SpeedWidget/Enabled", enabled);
}
int Preferences::getSpeedWidgetPeriod() const
{
return value("SpeedWidget/period", 1).toInt();
@@ -1594,10 +1478,10 @@ void Preferences::upgrade()
{
SettingsStorage *settingsStorage = SettingsStorage::instance();
QStringList labels = value("TransferListFilters/customLabels").toStringList();
const QStringList labels = value("TransferListFilters/customLabels").toStringList();
if (!labels.isEmpty()) {
QVariantMap categories = value("BitTorrent/Session/Categories").toMap();
foreach (const QString &label, labels) {
for (const QString &label : labels) {
if (!categories.contains(label))
categories[label] = "";
}

View File

@@ -201,6 +201,8 @@ public:
void setWebUiClickjackingProtectionEnabled(bool enabled);
bool isWebUiCSRFProtectionEnabled() const;
void setWebUiCSRFProtectionEnabled(bool enabled);
bool isWebUIHostHeaderValidationEnabled() const;
void setWebUIHostHeaderValidationEnabled(bool enabled);
// HTTPS
bool isWebUiHttpsEnabled() const;
@@ -259,7 +261,6 @@ public:
bool recursiveDownloadDisabled() const;
void disableRecursiveDownload(bool disable = true);
#ifdef Q_OS_WIN
static QString getPythonPath();
bool neverCheckFileAssoc() const;
void setNeverCheckFileAssoc(bool check = true);
static bool isTorrentFileAssocSet();
@@ -298,7 +299,7 @@ public:
void setCloseToTrayNotified(bool b);
TrayIcon::Style trayIconStyle() const;
void setTrayIconStyle(TrayIcon::Style style);
#endif
#endif // Q_OS_MAC
// Stuff that don't appear in the Options GUI but are saved
// in the same file.
@@ -342,6 +343,8 @@ public:
void setRssMainSplitterState(const QByteArray &state);
QByteArray getSearchTabHeaderState() const;
void setSearchTabHeaderState(const QByteArray &state);
bool getRegexAsFilteringPatternForSearchJob() const;
void setRegexAsFilteringPatternForSearchJob(bool checked);
QStringList getSearchEngDisabled() const;
void setSearchEngDisabled(const QStringList &engines);
QString getTorImportLastContentDir() const;
@@ -356,8 +359,8 @@ public:
void setTransSelFilter(const int &index);
QByteArray getTransHeaderState() const;
void setTransHeaderState(const QByteArray &state);
bool getRegexAsFilteringPattern() const;
void setRegexAsFilteringPattern(bool checked);
bool getRegexAsFilteringPatternForTransferList() const;
void setRegexAsFilteringPatternForTransferList(bool checked);
int getToolbarTextPosition() const;
void setToolbarTextPosition(const int position);
@@ -370,6 +373,8 @@ public:
void setNetworkCookies(const QList<QNetworkCookie> &cookies);
// SpeedWidget
bool isSpeedWidgetEnabled() const;
void setSpeedWidgetEnabled(bool enabled);
int getSpeedWidgetPeriod() const;
void setSpeedWidgetPeriod(const int period);
bool getSpeedWidgetGraphEnable(int id) const;

View File

@@ -435,13 +435,13 @@ namespace
if (leapSecond)
second = 59; // apparently a leap second - validate below, once time zone is known
int month = 0;
for ( ; (month < 12) && (parts[nmonth] != shortMonth[month]); ++month);
for ( ; (month < 12) && (parts[nmonth] != shortMonth[month]); ++month);
int dayOfWeek = -1;
if (!parts[nwday].isEmpty()) {
// Look up the weekday name
while (++dayOfWeek < 7 && (shortDay[dayOfWeek] != parts[nwday]));
while ((++dayOfWeek < 7) && (shortDay[dayOfWeek] != parts[nwday]));
if (dayOfWeek >= 7)
for (dayOfWeek = 0; dayOfWeek < 7 && (longDay[dayOfWeek] != parts[nwday]); ++dayOfWeek);
for (dayOfWeek = 0; (dayOfWeek < 7) && (longDay[dayOfWeek] != parts[nwday]); ++dayOfWeek);
}
// if (month >= 12 || dayOfWeek >= 7
@@ -450,7 +450,7 @@ namespace
int i = parts[nyear].size();
if (i < 4) {
// It's an obsolete year specification with less than 4 digits
year += (i == 2 && year < 50) ? 2000 : 1900;
year += ((i == 2) && (year < 50)) ? 2000 : 1900;
}
// Parse the UTC offset part
@@ -473,17 +473,17 @@ namespace
else {
// Check for an obsolete time zone name
QByteArray zone = parts[10].toLatin1();
if (zone.length() == 1 && isalpha(zone[0]) && toupper(zone[0]) != 'J') {
if ((zone.length() == 1) && (isalpha(zone[0])) && (toupper(zone[0]) != 'J')) {
negOffset = true; // military zone: RFC 2822 treats as '-0000'
}
else if (zone != "UT" && zone != "GMT") { // treated as '+0000'
else if ((zone != "UT") && (zone != "GMT")) { // treated as '+0000'
offset = (zone == "EDT")
? -4 * 3600
: ((zone == "EST") || (zone == "CDT"))
? -5 * 3600
: ((zone == "CST") || (zone == "MDT"))
? -6 * 3600
: (zone == "MST" || zone == "PDT")
: ((zone == "MST") || (zone == "PDT"))
? -7 * 3600
: (zone == "PST")
? -8 * 3600
@@ -502,12 +502,12 @@ namespace
}
}
QDate qdate(year, month + 1, day); // convert date, and check for out-of-range
if (!qdate.isValid())
QDate qDate(year, month + 1, day); // convert date, and check for out-of-range
if (!qDate.isValid())
return QDateTime::currentDateTime();
QTime qTime(hour, minute, second);
QDateTime result(qdate, qTime, Qt::UTC);
QDateTime result(qDate, qTime, Qt::UTC);
if (offset)
result = result.addSecs(-offset);
if (!result.isValid())
@@ -582,24 +582,16 @@ void Parser::parse_impl(const QByteArray &feedData)
.arg(xml.errorString()).arg(xml.lineNumber())
.arg(xml.columnNumber()).arg(xml.characterOffset());
}
else {
// Sort article list chronologically
// NOTE: We don't need to sort it here if articles are always
// sorted in fetched XML in reverse chronological order
std::sort(m_result.articles.begin(), m_result.articles.end()
, [](const QVariantHash &a1, const QVariantHash &a2)
{
return a1["date"].toDateTime() < a2["date"].toDateTime();
});
}
emit finished(m_result);
m_result.articles.clear(); // clear articles only
m_articleIDs.clear();
}
void Parser::parseRssArticle(QXmlStreamReader &xml)
{
QVariantHash article;
QString altTorrentUrl;
while (!xml.atEnd()) {
xml.readNext();
@@ -615,6 +607,8 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
else if (name == QLatin1String("enclosure")) {
if (xml.attributes().value("type") == QLatin1String("application/x-bittorrent"))
article[Article::KeyTorrentURL] = xml.attributes().value(QLatin1String("url")).toString();
else if (xml.attributes().value("type").isEmpty())
altTorrentUrl = xml.attributes().value(QLatin1String("url")).toString();
}
else if (name == QLatin1String("link")) {
const QString text {xml.readElementText().trimmed()};
@@ -641,7 +635,10 @@ void Parser::parseRssArticle(QXmlStreamReader &xml)
}
}
m_result.articles.prepend(article);
if (article[Article::KeyTorrentURL].toString().isEmpty())
article[Article::KeyTorrentURL] = altTorrentUrl;
addArticle(article);
}
void Parser::parseRSSChannel(QXmlStreamReader &xml)
@@ -736,7 +733,7 @@ void Parser::parseAtomArticle(QXmlStreamReader &xml)
}
}
m_result.articles.prepend(article);
addArticle(article);
}
void Parser::parseAtomChannel(QXmlStreamReader &xml)
@@ -766,3 +763,34 @@ void Parser::parseAtomChannel(QXmlStreamReader &xml)
}
}
}
void Parser::addArticle(QVariantHash article)
{
QVariant &torrentURL = article[Article::KeyTorrentURL];
if (torrentURL.toString().isEmpty())
torrentURL = article[Article::KeyLink];
// If item does not have an ID, fall back to some other identifier.
QVariant &localId = article[Article::KeyId];
if (localId.toString().isEmpty())
localId = article.value(Article::KeyTorrentURL);
if (localId.toString().isEmpty())
localId = article.value(Article::KeyTitle);
if (localId.toString().isEmpty()) {
// The article could not be uniquely identified
// since it has no appropriate data.
// Just ignore it.
return;
}
if (m_articleIDs.contains(localId.toString())) {
// The article could not be uniquely identified
// since the Feed has duplicate identifiers.
// Just ignore it.
return;
}
m_articleIDs.insert(localId.toString());
m_result.articles.prepend(article);
}

View File

@@ -31,6 +31,7 @@
#include <QList>
#include <QObject>
#include <QSet>
#include <QString>
#include <QVariantHash>
@@ -65,9 +66,11 @@ namespace RSS
void parseRSSChannel(QXmlStreamReader &xml);
void parseAtomArticle(QXmlStreamReader &xml);
void parseAtomChannel(QXmlStreamReader &xml);
void addArticle(QVariantHash article);
QString m_baseUrl;
ParsingResult m_result;
QSet<QString> m_articleIDs;
};
}
}

View File

@@ -30,7 +30,6 @@
#include "rss_article.h"
#include <stdexcept>
#include <QJsonObject>
#include <QVariant>
@@ -73,23 +72,6 @@ Article::Article(Feed *feed, const QVariantHash &varHash)
, m_isRead(varHash.value(KeyIsRead, false).toBool())
, m_data(varHash)
{
if (!m_date.isValid())
throw std::runtime_error("Bad RSS Article data");
// If item does not have a guid, fall back to some other identifier
if (m_guid.isEmpty())
m_guid = varHash.value(KeyTorrentURL).toString();
if (m_guid.isEmpty())
m_guid = varHash.value(KeyTitle).toString();
if (m_guid.isEmpty())
throw std::runtime_error("Bad RSS Article data");
m_data[KeyId] = m_guid;
if (m_torrentURL.isEmpty()) {
m_torrentURL = m_link;
m_data[KeyTorrentURL] = m_torrentURL;
}
}
Article::Article(Feed *feed, const QJsonObject &jsonObj)

View File

@@ -66,6 +66,7 @@ const QString RulesFileName(QStringLiteral("download_rules.json"));
const QString SettingsKey_ProcessingEnabled(QStringLiteral("RSS/AutoDownloader/EnableProcessing"));
const QString SettingsKey_SmartEpisodeFilter(QStringLiteral("RSS/AutoDownloader/SmartEpisodeFilter"));
const QString SettingsKey_DownloadRepacks(QStringLiteral("RSS/AutoDownloader/DownloadRepacks"));
namespace
{
@@ -240,7 +241,7 @@ void AutoDownloader::importRules(const QByteArray &data, AutoDownloader::RulesFi
QByteArray AutoDownloader::exportRulesToJSONFormat() const
{
QJsonObject jsonObj;
for (const auto &rule : copyAsConst(rules()))
for (const auto &rule : asConst(rules()))
jsonObj.insert(rule.name(), rule.toJsonObject());
return QJsonDocument(jsonObj).toJson();
@@ -248,14 +249,14 @@ QByteArray AutoDownloader::exportRulesToJSONFormat() const
void AutoDownloader::importRulesFromJSONFormat(const QByteArray &data)
{
for (const auto &rule : copyAsConst(rulesFromJSON(data)))
for (const auto &rule : asConst(rulesFromJSON(data)))
insertRule(rule);
}
QByteArray AutoDownloader::exportRulesToLegacyFormat() const
{
QVariantHash dict;
for (const auto &rule : copyAsConst(rules()))
for (const auto &rule : asConst(rules()))
dict[rule.name()] = rule.toLegacyDict();
QByteArray data;
@@ -275,7 +276,7 @@ void AutoDownloader::importRulesFromLegacyFormat(const QByteArray &data)
if (in.status() != QDataStream::Ok)
throw ParsingError(tr("Invalid data format"));
for (const QVariant &val : qAsConst(dict))
for (const QVariant &val : asConst(dict))
insertRule(AutoDownloadRule::fromLegacyDict(val.toHash()));
}
@@ -310,6 +311,16 @@ void AutoDownloader::setSmartEpisodeFilters(const QStringList &filters)
m_smartEpisodeRegex.setPattern(regex);
}
bool AutoDownloader::downloadRepacks() const
{
return SettingsStorage::instance()->loadValue(SettingsKey_DownloadRepacks, true).toBool();
}
void AutoDownloader::setDownloadRepacks(const bool downloadRepacks)
{
SettingsStorage::instance()->storeValue(SettingsKey_DownloadRepacks, downloadRepacks);
}
void AutoDownloader::process()
{
if (m_processingQueue.isEmpty()) return; // processing was disabled
@@ -362,7 +373,7 @@ void AutoDownloader::addJobForArticle(Article *article)
void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
{
for (AutoDownloadRule &rule: m_rules) {
for (AutoDownloadRule &rule : m_rules) {
if (!rule.isEnabled()) continue;
if (!rule.feedURLs().contains(job->feedURL)) continue;
if (!rule.accepts(job->articleData)) continue;
@@ -424,8 +435,8 @@ void AutoDownloader::loadRules(const QByteArray &data)
void AutoDownloader::loadRulesLegacy()
{
SettingsPtr settings = Profile::instance().applicationSettings(QStringLiteral("qBittorrent-rss"));
QVariantHash rules = settings->value(QStringLiteral("download_rules")).toHash();
foreach (const QVariant &ruleVar, rules) {
const QVariantHash rules = settings->value(QStringLiteral("download_rules")).toHash();
for (const QVariant &ruleVar : rules) {
auto rule = AutoDownloadRule::fromLegacyDict(ruleVar.toHash());
if (!rule.name().isEmpty())
insertRule(rule);
@@ -440,7 +451,7 @@ void AutoDownloader::store()
m_savingTimer.stop();
QJsonObject jsonObj;
foreach (auto rule, m_rules)
for (const auto &rule : asConst(m_rules))
jsonObj.insert(rule.name(), rule.toJsonObject());
m_fileStorage->store(RulesFileName, QJsonDocument(jsonObj).toJson());
@@ -462,7 +473,7 @@ void AutoDownloader::resetProcessingQueue()
m_processingQueue.clear();
if (!m_processingEnabled) return;
foreach (Article *article, Session::instance()->rootFolder()->articles()) {
for (Article *article : asConst(Session::instance()->rootFolder()->articles())) {
if (!article->isRead() && !article->torrentUrl().isEmpty())
addJobForArticle(article);
}

View File

@@ -85,6 +85,9 @@ namespace RSS
void setSmartEpisodeFilters(const QStringList &filters);
QRegularExpression smartEpisodeRegex() const;
bool downloadRepacks() const;
void setDownloadRepacks(bool downloadRepacks);
bool hasRule(const QString &ruleName) const;
AutoDownloadRule ruleByName(const QString &ruleName) const;
QList<AutoDownloadRule> rules() const;

View File

@@ -126,7 +126,7 @@ namespace RSS
bool smartFilter = false;
QStringList previouslyMatchedEpisodes;
mutable QString lastComputedEpisode;
mutable QStringList lastComputedEpisodes;
mutable QHash<QString, QRegularExpression> cachedRegexes;
bool operator==(const AutoDownloadRuleData &other) const
@@ -237,7 +237,7 @@ bool AutoDownloadRule::matchesMustContainExpression(const QString &articleTitle)
// Each expression is either a regex, or a set of wildcards separated by whitespace.
// Accept if any complete expression matches.
for (const QString &expression : qAsConst(m_dataPtr->mustContain)) {
for (const QString &expression : asConst(m_dataPtr->mustContain)) {
// A regex of the form "expr|" will always match, so do the same for wildcards
if (matchesExpression(articleTitle, expression))
return true;
@@ -246,14 +246,14 @@ bool AutoDownloadRule::matchesMustContainExpression(const QString &articleTitle)
return false;
}
bool AutoDownloadRule::matchesMustNotContainExpression(const QString& articleTitle) const
bool AutoDownloadRule::matchesMustNotContainExpression(const QString &articleTitle) const
{
if (m_dataPtr->mustNotContain.empty())
return true;
// Each expression is either a regex, or a set of wildcards separated by whitespace.
// Reject if any complete expression matches.
for (const QString &expression : qAsConst(m_dataPtr->mustNotContain)) {
for (const QString &expression : asConst(m_dataPtr->mustNotContain)) {
// A regex of the form "expr|" will always match, so do the same for wildcards
if (matchesExpression(articleTitle, expression))
return false;
@@ -262,10 +262,10 @@ bool AutoDownloadRule::matchesMustNotContainExpression(const QString& articleTit
return true;
}
bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString& articleTitle) const
bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString &articleTitle) const
{
// Reset the lastComputedEpisode, we don't want to leak it between matches
m_dataPtr->lastComputedEpisode.clear();
m_dataPtr->lastComputedEpisodes.clear();
if (m_dataPtr->episodeFilter.isEmpty())
return true;
@@ -332,7 +332,7 @@ bool AutoDownloadRule::matchesEpisodeFilterExpression(const QString& articleTitl
return false;
}
bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString& articleTitle) const
bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString &articleTitle) const
{
if (!useSmartFilter())
return true;
@@ -343,11 +343,35 @@ bool AutoDownloadRule::matchesSmartEpisodeFilter(const QString& articleTitle) co
// See if this episode has been downloaded before
const bool previouslyMatched = m_dataPtr->previouslyMatchedEpisodes.contains(episodeStr);
const bool isRepack = articleTitle.contains("REPACK", Qt::CaseInsensitive) || articleTitle.contains("PROPER", Qt::CaseInsensitive);
if (previouslyMatched && !isRepack)
return false;
if (previouslyMatched) {
if (!AutoDownloader::instance()->downloadRepacks())
return false;
m_dataPtr->lastComputedEpisode = episodeStr;
// Now see if we've downloaded this particular repack/proper combination
const bool isRepack = articleTitle.contains("REPACK", Qt::CaseInsensitive);
const bool isProper = articleTitle.contains("PROPER", Qt::CaseInsensitive);
if (!isRepack && !isProper)
return false;
const QString fullEpisodeStr = QString("%1%2%3").arg(episodeStr,
isRepack ? "-REPACK" : "",
isProper ? "-PROPER" : "");
const bool previouslyMatchedFull = m_dataPtr->previouslyMatchedEpisodes.contains(fullEpisodeStr);
if (previouslyMatchedFull)
return false;
m_dataPtr->lastComputedEpisodes.append(fullEpisodeStr);
// If this is a REPACK and PROPER download, add the individual entries to the list
// so we don't download those
if (isRepack && isProper) {
m_dataPtr->lastComputedEpisodes.append(QString("%1-REPACK").arg(episodeStr));
m_dataPtr->lastComputedEpisodes.append(QString("%1-PROPER").arg(episodeStr));
}
}
m_dataPtr->lastComputedEpisodes.append(episodeStr);
return true;
}
@@ -379,10 +403,10 @@ bool AutoDownloadRule::accepts(const QVariantHash &articleData)
setLastMatch(articleData[Article::KeyDate].toDateTime());
if (!m_dataPtr->lastComputedEpisode.isEmpty()) {
// TODO: probably need to add a marker for PROPER/REPACK to avoid duplicate downloads
m_dataPtr->previouslyMatchedEpisodes.append(m_dataPtr->lastComputedEpisode);
m_dataPtr->lastComputedEpisode.clear();
// If there's a matched episode string, add that to the previously matched list
if (!m_dataPtr->lastComputedEpisodes.isEmpty()) {
m_dataPtr->previouslyMatchedEpisodes.append(m_dataPtr->lastComputedEpisodes);
m_dataPtr->lastComputedEpisodes.clear();
}
return true;
@@ -442,7 +466,7 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
QStringList feedURLs;
if (feedsVal.isString())
feedURLs << feedsVal.toString();
else foreach (const QJsonValue &urlVal, feedsVal.toArray())
else for (const QJsonValue &urlVal : asConst(feedsVal.toArray()))
feedURLs << urlVal.toString();
rule.setFeedURLs(feedURLs);
@@ -452,7 +476,7 @@ AutoDownloadRule AutoDownloadRule::fromJsonObject(const QJsonObject &jsonObj, co
previouslyMatched << previouslyMatchedVal.toString();
}
else {
foreach (const QJsonValue &val, previouslyMatchedVal.toArray())
for (const QJsonValue &val : asConst(previouslyMatchedVal.toArray()))
previouslyMatched << val.toString();
}
rule.setPreviouslyMatchedEpisodes(previouslyMatched);

View File

@@ -30,6 +30,9 @@
#include "rss_feed.h"
#include <algorithm>
#include <vector>
#include <QDebug>
#include <QDir>
#include <QJsonArray>
@@ -106,7 +109,7 @@ QList<Article *> Feed::articles() const
void Feed::markAsRead()
{
auto oldUnreadCount = m_unreadCount;
foreach (Article *article, m_articles) {
for (Article *article : asConst(m_articles)) {
if (!article->isRead()) {
article->disconnect(this);
article->markAsRead();
@@ -215,47 +218,30 @@ void Feed::handleParsingFinished(const RSS::Private::ParsingResult &result)
{
m_hasError = !result.error.isEmpty();
if (!result.title.isEmpty() && (title() != result.title)) {
m_title = result.title;
m_dirty = true;
emit titleChanged(this);
}
if (!result.lastBuildDate.isEmpty()) {
m_lastBuildDate = result.lastBuildDate;
m_dirty = true;
}
// For some reason, the RSS feed may contain malformed XML data and it may not be
// successfully parsed by the XML parser. We are still trying to load as many articles
// as possible until we encounter corrupted data. So we can have some articles here
// even in case of parsing error.
if (!m_hasError || !result.articles.isEmpty()) {
if (title() != result.title) {
m_title = result.title;
emit titleChanged(this);
}
m_lastBuildDate = result.lastBuildDate;
int newArticlesCount = 0;
const QDateTime now {QDateTime::currentDateTime()};
for (QVariantHash varHash : result.articles) {
// if article has no publication date we use feed update time as a fallback
QVariant &articleDate = varHash[Article::KeyDate];
if (!articleDate.toDateTime().isValid())
articleDate = now;
try {
auto article = new Article(this, varHash);
if (addArticle(article))
++newArticlesCount;
else
delete article;
}
catch (const std::runtime_error&) {}
}
m_dirty = (newArticlesCount > 0);
store();
LogMsg(tr("RSS feed at '%1' updated. Added %2 new articles.")
.arg(m_url, QString::number(newArticlesCount)));
}
const int newArticlesCount = updateArticles(result.articles);
store();
if (m_hasError) {
LogMsg(tr("Failed to parse RSS feed at '%1'. Reason: %2").arg(m_url, result.error)
, Log::WARNING);
}
LogMsg(tr("RSS feed at '%1' updated. Added %2 new articles.")
.arg(url(), QString::number(newArticlesCount)));
m_isLoading = false;
emit stateChanged(this);
@@ -296,9 +282,9 @@ void Feed::loadArticles(const QByteArray &data)
return;
}
QJsonArray jsonArr = jsonDoc.array();
const QJsonArray jsonArr = jsonDoc.array();
int i = -1;
foreach (const QJsonValue &jsonVal, jsonArr) {
for (const QJsonValue &jsonVal : jsonArr) {
++i;
if (!jsonVal.isObject()) {
LogMsg(tr("Couldn't load RSS article '%1#%2'. Invalid data format.").arg(m_url).arg(i)
@@ -318,9 +304,9 @@ void Feed::loadArticles(const QByteArray &data)
void Feed::loadArticlesLegacy()
{
SettingsPtr qBTRSSFeeds = Profile::instance().applicationSettings(QStringLiteral("qBittorrent-rss-feeds"));
QVariantHash allOldItems = qBTRSSFeeds->value("old_items").toHash();
const QVariantHash allOldItems = qBTRSSFeeds->value("old_items").toHash();
foreach (const QVariant &var, allOldItems.value(m_url).toList()) {
for (const QVariant &var : asConst(allOldItems.value(m_url).toList())) {
auto hash = var.toHash();
// update legacy keys
hash[Article::KeyLink] = hash.take(QLatin1String("news_link"));
@@ -343,7 +329,7 @@ void Feed::store()
m_savingTimer.stop();
QJsonArray jsonArr;
foreach (Article *article, m_articles)
for (Article *article :asConst(m_articles))
jsonArr << article->toJsonObject();
m_session->dataFileStorage()->store(m_dataFileName, QJsonDocument(jsonArr).toJson());
@@ -358,9 +344,7 @@ void Feed::storeDeferred()
bool Feed::addArticle(Article *article)
{
Q_ASSERT(article);
if (m_articles.contains(article->guid()))
return false;
Q_ASSERT(!m_articles.contains(article->guid()));
// Insertion sort
const int maxArticles = m_session->maxArticlesPerFeed();
@@ -375,6 +359,8 @@ bool Feed::addArticle(Article *article)
increaseUnreadCount();
connect(article, &Article::read, this, &Feed::handleArticleRead);
}
m_dirty = true;
emit newArticle(article);
if (m_articlesByDate.size() > maxArticles)
@@ -424,6 +410,71 @@ void Feed::downloadIcon()
, this, &Feed::handleIconDownloadFinished);
}
int Feed::updateArticles(const QList<QVariantHash> &loadedArticles)
{
if (loadedArticles.empty())
return 0;
QDateTime dummyPubDate {QDateTime::currentDateTime()};
QVector<QVariantHash> newArticles;
newArticles.reserve(loadedArticles.size());
for (QVariantHash article : loadedArticles) {
// If article has no publication date we use feed update time as a fallback.
// To prevent processing of "out-of-limit" articles we must not assign dates
// that are earlier than the dates of existing articles.
const Article *existingArticle = articleByGUID(article[Article::KeyId].toString());
if (existingArticle) {
dummyPubDate = existingArticle->date().addMSecs(-1);
continue;
}
QVariant &articleDate = article[Article::KeyDate];
if (!articleDate.toDateTime().isValid())
articleDate = dummyPubDate;
newArticles.append(article);
}
if (newArticles.empty())
return 0;
using ArticleSortAdaptor = QPair<QDateTime, const QVariantHash *>;
std::vector<ArticleSortAdaptor> sortData;
const QList<Article *> existingArticles = articles();
sortData.reserve(existingArticles.size() + newArticles.size());
std::transform(existingArticles.begin(), existingArticles.end(), std::back_inserter(sortData)
, [](const Article *article)
{
return qMakePair(article->date(), nullptr);
});
std::transform(newArticles.begin(), newArticles.end(), std::back_inserter(sortData)
, [](const QVariantHash &article)
{
return qMakePair(article[Article::KeyDate].toDateTime(), &article);
});
// Sort article list in reverse chronological order
std::sort(sortData.begin(), sortData.end()
, [](const ArticleSortAdaptor &a1, const ArticleSortAdaptor &a2)
{
return (a1.first > a2.first);
});
if (sortData.size() > static_cast<uint>(m_session->maxArticlesPerFeed()))
sortData.resize(m_session->maxArticlesPerFeed());
int newArticlesCount = 0;
std::for_each(sortData.crbegin(), sortData.crend(), [this, &newArticlesCount](const ArticleSortAdaptor &a)
{
if (a.second) {
addArticle(new Article {this, *a.second});
++newArticlesCount;
}
});
return newArticlesCount;
}
QString Feed::iconPath() const
{
return m_iconPath;
@@ -442,7 +493,7 @@ QJsonValue Feed::toJsonValue(bool withData) const
jsonObj.insert(KEY_HASERROR, hasError());
QJsonArray jsonArr;
for (Article *article : qAsConst(m_articles))
for (Article *article : asConst(m_articles))
jsonArr << article->toJsonObject();
jsonObj.insert(KEY_ARTICLES, jsonArr);
}

View File

@@ -104,6 +104,7 @@ namespace RSS
void increaseUnreadCount();
void decreaseUnreadCount();
void downloadIcon();
int updateArticles(const QList<QVariantHash> &loadedArticles);
Session *m_session;
Private::Parser *m_parser;

View File

@@ -47,7 +47,7 @@ Folder::~Folder()
{
emit aboutToBeDestroyed(this);
foreach (auto item, items())
for (auto item : asConst(items()))
delete item;
}
@@ -55,7 +55,7 @@ QList<Article *> Folder::articles() const
{
QList<Article *> news;
foreach (Item *item, items()) {
for (Item *item : asConst(items())) {
int n = news.size();
news << item->articles();
std::inplace_merge(news.begin(), news.begin() + n, news.end()
@@ -70,20 +70,20 @@ QList<Article *> Folder::articles() const
int Folder::unreadCount() const
{
int count = 0;
foreach (Item *item, items())
for (Item *item : asConst(items()))
count += item->unreadCount();
return count;
}
void Folder::markAsRead()
{
foreach (Item *item, items())
for (Item *item : asConst(items()))
item->markAsRead();
}
void Folder::refresh()
{
foreach (Item *item, items())
for (Item *item : asConst(items()))
item->refresh();
}
@@ -95,7 +95,7 @@ QList<Item *> Folder::items() const
QJsonValue Folder::toJsonValue(bool withData) const
{
QJsonObject jsonObj;
foreach (Item *item, items())
for (Item *item : asConst(items()))
jsonObj.insert(item->name(), item->toJsonValue(withData));
return jsonObj;
@@ -108,7 +108,7 @@ void Folder::handleItemUnreadCountChanged()
void Folder::cleanup()
{
foreach (Item *item, items())
for (Item *item : asConst(items()))
item->cleanup();
}
@@ -123,7 +123,7 @@ void Folder::addItem(Item *item)
connect(item, &Item::articleAboutToBeRemoved, this, &Item::articleAboutToBeRemoved);
connect(item, &Item::unreadCountChanged, this, &Folder::handleItemUnreadCountChanged);
for (auto article: copyAsConst(item->articles()))
for (auto article : asConst(item->articles()))
emit newArticle(article);
if (item->unreadCount() > 0)
@@ -134,7 +134,7 @@ void Folder::removeItem(Item *item)
{
Q_ASSERT(m_items.contains(item));
for (auto article: copyAsConst(item->articles()))
for (auto article : asConst(item->articles()))
emit articleAboutToBeRemoved(article);
item->disconnect(this);

View File

@@ -41,6 +41,7 @@
#include <QVariantHash>
#include "../asyncfilestorage.h"
#include "../global.h"
#include "../logger.h"
#include "../profile.h"
#include "../settingsstorage.h"
@@ -283,7 +284,7 @@ void Session::load()
void Session::loadFolder(const QJsonObject &jsonObj, Folder *folder)
{
bool updated = false;
foreach (const QString &key, jsonObj.keys()) {
for (const QString &key : asConst(jsonObj.keys())) {
const QJsonValue val {jsonObj[key]};
if (val.isString()) {
// previous format (reduced form) doesn't contain UID
@@ -355,7 +356,7 @@ void Session::loadLegacy()
const QString parentFolderPath = Item::parentPath(legacyPath);
const QString feedUrl = Item::relativeName(legacyPath);
foreach (const QString &folderPath, Item::expandPath(parentFolderPath))
for (const QString &folderPath : asConst(Item::expandPath(parentFolderPath)))
addFolder(folderPath);
const QString feedPath = feedAliases[i].isEmpty()

View File

@@ -35,6 +35,7 @@
#include "bittorrent/session.h"
#include "filesystemwatcher.h"
#include "global.h"
#include "preferences.h"
#include "utils/fs.h"
@@ -254,7 +255,7 @@ void ScanFoldersModel::addToFSWatcher(const QStringList &watchPaths)
if (!m_fsWatcher)
return; // addPath() wasn't called before this
foreach (const QString &path, watchPaths) {
for (const QString &path : watchPaths) {
QDir watchDir(path);
const QString canonicalWatchPath = watchDir.canonicalPath();
m_fsWatcher->addPath(canonicalWatchPath);
@@ -282,7 +283,7 @@ bool ScanFoldersModel::removePath(const QString &path, bool removeFromFSWatcher)
void ScanFoldersModel::removeFromFSWatcher(const QStringList &watchPaths)
{
foreach (const QString &path, watchPaths)
for (const QString &path : watchPaths)
m_fsWatcher->removePath(path);
}
@@ -326,7 +327,7 @@ void ScanFoldersModel::makePersistent()
{
QVariantHash dirs;
foreach (const PathData *pathData, m_pathList) {
for (const PathData *pathData : asConst(m_pathList)) {
if (pathData->downloadType == CUSTOM_LOCATION)
dirs.insert(Utils::Fs::fromNativePath(pathData->watchPath), Utils::Fs::fromNativePath(pathData->downloadPath));
else
@@ -350,7 +351,7 @@ void ScanFoldersModel::configure()
void ScanFoldersModel::addTorrentsToSession(const QStringList &pathList)
{
foreach (const QString &file, pathList) {
for (const QString &file : pathList) {
qDebug("File %s added", qUtf8Printable(file));
BitTorrent::AddTorrentParams params;

View File

@@ -32,6 +32,7 @@
#include <QProcess>
#include <QTimer>
#include "../global.h"
#include "../utils/foreignapps.h"
#include "../utils/fs.h"
#include "searchpluginmanager.h"
@@ -138,7 +139,7 @@ void SearchHandler::readSearchOutput()
m_searchResultLineTruncated = lines.takeLast().trimmed();
QList<SearchResult> searchResultList;
foreach (const QByteArray &line, lines) {
for (const QByteArray &line : asConst(lines)) {
SearchResult searchResult;
if (parseSearchResult(QString::fromUtf8(line), searchResult))
searchResultList << searchResult;

View File

@@ -47,6 +47,7 @@
#include "base/net/downloadmanager.h"
#include "base/preferences.h"
#include "base/profile.h"
#include "base/utils/bytearray.h"
#include "base/utils/foreignapps.h"
#include "base/utils/fs.h"
#include "base/utils/misc.h"
@@ -64,7 +65,7 @@ namespace
while (iter.hasNext())
dirs += iter.next();
for (const QString &dir : qAsConst(dirs)) {
for (const QString &dir : asConst(dirs)) {
// python 3: remove "__pycache__" folders
if (dir.endsWith("/__pycache__")) {
Utils::Fs::removeDirRecursive(dir);
@@ -73,7 +74,7 @@ namespace
// python 2: remove "*.pyc" files
const QStringList files = QDir(dir).entryList(QDir::Files);
for (const QString file : files) {
for (const QString &file : files) {
if (file.endsWith(".pyc"))
Utils::Fs::forceRemove(file);
}
@@ -100,9 +101,17 @@ SearchPluginManager::~SearchPluginManager()
SearchPluginManager *SearchPluginManager::instance()
{
if (!m_instance)
m_instance = new SearchPluginManager;
return m_instance;
}
void SearchPluginManager::freeInstance()
{
if (m_instance)
delete m_instance;
}
QStringList SearchPluginManager::allPlugins() const
{
return m_plugins.keys();
@@ -111,7 +120,7 @@ QStringList SearchPluginManager::allPlugins() const
QStringList SearchPluginManager::enabledPlugins() const
{
QStringList plugins;
for (const PluginInfo *plugin : qAsConst(m_plugins)) {
for (const PluginInfo *plugin : asConst(m_plugins)) {
if (plugin->enabled)
plugins << plugin->name;
}
@@ -122,9 +131,9 @@ QStringList SearchPluginManager::enabledPlugins() const
QStringList SearchPluginManager::supportedCategories() const
{
QStringList result;
for (const PluginInfo *plugin : qAsConst(m_plugins)) {
for (const PluginInfo *plugin : asConst(m_plugins)) {
if (plugin->enabled) {
foreach (QString cat, plugin->supportedCategories) {
for (const QString &cat : plugin->supportedCategories) {
if (!result.contains(cat))
result << cat;
}
@@ -145,8 +154,8 @@ QStringList SearchPluginManager::getPluginCategories(const QString &pluginName)
plugins << pluginName.trimmed();
QSet<QString> categories;
for (const QString &pluginName : qAsConst(plugins)) {
const PluginInfo *plugin = pluginInfo(pluginName);
for (const QString &name : asConst(plugins)) {
const PluginInfo *plugin = pluginInfo(name);
if (!plugin) continue; // plugin wasn't found
for (const QString &category : plugin->supportedCategories)
categories << category;
@@ -187,8 +196,6 @@ void SearchPluginManager::updatePlugin(const QString &name)
// Install or update plugin from file or url
void SearchPluginManager::installPlugin(const QString &source)
{
qDebug("Asked to install plugin at %s", qUtf8Printable(source));
clearPythonCache(engineLocation());
if (Utils::Misc::isUrl(source)) {
@@ -215,12 +222,10 @@ void SearchPluginManager::installPlugin(const QString &source)
void SearchPluginManager::installPlugin_impl(const QString &name, const QString &path)
{
PluginVersion newVersion = getPluginVersion(path);
qDebug() << "Version to be installed:" << newVersion;
const PluginVersion newVersion = getPluginVersion(path);
PluginInfo *plugin = pluginInfo(name);
if (plugin && !(plugin->version < newVersion)) {
qDebug("Apparently update is not needed, we have a more recent version");
LogMsg(tr("Plugin already at version %1, which is greater than %2").arg(plugin->version, newVersion), Log::INFO);
emit pluginUpdateFailed(name, tr("A more recent version of this plugin is already installed."));
return;
}
@@ -242,6 +247,7 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
if (!m_plugins.contains(name)) {
// Remove broken file
Utils::Fs::forceRemove(destPath);
LogMsg(tr("Plugin %1 is not supported.").arg(name), Log::INFO);
if (updated) {
// restore backup
QFile::copy(destPath + ".bak", destPath);
@@ -256,8 +262,10 @@ void SearchPluginManager::installPlugin_impl(const QString &name, const QString
}
else {
// Install was successful, remove backup
if (updated)
if (updated) {
LogMsg(tr("Plugin %1 has been successfully updated.").arg(name), Log::INFO);
Utils::Fs::forceRemove(destPath + ".bak");
}
}
}
@@ -269,9 +277,8 @@ bool SearchPluginManager::uninstallPlugin(const QString &name)
QDir pluginsFolder(pluginsLocation());
QStringList filters;
filters << name + ".*";
QStringList files = pluginsFolder.entryList(filters, QDir::Files, QDir::Unsorted);
QString file;
foreach (file, files)
const QStringList files = pluginsFolder.entryList(filters, QDir::Files, QDir::Unsorted);
for (const QString &file : files)
Utils::Fs::forceRemove(pluginsFolder.absoluteFilePath(file));
// Remove it from supported engines
delete m_plugins.take(name);
@@ -494,37 +501,37 @@ void SearchPluginManager::update()
void SearchPluginManager::parseVersionInfo(const QByteArray &info)
{
qDebug("Checking if update is needed");
QHash<QString, PluginVersion> updateInfo;
bool dataCorrect = false;
QList<QByteArray> lines = info.split('\n');
foreach (QByteArray line, lines) {
int numCorrectData = 0;
const QList<QByteArray> lines = Utils::ByteArray::splitToViews(info, "\n", QString::SkipEmptyParts);
for (QByteArray line : lines) {
line = line.trimmed();
if (line.isEmpty()) continue;
if (line.startsWith('#')) continue;
QList<QByteArray> list = line.split(' ');
const QList<QByteArray> list = Utils::ByteArray::splitToViews(line, ":", QString::SkipEmptyParts);
if (list.size() != 2) continue;
QString pluginName = QString(list.first());
if (!pluginName.endsWith(':')) continue;
const QString pluginName = list.first().trimmed();
const PluginVersion version = PluginVersion::tryParse(list.last().trimmed(), {});
pluginName.chop(1); // remove trailing ':'
PluginVersion version = PluginVersion::tryParse(list.last(), {});
if (version == PluginVersion()) continue;
if (!version.isValid()) continue;
dataCorrect = true;
++numCorrectData;
if (isUpdateNeeded(pluginName, version)) {
qDebug("Plugin: %s is outdated", qUtf8Printable(pluginName));
LogMsg(tr("Plugin \"%1\" is outdated, updating to version %2").arg(pluginName, version), Log::INFO);
updateInfo[pluginName] = version;
}
}
if (!dataCorrect)
emit checkForUpdatesFailed(tr("An incorrect update info received."));
else
if (numCorrectData < lines.size()) {
emit checkForUpdatesFailed(tr("Incorrect update info received for %1 out of %2 plugins.")
.arg(QString::number(lines.size() - numCorrectData), QString::number(lines.size())));
}
else {
emit checkForUpdatesFinished(updateInfo);
}
}
bool SearchPluginManager::isUpdateNeeded(QString pluginName, PluginVersion newVersion) const
@@ -533,7 +540,6 @@ bool SearchPluginManager::isUpdateNeeded(QString pluginName, PluginVersion newVe
if (!plugin) return true;
PluginVersion oldVersion = plugin->version;
qDebug() << "IsUpdate needed? to be installed:" << newVersion << ", already installed:" << oldVersion;
return (newVersion > oldVersion);
}

View File

@@ -62,6 +62,7 @@ public:
~SearchPluginManager() override;
static SearchPluginManager *instance();
static void freeInstance();
QStringList allPlugins() const;
QStringList enabledPlugins() const;

View File

@@ -33,6 +33,7 @@
#include <QFile>
#include <QHash>
#include "global.h"
#include "logger.h"
#include "profile.h"
#include "utils/fs.h"
@@ -286,7 +287,7 @@ QString TransactionalSettings::deserialize(const QString &name, QVariantHash &da
// Copy everything into memory. This means even keys inserted in the file manually
// or that we don't touch directly in this code (eg disabled by ifdef). This ensures
// that they will be copied over when save our settings to disk.
foreach (const QString &key, settings->allKeys())
for (const QString &key : asConst(settings->allKeys()))
data.insert(key, settings->value(key));
return settings->fileName();

View File

@@ -26,6 +26,8 @@
* exception statement from your version.
*/
#pragma once
// This file must be encoded in "UTF-8 with BOM"
#ifdef _MSC_VER
#pragma execution_character_set("utf-8")
@@ -36,8 +38,6 @@
// See issue #3059 for more details (https://github.com/qbittorrent/qBittorrent/issues/3059).
const char C_INFINITY[] = "";
const char C_NON_BREAKING_SPACE[] = " ";
const char C_UP[] = "";
const char C_DOWN[] = "";
const char C_COPYRIGHT[] = "©";
const char C_THIN_SPACE[] = "";
const char C_UTP[] = "μTP";

View File

@@ -29,11 +29,19 @@
#include "foreignapps.h"
#if defined(Q_OS_WIN)
#include <windows.h>
#endif
#include <QCoreApplication>
#include <QProcess>
#include <QRegularExpression>
#include <QStringList>
#if defined(Q_OS_WIN)
#include <QDir>
#endif
#include "base/logger.h"
using namespace Utils::ForeignApps;
@@ -65,16 +73,163 @@ namespace
try {
info = {exeName, versionStr.left(idx)};
}
catch (const std::runtime_error &err) {
catch (const std::runtime_error &) {
return false;
}
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Python detected, version: %1").arg(info.version), Log::INFO);
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Python detected, executable name: '%1', version: %2")
.arg(info.executableName, info.version), Log::INFO);
return true;
}
return false;
}
#if defined(Q_OS_WIN)
enum REG_SEARCH_TYPE
{
USER,
SYSTEM_32BIT,
SYSTEM_64BIT
};
QStringList getRegSubkeys(const HKEY handle)
{
QStringList keys;
DWORD cSubKeys = 0;
DWORD cMaxSubKeyLen = 0;
LONG res = ::RegQueryInfoKeyW(handle, NULL, NULL, NULL, &cSubKeys, &cMaxSubKeyLen, NULL, NULL, NULL, NULL, NULL, NULL);
if (res == ERROR_SUCCESS) {
++cMaxSubKeyLen; // For null character
LPWSTR lpName = new WCHAR[cMaxSubKeyLen];
DWORD cName;
for (DWORD i = 0; i < cSubKeys; ++i) {
cName = cMaxSubKeyLen;
res = ::RegEnumKeyExW(handle, i, lpName, &cName, NULL, NULL, NULL, NULL);
if (res == ERROR_SUCCESS)
keys.push_back(QString::fromWCharArray(lpName));
}
delete[] lpName;
}
return keys;
}
QString getRegValue(const HKEY handle, const QString &name = QString())
{
QString result;
DWORD type = 0;
DWORD cbData = 0;
LPWSTR lpValueName = NULL;
if (!name.isEmpty()) {
lpValueName = new WCHAR[name.size() + 1];
name.toWCharArray(lpValueName);
lpValueName[name.size()] = 0;
}
// Discover the size of the value
::RegQueryValueExW(handle, lpValueName, NULL, &type, NULL, &cbData);
DWORD cBuffer = (cbData / sizeof(WCHAR)) + 1;
LPWSTR lpData = new WCHAR[cBuffer];
LONG res = ::RegQueryValueExW(handle, lpValueName, NULL, &type, (LPBYTE)lpData, &cbData);
if (lpValueName)
delete[] lpValueName;
if (res == ERROR_SUCCESS) {
lpData[cBuffer - 1] = 0;
result = QString::fromWCharArray(lpData);
}
delete[] lpData;
return result;
}
QString pythonSearchReg(const REG_SEARCH_TYPE type)
{
HKEY hkRoot;
if (type == USER)
hkRoot = HKEY_CURRENT_USER;
else
hkRoot = HKEY_LOCAL_MACHINE;
REGSAM samDesired = KEY_READ;
if (type == SYSTEM_32BIT)
samDesired |= KEY_WOW64_32KEY;
else if (type == SYSTEM_64BIT)
samDesired |= KEY_WOW64_64KEY;
QString path;
LONG res = 0;
HKEY hkPythonCore;
res = ::RegOpenKeyExW(hkRoot, L"SOFTWARE\\Python\\PythonCore", 0, samDesired, &hkPythonCore);
if (res == ERROR_SUCCESS) {
QStringList versions = getRegSubkeys(hkPythonCore);
qDebug("Python versions nb: %d", versions.size());
versions.sort();
bool found = false;
while (!found && !versions.empty()) {
const QString version = versions.takeLast() + "\\InstallPath";
LPWSTR lpSubkey = new WCHAR[version.size() + 1];
version.toWCharArray(lpSubkey);
lpSubkey[version.size()] = 0;
HKEY hkInstallPath;
res = ::RegOpenKeyExW(hkPythonCore, lpSubkey, 0, samDesired, &hkInstallPath);
delete[] lpSubkey;
if (res == ERROR_SUCCESS) {
qDebug("Detected possible Python v%s location", qUtf8Printable(version));
path = getRegValue(hkInstallPath);
::RegCloseKey(hkInstallPath);
if (!path.isEmpty() && QDir(path).exists("python.exe")) {
found = true;
path = QDir(path).filePath("python.exe");
}
}
}
if (!found)
path = QString();
::RegCloseKey(hkPythonCore);
}
return path;
}
QString findPythonPath()
{
QString path = pythonSearchReg(USER);
if (!path.isEmpty())
return path;
path = pythonSearchReg(SYSTEM_32BIT);
if (!path.isEmpty())
return path;
path = pythonSearchReg(SYSTEM_64BIT);
if (!path.isEmpty())
return path;
// Fallback: Detect python from default locations
const QFileInfoList dirs = QDir("C:/").entryInfoList({"Python*"}, QDir::Dirs, (QDir::Name | QDir::Reversed));
for (const QFileInfo &info : dirs) {
const QString path {info.absolutePath() + "/python.exe"};
if (QFile::exists(path))
return path;
}
return QString();
}
#endif // Q_OS_WIN
}
bool Utils::ForeignApps::PythonInfo::isValid() const
@@ -82,13 +237,21 @@ bool Utils::ForeignApps::PythonInfo::isValid() const
return (!executableName.isEmpty() && version.isValid());
}
bool Utils::ForeignApps::PythonInfo::isSupportedVersion() const
{
const int majorVer = version.majorNumber();
return ((majorVer > 3)
|| ((majorVer == 3) && (version >= Version {3, 3, 0}))
|| ((majorVer == 2) && (version >= Version {2, 7, 9})));
}
PythonInfo Utils::ForeignApps::pythonInfo()
{
static PythonInfo pyInfo;
if (!pyInfo.isValid()) {
#if defined(Q_OS_UNIX)
// On Unix-Like Systems python2 and python3 should always exist
// https://legacy.python.org/dev/peps/pep-0394/
// https://www.python.org/dev/peps/pep-0394/
if (testPythonInstallation("python3", pyInfo))
return pyInfo;
if (testPythonInstallation("python2", pyInfo))
@@ -99,6 +262,11 @@ PythonInfo Utils::ForeignApps::pythonInfo()
if (testPythonInstallation("python", pyInfo))
return pyInfo;
#if defined(Q_OS_WIN)
if (testPythonInstallation(findPythonPath(), pyInfo))
return pyInfo;
#endif
LogMsg(QCoreApplication::translate("Utils::ForeignApps", "Python not detected"), Log::INFO);
}

View File

@@ -42,6 +42,7 @@ namespace Utils
using Version = Utils::Version<quint8, 3, 1>;
bool isValid() const;
bool isSupportedVersion() const;
QString executableName;
Version version;

View File

@@ -30,6 +30,10 @@
#include <cstring>
#if defined(Q_OS_WIN)
#include <memory>
#endif
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
@@ -44,7 +48,7 @@
#if defined(Q_OS_WIN)
#include <Windows.h>
#elif defined(Q_OS_MAC) || defined(Q_OS_FREEBSD)
#elif defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
#include <sys/param.h>
#include <sys/mount.h>
#elif defined(Q_OS_HAIKU)
@@ -129,7 +133,7 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
std::sort(dirList.begin(), dirList.end()
, [](const QString &l, const QString &r) { return l.count('/') > r.count('/'); });
for (const QString &p : qAsConst(dirList)) {
for (const QString &p : asConst(dirList)) {
// remove unwanted files
for (const QString &f : deleteFilesList) {
forceRemove(p + f);
@@ -137,7 +141,7 @@ bool Utils::Fs::smartRemoveEmptyFolderTree(const QString &path)
// remove temp files on linux (file ends with '~'), e.g. `filename~`
QDir dir(p);
QStringList tmpFileList = dir.entryList(QDir::Files);
const QStringList tmpFileList = dir.entryList(QDir::Files);
for (const QString &f : tmpFileList) {
if (f.endsWith('~'))
forceRemove(p + f);
@@ -301,9 +305,17 @@ bool Utils::Fs::isRegularFile(const QString &path)
return (st.st_mode & S_IFMT) == S_IFREG;
}
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
#if !defined Q_OS_HAIKU
bool Utils::Fs::isNetworkFileSystem(const QString &path)
{
#if defined(Q_OS_WIN)
const std::wstring pathW {path.toStdWString()};
std::unique_ptr<wchar_t[]> volumePath {new wchar_t[path.length() + 1] {}};
if (!::GetVolumePathNameW(pathW.c_str(), volumePath.get(), (path.length() + 1)))
return false;
return (::GetDriveTypeW(volumePath.get()) == DRIVE_REMOTE);
#elif defined(Q_OS_MAC) || defined(Q_OS_OPENBSD)
QString file = path;
if (!file.endsWith('/'))
file += '/';
@@ -313,20 +325,32 @@ bool Utils::Fs::isNetworkFileSystem(const QString &path)
if (statfs(file.toLocal8Bit().constData(), &buf) != 0)
return false;
#ifdef Q_OS_MAC
// XXX: should we make sure HAVE_STRUCT_FSSTAT_F_FSTYPENAME is defined?
return ((strncmp(buf.f_fstypename, "nfs", sizeof(buf.f_fstypename)) == 0)
|| (strncmp(buf.f_fstypename, "cifs", sizeof(buf.f_fstypename)) == 0)
return ((strncmp(buf.f_fstypename, "cifs", sizeof(buf.f_fstypename)) == 0)
|| (strncmp(buf.f_fstypename, "nfs", sizeof(buf.f_fstypename)) == 0)
|| (strncmp(buf.f_fstypename, "smbfs", sizeof(buf.f_fstypename)) == 0));
#else
// usually defined in /usr/include/linux/magic.h
const unsigned long CIFS_MAGIC_NUMBER = 0xFF534D42;
const unsigned long NFS_SUPER_MAGIC = 0x6969;
const unsigned long SMB_SUPER_MAGIC = 0x517B;
#else // Q_OS_WIN
QString file = path;
if (!file.endsWith('/'))
file += '/';
file += '.';
return ((buf.f_type == CIFS_MAGIC_NUMBER)
|| (buf.f_type == NFS_SUPER_MAGIC)
|| (buf.f_type == SMB_SUPER_MAGIC));
#endif
struct statfs buf {};
if (statfs(file.toLocal8Bit().constData(), &buf) != 0)
return false;
// Magic number references:
// 1. /usr/include/linux/magic.h
// 2. https://github.com/coreutils/coreutils/blob/master/src/stat.c
switch (buf.f_type) {
case 0xFF534D42: // CIFS_MAGIC_NUMBER
case 0x6969: // NFS_SUPER_MAGIC
case 0x517B: // SMB_SUPER_MAGIC
case 0xFE534D42: // S_MAGIC_SMB2
return true;
default:
return false;
}
#endif // Q_OS_WIN
}
#endif
#endif // Q_OS_HAIKU

View File

@@ -62,7 +62,7 @@ namespace Utils
QString tempPath();
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
#if !defined Q_OS_HAIKU
bool isNetworkFileSystem(const QString &path);
#endif
}

View File

@@ -66,7 +66,7 @@
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
#include "base/utils/version.h"
#endif
#endif
#endif // DISABLE_GUI
#include "base/logger.h"
#include "base/unicodestrings.h"
@@ -84,6 +84,27 @@ namespace
QT_TRANSLATE_NOOP3("misc", "PiB", "pebibytes (1024 tebibytes)"),
QT_TRANSLATE_NOOP3("misc", "EiB", "exbibytes (1024 pebibytes)")
};
// return best userfriendly storage unit (B, KiB, MiB, GiB, TiB, ...)
// use Binary prefix standards from IEC 60027-2
// see http://en.wikipedia.org/wiki/Kilobyte
// value must be given in bytes
// to send numbers instead of strings with suffixes
bool splitToFriendlyUnit(const qint64 sizeInBytes, qreal &val, Utils::Misc::SizeUnit &unit)
{
if (sizeInBytes < 0) return false;
int i = 0;
qreal rawVal = static_cast<qreal>(sizeInBytes);
while ((rawVal >= 1024.) && (i <= static_cast<int>(Utils::Misc::SizeUnit::ExbiByte))) {
rawVal /= 1024.;
++i;
}
val = rawVal;
unit = static_cast<Utils::Misc::SizeUnit>(i);
return true;
}
}
void Utils::Misc::shutdownComputer(const ShutdownDialogAction &action)
@@ -237,52 +258,30 @@ QPoint Utils::Misc::screenCenter(const QWidget *w)
}
#endif
QString Utils::Misc::unitString(Utils::Misc::SizeUnit unit)
QString Utils::Misc::unitString(const SizeUnit unit, const bool isSpeed)
{
return QCoreApplication::translate("misc",
units[static_cast<int>(unit)].source, units[static_cast<int>(unit)].comment);
}
// return best userfriendly storage unit (B, KiB, MiB, GiB, TiB, ...)
// use Binary prefix standards from IEC 60027-2
// see http://en.wikipedia.org/wiki/Kilobyte
// value must be given in bytes
// to send numbers instead of strings with suffixes
bool Utils::Misc::friendlyUnit(qint64 sizeInBytes, qreal &val, Utils::Misc::SizeUnit &unit)
{
if (sizeInBytes < 0) return false;
int i = 0;
qreal rawVal = static_cast<qreal>(sizeInBytes);
while ((rawVal >= 1024.) && (i <= static_cast<int>(SizeUnit::ExbiByte))) {
rawVal /= 1024.;
++i;
}
val = rawVal;
unit = static_cast<SizeUnit>(i);
return true;
const auto &unitString = units[static_cast<int>(unit)];
QString ret = QCoreApplication::translate("misc", unitString.source, unitString.comment);
if (isSpeed)
ret += QCoreApplication::translate("misc", "/s", "per second");
return ret;
}
QString Utils::Misc::friendlyUnit(qint64 bytesValue, bool isSpeed)
{
SizeUnit unit;
qreal friendlyVal;
if (!friendlyUnit(bytesValue, friendlyVal, unit))
if (!splitToFriendlyUnit(bytesValue, friendlyVal, unit))
return QCoreApplication::translate("misc", "Unknown", "Unknown (size)");
QString ret;
if (unit == SizeUnit::Byte)
ret = QString::number(bytesValue) + QString::fromUtf8(C_NON_BREAKING_SPACE) + unitString(unit);
else
ret = Utils::String::fromDouble(friendlyVal, friendlyUnitPrecision(unit)) + QString::fromUtf8(C_NON_BREAKING_SPACE) + unitString(unit);
if (isSpeed)
ret += QCoreApplication::translate("misc", "/s", "per second");
return ret;
return Utils::String::fromDouble(friendlyVal, friendlyUnitPrecision(unit))
+ QString::fromUtf8(C_NON_BREAKING_SPACE)
+ unitString(unit, isSpeed);
}
int Utils::Misc::friendlyUnitPrecision(SizeUnit unit)
{
// friendlyUnit's number of digits after the decimal point
if (unit == SizeUnit::Byte) return 0;
if (unit <= SizeUnit::MebiByte) return 1;
else if (unit == SizeUnit::GibiByte) return 2;
else return 3;
@@ -335,6 +334,7 @@ bool Utils::Misc::isPreviewable(const QString &extension)
"RMVB",
"SWA",
"SWF",
"TS",
"VOB",
"WAV",
"WMA",
@@ -391,7 +391,7 @@ QString Utils::Misc::getUserIDString()
QStringList Utils::Misc::toStringList(const QList<bool> &l)
{
QStringList ret;
foreach (const bool &b, l)
for (const bool b : l)
ret << (b ? "1" : "0");
return ret;
}
@@ -399,7 +399,7 @@ QStringList Utils::Misc::toStringList(const QList<bool> &l)
QList<int> Utils::Misc::intListfromStringList(const QStringList &l)
{
QList<int> ret;
foreach (const QString &s, l)
for (const QString &s : l)
ret << s.toInt();
return ret;
}
@@ -407,7 +407,7 @@ QList<int> Utils::Misc::intListfromStringList(const QStringList &l)
QList<bool> Utils::Misc::boolListfromStringList(const QStringList &l)
{
QList<bool> ret;
foreach (const QString &s, l)
for (const QString &s : l)
ret << (s == "1");
return ret;
}
@@ -581,7 +581,7 @@ QString Utils::Misc::libtorrentVersionString()
QString Utils::Misc::windowsSystemPath()
{
static const QString path = []() -> QString {
WCHAR systemPath[64] = {0};
WCHAR systemPath[MAX_PATH] = {0};
GetSystemDirectoryW(systemPath, sizeof(systemPath) / sizeof(WCHAR));
return QString::fromWCharArray(systemPath);
}();

View File

@@ -79,11 +79,10 @@ namespace Utils
QString boostVersionString();
QString libtorrentVersionString();
QString unitString(SizeUnit unit);
QString unitString(SizeUnit unit, bool isSpeed = false);
// return the best user friendly storage unit (B, KiB, MiB, GiB, TiB)
// value must be given in bytes
bool friendlyUnit(qint64 sizeInBytes, qreal &val, SizeUnit &unit);
QString friendlyUnit(qint64 bytesValue, bool isSpeed = false);
int friendlyUnitPrecision(SizeUnit unit);
qint64 sizeInBytes(qreal size, SizeUnit unit);
@@ -125,7 +124,7 @@ namespace Utils
return reinterpret_cast<T>(
::GetProcAddress(::LoadLibraryW(pathWchar.get()), funcName));
}
#endif
#endif // Q_OS_WIN
}
}

View File

@@ -97,18 +97,29 @@ namespace
}
else if (leftChar.isDigit() && rightChar.isDigit()) {
// Both are digits, compare the numbers
const auto consumeNumber = [](const QString &str, int &pos) -> int
const auto numberView = [](const QString &str, int &pos) -> QStringRef
{
const int start = pos;
while ((pos < str.size()) && str[pos].isDigit())
++pos;
return str.midRef(start, (pos - start)).toInt();
return str.midRef(start, (pos - start));
};
const int numL = consumeNumber(left, posL);
const int numR = consumeNumber(right, posR);
if (numL != numR)
return (numL - numR);
const QStringRef numViewL = numberView(left, posL);
const QStringRef numViewR = numberView(right, posR);
if (numViewL.length() != numViewR.length())
return (numViewL.length() - numViewR.length());
// both string/view has the same length
for (int i = 0; i < numViewL.length(); ++i) {
const QChar numL = numViewL.at(i);
const QChar numR = numViewR.at(i);
if (numL != numR)
return (numL.unicode() - numR.unicode());
}
// String + digits do match and we haven't hit the end of both strings
// then continue to consume the remainings
@@ -202,3 +213,14 @@ TriStateBool Utils::String::parseTriStateBool(const QString &string)
return TriStateBool::False;
return TriStateBool::Undefined;
}
QString Utils::String::join(const QVector<QStringRef> &strings, const QString &separator)
{
if (strings.empty())
return {};
QString ret = strings[0].toString();
for (int i = 1; i < strings.count(); ++i)
ret += (separator + strings[i]);
return ret;
}

View File

@@ -31,6 +31,7 @@
#define UTILS_STRING_H
#include <QString>
#include <QVector>
class QByteArray;
class QLatin1String;
@@ -60,7 +61,7 @@ namespace Utils
{
if (str.length() < 2) return str;
for (auto const quote : quotes) {
for (const auto &quote : quotes) {
if (str.startsWith(quote) && str.endsWith(quote))
return str.mid(1, str.length() - 2);
}
@@ -70,6 +71,8 @@ namespace Utils
bool parseBool(const QString &string, const bool defaultValue);
TriStateBool parseTriStateBool(const QString &string);
QString join(const QVector<QStringRef> &strings, const QString &separator);
}
}

View File

@@ -168,12 +168,12 @@ namespace Utils
{
if ((static_cast<std::size_t>(versionParts.size()) > N)
|| (static_cast<std::size_t>(versionParts.size()) < Mandatory))
throw std::runtime_error ("Incorrect number of version components");
throw std::runtime_error("Incorrect number of version components");
bool ok = false;
ComponentsArray res {{}};
for (std::size_t i = 0; i < static_cast<std::size_t>(versionParts.size()); ++i) {
res[i] = static_cast<T>(versionParts[i].toInt(&ok));
res[i] = static_cast<T>(versionParts[static_cast<typename StringsList::size_type>(i)].toInt(&ok));
if (!ok)
throw std::runtime_error("Can not parse version component");
}

View File

@@ -68,7 +68,7 @@ public:
"</table>"
"</p>")
.arg(tr("An advanced BitTorrent client programmed in C++, based on Qt toolkit and libtorrent-rasterbar.")
, tr("Copyright %1 2006-2018 The qBittorrent project").arg(QString::fromUtf8(C_COPYRIGHT))
, tr("Copyright %1 2006-2019 The qBittorrent project").arg(QString::fromUtf8(C_COPYRIGHT))
, tr("Home Page:")
, tr("Forum:")
, tr("Bug Tracker:"));

View File

@@ -30,23 +30,24 @@
#include <QDebug>
#include <QFile>
#include <QDir>
#include <QMenu>
#include <QPushButton>
#include <QShortcut>
#include <QString>
#include <QUrl>
#include <QVector>
#include "base/bittorrent/filepriority.h"
#include "base/bittorrent/magneturi.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrenthandle.h"
#include "base/bittorrent/torrentinfo.h"
#include "base/global.h"
#include "base/net/downloadhandler.h"
#include "base/net/downloadmanager.h"
#include "base/preferences.h"
#include "base/settingsstorage.h"
#include "base/torrentfileguard.h"
#include "base/unicodestrings.h"
#include "base/utils/fs.h"
#include "base/utils/misc.h"
#include "base/utils/string.h"
@@ -61,16 +62,14 @@
namespace
{
#define SETTINGS_KEY(name) QStringLiteral("AddNewTorrentDialog/" name)
const QString KEY_ENABLED = SETTINGS_KEY("Enabled");
const QString KEY_DEFAULTCATEGORY = SETTINGS_KEY("DefaultCategory");
const QString KEY_TREEHEADERSTATE = SETTINGS_KEY("TreeHeaderState");
const QString KEY_WIDTH = SETTINGS_KEY("Width");
const QString KEY_EXPANDED = SETTINGS_KEY("Expanded");
const QString KEY_TOPLEVEL = SETTINGS_KEY("TopLevel");
const QString KEY_SAVEPATHHISTORY = SETTINGS_KEY("SavePathHistory");
const QString KEY_SAVEPATHHISTORYLENGTH = SETTINGS_KEY("SavePathHistoryLength");
const QString KEY_REMEMBERLASTSAVEPATH = SETTINGS_KEY("RememberLastSavePath");
#define SETTINGS_KEY(name) "AddNewTorrentDialog/" name
const QString KEY_ENABLED = QStringLiteral(SETTINGS_KEY("Enabled"));
const QString KEY_DEFAULTCATEGORY = QStringLiteral(SETTINGS_KEY("DefaultCategory"));
const QString KEY_TREEHEADERSTATE = QStringLiteral(SETTINGS_KEY("TreeHeaderState"));
const QString KEY_TOPLEVEL = QStringLiteral(SETTINGS_KEY("TopLevel"));
const QString KEY_SAVEPATHHISTORY = QStringLiteral(SETTINGS_KEY("SavePathHistory"));
const QString KEY_SAVEPATHHISTORYLENGTH = QStringLiteral(SETTINGS_KEY("SavePathHistoryLength"));
const QString KEY_REMEMBERLASTSAVEPATH = QStringLiteral(SETTINGS_KEY("RememberLastSavePath"));
// just a shortcut
inline SettingsStorage *settings()
@@ -90,10 +89,13 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
, m_hasMetadata(false)
, m_oldIndex(0)
, m_torrentParams(inParams)
, m_storeDialogSize(SETTINGS_KEY("DialogSize"))
, m_storeSplitterState(SETTINGS_KEY("SplitterState"))
{
// TODO: set dialog file properties using m_torrentParams.filePriorities
m_ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
m_ui->lblMetaLoading->setVisible(false);
m_ui->progMetaLoading->setVisible(false);
@@ -143,18 +145,19 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
m_ui->categoryComboBox->addItem(defaultCategory);
m_ui->categoryComboBox->addItem("");
foreach (const QString &category, categories)
for (const QString &category : asConst(categories))
if (category != defaultCategory && category != m_torrentParams.category)
m_ui->categoryComboBox->addItem(category);
m_ui->contentTreeView->header()->setSortIndicator(0, Qt::AscendingOrder);
loadState();
// Signal / slots
connect(m_ui->toolButtonAdvanced, &QToolButton::clicked, this, &AddNewTorrentDialog::showAdvancedSettings);
connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked);
QShortcut *editHotkey = new QShortcut(Qt::Key_F2, m_ui->contentTreeView, nullptr, nullptr, Qt::WidgetShortcut);
connect(editHotkey, &QShortcut::activated, this, &AddNewTorrentDialog::renameSelectedFile);
connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked, this, &AddNewTorrentDialog::renameSelectedFile);
connect(editHotkey, &QShortcut::activated
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked
, this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
m_ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
}
@@ -208,22 +211,17 @@ void AddNewTorrentDialog::setSavePathHistoryLength(int value)
void AddNewTorrentDialog::loadState()
{
Utils::Gui::resize(this, m_storeDialogSize);
m_ui->splitter->restoreState(m_storeSplitterState);
m_headerState = settings()->loadValue(KEY_TREEHEADERSTATE).toByteArray();
const QSize newSize = Utils::Gui::scaledSize(this, size());
const int width = settings()->loadValue(KEY_WIDTH, newSize.width()).toInt();
const int height = newSize.height();
resize(width, height);
m_ui->toolButtonAdvanced->setChecked(settings()->loadValue(KEY_EXPANDED).toBool());
}
void AddNewTorrentDialog::saveState()
{
m_storeDialogSize = size();
m_storeSplitterState = m_ui->splitter->saveState();
if (m_contentModel)
settings()->storeValue(KEY_TREEHEADERSTATE, m_ui->contentTreeView->header()->saveState());
settings()->storeValue(KEY_WIDTH, width());
settings()->storeValue(KEY_EXPANDED, m_ui->toolButtonAdvanced->isChecked());
}
void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
@@ -234,7 +232,7 @@ void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParam
// Launch downloader
// TODO: Don't save loaded torrent to file, just use downloaded data!
Net::DownloadHandler *handler = Net::DownloadManager::instance()->download(
Net::DownloadRequest(source).limit(10485760 /* 10MB */).handleRedirectToMagnet(true).saveToFile(true));
Net::DownloadRequest(source).limit(MAX_TORRENT_SIZE).handleRedirectToMagnet(true).saveToFile(true));
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QString &)>(&Net::DownloadHandler::downloadFinished)
, dlg, &AddNewTorrentDialog::handleDownloadFinished);
connect(handler, &Net::DownloadHandler::downloadFailed, dlg, &AddNewTorrentDialog::handleDownloadFailed);
@@ -249,11 +247,7 @@ void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParam
ok = dlg->loadTorrent(source);
if (ok)
#ifdef Q_OS_MAC
dlg->exec();
#else
dlg->open();
#endif
dlg->QDialog::show();
else
delete dlg;
}
@@ -313,7 +307,7 @@ bool AddNewTorrentDialog::loadTorrent(const QString &torrentPath)
return false;
}
m_ui->lblhash->setText(m_hash);
m_ui->labelHashData->setText(m_hash);
setupTreeview();
TMMChanged(m_ui->comboTTM->currentIndex());
return true;
@@ -358,7 +352,7 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
BitTorrent::Session::instance()->loadMetadata(magnetUri);
setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
m_ui->lblhash->setText(m_hash);
m_ui->labelHashData->setText(m_hash);
return true;
}
@@ -372,33 +366,12 @@ void AddNewTorrentDialog::showEvent(QShowEvent *event)
raise();
}
void AddNewTorrentDialog::showAdvancedSettings(bool show)
{
const int minimumW = minimumWidth();
setMinimumWidth(width()); // to remain the same width
if (show) {
m_ui->toolButtonAdvanced->setText(QString::fromUtf8(C_UP));
m_ui->groupBoxSettings->setVisible(true);
m_ui->infoGroup->setVisible(true);
m_ui->contentTreeView->setVisible(m_hasMetadata);
static_cast<QVBoxLayout *>(layout())->insertWidget(layout()->indexOf(m_ui->checkBoxNeverShow) + 1, m_ui->toolButtonAdvanced);
}
else {
m_ui->toolButtonAdvanced->setText(QString::fromUtf8(C_DOWN));
m_ui->groupBoxSettings->setVisible(false);
m_ui->infoGroup->setVisible(false);
m_ui->buttonsHLayout->insertWidget(0, layout()->takeAt(layout()->indexOf(m_ui->checkBoxNeverShow) + 1)->widget());
}
adjustSize();
setMinimumWidth(minimumW);
}
void AddNewTorrentDialog::saveSavePathHistory() const
{
// Get current history
QStringList history = settings()->loadValue(KEY_SAVEPATHHISTORY).toStringList();
QVector<QDir> historyDirs;
for (const QString &path : qAsConst(history))
for (const QString &path : asConst(history))
historyDirs << QDir {path};
const QDir selectedSavePath {m_ui->savePath->selectedPath()};
@@ -426,7 +399,7 @@ int AddNewTorrentDialog::indexOfSavePath(const QString &savePath)
void AddNewTorrentDialog::updateDiskSpaceLabel()
{
// Determine torrent size
qulonglong torrentSize = 0;
qlonglong torrentSize = 0;
if (m_hasMetadata) {
if (m_contentModel) {
@@ -446,7 +419,7 @@ void AddNewTorrentDialog::updateDiskSpaceLabel()
sizeString += tr("Free space on disk: %1").arg(Utils::Misc::friendlyUnit(Utils::Fs::freeDiskSpaceOnPath(
m_ui->savePath->selectedPath())));
sizeString += ')';
m_ui->labelSize->setText(sizeString);
m_ui->labelSizeData->setText(sizeString);
}
void AddNewTorrentDialog::onSavePathChanged(const QString &newPath)
@@ -479,107 +452,6 @@ void AddNewTorrentDialog::setSavePath(const QString &newPath)
onSavePathChanged(newPath);
}
void AddNewTorrentDialog::renameSelectedFile()
{
const QModelIndexList selectedIndexes = m_ui->contentTreeView->selectionModel()->selectedRows(0);
if (selectedIndexes.size() != 1) return;
const QModelIndex modelIndex = selectedIndexes.first();
if (!modelIndex.isValid()) return;
// Ask for new name
bool ok = false;
QString newName = AutoExpandableDialog::getText(this, tr("Renaming"), tr("New name:"), QLineEdit::Normal, modelIndex.data().toString(), &ok)
.trimmed();
if (!ok) return;
if (newName.isEmpty() || !Utils::Fs::isValidFileSystemName(newName)) {
RaisedMessageBox::warning(this, tr("Rename error"),
tr("The name is empty or contains forbidden characters, please choose a different one."),
QMessageBox::Ok);
return;
}
if (m_contentModel->itemType(modelIndex) == TorrentContentModelItem::FileType) {
// renaming a file
const int fileIndex = m_contentModel->getFileIndex(modelIndex);
if (newName.endsWith(QB_EXT))
newName.chop(QB_EXT.size());
const QString oldFileName = m_torrentInfo.fileName(fileIndex);
const QString oldFilePath = m_torrentInfo.filePath(fileIndex);
const QString newFilePath = oldFilePath.leftRef(oldFilePath.size() - oldFileName.size()) + newName;
if (oldFileName == newName) {
qDebug("Name did not change: %s", qUtf8Printable(oldFileName));
return;
}
// check if that name is already used
for (int i = 0; i < m_torrentInfo.filesCount(); ++i) {
if (i == fileIndex) continue;
if (Utils::Fs::sameFileNames(m_torrentInfo.filePath(i), newFilePath)) {
RaisedMessageBox::warning(this, tr("Rename error"),
tr("This name is already in use in this folder. Please use a different name."),
QMessageBox::Ok);
return;
}
}
qDebug("Renaming %s to %s", qUtf8Printable(oldFilePath), qUtf8Printable(newFilePath));
m_torrentInfo.renameFile(fileIndex, newFilePath);
m_contentModel->setData(modelIndex, newName);
}
else {
// renaming a folder
QStringList pathItems;
pathItems << modelIndex.data().toString();
QModelIndex parent = m_contentModel->parent(modelIndex);
while (parent.isValid()) {
pathItems.prepend(parent.data().toString());
parent = m_contentModel->parent(parent);
}
const QString oldPath = pathItems.join('/');
pathItems.removeLast();
pathItems << newName;
QString newPath = pathItems.join('/');
if (Utils::Fs::sameFileNames(oldPath, newPath)) {
qDebug("Name did not change");
return;
}
if (!newPath.endsWith('/')) newPath += '/';
// Check for overwriting
for (int i = 0; i < m_torrentInfo.filesCount(); ++i) {
const QString currentName = m_torrentInfo.filePath(i);
#if defined(Q_OS_UNIX) || defined(Q_WS_QWS)
if (currentName.startsWith(newPath, Qt::CaseSensitive)) {
#else
if (currentName.startsWith(newPath, Qt::CaseInsensitive)) {
#endif
RaisedMessageBox::warning(this, tr("The folder could not be renamed"),
tr("This name is already in use in this folder. Please use a different name."),
QMessageBox::Ok);
return;
}
}
// Replace path in all files
for (int i = 0; i < m_torrentInfo.filesCount(); ++i) {
const QString currentName = m_torrentInfo.filePath(i);
if (currentName.startsWith(oldPath)) {
QString newName = currentName;
newName.replace(0, oldPath.length(), newPath);
newName = Utils::Fs::expandPath(newName);
qDebug("Rename %s to %s", qUtf8Printable(currentName), qUtf8Printable(newName));
m_torrentInfo.renameFile(i, newName);
}
}
// Rename folder in torrent files model too
m_contentModel->setData(modelIndex, newName);
}
}
void AddNewTorrentDialog::populateSavePathComboBox()
{
m_ui->savePath->clear();
@@ -619,21 +491,21 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
QAction *act = myFilesLlistMenu.exec(QCursor::pos());
if (act) {
if (act == actRename) {
renameSelectedFile();
m_ui->contentTreeView->renameSelectedFile(m_torrentInfo);
}
else {
int prio = prio::NORMAL;
BitTorrent::FilePriority prio = BitTorrent::FilePriority::Normal;
if (act == m_ui->actionHigh)
prio = prio::HIGH;
prio = BitTorrent::FilePriority::High;
else if (act == m_ui->actionMaximum)
prio = prio::MAXIMUM;
prio = BitTorrent::FilePriority::Maximum;
else if (act == m_ui->actionNotDownloaded)
prio = prio::IGNORED;
prio = BitTorrent::FilePriority::Ignored;
qDebug("Setting files priority");
foreach (const QModelIndex &index, selectedRows) {
qDebug("Setting priority(%d) for file at row %d", prio, index.row());
m_contentModel->setData(m_contentModel->index(index.row(), PRIORITY, index.parent()), prio);
for (const QModelIndex &index : selectedRows) {
qDebug("Setting priority(%d) for file at row %d", static_cast<int>(prio), index.row());
m_contentModel->setData(m_contentModel->index(index.row(), PRIORITY, index.parent()), static_cast<int>(prio));
}
}
}
@@ -641,9 +513,6 @@ void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
void AddNewTorrentDialog::accept()
{
if (!m_hasMetadata)
disconnect(this, SLOT(updateMetadata(const BitTorrent::TorrentInfo&)));
// TODO: Check if destination actually exists
m_torrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked();
@@ -689,7 +558,6 @@ void AddNewTorrentDialog::accept()
void AddNewTorrentDialog::reject()
{
if (!m_hasMetadata) {
disconnect(this, SLOT(updateMetadata(BitTorrent::TorrentInfo)));
setMetadataProgressIndicator(false);
BitTorrent::Session::instance()->cancelLoadMetadata(m_hash);
}
@@ -701,7 +569,8 @@ void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &info)
{
if (info.hash() != m_hash) return;
disconnect(this, SLOT(updateMetadata(BitTorrent::TorrentInfo)));
disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataLoaded, this, &AddNewTorrentDialog::updateMetadata);
if (!info.isValid()) {
RaisedMessageBox::critical(this, tr("I/O Error"), ("Invalid metadata."));
setMetadataProgressIndicator(false, tr("Invalid metadata"));
@@ -729,16 +598,16 @@ void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, co
void AddNewTorrentDialog::setupTreeview()
{
if (!m_hasMetadata) {
setCommentText(tr("Not Available", "This comment is unavailable"));
m_ui->labelDate->setText(tr("Not Available", "This date is unavailable"));
m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
}
else {
// Set dialog title
setWindowTitle(m_torrentInfo.name());
// Set torrent information
setCommentText(Utils::Misc::parseHtmlLinks(m_torrentInfo.comment()));
m_ui->labelDate->setText(!m_torrentInfo.creationDate().isNull() ? m_torrentInfo.creationDate().toString(Qt::DefaultLocaleShortDate) : tr("Not available"));
m_ui->labelCommentData->setText(Utils::Misc::parseHtmlLinks(m_torrentInfo.comment()));
m_ui->labelDateData->setText(!m_torrentInfo.creationDate().isNull() ? m_torrentInfo.creationDate().toString(Qt::DefaultLocaleShortDate) : tr("Not available"));
// Prepare content tree
m_contentModel = new TorrentContentFilterModel(this);
@@ -765,7 +634,6 @@ void AddNewTorrentDialog::setupTreeview()
}
updateDiskSpaceLabel();
showAdvancedSettings(settings()->loadValue(KEY_EXPANDED, false).toBool());
}
void AddNewTorrentDialog::handleDownloadFailed(const QString &url, const QString &reason)
@@ -800,7 +668,6 @@ void AddNewTorrentDialog::TMMChanged(int index)
m_ui->groupBoxSavePath->setEnabled(true);
m_ui->savePath->blockSignals(false);
m_ui->savePath->setCurrentIndex(m_oldIndex < m_ui->savePath->count() ? m_oldIndex : m_ui->savePath->count() - 1);
m_ui->toolButtonAdvanced->setEnabled(true);
}
else {
m_ui->groupBoxSavePath->setEnabled(false);
@@ -808,23 +675,9 @@ void AddNewTorrentDialog::TMMChanged(int index)
m_ui->savePath->clear();
QString savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->categoryComboBox->currentText());
m_ui->savePath->addItem(savePath);
m_ui->toolButtonAdvanced->setChecked(true);
m_ui->toolButtonAdvanced->setEnabled(false);
showAdvancedSettings(true);
}
}
void AddNewTorrentDialog::setCommentText(const QString &str) const
{
m_ui->commentLabel->setText(str);
// workaround for the additional space introduced by QScrollArea
int lineHeight = m_ui->commentLabel->fontMetrics().lineSpacing();
int lines = 1 + str.count('\n');
int height = lineHeight * lines;
m_ui->scrollArea->setMaximumHeight(height);
}
void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked)
{
m_torrentGuard->setAutoRemove(!checked);

View File

@@ -31,11 +31,11 @@
#include <QDialog>
#include <QScopedPointer>
#include <QShortcut>
#include "base/bittorrent/addtorrentparams.h"
#include "base/bittorrent/infohash.h"
#include "base/bittorrent/torrentinfo.h"
#include "base/settingvalue.h"
namespace BitTorrent
{
@@ -72,11 +72,9 @@ public:
static void show(QString source, QWidget *parent);
private slots:
void showAdvancedSettings(bool show);
void displayContentTreeMenu(const QPoint &);
void updateDiskSpaceLabel();
void onSavePathChanged(const QString &newPath);
void renameSelectedFile();
void updateMetadata(const BitTorrent::TorrentInfo &info);
void handleDownloadFailed(const QString &url, const QString &reason);
void handleRedirectedToMagnet(const QString &url, const QString &magnetUri);
@@ -99,7 +97,6 @@ private:
void saveState();
void setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText = QString());
void setupTreeview();
void setCommentText(const QString &str) const;
void setSavePath(const QString &newPath);
void showEvent(QShowEvent *event) override;
@@ -115,6 +112,9 @@ private:
int m_oldIndex;
QScopedPointer<TorrentFileGuard> m_torrentGuard;
BitTorrent::AddTorrentParams m_torrentParams;
CachedSettingValue<QSize> m_storeDialogSize;
CachedSettingValue<QByteArray> m_storeSplitterState;
};
#endif // ADDNEWTORRENTDIALOG_H

View File

@@ -6,37 +6,346 @@
<rect>
<x>0</x>
<y>0</y>
<width>414</width>
<height>630</height>
<width>900</width>
<height>620</height>
</rect>
</property>
<layout class="QVBoxLayout" name="AddNewTorrentDialogLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QFrame" name="torrentoptionsFrame">
<layout class="QVBoxLayout" name="mainlayout_addui">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="managementLayout">
<item>
<widget class="QLabel" name="labelTorrentManagementMode">
<property name="text">
<string>Torrent Management Mode:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboTTM">
<property name="toolTip">
<string>Automatic mode means that various torrent properties(eg save path) will be decided by the associated category</string>
</property>
<item>
<property name="text">
<string>Manual</string>
</property>
</item>
<item>
<property name="text">
<string>Automatic</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBoxSavePath">
<property name="title">
<string>Save at</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="FileSystemPathComboEdit" name="savePath" native="true"/>
</item>
<item alignment="Qt::AlignRight">
<widget class="QCheckBox" name="checkBoxRememberLastSavePath">
<property name="text">
<string>Remember last used save path</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBoxSettings">
<property name="title">
<string>Torrent settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="categoryLayout">
<item>
<widget class="QLabel" name="labelCategory">
<property name="text">
<string>Category:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="categoryComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAtTop</enum>
</property>
</widget>
</item>
</layout>
</item>
<item alignment="Qt::AlignRight">
<widget class="QCheckBox" name="defaultCategoryCheckbox">
<property name="text">
<string>Set as default category</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QCheckBox" name="doNotDeleteTorrentCheckBox">
<property name="toolTip">
<string>When checked, the .torrent file will not be deleted despite the settings at the &quot;Download&quot; page of the options dialog</string>
</property>
<property name="text">
<string>Do not delete .torrent file</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="firstLastCheckBox">
<property name="text">
<string>Download first and last pieces first</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="skipCheckingCheckBox">
<property name="text">
<string>Skip hash check</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="sequentialCheckBox">
<property name="text">
<string>Download in sequential order</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="createSubfolderCheckBox">
<property name="text">
<string>Create subfolder</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="startTorrentCheckBox">
<property name="text">
<string>Start torrent</string>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="infoGroup">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Torrent information</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<widget class="QLabel" name="labelDate">
<property name="text">
<string>Date:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="labelSizeData"/>
</item>
<item row="1" column="1">
<widget class="QLabel" name="labelDateData"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelSize">
<property name="text">
<string>Size:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="labelHashData">
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QScrollArea" name="scrollArea">
<property name="styleSheet">
<string notr="true">background-color: rgba(0, 0, 0, 0);</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>421</width>
<height>68</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="labelCommentData">
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelHash">
<property name="text">
<string>Hash:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelComment">
<property name="text">
<string>Comment:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="TorrentContentTreeView" name="contentTreeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="buttonsHLayout">
<item>
<widget class="QLabel" name="labelTorrentManagementMode">
<widget class="QCheckBox" name="checkBoxNeverShow">
<property name="text">
<string>Torrent Management Mode:</string>
<string>Never show again</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboTTM">
<property name="toolTip">
<string>Automatic mode means that various torrent properties(eg save path) will be decided by the associated category</string>
</property>
<item>
<property name="text">
<string>Manual</string>
</property>
</item>
<item>
<property name="text">
<string>Automatic</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
@@ -53,289 +362,7 @@
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBoxSavePath">
<property name="title">
<string>Save at</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="FileSystemPathComboEdit" name="savePath" native="true"/>
</item>
<item>
<widget class="QCheckBox" name="checkBoxRememberLastSavePath">
<property name="text">
<string>Remember last used save path</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="doNotDeleteTorrentCheckBox">
<property name="toolTip">
<string>When checked, the .torrent file will not be deleted despite the settings at the &quot;Download&quot; page of the options dialog</string>
</property>
<property name="text">
<string>Do not delete .torrent file</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxNeverShow">
<property name="text">
<string>Never show again</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolButtonAdvanced">
<property name="text">
<string notr="true">▼</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBoxSettings">
<property name="title">
<string>Torrent settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="2">
<widget class="QCheckBox" name="defaultCategoryCheckbox">
<property name="text">
<string>Set as default category</string>
</property>
</widget>
</item>
<item row="0" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_1">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Category:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="categoryComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="insertPolicy">
<enum>QComboBox::InsertAtTop</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="startTorrentCheckBox">
<property name="text">
<string>Start torrent</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="skipCheckingCheckBox">
<property name="text">
<string>Skip hash check</string>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>35</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="createSubfolderCheckBox">
<property name="text">
<string>Create subfolder</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="sequentialCheckBox">
<property name="text">
<string>Download in sequential order</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="firstLastCheckBox">
<property name="text">
<string>Download first and last pieces first</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="infoGroup">
<property name="title">
<string>Torrent information</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="1">
<widget class="QLabel" name="lblhash">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Hash:</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="TorrentContentTreeView" name="contentTreeView">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="labelDate">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Date:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Size:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="labelSize">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Comment:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>321</width>
<height>69</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="commentLabel">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="buttonsHLayout">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QProgressBar" name="progMetaLoading">
<property name="sizePolicy">
@@ -353,13 +380,17 @@
<property name="textVisible">
<bool>false</bool>
</property>
<property name="format">
<string notr="true">%p%</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="lblMetaLoading"/>
<widget class="QLabel" name="lblMetaLoading">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
@@ -405,18 +436,6 @@
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>savePath</tabstop>
<tabstop>checkBoxRememberLastSavePath</tabstop>
<tabstop>checkBoxNeverShow</tabstop>
<tabstop>toolButtonAdvanced</tabstop>
<tabstop>startTorrentCheckBox</tabstop>
<tabstop>skipCheckingCheckBox</tabstop>
<tabstop>categoryComboBox</tabstop>
<tabstop>defaultCategoryCheckbox</tabstop>
<tabstop>scrollArea</tabstop>
<tabstop>contentTreeView</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
@@ -426,8 +445,8 @@
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>403</x>
<y>579</y>
<x>928</x>
<y>855</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
@@ -442,8 +461,8 @@
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>403</x>
<y>579</y>
<x>928</x>
<y>855</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
@@ -451,22 +470,6 @@
</hint>
</hints>
</connection>
<connection>
<sender>categoryComboBox</sender>
<signal>currentIndexChanged(int)</signal>
<receiver>AddNewTorrentDialog</receiver>
<slot>categoryChanged(int)</slot>
<hints>
<hint type="sourcelabel">
<x>337</x>
<y>205</y>
</hint>
<hint type="destinationlabel">
<x>403</x>
<y>160</y>
</hint>
</hints>
</connection>
<connection>
<sender>comboTTM</sender>
<signal>currentIndexChanged(int)</signal>
@@ -474,12 +477,28 @@
<slot>TMMChanged(int)</slot>
<hints>
<hint type="sourcelabel">
<x>200</x>
<y>19</y>
<x>250</x>
<y>53</y>
</hint>
<hint type="destinationlabel">
<x>206</x>
<y>294</y>
<x>467</x>
<y>249</y>
</hint>
</hints>
</connection>
<connection>
<sender>categoryComboBox</sender>
<signal>currentIndexChanged(int)</signal>
<receiver>AddNewTorrentDialog</receiver>
<slot>categoryChanged(int)</slot>
<hints>
<hint type="sourcelabel">
<x>266</x>
<y>231</y>
</hint>
<hint type="destinationlabel">
<x>467</x>
<y>249</y>
</hint>
</hints>
</connection>

View File

@@ -36,6 +36,7 @@
#include <QNetworkInterface>
#include "base/bittorrent/session.h"
#include "base/global.h"
#include "base/preferences.h"
#include "base/unicodestrings.h"
#include "app/application.h"
@@ -73,6 +74,7 @@ enum AdvSettingsRows
CONFIRM_REMOVE_ALL_TAGS,
DOWNLOAD_TRACKER_FAVICON,
SAVE_PATH_HISTORY_LENGTH,
ENABLE_SPEED_WIDGET,
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC))
USE_ICON_THEME,
#endif
@@ -82,6 +84,7 @@ enum AdvSettingsRows
#if LIBTORRENT_VERSION_NUM >= 10100
ASYNC_IO_THREADS,
#endif
CHECKING_MEM_USAGE,
// cache
DISK_CACHE,
DISK_CACHE_TTL,
@@ -151,6 +154,8 @@ void AdvancedSettings::saveAdvancedSettings()
// Async IO threads
session->setAsyncIOThreads(spinBoxAsyncIOThreads.value());
#endif
// Checking Memory Usage
session->setCheckingMemUsage(spinBoxCheckingMemUsage.value());
// Disk write cache
session->setDiskCacheSize(spinBoxCache.value());
session->setDiskCacheTTL(spinBoxCacheTTL.value());
@@ -218,6 +223,7 @@ void AdvancedSettings::saveAdvancedSettings()
// Misc GUI properties
mainWindow->setDownloadTrackerFavicon(checkBoxTrackerFavicon.isChecked());
AddNewTorrentDialog::setSavePathHistoryLength(spinBoxSavePathHistoryLength.value());
pref->setSpeedWidgetEnabled(checkBoxSpeedWidgetEnabled.isChecked());
// Tracker
session->setTrackerEnabled(checkBoxTrackerStatus.isChecked());
@@ -284,13 +290,13 @@ void AdvancedSettings::updateInterfaceAddressCombo()
};
if (ifaceName.isEmpty()) {
foreach (const QHostAddress &ip, QNetworkInterface::allAddresses())
for (const QHostAddress &ip : asConst(QNetworkInterface::allAddresses()))
populateCombo(ip.toString(), ip.protocol());
}
else {
const QNetworkInterface iface = QNetworkInterface::interfaceFromName(ifaceName);
const QList<QNetworkAddressEntry> addresses = iface.addressEntries();
foreach (const QNetworkAddressEntry &entry, addresses) {
for (const QNetworkAddressEntry &entry : addresses) {
const QHostAddress ip = entry.ip();
populateCombo(ip.toString(), ip.protocol());
}
@@ -323,12 +329,19 @@ void AdvancedSettings::loadAdvancedSettings()
spinBoxAsyncIOThreads.setValue(session->asyncIOThreads());
addRow(ASYNC_IO_THREADS, tr("Asynchronous I/O threads"), &spinBoxAsyncIOThreads);
#endif
// Checking Memory Usage
spinBoxCheckingMemUsage.setMinimum(1);
spinBoxCheckingMemUsage.setValue(session->checkingMemUsage());
spinBoxCheckingMemUsage.setSuffix(tr(" MiB"));
addRow(CHECKING_MEM_USAGE, tr("Outstanding memory when checking torrents"), &spinBoxCheckingMemUsage);
// Disk write cache
spinBoxCache.setMinimum(-1);
// When build as 32bit binary, set the maximum at less than 2GB to prevent crashes.
// These macros may not be available on compilers other than MSVC and GCC
#if defined(__x86_64__) || defined(_M_X64)
spinBoxCache.setMaximum(4096);
spinBoxCache.setMaximum(33554431); // 32768GiB
#else
// allocate 1536MiB and leave 512MiB to the rest of program data in RAM
spinBoxCache.setMaximum(1536);
@@ -337,8 +350,8 @@ void AdvancedSettings::loadAdvancedSettings()
updateCacheSpinSuffix(spinBoxCache.value());
addRow(DISK_CACHE, tr("Disk cache"), &spinBoxCache);
// Disk cache expiry
spinBoxCacheTTL.setMinimum(15);
spinBoxCacheTTL.setMaximum(600);
spinBoxCacheTTL.setMinimum(1);
spinBoxCacheTTL.setMaximum(std::numeric_limits<int>::max());
spinBoxCacheTTL.setValue(session->diskCacheTTL());
spinBoxCacheTTL.setSuffix(tr(" s", " seconds"));
addRow(DISK_CACHE_TTL, tr("Disk cache expiry interval"), &spinBoxCacheTTL);
@@ -423,7 +436,7 @@ void AdvancedSettings::loadAdvancedSettings()
const QString currentInterface = session->networkInterface();
bool interfaceExists = currentInterface.isEmpty();
int i = 1;
foreach (const QNetworkInterface& iface, QNetworkInterface::allInterfaces()) {
for (const QNetworkInterface &iface : asConst(QNetworkInterface::allInterfaces())) {
// This line fixes a Qt bug => https://bugreports.qt.io/browse/QTBUG-52633
// Tested in Qt 5.6.0. For more info see:
// https://github.com/qbittorrent/qBittorrent/issues/5131
@@ -467,6 +480,9 @@ void AdvancedSettings::loadAdvancedSettings()
spinBoxSavePathHistoryLength.setRange(AddNewTorrentDialog::minPathHistoryLength, AddNewTorrentDialog::maxPathHistoryLength);
spinBoxSavePathHistoryLength.setValue(AddNewTorrentDialog::savePathHistoryLength());
addRow(SAVE_PATH_HISTORY_LENGTH, tr("Save path history length"), &spinBoxSavePathHistoryLength);
// Enable speed graphs
checkBoxSpeedWidgetEnabled.setChecked(pref->isSpeedWidgetEnabled());
addRow(ENABLE_SPEED_WIDGET, tr("Enable speed graphs"), &checkBoxSpeedWidgetEnabled);
// Tracker State
checkBoxTrackerStatus.setChecked(session->isTrackerEnabled());
addRow(TRACKER_STATUS, tr("Enable embedded tracker"), &checkBoxTrackerStatus);

View File

@@ -59,13 +59,13 @@ private:
template <typename T> void addRow(int row, const QString &rowText, T *widget);
QLabel labelQbtLink, labelLibtorrentLink;
QSpinBox spinBoxAsyncIOThreads, spinBoxCache, spinBoxSaveResumeDataInterval, spinBoxOutgoingPortsMin, spinBoxOutgoingPortsMax, spinBoxListRefresh, spinBoxMaxHalfOpen,
QSpinBox spinBoxAsyncIOThreads, spinBoxCheckingMemUsage, spinBoxCache, spinBoxSaveResumeDataInterval, spinBoxOutgoingPortsMin, spinBoxOutgoingPortsMax, spinBoxListRefresh, spinBoxMaxHalfOpen,
spinBoxTrackerPort, spinBoxCacheTTL, spinBoxSendBufferWatermark, spinBoxSendBufferLowWatermark,
spinBoxSendBufferWatermarkFactor, spinBoxSavePathHistoryLength;
QCheckBox checkBoxOsCache, checkBoxRecheckCompleted, checkBoxResolveCountries, checkBoxResolveHosts, checkBoxSuperSeeding,
checkBoxProgramNotifications, checkBoxTorrentAddedNotifications, checkBoxTrackerFavicon, checkBoxTrackerStatus,
checkBoxConfirmTorrentRecheck, checkBoxConfirmRemoveAllTags, checkBoxListenIPv6, checkBoxAnnounceAllTrackers, checkBoxAnnounceAllTiers,
checkBoxGuidedReadCache, checkBoxMultiConnectionsPerIp, checkBoxSuggestMode, checkBoxCoalesceRW;
checkBoxGuidedReadCache, checkBoxMultiConnectionsPerIp, checkBoxSuggestMode, checkBoxCoalesceRW, checkBoxSpeedWidgetEnabled;
QComboBox comboBoxInterface, comboBoxInterfaceAddress, comboBoxUtpMixedMode, comboBoxChokingAlgorithm, comboBoxSeedChokingAlgorithm;
QLineEdit lineEditAnnounceIP;

View File

@@ -48,7 +48,7 @@ AutoExpandableDialog::~AutoExpandableDialog()
QString AutoExpandableDialog::getText(QWidget *parent, const QString &title, const QString &label,
QLineEdit::EchoMode mode, const QString &text,
bool *ok, Qt::InputMethodHints inputMethodHints)
bool *ok, const bool excludeExtension, Qt::InputMethodHints inputMethodHints)
{
AutoExpandableDialog d(parent);
d.setWindowTitle(title);
@@ -57,6 +57,16 @@ QString AutoExpandableDialog::getText(QWidget *parent, const QString &title, con
d.m_ui->textEdit->setEchoMode(mode);
d.m_ui->textEdit->setInputMethodHints(inputMethodHints);
d.m_ui->textEdit->selectAll();
if (excludeExtension) {
int lastDotIndex = text.lastIndexOf('.');
if ((lastDotIndex > 3) && (text.mid(lastDotIndex - 4, 4).toLower() == ".tar"))
lastDotIndex -= 4;
// Select file name without extension, except dot files like .gitignore
if (lastDotIndex > 0)
d.m_ui->textEdit->setSelection(0, lastDotIndex);
}
bool res = d.exec();
if (ok)
*ok = res;

View File

@@ -48,7 +48,7 @@ public:
static QString getText(QWidget *parent, const QString &title, const QString &label,
QLineEdit::EchoMode mode = QLineEdit::Normal, const QString &text = QString(),
bool *ok = nullptr, Qt::InputMethodHints inputMethodHints = Qt::ImhNone);
bool *ok = nullptr, bool excludeExtension = false, Qt::InputMethodHints inputMethodHints = Qt::ImhNone);
protected:
void showEvent(QShowEvent *e) override;

View File

@@ -107,8 +107,8 @@ void BanListOptionsDialog::on_buttonBanIP_clicked()
void BanListOptionsDialog::on_buttonDeleteIP_clicked()
{
QModelIndexList selection = m_ui->bannedIPList->selectionModel()->selectedIndexes();
for (auto &i : selection)
const QModelIndexList selection = m_ui->bannedIPList->selectionModel()->selectedIndexes();
for (const auto &i : selection)
m_sortFilter->removeRow(i.row());
m_modified = true;

View File

@@ -33,6 +33,7 @@
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrenthandle.h"
#include "base/global.h"
#include "guiiconprovider.h"
class CategoryModelItem
@@ -228,7 +229,7 @@ QVariant CategoryFilterModel::data(const QModelIndex &index, int role) const
Qt::ItemFlags CategoryFilterModel::flags(const QModelIndex &index) const
{
if (!index.isValid()) return 0;
if (!index.isValid()) return Qt::NoItemFlags;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
@@ -407,7 +408,7 @@ void CategoryFilterModel::populate()
const QString &category = i.key();
if (m_isSubcategoriesEnabled) {
CategoryModelItem *parent = m_rootItem;
foreach (const QString &subcat, session->expandCategory(category)) {
for (const QString &subcat : asConst(session->expandCategory(category))) {
const QString subcatName = shortName(subcat);
if (!parent->hasChild(subcatName)) {
new CategoryModelItem(
@@ -436,7 +437,7 @@ CategoryModelItem *CategoryFilterModel::findItem(const QString &fullName) const
return m_rootItem->child(fullName);
CategoryModelItem *item = m_rootItem;
foreach (const QString &subcat, BitTorrent::Session::expandCategory(fullName)) {
for (const QString &subcat : asConst(BitTorrent::Session::expandCategory(fullName))) {
const QString subcatName = shortName(subcat);
if (!item->hasChild(subcatName)) return nullptr;
item = item->child(subcatName);

View File

@@ -231,7 +231,7 @@ void CategoryFilterWidget::removeCategory()
void CategoryFilterWidget::removeUnusedCategories()
{
auto session = BitTorrent::Session::instance();
for (const QString &category : copyAsConst(session->categories().keys())) {
for (const QString &category : asConst(session->categories().keys())) {
if (model()->data(static_cast<CategoryFilterProxyModel *>(model())->index(category), Qt::UserRole) == 0)
session->removeCategory(category);
}

View File

@@ -26,7 +26,9 @@
* exception statement from your version.
*/
#include <QTreeView>
#pragma once
#include <QTreeView>
class CategoryFilterWidget : public QTreeView
{

View File

@@ -30,6 +30,7 @@
#include <algorithm>
#include "base/global.h"
#include "base/net/downloadmanager.h"
#include "base/settingsstorage.h"
#include "cookiesmodel.h"
@@ -100,6 +101,6 @@ void CookiesDialog::onButtonDeleteClicked()
}
);
for (const QModelIndex &idx : idxs)
for (const QModelIndex &idx : asConst(idxs))
m_cookiesModel->removeRow(idx.row());
}

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