Compare commits

...

302 Commits

Author SHA1 Message Date
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
sledgehammer999
d1ebbcb35d Bump to 4.1.2 2018-08-12 21:16:29 +03:00
sledgehammer999
2743d998a8 Update Changelog 2018-08-12 21:13:12 +03:00
sledgehammer999
dbbfbaff9f Bump Web API version 2018-08-12 21:10:28 +03:00
sledgehammer999
0be8439cf6 Sync translations from Transifex and run lupdate 2018-08-12 21:10:28 +03:00
Chocobo1
66982c5524 Fix lupdate errors
I suspect lupdate isn't smart enough to figure out what tr() is suppose
to do, so just make it a static function in class.
The error was: tr() cannot be called without context
2018-08-12 20:59:52 +03:00
Chocobo1
85af8547f7 Generate i18n .ts files in the correct directory
Fixup 5b7c089dd2.
Closes #9313.
2018-08-12 20:59:52 +03:00
thalieht
e26977ab2c Add hotkey for toggling focus between the search LineEdits 2018-08-12 16:50:13 +03:00
thalieht
ec1cc783a6 Limit the scope of find torrents hotkey in MainWindow
To TransferListWidget because it will interfere with the SearchWidget's focus hotkey
2018-08-12 16:50:13 +03:00
thalieht
03b00ec045 Add a name filter for search results
Closes #8226
2018-08-12 16:50:13 +03:00
Lukas Greib
5e90156e9e Inhibit sleep regardless of activity
"Active torrents" is a somewhat unintuitive concept as a basis for
preventing sleep, as torrents can become active or inactive on the
network at any time. This brings some predictability to the inhibit
sleep option, and will inhibit sleep as long as there are unpaused
downloads or uploads, regardless of network activity.

Closes #1696, #4592, #4655, #7019, #7159, #7452
2018-08-12 16:50:13 +03:00
thalieht
052206efa1 Add option for regexps in the transferlist search filter's context menu 2018-08-12 16:50:13 +03:00
sledgehammer999
305d73180b Update Changelog 2018-08-12 11:51:43 +03:00
Couchy
80000bf0fd Avoid allocating large memory when loading a .torrent file
`QIODevice::read(qint64 maxSize)` will allocate full `maxSize` of memory no matter
what the real file size was, this caused users to experience out-of-memory
exception on 32-bit qbt.
Also handle the OOM execption if it still fails.

Closes #9064, #9075, #9130, #9239, #9246, #9279.
2018-08-12 11:08:06 +03:00
sledgehammer999
06ebe756e8 Notify users on 1st time close/minimize to tray 2018-08-12 11:08:06 +03:00
sledgehammer999
5fa3d9f19c Revert "Set "close to tray" to false as default"
This reverts commit dc9ec0e408.
2018-08-12 11:08:06 +03:00
Chocobo1
5b4c6d3665 Fix I/O error after fetching magnet metadata
It is caused by an extra path seperator, so removing it fixes it.
Fixup 9612a75faa.
2018-08-12 11:08:06 +03:00
sledgehammer999
77bd0f17d1 Update AppVeyor config 2018-08-12 11:08:06 +03:00
Vladimir Golovnev (Glassez)
03a702cfbd Never save resume data for already paused torrents 2018-08-12 11:08:06 +03:00
Vladimir Golovnev (Glassez)
a932cd2ec1 Reorder resume data saving conditionals
Having conditionals which can break saving of torrent resume data
in order from more likely to less likely is more effective.
2018-08-12 11:08:06 +03:00
Chocobo1
8e5743380a Make ProgramUpdater upgrade to 64-bit qbt when running on 64-bit Windows 2018-08-12 11:08:06 +03:00
Chocobo1
8001eb0368 Avoid regenerating .qm files unnecessarily
Translations included at top level is to avoid regenerating the .qm files every
time when src.pro is processed.
Partially revert 5b7c089dd2.
2018-08-12 11:08:06 +03:00
Chocobo1
f214dc88fc Put temporary files in qbt own temp folder 2018-08-12 11:08:06 +03:00
Chocobo1
5cff5ab135 Refactor GuiIconProvider::generateDifferentSizes() 2018-08-12 11:08:06 +03:00
Chocobo1
82ba154b64 Add const to function signature 2018-08-12 11:08:06 +03:00
thalieht
4ea44bbd2b Set "close to tray" to false as default 2018-08-12 11:08:06 +03:00
Chocobo1
a5e68a8725 Avoid potentially setting the wrong piece priorities
Updating file priorities is an async operation in libtorrent, when we
just updated it and immediately query it, we might get the
old/wrong values, so we rely on `updatedFilePrio` in this case.
2018-08-12 11:08:06 +03:00
Chocobo1
70291014d1 Refactor code
Make the code slightly easier to read.
2018-08-12 11:08:06 +03:00
Chocobo1
1aabcfc30c Add const to function parameters 2018-08-12 11:08:06 +03:00
Thomas Piccirello
aba80e2b1c Send all rechecks in one request 2018-08-12 11:08:06 +03:00
Thomas Piccirello
be683fbcd3 Add WebUI Force Reannounce option 2018-08-12 11:08:06 +03:00
Chocobo1
2bcf09cfa5 Remove static keyword overuse 2018-08-12 11:08:06 +03:00
Chocobo1
697325af63 Move member variable initialization
Move the initialization from constructor body to member initializer
list.
Remove superfluous initializer.
2018-08-12 11:08:06 +03:00
Chocobo1
c21bd77be5 Remove unnecessary dynamic allocation
Also remove unneeded `if ()` conditional.
2018-08-12 11:08:06 +03:00
Chocobo1
d5430adaaa Avoid binding constant reference to returned object
In such cases, it makes no sense doing so.
2018-08-12 11:08:06 +03:00
Chocobo1
9e99a0d3f5 Replace single-character string with character literal
Also remove unnecessary dynamic allocation.
2018-08-12 11:08:06 +03:00
sledgehammer999
d088ab6f43 Remove trailing dot from headings 2018-08-12 11:08:06 +03:00
sledgehammer999
820d510c12 Add a TOC in the coding guidelines 2018-08-12 11:08:06 +03:00
Chocobo1
676847fcd0 Add options in AddNewTorrentDialog
The new checkboxes are: "Download in sequential order", "Download first
and last pieces first".
2018-08-12 11:08:06 +03:00
Chocobo1
0204630ee6 Revise dialog messages
Using critical dialog is a bit too strong as qbt didn't actually run
into something that fails, so downgrade to warning dialog.
2018-08-12 11:08:06 +03:00
Chocobo1
b515c7eda4 Fix typo 2018-08-12 11:08:06 +03:00
Chocobo1
73fcecac76 Simplify code
This avoids "deferencing null pointer" warning from static analyzers.
2018-08-12 11:08:06 +03:00
Chocobo1
a7b82ebcb5 Cache more preference values
These values from Preference class are frequently used.
Also group related variables together.
2018-08-12 11:08:06 +03:00
Eugene Shalygin
f8598b010d Use proper include files in the QtSingleApplication find module.
Look for qtsinglecoreapplication.h when Qt5Widgets module was not found,
and qtsingleapplication.h otherwise. Fixes #9196.

This also removes Qt4 support from the QtSingleApplication find module.
2018-08-12 11:08:06 +03:00
Vladimir Golovnev (Glassez)
93779bcc4b Download favicon using appropriate protocol
Some trackers use UDP protocol but we can't download its favicon
using UDP. Just try to download it using HTTP.
2018-08-12 11:08:06 +03:00
Vladimir Golovnev (Glassez)
938f5b9dd9 Apply proxy settings on DownloadManager creation 2018-08-12 11:08:06 +03:00
Vladimir Golovnev (Glassez)
3b4d9f49d5 Improve torrent initialization
Don't post "torrent resumed" event when torrent starts in "resumed"
state.
Fix confusing names. Now "resumed torrent" means "unpaused torrent"
only. When we load previously added torrent it is called "restored
torrent".
2018-08-12 11:08:06 +03:00
Vladimir Golovnev (Glassez)
171c93af50 Save resume data on torrent change events
Closes #9174.
2018-08-12 11:08:06 +03:00
sledgehammer999
6f81e40106 Revert email address changes in copyright notices 2018-08-12 11:08:06 +03:00
tjjh89017
e19b5cb2ce Add async io threads option to AdvancedSettings 2018-08-12 11:08:06 +03:00
Chocobo1
2c69faca58 Allow save resume interval to be disabled
Also raise the allowable upper limit
2018-08-12 11:08:06 +03:00
Chocobo1
9272151d0a Prolong resume data save interval 2018-08-12 11:08:06 +03:00
Chocobo1
d45ebf5a43 Remove unnecessary resources inclusion 2018-08-12 11:08:06 +03:00
thalieht
8074be7644 Delete several unused #include 2018-08-12 11:08:06 +03:00
thalieht
c99ac99a99 Fix coding style 2018-08-12 11:08:06 +03:00
thalieht
976e2450ec Convert the names used in ui and c++ files from snake_case to camelCase 2018-08-12 11:08:06 +03:00
Chocobo1
7e4db8fafd Fix python version detection
Closes #9146.
2018-08-12 11:08:06 +03:00
Chocobo1
115a409d92 Clear python cache conditionally
Clear the cache artifacts on plugin install and plugin uninstall events.
2018-08-12 11:08:06 +03:00
Chocobo1
c203ab3d16 Refactor function
In SearchPluginManager::updateNova(), omit removing __pycache__ folder and pyc
files, those files will be recreated anyway.
Add const to variables
2018-08-12 11:08:06 +03:00
Chocobo1
5dff96496d Rename function 2018-08-12 11:08:06 +03:00
Chocobo1
f813935011 Cache SearchPluginManager::engineLocation() result
Also the folder is only created on first usage.
2018-08-12 11:08:06 +03:00
Chocobo1
2be719449f Replace less-efficient QProcess::setEnvironment
Also small refactor
2018-08-12 11:08:06 +03:00
Chocobo1
2094c870d5 Simplify function 2018-08-12 11:08:06 +03:00
Chocobo1
4fe93ae8b8 Add checkbox for recursive download dialog
Also group similar options together.
2018-08-12 11:08:06 +03:00
Chocobo1
fff1103cf4 Work around crash when procesing recursive download
The messagebox is modal and exec() it makes it generates a new local
event loop, however the new local event loop will continue to process
libtorrent events (in Session::readAlerts()), at the time exec()
returns, the original libt::alert pointers are lost and resume
processing alerts will cause the crash.
One solution is to make the messagebox use show() and avoid exec().

Closes #9086.
2018-08-12 11:08:06 +03:00
Vladimir Golovnev (Glassez)
8cede43a45 Make TorrentInfo loading behavior uniform 2018-08-12 11:08:06 +03:00
Vladimir Golovnev (Glassez)
9b1fa3a5af Use new DownloadManager interface 2018-08-12 11:08:06 +03:00
Vladimir Golovnev (Glassez)
409e73c074 Implement "Sequential downloading" feature
Closes #6835.
2018-08-12 11:08:06 +03:00
Vladimir Golovnev (Glassez)
c893729d62 Implement DownloadRequest helper 2018-08-12 11:08:06 +03:00
Chocobo1
945466968c Reduce queries to python version
Instead of doing at least 2 queries for python infos, now requires only
1 query (in ideal condition), and the result is cached.
2018-08-12 11:08:06 +03:00
Chocobo1
54f080b755 Move related functions to Utils::Python 2018-08-12 11:08:06 +03:00
Goshik
bfad14d552 Create non-existing path in setLocationAction()
When using qbittorrent-nox it is not always possible to manually create
the target path for torrent moving. This commit allows automatic path
creation. It also allows to display error messages in the
'Set location' window.
2018-08-12 11:08:06 +03:00
Chocobo1
2972e1596d Move optimization flags
Those flags aren't strictly required when compiling but they are
recommended to be on.
2018-08-12 11:08:06 +03:00
Chocobo1
987d2aae88 Turn on Control Flow Guard for MSVC builds
The performance impact should be negligible according to the
documentation on msdn.
Closes #9101.
2018-08-12 11:08:06 +03:00
hannsen
4707d34fad Properly normalize version string before parsing it
This allows python scripts to be formatted in PEP 8 style.
2018-08-12 11:08:06 +03:00
Chocobo1
2ffc09d097 Move qm_gen.pri 2018-08-12 11:08:06 +03:00
Chocobo1
afa8d6bb8f Move .desktop file 2018-08-12 11:08:06 +03:00
Chocobo1
a37ead98e8 Move .ico files into icons dir 2018-08-12 11:08:06 +03:00
Chocobo1
c73cd8d618 Move .qrc files into its own directory 2018-08-12 11:08:06 +03:00
thalieht
800a3aa61e Change file names and classes names to match them 2018-08-12 11:08:06 +03:00
Thomas Piccirello
ebd815be75 Add WebUI support for Mac ⌘ (Command) key
The Command key will now be recognized for WebUI Table multi-selection, as Ctrl is.
2018-08-12 11:08:06 +03:00
Thomas Piccirello
ef669acf89 Implement key functions in Class and refactor 2018-08-12 11:08:06 +03:00
thalieht
ac6426eab1 Fix coding style 2018-08-12 11:08:06 +03:00
Chocobo1
b107b745f2 Revise usage of BOOST_NO_CXX11_RVALUE_REFERENCES
Now the flag will be present when building with boost version <= 1.59.
Closes #8990.
2018-08-12 11:08:06 +03:00
Goshik
3d851a448f Show current save path in 'Set location' window
The feature is useful when the user needs to move their torrent to a
sub or parent folder. If more than one torrent is chosen, the path
of the first selected torrent is used. The window was made wider to
allow more convenient editing of long paths.
2018-08-12 11:08:06 +03:00
Chocobo1
ce133f01aa Replace deprecated function on macOS
Closes #8993.
2018-08-12 11:08:06 +03:00
Chocobo1
492d378537 Disable certain mouse wheel events in Options dialog
The mouse wheel events for QComboBox & QSpinBox widgets in Options
dialog are filtered out.
2018-08-12 11:08:06 +03:00
Chocobo1
7ece484423 Remove duplicate private sections in class
And group related methods & variables together.
2018-08-12 11:08:06 +03:00
Chocobo1
be5ad63e21 Replace post-increment with pre-increment
And post-decrement with pre-decrement.
2018-08-12 11:08:06 +03:00
Eugene Shalygin
bdac8f8db8 Fix option name in winconf-xxx.cmake
The mistake was made by commit 7712d0ada0
2018-08-12 11:08:06 +03:00
Eugene Shalygin
bb893e70c5 Refactor CMake build scripts
1. Use FeatureSummary module to show configuration results.

2. Invert option()/find_package() relationship: instead of
calling find_package(... REQUIRED) when option is set, rely on optional
find package call and PackageName_FOUND variable.

3. Refactor handling options that result in simple preprocessor defines
(actually copy the snippet from libtorrent) so that everything is done
in a single function call.

4. Populate target properties in order to get rid of
include_directories() calls.
2018-08-12 11:08:06 +03:00
Chocobo1
57ec9db532 Fix WebUI cache behavior for css files
The style.css in public & private folders share the same URI, this
confuses the browser cache, so rename one of them.
2018-08-12 11:08:06 +03:00
Chocobo1
0287481001 Send Cache-Control header in WebUI responses
Tune the caching time to be shorter, in case there is a program
update.
Change the cacheability to private, as WebUI resources are not intended
to be cached at proxy.
For uncacheable responses, send out "no-store" explicitly to halt
browser caching.
2018-08-12 11:08:06 +03:00
Chocobo1
0167496ecb Add changelog link in program updater
Closes #8997.
2018-08-12 11:08:06 +03:00
Thomas Piccirello
d86cf193a0 Rename variables for clarity 2018-08-12 11:08:06 +03:00
Thomas Piccirello
246cad1108 Add form-action to CSP
This option restricts all form submissions to the WebUI's origin.
qBittorrent only ever submits forms to the origin, so this is intended as a security measure.
2018-08-12 11:08:06 +03:00
Thomas Piccirello
23bf86a8a8 Add upgrade-insecure-requests to CSP when HTTPS is enabled
This option automatically upgrades all http connections to https.
It ensures http urls cannot be accessed when in https mode, and is intended as a security measure.
2018-08-12 11:08:06 +03:00
thalieht
6ce4c885b9 Fix coding style 2018-08-12 11:08:06 +03:00
Chocobo1
faf84e483a Reset WebUI ban counter on login success 2018-08-12 11:08:06 +03:00
Chocobo1
576004c840 Add logging messages in WebUI login action 2018-08-12 11:08:06 +03:00
Chocobo1
c93b05c293 Replace QRegExp with QRegularExpression
Revise `static` keyword usage, static is added to frequently used
instances.
2018-08-12 11:08:06 +03:00
Chocobo1
55c3813fac Cleanup header include order
Add missing header.
Cleanup license.
2018-08-12 11:08:06 +03:00
Chocobo1
725c6857be Improve WebUI security measures
CSP was erroneously disabled in bad4d94f77
when clickjacking protection is off, now it is back.
Also added CSP 'frame-ancestors' directive when clickjacking
protection is enabled.
2018-08-12 11:08:06 +03:00
Chocobo1
86767c9ab4 Refactor function 2018-08-12 11:08:06 +03:00
Chocobo1
46aa631d2b Improve DownloadFromURL behavior
URL should be considered case sensitive.
2018-08-12 11:08:06 +03:00
Chocobo1
7c61a937c9 Move DownloadFromURLDialog to its own file
Simplify code, no functionality changes.
Remove debug messages.
Capitalize dialog name.
Capitalize class name.
Update license text.
2018-08-12 11:08:06 +03:00
Chocobo1
b8d65dcc45 Add missing header include 2018-08-12 11:08:06 +03:00
Vladimir Golovnev (Glassez)
b9ab83eaf2 Don't use RSS feed URLs as base for file names
RSS feed URLs can be too long and exceed max path limit.
Add RSS feed UIDs and use UIDs as base for file names instead of URLs.
Closes #8399.
2018-08-12 11:08:06 +03:00
Chocobo1
8b7b563992 Add constexpr to IndexInterval class
Add const to IndexRange private members.
Remove redundant inline specifier.
Add missing parentheses.
2018-08-12 11:08:06 +03:00
Chocobo1
b813a878d7 Add constexpr to TriStateBool class 2018-08-12 11:08:06 +03:00
Chocobo1
54e486c389 Improve Utils::Version class
Add operator>=() and operator<=().
More methods are suitable to be constexpr.
Remove redundant boundary checking.
2018-08-12 11:08:06 +03:00
Chocobo1
12d0a3acc1 Add option to control CSRF protection
Some users are using WebUI with simple port-forwarding from their router,
providing an option to control the protection will save them from setting up an
non-trival web proxy.
Closes #7274.
2018-08-12 11:08:06 +03:00
Chocobo1
6ad2a13386 Add option to control WebUI clickjacking protection
Some users actually want embedding WebUI into their custom build iframe.
Closes #7370.
2018-08-12 11:08:06 +03:00
Chocobo1
2a9c401db9 Update Options dialog layout in WebUI 2018-08-12 11:08:06 +03:00
659 changed files with 250506 additions and 128842 deletions

View File

@@ -45,8 +45,7 @@ before_build:
- CALL "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars32.bat"
- SET PATH=%PATH%;c:\qbt\qt5_32\bin;%CACHE_DIR%\jom;
# setup project
- COPY /Y "%CACHE_DIR%\winconf.pri" "%REPO_DIR%"
- COPY /Y "%CACHE_DIR%\winconf-msvc.pri" "%REPO_DIR%"
- COPY /Y "%CACHE_DIR%\conf.pri" "%REPO_DIR%"
# workarounds
- MKLINK /J "c:\qbt\base" "%CACHE_DIR%\base"

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""
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,34 +105,16 @@ 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 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
fi
MY_CMAKE_OPENSSL_HINT="-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/"
@@ -156,6 +122,7 @@ install:
- |
if [ "$TRAVIS_BRANCH" != "$coverity_branch" ]; then
export use_ccache=true
ccache -M 512M
ccache -V && ccache --show-stats && ccache --zero-stats
fi
@@ -166,21 +133,15 @@ script:
if [ "$build_system" = "cmake" ]; then
mkdir build
cd build
cmake -DGUI=${gui} -DCMAKE_INSTALL_PREFIX="$qbt_path" "$MY_CMAKE_OPENSSL_HINT" \
if [ "$gui" = "false" ]; then
DISABLE_GUI_OPTION="-DCMAKE_DISABLE_FIND_PACKAGE_Qt5Widgets=ON"
fi
cmake $DISABLE_GUI_OPTION -DCMAKE_INSTALL_PREFIX="$qbt_path" "$MY_CMAKE_OPENSSL_HINT" \
-G "Ninja" -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=TRUE ..
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 = src/icons/qBittorrent.desktop
source_file = dist/unix/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,7 +1,6 @@
cmake_minimum_required(VERSION 3.5)
cmake_policy(VERSION 3.5)
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)
@@ -27,32 +26,29 @@ add_definitions(-DQBT_VERSION_BUILD=${VER_BUILD})
add_definitions(-DQBT_VERSION="v${PROJECT_VERSION}")
add_definitions(-DQBT_VERSION_2="${PROJECT_VERSION}")
if (UNIX AND NOT APPLE)
include(GNUInstallDirs)
endif (UNIX AND NOT APPLE)
include(GNUInstallDirs)
include(FeatureSummary)
# version requirements
set(requiredBoostVersion 1.35)
set(requiredQtVersion 5.5.1)
if(WIN32)
include(winconf)
endif(WIN32)
# we need options here, because they are used not only in "src" subdir, but in the "dist" dir too
include(CMakeDependentOption)
option(SYSTEM_QTSINGLEAPPLICATION
"Use the system qtsingleapplication library or shipped one otherwise")
option(GUI "Allows to disable GUI for headless running. Disables QtDBus and the GeoIP Database" ON)
option(WEBUI "Allows to disable the WebUI." ON)
option(STACKTRACE "Enable stacktrace feature" ON)
if (UNIX)
cmake_dependent_option(SYSTEMD "Install the systemd service file (headless only)" OFF
"NOT GUI" OFF)
cmake_dependent_option(DBUS "Enable use of QtDBus (GUI only)" ON "GUI" OFF)
endif(UNIX)
# we need options here, at the top level, because they are used not only in "src" subdir, but in the "dist" dir too
include(CompileFeature)
optional_compile_definitions(COUNTRIES_RESOLUTION FEATURE DESCRIPTION "Enable resolving peers IP addresses to countries"
DEFAULT ON DISABLED DISABLE_COUNTRIES_RESOLUTION)
optional_compile_definitions(STACKTRACE FEATURE DESCRIPTION "Enable stacktraces"
DEFAULT ON ENABLED STACKTRACE)
optional_compile_definitions(WEBUI FEATURE DESCRIPTION "Enables built-in HTTP server for headless use"
DEFAULT ON DISABLED DISABLE_WEBUI)
add_subdirectory(src)
add_subdirectory(dist)
feature_summary(DESCRIPTION "\nConfiguration results:" WHAT ALL)

View File

@@ -6,6 +6,31 @@ For programming languages other than C++ (e.g. JavaScript) used in this reposito
**Note 2:** You can use the `uncrustify` program/tool to clean up any source file. Use it with the `uncrustify.cfg` configuration file found in the root folder.
**Note 3:** There is also a style for QtCreator but it doesn't cover all cases. In QtCreator `Tools->Options...->C++->Code Style->Import...` and choose the `codingStyleQtCreator.xml` file found in the root folder.
### Table Of Contents
* [1. New lines &amp; curly braces](#1-new-lines--curly-braces)
* [a. Function blocks, class/struct definitions, namespaces](#a-function-blocks-classstruct-definitions-namespaces)
* [b. Other code blocks](#b-other-code-blocks)
* [c. Blocks in switch's case labels](#c-blocks-in-switchs-case-labels)
* [d. If-else statements](#d-if-else-statements)
* [e. Single statement if blocks](#e-single-statement-if-blocks)
* [f. Acceptable conditions to omit braces](#f-acceptable-conditions-to-omit-braces)
* [g. Brace enclosed initializers](#g-brace-enclosed-initializers)
* [2. Indentation](#2-indentation)
* [3. File encoding and line endings](#3-file-encoding-and-line-endings)
* [4. Initialization lists](#4-initialization-lists)
* [5. Enums](#5-enums)
* [6. Names](#6-names)
* [a. Type names and namespaces](#a-type-names-and-namespaces)
* [b. Variable names](#b-variable-names)
* [c. Private member variable names](#c-private-member-variable-names)
* [7. Header inclusion order](#7-header-inclusion-order)
* [8. Include guard](#8-include-guard)
* [9. Misc](#9-misc)
* [10. Git commit message](#10-git-commit-message)
* [11. Not covered above](#11-not-covered-above)
---
### 1. New lines & curly braces ###
#### a. Function blocks, class/struct definitions, namespaces ####
@@ -165,11 +190,11 @@ QVariantMap map {{"key1", 5}, {"key2", 10}};
### 2. Indentation ###
4 spaces.
### 3. File encoding and line endings. ###
### 3. File encoding and line endings ###
UTF-8 and Unix-like line ending (LF). Unless some platform specific files need other encodings/line endings.
### 4. Initialization lists. ###
### 4. Initialization lists ###
Initialization lists should be vertical. This will allow for more easily readable diffs. The initialization colon should be indented and in its own line along with first argument. The rest of the arguments should be indented too and have the comma prepended.
```c++
myClass::myClass(int a, int b, int c, int d)
@@ -182,7 +207,7 @@ myClass::myClass(int a, int b, int c, int d)
}
```
### 5. Enums. ###
### 5. Enums ###
Enums should be vertical. This will allow for more easily readable diffs. The members should be indented.
```c++
enum Days
@@ -197,7 +222,7 @@ enum Days
};
```
### 6. Names. ###
### 6. Names ###
All names should be camelCased.
#### a. Type names and namespaces ####
@@ -231,7 +256,7 @@ class MyClass
}
```
### 7. Header inclusion order. ###
### 7. Header inclusion order ###
The headers should be placed in the following group order:
1. Module header (in .cpp)
2. C++ Standard Library headers
@@ -297,7 +322,7 @@ Example:
#include "ui_examplewidget.h"
```
### 8. Include guard. ###
### 8. Include guard ###
`#pragma once` should be used instead of "include guard" in new code:
```c++
// examplewidget.h
@@ -313,7 +338,7 @@ class ExampleWidget : public QWidget
```
### 9. Misc. ###
### 9. Misc ###
* Line breaks for long lines with operation:

166
Changelog
View File

@@ -1,3 +1,169 @@
* 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)
- FEATURE: Add async io threads option to AdvancedSettings (tjjh89017)
- FEATURE: Allow save resume interval to be disabled (Chocobo1)
- FEATURE: Add checkbox for recursive download dialog (Chocobo1)
- FEATURE: Add changelog link in program updater (Chocobo1)
- BUGFIX: Avoid allocating large memory when loading a .torrent file (Couchy)
- BUGFIX: Notify users on 1st time close/minimize to tray (sledgehammer999)
- BUGFIX: Fix I/O error after fetching magnet metadata (Chocobo1)
- BUGFIX: Never save resume data for already paused torrents (glassez)
- BUGFIX: Make ProgramUpdater upgrade to 64-bit qbt when running on 64-bit Windows (Chocobo1)
- BUGFIX: Put temporary files in qbt own temp folder (Chocobo1)
- BUGFIX: Avoid potentially setting the wrong piece priorities (Chocobo1)
- BUGFIX: Various code refactorings/improvements (Chocobo1, thalieht, glassez)
- BUGFIX: Add options "Download in sequential order" and "Download first and last pieces first" in AddNewTorrentDialog (Chocobo1)
- BUGFIX: Download favicon using appropriate protocol (glassez)
- BUGFIX: Apply proxy settings on DownloadManager creation (glassez)
- BUGFIX: Improve torrent initialization (glassez)
- BUGFIX: Save resume data on torrent change events (glassez)
- BUGFIX: Increase default resume data save interval (Chocobo1)
- BUGFIX: Work around crash when procesing recursive download. Closes #9086 (Chocobo1)
- BUGFIX: Reduce queries to python version (Chocobo1)
- BUGFIX: Disable certain mouse wheel events in Options dialog (Chocobo1)
- WEBUI: Send all rechecks in one request (Thomas Piccirello)
- WEBUI: Add WebUI Force Reannounce option (Thomas Piccirello)
- WEBUI: Create non-existing path in setLocationAction() (Goshik)
- WEBUI: Add WebUI support for Mac ⌘ (Command) key (Thomas Piccirello)
- WEBUI: Show current save path in 'Set location' window (Goshik)
- WEBUI: Fix WebUI cache behavior for css files (Chocobo1)
- WEBUI: Send Cache-Control header in WebUI responses (Chocobo1)
- WEBUI: Add form-action to CSP (Thomas Piccirello)
- WEBUI: Add upgrade-insecure-requests to CSP when HTTPS is enabled (Thomas Piccirello)
- WEBUI: Reset WebUI ban counter on login success (Chocobo1)
- WEBUI: Add logging messages in WebUI login action (Chocobo1)
- WEBUI: Add option to control CSRF protection (Chocobo1)
- WEBUI: Add option to control WebUI clickjacking protection (Chocobo1)
- RSS: Implement "Sequential downloading" feature. Closes #6835 (glassez)
- RSS: Don't use RSS feed URLs as base for file names. Closes #8399 (glassez)
- SEARCH: Add a name filter for search results (thalieht)
- SEARCH: Fix python version detection (Chocobo1)
- SEARCH: Clear python cache conditionally (Chocobo1)
- SEARCH: Properly normalize version string before parsing it (hannsen)
- WINDOWS: Turn on Control Flow Guard for MSVC builds (Chocobo1)
- MACOS: Replace deprecated function IOPMAssertionCreate() on macOS (Chocobo1)
- OTHER: Fix CMake build with QtSingleApplication. Fixes #9196 (Eugene Shalygin)
* Sun May 27 2018 - sledgehammer999 <sledgehammer999@qbittorrent.org> - v4.1.1
- FEATURE: Add 'Moving' state for torrents being relocated/moved (sledgehammer999)
- FEATURE: Show rechecking progress (sledgehammer999)

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,22 @@
# Helper function for coupling add_feature_info(), option(), and add_definitions()
function(optional_compile_definitions _name)
set(options FEATURE)
set(oneValueArgs DESCRIPTION DEFAULT)
set(multiValueArgs ENABLED DISABLED)
cmake_parse_arguments(OCD "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
option(${_name} "${OCD_DESCRIPTION}" ${OCD_DEFAULT})
if (${${_name}})
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY COMPILE_DEFINITIONS ${OCD_ENABLED})
else()
set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY COMPILE_DEFINITIONS ${OCD_DISABLED})
endif()
if(${OCD_FEATURE})
add_feature_info(${_name} ${_name} "${OCD_DESCRIPTION}")
endif()
endfunction()
macro(feature_option _name _description _default)
option(${_name} "${_description}" ${_default})
add_feature_info(${_name} ${_name} "${_description}")
endmacro()

View File

@@ -99,6 +99,7 @@ list(FIND LibtorrentRasterbar_DEFINITIONS -DTORRENT_USE_OPENSSL LibtorrentRaster
if(LibtorrentRasterbar_ENCRYPTION_INDEX GREATER -1)
find_package(OpenSSL REQUIRED)
set(LibtorrentRasterbar_LIBRARIES ${LibtorrentRasterbar_LIBRARIES} OpenSSL::SSL OpenSSL::Crypto)
list(APPEND LibtorrentRasterbar_INCLUDE_DIRS "${OPENSSL_INCLUDE_DIR}")
set(LibtorrentRasterbar_OPENSSL_ENABLED ON)
endif()
@@ -113,10 +114,10 @@ mark_as_advanced(LibtorrentRasterbar_INCLUDE_DIR LibtorrentRasterbar_LIBRARY
LibtorrentRasterbar_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES
LibtorrentRasterbar_ENCRYPTION_INDEX)
if (LibtorrentRasterbar_FOUND AND NOT TARGET LibtorrentRasterbar::LibTorrent)
add_library(LibtorrentRasterbar::LibTorrent UNKNOWN IMPORTED)
if (LibtorrentRasterbar_FOUND AND NOT TARGET LibtorrentRasterbar::torrent-rasterbar)
add_library(LibtorrentRasterbar::torrent-rasterbar UNKNOWN IMPORTED)
set_target_properties(LibtorrentRasterbar::LibTorrent PROPERTIES
set_target_properties(LibtorrentRasterbar::torrent-rasterbar PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${LibtorrentRasterbar_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${LibtorrentRasterbar_INCLUDE_DIRS}"

View File

@@ -1,94 +1,79 @@
# - Try to find the QtSingleApplication includes and library
# which defines
#
# QTSINGLEAPPLICATION_FOUND - system has QtSingleApplication
# QTSINGLEAPPLICATION_INCLUDE_DIR - where to find header QtSingleApplication
# QTSINGLEAPPLICATION_LIBRARIES - the libraries to link against to use QtSingleApplication
# QTSINGLEAPPLICATION_LIBRARY - where to find the QtSingleApplication library (not for general use)
# QtSingleApplication_FOUND - system has QtSingleApplication
# QtSingleApplication_INCLUDE_DIR - where to find header QtSingleApplication
# QtSingleApplication_LIBRARIES - the libraries to link against to use QtSingleApplication
# QtSingleApplication_LIBRARY - where to find the QtSingleApplication library (not for general use)
# copyright (c) 2013 TI_Eugene ti.eugene@gmail.com
#
# Redistribution and use is allowed according to the terms of the FreeBSD license.
SET(QTSINGLEAPPLICATION_FOUND FALSE)
SET(QtSingleApplication_FOUND FALSE)
IF(QT4_FOUND)
message(STATUS "Looking for Qt4 single application library")
FIND_PATH(QTSINGLEAPPLICATION_INCLUDE_DIR QtSingleApplication
# standard locations
/usr/include
/usr/include/QtSolutions
# qt4 location except mac's frameworks
"${QT_INCLUDE_DIR}/QtSolutions"
# mac's frameworks
${FRAMEWORK_INCLUDE_DIR}/QtSolutions
)
if (Qt5Widgets_FOUND)
set(_includeFileName qtsingleapplication.h)
else()
set(_includeFileName qtsinglecoreapplication.h)
endif()
SET(QTSINGLEAPPLICATION_NAMES ${QTSINGLEAPPLICATION_NAMES}
QtSolutions_SingleApplication-2.6 libQtSolutions_SingleApplication-2.6)
FIND_LIBRARY(QTSINGLEAPPLICATION_LIBRARY
NAMES ${QTSINGLEAPPLICATION_NAMES}
PATHS ${QT_LIBRARY_DIR}
)
ELSEIF(Qt5Core_FOUND)
message(STATUS "Looking for Qt5 single application library")
FOREACH(TOP_INCLUDE_PATH in ${Qt5Core_INCLUDE_DIRS} ${FRAMEWORK_INCLUDE_DIR})
FIND_PATH(QTSINGLEAPPLICATION_INCLUDE_DIR QtSingleApplication ${TOP_INCLUDE_PATH}/QtSolutions)
FOREACH(TOP_INCLUDE_PATH in ${Qt5Core_INCLUDE_DIRS} ${FRAMEWORK_INCLUDE_DIR})
FIND_PATH(QtSingleApplication_INCLUDE_DIR ${_includeFileName} ${TOP_INCLUDE_PATH}/QtSolutions)
IF(QTSINGLEAPPLICATION_INCLUDE_DIR)
BREAK()
ENDIF()
ENDFOREACH()
IF(QtSingleApplication_INCLUDE_DIR)
BREAK()
ENDIF()
ENDFOREACH()
SET(QTSINGLEAPPLICATION_NAMES ${QTSINGLEAPPLICATION_NAMES}
Qt5Solutions_SingleApplication-2.6 libQt5Solutions_SingleApplication-2.6
QtSolutions_SingleApplication-2.6 libQtSolutions_SingleApplication-2.6)
GET_TARGET_PROPERTY(_QT5_CORELIBRARY Qt5::Core LOCATION)
GET_FILENAME_COMPONENT(_QT5_CORELIBRARYPATH ${_QT5_CORELIBRARY} PATH)
SET(QtSingleApplication_NAMES ${QtSingleApplication_NAMES}
Qt5Solutions_SingleApplication-2.6 libQt5Solutions_SingleApplication-2.6
QtSolutions_SingleApplication-2.6 libQtSolutions_SingleApplication-2.6)
GET_TARGET_PROPERTY(_QT5_CORELIBRARY Qt5::Core LOCATION)
GET_FILENAME_COMPONENT(_QT5_CORELIBRARYPATH ${_QT5_CORELIBRARY} PATH)
FIND_LIBRARY(QTSINGLEAPPLICATION_LIBRARY
NAMES ${QTSINGLEAPPLICATION_NAMES}
PATHS ${_QT5_CORELIBRARYPATH}
)
ENDIF()
FIND_LIBRARY(QtSingleApplication_LIBRARY
NAMES ${QtSingleApplication_NAMES}
PATHS ${_QT5_CORELIBRARYPATH}
)
IF (QTSINGLEAPPLICATION_LIBRARY AND QTSINGLEAPPLICATION_INCLUDE_DIR)
IF (QtSingleApplication_LIBRARY AND QtSingleApplication_INCLUDE_DIR)
SET(QTSINGLEAPPLICATION_LIBRARIES ${QTSINGLEAPPLICATION_LIBRARY})
SET(QTSINGLEAPPLICATION_FOUND TRUE)
SET(QtSingleApplication_LIBRARIES ${QtSingleApplication_LIBRARY})
SET(QtSingleApplication_FOUND TRUE)
IF (CYGWIN)
IF(BUILD_SHARED_LIBS)
# No need to define QTSINGLEAPPLICATION_USE_DLL here, because it's default for Cygwin.
# No need to define QtSingleApplication_USE_DLL here, because it's default for Cygwin.
ELSE(BUILD_SHARED_LIBS)
SET (QTSINGLEAPPLICATION_DEFINITIONS -DQTSINGLEAPPLICATION_STATIC)
SET (QtSingleApplication_DEFINITIONS -DQTSINGLEAPPLICATION_STATIC)
ENDIF(BUILD_SHARED_LIBS)
ENDIF (CYGWIN)
ENDIF (QTSINGLEAPPLICATION_LIBRARY AND QTSINGLEAPPLICATION_INCLUDE_DIR)
ENDIF (QtSingleApplication_LIBRARY AND QtSingleApplication_INCLUDE_DIR)
IF (QTSINGLEAPPLICATION_FOUND)
IF (NOT QtSingleApplication_FIND_QUIETLY)
MESSAGE(STATUS "Found QtSingleApplication: ${QTSINGLEAPPLICATION_LIBRARY}")
MESSAGE(STATUS " includes: ${QTSINGLEAPPLICATION_INCLUDE_DIR}")
ENDIF (NOT QtSingleApplication_FIND_QUIETLY)
ELSE (QTSINGLEAPPLICATION_FOUND)
IF (QtSingleApplication_FOUND)
IF (NOT QtSingleApplication_FIND_QUIETLY)
MESSAGE(STATUS "Found QtSingleApplication: ${QtSingleApplication_LIBRARY}")
MESSAGE(STATUS " includes: ${QtSingleApplication_INCLUDE_DIR}")
ENDIF (NOT QtSingleApplication_FIND_QUIETLY)
if(NOT TARGET QtSingleApplication::QtSingleApplication)
add_library(QtSingleApplication::QtSingleApplication UNKNOWN IMPORTED)
set_target_properties(QtSingleApplication::QtSingleApplication PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${QtSingleApplication_INCLUDE_DIR}"
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${QtSingleApplication_INCLUDE_DIR}"
)
if(EXISTS "${QtSingleApplication_LIBRARY}")
set_target_properties(QtSingleApplication::QtSingleApplication PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${QtSingleApplication_LIBRARY}")
endif()
endif(NOT TARGET QtSingleApplication::QtSingleApplication)
ELSE (QtSingleApplication_FOUND)
IF (QtSingleApplication_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find QtSingleApplication library")
ENDIF (QtSingleApplication_FIND_REQUIRED)
ENDIF (QTSINGLEAPPLICATION_FOUND)
ENDIF (QtSingleApplication_FOUND)
MARK_AS_ADVANCED(QTSINGLEAPPLICATION_INCLUDE_DIR QTSINGLEAPPLICATION_LIBRARY)
if(NOT TARGET QtSingleApplication::QtSingleApplication)
add_library(QtSingleApplication::QtSingleApplication UNKNOWN IMPORTED)
set_target_properties(QtSingleApplication::QtSingleApplication PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${QTSINGLEAPPLICATION_INCLUDE_DIR}"
INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${QTSINGLEAPPLICATION_INCLUDE_DIR}"
)
if(EXISTS "${QTSINGLEAPPLICATION_LIBRARY}")
set_target_properties(QtSingleApplication::QtSingleApplication PROPERTIES
IMPORTED_LINK_INTERFACE_LANGUAGES "CXX"
IMPORTED_LOCATION "${QTSINGLEAPPLICATION_LIBRARY}")
endif()
endif(NOT TARGET QtSingleApplication::QtSingleApplication)
MARK_AS_ADVANCED(QtSingleApplication_INCLUDE_DIR QtSingleApplication_LIBRARY)

View File

@@ -1,16 +1,17 @@
# a helper function which appends source to the main qBt target
# sources file names are relative to the the ${qBittorrent_SOURCE_DIR}
# a helper function which appends source to the target
# sources file names are relative to the the target source dir
function (qbt_target_sources)
set (_sources_rel "")
foreach (_source IN ITEMS ${ARGN})
if (IS_ABSOLUTE "${_source}")
set(source_abs "${_source}")
function (qbt_target_sources _target _scope)
get_target_property(targetSourceDir ${_target} SOURCE_DIR)
set(sourcesRelative "")
foreach(source IN ITEMS ${ARGN})
if(IS_ABSOLUTE "${source}")
set(sourceAbsolutePath "${source}")
else()
get_filename_component(_source_abs "${_source}" ABSOLUTE)
get_filename_component(sourceAbsolutePath "${source}" ABSOLUTE)
endif()
file (RELATIVE_PATH _source_rel "${qbt_executable_SOURCE_DIR}" "${_source_abs}")
list (APPEND _sources_rel "${_source_rel}")
file(RELATIVE_PATH sourceRelativePath "${targetSourceDir}" "${sourceAbsolutePath}")
list(APPEND sourcesRelative "${sourceRelativePath}")
endforeach()
target_sources (qBittorrent PRIVATE "${_sources_rel}")
endfunction (qbt_target_sources)
target_sources(${_target} ${_scope} "${sourcesRelative}")
endfunction(qbt_target_sources)

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

@@ -1,9 +1,9 @@
if (STACKTRACE_WIN)
if (STACKTRACE)
if ("${WINXXBITS}" NOT STREQUAL "Win64")
add_compile_options(-fno-omit-frame-pointer)
endif ("${WINXXBITS}" NOT STREQUAL "Win64")
link_libraries(libdbghelp -Wl,--export-all-symbols)
endif (STACKTRACE_WIN)
endif (STACKTRACE)
if (("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") OR ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo"))
link_libraries(-Wl,--dynamicbase)

View File

@@ -1,4 +1,4 @@
if (STACKTRACE_WIN)
if (STACKTRACE)
if ("${WINXXBITS}" STREQUAL "Win64")
add_compile_options(-Zi)
else ("${WINXXBITS}" STREQUAL "Win64")
@@ -6,7 +6,7 @@ if (STACKTRACE_WIN)
add_compile_options(-Oy-)
endif ("${WINXXBITS}" STREQUAL "Win64")
link_libraries(dbghelp.lib)
endif (STACKTRACE_WIN)
endif (STACKTRACE)
# Enable Wide characters
add_definitions(-DTORRENT_USE_WPATH)

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@

View File

@@ -50,6 +50,8 @@ DEFINES += BOOST_USE_WINAPI_VERSION=0x0501
#DEFINES += BOOST_ASIO_SEPARATE_COMPILATION
# Enable if building against libtorrent 1.0.x (RC_1_0) (dynamic linking)
#DEFINES += BOOST_ASIO_DYN_LINK
# Enable if encountered build error with boost version <= 1.59
#DEFINES += BOOST_NO_CXX11_RVALUE_REFERENCES
# Enable if building against libtorrent 1.1.x (RC_1_1)
# built with this flag defined
@@ -59,3 +61,9 @@ DEFINES += BOOST_USE_WINAPI_VERSION=0x0501
# Enable stack trace support
CONFIG += stacktrace
win32-msvc* {
QMAKE_CXXFLAGS += "/guard:cf"
QMAKE_LFLAGS += "/guard:cf"
QMAKE_LFLAGS_RELEASE += "/OPT:REF /OPT:ICF"
}

486
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.1.
# Generated by GNU Autoconf 2.69 for qbittorrent v4.1.5.
#
# 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.1'
PACKAGE_STRING='qbittorrent v4.1.1'
PACKAGE_VERSION='v4.1.5'
PACKAGE_STRING='qbittorrent v4.1.5'
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.1 to adapt to many kinds of systems.
\`configure' configures qbittorrent v4.1.5 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.1:";;
short | recursive ) echo "Configuration of qbittorrent v4.1.5:";;
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.1
qbittorrent configure v4.1.5
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.1, which was
It was created by qbittorrent $as_me v4.1.5, 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.1'
VERSION='v4.1.5'
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,9 +5015,31 @@ fi
CPPFLAGS="$BOOST_CPPFLAGS $CPPFLAGS"
CXXFLAGS="$BOOST_CPPFLAGS $CXXFLAGS"
LDFLAGS="$BOOST_LDFLAGS $LDFLAGS"
# add workaround for problematic boost version
# taken from ax_boost_base.m4
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <boost/version.hpp>
int
main ()
{
(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < (106000))]));
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_compile "$LINENO"; then :
else
QBT_ADD_DEFINES="$QBT_ADD_DEFINES BOOST_NO_CXX11_RVALUE_REFERENCES"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
# Check whether --with-boost-system was given.
@@ -5366,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
@@ -5459,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.
@@ -5549,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
@@ -5568,6 +5700,8 @@ QBT_ADD_DEFINES="$QBT_ADD_DEFINES $QBT_CONF_DEFINES"
ac_config_files="$ac_config_files conf.pri"
cat >confcache <<\_ACEOF
@@ -6140,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.1, which was
This file was extended by qbittorrent $as_me v4.1.5, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -6198,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.1
qbittorrent config.status v4.1.5
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
@@ -6306,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
@@ -6751,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
@@ -6791,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
}
;;
@@ -7455,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.1, which was
This file was extended by qbittorrent $as_me v4.1.5, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -7513,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.1
qbittorrent config.status v4.1.5
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
@@ -7621,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
@@ -8067,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
@@ -8107,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.1], [bugs.qbittorrent.org], [], [https://www.qbittorrent.org/])
AC_INIT([qbittorrent], [v4.1.5], [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,12 +164,21 @@ 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
# taken from ax_boost_base.m4
m4_define([DETECT_BOOST_VERSION_PROGRAM],
[AC_LANG_PROGRAM([[#include <boost/version.hpp>]],
[[(void) ((void)sizeof(char[1 - 2*!!((BOOST_VERSION) < ($1))]));]])])
AC_COMPILE_IFELSE([DETECT_BOOST_VERSION_PROGRAM(106000)], [],
[QBT_ADD_DEFINES="$QBT_ADD_DEFINES BOOST_NO_CXX11_RVALUE_REFERENCES"])
AX_BOOST_SYSTEM()
AC_MSG_NOTICE([Boost.System LIB: "$BOOST_SYSTEM_LIB"])
LIBS="$BOOST_SYSTEM_LIB $LIBS"
@@ -185,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])
@@ -219,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)

2
dist/CMakeLists.txt vendored
View File

@@ -1,3 +1,5 @@
find_package(Qt5Widgets ${requiredQtVersion}) # to conditionally install desktop-related files
if (APPLE)
add_subdirectory(mac)
else (APPLE)

2
dist/mac/Info.plist vendored
View File

@@ -45,7 +45,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.1.1</string>
<string>4.1.5</string>
<key>CFBundleSignature</key>
<string>qBit</string>
<key>CFBundleExecutable</key>

Binary file not shown.

View File

@@ -1,31 +1,37 @@
if (SYSTEMD)
find_package(Systemd)
if (SYSTEMD_FOUND)
if (NOT Qt5Widgets_FOUND)
feature_option(SYSTEMD "Install systemd service file (headless only)" OFF)
if (SYSTEMD)
if (NOT Systemd_SERVICES_INSTALL_DIR)
find_package(Systemd)
if (NOT Systemd_FOUND)
message(FATAL_ERROR "Could not locate systemd services install dir."
" Either pass -DSystemd_SERVICES_INSTALL_DIR=/path/to/systemd/services option or install systemd pkg-config")
endif(NOT Systemd_FOUND)
endif(NOT Systemd_SERVICES_INSTALL_DIR)
set(EXPAND_BINDIR ${CMAKE_INSTALL_FULL_BINDIR})
configure_file(systemd/qbittorrent-nox@.service.in ${CMAKE_CURRENT_BINARY_DIR}/qbittorrent-nox@.service @ONLY)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qbittorrent-nox@.service
DESTINATION ${SYSTEMD_SERVICES_INSTALL_DIR}
DESTINATION ${Systemd_SERVICES_INSTALL_DIR}
COMPONENT data)
endif(SYSTEMD_FOUND)
endif(SYSTEMD)
endif(SYSTEMD)
endif()
if (GUI)
if (Qt5Widgets_FOUND)
list(APPEND MAN_FILES ${qBittorrent_SOURCE_DIR}/doc/qbittorrent.1)
else (GUI)
else (Qt5Widgets_FOUND)
list(APPEND MAN_FILES ${qBittorrent_SOURCE_DIR}/doc/qbittorrent-nox.1)
endif (GUI)
endif (Qt5Widgets_FOUND)
install(FILES ${MAN_FILES}
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
COMPONENT doc)
if (GUI)
if (Qt5Widgets_FOUND)
install(DIRECTORY menuicons/
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor
FILES_MATCHING PATTERN "*.png")
install(FILES ${qBittorrent_SOURCE_DIR}/src/icons/qbittorrent.desktop
install(FILES qbittorrent.desktop
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications/
COMPONENT data)

View File

@@ -27,7 +27,7 @@ XPStyle on
!define CSIDL_LOCALAPPDATA '0x1C' ;Local Application Data path
; Program specific
!define PROG_VERSION "4.1.1"
!define PROG_VERSION "4.1.5"
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION PageFinishRun

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

@@ -3,7 +3,6 @@ TEMPLATE = subdirs
SUBDIRS += src
include(version.pri)
include(qm_gen.pri)
# Make target to create release tarball. Use 'make tarball'
tarball.commands += rm -fR ../$${PROJECT_NAME}-$${PROJECT_VERSION}/ &&
@@ -18,6 +17,10 @@ tarball.commands += rm -fR $${PROJECT_NAME}-$${PROJECT_VERSION}
QMAKE_EXTRA_TARGETS += tarball
# Translations included here (at top level) is to avoid regenerating the .qm files
# every time when src.pro is processed
include(src/lang/lang.pri)
# For Qt Creator beautifier
DISTFILES += \
uncrustify.cfg

View File

@@ -1,21 +0,0 @@
TS_IN = $$fromfile(src/src.pro,TRANSLATIONS)
TS_IN_NOEXT = $$replace(TS_IN,".ts","")
isEmpty(QMAKE_LRELEASE) {
win32:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]\\lrelease.exe
else:QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease
unix {
equals(QT_MAJOR_VERSION, 5) {
!exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease-qt5 }
}
} else {
!exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease }
}
}
message("Building translations")
for(L,TS_IN_NOEXT) {
message("Processing $${L}")
system("$$QMAKE_LRELEASE -silent src/$${L}.ts -qm src/$${L}.qm")
!exists("src/$${L}.qm"):error("Building translations failed, cannot continue")
}

View File

@@ -1,40 +1,53 @@
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_STANDARD "11")
add_definitions(-DBOOST_NO_CXX11_RVALUE_REFERENCES)
# 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()
include(MacroLinkQtComponents)
include(QbtTargetSources)
find_package(Boost ${requiredBoostVersion} REQUIRED)
find_package(LibtorrentRasterbar REQUIRED)
# Qt
list(APPEND QBT_QT_COMPONENTS Core Network Xml)
if (GUI)
list (APPEND QBT_QT_COMPONENTS Gui Svg Widgets)
if (WIN32)
list (APPEND QBT_QT_COMPONENTS WinExtras)
endif(WIN32)
if (APPLE)
list (APPEND QBT_GUI_OPTIONAL_LINK_LIBRARIES objc)
list (APPEND QBT_QT_COMPONENTS MacExtras)
endif (APPLE)
endif (GUI)
if (DBUS)
list (APPEND QBT_QT_COMPONENTS DBus)
endif (DBUS)
find_package(Qt5 5.5.1 COMPONENTS ${QBT_QT_COMPONENTS} REQUIRED)
if (Boost_VERSION VERSION_LESS 106000)
add_definitions(-DBOOST_NO_CXX11_RVALUE_REFERENCES)
endif()
if (GUI AND APPLE)
# Fix MOC inability to detect macOS. This seems to only affect cmake.
# Relevant issue: https://bugreports.qt.io/browse/QTBUG-58325
set(CMAKE_AUTOMOC_MOC_OPTIONS ${CMAKE_AUTOMOC_MOC_OPTIONS} -DQ_OS_MAC)
endif ()
find_package(Qt5 ${requiredQtVersion} REQUIRED COMPONENTS Core Network Xml LinguistTools)
find_package(Qt5Widgets ${requiredQtVersion})
if (Qt5Widgets_FOUND)
find_package(Qt5DBus ${requiredQtVersion})
else()
add_definitions(-DDISABLE_GUI)
endif()
set_package_properties(Qt5Widgets PROPERTIES
DESCRIPTION "Set of components for creating classic desktop-style UIs for the Qt5 framework"
PURPOSE "Enables qBittorrent GUI. Unneeded for headless configuration."
TYPE OPTIONAL
)
set_package_properties(Qt5DBus PROPERTIES
DESCRIPTION "Qt5 module for inter-process communication over the D-Bus protocol"
PURPOSE "Enables communication with other system components (e.g. notification service) via D-Bus. "
TYPE RECOMMENDED
)
set(CMAKE_AUTOMOC True)
list(APPEND CMAKE_AUTORCC_OPTIONS -compress 9 -threshold 5)
if (APPLE)
# Workaround CMake bug (autogen does not pass required parameters to moc)
# Relevant issue: https://gitlab.kitware.com/cmake/cmake/issues/18041
list(APPEND CMAKE_AUTOMOC_MOC_OPTIONS -DQ_OS_MAC -DQ_OS_DARWIN)
endif ()
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
@@ -43,56 +56,34 @@ add_definitions(-DQT_NO_CAST_TO_ASCII)
# Efficient construction for QString & QByteArray (Qt >= 4.8)
add_definitions(-DQT_USE_QSTRINGBUILDER)
if (NOT GUI)
add_definitions(-DDISABLE_GUI -DDISABLE_COUNTRIES_RESOLUTION)
endif (NOT GUI)
if (NOT WEBUI)
add_definitions(-DDISABLE_WEBUI)
endif (NOT WEBUI)
if (STACKTRACE)
add_definitions(-DSTACKTRACE)
endif(STACKTRACE)
# nogui {
# TARGET = qbittorrent-nox
# } else {
# CONFIG(static) {
# DEFINES += QBT_STATIC_QT
# QTPLUGIN += qico
# }
# TARGET = qbittorrent
# }
if (UNIX AND NOT APPLE)
add_compile_options(-Wformat -Wformat-security)
endif ()
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
if (CMAKE_BUILD_TYPE MATCHES "Debug")
message(STATUS "Project is built in DEBUG mode.")
else (CMAKE_BUILD_TYPE STREQUAL "Debug")
else()
message(STATUS "Project is built in RELEASE mode.")
message(STATUS "Disabling debug output.")
add_definitions(-DQT_NO_DEBUG_OUTPUT)
endif (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(QBT_USE_GUI ${GUI})
set(QBT_USE_WEBUI ${WEBUI})
endif()
configure_file(config.h.cmakein ${CMAKE_CURRENT_BINARY_DIR}/config.h)
if (SYSTEM_QTSINGLEAPPLICATION)
find_package(QtSingleApplication REQUIRED)
else (SYSTEM_QTSINGLEAPPLICATION)
find_package(QtSingleApplication)
set_package_properties(QtSingleApplication PROPERTIES
URL "https://code.qt.io/cgit/qt-solutions/qt-solutions.git/"
DESCRIPTION "Qt library to start applications only once per user"
TYPE RECOMMENDED
PURPOSE "Use the system qtsingleapplication library or shipped one otherwise"
)
if (NOT QtSingleApplication_FOUND)
add_subdirectory(app/qtsingleapplication)
endif (SYSTEM_QTSINGLEAPPLICATION)
endif ()
add_subdirectory(app)
add_subdirectory(base)
if (GUI)
if (Qt5Widgets_FOUND)
add_subdirectory(gui)
endif (GUI)
endif ()
if (WEBUI)
add_subdirectory(webui)

View File

@@ -1,43 +1,41 @@
project(qbt_executable)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
set(QBT_APP_HEADERS
add_executable(qBittorrent
application.h
cmdoptions.h
filelogger.h
)
set(QBT_APP_SOURCES
upgrade.h
application.cpp
cmdoptions.cpp
filelogger.cpp
main.cpp
)
target_include_directories(qBittorrent PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(qBittorrent
PRIVATE
qbt_base
)
set_target_properties(qBittorrent
PROPERTIES
AUTOUIC True
AUTORCC True
MACOSX_BUNDLE True
)
# 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.qrc" ABSOLUTE)
get_filename_component(_lang_qrc_dst "${CMAKE_CURRENT_BINARY_DIR}/../lang.qrc" ABSOLUTE)
get_filename_component(_lang_qrc_dst_dir "${CMAKE_CURRENT_BINARY_DIR}/../" 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.qrc
../searchengine.qrc
"${_lang_qrc_dst}"
../icons/icons.qrc
../searchengine/searchengine.qrc
)
# With AUTORCC rcc is ran by cmake before language files are generated,
@@ -46,51 +44,41 @@ qt5_add_resources(QBT_APP_RESOURCE_SOURCE ${QBT_APP_RESOURCES})
if (WIN32)
if (MINGW)
list (APPEND QBT_APP_SOURCES ../qbittorrent_mingw.rc)
target_sources(qBittorrent PRIVATE ../qbittorrent_mingw.rc)
else (MINGW)
list (APPEND QBT_APP_SOURCES ../qbittorrent.rc)
target_sources(qBittorrent PRIVATE ../qbittorrent.rc)
endif (MINGW)
list(APPEND QBT_APP_SOURCES ../qbittorrent.exe.manifest)
target_sources(qBittorrent PRIVATE ../qbittorrent.exe.manifest)
endif (WIN32)
if (STACKTRACE)
if (UNIX)
list(APPEND QBT_APP_HEADERS stacktrace.h)
target_sources(qBittorrent PRIVATE stacktrace.h)
else (UNIX)
list(APPEND QBT_APP_HEADERS stacktrace_win.h)
if (GUI)
list(APPEND QBT_APP_HEADERS stacktrace_win_dlg.h)
endif (GUI)
target_sources(qBittorrent PRIVATE stacktrace_win.h)
if (Qt5Widgets_FOUND)
target_sources(qBittorrent PRIVATE stacktracedialog.h)
endif (Qt5Widgets_FOUND)
endif (UNIX)
endif (STACKTRACE)
# usesystemqtsingleapplication {
# nogui {
# CONFIG += qtsinglecoreapplication
# } else {
# CONFIG += qtsingleapplication
# }
# } else {
# nogui {
# include(qtsingleapplication/qtsinglecoreapplication.pri)
# } else {
# include(qtsingleapplication/qtsingleapplication.pri)
# }
# }
# upgrade code
list(APPEND QBT_APP_HEADERS upgrade.h)
list(APPEND QBT_TARGET_LIBRARIES qbt_base)
if (GUI)
list(APPEND QBT_TARGET_LIBRARIES qbt_searchengine qbt_gui)
include_directories(../gui
${CMAKE_CURRENT_BINARY_DIR}/../gui
if (Qt5Widgets_FOUND)
target_link_libraries(qBittorrent PRIVATE qbt_searchengine qbt_gui)
set_target_properties(qBittorrent
PROPERTIES
OUTPUT_NAME qbittorrent
WIN32_EXECUTABLE True
)
endif (GUI)
else(Qt5Widgets_FOUND)
set_target_properties(qBittorrent
PROPERTIES
OUTPUT_NAME qbittorrent-nox
)
endif (Qt5Widgets_FOUND)
if (WEBUI)
list(APPEND QBT_TARGET_LIBRARIES qbt_webui)
target_link_libraries(qBittorrent PRIVATE qbt_webui)
endif (WEBUI)
# we have to include resources into the bundle
@@ -142,30 +130,11 @@ if (APPLE)
PROPERTIES MACOSX_PACKAGE_LOCATION translations)
endif (APPLE)
add_executable(qBittorrent ${QBT_APP_HEADERS} ${QBT_APP_SOURCES} ${QBT_QM_FILES} ${QBT_APP_RESOURCE_SOURCE})
if (GUI)
set_target_properties(qBittorrent
PROPERTIES
OUTPUT_NAME qbittorrent
WIN32_EXECUTABLE True
)
else (GUI)
set_target_properties(qBittorrent
PROPERTIES
OUTPUT_NAME qbittorrent-nox
)
endif (GUI)
set_target_properties(qBittorrent
PROPERTIES
AUTOUIC True
AUTORCC True
MACOSX_BUNDLE True
)
target_sources(qBittorrent PRIVATE ${QBT_QM_FILES} ${QBT_APP_RESOURCE_SOURCE})
get_target_property(QBT_EXECUTABLE_NAME qBittorrent OUTPUT_NAME)
target_link_libraries(qBittorrent ${QBT_TARGET_LIBRARIES} QtSingleApplication::QtSingleApplication)
target_link_libraries(qBittorrent PRIVATE ${QBT_TARGET_LIBRARIES} QtSingleApplication::QtSingleApplication)
if (APPLE)
set(qbt_BUNDLE_NAME ${QBT_EXECUTABLE_NAME})
@@ -186,6 +155,7 @@ install(TARGETS qBittorrent
BUNDLE DESTINATION .
COMPONENT runtime)
if (GUI AND APPLE)
if (Qt5Widgets_FOUND AND APPLE)
find_package(Qt5Svg REQUIRED)
include(bundle)
endif (GUI AND APPLE)
endif (Qt5Widgets_FOUND AND APPLE)

View File

@@ -32,8 +32,8 @@ stacktrace {
else {
HEADERS += $$PWD/stacktrace_win.h
!nogui {
HEADERS += $$PWD/stacktrace_win_dlg.h
FORMS += $$PWD/stacktrace_win_dlg.ui
HEADERS += $$PWD/stacktracedialog.h
FORMS += $$PWD/stacktracedialog.ui
}
}
}

View File

@@ -31,20 +31,38 @@
#include <algorithm>
#ifdef Q_OS_WIN
#include <memory>
#endif
#include <QAtomicInt>
#include <QDebug>
#include <QFileInfo>
#include <QLibraryInfo>
#include <QLocale>
#include <QProcess>
#include <QSysInfo>
#ifdef Q_OS_WIN
#include <memory>
#include <Shellapi.h>
#endif
#ifndef DISABLE_GUI
#include <QMessageBox>
#ifdef Q_OS_WIN
#include <QSessionManager>
#include <QSharedMemory>
#endif // Q_OS_WIN
#ifdef Q_OS_MAC
#include <QFileOpenEvent>
#endif // Q_OS_MAC
#include "addnewtorrentdialog.h"
#include "gui/guiiconprovider.h"
#include "mainwindow.h"
#include "shutdownconfirmdialog.h"
#else // DISABLE_GUI
#include <cstdio>
#endif // DISABLE_GUI
#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"
@@ -56,34 +74,13 @@
#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"
#include "base/utils/string.h"
#include "filelogger.h"
#ifndef DISABLE_GUI
#ifdef Q_OS_WIN
#include <QSessionManager>
#include <QSharedMemory>
#endif // Q_OS_WIN
#ifdef Q_OS_MAC
#include <QFileOpenEvent>
#include <QFont>
#include <QUrl>
#endif // Q_OS_MAC
#include "addnewtorrentdialog.h"
#include "gui/guiiconprovider.h"
#include "mainwindow.h"
#include "shutdownconfirmdlg.h"
#else // DISABLE_GUI
#include <cstdio>
#endif // DISABLE_GUI
#ifdef Q_OS_WIN
#include <Shellapi.h>
#endif
#ifndef DISABLE_WEBUI
#include "webui/webui.h"
#endif
@@ -102,13 +99,13 @@ namespace
const QString KEY_FILELOGGER_AGE = FILELOGGER_SETTINGS_KEY("Age");
const QString KEY_FILELOGGER_AGETYPE = FILELOGGER_SETTINGS_KEY("AgeType");
//just a shortcut
// just a shortcut
inline SettingsStorage *settings() { return SettingsStorage::instance(); }
const QString LOG_FOLDER("logs");
const char PARAMS_SEPARATOR[] = "|";
const QString LOG_FOLDER = QStringLiteral("logs");
const QChar PARAMS_SEPARATOR = '|';
const QString DEFAULT_PORTABLE_MODE_PROFILE_DIR = QLatin1String("profile");
const QString DEFAULT_PORTABLE_MODE_PROFILE_DIR = QStringLiteral("profile");
const int MIN_FILELOG_SIZE = 1024; // 1KiB
const int MAX_FILELOG_SIZE = 1000 * 1024 * 1024; // 1000MiB
@@ -261,17 +258,17 @@ void Application::setFileLoggerAge(const int value)
int Application::fileLoggerAgeType() const
{
int val = settings()->loadValue(KEY_FILELOGGER_AGETYPE, 1).toInt();
return (val < 0 || val > 2) ? 1 : val;
return ((val < 0) || (val > 2)) ? 1 : val;
}
void Application::setFileLoggerAgeType(const int value)
{
settings()->storeValue(KEY_FILELOGGER_AGETYPE, (value < 0 || value > 2) ? 1 : value);
settings()->storeValue(KEY_FILELOGGER_AGETYPE, ((value < 0) || (value > 2)) ? 1 : value);
}
void Application::processMessage(const QString &message)
{
QStringList params = message.split(QLatin1String(PARAMS_SEPARATOR), QString::SkipEmptyParts);
QStringList params = message.split(PARAMS_SEPARATOR, QString::SkipEmptyParts);
// If Application is not running (i.e., other
// components are not ready) store params
if (m_running)
@@ -338,12 +335,12 @@ void Application::runExternalProgram(const BitTorrent::TorrentHandle *torrent) c
void Application::sendNotificationEmail(const BitTorrent::TorrentHandle *torrent)
{
// Prepare mail content
const QString content = tr("Torrent name: %1").arg(torrent->name()) + "\n"
+ tr("Torrent size: %1").arg(Utils::Misc::friendlyUnit(torrent->wantedSize())) + "\n"
const QString content = tr("Torrent name: %1").arg(torrent->name()) + '\n'
+ tr("Torrent size: %1").arg(Utils::Misc::friendlyUnit(torrent->wantedSize())) + '\n'
+ tr("Save path: %1").arg(torrent->savePath()) + "\n\n"
+ tr("The torrent was downloaded in %1.", "The torrent was downloaded in 1 hour and 20 seconds")
.arg(Utils::Misc::userFriendlyDuration(torrent->activeTime())) + "\n\n\n"
+ tr("Thank you for using qBittorrent.") + "\n";
+ tr("Thank you for using qBittorrent.") + '\n';
// Send the notification email
const Preferences *pref = Preferences::instance();
@@ -394,7 +391,7 @@ void Application::allTorrentsFinished()
// do nothing & skip confirm
}
else {
if (!ShutdownConfirmDlg::askForConfirmation(m_window, action)) return;
if (!ShutdownConfirmDialog::askForConfirmation(m_window, action)) return;
}
#endif // DISABLE_GUI
@@ -415,7 +412,7 @@ void Application::allTorrentsFinished()
bool Application::sendParams(const QStringList &params)
{
return sendMessage(params.join(QLatin1String(PARAMS_SEPARATOR)));
return sendMessage(params.join(PARAMS_SEPARATOR));
}
// As program parameters, we can get paths or urls.
@@ -433,7 +430,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.
@@ -500,30 +497,46 @@ 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
Preferences* const pref = Preferences::instance();
Preferences *const pref = Preferences::instance();
// Display some information to the user
const QString mesg = QString("\n******** %1 ********\n").arg(tr("Information"))
+ tr("To control qBittorrent, access the Web UI at %1")
@@ -619,7 +632,7 @@ bool Application::notify(QObject *receiver, QEvent *event)
void Application::initializeTranslation()
{
Preferences* const pref = Preferences::instance();
Preferences *const pref = Preferences::instance();
// Load translation
QString localeStr = pref->getLocale();
@@ -685,7 +698,7 @@ void Application::cleanup()
#ifndef DISABLE_GUI
if (m_window) {
// Hide the window and not leave it on screen as
// Hide the window and don't leave it on screen as
// unresponsive. Also for Windows take the WinId
// after it's hidden, because hide() may cause a
// WinId change.
@@ -729,6 +742,7 @@ void Application::cleanup()
delete m_fileLogger;
Logger::freeInstance();
IconProvider::freeInstance();
SearchPluginManager::freeInstance();
Utils::Fs::removeDirRecursive(Utils::Fs::tempPath());
#ifndef DISABLE_GUI

View File

@@ -40,15 +40,13 @@ typedef QtSingleApplication BaseApplication;
class MainWindow;
#ifdef Q_OS_WIN
QT_BEGIN_NAMESPACE
class QSessionManager;
QT_END_NAMESPACE
#endif // Q_OS_WIN
#else
#include "qtsinglecoreapplication.h"
typedef QtSingleCoreApplication BaseApplication;
#endif
#endif // DISABLE_GUI
#include "base/utils/misc.h"
#include "cmdoptions.h"
@@ -112,7 +110,7 @@ protected:
#ifdef Q_OS_MAC
bool event(QEvent *) override;
#endif
bool notify(QObject* receiver, QEvent* event) override;
bool notify(QObject *receiver, QEvent *event) override;
#endif
private slots:

View File

@@ -2,7 +2,7 @@
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2016 Eugene Shalygin <eugene.shalygin@gmail.com>
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* 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,6 @@
* 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.
*
* Contact : chris@qbittorrent.org
*/
#include "cmdoptions.h"
@@ -43,6 +41,7 @@
#include <QMessageBox>
#endif
#include "base/global.h"
#include "base/utils/misc.h"
#include "base/utils/string.h"
@@ -100,7 +99,7 @@ namespace
};
// Boolean option.
class BoolOption: protected Option
class BoolOption : protected Option
{
public:
constexpr BoolOption(const char *name, char shortcut = 0)
@@ -118,7 +117,7 @@ namespace
{
QString val = env.value(envVarName());
// we accept "1" and "true" (upper or lower cased) as boolean 'true' values
return (val == QLatin1String("1") || val.toUpper() == QLatin1String("TRUE"));
return ((val == QLatin1String("1")) || (val.toUpper() == QLatin1String("TRUE")));
}
QString usage() const
@@ -137,7 +136,7 @@ namespace
}
// Option with string value. May not have a shortcut
struct StringOption: protected Option
struct StringOption : protected Option
{
public:
constexpr StringOption(const char *name)
@@ -184,7 +183,7 @@ namespace
}
// Option with integer value. May not have a shortcut
class IntOption: protected StringOption
class IntOption : protected StringOption
{
public:
constexpr IntOption(const char *name)
@@ -230,7 +229,7 @@ namespace
// Option that is explicitly set to true or false, and whose value is undefined when unspecified.
// May not have a shortcut.
class TriStateBoolOption: protected Option
class TriStateBoolOption : protected Option
{
public:
constexpr TriStateBoolOption(const char *name, bool defaultValue)
@@ -260,10 +259,10 @@ namespace
else if (parts.size() == 2) {
QString val = parts[1];
if (val.toUpper() == QLatin1String("TRUE") || val == QLatin1String("1")) {
if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1"))) {
return TriStateBool::True;
}
else if (val.toUpper() == QLatin1String("FALSE") || val == QLatin1String("0")) {
else if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0"))) {
return TriStateBool::False;
}
}
@@ -285,10 +284,10 @@ namespace
else if (val == QLatin1String("-1")) {
return TriStateBool::Undefined;
}
else if (val.toUpper() == QLatin1String("TRUE") || val == QLatin1String("1")) {
else if ((val.toUpper() == QLatin1String("TRUE")) || (val == QLatin1String("1"))) {
return TriStateBool::True;
}
else if (val.toUpper() == QLatin1String("FALSE") || val == QLatin1String("0")) {
else if ((val.toUpper() == QLatin1String("FALSE")) || (val == QLatin1String("0"))) {
return TriStateBool::False;
}
else {
@@ -360,7 +359,7 @@ QStringList QBtCommandLineParameters::paramList() const
// the user has specified. Here we place special strings that are
// almost certainly not going to collide with a file path or URL
// specified by the user, and placing them at the beginning of the
// string listr so that they will be processed before the list of
// string list so that they will be processed before the list of
// torrent paths or URLs.
if (!savePath.isEmpty())
@@ -404,9 +403,9 @@ QBtCommandLineParameters parseCommandLine(const QStringList &args)
const QString &arg = args[i];
if ((arg.startsWith("--") && !arg.endsWith(".torrent"))
|| (arg.startsWith("-") && (arg.size() == 2))) {
|| (arg.startsWith('-') && (arg.size() == 2))) {
// Parse known parameters
if ((arg == SHOW_HELP_OPTION)) {
if (arg == SHOW_HELP_OPTION) {
result.showHelp = true;
}
#ifndef Q_OS_WIN
@@ -488,7 +487,7 @@ CommandLineParameterError::CommandLineParameterError(const QString &messageForUs
{
}
const QString& CommandLineParameterError::messageForUser() const
const QString &CommandLineParameterError::messageForUser() const
{
return m_messageForUser;
}
@@ -499,9 +498,9 @@ 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);
lines.last().append(' ' + word);
}
else {
lines.append(QString(initialIndentation, ' ') + word);
@@ -509,7 +508,7 @@ QString wrapText(const QString &text, int initialIndentation = USAGE_TEXT_COLUMN
}
}
return lines.join("\n");
return lines.join('\n');
}
QString makeUsage(const QString &prgName)

View File

@@ -2,7 +2,7 @@
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2016 Eugene Shalygin <eugene.shalygin@gmail.com>
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* 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,6 @@
* 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.
*
* Contact : chris@qbittorrent.org
*/
#ifndef APP_OPTIONS_H
@@ -62,11 +60,11 @@ struct QBtCommandLineParameters
QStringList paramList() const;
};
class CommandLineParameterError: public std::runtime_error
class CommandLineParameterError : public std::runtime_error
{
public:
CommandLineParameterError(const QString &messageForUser);
const QString& messageForUser() const;
const QString &messageForUser() const;
private:
const QString m_messageForUser;

View File

@@ -26,11 +26,14 @@
* exception statement from your version.
*/
#include "filelogger.h"
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QTextStream>
#include "filelogger.h"
#include "base/global.h"
#include "base/logger.h"
#include "base/utils/fs.h"
@@ -47,8 +50,8 @@ FileLogger::FileLogger(const QString &path, const bool backup, const int maxSize
if (deleteOld)
this->deleteOld(age, ageType);
const Logger* const logger = Logger::instance();
foreach (const Log::Msg& msg, logger->getMessages())
const Logger *const logger = Logger::instance();
for (const Log::Msg &msg : asConst(logger->getMessages()))
addLogMessage(msg);
connect(logger, &Logger::newLogMessage, this, &FileLogger::addLogMessage);
@@ -61,7 +64,7 @@ FileLogger::~FileLogger()
delete m_logFile;
}
void FileLogger::changePath(const QString& newPath)
void FileLogger::changePath(const QString &newPath)
{
QString tmpPath = Utils::Fs::fromNativePath(newPath);
QDir dir(tmpPath);
@@ -85,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

@@ -76,4 +76,3 @@ private:
};
#endif // FILELOGGER_H

View File

@@ -1,7 +1,7 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -25,8 +25,6 @@
* 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.
*
* Contact : chris@qbittorrent.org
*/
#include <cstdlib>
@@ -63,14 +61,14 @@ Q_IMPORT_PLUGIN(QICOPlugin)
#include "stacktrace.h"
#else
#include "stacktrace_win.h"
#include "stacktrace_win_dlg.h"
#include "stacktracedialog.h"
#endif // Q_OS_UNIX
#endif //STACKTRACE
#include "application.h"
#include "base/preferences.h"
#include "base/profile.h"
#include "base/utils/misc.h"
#include "base/preferences.h"
#include "application.h"
#include "cmdoptions.h"
#include "upgrade.h"
@@ -95,7 +93,7 @@ const char *sysSigName[] = {
};
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
void reportToUser(const char* str);
void reportToUser(const char *str);
#endif
void displayVersion();
@@ -104,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
@@ -122,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();
@@ -212,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.
@@ -236,7 +226,7 @@ int main(int argc, char *argv[])
#ifdef DISABLE_GUI
if (params.shouldDaemonize) {
app.reset(); // Destroy current application
if ((daemon(1, 0) == 0)) {
if (daemon(1, 0) == 0) {
app.reset(new Application(appId, argc, argv));
if (app->isRunning()) {
// Another instance had time to start.
@@ -269,7 +259,7 @@ int main(int argc, char *argv[])
}
#if !defined Q_OS_WIN && !defined Q_OS_HAIKU
void reportToUser(const char* str)
void reportToUser(const char *str)
{
const size_t strLen = strlen(str);
if (write(STDERR_FILENO, str, strLen) < static_cast<ssize_t>(strLen)) {
@@ -308,7 +298,7 @@ void sigAbnormalHandler(int signum)
#endif
#if defined Q_OS_WIN
StraceDlg dlg; // unsafe
StacktraceDialog dlg; // unsafe
dlg.setStacktraceString(QLatin1String(sigName), straceWin::getBacktrace());
dlg.exec();
#endif
@@ -321,25 +311,17 @@ void sigAbnormalHandler(int signum)
#if !defined(DISABLE_GUI)
void showSplashScreen()
{
QPixmap splash_img(":/icons/skin/splash.png");
QPainter painter(&splash_img);
QPixmap splashImg(":/icons/skin/splash.png");
QPainter painter(&splashImg);
QString version = QBT_VERSION;
painter.setPen(QPen(Qt::white));
painter.setFont(QFont("Arial", 22, QFont::Black));
painter.drawText(224 - painter.fontMetrics().width(version), 270, version);
QSplashScreen *splash = new QSplashScreen(splash_img);
QSplashScreen *splash = new QSplashScreen(splashImg);
splash->show();
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()
@@ -347,7 +329,7 @@ void displayVersion()
printf("%s %s\n", qUtf8Printable(qApp->applicationName()), QBT_VERSION);
}
void displayBadArgMessage(const QString& message)
void displayBadArgMessage(const QString &message)
{
QString help = QObject::tr("Run application with -h option to read about command line parameters.");
#ifdef Q_OS_WIN
@@ -366,7 +348,7 @@ void displayBadArgMessage(const QString& message)
bool userAgreesWithLegalNotice()
{
Preferences* const pref = Preferences::instance();
Preferences *const pref = Preferences::instance();
if (pref->getAcceptedLegal()) // Already accepted once
return true;
@@ -378,7 +360,7 @@ bool userAgreesWithLegalNotice()
printf("%s", qUtf8Printable(eula));
char ret = getchar(); // Read pressed key
if (ret == 'y' || ret == 'Y') {
if ((ret == 'y') || (ret == 'Y')) {
// Save the answer
pref->setAcceptedLegal(true);
return true;
@@ -388,16 +370,16 @@ bool userAgreesWithLegalNotice()
msgBox.setText(QObject::tr("qBittorrent is a file sharing program. When you run a torrent, its data will be made available to others by means of upload. Any content you share is your sole responsibility.\n\nNo further notices will be issued."));
msgBox.setWindowTitle(QObject::tr("Legal notice"));
msgBox.addButton(QObject::tr("Cancel"), QMessageBox::RejectRole);
QAbstractButton *agree_button = msgBox.addButton(QObject::tr("I Agree"), QMessageBox::AcceptRole);
QAbstractButton *agreeButton = msgBox.addButton(QObject::tr("I Agree"), QMessageBox::AcceptRole);
msgBox.show(); // Need to be shown or to moveToCenter does not work
msgBox.move(Utils::Misc::screenCenter(&msgBox));
msgBox.exec();
if (msgBox.clickedButton() == agree_button) {
if (msgBox.clickedButton() == agreeButton) {
// Save the answer
pref->setAcceptedLegal(true);
return true;
}
#endif
#endif // DISABLE_GUI
return false;
}

View File

@@ -8,24 +8,24 @@ set(QBT_QTSINGLEAPPLICATION_SOURCES
qtlocalpeer.cpp
)
if (GUI)
if (Qt5Widgets_FOUND)
list(APPEND QBT_QTSINGLEAPPLICATION_HEADERS qtsingleapplication.h)
list(APPEND QBT_QTSINGLEAPPLICATION_SOURCES qtsingleapplication.cpp)
else (GUI)
else (Qt5Widgets_FOUND)
list(APPEND QBT_QTSINGLEAPPLICATION_HEADERS qtsinglecoreapplication.h)
list(APPEND QBT_QTSINGLEAPPLICATION_SOURCES qtsinglecoreapplication.cpp)
endif (GUI)
endif (Qt5Widgets_FOUND)
add_library(qtsingleapplication STATIC ${QBT_QTSINGLEAPPLICATION_HEADERS} ${QBT_QTSINGLEAPPLICATION_SOURCES})
target_include_directories(qtsingleapplication INTERFACE "${qtsingleapplication_SOURCE_DIR}")
target_link_qt_components(qtsingleapplication Network)
target_link_libraries(qtsingleapplication PRIVATE Qt5::Network)
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
target_compile_options(qtsingleapplication PRIVATE "-w") # disable warning for 3rdparty code
endif()
if (GUI)
target_link_qt_components(qtsingleapplication Widgets)
endif (GUI)
if (Qt5Widgets_FOUND)
target_link_libraries(qtsingleapplication PRIVATE Qt5::Widgets)
endif (Qt5Widgets_FOUND)
add_library(QtSingleApplication::QtSingleApplication ALIAS qtsingleapplication)

View File

@@ -27,20 +27,21 @@
*
*/
#ifndef STACKTRACE_WIN_DLG_H
#define STACKTRACE_WIN_DLG_H
#ifndef STACKTRACEDIALOG_H
#define STACKTRACEDIALOG_H
#include <QString>
#include <QDialog>
#include "base/utils/misc.h"
#include "ui_stacktrace_win_dlg.h"
class StraceDlg : public QDialog, private Ui::errorDialog
#include "base/utils/misc.h"
#include "ui_stacktracedialog.h"
class StacktraceDialog : public QDialog, private Ui::StacktraceDialog
{
Q_OBJECT
public:
StraceDlg(QWidget *parent = nullptr)
StacktraceDialog(QWidget *parent = nullptr)
: QDialog(parent)
{
setupUi(this);
@@ -83,4 +84,4 @@ public:
}
};
#endif
#endif // STACKTRACEDIALOG_H

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>errorDialog</class>
<widget class="QDialog" name="errorDialog">
<class>StacktraceDialog</class>
<widget class="QDialog" name="StacktraceDialog">
<property name="geometry">
<rect>
<x>0</x>

View File

@@ -29,34 +29,34 @@
#ifndef UPGRADE_H
#define UPGRADE_H
#include <libtorrent/version.hpp>
#if LIBTORRENT_VERSION_NUM >= 10100
#include <libtorrent/bdecode.hpp>
#endif
#include <libtorrent/bencode.hpp>
#include <libtorrent/entry.hpp>
#if LIBTORRENT_VERSION_NUM < 10100
#include <libtorrent/version.hpp>
#if LIBTORRENT_VERSION_NUM >= 10100
#include <libtorrent/bdecode.hpp>
#else
#include <libtorrent/lazy_entry.hpp>
#endif
#include <QDir>
#include <QFile>
#include <QRegularExpression>
#include <QString>
#ifndef DISABLE_GUI
#include <QMessageBox>
#endif
#include <QRegExp>
#include <QString>
#ifdef Q_OS_MAC
#include <QSettings>
#endif
#include "base/logger.h"
#include "base/preferences.h"
#include "base/profile.h"
#include "base/utils/fs.h"
#include "base/utils/misc.h"
#include "base/utils/string.h"
#include "base/preferences.h"
bool userAcceptsUpgrade()
{
@@ -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;
}
@@ -114,8 +114,9 @@ bool upgradeResumeFile(const QString &filepath, const QVariantHash &oldTorrent =
bool v3_3 = false;
int queuePosition = 0;
QString outFilePath = filepath;
QRegExp rx(QLatin1String("([A-Fa-f0-9]{40})\\.fastresume\\.(.+)$"));
if (rx.indexIn(filepath) != -1) {
static const QRegularExpression rx(QLatin1String("([A-Fa-f0-9]{40})\\.fastresume\\.(.+)$"));
const QRegularExpressionMatch rxMatch = rx.match(filepath);
if (rxMatch.hasMatch()) {
// Old v3.3.x format had a number at the end indicating the queue position.
// The naming scheme was '<infohash>.fastresume.<queueposition>'.
// However, QSaveFile, which uses QTemporaryFile internally, might leave
@@ -127,14 +128,14 @@ bool upgradeResumeFile(const QString &filepath, const QVariantHash &oldTorrent =
// and is deleted, because it may be a corrupted/incomplete fastresume.
// NOTE: When the upgrade code is removed, we must continue to perform
// cleanup of non-commited QSaveFile/QTemporaryFile fastresumes
queuePosition = rx.cap(2).toInt();
if ((rx.cap(2).size() == 6) && (queuePosition <= 99999)) {
queuePosition = rxMatch.captured(2).toInt();
if ((rxMatch.captured(2).size() == 6) && (queuePosition <= 99999)) {
Utils::Fs::forceRemove(filepath);
return true;
}
v3_3 = true;
outFilePath.replace(QRegExp("\\.fastresume\\..+$"), ".fastresume");
outFilePath.replace(QRegularExpression("\\.fastresume\\..+$"), ".fastresume");
}
else {
queuePosition = fastOld.dict_find_int_value("qBt-queuePosition", 0);
@@ -178,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));
// ****************************************************************************************
@@ -196,15 +197,17 @@ 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);
QRegExp rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
foreach (QString backupFile, backupFiles) {
if (rx.indexIn(backupFile) != -1) {
if (upgradeResumeFile(backupFolderDir.absoluteFilePath(backupFile), oldResumeData[rx.cap(1)].toHash()))
oldResumeData.remove(rx.cap(1));
const QRegularExpression rx(QLatin1String("^([A-Fa-f0-9]{40})\\.fastresume$"));
for (const QString &backupFile : backupFiles) {
const QRegularExpressionMatch rxMatch = rx.match(backupFile);
if (rxMatch.hasMatch()) {
const QString hashStr = rxMatch.captured(1);
if (upgradeResumeFile(backupFolderDir.absoluteFilePath(backupFile), oldResumeData[hashStr].toHash()))
oldResumeData.remove(hashStr);
else
Logger::instance()->addMessage(QObject::tr("Couldn't migrate torrent with hash: %1").arg(rx.cap(1)), Log::WARNING);
Logger::instance()->addMessage(QObject::tr("Couldn't migrate torrent with hash: %1").arg(hashStr), Log::WARNING);
}
else {
Logger::instance()->addMessage(QObject::tr("Couldn't migrate torrent. Invalid fastresume file name: %1").arg(backupFile), Log::WARNING);
@@ -262,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();
}
@@ -296,6 +299,6 @@ void migrateRSS()
qBTRSSLegacy->remove("old_items");
}
}
#endif
#endif // DISABLE_GUI
#endif // UPGRADE_H

View File

@@ -1,8 +1,10 @@
find_package(ZLIB 1.2.5.2 REQUIRED)
set(QBT_BASE_HEADERS
add_library(qbt_base STATIC
# headers
bittorrent/addtorrentparams.h
bittorrent/cachestatus.h
bittorrent/filepriority.h
bittorrent/infohash.h
bittorrent/magneturi.h
bittorrent/peerinfo.h
@@ -48,6 +50,7 @@ search/searchdownloadhandler.h
search/searchhandler.h
search/searchpluginmanager.h
utils/bytearray.h
utils/foreignapps.h
utils/fs.h
utils/gzip.h
utils/misc.h
@@ -72,9 +75,9 @@ torrentfilter.h
tristatebool.h
types.h
unicodestrings.h
)
set(QBT_BASE_SOURCES
# sources
bittorrent/filepriority.cpp
bittorrent/infohash.cpp
bittorrent/magneturi.cpp
bittorrent/peerinfo.cpp
@@ -117,6 +120,7 @@ search/searchdownloadhandler.cpp
search/searchhandler.cpp
search/searchpluginmanager.cpp
utils/bytearray.cpp
utils/foreignapps.cpp
utils/fs.cpp
utils/gzip.cpp
utils/misc.cpp
@@ -137,16 +141,20 @@ torrentfilter.cpp
tristatebool.cpp
)
add_library(qbt_base STATIC ${QBT_BASE_HEADERS} ${QBT_BASE_SOURCES})
target_link_libraries(qbt_base PRIVATE ZLIB::ZLIB PUBLIC LibtorrentRasterbar::LibTorrent)
target_link_qt_components(qbt_base PUBLIC Core Network Xml)
target_link_libraries(qbt_base
PRIVATE
ZLIB::ZLIB
PUBLIC
LibtorrentRasterbar::torrent-rasterbar
Qt5::Core Qt5::Network Qt5::Xml
)
if (GUI)
if (Qt5Widgets_FOUND)
target_link_libraries(qbt_base PUBLIC Qt5::Gui Qt5::Widgets)
endif (GUI)
endif (Qt5Widgets_FOUND)
if (DBUS)
target_link_qt_components(qbt_base PRIVATE DBus)
if (Qt5DBus_FOUND)
target_link_libraries(qbt_base PRIVATE Qt5::DBus)
endif ()
if (APPLE)

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 \
@@ -64,6 +65,7 @@ HEADERS += \
$$PWD/types.h \
$$PWD/unicodestrings.h \
$$PWD/utils/bytearray.h \
$$PWD/utils/foreignapps.h \
$$PWD/utils/fs.h \
$$PWD/utils/gzip.h \
$$PWD/utils/misc.h \
@@ -74,6 +76,7 @@ HEADERS += \
SOURCES += \
$$PWD/asyncfilestorage.cpp \
$$PWD/bittorrent/filepriority.cpp \
$$PWD/bittorrent/infohash.cpp \
$$PWD/bittorrent/magneturi.cpp \
$$PWD/bittorrent/peerinfo.cpp \
@@ -127,6 +130,7 @@ SOURCES += \
$$PWD/torrentfilter.cpp \
$$PWD/tristatebool.cpp \
$$PWD/utils/bytearray.cpp \
$$PWD/utils/foreignapps.cpp \
$$PWD/utils/fs.cpp \
$$PWD/utils/gzip.cpp \
$$PWD/utils/misc.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

@@ -26,9 +26,10 @@
* exception statement from your version.
*/
#include <QHash>
#include "infohash.h"
#include <QHash>
using namespace BitTorrent;
InfoHash::InfoHash()

View File

@@ -29,8 +29,8 @@
#ifndef BITTORRENT_INFOHASH_H
#define BITTORRENT_INFOHASH_H
#include <QString>
#include <libtorrent/sha1_hash.hpp>
#include <QString>
namespace BitTorrent
{

View File

@@ -26,16 +26,17 @@
* exception statement from your version.
*/
#include <QByteArray>
#include <QRegExp>
#include <QStringList>
#include "magneturi.h"
#include <libtorrent/bencode.hpp>
#include <libtorrent/error_code.hpp>
#include <libtorrent/magnet_uri.hpp>
#include <QByteArray>
#include <QRegularExpression>
#include <QStringList>
#include "base/utils/string.h"
#include "magneturi.h"
namespace
{
@@ -45,7 +46,7 @@ namespace
rawBc = rawBc.mid(8); // skip bc://bt/
rawBc = QByteArray::fromBase64(rawBc); // Decode base64
// Format is now AA/url_encoded_filename/size_bytes/info_hash/ZZ
QStringList parts = QString(rawBc).split("/");
QStringList parts = QString(rawBc).split('/');
if (parts.size() != 5) return QString();
QString filename = parts.at(1);
@@ -69,8 +70,8 @@ MagnetUri::MagnetUri(const QString &source)
qDebug("Creating magnet link from bc link");
m_url = bcLinkToMagnet(source);
}
else if (((source.size() == 40) && !source.contains(QRegExp("[^0-9A-Fa-f]")))
|| ((source.size() == 32) && !source.contains(QRegExp("[^2-7A-Za-z]")))) {
else if (((source.size() == 40) && !source.contains(QRegularExpression("[^0-9A-Fa-f]")))
|| ((source.size() == 32) && !source.contains(QRegularExpression("[^2-7A-Za-z]")))) {
m_url = "magnet:?xt=urn:btih:" + source;
}
@@ -82,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

@@ -246,8 +246,8 @@ QString PeerInfo::connectionType() const
void PeerInfo::calcRelevance(const TorrentHandle *torrent)
{
const QBitArray &allPieces = torrent->pieces();
const QBitArray &peerPieces = pieces();
const QBitArray allPieces = torrent->pieces();
const QBitArray peerPieces = pieces();
int localMissing = 0;
int remoteHaves = 0;
@@ -377,7 +377,7 @@ void PeerInfo::determineFlags()
// L = Peer is local
if (fromLSD()) {
m_flags += "L";
m_flags += 'L';
flagsDescriptionList += "L = "
+ tr("peer from LSD");
}

View File

@@ -407,24 +407,24 @@ int FilterParserThread::parseP2PFilterFile()
int FilterParserThread::getlineInStream(QDataStream &stream, std::string &name, char delim)
{
char c;
int total_read = 0;
int totalRead = 0;
int read;
do {
read = stream.readRawData(&c, 1);
total_read += read;
totalRead += read;
if (read > 0) {
if (c != delim) {
name += c;
}
else {
// Delim found
return total_read;
return totalRead;
}
}
}
while(read > 0);
while (read > 0);
return total_read;
return totalRead;
}
// Parser for PeerGuardian ip filter in p2p format
@@ -455,7 +455,7 @@ int FilterParserThread::parseP2BFilterFile()
unsigned int start, end;
std::string name;
while(getlineInStream(stream, name, '\0') && !m_abort) {
while (getlineInStream(stream, name, '\0') && !m_abort) {
if (!stream.readRawData(reinterpret_cast<char*>(&start), sizeof(start))
|| !stream.readRawData(reinterpret_cast<char*>(&end), sizeof(end))) {
LogMsg(tr("Parsing Error: The filter file is not a valid PeerGuardian P2B file."), Log::CRITICAL);

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,30 +26,36 @@
* exception statement from your version.
*/
#include "resumedatasavingmanager.h"
#include <QDebug>
#include <QSaveFile>
#include "base/logger.h"
#include "base/utils/fs.h"
#include "resumedatasavingmanager.h"
ResumeDataSavingManager::ResumeDataSavingManager(const QString &resumeFolderPath)
: m_resumeDataDir(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

View File

@@ -28,7 +28,6 @@ private:
void save() const;
void load();
private:
BitTorrent::Session *m_session;
// Will overflow at 15.9 EiB
quint64 m_alltimeUL;

File diff suppressed because it is too large Load Diff

View File

@@ -138,7 +138,7 @@ namespace BitTorrent
class Tracker;
class MagnetUri;
class TrackerEntry;
struct AddTorrentData;
struct CreateTorrentParams;
struct TorrentStatusReport
{
@@ -237,7 +237,7 @@ namespace BitTorrent
int diskJobTime = 0;
} disk;
};
#endif
#endif // LIBTORRENT_VERSION_NUM >= 10100
class Session : public QObject
{
@@ -375,6 +375,10 @@ namespace BitTorrent
void setAnnounceToAllTrackers(bool val);
bool announceToAllTiers() const;
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;
@@ -452,6 +456,7 @@ namespace BitTorrent
TorrentStatusReport torrentStatusReport() const;
bool hasActiveTorrents() const;
bool hasUnfinishedTorrents() const;
bool hasRunningSeed() const;
const SessionStatus &status() const;
const CacheStatus &cacheStatus() const;
quint64 getAlltimeDL() const;
@@ -478,6 +483,7 @@ namespace BitTorrent
// TorrentHandle interface
void handleTorrentShareLimitChanged(TorrentHandle *const torrent);
void handleTorrentNameChanged(TorrentHandle *const torrent);
void handleTorrentSavePathChanged(TorrentHandle *const torrent);
void handleTorrentCategoryChanged(TorrentHandle *const torrent, const QString &oldCategory);
void handleTorrentTagAdded(TorrentHandle *const torrent, const QString &tag);
@@ -547,7 +553,7 @@ namespace BitTorrent
void generateResumeData(bool final = false);
void handleIPFilterParsed(int ruleCount);
void handleIPFilterError();
void handleDownloadFinished(const QString &url, const QString &filePath);
void handleDownloadFinished(const QString &url, const QByteArray &data);
void handleDownloadFailed(const QString &url, const QString &reason);
void handleRedirectedToMagnet(const QString &url, const QString &magnetUri);
@@ -595,14 +601,14 @@ namespace BitTorrent
void enableIPFilter();
void disableIPFilter();
bool addTorrent_impl(AddTorrentData addData, const MagnetUri &magnetUri,
bool addTorrent_impl(CreateTorrentParams params, const MagnetUri &magnetUri,
TorrentInfo torrentInfo = TorrentInfo(),
const QByteArray &fastresumeData = QByteArray());
bool findIncompleteFiles(TorrentInfo &torrentInfo, QString &savePath) const;
void updateSeedingLimitTimer();
void exportTorrentFile(TorrentHandle *const torrent, TorrentExportFolder folder = TorrentExportFolder::Regular);
void saveTorrentResumeData(TorrentHandle *const torrent, bool finalSave = false);
void saveTorrentResumeData(TorrentHandle *const torrent);
void handleAlert(libtorrent::alert *a);
void dispatchTorrentAlert(libtorrent::alert *a);
@@ -628,6 +634,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);
@@ -651,6 +659,8 @@ namespace BitTorrent
CachedSettingValue<QString> m_IPFilterFile;
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;
@@ -754,7 +764,7 @@ namespace BitTorrent
QHash<InfoHash, TorrentInfo> m_loadedMetadata;
QHash<InfoHash, TorrentHandle *> m_torrents;
QHash<InfoHash, AddTorrentData> m_addingTorrents;
QHash<InfoHash, CreateTorrentParams> m_addingTorrents;
QHash<QString, AddTorrentParams> m_downloadedTorrents;
QHash<InfoHash, RemovingTorrentData> m_removingTorrents;
TorrentStatusReport m_torrentStatusReport;

View File

@@ -89,7 +89,7 @@ void TorrentCreatorThread::run()
emit updateProgress(0);
try {
const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + "/";
const QString parentPath = Utils::Fs::branchPath(m_params.inputPath) + '/';
// Adding files to the torrent
libt::file_storage fs;
@@ -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;
}
@@ -85,16 +86,16 @@ namespace
// AddTorrentData
AddTorrentData::AddTorrentData()
: resumed(false)
CreateTorrentParams::CreateTorrentParams()
: restored(false)
, disableTempPath(false)
, sequential(false)
, firstLastPiecePriority(false)
, hasSeedStatus(false)
, skipChecking(false)
, hasRootFolder(true)
, addForced(false)
, addPaused(false)
, forced(false)
, paused(false)
, uploadLimit(-1)
, downloadLimit(-1)
, ratioLimit(TorrentHandle::USE_GLOBAL_RATIO)
@@ -102,8 +103,8 @@ AddTorrentData::AddTorrentData()
{
}
AddTorrentData::AddTorrentData(const AddTorrentParams &params)
: resumed(false)
CreateTorrentParams::CreateTorrentParams(const AddTorrentParams &params)
: restored(false)
, name(params.name)
, category(params.category)
, tags(params.tags)
@@ -116,8 +117,8 @@ AddTorrentData::AddTorrentData(const AddTorrentParams &params)
, hasRootFolder(params.createSubfolder == TriStateBool::Undefined
? Session::instance()->isCreateTorrentSubfolder()
: params.createSubfolder == TriStateBool::True)
, addForced(params.addForced == TriStateBool::True)
, addPaused(params.addPaused == TriStateBool::Undefined
, forced(params.addForced == TriStateBool::True)
, paused(params.addPaused == TriStateBool::Undefined
? Session::instance()->isAddTorrentPaused()
: params.addPaused == TriStateBool::True)
, uploadLimit(params.uploadLimit)
@@ -158,40 +159,39 @@ 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);
}
}
TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle &nativeHandle,
const AddTorrentData &data)
const CreateTorrentParams &params)
: QObject(session)
, m_session(session)
, m_nativeHandle(nativeHandle)
, m_state(TorrentState::Unknown)
, m_renameCount(0)
, m_useAutoTMM(data.savePath.isEmpty())
, m_name(data.name)
, m_savePath(Utils::Fs::toNativePath(data.savePath))
, m_category(data.category)
, m_tags(data.tags)
, m_hasSeedStatus(data.hasSeedStatus)
, m_ratioLimit(data.ratioLimit)
, m_seedingTimeLimit(data.seedingTimeLimit)
, m_tempPathDisabled(data.disableTempPath)
, m_useAutoTMM(params.savePath.isEmpty())
, m_name(params.name)
, m_savePath(Utils::Fs::toNativePath(params.savePath))
, m_category(params.category)
, m_tags(params.tags)
, m_hasSeedStatus(params.hasSeedStatus)
, m_ratioLimit(params.ratioLimit)
, m_seedingTimeLimit(params.seedingTimeLimit)
, m_tempPathDisabled(params.disableTempPath)
, m_hasMissingFiles(false)
, m_hasRootFolder(data.hasRootFolder)
, m_hasRootFolder(params.hasRootFolder)
, m_needsToSetFirstLastPiecePriority(false)
, m_pauseAfterRecheck(false)
, m_needSaveResumeData(false)
, m_needsToStartForced(params.forced)
{
if (m_useAutoTMM)
m_savePath = Utils::Fs::toNativePath(m_session->categorySavePath(m_category));
@@ -207,15 +207,29 @@ TorrentHandle::TorrentHandle(Session *session, const libtorrent::torrent_handle
// download sequentially or have first/last piece priority enabled when
// its resume data was saved. These two settings are restored later. But
// if we set them to false now, both will erroneously not be restored.
if (!data.resumed || data.sequential)
setSequentialDownload(data.sequential);
if (!data.resumed || data.firstLastPiecePriority)
setFirstLastPiecePriority(data.firstLastPiecePriority);
if (!params.restored || params.sequential)
setSequentialDownload(params.sequential);
if (!params.restored || params.firstLastPiecePriority)
setFirstLastPiecePriority(params.firstLastPiecePriority);
if (!data.resumed && hasMetadata()) {
if (!params.restored && hasMetadata()) {
if (filesCount() == 1)
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).
if (params.paused) {
m_startupState = Started;
}
else if (!params.restored || !hasMetadata()) {
// Resume torrent because it was added in "resumed" state
// but it's actually paused during initialization
m_startupState = Starting;
resume(params.forced);
}
}
TorrentHandle::~TorrentHandle() {}
@@ -315,7 +329,7 @@ QString TorrentHandle::rootPath(bool actual) const
return QString();
QString firstFilePath = filePath(0);
const int slashIndex = firstFilePath.indexOf("/");
const int slashIndex = firstFilePath.indexOf('/');
if (slashIndex >= 0)
return QDir(savePath(actual)).absoluteFilePath(firstFilePath.left(slashIndex));
else
@@ -361,10 +375,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;
@@ -378,7 +391,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;
}
@@ -387,13 +400,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;
@@ -425,9 +438,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;
@@ -436,7 +449,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;
}
@@ -448,7 +461,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;
}
@@ -488,18 +501,12 @@ bool TorrentHandle::connectPeer(const PeerAddress &peerAddress)
bool TorrentHandle::needSaveResumeData() const
{
if (m_needSaveResumeData) return true;
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_needSaveResumeData = false;
}
int TorrentHandle::filesCount() const
@@ -546,7 +553,7 @@ bool TorrentHandle::belongsToCategory(const QString &category) const
if (m_category == category) return true;
if (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + "/"))
if (m_session->isSubcategoriesEnabled() && m_category.startsWith(category + '/'))
return true;
return false;
@@ -573,7 +580,6 @@ bool TorrentHandle::addTag(const QString &tag)
return false;
m_tags.insert(tag);
m_session->handleTorrentTagAdded(this, tag);
m_needSaveResumeData = true;
return true;
}
return false;
@@ -583,7 +589,6 @@ bool TorrentHandle::removeTag(const QString &tag)
{
if (m_tags.remove(tag)) {
m_session->handleTorrentTagRemoved(this, tag);
m_needSaveResumeData = true;
return true;
}
return false;
@@ -591,8 +596,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);
}
@@ -618,7 +622,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));
}
@@ -631,7 +635,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;
@@ -642,7 +646,7 @@ QStringList TorrentHandle::absoluteFilePaths() const
QStringList TorrentHandle::absoluteFilePathsUnwanted() const
{
if (!hasMetadata()) return QStringList();
if (!hasMetadata()) return QStringList();
QDir saveDir(savePath(true));
QStringList res;
@@ -802,7 +806,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()) {
@@ -834,9 +841,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;
@@ -878,9 +882,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;
@@ -968,12 +972,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
@@ -1081,7 +1086,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;
@@ -1198,23 +1203,18 @@ void TorrentHandle::setName(const QString &name)
{
if (m_name != name) {
m_name = name;
m_needSaveResumeData = true;
m_session->handleTorrentNameChanged(this);
}
}
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;
m_needSaveResumeData = true;
m_session->handleTorrentCategoryChanged(this, oldCategory);
if (m_useAutoTMM) {
@@ -1252,7 +1252,6 @@ void TorrentHandle::move_impl(QString path, bool overwrite)
}
else {
m_savePath = path;
m_needSaveResumeData = true;
m_session->handleTorrentSavePathChanged(this);
}
}
@@ -1271,12 +1270,13 @@ void TorrentHandle::forceRecheck()
{
if (!hasMetadata()) return;
m_nativeHandle.force_recheck();
m_unchecked = false;
if (isPaused()) {
m_pauseAfterRecheck = true;
m_nativeHandle.stop_when_ready(true);
resume_impl(true, true);
}
m_nativeHandle.force_recheck();
}
void TorrentHandle::setSequentialDownload(bool b)
@@ -1294,13 +1294,21 @@ void TorrentHandle::toggleSequentialDownload()
void TorrentHandle::setFirstLastPiecePriority(const bool enabled)
{
setFirstLastPiecePriorityImpl(enabled);
}
void TorrentHandle::setFirstLastPiecePriorityImpl(const bool enabled, const QVector<int> &updatedFilePrio)
{
// Download first and last pieces first for every file in the torrent
if (!hasMetadata()) {
m_needsToSetFirstLastPiecePriority = enabled;
return;
}
// Download first and last pieces first for every file in the torrent
const std::vector<int> filePriorities = nativeHandle().file_priorities();
// Updating file priorities is an async operation in libtorrent, when we just updated it and immediately query it
// we might get the old/wrong values, so we rely on `updatedFilePrio` in this case.
const std::vector<int> filePriorities = !updatedFilePrio.isEmpty() ? updatedFilePrio.toStdVector() : nativeHandle().file_priorities();
std::vector<int> piecePriorities = nativeHandle().piece_priorities();
for (int index = 0; index < static_cast<int>(filePriorities.size()); ++index) {
const int filePrio = filePriorities[index];
@@ -1336,6 +1344,12 @@ void TorrentHandle::pause()
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)
@@ -1347,7 +1361,12 @@ void TorrentHandle::resume_impl(bool forced, bool uploadMode)
{
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();
@@ -1405,16 +1424,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);
@@ -1539,21 +1548,32 @@ 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 == NotStarted) {
if (!m_hasMissingFiles) {
// Resume torrent because it was added in "resumed" state
// but it's actually paused during initialization.
m_startupState = Starting;
resume(m_needsToStartForced);
}
else {
// Torrent that has missing files is marked as "started"
// but it remains paused.
m_startupState = Started;
}
}
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);
@@ -1562,7 +1582,7 @@ 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");
if (m_hasSeedStatus) return;
@@ -1574,13 +1594,13 @@ void TorrentHandle::handleTorrentFinishedAlert(const libtorrent::torrent_finishe
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);
}
@@ -1589,15 +1609,22 @@ 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) {
updateStatus();
m_speedMonitor.reset();
m_session->handleTorrentPaused(this);
}
}
void TorrentHandle::handleTorrentResumedAlert(const libtorrent::torrent_resumed_alert *p)
{
Q_UNUSED(p);
m_session->handleTorrentResumed(this);
if (m_startupState == Started)
m_session->handleTorrentResumed(this);
else if (m_startupState == Starting)
m_startupState = Started;
}
void TorrentHandle::handleSaveResumeDataAlert(const libtorrent::save_resume_data_alert *p)
@@ -1628,7 +1655,7 @@ 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;
m_session->handleTorrentResumeDataReady(this, resumeData);
@@ -1646,15 +1673,10 @@ void TorrentHandle::handleSaveResumeDataFailedAlert(const libtorrent::save_resum
void TorrentHandle::handleFastResumeRejectedAlert(const libtorrent::fastresume_rejected_alert *p)
{
qDebug("/!\\ Fast resume failed for %s, reason: %s", qUtf8Printable(name()), p->message().c_str());
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...")
@@ -1673,12 +1695,12 @@ void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert
// TODO: Check this!
if (filesCount() > 1) {
// Check if folders were renamed
QStringList oldPathParts = m_torrentInfo.origFilePath(p->index).split("/");
QStringList oldPathParts = m_torrentInfo.origFilePath(p->index).split('/');
oldPathParts.removeLast();
QString oldPath = oldPathParts.join("/");
QStringList newPathParts = newName.split("/");
QString oldPath = oldPathParts.join('/');
QStringList newPathParts = newName.split('/');
newPathParts.removeLast();
QString newPath = newPathParts.join("/");
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);
@@ -1687,7 +1709,9 @@ void TorrentHandle::handleFileRenamedAlert(const libtorrent::file_renamed_alert
}
}
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()};
--m_renameCount;
while (!isMoveInProgress() && (m_renameCount == 0) && !m_moveFinishedTriggers.isEmpty())
@@ -1705,7 +1729,9 @@ void TorrentHandle::handleFileRenameFailedAlert(const libtorrent::file_rename_fa
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()) {
@@ -1806,6 +1832,9 @@ void TorrentHandle::handleAlert(libtorrent::alert *a)
case libt::torrent_paused_alert::alert_type:
handleTorrentPausedAlert(static_cast<libt::torrent_paused_alert*>(a));
break;
case libt::torrent_resumed_alert::alert_type:
handleTorrentResumedAlert(static_cast<libt::torrent_resumed_alert*>(a));
break;
case libt::tracker_error_alert::alert_type:
handleTrackerErrorAlert(static_cast<libt::tracker_error_alert*>(a));
break;
@@ -1831,7 +1860,7 @@ void TorrentHandle::manageIncompleteFiles()
{
const bool isAppendExtensionEnabled = m_session->isAppendExtensionEnabled();
QVector<qreal> fp = filesProgress();
if( fp.size() != filesCount() ) {
if (fp.size() != filesCount()) {
qDebug() << "skip manageIncompleteFiles because of invalid torrent meta-data or empty file-progress";
return;
}
@@ -1917,6 +1946,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)
@@ -1928,7 +1964,6 @@ void TorrentHandle::setRatioLimit(qreal limit)
if (m_ratioLimit != limit) {
m_ratioLimit = limit;
m_needSaveResumeData = true;
m_session->handleTorrentShareLimitChanged(this);
}
}
@@ -1942,7 +1977,6 @@ void TorrentHandle::setSeedingTimeLimit(int limit)
if (m_seedingTimeLimit != limit) {
m_seedingTimeLimit = limit;
m_needSaveResumeData = true;
m_session->handleTorrentShareLimitChanged(this);
}
}
@@ -1960,8 +1994,6 @@ void TorrentHandle::setDownloadLimit(int limit)
void TorrentHandle::setSuperSeeding(bool enable)
{
m_nativeHandle.super_seeding(enable);
if (superSeeding() != enable)
updateStatus();
}
void TorrentHandle::flushCache()
@@ -1980,7 +2012,7 @@ void TorrentHandle::prioritizeFiles(const QVector<int> &priorities)
if (priorities.size() != filesCount()) return;
// Save first/last piece first option state
bool firstLastPieceFirst = hasFirstLastPiecePriority();
const bool firstLastPieceFirst = hasFirstLastPiecePriority();
// Reset 'm_hasSeedStatus' if needed in order to react again to
// 'torrent_finished_alert' and eg show tray notifications
@@ -2007,7 +2039,7 @@ void TorrentHandle::prioritizeFiles(const QVector<int> &priorities)
// Make sure the file does not already exists
if (QDir(parentAbsPath).dirName() != ".unwanted") {
QString unwantedAbsPath = parentAbsPath + "/.unwanted";
QString newAbsPath = unwantedAbsPath + "/" + Utils::Fs::fileName(filepath);
QString newAbsPath = unwantedAbsPath + '/' + Utils::Fs::fileName(filepath);
qDebug() << "Unwanted path is" << unwantedAbsPath;
if (QFile::exists(newAbsPath)) {
qWarning() << "File" << newAbsPath << "already exists at destination.";
@@ -2020,15 +2052,15 @@ 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);
}
#endif
QString parentPath = Utils::Fs::branchPath(filepath);
if (!parentPath.isEmpty() && !parentPath.endsWith("/"))
parentPath += "/";
if (!parentPath.isEmpty() && !parentPath.endsWith('/'))
parentPath += '/';
renameFile(i, parentPath + ".unwanted/" + Utils::Fs::fileName(filepath));
}
}
@@ -2045,22 +2077,20 @@ void TorrentHandle::prioritizeFiles(const QVector<int> &priorities)
renameFile(i, QDir(newRelPath).filePath(oldName));
// Remove .unwanted directory if empty
qDebug() << "Attempting to remove .unwanted folder at " << QDir(spath + "/" + newRelPath).absoluteFilePath(".unwanted");
QDir(spath + "/" + newRelPath).rmdir(".unwanted");
qDebug() << "Attempting to remove .unwanted folder at " << QDir(spath + '/' + newRelPath).absoluteFilePath(".unwanted");
QDir(spath + '/' + newRelPath).rmdir(".unwanted");
}
}
}
// Restore first/last piece first option if necessary
if (firstLastPieceFirst)
setFirstLastPiecePriority(true);
updateStatus();
setFirstLastPiecePriorityImpl(true, priorities);
}
QVector<qreal> TorrentHandle::availableFileFractions() const
{
const auto filesCount = this->filesCount();
const int filesCount = this->filesCount();
if (filesCount < 0) return {};
const QVector<int> piecesAvailability = pieceAvailability();
@@ -2069,12 +2099,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

@@ -88,10 +88,10 @@ namespace BitTorrent
class TrackerEntry;
struct AddTorrentParams;
struct AddTorrentData
struct CreateTorrentParams
{
bool resumed;
// for both new and resumed torrents
bool restored; // is existing torrent job?
// for both new and restored torrents
QString name;
QString category;
QSet<QString> tags;
@@ -102,18 +102,18 @@ namespace BitTorrent
bool hasSeedStatus;
bool skipChecking;
bool hasRootFolder;
bool addForced;
bool addPaused;
bool forced;
bool paused;
int uploadLimit;
int downloadLimit;
// for new torrents
QVector<int> filePriorities;
// for resumed torrents
// for restored torrents
qreal ratioLimit;
int seedingTimeLimit;
AddTorrentData();
AddTorrentData(const AddTorrentParams &params);
CreateTorrentParams();
CreateTorrentParams(const AddTorrentParams &params);
};
struct TrackerInfo
@@ -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;
@@ -170,7 +171,7 @@ namespace BitTorrent
static const int MAX_SEEDING_TIME;
TorrentHandle(Session *session, const libtorrent::torrent_handle &nativeHandle,
const AddTorrentData &data);
const CreateTorrentParams &params);
~TorrentHandle();
bool isValid() const;
@@ -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
@@ -420,6 +420,7 @@ namespace BitTorrent
bool addTracker(const TrackerEntry &tracker);
bool addUrlSeed(const QUrl &urlSeed);
bool removeUrlSeed(const QUrl &urlSeed);
void setFirstLastPiecePriorityImpl(bool enabled, const QVector<int> &updatedFilePrio = {});
Session *const m_session;
libtorrent::torrent_handle m_nativeHandle;
@@ -459,10 +460,19 @@ namespace BitTorrent
bool m_hasMissingFiles;
bool m_hasRootFolder;
bool m_needsToSetFirstLastPiecePriority;
bool m_needsToStartForced;
bool m_pauseAfterRecheck;
bool m_needSaveResumeData;
QHash<QString, TrackerInfo> m_trackerInfos;
enum StartupState
{
NotStarted,
Starting,
Started
};
StartupState m_startupState = NotStarted;
bool m_unchecked = false;
};
}

View File

@@ -62,46 +62,6 @@ TorrentInfo &TorrentInfo::operator=(const TorrentInfo &other)
TorrentInfo TorrentInfo::load(const QByteArray &data, QString *error) noexcept
{
libt::error_code ec;
TorrentInfo info(NativePtr(new libt::torrent_info(data.constData(), data.size(), ec)));
if (error) {
if (ec)
*error = QString::fromStdString(ec.message());
else
error->clear();
}
return info;
}
TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexcept
{
if (error)
error->clear();
QFile file {path};
if (!file.open(QIODevice::ReadOnly)) {
if (error)
*error = file.errorString();
return TorrentInfo();
}
const qint64 fileSizeLimit = 100 * 1024 * 1024; // 100 MB
if (file.size() > fileSizeLimit) {
if (error)
*error = tr("File size exceeds max limit %1").arg(fileSizeLimit);
return TorrentInfo();
}
const QByteArray data = file.read(fileSizeLimit);
if (data.size() != file.size()) {
if (error)
*error = tr("Torrent file read error");
return TorrentInfo();
}
file.close();
// 2-step construction to overcome default limits of `depth_limit` & `token_limit` which are
// used in `torrent_info()` constructor
const int depthLimit = 100;
@@ -133,6 +93,45 @@ TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexc
return info;
}
TorrentInfo TorrentInfo::loadFromFile(const QString &path, QString *error) noexcept
{
if (error)
error->clear();
QFile file {path};
if (!file.open(QIODevice::ReadOnly)) {
if (error)
*error = file.errorString();
return TorrentInfo();
}
const qint64 fileSizeLimit = 100 * 1024 * 1024; // 100 MB
if (file.size() > fileSizeLimit) {
if (error)
*error = tr("File size exceeds max limit %1").arg(fileSizeLimit);
return TorrentInfo();
}
QByteArray data;
try {
data = file.readAll();
}
catch (const std::bad_alloc &e) {
if (error)
*error = tr("Torrent file read error: %1").arg(e.what());
return TorrentInfo();
}
if (data.size() != file.size()) {
if (error)
*error = tr("Torrent file read error: size mismatch");
return TorrentInfo();
}
file.close();
return load(data, error);
}
bool TorrentInfo::isValid() const
{
return (m_nativeInfo && m_nativeInfo->is_valid() && (m_nativeInfo->num_files() > 0));
@@ -248,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;
@@ -259,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()));
@@ -340,18 +339,18 @@ 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);
return makeInterval(static_cast<int>(fileOffset / pieceLength()),
static_cast<int>((fileOffset + fileSize - 1) / pieceLength()));
}
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

@@ -60,7 +60,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 +68,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);
@@ -136,7 +136,7 @@ void Tracker::respondToAnnounceRequest()
QMap<QString, QByteArray> queryParams;
// Parse GET parameters
using namespace Utils::ByteArray;
for (const QByteArray &param : copyAsConst(splitToViews(m_request.query, "&"))) {
for (const QByteArray &param : asConst(splitToViews(m_request.query, "&"))) {
const int sepPos = param.indexOf('=');
if (sepPos <= 0) continue; // ignores params without name
@@ -145,10 +145,12 @@ void Tracker::respondToAnnounceRequest()
queryParams[paramName] = paramValue;
}
TrackerAnnounceRequest annonceReq;
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 +158,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 +172,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 +187,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 +241,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,6 +32,7 @@
#include <QHash>
#include <QObject>
#include <QHostAddress>
#include "base/http/irequesthandler.h"
#include "base/http/responsebuilder.h"
@@ -51,7 +52,7 @@ namespace BitTorrent
{
struct Peer
{
QString ip;
QHostAddress ip;
QByteArray peerId;
int port;
@@ -90,9 +91,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

@@ -26,11 +26,12 @@
* exception statement from your version.
*/
#include "trackerentry.h"
#include <QString>
#include "base/utils/misc.h"
#include "base/utils/string.h"
#include "trackerentry.h"
using namespace BitTorrent;

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

@@ -33,16 +33,13 @@
const char C_TORRENT_FILE_EXTENSION[] = ".torrent";
#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

@@ -30,7 +30,6 @@
#include "connection.h"
#include <QRegExp>
#include <QTcpSocket>
#include "base/logger.h"
@@ -134,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

@@ -146,9 +146,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

@@ -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";
@@ -63,10 +64,12 @@ namespace Http
const char HEADER_REQUEST_METHOD_POST[] = "POST";
const char CONTENT_TYPE_HTML[] = "text/html";
const char CONTENT_TYPE_CSS[] = "text/css";
const char CONTENT_TYPE_TXT[] = "text/plain";
const char CONTENT_TYPE_JS[] = "application/javascript";
const char CONTENT_TYPE_JSON[] = "application/json";
const char CONTENT_TYPE_GIF[] = "image/gif";
const char CONTENT_TYPE_PNG[] = "image/png";
const char CONTENT_TYPE_TXT[] = "text/plain";
const char CONTENT_TYPE_FORM_ENCODED[] = "application/x-www-form-urlencoded";
const char CONTENT_TYPE_FORM_DATA[] = "multipart/form-data";
@@ -105,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
@@ -114,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

@@ -27,9 +27,10 @@
* exception statement from your version.
*/
#include <QString>
#include "iconprovider.h"
#include <QFileInfo>
IconProvider::IconProvider(QObject *parent)
: QObject(parent)
{
@@ -56,9 +57,15 @@ IconProvider *IconProvider::instance()
return m_instance;
}
QString IconProvider::getIconPath(const QString &iconId)
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

@@ -31,8 +31,7 @@
#define ICONPROVIDER_H
#include <QObject>
class QString;
#include <QString>
class IconProvider : public QObject
{
@@ -43,7 +42,7 @@ public:
static void freeInstance();
static IconProvider *instance();
virtual QString getIconPath(const QString &iconId);
virtual QString getIconPath(const QString &iconId) const;
protected:
explicit IconProvider(QObject *parent = nullptr);

View File

@@ -38,30 +38,30 @@ class IndexInterval
public:
using IndexType = Index;
IndexInterval(IndexType first, IndexType last)
IndexInterval(IndexType first, IndexType last) // add constexpr when using C++14
: m_first {first}
, m_last {last}
{
Q_ASSERT(first <= last);
}
IndexType first() const
constexpr IndexType first() const
{
return m_first;
}
IndexType last() const
constexpr IndexType last() const
{
return m_last;
}
private:
IndexType m_first;
IndexType m_last;
const IndexType m_first;
const IndexType m_last;
};
template <typename T>
inline IndexInterval<T> makeInterval(T first, T last)
constexpr IndexInterval<T> makeInterval(T first, T last)
{
return {first, last};
}
@@ -99,7 +99,7 @@ public:
constexpr IndexType end() const
{
return m_first + m_size;
return (m_first + m_size);
}
constexpr IndexDiffType size() const
@@ -114,12 +114,12 @@ public:
constexpr IndexType last() const
{
return m_first + m_size - 1;
return (m_first + m_size - 1);
}
constexpr bool isEmpty() const
{
return m_size == 0;
return (m_size == 0);
}
private:

View File

@@ -26,15 +26,15 @@
* exception statement from your version.
*/
#include "dnsupdater.h"
#include <QDebug>
#include <QRegExp>
#include <QStringList>
#include <QRegularExpression>
#include <QUrlQuery>
#include "base/logger.h"
#include "base/net/downloadhandler.h"
#include "base/net/downloadmanager.h"
#include "dnsupdater.h"
using namespace Net;
@@ -74,9 +74,8 @@ void DNSUpdater::checkPublicIP()
{
Q_ASSERT(m_state == OK);
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
"http://checkip.dyndns.org", false, 0, false,
"qBittorrent/" QBT_VERSION_2);
DownloadHandler *handler = DownloadManager::instance()->download(
DownloadRequest("http://checkip.dyndns.org").userAgent("qBittorrent/" QBT_VERSION_2));
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
, this, &DNSUpdater::ipRequestFinished);
connect(handler, &Net::DownloadHandler::downloadFailed, this, &DNSUpdater::ipRequestFailed);
@@ -89,9 +88,9 @@ void DNSUpdater::ipRequestFinished(const QString &url, const QByteArray &data)
Q_UNUSED(url);
// Parse response
QRegExp ipregex("Current IP Address:\\s+([^<]+)</body>");
if (ipregex.indexIn(data) >= 0) {
QString ipStr = ipregex.cap(1);
const QRegularExpressionMatch ipRegexMatch = QRegularExpression("Current IP Address:\\s+([^<]+)</body>").match(data);
if (ipRegexMatch.hasMatch()) {
QString ipStr = ipRegexMatch.captured(1);
qDebug() << Q_FUNC_INFO << "Regular expression captured the following IP:" << ipStr;
QHostAddress newIp(ipStr);
if (!newIp.isNull()) {
@@ -122,9 +121,8 @@ void DNSUpdater::updateDNSService()
qDebug() << Q_FUNC_INFO;
m_lastIPCheckTime = QDateTime::currentDateTime();
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(
getUpdateUrl(), false, 0, false,
"qBittorrent/" QBT_VERSION_2);
DownloadHandler *handler = DownloadManager::instance()->download(
DownloadRequest(getUpdateUrl()).userAgent("qBittorrent/" QBT_VERSION_2));
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
, this, &DNSUpdater::ipUpdateFinished);
connect(handler, &Net::DownloadHandler::downloadFailed, this, &DNSUpdater::ipUpdateFailed);
@@ -183,7 +181,7 @@ void DNSUpdater::processIPUpdateReply(const QString &reply)
{
Logger *const logger = Logger::instance();
qDebug() << Q_FUNC_INFO << reply;
QString code = reply.split(" ").first();
QString code = reply.split(' ').first();
qDebug() << Q_FUNC_INFO << "Code:" << code;
if ((code == "good") || (code == "nochg")) {
@@ -246,8 +244,8 @@ void DNSUpdater::updateCredentials()
}
if (m_domain != pref->getDynDomainName()) {
m_domain = pref->getDynDomainName();
QRegExp domain_regex("^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$");
if (domain_regex.indexIn(m_domain) < 0) {
const QRegularExpressionMatch domainRegexMatch = QRegularExpression("^(?:(?!\\d|-)[a-zA-Z0-9\\-]{1,63}\\.)+[a-zA-Z]{2,}$").match(m_domain);
if (!domainRegexMatch.hasMatch()) {
logger->addMessage(tr("Dynamic DNS error: supplied domain name is invalid."), Log::CRITICAL);
m_lastIP.clear();
m_ipCheckTimer.stop();

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>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -29,11 +29,11 @@
#include "downloadhandler.h"
#include <QCoreApplication>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkCookie>
#include <QNetworkProxy>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTemporaryFile>
#include <QUrl>
@@ -43,35 +43,57 @@
#include "base/utils/misc.h"
#include "downloadmanager.h"
static QString errorCodeToString(QNetworkReply::NetworkError status);
namespace
{
bool saveToFile(const QByteArray &replyData, QString &filePath)
{
QTemporaryFile tmpfile {Utils::Fs::tempPath() + "XXXXXX"};
tmpfile.setAutoRemove(false);
using namespace Net;
if (!tmpfile.open())
return false;
DownloadHandler::DownloadHandler(QNetworkReply *reply, DownloadManager *manager, bool saveToFile, qint64 limit, bool handleRedirectToMagnet)
filePath = tmpfile.fileName();
tmpfile.write(replyData);
return true;
}
}
Net::DownloadHandler::DownloadHandler(QNetworkReply *reply, DownloadManager *manager, const DownloadRequest &downloadRequest)
: QObject(manager)
, m_reply(reply)
, m_manager(manager)
, m_saveToFile(saveToFile)
, m_sizeLimit(limit)
, m_handleRedirectToMagnet(handleRedirectToMagnet)
, m_url(reply->url().toString())
, m_downloadRequest(downloadRequest)
{
init();
if (reply)
assignNetworkReply(reply);
}
DownloadHandler::~DownloadHandler()
Net::DownloadHandler::~DownloadHandler()
{
if (m_reply)
delete m_reply;
}
// Returns original url
QString DownloadHandler::url() const
void Net::DownloadHandler::assignNetworkReply(QNetworkReply *reply)
{
return m_url;
Q_ASSERT(reply);
m_reply = reply;
m_reply->setParent(this);
if (m_downloadRequest.limit() > 0)
connect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize);
connect(m_reply, &QNetworkReply::finished, this, &Net::DownloadHandler::processFinishedDownload);
}
void DownloadHandler::processFinishedDownload()
// Returns original url
QString Net::DownloadHandler::url() const
{
return m_downloadRequest.url();
}
void Net::DownloadHandler::processFinishedDownload()
{
QString url = m_reply->url().toString();
qDebug("Download finished: %s", qUtf8Printable(url));
@@ -79,7 +101,7 @@ void DownloadHandler::processFinishedDownload()
if (m_reply->error() != QNetworkReply::NoError) {
// Failure
qDebug("Download failure (%s), reason: %s", qUtf8Printable(url), qUtf8Printable(errorCodeToString(m_reply->error())));
emit downloadFailed(m_url, errorCodeToString(m_reply->error()));
emit downloadFailed(m_downloadRequest.url(), errorCodeToString(m_reply->error()));
this->deleteLater();
}
else {
@@ -97,15 +119,15 @@ void DownloadHandler::processFinishedDownload()
replyData = Utils::Gzip::decompress(replyData);
}
if (m_saveToFile) {
if (m_downloadRequest.saveToFile()) {
QString filePath;
if (saveToFile(replyData, filePath))
emit downloadFinished(m_url, filePath);
emit downloadFinished(m_downloadRequest.url(), filePath);
else
emit downloadFailed(m_url, tr("I/O Error"));
}
emit downloadFailed(m_downloadRequest.url(), tr("I/O Error"));
}
else {
emit downloadFinished(m_url, replyData);
emit downloadFinished(m_downloadRequest.url(), replyData);
}
this->deleteLater();
@@ -113,137 +135,116 @@ void DownloadHandler::processFinishedDownload()
}
}
void DownloadHandler::checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal)
void Net::DownloadHandler::checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal)
{
QString msg = tr("The file size is %1. It exceeds the download limit of %2.");
if (bytesTotal > 0) {
// Total number of bytes is available
if (bytesTotal > m_sizeLimit) {
if (bytesTotal > m_downloadRequest.limit()) {
m_reply->abort();
emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesTotal), Utils::Misc::friendlyUnit(m_sizeLimit)));
emit downloadFailed(m_downloadRequest.url(), msg.arg(Utils::Misc::friendlyUnit(bytesTotal), Utils::Misc::friendlyUnit(m_downloadRequest.limit())));
}
else {
disconnect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize);
}
}
else if (bytesReceived > m_sizeLimit) {
else if (bytesReceived > m_downloadRequest.limit()) {
m_reply->abort();
emit downloadFailed(m_url, msg.arg(Utils::Misc::friendlyUnit(bytesReceived), Utils::Misc::friendlyUnit(m_sizeLimit)));
emit downloadFailed(m_downloadRequest.url(), msg.arg(Utils::Misc::friendlyUnit(bytesReceived), Utils::Misc::friendlyUnit(m_downloadRequest.limit())));
}
}
void DownloadHandler::init()
{
m_reply->setParent(this);
if (m_sizeLimit > 0)
connect(m_reply, &QNetworkReply::downloadProgress, this, &Net::DownloadHandler::checkDownloadSize);
connect(m_reply, &QNetworkReply::finished, this, &Net::DownloadHandler::processFinishedDownload);
}
bool DownloadHandler::saveToFile(const QByteArray &replyData, QString &filePath)
{
QTemporaryFile *tmpfile = new QTemporaryFile(Utils::Fs::tempPath() + "XXXXXX");
if (!tmpfile->open()) {
delete tmpfile;
return false;
}
tmpfile->setAutoRemove(false);
filePath = tmpfile->fileName();
qDebug("Temporary filename is: %s", qUtf8Printable(filePath));
if (m_reply->isOpen() || m_reply->open(QIODevice::ReadOnly)) {
tmpfile->write(replyData);
tmpfile->close();
// XXX: tmpfile needs to be deleted on Windows before using the file
// or it will complain that the file is used by another process.
delete tmpfile;
return true;
}
else {
delete tmpfile;
Utils::Fs::forceRemove(filePath);
}
return false;
}
void DownloadHandler::handleRedirection(QUrl newUrl)
void Net::DownloadHandler::handleRedirection(QUrl newUrl)
{
// Resolve relative urls
if (newUrl.isRelative())
newUrl = m_reply->url().resolved(newUrl);
const QString newUrlString = newUrl.toString();
qDebug("Redirecting from %s to %s", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString));
qDebug("Redirecting from %s to %s...", qUtf8Printable(m_reply->url().toString()), qUtf8Printable(newUrlString));
// Redirect to magnet workaround
if (newUrlString.startsWith("magnet:", Qt::CaseInsensitive)) {
qDebug("Magnet redirect detected.");
m_reply->abort();
if (m_handleRedirectToMagnet)
emit redirectedToMagnet(m_url, newUrlString);
if (m_downloadRequest.handleRedirectToMagnet())
emit redirectedToMagnet(m_downloadRequest.url(), newUrlString);
else
emit downloadFailed(m_url, tr("Unexpected redirect to magnet URI."));
emit downloadFailed(m_downloadRequest.url(), tr("Unexpected redirect to magnet URI."));
this->deleteLater();
}
else {
DownloadHandler *tmp = m_manager->downloadUrl(newUrlString, m_saveToFile, m_sizeLimit, m_handleRedirectToMagnet);
m_reply->deleteLater();
m_reply = tmp->m_reply;
init();
tmp->m_reply = nullptr;
delete tmp;
DownloadHandler *redirected = m_manager->download(DownloadRequest(m_downloadRequest).url(newUrlString));
connect(redirected, &DownloadHandler::destroyed, this, &DownloadHandler::deleteLater);
connect(redirected, &DownloadHandler::downloadFailed, this, [this](const QString &, const QString &reason)
{
emit downloadFailed(url(), reason);
});
connect(redirected, &DownloadHandler::redirectedToMagnet, this, [this](const QString &, const QString &magnetUri)
{
emit redirectedToMagnet(url(), magnetUri);
});
connect(redirected, static_cast<void (DownloadHandler::*)(const QString &, const QString &)>(&DownloadHandler::downloadFinished)
, this, [this](const QString &, const QString &fileName)
{
emit downloadFinished(url(), fileName);
});
connect(redirected, static_cast<void (DownloadHandler::*)(const QString &, const QByteArray &)>(&DownloadHandler::downloadFinished)
, this, [this](const QString &, const QByteArray &data)
{
emit downloadFinished(url(), data);
});
}
}
QString errorCodeToString(QNetworkReply::NetworkError status)
QString Net::DownloadHandler::errorCodeToString(const QNetworkReply::NetworkError status)
{
switch (status) {
case QNetworkReply::HostNotFoundError:
return QObject::tr("The remote host name was not found (invalid hostname)");
return tr("The remote host name was not found (invalid hostname)");
case QNetworkReply::OperationCanceledError:
return QObject::tr("The operation was canceled");
return tr("The operation was canceled");
case QNetworkReply::RemoteHostClosedError:
return QObject::tr("The remote server closed the connection prematurely, before the entire reply was received and processed");
return tr("The remote server closed the connection prematurely, before the entire reply was received and processed");
case QNetworkReply::TimeoutError:
return QObject::tr("The connection to the remote server timed out");
return tr("The connection to the remote server timed out");
case QNetworkReply::SslHandshakeFailedError:
return QObject::tr("SSL/TLS handshake failed");
return tr("SSL/TLS handshake failed");
case QNetworkReply::ConnectionRefusedError:
return QObject::tr("The remote server refused the connection");
return tr("The remote server refused the connection");
case QNetworkReply::ProxyConnectionRefusedError:
return QObject::tr("The connection to the proxy server was refused");
return tr("The connection to the proxy server was refused");
case QNetworkReply::ProxyConnectionClosedError:
return QObject::tr("The proxy server closed the connection prematurely");
return tr("The proxy server closed the connection prematurely");
case QNetworkReply::ProxyNotFoundError:
return QObject::tr("The proxy host name was not found");
return tr("The proxy host name was not found");
case QNetworkReply::ProxyTimeoutError:
return QObject::tr("The connection to the proxy timed out or the proxy did not reply in time to the request sent");
return tr("The connection to the proxy timed out or the proxy did not reply in time to the request sent");
case QNetworkReply::ProxyAuthenticationRequiredError:
return QObject::tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered");
return tr("The proxy requires authentication in order to honor the request but did not accept any credentials offered");
case QNetworkReply::ContentAccessDenied:
return QObject::tr("The access to the remote content was denied (401)");
return tr("The access to the remote content was denied (401)");
case QNetworkReply::ContentOperationNotPermittedError:
return QObject::tr("The operation requested on the remote content is not permitted");
return tr("The operation requested on the remote content is not permitted");
case QNetworkReply::ContentNotFoundError:
return QObject::tr("The remote content was not found at the server (404)");
return tr("The remote content was not found at the server (404)");
case QNetworkReply::AuthenticationRequiredError:
return QObject::tr("The remote server requires authentication to serve the content but the credentials provided were not accepted");
return tr("The remote server requires authentication to serve the content but the credentials provided were not accepted");
case QNetworkReply::ProtocolUnknownError:
return QObject::tr("The Network Access API cannot honor the request because the protocol is not known");
return tr("The Network Access API cannot honor the request because the protocol is not known");
case QNetworkReply::ProtocolInvalidOperationError:
return QObject::tr("The requested operation is invalid for this protocol");
return tr("The requested operation is invalid for this protocol");
case QNetworkReply::UnknownNetworkError:
return QObject::tr("An unknown network-related error was detected");
return tr("An unknown network-related error was detected");
case QNetworkReply::UnknownProxyError:
return QObject::tr("An unknown proxy-related error was detected");
return tr("An unknown proxy-related error was detected");
case QNetworkReply::UnknownContentError:
return QObject::tr("An unknown error related to the remote content was detected");
return tr("An unknown error related to the remote content was detected");
case QNetworkReply::ProtocolFailure:
return QObject::tr("A breakdown in protocol was detected");
return tr("A breakdown in protocol was detected");
default:
return QObject::tr("Unknown error");
return tr("Unknown error");
}
}

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>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -30,10 +30,11 @@
#ifndef NET_DOWNLOADHANDLER_H
#define NET_DOWNLOADHANDLER_H
#include <QNetworkReply>
#include <QObject>
class QNetworkAccessManager;
class QNetworkReply;
#include "downloadmanager.h"
class QUrl;
namespace Net
@@ -43,10 +44,14 @@ namespace Net
class DownloadHandler : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(DownloadHandler)
friend class DownloadManager;
DownloadHandler(QNetworkReply *reply, DownloadManager *manager, const DownloadRequest &downloadRequest);
public:
DownloadHandler(QNetworkReply *reply, DownloadManager *manager, bool saveToFile = false, qint64 limit = 0, bool handleRedirectToMagnet = false);
~DownloadHandler();
~DownloadHandler() override;
QString url() const;
@@ -61,16 +66,14 @@ namespace Net
void checkDownloadSize(qint64 bytesReceived, qint64 bytesTotal);
private:
void init();
bool saveToFile(const QByteArray &replyData, QString &filePath);
void assignNetworkReply(QNetworkReply *reply);
void handleRedirection(QUrl newUrl);
static QString errorCodeToString(QNetworkReply::NetworkError status);
QNetworkReply *m_reply;
DownloadManager *m_manager;
bool m_saveToFile;
qint64 m_sizeLimit;
bool m_handleRedirectToMagnet;
QString m_url;
const DownloadRequest m_downloadRequest;
};
}

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>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -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);
}
@@ -103,28 +104,47 @@ namespace
return QNetworkCookieJar::setCookiesFromUrl(cookies, url);
}
};
QNetworkRequest createNetworkRequest(const Net::DownloadRequest &downloadRequest)
{
QNetworkRequest request {downloadRequest.url()};
if (downloadRequest.userAgent().isEmpty())
request.setRawHeader("User-Agent", DEFAULT_USER_AGENT);
else
request.setRawHeader("User-Agent", downloadRequest.userAgent().toUtf8());
// Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents
request.setRawHeader("Referer", request.url().toEncoded().data());
// Accept gzip
request.setRawHeader("Accept-Encoding", "gzip");
return request;
}
}
using namespace Net;
Net::DownloadManager *Net::DownloadManager::m_instance = nullptr;
DownloadManager *DownloadManager::m_instance = nullptr;
DownloadManager::DownloadManager(QObject *parent)
Net::DownloadManager::DownloadManager(QObject *parent)
: QObject(parent)
{
#ifndef QT_NO_OPENSSL
connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this, &Net::DownloadManager::ignoreSslErrors);
#endif
connect(&m_networkManager, &QNetworkAccessManager::finished, this, &DownloadManager::handleReplyFinished);
connect(ProxyConfigurationManager::instance(), &ProxyConfigurationManager::proxyConfigurationChanged
, this, &DownloadManager::applyProxySettings);
m_networkManager.setCookieJar(new NetworkCookieJar(this));
applyProxySettings();
}
void DownloadManager::initInstance()
void Net::DownloadManager::initInstance()
{
if (!m_instance)
m_instance = new DownloadManager;
}
void DownloadManager::freeInstance()
void Net::DownloadManager::freeInstance()
{
if (m_instance) {
delete m_instance;
@@ -132,62 +152,65 @@ void DownloadManager::freeInstance()
}
}
DownloadManager *DownloadManager::instance()
Net::DownloadManager *Net::DownloadManager::instance()
{
return m_instance;
}
DownloadHandler *DownloadManager::downloadUrl(const QString &url, bool saveToFile, qint64 limit, bool handleRedirectToMagnet, const QString &userAgent)
Net::DownloadHandler *Net::DownloadManager::download(const DownloadRequest &downloadRequest)
{
// Update proxy settings
applyProxySettings();
// Process download request
qDebug("url is %s", qUtf8Printable(url));
const QUrl qurl = QUrl(url);
QNetworkRequest request(qurl);
const QNetworkRequest request = createNetworkRequest(downloadRequest);
const ServiceID id = ServiceID::fromURL(request.url());
const bool isSequentialService = m_sequentialServices.contains(id);
if (!isSequentialService || !m_busyServices.contains(id)) {
qDebug("Downloading %s...", qUtf8Printable(downloadRequest.url()));
if (isSequentialService)
m_busyServices.insert(id);
return new DownloadHandler {
m_networkManager.get(request), this, downloadRequest};
}
if (userAgent.isEmpty())
request.setRawHeader("User-Agent", DEFAULT_USER_AGENT);
else
request.setRawHeader("User-Agent", userAgent.toUtf8());
// Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents
request.setRawHeader("Referer", request.url().toEncoded().data());
qDebug("Downloading %s...", request.url().toEncoded().data());
qDebug() << "Cookies:" << m_networkManager.cookieJar()->cookiesForUrl(request.url());
// accept gzip
request.setRawHeader("Accept-Encoding", "gzip");
return new DownloadHandler(m_networkManager.get(request), this, saveToFile, limit, handleRedirectToMagnet);
auto *downloadHandler = new DownloadHandler {nullptr, this, downloadRequest};
connect(downloadHandler, &DownloadHandler::destroyed, this, [this, id, downloadHandler]()
{
m_waitingJobs[id].removeOne(downloadHandler);
});
m_waitingJobs[id].enqueue(downloadHandler);
return downloadHandler;
}
QList<QNetworkCookie> DownloadManager::cookiesForUrl(const QUrl &url) const
void Net::DownloadManager::registerSequentialService(const Net::ServiceID &serviceID)
{
m_sequentialServices.insert(serviceID);
}
QList<QNetworkCookie> Net::DownloadManager::cookiesForUrl(const QUrl &url) const
{
return m_networkManager.cookieJar()->cookiesForUrl(url);
}
bool DownloadManager::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
bool Net::DownloadManager::setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url)
{
return m_networkManager.cookieJar()->setCookiesFromUrl(cookieList, url);
}
QList<QNetworkCookie> DownloadManager::allCookies() const
QList<QNetworkCookie> Net::DownloadManager::allCookies() const
{
return static_cast<NetworkCookieJar *>(m_networkManager.cookieJar())->allCookies();
}
void DownloadManager::setAllCookies(const QList<QNetworkCookie> &cookieList)
void Net::DownloadManager::setAllCookies(const QList<QNetworkCookie> &cookieList)
{
static_cast<NetworkCookieJar *>(m_networkManager.cookieJar())->setAllCookies(cookieList);
}
bool DownloadManager::deleteCookie(const QNetworkCookie &cookie)
bool Net::DownloadManager::deleteCookie(const QNetworkCookie &cookie)
{
return static_cast<NetworkCookieJar *>(m_networkManager.cookieJar())->deleteCookie(cookie);
}
void DownloadManager::applyProxySettings()
void Net::DownloadManager::applyProxySettings()
{
auto proxyManager = ProxyConfigurationManager::instance();
ProxyConfiguration proxyConfig = proxyManager->proxyConfiguration();
@@ -208,7 +231,7 @@ void DownloadManager::applyProxySettings()
}
// Authentication?
if (proxyManager->isAuthenticationRequired()) {
qDebug("Proxy requires authentication, authenticating");
qDebug("Proxy requires authentication, authenticating...");
proxy.setUser(proxyConfig.username);
proxy.setPassword(proxyConfig.password);
}
@@ -220,11 +243,101 @@ void DownloadManager::applyProxySettings()
m_networkManager.setProxy(proxy);
}
void Net::DownloadManager::handleReplyFinished(QNetworkReply *reply)
{
const ServiceID id = ServiceID::fromURL(reply->url());
auto waitingJobsIter = m_waitingJobs.find(id);
if ((waitingJobsIter == m_waitingJobs.end()) || waitingJobsIter.value().isEmpty()) {
m_busyServices.remove(id);
return;
}
DownloadHandler *handler = waitingJobsIter.value().dequeue();
qDebug("Downloading %s...", qUtf8Printable(handler->m_downloadRequest.url()));
handler->assignNetworkReply(m_networkManager.get(createNetworkRequest(handler->m_downloadRequest)));
handler->disconnect(this);
}
#ifndef QT_NO_OPENSSL
void DownloadManager::ignoreSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
void Net::DownloadManager::ignoreSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
{
Q_UNUSED(errors)
// Ignore all SSL errors
reply->ignoreSslErrors();
}
#endif
Net::DownloadRequest::DownloadRequest(const QString &url)
: m_url {url}
{
}
QString Net::DownloadRequest::url() const
{
return m_url;
}
Net::DownloadRequest &Net::DownloadRequest::url(const QString &value)
{
m_url = value;
return *this;
}
QString Net::DownloadRequest::userAgent() const
{
return m_userAgent;
}
Net::DownloadRequest &Net::DownloadRequest::userAgent(const QString &value)
{
m_userAgent = value;
return *this;
}
qint64 Net::DownloadRequest::limit() const
{
return m_limit;
}
Net::DownloadRequest &Net::DownloadRequest::limit(qint64 value)
{
m_limit = value;
return *this;
}
bool Net::DownloadRequest::saveToFile() const
{
return m_saveToFile;
}
Net::DownloadRequest &Net::DownloadRequest::saveToFile(bool value)
{
m_saveToFile = value;
return *this;
}
bool Net::DownloadRequest::handleRedirectToMagnet() const
{
return m_handleRedirectToMagnet;
}
Net::DownloadRequest &Net::DownloadRequest::handleRedirectToMagnet(bool value)
{
m_handleRedirectToMagnet = value;
return *this;
}
Net::ServiceID Net::ServiceID::fromURL(const QUrl &url)
{
return {url.host(), url.port(80)};
}
uint Net::qHash(const ServiceID &serviceID, uint seed)
{
return ::qHash(serviceID.hostName, seed) ^ serviceID.port;
}
bool Net::operator==(const ServiceID &lhs, const ServiceID &rhs)
{
return ((lhs.hostName == rhs.hostName) && (lhs.port == rhs.port));
}

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>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -30,8 +30,12 @@
#ifndef NET_DOWNLOADMANAGER_H
#define NET_DOWNLOADMANAGER_H
#include <QObject>
#include <QHash>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QObject>
#include <QQueue>
#include <QSet>
class QNetworkReply;
class QNetworkCookie;
@@ -42,16 +46,57 @@ namespace Net
{
class DownloadHandler;
class DownloadRequest
{
public:
DownloadRequest(const QString &url);
DownloadRequest(const DownloadRequest &other) = default;
QString url() const;
DownloadRequest &url(const QString &value);
QString userAgent() const;
DownloadRequest &userAgent(const QString &value);
qint64 limit() const;
DownloadRequest &limit(qint64 value);
bool saveToFile() const;
DownloadRequest &saveToFile(bool value);
bool handleRedirectToMagnet() const;
DownloadRequest &handleRedirectToMagnet(bool value);
private:
QString m_url;
QString m_userAgent;
qint64 m_limit = 0;
bool m_saveToFile = false;
bool m_handleRedirectToMagnet = false;
};
struct ServiceID
{
QString hostName;
int port;
static ServiceID fromURL(const QUrl &url);
};
class DownloadManager : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(DownloadManager)
public:
static void initInstance();
static void freeInstance();
static DownloadManager *instance();
DownloadHandler *downloadUrl(const QString &url, bool saveToFile = false, qint64 limit = 0, bool handleRedirectToMagnet = false, const QString &userAgent = "");
DownloadHandler *download(const DownloadRequest &downloadRequest);
void registerSequentialService(const ServiceID &serviceID);
QList<QNetworkCookie> cookiesForUrl(const QUrl &url) const;
bool setCookiesFromUrl(const QList<QNetworkCookie> &cookieList, const QUrl &url);
QList<QNetworkCookie> allCookies() const;
@@ -60,17 +105,25 @@ namespace Net
private slots:
#ifndef QT_NO_OPENSSL
void ignoreSslErrors(QNetworkReply *,const QList<QSslError> &);
void ignoreSslErrors(QNetworkReply *, const QList<QSslError> &);
#endif
private:
explicit DownloadManager(QObject *parent = nullptr);
void applyProxySettings();
void handleReplyFinished(QNetworkReply *reply);
static DownloadManager *m_instance;
QNetworkAccessManager m_networkManager;
QSet<ServiceID> m_sequentialServices;
QSet<ServiceID> m_busyServices;
QHash<ServiceID, QQueue<DownloadHandler *>> m_waitingJobs;
};
uint qHash(const ServiceID &serviceID, uint seed);
bool operator==(const ServiceID &lhs, const ServiceID &rhs);
}
#endif // NET_DOWNLOADMANAGER_H

View File

@@ -118,7 +118,7 @@ void GeoIPManager::manageDatabaseUpdate()
void GeoIPManager::downloadDatabaseFile()
{
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(DATABASE_URL);
DownloadHandler *handler = DownloadManager::instance()->download({DATABASE_URL});
connect(handler, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
, this, &GeoIPManager::downloadFinished);
connect(handler, &Net::DownloadHandler::downloadFailed, this, &GeoIPManager::downloadFailed);

View File

@@ -29,7 +29,6 @@
#include <QDateTime>
#include <QDebug>
#include <QFile>
#include <QHash>
#include <QHostAddress>
#include <QVariant>
@@ -93,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());
@@ -106,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;
@@ -123,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());
@@ -132,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

@@ -75,7 +75,7 @@ void ReverseResolution::resolve(const QString &ip)
void ReverseResolution::hostResolved(const QHostInfo &host)
{
const QString &ip = m_lookups.take(host.lookupId());
const QString ip = m_lookups.take(host.lookupId());
Q_ASSERT(!ip.isNull());
if (host.error() != QHostInfo::NoError) {
@@ -83,7 +83,7 @@ void ReverseResolution::hostResolved(const QHostInfo &host)
return;
}
const QString &hostname = host.hostName();
const QString hostname = host.hostName();
qDebug() << Q_FUNC_INFO << ip << QString("->") << hostname;
m_cache.insert(ip, new QString(hostname));

View File

@@ -34,18 +34,16 @@
#include <QCryptographicHash>
#include <QDebug>
#include <QHostAddress>
#include <QHostInfo>
#include <QNetworkInterface>
#include <QStringList>
#include <QTextCodec>
#include <QTextStream>
#ifndef QT_NO_OPENSSL
#include <QSslSocket>
#else
#include <QTcpSocket>
#endif
#include "base/global.h"
#include "base/logger.h"
#include "base/preferences.h"
@@ -67,7 +65,7 @@ namespace
// ascii characters 0x36 ("6") and 0x5c ("\") are selected because they have large
// Hamming distance (http://en.wikipedia.org/wiki/Hamming_distance)
for (int i = 0; i < key.length(); i++) {
for (int i = 0; i < key.length(); ++i) {
innerPadding[i] = innerPadding[i] ^ key.at(i); // XOR operation between every byte in key and innerpadding, of key length
outerPadding[i] = outerPadding[i] ^ key.at(i); // XOR operation between every byte in key and outerpadding, of key length
}
@@ -294,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();
@@ -302,7 +300,7 @@ QByteArray Smtp::encodeMimeHeader(const QString &key, const QString &value, QTex
if (firstWord)
line += word;
else
line += " " + word;
line += ' ' + word;
firstWord = false;
}
}
@@ -426,7 +424,7 @@ void Smtp::authenticate()
// Skip authentication
logError("The SMTP server does not seem to support any of the authentications modes "
"we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, "
"knowing it is likely to fail... Server Auth Modes: " + auth.join("|"));
"knowing it is likely to fail... Server Auth Modes: " + auth.join('|'));
m_state = Authenticated;
// At this point the server will not send any response
// So fill the buffer with a fake one to pass the tests
@@ -506,7 +504,7 @@ void Smtp::authLogin()
void Smtp::logError(const QString &msg)
{
qDebug() << "Email Notification Error:" << msg;
Logger::instance()->addMessage(tr("Email Notification Error:") + " " + msg, Log::CRITICAL);
Logger::instance()->addMessage(tr("Email Notification Error:") + ' ' + msg, Log::CRITICAL);
}
QString Smtp::getCurrentDateTime() const
@@ -532,7 +530,7 @@ QString Smtp::getCurrentDateTime() const
std::snprintf(buf, sizeof(buf), "%+05d", timeOffset);
QString timeOffsetStr = buf;
QString ret = weekDayStr + ", " + dayStr + " " + monthStr + " " + yearStr + " " + timeStr + " " + timeOffsetStr;
QString ret = weekDayStr + ", " + dayStr + ' ' + monthStr + ' ' + yearStr + ' ' + timeStr + ' ' + timeOffsetStr;
return ret;
}

View File

@@ -46,7 +46,6 @@ class QSslSocket;
class QTcpSocket;
#endif
class QTextCodec;
class QTextStream;
namespace Net
{

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 sledgehammer999 <sledgehammer999@qbittorrent.org>
* Copyright (C) 2014 sledgehammer999 <hammered999@gmail.com>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -33,7 +33,6 @@
#include <QDir>
#include <QLocale>
#include <QMutableListIterator>
#include <QPair>
#include <QSettings>
#ifndef DISABLE_GUI
@@ -44,13 +43,14 @@
#ifdef Q_OS_WIN
#include <shlobj.h>
#include <winreg.h>
#include <QRegularExpression>
#endif
#ifdef Q_OS_MAC
#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)
@@ -183,6 +184,16 @@ void Preferences::setMinimizeToTray(bool b)
setValue("Preferences/General/MinimizeToTray", b);
}
bool Preferences::minimizeToTrayNotified() const
{
return value("Preferences/General/MinimizeToTrayNotified", false).toBool();
}
void Preferences::setMinimizeToTrayNotified(bool b)
{
setValue("Preferences/General/MinimizeToTrayNotified", b);
}
bool Preferences::closeToTray() const
{
return value("Preferences/General/CloseToTray", true).toBool();
@@ -192,7 +203,17 @@ void Preferences::setCloseToTray(bool b)
{
setValue("Preferences/General/CloseToTray", b);
}
#endif
bool Preferences::closeToTrayNotified() const
{
return value("Preferences/General/CloseToTrayNotified", false).toBool();
}
void Preferences::setCloseToTrayNotified(bool b)
{
setValue("Preferences/General/CloseToTrayNotified", b);
}
#endif // Q_OS_MAC
bool Preferences::isToolbarDisplayed() const
{
@@ -235,14 +256,24 @@ void Preferences::setSplashScreenDisabled(bool b)
}
// Preventing from system suspend while active torrents are presented.
bool Preferences::preventFromSuspend() const
bool Preferences::preventFromSuspendWhenDownloading() const
{
return value("Preferences/General/PreventFromSuspend", false).toBool();
return value("Preferences/General/PreventFromSuspendWhenDownloading", false).toBool();
}
void Preferences::setPreventFromSuspend(bool b)
void Preferences::setPreventFromSuspendWhenDownloading(bool b)
{
setValue("Preferences/General/PreventFromSuspend", b);
setValue("Preferences/General/PreventFromSuspendWhenDownloading", b);
}
bool Preferences::preventFromSuspendWhenSeeding() const
{
return value("Preferences/General/PreventFromSuspendWhenSeeding", false).toBool();
}
void Preferences::setPreventFromSuspendWhenSeeding(bool b)
{
setValue("Preferences/General/PreventFromSuspendWhenSeeding", b);
}
#ifdef Q_OS_WIN
@@ -256,14 +287,14 @@ void Preferences::setWinStartup(bool b)
{
QSettings settings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);
if (b) {
const QString bin_path = "\"" + Utils::Fs::toNativePath(qApp->applicationFilePath()) + "\"";
settings.setValue("qBittorrent", bin_path);
const QString binPath = '"' + Utils::Fs::toNativePath(qApp->applicationFilePath()) + '"';
settings.setValue("qBittorrent", binPath);
}
else {
settings.remove("qBittorrent");
}
}
#endif
#endif // Q_OS_WIN
// Downloads
QString Preferences::lastLocationPath() const
@@ -475,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)
@@ -500,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)
@@ -510,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)
@@ -554,28 +585,58 @@ void Preferences::setWebUiUsername(const QString &username)
QString Preferences::getWebUiPassword() const
{
QString pass_ha1 = value("Preferences/WebUI/Password_ha1").toString();
if (pass_ha1.isEmpty()) {
QString passHa1 = value("Preferences/WebUI/Password_ha1").toString();
if (passHa1.isEmpty()) {
QCryptographicHash md5(QCryptographicHash::Md5);
md5.addData("adminadmin");
pass_ha1 = md5.result().toHex();
passHa1 = md5.result().toHex();
}
return pass_ha1;
return passHa1;
}
void Preferences::setWebUiPassword(const QString &new_password)
void Preferences::setWebUiPassword(const QString &newPassword)
{
// Do not overwrite current password with its hash
if (new_password == getWebUiPassword())
if (newPassword == getWebUiPassword())
return;
// Encode to md5 and save
QCryptographicHash md5(QCryptographicHash::Md5);
md5.addData(new_password.toLocal8Bit());
md5.addData(newPassword.toLocal8Bit());
setValue("Preferences/WebUI/Password_ha1", md5.result().toHex());
}
bool Preferences::isWebUiClickjackingProtectionEnabled() const
{
return value("Preferences/WebUI/ClickjackingProtection", true).toBool();
}
void Preferences::setWebUiClickjackingProtectionEnabled(bool enabled)
{
setValue("Preferences/WebUI/ClickjackingProtection", enabled);
}
bool Preferences::isWebUiCSRFProtectionEnabled() const
{
return value("Preferences/WebUI/CSRFProtection", true).toBool();
}
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();
@@ -828,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();
@@ -1001,13 +915,14 @@ bool Preferences::isMagnetLinkAssocSet()
QSettings settings("HKEY_CURRENT_USER\\Software\\Classes", QSettings::NativeFormat);
// Check magnet link assoc
QRegExp exe_reg("\"([^\"]+)\".*");
QString shell_command = Utils::Fs::toNativePath(settings.value("magnet/shell/open/command/Default", "").toString());
if (exe_reg.indexIn(shell_command) < 0)
const QString shellCommand = Utils::Fs::toNativePath(settings.value("magnet/shell/open/command/Default", "").toString());
const QRegularExpressionMatch exeRegMatch = QRegularExpression("\"([^\"]+)\".*").match(shellCommand);
if (!exeRegMatch.hasMatch())
return false;
QString assoc_exe = exe_reg.cap(1);
qDebug("exe: %s", qUtf8Printable(assoc_exe));
if (assoc_exe.compare(Utils::Fs::toNativePath(qApp->applicationFilePath()), Qt::CaseInsensitive) != 0)
const QString assocExe = exeRegMatch.captured(1);
if (assocExe.compare(Utils::Fs::toNativePath(qApp->applicationFilePath()), Qt::CaseInsensitive) != 0)
return false;
return true;
@@ -1019,9 +934,9 @@ void Preferences::setTorrentFileAssoc(bool set)
// .Torrent association
if (set) {
QString old_progid = settings.value(".torrent/Default").toString();
if (!old_progid.isEmpty() && (old_progid != "qBittorrent"))
settings.setValue(".torrent/OpenWithProgids/" + old_progid, "");
QString oldProgId = settings.value(".torrent/Default").toString();
if (!oldProgId.isEmpty() && (oldProgId != "qBittorrent"))
settings.setValue(".torrent/OpenWithProgids/" + oldProgId, "");
settings.setValue(".torrent/Default", "qBittorrent");
}
else if (isTorrentFileAssocSet()) {
@@ -1037,15 +952,15 @@ void Preferences::setMagnetLinkAssoc(bool set)
// Magnet association
if (set) {
const QString command_str = "\"" + qApp->applicationFilePath() + "\" \"%1\"";
const QString icon_str = "\"" + qApp->applicationFilePath() + "\",1";
const QString commandStr = '"' + qApp->applicationFilePath() + "\" \"%1\"";
const QString iconStr = '"' + qApp->applicationFilePath() + "\",1";
settings.setValue("magnet/Default", "URL:Magnet link");
settings.setValue("magnet/Content Type", "application/x-magnet");
settings.setValue("magnet/URL Protocol", "");
settings.setValue("magnet/DefaultIcon/Default", Utils::Fs::toNativePath(icon_str));
settings.setValue("magnet/DefaultIcon/Default", Utils::Fs::toNativePath(iconStr));
settings.setValue("magnet/shell/Default", "open");
settings.setValue("magnet/shell/open/command/Default", Utils::Fs::toNativePath(command_str));
settings.setValue("magnet/shell/open/command/Default", Utils::Fs::toNativePath(commandStr));
}
else if (isMagnetLinkAssocSet()) {
settings.remove("magnet");
@@ -1053,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
@@ -1109,7 +1024,7 @@ void Preferences::setMagnetLinkAssoc()
CFStringRef myBundleId = CFBundleGetIdentifier(CFBundleGetMainBundle());
LSSetDefaultHandlerForURLScheme(magnetUrlScheme, myBundleId);
}
#endif
#endif // Q_OS_MAC
int Preferences::getTrackerPort() const
{
@@ -1378,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();
@@ -1468,6 +1393,16 @@ void Preferences::setTransHeaderState(const QByteArray &state)
setValue("TransferList/qt5/HeaderState", state);
}
bool Preferences::getRegexAsFilteringPatternForTransferList() const
{
return value("TransferList/UseRegexAsFilteringPattern", false).toBool();
}
void Preferences::setRegexAsFilteringPatternForTransferList(const bool checked)
{
setValue("TransferList/UseRegexAsFilteringPattern", checked);
}
// From old RssSettings class
bool Preferences::isRSSWidgetEnabled() const
{
@@ -1492,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;
@@ -1502,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();
@@ -1531,18 +1476,27 @@ void Preferences::setSpeedWidgetGraphEnable(int id, const bool enable)
void Preferences::upgrade()
{
QStringList labels = value("TransferListFilters/customLabels").toStringList();
SettingsStorage *settingsStorage = SettingsStorage::instance();
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] = "";
}
setValue("BitTorrent/Session/Categories", categories);
SettingsStorage::instance()->removeValue("TransferListFilters/customLabels");
settingsStorage->removeValue("TransferListFilters/customLabels");
}
SettingsStorage::instance()->removeValue("Preferences/Downloads/AppendLabel");
settingsStorage->removeValue("Preferences/Downloads/AppendLabel");
// Inhibit sleep based on running downloads/available seeds rather than network activity.
if (value("Preferences/General/PreventFromSuspend", false).toBool()) {
setPreventFromSuspendWhenDownloading(true);
setPreventFromSuspendWhenSeeding(true);
}
settingsStorage->removeValue("Preferences/General/PreventFromSuspend");
}
void Preferences::apply()

View File

@@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 sledgehammer999 <sledgehammer999@qbittorrent.org>
* Copyright (C) 2014 sledgehammer999 <hammered999@gmail.com>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@@ -123,8 +123,10 @@ public:
void setStartMinimized(bool b);
bool isSplashScreenDisabled() const;
void setSplashScreenDisabled(bool b);
bool preventFromSuspend() const;
void setPreventFromSuspend(bool b);
bool preventFromSuspendWhenDownloading() const;
void setPreventFromSuspendWhenDownloading(bool b);
bool preventFromSuspendWhenSeeding() const;
void setPreventFromSuspendWhenSeeding(bool b);
#ifdef Q_OS_WIN
bool WinStartup() const;
void setWinStartup(bool b);
@@ -192,7 +194,15 @@ public:
QString getWebUiUsername() const;
void setWebUiUsername(const QString &username);
QString getWebUiPassword() const;
void setWebUiPassword(const QString &new_password);
void setWebUiPassword(const QString &newPassword);
// WebUI security
bool isWebUiClickjackingProtectionEnabled() const;
void setWebUiClickjackingProtectionEnabled(bool enabled);
bool isWebUiCSRFProtectionEnabled() const;
void setWebUiCSRFProtectionEnabled(bool enabled);
bool isWebUIHostHeaderValidationEnabled() const;
void setWebUIHostHeaderValidationEnabled(bool enabled);
// HTTPS
bool isWebUiHttpsEnabled() const;
@@ -251,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();
@@ -280,13 +289,17 @@ public:
#ifndef Q_OS_MAC
bool systrayIntegration() const;
void setSystrayIntegration(bool enabled);
bool minimizeToTrayNotified() const;
void setMinimizeToTrayNotified(bool b);
bool minimizeToTray() const;
void setMinimizeToTray(bool b);
bool closeToTray() const;
void setCloseToTray(bool b);
bool closeToTrayNotified() const;
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.
@@ -330,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;
@@ -344,6 +359,8 @@ public:
void setTransSelFilter(const int &index);
QByteArray getTransHeaderState() const;
void setTransHeaderState(const QByteArray &state);
bool getRegexAsFilteringPatternForTransferList() const;
void setRegexAsFilteringPatternForTransferList(bool checked);
int getToolbarTextPosition() const;
void setToolbarTextPosition(const int position);
@@ -356,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

@@ -25,7 +25,6 @@
* 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 "profile_p.h"

View File

@@ -25,7 +25,6 @@
* 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.
*
*/
#ifndef QBT_PROFILE_H

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,16 +582,6 @@ 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

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);
@@ -502,7 +526,7 @@ void AutoDownloadRule::setMustContain(const QString &tokens)
if (m_dataPtr->useRegex)
m_dataPtr->mustContain = QStringList() << tokens;
else
m_dataPtr->mustContain = tokens.split("|");
m_dataPtr->mustContain = tokens.split('|');
// Check for single empty string - if so, no condition
if ((m_dataPtr->mustContain.size() == 1) && m_dataPtr->mustContain[0].isEmpty())
@@ -516,7 +540,7 @@ void AutoDownloadRule::setMustNotContain(const QString &tokens)
if (m_dataPtr->useRegex)
m_dataPtr->mustNotContain = QStringList() << tokens;
else
m_dataPtr->mustNotContain = tokens.split("|");
m_dataPtr->mustNotContain = tokens.split('|');
// Check for single empty string - if so, no condition
if ((m_dataPtr->mustNotContain.size() == 1) && m_dataPtr->mustNotContain[0].isEmpty())
@@ -605,12 +629,12 @@ int AutoDownloadRule::ignoreDays() const
QString AutoDownloadRule::mustContain() const
{
return m_dataPtr->mustContain.join("|");
return m_dataPtr->mustContain.join('|');
}
QString AutoDownloadRule::mustNotContain() const
{
return m_dataPtr->mustNotContain.join("|");
return m_dataPtr->mustNotContain.join('|');
}
bool AutoDownloadRule::useSmartFilter() const

View File

@@ -30,7 +30,9 @@
#include "rss_feed.h"
#include <QCryptographicHash>
#include <algorithm>
#include <vector>
#include <QDebug>
#include <QDir>
#include <QJsonArray>
@@ -41,6 +43,7 @@
#include <QUrl>
#include "../asyncfilestorage.h"
#include "../global.h"
#include "../logger.h"
#include "../net/downloadhandler.h"
#include "../net/downloadmanager.h"
@@ -50,21 +53,30 @@
#include "rss_article.h"
#include "rss_session.h"
const QString Str_Url(QStringLiteral("url"));
const QString Str_Title(QStringLiteral("title"));
const QString Str_LastBuildDate(QStringLiteral("lastBuildDate"));
const QString Str_IsLoading(QStringLiteral("isLoading"));
const QString Str_HasError(QStringLiteral("hasError"));
const QString Str_Articles(QStringLiteral("articles"));
const QString KEY_UID(QStringLiteral("uid"));
const QString KEY_URL(QStringLiteral("url"));
const QString KEY_TITLE(QStringLiteral("title"));
const QString KEY_LASTBUILDDATE(QStringLiteral("lastBuildDate"));
const QString KEY_ISLOADING(QStringLiteral("isLoading"));
const QString KEY_HASERROR(QStringLiteral("hasError"));
const QString KEY_ARTICLES(QStringLiteral("articles"));
using namespace RSS;
Feed::Feed(const QString &url, const QString &path, Session *session)
Feed::Feed(const QUuid &uid, const QString &url, const QString &path, Session *session)
: Item(path)
, m_session(session)
, m_uid(uid)
, m_url(url)
{
m_dataFileName = QString("%1.json").arg(Utils::Fs::toValidFileSystemName(m_url, false, QLatin1String("_")));
m_dataFileName = QString::fromLatin1(m_uid.toRfc4122().toHex()) + QLatin1String(".json");
// Move to new file naming scheme (since v4.1.2)
const QString legacyFilename {Utils::Fs::toValidFileSystemName(m_url, false, QLatin1String("_"))
+ QLatin1String(".json")};
const QDir storageDir {m_session->dataFileStorage()->storageDir()};
if (!QFile::exists(storageDir.absoluteFilePath(m_dataFileName)))
QFile::rename(storageDir.absoluteFilePath(legacyFilename), storageDir.absoluteFilePath(m_dataFileName));
m_parser = new Private::Parser(m_lastBuildDate);
m_parser->moveToThread(m_session->workingThread());
@@ -78,6 +90,8 @@ Feed::Feed(const QString &url, const QString &path, Session *session)
else
connect(m_session, &Session::processingStateChanged, this, &Feed::handleSessionProcessingEnabledChanged);
Net::DownloadManager::instance()->registerSequentialService(Net::ServiceID::fromURL(m_url));
load();
}
@@ -95,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();
@@ -117,7 +131,7 @@ void Feed::refresh()
// NOTE: Should we allow manually refreshing for disabled session?
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(m_url);
Net::DownloadHandler *handler = Net::DownloadManager::instance()->download({m_url});
connect(handler
, static_cast<void (Net::DownloadHandler::*)(const QString &, const QByteArray &)>(&Net::DownloadHandler::downloadFinished)
, this, &Feed::handleDownloadFinished);
@@ -127,6 +141,11 @@ void Feed::refresh()
emit stateChanged(this);
}
QUuid Feed::uid() const
{
return m_uid;
}
QString Feed::url() const
{
return m_url;
@@ -199,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);
@@ -280,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)
@@ -302,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"));
@@ -327,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());
@@ -342,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();
@@ -359,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)
@@ -401,12 +403,92 @@ void Feed::downloadIcon()
// XXX: This works for most sites but it is not perfect
const QUrl url(m_url);
auto iconUrl = QString("%1://%2/favicon.ico").arg(url.scheme(), url.host());
Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl(iconUrl, true);
Net::DownloadHandler *handler = Net::DownloadManager::instance()->download(
Net::DownloadRequest(iconUrl).saveToFile(true));
connect(handler
, static_cast<void (Net::DownloadHandler::*)(const QString &, const QString &)>(&Net::DownloadHandler::downloadFinished)
, 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) {
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())
continue;
// 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(localId.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;
@@ -414,25 +496,21 @@ QString Feed::iconPath() const
QJsonValue Feed::toJsonValue(bool withData) const
{
if (!withData) {
// if feed alias is empty we create "reduced" JSON
// value for it since its name is equal to its URL
return (name() == url() ? "" : url());
// if we'll need storing some more properties we should check
// for its default values and produce JSON object instead of (if it's required)
}
QJsonArray jsonArr;
foreach (Article *article, m_articles)
jsonArr << article->toJsonObject();
QJsonObject jsonObj;
jsonObj.insert(Str_Url, url());
jsonObj.insert(Str_Title, title());
jsonObj.insert(Str_LastBuildDate, lastBuildDate());
jsonObj.insert(Str_IsLoading, isLoading());
jsonObj.insert(Str_HasError, hasError());
jsonObj.insert(Str_Articles, jsonArr);
jsonObj.insert(KEY_UID, uid().toString());
jsonObj.insert(KEY_URL, url());
if (withData) {
jsonObj.insert(KEY_TITLE, title());
jsonObj.insert(KEY_LASTBUILDDATE, lastBuildDate());
jsonObj.insert(KEY_ISLOADING, isLoading());
jsonObj.insert(KEY_HASERROR, hasError());
QJsonArray jsonArr;
for (Article *article : asConst(m_articles))
jsonArr << article->toJsonObject();
jsonObj.insert(KEY_ARTICLES, jsonArr);
}
return jsonObj;
}

View File

@@ -33,6 +33,7 @@
#include <QBasicTimer>
#include <QHash>
#include <QList>
#include <QUuid>
#include "rss_item.h"
@@ -56,7 +57,7 @@ namespace RSS
friend class Session;
Feed(const QString &url, const QString &path, Session *session);
Feed(const QUuid &uid, const QString &url, const QString &path, Session *session);
~Feed() override;
public:
@@ -65,6 +66,7 @@ namespace RSS
void markAsRead() override;
void refresh() override;
QUuid uid() const;
QString url() const;
QString title() const;
QString lastBuildDate() const;
@@ -102,9 +104,11 @@ namespace RSS
void increaseUnreadCount();
void decreaseUnreadCount();
void downloadIcon();
int updateArticles(const QList<QVariantHash> &loadedArticles);
Session *m_session;
Private::Parser *m_parser;
const QUuid m_uid;
const QString m_url;
QString m_title;
QString m_lastBuildDate;

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